124 lines
6.4 KiB
PHP
124 lines
6.4 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', 'ready');
|
|
$filters = ['month' => $month, 'match_status' => in_array($status, ['ready', 'matched', 'unmatched', 'all'], true) ? $status : 'ready'];
|
|
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 (!self::inCategory($row, $filters['match_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')]),
|
|
];
|
|
}
|
|
|
|
public static function inCategory(array $row, string $category): bool
|
|
{
|
|
return $category === 'all' || ($category === 'ready'
|
|
? $row['deduction_state'] === 'ready' && $row['deduction_cents'] > 0
|
|
: $row['status'] === $category);
|
|
}
|
|
}
|