313 lines
23 KiB
PHP
313 lines
23 KiB
PHP
<x-layouts.portal>
|
||
<x-slot name="title">供应商对账</x-slot>
|
||
|
||
<x-slot name="content">
|
||
<div class="supplier-portal-page">
|
||
<style>
|
||
.supplier-portal-page { display: grid; gap: 16px; color: var(--vip-content-ink, #171717); }
|
||
.supplier-portal-page .sp-card { min-width: 0; max-width: 100%; padding: 16px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 12px; background: #fff; display: grid; gap: 12px; }
|
||
.supplier-portal-page h2 { margin: 0; font-size: .875rem; font-weight: 600; }
|
||
.supplier-portal-page .sp-toolbar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; }
|
||
.supplier-portal-page .sp-field { display: grid; gap: 4px; }
|
||
.supplier-portal-page .sp-field span { color: var(--vip-content-muted, #595959); font-size: .75rem; font-weight: 600; }
|
||
.supplier-portal-page select,
|
||
.supplier-portal-page input[type="text"],
|
||
.supplier-portal-page input[type="date"] { width: 100%; height: 34px; min-width: 0; padding: 0 10px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: #fff; font-size: .8125rem; }
|
||
.supplier-portal-page .sp-button { display: inline-flex; height: 34px; align-items: center; justify-content: center; gap: 6px; padding: 0 14px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: #fff; color: var(--vip-content-ink, #171717); font-size: .8125rem; font-weight: 600; text-decoration: none; cursor: pointer; }
|
||
.supplier-portal-page .sp-button.is-primary { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-dark, #856632); color: #fff; }
|
||
.supplier-portal-page .sp-button.is-small { height: 24px; padding: 0 10px; font-size: .75rem; }
|
||
.supplier-portal-page .sp-hint { margin: 0; color: var(--vip-content-muted, #595959); font-size: .75rem; line-height: 1.7; }
|
||
.supplier-portal-page .sp-scroll { min-width: 0; max-width: 100%; overflow-x: auto; scrollbar-width: thin; }
|
||
.supplier-portal-page table { width: 100%; min-width: 720px; border-collapse: collapse; font-size: .8125rem; }
|
||
.supplier-portal-page th { padding: 8px 10px; background: var(--vip-content-accent-soft, #F7F0DE); border-bottom: 1px solid var(--vip-border, #D8D2C7); color: #5B5343; font-size: .75rem; font-weight: 600; text-align: left; white-space: nowrap; }
|
||
.supplier-portal-page td { padding: 8px 10px; border-bottom: 1px solid #EFEAE0; }
|
||
.supplier-portal-page td.is-num { text-align: right; font-variant-numeric: tabular-nums; }
|
||
.supplier-portal-page .sp-muted { color: var(--vip-content-muted, #595959); }
|
||
.supplier-portal-page .sp-empty { padding: 28px; border: 1px dashed var(--vip-border, #D8D2C7); border-radius: 8px; color: var(--vip-content-muted, #595959); font-size: .8125rem; text-align: center; }
|
||
.supplier-portal-page .sp-alert { padding: 10px 12px; border: 1px solid #C9D9C2; border-radius: 8px; background: #F1F7EE; color: #3F6A31; font-size: .8125rem; }
|
||
.supplier-portal-page .sp-alert.is-error { border-color: #E8C4C0; background: #FBEBE9; color: #A6392F; }
|
||
.supplier-portal-page .sp-upload { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
|
||
.supplier-portal-page .sp-summary { display: flex; flex-wrap: wrap; gap: 6px 16px; color: var(--vip-content-muted, #595959); font-size: .75rem; }
|
||
.supplier-portal-page .sp-return-table { min-width: 1420px; }
|
||
.supplier-portal-page .sp-filter { display: grid; grid-template-columns: minmax(220px, 1.6fr) repeat(2, minmax(135px, .8fr)) minmax(150px, 1fr) minmax(125px, .75fr); gap: 10px 12px; align-items: end; padding: 12px; border: 1px solid #EFEAE0; border-radius: 8px; background: #FCFAF5; }
|
||
.supplier-portal-page .sp-filter-actions { display: flex; flex-wrap: wrap; gap: 8px; grid-column: 1 / -1; }
|
||
.supplier-portal-page .sp-pagination { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; color: var(--vip-content-muted, #595959); font-size: .8125rem; }
|
||
.supplier-portal-page .sp-tag { display: inline-flex; align-items: center; height: 22px; padding: 0 8px; border-radius: 4px; background: #EFEAE0; color: #595959; font-size: .75rem; white-space: nowrap; }
|
||
.supplier-portal-page .sp-tag.is-match { background: #ECF4EC; color: #3F7A46; }
|
||
.supplier-portal-page .sp-tag.is-mismatch { background: #FBEBE9; color: #A6392F; }
|
||
.supplier-portal-page .sp-tag.is-logistics { margin-left: 4px; background: #F7F0DE; color: #856632; }
|
||
.supplier-portal-page .sp-tag.is-no-bookkeeping { margin-left: 4px; background: #EFEAE0; color: #595959; }
|
||
.supplier-portal-page tr.is-logistics td { background: #FFFDF7; }
|
||
.supplier-portal-page .sp-inline-form { display: inline-flex; }
|
||
.supplier-portal-page .sp-actions { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
|
||
@media (max-width: 960px) {
|
||
.supplier-portal-page .sp-filter { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
}
|
||
@media (max-width: 560px) {
|
||
.supplier-portal-page .sp-filter { grid-template-columns: minmax(0, 1fr); }
|
||
}
|
||
</style>
|
||
|
||
@if (is_string(session('success')))
|
||
<div class="sp-alert" role="status">{{ session('success') }}</div>
|
||
@endif
|
||
@if ($errors->has('import'))
|
||
<div class="sp-alert is-error" role="alert">{{ $errors->first('import') }}</div>
|
||
@endif
|
||
|
||
<div class="sp-card">
|
||
<div>
|
||
<h2>{{ $supplier }} · 厂家对账</h2>
|
||
<p class="sp-hint">当前登录账号只显示本厂家的上传数据和退货数据,其他厂家数据不会展示。</p>
|
||
</div>
|
||
<div class="sp-upload">
|
||
<a href="{{ $statementTemplateUrl }}" class="sp-button">下载对账单模板</a>
|
||
<form method="POST" action="{{ $uploadUrl }}" enctype="multipart/form-data" class="sp-upload">
|
||
@csrf
|
||
<input type="file" name="import" accept=".xlsx,.xls,.csv" required>
|
||
<button type="submit" class="sp-button is-primary">重新上传 {{ $supplier }} 对账单</button>
|
||
</form>
|
||
</div>
|
||
<p class="sp-hint">
|
||
请在每月 5 号前上传上月对账单。表头名称、顺序和列数必须与模板完全一致;快递名称、快递单号、采购单号可留空,
|
||
单价或金额留空时会按数量自动补齐。
|
||
</p>
|
||
<div class="sp-summary">
|
||
@if (! empty($uploads[0]))
|
||
<span>最近上传:{{ $uploads[0]['uploaded_at'] ?? '—' }}</span>
|
||
<span>文件:{{ $uploads[0]['filename'] ?? '—' }}</span>
|
||
<span>行数:{{ number_format((int) ($uploads[0]['rows'] ?? 0)) }}</span>
|
||
@else
|
||
<span>当前还没有上传过对账单。</span>
|
||
@endif
|
||
<span>退货明细:共 {{ number_format($returnPanel['total_rows']) }} 行</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sp-card">
|
||
<h2>退货明细(全部退货数据)</h2>
|
||
<p class="sp-hint">
|
||
这里展示本厂家的全部退货数据(包含待确认、已确认和异议中),按导入顺序排列。
|
||
@if ($returnPanel['is_filtered'])
|
||
当前筛选出 {{ number_format($returnPanel['total_rows']) }} 行。
|
||
@endif
|
||
</p>
|
||
<div class="sp-summary">
|
||
<span>退货合计 {{ number_format($returnPanel['total_rows']) }} 行</span>
|
||
<span>扣减合计 ¥{{ number_format($returnPanel['total_amount'], 2) }}</span>
|
||
<span>已确认 {{ $returnPanel['confirmed_count'] }} 行(¥{{ number_format($returnPanel['confirmed_amount'], 2) }})</span>
|
||
<span>异议 {{ $returnPanel['disputed_count'] }} 行(暂不扣减 ¥{{ number_format($returnPanel['disputed_amount'], 2) }})</span>
|
||
<span>待确认 {{ max($returnPanel['total_rows'] - $returnPanel['confirmed_count'] - $returnPanel['disputed_count'], 0) }} 行</span>
|
||
@if ($returnPanel['accounting_summary']['logistics_rows'] > 0)
|
||
<span>转物流记账 {{ $returnPanel['accounting_summary']['logistics_rows'] }} 行,{{ number_format($returnPanel['accounting_summary']['logistics_qty'], 2) }} 件(¥{{ number_format($returnPanel['accounting_summary']['logistics_amount'], 2) }})</span>
|
||
@endif
|
||
</div>
|
||
|
||
<form method="GET"
|
||
action="{{ route('portal.supplier-reconciliation.index') }}"
|
||
class="sp-filter"
|
||
aria-label="查询退货明细">
|
||
<label class="sp-field"><span>关键字</span>
|
||
<input type="text" name="return_keyword"
|
||
value="{{ $returnPanel['filters']['keyword'] }}"
|
||
maxlength="100"
|
||
placeholder="条形码 / 商品名称 / 供应商条码 / 配货单号 / 快递单号">
|
||
</label>
|
||
<label class="sp-field"><span>发货日期起</span>
|
||
<input type="date" name="return_date_from" value="{{ $returnPanel['filters']['date_from'] }}">
|
||
</label>
|
||
<label class="sp-field"><span>发货日期止</span>
|
||
<input type="date" name="return_date_to" value="{{ $returnPanel['filters']['date_to'] }}">
|
||
</label>
|
||
<label class="sp-field"><span>仓库名称</span>
|
||
<select name="return_warehouse">
|
||
<option value="">全部仓库</option>
|
||
@foreach ($returnWarehouses as $warehouse)
|
||
<option value="{{ $warehouse }}" @selected($returnPanel['filters']['warehouse'] === $warehouse)>{{ $warehouse }}</option>
|
||
@endforeach
|
||
</select>
|
||
</label>
|
||
<label class="sp-field"><span>确认状态</span>
|
||
<select name="return_status">
|
||
<option value="">全部状态</option>
|
||
<option value="pending" @selected($returnPanel['filters']['status'] === 'pending')>待确认</option>
|
||
<option value="confirmed" @selected($returnPanel['filters']['status'] === 'confirmed')>已确认</option>
|
||
<option value="disputed" @selected($returnPanel['filters']['status'] === 'disputed')>异议中</option>
|
||
</select>
|
||
</label>
|
||
<div class="sp-filter-actions">
|
||
<button type="submit" class="sp-button is-primary">查询</button>
|
||
<a class="sp-button" href="{{ route('portal.supplier-reconciliation.index') }}">重置</a>
|
||
<a class="sp-button"
|
||
href="{{ route('portal.supplier-reconciliation.returns.export', $returnPanel['filter_query']) }}">
|
||
导出当前筛选结果({{ number_format($returnPanel['total_rows']) }} 行)
|
||
</a>
|
||
</div>
|
||
</form>
|
||
|
||
<div class="sp-scroll">
|
||
<table class="sp-return-table">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">发货时间</th><th scope="col">明细供应商</th><th scope="col">条形码</th>
|
||
<th scope="col">商品名称</th><th scope="col">发货量</th><th scope="col">单价</th>
|
||
<th scope="col">金额</th><th scope="col">供应商条码</th><th scope="col">备注</th>
|
||
<th scope="col">标题</th><th scope="col">wms配货单号</th><th scope="col">仓库名称</th>
|
||
<th scope="col">快递单号</th><th scope="col">类型</th><th scope="col">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@forelse ($returnPanel['rows'] as $row)
|
||
<tr @class(['is-logistics' => $row['_accounting_target'] === '物流'])>
|
||
<td>{{ $row['send_time'] ?? '—' }}</td>
|
||
<td>{{ $row['supplier'] ?? '—' }}</td>
|
||
<td>{{ $row['barcode'] ?? '—' }}</td>
|
||
<td>{{ $row['sku_name'] ?? '—' }}</td>
|
||
<td class="is-num">{{ $row['qty'] ?? '—' }}</td>
|
||
<td class="is-num">{{ $row['unit_price'] ?? '—' }}</td>
|
||
<td class="is-num">{{ $row['amount'] ?? '—' }}</td>
|
||
<td>{{ $row['supplier_barcode'] ?? '—' }}</td>
|
||
<td>
|
||
{{ $row['remark'] ?? '—' }}
|
||
@if ($row['_accounting_target'] !== '厂家')
|
||
<span class="sp-tag {{ $row['_accounting_target'] === '物流' ? 'is-logistics' : 'is-no-bookkeeping' }}">{{ $row['_accounting_label'] }}</span>
|
||
@endif
|
||
@if ($row['_accounting_note'] !== '按厂家退货扣减')
|
||
<div class="sp-muted" style="font-size:.7rem">{{ $row['_accounting_note'] }}</div>
|
||
@endif
|
||
</td>
|
||
<td>{{ $row['title'] ?? '—' }}</td>
|
||
<td>{{ $row['wms_pick_no'] ?? '—' }}</td>
|
||
<td>{{ $row['warehouse'] ?? '—' }}</td>
|
||
<td>{{ $row['express_no'] ?? '—' }}</td>
|
||
<td><span class="sp-tag">退货</span></td>
|
||
<td>
|
||
<span class="sp-actions">
|
||
@if ($row['status'] === 'confirmed')
|
||
<span class="sp-tag is-match">已确认</span>
|
||
@elseif ($row['status'] === 'disputed')
|
||
<span class="sp-tag is-mismatch">异议中</span>
|
||
@else
|
||
<form method="POST" action="{{ $returnConfirmUrl }}" class="sp-inline-form">
|
||
@csrf
|
||
<input type="hidden" name="key" value="{{ $row['_key'] }}">
|
||
<button type="submit" class="sp-button is-small is-primary">确认</button>
|
||
</form>
|
||
<form method="POST" action="{{ $returnDisputeUrl }}" class="sp-inline-form">
|
||
@csrf
|
||
<input type="hidden" name="key" value="{{ $row['_key'] }}">
|
||
<button type="submit" class="sp-button is-small">异议</button>
|
||
</form>
|
||
@endif
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
@empty
|
||
<tr><td colspan="15"><div class="sp-empty">没有符合当前查询条件的退货数据。</div></td></tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
@if ($returnPanel['pages'] > 1)
|
||
@php
|
||
$pageCount = $returnPanel['pages'];
|
||
$pageCurrent = $returnPanel['page'];
|
||
$pageStart = max(1, min($pageCurrent - 4, $pageCount - 8));
|
||
$pageEnd = min($pageCount, $pageStart + 8);
|
||
$pageUrl = fn (int $number): string => route('portal.supplier-reconciliation.index', array_merge($returnPanel['filter_query'], [
|
||
'rows_page' => $rowsPage > 1 ? $rowsPage : null,
|
||
'return_page' => $number,
|
||
]));
|
||
@endphp
|
||
<div class="sp-pagination">
|
||
<span>第 {{ $pageCurrent }} / {{ $pageCount }} 页(每页 {{ $returnPanel['per_page'] }} 行)</span>
|
||
@if ($pageCurrent > 1)
|
||
<a class="sp-button is-small" href="{{ $pageUrl($pageCurrent - 1) }}">上一页</a>
|
||
@endif
|
||
@for ($number = $pageStart; $number <= $pageEnd; $number++)
|
||
<a class="sp-button is-small {{ $number === $pageCurrent ? 'is-primary' : '' }}"
|
||
@if ($number === $pageCurrent) aria-current="page" @endif
|
||
href="{{ $pageUrl($number) }}">{{ $number }}</a>
|
||
@endfor
|
||
@if ($pageCurrent < $pageCount)
|
||
<a class="sp-button is-small" href="{{ $pageUrl($pageCurrent + 1) }}">下一页</a>
|
||
@endif
|
||
</div>
|
||
@endif
|
||
|
||
<div class="sp-toolbar">
|
||
<form method="POST" action="{{ $returnConfirmAllUrl }}" class="sp-inline-form">
|
||
@csrf
|
||
@foreach ($returnPanel['filter_query'] as $filterName => $filterValue)
|
||
<input type="hidden" name="{{ $filterName }}" value="{{ $filterValue }}">
|
||
@endforeach
|
||
<button type="submit" class="sp-button is-primary">
|
||
{{ $returnPanel['is_filtered'] ? '全部确认当前筛选' : '全部确认无异议' }}
|
||
</button>
|
||
</form>
|
||
<span class="sp-hint">确认后退货扣减生效;异议行在财务复核前暂不扣减。</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sp-card">
|
||
<h2>{{ $supplier }} · 我上传的对账数据(共 {{ number_format($statementTotal) }} 行)</h2>
|
||
<div class="sp-scroll">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">快递名称</th><th scope="col">快递单号</th><th scope="col">单据编号(采购单号)</th>
|
||
<th scope="col">创建时间</th><th scope="col">供应商</th><th scope="col">条码</th>
|
||
<th scope="col">规格名称</th><th scope="col">数量</th><th scope="col">单价</th><th scope="col">金额</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@forelse ($statementRows as $row)
|
||
<tr>
|
||
<td class="sp-muted">{{ $row['expressName'] ?? '—' }}</td>
|
||
<td class="sp-muted">{{ $row['expressNo'] ?? '—' }}</td>
|
||
<td class="sp-muted">{{ $row['purchaseNo'] ?? '—' }}</td>
|
||
<td>{{ $row['createdAt'] ?? '—' }}</td>
|
||
<td>{{ $row['supplier'] ?? '—' }}</td>
|
||
<td>{{ $row['barcode'] ?? '—' }}</td>
|
||
<td>{{ $row['skuName'] ?? '—' }}</td>
|
||
<td class="is-num">{{ $row['qty'] ?? '—' }}</td>
|
||
<td class="is-num">{{ $row['unitPrice'] ?? '—' }}</td>
|
||
<td class="is-num">{{ $row['amount'] ?? '—' }}</td>
|
||
</tr>
|
||
@empty
|
||
<tr><td colspan="10"><div class="sp-empty">还没有上传过对账单。</div></td></tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if ($statementTotal > $perPage)
|
||
@php
|
||
$statementPageCount = max((int) ceil($statementTotal / $perPage), 1);
|
||
$statementPageStart = max(1, min($rowsPage - 4, $statementPageCount - 8));
|
||
$statementPageEnd = min($statementPageCount, $statementPageStart + 8);
|
||
$statementPageUrl = fn (int $number): string => route('portal.supplier-reconciliation.index', array_merge($returnPanel['filter_query'], [
|
||
'rows_page' => $number,
|
||
'return_page' => $returnPanel['page'] > 1 ? $returnPanel['page'] : null,
|
||
]));
|
||
@endphp
|
||
<div class="sp-pagination">
|
||
<span>第 {{ $rowsPage }} / {{ $statementPageCount }} 页(每页 {{ $perPage }} 行)</span>
|
||
@if ($rowsPage > 1)
|
||
<a class="sp-button is-small" href="{{ $statementPageUrl($rowsPage - 1) }}">上一页</a>
|
||
@endif
|
||
@for ($number = $statementPageStart; $number <= $statementPageEnd; $number++)
|
||
<a class="sp-button is-small {{ $number === $rowsPage ? 'is-primary' : '' }}"
|
||
@if ($number === $rowsPage) aria-current="page" @endif
|
||
href="{{ $statementPageUrl($number) }}">{{ $number }}</a>
|
||
@endfor
|
||
@if ($rowsPage < $statementPageCount)
|
||
<a class="sp-button is-small" href="{{ $statementPageUrl($rowsPage + 1) }}">下一页</a>
|
||
@endif
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</x-slot>
|
||
</x-layouts.portal>
|