299 lines
21 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.

<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-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>
</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>
<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'] ?? '—' }}</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>