原来取的是中台库 dbo.flow_flow,那份数据停在 2026-07-16,7 月之后的申请 (比如 shp20260716165004772 京喜-清木居工厂店 关店流程)审批状态全是空的, 而工作平台显示「流程中」。 jiansun.dbo.flow_flow 是实时表(今天还在更新,10543 行),同名单据里 flowStatus=1、当前审批人=山时。已登记并完成首次全量 + 建 flowId 索引; 中台那份旧副本改为停止同步(数据保留)。 流程统计页与店铺账号密码的「流程类型 / 审批状态 / 流程状态」两处取值都跟着换成实时表。
239 lines
13 KiB
PHP
239 lines
13 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\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_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));
|
||
}
|
||
}
|