Compare commits
1 Commits
main
...
feature/ch
| Author | SHA1 | Date | |
|---|---|---|---|
| 985281c100 |
126
app/Console/Commands/ImportAccountLedgerRemarks.php
Normal file
126
app/Console/Commands/ImportAccountLedgerRemarks.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@ -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'],
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* 店铺收款账户账单「总账备注」映射:按「平台 + 账单项目」查出总账备注。
|
||||
*
|
||||
* 页面与导出在读取账单行时用该表补齐总账备注,不改动上游账单数据。
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('ec_account_ledger_remarks', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Http\Concerns;
|
||||
|
||||
use Modules\EcommerceFinance\Services\AccountLedgerRemarkMap;
|
||||
|
||||
/**
|
||||
* 店铺收款账户账单:给每行账单补「总账备注」(按平台 + 账单项目匹配映射表)。
|
||||
*
|
||||
* 只在读取时补齐,不改动上游账单与镜像数据。
|
||||
*/
|
||||
trait DecoratesLedgerRemarks
|
||||
{
|
||||
protected function ledgerRemarkMap(): AccountLedgerRemarkMap
|
||||
{
|
||||
return app(AccountLedgerRemarkMap::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页结果(含 data 行数组)补总账备注。
|
||||
*
|
||||
* @param array<string,mixed> $data
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
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<int,array<string,mixed>> $rows
|
||||
* @return iterable<int,array<string,mixed>>
|
||||
*/
|
||||
protected function ledgerRemarkRows(iterable $rows, string $tab): iterable
|
||||
{
|
||||
return $this->ledgerRemarkMap()->decorateAll($rows, $tab);
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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");
|
||||
|
||||
166
modules/EcommerceFinance/Services/AccountLedgerRemarkMap.php
Normal file
166
modules/EcommerceFinance/Services/AccountLedgerRemarkMap.php
Normal file
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 「总账备注」取值:按(平台 + 账单项目)匹配 ec_account_ledger_remarks。
|
||||
*
|
||||
* 店铺收款账户账单各页签的账单项目名称不一致,平台口径以店铺名前缀为准
|
||||
* (如「抖音-xxx」「天猫-xxx」),页签默认平台只作为兜底,避免淘系页签里
|
||||
* 天猫/淘宝/阿里巴巴三类项目混用同一份映射。
|
||||
*/
|
||||
class AccountLedgerRemarkMap
|
||||
{
|
||||
public const CACHE_KEY = 'ec_account_ledger_remarks_map_v1';
|
||||
|
||||
/** 页签默认平台(店铺名没有平台前缀时使用)。 */
|
||||
private const TAB_PLATFORM = [
|
||||
'taobao-alipay' => '淘宝',
|
||||
'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<string,array<string,string>> [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<string,mixed> $row
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
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<int,array<string,mixed>> $rows
|
||||
* @return \Generator<int,array<string,mixed>>
|
||||
*/
|
||||
public function decorateAll(iterable $rows, string $tab): \Generator
|
||||
{
|
||||
foreach ($rows as $row) {
|
||||
yield is_array($row) ? $this->decorate($row, $tab) : $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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', '业务关联'],
|
||||
|
||||
@ -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'),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
86
tests/Unit/EcommerceFinance/AccountLedgerRemarkMapTest.php
Normal file
86
tests/Unit/EcommerceFinance/AccountLedgerRemarkMapTest.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\EcommerceFinance;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Modules\EcommerceFinance\AccountBillColumns;
|
||||
use Modules\EcommerceFinance\Services\AccountLedgerRemarkMap;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AccountLedgerRemarkMapTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Cache::flush();
|
||||
Schema::dropIfExists('ec_account_ledger_remarks');
|
||||
Schema::create('ec_account_ledger_remarks', function (Blueprint $table): void {
|
||||
$table->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']);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user