fix: 管事/组长分红入账接入资金冻结网关(会员/商家单/押金)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
items = [scope]
|
||||
else:
|
||||
try:
|
||||
return source_type in list(scope)
|
||||
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:
|
||||
"""
|
||||
结算冻结规则按「入账人所属俱乐部」,不按订单俱乐部。
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -182,8 +182,18 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
guanshi = UserGuanshi.objects.select_for_update().get(user__UserUID=guanshi_id)
|
||||
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(
|
||||
yue=F('yue') + guanshi_fencheng,
|
||||
chongzhifenrun=F('chongzhifenrun') + guanshi_fencheng,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user