fix: 管事/组长分红入账接入资金冻结网关(会员/商家单/押金)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-29 00:50:13 +08:00
parent 0ebe5b4872
commit 8e7481e25d
6 changed files with 173 additions and 34 deletions

View File

@@ -5,6 +5,7 @@ import logging
from decimal import Decimal
from django.db import transaction
from django.db.models import F
from backend.utils import update_guanshi_daily_by_action
from jituan.constants import CLUB_ID_DEFAULT
@@ -152,9 +153,20 @@ def _apply_yajin_fenhong(order, dashou, amount):
if fenhong_jine <= 0:
return
guanshi = UserGuanshi.objects.select_for_update().get(user__UserUID=guanshi_id)
guanshi.yue += fenhong_jine
guanshi.chongzhifenrun += fenhong_jine
guanshi.save(update_fields=['yue', 'chongzhifenrun'])
from jituan.services.fund_freeze import credit_guanshi_fenhong
dingdan_id = str(order.dingdan_id or '')
result = credit_guanshi_fenhong(
user_id=str(guanshi_id),
amount=fenhong_jine,
biz_id=f'{dingdan_id}:guanshi:yajin',
profile=guanshi,
czjilu_id=dingdan_id,
freeze_reason=f'押金分红 {dingdan_id}',
)
if not result.get('idempotent'):
UserGuanshi.objects.filter(pk=guanshi.pk).update(
chongzhifenrun=F('chongzhifenrun') + fenhong_jine,
)
user_main = dashou.user
Gsfenhong.query.create(
dingdan_id=order.dingdan_id,

View File

@@ -90,16 +90,28 @@ def calc_freeze_amount(credit: Decimal, cfg: ClubFundFreezeConfig) -> Decimal:
return frozen
def _source_allowed(cfg: ClubFundFreezeConfig, source_type: str) -> bool:
def _source_allowed(cfg: ClubFundFreezeConfig, source_type: str, role: str = '') -> bool:
scope = cfg.source_scope
if not scope:
return True
if isinstance(scope, str):
return source_type == scope
try:
return source_type in list(scope)
except TypeError:
items = [scope]
else:
try:
items = list(scope)
except TypeError:
return True
if source_type in items:
return True
# 管事/组长几乎没有 order_settle 入账;历史配置只勾了「订单结算」时仍允许分红冻结
role = (role or '').strip().lower()
if (
role in (ROLE_GUANSHI, ROLE_ZUZHANG, ROLE_SHENHEGUAN)
and source_type == SOURCE_FENHONG
and set(items) == {'order_settle'}
):
return True
return False
def _config_snapshot(cfg: ClubFundFreezeConfig) -> Dict[str, Any]:
@@ -195,7 +207,7 @@ def credit_role_balance(
cfg = get_freeze_config(cid, role)
freeze_on = bool(
cfg and cfg.enabled and _source_allowed(cfg, source_type) and biz_id
cfg and cfg.enabled and _source_allowed(cfg, source_type, role=role) and biz_id
)
skip_reason = ''
if not freeze_on:
@@ -203,7 +215,7 @@ def credit_role_balance(
skip_reason = 'no_config'
elif not cfg.enabled:
skip_reason = 'disabled'
elif not _source_allowed(cfg, source_type):
elif not _source_allowed(cfg, source_type, role=role):
skip_reason = f'source_not_in_scope:{source_type}'
elif not biz_id:
skip_reason = 'empty_biz_id'
@@ -306,13 +318,68 @@ def _load_profile(role: str, user_id: str):
def split_preview(club_id: Optional[str], role: str, amount, source_type: str = SOURCE_ORDER_SETTLE) -> Tuple[Decimal, Decimal]:
"""只算不写库:后台试算用。"""
credit = _money(amount)
role = (role or '').strip().lower()
cfg = get_freeze_config(club_id, role)
if not cfg or not cfg.enabled or not _source_allowed(cfg, source_type):
if not cfg or not cfg.enabled or not _source_allowed(cfg, source_type, role=role):
return credit, Decimal('0.00')
frozen = calc_freeze_amount(credit, cfg)
return _money(credit - frozen), frozen
def credit_guanshi_fenhong(
*,
user_id: str,
amount,
biz_id: str,
profile=None,
order_id: str = '',
czjilu_id: str = '',
club_id: Optional[str] = None,
freeze_reason: str = '',
) -> Dict[str, Any]:
"""管事分红入账:未开冻结=全额进 yue开启则按配置拆冻结池。调用方须在 atomic 内。"""
cid = settle_freeze_club_id(user_id=str(user_id or '')) if not club_id else _club(club_id)
return credit_role_balance(
club_id=cid,
user_id=str(user_id),
role=ROLE_GUANSHI,
amount=amount,
source_type=SOURCE_FENHONG,
biz_id=biz_id,
profile=profile,
order_id=order_id or '',
czjilu_id=czjilu_id or '',
freeze_reason=freeze_reason or f'分红 {biz_id}',
)
def credit_zuzhang_fenhong(
*,
user_id: str,
amount,
biz_id: str,
profile=None,
order_id: str = '',
czjilu_id: str = '',
club_id: Optional[str] = None,
freeze_reason: str = '',
) -> Dict[str, Any]:
"""组长分红入账:未开冻结=全额进 ketixian_jine。调用方须在 atomic 内。"""
cid = settle_freeze_club_id(user_id=str(user_id or '')) if not club_id else _club(club_id)
return credit_role_balance(
club_id=cid,
user_id=str(user_id),
role=ROLE_ZUZHANG,
amount=amount,
source_type=SOURCE_FENHONG,
biz_id=biz_id,
profile=profile,
order_id=order_id or '',
czjilu_id=czjilu_id or '',
freeze_reason=freeze_reason or f'分红 {biz_id}',
)
def settle_freeze_club_id(order=None, user_id: str = '') -> str:
"""
结算冻结规则按「入账人所属俱乐部」,不按订单俱乐部。

View File

@@ -137,7 +137,8 @@ def save_config(request, data: dict, operator: str = '') -> Tuple[Optional[dict]
source_scope = data.get('source_scope')
if source_scope is None:
source_scope = ['order_settle']
# 默认同时覆盖订单结算与分红(管事/组长主要靠分红入账)
source_scope = ['order_settle', 'fenhong']
if isinstance(source_scope, str):
try:
source_scope = json.loads(source_scope)

View File

@@ -23,6 +23,7 @@ from decimal import Decimal
import defusedxml.ElementTree as ET
import requests
from django.db import transaction
from django.db.models import F
from django.utils import timezone
from backend.utils import (
@@ -538,23 +539,44 @@ def _apply_member_fenhong(order, huiyuan, formal_cishu=None, trial_guanshi_fc=No
return
user_main = dashou.user
dingdan_id = str(order.dingdan_id or '')
from jituan.services.fund_freeze import credit_guanshi_fenhong, credit_zuzhang_fenhong
if guanshi_fenhong > 0:
guanshi.chongzhifenrun += guanshi_fenhong
guanshi.yue += guanshi_fenhong
guanshi.jinrichongzhi += 1
guanshi.jinyuechongzhi += 1
guanshi.save(update_fields=[
'chongzhifenrun', 'yue', 'jinrichongzhi', 'jinyuechongzhi',
])
result = credit_guanshi_fenhong(
user_id=str(guanshi_id),
amount=guanshi_fenhong,
biz_id=f'{dingdan_id}:guanshi:member',
profile=guanshi,
czjilu_id=dingdan_id,
freeze_reason=f'会员充值分红 {dingdan_id}',
)
if result.get('idempotent'):
logger.warning('会员管事分红幂等跳过 dingdan=%s guanshi=%s', dingdan_id, guanshi_id)
else:
type(guanshi).query.filter(pk=guanshi.pk).update(
chongzhifenrun=F('chongzhifenrun') + guanshi_fenhong,
jinrichongzhi=F('jinrichongzhi') + 1,
jinyuechongzhi=F('jinyuechongzhi') + 1,
)
if zuzhang_fenhong > 0 and zuzhang:
zuzhang.fenyong_zonge += zuzhang_fenhong
zuzhang.ketixian_jine += zuzhang_fenhong
zuzhang.jinri_fenyong += zuzhang_fenhong
zuzhang.jinyue_fenyong += zuzhang_fenhong
zuzhang.save(update_fields=[
'fenyong_zonge', 'ketixian_jine', 'jinri_fenyong', 'jinyue_fenyong',
])
result = credit_zuzhang_fenhong(
user_id=str(zuzhang_id),
amount=zuzhang_fenhong,
biz_id=f'{dingdan_id}:zuzhang:member',
profile=zuzhang,
czjilu_id=dingdan_id,
freeze_reason=f'会员充值分红 {dingdan_id}',
)
if result.get('idempotent'):
logger.warning('会员组长分红幂等跳过 dingdan=%s zuzhang=%s', dingdan_id, zuzhang_id)
else:
type(zuzhang).query.filter(pk=zuzhang.pk).update(
fenyong_zonge=F('fenyong_zonge') + zuzhang_fenhong,
jinri_fenyong=F('jinri_fenyong') + zuzhang_fenhong,
jinyue_fenyong=F('jinyue_fenyong') + zuzhang_fenhong,
)
Gsfenhong.query.create(
dingdan_id=order.dingdan_id,

View File

@@ -182,10 +182,20 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id):
try:
with transaction.atomic():
guanshi = UserGuanshi.objects.select_for_update().get(user__UserUID=guanshi_id)
UserGuanshi.objects.filter(id=guanshi.id).update(
yue=F('yue') + guanshi_fencheng,
chongzhifenrun=F('chongzhifenrun') + guanshi_fencheng,
from jituan.services.fund_freeze import credit_guanshi_fenhong
order_id = str(getattr(order, 'OrderID', '') or '')
result = credit_guanshi_fenhong(
user_id=str(guanshi_id),
amount=guanshi_fencheng,
biz_id=f'{order_id}:guanshi:merchant',
profile=guanshi,
order_id=order_id,
freeze_reason=f'商家订单分红 {order_id}',
)
if not result.get('idempotent'):
UserGuanshi.objects.filter(id=guanshi.id).update(
chongzhifenrun=F('chongzhifenrun') + guanshi_fencheng,
)
user_main = dashou.user
Gsfenhong.objects.create(

View File

@@ -946,7 +946,7 @@ def claim_reward(claim_id: int, user, request_shenfen: str = '') -> dict:
amount = claim.reward_amount
_write_claim_log(claim, uid, RankRewardClaimLog.ACTION_START, amount, '开始入账')
_credit_balance(claim.shenfen, uid, amount)
_credit_balance(claim.shenfen, uid, amount, biz_id=f'rank_claim:{claim.id}')
claim.status = RankRewardClaim.STATUS_CLAIMED
claim.claimed_at = timezone.now()
@@ -962,17 +962,44 @@ def claim_reward(claim_id: int, user, request_shenfen: str = '') -> dict:
}
def _credit_balance(shenfen: str, yonghuid: str, amount: Decimal):
def _credit_balance(shenfen: str, yonghuid: str, amount: Decimal, biz_id: str = ''):
amount = Decimal(str(amount))
biz = (biz_id or '').strip() or f'rank:{shenfen}:{yonghuid}:{amount}'
if shenfen == 'dashou':
from jituan.services.fund_freeze import (
credit_role_balance, settle_freeze_club_id, ROLE_DASHOU, SOURCE_FENHONG,
)
p = UserDashou.objects.select_for_update().get(user__UserUID=yonghuid)
UserDashou.objects.filter(pk=p.pk).update(yue=F('yue') + amount)
credit_role_balance(
club_id=settle_freeze_club_id(user_id=str(yonghuid)),
user_id=str(yonghuid),
role=ROLE_DASHOU,
amount=amount,
source_type=SOURCE_FENHONG,
biz_id=biz,
profile=p,
freeze_reason='排行榜奖励',
)
elif shenfen == 'guanshi':
from jituan.services.fund_freeze import credit_guanshi_fenhong
p = UserGuanshi.objects.select_for_update().get(user__UserUID=yonghuid)
UserGuanshi.objects.filter(pk=p.pk).update(yue=F('yue') + amount)
credit_guanshi_fenhong(
user_id=str(yonghuid),
amount=amount,
biz_id=biz,
profile=p,
freeze_reason='排行榜奖励',
)
elif shenfen == 'zuzhang':
from jituan.services.fund_freeze import credit_zuzhang_fenhong
p = UserZuzhang.objects.select_for_update().get(user__UserUID=yonghuid)
UserZuzhang.objects.filter(pk=p.pk).update(ketixian_jine=F('ketixian_jine') + amount)
credit_zuzhang_fenhong(
user_id=str(yonghuid),
amount=amount,
biz_id=biz,
profile=p,
freeze_reason='排行榜奖励',
)
elif shenfen == 'shangjia':
p = UserShangjia.objects.select_for_update().get(user__UserUID=yonghuid)
UserShangjia.objects.filter(pk=p.pk).update(yue=F('yue') + amount)