chenxi 0ec6f703fe 商品成本:移除两个筛选复选框,默认全部数据展示
- 去掉「只看未匹配商品单价」「包含-不看仓」两个复选框
- 页面与导出默认展示全部行:已匹配/未匹配单价、含「-不看」仓全部纳入
- 汇总卡片按全部口径统计,移除「已隐藏的-不看仓行数」
2026-10-20 17:46:34 +08:00

176 lines
13 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.

@php
$rows = $productCostRows ?? null;
$filters = $productCostFilters ?? [];
$summary = $productCostSummary ?? [];
$months = $productCostMonths ?? [];
$platforms = $productCostPlatforms ?? [];
$warehouses = $productCostWarehouses ?? [];
$error = $productCostError ?? null;
$money = static fn (mixed $value): string => $value === null ? '-' : number_format((float) $value, 2);
$text = static fn (mixed $value): string => $value === null || $value === '' ? '-' : (string) $value;
$qty = static function (mixed $value): string {
if ($value === null) {
return '-';
}
$formatted = number_format((float) $value, 2, '.', ',');
return str_contains($formatted, '.')
? rtrim(rtrim($formatted, '0'), '.')
: $formatted;
};
@endphp
<div class="product-cost-page">
<style>
.product-cost-page { color: var(--vip-content-ink, #171717); font-size: .8125rem; }
.product-cost-toolbar { display:flex; flex-wrap:wrap; align-items:flex-end; gap:.75rem; padding:1rem; margin-bottom:1rem; border:1px solid var(--vip-border,#D8D2C7); border-radius:.5rem; background:var(--vip-surface,#fff); }
.product-cost-field { display:flex; flex-direction:column; gap:.35rem; min-width:12rem; }
.product-cost-field label { color:var(--vip-content-muted,#595959); font-size:.75rem; font-weight:600; }
.product-cost-field input,.product-cost-field select { height:2.25rem; padding:0 .65rem; border:1px solid var(--vip-border,#D8D2C7); border-radius:.5rem; background:#fff; color:var(--vip-content-ink,#171717); }
.product-cost-field input:focus,.product-cost-field select:focus { border-color:var(--vip-content-accent-dark,#856632); outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.2); }
.product-cost-check { display:flex; align-items:center; gap:.4rem; height:2.25rem; color:var(--vip-content-muted,#595959); font-size:.75rem; }
.product-cost-actions { display:flex; gap:.5rem; }
.product-cost-button { display:inline-flex; align-items:center; gap:.35rem; height:2.25rem; padding:0 .85rem; border:1px solid var(--vip-border,#D8D2C7); border-radius:.5rem; background:#fff; color:var(--vip-content-ink,#171717); text-decoration:none; font-weight:600; }
.product-cost-button.primary { border-color:var(--vip-content-accent-dark,#856632); background:var(--vip-content-accent-dark,#856632); color:#fff; }
.product-cost-alert { padding:.75rem 1rem; margin-bottom:1rem; border:1px solid var(--vip-danger,#B80000); border-radius:.5rem; background:#fff5f5; color:var(--vip-danger,#B80000); }
.product-cost-summary { display:flex; flex-wrap:wrap; gap:1rem; padding:.75rem 1rem; margin-bottom:1rem; border:1px solid var(--vip-border,#D8D2C7); border-radius:.5rem; background:var(--vip-content-accent-soft,#F7F0DE); }
.product-cost-summary .item { display:flex; flex-direction:column; gap:.15rem; }
.product-cost-summary .item span { color:var(--vip-content-muted,#595959); font-size:.7rem; }
.product-cost-summary .item b { font-size:.95rem; font-variant-numeric:tabular-nums; }
.product-cost-summary .item.is-warn b { color:var(--vip-danger,#B80000); }
.product-cost-note { margin-bottom:.75rem; color:var(--vip-content-muted,#595959); font-size:.75rem; line-height:1.6; }
.product-cost-table-wrap { overflow:auto; border:1px solid var(--vip-border,#D8D2C7); border-radius:.5rem; background:#fff; }
.product-cost-table { width:100%; min-width:78rem; border-collapse:separate; border-spacing:0; font-size:.75rem; }
.product-cost-table th,.product-cost-table td { padding:.55rem .65rem; border-right:1px solid var(--vip-border,#D8D2C7); border-bottom:1px solid var(--vip-border,#D8D2C7); white-space:nowrap; vertical-align:top; }
.product-cost-table th { position:sticky; top:0; z-index:1; background:var(--vip-content-accent-soft,#F7F0DE); text-align:left; font-weight:700; }
.product-cost-table tr:hover td { background:#fcfaf4; }
.product-cost-table .num { text-align:right; font-variant-numeric:tabular-nums; }
.product-cost-table .missing { color:var(--vip-danger,#B80000); }
.product-cost-empty { padding:4rem 1rem; text-align:center; color:var(--vip-content-muted,#595959); }
.product-cost-pagination { display:flex; justify-content:space-between; padding:.75rem 1rem; color:var(--vip-content-muted,#595959); }
.product-cost-pagination a { color:var(--vip-content-accent-dark,#856632); margin-left:.75rem; }
</style>
<form method="GET" class="product-cost-toolbar" aria-label="商品成本筛选">
<div class="product-cost-field">
<label for="product-cost-month">月份</label>
<select id="product-cost-month" name="month">
@forelse ($months as $option)
<option value="{{ $option }}" @selected(($filters['month'] ?? '') === $option)>{{ $option }}</option>
@empty
<option value="{{ $filters['month'] ?? '' }}">{{ $filters['month'] ?? '暂无数据' }}</option>
@endforelse
</select>
</div>
<div class="product-cost-field">
<label for="product-cost-platform">平台</label>
<select id="product-cost-platform" name="platform">
<option value="">全部</option>
@foreach ($platforms as $option)
<option value="{{ $option }}" @selected(($filters['platform'] ?? '') === $option)>{{ $option }}</option>
@endforeach
</select>
</div>
<div class="product-cost-field">
<label for="product-cost-store">店铺名称</label>
<input id="product-cost-store" name="store_name" value="{{ $filters['store_name'] ?? '' }}" placeholder="支持包含查询">
</div>
<div class="product-cost-field">
<label for="product-cost-order">线上单号</label>
<input id="product-cost-order" name="online_order_no" value="{{ $filters['online_order_no'] ?? '' }}" placeholder="支持包含查询">
</div>
<div class="product-cost-field">
<label for="product-cost-code">商品编码</label>
<input id="product-cost-code" name="product_code" value="{{ $filters['product_code'] ?? '' }}" placeholder="支持包含查询">
</div>
<div class="product-cost-field">
<label for="product-cost-warehouse">仓库简称</label>
<select id="product-cost-warehouse" name="warehouse_short_name">
<option value="">全部</option>
@foreach ($warehouses as $option)
<option value="{{ $option }}" @selected(($filters['warehouse_short_name'] ?? '') === $option)>{{ $option }}</option>
@endforeach
</select>
</div>
<div class="product-cost-actions">
<button class="product-cost-button primary" type="submit"><span class="material-icons-outlined" aria-hidden="true">search</span>查询</button>
<a class="product-cost-button" href="{{ route('business.financial-operations.product-costs.export', array_merge(['company_id' => company_id()], request()->except('page'))) }}" aria-label="导出当前筛选结果" title="导出当前筛选结果"><span class="material-icons-outlined" aria-hidden="true">download</span>导出</a>
<a class="product-cost-button" href="{{ route('business.pages.show', ['section' => $section['slug'], 'page' => $page['slug']]) }}"><span class="material-icons-outlined" aria-hidden="true">refresh</span>重置</a>
</div>
</form>
@if ($error)
<div class="product-cost-alert" role="alert">{{ $error }}</div>
@endif
<div class="product-cost-note">
口径:以「业绩明细」的账单行为基数(发生时间 / 店铺名称 / 线上单号),按「店铺 + 线上订单号」展开订单侧商品行,
只取收入金额有数据且不为 0 的账单行,「种菜金额」有数据(&gt;0)的账单行整条排除、不参与匹配;
一行 = 一个发货仓库的一个商品;发货仓库取订单的聚水潭仓库名称,
仓库简称按仓库映射表用「仓储方」匹配「仓库名含主仓」的那条取简称(没有主仓行时认同名仓库,
常州简尚家居有限公司对应映射表里的「配件单仓」行),
商品单价按「仓库简称 + 商品编码」取「商品月加权平均单价」里的加权平均价;匹配不到时退回「线上代发价格」
(采购单价优先、其次钉钉代发单价),仍匹配不到时用「普通商品资料」里的商品成本价兜底;商品成本 = 商品数量 × 商品单价。
拆单子单重复列出的同一批商品只算一次,同一单分多仓发货的按子单实际仓库分行;虚拟条码(X0000x)不计入;
套餐/服务占位编码 TCXJ、FUWU 不取数(组合装里拆出的这两个编码同样剔除)。
定制配件这类明细行,聚水潭把配件件数写进了「商品数量」(例如数量 9524、金额 2000.04,实际单价 0.21),
乘成品的月加权/代发单价会放大几百倍;成本单价高于该行明细单价 3 倍时判为「数量与单价口径不一致」,成本留空、不计入合计。
页面默认展示全部数据:已匹配/未匹配商品单价的行、仓库映射表里标记为「-不看」的仓
(次品 / 销退 / 虚拟 / 信誉订单 / 京东云仓等)都纳入列表与统计。
</div>
<div class="product-cost-summary">
<div class="item"><span>明细行数</span><b>{{ number_format((int) ($summary['rows'] ?? 0)) }}</b></div>
<div class="item"><span>商品数量合计(已匹配单价)</span><b>{{ $qty($summary['quantity_total'] ?? null) }}</b></div>
<div class="item"><span>商品成本合计(已匹配单价)</span><b>{{ $money($summary['cost_total'] ?? null) }}</b></div>
<div class="item {{ ($summary['missing_price_rows'] ?? 0) > 0 ? 'is-warn' : '' }}"><span>未匹配商品单价行数</span><b>{{ number_format((int) ($summary['missing_price_rows'] ?? 0)) }}</b></div>
<div class="item"><span>未匹配单价的商品数量</span><b>{{ $qty($summary['missing_price_quantity'] ?? null) }}</b></div>
<div class="item"><span>其中用线上代发价格兜底</span><b>{{ number_format((int) ($summary['fallback_price_rows'] ?? 0)) }}</b></div>
<div class="item"><span>其中用商品资料成本价兜底</span><b>{{ number_format((int) ($summary['catalog_price_rows'] ?? 0)) }}</b></div>
<div class="item"><span>数量与单价口径不一致(不计成本)</span><b>{{ number_format((int) ($summary['unit_mismatch_rows'] ?? 0)) }}</b></div>
<div class="item {{ ($summary['missing_warehouse_rows'] ?? 0) > 0 ? 'is-warn' : '' }}"><span>未匹配仓库简称行数</span><b>{{ number_format((int) ($summary['missing_warehouse_rows'] ?? 0)) }}</b></div>
</div>
<div class="product-cost-table-wrap">
<table class="product-cost-table">
<thead><tr>
<th>发生时间</th>
<th>店铺名称</th>
<th>线上单号</th>
<th>发货仓库</th>
<th>仓库简称</th>
<th>商品编码</th>
<th class="num">商品数量</th>
<th class="num">商品单价</th>
<th class="num">商品成本</th>
</tr></thead>
<tbody>
@forelse ($rows ?? [] as $row)
<tr>
<td>{{ $text(substr((string) ($row->occurred_date ?? ''), 0, 10)) }}</td>
<td>{{ $text($row->store_name) }}</td>
<td>{{ $text($row->online_order_no) }}</td>
<td>{{ $text($row->warehouse) }}</td>
<td class="{{ ($row->warehouse_short_name ?? '') === '' ? 'missing' : '' }}">{{ $text($row->warehouse_short_name) }}</td>
<td>{{ $text($row->product_code) }}</td>
<td class="num">{{ $qty($row->product_quantity) }}</td>
<td class="num {{ $row->unit_price === null ? 'missing' : '' }}">{{ $money($row->unit_price) }}</td>
<td class="num {{ $row->product_cost === null ? 'missing' : '' }}">{{ $money($row->product_cost) }}</td>
</tr>
@empty
<tr><td colspan="9"><div class="product-cost-empty">
{{ $error ? '镜像读取失败,请稍后重试。' : '暂无符合条件的商品成本数据;生成完成后会自动显示(store-receipt:product-cost:sync)。' }}
</div></td></tr>
@endforelse
</tbody>
</table>
<div class="product-cost-pagination">
<span>当前页 {{ $rows?->count() ?? 0 }} 条 · 第 {{ $rows?->currentPage() ?? 1 }} 页 · 共 {{ $rows?->total() ?? 0 }} 条</span>
<span>
@if ($rows && ! $rows->onFirstPage())<a href="{{ $rows->previousPageUrl() }}">上一页</a>@endif
@if ($rows && $rows->hasMorePages())<a href="{{ $rows->nextPageUrl() }}">下一页</a>@endif
</span>
</div>
</div>
</div>