From caf2030bdf47740597c7168a874d775934ff22be Mon Sep 17 00:00:00 2001 From: chenxi Date: Sun, 20 Sep 2026 22:19:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BF=9B=E9=94=80=E5=AD=98?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=AD=90=E4=BB=93=E7=BB=B4=E5=BA=A6=20(#220)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...reate_inventory_subwarehouse_snapshots.php | 61 ++++++++++++++++ ...dPartyWarehouseStorageMirrorRepository.php | 49 +++++++++---- ...artyWarehouseStorageUpstreamRepository.php | 6 ++ ...ThirdPartyWarehouseStorageSynchronizer.php | 54 ++++++++++++++ ...tyWarehouseStorageMirrorRepositoryTest.php | 73 ++++++++++++++++++- ...dPartyWarehouseStorageSynchronizerTest.php | 30 ++++++++ ...WarehouseStorageUpstreamRepositoryTest.php | 3 + 7 files changed, 258 insertions(+), 18 deletions(-) create mode 100644 database/migrations/2026_09_20_181000_create_inventory_subwarehouse_snapshots.php diff --git a/database/migrations/2026_09_20_181000_create_inventory_subwarehouse_snapshots.php b/database/migrations/2026_09_20_181000_create_inventory_subwarehouse_snapshots.php new file mode 100644 index 00000000..ebaf23be --- /dev/null +++ b/database/migrations/2026_09_20_181000_create_inventory_subwarehouse_snapshots.php @@ -0,0 +1,61 @@ +connection()); + if ($schema->hasTable('third_party_warehouse_inventory_subwarehouse_snapshots')) { + return; + } + + $schema->create('third_party_warehouse_inventory_subwarehouse_snapshots', function (Blueprint $table): void { + $table->bigIncrements('_mirror_id'); + $table->uuid('generation'); + $table->string('source_key', 64)->default('jiansun'); + $table->unsignedBigInteger('id'); + $table->unsignedBigInteger('wms_co_id'); + $table->unsignedSmallInteger('wh_id'); + $table->string('sku_code', 191); + $table->decimal('qty', 20, 4)->default(0); + $table->dateTime('source_modified_at', 3)->nullable(); + $table->dateTime('captured_at', 6); + $table->string('row_hash', 64); + $table->dateTime('synced_at', 6); + $table->unique(['generation', 'id', 'wh_id'], 'tpw_subinventory_generation_id_wh_unique'); + $table->index( + ['generation', 'wms_co_id', 'wh_id', 'sku_code'], + 'tpw_subinventory_report_index', + ); + $table->index(['generation', 'sku_code'], 'tpw_subinventory_sku_index'); + }); + + $connection = DB::connection($this->connection()); + $connection->table('third_party_warehouse_inventory_subwarehouse_snapshots')->insertUsing( + [ + 'generation', 'source_key', 'id', 'wms_co_id', 'wh_id', 'sku_code', 'qty', + 'source_modified_at', 'captured_at', 'row_hash', 'synced_at', + ], + $connection->table('third_party_warehouse_inventory_snapshots')->select([ + 'generation', 'source_key', 'id', 'wms_co_id', DB::raw('1'), 'sku_code', 'qty', + 'source_modified_at', 'captured_at', 'row_hash', 'synced_at', + ]), + ); + } + + public function down(): void + { + Schema::connection($this->connection()) + ->dropIfExists('third_party_warehouse_inventory_subwarehouse_snapshots'); + } +}; diff --git a/modules/EcommerceFinance/Repositories/ThirdPartyWarehouseStorageMirrorRepository.php b/modules/EcommerceFinance/Repositories/ThirdPartyWarehouseStorageMirrorRepository.php index 2034b167..abd71fa9 100644 --- a/modules/EcommerceFinance/Repositories/ThirdPartyWarehouseStorageMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/ThirdPartyWarehouseStorageMirrorRepository.php @@ -32,6 +32,8 @@ class ThirdPartyWarehouseStorageMirrorRepository private const INVENTORY_TABLE = 'third_party_warehouse_inventory_snapshots'; + private const SUBWAREHOUSE_INVENTORY_TABLE = 'third_party_warehouse_inventory_subwarehouse_snapshots'; + private const AUTHORITATIVE_STATE_TABLE = 'third_party_warehouse_authoritative_anchor_states'; private const AUTHORITATIVE_ANCHOR_TABLE = 'third_party_warehouse_authoritative_anchors'; @@ -68,6 +70,17 @@ class ThirdPartyWarehouseStorageMirrorRepository 'other_out' => '其它出库', ]; + /** @var array */ + private const SUBWAREHOUSE_NAMES = [ + 1 => '主仓', + 2 => '销退仓', + 3 => '进货仓', + 4 => '次品仓', + 6 => '自定义仓1', + 7 => '自定义仓2', + 8 => '自定义仓3', + ]; + /** @var list */ private const OUTPUT_COLUMNS = [ 'owner', 'date', 'sku_code', 'image_url', 'product_name', 'short_name', @@ -272,13 +285,14 @@ class ThirdPartyWarehouseStorageMirrorRepository $latestWarehouses = $this->connection->table(self::WAREHOUSE_TABLE) ->whereNotNull('wms_co_id')->groupBy('wms_co_id') ->select('wms_co_id')->selectRaw('MAX(Id) AS Id'); - $inventoryPairs = $this->connection->table(self::INVENTORY_TABLE) + $inventoryDimensions = $this->connection->table(self::SUBWAREHOUSE_INVENTORY_TABLE) ->where('generation', $inventoryGeneration) ->where('qty', '!=', 0) ->whereNotNull('sku_code')->where('sku_code', '!=', '') - ->select(['wms_co_id', 'sku_code']); - $candidates = $this->connection->query()->fromSub($inventoryPairs, 'pairs') - ->select(['pairs.wms_co_id', 'pairs.sku_code']); + ->select(['wms_co_id', 'wh_id', 'sku_code', 'captured_at']) + ->selectRaw('qty AS snapshot_qty'); + $candidates = $this->connection->query()->fromSub($inventoryDimensions, 'pairs') + ->select(['pairs.wms_co_id', 'pairs.sku_code', 'pairs.wh_id']); $this->containsFilter($candidates, 'pairs.sku_code', $filters['product_code'] ?? ''); $warehouseFilter = trim((string) ($filters['warehouse_provider'] ?? '')); if ($warehouseFilter !== '') { @@ -304,7 +318,7 @@ class ThirdPartyWarehouseStorageMirrorRepository $total = $includeTotal ? (clone $candidates)->count() : 0; $pagePairs = (clone $candidates) - ->orderBy('pairs.wms_co_id')->orderBy('pairs.sku_code') + ->orderBy('pairs.wms_co_id')->orderBy('pairs.wh_id')->orderBy('pairs.sku_code') ->forPage($page, $perPage); $pairRows = (clone $pagePairs)->get(); if ($pairRows->isEmpty()) { @@ -327,15 +341,15 @@ class ThirdPartyWarehouseStorageMirrorRepository $join->on('w.wms_co_id', '=', 'latest_warehouse.wms_co_id') ->on('w.Id', '=', 'latest_warehouse.Id'); }) - ->select(['pairs.wms_co_id', 'pairs.sku_code', 's.category', 'w.name as warehouse_name']); + ->select(['pairs.wms_co_id', 'pairs.wh_id', 'pairs.sku_code', 's.category', 'w.name as warehouse_name']); $anchors = $this->connection->query()->fromSub($dimensionedPagePairs, 'pairs') - ->leftJoin(self::INVENTORY_TABLE . ' as inventory', function ($join) use ($inventoryGeneration): void { + ->leftJoinSub(clone $inventoryDimensions, 'inventory', function ($join): void { $join->on('inventory.wms_co_id', '=', 'pairs.wms_co_id') ->on('inventory.sku_code', '=', 'pairs.sku_code') - ->where('inventory.generation', '=', $inventoryGeneration); + ->on('inventory.wh_id', '=', 'pairs.wh_id'); }) - ->select(['pairs.wms_co_id', 'pairs.sku_code', 'pairs.category', 'pairs.warehouse_name']) - ->selectRaw('COALESCE(inventory.qty, 0) AS snapshot_qty') + ->select(['pairs.wms_co_id', 'pairs.wh_id', 'pairs.sku_code', 'pairs.category', 'pairs.warehouse_name']) + ->selectRaw('COALESCE(inventory.snapshot_qty, 0) AS snapshot_qty') ->selectRaw('COALESCE(inventory.captured_at, ?) AS snapshot_at', [$anchorAt]); $movements = $this->confirmedMovements($headerGeneration, $itemGeneration, null) @@ -346,7 +360,8 @@ class ThirdPartyWarehouseStorageMirrorRepository ->select([ 'h.wms_co_id', 'h.io_type', 'h.occurred_at', 'i.sku_code', 'i.qty as movement_qty', 'i.qty_type as movement_qty_type', - ]); + ]) + ->selectRaw('COALESCE(NULLIF(h.wh_id, 0), 1) AS wh_id'); $signed = '(COALESCE(mv.movement_qty, 0) * COALESCE(mv.movement_qty_type, 0))'; $balanceAt = static fn (string $target): array => [ 'a.snapshot_qty + COALESCE(SUM(CASE ' @@ -360,13 +375,14 @@ class ThirdPartyWarehouseStorageMirrorRepository $monthly = $this->connection->query()->fromSub($anchors, 'a') ->leftJoinSub($movements, 'mv', function ($join): void { $join->on('mv.wms_co_id', '=', 'a.wms_co_id') + ->on('mv.wh_id', '=', 'a.wh_id') ->on('mv.sku_code', '=', 'a.sku_code'); }) ->groupBy([ - 'a.wms_co_id', 'a.sku_code', 'a.category', 'a.warehouse_name', + 'a.wms_co_id', 'a.wh_id', 'a.sku_code', 'a.category', 'a.warehouse_name', 'a.snapshot_qty', 'a.snapshot_at', ]) - ->select(['a.wms_co_id', 'a.sku_code', 'a.category', 'a.warehouse_name']) + ->select(['a.wms_co_id', 'a.wh_id', 'a.sku_code', 'a.category', 'a.warehouse_name']) ->selectRaw($openingSql . ' AS opening_qty', $openingBindings) ->selectRaw($closingSql . ' AS closing_qty', $closingBindings); foreach (self::MOVEMENT_COLUMNS as $column => $type) { @@ -379,9 +395,12 @@ class ThirdPartyWarehouseStorageMirrorRepository $monthly->selectRaw('0 AS opening_aux'); $rows = $this->connection->query()->fromSub($monthly, 'm') - ->select('m.*')->orderBy('m.warehouse_name')->orderBy('m.sku_code')->get() + ->select('m.*')->orderBy('m.warehouse_name')->orderBy('m.wh_id')->orderBy('m.sku_code')->get() ->map(static function (object $row) use ($period): object { $warehouse = trim((string) ($row->warehouse_name ?? '')); + $subwarehouseId = (int) ($row->wh_id ?? 0); + $subwarehouse = self::SUBWAREHOUSE_NAMES[$subwarehouseId] + ?? '其他子仓(' . $subwarehouseId . ')'; return (object) [ 'owner' => (string) config( @@ -390,7 +409,7 @@ class ThirdPartyWarehouseStorageMirrorRepository ), 'month' => Carbon::parse($period['start'])->toDateString(), 'warehouse_provider' => $warehouse, - 'warehouse' => $warehouse, + 'warehouse' => $subwarehouse, 'product_code' => (string) ($row->sku_code ?? ''), 'category' => (string) ($row->category ?? ''), 'opening_quantity' => $row->opening_qty, diff --git a/modules/EcommerceFinance/Repositories/ThirdPartyWarehouseStorageUpstreamRepository.php b/modules/EcommerceFinance/Repositories/ThirdPartyWarehouseStorageUpstreamRepository.php index 8da325f4..6b623dd7 100644 --- a/modules/EcommerceFinance/Repositories/ThirdPartyWarehouseStorageUpstreamRepository.php +++ b/modules/EcommerceFinance/Repositories/ThirdPartyWarehouseStorageUpstreamRepository.php @@ -185,6 +185,12 @@ SQL, [$cursorAt, $throughAt, $cursorAt, $cursorAt, $cursorId]); [wms_co_id], [sku_id] AS sku_code, [qty], + [return_qty], + [in_qty], + [defective_qty], + [customize_qty_1], + [customize_qty_2], + [customize_qty_3], CONVERT(varchar(27), [modified], 126) AS source_modified_at FROM [dbo].[jst_inventoryInfo] WHERE [Id] > ? diff --git a/modules/EcommerceFinance/Services/ThirdPartyWarehouseStorageSynchronizer.php b/modules/EcommerceFinance/Services/ThirdPartyWarehouseStorageSynchronizer.php index 519484b5..e01ca858 100644 --- a/modules/EcommerceFinance/Services/ThirdPartyWarehouseStorageSynchronizer.php +++ b/modules/EcommerceFinance/Services/ThirdPartyWarehouseStorageSynchronizer.php @@ -30,6 +30,14 @@ class ThirdPartyWarehouseStorageSynchronizer private const INVENTORY_TABLE = 'third_party_warehouse_inventory_snapshots'; + private const SUBWAREHOUSE_INVENTORY_TABLE = 'third_party_warehouse_inventory_subwarehouse_snapshots'; + + /** @var array */ + private const SUBWAREHOUSE_QUANTITY_FIELDS = [ + 1 => 'qty', 2 => 'return_qty', 3 => 'in_qty', 4 => 'defective_qty', + 6 => 'customize_qty_1', 7 => 'customize_qty_2', 8 => 'customize_qty_3', + ]; + private readonly ConnectionInterface $connection; private readonly Repository $cache; @@ -610,9 +618,14 @@ class ThirdPartyWarehouseStorageSynchronizer fn (array $row): ?array => $this->mapInventory($row, $generation, $now), $rows, ))); + $subwarehouseMapped = []; + foreach ($rows as $row) { + array_push($subwarehouseMapped, ...$this->mapSubwarehouseInventory($row, $generation, $now)); + } $this->connection->transaction(function () use ( $mapped, + $subwarehouseMapped, $generation, $cursor, $now, @@ -620,6 +633,11 @@ class ThirdPartyWarehouseStorageSynchronizer ): void { $this->assertCurrentRunToken($runToken); $this->upsertRows(self::INVENTORY_TABLE, $mapped, ['generation', 'id']); + $this->upsertRows( + self::SUBWAREHOUSE_INVENTORY_TABLE, + $subwarehouseMapped, + ['generation', 'id', 'wh_id'], + ); $this->updateStateForToken([ 'inventory_cursor' => $cursor, 'updated_at' => $now, @@ -987,6 +1005,7 @@ class ThirdPartyWarehouseStorageSynchronizer [self::HEADER_TABLE, 'header_generation', (string) $release->header_generation], [self::ITEM_TABLE, 'item_generation', (string) $release->item_generation], [self::INVENTORY_TABLE, 'inventory_generation', (string) $release->inventory_generation], + [self::SUBWAREHOUSE_INVENTORY_TABLE, 'inventory_generation', (string) $release->inventory_generation], ] as [$table, $column, $generation]) { if ($generation === '' || in_array($generation, $protected, true)) { continue; @@ -1110,6 +1129,40 @@ class ThirdPartyWarehouseStorageSynchronizer ]; } + /** @return list> */ + private function mapSubwarehouseInventory(array $row, string $generation, mixed $now): array + { + $base = $this->mapInventory($row, $generation, $now); + if ($base === null) { + return []; + } + + $rows = []; + foreach (self::SUBWAREHOUSE_QUANTITY_FIELDS as $warehouseId => $field) { + $quantity = (float) ($row[$field] ?? 0); + if ($quantity == 0.0) { + continue; + } + $values = [ + 'generation' => $generation, + 'source_key' => $base['source_key'], + 'id' => $base['id'], + 'wms_co_id' => $base['wms_co_id'], + 'wh_id' => $warehouseId, + 'sku_code' => $base['sku_code'], + 'qty' => $quantity, + 'source_modified_at' => $base['source_modified_at'], + 'captured_at' => $base['captured_at'], + ]; + $rows[] = $values + [ + 'row_hash' => $this->rowHash($values), + 'synced_at' => $now, + ]; + } + + return $rows; + } + /** * @param list> $rows * @param list $uniqueBy @@ -1223,6 +1276,7 @@ class ThirdPartyWarehouseStorageSynchronizer self::HEADER_TABLE, self::ITEM_TABLE, self::INVENTORY_TABLE, + self::SUBWAREHOUSE_INVENTORY_TABLE, ] as $table) { if (! $schema->hasTable($table)) { throw new RuntimeException('third_party_warehouse_mirror_schema_missing:' . $table); diff --git a/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageMirrorRepositoryTest.php index e86968a4..405d282b 100644 --- a/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageMirrorRepositoryTest.php @@ -160,7 +160,7 @@ class ThirdPartyWarehouseStorageMirrorRepositoryTest extends TestCase $this->assertSame(1, $result['total']); $this->assertSame('2026-08-01', $row->month); $this->assertSame('三方-南昌樵舍仓', $row->warehouse_provider); - $this->assertSame('三方-南昌樵舍仓', $row->warehouse); + $this->assertSame('主仓', $row->warehouse); $this->assertSame('SKU-MONTHLY', $row->product_code); $this->assertSame('住宅家具 - 书桌', $row->category); $this->assertSame(8.0, (float) $row->opening_quantity); @@ -170,6 +170,35 @@ class ThirdPartyWarehouseStorageMirrorRepositoryTest extends TestCase $this->assertSame(2.0, (float) $row->sales_return); } + public function test_inventory_report_splits_main_return_inbound_and_other_subwarehouses(): void + { + $this->markReady(); + $this->insertProduct('SKU-SUBWAREHOUSE', ['category' => '测试分类']); + $this->insertInventory('SKU-SUBWAREHOUSE', 10, '2026-09-01 00:00:00', [ + 'return_qty' => 4, + 'in_qty' => 3, + 'customize_qty_1' => 2, + ]); + $this->insertMovement('SKU-SUBWAREHOUSE', '销售退货', 2, 1, '2026-08-20 10:00:00', ['wh_id' => 2]); + $this->insertMovement('SKU-SUBWAREHOUSE', '采购进仓', 3, 1, '2026-08-21 10:00:00', ['wh_id' => 3]); + + $result = $this->repository->paginateMonthlyInventoryReport([ + 'month' => '2026-08', + 'product_code' => 'SKU-SUBWAREHOUSE', + 'warehouse_provider' => '', + 'category' => '', + ], 1, 25); + $rows = collect($result['data'])->keyBy('warehouse'); + + $this->assertSame(4, $result['total']); + $this->assertSame(['主仓', '销退仓', '进货仓', '自定义仓1'], $rows->keys()->all()); + $this->assertSame(2.0, (float) $rows['销退仓']->opening_quantity); + $this->assertSame(4.0, (float) $rows['销退仓']->closing_quantity); + $this->assertSame(2.0, (float) $rows['销退仓']->sales_return); + $this->assertSame(0.0, (float) $rows['进货仓']->opening_quantity); + $this->assertSame(3.0, (float) $rows['进货仓']->closing_quantity); + } + public function test_inventory_report_reads_and_filters_warehouses_outside_the_third_party_scope(): void { $this->markReady(); @@ -539,6 +568,16 @@ class ThirdPartyWarehouseStorageMirrorRepositoryTest extends TestCase $table->dateTime('source_modified_at')->nullable(); $table->dateTime('captured_at')->nullable(); }); + $schema->create('third_party_warehouse_inventory_subwarehouse_snapshots', function (Blueprint $table): void { + $table->id('_mirror_id'); + $table->string('generation'); + $table->unsignedBigInteger('id'); + $table->unsignedBigInteger('wms_co_id'); + $table->unsignedSmallInteger('wh_id'); + $table->string('sku_code'); + $table->decimal('qty', 20, 4); + $table->dateTime('captured_at')->nullable(); + }); $schema->create('third_party_warehouse_authoritative_anchor_states', function (Blueprint $table): void { $table->string('dataset_key')->primary(); $table->string('status'); @@ -694,14 +733,41 @@ class ThirdPartyWarehouseStorageMirrorRepositoryTest extends TestCase 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([ + $subwarehouseValues = array_merge([ + 'qty' => $qty, + 'return_qty' => 0, + 'in_qty' => 0, + 'defective_qty' => 0, + 'customize_qty_1' => 0, + 'customize_qty_2' => 0, + 'customize_qty_3' => 0, + ], array_intersect_key($overrides, array_flip([ + 'qty', 'return_qty', 'in_qty', 'defective_qty', + 'customize_qty_1', 'customize_qty_2', 'customize_qty_3', + ]))); + $values = array_merge([ 'generation' => self::INVENTORY_GENERATION, 'id' => $id++, 'wms_co_id' => ThirdPartyWarehouseStorageMirrorRepository::WAREHOUSE_ID, 'sku_code' => $sku, 'qty' => $qty, 'captured_at' => $capturedAt, - ], $overrides)); + ], array_diff_key($overrides, $subwarehouseValues)); + DB::connection('business_mirror')->table('third_party_warehouse_inventory_snapshots')->insert($values); + foreach ([ + 1 => 'qty', 2 => 'return_qty', 3 => 'in_qty', 4 => 'defective_qty', + 6 => 'customize_qty_1', 7 => 'customize_qty_2', 8 => 'customize_qty_3', + ] as $warehouseId => $column) { + DB::connection('business_mirror')->table('third_party_warehouse_inventory_subwarehouse_snapshots')->insert([ + 'generation' => $values['generation'], + 'id' => $values['id'], + 'wms_co_id' => $values['wms_co_id'], + 'wh_id' => $warehouseId, + 'sku_code' => $values['sku_code'], + 'qty' => $subwarehouseValues[$column], + 'captured_at' => $values['captured_at'], + ]); + } } private function insertAuthoritativeAnchor(string $sku, float $qty, string $anchorAt): void @@ -757,6 +823,7 @@ class ThirdPartyWarehouseStorageMirrorRepositoryTest extends TestCase 'id' => $overrides['header_id'] ?? $this->nextHeaderId++, 'io_id' => $ioId, 'wms_co_id' => $overrides['warehouse_id'] ?? ThirdPartyWarehouseStorageMirrorRepository::WAREHOUSE_ID, + 'wh_id' => $overrides['wh_id'] ?? 1, 'status' => $overrides['status'] ?? 'Confirmed', 'io_type' => $type, 'occurred_at' => $occurredAt, diff --git a/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageSynchronizerTest.php b/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageSynchronizerTest.php index 70eddce5..bd09779e 100644 --- a/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageSynchronizerTest.php +++ b/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageSynchronizerTest.php @@ -144,6 +144,9 @@ class ThirdPartyWarehouseStorageSynchronizerTest extends TestCase 'wms_co_id' => 15045364, 'sku_code' => 'NEW-1', 'qty' => 12, + 'return_qty' => 3, + 'in_qty' => 2, + 'customize_qty_1' => 1, 'source_modified_at' => '2026-09-11T03:19:00.000', ], [ 'source_id' => 190, @@ -166,6 +169,18 @@ class ThirdPartyWarehouseStorageSynchronizerTest extends TestCase ->table('third_party_warehouse_inventory_snapshots') ->where('generation', $state->active_inventory_generation) ->orderBy('id')->pluck('sku_code')->all()); + $this->assertSame([ + ['wh_id' => 1, 'qty' => 12.0], + ['wh_id' => 2, 'qty' => 3.0], + ['wh_id' => 3, 'qty' => 2.0], + ['wh_id' => 6, 'qty' => 1.0], + ['wh_id' => 1, 'qty' => 4.0], + ], DB::connection('business_mirror') + ->table('third_party_warehouse_inventory_subwarehouse_snapshots') + ->where('generation', $state->active_inventory_generation) + ->orderBy('id')->orderBy('wh_id')->get(['wh_id', 'qty']) + ->map(fn (object $row): array => ['wh_id' => (int) $row->wh_id, 'qty' => (float) $row->qty]) + ->all()); $this->assertSame([[0, 200, 5]], $this->upstream->inventoryCalls); } @@ -565,6 +580,21 @@ class ThirdPartyWarehouseStorageSynchronizerTest extends TestCase $table->unique(['generation', 'id']); $table->unique(['generation', 'wms_co_id', 'sku_code']); }); + $schema->create('third_party_warehouse_inventory_subwarehouse_snapshots', function (Blueprint $table): void { + $table->id('_mirror_id'); + $table->string('generation'); + $table->string('source_key')->default('jiansun'); + $table->unsignedBigInteger('id'); + $table->unsignedBigInteger('wms_co_id'); + $table->unsignedSmallInteger('wh_id'); + $table->string('sku_code', 191); + $table->decimal('qty', 20, 4)->default(0); + $table->dateTime('source_modified_at')->nullable(); + $table->dateTime('captured_at')->nullable(); + $table->string('row_hash'); + $table->dateTime('synced_at'); + $table->unique(['generation', 'id', 'wh_id']); + }); } private function seedReadyMirror(): void diff --git a/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageUpstreamRepositoryTest.php b/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageUpstreamRepositoryTest.php index 74c583c6..b313931f 100644 --- a/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageUpstreamRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/ThirdPartyWarehouseStorageUpstreamRepositoryTest.php @@ -134,6 +134,9 @@ class ThirdPartyWarehouseStorageUpstreamRepositoryTest extends TestCase $this->assertStringNotContainsString('[wms_co_id] = ?', $query->selectCalls[0]['sql']); $this->assertStringContainsString('ORDER BY [Id]', $query->selectCalls[0]['sql']); $this->assertStringContainsString('AS source_modified_at', $query->selectCalls[0]['sql']); + foreach (['return_qty', 'in_qty', 'defective_qty', 'customize_qty_1', 'customize_qty_2', 'customize_qty_3'] as $column) { + $this->assertStringContainsString("[{$column}]", $query->selectCalls[0]['sql']); + } $this->assertSame([12350, 191818], $query->selectCalls[0]['bindings']); $repository->inventoryBatch(191818, 191818, 2000);