renshen db08b2c264 供应商对账:新增财务比对汇总导出 (#407)
财务端比对明细新增CSV导出,包含筛选汇总和完整比对结果。
2026-10-20 17:29:46 +08:00

569 lines
39 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.

<div class="supplier-reconciliation-page">
<style>
.supplier-reconciliation-page { display: grid; gap: 16px; }
.supplier-reconciliation-page .sr-card { min-width: 0; max-width: 100%; padding: 16px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: var(--vip-surface, #fff); display: grid; gap: 12px; }
.supplier-reconciliation-page h2 { margin: 0; font-size: .875rem; font-weight: 600; color: var(--vip-content-ink, #171717); }
.supplier-reconciliation-page .sr-toolbar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; }
.supplier-reconciliation-page .sr-filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin: 0; }
.supplier-reconciliation-page .sr-field { display: grid; gap: 4px; }
.supplier-reconciliation-page .sr-field span { color: var(--vip-content-muted, #595959); font-size: .75rem; font-weight: 600; }
.supplier-reconciliation-page select,
.supplier-reconciliation-page input[type="search"],
.supplier-reconciliation-page input[type="url"],
.supplier-reconciliation-page input[type="password"] { height: 36px; min-width: 12rem; padding: 0 10px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: #fff; font-size: .8125rem; }
.supplier-reconciliation-page .sr-button { display: inline-flex; height: 36px; align-items: 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-reconciliation-page .sr-button.is-primary { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-dark, #856632); color: #fff; }
.supplier-reconciliation-page .sr-button.is-disabled { opacity: .55; cursor: not-allowed; }
.supplier-reconciliation-page .sr-button.is-small { height: 26px; padding: 0 9px; font-size: .75rem; }
.supplier-reconciliation-page .sr-hint { margin: 0; color: var(--vip-content-muted, #595959); font-size: .75rem; line-height: 1.7; }
.supplier-reconciliation-page .sr-scroll { min-width: 0; max-width: 100%; overflow-x: auto; scrollbar-width: thin; }
.supplier-reconciliation-page table { width: 100%; min-width: 720px; border-collapse: collapse; font-size: .8125rem; }
.supplier-reconciliation-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-reconciliation-page td { padding: 8px 10px; border-bottom: 1px solid #EFEAE0; vertical-align: middle; }
.supplier-reconciliation-page td.is-num { text-align: right; font-variant-numeric: tabular-nums; }
.supplier-reconciliation-page .sr-muted { color: var(--vip-content-muted, #595959); }
.supplier-reconciliation-page .sr-tag { display: inline-flex; align-items: center; height: 22px; padding: 0 8px; border-radius: 4px; font-size: .75rem; white-space: nowrap; }
.supplier-reconciliation-page .sr-tag.is-match { background: #ECF4EC; color: #3F7A46; }
.supplier-reconciliation-page .sr-tag.is-extra { background: #FDF1E2; color: #B4650A; }
.supplier-reconciliation-page .sr-tag.is-mismatch { background: #FBEBE9; color: #A6392F; }
.supplier-reconciliation-page .sr-tag.is-missing { background: #EAF2F9; color: #2F6FA8; }
.supplier-reconciliation-page .sr-tag.is-logistics { background: #F7F0DE; color: #856632; }
.supplier-reconciliation-page .sr-tag.is-no-bookkeeping { background: #EFEAE0; color: #595959; }
.supplier-reconciliation-page tr.is-extra td,
.supplier-reconciliation-page tr.is-mismatch td { background: #FFFDF7; }
.supplier-reconciliation-page tr.is-logistics td { background: #FFFDF7; }
.supplier-reconciliation-page .sr-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; }
.supplier-reconciliation-page .sr-summary-cell { padding: 10px 12px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; display: grid; gap: 4px; }
.supplier-reconciliation-page .sr-summary-cell b { font-size: 1.125rem; font-weight: 600; }
.supplier-reconciliation-page .sr-summary-cell span { color: var(--vip-content-muted, #595959); font-size: .75rem; }
.supplier-reconciliation-page .sr-comparison-scroll { max-height: min(62vh, 560px); overflow: auto; scrollbar-gutter: stable; }
.supplier-reconciliation-page .sr-comparison-scroll thead th { position: sticky; top: 0; z-index: 4; box-shadow: 0 1px 0 var(--vip-border, #D8D2C7); }
.supplier-reconciliation-page .sr-pagination { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; color: var(--vip-content-muted, #595959); font-size: .8125rem; }
.supplier-reconciliation-page .sr-robot-config { padding: 10px 12px; border: 1px solid #EFEAE0; border-radius: 8px; background: #FCFAF5; }
.supplier-reconciliation-page .sr-robot-config summary { cursor: pointer; color: #5B5343; font-size: .8125rem; font-weight: 600; }
.supplier-reconciliation-page .sr-robot-form { display: grid; grid-template-columns: minmax(260px, 2fr) minmax(180px, 1fr) auto; gap: 10px; align-items: end; margin-top: 12px; }
.supplier-reconciliation-page .sr-robot-check { display: flex; align-items: center; gap: 6px; min-height: 36px; color: var(--vip-content-muted, #595959); font-size: .75rem; }
.supplier-reconciliation-page .sr-select-cell { width: 34px; text-align: center; }
.supplier-reconciliation-page .sr-select-cell input { width: 15px; height: 15px; accent-color: var(--vip-content-accent-dark, #856632); }
.supplier-reconciliation-page .sr-robot-form input[type="url"],
.supplier-reconciliation-page .sr-robot-form input[type="password"] { width: 100%; min-width: 0; }
@media (max-width: 900px) {
.supplier-reconciliation-page .sr-robot-form { grid-template-columns: minmax(0, 1fr); }
}
.supplier-reconciliation-page .sr-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; }
</style>
@if (is_string(session('success')))
<div class="biz-alert" role="status">{{ session('success') }}</div>
@endif
@if ($errors->has('import'))
<div class="biz-alert biz-alert-error" role="alert">{{ $errors->first('import') }}</div>
@endif
@if ($errors->has('dingtalk_push') || $errors->has('dingtalk_webhook') || $errors->has('dingtalk_secret'))
<div class="biz-alert biz-alert-error" role="alert">
{{ $errors->first('dingtalk_push') ?: $errors->first('dingtalk_webhook') ?: $errors->first('dingtalk_secret') }}
</div>
@endif
<div class="sr-card">
<form method="GET" class="sr-toolbar" aria-label="供应商对账筛选">
<div class="sr-filters">
<label class="sr-field"><span>月份</span>
<select name="month" onchange="this.form.submit()">
<option value="">全部月份</option>
@foreach ($months as $optionMonth)
<option value="{{ $optionMonth }}" @selected($month === $optionMonth)>{{ $optionMonth }}</option>
@endforeach
</select>
</label>
<label class="sr-field"><span>对账人</span>
<select name="reconciler" onchange="this.form.submit()">
<option value="">全部对账人</option>
@foreach ($reconcilers as $optionReconciler)
<option value="{{ $optionReconciler }}" @selected($reconciler === $optionReconciler)>{{ $optionReconciler }}</option>
@endforeach
</select>
</label>
<label class="sr-field"><span>供应商</span>
<input type="search"
name="supplier"
value="{{ $supplierQuery !== '' ? $supplierQuery : $selectedSupplier }}"
list="finance-supplier-options"
placeholder="输入厂家名称搜索"
autocomplete="off">
<datalist id="finance-supplier-options">
@foreach ($supplierNames as $supplier)
<option value="{{ $supplier }}"></option>
@endforeach
</datalist>
</label>
<button type="submit" class="sr-button is-primary">查询</button>
</div>
<span style="flex:1 1 auto"></span>
<a href="{{ $statementTemplateUrl }}" class="sr-button">下载对账单模板</a>
@canany(['read-purchases-vendors', 'create-purchases-vendors'])
<a href="{{ route('vendors.index') }}" class="sr-button">厂家登录账号管理</a>
@endcanany
</form>
@if ($supplierNotFound)
<div class="biz-alert biz-alert-error" role="alert">
未找到名为「{{ $supplierQuery }}」的供应商,请输入完整名称并从搜索建议中选择。
</div>
@endif
<p class="sr-hint">
比对口径:按「采购单号 + 日期 + 条码」核对,同一采购单号、日期和条码的多行先合并数量与金额。
数量、金额一致 → 正常结算;厂家多(我方没有)→ 不结算、退回修改;
数量或金额不一致 → 明确列出双方数量、单价、金额及厂家多算部分,取金额低的一方结算;
厂家少(我方有)→ 按厂家数据结算(不付款)。厂家未填采购单号时,仅在同日期+条码能唯一匹配我方一条数据时兜底。<br>
厂家登录:在「厂家登录账号管理」新增供应商并勾选可登录,供应商名称必须与对账数据中的厂家名称完全一致;
厂家登录后只能查看自己的对账单上传记录、全部退货明细及导出数据。
</p>
</div>
@if ($detail)
@php
$differenceCount = (int) $detail['summary']['supplierExtra']
+ (int) $detail['summary']['mismatch']
+ (int) $detail['summary']['supplierMissing'];
@endphp
<div class="sr-card">
<div class="sr-toolbar">
<h2>{{ $selectedSupplier }} · 比对明细</h2>
<span class="sr-muted" style="font-size:.75rem">
月份 {{ $month !== '' ? $month : '全部' }}
@if ($reconciler !== '') · 对账人 {{ $reconciler }} @endif
@if (($uploads[$selectedSupplier]['uploaded_at'] ?? null) !== null)
· 最近上传 {{ $uploads[$selectedSupplier]['uploaded_at'] }}({{ number_format((int) ($uploads[$selectedSupplier]['rows'] ?? 0)) }} 行)
@else
· 该厂家还没有上传对账单
@endif
</span>
<span style="flex:1 1 auto"></span>
<a href="{{ $listUrl }}" class="sr-button">← 返回供应商列表</a>
<a href="{{ route('business.supplier-reconciliation.comparison.export', array_filter([
'supplier' => $selectedSupplier,
'month' => $month,
'reconciler' => $reconciler,
], static fn (mixed $value): bool => $value !== '' && $value !== null)) }}"
class="sr-button">导出比对汇总</a>
<a href="{{ $statementTemplateUrl }}" class="sr-button">下载对账单模板</a>
@if ($differenceCount > 0)
<form method="POST"
action="{{ route('business.supplier-reconciliation.push-differences') }}"
onsubmit="return confirm('确认将当前筛选的 {{ $differenceCount }} 条差异推送到该厂家钉钉群?')">
@csrf
<input type="hidden" name="supplier" value="{{ $selectedSupplier }}">
<input type="hidden" name="month" value="{{ $month }}">
<input type="hidden" name="reconciler" value="{{ $reconciler }}">
<input type="hidden" name="scope" value="all">
<button type="submit"
class="sr-button is-primary {{ $robot['configured'] ? '' : 'is-disabled' }}"
@disabled(! $robot['configured'])
title="{{ $robot['configured'] ? '推送当前筛选的全部差异' : '请先在下方配置厂家钉钉群机器人' }}">
推送全部差异({{ $differenceCount }} 条)
</button>
</form>
<form id="push-selected-form"
method="POST"
action="{{ route('business.supplier-reconciliation.push-differences') }}">
@csrf
<input type="hidden" name="supplier" value="{{ $selectedSupplier }}">
<input type="hidden" name="month" value="{{ $month }}">
<input type="hidden" name="reconciler" value="{{ $reconciler }}">
<input type="hidden" name="scope" value="selected">
<button id="push-selected-button"
type="submit"
class="sr-button is-primary is-disabled"
disabled
title="先在表格中勾选需要推送的差异">
推送选中(<span id="push-selected-count">0</span>)
</button>
</form>
@endif
</div>
<details class="sr-robot-config" @if (! $robot['configured']) open @endif>
<summary>
厂家钉钉群机器人:
{{ $robot['configured'] ? '已配置' : '未配置,配置后才能推送差异' }}
@if ($robot['last_pushed_at'] !== '')
· 上次推送 {{ $robot['last_pushed_at'] }}({{ $robot['last_push_count'] }} 条)
@endif
</summary>
<form method="POST"
action="{{ route('business.supplier-reconciliation.dingtalk-robot.save') }}"
class="sr-robot-form">
@csrf
<input type="hidden" name="supplier" value="{{ $selectedSupplier }}">
<label class="sr-field"><span>机器人 Webhook</span>
<input type="url"
name="dingtalk_webhook"
value=""
placeholder="{{ $robot['configured'] ? $robot['webhook_hint'] . '(留空表示不更换)' : 'https://oapi.dingtalk.com/robot/send?access_token=...' }}"
autocomplete="off">
</label>
<label class="sr-field"><span>加签密钥(可选)</span>
<input type="password"
name="dingtalk_secret"
value=""
placeholder="{{ $robot['configured'] ? '留空保留原密钥' : '机器人安全设置中的加签密钥' }}"
autocomplete="new-password">
</label>
<div>
<label class="sr-robot-check">
<input type="checkbox" name="dingtalk_at_all" value="1" @checked($robot['at_all'])>
推送时 @ 所有人(默认关闭)
</label>
<button type="submit" class="sr-button">保存机器人</button>
</div>
</form>
<p class="sr-hint">
在厂家钉钉群的「群设置 → 智能群助手 → 添加机器人 → 自定义」中复制 Webhook;如机器人启用加签,请同时填写加签密钥。
如机器人使用关键词校验,请把关键词设为「货款对账」。
Webhook 和密钥保存后仅用于本系统推送,页面不会再次展示完整内容。
</p>
</details>
</div>
@else
<div class="sr-card">
<h2>各厂家上传情况(实时)</h2>
<div class="sr-scroll">
<table>
<thead>
<tr>
<th scope="col">供应商</th>
<th scope="col">最近上传时间</th>
<th scope="col">上传行数</th>
<th scope="col">比对</th>
</tr>
</thead>
<tbody>
@forelse ($supplierNames as $supplier)
@php $upload = $uploads[$supplier] ?? null; @endphp
<tr>
<td>{{ $supplier }}</td>
<td class="sr-muted">{{ $upload['uploaded_at'] ?? '—' }}</td>
<td class="is-num">{{ $upload ? number_format((int) ($upload['rows'] ?? 0)) : '—' }}</td>
<td>
<a class="sr-button" href="{{ route('business.pages.show', ['section' => $section['slug'], 'page' => $page['slug'], 'month' => $month, 'reconciler' => $reconciler, 'supplier' => $supplier]) }}">查看比对</a>
</td>
</tr>
@empty
<tr><td colspan="4"><div class="sr-empty">还没有可对账的供应商:请先在「供应商应付(终版)→ 货款对账」导入我方结算数据。</div></td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
@endif
@if ($detail)
<div class="sr-card">
<h2>比对汇总(我方 {{ number_format($detail['mineRows']) }} 行 / 厂家 {{ number_format($detail['theirsRows']) }} 行)</h2>
<div class="sr-summary">
<div class="sr-summary-cell"><span>双方一致</span><b>{{ $detail['summary']['match'] }}</b></div>
<div class="sr-summary-cell"><span>厂家多(退回修改)</span><b>{{ $detail['summary']['supplierExtra'] }}</b></div>
<div class="sr-summary-cell"><span>数量/金额不一致(按低结算)</span><b>{{ $detail['summary']['mismatch'] }}</b></div>
<div class="sr-summary-cell"><span>厂家少(按厂家结算)</span><b>{{ $detail['summary']['supplierMissing'] }}</b></div>
<div class="sr-summary-cell"><span>我方金额合计</span><b>{{ number_format($detail['summary']['mineAmount'], 2) }}</b></div>
<div class="sr-summary-cell"><span>厂家金额合计</span><b>{{ number_format($detail['summary']['theirsAmount'], 2) }}</b></div>
<div class="sr-summary-cell"><span>厂家多算金额</span><b>{{ number_format($detail['summary']['supplierOverAmount'], 2) }}</b></div>
<div class="sr-summary-cell"><span>按口径结算合计</span><b>{{ number_format($detail['summary']['settleAmount'], 2) }}</b></div>
<div class="sr-summary-cell"><span>差异金额</span><b>{{ number_format($detail['summary']['differenceAmount'], 2) }}</b></div>
</div>
<div class="sr-scroll sr-comparison-scroll">
<table>
<thead>
<tr>
<th scope="col" class="sr-select-cell">
<input id="select-all-differences" type="checkbox" aria-label="选择全部差异">
</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">厂家金额</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 ($detail['rows'] as $row)
@php
$tagClass = match ($row['type']) {
'厂家多' => 'is-extra',
'数量/金额不一致' => 'is-mismatch',
'厂家少' => 'is-missing',
default => 'is-match',
};
@endphp
<tr class="{{ $tagClass }}">
<td class="sr-select-cell">
@if ($row['type'] !== '一致')
<input class="js-difference-checkbox"
form="push-selected-form"
type="checkbox"
name="selected_rows[]"
value="{{ json_encode(['purchaseNo' => $row['purchaseNo'], 'date' => $row['date'], 'barcode' => $row['barcode'], 'type' => $row['type']], JSON_UNESCAPED_UNICODE) }}"
aria-label="选择差异 {{ $row['barcode'] }}">
@else
<span class="sr-muted">—</span>
@endif
</td>
<td><span class="sr-tag {{ $tagClass }}">{{ $row['type'] }}</span></td>
<td>{{ $row['purchaseNo'] !== '' ? $row['purchaseNo'] : '—' }}</td>
<td>{{ $row['date'] !== '' ? $row['date'] : '—' }}</td>
<td>
{{ $row['barcode'] }}
@if (! empty($row['supplierBarcode']))
<span class="sr-muted" style="font-size:.7rem">(厂家:{{ $row['supplierBarcode'] }})</span>
@endif
</td>
<td class="is-num">{{ $row['mineQty'] !== 0.0 ? number_format($row['mineQty'], 2) : '—' }}</td>
<td class="is-num">{{ $row['mineUnitPrice'] !== 0.0 ? number_format($row['mineUnitPrice'], 2) : '—' }}</td>
<td class="is-num">{{ $row['mineAmount'] !== 0.0 ? number_format($row['mineAmount'], 2) : '—' }}</td>
<td class="is-num">{{ $row['theirsQty'] !== 0.0 ? number_format($row['theirsQty'], 2) : '—' }}</td>
<td class="is-num">{{ $row['theirsUnitPrice'] !== 0.0 ? number_format($row['theirsUnitPrice'], 2) : '—' }}</td>
<td class="is-num">{{ $row['theirsAmount'] !== 0.0 ? number_format($row['theirsAmount'], 2) : '—' }}</td>
<td class="is-num">
@if (abs($row['qtyDifference']) < 0.005)
一致
@else
厂家{{ $row['qtyDifference'] > 0 ? '多' : '少' }} {{ number_format(abs($row['qtyDifference']), 2) }}
@endif
</td>
<td class="is-num">
@if (abs($row['amountDifference']) < 0.005)
一致
@else
厂家{{ $row['amountDifference'] > 0 ? '多' : '少' }} ¥{{ number_format(abs($row['amountDifference']), 2) }}
@endif
</td>
<td class="sr-muted">{{ $row['note'] }}</td>
<td class="is-num">{{ number_format($row['settleAmount'], 2) }}</td>
<td>
@if ($row['type'] !== '一致')
<form method="POST"
action="{{ route('business.supplier-reconciliation.push-differences') }}"
onsubmit="return confirm('确认将这条差异推送到该厂家钉钉群?')">
@csrf
<input type="hidden" name="supplier" value="{{ $selectedSupplier }}">
<input type="hidden" name="month" value="{{ $month }}">
<input type="hidden" name="reconciler" value="{{ $reconciler }}">
<input type="hidden" name="scope" value="row">
<input type="hidden" name="purchase_no" value="{{ $row['purchaseNo'] }}">
<input type="hidden" name="date" value="{{ $row['date'] }}">
<input type="hidden" name="barcode" value="{{ $row['barcode'] }}">
<input type="hidden" name="difference_type" value="{{ $row['type'] }}">
<button type="submit"
class="sr-button is-small {{ $robot['configured'] ? 'is-primary' : 'is-disabled' }}"
@disabled(! $robot['configured'])
title="{{ $robot['configured'] ? '只推送这条差异' : '请先配置厂家钉钉群机器人' }}">
推送本条
</button>
</form>
@else
<span class="sr-muted">—</span>
@endif
</td>
</tr>
@empty
<tr><td colspan="16"><div class="sr-empty">该厂家还没有上传对账单,或我方还没有这家供应商的结算数据。</div></td></tr>
@endforelse
</tbody>
</table>
</div>
<div class="sr-pagination">
@php
$comparisonPageCount = max((int) ($detail['pages'] ?? 1), 1);
$comparisonPageCurrent = min(max((int) ($detail['page'] ?? 1), 1), $comparisonPageCount);
$comparisonPageStart = max(1, min($comparisonPageCurrent - 4, $comparisonPageCount - 8));
$comparisonPageEnd = min($comparisonPageCount, $comparisonPageStart + 8);
$comparisonPageUrl = fn (int $number): string => route('business.pages.show', array_merge(request()->query(), [
'section' => $section['slug'],
'page' => $page['slug'],
'compare_page' => $number,
]));
@endphp
<span>
共 {{ number_format((int) ($detail['total_rows'] ?? 0)) }} 条
· 每页 {{ number_format((int) ($detail['per_page'] ?? 15)) }} 条
· 第 {{ $comparisonPageCurrent }} / {{ $comparisonPageCount }} 页
</span>
@if ($comparisonPageCurrent > 1)
<a class="sr-button is-small" href="{{ $comparisonPageUrl($comparisonPageCurrent - 1) }}">上一页</a>
@endif
@if ($comparisonPageStart > 1)
<a class="sr-button is-small" href="{{ $comparisonPageUrl(1) }}">1</a>
@if ($comparisonPageStart > 2) <span>…</span> @endif
@endif
@for ($number = $comparisonPageStart; $number <= $comparisonPageEnd; $number++)
<a class="sr-button is-small {{ $number === $comparisonPageCurrent ? 'is-primary' : '' }}"
@if ($number === $comparisonPageCurrent) aria-current="page" @endif
href="{{ $comparisonPageUrl($number) }}">{{ $number }}</a>
@endfor
@if ($comparisonPageEnd < $comparisonPageCount)
@if ($comparisonPageEnd < $comparisonPageCount - 1) <span>…</span> @endif
<a class="sr-button is-small" href="{{ $comparisonPageUrl($comparisonPageCount) }}">{{ $comparisonPageCount }}</a>
@endif
@if ($comparisonPageCurrent < $comparisonPageCount)
<a class="sr-button is-small" href="{{ $comparisonPageUrl($comparisonPageCurrent + 1) }}">下一页</a>
@endif
</div>
</div>
@php $returnAccounting = $financeReturnPanel['accounting_summary']; @endphp
<div class="sr-card">
<div class="sr-toolbar">
<h2>退货扣减 · 财务与厂家同步</h2>
<span class="sr-muted" style="font-size:.75rem">
直接读取「应付款终版 → 退货对账」同一数据集;修改退货数量、备注、标题后,财务端和厂家门户同时更新。
</span>
</div>
<div class="sr-summary">
<div class="sr-summary-cell"><span>退货行数</span><b>{{ number_format($returnAccounting['count']) }}</b></div>
<div class="sr-summary-cell"><span>厂家净扣减</span><b>¥{{ number_format($returnAccounting['supplier_amount'], 2) }}</b></div>
<div class="sr-summary-cell"><span>转物流记账</span><b>{{ number_format($returnAccounting['logistics_qty'], 2) }} 件</b></div>
<div class="sr-summary-cell"><span>转物流金额</span><b>¥{{ number_format($returnAccounting['logistics_amount'], 2) }}</b></div>
<div class="sr-summary-cell"><span>不记账行数</span><b>{{ number_format($returnAccounting['no_bookkeeping_rows']) }}</b></div>
</div>
@if ($returnAccounting['logistics_rows'] > 0)
<p class="sr-hint">
已识别 {{ number_format($returnAccounting['logistics_rows']) }} 行“不扣本厂家、转物流记账”
@if (! empty($returnAccounting['logistics_companies']))
,物流公司:{{ implode('、', $returnAccounting['logistics_companies']) }}
@endif
。厂家端显示相同备注和“转物流记账”标记,厂家扣减金额按调整后的净数量/金额计算。
</p>
@endif
<div class="sr-scroll">
<table class="sr-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">快递单号</th><th scope="col">对账人</th><th scope="col">确认状态</th>
</tr>
</thead>
<tbody>
@forelse ($financeReturnPanel['rows'] as $row)
<tr @class(['is-logistics' => $row['_accounting_target'] === '物流'])>
<td>{{ $row['send_time'] ?? '—' }}</td>
<td>{{ $row['barcode'] ?? '—' }}</td>
<td>{{ $row['sku_name'] ?? '—' }}</td>
<td class="is-num">{{ $row['_accounting_target'] === '不记账' ? '—' : number_format($row['_supplier_qty'], 2) }}</td>
<td class="is-num">{{ $row['_accounting_target'] === '不记账' ? '—' : number_format($row['_supplier_amount'], 2) }}</td>
<td>
<span class="sr-tag {{ $row['_accounting_target'] === '物流' ? 'is-logistics' : ($row['_accounting_target'] === '不记账' ? 'is-no-bookkeeping' : 'is-match') }}">
{{ $row['_accounting_label'] }}
</span>
@if ($row['_accounting_note'] !== '按厂家退货扣减')
<div class="sr-muted" style="font-size:.7rem">{{ $row['_accounting_note'] }}</div>
@endif
</td>
<td class="is-num">{{ $row['_logistics_qty'] > 0 ? number_format($row['_logistics_qty'], 2) : '—' }}</td>
<td class="is-num">{{ $row['_logistics_amount'] > 0 ? number_format($row['_logistics_amount'], 2) : '—' }}</td>
<td>{{ $row['_logistics_company'] !== '' ? $row['_logistics_company'] : '—' }}</td>
<td>{{ $row['remark'] ?? '—' }}</td>
<td>{{ $row['express_no'] ?? '—' }}</td>
<td>{{ $row['reconciler'] ?? '—' }}</td>
<td>
@if ($row['status'] === 'confirmed')
<span class="sr-tag is-match">已确认</span>
@elseif ($row['status'] === 'disputed')
<span class="sr-tag is-mismatch">异议中</span>
@else
<span class="sr-muted">待确认</span>
@endif
</td>
</tr>
@empty
<tr><td colspan="13"><div class="sr-empty">当前月份和对账人筛选下没有退货数据。</div></td></tr>
@endforelse
</tbody>
</table>
</div>
@if ($financeReturnPanel['pages'] > 1)
@php
$returnPageCount = $financeReturnPanel['pages'];
$returnPageCurrent = $financeReturnPanel['page'];
$returnPageStart = max(1, min($returnPageCurrent - 4, $returnPageCount - 8));
$returnPageEnd = min($returnPageCount, $returnPageStart + 8);
$returnPageUrl = fn (int $number): string => route('business.pages.show', array_filter([
'section' => $section['slug'],
'page' => $page['slug'],
'supplier' => $selectedSupplier,
'month' => $month,
'reconciler' => $reconciler,
'finance_return_page' => $number,
], static fn (mixed $value): bool => $value !== null && $value !== ''));
@endphp
<div class="sr-pagination">
<span>第 {{ $returnPageCurrent }} / {{ $returnPageCount }} 页(每页 {{ $financeReturnPanel['per_page'] }} 行)</span>
@if ($returnPageCurrent > 1)
<a class="sr-button is-small" href="{{ $returnPageUrl($returnPageCurrent - 1) }}">上一页</a>
@endif
@for ($number = $returnPageStart; $number <= $returnPageEnd; $number++)
<a class="sr-button is-small {{ $number === $returnPageCurrent ? 'is-primary' : '' }}"
@if ($number === $returnPageCurrent) aria-current="page" @endif
href="{{ $returnPageUrl($number) }}">{{ $number }}</a>
@endfor
@if ($returnPageCurrent < $returnPageCount)
<a class="sr-button is-small" href="{{ $returnPageUrl($returnPageCurrent + 1) }}">下一页</a>
@endif
</div>
@endif
</div>
@endif
</div>
@if ($detail && $differenceCount > 0)
<script>
(() => {
const form = document.getElementById('push-selected-form');
const button = document.getElementById('push-selected-button');
const countLabel = document.getElementById('push-selected-count');
const selectAll = document.getElementById('select-all-differences');
const checkboxes = Array.from(document.querySelectorAll('.js-difference-checkbox'));
const robotConfigured = @json($robot['configured']);
if (!form || !button || !countLabel || !selectAll) {
return;
}
const updateSelection = () => {
const selected = checkboxes.filter((checkbox) => checkbox.checked).length;
countLabel.textContent = String(selected);
button.disabled = !robotConfigured || selected === 0;
button.classList.toggle('is-disabled', button.disabled);
selectAll.checked = selected > 0 && selected === checkboxes.length;
selectAll.indeterminate = selected > 0 && selected < checkboxes.length;
};
checkboxes.forEach((checkbox) => checkbox.addEventListener('change', updateSelection));
selectAll.addEventListener('change', () => {
checkboxes.forEach((checkbox) => {
checkbox.checked = selectAll.checked;
});
updateSelection();
});
form.addEventListener('submit', (event) => {
const selected = checkboxes.filter((checkbox) => checkbox.checked).length;
if (selected === 0 || !window.confirm(`确认将这 ${selected} 条差异推送到该厂家钉钉群?`)) {
event.preventDefault();
}
});
updateSelection();
})();
</script>
@endif