|
@@ -9,6 +9,7 @@ import {
|
|
|
getRejectReason,
|
|
getRejectReason,
|
|
|
normalizeGroupName,
|
|
normalizeGroupName,
|
|
|
} from '@/utils/aggregateReportRecords';
|
|
} from '@/utils/aggregateReportRecords';
|
|
|
|
|
+import { NOTIFY_TZLX } from '@/utils/notificationFields';
|
|
|
import { buildManageRoute } from '@/utils/manageRoutes';
|
|
import { buildManageRoute } from '@/utils/manageRoutes';
|
|
|
|
|
|
|
|
const pad2 = (n) => String(n).padStart(2, '0');
|
|
const pad2 = (n) => String(n).padStart(2, '0');
|
|
@@ -25,12 +26,12 @@ const getPreviousPeriod = (period) => {
|
|
|
return `${prevYear}-${pad2(prevMonth)}`;
|
|
return `${prevYear}-${pad2(prevMonth)}`;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-/** 账期截止:次月 5 日 23:59:59 */
|
|
|
|
|
|
|
+/** 账期截止:次月 1 日 23:59:59 */
|
|
|
const getPeriodDeadlineDate = (period) => {
|
|
const getPeriodDeadlineDate = (period) => {
|
|
|
const [year, month] = period.split('-').map(Number);
|
|
const [year, month] = period.split('-').map(Number);
|
|
|
const nextMonth = month === 12 ? 1 : month + 1;
|
|
const nextMonth = month === 12 ? 1 : month + 1;
|
|
|
const nextYear = month === 12 ? year + 1 : year;
|
|
const nextYear = month === 12 ? year + 1 : year;
|
|
|
- return new Date(nextYear, nextMonth - 1, 5, 23, 59, 59);
|
|
|
|
|
|
|
+ return new Date(nextYear, nextMonth - 1, 1, 23, 59, 59);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const toTimestamp = (val) => {
|
|
const toTimestamp = (val) => {
|
|
@@ -60,18 +61,35 @@ const groupHasRejected = (rejectedList, group, period) =>
|
|
|
|
|
|
|
|
const buildRoute = (tab, query = {}) => buildManageRoute({ tab, ...query });
|
|
const buildRoute = (tab, query = {}) => buildManageRoute({ tab, ...query });
|
|
|
|
|
|
|
|
-/** 通知 id 仅依赖类型 + 账期 + 集团,保证同一条通知不会重复入库 */
|
|
|
|
|
-const notifyRecordId = (type, record) => {
|
|
|
|
|
|
|
+const notifyYwid = (prefix, period, group, suffix = '') =>
|
|
|
|
|
+ suffix ? `${prefix}:${period}:${group}:${suffix}` : `${prefix}:${period}:${group}`;
|
|
|
|
|
+
|
|
|
|
|
+/** 月报聚合记录唯一 ywid(账期 + 青浦集团 + 所属集团,避免同集团多条记录互相覆盖) */
|
|
|
|
|
+const notifyAggregateYwid = (prefix, record) => {
|
|
|
const qingpuGroup = (record.qingpuGroup ?? '').trim() || 'unknown';
|
|
const qingpuGroup = (record.qingpuGroup ?? '').trim() || 'unknown';
|
|
|
- const group = (record.group ?? '').trim() || 'unknown';
|
|
|
|
|
- return `${type}:${record.period}:${qingpuGroup}:${group}`;
|
|
|
|
|
|
|
+ const groupName = (record.group ?? '').trim() || qingpuGroup;
|
|
|
|
|
+ return `${prefix}:${record.period}:${qingpuGroup}:${groupName}`;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/** 通知列表过滤、展示用青浦集团 */
|
|
|
|
|
+const notifyDisplayGroup = (record) => record.qingpuGroup || record.group;
|
|
|
|
|
+
|
|
|
|
|
+/** 截止前剩余天数 → c_tbtx(1=7天,2=3天,3=1天) */
|
|
|
|
|
+const getReminderTbtx = (daysLeft) => {
|
|
|
|
|
+ if (daysLeft <= 0) return 0;
|
|
|
|
|
+ if (daysLeft <= 1) return 3;
|
|
|
|
|
+ if (daysLeft <= 3) return 2;
|
|
|
|
|
+ if (daysLeft <= 7) return 1;
|
|
|
|
|
+ return 0;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const reminderTitle = (period, group, daysLeft) => {
|
|
|
|
|
+ const label = daysLeft <= 1 ? '1 天' : `${daysLeft} 天`;
|
|
|
|
|
+ return `${period} 月报截止倒计时 ${label}(${group})`;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 生成当前时刻所有适用的通知(幂等,同 id 不会重复入库)
|
|
|
|
|
- * @param {{ pendingList: object[], approvedList: object[], rejectedList: object[] }} lists
|
|
|
|
|
- * @param {{ currentGroup?: string }} options - 可选,限定集团范围
|
|
|
|
|
- * @returns {Array<{ id: string, time: string, type: string, title: string, timestamp: number, route?: object, group: string }>}
|
|
|
|
|
|
|
+ * 生成当前时刻所有适用的通知(幂等,同 c_ywid 不会重复入库)
|
|
|
*/
|
|
*/
|
|
|
export const buildNotifications = ({
|
|
export const buildNotifications = ({
|
|
|
pendingList = [],
|
|
pendingList = [],
|
|
@@ -85,23 +103,30 @@ export const buildNotifications = ({
|
|
|
const currentPeriod = getCurrentPeriod();
|
|
const currentPeriod = getCurrentPeriod();
|
|
|
const deadline = getPeriodDeadlineDate(currentPeriod);
|
|
const deadline = getPeriodDeadlineDate(currentPeriod);
|
|
|
const daysLeft = Math.ceil((deadline.getTime() - now) / (86400000));
|
|
const daysLeft = Math.ceil((deadline.getTime() - now) / (86400000));
|
|
|
|
|
+ const reminderTbtx = getReminderTbtx(daysLeft);
|
|
|
|
|
|
|
|
- // ── 待审核(来自 pendingList)─────────────────────────────
|
|
|
|
|
|
|
+ // ── 待审核 ────────────────────────────────────────────────
|
|
|
aggregateReportRecords(pendingList, { fallbackPeriod: currentPeriod }).forEach((record) => {
|
|
aggregateReportRecords(pendingList, { fallbackPeriod: currentPeriod }).forEach((record) => {
|
|
|
- const group = record.qingpuGroup || record.group;
|
|
|
|
|
|
|
+ const group = notifyDisplayGroup(record);
|
|
|
if (currentGroup && group !== currentGroup) return;
|
|
if (currentGroup && group !== currentGroup) return;
|
|
|
|
|
|
|
|
const latest = record.items?.reduce((best, item) => {
|
|
const latest = record.items?.reduce((best, item) => {
|
|
|
const t = toTimestamp(getFillTime(item));
|
|
const t = toTimestamp(getFillTime(item));
|
|
|
return t >= toTimestamp(getFillTime(best)) ? item : best;
|
|
return t >= toTimestamp(getFillTime(best)) ? item : best;
|
|
|
}, record.items?.[0]);
|
|
}, record.items?.[0]);
|
|
|
|
|
+ const title = `${group} ${record.period} 资产月报(${record.count} 条)待审核`;
|
|
|
|
|
+
|
|
|
notifications.push({
|
|
notifications.push({
|
|
|
- id: notifyRecordId('pending', record),
|
|
|
|
|
|
|
+ ywid: notifyAggregateYwid('pending', record),
|
|
|
time: record.submittedAt,
|
|
time: record.submittedAt,
|
|
|
type: '待审核',
|
|
type: '待审核',
|
|
|
- title: `${group} ${record.period} 资产月报(${record.count} 条)待审核`,
|
|
|
|
|
|
|
+ title,
|
|
|
|
|
+ content: title,
|
|
|
timestamp: toTimestamp(getFillTime(latest)),
|
|
timestamp: toTimestamp(getFillTime(latest)),
|
|
|
group,
|
|
group,
|
|
|
|
|
+ tzlx: NOTIFY_TZLX.PENDING,
|
|
|
|
|
+ tbtx: 0,
|
|
|
|
|
+ shjg: 0,
|
|
|
route: buildRoute('audit', {
|
|
route: buildRoute('audit', {
|
|
|
sub: 'pending',
|
|
sub: 'pending',
|
|
|
group: record.qingpuGroup || group,
|
|
group: record.qingpuGroup || group,
|
|
@@ -110,22 +135,28 @@ export const buildNotifications = ({
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // ── 已通过(来自 approvedList)────────────────────────────
|
|
|
|
|
|
|
+ // ── 已通过 ────────────────────────────────────────────────
|
|
|
aggregateReportRecords(approvedList, { fallbackPeriod: currentPeriod }).forEach((record) => {
|
|
aggregateReportRecords(approvedList, { fallbackPeriod: currentPeriod }).forEach((record) => {
|
|
|
- const group = record.qingpuGroup || record.group;
|
|
|
|
|
|
|
+ const group = notifyDisplayGroup(record);
|
|
|
if (currentGroup && group !== currentGroup) return;
|
|
if (currentGroup && group !== currentGroup) return;
|
|
|
|
|
|
|
|
const latest = record.items?.reduce((best, item) => {
|
|
const latest = record.items?.reduce((best, item) => {
|
|
|
const t = toTimestamp(getAuditTime(item));
|
|
const t = toTimestamp(getAuditTime(item));
|
|
|
return t >= toTimestamp(getAuditTime(best)) ? item : best;
|
|
return t >= toTimestamp(getAuditTime(best)) ? item : best;
|
|
|
}, record.items?.[0]);
|
|
}, record.items?.[0]);
|
|
|
|
|
+ const title = `${record.period} 月报已通过(${record.group},${record.count} 条)`;
|
|
|
|
|
+
|
|
|
notifications.push({
|
|
notifications.push({
|
|
|
- id: notifyRecordId('approved', record),
|
|
|
|
|
|
|
+ ywid: notifyAggregateYwid('approved', record),
|
|
|
time: formatDateTime(getAuditTime(latest)),
|
|
time: formatDateTime(getAuditTime(latest)),
|
|
|
type: '审核结果',
|
|
type: '审核结果',
|
|
|
- title: `${record.period} 月报已通过(${group},${record.count} 条)`,
|
|
|
|
|
|
|
+ title,
|
|
|
|
|
+ content: title,
|
|
|
timestamp: toTimestamp(getAuditTime(latest)),
|
|
timestamp: toTimestamp(getAuditTime(latest)),
|
|
|
group,
|
|
group,
|
|
|
|
|
+ tzlx: NOTIFY_TZLX.AUDIT_RESULT,
|
|
|
|
|
+ tbtx: 0,
|
|
|
|
|
+ shjg: 1,
|
|
|
route: buildRoute('archive', {
|
|
route: buildRoute('archive', {
|
|
|
sub: 'history',
|
|
sub: 'history',
|
|
|
group: record.qingpuGroup || group,
|
|
group: record.qingpuGroup || group,
|
|
@@ -135,9 +166,9 @@ export const buildNotifications = ({
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // ── 已驳回(来自 rejectedList)────────────────────────────
|
|
|
|
|
|
|
+ // ── 已驳回 ────────────────────────────────────────────────
|
|
|
aggregateReportRecords(rejectedList, { fallbackPeriod: currentPeriod }).forEach((record) => {
|
|
aggregateReportRecords(rejectedList, { fallbackPeriod: currentPeriod }).forEach((record) => {
|
|
|
- const group = record.qingpuGroup || record.group;
|
|
|
|
|
|
|
+ const group = notifyDisplayGroup(record);
|
|
|
if (currentGroup && group !== currentGroup) return;
|
|
if (currentGroup && group !== currentGroup) return;
|
|
|
|
|
|
|
|
const latest = record.items?.reduce((best, item) => {
|
|
const latest = record.items?.reduce((best, item) => {
|
|
@@ -145,13 +176,19 @@ export const buildNotifications = ({
|
|
|
return t >= toTimestamp(getAuditTime(best)) ? item : best;
|
|
return t >= toTimestamp(getAuditTime(best)) ? item : best;
|
|
|
}, record.items?.[0]);
|
|
}, record.items?.[0]);
|
|
|
const reason = getRejectReason(latest);
|
|
const reason = getRejectReason(latest);
|
|
|
|
|
+ const title = `${record.period} 月报已被驳回(${record.group})${reason ? `:${reason}` : ''}`;
|
|
|
|
|
+
|
|
|
notifications.push({
|
|
notifications.push({
|
|
|
- id: notifyRecordId('rejected', record),
|
|
|
|
|
|
|
+ ywid: notifyAggregateYwid('rejected', record),
|
|
|
time: formatDateTime(getAuditTime(latest)),
|
|
time: formatDateTime(getAuditTime(latest)),
|
|
|
type: '审核结果',
|
|
type: '审核结果',
|
|
|
- title: `${record.period} 月报已被驳回(${group})${reason ? `:${reason}` : ''}`,
|
|
|
|
|
|
|
+ title,
|
|
|
|
|
+ content: title,
|
|
|
timestamp: toTimestamp(getAuditTime(latest)),
|
|
timestamp: toTimestamp(getAuditTime(latest)),
|
|
|
group,
|
|
group,
|
|
|
|
|
+ tzlx: NOTIFY_TZLX.AUDIT_RESULT,
|
|
|
|
|
+ tbtx: 0,
|
|
|
|
|
+ shjg: 0,
|
|
|
route: buildRoute('archive', {
|
|
route: buildRoute('archive', {
|
|
|
sub: 'history',
|
|
sub: 'history',
|
|
|
group: record.qingpuGroup || group,
|
|
group: record.qingpuGroup || group,
|
|
@@ -161,25 +198,31 @@ export const buildNotifications = ({
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // ── 填报提醒(当前账期截止前 7 天,尚未提交的集团)────────
|
|
|
|
|
- if (daysLeft > 0 && daysLeft <= 7) {
|
|
|
|
|
|
|
+ // ── 填报提醒(截止前 7/3/1 天,尚未提交的集团)────────────
|
|
|
|
|
+ if (reminderTbtx > 0) {
|
|
|
const allLists = [pendingList, approvedList, rejectedList];
|
|
const allLists = [pendingList, approvedList, rejectedList];
|
|
|
GROUP_ORDER.forEach((group) => {
|
|
GROUP_ORDER.forEach((group) => {
|
|
|
if (currentGroup && group !== currentGroup) return;
|
|
if (currentGroup && group !== currentGroup) return;
|
|
|
if (groupHasAnySubmission(allLists, group, currentPeriod)) return;
|
|
if (groupHasAnySubmission(allLists, group, currentPeriod)) return;
|
|
|
|
|
+
|
|
|
|
|
+ const title = reminderTitle(currentPeriod, group, daysLeft);
|
|
|
notifications.push({
|
|
notifications.push({
|
|
|
- id: `reminder:${currentPeriod}:${group}`,
|
|
|
|
|
|
|
+ ywid: notifyYwid('reminder', currentPeriod, group, String(reminderTbtx)),
|
|
|
time: formatDateTime(now),
|
|
time: formatDateTime(now),
|
|
|
type: '填报提醒',
|
|
type: '填报提醒',
|
|
|
- title: `${currentPeriod} 月报截止倒计时 ${daysLeft} 天(${group})`,
|
|
|
|
|
|
|
+ title,
|
|
|
|
|
+ content: title,
|
|
|
timestamp: now,
|
|
timestamp: now,
|
|
|
group,
|
|
group,
|
|
|
|
|
+ tzlx: NOTIFY_TZLX.REMINDER,
|
|
|
|
|
+ tbtx: reminderTbtx,
|
|
|
|
|
+ shjg: 0,
|
|
|
route: buildRoute('archive'),
|
|
route: buildRoute('archive'),
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // ── 逾期催办(上月截止后未通过/未提交的集团)──────────────
|
|
|
|
|
|
|
+ // ── 逾期催办 ──────────────────────────────────────────────
|
|
|
const prevPeriod = getPreviousPeriod(currentPeriod);
|
|
const prevPeriod = getPreviousPeriod(currentPeriod);
|
|
|
const prevDeadline = getPeriodDeadlineDate(prevPeriod);
|
|
const prevDeadline = getPeriodDeadlineDate(prevPeriod);
|
|
|
if (now > prevDeadline.getTime()) {
|
|
if (now > prevDeadline.getTime()) {
|
|
@@ -196,33 +239,43 @@ export const buildNotifications = ({
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
if (!hasAny) {
|
|
if (!hasAny) {
|
|
|
|
|
+ const title = `${prevPeriod} 月报已逾期,请补报(${group})`;
|
|
|
notifications.push({
|
|
notifications.push({
|
|
|
- id: `overdue:${prevPeriod}:${group}`,
|
|
|
|
|
|
|
+ ywid: notifyYwid('overdue', prevPeriod, group),
|
|
|
time: formatDateTime(prevDeadline),
|
|
time: formatDateTime(prevDeadline),
|
|
|
type: '逾期催办',
|
|
type: '逾期催办',
|
|
|
- title: `${prevPeriod} 月报已逾期,请补报(${group})`,
|
|
|
|
|
|
|
+ title,
|
|
|
|
|
+ content: title,
|
|
|
timestamp: prevDeadline.getTime(),
|
|
timestamp: prevDeadline.getTime(),
|
|
|
group,
|
|
group,
|
|
|
|
|
+ tzlx: NOTIFY_TZLX.OVERDUE,
|
|
|
|
|
+ tbtx: 0,
|
|
|
|
|
+ shjg: 0,
|
|
|
route: buildRoute('archive'),
|
|
route: buildRoute('archive'),
|
|
|
});
|
|
});
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (rejected) {
|
|
if (rejected) {
|
|
|
|
|
+ const title = `${prevPeriod} 月报被驳回且未重报,请尽快处理(${group})`;
|
|
|
notifications.push({
|
|
notifications.push({
|
|
|
- id: `overdue-reject:${prevPeriod}:${group}`,
|
|
|
|
|
|
|
+ ywid: notifyYwid('overdue-reject', prevPeriod, group),
|
|
|
time: formatDateTime(now),
|
|
time: formatDateTime(now),
|
|
|
type: '逾期催办',
|
|
type: '逾期催办',
|
|
|
- title: `${prevPeriod} 月报被驳回且未重报,请尽快处理(${group})`,
|
|
|
|
|
|
|
+ title,
|
|
|
|
|
+ content: title,
|
|
|
timestamp: now,
|
|
timestamp: now,
|
|
|
group,
|
|
group,
|
|
|
|
|
+ tzlx: NOTIFY_TZLX.OVERDUE,
|
|
|
|
|
+ tbtx: 0,
|
|
|
|
|
+ shjg: 0,
|
|
|
route: buildRoute('archive', { sub: 'history', group, period: prevPeriod, status: '已驳回' }),
|
|
route: buildRoute('archive', { sub: 'history', group, period: prevPeriod, status: '已驳回' }),
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // ── 租约到期/预警(来自已通过资产的 status 字段)──────────
|
|
|
|
|
|
|
+ // ── 租约到期/预警 ─────────────────────────────────────────
|
|
|
const expiryItems = approvedList
|
|
const expiryItems = approvedList
|
|
|
.filter((item) => item.status === 'red' || item.status === 'orange')
|
|
.filter((item) => item.status === 'red' || item.status === 'orange')
|
|
|
.slice(0, 8);
|
|
.slice(0, 8);
|
|
@@ -233,13 +286,18 @@ export const buildNotifications = ({
|
|
|
const group = normalizeGroupName(item.c_ssqpjt) || '未知';
|
|
const group = normalizeGroupName(item.c_ssqpjt) || '未知';
|
|
|
if (currentGroup && group !== currentGroup) return;
|
|
if (currentGroup && group !== currentGroup) return;
|
|
|
|
|
|
|
|
|
|
+ const title = `${label} 租约${withinOneMonth ? '将于一个月内到期' : '将于三个月内到期'},请关注`;
|
|
|
notifications.push({
|
|
notifications.push({
|
|
|
- id: `expiry:${item.id}`,
|
|
|
|
|
|
|
+ ywid: `expiry:${item.id}`,
|
|
|
time: formatDateTime(item.update_time || now),
|
|
time: formatDateTime(item.update_time || now),
|
|
|
type: withinOneMonth ? '租约到期' : '租约预警',
|
|
type: withinOneMonth ? '租约到期' : '租约预警',
|
|
|
- title: `${label} 租约${withinOneMonth ? '将于一个月内到期' : '将于三个月内到期'},请关注`,
|
|
|
|
|
|
|
+ title,
|
|
|
|
|
+ content: title,
|
|
|
timestamp: toTimestamp(item.update_time || now),
|
|
timestamp: toTimestamp(item.update_time || now),
|
|
|
group,
|
|
group,
|
|
|
|
|
+ tzlx: withinOneMonth ? NOTIFY_TZLX.LEASE_EXPIRY : NOTIFY_TZLX.LEASE_WARNING,
|
|
|
|
|
+ tbtx: 0,
|
|
|
|
|
+ shjg: 0,
|
|
|
route: buildRoute('assets'),
|
|
route: buildRoute('assets'),
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|