CaiWuXiTong/app/Business/FundForecastWorkbookImporter.php

163 lines
6.1 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\Business;
use App\Models\Business\FundForecast;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
class FundForecastWorkbookImporter
{
private const HEADER_FIELDS = [
'预策月份' => 'budget_month',
'预测月份' => 'budget_month',
'公司名称' => 'company_name',
'余额/银行' => 'balance_bank',
'余额/京东' => 'balance_jd',
'余额/抖音' => 'balance_douyin',
'余额/得物' => 'balance_dewu',
'余额/淘系支付宝' => 'balance_taobao_alipay',
'余额/微信' => 'balance_wechat',
'收入/京东' => 'income_jd',
'收入/抖音' => 'income_douyin',
'收入/得物' => 'income_dewu',
'收入/淘系支付宝' => 'income_taobao_alipay',
'收入/货款' => 'income_goods_payment',
'收入/其他收入' => 'income_other',
'支出/供应商货款' => 'expense_supplier_goods',
'支出/快递款' => 'expense_courier',
'支出/纸厂压贴' => 'expense_paper_lamination',
'支出/封边条' => 'expense_edge_band',
'支出/日常报销' => 'expense_daily_reimbursement',
'支出/淘宝红包' => 'expense_taobao_red_packet',
'支出/板材' => 'expense_board',
'支出/店铺充值' => 'expense_store_top_up',
'支出/种菜' => 'expense_planting',
'支出/工资' => 'expense_salary',
'支出/税金' => 'expense_tax',
'支出/店铺花费暂估10%' => 'expense_store_spend',
];
public function import(UploadedFile $file, int $companyId): array
{
$spreadsheet = IOFactory::load($file->getRealPath());
$worksheet = $spreadsheet->getSheet(0);
$rows = $worksheet->toArray(null, true, false, false);
$columnMap = $this->columnMap($rows[0] ?? [], $rows[1] ?? []);
foreach (['budget_month', 'company_name'] as $required) {
if (! in_array($required, $columnMap, true)) {
throw ValidationException::withMessages([
'import' => '资金预测表缺少“预测月份”或“公司名称”表头。',
]);
}
}
$created = 0;
$updated = 0;
DB::transaction(function () use ($rows, $columnMap, $companyId, &$created, &$updated): void {
foreach (array_slice($rows, 2) as $offset => $row) {
if (collect($row)->every(fn ($value): bool => $value === null || trim((string) $value) === '')) {
continue;
}
$rowNumber = $offset + 3;
$data = [];
foreach ($columnMap as $columnIndex => $field) {
$data[$field] = $this->value($row[$columnIndex] ?? null, $field, $rowNumber);
}
if (($data['budget_month'] ?? '') === '' || ($data['company_name'] ?? '') === '') {
throw ValidationException::withMessages([
'import' => "第 {$rowNumber} 行必须填写预测月份和公司名称。",
]);
}
$identity = [
'company_id' => $companyId,
'budget_month' => $data['budget_month'],
'company_name' => $data['company_name'],
];
$record = FundForecast::query()->where($identity)->first();
$data['report_status'] = '待审核';
if ($record) {
$record->update($data);
$updated++;
} else {
FundForecast::create(array_merge($identity, $data));
$created++;
}
}
});
if ($created + $updated === 0) {
throw ValidationException::withMessages([
'import' => '表格中没有可导入的数据行。',
]);
}
return compact('created', 'updated');
}
private function columnMap(array $groups, array $headers): array
{
$map = [];
$group = '';
$count = max(count($groups), count($headers));
for ($index = 0; $index < $count; $index++) {
$candidate = trim((string) ($groups[$index] ?? ''));
if ($candidate !== '') {
$group = $candidate;
}
$header = trim((string) ($headers[$index] ?? ''));
$combined = $header === '' ? $group : ($group === '' ? $header : $group . '/' . $header);
$field = self::HEADER_FIELDS[$this->normalizeHeader($combined)] ?? null;
if ($field !== null) {
$map[$index] = $field;
}
}
return $map;
}
private function normalizeHeader(string $header): string
{
return preg_replace('/[\s_\-()()]+/u', '', trim($header));
}
private function value(mixed $value, string $field, int $rowNumber): mixed
{
if ($value === null || trim((string) $value) === '') {
return null;
}
if ($field === 'budget_month') {
if (is_numeric($value)) {
return ExcelDate::excelToDateTimeObject($value)->format('Y-m');
}
$text = trim((string) $value);
if (preg_match('/^(\d{4})[年\/.\-](\d{1,2})(?:月)?$/u', $text, $matches) === 1) {
$month = (int) $matches[2];
if ($month >= 1 && $month <= 12) {
return sprintf('%04d-%02d', (int) $matches[1], $month);
}
}
throw ValidationException::withMessages(['import' => "第 {$rowNumber} 行预测月份格式不正确。"]);
}
if ($field === 'company_name') {
return mb_substr(trim((string) $value), 0, 191);
}
$number = str_replace([',', ',', '¥', '¥'], '', trim((string) $value));
if (! is_numeric($number)) {
throw ValidationException::withMessages(['import' => "第 {$rowNumber} 行包含无效金额。"]);
}
return (float) $number;
}
}