"""商家订单操作流水:真实操作人落库(支持同一订单多次操作)。""" import logging from decimal import Decimal, InvalidOperation from merchant_ops.constants import ( ACTOR_MERCHANT, ACTOR_STAFF, ORDER_ACTION_LABELS, ) from merchant_ops.models import MerchantOrderActionLog logger = logging.getLogger(__name__) def _to_decimal(amount): if amount is None or amount == '': return None try: return Decimal(str(amount)) except (InvalidOperation, TypeError, ValueError): return None def _client_ip(request): if request is None: return '' xff = request.META.get('HTTP_X_FORWARDED_FOR') if xff: return xff.split(',')[0].strip() return request.META.get('REMOTE_ADDR', '') or '' def resolve_operator_from_request(request, merchant_id): """ 优先 request._staff_member(子客服代理);否则记为商家本人。 返回 dict: operator_user_id/operator_type/staff_member_id/role_code/role_name """ mid = str(merchant_id or '').strip() staff_member = getattr(request, '_staff_member', None) if request is not None else None if staff_member is not None: role = getattr(staff_member, 'role', None) return { 'operator_user_id': str(getattr(staff_member, 'staff_user_id', '') or ''), 'operator_type': ACTOR_STAFF, 'staff_member_id': getattr(staff_member, 'id', None), 'role_code': getattr(role, 'role_code', '') or '', 'role_name': getattr(role, 'role_name', '') or '', } # 商家本人:代理场景下 request.user 已是商家 uid = '' if request is not None and getattr(request, 'user', None) is not None: uid = str(getattr(request.user, 'UserUID', '') or '') if not uid: uid = mid return { 'operator_user_id': uid, 'operator_type': ACTOR_MERCHANT, 'staff_member_id': None, 'role_code': 'OWNER', 'role_name': '商家本人', } def record_order_action( *, merchant_id, order_id, action_code, operator_user_id, operator_type, staff_member_id=None, role_code='', role_name='', amount=None, leixing_id=None, remark='', club_id='xq', request=None, ): """写入一条操作流水。失败只打日志,不影响主业务。""" try: oid = str(order_id or '').strip() mid = str(merchant_id or '').strip() if not oid or not mid or not operator_user_id: logger.warning( '操作流水跳过:参数不完整 merchant=%s order=%s action=%s operator=%s', mid, oid, action_code, operator_user_id, ) return None code = int(action_code) row = MerchantOrderActionLog.objects.create( club_id=(club_id or 'xq')[:16], merchant_id=mid[:7], order_id=oid[:32], action_code=code, operator_user_id=str(operator_user_id)[:7], operator_type=(operator_type or ACTOR_MERCHANT)[:16], staff_member_id=staff_member_id, role_code=(role_code or '')[:32], role_name=(role_name or '')[:64], amount=_to_decimal(amount), leixing_id=int(leixing_id) if leixing_id not in (None, '') else None, remark=(remark or '')[:500], ) logger.info( '【商家操作流水】写入成功 id=%s merchant=%s order=%s action=%s(%s) ' 'operator=%s type=%s role=%s amount=%s leixing=%s ip=%s', row.id, mid, oid, code, ORDER_ACTION_LABELS.get(code, '?'), operator_user_id, operator_type, role_code or role_name, amount, leixing_id, _client_ip(request), ) return row except Exception: logger.error( '【商家操作流水】写入失败 merchant=%s order=%s action=%s', merchant_id, order_id, action_code, exc_info=True, ) return None def record_order_action_from_request( request, action_code, order_id, *, merchant_id=None, amount=None, leixing_id=None, remark='', club_id='xq', ): """从 request(含 _staff_member)自动识别操作人并落库。""" mid = str(merchant_id or '').strip() if not mid and request is not None and getattr(request, 'user', None) is not None: mid = str(getattr(request.user, 'UserUID', '') or '') op = resolve_operator_from_request(request, mid) return record_order_action( merchant_id=mid, order_id=order_id, action_code=action_code, operator_user_id=op['operator_user_id'], operator_type=op['operator_type'], staff_member_id=op.get('staff_member_id'), role_code=op.get('role_code', ''), role_name=op.get('role_name', ''), amount=amount, leixing_id=leixing_id, remark=remark, club_id=club_id, request=request, ) def leixing_id_from_order(order): if order is None: return None return getattr(order, 'ProductTypeID', None) or getattr(order, 'leixing_id', None)