569 lines
39 KiB
PHP
569 lines
39 KiB
PHP
<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
|