CaiWuXiTong/modules/EcommerceFinance/Repositories/ProductPriceRepository.php

146 lines
6.0 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
/**
* Completed product price-limit approvals from the operations replica.
*/
class ProductPriceRepository
{
private const SOURCE = 'operations_replica';
private const TABLE = '[rpt].[Bulk_Price_Limit]';
private readonly UpstreamReadOnlyQuery $query;
public function __construct(UpstreamReadOnlyQueryFactory $queries)
{
$this->query = $queries->forSource(self::SOURCE);
}
/** @return array{rows:array<int,array<string,mixed>>} */
public function paginate(array $filters, int $page, int $limit): array
{
$page = max($page, 1);
$limit = min(max($limit, 1), 100);
$where = [];
$bindings = [];
if (($filters['barcode'] ?? '') !== '') {
$where[] = "[product_code] LIKE ? ESCAPE '\\'";
$bindings[] = $this->contains((string) $filters['barcode']);
}
if (($filters['completed_from'] ?? '') !== '') {
$where[] = '[process_end_date] >= CAST(? AS date)';
$bindings[] = (string) $filters['completed_from'];
}
if (($filters['completed_to'] ?? '') !== '') {
$where[] = '[process_end_date] < DATEADD(day, 1, CAST(? AS date))';
$bindings[] = (string) $filters['completed_to'];
}
$whereSql = $where === [] ? '' : ' WHERE ' . implode(' AND ', $where);
$offset = ($page - 1) * $limit;
$fetch = $limit + 1;
$sql = 'SELECT '
. '[product_code] AS barcode, '
. '[spu] AS spu, '
. '[product_name] AS product_name, '
. '[category] AS category, '
. '[production_status] AS production_status, '
. '[min_price] AS minimum_price, '
. '[price_limit] AS price_limit, '
. '[purchase_price] AS purchase_price, '
. '[shipping_fee] AS shipping_fee, '
. '[jingxi_limit_price] AS jingxi_limit_price, '
. "CASE [status] WHEN 'COMPLETED' THEN N'已完成' ELSE NULLIF([status], '') END AS approval_status, "
. "CASE [result] WHEN 'agree' THEN N'已同意' WHEN 'refuse' THEN N'已拒绝' ELSE NULLIF([result], '') END AS approval_result, "
. '[title] AS workflow_title, '
. 'CONVERT(varchar(19), [process_end_date], 120) AS completed_at '
. 'FROM ' . self::TABLE
. $whereSql
. ' ORDER BY [process_end_date] DESC, [businessId] DESC, [product_code] ASC'
. " OFFSET {$offset} ROWS FETCH NEXT {$fetch} ROWS ONLY";
return ['rows' => array_map([$this, 'normalize'], $this->query->select($sql, $bindings))];
}
/**
* 按稳定游标分批读取已完成的上游记录,只供后台镜像同步使用。
*
* @param array{completed_at:string,business_id:string,barcode:string}|null $cursor
* @return array<int,array<string,mixed>>
*/
public function mirrorBatch(?array $cursor, string $since, string $until, int $limit): array
{
$limit = min(max($limit, 1), 2000);
$bindings = [$since, $until];
$cursorSql = '';
if ($cursor !== null) {
$cursorSql = ' AND ('
. '[process_end_date] > CAST(? AS datetime2(7))'
. " OR ([process_end_date] = CAST(? AS datetime2(7)) AND COALESCE([businessId], '') > ?)"
. " OR ([process_end_date] = CAST(? AS datetime2(7)) AND COALESCE([businessId], '') = ?"
. " AND COALESCE([product_code], '') > ?)"
. ')';
$bindings = array_merge($bindings, [
$cursor['completed_at'],
$cursor['completed_at'],
$cursor['business_id'],
$cursor['completed_at'],
$cursor['business_id'],
$cursor['barcode'],
]);
}
$sql = "SELECT TOP {$limit} "
. "COALESCE([businessId], '') AS business_id, "
. "COALESCE([product_code], '') AS barcode, "
. '[spu] AS spu, '
. '[product_name] AS product_name, '
. '[category] AS category, '
. '[production_status] AS production_status, '
. '[min_price] AS minimum_price, '
. '[price_limit] AS price_limit, '
. '[purchase_price] AS purchase_price, '
. '[shipping_fee] AS shipping_fee, '
. '[jingxi_limit_price] AS jingxi_limit_price, '
. "CASE [status] WHEN 'COMPLETED' THEN N'已完成' ELSE NULLIF([status], '') END AS approval_status, "
. "CASE [result] WHEN 'agree' THEN N'已同意' WHEN 'refuse' THEN N'已拒绝' ELSE NULLIF([result], '') END AS approval_result, "
. '[title] AS workflow_title, '
. 'CONVERT(varchar(33), [process_end_date], 126) AS cursor_completed_at, '
. 'CONVERT(varchar(26), [process_end_date], 121) AS completed_at '
. 'FROM ' . self::TABLE
. ' WHERE [process_end_date] IS NOT NULL'
. ' AND [process_end_date] >= CAST(? AS datetime2(7))'
. ' AND [process_end_date] < CAST(? AS datetime2(7))'
. $cursorSql
. " ORDER BY [process_end_date] ASC, COALESCE([businessId], '') ASC, COALESCE([product_code], '') ASC";
return array_map([$this, 'normalize'], $this->query->select($sql, $bindings));
}
private function contains(string $value): string
{
return '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], trim($value)) . '%';
}
/** @param array<string,mixed> $row */
private function normalize(array $row): array
{
foreach (['minimum_price', 'price_limit', 'purchase_price', 'shipping_fee', 'jingxi_limit_price'] as $field) {
$row[$field] = $row[$field] === null || $row[$field] === ''
? null
: (float) $row[$field];
}
return $row;
}
}