营收统计偏差排查说明

结论

本次排查的统计入口为管理端 DataStatisticsController#getIncomeData(/dataStatistics/getIncomeData)。它将多个业务订单转换为 IncomeVO 后做年、月、周、日汇总,并非从统一支付流水汇总。

代码已能确认:当前结果**不能作为全量实际收入**。偏差同时来自“应统计订单未接入”“已接入订单取错金额/时间/范围”和“退款、优惠券处理不完整”。工作区没有可连接的业务库,因此无法在本次排查中给出每一项的实际差额或判定具体旧/测试订单;文末列出了数据核对方式。

已确认的问题(按优先级)

  1. 惠民卡、优惠券购买完全没有进入主统计。
    主统计只读取会员、充值、课程、赛事、订场、积分商品和游戏记录;没有调用 PayHuiminClient 或 UserCouponPayment。因此所有付费惠民卡、积分+现金购买优惠券都被漏记。

  2. 积分商品统计把商品配置价当成订单实付,并且混入非门票商品。
    UserPointsMerchandiseMapper#getAmount 取的是 t_points_merchandise.cash,而非 t_user_points_merchandise.cash;前者可能在下单后被修改,后者才是订单快照实付金额。查询亦未限制商品类型,所以实体商品、运动营、门票及其它积分商品都会进入同一笔“营收”。这会同时造成门票收入不准确、积分商城收入无法拆分,并可能高估或低估。

  3. 会员退款在源查询已被排除,主统计也没有退款补记。
    管理员退款将 t_vip_payment.payStatus 改为 3,而会员查询固定 payStatus = 2。退款订单从总营收中消失,但没有在退款日生成负数退款记录。因此退款前后的历史及当期趋势都失真,无法满足“管理员修改订单状态后减收入”的要求。

  4. 课程退款未参与主统计。
    课程查询固定 t_course_package_order.payStatus = 2,主统计没有读取课程退款记录(MoneyBack 仅被另一个退费页面使用)。若课程退款改变订单状态,会直接从收入中消失;若保留支付状态,则仍会留在收入中。两种情况都没有按退款时间扣减。

  5. 赛事、订场退款只生成退款记录,没有同时保留原支付记录。
    对状态为退款的订单,主统计只写入 refundAmount,并 continue 跳过原支付金额。全量净额偶然可能相等,但按支付日/退款日统计时,原支付日少记、退款日出现负数;“总营收”也不是支付成交额。赛事还会排除 payType=3/4,订场同样排除,这些订单是否属于实际收入需业务确认。

  6. 优惠券抵扣仅按当前券配置中的固定 deductionAmount 计算,不是订单实际优惠。
    课程和订场通过 userCouponId 回查券配置,并按 content.deductionAmount 回填。满减券、代金券若该字段存在可被扣减;折扣券的优惠取决于订单金额,当前无订单优惠快照字段参与计算,通常得到 0 或固定值,不能保证正确。券规则后来修改也会回算历史订单,导致历史数据漂移。退款订单也没有将已抵扣券恢复/冲销的独立口径。

  7. 统计混入旧储值/测试数据的风险高,且范围规则不统一。
    主统计仍包含 t_recharge_records;但 2026-09 的迁移已禁用充值和储值管理,历史充值是否应属于“营收”需要单独定口径。会员、充值按用户 insertType/创建人筛选,课程/订场按门店,赛事按运营商或门店,积分商品按 cityManagerId,游戏按游戏归属。相同门店下的不同收入类型可被不同范围纳入或排除,旧系统和测试用户没有统一剔除条件。

  8. 游戏记录在门店分支的支付方式条件互斥。
    BallController#getData 的门店查询连续 eq(payType, 1).eq(payType, 2),等价于 payType=1 AND payType=2,不会返回数据。因此门店维度的激战设备游戏启动支付收入必然漏记;平台/运营商分支则排除 payType=3/4。

收入类型覆盖矩阵

