feat(jituan): 集团多俱乐部改造 — club 隔离、财务/提现/分红/展示配置
This commit is contained in:
395
users/views.py
395
users/views.py
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user