费用明细分类汇总改为弹窗明细 #541
@ -110,12 +110,12 @@ class BillingDetailsPageData
|
||||
'remark' => ['备注', 'text'],
|
||||
];
|
||||
|
||||
/** 费用明细精简列:平台、店铺、线上单号、总账备注、收支净额。 */
|
||||
/** 费用明细精简列:平台、店铺、线上单号、账单费用、收支净额。 */
|
||||
public const EXPENSE_COLUMNS = [
|
||||
'platform' => ['平台', 'text'],
|
||||
'store_name' => ['店铺', 'text'],
|
||||
'online_order_no' => ['线上单号', 'text'],
|
||||
'finance_remark' => ['总账备注', 'text'],
|
||||
'finance_remark' => ['账单费用', 'text'],
|
||||
'amount' => ['金额(收-支)', 'num'],
|
||||
];
|
||||
|
||||
@ -365,7 +365,7 @@ class BillingDetailsPageData
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用明细的总账备注分类汇总:只响应汇总请求,避免拖慢主表首屏。
|
||||
* 费用明细的账单费用分类汇总:只响应汇总请求,避免拖慢主表首屏。
|
||||
*
|
||||
* @return array{
|
||||
* categories:list<array{
|
||||
@ -377,7 +377,8 @@ class BillingDetailsPageData
|
||||
* }>,
|
||||
* category_count:int,
|
||||
* invoice_net:float,
|
||||
* non_invoice_net:float
|
||||
* non_invoice_net:float,
|
||||
* net:float
|
||||
* }
|
||||
*/
|
||||
public function expenseCategorySummary(?array $input = null, array $forcedFilters = []): array
|
||||
@ -387,7 +388,7 @@ class BillingDetailsPageData
|
||||
$signature = md5((string) json_encode($filters, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return cache()->remember(
|
||||
'billing-details-item-summary-v4:' . $signature,
|
||||
'billing-details-item-summary-v5:' . $signature,
|
||||
now()->addMinutes(2),
|
||||
function () use ($filters): array {
|
||||
$connection = $this->connection();
|
||||
@ -431,7 +432,7 @@ class BillingDetailsPageData
|
||||
foreach ($memoConditions as $memo => $conditions) {
|
||||
$memoSql .= ' WHEN (' . implode(' OR ', $conditions) . ') THEN ' . $this->literal($memo);
|
||||
}
|
||||
$memoSql .= ' ELSE ' . $this->literal('未填写总账备注') . ' END';
|
||||
$memoSql .= ' ELSE ' . $this->literal('未填写账单费用') . ' END';
|
||||
|
||||
$bucketSql = 'CASE';
|
||||
if ($invoiceConditions !== []) {
|
||||
@ -485,6 +486,7 @@ class BillingDetailsPageData
|
||||
'category_count' => count($items),
|
||||
'invoice_net' => round($invoiceNet, 2),
|
||||
'non_invoice_net' => round($nonInvoiceNet, 2),
|
||||
'net' => round($invoiceNet + $nonInvoiceNet, 2),
|
||||
];
|
||||
},
|
||||
);
|
||||
@ -504,7 +506,7 @@ class BillingDetailsPageData
|
||||
float $net,
|
||||
string $invoiceBucket,
|
||||
): void {
|
||||
$ledgerMemo = trim($ledgerMemo) ?: '未填写总账备注';
|
||||
$ledgerMemo = trim($ledgerMemo) ?: '未填写账单费用';
|
||||
$billItem = $this->normalizeItem($billItem) ?: '未填写账单项目';
|
||||
$totals[$ledgerMemo] ??= ['invoice_net' => 0.0, 'non_invoice_net' => 0.0, 'items' => []];
|
||||
$totals[$ledgerMemo]['items'][$billItem] ??= ['invoice_net' => 0.0, 'non_invoice_net' => 0.0];
|
||||
@ -747,7 +749,7 @@ class BillingDetailsPageData
|
||||
if ($platform === '' || $item === '' || trim((string) ($rule['category'] ?? '')) !== '费用') {
|
||||
continue;
|
||||
}
|
||||
$memo = trim((string) ($rule['memo'] ?? '')) ?: '未填写总账备注';
|
||||
$memo = trim((string) ($rule['memo'] ?? '')) ?: '未填写账单费用';
|
||||
$previous = $groups[$platform][$item] ?? null;
|
||||
if ($previous !== null && $previous !== $memo) {
|
||||
throw new \RuntimeException('费用分类无法唯一确定:' . $platform . ' / ' . $item);
|
||||
|
||||
@ -37,26 +37,43 @@
|
||||
.expense-details-page #pdItemSummary { order:2; }
|
||||
.expense-details-page #pdFilterForm { order:3; }
|
||||
.expense-details-page .pd-detail-list { order:4; }
|
||||
.pd-item-summary { margin:0 0 1rem; border:1px solid #d8d2c7; border-radius:.75rem; background:#fff; overflow:hidden; }
|
||||
.pd-item-summary__head { display:flex; flex-wrap:wrap; align-items:center; gap:.5rem .875rem; padding:.75rem 1rem; border-bottom:1px solid #e8e0d2; background:#faf6ee; }
|
||||
.pd-item-summary__head strong { color:#2f2a23; }
|
||||
.pd-item-summary__head span { color:#7c7468; font-size:.75rem; }
|
||||
.pd-item-summary__wrap { max-height:clamp(24rem, 58vh, 32rem); overflow:auto; }
|
||||
.pd-item-summary table { width:100%; border-collapse:collapse; font-size:.8125rem; }
|
||||
.pd-item-summary th { position:sticky; top:0; z-index:1; padding:.55rem .8rem; border-bottom:1px solid #d8d2c7; background:#f3ece0; color:#3d3a35; text-align:left; white-space:nowrap; }
|
||||
.pd-item-summary { margin:0 0 1.25rem; border:1px solid #d8d2c7; border-radius:1rem; background:#fff; overflow:hidden; }
|
||||
.pd-item-summary__head { display:flex; flex-wrap:wrap; align-items:center; gap:.75rem 1.25rem; padding:1rem 1.25rem; border-bottom:1px solid #e8e0d2; background:#faf6ee; }
|
||||
.pd-item-summary__head-main { display:grid; gap:.25rem; min-width:18rem; }
|
||||
.pd-item-summary__title { display:flex; flex-wrap:wrap; align-items:baseline; gap:.35rem .75rem; }
|
||||
.pd-item-summary__title strong { color:#2f2a23; font-size:1rem; }
|
||||
.pd-item-summary__title span, .pd-item-summary__status { color:#7c7468; font-size:.75rem; }
|
||||
.pd-item-summary__total { display:flex; align-items:baseline; gap:.55rem; margin-left:auto; padding-left:1.25rem; border-left:1px solid #e3dac9; }
|
||||
.pd-item-summary__total span { color:#7c7468; font-size:.75rem; }
|
||||
.pd-item-summary__total strong { color:#856632; font-size:1.375rem; font-variant-numeric:tabular-nums; }
|
||||
.pd-item-summary__wrap { max-height:clamp(30rem, 70vh, 44rem); overflow:auto; }
|
||||
.pd-item-summary table { width:100%; border-collapse:collapse; font-size:.875rem; }
|
||||
.pd-item-summary th { position:sticky; top:0; z-index:1; padding:.7rem 1rem; border-bottom:1px solid #d8d2c7; background:#f3ece0; color:#3d3a35; text-align:left; white-space:nowrap; }
|
||||
.pd-item-summary th.num, .pd-item-summary td.num { text-align:right; }
|
||||
.pd-item-summary td { padding:.5rem .8rem; border-bottom:1px solid #eee7da; color:#3d3a35; }
|
||||
.pd-item-summary td { padding:.65rem 1rem; border-bottom:1px solid #eee7da; color:#3d3a35; }
|
||||
.pd-item-summary td.num { font-variant-numeric:tabular-nums; white-space:nowrap; }
|
||||
.pd-item-summary tfoot td { position:sticky; bottom:0; z-index:1; border-top:1px solid #d8d2c7; background:#fff8e9; font-weight:700; }
|
||||
.pd-item-summary__toggle { display:inline-flex; align-items:center; gap:.35rem; padding:0; border:0; background:transparent; color:#2f2a23; font:inherit; font-weight:600; cursor:pointer; }
|
||||
.pd-item-summary__toggle:hover { color:#856632; }
|
||||
.pd-item-summary__toggle .material-icons-outlined { font-size:1rem; transition:transform .18s ease; }
|
||||
.pd-item-summary__toggle[aria-expanded="true"] .material-icons-outlined { transform:rotate(180deg); }
|
||||
.pd-item-summary__drill td { padding:.65rem .9rem .85rem 2rem; background:#fcfaf5; }
|
||||
.pd-item-summary__drill-title { margin-bottom:.4rem; color:#6d6458; font-size:.75rem; }
|
||||
.pd-item-summary__drill table { border:1px solid #e7dfd2; border-radius:.4rem; overflow:hidden; }
|
||||
.pd-item-summary__drill th { position:static; padding:.4rem .55rem; border-bottom:1px solid #e7dfd2; background:#f4eee4; }
|
||||
.pd-item-summary__drill td { padding:.4rem .55rem; background:#fff; }
|
||||
.pd-item-summary tbody tr:hover { background:#fcfaf5; }
|
||||
.pd-item-summary__open { display:inline-flex; align-items:center; gap:.35rem; padding:0; border:0; background:transparent; color:#2f2a23; font:inherit; font-weight:600; cursor:pointer; }
|
||||
.pd-item-summary__open:hover { color:#856632; }
|
||||
.pd-item-summary__open .material-icons-outlined { color:#856632; font-size:1rem; }
|
||||
.pd-expense-dialog { width:min(76rem, calc(100vw - 2rem)); max-height:min(86vh, 52rem); padding:0; border:1px solid #d8d2c7; border-radius:1rem; background:#fff; color:#171717; overflow:hidden; }
|
||||
.pd-expense-dialog::backdrop { background:rgba(23, 23, 23, .48); }
|
||||
.pd-expense-dialog__head { display:flex; flex-wrap:wrap; align-items:center; gap:.75rem 1.25rem; padding:1rem 1.25rem; border-bottom:1px solid #e8e0d2; background:#faf6ee; }
|
||||
.pd-expense-dialog__head-main { display:grid; gap:.25rem; min-width:18rem; }
|
||||
.pd-expense-dialog__title { margin:0; color:#2f2a23; font-size:1rem; }
|
||||
.pd-expense-dialog__subtitle { color:#7c7468; font-size:.75rem; }
|
||||
.pd-expense-dialog__total { display:flex; align-items:baseline; gap:.55rem; margin-left:auto; }
|
||||
.pd-expense-dialog__total span { color:#7c7468; font-size:.75rem; }
|
||||
.pd-expense-dialog__total strong { color:#856632; font-size:1.375rem; font-variant-numeric:tabular-nums; }
|
||||
.pd-expense-dialog__close { display:inline-flex; align-items:center; justify-content:center; width:2rem; height:2rem; padding:0; border:1px solid #d8d2c7; border-radius:999px; background:#fff; color:#595959; font-size:1.25rem; cursor:pointer; }
|
||||
.pd-expense-dialog__close:hover { background:#f7f0de; color:#856632; }
|
||||
.pd-expense-dialog__body { max-height:calc(86vh - 6rem); overflow:auto; padding:0 1.25rem 1.25rem; }
|
||||
.pd-expense-dialog__table { width:100%; border-collapse:collapse; font-size:.875rem; }
|
||||
.pd-expense-dialog__table th { position:sticky; top:0; z-index:1; padding:.65rem .85rem; border-bottom:1px solid #d8d2c7; background:#f3ece0; color:#3d3a35; text-align:left; white-space:nowrap; }
|
||||
.pd-expense-dialog__table td { padding:.6rem .85rem; border-bottom:1px solid #eee7da; color:#3d3a35; }
|
||||
.pd-expense-dialog__table th.num, .pd-expense-dialog__table td.num { text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
|
||||
.pd-expense-dialog__empty { padding:2rem 1rem; color:#7c7468; text-align:center; }
|
||||
/* 筛选栏:12 列栅格对齐——一行 4 个下拉;账单项目与关键词同一行;发生时间单独一行 */
|
||||
.pd-filter-grid { display:grid; grid-template-columns:repeat(12, minmax(0, 1fr)); gap:.75rem 1rem; align-items:end; }
|
||||
.pd-filter-grid .upstream-data-field { min-width:0; }
|
||||
@ -127,7 +144,7 @@
|
||||
.pd-export-progress__bar { height:.5rem; border:1px solid #d8d2c7; border-radius:999px; background:#fff; overflow:hidden; }
|
||||
.pd-export-progress__bar span { display:block; height:100%; width:0; background:#856632; transition:width .3s ease; }
|
||||
.pd-export-progress__text { margin-top:.35rem; color:#595959; font-size:.75rem; }
|
||||
@media (max-width:900px) { .pd-stats { grid-template-columns:repeat(2, minmax(0, 1fr)); } .pd-compact-summary { margin-left:0; } }
|
||||
@media (max-width:900px) { .pd-stats { grid-template-columns:repeat(2, minmax(0, 1fr)); } .pd-compact-summary { margin-left:0; } .pd-item-summary__total { margin-left:0; padding-left:0; border-left:0; } }
|
||||
@media (max-width:520px) { .pd-stats { grid-template-columns:1fr; } }
|
||||
</style>
|
||||
|
||||
@ -146,7 +163,7 @@
|
||||
<div class="pd-page-description">
|
||||
数据源=各平台已导入账单(与「店铺收款账户账单」同源);<b>财务备注</b>、<b>财务类型</b>由科目映射表按「平台+账单项目(wx 自动去前缀)」带出,匹配不到的留空并标红。
|
||||
@if ($expenseOnly)
|
||||
本页只展示科目映射表中<b>财务类型=费用</b>的账单;费用按<b>总账备注</b>分类汇总,金额单位为<b>万</b>。
|
||||
本页只展示科目映射表中<b>财务类型=费用</b>的账单;费用按<b>账单费用</b>分类汇总,金额单位为<b>万</b>。
|
||||
@else
|
||||
同一交易单号跨月合并后已与订单实付对平的,差额备注优先显示为<b>差额=跨月收款(合并已对平)</b>;未对平仍显示<b>差额待核实</b>。
|
||||
@endif
|
||||
@ -266,17 +283,26 @@
|
||||
@if ($expenseOnly)
|
||||
<div class="pd-item-summary" id="pdItemSummary" data-endpoint="{{ $expenseItemSummaryRoute }}">
|
||||
<div class="pd-item-summary__head">
|
||||
<strong>费用分类汇总</strong>
|
||||
<span>单位:万;汇总金额 = 收入 - 支出</span>
|
||||
<span id="pdItemSummaryStatus">正在汇总…</span>
|
||||
<div class="pd-item-summary__head-main">
|
||||
<div class="pd-item-summary__title">
|
||||
<strong>费用分类汇总</strong>
|
||||
<span>单位:万元;汇总金额 = 收入 - 支出</span>
|
||||
</div>
|
||||
<span class="pd-item-summary__status" id="pdItemSummaryStatus">正在汇总…</span>
|
||||
</div>
|
||||
<div class="pd-item-summary__total">
|
||||
<span>总金额(万元)</span>
|
||||
<strong id="pdItemSummaryTotalAll">—</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pd-item-summary__wrap" id="pdItemSummaryWrap" hidden>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>总账备注</th>
|
||||
<th>账单费用</th>
|
||||
<th class="num">开票金额(万)</th>
|
||||
<th class="num">不开票金额(万)</th>
|
||||
<th class="num">总金额(万)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pdItemSummaryRows"></tbody>
|
||||
@ -285,11 +311,40 @@
|
||||
<td>合计</td>
|
||||
<td class="num" id="pdItemSummaryTotalInvoice">—</td>
|
||||
<td class="num" id="pdItemSummaryTotalNonInvoice">—</td>
|
||||
<td class="num" id="pdItemSummaryTotalNet">—</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog class="pd-expense-dialog" id="pdExpenseCategoryDialog" aria-labelledby="pdExpenseCategoryDialogTitle">
|
||||
<div class="pd-expense-dialog__head">
|
||||
<div class="pd-expense-dialog__head-main">
|
||||
<h2 class="pd-expense-dialog__title" id="pdExpenseCategoryDialogTitle">费用分类明细</h2>
|
||||
<span class="pd-expense-dialog__subtitle">按账单名称汇总;开票与不开票金额均以万元展示</span>
|
||||
</div>
|
||||
<div class="pd-expense-dialog__total">
|
||||
<span>当前分类合计(万元)</span>
|
||||
<strong id="pdExpenseCategoryDialogTotal">—</strong>
|
||||
</div>
|
||||
<button type="button" class="pd-expense-dialog__close" id="pdExpenseCategoryDialogClose" aria-label="关闭费用分类明细">×</button>
|
||||
</div>
|
||||
<div class="pd-expense-dialog__body">
|
||||
<table class="pd-expense-dialog__table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>账单名称</th>
|
||||
<th class="num">开票金额(万)</th>
|
||||
<th class="num">不开票金额(万)</th>
|
||||
<th class="num">总金额(万)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pdExpenseCategoryDialogRows"></tbody>
|
||||
</table>
|
||||
<div class="pd-expense-dialog__empty" id="pdExpenseCategoryDialogEmpty" hidden>当前分类没有账单名称明细。</div>
|
||||
</div>
|
||||
</dialog>
|
||||
@endif
|
||||
|
||||
<div class="pd-detail-list">
|
||||
@ -326,7 +381,7 @@
|
||||
<tr>
|
||||
@if ($expenseOnly)
|
||||
<th data-col="platform">平台</th><th data-col="store_name">店铺</th>
|
||||
<th data-col="online_order_no">线上单号</th><th data-col="finance_remark">总账备注</th>
|
||||
<th data-col="online_order_no">线上单号</th><th data-col="finance_remark">账单费用</th>
|
||||
<th data-col="amount">金额(收-支)</th>
|
||||
@else
|
||||
<th data-col="record_code">记录编码</th><th data-col="store_name">店铺</th><th data-col="finance_remark">财务备注</th>
|
||||
@ -676,6 +731,13 @@
|
||||
var summaryRows = document.getElementById('pdItemSummaryRows');
|
||||
var summaryTotalInvoice = document.getElementById('pdItemSummaryTotalInvoice');
|
||||
var summaryTotalNonInvoice = document.getElementById('pdItemSummaryTotalNonInvoice');
|
||||
var summaryTotalNet = document.getElementById('pdItemSummaryTotalNet');
|
||||
var summaryTotalAll = document.getElementById('pdItemSummaryTotalAll');
|
||||
var categoryDialog = document.getElementById('pdExpenseCategoryDialog');
|
||||
var categoryDialogTitle = document.getElementById('pdExpenseCategoryDialogTitle');
|
||||
var categoryDialogTotal = document.getElementById('pdExpenseCategoryDialogTotal');
|
||||
var categoryDialogRows = document.getElementById('pdExpenseCategoryDialogRows');
|
||||
var categoryDialogEmpty = document.getElementById('pdExpenseCategoryDialogEmpty');
|
||||
var summaryParams = new URLSearchParams(window.location.search);
|
||||
summaryParams.delete('page');
|
||||
var summaryUrl = itemSummaryBox.getAttribute('data-endpoint')
|
||||
@ -687,6 +749,50 @@
|
||||
});
|
||||
};
|
||||
|
||||
function openCategoryDialog(category) {
|
||||
categoryDialogTitle.textContent = String(category.memo || '费用分类') + ' · 费用明细';
|
||||
categoryDialogTotal.textContent = formatWan(
|
||||
category.net === undefined
|
||||
? Number(category.invoice_net || 0) + Number(category.non_invoice_net || 0)
|
||||
: category.net
|
||||
);
|
||||
categoryDialogRows.replaceChildren();
|
||||
(category.items || []).forEach(function (item) {
|
||||
var tr = document.createElement('tr');
|
||||
[
|
||||
item.bill_item,
|
||||
formatWan(item.invoice_net),
|
||||
formatWan(item.non_invoice_net),
|
||||
formatWan(item.net)
|
||||
].forEach(function (value, index) {
|
||||
var cell = document.createElement('td');
|
||||
cell.textContent = value;
|
||||
if (index > 0) cell.className = 'num';
|
||||
tr.appendChild(cell);
|
||||
});
|
||||
categoryDialogRows.appendChild(tr);
|
||||
});
|
||||
categoryDialogEmpty.hidden = Number((category.items || []).length) > 0;
|
||||
if (typeof categoryDialog.showModal === 'function') {
|
||||
if (!categoryDialog.open) categoryDialog.showModal();
|
||||
} else {
|
||||
categoryDialog.setAttribute('open', '');
|
||||
}
|
||||
}
|
||||
|
||||
function closeCategoryDialog() {
|
||||
if (typeof categoryDialog.close === 'function') {
|
||||
categoryDialog.close();
|
||||
} else {
|
||||
categoryDialog.removeAttribute('open');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('pdExpenseCategoryDialogClose').addEventListener('click', closeCategoryDialog);
|
||||
categoryDialog.addEventListener('click', function (event) {
|
||||
if (event.target === categoryDialog) closeCategoryDialog();
|
||||
});
|
||||
|
||||
fetch(summaryUrl, { headers: { Accept: 'application/json' } })
|
||||
.then(function (response) {
|
||||
return response.json().then(function (payload) {
|
||||
@ -699,21 +805,29 @@
|
||||
(summary.categories || []).forEach(function (category) {
|
||||
var tr = document.createElement('tr');
|
||||
var memoCell = document.createElement('td');
|
||||
var toggle = document.createElement('button');
|
||||
toggle.type = 'button';
|
||||
toggle.className = 'pd-item-summary__toggle';
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
var openButton = document.createElement('button');
|
||||
openButton.type = 'button';
|
||||
openButton.className = 'pd-item-summary__open';
|
||||
openButton.setAttribute('aria-haspopup', 'dialog');
|
||||
openButton.setAttribute('aria-label', '查看' + String(category.memo || '费用分类') + '的账单名称明细');
|
||||
var memoLabel = document.createElement('span');
|
||||
memoLabel.textContent = String(category.memo || '');
|
||||
var memoIcon = document.createElement('span');
|
||||
memoIcon.className = 'material-icons-outlined';
|
||||
memoIcon.setAttribute('aria-hidden', 'true');
|
||||
memoIcon.textContent = 'expand_more';
|
||||
toggle.appendChild(memoLabel);
|
||||
toggle.appendChild(memoIcon);
|
||||
memoCell.appendChild(toggle);
|
||||
memoIcon.textContent = 'chevron_right';
|
||||
openButton.appendChild(memoLabel);
|
||||
openButton.appendChild(memoIcon);
|
||||
openButton.addEventListener('click', function () {
|
||||
openCategoryDialog(category);
|
||||
});
|
||||
memoCell.appendChild(openButton);
|
||||
tr.appendChild(memoCell);
|
||||
[formatWan(category.invoice_net), formatWan(category.non_invoice_net)]
|
||||
[
|
||||
formatWan(category.invoice_net),
|
||||
formatWan(category.non_invoice_net),
|
||||
formatWan(category.net)
|
||||
]
|
||||
.forEach(function (value) {
|
||||
var td = document.createElement('td');
|
||||
td.className = 'num';
|
||||
@ -721,42 +835,14 @@
|
||||
tr.appendChild(td);
|
||||
});
|
||||
summaryRows.appendChild(tr);
|
||||
|
||||
var drill = document.createElement('tr');
|
||||
drill.className = 'pd-item-summary__drill';
|
||||
drill.hidden = true;
|
||||
var drillCell = document.createElement('td');
|
||||
drillCell.colSpan = 3;
|
||||
var title = document.createElement('div');
|
||||
title.className = 'pd-item-summary__drill-title';
|
||||
title.textContent = '包含账单名称:' + Number((category.items || []).length).toLocaleString('zh-CN') + ' 个';
|
||||
var itemTable = document.createElement('table');
|
||||
itemTable.innerHTML = '<thead><tr><th>账单项目</th><th class="num">开票金额(万)</th><th class="num">不开票金额(万)</th></tr></thead><tbody></tbody>';
|
||||
var itemBody = itemTable.querySelector('tbody');
|
||||
(category.items || []).forEach(function (item) {
|
||||
var itemRow = document.createElement('tr');
|
||||
[item.bill_item, formatWan(item.invoice_net), formatWan(item.non_invoice_net)]
|
||||
.forEach(function (value, index) {
|
||||
var cell = document.createElement('td');
|
||||
cell.textContent = value;
|
||||
if (index > 0) cell.className = 'num';
|
||||
itemRow.appendChild(cell);
|
||||
});
|
||||
itemBody.appendChild(itemRow);
|
||||
});
|
||||
drillCell.appendChild(title);
|
||||
drillCell.appendChild(itemTable);
|
||||
drill.appendChild(drillCell);
|
||||
summaryRows.appendChild(drill);
|
||||
|
||||
toggle.addEventListener('click', function () {
|
||||
var expanded = toggle.getAttribute('aria-expanded') === 'true';
|
||||
toggle.setAttribute('aria-expanded', expanded ? 'false' : 'true');
|
||||
drill.hidden = expanded;
|
||||
});
|
||||
});
|
||||
summaryTotalInvoice.textContent = formatWan(summary.invoice_net);
|
||||
summaryTotalNonInvoice.textContent = formatWan(summary.non_invoice_net);
|
||||
var summaryNet = summary.net === undefined
|
||||
? Number(summary.invoice_net || 0) + Number(summary.non_invoice_net || 0)
|
||||
: summary.net;
|
||||
summaryTotalNet.textContent = formatWan(summaryNet);
|
||||
summaryTotalAll.textContent = formatWan(summaryNet);
|
||||
summaryStatus.textContent = Number(summary.category_count || 0) > 0
|
||||
? (Number(summary.category_count).toLocaleString('zh-CN') + ' 个费用分类')
|
||||
: '当前筛选没有费用项目';
|
||||
|
||||
@ -6,7 +6,7 @@ use Tests\Feature\FeatureTestCase;
|
||||
|
||||
class ExpenseDetailsDrilldownPageTest extends FeatureTestCase
|
||||
{
|
||||
public function testExpenseDetailsPageRendersCategoryDrilldownAndCompactColumns(): void
|
||||
public function testExpenseDetailsPageRendersCategoryDialogAndCompactColumns(): void
|
||||
{
|
||||
$this->loginAs()
|
||||
->get(route('business.pages.show', [
|
||||
@ -16,8 +16,11 @@ class ExpenseDetailsDrilldownPageTest extends FeatureTestCase
|
||||
->assertOk()
|
||||
->assertSeeText('费用明细')
|
||||
->assertSeeText('费用分类汇总')
|
||||
->assertSeeText('账单费用')
|
||||
->assertSeeText('总金额(万元)')
|
||||
->assertSee('/workspace/financial-operations/expense-details/item-summary', false)
|
||||
->assertSee('id="pdItemSummary"', false)
|
||||
->assertSee('id="pdExpenseCategoryDialog"', false)
|
||||
->assertDontSee('data-col="record_code"', false)
|
||||
->assertDontSee('data-col="bill_item"', false);
|
||||
|
||||
@ -37,10 +40,13 @@ class ExpenseDetailsDrilldownPageTest extends FeatureTestCase
|
||||
'finance_remark',
|
||||
'amount',
|
||||
], $columns[1]);
|
||||
$this->assertStringContainsString('pd-item-summary__toggle', $template);
|
||||
$this->assertStringContainsString('category.items', $template);
|
||||
$this->assertStringContainsString('pd-item-summary__open', $template);
|
||||
$this->assertStringContainsString('openCategoryDialog(category)', $template);
|
||||
$this->assertStringContainsString('pd-expense-dialog__table', $template);
|
||||
$this->assertStringNotContainsString('pd-item-summary__toggle', $template);
|
||||
$this->assertStringNotContainsString('pd-item-summary__drill', $template);
|
||||
$this->assertStringContainsString(
|
||||
'max-height:clamp(24rem, 58vh, 32rem)',
|
||||
'max-height:clamp(30rem, 70vh, 44rem)',
|
||||
$template,
|
||||
);
|
||||
}
|
||||
|
||||
@ -143,14 +143,15 @@ class NavigationTest extends FeatureTestCase
|
||||
]))
|
||||
->assertOk()
|
||||
->assertSeeText('费用明细')
|
||||
->assertSeeText('本页只展示科目映射表中财务类型=费用的账单;费用按总账备注分类汇总,金额单位为万。')
|
||||
->assertSeeText('本页只展示科目映射表中财务类型=费用的账单;费用按账单费用分类汇总,金额单位为万。')
|
||||
->assertSeeText('费用分类汇总')
|
||||
->assertSee('/workspace/financial-operations/expense-details/export', false)
|
||||
->assertSee('/workspace/financial-operations/expense-details/filter-options', false)
|
||||
->assertSee('/workspace/financial-operations/expense-details/item-summary', false)
|
||||
->assertSee('id="pdItemSummary"', false)
|
||||
->assertSee('pd-compact-summary', false)
|
||||
->assertSeeText('总账备注')
|
||||
->assertSeeText('账单费用')
|
||||
->assertSeeText('总金额(万元)')
|
||||
->assertSeeText('开票金额(万)')
|
||||
->assertSeeText('不开票金额(万)')
|
||||
->assertDontSee('<div class="pd-stats">', false)
|
||||
|
||||
@ -26,6 +26,7 @@ class BillingDetailsExpenseFilterTest extends TestCase
|
||||
[],
|
||||
BillingDetailsPageData::EXPENSE_COLUMNS,
|
||||
)));
|
||||
$this->assertSame('账单费用', BillingDetailsPageData::EXPENSE_COLUMNS['finance_remark'][0]);
|
||||
}
|
||||
|
||||
public function test_ledger_memo_totals_split_invoice_buckets(): void
|
||||
@ -64,7 +65,7 @@ class BillingDetailsExpenseFilterTest extends TestCase
|
||||
],
|
||||
],
|
||||
[
|
||||
'memo' => '未填写总账备注',
|
||||
'memo' => '未填写账单费用',
|
||||
'invoice_net' => 0.0,
|
||||
'non_invoice_net' => -0.5,
|
||||
'net' => -0.5,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user