CaiWuXiTong/tests/Unit/EcommerceFinance/JdJingxiBillSplitTest.php
qiuxiang a0c99c3e8d 新增店铺收款京喜账单页签并从京东账单切分
在京东账单右侧新增京喜账单页签,复用京东日账单的取数、列定义、筛选、排序、分页与导出,只增加平台维度过滤:京东账单排除京喜店铺,京喜账单只取京喜店铺。平台口径集中在 config/jd_bill_platform.php 与 JdBillPlatform(含口径切换起始日期参数,默认全量历史),仓库通过 forPlatform() 收窄,两个口径互补、总额守恒,底层镜像数据不修改不删除。真实镜像实测:京喜 488,464 笔 + 京东 210,983 笔 = 拆分前 699,447 笔,收入/支出/差额逐项误差为 0。
2026-09-28 19:25:53 +08:00

171 lines
7.7 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\Support\Facades\DB;
use Modules\EcommerceFinance\AccountBillColumns;
use Modules\EcommerceFinance\JdBillPlatform;
use Modules\EcommerceFinance\Repositories\JdDailyBillRepository;
use Modules\EcommerceFinance\Services\JdDailyBillImporter;
use PHPUnit\Framework\TestCase;
/**
* 京东账单 › 京喜账单的剪切口径:
* 拆分后两个页签的笔数与金额之和恒等于拆分前,且不修改底层数据。
*/
class JdJingxiBillSplitTest extends TestCase
{
private array $files = [];
protected function setUp(): void
{
parent::setUp();
$app = require dirname(__DIR__, 3) . '/bootstrap/app.php';
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
config(['database.connections.business_mirror' => ['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '']]);
DB::purge('business_mirror');
(require database_path('migrations/2026_09_18_140000_create_jd_daily_bill_mirror_tables.php'))->up();
}
protected function tearDown(): void
{
foreach ($this->files as $file) unlink($file);
parent::tearDown();
}
public function test_split_keeps_京东_and_京喜_totals_equal_to_the_unsplit_bill(): void
{
$this->importFixture();
$all = $this->repository(null)->paginate([], 'occurred_at', 'desc', 1, 10);
$jd = $this->repository(JdBillPlatform::BILL_TYPE_JD)->paginate([], 'occurred_at', 'desc', 1, 10);
$jingxi = $this->repository(JdBillPlatform::BILL_TYPE_JINGXI)->paginate([], 'occurred_at', 'desc', 1, 10);
// 全量共 6 笔;京喜 4 笔(含京喜小店),京东账单 2 笔。
$this->assertSame(6, $all['total']);
$this->assertSame(4, $jingxi['total']);
$this->assertSame(2, $jd['total']);
foreach (['count', 'income', 'expense', 'balance'] as $field) {
$this->assertSame(
round($all['summary'][$field], 2),
round($jd['summary'][$field] + $jingxi['summary'][$field], 2),
"拆分后 {$field} 不守恒",
);
}
}
public function test_same_day_rows_are_only_visible_in_the_jingxi_tab(): void
{
$this->importFixture();
$filters = ['start' => '2026-09-23', 'end' => '2026-09-23', 'store_name' => '京喜-安隅京喜自营工厂店'];
$jingxi = $this->repository(JdBillPlatform::BILL_TYPE_JINGXI)->paginate($filters, 'occurred_at', 'desc', 1, 10);
$jd = $this->repository(JdBillPlatform::BILL_TYPE_JD)->paginate($filters, 'occurred_at', 'desc', 1, 10);
$this->assertSame(2, $jingxi['total']);
$this->assertSame(['京喜-安隅京喜自营工厂店'], array_unique(array_column($jingxi['data'], 'store_name')));
$this->assertSame(0, $jd['total']);
$this->assertSame([], $jd['data']);
}
public function test_cutoff_date_keeps_earlier_jingxi_rows_in_the_jd_bill(): void
{
$this->importFixture();
config(['jd_bill_platform.scope_start_date' => '2026-09-23']);
$all = $this->repository(null)->paginate([], 'occurred_at', 'desc', 1, 10);
$jd = $this->repository(JdBillPlatform::BILL_TYPE_JD)->paginate([], 'occurred_at', 'desc', 1, 10);
$jingxi = $this->repository(JdBillPlatform::BILL_TYPE_JINGXI)->paginate([], 'occurred_at', 'desc', 1, 10);
// 只有 09-23 起的京喜行被切走,09-10 的京喜行仍留在京东账单,总数守恒。
$this->assertSame(3, $jingxi['total']);
$this->assertSame(3, $jd['total']);
$this->assertSame($all['total'], $jd['total'] + $jingxi['total']);
$this->assertSame(
round($all['summary']['income'], 2),
round($jd['summary']['income'] + $jingxi['summary']['income'], 2),
);
}
public function test_filter_options_follow_the_tab_scope(): void
{
$this->importFixture();
$jingxi = $this->repository(JdBillPlatform::BILL_TYPE_JINGXI)->filterOptions();
$jd = $this->repository(JdBillPlatform::BILL_TYPE_JD)->filterOptions();
$this->assertSame(['京喜-安隅京喜自营工厂店', '京喜小店-简尚家居家具制造店'], $jingxi['shops']);
$this->assertSame(['京东-慕加旗舰店'], $jd['shops']);
}
public function test_export_follows_the_tab_scope(): void
{
$this->importFixture();
$jingxi = array_column(iterator_to_array($this->repository(JdBillPlatform::BILL_TYPE_JINGXI)->exportRows([], 'occurred_at', 'asc')), 'store_name');
$jd = array_column(iterator_to_array($this->repository(JdBillPlatform::BILL_TYPE_JD)->exportRows([], 'occurred_at', 'asc')), 'store_name');
$this->assertCount(4, $jingxi);
$this->assertCount(2, $jd);
$this->assertSame([], array_intersect($jingxi, $jd));
}
public function test_jingxi_tab_reuses_the_jd_bill_columns_and_sits_next_to_it(): void
{
$jd = AccountBillColumns::for(JdBillPlatform::BILL_TYPE_JD);
$jingxi = AccountBillColumns::for(JdBillPlatform::BILL_TYPE_JINGXI);
$this->assertSame('京喜账单', $jingxi['title']);
$this->assertSame($jd['columns'], $jingxi['columns']);
$this->assertSame([], $jingxi['groups']);
$tabs = collect(collect(config('business_navigation'))->firstWhere('slug', 'billing-transactions')['pages'])
->firstWhere('slug', 'store-receiving-bills')['tabs'];
$slugs = array_column($tabs, 'slug');
$this->assertSame('jingxi', $slugs[array_search('jd-transactions', $slugs, true) + 1]);
}
private function repository(?string $platform): JdDailyBillRepository
{
return app(JdDailyBillRepository::class)->forPlatform(
$platform === null ? null : JdBillPlatform::platformForBillType($platform),
);
}
/** 6 笔:京喜 4 笔(含「京喜小店」店铺)、京东 2 笔,横跨 2026-09-10 与 2026-09-23 两个记账日期。 */
private function importFixture(): void
{
$rows = [
$this->row('1', '京喜-安隅京喜自营工厂店', '2026-09-10 09:00:00', '10.00', '0.00'),
$this->row('2', '京喜-安隅京喜自营工厂店', '2026-09-23 09:00:00', '20.00', '0.00'),
$this->row('3', '京喜-安隅京喜自营工厂店', '2026-09-23 10:00:00', '0.00', '5.00'),
$this->row('4', '京喜小店-简尚家居家具制造店', '2026-09-23 11:00:00', '7.00', '0.00'),
$this->row('5', '京东-慕加旗舰店', '2026-09-23 12:00:00', '3.00', '0.00'),
$this->row('6', '京东-慕加旗舰店', '2026-09-10 12:00:00', '0.00', '2.00'),
];
$this->import($rows, 6, '40.00', '7.00');
}
private function import(array $rows, int $count, string $income, string $expense): array
{
$path = tempnam(sys_get_temp_dir(), 'jd-jingxi-');
$this->files[] = $path;
$file = fopen($path, 'wb');
fputcsv($file, array_keys($rows[0]));
foreach ($rows as $row) fputcsv($file, array_values($row));
fclose($file);
return app(JdDailyBillImporter::class)->import($path, '2026-09', $count, $income, $expense);
}
private function row(string $code, string $store, string $occurredAt, string $income, string $expense): array
{
return array_replace(array_fill_keys(array_values(JdDailyBillImporter::PAYLOAD_FIELDS), ''), [
'记录编码' => $code, '店铺' => $store, '账单项目' => '交易服务费', '订单编号' => '360640400000000' . $code,
'单据编号' => '1010012608312386678178000' . $code, '费用结算时间' => $occurredAt,
'费用发生时间' => $occurredAt, '收入金额' => $income, '支出金额' => $expense, '币种' => 'CNY',
]);
}
}