CaiWuXiTong/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php

157 lines
6.4 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Illuminate\Database\Query\Builder;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use RuntimeException;
/** 店铺收款账单·淘系支付宝账户只读共享业务镜像。 */
class StoreReceivingAlipayBillMirrorRepository
{
public const DATASET = 'jiansun.dbo.alipay_Bill_Detail';
public function __construct(
private readonly BusinessMirrorDatasetRegistry $datasets,
private readonly GenericBusinessMirrorRepository $mirror,
) {}
/** @return array{data:array<int,array<string,mixed>>,total:int,current_page:int,last_page:int,per_page:int,summary:array{income:float,expense:float,balance:float,count:int},sync:array<string,mixed>} */
public function paginate(array $filters, string $sort, string $direction, int $page, int $perPage): array
{
$dataset = $this->datasets->get(self::DATASET);
if (! $this->mirror->ready($dataset)) {
throw new RuntimeException('store_receiving_alipay_bill_mirror_not_ready');
}
$page = max($page, 1);
$perPage = min(max($perPage, 1), 100);
$direction = $direction === 'asc' ? 'asc' : 'desc';
$sortColumn = [
'record_code' => 'Id',
'occurred_at' => 'happenTime',
'income' => 'inAmount',
'expense' => 'outAmount',
'balance' => 'accountAmount',
][$sort] ?? 'happenTime';
$query = $this->mirror->connection()->table($dataset->targetTable());
$this->applyFilters($query, $filters);
$total = (clone $query)->count();
$summaryRow = (clone $query)->selectRaw(
'COALESCE(SUM(CASE WHEN inAmount > 0 THEN inAmount ELSE 0 END), 0) AS income, '
. 'COALESCE(SUM(ABS(COALESCE(outAmount, 0))), 0) AS expense, COUNT(*) AS aggregate_count'
)->first();
$latestBalance = (clone $query)
->orderByDesc('happenTime')
->orderByDesc('Id')
->value('accountAmount');
$rows = $query
->orderBy($sortColumn, $direction)
->orderBy('Id', $direction)
->forPage($page, $perPage)
->get()
->map(fn (object $row): array => $this->mapRow($row))
->all();
$state = $this->mirror->state($dataset) ?? [];
return [
'data' => $rows,
'total' => $total,
'current_page' => $page,
'last_page' => max((int) ceil($total / $perPage), 1),
'per_page' => $perPage,
'summary' => [
'income' => (float) ($summaryRow->income ?? 0),
'expense' => (float) ($summaryRow->expense ?? 0),
'balance' => (float) ($latestBalance ?? 0),
'count' => (int) ($summaryRow->aggregate_count ?? 0),
],
'sync' => [
'status' => $state['status'] ?? 'unknown',
'last_succeeded_at' => $state['last_succeeded_at'] ?? null,
],
];
}
private function applyFilters(Builder $query, array $filters): void
{
if (($filters['store_name'] ?? '') !== '') {
$query->where('shopName', $filters['store_name']);
}
if (($filters['alipay_account'] ?? '') !== '') {
$value = '%' . $this->escapeLike((string) $filters['alipay_account']) . '%';
$query->where(function (Builder $query) use ($value): void {
$query->where('appId', 'like', $value)
->orWhere('account', 'like', $value);
});
}
if (($filters['bill_item'] ?? '') !== '') {
$query->where('tradeChannel', $filters['bill_item']);
}
if (($filters['business_type'] ?? '') !== '') {
$query->where('businessType', $filters['business_type']);
}
if (($filters['start'] ?? '') !== '') {
$query->where('happenTime', '>=', $filters['start'] . ' 00:00:00');
}
if (($filters['end'] ?? '') !== '') {
$query->where('happenTime', '<', date('Y-m-d', strtotime($filters['end'] . ' +1 day')) . ' 00:00:00');
}
if (($filters['keyword'] ?? '') !== '') {
$value = '%' . $this->escapeLike((string) $filters['keyword']) . '%';
$query->where(function (Builder $query) use ($value): void {
$query->where('commodityName', 'like', $value)
->orWhere('accountNum', 'like', $value)
->orWhere('businessNum', 'like', $value)
->orWhere('merchantOrder', 'like', $value)
->orWhere('storeNum', 'like', $value)
->orWhere('remark', 'like', $value);
});
}
}
private function mapRow(object $row): array
{
return [
'id' => (string) $row->Id,
'record_code' => (string) $row->Id,
'store_name' => $row->shopName,
'custom_remark' => $row->buyerRemark,
'bill_item' => $row->tradeChannel,
'alipay_account' => $row->appId ?: $row->account,
'account_order_no' => $row->accountNum,
'business_serial_no' => $row->businessNum,
'merchant_order_no' => $row->merchantOrder,
'business_base_order_no' => $row->busiBasicNum,
'goods_name' => $row->commodityName,
'counterparty_account' => $row->oppAccount,
'occurred_at' => $this->dateTime($row->happenTime),
'income' => max((float) ($row->inAmount ?? 0), 0),
'expense' => abs((float) ($row->outAmount ?? 0)),
'balance' => (float) ($row->accountAmount ?? 0),
'business_type' => $row->businessType,
'remark' => $row->remark,
'online_order_no' => $row->storeNum,
'online_real_store' => $row->shopName,
'business_description' => $row->busiDescription,
'business_bill_source' => $row->busiBillSource,
'business_order_no' => $row->busiNum,
'created_at' => '',
'updated_at' => '',
'modifier' => '',
];
}
private function dateTime(mixed $value): string
{
return $value === null || $value === '' ? '' : date('Y-m-d H:i:s', strtotime((string) $value));
}
private function escapeLike(string $value): string
{
return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value);
}
}