费用明细:按总账备注分类并精简明细列 #514
@ -1100,17 +1100,27 @@ class Pages extends Controller
|
||||
$section = collect(SidebarPermissions::navigationFor(user()))->firstWhere('slug', 'financial-operations');
|
||||
abort_unless($section && collect($section['pages'] ?? [])->contains('slug', 'expense-details'), 403, '当前账号没有该页面权限。');
|
||||
|
||||
return $this->streamBillingDetailsExport($request, ['type' => ['费用']], '费用明细');
|
||||
return $this->streamBillingDetailsExport(
|
||||
$request,
|
||||
['type' => ['费用']],
|
||||
'费用明细',
|
||||
\Modules\EcommerceFinance\View\BillingDetailsPageData::EXPENSE_COLUMNS,
|
||||
);
|
||||
}
|
||||
|
||||
private function streamBillingDetailsExport(Request $request, array $forcedFilters, string $filenamePrefix): StreamedResponse
|
||||
private function streamBillingDetailsExport(
|
||||
Request $request,
|
||||
array $forcedFilters,
|
||||
string $filenamePrefix,
|
||||
?array $columnDefinitions = null,
|
||||
): StreamedResponse
|
||||
{
|
||||
$pageData = app(\Modules\EcommerceFinance\View\BillingDetailsPageData::class);
|
||||
$filters = $pageData->exportFilters($request->all(), $forcedFilters);
|
||||
$columns = $pageData->exportColumns(array_values(array_filter(
|
||||
(array) $request->get('columns', []),
|
||||
static fn ($key): bool => is_string($key) && $key !== '',
|
||||
)));
|
||||
)), $columnDefinitions);
|
||||
|
||||
$cell = static function (array $row, string $key): string {
|
||||
$income = (float) ($row['income'] ?? 0);
|
||||
@ -1206,7 +1216,7 @@ class Pages extends Controller
|
||||
|
||||
try {
|
||||
$summary = app(\Modules\EcommerceFinance\View\BillingDetailsPageData::class)
|
||||
->billItemSummary($request->all(), ['type' => ['费用']]);
|
||||
->expenseCategorySummary($request->all(), ['type' => ['费用']]);
|
||||
} catch (\Throwable $exception) {
|
||||
report($exception);
|
||||
|
||||
|
||||
@ -74,7 +74,7 @@ class BillingDetailsPageData
|
||||
return [
|
||||
'view' => 'business.pages.financial-operations.billing-details',
|
||||
'options' => $this->options(),
|
||||
'columns' => self::COLUMNS,
|
||||
'columns' => self::EXPENSE_COLUMNS,
|
||||
] + $this->data();
|
||||
}
|
||||
|
||||
@ -110,6 +110,16 @@ class BillingDetailsPageData
|
||||
'remark' => ['备注', 'text'],
|
||||
];
|
||||
|
||||
/** 费用明细精简列:平台、店铺、线上单号、账单项目、总账备注、收支净额。 */
|
||||
public const EXPENSE_COLUMNS = [
|
||||
'platform' => ['平台', 'text'],
|
||||
'store_name' => ['店铺', 'text'],
|
||||
'online_order_no' => ['线上单号', 'text'],
|
||||
'bill_item' => ['账单项目', 'tag'],
|
||||
'finance_remark' => ['总账备注', 'text'],
|
||||
'amount' => ['金额(收-支)', 'num'],
|
||||
];
|
||||
|
||||
/** 导出用:按请求解析筛选条件(与页面同一套口径) */
|
||||
public function exportFilters(array $input, array $forcedFilters = []): array
|
||||
{
|
||||
@ -147,20 +157,21 @@ class BillingDetailsPageData
|
||||
* @param list<string> $keys
|
||||
* @return array<string,array{0:string,1:string}>
|
||||
*/
|
||||
public function exportColumns(array $keys): array
|
||||
public function exportColumns(array $keys, ?array $definitions = null): array
|
||||
{
|
||||
$definitions ??= self::COLUMNS;
|
||||
if ($keys === []) {
|
||||
return self::COLUMNS;
|
||||
return $definitions;
|
||||
}
|
||||
$wanted = array_flip(array_map(static fn ($key): string => trim((string) $key), $keys));
|
||||
$columns = [];
|
||||
foreach (self::COLUMNS as $key => $definition) {
|
||||
foreach ($definitions as $key => $definition) {
|
||||
if (isset($wanted[$key])) {
|
||||
$columns[$key] = $definition;
|
||||
}
|
||||
}
|
||||
|
||||
return $columns === [] ? self::COLUMNS : $columns;
|
||||
return $columns === [] ? $definitions : $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -355,33 +366,35 @@ class BillingDetailsPageData
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用明细的账单项目汇总:只响应汇总请求,避免拖慢主表首屏。
|
||||
* 费用明细的总账备注分类汇总:只响应汇总请求,避免拖慢主表首屏。
|
||||
*
|
||||
* @return array{
|
||||
* items:list<array{item:string,invoice_net:float,non_invoice_net:float,net:float}>,
|
||||
* item_count:int,
|
||||
* categories:list<array{memo:string,invoice_net:float,non_invoice_net:float,net:float}>,
|
||||
* category_count:int,
|
||||
* invoice_net:float,
|
||||
* non_invoice_net:float
|
||||
* }
|
||||
*/
|
||||
public function billItemSummary(?array $input = null, array $forcedFilters = []): array
|
||||
public function expenseCategorySummary(?array $input = null, array $forcedFilters = []): array
|
||||
{
|
||||
$input ??= request()->all();
|
||||
$filters = array_replace($this->parseFilters($input), $forcedFilters);
|
||||
$signature = md5((string) json_encode($filters, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return cache()->remember(
|
||||
'billing-details-item-summary-v2:' . $signature,
|
||||
'billing-details-item-summary-v3:' . $signature,
|
||||
now()->addMinutes(2),
|
||||
function () use ($filters): array {
|
||||
$connection = $this->connection();
|
||||
$rulesByPlatform = $this->expenseRulesByPlatform();
|
||||
$memoByPlatformItem = $this->expenseMemoByPlatformItem();
|
||||
$parts = [];
|
||||
$bind = [];
|
||||
foreach (self::SOURCES as $label => $meta) {
|
||||
$branch = $this->branch($label, $meta, $filters);
|
||||
$invoiceConditions = [];
|
||||
$nonInvoiceConditions = [];
|
||||
$memoConditions = [];
|
||||
foreach (self::BRANCH_PLATFORMS[$label] ?? [] as $platform) {
|
||||
$invoiceItems = array_keys($rulesByPlatform[$platform]['invoice_expense'] ?? []);
|
||||
if ($invoiceItems !== []) {
|
||||
@ -395,11 +408,26 @@ class BillingDetailsPageData
|
||||
. ' AND ' . $this->normalizedItemSql()
|
||||
. ' IN (' . $this->literals($nonInvoiceItems) . '))';
|
||||
}
|
||||
$memoItems = [];
|
||||
foreach ($memoByPlatformItem[$platform] ?? [] as $item => $memo) {
|
||||
$memoItems[$memo][$item] = true;
|
||||
}
|
||||
foreach ($memoItems as $memo => $items) {
|
||||
$memoConditions[$memo][] = '(' . $this->storePrefixCondition($platform)
|
||||
. ' AND ' . $this->normalizedItemSql()
|
||||
. ' IN (' . $this->literals(array_keys($items)) . '))';
|
||||
}
|
||||
}
|
||||
if ($invoiceConditions === [] && $nonInvoiceConditions === []) {
|
||||
if ($invoiceConditions === [] && $nonInvoiceConditions === [] && $memoConditions === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$memoSql = 'CASE';
|
||||
foreach ($memoConditions as $memo => $conditions) {
|
||||
$memoSql .= ' WHEN (' . implode(' OR ', $conditions) . ') THEN ' . $this->literal($memo);
|
||||
}
|
||||
$memoSql .= ' ELSE ' . $this->literal('未填写总账备注') . ' END';
|
||||
|
||||
$bucketSql = 'CASE';
|
||||
if ($invoiceConditions !== []) {
|
||||
$bucketSql .= ' WHEN (' . implode(' OR ', $invoiceConditions)
|
||||
@ -412,11 +440,12 @@ class BillingDetailsPageData
|
||||
$bucketSql .= " ELSE 'unknown' END";
|
||||
|
||||
foreach ($branch['scopes'] as $scope) {
|
||||
$parts[] = 'SELECT ' . $bucketSql . ' AS invoice_bucket, bill_item, '
|
||||
$parts[] = 'SELECT ' . $memoSql . ' AS ledger_memo, '
|
||||
. $bucketSql . ' AS invoice_bucket, '
|
||||
. 'COALESCE(SUM(' . $branch['income'] . '),0) AS inc, '
|
||||
. 'COALESCE(SUM(' . $branch['expense'] . '),0) AS exp '
|
||||
. 'FROM ' . $branch['table'] . ' WHERE ' . $scope['where']
|
||||
. ' GROUP BY invoice_bucket, bill_item';
|
||||
. ' GROUP BY ledger_memo, invoice_bucket';
|
||||
$bind = array_merge($bind, $scope['bind']);
|
||||
}
|
||||
}
|
||||
@ -435,19 +464,19 @@ class BillingDetailsPageData
|
||||
} else {
|
||||
$nonInvoiceNet += $net;
|
||||
}
|
||||
$this->accumulateBillItemTotal(
|
||||
$this->accumulateLedgerMemoTotal(
|
||||
$totals,
|
||||
(string) ($part->bill_item ?? ''),
|
||||
(string) ($part->ledger_memo ?? ''),
|
||||
$net,
|
||||
$bucket,
|
||||
);
|
||||
}
|
||||
|
||||
$items = $this->sortedBillItemTotals($totals);
|
||||
$items = $this->sortedLedgerMemoTotals($totals);
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'item_count' => count($items),
|
||||
'categories' => $items,
|
||||
'category_count' => count($items),
|
||||
'invoice_net' => round($invoiceNet, 2),
|
||||
'non_invoice_net' => round($nonInvoiceNet, 2),
|
||||
];
|
||||
@ -456,30 +485,30 @@ class BillingDetailsPageData
|
||||
}
|
||||
|
||||
/** @param array<string,array{invoice_net:float,non_invoice_net:float}> $totals */
|
||||
private function accumulateBillItemTotal(
|
||||
private function accumulateLedgerMemoTotal(
|
||||
array &$totals,
|
||||
string $billItem,
|
||||
string $ledgerMemo,
|
||||
float $net,
|
||||
string $invoiceBucket,
|
||||
): void {
|
||||
$billItem = $this->normalizeItem($billItem) ?: '未填写账单项目';
|
||||
$totals[$billItem] ??= ['invoice_net' => 0.0, 'non_invoice_net' => 0.0];
|
||||
$ledgerMemo = trim($ledgerMemo) ?: '未填写总账备注';
|
||||
$totals[$ledgerMemo] ??= ['invoice_net' => 0.0, 'non_invoice_net' => 0.0];
|
||||
$key = $invoiceBucket === 'invoice' ? 'invoice_net' : 'non_invoice_net';
|
||||
$totals[$billItem][$key] += $net;
|
||||
$totals[$ledgerMemo][$key] += $net;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,array{invoice_net:float,non_invoice_net:float}> $totals
|
||||
* @return list<array{item:string,invoice_net:float,non_invoice_net:float,net:float}>
|
||||
* @return list<array{memo:string,invoice_net:float,non_invoice_net:float,net:float}>
|
||||
*/
|
||||
private function sortedBillItemTotals(array $totals): array
|
||||
private function sortedLedgerMemoTotals(array $totals): array
|
||||
{
|
||||
$items = [];
|
||||
foreach ($totals as $item => $total) {
|
||||
foreach ($totals as $memo => $total) {
|
||||
$invoiceNet = round((float) $total['invoice_net'], 2);
|
||||
$nonInvoiceNet = round((float) $total['non_invoice_net'], 2);
|
||||
$items[] = [
|
||||
'item' => $item,
|
||||
'memo' => $memo,
|
||||
'invoice_net' => $invoiceNet,
|
||||
'non_invoice_net' => $nonInvoiceNet,
|
||||
'net' => round($invoiceNet + $nonInvoiceNet, 2),
|
||||
@ -490,7 +519,7 @@ class BillingDetailsPageData
|
||||
return $left['net'] <=> $right['net'];
|
||||
}
|
||||
|
||||
return strnatcasecmp($left['item'], $right['item']);
|
||||
return strnatcasecmp($left['memo'], $right['memo']);
|
||||
});
|
||||
|
||||
return $items;
|
||||
@ -659,6 +688,31 @@ class BillingDetailsPageData
|
||||
return $groups ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用分类汇总使用:平台+账单项目 → 总账备注。
|
||||
*
|
||||
* @return array<string,array<string,string>>
|
||||
*/
|
||||
private function expenseMemoByPlatformItem(): array
|
||||
{
|
||||
$groups = [];
|
||||
foreach (app(AccountMappingStore::class)->all() as $rule) {
|
||||
$platform = trim((string) ($rule['platform'] ?? ''));
|
||||
$item = $this->normalizeItem((string) ($rule['item'] ?? ''));
|
||||
if ($platform === '' || $item === '' || trim((string) ($rule['category'] ?? '')) !== '费用') {
|
||||
continue;
|
||||
}
|
||||
$memo = trim((string) ($rule['memo'] ?? '')) ?: '未填写总账备注';
|
||||
$previous = $groups[$platform][$item] ?? null;
|
||||
if ($previous !== null && $previous !== $memo) {
|
||||
throw new \RuntimeException('费用分类无法唯一确定:' . $platform . ' / ' . $item);
|
||||
}
|
||||
$groups[$platform][$item] = $memo;
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* 科目映射表里的账单项目同时生成带 / 不带 wx 前缀的查找值,
|
||||
* 保留各账单表的 bill_item 索引,避免对整月数据执行 SUBSTRING。
|
||||
|
||||
@ -137,7 +137,7 @@
|
||||
<div class="pd-page-description">
|
||||
数据源=各平台已导入账单(与「店铺收款账户账单」同源);<b>财务备注</b>、<b>财务类型</b>由科目映射表按「平台+账单项目(wx 自动去前缀)」带出,匹配不到的留空并标红。
|
||||
@if ($expenseOnly)
|
||||
本页只展示科目映射表中<b>财务类型=费用</b>的账单;账单项目按当前筛选汇总,金额单位为<b>万</b>。
|
||||
本页只展示科目映射表中<b>财务类型=费用</b>的账单;费用按<b>总账备注</b>分类汇总,金额单位为<b>万</b>。
|
||||
@else
|
||||
同一交易单号跨月合并后已与订单实付对平的,差额备注优先显示为<b>差额=跨月收款(合并已对平)</b>;未对平仍显示<b>差额待核实</b>。
|
||||
@endif
|
||||
@ -257,7 +257,7 @@
|
||||
@if ($expenseOnly)
|
||||
<div class="pd-item-summary" id="pdItemSummary" data-endpoint="{{ $expenseItemSummaryRoute }}">
|
||||
<div class="pd-item-summary__head">
|
||||
<strong>账单项目汇总</strong>
|
||||
<strong>费用分类汇总</strong>
|
||||
<span>单位:万;汇总金额 = 收入 - 支出</span>
|
||||
<span id="pdItemSummaryStatus">正在汇总…</span>
|
||||
</div>
|
||||
@ -265,7 +265,7 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>账单项目</th>
|
||||
<th>总账备注</th>
|
||||
<th class="num">开票金额(万)</th>
|
||||
<th class="num">不开票金额(万)</th>
|
||||
</tr>
|
||||
@ -315,16 +315,30 @@
|
||||
<table class="pd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@if ($expenseOnly)
|
||||
<th data-col="platform">平台</th><th data-col="store_name">店铺</th>
|
||||
<th data-col="online_order_no">线上单号</th><th data-col="bill_item">账单项目</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>
|
||||
<th data-col="online_order_no">线上单号</th><th data-col="amount">金额</th><th data-col="bill_item">账单项目</th>
|
||||
<th data-col="merchant_order_no">商户订单号</th><th data-col="custom_remark">自定义备注</th>
|
||||
<th data-col="finance_type">财务类型</th><th data-col="type_text">类型</th><th data-col="occurred_at">发生时间</th>
|
||||
<th data-col="income">收入金额</th><th data-col="expense">支出金额</th><th data-col="remark">备注</th>
|
||||
@endif
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($rows as $row)
|
||||
<tr>
|
||||
@if ($expenseOnly)
|
||||
<td data-col="platform">{{ $row['platform'] }}</td>
|
||||
<td data-col="store_name">{{ $row['store_name'] }}</td>
|
||||
<td data-col="online_order_no">{{ $row['online_order_no'] }}</td>
|
||||
<td data-col="bill_item"><span class="pd-tag">{{ $row['bill_item'] }}</span></td>
|
||||
<td data-col="finance_remark">{!! $row['finance_remark'] !== '' ? e($row['finance_remark']) : '<span class="pd-miss">—</span>' !!}</td>
|
||||
<td data-col="amount" class="num">{{ number_format((float) $row['income'] - (float) $row['expense'], 2) }}</td>
|
||||
@else
|
||||
<td data-col="record_code">{{ $row['record_code'] }}</td>
|
||||
<td data-col="store_name">{{ $row['store_name'] }}</td>
|
||||
<td data-col="finance_remark">{!! $row['finance_remark'] !== '' ? e($row['finance_remark']) : '<span class="pd-miss">—</span>' !!}</td>
|
||||
@ -339,9 +353,10 @@
|
||||
<td data-col="income" class="num">{{ $row['income'] > 0 ? number_format((float) $row['income'], 2) : '' }}</td>
|
||||
<td data-col="expense" class="num">{{ $row['expense'] > 0 ? number_format((float) $row['expense'], 2) : '' }}</td>
|
||||
<td data-col="remark">{{ $row['remark'] }}</td>
|
||||
@endif
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="14" style="text-align:center;color:#8a8377;padding:1.5rem;">当前条件下没有数据。</td></tr>
|
||||
<tr><td colspan="{{ $expenseOnly ? 6 : 14 }}" style="text-align:center;color:#8a8377;padding:1.5rem;">当前条件下没有数据。</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
@ -624,7 +639,7 @@
|
||||
}).join('\r\n');
|
||||
var link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(new Blob(['\ufeff', csv], { type: 'text/csv;charset=utf-8' }));
|
||||
link.download = '账单明细_当前页.csv';
|
||||
link.download = @json($pageTitle, JSON_UNESCAPED_UNICODE) + '_当前页.csv';
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
}
|
||||
@ -673,9 +688,9 @@
|
||||
})
|
||||
.then(function (summary) {
|
||||
summaryRows.replaceChildren();
|
||||
(summary.items || []).forEach(function (item) {
|
||||
(summary.categories || []).forEach(function (item) {
|
||||
var tr = document.createElement('tr');
|
||||
[item.item, formatWan(item.invoice_net), formatWan(item.non_invoice_net)]
|
||||
[item.memo, formatWan(item.invoice_net), formatWan(item.non_invoice_net)]
|
||||
.forEach(function (value, index) {
|
||||
var td = document.createElement('td');
|
||||
td.textContent = value;
|
||||
@ -686,10 +701,10 @@
|
||||
});
|
||||
summaryTotalInvoice.textContent = formatWan(summary.invoice_net);
|
||||
summaryTotalNonInvoice.textContent = formatWan(summary.non_invoice_net);
|
||||
summaryStatus.textContent = Number(summary.item_count || 0) > 0
|
||||
? (Number(summary.item_count).toLocaleString('zh-CN') + ' 个账单项目')
|
||||
summaryStatus.textContent = Number(summary.category_count || 0) > 0
|
||||
? (Number(summary.category_count).toLocaleString('zh-CN') + ' 个费用分类')
|
||||
: '当前筛选没有费用项目';
|
||||
summaryWrap.hidden = Number(summary.item_count || 0) === 0;
|
||||
summaryWrap.hidden = Number(summary.category_count || 0) === 0;
|
||||
})
|
||||
.catch(function (error) {
|
||||
summaryStatus.textContent = error.message || '汇总暂时无法读取';
|
||||
|
||||
@ -139,15 +139,18 @@ 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('不开票金额(万)')
|
||||
->assertDontSee('<div class="pd-stats">', false)
|
||||
->assertDontSee('data-col="record_code"', false)
|
||||
->assertDontSee('id="pdType"', false);
|
||||
}
|
||||
|
||||
|
||||
@ -16,26 +16,38 @@ class BillingDetailsExpenseFilterTest extends TestCase
|
||||
);
|
||||
|
||||
$this->assertSame(['费用'], $filters['type']);
|
||||
$this->assertSame([
|
||||
'platform',
|
||||
'store_name',
|
||||
'online_order_no',
|
||||
'bill_item',
|
||||
'finance_remark',
|
||||
'amount',
|
||||
], array_keys(app(BillingDetailsPageData::class)->exportColumns(
|
||||
[],
|
||||
BillingDetailsPageData::EXPENSE_COLUMNS,
|
||||
)));
|
||||
}
|
||||
|
||||
public function test_bill_item_totals_merge_wx_prefix_and_split_invoice_buckets(): void
|
||||
public function test_ledger_memo_totals_split_invoice_buckets(): void
|
||||
{
|
||||
$data = app(BillingDetailsPageData::class);
|
||||
$totals = [];
|
||||
$accumulate = new ReflectionMethod(BillingDetailsPageData::class, 'accumulateBillItemTotal');
|
||||
$accumulate = new ReflectionMethod(BillingDetailsPageData::class, 'accumulateLedgerMemoTotal');
|
||||
$accumulate->setAccessible(true);
|
||||
$accumulate->invokeArgs($data, [&$totals, 'wx技术服务费', -90.0, 'invoice']);
|
||||
$accumulate->invokeArgs($data, [&$totals, '技术服务费', -10.0, 'invoice']);
|
||||
$accumulate->invokeArgs($data, [&$totals, '技术服务费', -5.0, 'non_invoice']);
|
||||
$accumulate->invokeArgs($data, [&$totals, '技术服务费', -90.0, 'invoice']);
|
||||
$accumulate->invokeArgs($data, [&$totals, '技术服务费', -10.0, 'non_invoice']);
|
||||
$accumulate->invokeArgs($data, [&$totals, '推广费用', -5.0, 'invoice']);
|
||||
$accumulate->invokeArgs($data, [&$totals, '', -0.5, 'non_invoice']);
|
||||
|
||||
$sorted = new ReflectionMethod(BillingDetailsPageData::class, 'sortedBillItemTotals');
|
||||
$sorted = new ReflectionMethod(BillingDetailsPageData::class, 'sortedLedgerMemoTotals');
|
||||
$sorted->setAccessible(true);
|
||||
$rows = $sorted->invoke($data, $totals);
|
||||
|
||||
$this->assertSame([
|
||||
['item' => '技术服务费', 'invoice_net' => -100.0, 'non_invoice_net' => -5.0, 'net' => -105.0],
|
||||
['item' => '未填写账单项目', 'invoice_net' => 0.0, 'non_invoice_net' => -0.5, 'net' => -0.5],
|
||||
['memo' => '技术服务费', 'invoice_net' => -90.0, 'non_invoice_net' => -10.0, 'net' => -100.0],
|
||||
['memo' => '推广费用', 'invoice_net' => -5.0, 'non_invoice_net' => 0.0, 'net' => -5.0],
|
||||
['memo' => '未填写总账备注', 'invoice_net' => 0.0, 'non_invoice_net' => -0.5, 'net' => -0.5],
|
||||
], $rows);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user