GWM
This commit is contained in:
parent
064084b0c5
commit
38f97d197f
@ -1,9 +1,6 @@
|
|||||||
const db = require("../models");
|
|
||||||
const { Op } = require("sequelize");
|
const { Sequelize, Op } = require("sequelize");
|
||||||
const { Sequelize } = require("sequelize");
|
const { Establishment, Submission, Emirate, EstablishmentUser, SubmissionProduct, SubmissionDeadline } = require("../models");
|
||||||
const Submission = db.Submission;
|
|
||||||
const SubmissionProduct = db.SubmissionProduct;
|
|
||||||
const SubmissionDeadline = db.SubmissionDeadline;
|
|
||||||
|
|
||||||
function getQuarter(date) {
|
function getQuarter(date) {
|
||||||
const month = date.getMonth() + 1;
|
const month = date.getMonth() + 1;
|
||||||
@ -76,12 +73,7 @@ exports.getEstablishmentDashboard = async (req, res) => {
|
|||||||
const today = new Date();
|
const today = new Date();
|
||||||
const quarters = getPrevCurrNextQuarter(today);
|
const quarters = getPrevCurrNextQuarter(today);
|
||||||
|
|
||||||
// Last 5 submissions
|
|
||||||
// const submission_history = await Submission.findAll({
|
|
||||||
// where: { establishment_id },
|
|
||||||
// order: [["created_at", "DESC"]],
|
|
||||||
// limit: 5,
|
|
||||||
// });
|
|
||||||
|
|
||||||
const submission_history = await Submission.findAll({
|
const submission_history = await Submission.findAll({
|
||||||
where: { establishment_id },
|
where: { establishment_id },
|
||||||
@ -99,12 +91,7 @@ exports.getEstablishmentDashboard = async (req, res) => {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// include: [
|
|
||||||
// {
|
|
||||||
// model: SubmissionProduct,
|
|
||||||
// as: "products", // ✅ use your actual alias here
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -164,3 +151,92 @@ exports.getEstablishmentDashboard = async (req, res) => {
|
|||||||
res.status(500).json({ status: "failed", message: err.message });
|
res.status(500).json({ status: "failed", message: err.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exports.adminDashboard = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { quarter, year } = req.query; // e.g., Q1, 2025
|
||||||
|
|
||||||
|
// --- 1️⃣ Summary Counts ---
|
||||||
|
const [
|
||||||
|
totalEstablishments,
|
||||||
|
submittedCount,
|
||||||
|
approvedCount,
|
||||||
|
rejectedCount,
|
||||||
|
pendingCount
|
||||||
|
] = await Promise.all([
|
||||||
|
Establishment.count(),
|
||||||
|
Submission.count(),
|
||||||
|
Submission.count({ where: { status: "Approved" } }),
|
||||||
|
Submission.count({ where: { status: "Rejected" } }),
|
||||||
|
Submission.count({ where: { status: "Pending" } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Not Started = total establishments - those that have any submission
|
||||||
|
const startedEstIds = await Submission.findAll({
|
||||||
|
attributes: [[Sequelize.fn("DISTINCT", Sequelize.col("establishment_id")), "id"]],
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
const startedIds = startedEstIds.map(e => e.id);
|
||||||
|
const notStartedCount = await Establishment.count({
|
||||||
|
where: { id: { [Op.notIn]: startedIds } },
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- 2️⃣ Recent 10 submissions for given quarter & year ---
|
||||||
|
const whereCond = {};
|
||||||
|
if (quarter) whereCond.quarter = quarter;
|
||||||
|
if (year) whereCond.year = year;
|
||||||
|
|
||||||
|
const recentSubmissions = await Submission.findAll({
|
||||||
|
where: whereCond,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Establishment,
|
||||||
|
as: "establishment",
|
||||||
|
attributes: ["factory_name"],
|
||||||
|
include: [
|
||||||
|
{ model: Emirate, as: "establishment_emirate", attributes: ["name"] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: EstablishmentUser,
|
||||||
|
as: "created_user",
|
||||||
|
attributes: ["name"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attributes: {
|
||||||
|
include: [
|
||||||
|
[
|
||||||
|
Sequelize.literal(`(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM submission_products AS sp
|
||||||
|
WHERE sp.submission_id = submission.id
|
||||||
|
)`),
|
||||||
|
"product_count",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
order: [["created_at", "DESC"]],
|
||||||
|
limit: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- 3️⃣ Send Response ---
|
||||||
|
return res.status(200).json({
|
||||||
|
status: "success",
|
||||||
|
summary: {
|
||||||
|
total_establishments: totalEstablishments,
|
||||||
|
submitted: submittedCount,
|
||||||
|
approved: approvedCount,
|
||||||
|
rejected: rejectedCount,
|
||||||
|
pending: pendingCount,
|
||||||
|
not_started: notStartedCount,
|
||||||
|
},
|
||||||
|
recent_submissions: recentSubmissions,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return res.status(500).json({ status: "failed", message: err.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -241,7 +241,7 @@ exports.getAllEstablishments = async (req, res) => {
|
|||||||
...(exportType ? {} : { limit, offset }), // pagination only when not exporting
|
...(exportType ? {} : { limit, offset }), // pagination only when not exporting
|
||||||
});
|
});
|
||||||
|
|
||||||
// 📦 If export = excel → generate file
|
// If export = excel → generate file
|
||||||
if (exportType && exportType.toLowerCase() === "excel") {
|
if (exportType && exportType.toLowerCase() === "excel") {
|
||||||
const workbook = new ExcelJS.Workbook();
|
const workbook = new ExcelJS.Workbook();
|
||||||
const worksheet = workbook.addWorksheet("Establishments");
|
const worksheet = workbook.addWorksheet("Establishments");
|
||||||
@ -302,7 +302,7 @@ exports.getAllEstablishments = async (req, res) => {
|
|||||||
return res.end();
|
return res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Otherwise return JSON (paginated)
|
// Otherwise return JSON (paginated)
|
||||||
const totalCount = await Establishment.count({ where: whereClause });
|
const totalCount = await Establishment.count({ where: whereClause });
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
status: "success",
|
status: "success",
|
||||||
|
|||||||
@ -7,6 +7,9 @@ const UnitMaster = db.UnitMaster;
|
|||||||
const VariationReasonMaster = db.VariationReasonMaster;
|
const VariationReasonMaster = db.VariationReasonMaster;
|
||||||
const ZeroTargetReasonMaster = db.ZeroTargetReasonMaster;
|
const ZeroTargetReasonMaster = db.ZeroTargetReasonMaster;
|
||||||
const Product = db.Product;
|
const Product = db.Product;
|
||||||
|
const CityTown = db.CityTown;
|
||||||
|
const Emirate = db.Emirate;
|
||||||
|
const ExcelJS = require("exceljs");
|
||||||
const { Op } = require("sequelize");
|
const { Op } = require("sequelize");
|
||||||
const { Sequelize } = require("sequelize");
|
const { Sequelize } = require("sequelize");
|
||||||
const { sendEmail } = require("../services/email.service");
|
const { sendEmail } = require("../services/email.service");
|
||||||
@ -204,41 +207,147 @@ exports.submissionHistory = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// exports.submissionList = async (req, res) => {
|
||||||
|
// try {
|
||||||
|
|
||||||
|
// const data = await Submission.findAll({
|
||||||
|
// include: [
|
||||||
|
// {
|
||||||
|
// model: Establishment,
|
||||||
|
// as: "establishment" ,
|
||||||
|
// attributes: ["factory_name","establishment_code"],
|
||||||
|
// include: [
|
||||||
|
// { model: Emirate, as: "establishment_emirate", attributes: ["name"] }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// model: EstablishmentUser,
|
||||||
|
// as: "created_user",
|
||||||
|
// attributes: ["name"],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// attributes: {
|
||||||
|
// include: [
|
||||||
|
// [
|
||||||
|
// Sequelize.literal(`(SELECT COUNT(*) FROM submission_products AS sp WHERE sp.submission_id = submission.id )`),
|
||||||
|
// "product_count",
|
||||||
|
// ],
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// order: [["id", "DESC"]],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// res.status(200).json({ status: "success", data });
|
||||||
|
|
||||||
|
// } catch (err) {
|
||||||
|
// res.status(500).json({ status: "failed", message: err.message });
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
exports.submissionList = async (req, res) => {
|
exports.submissionList = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const {
|
||||||
|
page = 1,
|
||||||
|
limit = 10,
|
||||||
|
search = "",
|
||||||
|
quarter,
|
||||||
|
year,
|
||||||
|
status,
|
||||||
|
emirate_id,
|
||||||
|
export_excel = false,
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
const data = await Submission.findAll({
|
const offset = (page - 1) * limit;
|
||||||
|
const where = {};
|
||||||
|
|
||||||
|
// 🔹 Filters
|
||||||
|
if (year) where.year = year;
|
||||||
|
if (quarter) where.quarter = quarter;
|
||||||
|
if (status) where.status = status;
|
||||||
|
|
||||||
|
// 🔹 Search — establishment name or code or submitted user
|
||||||
|
const establishmentWhere = {};
|
||||||
|
if (search) {
|
||||||
|
establishmentWhere[Op.or] = [
|
||||||
|
{ factory_name: { [Op.like]: `%${search}%` } },
|
||||||
|
{ establishment_code: { [Op.like]: `%${search}%` } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 Emirate filter
|
||||||
|
if (emirate_id) establishmentWhere.establishment_emirate_id = emirate_id;
|
||||||
|
|
||||||
|
// 🔹 Query
|
||||||
|
const { rows, count } = await Submission.findAndCountAll({
|
||||||
|
where,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: Establishment,
|
model: Establishment,
|
||||||
as: "establishment" ,
|
as: "establishment",
|
||||||
attributes: ["factory_name","establishment_code","emirate"],
|
attributes: ["factory_name", "establishment_code"],
|
||||||
|
where: establishmentWhere,
|
||||||
|
include: [
|
||||||
|
{ model: Emirate, as: "establishment_emirate", attributes: ["name"] },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: EstablishmentUser,
|
model: EstablishmentUser,
|
||||||
as: "created_user",
|
as: "created_user",
|
||||||
attributes: ["name", "email"],
|
attributes: ["name"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
attributes: {
|
attributes: {
|
||||||
include: [
|
include: [
|
||||||
[
|
[
|
||||||
Sequelize.literal(`(SELECT COUNT(*) FROM submission_products AS sp WHERE sp.submission_id = submission.id )`),
|
Sequelize.literal(`(
|
||||||
|
SELECT COUNT(*) FROM submission_products AS sp
|
||||||
|
WHERE sp.submission_id = submission.id
|
||||||
|
)`),
|
||||||
"product_count",
|
"product_count",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
order: [["id", "DESC"]],
|
order: [["id", "DESC"]],
|
||||||
|
offset: parseInt(offset),
|
||||||
|
limit: parseInt(limit),
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).json({ status: "success", data });
|
// 🔹 Export to Excel
|
||||||
|
if (export_excel && export_excel === "true") {
|
||||||
|
const excelData = rows.map((s) => ({
|
||||||
|
Establishment: s.establishment?.factory_name,
|
||||||
|
Emirate: s.establishment?.establishment_emirate?.name,
|
||||||
|
Year: s.year,
|
||||||
|
Quarter: s.quarter,
|
||||||
|
Products: s.dataValues.product_count,
|
||||||
|
Status: s.status,
|
||||||
|
Submitted_By: s.created_user?.name,
|
||||||
|
Submission_Date: s.created_at,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const filePath = await exportToExcel("Submission_List", excelData);
|
||||||
|
return res.download(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 Paginated Response
|
||||||
|
return res.status(200).json({
|
||||||
|
status: "success",
|
||||||
|
message: "Fetched successfully",
|
||||||
|
pagination: {
|
||||||
|
total: count,
|
||||||
|
page: Number(page),
|
||||||
|
limit: Number(limit),
|
||||||
|
totalPages: Math.ceil(count / limit),
|
||||||
|
},
|
||||||
|
data: rows,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
res.status(500).json({ status: "failed", message: err.message });
|
res.status(500).json({ status: "failed", message: err.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.viewSubmissionDetails = async (req, res) => {
|
exports.viewSubmissionDetails = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|||||||
@ -1767,14 +1767,45 @@ router.get("/submissions/history/:establishment_id",[verifySignature, verifyToke
|
|||||||
* @swagger
|
* @swagger
|
||||||
* /api/submissions:
|
* /api/submissions:
|
||||||
* get:
|
* get:
|
||||||
* summary: Get all submissions
|
* summary: Get submissions list with pagination, filters, search, and export
|
||||||
* tags: [Submissions]
|
* tags: [Submissions]
|
||||||
* security:
|
* security:
|
||||||
* - appSignature: []
|
* - appSignature: []
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: page
|
||||||
|
* schema: { type: integer, default: 1 }
|
||||||
|
* description: Page number
|
||||||
|
* - in: query
|
||||||
|
* name: limit
|
||||||
|
* schema: { type: integer, default: 10 }
|
||||||
|
* description: Items per page
|
||||||
|
* - in: query
|
||||||
|
* name: search
|
||||||
|
* schema: { type: string }
|
||||||
|
* description: Search by establishment name or code
|
||||||
|
* - in: query
|
||||||
|
* name: quarter
|
||||||
|
* schema: { type: string }
|
||||||
|
* - in: query
|
||||||
|
* name: year
|
||||||
|
* schema: { type: integer }
|
||||||
|
* - in: query
|
||||||
|
* name: status
|
||||||
|
* schema: { type: string }
|
||||||
|
* - in: query
|
||||||
|
* name: emirate_id
|
||||||
|
* schema: { type: integer }
|
||||||
|
* - in: query
|
||||||
|
* name: export_excel
|
||||||
|
* schema: { type: boolean }
|
||||||
|
* description: If true, exports data to Excel file
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: All submissions
|
* description: Submission list retrieved successfully
|
||||||
|
* 500:
|
||||||
|
* description: Server error
|
||||||
*/
|
*/
|
||||||
router.get("/submissions",[verifySignature, verifyToken], submissionController.submissionList);
|
router.get("/submissions",[verifySignature, verifyToken], submissionController.submissionList);
|
||||||
|
|
||||||
@ -2011,6 +2042,39 @@ router.post("/submission_deadlines",[verifySignature, verifyToken], ConfigContro
|
|||||||
router.get("/establishment_dashboard",[verifySignature, verifyToken],dashboardController.getEstablishmentDashboard);
|
router.get("/establishment_dashboard",[verifySignature, verifyToken],dashboardController.getEstablishmentDashboard);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/admin_dashboard:
|
||||||
|
* get:
|
||||||
|
* summary: Get dashboard data for admin user
|
||||||
|
* tags: [Dashboard]
|
||||||
|
* security:
|
||||||
|
* - appSignature: []
|
||||||
|
* - bearerAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: quarter
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* - in: query
|
||||||
|
* name: year
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Dashboard data fetched successfully
|
||||||
|
* 400:
|
||||||
|
* description: Bad request
|
||||||
|
* 403:
|
||||||
|
* description: Invalid signature
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* 500:
|
||||||
|
* description: Server error
|
||||||
|
*/
|
||||||
|
router.get("/admin_dashboard",[verifySignature, verifyToken], dashboardController.adminDashboard);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user