From 01de726227d6fde8aaf27c808054a7f8c3ec42a1 Mon Sep 17 00:00:00 2001 From: zhoushengchen <3057083327@qq.com> Date: Wed, 23 Sep 2026 11:09:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BF=AB=E9=80=92=E9=99=84=E5=8A=A0=E8=B4=B9?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E6=94=AF=E6=8C=81=E4=BE=9B=E5=BA=94=E5=95=86?= =?UTF-8?q?=E3=80=81=E6=97=A5=E5=BF=97=E7=AD=9B=E9=80=89=E4=B8=8E=E5=88=97?= =?UTF-8?q?=E5=AE=BD=E8=B0=83=E6=95=B4=20(#246)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CourierSurchargeAuditLogRepository.php | 3 + .../CourierSurchargeQuoteRepository.php | 21 ++- .../CourierSurchargeQuoteWorkbookImporter.php | 14 +- .../Business/CourierSurchargeQuotes.php | 2 + app/Http/Controllers/Business/Pages.php | 1 + app/Models/Business/CourierSurchargeQuote.php | 1 + ...lier_to_courier_surcharge_quotes_table.php | 32 ++++ .../courier-surcharge.blade.php | 156 ++++++++++++++++-- .../Business/CourierSurchargePageTest.php | 108 ++++++++++-- 9 files changed, 301 insertions(+), 37 deletions(-) create mode 100644 database/migrations/2026_09_22_010000_add_supplier_to_courier_surcharge_quotes_table.php diff --git a/app/Business/CourierSurchargeAuditLogRepository.php b/app/Business/CourierSurchargeAuditLogRepository.php index f6140b4b..eb3acdd5 100644 --- a/app/Business/CourierSurchargeAuditLogRepository.php +++ b/app/Business/CourierSurchargeAuditLogRepository.php @@ -20,6 +20,7 @@ class CourierSurchargeAuditLogRepository 'courier' => '快递名称', 'scope' => '快递归属', 'reconciler' => '对账人', + 'supplier' => '供应商', 'types' => '附加费类型', 'mode' => '结算方式', 'standard' => '计价标准', @@ -98,6 +99,8 @@ class CourierSurchargeAuditLogRepository 'actor_name' => $log->actor?->name ?: '系统', 'action' => $log->action, 'action_label' => self::ACTION_LABELS[$log->action] ?? $log->action, + 'target_type' => $log->target_type, + 'target_id' => $log->target_id, 'target_type_label' => self::TARGET_LABELS[$log->target_type] ?? $log->target_type, 'target_label' => $log->target_label, 'changes_label' => $this->changesLabel($log), diff --git a/app/Business/CourierSurchargeQuoteRepository.php b/app/Business/CourierSurchargeQuoteRepository.php index 8f43052b..bd084c20 100644 --- a/app/Business/CourierSurchargeQuoteRepository.php +++ b/app/Business/CourierSurchargeQuoteRepository.php @@ -40,7 +40,11 @@ class CourierSurchargeQuoteRepository public function ready(): bool { try { - return Schema::hasTable((new CourierSurchargeQuote())->getTable()) && $this->types->ready(); + $table = (new CourierSurchargeQuote())->getTable(); + + return Schema::hasTable($table) + && Schema::hasColumn($table, 'supplier') + && $this->types->ready(); } catch (QueryException) { return false; } @@ -63,8 +67,7 @@ class CourierSurchargeQuoteRepository try { $all = CourierSurchargeQuote::query() - ->orderByDesc('effective_at') - ->orderByDesc('id') + ->orderBy('id') ->get(); } catch (QueryException) { return [ @@ -76,7 +79,7 @@ class CourierSurchargeQuoteRepository } $filtered = $all->filter(function (CourierSurchargeQuote $quote) use ($filters): bool { - foreach (['courier', 'scope', 'reconciler', 'mode'] as $key) { + foreach (['courier', 'scope', 'reconciler', 'supplier', 'mode'] as $key) { if (($filters[$key] ?? '') !== '' && (string) $quote->{$key} !== $filters[$key]) { return false; } @@ -96,6 +99,7 @@ class CourierSurchargeQuoteRepository $quote->courier, $quote->scope, $quote->reconciler, + (string) $quote->supplier, $quote->typeLabel(), $quote->mode, $quote->standard, @@ -139,6 +143,7 @@ class CourierSurchargeQuoteRepository 'modes' => collect(self::MODES), 'scopes' => self::SCOPES, 'reconcilers' => $all->pluck('reconciler')->filter()->unique()->sort()->values(), + 'suppliers' => $all->pluck('supplier')->filter()->unique()->sort()->values(), 'statuses' => ['已启用', '已停用'], ], ]; @@ -168,6 +173,9 @@ class CourierSurchargeQuoteRepository ->where('condition', trim((string) $data['condition'])) ->whereDate('effective_at', (string) $data['effective_at']); + $supplier = trim((string) ($data['supplier'] ?? '')); + $supplier === '' ? $query->whereNull('supplier') : $query->where('supplier', $supplier); + if ($lockForUpdate) { $query->lockForUpdate(); } @@ -186,6 +194,7 @@ class CourierSurchargeQuoteRepository trim((string) ($data['courier'] ?? '')), trim((string) ($data['scope'] ?? '')), trim((string) ($data['reconciler'] ?? '')), + trim((string) ($data['supplier'] ?? '')), $this->normalizedTypes((array) ($data['types'] ?? [])), trim((string) ($data['mode'] ?? '')), trim((string) ($data['effective_at'] ?? '')), @@ -279,6 +288,7 @@ class CourierSurchargeQuoteRepository 'courier' => $quote->courier, 'scope' => $quote->scope, 'reconciler' => $quote->reconciler, + 'supplier' => $quote->supplier, 'types' => array_values((array) $quote->types), 'mode' => $quote->mode, 'standard' => $quote->standard, @@ -302,6 +312,7 @@ class CourierSurchargeQuoteRepository 'courier' => trim((string) $data['courier']), 'scope' => trim((string) $data['scope']), 'reconciler' => trim((string) $data['reconciler']), + 'supplier' => trim((string) ($data['supplier'] ?? '')) ?: null, 'types' => array_values(array_unique(array_map('strval', $data['types']))), 'mode' => trim((string) $data['mode']), 'standard' => trim((string) $data['standard']), @@ -333,6 +344,7 @@ class CourierSurchargeQuoteRepository 'courier' => $quote->courier, 'scope' => $quote->scope, 'reconciler' => $quote->reconciler, + 'supplier' => $quote->supplier, 'types' => array_values((array) $quote->types), 'type' => $quote->typeLabel(), 'mode' => $quote->mode, @@ -356,6 +368,7 @@ class CourierSurchargeQuoteRepository 'modes' => collect(self::MODES), 'scopes' => self::SCOPES, 'reconcilers' => collect(), + 'suppliers' => collect(), 'statuses' => ['已启用', '已停用'], ]; } diff --git a/app/Business/CourierSurchargeQuoteWorkbookImporter.php b/app/Business/CourierSurchargeQuoteWorkbookImporter.php index 22ecff3a..a630cc1f 100644 --- a/app/Business/CourierSurchargeQuoteWorkbookImporter.php +++ b/app/Business/CourierSurchargeQuoteWorkbookImporter.php @@ -29,6 +29,7 @@ class CourierSurchargeQuoteWorkbookImporter '快递名称', '快递归属', '对账人', + '供应商', '附加费类型', '结算方式', '计价标准', @@ -64,7 +65,7 @@ class CourierSurchargeQuoteWorkbookImporter $sheet->freezePane('A2'); $sheet->setAutoFilter($headerRange); - $widths = [18, 14, 14, 24, 18, 30, 32, 14, 12, 30]; + $widths = [18, 14, 14, 20, 24, 18, 30, 32, 14, 12, 30]; foreach ($widths as $index => $width) { $column = Coordinate::stringFromColumnIndex($index + 1); $sheet->getColumnDimension($column)->setWidth($width); @@ -78,6 +79,7 @@ class CourierSurchargeQuoteWorkbookImporter $instructions->fromArray([ ['项目', '填写要求'], ['必填字段', '快递名称、快递归属、对账人、附加费类型、结算方式、计价标准、生效条件、生效日期、状态。'], + ['供应商', '选填;没有供应商时保持空白。'], ['附加费类型', '多种类型使用“、”分隔;只能填写当前已启用类型。当前可选:' . implode('、', $types)], ['快递归属', '自有快递 或 代发仓。'], ['结算方式', implode('、', CourierSurchargeQuoteRepository::MODES)], @@ -92,8 +94,8 @@ class CourierSurchargeQuoteWorkbookImporter ]); $instructions->getColumnDimension('A')->setWidth(18); $instructions->getColumnDimension('B')->setWidth(90); - $instructions->getStyle('A1:B9')->getAlignment()->setVertical(Alignment::VERTICAL_CENTER)->setWrapText(true); - foreach (range(1, 9) as $row) { + $instructions->getStyle('A1:B10')->getAlignment()->setVertical(Alignment::VERTICAL_CENTER)->setWrapText(true); + foreach (range(1, 10) as $row) { $instructions->getRowDimension($row)->setRowHeight($row === 1 ? 24 : 34); } @@ -144,7 +146,7 @@ class CourierSurchargeQuoteWorkbookImporter } if ($headers !== self::HEADERS) { throw ValidationException::withMessages([ - 'import' => '表头不正确。请下载最新模板,并保持 10 个字段名称及顺序不变。', + 'import' => '表头不正确。请下载最新模板,并保持 11 个字段名称及顺序不变。', ]); } @@ -273,7 +275,7 @@ class CourierSurchargeQuoteWorkbookImporter } $errors = []; - foreach (array_slice(self::HEADERS, 0, 9) as $header) { + foreach (['快递名称', '快递归属', '对账人', '附加费类型', '结算方式', '计价标准', '生效条件', '生效日期', '状态'] as $header) { if ($display[$header] === '') { $errors[] = $header . '不能为空。'; } @@ -281,6 +283,7 @@ class CourierSurchargeQuoteWorkbookImporter $this->checkLength($display, '快递名称', 191, $errors); $this->checkLength($display, '对账人', 100, $errors); + $this->checkLength($display, '供应商', 191, $errors); $this->checkLength($display, '计价标准', 2000, $errors); $this->checkLength($display, '生效条件', 2000, $errors); $this->checkLength($display, '备注', 2000, $errors); @@ -333,6 +336,7 @@ class CourierSurchargeQuoteWorkbookImporter 'courier' => $display['快递名称'], 'scope' => $display['快递归属'], 'reconciler' => $display['对账人'], + 'supplier' => $display['供应商'] !== '' ? $display['供应商'] : null, 'types' => $types, 'mode' => $display['结算方式'], 'standard' => $display['计价标准'], diff --git a/app/Http/Controllers/Business/CourierSurchargeQuotes.php b/app/Http/Controllers/Business/CourierSurchargeQuotes.php index 5825c7f6..3364bf36 100644 --- a/app/Http/Controllers/Business/CourierSurchargeQuotes.php +++ b/app/Http/Controllers/Business/CourierSurchargeQuotes.php @@ -134,6 +134,7 @@ class CourierSurchargeQuotes extends Controller 'courier' => ['required', 'string', 'max:191'], 'scope' => ['required', 'in:' . implode(',', CourierSurchargeQuoteRepository::SCOPES)], 'reconciler' => ['required', 'string', 'max:100'], + 'supplier' => ['nullable', 'string', 'max:191'], 'types' => ['required', 'array', 'min:1', 'max:50'], 'types.*' => ['string', 'in:' . implode(',', $this->quotes->activeTypeNames())], 'mode' => ['required', 'in:' . implode(',', CourierSurchargeQuoteRepository::MODES)], @@ -145,6 +146,7 @@ class CourierSurchargeQuotes extends Controller $data['courier'] = trim($data['courier']); $data['reconciler'] = trim($data['reconciler']); + $data['supplier'] = trim((string) ($data['supplier'] ?? '')) ?: null; $data['types'] = array_values(array_unique($data['types'])); return $data; diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index d8e77296..c3a51bd6 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -1128,6 +1128,7 @@ class Pages extends Controller 'mode' => $clean(request('mode', ''), 40), 'scope' => $clean(request('scope', ''), 20), 'reconciler' => $clean(request('reconciler', ''), 40), + 'supplier' => $clean(request('supplier', ''), 191), 'status' => $clean(request('status', ''), 20), ]; $data = $this->courierSurchargeQuotes->pageData($filters); diff --git a/app/Models/Business/CourierSurchargeQuote.php b/app/Models/Business/CourierSurchargeQuote.php index 8cf53c08..5f3ff814 100644 --- a/app/Models/Business/CourierSurchargeQuote.php +++ b/app/Models/Business/CourierSurchargeQuote.php @@ -17,6 +17,7 @@ class CourierSurchargeQuote extends Model 'courier', 'scope', 'reconciler', + 'supplier', 'types', 'mode', 'standard', diff --git a/database/migrations/2026_09_22_010000_add_supplier_to_courier_surcharge_quotes_table.php b/database/migrations/2026_09_22_010000_add_supplier_to_courier_surcharge_quotes_table.php new file mode 100644 index 00000000..0ba54e23 --- /dev/null +++ b/database/migrations/2026_09_22_010000_add_supplier_to_courier_surcharge_quotes_table.php @@ -0,0 +1,32 @@ +string('supplier', 191)->nullable()->after('reconciler'); + }); + } + + public function down(): void + { + if (! Schema::hasTable('business_courier_surcharge_quotes') + || ! Schema::hasColumn('business_courier_surcharge_quotes', 'supplier')) { + return; + } + + Schema::table('business_courier_surcharge_quotes', function (Blueprint $table): void { + $table->dropColumn('supplier'); + }); + } +}; diff --git a/resources/views/business/pages/courier-payables/courier-surcharge.blade.php b/resources/views/business/pages/courier-payables/courier-surcharge.blade.php index 5b72ec04..bfc82ce6 100644 --- a/resources/views/business/pages/courier-payables/courier-surcharge.blade.php +++ b/resources/views/business/pages/courier-payables/courier-surcharge.blade.php @@ -17,6 +17,7 @@ ['key' => 'courier', 'label' => '快递名称', 'exportable' => true], ['key' => 'scope', 'label' => '快递归属', 'exportable' => true], ['key' => 'reconciler', 'label' => '对账人', 'exportable' => true], + ['key' => 'supplier', 'label' => '供应商', 'exportable' => true], ['key' => 'type', 'label' => '附加费类型', 'exportable' => true], ['key' => 'mode', 'label' => '结算方式', 'exportable' => true], ['key' => 'standard', 'label' => '计价标准', 'exportable' => true], @@ -62,6 +63,16 @@ .sm-audit-table td:nth-child(5) { min-width:24rem; white-space:normal; line-height:1.5; } .sm-audit-action { display:inline-flex; align-items:center; padding:.15rem .45rem; border-radius:999px; background:var(--sm-soft); color:var(--sm-accent); font-size:.7rem; font-weight:700; } .sm-audit-empty { padding:1.8rem 1rem; text-align:center; color:var(--sm-muted); } + .sm-audit-empty[hidden],.sm-audit-table-wrap[hidden],.sm-audit-table tr[hidden] { display:none; } + .sm-table tr[data-surcharge-row] { cursor:pointer; } + .sm-table tr[data-surcharge-row]:focus-visible { outline:2px solid var(--sm-accent); outline-offset:-2px; } + .sm-table tr.is-log-selected td,.sm-table tr.is-log-selected:hover td,.sm-table tr.is-log-selected [data-column-key="id"],.sm-table tr.is-log-selected [data-column-key="actions"] { background:var(--sm-soft); } + .sm-table td { overflow:hidden; text-overflow:ellipsis; } + .sm-table th .sm-table__resize { position:absolute; top:0; right:-.3rem; z-index:7; width:.6rem; height:100%; cursor:col-resize; touch-action:none; } + .sm-table th .sm-table__resize::after { position:absolute; top:20%; bottom:20%; left:calc(50% - .5px); width:1px; background:var(--sm-border); content:""; } + .sm-table th .sm-table__resize:hover::after,.sm-table th .sm-table__resize:focus-visible::after { width:2px; background:var(--sm-accent); } + .sm-table th .sm-table__resize:focus-visible { outline:2px solid var(--sm-accent); outline-offset:-2px; } + .sm-table.is-resizing,.sm-table.is-resizing * { cursor:col-resize !important; user-select:none; } .sm-column-picker { position:relative; } .sm-column-picker__panel { position:absolute; right:0; z-index:20; display:grid; width:min(22rem,calc(100vw - 2rem)); gap:.65rem; margin-top:.35rem; padding:.8rem; border:1px solid var(--sm-border); border-radius:.55rem; background:#fff; box-shadow:0 4px 8px rgba(23,23,23,.1); } .sm-column-picker__panel[hidden] { display:none; } .sm-column-picker__intro { display:grid; gap:.2rem; color:var(--sm-muted); font-size:.7rem; line-height:1.45; } .sm-column-picker__intro strong { color:var(--sm-ink); font-size:.78rem; } .sm-visually-hidden { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; } .sm-column-picker__list { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.35rem; max-height:15rem; overflow:auto; } .sm-column-picker__option { display:flex; align-items:center; gap:.35rem; min-width:0; padding:.35rem .4rem; border:1px solid transparent; border-radius:.35rem; color:var(--sm-ink); font-size:.7rem; cursor:grab; } .sm-column-picker__option:hover,.sm-column-picker__option.is-drop-target { border-color:var(--sm-border); background:#fffdf8; } .sm-column-picker__option.is-fixed { cursor:default; } .sm-column-picker__option.is-dragging { opacity:.55; } .sm-column-picker__option input { flex:none; accent-color:var(--sm-accent); } .sm-column-picker__option .material-icons-outlined { color:var(--sm-muted); font-size:.95rem; } .sm-column-picker__foot { display:flex; justify-content:flex-end; padding-top:.35rem; border-top:1px solid var(--sm-border); } .sm-badge { display:inline-flex; align-items:center; padding:.15rem .5rem; border-radius:999px; font-size:.7rem; font-weight:700; } .sm-badge.is-active { background:#edf5e8; color:#53793e; } .sm-badge.is-inactive { background:#fff4df; color:#935f07; } .sm-empty { padding:3rem 1rem; text-align:center; color:var(--sm-muted); } .sm-rule-note { padding:1rem 1.15rem; color:var(--sm-muted); font-size:.75rem; line-height:1.65; background:#fffdf8; } @@ -114,10 +125,11 @@
筛选报价规则
-
+
+
@@ -139,33 +151,33 @@
当前筛选结果(全部):{{ $rows->count() }} 条 · 共 {{ $rows->isEmpty() ? 0 : 1 }} 页规则编号与操作列固定显示;列设置仅保存于当前用户
@foreach ($columns as $column) - + @endforeach @forelse ($rows as $row) - - + + @empty - + @endforelse
@if (empty($column['fixed']))@endif{{ $column['label'] }}@if (empty($column['fixed']))@endif{{ $column['label'] }}
{{ $row['id'] }}{{ $row['courier'] }}{{ $row['scope'] }}{{ $row['reconciler'] }}{{ $row['type'] }}{{ $row['mode'] }}{{ $row['standard'] }}{{ $row['condition'] }}{{ $row['effective_at'] }}{{ $row['status'] }}{{ $row['remark'] ?: '—' }}
@csrf
{{ $row['id'] }}{{ $row['courier'] }}{{ $row['scope'] }}{{ $row['reconciler'] }}{{ $row['supplier'] ?: '—' }}{{ $row['type'] }}{{ $row['mode'] }}{{ $row['standard'] }}{{ $row['condition'] }}{{ $row['effective_at'] }}{{ $row['status'] }}{{ $row['remark'] ?: '—' }}
@csrf
没有符合条件的报价规则,请点击“新增报价规则”。
没有符合条件的报价规则,请点击“新增报价规则”。
维护建议:固定单价、按重量、按体积、按尺寸/最长边等方式直接维护标准;聚工单客服核实、取发出运费和按账单等方式只维护触发条件,具体金额由后续结算链路读取业务凭证。批量导入必须先预览校验,已有规则需明确选择跳过或更新。
-
-
操作日志
记录当前公司最近 50 条新增、更改、启用和停用操作
+
+
操作日志
请选择报价
@if (!$auditReady)
操作日志表尚未就绪,完成迁移后将从首次新操作开始记录。
- @elseif ($auditLogs->isEmpty()) -
暂无操作日志。新增、编辑或切换报价规则/附加费类型状态后,记录会显示在这里。
@else -
+
请选择上方一条报价
+ +