CaiWuXiTong/tests/Unit/EcommerceFinance/StoreAccountMirrorRepositoryTest.php
renshen d69a7d2ca7 店铺账号密码:把流程类型和审批状态两列都拉过来
上一版只带了「流程状态」(关店流程的审批状态)。改成按 ERP 店铺名取该店铺
最近一单申请流程(开店/变更/关店/主体注销自动),同时给出流程类型与审批状态,
列位置仍在「店铺状态」后面;两列都可搜索多选筛选、可排序。

线上:966 个账号店铺里 396 家最近一单是关店流程;状态文案与流程统计页一致。
状态映射缓存从 10 分钟放宽到 30 分钟(全量算一次要几秒,数据变化很慢)。
2026-10-08 15:12:41 +08:00

239 lines
13 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'],
],
'js_data_middle_platform.dbo.flow_flow' => [
'source_key' => 'js_data_middle_platform', '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_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));
}
}