需求收入类型 当前数据来源与现状 结论
预约管理散客及订场 t_site_booking.payMoney,包含退款订单后另记退款 部分覆盖;退款时间口径错误,券抵扣不可靠
所有在售门票 积分商品查询,未限定门票类型且取配置价 不正确,无法保证“所有门票”
所有惠民卡 主统计未查询 t_pay_huimin 完全漏记
会员支付 t_vip_payment.amount 付费订单可计;管理员退款未正确扣减
课程售卖 t_course_package_order.cashPayment 付费订单可计;课程退款、折扣券不正确
活动及赛事报名 t_payment_competition.amount 部分覆盖;退款按退款日缺少原支付日,支付方式范围待确认
激战设备游戏启动支付 t_game_record.money 平台/运营商部分覆盖;门店维度必漏
积分商城 与门票混在同一查询 未独立统计;金额取错
优惠券购买 t_user_coupon_payment 未接入 完全漏记

券与退款核对结果

场景 当前行为 是否满足要求
代金券、满减券 尝试从当前券配置读取固定抵扣值后从净额扣除 仅在规则未变且字段完整时可能正确,不能确认
折扣券 没有按订单金额计算折扣,也没有订单优惠快照 否
用户自主退款(订场、赛事) 仅在退款日写退款额,原支付额不保留 全量净额可能接近正确;分期/趋势统计不正确
管理员手动退款/改状态(会员) 源查询排除退款单,没有退款流水 否
课程退款 主统计未接入退款记录 否
惠民卡退款 主统计未接入惠民卡 否

建议的统一口径

  • 统计单位应为“支付/退款事实流水”,而不是当前订单状态。每个成功支付生成一条正流水,退款成功生成一条负流水;分别使用 payTime 和 refundTime 入账。
  • 实收 应使用订单支付快照(如 cashPayment、payMoney、amount、订单 cash),不得使用可变的商品/券配置价格。
  • 同时输出三个互不混淆的指标:成交额(原价/应收)、优惠金额、实收净额;页面若仅叫“营收”,应明确显示的是实收净额。
  • 给每条流水记录 businessType、订单 ID、支付状态、退款状态、支付方式、门店/运营商快照和是否测试/迁移数据标识;统一由一个统计查询或日汇总表生成。
  • 充值/储值应与销售收入分开。若业务定义为负债充值,则不应加入营收;若保留历史展示,应单列“历史储值”并指定截止时间和测试账号剔除规则。

上线调整前的数据核实清单

  1. 从各订单表按支付成功状态、支付时间、实际支付字段汇总,并与支付渠道(微信/支付宝)账单按订单号核对;确认 payType=3/4 是非现金、赠送还是有效支付。
  2. 单独汇总 t_pay_huimin(排除后台赠送 paymentType=3)、t_user_coupon_payment.status=2、以及积分商品订单中的门票和非门票;与主统计差额应能直接定位到遗漏类型。
  3. 对每个订单表列出退款状态、退款时间、退款金额和订单号;区分真实退款回调成功、线下退款、仅管理员改状态。不能以“当前订单非已支付”替代退款流水。
  4. 抽取所有带 userCouponId 的课程/订场订单,比较订单原价、实付、券规则、实际优惠;特别抽查折扣券和规则修改前的历史券。
  5. 建立测试/迁移数据名单或明确切割日期、门店、用户/订单号前缀;在统一统计 SQL 中显式排除,而非依赖当前角色或用户创建类型的隐式过滤。

关键代码位置

  • 主统计拼接与退款/券处理:cloud-server-management/.../DataStatisticsController.java:169-386
  • 汇总公式:DataStatisticsController.java:463-481
  • 积分商品查询:cloud-server-activity/.../UserPointsMerchandiseMapper.xml:20-30
  • 惠民卡已有统计口径:cloud-server-activity/.../PayHuiminMapper.xml:67-118
  • 会员已支付筛选:cloud-server-account/.../RechargeRecordsMapper.xml:38-60
  • 课程已支付筛选:cloud-server-course/.../CoursePackagePaymentConfigMapper.xml:64-120
  • 订场退款筛选:cloud-server-other/.../SiteBookingMapper.xml:23-50
  • 赛事退款筛选:cloud-server-competition/.../PaymentCompetitionMapper.xml:65-93