业绩明细:差额/匹配状态/备注改为与订单实付金额核对;科目映射表筛选改按财务备注

- 业绩明细「差额 / 匹配状态 / 差额异常备注」的比对基准由「订单商品金额」换成「订单实付金额」:
  差额=(收入金额+优惠金额+冲减金额)− 订单实付金额;匹配状态以订单侧能否取到订单实付为准;
  备注文案改为 订单实付未匹配(账单无单号/单号格式异常/订单表无此单/订单无实付金额/无收款账单)、
  订单实付取数为0(种菜)、订单实付取数为0、订单实付未减冲减、订单实付未减优惠、订单实付未减优惠和冲减
- 商品金额列保留展示、不再作为核对基准;退款金额仍单独列示、不参与差额
- 两个口径共用同一条取数链(同店同号 → 店铺名不一致 → 同号多条 → 空店铺兜底),
  差额口径做成开关:config/performance_details.php 的 difference_basis 填 product 即可切回旧口径
- 统计卡「金额构成」补「订单实付金额合计」,与差额公式对齐
- 科目映射表筛选:第二个筛选项由「账单项目」改为「财务备注」(候选按平台收窄、可搜索、多选);
  账单项目仍可用左上角关键词搜索
This commit is contained in:
yueyang 2026-10-10 15:14:00 +08:00
parent 37d18612a6
commit 158c5e1b23
6 changed files with 218 additions and 108 deletions

View File

