'交易单号', 'express_no' => '快递单号', 'supplier_name' => '供应商', 'normalized_barcode' => '处理后条形码', 'province' => '联系人省']; public const COLUMNS = ['state' => '扣款状态', 'trade_no' => '交易单号', 'express_no' => '快递单号', 'courier' => '快递名称', 'ship_time' => '发货时间', 'barcode' => '原始条形码', 'normalized_barcode' => '处理后条形码', 'product_name' => '商品名称', 'supplier_name' => '供应商', 'province' => '联系人省', 'freight' => '配件运费(元)', 'deduction' => '扣款金额(元)', 'reason' => '说明']; public function exportColumns(): array { $requested = request()->query('columns'); if ($requested === null) return self::COLUMNS; abort_unless(is_string($requested) && strlen($requested) <= 500, 422, '导出列无效。'); $keys = explode(',', $requested); abort_unless($keys !== [] && count($keys) === count(array_unique($keys)) && array_diff($keys, array_keys(self::COLUMNS)) === [], 422, '导出列无效。'); return array_replace(array_flip($keys), array_intersect_key(self::COLUMNS, array_flip($keys))); } public function __construct(private readonly SupplierAccessoryFreightRepository $repository) {} 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, '请选择有效业务月份。'); $state = request()->query('state', 'ready'); abort_unless(is_string($state) && in_array($state, ['all', ...array_keys(SupplierAccessoryFreightMatcher::STATES)], true), 422, '扣款状态无效。'); $filters = compact('month', 'state'); foreach (self::TEXT_FILTERS as $key => $label) { $value = request()->query($key, ''); abort_unless(is_string($value) && mb_strlen($value) <= 200, 422, '筛选条件格式无效。'); $filters[$key] = trim($value); } $filters['province'] = match ($filters['province']) { '山东省' => '山东', '河北省' => '河北', default => $filters['province'] }; return $filters; } public function rows(array $filters): \Generator { foreach ($this->repository->rows($filters['month']) as $row) { foreach (self::TEXT_FILTERS as $key => $label) { if (($filters[$key] ?? '') !== '' && mb_stripos((string) ($row[$key] ?? ''), $filters[$key]) === false) continue 2; } yield $row; } } public function page(): array { $filters = $this->filters(); $page = max(1, (int) request('page', 1)); $summary = ['all' => 0, 'ready' => 0, 'excluded' => 0, 'pending' => 0, 'amount' => 0]; $items = []; $total = 0; $error = null; try { foreach ($this->rows($filters) as $row) { $summary['all']++; $summary[$row['state']]++; $summary['amount'] += $row['deduction_cents']; if ($filters['state'] !== 'all' && $filters['state'] !== $row['state']) continue; if ($total >= ($page - 1) * 50 && count($items) < 50) $items[] = $row; $total++; } } catch (\Throwable $exception) { report($exception); $error = '发货、商品或供应商资料暂时无法读取,或尚未完成同步,请稍后重试。'; $summary = array_fill_keys(array_keys($summary), 0); $items = []; $total = 0; } return ['view' => 'business.pages.supplier-payables.accessory-freight-deductions', 'freightFilters' => $filters, 'freightSummary' => $summary, 'freightError' => $error, 'freightRows' => new LengthAwarePaginator($items, $total, 50, $page, ['path' => request()->url(), 'query' => request()->except('page')])]; } }