201 lines
6.2 KiB
PHP
201 lines
6.2 KiB
PHP
<?php
|
|
|
|
namespace Modules\EcommerceFinance\Repositories;
|
|
|
|
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
|
|
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
|
|
|
|
class ThirdPartyWarehouseStorageUpstreamRepository
|
|
{
|
|
public const SOURCE_KEY = 'jiansun';
|
|
|
|
/** 三方仓页面仍固定读取该仓;共享流水镜像覆盖全部仓库。 */
|
|
public const WAREHOUSE_ID = 15045364;
|
|
|
|
public const START_DATE = '2026-08-01';
|
|
|
|
private ?UpstreamReadOnlyQuery $query = null;
|
|
|
|
public function __construct(private readonly UpstreamReadOnlyQueryFactory $queries)
|
|
{
|
|
}
|
|
|
|
public function headerHighWatermark(): int
|
|
{
|
|
return (int) $this->query()->scalar(
|
|
'SELECT COALESCE(MAX([Id]), 0) FROM [dbo].[jst_inoutWater]',
|
|
);
|
|
}
|
|
|
|
/** @return list<array<string,mixed>> */
|
|
public function headerRange(int $afterId, int $throughId): array
|
|
{
|
|
if ($throughId <= $afterId) {
|
|
return [];
|
|
}
|
|
|
|
return $this->query()->select(<<<'SQL'
|
|
SELECT
|
|
h.[Id] AS source_id,
|
|
h.[io_id],
|
|
h.[wms_co_id],
|
|
h.[wh_id],
|
|
h.[status],
|
|
h.[type] AS io_type,
|
|
CONVERT(varchar(27), h.[io_date], 126) AS occurred_at,
|
|
CONVERT(varchar(27), h.[modified], 126) AS modified_at
|
|
FROM [dbo].[jst_inoutWater] h
|
|
WHERE h.[Id] > ?
|
|
AND h.[Id] <= ?
|
|
AND h.[io_date] >= ?
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM [dbo].[jst_inoutWater] newer
|
|
WHERE newer.[io_id] = h.[io_id] AND newer.[Id] > h.[Id]
|
|
)
|
|
ORDER BY h.[Id]
|
|
SQL, [$afterId, $throughId, self::START_DATE]);
|
|
}
|
|
|
|
/** @return list<array<string,mixed>> */
|
|
public function modifiedHeadersBatch(
|
|
string $cursorAt,
|
|
int $cursorId,
|
|
string $throughAt,
|
|
int $limit,
|
|
): array {
|
|
$limit = min(max($limit, 1), 5000);
|
|
|
|
return $this->query()->select('SELECT TOP (' . $limit . ') '
|
|
. <<<'SQL'
|
|
[Id] AS source_id,
|
|
[io_id],
|
|
[wms_co_id],
|
|
[wh_id],
|
|
[status],
|
|
[type] AS io_type,
|
|
CONVERT(varchar(27), [io_date], 126) AS occurred_at,
|
|
CONVERT(varchar(27), [modified], 126) AS modified_at
|
|
FROM [dbo].[jst_inoutWater]
|
|
WHERE [modified] >= ?
|
|
AND [modified] < ?
|
|
AND ([modified] > ? OR ([modified] = ? AND [Id] > ?))
|
|
ORDER BY [modified], [Id]
|
|
SQL, [$cursorAt, $throughAt, $cursorAt, $cursorAt, $cursorId]);
|
|
}
|
|
|
|
/**
|
|
* Reload the current version for every changed document before replacing
|
|
* its mirror rows. A document can have more than one historical header Id.
|
|
*
|
|
* @param list<int|string> $ioIds
|
|
* @return list<array<string,mixed>>
|
|
*/
|
|
public function latestHeadersForIoIds(array $ioIds, string $throughAt): array
|
|
{
|
|
$ioIds = array_values(array_unique(array_filter(
|
|
array_map(static fn (mixed $id): int => (int) $id, $ioIds),
|
|
static fn (int $id): bool => $id > 0,
|
|
)));
|
|
if ($ioIds === []) {
|
|
return [];
|
|
}
|
|
|
|
$rows = [];
|
|
foreach (array_chunk($ioIds, 500) as $chunk) {
|
|
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
|
|
$batch = $this->query()->select(
|
|
'SELECT h.[Id] AS source_id, h.[io_id], h.[wms_co_id], h.[wh_id], h.[status], '
|
|
. 'h.[type] AS io_type, CONVERT(varchar(27), h.[io_date], 126) AS occurred_at, '
|
|
. 'CONVERT(varchar(27), h.[modified], 126) AS modified_at '
|
|
. 'FROM [dbo].[jst_inoutWater] h '
|
|
. 'INNER JOIN (SELECT [io_id], MAX([Id]) AS latest_id '
|
|
. 'FROM [dbo].[jst_inoutWater] '
|
|
. "WHERE [io_id] IN ({$placeholders}) AND [modified] < ? GROUP BY [io_id]) latest "
|
|
. 'ON latest.[latest_id] = h.[Id] '
|
|
. 'WHERE h.[io_date] >= ? '
|
|
. 'ORDER BY h.[Id]',
|
|
[...$chunk, $throughAt, self::START_DATE],
|
|
);
|
|
array_push($rows, ...$batch);
|
|
}
|
|
|
|
usort($rows, static fn (array $left, array $right): int =>
|
|
((int) ($left['source_id'] ?? 0)) <=> ((int) ($right['source_id'] ?? 0))
|
|
);
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* The upstream item table has an index on io_id. Fetching items only after
|
|
* the warehouse headers have been selected avoids scanning unrelated rows.
|
|
*
|
|
* @param list<int|string> $ioIds
|
|
* @return list<array<string,mixed>>
|
|
*/
|
|
public function itemsForIoIds(array $ioIds): array
|
|
{
|
|
$ioIds = array_values(array_unique(array_filter(
|
|
array_map(static fn (mixed $id): int => (int) $id, $ioIds),
|
|
static fn (int $id): bool => $id > 0,
|
|
)));
|
|
|
|
if ($ioIds === []) {
|
|
return [];
|
|
}
|
|
|
|
$rows = [];
|
|
foreach (array_chunk($ioIds, 500) as $chunk) {
|
|
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
|
|
$batch = $this->query()->select(
|
|
'SELECT [Id] AS source_id, [io_id], [sku_id] AS sku_code, [qty], [qty_type] '
|
|
. 'FROM [dbo].[jst_inoutWaterItem] '
|
|
. "WHERE [io_id] IN ({$placeholders}) ORDER BY [Id]",
|
|
$chunk,
|
|
);
|
|
array_push($rows, ...$batch);
|
|
}
|
|
|
|
usort($rows, static fn (array $left, array $right): int =>
|
|
((int) ($left['source_id'] ?? 0)) <=> ((int) ($right['source_id'] ?? 0))
|
|
);
|
|
|
|
return $rows;
|
|
}
|
|
|
|
public function inventoryHighWatermark(): int
|
|
{
|
|
return (int) $this->query()->scalar(
|
|
'SELECT COALESCE(MAX([Id]), 0) FROM [dbo].[jst_inventoryInfo]',
|
|
);
|
|
}
|
|
|
|
/** @return list<array<string,mixed>> */
|
|
public function inventoryBatch(int $afterId, int $highWatermark, int $limit): array
|
|
{
|
|
if ($highWatermark <= $afterId) {
|
|
return [];
|
|
}
|
|
|
|
$limit = min(max($limit, 1), 5000);
|
|
|
|
return $this->query()->select('SELECT TOP (' . $limit . ') '
|
|
. <<<'SQL'
|
|
[Id] AS source_id,
|
|
[wms_co_id],
|
|
[sku_id] AS sku_code,
|
|
[qty],
|
|
CONVERT(varchar(27), [modified], 126) AS source_modified_at
|
|
FROM [dbo].[jst_inventoryInfo]
|
|
WHERE [Id] > ?
|
|
AND [Id] <= ?
|
|
ORDER BY [Id]
|
|
SQL, [$afterId, $highWatermark]);
|
|
}
|
|
|
|
private function query(): UpstreamReadOnlyQuery
|
|
{
|
|
return $this->query ??= $this->queries->forSource(self::SOURCE_KEY);
|
|
}
|
|
}
|