301 lines
8.8 KiB
JavaScript
301 lines
8.8 KiB
JavaScript
|
|
const { Sequelize, Op } = require("sequelize");
|
|
const { Establishment, Submission, Emirate, EstablishmentUser, SubmissionProduct, SubmissionDeadline, QuarterlyWindowsConfiguration } = require("../models");
|
|
const logger = require("../services/logger");
|
|
|
|
|
|
|
|
function getQuarterDates(quarter, year) {
|
|
switch (quarter) {
|
|
case "Q1":
|
|
return { start: new Date(year, 3, 1), end: new Date(year, 5, 30) };
|
|
case "Q2":
|
|
return { start: new Date(year, 6, 1), end: new Date(year, 8, 30) };
|
|
case "Q3":
|
|
return { start: new Date(year, 9, 1), end: new Date(year, 11, 31) };
|
|
case "Q4":
|
|
return { start: new Date(year, 0, 1), end: new Date(year, 2, 31) };
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function getPrevCurrNextQuarter(date) {
|
|
const month = date.getMonth() + 1; // 1-12
|
|
const year = date.getFullYear();
|
|
const quarters = ["Q1", "Q2", "Q3", "Q4"];
|
|
|
|
|
|
const getQuarterIndex = (m) => {
|
|
if (m >= 1 && m <= 3) return 0; // Jan-Mar -> Q1
|
|
if (m >= 4 && m <= 6) return 1; // Apr-Jun -> Q2
|
|
if (m >= 7 && m <= 9) return 2; // Jul-Sep -> Q3
|
|
if (m >= 10 && m <=12) return 3; // Oct-Dec -> Q4
|
|
};
|
|
|
|
const currentIndex = getQuarterIndex(month);
|
|
const currentQuarter = quarters[currentIndex];
|
|
|
|
const previousIndex = (currentIndex - 1 + 4) % 4;
|
|
const nextIndex = (currentIndex + 1) % 4;
|
|
|
|
const previousQuarter = quarters[previousIndex];
|
|
const nextQuarter = quarters[nextIndex];
|
|
|
|
const previousQuarterYear = (currentIndex === 1) ? year - 1 : year;
|
|
const nextQuarterYear = (currentIndex === 3) ? year + 1 : year;
|
|
|
|
return {
|
|
previous_quarter: previousQuarter,
|
|
previous_quarter_year: previousQuarterYear,
|
|
current_quarter: currentQuarter,
|
|
current_quarter_year: year,
|
|
next_quarter: nextQuarter,
|
|
next_quarter_year: nextQuarterYear,
|
|
};
|
|
}
|
|
|
|
|
|
|
|
|
|
exports.getEstablishmentDashboard = async (req, res) => {
|
|
try {
|
|
const establishment_id = req.query.establishment_id;
|
|
if (!establishment_id) {
|
|
return res.status(400).json({ status: "failed", message: "establishment_id required" });
|
|
}
|
|
|
|
const today = new Date();
|
|
const quarters = getPrevCurrNextQuarter(today);
|
|
|
|
|
|
const submission_history = await Submission.findAll({
|
|
where: { establishment_id },
|
|
order: [["created_at", "DESC"]],
|
|
// limit: 5,
|
|
attributes: {
|
|
include: [
|
|
[
|
|
Sequelize.literal(`( SELECT COUNT(*) FROM submission_products AS sp WHERE sp.submission_id = submission.id )`),
|
|
"product_count",
|
|
],
|
|
[
|
|
Sequelize.literal(`(
|
|
SELECT survey_name FROM quarterly_windows_configuration_master AS QW
|
|
WHERE QW.quarter = submission.quarter AND QW.year = submission.year limit 1
|
|
)`),
|
|
"survey_name",
|
|
],
|
|
],
|
|
},
|
|
|
|
});
|
|
|
|
|
|
// Submission status for current quarter
|
|
const currentQuarterDates = getQuarterDates(quarters.current_quarter, quarters.current_quarter_year);
|
|
|
|
const currentSubmission = await Submission.findOne({
|
|
where: {
|
|
establishment_id,
|
|
quarter: quarters.current_quarter,
|
|
year: quarters.current_quarter_year,
|
|
},
|
|
order: [["created_at", "DESC"]],
|
|
});
|
|
|
|
// Get submission deadline (assuming only one row)
|
|
const deadline = await SubmissionDeadline.findByPk(1);
|
|
|
|
let nextDeadline = null;
|
|
let submissionStatus = "Pending";
|
|
|
|
if (deadline) {
|
|
const quarterEnd = currentQuarterDates.end;
|
|
const deadlineDate = new Date(quarterEnd);
|
|
deadlineDate.setDate(deadlineDate.getDate() + deadline.deadline_days_after_quarter_end);
|
|
|
|
nextDeadline = deadlineDate;
|
|
|
|
if (currentSubmission) {
|
|
submissionStatus = "Submitted";
|
|
}
|
|
}
|
|
|
|
let lastSubmissionInfo = null;
|
|
if (submission_history.length > 0) {
|
|
const last = submission_history[0];
|
|
lastSubmissionInfo = {
|
|
quarter: last.quarter,
|
|
year: last.year,
|
|
submitted_on: last.created_at,
|
|
};
|
|
}
|
|
|
|
|
|
let surveyReady = [];
|
|
|
|
const activeQuarters = await QuarterlyWindowsConfiguration.findAll({
|
|
where: { is_active: true },
|
|
order: [["created_at", "DESC"]],
|
|
});
|
|
|
|
if (activeQuarters?.length > 0) {
|
|
|
|
for (const q of activeQuarters) {
|
|
const endDate = new Date(q.end_date);
|
|
const graceDays = Number(q.grace_periods_days || 0);
|
|
const finalDeadline = new Date(endDate);
|
|
finalDeadline.setDate(finalDeadline.getDate() + graceDays);
|
|
const now = new Date();
|
|
|
|
// Skip this quarter if it's closed (deadline has passed)
|
|
if (now > finalDeadline) {
|
|
continue;
|
|
}
|
|
|
|
const submissionMatch = await Submission.findOne({
|
|
where: {
|
|
year: q.year,
|
|
quarter: q.quarter,
|
|
establishment_id: establishment_id
|
|
}
|
|
});
|
|
|
|
// if not submitted yet → add for start survey
|
|
if (!submissionMatch) {
|
|
surveyReady.push({
|
|
survey_name:q.survey_name,
|
|
year: q.year,
|
|
quarter: q.quarter,
|
|
start_date: q.start_date,
|
|
end_date: q.end_date,
|
|
grace_periods_days: q.grace_periods_days,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
res.status(200).json({
|
|
status: "success",
|
|
data: {
|
|
...quarters,
|
|
submission_status: submissionStatus,
|
|
next_deadline: nextDeadline,
|
|
last_submission: lastSubmissionInfo,
|
|
survey_ready: surveyReady,
|
|
submission_history,
|
|
},
|
|
});
|
|
|
|
} catch (err) {
|
|
logger.error(err.message);
|
|
logger.error(`Stack trace: ${err.stack}`);
|
|
res.status(500).json({ status: "failed", message: "Internal server error" });
|
|
}
|
|
};
|
|
|
|
exports.adminDashboard = async (req, res) => {
|
|
try {
|
|
let { quarter, year } = req.query;
|
|
let whereCond = {};
|
|
if (quarter === 'All') quarter = null;
|
|
if (year === 'All') year = null;
|
|
|
|
if (quarter) whereCond.quarter = quarter;
|
|
if (year) whereCond.year = year;
|
|
|
|
const quarterlyWindows = await QuarterlyWindowsConfiguration.findOne({ where: whereCond });
|
|
|
|
const totalEstablishments = await Establishment.count();
|
|
const submittedCount = await Submission.count({ where: { ...whereCond, status: "Submitted" } });
|
|
const approvedCount = await Submission.count({ where: { ...whereCond, status: "Approved" } });
|
|
const rejectedCount = await Submission.count({ where: { ...whereCond, status: "Rejected" } });
|
|
const pendingCount = await Submission.count({ where: { ...whereCond, status: "Pending" } });
|
|
|
|
// only when query param not used → not started count
|
|
let notStartedCount = 0;
|
|
|
|
const startedEstIds = await Submission.findAll({
|
|
where: whereCond,
|
|
attributes: [[Sequelize.fn("DISTINCT", Sequelize.col("establishment_id")), "id"]],
|
|
raw: true,
|
|
});
|
|
|
|
const startedIds = startedEstIds.map(e => e.id);
|
|
notStartedCount = await Establishment.count({
|
|
where: { id: { [Op.notIn]: startedIds } },
|
|
});
|
|
|
|
|
|
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",
|
|
],
|
|
[
|
|
Sequelize.literal(`(
|
|
SELECT eu.name
|
|
FROM establishment_users eu
|
|
WHERE eu.id = submission.created_by
|
|
LIMIT 1
|
|
)`),
|
|
"created_by_name"
|
|
],
|
|
[
|
|
Sequelize.literal(`(
|
|
SELECT au.name
|
|
FROM admin_users au
|
|
WHERE au.id = submission.approve_or_reject_by
|
|
LIMIT 1
|
|
)`),
|
|
"reviewer_name"
|
|
],
|
|
],
|
|
},
|
|
order: [["created_at", "DESC"]],
|
|
limit: 10,
|
|
});
|
|
return res.status(200).json({
|
|
status: "success",
|
|
selected_quarter: quarter,
|
|
selected_year: year,
|
|
summary: {
|
|
total_establishments: totalEstablishments,
|
|
submitted: submittedCount,
|
|
approved: approvedCount,
|
|
rejected: rejectedCount,
|
|
pending: pendingCount,
|
|
not_started: notStartedCount,
|
|
},
|
|
quarterly_windows : quarterlyWindows,
|
|
recent_submissions: recentSubmissions,
|
|
});
|
|
|
|
} catch (err) {
|
|
logger.error(err.message);
|
|
logger.error(`Stack trace: ${err.stack}`);
|
|
return res.status(500).json({ status: "failed", message: "Internal server error" });
|
|
}
|
|
};
|
|
|