From e806dcb3d6d7d4c7cd8fcfae9b130b629935324a Mon Sep 17 00:00:00 2001 From: limeng <1131177064@qq.com> Date: Fri, 18 Sep 2026 18:32:36 +0800 Subject: [PATCH 01/25] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20E=20=E7=AD=BE?= =?UTF-8?q?=E5=AE=9D=E7=AD=BE=E7=BD=B2=E6=96=B9=E8=B4=A6=E5=8F=B7=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=20(#207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agreement-signing.blade.php | 23 +++++++++++++++++-- .../Business/AgreementSigningPageTest.php | 8 ++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/resources/views/business/pages/courier-payables/agreement-signing.blade.php b/resources/views/business/pages/courier-payables/agreement-signing.blade.php index 8cada51f..562ab5c1 100644 --- a/resources/views/business/pages/courier-payables/agreement-signing.blade.php +++ b/resources/views/business/pages/courier-payables/agreement-signing.blade.php @@ -1395,7 +1395,7 @@
- +

选择快递公司后自动带出工作平台的“电子签章经办人”(dzqzjbr);如资料未维护,可手工填写对方在 E 签宝实名绑定的手机号或邮箱。

@@ -1416,7 +1416,7 @@

确认单绑定已审核报价版本,生成后保存该版本快照;变价请生成新版本并重新签署。

@@ -2216,6 +2216,24 @@ }); } + function refreshSignerAccountLabels(templateType) { + var labels = { + primary: '对方 E 签宝账号', + secondary: '第二签署方 E 签宝账号' + }; + + if (templateType === 'cooperation-contract') { + labels.primary = '快递方 E 签宝账号'; + labels.secondary = '我方 E 签宝账号'; + } else if (templateType === 'authorization') { + labels.primary = '委托方 E 签宝账号'; + labels.secondary = '受托方 E 签宝账号'; + } + + setText('[data-primary-esign-label]', labels.primary); + setText('[data-secondary-esign-label]', labels.secondary); + } + function formatDateField(selector, fallback) { var value = fieldValue(selector, ''); var parts = value.split('-'); @@ -2371,6 +2389,7 @@ var courier = fieldValue('[name="courier_name"]', '快递公司'); var companyName = fieldValue('[name="company_full_name"]', '待填写公司全称'); activateTemplate(templateType); + refreshSignerAccountLabels(templateType); var secondary = root.querySelector('[data-secondary-signer-fields]'); if (secondary) { var needsSecondary = templateType === 'entity-change' || templateType === 'cooperation-contract' || templateType === 'authorization'; diff --git a/tests/Feature/Business/AgreementSigningPageTest.php b/tests/Feature/Business/AgreementSigningPageTest.php index 19f871c7..c0c0925c 100644 --- a/tests/Feature/Business/AgreementSigningPageTest.php +++ b/tests/Feature/Business/AgreementSigningPageTest.php @@ -67,7 +67,13 @@ class AgreementSigningPageTest extends FeatureTestCase ->assertSee('data-company-name', false) ->assertSee('data-subject-linked-name', false) ->assertSeeText('签署截止日期') - ->assertSeeText('对方E签宝账号') + ->assertSeeText('对方 E 签宝账号') + ->assertSee('data-primary-esign-label', false) + ->assertSee('data-secondary-esign-label', false) + ->assertSee("labels.primary = '快递方 E 签宝账号'", false) + ->assertSee("labels.secondary = '我方 E 签宝账号'", false) + ->assertSee("labels.primary = '委托方 E 签宝账号'", false) + ->assertSee("labels.secondary = '受托方 E 签宝账号'", false) ->assertSeeText('合作合同统一使用我方账号 18052733960') ->assertSeeText('填写变量') ->assertSeeText('签署完成后自动归档') From 1751d8e06285ad8303e73ced7c4e38896c907daf Mon Sep 17 00:00:00 2001 From: chenxi Date: Sat, 19 Sep 2026 01:52:45 +0800 Subject: [PATCH 02/25] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E6=B7=98=E7=B3=BB?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E8=B4=A6=E5=8D=95=E5=89=AF=E6=9C=AC?= =?UTF-8?q?=E5=BA=93=E5=85=A8=E9=87=8F=E6=95=B0=E6=8D=AE=20(#208)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用经营分析副本库作为全量分页、筛选、汇总与导出主数据源;演示表仅按记录编码补充源表缺失字段。验证:47 项 PHP 测试、435 assertions;6 项账户账单脚本测试;真实页面全量及日期筛选验收通过。 --- .../TaobaoAlipayBillSpreadsheetImporter.php | 32 +++-- .../Commands/ImportTaobaoAlipayBills.php | 6 +- .../Http/Controllers/AlipayBills.php | 10 +- ...oreReceivingAlipayBillMirrorRepository.php | 112 ++++++++++++++---- .../components/business/TaobaoAlipayBills.vue | 12 +- .../Console/ImportTaobaoAlipayBillsTest.php | 24 ++++ ...eceivingAlipayBillMirrorRepositoryTest.php | 47 +++++++- .../Unit/View/TaobaoAlipayBillsTableTest.php | 15 +-- 8 files changed, 196 insertions(+), 62 deletions(-) diff --git a/app/Business/TaobaoAlipayBillSpreadsheetImporter.php b/app/Business/TaobaoAlipayBillSpreadsheetImporter.php index c6fad7a1..fb8af9e2 100644 --- a/app/Business/TaobaoAlipayBillSpreadsheetImporter.php +++ b/app/Business/TaobaoAlipayBillSpreadsheetImporter.php @@ -35,7 +35,7 @@ class TaobaoAlipayBillSpreadsheetImporter 'counterparty_account', 'account_order_no', 'business_serial_no', 'goods_name', ]; - public function import(string $path, int $companyId): array + public function import(string $path, int $companyId, bool $replace = false): array { if (! is_file($path) || ! is_readable($path)) { throw new InvalidArgumentException('导入文件不存在或不可读取。'); @@ -55,21 +55,27 @@ class TaobaoAlipayBillSpreadsheetImporter throw new InvalidArgumentException('表头与淘系支付宝账户模板不一致。'); } + $rows = []; + $skipped = 0; + for ($rowNumber = 2; $rowNumber <= $sheet->getHighestDataRow(); $rowNumber++) { + $values = $sheet->rangeToArray("A{$rowNumber}:Y{$rowNumber}", null, false, false)[0]; + if (collect($values)->every(fn ($value) => $value === null || trim((string) $value) === '')) { + $skipped++; + continue; + } + $rows[] = $this->normalizeRow(array_combine(self::FIELDS, $values), $rowNumber); + } + $inserted = 0; $updated = 0; - $skipped = 0; + $replaced = 0; - DB::transaction(function () use ($sheet, $companyId, &$inserted, &$updated, &$skipped) { - for ($rowNumber = 2; $rowNumber <= $sheet->getHighestDataRow(); $rowNumber++) { - $values = $sheet->rangeToArray("A{$rowNumber}:Y{$rowNumber}", null, false, false)[0]; + DB::transaction(function () use ($rows, $companyId, $replace, &$inserted, &$updated, &$replaced) { + if ($replace) { + $replaced = DB::table('ec_alipay_bills')->where('company_id', $companyId)->delete(); + } - if (collect($values)->every(fn ($value) => $value === null || trim((string) $value) === '')) { - $skipped++; - continue; - } - - $data = array_combine(self::FIELDS, $values); - $data = $this->normalizeRow($data, $rowNumber); + foreach ($rows as $data) { $record = AlipayBill::query() ->where('company_id', $companyId) ->where('record_code', $data['record_code']) @@ -93,7 +99,7 @@ class TaobaoAlipayBillSpreadsheetImporter $spreadsheet->disconnectWorksheets(); - return compact('inserted', 'updated', 'skipped'); + return compact('inserted', 'updated', 'skipped', 'replaced'); } private function normalizeRow(array $data, int $rowNumber): array diff --git a/app/Console/Commands/ImportTaobaoAlipayBills.php b/app/Console/Commands/ImportTaobaoAlipayBills.php index 57dae215..3e4b228e 100644 --- a/app/Console/Commands/ImportTaobaoAlipayBills.php +++ b/app/Console/Commands/ImportTaobaoAlipayBills.php @@ -10,7 +10,8 @@ class ImportTaobaoAlipayBills extends Command { protected $signature = 'business:import-taobao-alipay-bills {file : 淘系支付宝账户 Excel 文件路径} - {--company=1 : 数据所属公司 ID}'; + {--company=1 : 数据所属公司 ID} + {--replace : 校验通过后替换该公司的全部淘系支付宝账单}'; protected $description = '从标准附件导入店铺收款账户账单的淘系支付宝账户数据'; @@ -20,6 +21,7 @@ class ImportTaobaoAlipayBills extends Command $result = $importer->import( (string) $this->argument('file'), max(1, (int) $this->option('company')), + (bool) $this->option('replace'), ); } catch (Throwable $e) { $this->components->error($e->getMessage()); @@ -28,7 +30,7 @@ class ImportTaobaoAlipayBills extends Command } $this->components->info( - "导入完成:新增 {$result['inserted']} 条,更新 {$result['updated']} 条,跳过 {$result['skipped']} 条。" + "导入完成:新增 {$result['inserted']} 条,更新 {$result['updated']} 条,替换旧数据 {$result['replaced']} 条,跳过 {$result['skipped']} 条。" ); return self::SUCCESS; diff --git a/modules/EcommerceFinance/Http/Controllers/AlipayBills.php b/modules/EcommerceFinance/Http/Controllers/AlipayBills.php index 34520ed5..ab0e08c1 100644 --- a/modules/EcommerceFinance/Http/Controllers/AlipayBills.php +++ b/modules/EcommerceFinance/Http/Controllers/AlipayBills.php @@ -16,17 +16,17 @@ class AlipayBills extends Controller 'store_name' => '原始店铺', 'custom_remark' => '自定义备注', 'bill_item' => '账单项目', - 'alipay_account' => '平台渠道', + 'alipay_account' => '支付宝账号', 'account_order_no' => '账务订单号', 'business_serial_no' => '业务流水号', 'merchant_order_no' => '商户订单号', 'business_base_order_no' => '业务基础订单号', - 'goods_name' => '商品编码', + 'goods_name' => '商品名称', 'counterparty_account' => '对方账号', 'occurred_at' => '发生时间', 'income' => '收入', 'expense' => '支出', - 'balance' => '收支净额', + 'balance' => '余额', 'business_type' => '业务类型', 'remark' => '备注', 'online_order_no' => '线上单号', @@ -52,7 +52,7 @@ class AlipayBills extends Controller $dir = $request->get('dir') === 'asc' ? 'asc' : 'desc'; $filters = $request->validate([ 'store_name' => ['nullable', 'string', 'max:255'], - 'alipay_account' => ['nullable', 'string', 'max:128'], + 'source_channel' => ['nullable', 'string', 'max:128'], 'bill_item' => ['nullable', 'string', 'max:255'], 'business_type' => ['nullable', 'string', 'max:255'], 'start' => ['nullable', 'date_format:Y-m-d'], @@ -90,7 +90,7 @@ class AlipayBills extends Controller $this->authorizeStoreReceivingBills(); $filters = $request->validate([ 'store_name' => ['nullable', 'string', 'max:255'], - 'alipay_account' => ['nullable', 'string', 'max:128'], + 'source_channel' => ['nullable', 'string', 'max:128'], 'bill_item' => ['nullable', 'string', 'max:255'], 'business_type' => ['nullable', 'string', 'max:255'], 'start' => ['nullable', 'date_format:Y-m-d'], diff --git a/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php b/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php index 6b68c570..eb51b30d 100644 --- a/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php @@ -3,6 +3,7 @@ namespace Modules\EcommerceFinance\Repositories; use Illuminate\Database\Query\Builder; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry; use RuntimeException; @@ -17,6 +18,7 @@ class StoreReceivingAlipayBillMirrorRepository public const DATASET = 'operations_replica.rpt.jst_originalBillInfo'; public const SHOP_DATASET = 'operations_replica.rpt.jst_shopInfo'; + public const SKU_DATASET = 'operations_replica.rpt.jst_sku'; public function __construct( private readonly BusinessMirrorDatasetRegistry $datasets, @@ -58,7 +60,7 @@ class StoreReceivingAlipayBillMirrorRepository $perPage = min(max($perPage, 1), 100); $direction = $direction === 'asc' ? 'asc' : 'desc'; $sortColumn = [ - 'record_code' => in_array($mapping, ['pinduoduo', 'dewu'], true) ? 'b.auto_id' : 'b.Id', + 'record_code' => in_array($mapping, ['pinduoduo', 'dewu'], true) ? 'b.auto_id' : ($mapping === 'taobao' ? 'b.p_auto_id' : 'b.Id'), 'occurred_at' => 'b.fee_time', 'income' => 'b.amount', 'expense' => 'b.amount', @@ -88,8 +90,10 @@ class StoreReceivingAlipayBillMirrorRepository $query, $sortColumn, $direction, $page, $perPage, $filters, $channel, $targetTable, ); $shopNames = $this->shopNames($billRows); + $productNames = $mapping === 'taobao' ? $this->productNames($billRows) : []; + $importedRows = $mapping === 'taobao' ? $this->importedRows($billRows) : []; $rows = collect($billRows) - ->map(fn (object $row): array => $this->mapRowFor($row, $shopNames[(string) $row->shop_id] ?? null, $mapping)) + ->map(fn (object $row): array => $this->mapRowFor($row, $shopNames[(string) $row->shop_id] ?? null, $mapping, $productNames, $importedRows)) ->all(); $income = $summary['income']; $expense = $summary['expense']; @@ -146,7 +150,7 @@ class StoreReceivingAlipayBillMirrorRepository $shopNames = $this->allShopNames(); $direction = $direction === 'desc' ? 'desc' : 'asc'; $sortColumn = [ - 'record_code' => in_array($mapping, ['pinduoduo', 'dewu'], true) ? 'b.auto_id' : 'b.Id', + 'record_code' => in_array($mapping, ['pinduoduo', 'dewu'], true) ? 'b.auto_id' : ($mapping === 'taobao' ? 'b.p_auto_id' : 'b.Id'), 'occurred_at' => 'b.fee_time', 'income' => 'b.amount', 'expense' => 'b.amount', @@ -238,8 +242,10 @@ class StoreReceivingAlipayBillMirrorRepository } $rows = $batch->select('b.*')->orderBy($sortColumn, $direction) ->orderBy('b.Id', $direction)->limit(1000)->get(); + $productNames = $mapping === 'taobao' ? $this->productNames($rows->all()) : []; + $importedRows = $mapping === 'taobao' ? $this->importedRows($rows->all()) : []; foreach ($rows as $row) { - yield $this->mapRowFor($row, $shopNames[(string) $row->shop_id] ?? null, $mapping); + yield $this->mapRowFor($row, $shopNames[(string) $row->shop_id] ?? null, $mapping, $productNames, $importedRows); } $last = $rows->last(); } while ($rows->count() === 1000); @@ -321,7 +327,7 @@ class StoreReceivingAlipayBillMirrorRepository private function dailySummarySupports(array $filters): bool { - return ($filters['alipay_account'] ?? '') === '' + return ($filters['source_channel'] ?? '') === '' && ($filters['bill_item'] ?? '') === '' && ($filters['keyword'] ?? '') === ''; } @@ -377,8 +383,8 @@ class StoreReceivingAlipayBillMirrorRepository $query->whereIn('b.shop_id', $shopIds); } } - if (($filters['alipay_account'] ?? '') !== '') { - $query->where('b.channel', 'like', '%' . $this->escapeLike((string) $filters['alipay_account']) . '%'); + if (($filters['source_channel'] ?? '') !== '') { + $query->where('b.channel', 'like', '%' . $this->escapeLike((string) $filters['source_channel']) . '%'); } if (($filters['bill_item'] ?? '') !== '') { $query->where('b.fee_name', 'like', '%' . $this->escapeLike((string) $filters['bill_item']) . '%'); @@ -488,6 +494,47 @@ class StoreReceivingAlipayBillMirrorRepository ->all(); } + /** @param array $rows @return array */ + private function productNames(array $rows): array + { + $skuIds = collect($rows)->map(fn (object $row): ?string => $row->sku_id ?: $row->shop_sku_id) + ->filter()->unique()->values()->all(); + if ($skuIds === []) return []; + + try { + $dataset = $this->datasets->get(self::SKU_DATASET); + } catch (\Throwable) { + return []; + } + if (! $this->mirror->ready($dataset) || ! Schema::connection($this->mirror->connection()->getName())->hasTable('ops_jst_sku')) { + return []; + } + + return $this->mirror->connection()->table('ops_jst_sku') + ->whereIn('sku_id', $skuIds)->pluck('name', 'sku_id') + ->mapWithKeys(static fn (mixed $name, mixed $id): array => [(string) $id => (string) $name]) + ->all(); + } + + /** @param array $rows @return array */ + private function importedRows(array $rows): array + { + if (! Schema::hasTable('ec_alipay_bills')) { + return []; + } + + $recordCodes = collect($rows)->pluck('p_auto_id')->filter()->unique()->values()->all(); + if ($recordCodes === []) { + return []; + } + + return DB::table('ec_alipay_bills') + ->where('company_id', (int) (company_id() ?: 1)) + ->whereNull('deleted_at') + ->whereIn('record_code', $recordCodes) + ->get()->keyBy(fn (object $row): string => (string) $row->record_code)->all(); + } + /** @param array $rows */ public function mapDouyinRows(array $rows): array { @@ -495,44 +542,63 @@ class StoreReceivingAlipayBillMirrorRepository return array_map(fn (object $row): array => $this->mapRowFor($row, $shops[(string) $row->shop_id] ?? null, 'douyin'), $rows); } - private function mapRow(object $row, ?string $shopName): array + private function mapRow(object $row, ?string $shopName, array $productNames = [], array $importedRows = []): array { $amount = abs((float) ($row->amount ?? 0)); $income = (string) ($row->inout_type ?? '') === '1' ? $amount : 0.0; $expense = (string) ($row->inout_type ?? '') === '-1' ? $amount : 0.0; - return [ + $mapped = [ 'id' => (string) $row->Id, - 'record_code' => (string) $row->Id, + 'record_code' => (string) ($row->p_auto_id ?: $row->Id), 'store_name' => $shopName, 'custom_remark' => null, 'bill_item' => $row->fee_name, - 'alipay_account' => $row->channel, - 'account_order_no' => $row->primary_key, - 'business_serial_no' => $row->auto_id, - 'merchant_order_no' => $row->p_auto_id, - 'business_base_order_no' => $row->so_id, - 'goods_name' => $row->sku_id ?: $row->shop_sku_id, + 'alipay_account' => null, + 'account_order_no' => null, + 'business_serial_no' => null, + 'merchant_order_no' => null, + 'business_base_order_no' => null, + 'goods_name' => $productNames[(string) ($row->sku_id ?: $row->shop_sku_id)] ?? null, 'counterparty_account' => null, 'occurred_at' => $this->dateTime($row->fee_time), 'income' => $income, 'expense' => $expense, - 'balance' => round($income - $expense, 2), - 'business_type' => $this->directionName($row->inout_type), + 'balance' => null, + 'business_type' => null, 'remark' => null, 'online_order_no' => $row->so_id, 'online_real_store' => $shopName, - 'business_description' => $row->combine_type, - 'business_bill_source' => $row->channel, - 'business_order_no' => $row->p_auto_id, + 'business_description' => null, + 'business_bill_source' => null, + 'business_order_no' => null, 'created_at' => $this->dateTime($row->createTime), 'updated_at' => $this->dateTime($row->updateTime), 'modifier' => '', ]; + + $imported = $importedRows[$mapped['record_code']] ?? null; + if ($imported === null) { + return $mapped; + } + + foreach ([ + 'custom_remark', 'alipay_account', 'account_order_no', 'business_serial_no', + 'merchant_order_no', 'business_base_order_no', 'counterparty_account', 'balance', + 'business_type', 'remark', 'business_description', 'business_bill_source', + 'business_order_no', 'modifier', + ] as $field) { + $mapped[$field] = $imported->{$field} ?? $mapped[$field]; + } + if ($mapped['goods_name'] === null || $mapped['goods_name'] === '') { + $mapped['goods_name'] = $imported->goods_name ?? null; + } + + return $mapped; } /** @return array */ - private function mapRowFor(object $row, ?string $shopName, string $mapping): array + private function mapRowFor(object $row, ?string $shopName, string $mapping, array $productNames = [], array $importedRows = []): array { if ($mapping === 'pinduoduo') { return $this->mapPinduoduoRow($row, $shopName); @@ -543,7 +609,7 @@ class StoreReceivingAlipayBillMirrorRepository } if ($mapping !== 'douyin') { - return $this->mapRow($row, $shopName); + return $this->mapRow($row, $shopName, $productNames, $importedRows); } $amount = abs((float) ($row->amount ?? 0)); diff --git a/resources/assets/js/components/business/TaobaoAlipayBills.vue b/resources/assets/js/components/business/TaobaoAlipayBills.vue index d2663e30..98afe930 100644 --- a/resources/assets/js/components/business/TaobaoAlipayBills.vue +++ b/resources/assets/js/components/business/TaobaoAlipayBills.vue @@ -10,7 +10,7 @@
- + @@ -71,8 +71,8 @@ import TableColumnSettings from './TableColumnSettings.vue'; const COLUMNS = [ ['记录编码', 'record_code', 'num', '基础信息'], ['原始店铺', 'store_name', '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', 'num', '账户与交易'], ['对方账号', 'counterparty_account', 'text', '账户与交易'], ['发生时间', 'occurred_at', 'date', '账户与交易'], - ['收入', 'income', 'money_in', '金额(元)'], ['支出', 'expense', 'money_out', '金额(元)'], ['收支净额', 'balance', 'money', '金额(元)'], + ['支付宝账号', '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', '业务关联'], ['业务订单号', 'business_order_no', 'num', '维护信息'], ['创建时间', 'created_at', 'date', '维护信息'], ['修改时间', 'updated_at', 'date', '维护信息'], ['修改人', 'modifier', 'text', '维护信息'], ].map(([label, field, kind, group]) => ({ key: field, label, field, kind, group })); @@ -81,17 +81,17 @@ const SORTABLE = ['occurred_at', 'income', 'expense', 'balance', 'record_code']; export default { name: 'TaobaoAlipayBills', components: { TableColumnSettings }, props: { apiBase: { type: String, required: true }, exportBase: { type: String, required: true }, userKey: { type: [String, Number], required: true } }, - data() { return { loading: false, error: '', rows: [], configuredColumns: [], summary: { income: 0, expense: 0, balance: 0, count: 0 }, total: 0, currentPage: 1, lastPage: 1, perPage: 20, filters: { store_name: '', alipay_account: '', bill_item: '', business_type: '', start: '', end: '', keyword: '' }, sort: { key: 'occurred_at', dir: 'desc' }, exportMode: 'filtered-visible' }; }, + data() { return { loading: false, error: '', rows: [], configuredColumns: [], summary: { income: 0, expense: 0, balance: 0, count: 0 }, total: 0, currentPage: 1, lastPage: 1, perPage: 20, filters: { store_name: '', source_channel: '', bill_item: '', business_type: '', start: '', end: '', keyword: '' }, sort: { key: 'occurred_at', dir: 'desc' }, exportMode: 'filtered-visible' }; }, computed: { defaultColumns() { return COLUMNS; }, visibleColumns() { return this.configuredColumns.filter((column) => column.visible); }, visibleGroups() { return this.visibleColumns.reduce((groups, column) => { const last = groups[groups.length - 1]; if (last && last.label === column.group) last.span += 1; else groups.push({ label: column.group, span: 1 }); return groups; }, []); }, - stats() { return [{ label: '收入合计', value: this.money(this.summary.income), note: `${this.summary.count} 笔交易`, className: 'is-income' }, { label: '支出合计', value: this.money(this.summary.expense), note: '按当前筛选', className: 'is-expense' }, { label: '收支净额', value: this.money(this.summary.balance), note: '收入合计 - 支出合计', className: '' }, { label: '交易笔数', value: this.summary.count, note: '当前筛选结果', className: '' }]; }, + stats() { return [{ label: '收入合计', value: this.money(this.summary.income), note: `${this.summary.count} 笔交易`, className: 'is-income' }, { label: '支出合计', value: this.money(this.summary.expense), note: '按当前筛选', className: 'is-expense' }, { label: '收支差额', value: this.money(this.summary.balance), note: '收入合计 - 支出合计', className: '' }, { label: '交易笔数', value: this.summary.count, note: '当前筛选结果', className: '' }]; }, rangeStart() { return this.total === 0 ? 0 : (this.currentPage - 1) * this.perPage + 1; }, rangeEnd() { return Math.min(this.currentPage * this.perPage, this.total); }, }, mounted() { this.fetch(); }, methods: { async fetch() { this.loading = true; this.error = ''; try { const response = await window.axios.get(this.apiBase, { params: { ...this.filters, sort: this.sort.key, dir: this.sort.dir, limit: this.perPage, page: this.currentPage } }); const body = response.data || {}; this.rows = body.data || []; this.total = body.total || 0; this.currentPage = body.current_page || 1; this.lastPage = body.last_page || 1; this.perPage = body.per_page || this.perPage; this.summary = body.summary || { income: 0, expense: 0, balance: 0, count: 0 }; } catch (error) { this.rows = []; this.total = 0; this.summary = { income: 0, expense: 0, balance: 0, count: 0 }; this.error = (error.response && error.response.data && error.response.data.message) || '淘系支付宝账单暂时无法读取,请稍后重试。'; } finally { this.loading = false; } }, - applyFilters() { this.currentPage = 1; this.fetch(); }, resetFilters() { this.filters = { store_name: '', alipay_account: '', bill_item: '', business_type: '', start: '', end: '', keyword: '' }; this.applyFilters(); }, + applyFilters() { this.currentPage = 1; this.fetch(); }, resetFilters() { this.filters = { store_name: '', source_channel: '', bill_item: '', business_type: '', start: '', end: '', keyword: '' }; this.applyFilters(); }, changePage(page) { this.currentPage = page; this.fetch(); this.$nextTick(() => { if (this.$refs.scroll) this.$refs.scroll.scrollTop = 0; }); }, maybeSort(column) { if (!this.sortableField(column.field)) return; this.sort.dir = this.sort.key === column.field && this.sort.dir === 'desc' ? 'asc' : 'desc'; this.sort.key = column.field; this.applyFilters(); }, sortableField(field) { return SORTABLE.indexOf(field) !== -1; }, sortMark(field) { return this.sort.key === field ? (this.sort.dir === 'asc' ? '↑' : '↓') : '↕'; }, rawValue(row, column) { const value = row[column.field]; return value === null || value === undefined ? '' : value; }, diff --git a/tests/Feature/Console/ImportTaobaoAlipayBillsTest.php b/tests/Feature/Console/ImportTaobaoAlipayBillsTest.php index ddac2684..65ffd9bf 100644 --- a/tests/Feature/Console/ImportTaobaoAlipayBillsTest.php +++ b/tests/Feature/Console/ImportTaobaoAlipayBillsTest.php @@ -64,6 +64,30 @@ class ImportTaobaoAlipayBillsTest extends TestCase ->assertFailed(); } + public function testReplaceValidatesFirstAndAtomicallyRemovesOldRows(): void + { + $old = AlipayBill::query()->create([ + 'company_id' => 1, 'record_code' => 'OLD-ROW', 'store_name' => '旧店铺', + 'bill_item' => '旧账单', 'business_type' => '', 'alipay_account' => '', + 'counterparty_account' => '', 'account_order_no' => '', 'business_serial_no' => '', + 'goods_name' => '', 'income' => 0, 'expense' => 0, 'balance' => 0, + 'platform' => 'taobao', 'created_from' => 'old-data', + ]); + + $this->artisan('business:import-taobao-alipay-bills', [ + 'file' => $this->workbook('错误表头'), '--company' => 1, '--replace' => true, + ])->assertFailed(); + $this->assertDatabaseHas('ec_alipay_bills', ['id' => $old->id, 'record_code' => 'OLD-ROW']); + + $this->artisan('business:import-taobao-alipay-bills', [ + 'file' => $this->workbook(), '--company' => 1, '--replace' => true, + ])->expectsOutputToContain('替换旧数据 1 条')->assertSuccessful(); + + $this->assertSame(1, AlipayBill::query()->where('company_id', 1)->count()); + $this->assertDatabaseMissing('ec_alipay_bills', ['record_code' => 'OLD-ROW']); + $this->assertDatabaseHas('ec_alipay_bills', ['record_code' => '2000000001141269665']); + } + private function workbook(?string $firstHeader = null, string $sheetName = '淘系支付宝账户'): string { $spreadsheet = new Spreadsheet(); diff --git a/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php index b04b6025..f6c4c46a 100644 --- a/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php @@ -90,10 +90,11 @@ class StoreReceivingAlipayBillMirrorRepositoryTest extends TestCase ); $this->assertSame(2, $result['total']); - $this->assertSame('2', $result['data'][0]['record_code']); + $this->assertSame('P-2', $result['data'][0]['record_code']); $this->assertSame('天猫-经营店', $result['data'][0]['store_name']); - $this->assertSame('支出', $result['data'][0]['business_type']); - $this->assertSame('alipayCSV', $result['data'][0]['alipay_account']); + $this->assertNull($result['data'][0]['business_type']); + $this->assertNull($result['data'][0]['alipay_account']); + $this->assertNull($result['data'][0]['balance']); $this->assertSame(100.25, $result['summary']['income']); $this->assertSame(20.10, $result['summary']['expense']); $this->assertSame(80.15, $result['summary']['balance']); @@ -107,6 +108,40 @@ class StoreReceivingAlipayBillMirrorRepositoryTest extends TestCase $this->repository->paginate([], 'occurred_at', 'desc', 1, 10); } + public function test_imported_attachment_rows_enrich_matching_mirror_rows_for_the_taobao_page(): void + { + $this->markReady(); + DB::connection('business_mirror')->table('ops_jst_shop_info')->insert([ + 'Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫-经营店', 'shop_site' => '淘宝天猫', + ]); + DB::connection('business_mirror')->table('ops_jst_original_bill_info')->insert( + $this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25), + ); + DB::table('ec_alipay_bills')->insert([ + 'company_id' => 1, 'record_code' => 'P-1', 'store_name' => '天猫-附件店铺', + 'custom_remark' => '模板数据', 'bill_item' => '交易收款', 'alipay_account' => '20880001', + 'account_order_no' => 'ACCOUNT-1', 'business_serial_no' => 'SERIAL-1', + 'merchant_order_no' => 'MERCHANT-1', 'business_base_order_no' => 'BASE-1', + 'goods_name' => '附件商品', 'counterparty_account' => '买家', + 'occurred_at' => '2026-08-01 09:00:00', 'income' => 100.25, 'expense' => 0, 'balance' => 300.50, + 'business_type' => '交易付款', 'remark' => '备注', 'online_order_no' => 'ONLINE-1', + 'online_real_store' => '天猫-附件店铺', 'business_description' => '业务描述', + 'business_bill_source' => '支付宝', 'business_order_no' => 'BUSINESS-1', + 'modifier' => '导入人', 'platform' => 'taobao', 'created_from' => 'attachment-import', + 'created_at' => '2026-08-02 09:00:00', 'updated_at' => '2026-08-02 10:00:00', + ]); + + $result = $this->repository->paginate([], 'occurred_at', 'desc', 1, 10); + + $this->assertSame(1, $result['total']); + $this->assertSame('P-1', $result['data'][0]['record_code']); + $this->assertSame('天猫-经营店', $result['data'][0]['store_name']); + $this->assertSame('20880001', $result['data'][0]['alipay_account']); + $this->assertSame(300.50, $result['data'][0]['balance']); + $this->assertSame(100.25, $result['summary']['income']); + $this->assertSame('succeeded', $result['sync']['status']); + } + public function test_export_uses_the_same_filters_and_mapping_as_the_page(): void { $this->markReady(); @@ -119,11 +154,11 @@ class StoreReceivingAlipayBillMirrorRepositoryTest extends TestCase ]); $rows = iterator_to_array($this->repository->exportRows([ - 'business_type' => '支出', 'alipay_account' => 'alipayCSV', 'keyword' => 'SKU-2', + 'business_type' => '支出', 'source_channel' => 'alipayCSV', 'keyword' => 'SKU-2', ])); $this->assertCount(1, $rows); - $this->assertSame('2', $rows[0]['record_code']); + $this->assertSame('P-2', $rows[0]['record_code']); $this->assertSame(20.10, $rows[0]['expense']); } @@ -197,7 +232,7 @@ class StoreReceivingAlipayBillMirrorRepositoryTest extends TestCase } foreach (array_chunk($fixtures, 100) as $chunk) $table->insert($chunk); $table->insert($this->row(9999, 7, '2026-08-01 09:00:00', '1', 10, 'OtherChannel')); - foreach (['occurred_at' => 'fee_time', 'income' => 'amount', 'record_code' => 'Id'] as $sort => $column) { + foreach (['occurred_at' => 'fee_time', 'income' => 'amount', 'record_code' => 'p_auto_id'] as $sort => $column) { foreach (['asc', 'desc'] as $direction) { $expected = (clone $table)->where('channel', 'CommonBillTGCPay') ->orderBy($column, $direction)->orderBy('Id', $direction)->pluck('Id')->map('strval')->all(); diff --git a/tests/Unit/View/TaobaoAlipayBillsTableTest.php b/tests/Unit/View/TaobaoAlipayBillsTableTest.php index 62e54c09..87dcc33a 100644 --- a/tests/Unit/View/TaobaoAlipayBillsTableTest.php +++ b/tests/Unit/View/TaobaoAlipayBillsTableTest.php @@ -14,13 +14,14 @@ class TaobaoAlipayBillsTableTest extends TestCase ); $this->assertIsString($source); - $this->assertStringContainsString('基础信息', $source); - $this->assertStringContainsString('assertStringContainsString('v-if="error" class="tb-error"', $source); - $this->assertStringContainsString('e.response.data.message', $source); - $this->assertStringContainsString('', $source); - $this->assertStringContainsString('@click="exportRows">导出', $source); - $this->assertStringContainsString('收支净额', $source); + $this->assertStringContainsString('v-for="(group, index) in visibleGroups"', $source); + $this->assertStringContainsString(':colspan="visibleColumns.length"', $source); + $this->assertStringContainsString('v-if="error" class="tw-alert"', $source); + $this->assertStringContainsString('error.response.data.message', $source); + $this->assertStringContainsString('支付宝账号', $source); + $this->assertStringContainsString('@click="exportRows"', $source); + $this->assertStringContainsString('商品名称', $source); + $this->assertStringContainsString('余额', $source); $this->assertStringNotContainsString('财务备注', $source); $this->assertStringNotContainsString('订单号', $source); $this->assertStringNotContainsString('金额', $source); From 9846f212974b6470ef5be226efcde9402fd2aeff Mon Sep 17 00:00:00 2001 From: renshen <1005457301@qq.com> Date: Sun, 20 Sep 2026 10:05:55 +0800 Subject: [PATCH 03/25] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=BF=90=E8=B4=B9?= =?UTF-8?q?=E5=B7=AE=E5=AF=BC=E5=85=A5=E6=A8=A1=E6=9D=BF=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=20(#209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Business/CourierPayableSummaries.php | 21 +++ .../freight-difference-import-template.xlsx | Bin 0 -> 54410 bytes .../courier-payables/summary-table.blade.php | 125 +++++++++++++++- routes/admin.php | 3 + .../CourierPayableSummaryPagesTest.php | 134 ++++++++++++++++++ 5 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 resources/templates/courier-payables/freight-difference-import-template.xlsx diff --git a/app/Http/Controllers/Business/CourierPayableSummaries.php b/app/Http/Controllers/Business/CourierPayableSummaries.php index 58fc94c0..cd6e88e0 100644 --- a/app/Http/Controllers/Business/CourierPayableSummaries.php +++ b/app/Http/Controllers/Business/CourierPayableSummaries.php @@ -8,6 +8,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use RuntimeException; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\StreamedResponse; class CourierPayableSummaries extends Controller @@ -45,6 +46,26 @@ class CourierPayableSummaries extends Controller ); } + public function template(string $summaryPage): BinaryFileResponse + { + $this->authorizePage($summaryPage); + abort_unless($summaryPage === 'freight-difference', 404); + + $path = resource_path('templates/courier-payables/freight-difference-import-template.xlsx'); + abort_unless(is_file($path), 404); + + $response = response()->download($path, 'freight_difference_import_template.xlsx', [ + 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'Cache-Control' => 'private, no-store', + ]); + + return $response->setContentDisposition( + 'attachment', + '运费差导入模板.xlsx', + 'freight_difference_import_template.xlsx', + ); + } + public function export(string $summaryPage): StreamedResponse|RedirectResponse { $this->authorizePage($summaryPage); diff --git a/resources/templates/courier-payables/freight-difference-import-template.xlsx b/resources/templates/courier-payables/freight-difference-import-template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..aab26937b83d3a1e064d581e180d9088c0b6b089 GIT binary patch literal 54410 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
;AMv?lII<-b9@;PG$2K{ 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 literal 0 HcmV?d00001 diff --git a/resources/views/business/pages/courier-payables/summary-table.blade.php b/resources/views/business/pages/courier-payables/summary-table.blade.php index f68c5588..cbd9fbc3 100644 --- a/resources/views/business/pages/courier-payables/summary-table.blade.php +++ b/resources/views/business/pages/courier-payables/summary-table.blade.php @@ -9,6 +9,12 @@ @endif
+ @if ($courierSummaryPage === 'freight-difference') + + @else @csrf @@ -17,6 +23,7 @@ 导入 + @endif $courierSummaryRows->total() === 0]) @@ -77,6 +84,61 @@ {{ $courierSummaryRows->onEachSide(1)->links() }} @endif + + @if ($courierSummaryPage === 'freight-difference') + +
+ @csrf +
+
+ 导入运费差 + 支持 .xlsx、.xls、.csv,单个文件不超过 10 MB +
+ +
+
+
+ + +
+
+ + @endif + +@if ($courierSummaryPage === 'freight-difference') + +@endif diff --git a/routes/admin.php b/routes/admin.php index 41430275..5e432428 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -200,6 +200,9 @@ Route::prefix('workspace/courier-payables/courier-surcharge') Route::post('workspace/courier-payables/{summaryPage}/summary-import', [CourierPayableSummaries::class, 'import']) ->where('summaryPage', 'freight-difference|other-surcharge-summary') ->name('business.courier-payable-summaries.import'); +Route::get('workspace/courier-payables/{summaryPage}/summary-template', [CourierPayableSummaries::class, 'template']) + ->where('summaryPage', 'freight-difference') + ->name('business.courier-payable-summaries.template'); Route::get('workspace/courier-payables/{summaryPage}/summary-export', [CourierPayableSummaries::class, 'export']) ->where('summaryPage', 'freight-difference|other-surcharge-summary') ->name('business.courier-payable-summaries.export'); diff --git a/tests/Feature/Business/CourierPayableSummaryPagesTest.php b/tests/Feature/Business/CourierPayableSummaryPagesTest.php index b8f27e3b..4e7ebe41 100644 --- a/tests/Feature/Business/CourierPayableSummaryPagesTest.php +++ b/tests/Feature/Business/CourierPayableSummaryPagesTest.php @@ -6,6 +6,8 @@ use App\Business\CourierPayableSummaryStore; use App\Models\Auth\Permission; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; +use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\IOFactory; use Tests\Feature\FeatureTestCase; class CourierPayableSummaryPagesTest extends FeatureTestCase @@ -49,6 +51,134 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase } } + public function test_freight_difference_import_dialog_downloads_the_supplied_xlsx_template(): void + { + $pageResponse = $this->loginAs()->get(route('business.pages.show', [ + 'section' => 'courier-payables', + 'page' => 'freight-difference', + ])); + + $pageResponse + ->assertOk() + ->assertSee('data-courier-summary-import-dialog', false) + ->assertSeeText('下载模板') + ->assertSee(route('business.courier-payable-summaries.template', [ + 'summaryPage' => 'freight-difference', + ]), false); + + $templateResponse = $this->loginAs()->get(route('business.courier-payable-summaries.template', [ + 'summaryPage' => 'freight-difference', + ])); + + $templateResponse + ->assertOk() + ->assertDownload('freight_difference_import_template.xlsx') + ->assertHeader('content-type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + $this->assertStringContainsString( + "filename*=utf-8''" . rawurlencode('运费差导入模板.xlsx'), + (string) $templateResponse->headers->get('Content-Disposition'), + ); + + $spreadsheet = IOFactory::load(resource_path('templates/courier-payables/freight-difference-import-template.xlsx')); + $worksheet = $spreadsheet->getActiveSheet(); + + $this->assertSame([ + '负责人', + '快递公司', + '快递单号', + '店铺', + 'id', + '条码', + '交易单号', + '差异(结算运费-提供成本运费)', + '是否配件', + '配件成品条码', + ], $worksheet->rangeToArray('A1:J1', null, false, false)[0]); + $this->assertSame('FFFFC000', $worksheet->getStyle('D1')->getFill()->getStartColor()->getARGB()); + $this->assertSame('FFFFC000', $worksheet->getStyle('E1')->getFill()->getStartColor()->getARGB()); + $this->assertSame('FFFFC000', $worksheet->getStyle('G1')->getFill()->getStartColor()->getARGB()); + $this->assertSame('@', $worksheet->getStyle('E2')->getNumberFormat()->getFormatCode()); + $this->assertSame('@', $worksheet->getStyle('G2')->getNumberFormat()->getFormatCode()); + $this->assertSame('', (string) $worksheet->getCell('A2')->getValue()); + + $spreadsheet->disconnectWorksheets(); + } + + public function test_other_surcharge_page_does_not_offer_the_freight_difference_template(): void + { + $this->loginAs()->get(route('business.pages.show', [ + 'section' => 'courier-payables', + 'page' => 'other-surcharge-summary', + ])) + ->assertOk() + ->assertDontSeeText('下载模板') + ->assertDontSee('data-courier-summary-import-dialog', false); + } + + public function test_completed_freight_difference_template_preserves_long_identifiers_on_import(): void + { + $spreadsheet = IOFactory::load(resource_path('templates/courier-payables/freight-difference-import-template.xlsx')); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray([ + '苏木', + '测试快递', + 'YT7632543323146', + '测试店铺', + '855226246304', + 'DNZ0117123N1210V01', + '3312228147193110069', + 0.1827, + '成品', + '', + ], null, 'A2'); + $worksheet->getCell('E2')->setValueExplicit('855226246304', DataType::TYPE_STRING); + $worksheet->getCell('G2')->setValueExplicit('3312228147193110069', DataType::TYPE_STRING); + + $path = tempnam(sys_get_temp_dir(), 'freight-template-'); + $this->assertNotFalse($path); + $xlsxPath = $path . '.xlsx'; + + try { + IOFactory::createWriter($spreadsheet, 'Xlsx')->save($xlsxPath); + $spreadsheet->disconnectWorksheets(); + + $file = new UploadedFile( + $xlsxPath, + '运费差导入模板.xlsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + null, + true, + ); + + $this->loginAs()->post(route('business.courier-payable-summaries.import', [ + 'summaryPage' => 'freight-difference', + ]), ['import' => $file]) + ->assertRedirect(route('business.pages.show', [ + 'section' => 'courier-payables', + 'page' => 'freight-difference', + ])) + ->assertSessionHas('success', '导入完成:1 条数据,10 个字段。'); + + $snapshot = app(CourierPayableSummaryStore::class)->snapshot( + (int) $this->company->id, + 'freight-difference', + ); + $this->assertSame('855226246304', $snapshot['rows'][0][4]); + $this->assertSame('3312228147193110069', $snapshot['rows'][0][6]); + $this->assertSame('0.1827', $snapshot['rows'][0][7]); + } finally { + if (isset($spreadsheet)) { + $spreadsheet->disconnectWorksheets(); + } + if ($path !== false && is_file($path)) { + unlink($path); + } + if (isset($xlsxPath) && is_file($xlsxPath)) { + unlink($xlsxPath); + } + } + } + public function test_import_replaces_the_page_snapshot_and_export_returns_all_rows(): void { $file = UploadedFile::fake()->createWithContent( @@ -104,5 +234,9 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase $this->loginAs()->get(route('business.courier-payable-summaries.export', [ 'summaryPage' => 'freight-difference', ]))->assertForbidden(); + + $this->loginAs()->get(route('business.courier-payable-summaries.template', [ + 'summaryPage' => 'freight-difference', + ]))->assertForbidden(); } } From 5947d385eca2a49f31f751d91b16eaa345b45953 Mon Sep 17 00:00:00 2001 From: renshen <1005457301@qq.com> Date: Sun, 20 Sep 2026 11:04:01 +0800 Subject: [PATCH 04/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=BF=90=E8=B4=B9?= =?UTF-8?q?=E5=B7=AE=E6=A8=A1=E6=9D=BF=E6=97=A5=E6=9C=9F=E5=88=97=20(#210)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../freight-difference-import-template.xlsx | Bin 54410 -> 59600 bytes .../courier-payables/summary-table.blade.php | 2 +- .../CourierPayableSummaryPagesTest.php | 26 ++++++++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/resources/templates/courier-payables/freight-difference-import-template.xlsx b/resources/templates/courier-payables/freight-difference-import-template.xlsx index aab26937b83d3a1e064d581e180d9088c0b6b089..6e3df03b2d3af31aad03506516c2c474f95b7910 100644 GIT binary patch literal 59600 zcmeI530#cp_rN1$57}LnEW?#1MM=wK?II1b)J4-a5^b8Osmvg5T&WmJO5@tP);1I^ zBO$2_g-R-EnNpc*T4tK<|IGWKCQbL({r|4(-rwuEoocA5ptdj3H zT24-Gpxnug^R}{_@aiex-LyV(a*E*9%L(i0>b}#~)pe(am$TEcpt&ycQ${R{K5&`Y z*X)-2#;kiQ%60Vm>`yy7H2c@I;Tb`7OLv8TtQkXKGyNlJiMEwLJ=&){dZ2osuR2CP zbZlg+n?;q;-nE9*QD>JiuQa?IcKwDk?PT4?>=1=FG0hQuUpY-uT#rAI@at>NuLbTW z^e-(Bb5?gNO4=-%y4GpAyo&B?)sG*mhixCR``42D{NLq`X5)U0Zo__fmFBuah#z!l z#N^cDN%rdXm`k>CbJms3cNw}^Bi`3OGo!q6(i$mGnZ#Q1g1&NcJAw1Vpr<4cvJ;Uc z@w3=sm1~jaP|@rId#nUc77krh>@vjKI@BsR{pPQ$_504u3_g`z6Rk2$|BlkF?Po3? zo=liF>U~sD)eH0MdyMP+g@FtxB1|s!S{n# zo>O<&uyoVo(b~+}v@ruWnyv2>H|q4PIj3h~RrFX~1 zYEEDA^5-*$e=(eXB^2klbV+H{#a|+>jaF-?jbb08rHNKh-}6(&mv|9j`BpopXiZmS zC;Z6zIHXWBVEc%=n|6J7yozwFSYF?-XRL{ZMxw>_%2b>9D?GP{ANv48!_Lk2ggb;F6yQkd?&YQLACD!HE*I#@% zGI{&5=FEEs`00m4BQAK@>gxD%6{SP0eM=upd3iaxHz2;0V2F8aCpr_c$e(j1ftIqV z$UR(hv|!9~_Ap%%=b-(l3>TGL$EZxjC#uHBMkuccJj-CKy7UbwpSPz@VU;bja2YG+ zL4E>1Vte%AU0a@yIK64W^AX2>9iKO^-{#}KoV5C!`4LBlR{nHB(`1T;{Tg4faOkbQ zW;5yOBkUJDRo#1bVQ6JAVa%kZZF(yPhb&2p9vt#vuAjbXUPN_>O``Ykx&56>rZ3Vv ze;{oFyecpw&sv6tGWDMQ!iLBJZzsIoYuzS4?drsP z7xoo4J&O+!${l$+@G{mev6h!wbbqtyx?mNB#{IPuOP2Vtt?%XT*wFV!?_+JVSGXOt zFRg5FP21RiOx)8o)>n@t3|ncvUT5Ysubi0H!+EAZDK9W-m~8wosBd#0hrpVozJY60 zra0Fu9I&-`=-v5uPy0?&GJQhxnp%B))vjdK1y(kTE0!+5eW}2H$b*Rwlr@PllQ;`E zJiWa-ZHe{1%q_W#G{?L;Q#&7T7(sIu#UHM{th-r%A8Gh9&osBu^z9QW)GM$4GRiY< zI5YD3ix6v6zx#c})>(>a>-{Qcm1XZpsvJ3OrpAzsnCeq4>eGjB zGxxOLyJshU=3J(Az?tO7Cr@12R(r2u=(3F$y+k(C7imtp`1b0m(UvMjeSQ;$-I>ix zsro3&Qr8JMs_-O!Fa~>@HG9MMzUjQa6R*7u%X(3rZn$*C0kyfYxvN#~{vQ8BvcS?q zZ`Pf@u~F(JjyvOsRO=u5W@t~A-#)kM{H%Rr?Oxwza)swN``gpf-emeHIv)xx4>2D( zyK?BIHrDO;yYDT`+SNjNdH?q1y4T^9QCK&l0Z;E<|qO%m+ivC&1;NBWu>rhK=W2(A?p_$}F%zy?IxZsSh0 zH}`ONba6nkbN{rpgVR-q%00jKlgQz%(c|DZk;~rP8a!hxZCDVaEyUTKgq!$t(loDj zxj=ii$*I|C`Oh*G?(7a*`|54QjEN2hZ1Tk zZu&oUdlp^fKoC63Z^KfWF95?8%~Yt( zytY(P8E{b%C(ayKD&(r{<+bD!L|%eg@Zqa`I#u|{6DK-L-^(lYZKMG;&s@c?J(Web zTH0F}wDuf-u0M`n%?El};tCUjm_-m#d>hkCg=yUM=u&|>MHF7jZ!#%OF7qLXsxPRB z-43=jn&3nNR;i%0R9x1^5T$wID0+JZFBB~LtGQ9bf`L5z3K}|j(Mc7`OKy6R(;@59-WeD5j#m*(7 z_G<|YQL(5Q4@Tj|F0SIbO|D|6RG}|#bF#3KmcY=Z3yMWFY8bWAN;FnnwwIMp^<(g6 zQ+-7(ta5H)C`0^$nM!`aEu!2@P9ckg;wnZ^>7;0$0#?GOR1hn{8=WdD)D`Elg{@d| z1yx+hXwNUlb5j^RQkA}lhGBEB;e`3rmMC#8K~PAqS>1+lZGS}Ng4Je7JZ6e>u%ZU> z3x1$0wUohCp)~r7&DgUU-lcjBHd9m)Ej}$CUQ6>7H1NH5`;09TH!}p4VgbG-(5s0l zK8oX)Qg~P~mxrlgj^cWXTgOpFOi?*qoJJ7waiT0LA46?X5miTvn#DpNh9Di+epJk+ z3My3uFR(Gf#O;q%gep`aQ~Zb^;EOl2`3)6&shmK9C{0E9 zK1e)kG547+b(V_AB~n>5I5L%I5-8r(c9tNXqf~OpKW5;ym_bVlsE0U=HSz;xD6b^%|%AK<)a28AELt)$z5b zR5VK43!@e3!mCV;@*Ku>%;(x(=UwSvlt5|C z_h*WvMln7}6k36~sO=4fxL2Krfp@?T-}W1HVLTWY*4akMJ1bsUJeUqkH`tSnZ?3uG z`C-wVmn)rLo@>?JYW{8~?s~hC|D~M_O@*4X-fM>3UN>p}@50sQ!@cYMHlt_Jmd!1DhX+-<3WEAn+;2BA{Qc0+SB_15DY`sM;q`rF zSU*BRzs^&w){7Z+6U>ycZb7QXp&u-UTv52aX7=s1es4plh*b+b3-XLr8*WLI4*^` z-#beA<>DJJmk--P&1*h2wH18GYAC>D;6~`h;MXbc_-*5k3j=O59G~vr>;9@uPn}@g zylI4R`cmq!7lftFn??$WOR1Az5PoXjG)j2sp(~pfm0?7-rC%(OGR6oohF`%ARB*SZ zU+kFjq&&akDx2h0N5izP_#nl;j&?1TSr&_rEWmW}8jpD0(=@L&do_(%`K=w@ARfPt z-D!bSCuMv#J8bWX&YU`ocIeh<8(!5dhJL*(p3lz{0eDO5UDxt`UW#ViW~{1rHK+Q} z4REcQjq%1$_5;P8!tIO8g|b}UCW~w@O_Xr$ZW0mej55FCkhH(eN@kxevy!vj^cWh01WqO@1SmkU0}>n53&b;lK4^Is{ey#G1APD%6rg6PU)#)fjjwMOvMgI@(`u2vz(s7GKh3;TcE1Tph={wH|QQm4&FQGLULM zUD9^aygLweKZym!ric++Pbh3)NAS+mSPmNZjn17Xjzn`+{te z5DnQcf%(iXSI(DU=S`4leiv;Wf#lG=`M|b-?rf86tn}D!!vd^@w#>D7+->lwE6!~* z&lZj+;WFMTYPHd_g)>RGthb8VZF$+k1)O3|cD}+H9QiztW&&O=^JxCy^caC3^E3TjJph)*(@2P*^C#=^XM|uGGx$lFlc#~ zL5Gn+!C=tIu7g$~gG|97(=LOmkwFb$P;=KoYY7v4=B*0a8<)+oKf}t@VbXR*VrmWW zRpiJzj=_X#a@=!{{dtF^7x97329`Bstvrr>6f1KLlXfW*V{VAQ;~g2$VqjTI*2<5s ziLJF}(jG-(Xoh$lpU8k#1Iv1{RuRWOftBgTq*X>@@(h#eBX=jWGQF8JUL=NZh#%k^ zsm?VBYaqv!aqJ(mGJj>#CeVumnF}j8_L;2AlT6wiI>ycj|JpY)n`cnSA!}80>|d}l zFEU;7945Dr<34cgU$ZiAGHEt+OqLP;^4>@%zQKexa@-VWYUJ3L zvofDBX{YEIZ5)0NC32|Hz_OjJ)e>LSZka)eye>2d96zG|P2k1;k&^?BmSK|B|u>_=O2a(<{X(!&n9 zmvJ+|G(@>}w<<>}>cUr?SJfOm_b;iZ=4~|-nS_e9MyT57(NX@U?yFCjZxUS zx6K8_dE2*SKQw%uw5ZDVX4SUgBzlDQuAt&Ng%ox9#pSj$KE{@e&>BaYuS+`3uZTF} zQ@3qZHF1K{#Bo5XB{H}pp5NN(CN0Kh#XL_ueZ=Q_!l5r#I=oj-Dje@^x%`*Mk4)d% zrD)r0RPAu7(rnArQ)(;7DS4iywP2pcw)e`lIa5e;H<7gc@8X|@C1$wX@wWc$YrelKtv!w#`Qx2~Z9{dm)rq4cB6`iPTy+DFc^Gpi73*E{TCl#uG!e8vC8# zX?|X%lSiV0M@^+&%J3eR>8q-wWKo@lVH>JX1zrWBqVmqLk7 zj!3Bbtde#~1Gu!ReBF#lE3FP^0TOdt`?zL{Xw5KoRl1Ze!^@$>V5lqHR7#hVc8Yqr zDa7L-jVB7=*#Yrvq)FikUyaS$o`>M^441~^>TNk70^%u?#uJj4rBwy-7-FRG>^W-{ zViN_UM_(FGBZwXYh=(POCnYMB*xvw!rF6AMS%vhu3Q@0-*0ro9E?_Oh6C{mi>(x-=X^5w2y%Zj&^(pF8H$XgBr13-p zJl7$fZKtI06m7s}dA)+U=bAL0Sde?JLp(%jJeOZ(Y3+b`w$7Gz>4sIv{%gBtDdD$- zeR0+-%nyG7^EHU?9p`j73y?TXUp`}1P|MSh&buhGKNW!sH390nE3K;)=t_lngma`& z?@6=@nQ{|G&n{^^nIL-HA)ZiaJbQ1363;OLt3|Gh1s>MLb|fhhcn7XeyP(PAv~7ascVuwzZgqgEvQJ&&=|Mi% zJA8;3j)M`D}<`G>mbw^dho$|J<4kV}d0e z6Os9jU=~;cNw0^GRTPm4m)FNiYI*Cv@kFzfI-d`o9*h|o zM~2bLl#W)z=)5=<41PnbRPeVZn`K49z-#L^@Q~7xBOS-zcp|V9fCc|@8hiTC#|rKQ ztgm;1e}Fqk)!5qwlEi-Ymt$v?X)@~+8U|<>pkaV!0}PrC_PYPCg~2n)t{r@&QvpoQ zd5F{o|Ck!D*JUbO+Y%IVm%!df>?mxB?+v3LKC!QfE#YWMHD*C0%g2f(yNU~ySuR@1 zEEmZ#%f)Xy(bI#S>tC(1Yq)91i3n87o%)K%iHNfh3yu%nS8>TQ%SDdNauEqUNo(-y za`Br^v~C7i@QzakI4NvG`b2E<*L|Ys@86w2wLl>Du^mUbSxRg&JQ$;4V1b4KXzQcd z01X2)48YBU|B*1rlI+^{ww>DJR@#k7ZSZ&X@p@h3^|lq+$TyYr{Um#zRG*wV8|jmS zBBg7eJTjjsvx*CqSuSps-CdLvrN|x8ueyuhbfQqLDV^%yC{ykSMcG|44Vg{9d+IXF z#WJ^bg_<4Fk#D)Bn6@ zj)noV7R?69Xc+t(!{8p+wTFbA?)30XzNrAI4gO2_YX9;Yuh+Mm1x7SUAt&hxcif;h z2Mr_m6b)#abbZQ>wR@|~DsH#Taxq_axhT84_)RCeWcs92{X0u3Rr1(?tOHT4%% z+2x{!%yLl@c-?Q`-*%$jbjj|0|L$~KX8*wg^{QF=ZCB^dxYK>J^bg_<4Fm9OWiNW> zXc%zNY+x9ThQYrv4A4gk;Wr_Aex$HicGGXO%yQ9LX1SOkvt0bP6ZPtm!aFjnxFVV5 zqKoWuQ4)CFZ{Oc^qF%K}zJGVRFSGw(fqK;?`=+b&r`+kjyV!HQp<$4ph=xJ$nx$wq zVAi79Ao<@H23C@u)fA;py|Jb7qt`r96VUk9<&LDKi{)i!`&LcYw}^?N2Q zvO5s6JB2B-J5I72kI^yFfq)(dm_{8|9TOC_N>ejiu zXk#!R?wq>e;y&(-k+-bVe4=w>lUa|risg#sXo_(IOpLuQF~ zWaiOEYl8{c$Kj246ZOji_ZD#4qw2_&i!pB{W4$yZ1ICr-aJRfY>y}|uM;4m5CcFQp z^wFY(V{0%T|M+O*HeZd8K#xjmvS^l%)!pZVJk{Ccxyk{U@<(3UM&mqz9z`E3@kztz zE!7A0L)wzZD?X+*+9pq+A5BvFSm19MO??`~Rar0xJ!%kNq0$lgGP0X1)J-P`}lNSxb*&ojMxP5IL^1d!z?GR>S;g|9Z!V zfc0nsu6JG6AHf>jA5@MDz;&>EHHa!M(pi1&=3KKfu2eS&^mM9uk=`|7P+P*lGGyqvP!fEhZts(V~;yH zu5_-ta|W@=^M{(PL~47fBL35XC`tC&EG0|!IqDxXObv5btl8;Ep^M=PUDMxP^pKn? zMd?xp2joQhukc#NX&dnk4Uta$07)FHVI)nBPA5pv1K5OyNT&lonnlnM>FC*^S)^-M z5zQi9+PY|nbPW-)K!y0cmZ5Zgpm?fas_#JhgV2UK0$=j&sbcXSGm0qijo7T!&vLc_ zMbqg}hT`QL4cxRuVUr5(bi!F0ogv@Gpb4$t4-QfZ*izQ$7bcPO0ROj>;$;_B1&duE zWZUCTCp{=v@Z01iux^sbm0-nZ)p+$^fVy052X z>UWZ21*z0HD61ec z$v7x;;5w&e+t55WC{qt*I7A5_YYCrV z@kr4~ql5UeRot#Lz#pZ zkk19kXYmQ=E!PG!EPeJoEp^P(>;dn^+tU>sk@4_eQ z0~~k*4zz{?bKt=E-H^`~=vOk7p+K2INsv!0^vfQ~WI>sTt&q4D3id3d`cmozL5x@kp_s2z89h=ssXw= z43ET*DwJ6bW#YXgd;$?; zWtKph04vC+2J*>+e4?Ptx@Z_bEs#$w9@!GYR) zA)lAfuj^383d&@rLO%7-ueVSp3Cj2qARjK|(+K&LLz${KOn)d?ABte-dF?3T1&67Ka@EK-|=+WLM;nR;R z$v=bFbcvrnaDN}O1_?Y(IM5jm30aJe+D5kC8MAoOaaV42Vng4M`YN85t+!bb+Guk{Cypz2m0SsbQt7{kU4wa>0_nU8055gE zI4=Cl7UUnr?p@SJ#j2$^5VW1o^KDc4et%0ae{by4@Wm?GM?kI#=5;>3gJksmC4#?! zoge)k?zEEd2}qVf4O4sv6CNZqa4L&~>J3a@e8usY! zh3;NGW?*#p`tvv0(57>b?9gL7dTj4`^X}=Ch!#FQvXeBOB{!7OrnAiEI(qi%apy;y z&gkydW8)Ovy?V^RJ+ec0ub$bVP3ImTi9?U==&`-W%^N+o_qciY$PP4}8EDfPZ94au zfqQ(Q8-3EbM|SA$h3;NGW?*#p`t!RN+H~%b9olq8kL^7lJ<+CfkDGT-g^#4^ya{bO z_w>*<+H~%5=SQ2)=Kb_i4Cn@xadCvk)|h8Q`B7$z|#lcK(+&vdjJXJn(PEy=8)LzyEK1_*XYJ z#8ey4gsb(tL)Qw{FCK2ZdD+ziA1f>g{S?(~&y2;b%N^I;Q2DFJ9M+cdj3iI7f|ASl z&35eeIqehpemN`!VSq5rQev-6G7DPyhK{eJKUMCt}jo;OT z>CC&hL~WrpSS)$LAi)Gdf58|*y% z=>J_U;P^bgq}t*+iY9YAZ{Gw;yRc0ci!;Hoop8m>999(nE>GZr)1}itvOpb&Z{vX4 z<^@x~2;WBH@MbI%{;o@wIYfKooyvg*F7r#~Gsj(%4YDpQ^b)6Ui?fHRQFJN!H5 zJM6pYcdjaeRzG3d_hsofUkenC%%Tzv zyaRsvQu|6uWRAYLB8txg8dH<5`X#?q_;zv^)=#Y!#sC}sesC}sYsr+{dDs_tgpcXia zM&{8FMT4S|I=g?mrS~`78bWi8%o~a*8WfGxt@i(@v5cpPs&q3%@1h;JeY&*%@Hj8A zFei&CX0r&)R+@0A5w4K=j>Fl^&7=&V)9W0P!5=J!h)xx7R49VRta5)WuC9en;nrkO zd1aw=5tl#}RpBTCVJNk|RhQb5O@^>Fyi_dUgtnE7Dcn+=xQWdW)zQV{)~u56H(E~e>*U6HTle1mb^FXda&nU2 zL%`n=+=)&ktcK)Ib`rw?=jH6Ax`*gaa&&buRGX`zsisPFv2(R|ba60L+wHM!&H^=@ zk-<8mlZ^+EAZ>SaBdLNRE+j*>?H(R(3$a*|-FBk04N1e*jpzbC+2-o*Y~ummx;tRq zZ0vU0I1sT~nwmOTFk^=fHEEry>&b4!|3stfwr!4f#HFrwyPb(H9^EL$N=vAzuD5Y_ zAbJ?8VZEHNp04gYZCzb=Y5+UcR9Dy=s;#r1YonuSV>eIBmS{ImXP&K^D%QvVE9I}z zXP^TSpvxCp8jl1poBGPhO$0!JUdXhwc7*Tk6RXYAHAgIuwk6}8bRFNXU!7!=ey6$b zW95vsLZw2Diw<^a#}oUWS3RjZ*6Ci@Bi-D*a6CuUey14!?7YKa$APKo*UydLAG&B* zD!W0iRzI*UslLzkCrcVKhuSTs#T)G|s4#FDxFUC-(v^WzxtW%e&nxMTnbK-Mx@nrX z>g<&}N3n(`+n+Bd9u$x8d+m3SugVwh#toMqAAV~(bCO$3^WEhHTF&*+xp|4Jiw9U$f+QG7TP2HhgSJRQa9Q*y^3fep>LV7fNRqp= zuivlK#m3s!sSVi4ZwBgR&h*QdM*EEyv|Ew0bb*pN#z zW<&+JYH|V)92N`9Bk%~w{FaT z-Y>pwpZcsB&xf_h)t${Y_jYbn^Ls^q)-O3-l$g9E<=DL0$_hVa>Z~!Sd8qhi z$()zfqZ`xuWNPbXKc7Co?VXKxK1Sim=|^!XX3q^saRp~nIG(NRg7bs-@TOidFk_3g zv$N@bzx%3%Bf;y%{U6|9k_Xv|NRq5Z*P-oO`dG??#d`zBNfz+)uZ#OvgTDpydVtJ8gn!QM zPQftcyVXQ6iUfYcj0II(+`oSSqJXsB#+_(y?%@v7QMV>UobI>faFLvx?b(5H<3G<2 zUN7z+ukgin(sm-zgM>w1y4cZuj{L0X;{Mm~r7~t$0$p=K$3RR4GQEt<&{a>@OQA!L aTV=Nw#+p_A0U=2M43v`xtCvtEdG~)cEUAM4 literal 54410 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
;AMv?lII<-b9@;PG$2K{ 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 diff --git a/resources/views/business/pages/courier-payables/summary-table.blade.php b/resources/views/business/pages/courier-payables/summary-table.blade.php index cbd9fbc3..26d7b94c 100644 --- a/resources/views/business/pages/courier-payables/summary-table.blade.php +++ b/resources/views/business/pages/courier-payables/summary-table.blade.php @@ -108,7 +108,7 @@
运费差导入模板 - .xlsx · 10 个字段 + .xlsx · 11 个字段
diff --git a/tests/Feature/Business/CourierPayableSummaryPagesTest.php b/tests/Feature/Business/CourierPayableSummaryPagesTest.php index 4e7ebe41..4a7c6c34 100644 --- a/tests/Feature/Business/CourierPayableSummaryPagesTest.php +++ b/tests/Feature/Business/CourierPayableSummaryPagesTest.php @@ -83,6 +83,7 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase $worksheet = $spreadsheet->getActiveSheet(); $this->assertSame([ + '日期', '负责人', '快递公司', '快递单号', @@ -93,12 +94,13 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase '差异(结算运费-提供成本运费)', '是否配件', '配件成品条码', - ], $worksheet->rangeToArray('A1:J1', null, false, false)[0]); - $this->assertSame('FFFFC000', $worksheet->getStyle('D1')->getFill()->getStartColor()->getARGB()); + ], $worksheet->rangeToArray('A1:K1', null, false, false)[0]); $this->assertSame('FFFFC000', $worksheet->getStyle('E1')->getFill()->getStartColor()->getARGB()); - $this->assertSame('FFFFC000', $worksheet->getStyle('G1')->getFill()->getStartColor()->getARGB()); - $this->assertSame('@', $worksheet->getStyle('E2')->getNumberFormat()->getFormatCode()); - $this->assertSame('@', $worksheet->getStyle('G2')->getNumberFormat()->getFormatCode()); + $this->assertSame('FFFFC000', $worksheet->getStyle('F1')->getFill()->getStartColor()->getARGB()); + $this->assertSame('FFFFC000', $worksheet->getStyle('H1')->getFill()->getStartColor()->getARGB()); + $this->assertSame('@', $worksheet->getStyle('A2')->getNumberFormat()->getFormatCode()); + $this->assertSame('@', $worksheet->getStyle('F2')->getNumberFormat()->getFormatCode()); + $this->assertSame('@', $worksheet->getStyle('H2')->getNumberFormat()->getFormatCode()); $this->assertSame('', (string) $worksheet->getCell('A2')->getValue()); $spreadsheet->disconnectWorksheets(); @@ -120,6 +122,7 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase $spreadsheet = IOFactory::load(resource_path('templates/courier-payables/freight-difference-import-template.xlsx')); $worksheet = $spreadsheet->getActiveSheet(); $worksheet->fromArray([ + '2026-07', '苏木', '测试快递', 'YT7632543323146', @@ -131,8 +134,8 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase '成品', '', ], null, 'A2'); - $worksheet->getCell('E2')->setValueExplicit('855226246304', DataType::TYPE_STRING); - $worksheet->getCell('G2')->setValueExplicit('3312228147193110069', DataType::TYPE_STRING); + $worksheet->getCell('F2')->setValueExplicit('855226246304', DataType::TYPE_STRING); + $worksheet->getCell('H2')->setValueExplicit('3312228147193110069', DataType::TYPE_STRING); $path = tempnam(sys_get_temp_dir(), 'freight-template-'); $this->assertNotFalse($path); @@ -157,15 +160,16 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase 'section' => 'courier-payables', 'page' => 'freight-difference', ])) - ->assertSessionHas('success', '导入完成:1 条数据,10 个字段。'); + ->assertSessionHas('success', '导入完成:1 条数据,11 个字段。'); $snapshot = app(CourierPayableSummaryStore::class)->snapshot( (int) $this->company->id, 'freight-difference', ); - $this->assertSame('855226246304', $snapshot['rows'][0][4]); - $this->assertSame('3312228147193110069', $snapshot['rows'][0][6]); - $this->assertSame('0.1827', $snapshot['rows'][0][7]); + $this->assertSame('2026-07', $snapshot['rows'][0][0]); + $this->assertSame('855226246304', $snapshot['rows'][0][5]); + $this->assertSame('3312228147193110069', $snapshot['rows'][0][7]); + $this->assertSame('0.1827', $snapshot['rows'][0][8]); } finally { if (isset($spreadsheet)) { $spreadsheet->disconnectWorksheets(); From 56949d6d296b3c7a16763e284ec08f623b71b242 Mon Sep 17 00:00:00 2001 From: renshen <1005457301@qq.com> Date: Sun, 20 Sep 2026 11:39:50 +0800 Subject: [PATCH 05/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E9=99=84=E5=8A=A0=E8=B4=B9=E5=AF=BC=E5=85=A5=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=20(#211)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 实际改动 - 为“其他附加费汇总”增加与“运费差”一致的导入弹窗和模板下载入口。 - 新增空白 Excel 模板,字段为日期、负责人、快递公司、快递单号、商品ID、条码、店铺、交易单号、金额、备注、成品条码。 - 日期及编号列按文本保存,金额列保留数值格式;模板不包含用户提供文件中的真实业务数据。 - 扩展模板下载路由与控制器映射,并补充权限、格式、长编号和实际导入回归测试。 ## 修改文件 - app/Http/Controllers/Business/CourierPayableSummaries.php - resources/templates/courier-payables/other-surcharge-import-template.xlsx - resources/views/business/pages/courier-payables/summary-table.blade.php - routes/admin.php - tests/Feature/Business/CourierPayableSummaryPagesTest.php ## 验证结果 - CourierPayableSummaryPagesTest:8 passed,88 assertions。 - Blade 编译:php artisan view:cache 通过。 - git diff --check 通过。 - 模板已核对为空白,且未提交真实财务数据。 ## 风险检查 - 涉及导入和页面权限,已增加对应回归测试。 - 不涉及数据库迁移、生产配置或数据修复。 - 前端复用现有 VIP 黑金组件样式,符合 DESIGN.md。 Reviewed-on: http://git.1080p.hk/WuYongJie/CaiWuXiTong/pulls/211 --- .../Business/CourierPayableSummaries.php | 24 +++- .../other-surcharge-import-template.xlsx | Bin 0 -> 59254 bytes .../courier-payables/summary-table.blade.php | 38 ++--- routes/admin.php | 2 +- .../CourierPayableSummaryPagesTest.php | 130 +++++++++++++++++- 5 files changed, 167 insertions(+), 27 deletions(-) create mode 100644 resources/templates/courier-payables/other-surcharge-import-template.xlsx diff --git a/app/Http/Controllers/Business/CourierPayableSummaries.php b/app/Http/Controllers/Business/CourierPayableSummaries.php index cd6e88e0..610f4d14 100644 --- a/app/Http/Controllers/Business/CourierPayableSummaries.php +++ b/app/Http/Controllers/Business/CourierPayableSummaries.php @@ -13,6 +13,19 @@ use Symfony\Component\HttpFoundation\StreamedResponse; class CourierPayableSummaries extends Controller { + private const IMPORT_TEMPLATES = [ + 'freight-difference' => [ + 'path' => 'templates/courier-payables/freight-difference-import-template.xlsx', + 'ascii_name' => 'freight_difference_import_template.xlsx', + 'download_name' => '运费差导入模板.xlsx', + ], + 'other-surcharge-summary' => [ + 'path' => 'templates/courier-payables/other-surcharge-import-template.xlsx', + 'ascii_name' => 'other_surcharge_import_template.xlsx', + 'download_name' => '其他附加费导入模板.xlsx', + ], + ]; + public function __construct(private readonly CourierPayableSummaryStore $store) { $this->middleware('auth'); @@ -49,20 +62,21 @@ class CourierPayableSummaries extends Controller public function template(string $summaryPage): BinaryFileResponse { $this->authorizePage($summaryPage); - abort_unless($summaryPage === 'freight-difference', 404); + $template = self::IMPORT_TEMPLATES[$summaryPage] ?? null; + abort_unless(is_array($template), 404); - $path = resource_path('templates/courier-payables/freight-difference-import-template.xlsx'); + $path = resource_path($template['path']); abort_unless(is_file($path), 404); - $response = response()->download($path, 'freight_difference_import_template.xlsx', [ + $response = response()->download($path, $template['ascii_name'], [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'Cache-Control' => 'private, no-store', ]); return $response->setContentDisposition( 'attachment', - '运费差导入模板.xlsx', - 'freight_difference_import_template.xlsx', + $template['download_name'], + $template['ascii_name'], ); } diff --git a/resources/templates/courier-payables/other-surcharge-import-template.xlsx b/resources/templates/courier-payables/other-surcharge-import-template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..728aecd7999af2982bcc435bf2a81b29e0f0c0f4 GIT binary patch literal 59254 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 #&Fo{^;^3!RTeJ2!TKY}+v|V&(#`zQ(IT_S~ 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 literal 0 HcmV?d00001 diff --git a/resources/views/business/pages/courier-payables/summary-table.blade.php b/resources/views/business/pages/courier-payables/summary-table.blade.php index 26d7b94c..ee28f64b 100644 --- a/resources/views/business/pages/courier-payables/summary-table.blade.php +++ b/resources/views/business/pages/courier-payables/summary-table.blade.php @@ -1,3 +1,18 @@ +@php + $courierSummaryImportTemplate = [ + 'freight-difference' => [ + 'dialog_title' => '导入运费差', + 'template_name' => '运费差导入模板', + 'field_count' => 11, + ], + 'other-surcharge-summary' => [ + 'dialog_title' => '导入其他附加费汇总', + 'template_name' => '其他附加费导入模板', + 'field_count' => 11, + ], + ][$courierSummaryPage] ?? null; +@endphp +
@@ -9,20 +24,11 @@ @endif
- @if ($courierSummaryPage === 'freight-difference') + @if ($courierSummaryImportTemplate) - @else -
- @csrf - - -
@endif
@endif - @if ($courierSummaryPage === 'freight-difference') + @if ($courierSummaryImportTemplate)
- 导入运费差 + {{ $courierSummaryImportTemplate['dialog_title'] }} 支持 .xlsx、.xls、.csv,单个文件不超过 10 MB