CaiWuXiTong/resources/views/business/pages/courier-payables/courier-information.blade.php
limeng 71652c5a64 feat(快递入驻): 快递扫码填写入驻资料 + 两级审核 + 首版报价 + 签署开通
快递应付 → 快递报价 下新增「快递入驻」页:生成入驻二维码发给快递方,
对方免登录在手机上填写主体、联系人、e签宝签署人、结算账户、报价并上传资质,
内部对接人初审、管理员财务复审后生成首版报价,并自动生成报价确认单 PDF
推送到快递填写的 e签宝账号,签署归档后开通。

- 新增三张表 business_courier_onboardings / _files / _events(纯新增,不改旧表)
- 免登录填报页 /onboarding/courier/{token}:40 位随机 token,可暂存、提交、退回后补件,
  链接有效期可延长、可作废;银行账号加密存储;附件存 uploads 盘
- 新增 COURIER_ONBOARDING_PUBLIC_URL 配置二维码里的站点地址(留空用当前访问地址)
- 内置纯 PHP 二维码生成(App\Support\QrCode,SVG 输出,无新增依赖)
- 生成二维码时必须定好系统快递名称(唯一,快递方不可见);财务复审沿用该名称,快递所填报价写成报价管理的 CourierQuoteVersion,
  复用 CourierQuoteSignatureService 生成报价确认单并推送 e签宝(失败可重推/同步状态);初审人默认不能复审(COURIER_ONBOARDING_ALLOW_SAME_REVIEWER=true 可在测试环境放开)
- 支持公司 / 个人两种入驻:个人填姓名+身份证号(加密)、传身份证和银行卡,报价确认单按个人身份在 e签宝签署
- 扫码先选入驻方式;证件改为 营业执照 / 法人身份证正反面 / 银行卡正反面 各一张(个人营业执照选填),手机端上传前自动压缩,超限回到填报页提示
- 报价支持 4 种计费方式(面单+费率 / 首重+续重 / 按重量段一口价 / 小重量一口价+超出另算,费率可分段)+「其他」上传报价表;附加规则:计泡、起算重量、取整、每票加收(CourierQuoteStructure)
- 不再要求上传盖章报价单,至少填写一行在线报价
- 开通:报价确认单签署归档、或匹配到已签署协议、或填写线下签署说明
- 生成二维码前必填「快递公司代码」(自动大写、不可重复),入驻信息按代码对齐快递资料
- 公司入驻对公(营业执照主体)+ 对私(法定代表人)两个打款账户必填并自动核对;个人入驻对私必填、对公选填
- 变价:报价管理里后台改价 → 审核 → 自动推送快递 e签宝 → 回签;回签单导出 xlsx/zip;误发版本可作废
- 短期(临时)变价:变价单/报价版本加选填「结束日期」(end_date);按日期取价,期间用临时价,到期次日自动恢复原长期报价,
  不用重签;报价管理显示 生效中(临时)/ 暂停中 / 已到期,确认单写明执行期间和到期后恢复的版本,审核提醒期间重叠
- 快递资料分四类:快递资料 / 网点 / 打款信息 / 操作日志,支持查询、导出、重置、编辑;
  财务系统入驻的快递直接作为快递资料行,合同起止取协议签署里已签署的合作合同;
  点开一行在下方看该快递的网点 / 打款信息(完整账号)/ 操作日志
- 编辑存财务系统覆盖值(business_courier_profiles,不回写工作平台),改动记操作日志(business_courier_profile_events)
- 打款信息新增 / 变更(business_courier_payment_accounts,账号加密)保存后跳到协议签署,自动预填收款账户证明 / 收款账户变更,
  按签署状态显示 待推送协议 / 签署中 / 已生效;入口在快递资料明细和「打款信息」页签(表格每个账户可直接「变更」)
- ERP 开通:入驻开通后标「待 ERP 开通」,可手动补录(钉钉「新建快递」流程同步待接入)
- 新增 CourierOnboardingWorkflowTest(8 个用例),更新快递报价目录顺序断言

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019gzfsnV3fJ89CoUy1D9V9g
2026-10-14 13:47:00 +08:00

