diff --git a/PRODUCT.md b/PRODUCT.md
index e1293bc..0f266e6 100644
--- a/PRODUCT.md
+++ b/PRODUCT.md
@@ -30,7 +30,7 @@ web
- “进行中项目”项目明细按业务导出表展示审批标题、审批编号、需求人、需求部门、平台、品牌、类目、SPU、需求类型、标准时效、当前节点与负责人;需求人取流程发起人,需求部门按 HR 人员架构匹配。
- 美术设计流程的明细开始时间取“品牌策划主管分配任务”节点创建时间,结束时间取“品牌策划主管审核”节点完成时间;内容视频流程的明细开始时间取已确认的“内容主管分配任务”节点创建时间,结束时间按规则分支取“编导审核”或“内容主管审核”节点完成时间。内容流程中多个主管分配节点、编导制作节点及普通领导者任务均不等同于最终审核;未知 activityId 不得仅凭负责人身份、AGREE 结果或完成状态推断为审核节点。
- 美术设计申请流程的 `ding_WorkflowTask.activityId` 按已确认流程定义映射为精确中文节点名并写入本地快照;未知节点继续使用同步字段推断或明确回退,不改写上游只读数据。
-- 钉钉流程快照每小时从只读报表库的 `ding_Workflow`、`ding_WorkflowTask`、`ding_WorkflowScheme` 联合增量同步,只接收已批准的美术设计申请和内容视频流程;三表复合游标写入现有同步状态并继续复用同一批生产快照数据。
+- 钉钉流程快照每小时从只读报表库的 `ding_Workflow`、`ding_WorkflowTask`、`ding_WorkflowScheme` 联合增量同步,只接收已批准的美术设计申请和内容视频流程;三表复合游标写入现有同步状态,并每 24 小时全量校准流程、品牌和商品 ID,修正上游未推进游标的历史表单改动。
- “本月上新”只读取表单字段“需求类型”为“新品”的六品牌美术设计流程,不以流程标题关键词判断,也不计“优化”等其他需求类型;分子为本月整条流程已完结数量,分母为本月已完结数量与当前进行中数量之和;其中进行中流程直接复用“进行中项目”的美术设计流程状态、时间与当前品牌中心处理人条件。
- “按时交付”只统计对应主管/规则审核节点在本月完成、审批状态为已完结且审批结论未拒绝的美术设计与内容视频流程;待计算流程不进入分母。详情页可在全部流程、美术设计流程和内容视频流程之间筛选,公式、数量与明细必须共用当前筛选口径。
- “首轮通过”读取作品过稿数据,分子为 V1 版本审核通过的作品数,分母为当前已通过的作品数;详情页列出部门范围内所有含 V1 的作品,搜索只过滤明细,不改变顶部完整口径。
diff --git a/components/department-dashboard/PerformanceConversionDashboard.test.tsx b/components/department-dashboard/PerformanceConversionDashboard.test.tsx
index 3f0690e..993e854 100644
--- a/components/department-dashboard/PerformanceConversionDashboard.test.tsx
+++ b/components/department-dashboard/PerformanceConversionDashboard.test.tsx
@@ -16,7 +16,11 @@ const emptyList = {
page: 1,
pageSize: 20,
hasMore: false,
- lastWorkflowSyncedAt: '2026-09-17T00:00:00.000Z',
+ workflowSync: {
+ status: 'ready' as const,
+ lastCompletedAt: '2099-09-17T01:00:00.000Z',
+ lastFullReconciledAt: '2099-09-17T00:00:00.000Z',
+ },
}
const workflowItem = {
@@ -65,6 +69,33 @@ describe('PerformanceConversionDashboard filters', () => {
expect(requestUrl).toContain('completedTo=2026-07-31')
expect(window.location.search).toContain('completedFrom=2026-07-01')
expect(window.location.search).toContain('completedTo=2026-07-31')
+ expect(screen.getByText('流程与商品 ID 每日增量同步')).toBeInTheDocument()
+ expect(screen.getByText('最近成功 2099/09/17 09:00')).toBeInTheDocument()
+ })
+
+ it.each([
+ ['syncing', '正在同步流程与商品 ID'],
+ ['error', '流程与商品 ID 同步异常'],
+ ['pending', '等待首次流程与商品 ID 同步'],
+ ] as const)('shows the %s workflow synchronization state', async (status, label) => {
+ global.fetch = jest.fn().mockResolvedValue({
+ ok: true,
+ json: async () => ({
+ success: true,
+ data: {
+ ...emptyList,
+ workflowSync: {
+ status,
+ lastCompletedAt: status === 'pending' ? null : '2099-09-17T01:00:00.000Z',
+ lastFullReconciledAt: status === 'pending' ? null : '2099-09-17T00:00:00.000Z',
+ },
+ },
+ }),
+ } as Response)
+
+ render(
+ + {view.label} +
+{view.detail}
+当前品牌
{appliedFilters.completedFrom || appliedFilters.completedTo ? '区间内 ' : ''}{selectedBrandCount} 个有效流程商品 ID{listState.data?.invalidProductIdCount ? ` · ${listState.data.invalidProductIdCount} 条流程未填写有效 ID` : ''}
当前品牌
{appliedFilters.completedFrom || appliedFilters.completedTo ? '区间内 ' : ''}{selectedBrandCount} 个有效流程商品 ID{listState.data?.invalidProductIdCount ? ` · ${listState.data.invalidProductIdCount} 条流程未填写有效 ID` : ''}