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); } }