fix: separate penalty czjilu from merchant recharge and correct finance stats
This commit is contained in:
219
backend/view.py
219
backend/view.py
@@ -7663,228 +7663,29 @@ class CwqtczhqView(APIView):
|
||||
if 'qtczcaiwu' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无其他充值财务查看权限'}, status=403)
|
||||
|
||||
from jituan.services.finance_detail_stats import build_chongzhi_finance_payload
|
||||
|
||||
granularity = request.data.get('granularity', 'day')
|
||||
year = request.data.get('year')
|
||||
month = request.data.get('month')
|
||||
types = request.data.get('types', [2, 3, 4, 5, 6]) # 默认全部
|
||||
types = request.data.get('types', [2, 3, 4, 5, 6])
|
||||
summary_date = request.data.get('summary_date')
|
||||
|
||||
if isinstance(types, list) and len(types) == 0:
|
||||
types = [2, 3, 4, 5, 6]
|
||||
|
||||
now = date.today()
|
||||
data, err = build_chongzhi_finance_payload(
|
||||
request, granularity, year, month, types, summary_date,
|
||||
)
|
||||
if err:
|
||||
return Response({'code': 1, 'msg': err}, status=400)
|
||||
|
||||
# ---------- 时间范围(用于曲线图) ----------
|
||||
if granularity == 'day':
|
||||
if not year or not month:
|
||||
return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400)
|
||||
start_date = date(year, month, 1)
|
||||
end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1)
|
||||
trunc_func = TruncDate('CreateTime')
|
||||
# 默认汇总日期
|
||||
if not summary_date:
|
||||
summary_date = now.strftime('%Y-%m-%d')
|
||||
summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date()
|
||||
elif granularity == 'month':
|
||||
if not year:
|
||||
return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400)
|
||||
start_date = date(year, 1, 1)
|
||||
end_date = date(year + 1, 1, 1)
|
||||
trunc_func = TruncMonth('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = now.strftime('%Y-%m')
|
||||
summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date()
|
||||
elif granularity == 'year':
|
||||
year = year or now.year
|
||||
start_date = date(year, 1, 1)
|
||||
end_date = date(year + 1, 1, 1)
|
||||
trunc_func = TruncYear('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = str(year)
|
||||
summary_q = date(year, 1, 1)
|
||||
else:
|
||||
return Response({'code': 1, 'msg': '无效的颗粒度'}, status=400)
|
||||
|
||||
start_dt = datetime.combine(start_date, datetime.min.time())
|
||||
end_dt = datetime.combine(end_date, datetime.min.time())
|
||||
|
||||
# ---------- 构建时间序列 ----------
|
||||
time_series_map = {} # period_str -> { type_code: {count, amount, profit, fenhong_count, fenhong_amount} }
|
||||
|
||||
def ensure_date(ds):
|
||||
if ds not in time_series_map:
|
||||
time_series_map[ds] = {}
|
||||
return time_series_map[ds]
|
||||
|
||||
# 1. 处理充值记录(Czjilu)—— 完全不变
|
||||
cz_types = [t for t in types if t in [2, 3, 4, 5]]
|
||||
if cz_types:
|
||||
cz_qs = filter_queryset_by_club(
|
||||
Czjilu.query.filter(
|
||||
CreateTime__gte=start_dt,
|
||||
CreateTime__lt=end_dt,
|
||||
leixing__in=cz_types,
|
||||
zhuangtai=3,
|
||||
),
|
||||
request,
|
||||
).annotate(
|
||||
period=trunc_func
|
||||
).values('period', 'leixing').annotate(
|
||||
total_count=Count('id'),
|
||||
total_amount=Sum('jine')
|
||||
).order_by('period', 'leixing')
|
||||
|
||||
for item in cz_qs:
|
||||
period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
|
||||
item['period'].strftime('%Y-%m') if granularity == 'month' else \
|
||||
item['period'].strftime('%Y')
|
||||
lx = item['leixing']
|
||||
amount = float(item['total_amount'] or 0)
|
||||
count = item['total_count']
|
||||
# 收益:积分(3)收益为金额本身,其他为0
|
||||
profit = amount if lx == 3 else 0.0
|
||||
|
||||
day_data = ensure_date(period_str)
|
||||
day_data[str(lx)] = {
|
||||
'count': count,
|
||||
'amount': amount,
|
||||
'profit': profit,
|
||||
'fenhong_count': 0, # 充值类型无分红
|
||||
'fenhong_amount': 0.0
|
||||
}
|
||||
|
||||
# 2. 处理罚款(Penalty)—— 修改:直接从 Penalty 表获取分红总额和分红笔数
|
||||
if 6 in types:
|
||||
# 使用一次聚合查询,同时获取罚款笔数、罚款金额、分红总额、分红笔数
|
||||
penalty_qs = filter_penalty_qs(
|
||||
Penalty.query.filter(
|
||||
CreateTime__gte=start_dt,
|
||||
CreateTime__lt=end_dt,
|
||||
Status=2,
|
||||
),
|
||||
request,
|
||||
).annotate(
|
||||
period=trunc_func
|
||||
).values('period').annotate(
|
||||
penalty_count=Count('id'),
|
||||
penalty_amount=Sum('FineAmount'),
|
||||
# 直接使用罚单表中的申请人分红金额字段
|
||||
fenhong_amount=Sum('ApplicantBonusAmount'),
|
||||
fenhong_count=Count('id', filter=Q(ApplicantBonusAmount__gt=0))
|
||||
).order_by('period')
|
||||
|
||||
for item in penalty_qs:
|
||||
period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
|
||||
item['period'].strftime('%Y-%m') if granularity == 'month' else \
|
||||
item['period'].strftime('%Y')
|
||||
day_data = ensure_date(period_str)
|
||||
penalty_amt = float(item['penalty_amount'] or 0)
|
||||
fenhong_amt = float(item['fenhong_amount'] or 0)
|
||||
profit = round(penalty_amt - fenhong_amt, 2)
|
||||
day_data['6'] = {
|
||||
'count': item['penalty_count'],
|
||||
'amount': penalty_amt,
|
||||
'profit': profit,
|
||||
'fenhong_count': item['fenhong_count'],
|
||||
'fenhong_amount': fenhong_amt
|
||||
}
|
||||
|
||||
# 构造有序时间序列列表
|
||||
time_series = []
|
||||
for period_str in sorted(time_series_map.keys()):
|
||||
entry = {'date': period_str, 'types': {}}
|
||||
for type_code in sorted(time_series_map[period_str].keys()):
|
||||
entry['types'][type_code] = time_series_map[period_str][type_code]
|
||||
time_series.append(entry)
|
||||
|
||||
# ---------- 汇总指定日期(summary)—— 完全保留原逻辑,仅将罚款部分改为从 Penalty 表直接取分红 ----------
|
||||
summary_result = {'by_type': {}, 'total_count': 0, 'total_amount': 0.0, 'total_profit': 0.0}
|
||||
|
||||
# 汇总充值类型(完全不变)
|
||||
for lx in cz_types:
|
||||
filter_kwargs = {
|
||||
'leixing': lx,
|
||||
'zhuangtai': 3,
|
||||
}
|
||||
if granularity == 'day':
|
||||
filter_kwargs['CreateTime__date'] = summary_q
|
||||
elif granularity == 'month':
|
||||
filter_kwargs['CreateTime__year'] = summary_q.year
|
||||
filter_kwargs['CreateTime__month'] = summary_q.month
|
||||
else:
|
||||
filter_kwargs['CreateTime__year'] = summary_q.year
|
||||
agg = filter_queryset_by_club(
|
||||
Czjilu.query.filter(**filter_kwargs), request,
|
||||
).aggregate(count=Count('id'), amount=Sum('jine'))
|
||||
count = agg['count'] or 0
|
||||
amount = float(agg['amount'] or 0)
|
||||
profit = amount if lx == 3 else 0.0
|
||||
summary_result['by_type'][str(lx)] = {
|
||||
'count': count,
|
||||
'amount': round(amount, 2),
|
||||
'profit': round(profit, 2),
|
||||
'fenhong_count': 0,
|
||||
'fenhong_amount': 0.0
|
||||
}
|
||||
summary_result['total_count'] += count
|
||||
summary_result['total_amount'] += amount
|
||||
summary_result['total_profit'] += profit
|
||||
|
||||
# 汇总罚款(修改:从 Penalty 表直接取分红,不再查询 PenaltyBonus)
|
||||
if 6 in types:
|
||||
penalty_filter = {
|
||||
'Status': 2,
|
||||
}
|
||||
if granularity == 'day':
|
||||
penalty_filter['CreateTime__date'] = summary_q
|
||||
elif granularity == 'month':
|
||||
penalty_filter['CreateTime__year'] = summary_q.year
|
||||
penalty_filter['CreateTime__month'] = summary_q.month
|
||||
else:
|
||||
penalty_filter['CreateTime__year'] = summary_q.year
|
||||
|
||||
# 一次聚合:罚款笔数、金额、分红总额、分红笔数
|
||||
agg = filter_penalty_qs(
|
||||
Penalty.query.filter(**penalty_filter), request,
|
||||
).aggregate(
|
||||
count=Count('id'),
|
||||
amount=Sum('FineAmount'),
|
||||
fenhong_amount=Sum('ApplicantBonusAmount'),
|
||||
fenhong_count=Count('id', filter=Q(ApplicantBonusAmount__gt=0))
|
||||
)
|
||||
penalty_count = agg['count'] or 0
|
||||
penalty_amount = float(agg['amount'] or 0)
|
||||
fh_amount = float(agg['fenhong_amount'] or 0)
|
||||
fh_count = agg['fenhong_count'] or 0
|
||||
profit = round(penalty_amount - fh_amount, 2)
|
||||
|
||||
summary_result['by_type']['6'] = {
|
||||
'count': penalty_count,
|
||||
'amount': round(penalty_amount, 2),
|
||||
'profit': profit,
|
||||
'fenhong_count': fh_count,
|
||||
'fenhong_amount': round(fh_amount, 2)
|
||||
}
|
||||
summary_result['total_count'] += penalty_count
|
||||
summary_result['total_amount'] += penalty_amount
|
||||
summary_result['total_profit'] += profit
|
||||
|
||||
summary_result['total_amount'] = round(summary_result['total_amount'], 2)
|
||||
summary_result['total_profit'] = round(summary_result['total_profit'], 2)
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'time_series': time_series,
|
||||
'summary': summary_result
|
||||
}
|
||||
})
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': data})
|
||||
except Exception as e:
|
||||
logger.exception("CwqtczhqView 接口错误")
|
||||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||||
|
||||
'''class CwqtczhqView(APIView):
|
||||
'''class CwqtczhqView_LEGACY(APIView):
|
||||
"""
|
||||
其他充值/罚款数据统计(非会员充值)
|
||||
POST /houtai/cwqtczhq
|
||||
|
||||
@@ -20,6 +20,12 @@ def _club_id_from_user_uid(uid):
|
||||
return None
|
||||
|
||||
|
||||
def penalty_cz_club_id(fadan):
|
||||
"""罚款支付 czjilu.club_id:与罚单归属一致(申请人俱乐部)。"""
|
||||
cid = (getattr(fadan, 'ClubID', None) or '').strip()
|
||||
return cid or CLUB_ID_DEFAULT
|
||||
|
||||
|
||||
def resolve_penalty_club_id(
|
||||
order=None,
|
||||
penalized_user_id=None,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""财务子模块统计(订单/会员/其他充值),供 /jituan/houtai/*;旧 /houtai/* 不变。"""
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db.models import Count, Q, Sum
|
||||
from django.db.models import Case, Count, DecimalField, Q, Sum, Value, When
|
||||
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
|
||||
|
||||
from jituan.constants import DATA_SCOPE_ALL
|
||||
@@ -13,6 +14,7 @@ from jituan.services.club_context import (
|
||||
)
|
||||
from jituan.services.club_penalty import filter_gsfenhong_qs, filter_penalty_qs
|
||||
from orders.models import Order, Penalty
|
||||
from products.czjilu_types import CZJILU_LEIXING_SHANGJIA, exclude_penalty_from_cz_qs
|
||||
from products.models import Czjilu, Gsfenhong, Huiyuan, ShangpinLeixing
|
||||
from rank.models import Bankuai
|
||||
|
||||
@@ -77,6 +79,57 @@ def _summary_date_filter(granularity, summary_q, field_prefix='CreateTime'):
|
||||
return {f'{field_prefix}__year': summary_q.year}
|
||||
|
||||
|
||||
def _penalty_trunc_func(granularity):
|
||||
if granularity == 'day':
|
||||
return TruncDate('UpdateTime')
|
||||
if granularity == 'month':
|
||||
return TruncMonth('UpdateTime')
|
||||
return TruncYear('UpdateTime')
|
||||
|
||||
|
||||
def _penalty_fenhong_aggregates():
|
||||
"""罚款分红只统计已入账(BonusCredited),与 process_fadan_fenhong 一致。"""
|
||||
zero = Value(Decimal('0.00'), output_field=DecimalField(max_digits=12, decimal_places=2))
|
||||
return {
|
||||
'fenhong_amount': Sum(
|
||||
Case(
|
||||
When(BonusCredited=True, then='ApplicantBonusAmount'),
|
||||
default=zero,
|
||||
output_field=DecimalField(max_digits=12, decimal_places=2),
|
||||
)
|
||||
),
|
||||
'fenhong_count': Count(
|
||||
'id',
|
||||
filter=Q(BonusCredited=True, ApplicantBonusAmount__gt=0),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _czjilu_finance_qs(request, start_dt, end_dt, cz_types):
|
||||
"""充值财务 czjilu:已支付;type4 排除罚款误标。"""
|
||||
qs = filter_queryset_by_club(
|
||||
Czjilu.query.filter(
|
||||
CreateTime__gte=start_dt,
|
||||
CreateTime__lt=end_dt,
|
||||
leixing__in=cz_types,
|
||||
zhuangtai=3,
|
||||
),
|
||||
request,
|
||||
)
|
||||
if CZJILU_LEIXING_SHANGJIA in cz_types:
|
||||
qs = exclude_penalty_from_cz_qs(qs)
|
||||
return qs
|
||||
|
||||
|
||||
def _czjilu_summary_qs(request, lx, granularity, summary_q):
|
||||
filter_kwargs = {'leixing': lx, 'zhuangtai': 3}
|
||||
filter_kwargs.update(_summary_date_filter(granularity, summary_q))
|
||||
qs = filter_queryset_by_club(Czjilu.query.filter(**filter_kwargs), request)
|
||||
if lx == CZJILU_LEIXING_SHANGJIA:
|
||||
qs = exclude_penalty_from_cz_qs(qs)
|
||||
return qs
|
||||
|
||||
|
||||
def build_order_types_payload():
|
||||
types = ShangpinLeixing.query.filter(shenhezhuangtai=1).values('id', 'jieshao')
|
||||
return list(types)
|
||||
@@ -209,15 +262,9 @@ def build_chongzhi_finance_payload(request, granularity, year, month, types=None
|
||||
|
||||
cz_types = [t for t in types if t in [2, 3, 4, 5]]
|
||||
if cz_types:
|
||||
cz_qs = filter_queryset_by_club(
|
||||
Czjilu.query.filter(
|
||||
CreateTime__gte=start_dt,
|
||||
CreateTime__lt=end_dt,
|
||||
leixing__in=cz_types,
|
||||
zhuangtai=3,
|
||||
),
|
||||
request,
|
||||
).annotate(period=trunc_func).values('period', 'leixing').annotate(
|
||||
cz_qs = _czjilu_finance_qs(request, start_dt, end_dt, cz_types).annotate(
|
||||
period=trunc_func,
|
||||
).values('period', 'leixing').annotate(
|
||||
total_count=Count('id'),
|
||||
total_amount=Sum('jine'),
|
||||
).order_by('period', 'leixing')
|
||||
@@ -242,14 +289,18 @@ def build_chongzhi_finance_payload(request, granularity, year, month, types=None
|
||||
}
|
||||
|
||||
if 6 in types:
|
||||
penalty_trunc = _penalty_trunc_func(granularity)
|
||||
penalty_qs = filter_penalty_qs(
|
||||
Penalty.query.filter(CreateTime__gte=start_dt, CreateTime__lt=end_dt, Status=2),
|
||||
Penalty.query.filter(
|
||||
UpdateTime__gte=start_dt,
|
||||
UpdateTime__lt=end_dt,
|
||||
Status=2,
|
||||
),
|
||||
request,
|
||||
).annotate(period=trunc_func).values('period').annotate(
|
||||
).annotate(period=penalty_trunc).values('period').annotate(
|
||||
penalty_count=Count('id'),
|
||||
penalty_amount=Sum('FineAmount'),
|
||||
fenhong_amount=Sum('ApplicantBonusAmount'),
|
||||
fenhong_count=Count('id', filter=Q(ApplicantBonusAmount__gt=0)),
|
||||
**_penalty_fenhong_aggregates(),
|
||||
).order_by('period')
|
||||
|
||||
for item in penalty_qs:
|
||||
@@ -279,9 +330,7 @@ def build_chongzhi_finance_payload(request, granularity, year, month, types=None
|
||||
summary_result = {'by_type': {}, 'total_count': 0, 'total_amount': 0.0, 'total_profit': 0.0}
|
||||
|
||||
for lx in cz_types:
|
||||
filter_kwargs = {'leixing': lx, 'zhuangtai': 3}
|
||||
filter_kwargs.update(_summary_date_filter(granularity, summary_q))
|
||||
agg = filter_queryset_by_club(Czjilu.query.filter(**filter_kwargs), request).aggregate(
|
||||
agg = _czjilu_summary_qs(request, lx, granularity, summary_q).aggregate(
|
||||
count=Count('id'), amount=Sum('jine'),
|
||||
)
|
||||
count = agg['count'] or 0
|
||||
@@ -300,12 +349,11 @@ def build_chongzhi_finance_payload(request, granularity, year, month, types=None
|
||||
|
||||
if 6 in types:
|
||||
penalty_filter = {'Status': 2}
|
||||
penalty_filter.update(_summary_date_filter(granularity, summary_q))
|
||||
penalty_filter.update(_summary_date_filter(granularity, summary_q, field_prefix='UpdateTime'))
|
||||
agg = filter_penalty_qs(Penalty.query.filter(**penalty_filter), request).aggregate(
|
||||
count=Count('id'),
|
||||
amount=Sum('FineAmount'),
|
||||
fenhong_amount=Sum('ApplicantBonusAmount'),
|
||||
fenhong_count=Count('id', filter=Q(ApplicantBonusAmount__gt=0)),
|
||||
**_penalty_fenhong_aggregates(),
|
||||
)
|
||||
penalty_count = agg['count'] or 0
|
||||
penalty_amount = float(agg['amount'] or 0)
|
||||
|
||||
@@ -309,8 +309,8 @@ def sync_szjilu_income_from_logs():
|
||||
|
||||
# 订单已付款且非退款(repair 错误地把 Status=5 退款单也计入了收入)
|
||||
_ORDER_INCOME_STATUSES = [1, 2, 3, 4, 6, 7, 8]
|
||||
# 微信充值类 czjilu:会员/押金/积分/商家/罚单/考核
|
||||
_CZ_WECHAT_LEIXING = [1, 2, 3, 4, 5]
|
||||
# 微信充值类 czjilu:会员/押金/积分/商家/考核/罚款
|
||||
_CZ_WECHAT_LEIXING = [1, 2, 3, 4, 5, 6]
|
||||
|
||||
|
||||
def collect_canonical_wechat_income_for_date(stat_date, existing_refs=None):
|
||||
@@ -701,7 +701,7 @@ def reset_all_daily_szjilu():
|
||||
|
||||
# 合法微信支付收入(不含退款 Status=5)
|
||||
_LEGIT_ORDER_STATUSES = [1, 2, 3, 4, 6, 7, 8]
|
||||
_LEGIT_CZ_LEIXING = [1, 2, 3, 4, 5]
|
||||
_LEGIT_CZ_LEIXING = [1, 2, 3, 4, 5, 6]
|
||||
|
||||
|
||||
def collect_legitimate_wechat_payments(since_year, until_year):
|
||||
|
||||
81
products/czjilu_types.py
Normal file
81
products/czjilu_types.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""czjilu 充值类型常量与查询辅助(避免罚款与商家充值混用 leixing=4)。"""
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
|
||||
# 与 czjilu.leixing 一致
|
||||
CZJILU_LEIXING_HUIYUAN = 1
|
||||
CZJILU_LEIXING_YAJIN = 2
|
||||
CZJILU_LEIXING_JIFEN = 3
|
||||
CZJILU_LEIXING_SHANGJIA = 4
|
||||
CZJILU_LEIXING_KAOHE = 5
|
||||
CZJILU_LEIXING_FAKUAN = 6
|
||||
|
||||
CZJILU_PAID_STATUS = 3
|
||||
|
||||
FAKUAN_ORDER_PREFIX = 'FK'
|
||||
PENALTY_SHUOMING_MARKER = '罚款'
|
||||
PENALTY_FADAN_TAG = '#fadan:'
|
||||
|
||||
|
||||
def penalty_cz_shuoming(fadan_id):
|
||||
"""罚款微信支付/抵扣流水说明(含罚单 ID 便于对账)。"""
|
||||
desc = getattr(settings, 'FAKUAN_PAY_DESCRIPTION', '罚款缴纳')
|
||||
return f'{desc}{PENALTY_FADAN_TAG}{fadan_id}'
|
||||
|
||||
|
||||
def is_legacy_penalty_leixing4(cz):
|
||||
"""历史误写 leixing=4 的罚款流水。"""
|
||||
dingdan = (getattr(cz, 'dingdan_id', None) or '').strip()
|
||||
shuoming = (getattr(cz, 'shuoming', None) or '')
|
||||
if dingdan.startswith(FAKUAN_ORDER_PREFIX):
|
||||
return True
|
||||
if PENALTY_SHUOMING_MARKER in shuoming:
|
||||
return True
|
||||
if PENALTY_FADAN_TAG in shuoming:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_penalty_czjilu(cz):
|
||||
leixing = getattr(cz, 'leixing', None)
|
||||
if leixing == CZJILU_LEIXING_FAKUAN:
|
||||
return True
|
||||
if leixing == CZJILU_LEIXING_SHANGJIA:
|
||||
return is_legacy_penalty_leixing4(cz)
|
||||
return False
|
||||
|
||||
|
||||
def is_merchant_recharge_czjilu(cz):
|
||||
return (
|
||||
getattr(cz, 'leixing', None) == CZJILU_LEIXING_SHANGJIA
|
||||
and not is_legacy_penalty_leixing4(cz)
|
||||
)
|
||||
|
||||
|
||||
def exclude_penalty_from_cz_qs(qs):
|
||||
"""从 czjilu 查询中排除罚款流水(含历史 leixing=4 误标)。"""
|
||||
return qs.exclude(
|
||||
Q(leixing=CZJILU_LEIXING_FAKUAN)
|
||||
| Q(leixing=CZJILU_LEIXING_SHANGJIA, dingdan_id__startswith=FAKUAN_ORDER_PREFIX)
|
||||
| Q(leixing=CZJILU_LEIXING_SHANGJIA, shuoming__icontains=PENALTY_SHUOMING_MARKER)
|
||||
| Q(leixing=CZJILU_LEIXING_SHANGJIA, shuoming__icontains=PENALTY_FADAN_TAG)
|
||||
)
|
||||
|
||||
|
||||
def merchant_recharge_cz_qs(qs):
|
||||
"""仅真·商家余额充值(微信 CzSJ… 或 leixing=4 且非罚款)。"""
|
||||
prefix = getattr(settings, 'SHANGJIA_CZ_PREFIX', 'CzSJ')
|
||||
return exclude_penalty_from_cz_qs(qs).filter(
|
||||
leixing=CZJILU_LEIXING_SHANGJIA,
|
||||
dingdan_id__startswith=prefix,
|
||||
)
|
||||
|
||||
|
||||
def penalty_payment_cz_qs(qs):
|
||||
"""罚款支付流水(微信 FK… 或 leixing=6 / 历史误标)。"""
|
||||
return qs.filter(
|
||||
Q(leixing=CZJILU_LEIXING_FAKUAN)
|
||||
| Q(leixing=CZJILU_LEIXING_SHANGJIA, dingdan_id__startswith=FAKUAN_ORDER_PREFIX)
|
||||
| Q(leixing=CZJILU_LEIXING_SHANGJIA, shuoming__icontains=PENALTY_SHUOMING_MARKER)
|
||||
| Q(leixing=CZJILU_LEIXING_SHANGJIA, shuoming__icontains=PENALTY_FADAN_TAG)
|
||||
)
|
||||
41
products/migrations/0010_backfill_penalty_czjilu_leixing.py
Normal file
41
products/migrations/0010_backfill_penalty_czjilu_leixing.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# 历史罚款 czjilu 从 leixing=4 纠正为 6,club_id 对齐罚单申请人俱乐部
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
from products.czjilu_types import (
|
||||
CZJILU_LEIXING_FAKUAN,
|
||||
CZJILU_LEIXING_SHANGJIA,
|
||||
is_legacy_penalty_leixing4,
|
||||
)
|
||||
|
||||
|
||||
def _backfill_penalty_czjilu(apps, schema_editor):
|
||||
Czjilu = apps.get_model('products', 'Czjilu')
|
||||
Penalty = apps.get_model('orders', 'Penalty')
|
||||
|
||||
for cz in Czjilu.objects.filter(leixing=CZJILU_LEIXING_SHANGJIA).iterator():
|
||||
if not is_legacy_penalty_leixing4(cz):
|
||||
continue
|
||||
updates = {'leixing': CZJILU_LEIXING_FAKUAN}
|
||||
shuoming = (cz.shuoming or '')
|
||||
if '#fadan:' in shuoming:
|
||||
try:
|
||||
fadan_id = int(shuoming.split('#fadan:')[1].split()[0].strip())
|
||||
fadan = Penalty.objects.filter(id=fadan_id).first()
|
||||
if fadan and getattr(fadan, 'ClubID', None):
|
||||
updates['club_id'] = fadan.ClubID
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
Czjilu.objects.filter(pk=cz.pk).update(**updates)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0006_member_trial_fields'),
|
||||
('orders', '0009_penalty_bonus_lock_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(_backfill_penalty_czjilu, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -215,7 +215,10 @@ class Czjilu(QModel):
|
||||
zhuangtai = models.IntegerField(null=True, blank=True, verbose_name='订单状态')
|
||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
|
||||
jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='订单金额')
|
||||
leixing = models.IntegerField(null=True, blank=True, verbose_name='充值类型,1:会员,2:押金,3:积分,4:商家')
|
||||
leixing = models.IntegerField(
|
||||
null=True, blank=True,
|
||||
verbose_name='充值类型,1:会员,2:押金,3:积分,4:商家充值,5:考核,6:罚款缴纳',
|
||||
)
|
||||
shuoming = models.CharField(max_length=100, null=True, blank=True, verbose_name='订单说明')
|
||||
club_id = models.CharField(
|
||||
max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部',
|
||||
|
||||
@@ -2278,6 +2278,14 @@ class ShangjiaHuitiao(View):
|
||||
logger.error(f"商家订单不存在: {out_trade_no}")
|
||||
return self.wechat_response('FAIL', '订单不存在')
|
||||
|
||||
from products.czjilu_types import is_merchant_recharge_czjilu
|
||||
if not is_merchant_recharge_czjilu(order):
|
||||
logger.error(
|
||||
'非商家充值订单误入商家回调: %s, leixing=%s, shuoming=%s',
|
||||
out_trade_no, order.leixing, order.shuoming,
|
||||
)
|
||||
return self.wechat_response('FAIL', '订单类型错误')
|
||||
|
||||
# 5. 检查订单状态是否为未支付(9)
|
||||
if order.zhuangtai != 9:
|
||||
logger.warning(f"商家订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}")
|
||||
@@ -5986,16 +5994,30 @@ class DsqrgmdhView(APIView):
|
||||
action_desc = f"缴纳罚款 {target_price}元"
|
||||
|
||||
shuoming = f"{identity['name']}抵扣 {action_desc},抵扣金额{required}元"
|
||||
cz_leixing = leixing
|
||||
cz_club_id = club_id
|
||||
cz_zhuangtai = 1
|
||||
if leixing == 4:
|
||||
from jituan.services.club_penalty import penalty_cz_club_id
|
||||
from products.czjilu_types import (
|
||||
CZJILU_LEIXING_FAKUAN,
|
||||
CZJILU_PAID_STATUS,
|
||||
PENALTY_FADAN_TAG,
|
||||
)
|
||||
cz_leixing = CZJILU_LEIXING_FAKUAN
|
||||
cz_club_id = penalty_cz_club_id(fadan_obj)
|
||||
cz_zhuangtai = CZJILU_PAID_STATUS
|
||||
shuoming = f"{shuoming}{PENALTY_FADAN_TAG}{fadan_obj.id}"
|
||||
|
||||
member_purchase_days = 0
|
||||
czjilu_kwargs = {
|
||||
'dingdan_id': dingdan_id,
|
||||
'zhuangtai': 1,
|
||||
'zhuangtai': cz_zhuangtai,
|
||||
'yonghuid': yonghuid,
|
||||
'jine': required,
|
||||
'leixing': leixing,
|
||||
'leixing': cz_leixing,
|
||||
'shuoming': shuoming,
|
||||
'club_id': club_id,
|
||||
'club_id': cz_club_id,
|
||||
}
|
||||
if leixing == 1:
|
||||
from jituan.services.member_recharge import club_huiyuan_sellable
|
||||
|
||||
@@ -12011,8 +12011,10 @@ class FaKuanPayView(APIView):
|
||||
random_num = random.randint(0, 99)
|
||||
dingdanid = f"FK{timestamp_ms:011d}{timestamp_ns:06d}{random_num:02d}"
|
||||
|
||||
from jituan.services.club_write import resolve_club_id_for_write
|
||||
from jituan.services.club_penalty import penalty_cz_club_id
|
||||
from jituan.services.club_user import get_payment_openid
|
||||
from products.czjilu_types import CZJILU_LEIXING_FAKUAN, penalty_cz_shuoming
|
||||
|
||||
pay_openid, _ = get_payment_openid(request)
|
||||
if not pay_openid:
|
||||
return Response({
|
||||
@@ -12025,9 +12027,9 @@ class FaKuanPayView(APIView):
|
||||
zhuangtai=9,
|
||||
yonghuid=request.user.UserUID,
|
||||
jine=jine,
|
||||
leixing=4,
|
||||
shuoming=f'{settings.FAKUAN_PAY_DESCRIPTION}#fadan:{fadan.id}',
|
||||
club_id=resolve_club_id_for_write(request, request.user),
|
||||
leixing=CZJILU_LEIXING_FAKUAN,
|
||||
shuoming=penalty_cz_shuoming(fadan.id),
|
||||
club_id=penalty_cz_club_id(fadan),
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -12233,6 +12235,11 @@ class FaKuanHuitiaoView(View):
|
||||
logger.error(f"订单不存在: {out_trade_no}")
|
||||
return self.wechat_response('FAIL', '订单不存在')
|
||||
|
||||
from products.czjilu_types import is_penalty_czjilu
|
||||
if not is_penalty_czjilu(order):
|
||||
logger.error(f"非罚款支付订单误入罚款回调: {out_trade_no}, leixing={order.leixing}")
|
||||
return self.wechat_response('FAIL', '订单类型错误')
|
||||
|
||||
# 5. 检查订单状态是否为未支付(9)
|
||||
if order.zhuangtai != 9:
|
||||
logger.warning(f"订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}")
|
||||
|
||||
Reference in New Issue
Block a user