feat(jituan): 集团多俱乐部改造 — club 隔离、财务/提现/分红/展示配置

This commit is contained in:
XingQue
2026-06-24 05:02:18 +08:00
parent ce9b09f096
commit dcc4936428
82 changed files with 5748 additions and 924 deletions

View File

@@ -43,8 +43,6 @@ from gvsdsdk.fluent import db, func, FQ
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
from utils.fadan_utils import check_fadan_qiangdan_eligible
from utils.wechat_v3 import verify_wechat_sign, decrypt_callback_ciphertext
from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode
from users.fadan_fenhong_utils import process_fadan_fenhong
@@ -83,7 +81,7 @@ from orders.models import (
from products.models import (
ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu
)
from config.models import Qunpeizhi, Szjilu
from config.models import Qunpeizhi
from backend.models import MerchantDailyStats
from rank.models import (
KaohePayTemp, Chenghao, ShenheJilu,
@@ -1327,6 +1325,15 @@ class DashouZhuceView(APIView):
'data': None
}, status=status.HTTP_404_NOT_FOUND)
from jituan.services.invite_guard import assert_invite_same_club
ok, msg = assert_invite_same_club(request, guanshi_profile.user, current_user)
if not ok:
return Response({
'code': 403,
'message': msg,
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 4. 验证管事状态
# 5. 核心:在数据库事务中创建用户的所有身份
@@ -1590,25 +1597,20 @@ class TixianXinxiHuoquView(APIView):
# 从request.user获取当前用户
user_main = request.user
# 🔥【修改开始 - 使用Decimal字段名是Rate】
# 查询打手提现费率 (Platform='5')
dashou_rate_obj = CommissionRate.query.filter(Platform='5').first()
dashou_rate = decimal.Decimal(str(dashou_rate_obj.Rate)) if dashou_rate_obj else decimal.Decimal('0.00')
from jituan.services.club_user import get_user_club_id
from jituan.services.withdraw_config import get_tixian_feilv
# 查询管事提现费率 (Platform='6')
guanshi_rate_obj = CommissionRate.query.filter(Platform='6').first()
guanshi_rate = decimal.Decimal(str(guanshi_rate_obj.Rate)) if guanshi_rate_obj else decimal.Decimal('0.00')
# 🔥【修改结束】
club_id = get_user_club_id(user_main)
dashou_rate = get_tixian_feilv(club_id, 1)
guanshi_rate = get_tixian_feilv(club_id, 2)
# 构建返回数据
response_data = {
'txdianhua': user_main.Phone if user_main.Phone else '',
'txtupian': user_main.PaymentQRCode if user_main.PaymentQRCode else '',
'txzh': user_main.PaymentAccount if user_main.PaymentAccount else '',
# 🔥【修改开始】
'dashou_rate': dashou_rate, # 打手提现费率
'guanshi_rate': guanshi_rate, # 管事提现费率
# 🔥【修改结束】
'dashou_rate': dashou_rate,
'guanshi_rate': guanshi_rate,
}
# 返回成功响应
@@ -1850,7 +1852,10 @@ class TixianShenqingView(APIView):
return Response({'code': 5, 'msg': '无效的收款方式只能是1(微信)或2(支付宝)'},
status=status.HTTP_400_BAD_REQUEST)
# 2. 获取对应费率及手续费
from jituan.services.club_user import get_user_club_id
from jituan.services.withdraw_config import get_tixian_feilv
club_id = get_user_club_id(user_main)
current_rate = decimal.Decimal('0.00')
rate_key = {
1: '5', # 打手佣金
@@ -1862,10 +1867,9 @@ class TixianShenqingView(APIView):
}.get(leixing)
if rate_key:
rate_obj = CommissionRate.query.filter(Platform=rate_key).first()
if rate_obj:
current_rate = decimal.Decimal(str(rate_obj.Rate))
else:
try:
current_rate = get_tixian_feilv(club_id, leixing)
except ValueError:
return Response({'code': 41, 'msg': f'提现费率未配置(类型{leixing}'},
status=status.HTTP_400_BAD_REQUEST)
@@ -1956,7 +1960,9 @@ class TixianShenqingView(APIView):
phone = user_main.Phone or ''
zhifu = user_main.PaymentQRCode or ''
skzhanghao = user_main.PaymentAccount or ''
from jituan.services.club_user import get_user_club_id
tixian_record = Tixianjilu.query.create(
club_id=get_user_club_id(user_main),
yonghuid=yonghuid,
avatar=avatar,
phone=phone,
@@ -2482,43 +2488,10 @@ class AdminFinancialDataView(APIView):
status=status.HTTP_401_UNAUTHORIZED
)
# 6. 查询收支记录表(Szjilu
# 使用高效的ORM查询获取最新的一条记录
# 注意:根据你的业务需求,这里获取最新的一条记录
# 如果需要汇总多条记录可以使用aggregate进行聚合查询
# 方法1获取最新的一条记录假设表中只有一条汇总记录
shouzhijilu = Szjilu.query.order_by('-CreateTime').first()
# 方法2如果表中可能有多条记录需要汇总可以使用以下方式
# from django.db.models Sum
# zonghe = Szjilu.query.aggregate(
# zongliushui=Sum('TotalFlow'),
# zongshouyi=Sum('TotalIncome'),
# zongzhichu=Sum('TotalExpense'),
# DailyFlow=Sum('DailyFlow'),
# DailyExpense=Sum('DailyExpense')
# )
# 准备返回数据
if shouzhijilu:
# 有记录的情况
huizongshuju = {
'zongliushui': float(shouzhijilu.TotalFlow or 0),
'zongshouyi': float(shouzhijilu.TotalIncome or 0),
'zongzhichu': float(shouzhijilu.TotalExpense or 0),
'jrls': float(shouzhijilu.DailyFlow or 0),
'jrzc': float(shouzhijilu.DailyExpense or 0)
}
else:
# 没有记录的情况,返回默认值
huizongshuju = {
'zongliushui': 0.00,
'zongshouyi': 0.00,
'zongzhichu': 0.00,
'jrls': 0.00,
'jrzc': 0.00
}
# 6. 查询收支记录表(按俱乐部 xq旧管理员端默认
from jituan.constants import CLUB_ID_DEFAULT
from jituan.services.szjilu_accounting import get_szjilu_snapshot
huizongshuju = get_szjilu_snapshot(CLUB_ID_DEFAULT)
# 7. 返回成功响应
return Response({
@@ -4347,11 +4320,13 @@ class AdcjxgView(APIView):
# 🔥 修改:新购买时只能增加天数
if days > 0:
daoqi_time = timezone.now() + timedelta(days=days)
from jituan.services.club_user import get_user_club_id
new_record = Huiyuangoumai.query.create(
huiyuan_id=huiyuan_id,
yonghu_id=uid,
jieshao=huiyuan.jieshao,
daoqi_time=daoqi_time
daoqi_time=daoqi_time,
club_id=get_user_club_id(User.query.filter(UserUID=uid).first()),
)
# 记录会员购买
@@ -4638,29 +4613,14 @@ class AdtixianqkView(APIView):
# 🟢 新增如果同意提现result=2更新收支记录表
if result == 2:
try:
# 查询收支记录表id=1的记录
szjilu_record = Szjilu.query.get(id=1)
# 获取提现金额
tixian_jine = withdrawal.jine # 假设提现记录模型中有jine字段
update_daily_payout(tixian_jine)
# 更新收支记录
szjilu_record.TotalIncome -= tixian_jine # 总收益减去提现金额
szjilu_record.TotalExpense += tixian_jine # 总支出加上提现金额
szjilu_record.DailyExpense += tixian_jine # 今日支出加上提现金额
szjilu_record.UpdateTime = timezone.now()
szjilu_record.save()
except Szjilu.DoesNotExist:
# 如果收支记录表不存在id=1的记录创建一个
szjilu_record = Szjilu.query.create(
id=1,
TotalIncome=-tixian_jine if result == 2 else 0,
TotalExpense=tixian_jine if result == 2 else 0,
DailyExpense=tixian_jine if result == 2 else 0
from jituan.services.szjilu_accounting import apply_szjilu_expense
tixian_jine = withdrawal.jine
from jituan.services.club_user import get_user_club_id
payout_club = get_user_club_id(
User.query.filter(UserUID=withdrawal.yonghuid).first()
)
update_daily_payout(tixian_jine, payout_club)
apply_szjilu_expense(tixian_jine, payout_club)
except Exception as e:
# 如果更新收支记录失败,抛出异常,事务会回滚
raise Exception(f"更新收支记录失败: {str(e)}")
@@ -5372,6 +5332,22 @@ class WechatLoginAndDashouRegisterView(APIView):
# 这里我们继续执行,但只返回登录信息
return self.fanhuiZhihouDengluXinxi(user_main, yaoqingma_wuxiao=True)
from jituan.services.invite_guard import assert_invite_same_club
from jituan.services.club_user import ensure_user_club_id
from jituan.services.club_context import resolve_club_id_from_request
ok, invite_msg = assert_invite_same_club(
request, guanshi_profile.user, user_main,
)
if not ok:
return Response({
'code': 403,
'msg': invite_msg,
'data': None
}, status=status.HTTP_403_FORBIDDEN)
ensure_user_club_id(user_main, resolve_club_id_from_request(request))
# 验证管事状态
# 4.3 检查用户是否已是打手
@@ -7100,8 +7076,12 @@ class KefuGetOrderListView(APIView):
if clkf:
q_conditions &= Q(AssignedCS__icontains=clkf)
from jituan.services.club_context import orders_for_request
# ---------- 统计(仅按发单平台,非跨平台) ----------
stats = Order.query.filter(Platform=1).aggregate(
club_orders = orders_for_request(request)
platform_orders = club_orders.filter(Platform=1)
stats = platform_orders.aggregate(
total_orders=Count('id'),
completed_orders=Count('id', filter=Q(Status=3)),
refund_orders=Count('id', filter=Q(Status=5)),
@@ -7110,20 +7090,20 @@ class KefuGetOrderListView(APIView):
# ---------- 状态计数(所有状态都返回,非跨平台) ----------
status_counts = {
'all': Order.query.filter(Platform=1).count(),
'1,7': Order.query.filter(Platform=1, Status__in=[1,7]).count(),
'2': Order.query.filter(Platform=1, Status=2).count(),
'8': Order.query.filter(Platform=1, Status=8).count(),
'3': Order.query.filter(Platform=1, Status=3).count(),
'4': Order.query.filter(Platform=1, Status=4).count(),
'5': Order.query.filter(Platform=1, Status=5).count(),
'6': Order.query.filter(Platform=1, Status=6).count(),
'all': platform_orders.count(),
'1,7': platform_orders.filter(Status__in=[1,7]).count(),
'2': platform_orders.filter(Status=2).count(),
'8': platform_orders.filter(Status=8).count(),
'3': platform_orders.filter(Status=3).count(),
'4': platform_orders.filter(Status=4).count(),
'5': platform_orders.filter(Status=5).count(),
'6': platform_orders.filter(Status=6).count(),
}
# ---------- 分页查询 ----------
total_count = Order.query.filter(q_conditions).count()
total_count = club_orders.filter(q_conditions).count()
orders_qs = Order.query.filter(q_conditions).select_related(
orders_qs = club_orders.filter(q_conditions).select_related(
'pingtai_kuozhan'
).only(
'OrderID', 'Description', 'Status', 'ProductTypeID', 'Platform',
@@ -7158,6 +7138,8 @@ class KefuGetOrderListView(APIView):
'youxi_nicheng': youxi_nicheng
})
from jituan.services.club_user_access import list_response_meta
response_data = {
'stats': {
'totalOrders': stats['total_orders'] or 0,
@@ -7169,7 +7151,8 @@ class KefuGetOrderListView(APIView):
'list': result_list,
'total': total_count,
'page': page,
'pageSize': page_size
'pageSize': page_size,
**list_response_meta(request),
}
logger.info(f"平台订单接口总耗时: {time.time()-start_total:.3f}s")
@@ -7269,8 +7252,12 @@ class KefuGetShangjiaOrderListView(APIView):
ShopProfile__nicheng__icontains=shangjia_nicheng
).values_list('UserUID', flat=True))
from jituan.services.club_context import orders_for_request
# ---------- 统计(仅按发单平台,非跨平台) ----------
stats = Order.query.filter(Platform=2).aggregate(
club_orders = orders_for_request(request)
merchant_orders = club_orders.filter(Platform=2)
stats = merchant_orders.aggregate(
total_orders=Count('id'),
completed_orders=Count('id', filter=Q(Status=3)),
refund_orders=Count('id', filter=Q(Status=5)),
@@ -7279,20 +7266,20 @@ class KefuGetShangjiaOrderListView(APIView):
# ---------- 状态计数(所有状态都返回,非跨平台) ----------
status_counts = {
'all': Order.query.filter(Platform=2).count(),
'1,7': Order.query.filter(Platform=2, Status__in=[1,7]).count(),
'2': Order.query.filter(Platform=2, Status=2).count(),
'8': Order.query.filter(Platform=2, Status=8).count(),
'3': Order.query.filter(Platform=2, Status=3).count(),
'4': Order.query.filter(Platform=2, Status=4).count(),
'5': Order.query.filter(Platform=2, Status=5).count(),
'6': Order.query.filter(Platform=2, Status=6).count(),
'all': merchant_orders.count(),
'1,7': merchant_orders.filter(Status__in=[1,7]).count(),
'2': merchant_orders.filter(Status=2).count(),
'8': merchant_orders.filter(Status=8).count(),
'3': merchant_orders.filter(Status=3).count(),
'4': merchant_orders.filter(Status=4).count(),
'5': merchant_orders.filter(Status=5).count(),
'6': merchant_orders.filter(Status=6).count(),
}
# ---------- 分页查询 ----------
total_count = Order.query.filter(q_conditions).count()
total_count = club_orders.filter(q_conditions).count()
orders_qs = Order.query.filter(q_conditions).select_related(
orders_qs = club_orders.filter(q_conditions).select_related(
'shangjia_kuozhan'
).only(
'OrderID', 'Description', 'Status', 'ProductTypeID', 'Platform',
@@ -7327,6 +7314,8 @@ class KefuGetShangjiaOrderListView(APIView):
'youxi_nicheng': youxi_nicheng
})
from jituan.services.club_user_access import list_response_meta
response_data = {
'stats': {
'totalOrders': stats['total_orders'] or 0,
@@ -7338,7 +7327,8 @@ class KefuGetShangjiaOrderListView(APIView):
'list': result_list,
'total': total_count,
'page': page,
'pageSize': page_size
'pageSize': page_size,
**list_response_meta(request),
}
logger.info(f"商家订单接口总耗时: {time.time()-start_total:.3f}s")
@@ -7708,7 +7698,7 @@ class KefuPlatformRefundView(APIView):
# 10. 更新退款记录表(假设存在 RefundRecord 模型,字段需根据实际调整)
#更新支出记录
update_daily_payout(order.Amount)
update_daily_payout(order.Amount, getattr(order, 'ClubID', None))
try:
@@ -9033,14 +9023,18 @@ class KefuPunishmentListView(APIView):
return Response({'code': 403, 'msg': '您无权查看平台订单列表'})
# 3. 获取统计信息(不分页)
from jituan.services.club_penalty import filter_penalty_record_qs
from jituan.services.club_user_access import list_response_meta
base_qs = filter_penalty_record_qs(PenaltyRecord.query.all(), request)
stats = {
'zongshu': PenaltyRecord.query.count(),
'daichuli': PenaltyRecord.query.filter(ApplyStatus__in=[0, 3]).count(),
'yichuli': PenaltyRecord.query.filter(ApplyStatus__in=[1, 2]).count(),
'zongshu': base_qs.count(),
'daichuli': base_qs.filter(ApplyStatus__in=[0, 3]).count(),
'yichuli': base_qs.filter(ApplyStatus__in=[1, 2]).count(),
}
# 4. 构建查询条件
queryset = PenaltyRecord.query.all()
queryset = base_qs
if status_list and isinstance(status_list, list):
queryset = queryset.filter(ApplyStatus__in=status_list)
@@ -9075,7 +9069,8 @@ class KefuPunishmentListView(APIView):
'data': {
'list': list_data,
'total': total,
'stats': stats
'stats': stats,
**list_response_meta(request),
}
})
@@ -9696,12 +9691,14 @@ class KefuWithdrawListView(APIView):
)
return False
def _list_queryset(self, q, shenhe_field_ok):
def _list_queryset(self, q, shenhe_field_ok, request):
"""字段未迁移时 only 旧字段,避免 SELECT 不存在的列导致 500"""
fields = _TIXIAN_LIST_BASE_FIELDS + (
_TIXIAN_LIST_AUDIT_FIELDS if shenhe_field_ok else ()
)
return Tixianjilu.query.filter(q).only(*fields)
from jituan.services.tixian_club import filter_tixianjilu_by_request
qs = Tixianjilu.query.filter(q).only(*fields)
return filter_tixianjilu_by_request(qs, request)
def _resolve_row_dakuan_mode(self, item, audit_mode_map, shenhe_field_ok):
"""无关联审核或审核表未标记自动 → 默认手动(1)"""
@@ -9747,7 +9744,7 @@ class KefuWithdrawListView(APIView):
if kefu is None:
return permissions
if '005bb' not in permissions:
return Response({'code': 403, 'msg': '您无权查看平台订单列表'})
return Response({'code': 403, 'msg': '您无权查看提现审核列表'})
q = Q()
@@ -9824,11 +9821,11 @@ class KefuWithdrawListView(APIView):
processed_statuses = [2, 3, 4, 5, 6] if effective_dakuan == 2 else [2, 3]
base_qs = self._list_queryset(q, shenhe_field_ok)
base_qs = self._list_queryset(q, shenhe_field_ok, request)
total_count = base_qs.count()
awaiting_count = self._list_queryset(q & Q(zhuangtai=1), shenhe_field_ok).count()
awaiting_count = self._list_queryset(q & Q(zhuangtai=1), shenhe_field_ok, request).count()
processed_count = self._list_queryset(
q & Q(zhuangtai__in=processed_statuses), shenhe_field_ok,
q & Q(zhuangtai__in=processed_statuses), shenhe_field_ok, request,
).count()
agg = base_qs.aggregate(total=Sum('jine'))
total_amount = agg.get('total') or 0
@@ -9888,12 +9885,15 @@ class KefuWithdrawListView(APIView):
'UpdateTime': item.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if item.UpdateTime else '',
})
from jituan.services.club_user_access import list_response_meta
return Response({
'code': 0,
'data': {
'list': list_data,
'total': total,
'stats': stats,
**list_response_meta(request),
},
})
@@ -9971,6 +9971,11 @@ class KefuWithdrawDetailView(APIView):
except Tixianjilu.DoesNotExist:
return Response({'code': 404, 'msg': '提现记录不存在'}, status=status.HTTP_404_NOT_FOUND)
from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
deny = forbid_if_tixian_out_of_scope(request, record)
if deny:
return deny
# 查询用户主表
try:
user = User.query.get(UserUID=record.yonghuid)
@@ -10113,7 +10118,7 @@ class KefuWithdrawActionView(APIView):
return None, Response({'code': 400, 'msg': '参数格式错误'}, status=status.HTTP_400_BAD_REQUEST)
return [item], None
def _process_auto_item(self, item, action, reason, kefu_user):
def _process_auto_item(self, request, item, action, reason, kefu_user):
"""自动打款单条处理(须在 transaction.atomic 内调用)"""
tixian_id = item['tixian_id']
req_yonghuid = item['yonghuid']
@@ -10127,6 +10132,11 @@ class KefuWithdrawActionView(APIView):
except Tixianjilu.DoesNotExist:
raise ValueError(f'提现记录{tixian_id}不存在')
from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
deny = forbid_if_tixian_out_of_scope(request, tixian)
if deny:
raise ValueError(deny.data.get('msg', '无权限处理该提现记录'))
if tixian.yonghuid != req_yonghuid:
raise ValueError(f'提现记录{tixian_id}用户ID不匹配')
if tixian.leixing != req_leixing:
@@ -10181,7 +10191,7 @@ class KefuWithdrawActionView(APIView):
tixian.bhliyou = reason
tixian.save(update_fields=['shenheid', 'bhliyou', 'UpdateTime'])
def _process_manual_item(self, item, action, reason, phone):
def _process_manual_item(self, request, item, action, reason, phone):
"""手动打款单条处理(原逻辑,须在 transaction.atomic 内调用)"""
tixian_id = item['tixian_id']
try:
@@ -10189,6 +10199,11 @@ class KefuWithdrawActionView(APIView):
except Tixianjilu.DoesNotExist:
raise ValueError(f'提现记录{tixian_id}不存在')
from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
deny = forbid_if_tixian_out_of_scope(request, tixian)
if deny:
raise ValueError(deny.data.get('msg', '无权限处理该提现记录'))
req_yonghuid = item.get('yonghuid')
req_leixing = item.get('leixing')
if req_yonghuid and tixian.yonghuid != req_yonghuid:
@@ -10206,16 +10221,14 @@ class KefuWithdrawActionView(APIView):
except User.DoesNotExist:
raise ValueError(f'用户{tixian.yonghuid}不存在')
from jituan.services.club_user import get_user_club_id
if action == 1:
tixian.zhuangtai = 2
try:
update_daily_payout(tixian.jine)
szjilu, _ = Szjilu.query.get_or_create(id=1)
szjilu.TotalIncome -= tixian.jine
szjilu.TotalExpense += tixian.jine
szjilu.DailyExpense += tixian.jine
szjilu.UpdateTime = timezone.now()
szjilu.save()
from jituan.services.szjilu_accounting import apply_szjilu_expense
update_daily_payout(tixian.jine, get_user_club_id(user))
apply_szjilu_expense(tixian.jine, get_user_club_id(user))
except Exception as e:
logger.error(f'更新收支记录失败: {e}')
else:
@@ -10265,15 +10278,20 @@ class KefuWithdrawActionView(APIView):
except Tixianjilu.DoesNotExist:
raise ValueError(f'提现记录{tixian_id}不存在')
from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
deny = forbid_if_tixian_out_of_scope(request, tixian_probe)
if deny:
raise ValueError(deny.data.get('msg', '无权限处理该提现记录'))
# 有关联审核记录(shenhe_jilu_id)即为 zddksh 自动打款,不依赖 dakuan_mode 字段
is_auto = bool(getattr(tixian_probe, 'shenhe_jilu_id', None))
if is_auto:
self._process_auto_item(item, action, reason, kefu_user)
self._process_auto_item(request, item, action, reason, kefu_user)
else:
if is_batch:
raise ValueError(f'提现记录{tixian_id}:批量操作仅支持自动打款记录')
self._process_manual_item(item, action, reason, phone)
self._process_manual_item(request, item, action, reason, phone)
except ValueError as e:
return Response({'code': 400, 'msg': str(e)}, status=status.HTTP_400_BAD_REQUEST)
@@ -10943,6 +10961,12 @@ class GuanshiRegisterView(APIView):
return Response({'code': 403, 'msg': '该邀请码对应的组长账号已被禁用', 'data': None},
status=status.HTTP_403_FORBIDDEN)
from jituan.services.invite_guard import assert_invite_same_club
ok, invite_msg = assert_invite_same_club(request, zuzhang.user, current_user)
if not ok:
return Response({'code': 403, 'msg': invite_msg, 'data': None},
status=status.HTTP_403_FORBIDDEN)
zuzhang_yonghuid = zuzhang.user.UserUID
# 4. 事务内创建管事实体和打手(如果需要)
@@ -11197,6 +11221,10 @@ class ZuzhangZhuceView(APIView):
if invite_code != self.ACTIVATION_CODE:
return Response({'code': 400, 'msg': '邀请码无效'}, status=400)
from jituan.services.club_user import ensure_user_club_id
from jituan.services.club_context import resolve_club_id_from_request
ensure_user_club_id(user, resolve_club_id_from_request(request))
# 生成唯一组长邀请码:时间戳 + 用户ID后6位 + 随机字符
yaoqingma = self._generate_unique_yaoqingma(user.UserUID)
@@ -11707,13 +11735,15 @@ 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
chongzhi_jilu = Czjilu.query.create(
dingdan_id=dingdanid,
zhuangtai=9,
yonghuid=request.user.UserUID,
jine=jine,
leixing=4,
shuoming=settings.FAKUAN_PAY_DESCRIPTION
shuoming=settings.FAKUAN_PAY_DESCRIPTION,
club_id=resolve_club_id_for_write(request, request.user),
)
try:
@@ -11746,9 +11776,12 @@ class FaKuanPayView(APIView):
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def generate_wechat_pay_params(self, dingdanid, jine, openid, pay_type):
APPID = settings.WEIXIN_APPID
MCHID = settings.WEIXIN_MCHID
KEY = settings.WEIXIN_SHANGHUMIYAO
from jituan.services.club_write import resolve_club_id_for_write
from jituan.services.wechat_pay import get_wechat_v2_config
pay_cfg = get_wechat_v2_config(resolve_club_id_for_write(self.request, request.user))
APPID = pay_cfg['appid']
MCHID = pay_cfg['mch_id']
KEY = pay_cfg['key']
if pay_type == 'fakuan':
PAY_DESCRIPTION = settings.FAKUAN_PAY_DESCRIPTION
@@ -11884,7 +11917,9 @@ class FaKuanHuitiaoView(View):
return self.wechat_response('FAIL', '支付失败')
# 3. 验证签名(确保是微信发来的)
if not self.verify_signature(request_body):
from jituan.services.wechat_pay import club_id_from_czjilu, verify_wechat_v2_signature
notify_club = club_id_from_czjilu(out_trade_no)
if not verify_wechat_v2_signature(request_body, notify_club):
logger.error(f"签名验证失败: {out_trade_no}")
return self.wechat_response('FAIL', '签名验证失败')
@@ -11944,26 +11979,9 @@ class FaKuanHuitiaoView(View):
# 获取订单金额确保是Decimal类型
jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine
update_daily_income(jine_decimal)
# 查询或创建ID=1的收支记录
szjilu, created = Szjilu.query.get_or_create(
id=1,
defaults={
'TotalIncome': Decimal('0.00'),
'TotalFlow': Decimal('0.00'),
'TotalExpense': Decimal('0.00'),
'DailyExpense': Decimal('0.00'),
'DailyFlow': Decimal('0.00')
}
)
# 更新三个字段
szjilu.TotalIncome += jine_decimal # 总收益
szjilu.TotalFlow += jine_decimal # 总流水
szjilu.DailyFlow += jine_decimal # 今日流水
szjilu.save()
update_daily_income(jine_decimal, getattr(order, 'club_id', None))
from jituan.services.szjilu_accounting import apply_szjilu_income
apply_szjilu_income(jine_decimal, getattr(order, 'club_id', None))
logger.info(f"收支记录更新成功: 订单{order.dingdan_id}, 金额{jine_decimal}")
@@ -12181,13 +12199,15 @@ class KaohePayView(APIView):
logger.info(f"生成订单ID: {dingdanid}")
# 8. 创建支付订单Czjilu
from jituan.services.club_write import resolve_club_id_for_write
order = Czjilu.query.create(
dingdan_id=dingdanid,
zhuangtai=9, # 未支付
yonghuid=user.UserUID,
jine=float(fee),
leixing=5, # 5-考核支付
shuoming=settings.KAOHE_PAY_DESCRIPTION
shuoming=settings.KAOHE_PAY_DESCRIPTION,
club_id=resolve_club_id_for_write(request, user),
)
logger.info(f"创建支付订单成功: {dingdanid}")
@@ -12249,9 +12269,12 @@ class KaohePayView(APIView):
def generate_wechat_pay_params(self, dingdanid, jine, openid, attach, pay_type):
"""生成微信JSAPI支付参数"""
APPID = settings.WEIXIN_APPID
MCHID = settings.WEIXIN_MCHID
KEY = settings.WEIXIN_SHANGHUMIYAO
from jituan.services.club_write import resolve_club_id_for_write
from jituan.services.wechat_pay import get_wechat_v2_config
pay_cfg = get_wechat_v2_config(resolve_club_id_for_write(self.request, request.user))
APPID = pay_cfg['appid']
MCHID = pay_cfg['mch_id']
KEY = pay_cfg['key']
if pay_type == 'kaohe':
PAY_DESCRIPTION = settings.KAOHE_PAY_DESCRIPTION
@@ -12441,7 +12464,9 @@ class KaohePayCallbackView(View):
return self.wechat_response('FAIL', '支付失败')
# 验证签名
if not self.verify_signature(request_body):
from jituan.services.wechat_pay import club_id_from_czjilu, verify_wechat_v2_signature
notify_club = club_id_from_czjilu(out_trade_no)
if not verify_wechat_v2_signature(request_body, notify_club):
logger.error(f"签名验证失败,订单号: {out_trade_no}")
return self.wechat_response('FAIL', '签名验证失败')
logger.info(f"签名验证成功,订单号: {out_trade_no}")
@@ -12513,24 +12538,11 @@ class KaohePayCallbackView(View):
# 更新收支记录
try:
jine_decimal = Decimal(str(order.jine))
update_daily_income(jine_decimal)
update_daily_income(jine_decimal, getattr(order, 'club_id', None))
logger.info(f"更新每日收入成功: {jine_decimal}")
szjilu, created = Szjilu.query.get_or_create(
id=1,
defaults={
'TotalIncome': Decimal('0.00'),
'TotalFlow': Decimal('0.00'),
'TotalExpense': Decimal('0.00'),
'DailyExpense': Decimal('0.00'),
'DailyFlow': Decimal('0.00'),
}
)
szjilu.TotalIncome += jine_decimal
szjilu.TotalFlow += jine_decimal
szjilu.DailyFlow += jine_decimal
szjilu.save()
logger.info(f"更新收支记录成功: zongsy={szjilu.TotalIncome}")
from jituan.services.szjilu_accounting import apply_szjilu_income
apply_szjilu_income(jine_decimal, getattr(order, 'club_id', None))
logger.info(f"更新收支记录成功")
except Exception as e:
logger.error(f"更新收支记录失败: {str(e)}", exc_info=True)
@@ -12709,30 +12721,11 @@ class TixianAssetView(APIView):
except ObjectDoesNotExist:
pass
# ------------------- 费率查询(从 CommissionRate 获取) -------------------
# 打手提现费率 (类型 '5')
lilu_obj = CommissionRate.query.filter(Platform='5').first()
data['dashou_rate'] = float(lilu_obj.Rate) if lilu_obj and lilu_obj.Rate is not None else 0.00
from jituan.services.club_user import get_user_club_id
from jituan.services.withdraw_config import build_tixian_asset_rates
# 打手保证金提现费率 (类型 '11')
lilu_obj = CommissionRate.query.filter(Platform='11').first()
data['dashou_yajin_rate'] = float(lilu_obj.Rate) if lilu_obj and lilu_obj.Rate is not None else 0.00
# 商家提现费率 (类型 '10')
lilu_obj = CommissionRate.query.filter(Platform='10').first()
data['shangjia_rate'] = float(lilu_obj.Rate) if lilu_obj and lilu_obj.Rate is not None else 0.00
# 管事提现费率 (类型 '6')
lilu_obj = CommissionRate.query.filter(Platform='6').first()
data['guanshi_rate'] = float(lilu_obj.Rate) if lilu_obj and lilu_obj.Rate is not None else 0.00
# 组长提现费率 (类型 '8')
lilu_obj = CommissionRate.query.filter(Platform='8').first()
data['zuzhang_rate'] = float(lilu_obj.Rate) if lilu_obj and lilu_obj.Rate is not None else 0.00
# 审核官(考核官)提现费率 (类型 '9')
lilu_obj = CommissionRate.query.filter(Platform='9').first()
data['kaoheguan_rate'] = float(lilu_obj.Rate) if lilu_obj and lilu_obj.Rate is not None else 0.00
club_id = get_user_club_id(user)
data.update(build_tixian_asset_rates(club_id))
return Response({
'code': 200,
@@ -12861,8 +12854,10 @@ class TixianCallbackV3View(APIView):
headers = request.headers
body = request.body.decode('utf-8')
# 1. 验证签名
if not verify_wechat_sign(headers, body):
# 1. 验证签名(多商户依次验签)
from utils.wechat_v3 import resolve_callback_club_id, decrypt_callback_ciphertext
callback_club_id = resolve_callback_club_id(headers, body)
if not callback_club_id:
return Response({'code': 'FAIL', 'message': '签名验证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 2. 解密数据
@@ -12878,7 +12873,7 @@ class TixianCallbackV3View(APIView):
if not nonce or not ciphertext:
return Response({'code': 'FAIL', 'message': '解密字段缺失'}, status=status.HTTP_400_BAD_REQUEST)
plaintext = decrypt_callback_ciphertext(associated_data, nonce, ciphertext)
plaintext = decrypt_callback_ciphertext(associated_data, nonce, ciphertext, club_id=callback_club_id)
callback_data = json.loads(plaintext)
except Exception as e:
logger.error(f"回调解密失败: {str(e)}", exc_info=True)