From 9eb53ee57da951a94150a83e31b0f73cf5dd614e Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 15 Jun 2026 02:09:51 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B4=AF=E6=AD=BB=E4=BA=86=EF=BC=8C=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E4=BA=86=E6=89=80=E6=9C=89=E8=B7=A8=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E8=BF=9B=E8=A1=8C=E4=BA=86=E8=A1=A8?= =?UTF-8?q?=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- a_long_dianjing/settings.py | 14 +- backend/migrations/0001_initial.py | 6 +- ...zhangritongji_shangjiaritongji_and_more.py | 2 +- backend/migrations/0003_tixianritongji.py | 2 +- ...lter_rolepermission_permission_and_more.py | 34 + backend/urls.py | 38 +- backend/view.py | 3900 +---------------- .../0002_shangjiamoban_shangjialianjie.py | 2 +- .../0003_tupianpeizhi_fangwen_cishu.py | 2 +- config/migrations/0004_tixianquotadefault.py | 2 +- ...ordertypemapping_dailydispatchstat_club.py | 2 +- .../0006_clubconfig_accountpermission.py | 2 +- ...ilydispatchstat_claimed_amount_and_more.py | 2 +- .../0008_clubconfig_storage_bucket_name.py | 2 +- .../0009_dailypayoutstat_dailyincomestat.py | 2 +- ...ge_popupimage_popupconfig_page_and_more.py | 6 +- config/migrations/0011_withdrawconfig.py | 2 +- ...0012_shangjiamoban_chenghao_id_and_more.py | 2 +- ...jialianjie_previous_dingdan_id_and_more.py | 2 +- ..._delete_club_delete_clubconfig_and_more.py | 30 + config/models.py | 152 - config/views.py | 86 +- fix_migration_history.py | 35 + orders/cross_platform_tasks.py | 38 - orders/migrations/0001_initial.py | 4 +- ...dan_auto_expire_at_dingdan_auto_task_id.py | 2 +- ..._pending_dispatch_dingdan_status_8_time.py | 2 +- .../0004_alter_tuikuanjilu_chuliid.py | 2 +- .../0005_alter_chufajilu_chuliid.py | 2 +- orders/migrations/0006_chufatupian.py | 2 +- orders/migrations/0007_chufajilu_ssliyou.py | 2 +- ...ngdan_is_cross_dingdan_partner_order_id.py | 2 +- ...ingtai_clkf_dingdanpingtai_jjjs_biaoshi.py | 2 +- ...remove_dingdanpingtai_clkf_dingdan_clkf.py | 2 +- orders/migrations/0011_dingdan_tkly.py | 2 +- ..._dingdan_wechat_transaction_id_and_more.py | 2 +- .../0013_alter_chufajilu_qingqiuid.py | 2 +- ...n_dispatch_type_dingdan_partner_club_id.py | 2 +- ...esettlement_orderdashouhistory_and_more.py | 2 +- ...alter_presettlement_dingdan_id_and_more.py | 2 +- .../0017_dingdanpingtai_dianpu_id_and_more.py | 2 +- .../0018_dingdanshangjia_partner_yonghu_id.py | 2 +- ...ngdanpingtai_shangji_dianpu_id_and_more.py | 2 +- orders/migrations/0020_fadanfenhong_fadan.py | 2 +- orders/migrations/0021_fadanshensutupian.py | 4 +- ...rtner_club_id_fadan_shenqingren_shenfen.py | 2 +- .../0023_fadanshensutupian_yongtu.py | 2 +- ...4_fadan_chulizhe_fadan_chulizhe_shenfen.py | 2 +- .../0025_alter_fadan_shenqingren_shenfen.py | 2 +- ...lter_fadan_shenqingren_shenfen_and_more.py | 2 +- orders/migrations/0027_dingdanpeihu.py | 2 +- ...28_dingdanshangjia_shangyige_dingdan_id.py | 2 +- .../0029_fadanfenhong_is_individual_rate.py | 2 +- .../0030_fadan_shenqingren_fenhong_jine.py | 2 +- .../0031_dingdan_guanshi_fencheng.py | 2 +- ...orderdata_delete_presettlement_and_more.py | 43 + orders/models.py | 93 - orders/signals.py | 2 +- orders/tasks.py | 8 +- orders/urls.py | 8 +- orders/utils.py | 224 +- orders/views.py | 1061 +---- ..._huiyuangoumai_unique_together_and_more.py | 2 +- ...in_paixu_shangpinleixing_paixu_and_more.py | 2 +- .../0004_shangpin_shenhezhuangtai_and_more.py | 2 +- .../0005_alter_gsfenhong_dashouid.py | 2 +- ...iyuan_id_huiyuan_zuzhangfc_duocifenhong.py | 4 +- ...duoci_fenho_guanshi_b3de53_idx_and_more.py | 2 +- ...ng_zuzhang_fenhong_gsfenhong_zuzhang_id.py | 2 +- ..._shangpin_ewai_dashou_fencheng_and_more.py | 2 +- .../0010_shangpinleixing_is_cross_enabled.py | 2 +- ...dianpu_shangpin_dianpu_leixing_and_more.py | 12 +- .../migrations/0012_shangpinmeiritongji.py | 4 +- .../0013_shangpinleixing_bankuai.py | 6 +- products/migrations/0014_huiyuan_bankuai.py | 6 +- .../0015_gsfenhong_fenhong_leixing.py | 2 +- ...remove_shangpinleixing_is_cross_enabled.py | 17 + products/models.py | 1 - rank/migrations/0001_initial.py | 12 +- .../0002_alter_dashoubiaoxian_dingdan.py | 6 +- .../0003_alter_dashoubiaoxian_bankuai.py | 4 +- rank/migrations/0004_dingdanbiaoqian.py | 4 +- rank/migrations/0005_fix_state.py | 8 +- rank/migrations/0006_shenhejilu.py | 6 +- rank/migrations/0007_kaoheguanbankuai.py | 4 +- ...guize_shenhejilu_dashou_beizhu_and_more.py | 2 +- ...inpai_alter_shenhejilu_jilu_id_and_more.py | 6 +- rank/migrations/0010_kaohejilufeiyong.py | 4 +- rank/migrations/0011_kaohepaytemp.py | 2 +- shop/migrations/0001_initial.py | 10 +- .../0002_alter_yonghupingzheng_yonghu.py | 2 +- ...eizhi_dianpu_kaiqi_meiri_xiane_and_more.py | 2 +- .../0004_dianpushangpinshenheshezhi.py | 2 +- ...dianpu_fenhong_choucheng_feilv_and_more.py | 2 +- ...imeiritongji_gei_shangji_cishu_and_more.py | 2 +- shop/models.py | 2 +- shop/views.py | 395 +- .../migrations/0002_adminprofile_yaoqingma.py | 2 +- users/migrations/0003_rankingrecord.py | 2 +- .../migrations/0004_alter_usermain_avatar.py | 2 +- ...05_officialaccountuser_usermain_unionid.py | 2 +- .../0006_alter_officialaccountuser_unionid.py | 2 +- users/migrations/0007_userkefu.py | 2 +- ...0008_userguanshi_fenghong_erci_and_more.py | 2 +- ...ne_userdashou_jinritixian_jine_and_more.py | 2 +- .../0010_userguanshi_invite_qrcode_url.py | 2 +- .../0011_usermain_shoujihao_renzheng.py | 2 +- users/migrations/0012_usershenheguan.py | 2 +- .../0013_usershenheguan_is_renzheng.py | 2 +- ...ianautorecord_tixianshenhejilu_and_more.py | 2 +- users/views.py | 44 +- utils/chat_utils.py | 193 +- 112 files changed, 468 insertions(+), 6208 deletions(-) create mode 100644 backend/migrations/0004_alter_rolepermission_permission_and_more.py create mode 100644 config/migrations/0014_delete_club_delete_clubconfig_and_more.py create mode 100644 fix_migration_history.py delete mode 100644 orders/cross_platform_tasks.py create mode 100644 orders/migrations/0032_delete_crossplatformorderdata_delete_presettlement_and_more.py create mode 100644 products/migrations/0016_remove_shangpinleixing_is_cross_enabled.py diff --git a/a_long_dianjing/settings.py b/a_long_dianjing/settings.py index 5728cb2..36df169 100644 --- a/a_long_dianjing/settings.py +++ b/a_long_dianjing/settings.py @@ -301,19 +301,7 @@ MONTHLY_TASK_HOUR = 0 MONTHLY_TASK_MINUTE = 0 ORDER_CHECK_INTERVAL = 5 -# ==================== 跨平台订单互通 ==================== -CROSS_PLATFORM_TOKEN = app_secrets.CROSS_PLATFORM_TOKEN -CROSS_PLATFORM_SYNC_TOKEN = app_secrets.CROSS_PLATFORM_SYNC_TOKEN -PARTNER_BASE_URL = app_secrets.PARTNER_BASE_URL -PARTNER_SYNC_ORDER_URL = '/dingdan/partner_sync_order' -PARTNER_CHECK_CLAIM_URL = '/dingdan/partner_check_claim' -PARTNER_NOTIFY_CLAIM_URL = '/dingdan/partner_notify_claim' -PARTNER_REFUND_NOTIFY_URL = '/dingdan/partner_refund_notify' -AGENT_SHANGJIA_ID = app_secrets.AGENT_SHANGJIA_ID -CROSS_PLATFORM_MEMBER_ID = app_secrets.CROSS_PLATFORM_MEMBER_ID -CROSS_PLATFORM_GAME_TYPE_ID = 1 -CROSS_PLATFORM_WHITELIST = app_secrets.CROSS_PLATFORM_WHITELIST -SPECIAL_GUANSHI_IDS = app_secrets.SPECIAL_GUANSHI_IDS + # ==================== 微信支付 V3 ==================== WECHAT_PAY_V3_CONFIG = { diff --git a/backend/migrations/0001_initial.py b/backend/migrations/0001_initial.py index e71b4e2..d065534 100644 --- a/backend/migrations/0001_initial.py +++ b/backend/migrations/0001_initial.py @@ -215,7 +215,7 @@ class Migration(migrations.Migration): "role", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - to="houtai.role", + to="backend.role", verbose_name="角色", ), ), @@ -238,7 +238,7 @@ class Migration(migrations.Migration): "permission", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - to="houtai.permission", + to="backend.permission", verbose_name="权限", ), ), @@ -246,7 +246,7 @@ class Migration(migrations.Migration): "role", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - to="houtai.role", + to="backend.role", verbose_name="角色", ), ), diff --git a/backend/migrations/0002_zuzhangritongji_shangjiaritongji_and_more.py b/backend/migrations/0002_zuzhangritongji_shangjiaritongji_and_more.py index 6284dd8..fa5cae7 100644 --- a/backend/migrations/0002_zuzhangritongji_shangjiaritongji_and_more.py +++ b/backend/migrations/0002_zuzhangritongji_shangjiaritongji_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ("houtai", "0001_initial"), + ("backend", "0001_initial"), ] operations = [ diff --git a/backend/migrations/0003_tixianritongji.py b/backend/migrations/0003_tixianritongji.py index 8149a08..40d140c 100644 --- a/backend/migrations/0003_tixianritongji.py +++ b/backend/migrations/0003_tixianritongji.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ("houtai", "0002_zuzhangritongji_shangjiaritongji_and_more"), + ("backend", "0002_zuzhangritongji_shangjiaritongji_and_more"), ] operations = [ diff --git a/backend/migrations/0004_alter_rolepermission_permission_and_more.py b/backend/migrations/0004_alter_rolepermission_permission_and_more.py new file mode 100644 index 0000000..faa7b49 --- /dev/null +++ b/backend/migrations/0004_alter_rolepermission_permission_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.27 on 2026-06-15 01:42 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('backend', '0003_tixianritongji'), + ] + + operations = [ + migrations.AlterField( + model_name='rolepermission', + name='permission', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.permission', verbose_name='权限'), + ), + migrations.AlterField( + model_name='rolepermission', + name='role', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.role', verbose_name='角色'), + ), + migrations.AlterField( + model_name='tixianritongji', + name='leixing', + field=models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '组长'), (4, '考核官'), (5, '打手押金'), (6, '商家余额')], verbose_name='用户类型'), + ), + migrations.AlterField( + model_name='userrole', + name='role', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.role', verbose_name='角色'), + ), + ] diff --git a/backend/urls.py b/backend/urls.py index 700f7b5..4744987 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -1,11 +1,7 @@ from django.urls import path from django.views.decorators.csrf import csrf_exempt -from .view import (GetClubConfigView, GetCrossOrderListView, PartnerGetOrderDataView,PartnerRefundNotifyView, - CrossPlatformRefundView, GetCrossOrderDetailView, CrossPlatformForceCompleteView, CrossPlatformTransferHallView, - PartnerTransferHallNotifyView, GetSettingsView, UpdateSettingsView, GetMappingsView, UpdateMappingsView, - PartnerUpdateSelfConfigView, GetPartnerClubsView, GetStatsView, PunishDashouView, PartnerPunishNotifyView, - GetClubConfigForPreSettlementView, PreSettlementActionView, GetPreSettlementDataView, GetRolePermissionView, +from .view import (GetRolePermissionView, ModifyRolePermissionView, AddRoleView, GetAdminRolesView, GetAdminUserListView, AddAdminUserView, ModifyAdminUserView, KefuGetDashouListView, KefuGetDashouDetailView, KefuUpdateDashouView, KefuGetShangjiaListView, KefuGetShangjiaDetailView, KefuUpdateShangjiaView, GetProductBaseDataView, GetProductListView, SaveProductOrderView, @@ -15,34 +11,11 @@ from .view import (GetClubConfigView, GetCrossOrderListView, PartnerGetOrderData ModifyProductTypeZoneView, GetRateView, ModifyRateView, PopupNoticeListAPIView, PopupNoticeModifyAPIView,ShopListView, ShopModifyView, ShopPublicTypeAndAuditView, ShopListForProductView, ShopProductModifyView, ShopProductTypeMappingView, ProductListView, ProductModifyView, KefuChatPermissionsView, - PartnerFineNotifyView,FineApplyView, FaKuanTongJiView, FaKuanLieBiaoView, FaKuanChuLiView, FaKuanChuangJianView, + FaKuanTongJiView, FaKuanLieBiaoView, FaKuanChuLiView, FaKuanChuangJianView, HqbkxxView,BkxgView,CaiwuView,CwhybkhqView, HybkjtsjView, SzxxView, CwddhqlxView, CwhqjtddsjView, - CwqtczhqView, KhpzhqView, ChzsgcView,KhgglView, ShgxgsjView, ZxkfghdsView, KptxwztjbView) + CwqtczhqView, KhpzhqView, ChzsgcView,KhgglView, ShgxgsjView, ZxkfghdsView) urlpatterns = [ - path('kpthq', GetClubConfigView.as_view(), name='跨平台订单页面获取俱乐部数据'), - path('hqddlb', GetCrossOrderListView.as_view(), name='获取跨平台订单'), - path('kpthqtp',csrf_exempt(PartnerGetOrderDataView.as_view()), name='跨平台接受对方服务器获取订单数据'), - # 对方平台退款通知接口 - path('dfddtk', csrf_exempt(PartnerRefundNotifyView.as_view()), name='跨平台接受对方服务器更新订单状态'), - path('kptk', CrossPlatformRefundView.as_view(), name='跨平台订单退款'), - path('hqkptddxq', GetCrossOrderDetailView.as_view(), name='获取跨平台订单数据详情'), - path('kpqzjd', CrossPlatformForceCompleteView.as_view(), name='跨平台订单结算'), - path('kpzydt', CrossPlatformTransferHallView.as_view(), name='跨平台转移大厅'), - path('kpttzzydt', csrf_exempt(PartnerTransferHallNotifyView.as_view()), name='跨平台接受对方转移大厅通知'), - path('hqkptsz', GetSettingsView.as_view(), name='获取俱乐部数据信息'), - path('xgjlbsj', UpdateSettingsView.as_view(), name='更新俱乐部数据信息'), - path('hqqtjlbsj', GetMappingsView.as_view(), name='获取俱乐部订单类型映射'), - path('xgdfddys', UpdateMappingsView.as_view(), name='工薪俱乐部订单类型映射'), - path('xgzjsjcs', csrf_exempt(PartnerUpdateSelfConfigView.as_view()), name='partner_update_self_config'), - path('kpthqpt', GetPartnerClubsView.as_view(), name='前端获取俱乐部数据展示派发数据'), - path('hqpfsj', GetStatsView.as_view(), name='前端派发统计表获取'), - path('kpcf', PunishDashouView.as_view(), name='跨平台处罚打手'), - path('kptcfds', PartnerPunishNotifyView.as_view(), name='接受跨平台对方平台处罚申请'), - path('hqkttjsyjs', GetClubConfigForPreSettlementView.as_view(), name='预处理获取俱乐部数据'), - path('hqyjssj', GetPreSettlementDataView.as_view(), name='获取预处理数据'), - path('yjsjjhdk', PreSettlementActionView.as_view(), name='操作预处理'), - path('gljshq', GetRolePermissionView.as_view(), name='获取就角色信息'), path('sctjjsnr', ModifyRolePermissionView.as_view(), name='修改角色数据'), path('tjjs', AddRoleView.as_view(), name='添加角色'), @@ -101,9 +74,6 @@ urlpatterns = [ path('kfhqltqx', KefuChatPermissionsView.as_view(), name='后台获取客服聊天配置'), - path('kffkdssq', FineApplyView.as_view(), name='跨平台处罚罚款申请接口'), - path('kffkdssq_notify', PartnerFineNotifyView.as_view(), name='跨屏要对方平台处罚通知接口'), - path('htglyhqcfsltj', FaKuanTongJiView.as_view(),name='后台罚单统计图片'), # 罚款统计 path('hthqfklb', FaKuanLieBiaoView.as_view(),name='后台罚单列表'), # 罚款列表 @@ -130,8 +100,6 @@ urlpatterns = [ path('zxkfghds', ZxkfghdsView.as_view(), name='链接换打手或重新下单'), - path('kptxwztjb', csrf_exempt(KptxwztjbView.as_view()), name='跨平台脚本询问订单状态'), - ] \ No newline at end of file diff --git a/backend/view.py b/backend/view.py index 9425236..cb063e3 100644 --- a/backend/view.py +++ b/backend/view.py @@ -37,7 +37,7 @@ from backend.utils import ( update_dashou_daily_by_action, update_shangjia_daily ) -from orders.utils import update_daily_dispatch_stat, update_daily_payout, sync_order_to_partners +from orders.utils import update_daily_payout from shop.utils import update_dianpu_daily_stat from products.utils import update_shangpin_daily_stat from orders.notice_tasks import dingdan_guangbo @@ -55,8 +55,8 @@ from users.models import ( ## orders from orders.models import ( - Lilubiao, Dashoutupian, Chufajilu, PreSettlement, - OrderDashouHistory, Dingdan, Tuikuanjilu, CrossPlatformOrderData, + Lilubiao, Dashoutupian, Chufajilu, + OrderDashouHistory, Dingdan, Tuikuanjilu, DingdanShangjia, Fadan, FadanShensuTupian, FadanFenhong ) @@ -71,8 +71,8 @@ from products.models import ( ## config from config.models import ( - WithdrawConfig, ShangjiaLianjie, AccountPermission, ClubConfig, - Club, DailyDispatchStat, OrderTypeMapping, TixianQuotaDefault, + WithdrawConfig, ShangjiaLianjie, AccountPermission, + TixianQuotaDefault, DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage ) @@ -91,3503 +91,6 @@ SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd'] -class GetClubConfigView(APIView): - """ - 获取对方俱乐部配置列表(用于跨平台选择) - 权限要求:客服身份 + 权限代码1且开启 - """ - - permission_classes = [IsAuthenticated] - - def post(self, request): - """ - 处理 POST 请求,返回对方俱乐部配置数据(is_self=0) - """ - try: - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '003aa' not in permissions: - return Response({'code': 403, 'msg': '您无权查看俱乐部配置'}) - - # 5. 查询所有对方俱乐部(is_self=0),只取必要字段 - club_configs = ClubConfig.objects.filter( - is_self=0 # 只返回对方俱乐部 - ).only( - 'club_id', - 'club_nickname', - 'club_avatar', - 'storage_bucket_domain' # 如果前端需要存储桶域名,一并返回 - ) - - # 构造返回数据列表 - data_list = [] - for club in club_configs: - data_list.append({ - 'club_id': club.club_id, - 'club_nickname': club.club_nickname or club.club_id, # 若昵称为空,用ID代替 - 'club_avatar': club.club_avatar or '', # 可能为空 - 'storage_bucket_domain': club.storage_bucket_domain or '' # 可选 - }) - - return Response({ - 'code': 0, - 'msg': 'success', - 'data': data_list - }) - - except Exception as e: - logger.error(f"获取俱乐部配置失败: {str(e)}") - return Response({ - 'code': 500, - 'msg': '服务器内部错误' - }) - - - - - -class GetCrossOrderListView(APIView): - """ - 获取跨平台订单列表(客服后台) - 权限要求:客服身份 + 权限代码1且开启 - """ - - permission_classes = [IsAuthenticated] - - def post(self, request): - """ - 处理 POST 请求,返回分页订单数据及统计信息 - """ - try: - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '003aa' not in permissions: - return Response({'code': 403, 'msg': '您无权查看俱乐部订单'}) - - # 5. 获取请求参数 - data = request.data - - - - - # 分页参数 - page = int(data.get('page', 1)) - page_size = int(data.get('pageSize', 20)) - - # 查询参数 - leixing = data.get('leixing') # 订单类型ID - zhuangtai = data.get('zhuangtai') # 状态字符串,如 "1,7" - club_id = data.get('club_id') # 对方俱乐部ID(精确) - direction = data.get('direction') # send/receive/all - dingdan_id = data.get('dingdan_id') # 订单ID模糊搜索 - club_id_search = data.get('club_id_search') # 对方俱乐部ID模糊搜索 - date = data.get('date') # 日期 YYYY-MM-DD - dashou_id = data.get('dashou_id') # 打手ID(接单打手ID) - clkf = data.get('clkf') # 处理客服 - - # 6. 构建基础查询(跨平台订单) - base_q = Q(is_cross=1) - - # 添加筛选条件(非空且非全部) - if leixing and leixing != '': - base_q &= Q(leixing_id=leixing) - - if zhuangtai and zhuangtai != 'all': - # 解析逗号分隔的状态码 - status_list = [int(s.strip()) for s in zhuangtai.split(',') if s.strip().isdigit()] - if status_list: - base_q &= Q(zhuangtai__in=status_list) - - if club_id and club_id != '': - base_q &= Q(partner_club_id=club_id) - - # 方向映射 - if direction == 'send': - base_q &= Q(dispatch_type=1) - elif direction == 'receive': - base_q &= Q(dispatch_type=2) - # direction == 'all' 时不加方向条件 - - if dingdan_id: - base_q &= Q(dingdan_id__icontains=dingdan_id) - - if club_id_search: - base_q &= Q(partner_club_id__icontains=club_id_search) - - if date: - # 按日期精确过滤(假设 create_time 是带时区的 DateTimeField) - base_q &= Q(create_time__date=date) - - if dashou_id: - base_q &= Q(jiedan_dashou_id=dashou_id) - - if clkf: - base_q &= Q(clkf__icontains=clkf) - - # 7. 统计各状态数量(基于相同过滤条件) - # 获取所有符合条件的订单的状态分组统计 - status_counts = Dingdan.objects.filter(base_q).values('zhuangtai').annotate(count=Count('id')).order_by() - - # 转换为字典 {状态码: 数量} - status_map = {item['zhuangtai']: item['count'] for item in status_counts} - - # 定义状态列表(与前端一致) - status_list_map = { - 'all': 0, # 稍后填充总数量 - '1,7': 0, - '2': 0, - '8': 0, - '3': 0, - '4': 0, - '5': 0, - '6': 0, - } - # 填充总数 - status_list_map['all'] = sum(status_map.values()) - # 填充单个状态 - for code, cnt in status_map.items(): - if code == 1 or code == 7: - status_list_map['1,7'] += cnt - elif code in status_list_map: - status_list_map[str(code)] = cnt - # 其他状态暂不处理 - - # 构建 stats 统计 - total_orders = status_list_map['all'] - completed_orders = status_map.get(3, 0) - refund_orders = status_map.get(5, 0) - pending_orders = status_map.get(8, 0) + status_map.get(4, 0) - - stats = { - 'totalOrders': total_orders, - 'completedOrders': completed_orders, - 'refundOrders': refund_orders, - 'pendingOrders': pending_orders, - } - - # 8. 分页获取订单列表 - # 按创建时间倒序,保证分页稳定 - orders_qs = Dingdan.objects.filter(base_q).order_by('-create_time') - - # 使用 Paginator 分页 - paginator = Paginator(orders_qs, page_size) - current_page = paginator.get_page(page) - order_list = current_page.object_list - - # 只返回前端需要的字段,减少数据传输 - data_list = [] - for order in order_list: - data_list.append({ - 'dingdan_id': order.dingdan_id, - 'nicheng': order.nicheng or '', - 'jieshao': order.jieshao or '', - 'jiage': float(order.jine) if order.jine else 0, - 'zhuangtai': order.zhuangtai, - }) - - # 9. 返回结果 - return Response({ - 'code': 0, - 'msg': 'success', - 'data': { - 'list': data_list, - 'total': total_orders, - 'statusCounts': status_list_map, - 'stats': stats, - } - }) - - except Exception as e: - logger.error(f"获取跨平台订单列表失败: {str(e)}") - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - - - - -class GetCrossOrderDetailView(APIView): - """ - 客服获取跨平台订单详情接口 - 请求:POST /houtai/hqkptddxq - 参数:{"username": "客服手机号", "dingdan_id": "订单ID"} - 认证:JWT,权限码1 - 返回:code=0 + 订单详情数据(包含俱乐部信息、打手图片、处罚记录等) - """ - - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - start_time = time.time() - - # 1. 获取参数 - username = request.data.get('username', '').strip() - dingdan_id = request.data.get('dingdan_id', '').strip() - if not username or not dingdan_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - try: - # 2. 身份验证 - current_user = request.user - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '003aa' not in permissions: - return Response({'code': 403, 'msg': '您无权查看俱乐部订单'}) - - # 4. 查询订单 - dingdan_obj = Dingdan.objects.select_related( - 'pingtai_kuozhan', - 'shangjia_kuozhan' - ).get(dingdan_id=dingdan_id) - - # 5. 构建基础响应数据 - response_data = { - 'dingdan_id': dingdan_obj.dingdan_id or '', - 'zhuangtai': dingdan_obj.zhuangtai or 0, - 'fadanpingtai': dingdan_obj.fadan_pingtai or 0, - 'dispatch_type': dingdan_obj.dispatch_type or 0, # 1=我方派单, 2=对方派单 - 'is_cross': dingdan_obj.is_cross or 0, - 'partner_order_id': dingdan_obj.partner_order_id or '', - 'partner_club_id': dingdan_obj.partner_club_id or '', - 'jine': float(dingdan_obj.jine) if dingdan_obj.jine else 0.00, - 'dashou_fencheng': float(dingdan_obj.dashou_fencheng) if dingdan_obj.dashou_fencheng else 0.00, - 'jiedan_dashou_id': dingdan_obj.jiedan_dashou_id or '', - 'dashou_liuyan': dingdan_obj.dashou_liuyan or '', - 'zhiding_id': dingdan_obj.zhiding_id or '', - 'shangpin_id': dingdan_obj.shangpin_id or '', - 'leixing': dingdan_obj.leixing_id or 0, - 'tupian_url': dingdan_obj.tupian or '', - 'jieshao': dingdan_obj.jieshao or '', - 'beizhu': dingdan_obj.beizhu or '', - 'tkly': dingdan_obj.tkly or '', - 'nicheng': dingdan_obj.nicheng or '', - 'clkf': dingdan_obj.clkf or '', - 'chuangjianshijian': dingdan_obj.create_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan_obj.create_time else '', - 'genggaishijian': dingdan_obj.update_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan_obj.update_time else '', - } - - # 6. 扩展表数据 - if dingdan_obj.fadan_pingtai == 1: - if hasattr(dingdan_obj, 'pingtai_kuozhan') and dingdan_obj.pingtai_kuozhan: - response_data['pingtai_kuozhan'] = { - 'laoban_id': dingdan_obj.pingtai_kuozhan.laoban_id or '', - 'laoban_pingjia': dingdan_obj.pingtai_kuozhan.laoban_pingjia or '', - } - else: - response_data['pingtai_kuozhan'] = {'laoban_id': '', 'laoban_pingjia': ''} - elif dingdan_obj.fadan_pingtai == 2: - if hasattr(dingdan_obj, 'shangjia_kuozhan') and dingdan_obj.shangjia_kuozhan: - shangjia = dingdan_obj.shangjia_kuozhan - response_data['shangjia_kuozhan'] = { - 'shangjia_id': shangjia.shangjia_id or '', - 'sjnicheng': shangjia.sjnicheng or '', - 'shangjia_pingjia': shangjia.shangjia_pingjia or '', - 'cfliyou': shangjia.cfliyou or '', - 'sqzhuangtai': shangjia.sqzhuangtai if shangjia.sqzhuangtai is not None else 0, - 'bhliyou': shangjia.bhliyou or '', - } - else: - response_data['shangjia_kuozhan'] = { - 'shangjia_id': '', 'sjnicheng': '', 'shangjia_pingjia': '', - 'cfliyou': '', 'sqzhuangtai': 0, 'bhliyou': '' - } - - # ========== 核心逻辑:判断派单/接单关系 ========== - dispatch_type = dingdan_obj.dispatch_type # 1=我方派单, 2=对方派单 - is_cross = dingdan_obj.is_cross - partner_club_id = dingdan_obj.partner_club_id - partner_order_id = dingdan_obj.partner_order_id - - # 获取我方俱乐部配置(用于本地存储桶域名) - my_club_config = ClubConfig.objects.filter(is_self=1).first() - my_storage_domain = my_club_config.storage_bucket_domain if my_club_config else '' - - # 初始化字段 - response_data['club_info'] = None # 对方俱乐部信息 - response_data['partner_images'] = [] # 打手提交图片(对方平台打手或我方打手) - response_data['punishment_info'] = {} # 处罚信息 - response_data['is_punished'] = False # 是否已处罚(用于前端禁用按钮) - response_data['receive_side'] = '' # 接单方:'self' 我方接单,'partner' 对方接单 - response_data['dispatch_side'] = '' # 派单方:'self' 我方派单,'partner' 对方派单 - - # 设置派单方 - if dispatch_type == 1: - response_data['dispatch_side'] = 'self' - elif dispatch_type == 2: - response_data['dispatch_side'] = 'partner' - else: - # 兼容旧数据:根据 partner_club_id 或 partner_order_id 推断 - if partner_club_id or partner_order_id: - response_data['dispatch_side'] = 'partner' - else: - response_data['dispatch_side'] = 'self' - - # 情况1:对方派单(对方平台发单,我方打手接单) - if response_data['dispatch_side'] == 'partner': - # 接单方为我方 - response_data['receive_side'] = 'self' - - # 获取对方俱乐部信息(用于展示) - if partner_club_id: - partner_config = ClubConfig.objects.filter(club_id=partner_club_id, is_self=0).first() - if partner_config: - response_data['club_info'] = { - 'club_id': partner_config.club_id, - 'club_nickname': partner_config.club_nickname or partner_config.club_id, - 'club_avatar': partner_config.club_avatar or '', - } - else: - response_data['club_info'] = {'club_id': partner_club_id, 'club_nickname': '', 'club_avatar': ''} - else: - response_data['club_info'] = None - - # 查询我方打手提交的图片(本地) - dashou_images = Dashoutupian.objects.filter( - dingdan_id=dingdan_id, - dashou=dingdan_obj.jiedan_dashou_id - ).values_list('tupian', flat=True) - images = [] - for rel_path in dashou_images: - if rel_path: - if rel_path.startswith('http'): - images.append(rel_path) - else: - images.append(my_storage_domain + rel_path) - response_data['partner_images'] = images - - # 查询我方打手处罚记录 - punish_records = Chufajilu.objects.filter( - dingdan_id=dingdan_id, - dashouid=dingdan_obj.jiedan_dashou_id - ).order_by('-create_time') - if punish_records.exists(): - latest = punish_records.first() - response_data['punishment_info'] = { - 'cfliyou': latest.cfliyou or '', - 'sqzhuangtai': latest.sqzhuangtai or 0, - 'bhliyou': latest.bhliyou or '', - 'ssliyou': latest.ssliyou or '', - 'jifen': latest.jifen or 0, - } - response_data['is_punished'] = True - else: - response_data['punishment_info'] = {} - response_data['is_punished'] = False - - # 情况2:我方派单(我方平台发单) - else: - response_data['dispatch_side'] = 'self' - # 根据是否有对方订单ID/俱乐部ID判断接单方 - if partner_club_id or partner_order_id: - # 有对方信息,说明订单派给了对方平台,对方接单 - response_data['receive_side'] = 'partner' - - # 获取对方俱乐部信息 - if partner_club_id: - partner_config = ClubConfig.objects.filter(club_id=partner_club_id, is_self=0).first() - if partner_config: - response_data['club_info'] = { - 'club_id': partner_config.club_id, - 'club_nickname': partner_config.club_nickname or partner_config.club_id, - 'club_avatar': partner_config.club_avatar or '', - } - else: - response_data['club_info'] = {'club_id': partner_club_id, 'club_nickname': '', 'club_avatar': ''} - - # 需要调用对方接口获取图片和处罚记录 - if partner_club_id and partner_order_id: - # 获取对方服务器域名 - if my_club_config: - self_club_id = my_club_config.club_id - else: - logger.error(f"无法获取本俱乐部ID,订单ID: {dingdan_id}") - return Response({'code': 500, 'msg': '系统配置错误,请联系管理员'}) - - try: - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain - except ObjectDoesNotExist: - logger.error(f"未找到合作关系: {self_club_id} <-> {partner_club_id}") - return Response({'code': 500, 'msg': '未找到对方平台配置'}) - - # 调用对方接口,最多重试3次 - url = partner_domain.rstrip('/') + '/houtai/kpthqtp' - payload = { - 'order_id': partner_order_id, - 'club_id': self_club_id, - } - timeout = 3 - retries = 3 - success = False - for attempt in range(retries): - try: - resp = requests.post(url, json=payload, timeout=timeout) - if resp.status_code == 200: - resp_json = resp.json() - if resp_json.get('code') == 0: - data = resp_json.get('data', {}) - response_data['partner_images'] = data.get('images', []) - response_data['punishment_info'] = data.get('punishment', {}) - response_data['is_punished'] = bool(response_data['punishment_info'].get('cfliyou')) - success = True - break - else: - logger.warning(f"对方接口返回错误 (尝试{attempt+1}): {resp_json.get('msg')}") - else: - logger.warning(f"对方接口HTTP错误 (尝试{attempt+1}): {resp.status_code}") - except requests.exceptions.Timeout: - logger.warning(f"请求对方接口超时 (尝试{attempt+1}): {url}") - except Exception as e: - logger.warning(f"请求对方接口异常 (尝试{attempt+1}): {str(e)}") - if not success: - # 三次失败,返回部分数据,但提示前端对方数据获取失败 - response_data['partner_images'] = [] - response_data['punishment_info'] = {} - response_data['is_punished'] = False - response_data['partner_data_error'] = '获取对方平台数据失败,请稍后重试' - else: - # 对方信息不完整,无法获取数据 - response_data['partner_images'] = [] - response_data['punishment_info'] = {} - response_data['is_punished'] = False - response_data['partner_data_error'] = '对方订单信息不完整' - else: - # 无对方信息,说明我方派单且我方接单 - response_data['receive_side'] = 'self' - # 我方打手提交图片(本地) - dashou_images = Dashoutupian.objects.filter( - dingdan_id=dingdan_id, - dashou=dingdan_obj.jiedan_dashou_id - ).values_list('tupian', flat=True) - images = [] - for rel_path in dashou_images: - if rel_path: - if rel_path.startswith('http'): - images.append(rel_path) - else: - images.append(my_storage_domain + rel_path) - response_data['partner_images'] = images - - # 处罚记录 - punish_records = Chufajilu.objects.filter( - dingdan_id=dingdan_id, - dashouid=dingdan_obj.jiedan_dashou_id - ).order_by('-create_time') - if punish_records.exists(): - latest = punish_records.first() - response_data['punishment_info'] = { - 'cfliyou': latest.cfliyou or '', - 'sqzhuangtai': latest.sqzhuangtai or 0, - 'bhliyou': latest.bhliyou or '', - 'ssliyou': latest.ssliyou or '', - 'jifen': latest.jifen or 0, - } - response_data['is_punished'] = True - else: - response_data['punishment_info'] = {} - response_data['is_punished'] = False - - # 如果是我方派单且我方接单,对方俱乐部信息为空 - response_data['club_info'] = None - - # 7. 查询退款理由 - try: - tuikuan_record = Tuikuanjilu.objects.filter( - dingdan_id=dingdan_id - ).order_by('-create_time').first() - response_data['tuikuan_liyou'] = tuikuan_record.liyou if tuikuan_record else '' - except Exception as e: - logger.warning(f"查询退款记录失败: {str(e)}") - response_data['tuikuan_liyou'] = '' - - # ========== 新增:查询更换打手生成的新订单信息 ========== - try: - new_lianjie = ShangjiaLianjie.objects.filter( - previous_dingdan_id=dingdan_id - ).order_by('-create_time').first() - if new_lianjie: - response_data['new_dingdan_id'] = new_lianjie.dingdan_id or '' - response_data['new_lianjie'] = new_lianjie.lianjie or '' - else: - response_data['new_dingdan_id'] = '' - response_data['new_lianjie'] = '' - except Exception as e: - logger.warning(f"查询新订单链接失败: {str(e)}") - response_data['new_dingdan_id'] = '' - response_data['new_lianjie'] = '' - # ===================================================== - - elapsed = time.time() - start_time - logger.info(f"获取跨平台订单详情接口耗时: {elapsed:.3f}s, 订单ID: {dingdan_id}") - return Response({'code': 0, 'data': response_data}) - - except Dingdan.DoesNotExist: - logger.warning(f"订单不存在: {dingdan_id}") - return Response({'code': 404, 'msg': '订单不存在'}) - except Exception as e: - logger.error(f"获取跨平台订单详情接口异常: {traceback.format_exc()}") - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - -class CrossPlatformRefundView(APIView): - """ - 跨平台订单退款接口(同时支持本地订单和跨平台订单) - 请求:POST /houtai/kptk - 参数:{ - "username": "客服账号ID", - "dingdan_id": "订单ID", - "tuikuan_liyou": "退款理由(可选)" - } - 认证:JWT - 返回:code=0 成功,其他失败 - """ - - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取参数 - username = request.data.get('username', '').strip() - dingdan_id = request.data.get('dingdan_id', '').strip() - tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip() - - if not username or not dingdan_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 身份验证 - current_user = request.user - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '003aa' not in permissions: - return Response({'code': 403, 'msg': '您无权处理订单'}) - - # 4. 查询订单(带扩展表) - try: - order = Dingdan.objects.select_related( - 'pingtai_kuozhan', - 'shangjia_kuozhan' - ).get(dingdan_id=dingdan_id) - except Dingdan.DoesNotExist: - logger.warning(f"订单不存在: {dingdan_id}") - return Response({'code': 404, 'msg': '订单不存在'}) - - # 5. 订单状态校验(只有 1,7,2,8,4 可退款) - allowed_statuses = [1, 7, 2, 8, 4] - if order.zhuangtai not in allowed_statuses: - logger.warning(f"订单状态不允许退款: {order.zhuangtai}") - return Response({'code': 400, 'msg': '订单状态不允许退款'}) - - # 6. 根据派单方向分流 - dispatch_type = order.dispatch_type # 1=我方派单,2=对方派单 - - # ---------- 情况一:对方派单(我方打手接对方的单)---------- - if dispatch_type == 2: - # 对方派单,资金在对方平台,我方只处理打手状态和订单状态 - with transaction.atomic(): - # 更新订单状态为已退款 - order.zhuangtai = 5 - order.clkf = current_user.phone - if tuikuan_liyou: - order.tkly = tuikuan_liyou - order.save() - - # 更新打手数据(接单打手) - dashou_id = order.jiedan_dashou_id - if dashou_id: - try: - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - dashou_profile.tuikuanliang += 1 - dashou_profile.zhuangtai = 1 # 设为空闲 - dashou_profile.save() - except (UserMain.DoesNotExist, ObjectDoesNotExist): - logger.warning(f"对方派单退款:打手 {dashou_id} 不存在,跳过更新") - - # 更新退款记录(记录操作) - try: - refund_record, created = Tuikuanjilu.objects.get_or_create( - dingdan_id=dingdan_id, - defaults={ - 'dashouid': dashou_id or '', - 'qingqiuid': '', - 'chuliid': current_user.phone, - 'liyou': tuikuan_liyou or '客服退款(对方派单)', - 'sqzhuangtai': 1, - 'jine': order.jine or 0, - 'dashou_fencheng': order.dashou_fencheng or 0, - 'jieshao': order.jieshao or '', - 'beizhu': '对方派单退款', - 'nicheng': order.nicheng or '', - } - ) - if not created: - refund_record.sqzhuangtai = 1 - refund_record.chuliid = current_user.phone - if tuikuan_liyou: - refund_record.liyou = tuikuan_liyou - refund_record.save() - except Exception as e: - logger.error(f"更新退款记录失败: {str(e)}") - - # 🔥 新增:调用打手每日统计(成交) - try: - update_dashou_daily_by_action( - yonghuid=order.jiedan_dashou_id, - amount=order.dashou_fencheng, - action=3 # 3 表示退款(退款) - ) - logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功") - except Exception as e: - logger.error(f"打手每日统计更新失败: {e}") - # 不影响主流程 - - - # ✅ 新方法(行为驱动,更安全) - shangjia_ext = order.shangjia_kuozhan - shangjia_id = shangjia_ext.shangjia_id - update_shangjia_daily( - yonghuid=shangjia_id, - amount=Decimal(str(order.jine)), - action=3 # 3 = 退款 - ) - - # 更新客服统计 - kefu.jinrichuli += 1 - kefu.jinyuechuli += 1 - kefu.zongchuli += 1 - kefu.save() - - logger.info(f"对方派单退款成功,订单 {dingdan_id}") - return Response({'code': 0, 'msg': '退款成功'}) - - # ---------- 情况二:我方派单(订单由我方发出)---------- - # 需要进一步判断是本地订单还是跨平台订单 - is_cross = order.is_cross - partner_club_id = order.partner_club_id - partner_order_id = order.partner_order_id - - # 子情况2.1:本地订单(非跨平台) - if not is_cross or not partner_club_id: - # 按原有逻辑处理:平台订单或商家订单 - if order.fadan_pingtai == 1: - # 平台订单退款(调用微信支付) - return self._refund_platform_order(order, current_user, tuikuan_liyou, kefu) - elif order.fadan_pingtai == 2: - # 商家订单退款(直接退余额) - return self._refund_merchant_order(order, current_user, tuikuan_liyou, kefu) - else: - return Response({'code': 400, 'msg': '未知的发单平台类型'}) - - # 子情况2.2:跨平台订单(我方派单给对方) - else: - # 必须先执行本地资金退款(因为钱在我方平台) - # 根据发单平台类型执行退款 - local_refund_success = False - try: - with transaction.atomic(): - if order.fadan_pingtai == 1: - # 平台订单:调用微信退款 - refund_result = self._call_wechat_refund(order) - if refund_result['code'] != 0: - raise Exception(refund_result['msg']) - # 微信退款成功,更新订单状态 - order.zhuangtai = 5 - order.clkf = current_user.phone - if tuikuan_liyou: - order.tkly = tuikuan_liyou - order.save() - local_refund_success = True - elif order.fadan_pingtai == 2: - # 商家订单:退余额给商家 - self._refund_to_merchant(order, current_user.phone, tuikuan_liyou) - order.zhuangtai = 5 - order.clkf = current_user.phone - if tuikuan_liyou: - order.tkly = tuikuan_liyou - order.save() - local_refund_success = True - else: - return Response({'code': 400, 'msg': '未知的发单平台类型'}) - - # 更新打手数据(接单打手) - dashou_id = order.jiedan_dashou_id - if dashou_id: - try: - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - dashou_profile.tuikuanliang += 1 - dashou_profile.zhuangtai = 1 - dashou_profile.save() - except (UserMain.DoesNotExist, ObjectDoesNotExist): - logger.warning(f"跨平台退款:打手 {dashou_id} 不存在,跳过更新") - - # 更新退款记录 - self._update_refund_record(order, dashou_id, current_user.phone, tuikuan_liyou) - - # 更新客服统计 - kefu.jinrichuli += 1 - kefu.jinyuechuli += 1 - kefu.zongchuli += 1 - kefu.save() - - except Exception as e: - logger.error(f"跨平台订单本地退款失败: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': f'本地退款失败: {str(e)}'}) - - # 本地退款成功后,通知对方平台 - if local_refund_success: - # 查询对方平台域名 - try: - # 获取我方俱乐部ID - my_club_config = ClubConfig.objects.filter(is_self=1).first() - if not my_club_config: - logger.error("未配置我方俱乐部信息") - # 本地已成功,仅记录错误,不影响返回 - else: - self_club_id = my_club_config.club_id - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain - - # 调用对方退款通知接口 - notify_url = partner_domain.rstrip('/') + '/houtai/dfddtk' - payload = { - 'order_id': partner_order_id, # 对方订单ID - 'club_id': self_club_id, # 我方俱乐部ID - 'status': 5, # 通知对方我方已退款 - 'reason': tuikuan_liyou - } - # 设置超时3秒,不阻塞主流程 - try: - resp = requests.post(notify_url, json=payload, timeout=3) - if resp.status_code == 200: - resp_json = resp.json() - if resp_json.get('code') == 0: - # 更新跨平台订单扩展表 - cross_data, _ = CrossPlatformOrderData.objects.get_or_create( - dingdan_id=dingdan_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'partner_order_status': resp_json.get('data', {}).get('partner_status', 0), - 'partner_dashou_id': resp_json.get('data', {}).get('partner_dashou_id', ''), - 'partner_amount': order.jine or 0, - } - ) - if not _: - cross_data.partner_order_status = resp_json.get('data', {}).get('partner_status', 0) - cross_data.save() - else: - logger.warning(f"对方平台返回错误: {resp_json.get('msg')}") - else: - logger.warning(f"对方平台接口HTTP错误: {resp.status_code}") - except requests.exceptions.Timeout: - logger.error(f"通知对方平台超时: {notify_url}") - except Exception as e: - logger.error(f"通知对方平台异常: {str(e)}") - except Exception as e: - logger.error(f"查询对方平台配置失败: {str(e)}") - - return Response({'code': 0, 'msg': '退款成功'}) - - # ========== 辅助方法 ========== - def _refund_platform_order(self, order, current_user, tuikuan_liyou, kefu): - """平台订单退款(调用微信支付)""" - # 调用微信退款 - refund_result = self._call_wechat_refund(order) - if refund_result['code'] != 0: - return Response({'code': 400, 'msg': refund_result['msg']}) - - with transaction.atomic(): - order.zhuangtai = 5 - order.clkf = current_user.phone - if tuikuan_liyou: - order.tkly = tuikuan_liyou - order.save() - - # 更新打手 - dashou_id = order.jiedan_dashou_id - if dashou_id: - try: - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - dashou_profile.tuikuanliang += 1 - dashou_profile.zhuangtai = 1 - dashou_profile.save() - except (UserMain.DoesNotExist, ObjectDoesNotExist): - logger.warning(f"平台退款:打手 {dashou_id} 不存在,跳过更新") - - # 更新退款记录 - self._update_refund_record(order, dashou_id, current_user.phone, tuikuan_liyou) - - is_cross = order.is_cross - partner_club_id = order.partner_club_id - partner_order_id = order.partner_order_id - - if not is_cross or not partner_order_id: - - # 🔥 新增:调用打手每日统计(成交) - try: - update_dashou_daily_by_action( - yonghuid=order.jiedan_dashou_id, - amount=order.dashou_fencheng, - action=3 # 3 表示退款(退款) - ) - logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功") - except Exception as e: - logger.error(f"打手每日统计更新失败: {e}") - # 不影响主流程 - else: - pass - - # 客服统计 - kefu.jinrichuli += 1 - kefu.jinyuechuli += 1 - kefu.zongchuli += 1 - kefu.save() - - logger.info(f"平台订单退款成功: {order.dingdan_id}") - return Response({'code': 0, 'msg': '退款成功'}) - - def _refund_merchant_order(self, order, current_user, tuikuan_liyou, kefu): - """商家订单退款(退余额给商家)""" - # 获取商家ID - try: - shangjia_ext = order.shangjia_kuozhan - shangjia_id = shangjia_ext.shangjia_id - except ObjectDoesNotExist: - return Response({'code': 400, 'msg': '订单商家信息不存在'}) - - with transaction.atomic(): - # 订单状态改已退款 - order.zhuangtai = 5 - order.clkf = current_user.phone - if tuikuan_liyou: - order.tkly = tuikuan_liyou - order.save() - - # 退余额给商家 - try: - shangjia_user = UserMain.objects.get(yonghuid=shangjia_id) - shangjia_profile = shangjia_user.shop_profile - shangjia_profile.tuikuan += 1 - shangjia_profile.yue += order.jine or 0 - shangjia_profile.save() - except (UserMain.DoesNotExist, ObjectDoesNotExist): - logger.error(f"商家退款:商家 {shangjia_id} 不存在,无法退余额") - raise Exception("商家账户不存在") - - # 更新打手 - dashou_id = order.jiedan_dashou_id - if dashou_id: - try: - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - dashou_profile.tuikuanliang += 1 - dashou_profile.zhuangtai = 1 - dashou_profile.save() - except (UserMain.DoesNotExist, ObjectDoesNotExist): - logger.warning(f"商家退款:打手 {dashou_id} 不存在,跳过更新") - - # 更新退款记录 - self._update_refund_record(order, dashou_id, current_user.phone, tuikuan_liyou) - - - is_cross = order.is_cross - partner_club_id = order.partner_club_id - partner_order_id = order.partner_order_id - - if not is_cross or not partner_order_id: - - # 🔥 新增:调用打手每日统计(成交) - try: - update_dashou_daily_by_action( - yonghuid=order.jiedan_dashou_id, - amount=order.dashou_fencheng, - action=3 # 3 表示退款(退款) - ) - logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功") - except Exception as e: - logger.error(f"打手每日统计更新失败: {e}") - # 不影响主流程 - else: - pass - - # ✅ 新方法(行为驱动,更安全) - update_shangjia_daily( - yonghuid=shangjia_id, - amount=Decimal(str(order.jine)), - action=3 # 3 = 退款 - ) - - # 客服统计 - kefu.jinrichuli += 1 - kefu.jinyuechuli += 1 - kefu.zongchuli += 1 - kefu.save() - - logger.info(f"商家订单退款成功: {order.dingdan_id}") - return Response({'code': 0, 'msg': '退款成功'}) - - def _call_wechat_refund(self, order): - """调用微信支付退款接口,返回 {'code':0, 'msg':'success'} 或错误信息""" - # 获取微信支付配置 - appid = getattr(settings, 'WEIXIN_APPID', '') - mch_id = getattr(settings, 'WEIXIN_MCHID', '') - key = getattr(settings, 'WEIXIN_SHANGHUMIYAO', '') - cert_path = getattr(settings, 'WEIXIN_CERT_PATH', '') - key_path = getattr(settings, 'WEIXIN_KEY_PATH', '') - - if not all([appid, mch_id, key, cert_path, key_path]): - missing = [k for k, v in {'appid': appid, 'mch_id': mch_id, 'key': key, 'cert_path': cert_path, 'key_path': key_path}.items() if not v] - logger.error(f"微信支付配置缺失: {missing}") - return {'code': 500, 'msg': f'系统支付配置缺失: {missing}'} - - # 生成退款单号 - out_refund_no = self._generate_refund_no(order.dingdan_id) - total_fee = int(float(order.jine or 0) * 100) - refund_fee = total_fee - refund_desc = '客服退款' - - params = { - 'appid': appid, - 'mch_id': mch_id, - 'nonce_str': self._generate_nonce_str(), - 'out_refund_no': out_refund_no, - 'total_fee': total_fee, - 'refund_fee': refund_fee, - 'refund_desc': refund_desc, - } - # 优先使用微信交易号 - if order.wechat_transaction_id: - params['transaction_id'] = order.wechat_transaction_id - else: - params['out_trade_no'] = order.dingdan_id - - # 签名 - sorted_keys = sorted(params.keys()) - stringA = '&'.join([f"{k}={params[k]}" for k in sorted_keys]) - stringSignTemp = f"{stringA}&key={key}" - sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper() - params['sign'] = sign - - xml_data = self._dict_to_xml(params) - url = 'https://api.mch.weixin.qq.com/secapi/pay/refund' - - try: - response = requests.post(url, data=xml_data, cert=(cert_path, key_path), timeout=10) - result = xmltodict.parse(response.content)['xml'] - logger.info(f"微信退款响应: {result}") - except Exception as e: - logger.error(f"微信退款请求异常: {str(e)}", exc_info=True) - return {'code': 500, 'msg': '微信退款请求异常'} - - if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS': - return {'code': 0, 'msg': 'success'} - else: - err_msg = result.get('err_code_des', result.get('return_msg', '未知错误')) - logger.warning(f"微信退款失败: {err_msg}") - return {'code': 400, 'msg': err_msg} - - def _refund_to_merchant(self, order, clkf_phone, tuikuan_liyou): - """给商家退余额(事务内调用,不再独立事务)""" - try: - shangjia_ext = order.shangjia_kuozhan - shangjia_id = shangjia_ext.shangjia_id - except ObjectDoesNotExist: - raise Exception("订单商家信息不存在") - - try: - shangjia_user = UserMain.objects.get(yonghuid=shangjia_id) - shangjia_profile = shangjia_user.shop_profile - shangjia_profile.tuikuan += 1 - shangjia_profile.yue += order.jine or 0 - shangjia_profile.save() - except (UserMain.DoesNotExist, ObjectDoesNotExist): - logger.error(f"商家退款:商家 {shangjia_id} 不存在") - raise Exception("商家账户不存在") - - def _update_refund_record(self, order, dashou_id, chuli_phone, tuikuan_liyou): - """更新退款记录表""" - try: - #更新支出记录 - if order.fadan_pingtai == 1: - update_daily_payout(order.jine) - - # ========== 新增:商品和店铺每日统计 ========== - try: - # 商品每日统计(只要有商品ID就执行) - if order.shangpin_id: - # 从扩展表获取已计算好的收益值(下单时已存储) - pingtai_shouyi = Decimal('0.00') - dianpu_shouyi = Decimal('0.00') - if hasattr(order, 'pingtai_kuozhan'): - pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00') - dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') - - update_shangpin_daily_stat( - shangpin_id=order.shangpin_id, - caozuo_leixing=3, # 3 = 退款 - dingdan_jiage=order.jine, - pingtai_shouyi=pingtai_shouyi, - dianpu_zongshouyi=dianpu_shouyi - ) - logger.info(f"商品统计更新完成,商品ID: {order.shangpin_id}") - - # 店铺每日统计(仅当存在店铺ID时执行) - dianpu_id = None - if hasattr(order, 'pingtai_kuozhan'): - dianpu_id = order.pingtai_kuozhan.dianpu_id - if dianpu_id: - dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') - update_dianpu_daily_stat( - dianpu_id=dianpu_id, - caozuo_leixing=3, # 3 = 退款 - dingdan_jiage=order.jine, - dianpu_shouyi=dianpu_shouyi - ) - logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}") - except Exception as e: - logger.error(f"商品/店铺统计更新失败: {e}") - logger.error(traceback.format_exc()) - # 统计失败不影响主流程 - # ============================================= - - - refund_record, created = Tuikuanjilu.objects.get_or_create( - dingdan_id=order.dingdan_id, - defaults={ - 'dashouid': dashou_id or '', - 'qingqiuid': '', - 'chuliid': chuli_phone, - 'liyou': tuikuan_liyou or '客服退款', - 'sqzhuangtai': 1, - 'jine': order.jine or 0, - 'dashou_fencheng': order.dashou_fencheng or 0, - 'jieshao': order.jieshao or '', - 'beizhu': '客服退款', - 'nicheng': order.nicheng or '', - } - ) - if not created: - refund_record.sqzhuangtai = 1 - refund_record.chuliid = chuli_phone - if tuikuan_liyou: - refund_record.liyou = tuikuan_liyou - refund_record.save() - except Exception as e: - logger.error(f"更新退款记录异常: {str(e)}") - - def _generate_nonce_str(self): - return ''.join(random.choices(string.ascii_letters + string.digits, k=32)) - - def _generate_refund_no(self, dingdan_id): - timestamp = str(int(time.time())) - rand = ''.join(random.choices('0123456789', k=4)) - return f"{dingdan_id}REF{timestamp}{rand}" - - def _dict_to_xml(self, data): - xml = [''] - for k, v in data.items(): - xml.append(f'<{k}>{v}') - xml.append('') - return ''.join(xml) - - - - -class PartnerGetOrderDataView(APIView): - """ - 对方平台调用,获取我方订单的打手图片和处罚记录 - 请求:POST /houtai/kpthqtp - 参数:{ - "order_id": "对方平台的订单ID", - "club_id": "对方俱乐部ID" - } - 无需认证 - 返回:code=0 + images列表 + punishment信息 - """ - permission_classes = [AllowAny] - parser_classes = [JSONParser] - - def post(self, request): - # token = request.data.get('token') - # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200) - try: - # 1. 获取参数 - partner_order_id = request.data.get('order_id', '').strip() - partner_club_id = request.data.get('club_id', '').strip() - if not partner_order_id or not partner_club_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 查询我方存储桶域名(自己俱乐部的配置) - my_club_config = ClubConfig.objects.filter(is_self=1).first() - if not my_club_config: - logger.error("未配置我方俱乐部存储桶域名") - return Response({'code': 500, 'msg': '系统配置错误'}) - storage_domain = my_club_config.storage_bucket_domain or '' - - # 3. 查询我方订单(通过对方订单ID和对方俱乐部ID) - try: - order = Dingdan.objects.get( - dingdan_id=partner_order_id, - partner_club_id=partner_club_id - ) - except Dingdan.DoesNotExist: - logger.warning(f"订单不存在: partner_order_id={partner_order_id}, partner_club_id={partner_club_id}") - return Response({'code': 404, 'msg': '订单不存在'}) - - dingdan_id = order.dingdan_id - dashou_id = order.jiedan_dashou_id - - # 4. 查询打手图片(绝对URL) - images = [] - if dashou_id: - dashou_images = Dashoutupian.objects.filter( - dingdan_id=dingdan_id, - dashou=dashou_id - ).values_list('tupian', flat=True) - for rel_path in dashou_images: - if rel_path: - if rel_path.startswith('http'): - images.append(rel_path) - else: - images.append(storage_domain + rel_path) - - # 5. 查询处罚记录(最新一条) - punishment = { - 'cfliyou': '', - 'sqzhuangtai': 0, - 'bhliyou': '', - 'ssliyou': '', - 'jifen': 0 - } - if dashou_id: - punish_record = Chufajilu.objects.filter( - dingdan_id=dingdan_id, - dashouid=dashou_id - ).order_by('-create_time').first() - if punish_record: - punishment = { - 'cfliyou': punish_record.cfliyou or '', - 'sqzhuangtai': punish_record.sqzhuangtai or 0, - 'bhliyou': punish_record.bhliyou or '', - 'ssliyou': punish_record.ssliyou or '', - 'jifen': punish_record.jifen or 0, - } - - # 6. 返回数据 - return Response({ - 'code': 0, - 'msg': 'success', - 'data': { - 'images': images, - 'punishment': punishment - } - }) - - except Exception as e: - logger.error(f"对方获取订单数据接口异常: {traceback.format_exc()}") - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - -# ========== 新增:对方平台退款通知接口 ========== -class PartnerRefundNotifyView(APIView): - """ - 接收对方平台推送的订单状态变更通知(完成/退款),同步更新我方数据。 - 请求:POST /houtai/dfddtk - 参数: - { - "order_id": "对方平台订单ID(实际为我方订单ID)", - "club_id": "对方俱乐部ID", - "status": 3 或 5, - "reason": "理由(可选)" - } - 返回:code=0 + data包含我方订单当前状态和打手ID - - 核心职责: - 1. 更新跨平台订单扩展表 - 2. 对于对方派单(我方打手接对方的单): - a. 状态3(完成)→ 更新订单为已完成,创建预结算(如无) - b. 状态5(退款)→ 更新订单为已退款,将预结算待结算改为已拒绝 - c. 在此过程中,若订单原状态为“进行中(2)”,则将打手状态重置为空闲(1) - """ - - permission_classes = [AllowAny] - parser_classes = [JSONParser] - - def post(self, request): - # token = request.data.get('token') - # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200) - # 1. 参数校验(事务外执行) - partner_order_id = request.data.get('order_id', '').strip() - partner_club_id = request.data.get('club_id', '').strip() - partner_status = request.data.get('status') - reason = request.data.get('reason', '').strip() - - if not partner_order_id or not partner_club_id: - return Response({'code': 400, 'msg': '参数不完整'}) - if partner_status not in (3, 5): - return Response({'code': 400, 'msg': '不支持的状态码'}) - - # 2. 核心业务在事务内执行 - try: - with transaction.atomic(): - # 2.1 锁定订单行(必须事务内) - try: - order = Dingdan.objects.select_for_update().get( - dingdan_id=partner_order_id, - partner_club_id=partner_club_id - ) - except Dingdan.DoesNotExist: - logger.warning(f"通知订单不存在: order_id={partner_order_id}, club_id={partner_club_id}") - return Response({'code': 404, 'msg': '订单不存在'}) - - my_dingdan_id = order.dingdan_id - dashou_id = order.jiedan_dashou_id or '' - - # 记录原订单状态,用于后续打手状态重置 - original_order_status = order.zhuangtai - - # 2.2 更新跨平台扩展表(记录对方状态) - cross_data, created = CrossPlatformOrderData.objects.get_or_create( - dingdan_id=my_dingdan_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'partner_order_status': partner_status, - 'partner_dashou_id': '', - 'partner_amount': order.jine or 0, - } - ) - if not created: - cross_data.partner_order_status = partner_status - cross_data.save(update_fields=['partner_order_status', 'update_time']) - - # 2.3 仅处理对方派单(dispatch_type == 2) - if order.dispatch_type == 2: - # ========== 打手状态恢复逻辑 ========== - # 如果原订单状态为“进行中(2)”,且存在接单打手,则将打手状态改为空闲(1) - if original_order_status == 2 and dashou_id: - try: - # 通过打手ID查用户主表,再反向获取打手扩展表 - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - dashou_profile.zhuangtai = 1 # 状态置为空闲 - dashou_profile.save(update_fields=['zhuangtai']) - logger.info(f"打手 {dashou_id} 状态已重置为1(空闲)") - except UserMain.DoesNotExist: - logger.warning(f"打手用户不存在: {dashou_id}") - except UserDashou.DoesNotExist: - logger.warning(f"打手扩展信息不存在: {dashou_id}") - # 🔥 新增:调用打手每日统计(成交) - if partner_status ==3: - - try: - update_dashou_daily_by_action( - yonghuid=order.jiedan_dashou_id, - amount=order.dashou_fencheng, - action=2 # 3 表示退款(退款) - ) - logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功") - except Exception as e: - logger.error(f"打手每日统计更新失败: {e}") - if partner_status ==5: - try: - update_dashou_daily_by_action( - yonghuid=order.jiedan_dashou_id, - amount=order.dashou_fencheng, - action=3 # 3 表示退款(退款) - ) - logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功") - except Exception as e: - logger.error(f"打手每日统计更新失败: {e}") - - # ========== 情况A:对方完成(status=3) ========== - if partner_status == 3: - order.zhuangtai = 3 - - order.clkf = partner_club_id # 处理人(对方俱乐部ID) - order.save(update_fields=['zhuangtai', 'update_time','clkf']) - - if dashou_id: - yjs, yjs_created = PreSettlement.objects.get_or_create( - dingdan_id=my_dingdan_id, - dashou_id=dashou_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'amount': order.dashou_fencheng or 0, - 'order_amount': order.jine or 0, - 'status': 0, # 待结算 - 'operator': '', - } - ) - if yjs_created: - logger.info(f"预结算创建成功: {my_dingdan_id}, 打手 {dashou_id}, 金额 {order.dashou_fencheng}") - else: - logger.info(f"预结算已存在,跳过: {my_dingdan_id}, 打手 {dashou_id}") - else: - logger.warning(f"订单 {my_dingdan_id} 无接单打手,跳过预结算创建") - - # ========== 情况B:对方退款(status=5) ========== - elif partner_status == 5: - order.zhuangtai = 5 - order.tkly = reason if reason else '' # 退款理由 - order.clkf = partner_club_id # 处理人(对方俱乐部ID) - order.save(update_fields=['zhuangtai', 'update_time','clkf','tkly']) - - if dashou_id: - try: - yjs = PreSettlement.objects.get( - dingdan_id=my_dingdan_id, - dashou_id=dashou_id - ) - if yjs.status == 0: # 待结算 → 已拒绝 - yjs.status = 2 - yjs.process_time = timezone.now() - yjs.save(update_fields=['status', 'process_time', 'update_time']) - logger.info(f"预结算状态已改为已拒绝: {my_dingdan_id}") - except PreSettlement.DoesNotExist: - logger.info(f"订单 {my_dingdan_id} 无预结算记录,无需修改") - - logger.info(f"对方通知处理完成: dingdan_id={my_dingdan_id}, partner_status={partner_status}") - - # 返回结果 - return Response({ - 'code': 0, - 'msg': 'success', - 'data': { - 'partner_status': order.zhuangtai, - 'partner_dashou_id': dashou_id - } - }) - - except Exception as e: - logger.error(f"对方通知接口异常: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - - - - -class CrossPlatformForceCompleteView(APIView): - """ - 跨平台订单强制结单接口 - 请求:POST /houtai/kpqzjd - 参数:{"username": "客服账号ID", "dingdan_id": "订单ID"} - 认证:JWT - """ - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取参数 - username = request.data.get('username', '').strip() - dingdan_id = request.data.get('dingdan_id', '').strip() - if not username or not dingdan_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 身份验证 - current_user = request.user - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '003aa' not in permissions: - return Response({'code': 403, 'msg': '您无权查处理订单'}) - - # 4. 查询订单并加锁 - try: - with transaction.atomic(): - order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id) - - # 5. 校验订单状态必须为8(结算中) - if order.zhuangtai not in (2,8): - return Response({'code': 400, 'msg': f'订单状态不是结算中,当前状态: {order.zhuangtai}'}) - - # 获取关键字段 - dispatch_type = order.dispatch_type # 1=我方派单,2=对方派单 - is_cross = order.is_cross # 0=本地,1=跨平台 - partner_club_id = order.partner_club_id - partner_order_id = order.partner_order_id - dashou_id = order.jiedan_dashou_id - dashou_fencheng = order.dashou_fencheng or Decimal('0') - jine = order.jine or Decimal('0') - fadan_pingtai = order.fadan_pingtai # 1=平台,2=商家 - - # ========== 修复点1:完善跨平台判断 ========== - # 跨平台且被对方打手接单的条件:is_cross==1 且 (partner_club_id 或 partner_order_id 有值) - is_cross_claimed_by_partner = (is_cross == 1 and (partner_club_id or partner_order_id)) - # 如果是跨平台订单但两个字段都为空,则按本地订单处理(即自家打手接单) - - # ========== 修复点2:根据方向处理 ========== - # ---------- 对方派单(我方打手接对方单)---------- - if dispatch_type == 2: - # 创建预结算记录(如果已存在则更新) - pre_settlement, created = PreSettlement.objects.get_or_create( - dingdan_id=dingdan_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'dashou_id': dashou_id, - 'amount': dashou_fencheng, - 'order_amount': jine, - 'status': 0, # 待结算 - 'operator': current_user.phone, - } - ) - if not created: - pre_settlement.partner_order_id = partner_order_id - pre_settlement.partner_club_id = partner_club_id - pre_settlement.dashou_id = dashou_id - pre_settlement.amount = dashou_fencheng - pre_settlement.order_amount = jine - pre_settlement.status = 0 - pre_settlement.process_time = None - pre_settlement.operator = current_user.phone - pre_settlement.save() - - # 更新订单状态 - order.zhuangtai = 3 - order.clkf = current_user.phone - order.save() - - # 更新每日统计表(对方派单方向,使用订单金额) - if partner_club_id: - # 修复点3:使用公共统计函数 - update_daily_dispatch_stat( - direction=2, - partner_club_id=partner_club_id, - success_amount=jine, - success_count=1 - ) - else: - # 如果没有俱乐部ID,使用默认000001 - update_daily_dispatch_stat( - direction=2, - partner_club_id='000001', - success_amount=jine, - success_count=1 - ) - - # 通知对方平台订单已完成(修复点4:移到事务外) - if partner_club_id and partner_order_id: - transaction.on_commit( - lambda: self._notify_partner_order_status( - partner_club_id=partner_club_id, - partner_order_id=partner_order_id, - new_status=3, - current_user=current_user - ) - ) - - # ---------- 我方派单 ---------- - elif dispatch_type == 1: - # 判断是否为跨平台订单且被对方打手接单(即 partner_club_id 或 partner_order_id 有值) - if is_cross_claimed_by_partner: - # 跨平台订单:我方派单给外平台打手 - # 修复点5:处理只有 partner_order_id 没有 partner_club_id 的情况 - real_partner_club_id = partner_club_id or '000001' - - # 更新订单状态 - order.zhuangtai = 3 - order.clkf = current_user.phone - order.save() - - # 更新每日统计表(我方派单方向,使用打手分成金额) - update_daily_dispatch_stat( - direction=1, - partner_club_id=real_partner_club_id, - success_amount=dashou_fencheng, - success_count=1 - ) - - - # ========== 新增:商品和店铺每日统计 ========== - if order.fadan_pingtai == 1: - try: - # 商品每日统计(只要有商品ID就执行) - if order.shangpin_id: - # 从扩展表获取已计算好的收益值(下单时已存储) - pingtai_shouyi = Decimal('0.00') - dianpu_shouyi = Decimal('0.00') - if hasattr(order, 'pingtai_kuozhan'): - pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00') - dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') - - update_shangpin_daily_stat( - shangpin_id=order.shangpin_id, - caozuo_leixing=3, # 3 = 退款 - dingdan_jiage=order.jine, - pingtai_shouyi=pingtai_shouyi, - dianpu_zongshouyi=dianpu_shouyi - ) - logger.info(f"商品统计更新完成,商品ID: {order.shangpin_id}") - - # 店铺每日统计(仅当存在店铺ID时执行) - dianpu_id = None - if hasattr(order, 'pingtai_kuozhan'): - dianpu_id = order.pingtai_kuozhan.dianpu_id - if dianpu_id: - dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') - update_dianpu_daily_stat( - dianpu_id=dianpu_id, - caozuo_leixing=3, # 3 = 退款 - dingdan_jiage=order.jine, - dianpu_shouyi=dianpu_shouyi - ) - logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}") - except Exception as e: - logger.error(f"商品/店铺统计更新失败: {e}") - logger.error(traceback.format_exc()) - # 统计失败不影响主流程 - # ============================================= - - # 通知对方平台订单已完成(异步) - if partner_order_id: - transaction.on_commit( - lambda: self._notify_partner_order_status( - partner_club_id=real_partner_club_id, - partner_order_id=partner_order_id, - new_status=3, - current_user=current_user - ) - ) - else: - # 本地订单(非跨平台 或 跨平台但实际是自家打手接单),按原有逻辑给自家打手结算 - if not dashou_id: - return Response({'code': 400, 'msg': '订单无接单打手'}) - if dashou_fencheng <= 0: - return Response({'code': 400, 'msg': '打手分成金额无效'}) - - # ========== 新增:商品和店铺每日统计 ========== - if order.fadan_pingtai == 1: - try: - # 商品每日统计(只要有商品ID就执行) - if order.shangpin_id: - # 从扩展表获取已计算好的收益值(下单时已存储) - pingtai_shouyi = Decimal('0.00') - dianpu_shouyi = Decimal('0.00') - if hasattr(order, 'pingtai_kuozhan'): - pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00') - dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') - - update_shangpin_daily_stat( - shangpin_id=order.shangpin_id, - caozuo_leixing=3, # 3 = 退款 - dingdan_jiage=order.jine, - pingtai_shouyi=pingtai_shouyi, - dianpu_zongshouyi=dianpu_shouyi - ) - logger.info(f"商品统计更新完成,商品ID: {order.shangpin_id}") - - # 店铺每日统计(仅当存在店铺ID时执行) - dianpu_id = None - if hasattr(order, 'pingtai_kuozhan'): - dianpu_id = order.pingtai_kuozhan.dianpu_id - if dianpu_id: - dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') - update_dianpu_daily_stat( - dianpu_id=dianpu_id, - caozuo_leixing=3, # 3 = 退款 - dingdan_jiage=order.jine, - dianpu_shouyi=dianpu_shouyi - ) - logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}") - except Exception as e: - logger.error(f"商品/店铺统计更新失败: {e}") - logger.error(traceback.format_exc()) - # 统计失败不影响主流程 - # ============================================= - - # 更新打手余额和统计 - try: - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - dashou_profile.chengjiaozongliang += 1 - dashou_profile.yue += dashou_fencheng - dashou_profile.zonge += dashou_fencheng - dashou_profile.jinrishouyi += dashou_fencheng - dashou_profile.jinyueshouyi += dashou_fencheng - dashou_profile.save() - except (UserMain.DoesNotExist, ObjectDoesNotExist): - logger.warning(f"本地订单结算:打手 {dashou_id} 不存在,跳过") - - # 如果是商家订单,更新商家成交单量 - if fadan_pingtai == 2: - try: - shangjia_ext = order.shangjia_kuozhan - if shangjia_ext and shangjia_ext.shangjia_id: - shangjia_user = UserMain.objects.get(yonghuid=shangjia_ext.shangjia_id) - shangjia_profile = shangjia_user.shop_profile - shangjia_profile.chengjiao += 1 - shangjia_profile.save() - except Exception as e: - logger.warning(f"商家订单更新失败: {str(e)}") - - # 更新订单状态 - order.zhuangtai = 3 - order.clkf = current_user.phone - order.save() - - # 本地订单不涉及跨平台统计,无需更新 DailyDispatchStat - - else: - return Response({'code': 400, 'msg': '未知的派单方向'}) - - - if dispatch_type == 1 and is_cross_claimed_by_partner: - pass - # 判断是否为跨平台订单且被对方打手接单(即 partner_club_id 或 partner_order_id 有值) - elif dispatch_type == 2: - pass - else: - - - # 🔥 新增:调用打手每日统计(成交) - try: - update_dashou_daily_by_action( - yonghuid=order.jiedan_dashou_id, - amount=dashou_fencheng, - action=2 # 2 表示成交(结算) - ) - logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功") - except Exception as e: - logger.error(f"打手每日统计更新失败: {e}") - # 不影响主流程 - - if order.fadan_pingtai == 2: - shangjia_ext = order.shangjia_kuozhan - # ✅ 新方法(行为驱动,更安全) - update_shangjia_daily( - yonghuid=shangjia_ext.shangjia_id, - amount=Decimal(str(order.jine)), - action=2 # 2 = 结算 - ) - else: - pass - - # 7. 更新客服统计 - kefu.jinrichuli += 1 - kefu.jinyuechuli += 1 - kefu.zongchuli += 1 - kefu.save() - - # 8. 记录操作日志 - logger.info(f"强制结单成功,订单 {dingdan_id},方向 {dispatch_type},客服 {username}") - - return Response({'code': 0, 'msg': '强制结单成功'}) - - except Dingdan.DoesNotExist: - logger.warning(f"订单不存在: {dingdan_id}") - return Response({'code': 404, 'msg': '订单不存在'}) - except Exception as e: - logger.error(f"强制结单接口异常: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - # ========== 辅助方法 ========== - def _notify_partner_order_status(self, partner_club_id, partner_order_id, new_status, current_user): - """ - 通知对方平台订单状态变更(已在事务外调用) - """ - try: - # 获取我方俱乐部ID - my_club_config = ClubConfig.objects.filter(is_self=1).first() - if not my_club_config: - logger.error("未配置我方俱乐部信息,无法通知对方") - return - self_club_id = my_club_config.club_id - - # 查询对方服务器域名 - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain - if not partner_domain: - logger.error(f"对方俱乐部 {partner_club_id} 未配置域名") - return - - url = partner_domain.rstrip('/') + '/houtai/dfddtk' - payload = { - 'order_id': partner_order_id, - 'club_id': self_club_id, - 'status': new_status - } - resp = requests.post(url, json=payload, timeout=3) - if resp.status_code == 200: - resp_json = resp.json() - if resp_json.get('code') == 0: - # 更新跨平台订单扩展表中的对方状态 - cross_data, _ = CrossPlatformOrderData.objects.get_or_create( - dingdan_id=partner_order_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'partner_order_status': new_status, - 'partner_dashou_id': '', - 'partner_amount': Decimal('0') - } - ) - if not _: - cross_data.partner_order_status = new_status - cross_data.save(update_fields=['partner_order_status']) - logger.info(f"通知对方订单状态成功: {partner_order_id} -> {new_status}") - else: - logger.warning(f"对方接口返回错误: {resp_json.get('msg')}") - else: - logger.warning(f"对方接口HTTP错误: {resp.status_code}") - except requests.exceptions.Timeout: - logger.error(f"通知对方平台超时: {partner_club_id}") - except Exception as e: - logger.error(f"通知对方平台异常: {str(e)}") - - - - -class CrossPlatformTransferHallView(APIView): - """ - 跨平台订单转移大厅接口 - 请求:POST /houtai/kpzydt - 参数:{"username": "客服账号ID", "dingdan_id": "订单ID"} - 认证:JWT - """ - - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取参数 - username = request.data.get('username', '').strip() - dingdan_id = request.data.get('dingdan_id', '').strip() - if not username or not dingdan_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 客服身份验证 - current_user = request.user - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '003aa' not in permissions: - return Response({'code': 403, 'msg': '您无权处理俱乐部订单'}) - - # 4. 查询订单并加锁 - try: - with transaction.atomic(): - order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id) - - # 5. 状态校验 - if order.zhuangtai not in [2, 8, 4]: - return Response({'code': 400, 'msg': f'订单状态不允许转移大厅,当前状态: {order.zhuangtai}'}) - - # 获取关键字段 - is_cross = order.is_cross - dispatch_type = order.dispatch_type - partner_club_id = order.partner_club_id - partner_order_id = order.partner_order_id - current_dashou_id = order.jiedan_dashou_id - zhiding_id = order.zhiding_id - - # ========== 判断是否需要通知对方平台 ========== - # 条件:跨平台 且 我方派单(dispatch_type=1) 且 (partner_club_id 或 partner_order_id 有值) - need_notify = (is_cross == 1 and (partner_club_id or partner_order_id)) - - # ========== 判断是否需要修改本地打手状态 ========== - # 当接单打手是我方打手时,才需要修改状态(改为空闲1) - # 我方打手的情况: - # 1. 非跨平台订单 (is_cross != 1) - # 2. 跨平台且对方派单 (dispatch_type == 2) —— 此时打手是我方 - # 3. 跨平台且我方派单但没有对方信息 (即 need_notify == False) —— 此时打手也是我方 - need_update_dashou_status = (current_dashou_id and - (is_cross != 1 or dispatch_type == 2 or not need_notify)) - - # ========== 记录打手历史(无论打手是谁,只要有ID就记录) ========== - if current_dashou_id: - last_history = OrderDashouHistory.objects.filter( - dingdan_id=dingdan_id - ).order_by('-times').first() - next_times = (last_history.times + 1) if last_history else 1 - OrderDashouHistory.objects.create( - dingdan_id=dingdan_id, - dashou_id=current_dashou_id, - times=next_times, - operator=username - ) - - # ========== 修改本地打手状态(仅当是我方打手时) ========== - if need_update_dashou_status: - try: - dashou_profile = UserDashou.objects.get(user__yonghuid=current_dashou_id) - dashou_profile.zhuangtai = 1 - dashou_profile.save() - logger.info(f"打手 {current_dashou_id} 状态改为空闲") - except ObjectDoesNotExist: - logger.warning(f"打手 {current_dashou_id} 不存在,跳过状态更新") - - # ========== 更新订单状态(清空打手,改为1或7) ========== - if zhiding_id: - order.zhuangtai = 7 # 指定中 - else: - order.zhuangtai = 1 # 下单中 - order.jiedan_dashou_id = None - order.clkf = username - order.save() - - # ========== 如果需要通知对方平台,异步发送通知 ========== - if need_notify: - # 确定对方俱乐部ID(如果没有则用默认000001) - real_partner_club_id = partner_club_id if partner_club_id else '000001' - real_partner_order_id = partner_order_id if partner_order_id else dingdan_id - - # 获取我方俱乐部ID - my_club_config = ClubConfig.objects.filter(is_self=1).first() - if my_club_config: - self_club_id = my_club_config.club_id - # 查询对方域名 - try: - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=real_partner_club_id) - partner_domain = club_rel.partner_domain - if partner_domain: - url = partner_domain.rstrip('/') + '/houtai/kpttzzydt' - payload = { - 'order_id': real_partner_order_id, - 'club_id': self_club_id - } - - def notify(): - try: - resp = requests.post(url, json=payload, timeout=3) - if resp.status_code == 200: - resp_json = resp.json() - if resp_json.get('code') == 0: - # 更新跨平台订单扩展表中的对方状态 - cross_data, created = CrossPlatformOrderData.objects.get_or_create( - dingdan_id=dingdan_id, - defaults={ - 'partner_order_id': real_partner_order_id, - 'partner_club_id': real_partner_club_id, - 'partner_order_status': resp_json.get('data', {}).get('partner_status', 0), - 'partner_dashou_id': '', - 'partner_amount': order.jine or 0, - } - ) - if not created: - cross_data.partner_order_status = resp_json.get('data', {}).get('partner_status', 0) - cross_data.save(update_fields=['partner_order_status']) - logger.info(f"转移大厅通知对方成功: {dingdan_id}") - - # ========== 新增代码开始 ========== - # 对方成功转移大厅,清空我方订单中的对方相关信息 - # ========== 新增代码开始 ========== - # 只有我方派单时才清空对方平台信息,对方派单则保留 - if dispatch_type == 1: - Dingdan.objects.filter(dingdan_id=dingdan_id).update( - partner_club_id=None, - partner_order_id=None, - jiedan_dashou_id=None, - zhuangtai=1 - ) - logger.info(f"我方派单,已清空订单 {dingdan_id} 的对方信息") - else: - # 对方派单,只清空打手并恢复状态,保留对方信息 - Dingdan.objects.filter(dingdan_id=dingdan_id).update( - jiedan_dashou_id=None, - zhuangtai=1 - ) - logger.info(f"对方派单,只清空打手,保留对方信息,订单 {dingdan_id}") - # ========== 新增代码结束 ========== - # ========== 新增代码结束 ========== - - else: - logger.warning(f"对方平台返回错误: {resp_json.get('msg')}") - else: - logger.warning(f"对方接口HTTP错误: {resp.status_code}") - except Exception as e: - logger.error(f"转移大厅通知对方异常: {str(e)}") - - threading.Thread(target=notify).start() - except Club.DoesNotExist: - logger.error(f"未找到对方俱乐部配置: partner_club_id={real_partner_club_id}") - except Exception as e: - logger.error(f"查询对方平台配置失败: {str(e)}") - - # ========== 更新客服统计 ========== - kefu.jinrichuli += 1 - kefu.jinyuechuli += 1 - kefu.zongchuli += 1 - kefu.save() - - return Response({'code': 0, 'msg': '转移大厅成功'}) - - except Dingdan.DoesNotExist: - return Response({'code': 404, 'msg': '订单不存在'}) - except Exception as e: - logger.error(f"转移大厅接口异常: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - -class PartnerTransferHallNotifyView(APIView): - """ - 对方平台转移大厅通知接口 - 对方调用此接口通知我方订单已转移大厅,我方同步更新订单状态 - 请求:POST /houtai/kpttzzydt - 参数:{ - "order_id": "对方平台的订单ID", - "club_id": "对方俱乐部ID" - } - 无需认证 - 返回:{ - "code": 0, - "msg": "success", - "data": { - "partner_status": 1 # 我方订单当前状态 - } - } - """ - parser_classes = [JSONParser] - permission_classes = [AllowAny] - - def post(self, request): - # token = request.data.get('token') - # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200) - try: - # 1. 获取参数 - partner_order_id = request.data.get('order_id', '').strip() - partner_club_id = request.data.get('club_id', '').strip() - if not partner_order_id or not partner_club_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 查询我方订单 - try: - order = Dingdan.objects.get( - dingdan_id=partner_order_id, - partner_club_id=partner_club_id - ) - except Dingdan.DoesNotExist: - logger.warning(f"对方转移大厅通知:订单不存在 partner_order_id={partner_order_id}, partner_club_id={partner_club_id}") - return Response({'code': 404, 'msg': '订单不存在'}) - - # 3. 加锁处理 - with transaction.atomic(): - order = Dingdan.objects.select_for_update().get(id=order.id) - - current_status = order.zhuangtai - current_dashou_id = order.jiedan_dashou_id - - # 4. 根据订单状态处理 - if current_status == 3: - # 已完成状态,需要检查预结算记录 - try: - pre_settlement = PreSettlement.objects.select_for_update().get( - dingdan_id=order.dingdan_id, - dashou_id=current_dashou_id - ) - # 如果预结算状态是0(待结算),改为2(已拒绝) - if pre_settlement.status == 0: - pre_settlement.status = 2 - pre_settlement.process_time = timezone.now() - pre_settlement.operator = partner_club_id # 记录操作方 - pre_settlement.save() - logger.info(f"预结算记录已拒绝: {order.dingdan_id}") - else: - logger.info(f"预结算记录已是其他状态 {pre_settlement.status},不重复修改") - except PreSettlement.DoesNotExist: - logger.warning(f"订单 {order.dingdan_id} 已完成但无预结算记录,无法转移大厅") - return Response({'code': 400, 'msg': '订单已完成且无预结算记录,无法转移大厅'}) - - # 5. 记录打手历史(如果有接单打手) - if current_dashou_id: - last_history = OrderDashouHistory.objects.filter( - dingdan_id=order.dingdan_id - ).order_by('-times').first() - next_times = (last_history.times + 1) if last_history else 1 - OrderDashouHistory.objects.create( - dingdan_id=order.dingdan_id, - dashou_id=current_dashou_id, - times=next_times, - operator=partner_club_id # 记录操作方 - ) - # 将打手状态改为空闲 - try: - dashou_profile = UserDashou.objects.get(user__yonghuid=current_dashou_id) - dashou_profile.zhuangtai = 1 - dashou_profile.save() - - # 打手退款每日统计 - if order.dispatch_type == 2: - try: - update_dashou_daily_by_action( - yonghuid=current_dashou_id, - amount=dashou_profile.dashou_fencheng, - action=3 # 退款 - ) - except Exception as e: - logger.error(f"记录打手统计错误: {str(e)}") - except ObjectDoesNotExist: - logger.warning(f"打手 {current_dashou_id} 不存在,跳过状态更新") - - # 6. 更新订单状态为1(下单中)或7(指定中) - # 7. 根据派单方向清理对方信息 - if order.dispatch_type == 1: - # 我方派单:清空对方俱乐部ID和订单ID - order.partner_club_id = None - order.partner_order_id = None - # 对方派单时保留对方信息,不清空 - - # 8. 更新订单状态 - if order.zhiding_id: - order.zhuangtai = 7 # 指定中 - else: - order.zhuangtai = 1 # 下单中 - - - order.jiedan_dashou_id = None - order.clkf = partner_club_id # 记录操作方 - order.save() - - logger.info(f"对方转移大厅通知成功: 订单 {order.dingdan_id},新状态 {order.zhuangtai}") - - # 7. 返回我方订单状态 - return Response({ - 'code': 0, - 'msg': 'success', - 'data': { - 'partner_status': order.zhuangtai - } - }) - - except Exception as e: - logger.error(f"对方转移大厅通知接口异常: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - - - - - -logger = logging.getLogger(__name__) - - -# ========== 1. 获取俱乐部设置数据接口 ========== -class GetSettingsView(APIView): - """ - 获取跨平台设置页面数据 - 请求:POST /houtai/hqkptsz - 参数:{"username": "客服账号ID"} - 权限:权限码2且开启 - 返回:包含自己俱乐部信息(含客服字段)和合作俱乐部列表 - """ - - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 客服身份验证 - current_user = request.user - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '004aa' not in permissions: - return Response({'code': 403, 'msg': '您无权查看俱乐部配置'}) - - # 4. 查询数据 - try: - # 4.1 自己俱乐部配置(is_self=1) - self_config = ClubConfig.objects.filter(is_self=1).first() - if not self_config: - logger.error("未找到自己俱乐部配置") - return Response({'code': 500, 'msg': '系统配置错误'}) - - self_club = { - 'club_id': self_config.club_id, - 'club_nickname': self_config.club_nickname, - 'club_avatar': self_config.club_avatar, - 'backend_domain': self_config.backend_domain, - 'chat_api_key': self_config.chat_api_key, - 'chat_api_id': self_config.chat_api_id, - 'customer_service_api_url': self_config.customer_service_api_url, - 'customer_service_corp_id': self_config.customer_service_corp_id, - 'storage_bucket_domain': self_config.storage_bucket_domain, - } - - # 4.2 合作俱乐部列表(Club 表中本俱乐部ID = self_config.club_id) - partner_records = Club.objects.filter(club_id=self_config.club_id) - partner_clubs = [] - for record in partner_records: - partner_config = ClubConfig.objects.filter( - club_id=record.partner_club_id, - is_self=0 - ).first() - partner_clubs.append({ - 'club_id': record.partner_club_id, - 'club_nickname': partner_config.club_nickname if partner_config else record.partner_club_id, - 'club_avatar': partner_config.club_avatar if partner_config else '', - 'is_interop_enabled': record.is_interop_enabled, - 'is_receive_enabled': record.is_receive_enabled, - }) - - return Response({'code': 0, 'data': {'self_club': self_club, 'partner_clubs': partner_clubs}}) - - except Exception as e: - logger.error(f"获取设置失败: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - -# ========== 2. 获取订单类型映射接口 ========== -class GetMappingsView(APIView): - """ - 获取指定合作俱乐部的订单类型映射 - 请求:POST /houtai/hqqtjlbsj - 参数:{"username": "客服账号ID", "partner_club_id": "对方俱乐部ID"} - 权限:权限码2 - 返回:对方订单类型列表、我方订单类型列表、当前映射关系 - """ - - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取参数 - username = request.data.get('username', '').strip() - partner_club_id = request.data.get('partner_club_id', '').strip() - if not username or not partner_club_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 客服身份验证 - current_user = request.user - - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '004aa' not in permissions: - return Response({'code': 403, 'msg': '您无权查看俱乐部配置'}) - - try: - # 4. 获取我方所有商品类型(订单类型) - our_types = ShangpinLeixing.objects.all().values('id', 'jieshao', 'tupian_url') - our_type_list = [ - {'type_id': t['id'], 'type_name': t['jieshao'], 'icon': t['tupian_url']} - for t in our_types - ] - - # 5. 获取对方订单类型(从映射表去重) - mappings_query = OrderTypeMapping.objects.filter(partner_club_id=partner_club_id) - partner_types_dict = {} - for m in mappings_query: - partner_types_dict[m.partner_type_id] = { - 'type_id': m.partner_type_id, - 'type_name': m.partner_type_name, - 'icon': '' - } - partner_type_list = list(partner_types_dict.values()) - - # 6. 获取当前映射关系 - mapping_list = [ - {'partner_type_id': m.partner_type_id, 'our_type_id': m.our_type_id} - for m in mappings_query - ] - - return Response({ - 'code': 0, - 'data': { - 'partner_types': partner_type_list, - 'our_types': our_type_list, - 'mappings': mapping_list - } - }) - - except Exception as e: - logger.error(f"获取映射失败: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - -# ========== 3. 保存设置接口(自己俱乐部 + 合作俱乐部开关)========== -class UpdateSettingsView(APIView): - """ - 保存俱乐部设置(自己俱乐部信息 + 合作俱乐部开关) - 请求:POST /houtai/xgjlbsj - 参数:{ - "username": "客服账号ID", - "self_club": { - "club_nickname": "...", - "backend_domain": "...", - "chat_api_key": "...", - "chat_api_id": "...", - "customer_service_api_url": "...", - "customer_service_corp_id": "...", - "storage_bucket_domain": "..." - }, - "partner_updates": [ - {"club_id": "654321", "is_interop_enabled": true, "is_receive_enabled": false} - ] - } - 权限:权限码2 - 返回:{"code": 0, "msg": "success"} - """ - - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 客服身份验证 - current_user = request.user - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '004aa' not in permissions: - return Response({'code': 403, 'msg': '您无权修改俱乐部配置'}) - - # 4. 获取自己俱乐部当前配置 - try: - self_config = ClubConfig.objects.get(is_self=1) - except ObjectDoesNotExist: - logger.error("未找到自己俱乐部配置") - return Response({'code': 500, 'msg': '系统配置错误'}) - - # 5. 更新自己俱乐部配置(包含客服字段) - self_updates = request.data.get('self_club', {}) - if self_updates: - allowed_fields = [ - 'club_nickname', 'backend_domain', 'chat_api_key', 'chat_api_id', - 'customer_service_api_url', 'customer_service_corp_id', - 'storage_bucket_domain' - ] - fields_to_update = [] - for field in allowed_fields: - if field in self_updates: - setattr(self_config, field, self_updates[field]) - fields_to_update.append(field) - if fields_to_update: - self_config.save(update_fields=fields_to_update) - - # 6. 更新合作俱乐部开关 - partner_updates = request.data.get('partner_updates', []) - for update in partner_updates: - club_id = update.get('club_id') - if not club_id: - continue - try: - partner_record = Club.objects.get(club_id=self_config.club_id, partner_club_id=club_id) - if 'is_interop_enabled' in update: - partner_record.is_interop_enabled = update['is_interop_enabled'] - if 'is_receive_enabled' in update: - partner_record.is_receive_enabled = update['is_receive_enabled'] - partner_record.save(update_fields=['is_interop_enabled', 'is_receive_enabled']) - except ObjectDoesNotExist: - logger.warning(f"合作俱乐部记录不存在: {club_id}") - - # 7. 异步通知所有合作俱乐部(如果自己数据有修改) - if self_updates: - self._notify_all_partners(self_config, self_updates) - - return Response({'code': 0, 'msg': 'success'}) - - def _notify_all_partners(self, self_config, updates): - """异步通知所有合作俱乐部更新我方配置""" - partner_records = Club.objects.filter(club_id=self_config.club_id) - if not partner_records: - return - - data_to_send = { - 'club_id': self_config.club_id, - 'updates': updates - } - - def notify(partner_domain): - try: - url = partner_domain.rstrip('/') + '/houtai/xgzjsjcs' - resp = requests.post(url, json=data_to_send, timeout=3) - if resp.status_code != 200: - logger.warning(f"通知 {partner_domain} 失败,状态码 {resp.status_code}") - except Exception as e: - logger.error(f"通知 {partner_domain} 异常: {str(e)}") - - for record in partner_records: - domain = record.partner_domain - if domain: - threading.Thread(target=notify, args=(domain,)).start() - - - - - -class UpdateMappingsView(APIView): - """ - 保存指定合作俱乐部的订单类型映射 - 请求:POST /houtai/xgdfddys - 参数:{ - "username": "客服手机号", - "partner_club_id": "对方俱乐部ID", - "mappings": [ - {"partner_type_id": 1, "our_type_id": 10}, - {"partner_type_id": 2, "our_type_id": 11} - ] - } - 权限:权限码2 - 返回:{"code": 0, "msg": "success"} - """ - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取参数 - username = request.data.get('username', '').strip() - partner_club_id = request.data.get('partner_club_id', '').strip() - mappings = request.data.get('mappings', []) - - if not username or not partner_club_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 客服身份验证 - current_user = request.user - - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '004aa' not in permissions: - return Response({'code': 403, 'msg': '您无权修改俱乐部配置'}) - - try: - with transaction.atomic(): - # 4. 获取当前该俱乐部所有现有映射(按 partner_type_id 索引) - existing_mappings = { - m.partner_type_id: m - for m in OrderTypeMapping.objects.filter(partner_club_id=partner_club_id) - } - - # 5. 处理前端传来的映射(更新或新增) - processed_partner_ids = set() - for mapping in mappings: - partner_type_id = mapping.get('partner_type_id') - our_type_id = mapping.get('our_type_id') - if partner_type_id is None or our_type_id is None: - continue - processed_partner_ids.add(partner_type_id) - - # 获取我方类型名称 - our_type_name = '' - try: - our_type = ShangpinLeixing.objects.get(id=our_type_id) - our_type_name = our_type.jieshao - except ObjectDoesNotExist: - logger.warning(f"我方订单类型不存在: {our_type_id}") - - # 查找是否存在该对方类型的映射 - if partner_type_id in existing_mappings: - # 更新现有记录 - mapping_obj = existing_mappings[partner_type_id] - mapping_obj.our_type_id = our_type_id - mapping_obj.our_type_name = our_type_name - mapping_obj.save(update_fields=['our_type_id', 'our_type_name']) - # 从字典中移除,以便后续知道哪些被删除了 - del existing_mappings[partner_type_id] - else: - # 新增记录 - # 对方类型名称:优先从现有映射中查找(可能被其他俱乐部共享?不,每个俱乐部独立) - # 由于该对方类型没有映射过,我们暂时无法获取名称,可留空或使用默认值。 - # 但为保证数据完整性,我们尝试从已有记录中查找(如果该俱乐部之前删除过又重建,则可能丢失名称) - # 更稳妥的方式是前端传递 partner_type_name,但当前未要求,因此此处留空并记录警告。 - partner_type_name = '' - logger.warning(f"新增映射时对方类型名称缺失: partner_club_id={partner_club_id}, partner_type_id={partner_type_id}") - - OrderTypeMapping.objects.create( - partner_club_id=partner_club_id, - partner_type_id=partner_type_id, - partner_type_name=partner_type_name, - our_type_id=our_type_id, - our_type_name=our_type_name - ) - - # 6. 删除前端未传入的映射(即用户取消的映射) - for partner_type_id, mapping_obj in existing_mappings.items(): - mapping_obj.delete() - logger.info(f"删除映射: partner_club_id={partner_club_id}, partner_type_id={partner_type_id}") - - return Response({'code': 0, 'msg': 'success'}) - - except Exception as e: - logger.error(f"保存映射失败: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) -# ========== 5. 接收对方平台修改数据接口 ========== -class PartnerUpdateSelfConfigView(APIView): - """ - 接收对方平台修改自己俱乐部配置的通知 - 请求:POST /houtai/xgzjsjcs - 参数:{ - "club_id": "对方俱乐部ID", - "updates": { - "club_nickname": "新昵称", - "backend_domain": "新域名", - "chat_api_key": "新key", - "chat_api_id": "新id", - "customer_service_api_url": "新URL", - "customer_service_corp_id": "新企业ID", - "storage_bucket_domain": "新存储桶" - } - } - 无需认证,对方平台可信调用 - 返回:{"code": 0, "msg": "success"} - """ - parser_classes = [JSONParser] - - def post(self, request): - # token = request.data.get('token') - # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200) - # 1. 获取参数 - club_id = request.data.get('club_id', '').strip() - updates = request.data.get('updates', {}) - - if not club_id or not updates: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 查询对方俱乐部的配置记录(is_self=0) - try: - config = ClubConfig.objects.get(club_id=club_id, is_self=0) - except ClubConfig.DoesNotExist: - logger.warning(f"对方俱乐部配置不存在: {club_id}") - return Response({'code': 404, 'msg': '俱乐部不存在'}) - - # 3. 允许修改的字段白名单(包含客服字段) - allowed_fields = [ - 'club_nickname', - 'backend_domain', - 'chat_api_key', - 'chat_api_id', - 'customer_service_api_url', - 'customer_service_corp_id', - 'storage_bucket_domain' - ] - - # 4. 更新字段 - with transaction.atomic(): - fields_updated = [] - for field in allowed_fields: - if field in updates: - new_value = updates[field] - setattr(config, field, new_value) - fields_updated.append(field) - if fields_updated: - config.save(update_fields=fields_updated) - logger.info(f"对方俱乐部 {club_id} 配置已更新: {fields_updated}") - else: - logger.info(f"对方俱乐部 {club_id} 无有效字段更新") - - return Response({'code': 0, 'msg': 'success'}) - - - - -# apps/houtai/views.py - - - - - -# ========== 1. 获取合作俱乐部列表接口(完整) ========== -class GetPartnerClubsView(APIView): - """ - 获取我方合作俱乐部列表(用于统计页面) - 请求:POST /houtai/kpthqpt - 参数:{"username": "客服手机号"} - 权限:权限码2且开启 - 返回:{"code":0, "data":[{"club_id":"xxx","club_nickname":"xxx","club_avatar":"xxx"}]} - """ - - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 2. 客服身份验证 - current_user = request.user - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '004bb' not in permissions: - return Response({'code': 403, 'msg': '您无权查看俱乐部配置'}) - - # 4. 获取我方俱乐部ID - try: - self_config = ClubConfig.objects.get(is_self=1) - except ObjectDoesNotExist: - logger.error("未找到自己俱乐部配置") - return Response({'code': 500, 'msg': '系统配置错误'}) - - # 5. 查询所有合作俱乐部 - try: - records = Club.objects.filter(club_id=self_config.club_id) - result = [] - for record in records: - partner_config = ClubConfig.objects.filter( - club_id=record.partner_club_id, - is_self=0 - ).first() - result.append({ - 'club_id': record.partner_club_id, - 'club_nickname': partner_config.club_nickname if partner_config else record.partner_club_id, - 'club_avatar': partner_config.club_avatar if partner_config else '', - }) - return Response({'code': 0, 'data': result}) - except Exception as e: - logger.error(f"查询合作俱乐部失败: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - -# ========== 2. 获取统计数据接口(完整) ========== - - - - - -class GetStatsView(APIView): - """ - 获取派单统计数据(按日或按月) - 请求:POST /houtai/hqpfsj - 参数:{ - "username": "客服手机号", - "club_id": "对方俱乐部ID", - "direction": 1, # 1=我方派单,2=对方派单 - "granularity": "day", # "day" 或 "month" - # 趋势图参数(可选,不传则使用默认值): - "trend_year_month": "YYYY-MM", # 按日时表示月份,缺省当前月 - "trend_year": "YYYY", # 按月时表示年份,缺省当前年 - # KPI参数(可选,不传则使用默认值): - "date": "YYYY-MM-DD", # 按日时表示日期,缺省当天 - "year_month": "YYYY-MM" # 按月时表示月份,缺省当前月 - } - 权限:权限码2 - 返回:{ - "code": 0, - "data": { - "summary": { ... }, - "chart": { ... } - } - } - """ - - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取基础参数 - username = request.data.get('username', '').strip() - club_id = request.data.get('club_id', '').strip() - direction = request.data.get('direction') - granularity = request.data.get('granularity') - - # 基础参数校验 - if not username: - return Response({'code': 400, 'msg': 'username 不能为空'}) - if not club_id: - return Response({'code': 400, 'msg': 'club_id 不能为空'}) - if direction is None: - return Response({'code': 400, 'msg': 'direction 不能为空'}) - if granularity not in ('day', 'month'): - return Response({'code': 400, 'msg': 'granularity 必须是 day 或 month'}) - try: - direction = int(direction) - except (TypeError, ValueError): - return Response({'code': 400, 'msg': 'direction 必须为整数'}) - - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '004bb' not in permissions: - return Response({'code': 403, 'msg': '您无权查看俱乐部配置'}) - - # 4. 获取当前时间用于默认值 - now = datetime.now() - - # 5. 处理趋势图参数(默认当前月/年) - if granularity == 'day': - trend_year_month = request.data.get('trend_year_month') - if not trend_year_month: - trend_year_month = now.strftime('%Y-%m') - try: - year, month = map(int, trend_year_month.split('-')) - start_date = datetime(year, month, 1).date() - _, days_in_month = monthrange(year, month) - end_date = datetime(year, month, days_in_month).date() - except (ValueError, TypeError): - # 如果格式错误,回退到当前月 - year, month = now.year, now.month - start_date = datetime(year, month, 1).date() - _, days_in_month = monthrange(year, month) - end_date = datetime(year, month, days_in_month).date() - else: - trend_year = request.data.get('trend_year') - if not trend_year: - trend_year = str(now.year) - try: - year = int(trend_year) - except (ValueError, TypeError): - year = now.year - start_date = datetime(year, 1, 1).date() - end_date = datetime(year, 12, 31).date() - - # 6. 处理KPI参数(默认当天/当月) - if granularity == 'day': - date_str = request.data.get('date') - if not date_str: - date_str = now.strftime('%Y-%m-%d') - try: - target_date = datetime.strptime(date_str, '%Y-%m-%d').date() - except (ValueError, TypeError): - target_date = now.date() - else: - year_month_str = request.data.get('year_month') - if not year_month_str: - year_month_str = now.strftime('%Y-%m') - try: - year, month = map(int, year_month_str.split('-')) - start_date_kpi = datetime(year, month, 1).date() - _, days_in_month = monthrange(year, month) - end_date_kpi = datetime(year, month, days_in_month).date() - except (ValueError, TypeError): - year, month = now.year, now.month - start_date_kpi = datetime(year, month, 1).date() - _, days_in_month = monthrange(year, month) - end_date_kpi = datetime(year, month, days_in_month).date() - - # 7. 查询趋势图数据 - response_data = { - 'summary': {'dispatch_count': 0, 'dispatch_amount': 0.0, 'success_count': 0, 'success_amount': 0.0}, - 'chart': {'labels': [], 'dispatch_counts': [], 'dispatch_amounts': [], 'success_counts': [], 'success_amounts': []} - } - - try: - if granularity == 'day': - qs = DailyDispatchStat.objects.filter( - partner_club_id=club_id, - direction=direction, - date__gte=start_date, - date__lte=end_date - ).order_by('date') - data_dict = {} - for item in qs: - data_dict[item.date] = { - 'dispatch_count': item.dispatch_count, - 'dispatch_amount': float(item.dispatch_amount), - 'success_count': item.success_count, - 'success_amount': float(item.success_amount), - } - labels = [] - dispatch_counts = [] - dispatch_amounts = [] - success_counts = [] - success_amounts = [] - current = start_date - while current <= end_date: - labels.append(current.strftime('%d')) - day_data = data_dict.get(current, {}) - dispatch_counts.append(day_data.get('dispatch_count', 0)) - dispatch_amounts.append(day_data.get('dispatch_amount', 0.0)) - success_counts.append(day_data.get('success_count', 0)) - success_amounts.append(day_data.get('success_amount', 0.0)) - current += timedelta(days=1) - response_data['chart'] = { - 'labels': labels, - 'dispatch_counts': dispatch_counts, - 'dispatch_amounts': dispatch_amounts, - 'success_counts': success_counts, - 'success_amounts': success_amounts, - } - else: # month - qs = DailyDispatchStat.objects.filter( - partner_club_id=club_id, - direction=direction, - date__gte=start_date, - date__lte=end_date - ).order_by('date') - month_totals = {m: {'dispatch_count': 0, 'dispatch_amount': 0.0, 'success_count': 0, 'success_amount': 0.0} - for m in range(1, 13)} - for item in qs: - m = item.date.month - month_totals[m]['dispatch_count'] += item.dispatch_count - month_totals[m]['dispatch_amount'] += float(item.dispatch_amount) - month_totals[m]['success_count'] += item.success_count - month_totals[m]['success_amount'] += float(item.success_amount) - labels = [] - dispatch_counts = [] - dispatch_amounts = [] - success_counts = [] - success_amounts = [] - for m in range(1, 13): - labels.append(f"{m}月") - dispatch_counts.append(month_totals[m]['dispatch_count']) - dispatch_amounts.append(month_totals[m]['dispatch_amount']) - success_counts.append(month_totals[m]['success_count']) - success_amounts.append(month_totals[m]['success_amount']) - response_data['chart'] = { - 'labels': labels, - 'dispatch_counts': dispatch_counts, - 'dispatch_amounts': dispatch_amounts, - 'success_counts': success_counts, - 'success_amounts': success_amounts, - } - - # 8. 查询KPI数据 - if granularity == 'day': - try: - stat = DailyDispatchStat.objects.get( - partner_club_id=club_id, - direction=direction, - date=target_date - ) - response_data['summary'] = { - 'dispatch_count': stat.dispatch_count, - 'dispatch_amount': float(stat.dispatch_amount), - 'success_count': stat.success_count, - 'success_amount': float(stat.success_amount), - } - except DailyDispatchStat.DoesNotExist: - response_data['summary'] = { - 'dispatch_count': 0, - 'dispatch_amount': 0.0, - 'success_count': 0, - 'success_amount': 0.0, - } - else: # month - qs_kpi = DailyDispatchStat.objects.filter( - partner_club_id=club_id, - direction=direction, - date__gte=start_date_kpi, - date__lte=end_date_kpi - ) - summary = qs_kpi.aggregate( - dispatch_count=Sum('dispatch_count'), - dispatch_amount=Sum('dispatch_amount'), - success_count=Sum('success_count'), - success_amount=Sum('success_amount') - ) - response_data['summary'] = { - 'dispatch_count': summary['dispatch_count'] or 0, - 'dispatch_amount': float(summary['dispatch_amount'] or 0.0), - 'success_count': summary['success_count'] or 0, - 'success_amount': float(summary['success_amount'] or 0.0), - } - - return Response({'code': 0, 'data': response_data}) - - except Exception as e: - logger.error(f"获取统计数据失败: {traceback.format_exc()}") - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - - - -class PunishDashouView(APIView): - """ - 客服处罚打手接口 - 请求:POST /houtai/kpcf - 参数:{ - "username": "客服手机号", - "dingdan_id": "订单ID", - "reason": "处罚原因(可选)" - } - 认证:JWT,权限码1 - 返回:code=0 + 处罚结果 - """ - - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - start_time = time.time() - - # 1. 获取参数 - username = request.data.get('username', '').strip() - dingdan_id = request.data.get('dingdan_id', '').strip() - reason = request.data.get('reason', '').strip() - - if not username or not dingdan_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - try: - # 2. 身份验证 - current_user = request.user - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '003aa' not in permissions: - return Response({'code': 403, 'msg': '您无权处罚'}) - - # 4. 查询订单并加锁 - with transaction.atomic(): - order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id) - - # 5. 订单状态校验(允许处罚的状态:1,2,3,4,5,6,7,8,即除已完成以外的状态?这里宽松处理) - if order.zhuangtai not in [1,2,3,4,5,6,7,8]: - return Response({'code': 400, 'msg': f'订单状态 {order.zhuangtai} 不允许处罚'}) - - # 6. 判断是否已处罚(避免重复) - existing_punish = Chufajilu.objects.filter( - dingdan_id=dingdan_id, - dashouid=order.jiedan_dashou_id - ).exists() - if existing_punish: - return Response({'code': 400, 'msg': '该打手已被处罚过'}) - - # 7. 区分本地处罚还是通知对方平台 - is_cross = order.is_cross - dispatch_type = order.dispatch_type - partner_club_id = order.partner_club_id - partner_order_id = order.partner_order_id - dashou_id = order.jiedan_dashou_id - - # 如果没有接单打手ID,无法处罚 - if not dashou_id: - return Response({'code': 400, 'msg': '订单无接单打手,无法处罚'}) - - # 本地处罚(处罚我方打手) - def local_punish(): - # 扣除打手积分(假设每次扣5分) - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - dashou_profile.jifen -= 5 - dashou_profile.save() - - # 创建处罚记录 - Chufajilu.objects.create( - dingdan_id=dingdan_id, - dashouid=dashou_id, - qingqiuid='', # 请求处罚ID,可空 - chuliid=username, - cfliyou=reason or '', - sqzhuangtai=1, # 成功 - bhliyou='', - ssliyou='', - jifen=5, - ) - logger.info(f"本地处罚打手成功: 订单 {dingdan_id}, 打手 {dashou_id}, 扣分5") - - # 情况A:对方派单(dispatch_type=2)或 本地订单 - if dispatch_type == 2 or not is_cross: - # 直接本地处罚 - local_punish() - return Response({'code': 0, 'msg': '处罚成功'}) - - # 情况B:我方派单且有对方信息(需要通知对方平台) - elif dispatch_type == 1 and partner_club_id and partner_order_id: - # 需要通知对方平台处罚(对方平台打手) - # 获取我方俱乐部ID - my_club_config = ClubConfig.objects.filter(is_self=1).first() - if not my_club_config: - logger.error("未找到我方俱乐部配置") - return Response({'code': 500, 'msg': '系统配置错误'}) - self_club_id = my_club_config.club_id - - # 获取对方服务器域名 - try: - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain - except ObjectDoesNotExist: - logger.error(f"未找到合作关系: {self_club_id} <-> {partner_club_id}") - return Response({'code': 500, 'msg': '未找到对方平台配置'}) - - # 调用对方平台处罚接口,最多重试2次 - url = partner_domain.rstrip('/') + '/houtai/kptcfds' - payload = { - 'order_id': partner_order_id, # 对方平台的订单ID - 'club_id': self_club_id, - 'reason': reason, - 'operator': username - } - timeout = 3 - retries = 2 - success = False - for attempt in range(retries): - try: - resp = requests.post(url, json=payload, timeout=timeout) - if resp.status_code == 200: - resp_json = resp.json() - if resp_json.get('code') == 0: - success = True - logger.info(f"通知对方平台处罚成功: {dingdan_id}") - # 可选:在本平台记录处罚记录(标记已通知对方,但不扣分) - Chufajilu.objects.create( - dingdan_id=dingdan_id, - dashouid=dashou_id, - qingqiuid='', - chuliid=username, - cfliyou=reason or '', - sqzhuangtai=1, - bhliyou='', - ssliyou='', - jifen=0, # 对方扣分,本方不扣 - ) - break - else: - logger.warning(f"对方平台返回错误 (尝试{attempt+1}): {resp_json.get('msg')}") - else: - logger.warning(f"对方平台HTTP错误 (尝试{attempt+1}): {resp.status_code}") - except requests.exceptions.Timeout: - logger.warning(f"请求对方平台超时 (尝试{attempt+1}): {url}") - except Exception as e: - logger.warning(f"请求对方平台异常 (尝试{attempt+1}): {str(e)}") - if not success: - return Response({'code': 500, 'msg': '通知对方平台失败,请稍后重试'}) - - return Response({'code': 0, 'msg': '处罚成功'}) - - else: - # 其他情况(例如我方派单但缺少对方信息),视为本地订单,处罚我方打手 - local_punish() - return Response({'code': 0, 'msg': '处罚成功'}) - - except Dingdan.DoesNotExist: - logger.warning(f"订单不存在: {dingdan_id}") - return Response({'code': 404, 'msg': '订单不存在'}) - except Exception as e: - logger.error(f"处罚接口异常: {traceback.format_exc()}") - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - - - - - -@method_decorator(csrf_exempt, name='dispatch') -class PartnerPunishNotifyView(APIView): - """ - 接收对方平台处罚通知接口 - """ - authentication_classes = [] - permission_classes = [AllowAny] - parser_classes = [JSONParser] - - def post(self, request): - # token = request.data.get('token') - # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200) - try: - order_id = request.data.get('order_id', '').strip() - club_id = request.data.get('club_id', '').strip() - reason = request.data.get('reason', '').strip() - operator = request.data.get('operator', '').strip() - - if not order_id or not club_id: - return Response({'code': 400, 'msg': '参数不完整'}) - - # 事务开始 - with transaction.atomic(): - # 查询订单并加锁 - try: - order = Dingdan.objects.select_for_update().get(dingdan_id=order_id) - except Dingdan.DoesNotExist: - logger.warning(f"处罚通知:订单不存在 {order_id}") - return Response({'code': 404, 'msg': '订单不存在'}) - - dashou_id = order.jiedan_dashou_id - if not dashou_id: - logger.warning(f"订单 {order_id} 无接单打手,无法处罚") - return Response({'code': 400, 'msg': '订单无接单打手'}) - - # 检查是否已处罚 - existing = Chufajilu.objects.filter( - dingdan_id=order_id, - dashouid=dashou_id - ).exists() - if existing: - logger.info(f"订单 {order_id} 打手 {dashou_id} 已被处罚过,跳过") - return Response({'code': 0, 'msg': 'already punished'}) - - # 扣除积分 - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - dashou_profile.jifen -= 5 - dashou_profile.save() - - # 创建处罚记录 - Chufajilu.objects.create( - dingdan_id=order_id, - dashouid=dashou_id, - qingqiuid='', - chuliid=operator, - cfliyou=reason or '', - sqzhuangtai=0, - bhliyou='', - ssliyou='', - jifen=5, - ) - logger.info(f"对方平台处罚成功: 订单 {order_id}, 打手 {dashou_id}") - - return Response({'code': 0, 'msg': 'success'}) - - except Exception as e: - logger.error(f"对方处罚通知接口异常: {traceback.format_exc()}") - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - - - - - - - -# ==================== 接口1:获取俱乐部列表(权限码2) ==================== -class GetClubConfigForPreSettlementView(APIView): - """ - 获取对方俱乐部配置列表(用于预结算页面) - 权限要求:客服身份 + 权限代码2且开启 - """ - permission_classes = [IsAuthenticated] - - def post(self, request): - try: - user = request.user - # 1. 校验客服身份 - try: - kefu = UserKefu.objects.select_related('user').get(user=user) - except ObjectDoesNotExist: - return Response({'code': 403, 'msg': '身份错误,非客服账号'}) - if kefu.zhuangtai != 1: - return Response({'code': 403, 'msg': '客服账号已被禁用'}) - - # 2. 获取请求参数中的 username 并校验 - data = request.data - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '004aa' not in permissions: - return Response({'code': 403, 'msg': '您无权查看俱乐部配置'}) - - # 4. 查询所有对方俱乐部(is_self=0) - club_configs = ClubConfig.objects.filter(is_self=0).only( - 'club_id', 'club_nickname', 'club_avatar', 'storage_bucket_domain' - ) - data_list = [] - for club in club_configs: - data_list.append({ - 'club_id': club.club_id, - 'club_nickname': club.club_nickname or club.club_id, - 'club_avatar': club.club_avatar or '', - 'storage_bucket_domain': club.storage_bucket_domain or '' - }) - return Response({'code': 0, 'msg': 'success', 'data': data_list}) - - except Exception as e: - logger.error(f"获取俱乐部配置失败: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - -# ==================== 接口2:获取预结算订单列表 ==================== -class GetPreSettlementDataView(APIView): - """获取预结算订单列表(支持筛选、分页、统计)""" - permission_classes = [IsAuthenticated] - - def post(self, request): - try: - user = request.user - # 1. 客服身份校验 - try: - kefu = UserKefu.objects.select_related('user').get(user=user) - except ObjectDoesNotExist: - return Response({'code': 403, 'msg': '身份错误,非客服账号'}) - if kefu.zhuangtai != 1: - return Response({'code': 403, 'msg': '客服账号已被禁用'}) - - data = request.data - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限(003aa) - if '004aa' not in permissions: - return Response({'code': 403, 'msg': '您无权查看俱乐部配置'}) - - # 2. 获取筛选参数 - status_filter = data.get('status_filter') # 0=待处理, 1=已处理(包含已拒绝) - club_id = data.get('club_id') - dingdan_id = data.get('dingdan_id') - partner_order_id = data.get('partner_order_id') - dashou_id = data.get('dashou_id') - clkf = data.get('clkf') - date_str = data.get('date') # 创建日期 YYYY-MM-DD - page = int(data.get('page', 1)) - page_size = int(data.get('pageSize', 20)) - - # 3. 构建查询集 - queryset = PreSettlement.objects.all() - - # 状态筛选:0待处理,1已处理(status=1或2) - if status_filter == 0: - queryset = queryset.filter(status=0) - elif status_filter == 1: - queryset = queryset.filter(status__in=[1, 2]) - # 如果status_filter为其他值(如'all'),则不筛选 - - if club_id: - queryset = queryset.filter(partner_club_id=club_id) - if dingdan_id: - queryset = queryset.filter(dingdan_id__icontains=dingdan_id) - if partner_order_id: - queryset = queryset.filter(partner_order_id__icontains=partner_order_id) - if dashou_id: - queryset = queryset.filter(dashou_id__icontains=dashou_id) - if clkf: - queryset = queryset.filter(operator__icontains=clkf) - if date_str: - try: - target_date = datetime.strptime(date_str, '%Y-%m-%d').date() - queryset = queryset.filter(create_time__date=target_date) - except ValueError: - pass - - # 4. 统计数据(待处理总数量和总金额、已处理总数量) - pending_queryset = PreSettlement.objects.filter(status=0) - # 应用相同的筛选条件(除了状态) - if club_id: - pending_queryset = pending_queryset.filter(partner_club_id=club_id) - if dingdan_id: - pending_queryset = pending_queryset.filter(dingdan_id__icontains=dingdan_id) - if partner_order_id: - pending_queryset = pending_queryset.filter(partner_order_id__icontains=partner_order_id) - if dashou_id: - pending_queryset = pending_queryset.filter(dashou_id__icontains=dashou_id) - if clkf: - pending_queryset = pending_queryset.filter(operator__icontains=clkf) - if date_str: - pending_queryset = pending_queryset.filter(create_time__date=target_date) - - pending_count = pending_queryset.count() - pending_amount = pending_queryset.aggregate(total=Sum('order_amount'))['total'] or Decimal('0.00') - - processed_count = PreSettlement.objects.filter(status__in=[1, 2]) - if club_id: - processed_count = processed_count.filter(partner_club_id=club_id) - if dingdan_id: - processed_count = processed_count.filter(dingdan_id__icontains=dingdan_id) - if partner_order_id: - processed_count = processed_count.filter(partner_order_id__icontains=partner_order_id) - if dashou_id: - processed_count = processed_count.filter(dashou_id__icontains=dashou_id) - if clkf: - processed_count = processed_count.filter(operator__icontains=clkf) - if date_str: - processed_count = processed_count.filter(create_time__date=target_date) - processed_count = processed_count.count() - - # 5. 分页 - total = queryset.count() - start = (page - 1) * page_size - end = start + page_size - items = queryset.order_by('-create_time')[start:end] - - # 6. 构造返回数据 - list_data = [] - for item in items: - list_data.append({ - 'dingdan_id': item.dingdan_id, - 'partner_order_id': item.partner_order_id, - 'partner_club_id': item.partner_club_id, - 'dashou_id': item.dashou_id, - 'amount': float(item.amount), - 'order_amount': float(item.order_amount), - 'operator': item.operator, - 'status': item.status, - 'status_text': dict(PreSettlement.STATUS_CHOICES).get(item.status, '未知'), - 'create_time': item.create_time.strftime('%Y-%m-%d %H:%M:%S') if item.create_time else '', - 'process_time': item.process_time.strftime('%Y-%m-%d %H:%M:%S') if item.process_time else '', - }) - - return Response({ - 'code': 0, - 'msg': 'success', - 'data': { - 'list': list_data, - 'total': total, - 'stats': { - 'pending_count': pending_count, - 'pending_amount': float(pending_amount), - 'processed_count': processed_count - } - } - }) - - except Exception as e: - logger.error(f"获取预结算数据失败: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - -# ==================== 接口3:打款/拒绝操作 ==================== -class PreSettlementActionView(APIView): - """预结算打款/拒绝操作(原子事务,保证资金安全)""" - permission_classes = [IsAuthenticated] - - def post(self, request): - try: - user = request.user - # 1. 客服身份校验 - try: - kefu = UserKefu.objects.select_related('user').get(user=user) - except ObjectDoesNotExist: - return Response({'code': 403, 'msg': '身份错误,非客服账号'}) - if kefu.zhuangtai != 1: - return Response({'code': 403, 'msg': '客服账号已被禁用'}) - - data = request.data - - # 1. 获取前端参数 - username = request.data.get('username', '').strip() - if not username: - return Response({'code': 400, 'msg': '缺少username'}) - - # 2. 公共权限校验(验证客服身份,并获取权限列表) - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions # 直接返回错误响应 - - # 3. 检查是否有跨平台管理权限() - if '004aa' not in permissions: - return Response({'code': 403, 'msg': '您无权处理预结算打款'}) - - dingdan_id = data.get('dingdan_id') - action = data.get('action') # 'pay' 或 'reject' - - if not dingdan_id or action not in ['pay', 'reject']: - return Response({'code': 400, 'msg': '参数错误'}) - - with transaction.atomic(): - # 锁定预结算记录(行锁) - pre_settlement = PreSettlement.objects.select_for_update().get(dingdan_id=dingdan_id) - - # 只有状态为0(待处理)才能操作 - if pre_settlement.status != 0: - return Response({'code': 400, 'msg': '该记录已处理,不可重复操作'}) - - if action == 'pay': - # 打款操作:增加打手余额及相关统计 - dashou_id = pre_settlement.dashou_id - amount = pre_settlement.amount - - # 查询打手扩展表 - try: - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - except (UserMain.DoesNotExist, ObjectDoesNotExist): - return Response({'code': 404, 'msg': '打手不存在'}) - - # 使用 F 表达式原子更新余额和统计字段(防止并发覆盖) - dashou_profile.yue = F('yue') + amount - dashou_profile.zonge = F('zonge') + amount - dashou_profile.jinrishouyi = F('jinrishouyi') + amount - dashou_profile.jinyueshouyi = F('jinyueshouyi') + amount - dashou_profile.chengjiaozongliang = F('chengjiaozongliang') + 1 - dashou_profile.save(update_fields=['yue', 'zonge', 'jinrishouyi', 'jinyueshouyi', 'chengjiaozongliang']) - - # 更新预结算状态为已结算 - pre_settlement.status = 1 - pre_settlement.process_time = timezone.now() - pre_settlement.operator = user.phone - pre_settlement.save() - - # ---------- 打手每日统计(成交) ---------- - - try: - update_dashou_daily_by_action( - yonghuid=dashou_id, - amount=amount, - action=2 # 2 表示成交(结算) - ) - except Exception as e: - logger.error(f"打手每日统计更新失败: {e}") - - - logger.info(f"预结算打款成功: 订单 {dingdan_id}, 打手 {dashou_id}, 金额 {amount}") - - logger.info(f"预结算打款成功: 订单 {dingdan_id}, 打手 {dashou_id}, 金额 {amount}") - - elif action == 'reject': - # 拒绝操作:仅修改状态,不涉及资金变动 - pre_settlement.status = 2 - pre_settlement.process_time = timezone.now() - pre_settlement.operator = user.phone - pre_settlement.save() - logger.info(f"预结算拒绝: 订单 {dingdan_id}") - - # ---------- 打手每日统计(成交) ---------- - dashou_id = pre_settlement.dashou_id - amount = pre_settlement.amount - try: - dashou_id = pre_settlement.dashou_id - amount = pre_settlement.amount - update_dashou_daily_by_action( - yonghuid=dashou_id, - amount=amount, - action=3 # 3 表示退款(退款) - ) - except Exception as e: - logger.error(f"打手每日统计更新失败: {e}") - logger.info(f"预结算拒绝: 订单 {dingdan_id}") - - return Response({'code': 0, 'msg': '操作成功'}) - - except PreSettlement.DoesNotExist: - return Response({'code': 404, 'msg': '预结算记录不存在'}) - except Exception as e: - logger.error(f"预结算操作异常: {str(e)}", exc_info=True) - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - - - - - class GetRolePermissionView(APIView): """ 获取所有角色、权限以及角色关联的用户数 @@ -7448,7 +3951,7 @@ class GetProductTypeZoneView(APIView): 返回: code: 0 data: { - type_list: [{id, jieshao, tupian_url, yaoqiuleixing, huiyuan_id, yongjin, shenhezhuangtai, is_cross_enabled, paixu, product_count}, ...], + type_list: [{id, jieshao, tupian_url, yaoqiuleixing, huiyuan_id, yongjin, shenhezhuangtai, paixu, product_count}, ...], zone_list: [{id, mingzi, leixing_id, shenhezhuangtai, paixu}, ...], member_list: [{huiyuan_id, jieshao, jiage}, ...] } @@ -7482,7 +3985,6 @@ class GetProductTypeZoneView(APIView): 'huiyuan_id': t.huiyuan_id or '', 'yongjin': float(t.yongjin) if t.yongjin is not None else None, 'shenhezhuangtai': t.shenhezhuangtai, - 'is_cross_enabled': t.is_cross_enabled, 'paixu': t.paixu, 'product_count': zone_count, }) @@ -7585,9 +4087,6 @@ class ModifyProductTypeZoneView(APIView): except (TypeError, ValueError): return Response({'code': 400, 'msg': '审核状态无效(必须为1或2)'}) - is_cross_enabled = request.data.get('is_cross_enabled', False) - is_cross_enabled = is_cross_enabled in [True, 'true', '1', 'on'] - huiyuan_id = request.data.get('huiyuan_id', '').strip() or None yongjin = request.data.get('yongjin') if yongjin is not None and yongjin != '': @@ -7629,7 +4128,6 @@ class ModifyProductTypeZoneView(APIView): huiyuan_id=huiyuan_id, yongjin=yongjin, shenhezhuangtai=shenhezhuangtai, - is_cross_enabled=is_cross_enabled, paixu=0 ) return Response({'code': 0, 'msg': '添加成功', 'data': {'id': leixing.id}}) @@ -7695,13 +4193,6 @@ class ModifyProductTypeZoneView(APIView): except (TypeError, ValueError): return Response({'code': 400, 'msg': '审核状态无效(必须为1或2)'}) - # 跨平台互通 - is_cross = request.data.get('is_cross_enabled') - if is_cross is not None: - new_cross = is_cross in [True, 'true', '1', 'on'] - if new_cross != leixing.is_cross_enabled: - changes['is_cross_enabled'] = new_cross - # 图片处理 image_file = request.FILES.get('file') old_image_path = leixing.tupian_url @@ -8619,7 +5110,7 @@ class ShopPublicTypeAndAuditView(APIView): try: # shenhezhuangtai = 1 表示正常 (根据模型默认值) types_qs = ShangpinLeixing.objects.filter(shenhezhuangtai=1).values( - 'id', 'jieshao', 'tupian_url', 'yaoqiuleixing', 'huiyuan_id', 'yongjin', 'is_cross_enabled' + 'id', 'jieshao', 'tupian_url', 'yaoqiuleixing', 'huiyuan_id', 'yongjin' ) type_list = [] for t in types_qs: @@ -8630,7 +5121,6 @@ class ShopPublicTypeAndAuditView(APIView): 'yaoqiuleixing': t['yaoqiuleixing'], 'huiyuan_id': t['huiyuan_id'], 'yongjin': t['yongjin'], - 'is_cross_enabled': t['is_cross_enabled'], }) except Exception as e: logger.exception("查询公共商品类型失败") @@ -9171,12 +5661,10 @@ class KefuChatPermissionsView(APIView): # 4. 检查是否拥有至少一个聊天权限 has_chat_perm = any(p in CHAT_PERM_CODES for p in permissions) - # 5. 若有聊天权限,查询我方俱乐部的 chat_api_key + # 5. 若有聊天权限,获取 GoEasy AppKey goeasy_appkey = '' if has_chat_perm: - config = ClubConfig.objects.filter(is_self=1).first() - if config: - goeasy_appkey = config.chat_api_key or '' + goeasy_appkey = getattr(settings, 'GOEASY_APPKEY', '') # 6. 返回数据 return Response({ @@ -9192,250 +5680,6 @@ class KefuChatPermissionsView(APIView): -class FineApplyView(APIView): - """ - 客服申请罚款接口(金额罚款,非积分) - 路径:POST /houtai/kffkdssq - 参数: - username: 客服手机号 - dingdan_id: 订单ID - chufaliyou: 罚款原因 - fakuanjine: 罚款金额(元) - yingxiang_qiangdan: 1影响抢单 / 0不影响抢单 - shenqingren_shenfen: 操作人身份 (1客服/2售后/3管理员等) - 权限:JWT + 权限码99933abs - """ - permission_classes = [IsAuthenticated] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 提取参数 - username = request.data.get('username', '').strip() - dingdan_id = request.data.get('dingdan_id', '').strip() - chufaliyou = request.data.get('chufaliyou', '').strip() - fakuanjine = request.data.get('fakuanjine', 0) - yingxiang_qiangdan = request.data.get('yingxiang_qiangdan', 1) - shenqingren_shenfen = request.data.get('shenqingren_shenfen', 2) - - if not all([username, dingdan_id, chufaliyou, fakuanjine]): - return Response({'code': 400, 'msg': '参数不完整'}) - - try: - # 2. 权限校验 - kefu, permissions = verify_kefu_permission(request, username) - if kefu is None: - return permissions - if '99933abs' not in permissions: - return Response({'code': 403, 'msg': '您无罚款权限'}) - - # 3. 获取订单(无需 select_for_update,因不修改订单) - try: - order = Dingdan.objects.get(dingdan_id=dingdan_id) - except Dingdan.DoesNotExist: - return Response({'code': 404, 'msg': '订单不存在'}) - - dashou_id = order.jiedan_dashou_id - if not dashou_id: - return Response({'code': 400, 'msg': '该订单无接单打手,无法罚款'}) - - # 4. 重复罚单检查 - if Fadan.objects.filter(beichufa_id=dashou_id, guanliandingdan_id=dingdan_id).exists(): - return Response({'code': 400, 'msg': '该打手已被罚款过'}) - - # 5. 跨平台相关字段 - partner_order_id = order.partner_order_id or '' - partner_club_id = order.partner_club_id or '' - dispatch_type = order.dispatch_type or 0 - - # 本地创建罚单的可复用函数 - def create_local_fadan(): - Fadan.objects.create( - beichufa_id=dashou_id, - shenqing_chufa=username, - shenfen=1, # 1 代表打手 - chufaliyou=chufaliyou, - fakuanjine=fakuanjine, - guanliandingdan_id=dingdan_id, - zhuangtai=1, # 待缴纳 - yingxiang_qiangdan=yingxiang_qiangdan, - shenqingren_shenfen=shenqingren_shenfen, - partner_club_id='', - ) - - # 6. 判断:没有对方订单ID,或对方派单 → 本地处罚 - if not partner_order_id or dispatch_type == 2: - with transaction.atomic(): - create_local_fadan() - logger.info(f"本地罚款成功: 订单 {dingdan_id}, 打手 {dashou_id}, 金额 {fakuanjine}") - return Response({'code': 0, 'msg': '罚款已生成'}) - - # 7. 我方派单且有对方订单ID → 通知对方平台 - # 获取我方俱乐部ID - try: - my_club = ClubConfig.objects.filter(is_self=1).first() - if not my_club: - return Response({'code': 500, 'msg': '系统配置错误'}) - self_club_id = my_club.club_id - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain - except ObjectDoesNotExist: - return Response({'code': 500, 'msg': '对方平台信息未配置'}) - - url = partner_domain.rstrip('/') + '/houtai/kffkdssq_notify' - payload = { - 'order_id': partner_order_id, - 'club_id': self_club_id, - 'chufaliyou': chufaliyou, - 'fakuanjine': fakuanjine, - 'yingxiang_qiangdan': yingxiang_qiangdan, - 'shenqingren_shenfen': shenqingren_shenfen, - 'operator': username, - } - timeout = 3 - retries = 2 - success = False - for attempt in range(retries): - try: - resp = requests.post(url, json=payload, timeout=timeout) - if resp.status_code == 200 and resp.json().get('code') == 0: - success = True - break - except Exception: - continue - - if not success: - return Response({'code': 500, 'msg': '通知对方平台失败,请稍后重试'}) - - # 通知成功后,本地也记录一笔(标记为跨平台) - with transaction.atomic(): - Fadan.objects.create( - beichufa_id=dashou_id, - shenqing_chufa=username, - shenfen=1, - chufaliyou=chufaliyou, - fakuanjine=fakuanjine, - guanliandingdan_id=dingdan_id, - zhuangtai=1, - yingxiang_qiangdan=yingxiang_qiangdan, - shenqingren_shenfen=shenqingren_shenfen, - partner_club_id=partner_club_id, - ) - logger.info(f"跨平台罚款通知成功: {dingdan_id}") - return Response({'code': 0, 'msg': '罚款已提交至对方平台'}) - - except Exception as e: - logger.error(f"罚款申请异常: {traceback.format_exc()}") - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - - - - -class PartnerFineNotifyView(APIView): - """ - 接收对方平台罚款通知(无认证) - 路径:POST /houtai/kffkdssq_notify - 参数: - order_id: 对方平台的订单ID(需通过这个找到我方订单) - club_id: 对方俱乐部ID - chufaliyou: 罚款原因 - fakuanjine: 罚款金额 - yingxiang_qiangdan: 1影响抢单 / 0不影响 - shenqingren_shenfen: 申请处罚人身份 - operator: 操作人标识 - """ - authentication_classes = [] - permission_classes = [AllowAny] - parser_classes = [JSONParser] - - def post(self, request): - # token = request.data.get('token') - # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200) - try: - order_id = request.data.get('order_id', '').strip() # 对方平台订单ID - club_id = request.data.get('club_id', '').strip() - chufaliyou = request.data.get('chufaliyou', '').strip() - fakuanjine = request.data.get('fakuanjine', 0) - yingxiang_qiangdan = request.data.get('yingxiang_qiangdan', 1) - shenqingren_shenfen = request.data.get('shenqingren_shenfen', 2) - operator = request.data.get('operator', '').strip() - - if not all([order_id, chufaliyou]): - return Response({'code': 400, 'msg': '参数不完整'}) - - # 通过对方订单ID查找我方订单 - try: - cross = CrossPlatformOrderData.objects.get(partner_order_id=order_id) - my_dingdan_id = cross.dingdan_id - order = Dingdan.objects.get(dingdan_id=my_dingdan_id) - dashou_id = order.jiedan_dashou_id - except CrossPlatformOrderData.DoesNotExist: - return Response({'code': 404, 'msg': '未找到关联订单'}) - except Dingdan.DoesNotExist: - return Response({'code': 404, 'msg': '我方订单不存在'}) - - if not dashou_id: - return Response({'code': 400, 'msg': '订单无接单打手'}) - - # 重复检查 - if Fadan.objects.filter(beichufa_id=dashou_id, guanliandingdan_id=my_dingdan_id).exists(): - return Response({'code': 0, 'msg': '已存在罚单'}) - - # 创建罚单 - Fadan.objects.create( - beichufa_id=dashou_id, - shenqing_chufa=operator, - shenfen=1, - chufaliyou=chufaliyou, - fakuanjine=fakuanjine, - guanliandingdan_id=my_dingdan_id, - zhuangtai=1, - yingxiang_qiangdan=yingxiang_qiangdan, - shenqingren_shenfen=shenqingren_shenfen, - partner_club_id=club_id, - ) - logger.info(f"接收对方罚款成功: 订单 {my_dingdan_id}, 打手 {dashou_id}") - return Response({'code': 0, 'msg': 'success'}) - - except Exception as e: - logger.error(f"接收罚款通知异常: {traceback.format_exc()}") - return Response({'code': 500, 'msg': '服务器内部错误'}) - - - - - - - - - - -# 权限码到身份的映射 -PERMISSION_TO_SHENFEN = { - '66693a': 1, # 打手 - '66693b': 3, # 商家 - '66693c': 2, # 管事 - '66694c': 4, # 组长 -} - -# 身份对应的用户扩展关系名 -SHENFEN_PROFILE_MAP = { - 1: 'dashou_profile', - 2: 'guanshi_profile', - 3: 'shop_profile', - 4: 'zuzhang_profile', -} - - -def check_fadan_permission(permissions, shenfen): - """检查是否有处理指定身份罚单的权限""" - perm_code = {1: '66693a', 2: '66693c', 3: '66693b', 4: '66694c'}.get(shenfen) - return perm_code in permissions - - class FaKuanTongJiView(APIView): """ 罚款统计接口 @@ -11771,13 +8015,7 @@ class ZxkfghdsView(APIView): logger.error(f"商家信息缺失,订单: {dingdan_id}") return Response({'code': 500, 'msg': '商家数据异常'}) - # ========== 4. 判断是否需要跨平台退款通知 ========== - is_cross = old_order.is_cross - partner_club_id = old_order.partner_club_id - partner_order_id = old_order.partner_order_id - need_notify_partner = is_cross and partner_club_id and partner_order_id - - # ========== 5. 退款处理(仅打手侧,不碰商家) ========== + # ========== 4. 退款处理(仅打手侧,不碰商家) ========== with transaction.atomic(): # 5.1 订单状态改为已退款 old_order.zhuangtai = 5 @@ -11830,52 +8068,6 @@ class ZxkfghdsView(APIView): kefu.zongchuli += 1 kefu.save() - # 5.6 跨平台退款通知(需要时) - if need_notify_partner: - try: - # 获取我方俱乐部ID - my_club_config = ClubConfig.objects.filter(is_self=1).first() - if not my_club_config: - raise Exception("我方俱乐部配置不存在") - self_club_id = my_club_config.club_id - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain - - notify_url = partner_domain.rstrip('/') + '/houtai/dfddtk' - payload = { - 'order_id': partner_order_id, - 'club_id': self_club_id, - 'status': 5, - 'reason': '客服更换打手退款' - } - - # 调用对方接口(必须成功,否则整个事务回滚) - resp = requests.post(notify_url, json=payload, timeout=5) - if resp.status_code == 200: - resp_json = resp.json() - if resp_json.get('code') != 0: - transaction.set_rollback(True) - logger.error(f"对方平台退款通知失败: {resp_json.get('msg')}") - return Response({'code': 500, 'msg': f'对方平台通知失败: {resp_json.get("msg")}'}) - else: - transaction.set_rollback(True) - logger.error(f"对方平台接口HTTP错误: {resp.status_code}") - return Response({'code': 500, 'msg': '通知对方平台失败'}) - - # 更新跨平台扩展表(如果存在) - CrossPlatformOrderData.objects.update_or_create( - dingdan_id=dingdan_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'partner_order_status': 5, - } - ) - except Exception as e: - transaction.set_rollback(True) - logger.error(f"通知对方平台异常: {e}") - return Response({'code': 500, 'msg': f'通知对方平台失败: {str(e)}'}) - # 至此,原订单已退款,事务提交 # ========== 6. 生成新订单和链接(不扣商家余额) ========== @@ -11957,12 +8149,6 @@ class ZxkfghdsView(APIView): except Exception as e: logger.error(f"广播通知失败: {e}") - # 跨平台同步(与客户填写接口保持一致) - try: - sync_order_to_partners(new_dingdan_id) - except Exception as e: - logger.error(f"跨平台同步失败: {e}") - # 注意:此处不更新商家余额、发布量、每日统计,因为这是替换订单 logger.info(f"更换打手成功:旧订单{dingdan_id}→退款,新订单{new_dingdan_id}已生成") @@ -11996,69 +8182,3 @@ class ZxkfghdsView(APIView): -class KptxwztjbView(APIView): - """ - 跨平台订单状态同步接口(对方平台调用) - 请求:POST /houtai/kptxwztjb - 参数: - - token: 同步专用令牌(明文) - - partner_order_id: 我方订单ID(在对方眼中是partner_order_id,实际查本库dingdan_id) - - our_order_id: 对方订单ID(在本库中为partner_order_id) - - club_id: 俱乐部ID(可选验证) - 返回: - code=0 成功,data.status 为订单状态 - 其他 code 为错误 - 注意:此接口仅用于查询对方订单状态,不做任何修改 - """ - - authentication_classes = [] # 无需JWT等认证 - permission_classes = [] # 公开接口 - parser_classes = [JSONParser] - - def post(self, request): - # 1. 获取参数 - token = request.data.get('token', '').strip() - partner_order_id = request.data.get('partner_order_id', '').strip() - our_order_id = request.data.get('our_order_id', '').strip() - club_id = request.data.get('club_id', '').strip() - - if not all([token, partner_order_id, our_order_id]): - logger.warning("跨平台状态同步参数缺失") - return Response({'code': 400, 'msg': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST) - - # 2. 验证同步令牌 - SYNC_TOKEN = getattr(settings, 'CROSS_PLATFORM_SYNC_TOKEN', None) - if not SYNC_TOKEN or token != SYNC_TOKEN: - logger.warning(f"无效的同步令牌: {token[:8]}...") - return Response({'code': 403, 'msg': '无效令牌'}, status=status.HTTP_403_FORBIDDEN) - - # 3. 查询订单 - # partner_order_id 在对方视角是“我方订单ID”,在本库中应匹配 dingdan_id - try: - order = Dingdan.objects.get(dingdan_id=partner_order_id) - except Dingdan.DoesNotExist: - logger.warning(f"订单不存在, partner_order_id: {partner_order_id}") - return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) - - # 4. 验证订单关联关系:本库中的 partner_order_id 应等于传入的 our_order_id - if order.partner_order_id != our_order_id: - logger.warning( - f"订单关联不匹配: partner_order_id={order.partner_order_id}, our_order_id={our_order_id}" - ) - return Response({'code': 400, 'msg': '订单关联信息不匹配'}, status=status.HTTP_400_BAD_REQUEST) - - - - # 6. 返回订单状态 - status_code = order.zhuangtai - logger.info( - f"跨平台状态查询成功, dingdan_id={order.dingdan_id}, status={status_code}" - ) - return Response({ - 'code': 0, - 'msg': 'success', - 'data': { - 'dingdan_id': order.dingdan_id, - 'status': status_code, - } - }) \ No newline at end of file diff --git a/config/migrations/0002_shangjiamoban_shangjialianjie.py b/config/migrations/0002_shangjiamoban_shangjialianjie.py index b1f0403..c3451a5 100644 --- a/config/migrations/0002_shangjiamoban_shangjialianjie.py +++ b/config/migrations/0002_shangjiamoban_shangjialianjie.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0001_initial'), + ('config', '0001_initial'), ] operations = [ diff --git a/config/migrations/0003_tupianpeizhi_fangwen_cishu.py b/config/migrations/0003_tupianpeizhi_fangwen_cishu.py index 3538643..a312d76 100644 --- a/config/migrations/0003_tupianpeizhi_fangwen_cishu.py +++ b/config/migrations/0003_tupianpeizhi_fangwen_cishu.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0002_shangjiamoban_shangjialianjie'), + ('config', '0002_shangjiamoban_shangjialianjie'), ] operations = [ diff --git a/config/migrations/0004_tixianquotadefault.py b/config/migrations/0004_tixianquotadefault.py index 4b2a1bd..2e4267e 100644 --- a/config/migrations/0004_tixianquotadefault.py +++ b/config/migrations/0004_tixianquotadefault.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0003_tupianpeizhi_fangwen_cishu'), + ('config', '0003_tupianpeizhi_fangwen_cishu'), ] operations = [ diff --git a/config/migrations/0005_ordertypemapping_dailydispatchstat_club.py b/config/migrations/0005_ordertypemapping_dailydispatchstat_club.py index 161236c..2ef0046 100644 --- a/config/migrations/0005_ordertypemapping_dailydispatchstat_club.py +++ b/config/migrations/0005_ordertypemapping_dailydispatchstat_club.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0004_tixianquotadefault'), + ('config', '0004_tixianquotadefault'), ] operations = [ diff --git a/config/migrations/0006_clubconfig_accountpermission.py b/config/migrations/0006_clubconfig_accountpermission.py index 6a5fd32..dbe8188 100644 --- a/config/migrations/0006_clubconfig_accountpermission.py +++ b/config/migrations/0006_clubconfig_accountpermission.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0005_ordertypemapping_dailydispatchstat_club'), + ('config', '0005_ordertypemapping_dailydispatchstat_club'), ] operations = [ diff --git a/config/migrations/0007_club_receive_rate_dailydispatchstat_claimed_amount_and_more.py b/config/migrations/0007_club_receive_rate_dailydispatchstat_claimed_amount_and_more.py index 2290868..5072dad 100644 --- a/config/migrations/0007_club_receive_rate_dailydispatchstat_claimed_amount_and_more.py +++ b/config/migrations/0007_club_receive_rate_dailydispatchstat_claimed_amount_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0006_clubconfig_accountpermission'), + ('config', '0006_clubconfig_accountpermission'), ] operations = [ diff --git a/config/migrations/0008_clubconfig_storage_bucket_name.py b/config/migrations/0008_clubconfig_storage_bucket_name.py index c7a0fbf..7c7310a 100644 --- a/config/migrations/0008_clubconfig_storage_bucket_name.py +++ b/config/migrations/0008_clubconfig_storage_bucket_name.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0007_club_receive_rate_dailydispatchstat_claimed_amount_and_more'), + ('config', '0007_club_receive_rate_dailydispatchstat_claimed_amount_and_more'), ] operations = [ diff --git a/config/migrations/0009_dailypayoutstat_dailyincomestat.py b/config/migrations/0009_dailypayoutstat_dailyincomestat.py index 47a2925..8f8dfcb 100644 --- a/config/migrations/0009_dailypayoutstat_dailyincomestat.py +++ b/config/migrations/0009_dailypayoutstat_dailyincomestat.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0008_clubconfig_storage_bucket_name'), + ('config', '0008_clubconfig_storage_bucket_name'), ] operations = [ diff --git a/config/migrations/0010_popupconfig_popuppage_popupimage_popupconfig_page_and_more.py b/config/migrations/0010_popupconfig_popuppage_popupimage_popupconfig_page_and_more.py index ed347a7..0aeb527 100644 --- a/config/migrations/0010_popupconfig_popuppage_popupimage_popupconfig_page_and_more.py +++ b/config/migrations/0010_popupconfig_popuppage_popupimage_popupconfig_page_and_more.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0009_dailypayoutstat_dailyincomestat'), + ('config', '0009_dailypayoutstat_dailyincomestat'), ] operations = [ @@ -63,7 +63,7 @@ class Migration(migrations.Migration): ('text', models.TextField(blank=True, verbose_name='图片上方文字')), ('sort_order', models.PositiveIntegerField(default=0, verbose_name='展示顺序')), ('created_at', models.DateTimeField(auto_now_add=True)), - ('popup_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='peizhi.popupconfig')), + ('popup_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='config.popupconfig')), ], options={ 'verbose_name': '弹窗图片', @@ -75,7 +75,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='popupconfig', name='page', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='popups', to='peizhi.popuppage'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='popups', to='config.popuppage'), ), migrations.AlterUniqueTogether( name='popupconfig', diff --git a/config/migrations/0011_withdrawconfig.py b/config/migrations/0011_withdrawconfig.py index 1ef207e..62e3800 100644 --- a/config/migrations/0011_withdrawconfig.py +++ b/config/migrations/0011_withdrawconfig.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0010_popupconfig_popuppage_popupimage_popupconfig_page_and_more'), + ('config', '0010_popupconfig_popuppage_popupimage_popupconfig_page_and_more'), ] operations = [ diff --git a/config/migrations/0012_shangjiamoban_chenghao_id_and_more.py b/config/migrations/0012_shangjiamoban_chenghao_id_and_more.py index 6e6970d..c22a4c8 100644 --- a/config/migrations/0012_shangjiamoban_chenghao_id_and_more.py +++ b/config/migrations/0012_shangjiamoban_chenghao_id_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0011_withdrawconfig'), + ('config', '0011_withdrawconfig'), ] operations = [ diff --git a/config/migrations/0013_shangjialianjie_previous_dingdan_id_and_more.py b/config/migrations/0013_shangjialianjie_previous_dingdan_id_and_more.py index 3fedcbf..bbb9ff6 100644 --- a/config/migrations/0013_shangjialianjie_previous_dingdan_id_and_more.py +++ b/config/migrations/0013_shangjialianjie_previous_dingdan_id_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('peizhi', '0012_shangjiamoban_chenghao_id_and_more'), + ('config', '0012_shangjiamoban_chenghao_id_and_more'), ] operations = [ diff --git a/config/migrations/0014_delete_club_delete_clubconfig_and_more.py b/config/migrations/0014_delete_club_delete_clubconfig_and_more.py new file mode 100644 index 0000000..5e5942d --- /dev/null +++ b/config/migrations/0014_delete_club_delete_clubconfig_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.27 on 2026-06-15 01:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('config', '0013_shangjialianjie_previous_dingdan_id_and_more'), + ] + + operations = [ + migrations.DeleteModel( + name='Club', + ), + migrations.DeleteModel( + name='ClubConfig', + ), + migrations.DeleteModel( + name='DailyDispatchStat', + ), + migrations.DeleteModel( + name='OrderTypeMapping', + ), + migrations.AlterField( + model_name='tixianquotadefault', + name='leixing', + field=models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '组长'), (4, '考核官'), (5, '打手押金'), (6, '商家余额')], unique=True, verbose_name='用户类型'), + ), + ] diff --git a/config/models.py b/config/models.py index 0e3305f..fa13249 100644 --- a/config/models.py +++ b/config/models.py @@ -270,112 +270,6 @@ class WithdrawConfig(models.Model): -class Club(models.Model): - """合作俱乐部/平台信息表""" - # 自己的俱乐部ID(6位字符) - club_id = models.CharField(max_length=6, db_index=True, verbose_name='本俱乐部ID') - # 对方的俱乐部ID(6位字符) - partner_club_id = models.CharField(max_length=6, unique= True,verbose_name='对方俱乐部ID') - # 是否开启平台互通(双方互相同意才可传递订单) - is_interop_enabled = models.BooleanField(default=False, verbose_name='是否开启平台互通') - # 是否同意接收对方订单(对方同意我方发单,但接收订单需我方同意) - is_receive_enabled = models.BooleanField(default=False, verbose_name='是否同意接收对方订单') - # 打手分红费率(默认100%,即原金额,对方平台打手分成 = 我方打手分成 × 费率) - dashou_rate = models.DecimalField(max_digits=5, decimal_places=4, default=1.0000, verbose_name='打手分红费率') - # 新增字段:接收对方订单时打手分红费率(默认100%) - receive_rate = models.DecimalField(max_digits=5, decimal_places=4, default=1.0000, verbose_name='接收打手分红费率') - - - # 对方服务器域名(完整 URL,如 https://api.partner.com) - partner_domain = models.URLField(max_length=200, blank=True, verbose_name='对方服务器域名') - - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'club' - verbose_name = '俱乐部' - verbose_name_plural = '俱乐部' - indexes = [ - models.Index(fields=['club_id']), - models.Index(fields=['partner_club_id']), - ] - - -class OrderTypeMapping(models.Model): - """对方订单类型 → 我方订单类型 映射表""" - partner_club_id = models.CharField(max_length=6, db_index=True, verbose_name='对方俱乐部ID') - partner_type_id = models.PositiveIntegerField(verbose_name='对方订单类型ID') - partner_type_name = models.CharField(max_length=100, verbose_name='对方订单类型名称') - our_type_id = models.PositiveIntegerField(verbose_name='我方订单类型ID') - our_type_name = models.CharField(max_length=100, verbose_name='我方订单类型名称') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'order_type_mapping' - verbose_name = '订单类型映射' - verbose_name_plural = '订单类型映射' - unique_together = (('partner_club_id', 'partner_type_id'),) - - -class ClubConfig(models.Model): - """ - 俱乐部配置信息表 - 存储俱乐部(我方或对方)的详细配置,如昵称、头像、API密钥等 - """ - # 俱乐部ID(6位字符,与 Club 表中的 club_id 或 partner_club_id 对应) - club_id = models.CharField(max_length=6, db_index=True, verbose_name='俱乐部ID') - - # 俱乐部昵称 - club_nickname = models.CharField(max_length=50, blank=True, default='', verbose_name='俱乐部昵称') - - # 俱乐部头像地址(URL) - club_avatar = models.CharField(max_length=500, blank=True, default='', verbose_name='俱乐部头像地址') - - # 俱乐部后台域名(用于API请求) - backend_domain = models.CharField(max_length=200, blank=True, default='', verbose_name='后台域名') - - # 聊天API密钥 - chat_api_key = models.CharField(max_length=200, blank=True, default='', verbose_name='聊天API密钥') - - # 聊天API ID - chat_api_id = models.CharField(max_length=200, blank=True, default='', verbose_name='聊天API ID') - - # 客服路径 - customer_service_api_url = models.CharField(max_length=200, blank=True, default='', verbose_name='客服API URL') - customer_service_corp_id = models.CharField(max_length=100, blank=True, default='', verbose_name='客服企业ID') - - # 存储桶域名(如腾讯云COS、阿里云OSS的访问域名) - storage_bucket_domain = models.CharField(max_length=500, blank=True, default='', verbose_name='存储桶域名') - storage_bucket_name = models.CharField(max_length=100, blank=True, default='', verbose_name='存储桶名称') - - # 标识是我方还是对方俱乐部:1=我方,0=对方(默认0) - is_self = models.SmallIntegerField( - choices=((0, '对方'), (1, '我方')), - default=0, - db_index=True, - verbose_name='是否我方俱乐部' - ) - - # 创建时间 - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - # 更新时间 - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'club_config' - verbose_name = '俱乐部配置' - verbose_name_plural = '俱乐部配置' - # 联合唯一约束:同一个俱乐部ID只能有一条记录(无论我方对方),若需要分开则可不加 - # 但通常一个俱乐部只会存在一种身份,所以加联合唯一约束更安全 - unique_together = [['club_id']] # 确保每个club_id只有一条配置记录 - - def __str__(self): - return f"{self.club_nickname or self.club_id} ({'我方' if self.is_self else '对方'})" - - class AccountPermission(models.Model): """账号权限表:记录每个账号拥有的权限及开关状态""" account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID') @@ -396,52 +290,6 @@ class AccountPermission(models.Model): -# apps/dingdan/models.py (添加新模型) -class DailyDispatchStat(models.Model): - """ - 每日派单统计表 - 记录每天向各合作俱乐部派单的数据,用于替代俱乐部表中的累计字段。 - """ - # 日期相关字段(便于快速筛选) - date = models.DateField(verbose_name='统计日期', db_index=True) - year = models.PositiveSmallIntegerField(verbose_name='年份') - month = models.PositiveSmallIntegerField(verbose_name='月份') - day = models.PositiveSmallIntegerField(verbose_name='日期') - # 派单方向:1=我方派单(我方订单被对方抢单),2=对方派单(对方订单被我方抢单) - direction = models.PositiveSmallIntegerField(choices=[(1, '我方派单'), (2, '对方派单')], verbose_name='派单方向') - # 对方俱乐部ID - partner_club_id = models.CharField(max_length=6, db_index=True, verbose_name='对方俱乐部ID') - # 派单数 - dispatch_count = models.PositiveIntegerField(default=0, verbose_name='派单数') - # 派单总额(打手分成 * 费率) - dispatch_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='派单总额') - # 抢单统计(我方实际抢到的对方派单数/金额) - claimed_count = models.PositiveIntegerField(default=0, verbose_name='抢单数') - claimed_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='抢单总额') - # 成交订单数(预留,当前暂不更新) - success_count = models.PositiveIntegerField(default=0, verbose_name='成交订单数') - # 成交总额(预留) - success_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='成交总额') - # 时间戳 - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'daily_dispatch_stat' - verbose_name = '每日派单统计' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['year', 'month', 'day', 'direction', 'partner_club_id']), - models.Index(fields=['date']), - ] - - def __str__(self): - return f"{self.date} - 方向{self.direction} - 俱乐部{self.partner_club_id} - 派单{self.dispatch_count}次" - - - - - # apps/dingdan/models.py (新增模型) class DailyIncomeStat(models.Model): diff --git a/config/views.py b/config/views.py index 3180f0c..30e4ef6 100644 --- a/config/views.py +++ b/config/views.py @@ -29,10 +29,9 @@ from utils.weixin_broadcast import WeixinBroadcastSender from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode from backend.utils import update_shangjia_daily -from orders.utils import sync_order_to_partners from .models import ( - Gonggao, Lunbo, ClubConfig, Tupianpeizhi, Qunpeizhi, + Gonggao, Lunbo, Tupianpeizhi, Qunpeizhi, ShangjiaMoban, ShangjiaLianjie, PopupPage, PopupConfig, WithdrawConfig ) @@ -63,49 +62,19 @@ class GetDynamicConfigView(APIView): def post(self, request): try: - # 查询我方俱乐部配置(is_self=1),取第一条(通常只有一条) - club_config = ClubConfig.objects.filter(is_self=1).only( - 'storage_bucket_domain', - 'storage_bucket_name', - 'chat_api_key', - 'customer_service_api_url', - 'customer_service_corp_id' - ).first() - - # 默认值(防止空数据导致前端报错) - cos_bucket = '' - cos_region = 'ap-shanghai' - cos_oss_url = '' + # 从 settings 获取配置 + cos_oss_url = getattr(settings, 'COS_DOMAIN', '') + if cos_oss_url and not cos_oss_url.endswith('/'): + cos_oss_url += '/' + cos_bucket = getattr(settings, 'COS_BUCKET', '') + cos_region = getattr(settings, 'COS_REGION', 'ap-shanghai') goeasy_host = 'hangzhou.goeasy.io' - goeasy_appkey = '' - kefu_link = '' - kefu_enterprise_id = '' + goeasy_appkey = getattr(settings, 'GOEASY_APPKEY', '') + kefu_link = getattr(settings, 'KEFU_LINK', '') + kefu_enterprise_id = getattr(settings, 'KEFU_ENTERPRISE_ID', '') morentouxiang = 'beijing/morentouxiang.jpg' dashouguize = 'a_long/dashouguize.jpg' - if club_config: - # 存储桶配置 - bucket = club_config.storage_bucket_name or '' - region = 'ap-shanghai' # 若需要动态,可单独存储 - cos_oss_url = club_config.storage_bucket_domain or '' - if cos_oss_url and not cos_oss_url.endswith('/'): - cos_oss_url += '/' - cos_bucket = bucket - cos_region = region - - # GoEasy 配置 - if club_config.chat_api_key: - goeasy_appkey = club_config.chat_api_key - # host 如果需要可动态,这里固定或从其他字段获取 - - # 客服配置 - if club_config.customer_service_api_url: - kefu_link = club_config.customer_service_api_url - if club_config.customer_service_corp_id: - kefu_enterprise_id = club_config.customer_service_corp_id - - # 其他配置可在此扩展 - # 构造返回数据 data = { "code": 0, @@ -2229,26 +2198,10 @@ class KehuGetDingdanLianjieView(APIView): # ========== 新增:确定俱乐部配置 ========== # 默认头像(相对路径) default_avatar = 'a_long/morentouxiang.jpg' - # 初始化配置字段默认值 - kefu_link = '' - oss_url = '' - goeasy_appkey = '' - - # 判断是否为跨平台订单(保留,但不影响配置选择) - is_cross = dingdan_obj.is_cross if hasattr(dingdan_obj, 'is_cross') else 0 - - # 始终使用我方俱乐部配置 - try: - club_config = ClubConfig.objects.filter(is_self=1).first() - if club_config: - logger.info(f"使用我方俱乐部配置,club_id={club_config.club_id}") - kefu_link = club_config.customer_service_api_url or '' - oss_url = club_config.storage_bucket_domain or '' - goeasy_appkey = club_config.chat_api_key or '' - else: - logger.warning("未找到我方俱乐部配置(is_self=1)") - except Exception as e: - logger.error(f"查询我方俱乐部配置异常: {str(e)}", exc_info=True) + # 从 settings 获取配置 + kefu_link = getattr(settings, 'KEFU_LINK', '') + oss_url = getattr(settings, 'COS_DOMAIN', '') + goeasy_appkey = getattr(settings, 'GOEASY_APPKEY', '') # ========== 构建订单信息 ========== dingdan_info = { @@ -2256,7 +2209,6 @@ class KehuGetDingdanLianjieView(APIView): 'jiage': str(dingdan_obj.jine) if dingdan_obj.jine is not None else '0.00', # 金额字段名是 jine 'shangjia_mingcheng': shangjia_nicheng, 'zhuangtai': dingdan_obj.zhuangtai, - 'is_cross': is_cross, # 新增配置字段 'kefu_link': kefu_link, 'oss_url': oss_url, @@ -2313,7 +2265,6 @@ class KehuGetDingdanLianjieView(APIView): 'dashou_biaoshi': '', 'shangjia_biaoshi': '', 'you_dashou': False, - 'is_cross': 0, 'kefu_link': '', 'oss_url': '', 'default_avatar': 'a_long/morentouxiang.jpg', @@ -2330,7 +2281,6 @@ class KehuGetDingdanLianjieView(APIView): 'dashou_biaoshi': '', 'shangjia_biaoshi': '', 'you_dashou': False, - 'is_cross': 0, 'kefu_link': '', 'oss_url': '', 'default_avatar': 'a_long/morentouxiang.jpg', @@ -2348,7 +2298,6 @@ class KehuGetDingdanLianjieView(APIView): 'dashou_biaoshi': '', 'shangjia_biaoshi': '', 'you_dashou': False, - 'is_cross': 0, 'kefu_link': '', 'oss_url': '', 'default_avatar': 'a_long/morentouxiang.jpg', @@ -2627,13 +2576,6 @@ class KehuTianxieDingdanView(APIView): shangjia_mingcheng = f"商家{lianjie_obj.yonghu_id}" logger.warning(f"获取商家昵称失败,使用默认 - 用户ID: {lianjie_obj.yonghu_id}") - # ========== 新增:判断是否需要跨平台同步 ========== - # 获取商家ID(从链接记录) - shangjia_user_id = lianjie_obj.yonghu_id - # 跨平台条件:商家ID在白名单、订单为普通状态(zhuangtai=1)、要求类型为1、会员ID等于配置值 - sync_order_to_partners(dingdan_obj.dingdan_id) - # ========== 新增结束 ========== - # 14. 返回成功响应 response_data = { #'dingdan_id': dingdan_obj.dingdan_id, diff --git a/fix_migration_history.py b/fix_migration_history.py new file mode 100644 index 0000000..8b88ebb --- /dev/null +++ b/fix_migration_history.py @@ -0,0 +1,35 @@ +""" +修复 django_migrations 表中的旧 app 名称引用。 +在服务器上执行: python manage.py shell < fix_migration_history.py +或: python manage.py shell +然后粘贴此脚本内容 +""" +from django.db import connection + +APP_NAME_MAPPINGS = { + 'houtai': 'backend', + 'yonghu': 'users', + 'dingdan': 'orders', + 'peizhi': 'config', + 'shangpin': 'products', + 'shangdian': 'shop', + 'dengji': 'rank', +} + +with connection.cursor() as cursor: + for old_name, new_name in APP_NAME_MAPPINGS.items(): + cursor.execute( + "SELECT COUNT(*) FROM django_migrations WHERE app = %s", + [old_name] + ) + count = cursor.fetchone()[0] + if count > 0: + cursor.execute( + "UPDATE django_migrations SET app = %s WHERE app = %s", + [new_name, old_name] + ) + print(f"已更新 {count} 条记录: {old_name} -> {new_name}") + else: + print(f"无需更新: {old_name} (无记录)") + +print("\n修复完成!请运行 python manage.py migrate") diff --git a/orders/cross_platform_tasks.py b/orders/cross_platform_tasks.py deleted file mode 100644 index afcf707..0000000 --- a/orders/cross_platform_tasks.py +++ /dev/null @@ -1,38 +0,0 @@ -# dingdan/cross_platform_tasks.py -import logging -import requests -from django.conf import settings - -logger = logging.getLogger(__name__) - -def send_partner_sync_order(partner_order_id, jine, dashou_fencheng, jieshao, beizhu, create_time): - """ - 异步发送派单通知给对方平台(使用简单线程,非Celery) - 注意:此函数应在线程中调用,避免阻塞主请求。 - 生产环境建议使用Celery,此处提供线程兼容版本。 - """ - url = f"{settings.PARTNER_BASE_URL}/dingdan/partner_sync_order" - payload = { - 'token': settings.CROSS_PLATFORM_TOKEN, - 'partner_order_id': partner_order_id, - 'jine': jine, - 'dashou_fencheng': dashou_fencheng, - 'jieshao': jieshao, - 'beizhu': beizhu, - 'create_time': create_time, - } - try: - response = requests.post(url, json=payload, timeout=5) - if response.status_code == 200: - data = response.json() - if data.get('code') == 0: - logger.info(f"跨平台派单同步成功: {partner_order_id}") - # 可在此处保存对方返回的 local_order_id 到本地订单(如果需要) - # local_order_id = data['data'].get('local_order_id') - # Dingdan.objects.filter(dingdan_id=partner_order_id).update(partner_order_id=local_order_id) - else: - logger.error(f"跨平台派单同步业务失败: {data}") - else: - logger.error(f"跨平台派单同步HTTP失败: {response.status_code}") - except Exception as e: - logger.error(f"跨平台派单同步异常: {str(e)}", exc_info=True) \ No newline at end of file diff --git a/orders/migrations/0001_initial.py b/orders/migrations/0001_initial.py index 964ac46..784b965 100644 --- a/orders/migrations/0001_initial.py +++ b/orders/migrations/0001_initial.py @@ -174,7 +174,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('laoban_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='老板ID')), ('laoban_pingjia', models.TextField(blank=True, null=True, verbose_name='老板评价')), - ('dingdan', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='pingtai_kuozhan', to='dingdan.dingdan', verbose_name='订单')), + ('dingdan', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='pingtai_kuozhan', to='orders.dingdan', verbose_name='订单')), ], options={ 'verbose_name': '平台订单扩展', @@ -193,7 +193,7 @@ class Migration(migrations.Migration): ('sqzhuangtai', models.IntegerField(blank=True, default=0, null=True, verbose_name='处罚申请状态:0待处理,1成功,2驳回')), ('bhliyou', models.TextField(blank=True, null=True, verbose_name='驳回商家处罚理由')), ('sjnicheng', models.CharField(max_length=50, null=True, verbose_name='商家昵称')), - ('dingdan', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='shangjia_kuozhan', to='dingdan.dingdan', verbose_name='订单')), + ('dingdan', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='shangjia_kuozhan', to='orders.dingdan', verbose_name='订单')), ], options={ 'verbose_name': '商家订单扩展', diff --git a/orders/migrations/0002_dingdan_auto_expire_at_dingdan_auto_task_id.py b/orders/migrations/0002_dingdan_auto_expire_at_dingdan_auto_task_id.py index 70c4d19..e9bbc52 100644 --- a/orders/migrations/0002_dingdan_auto_expire_at_dingdan_auto_task_id.py +++ b/orders/migrations/0002_dingdan_auto_expire_at_dingdan_auto_task_id.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0001_initial'), + ('orders', '0001_initial'), ] operations = [ diff --git a/orders/migrations/0003_dingdan_pending_dispatch_dingdan_status_8_time.py b/orders/migrations/0003_dingdan_pending_dispatch_dingdan_status_8_time.py index 0991dfd..06253b7 100644 --- a/orders/migrations/0003_dingdan_pending_dispatch_dingdan_status_8_time.py +++ b/orders/migrations/0003_dingdan_pending_dispatch_dingdan_status_8_time.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0002_dingdan_auto_expire_at_dingdan_auto_task_id'), + ('orders', '0002_dingdan_auto_expire_at_dingdan_auto_task_id'), ] operations = [ diff --git a/orders/migrations/0004_alter_tuikuanjilu_chuliid.py b/orders/migrations/0004_alter_tuikuanjilu_chuliid.py index b188bc8..bdd63ca 100644 --- a/orders/migrations/0004_alter_tuikuanjilu_chuliid.py +++ b/orders/migrations/0004_alter_tuikuanjilu_chuliid.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0003_dingdan_pending_dispatch_dingdan_status_8_time'), + ('orders', '0003_dingdan_pending_dispatch_dingdan_status_8_time'), ] operations = [ diff --git a/orders/migrations/0005_alter_chufajilu_chuliid.py b/orders/migrations/0005_alter_chufajilu_chuliid.py index b2fa0f3..5514d10 100644 --- a/orders/migrations/0005_alter_chufajilu_chuliid.py +++ b/orders/migrations/0005_alter_chufajilu_chuliid.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0004_alter_tuikuanjilu_chuliid'), + ('orders', '0004_alter_tuikuanjilu_chuliid'), ] operations = [ diff --git a/orders/migrations/0006_chufatupian.py b/orders/migrations/0006_chufatupian.py index 9c33af9..1eab403 100644 --- a/orders/migrations/0006_chufatupian.py +++ b/orders/migrations/0006_chufatupian.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0005_alter_chufajilu_chuliid'), + ('orders', '0005_alter_chufajilu_chuliid'), ] operations = [ diff --git a/orders/migrations/0007_chufajilu_ssliyou.py b/orders/migrations/0007_chufajilu_ssliyou.py index 542bd6b..f00f99a 100644 --- a/orders/migrations/0007_chufajilu_ssliyou.py +++ b/orders/migrations/0007_chufajilu_ssliyou.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0006_chufatupian'), + ('orders', '0006_chufatupian'), ] operations = [ diff --git a/orders/migrations/0008_dingdan_is_cross_dingdan_partner_order_id.py b/orders/migrations/0008_dingdan_is_cross_dingdan_partner_order_id.py index 710df57..7bdaf56 100644 --- a/orders/migrations/0008_dingdan_is_cross_dingdan_partner_order_id.py +++ b/orders/migrations/0008_dingdan_is_cross_dingdan_partner_order_id.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0007_chufajilu_ssliyou'), + ('orders', '0007_chufajilu_ssliyou'), ] operations = [ diff --git a/orders/migrations/0009_dingdanpingtai_clkf_dingdanpingtai_jjjs_biaoshi.py b/orders/migrations/0009_dingdanpingtai_clkf_dingdanpingtai_jjjs_biaoshi.py index b7fd291..8b7cd25 100644 --- a/orders/migrations/0009_dingdanpingtai_clkf_dingdanpingtai_jjjs_biaoshi.py +++ b/orders/migrations/0009_dingdanpingtai_clkf_dingdanpingtai_jjjs_biaoshi.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0008_dingdan_is_cross_dingdan_partner_order_id'), + ('orders', '0008_dingdan_is_cross_dingdan_partner_order_id'), ] operations = [ diff --git a/orders/migrations/0010_remove_dingdanpingtai_clkf_dingdan_clkf.py b/orders/migrations/0010_remove_dingdanpingtai_clkf_dingdan_clkf.py index 25befe3..59c7732 100644 --- a/orders/migrations/0010_remove_dingdanpingtai_clkf_dingdan_clkf.py +++ b/orders/migrations/0010_remove_dingdanpingtai_clkf_dingdan_clkf.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0009_dingdanpingtai_clkf_dingdanpingtai_jjjs_biaoshi'), + ('orders', '0009_dingdanpingtai_clkf_dingdanpingtai_jjjs_biaoshi'), ] operations = [ diff --git a/orders/migrations/0011_dingdan_tkly.py b/orders/migrations/0011_dingdan_tkly.py index e535e27..7235148 100644 --- a/orders/migrations/0011_dingdan_tkly.py +++ b/orders/migrations/0011_dingdan_tkly.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0010_remove_dingdanpingtai_clkf_dingdan_clkf'), + ('orders', '0010_remove_dingdanpingtai_clkf_dingdan_clkf'), ] operations = [ diff --git a/orders/migrations/0012_dingdan_wechat_transaction_id_and_more.py b/orders/migrations/0012_dingdan_wechat_transaction_id_and_more.py index f85a37f..a6fba66 100644 --- a/orders/migrations/0012_dingdan_wechat_transaction_id_and_more.py +++ b/orders/migrations/0012_dingdan_wechat_transaction_id_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0011_dingdan_tkly'), + ('orders', '0011_dingdan_tkly'), ] operations = [ diff --git a/orders/migrations/0013_alter_chufajilu_qingqiuid.py b/orders/migrations/0013_alter_chufajilu_qingqiuid.py index 7f1cde8..5d7fb22 100644 --- a/orders/migrations/0013_alter_chufajilu_qingqiuid.py +++ b/orders/migrations/0013_alter_chufajilu_qingqiuid.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0012_dingdan_wechat_transaction_id_and_more'), + ('orders', '0012_dingdan_wechat_transaction_id_and_more'), ] operations = [ diff --git a/orders/migrations/0014_dingdan_dispatch_type_dingdan_partner_club_id.py b/orders/migrations/0014_dingdan_dispatch_type_dingdan_partner_club_id.py index 609b314..3dcbe37 100644 --- a/orders/migrations/0014_dingdan_dispatch_type_dingdan_partner_club_id.py +++ b/orders/migrations/0014_dingdan_dispatch_type_dingdan_partner_club_id.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0013_alter_chufajilu_qingqiuid'), + ('orders', '0013_alter_chufajilu_qingqiuid'), ] operations = [ diff --git a/orders/migrations/0015_presettlement_orderdashouhistory_and_more.py b/orders/migrations/0015_presettlement_orderdashouhistory_and_more.py index e545049..fc3fe17 100644 --- a/orders/migrations/0015_presettlement_orderdashouhistory_and_more.py +++ b/orders/migrations/0015_presettlement_orderdashouhistory_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0014_dingdan_dispatch_type_dingdan_partner_club_id'), + ('orders', '0014_dingdan_dispatch_type_dingdan_partner_club_id'), ] operations = [ diff --git a/orders/migrations/0016_alter_presettlement_dingdan_id_and_more.py b/orders/migrations/0016_alter_presettlement_dingdan_id_and_more.py index fc7e742..34e0859 100644 --- a/orders/migrations/0016_alter_presettlement_dingdan_id_and_more.py +++ b/orders/migrations/0016_alter_presettlement_dingdan_id_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0015_presettlement_orderdashouhistory_and_more'), + ('orders', '0015_presettlement_orderdashouhistory_and_more'), ] operations = [ diff --git a/orders/migrations/0017_dingdanpingtai_dianpu_id_and_more.py b/orders/migrations/0017_dingdanpingtai_dianpu_id_and_more.py index 8df76b7..03a58c2 100644 --- a/orders/migrations/0017_dingdanpingtai_dianpu_id_and_more.py +++ b/orders/migrations/0017_dingdanpingtai_dianpu_id_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0016_alter_presettlement_dingdan_id_and_more'), + ('orders', '0016_alter_presettlement_dingdan_id_and_more'), ] operations = [ diff --git a/orders/migrations/0018_dingdanshangjia_partner_yonghu_id.py b/orders/migrations/0018_dingdanshangjia_partner_yonghu_id.py index 476cab1..2c45ee5 100644 --- a/orders/migrations/0018_dingdanshangjia_partner_yonghu_id.py +++ b/orders/migrations/0018_dingdanshangjia_partner_yonghu_id.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0017_dingdanpingtai_dianpu_id_and_more'), + ('orders', '0017_dingdanpingtai_dianpu_id_and_more'), ] operations = [ diff --git a/orders/migrations/0019_dingdanpingtai_shangji_dianpu_id_and_more.py b/orders/migrations/0019_dingdanpingtai_shangji_dianpu_id_and_more.py index 92e3eef..780942d 100644 --- a/orders/migrations/0019_dingdanpingtai_shangji_dianpu_id_and_more.py +++ b/orders/migrations/0019_dingdanpingtai_shangji_dianpu_id_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0018_dingdanshangjia_partner_yonghu_id'), + ('orders', '0018_dingdanshangjia_partner_yonghu_id'), ] operations = [ diff --git a/orders/migrations/0020_fadanfenhong_fadan.py b/orders/migrations/0020_fadanfenhong_fadan.py index 526cbdf..9849a9d 100644 --- a/orders/migrations/0020_fadanfenhong_fadan.py +++ b/orders/migrations/0020_fadanfenhong_fadan.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0019_dingdanpingtai_shangji_dianpu_id_and_more'), + ('orders', '0019_dingdanpingtai_shangji_dianpu_id_and_more'), ] operations = [ diff --git a/orders/migrations/0021_fadanshensutupian.py b/orders/migrations/0021_fadanshensutupian.py index 2270083..e6cc657 100644 --- a/orders/migrations/0021_fadanshensutupian.py +++ b/orders/migrations/0021_fadanshensutupian.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0020_fadanfenhong_fadan'), + ('orders', '0020_fadanfenhong_fadan'), ] operations = [ @@ -18,7 +18,7 @@ class Migration(migrations.Migration): ('beichufa_id', models.CharField(db_index=True, max_length=7, verbose_name='被处罚用户ID')), ('tupian_url', models.CharField(max_length=500, verbose_name='图片相对URL')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('fadan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shensu_tupians', to='dingdan.fadan', verbose_name='关联罚单')), + ('fadan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shensu_tupians', to='orders.fadan', verbose_name='关联罚单')), ], options={ 'verbose_name': '罚款申诉图片', diff --git a/orders/migrations/0022_fadan_partner_club_id_fadan_shenqingren_shenfen.py b/orders/migrations/0022_fadan_partner_club_id_fadan_shenqingren_shenfen.py index e880b33..e104eb5 100644 --- a/orders/migrations/0022_fadan_partner_club_id_fadan_shenqingren_shenfen.py +++ b/orders/migrations/0022_fadan_partner_club_id_fadan_shenqingren_shenfen.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0021_fadanshensutupian'), + ('orders', '0021_fadanshensutupian'), ] operations = [ diff --git a/orders/migrations/0023_fadanshensutupian_yongtu.py b/orders/migrations/0023_fadanshensutupian_yongtu.py index 1540e2f..8a0ba9b 100644 --- a/orders/migrations/0023_fadanshensutupian_yongtu.py +++ b/orders/migrations/0023_fadanshensutupian_yongtu.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0022_fadan_partner_club_id_fadan_shenqingren_shenfen'), + ('orders', '0022_fadan_partner_club_id_fadan_shenqingren_shenfen'), ] operations = [ diff --git a/orders/migrations/0024_fadan_chulizhe_fadan_chulizhe_shenfen.py b/orders/migrations/0024_fadan_chulizhe_fadan_chulizhe_shenfen.py index 0d6fb37..59c6b7f 100644 --- a/orders/migrations/0024_fadan_chulizhe_fadan_chulizhe_shenfen.py +++ b/orders/migrations/0024_fadan_chulizhe_fadan_chulizhe_shenfen.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0023_fadanshensutupian_yongtu'), + ('orders', '0023_fadanshensutupian_yongtu'), ] operations = [ diff --git a/orders/migrations/0025_alter_fadan_shenqingren_shenfen.py b/orders/migrations/0025_alter_fadan_shenqingren_shenfen.py index 0a408f9..e9cfae8 100644 --- a/orders/migrations/0025_alter_fadan_shenqingren_shenfen.py +++ b/orders/migrations/0025_alter_fadan_shenqingren_shenfen.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0024_fadan_chulizhe_fadan_chulizhe_shenfen'), + ('orders', '0024_fadan_chulizhe_fadan_chulizhe_shenfen'), ] operations = [ diff --git a/orders/migrations/0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more.py b/orders/migrations/0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more.py index 3989e0a..3ff4939 100644 --- a/orders/migrations/0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more.py +++ b/orders/migrations/0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0025_alter_fadan_shenqingren_shenfen'), + ('orders', '0025_alter_fadan_shenqingren_shenfen'), ] operations = [ diff --git a/orders/migrations/0027_dingdanpeihu.py b/orders/migrations/0027_dingdanpeihu.py index c929d3b..d7f96e7 100644 --- a/orders/migrations/0027_dingdanpeihu.py +++ b/orders/migrations/0027_dingdanpeihu.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), + ('orders', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), ] operations = [ diff --git a/orders/migrations/0028_dingdanshangjia_shangyige_dingdan_id.py b/orders/migrations/0028_dingdanshangjia_shangyige_dingdan_id.py index ee6851d..e69a717 100644 --- a/orders/migrations/0028_dingdanshangjia_shangyige_dingdan_id.py +++ b/orders/migrations/0028_dingdanshangjia_shangyige_dingdan_id.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0027_dingdanpeihu'), + ('orders', '0027_dingdanpeihu'), ] operations = [ diff --git a/orders/migrations/0029_fadanfenhong_is_individual_rate.py b/orders/migrations/0029_fadanfenhong_is_individual_rate.py index 99076b9..0b8abc8 100644 --- a/orders/migrations/0029_fadanfenhong_is_individual_rate.py +++ b/orders/migrations/0029_fadanfenhong_is_individual_rate.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0028_dingdanshangjia_shangyige_dingdan_id'), + ('orders', '0028_dingdanshangjia_shangyige_dingdan_id'), ] operations = [ diff --git a/orders/migrations/0030_fadan_shenqingren_fenhong_jine.py b/orders/migrations/0030_fadan_shenqingren_fenhong_jine.py index 7ddb093..dbd48ca 100644 --- a/orders/migrations/0030_fadan_shenqingren_fenhong_jine.py +++ b/orders/migrations/0030_fadan_shenqingren_fenhong_jine.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0029_fadanfenhong_is_individual_rate'), + ('orders', '0029_fadanfenhong_is_individual_rate'), ] operations = [ diff --git a/orders/migrations/0031_dingdan_guanshi_fencheng.py b/orders/migrations/0031_dingdan_guanshi_fencheng.py index 3831bb7..0bd71be 100644 --- a/orders/migrations/0031_dingdan_guanshi_fencheng.py +++ b/orders/migrations/0031_dingdan_guanshi_fencheng.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0030_fadan_shenqingren_fenhong_jine'), + ('orders', '0030_fadan_shenqingren_fenhong_jine'), ] operations = [ diff --git a/orders/migrations/0032_delete_crossplatformorderdata_delete_presettlement_and_more.py b/orders/migrations/0032_delete_crossplatformorderdata_delete_presettlement_and_more.py new file mode 100644 index 0000000..f87b00b --- /dev/null +++ b/orders/migrations/0032_delete_crossplatformorderdata_delete_presettlement_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 4.2.27 on 2026-06-15 01:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('orders', '0031_dingdan_guanshi_fencheng'), + ] + + operations = [ + migrations.DeleteModel( + name='CrossPlatformOrderData', + ), + migrations.DeleteModel( + name='PreSettlement', + ), + migrations.RemoveField( + model_name='dingdan', + name='dispatch_type', + ), + migrations.RemoveField( + model_name='dingdan', + name='is_cross', + ), + migrations.RemoveField( + model_name='dingdan', + name='partner_club_id', + ), + migrations.RemoveField( + model_name='dingdan', + name='partner_order_id', + ), + migrations.RemoveField( + model_name='dingdanshangjia', + name='partner_yonghu_id', + ), + migrations.RemoveField( + model_name='fadan', + name='partner_club_id', + ), + ] diff --git a/orders/models.py b/orders/models.py index ca8cba7..8371ff0 100644 --- a/orders/models.py +++ b/orders/models.py @@ -26,12 +26,7 @@ class Dingdan(models.Model): # 订单描述信息 jieshao = models.TextField(null=True, blank=True, verbose_name='订单介绍') beizhu = models.TextField(null=True, blank=True, verbose_name='订单备注') - partner_order_id = models.CharField(max_length=32, null=True, blank=True, db_index=True,verbose_name='对方平台订单ID') - is_cross = models.IntegerField(default=0, verbose_name='是否跨平台订单(0否,1是)') - dispatch_type = models.IntegerField(default=0, choices=[(1, '我方派单'), (2, '对方派单')], verbose_name='派发情况') - # 🔥 新增:对方俱乐部ID - partner_club_id = models.CharField(max_length=6, null=True, blank=True, db_index=True, verbose_name='对方俱乐部ID') # 新增字段:游戏昵称(前端提交的游戏昵称) nicheng = models.CharField(max_length=50, null=True, blank=True, verbose_name='游戏昵称') user1_id = models.CharField(max_length=30,null=True, db_index=True, verbose_name='用户1标识') @@ -112,7 +107,6 @@ class DingdanShangjia(models.Model): sqzhuangtai =models.IntegerField(default=0, null=True, blank=True, verbose_name='处罚申请状态:0待处理,1成功,2驳回') bhliyou = models.TextField(null=True, blank=True, verbose_name='驳回商家处罚理由') sjnicheng =models.CharField(max_length=50,null=True, verbose_name='商家昵称') - partner_yonghu_id = models.CharField(max_length=32, blank=True, null=True, verbose_name='对方平台用户ID') # 在 dingdan/models.py 的 DingdanShangjia 类中添加 shangyige_dingdan_id = models.CharField( max_length=32, @@ -186,9 +180,6 @@ class DingdanPeihu(models.Model): -# 建议路径:dingdan/models.py 或 独立 fadan/models.py -from django.db import models - class Fadan(models.Model): """罚单表——平台各类处罚信息""" id = models.AutoField(primary_key=True, verbose_name='自增主键') @@ -206,8 +197,6 @@ class Fadan(models.Model): create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') # 在 Fadan 模型中添加 - partner_club_id = models.CharField(max_length=6, blank=True, null=True, db_index=True, verbose_name='对方俱乐部ID') - # 在 class Fadan 中添加 shenqingren_shenfen = models.IntegerField( choices=[(1, '客服'), (2, '售后'), (3, '管理员'),(4,'店铺'),(5,'商家')], # 按实际扩展 default=1, @@ -310,88 +299,6 @@ class FadanFenhongLilv(models.Model): -class CrossPlatformOrderData(models.Model): - """ - 跨平台订单扩展数据表 - 存储我方订单与对方平台订单的关联及对方订单状态 - """ - dingdan_id = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='我方订单ID') - partner_order_id = models.CharField(max_length=32, db_index=True, verbose_name='对方平台订单ID') - partner_club_id = models.CharField(max_length=6, db_index=True, verbose_name='对方俱乐部ID') - partner_order_status = models.IntegerField(default=0, verbose_name='对方平台订单状态') # 对方订单当前状态码 - partner_dashou_id = models.CharField(max_length=32, blank=True, default='', verbose_name='对方平台接单打手ID') - partner_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='对方平台订单金额') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'cross_platform_order_data' - verbose_name = '跨平台订单扩展' - verbose_name_plural = '跨平台订单扩展' - indexes = [ - models.Index(fields=['dingdan_id']), - models.Index(fields=['partner_order_id']), - models.Index(fields=['partner_club_id']), - ] - - def __str__(self): - return f"{self.dingdan_id} -> {self.partner_order_id}" - - - - -# apps/dingdan/models.py 中添加 - -class PreSettlement(models.Model): - """ - 预结算表 - 用于记录对方平台派单订单的待结算款项,等待对方确认后才能真正入账 - """ - STATUS_CHOICES = ( - (0, '待结算'), - (1, '已结算'), - (2, '已拒绝'), - ) - # 我方订单ID - dingdan_id = models.CharField(max_length=32, db_index=True, verbose_name='我方订单ID') - # 对方订单ID - partner_order_id = models.CharField(max_length=32, db_index=True, verbose_name='对方订单ID') - # 对方俱乐部ID - partner_club_id = models.CharField(max_length=6, db_index=True, verbose_name='对方俱乐部ID') - # 接单打手ID - dashou_id = models.CharField(max_length=7, db_index=True, verbose_name='接单打手ID') - # 打手应分金额 - amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='打手应分金额') - # 订单金额(原金额,可选) - order_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='订单原金额') - # 结算状态 - status = models.SmallIntegerField(choices=STATUS_CHOICES, default=0, verbose_name='结算状态') - # 处理时间(结算或拒绝的时间) - process_time = models.DateTimeField(null=True, blank=True, verbose_name='处理时间') - # 处理客服/对方操作人(可选) - operator = models.CharField(max_length=32, blank=True, default='', verbose_name='操作人') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'pre_settlement' - verbose_name = '预结算表' - verbose_name_plural = verbose_name - unique_together = [['dingdan_id', 'dashou_id']] - indexes = [ - models.Index(fields=['dingdan_id']), - models.Index(fields=['partner_order_id']), - models.Index(fields=['partner_club_id']), - models.Index(fields=['dashou_id']), - models.Index(fields=['status']), - ] - - def __str__(self): - return f"预结算-{self.dingdan_id} -> {self.partner_order_id}" - - -# apps/dingdan/models.py 中添加 - class OrderDashouHistory(models.Model): """ 订单打手变更历史表 diff --git a/orders/signals.py b/orders/signals.py index 5055f15..5bcfc3c 100644 --- a/orders/signals.py +++ b/orders/signals.py @@ -38,7 +38,7 @@ def ensure_redis_loaded(): # 预加载redis模块 ensure_redis_loaded() -@receiver(pre_save, sender='dingdan.Dingdan') +@receiver(pre_save, sender='orders.Dingdan') def handle_order_status_8(sender, instance, **kwargs): """ 订单状态变为8时,提交定时任务 diff --git a/orders/tasks.py b/orders/tasks.py index 544adc5..39aea2c 100644 --- a/orders/tasks.py +++ b/orders/tasks.py @@ -18,8 +18,6 @@ from orders.models import Dingdan from users.models import UserMain, UserDashou, UserShangjia from utils.celery_utils import safe_decimal_operation, log_task_execution, rollback_on_failure -# from users.utils import update_platform_profit # 导入函数 - logger = logging.getLogger(__name__) @@ -110,11 +108,7 @@ def process_expired_order(self, dingdan_id): result_msg += ",商家信息已更新" if fadan_pingtai == 1: - try: - update_platform_profit(dingdan_id) - except Exception as e: - # 平台收益更新失败不影响主流程,但记录日志 - logger.error(f"强制结单后更新平台收益失败: {str(e)}") + pass # 平台收益更新逻辑待完善 log_task_execution(f"自动结算订单 {dingdan_id}", True, result_msg) return result_msg diff --git a/orders/urls.py b/orders/urls.py index 9a39f63..8122260 100644 --- a/orders/urls.py +++ b/orders/urls.py @@ -7,7 +7,7 @@ from .views import CreateOrderView, WechatPayNotifyView, \ ShangjiaChexiaoView, DashouDingdanHuoquView, ShangpinLeixingHuoquView, QiangdanView, \ DashouDingdanHuoquView1, DashouCOSZhengshuView, DashouTijiaoView, DashouDingdanXiangqingView, AdGetDingdanXiangQing, \ AdTongYiTuiKuanPingTai, AdTongYiTuiKuanShangJia, AdQiangZhiJieDan, AdJuJueTuiKuan,\ - AdGengHuanDaShou,ShangjiaCOSZhengshuView, DashouHuoquLeixingView,PartnerNotifyClaimView, PartnerSyncOrderView, PartnerCheckClaimView, PartnerRefundNotifyView,AdJuJueJieSuan, AdZhuanYiDaTing, AdQuXiaoZhiDing,DashouXiugaiView,DaiLiQunLiaoXiaoXiView,\ + AdGengHuanDaShou,ShangjiaCOSZhengshuView, DashouHuoquLeixingView,AdJuJueJieSuan, AdZhuanYiDaTing, AdQuXiaoZhiDing,DashouXiugaiView,DaiLiQunLiaoXiaoXiView,\ ShangjiaLianjieLiuYanZhuanFaView, DingdanXiangqingView2, JiedanView2, ZxsjghdsView, \ ShangjiaFakuanApplyView, ShangjiaFakuaiXiugaiView @@ -65,12 +65,6 @@ urlpatterns = [ - path('partner_sync_order', csrf_exempt(PartnerSyncOrderView.as_view()), name='东道主派单接口'), - path('partner_check_claim', csrf_exempt(PartnerCheckClaimView.as_view()), name='对方抢单询问接口'), - - - path('partner_notify_claim', csrf_exempt(PartnerNotifyClaimView.as_view()), name='东道主订单被抢通知接口'), - #path('partner_refund_notify', csrf_exempt(PartnerRefundNotifyView.as_view()), name='接受东道主退款通知'), path('dsxiugaidd', DashouXiugaiView.as_view(), name='打手修改订单详情'), path('kptxxfs', DaiLiQunLiaoXiaoXiView.as_view(), name='前端聊天转发'), # 群聊消息代理发送 diff --git a/orders/utils.py b/orders/utils.py index 69d97ba..1ffd140 100644 --- a/orders/utils.py +++ b/orders/utils.py @@ -1,17 +1,13 @@ -import threading -import requests -import time import logging from datetime import date from decimal import Decimal -from django.conf import settings from django.db import transaction from django.db.models import F -from config.models import Club, DailyDispatchStat, DailyIncomeStat, DailyPayoutStat +from config.models import DailyIncomeStat, DailyPayoutStat from orders.models import Dingdan, DingdanPingtai, DingdanShangjia, Lilubiao -from products.models import ShangpinLeixing, Gsfenhong +from products.models import Gsfenhong from users.models import UserDashou, UserGuanshi from backend.utils import update_guanshi_daily_by_action @@ -19,234 +15,22 @@ from backend.utils import update_guanshi_daily_by_action logger = logging.getLogger(__name__) - -# 可选:线程池限制(避免大量线程同时创建,建议使用 Celery,这里简单用线程) -THREAD_POOL_SIZE = 10 -_semaphore = threading.Semaphore(THREAD_POOL_SIZE) - -def sync_order_to_partners(dingdan_id): - """ - 异步向所有开启互通的合作平台同步订单。 - 使用信号量限制并发线程数。 - """ - def _task(): - _semaphore.acquire() - try: - _sync_order_task(dingdan_id) - finally: - _semaphore.release() - - thread = threading.Thread(target=_task) - thread.daemon = True - thread.start() - -def _sync_order_task(dingdan_id): - """实际同步任务(在线程中执行)""" - try: - # 获取订单对象(只取必要字段,提升性能) - dingdan = Dingdan.objects.only( - 'dingdan_id', 'zhuangtai', 'jine', 'dashou_fencheng', 'jieshao', - 'beizhu', 'nicheng', 'leixing_id', 'yaoqiuleixing', 'huiyuan_id', - 'yongjin', 'zhiding_id', 'create_time' - ).get(dingdan_id=dingdan_id) - - # 基本条件判断 - if dingdan.zhuangtai != 1: - logger.info(f"订单 {dingdan_id} 状态 {dingdan.zhuangtai} 不是1,不同步") - return - if dingdan.zhiding_id: - logger.info(f"订单 {dingdan_id} 是指定单,不同步") - return - if dingdan.yaoqiuleixing != 1: - logger.info(f"订单 {dingdan_id} 抢单要求类型不是会员抢单,不同步") - return - if not dingdan.leixing_id: - logger.info(f"订单 {dingdan_id} 无商品类型,不同步") - return - try: - leixing = ShangpinLeixing.objects.get(id=dingdan.leixing_id) - if not leixing.is_cross_enabled: - logger.info(f"订单 {dingdan_id} 的商品类型未开启互通,不同步") - return - except ShangpinLeixing.DoesNotExist: - logger.info(f"订单 {dingdan_id} 的商品类型不存在,不同步") - return - - # 查询所有开启互通的俱乐部 - clubs = Club.objects.filter(is_interop_enabled=True).select_related() - if not clubs.exists(): - return - - # 为每个俱乐部发送订单 - for club in clubs: - _send_to_single_partner(dingdan, club) - - except Exception as e: - logger.error(f"同步订单任务异常: {e}", exc_info=True) - -def _send_to_single_partner(dingdan, club): - """向单个俱乐部发送订单(带重试,幂等处理)""" - # 计算实际金额:订单打手分成 × 对方费率 - actual_amount = dingdan.dashou_fencheng * club.dashou_rate - - # 构建请求数据 - payload = { - 'token': settings.CROSS_PLATFORM_TOKEN, - 'our_club_id': club.club_id, # 我方在对方系统中的标识(存储于对方表的 partner_club_id) - 'partner_club_id': club.partner_club_id, # 对方自己的俱乐部ID - 'partner_order_id': dingdan.dingdan_id, # 我方订单ID作为对方的外部订单号 - 'jine': str(actual_amount), # 对方平台应展示的订单金额 - 'dashou_fencheng': str(actual_amount), # 对方平台打手分成(与金额一致) - 'jieshao': dingdan.jieshao, - 'beizhu': dingdan.beizhu, - 'create_time': dingdan.create_time.isoformat(), - 'nicheng': dingdan.nicheng, - 'leixing_id': dingdan.leixing_id, # 我方商品类型ID,对方需要映射 - 'user_id': get_order_user_id(dingdan), # 对方平台下单用户ID(不带前缀) - } - - # 重试机制(最多3次,指数退避) - for attempt in range(1, 4): - try: - response = requests.post( - club.partner_domain + '/dingdan/partner_sync_order', - json=payload, - timeout=5 - ) - if response.status_code == 200: - data = response.json() - # 对方返回 code=0 表示成功(包括新建或已存在) - if data.get('code') == 0: - # 判断本次响应是新创建还是已存在 - status = data.get('data', {}).get('status', '') - if status == 'existed': - # 订单已经存在于对方系统,不重复统计 - logger.info(f"订单 {dingdan.dingdan_id} 在俱乐部 {club.club_id} 已存在,跳过统计") - else: - # 新创建成功,更新统计 - try: - update_daily_dispatch_stat( - direction=1, # 我方派单 - partner_club_id=club.partner_club_id, - dispatch_amount=actual_amount, - - ) - logger.info(f"成功同步订单 {dingdan.dingdan_id} 到俱乐部 {club.club_id}") - except Exception as e: - logger.error(f"更新派单统计失败: {e}", exc_info=True) - - # 标记订单为跨平台(仅当第一次成功时标记,避免重复标记) - if not dingdan.is_cross: - Dingdan.objects.filter(dingdan_id=dingdan.dingdan_id).update( - is_cross=1, - dispatch_type=1 # 我方派单 - ) - logger.info(f"订单 {dingdan.dingdan_id} 已标记为跨平台订单") - break - else: - # 对方返回业务错误(如映射不存在等),不重试 - logger.error(f"同步订单失败(业务): {data}") - break - else: - logger.error(f"同步订单HTTP失败: {response.status_code}") - except Exception as e: - logger.error(f"同步订单异常(尝试 {attempt}/3): {e}") - - if attempt < 3: - time.sleep(2 ** attempt) # 指数退避 - else: - logger.error(f"同步订单 {dingdan.dingdan_id} 到俱乐部 {club.club_id} 最终失败") - - - -# 获取下单方ID(根据订单类型) def get_order_user_id(order): if order.fadan_pingtai == 1: try: ext = DingdanPingtai.objects.get(dingdan=order) - return ext.laoban_id # 老板ID + return ext.laoban_id except DingdanPingtai.DoesNotExist: return None elif order.fadan_pingtai == 2: try: ext = DingdanShangjia.objects.get(dingdan=order) - return ext.shangjia_id # 商家ID + return ext.shangjia_id except DingdanShangjia.DoesNotExist: return None return None - - - -def update_daily_dispatch_stat(direction, partner_club_id, - dispatch_amount=None, dispatch_count=1, - claimed_amount=None, claimed_count=1, - success_amount=None, success_count=1): - """ - 原子更新每日派单统计(支持派单、抢单、成交三种类型)。 - 只更新传递了非None参数的字段,未传递的字段保持不变。 - - 参数: - direction: 1=我方派单,2=对方派单 - partner_club_id: 对方俱乐部ID - dispatch_amount: 派单金额(Decimal),如果提供则累加到 dispatch_amount 和 dispatch_count - dispatch_count: 派单数量,默认为1,与 dispatch_amount 同时提供时生效 - claimed_amount: 抢单金额(Decimal),如果提供则累加到 claimed_amount 和 claimed_count - claimed_count: 抢单数量,默认为1,与 claimed_amount 同时提供时生效 - success_amount: 成交金额(Decimal),如果提供则累加到 success_amount 和 success_count - success_count: 成交数量,默认为1,与 success_amount 同时提供时生效 - """ - today = date.today() - year, month, day = today.year, today.month, today.day - - with transaction.atomic(): - # 使用 select_for_update 锁定行,避免并发累加冲突 - stat, created = DailyDispatchStat.objects.select_for_update().get_or_create( - date=today, - year=year, - month=month, - day=day, - direction=direction, - partner_club_id=partner_club_id, - defaults={ - 'dispatch_count': dispatch_count if dispatch_amount is not None else 0, - 'dispatch_amount': dispatch_amount or Decimal('0.00'), - 'claimed_count': claimed_count if claimed_amount is not None else 0, - 'claimed_amount': claimed_amount or Decimal('0.00'), - 'success_count': success_count if success_amount is not None else 0, - 'success_amount': success_amount or Decimal('0.00'), - } - ) - - if not created: - update_fields = [] - if dispatch_amount is not None: - stat.dispatch_count = F('dispatch_count') + dispatch_count - stat.dispatch_amount = F('dispatch_amount') + dispatch_amount - update_fields.extend(['dispatch_count', 'dispatch_amount']) - if claimed_amount is not None: - stat.claimed_count = F('claimed_count') + claimed_count - stat.claimed_amount = F('claimed_amount') + claimed_amount - update_fields.extend(['claimed_count', 'claimed_amount']) - if success_amount is not None: - stat.success_count = F('success_count') + success_count - stat.success_amount = F('success_amount') + success_amount - update_fields.extend(['success_count', 'success_amount']) - - if update_fields: - stat.save(update_fields=update_fields) - - logger.info(f"更新派单统计: 日期={today}, 方向={direction}, 俱乐部={partner_club_id}, " - f"派单+{dispatch_count if dispatch_amount else 0}单/{dispatch_amount or 0}, " - f"抢单+{claimed_count if claimed_amount else 0}单/{claimed_amount or 0}, " - f"成交+{success_count if success_amount else 0}单/{success_amount or 0}") - - - - - - def update_daily_income(amount): """ 原子更新当日收入统计(金额累加,笔数加1)。 diff --git a/orders/views.py b/orders/views.py index 8ede6e5..5935591 100644 --- a/orders/views.py +++ b/orders/views.py @@ -39,11 +39,10 @@ from tencentcloud.sts.v20180813 import sts_client, models from utils.weixin_broadcast import WeixinBroadcastSender from utils.chat_utils import _send_group_message, _subscribe_users_to_group, establish_order_chat from utils.fadan_utils import check_fadan_qiangdan_eligible -# from utils.admin import update_platform_profit from orders.utils import ( - calc_shangjia_order_fencheng, sync_order_to_partners, - update_daily_dispatch_stat, update_daily_income, update_daily_payout, + calc_shangjia_order_fencheng, + update_daily_income, update_daily_payout, settle_shangjia_order_guanshi_fenhong ) from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat @@ -59,22 +58,18 @@ from .models import ( Chufajilu, Chufatupian, Fadan, FadanFenhong ) from orders.models import ( - Lilubiao, Pingfen, PreSettlement, Tuikuanjilu, CrossPlatformOrderData + Lilubiao, Pingfen, Tuikuanjilu ) from products.models import Shangpin, ShangpinLeixing, Huiyuangoumai from users.models import UserMain, UserDashou, UserShangjia, UserBoss from rank.models import DashouBiaoxian, Chenghao, DingdanBiaoqian, YonghuChenghao from config.models import ( - Szjilu, Club, ClubConfig, ShangjiaLianjie, OrderTypeMapping + Szjilu, ShangjiaLianjie ) logger = logging.getLogger(__name__) -logger = logging.getLogger(__name__) - - - class PaymentFailView(APIView): """ @@ -312,9 +307,6 @@ class WechatPayNotifyView(APIView): # 更新每日收入统计 update_daily_income(dingdan.jine) - # 跨平台同步 - sync_order_to_partners(dingdan.dingdan_id) - # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用) order_info = { 'dingdan_id': dingdan.dingdan_id, @@ -592,9 +584,6 @@ class PaymentVerifyView(APIView): # 更新每日收入统计 update_daily_income(dingdan.jine) - # 跨平台同步(复查成功也需要通知) - sync_order_to_partners(dingdan.dingdan_id) - def _query_wechat_payment(self, out_trade_no): """ 调用微信支付订单查询接口,返回 (trade_state, transaction_id) @@ -1356,34 +1345,6 @@ class JiedanView(APIView): shangpin.zhenshi_xiaoliang = (shangpin.zhenshi_xiaoliang or 0) + 1 shangpin.save() - # ========== 新增:跨平台我方派单且有对方信息,直接通知对方并跳过本地结算 ========== - if (dingdan.is_cross == 1 - and dingdan.partner_order_id - and dingdan.partner_club_id): - # 更新订单状态为已完成 - dingdan.zhuangtai = 3 - dingdan.clkf = current_user.phone if hasattr(current_user, 'phone') else yonghuid - dingdan.save() - - # 事务提交后异步通知对方平台 - transaction.on_commit( - lambda: self._notify_partner_jiedan( - my_dingdan_id=dingdan.dingdan_id, - partner_club_id=dingdan.partner_club_id, - partner_order_id=dingdan.partner_order_id, - new_status=3, - current_user=current_user - ) - ) - - # 直接返回,跳过后续所有本地打手结算、评价、统计等 - return Response({ - 'code': 0, - 'msg': '结单成功(已通知对方平台)', - 'data': None - }) - # ==================================================================== - # 7. 更新打手扩展表(如果有打手ID和分成) if jiedan_dashou_id and dashou_fencheng > 0: # 查询打手用户 @@ -1490,14 +1451,6 @@ class JiedanView(APIView): # 统计失败不影响主流程 # ============================================= - # 12. 更新平台收益流水(仅在平台发单时) - if dingdan.fadan_pingtai == 1: - try: - update_platform_profit(dingdan_id) - except Exception as e: - # 平台收益更新失败不影响主流程,但记录日志 - logger.error(f"强制结单后更新平台收益失败: {str(e)}") - # 9. 返回成功响应 return Response({ 'code': 0, @@ -1515,61 +1468,6 @@ class JiedanView(APIView): }) - def _notify_partner_jiedan(self, my_dingdan_id, partner_club_id, partner_order_id, new_status, current_user): - """ - 通知对方俱乐部我方订单已完成 - 参照 CrossPlatformForceCompleteView._notify_partner_order_status 实现 - """ - try: - # 1. 获取我方俱乐部ID - my_club_config = ClubConfig.objects.filter(is_self=1).first() - if not my_club_config: - logger.error("未配置我方俱乐部信息,无法通知对方") - return - self_club_id = my_club_config.club_id - - # 2. 查询对方服务器域名 - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain - if not partner_domain: - logger.error(f"对方俱乐部 {partner_club_id} 未配置域名") - return - - # 3. 构造通知请求 - url = partner_domain.rstrip('/') + '/houtai/dfddtk' - payload = { - 'order_id': partner_order_id, # 在对方看来这是他们的订单ID - 'club_id': self_club_id, - 'status': new_status - } - resp = requests.post(url, json=payload, timeout=3) - if resp.status_code == 200: - resp_json = resp.json() - if resp_json.get('code') == 0: - # 4. 更新跨平台订单扩展表(记录对方状态) - CrossPlatformOrderData.objects.update_or_create( - dingdan_id=my_dingdan_id, # 我方订单ID - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'partner_order_status': new_status, - 'partner_dashou_id': '', - 'partner_amount': Decimal('0') - } - ) - logger.info(f"老板结单通知对方成功: {partner_order_id} -> {new_status}") - else: - logger.warning(f"对方接口返回错误: {resp_json.get('msg')}") - else: - logger.warning(f"对方接口HTTP错误: {resp.status_code}") - except requests.exceptions.Timeout: - logger.error(f"通知对方平台超时: {partner_club_id}") - except Exception as e: - logger.error(f"通知对方平台异常: {str(e)}") - - - - class DingdanXiangqingView2(APIView): """订单详情接口(老板端)- 返回完整评价信息""" permission_classes = [IsAuthenticated] @@ -1825,13 +1723,6 @@ class JiedanView2(APIView): except Exception as e: logger.error(f"商品/店铺统计失败: {e}") - # 13. 平台收益流水(仅平台发单) - if order.fadan_pingtai == 1: - try: - update_platform_profit(dingdan_id) - except Exception as e: - logger.error(f"平台收益更新失败: {e}") - # 14. 🆕 写入打手表现表(结算评价) if jiedan_dashou_id: record_dashou_biaoxian( @@ -2099,12 +1990,6 @@ class ShangjiaPaifaView(APIView): return Response({'code': 500, 'msg': '订单创建失败,请稍后重试'}) # ========== 以下操作均在事务外,避免异步任务查不到订单 ========== - # 跨平台同步(内部可能是异步任务,但函数本身是同步调用,现在订单已存在) - try: - sync_order_to_partners(dingdan.dingdan_id) - except Exception as e: - logger.error(f"跨平台同步失败: {e}", exc_info=True) - # Celery 广播任务(异步) try: order_info = { @@ -2446,7 +2331,7 @@ class ShangjiaDingdanXiangqingView(APIView): class ShangjiaJiesuanView(APIView): - """商家结单接口(支持跨平台订单)""" + """商家结单接口""" permission_classes = [permissions.IsAuthenticated] def post(self, request): @@ -2529,10 +2414,6 @@ class ShangjiaJiesuanView(APIView): jine = order.jine or Decimal('0') dashou_fencheng = order.dashou_fencheng or Decimal('0') jiedan_dashou_id = order.jiedan_dashou_id - dispatch_type = order.dispatch_type # 1=我方派单,2=对方派单 - is_cross = order.is_cross - partner_club_id = order.partner_club_id - partner_order_id = order.partner_order_id if not jiedan_dashou_id: return Response({ @@ -2541,79 +2422,35 @@ class ShangjiaJiesuanView(APIView): 'data': None }, status=status.HTTP_400_BAD_REQUEST) - # ========== 跨平台判断 ========== - is_cross_claimed_by_partner = (is_cross == 1 and (partner_club_id or partner_order_id)) + # ========== 本地订单正常结算给打手 ========== + # 查询打手信息 + try: + dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id) + dashou = dashou_user.dashou_profile + except (UserMain.DoesNotExist, UserDashou.DoesNotExist): + return Response({ + 'code': 8, + 'msg': '接单打手不存在或信息不完整', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) - # ========== 根据订单类型处理 ========== - # 情况1:对方派单(我方打手接单)-> 预结算,不直接给打手加钱 - if dispatch_type == 2: - # 创建预结算记录 - PreSettlement.objects.update_or_create( - dingdan_id=dingdan_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id or '000001', - 'dashou_id': jiedan_dashou_id, - 'amount': dashou_fencheng, - 'order_amount': jine, - 'status': 0, - 'operator': request.user.phone, - } - ) - # 更新订单状态 - order.zhuangtai = 3 - order.save() + # 原子更新打手数据(使用 F 表达式) + UserDashou.objects.filter(id=dashou.id).update( + chengjiaozongliang=F('chengjiaozongliang') + 1, + yue=F('yue') + dashou_fencheng, + zonge=F('zonge') + dashou_fencheng, + jinrishouyi=F('jinrishouyi') + dashou_fencheng, + jinyueshouyi=F('jinyueshouyi') + dashou_fencheng, + ) - # 更新统计表 - update_daily_dispatch_stat( - direction=2, - partner_club_id=partner_club_id or '000001', - success_amount=jine, - success_count=1 - ) + # 更新商家成交单量 + UserShangjia.objects.filter(id=shangjia.id).update( + chengjiao=F('chengjiao') + 1, + ) - # 情况2:我方派单且被对方打手接单 -> 只改状态,不给本地打手加钱 - elif dispatch_type == 1 and is_cross_claimed_by_partner: - order.zhuangtai = 3 - order.save() - - update_daily_dispatch_stat( - direction=1, - partner_club_id=partner_club_id or '000001', - success_amount=dashou_fencheng, - success_count=1 - ) - - # 情况3:本地订单 或 跨平台但自家打手接单 -> 正常结算给打手 - else: - # 查询打手信息 - try: - dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id) - dashou = dashou_user.dashou_profile - except (UserMain.DoesNotExist, UserDashou.DoesNotExist): - return Response({ - 'code': 8, - 'msg': '接单打手不存在或信息不完整', - 'data': None - }, status=status.HTTP_400_BAD_REQUEST) - - # 原子更新打手数据(使用 F 表达式) - UserDashou.objects.filter(id=dashou.id).update( - chengjiaozongliang=F('chengjiaozongliang') + 1, - yue=F('yue') + dashou_fencheng, - zonge=F('zonge') + dashou_fencheng, - jinrishouyi=F('jinrishouyi') + dashou_fencheng, - jinyueshouyi=F('jinyueshouyi') + dashou_fencheng, - ) - - # 更新商家成交单量 - UserShangjia.objects.filter(id=shangjia.id).update( - chengjiao=F('chengjiao') + 1, - ) - - # 更新订单状态 - order.zhuangtai = 3 - order.save() + # 更新订单状态 + order.zhuangtai = 3 + order.save() # 处理评分(所有情况都处理) if pingfen > 0: @@ -2941,34 +2778,6 @@ class ShangjiaTuikuanShenqingView(APIView): except Exception as e: logger.error(f"更新打手状态失败:{str(e)},订单{dingdan_id},打手ID{dingdan.jiedan_dashou_id}") - # ========== 新增:如果是跨平台订单,异步通知对方平台 ========== - if dingdan.is_cross == 1: - - def send_refund_notify(): - url = f"{settings.PARTNER_BASE_URL}{settings.PARTNER_REFUND_NOTIFY_URL}" - payload = { - 'token': settings.CROSS_PLATFORM_TOKEN, - 'order_id': dingdan.dingdan_id - } - try: - resp = requests.post(url, json=payload, timeout=5) - if resp.status_code == 200: - data = resp.json() - if data.get('code') == 0: - logger.info(f"跨平台退款通知发送成功: {dingdan.dingdan_id}") - else: - logger.error(f"跨平台退款通知业务失败: {data}") - else: - logger.error(f"跨平台退款通知HTTP失败: {resp.status_code}") - except Exception as e: - logger.error(f"跨平台退款通知异常: {str(e)}", exc_info=True) - - thread = threading.Thread(target=send_refund_notify) - thread.daemon = True - thread.start() - logger.info(f"跨平台退款通知线程已启动: {dingdan.dingdan_id}") - # ========== 新增结束 ========== - return Response({ 'code': 0, 'msg': '退款申请提交成功', @@ -3430,24 +3239,21 @@ class ShangpinLeixingHuoquView(APIView): # ----------------- 基础配置获取 ----------------- def get_self_goeasy_appkey(): try: - cfg = ClubConfig.objects.filter(is_self=1).first() - return cfg.chat_api_key if cfg else None + return getattr(settings, 'GOEASY_APPKEY', None) except Exception: return None def get_self_goeasy_secret(): try: - cfg = ClubConfig.objects.filter(is_self=1).first() - return cfg.chat_api_id if cfg else None + return getattr(settings, 'GOEASY_SECRET', None) except Exception: return None def get_self_storage_domain(): try: - cfg = ClubConfig.objects.filter(is_self=1).first() - return cfg.storage_bucket_domain if cfg else '' + return getattr(settings, 'COS_DOMAIN', '') except Exception: return '' @@ -3493,7 +3299,7 @@ def subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None): def send_group_message( appkey, secret, group_id, sender_id, sender_name, sender_avatar, - message_text='', custom_payload=None, group_name='', order_id='', is_cross=0 + message_text='', custom_payload=None, group_name='', order_id='' ): """ 通过 GoEasy REST API 发送群消息。 @@ -3505,7 +3311,6 @@ def send_group_message( "name": group_name or group_id, "avatar": sender_avatar or '', "orderId": order_id, - "isCross": is_cross } msg_type = 'order' if custom_payload else 'text' payload = custom_payload if custom_payload else message_text @@ -3584,24 +3389,12 @@ def check_user_permission(identity_type, uid, order): return True, '' -# ----------------- 跨平台对方配置获取 ----------------- -def get_partner_club_config(partner_club_id): - try: - return ClubConfig.objects.get(club_id=partner_club_id, is_self=0) - except ClubConfig.DoesNotExist: - return None - - # ----------------- 主视图 ----------------- class DaiLiQunLiaoXiaoXiView(APIView): """ 代理发送群聊消息 - 支持: - - 非跨平台订单:直接用我方 GoEasy Key 以真实身份发送 - - 跨平台订单(双方任意一方派单):除了我方群外,还需用对方 GoEasy Key 以代理身份向对方群发送 - 跨平台判断条件:订单同时存在 partner_order_id 和 partner_club_id 即视为跨平台 """ permission_classes = [permissions.IsAuthenticated] @@ -3659,16 +3452,10 @@ class DaiLiQunLiaoXiaoXiView(APIView): logger.error("[群聊代理] 订阅群组失败") return Response({'code': 500, 'msg': '订阅群组失败'}) - # ===== 8. 判断跨平台(只看对方订单ID和俱乐部ID) ===== - partner_order_id = order.partner_order_id - partner_club_id = order.partner_club_id - is_cross = 1 if (partner_order_id and partner_club_id) else 0 - logger.info(f"[群聊代理] 订单 {order_id} partner_order_id={partner_order_id}, partner_club_id={partner_club_id}, 判定跨平台={is_cross}") - custom_payload = order_payload if message_type == 'order' else None text_content = message_text - # ---------- 先向我方群发送消息(永远执行) ---------- + # ---------- 向我方群发送消息 ---------- local_ok = send_group_message( appkey=self_appkey, secret=self_secret, @@ -3680,88 +3467,19 @@ class DaiLiQunLiaoXiaoXiView(APIView): custom_payload=custom_payload, group_name=group_name, order_id=order.dingdan_id, - is_cross=is_cross ) if not local_ok: return Response({'code': 500, 'msg': '消息发送失败'}) - # ---------- 跨平台转发(如果有对方订单ID和俱乐部ID) ---------- - if is_cross: - logger.info("[群聊代理] 满足跨平台条件,准备双平台发送") - partner_cfg = get_partner_club_config(partner_club_id) - if not partner_cfg or not partner_cfg.chat_api_key: - logger.warning("[群聊代理] 对方俱乐部配置不可用,仅发送我方群") - return Response({'code': 200, 'msg': '发送成功(对方GoEasy未配置)'}) + return Response({'code': 200, 'msg': '发送成功'}) - partner_appkey = partner_cfg.chat_api_key - partner_secret = partner_cfg.chat_api_id or '' - partner_group_id = f"group_{partner_order_id}" - - # 构造代理身份 - agent_sender_id = f"{order.dingdan_id}_{sender_id}" - if identity_type == 'dashou': - agent_sender_name = f"[我方]{sender_name}" - elif identity_type == 'shangjia': - agent_sender_name = f"[我方商家]{sender_name}" - else: - agent_sender_name = f"[我方老板]{sender_name}" - agent_avatar = full_avatar('') - - logger.info(f"[群聊代理] 对方群组={partner_group_id}, 代理身份={agent_sender_id}/{agent_sender_name}") - - # 向对方群发送代理消息 - partner_ok = send_group_message( - appkey=partner_appkey, - secret=partner_secret, - group_id=partner_group_id, - sender_id=agent_sender_id, - sender_name=agent_sender_name, - sender_avatar=agent_avatar, - message_text=text_content, - custom_payload=custom_payload, - group_name='跨平台订单交流', - order_id=partner_order_id, - is_cross=1 - ) - logger.info(f"[群聊代理] 对方群发送结果: {partner_ok}") - - if partner_ok: - return Response({'code': 200, 'msg': '发送成功(双平台)'}) - else: - return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'}) - else: - logger.info("[群聊代理] 非跨平台,仅发送我方群") - return Response({'code': 200, 'msg': '发送成功'}) - - -#通过跨平台字段判断是否是跨平台 -# ---------- 基础配置 ---------- -def get_self_goeasy_appkey(): - try: - cfg = ClubConfig.objects.filter(is_self=1).first() - return cfg.chat_api_key if cfg else None - except Exception: - return None - -def get_self_goeasy_secret(): - try: - cfg = ClubConfig.objects.filter(is_self=1).first() - return cfg.chat_api_id if cfg else None - except Exception: - return None - -def get_partner_club_config(partner_club_id): - try: - return ClubConfig.objects.get(club_id=partner_club_id, is_self=0) - except ClubConfig.DoesNotExist: - return None class ShangjiaLianjieLiuYanZhuanFaView(APIView): """ 商家链接留言转发接口 路径:POST /dingdan/sjljlbxxzf - 固定以 B{商家ID} 身份发送群消息。通过 partner_order_id/partner_club_id 判断是否跨平台。 + 固定以 B{商家ID} 身份发送群消息。 """ permission_classes = [] @@ -3798,8 +3516,6 @@ class ShangjiaLianjieLiuYanZhuanFaView(APIView): group_id = f"group_{order.dingdan_id}" group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}" - local_cross_flag = 1 if (order.partner_order_id and order.partner_club_id) else 0 - if not _subscribe_users_to_group([sender_id], [group_id], appkey, secret): return Response({'code': 500, 'msg': '订阅群组失败'}) @@ -3814,45 +3530,11 @@ class ShangjiaLianjieLiuYanZhuanFaView(APIView): custom_payload=None, group_name=group_name, order_id=order.dingdan_id, - is_cross=local_cross_flag ) if not local_ok: return Response({'code': 500, 'msg': '消息发送失败'}) - # 跨平台转发 - partner_order_id = order.partner_order_id - partner_club_id = order.partner_club_id - - if not partner_order_id or not partner_club_id: - return Response({'code': 200, 'msg': '发送成功'}) - - partner_cfg = get_partner_club_config(partner_club_id) - if not partner_cfg or not partner_cfg.chat_api_key: - return Response({'code': 200, 'msg': '发送成功(对方未配置)'}) - - agent_sender_id = f"{order.dingdan_id}_{sender_id}" - agent_sender_name = f"[我方老板]{shangjia_uid}" - - partner_group_id = f"group_{partner_order_id}" - - partner_ok = _send_group_message( - appkey=partner_cfg.chat_api_key, - secret=partner_cfg.chat_api_id or '', - group_id=partner_group_id, - sender_id=agent_sender_id, - sender_name=agent_sender_name, - sender_avatar='', - message_text=text, - custom_payload=None, - group_name='跨平台订单交流', - order_id=partner_order_id, - is_cross=1 - ) - - if partner_ok: - return Response({'code': 200, 'msg': '发送成功(双平台)'}) - else: - return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'}) + return Response({'code': 200, 'msg': '发送成功'}) class QiangdanView(APIView): @@ -3890,53 +3572,10 @@ class QiangdanView(APIView): return Response({'code': 400, 'msg': order_result}) order = order_result - is_cross = order.is_cross == 1 - dispatch_type = order.dispatch_type - - if is_cross and dispatch_type == 2: - validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid) - if not validation_result['success']: - return Response({'code': 400, 'msg': validation_result['message']}) - - claim_allowed = self._request_partner_check_claim(order, request.user.yonghuid) - if claim_allowed is None: - return Response({'code': 500, 'msg': '跨平台通信失败,请重试'}, status=500) - elif claim_allowed is False: - if order.zhuangtai == 1: - order.zhuangtai = 21 - order.save() - logger.info(f"订单 {dingdan_id} 被对方拒绝抢单,状态改为21") - else: - order.zhuangtai = 20 - order.save() - logger.info(f"订单 {dingdan_id} 已被其他平台抢走,状态改为20") - return Response({'code': 400, 'msg': '订单已被其他平台打手抢走或不可抢'}) - - self._execute_qiangdan(order, dashou_profile, request.user.yonghuid) - - try: - update_daily_dispatch_stat( - direction=2, - partner_club_id=order.partner_club_id or '000001', - claimed_amount=order.jine, - claimed_count=1 - ) - except Exception as e: - logger.error(f"更新抢单统计失败: {e}", exc_info=True) - - elif is_cross and dispatch_type == 1: - validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid) - if not validation_result['success']: - return Response({'code': 400, 'msg': validation_result['message']}) - - self._execute_qiangdan(order, dashou_profile, request.user.yonghuid) - self._async_notify_partners_order_claimed(order) - - else: - validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid) - if not validation_result['success']: - return Response({'code': 400, 'msg': validation_result['message']}) - self._execute_qiangdan(order, dashou_profile, request.user.yonghuid) + validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid) + if not validation_result['success']: + return Response({'code': 400, 'msg': validation_result['message']}) + self._execute_qiangdan(order, dashou_profile, request.user.yonghuid) # ========== 【唯一改动】聊天建立方式 ========== chat_success = establish_order_chat(dingdan_id) @@ -4040,86 +3679,6 @@ class QiangdanView(APIView): dashou_profile.jinyuejiedan = F('jinyuejiedan') + 1 dashou_profile.save() - # ---------- 跨平台询问对方(照抄) ---------- - def _request_partner_check_claim(self, order, dashou_id): - partner_club_id = order.partner_club_id or '000001' - try: - club = Club.objects.filter(partner_club_id=partner_club_id).first() - if not club or not club.partner_domain: - logger.error(f"未找到对方俱乐部配置: partner_club_id={partner_club_id}") - return None - - my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first() - if not my_club: - logger.error(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}") - return None - - payload = { - 'token': settings.CROSS_PLATFORM_TOKEN, - 'wofangdingdan_id': order.partner_order_id, - 'partner_order_id': order.dingdan_id, - 'our_club_id': my_club.club_id, - 'partner_club_id': club.partner_club_id, - 'dashou_id': dashou_id, - 'sj_id': f"Sj{settings.AGENT_SHANGJIA_ID}" - } - - url = club.partner_domain.rstrip('/') + '/dingdan/partner_check_claim' - resp = requests.post(url, json=payload, timeout=5) - - if resp.status_code != 200: - logger.error(f"跨平台抢单询问HTTP失败: {resp.status_code}") - return None - - data = resp.json() - if data.get('code') == 0: - return data.get('data', {}).get('can_claim') is True - else: - logger.error(f"跨平台抢单询问业务失败: {data}") - return False - except Exception as e: - logger.error(f"跨平台抢单询问异常: {e}", exc_info=True) - return None - - # ---------- 异步通知其他俱乐部 ---------- - def _async_notify_partners_order_claimed(self, order): - def notify_task(): - clubs = Club.objects.filter(is_interop_enabled=True).select_related() - for club in clubs: - for attempt in range(1, 4): - success = self._send_claim_notification_to_partner(order, club) - if success: - break - if attempt < 3: - time.sleep(2 ** attempt) - else: - logger.error(f"通知俱乐部 {club.club_id} 订单已抢失败,重试3次后仍失败") - - thread = threading.Thread(target=notify_task) - thread.daemon = True - thread.start() - - def _send_claim_notification_to_partner(self, order, club): - try: - my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first() - if not my_club: - logger.warning(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}") - return False - - payload = { - 'token': settings.CROSS_PLATFORM_TOKEN, - 'our_club_id': my_club.club_id, - 'partner_club_id': club.partner_club_id, - 'partner_order_id': order.dingdan_id, - 'claimed': True - } - url = club.partner_domain.rstrip('/') + '/dingdan/partner_notify_claim' - resp = requests.post(url, json=payload, timeout=3) - return resp.status_code == 200 - except Exception as e: - logger.error(f"通知俱乐部 {club.club_id} 异常: {e}") - return False - class DashouDingdanHuoquView1(APIView): """ @@ -5419,14 +4978,6 @@ class AdQiangZhiJieDan(APIView): # 商家扩展表不存在,跳过商家更新 logger.warning(f"强制结单:商家{shangjia_id}扩展信息不存在,跳过商家更新") - # 12. 更新平台收益流水(仅在平台发单时) - if dingdan_obj.fadan_pingtai == 1: - try: - update_platform_profit(dingdan_id) - except Exception as e: - # 平台收益更新失败不影响主流程,但记录日志 - logger.error(f"强制结单后更新平台收益失败: {str(e)}") - # 11. 返回成功响应 return Response({ 'code': 0, @@ -6236,388 +5787,6 @@ class DashouHuoquLeixingView(APIView): -class PartnerSyncOrderView(APIView): - """ - 接收对方平台派单通知接口 - 对方平台商家派单成功后,异步调用此接口,将订单信息同步过来。 - """ - throttle_classes = [AnonRateThrottle] - permission_classes = [AllowAny] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 验证 token - token = request.data.get('token') - if not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - logger.warning(f"跨平台派单 token 无效: {token}") - return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN) - - # 2. 获取必要参数 - our_club_id = request.data.get('our_club_id') # 对方平台发送的我方俱乐部ID(即我方在对方表中的 partner_club_id) - partner_club_id = request.data.get('partner_club_id') # 对方平台的俱乐部ID(对方自己的ID) - partner_order_id = request.data.get('partner_order_id') - jine = request.data.get('jine') - dashou_fencheng = request.data.get('dashou_fencheng') - jieshao = request.data.get('jieshao') - beizhu = request.data.get('beizhu', '') - create_time_str = request.data.get('create_time') - nicheng = request.data.get('nicheng') - leixing_id = request.data.get('leixing_id') # 对方平台的商品类型ID - partner_yonghu_id = request.data.get('user_id') # 对方下单用户ID - - # 必需字段校验 - required_fields = [our_club_id, partner_club_id, partner_order_id, jine, dashou_fencheng, - jieshao, create_time_str, nicheng, leixing_id] - if not all(required_fields): - logger.error(f"跨平台派单参数缺失: {request.data}") - return Response({'code': 400, 'message': 'Missing required parameters'}, status=status.HTTP_400_BAD_REQUEST) - - # 3. 验证我方俱乐部是否存在且同意接收订单 - try: - my_club = Club.objects.get(partner_club_id=our_club_id) - except Club.DoesNotExist: - logger.warning(f"我方俱乐部不存在,partner_club_id={our_club_id}") - return Response({'code': 404, 'message': 'Club not found'}, status=status.HTTP_404_NOT_FOUND) - - if not my_club.is_receive_enabled: - logger.warning(f"我方俱乐部 {my_club.club_id} 未同意接收订单") - return Response({'code': 403, 'message': 'Receiving not allowed'}, status=status.HTTP_403_FORBIDDEN) - - # 4. 查找订单类型映射 - try: - mapping = OrderTypeMapping.objects.get( - partner_club_id=our_club_id, # 对方在我方的标识 - partner_type_id=leixing_id - ) - our_type_id = mapping.our_type_id - except OrderTypeMapping.DoesNotExist: - logger.error(f"未找到订单类型映射: 对方俱乐部={our_club_id}, 对方类型ID={leixing_id}") - return Response({'code': 400, 'message': 'Order type mapping not found'}, status=status.HTTP_400_BAD_REQUEST) - - # 5. 获取我方商品类型信息(用于获取会员ID、佣金等) - try: - our_leixing = ShangpinLeixing.objects.get(id=our_type_id) - except ShangpinLeixing.DoesNotExist: - logger.error(f"我方商品类型不存在: {our_type_id}") - return Response({'code': 400, 'message': 'Local product type not found'}, status=status.HTTP_400_BAD_REQUEST) - - # 6. 幂等检查:如果已存在相同 partner_order_id 的订单,直接返回(附带 status='existed') - existing = Dingdan.objects.filter(partner_order_id=partner_order_id).first() - if existing: - logger.info(f"跨平台派单幂等返回: partner_order_id={partner_order_id} 已存在") - return Response({ - 'code': 0, - 'data': { - 'local_order_id': existing.dingdan_id, - 'status': 'existed' # 明确告知已存在 - } - }) - - # 7. 生成我方订单ID(确保唯一性) - def generate_dingdan_id(): - timestamp = int(time.time() * 1000) - process_id = os.getpid() % 1000 - random_num = random.randint(1000, 9999) - return f"SJ{timestamp}{process_id:03d}{random_num}" - - # 8. 获取代理商家头像(用于订单图片) - agent_user = UserMain.objects.filter(yonghuid=settings.AGENT_SHANGJIA_ID).first() - agent_avatar = agent_user.avatar if agent_user else '' - - # 9. 计算我方实际打手分成金额:对方传来的打手分成 × 我方接收费率 - try: - actual_dashou_fencheng = Decimal(dashou_fencheng) * my_club.receive_rate - except Exception: - actual_dashou_fencheng = Decimal(dashou_fencheng) - - # 10. 事务创建订单 - try: - with transaction.atomic(): - new_id = generate_dingdan_id() - # 创建主订单 - dingdan = Dingdan.objects.create( - dingdan_id=new_id, - zhuangtai=1, # 普通订单(待接单) - fadan_pingtai=2, # 商家发单 - jine=jine, # 原始订单金额 - dashou_fencheng=actual_dashou_fencheng, # 调整后的打手分成 - jieshao=jieshao, - beizhu=beizhu, - create_time=create_time_str, - yaoqiuleixing=our_leixing.yaoqiuleixing, - nicheng=nicheng, - huiyuan_id=our_leixing.huiyuan_id, - yongjin=our_leixing.yongjin, - leixing_id=our_type_id, - partner_order_id=partner_order_id, # 存储对方订单ID - is_cross=1, - dispatch_type=2, # 对方派单 - partner_club_id=our_club_id, # 对方俱乐部ID - tupian=agent_avatar, # 使用代理商家头像作为订单图片 - ) - # 创建商家扩展表(使用代理商家) - DingdanShangjia.objects.create( - dingdan=dingdan, - shangjia_id=settings.AGENT_SHANGJIA_ID, - sjnicheng='跨平台代理', - partner_yonghu_id=partner_yonghu_id or '', - ) - - # 成功创建后更新每日派单统计(方向=对方派单) - try: - update_daily_dispatch_stat( - direction=2, # 对方派单 - partner_club_id=our_club_id, - dispatch_amount=Decimal(jine), # 统计使用原始订单金额 - - ) - except Exception as e: - logger.error(f"更新派单统计失败: {e}", exc_info=True) - - logger.info(f"跨平台订单同步成功: partner_order_id={partner_order_id}, local_id={new_id}") - - - # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用) - order_info = { - 'dingdan_id': dingdan.dingdan_id, - 'game_type': self._get_game_type_name(dingdan.leixing_id), - 'amount': str(dingdan.jine), - 'order_desc': (dingdan.jieshao or '')[:50], - } - dingdan_guangbo.delay(order_info) # 瞬间返回,不阻塞请求 - - - return Response({ - 'code': 0, - 'data': { - 'local_order_id': new_id, - 'status': 'created' - } - }) - - except Exception as e: - logger.error(f"跨平台订单同步失败: {str(e)}", exc_info=True) - return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - def _get_game_type_name(self, leixing_id): - try: - gt = ShangpinLeixing.objects.filter(id=leixing_id).first() - return gt.jieshao if gt else '游戏订单' - except Exception as e: - logger.error(f"获取游戏类型名称异常: {e}") - return '游戏订单' - - - - - -class PartnerCheckClaimView(APIView): - """ - 接收对方平台抢单询问(对方打手抢我方派出的订单) - 如果允许抢单: - 1. 订单状态改为2(已接单) - 2. 记录对方打手ID、对方俱乐部ID、对方订单ID - 3. 更新派单成交统计(success_count/success_amount) - """ - throttle_classes = [AnonRateThrottle] - permission_classes = [AllowAny] - parser_classes = [JSONParser] - - def post(self, request): - token = request.data.get('token') - if not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - logger.warning(f"token无效: {token}") - return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN) - - wofangdingdan_id = request.data.get('wofangdingdan_id') - partner_order_id = request.data.get('partner_order_id') - our_club_id = request.data.get('our_club_id') - dashou_id = request.data.get('dashou_id') - sj_id = request.data.get('sj_id') - - if not all([wofangdingdan_id, partner_order_id, our_club_id, dashou_id, sj_id]): - logger.error(f"参数缺失: {request.data}") - return Response({'code': 400, 'message': 'Missing parameters'}, status=status.HTTP_400_BAD_REQUEST) - - try: - with transaction.atomic(): - # 查询我方俱乐部配置 - try: - club = Club.objects.select_for_update().get(partner_club_id=our_club_id) - except Club.DoesNotExist: - logger.warning(f"俱乐部配置不存在 partner_club_id={our_club_id}") - return Response({'code': 0, 'data': {'can_claim': False}}) - - if not club.is_interop_enabled: - logger.warning(f"俱乐部 {club.club_id} 未开启互通") - return Response({'code': 0, 'data': {'can_claim': False}}) - - # 锁定订单 - try: - order = Dingdan.objects.select_for_update().get(dingdan_id=wofangdingdan_id) - except Dingdan.DoesNotExist: - logger.warning(f"订单不存在: {wofangdingdan_id}") - return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND) - - # 只有状态1可抢 - if order.zhuangtai != 1: - logger.info(f"订单 {order.dingdan_id} 状态 {order.zhuangtai} 不可抢") - return Response({'code': 0, 'data': {'can_claim': False}}) - - # 商品类型检查 - try: - leixing = ShangpinLeixing.objects.get(id=order.leixing_id) - except ShangpinLeixing.DoesNotExist: - logger.warning(f"商品类型不存在: {order.leixing_id}") - return Response({'code': 0, 'data': {'can_claim': False}}) - - if not leixing.is_cross_enabled: - logger.warning(f"商品类型 {leixing.jieshao} 未开启跨平台") - return Response({'code': 0, 'data': {'can_claim': False}}) - - # 执行抢单:状态改为2(已接单) - order.zhuangtai = 2 - order.jiedan_dashou_id = dashou_id - order.partner_club_id = our_club_id - order.partner_order_id = partner_order_id - if order.is_cross != 1: - order.is_cross = 1 - # dispatch_type 保持原值(1-我方派单),不变 - order.save() - - # 更新派单成交统计(我方派单被成功接单) - update_daily_dispatch_stat( - direction=1, - partner_club_id=our_club_id, - claimed_amount=order.jine, - - ) - - logger.info(f"对方抢单成功: 订单 {order.dingdan_id}, 对方打手 {dashou_id}") - return Response({'code': 0, 'data': {'can_claim': True}}) - - except Exception as e: - logger.error(f"异常: {e}", exc_info=True) - return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -class PartnerNotifyClaimView(APIView): - """ - 接收对方平台抢单成功通知(对方派单被他们自己的打手抢了) - 我方根据 partner_order_id 找到订单,如果状态为1则改为20,不更新任何统计。 - """ - throttle_classes = [AnonRateThrottle] - permission_classes = [AllowAny] - parser_classes = [JSONParser] - - def post(self, request): - token = request.data.get('token') - if not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - logger.warning(f"token无效: {token}") - return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN) - - partner_order_id = request.data.get('partner_order_id') - if not partner_order_id: - logger.error("缺少 partner_order_id") - return Response({'code': 400, 'message': 'Missing partner_order_id'}, status=status.HTTP_400_BAD_REQUEST) - - try: - with transaction.atomic(): - order = Dingdan.objects.select_for_update().get(partner_order_id=partner_order_id) - # 只有状态为1(未被抢)时才改为20 - if order.zhuangtai == 1: - order.zhuangtai = 20 - order.save() - logger.info(f"订单 {order.dingdan_id} 状态改为20(被其他俱乐部抢走)") - else: - logger.info(f"订单 {order.dingdan_id} 当前状态 {order.zhuangtai},无需修改") - return Response({'code': 0, 'message': 'OK'}) - except Dingdan.DoesNotExist: - logger.warning(f"订单不存在 partner_order_id={partner_order_id}") - return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND) - except Exception as e: - logger.error(f"异常: {e}", exc_info=True) - return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -class PartnerRefundNotifyView(APIView): - """ - 接收对方平台退款通知接口 - 对方平台商家申请退款时,调用此接口通知我方,我方将本地对应订单改为退款状态。 - 请求方式: POST - 接口地址: /dingdan/partner_refund_notify - 请求参数: - token: 认证令牌,需与settings.CROSS_PLATFORM_TOKEN一致 - order_id: 对方平台的订单ID(即我方记录的 partner_order_id) - 返回: - code: 0成功,其他失败 - message: 描述信息 - """ - throttle_classes = [AnonRateThrottle] - permission_classes = [AllowAny] - parser_classes = [JSONParser] - - def post(self, request): - # 1. 验证token - token = request.data.get('token') - if not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN): - logger.warning(f"跨平台退款通知 token 无效: {token}") - return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN) - - order_id = request.data.get('order_id') # 对方平台的订单ID(即我方的 partner_order_id) - if not order_id: - logger.error("跨平台退款通知参数缺失: order_id") - return Response({'code': 400, 'message': 'Missing order_id'}, status=status.HTTP_400_BAD_REQUEST) - - try: - with transaction.atomic(): - # 2. 根据 partner_order_id 查找订单(必须存在且为跨平台订单) - order = Dingdan.objects.select_for_update().get(partner_order_id=order_id, is_cross=1) - # 如果订单已完成,不允许退款 - if order.zhuangtai == 3: - logger.warning(f"跨平台退款通知:订单 {order_id} 已完成,不可退款") - return Response({'code': 400, 'message': 'Order already completed, cannot refund'}, status=status.HTTP_400_BAD_REQUEST) - - # 3. 如果订单状态不是5,则改为5(已退款),并更新打手信息 - if order.zhuangtai != 5: - # 记录原打手ID - old_dashou_id = order.jiedan_dashou_id - if old_dashou_id: - try: - # 查询打手用户主表 - dashou_user = UserMain.objects.get(yonghuid=old_dashou_id) - # 通过反向关系获取打手扩展表 - if hasattr(dashou_user, 'dashou_profile'): - dashou_profile = dashou_user.dashou_profile - dashou_profile.zhuangtai = 1 # 恢复打手状态 - dashou_profile.tuikuanliang += 1 # 退款订单量+1 - dashou_profile.save() - logger.info(f"退款通知:打手 {old_dashou_id} 状态已恢复,退款订单+1") - except UserMain.DoesNotExist: - logger.warning(f"退款通知:打手用户不存在 {old_dashou_id}") - except Exception as e: - logger.error(f"退款通知:更新打手信息失败 {str(e)}") - - # 更新订单状态为5 - order.zhuangtai = 5 - # 可选:清空接单打手ID - #order.jiedan_dashou_id = None - order.save() - logger.info(f"跨平台退款通知成功:订单 {order_id} 已改为退款状态") - else: - logger.info(f"跨平台退款通知:订单 {order_id} 已是退款状态,无需重复处理") - - return Response({'code': 0, 'message': 'Refund processed successfully'}) - - except Dingdan.DoesNotExist: - logger.warning(f"跨平台退款通知:订单不存在或不是跨平台订单,partner_order_id={order_id}") - return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND) - except Exception as e: - logger.error(f"跨平台退款通知异常: {str(e)}", exc_info=True) - return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -logger = logging.getLogger(__name__) class AdJuJueJieSuan(APIView): @@ -7043,12 +6212,6 @@ class ZxsjghdsView(APIView): tupian = old_order.tupian fadan_pingtai = old_order.fadan_pingtai - # 跨平台相关 - is_cross = old_order.is_cross - partner_club_id = old_order.partner_club_id - partner_order_id = old_order.partner_order_id - need_notify_partner = is_cross and partner_club_id and partner_order_id - dashou_id = old_order.jiedan_dashou_id # 可能为空 # ========== 6. 退款处理(事务块1:退款原订单 + 打手统计 + 跨平台通知) ========== @@ -7098,48 +6261,6 @@ class ZxsjghdsView(APIView): logger.error(f"更新打手每日统计失败: {e}") # 统计失败不应阻断主流程,但可记录 - # 6.5 跨平台退款通知 - if need_notify_partner: - try: - my_club_config = ClubConfig.objects.filter(is_self=1).first() - if not my_club_config: - raise Exception("我方俱乐部配置不存在") - self_club_id = my_club_config.club_id - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain.rstrip('/') - - notify_url = f"{partner_domain}/houtai/dfddtk" - payload = { - 'order_id': partner_order_id, - 'club_id': self_club_id, - 'status': 5, - 'reason': '商家更换打手退款' - } - resp = requests.post(notify_url, json=payload, timeout=5) - if resp.status_code != 200: - transaction.set_rollback(True) - logger.error(f"对方平台接口HTTP错误: {resp.status_code}") - return Response({'code': 500, 'msg': '通知对方平台失败'}) - resp_json = resp.json() - if resp_json.get('code') != 0: - transaction.set_rollback(True) - logger.error(f"对方平台退款通知失败: {resp_json.get('msg')}") - return Response({'code': 500, 'msg': f"对方平台通知失败: {resp_json.get('msg')}"}) - - # 更新跨平台扩展数据 - CrossPlatformOrderData.objects.update_or_create( - dingdan_id=dingdan_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'partner_order_status': 5, - } - ) - except Exception as e: - transaction.set_rollback(True) - logger.error(f"通知对方平台异常: {e}") - return Response({'code': 500, 'msg': f'通知对方平台失败: {str(e)}'}) - except Exception as e: logger.error(f"退款处理事务失败: {e}", exc_info=True) return Response({'code': 500, 'msg': '退款处理失败'}) @@ -7197,12 +6318,6 @@ class ZxsjghdsView(APIView): except Exception as e: logger.error(f"广播通知失败: {e}") - # 7.5 跨平台同步(如果适用) - try: - sync_order_to_partners(new_dingdan_id) - except Exception as e: - logger.error(f"跨平台同步失败: {e}") - # 注意:此处不更新商家余额、发布量等统计,因为是替换订单 logger.info(f"商家{yonghuid}更换打手成功,旧订单{dingdan_id}已退款,新订单{new_dingdan_id}") @@ -7295,17 +6410,8 @@ class ShangjiaFakuanApplyView(APIView): if Fadan.objects.filter(beichufa_id=dashou_id, guanliandingdan_id=dingdan_id).exists(): return Response({'code': 5, 'msg': '该打手在此订单已被罚款,不能重复申请'}, status=status.HTTP_400_BAD_REQUEST) - # ========== 6. 跨平台信息 ========== - is_cross = order.is_cross - partner_order_id = order.partner_order_id or '' - partner_club_id = order.partner_club_id or '' - dispatch_type = order.dispatch_type or 0 # 1=我方派单,2=对方派单 - - # 是否需要通知对方平台:我方派单且存在对方订单ID - need_notify_partner = is_cross and partner_order_id and dispatch_type == 1 - - # ========== 7. 本地创建罚单(可重用) ========== - def create_local_fadan(): + # ========== 6. 本地创建罚单 ========== + with transaction.atomic(): Fadan.objects.create( beichufa_id=dashou_id, shenqing_chufa=shangjia_id, # 申请人为当前商家 @@ -7316,82 +6422,9 @@ class ShangjiaFakuanApplyView(APIView): zhuangtai=1, # 待缴纳 yingxiang_qiangdan=yingxiang_qiangdan, shenqingren_shenfen=5, # 申请人是商家 - partner_club_id='', ) - - # ========== 8. 本地处罚分支 ========== - if not need_notify_partner: - with transaction.atomic(): - create_local_fadan() - logger.info(f"商家{shangjia_id}对订单{dingdan_id}打手{dashou_id}罚款成功,金额{fakuanjine}") - return Response({'code': 0, 'msg': '罚款申请已提交'}) - - # ========== 9. 跨平台通知分支 ========== - try: - # 9.1 获取我方俱乐部ID - my_club = ClubConfig.objects.filter(is_self=1).first() - if not my_club: - return Response({'code': 500, 'msg': '系统配置错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - self_club_id = my_club.club_id - - # 9.2 获取对方平台域名 - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain.rstrip('/') - except ObjectDoesNotExist: - return Response({'code': 500, 'msg': '对方平台信息未配置'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - # 9.3 构造通知参数 - notify_url = f"{partner_domain}/houtai/kffkdssq_notify" # 与后台接口保持一致 - payload = { - 'order_id': partner_order_id, - 'club_id': self_club_id, - 'chufaliyou': chufa_liyou, - 'fakuanjine': fakuanjine, - 'yingxiang_qiangdan': yingxiang_qiangdan, - 'shenqingren_shenfen': 5, # 商家身份 - 'operator': shangjia_id, # 操作人ID - } - - # 9.4 带重试的通知 - timeout = 3 - retries = 2 - success = False - for attempt in range(retries): - try: - resp = requests.post(notify_url, json=payload, timeout=timeout) - if resp.status_code == 200: - resp_data = resp.json() - if resp_data.get('code') == 0: - success = True - break - else: - logger.warning(f"对方平台罚款通知失败,响应: {resp_data}") - else: - logger.warning(f"对方平台HTTP错误: {resp.status_code}") - except Exception as e: - logger.warning(f"通知对方平台异常,第{attempt+1}次尝试: {e}") - continue - - if not success: - return Response({'code': 500, 'msg': '通知对方平台失败,请稍后重试'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - # 9.5 通知成功后,本地也创建罚单(标记跨平台) - with transaction.atomic(): - Fadan.objects.create( - beichufa_id=dashou_id, - shenqing_chufa=shangjia_id, - shenfen=1, - chufaliyou=chufa_liyou, - fakuanjine=fakuanjine, - guanliandingdan_id=dingdan_id, - zhuangtai=1, - yingxiang_qiangdan=yingxiang_qiangdan, - shenqingren_shenfen=5, - partner_club_id=partner_club_id, # 记录对方俱乐部ID - ) - - logger.info(f"商家{shangjia_id}跨平台罚款成功: {dingdan_id}") - return Response({'code': 0, 'msg': '罚款已提交至对方平台'}) + logger.info(f"商家{shangjia_id}对订单{dingdan_id}打手{dashou_id}罚款成功,金额{fakuanjine}") + return Response({'code': 0, 'msg': '罚款申请已提交'}) class ShangjiaFakuaiXiugaiView(APIView): diff --git a/products/migrations/0002_alter_huiyuangoumai_unique_together_and_more.py b/products/migrations/0002_alter_huiyuangoumai_unique_together_and_more.py index 4807b38..873aaaf 100644 --- a/products/migrations/0002_alter_huiyuangoumai_unique_together_and_more.py +++ b/products/migrations/0002_alter_huiyuangoumai_unique_together_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0001_initial'), + ('products', '0001_initial'), ] operations = [ diff --git a/products/migrations/0003_shangpin_paixu_shangpinleixing_paixu_and_more.py b/products/migrations/0003_shangpin_paixu_shangpinleixing_paixu_and_more.py index d3ceedd..011b22a 100644 --- a/products/migrations/0003_shangpin_paixu_shangpinleixing_paixu_and_more.py +++ b/products/migrations/0003_shangpin_paixu_shangpinleixing_paixu_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0002_alter_huiyuangoumai_unique_together_and_more'), + ('products', '0002_alter_huiyuangoumai_unique_together_and_more'), ] operations = [ diff --git a/products/migrations/0004_shangpin_shenhezhuangtai_and_more.py b/products/migrations/0004_shangpin_shenhezhuangtai_and_more.py index 5c71efd..c5e7a35 100644 --- a/products/migrations/0004_shangpin_shenhezhuangtai_and_more.py +++ b/products/migrations/0004_shangpin_shenhezhuangtai_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0003_shangpin_paixu_shangpinleixing_paixu_and_more'), + ('products', '0003_shangpin_paixu_shangpinleixing_paixu_and_more'), ] operations = [ diff --git a/products/migrations/0005_alter_gsfenhong_dashouid.py b/products/migrations/0005_alter_gsfenhong_dashouid.py index c339de3..4d4e422 100644 --- a/products/migrations/0005_alter_gsfenhong_dashouid.py +++ b/products/migrations/0005_alter_gsfenhong_dashouid.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0004_shangpin_shenhezhuangtai_and_more'), + ('products', '0004_shangpin_shenhezhuangtai_and_more'), ] operations = [ diff --git a/products/migrations/0006_gsfenhong_huiyuan_id_huiyuan_zuzhangfc_duocifenhong.py b/products/migrations/0006_gsfenhong_huiyuan_id_huiyuan_zuzhangfc_duocifenhong.py index 6f00224..adabfa6 100644 --- a/products/migrations/0006_gsfenhong_huiyuan_id_huiyuan_zuzhangfc_duocifenhong.py +++ b/products/migrations/0006_gsfenhong_huiyuan_id_huiyuan_zuzhangfc_duocifenhong.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0005_alter_gsfenhong_dashouid'), + ('products', '0005_alter_gsfenhong_dashouid'), ] operations = [ @@ -32,7 +32,7 @@ class Migration(migrations.Migration): ('zuzhang_fenhong', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='组长分红金额')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('huiyuan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shangpin.huiyuan', verbose_name='会员')), + ('huiyuan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.huiyuan', verbose_name='会员')), ], options={ 'verbose_name': '多次分红配置', diff --git a/products/migrations/0007_remove_duocifenhong_duoci_fenho_guanshi_b3de53_idx_and_more.py b/products/migrations/0007_remove_duocifenhong_duoci_fenho_guanshi_b3de53_idx_and_more.py index ef78070..a354d2b 100644 --- a/products/migrations/0007_remove_duocifenhong_duoci_fenho_guanshi_b3de53_idx_and_more.py +++ b/products/migrations/0007_remove_duocifenhong_duoci_fenho_guanshi_b3de53_idx_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0006_gsfenhong_huiyuan_id_huiyuan_zuzhangfc_duocifenhong'), + ('products', '0006_gsfenhong_huiyuan_id_huiyuan_zuzhangfc_duocifenhong'), ] operations = [ diff --git a/products/migrations/0008_gsfenhong_zuzhang_fenhong_gsfenhong_zuzhang_id.py b/products/migrations/0008_gsfenhong_zuzhang_fenhong_gsfenhong_zuzhang_id.py index c3493dd..04974ac 100644 --- a/products/migrations/0008_gsfenhong_zuzhang_fenhong_gsfenhong_zuzhang_id.py +++ b/products/migrations/0008_gsfenhong_zuzhang_fenhong_gsfenhong_zuzhang_id.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0007_remove_duocifenhong_duoci_fenho_guanshi_b3de53_idx_and_more'), + ('products', '0007_remove_duocifenhong_duoci_fenho_guanshi_b3de53_idx_and_more'), ] operations = [ diff --git a/products/migrations/0009_shangpin_ewai_dashou_fencheng_and_more.py b/products/migrations/0009_shangpin_ewai_dashou_fencheng_and_more.py index 3f1401d..5783b55 100644 --- a/products/migrations/0009_shangpin_ewai_dashou_fencheng_and_more.py +++ b/products/migrations/0009_shangpin_ewai_dashou_fencheng_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0008_gsfenhong_zuzhang_fenhong_gsfenhong_zuzhang_id'), + ('products', '0008_gsfenhong_zuzhang_fenhong_gsfenhong_zuzhang_id'), ] operations = [ diff --git a/products/migrations/0010_shangpinleixing_is_cross_enabled.py b/products/migrations/0010_shangpinleixing_is_cross_enabled.py index 9b7dedc..3f2582d 100644 --- a/products/migrations/0010_shangpinleixing_is_cross_enabled.py +++ b/products/migrations/0010_shangpinleixing_is_cross_enabled.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0009_shangpin_ewai_dashou_fencheng_and_more'), + ('products', '0009_shangpin_ewai_dashou_fencheng_and_more'), ] operations = [ diff --git a/products/migrations/0011_shangpin_dianpu_shangpin_dianpu_leixing_and_more.py b/products/migrations/0011_shangpin_dianpu_shangpin_dianpu_leixing_and_more.py index 213c5dd..9ec490a 100644 --- a/products/migrations/0011_shangpin_dianpu_shangpin_dianpu_leixing_and_more.py +++ b/products/migrations/0011_shangpin_dianpu_shangpin_dianpu_leixing_and_more.py @@ -7,20 +7,20 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('shangdian', '0001_initial'), - ('shangpin', '0010_shangpinleixing_is_cross_enabled'), + ('shop', '0001_initial'), + ('products', '0010_shangpinleixing_is_cross_enabled'), ] operations = [ migrations.AddField( model_name='shangpin', name='dianpu', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='shangdian.dianpu', verbose_name='所属店铺'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='shop.dianpu', verbose_name='所属店铺'), ), migrations.AddField( model_name='shangpin', name='dianpu_leixing', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shangdian.shangpinleixingdianpu', verbose_name='店铺商品类型'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shop.shangpinleixingdianpu', verbose_name='店铺商品类型'), ), migrations.AddField( model_name='shangpin', @@ -40,12 +40,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='shangpinzhuanqu', name='dianpu', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='shangdian.dianpu', verbose_name='所属店铺'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='shop.dianpu', verbose_name='所属店铺'), ), migrations.AddField( model_name='shangpinzhuanqu', name='dianpu_leixing', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shangdian.shangpinleixingdianpu', verbose_name='店铺商品类型'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shop.shangpinleixingdianpu', verbose_name='店铺商品类型'), ), migrations.AddField( model_name='shangpinzhuanqu', diff --git a/products/migrations/0012_shangpinmeiritongji.py b/products/migrations/0012_shangpinmeiritongji.py index 10bc77c..0df137a 100644 --- a/products/migrations/0012_shangpinmeiritongji.py +++ b/products/migrations/0012_shangpinmeiritongji.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0011_shangpin_dianpu_shangpin_dianpu_leixing_and_more'), + ('products', '0011_shangpin_dianpu_shangpin_dianpu_leixing_and_more'), ] operations = [ @@ -29,7 +29,7 @@ class Migration(migrations.Migration): ('dianpu_zongshouyi', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='店铺总收益')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('shangpin', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shangpin.shangpin', verbose_name='商品ID')), + ('shangpin', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.shangpin', verbose_name='商品ID')), ], options={ 'verbose_name': '商品每日统计', diff --git a/products/migrations/0013_shangpinleixing_bankuai.py b/products/migrations/0013_shangpinleixing_bankuai.py index e0802dc..cb260b2 100644 --- a/products/migrations/0013_shangpinleixing_bankuai.py +++ b/products/migrations/0013_shangpinleixing_bankuai.py @@ -7,14 +7,14 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dengji', '0003_alter_dashoubiaoxian_bankuai'), - ('shangpin', '0012_shangpinmeiritongji'), + ('rank', '0003_alter_dashoubiaoxian_bankuai'), + ('products', '0012_shangpinmeiritongji'), ] operations = [ migrations.AddField( model_name='shangpinleixing', name='bankuai', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dengji.bankuai', verbose_name='所属板块'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rank.bankuai', verbose_name='所属板块'), ), ] diff --git a/products/migrations/0014_huiyuan_bankuai.py b/products/migrations/0014_huiyuan_bankuai.py index 61dbc6d..a63de19 100644 --- a/products/migrations/0014_huiyuan_bankuai.py +++ b/products/migrations/0014_huiyuan_bankuai.py @@ -7,14 +7,14 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dengji', '0010_kaohejilufeiyong'), - ('shangpin', '0013_shangpinleixing_bankuai'), + ('rank', '0010_kaohejilufeiyong'), + ('products', '0013_shangpinleixing_bankuai'), ] operations = [ migrations.AddField( model_name='huiyuan', name='bankuai', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dengji.bankuai', verbose_name='所属板块'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rank.bankuai', verbose_name='所属板块'), ), ] diff --git a/products/migrations/0015_gsfenhong_fenhong_leixing.py b/products/migrations/0015_gsfenhong_fenhong_leixing.py index 777c6d4..0248dd1 100644 --- a/products/migrations/0015_gsfenhong_fenhong_leixing.py +++ b/products/migrations/0015_gsfenhong_fenhong_leixing.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangpin', '0014_huiyuan_bankuai'), + ('products', '0014_huiyuan_bankuai'), ] operations = [ diff --git a/products/migrations/0016_remove_shangpinleixing_is_cross_enabled.py b/products/migrations/0016_remove_shangpinleixing_is_cross_enabled.py new file mode 100644 index 0000000..a23701b --- /dev/null +++ b/products/migrations/0016_remove_shangpinleixing_is_cross_enabled.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.27 on 2026-06-15 01:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0015_gsfenhong_fenhong_leixing'), + ] + + operations = [ + migrations.RemoveField( + model_name='shangpinleixing', + name='is_cross_enabled', + ), + ] diff --git a/products/models.py b/products/models.py index 3a91f7e..c91f3ab 100644 --- a/products/models.py +++ b/products/models.py @@ -75,7 +75,6 @@ class ShangpinLeixing(models.Model): yongjin = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='佣金要求') jieshao = models.CharField(max_length=45, null=True, blank=True, verbose_name='类型介绍') # 15汉字 tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='类型图片URL') # 用CharField - is_cross_enabled = models.BooleanField(default=False, verbose_name='是否开启平台互通') paixu = models.IntegerField(default=0, verbose_name='排序权重') shenhezhuangtai = models.IntegerField(default=1, verbose_name='审核状态', help_text='1=正常, 2=审核专用') bankuai = models.ForeignKey( diff --git a/rank/migrations/0001_initial.py b/rank/migrations/0001_initial.py index d9615ff..c672140 100644 --- a/rank/migrations/0001_initial.py +++ b/rank/migrations/0001_initial.py @@ -11,7 +11,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), + ('orders', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), ] operations = [ @@ -37,7 +37,7 @@ class Migration(migrations.Migration): ('tubiao_url', models.CharField(blank=True, max_length=200, null=True, verbose_name='称号图标相对URL')), ('texiao_miaoshu', models.TextField(blank=True, null=True, verbose_name='特效描述(JSON)')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('bankuai', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='所属板块(空=全板块通用)')), + ('bankuai', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='rank.bankuai', verbose_name='所属板块(空=全板块通用)')), ], options={ 'verbose_name': '称号', @@ -50,7 +50,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('huode_time', models.DateTimeField(auto_now_add=True, verbose_name='获得时间')), - ('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.chenghao', verbose_name='称号')), + ('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rank.chenghao', verbose_name='称号')), ('yonghu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chenghao_list', to=settings.AUTH_USER_MODEL, verbose_name='用户')), ], options={ @@ -74,7 +74,7 @@ class Migration(migrations.Migration): ('dingdan_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='关联订单ID')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='所属板块')), + ('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rank.bankuai', verbose_name='所属板块')), ], options={ 'verbose_name': '经验变动记录', @@ -94,8 +94,8 @@ class Migration(migrations.Migration): ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('jiedan_time', models.DateTimeField(blank=True, null=True, verbose_name='接单时间')), ('tijiao_time', models.DateTimeField(blank=True, null=True, verbose_name='提交时间')), - ('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='所属板块')), - ('dingdan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='biaoxian', to='dingdan.dingdan', verbose_name='关联订单')), + ('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rank.bankuai', verbose_name='所属板块')), + ('dingdan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='biaoxian', to='orders.dingdan', verbose_name='关联订单')), ], options={ 'verbose_name': '打手订单表现', diff --git a/rank/migrations/0002_alter_dashoubiaoxian_dingdan.py b/rank/migrations/0002_alter_dashoubiaoxian_dingdan.py index 5b42a74..ba3c12e 100644 --- a/rank/migrations/0002_alter_dashoubiaoxian_dingdan.py +++ b/rank/migrations/0002_alter_dashoubiaoxian_dingdan.py @@ -7,14 +7,14 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), - ('dengji', '0001_initial'), + ('orders', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), + ('rank', '0001_initial'), ] operations = [ migrations.AlterField( model_name='dashoubiaoxian', name='dingdan', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='biaoxian', to='dingdan.dingdan', to_field='dingdan_id', verbose_name='关联订单'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='biaoxian', to='orders.dingdan', to_field='dingdan_id', verbose_name='关联订单'), ), ] diff --git a/rank/migrations/0003_alter_dashoubiaoxian_bankuai.py b/rank/migrations/0003_alter_dashoubiaoxian_bankuai.py index ca07ec2..921d444 100644 --- a/rank/migrations/0003_alter_dashoubiaoxian_bankuai.py +++ b/rank/migrations/0003_alter_dashoubiaoxian_bankuai.py @@ -7,13 +7,13 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dengji', '0002_alter_dashoubiaoxian_dingdan'), + ('rank', '0002_alter_dashoubiaoxian_dingdan'), ] operations = [ migrations.AlterField( model_name='dashoubiaoxian', name='bankuai', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='所属板块'), + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='rank.bankuai', verbose_name='所属板块'), ), ] diff --git a/rank/migrations/0004_dingdanbiaoqian.py b/rank/migrations/0004_dingdanbiaoqian.py index bddbc36..56a9f8b 100644 --- a/rank/migrations/0004_dingdanbiaoqian.py +++ b/rank/migrations/0004_dingdanbiaoqian.py @@ -4,8 +4,8 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), # 保持你之前的依赖 - ('dengji', '0003_alter_dashoubiaoxian_bankuai'), + ('orders', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), # 保持你之前的依赖 + ('rank', '0003_alter_dashoubiaoxian_bankuai'), ] operations = [ diff --git a/rank/migrations/0005_fix_state.py b/rank/migrations/0005_fix_state.py index 0128710..beb2aea 100644 --- a/rank/migrations/0005_fix_state.py +++ b/rank/migrations/0005_fix_state.py @@ -5,8 +5,8 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), # 保持你之前的依赖 - ('dengji', '0004_dingdanbiaoqian'), + ('orders', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), # 保持你之前的依赖 + ('rank', '0004_dingdanbiaoqian'), ] operations = [ @@ -18,8 +18,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.chenghao', verbose_name='标签')), - ('dingdan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dingdan.dingdan', to_field='dingdan_id', verbose_name='订单')), + ('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rank.chenghao', verbose_name='标签')), + ('dingdan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='orders.dingdan', to_field='dingdan_id', verbose_name='订单')), ], options={ 'verbose_name': '订单需求标签', diff --git a/rank/migrations/0006_shenhejilu.py b/rank/migrations/0006_shenhejilu.py index a41aa13..60e1db8 100644 --- a/rank/migrations/0006_shenhejilu.py +++ b/rank/migrations/0006_shenhejilu.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dengji', '0005_fix_state'), + ('rank', '0005_fix_state'), ] operations = [ @@ -22,8 +22,8 @@ class Migration(migrations.Migration): ('jiaofei_jine', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='缴纳费用')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('bankuai', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='审核板块')), - ('chenghao', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dengji.chenghao', verbose_name='审核标签')), + ('bankuai', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='rank.bankuai', verbose_name='审核板块')), + ('chenghao', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='rank.chenghao', verbose_name='审核标签')), ], options={ 'verbose_name': '审核记录', diff --git a/rank/migrations/0007_kaoheguanbankuai.py b/rank/migrations/0007_kaoheguanbankuai.py index cd43b15..9b5a8bc 100644 --- a/rank/migrations/0007_kaoheguanbankuai.py +++ b/rank/migrations/0007_kaoheguanbankuai.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dengji', '0006_shenhejilu'), + ('rank', '0006_shenhejilu'), ] operations = [ @@ -16,7 +16,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('kaoheguan_id', models.CharField(db_index=True, max_length=7, verbose_name='考核官用户ID')), - ('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.bankuai', verbose_name='所属板块')), + ('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rank.bankuai', verbose_name='所属板块')), ], options={ 'verbose_name': '考核官板块关联', diff --git a/rank/migrations/0008_chenghao_kaohe_guize_shenhejilu_dashou_beizhu_and_more.py b/rank/migrations/0008_chenghao_kaohe_guize_shenhejilu_dashou_beizhu_and_more.py index f69f5fc..544c853 100644 --- a/rank/migrations/0008_chenghao_kaohe_guize_shenhejilu_dashou_beizhu_and_more.py +++ b/rank/migrations/0008_chenghao_kaohe_guize_shenhejilu_dashou_beizhu_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dengji', '0007_kaoheguanbankuai'), + ('rank', '0007_kaoheguanbankuai'), ] operations = [ diff --git a/rank/migrations/0009_chenghao_kaioi_jinpai_alter_shenhejilu_jilu_id_and_more.py b/rank/migrations/0009_chenghao_kaioi_jinpai_alter_shenhejilu_jilu_id_and_more.py index 2c45282..9854f9b 100644 --- a/rank/migrations/0009_chenghao_kaioi_jinpai_alter_shenhejilu_jilu_id_and_more.py +++ b/rank/migrations/0009_chenghao_kaioi_jinpai_alter_shenhejilu_jilu_id_and_more.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dengji', '0008_chenghao_kaohe_guize_shenhejilu_dashou_beizhu_and_more'), + ('rank', '0008_chenghao_kaohe_guize_shenhejilu_dashou_beizhu_and_more'), ] operations = [ @@ -30,7 +30,7 @@ class Migration(migrations.Migration): ('shenheguan_id', models.CharField(max_length=7, verbose_name='考核官用户ID')), ('yuanyin', models.TextField(verbose_name='拒绝原因')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('jilu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.shenhejilu', to_field='jilu_id', verbose_name='审核记录')), + ('jilu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rank.shenhejilu', to_field='jilu_id', verbose_name='审核记录')), ], options={ 'verbose_name': '拒绝原因记录', @@ -45,7 +45,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('cishu', models.IntegerField(verbose_name='第几次考核')), ('feiyong', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='需缴纳金额')), - ('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.chenghao', verbose_name='标签')), + ('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rank.chenghao', verbose_name='标签')), ], options={ 'verbose_name': '考核次数费用', diff --git a/rank/migrations/0010_kaohejilufeiyong.py b/rank/migrations/0010_kaohejilufeiyong.py index acad590..43d7128 100644 --- a/rank/migrations/0010_kaohejilufeiyong.py +++ b/rank/migrations/0010_kaohejilufeiyong.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dengji', '0009_chenghao_kaioi_jinpai_alter_shenhejilu_jilu_id_and_more'), + ('rank', '0009_chenghao_kaioi_jinpai_alter_shenhejilu_jilu_id_and_more'), ] operations = [ @@ -17,7 +17,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('cishu', models.IntegerField(verbose_name='第几次考核')), ('jine', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='缴纳金额')), - ('jilu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.shenhejilu', to_field='jilu_id', verbose_name='考核记录')), + ('jilu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rank.shenhejilu', to_field='jilu_id', verbose_name='考核记录')), ], options={ 'verbose_name': '考核记录费用', diff --git a/rank/migrations/0011_kaohepaytemp.py b/rank/migrations/0011_kaohepaytemp.py index 6a24eac..d040f67 100644 --- a/rank/migrations/0011_kaohepaytemp.py +++ b/rank/migrations/0011_kaohepaytemp.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dengji', '0010_kaohejilufeiyong'), + ('rank', '0010_kaohejilufeiyong'), ] operations = [ diff --git a/shop/migrations/0001_initial.py b/shop/migrations/0001_initial.py index 6077a4d..797a874 100644 --- a/shop/migrations/0001_initial.py +++ b/shop/migrations/0001_initial.py @@ -11,7 +11,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('shangpin', '0010_shangpinleixing_is_cross_enabled'), + ('products', '0010_shangpinleixing_is_cross_enabled'), ] operations = [ @@ -45,7 +45,7 @@ class Migration(migrations.Migration): ('erweima_url', models.CharField(blank=True, max_length=500, null=True, verbose_name='店铺二维码相对URL')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('pingzheng', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='shangdian.yonghupingzheng', verbose_name='关联用户凭证')), + ('pingzheng', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='shop.yonghupingzheng', verbose_name='关联用户凭证')), ], options={ 'verbose_name': '平台商家店铺', @@ -59,7 +59,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='绑定自增ID')), ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('dianpu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shangdian.dianpu', verbose_name='关联店铺ID')), + ('dianpu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.dianpu', verbose_name='关联店铺ID')), ('yonghu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, to_field='yonghuid', verbose_name='用户ID')), ], options={ @@ -79,8 +79,8 @@ class Migration(migrations.Migration): ('shangjia_zhuangtai', models.BooleanField(default=True, verbose_name='是否上架')), ('fengjin_zhuangtai', models.BooleanField(default=False, verbose_name='是否封禁')), ('shenhe_zhuangtai', models.BooleanField(default=False, help_text='True=审核通过,False=未审核', verbose_name='平台审核状态')), - ('dianpu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shangdian.dianpu', verbose_name='所属店铺')), - ('gonggong_leixing', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shangpin.shangpinleixing', verbose_name='映射的公共商品类型')), + ('dianpu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.dianpu', verbose_name='所属店铺')), + ('gonggong_leixing', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.shangpinleixing', verbose_name='映射的公共商品类型')), ], options={ 'verbose_name': '店铺商品类型映射', diff --git a/shop/migrations/0002_alter_yonghupingzheng_yonghu.py b/shop/migrations/0002_alter_yonghupingzheng_yonghu.py index 9dc84eb..dc49c90 100644 --- a/shop/migrations/0002_alter_yonghupingzheng_yonghu.py +++ b/shop/migrations/0002_alter_yonghupingzheng_yonghu.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangdian', '0001_initial'), + ('shop', '0001_initial'), ] operations = [ diff --git a/shop/migrations/0003_dianpumorenpeizhi_dianpu_kaiqi_meiri_xiane_and_more.py b/shop/migrations/0003_dianpumorenpeizhi_dianpu_kaiqi_meiri_xiane_and_more.py index 8f09233..22f7435 100644 --- a/shop/migrations/0003_dianpumorenpeizhi_dianpu_kaiqi_meiri_xiane_and_more.py +++ b/shop/migrations/0003_dianpumorenpeizhi_dianpu_kaiqi_meiri_xiane_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangdian', '0002_alter_yonghupingzheng_yonghu'), + ('shop', '0002_alter_yonghupingzheng_yonghu'), ] operations = [ diff --git a/shop/migrations/0004_dianpushangpinshenheshezhi.py b/shop/migrations/0004_dianpushangpinshenheshezhi.py index d1fda6c..a9adb9b 100644 --- a/shop/migrations/0004_dianpushangpinshenheshezhi.py +++ b/shop/migrations/0004_dianpushangpinshenheshezhi.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangdian', '0003_dianpumorenpeizhi_dianpu_kaiqi_meiri_xiane_and_more'), + ('shop', '0003_dianpumorenpeizhi_dianpu_kaiqi_meiri_xiane_and_more'), ] operations = [ diff --git a/shop/migrations/0005_dianpu_fenhong_choucheng_feilv_and_more.py b/shop/migrations/0005_dianpu_fenhong_choucheng_feilv_and_more.py index aabbb9d..2265498 100644 --- a/shop/migrations/0005_dianpu_fenhong_choucheng_feilv_and_more.py +++ b/shop/migrations/0005_dianpu_fenhong_choucheng_feilv_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangdian', '0004_dianpushangpinshenheshezhi'), + ('shop', '0004_dianpushangpinshenheshezhi'), ] operations = [ diff --git a/shop/migrations/0006_dianpushouzhimeiritongji_gei_shangji_cishu_and_more.py b/shop/migrations/0006_dianpushouzhimeiritongji_gei_shangji_cishu_and_more.py index 2426c1e..16c33a4 100644 --- a/shop/migrations/0006_dianpushouzhimeiritongji_gei_shangji_cishu_and_more.py +++ b/shop/migrations/0006_dianpushouzhimeiritongji_gei_shangji_cishu_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shangdian', '0005_dianpu_fenhong_choucheng_feilv_and_more'), + ('shop', '0005_dianpu_fenhong_choucheng_feilv_and_more'), ] operations = [ diff --git a/shop/models.py b/shop/models.py index aea67c8..e55cbe5 100644 --- a/shop/models.py +++ b/shop/models.py @@ -178,7 +178,7 @@ class ShangpinLeixingDianpu(models.Model): ) # 🔥 强制映射到公共商品类型,不可为空 gonggong_leixing = models.ForeignKey( - 'shop.ShangpinLeixing', + 'products.ShangpinLeixing', on_delete=models.CASCADE, db_index=True, verbose_name='映射的公共商品类型' diff --git a/shop/views.py b/shop/views.py index 61b0e93..0583b04 100644 --- a/shop/views.py +++ b/shop/views.py @@ -49,12 +49,9 @@ from products.models import ShangpinLeixing, ShangpinZhuanqu, Shangpin, Huiyuan from orders.models import ( Dingdan, DingdanPingtai, Dashoutupian, Tuikuanjilu, - OrderDashouHistory, CrossPlatformOrderData, Fadan + OrderDashouHistory, Fadan ) -from config.models import ClubConfig, Club - - logger = logging.getLogger(__name__) @@ -2021,8 +2018,6 @@ class SubShopModifyView(APIView): """ -# 如果有 ClubConfig 模型用于获取存储桶域名,确保导入(如没有可忽略,使用 settings 中的 OSS 域名) - # ---------- 图片域名 ---------- """ 店铺订单管理接口 @@ -2192,22 +2187,9 @@ class ShopOrderStatisticsView(APIView): def get_oss_domain(): """ 获取对象存储的域名,末尾包含 '/'。 - 优先从跨平台配置表 ClubConfig 中获取本俱乐部的 storage_bucket_domain, - 如果获取失败或表不存在,则使用预设的 OSS 域名作为降级方案。 返回:字符串,如 'https://example.com/' """ - try: - # 尝试从跨平台配置中读取 - from kuaping.models import ClubConfig - config = ClubConfig.objects.filter(is_self=1).first() - if config and config.storage_bucket_domain: - # 确保域名末尾有 '/' - return config.storage_bucket_domain.rstrip('/') + '/' - except Exception: - # 表不存在或其它异常,忽略,使用下方默认域名 - pass - # 默认 OSS 域名,请替换为你的实际域名 - return 'https://xingque999qygwuyq-1404472910.cos.ap-shanghai.myqcloud.com/' + return getattr(settings, 'COS_DOMAIN', 'https://xingque999qygwuyq-1404472910.cos.ap-shanghai.myqcloud.com/').rstrip('/') + '/' # ================================================================ @@ -2674,15 +2656,6 @@ class ShopRefundView(APIView): logger.exception("退款事务执行失败") return Response({'code': 500, 'msg': '退款处理失败,请稍后重试'}) - # 7. 跨平台通知(如果存在对方平台订单ID,异步通知,不影响主流程) - partner_order_id = order.partner_order_id - partner_club_id = order.partner_club_id - if partner_order_id and partner_club_id: - try: - self._notify_partner_refund(dingdan_id, partner_club_id, partner_order_id, reason, order.jine) - except Exception as e: - logger.error(f"跨平台退款通知异常: {e}") - return Response({'code': 200, 'msg': '退款成功'}) # ========== 微信退款 ========== @@ -2767,53 +2740,6 @@ class ShopRefundView(APIView): except Exception as e: logger.error(f"更新退款记录异常: {e}") - # ========== 跨平台通知 ========== - def _notify_partner_refund(self, dingdan_id, partner_club_id, partner_order_id, reason, amount): - """ - 通知对方平台我方已退款。 - 此方法执行不影响主流程,任何异常仅记录日志。 - """ - try: - my_config = ClubConfig.objects.filter(is_self=1).first() - if not my_config: - logger.error("未配置我方俱乐部信息,无法通知对方") - return - self_club_id = my_config.club_id - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain.rstrip('/') - notify_url = f"{partner_domain}/houtai/dfddtk" - - payload = { - 'order_id': partner_order_id, - 'club_id': self_club_id, - 'status': 5, - 'reason': reason or '店铺退款' - } - resp = requests.post(notify_url, json=payload, timeout=3) - if resp.status_code == 200: - resp_json = resp.json() - if resp_json.get('code') == 0: - # 更新跨平台扩展表状态 - cross_data, _ = CrossPlatformOrderData.objects.get_or_create( - dingdan_id=dingdan_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'partner_order_status': resp_json.get('data', {}).get('partner_status', 0), - 'partner_dashou_id': resp_json.get('data', {}).get('partner_dashou_id', ''), - 'partner_amount': amount or 0, - } - ) - if not _: - cross_data.partner_order_status = resp_json.get('data', {}).get('partner_status', 0) - cross_data.save() - else: - logger.warning(f"对方平台返回错误: {resp_json.get('msg')}") - else: - logger.warning(f"通知对方HTTP错误: {resp.status_code}") - except Exception as e: - logger.error(f"通知对方平台异常: {e}") - # ========== 工具方法 ========== def _generate_nonce_str(self): return ''.join(random.choices(string.ascii_letters + string.digits, k=32)) @@ -2887,8 +2813,6 @@ class ShopForceCompleteView(APIView): logger.warning(f"订单 {dingdan_id} 当前状态为 {order.zhuangtai},不是结算中") return Response({'code': 400, 'msg': f'订单状态不是结算中,当前状态: {order.zhuangtai}'}) - # ========== 5. 判断是否为跨平台订单(有对方订单ID就认为是跨平台) ========== - is_cross = order.is_cross and (order.partner_order_id or order.partner_club_id) dashou_id = order.jiedan_dashou_id dashou_fencheng = order.dashou_fencheng or Decimal('0.00') jine = order.jine or Decimal('0.00') @@ -2898,103 +2822,72 @@ class ShopForceCompleteView(APIView): # 锁定订单行,防止并发 order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id) - # ============================ - # 情况1:跨平台订单(我方派单给对方平台打手) - # ============================ - if is_cross: - # 只需更新我方订单状态,打手在对方平台,不处理我方打手余额 - order.zhuangtai = 3 # 已完成 - order.clkf = str(shop_id) # 处理人记录为店铺ID - order.save() + if not dashou_id: + return Response({'code': 400, 'msg': '订单无接单打手,无法结算'}) + if dashou_fencheng < 0: + return Response({'code': 400, 'msg': '打手分成金额无效'}) - # 店铺结算时,若涉及跨平台收益,这里是否要更新店铺统计? - # 根据业务需求,可在此处调用 update_dianpu_daily_stat 等 - # 但通常跨平台订单的资金已在对方平台流转,店铺仅作为发单方, - # 收益可能已在其他环节计算,此处可根据需要添加统计。 + # ---------- 更新打手余额和统计 ---------- + try: + dashou_user = UserMain.objects.get(yonghuid=dashou_id) + dashou_profile = dashou_user.dashou_profile + dashou_profile.chengjiaozongliang += 1 + dashou_profile.yue += dashou_fencheng + dashou_profile.zonge += dashou_fencheng + dashou_profile.jinrishouyi += dashou_fencheng + dashou_profile.jinyueshouyi += dashou_fencheng + dashou_profile.zhuangtai = 1 # 空闲 + dashou_profile.save() + except (UserMain.DoesNotExist, ObjectDoesNotExist): + logger.warning(f"本地订单结算:打手 {dashou_id} 不存在,跳过打手更新") - # 通知对方平台(异步,不影响主流程) - if order.partner_club_id and order.partner_order_id: - transaction.on_commit( - lambda: self._notify_partner_order_complete( - order.partner_club_id, - order.partner_order_id, - shop_id + # ---------- 更新订单状态 ---------- + order.zhuangtai = 3 + order.clkf = str(shop_id) + order.save() + + # ---------- 打手每日统计(成交) ---------- + try: + update_dashou_daily_by_action( + yonghuid=dashou_id, + amount=dashou_fencheng, + action=2 # 2 表示成交(结算) + ) + except Exception as e: + logger.error(f"打手每日统计更新失败: {e}") + + # ---------- 店铺收益统计(结算) ---------- + if pingtai_ext.dianpu_shouyi: + try: + # 商品每日统计(只要有商品ID就执行) + if order.shangpin_id: + # 从扩展表获取已计算好的收益值(下单时已存储) + pingtai_shouyi = Decimal('0.00') + dianpu_shouyi = Decimal('0.00') + if hasattr(order, 'pingtai_kuozhan'): + pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00') + dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') + + update_shangpin_daily_stat( + shangpin_id=order.shangpin_id, + caozuo_leixing=2, # 3 = 退款 + dingdan_jiage=order.jine, + pingtai_shouyi=pingtai_shouyi, + dianpu_zongshouyi=dianpu_shouyi ) - ) + logger.info(f"商品统计更新完成,商品ID: {order.shangpin_id}") - # ============================ - # 情况2:本地订单(我方打手接单) - # ============================ - else: - if not dashou_id: - return Response({'code': 400, 'msg': '订单无接单打手,无法结算'}) - if dashou_fencheng < 0: - return Response({'code': 400, 'msg': '打手分成金额无效'}) - # ---------- 更新打手余额和统计 ---------- - try: - dashou_user = UserMain.objects.get(yonghuid=dashou_id) - dashou_profile = dashou_user.dashou_profile - dashou_profile.chengjiaozongliang += 1 - dashou_profile.yue += dashou_fencheng - dashou_profile.zonge += dashou_fencheng - dashou_profile.jinrishouyi += dashou_fencheng - dashou_profile.jinyueshouyi += dashou_fencheng - dashou_profile.zhuangtai = 1 # 空闲 - dashou_profile.save() - except (UserMain.DoesNotExist, ObjectDoesNotExist): - logger.warning(f"本地订单结算:打手 {dashou_id} 不存在,跳过打手更新") - - # ---------- 更新订单状态 ---------- - order.zhuangtai = 3 - order.clkf = str(shop_id) - order.save() - - # ---------- 打手每日统计(成交) ---------- - try: - update_dashou_daily_by_action( - yonghuid=dashou_id, - amount=dashou_fencheng, - action=2 # 2 表示成交(结算) + update_dianpu_daily_stat( + dianpu_id=shop_id, + caozuo_leixing=2, # 结算操作 + dingdan_jiage=jine, + dianpu_shouyi=pingtai_ext.dianpu_shouyi, ) except Exception as e: - logger.error(f"打手每日统计更新失败: {e}") + logger.error(f"店铺每日统计更新失败: {e}") - # ---------- 店铺收益统计(结算) ---------- - # 根据订单上的店铺收益 (从扩展表取) 更新店铺每日统计及余额 - # 前提:订单创建时已存储了 dianpu_shouyi 到扩展表 - if pingtai_ext.dianpu_shouyi: - try: - # 商品每日统计(只要有商品ID就执行) - if order.shangpin_id: - # 从扩展表获取已计算好的收益值(下单时已存储) - pingtai_shouyi = Decimal('0.00') - dianpu_shouyi = Decimal('0.00') - if hasattr(order, 'pingtai_kuozhan'): - pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00') - dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') - - update_shangpin_daily_stat( - shangpin_id=order.shangpin_id, - caozuo_leixing=2, # 3 = 退款 - dingdan_jiage=order.jine, - pingtai_shouyi=pingtai_shouyi, - dianpu_zongshouyi=dianpu_shouyi - ) - logger.info(f"商品统计更新完成,商品ID: {order.shangpin_id}") - - - update_dianpu_daily_stat( - dianpu_id=shop_id, - caozuo_leixing=2, # 结算操作 - dingdan_jiage=jine, - dianpu_shouyi=pingtai_ext.dianpu_shouyi, - # 如果涉及上级分红,可从扩展表取相应字段传入,此处略 - ) - except Exception as e: - logger.error(f"店铺每日统计更新失败: {e}") - - # ========== 6. 记录操作日志 ========== + # ========== 5. 记录操作日志 ========== logger.info(f"强制结单成功,订单 {dingdan_id},店铺 {shop_id}") return Response({'code': 200, 'msg': '强制结单成功'}) @@ -3006,40 +2899,6 @@ class ShopForceCompleteView(APIView): logger.error(f"强制结单接口异常: {str(e)}", exc_info=True) return Response({'code': 500, 'msg': '服务器内部错误'}) - # ==================== 通知对方平台(异步) ==================== - def _notify_partner_order_complete(self, partner_club_id, partner_order_id, shop_id): - """通知对方平台我方订单已完成(状态3)""" - try: - # 尝试获取对方域名等配置(与退款接口类似) - - - my_config = ClubConfig.objects.filter(is_self=1).first() - if not my_config: - logger.error("未配置我方俱乐部信息,无法通知对方") - return - self_club_id = my_config.club_id - - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain.rstrip('/') - notify_url = f"{partner_domain}/houtai/dfddtk" - - payload = { - 'order_id': partner_order_id, - 'club_id': self_club_id, - 'status': 3 - } - resp = requests.post(notify_url, json=payload, timeout=3) - if resp.status_code == 200: - resp_data = resp.json() - if resp_data.get('code') == 0: - logger.info(f"通知对方订单已完成: {partner_order_id}") - else: - logger.warning(f"对方平台返回错误: {resp_data.get('msg')}") - else: - logger.warning(f"通知对方HTTP错误: {resp.status_code}") - except Exception as e: - logger.error(f"通知对方平台异常: {e}") - @@ -3105,13 +2964,8 @@ class ShopTransferHallView(APIView): # ---------- 5. 提取关键字段 ---------- current_dashou_id = order.jiedan_dashou_id zhiding_id = order.zhiding_id - partner_club_id = order.partner_club_id - partner_order_id = order.partner_order_id dashou_fencheng = order.dashou_fencheng or 0 # 用于退款统计 - # 是否跨平台:只要有对方信息即认为是跨平台订单 - is_cross = bool(partner_club_id or partner_order_id) - try: with transaction.atomic(): # 锁定订单行 @@ -3130,8 +2984,8 @@ class ShopTransferHallView(APIView): operator=str(shop_id) ) - # ---------- 7. 释放本地打手 + 退款统计(仅当非跨平台时处理) ---------- - if not is_cross and current_dashou_id: + # ---------- 7. 释放本地打手 + 退款统计 ---------- + if current_dashou_id: try: dashou_profile = UserDashou.objects.get(user__yonghuid=current_dashou_id) dashou_profile.zhuangtai = 1 # 设为空闲 @@ -3150,9 +3004,7 @@ class ShopTransferHallView(APIView): except Exception as e: logger.error(f"打手每日统计失败: {e}") - # ---------- 8. 清空对方平台信息并重置订单 ---------- - order.partner_club_id = None - order.partner_order_id = None + # ---------- 8. 重置订单 ---------- order.jiedan_dashou_id = None order.clkf = str(shop_id) @@ -3160,59 +3012,6 @@ class ShopTransferHallView(APIView): order.zhuangtai = 7 if zhiding_id else 1 order.save() - # ---------- 9. 异步通知对方平台(如果有对方信息) ---------- - if is_cross and partner_club_id and partner_order_id: - # 获取我方俱乐部ID - my_club_config = None - try: - my_club_config = ClubConfig.objects.filter(is_self=1).first() - except Exception as e: - logger.error(f"获取俱乐部配置失败: {e}") - - if my_club_config: - self_club_id = my_club_config.club_id - def notify(): - try: - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain.rstrip('/') - notify_url = f"{partner_domain}/houtai/kpttzzydt" - payload = { - 'order_id': partner_order_id, - 'club_id': self_club_id - } - resp = requests.post(notify_url, json=payload, timeout=3) - if resp.status_code == 200: - resp_json = resp.json() - if resp_json.get('code') == 0: - # 更新跨平台订单数据(原接口逻辑) - try: - cross_data, _ = CrossPlatformOrderData.objects.get_or_create( - dingdan_id=dingdan_id, - defaults={ - 'partner_order_id': partner_order_id, - 'partner_club_id': partner_club_id, - 'partner_order_status': resp_json.get('data', {}).get('partner_status', 0), - 'partner_dashou_id': '', - 'partner_amount': order.jine or 0, - } - ) - if not _: - cross_data.partner_order_status = resp_json.get('data', {}).get('partner_status', 0) - cross_data.save(update_fields=['partner_order_status']) - logger.info(f"转移大厅通知对方成功: {dingdan_id}") - except Exception as e: - logger.error(f"更新跨平台订单数据失败: {e}") - else: - logger.warning(f"对方平台返回错误: {resp_json.get('msg')}") - else: - logger.warning(f"对方接口HTTP错误: {resp.status_code}") - except Club.DoesNotExist: - logger.error(f"未找到对方俱乐部配置: partner_club_id={partner_club_id}") - except Exception as e: - logger.error(f"转移大厅通知对方异常: {e}") - - threading.Thread(target=notify).start() - logger.info(f"转移大厅成功,订单 {dingdan_id}") return Response({'code': 200, 'msg': '转移大厅成功'}) @@ -3292,13 +3091,8 @@ class ShopFineApplyView(APIView): if Fadan.objects.filter(beichufa_id=dashou_id, guanliandingdan_id=dingdan_id).exists(): return Response({'code': 400, 'msg': '该打手已被罚款过'}) - # ---------- 6. 跨平台相关字段 ---------- - partner_order_id = order.partner_order_id or '' - partner_club_id = order.partner_club_id or '' - dispatch_type = order.dispatch_type or 0 # 1=我方派单,2=对方派单 - - # 本地创建罚单的通用函数 - def create_local_fadan(partner_club=''): + # ---------- 6. 创建罚单 ---------- + with transaction.atomic(): Fadan.objects.create( beichufa_id=dashou_id, shenqing_chufa=str(shop_id), # 申请人记为店铺ID @@ -3309,64 +3103,9 @@ class ShopFineApplyView(APIView): zhuangtai=1, # 待缴纳 yingxiang_qiangdan=yingxiang_qiangdan, shenqingren_shenfen=4, # 申请人身份:4 店铺管理员 - partner_club_id=partner_club, ) - - # ---------- 7. 判断处罚流向 ---------- - # 情况A:没有对方订单ID,或对方派单 → 本地处罚 - if not partner_order_id or dispatch_type == 2: - with transaction.atomic(): - create_local_fadan() - logger.info(f"本地罚款成功: 订单 {dingdan_id}, 打手 {dashou_id}, 金额 {fakuanjine}") - return Response({'code': 200, 'msg': '罚款已生成'}) - - # ---------- 8. 情况B:有对方订单ID且我方派单 → 跨平台通知对方 ---------- - # 获取我方俱乐部ID - try: - my_club = ClubConfig.objects.filter(is_self=1).first() - if not my_club: - return Response({'code': 500, 'msg': '系统配置错误,缺少本俱乐部信息'}) - self_club_id = my_club.club_id - club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id) - partner_domain = club_rel.partner_domain - except Club.DoesNotExist: - return Response({'code': 500, 'msg': '未找到对方平台配置,无法通知'}) - except Exception as e: - logger.error(f"获取对方平台信息失败: {e}") - return Response({'code': 500, 'msg': '获取对方平台信息失败'}) - - # 构造通知 payload - url = partner_domain.rstrip('/') + '/houtai/kffkdssq_notify' - payload = { - 'order_id': partner_order_id, # 对方平台的订单ID - 'club_id': self_club_id, # 我方俱乐部ID - 'chufaliyou': chufaliyou, - 'fakuanjine': fakuanjine, - 'yingxiang_qiangdan': yingxiang_qiangdan, - 'shenqingren_shenfen': 4, # 店铺管理员身份 - 'operator': str(shop_id), - } - - # 带重试的通知 - success = False - for _ in range(2): - try: - resp = requests.post(url, json=payload, timeout=3) - if resp.status_code == 200 and resp.json().get('code') == 0: - success = True - break - except Exception: - continue - - if not success: - return Response({'code': 500, 'msg': '通知对方平台失败,请稍后重试'}) - - # 通知成功后,本地也记录一笔(标记为跨平台) - with transaction.atomic(): - create_local_fadan(partner_club=partner_club_id) - - logger.info(f"跨平台罚款通知成功: 订单 {dingdan_id}") - return Response({'code': 200, 'msg': '罚款已提交至对方平台'}) + logger.info(f"本地罚款成功: 订单 {dingdan_id}, 打手 {dashou_id}, 金额 {fakuanjine}") + return Response({'code': 200, 'msg': '罚款已生成'}) except Exception as e: logger.error(f"店铺罚款申请异常: {traceback.format_exc()}") diff --git a/users/migrations/0002_adminprofile_yaoqingma.py b/users/migrations/0002_adminprofile_yaoqingma.py index 638ed87..7bb1ce2 100644 --- a/users/migrations/0002_adminprofile_yaoqingma.py +++ b/users/migrations/0002_adminprofile_yaoqingma.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0001_initial'), + ('users', '0001_initial'), ] operations = [ diff --git a/users/migrations/0003_rankingrecord.py b/users/migrations/0003_rankingrecord.py index 0110fff..49857bd 100644 --- a/users/migrations/0003_rankingrecord.py +++ b/users/migrations/0003_rankingrecord.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0002_adminprofile_yaoqingma'), + ('users', '0002_adminprofile_yaoqingma'), ] operations = [ diff --git a/users/migrations/0004_alter_usermain_avatar.py b/users/migrations/0004_alter_usermain_avatar.py index b006029..2ed5ea8 100644 --- a/users/migrations/0004_alter_usermain_avatar.py +++ b/users/migrations/0004_alter_usermain_avatar.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0003_rankingrecord'), + ('users', '0003_rankingrecord'), ] operations = [ diff --git a/users/migrations/0005_officialaccountuser_usermain_unionid.py b/users/migrations/0005_officialaccountuser_usermain_unionid.py index 7b368ae..7cbd851 100644 --- a/users/migrations/0005_officialaccountuser_usermain_unionid.py +++ b/users/migrations/0005_officialaccountuser_usermain_unionid.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0004_alter_usermain_avatar'), + ('users', '0004_alter_usermain_avatar'), ] operations = [ diff --git a/users/migrations/0006_alter_officialaccountuser_unionid.py b/users/migrations/0006_alter_officialaccountuser_unionid.py index e6d9df2..79f43ed 100644 --- a/users/migrations/0006_alter_officialaccountuser_unionid.py +++ b/users/migrations/0006_alter_officialaccountuser_unionid.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0005_officialaccountuser_usermain_unionid'), + ('users', '0005_officialaccountuser_usermain_unionid'), ] operations = [ diff --git a/users/migrations/0007_userkefu.py b/users/migrations/0007_userkefu.py index cf8e244..a159dbf 100644 --- a/users/migrations/0007_userkefu.py +++ b/users/migrations/0007_userkefu.py @@ -8,7 +8,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0006_alter_officialaccountuser_unionid'), + ('users', '0006_alter_officialaccountuser_unionid'), ] operations = [ diff --git a/users/migrations/0008_userguanshi_fenghong_erci_and_more.py b/users/migrations/0008_userguanshi_fenghong_erci_and_more.py index 84e4b47..7ad4d6b 100644 --- a/users/migrations/0008_userguanshi_fenghong_erci_and_more.py +++ b/users/migrations/0008_userguanshi_fenghong_erci_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0007_userkefu'), + ('users', '0007_userkefu'), ] operations = [ diff --git a/users/migrations/0009_userdashou_ewai_xiane_userdashou_jinritixian_jine_and_more.py b/users/migrations/0009_userdashou_ewai_xiane_userdashou_jinritixian_jine_and_more.py index f0290f3..7c81b14 100644 --- a/users/migrations/0009_userdashou_ewai_xiane_userdashou_jinritixian_jine_and_more.py +++ b/users/migrations/0009_userdashou_ewai_xiane_userdashou_jinritixian_jine_and_more.py @@ -8,7 +8,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0008_userguanshi_fenghong_erci_and_more'), + ('users', '0008_userguanshi_fenghong_erci_and_more'), ] operations = [ diff --git a/users/migrations/0010_userguanshi_invite_qrcode_url.py b/users/migrations/0010_userguanshi_invite_qrcode_url.py index 818f7d2..36df9ed 100644 --- a/users/migrations/0010_userguanshi_invite_qrcode_url.py +++ b/users/migrations/0010_userguanshi_invite_qrcode_url.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0009_userdashou_ewai_xiane_userdashou_jinritixian_jine_and_more'), + ('users', '0009_userdashou_ewai_xiane_userdashou_jinritixian_jine_and_more'), ] operations = [ diff --git a/users/migrations/0011_usermain_shoujihao_renzheng.py b/users/migrations/0011_usermain_shoujihao_renzheng.py index 7bf606a..16e2dc4 100644 --- a/users/migrations/0011_usermain_shoujihao_renzheng.py +++ b/users/migrations/0011_usermain_shoujihao_renzheng.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0010_userguanshi_invite_qrcode_url'), + ('users', '0010_userguanshi_invite_qrcode_url'), ] operations = [ diff --git a/users/migrations/0012_usershenheguan.py b/users/migrations/0012_usershenheguan.py index 85dae1c..2975667 100644 --- a/users/migrations/0012_usershenheguan.py +++ b/users/migrations/0012_usershenheguan.py @@ -8,7 +8,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0011_usermain_shoujihao_renzheng'), + ('users', '0011_usermain_shoujihao_renzheng'), ] operations = [ diff --git a/users/migrations/0013_usershenheguan_is_renzheng.py b/users/migrations/0013_usershenheguan_is_renzheng.py index 41f4a85..c78645a 100644 --- a/users/migrations/0013_usershenheguan_is_renzheng.py +++ b/users/migrations/0013_usershenheguan_is_renzheng.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0012_usershenheguan'), + ('users', '0012_usershenheguan'), ] operations = [ diff --git a/users/migrations/0014_tixianautorecord_tixianshenhejilu_and_more.py b/users/migrations/0014_tixianautorecord_tixianshenhejilu_and_more.py index bf27299..6500e1e 100644 --- a/users/migrations/0014_tixianautorecord_tixianshenhejilu_and_more.py +++ b/users/migrations/0014_tixianautorecord_tixianshenhejilu_and_more.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('yonghu', '0013_usershenheguan_is_renzheng'), + ('users', '0013_usershenheguan_is_renzheng'), ] operations = [ diff --git a/users/views.py b/users/views.py index ebef143..fdd29e5 100644 --- a/users/views.py +++ b/users/views.py @@ -47,7 +47,7 @@ from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCod from users.fadan_fenhong_utils import process_fadan_fenhong from orders.utils import ( - update_daily_payout, update_daily_dispatch_stat, + update_daily_payout, update_daily_income, settle_shangjia_order_guanshi_fenhong ) from backend.utils import ( @@ -72,7 +72,7 @@ from .tixian_shenhe_views import process_audit_collect from orders.models import ( Dingdan, DingdanPingtai, DingdanShangjia, Lilubiao, Chufajilu, Chufatupian, Tuikuanjilu, Dashoutupian, - PreSettlement, Fadan, FadanShensuTupian + Fadan, FadanShensuTupian ) from products.models import ( ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu @@ -6990,7 +6990,7 @@ class KefuGetOrderListView(APIView): # ---------- 查询条件 ---------- # 基础条件:平台发单 + 非跨平台 - base_q = Q(fadan_pingtai=1) & Q(is_cross=0) + base_q = Q(fadan_pingtai=1) if dingdan_id: q_conditions = base_q & Q(dingdan_id=dingdan_id) @@ -7024,7 +7024,7 @@ class KefuGetOrderListView(APIView): q_conditions &= Q(clkf__icontains=clkf) # ---------- 统计(仅按发单平台,非跨平台) ---------- - stats = Dingdan.objects.filter(fadan_pingtai=1, is_cross=0).aggregate( + stats = Dingdan.objects.filter(fadan_pingtai=1).aggregate( total_orders=Count('id'), completed_orders=Count('id', filter=Q(zhuangtai=3)), refund_orders=Count('id', filter=Q(zhuangtai=5)), @@ -7033,14 +7033,14 @@ class KefuGetOrderListView(APIView): # ---------- 状态计数(所有状态都返回,非跨平台) ---------- status_counts = { - 'all': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0).count(), - '1,7': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai__in=[1,7]).count(), - '2': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=2).count(), - '8': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=8).count(), - '3': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=3).count(), - '4': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=4).count(), - '5': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=5).count(), - '6': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=6).count(), + 'all': Dingdan.objects.filter(fadan_pingtai=1).count(), + '1,7': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai__in=[1,7]).count(), + '2': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=2).count(), + '8': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=8).count(), + '3': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=3).count(), + '4': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=4).count(), + '5': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=5).count(), + '6': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=6).count(), } # ---------- 分页查询 ---------- @@ -7155,7 +7155,7 @@ class KefuGetShangjiaOrderListView(APIView): # ---------- 查询条件 ---------- # 基础条件:商家发单 + 非跨平台 - base_q = Q(fadan_pingtai=2) & Q(is_cross=0) + base_q = Q(fadan_pingtai=2) if dingdan_id: q_conditions = base_q & Q(dingdan_id=dingdan_id) @@ -7193,7 +7193,7 @@ class KefuGetShangjiaOrderListView(APIView): ).values_list('yonghuid', flat=True)) # ---------- 统计(仅按发单平台,非跨平台) ---------- - stats = Dingdan.objects.filter(fadan_pingtai=2, is_cross=0).aggregate( + stats = Dingdan.objects.filter(fadan_pingtai=2).aggregate( total_orders=Count('id'), completed_orders=Count('id', filter=Q(zhuangtai=3)), refund_orders=Count('id', filter=Q(zhuangtai=5)), @@ -7202,14 +7202,14 @@ class KefuGetShangjiaOrderListView(APIView): # ---------- 状态计数(所有状态都返回,非跨平台) ---------- status_counts = { - 'all': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0).count(), - '1,7': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai__in=[1,7]).count(), - '2': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=2).count(), - '8': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=8).count(), - '3': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=3).count(), - '4': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=4).count(), - '5': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=5).count(), - '6': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=6).count(), + 'all': Dingdan.objects.filter(fadan_pingtai=2).count(), + '1,7': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai__in=[1,7]).count(), + '2': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=2).count(), + '8': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=8).count(), + '3': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=3).count(), + '4': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=4).count(), + '5': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=5).count(), + '6': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=6).count(), } # ---------- 分页查询 ---------- diff --git a/utils/chat_utils.py b/utils/chat_utils.py index b33287b..2e412b9 100644 --- a/utils/chat_utils.py +++ b/utils/chat_utils.py @@ -4,65 +4,24 @@ import logging import requests from django.conf import settings from orders.models import Dingdan, DingdanPingtai, DingdanShangjia -from config.models import ClubConfig from users.models import UserMain, UserDashou, UserBoss, UserShangjia logger = logging.getLogger('chat_utils') -# ========== 辅助配置获取(从 ClubConfig) ========== -def _get_self_club_config(): - if not hasattr(_get_self_club_config, '_config'): - try: - _get_self_club_config._config = ClubConfig.objects.filter(is_self=1).first() - except Exception as e: - logger.warning(f"获取我方俱乐部配置失败: {e}") - _get_self_club_config._config = None - return _get_self_club_config._config - +# ========== 辅助配置获取 ========== def _get_self_goeasy_appkey(): - cfg = _get_self_club_config() - if cfg and cfg.chat_api_key: - return cfg.chat_api_key return getattr(settings, 'GOEASY_APPKEY', '') def _get_self_goeasy_secret(): - cfg = _get_self_club_config() - if cfg and cfg.chat_api_id: - return cfg.chat_api_id return getattr(settings, 'GOEASY_SECRET', '') -def _get_self_storage_domain(): - cfg = _get_self_club_config() - if cfg and cfg.storage_bucket_domain: - return cfg.storage_bucket_domain - return '' - -def _get_self_default_avatar(): - cfg = _get_self_club_config() - if cfg and cfg.club_avatar: - return cfg.club_avatar - return '' - # ========== 头像拼接 ========== def _full_local_avatar(relative_url): - storage_domain = _get_self_storage_domain() - default_avatar = _get_self_default_avatar() - if not relative_url: - return default_avatar or '' - if relative_url.startswith('http'): - return relative_url - if storage_domain.endswith('/'): - return storage_domain + relative_url.lstrip('/') - return storage_domain + '/' + relative_url if storage_domain else relative_url - -def _full_remote_avatar(storage_bucket_domain, relative_url): if not relative_url: return '' if relative_url.startswith('http'): return relative_url - if storage_bucket_domain.endswith('/'): - return storage_bucket_domain + relative_url.lstrip('/') - return storage_bucket_domain + '/' + relative_url if storage_bucket_domain else relative_url + return relative_url # ========== GoEasy 订阅 ========== def _subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None): @@ -90,7 +49,7 @@ def _subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None): # ========== 发送群消息(完整参数) ========== def _send_group_message(appkey, secret, group_id, sender_id, sender_name, sender_avatar, - message_text, custom_payload=None, group_name=None, order_id=None, is_cross=0): + message_text, custom_payload=None, group_name=None, order_id=None): if not appkey: appkey = _get_self_goeasy_appkey() if not secret: @@ -108,7 +67,6 @@ def _send_group_message(appkey, secret, group_id, sender_id, sender_name, sender } if order_id: to_data["orderId"] = order_id - to_data["isCross"] = is_cross payload_str = custom_payload.get("text", message_text) if custom_payload else message_text @@ -165,17 +123,8 @@ def establish_order_chat(dingdan_id): logger.error("GoEasy AppKey 未配置") return False - # 判断是否跨平台 - if order.is_cross != 1: - return _handle_local_order(order, dashou_goeasy_id, dashou_nickname, dashou_avatar, appkey, secret) - else: - if order.dispatch_type == 1: - # 我方派单,与普通订单处理一致 - return _handle_local_order(order, dashou_goeasy_id, dashou_nickname, dashou_avatar, appkey, secret) - elif order.dispatch_type == 2: - return _handle_cross_partner_order(order, dashou_goeasy_id, dashou_nickname, dashou_avatar, appkey, secret) - else: - return False + # 本地订单处理 + return _handle_local_order(order, dashou_goeasy_id, dashou_nickname, dashou_avatar, appkey, secret) # ========== 普通订单 / 我方派单 ========== def _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret): @@ -195,16 +144,14 @@ def _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, app init_msg_text = f"订单已接单,内容:{order.jieshao},备注:{order.beizhu},游戏ID:{order.nicheng}" success1 = _send_group_message( appkey, secret, group_id, dashou_goeasy_id, dashou_name, group_avatar, - init_msg_text, None, group_name=group_name, order_id=order.dingdan_id, - is_cross=order.is_cross + init_msg_text, None, group_name=group_name, order_id=order.dingdan_id ) # 2. 【新增】派单方也发一条消息,确保他也能看到群聊 partner_msg = f"订单已确认,请开始服务。" success2 = _send_group_message( appkey, secret, group_id, partner_goeasy_id, partner_name, partner_avatar, - partner_msg, None, group_name=group_name, order_id=order.dingdan_id, - is_cross=order.is_cross + partner_msg, None, group_name=group_name, order_id=order.dingdan_id ) return success1 and success2 @@ -247,128 +194,4 @@ def _get_local_partner_info(order): else: if order.user1_id: return order.user1_id, "用户", _full_local_avatar('') - return None, None, None - -# ========== 跨平台 + 对方派单 ========== -def _handle_cross_partner_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret): - partner_club_id = order.partner_club_id - if not partner_club_id: - logger.warning("缺少 partner_club_id") - return _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret) - - try: - partner_config = ClubConfig.objects.get(club_id=partner_club_id, is_self=0) - except ClubConfig.DoesNotExist: - logger.error(f"未找到对方俱乐部配置: {partner_club_id}") - return _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret) - - partner_order_id = order.partner_order_id - if not partner_order_id: - logger.error("partner_order_id 为空") - return _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret) - - # 推断对方下单方标识 - target_partner_goeasy_id = _get_cross_partner_identity(order, partner_order_id) - if not target_partner_goeasy_id: - logger.error("无法推断对方下单方标识") - return _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret) - - # 对方默认头像 - partner_default_avatar = _full_remote_avatar(partner_config.storage_bucket_domain or '', partner_config.club_avatar or '') or _full_local_avatar('') - - # ---- 1. 在我方建群 ---- - local_group_id = f"group_{order.dingdan_id}" - local_group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"跨平台订单{order.dingdan_id}" - local_group_avatar = _full_local_avatar(order.tupian) if order.tupian else _full_local_avatar('') - proxy_partner_id = f"{partner_order_id}_{target_partner_goeasy_id}" - proxy_partner_name = f"对方用户({partner_club_id})" - # 对方代理头像使用对方默认头像 - proxy_partner_avatar = partner_default_avatar - - if not _subscribe_users_to_group([dashou_goeasy_id, proxy_partner_id], [local_group_id], appkey, secret): - return False - - # 1. 打手发送初始化消息 - init_msg_text = f"跨平台订单已接单,内容:{order.jieshao},备注:{order.beizhu}" - _send_group_message( - appkey, secret, local_group_id, dashou_goeasy_id, dashou_name, local_group_avatar, - init_msg_text, None, group_name=local_group_name, order_id=order.dingdan_id, - is_cross=1 - ) - - # 2. 【新增】对方代理也发一条消息,确保对方能看到群聊 - partner_msg = f"订单已确认,请等待服务。" - _send_group_message( - appkey, secret, local_group_id, proxy_partner_id, proxy_partner_name, proxy_partner_avatar, - partner_msg, None, group_name=local_group_name, order_id=order.dingdan_id, - is_cross=1 - ) - - # ---- 2. 在对方建群 ---- - partner_appkey = partner_config.chat_api_key - partner_secret = partner_config.chat_api_id or '' - if not partner_appkey: - return True # 无对方 key,仅完成我方建群 - - partner_group_id = f"group_{partner_order_id}" - partner_group_name = f"跨平台订单-我方打手已接单" - # 代理我方打手 ID - our_agent_id = f"{order.dingdan_id}_{dashou_goeasy_id}" - our_agent_name = f"[我方]{dashou_name}" - # 对方频道头像用对方俱乐部默认头像 - partner_group_avatar = partner_default_avatar - - if not _subscribe_users_to_group([our_agent_id, target_partner_goeasy_id], [partner_group_id], partner_appkey, partner_secret): - logger.error("向对方频道订阅失败") - return True - - # 我方打手在对方频道发消息 - cross_msg_text = f"我方打手已接单,订单:{order.jieshao},请确认" - _send_group_message( - partner_appkey, partner_secret, partner_group_id, our_agent_id, our_agent_name, partner_group_avatar, - cross_msg_text, None, group_name=partner_group_name, order_id=partner_order_id, - is_cross=1 - ) - - # 【新增】对方用户在对方频道也发一条消息(以对方原始身份) - target_partner_name = f"用户{target_partner_goeasy_id}" # 简单命名,可按需优化 - _send_group_message( - partner_appkey, partner_secret, partner_group_id, target_partner_goeasy_id, target_partner_name, partner_default_avatar, - "订单信息已收到。", None, group_name=partner_group_name, order_id=partner_order_id, - is_cross=1 - ) - - return True - -def _get_cross_partner_identity(order, partner_order_id): - """ - 获取对方下单方的 GoEasy 标识。 - 优先从商家扩展表取 partner_yonghu_id,否则按 partner_order_id 前缀推断。 - """ - # 尝试从商家扩展表取(仅当发单平台为商家且存在 partner_yonghu_id) - if order.fadan_pingtai == 2 and hasattr(order, 'shangjia_kuozhan'): - ext = order.shangjia_kuozhan - if ext and ext.partner_yonghu_id: - # 直接根据订单前缀构造标识 - prefix = partner_order_id[:2].upper() - if prefix == 'SJ': - return f"Sj{ext.partner_yonghu_id}" - elif prefix == 'PT': - return f"Boss{ext.partner_yonghu_id}" - else: - return f"User{ext.partner_yonghu_id}" - - # 后备:根据 partner_order_id 前缀推断 - prefix = partner_order_id[:2].upper() - uid = partner_order_id[2:] if len(partner_order_id) > 2 else '' - if prefix == 'SJ': - return f"Sj{uid}" - elif prefix == 'PT': - return f"Boss{uid}" - else: - return f"User{uid}" - -def _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret): - group_id = f"group_{order.dingdan_id}" - _subscribe_users_to_group([dashou_goeasy_id], [group_id], appkey, secret) - return False \ No newline at end of file + return None, None, None \ No newline at end of file