From 0df0d647dd2e76a2c151e48ff724b6ed59d30d74 Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Tue, 28 Oct 2025 12:29:13 +0530 Subject: [PATCH] GWM : submission data updated --- app/controllers/submission.controller.js | 260 +++++++++++++++++++---- app/models/submissionProduct.model.js | 28 +++ app/routes/routes.js | 60 +++++- app/services/quarterService.js | 73 +++++++ 4 files changed, 373 insertions(+), 48 deletions(-) create mode 100644 app/services/quarterService.js diff --git a/app/controllers/submission.controller.js b/app/controllers/submission.controller.js index 73287fe..4559502 100644 --- a/app/controllers/submission.controller.js +++ b/app/controllers/submission.controller.js @@ -10,6 +10,7 @@ const Product = db.Product; const { Op } = require("sequelize"); const { Sequelize } = require("sequelize"); const { sendEmail } = require("../services/email.service"); +const { getQuarterPeriods } = require("../services/quarterService"); @@ -23,8 +24,59 @@ exports.createSubmission = async (req, res) => { const submission = await Submission.create(submissionData); if (products.length) { - products.forEach((p) => (p.submission_id = submission.id)); - await SubmissionProduct.bulkCreate(products); + // products.forEach((p) => (p.submission_id = submission.id)); + // await SubmissionProduct.bulkCreate(products); + + // Process products before insert + const processedProducts = products.map((p) => { + const num = (v) => (isNaN(parseFloat(v)) ? 0 : parseFloat(v)); // safe number conversion + + // Calculate totals for each section + const previous_quantity = + num(p.previous_quantity_period_one) + + num(p.previous_quantity_period_two) + + num(p.previous_quantity_period_three); + + const previous_cost = + num(p.previous_cost_period_one) + + num(p.previous_cost_period_two) + + num(p.previous_cost_period_three); + + const current_quantity = + num(p.current_quantity_period_one) + + num(p.current_quantity_period_two) + + num(p.current_quantity_period_three); + + const current_cost = + num(p.current_cost_period_one) + + num(p.current_cost_period_two) + + num(p.current_cost_period_three); + + const forecast_quantity = + num(p.forecast_quantity_period_one) + + num(p.forecast_quantity_period_two) + + num(p.forecast_quantity_period_three); + + const forecast_cost = + num(p.forecast_cost_period_one) + + num(p.forecast_cost_period_two) + + num(p.forecast_cost_period_three); + + return { + ...p, + submission_id: submission.id, + previous_quantity: previous_quantity.toString(), + previous_cost: previous_cost.toString(), + current_quantity: current_quantity.toString(), + current_cost: current_cost.toString(), + forecast_quantity: forecast_quantity.toString(), + forecast_cost: forecast_cost.toString(), + }; + }); + + // Bulk insert products + await SubmissionProduct.bulkCreate(processedProducts); + } const result = await Submission.findByPk(submission.id, { @@ -38,58 +90,150 @@ exports.createSubmission = async (req, res) => { } }; -exports.updateSubmission = async (req, res) => { - try { - const { id } = req.params; - const { products = [], ...submissionData } = req.body; +// exports.updateSubmission = async (req, res) => { +// try { +// const { id } = req.params; +// const { products = [], ...submissionData } = req.body; - // Check submission exists - const submission = await Submission.findByPk(id); - if (!submission) - return res.status(404).json({ status: "failed", message: "Submission not found" }); +// // Check submission exists +// const submission = await Submission.findByPk(id); +// if (!submission) +// return res.status(404).json({ status: "failed", message: "Submission not found" }); - // Update submission main data - await submission.update(submissionData); +// // Update submission main data +// await submission.update(submissionData); - // Process products - for (const product of products) { - if (product.id) { - // If product ID exists, update - const existingProduct = await SubmissionProduct.findOne({ - where: { id: product.id, submission_id: id }, - }); +// // Process products +// for (const product of products) { +// if (product.id) { +// // If product ID exists, update +// const existingProduct = await SubmissionProduct.findOne({ +// where: { id: product.id, submission_id: id }, +// }); - if (existingProduct) { - await existingProduct.update(product); - } else { - // fallback: if given ID not found, create new - product.submission_id = id; - await SubmissionProduct.create(product); - } - } else { - // If product has no ID → create new record - product.submission_id = id; - await SubmissionProduct.create(product); - } - } +// if (existingProduct) { +// await existingProduct.update(product); +// } else { +// // fallback: if given ID not found, create new +// product.submission_id = id; +// await SubmissionProduct.create(product); +// } +// } else { +// // If product has no ID → create new record +// product.submission_id = id; +// await SubmissionProduct.create(product); +// } +// } - // Get updated full record - const updatedSubmission = await Submission.findByPk(id, { - include: [{ model: SubmissionProduct, as: "products" }], - }); +// // Get updated full record +// const updatedSubmission = await Submission.findByPk(id, { +// include: [{ model: SubmissionProduct, as: "products" }], +// }); - res.status(200).json({ - status: "success", - message: "Submission updated successfully", - data: updatedSubmission, - }); +// res.status(200).json({ +// status: "success", +// message: "Submission updated successfully", +// data: updatedSubmission, +// }); - } catch (err) { - console.error(err); - res.status(500).json({ status: "failed", message: err.message }); - } - }; +// } catch (err) { +// console.error(err); +// res.status(500).json({ status: "failed", message: err.message }); +// } +// }; +exports.updateSubmission = async (req, res) => { + try { + const { id } = req.params; + const { products = [], ...submissionData } = req.body; + + // Check if submission exists + const submission = await Submission.findByPk(id); + if (!submission) + return res.status(404).json({ status: "failed", message: "Submission not found" }); + + // Update main submission record + await submission.update(submissionData); + + // Helper: safe numeric conversion + const num = (v) => (isNaN(parseFloat(v)) ? 0 : parseFloat(v)); + + // Loop through products + for (const p of products) { + // --- Auto-calculate totals --- + const previous_quantity = + num(p.previous_quantity_period_one) + + num(p.previous_quantity_period_two) + + num(p.previous_quantity_period_three); + + const previous_cost = + num(p.previous_cost_period_one) + + num(p.previous_cost_period_two) + + num(p.previous_cost_period_three); + + const current_quantity = + num(p.current_quantity_period_one) + + num(p.current_quantity_period_two) + + num(p.current_quantity_period_three); + + const current_cost = + num(p.current_cost_period_one) + + num(p.current_cost_period_two) + + num(p.current_cost_period_three); + + const forecast_quantity = + num(p.forecast_quantity_period_one) + + num(p.forecast_quantity_period_two) + + num(p.forecast_quantity_period_three); + + const forecast_cost = + num(p.forecast_cost_period_one) + + num(p.forecast_cost_period_two) + + num(p.forecast_cost_period_three); + + // Attach calculated fields + const updatedProductData = { + ...p, + submission_id: id, + previous_quantity: previous_quantity.toString(), + previous_cost: previous_cost.toString(), + current_quantity: current_quantity.toString(), + current_cost: current_cost.toString(), + forecast_quantity: forecast_quantity.toString(), + forecast_cost: forecast_cost.toString(), + }; + + // --- Update or Create --- + if (p.id) { + const existingProduct = await SubmissionProduct.findOne({ + where: { id: p.id, submission_id: id }, + }); + + if (existingProduct) { + await existingProduct.update(updatedProductData); + } else { + await SubmissionProduct.create(updatedProductData); + } + } else { + await SubmissionProduct.create(updatedProductData); + } + } + + // Fetch updated data with associations + const updatedSubmission = await Submission.findByPk(id, { + include: [{ model: SubmissionProduct, as: "products" }], + }); + + res.status(200).json({ + status: "success", + message: "Submission updated successfully", + data: updatedSubmission, + }); + } catch (err) { + console.error(err); + res.status(500).json({ status: "failed", message: err.message }); + } +}; exports.submissionHistory = async (req, res) => { @@ -185,10 +329,12 @@ exports.viewSubmissionDetails = async (req, res) => { ], }); + const quarter_periods = getQuarterPeriods(Number(data.year), data.quarter); + if (!data) return res.status(404).json({ status: "failed", message: "Not found" }); - res.status(200).json({ status: "success", data }); + res.status(200).json({ status: "success", data , quarter_periods }); } catch (err) { res.status(500).json({ status: "failed", message: err.message }); @@ -281,3 +427,23 @@ exports.approveOrRejectSubmission = async (req, res) => { res.status(500).json({ status: "failed", message: err.message }); } }; + + + +exports.getQuarterPeriods = async (req, res) => { + try { + const { current_year, current_quarter } = req.body; + + if (!current_year || !current_quarter) { + return res + .status(400) + .json({ status: "failed", message: "current_year and current_quarter are required" }); + } + + const quarter_periods = getQuarterPeriods(Number(current_year), current_quarter); + + return res.status(200).json({ status: "success", data: quarter_periods }); + } catch (err) { + return res.status(500).json({ status: "failed", message: err.message }); + } +}; diff --git a/app/models/submissionProduct.model.js b/app/models/submissionProduct.model.js index c31c0a1..4b45584 100644 --- a/app/models/submissionProduct.model.js +++ b/app/models/submissionProduct.model.js @@ -12,6 +12,34 @@ module.exports = (sequelize, DataTypes) => { product_id: DataTypes.INTEGER, unit_id: DataTypes.INTEGER, annual_installed_capacity: DataTypes.STRING(100), + + // --- Previous Periods --- + previous_quantity_period_one: { type: DataTypes.STRING(100), allowNull: true }, + previous_quantity_period_two: { type: DataTypes.STRING(100), allowNull: true }, + previous_quantity_period_three: { type: DataTypes.STRING(100), allowNull: true }, + + previous_cost_period_one: { type: DataTypes.STRING(100), allowNull: true }, + previous_cost_period_two: { type: DataTypes.STRING(100), allowNull: true }, + previous_cost_period_three: { type: DataTypes.STRING(100), allowNull: true }, + + // --- Current Periods --- + current_quantity_period_one: { type: DataTypes.STRING(100), allowNull: true }, + current_quantity_period_two: { type: DataTypes.STRING(100), allowNull: true }, + current_quantity_period_three: { type: DataTypes.STRING(100), allowNull: true }, + + current_cost_period_one: { type: DataTypes.STRING(100), allowNull: true }, + current_cost_period_two: { type: DataTypes.STRING(100), allowNull: true }, + current_cost_period_three: { type: DataTypes.STRING(100), allowNull: true }, + + // --- Forecast Periods --- + forecast_quantity_period_one: { type: DataTypes.STRING(100), allowNull: true }, + forecast_quantity_period_two: { type: DataTypes.STRING(100), allowNull: true }, + forecast_quantity_period_three: { type: DataTypes.STRING(100), allowNull: true }, + + forecast_cost_period_one: { type: DataTypes.STRING(100), allowNull: true }, + forecast_cost_period_two: { type: DataTypes.STRING(100), allowNull: true }, + forecast_cost_period_three: { type: DataTypes.STRING(100), allowNull: true }, + previous_quantity: DataTypes.STRING(100), previous_cost: DataTypes.STRING(100), current_quantity: DataTypes.STRING(100), diff --git a/app/routes/routes.js b/app/routes/routes.js index 519d20f..9dcbe5b 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -1545,6 +1545,24 @@ router.delete("/unit_master/:id",[verifySignature, verifyToken], unitMasterContr * product_id: { type: integer } * unit_id: { type: integer } * annual_installed_capacity: { type: string } + * previous_quantity_period_one: { type: string } + * previous_quantity_period_two: { type: string } + * previous_quantity_period_three: { type: string } + * previous_cost_period_one: { type: string } + * previous_cost_period_two: { type: string } + * previous_cost_period_three: { type: string } + * current_quantity_period_one: { type: string } + * current_quantity_period_two: { type: string } + * current_quantity_period_three: { type: string } + * current_cost_period_one: { type: string } + * current_cost_period_two: { type: string } + * current_cost_period_three: { type: string } + * forecast_quantity_period_one: { type: string } + * forecast_quantity_period_two: { type: string } + * forecast_quantity_period_three: { type: string } + * forecast_cost_period_one: { type: string } + * forecast_cost_period_two: { type: string } + * forecast_cost_period_three: { type: string } * previous_quantity: { type: string } * previous_cost: { type: string } * current_quantity: { type: string } @@ -1600,6 +1618,24 @@ router.post("/submissions",[verifySignature, verifyToken], submissionController. * product_id: { type: integer } * unit_id: { type: integer } * annual_installed_capacity: { type: string } + * previous_quantity_period_one: { type: string } + * previous_quantity_period_two: { type: string } + * previous_quantity_period_three: { type: string } + * previous_cost_period_one: { type: string } + * previous_cost_period_two: { type: string } + * previous_cost_period_three: { type: string } + * current_quantity_period_one: { type: string } + * current_quantity_period_two: { type: string } + * current_quantity_period_three: { type: string } + * current_cost_period_one: { type: string } + * current_cost_period_two: { type: string } + * current_cost_period_three: { type: string } + * forecast_quantity_period_one: { type: string } + * forecast_quantity_period_two: { type: string } + * forecast_quantity_period_three: { type: string } + * forecast_cost_period_one: { type: string } + * forecast_cost_period_two: { type: string } + * forecast_cost_period_three: { type: string } * previous_quantity: { type: string } * previous_cost: { type: string } * current_quantity: { type: string } @@ -1760,7 +1796,29 @@ router.put("/enableOrDisableSubmissionEditAccess/:id",[verifySignature, verifyTo router.put("/approveOrRejectSubmission/:id",[verifySignature, verifyToken], submissionController.approveOrRejectSubmission); - +/** + * @swagger + * /api/getQuarterPeriods: + * post: + * summary: get Quarter Periods based on current quarter and year + * tags: [Submissions] + * security: + * - appSignature: [] + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * current_year: { type: integer } + * current_quarter: { type: string } + * responses: + * 200: + * description: Updated successfully + */ +router.post("/getQuarterPeriods",[verifySignature, verifyToken], submissionController.getQuarterPeriods); diff --git a/app/services/quarterService.js b/app/services/quarterService.js new file mode 100644 index 0000000..dfc5368 --- /dev/null +++ b/app/services/quarterService.js @@ -0,0 +1,73 @@ +// services/quarterService.js + +/** + * Get Quarter and Month details based on current quarter and financial year + * @param {number} currentYear - e.g. 2025 + * @param {string} currentQuarter - e.g. 'Q4' + * @returns {object} - structured period data + */ +exports.getQuarterPeriods = (currentYear, currentQuarter) => { + // Define quarters based on financial year (Apr–Mar) + const quarters = { + Q1: ["Apr", "May", "Jun"], + Q2: ["Jul", "Aug", "Sep"], + Q3: ["Oct", "Nov", "Dec"], + Q4: ["Jan", "Feb", "Mar"], + }; + + const allQuarters = ["Q1", "Q2", "Q3", "Q4"]; + const currentIndex = allQuarters.indexOf(currentQuarter); + + if (currentIndex === -1) { + throw new Error("Invalid quarter. Use one of: Q1, Q2, Q3, Q4"); + } + + // --- Previous Quarter --- + let previousQuarter = allQuarters[(currentIndex - 1 + 4) % 4]; + let previousYear = currentYear; + // If current quarter is Q1, previous year is same financial year → currentYear + // But Q1 (Apr-Jun) comes after Mar, so same financial year + if (currentQuarter === "Q1") { + previousYear = currentYear - 1; + } else if (currentQuarter === "Q4") { + previousYear = currentYear; // Q4 (Jan–Mar) belongs to same financial year end + } + + // --- Forecast Quarter --- + let forecastQuarter = allQuarters[(currentIndex + 1) % 4]; + let forecastYear = currentYear; + // If next quarter is Q1, then financial year increments + if (forecastQuarter === "Q1") { + forecastYear = currentYear + 1; + } + + // Construct Output Object + const output = { + previous_month: { + previous_period_one: quarters[previousQuarter][0], + previous_period_two: quarters[previousQuarter][1], + previous_period_three: quarters[previousQuarter][2], + }, + previous_year: previousYear, + previous_quarter: previousQuarter, + + current_month: { + current_period_one: quarters[currentQuarter][0], + current_period_two: quarters[currentQuarter][1], + current_period_three: quarters[currentQuarter][2], + }, + current_year: currentYear, + current_quarter: currentQuarter, + + forecast_month: { + forecast_period_one: quarters[forecastQuarter][0], + forecast_period_two: quarters[forecastQuarter][1], + forecast_period_three: quarters[forecastQuarter][2], + }, + forecast_year: forecastYear, + forecast_quarter: forecastQuarter, + }; + + return output; + }; + \ No newline at end of file