diff --git a/orders/views/dashou.py b/orders/views/dashou.py index 119d11d..8332203 100644 --- a/orders/views/dashou.py +++ b/orders/views/dashou.py @@ -797,6 +797,11 @@ class DashouDingdanHuoquView1(APIView): if wants_auto_settle_pending(data): query &= Q(Status=8, AutoSettleEligible=True, AutoExpireAt__isnull=False) + # 佣金记录页:with_my_share=1 时追加本人入账额(不改 jine 语义) + with_my_share = str(data.get('with_my_share') or '').strip().lower() in ( + '1', 'true', 'yes', 'y', + ) + # 5. 执行查询,按创建时间倒序 dingdan_query = Order.query.filter(query).order_by('-CreateTime') @@ -826,12 +831,18 @@ class DashouDingdanHuoquView1(APIView): from orders.services.team_recruit import batch_team_list_flags page_oids = [o.OrderID for o in current_page_orders] team_flags = batch_team_list_flags(page_oids, viewer_uid=yonghuid) + + settle_map = {} + if with_my_share and page_oids: + settle_map = self._batch_my_settle_amounts(page_oids, yonghuid) + order_list = [] for order in current_page_orders: + full_amt = float(order.PlayerCommission) if order.PlayerCommission else 0.0 order_data = { 'dingdan_id': order.OrderID, 'zhuangtai': order.Status, - 'jine': float(order.PlayerCommission) if order.PlayerCommission else 0.0, + 'jine': full_amt, 'jieshao': order.Description or '', 'tupian': order.ImageURL or '', 'nicheng': order.Nickname or '', @@ -840,6 +851,15 @@ class DashouDingdanHuoquView1(APIView): order_data.update(order_auto_settle_payload(order)) tf = team_flags.get(order.OrderID) or {'has_team': False} order_data.update(tf) + if with_my_share: + is_team = bool(tf.get('has_team')) + my_amt = settle_map.get(order.OrderID) + if my_amt is None: + my_amt = full_amt + order_data['my_settle_amount'] = float(my_amt) + order_data['is_team'] = is_team + if 'team_role' not in order_data: + order_data['team_role'] = tf.get('team_role') or '' order_list.append(order_data) # 8. 判断是否还有更多数据 @@ -878,6 +898,53 @@ class DashouDingdanHuoquView1(APIView): 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + @staticmethod + def _batch_my_settle_amounts(order_ids, user_uid): + """ + 本人实际入账额:优先 team_settle_ledger;否则 TeamMember.share_amount; + 都没有则由调用方回落 PlayerCommission。 + 返回 {order_id: float} + """ + from decimal import Decimal + from orders.models import TeamSettleLedger, TeamRecruit, TeamMember + + oids = [str(x) for x in (order_ids or []) if x] + uid = (user_uid or '').strip() + if not oids or not uid: + return {} + + out = {} + try: + for row in TeamSettleLedger.query.filter(order_id__in=oids, user_uid=uid): + oid = row.order_id + amt = Decimal(str(row.amount or 0)) + out[oid] = float(out.get(oid, 0) + float(amt)) + except Exception as e: + logger.warning('batch settle ledger 失败: %s', e) + + missing = [oid for oid in oids if oid not in out] + if not missing: + return out + + try: + recs = list(TeamRecruit.query.filter(order_id__in=missing).order_by('-id')) + best = {} + for r in recs: + if r.order_id not in best: + best[r.order_id] = r + if best: + rid_to_oid = {r.id: oid for oid, r in best.items()} + for m in TeamMember.query.filter( + recruit_id__in=list(rid_to_oid.keys()), + user_uid=uid, + join_status=TeamMember.JOIN_ACTIVE, + ): + oid = rid_to_oid.get(m.recruit_id) + if oid and oid not in out: + out[oid] = float(Decimal(str(m.share_amount or 0))) + except Exception as e: + logger.warning('batch member share 失败: %s', e) + return out class DashouCOSZhengshuView(APIView): diff --git a/products/urls.py b/products/urls.py index aac3327..da189c3 100644 --- a/products/urls.py +++ b/products/urls.py @@ -4,7 +4,7 @@ from django.views.decorators.csrf import csrf_exempt from .views import ShangpinHuoquView, ShangpinXiangqingHuoquView, DashouHuiyuanList, \ YajinGoumai, YajinHuitiao, YajinShibai, YajinLunxun, JifenBuchong, JifenHuitiao, JifenShibai, \ JifenLunxun, HuiyuanGoumai, HuiyuanHuitiao, HuiyuanShibai, HuiyuanLunxun, ShangjiaChenggong, \ - ShangjiaChongzhi, ShangjiaHuitiao, ShangjiaShibai, GuanshiFenhongListAPI, AdGetAllDataView, ShangpinLeixingFabuView, \ + ShangjiaChongzhi, ShangjiaHuitiao, ShangjiaShibai, GuanshiFenhongListAPI, GuanshiFenhongListV2API, AdGetAllDataView, ShangpinLeixingFabuView, \ ShangpinZhuanquFabuView, ShangpinFabuView, GuizeTupianShangchuanView, AdHuiyuanHqView, HuiyuanXiugaiView, \ AdHuiyuanFbView, AdShangpinHuQu, AdLeixingXiuGai, AdLunboXiuGai, AdZhuanquXiuGai, AdGonggaoXiuGai, \ AdShangpinGuizeXiuGai, AdShangpinXiuGai, AdShangpinNeiRongHuoQu, \ @@ -56,6 +56,7 @@ urlpatterns = [ path('sjshibai', ShangjiaShibai.as_view(), name='商家支付失败'), # 商家支付失败处理 path('sjcg', ShangjiaChenggong.as_view(), name='商家支付成功'), # 商家支付成功轮询 path('fenhonghq', GuanshiFenhongListAPI.as_view(), name='管事获取打手充值记录'), + path('guanshi-fenhong-v2', GuanshiFenhongListV2API.as_view(), name='管事分红明细v2'), path('adhqlx', AdGetAllDataView.as_view(), name='管理员获取商品类型会员类型专区类型'), path('leixingfabu', ShangpinLeixingFabuView.as_view(), name='添加商品类型'), # 商品专区相关接口 diff --git a/products/views/__init__.py b/products/views/__init__.py index 535335b..6904f93 100644 --- a/products/views/__init__.py +++ b/products/views/__init__.py @@ -21,6 +21,7 @@ from .product_query import ( ShangpinXiangqingHuoquView, DashouHuiyuanList, GuanshiFenhongListAPI, + GuanshiFenhongListV2API, AdGetAllDataView, HuiyuanTypeListView, HuiyuanRecordListView, @@ -97,7 +98,7 @@ logger = logging.getLogger(__name__) __all__ = [ # product_query 'ShangpinHuoquView', 'ShangpinXiangqingHuoquView', 'DashouHuiyuanList', - 'GuanshiFenhongListAPI', 'AdGetAllDataView', + 'GuanshiFenhongListAPI', 'GuanshiFenhongListV2API', 'AdGetAllDataView', 'HuiyuanTypeListView', 'HuiyuanRecordListView', 'ZuzhangFenhongView', # yajin_payment 'YajinGoumai', 'YajinHuitiao', 'YajinShibai', 'YajinLunxun', diff --git a/products/views/product_query.py b/products/views/product_query.py index eee7539..51dc303 100644 --- a/products/views/product_query.py +++ b/products/views/product_query.py @@ -457,6 +457,119 @@ class GuanshiFenhongListAPI(APIView): }) +FENHONG_LEIXING_LABELS = { + 1: '会员分红', + 2: '押金分红', + 3: '订单分佣', +} + + +class GuanshiFenhongListV2API(APIView): + """ + 管事分红明细 v2(旧 fenhonghq 不动)。 + POST /shangpin/guanshi-fenhong-v2 + - 列表带类型/说明/订单号/双时间字段 + - 可选 fenhong_leixing 筛选;分项统计始终按管事全量汇总 + """ + permission_classes = [IsAuthenticated] + + def post(self, request): + try: + from django.db.models import Sum, Count + + yonghuid = request.user.UserUID + page = int(request.data.get('page', 1) or 1) + page_size = int(request.data.get('page_size', 50) or 50) + if page < 1: + page = 1 + if page_size < 1 or page_size > 100: + page_size = 50 + + leixing_raw = request.data.get('fenhong_leixing', None) + leixing_filter = None + if leixing_raw is not None and str(leixing_raw).strip() != '': + try: + leixing_filter = int(leixing_raw) + except (TypeError, ValueError): + return Response({'code': 400, 'msg': 'fenhong_leixing 无效', 'data': {}}) + if leixing_filter not in FENHONG_LEIXING_LABELS: + return Response({'code': 400, 'msg': 'fenhong_leixing 无效', 'data': {}}) + + base_qs = Gsfenhong.objects.filter(guanshi=yonghuid) + + # 分项统计:始终全量(不被列表筛选缩小) + amount_by = {'1': '0.00', '2': '0.00', '3': '0.00'} + count_by = {'1': 0, '2': 0, '3': 0} + total_amount = Decimal('0.00') + for row in base_qs.values('fenhong_leixing').annotate( + amount=Sum('fenhong'), cnt=Count('id'), + ): + key = str(int(row.get('fenhong_leixing') or 1)) + amt = Decimal(str(row.get('amount') or 0)).quantize(Decimal('0.01')) + cnt = int(row.get('cnt') or 0) + if key in amount_by: + amount_by[key] = str(amt) + count_by[key] = cnt + total_amount += amt + + list_qs = base_qs + if leixing_filter is not None: + list_qs = list_qs.filter(fenhong_leixing=leixing_filter) + + zong_tiaoshu = list_qs.count() + chongzhi_dashou_shuliang = base_qs.values('dashouid').distinct().count() + offset = (page - 1) * page_size + rows = list( + list_qs.order_by('-CreateTime') + .values( + 'dingdan_id', 'dashouid', 'avatar', 'nicheng', 'fenhong', + 'shuoming', 'fenhong_leixing', 'CreateTime', + )[offset:offset + page_size] + ) + + formatted_list = [] + for item in rows: + leixing = int(item.get('fenhong_leixing') or 1) + ts = '' + if item.get('CreateTime'): + ts = item['CreateTime'].strftime('%Y-%m-%d %H:%M:%S') + formatted_list.append({ + 'yonghuid': item.get('dashouid') or '', + 'avatar': item.get('avatar') or '', + 'nicheng': item.get('nicheng') or '未知用户', + 'fenhong': str(item.get('fenhong') or '0'), + 'CreateTime': ts, + 'create_time': ts, + 'fenhong_leixing': leixing, + 'leixing_label': FENHONG_LEIXING_LABELS.get(leixing, '分红'), + 'shuoming': (item.get('shuoming') or '').strip(), + 'dingdan_id': item.get('dingdan_id') or '', + }) + + return Response({ + 'code': 200, + 'msg': '成功', + 'data': { + 'list': formatted_list, + 'total': zong_tiaoshu, + 'chongzhi_dashou_shuliang': chongzhi_dashou_shuliang, + 'page': page, + 'page_size': page_size, + 'has_more': len(formatted_list) == page_size, + 'total_amount': str(total_amount.quantize(Decimal('0.01'))), + 'amount_by_leixing': amount_by, + 'count_by_leixing': count_by, + }, + }) + except Exception as e: + logger.error('查询管事分红记录 v2 失败: %s', e, exc_info=True) + return Response({ + 'code': 500, + 'msg': f'服务器错误: {str(e)}', + 'data': {}, + }) + + # views.py - 商品模块视图 class AdGetAllDataView(APIView): """