CaiWuXiTong/tests/Unit/EcommerceFinance/StoreAccountMirrorRepositoryTest.php

259 lines
14 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\DB;
use Illuminate\Support\Facades\Schema;
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
use Modules\EcommerceFinance\Repositories\StoreAccountMirrorRepository;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use Tests\TestCase;
class StoreAccountMirrorRepositoryTest extends TestCase
{
private StoreAccountMirrorRepository $repository;
protected function setUp(): void
{
parent::setUp();
config([
'database.connections.business_mirror' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
'upstream_mirrors.connection' => 'business_mirror',
'upstream_mirrors.datasets' => [
StoreAccountMirrorRepository::DATASET => [
'source_key' => 'database_4',
'schema' => 'external_store_accounts',
'object' => 'accounts_v1',
'target' => 'store_accounts',
'keys' => ['id'],
// 模拟 business-mirror:follow-upstream-columns 记录下来的跟随字段。
'followed_columns' => ['platform'],
],
'jiansun.dbo.fys_shopInfo_flow' => [
'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'fys_shopInfo_flow',
'target' => 'shop_info_flow_form', 'keys' => ['Id'],
],
'jiansun.dbo.flow_flow' => [
'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'flow_flow',
'target' => 'oa_flow_flow', 'keys' => ['Id'],
],
],
]);
DB::purge('business_mirror');
Schema::connection('business_mirror')->create('business_mirror_table_states', function (Blueprint $table): void {
$table->string('dataset_key')->primary();
$table->dateTime('initial_completed_at')->nullable();
});
Schema::connection('business_mirror')->create('store_accounts', function (Blueprint $table): void {
$table->string('id')->primary();
foreach (['storeName', 'operationOwner', 'operationDepartmentHead', 'operationDirector', 'profitCenter', 'accountType', 'account', 'boundPhone', 'verificationContact', 'accountGroupName', 'createdAt', 'updatedAt'] as $column) {
$table->string($column)->nullable();
}
$table->boolean('passwordConfigured')->nullable();
// 自动跟随上游补进来的字段。
$table->string('platform')->nullable();
});
// 授权状态 / 店铺状态来自这两张镜像表(按店铺名关联)。
Schema::connection('business_mirror')->create('ops_fys_shop_info', function (Blueprint $table): void {
$table->integer('Id');
$table->string('erpShopName')->nullable();
$table->integer('shopState')->nullable();
});
Schema::connection('business_mirror')->create('ops_jst_shop_info', function (Blueprint $table): void {
$table->integer('Id');
$table->string('shop_name')->nullable();
$table->dateTime('session_expired')->nullable();
});
// 店铺关店流程:表单(含 ERP 店铺名)+ OA 流程状态。
Schema::connection('business_mirror')->create('shop_info_flow_form', function (Blueprint $table): void {
$table->integer('Id');
$table->string('flowId')->nullable();
$table->integer('flowType')->nullable();
$table->string('erpShopName')->nullable();
$table->dateTime('applyTime')->nullable();
});
Schema::connection('business_mirror')->create('oa_flow_flow', function (Blueprint $table): void {
$table->integer('Id');
$table->string('flowId')->nullable();
$table->integer('flowStatus')->nullable();
});
DB::connection('business_mirror')->table('business_mirror_table_states')->insert([
'dataset_key' => StoreAccountMirrorRepository::DATASET,
'initial_completed_at' => '2026-09-04 10:00:00',
]);
$this->repository = new StoreAccountMirrorRepository(
new BusinessMirrorDatasetRegistry(),
new GenericBusinessMirrorRepository(app('db')),
);
}
public function test_it_maps_account_type_codes_to_business_labels(): void
{
foreach (['MAIN' => '主账号', 'SUB' => '子账号', '/' => null] as $type => $expected) {
DB::connection('business_mirror')->table('store_accounts')->insert([
'id' => 'account-' . md5($type),
'storeName' => ['MAIN' => 'B店铺', 'SUB' => 'A店铺', '/' => 'C店铺'][$type],
'accountType' => $type,
'account' => 'test-account',
'passwordConfigured' => $type === 'MAIN' ? true : ($type === 'SUB' ? false : null),
]);
}
$rows = collect($this->repository->paginate(1, 50)['data'])->keyBy('id');
$this->assertSame('主账号', $rows['account-' . md5('MAIN')]['account_type']);
$this->assertSame('子账号', $rows['account-' . md5('SUB')]['account_type']);
$this->assertNull($rows['account-' . md5('/')]['account_type']);
$this->assertTrue($rows['account-' . md5('MAIN')]['password_configured']);
$this->assertFalse($rows['account-' . md5('SUB')]['password_configured']);
$this->assertNull($rows['account-' . md5('/')]['password_configured']);
$sorted = $this->repository->paginate(1, 50, ['_sort' => 'store_name', '_direction' => 'asc']);
$this->assertSame(['A店铺', 'B店铺', 'C店铺'], array_column($sorted['data'], 'store_name'));
}
public function test_ownership_uses_operation_owner_and_department_head_with_normalized_keys(): void
{
DB::connection('business_mirror')->table('store_accounts')->insert([
'id' => 'account-owner',
'storeName' => '拼多多_甲店',
'operationOwner' => '店铺负责人',
'operationDepartmentHead' => '部门负责人',
'operationDirector' => '运营总监',
'profitCenter' => '运营中心A',
]);
$ownership = $this->repository->ownershipByStoreName();
$expectedKey = StoreAccountMirrorRepository::storeNameKey('拼多多-甲店');
$this->assertArrayHasKey($expectedKey, $ownership);
$this->assertSame('店铺负责人', $ownership[$expectedKey]['operation_owner']);
$this->assertSame('部门负责人', $ownership[$expectedKey]['operation_department_head']);
$this->assertSame('运营总监', $ownership[$expectedKey]['operation_director']);
}
public function test_it_shows_the_latest_application_flow(): void
{
$db = DB::connection('business_mirror');
// 账号表用的就是带平台前缀的 ERP 店铺名(关店流程里可能写成下划线,需要归一)。
foreach (['拼多多-甲店' => '拼多多_甲店', '拼多多-乙店' => '拼多多-乙店', '拼多多-丙店' => '拼多多-丙店'] as $store => $erp) {
$db->table('store_accounts')->insert([
'id' => 'account-' . md5($store), 'storeName' => $store, 'account' => 'acct-' . $store,
]);
// 只有建店流程的表单也放进来,确认只取关店流程(flowType=10)。
$db->table('shop_info_flow_form')->insert([
'Id' => crc32('open-' . $store), 'flowId' => 'open-' . $store, 'flowType' => 8,
'erpShopName' => $erp, 'applyTime' => '2026-09-01 09:00:00',
]);
}
// 甲店/乙店:最近一单是关店流程(流程中 / 完成);丙店只有开店流程。
$db->table('shop_info_flow_form')->insert([
['Id' => 1, 'flowId' => 'close-jia', 'flowType' => 10, 'erpShopName' => '拼多多_甲店', 'applyTime' => '2026-10-01 09:00:00'],
['Id' => 2, 'flowId' => 'close-yi', 'flowType' => 10, 'erpShopName' => '拼多多-乙店', 'applyTime' => '2026-10-02 09:00:00'],
]);
$db->table('oa_flow_flow')->insert([
['Id' => 1, 'flowId' => 'close-jia', 'flowStatus' => 1],
['Id' => 2, 'flowId' => 'close-yi', 'flowStatus' => 3],
]);
$rows = collect($this->repository->paginate(1, 10)['data'])->keyBy('store_name');
$this->assertSame('店铺关店', $rows['拼多多-甲店']['flow_type']);
$this->assertSame('流程中', $rows['拼多多-甲店']['approval_status']);
$this->assertSame('店铺关店', $rows['拼多多-乙店']['flow_type']);
$this->assertSame('完成', $rows['拼多多-乙店']['approval_status']);
// 丙店最近一单是开店流程,没有对应 OA 状态。
$this->assertSame('店铺开店', $rows['拼多多-丙店']['flow_type']);
$this->assertSame('', $rows['拼多多-丙店']['approval_status']);
$filtered = $this->repository->paginate(1, 10, ['approval_status' => ['流程中']]);
$this->assertSame(['拼多多-甲店'], array_column($filtered['data'], 'store_name'));
$byType = $this->repository->paginate(1, 10, ['flow_type' => ['店铺门店']]);
$this->assertSame([], array_column($byType['data'], 'store_name'));
$closeOnly = $this->repository->paginate(1, 10, ['flow_type' => ['店铺关店']]);
$this->assertSame(2, $closeOnly['total']);
}
public function test_it_exposes_followed_upstream_columns_to_the_page(): void
{
DB::connection('business_mirror')->table('store_accounts')->insert([
'id' => 'account-followed',
'storeName' => 'A店铺',
'account' => 'acct-A',
'platform' => '拼多多',
]);
$this->assertSame(['platform'], $this->repository->followedColumns());
$this->assertSame([
['name' => 'store_name', 'label' => '店铺名称'],
['name' => 'platform', 'label' => 'platform', 'followed' => true],
], $this->repository->withFollowedFields([['name' => 'store_name', 'label' => '店铺名称']]));
$rows = collect($this->repository->paginate(1, 10)['data'])->keyBy('id');
$this->assertSame('拼多多', $rows['account-followed']['platform']);
}
public function test_it_attaches_authorization_and_store_status_and_filters_them(): void
{
$db = DB::connection('business_mirror');
foreach (['A店铺' => '主账号', 'B店铺' => '主账号', 'C店铺' => '子账号', 'D店铺' => '子账号'] as $store => $type) {
$db->table('store_accounts')->insert([
'id' => 'account-' . md5($store),
'storeName' => $store,
'accountType' => $type === '主账号' ? 'MAIN' : 'SUB',
'account' => 'acct-' . $store,
]);
}
// 店铺档案:A 正常、B 已关闭;聚水潭授权:A 未过期、B 已过期、C 无授权时间,
// D 两条记录都没有。
$db->table('ops_fys_shop_info')->insert([
['Id' => 1, 'erpShopName' => 'A店铺', 'shopState' => 0],
['Id' => 2, 'erpShopName' => 'B店铺', 'shopState' => 6],
]);
$db->table('ops_jst_shop_info')->insert([
['Id' => 1, 'shop_name' => 'A店铺', 'session_expired' => now()->addYear()->toDateTimeString()],
['Id' => 2, 'shop_name' => 'B店铺', 'session_expired' => now()->subDay()->toDateTimeString()],
['Id' => 3, 'shop_name' => 'C店铺', 'session_expired' => null],
]);
$rows = collect($this->repository->paginate(1, 50)['data'])->keyBy('store_name');
$this->assertSame('已授权', $rows['A店铺']['authorization_status']);
$this->assertSame('正常', $rows['A店铺']['store_status']);
$this->assertSame('已过期', $rows['B店铺']['authorization_status']);
$this->assertSame('已关闭', $rows['B店铺']['store_status']);
$this->assertSame('未授权', $rows['C店铺']['authorization_status']);
$this->assertSame('', $rows['C店铺']['store_status']);
$this->assertSame('', $rows['D店铺']['authorization_status']);
$this->assertSame('', $rows['D店铺']['store_status']);
$authorized = $this->repository->paginate(1, 50, ['authorization_status' => '已授权', 'store_status' => '正常']);
$this->assertSame(1, $authorized['total']);
$this->assertSame(['A店铺'], array_column($authorized['data'], 'store_name'));
$missing = $this->repository->paginate(1, 50, ['authorization_status' => '未授权']);
$this->assertSame(['C店铺'], array_column($missing['data'], 'store_name'));
// 多选:勾选多个状态时取并集(正常 + 已关闭)。
$multi = $this->repository->paginate(1, 50, ['store_status' => ['正常', '已关闭']]);
$this->assertSame(['A店铺', 'B店铺'], array_column($multi['data'], 'store_name'));
$multiAuth = $this->repository->paginate(1, 50, ['authorization_status' => ['已授权', '未授权']]);
$multiAuthNames = array_column($multiAuth['data'], 'store_name');
sort($multiAuthNames);
$this->assertSame(['A店铺', 'C店铺'], $multiAuthNames);
// 关联出来的列也能排序:正常 -> 已关闭 -> 无档案。
$sorted = $this->repository->paginate(1, 50, ['_sort' => 'store_status', '_direction' => 'asc']);
$this->assertSame(['A店铺', 'B店铺'], array_slice(array_column($sorted['data'], 'store_name'), 0, 2));
$sortedDesc = $this->repository->paginate(1, 50, ['_sort' => 'store_status', '_direction' => 'desc']);
$this->assertSame(['B店铺', 'A店铺'], array_slice(array_column($sortedDesc['data'], 'store_name'), 2));
}
}