parent
d45dc19261
commit
cd2004c5b2
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private function connection(): string
|
||||
{
|
||||
return (string) config('upstream_mirrors.connection', 'business_mirror');
|
||||
}
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (app()->environment('testing')
|
||||
|| (string) config('system_database.mode', 'bootstrap') === 'bootstrap') {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema = Schema::connection($this->connection());
|
||||
if (! $schema->hasTable('oa_process_account_mapping')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mapping = json_decode(
|
||||
(string) file_get_contents(base_path('resources/data/oa_process_account_mapping.json')),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR,
|
||||
);
|
||||
$codes = array_column($mapping, 'account_code');
|
||||
if ($mapping === [] || count($codes) !== count(array_unique($codes))) {
|
||||
throw new \RuntimeException('流程科目映射快照为空或科目编码重复。');
|
||||
}
|
||||
|
||||
$database = $schema->getConnection();
|
||||
$now = now();
|
||||
foreach (array_chunk($mapping, 100) as $chunk) {
|
||||
$database->table('oa_process_account_mapping')->upsert(
|
||||
array_map(static fn (array $row): array => [
|
||||
'account_code' => (string) $row['account_code'],
|
||||
'account_name' => (string) $row['account_name'],
|
||||
'expense_category' => (string) $row['expense_category'],
|
||||
'second_level' => $row['second_level']
|
||||
?? ((string) $row['expense_category'] . '(未细分)'),
|
||||
'third_level' => $row['third_level'] ?? '未细分',
|
||||
'in_fee_ratio' => (bool) $row['in_fee_ratio'],
|
||||
'allocation' => $row['allocation'] ?? null,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
], $chunk),
|
||||
['account_code'],
|
||||
[
|
||||
'account_name',
|
||||
'expense_category',
|
||||
'second_level',
|
||||
'third_level',
|
||||
'in_fee_ratio',
|
||||
'allocation',
|
||||
'updated_at',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
$database->table('oa_process_account_mapping')
|
||||
->whereNotIn('account_code', $codes)
|
||||
->delete();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Confirmed business mapping is kept during rollback.
|
||||
}
|
||||
};
|
||||
@ -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 个科目和补充的 `5601.11.01 货代费`、`1221.07 种菜备用金`,共 193 个科目,其中 37 个进费比科目和 125 个分摊规则。页面按打款时间提供整月选择并默认展示上个月 OA 流程中审批状态为“已完成”、涉及付款且出纳已打款的明细,不涉及付款或出纳未打款的数据不进入汇总,按费用大类和二级汇总进费比/不进费比/未映射金额,点击二级查看三级汇总与 9 列明细模板。审批完成时间写入 `ding_process_detail_mirror.approval_finished_at`,页面月份筛选使用 `ding_process_detail_mirror.paid_at`;钉钉副本把费用明细表保存为未命名字段时,按配置中的金额列识别包含该列的未命名表格组件,避免只抓到付款明细而漏掉费用科目和金额。明细行没有科目时,使用同一审批表单表头的 `科目编码 / 科目名称` 回填;行级科目始终优先。 |
|
||||
| 本次接入 | 财务经营报表 › 管理费用使用 `oa_process_account_mapping` 保存流程科目映射;当前快照包含原工作簿 191 个科目和补充的 `5601.11.01 货代费`、`1221.07 种菜备用金`、`5602.11.03.19 AI阿里云软件费`,共 194 个科目,其中 37 个进费比科目和 126 个分摊规则。页面按打款时间提供整月选择并默认展示上个月 OA 流程中审批状态为“已完成”、涉及付款且出纳已打款的明细,不涉及付款或出纳未打款的数据不进入汇总,按费用大类和二级汇总进费比/不进费比/未映射金额,点击二级查看三级汇总与 9 列明细模板。审批完成时间写入 `ding_process_detail_mirror.approval_finished_at`,页面月份筛选使用 `ding_process_detail_mirror.paid_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` 不变而被漏掉;原地改写同一组件仍由每周全量校准兜底。付款状态只按钉钉“出纳打款”节点判断,分为“出纳状态待同步 / 出纳未开始 / 出纳待打款 / 出纳退回 / 出纳已打款 / 流程终止 / 不涉及付款”。金额差异不参与付款状态判断,另以“付款差额 = 费用合计 − 已付款合计”展示。不能因为金额存在少量差异就覆盖出纳节点事实,也不能因为出纳已通过就隐藏金额差异。
|
||||
|
||||
|
||||
@ -1928,5 +1928,15 @@
|
||||
"in_fee_ratio": true,
|
||||
"allocation": null,
|
||||
"source_row": 194
|
||||
},
|
||||
{
|
||||
"account_code": "5602.11.03.19",
|
||||
"account_name": "AI阿里云软件费",
|
||||
"expense_category": "管理费用",
|
||||
"second_level": "技术服务费",
|
||||
"third_level": "AI算力软件",
|
||||
"in_fee_ratio": false,
|
||||
"allocation": "分摊",
|
||||
"source_row": 195
|
||||
}
|
||||
]
|
||||
|
||||
@ -130,10 +130,10 @@ class ManagementExpensesPageDataTest extends TestCase
|
||||
JSON_THROW_ON_ERROR,
|
||||
);
|
||||
|
||||
$this->assertCount(193, $mapping);
|
||||
$this->assertCount(193, array_unique(array_column($mapping, 'account_code')));
|
||||
$this->assertCount(194, $mapping);
|
||||
$this->assertCount(194, array_unique(array_column($mapping, 'account_code')));
|
||||
$this->assertSame(37, count(array_filter($mapping, static fn (array $row): bool => $row['in_fee_ratio'])));
|
||||
$this->assertSame(125, count(array_filter($mapping, static fn (array $row): bool => $row['allocation'] !== null)));
|
||||
$this->assertSame(126, count(array_filter($mapping, static fn (array $row): bool => $row['allocation'] !== null)));
|
||||
$mappingByCode = array_column($mapping, null, 'account_code');
|
||||
$this->assertSame('往来', $mappingByCode['2202.01']['expense_category']);
|
||||
$this->assertSame('往来', $mappingByCode['2202.01']['second_level']);
|
||||
@ -147,6 +147,11 @@ class ManagementExpensesPageDataTest extends TestCase
|
||||
$this->assertSame('推广费用', $mappingByCode['1221.07']['second_level']);
|
||||
$this->assertSame('空包费', $mappingByCode['1221.07']['third_level']);
|
||||
$this->assertTrue($mappingByCode['1221.07']['in_fee_ratio']);
|
||||
$this->assertSame('管理费用', $mappingByCode['5602.11.03.19']['expense_category']);
|
||||
$this->assertSame('技术服务费', $mappingByCode['5602.11.03.19']['second_level']);
|
||||
$this->assertSame('AI算力软件', $mappingByCode['5602.11.03.19']['third_level']);
|
||||
$this->assertFalse($mappingByCode['5602.11.03.19']['in_fee_ratio']);
|
||||
$this->assertSame('分摊', $mappingByCode['5602.11.03.19']['allocation']);
|
||||
$this->assertNotContains('报销', array_column($mapping, 'expense_category'));
|
||||
$this->assertNotContains('订单退款', array_column($mapping, 'expense_category'));
|
||||
$this->assertContains('分摊', array_column($mapping, 'allocation'));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user