666 lines
38 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@php
$tab = $courierTab ?? 'express';
$filters = $courierFilters ?? [];
$rows = $courierRows ?? null;
$sync = $courierSyncStatus ?? ['code' => 'unavailable', 'lag_minutes' => null];
$state = $courierState ?? null;
$statusLabels = [
'healthy' => '同步正常',
'delayed' => '同步延迟',
'syncing' => '增量同步中',
'reconciling' => '全量校准中',
'initializing' => '首次同步中',
'initial_failed' => '首次同步失败',
'failed' => '同步失败',
'pending' => '等待首次同步',
'unavailable' => '镜像未建立',
];
$statusClass = in_array($sync['code'] ?? '', ['healthy'], true)
? 'is-success'
: (in_array($sync['code'] ?? '', ['failed', 'initial_failed', 'unavailable'], true) ? 'is-danger' : 'is-warning');
$date = static function ($value): string {
if ($value === null || $value === '') {
return '-';
}
try {
return \Illuminate\Support\Carbon::parse((string) $value)->format('Y-m-d');
} catch (\Throwable) {
return (string) $value;
}
};
$datetime = static function ($value): string {
if ($value === null || $value === '') {
return '-';
}
try {
return \Illuminate\Support\Carbon::parse((string) $value)->format('Y-m-d H:i');
} catch (\Throwable) {
return (string) $value;
}
};
$bool = static function ($value): string {
return in_array(strtolower((string) $value), ['1', 'true', 'yes', 'y'], true) || $value === true ? '是' : '否';
};
$tabUrl = static function (string $targetTab) use ($filters): string {
return route('business.pages.show', array_merge(
['section' => 'courier-payables', 'page' => 'courier-information', 'courier_tab' => $targetTab],
array_filter($filters, static fn ($value) => $value !== '' && $value !== null),
));
};
$displayRows = $courierDisplayRows ?? [];
$detail = $courierDetail ?? null;
$selectedCode = (string) ($detail['code'] ?? '');
$selectedRow = $detail['row'] ?? null;
$selectUrl = static function (string $code, array $extra = []) use ($filters): string {
$page = (int) request('page', 1);
return route('business.pages.show', array_merge(
['section' => 'courier-payables', 'page' => 'courier-information', 'courier_tab' => 'express'],
array_filter($filters, static fn ($value) => $value !== '' && $value !== null),
$page > 1 ? ['page' => $page] : [],
['selected' => $code],
$extra,
));
};
@endphp
<style>
.courier-information-page {
color: var(--vip-content-ink, #171717);
font-family: JetBrains Mono, Microsoft YaHei, PingFang SC, monospace;
font-size: 0.8125rem;
}
.courier-information-status {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
padding: 10px 12px;
border: 1px solid var(--vip-border, #d8d2c7);
border-radius: 8px;
background: #fff;
}
.courier-information-status__main,
.courier-information-status__meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.courier-information-status__meta { color: var(--vip-content-muted, #595959); font-size: 0.75rem; }
.courier-information-status__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--vip-warning, #935f07);
}
.courier-information-status__dot.is-success { background: var(--vip-success, #53793e); }
.courier-information-status__dot.is-danger { background: var(--vip-danger, #b80000); }
.courier-information-tabs {
display: flex;
gap: 24px;
min-height: 48px;
margin-bottom: 16px;
padding: 0 16px;
border-bottom: 1px solid var(--vip-border, #d8d2c7);
background: #fff;
}
.courier-information-tab {
position: relative;
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 48px;
color: var(--vip-content-muted, #595959);
font-size: 0.8125rem;
white-space: nowrap;
}
.courier-information-tab:hover,
.courier-information-tab.is-active { color: var(--vip-content-accent-dark, #856632); }
.courier-information-tab.is-active::after {
position: absolute;
right: 0;
bottom: -1px;
left: 0;
height: 3px;
background: var(--vip-content-accent, #b9954f);
content: '';
}
.courier-information-filters {
display: grid;
grid-template-columns: repeat(4, minmax(150px, 1fr));
gap: 12px;
margin-bottom: 16px;
padding: 16px;
border: 1px solid var(--vip-border, #d8d2c7);
border-radius: 12px;
background: #fff;
}
.courier-information-field { min-width: 0; }
.courier-information-field label {
display: block;
margin-bottom: 5px;
color: var(--vip-content-muted, #595959);
font-size: 0.75rem;
font-weight: 500;
}
.courier-information-field input,
.courier-information-field select {
width: 100%;
height: 34px;
padding: 0 10px;
border: 1px solid var(--vip-border, #d8d2c7);
border-radius: 8px;
background: #fff;
color: var(--vip-content-ink, #171717);
font: inherit;
}
.courier-information-field input:focus,
.courier-information-field select:focus {
border-color: var(--vip-content-accent-dark, #856632);
outline: 3px solid rgba(133, 102, 50, 0.2);
}
.courier-information-actions {
display: flex;
align-items: flex-end;
gap: 8px;
}
.courier-information-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
height: 34px;
padding: 0 12px;
border: 1px solid var(--vip-border, #d8d2c7);
border-radius: 8px;
background: #fff;
color: var(--vip-content-ink, #171717);
font: inherit;
font-weight: 500;
white-space: nowrap;
}
.courier-information-button:hover { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-soft, #f7f0de); }
.courier-information-button.is-primary { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-dark, #856632); color: #fff; }
.courier-information-notice {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 16px;
padding: 10px 12px;
border: 1px solid rgba(147, 95, 7, 0.35);
border-radius: 8px;
background: rgba(147, 95, 7, 0.06);
color: var(--vip-warning, #935f07);
}
.courier-information-notice.is-error {
border-color: rgba(184, 0, 0, 0.3);
background: rgba(184, 0, 0, 0.05);
color: var(--vip-danger, #b80000);
}
.courier-information-table-panel {
overflow: hidden;
border: 1px solid var(--vip-border, #d8d2c7);
border-radius: 12px;
background: #fff;
}
.courier-information-table-wrap {
overflow: auto;
}
.courier-information-table-wrap.finance-table-scroll-owner {
max-height: max(12rem, calc(100vh - 27.75rem));
}
.courier-information-table {
width: 100%;
min-width: 2260px;
border-collapse: collapse;
font-size: 0.78125rem;
}
.courier-information-table th {
padding: 9px 10px;
border-bottom: 1px solid var(--vip-border, #d8d2c7);
background: var(--vip-content-accent-soft, #f7f0de);
color: #4f4a43;
font-weight: 600;
text-align: left;
white-space: nowrap;
}
.courier-information-table td {
max-width: 240px;
padding: 9px 10px;
border-bottom: 1px solid #eeeae3;
color: var(--vip-content-ink, #171717);
line-height: 1.45;
vertical-align: middle;
white-space: nowrap;
}
.courier-information-table tbody tr:hover { background: #fcfaf5; }
.courier-information-table .is-code { font-variant-numeric: tabular-nums; }
.courier-information-table .is-clamped { overflow: hidden; text-overflow: ellipsis; }
.courier-information-badge {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 2px 7px;
border: 1px solid var(--vip-border, #d8d2c7);
border-radius: 6px;
background: #fff;
color: var(--vip-content-muted, #595959);
}
.courier-information-badge.is-enabled { border-color: rgba(83, 121, 62, .3); background: rgba(83, 121, 62, .08); color: var(--vip-success, #53793e); }
.courier-information-badge.is-disabled { border-color: rgba(184, 0, 0, .25); background: rgba(184, 0, 0, .05); color: var(--vip-danger, #b80000); }
.courier-information-empty { min-height: 160px; padding: 42px 16px !important; color: var(--vip-content-muted, #595959) !important; text-align: center; }
.courier-information-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
border-top: 1px solid var(--vip-border, #d8d2c7);
color: var(--vip-content-muted, #595959);
font-size: 0.75rem;
}
.courier-information-pagination { display: flex; align-items: center; gap: 6px; }
.courier-information-page-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 32px;
height: 30px;
border: 1px solid var(--vip-border, #d8d2c7);
border-radius: 6px;
color: var(--vip-content-ink, #171717);
}
.courier-information-page-link:hover { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-soft, #f7f0de); }
.courier-information-page-link.is-disabled { opacity: .4; pointer-events: none; }
@media (max-width: 1000px) {
.courier-information-filters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.courier-information-table th.is-onboarding { background: #EEF3F8; color: #3C5A75; }
.courier-information-table td.is-onboarding { background: #F8FAFC; }
.courier-information-table tr.is-selectable { cursor: pointer; }
.courier-information-table tr.is-selected td { background: #F7EFDD !important; }
.courier-information-table tr.is-selected td:first-child { box-shadow: inset 3px 0 0 var(--vip-content-accent, #b9954f); }
.courier-information-table td.is-overridden { background: #FFF8E8; }
.courier-information-table tr.is-finance-row td { background: #FBFAF6; }
.courier-information-button[disabled] { opacity: .5; cursor: not-allowed; }
.courier-information-link { margin-left: 6px; color: var(--vip-content-accent-dark, #856632); text-decoration: underline; }
.courier-detail { margin-top: 20px; padding: 16px; border: 1px solid var(--vip-border, #d8d2c7); border-radius: 12px; background: #fff; scroll-margin-top: 80px; }
.courier-detail__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.courier-detail__title { margin: 0 0 4px; font-size: 1rem; font-weight: 600; }
.courier-detail__actions, .courier-detail__toolbar, .courier-detail__form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.courier-detail__toolbar { margin-bottom: 12px; }
.courier-detail__tabs { display: flex; gap: 20px; margin: 4px 0 12px; border-bottom: 1px solid var(--vip-border, #d8d2c7); }
.courier-detail__tab { position: relative; min-height: 40px; padding: 0; border: 0; background: none; color: var(--vip-content-muted, #595959); font: inherit; cursor: pointer; }
.courier-detail__tab.is-active { color: var(--vip-content-accent-dark, #856632); }
.courier-detail__tab.is-active::after { position: absolute; right: 0; bottom: -1px; left: 0; height: 3px; background: var(--vip-content-accent, #b9954f); content: ''; }
.courier-detail__form { margin-bottom: 14px; padding: 14px; border: 1px solid #E3C98F; border-radius: 10px; background: #FFFCF4; }
.courier-detail__form h4 { margin: 0 0 12px; font-size: .875rem; font-weight: 600; }
.courier-detail__form-actions { margin-top: 12px; }
@media (max-width: 560px) { .courier-detail__head { flex-direction: column; } }
.courier-overview-sub { color: #8A8478; font-size: 12px; }
.courier-overview-tag { display: inline-block; padding: 1px 8px; border-radius: 6px; font-size: 12px; background: #F1EEE6; color: #6F6A60; white-space: nowrap; }
.courier-overview-tag.is-ok { background: #EEF6EA; color: #2F6B2A; }
.courier-overview-tag.is-warn { background: #FFF6E5; color: #935F07; }
.courier-overview-tag.is-bad { background: #FDEDEC; color: #A1281F; }
.courier-overview-tag.is-muted { background: #F1EEE6; color: #7A7468; }
@media (max-width: 560px) {
.courier-information-filters { grid-template-columns: 1fr; }
.courier-information-status { align-items: flex-start; flex-direction: column; }
}
</style>
<div class="courier-information-page" data-courier-information>
<div class="courier-information-status" role="status">
<div class="courier-information-status__main">
<span class="courier-information-status__dot {{ $statusClass }}" aria-hidden="true"></span>
<span>{{ $statusLabels[$sync['code'] ?? 'unavailable'] ?? '镜像状态未知' }}</span>
</div>
<div class="courier-information-status__meta">
@if (!empty($state['last_succeeded_at']))
<span>最近同步 {{ $datetime($state['last_succeeded_at']) }}</span>
@endif
@if (($sync['lag_minutes'] ?? null) !== null)
<span>数据延迟 {{ number_format((int) $sync['lag_minutes']) }} 分钟</span>
@endif
@if (isset($state['mirror_row_count']))
<span>主表 {{ number_format((int) $state['mirror_row_count']) }} 条</span>
@endif
@if ($rows && !$courierError)
<span>当前结果 {{ number_format($rows->total()) }} 条</span>
@endif
</div>
</div>
<nav class="courier-information-tabs" aria-label="快递资料分组">
@foreach ([
'express' => ['icon' => 'local_shipping', 'label' => '快递资料'],
'networks' => ['icon' => 'storefront', 'label' => '网点'],
'accounts' => ['icon' => 'account_balance', 'label' => '打款信息'],
'logs' => ['icon' => 'history', 'label' => '操作日志'],
] as $key => $item)
<a class="courier-information-tab {{ $tab === $key ? 'is-active' : '' }}" href="{{ $tabUrl($key) }}" @if ($tab === $key) aria-current="page" @endif>
<span class="material-icons-outlined" aria-hidden="true">{{ $item['icon'] }}</span>
<span>{{ $item['label'] }}</span>
</a>
@endforeach
</nav>
@if ($tab === 'express')
<form method="GET" class="courier-information-filters" aria-label="快递资料筛选">
<input type="hidden" name="courier_tab" value="express">
<div class="courier-information-field">
<label for="courier-expcode">快递公司代码</label>
<input id="courier-expcode" name="expcode" type="text" value="{{ $filters['expcode'] ?? '' }}" placeholder="精确匹配">
</div>
<div class="courier-information-field">
<label for="courier-expname">快递公司名称</label>
<input id="courier-expname" name="expname" type="text" value="{{ $filters['expname'] ?? '' }}" placeholder="名称关键词">
</div>
<div class="courier-information-field">
<label for="courier-start">合同开始时间(起)</label>
<input id="courier-start" name="tradebtime_from" type="date" value="{{ $filters['tradebtime_from'] ?? '' }}">
</div>
<div class="courier-information-field">
<label for="courier-end">合同开始时间(止)</label>
<input id="courier-end" name="tradebtime_to" type="date" value="{{ $filters['tradebtime_to'] ?? '' }}">
</div>
<div class="courier-information-field">
<label for="courier-custom-1">自定义1</label>
<input id="courier-custom-1" name="undefined1" type="text" value="{{ $filters['undefined1'] ?? '' }}">
</div>
<div class="courier-information-field">
<label for="courier-custom-2">自定义2</label>
<input id="courier-custom-2" name="undefined2" type="text" value="{{ $filters['undefined2'] ?? '' }}">
</div>
<div class="courier-information-field">
<label for="courier-custom-3">自定义3</label>
<input id="courier-custom-3" name="undefined3" type="text" value="{{ $filters['undefined3'] ?? '' }}">
</div>
<div class="courier-information-field">
<label for="courier-status">是否启用</label>
<select id="courier-status" name="status">
<option value="">全部状态</option>
<option value="1" @selected(($filters['status'] ?? '') === '1')>已启用</option>
<option value="0" @selected(($filters['status'] ?? '') === '0')>已停用</option>
</select>
</div>
<div class="courier-information-actions">
<button type="submit" class="courier-information-button is-primary"><span class="material-icons-outlined" aria-hidden="true">search</span>查询</button>
<a class="courier-information-button" href="{{ route('business.courier-payables.courier-information.export', array_filter($filters, static fn ($value) => $value !== '' && $value !== null)) }}"><span class="material-icons-outlined" aria-hidden="true">download</span>导出</a>
<a class="courier-information-button" href="{{ route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-information']) }}"><span class="material-icons-outlined" aria-hidden="true">refresh</span>重置</a>
@if ($selectedRow)
<a class="courier-information-button" href="{{ $selectUrl($selectedCode, ['edit' => 1]) }}#courier-detail" data-courier-edit-open><span class="material-icons-outlined" aria-hidden="true">edit</span>编辑</a>
@else
<button type="button" class="courier-information-button" disabled title="先在表格里点选一家快递,再编辑"><span class="material-icons-outlined" aria-hidden="true">edit</span>编辑</button>
@endif
</div>
</form>
@else
<form method="GET" class="courier-information-filters" aria-label="{{ ['networks' => '网点', 'accounts' => '打款信息', 'logs' => '操作日志'][$tab] }}筛选">
<input type="hidden" name="courier_tab" value="{{ $tab }}">
<div class="courier-information-field">
<label for="courier-related-expcode">快递公司代码</label>
<input id="courier-related-expcode" name="expcode" type="text" value="{{ $filters['expcode'] ?? '' }}" placeholder="留空显示全部">
</div>
<div class="courier-information-actions">
<button type="submit" class="courier-information-button is-primary"><span class="material-icons-outlined" aria-hidden="true">search</span>查询</button>
<a class="courier-information-button" href="{{ route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-information', 'courier_tab' => $tab]) }}"><span class="material-icons-outlined" aria-hidden="true">refresh</span>重置</a>
</div>
</form>
@endif
@if ($courierError)
<div class="courier-information-notice is-error" role="alert">
<span class="material-icons-outlined" aria-hidden="true">error_outline</span>
<span>{{ $courierError }}</span>
</div>
@elseif ($courierPending)
<div class="courier-information-notice" role="status">
<span class="material-icons-outlined" aria-hidden="true">schedule</span>
<span>首次全量同步完成后刷新本页,即可查询工作平台中的快递资料。</span>
</div>
@endif
@if ($tab === 'express')
<div class="courier-information-table-panel">
<div class="courier-information-table-wrap">
<table class="courier-information-table" aria-label="快递资料">
<thead>
<tr>
<th>ID</th><th>快递公司代码</th><th>快递公司名称</th><th>外部编码</th><th>杉橙快递代码</th><th>运费打款方式</th>
<th>是否启用</th><th>可达服务</th><th>是否快运</th><th>自动寻号</th><th>配货单号寻号</th><th>数量拆单</th><th>重量0寻号</th>
<th>停运时间</th><th>合同开始时间</th><th>合同结束时间</th><th>联系人</th><th>联系电话</th><th>自定义1</th><th>自定义2</th><th>自定义3</th>
<th>体积0寻号</th><th>手工单号</th><th>解密</th><th>电子签章经办人</th><th>最后修改</th>
<th class="is-onboarding">入驻单号</th><th class="is-onboarding">入驻状态</th><th class="is-onboarding">ERP 开通</th><th class="is-onboarding">入驻主体</th>
<th class="is-onboarding">证照</th><th class="is-onboarding">生效报价</th>
</tr>
</thead>
<tbody>
@forelse ($displayRows as $row)
@php
$code = (string) ($row->expcode ?? '');
$onlyOnboarding = ! empty($row->_onboarding_only);
$overridden = $row->_overridden ?? [];
$cell = static fn (string $field): string => in_array($field, $overridden, true) ? 'is-overridden' : '';
$enabled = $bool($row->status ?? null) === '是';
$isSelected = $code !== '' && strcasecmp($code, $selectedCode) === 0;
@endphp
<tr class="{{ $code !== '' ? 'is-selectable' : '' }} {{ $isSelected ? 'is-selected' : '' }} {{ $onlyOnboarding ? 'is-onboarding-only' : '' }}" @if ($code !== '') data-select-url="{{ $selectUrl($code) }}#courier-detail" @endif>
<td class="is-code">{{ $row->Id ?? '-' }}</td>
<td class="is-code">
@if ($code !== '')
<a href="{{ $selectUrl($code) }}#courier-detail" title="点开看网点 / 打款信息 / 操作日志">{{ $code }}</a>
@else
<span class="courier-overview-sub" title="入驻时没填快递公司代码,到入驻详情的「ERP 开通」里补上">未填代码</span>
@endif
</td>
<td class="is-clamped {{ $cell('expname') }}" title="{{ $row->expname ?? '' }}"><strong>{{ $row->expname ?? '-' }}</strong></td>
<td class="is-code">{{ $row->outcode ?? '-' }}</td>
<td class="is-code">{{ $row->scexpcode ?? '-' }}</td>
<td class="{{ $cell('paymethod') }}">{{ $row->paymethod ?? '-' }}</td>
<td class="{{ $cell('status') }}"><span class="courier-information-badge {{ $enabled ? 'is-enabled' : 'is-disabled' }}">{{ $enabled ? '是' : '否' }}</span></td>
<td>{{ $onlyOnboarding ? '-' : $bool($row->addvalidsgn ?? null) }}</td>
<td class="{{ $cell('kuaiyunsgn') }}">{{ $bool($row->kuaiyunsgn ?? null) }}</td>
<td>{{ $onlyOnboarding ? '-' : $bool($row->autonosgn ?? null) }}</td>
<td>{{ $onlyOnboarding ? '-' : $bool($row->packnosgn ?? null) }}</td>
<td>{{ $onlyOnboarding ? '-' : $bool($row->skucountsplitsgn ?? null) }}</td>
<td>{{ $onlyOnboarding ? '-' : $bool($row->weight0sgn ?? null) }}</td>
<td class="{{ $cell('stopdate') }}">{{ $date($row->stopdate ?? null) }}</td>
<td class="{{ $cell('tradebtime') }}" @if ($onlyOnboarding) title="协议签署 · 合作合同(已签署)" @endif>{{ $date($row->tradebtime ?? null) }}</td>
<td class="{{ $cell('tradeetime') }}" @if ($onlyOnboarding) title="协议签署 · 合作合同(已签署)" @endif>{{ $date($row->tradeetime ?? null) }}</td>
<td class="{{ $cell('linker') }}">{{ $row->linker ?? '-' }}</td>
<td class="{{ $cell('linkmobile') }}">{{ $row->linkmobile ?? '-' }}</td>
<td class="{{ $cell('undefined1') }}">{{ $row->undefined1 ?? '-' }}</td>
<td class="{{ $cell('undefined2') }}">{{ $row->undefined2 ?? '-' }}</td>
<td class="{{ $cell('undefined3') }}">{{ $row->undefined3 ?? '-' }}</td>
<td>{{ $onlyOnboarding ? '-' : $bool($row->volume0sgn ?? null) }}</td>
<td>{{ $onlyOnboarding ? '-' : $bool($row->handnosgn ?? null) }}</td>
<td>{{ $onlyOnboarding ? '-' : $bool($row->decryptsgn ?? null) }}</td>
<td class="is-code {{ $cell('dzqzjbr') }}">{{ $row->dzqzjbr ?? '-' }}</td>
<td class="is-code">{{ $datetime($row->modified ?? null) }}</td>
@include('business.pages.courier-payables.partials.courier-onboarding-cells', ['info' => $row->_info ?? null, 'platformName' => $row->expname ?? null, 'platformCode' => $row->expcode ?? null])
</tr>
@empty
<tr><td colspan="32" class="courier-information-empty">{{ $courierError ? '数据暂时不可用。' : '当前筛选条件下没有快递资料。' }}</td></tr>
@endforelse
</tbody>
</table>
</div>
@if ($rows && !$courierError)
<div class="courier-information-footer">
<span>第 {{ $rows->currentPage() }} / {{ max($rows->lastPage(), 1) }} 页,本页 {{ $rows->count() }} 条,共 {{ number_format($rows->total()) }} 条 · 点一行快递在下方看网点 / 打款信息(含打款信息新增、变更)/ 操作日志;浅色底的格子是财务系统改过的值</span>
<nav class="courier-information-pagination" aria-label="快递资料分页">
<a class="courier-information-page-link {{ $rows->onFirstPage() ? 'is-disabled' : '' }}" href="{{ $rows->previousPageUrl() ?: '#' }}" aria-label="上一页"><span class="material-icons-outlined" aria-hidden="true">chevron_left</span></a>
<a class="courier-information-page-link {{ $rows->hasMorePages() ? '' : 'is-disabled' }}" href="{{ $rows->nextPageUrl() ?: '#' }}" aria-label="下一页"><span class="material-icons-outlined" aria-hidden="true">chevron_right</span></a>
</nav>
</div>
@endif
</div>
@if ($detail)
@include('business.pages.courier-payables.partials.courier-information-detail', ['detail' => $detail])
@endif
@elseif ($tab === 'networks')
<div class="courier-information-table-panel">
<div class="courier-information-table-wrap">
@include('business.pages.courier-payables.partials.courier-network-table', ['networkRows' => $rows ?? [], 'emptyText' => $courierError ? '数据暂时不可用。' : '当前筛选条件下没有网点资料。'])
</div>
@if ($rows && !$courierError)
<div class="courier-information-footer"><span>第 {{ $rows->currentPage() }} / {{ max($rows->lastPage(), 1) }} 页,本页 {{ $rows->count() }} 条,共 {{ number_format($rows->total()) }} 条</span><nav class="courier-information-pagination" aria-label="网点分页"><a class="courier-information-page-link {{ $rows->onFirstPage() ? 'is-disabled' : '' }}" href="{{ $rows->previousPageUrl() ?: '#' }}" aria-label="上一页"><span class="material-icons-outlined" aria-hidden="true">chevron_left</span></a><a class="courier-information-page-link {{ $rows->hasMorePages() ? '' : 'is-disabled' }}" href="{{ $rows->nextPageUrl() ?: '#' }}" aria-label="下一页"><span class="material-icons-outlined" aria-hidden="true">chevron_right</span></a></nav></div>
@endif
</div>
@elseif ($tab === 'accounts')
@php
$tabOldAccounts = collect($courierFinanceAccounts ?? [])
->map(static fn (array $account): array => ['type' => $account['type'], 'name' => $account['name'], 'number' => $account['number'], 'bank' => $account['bank'], 'source' => $account['source']])
->merge(($filters['expcode'] ?? '') !== '' && $rows ? collect($rows->items())->map(static fn ($account): array => ['type' => in_array($account->accountType ?? '', ['对公', '对私'], true) ? $account->accountType : '', 'name' => $account->accountName ?? '', 'number' => $account->account ?? '', 'bank' => $account->accountkhh ?? '', 'source' => '工作平台']) : [])
->filter(static fn (array $account): bool => filled($account['number']) && ($filters['expcode'] ?? '') !== '')
->unique('number')
->values();
@endphp
<div class="courier-detail" style="margin: 0 0 16px">
@if ($errors->any() && in_array((string) old('form'), ['add', 'change'], true))
<div class="courier-information-notice is-error" role="alert"><span class="material-icons-outlined" aria-hidden="true">error_outline</span><span>{{ $errors->first() }}</span></div>
@endif
@include('business.pages.courier-payables.partials.courier-payment-account-forms', ['code' => null, 'prefillCode' => $filters['expcode'] ?? '', 'oldAccounts' => $tabOldAccounts])
</div>
<div class="courier-information-table-panel">
<div class="courier-information-table-wrap">
@include('business.pages.courier-payables.partials.courier-account-table', ['financeAccounts' => $courierFinanceAccounts ?? [], 'platformAccounts' => $rows ?? [], 'showCourier' => true, 'rowActions' => true, 'emptyText' => '当前筛选条件下没有打款信息。'])
</div>
@if ($rows && !$courierError)
<div class="courier-information-footer"><span>第 {{ $rows->currentPage() }} / {{ max($rows->lastPage(), 1) }} 页 · 工作平台账户共 {{ number_format($rows->total()) }} 条;财务系统的打款信息(入驻对公/对私、新增/变更)显示在第一页最前面</span><nav class="courier-information-pagination" aria-label="打款信息分页"><a class="courier-information-page-link {{ $rows->onFirstPage() ? 'is-disabled' : '' }}" href="{{ $rows->previousPageUrl() ?: '#' }}" aria-label="上一页"><span class="material-icons-outlined" aria-hidden="true">chevron_left</span></a><a class="courier-information-page-link {{ $rows->hasMorePages() ? '' : 'is-disabled' }}" href="{{ $rows->nextPageUrl() ?: '#' }}" aria-label="下一页"><span class="material-icons-outlined" aria-hidden="true">chevron_right</span></a></nav></div>
@endif
</div>
@else
<div class="courier-information-table-panel">
<div class="courier-information-table-wrap">
@include('business.pages.courier-payables.partials.courier-log-table', ['financeEvents' => $courierFinanceEvents ?? collect(), 'platformLogs' => $rows ?? [], 'showCourier' => true, 'emptyText' => '当前筛选条件下没有操作日志。'])
</div>
@if ($rows && !$courierError)
<div class="courier-information-footer"><span>第 {{ $rows->currentPage() }} / {{ max($rows->lastPage(), 1) }} 页 · 工作平台日志共 {{ number_format($rows->total()) }} 条;财务系统的操作(编辑、打款信息新增/变更)显示在第一页最前面</span><nav class="courier-information-pagination" aria-label="操作日志分页"><a class="courier-information-page-link {{ $rows->onFirstPage() ? 'is-disabled' : '' }}" href="{{ $rows->previousPageUrl() ?: '#' }}" aria-label="上一页"><span class="material-icons-outlined" aria-hidden="true">chevron_left</span></a><a class="courier-information-page-link {{ $rows->hasMorePages() ? '' : 'is-disabled' }}" href="{{ $rows->nextPageUrl() ?: '#' }}" aria-label="下一页"><span class="material-icons-outlined" aria-hidden="true">chevron_right</span></a></nav></div>
@endif
</div>
@endif
</div>
<script>
(function () {
var page = document.querySelector('[data-courier-information]');
if (!page || page.dataset.ready === 'true') return;
page.dataset.ready = 'true';
// 点表格行(不是点链接/按钮时)= 点开这家快递的明细。
page.addEventListener('click', function (event) {
var row = event.target.closest('tr[data-select-url]');
if (!row || event.target.closest('a, button, input, select, label')) return;
window.location.href = row.dataset.selectUrl;
});
// 明细里的网点 / 打款信息 / 操作日志:局部切换。
page.querySelectorAll('[data-detail-tab]').forEach(function (button) {
button.addEventListener('click', function () {
var name = button.dataset.detailTab;
page.querySelectorAll('[data-detail-tab]').forEach(function (item) {
var active = item.dataset.detailTab === name;
item.classList.toggle('is-active', active);
item.setAttribute('aria-selected', active ? 'true' : 'false');
});
page.querySelectorAll('[data-detail-panel]').forEach(function (panel) {
panel.hidden = panel.dataset.detailPanel !== name;
});
try {
var url = new URL(window.location.href);
url.searchParams.set('detail_tab', name);
window.history.replaceState(null, '', url.toString());
} catch (error) {}
});
});
// 打款信息新增 / 变更表单开关。
page.querySelectorAll('[data-account-form-open]').forEach(function (button) {
button.addEventListener('click', function () {
var target = button.dataset.accountFormOpen;
page.querySelectorAll('[data-account-form]').forEach(function (form) {
form.hidden = form.dataset.accountForm !== target ? true : !form.hidden;
});
var opened = page.querySelector('[data-account-form="' + target + '"]');
if (opened && !opened.hidden) {
var first = opened.querySelector('select, input:not([type="hidden"])');
if (first) first.focus();
}
});
});
page.querySelectorAll('[data-account-form-close]').forEach(function (button) {
button.addEventListener('click', function () {
button.closest('[data-account-form]').hidden = true;
});
});
// 打款信息表里某个账户点「变更」:打开变更表单,带出快递公司代码和原账户。
page.querySelectorAll('[data-account-change-from]').forEach(function (button) {
button.addEventListener('click', function () {
var form = page.querySelector('[data-account-form="change"]');
if (!form) return;
page.querySelectorAll('[data-account-form]').forEach(function (item) { item.hidden = item !== form; });
form.hidden = false;
var code = form.querySelector('[data-account-expcode]');
if (code) code.value = button.dataset.code || '';
form.querySelector('[name="old_account_name"]').value = button.dataset.name || '';
form.querySelector('[name="old_account_number"]').value = button.dataset.number || '';
if (button.dataset.type) form.querySelector('[name="account_type"]').value = button.dataset.type;
form.scrollIntoView({ behavior: 'smooth', block: 'start' });
var focus = form.querySelector('[name="account_name"]');
if (focus) focus.focus({ preventScroll: true });
});
});
// 变更:选原账户后带出原开户名称、原账号、账户类型。
page.querySelectorAll('[data-old-account-select]').forEach(function (select) {
select.addEventListener('change', function () {
var option = select.options[select.selectedIndex];
var form = select.closest('form');
if (!option || !option.value) return;
form.querySelector('[name="old_account_name"]').value = option.dataset.name || '';
form.querySelector('[name="old_account_number"]').value = option.dataset.number || '';
if (option.dataset.type) form.querySelector('[name="account_type"]').value = option.dataset.type;
});
});
})();
</script>