CaiWuXiTong/app/Http/Controllers/Business/PayablesFinalActions.php
renshen 813e956df5 应付款终版目录接入独立按表数据
两个「终版」目录(供应商应付(终版)、快递应付(终版))改为独立数据:
保留原有导入、导出、查询、重置、编辑按钮和导入模板,数据初始为空、按表独立存放,
不再借用基础目录的页面与数据。

- 终版目录拥有自己的同名子页面与横向页签
- 新增终版按表数据的独立导入/导出/模板/编辑接口(数据集键 final:<目录>:<页面>)
- 供应商终版 13 个页面、快递终版 4 个页面已接入
- 终版导入模板统一增加“月份”列,记录标识列改为选填
2026-09-29 11:06:41 +08:00

153 lines
5.6 KiB
PHP

<?php
namespace App\Http\Controllers\Business;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\EcommerceFinance\Services\PayablesFinalImporter;
use Modules\EcommerceFinance\Services\PayablesFinalRegistry;
use Modules\EcommerceFinance\Services\PayablesFieldNormalizer;
use Modules\EcommerceFinance\Services\SupplierPayableImportTemplate;
use Modules\EcommerceFinance\Services\SupplierPayableOverrideStore;
use RuntimeException;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* 「终版」目录的按表数据:界面与基础页面一致,数据独立存放
* (数据集键 final:<目录>:<页面>),初始为空,由页面上的导入、导出、查询、编辑维护。
*/
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);
}
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<string,array{label:string,type:string}> */
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;
}
}