:<页面>),初始为空,由页面上的导入、导出、查询、编辑维护。 */ class PayablesFinalActions extends Controller { public const DATASET_PREFIX = 'final:'; public function __construct( private readonly PayablesFinalRegistry $registry, private readonly SupplierPayableImportTemplate $template, private readonly SupplierPayableOverrideStore $overrides, private readonly PayablesFinalImporter $importer, private readonly PayablesFieldNormalizer $normalizer, ) { $this->middleware('auth'); $this->middleware('company.identify'); } public function definition(Request $request): JsonResponse { return response()->json([ 'dataset' => $this->dataset($request), 'fields' => collect($this->fields($request)) ->map(static fn (array $definition, string $name): array => ['name' => $name] + $definition) ->values() ->all(), ]); } public function template(Request $request): StreamedResponse { return $this->template->download( $this->registry->title($this->section($request), $this->dataset($request)), $this->fields($request), true, ); } public function import(Request $request): JsonResponse|RedirectResponse { $request->validate(['import' => ['required', 'file', 'mimes:xlsx,xls,csv', 'max:10240']]); $result = $this->importer->import( $this->fields($request), $this->storeDataset($request), $request->file('import'), (int) (company_id() ?: 1), user_id() ? (int) user_id() : null, ); $message = "导入完成:新增 {$result['created']} 条,更新 {$result['updated']} 条。"; return $request->expectsJson() ? response()->json(['message' => $message] + $result) : back()->with('success', $message); } /** 大文件导入第 1 步:上传文件并建立行缓存(前端随后按片拉取处理)。 */ public function importUpload(Request $request, PayablesFinalImportRunner $runner): JsonResponse { $request->validate(['import' => ['required', 'file', 'mimes:xlsx,xls,csv', 'max:51200']]); $prepared = $runner->prepare( $request->file('import'), $this->fields($request), $this->storeDataset($request), (int) (company_id() ?: 1), user_id() ? (int) user_id() : null, ); return response()->json($prepared + ['offset' => 0, 'created' => 0, 'updated' => 0, 'done' => false]); } /** 大文件导入第 2 步起:每次处理一片,前端拿到进度后继续。 */ public function importChunk(Request $request, PayablesFinalImportRunner $runner): JsonResponse { $data = $request->validate([ 'token' => ['required', 'string', 'regex:/^[a-f0-9]{32}$/'], 'offset' => ['required', 'integer', 'min:0'], ]); return response()->json($runner->process( $data['token'], (int) $data['offset'], null, (int) (company_id() ?: 1), )); } public function update(Request $request, string $recordKey): JsonResponse|RedirectResponse { $values = $this->normalizer->normalize($this->fields($request), (array) $request->input('values', [])); $result = $this->overrides->save( $this->storeDataset($request), $recordKey, $values, (int) (company_id() ?: 1), user_id() ? (int) user_id() : null, ); return $request->expectsJson() ? response()->json(['message' => '保存成功。'] + $result) : back()->with('success', '保存成功。'); } public function export(Request $request): StreamedResponse { $fields = $this->fields($request); $title = $this->registry->title($this->section($request), $this->dataset($request)); $rows = $this->overrides->listRows($this->storeDataset($request), (int) (company_id() ?: 1)); return response()->streamDownload(function () use ($rows, $fields): void { $csv = fopen('php://output', 'w'); if ($csv === false) { throw new RuntimeException('无法创建导出文件。'); } fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF)); fputcsv($csv, array_merge(['记录标识'], array_column($fields, 'label'))); foreach ($rows as $row) { $values = [$this->csvText($row['_action_key'] ?? '')]; foreach ($fields as $field => $definition) { $value = $row[$field] ?? ''; $values[] = $definition['type'] === 'number' && is_numeric($value) ? $value : $this->csvText($value); } fputcsv($csv, $values); } fclose($csv); }, $title . '_' . now()->format('Ymd_His') . '.csv', [ 'Content-Type' => 'text/csv; charset=UTF-8', 'Cache-Control' => 'private, no-store', ]); } private function section(Request $request): string { return (string) $request->route('finalSection'); } private function dataset(Request $request): string { $dataset = (string) $request->route('dataset'); $this->registry->title($this->section($request), $dataset); return $dataset; } /** @return array */ private function fields(Request $request): array { return $this->registry->fields($this->section($request), $this->dataset($request)); } private function storeDataset(Request $request): string { $section = $this->section($request) === PayablesFinalRegistry::SECTION_COURIER ? 'courier' : 'supplier'; return self::DATASET_PREFIX . $section . ':' . $this->dataset($request); } private function csvText(mixed $value): string { $value = (string) $value; return preg_match('/^[=+\-@\t\r]/u', $value) === 1 ? "\t" . $value : $value; } }