第二轮:客服额度资金闭环与收回额度
- 划额度扣商家余额,客服派单/链接只扣个人额度 - 新增 wallet/revoke 收回未使用额度,权限 wallet_revoke - 客服额度单撤销/退款只释放额度,不再误退商家余额 - 链接待填单(13)可撤销;总管/财务可划收额度
This commit is contained in:
@@ -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': '更换客服角色',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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} 不存在,跳过更新")
|
||||
|
||||
Reference in New Issue
Block a user