CaiWuXiTong/app/Console/Commands/ImportAccountLedgerRemarks.php
qiuxiang 985281c100 店铺收款账户账单新增「总账备注」列,按平台+账单项目匹配映射表
- 新增 ec_account_ledger_remarks 表与 account-ledger-remarks:import 导入命令(平台/账单项目/总账备注)
- 新增 AccountLedgerRemarkMap:按店铺名前缀识别平台(天猫/淘宝/阿里巴巴/抖音/得物/京东/京喜/拼多多/唯品会),无前缀用页签默认平台
- 全部账户页签在「店铺/店铺名称」右侧新增「总账备注」列(含淘系支付宝自有列定义),页面、列设置、导出共用同一份列定义
- 控制器在读取分页与导出流时补齐 payload.ledger_remark,不改动上游账单与镜像数据
- 用例:新增 AccountLedgerRemarkMapTest;修正唯品会 JX/MP 的列清单断言
2026-10-06 15:41:10 +08:00

127 lines
4.0 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\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Modules\EcommerceFinance\Services\AccountLedgerRemarkMap;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Throwable;
/**
* 导入「平台 | 账单项目 | 总账备注」映射表,供店铺收款账户账单的「总账备注」列使用。
*/
class ImportAccountLedgerRemarks extends Command
{
protected $signature = 'account-ledger-remarks:import {path : xlsx/csv 文件} {--replace : 先清空再导入(默认按平台+账单项目覆盖)}';
protected $description = '导入账单项目对应的总账备注映射表';
public function handle(AccountLedgerRemarkMap $map): int
{
$path = (string) $this->argument('path');
if (! is_file($path)) {
$this->error('文件不存在:' . $path);
return self::FAILURE;
}
try {
$rows = $this->readRows($path);
} catch (Throwable $e) {
$this->error('读取失败:' . $e->getMessage());
return self::FAILURE;
}
if ($rows === []) {
$this->error('没有解析到有效数据(需要表头:平台 / 账单项目 / 总账备注)。');
return self::FAILURE;
}
$now = now();
DB::transaction(function () use ($rows, $now): void {
if ($this->option('replace')) {
DB::table('ec_account_ledger_remarks')->delete();
}
foreach (array_chunk($rows, 500) as $chunk) {
DB::table('ec_account_ledger_remarks')->upsert(
array_map(static fn (array $row): array => $row + ['created_at' => $now, 'updated_at' => $now], $chunk),
['platform', 'bill_item'],
['ledger_remark', 'updated_at'],
);
}
});
$map->forget();
$platforms = [];
foreach ($rows as $row) {
$platforms[$row['platform']] = ($platforms[$row['platform']] ?? 0) + 1;
}
ksort($platforms);
$this->info(sprintf('已导入 %d 条总账备注映射。', count($rows)));
foreach ($platforms as $platform => $count) {
$this->line(sprintf(' %s:%d 条', $platform, $count));
}
return self::SUCCESS;
}
/**
* @return list<array{platform:string,bill_item:string,ledger_remark:string}>
*/
private function readRows(string $path): array
{
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$readerType = match ($extension) {
'xlsx' => 'Xlsx',
'xls' => 'Xls',
'csv' => 'Csv',
default => throw new \InvalidArgumentException('只支持 xlsx / xls / csv 文件。'),
};
$reader = IOFactory::createReader($readerType);
$reader->setReadDataOnly(true);
if ($readerType === 'Csv') {
$reader->setInputEncoding('UTF-8');
}
$sheet = $reader->load($path)->getActiveSheet();
$rows = [];
$header = null;
foreach ($sheet->getRowIterator() as $row) {
$cells = [];
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(false);
foreach ($cellIterator as $cell) {
$cells[] = trim((string) $cell->getValue());
}
if ($header === null) {
$cells[0] = preg_replace('/^\xEF\xBB\xBF/', '', $cells[0] ?? '');
$header = $cells;
continue;
}
$platform = trim($cells[0] ?? '');
$item = trim($cells[1] ?? '');
$remark = trim($cells[2] ?? '');
if ($platform === '' || $item === '') {
continue;
}
$rows[] = [
'platform' => mb_substr($platform, 0, 32),
'bill_item' => mb_substr($item, 0, 191),
'ledger_remark' => mb_substr($remark, 0, 191),
];
}
return $rows;
}
}