- 去掉「只看未匹配商品单价」「包含-不看仓」两个复选框 - 页面与导出默认展示全部行:已匹配/未匹配单价、含「-不看」仓全部纳入 - 汇总卡片按全部口径统计,移除「已隐藏的-不看仓行数」
176 lines
13 KiB
PHP
176 lines
13 KiB
PHP
@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 的账单行,「种菜金额」有数据(>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>
|