新增经营报表 › 公司盘点,支持导入预览、筛选、备注关键词搜索、编辑、导出及未完结店铺关联。 验证:公司盘点与导航相关测试 15 项通过(161 断言),目录表头测试 99 断言通过。 边界:原始 Excel 与本地 SQLite 数据不进入 Git;本次无数据库迁移。
187 lines
7.2 KiB
PHP
187 lines
7.2 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Business;
|
||
|
||
use App\Business\CompanyPlan\CompanyPlanPageData;
|
||
use App\Business\CompanyPlan\CompanyPlanRepository;
|
||
use App\Business\CompanyPlan\CompanyPlanWorkbookExporter;
|
||
use App\Business\CompanyPlan\CompanyPlanWorkbookImporter;
|
||
use Illuminate\Http\RedirectResponse;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Validator;
|
||
use Illuminate\Validation\ValidationException;
|
||
use Throwable;
|
||
|
||
class CompanyPlan
|
||
{
|
||
public function previewImport(
|
||
Request $request,
|
||
CompanyPlanWorkbookImporter $importer,
|
||
): RedirectResponse {
|
||
$request->validate([
|
||
'import' => ['required', 'file', 'mimes:xlsx,xls', 'max:' . (int) config('company_plan.max_upload_kb', 20480)],
|
||
'duplicate_mode' => ['required', 'in:update,skip'],
|
||
]);
|
||
|
||
$token = (string) $request->session()->get('company_plan_preview_token', '');
|
||
if ($token !== '') {
|
||
$importer->deletePreview($token);
|
||
}
|
||
|
||
try {
|
||
$preview = $importer->preview(
|
||
$request->file('import'),
|
||
(int) (company_id() ?: 1),
|
||
(int) (auth()->id() ?: 0),
|
||
(string) $request->input('duplicate_mode', 'update'),
|
||
);
|
||
$token = $importer->storePreview($preview);
|
||
$request->session()->put('company_plan_preview_token', $token);
|
||
} catch (ValidationException $exception) {
|
||
return $this->redirectToPage()->withErrors($exception->errors());
|
||
} catch (Throwable $exception) {
|
||
report($exception);
|
||
|
||
return $this->redirectToPage()->with('error', '导入预览失败,请确认 Excel 可正常打开后重试。');
|
||
}
|
||
|
||
return $this->redirectToPage()->with('success', sprintf(
|
||
'已识别“%s”:新增 %s 行,更新 %s 行,跳过 %s 行,异常 %s 行。请确认后导入。',
|
||
$preview['source_sheet'],
|
||
number_format($preview['new_count']),
|
||
number_format($preview['update_count']),
|
||
number_format($preview['skip_count']),
|
||
number_format($preview['invalid_count']),
|
||
));
|
||
}
|
||
|
||
public function confirmImport(
|
||
Request $request,
|
||
CompanyPlanWorkbookImporter $importer,
|
||
): RedirectResponse {
|
||
$data = $request->validate([
|
||
'preview_token' => ['required', 'string', 'size:40'],
|
||
'duplicate_mode' => ['required', 'in:update,skip'],
|
||
]);
|
||
$sessionToken = (string) $request->session()->get('company_plan_preview_token', '');
|
||
if ($sessionToken === '' || ! hash_equals($sessionToken, $data['preview_token'])) {
|
||
return $this->redirectToPage()->with('error', '导入预览已失效,请重新选择文件。');
|
||
}
|
||
|
||
try {
|
||
$result = $importer->confirm(
|
||
$data['preview_token'],
|
||
(int) (company_id() ?: 1),
|
||
(int) (auth()->id() ?: 0),
|
||
$data['duplicate_mode'],
|
||
);
|
||
$request->session()->forget('company_plan_preview_token');
|
||
} catch (ValidationException $exception) {
|
||
return $this->redirectToPage()->withErrors($exception->errors());
|
||
} catch (Throwable $exception) {
|
||
report($exception);
|
||
|
||
return $this->redirectToPage()->with('error', '公司盘点导入失败,原有数据未改变。请检查文件后重试。');
|
||
}
|
||
|
||
return $this->redirectToPage()->with('success', sprintf(
|
||
'公司盘点导入完成:新增 %s 条,更新 %s 条,跳过 %s 条。',
|
||
number_format($result['created']),
|
||
number_format($result['updated']),
|
||
number_format($result['skipped']),
|
||
));
|
||
}
|
||
|
||
public function cancelImport(
|
||
Request $request,
|
||
CompanyPlanWorkbookImporter $importer,
|
||
): RedirectResponse {
|
||
$token = (string) $request->session()->get('company_plan_preview_token', '');
|
||
$importer->deletePreview($token);
|
||
$request->session()->forget('company_plan_preview_token');
|
||
|
||
return $this->redirectToPage()->with('success', '已取消本次导入,现有公司盘点数据未改变。');
|
||
}
|
||
|
||
public function update(
|
||
Request $request,
|
||
CompanyPlanRepository $repository,
|
||
int $row,
|
||
): RedirectResponse {
|
||
$validator = Validator::make($request->all(), [
|
||
'sequence_no' => ['nullable', 'integer', 'min:0', 'max:9999999'],
|
||
'company_name' => ['required', 'string', 'max:255'],
|
||
'company_type' => ['nullable', 'string', 'max:128'],
|
||
'taxpayer_type' => ['nullable', 'string', 'max:128'],
|
||
'registered_at' => ['nullable', 'date_format:Y-m-d'],
|
||
'business_registration_type' => ['nullable', 'string', 'max:128'],
|
||
'unified_social_credit_code' => ['nullable', 'string', 'max:64'],
|
||
'plan' => ['nullable', 'string', 'max:64'],
|
||
'company_status' => ['nullable', 'string', 'max:64'],
|
||
'tax_cancellation' => ['nullable', 'string', 'max:64'],
|
||
'business_cancellation' => ['nullable', 'string', 'max:64'],
|
||
'bank_cancellation' => ['nullable', 'string', 'max:64'],
|
||
'notes' => ['nullable', 'string', 'max:5000'],
|
||
]);
|
||
$data = $validator->validate();
|
||
$companyId = (int) (company_id() ?: 1);
|
||
|
||
if ($repository->findForCompany($companyId, $row) === null) {
|
||
abort(404);
|
||
}
|
||
if ($repository->companyNameExists($companyId, (string) $data['company_name'], $row)) {
|
||
throw ValidationException::withMessages([
|
||
'company_name' => '另一家主体已使用该公司名称,不能产生重复记录。',
|
||
]);
|
||
}
|
||
|
||
$repository->update(
|
||
$companyId,
|
||
$row,
|
||
$data,
|
||
(int) (auth()->id() ?: 0),
|
||
);
|
||
|
||
return back()->with('success', '公司盘点已保存。');
|
||
}
|
||
|
||
public function export(
|
||
Request $request,
|
||
CompanyPlanRepository $repository,
|
||
CompanyPlanWorkbookExporter $exporter,
|
||
) {
|
||
$companyId = (int) (company_id() ?: 1);
|
||
$filters = CompanyPlanPageData::filters($request);
|
||
$rows = $repository->exportRows($companyId, $filters);
|
||
$columns = array_values(array_filter(
|
||
array_map('trim', explode(',', (string) $request->query('columns', ''))),
|
||
));
|
||
|
||
return $exporter->download(
|
||
$rows,
|
||
$columns,
|
||
'公司盘点_' . now()->format('Ymd_His') . '.xlsx',
|
||
);
|
||
}
|
||
|
||
public function template(CompanyPlanWorkbookExporter $exporter)
|
||
{
|
||
return $exporter->download(
|
||
[],
|
||
array_column(CompanyPlanRepository::FIELDS, 'name'),
|
||
'公司盘点导入模板.xlsx',
|
||
);
|
||
}
|
||
|
||
private function redirectToPage(): RedirectResponse
|
||
{
|
||
$query = request()->except(['_token', 'import', 'duplicate_mode', 'preview_token', '_method']);
|
||
|
||
return redirect()->route('business.pages.show', array_filter([
|
||
'section' => 'company-management',
|
||
'page' => 'company-plan',
|
||
...$query,
|
||
], static fn (mixed $value): bool => $value !== null && $value !== ''));
|
||
}
|
||
}
|