优化全仓进销存分页查询 #217

Merged
chenxi merged 1 commits from fix/chenxi-inventory-report-performance into main 2026-09-20 16:12:08 +08:00

View File

@ -260,28 +260,88 @@ class ThirdPartyWarehouseStorageMirrorRepository
$anchorAt = $this->stateAnchor($state);
$rangeStart = min($period['start'], $anchorAt);
$rangeEnd = max($period['end'], $anchorAt);
$movementPairs = $this->confirmedMovements($headerGeneration, $itemGeneration, null)
->where('h.occurred_at', '>=', $rangeStart)
->where('h.occurred_at', '<', $rangeEnd)
->select(['h.wms_co_id', 'i.sku_code']);
$latestProducts = $this->connection->table(self::PRODUCT_TABLE)
->where('sku_type', 'normal')->whereNotNull('sku_id')->where('sku_id', '!=', '')
->groupBy('sku_id')->select('sku_id')->selectRaw('MAX(autoid) AS autoid');
$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)
->where('generation', $inventoryGeneration)
->whereNotNull('sku_code')->where('sku_code', '!=', '')
->select(['wms_co_id', 'sku_code'])
->union($movementPairs);
$anchors = $this->connection->query()->fromSub($inventoryPairs, 'pairs')
->select(['wms_co_id', 'sku_code']);
$candidates = $this->connection->query()->fromSub($inventoryPairs, 'pairs')
->select(['pairs.wms_co_id', 'pairs.sku_code']);
$this->containsFilter($candidates, 'pairs.sku_code', $filters['product_code'] ?? '');
$warehouseFilter = trim((string) ($filters['warehouse_provider'] ?? ''));
if ($warehouseFilter !== '') {
$warehouseIds = $this->connection->query()->fromSub(
clone $latestWarehouses,
'filter_latest_warehouse',
)->join(self::WAREHOUSE_TABLE . ' as filter_warehouse', function ($join): void {
$join->on('filter_warehouse.wms_co_id', '=', 'filter_latest_warehouse.wms_co_id')
->on('filter_warehouse.Id', '=', 'filter_latest_warehouse.Id');
});
$this->containsFilter($warehouseIds, 'filter_warehouse.name', $warehouseFilter);
$candidates->whereIn('pairs.wms_co_id', $warehouseIds->select('filter_warehouse.wms_co_id'));
}
$categoryFilter = trim((string) ($filters['category'] ?? ''));
if ($categoryFilter !== '') {
$categorySkus = $this->connection->table(self::PRODUCT_TABLE)
->where('sku_type', 'normal')
->whereNotNull('sku_id')->where('sku_id', '!=', '')
->select('sku_id')->distinct();
$this->containsFilter($categorySkus, 'category', $categoryFilter);
$candidates->whereIn('pairs.sku_code', $categorySkus);
}
$total = (clone $candidates)->count();
$pagePairs = (clone $candidates)
->orderBy('pairs.wms_co_id')->orderBy('pairs.sku_code')
->forPage($page, $perPage);
$pairRows = (clone $pagePairs)->get();
if ($pairRows->isEmpty()) {
return [
'data' => [],
'total' => $total,
'state' => $state,
'sync' => $this->syncStatus($state),
];
}
$dimensionedPagePairs = $this->connection->query()->fromSub($pagePairs, 'pairs')
->leftJoinSub($latestProducts, 'latest_product', 'latest_product.sku_id', '=', 'pairs.sku_code')
->leftJoin(self::PRODUCT_TABLE . ' as s', function ($join): void {
$join->on('s.sku_id', '=', 'latest_product.sku_id')
->on('s.autoid', '=', 'latest_product.autoid');
})
->leftJoinSub($latestWarehouses, 'latest_warehouse', 'latest_warehouse.wms_co_id', '=', 'pairs.wms_co_id')
->leftJoin(self::WAREHOUSE_TABLE . ' as w', function ($join): void {
$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']);
$anchors = $this->connection->query()->fromSub($dimensionedPagePairs, 'pairs')
->leftJoin(self::INVENTORY_TABLE . ' as inventory', function ($join) use ($inventoryGeneration): void {
$join->on('inventory.wms_co_id', '=', 'pairs.wms_co_id')
->on('inventory.sku_code', '=', 'pairs.sku_code')
->where('inventory.generation', '=', $inventoryGeneration);
})
->select(['pairs.wms_co_id', 'pairs.sku_code'])
->select(['pairs.wms_co_id', 'pairs.sku_code', 'pairs.category', 'pairs.warehouse_name'])
->selectRaw('COALESCE(inventory.qty, 0) AS snapshot_qty')
->selectRaw('COALESCE(inventory.captured_at, ?) AS snapshot_at', [$anchorAt]);
$movements = $this->confirmedMovements($headerGeneration, $itemGeneration, null)
->where('h.occurred_at', '>=', $rangeStart)
->where('h.occurred_at', '<', $rangeEnd)
->where(function (Builder $pairs) use ($pairRows): void {
foreach ($pairRows as $pair) {
$pairs->orWhere(function (Builder $candidate) use ($pair): void {
$candidate->where('h.wms_co_id', (int) $pair->wms_co_id)
->where('i.sku_code', (string) $pair->sku_code);
});
}
})
->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',
@ -301,8 +361,11 @@ class ThirdPartyWarehouseStorageMirrorRepository
$join->on('mv.wms_co_id', '=', 'a.wms_co_id')
->on('mv.sku_code', '=', 'a.sku_code');
})
->groupBy(['a.wms_co_id', 'a.sku_code', 'a.snapshot_qty', 'a.snapshot_at'])
->select(['a.wms_co_id', 'a.sku_code'])
->groupBy([
'a.wms_co_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'])
->selectRaw($openingSql . ' AS opening_qty', $openingBindings)
->selectRaw($closingSql . ' AS closing_qty', $closingBindings);
foreach (self::MOVEMENT_COLUMNS as $column => $type) {
@ -314,37 +377,8 @@ class ThirdPartyWarehouseStorageMirrorRepository
}
$monthly->selectRaw('0 AS opening_aux');
$latestProducts = $this->connection->table(self::PRODUCT_TABLE)
->where('sku_type', 'normal')->whereNotNull('sku_id')->where('sku_id', '!=', '')
->groupBy('sku_id')->select('sku_id')->selectRaw('MAX(autoid) AS autoid');
$latestWarehouses = $this->connection->table(self::WAREHOUSE_TABLE)
->whereNotNull('wms_co_id')->groupBy('wms_co_id')
->select('wms_co_id')->selectRaw('MAX(Id) AS Id');
$query = $this->connection->query()->fromSub($monthly, 'm')
->leftJoinSub($latestProducts, 'latest_product', 'latest_product.sku_id', '=', 'm.sku_code')
->leftJoin(self::PRODUCT_TABLE . ' as s', function ($join): void {
$join->on('s.sku_id', '=', 'latest_product.sku_id')
->on('s.autoid', '=', 'latest_product.autoid');
})
->leftJoinSub($latestWarehouses, 'latest_warehouse', 'latest_warehouse.wms_co_id', '=', 'm.wms_co_id')
->leftJoin(self::WAREHOUSE_TABLE . ' as w', function ($join): void {
$join->on('w.wms_co_id', '=', 'latest_warehouse.wms_co_id')
->on('w.Id', '=', 'latest_warehouse.Id');
})
->select(['m.*', 's.category', 'w.name as warehouse_name']);
$this->containsFilter($query, 'm.sku_code', $filters['product_code'] ?? '');
$this->containsFilter($query, 's.category', $filters['category'] ?? '');
$this->containsFilter($query, 'w.name', $filters['warehouse_provider'] ?? '');
$nonZero = ['opening_qty', 'closing_qty', ...array_keys(self::MOVEMENT_COLUMNS)];
$query->where(function (Builder $values) use ($nonZero): void {
foreach ($nonZero as $index => $column) {
$method = $index === 0 ? 'where' : 'orWhere';
$values->{$method}('m.' . $column, '<>', 0);
}
});
$total = (clone $query)->count();
$rows = $query->orderBy('w.name')->orderBy('m.sku_code')->forPage($page, $perPage)->get()
$rows = $this->connection->query()->fromSub($monthly, 'm')
->select('m.*')->orderBy('m.warehouse_name')->orderBy('m.sku_code')->get()
->map(static function (object $row) use ($period): object {
$warehouse = trim((string) ($row->warehouse_name ?? ''));