import { GROUP_ORDER, aggregateReportRecords, extractFillMonth, extractPeriod, formatDateTime, getAuditTime, getFillTime, getRejectReason, normalizeGroupName, } from '@/utils/aggregateReportRecords'; import { NOTIFY_TZLX } from '@/utils/notificationFields'; import { buildManageRoute } from '@/utils/manageRoutes'; const pad2 = (n) => String(n).padStart(2, '0'); const getCurrentPeriod = () => { const now = new Date(); return `${now.getFullYear()}-${pad2(now.getMonth() + 1)}`; }; const getPreviousPeriod = (period) => { const [year, month] = period.split('-').map(Number); const prevMonth = month === 1 ? 12 : month - 1; const prevYear = month === 1 ? year - 1 : year; return `${prevYear}-${pad2(prevMonth)}`; }; /** 账期截止:次月 1 日 23:59:59 */ const getPeriodDeadlineDate = (period) => { const [year, month] = period.split('-').map(Number); const nextMonth = month === 12 ? 1 : month + 1; const nextYear = month === 12 ? year + 1 : year; return new Date(nextYear, nextMonth - 1, 1, 23, 59, 59); }; const toTimestamp = (val) => { if (val == null || val === '') return 0; const n = Number(val); if (Number.isFinite(n) && n > 1e11) return n; const d = new Date(val); return Number.isNaN(d.getTime()) ? 0 : d.getTime(); }; const matchesGroupPeriod = (item, group, period) => { const g = normalizeGroupName(item.c_ssqpjt); if (g !== group) return false; const fillMonth = extractFillMonth(item); if (fillMonth) return fillMonth === period; return extractPeriod(item, period) === period; }; const groupHasAnySubmission = (lists, group, period) => lists.some((list) => list.some((item) => matchesGroupPeriod(item, group, period))); const groupHasApproved = (approvedList, group, period) => approvedList.some((item) => matchesGroupPeriod(item, group, period)); const groupHasRejected = (rejectedList, group, period) => rejectedList.some((item) => matchesGroupPeriod(item, group, period)); const buildRoute = (tab, query = {}) => buildManageRoute({ tab, ...query }); 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 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})`; }; /** * 生成当前时刻所有适用的通知(幂等,同 c_ywid 不会重复入库) */ export const buildNotifications = ({ pendingList = [], approvedList = [], rejectedList = [], } = {}, { currentGroup = '', } = {}) => { const notifications = []; const now = Date.now(); const currentPeriod = getCurrentPeriod(); const deadline = getPeriodDeadlineDate(currentPeriod); const daysLeft = Math.ceil((deadline.getTime() - now) / (86400000)); const reminderTbtx = getReminderTbtx(daysLeft); // ── 待审核 ──────────────────────────────────────────────── aggregateReportRecords(pendingList, { fallbackPeriod: currentPeriod }).forEach((record) => { const group = notifyDisplayGroup(record); if (currentGroup && group !== currentGroup) return; const latest = record.items?.reduce((best, item) => { const t = toTimestamp(getFillTime(item)); return t >= toTimestamp(getFillTime(best)) ? item : best; }, record.items?.[0]); const title = `${group} ${record.period} 资产月报(${record.count} 条)待审核`; notifications.push({ ywid: notifyAggregateYwid('pending', record), time: record.submittedAt, type: '待审核', title, content: title, timestamp: toTimestamp(getFillTime(latest)), group, tzlx: NOTIFY_TZLX.PENDING, tbtx: 0, shjg: 0, route: buildRoute('audit', { sub: 'pending', group: record.qingpuGroup || group, period: record.period, }), }); }); // ── 已通过 ──────────────────────────────────────────────── aggregateReportRecords(approvedList, { fallbackPeriod: currentPeriod }).forEach((record) => { const group = notifyDisplayGroup(record); if (currentGroup && group !== currentGroup) return; const latest = record.items?.reduce((best, item) => { const t = toTimestamp(getAuditTime(item)); return t >= toTimestamp(getAuditTime(best)) ? item : best; }, record.items?.[0]); const title = `${record.period} 月报已通过(${record.group},${record.count} 条)`; notifications.push({ ywid: notifyAggregateYwid('approved', record), time: formatDateTime(getAuditTime(latest)), type: '审核结果', title, content: title, timestamp: toTimestamp(getAuditTime(latest)), group, tzlx: NOTIFY_TZLX.AUDIT_RESULT, tbtx: 0, shjg: 1, route: buildRoute('archive', { sub: 'history', group: record.qingpuGroup || group, period: record.period, status: '已通过', }), }); }); // ── 已驳回 ──────────────────────────────────────────────── aggregateReportRecords(rejectedList, { fallbackPeriod: currentPeriod }).forEach((record) => { const group = notifyDisplayGroup(record); if (currentGroup && group !== currentGroup) return; const latest = record.items?.reduce((best, item) => { const t = toTimestamp(getAuditTime(item)); return t >= toTimestamp(getAuditTime(best)) ? item : best; }, record.items?.[0]); const reason = getRejectReason(latest); const title = `${record.period} 月报已被驳回(${record.group})${reason ? `:${reason}` : ''}`; notifications.push({ ywid: notifyAggregateYwid('rejected', record), time: formatDateTime(getAuditTime(latest)), type: '审核结果', title, content: title, timestamp: toTimestamp(getAuditTime(latest)), group, tzlx: NOTIFY_TZLX.AUDIT_RESULT, tbtx: 0, shjg: 0, route: buildRoute('archive', { sub: 'history', group: record.qingpuGroup || group, period: record.period, status: '已驳回', }), }); }); // ── 填报提醒(截止前 7/3/1 天,尚未提交的集团)──────────── if (reminderTbtx > 0) { const allLists = [pendingList, approvedList, rejectedList]; GROUP_ORDER.forEach((group) => { if (currentGroup && group !== currentGroup) return; if (groupHasAnySubmission(allLists, group, currentPeriod)) return; const title = reminderTitle(currentPeriod, group, daysLeft); notifications.push({ ywid: notifyYwid('reminder', currentPeriod, group, String(reminderTbtx)), time: formatDateTime(now), type: '填报提醒', title, content: title, timestamp: now, group, tzlx: NOTIFY_TZLX.REMINDER, tbtx: reminderTbtx, shjg: 0, route: buildRoute('archive'), }); }); } // ── 逾期催办 ────────────────────────────────────────────── const prevPeriod = getPreviousPeriod(currentPeriod); const prevDeadline = getPeriodDeadlineDate(prevPeriod); if (now > prevDeadline.getTime()) { GROUP_ORDER.forEach((group) => { if (currentGroup && group !== currentGroup) return; if (groupHasApproved(approvedList, group, prevPeriod)) return; const rejected = groupHasRejected(rejectedList, group, prevPeriod); const hasAny = groupHasAnySubmission( [pendingList, approvedList, rejectedList], group, prevPeriod ); if (!hasAny) { const title = `${prevPeriod} 月报已逾期,请补报(${group})`; notifications.push({ ywid: notifyYwid('overdue', prevPeriod, group), time: formatDateTime(prevDeadline), type: '逾期催办', title, content: title, timestamp: prevDeadline.getTime(), group, tzlx: NOTIFY_TZLX.OVERDUE, tbtx: 0, shjg: 0, route: buildRoute('archive'), }); return; } if (rejected) { const title = `${prevPeriod} 月报被驳回且未重报,请尽快处理(${group})`; notifications.push({ ywid: notifyYwid('overdue-reject', prevPeriod, group), time: formatDateTime(now), type: '逾期催办', title, content: title, timestamp: now, group, tzlx: NOTIFY_TZLX.OVERDUE, tbtx: 0, shjg: 0, route: buildRoute('archive', { sub: 'history', group, period: prevPeriod, status: '已驳回' }), }); } }); } // ── 租约到期/预警 ───────────────────────────────────────── const expiryItems = approvedList .filter((item) => item.status === 'red' || item.status === 'orange') .slice(0, 8); expiryItems.forEach((item) => { const label = item.c_bh || item.c_qymc || '资产'; const withinOneMonth = item.status === 'red'; const group = normalizeGroupName(item.c_ssqpjt) || '未知'; if (currentGroup && group !== currentGroup) return; const title = `${label} 租约${withinOneMonth ? '将于一个月内到期' : '将于三个月内到期'},请关注`; notifications.push({ ywid: `expiry:${item.id}`, time: formatDateTime(item.update_time || now), type: withinOneMonth ? '租约到期' : '租约预警', title, content: title, timestamp: toTimestamp(item.update_time || now), group, tzlx: withinOneMonth ? NOTIFY_TZLX.LEASE_EXPIRY : NOTIFY_TZLX.LEASE_WARNING, tbtx: 0, shjg: 0, route: buildRoute('assets'), }); }); return notifications.sort((a, b) => b.timestamp - a.timestamp); };