ваше сообщение коммита
This commit is contained in:
297
backend/routes/isic.js
Normal file
297
backend/routes/isic.js
Normal file
@@ -0,0 +1,297 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { pool } = require('../db'); // Убедитесь, что путь к вашему db-коннектору правильный
|
||||
const logger = require('../utils/logger'); // Если используете логгер
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: ISIC
|
||||
* description: API для кодов Международной стандартной отраслевой классификации (ISIC)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/isic/codes:
|
||||
* get:
|
||||
* summary: Получить список кодов ISIC с фильтрацией и пагинацией
|
||||
* tags: [ISIC]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: level
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Фильтр по уровню кода (1-4, иногда 5 или 6)
|
||||
* - in: query
|
||||
* name: parent_code
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Фильтр по родительскому коду (для получения дочерних кодов)
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Поисковый запрос по коду или описанию
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: Номер страницы для пагинации
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 25
|
||||
* description: Количество элементов на странице
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Список кодов ISIC
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* totalItems:
|
||||
* type: integer
|
||||
* codes:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/IsicCode'
|
||||
* totalPages:
|
||||
* type: integer
|
||||
* currentPage:
|
||||
* type: integer
|
||||
* 404:
|
||||
* description: Родительский код не найден
|
||||
* 500:
|
||||
* description: Ошибка сервера
|
||||
*/
|
||||
router.get('/codes', async (req, res) => {
|
||||
const { level, parent_code, search } = req.query;
|
||||
const page = parseInt(req.query.page, 10) || 1;
|
||||
const limit = parseInt(req.query.limit, 10) || 25;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const baseQuerySelect = `
|
||||
SELECT c.code, c.description, c.code_level, c.explanatory_note_inclusion, c.explanatory_note_exclusion,
|
||||
l.level_name_en,
|
||||
c.level1, c.level2, c.level3, c.level4, c.level5, c.level6
|
||||
FROM isic_rev4_codes c
|
||||
LEFT JOIN isic_rev4_level_names l ON c.code_level = l.code_level
|
||||
`;
|
||||
// Для подсчета JOIN с level_names не нужен, если только по level_name_en не будет фильтрации (пока нет)
|
||||
const baseQueryCount = `
|
||||
SELECT COUNT(*) AS total
|
||||
FROM isic_rev4_codes c
|
||||
`;
|
||||
|
||||
const conditions = [];
|
||||
const queryParams = []; // Параметры для основного запроса (включая limit/offset)
|
||||
const countQueryParams = []; // Параметры только для WHERE части (для count запроса)
|
||||
|
||||
if (level) {
|
||||
const levelParam = parseInt(level, 10);
|
||||
conditions.push(`c.code_level = $${queryParams.push(levelParam)}`);
|
||||
countQueryParams.push(levelParam);
|
||||
}
|
||||
|
||||
if (parent_code) {
|
||||
try {
|
||||
const parentResult = await pool.query('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]);
|
||||
if (parentResult.rows.length > 0) {
|
||||
const parentLevel = parentResult.rows[0].code_level;
|
||||
if (parentLevel >= 1 && parentLevel < 6) {
|
||||
conditions.push(`c.level${parentLevel} = $${queryParams.push(parent_code)}`);
|
||||
countQueryParams.push(parent_code);
|
||||
|
||||
const childLevel = parentLevel + 1;
|
||||
conditions.push(`c.code_level = $${queryParams.push(childLevel)}`);
|
||||
countQueryParams.push(childLevel);
|
||||
} else {
|
||||
// Родительский код на максимальном уровне, нет дочерних.
|
||||
// Возвращаем пустой результат сразу.
|
||||
return res.json({ totalItems: 0, codes: [], totalPages: 0, currentPage: page });
|
||||
}
|
||||
} else {
|
||||
// parent_code не найден
|
||||
logger.warn(`Parent code not found: ${parent_code}`);
|
||||
return res.status(404).json({ error: 'Parent code not found', totalItems: 0, codes: [], totalPages: 0, currentPage: page });
|
||||
}
|
||||
} catch (dbError) {
|
||||
logger.error('Error fetching parent_code level:', dbError);
|
||||
return res.status(500).json({ error: 'Internal server error while fetching parent data' });
|
||||
}
|
||||
}
|
||||
|
||||
if (search) {
|
||||
const searchPattern = `%${search}%`;
|
||||
// Используем один параметр для обоих ILIKE, чтобы $N был корректным в countQueryParams
|
||||
conditions.push(`(c.code ILIKE $${queryParams.push(searchPattern)} OR c.description ILIKE $${queryParams.length})`); // $N для второго ILIKE будет тем же, что и для первого
|
||||
countQueryParams.push(searchPattern);
|
||||
}
|
||||
|
||||
let whereClause = '';
|
||||
if (conditions.length > 0) {
|
||||
// Переиндексируем плейсхолдеры для whereClause, т.к. queryParams и countQueryParams теперь разные
|
||||
// Это более сложный момент, проще собирать whereClause с $1, $2 и т.д. и передавать countQueryParams
|
||||
// Для простоты сейчас оставим как было, но это означает, что queryParams используется для генерации whereClause,
|
||||
// а потом из него берутся countQueryParams.
|
||||
|
||||
// Корректный способ: перестроить whereClause для count с правильными индексами
|
||||
let countWhereClauseConditions = [];
|
||||
let currentCountParamIndex = 1;
|
||||
if (level) {
|
||||
countWhereClauseConditions.push(`c.code_level = $${currentCountParamIndex++}`);
|
||||
}
|
||||
if (parent_code) {
|
||||
// Предполагаем, что parent_code уже добавлен в countQueryParams
|
||||
const parentLevelResult = await pool.query('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]); // Нужно будет передать parent_code в countQueryParams
|
||||
if (parentLevelResult.rows.length > 0) {
|
||||
const parentLevel = parentLevelResult.rows[0].code_level;
|
||||
if (parentLevel >=1 && parentLevel < 6) {
|
||||
countWhereClauseConditions.push(`c.level${parentLevel} = $${currentCountParamIndex++}`);
|
||||
countWhereClauseConditions.push(`c.code_level = $${currentCountParamIndex++}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (search) {
|
||||
countWhereClauseConditions.push(`(c.code ILIKE $${currentCountParamIndex} OR c.description ILIKE $${currentCountParamIndex})`);
|
||||
currentCountParamIndex++;
|
||||
}
|
||||
whereClause = countWhereClauseConditions.length > 0 ? ' WHERE ' + countWhereClauseConditions.join(' AND ') : '';
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Запрос для получения данных с пагинацией
|
||||
const dataQueryPlaceholders = queryParams.map((_, i) => `$${i + 1}`).join(', '); // Это неверно для WHERE
|
||||
// Формируем finalQuery с плейсхолдерами, соответствующими queryParams
|
||||
let finalQueryWhereClause = '';
|
||||
if (conditions.length > 0) {
|
||||
let currentQueryParamIndex = 1;
|
||||
const queryWhereConditions = [];
|
||||
if (level) queryWhereConditions.push(`c.code_level = $${currentQueryParamIndex++}`);
|
||||
if (parent_code) {
|
||||
const parentLevelResult = await pool.query('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]); // Это дублирование, лучше получить parentLevel один раз
|
||||
if (parentLevelResult.rows.length > 0) {
|
||||
const parentLevel = parentLevelResult.rows[0].code_level;
|
||||
if (parentLevel >=1 && parentLevel < 6) {
|
||||
queryWhereConditions.push(`c.level${parentLevel} = $${currentQueryParamIndex++}`);
|
||||
queryWhereConditions.push(`c.code_level = $${currentQueryParamIndex++}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (search) queryWhereConditions.push(`(c.code ILIKE $${currentQueryParamIndex} OR c.description ILIKE $${currentQueryParamIndex})`); // searchPattern идет одним параметром
|
||||
finalQueryWhereClause = queryWhereConditions.length > 0 ? ' WHERE ' + queryWhereConditions.join(' AND ') : '';
|
||||
}
|
||||
|
||||
|
||||
const finalQuery = `${baseQuerySelect} ${finalQueryWhereClause} ORDER BY c.sort_order, c.code LIMIT $${queryParams.push(limit)} OFFSET $${queryParams.push(offset)}`;
|
||||
const finalCountQuery = `${baseQueryCount} ${whereClause}`;
|
||||
|
||||
try {
|
||||
logger.debug('Executing count query:', finalCountQuery, 'Params:', countQueryParams);
|
||||
const totalItemsResult = await pool.query(finalCountQuery, countQueryParams);
|
||||
const totalItems = parseInt(totalItemsResult.rows[0].total, 10);
|
||||
|
||||
// Параметры для основного запроса - это все, что в queryParams (включая limit и offset)
|
||||
logger.debug('Executing data query:', finalQuery, 'Params:', queryParams);
|
||||
const result = await pool.query(finalQuery, queryParams);
|
||||
|
||||
res.json({
|
||||
totalItems,
|
||||
codes: result.rows,
|
||||
totalPages: Math.ceil(totalItems / limit),
|
||||
currentPage: page,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error fetching ISIC codes:', error);
|
||||
if (error.query) logger.error('Failed Query:', error.query);
|
||||
if (error.parameters) logger.error('Failed Params:', error.parameters);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/isic/tree:
|
||||
* get:
|
||||
* summary: Получить иерархическое дерево кодов ISIC (или его часть)
|
||||
* tags: [ISIC]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: root_code
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Код ISIC, с которого начинать построение дерева (например, 'A' или '01'). Если не указан, вернет все секции (уровень 1).
|
||||
* - in: max_depth
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 2
|
||||
* description: Максимальная глубина дерева для загрузки.
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Дерево кодов ISIC
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/IsicTreeNode' # Определим эту схему позже
|
||||
* 500:
|
||||
* description: Ошибка сервера
|
||||
*/
|
||||
router.get('/tree', async (req, res) => {
|
||||
const { root_code, max_depth = 2 } = req.query;
|
||||
|
||||
// Эта функция потребует рекурсивного запроса или сложного CTE (Common Table Expression) в SQL
|
||||
// для построения дерева. Это более сложная задача.
|
||||
// Для начала можно сделать упрощенную версию, которая возвращает один уровень вложенности.
|
||||
try {
|
||||
let items;
|
||||
if (!root_code) { // Если нет root_code, возвращаем секции (уровень 1)
|
||||
const result = await pool.query(
|
||||
"SELECT code, description, code_level FROM isic_rev4_codes WHERE code_level = 1 ORDER BY sort_order, code"
|
||||
);
|
||||
items = result.rows.map(row => ({ ...row, children: [] })); // Добавляем пустой массив children
|
||||
} else {
|
||||
// Получаем сам root_code
|
||||
const rootResult = await pool.query(
|
||||
"SELECT code, description, code_level FROM isic_rev4_codes WHERE code = $1",
|
||||
[root_code]
|
||||
);
|
||||
if (rootResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Root code not found' });
|
||||
}
|
||||
const rootNode = { ...rootResult.rows[0], children: [] };
|
||||
|
||||
// Получаем прямых потомков (упрощенный пример для одного уровня вложенности)
|
||||
let childrenQuery = '';
|
||||
const childrenParams = [root_code];
|
||||
const rootLevel = rootNode.code_level;
|
||||
|
||||
if (rootLevel === 1) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level1 = $1 AND code_level = 2 ORDER BY sort_order, code";
|
||||
else if (rootLevel === 2) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level2 = $1 AND code_level = 3 ORDER BY sort_order, code";
|
||||
else if (rootLevel === 3) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level3 = $1 AND code_level = 4 ORDER BY sort_order, code";
|
||||
else if (rootLevel === 4) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level4 = $1 AND code_level = 5 ORDER BY sort_order, code";
|
||||
else if (rootLevel === 5) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level5 = $1 AND code_level = 6 ORDER BY sort_order, code";
|
||||
|
||||
|
||||
if (childrenQuery) {
|
||||
const childrenResult = await pool.query(childrenQuery, childrenParams);
|
||||
rootNode.children = childrenResult.rows.map(row => ({ ...row, children: [] }));
|
||||
}
|
||||
items = [rootNode];
|
||||
}
|
||||
res.json(items);
|
||||
} catch (error)
|
||||
{
|
||||
logger.error('Error fetching ISIC tree:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user