579 lines
25 KiB
PHP
579 lines
25 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\EcommerceFinance\Repositories\ThirdPartyWarehouseStorageMirrorRepository;
|
|
use RuntimeException;
|
|
use Tests\TestCase;
|
|
|
|
class ThirdPartyWarehouseStorageMirrorRepositoryTest extends TestCase
|
|
{
|
|
private const HEADER_GENERATION = '11111111-1111-1111-1111-111111111111';
|
|
|
|
private const ITEM_GENERATION = '22222222-2222-2222-2222-222222222222';
|
|
|
|
private const INVENTORY_GENERATION = '33333333-3333-3333-3333-333333333333';
|
|
|
|
private ThirdPartyWarehouseStorageMirrorRepository $repository;
|
|
|
|
private int $nextHeaderId = 1;
|
|
|
|
private int $nextItemId = 1;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'database.connections.business_mirror' => [
|
|
'driver' => 'sqlite',
|
|
'database' => ':memory:',
|
|
'prefix' => '',
|
|
],
|
|
'upstream_mirrors.connection' => 'business_mirror',
|
|
'upstream_mirrors.third_party_warehouse_storage.owner_name' => '常州简尚家居有限公司',
|
|
'upstream_mirrors.third_party_warehouse_storage.stale_after_minutes' => 1560,
|
|
]);
|
|
DB::purge('business_mirror');
|
|
|
|
$this->createMirrorTables();
|
|
$this->createProductTable();
|
|
$this->createPriceHistoryTables();
|
|
$this->repository = new ThirdPartyWarehouseStorageMirrorRepository(app('db'));
|
|
}
|
|
|
|
public function test_it_refuses_queries_until_all_active_generations_finish_initial_sync(): void
|
|
{
|
|
$this->insertState([
|
|
'status' => 'running',
|
|
'run_mode' => 'full',
|
|
'active_header_generation' => self::HEADER_GENERATION,
|
|
'active_item_generation' => self::ITEM_GENERATION,
|
|
'active_inventory_generation' => null,
|
|
'initial_completed_at' => null,
|
|
]);
|
|
DB::connection('business_mirror')->table('business_mirror_table_states')
|
|
->where('dataset_key', 'jiansun.dbo.pm_priceAdjustment_log')
|
|
->update(['initial_completed_at' => null, 'status' => 'running']);
|
|
|
|
$this->assertTrue($this->repository->available());
|
|
$this->assertFalse($this->repository->ready());
|
|
$this->assertSame('initializing', $this->repository->syncStatus()['code']);
|
|
|
|
$this->expectException(RuntimeException::class);
|
|
$this->expectExceptionMessage('third_party_warehouse_mirror_not_ready');
|
|
$this->repository->paginate($this->filters(), 1, 25);
|
|
}
|
|
|
|
public function test_failed_refresh_keeps_reading_only_active_generations(): void
|
|
{
|
|
$this->markReady(['status' => 'failed', 'last_error_code' => 'upstream_timeout']);
|
|
$this->insertProduct('SKU-A', ['autoid' => 10]);
|
|
$this->insertInventory('SKU-A', 6, '2026-09-01 00:00:00');
|
|
$this->insertInventory('SKU-OLD', 99, '2026-09-01 00:00:00', [
|
|
'generation' => '99999999-9999-9999-9999-999999999999',
|
|
]);
|
|
$this->insertMovement('SKU-A', '采购进仓', 1, 1, '2026-08-10 10:00:00');
|
|
$this->insertMovement('SKU-OLD', '采购进仓', 99, 1, '2026-08-10 10:00:00', [
|
|
'header_generation' => '99999999-9999-9999-9999-999999999999',
|
|
'item_generation' => '99999999-9999-9999-9999-999999999999',
|
|
]);
|
|
|
|
$result = $this->repository->paginate($this->filters(), 1, 25);
|
|
|
|
$this->assertTrue($this->repository->ready());
|
|
$this->assertSame('failed', $result['sync']['code']);
|
|
$this->assertSame(1, $result['total']);
|
|
$this->assertSame('SKU-A', $result['data'][0]['sku_code']);
|
|
}
|
|
|
|
public function test_monthly_report_uses_signed_movements_and_each_sku_snapshot_anchor(): void
|
|
{
|
|
$this->markReady();
|
|
$this->insertProduct('SKU-MONTH', [
|
|
'name' => '月报商品',
|
|
'short_name' => null,
|
|
'supplier_name' => '供应商甲',
|
|
'category' => '桌类',
|
|
'purchase_price' => 30,
|
|
]);
|
|
$this->insertInventory('SKU-MONTH', 25, '2026-09-03 12:00:00');
|
|
$this->insertMovement('SKU-MONTH', '采购进仓', 10, 1, '2026-08-01 00:00:00');
|
|
$this->insertMovement('SKU-MONTH', '销售出仓', 8, -1, '2026-08-15 12:00:00');
|
|
$this->insertMovement('SKU-MONTH', '其它进仓', 2, 1, '2026-08-31 23:59:59');
|
|
$this->insertMovement('SKU-MONTH', '采购进仓', 5, 1, '2026-09-01 00:00:00');
|
|
$this->insertMovement('SKU-MONTH', '销售出仓', 4, -1, '2026-09-02 00:00:00');
|
|
// 在该 SKU 自己的快照时间之后,不能参与从快照回推历史余额。
|
|
$this->insertMovement('SKU-MONTH', '采购进仓', 100, 1, '2026-09-04 00:00:00');
|
|
|
|
// 不提供统一 period 时仍兼容旧页面的 month 参数。
|
|
$row = $this->repository->paginate($this->filters(['period' => '']), 1, 25)['data'][0];
|
|
|
|
$this->assertSame('常州简尚家居有限公司', $row['owner']);
|
|
$this->assertSame('月报商品', $row['short_name']);
|
|
$this->assertSame(10.0, $row['purchase_in']);
|
|
$this->assertSame(-8.0, $row['sales_out']);
|
|
$this->assertSame(2.0, $row['other_in']);
|
|
$this->assertSame(24.0, $row['closing_qty']);
|
|
$this->assertSame(20.0, $row['opening_qty']);
|
|
$this->assertSame(93, $row['turnover_days']);
|
|
$this->assertSame(600.0, $row['opening_amount']);
|
|
$this->assertSame(720.0, $row['closing_amount']);
|
|
$this->assertCount(45, $row);
|
|
|
|
$this->insertProduct('SKU-EARLY', ['autoid' => 20, 'purchase_price' => 2]);
|
|
$this->insertInventory('SKU-EARLY', 10, '2026-08-10 00:00:00');
|
|
$this->insertMovement('SKU-EARLY', '采购进仓', 5, 1, '2026-08-20 00:00:00');
|
|
$early = collect($this->repository->paginate($this->filters(), 1, 25)['data'])
|
|
->firstWhere('sku_code', 'SKU-EARLY');
|
|
|
|
$this->assertSame(10.0, $early['opening_qty']);
|
|
$this->assertSame(15.0, $early['closing_qty']);
|
|
}
|
|
|
|
public function test_opening_and_closing_amounts_use_the_price_effective_at_each_boundary(): void
|
|
{
|
|
$this->markReady();
|
|
$this->insertProduct('SKU-PRICE', ['purchase_price' => 999]);
|
|
$this->insertInventory('SKU-PRICE', 28, '2026-09-03 12:00:00');
|
|
$this->insertMovement('SKU-PRICE', '销售出仓', 2, -1, '2026-08-10 12:00:00');
|
|
$this->insertPriceChange('SKU-PRICE', '2026-08-01 00:00:00', 154, 160);
|
|
|
|
$row = $this->repository->paginate($this->filters(), 1, 25)['data'][0];
|
|
|
|
$this->assertSame(30.0, $row['opening_qty']);
|
|
$this->assertSame(28.0, $row['closing_qty']);
|
|
$this->assertSame(4620.0, $row['opening_amount']);
|
|
$this->assertSame(4480.0, $row['closing_amount']);
|
|
}
|
|
|
|
public function test_price_history_must_finish_initial_sync_before_amounts_are_exposed(): void
|
|
{
|
|
$this->markReady();
|
|
DB::connection('business_mirror')->table('business_mirror_table_states')
|
|
->where('dataset_key', 'jiansun.dbo.pm_priceAdjustment_log')
|
|
->update(['initial_completed_at' => null, 'status' => 'running']);
|
|
|
|
$this->assertFalse($this->repository->ready());
|
|
$this->assertSame('dependency_pending', $this->repository->syncStatus()['code']);
|
|
}
|
|
|
|
public function test_product_catalog_must_finish_initial_sync_before_product_fields_are_exposed(): void
|
|
{
|
|
$this->markReady();
|
|
DB::connection('business_mirror')->table('business_mirror_table_states')
|
|
->where('dataset_key', 'operations_replica.rpt.jst_sku')
|
|
->update(['initial_completed_at' => null, 'status' => 'running']);
|
|
|
|
$this->assertFalse($this->repository->ready());
|
|
$this->assertSame('dependency_pending', $this->repository->syncStatus()['code']);
|
|
}
|
|
|
|
public function test_day_dimension_uses_half_open_boundary_and_one_day_turnover(): void
|
|
{
|
|
$this->markReady();
|
|
$this->insertProduct('SKU-DAY', ['name' => '按天商品', 'purchase_price' => 10]);
|
|
$this->insertInventory('SKU-DAY', 22, '2026-08-17 00:00:00');
|
|
$this->insertMovement('SKU-DAY', '采购进仓', 10, 1, '2026-08-15 00:00:00');
|
|
$this->insertMovement('SKU-DAY', '销售出仓', 4, -1, '2026-08-15 23:59:59');
|
|
// 次日零点不属于 8 月 15 日,但属于日末到库存锚点之间的回推区间。
|
|
$this->insertMovement('SKU-DAY', '采购进仓', 3, 1, '2026-08-16 00:00:00');
|
|
|
|
$row = $this->repository->paginate($this->filters([
|
|
'dimension' => 'day',
|
|
'period' => '2026-08-15',
|
|
'month' => '',
|
|
]), 1, 25)['data'][0];
|
|
|
|
$this->assertSame(10.0, $row['purchase_in']);
|
|
$this->assertSame(-4.0, $row['sales_out']);
|
|
$this->assertSame(19.0, $row['closing_qty']);
|
|
$this->assertSame(13.0, $row['opening_qty']);
|
|
$this->assertSame(5, $row['turnover_days']);
|
|
}
|
|
|
|
public function test_it_maps_all_twelve_types_and_excludes_wrong_scope_and_unknown_types(): void
|
|
{
|
|
$this->markReady();
|
|
$this->insertProduct('SKU-TYPES');
|
|
$this->insertInventory('SKU-TYPES', 50, '2026-09-01 00:00:00');
|
|
$types = [
|
|
'加工进仓' => [1, 1],
|
|
'加工出仓' => [2, -1],
|
|
'采购进仓' => [3, 1],
|
|
'采购退货' => [4, -1],
|
|
'其它进仓' => [5, 1],
|
|
'其它退货' => [6, -1],
|
|
'销售出仓' => [7, -1],
|
|
'销售退货' => [8, 1],
|
|
'盘点' => [9, -1],
|
|
'调拨出' => [10, -1],
|
|
'调拨入' => [11, 1],
|
|
'其它出库' => [12, -1],
|
|
];
|
|
foreach ($types as $type => [$qty, $qtyType]) {
|
|
$this->insertMovement('SKU-TYPES', $type, $qty, $qtyType, '2026-08-20 12:00:00');
|
|
}
|
|
$this->insertMovement('SKU-TYPES', '维修', 500, -1, '2026-08-20 12:00:00');
|
|
$this->insertMovement('SKU-TYPES', '虚拟仓调拨入', 500, 1, '2026-08-20 12:00:00');
|
|
$this->insertMovement('SKU-TYPES', '装箱快递', 500, -1, '2026-08-20 12:00:00');
|
|
$this->insertMovement('SKU-TYPES', '采购进仓', 500, 1, '2026-08-20 12:00:00', ['status' => 'WaitConfirm']);
|
|
$this->insertMovement('SKU-TYPES', '采购进仓', 500, 1, '2026-08-20 12:00:00', ['warehouse_id' => 999]);
|
|
|
|
// 同一单据出现更新版本时只认最新头记录,避免重复累计旧版本。
|
|
$this->insertMovement('SKU-TYPES', '采购进仓', 600, 1, '2026-08-20 12:00:00', [
|
|
'io_id' => 9000,
|
|
'header_id' => 9000,
|
|
]);
|
|
$this->insertHeaderOnly(9001, 9000, '采购进仓', '2026-08-20 12:00:00', 'WaitConfirm');
|
|
|
|
$row = $this->repository->paginate($this->filters(), 1, 25)['data'][0];
|
|
|
|
$expected = [
|
|
'processing_in' => 1.0,
|
|
'processing_out' => -2.0,
|
|
'purchase_in' => 3.0,
|
|
'purchase_return' => -4.0,
|
|
'other_in' => 5.0,
|
|
'other_return' => -6.0,
|
|
'sales_out' => -7.0,
|
|
'sales_return' => 8.0,
|
|
'stocktake' => -9.0,
|
|
'transfer_out' => -10.0,
|
|
'transfer_in' => 11.0,
|
|
'other_out' => -12.0,
|
|
];
|
|
foreach ($expected as $column => $value) {
|
|
$this->assertSame($value, $row[$column], $column);
|
|
}
|
|
}
|
|
|
|
public function test_filters_paging_summary_options_and_export_share_the_non_zero_scope(): void
|
|
{
|
|
$this->markReady();
|
|
foreach ([
|
|
['SKU-C', '椅子 C', '供应商乙', '椅类', 3],
|
|
['SKU-A', '桌子 A', '供应商甲', '桌类', 1],
|
|
['SKU-B', '桌子 B', '供应商甲', '桌类', 2],
|
|
] as [$sku, $name, $supplier, $category, $qty]) {
|
|
$this->insertProduct($sku, [
|
|
'name' => $name,
|
|
'supplier_name' => $supplier,
|
|
'category' => $category,
|
|
]);
|
|
$this->insertInventory($sku, $qty, '2026-09-01 00:00:00');
|
|
}
|
|
$this->insertProduct('SKU-ZERO', ['name' => '零库存', 'supplier_name' => '供应商甲', 'category' => '桌类']);
|
|
$this->insertInventory('SKU-ZERO', 0, '2026-09-01 00:00:00');
|
|
// 没有库存快照但存在相关流水的 SKU 也必须进入候选集合。
|
|
$this->insertProduct('SKU-FLOW', ['name' => '桌子流水', 'supplier_name' => '供应商甲', 'category' => '桌类']);
|
|
$this->insertMovement('SKU-FLOW', '销售出仓', 2, -1, '2026-08-10 00:00:00');
|
|
$this->insertMovement('SKU-FLOW', '销售出仓', 3, -1, '2026-09-02 00:00:00');
|
|
|
|
$filters = $this->filters(['product_name' => '桌子', 'supplier' => '供应商甲', 'category' => '桌类']);
|
|
$pageOne = $this->repository->paginate($filters, 1, 1);
|
|
$pageTwo = $this->repository->paginate($filters, 2, 1);
|
|
$summary = $this->repository->summary($filters);
|
|
$options = $this->repository->filterOptions($this->filters(['product_name' => '桌子']));
|
|
$export = iterator_to_array($this->repository->exportRows($filters, 1), false);
|
|
|
|
$this->assertSame(3, $pageOne['total']);
|
|
$this->assertSame('SKU-A', $pageOne['data'][0]['sku_code']);
|
|
$this->assertSame('SKU-B', $pageTwo['data'][0]['sku_code']);
|
|
$this->assertSame([
|
|
'count' => 3,
|
|
'opening_qty' => 8.0,
|
|
'closing_qty' => 6.0,
|
|
'opening_amount' => 440.0,
|
|
'closing_amount' => 330.0,
|
|
], $summary);
|
|
$this->assertSame(['供应商甲'], $options['suppliers']);
|
|
$this->assertSame(['桌类'], $options['categories']);
|
|
$this->assertSame(['SKU-A', 'SKU-B', 'SKU-FLOW'], array_column($export, 'sku_code'));
|
|
$this->assertSame(1, $this->repository->summary($this->filters(['sku_code' => 'SKU-B']))['count']);
|
|
}
|
|
|
|
public function test_export_keeps_its_initial_generation_when_publication_switches_between_batches(): void
|
|
{
|
|
$this->markReady();
|
|
foreach (['SKU-A', 'SKU-B'] as $sku) {
|
|
$this->insertProduct($sku);
|
|
$this->insertInventory($sku, 1, '2026-09-01 00:00:00');
|
|
}
|
|
|
|
$export = $this->repository->exportRows($this->filters(), 1);
|
|
$this->assertTrue($export->valid());
|
|
$this->assertSame('SKU-A', $export->current()['sku_code']);
|
|
|
|
DB::connection('business_mirror')->table('third_party_warehouse_sync_states')->update([
|
|
'active_header_generation' => 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
'active_item_generation' => 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
|
'active_inventory_generation' => 'cccccccc-cccc-cccc-cccc-cccccccccccc',
|
|
]);
|
|
$export->next();
|
|
|
|
$this->assertTrue($export->valid());
|
|
$this->assertSame('SKU-B', $export->current()['sku_code']);
|
|
}
|
|
|
|
private function createMirrorTables(): void
|
|
{
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create('third_party_warehouse_sync_states', function (Blueprint $table): void {
|
|
$table->string('dataset_key')->primary();
|
|
$table->string('status')->nullable();
|
|
$table->string('run_mode')->nullable();
|
|
$table->string('active_header_generation')->nullable();
|
|
$table->string('active_item_generation')->nullable();
|
|
$table->string('active_inventory_generation')->nullable();
|
|
$table->dateTime('initial_completed_at')->nullable();
|
|
$table->dateTime('last_succeeded_at')->nullable();
|
|
$table->string('last_error_code')->nullable();
|
|
});
|
|
$schema->create('third_party_warehouse_inout_headers', function (Blueprint $table): void {
|
|
$table->id('_mirror_id');
|
|
$table->string('generation');
|
|
$table->unsignedBigInteger('id');
|
|
$table->unsignedBigInteger('io_id');
|
|
$table->unsignedBigInteger('wms_co_id');
|
|
$table->string('status')->nullable();
|
|
$table->string('io_type')->nullable();
|
|
$table->dateTime('occurred_at')->nullable();
|
|
$table->dateTime('source_modified_at')->nullable();
|
|
});
|
|
$schema->create('third_party_warehouse_inout_items', function (Blueprint $table): void {
|
|
$table->id('_mirror_id');
|
|
$table->string('generation');
|
|
$table->unsignedBigInteger('id');
|
|
$table->unsignedBigInteger('io_id');
|
|
$table->string('sku_code');
|
|
$table->decimal('qty', 20, 4);
|
|
$table->smallInteger('qty_type');
|
|
$table->dateTime('source_modified_at')->nullable();
|
|
});
|
|
$schema->create('third_party_warehouse_inventory_snapshots', function (Blueprint $table): void {
|
|
$table->id('_mirror_id');
|
|
$table->string('generation');
|
|
$table->unsignedBigInteger('id');
|
|
$table->unsignedBigInteger('wms_co_id');
|
|
$table->string('sku_code');
|
|
$table->decimal('qty', 20, 4);
|
|
$table->dateTime('source_modified_at')->nullable();
|
|
$table->dateTime('captured_at')->nullable();
|
|
});
|
|
}
|
|
|
|
private function createProductTable(): void
|
|
{
|
|
Schema::connection('business_mirror')->create('ops_jst_sku', function (Blueprint $table): void {
|
|
$table->unsignedBigInteger('autoid');
|
|
$table->string('sku_id')->nullable();
|
|
$table->string('sku_type')->nullable();
|
|
foreach ([
|
|
'pic_big', 'pic', 'name', 'short_name', 'supplier_name', 'category',
|
|
'vc_name', 'supplier_sku_id', 'properties_value', 'color', 'brand',
|
|
'unit', 'other_price_1', 'other_price_2', 'other_price_3',
|
|
'other_price_4', 'other_1', 'other_2', 'other_3', 'other_4',
|
|
'other_5', 'other_6', 'other_7', 'other_8', 'other_9', 'other_10',
|
|
] as $column) {
|
|
$table->string($column)->nullable();
|
|
}
|
|
$table->decimal('pack_qty', 20, 4)->nullable();
|
|
$table->decimal('purchase_price', 20, 4)->nullable();
|
|
});
|
|
}
|
|
|
|
private function createPriceHistoryTables(): void
|
|
{
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create('business_mirror_table_states', function (Blueprint $table): void {
|
|
$table->string('dataset_key')->primary();
|
|
$table->string('status')->nullable();
|
|
$table->dateTime('initial_completed_at')->nullable();
|
|
$table->dateTime('last_succeeded_at')->nullable();
|
|
});
|
|
$schema->create('js_pm_price_adjustment_log', function (Blueprint $table): void {
|
|
$table->unsignedBigInteger('Id');
|
|
$table->string('skucode')->nullable();
|
|
$table->integer('priceType')->nullable();
|
|
$table->decimal('originalPrice', 20, 4)->nullable();
|
|
$table->decimal('afterPrice', 20, 4)->nullable();
|
|
$table->dateTime('effectStartTime')->nullable();
|
|
});
|
|
DB::connection('business_mirror')->table('business_mirror_table_states')->insert([
|
|
[
|
|
'dataset_key' => 'operations_replica.rpt.jst_sku',
|
|
'status' => 'succeeded',
|
|
'initial_completed_at' => '2026-09-01 03:00:00',
|
|
'last_succeeded_at' => '2026-09-11 03:00:00',
|
|
],
|
|
[
|
|
'dataset_key' => 'jiansun.dbo.pm_priceAdjustment_log',
|
|
'status' => 'succeeded',
|
|
'initial_completed_at' => '2026-09-01 03:00:00',
|
|
'last_succeeded_at' => '2026-09-11 03:00:00',
|
|
],
|
|
]);
|
|
}
|
|
|
|
/** @param array<string,mixed> $overrides */
|
|
private function markReady(array $overrides = []): void
|
|
{
|
|
$this->insertState(array_merge([
|
|
'status' => 'succeeded',
|
|
'run_mode' => null,
|
|
'active_header_generation' => self::HEADER_GENERATION,
|
|
'active_item_generation' => self::ITEM_GENERATION,
|
|
'active_inventory_generation' => self::INVENTORY_GENERATION,
|
|
'initial_completed_at' => '2026-09-11 03:20:00',
|
|
'last_succeeded_at' => '2026-09-11 03:21:00',
|
|
'last_error_code' => null,
|
|
], $overrides));
|
|
}
|
|
|
|
/** @param array<string,mixed> $values */
|
|
private function insertState(array $values): void
|
|
{
|
|
DB::connection('business_mirror')->table('third_party_warehouse_sync_states')->insert(
|
|
['dataset_key' => ThirdPartyWarehouseStorageMirrorRepository::DATASET_KEY] + $values,
|
|
);
|
|
}
|
|
|
|
/** @param array<string,mixed> $overrides */
|
|
private function insertProduct(string $sku, array $overrides = []): void
|
|
{
|
|
DB::connection('business_mirror')->table('ops_jst_sku')->insert(array_merge([
|
|
'autoid' => 1,
|
|
'sku_id' => $sku,
|
|
'sku_type' => 'normal',
|
|
'pic_big' => 'https://example.test/' . $sku . '.jpg',
|
|
'pic' => null,
|
|
'name' => '测试商品',
|
|
'short_name' => '测试简称',
|
|
'supplier_name' => '测试供应商',
|
|
'category' => '测试分类',
|
|
'vc_name' => '测试虚拟分类',
|
|
'supplier_sku_id' => 'SUP-' . $sku,
|
|
'properties_value' => '原木色',
|
|
'color' => null,
|
|
'brand' => '测试品牌',
|
|
'unit' => '件',
|
|
'purchase_price' => 55,
|
|
'other_price_1' => '采购价',
|
|
'other_price_2' => '按需叫货',
|
|
'other_price_3' => null,
|
|
'other_price_4' => null,
|
|
'pack_qty' => 1,
|
|
'other_1' => '型号',
|
|
'other_2' => '板材',
|
|
'other_3' => '采购',
|
|
'other_4' => null,
|
|
'other_5' => '在产',
|
|
'other_6' => null,
|
|
'other_7' => 'ITEM-' . $sku,
|
|
'other_8' => '单包',
|
|
'other_9' => 'DRAW-' . $sku,
|
|
'other_10' => '木',
|
|
], $overrides));
|
|
}
|
|
|
|
/** @param array<string,mixed> $overrides */
|
|
private function insertInventory(string $sku, float $qty, string $capturedAt, array $overrides = []): void
|
|
{
|
|
static $id = 1;
|
|
DB::connection('business_mirror')->table('third_party_warehouse_inventory_snapshots')->insert(array_merge([
|
|
'generation' => self::INVENTORY_GENERATION,
|
|
'id' => $id++,
|
|
'wms_co_id' => ThirdPartyWarehouseStorageMirrorRepository::WAREHOUSE_ID,
|
|
'sku_code' => $sku,
|
|
'qty' => $qty,
|
|
'captured_at' => $capturedAt,
|
|
], $overrides));
|
|
}
|
|
|
|
private function insertPriceChange(
|
|
string $sku,
|
|
string $effectStartTime,
|
|
float $originalPrice,
|
|
float $afterPrice,
|
|
): void {
|
|
static $id = 1;
|
|
DB::connection('business_mirror')->table('js_pm_price_adjustment_log')->insert([
|
|
'Id' => $id++,
|
|
'skucode' => $sku,
|
|
'priceType' => 1,
|
|
'originalPrice' => $originalPrice,
|
|
'afterPrice' => $afterPrice,
|
|
'effectStartTime' => $effectStartTime,
|
|
]);
|
|
}
|
|
|
|
/** @param array<string,mixed> $overrides */
|
|
private function insertMovement(
|
|
string $sku,
|
|
string $type,
|
|
float $qty,
|
|
int $qtyType,
|
|
string $occurredAt,
|
|
array $overrides = [],
|
|
): void {
|
|
$ioId = $overrides['io_id'] ?? (1000 + $this->nextHeaderId);
|
|
$headerGeneration = $overrides['header_generation'] ?? self::HEADER_GENERATION;
|
|
$itemGeneration = $overrides['item_generation'] ?? self::ITEM_GENERATION;
|
|
DB::connection('business_mirror')->table('third_party_warehouse_inout_headers')->insert([
|
|
'generation' => $headerGeneration,
|
|
'id' => $overrides['header_id'] ?? $this->nextHeaderId++,
|
|
'io_id' => $ioId,
|
|
'wms_co_id' => $overrides['warehouse_id'] ?? ThirdPartyWarehouseStorageMirrorRepository::WAREHOUSE_ID,
|
|
'status' => $overrides['status'] ?? 'Confirmed',
|
|
'io_type' => $type,
|
|
'occurred_at' => $occurredAt,
|
|
]);
|
|
DB::connection('business_mirror')->table('third_party_warehouse_inout_items')->insert([
|
|
'generation' => $itemGeneration,
|
|
'id' => $this->nextItemId++,
|
|
'io_id' => $ioId,
|
|
'sku_code' => $sku,
|
|
'qty' => $qty,
|
|
'qty_type' => $qtyType,
|
|
]);
|
|
}
|
|
|
|
private function insertHeaderOnly(
|
|
int $id,
|
|
int $ioId,
|
|
string $type,
|
|
string $occurredAt,
|
|
string $status,
|
|
): void {
|
|
DB::connection('business_mirror')->table('third_party_warehouse_inout_headers')->insert([
|
|
'generation' => self::HEADER_GENERATION,
|
|
'id' => $id,
|
|
'io_id' => $ioId,
|
|
'wms_co_id' => ThirdPartyWarehouseStorageMirrorRepository::WAREHOUSE_ID,
|
|
'status' => $status,
|
|
'io_type' => $type,
|
|
'occurred_at' => $occurredAt,
|
|
]);
|
|
}
|
|
|
|
/** @param array<string,mixed> $overrides */
|
|
private function filters(array $overrides = []): array
|
|
{
|
|
return array_merge([
|
|
'dimension' => 'month',
|
|
'period' => '2026-08',
|
|
'month' => '2026-08',
|
|
'date' => '',
|
|
'sku_code' => '',
|
|
'product_name' => '',
|
|
'supplier' => '',
|
|
'category' => '',
|
|
], $overrides);
|
|
}
|
|
}
|