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,6 +43,10 @@ class User(_UserBase):
IP = models.CharField(max_length=50, db_column='IP', verbose_name='来访用户IP', null=True, blank=True)
IsStaff = models.BooleanField(default=False, db_column='IsStaff', verbose_name='是否员工')
IsSuperuser = models.BooleanField(default=False, db_column='IsSuperuser', verbose_name='是否超级用户')
ClubID = models.CharField(
max_length=16, default='xq', db_column='ClubID', verbose_name='所属俱乐部',
null=True, blank=True,
)
objects = UserManager()

View File

@@ -64,7 +64,7 @@ def process_fadan_fenhong(fadan_id):
return True, "罚款金额为0无分红"
# 5. 确定分红利率
rate = _get_fenhong_rate(shenqing_chufa, fenhong_shenfen, lilv_shenfen)
rate = _get_fenhong_rate(shenqing_chufa, fenhong_shenfen, lilv_shenfen, club_id=fadan.ClubID)
if rate is None:
logger.warning(f"未找到身份 {src_shenfen} (映射后 fenhong_shenfen={fenhong_shenfen}, lilv_shenfen={lilv_shenfen}) 的分红利率,罚单 {fadan_id} 不进行分红")
fadan.ApplicantBonusAmount = Decimal('0.00')
@@ -79,6 +79,8 @@ def process_fadan_fenhong(fadan_id):
fadan.save(update_fields=['ApplicantBonusAmount'])
return True, "分红金额为0"
club_id = fadan.ClubID or 'xq'
# 7. 根据申请处罚者身份执行不同的分红逻辑
# src_shenfen == 5 代表商家(源身份)
if src_shenfen == 5:
@@ -87,6 +89,7 @@ def process_fadan_fenhong(fadan_id):
fenhong_record, created = PenaltyBonus.objects.get_or_create(
BeneficiaryID=shenqing_chufa,
Identity=fenhong_shenfen,
ClubID=club_id,
defaults={
'TotalAmount': Decimal('0.00'),
'WithdrawableAmount': Decimal('0.00'),
@@ -120,6 +123,7 @@ def process_fadan_fenhong(fadan_id):
fenhong_record, created = PenaltyBonus.objects.get_or_create(
BeneficiaryID=shenqing_chufa,
Identity=fenhong_shenfen,
ClubID=club_id,
defaults={
'TotalAmount': Decimal('0.00'),
'WithdrawableAmount': Decimal('0.00'),
@@ -145,7 +149,7 @@ def process_fadan_fenhong(fadan_id):
return False, f"分红处理异常: {str(e)}"
def _get_fenhong_rate(fenhongzhe_id, fenhong_shenfen, lilv_shenfen):
def _get_fenhong_rate(fenhongzhe_id, fenhong_shenfen, lilv_shenfen, club_id='xq'):
"""
获取分红利率(优先使用个人单独配置,否则使用全局身份配置)
"""
@@ -153,6 +157,7 @@ def _get_fenhong_rate(fenhongzhe_id, fenhong_shenfen, lilv_shenfen):
individual = PenaltyBonus.objects.get(
BeneficiaryID=fenhongzhe_id,
Identity=fenhong_shenfen,
ClubID=club_id,
is_individual_rate=True
)
return individual.IndividualRate

View File

@@ -0,0 +1,20 @@
"""users: User.ClubID 字段"""
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='ClubID',
field=models.CharField(
blank=True, db_column='ClubID', default='xq',
max_length=16, null=True, verbose_name='所属俱乐部',
),
),
]

View File

@@ -0,0 +1,30 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_user_clubid'),
]
operations = [
migrations.AddField(
model_name='tixianautorecord',
name='club_id',
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
),
migrations.AddField(
model_name='tixianjilu',
name='club_id',
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
),
migrations.AddField(
model_name='tixianshenhejilu',
name='club_id',
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
),
migrations.AddIndex(
model_name='tixianjilu',
index=models.Index(fields=['club_id', 'zhuangtai'], name='tixianjilu_club_status_idx'),
),
]

View File

@@ -340,6 +340,7 @@ class UserKefu(QModel):
class Tixianjilu(QModel):
id = models.AutoField(primary_key=True, verbose_name='提现记录ID')
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='提现用户ID')
avatar = models.CharField(max_length=500, null=True, blank=True, verbose_name='头像') # 用CharField
phone = models.CharField(max_length=11, blank=True, null=True, verbose_name='手机号')
@@ -370,6 +371,7 @@ class Tixianjilu(QModel):
verbose_name = '提现记录表'
verbose_name_plural = verbose_name
indexes = [
models.Index(fields=['club_id', 'zhuangtai']),
models.Index(fields=['yonghuid']),
models.Index(fields=['zhuangtai']),
models.Index(fields=['shenheid']),
@@ -393,6 +395,7 @@ class TixianShenheJilu(QModel):
shenhe_danhao = models.CharField(
max_length=32, unique=True, db_index=True, verbose_name='审核单号'
)
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
# 提现类型1打手佣金 2管事分红 3组长分红 4考核官分佣 5打手押金 6商家余额
leixing = models.PositiveSmallIntegerField(db_index=True, verbose_name='提现类型')
@@ -454,6 +457,7 @@ class TixianAutoRecord(QModel):
db_index=True,
verbose_name='提现订单号(自定义)'
)
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
leixing = models.IntegerField(verbose_name='提现类型 1打手 2管事')
jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='申请提现金额')

