diff --git a/merchant_ops/constants.py b/merchant_ops/constants.py index e2b8328..481a4f9 100644 --- a/merchant_ops/constants.py +++ b/merchant_ops/constants.py @@ -30,6 +30,7 @@ PERMISSION_DEFINITIONS = [ ('staff_manage', '子客服管理'), ('role_manage', '角色管理'), ('wallet_allocate', '划额度'), + ('wallet_revoke', '收回额度'), ('wallet_view', '查看额度'), ('finance_view', '查看商家余额'), ('order_dispatch', '派单发单'), @@ -43,7 +44,7 @@ PERMISSION_DEFINITIONS = [ ('penalty_apply', '申请罚单'), ('penalty_manage', '改撤罚单'), ('penalty_view', '查看罚单'), - ('dashou_ban', '封禁解封打手'), + ('staff_disable', '封禁解封客服'), ('template_manage', '派单模板'), ('audit_view', '操作日志'), ('rank_view', '客服排行榜'), @@ -58,10 +59,10 @@ SYSTEM_ROLE_TEMPLATES = [ 'role_name': '总管', 'sort_order': 100, 'permissions': [ - 'staff_manage', 'wallet_allocate', 'wallet_view', 'finance_view', + 'staff_manage', 'wallet_allocate', 'wallet_revoke', 'wallet_view', 'finance_view', 'order_dispatch', 'order_view_all', 'order_detail', 'order_settle', 'order_refund_apply', 'order_cancel', 'order_change_player', - 'penalty_apply', 'penalty_manage', 'penalty_view', 'dashou_ban', + 'penalty_apply', 'penalty_manage', 'penalty_view', 'staff_disable', 'template_manage', 'audit_view', 'rank_view', 'im_chat', 'stats_view', ], }, @@ -70,7 +71,7 @@ SYSTEM_ROLE_TEMPLATES = [ 'role_name': '财务', 'sort_order': 80, 'permissions': [ - 'wallet_allocate', 'wallet_view', 'finance_view', 'order_view_all', + 'wallet_allocate', 'wallet_revoke', 'wallet_view', 'finance_view', 'order_view_all', 'order_detail', 'penalty_view', 'audit_view', 'rank_view', 'stats_view', ], }, @@ -92,7 +93,7 @@ IMPORTANT_AUDIT_ACTIONS = frozenset([ 'DISPATCH', 'LINK_GENERATE', 'SETTLE', 'CANCEL', 'REFUND_APPLY', 'CHANGE_PLAYER', 'PENALTY_APPLY', 'PENALTY_MANAGE', 'TEMPLATE_CREATE', 'TEMPLATE_UPDATE', 'TEMPLATE_DELETE', - 'WALLET_ALLOCATE', 'INVITE_CREATE', 'INVITE_REVOKE', + 'WALLET_ALLOCATE', 'WALLET_REVOKE', 'INVITE_CREATE', 'INVITE_REVOKE', 'MEMBER_UPDATE_ROLE', 'MEMBER_REMOVE', 'MEMBER_DISABLE', 'MEMBER_ENABLE', 'ROLE_CREATE', 'ROLE_SET_PERMS', 'MEMBER_SET_PERMS', 'STAFF_BIND', @@ -111,6 +112,7 @@ AUDIT_ACTION_LABELS = { 'TEMPLATE_UPDATE': '修改模板', 'TEMPLATE_DELETE': '删除模板', 'WALLET_ALLOCATE': '划额度', + 'WALLET_REVOKE': '收回额度', 'INVITE_CREATE': '生成邀请码', 'INVITE_REVOKE': '作废邀请码', 'MEMBER_UPDATE_ROLE': '更换客服角色', diff --git a/merchant_ops/services/dispatch.py b/merchant_ops/services/dispatch.py index cd31721..56057cf 100644 --- a/merchant_ops/services/dispatch.py +++ b/merchant_ops/services/dispatch.py @@ -98,8 +98,10 @@ def staff_dispatch_order(member, staff_user, data): if shangjia.zhuangtai != 1: return False, {'code': 403, 'msg': '商家状态异常', 'data': None} - if jiage > float(shangjia.yue): - return False, {'code': 400, 'msg': f'商家余额不足,当前{shangjia.yue}元', 'data': None} + try: + check_wallet_available(member, jiage) + except ValueError as e: + return False, {'code': 400, 'msg': str(e), 'data': None} zhiding_dashou = None if zhiding_uid: @@ -152,7 +154,6 @@ def staff_dispatch_order(member, staff_user, data): UserShangjia.query.filter(id=shangjia.id).update( fabu=F('fabu') + 1, - yue=F('yue') - Decimal(str(jiage)), jinridingdan=F('jinridingdan') + 1, jinriliushui=F('jinriliushui') + Decimal(str(jiage)), jinyuedingdan=F('jinyuedingdan') + 1, diff --git a/merchant_ops/services/link.py b/merchant_ops/services/link.py index 26a5412..a997c1b 100644 --- a/merchant_ops/services/link.py +++ b/merchant_ops/services/link.py @@ -97,8 +97,10 @@ def staff_generate_link(member, staff_user, data): return False, {'code': 400, 'msg': '模板不存在', 'data': {}} jiage = moban.Price - if jiage > shangjia.yue: - return False, {'code': 400, 'msg': f'商家余额不足,当前{shangjia.yue}元', 'data': {}} + try: + check_wallet_available(member, jiage) + except ValueError as e: + return False, {'code': 400, 'msg': str(e), 'data': {}} try: lilu = float(CommissionRate.query.get(Platform='3').Rate) or 1.0 @@ -160,7 +162,6 @@ def staff_generate_link(member, staff_user, data): UserShangjia.query.filter(id=shangjia.id).update( fabu=F('fabu') + 1, - yue=F('yue') - jiage, jinridingdan=F('jinridingdan') + 1, jinriliushui=F('jinriliushui') + jiage, jinyuedingdan=F('jinyuedingdan') + 1, diff --git a/merchant_ops/services/stats.py b/merchant_ops/services/stats.py index fab536e..15dac0d 100644 --- a/merchant_ops/services/stats.py +++ b/merchant_ops/services/stats.py @@ -9,6 +9,7 @@ from django.db.models import F from backend.utils import update_shangjia_daily from merchant_ops.constants import STAT_ACTION_DISPATCH, STAT_ACTION_REFUND, STAT_ACTION_SETTLE from merchant_ops.models import MerchantOrderDispatch, MerchantStaffDailyStats, MerchantStaffMember +from merchant_ops.services.wallet import release_dispatch_quota logger = logging.getLogger(__name__) @@ -81,6 +82,11 @@ def sync_staff_stats_by_order_id(order_id, amount, action): today_dispatch_count=F('today_dispatch_count') + 1, today_dispatch_amount=F('today_dispatch_amount') + amount, ) + elif action == STAT_ACTION_REFUND: + try: + release_dispatch_quota(order_id, amount, member.staff_user_id) + except Exception: + logger.warning('释放客服派单额度失败 order_id=%s', order_id, exc_info=True) def update_merchant_and_staff_stats(merchant_id, staff_member, amount, action, order_id=None): diff --git a/merchant_ops/services/wallet.py b/merchant_ops/services/wallet.py index 3617474..8835551 100644 --- a/merchant_ops/services/wallet.py +++ b/merchant_ops/services/wallet.py @@ -1,11 +1,14 @@ -"""资金池:划额度、派单扣减。""" +"""资金池:划额度、派单扣减、退款释放。""" from decimal import Decimal from django.db import transaction from django.db.models import F -from merchant_ops.constants import WALLET_ALLOCATE, WALLET_DISPATCH_CONFIRM +from merchant_ops.constants import ( + WALLET_ALLOCATE, WALLET_DISPATCH_CONFIRM, WALLET_REFUND_RELEASE, +) from merchant_ops.models import MerchantStaffWallet, MerchantStaffWalletLedger +from users.models import UserShangjia def get_or_create_wallet(member): @@ -18,15 +21,23 @@ def get_or_create_wallet(member): def allocate_quota(member, amount, operator_id, remark=''): + """ + 老板划额度:从商家余额转入客服可用额度(商家余额减少,客服 quota_total 增加)。 + """ amount = Decimal(str(amount)) if amount <= 0: raise ValueError('划额度必须大于0') with transaction.atomic(): + shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=member.merchant_id) + if amount > shangjia.yue: + raise ValueError(f'商家余额不足,当前{shangjia.yue}元') + wallet = MerchantStaffWallet.objects.select_for_update().get( merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, ) + UserShangjia.objects.filter(id=shangjia.id).update(yue=F('yue') - amount) wallet.quota_total = F('quota_total') + amount wallet.save(update_fields=['quota_total']) wallet.refresh_from_db() @@ -38,7 +49,7 @@ def allocate_quota(member, amount, operator_id, remark=''): amount=amount, balance_after=wallet.quota_available, operator_id=operator_id, - remark=remark, + remark=remark or '老板划额度', ) return wallet @@ -47,11 +58,11 @@ def check_wallet_available(member, amount): amount = Decimal(str(amount)) wallet = get_or_create_wallet(member) if amount > wallet.quota_available: - raise ValueError('客服可用额度不足') + raise ValueError(f'客服可用额度不足,当前{wallet.quota_available}元') def confirm_dispatch_deduct(member, amount, order_id, operator_id): - """派单成功:扣客服额度。""" + """派单成功:仅扣客服额度(商家余额已在划额度时扣除)。""" amount = Decimal(str(amount)) with transaction.atomic(): wallet = MerchantStaffWallet.objects.select_for_update().get( @@ -75,3 +86,97 @@ def confirm_dispatch_deduct(member, amount, order_id, operator_id): operator_id=operator_id, remark='派单扣额度', ) + + +def release_dispatch_quota(order_id, amount, operator_id, remark='退款释放额度'): + """订单撤销/退款:释放子客服已扣额度。""" + from merchant_ops.models import MerchantOrderDispatch, MerchantStaffMember + + amount = Decimal(str(amount)) + if amount <= 0 or not order_id: + return + + disp = MerchantOrderDispatch.objects.filter(order_id=order_id).first() + if not disp or not disp.staff_member_id: + return + + member = MerchantStaffMember.objects.filter(id=disp.staff_member_id).first() + if not member: + return + + with transaction.atomic(): + wallet = MerchantStaffWallet.objects.select_for_update().filter( + merchant_id=member.merchant_id, + staff_user_id=member.staff_user_id, + ).first() + if not wallet: + return + release = min(amount, wallet.quota_used) + if release <= 0: + return + wallet.quota_used = F('quota_used') - release + wallet.save(update_fields=['quota_used']) + wallet.refresh_from_db() + MerchantStaffWalletLedger.objects.create( + wallet=wallet, + merchant_id=member.merchant_id, + staff_user_id=member.staff_user_id, + change_type=WALLET_REFUND_RELEASE, + amount=release, + balance_after=wallet.quota_available, + related_order_id=order_id, + operator_id=operator_id or member.staff_user_id, + remark=remark, + ) + + +def is_staff_quota_order(order_id): + """订单是否由子客服额度出资(划额度池 + 派单扣额度,非老板余额直扣)。""" + from merchant_ops.models import MerchantOrderDispatch + + if not order_id: + return False + return MerchantOrderDispatch.objects.filter( + order_id=order_id, + staff_member_id__isnull=False, + ).exists() + + +def revoke_quota(member, amount, operator_id, remark=''): + """ + 收回未使用额度:客服 quota_total 减少,商家余额增加。 + 已派单占用(quota_used)部分不可收回,防止资金悬空或重复退回。 + """ + amount = Decimal(str(amount)) + if amount <= 0: + raise ValueError('收回额度必须大于0') + + with transaction.atomic(): + wallet = MerchantStaffWallet.objects.select_for_update().get( + merchant_id=member.merchant_id, + staff_user_id=member.staff_user_id, + ) + available = wallet.quota_total - wallet.quota_used - wallet.quota_frozen + if amount > available: + raise ValueError( + f'最多可收回{available}元(未使用额度);' + f'已派单占用{wallet.quota_used}元需先撤销/退款订单后再释放' + ) + + shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=member.merchant_id) + wallet.quota_total = F('quota_total') - amount + wallet.save(update_fields=['quota_total']) + wallet.refresh_from_db() + UserShangjia.objects.filter(id=shangjia.id).update(yue=F('yue') + amount) + + MerchantStaffWalletLedger.objects.create( + wallet=wallet, + merchant_id=member.merchant_id, + staff_user_id=member.staff_user_id, + change_type=WALLET_REVOKE, + amount=-amount, + balance_after=wallet.quota_available, + operator_id=operator_id, + remark=remark or '收回未使用额度', + ) + return wallet diff --git a/merchant_ops/sql/seed_permissions.sql b/merchant_ops/sql/seed_permissions.sql index 4026be4..0ed19ea 100644 --- a/merchant_ops/sql/seed_permissions.sql +++ b/merchant_ops/sql/seed_permissions.sql @@ -5,7 +5,8 @@ INSERT INTO merchant_staff_permission (perm_code, perm_name, sort_order, status, ('staff_manage', '子客服管理', 0, 1, NOW(), NOW()), ('role_manage', '角色管理', 1, 1, NOW(), NOW()), ('wallet_allocate', '划额度', 2, 1, NOW(), NOW()), -('wallet_view', '查看额度', 3, 1, NOW(), NOW()), +('wallet_revoke', '收回额度', 3, 1, NOW(), NOW()), +('wallet_view', '查看额度', 4, 1, NOW(), NOW()), ('finance_view', '查看商家余额', 4, 1, NOW(), NOW()), ('order_dispatch', '派单发单', 5, 1, NOW(), NOW()), ('order_view_all', '查看全部订单', 6, 1, NOW(), NOW()), @@ -18,7 +19,7 @@ INSERT INTO merchant_staff_permission (perm_code, perm_name, sort_order, status, ('penalty_apply', '申请罚单', 13, 1, NOW(), NOW()), ('penalty_manage', '改撤罚单', 14, 1, NOW(), NOW()), ('penalty_view', '查看罚单', 15, 1, NOW(), NOW()), -('dashou_ban', '封禁解封打手', 16, 1, NOW(), NOW()), +('staff_disable', '封禁解封客服', 16, 1, NOW(), NOW()), ('template_manage', '派单模板', 17, 1, NOW(), NOW()), ('audit_view', '操作日志', 18, 1, NOW(), NOW()), ('rank_view', '客服排行榜', 19, 1, NOW(), NOW()), diff --git a/merchant_ops/urls.py b/merchant_ops/urls.py index 309d8ef..c73fbca 100644 --- a/merchant_ops/urls.py +++ b/merchant_ops/urls.py @@ -5,7 +5,7 @@ from merchant_ops.views_manage import ( StaffMemberListView, StaffMemberUpdateRoleView, StaffMemberRemoveView, StaffMemberDisableView, StaffMemberUpdatePermsView, StaffRoleListView, StaffRoleCreateView, StaffRoleSetPermsView, - StaffWalletAllocateView, StaffWalletLedgerView, StaffAuditListView, + StaffWalletAllocateView, StaffWalletRevokeView, StaffWalletLedgerView, StaffAuditListView, StaffRankView, ) from merchant_ops.views_staff import StaffBindView, StaffMeView @@ -36,6 +36,7 @@ urlpatterns = [ path('role/create', StaffRoleCreateView.as_view()), path('role/set-perms', StaffRoleSetPermsView.as_view()), path('wallet/allocate', StaffWalletAllocateView.as_view()), + path('wallet/revoke', StaffWalletRevokeView.as_view()), path('wallet/ledger', StaffWalletLedgerView.as_view()), path('audit/list', StaffAuditListView.as_view()), path('rank', StaffRankView.as_view()), diff --git a/merchant_ops/views_manage.py b/merchant_ops/views_manage.py index 73906a9..131814a 100644 --- a/merchant_ops/views_manage.py +++ b/merchant_ops/views_manage.py @@ -28,7 +28,8 @@ from merchant_ops.services.authz import ( ) from merchant_ops.services.audit import audit_owner from merchant_ops.services.bootstrap import ensure_merchant_roles -from merchant_ops.services.wallet import allocate_quota, get_or_create_wallet +from merchant_ops.services.wallet import allocate_quota, get_or_create_wallet, revoke_quota +from users.models import UserShangjia logger = logging.getLogger(__name__) @@ -57,6 +58,32 @@ def _role_hint(role_code, tpl_desc=None): return f'自定义角色:{name}' +def _resolve_manage_context(request, need_staff_manage=False): + """商家老板,或具备 staff_manage 的总管等子客服。""" + if is_merchant_owner(request.user): + ensure_owner_merchant(request.user.UserUID, request.user) + return request.user.UserUID, None + member = get_active_staff_member(request.user) + if not member: + raise StaffAuthError(403, '无权限') + if need_staff_manage and not _member_has_perm(member, 'staff_manage'): + raise StaffAuthError(403, '无客服管理权限') + ensure_merchant_roles(member.merchant_id) + return member.merchant_id, member + + +def _resolve_wallet_operator(request, perm_code): + """商家老板,或具备 wallet_allocate / wallet_revoke 的子客服(总管/财务)。""" + if is_merchant_owner(request.user): + ensure_owner_merchant(request.user.UserUID, request.user) + return request.user.UserUID, None + member = get_active_staff_member(request.user) + if not member or not _member_has_perm(member, perm_code): + raise StaffAuthError(403, f'无{perm_code}权限') + ensure_merchant_roles(member.merchant_id) + return member.merchant_id, member + + class StaffInviteCreateView(APIView): permission_classes = [IsAuthenticated] @@ -142,8 +169,7 @@ class StaffMemberListView(APIView): from users.business_models import User from merchant_ops.services.order_enrich import user_avatar_relative - mid = request.user.UserUID - ensure_owner_merchant(mid, request.user) + mid, _ = _resolve_manage_context(request, need_staff_manage=True) today = date.today() members = MerchantStaffMember.query.filter( merchant_id=mid, @@ -197,17 +223,17 @@ class StaffMemberUpdateRoleView(APIView): def post(self, request): try: - ensure_owner_merchant(request.user.UserUID, request.user) + mid, _ = _resolve_manage_context(request, need_staff_manage=True) member_id = request.data.get('member_id') role_id = request.data.get('role_id') m = MerchantStaffMember.query.get( - id=member_id, merchant_id=request.user.UserUID, status=MEMBER_STATUS_ACTIVE, + id=member_id, merchant_id=mid, status=MEMBER_STATUS_ACTIVE, ) - role = MerchantStaffRole.query.get(id=role_id, merchant_id=request.user.UserUID, status=1) + role = MerchantStaffRole.query.get(id=role_id, merchant_id=mid, status=1) old = m.role_id m.role = role m.save(update_fields=['role']) - audit_owner(request.user.UserUID, request.user.UserUID, 'MEMBER_UPDATE_ROLE', request, + audit_owner(mid, request.user.UserUID, 'MEMBER_UPDATE_ROLE', request, resource_type='member', resource_id=member_id, value_before={'role_id': old}, value_after={'role_id': role_id}) perms = list(role.role_permissions.values_list('perm_code', flat=True)) @@ -245,14 +271,14 @@ class StaffMemberDisableView(APIView): def post(self, request): try: - ensure_owner_merchant(request.user.UserUID, request.user) + mid, _ = _resolve_manage_context(request, need_staff_manage=True) member_id = request.data.get('member_id') disable = request.data.get('disable', True) - m = MerchantStaffMember.query.get(id=member_id, merchant_id=request.user.UserUID) + m = MerchantStaffMember.query.get(id=member_id, merchant_id=mid) m.status = MEMBER_STATUS_DISABLED if disable else MEMBER_STATUS_ACTIVE m.save(update_fields=['status']) action = 'MEMBER_DISABLE' if disable else 'MEMBER_ENABLE' - audit_owner(request.user.UserUID, request.user.UserUID, action, request, resource_id=member_id) + audit_owner(mid, request.user.UserUID, action, request, resource_id=member_id) return Response({'code': 0, 'msg': '成功', 'data': None}) except Exception as e: return _err(e) @@ -375,15 +401,50 @@ class StaffWalletAllocateView(APIView): def post(self, request): try: - ensure_owner_merchant(request.user.UserUID, request.user) + mid, _ = _resolve_wallet_operator(request, 'wallet_allocate') member_id = request.data.get('member_id') amount = request.data.get('amount') - m = MerchantStaffMember.query.get(id=member_id, merchant_id=request.user.UserUID, status=MEMBER_STATUS_ACTIVE) + m = MerchantStaffMember.query.get( + id=member_id, merchant_id=mid, status=MEMBER_STATUS_ACTIVE, + ) get_or_create_wallet(m) - allocate_quota(m, amount, request.user.UserUID, remark='老板划额度') - audit_owner(request.user.UserUID, request.user.UserUID, 'WALLET_ALLOCATE', request, + wallet = allocate_quota(m, amount, request.user.UserUID, remark='划额度') + audit_owner(mid, request.user.UserUID, 'WALLET_ALLOCATE', request, resource_type='wallet', resource_id=member_id, amount=amount) - return Response({'code': 0, 'msg': '划额度成功', 'data': None}) + shangjia = UserShangjia.query.filter(user__UserUID=mid).first() + return Response({'code': 0, 'msg': '划额度成功', 'data': { + 'quota_available': str(wallet.quota_available), + 'merchant_yue': str(shangjia.yue) if shangjia else '0', + }}) + except ValueError as e: + return Response({'code': 400, 'msg': str(e), 'data': None}) + except Exception as e: + return _err(e) + + +class StaffWalletRevokeView(APIView): + """收回客服未使用额度,退回商家余额。""" + permission_classes = [IsAuthenticated] + + def post(self, request): + try: + mid, _ = _resolve_wallet_operator(request, 'wallet_revoke') + member_id = request.data.get('member_id') + amount = request.data.get('amount') + remark = (request.data.get('remark') or '').strip() or '收回未使用额度' + m = MerchantStaffMember.query.get( + id=member_id, + merchant_id=mid, + status__in=[MEMBER_STATUS_ACTIVE, MEMBER_STATUS_DISABLED], + ) + wallet = revoke_quota(m, amount, request.user.UserUID, remark=remark) + audit_owner(mid, request.user.UserUID, 'WALLET_REVOKE', request, + resource_type='wallet', resource_id=member_id, amount=amount) + shangjia = UserShangjia.query.filter(user__UserUID=mid).first() + return Response({'code': 0, 'msg': '收回成功', 'data': { + 'quota_available': str(wallet.quota_available), + 'merchant_yue': str(shangjia.yue) if shangjia else '0', + }}) except ValueError as e: return Response({'code': 400, 'msg': str(e), 'data': None}) except Exception as e: diff --git a/orders/views.py b/orders/views.py index a19b5ee..9b49f12 100644 --- a/orders/views.py +++ b/orders/views.py @@ -2585,8 +2585,8 @@ class ShangjiaChexiaoView(APIView): 'data': None }, status=status.HTTP_404_NOT_FOUND) - # 6. 验证订单状态 - if dingdan.Status not in [1, 7]: + # 6. 验证订单状态(13=链接待填单,可撤销释放额度) + if dingdan.Status not in [1, 7, 13]: return Response({ 'code': 6, 'msg': '当前订单状态不可撤销', @@ -2620,16 +2620,19 @@ class ShangjiaChexiaoView(APIView): ) - # 9. 更新商家扩展表数据(使用F表达式原子操作) - UserShangjia.query.filter(id=shangjia.id).update( - yue=F('yue') + dingdan.Amount, - tuikuan=F('tuikuan') + 1, - jinridingdan=F('jinridingdan') - 1, - jinyuedingdan=F('jinyuedingdan') - 1, - jinriliushui=F('jinriliushui') - dingdan.Amount, - jinyueliushui=F('jinyueliushui') - dingdan.Amount, - - ) + # 9. 更新商家扩展表(客服额度单不退商家余额,只释放额度) + from merchant_ops.services.wallet import is_staff_quota_order + staff_quota_order = is_staff_quota_order(dingdan_id) + shangjia_updates = { + 'tuikuan': F('tuikuan') + 1, + 'jinridingdan': F('jinridingdan') - 1, + 'jinyuedingdan': F('jinyuedingdan') - 1, + 'jinriliushui': F('jinriliushui') - dingdan.Amount, + 'jinyueliushui': F('jinyueliushui') - dingdan.Amount, + } + if not staff_quota_order: + shangjia_updates['yue'] = F('yue') + dingdan.Amount + UserShangjia.query.filter(id=shangjia.id).update(**shangjia_updates) # 10. 更新订单状态为5(已退款) dingdan.Status = 5 @@ -5351,8 +5354,9 @@ class AdTongYiTuiKuanShangJia(APIView): # ✅ 确认字段:tuikuan 退款订单总量 shangjia_profile.tuikuan += 1 - # ✅ 确认字段:yue 商家余额(加上订单金额) - shangjia_profile.yue += jine + from merchant_ops.services.wallet import is_staff_quota_order + if not is_staff_quota_order(dingdan_id): + shangjia_profile.yue += jine shangjia_profile.save() logger.info(f"同意退款成功,商家{shangjia_id}退款订单+1,余额增加:{jine}") diff --git a/users/views.py b/users/views.py index 4f61bd2..7ee6505 100644 --- a/users/views.py +++ b/users/views.py @@ -8114,12 +8114,14 @@ class KefuMerchantRefundView(APIView): except (User.DoesNotExist, ObjectDoesNotExist): logger.warning(f"商家退款:打手 {jiedan_dashou_id} 不存在,跳过更新") - # 更新商家 + # 更新商家(客服额度出资的订单不退商家余额,仅释放子客服额度) try: + from merchant_ops.services.wallet import is_staff_quota_order shangjia_user = User.query.get(UserUID=shangjia_id) shangjia_profile = shangjia_user.ShopProfile shangjia_profile.tuikuan += 1 - shangjia_profile.yue += jine + if not is_staff_quota_order(dingdan_id): + shangjia_profile.yue += jine shangjia_profile.save() except (User.DoesNotExist, ObjectDoesNotExist): logger.warning(f"商家退款:商家 {shangjia_id} 不存在,跳过更新")