diff --git a/app/Business/CourierSurchargeAuditLogRepository.php b/app/Business/CourierSurchargeAuditLogRepository.php new file mode 100644 index 00000000..f6140b4b --- /dev/null +++ b/app/Business/CourierSurchargeAuditLogRepository.php @@ -0,0 +1,148 @@ + */ + private const FIELD_LABELS = [ + 'courier' => '快递名称', + 'scope' => '快递归属', + 'reconciler' => '对账人', + 'types' => '附加费类型', + 'mode' => '结算方式', + 'standard' => '计价标准', + 'condition' => '生效条件', + 'effective_at' => '生效日期', + 'remark' => '备注', + 'status' => '状态', + 'name' => '类型名称', + ]; + + /** @var array */ + private const ACTION_LABELS = [ + CourierSurchargeAuditLog::ACTION_CREATED => '新增', + CourierSurchargeAuditLog::ACTION_UPDATED => '更改', + CourierSurchargeAuditLog::ACTION_ACTIVATED => '启用', + CourierSurchargeAuditLog::ACTION_DEACTIVATED => '停用', + ]; + + /** @var array */ + private const TARGET_LABELS = [ + CourierSurchargeAuditLog::TARGET_QUOTE => '报价规则', + CourierSurchargeAuditLog::TARGET_TYPE => '附加费类型', + ]; + + public function ready(): bool + { + try { + return Schema::hasTable((new CourierSurchargeAuditLog())->getTable()); + } catch (QueryException) { + return false; + } + } + + /** @param array $changes */ + public function record( + string $action, + string $targetType, + ?int $targetId, + string $targetLabel, + array $changes, + int $actorId, + ): CourierSurchargeAuditLog { + abort_unless($this->ready(), 503, '附加费操作日志数据表尚未完成迁移。'); + abort_unless(array_key_exists($action, self::ACTION_LABELS), 422, '附加费日志动作不合法。'); + abort_unless(array_key_exists($targetType, self::TARGET_LABELS), 422, '附加费日志对象不合法。'); + + return CourierSurchargeAuditLog::query()->create([ + 'company_id' => company_id(), + 'actor_id' => $actorId ?: null, + 'action' => $action, + 'target_type' => $targetType, + 'target_id' => $targetId, + 'target_label' => mb_substr(trim($targetLabel), 0, 191), + 'changes' => $changes, + ]); + } + + /** + * @return Collection> + */ + public function recent(int $limit = 50): Collection + { + if (! $this->ready()) { + return collect(); + } + + try { + return CourierSurchargeAuditLog::query() + ->with('actor') + ->orderByDesc('id') + ->limit(max(1, min($limit, 200))) + ->get() + ->map(fn (CourierSurchargeAuditLog $log): array => [ + 'id' => $log->id, + 'occurred_at' => optional($log->created_at)->format('Y-m-d H:i:s') ?: '', + 'actor_name' => $log->actor?->name ?: '系统', + 'action' => $log->action, + 'action_label' => self::ACTION_LABELS[$log->action] ?? $log->action, + 'target_type_label' => self::TARGET_LABELS[$log->target_type] ?? $log->target_type, + 'target_label' => $log->target_label, + 'changes_label' => $this->changesLabel($log), + ]) + ->values(); + } catch (QueryException) { + return collect(); + } + } + + private function changesLabel(CourierSurchargeAuditLog $log): string + { + $changes = (array) $log->changes; + if ($log->action === CourierSurchargeAuditLog::ACTION_CREATED) { + return '新增记录'; + } + + if (in_array($log->action, [CourierSurchargeAuditLog::ACTION_ACTIVATED, CourierSurchargeAuditLog::ACTION_DEACTIVATED], true)) { + $before = data_get($changes, 'before.status'); + $after = data_get($changes, 'after.status'); + return sprintf('状态:%s → %s', $before ?: '—', $after ?: '—'); + } + + $before = (array) ($changes['before'] ?? []); + $after = (array) ($changes['after'] ?? []); + $parts = []; + foreach (array_unique(array_merge(array_keys($before), array_keys($after))) as $field) { + $old = $this->valueLabel($before[$field] ?? null); + $new = $this->valueLabel($after[$field] ?? null); + if ($old !== $new) { + $parts[] = sprintf('%s:%s → %s', self::FIELD_LABELS[$field] ?? $field, $old, $new); + } + } + + return $parts === [] ? '未改变字段' : implode(';', $parts); + } + + private function valueLabel(mixed $value): string + { + if (is_array($value)) { + return implode('、', array_map(static fn (mixed $item): string => (string) $item, $value)); + } + + $value = trim((string) $value); + + return $value === '' ? '—' : $value; + } +} diff --git a/app/Business/CourierSurchargeQuoteRepository.php b/app/Business/CourierSurchargeQuoteRepository.php index 8cc35a10..e5512285 100644 --- a/app/Business/CourierSurchargeQuoteRepository.php +++ b/app/Business/CourierSurchargeQuoteRepository.php @@ -2,10 +2,12 @@ namespace App\Business; +use App\Models\Business\CourierSurchargeAuditLog; use App\Models\Business\CourierSurchargeQuote; use App\Models\Business\CourierSurchargeType; use Illuminate\Database\QueryException; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; /** @@ -28,7 +30,10 @@ class CourierSurchargeQuoteRepository public const SCOPES = ['自有快递', '代发仓']; - public function __construct(private readonly CourierSurchargeTypeRepository $types) + public function __construct( + private readonly CourierSurchargeTypeRepository $types, + private readonly CourierSurchargeAuditLogRepository $audits, + ) { } @@ -154,27 +159,102 @@ class CourierSurchargeQuoteRepository /** @param array $data */ public function create(array $data, int $userId): CourierSurchargeQuote { - return CourierSurchargeQuote::query()->create($this->attributes($data, $userId) + [ - 'status' => CourierSurchargeQuote::STATUS_ACTIVE, - 'created_by' => $userId, - ]); + return DB::transaction(function () use ($data, $userId): CourierSurchargeQuote { + $quote = CourierSurchargeQuote::query()->create($this->attributes($data, $userId) + [ + 'status' => CourierSurchargeQuote::STATUS_ACTIVE, + 'created_by' => $userId, + ]); + + $this->audits->record( + CourierSurchargeAuditLog::ACTION_CREATED, + CourierSurchargeAuditLog::TARGET_QUOTE, + (int) $quote->id, + $this->targetLabel($quote), + ['after' => $this->snapshot($quote)], + $userId, + ); + + return $quote; + }); } /** @param array $data */ public function update(CourierSurchargeQuote $quote, array $data, int $userId): CourierSurchargeQuote { - $quote->update($this->attributes($data, $userId)); + return DB::transaction(function () use ($quote, $data, $userId): CourierSurchargeQuote { + $before = $this->snapshot($quote); + $quote->update($this->attributes($data, $userId)); + $fresh = $quote->fresh(); - return $quote->fresh(); + $this->audits->record( + CourierSurchargeAuditLog::ACTION_UPDATED, + CourierSurchargeAuditLog::TARGET_QUOTE, + (int) $fresh->id, + $this->targetLabel($fresh), + ['before' => $before, 'after' => $this->snapshot($fresh)], + $userId, + ); + + return $fresh; + }); } public function setStatus(CourierSurchargeQuote $quote, string $status, int $userId): CourierSurchargeQuote { abort_unless(in_array($status, [CourierSurchargeQuote::STATUS_ACTIVE, CourierSurchargeQuote::STATUS_INACTIVE], true), 422, '报价规则状态不合法。'); - $quote->forceFill(['status' => $status, 'updated_by' => $userId])->save(); + return DB::transaction(function () use ($quote, $status, $userId): CourierSurchargeQuote { + $oldStatus = $quote->status; + $quote->forceFill(['status' => $status, 'updated_by' => $userId])->save(); + $fresh = $quote->fresh(); - return $quote->fresh(); + if ($oldStatus !== $status) { + $this->audits->record( + $status === CourierSurchargeQuote::STATUS_ACTIVE + ? CourierSurchargeAuditLog::ACTION_ACTIVATED + : CourierSurchargeAuditLog::ACTION_DEACTIVATED, + CourierSurchargeAuditLog::TARGET_QUOTE, + (int) $fresh->id, + $this->targetLabel($fresh), + [ + 'before' => ['status' => $this->statusLabel($oldStatus)], + 'after' => ['status' => $this->statusLabel($status)], + ], + $userId, + ); + } + + return $fresh; + }); + } + + private function targetLabel(CourierSurchargeQuote $quote): string + { + $type = $quote->typeLabel(); + + return trim(sprintf('%s%s', $quote->courier, $type === '' ? '' : ' · ' . $type)); + } + + /** @return array */ + private function snapshot(CourierSurchargeQuote $quote): array + { + return [ + 'courier' => $quote->courier, + 'scope' => $quote->scope, + 'reconciler' => $quote->reconciler, + 'types' => array_values((array) $quote->types), + 'mode' => $quote->mode, + 'standard' => $quote->standard, + 'condition' => $quote->condition, + 'effective_at' => optional($quote->effective_at)->format('Y-m-d'), + 'status' => $this->statusLabel($quote->status), + 'remark' => $quote->remark, + ]; + } + + private function statusLabel(?string $status): string + { + return $status === CourierSurchargeQuote::STATUS_ACTIVE ? '已启用' : '已停用'; } /** @param array $data */ diff --git a/app/Business/CourierSurchargeTypeRepository.php b/app/Business/CourierSurchargeTypeRepository.php index 674cdee2..66791f73 100644 --- a/app/Business/CourierSurchargeTypeRepository.php +++ b/app/Business/CourierSurchargeTypeRepository.php @@ -2,10 +2,12 @@ namespace App\Business; +use App\Models\Business\CourierSurchargeAuditLog; use App\Models\Business\CourierSurchargeQuote; use App\Models\Business\CourierSurchargeType; use Illuminate\Database\QueryException; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Illuminate\Validation\ValidationException; @@ -17,6 +19,10 @@ use Illuminate\Validation\ValidationException; */ class CourierSurchargeTypeRepository { + public function __construct(private readonly CourierSurchargeAuditLogRepository $audits) + { + } + /** @var list */ public const DEFAULT_NAMES = [ '退回费', '拦截费', '改地址', '异形件', '超长费', @@ -89,14 +95,27 @@ class CourierSurchargeTypeRepository $sortOrder = ((int) CourierSurchargeType::query()->max('sort_order')) + 1; - return CourierSurchargeType::query()->create([ - 'company_id' => company_id(), - 'name' => $name, - 'status' => CourierSurchargeType::STATUS_ACTIVE, - 'sort_order' => $sortOrder, - 'created_by' => $userId, - 'updated_by' => $userId, - ]); + return DB::transaction(function () use ($name, $sortOrder, $userId): CourierSurchargeType { + $type = CourierSurchargeType::query()->create([ + 'company_id' => company_id(), + 'name' => $name, + 'status' => CourierSurchargeType::STATUS_ACTIVE, + 'sort_order' => $sortOrder, + 'created_by' => $userId, + 'updated_by' => $userId, + ]); + + $this->audits->record( + CourierSurchargeAuditLog::ACTION_CREATED, + CourierSurchargeAuditLog::TARGET_TYPE, + (int) $type->id, + $type->name, + ['after' => ['name' => $type->name, 'status' => '已启用']], + $userId, + ); + + return $type; + }); } catch (QueryException $exception) { if ((int) $exception->getCode() === 23000) { throw ValidationException::withMessages(['name' => '当前公司已有同名附加费类型。']); @@ -110,12 +129,37 @@ class CourierSurchargeTypeRepository { abort_unless(in_array($status, [CourierSurchargeType::STATUS_ACTIVE, CourierSurchargeType::STATUS_INACTIVE], true), 422, '附加费类型状态不合法。'); - $type->forceFill([ - 'status' => $status, - 'updated_by' => $userId, - ])->save(); + return DB::transaction(function () use ($type, $status, $userId): CourierSurchargeType { + $oldStatus = $type->status; + $type->forceFill([ + 'status' => $status, + 'updated_by' => $userId, + ])->save(); + $fresh = $type->fresh(); - return $type->fresh(); + if ($oldStatus !== $status) { + $this->audits->record( + $status === CourierSurchargeType::STATUS_ACTIVE + ? CourierSurchargeAuditLog::ACTION_ACTIVATED + : CourierSurchargeAuditLog::ACTION_DEACTIVATED, + CourierSurchargeAuditLog::TARGET_TYPE, + (int) $fresh->id, + $fresh->name, + [ + 'before' => ['status' => $this->statusLabel($oldStatus)], + 'after' => ['status' => $this->statusLabel($status)], + ], + $userId, + ); + } + + return $fresh; + }); + } + + private function statusLabel(?string $status): string + { + return $status === CourierSurchargeType::STATUS_ACTIVE ? '已启用' : '已停用'; } private function ensureDefaults(int $companyId): void diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 74626c89..b6199a3a 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -16,6 +16,7 @@ use App\Models\Business\CourierQuoteVersion; use App\Models\Business\CourierSigningSubject; use App\Models\Business\InstallationDetail; use App\Models\Business\StoreReceivingAccount; +use App\Business\CourierSurchargeAuditLogRepository; use App\Business\CourierSurchargeQuoteRepository; use App\Business\CourierSurchargeTypeRepository; use App\Models\StoreReceivables\StoreZhongcaiReconciliation; @@ -95,6 +96,7 @@ class Pages extends Controller private readonly UpstreamQueryCacheFactory $upstreamCaches, private readonly CourierSurchargeQuoteRepository $courierSurchargeQuotes, private readonly CourierSurchargeTypeRepository $courierSurchargeTypes, + private readonly CourierSurchargeAuditLogRepository $courierSurchargeAudits, private readonly JushuitanShopBalanceRepository $jushuitanShopBalances, ) { $this->middleware('auth'); @@ -982,6 +984,7 @@ class Pages extends Controller 'status' => $clean(request('status', ''), 20), ]; $data = $this->courierSurchargeQuotes->pageData($filters); + $auditReady = $this->courierSurchargeAudits->ready(); return [ 'view' => 'business.pages.courier-payables.courier-surcharge', @@ -989,8 +992,11 @@ class Pages extends Controller 'courierSurchargeFilters' => $filters, 'courierSurchargeSummary' => $data['summary'], 'courierSurchargeOptions' => $data['options'], - 'courierSurchargeReady' => $data['ready'], + 'courierSurchargeReady' => $data['ready'] && $auditReady, + 'courierSurchargeDataReady' => $data['ready'], + 'courierSurchargeAuditReady' => $auditReady, 'courierSurchargeTypeUsage' => $this->courierSurchargeTypes->usageCounts($data['options']['type_records'] ?? collect()), + 'courierSurchargeAuditLogs' => $this->courierSurchargeAudits->recent(), ]; } diff --git a/app/Models/Business/CourierSurchargeAuditLog.php b/app/Models/Business/CourierSurchargeAuditLog.php new file mode 100644 index 00000000..70657901 --- /dev/null +++ b/app/Models/Business/CourierSurchargeAuditLog.php @@ -0,0 +1,44 @@ + 'integer', + 'actor_id' => 'integer', + 'target_id' => 'integer', + 'changes' => 'array', + ]; + + public function actor() + { + return $this->belongsTo(user_model_class(), 'actor_id'); + } +} diff --git a/database/migrations/2026_09_21_000000_create_courier_surcharge_audit_logs_table.php b/database/migrations/2026_09_21_000000_create_courier_surcharge_audit_logs_table.php new file mode 100644 index 00000000..33f5a91e --- /dev/null +++ b/database/migrations/2026_09_21_000000_create_courier_surcharge_audit_logs_table.php @@ -0,0 +1,36 @@ +increments('id'); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('actor_id')->nullable(); + $table->string('action', 32); + $table->string('target_type', 32); + $table->unsignedInteger('target_id')->nullable(); + $table->string('target_label', 191); + $table->text('changes')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->index(['company_id', 'created_at'], 'courier_surcharge_audit_company_created_index'); + $table->index(['company_id', 'target_type', 'target_id'], 'courier_surcharge_audit_target_index'); + }); + } + + public function down(): void + { + Schema::dropIfExists('business_courier_surcharge_audit_logs'); + } +}; 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 0aaad980..490f76da 100644 --- a/resources/views/business/pages/courier-payables/courier-surcharge.blade.php +++ b/resources/views/business/pages/courier-payables/courier-surcharge.blade.php @@ -4,7 +4,10 @@ $summary = $courierSurchargeSummary ?? []; $options = $courierSurchargeOptions ?? []; $ready = (bool) ($courierSurchargeReady ?? false); + $dataReady = (bool) ($courierSurchargeDataReady ?? $ready); + $auditReady = (bool) ($courierSurchargeAuditReady ?? $ready); $typeUsage = $courierSurchargeTypeUsage ?? []; + $auditLogs = $courierSurchargeAuditLogs ?? collect(); $statusClass = static fn (string $status): string => $status === '已启用' ? 'is-active' : 'is-inactive'; $columns = [ ['key' => 'id', 'label' => '规则编号', 'fixed' => true, 'exportable' => true], @@ -49,6 +52,13 @@ .sm-actions { display:flex; flex-wrap:wrap; gap:.45rem; } .sm-button { display:inline-flex; align-items:center; justify-content:center; gap:.35rem; height:2.2rem; padding:0 .75rem; border:1px solid var(--sm-border); border-radius:.45rem; background:#fff; color:var(--sm-ink); font:inherit; font-weight:600; text-decoration:none; white-space:nowrap; cursor:pointer; } .sm-button .material-icons-outlined { font-size:1rem; } .sm-button--primary { border-color:var(--sm-accent); background:var(--sm-accent); color:#fff; } .sm-button--primary:hover { background:#6a5129; } .sm-button:disabled { cursor:not-allowed; opacity:.55; } .sm-table-wrap { max-height:min(62vh,42rem); overflow:auto; overscroll-behavior:contain; scrollbar-gutter:stable both-edges; } .sm-table { width:max-content; min-width:100%; border-collapse:separate; border-spacing:0; font-size:.75rem; } .sm-table th,.sm-table td { min-width:7.5rem; padding:.62rem .7rem; border-right:1px solid var(--sm-border); border-bottom:1px solid var(--sm-border); text-align:left; vertical-align:top; white-space:nowrap; } .sm-table th { position:sticky; top:0; z-index:2; background:var(--sm-soft); font-weight:700; } .sm-table [data-column-key="id"] { position:sticky; left:0; z-index:3; background:#fff; } .sm-table th[data-column-key="id"] { z-index:5; background:var(--sm-soft); } .sm-table [data-column-key="actions"] { position:sticky; right:0; z-index:3; background:#fff; box-shadow:-1px 0 0 var(--sm-border); } .sm-table th[data-column-key="actions"] { z-index:5; background:var(--sm-soft); } .sm-table tr:hover [data-column-key="id"],.sm-table tr:hover [data-column-key="actions"] { background:#fcfaf4; } .sm-table th[data-fixed="true"] { cursor:default; } .sm-table th[data-column-header]:not([data-fixed="true"]) { cursor:grab; } .sm-table th[data-column-header]:not([data-fixed="true"]):active { cursor:grabbing; } .sm-table th.is-dragging { opacity:.55; } .sm-table th.is-drop-target { box-shadow:inset 3px 0 0 var(--sm-accent); } .sm-table th .sm-table__header { display:inline-flex; align-items:center; gap:.35rem; } .sm-table th .sm-table__drag { color:var(--sm-muted); font-size:1rem; } .sm-table [data-column-key].is-column-hidden { display:none; } .sm-table tr:hover td { background:#fcfaf4; } .sm-table .wrap { max-width:18rem; white-space:normal; line-height:1.5; } .sm-table .actions { display:flex; gap:.35rem; } .sm-table .actions button { height:1.8rem; padding:0 .5rem; font-size:.7rem; } .sm-table-meta { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:.6rem 1rem; padding:.65rem 1.15rem; border-bottom:1px solid var(--sm-border); background:#fffdf8; color:var(--sm-muted); font-size:.72rem; } + .sm-audit-table-wrap { max-height:24rem; overflow:auto; overscroll-behavior:contain; scrollbar-gutter:stable both-edges; } + .sm-audit-table { width:max-content; min-width:100%; border-collapse:separate; border-spacing:0; font-size:.75rem; } + .sm-audit-table th,.sm-audit-table td { min-width:8rem; padding:.6rem .7rem; border-right:1px solid var(--sm-border); border-bottom:1px solid var(--sm-border); text-align:left; vertical-align:top; white-space:nowrap; } + .sm-audit-table th { background:var(--sm-soft); font-weight:700; } + .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-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; } @@ -58,7 +68,7 @@ @media (max-width:620px) { .sm-summary { grid-template-columns:repeat(2,1fr); } .sm-summary__intro { grid-column:span 2; } .sm-metric { border-left:0; border-top:1px solid var(--sm-border); } .sm-toolbar,.sm-form,.sm-type-create { grid-template-columns:minmax(0,1fr); } .sm-form__types { grid-template-columns:repeat(2,minmax(0,1fr)); } .sm-actions,.sm-form__field--full,.sm-form__foot { grid-column:auto; } .sm-actions .sm-button { flex:1; } } -
{{ $ready ? '正式维护数据' : '数据表尚未就绪' }} {{ $ready ? '报价规则已保存到当前公司数据表,刷新页面后仍会保留;后续结算页面将按启用中的规则计算。' : '附加费报价数据表尚未完成迁移,当前不能保存规则。请由管理员在目标数据库完成迁移后再使用本页。' }}
+
{{ $ready ? '正式维护数据' : '数据表尚未就绪' }} {{ $ready ? '报价规则已保存到当前公司数据表,刷新页面后仍会保留;后续结算页面将按启用中的规则计算。' : ($dataReady && !$auditReady ? '操作日志表尚未完成迁移;现有报价仍可查看,新增、编辑和启停暂时禁用,避免产生无日志修改。' : '附加费报价数据表尚未完成迁移,当前不能保存规则。请由管理员在目标数据库完成迁移后再使用本页。') }}
快递附加费报价
统一维护各快递的附加费结算规则
@@ -110,6 +120,32 @@
维护建议:固定单价、按重量、按体积、按尺寸/最长边等方式直接维护标准;聚工单客服核实、取发出运费和按账单等方式只维护触发条件,具体金额由后续结算链路读取业务凭证。当前筛选结果可导出为 CSV;本页暂未启用批量导入,避免在未确认字段规则前覆盖维护数据。
+
+
操作日志
记录当前公司最近 50 条新增、更改、启用和停用操作
+ @if (!$auditReady) +
操作日志表尚未就绪,完成迁移后将从首次新操作开始记录。
+ @elseif ($auditLogs->isEmpty()) +
暂无操作日志。新增、编辑或切换报价规则/附加费类型状态后,记录会显示在这里。
+ @else +
+ + + + @foreach ($auditLogs as $log) + + + + + + + + @endforeach + +
操作时间操作人动作对象变更内容
{{ $log['occurred_at'] }}{{ $log['actor_name'] }}{{ $log['action_label'] }}{{ $log['target_type_label'] }} · {{ $log['target_label'] }}{{ $log['changes_label'] }}
+
+ @endif +
+