264 lines
10 KiB
PHP
264 lines
10 KiB
PHP
<?php
|
||
|
||
namespace Modules\EcommerceFinance\Repositories;
|
||
|
||
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
|
||
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
|
||
|
||
/**
|
||
* 京东钱包充值和消耗账单的只读上游适配器。
|
||
*
|
||
* 数据来源:JianSun.dbo.jd_spreadBills、JianSun.dbo.jd_costBills。
|
||
* 金额字段已按元存储,查询只返回京东表格需要的字段。
|
||
*/
|
||
class JdWalletBillRepository
|
||
{
|
||
private const RECHARGE_TABLE = '[dbo].[jd_spreadBills]';
|
||
private const CONSUMPTION_TABLE = '[dbo].[jd_costBills]';
|
||
private const SOURCE = 'jiansun';
|
||
|
||
private UpstreamReadOnlyQuery $query;
|
||
|
||
public function __construct(
|
||
UpstreamReadOnlyQueryFactory $factory,
|
||
private readonly ?MarketingAccountBillMirrorRepository $mirror = null,
|
||
) {
|
||
$this->query = $factory->forSource(self::SOURCE);
|
||
}
|
||
|
||
public function usesBusinessMirror(string $ledger): bool
|
||
{
|
||
$dataset = $ledger === 'consumption'
|
||
? MarketingAccountBillMirrorRepository::JD_CONSUMPTION
|
||
: MarketingAccountBillMirrorRepository::JD_RECHARGE;
|
||
|
||
return $this->mirror?->ready($dataset) ?? false;
|
||
}
|
||
|
||
public function usesBusinessMirrorForOptions(): bool
|
||
{
|
||
return $this->usesBusinessMirror('recharge') && $this->usesBusinessMirror('consumption');
|
||
}
|
||
|
||
/** @return string[] */
|
||
public function shopOptions(): array
|
||
{
|
||
if ($this->usesBusinessMirrorForOptions()) {
|
||
return $this->mirror->jdShopOptions();
|
||
}
|
||
|
||
$rows = $this->query->select(
|
||
"SELECT TOP (500) shopName FROM (SELECT shopName FROM " . self::RECHARGE_TABLE
|
||
. " UNION SELECT shopName FROM " . self::CONSUMPTION_TABLE
|
||
. ") shops WHERE shopName IS NOT NULL AND LTRIM(RTRIM(shopName)) <> '' ORDER BY shopName"
|
||
);
|
||
|
||
return array_values(array_map(
|
||
static fn (array $row): string => (string) ($row['shopName'] ?? ''),
|
||
$rows,
|
||
));
|
||
}
|
||
|
||
/** @return array{rows: array<int,array<string,mixed>>, total: int} */
|
||
public function paginateRecharge(
|
||
?string $shopName = null,
|
||
?string $fromDate = null,
|
||
?string $toDate = null,
|
||
int $page = 1,
|
||
int $pageSize = 25,
|
||
): array {
|
||
if ($this->usesBusinessMirror('recharge')) {
|
||
$result = $this->mirror->paginateJdRecharge(
|
||
$shopName,
|
||
$fromDate,
|
||
$toDate,
|
||
$page,
|
||
$pageSize,
|
||
);
|
||
|
||
return [
|
||
'rows' => array_map(fn (array $row): array => $this->normalizeRecharge($row), $result['rows']),
|
||
'total' => $result['total'],
|
||
];
|
||
}
|
||
|
||
return $this->paginate(self::RECHARGE_TABLE, $this->rechargeWhere($shopName, $fromDate, $toDate), $page, $pageSize, [
|
||
'id', 'shopName', 'amount', 'balanceAmount', 'orderTypeName', 'pk',
|
||
'createDate', 'updateTime', 'statusName', 'remark',
|
||
], 'createDate', 'id', fn (array $row): array => $this->normalizeRecharge($row));
|
||
}
|
||
|
||
/** @return array{rows: array<int,array<string,mixed>>, total: int} */
|
||
public function paginateConsumption(
|
||
?string $shopName = null,
|
||
?string $fromDate = null,
|
||
?string $toDate = null,
|
||
int $page = 1,
|
||
int $pageSize = 25,
|
||
): array {
|
||
if ($this->usesBusinessMirror('consumption')) {
|
||
$result = $this->mirror->paginateJdConsumption(
|
||
$shopName,
|
||
$fromDate,
|
||
$toDate,
|
||
$page,
|
||
$pageSize,
|
||
);
|
||
|
||
return [
|
||
'rows' => array_map(fn (array $row): array => $this->normalizeConsumption($row), $result['rows']),
|
||
'total' => $result['total'],
|
||
];
|
||
}
|
||
|
||
return $this->paginate(self::CONSUMPTION_TABLE, $this->consumptionWhere($shopName, $fromDate, $toDate), $page, $pageSize, [
|
||
'id', 'shopName', 'amount', 'balanceAmount', 'orderTypeName', 'creatTime', 'settleTime', 'pk',
|
||
'invoiceApplyTypeName',
|
||
], 'creatTime', 'id', fn (array $row): array => $this->normalizeConsumption($row));
|
||
}
|
||
|
||
/** @return array{amount_total: float, balance_total: float, count: int} */
|
||
public function summaryRecharge(?string $shopName = null, ?string $fromDate = null, ?string $toDate = null): array
|
||
{
|
||
if ($this->usesBusinessMirror('recharge')) {
|
||
return $this->normalizeSummary($this->mirror->summarizeJd(
|
||
MarketingAccountBillMirrorRepository::JD_RECHARGE,
|
||
'createDate',
|
||
$shopName,
|
||
$fromDate,
|
||
$toDate,
|
||
));
|
||
}
|
||
|
||
return $this->summary(self::RECHARGE_TABLE, $this->rechargeWhere($shopName, $fromDate, $toDate));
|
||
}
|
||
|
||
/** @return array{amount_total: float, balance_total: float, count: int} */
|
||
public function summaryConsumption(?string $shopName = null, ?string $fromDate = null, ?string $toDate = null): array
|
||
{
|
||
if ($this->usesBusinessMirror('consumption')) {
|
||
return $this->normalizeSummary($this->mirror->summarizeJd(
|
||
MarketingAccountBillMirrorRepository::JD_CONSUMPTION,
|
||
'creatTime',
|
||
$shopName,
|
||
$fromDate,
|
||
$toDate,
|
||
));
|
||
}
|
||
|
||
return $this->summary(self::CONSUMPTION_TABLE, $this->consumptionWhere($shopName, $fromDate, $toDate));
|
||
}
|
||
|
||
/** @param array{sql:string,bindings:array<int,mixed>} $where */
|
||
private function paginate(string $table, array $where, int $page, int $pageSize, array $fields, string $dateField, string $idField, callable $normalize): array
|
||
{
|
||
$page = max(1, $page);
|
||
$pageSize = min(max(1, $pageSize), 100);
|
||
$total = (int) $this->query->scalar('SELECT COUNT(*) FROM ' . $table . ' src' . $where['sql'], $where['bindings']);
|
||
|
||
if ($total === 0) {
|
||
return ['rows' => [], 'total' => 0];
|
||
}
|
||
|
||
$start = (($page - 1) * $pageSize) + 1;
|
||
$end = $page * $pageSize;
|
||
$select = implode(', ', array_map(static fn (string $field): string => 'src.' . $field, $fields));
|
||
$sql = 'SELECT * FROM ('
|
||
. 'SELECT ' . $select . ', ROW_NUMBER() OVER (ORDER BY src.' . $dateField . ' DESC, src.' . $idField . ' DESC) AS rn '
|
||
. 'FROM ' . $table . ' src' . $where['sql']
|
||
. ') AS t WHERE t.rn BETWEEN ? AND ? ORDER BY t.rn';
|
||
$rows = $this->query->select($sql, array_merge($where['bindings'], [$start, $end]));
|
||
|
||
return ['rows' => array_map($normalize, $rows), 'total' => $total];
|
||
}
|
||
|
||
/** @param array{sql:string,bindings:array<int,mixed>} $where */
|
||
private function summary(string $table, array $where): array
|
||
{
|
||
$row = $this->query->first(
|
||
'SELECT COALESCE(SUM(ABS(amount)), 0) AS amount_total, COALESCE(SUM(balanceAmount), 0) AS balance_total, COUNT(*) AS cnt FROM '
|
||
. $table . ' src' . $where['sql'],
|
||
$where['bindings'],
|
||
) ?? [];
|
||
|
||
return $this->normalizeSummary($row);
|
||
}
|
||
|
||
/** @return array{amount_total: float, balance_total: float, count: int} */
|
||
private function normalizeSummary(array $row): array
|
||
{
|
||
return [
|
||
'amount_total' => round((float) ($row['amount_total'] ?? 0), 2),
|
||
'balance_total' => round((float) ($row['balance_total'] ?? 0), 2),
|
||
'count' => (int) ($row['cnt'] ?? 0),
|
||
];
|
||
}
|
||
|
||
/** @return array{sql:string,bindings:array<int,mixed>} */
|
||
private function rechargeWhere(?string $shopName, ?string $fromDate, ?string $toDate): array
|
||
{
|
||
return $this->dateWhere('src.createDate', $shopName, $fromDate, $toDate);
|
||
}
|
||
|
||
/** @return array{sql:string,bindings:array<int,mixed>} */
|
||
private function consumptionWhere(?string $shopName, ?string $fromDate, ?string $toDate): array
|
||
{
|
||
return $this->dateWhere('src.creatTime', $shopName, $fromDate, $toDate);
|
||
}
|
||
|
||
/** @return array{sql:string,bindings:array<int,mixed>} */
|
||
private function dateWhere(string $dateField, ?string $shopName, ?string $fromDate, ?string $toDate): array
|
||
{
|
||
$conditions = [];
|
||
$bindings = [];
|
||
if ($shopName !== null && $shopName !== '') {
|
||
$conditions[] = 'src.shopName = ?';
|
||
$bindings[] = $shopName;
|
||
}
|
||
if ($fromDate !== null && preg_match('/^\d{4}-\d{2}-\d{2}$/', $fromDate) === 1) {
|
||
$conditions[] = $dateField . ' >= ?';
|
||
$bindings[] = $fromDate . ' 00:00:00';
|
||
}
|
||
if ($toDate !== null && preg_match('/^\d{4}-\d{2}-\d{2}$/', $toDate) === 1) {
|
||
$conditions[] = $dateField . ' < DATEADD(day, 1, CAST(? AS date))';
|
||
$bindings[] = $toDate;
|
||
}
|
||
|
||
return ['sql' => $conditions === [] ? '' : ' WHERE ' . implode(' AND ', $conditions), 'bindings' => $bindings];
|
||
}
|
||
|
||
private function normalizeRecharge(array $row): array
|
||
{
|
||
return [
|
||
'id' => (string) ($row['id'] ?? $row['pk'] ?? ''),
|
||
'store_name' => (string) ($row['shopName'] ?? ''),
|
||
'amount' => round((float) ($row['amount'] ?? 0), 2),
|
||
'balance' => round((float) ($row['balanceAmount'] ?? 0), 2),
|
||
'transaction_type' => (string) ($row['orderTypeName'] ?? ''),
|
||
'order_number' => (string) ($row['pk'] ?? ''),
|
||
'recharge_time' => (string) ($row['createDate'] ?? ''),
|
||
'arrival_time' => (string) ($row['updateTime'] ?? ''),
|
||
'serial_number' => (string) ($row['id'] ?? $row['pk'] ?? ''),
|
||
'status' => (string) ($row['statusName'] ?? ''),
|
||
'notes' => (string) ($row['remark'] ?? ''),
|
||
];
|
||
}
|
||
|
||
private function normalizeConsumption(array $row): array
|
||
{
|
||
$amount = abs((float) ($row['amount'] ?? 0));
|
||
|
||
return [
|
||
'id' => (string) ($row['id'] ?? $row['pk'] ?? ''),
|
||
'store_name' => (string) ($row['shopName'] ?? ''),
|
||
'amount' => round($amount, 2),
|
||
'balance' => round((float) ($row['balanceAmount'] ?? 0), 2),
|
||
'transaction_type' => (string) ($row['orderTypeName'] ?? ''),
|
||
'investment_date' => (string) ($row['creatTime'] ?? ''),
|
||
'settlement_date' => (string) ($row['settleTime'] ?? ''),
|
||
'serial_number' => (string) ($row['pk'] ?? $row['id'] ?? ''),
|
||
'notes' => (string) ($row['invoiceApplyTypeName'] ?? ''),
|
||
];
|
||
}
|
||
}
|