DoDo 692d590509 feat: 公司盘点页面与未完结店铺关联
新增经营报表 › 公司盘点,支持导入预览、筛选、备注关键词搜索、编辑、导出及未完结店铺关联。

验证:公司盘点与导航相关测试 15 项通过(161 断言),目录表头测试 99 断言通过。
边界:原始 Excel 与本地 SQLite 数据不进入 Git;本次无数据库迁移。
2026-11-09 15:43:43 +08:00

187 lines
7.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 !== ''));
}
}