CaiWuXiTong/modules/EcommerceFinance/View/AccessoryReshipPageData.php

117 lines
6.2 KiB
PHP

<?php
namespace Modules\EcommerceFinance\View;
use Illuminate\Pagination\LengthAwarePaginator;
use Modules\EcommerceFinance\Repositories\ShippingOrderRepository;
use Modules\EcommerceFinance\Services\AccessoryReshipMatcher;
use Modules\EcommerceFinance\Services\AccessoryReshipCourierStore;
use Modules\EcommerceFinance\Services\AccessoryReshipRateCalculator;
use Modules\EcommerceFinance\Services\AccessoryReshipSelectionStore;
class AccessoryReshipPageData
{
public function __construct(
private readonly ShippingOrderRepository $shipping,
private readonly AccessoryReshipMatcher $matcher,
private readonly AccessoryReshipCourierStore $configs,
private readonly AccessoryReshipRateCalculator $calculator,
private readonly ?AccessoryReshipSelectionStore $selections = null,
) {}
public function filters(): array
{
$month = request()->query('month', now()->format('Y-m'));
abort_unless(is_string($month) && preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $month) && (int) substr($month, 0, 4) >= 2000, 422, '请选择有效业务月份。');
$status = request()->query('match_status', 'matched');
$filters = ['month' => $month, 'match_status' => in_array($status, ['matched', 'unmatched', 'all'], true) ? $status : 'matched'];
foreach (['trade_no','express_no','courier','ship_date','barcode','normalized_barcode','freight','deduction_state','original_courier','original_express_no','original_month','deduction_amount'] as $field) {
$value = request()->query($field, '');
abort_unless(is_string($value) && mb_strlen($value) <= 200, 422, '筛选条件格式无效。');
$filters[$field] = trim($value);
}
return $filters;
}
public function rows(array $filters): \Generator
{
$configs = $this->configs->activeRates((int) company_id());
$selections = ($this->selections ?? app(AccessoryReshipSelectionStore::class))->forMonth((int) company_id(), $filters['month']);
$start = \Carbon\Carbon::createFromFormat('!Y-m', $filters['month']);
foreach ($this->shipping->accessoryReshipBatches($start->format('Y-m-d'), $start->copy()->addMonth()->format('Y-m-d'), $start->copy()->subMonths(2)->format('Y-m-d')) as $batch) {
foreach ($this->matcher->match($batch['accessories'], $batch['originals']) as $row) {
$row['selection_key'] = AccessoryReshipSelectionStore::key($row);
$row = $this->calculator->apply($row, $configs, $selections[$row['selection_key']] ?? null);
if ($this->matchesFilters($row, $filters)) yield $row;
}
}
}
public function matchesFilters(array $row, array $filters): bool
{
$values = [
'trade_no' => $row['trade_no'], 'express_no' => ($row['express_no'] ?? ''),
'courier' => ($row['express_name1'] ?? '') ?: ($row['express_name2'] ?? ''),
'ship_date' => substr((string) ($row['ship_time'] ?? ''), 0, 10),
'barcode' => $row['barcode'], 'normalized_barcode' => $row['normalized_barcode'],
'freight' => $row['freight_cents'] === null ? '待核实' : number_format($row['freight_cents'] / 100, 2, '.', ''),
'deduction_amount' => $row['deduction_cents'] === null ? '待核实' : number_format($row['deduction_cents'] / 100, 2, '.', ''),
];
foreach ($values as $field => $value) {
if (($filters[$field] ?? '') !== '' && mb_stripos((string) $value, $filters[$field]) === false) return false;
}
if (($filters['deduction_state'] ?? '') !== '' && $filters['deduction_state'] !== $row['deduction_state']) return false;
$originalFilters = array_filter(['courier' => $filters['original_courier'] ?? '', 'express_no' => $filters['original_express_no'] ?? '', 'month' => $filters['original_month'] ?? ''], static fn ($value) => $value !== '');
if (!$originalFilters) return true;
foreach ($row['matches'] as $match) {
foreach ($originalFilters as $field => $value) {
if (mb_stripos((string) $match[$field], $value) === false) continue 2;
}
return true;
}
return false;
}
public function page(): array
{
if (request('reship_view') === 'config') {
return app(AccessoryReshipConfigPageData::class)->page();
}
$filters = $this->filters();
$page = max(1, (int) request('page', 1));
$summary = ['all' => 0, 'matched' => 0, 'unmatched' => 0, 'amount' => 0, 'invalid_amount' => 0, 'ambiguous' => 0, 'ready' => 0, 'missing_config' => 0, 'selection_required' => 0, 'zero_rate' => 0];
$items = [];
$total = 0;
$error = null;
try {
foreach ($this->rows($filters) as $row) {
$summary['all']++;
$summary[$row['status']]++;
if ($row['status'] === 'matched') {
$summary['amount'] += $row['deduction_cents'] ?? 0;
if ($row['freight_cents'] === null) $summary['invalid_amount']++;
if ($row['deduction_state'] === 'selection_required') $summary['ambiguous']++;
if (isset($summary[$row['deduction_state']])) $summary[$row['deduction_state']]++;
}
if ($filters['match_status'] !== 'all' && $filters['match_status'] !== $row['status']) continue;
if ($total >= ($page - 1) * 50 && count($items) < 50) $items[] = $row;
$total++;
}
} catch (\Throwable $exception) {
report($exception);
$error = '发货数据或扣款配置暂时无法读取,请检查连接及配置数据后重试。';
$items = [];
$total = 0;
$summary = array_fill_keys(array_keys($summary), 0);
}
return [
'view' => 'business.pages.courier-payables.accessory-reship-deductions',
'reshipView' => 'details',
'reshipFilters' => $filters,
'reshipSummary' => $summary,
'reshipError' => $error,
'reshipRows' => new LengthAwarePaginator($items, $total, 50, $page, ['path' => request()->url(), 'query' => request()->except('page')]),
];
}
}