From e147c90a2c85e6afae656a6f0dd50548a6eb7fc2 Mon Sep 17 00:00:00 2001 From: qiuxiang <394405259@qq.com> Date: Tue, 6 Oct 2026 15:42:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BA=97=E9=93=BA=E6=94=B6=E6=AC=BE=E8=B4=A6?= =?UTF-8?q?=E6=88=B7=E8=B4=A6=E5=8D=95=E6=96=B0=E5=A2=9E=E3=80=8C=E6=80=BB?= =?UTF-8?q?=E8=B4=A6=E5=A4=87=E6=B3=A8=E3=80=8D=E5=88=97=EF=BC=88=E6=8C=89?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0+=E8=B4=A6=E5=8D=95=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E8=A1=A8=E5=8F=96=E5=80=BC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ec_account_ledger_remarks 映射表与 account-ledger-remarks:import 导入命令(平台/账单项目/总账备注) - 新增 AccountLedgerRemarkMap:按店铺名前缀识别平台,无前缀时用页签默认平台;缺失匹配保持空白 - 全部账户页签(含淘系支付宝自有列定义)在「店铺/店铺名称」右侧新增「总账备注」列,页面/列设置/导出共用同一份列定义 - 控制器在分页与导出流读取时补齐 payload.ledger_remark,不改动上游账单与共享镜像数据 - 用例:新增 AccountLedgerRemarkMapTest;修正唯品会 JX/MP 列清单断言 --- .../Commands/ImportAccountLedgerRemarks.php | 126 +++++++++++++ .../EcommerceFinance/AccountBillColumns.php | 10 +- ...create_ec_account_ledger_remarks_table.php | 31 ++++ .../Http/Concerns/DecoratesLedgerRemarks.php | 48 +++++ .../Http/Controllers/AccountBills.php | 6 +- .../Http/Controllers/AlipayBills.php | 20 ++- .../Http/Controllers/DewuAccountBills.php | 6 +- .../Controllers/PinduoduoAccountBills.php | 6 +- .../Controllers/TaobaoWechatAccountBills.php | 6 +- .../Services/AccountLedgerRemarkMap.php | 166 ++++++++++++++++++ .../components/business/TaobaoAlipayBills.vue | 2 +- .../Business/VipshopJxReceivingBillsTest.php | 2 +- .../Business/VipshopMpReceivingBillsTest.php | 4 +- .../AccountLedgerRemarkMapTest.php | 86 +++++++++ 14 files changed, 504 insertions(+), 15 deletions(-) create mode 100644 app/Console/Commands/ImportAccountLedgerRemarks.php create mode 100644 modules/EcommerceFinance/Database/Migrations/2026_10_06_120000_create_ec_account_ledger_remarks_table.php create mode 100644 modules/EcommerceFinance/Http/Concerns/DecoratesLedgerRemarks.php create mode 100644 modules/EcommerceFinance/Services/AccountLedgerRemarkMap.php create mode 100644 tests/Unit/EcommerceFinance/AccountLedgerRemarkMapTest.php diff --git a/app/Console/Commands/ImportAccountLedgerRemarks.php b/app/Console/Commands/ImportAccountLedgerRemarks.php new file mode 100644 index 00000000..190b092e --- /dev/null +++ b/app/Console/Commands/ImportAccountLedgerRemarks.php @@ -0,0 +1,126 @@ +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 + */ + 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; + } +} diff --git a/modules/EcommerceFinance/AccountBillColumns.php b/modules/EcommerceFinance/AccountBillColumns.php index b577370e..12cf9a58 100644 --- a/modules/EcommerceFinance/AccountBillColumns.php +++ b/modules/EcommerceFinance/AccountBillColumns.php @@ -26,6 +26,7 @@ class AccountBillColumns private const JD_BILL_COLUMNS = [ ['记录编码', 'record_code', 'num'], ['店铺', 'store_name', 'text'], + ['总账备注', 'payload.ledger_remark', 'text'], ['自定义备注', 'custom_remark', 'text'], ['账单项目', 'bill_item', 'tag'], ['线上单号', 'payload.online_order_no', 'num'], @@ -64,6 +65,7 @@ class AccountBillColumns ['全局ID', 'record_code', 'num'], ['店铺ID', 'payload.shop_id', 'num'], ['店铺名称', 'store_name', 'text'], + ['总账备注', 'payload.ledger_remark', 'text'], ['订单签收或退款时间', 'occurred_at', 'date'], ['订单结算时间', 'payload.settle_period', 'date'], ['订单类型名称', 'bill_item', 'text'], @@ -101,6 +103,7 @@ class AccountBillColumns 'columns' => [ ['记录编码', 'record_code', 'num'], ['店铺', 'store_name', 'text'], + ['总账备注', 'payload.ledger_remark', 'text'], ['类型', 'bill_item', 'tag'], ['二次备注', 'payload.second_remark', 'text'], ['销售订单号', 'order_no', 'num'], @@ -114,7 +117,7 @@ class AccountBillColumns 'taobao-wechat' => [ 'title' => '淘系微信账户', 'groups' => [ - ['基础信息', 7], + ['基础信息', 8], ['账户与交易', 5], ['金额', 2], ['业务关联', 2], @@ -123,6 +126,7 @@ class AccountBillColumns 'columns' => [ ['记录编码', 'record_code', 'num'], ['店铺', 'store_name', 'text'], + ['总账备注', 'payload.ledger_remark', 'text'], ['财务备注', 'finance_remark', 'text'], ['订单号', 'order_no', 'num'], ['金额', 'amount', 'money'], @@ -147,6 +151,7 @@ class AccountBillColumns 'columns' => [ ['记录编码', 'record_code', 'num'], ['店铺', 'store_name', 'text'], + ['总账备注', 'payload.ledger_remark', 'text'], ['财务备注', 'finance_remark', 'text'], ['订单', 'order_no', 'num'], ['金额', 'amount', 'money'], @@ -170,6 +175,7 @@ class AccountBillColumns 'columns' => [ ['记录编码', 'record_code', 'num'], ['店铺', 'store_name', 'text'], + ['总账备注', 'payload.ledger_remark', 'text'], ['自定义备注', 'custom_remark', 'text'], ['账单项目', 'bill_item', 'tag'], ['线上单号', 'payload.online_order_no', 'num'], @@ -208,6 +214,7 @@ class AccountBillColumns 'columns' => [ ['记录编码', 'record_code', 'num'], ['店铺', 'store_name', 'text'], + ['总账备注', 'payload.ledger_remark', 'text'], ['财务备注', 'finance_remark', 'text'], ['订单', 'order_no', 'num'], ['金额', 'amount', 'money'], @@ -242,6 +249,7 @@ class AccountBillColumns 'columns' => [ ['记录编码', 'record_code', 'num'], ['店铺', 'store_name', 'text'], + ['总账备注', 'payload.ledger_remark', 'text'], ['财务备注', 'finance_remark', 'text'], ['金额', 'amount', 'money'], ['订单号', 'order_no', 'num'], diff --git a/modules/EcommerceFinance/Database/Migrations/2026_10_06_120000_create_ec_account_ledger_remarks_table.php b/modules/EcommerceFinance/Database/Migrations/2026_10_06_120000_create_ec_account_ledger_remarks_table.php new file mode 100644 index 00000000..3a9d7db8 --- /dev/null +++ b/modules/EcommerceFinance/Database/Migrations/2026_10_06_120000_create_ec_account_ledger_remarks_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('platform', 32); + $table->string('bill_item', 191); + $table->string('ledger_remark', 191); + $table->timestamps(); + + $table->unique(['platform', 'bill_item'], 'ec_account_ledger_remarks_unique'); + }); + } + + public function down(): void + { + Schema::dropIfExists('ec_account_ledger_remarks'); + } +}; diff --git a/modules/EcommerceFinance/Http/Concerns/DecoratesLedgerRemarks.php b/modules/EcommerceFinance/Http/Concerns/DecoratesLedgerRemarks.php new file mode 100644 index 00000000..4d23dbdf --- /dev/null +++ b/modules/EcommerceFinance/Http/Concerns/DecoratesLedgerRemarks.php @@ -0,0 +1,48 @@ + $data + * @return array + */ + protected function withLedgerRemarks(array $data, string $tab): array + { + if (! empty($data['data']) && is_array($data['data'])) { + $map = $this->ledgerRemarkMap(); + $data['data'] = array_map( + static fn (mixed $row): mixed => is_array($row) ? $map->decorate($row, $tab) : $row, + $data['data'], + ); + } + + return $data; + } + + /** + * 导出/流式行补总账备注。 + * + * @param iterable> $rows + * @return iterable> + */ + protected function ledgerRemarkRows(iterable $rows, string $tab): iterable + { + return $this->ledgerRemarkMap()->decorateAll($rows, $tab); + } +} diff --git a/modules/EcommerceFinance/Http/Controllers/AccountBills.php b/modules/EcommerceFinance/Http/Controllers/AccountBills.php index 16ba81d9..7c4ad7a8 100644 --- a/modules/EcommerceFinance/Http/Controllers/AccountBills.php +++ b/modules/EcommerceFinance/Http/Controllers/AccountBills.php @@ -3,6 +3,7 @@ namespace Modules\EcommerceFinance\Http\Controllers; use App\Utilities\SidebarPermissions; +use Modules\EcommerceFinance\Http\Concerns\DecoratesLedgerRemarks; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Modules\EcommerceFinance\AccountBillColumns; @@ -17,6 +18,7 @@ use RuntimeException; class AccountBills extends Controller { + use DecoratesLedgerRemarks; /** 走共享业务镜像的账户类型(其余类型读本系统账单表 ec_account_bills)。 */ private const MIRROR_TABS = ['douyin', 'pinduoduo', 'dewu', 'jd-transactions', 'jingxi', 'jd-wallet']; @@ -123,7 +125,7 @@ class AccountBills extends Controller ]; $data['columns'] = AccountBillColumns::for($billType); - return response()->json($data); + return response()->json($this->withLedgerRemarks($data, $billType)); } public function export(Request $request, string $bill_type) @@ -383,6 +385,8 @@ class AccountBills extends Controller $headers = $selected->pluck('label')->all(); + $rows = $this->ledgerRemarkRows($rows, $billType); + return response()->streamDownload(function () use ($rows, $selected, $headers): void { $csv = fopen('php://output', 'w'); fputs($csv, "\xEF\xBB\xBF"); diff --git a/modules/EcommerceFinance/Http/Controllers/AlipayBills.php b/modules/EcommerceFinance/Http/Controllers/AlipayBills.php index c2e40a9b..c5eeffdc 100644 --- a/modules/EcommerceFinance/Http/Controllers/AlipayBills.php +++ b/modules/EcommerceFinance/Http/Controllers/AlipayBills.php @@ -3,6 +3,7 @@ namespace Modules\EcommerceFinance\Http\Controllers; use App\Utilities\SidebarPermissions; +use Modules\EcommerceFinance\Http\Concerns\DecoratesLedgerRemarks; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Modules\EcommerceFinance\Models\AlipayBill; @@ -11,9 +12,11 @@ use RuntimeException; class AlipayBills extends Controller { + use DecoratesLedgerRemarks; private const EXPORT_COLUMNS = [ 'record_code' => '记录编码', 'store_name' => '原始店铺', + 'ledger_remark' => '总账备注', 'custom_remark' => '自定义备注', 'bill_item' => '账单项目', 'alipay_account' => '支付宝账号', @@ -61,12 +64,15 @@ class AlipayBills extends Controller ]); try { - return response()->json($repository->paginate( - array_map(static fn (mixed $value): string => trim((string) $value), $filters), - $sort, - $dir, - max((int) $request->get('page', 1), 1), - min(max((int) $request->get('limit', 10), 1), 100), + return response()->json($this->withLedgerRemarks( + $repository->paginate( + array_map(static fn (mixed $value): string => trim((string) $value), $filters), + $sort, + $dir, + max((int) $request->get('page', 1), 1), + min(max((int) $request->get('limit', 10), 1), 100), + ), + 'taobao-alipay', )); } catch (RuntimeException $exception) { if ($exception->getMessage() !== 'store_receiving_alipay_bill_mirror_not_ready') { @@ -146,6 +152,8 @@ class AlipayBills extends Controller return response()->json(['message' => '淘系支付宝账单共享业务镜像正在首次同步,完成后将自动显示。'], 503); } + $rows = $this->ledgerRemarkRows($rows, 'taobao-alipay'); + return response()->streamDownload(function () use ($rows, $columns): void { $csv = fopen('php://output', 'w'); fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF)); diff --git a/modules/EcommerceFinance/Http/Controllers/DewuAccountBills.php b/modules/EcommerceFinance/Http/Controllers/DewuAccountBills.php index 89e2cf1d..732ae137 100644 --- a/modules/EcommerceFinance/Http/Controllers/DewuAccountBills.php +++ b/modules/EcommerceFinance/Http/Controllers/DewuAccountBills.php @@ -6,11 +6,13 @@ use App\Utilities\SidebarPermissions; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Modules\EcommerceFinance\AccountBillColumns; +use Modules\EcommerceFinance\Http\Concerns\DecoratesLedgerRemarks; use Modules\EcommerceFinance\Repositories\DewuSettlementBillRepository; use RuntimeException; class DewuAccountBills extends Controller { + use DecoratesLedgerRemarks; public function index(Request $request, DewuSettlementBillRepository $repository) { $this->authorizeStoreReceivingBills(); @@ -54,7 +56,7 @@ class DewuAccountBills extends Controller $data['columns'] = AccountBillColumns::for('dewu'); - return response()->json($data); + return response()->json($this->withLedgerRemarks($data, 'dewu')); } public function filterOptions(DewuSettlementBillRepository $repository) @@ -123,6 +125,8 @@ class DewuAccountBills extends Controller return response()->json(['message' => '得物完整账单尚未完成首次导入。'], 503); } + $rows = $this->ledgerRemarkRows($rows, 'dewu'); + return response()->streamDownload(function () use ($rows, $columns): void { $csv = fopen('php://output', 'w'); fputs($csv, "\xEF\xBB\xBF"); diff --git a/modules/EcommerceFinance/Http/Controllers/PinduoduoAccountBills.php b/modules/EcommerceFinance/Http/Controllers/PinduoduoAccountBills.php index c990561b..79ba5a1d 100644 --- a/modules/EcommerceFinance/Http/Controllers/PinduoduoAccountBills.php +++ b/modules/EcommerceFinance/Http/Controllers/PinduoduoAccountBills.php @@ -6,11 +6,13 @@ use App\Utilities\SidebarPermissions; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Modules\EcommerceFinance\AccountBillColumns; +use Modules\EcommerceFinance\Http\Concerns\DecoratesLedgerRemarks; use Modules\EcommerceFinance\Repositories\PinduoduoSettlementBillRepository; use RuntimeException; class PinduoduoAccountBills extends Controller { + use DecoratesLedgerRemarks; public function index(Request $request, PinduoduoSettlementBillRepository $repository) { $this->authorizeStoreReceivingBills(); @@ -54,7 +56,7 @@ class PinduoduoAccountBills extends Controller $data['columns'] = AccountBillColumns::for('pinduoduo'); - return response()->json($data); + return response()->json($this->withLedgerRemarks($data, 'pinduoduo')); } public function filterOptions(PinduoduoSettlementBillRepository $repository) @@ -123,6 +125,8 @@ class PinduoduoAccountBills extends Controller return response()->json(['message' => '拼多多完整货款账单尚未完成首次导入。'], 503); } + $rows = $this->ledgerRemarkRows($rows, 'pinduoduo'); + return response()->streamDownload(function () use ($rows, $columns): void { $csv = fopen('php://output', 'w'); fputs($csv, "\xEF\xBB\xBF"); diff --git a/modules/EcommerceFinance/Http/Controllers/TaobaoWechatAccountBills.php b/modules/EcommerceFinance/Http/Controllers/TaobaoWechatAccountBills.php index 28395bc6..e1b491f6 100644 --- a/modules/EcommerceFinance/Http/Controllers/TaobaoWechatAccountBills.php +++ b/modules/EcommerceFinance/Http/Controllers/TaobaoWechatAccountBills.php @@ -6,11 +6,13 @@ use App\Utilities\SidebarPermissions; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Modules\EcommerceFinance\AccountBillColumns; +use Modules\EcommerceFinance\Http\Concerns\DecoratesLedgerRemarks; use Modules\EcommerceFinance\Repositories\TaobaoWechatSettlementBillRepository; use RuntimeException; class TaobaoWechatAccountBills extends Controller { + use DecoratesLedgerRemarks; public function index(Request $request, TaobaoWechatSettlementBillRepository $repository) { $this->authorizeStoreReceivingBills(); @@ -54,7 +56,7 @@ class TaobaoWechatAccountBills extends Controller $data['columns'] = AccountBillColumns::for('taobao-wechat'); - return response()->json($data); + return response()->json($this->withLedgerRemarks($data, 'taobao-wechat')); } public function filterOptions(TaobaoWechatSettlementBillRepository $repository) @@ -123,6 +125,8 @@ class TaobaoWechatAccountBills extends Controller return response()->json(['message' => '淘系微信完整账单尚未完成首次导入。'], 503); } + $rows = $this->ledgerRemarkRows($rows, 'taobao-wechat'); + return response()->streamDownload(function () use ($rows, $columns): void { $csv = fopen('php://output', 'w'); fputs($csv, "\xEF\xBB\xBF"); diff --git a/modules/EcommerceFinance/Services/AccountLedgerRemarkMap.php b/modules/EcommerceFinance/Services/AccountLedgerRemarkMap.php new file mode 100644 index 00000000..45f59774 --- /dev/null +++ b/modules/EcommerceFinance/Services/AccountLedgerRemarkMap.php @@ -0,0 +1,166 @@ + '淘宝', + 'taobao-wechat' => '淘宝', + 'pinduoduo' => '拼多多', + 'jd-transactions' => '京东', + 'jingxi' => '京喜', + 'jd-wallet' => '京东', + 'douyin' => '抖音', + 'dewu' => '得物', + 'vipshop' => '唯品会MP', + 'vipshop-jx' => '唯品会jx', + ]; + + /** 店铺名前缀可识别的平台(顺序敏感:先长后短)。 */ + private const STORE_PREFIX_PLATFORMS = [ + '唯品会JX' => '唯品会jx', + '唯品会MP' => '唯品会MP', + '阿里巴巴' => '阿里巴巴', + '拼多多' => '拼多多', + '天猫' => '天猫', + '淘宝' => '淘宝', + '抖音' => '抖音', + '得物' => '得物', + '京喜' => '京喜', + '京东' => '京东', + ]; + + /** + * @return array> [platform => [bill_item => ledger_remark]] + */ + public function map(): array + { + return Cache::rememberForever(self::CACHE_KEY, function (): array { + try { + if (! Schema::hasTable('ec_account_ledger_remarks')) { + return []; + } + + $map = []; + foreach (\Illuminate\Support\Facades\DB::table('ec_account_ledger_remarks') + ->select(['platform', 'bill_item', 'ledger_remark']) + ->get() as $row) { + $platform = trim((string) $row->platform); + $item = trim((string) $row->bill_item); + if ($platform === '' || $item === '') { + continue; + } + $map[$platform][$item] = (string) $row->ledger_remark; + } + + return $map; + } catch (Throwable) { + return []; + } + }); + } + + public function forget(): void + { + Cache::forget(self::CACHE_KEY); + } + + /** 页签默认平台。 */ + public function defaultPlatform(string $tab): ?string + { + return self::TAB_PLATFORM[$tab] ?? null; + } + + /** 解析某一行账单所属平台:优先店铺名前缀,其次页签默认。 */ + public function platform(?string $storeName, ?string $tabPlatform): ?string + { + $name = trim((string) $storeName); + if ($name !== '') { + foreach (self::STORE_PREFIX_PLATFORMS as $prefix => $platform) { + if (str_starts_with($name, $prefix)) { + return $platform; + } + } + } + + return $tabPlatform; + } + + /** 查总账备注;没有匹配返回 null。 */ + public function remark(?string $storeName, ?string $tabPlatform, ?string $billItem): ?string + { + $item = trim((string) $billItem); + if ($item === '') { + return null; + } + + $map = $this->map(); + if ($map === []) { + return null; + } + + foreach ([$this->platform($storeName, $tabPlatform), $tabPlatform] as $platform) { + if ($platform === null || $platform === '') { + continue; + } + if (isset($map[$platform][$item])) { + return $map[$platform][$item]; + } + } + + return null; + } + + /** + * 给一行账单补上 payload.ledger_remark(内部字段名 ledger_remark)。 + * + * @param array $row + * @return array + */ + public function decorate(array $row, string $tab): array + { + $tabPlatform = $this->defaultPlatform($tab); + $remark = $this->remark( + $row['store_name'] ?? null, + $tabPlatform, + $row['bill_item'] ?? null, + ); + if ($remark === null) { + return $row; + } + + $payload = is_array($row['payload'] ?? null) ? $row['payload'] : []; + $payload['ledger_remark'] = $remark; + $row['payload'] = $payload; + // 淘系支付宝页签按顶层字段取值,这里同时补一份,页面与导出共用。 + $row['ledger_remark'] = $remark; + + return $row; + } + + /** + * @param iterable> $rows + * @return \Generator> + */ + public function decorateAll(iterable $rows, string $tab): \Generator + { + foreach ($rows as $row) { + yield is_array($row) ? $this->decorate($row, $tab) : $row; + } + } +} diff --git a/resources/assets/js/components/business/TaobaoAlipayBills.vue b/resources/assets/js/components/business/TaobaoAlipayBills.vue index 97b27711..83c37de7 100644 --- a/resources/assets/js/components/business/TaobaoAlipayBills.vue +++ b/resources/assets/js/components/business/TaobaoAlipayBills.vue @@ -103,7 +103,7 @@ import 'flatpickr/dist/flatpickr.css'; const dateConfig = { dateFormat: 'Y-m-d', locale: Mandarin, disableMobile: true, allowInput: false, onReady: (_, value, picker) => picker.calendarContainer.classList.add('alipay-bill-calendar') }; const COLUMNS = [ - ['记录编码', 'record_code', 'num', '基础信息'], ['原始店铺', 'store_name', 'text', '基础信息'], ['自定义备注', 'custom_remark', 'text', '基础信息'], ['账单项目', 'bill_item', 'tag', '基础信息'], + ['记录编码', 'record_code', 'num', '基础信息'], ['原始店铺', 'store_name', 'text', '基础信息'], ['总账备注', 'ledger_remark', 'text', '基础信息'], ['自定义备注', 'custom_remark', 'text', '基础信息'], ['账单项目', 'bill_item', 'tag', '基础信息'], ['支付宝账号', 'alipay_account', 'num', '账户与交易'], ['账务订单号', 'account_order_no', 'num', '账户与交易'], ['业务流水号', 'business_serial_no', 'num', '账户与交易'], ['商户订单号', 'merchant_order_no', 'num', '账户与交易'], ['业务基础订单号', 'business_base_order_no', 'num', '账户与交易'], ['商品名称', 'goods_name', 'text', '账户与交易'], ['对方账号', 'counterparty_account', 'text', '账户与交易'], ['发生时间', 'occurred_at', 'date', '账户与交易'], ['收入', 'income', 'money_in', '金额(元)'], ['支出', 'expense', 'money_out', '金额(元)'], ['余额', 'balance', 'money', '金额(元)'], ['业务类型', 'business_type', 'tag', '业务关联'], ['备注', 'remark', 'text', '业务关联'], ['线上单号', 'online_order_no', 'num', '业务关联'], ['线上单号对应实际店铺', 'online_real_store', 'text', '业务关联'], ['业务描述', 'business_description', 'text', '业务关联'], ['业务账单来源', 'business_bill_source', 'text', '业务关联'], diff --git a/tests/Feature/Business/VipshopJxReceivingBillsTest.php b/tests/Feature/Business/VipshopJxReceivingBillsTest.php index e1013311..3a129a45 100644 --- a/tests/Feature/Business/VipshopJxReceivingBillsTest.php +++ b/tests/Feature/Business/VipshopJxReceivingBillsTest.php @@ -39,7 +39,7 @@ class VipshopJxReceivingBillsTest extends FeatureTestCase ->assertJsonPath('data.1.payload.settle_period', '2026-07'); $this->assertSame( - ['记录编码', '店铺', '类型', '二次备注', '销售订单号', '订单结算时间', '订单日期(按订单号)', '含税金额', '收入金额', '支出金额'], + ['记录编码', '店铺', '总账备注', '类型', '二次备注', '销售订单号', '订单结算时间', '订单日期(按订单号)', '含税金额', '收入金额', '支出金额'], array_column($response->json('columns.columns'), 'label'), ); } diff --git a/tests/Feature/Business/VipshopMpReceivingBillsTest.php b/tests/Feature/Business/VipshopMpReceivingBillsTest.php index a331180f..d943f853 100644 --- a/tests/Feature/Business/VipshopMpReceivingBillsTest.php +++ b/tests/Feature/Business/VipshopMpReceivingBillsTest.php @@ -27,8 +27,8 @@ class VipshopMpReceivingBillsTest extends FeatureTestCase ->assertJsonPath('data.1.payload.settle_period', '2026-08'); $this->assertSame( - ['全局ID', '店铺ID', '店铺名称', '订单签收或退款时间', '订单结算时间', '订单类型名称'], - array_slice(array_column($response->json('columns.columns'), 'label'), 0, 6), + ['全局ID', '店铺ID', '店铺名称', '总账备注', '订单签收或退款时间', '订单结算时间', '订单类型名称'], + array_slice(array_column($response->json('columns.columns'), 'label'), 0, 7), ); } diff --git a/tests/Unit/EcommerceFinance/AccountLedgerRemarkMapTest.php b/tests/Unit/EcommerceFinance/AccountLedgerRemarkMapTest.php new file mode 100644 index 00000000..97b78184 --- /dev/null +++ b/tests/Unit/EcommerceFinance/AccountLedgerRemarkMapTest.php @@ -0,0 +1,86 @@ +id(); + $table->string('platform', 32); + $table->string('bill_item', 191); + $table->string('ledger_remark', 191); + $table->timestamps(); + }); + } + + protected function tearDown(): void + { + Cache::flush(); + Schema::dropIfExists('ec_account_ledger_remarks'); + + parent::tearDown(); + } + + public function test_every_account_tab_places_ledger_remark_right_after_the_store_column(): void + { + foreach (AccountBillColumns::map() as $tab => $definition) { + $columns = $definition['columns'] ?? []; + $labels = array_map(static fn (array $column): string => (string) $column[0], $columns); + + $storeIndex = null; + foreach ($labels as $index => $label) { + if (in_array($label, ['店铺', '店铺名称'], true)) { + $storeIndex = $index; + break; + } + } + + $this->assertNotNull($storeIndex, $tab . ' 缺少店铺列'); + $this->assertSame('总账备注', $labels[$storeIndex + 1] ?? null, $tab . ' 的总账备注没有紧跟店铺列'); + $this->assertSame('payload.ledger_remark', $columns[$storeIndex + 1][1] ?? null, $tab . ' 的总账备注字段不正确'); + } + } + + public function test_it_matches_platform_and_bill_item_then_decorates_rows(): void + { + $now = now(); + DB::table('ec_account_ledger_remarks')->insert([ + ['platform' => '抖音', 'bill_item' => '保费扣除', 'ledger_remark' => '运费险', 'created_at' => $now, 'updated_at' => $now], + ['platform' => '天猫', 'bill_item' => '天猫佣金', 'ledger_remark' => '平台佣金', 'created_at' => $now, 'updated_at' => $now], + ]); + + $map = new AccountLedgerRemarkMap(); + + $this->assertSame('运费险', $map->remark('抖音-乾廷润凯家具专卖店', '抖音', '保费扣除')); + $this->assertSame('平台佣金', $map->remark('天猫-卓尔家具旗舰店', '淘宝', '天猫佣金')); + $this->assertSame('平台佣金', $map->remark('没有平台前缀的店铺', '天猫', '天猫佣金')); + $this->assertNull($map->remark('抖音-乾廷润凯家具专卖店', '抖音', '不存在的账单项目')); + + $row = $map->decorate([ + 'store_name' => '抖音-乾廷润凯家具专卖店', + 'bill_item' => '保费扣除', + 'payload' => ['settle_account' => 'TouTiaoFXGZJ'], + ], 'douyin'); + + $this->assertSame('运费险', $row['payload']['ledger_remark']); + $this->assertSame('运费险', $row['ledger_remark']); + $this->assertSame('TouTiaoFXGZJ', $row['payload']['settle_account']); + + $untouched = $map->decorate(['store_name' => '抖音-xx', 'bill_item' => '未知项目', 'payload' => []], 'douyin'); + $this->assertSame([], $untouched['payload']); + } +}