264 lines
20 KiB
PHP
264 lines
20 KiB
PHP
@php
|
||
$rows = $courierSurchargeSettlementRows ?? collect();
|
||
$filters = $courierSurchargeSettlementFilters ?? [];
|
||
$summary = $courierSurchargeSettlementSummary ?? ['count' => 0, 'settlement_amount' => 0];
|
||
$sync = $courierSurchargeSettlementSync ?? ['code' => 'unavailable', 'last_succeeded_at' => null];
|
||
$state = $courierSurchargeSettlementState ?? [];
|
||
$error = $courierSurchargeSettlementError ?? null;
|
||
$columns = [
|
||
['key' => 'courier_name', 'label' => '快递名称', 'fixed' => 'true'],
|
||
['key' => 'shipped_at', 'label' => '出库时间'],
|
||
['key' => 'waybill', 'label' => '单号'],
|
||
['key' => 'platform_product_id', 'label' => '平台商品ID'],
|
||
['key' => 'product_code', 'label' => '商品编码'],
|
||
['key' => 'shop_name', 'label' => '店铺名称'],
|
||
['key' => 'online_order_no', 'label' => '线上订单号'],
|
||
['key' => 'length', 'label' => '长'],
|
||
['key' => 'width', 'label' => '宽'],
|
||
['key' => 'height', 'label' => '高'],
|
||
['key' => 'volume', 'label' => '体积'],
|
||
['key' => 'inspection_type', 'label' => '查件类型'],
|
||
['key' => 'inspection_reason', 'label' => '查件原因'],
|
||
['key' => 'return_waybill_no', 'label' => '退回单号'],
|
||
['key' => 'settlement_weight', 'label' => '结算重量'],
|
||
['key' => 'settlement_amount', 'label' => '结算金额'],
|
||
['key' => 'surcharge_type', 'label' => '附加费类型', 'fixed' => 'end'],
|
||
];
|
||
$filterValue = static fn (string $key): string => trim((string) ($filters[$key] ?? ''));
|
||
$syncLabel = match ($sync['code'] ?? null) {
|
||
'fresh' => '最新',
|
||
'stale' => '稍有延迟',
|
||
'pending', 'initializing', 'syncing' => '同步中',
|
||
'failed', 'initial_failed' => '同步失败',
|
||
default => '不可用',
|
||
};
|
||
$emptyMessage = match (true) {
|
||
$error !== null => $error,
|
||
($sync['code'] ?? null) === 'pending' || ($sync['code'] ?? null) === 'syncing' => '镜像正在同步,完成后将自动显示附加费明细。',
|
||
($sync['code'] ?? null) === 'failed' || ($sync['code'] ?? null) === 'initial_failed' => '镜像同步失败,暂不展示不完整数据。',
|
||
default => '当前筛选条件没有匹配的附加费明细。',
|
||
};
|
||
$displayValue = static function (mixed $value, string $key): string {
|
||
if ($value === null || $value === '') {
|
||
return '—';
|
||
}
|
||
|
||
if ($key === 'settlement_amount' && is_numeric($value)) {
|
||
return number_format((float) $value, 2, '.', '');
|
||
}
|
||
|
||
return (string) $value;
|
||
};
|
||
@endphp
|
||
|
||
<section class="surcharge-settlement-page" aria-label="附加费结算">
|
||
<style>
|
||
.surcharge-settlement-page {
|
||
--settlement-ink: var(--vip-content-ink, #171717);
|
||
--settlement-muted: var(--vip-content-muted, #595959);
|
||
--settlement-accent: var(--vip-content-accent-dark, #856632);
|
||
--settlement-soft: var(--vip-content-accent-soft, #f7f0de);
|
||
--settlement-border: var(--vip-border, #d8d2c7);
|
||
display: grid;
|
||
gap: 1rem;
|
||
min-width: 0;
|
||
color: var(--settlement-ink);
|
||
font-family: "JetBrains Mono", "Microsoft YaHei", "PingFang SC", monospace;
|
||
font-size: .8125rem;
|
||
}
|
||
|
||
.surcharge-settlement-page *,
|
||
.surcharge-settlement-page *::before,
|
||
.surcharge-settlement-page *::after { box-sizing: border-box; }
|
||
.surcharge-settlement-page > * { min-width: 0; }
|
||
|
||
.settlement-notice {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: .65rem;
|
||
padding: .75rem 1rem;
|
||
border: 1px solid #d7c39d;
|
||
border-radius: .65rem;
|
||
background: #fffaf0;
|
||
color: #6a5129;
|
||
line-height: 1.55;
|
||
}
|
||
|
||
.settlement-notice .material-icons-outlined { color: var(--settlement-accent); font-size: 1.15rem; }
|
||
.settlement-notice strong { color: var(--settlement-ink); }
|
||
|
||
.settlement-summary {
|
||
display: grid;
|
||
grid-template-columns: 1.35fr repeat(3, minmax(8rem, 1fr));
|
||
overflow: hidden;
|
||
border: 1px solid var(--settlement-border);
|
||
border-radius: .75rem;
|
||
background: #fff;
|
||
}
|
||
|
||
.settlement-summary__intro,
|
||
.settlement-summary__metric { min-width: 0; padding: 1rem 1.15rem; }
|
||
.settlement-summary__intro { display: flex; align-items: center; gap: .75rem; }
|
||
.settlement-summary__icon { display: grid; flex: none; width: 2.4rem; height: 2.4rem; place-items: center; border-radius: .6rem; background: var(--settlement-soft); color: var(--settlement-accent); }
|
||
.settlement-summary__title { font-size: .95rem; font-weight: 700; }
|
||
.settlement-summary__subtitle { margin-top: .2rem; color: var(--settlement-muted); font-size: .72rem; }
|
||
.settlement-summary__metric { border-left: 1px solid var(--settlement-border); }
|
||
.settlement-summary__label { color: var(--settlement-muted); font-size: .72rem; }
|
||
.settlement-summary__value { margin-top: .25rem; font-size: 1.1rem; font-weight: 700; font-variant-numeric: tabular-nums; }
|
||
|
||
.settlement-filter-bar {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(10rem, 1fr));
|
||
gap: .7rem;
|
||
align-items: end;
|
||
padding: 1rem 1.15rem;
|
||
border: 1px solid var(--settlement-border);
|
||
border-radius: .75rem;
|
||
background: #fffdf8;
|
||
}
|
||
|
||
.settlement-field { display: grid; gap: .3rem; min-width: 0; }
|
||
.settlement-field label { color: var(--settlement-muted); font-size: .72rem; font-weight: 600; }
|
||
.settlement-field input,
|
||
.settlement-field select {
|
||
width: 100%;
|
||
height: 2.2rem;
|
||
min-width: 0;
|
||
padding: 0 .6rem;
|
||
border: 1px solid var(--settlement-border);
|
||
border-radius: .45rem;
|
||
background: #fff;
|
||
color: var(--settlement-ink);
|
||
font: inherit;
|
||
}
|
||
|
||
.settlement-field input:focus,
|
||
.settlement-field select:focus { border-color: var(--settlement-accent); outline: none; box-shadow: 0 0 0 3px rgba(133, 102, 50, .18); }
|
||
.settlement-actions { display: flex; flex-wrap: wrap; gap: .45rem; align-items: end; }
|
||
.settlement-button { display: inline-flex; align-items: center; justify-content: center; gap: .35rem; min-height: 2.2rem; padding: 0 .75rem; border: 1px solid var(--settlement-border); border-radius: .45rem; background: #fff; color: var(--settlement-ink); font: inherit; font-weight: 600; text-decoration: none; white-space: nowrap; cursor: pointer; }
|
||
.settlement-button--primary { border-color: var(--settlement-accent); background: var(--settlement-accent); color: #fff; }
|
||
.settlement-button .material-icons-outlined { font-size: 1rem; }
|
||
.settlement-import-bar { display: flex; flex-wrap: wrap; align-items: end; gap: .75rem 1rem; margin-top: .75rem; padding: .85rem 1.15rem; border: 1px solid var(--settlement-border); border-radius: .75rem; background: #fff; }
|
||
.settlement-import-bar .settlement-field { flex: 0 1 22rem; }
|
||
.settlement-import-bar input[type="file"] { width: 100%; height: 2.2rem; padding: .35rem .5rem; border: 1px dashed var(--settlement-border); border-radius: .45rem; background: #fffdf8; color: var(--settlement-ink); font: inherit; }
|
||
.settlement-import-bar .settlement-table-hint { align-self: center; }
|
||
|
||
.settlement-table-surface { overflow: visible; border: 1px solid var(--settlement-border); border-radius: .75rem; background: #fff; }
|
||
.settlement-table-head { display: flex; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; gap: .75rem 1rem; padding: .85rem 1.15rem; border-bottom: 1px solid var(--settlement-border); }
|
||
.settlement-table-title { display: flex; align-items: center; gap: .45rem; font-size: .9rem; font-weight: 700; }
|
||
.settlement-table-title .material-icons-outlined { color: var(--settlement-accent); font-size: 1.1rem; }
|
||
.settlement-table-hint { color: var(--settlement-muted); font-size: .72rem; }
|
||
.settlement-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(--settlement-border); background: #fffdf8; color: var(--settlement-muted); font-size: .72rem; }
|
||
.settlement-table-meta strong { color: var(--settlement-ink); }
|
||
.settlement-table-scroll { max-height: min(62vh, 42rem); overflow: auto; overscroll-behavior: contain; scrollbar-gutter: stable both-edges; }
|
||
.settlement-table { width: max-content; min-width: 100%; border-collapse: separate; border-spacing: 0; font-size: .75rem; }
|
||
.settlement-table th,
|
||
.settlement-table td { min-width: 7.5rem; padding: .62rem .7rem; border-right: 1px solid var(--settlement-border); border-bottom: 1px solid var(--settlement-border); text-align: left; vertical-align: top; white-space: nowrap; }
|
||
.settlement-table th { background: var(--settlement-soft); font-weight: 700; }
|
||
.settlement-table th[data-finance-fixed="true"],
|
||
.settlement-table td[data-finance-fixed="true"] { background: #fffdf8; }
|
||
.settlement-table th[data-finance-fixed="end"],
|
||
.settlement-table td[data-finance-fixed="end"] { background: #fffdf8; }
|
||
.settlement-table td { overflow: hidden; text-overflow: ellipsis; }
|
||
.settlement-table .settlement-empty td { min-width: 22rem; padding: 3rem 1rem; text-align: center; color: var(--settlement-muted); white-space: normal; }
|
||
|
||
@media (max-width: 900px) {
|
||
.settlement-summary { grid-template-columns: 1fr 1fr; }
|
||
.settlement-summary__intro { grid-column: 1 / -1; }
|
||
.settlement-summary__metric:nth-child(2) { border-left: 0; }
|
||
.settlement-filter-bar { grid-template-columns: repeat(2, minmax(10rem, 1fr)); }
|
||
}
|
||
|
||
@media (max-width: 560px) {
|
||
.settlement-summary { grid-template-columns: 1fr; }
|
||
.settlement-summary__metric { border-left: 0; border-top: 1px solid var(--settlement-border); }
|
||
.settlement-filter-bar { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
|
||
@if (session('error'))
|
||
<div class="settlement-notice" role="status">
|
||
<span class="material-icons-outlined" aria-hidden="true">error_outline</span>
|
||
<div><strong>导入核验失败</strong><div>{{ session('error') }}</div></div>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($error !== null || ($sync['code'] ?? null) !== 'fresh')
|
||
<div class="settlement-notice" role="status">
|
||
<span class="material-icons-outlined" aria-hidden="true">{{ $error !== null ? 'error_outline' : 'info' }}</span>
|
||
<div>
|
||
<strong>{{ $error !== null ? '附加费结算暂不可用' : '附加费结算数据状态' }}</strong>
|
||
<div>{{ $error ?? $emptyMessage }}</div>
|
||
@if (! empty($state['failure_stage']))
|
||
<small>失败阶段:{{ $state['failure_stage'] }}</small>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
<div class="settlement-summary" aria-label="附加费结算概览">
|
||
<div class="settlement-summary__intro"><span class="settlement-summary__icon material-icons-outlined" aria-hidden="true">calculate</span><div><div class="settlement-summary__title">附加费结算</div><div class="settlement-summary__subtitle">结算标准读取附加费报价维护</div></div></div>
|
||
<div class="settlement-summary__metric"><div class="settlement-summary__label">筛选结果</div><div class="settlement-summary__value">{{ number_format((int) ($summary['count'] ?? $rows->total()), 0) }}</div></div>
|
||
<div class="settlement-summary__metric"><div class="settlement-summary__label">当前页结算金额</div><div class="settlement-summary__value">{{ number_format((float) ($summary['settlement_amount'] ?? 0), 2) }}</div></div>
|
||
<div class="settlement-summary__metric"><div class="settlement-summary__label">镜像状态</div><div class="settlement-summary__value">{{ $syncLabel }}</div></div>
|
||
</div>
|
||
|
||
<form class="settlement-filter-bar" method="GET" action="{{ route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-surcharge', 'tab' => 'settlement']) }}" aria-label="附加费结算筛选">
|
||
<div class="settlement-field"><label for="settlement-courier">快递名称</label><input id="settlement-courier" type="search" name="courier_name" value="{{ $filterValue('courier_name') }}" placeholder="输入快递名称"></div>
|
||
<div class="settlement-field"><label for="settlement-waybill">单号</label><input id="settlement-waybill" type="search" name="waybill" value="{{ $filterValue('waybill') }}" placeholder="输入单号"></div>
|
||
<div class="settlement-field"><label for="settlement-platform-product">平台商品ID</label><input id="settlement-platform-product" type="search" name="platform_product_id" value="{{ $filterValue('platform_product_id') }}" placeholder="输入平台商品ID"></div>
|
||
<div class="settlement-field"><label for="settlement-product-code">商品编码</label><input id="settlement-product-code" type="search" name="product_code" value="{{ $filterValue('product_code') }}" placeholder="输入商品编码"></div>
|
||
<div class="settlement-field"><label for="settlement-shop">店铺名称</label><input id="settlement-shop" type="search" name="shop_name" value="{{ $filterValue('shop_name') }}" placeholder="输入店铺名称"></div>
|
||
<div class="settlement-field"><label for="settlement-online-order">线上订单号</label><input id="settlement-online-order" type="search" name="online_order_no" value="{{ $filterValue('online_order_no') }}" placeholder="输入线上订单号"></div>
|
||
<div class="settlement-field"><label for="settlement-inspection">查件类型</label><input id="settlement-inspection" type="search" name="inspection_type" value="{{ $filterValue('inspection_type') }}" placeholder="一级选项"></div>
|
||
<div class="settlement-field"><label for="settlement-reason">查件原因</label><input id="settlement-reason" type="search" name="inspection_reason" value="{{ $filterValue('inspection_reason') }}" placeholder="二级选项"></div>
|
||
<div class="settlement-field"><label for="settlement-surcharge-type">附加费类型</label><select id="settlement-surcharge-type" name="surcharge_type"><option value="">全部附加费类型</option>@foreach (['退回费','拦截费','改地址','上楼费','送货费','超区费','安装费','保费','进仓费','超长费'] as $type)<option value="{{ $type }}" @selected($filterValue('surcharge_type') === $type)>{{ $type }}</option>@endforeach</select></div>
|
||
<div class="settlement-field"><label for="settlement-status">结算状态</label><select id="settlement-status" name="status"><option value="">全部状态</option>@foreach (['未匹配报价','待核价','已计算'] as $status)<option value="{{ $status }}" @selected($filterValue('status') === $status)>{{ $status }}</option>@endforeach</select></div>
|
||
<div class="settlement-field"><label for="settlement-created-from">创建日期起</label><input id="settlement-created-from" type="date" name="created_from" value="{{ $filterValue('created_from') }}"></div>
|
||
<div class="settlement-field"><label for="settlement-created-to">创建日期止</label><input id="settlement-created-to" type="date" name="created_to" value="{{ $filterValue('created_to') }}"></div>
|
||
<div class="settlement-actions"><button class="settlement-button settlement-button--primary" type="submit"><span class="material-icons-outlined" aria-hidden="true">search</span>查询</button><a class="settlement-button" href="{{ route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-surcharge', 'tab' => 'settlement']) }}"><span class="material-icons-outlined" aria-hidden="true">refresh</span>重置</a><a class="settlement-button" href="{{ route('business.courier-surcharge.settlement.export', array_filter($filters, static fn ($value): bool => $value !== '' && $value !== null)) }}" title="按当前筛选导出附加费结算明细(XLSX,仅含已生成结算行的数据;未生成结算行的运单请用导入核验)"><span class="material-icons-outlined" aria-hidden="true">download</span>导出</a></div>
|
||
</form>
|
||
|
||
<form class="settlement-import-bar" method="POST" action="{{ route('business.courier-surcharge.settlement.import') }}" enctype="multipart/form-data" aria-label="附加费结算导入核验">
|
||
@csrf
|
||
<div class="settlement-field">
|
||
<label for="settlement-import-file">导入核验(单号 + 附加费类型)</label>
|
||
<input id="settlement-import-file" type="file" name="file" accept=".xlsx,.xls,.csv" required>
|
||
</div>
|
||
<div class="settlement-actions">
|
||
<button class="settlement-button settlement-button--primary" type="submit"><span class="material-icons-outlined" aria-hidden="true">upload_file</span>导入并下载结果</button>
|
||
<a class="settlement-button" href="{{ route('business.courier-surcharge.settlement.import-template') }}"><span class="material-icons-outlined" aria-hidden="true">download</span>下载导入模板</a>
|
||
<span class="settlement-table-hint">首行需包含「单号」「附加费类型」,建议提供「快递名称」;「金额」可选,填了会返回导入金额与差异金额(系统金额 − 导入金额)。未生成结算行的运单用导入核验获取字段明细。</span>
|
||
</div>
|
||
</form>
|
||
|
||
<div class="settlement-table-surface" data-finance-table-surface>
|
||
<div class="settlement-table-head"><div><div class="settlement-table-title"><span class="material-icons-outlined" aria-hidden="true">table_view</span>附加费结算明细</div><div class="settlement-table-hint">快递名称和附加费类型固定显示,其余字段可在“列设置”中调整。</div></div><div class="finance-table-controls" data-finance-controls><span class="settlement-table-hint">列设置按当前用户和本页面保存</span></div></div>
|
||
<div class="settlement-table-meta"><span>当前筛选结果:<strong>{{ number_format((int) ($summary['count'] ?? $rows->total()), 0) }}</strong> 条</span><span>数据状态:{{ $syncLabel }}@if (! empty($sync['last_succeeded_at'])),最近同步 {{ $sync['last_succeeded_at'] }}@endif</span></div>
|
||
<div class="settlement-table-scroll finance-table-scroll-owner">
|
||
<table class="settlement-table" data-finance-table data-finance-table-name="courier-surcharge-settlement" data-finance-table-key="{{ auth()->id() }}:{{ company_id() }}:courier-payables:courier-surcharge:settlement:details" aria-label="附加费结算明细">
|
||
<thead><tr>
|
||
@foreach ($columns as $column)
|
||
<th scope="col" data-finance-column-key="{{ $column['key'] }}" data-finance-fixed="{{ $column['fixed'] ?? '' }}" data-export-field="{{ $column['key'] }}">{{ $column['label'] }}</th>
|
||
@endforeach
|
||
</tr></thead>
|
||
<tbody>
|
||
@forelse ($rows as $row)
|
||
<tr>
|
||
@foreach ($columns as $column)
|
||
<td data-finance-column-key="{{ $column['key'] }}" data-export-value="{{ $displayValue(data_get($row, $column['key']), $column['key']) }}">{{ $displayValue(data_get($row, $column['key']), $column['key']) }}</td>
|
||
@endforeach
|
||
</tr>
|
||
@empty
|
||
<tr class="settlement-empty is-empty"><td colspan="{{ count($columns) }}"><span class="material-icons-outlined" aria-hidden="true">{{ $error !== null ? 'error_outline' : 'inbox' }}</span><div>{{ $emptyMessage }}</div></td></tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
@if (method_exists($rows, 'links') && $rows->hasPages())
|
||
<div class="settlement-pagination">{{ $rows->withQueryString()->links() }}</div>
|
||
@endif
|
||
</section>
|