CaiWuXiTong/modules/EcommerceFinance/Repositories/JdWalletBillRepository.php
WuYongJie 28a62434d2 整合并修正京东钱包账单 (#26)
接入京东充值与消耗账单,修复快照冷启动、刷新依赖、日期边界与标签局部切换。
2026-08-11 12:22:41 +08:00

188 lines
8.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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)
{
$this->query = $factory->forSource(self::SOURCE);
}
/** @return string[] */
public function shopOptions(): array
{
$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 {
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 {
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
{
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
{
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 [
'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'] ?? ''),
];
}
}