按七月期初滚动计算后续进销存月份
This commit is contained in:
parent
7e4a30365d
commit
053dcb4d6e
@ -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(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -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' => '子仓',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user