From 8ce1d8b8e4ccfd8ac1e545fc4366118400dd3c3e Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 24 Dec 2025 16:46:10 +0530 Subject: [PATCH] Added API: getManufacturingMonthlyOverview --- .../manufacturingIPI.controller.js | 150 ++++++++++++++++++ app/controllers/products.controller.js | 8 +- app/models/index.js | 4 +- app/models/isic2DigitIndices.model.js | 59 +++++++ app/models/isic3DigitIndices.model.js | 59 +++++++ app/models/isic4DigitIndices.model.js | 59 +++++++ app/routes/routes.js | 35 ++++ 7 files changed, 369 insertions(+), 5 deletions(-) create mode 100644 app/models/isic2DigitIndices.model.js create mode 100644 app/models/isic3DigitIndices.model.js create mode 100644 app/models/isic4DigitIndices.model.js diff --git a/app/controllers/manufacturingIPI.controller.js b/app/controllers/manufacturingIPI.controller.js index 8d80711..1a0937d 100644 --- a/app/controllers/manufacturingIPI.controller.js +++ b/app/controllers/manufacturingIPI.controller.js @@ -1,5 +1,8 @@ const db = require("../models"); const ManufacturingIpi = db.ManufacturingIpi; +const Isic2DigitIndices = db.Isic2DigitIndices; +const Isic3DigitIndices = db.Isic3DigitIndices; +const Isic4DigitIndices = db.Isic4DigitIndices; const logger = require("../services/logger"); const formatDecimal = (value) => { @@ -38,6 +41,153 @@ exports.getAllManufacturingIndexDetails = async (req, res) => { } catch (error) { logger.error(`getAllManufacturingIndexDetails API: ${error.message}`); + logger.error(`Stack trace: ${error.stack}`); res.status(500).send({ status: "failed", message: "Internal server error" }); } +}; + +exports.getManufacturingMonthlyOverviewByYearMonth = async (req, res) => { + try { + const { year, month } = req.query; + + // Validate parameters exist + if (!year || !month) { + logger.warn('getManufacturingMonthlyOverview API: Missing required parameters'); + return res.status(400).send({ + status: "failed", + message: "Year and month are required" + }); + } + + // Sanitize and validate input - prevent SQL injection + const yearInt = parseInt(year, 10); + const monthInt = parseInt(month, 10); + + // Validate that parsing was successful + if (isNaN(yearInt) || isNaN(monthInt)) { + logger.warn(`getManufacturingMonthlyOverview API: Invalid input - year: ${year}, month: ${month}`); + return res.status(400).send({ + status: "failed", + message: "Year and month must be valid numbers" + }); + } + + // Validate year range (reasonable bounds) + if (yearInt < 2000 || yearInt > 2100) { + return res.status(400).send({ + status: "failed", + message: "Year must be between 2000 and 2100" + }); + } + + // Validate month range + if (monthInt < 1 || monthInt > 12) { + return res.status(400).send({ + status: "failed", + message: "Month must be between 1 and 12" + }); + } + + // Common where clause + const whereClause = { + year: yearInt, + month: monthInt + }; + + // Fetch data from all tables in parallel + const [manufacturingData, isic2DigitData, isic3DigitData, isic4DigitData] = await Promise.all([ + ManufacturingIpi.findOne({ + attributes: ['manufacturing_index', 'mom_change', 'yoy_change'], + where: whereClause + }), + Isic2DigitIndices.findAll({ + attributes: ['isic_2digit_code', 'isic_description', 'total_weight', 'weighted_index_sum', 'isic_2digit_index'], + where: whereClause, + order: [['isic_2digit_code', 'ASC']] + }), + Isic3DigitIndices.findAll({ + attributes: ['isic_3digit_code', 'isic_description', 'total_weight', 'weighted_index_sum', 'isic_3digit_index'], + where: whereClause, + order: [['isic_3digit_code', 'ASC']] + }), + Isic4DigitIndices.findAll({ + attributes: ['isic_4digit_code', 'isic_description', 'total_weight', 'weighted_index_sum', 'isic_4digit_index'], + where: whereClause, + order: [['isic_4digit_code', 'ASC']] + }) + ]); + + // Check if manufacturing data exists (main data) + if (!manufacturingData) { + logger.info(`getManufacturingMonthlyOverview API: No data found for year ${yearInt}, month ${monthInt}`); + return res.status(404).send({ + status: "failed", + message: "No data found for the specified period" + }); + } + + // Format Manufacturing IPI data + const plainManufacturing = manufacturingData.get({ plain: true }); + const formattedManufacturing = { + manufacturing_index: formatDecimal(plainManufacturing.manufacturing_index), + mom_change: formatDecimal(plainManufacturing.mom_change), + yoy_change: formatDecimal(plainManufacturing.yoy_change), + }; + + // Format ISIC 2-digit data + const formattedIsic2Digit = isic2DigitData.map(item => { + const plainItem = item.get({ plain: true }); + return { + isic_2digit_code: plainItem.isic_2digit_code, + isic_description: plainItem.isic_description, + total_weight: formatDecimal(plainItem.total_weight), + weighted_index_sum: formatDecimal(plainItem.weighted_index_sum), + isic_2digit_index: formatDecimal(plainItem.isic_2digit_index), + }; + }); + + // Format ISIC 3-digit data + const formattedIsic3Digit = isic3DigitData.map(item => { + const plainItem = item.get({ plain: true }); + return { + isic_3digit_code: plainItem.isic_3digit_code, + isic_description: plainItem.isic_description, + total_weight: formatDecimal(plainItem.total_weight), + weighted_index_sum: formatDecimal(plainItem.weighted_index_sum), + isic_3digit_index: formatDecimal(plainItem.isic_3digit_index), + }; + }); + + // Format ISIC 4-digit data + const formattedIsic4Digit = isic4DigitData.map(item => { + const plainItem = item.get({ plain: true }); + return { + isic_4digit_code: plainItem.isic_4digit_code, + isic_description: plainItem.isic_description, + total_weight: formatDecimal(plainItem.total_weight), + weighted_index_sum: formatDecimal(plainItem.weighted_index_sum), + isic_4digit_index: formatDecimal(plainItem.isic_4digit_index), + }; + }); + + logger.info(`getManufacturingMonthlyOverview API: Successfully fetched data for year ${yearInt}, month ${monthInt}`); + + res.status(200).send({ + status: "success", + message: "Fetched successfully", + manufacturing_total: formattedManufacturing, + isic_2digit: formattedIsic2Digit, + isic_3digit: formattedIsic3Digit, + isic_4digit: formattedIsic4Digit + + }); + + } catch (error) { + logger.error(`getManufacturingMonthlyOverview API Error: ${error.message}`); + logger.error(`Stack trace: ${error.stack}`); + res.status(500).send({ + status: "failed", + message: "Internal server error" + }); + } }; \ No newline at end of file diff --git a/app/controllers/products.controller.js b/app/controllers/products.controller.js index a1be7d8..7e59582 100644 --- a/app/controllers/products.controller.js +++ b/app/controllers/products.controller.js @@ -118,7 +118,7 @@ exports.getAllProducts = async (req, res) => { }); } catch (error) { - logger.error(err.message); + logger.error(error.message); res.status(500).send({ status: "failed", message: "Internal server error" }); } }; @@ -133,7 +133,7 @@ exports.getProductById = async (req, res) => { res.status(200).send({'status':"success",'message':"Fetched successfully",'data': data }); } catch (error) { - logger.error(err.message); + logger.error(error.message); res.status(500).send({'status':"failed",'message': "Internal server error" }); } }; @@ -170,7 +170,7 @@ exports.deleteProduct = async (req, res) => { res.status(200).send({'status':"success",'message':"Deleted successfully",'data': "" }); } catch (error) { - logger.error(err.message); + logger.error(error.message); res.status(500).send({'status':"failed",'message': "Internal server error" }); } }; @@ -181,7 +181,7 @@ exports.downloadProductSample = async (req, res) => { const filePath = path.join(__dirname, "../downloads_csv/products_upload_sample.csv"); return res.download(filePath, "products_upload_sample.csv"); } catch (error) { - logger.error(err.message); + logger.error(error.message); res.status(500).send({'status':"failed",'message': "Internal server error" }); } }; diff --git a/app/models/index.js b/app/models/index.js index 0acb42c..1868525 100644 --- a/app/models/index.js +++ b/app/models/index.js @@ -30,7 +30,9 @@ db.Emirate = require("./emirate.model")(sequelize, DataTypes); db.CityTown = require("./cityTown.model")(sequelize, DataTypes); db.SubmissionHistory = require("./submissionHistory.model")(sequelize, DataTypes); db.ManufacturingIpi = require("./manufacturingIPI.model.js")(sequelize, DataTypes); - +db.Isic2DigitIndices = require("./isic2DigitIndices.model.js")(sequelize, DataTypes); +db.Isic3DigitIndices = require("./isic3DigitIndices.model.js")(sequelize, DataTypes); +db.Isic4DigitIndices = require("./isic4DigitIndices.model.js")(sequelize, DataTypes); // Associations db.Establishment.hasMany(db.EstablishmentUser, { foreignKey: "establishment_id", diff --git a/app/models/isic2DigitIndices.model.js b/app/models/isic2DigitIndices.model.js new file mode 100644 index 0000000..76d9a00 --- /dev/null +++ b/app/models/isic2DigitIndices.model.js @@ -0,0 +1,59 @@ +module.exports = (sequelize, DataTypes) => { + const Isic2DigitIndices = sequelize.define("isic_2digit_indices", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + isic_2digit_code: { + type: DataTypes.STRING, + allowNull: false, + }, + isic_description: { + type: DataTypes.STRING, + allowNull: true, + validate: { + len: { + args: [0, 1000], + msg: "Description is too long. Maximum allowed length is 1000 characters." + }} + }, + year: { + type: DataTypes.INTEGER, + allowNull: false, + }, + month: { + type: DataTypes.INTEGER, + allowNull: false, + }, + month_name: { + type: DataTypes.STRING, + allowNull: false, + }, + total_weight: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false, + }, + weighted_index_sum: { + type: DataTypes.DECIMAL(10, 4), + allowNull: false, + }, + isic_2digit_index: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + }, + }, { + tableName: "isic_2digit_indices", + timestamps: false, + }); + + return Isic2DigitIndices; +}; \ No newline at end of file diff --git a/app/models/isic3DigitIndices.model.js b/app/models/isic3DigitIndices.model.js new file mode 100644 index 0000000..da70f28 --- /dev/null +++ b/app/models/isic3DigitIndices.model.js @@ -0,0 +1,59 @@ +module.exports = (sequelize, DataTypes) => { + const Isic3DigitIndices = sequelize.define("isic_3digit_indices", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + isic_3digit_code: { + type: DataTypes.STRING, + allowNull: false, + }, + isic_description: { + type: DataTypes.STRING, + allowNull: true, + validate: { + len: { + args: [0, 1000], + msg: "Description is too long. Maximum allowed length is 1000 characters." + }} + }, + year: { + type: DataTypes.INTEGER, + allowNull: false, + }, + month: { + type: DataTypes.INTEGER, + allowNull: false, + }, + month_name: { + type: DataTypes.STRING, + allowNull: false, + }, + total_weight: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false, + }, + weighted_index_sum: { + type: DataTypes.DECIMAL(10, 4), + allowNull: false, + }, + isic_3digit_index: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + }, + }, { + tableName: "isic_3digit_indices", + timestamps: false, + }); + + return Isic3DigitIndices; +}; \ No newline at end of file diff --git a/app/models/isic4DigitIndices.model.js b/app/models/isic4DigitIndices.model.js new file mode 100644 index 0000000..daa65af --- /dev/null +++ b/app/models/isic4DigitIndices.model.js @@ -0,0 +1,59 @@ +module.exports = (sequelize, DataTypes) => { + const Isic4DigitIndices = sequelize.define("isic_4digit_indices", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + isic_4digit_code: { + type: DataTypes.STRING, + allowNull: false, + }, + isic_description: { + type: DataTypes.STRING, + allowNull: true, + validate: { + len: { + args: [0, 1000], + msg: "Description is too long. Maximum allowed length is 1000 characters." + }} + }, + year: { + type: DataTypes.INTEGER, + allowNull: false, + }, + month: { + type: DataTypes.INTEGER, + allowNull: false, + }, + month_name: { + type: DataTypes.STRING, + allowNull: false, + }, + total_weight: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false, + }, + weighted_index_sum: { + type: DataTypes.DECIMAL(10, 4), + allowNull: false, + }, + isic_4digit_index: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + }, + }, { + tableName: "isic_4digit_indices", + timestamps: false, + }); + + return Isic4DigitIndices; +}; \ No newline at end of file diff --git a/app/routes/routes.js b/app/routes/routes.js index e017150..ba7831e 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -3013,6 +3013,41 @@ router.get("/unit_master_download_sample_file",[verifySignature, verifyToken], u */ router.get("/manufacturing/getManufacturingIndex",[verifySignature, verifyToken], ManufacturingIpiController.getAllManufacturingIndexDetails); +/** + * @swagger + * /api/manufacturing/getManufacturingMonthlyOverview + * get: + * summary: Get Manufacturing IPI Monthly Overview by year and month + * tags: [ManufacturingIPI] + * security: + * - appSignature: [] + * - CSRF: [] + * cookieAuth: [] + * parameters: + * - name: year + * in: query + * required: true + * schema: + * type: integer + * description: Year (e.g., 2025) + * - name: month + * in: query + * required: true + * schema: + * type: integer + * description: Month (1-12) + * responses: + * 200: + * description: Manufacturing IPI Monthly Overview fetched successfully + * 400: + * description: Invalid parameters + * 404: + * description: Data not found + * 500: + * description: Server error + */ +router.get("/manufacturing/getManufacturingMonthlyOverview", [verifySignature, verifyToken], ManufacturingIpiController.getManufacturingMonthlyOverviewByYearMonth); +