CaiWuXiTong/tests/Unit/EcommerceFinance/SupplierBookkeepingWorkOrderRepositoryTest.php
limeng ed58e8ef43 供应商应付:新增厂家记账,接入聚工单记账工单(2026-09-01 起)
- 复用已登记镜像 jiansun.dbo.jst_workOrderInfo,不新增上游链路
- 口径与聚工单「记账」视图一致:工单类型“记账”,状态待审核/已拒绝/待处理/处理中/已完成,
  记账原因为厂家记账 12 项 + 仓库记账 5 项二级原因,发起时间 >= 2026-09-01;同一工单只取最新一版
- 新页面 供应商应付 → 厂家记账:筛选、记账金额合计、列设置、CSV 导出;首次同步未完成时明确提示
- 镜像索引迁移 (woTypeId, created, Id)

涉及:金额展示(只读)、镜像索引迁移;不涉及权限模型和上游写入。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5KQeRJaLrbJY9iKNC5veV
2026-10-18 22:27:16 +08:00

166 lines
9.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Tests\Unit\EcommerceFinance;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Modules\EcommerceFinance\Repositories\SupplierBookkeepingWorkOrderRepository as Repository;
use Modules\EcommerceFinance\View\SupplierBookkeepingPageData;
use Tests\TestCase;
class SupplierBookkeepingWorkOrderRepositoryTest extends TestCase
{
private const REASON = Repository::FIELD_REASON;
protected function setUp(): void
{
parent::setUp();
config([
'database.connections.business_mirror' => ['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => ''],
'upstream_mirrors.connection' => 'business_mirror',
'upstream_mirrors.datasets' => [
Repository::DATASET => [
'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'jst_workOrderInfo',
'target' => 'fixture_work_orders', 'keys' => ['Id'], 'watermark' => 'updateTime',
'indexes' => [['woTypeId', 'woNo', 'Id']],
],
],
]);
Cache::setDefaultDriver('array');
Cache::flush();
DB::purge('business_mirror');
$schema = Schema::connection('business_mirror');
$schema->create('business_mirror_table_states', function (Blueprint $table): void {
$table->string('dataset_key')->primary();
$table->string('status')->nullable();
$table->dateTime('initial_completed_at')->nullable();
$table->dateTime('last_succeeded_at')->nullable();
});
$schema->create('fixture_work_orders', function (Blueprint $table): void {
$table->integer('Id');
foreach (['woNo', 'woTypeId', 'status', 'createby', 'executors', 'created', 'updated', 'updateTime', 'businessFieldList'] as $column) {
$table->text($column)->nullable();
}
});
}
private function state(?string $initialCompletedAt = '2026-10-01 00:00:00', string $status = 'succeeded'): void
{
DB::connection('business_mirror')->table('business_mirror_table_states')->updateOrInsert(
['dataset_key' => Repository::DATASET],
['status' => $status, 'initial_completed_at' => $initialCompletedAt, 'last_succeeded_at' => '2026-10-18 21:00:00'],
);
}
/** @param array<string,mixed> $fields */
private function order(int $id, string $woNo, array $fields, array $patch = [], int $jsonFlags = JSON_UNESCAPED_UNICODE): void
{
$list = [];
foreach ($fields as $fieldId => $value) {
$list[] = is_array($value) && array_key_exists('__raw', $value)
? ['fieldId' => $fieldId] + $value['__raw']
: ['fieldId' => $fieldId, 'value' => $value, 'valueLabel' => null];
}
DB::connection('business_mirror')->table('fixture_work_orders')->insert(array_replace([
'Id' => $id, 'woNo' => $woNo, 'woTypeId' => Repository::WORK_TYPE_ID, 'status' => 'WAIT_CONFIRM',
'createby' => '发起人甲', 'executors' => '简尚', 'created' => '2026-09-05 10:00:00',
'updated' => '2026-09-06 10:00:00', 'updateTime' => '2026-09-06 10:00:00',
'businessFieldList' => json_encode($list, $jsonFlags),
], $patch));
}
private function repository(): Repository
{
return $this->app->make(Repository::class);
}
public function test_reports_pending_until_initial_sync_and_refuses_empty_reads(): void
{
$this->assertSame('unavailable', $this->repository()->syncStatus()['code']);
$this->state(null, 'running');
$this->assertSame('initializing', $this->repository()->syncStatus()['code']);
$this->expectExceptionMessage('business_mirror_dataset_not_ready');
$this->repository()->rows();
}
public function test_applies_confirmed_scope_latest_version_and_parses_fields(): void
{
$this->state();
$base = [
self::REASON => '厂家记账/板材爆口/掉漆/开裂/发霉/色差',
'SELECTField1413856620250906100658' => '补偿款项',
'NUMBERField1413856620250906100530' => '120.5',
'orderField' => '6918600558615604325',
'shopField' => ['__raw' => ['value' => 18533663, 'valueLabel' => '抖音-艺煦白墙专卖店']],
'attachmentField' => ['https://img.example/a.png', 'https://img.example/b.png'],
'itemInfo' => ['__raw' => ['value' => null, 'childFieldList' => [
[['fieldId' => 'skuId', 'value' => 'CBG007215N16V01'], ['fieldId' => 'iid', 'value' => 'CBG0072'], ['fieldId' => 'supplierName', 'value' => '闻其'], ['fieldId' => 'qty', 'value' => 1]],
[['fieldId' => 'skuId', 'value' => 'CBG007216N16V01'], ['fieldId' => 'iid', 'value' => 'CBG0072'], ['fieldId' => 'supplierName', 'value' => '闻其'], ['fieldId' => 'qty', 'value' => 2]],
]]],
];
// 同一工单两版:只取最新一版(金额被修改为 130)。
$this->order(1, 'GD-A', $base);
$this->order(9, 'GD-A', array_replace($base, ['NUMBERField1413856620250906100530' => '130']), ['status' => 'CONFIRMING']);
// 仓库记账:数组形式的级联值 + \u 转义 JSON,API 形式的商品明细。
$this->order(2, 'GD-B', [
self::REASON => ['仓库记账', '退货运费'],
'NUMBERField1413856620250906100530' => '10.05',
'itemInfo' => [['skuId' => 'SKU-B', 'supplierName' => '聚尚', 'priceCount' => 99.5]],
], ['created' => '2026-09-01 00:00:00'], 0);
// 以下均不应接入。
$this->order(3, 'GD-C', [self::REASON => '买家原因/原单退回(导致退货,退回财务改记账)']);
$this->order(4, 'GD-D', $base, ['created' => '2026-08-31 23:59:59']);
$this->order(5, 'GD-E', $base);
$this->order(6, 'GD-E', $base, ['status' => 'CANCELED']);
$this->order(7, 'GD-F', $base, ['woTypeId' => 'OTHER']);
$this->order(8, 'GD-G', [self::REASON => '厂家记账/未勾选的新原因']);
$this->order(12, 'GD-I', $base, ['status' => '已取消']);
$this->order(13, 'GD-J', $base, ['status' => '已完成', 'created' => '2026-09-02 00:00:00']);
$this->order(10, 'GD-H', $base);
$this->order(11, 'GD-H', array_replace($base, [self::REASON => '快递记账/原单退回']));
$rows = $this->repository()->rows();
$this->assertSame(['GD-A', 'GD-J', 'GD-B'], array_column($rows, 'work_order_no'));
[$a, $j, $b] = $rows;
$this->assertSame('已完成', $j['status']);
$this->assertSame('130.00', $a['amount']);
$this->assertSame('处理中', $a['status']);
$this->assertSame('厂家记账', $a['reason_level_1']);
$this->assertSame('板材爆口/掉漆/开裂/发霉/色差', $a['reason_level_2']);
$this->assertSame('补偿款项', $a['bookkeeping_type']);
$this->assertSame('抖音-艺煦白墙专卖店', $a['shop_name']);
$this->assertSame("CBG007215N16V01\nCBG007216N16V01", $a['product_code']);
$this->assertSame("1\n2", $a['product_quantity']);
$this->assertSame(2, $a['attachment_count']);
$this->assertSame('6918600558615604325', $a['online_order_no']);
$this->assertSame('仓库记账/退货运费', $b['bookkeeping_reason']);
$this->assertSame('聚尚', $b['supplier']);
$this->assertSame('99.5', $b['product_amount']);
$this->assertSame('待处理', $b['status']);
$this->assertCount(17, Repository::reasonPaths());
}
public function test_page_filters_and_totals_use_cents(): void
{
$rows = [
['started_at' => '2026-09-02 08:00:00', 'bookkeeping_reason' => '厂家记账/缺货损失', 'reason_level_1' => '厂家记账', 'status' => '待处理', 'bookkeeping_type' => '记账全款', 'amount' => '0.10', 'work_order_no' => 'GD-1', 'online_order_no' => '', 'tracking_no' => '', 'shop_name' => '天猫-亿家达旗舰店', 'supplier' => '彩虹', 'product_code' => 'DNZ01'],
['started_at' => '2026-09-20 08:00:00', 'bookkeeping_reason' => '仓库记账/退货运费', 'reason_level_1' => '仓库记账', 'status' => '已完成', 'bookkeeping_type' => '运费损耗', 'amount' => '0.20', 'work_order_no' => 'GD-2', 'online_order_no' => '', 'tracking_no' => '', 'shop_name' => '拼多多-宜自然', 'supplier' => '胜友', 'product_code' => 'XG01'],
];
$page = $this->app->make(SupplierBookkeepingPageData::class);
$filters = ['started_from' => '', 'started_to' => '', 'reason' => '', 'status' => '', 'bookkeeping_type' => ''] + array_fill_keys(array_keys(SupplierBookkeepingPageData::TEXT_FILTERS), '');
$all = iterator_to_array($page->rows($filters, $rows), false);
$this->assertSame(30, array_sum(array_map([SupplierBookkeepingPageData::class, 'cents'], array_column($all, 'amount'))));
$this->assertSame(['GD-1'], array_column(iterator_to_array($page->rows(['reason' => '厂家记账'] + $filters, $rows), false), 'work_order_no'));
$this->assertSame(['GD-2'], array_column(iterator_to_array($page->rows(['started_from' => '2026-09-10', 'supplier' => '胜'] + $filters, $rows), false), 'work_order_no'));
$this->assertSame([], iterator_to_array($page->rows(['started_to' => '2026-09-01'] + $filters, $rows), false));
}
}