View File

@@ -25,8 +25,15 @@ from django.conf import settings
from django.db import transaction
from django.utils import timezone
from orders.models import Order, MerchantOrderExt, Penalty, CommissionRate
from config.models import Szjilu, TixianQuotaDefault
from orders.models import Order, MerchantOrderExt, Penalty
from jituan.services.wechat_pay import club_id_for_tixian_yonghuid
from jituan.services.withdraw_config import (
get_or_create_platform_daily_stat,
get_personal_daily_quota,
get_platform_daily_limit,
get_platform_daily_total,
get_tixian_feilv as _get_tixian_feilv_for_club,
)
from utils.fadan_utils import check_fadan_qiangdan_eligible
@@ -67,6 +74,19 @@ LEIXING_RATE_KEY = {
6: '10',
}
def _club_id_for_user(user_main):
return club_id_for_tixian_yonghuid(user_main.UserUID)
def get_tixian_feilv(leixing, yonghuid=None, club_id=None):
"""按用户所属俱乐部读取提现手续费率。"""
if club_id is None:
if not yonghuid:
raise ValueError('缺少用户或俱乐部')
club_id = club_id_for_tixian_yonghuid(yonghuid)
return _get_tixian_feilv_for_club(club_id, leixing)
DAKUAN_MODE_MANUAL = 1
DAKUAN_MODE_AUTO = 2
@@ -149,16 +169,6 @@ def generate_shenhe_danhao():
raise RuntimeError('生成审核单号失败,请重试')
def get_tixian_feilv(leixing):
rate_key = LEIXING_RATE_KEY.get(leixing)
if not rate_key:
raise ValueError('无效的提现类型')
rate_obj = CommissionRate.query.filter(Platform=rate_key).first()
if not rate_obj:
raise ValueError(f'提现费率未配置(类型{leixing}')
return decimal.Decimal(str(rate_obj.Rate))
def calc_fee_amounts(jine, feilv):
"""申请金额 jine实际到账 = jine - 手续费"""
shouxufei = (jine * feilv).quantize(decimal.Decimal('0.01'))
@@ -410,25 +420,8 @@ def validate_collect_eligibility(user_main, leixing):
return False, '无效的提现类型'
def _get_personal_daily_quota(leixing, profile):
"""打手/管事/组长个人每日限额:额外开启优先,否则默认配置"""
if leixing == 1:
if profile.kaioi_ewai_xiane and profile.ewai_xiane > 0:
return profile.ewai_xiane
return TixianQuotaDefault.query.get(UserType=1).default_quota
if leixing == 2:
if profile.kaioi_ewai_xiane and profile.ewai_xiane > 0:
return profile.ewai_xiane
return TixianQuotaDefault.query.get(UserType=2).default_quota
if leixing == 3:
try:
default_quota = TixianQuotaDefault.query.get(UserType=3).default_quota
except TixianQuotaDefault.DoesNotExist:
default_quota = decimal.Decimal('0.00')
if profile.kaioi_ewai_tixian and profile.ewai_tixian_xiane > 0:
return profile.ewai_tixian_xiane
return default_quota
raise ValueError('无效的个人限额身份')
def _get_personal_daily_quota(club_id, leixing, profile):
return get_personal_daily_quota(club_id, leixing, profile)
def _get_active_auto_record(shenhe_danhao):
@@ -444,23 +437,12 @@ def _get_active_auto_record(shenhe_danhao):
)
def _get_platform_daily_total(leixing, today=None):
"""平台今日已提现总额:只读 houtai.WithdrawalDailyStats到账金额口径"""
if today is None:
today = date.today()
try:
return WithdrawalDailyStats.query.get(Date=today, WithdrawalType=leixing).total_amount
except WithdrawalDailyStats.DoesNotExist:
return decimal.Decimal('0.00')
def _get_platform_daily_total(club_id, leixing, today=None):
return get_platform_daily_total(club_id, leixing, today)
def _calc_platform_projected(leixing, shijidaozhang, shenhe_danhao=''):
"""
平台今日预占后总额(收款前只读预估)。
本审核单若已有进行中打款记录,其金额尚未写入统计表,但不应重复加计。
返回 (daily_used, projected_total, additional_amount)
"""
daily_used = _get_platform_daily_total(leixing)
def _calc_platform_projected(club_id, leixing, shijidaozhang, shenhe_danhao=''):
daily_used = _get_platform_daily_total(club_id, leixing)
active_rec = _get_active_auto_record(shenhe_danhao)
if active_rec:
return daily_used, daily_used, decimal.Decimal('0.00')
@@ -475,20 +457,14 @@ def _projected_personal_total(personal_today, jine, shenhe_danhao=''):
return personal_today - already_counted + jine
def _get_platform_daily_limit(leixing):
"""
平台每日总限额peizhi.TixianQuotaDefault
提现身份1打手→配置42管事→53组长→64考核官→75押金→86商家→9
"""
quota_leixing = leixing + 3
try:
return TixianQuotaDefault.query.get(UserType=quota_leixing).default_quota
except TixianQuotaDefault.DoesNotExist:
def _get_platform_daily_limit(club_id, leixing):
limit = get_platform_daily_limit(club_id, leixing)
if limit <= 0:
logger.warning(
'平台每日总限额未配置 leixing=%s(读取quota_leixing=%s)按0处理禁止新增收款',
leixing, quota_leixing,
'平台每日总限额为0 club_id=%s leixing=%s',
club_id, leixing,
)
return decimal.Decimal('0.00')
return limit
def _check_personal_quota_readonly(user_main, leixing, jine, shenhe_danhao=''):
@@ -525,7 +501,7 @@ def _check_personal_quota_readonly(user_main, leixing, jine, shenhe_danhao=''):
else:
personal_today = decimal.Decimal('0.00')
personal_quota = _get_personal_daily_quota(leixing, profile)
personal_quota = _get_personal_daily_quota(_club_id_for_user(user_main), leixing, profile)
projected_personal = _projected_personal_total(personal_today, jine, shenhe_danhao)
if projected_personal > personal_quota:
return (
@@ -536,22 +512,22 @@ def _check_personal_quota_readonly(user_main, leixing, jine, shenhe_danhao=''):
return True, '', ''
def _check_platform_quota_readonly(leixing, shijidaozhang, shenhe_danhao=''):
def _check_platform_quota_readonly(club_id, leixing, shijidaozhang, shenhe_danhao=''):
"""平台每日总限额只读校验:今日已用 + 本笔到账 > 总限额 则拒绝"""
platform_limit = _get_platform_daily_limit(leixing)
platform_limit = _get_platform_daily_limit(club_id, leixing)
shijidaozhang = shijidaozhang or decimal.Decimal('0.00')
if platform_limit <= 0 and shijidaozhang > 0:
return False, '今日该角色提现已关闭总限额为0暂无法收款', 'platform'
daily_used, projected, additional = _calc_platform_projected(
leixing, shijidaozhang, shenhe_danhao,
club_id, leixing, shijidaozhang, shenhe_danhao,
)
if projected > platform_limit:
logger.warning(
'平台总限额拦截 leixing=%s shenhe_danhao=%s daily_used=%s additional=%s '
'平台总限额拦截 club_id=%s leixing=%s shenhe_danhao=%s daily_used=%s additional=%s '
'projected=%s limit=%s',
leixing, shenhe_danhao, daily_used, additional, projected, platform_limit,
club_id, leixing, shenhe_danhao, daily_used, additional, projected, platform_limit,
)
return (
False,
@@ -570,7 +546,8 @@ def check_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe_d
ok, msg, kind = _check_personal_quota_readonly(user_main, leixing, jine, shenhe_danhao)
if not ok:
return False, msg, kind
return _check_platform_quota_readonly(leixing, shijidaozhang, shenhe_danhao)
club_id = _club_id_for_user(user_main)
return _check_platform_quota_readonly(club_id, leixing, shijidaozhang, shenhe_danhao)
def reserve_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe_danhao=''):
@@ -583,6 +560,7 @@ def reserve_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe
today = date.today()
shijidaozhang = shijidaozhang or decimal.Decimal('0.00')
club_id = _club_id_for_user(user_main)
if _get_active_auto_record(shenhe_danhao):
return True, '', ''
@@ -609,7 +587,7 @@ def reserve_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe
personal_profile.jinri_tixian = decimal.Decimal('0.00')
personal_today = personal_profile.jinri_tixian
personal_quota = _get_personal_daily_quota(leixing, personal_profile)
personal_quota = _get_personal_daily_quota(club_id, leixing, personal_profile)
if personal_today + jine > personal_quota:
return (
False,
@@ -617,12 +595,8 @@ def reserve_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe
'personal',
)
platform_stat, _ = WithdrawalDailyStats.objects.select_for_update().get_or_create(
Date=today,
WithdrawalType=leixing,
defaults={'total_amount': decimal.Decimal('0.00')},
)
platform_limit = _get_platform_daily_limit(leixing)
platform_stat = get_or_create_platform_daily_stat(club_id, leixing, today)
platform_limit = _get_platform_daily_limit(club_id, leixing)
if platform_limit <= 0 and shijidaozhang > 0:
return False, '今日该角色提现已关闭总限额为0暂无法收款', 'platform'
if platform_stat.total_amount + shijidaozhang > platform_limit:
@@ -659,6 +633,7 @@ def release_collect_quota_reservation(user_main, leixing, jine, shijidaozhang):
"""微信打款明确失败/用户取消时回滚收款预占的限额"""
today = date.today()
shijidaozhang = shijidaozhang or decimal.Decimal('0.00')
club_id = _club_id_for_user(user_main)
try:
with transaction.atomic():
if leixing == 1:
@@ -679,7 +654,7 @@ def release_collect_quota_reservation(user_main, leixing, jine, shijidaozhang):
try:
stat = WithdrawalDailyStats.objects.select_for_update().get(
Date=today, WithdrawalType=leixing,
club_id=club_id, Date=today, WithdrawalType=leixing,
)
stat.total_amount = max(
stat.total_amount - shijidaozhang,
@@ -727,8 +702,10 @@ def _quota_already_reserved_for_record(auto_record):
today = date.today()
shijidaozhang = auto_record.shijidaozhang or decimal.Decimal('0.00')
stat_total = _get_platform_daily_total(auto_record.leixing, today)
club_id = club_id_for_tixian_yonghuid(auto_record.yonghuid)
stat_total = _get_platform_daily_total(club_id, auto_record.leixing, today)
other_sum = TixianAutoRecord.query.filter(
club_id=club_id,
leixing=auto_record.leixing,
zhuangtai__in=[0, 1],
CreateTime__date=today,
@@ -758,11 +735,8 @@ def apply_transfer_success_quota(auto_record):
logger.error('打款成功累加限额失败:用户不存在 %s', auto_record.yonghuid)
return
platform_stat, _ = WithdrawalDailyStats.objects.select_for_update().get_or_create(
Date=today,
WithdrawalType=leixing,
defaults={'total_amount': decimal.Decimal('0.00')},
)
club_id = _club_id_for_user(user_main)
platform_stat = get_or_create_platform_daily_stat(club_id, leixing, today)
platform_stat.total_amount += shijidaozhang
platform_stat.save(update_fields=['total_amount'])
@@ -981,19 +955,28 @@ def close_audit_over_limit_refund(audit, auto_record=None, fail_reason='单笔
return True
def query_wechat_transfer_bill(out_bill_no):
def query_wechat_transfer_bill(out_bill_no, club_id=None):
"""
向微信查转账单状态
GET /v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
返回 dict网络/系统异常返回 None微信无此单返回 {'_not_found': True}
"""
from jituan.services.wechat_pay import club_id_for_tixian_yonghuid
if not club_id:
try:
rec = TixianAutoRecord.query.filter(tixian_id=out_bill_no).first()
if rec:
club_id = club_id_for_tixian_yonghuid(rec.yonghuid)
except Exception:
pass
url = (
'https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/'
f'transfer-bills/out-bill-no/{out_bill_no}'
)
try:
auth = build_authorization('GET', url, '')
auth = build_authorization('GET', url, '', club_id=club_id)
headers = {
'Authorization': auth,
'Accept': 'application/json',
@@ -1030,21 +1013,11 @@ def _parse_package_info(raw):
def _apply_platform_accounting(auto_record):
"""平台收支统计(幂等由 mark_transfer_success 外层保证只调一次)"""
update_daily_payout(auto_record.shijidaozhang)
try:
szjilu = Szjilu.objects.select_for_update().get(id=1)
szjilu.TotalIncome -= auto_record.shijidaozhang
szjilu.TotalExpense += auto_record.shijidaozhang
szjilu.DailyExpense += auto_record.shijidaozhang
szjilu.save()
except Szjilu.DoesNotExist:
Szjilu.query.create(
id=1,
TotalIncome=-auto_record.shijidaozhang,
TotalExpense=auto_record.shijidaozhang,
DailyExpense=auto_record.shijidaozhang,
)
from jituan.services.szjilu_accounting import apply_szjilu_expense
from jituan.services.wechat_pay import club_id_for_tixian_yonghuid
payout_club = club_id_for_tixian_yonghuid(auto_record.yonghuid)
update_daily_payout(auto_record.shijidaozhang, payout_club)
apply_szjilu_expense(auto_record.shijidaozhang, payout_club)
def mark_transfer_success(auto_record, *, wechat_transfer_no=None, with_accounting=True):
@@ -1210,7 +1183,7 @@ def reconcile_shenhe_wechat_bills(shenhe_danhao):
if rec.zhuangtai == 1:
return RECONCILE_COMPLETED, {'msg': '该提现已完成,请勿重复操作'}
wx_data = query_wechat_transfer_bill(rec.tixian_id)
wx_data = query_wechat_transfer_bill(rec.tixian_id, club_id_for_tixian_yonghuid(rec.yonghuid))
if wx_data is None:
logger.error(
'对账查单不可用 shenhe_danhao=%s bill=%s',
@@ -1316,7 +1289,7 @@ def handle_post_transfer_failure(tixian_id, shenhe_danhao, err_code='', err_msg=
except TixianAutoRecord.DoesNotExist:
return RECONCILE_PENDING, '收款处理中,请稍后再试'
wx_data = query_wechat_transfer_bill(tixian_id)
wx_data = query_wechat_transfer_bill(tixian_id, club_id_for_tixian_yonghuid(ar.yonghuid))
if wx_data is None:
logger.error(
'发起转账失败且查单不可用 bill=%s code=%s msg=%s,保持待查单',
@@ -1377,7 +1350,7 @@ def create_audit_application(user_main, leixing, jine):
if not ok:
raise ValueError(msg)
feilv = get_tixian_feilv(leixing)
feilv = get_tixian_feilv(leixing, yonghuid=yonghuid)
shouxufei, shijidaozhang = calc_fee_amounts(jine, feilv)
if shijidaozhang <= 0:
raise ValueError('提现金额过低,扣除手续费后无实际到账')
@@ -1391,9 +1364,11 @@ def create_audit_application(user_main, leixing, jine):
with transaction.atomic():
nicheng = deduct_balance(user_main, leixing, jine) or nicheng
club_id = club_id_for_tixian_yonghuid(yonghuid)
audit_kwargs = {
'shenhe_danhao': shenhe_danhao,
'club_id': club_id,
'yonghuid': yonghuid,
'leixing': leixing,
'zhuangtai': 1,
@@ -1407,6 +1382,7 @@ def create_audit_application(user_main, leixing, jine):
audit = TixianShenheJilu.query.create(**audit_kwargs)
tixian_record = Tixianjilu.query.create(
club_id=club_id,
yonghuid=yonghuid,
avatar=user_main.Avatar or '',
phone=user_main.Phone or '',

View File

@@ -120,10 +120,10 @@ def _build_wx_transfer_body(wx_cfg, tixian_id, openid, yonghuid, leixing, shijid
}
def _call_wechat_transfer(body, wx_cfg):
def _call_wechat_transfer(body, wx_cfg, club_id=None):
url = 'https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills'
body_json = json.dumps(body, separators=(',', ':'))
auth = build_authorization('POST', url, body_json)
auth = build_authorization('POST', url, body_json, club_id=club_id)
headers = {
'Authorization': auth,
'Content-Type': 'application/json',
@@ -203,9 +203,13 @@ def process_audit_collect(request):
if not user_main.OpenID:
return Response({'code': 10, 'msg': '用户未绑定微信,无法收款'})
wx_cfg = settings.WECHAT_PAY_V3_CONFIG
from jituan.services.club_user import get_user_club_id
from jituan.services.wechat_pay import get_wechat_v3_config
payout_club_id = get_user_club_id(user_main)
wx_cfg = get_wechat_v3_config(payout_club_id)
if not wx_cfg.get('APPID') or not wx_cfg.get('MCHID') or not wx_cfg.get('TRANSFER_SCENE_ID'):
logger.error('微信打款配置不完整 APPID=%s MCHID=%s', wx_cfg.get('APPID'), wx_cfg.get('MCHID'))
logger.error('微信打款配置不完整 club=%s APPID=%s MCHID=%s', payout_club_id, wx_cfg.get('APPID'), wx_cfg.get('MCHID'))
return Response({'code': 11, 'msg': '系统配置异常,请联系客服'})
ctx, err = resolve_collect_context(
@@ -295,8 +299,10 @@ def process_audit_collect(request):
quota_reserved = True
tixian_id = generate_tixian_id()
from jituan.services.club_user import get_user_club_id
TixianAutoRecord.query.create(
tixian_id=tixian_id,
club_id=get_user_club_id(user_main),
yonghuid=yonghuid,
leixing=leixing,
jine=jine,
@@ -314,7 +320,7 @@ def process_audit_collect(request):
)
try:
resp, wx_result = _call_wechat_transfer(body, wx_cfg)
resp, wx_result = _call_wechat_transfer(body, wx_cfg, payout_club_id)
if resp.status_code == 200 and wx_result.get('state') == 'WAIT_USER_CONFIRM':
package_info = wx_result.get('package_info')

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)