CaiWuXiTong/modules/EcommerceFinance/Repositories/BillOffsetMirrorRepository.php

201 lines
7.8 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Schema;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use RuntimeException;
/** 账单冲减只读共享业务镜像。 */
class BillOffsetMirrorRepository
{
public const DATASET = 'operations_replica.rpt.jst_originalBillInfo';
public function __construct(
private readonly BusinessMirrorDatasetRegistry $datasets,
private readonly GenericBusinessMirrorRepository $mirror,
) {
}
/**
* @return array{data:list<array<string,mixed>>,total:int,current_page:int,last_page:int,per_page:int,summary:array{amount:float,count:int},sync:array<string,mixed>}
*/
public function paginate(array $filters, int $page, int $perPage): array
{
$dataset = $this->datasets->get(self::DATASET);
if (! $this->mirror->ready($dataset)) {
throw new RuntimeException('bill_offset_mirror_not_ready');
}
$page = max($page, 1);
$perPage = min(max($perPage, 1), 100);
$query = $this->query();
$this->applyBillOffsetScope($query);
$this->applyFilters($query, $filters);
$total = (clone $query)->count('b.Id');
$summary = (clone $query)->selectRaw(
'COALESCE(SUM(COALESCE(b.amount, 0)), 0) AS amount, COUNT(*) AS aggregate_count'
)->first();
$rows = $query
->select($this->columns())
->orderByDesc('b.fee_time')
->orderByDesc('b.Id')
->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' => [
'amount' => round((float) ($summary->amount ?? 0), 2),
'count' => (int) ($summary->aggregate_count ?? 0),
],
'sync' => [
'status' => $state['status'] ?? 'unknown',
'last_succeeded_at' => $state['last_succeeded_at'] ?? null,
],
];
}
private function query(): Builder
{
$connection = $this->mirror->connection();
$query = $connection->table('ops_jst_original_bill_info as b');
$ordersAvailable = $this->ordersAvailable();
if ($ordersAvailable) {
$query->leftJoin('ops_jst_orders as o', function ($join): void {
$join->on('o.so_id', '=', 'b.so_id');
});
}
if ($ordersAvailable && $this->orderItemsAvailable()) {
$query->leftJoin('ops_jst_order_item as i', function ($join): void {
$join->on('i.o_id', '=', 'o.o_id')
->whereColumn('i.sku_id', 'b.sku_id');
});
}
return $query;
}
private function hasTable(string $table): bool
{
return Schema::connection($this->mirror->connection()->getName())->hasTable($table);
}
private function ordersAvailable(): bool
{
return $this->hasTable('ops_jst_orders');
}
private function orderItemsAvailable(): bool
{
return $this->hasTable('ops_jst_order_item');
}
private function applyFilters(Builder $query, array $filters): void
{
if (($filters['from'] ?? '') !== '') {
$query->where('b.fee_time', '>=', $filters['from'] . ' 00:00:00');
}
if (($filters['to'] ?? '') !== '') {
$query->where('b.fee_time', '<', date('Y-m-d 00:00:00', strtotime($filters['to'] . ' +1 day')));
}
if (($filters['order_no'] ?? '') !== '') {
$needle = $this->contains((string) $filters['order_no']);
$query->where(function (Builder $order) use ($needle): void {
$order->whereRaw("CAST(b.so_id AS CHAR) LIKE ? ESCAPE '!'", [$needle])
->orWhereRaw("CAST(b.p_auto_id AS CHAR) LIKE ? ESCAPE '!'", [$needle]);
if ($this->ordersAvailable()) {
$order->orWhereRaw("CAST(o.o_id AS CHAR) LIKE ? ESCAPE '!'", [$needle]);
}
});
}
if (($filters['shop_name'] ?? '') !== '') {
if ($this->ordersAvailable()) {
$query->whereRaw("o.shop_name LIKE ? ESCAPE '!'", [$this->contains((string) $filters['shop_name'])]);
} else {
$query->whereRaw('1 = 0');
}
}
if (($filters['sku_id'] ?? '') !== '') {
$needle = $this->contains((string) $filters['sku_id']);
$query->where(function (Builder $sku) use ($needle): void {
foreach (['b.sku_id', 'b.shop_sku_id', 'b.shop_i_id'] as $column) {
$sku->orWhereRaw($column . " LIKE ? ESCAPE '!'", [$needle]);
}
if ($this->ordersAvailable() && $this->orderItemsAvailable()) {
$sku->orWhereRaw("i.i_id LIKE ? ESCAPE '!'", [$needle]);
}
});
}
}
private function applyBillOffsetScope(Builder $query): void
{
// 原始账单镜像供多个业务复用,这个页面只呈现 500112 冲减收入。
// 兼容上游历史上仅保存名称、不带科目编号的“冲减收入”写法。
$query->where(function (Builder $offset): void {
$offset->whereRaw("b.fee_name LIKE ? ESCAPE '!'", ['%500112%'])
->orWhereRaw("b.fee_name LIKE ? ESCAPE '!'", ['%冲减收入%']);
});
}
/** @return list<string> */
private function columns(): array
{
$columns = [
'b.Id as record_id', 'b.fee_time', 'b.fee_name', 'b.inout_type', 'b.amount',
'b.so_id as bill_order_no', 'b.sku_id as bill_product_code',
'b.shop_sku_id as bill_shop_product_code', 'b.shop_i_id as bill_shop_style_code',
];
if ($this->ordersAvailable()) {
$columns = [...$columns, 'o.o_id as internal_order_no', 'o.so_id as online_order_no', 'o.shop_name', 'o.shop_id as shop_code'];
}
if ($this->ordersAvailable() && $this->orderItemsAvailable()) {
$columns = [...$columns, 'i.sku_id as product_code', 'i.i_id as style_code', 'i.shop_sku_id as shop_product_code', 'i.shop_i_id as shop_style_code'];
}
return $columns;
}
/** @return array<string,mixed> */
private function mapRow(object $row): array
{
return [
'record_id' => (string) ($row->record_id ?? ''),
'fee_time' => $this->dateTime($row->fee_time ?? null),
'fee_name' => $row->fee_name ?? null,
'inout_type' => $row->inout_type ?? null,
'internal_order_no' => $row->internal_order_no ?? $row->bill_order_no ?? null,
'online_order_no' => $row->online_order_no ?? $row->bill_order_no ?? null,
'shop_name' => $row->shop_name ?? null,
'shop_code' => $row->shop_code ?? null,
'product_code' => $row->product_code ?? $row->bill_product_code ?? null,
'style_code' => $row->style_code ?? null,
'shop_product_code' => $row->shop_product_code ?? $row->bill_shop_product_code ?? null,
'shop_style_code' => $row->shop_style_code ?? $row->bill_shop_style_code ?? null,
'amount' => round((float) ($row->amount ?? 0), 2),
];
}
private function dateTime(mixed $value): string
{
return $value === null || $value === '' ? '' : date('Y-m-d H:i:s', strtotime((string) $value));
}
private function contains(string $value): string
{
return '%' . str_replace(['!', '%', '_'], ['!!', '!%', '!_'], $value) . '%';
}
}