'工单编号', 'online_order_no' => '线上订单号', 'shop_name' => '店铺名称', 'task_remark' => '任务备注', 'seller_remark' => '卖家备注', 'express_no' => '快递单号', ]; public const COLUMNS = [ 'loss_result' => '损耗判定', 'deduction_amount' => '扣款金额', 'loss_basis' => '判定说明', 'deduction_detail' => '扣款计算(条码×数量×成本价+运费)', 'completed_at' => '报销完成时间', 'work_order_no' => '工单编号', 'platform' => '平台', 'shop_name' => '店铺名称', 'online_order_no' => '线上订单号', 'task_remark' => '任务备注', 'seller_remark' => '卖家备注', 'actual_paid_amount' => '实付金额', 'task_payment' => '任务付款金额', 'reimbursement_amount' => '最终报销金额', 'shipped_at' => '发货时间', 'express_no' => '快递单号', 'warehouse_name' => '发货仓库', 'shipped_barcode' => '发货条码', 'shipped_product_name' => '发货商品名称', 'shipped_qty' => '发货数量', 'shipped_amount' => '发货金额', 'shipped_freight' => '发货运费', 'initiator' => '发起人', 'payee_name' => '信誉账号名称', 'product_code' => '报销商品编码', 'product_name' => '报销商品名称', 'internal_order_no' => '内部单号', 'erp_order_status' => 'ERP订单状态', ]; public const MONEY_COLUMNS = ['deduction_amount', 'actual_paid_amount', 'task_payment', 'reimbursement_amount', 'shipped_amount', 'shipped_freight']; private const SUM_KEYS = ['deduction_amount', 'task_payment', 'reimbursement_amount', 'shipped_amount', 'shipped_freight']; public function __construct( private readonly Calculator $calculator, private readonly Repository $repository, ) {} /** @return array */ public function filters(): array { $filters = $this->baseFilters(); $result = request()->query('result', Calculator::RESULT_DEDUCT); abort_unless(is_string($result) && ($result === 'all' || in_array($result, Calculator::RESULTS, true)), 422, '损耗判定无效。'); $filters['result'] = $result; return $filters; } /** * @param array $filters * @param list>|null $rows * @return \Generator> */ public function rows(array $filters, ?array $rows = null): \Generator { foreach ($rows ?? $this->calculator->rows($filters['month']) as $row) { if ($filters['result'] !== 'all' && $row['loss_result'] !== $filters['result']) { continue; } if ($this->matchesText($row, $filters)) { yield $row; } } } public function page(): array { $filters = $this->filters(); $page = max(1, (int) request('page', 1)); $sync = $this->repository->syncStatus(); $shippingSync = $this->calculator->shippingStatus(); $empty = ['count' => 0, 'cost_missing' => 0] + array_fill_keys(self::SUM_KEYS, 0); $summary = ['total' => 0, 'matched' => 0, 'filtered' => $empty] + array_fill_keys(Calculator::RESULTS, $empty); $items = []; $error = $this->syncError($sync); if ($error === null) { try { $all = $this->calculator->rows($filters['month']); foreach ($all as $row) { $summary['total']++; $summary['matched'] += ($row['shipped_at'] ?? '') !== '' ? 1 : 0; $this->add($summary[$row['loss_result']], $row); } foreach ($this->rows($filters, $all) as $row) { if ($summary['filtered']['count'] >= ($page - 1) * self::PER_PAGE && count($items) < self::PER_PAGE) { $items[] = $row; } $this->add($summary['filtered'], $row); } } catch (\Throwable $exception) { report($exception); $error = '种菜损耗暂时无法计算(工单镜像或发货数据读取失败),请稍后重试。'; $summary = ['total' => 0, 'matched' => 0, 'filtered' => $empty] + array_fill_keys(Calculator::RESULTS, $empty); $items = []; } } return [ 'view' => 'business.pages.billing-transactions.planting-loss', 'plantingFilters' => $filters, 'plantingWindow' => Repository::window($filters['month']), 'plantingShippingWindow' => Calculator::shippingWindow($filters['month']), 'plantingMonths' => Repository::months(), 'plantingSummary' => $summary, 'plantingSync' => $sync, 'plantingShippingSync' => $shippingSync, 'plantingError' => $error, 'plantingRows' => new LengthAwarePaginator($items, $summary['filtered']['count'], self::PER_PAGE, $page, [ 'path' => request()->url(), 'query' => request()->except('page'), ]), ]; } /** @param array $bucket @param array $row */ private function add(array &$bucket, array $row): void { $bucket['count']++; $bucket['cost_missing'] += ! empty($row['cost_missing']) ? 1 : 0; foreach (self::SUM_KEYS as $key) { $bucket[$key] += self::cents($row[$key] ?? null); } } }