fix: 微信支付入账补记 + 财务按俱乐部分组

This commit is contained in:
XingQue
2026-06-24 21:10:55 +08:00
parent 0827da594c
commit a6068f7e2b
7 changed files with 240 additions and 35 deletions

View File

@@ -0,0 +1,102 @@
"""补记漏掉的微信收入统计(今日或指定日期已支付但未入账的单据)。"""
from datetime import date, datetime, timedelta
from django.core.management.base import BaseCommand
from django.db.models import Q
from config.models import PlatformIncomeLog
from jituan.constants import CLUB_ID_DEFAULT
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
from orders.models import Order
from products.models import Czjilu
class Command(BaseCommand):
help = '扫描已支付订单/充值单,补记 daily_income_stat幂等可重复执行'
def add_arguments(self, parser):
parser.add_argument(
'--date',
type=str,
default='',
help='补记日期 YYYY-MM-DD默认今天',
)
parser.add_argument(
'--days',
type=int,
default=1,
help='从指定日期起向前补几天,默认 1仅当天',
)
def handle(self, *args, **options):
if options['date']:
end_day = date.fromisoformat(options['date'])
else:
end_day = date.today()
days = max(1, int(options['days'] or 1))
start_day = end_day - timedelta(days=days - 1)
repaired = 0
skipped = 0
errors = 0
for day in range(days):
d = start_day + timedelta(days=day)
day_start = datetime.combine(d, datetime.min.time())
day_end = day_start + timedelta(days=1)
self.stdout.write(f'扫描 {d} ...')
orders = Order.query.filter(
CreateTime__gte=day_start,
CreateTime__lt=day_end,
Status__in=[1, 2, 3, 4, 5, 6, 7, 8],
)
for od in orders:
club_id = getattr(od, 'ClubID', None) or CLUB_ID_DEFAULT
ref = f'order:{od.OrderID}'
try:
ok = self._repair_one(od.Amount, club_id, biz_ref=ref)
if ok:
repaired += 1
self.stdout.write(f' + 订单 {od.OrderID} {od.Amount}元 club={club_id}')
else:
skipped += 1
except Exception as exc:
errors += 1
self.stderr.write(f' ! 订单 {od.OrderID} 失败: {exc}')
cz_orders = Czjilu.query.filter(
CreateTime__gte=day_start,
CreateTime__lt=day_end,
zhuangtai=3,
).filter(
Q(leixing__in=[1, 2, 3, 4]) | Q(leixing__isnull=False),
)
for cz in cz_orders:
club_id = getattr(cz, 'club_id', None) or CLUB_ID_DEFAULT
ref = f'cz:{cz.dingdan_id}'
try:
ok = self._repair_one(cz.jine, club_id, biz_ref=ref)
if ok:
repaired += 1
self.stdout.write(f' + 充值 {cz.dingdan_id} {cz.jine}元 leixing={cz.leixing} club={club_id}')
else:
skipped += 1
except Exception as exc:
errors += 1
self.stderr.write(f' ! 充值 {cz.dingdan_id} 失败: {exc}')
self.stdout.write(self.style.SUCCESS(
f'完成:新补记 {repaired} 笔,已存在跳过 {skipped} 笔,失败 {errors}',
))
def _repair_one(self, amount, club_id, biz_ref):
"""幂等补记;若仅有孤儿幂等日志无日统计,删日志后重试一次。"""
ref = (biz_ref or '').strip()
ok = repair_wechat_income_if_missing(amount, club_id, biz_ref=ref)
if ok:
return True
if PlatformIncomeLog.objects.filter(biz_ref=ref).exists():
PlatformIncomeLog.objects.filter(biz_ref=ref).delete()
return repair_wechat_income_if_missing(amount, club_id, biz_ref=ref)
return False

View File

