修正业绩明细订单核对金额展示 #520

Merged
yueyang merged 1 commits from fix/yueyang-performance-paid-basis-display into main 2026-10-24 19:58:11 +08:00
5 changed files with 138 additions and 6 deletions

View File

@ -115,7 +115,7 @@ class Pages extends Controller
/** 业绩明细可点击指标 → 主表金额列、账单项目明细桶、页面名称。 */
private const RECEIPT_METRIC_MAP = [
'income_amount' => ['label' => '账单收入', 'metric' => 'income'],
'paid_amount' => ['label' => '订单实付', 'metric' => null],
'paid_amount' => ['label' => '订单核对金额', 'metric' => null],
'discount_amount' => ['label' => '优惠收入', 'metric' => 'discount'],
'offset_amount' => ['label' => '冲减收入', 'metric' => 'offset'],
'default_compensation_amount' => ['label' => '违约赔付', 'metric' => 'default_compensation'],
@ -324,6 +324,35 @@ class Pages extends Controller
return "COALESCE(remark, '') NOT IN ('订单实付按卖家实收核对', '订单实付按应付/已付核对')";
}
/**
* 页面展示的订单核对金额。
*
* 命中卖家实收/应付口径时,原 buyer_paid_amount 可能远大于本次账单核对值;
* 页面改为展示实际参与比对的金额,避免“收入 10、订单实付 13,079.80、差额 0”的视觉冲突。
*/
private static function receiptComparablePaidSql(): string
{
return "CASE WHEN COALESCE(remark, '') IN (" . self::receiptBasisFallbackSqlList() . ')'
. ' THEN (' . self::receiptNetSql() . ')'
. ' ELSE COALESCE(paid_amount, 0) END';
}
private static function receiptComparablePaidAmount(object $row): float
{
$remark = trim((string) ($row->remark ?? ''));
if (! in_array($remark, self::RECEIPT_BASIS_FALLBACK_REMARKS, true)) {
return round((float) ($row->paid_amount ?? 0), 2);
}
return round(
(float) ($row->income_amount ?? 0)
+ (float) ($row->discount_amount ?? 0)
+ (float) ($row->offset_amount ?? 0)
+ (float) ($row->default_compensation_amount ?? 0),
2
);
}
/**
* 「差额异常备注」基础原因(不落库),按页面优先级排列。
*
@ -1510,7 +1539,7 @@ class Pages extends Controller
SUM(default_compensation_amount) as default_compensation_total,
SUM(refund_amount) as refund_total,
SUM(planting_amount) as planting_total,
SUM(paid_amount) as paid_total,
SUM(" . self::receiptComparablePaidSql() . ") as paid_total,
SUM({$differenceBaseExpr}) as difference_base,
SUM(CASE WHEN remark IN ({$fallbackSql}) THEN 1 ELSE 0 END) as basis_fallback_rows,
SUM(CASE WHEN payment_count > 1 THEN 1 ELSE 0 END) as multi_pay,
@ -1615,6 +1644,7 @@ class Pages extends Controller
// 未匹配行(差额为空)按比对基准金额=0 计入差额。
$items = $items->map(static function (object $row): object {
$rawRemark = trim((string) ($row->remark ?? ''));
$comparablePaid = self::receiptComparablePaidAmount($row);
// 未匹配原因列:未匹配行照旧;「订单实付按卖家实收核对」这类口径标识在已匹配行同样展示。
$row->unmatched_reason = $rawRemark === ''
? null
@ -1637,6 +1667,7 @@ class Pages extends Controller
&& in_array($rawRemark, ['订单实付按卖家实收核对', '订单实付按应付/已付核对'], true)) {
$row->difference = 0.0;
}
$row->paid_amount = $comparablePaid;
return $row;
});
@ -1767,7 +1798,9 @@ class Pages extends Controller
})
->values();
} else {
$amountSql = 'COALESCE(' . $metric . ', 0)';
$amountSql = $metric === 'paid_amount'
? self::receiptComparablePaidSql()
: 'COALESCE(' . $metric . ', 0)';
$rows = (clone $query)
->select(['platform', 'store_name'])
->selectRaw('SUM(' . $amountSql . ') as amount')
@ -1859,6 +1892,14 @@ class Pages extends Controller
private function receiptOrderMatchMetricTotal($query, string $metric, bool $crossMonthOnly = false): float
{
if ($metric !== 'difference') {
if ($metric === 'paid_amount') {
$total = (clone $query)
->selectRaw('SUM(' . self::receiptComparablePaidSql() . ') as aggregate')
->value('aggregate');
return round((float) $total, 2);
}
return round((float) (clone $query)->sum($metric), 2);
}
@ -2488,7 +2529,7 @@ class Pages extends Controller
'default_compensation_amount' => ['default_compensation_amount', '违约赔付(冲减收入)', 'money'],
'refund_amount' => ['refund_amount', '退款金额', 'money'],
'planting_amount' => ['planting_amount', '种菜金额', 'money'],
'paid_amount' => ['paid_amount', '订单实付金额', 'money'],
'paid_amount' => ['paid_amount', '订单核对金额', 'money'],
'difference' => ['difference', '差额', 'money'],
'match_status' => ['match_status', '匹配状态', 'text'],
'remark' => ['remark', '差额异常备注', 'text'],
@ -2577,6 +2618,9 @@ class Pages extends Controller
foreach ($selected as $key) {
$format = $map[$key][2];
$value = $row->{$map[$key][0]} ?? '';
if ($key === 'paid_amount') {
$value = self::receiptComparablePaidAmount($row);
}
// 「差额异常备注」列与页面一致:只写差额产生的原因
if ($key === 'remark') {
$value = self::receiptDifferenceRemark($row) ?? '';
@ -2592,6 +2636,11 @@ class Pages extends Controller
2
);
}
$rawRemark = trim((string) ($row->remark ?? ''));
if ($key === 'difference'
&& in_array($rawRemark, self::RECEIPT_BASIS_FALLBACK_REMARKS, true)) {
$value = 0;
}
if ($format === 'money') {
// 金额列空值导出成 0.00:Excel 里直接求和、做差额核对不用再补空。
$line[] = number_format((float) ($value === null || $value === '' ? 0 : $value), 2, '.', '');

View File

@ -835,7 +835,7 @@ $navigation = [
['name' => 'default_compensation_amount', 'label' => '违约赔付(冲减收入)', 'kind' => 'number'],
['name' => 'refund_amount', 'label' => '退款金额', 'kind' => 'number'],
['name' => 'planting_amount', 'label' => '种菜金额', 'kind' => 'number'],
['name' => 'paid_amount', 'label' => '订单实付/实收金额', 'kind' => 'number'],
['name' => 'paid_amount', 'label' => '订单核对金额', 'kind' => 'number'],
['name' => 'difference', 'label' => '差额', 'kind' => 'number'],
['name' => 'remark', 'label' => '差额异常备注'],
['name' => 'match_status', 'label' => '匹配状态'],

View File

@ -1454,7 +1454,7 @@
@php
$receiptMetricCards = [
['metric' => 'income_amount', 'label' => '账单收入', 'value' => $receiptStats['income_total']],
['metric' => 'paid_amount', 'label' => '订单实付', 'value' => $receiptStats['paid_total'] ?? 0],
['metric' => 'paid_amount', 'label' => '订单核对金额', 'value' => $receiptStats['paid_total'] ?? 0],
['metric' => 'discount_amount', 'label' => '优惠收入', 'value' => $receiptStats['discount_total'] ?? 0],
['metric' => 'offset_amount', 'label' => '冲减收入', 'value' => $receiptStats['offset_total'] ?? 0],
['metric' => 'default_compensation_amount', 'label' => '违约赔付', 'value' => $receiptStats['default_compensation_total'] ?? 0],
@ -1607,6 +1607,7 @@
<h3>金额关系</h3>
<ul>
<li>差额 = 收入 + 优惠收入 + 冲减收入 + 违约赔付 − 订单实付。</li>
<li>命中“订单实付按卖家实收核对”或“订单实付按应付/已付核对”时,订单核对金额展示实际参与比对的金额。</li>
<li>退款金额、种菜金额单独展示,不参与差额计算。</li>
<li>点击指标卡后,账单类指标按平台、店铺、账单名称汇总;明细合计应与指标总额一致。</li>
</ul>

View File

@ -117,6 +117,54 @@ class PerformanceDetailsMetricDetailsTest extends FeatureTestCase
], $response->json('rows'));
}
public function test_paid_metric_uses_the_amount_used_by_the_matching_basis(): void
{
$mirror = DB::connection('business_mirror');
$mirror->table('store_receipt_order_match_rows')->insert([
array_merge($this->row('row-basis', '店铺A', 0), [
'income_amount' => 10.00,
'paid_amount' => 13079.80,
'difference' => -13069.80,
'remark' => '订单实付按应付/已付核对',
]),
array_merge($this->row('row-normal', '店铺B', 0), [
'income_amount' => 80.00,
'paid_amount' => 100.00,
'difference' => -20.00,
]),
]);
$response = $this->loginAs()->getJson(route(
'business.financial-operations.performance-details.metric-details',
[
'metric' => 'paid_amount',
'month' => '2026-09',
],
));
$response
->assertOk()
->assertJsonPath('label', '订单核对金额')
->assertJsonPath('total', 110)
->assertJsonPath('detail_total', 110)
->assertJsonPath('balanced', true);
$this->assertContains([
'platform' => '京东账单',
'store_name' => '店铺A',
'bill_item' => '订单核对金额',
'amount' => 10,
'line_count' => 1,
], $response->json('rows'));
$this->assertContains([
'platform' => '京东账单',
'store_name' => '店铺B',
'bill_item' => '订单核对金额',
'amount' => 100,
'line_count' => 1,
], $response->json('rows'));
}
private function row(string $rowKey, string $storeName, float $offset, string $platform = '京东账单'): array
{
return [

View File

@ -40,6 +40,20 @@ class PerformanceDifferenceRemarkTest extends TestCase
return (new ReflectionMethod(Pages::class, 'receiptDifferenceRemark'))->invoke(null, $row);
}
private function comparablePaid(array $attributes): float
{
$row = (object) array_merge([
'income_amount' => 0,
'discount_amount' => 0,
'offset_amount' => 0,
'default_compensation_amount' => 0,
'paid_amount' => 0,
'remark' => null,
], $attributes);
return (new ReflectionMethod(Pages::class, 'receiptComparablePaidAmount'))->invoke(null, $row);
}
public function test_pinduoduo_full_refund_wins_before_other_difference_rules(): void
{
$this->assertSame('订单实付含已退款部分', $this->remark([
@ -158,6 +172,26 @@ class PerformanceDifferenceRemarkTest extends TestCase
]));
}
public function test_basis_matched_rows_display_the_amount_used_for_comparison(): void
{
$this->assertSame(10.0, $this->comparablePaid([
'income_amount' => 10.00,
'paid_amount' => 13079.80,
'remark' => '订单实付按应付/已付核对',
]));
$this->assertSame(90.0, $this->comparablePaid([
'income_amount' => 100.00,
'discount_amount' => -10.00,
'paid_amount' => 150.00,
'remark' => '订单实付按卖家实收核对',
]));
$this->assertSame(123.45, $this->comparablePaid([
'income_amount' => 100.00,
'paid_amount' => 123.45,
'remark' => null,
]));
}
public function test_refund_difference_requires_the_refund_amount_to_reconcile(): void
{
$this->assertSame('订单实付含已退款部分', $this->remark([