快递应付 → 快递报价 下新增「快递入驻」页:生成入驻二维码发给快递方,
对方免登录在手机上填写主体、联系人、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
666 lines
38 KiB
PHP
666 lines
38 KiB
PHP
@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>
|