fix: 管理费用汇总排除不涉及付款和未打款流程 #802
@ -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` 不变而被漏掉;原地改写同一组件仍由每周全量校准兜底。付款状态只按钉钉“出纳打款”节点判断,分为“出纳状态待同步 / 出纳未开始 / 出纳待打款 / 出纳退回 / 出纳已打款 / 流程终止 / 不涉及付款”。金额差异不参与付款状态判断,另以“付款差额 = 费用合计 − 已付款合计”展示。不能因为金额存在少量差异就覆盖出纳节点事实,也不能因为出纳已通过就隐藏金额差异。
|
||||
|
||||
|
||||
@ -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'] ?? '') !== '') {
|
||||
|
||||
@ -518,8 +518,9 @@
|
||||
<div>
|
||||
<h2>{{ $pageTitle ?? '管理费用' }}</h2>
|
||||
<p>
|
||||
数据源为 OA 流程中审批状态为“已完成”的审批明细。科目规则来自“流程科目映射”,
|
||||
默认展示本月审批完成的数据;二级汇总区分进费比、不进费比和未映射科目,
|
||||
数据源为 OA 流程中审批已完成、涉及付款且出纳已打款的明细。科目规则来自“流程科目映射”,
|
||||
默认展示本月审批完成的数据;不涉及付款或出纳未打款的审批不计入汇总,
|
||||
二级汇总区分进费比、不进费比和未映射科目,
|
||||
点击二级可查看三级和明细模板。
|
||||
</p>
|
||||
</div>
|
||||
@ -532,7 +533,7 @@
|
||||
@endif
|
||||
|
||||
<div class="me-stats">
|
||||
<div class="me-stats__item"><span>已审批金额</span><strong>¥{{ $money($summary['amount_total'] ?? 0) }}</strong></div>
|
||||
<div class="me-stats__item"><span>已打款金额</span><strong>¥{{ $money($summary['amount_total'] ?? 0) }}</strong></div>
|
||||
<div class="me-stats__item"><span>进费比金额</span><strong>¥{{ $money($summary['in_fee_ratio_amount'] ?? 0) }}</strong></div>
|
||||
<div class="me-stats__item"><span>不进费比金额</span><strong>¥{{ $money($summary['non_fee_ratio_amount'] ?? 0) }}</strong></div>
|
||||
</div>
|
||||
@ -543,7 +544,7 @@
|
||||
<span class="material-icons-outlined" aria-hidden="true">account_tree</span>
|
||||
二级汇总
|
||||
</span>
|
||||
<span class="me-panel__hint">点击二级行查看三级和已审批明细</span>
|
||||
<span class="me-panel__hint">点击二级行查看三级和已打款审批明细</span>
|
||||
</div>
|
||||
<div class="me-summary-wrap">
|
||||
<table class="me-table">
|
||||
@ -581,7 +582,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" class="me-empty">当前筛选条件没有已审批的 OA 流程明细。</td></tr>
|
||||
<tr><td colspan="7" class="me-empty">当前筛选条件没有符合汇总口径的 OA 流程明细。</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
@ -679,9 +680,9 @@
|
||||
<div class="me-panel__head">
|
||||
<span class="me-panel__title" id="me-detail-title">
|
||||
<span class="material-icons-outlined" aria-hidden="true">table_view</span>
|
||||
OA 已审批明细模板
|
||||
OA 已打款审批明细模板
|
||||
</span>
|
||||
<span class="me-panel__hint">仅展示审批状态为“已完成”的流程明细</span>
|
||||
<span class="me-panel__hint">仅展示审批已完成、涉及付款且出纳已打款的流程明细</span>
|
||||
</div>
|
||||
<div class="me-detail-wrap">
|
||||
<table class="me-table">
|
||||
@ -710,7 +711,7 @@
|
||||
<td>{{ $row['allocation'] }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="9" class="me-empty">当前筛选条件没有已审批的 OA 流程明细。</td></tr>
|
||||
<tr><td colspan="9" class="me-empty">当前筛选条件没有符合汇总口径的 OA 流程明细。</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
@ -751,7 +752,7 @@
|
||||
<div class="me-dialog__head">
|
||||
<div class="me-dialog__title">
|
||||
<strong id="management-expenses-dialog-title">二级明细</strong>
|
||||
<span id="management-expenses-dialog-subtitle">三级汇总与 OA 已审批明细</span>
|
||||
<span id="management-expenses-dialog-subtitle">三级汇总与 OA 已打款审批明细</span>
|
||||
</div>
|
||||
<button type="button" class="me-dialog__close" id="management-expenses-dialog-close" aria-label="关闭明细">×</button>
|
||||
</div>
|
||||
@ -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
|
||||
|
||||
@ -74,7 +74,8 @@ class ManagementExpensesPageTest extends FeatureTestCase
|
||||
]))->assertOk()
|
||||
->assertSee('管理费用')
|
||||
->assertSee('二级汇总')
|
||||
->assertSee('OA 已审批明细模板')
|
||||
->assertSee('OA 已打款审批明细模板')
|
||||
->assertSee('不涉及付款或出纳未打款的审批不计入汇总')
|
||||
->assertSee('审批时间')
|
||||
->assertSee('进费比')
|
||||
->assertSee('不进费比')
|
||||
|
||||
@ -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']);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user