业绩明细:差额/匹配状态/备注改为与订单实付金额核对;科目映射表筛选改按财务备注
- 业绩明细「差额 / 匹配状态 / 差额异常备注」的比对基准由「订单商品金额」换成「订单实付金额」: 差额=(收入金额+优惠金额+冲减金额)− 订单实付金额;匹配状态以订单侧能否取到订单实付为准; 备注文案改为 订单实付未匹配(账单无单号/单号格式异常/订单表无此单/订单无实付金额/无收款账单)、 订单实付取数为0(种菜)、订单实付取数为0、订单实付未减冲减、订单实付未减优惠、订单实付未减优惠和冲减 - 商品金额列保留展示、不再作为核对基准;退款金额仍单独列示、不参与差额 - 两个口径共用同一条取数链(同店同号 → 店铺名不一致 → 同号多条 → 空店铺兜底), 差额口径做成开关:config/performance_details.php 的 difference_basis 填 product 即可切回旧口径 - 统计卡「金额构成」补「订单实付金额合计」,与差额公式对齐 - 科目映射表筛选:第二个筛选项由「账单项目」改为「财务备注」(候选按平台收窄、可搜索、多选); 账单项目仍可用左上角关键词搜索
This commit is contained in:
parent
37d18612a6
commit
158c5e1b23
@ -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
|
||||
{
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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' => '平台'],
|
||||
|
||||
15
config/performance_details.php
Normal file
15
config/performance_details.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
* 业绩明细「差额 / 匹配状态 / 差额异常备注」的比对基准。
|
||||
*
|
||||
* paid = 订单实付金额(2026-10-10 起使用)
|
||||
* product = 订单商品金额(旧口径,2026-10-10 之前一直使用,保留以便随时切回)
|
||||
*
|
||||
* 两个口径的取数链完全一致(同店同号 → 店铺名不一致 → 同号多条 → 空店铺兜底),
|
||||
* 只是把「订单侧金额」从商品金额换成订单实付金额;差额公式仍是
|
||||
* (收入金额 + 优惠金额 + 冲减金额)− 订单侧金额,退款金额单独列示、不参与差额。
|
||||
*/
|
||||
'difference_basis' => env('PERFORMANCE_DETAILS_DIFFERENCE_BASIS', 'paid'),
|
||||
];
|
||||
@ -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();
|
||||
});
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user