两个「终版」目录(供应商应付(终版)、快递应付(终版))改为独立数据: 保留原有导入、导出、查询、重置、编辑按钮和导入模板,数据初始为空、按表独立存放, 不再借用基础目录的页面与数据。 - 终版目录拥有自己的同名子页面与横向页签 - 新增终版按表数据的独立导入/导出/模板/编辑接口(数据集键 final:<目录>:<页面>) - 供应商终版 13 个页面、快递终版 4 个页面已接入 - 终版导入模板统一增加“月份”列,记录标识列改为选填
153 lines
5.6 KiB
PHP
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;
|
|
}
|
|
}
|