@@ -35,6 +35,15 @@ def fulfill_yajin_recharge(dingdan_id, paid_amount_yuan=None):
if order.leixing != 2:
raise CzjiluFulfillError('订单类型不是押金', 'type_error')
if order.zhuangtai != 9:
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None) or CLUB_ID_DEFAULT,
biz_ref=f'cz:{dingdan_id}',
)
except Exception as exc:
logger.error('押金补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
return {'already_done': True, 'dingdan_id': dingdan_id, 'zhuangtai': order.zhuangtai}
amount = Decimal(str(order.jine))
@@ -56,8 +65,8 @@ def fulfill_yajin_recharge(dingdan_id, paid_amount_yuan=None):
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
try:
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(amount, club_id, biz_ref=f'cz:{dingdan_id}')
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(amount, club_id, biz_ref=f'cz:{dingdan_id}')
except Exception as exc:
logger.error('押金收支统计失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
@@ -116,6 +125,15 @@ def fulfill_jifen_recharge(dingdan_id, paid_amount_yuan=None):
if order.leixing != 3:
raise CzjiluFulfillError('订单类型不是积分', 'type_error')
if order.zhuangtai != 9:
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None) or CLUB_ID_DEFAULT,
biz_ref=f'cz:{dingdan_id}',
)
except Exception as exc:
logger.error('积分补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
return {'already_done': True, 'dingdan_id': dingdan_id, 'zhuangtai': order.zhuangtai}
amount = Decimal(str(order.jine))
@@ -140,8 +158,8 @@ def fulfill_jifen_recharge(dingdan_id, paid_amount_yuan=None):
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
try:
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(amount, club_id, biz_ref=f'cz:{dingdan_id}')
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(amount, club_id, biz_ref=f'cz:{dingdan_id}')
except Exception as exc:
logger.error('积分收支统计失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
@@ -162,6 +180,15 @@ def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing):
raise CzjiluFulfillError('订单类型不匹配', 'type_error')
if order.zhuangtai == 3:
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None) or CLUB_ID_DEFAULT,
biz_ref=f'cz:{dingdan_id}',
)
except Exception as exc:
logger.error('充值单补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
return {'already_done': True, 'zhuangtai': 3}
if order.zhuangtai != 9:

View File

@@ -335,6 +335,15 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'
'会员订单已处理,跳过履约 dingdan=%s zhuangtai=%s source=%s',
dingdan_id, order.zhuangtai, source,
)
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None) or CLUB_ID_DEFAULT,
biz_ref=f'cz:{dingdan_id}',
)
except Exception as exc:
logger.error('会员补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
return {
'already_done': True,
'dingdan_id': dingdan_id,
@@ -380,8 +389,8 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'
jine_decimal = Decimal(str(order.jine))
try:
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(jine_decimal, club_id, biz_ref=f'cz:{dingdan_id}')
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(jine_decimal, club_id, biz_ref=f'cz:{dingdan_id}')
except Exception as exc:
logger.error('会员收支统计更新失败: %s', exc, exc_info=True)

View File

@@ -60,7 +60,7 @@ def record_wechat_income_once(amount, club_id=None, biz_ref=None):
"""
微信入账:同一 biz_ref 只记一次 daily_income_stat + szjilu。
返回 True=本次新入账False=已记过(重复回调)。
若 platform_income_log 表不可用,仍记入收入(与改造前一致,不能因幂等表故障丢统计)
幂等日志与日统计在同一事务内提交,避免「已支付但未入账」
"""
ref = (biz_ref or '').strip()
if not ref:
@@ -75,6 +75,7 @@ def record_wechat_income_once(amount, club_id=None, biz_ref=None):
club_id=cid,
amount=jine,
)
_apply_income_stats(jine, cid)
except IntegrityError:
logger.info('平台入账已存在,跳过重复记账 biz_ref=%s', ref)
return False
@@ -87,24 +88,25 @@ def record_wechat_income_once(amount, club_id=None, biz_ref=None):
return True
except Exception as exc:
logger.error(
'platform_income_log 入异常,仍记入收入 biz_ref=%s err=%s',
'platform_income_log 入异常,仍尝试记入收入 biz_ref=%s err=%s',
ref, exc, exc_info=True,
)
_apply_income_stats(jine, cid)
return True
try:
_apply_income_stats(jine, cid)
except Exception:
try:
PlatformIncomeLog.objects.filter(biz_ref=ref).delete()
except Exception:
pass
raise
logger.info('平台入账记账成功 biz_ref=%s club=%s amount=%s', ref, cid, jine)
return True
def repair_wechat_income_if_missing(amount, club_id=None, biz_ref=None):
"""已支付订单/充值单重复回调时安全补记收支(幂等)。"""
try:
return record_wechat_income_once(amount, club_id, biz_ref)
except Exception as exc:
logger.error('补记收支失败 biz_ref=%s err=%s', biz_ref, exc, exc_info=True)
return False
def apply_szjilu_expense(amount, club_id=None):
"""平台出款/支出:总收益减少,总支出、今日支出增加。"""
jine = Decimal(str(amount))

View File

@@ -205,9 +205,16 @@ class WechatPayNotifyView(APIView):
logger.info(f"订单当前状态: {dingdan.Status}")
# 幂等处理只有状态为9未支付才处理已处理则跳过
# 幂等:已支付则补试收支记账(防止首次入账失败后再也无法统计)
if dingdan.Status != 9:
logger.info(f"订单 {out_trade_no} 已处理过,状态={dingdan.Status}跳过")
logger.info(f"订单 {out_trade_no} 已处理过,状态={dingdan.Status}补试收支记账")
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{out_trade_no}',
)
except Exception as e:
logger.error(f'订单补记收支失败 {out_trade_no}: {e}', exc_info=True)
return
# 保存微信交易号
@@ -309,8 +316,8 @@ class WechatPayNotifyView(APIView):
# 更新收支记录(总累计);统计失败不得回滚订单支付
try:
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{out_trade_no}',
)
except Exception as e:
@@ -498,7 +505,15 @@ class PaymentVerifyView(APIView):
# 重新锁定订单,防止并发更新
dingdan = Order.query.select_for_update().get(pk=dingdan.pk)
if dingdan.Status != 9:
# 订单状态已被其他进程更新,直接返回(幂等)
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
dingdan.Amount,
getattr(dingdan, 'ClubID', None),
biz_ref=f'order:{dingdan.OrderID}',
)
except Exception as e:
logger.error(f'订单复查补记收支失败 {dingdan.OrderID}: {e}', exc_info=True)
return
# 更新订单状态
@@ -571,10 +586,9 @@ class PaymentVerifyView(APIView):
# =============================================
# 更新收支记录
try:
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
dingdan.Amount,
getattr(dingdan, 'ClubID', None),
biz_ref=f'order:{dingdan.OrderID}',

