From a65fc769c62c1ded32f87d034133cda587133ac1 Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Wed, 5 Nov 2025 12:39:30 +0530 Subject: [PATCH] GWM : Submission history api --- app/controllers/auth.controller.js | 8 ++- app/controllers/submission.controller.js | 85 ++++++++++++++++++++++++ app/models/establishment_user.model.js | 4 ++ app/models/index.js | 1 + app/models/submissionHistory.model.js | 48 +++++++++++++ app/models/user.model.js | 12 ++++ app/routes/routes.js | 44 +++++++++++- 7 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 app/models/submissionHistory.model.js diff --git a/app/controllers/auth.controller.js b/app/controllers/auth.controller.js index 3e32d46..2ab0790 100644 --- a/app/controllers/auth.controller.js +++ b/app/controllers/auth.controller.js @@ -5,6 +5,7 @@ require("dotenv").config(); const User = db.user; const EstablishmentUser = db.EstablishmentUser; +const Establishment = db.Establishment; // Register new user exports.register = async (req, res) => { @@ -45,9 +46,12 @@ exports.login = async (req, res) => { if(userRole == 'Admin') { - tokenData = { id: userData.id, email: userData.email , name: userData.name , role : userRole } + tokenData = { id: userData.id, email: userData.email , name: userData.name , role : userRole , last_login : userData.last_login } + await User.update({ last_login : Date() }, { where: { id: userData.id } }); }else{ - tokenData = { id: userData.id, email: userData.email , name: userData.name , role : userRole , establishment_id: userData.establishment_id } + const establishment_data = await Establishment.findByPk(userData.establishment_id); + tokenData = { id: userData.id, email: userData.email , name: userData.name , role : userRole , establishment_id: userData.establishment_id , establishment_data , last_login : userData.last_login } + await EstablishmentUser.update({ last_login : Date() }, { where: { id: userData.id } }); } const token = jwt.sign(tokenData, process.env.JWT_SECRET, { diff --git a/app/controllers/submission.controller.js b/app/controllers/submission.controller.js index 5cf4eb0..15887b4 100644 --- a/app/controllers/submission.controller.js +++ b/app/controllers/submission.controller.js @@ -1,6 +1,7 @@ const db = require("../models"); const Submission = db.Submission; const SubmissionProduct = db.SubmissionProduct; +const SubmissionHistory = db.SubmissionHistory; const Establishment = db.Establishment; const EstablishmentUser = db.EstablishmentUser; const UnitMaster = db.UnitMaster; @@ -425,6 +426,90 @@ exports.viewSubmissionDetails = async (req, res) => { } }; +// GET Submission History +exports.getSubmissionHistory = async (req, res) => { + try { + + const { establishment_id, year, quarter, product_id } = req.query; + + let whereSubmission = {}; + let whereProduct = {}; + + // dynamic filter user can pass 0, null, empty -> no filter + if (establishment_id) whereSubmission.establishment_id = establishment_id; + if (year) whereSubmission.year = year; + if (quarter) whereSubmission.quarter = quarter; + if (product_id) whereProduct.product_id = product_id; + + // 1) Get all filtered submissions (or all) + const submissions = await Submission.findAll({ + where: whereSubmission + }); + + if (submissions.length === 0) { + return res.status(200).json({ status:"success", data: [] }); + } + + const submissionIds = submissions.map(x => x.id); + + // 2) Get submission products + const products = await SubmissionProduct.findAll({ + where: { + submission_id: submissionIds, + ...whereProduct + }, + include: [ + { model: Product, as: "product", attributes: ["product_name","hs_code"] } + ] + }); + + if (products.length === 0) { + return res.status(200).json({ status:"success", data: [] }); + } + + const productPKs = products.map(x => x.id); + + // 3) history table + const history = await SubmissionHistory.findAll({ + where: { primary_key: productPKs, table_name: "submission_products" }, + order:[["created_at","DESC"]] + }); + + // 4) merge here + let finalData = products.map(prod => { + const sub = submissions.find(s => s.id === prod.submission_id); + const productHistory = history.filter(h => h.primary_key === prod.id); + + return { + submission_date: sub.created_at, + establishment_id: sub.establishment_id, + year: sub.year, + quarter: sub.quarter, + hs_code: prod.product.hs_code, + product_name: prod.product.product_name, + status: sub.status, + actor: "Establishment", + details: productHistory.map(h => ({ + column: h.column_name, + old: h.old_value, + new: h.new_value, + date: h.created_at + })) + }; + }); + + + // remove empty details rows + finalData = finalData.filter(item => item.details.length > 0); + + return res.status(200).json({ status:"success", data: finalData }); + + } catch (err) { + console.log(err); + return res.status(500).json({ status:"failed", message: err.message }); + } +} + exports.submissionEditRequest = async (req, res) => { try { diff --git a/app/models/establishment_user.model.js b/app/models/establishment_user.model.js index dfa0c46..b847eef 100644 --- a/app/models/establishment_user.model.js +++ b/app/models/establishment_user.model.js @@ -48,6 +48,10 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.INTEGER, allowNull: true, }, + last_login: { + type: DataTypes.DATE, + allowNull: true, + }, reset_otp: { type: DataTypes.STRING, allowNull: true, diff --git a/app/models/index.js b/app/models/index.js index d6b203e..2062210 100644 --- a/app/models/index.js +++ b/app/models/index.js @@ -28,6 +28,7 @@ db.QuarterlyWindowsConfiguration = require("./quarterlyWindowsConfiguration.mode db.EstablishmentPasswordResetRequest = require("./establishmentPasswordResetRequest.model")(sequelize, DataTypes); db.Emirate = require("./emirate.model")(sequelize, DataTypes); db.CityTown = require("./cityTown.model")(sequelize, DataTypes); +db.SubmissionHistory = require("./submissionHistory.model")(sequelize, DataTypes); // Associations db.Establishment.hasMany(db.EstablishmentUser, { diff --git a/app/models/submissionHistory.model.js b/app/models/submissionHistory.model.js new file mode 100644 index 0000000..27d4627 --- /dev/null +++ b/app/models/submissionHistory.model.js @@ -0,0 +1,48 @@ +// models/SubmissionHistory.js +module.exports = (sequelize, DataTypes) => { + const SubmissionHistory = sequelize.define( + "SubmissionHistory", + { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + primary_key: { + type: DataTypes.INTEGER, + allowNull: false, // submission_products.id + }, + table_name: { + type: DataTypes.STRING(100), + allowNull: false, + }, + column_name: { + type: DataTypes.STRING(100), + allowNull: false, + }, + old_value: { + type: DataTypes.STRING, + allowNull: true, + }, + new_value: { + type: DataTypes.STRING, + allowNull: true, + }, + updated_by: { + type: DataTypes.INTEGER, + allowNull: true, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + } + }, + { + tableName: "submission_history", + timestamps: false, + } + ); + + return SubmissionHistory; + }; + \ No newline at end of file diff --git a/app/models/user.model.js b/app/models/user.model.js index 576c1d5..0dd3bf5 100644 --- a/app/models/user.model.js +++ b/app/models/user.model.js @@ -18,6 +18,18 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.STRING, allowNull: false, }, + last_login: { + type: DataTypes.DATE, + allowNull: true, + }, + reset_otp: { + type: DataTypes.STRING, + allowNull: true, + }, + reset_otp_expires_at: { + type: DataTypes.DATE, + allowNull: true, + }, }); return User; diff --git a/app/routes/routes.js b/app/routes/routes.js index c5097d6..f92b257 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -2073,7 +2073,49 @@ router.post("/getQuarterPeriods",[verifySignature, verifyToken], submissionContr * 500: * description: Server error */ -router.get("/submissions/getPreviousForecastData", submissionController.getPreviousForecastData); +router.get("/submissions/getPreviousForecastData",[verifySignature, verifyToken], submissionController.getPreviousForecastData); + +/** + * @swagger + * /api/submission-audit-history: + * get: + * summary: Get submission product update history (merged view) + * description: Returns submission history with submission + product level change history merged. If no filters passed → returns entire history list. + * tags: [Submissions] + * parameters: + * - in: query + * name: establishment_id + * schema: + * type: integer + * required: true + * description: Filter by establishment_id + * - in: query + * name: year + * schema: + * type: integer + * required: false + * description: Filter by year + * - in: query + * name: quarter + * schema: + * type: string + * enum: [Q1, Q2, Q3, Q4] + * required: false + * description: Filter by quarter + * - in: query + * name: product_id + * schema: + * type: integer + * required: false + * description: Filter by product_id + * responses: + * 200: + * description: Merged submission history list retrieved successfully + * 500: + * description: Server error + */ +router.get("/submission-audit-history", submissionController.getSubmissionHistory); +