authorizeStoreReceivingBills(); if ($request->boolean('export')) { return $this->exportResponse($request, $repository); } $validated = $request->validate($this->filterRules() + [ 'sort' => ['nullable', 'in:occurred_at,income,expense,record_code'], 'dir' => ['nullable', 'in:asc,desc'], 'page' => ['nullable', 'integer', 'min:1'], 'limit' => ['nullable', 'integer', 'min:1', 'max:100'], ]); $filters = collect($validated)->only(array_keys($this->filterRules()))->all(); $filters = $this->withLedgerRemarkFilter($filters, 'dewu'); try { $data = $repository->paginateAccountBills( $filters, (string) ($validated['sort'] ?? 'occurred_at'), (string) ($validated['dir'] ?? 'desc'), (int) ($validated['page'] ?? 1), (int) ($validated['limit'] ?? 10), ); } catch (RuntimeException $exception) { if ($exception->getMessage() !== 'dewu_authoritative_bills_not_ready') { throw $exception; } return response()->json([ 'message' => '得物完整账单尚未完成首次导入,当前不会回退到不完整的聚水潭辅助表。', 'data' => [], 'total' => 0, 'current_page' => 1, 'last_page' => 1, 'per_page' => (int) ($validated['limit'] ?? 10), 'summary' => ['income' => 0, 'expense' => 0, 'balance' => 0, 'count' => 0], 'columns' => AccountBillColumns::for('dewu'), ], 503); } $data['columns'] = AccountBillColumns::for('dewu'); return response()->json($this->withLedgerRemarks($data, 'dewu')); } public function filterOptions(DewuSettlementBillRepository $repository) { $this->authorizeStoreReceivingBills(); $ledgerRemarks = $this->ledgerRemarkMap()->remarksForTab('dewu'); try { return response()->json($repository->accountBillFilterOptions() + ['ledger_remarks' => $ledgerRemarks]); } catch (RuntimeException $exception) { if ($exception->getMessage() !== 'dewu_authoritative_bills_not_ready') { throw $exception; } return response()->json([ 'message' => '得物完整账单尚未完成首次导入。', 'shops' => [], 'bill_items' => [], 'ledger_remarks' => $ledgerRemarks, ], 503); } } public function export(Request $request, DewuSettlementBillRepository $repository) { $this->authorizeStoreReceivingBills(); return $this->exportResponse($request, $repository); } private function exportResponse(Request $request, DewuSettlementBillRepository $repository) { $validated = $request->validate($this->filterRules() + [ 'sort' => ['nullable', 'in:occurred_at,income,expense,record_code'], 'dir' => ['nullable', 'in:asc,desc'], ]); $filters = collect($validated)->only(array_keys($this->filterRules()))->all(); $filters = $this->withLedgerRemarkFilter($filters, 'dewu'); $definition = AccountBillColumns::for('dewu'); $available = collect($definition['columns'] ?? [])->keyBy('field'); $requested = $request->query('columns', []); if (is_string($requested)) { $requested = array_filter(array_map('trim', explode(',', $requested))); } if (! is_array($requested) || count($requested) > 40) { $requested = []; } $requested = collect($requested) ->filter(static fn (mixed $value): bool => is_scalar($value)) ->map(static fn (mixed $value): string => mb_substr(trim((string) $value), 0, 128)) ->filter() ->unique(); $columns = $requested->isEmpty() ? $available->values() : $requested->map(fn (string $field) => $available->get($field))->filter()->values(); abort_if($columns->isEmpty(), 422, '至少选择一个可导出的字段。'); try { $rows = $repository->exportAccountBills( $filters, (string) ($validated['sort'] ?? 'occurred_at'), (string) ($validated['dir'] ?? 'asc'), ); } catch (RuntimeException $exception) { if ($exception->getMessage() !== 'dewu_authoritative_bills_not_ready') { throw $exception; } return response()->json(['message' => '得物完整账单尚未完成首次导入。'], 503); } $rows = $this->ledgerRemarkRows($rows, 'dewu'); return response()->streamDownload(function () use ($rows, $columns): void { $csv = fopen('php://output', 'w'); fputs($csv, "\xEF\xBB\xBF"); fputcsv($csv, $columns->pluck('label')->all()); foreach ($rows as $row) { fputcsv($csv, $columns->map( fn (array $column): string => $this->exportValue($row, $column) )->all()); } fclose($csv); }, 'dewu_account_bills.csv', [ 'Content-Type' => 'text/csv; charset=UTF-8', 'Cache-Control' => 'private, no-store', ]); } /** @return array> */ private function filterRules(): array { return [ 'store_name' => ['nullable', 'string', 'max:255'], 'ledger_remark' => ['nullable', 'string', 'max:191'], 'bill_item' => ['nullable', 'string', 'max:255'], 'start' => ['nullable', 'date_format:Y-m-d'], 'end' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:start'], 'keyword' => ['nullable', 'string', 'max:255'], ]; } /** @param array $row */ private function exportValue(array $row, array $column): string { $field = (string) ($column['field'] ?? ''); $value = str_starts_with($field, 'payload.') ? data_get($row['payload'] ?? [], substr($field, 8)) : data_get($row, $field); $text = (string) ($value ?? ''); if (preg_match('/^\d{12,}$/', $text) === 1) { return '="' . $text . '"'; } return preg_match('/^[=+\-@]/u', $text) === 1 ? "'" . $text : $text; } private function authorizeStoreReceivingBills(): void { $section = collect(SidebarPermissions::navigationFor(user())) ->firstWhere('slug', 'billing-transactions'); $page = collect($section['pages'] ?? []) ->firstWhere('slug', 'store-receiving-bills'); abort_if(empty($page), 403, '当前账号没有店铺收款账户账单权限。'); } }