diff --git a/docs/上游业务镜像清单.md b/docs/上游业务镜像清单.md index 24fc68b6..1ca860eb 100644 --- a/docs/上游业务镜像清单.md +++ b/docs/上游业务镜像清单.md @@ -34,7 +34,7 @@ | 本次接入 | 普通商品资料的工作平台采购价格接入 `jiansun.dbo.pm_priceAdjustment`(镜像 `js_pm_price_adjustment`):按 `skuCode` 关联 `jst_sku.sku_id`,取 `effectTime` 不晚于当前时间的最新 `purchasePrice`,并在页面和导出中标记“工作平台价格”;源表没有可靠更新时间,按 `Id` 稳定键追加,首次全量完成前只显示普通商品自身采购价。 | | 待有效凭据后首次同步 | 普通商品当前成本价、商品类型和更新时间继续读取 `operations_replica.rpt.jst_sku`;`rpt.fys_skuCostPriceUpdate` 是一 SKU 一行的旧快照,不能充当变价历史。完整成本价变更仍读取聚水潭经典开放平台“商品操作日志查询”接口 `worklog.query`,只镜像操作内容含“采购价”的记录到 `jushuitan_cost_price_history_rows`;经典接口必须在服务器私有配置中使用同一应用的 `JUSHUITAN_PARTNER_ID / JUSHUITAN_PARTNER_KEY / JUSHUITAN_TOKEN`,不得把新开放平台的 App Key / App Secret / Access Token 混用。页面在首次同步完成前明确显示待同步或失败,不把 0 条解释为真实无历史。 | | 本次接入 | 流程明细读取 `operations_replica/rpt.ding_Workflow + rpt.ding_WorkflowScheme` 的结构化审批主表、费用明细和付款明细,写入 `ding_process_detail_mirror` 与 `ding_process_payment_mirror`;主表的 `originatorDeptName` 作为发起人部门单独保留。禁止先把明细表和付款表铺平成 CSV 后逐行求和;付款明细必须按“对应序号”关联到代表明细行,但合计始终读取独立付款事实。每个纳入范围的源审批至少保留一条页面记录,没有费用金额的审批以空金额显示,不静默隐藏。首次全量先运行 `business-mirror:sync-ding-process-details --full --dry-run` 校验,再使用 `business-mirror:sync-ding-process-details --full` 发布;日常通过 `updateAt/createAt` 48 小时重叠增量同步。当前纳入日常报销、应付、线上代发、种菜、快递面单、固定资产、日常物品、样品、一次性采买、板材、工资、外贸费用和种菜充值等 14 个财务流程;发现新的明细流程编码时全量同步报错并保留上一版。 | -| 本次接入 | 财务经营报表 › 管理费用使用 `oa_process_account_mapping` 保存“流程科目映射”工作簿中的 191 个科目编码、科目名称、费用大类、二级、三级、是否进费比和是否分摊规则;页面按审批完成时间默认展示本月 OA 流程中状态为“已完成”的审批明细,不要求出纳已完成打款,按费用大类和二级汇总进费比/不进费比/未映射金额,点击二级查看三级汇总与 9 列明细模板。审批完成时间写入 `ding_process_detail_mirror.approval_finished_at`。 | +| 本次接入 | 财务经营报表 › 管理费用使用 `oa_process_account_mapping` 保存“流程科目映射”工作簿中的 191 个科目编码、科目名称、费用大类、二级、三级、是否进费比和是否分摊规则;页面按审批完成时间默认展示本月 OA 流程中审批状态为“已完成”、涉及付款且出纳已打款的明细,不涉及付款或出纳未打款的数据不进入汇总,按费用大类和二级汇总进费比/不进费比/未映射金额,点击二级查看三级汇总与 9 列明细模板。审批完成时间写入 `ding_process_detail_mirror.approval_finished_at`。 | 流程明细另设独立定时任务,避免与每分钟科目映射消费争用同一上游目录时被静默跳过。正式发布脚本会覆盖安装 `/etc/cron.d/caiwu-ding-process-details`:每 5 分钟尝试运行 `business-mirror:sync-ding-process-details`,使用独立调度锁并在共用上游锁上最多等待 15 分钟;`scripts/install-ding-process-sync-cron.ps1` 仅作为手工修复入口。任务时间和镜像状态统一使用平台固定时区,审批等待时长按实际小时展示。OA 页面每小时最多把一次当前页“审批中”的审批编号写入定向刷新队列,由每分钟检查一次的批处理命令消费;其他时间直接展示最近镜像,已完成流程不进入即时刷新链路,仍由常规增量和每周全量校准处理付款等后续变化。增量同步除跟进审批主表更新时间外,还以 `ding_WorkflowScheme` 的数字 `id` 高水位发现后补表单行,避免付款明细等新组件写入后主审批 `updateAt` 不变而被漏掉;原地改写同一组件仍由每周全量校准兜底。付款状态只按钉钉“出纳打款”节点判断,分为“出纳状态待同步 / 出纳未开始 / 出纳待打款 / 出纳退回 / 出纳已打款 / 流程终止 / 不涉及付款”。金额差异不参与付款状态判断,另以“付款差额 = 费用合计 − 已付款合计”展示。不能因为金额存在少量差异就覆盖出纳节点事实,也不能因为出纳已通过就隐藏金额差异。 diff --git a/modules/EcommerceFinance/View/ManagementExpensesPageData.php b/modules/EcommerceFinance/View/ManagementExpensesPageData.php index c9f51864..be7a19f2 100644 --- a/modules/EcommerceFinance/View/ManagementExpensesPageData.php +++ b/modules/EcommerceFinance/View/ManagementExpensesPageData.php @@ -292,6 +292,11 @@ class ManagementExpensesPageData 'd.account_code', ) ->where('d.status', '已完成') + ->where('d.cashier_status', 'completed') + ->where(function (Builder $paymentSource): void { + $paymentSource->whereNull('d.payment_source_state') + ->orWhere('d.payment_source_state', '<>', 'not_applicable'); + }) ->whereNotNull('d.approval_finished_at'); if (($filters['approved_start'] ?? '') !== '') { diff --git a/resources/views/business/pages/financial-operations/management-expenses.blade.php b/resources/views/business/pages/financial-operations/management-expenses.blade.php index c74ab1fc..55b6814f 100644 --- a/resources/views/business/pages/financial-operations/management-expenses.blade.php +++ b/resources/views/business/pages/financial-operations/management-expenses.blade.php @@ -518,8 +518,9 @@

{{ $pageTitle ?? '管理费用' }}

- 数据源为 OA 流程中审批状态为“已完成”的审批明细。科目规则来自“流程科目映射”, - 默认展示本月审批完成的数据;二级汇总区分进费比、不进费比和未映射科目, + 数据源为 OA 流程中审批已完成、涉及付款且出纳已打款的明细。科目规则来自“流程科目映射”, + 默认展示本月审批完成的数据;不涉及付款或出纳未打款的审批不计入汇总, + 二级汇总区分进费比、不进费比和未映射科目, 点击二级可查看三级和明细模板。

@@ -532,7 +533,7 @@ @endif
-
已审批金额¥{{ $money($summary['amount_total'] ?? 0) }}
+
已打款金额¥{{ $money($summary['amount_total'] ?? 0) }}
进费比金额¥{{ $money($summary['in_fee_ratio_amount'] ?? 0) }}
不进费比金额¥{{ $money($summary['non_fee_ratio_amount'] ?? 0) }}
@@ -543,7 +544,7 @@ 二级汇总 - 点击二级行查看三级和已审批明细 + 点击二级行查看三级和已打款审批明细
@@ -581,7 +582,7 @@ @empty - + @endforelse
当前筛选条件没有已审批的 OA 流程明细。
当前筛选条件没有符合汇总口径的 OA 流程明细。
@@ -679,9 +680,9 @@
- OA 已审批明细模板 + OA 已打款审批明细模板 - 仅展示审批状态为“已完成”的流程明细 + 仅展示审批已完成、涉及付款且出纳已打款的流程明细
@@ -710,7 +711,7 @@ @empty - + @endforelse
{{ $row['allocation'] }}
当前筛选条件没有已审批的 OA 流程明细。
当前筛选条件没有符合汇总口径的 OA 流程明细。
@@ -751,7 +752,7 @@
二级明细 - 三级汇总与 OA 已审批明细 + 三级汇总与 OA 已打款审批明细
@@ -927,7 +928,7 @@ const loadDetails = async () => { if (!activeGroup) return; - detailStatus.textContent = '正在读取 OA 已审批明细…'; + detailStatus.textContent = '正在读取符合口径的 OA 审批明细…'; detailRows.replaceChildren(); const params = new URLSearchParams(window.location.search); params.delete('page'); @@ -955,7 +956,7 @@ prevButton.disabled = activePage <= 1; nextButton.disabled = activePage >= lastPage; detailStatus.textContent = activeThird === '' - ? '当前二级全部已审批明细' + ? '当前二级全部已打款审批明细' : `三级:${activeThird}`; } catch (error) { detailStatus.textContent = '明细读取失败,请稍后重试。'; @@ -973,7 +974,7 @@ activeThird = ''; activePage = 1; title.textContent = `${activeGroup.category} / ${activeGroup.secondLevel}`; - subtitle.textContent = '点击三级行继续查看对应 OA 已审批明细'; + subtitle.textContent = '点击三级行继续查看对应已打款审批明细'; const groupRows = secondLevelRows.find((row) => row.expense_category === activeGroup.category && row.second_level === activeGroup.secondLevel diff --git a/tests/Feature/Business/ManagementExpensesPageTest.php b/tests/Feature/Business/ManagementExpensesPageTest.php index 17e99432..8b3f767a 100644 --- a/tests/Feature/Business/ManagementExpensesPageTest.php +++ b/tests/Feature/Business/ManagementExpensesPageTest.php @@ -74,7 +74,8 @@ class ManagementExpensesPageTest extends FeatureTestCase ]))->assertOk() ->assertSee('管理费用') ->assertSee('二级汇总') - ->assertSee('OA 已审批明细模板') + ->assertSee('OA 已打款审批明细模板') + ->assertSee('不涉及付款或出纳未打款的审批不计入汇总') ->assertSee('审批时间') ->assertSee('进费比') ->assertSee('不进费比') diff --git a/tests/Unit/EcommerceFinance/ManagementExpensesPageDataTest.php b/tests/Unit/EcommerceFinance/ManagementExpensesPageDataTest.php index 12445f70..b3c194fb 100644 --- a/tests/Unit/EcommerceFinance/ManagementExpensesPageDataTest.php +++ b/tests/Unit/EcommerceFinance/ManagementExpensesPageDataTest.php @@ -40,30 +40,30 @@ class ManagementExpensesPageDataTest extends TestCase parent::tearDown(); } - public function test_page_groups_only_approved_oa_rows_by_second_level_and_fee_ratio(): void + public function test_page_groups_only_approved_paid_oa_rows_by_second_level_and_fee_ratio(): void { $data = $this->pageData->page(Request::create( '/1/workspace/financial-operations/management-expenses', 'GET', )); - $this->assertSame(5, $data['rows']->total()); - $this->assertSame(445.0, $data['summary']['amount_total']); - $this->assertSame(270.0, $data['summary']['in_fee_ratio_amount']); - $this->assertSame(125.0, $data['summary']['non_fee_ratio_amount']); + $this->assertSame(3, $data['rows']->total()); + $this->assertSame(350.0, $data['summary']['amount_total']); + $this->assertSame(200.0, $data['summary']['in_fee_ratio_amount']); + $this->assertSame(100.0, $data['summary']['non_fee_ratio_amount']); $this->assertSame(50.0, $data['summary']['unmapped_amount']); - $this->assertSame(5, $data['summary']['row_count']); + $this->assertSame(3, $data['summary']['row_count']); $this->assertSame('2026-10-01', $data['filters']['approved_start']); $this->assertSame('2026-10-31', $data['filters']['approved_end']); $groups = collect($data['secondLevelSummary'])->keyBy( static fn (array $row): string => $row['expense_category'] . '|' . $row['second_level'], ); - $this->assertSame(125.0, $groups['管理费用|办公费']['amount_total']); + $this->assertSame(100.0, $groups['管理费用|办公费']['amount_total']); $this->assertSame(1, $groups['管理费用|办公费']['approval_count']); - $this->assertSame(270.0, $groups['销售费用|技术服务费']['in_fee_ratio_amount']); + $this->assertSame(200.0, $groups['销售费用|技术服务费']['in_fee_ratio_amount']); $this->assertSame(0.0, $groups['销售费用|技术服务费']['non_fee_ratio_amount']); - $this->assertSame(270.0, $groups['销售费用|技术服务费']['amount_total']); + $this->assertSame(200.0, $groups['销售费用|技术服务费']['amount_total']); $this->assertSame(50.0, $groups['未映射科目|未映射科目']['unmapped_amount']); $this->assertCount(3, $data['secondLevelSummary']); } @@ -78,11 +78,11 @@ class ManagementExpensesPageDataTest extends TestCase $this->assertSame('', $data['filters']['approved_start']); $this->assertSame('', $data['filters']['approved_end']); - $this->assertSame(6, $data['rows']->total()); - $this->assertSame(465.0, $data['summary']['amount_total']); + $this->assertSame(4, $data['rows']->total()); + $this->assertSame(370.0, $data['summary']['amount_total']); } - public function test_detail_template_filters_and_paginates_approved_rows(): void + public function test_detail_template_filters_and_paginates_approved_paid_rows(): void { $data = $this->pageData->details(Request::create( '/1/workspace/financial-operations/management-expenses/details', @@ -94,14 +94,14 @@ class ManagementExpensesPageDataTest extends TestCase ], )); - $this->assertSame(2, $data['pagination']['total']); - $this->assertSame(125.0, $data['summary']['amount_total']); + $this->assertSame(1, $data['pagination']['total']); + $this->assertSame(100.0, $data['summary']['amount_total']); $this->assertSame( ['审批编号', '部门', '用途', '分类', '二级', '三级', '金额', '是否进费比', '是否分摊'], array_values(ManagementExpensesPageData::DETAIL_COLUMNS), ); $this->assertSame( - ['5602.04', '5602.04'], + ['5602.04'], array_column($data['rows'], 'business_id'), ); $this->assertSame('不进费比', $data['rows'][0]['fee_ratio']);