CaiWuXiTong/tests/Unit/EcommerceFinance/MarketingAccountBillMirrorRepositoryTest.php
chenxi 8bfd82aa29 店铺营销账户账单各平台导出 (#77)
为淘系、阿里巴巴和京东专用账单补齐按当前筛选条件导出,并保留拼多多及通用平台既有导出能力。导出统一优先读取共享业务镜像。
2026-08-24 14:29:46 +08:00

222 lines
14 KiB
PHP

<?php
namespace Tests\Unit\EcommerceFinance;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Modules\EcommerceFinance\Repositories\AlibabaBillRepository;
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
use Modules\EcommerceFinance\Repositories\JdWalletBillRepository;
use Modules\EcommerceFinance\Repositories\MarketingAccountBillMirrorRepository;
use Modules\EcommerceFinance\Repositories\PinduoduoSpreadBillRepository;
use Modules\EcommerceFinance\Repositories\TmallTaobaoSpreadBillRepository;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
use RuntimeException;
use Tests\TestCase;
class MarketingAccountBillMirrorRepositoryTest extends TestCase
{
private MarketingAccountBillMirrorRepository $mirror;
protected function setUp(): void
{
parent::setUp();
config([
'database.connections.business_mirror' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
'upstream_mirrors.connection' => 'business_mirror',
'upstream_mirrors.datasets' => [
MarketingAccountBillMirrorRepository::TAOBAO => $this->definition('tmall_spread_bills', 'js_tmall_spread_bills'),
MarketingAccountBillMirrorRepository::PINDUODUO => $this->definition('pdd_spread_bills', 'js_pdd_spread_bills'),
MarketingAccountBillMirrorRepository::ALIBABA => $this->definition('alibaba_bill', 'js_alibaba_bill'),
MarketingAccountBillMirrorRepository::JD_RECHARGE => $this->definition('jd_spreadBills', 'js_jd_spread_bills'),
MarketingAccountBillMirrorRepository::JD_CONSUMPTION => $this->definition('jd_costBills', 'js_jd_cost_bills'),
],
]);
DB::purge('business_mirror');
$this->createTables();
$generic = new GenericBusinessMirrorRepository(app('db'));
$this->mirror = new MarketingAccountBillMirrorRepository(
new BusinessMirrorDatasetRegistry(),
$generic,
);
}
public function test_ready_mirrors_drive_lists_summaries_options_monthly_balances_and_second_pages(): void
{
$db = DB::connection('business_mirror');
$db->table('js_tmall_spread_bills')->insert([
$this->taobao(1, '2026-05-31 23:00:00', 10000, 10000, 'DEBIT'),
$this->taobao(2, '2026-06-01 09:00:00', 5000, 15000, 'DEBIT'),
$this->taobao(3, '2026-06-02 09:00:00', 2000, 13000, 'CREDIT'),
]);
$db->table('js_pdd_spread_bills')->insert([
$this->pinduoduo(1, '2026-05-31 23:00:00', 100, 100, 1),
$this->pinduoduo(2, '2026-06-01 09:00:00', 50, 150, 1),
$this->pinduoduo(3, '2026-06-02 09:00:00', 20, 130, 2),
]);
$db->table('js_alibaba_bill')->insert([
['id' => 1, 'shopName' => '阿里店', 'tradeDay' => '2026-06-01 09:00:00', 'tradeTypeName' => '全站', 'tradeAMTIn' => 10, 'tradeAMTOut' => 1],
['id' => 2, 'shopName' => '阿里店', 'tradeDay' => '2026-06-02 09:00:00', 'tradeTypeName' => '全站', 'tradeAMTIn' => 20, 'tradeAMTOut' => 2],
]);
$db->table('js_jd_spread_bills')->insert([
['id' => 1, 'shopName' => '京东店', 'amount' => 100, 'balanceAmount' => 100, 'orderTypeName' => '充值', 'pk' => 'R1', 'createDate' => '2026-06-01 10:00:00', 'updateTime' => '2026-06-01 10:01:00', 'statusName' => '成功', 'remark' => ''],
['id' => 2, 'shopName' => '京东店', 'amount' => 200, 'balanceAmount' => 300, 'orderTypeName' => '充值', 'pk' => 'R2', 'createDate' => '2026-06-02 10:00:00', 'updateTime' => '2026-06-02 10:01:00', 'statusName' => '成功', 'remark' => ''],
]);
$db->table('js_jd_cost_bills')->insert([
['id' => 1, 'shopName' => '京东店', 'amount' => -30, 'balanceAmount' => 270, 'orderTypeName' => '消耗', 'creatTime' => '2026-06-03 10:00:00', 'settleTime' => '2026-06-04 00:00:00', 'pk' => 'C1', 'invoiceApplyTypeName' => '推广费'],
]);
$this->markReady();
$factory = $this->throwingFactory();
$taobao = new TmallTaobaoSpreadBillRepository($factory, $this->mirror);
$pinduoduo = new PinduoduoSpreadBillRepository($factory, $this->mirror);
$alibaba = new AlibabaBillRepository($factory, $this->mirror);
$jd = new JdWalletBillRepository($factory, $this->mirror);
$taobaoPage2 = $taobao->paginate('镜像店铺', '2026-06-01', '2026-06-30', null, 2, 1);
$taobaoSummary = $taobao->summary('镜像店铺', '2026-06-01', '2026-06-30');
$taobaoMonthly = $taobao->paginateMonthlyBalances('镜像店铺', '2026-06', 1, 25);
$pddPage2 = $pinduoduo->paginate('镜像店铺', '2026-06-01', '2026-06-30', null, 2, 1);
$pddSummary = $pinduoduo->summary('镜像店铺', '2026-06-01', '2026-06-30');
$pddMonthly = $pinduoduo->paginateMonthlyBalances('镜像店铺', '2026-06', 1, 25);
$alibabaPage2 = $alibaba->paginate('阿里店', '全站', '2026-06-01', '2026-06-30', 2, 1);
$jdRechargePage2 = $jd->paginateRecharge('京东店', '2026-06-01', '2026-06-30', 2, 1);
$taobaoExport = iterator_to_array($taobao->exportRows('镜像店铺', '2026-06-01', '2026-06-30', null, 1));
$alibabaExport = iterator_to_array($alibaba->exportRows('阿里店', '全站', '2026-06-01', '2026-06-30', 1));
$jdRechargeExport = iterator_to_array($jd->exportRows('recharge', '京东店', '2026-06-01', '2026-06-30', 1));
$jdConsumptionExport = iterator_to_array($jd->exportRows('consumption', '京东店', '2026-06-01', '2026-06-30', 1));
$this->assertSame(2, $taobaoPage2['total']);
$this->assertSame(50.0, $taobaoPage2['rows'][0]['amount']);
$this->assertSame(['镜像店铺'], $taobao->storeOptions());
$this->assertSame(30.0, $taobaoSummary['net']);
$this->assertSame(100.0, $taobaoMonthly['rows'][0]['opening_balance']);
$this->assertSame(130.0, $taobaoMonthly['rows'][0]['closing_balance']);
$this->assertSame(0.0, $taobaoMonthly['rows'][0]['balance_difference']);
$this->assertSame(2, $pddPage2['total']);
$this->assertSame(50.0, $pddPage2['rows'][0]['amount']);
$this->assertSame(30.0, $pddSummary['net']);
$this->assertSame(100.0, $pddMonthly['rows'][0]['opening_balance']);
$this->assertSame(130.0, $pddMonthly['rows'][0]['closing_balance']);
$this->assertSame(0.0, $pddMonthly['rows'][0]['balance_difference']);
$this->assertSame(2, $alibabaPage2['total']);
$this->assertSame(10.0, $alibabaPage2['rows'][0]['income_amount']);
$this->assertSame(['shops' => ['阿里店'], 'product_types' => ['全站']], $alibaba->filterOptions());
$this->assertSame(27.0, $alibaba->summary('阿里店', '全站')['net']);
$this->assertSame(2, $jdRechargePage2['total']);
$this->assertSame('R1', $jdRechargePage2['rows'][0]['order_number']);
$this->assertSame(['京东店'], $jd->shopOptions());
$this->assertSame(300.0, $jd->summaryRecharge('京东店')['amount_total']);
$this->assertSame(30.0, $jd->summaryConsumption('京东店')['amount_total']);
$this->assertCount(2, $taobaoExport);
$this->assertSame([20.0, 50.0], array_column($taobaoExport, 'amount'));
$this->assertCount(2, $alibabaExport);
$this->assertSame([20.0, 10.0], array_column($alibabaExport, 'income_amount'));
$this->assertSame(['R2', 'R1'], array_column($jdRechargeExport, 'order_number'));
$this->assertSame(['C1'], array_column($jdConsumptionExport, 'serial_number'));
}
public function test_unready_dataset_keeps_the_existing_upstream_repository_path(): void
{
$query = new class('jiansun') extends UpstreamReadOnlyQuery {
public function scalar(string $sql, array $bindings = []): mixed { return 1; }
public function select(string $sql, array $bindings = []): array
{
return [['shopName' => '上游回退', 'amount' => 8, 'balance' => 9, 'summary' => '', 'flowType' => 1, 'toAdvertiserFundType' => 1, 'createTime' => '2026-06-01']];
}
};
$factory = new class($query) extends UpstreamReadOnlyQueryFactory {
public function __construct(private readonly UpstreamReadOnlyQuery $query) {}
public function forSource(string $source): UpstreamReadOnlyQuery { return $this->query; }
};
$result = (new PinduoduoSpreadBillRepository($factory, $this->mirror))->paginate();
$this->assertSame(1, $result['total']);
$this->assertSame('上游回退', $result['rows'][0]['store_name']);
}
private function throwingFactory(): UpstreamReadOnlyQueryFactory
{
$query = new class('jiansun') extends UpstreamReadOnlyQuery {
public function scalar(string $sql, array $bindings = []): mixed { throw new RuntimeException('upstream_should_not_be_called'); }
public function select(string $sql, array $bindings = []): array { throw new RuntimeException('upstream_should_not_be_called'); }
public function first(string $sql, array $bindings = []): ?array { throw new RuntimeException('upstream_should_not_be_called'); }
};
return new class($query) extends UpstreamReadOnlyQueryFactory {
public function __construct(private readonly UpstreamReadOnlyQuery $query) {}
public function forSource(string $source): UpstreamReadOnlyQuery { return $this->query; }
};
}
private function createTables(): void
{
$schema = Schema::connection('business_mirror');
$schema->create('business_mirror_table_states', function (Blueprint $table): void {
$table->string('dataset_key')->primary();
$table->dateTime('initial_completed_at')->nullable();
});
$schema->create('js_tmall_spread_bills', function (Blueprint $table): void {
$table->integer('id'); $table->string('shopName')->nullable(); $table->dateTime('uniTransTime')->nullable(); $table->dateTime('transTime')->nullable();
$table->decimal('amount', 18, 2)->nullable(); $table->decimal('balance', 18, 2)->nullable(); $table->string('comment')->nullable();
$table->string('finType')->nullable(); $table->string('bizType')->nullable(); $table->string('tradeType')->nullable();
});
$schema->create('js_pdd_spread_bills', function (Blueprint $table): void {
$table->integer('id'); $table->string('shopName')->nullable(); $table->decimal('amount', 18, 2)->nullable(); $table->decimal('balance', 18, 2)->nullable();
$table->string('summary')->nullable(); $table->integer('flowType')->nullable(); $table->integer('toAdvertiserFundType')->nullable(); $table->dateTime('createTime')->nullable();
});
$schema->create('js_alibaba_bill', function (Blueprint $table): void {
$table->integer('id'); $table->string('shopName')->nullable(); $table->dateTime('tradeDay')->nullable(); $table->string('tradeTypeName')->nullable();
$table->decimal('tradeAMTIn', 18, 2)->nullable(); $table->decimal('tradeAMTOut', 18, 2)->nullable();
});
$schema->create('js_jd_spread_bills', function (Blueprint $table): void {
$table->integer('id'); $table->string('shopName')->nullable(); $table->decimal('amount', 18, 2)->nullable(); $table->decimal('balanceAmount', 18, 2)->nullable();
$table->string('orderTypeName')->nullable(); $table->string('pk')->nullable(); $table->dateTime('createDate')->nullable(); $table->dateTime('updateTime')->nullable();
$table->string('statusName')->nullable(); $table->string('remark')->nullable();
});
$schema->create('js_jd_cost_bills', function (Blueprint $table): void {
$table->integer('id'); $table->string('shopName')->nullable(); $table->decimal('amount', 18, 2)->nullable(); $table->decimal('balanceAmount', 18, 2)->nullable();
$table->string('orderTypeName')->nullable(); $table->dateTime('creatTime')->nullable(); $table->dateTime('settleTime')->nullable();
$table->string('pk')->nullable(); $table->string('invoiceApplyTypeName')->nullable();
});
}
private function markReady(): void
{
foreach (array_keys((array) config('upstream_mirrors.datasets')) as $key) {
DB::connection('business_mirror')->table('business_mirror_table_states')->insert([
'dataset_key' => $key,
'initial_completed_at' => '2026-08-14 12:00:00',
]);
}
}
private function definition(string $object, string $target): array
{
return ['source_key' => 'jiansun', 'schema' => 'dbo', 'object' => $object, 'target' => $target, 'keys' => ['id']];
}
private function taobao(int $id, string $time, float $amount, float $balance, string $finType): array
{
return ['id' => $id, 'shopName' => '镜像店铺', 'uniTransTime' => $time, 'transTime' => $time, 'amount' => $amount, 'balance' => $balance, 'comment' => '', 'finType' => $finType, 'bizType' => '', 'tradeType' => ''];
}
private function pinduoduo(int $id, string $time, float $amount, float $balance, int $flowType): array
{
return ['id' => $id, 'shopName' => '镜像店铺', 'amount' => $amount, 'balance' => $balance, 'summary' => '', 'flowType' => $flowType, 'toAdvertiserFundType' => 1, 'createTime' => $time];
}
}