View File

@@ -608,7 +608,15 @@ class YajinHuitiao(View):
# 5. 检查订单状态是否为未支付9
if order.zhuangtai != 9:
logger.warning(f"订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}")
# 已处理过的订单直接返回成功,避免微信重复回调
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None),
biz_ref=f'cz:{out_trade_no}',
)
except Exception as exc:
logger.error('押金回调补记收支失败 %s: %s', out_trade_no, exc, exc_info=True)
return self.wechat_response('SUCCESS', 'OK')
# 6. 验证金额(微信返回的是分,需要转换)
@@ -1117,6 +1125,15 @@ class JifenHuitiao(View):
# 5. 检查订单状态是否为未支付9
if order.zhuangtai != 9:
logger.warning(f"积分订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}")
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None),
biz_ref=f'cz:{out_trade_no}',
)
except Exception as exc:
logger.error('积分回调补记收支失败 %s: %s', out_trade_no, exc, exc_info=True)
return self.wechat_response('SUCCESS', 'OK')
# 6. 验证金额固定5元
@@ -1663,6 +1680,16 @@ class HuiyuanHuitiao(View):
if order.zhuangtai == 3:
from jituan.services.member_recharge import repair_member_entitlement_if_paid
repair_member_entitlement_if_paid(out_trade_no)
try:
from decimal import Decimal
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None),
biz_ref=f'cz:{out_trade_no}',
)
except Exception as exc:
logger.error('会员回调补记收支失败 %s: %s', out_trade_no, exc, exc_info=True)
return self.wechat_response('SUCCESS', 'OK')
wechat_amount_yuan = total_fee / 100
@@ -2232,7 +2259,15 @@ class ShangjiaHuitiao(View):
# 5. 检查订单状态是否为未支付9
if order.zhuangtai != 9:
logger.warning(f"商家订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}")
# 已处理过的订单直接返回成功,避免微信重复回调
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None),
biz_ref=f'cz:{order.dingdan_id}',
)
except Exception as exc:
logger.error('商家回调补记收支失败 %s: %s', out_trade_no, exc, exc_info=True)
return self.wechat_response('SUCCESS', 'OK')
# 6. 验证金额(微信返回的是分,需要转换)
@@ -2248,8 +2283,8 @@ class ShangjiaHuitiao(View):
# 更新收支记录表
try:
jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
jine_decimal,
getattr(order, 'club_id', None),
biz_ref=f'cz:{order.dingdan_id}',

View File

@@ -11928,7 +11928,15 @@ class FaKuanHuitiaoView(View):
# 5. 检查订单状态是否为未支付9
if order.zhuangtai != 9:
logger.warning(f"订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}")
# 已处理过的订单直接返回成功,避免微信重复回调
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None),
biz_ref=f'cz:{out_trade_no}',
)
except Exception as exc:
logger.error('罚款回调补记收支失败 %s: %s', out_trade_no, exc, exc_info=True)
return self.wechat_response('SUCCESS', 'OK')
# 6. 验证金额(微信返回的是分,需要转换)
@@ -11970,8 +11978,8 @@ class FaKuanHuitiaoView(View):
# 9. 更新收支记录表
try:
jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
jine_decimal,
getattr(order, 'club_id', None),
biz_ref=f'cz:{order.dingdan_id}',
@@ -12475,9 +12483,17 @@ class KaohePayCallbackView(View):
if order.zhuangtai != 9:
logger.warning(f"订单状态非未支付: {order.zhuangtai}, 订单号: {out_trade_no}")
# 已支付的订单直接返回成功,避免微信重复回调
if order.zhuangtai == 3:
logger.info(f"订单已支付,直接返回成功: {out_trade_no}")
logger.info(f"订单已支付,补试收支记账: {out_trade_no}")
try:
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
Decimal(str(order.jine)),
getattr(order, 'club_id', None),
biz_ref=f'cz:{out_trade_no}',
)
except Exception as exc:
logger.error('考核回调补记收支失败 %s: %s', out_trade_no, exc, exc_info=True)
return self.wechat_response('SUCCESS', 'OK')
return self.wechat_response('FAIL', '订单状态异常')
@@ -12532,8 +12548,8 @@ class KaohePayCallbackView(View):
# 更新收支记录
try:
jine_decimal = Decimal(str(order.jine))
from jituan.services.szjilu_accounting import record_wechat_income_once
record_wechat_income_once(
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
repair_wechat_income_if_missing(
jine_decimal,
getattr(order, 'club_id', None),
biz_ref=f'cz:{order.dingdan_id}',