diff --git a/dingdan/views.py b/dingdan/views.py index c09b7ae..dcb70d4 100644 --- a/dingdan/views.py +++ b/dingdan/views.py @@ -2522,6 +2522,11 @@ class ShangjiaPaifaView(APIView): def post(self, request): logger.info(f"商家派单请求: user_id={request.user.yonghuid}, data={request.data}") + from peizhi.xieyi_service import check_xieyi_or_response + blocked = check_xieyi_or_response(request.user.yonghuid, 'shangjia') + if blocked: + return blocked + try: data = request.data @@ -4137,6 +4142,11 @@ class QiangdanView(APIView): if not dingdan_id: return Response({'code': 400, 'msg': '订单ID不能为空'}, status=400) + from peizhi.xieyi_service import check_xieyi_or_response + blocked = check_xieyi_or_response(request.user.yonghuid, 'dashou') + if blocked: + return blocked + chat_success = False try: with transaction.atomic(): diff --git a/houtai/xieyi_views.py b/houtai/xieyi_views.py index 9b6d587..8882ede 100644 --- a/houtai/xieyi_views.py +++ b/houtai/xieyi_views.py @@ -39,6 +39,7 @@ def _serialize_admin(item): 'content_text': item.content_text or '', 'version': item.version, 'is_active': item.is_active, + 'must_sign': item.must_sign, 'sort_order': item.sort_order, 'images': [ {'id': img.id, 'image_path': img.image_path, 'sort_order': img.sort_order} @@ -104,6 +105,7 @@ class XieyiModifyAPIView(APIView): content_title=request.data.get('content_title', ''), content_text=request.data.get('content_text', ''), is_active=bool(request.data.get('is_active', True)), + must_sign=bool(request.data.get('must_sign', True)), sort_order=int(request.data.get('sort_order', 0)), ) return Response({'code': 0, 'msg': '创建成功', 'data': _serialize_admin(item)}) @@ -137,6 +139,8 @@ class XieyiModifyAPIView(APIView): content_changed = True if 'is_active' in request.data: item.is_active = bool(request.data.get('is_active')) + if 'must_sign' in request.data: + item.must_sign = bool(request.data.get('must_sign')) if 'sort_order' in request.data: item.sort_order = int(request.data.get('sort_order', 0)) if 'bump_version' in request.data and request.data.get('bump_version'): diff --git a/peizhi/migrations/0018_xcx_xieyi_must_sign.py b/peizhi/migrations/0018_xcx_xieyi_must_sign.py new file mode 100644 index 0000000..5e943af --- /dev/null +++ b/peizhi/migrations/0018_xcx_xieyi_must_sign.py @@ -0,0 +1,16 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('peizhi', '0017_xcx_xieyi'), + ] + + operations = [ + migrations.AddField( + model_name='xcxxieyi', + name='must_sign', + field=models.BooleanField(default=True, verbose_name='是否强制签署'), + ), + ] diff --git a/peizhi/models.py b/peizhi/models.py index 56793a1..8a8f16e 100644 --- a/peizhi/models.py +++ b/peizhi/models.py @@ -748,6 +748,7 @@ class XcxXieyi(models.Model): content_text = models.TextField(blank=True, default='', verbose_name='文字内容') version = models.PositiveIntegerField(default=1, verbose_name='版本号') is_active = models.BooleanField(default=True, verbose_name='是否启用') + must_sign = models.BooleanField(default=True, verbose_name='是否强制签署') sort_order = models.IntegerField(default=0, verbose_name='排序') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') diff --git a/peizhi/urls.py b/peizhi/urls.py index 1c0d3ff..32bcde8 100644 --- a/peizhi/urls.py +++ b/peizhi/urls.py @@ -11,7 +11,7 @@ from .xcx_config_views import ( XcxSysPeizhiQueryView, XcxSysPeizhiUpdateView, XcxPageZiyuanQueryView, XcxPageZiyuanUpdateView, XcxPageZiyuanUploadView, ) -from .xieyi_views import XieyiListView, XieyiDetailView, XieyiSignStatusView, XieyiSignView +from .xieyi_views import XieyiListView, XieyiDetailView, XieyiSignStatusView, XieyiSignView, XieyiCheckView urlpatterns = [ path('shangpingonggao/', ShangpinGonggaoView.as_view(), name='商品公告轮播获取'), @@ -55,6 +55,7 @@ urlpatterns = [ path('xcxyzsc', XcxPageZiyuanUploadView.as_view(), name='管理员上传页面资源'), path('xieyihq', XieyiListView.as_view(), name='小程序获取协议列表'), path('xieyixq', XieyiDetailView.as_view(), name='小程序获取协议详情'), + path('xieyijc', XieyiCheckView.as_view(), name='小程序检查协议签署'), path('xieyizt', XieyiSignStatusView.as_view(), name='小程序查询协议签署状态'), path('xieyiqm', XieyiSignView.as_view(), name='小程序签署协议'), ] \ No newline at end of file diff --git a/peizhi/xieyi_service.py b/peizhi/xieyi_service.py new file mode 100644 index 0000000..c6f0b43 --- /dev/null +++ b/peizhi/xieyi_service.py @@ -0,0 +1,56 @@ +"""协议签署校验 — 供接单/派单等接口调用""" +from rest_framework.response import Response + +from peizhi.models import XcxXieyi, XcxXieyiQianshu + +XIEYI_BLOCK_CODE = 4601 + + +def get_unsigned_xieyi_list(yonghuid: str, shenfen: str): + """返回未签署当前版本的必签协议列表""" + if not yonghuid or not shenfen: + return [] + active = XcxXieyi.objects.filter( + shenfen=shenfen, is_active=True, must_sign=True, + ).order_by('sort_order', 'id') + unsigned = [] + for xieyi in active: + signed = XcxXieyiQianshu.objects.filter( + yonghuid=yonghuid, + xieyi_id=xieyi.id, + xieyi_version=xieyi.version, + ).exists() + if not signed: + unsigned.append(xieyi) + return unsigned + + +def serialize_unsigned_brief(xieyi_list): + return [ + { + 'id': x.id, + 'title': x.title, + 'shenfen': x.shenfen, + 'version': x.version, + } + for x in xieyi_list + ] + + +def xieyi_required_response(unsigned_list): + return Response({ + 'code': XIEYI_BLOCK_CODE, + 'msg': '请先阅读并签署用户协议', + 'data': { + 'all_signed': False, + 'unsigned': serialize_unsigned_brief(unsigned_list), + }, + }) + + +def check_xieyi_or_response(yonghuid: str, shenfen: str): + """未签署则返回 Response,已签署返回 None""" + unsigned = get_unsigned_xieyi_list(yonghuid, shenfen) + if unsigned: + return xieyi_required_response(unsigned) + return None diff --git a/peizhi/xieyi_views.py b/peizhi/xieyi_views.py index 409538e..d1aab36 100644 --- a/peizhi/xieyi_views.py +++ b/peizhi/xieyi_views.py @@ -10,6 +10,7 @@ from rest_framework.response import Response from rest_framework.views import APIView from peizhi.models import XcxXieyi, XcxXieyiImage, XcxXieyiQianshu +from peizhi.xieyi_service import get_unsigned_xieyi_list, serialize_unsigned_brief from utils.oss_utils import upload_to_oss, validate_image logger = logging.getLogger(__name__) @@ -42,6 +43,7 @@ def _serialize_xieyi(item, yonghuid=None, include_images=False): 'content_text': item.content_text or '', 'version': item.version, 'is_active': item.is_active, + 'must_sign': item.must_sign, 'sort_order': item.sort_order, 'signed': signed, } @@ -91,6 +93,28 @@ class XieyiDetailView(APIView): }) +class XieyiCheckView(APIView): + """POST /peizhi/xieyijc — 检查必签协议是否已全部签署(后端为准)""" + + permission_classes = [permissions.IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + shenfen = (request.data.get('shenfen') or '').strip() + if shenfen not in VALID_SHENFEN: + return Response({'code': 400, 'msg': '无效的身份参数', 'data': None}) + yonghuid = request.user.yonghuid + unsigned = get_unsigned_xieyi_list(yonghuid, shenfen) + return Response({ + 'code': 0, + 'msg': 'success', + 'data': { + 'all_signed': len(unsigned) == 0, + 'unsigned': serialize_unsigned_brief(unsigned), + }, + }) + + class XieyiSignStatusView(APIView): """POST /peizhi/xieyizt — 是否已签署当前版本""" @@ -176,5 +200,6 @@ class XieyiSignView(APIView): 'id': record.id, 'signature_path': oss_path, 'signed_at': record.signed_at.strftime('%Y-%m-%d %H:%M:%S'), + 'all_signed': len(get_unsigned_xieyi_list(yonghuid, item.shenfen)) == 0, }, }) diff --git a/yonghu/urls.py b/yonghu/urls.py index 5d49da0..08827c9 100644 --- a/yonghu/urls.py +++ b/yonghu/urls.py @@ -82,7 +82,7 @@ urlpatterns = [ # 打手申诉接口 path('dscfss', DashouShensuView.as_view(), name='打手处罚上诉申请'), path('sjcfjlhq', ShangjiaChufaJiluHuoquView.as_view(), name='商家处罚记录获取'), - #path('officialcallback', csrf_exempt(WeixinOfficialCallbackView.as_view()), name='用户关注取关服务号回调'), + path('officialcallback', csrf_exempt(WeixinOfficialCallbackView.as_view()), name='用户关注取关服务号回调'), path('kefujinru', KefuLoginView.as_view(), name='客服登录'), path('kfjrhqzl', KefuStatsView.as_view(), name='客服获取数据'),