按七月期初滚动计算后续进销存月份

This commit is contained in:
chenxi 2026-09-23 13:57:25 +08:00
parent 7e4a30365d
commit 053dcb4d6e
2 changed files with 60 additions and 19 deletions

View File

@ -638,9 +638,9 @@ class ThirdPartyWarehouseStorageMirrorRepository
}
/**
* The month immediately after an authoritative report starts from that report's closing
* quantity. Current inventory is deliberately ignored because it cannot reproduce the
* historical opening balance of each subwarehouse.
* Every later month starts from the latest authoritative month closing quantity and
* rolls confirmed movements forward to the requested month. Current inventory is
* deliberately ignored because it cannot reproduce historical opening balances.
*
* @param array{start:string,end:string} $period
* @param array<string,mixed> $filters
@ -765,13 +765,15 @@ class ThirdPartyWarehouseStorageMirrorRepository
];
}
$authorityEnd = Carbon::parse($authority['source_month'])
->addMonth()->startOfMonth()->format('Y-m-d 00:00:00');
$movements = $this->confirmedMovements($headerGeneration, $itemGeneration, null)
->where('h.occurred_at', '>=', $period['start'])
->where('h.occurred_at', '>=', $authorityEnd)
->where('h.occurred_at', '<', $period['end'])
->whereIn('h.wms_co_id', $pairRows->pluck('wms_co_id')->filter()->unique()->values()->all())
->whereIn('i.sku_code', $pairRows->pluck('sku_code')->unique()->values()->all())
->select([
'h.wms_co_id', 'h.io_type', 'i.sku_code',
'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))';
@ -780,16 +782,22 @@ class ThirdPartyWarehouseStorageMirrorRepository
$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([
})->groupBy([
'a.owner', 'a.warehouse_provider', 'a.source_warehouse', 'a.wms_co_id',
'a.wh_id', 'a.sku_code', 'a.category', 'a.opening_qty',
])->select([
'a.owner', 'a.warehouse_provider', 'a.source_warehouse', 'a.wms_co_id',
'a.wh_id', 'a.sku_code', 'a.category', 'a.opening_qty',
]);
])->select([
'a.owner', 'a.warehouse_provider', 'a.source_warehouse', 'a.wms_co_id',
'a.wh_id', 'a.sku_code', 'a.category', 'a.opening_qty',
]);
$monthly->selectRaw(
'a.opening_qty + COALESCE(SUM(CASE WHEN mv.occurred_at >= ? AND mv.occurred_at < ? '
. 'THEN ' . $signed . ' ELSE 0 END), 0) AS rolled_opening_qty',
[$authorityEnd, $period['start']],
);
foreach (self::MOVEMENT_COLUMNS as $column => $type) {
$expression = 'COALESCE(SUM(CASE WHEN mv.io_type = ? THEN ' . $signed . ' ELSE 0 END), 0)';
$monthly->selectRaw($expression . ' AS ' . $column, [$type]);
$expression = 'COALESCE(SUM(CASE WHEN mv.occurred_at >= ? AND mv.occurred_at < ? '
. 'AND mv.io_type = ? THEN ' . $signed . ' ELSE 0 END), 0)';
$monthly->selectRaw($expression . ' AS ' . $column, [$period['start'], $period['end'], $type]);
}
$rows = $monthly->orderBy('a.warehouse_provider')->orderBy('a.wh_id')->orderBy('a.sku_code')
@ -801,7 +809,7 @@ class ThirdPartyWarehouseStorageMirrorRepository
trim((string) $row->warehouse_provider),
(int) $row->wh_id,
);
$closingQuantity = (float) $row->opening_qty;
$closingQuantity = (float) $row->rolled_opening_qty;
foreach (array_keys(self::MOVEMENT_COLUMNS) as $column) {
$closingQuantity += (float) $row->{$column};
}
@ -813,7 +821,7 @@ class ThirdPartyWarehouseStorageMirrorRepository
'warehouse' => $warehouse,
'product_code' => $row->sku_code,
'category' => $row->category,
'opening_quantity' => $row->opening_qty,
'opening_quantity' => $row->rolled_opening_qty,
'closing_quantity' => $closingQuantity,
'processing_in' => $row->processing_in,
'processing_out' => $row->processing_out,
@ -847,11 +855,11 @@ class ThirdPartyWarehouseStorageMirrorRepository
/** @param array{start:string,end:string} $period */
private function previousMonthlyInventoryAuthority(array $period): ?array
{
$previousMonth = Carbon::parse($period['start'])->subMonth()->startOfMonth()->toDateString();
$state = $this->connection->table(self::INVENTORY_REPORT_AUTHORITATIVE_STATE_TABLE)
->where('dataset_key', self::INVENTORY_REPORT_AUTHORITATIVE_DATASET_KEY)
->whereNotNull('active_generation')
->whereDate('source_month', $previousMonth)
->whereDate('source_month', '<', Carbon::parse($period['start'])->startOfMonth())
->orderByDesc('source_month')
->first();
if ($state === null || trim((string) $state->active_generation) === '') {
return null;
@ -859,7 +867,7 @@ class ThirdPartyWarehouseStorageMirrorRepository
return [
'generation' => (string) $state->active_generation,
'source_month' => $previousMonth,
'source_month' => Carbon::parse($state->source_month)->startOfMonth()->toDateString(),
];
}

View File

@ -239,6 +239,37 @@ class ThirdPartyWarehouseStorageMirrorRepositoryTest extends TestCase
$this->assertSame(5.0, (float) $rows['简尚-崔桥工厂仓主仓']->transfer_in);
}
public function test_inventory_report_rolls_multiple_months_from_the_july_authoritative_baseline(): void
{
$this->markReady();
$this->insertProduct('SKU-ROLL-MULTI');
$this->insertInventory('SKU-ROLL-MULTI', 999, '2026-09-11 03:21:00');
$this->insertInventoryReportAuthoritativeRow([
'source_month' => '2026-07-01',
'report_month' => '2026-07-01',
'warehouse_provider' => '三方-南昌樵舍仓',
'warehouse' => '三方-南昌樵舍仓主仓',
'product_code' => 'SKU-ROLL-MULTI',
'opening_quantity' => 10,
'closing_quantity' => 12,
]);
$this->insertMovement('SKU-ROLL-MULTI', '采购进仓', 3, 1, '2026-08-02 10:00:00');
$this->insertMovement('SKU-ROLL-MULTI', '销售出仓', 2, -1, '2026-09-03 10:00:00');
$result = $this->repository->paginateMonthlyInventoryReport([
'month' => '2026-09',
'product_code' => 'SKU-ROLL-MULTI',
'warehouse_provider' => '',
'category' => '',
], 1, 25);
$row = $result['data'][0];
$this->assertSame(1, $result['total']);
$this->assertSame(15.0, (float) $row->opening_quantity);
$this->assertSame(13.0, (float) $row->closing_quantity);
$this->assertSame(-2.0, (float) $row->sales_out);
}
public function test_inventory_report_product_code_filter_is_exact(): void
{
$this->markReady();
@ -971,16 +1002,18 @@ class ThirdPartyWarehouseStorageMirrorRepositoryTest extends TestCase
private function insertInventoryReportAuthoritativeRow(array $overrides = []): void
{
$generation = '55555555-5555-5555-5555-555555555555';
$sourceMonth = (string) ($overrides['source_month'] ?? '2026-08-01');
unset($overrides['source_month']);
DB::connection('business_mirror')->table('inventory_report_authoritative_states')->insert([
'dataset_key' => 'jiansun.inventory-report.authoritative-month',
'status' => 'succeeded',
'active_generation' => $generation,
'source_month' => '2026-08-01',
'source_month' => $sourceMonth,
]);
DB::connection('business_mirror')->table('inventory_report_authoritative_rows')->insert(array_merge([
'generation' => $generation,
'row_key' => hash('sha256', 'authoritative-row'),
'report_month' => '2026-08-01',
'report_month' => $sourceMonth,
'owner' => '常州简尚家居有限公司',
'warehouse_provider' => '仓储方',
'warehouse' => '子仓',