*/ public function financeData(): array { $companyId = $this->companyId(); $mine = $this->store->listRows( PayablesFinalActions::DATASET_PREFIX . 'supplier:payment-reconciliation', $companyId, ); $months = []; foreach ($mine as $row) { $month = substr(trim((string) ($row['summary_month'] ?? '')), 0, 7); if ($month !== '') { $months[$month] = true; } } $months = array_keys($months); rsort($months); $month = trim((string) request()->query('month', '')); if ($month !== '' && preg_match('/^\d{4}-\d{2}$/', $month) === 1) { $mine = array_values(array_filter( $mine, static fn (array $row): bool => substr(trim((string) ($row['summary_month'] ?? '')), 0, 7) === $month, )); } else { $month = ''; } $reconcilers = []; foreach ($mine as $row) { $name = trim((string) ($row['reconciler'] ?? '')); if ($name !== '') { $reconcilers[$name] = true; } } $reconcilers = array_keys($reconcilers); sort($reconcilers); $reconciler = trim((string) request()->query('reconciler', '')); if ($reconciler !== '') { $mine = array_values(array_filter( $mine, static fn (array $row): bool => trim((string) ($row['reconciler'] ?? '')) === $reconciler, )); } $suppliers = []; foreach ($mine as $row) { $name = trim((string) ($row['supplier'] ?? '')); if ($name !== '') { $suppliers[$name] = true; } } foreach ($this->statements->uploadedSuppliers($companyId) as $name) { $suppliers[$name] = true; } $supplierNames = array_keys($suppliers); sort($supplierNames); $uploads = []; foreach ($supplierNames as $name) { $latest = $this->statements->uploads($name, $companyId, 1); $uploads[$name] = $latest[0] ?? null; } // 选中厂家时页面切换成「比对明细」视图(列表不再渲染,避免 100+ 家时往下翻)。 $selected = trim((string) request()->query('supplier', '')); $detail = null; if ($selected !== '' && in_array($selected, $supplierNames, true)) { $detail = $this->detail($mine, $selected, $companyId); } else { $selected = ''; } return [ 'view' => 'business.pages.supplier-reconciliation.finance-data', 'supplierNames' => $supplierNames, 'uploads' => $uploads, 'months' => $months, 'month' => $month, 'reconcilers' => $reconcilers, 'reconciler' => $reconciler, 'selectedSupplier' => $selected, 'detail' => $detail, 'listUrl' => route('business.pages.show', array_filter([ 'section' => 'supplier-reconciliation', 'page' => 'finance-data', 'month' => $month, 'reconciler' => $reconciler, ], static fn (mixed $value): bool => $value !== '' && $value !== null)), 'statementTemplateUrl' => route('business.supplier-reconciliation.template'), 'uploadUrl' => route('business.supplier-reconciliation.statement.upload'), ]; } /** @return array */ public function supplierData(): array { $companyId = $this->companyId(); $mine = $this->store->listRows( PayablesFinalActions::DATASET_PREFIX . 'supplier:payment-reconciliation', $companyId, ); $suppliers = []; foreach ($mine as $row) { $name = trim((string) ($row['supplier'] ?? '')); if ($name !== '') { $suppliers[$name] = true; } } foreach ($this->statements->uploadedSuppliers($companyId) as $name) { $suppliers[$name] = true; } $supplierNames = array_keys($suppliers); sort($supplierNames); $selected = trim((string) request()->query('supplier', '')); if ($selected === '' || ! in_array($selected, $supplierNames, true)) { $selected = $supplierNames[0] ?? ''; } $page = max((int) request()->query('rows_page', 1), 1); $statementRows = $selected !== '' ? $this->statements->rows($selected, $companyId) : []; $returnRows = $selected !== '' ? $this->returnRows($selected, $companyId) : []; $returnPanel = $selected !== '' ? $this->returnConfirmationPanel($selected, $returnRows, $companyId) : null; return [ 'view' => 'business.pages.supplier-reconciliation.supplier-data', 'supplierNames' => $supplierNames, 'selectedSupplier' => $selected, 'uploads' => $selected !== '' ? $this->statements->uploads($selected, $companyId) : [], 'statementRows' => array_slice($statementRows, ($page - 1) * self::PER_PAGE, self::PER_PAGE), 'statementTotal' => count($statementRows), 'returnPanel' => $returnPanel, 'rowsPage' => $page, 'perPage' => self::PER_PAGE, 'statementTemplateUrl' => route('business.supplier-reconciliation.template'), 'uploadUrl' => route('business.supplier-reconciliation.statement.upload'), 'returnConfirmUrl' => route('business.supplier-reconciliation.return.confirm'), 'returnDisputeUrl' => route('business.supplier-reconciliation.return.dispute'), 'returnConfirmAllUrl' => route('business.supplier-reconciliation.return.confirm-all'), ]; } /** * 退货扣减确认面板:共 N 行、合计扣减、异议暂不扣减;表格展示金额最高的 15 行。 * * @param list> $returnRows * @return array */ private function returnConfirmationPanel(string $supplier, array $returnRows, int $companyId): array { $statuses = $this->returnConfirmations->statuses($supplier, $companyId); $total = 0.0; $confirmedAmount = 0.0; $disputedAmount = 0.0; $disputedCount = 0; $confirmedCount = 0; $rows = []; foreach ($returnRows as $row) { $key = (string) ($row['_action_key'] ?? ''); $deduction = $this->returnDeduction($row); $status = $statuses[$key] ?? ''; $total += $deduction; if ($status === SupplierReturnConfirmation::STATUS_CONFIRMED) { $confirmedAmount += $deduction; $confirmedCount++; } elseif ($status === SupplierReturnConfirmation::STATUS_DISPUTED) { $disputedAmount += $deduction; $disputedCount++; } $rows[] = ['_key' => $key, 'status' => $status, 'deduction' => $deduction] + $row; } // 全部退货行按导入顺序分页展示(每页 15 行),不做金额排序。 $perPage = self::PER_PAGE; $pages = max((int) ceil(count($rows) / $perPage), 1); $page = min(max((int) request()->query('return_page', 1), 1), $pages); return [ 'supplier' => $supplier, 'rows' => array_slice($rows, ($page - 1) * $perPage, $perPage), 'page' => $page, 'pages' => $pages, 'per_page' => $perPage, 'total_rows' => count($returnRows), 'total_amount' => round($total, 2), 'confirmed_count' => $confirmedCount, 'confirmed_amount' => round($confirmedAmount, 2), 'disputed_count' => $disputedCount, 'disputed_amount' => round($disputedAmount, 2), ]; } /** 扣减金额:金额为空时用预估金额(与采购方记账口径一致)。 */ private function returnDeduction(array $row): float { $amount = trim((string) ($row['amount'] ?? '')); if ($amount !== '' && is_numeric(str_replace(',', '', $amount))) { return (float) str_replace(',', '', $amount); } $guess = trim((string) ($row['guess_amount'] ?? '')); return is_numeric(str_replace(',', '', $guess)) ? (float) str_replace(',', '', $guess) : 0.0; } /** @return array */ private function detail(array $mine, string $supplier, int $companyId): array { $mineRows = array_values(array_filter( $mine, static fn (array $row): bool => trim((string) ($row['supplier'] ?? '')) === $supplier, )); $theirRows = $this->statements->rows($supplier, $companyId); $compared = $this->matcher->compare($mineRows, $theirRows); $compared['mineRows'] = count($mineRows); $compared['theirsRows'] = count($theirRows); return $compared; } /** @return list> 该厂家的退货明细(我方数据,展示给厂家确认扣减) */ public function returnRows(string $supplier, int $companyId): array { $rows = $this->store->listRows( PayablesFinalActions::DATASET_PREFIX . 'supplier:return-reconciliation', $companyId, ); return array_values(array_filter( $rows, static fn (array $row): bool => trim((string) ($row['supplier'] ?? '')) === $supplier, )); } private function companyId(): int { return (int) (company_id() ?: 1); } }