@ -150,6 +150,9 @@ class SyncStoreReceiptOrderMatches extends Command
private const REMARK_NO_AMOUNT = '订单无商品金额';
/** 比对基准为「订单实付金额」时,订单侧有记录但没有实付金额的标注。 */
private const REMARK_NO_PAID = '订单无实付金额';
private const REMARK_SHOP_DIFF = '店铺名不一致';
private const REMARK_DUP_ORDER = '订单侧同号多条';
@ -636,91 +639,67 @@ class SyncStoreReceiptOrderMatches extends Command
$stats = ['已匹配' => 0, '未匹配' => 0, self::REMARK_MULTI_PAY => 0];
$remarked = [];
$goodsTotal = 0.0;
$paidTotal = 0.0;
$differenceTotal = 0.0;
// 订单实付=订单级实付 − 虚拟条码(X0000x)明细行实付,下限 0;纯信誉/虚拟订单归零。
// 一次算好整张表,主循环里只做查表。
$orderPaidNet = [];
foreach ($orderPaid as $paidKey => $paidValue) {
$orderPaidNet[$paidKey] = round(max((float) $paidValue - (float) ($orderVirtualPaid[$paidKey] ?? 0.0), 0.0), 2);
}
// 比对基准(差额 / 匹配状态 / 差额异常备注):paid=订单实付金额,product=订单商品金额(旧口径)。
$basis = self::differenceBasis();
foreach ($groups as $group) {
$core = (string) $group['order_core_no'];
$income = round((float) $group['income_amount'], 2);
$remark = '';
$productAmount = null;
$paidAmount = null;
$status = self::STATUS_UNMATCHED;
// 订单实付=订单级实付 − 虚拟条码(X0000x)明细行实付,下限 0;
// 与商品金额保持一致的口径:纯信誉/虚拟订单的实付归零。
$paidKey = $group['shop_key'] . '|' . $core;
$paidAmount = isset($orderPaid[$paidKey])
? round(max((float) $orderPaid[$paidKey] - (float) ($orderVirtualPaid[$paidKey] ?? 0.0), 0.0), 2)
: null;
// 两个口径各跑一遍同一条取数链(同店同号 → 店铺名不一致 → 同号多条 → 空店铺兜底):
// 商品金额用于「商品金额」列,订单实付用于「订单实付金额」列与比对基准。
[$productStatus, $productAmount, $productRemark] = $this->matchOrderSide(
$core,
(string) $group['shop_key'],
$orderAmount,
$orderSeen,
$coreSeen,
$coresByCore,
self::REMARK_NO_AMOUNT,
);
[$paidStatus, $paidAmount, $paidRemark] = $this->matchOrderSide(
$core,
(string) $group['shop_key'],
$orderPaidNet,
$orderSeen,
$coreSeen,
$coresByCore,
self::REMARK_NO_PAID,
);
if ($core === '') {
$remark = self::REMARK_EMPTY_NO;
} elseif (preg_match('/^\d{8,}$/', $core) !== 1) {
$remark = self::REMARK_BAD_FORMAT;
} else {
$exactKey = $group['shop_key'] . '|' . $core;
if (isset($orderSeen[$exactKey])) {
if (isset($orderAmount[$exactKey])) {
$status = self::STATUS_MATCHED;
$productAmount = round((float) $orderAmount[$exactKey], 2);
if (count($coresByCore[$core] ?? []) > 1) {
$remark = self::REMARK_DUP_ORDER;
}
} else {
// 订单侧有记录,但全是 0 元/退款行:没有商品金额可比对。
$remark = self::REMARK_NO_AMOUNT;
}
} elseif (isset($coreSeen[$core]) && count($coresByCore[$core] ?? []) === 1) {
// 单号一致但店铺名称对不上:仍然带上金额,标注「店铺不一致」便于复核。
$onlyShop = array_key_first($coresByCore[$core]);
$otherKey = $onlyShop . '|' . $core;
if (isset($orderAmount[$otherKey])) {
$status = self::STATUS_MATCHED;
$productAmount = round((float) $orderAmount[$otherKey], 2);
$remark = self::REMARK_SHOP_DIFF;
} else {
$remark = self::REMARK_NO_AMOUNT;
}
} elseif (isset($coreSeen[$core]) && count($coresByCore[$core] ?? []) > 1) {
$sum = 0.0;
$hasAmount = false;
foreach (array_keys($coresByCore[$core]) as $shopKey) {
$key2 = $shopKey . '|' . $core;
if (isset($orderAmount[$key2])) {
$sum += (float) $orderAmount[$key2];
$hasAmount = true;
}
}
if ($hasAmount) {
$status = self::STATUS_MATCHED;
$productAmount = round($sum, 2);
$remark = self::REMARK_DUP_ORDER;
} else {
$remark = self::REMARK_NO_AMOUNT;
}
} elseif (isset($coreSeen[$core])) {
// 订单侧只有店铺名为空的记录:按订单号兜底匹配。
$blankKey = '|' . $core;
if (isset($orderAmount[$blankKey])) {
$status = self::STATUS_MATCHED;
$productAmount = round((float) $orderAmount[$blankKey], 2);
} else {
$remark = self::REMARK_NO_AMOUNT;
}
} else {
$remark = self::REMARK_NO_ORDER;
}
}
$status = $basis === 'product' ? $productStatus : $paidStatus;
$remark = $basis === 'product' ? $productRemark : $paidRemark;
$basisAmount = $basis === 'product' ? $productAmount : $paidAmount;
if ($status === self::STATUS_MATCHED && (int) $group['payment_count'] > 1 && $remark === '') {
$remark = self::REMARK_MULTI_PAY;
}
// 差额口径:(收入金额 + 优惠金额 + 冲减金额)− 订单侧商品金额。
// 差额口径:(收入金额 + 优惠金额 + 冲减金额)− 比对基准金额(默认订单实付金额)。
// 优惠/冲减属于同一订单的账单,把它们加回来,差额才只反映真正对不上的部分。
$promoAmount = round((float) ($group['discount_amount'] ?? 0.0) + (float) ($group['offset_amount'] ?? 0.0), 2);
$difference = $productAmount === null ? null : round($income + $promoAmount - $productAmount, 2);
$difference = $basisAmount === null ? null : round($income + $promoAmount - $basisAmount, 2);
if ($productAmount !== null) {
$goodsTotal += $productAmount;
}
if ($paidAmount !== null) {
$paidTotal += $paidAmount;
}
if ($basisAmount !== null) {
$differenceTotal += (float) $difference;
}
@ -785,6 +764,7 @@ class SyncStoreReceiptOrderMatches extends Command
}
$this->line(' 账单收入合计:' . number_format($incomeTotal, 2));
$this->line(' 订单侧商品金额合计:' . number_format($goodsTotal, 2));
$this->line(' 订单侧实付金额合计:' . number_format($paidTotal, 2));
$this->line(' 差额合计:' . number_format($differenceTotal, 2));
// 重建后自检:本月账单里确实存在「优惠收入 / 冲减收入 / 退款」的账单行,但重建出来的行
@ -943,12 +923,13 @@ class SyncStoreReceiptOrderMatches extends Command
}
// 只回填金额列时也要把「差额」跟着刷新,否则页面上差额列与优惠/冲减列对不上。
$basisColumn = self::differenceBasis() === 'product' ? 'product_amount' : 'paid_amount';
$target()
->where('report_month', $reportMonth)
->where('platform', $platform)
->whereNotNull('product_amount')
->whereNotNull($basisColumn)
->update([
'difference' => DB::raw('ROUND(income_amount + COALESCE(discount_amount, 0) + COALESCE(offset_amount, 0) - product_amount, 2)'),
'difference' => DB::raw('ROUND(income_amount + COALESCE(discount_amount, 0) + COALESCE(offset_amount, 0) - ' . $basisColumn . ', 2)'),
]);
$this->info(sprintf(
@ -1228,6 +1209,96 @@ class SyncStoreReceiptOrderMatches extends Command
return array_values(array_unique($values));
}
/**
* 「差额 / 匹配状态 / 差额异常备注」的比对基准:
* paid(默认)=订单实付金额;product=订单商品金额(旧口径,可用配置切回)。
*/
private static function differenceBasis(): string
{
return (string) config('performance_details.difference_basis', 'paid') === 'product' ? 'product' : 'paid';
}
/**
* 按「店铺 + 订单号」在订单侧取金额,返回 [匹配状态, 订单侧金额, 原因标注]。
*
* 两个比对基准共用这一条取数链:
* 同店同号 → 店铺名不一致(订单号唯一)→ 同号多条(多店铺合计)→ 空店铺兜底 → 订单表无此单。
*
* @param array<string,float> $amounts 订单侧金额集合(商品金额或订单实付)
* @param array<string,bool> $orderSeen 订单侧出现过的「店铺|单号」
* @param array<string,bool> $coreSeen 订单侧出现过的单号
* @param array<string,array<string,bool>> $coresByCore 单号 → 店铺集合
* @return array{0:string,1:?float,2:string}
*/
private function matchOrderSide(
string $core,
string $shopKey,
array $amounts,
array $orderSeen,
array $coreSeen,
array $coresByCore,
string $noAmountRemark,
): array {
if ($core === '') {
return [self::STATUS_UNMATCHED, null, self::REMARK_EMPTY_NO];
}
if (preg_match('/^\d{8,}$/', $core) !== 1) {
return [self::STATUS_UNMATCHED, null, self::REMARK_BAD_FORMAT];
}
$exactKey = $shopKey . '|' . $core;
if (isset($orderSeen[$exactKey])) {
if (isset($amounts[$exactKey])) {
$remark = count($coresByCore[$core] ?? []) > 1 ? self::REMARK_DUP_ORDER : '';
return [self::STATUS_MATCHED, round((float) $amounts[$exactKey], 2), $remark];
}
// 订单侧有记录,但没有这一项金额(全是 0 元/退款行)。
return [self::STATUS_UNMATCHED, null, $noAmountRemark];
}
if (isset($coreSeen[$core]) && count($coresByCore[$core] ?? []) === 1) {
// 单号一致但店铺名称对不上:仍然带上金额,标注「店铺不一致」便于复核。
$otherKey = array_key_first($coresByCore[$core]) . '|' . $core;
if (isset($amounts[$otherKey])) {
return [self::STATUS_MATCHED, round((float) $amounts[$otherKey], 2), self::REMARK_SHOP_DIFF];
}
return [self::STATUS_UNMATCHED, null, $noAmountRemark];
}
if (isset($coreSeen[$core]) && count($coresByCore[$core] ?? []) > 1) {
$sum = 0.0;
$hasAmount = false;
foreach (array_keys($coresByCore[$core]) as $otherShop) {
$key2 = $otherShop . '|' . $core;
if (isset($amounts[$key2])) {
$sum += (float) $amounts[$key2];
$hasAmount = true;
}
}
if ($hasAmount) {
return [self::STATUS_MATCHED, round($sum, 2), self::REMARK_DUP_ORDER];
}
return [self::STATUS_UNMATCHED, null, $noAmountRemark];
}
if (isset($coreSeen[$core])) {
// 订单侧只有店铺名为空的记录:按订单号兜底匹配。
$blankKey = '|' . $core;
if (isset($amounts[$blankKey])) {
return [self::STATUS_MATCHED, round((float) $amounts[$blankKey], 2), ''];
}
return [self::STATUS_UNMATCHED, null, $noAmountRemark];
}
return [self::STATUS_UNMATCHED, null, self::REMARK_NO_ORDER];
}
/** 账单行上的商品名称:科目映射表的「商品名称关键词」用它匹配。 */
private function billGoodsName(object $bill): string
{

View File

@ -125,28 +125,46 @@ class Pages extends Controller
'同单多笔收款',
];
/**
* 比对基准(差额 / 匹配状态 / 差额异常备注):
* paid(默认)=订单实付金额;product=订单商品金额(旧口径)。
*/
private static function differenceBasis(): array
{
$paid = (string) config('performance_details.difference_basis', 'paid') !== 'product';
return [
'column' => $paid ? 'paid_amount' : 'product_amount',
'label' => $paid ? '订单实付' : '商品金额',
'no_amount_remark' => $paid ? '订单无实付金额' : '订单无商品金额',
];
}
/**
* 「差额异常备注」原因(不落库):说明差额为什么产生,一行最多一个,按顺序取第一条命中。
* 未匹配行 → 商品金额未匹配(带库里的匹配细分);已匹配但商品金额=0 → 商品金额取数为0;
* 商品金额没有减掉优惠/冲减时,差额正好等于对应项。差额按有效值判定(未匹配按商品金额=0)。
* 未匹配行 → 订单实付未匹配(带库里的匹配细分);已匹配但基准金额=0 → 订单实付取数为0;
* 基准金额没有减掉优惠/冲减时,差额正好等于对应项。差额按有效值判定(未匹配按基准金额=0)。
*
* @return array<string,string> 备注文字 => SQL 条件(已按顺序排除前面命中的行)
*/
private static function receiptDifferenceRemarkConditions(): array
{
$unmatchedRemarks = ['账单无单号', '单号格式异常', '订单表无此单', '订单无商品金额', '无收款账单'];
$basis = self::differenceBasis();
$column = $basis['column'];
$label = $basis['label'];
$unmatchedRemarks = ['账单无单号', '单号格式异常', '订单表无此单', $basis['no_amount_remark'], '无收款账单'];
$rules = [];
foreach ($unmatchedRemarks as $unmatchedRemark) {
$rules['商品金额未匹配(' . $unmatchedRemark . ')'] = "match_status = '未匹配' AND remark = '" . $unmatchedRemark . "'";
$rules[$label . '未匹配(' . $unmatchedRemark . ')'] = "match_status = '未匹配' AND remark = '" . $unmatchedRemark . "'";
}
$rules['商品金额未匹配'] = "match_status = '未匹配' AND (remark IS NULL OR remark = '' OR remark NOT IN ('"
$rules[$label . '未匹配'] = "match_status = '未匹配' AND (remark IS NULL OR remark = '' OR remark NOT IN ('"
. implode("', '", $unmatchedRemarks) . "'))";
$rules['商品金额未加种菜'] = "match_status = '已匹配' AND planting_amount IS NOT NULL AND ABS(COALESCE(product_amount, 0)) < 0.005";
$rules['商品金额取数为0'] = "match_status = '已匹配' AND planting_amount IS NULL AND ABS(COALESCE(product_amount, 0)) < 0.005";
$rules['商品金额未减冲减'] = "match_status = '已匹配' AND ABS(COALESCE(product_amount, 0) - (COALESCE(income_amount, 0) + COALESCE(discount_amount, 0))) < 0.005";
$rules['商品金额未减优惠'] = "match_status = '已匹配' AND ABS(COALESCE(product_amount, 0) - (COALESCE(income_amount, 0) + COALESCE(offset_amount, 0))) < 0.005";
$rules['商品金额未减优惠和冲减'] = "match_status = '已匹配' AND ABS(COALESCE(product_amount, 0) - COALESCE(income_amount, 0)) < 0.005";
$rules[$label . '取数为0(种菜)'] = "match_status = '已匹配' AND planting_amount IS NOT NULL AND ABS(COALESCE($column, 0)) < 0.005";
$rules[$label . '取数为0'] = "match_status = '已匹配' AND planting_amount IS NULL AND ABS(COALESCE($column, 0)) < 0.005";
$rules[$label . '未减冲减'] = "match_status = '已匹配' AND ABS(COALESCE($column, 0) - (COALESCE(income_amount, 0) + COALESCE(discount_amount, 0))) < 0.005";
$rules[$label . '未减优惠'] = "match_status = '已匹配' AND ABS(COALESCE($column, 0) - (COALESCE(income_amount, 0) + COALESCE(offset_amount, 0))) < 0.005";
$rules[$label . '未减优惠和冲减'] = "match_status = '已匹配' AND ABS(COALESCE($column, 0) - COALESCE(income_amount, 0)) < 0.005";
$effective = 'COALESCE(difference, COALESCE(income_amount, 0) + COALESCE(discount_amount, 0) + COALESCE(offset_amount, 0))';
$conditions = [];
@ -163,6 +181,9 @@ class Pages extends Controller
/** 单行的「差额异常备注」(与筛选条件同一套口径,页面与导出共用)。 */
private static function receiptDifferenceRemark(object $row): ?string
{
$basis = self::differenceBasis();
$label = $basis['label'];
$difference = $row->difference === null
? (float) ($row->income_amount ?? 0) + (float) ($row->discount_amount ?? 0) + (float) ($row->offset_amount ?? 0)
: (float) $row->difference;
@ -174,25 +195,25 @@ class Pages extends Controller
if (($row->match_status ?? '') === '未匹配') {
$remark = trim((string) ($row->remark ?? ''));
return $remark !== '' ? '商品金额未匹配(' . $remark . ')' : '商品金额未匹配';
return $remark !== '' ? $label . '未匹配(' . $remark . ')' : $label . '未匹配';
}
$product = (float) ($row->product_amount ?? 0);
$product = (float) ($row->{$basis['column']} ?? 0);
$income = (float) ($row->income_amount ?? 0);
$discount = (float) ($row->discount_amount ?? 0);
$offset = (float) ($row->offset_amount ?? 0);
if (abs($product) < 0.005) {
return ($row->planting_amount ?? null) !== null ? '商品金额未加种菜' : '商品金额取数为0';
return ($row->planting_amount ?? null) !== null ? $label . '取数为0(种菜)' : $label . '取数为0';
}
if (abs($product - ($income + $discount)) < 0.005) {
return '商品金额未减冲减';
return $label . '未减冲减';
}
if (abs($product - ($income + $offset)) < 0.005) {
return '商品金额未减优惠';
return $label . '未减优惠';
}
if (abs($product - $income) < 0.005) {
return '商品金额未减优惠和冲减';
return $label . '未减优惠和冲减';
}
return null;
@ -750,6 +771,7 @@ class Pages extends Controller
SUM(refund_amount) as refund_total,
SUM(planting_amount) as planting_total,
SUM(product_amount) as goods_total,
SUM(paid_amount) as paid_total,
SUM(COALESCE(difference, COALESCE(income_amount, 0) + COALESCE(discount_amount, 0) + COALESCE(offset_amount, 0))) as difference_total,
SUM(CASE WHEN payment_count > 1 THEN 1 ELSE 0 END) as multi_pay,
SUM(CASE WHEN income_amount = 0 THEN 1 ELSE 0 END) as zero_income,
@ -773,6 +795,7 @@ class Pages extends Controller
'refund_total' => round((float) ($agg->refund_total ?? 0), 2),
'planting_total' => round((float) ($agg->planting_total ?? 0), 2),
'goods_total' => round((float) ($agg->goods_total ?? 0), 2),
'paid_total' => round((float) ($agg->paid_total ?? 0), 2),
'difference_total' => round((float) ($agg->difference_total ?? 0), 2),
'per_page' => $limit,
];
@ -820,7 +843,7 @@ class Pages extends Controller
->forPage($page, $limit)
->get();
// 差额异常备注列:只展示「差额为什么产生」的原因备注;未匹配行(差额为空)按商品金额=0 计入差额。
// 差额异常备注列:只展示「差额为什么产生」的原因备注;未匹配行(差额为空)按比对基准金额=0 计入差额。
$items = $items->map(static function (object $row): object {
$row->remark = self::receiptDifferenceRemark($row);
@ -1184,7 +1207,7 @@ class Pages extends Controller
if ($key === 'remark') {
$value = self::receiptDifferenceRemark($row) ?? '';
}
// 未匹配行(差额为空)按商品金额=0 计入差额,与页面口径一致
// 未匹配行(差额为空)按比对基准金额=0 计入差额,与页面口径一致
if ($key === 'difference' && $value === null) {
$value = round(
(float) ($row->income_amount ?? 0) + (float) ($row->discount_amount ?? 0) + (float) ($row->offset_amount ?? 0),

View File

@ -791,7 +791,7 @@ $navigation = [
'directory' => [
'icon' => 'receipt_long',
'status' => '共享镜像',
'description' => '覆盖店铺收款账户账单各平台页签(总账备注=订单收入),按「店铺 + 订单号」匹配订单侧商品金额;一行 = 一个店铺的一个订单,商品金额只挂一次。「商品金额」只取主单(拆单子单不参与,整单没有主单时退化为取全部行),并排除虚拟条码(X0000x:信誉订单 / 虚拟订单)明细行;「订单实付金额」取订单级实付(拆单取主单)再减去虚拟条码行实付。「优惠金额」「冲减金额」另取总账备注=优惠收入 / 冲减收入的账单金额,不计入收入金额;「退款金额」另取总账备注=退款的账单金额,只单独列示,不计入收入金额、也不参与差额;「种菜金额」是订单号在种菜明细表或聚水潭订单标签明细(是否种菜订单=是)任意一边出现过时,该订单的收入金额(两个条件是或的关系),只是收入金额里的一部分,不参与差额;差额=(收入金额+优惠金额+冲减金额)−商品金额,未匹配到商品金额的行按商品金额=0 计入差额,只反映真正对不上的部分。挂不到订单行的优惠/冲减会补成一行(收入/商品留空)。「差额异常备注」只说明差额为什么产生,按顺序自动判定:商品金额未匹配(账单无单号 / 单号格式异常 / 订单表无此单 / 订单无商品金额 / 无收款账单,未匹配到商品金额);商品金额未加种菜(商品金额=0 且是种菜单)、商品金额取数为0(商品金额=0 但不是种菜单)、商品金额未减冲减(商品金额=收入+优惠 → 差额=冲减金额)、商品金额未减优惠(商品金额=收入+冲减 → 差额=优惠金额)、商品金额未减优惠和冲减(商品金额=收入 → 差额=优惠+冲减);都不满足则留空。',
'description' => '覆盖店铺收款账户账单各平台页签(总账备注=订单收入),按「店铺 + 订单号」匹配订单侧商品金额与订单实付金额;一行 = 一个店铺的一个订单,商品金额只挂一次。「商品金额」只取主单(拆单子单不参与,整单没有主单时退化为取全部行),并排除虚拟条码(X0000x:信誉订单 / 虚拟订单)明细行;「订单实付金额」取订单级实付(拆单取主单)再减去虚拟条码行实付。「优惠金额」「冲减金额」另取总账备注=优惠收入 / 冲减收入的账单金额,不计入收入金额;「退款金额」另取总账备注=退款的账单金额,只单独列示,不计入收入金额、也不参与差额;「种菜金额」是订单号在种菜明细表或聚水潭订单标签明细(是否种菜订单=是)任意一边出现过时,该订单的收入金额(两个条件是或的关系),只是收入金额里的一部分,不参与差额;差额=(收入金额+优惠金额+冲减金额)−订单实付金额,匹配状态与差额异常备注同样按订单实付金额判定,未匹配到订单实付金额的行按订单实付金额=0 计入差额,只反映真正对不上的部分。挂不到订单行的优惠/冲减会补成一行(收入/商品留空)。「差额异常备注」只说明差额为什么产生,按顺序自动判定:订单实付未匹配(账单无单号 / 单号格式异常 / 订单表无此单 / 订单无实付金额 / 无收款账单,未匹配到订单实付金额);订单实付取数为0(种菜)(订单实付=0 且是种菜单)、订单实付取数为0(订单实付=0 但不是种菜单)、订单实付未减冲减(订单实付=收入+优惠 → 差额=冲减金额)、订单实付未减优惠(订单实付=收入+冲减 → 差额=优惠金额)、订单实付未减优惠和冲减(订单实付=收入 → 差额=优惠+冲减);都不满足则留空。',
'empty' => '店铺收款匹配订单明细尚未同步,请先执行 store-receipt:order-match:sync。',
'fields' => [
['name' => 'platform', 'label' => '平台'],

View File

@ -0,0 +1,15 @@
<?php
return [
/*
* 业绩明细「差额 / 匹配状态 / 差额异常备注」的比对基准。
*
* paid = 订单实付金额(2026-10-10 起使用)
* product = 订单商品金额(旧口径,2026-10-10 之前一直使用,保留以便随时切回)
*
* 两个口径的取数链完全一致(同店同号 → 店铺名不一致 → 同号多条 → 空店铺兜底),
* 只是把「订单侧金额」从商品金额换成订单实付金额;差额公式仍是
* (收入金额 + 优惠金额 + 冲减金额)− 订单侧金额,退款金额单独列示、不参与差额。
*/
'difference_basis' => env('PERFORMANCE_DETAILS_DIFFERENCE_BASIS', 'paid'),
];

View File

@ -218,7 +218,7 @@
.am-modal__close { border: 0; background: transparent; font-size: 1.1rem; cursor: pointer; color: #8a8377; }
/* 操作记录 */
/* 多选筛选(账单项目 / 平台 / 大类 / 方向基准):点按钮展开复选列表,可同时选多个 */
/* 多选筛选(财务备注 / 平台 / 大类 / 方向基准):点按钮展开复选列表,可同时选多个 */
.am-ms { position: relative; }
.am-ms__box { position: relative; display: block; }
.am-ms__btn {
@ -429,8 +429,8 @@
<span>搜索</span>
<input type="search" id="amKeyword" placeholder="平台 / 账单项目 / 备注 / 科目">
</label>
<div class="upstream-data-field am-ms" id="amItem">
<span>账单项目</span>
<div class="upstream-data-field am-ms" id="amMemo">
<span>财务备注</span>
<span class="am-ms__box">
<button type="button" class="am-ms__btn" data-role="btn" aria-expanded="false">
<span class="am-ms__label">全部</span><span class="am-ms__caret">▾</span>
@ -708,14 +708,14 @@
}
var msPlatform = createMultiSelect(document.getElementById('amPlatform'), {
onChange: function () { rebuildItemOptions(); render(); }
onChange: function () { rebuildMemoOptions(); render(); }
});
var msCategory = createMultiSelect(document.getElementById('amCategory'), { onChange: render });
var msBasis = createMultiSelect(document.getElementById('amBasis'), { onChange: render });
var msItem = createMultiSelect(document.getElementById('amItem'), {
var msMemo = createMultiSelect(document.getElementById('amMemo'), {
searchable: true,
searchPlaceholder: '打字找账单项目(可多选)',
dynamic: itemCandidates,
searchPlaceholder: '打字找财务备注(可多选)',
dynamic: memoCandidates,
onChange: render
});
@ -842,15 +842,15 @@
function visibleRows() {
var keyword = (kw.value || '').trim().toLowerCase();
var platforms = msPlatform.values();
var items = msItem.values();
var itemKeyword = msItem.keyword();
var memos = msMemo.values();
var memoKeyword = msMemo.keyword();
var categories = msCategory.values();
var bases = msBasis.values();
return sortedRows().filter(function (r) {
if (platforms.length && platforms.indexOf(r.platform) < 0) { return false; }
// 账单项目:勾选的按精确匹配(可多选,取并集);候选项列表里打的关键字按「包含」匹配
if (items.length && items.indexOf(String(r.item || '')) < 0) { return false; }
if (itemKeyword && String(r.item || '').toLowerCase().indexOf(itemKeyword) < 0) { return false; }
// 财务备注:勾选的按精确匹配(可多选,取并集);候选项列表里打的关键字按「包含」匹配
if (memos.length && memos.indexOf(String(r.memo || '')) < 0) { return false; }
if (memoKeyword && String(r.memo || '').toLowerCase().indexOf(memoKeyword) < 0) { return false; }
if (categories.length && categories.indexOf(r.category) < 0) { return false; }
if (bases.length && bases.indexOf(r.basis) < 0) { return false; }
if (fOnly.value) {
@ -1011,7 +1011,7 @@
msPlatform.refresh(OPTIONS.platforms || []);
msCategory.refresh(categoryOptions());
msBasis.refresh(['支出基准', '收入基准']);
rebuildItemOptions();
rebuildMemoOptions();
}
// 大类筛选只列「表里真的用到」的大类:清空后剩下 0 条的(例如原来的售后费用/平台佣金/平台服务费)
@ -1037,21 +1037,21 @@
return used;
}
// 账单项目的候选:按当前选中的平台收窄,列表里再按关键字过滤
function itemCandidates() {
// 财务备注的候选:按当前选中的平台收窄,列表里再按关键字过滤
function memoCandidates() {
var platforms = msPlatform.values();
var seen = {};
ROWS.forEach(function (r) {
if (platforms.length && platforms.indexOf(r.platform) < 0) { return; }
var item = String(r.item || '');
if (!item) { return; }
seen[item] = true;
var memo = String(r.memo || '');
if (!memo) { return; }
seen[memo] = true;
});
return Object.keys(seen).sort();
}
// 候选列表在数据变化(导入/新增/删除)后刷新
function rebuildItemOptions() { msItem.refresh(); }
function rebuildMemoOptions() { msMemo.refresh(); }
function loadCoa() {
if (COA) { return Promise.resolve(COA); }
@ -1462,11 +1462,11 @@
fOnly.addEventListener('change', render);
document.getElementById('amResetFilter').addEventListener('click', function () {
kw.value = ''; fOnly.value = '';
msItem.clear();
msMemo.clear();
msPlatform.clear();
msCategory.clear();
msBasis.clear();
rebuildItemOptions();
rebuildMemoOptions();
render();
});

View File

@ -1097,6 +1097,7 @@
<span class="receipt-match-summary__item"><em>退款金额合计</em><strong>{{ number_format((float) ($receiptStats['refund_total'] ?? 0), 2) }}</strong></span>
<span class="receipt-match-summary__item"><em>种菜金额合计</em><strong>{{ number_format((float) ($receiptStats['planting_total'] ?? 0), 2) }}</strong></span>
<span class="receipt-match-summary__item"><em>订单商品金额合计</em><strong>{{ number_format((float) $receiptStats['goods_total'], 2) }}</strong></span>
<span class="receipt-match-summary__item"><em>订单实付金额合计</em><strong>{{ number_format((float) ($receiptStats['paid_total'] ?? 0), 2) }}</strong></span>
<span class="receipt-match-summary__item receipt-match-summary__item--lead"><em>差额合计</em><strong>{{ number_format((float) $receiptStats['difference_total'], 2) }}</strong></span>
</div>
</div>