flower_bs 4 месяцев назад
Родитель
Сommit
63dd8be966

+ 50 - 3
bin/views.py

@@ -26,7 +26,7 @@ import copy
 import json
 from collections import defaultdict
 logger = logging.getLogger(__name__)
-
+from operation_log.views import log_operation,log_failure_operation,log_success_operation
 # 库位分配
 # 入库规则函数
 # 逻辑根据批次下的托盘数目来找满足区间范围的库位,按照优先级排序,
@@ -69,10 +69,26 @@ class locationViewSet(viewsets.ModelViewSet):
         to_attr='active_links'  # 新的属性名称
         )
 
-        if self.request.user:
+        if self.request.auth:
             if id is None:
+                log_operation(
+                        request=self.request,
+                        operation_content="查看库位列表",
+                        operation_level="view",
+                        operator=self.request.auth.name if self.request.auth else None,
+                        module_name="库位"
+
+                    )
                 return LocationModel.objects.prefetch_related(prefetch_containers).all()
             else:
+                log_operation(
+                    request=self.request,
+                    operation_content=f"查看库位详情 ID:{id}",
+                    operation_level="view",
+                    operator=self.request.auth.name if self.request.auth else None,
+                    object_id=id,
+                    module_name="库位"
+                )
                 return LocationModel.objects.prefetch_related(prefetch_containers).filter(id=id)
         else:
             return LocationModel.objects.none()
@@ -93,6 +109,13 @@ class locationViewSet(viewsets.ModelViewSet):
         # 处理库位对象
         location_obj = LocationModel.objects.filter(location_code=location_code).first()
         if not location_obj:
+            log_failure_operation(
+                request=self.request,
+                operation_content=f"更新库位失败,库位 {location_code} 不存在",
+                operation_level="update",
+                operator=self.request.auth.name if self.request.auth else None,
+                module_name="库位"
+            )
             logger.info(f"库位 {location_code} 不存在")
             return Response(
                 {'code': '400', 'message': '库位不存在', 'data': None},
@@ -104,6 +127,14 @@ class locationViewSet(viewsets.ModelViewSet):
             serializer = self.get_serializer(qs, data=data)
             serializer.is_valid(raise_exception=True)
             serializer.save()
+            log_success_operation(
+                request=self.request,
+                operation_content=f"更新库位成功,库位 {location_code} 已更新",
+                operation_level="update",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=location_obj.id,
+                module_name="库位"
+            )
             headers = self.get_success_headers(serializer.data)
             self.handle_group_location_status(location_code,location_obj.location_group)
             return Response(serializer.data, status=200, headers=headers)
@@ -232,6 +263,13 @@ class locationViewSet(viewsets.ModelViewSet):
                 "msg": "Success Create",
                 "data": return_data
             }
+        log_operation(
+            request=self.request,
+            operation_content=f"批量获取库位批次状态,层号:{layer}",
+            operation_level="view",
+            operator=self.request.auth.name if self.request.auth else None,
+            module_name="库位"
+        )
         
         return Response(data, status=200)
         
@@ -267,7 +305,7 @@ class locationGroupViewSet(viewsets.ModelViewSet):
                            
     def get_queryset(self):             
         id = self.get_project()
-        if self.request.user:
+        if self.request.auth:
             if id is None:
                 return LocationGroupModel.objects.filter()  
             else:
@@ -294,11 +332,20 @@ class locationGroupViewSet(viewsets.ModelViewSet):
         if group_obj:
             data['id'] = group_obj.id
             logger.info(f"库位组 {group_code} 已存在")
+            
         else:
             logger.info(f"库位组 {group_code} 不存在,创建库位组对象")
             serializer_list = LocationGroupPostSerializer(data=data)
             serializer_list.is_valid(raise_exception=True)
             serializer_list.save()
+            log_success_operation(
+                request=self.request,
+                operation_content=f"创建库位组成功,库位组 {group_code} 已创建",
+                operation_level="new",  
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=serializer_list.data.get('id'),
+                module_name="库位"
+            )
             data['id'] = serializer_list.data.get('id')
         return Response(data, status=status.HTTP_201_CREATED)
 

+ 237 - 24
bound/views.py

@@ -25,6 +25,9 @@ from warehouse.models import ListModel as warehouse
 from staff.models import ListModel as staff
 from rest_framework.permissions import AllowAny
 from rest_framework.views import APIView
+from operation_log.views import log_operation,log_failure_operation,log_success_operation
+from django.db.models import F
+
 
 # 出库需求视图类
 class OutBoundDemandViewSet(viewsets.ModelViewSet):
@@ -406,6 +409,14 @@ class BoundListViewSet(viewsets.ModelViewSet):
             if id is None:
                 return BoundListModel.objects.filter( is_delete=False)
             else:
+                log_operation(
+                    request=self.request,
+                    operation_content=f"查看入库单详情 ID:{id}",
+                    operation_level="view",
+                    operator=self.request.auth.name if self.request.auth else None,
+                    object_id=id,
+                    module_name="入库单"
+                )
                 return BoundListModel.objects.filter( id=id, is_delete=False)
         else:
             return BoundListModel.objects.none()
@@ -447,6 +458,14 @@ class BoundListViewSet(viewsets.ModelViewSet):
         serializer.save()
 
         headers = self.get_success_headers(serializer.data)
+        log_success_operation(
+            request=self.request,
+            operation_content=f"创建入库单 ID:{serializer.data['id']},创建内容:{data}",
+            operation_level="create",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=serializer.data['id'],
+            module_name="入库单"
+        )
         return Response(serializer.data, status=200, headers=headers)
     
     def update(self, request, pk):
@@ -456,6 +475,14 @@ class BoundListViewSet(viewsets.ModelViewSet):
         serializer.is_valid(raise_exception=True)
         serializer.save()
         headers = self.get_success_headers(serializer.data)
+        log_success_operation(
+            request=self.request,
+            operation_content=f"更新入库单 ID:{serializer.data['id']}:{data}",
+            operation_level="update",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=serializer.data['id'],
+            module_name="入库单"
+        )
         return Response(serializer.data, status=200, headers=headers)
 
 
@@ -479,7 +506,14 @@ class BoundListViewSet(viewsets.ModelViewSet):
                 qs.relate_out_bill.save()
         serializer = self.get_serializer(qs, many=False)
         headers = self.get_success_headers(serializer.data)
-        
+        log_success_operation(
+            request=self.request,
+            operation_content=f"删除入库单 ID:{qs.id}",
+            operation_level="delete",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=qs.id,
+            module_name="入库单"
+        )
         return Response(serializer.data, status=200, headers=headers)
 
 # 入库批次类视图
@@ -535,7 +569,7 @@ class BoundBatchViewSet(viewsets.ModelViewSet):
             order_day=str(timezone.now().strftime('-%Y%m'))
             order_month=str(timezone.now().strftime('%Y%m'))
             data['bound_month'] =str(timezone.now().strftime('%Y%m'))
-            print(data['order'])
+            # print(data['order'])
             if data['order'] == 'true':
                 qs_set = BoundBatchModel.objects.filter( goods_code=data['goods_code'], bound_month=order_month,  is_delete=False)
                 print('qs_set是:', len(qs_set))
@@ -548,13 +582,43 @@ class BoundBatchViewSet(viewsets.ModelViewSet):
                     data['bound_number'] = data['goods_code'] + order_day + '001'
             else:
                 data['bound_number'] = data['goods_code'] + '-' + str(data['bound_batch_order'])
-            serializer = self.get_serializer(data=data)
-            serializer.is_valid(raise_exception=True)
-            serializer.save()
-            headers = self.get_success_headers(serializer.data)
-            self.add_batch_log(serializer.data, 0, data['goods_qty'])
-
-            return Response(serializer.data, status=200, headers=headers)
+            batch_obj = BoundBatchModel.objects.filter(bound_number=data['bound_number'], is_delete=False)
+            if batch_obj.exists():
+                # 批次号已存在,追加数据到原批次
+                existing_batch = batch_obj.first()
+                existing_batch.goods_qty += data['goods_qty']
+                existing_batch.goods_total_weight += data['goods_total_weight']
+                existing_batch.update_time = timezone.now()
+                existing_batch.save()
+                log_success_operation(
+                    request=self.request,
+                    operation_content=f"创建入库批次 ID:{existing_batch.id} 成功,追加数据:{data}",
+                    operation_level="create",
+                    operator=self.request.auth.name if self.request.auth else None,
+                    object_id=existing_batch.id,
+                    module_name="入库批次"
+                )
+                self.add_batch_log(existing_batch.__dict__, 0, data['goods_qty'])
+                serializer = self.get_serializer(existing_batch, many=False)
+                headers = self.get_success_headers(serializer.data)
+                return Response(serializer.data, status=200, headers=headers)
+            
+            else:   
+            
+                serializer = self.get_serializer(data=data)
+                serializer.is_valid(raise_exception=True)
+                serializer.save()
+                headers = self.get_success_headers(serializer.data)
+                self.add_batch_log(serializer.data, 0, data['goods_qty'])
+                log_success_operation(
+                    request=self.request,
+                    operation_content=f"创建入库批次 ID:{serializer.data['id']},创建内容:{data}",
+                    operation_level="create",
+                    operator=self.request.auth.name if self.request.auth else None,
+                    object_id=serializer.data['id'],
+                    module_name="入库批次"
+                )
+                return Response(serializer.data, status=200, headers=headers)
         except Exception as e:
             print(e)
             raise APIException({"detail": "{}".format(e)})
@@ -596,17 +660,41 @@ class BoundBatchViewSet(viewsets.ModelViewSet):
         serializer.is_valid(raise_exception=True)
         serializer.save()
         headers = self.get_success_headers(serializer.data)
+        log_success_operation(
+            request=self.request,
+            operation_content=f"更新入库批次 ID:{serializer.data['id']}:{data}",
+            operation_level="update",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=serializer.data['id'],
+            module_name="入库批次"
+        )
         return Response(serializer.data, status=200, headers=headers)
 
     def destroy(self, request, pk):
         qs = self.get_object()
         if qs.openid != self.request.auth.openid:
+            log_failure_operation(
+                request=self.request,
+                operation_content=f"删除入库批次 ID:{qs.id} 失败,非所属用户操作",
+                operation_level="delete",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=qs.id,
+                module_name="入库批次"
+            )
             raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
         else:
             qs.is_delete = True
             qs.save()
             serializer = self.get_serializer(qs, many=False)
             headers = self.get_success_headers(serializer.data)
+            log_success_operation(
+                request=self.request,
+                operation_content=f"删除入库批次 ID:{qs.id}",
+                operation_level="delete",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=qs.id,
+                module_name="入库批次"
+            )
             return Response(serializer.data, status=200, headers=headers)
 
     def batchinout(self, request, *args, **kwargs):
@@ -657,7 +745,14 @@ class BoundBatchViewSet(viewsets.ModelViewSet):
             virtual_container_detail.goods_out_qty = batch_in_qty
             virtual_container_detail.save()
 
-        
+        log_success_operation(
+            request=self.request,
+            operation_content=f"批次进出库 ID:{batch_obj.id} 成功,进出库数目:{batch_in_qty}",
+            operation_level="create",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=batch_obj.id,
+            module_name="入库批次"
+        )
         return Response({"code": 200,"message": "操作成功","data": "批次进出库数目:{batch_in_qty}"}, status=200)
 
    
@@ -711,13 +806,14 @@ class BoundDetailViewSet(viewsets.ModelViewSet):
         data = self.request.data
         data['openid'] = self.request.auth.openid
         data.setdefault('is_delete', False)
-
-
         # 验证并保存数据
         data['detail_code'] = f"DC-{data['bound_list']:02}{data['bound_batch']:02}"
         print(data['detail_code'])
-        if BoundDetailModel.objects.filter(detail_code=data['detail_code'], is_delete=False).exists():
-            raise APIException({"detail": "Data exists"})
+        if BoundDetailModel.objects.filter(bound_list=data['bound_list'], bound_batch=data['bound_batch'], is_delete=False).exists():
+            detail_obj = BoundDetailModel.objects.get(bound_list=data['bound_list'], bound_batch=data['bound_batch'], is_delete=False)
+            serializer = self.get_serializer(detail_obj, many=False)
+            headers = self.get_success_headers(serializer.data)
+            return Response(serializer.data, status=200, headers=headers)
         else:
             serializer = self.get_serializer(data=data)
             serializer.is_valid(raise_exception=True)
@@ -725,6 +821,14 @@ class BoundDetailViewSet(viewsets.ModelViewSet):
 
             # 返回响应
             headers = self.get_success_headers(serializer.data)
+            log_success_operation(
+                request=self.request,
+                operation_content=f"创建入库明细 ID:{serializer.data['id']},创建内容:{data}",
+                operation_level="create",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=serializer.data['id'],
+                module_name="入库明细"
+            )
             return Response(serializer.data, status=200, headers=headers)
     
     def update(self, request, pk):
@@ -734,17 +838,41 @@ class BoundDetailViewSet(viewsets.ModelViewSet):
         serializer.is_valid(raise_exception=True)
         serializer.save()
         headers = self.get_success_headers(serializer.data)
+        log_success_operation(
+            request=self.request,
+            operation_content=f"更新入库明细 ID:{serializer.data['id']}:{data}",
+            operation_level="update",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=serializer.data['id'],
+            module_name="入库明细"
+            )
         return Response(serializer.data, status=200, headers=headers)
 
     def destroy(self, request, pk):
         qs = self.get_object()
         if qs.openid != self.request.auth.openid:
             raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
+            log_failure_operation(
+                request=self.request,
+                operation_content=f"删除入库明细 ID:{qs.id} 失败,非所属用户操作",
+                operation_level="delete",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=qs.id,
+                module_name="入库明细"
+            )
         else:
             qs.is_delete = True
             qs.save()
             serializer = self.get_serializer(qs, many=False)
             headers = self.get_success_headers(serializer.data)
+            log_success_operation(
+                request=self.request,
+                operation_content=f"删除入库明细 ID:{qs.id}",
+                operation_level="delete",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=qs.id,
+                module_name="入库明细"
+            )
             return Response(serializer.data, status=200, headers=headers)
 
 # 出库明细类视图      
@@ -798,9 +926,20 @@ class OutBoundDetailViewSet(viewsets.ModelViewSet):
         data['bound_batch_number'] = OutBatchModel.objects.get(id=data['bound_batch']).batch_number.id
         # 验证并保存数据
         data['detail_code'] = f"DC-{data['bound_list']:02}{data['bound_batch']:02}"
-        print(data['detail_code'])
+        # print(data['detail_code'])
         if OutBoundDetailModel.objects.filter(detail_code=data['detail_code'], is_delete=False).exists():
-            raise APIException({"detail": "Data exists"})
+            # 这里追加数目
+            OutBoundDetailModel.objects.filter(detail_code=data['detail_code'], is_delete=False).update(goods_qty=F('goods_qty')+data['goods_qty'])
+            log_success_operation(
+                request=self.request,
+                operation_content=f"更新出库明细 ID:{data['id']}:{data}",
+                operation_level="update",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=data['id'],
+                module_name="出库明细"
+            )
+            return Response({"detail": "数据存在,数量追加"}, status=200)
+            # raise APIException({"detail": "Data exists"})
         else:
             serializer = self.get_serializer(data=data)
             serializer.is_valid(raise_exception=True)
@@ -808,6 +947,14 @@ class OutBoundDetailViewSet(viewsets.ModelViewSet):
 
             # 返回响应
             headers = self.get_success_headers(serializer.data)
+            log_success_operation(
+                request=self.request,
+                operation_content=f"创建出库明细 ID:{serializer.data['id']},创建内容:{data}",
+                operation_level="create",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=serializer.data['id'],
+                module_name="出库明细"
+            )
             return Response(serializer.data, status=200, headers=headers)
 
     def update(self, request, pk):
@@ -817,17 +964,41 @@ class OutBoundDetailViewSet(viewsets.ModelViewSet):
         serializer.is_valid(raise_exception=True)
         serializer.save()
         headers = self.get_success_headers(serializer.data)
+        log_success_operation(
+            request=self.request,
+            operation_content=f"更新出库明细 ID:{serializer.data['id']}:{data}",
+            operation_level="update",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=serializer.data['id'],
+            module_name="出库明细"
+            )
         return Response(serializer.data, status=200, headers=headers)
 
     def destroy(self, request, pk):
         qs = self.get_object()
         if qs.openid != self.request.auth.openid:
             raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
+            log_failure_operation(
+                request=self.request,
+                operation_content=f"删除出库明细 ID:{qs.id} 失败,非所属用户操作",
+                operation_level="delete",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=qs.id,
+                module_name="出库明细"
+            )
         else:
             qs.is_delete = True
             qs.save()
             serializer = self.get_serializer(qs, many=False)
             headers = self.get_success_headers(serializer.data)
+            log_success_operation(
+                request=self.request,
+                operation_content=f"删除出库明细 ID:{qs.id}",
+                operation_level="delete",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=qs.id,
+                module_name="出库明细"
+            )
             return Response(serializer.data, status=200, headers=headers)       
 
 # 出库批次类视图      
@@ -892,18 +1063,10 @@ class OutBoundBatchViewSet(viewsets.ModelViewSet):
 
         #  data['goods_out_qty'] 是一个字符串或浮点数
         data['goods_out_qty'] = Decimal(str(data['goods_out_qty']))
-
-        # 现在 data['goods_out_qty'] 是 Decimal 类型,可以进行减法操作
         from decimal import Decimal
-
-        # 假设 data['goods_out_qty'] 是一个 float 类型的数值
         data['goods_out_qty'] = Decimal(str(data['goods_out_qty']))
-
-        # 现在所有数值都是 Decimal 类型,可以安全地进行减法运算
         data['goods_qty'] = batch_obj.goods_qty - batch_obj.goods_reserve_qty - data['goods_out_qty']
 
-        # data['goods_qty'] = batch_obj.goods_qty - batch_obj.goods_reserve_qty - data['goods_out_qty']
-
         data['status'] = 0  #现在处于出库申请状态
    
         serializer = self.get_serializer(data=data)
@@ -911,6 +1074,14 @@ class OutBoundBatchViewSet(viewsets.ModelViewSet):
         serializer.save()
         headers = self.get_success_headers(serializer.data)
         self.add_batch_log(serializer.data, 1, data['goods_out_qty'])
+        log_success_operation(
+            request=self.request,
+            operation_content=f"创建出库批次 ID:{serializer.data['id']},创建内容:{data}",
+            operation_level="create",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=serializer.data['id'],
+            module_name="出库批次"
+        )
         
         return Response(serializer.data, status=200, headers=headers)
 
@@ -925,17 +1096,42 @@ class OutBoundBatchViewSet(viewsets.ModelViewSet):
         serializer.save()
         headers = self.get_success_headers(serializer.data)
         self.add_batch_log(serializer.data, 1, data['goods_qty'])
+        log_success_operation(
+            request=self.request,
+            operation_content=f"更新出库批次 ID:{serializer.data['id']}:{data}",
+            operation_level="update",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=serializer.data['id'],
+            module_name="出库批次"
+        )
         return Response(serializer.data, status=200, headers=headers)
 
     def destroy(self, request, pk):
         qs = self.get_object()
         if qs.openid != self.request.auth.openid:            
             raise APIException({"detail": "该出库非您所属,禁止删除,您可以进行编辑"})
+            log_failure_operation(
+                request=self.request,
+                operation_content=f"删除出库批次 ID:{qs.id} 失败,非所属用户操作",
+                operation_level="delete",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=qs.id,
+                module_name="出库批次"
+            )
+
         else:
             qs.is_delete = True
             qs.save()
             serializer = self.get_serializer(qs, many=False)
             headers = self.get_success_headers(serializer.data)
+            log_success_operation(
+                request=self.request,
+                operation_content=f"删除出库批次 ID:{qs.id}",
+                operation_level="delete",
+                operator=self.request.auth.name if self.request.auth else None,
+                object_id=qs.id,
+                module_name="出库批次"
+                )
             return Response(serializer.data, status=200, headers=headers)
 
     def add_batch_log(self, data, log_type, goods_qty):
@@ -965,6 +1161,7 @@ class OutBoundBatchViewSet(viewsets.ModelViewSet):
 
         # 创建日志记录
         BatchOperateLogModel.objects.create(**log_data)
+
         return True 
 
 # 批次操作日志类视图      
@@ -1001,6 +1198,14 @@ class BoundBatchLogViewSet(viewsets.ModelViewSet):
         qs = self.get_object()
         qs.is_delete = True
         qs.save()
+        log_success_operation(
+            request=self.request,
+            operation_content=f"删除批次操作日志 ID:{qs.id}",
+            operation_level="delete",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=qs.id,
+            module_name="批次操作日志"
+        )
         serializer = self.get_serializer(qs, many=False)
         headers = self.get_success_headers(serializer.data)
         return Response(serializer.data, status=200, headers=headers)  
@@ -1046,4 +1251,12 @@ class BatchContainerAPIView(APIView):
                 container_dict[container_id]['goods_qty'] += container_detail.goods_qty-container_detail.goods_out_qty
         container_dict = list(container_dict.values())
         return_data = {'code': 200,'msg': 'Success Create', 'data': container_dict}
+        log_success_operation(
+            request=self.request,
+            operation_content=f"查询批次对应托盘 ID:{batch_id} 列表",
+            operation_level="view",
+            operator=self.request.auth.name if self.request.auth else None,
+            object_id=batch_id,
+            module_name="批次对应托盘"
+        )
         return Response(return_data)

+ 2 - 1
greaterwms/settings.py

@@ -41,7 +41,8 @@ INSTALLED_APPS = [
     'container.apps.ContainerConfig',
     'bin.apps.BinConfig',
     'erp.apps.ErpConfig',
-
+    'operation_log',
+    
     'throttle.apps.ThrottleConfig',
     'rest_framework',
     'django_filters',

+ 0 - 0
operation_log/__init__.py


+ 3 - 0
operation_log/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
operation_log/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class OperationLogConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'operation_log'

+ 36 - 0
operation_log/migrations/0001_initial.py

@@ -0,0 +1,36 @@
+# Generated by Django 4.1.2 on 2025-10-29 13:14
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='OperationLog',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('operation_content', models.TextField(verbose_name='操作内容')),
+                ('operation_level', models.CharField(choices=[('view', '查看'), ('update', '更新'), ('new', '新增'), ('delete', '删除'), ('download', '下载'), ('other', '其他')], max_length=20, verbose_name='操作级别')),
+                ('operation_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='操作时间')),
+                ('ip_address', models.GenericIPAddressField(blank=True, null=True, verbose_name='IP地址')),
+                ('user_agent', models.TextField(blank=True, null=True, verbose_name='用户代理')),
+                ('operator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='操作者')),
+            ],
+            options={
+                'verbose_name': '操作日志',
+                'verbose_name_plural': '操作日志',
+                'db_table': 'operation_log',
+                'ordering': ['-operation_time'],
+            },
+        ),
+    ]

+ 18 - 0
operation_log/migrations/0002_alter_operationlog_operator.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2025-10-29 14:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('operation_log', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='operationlog',
+            name='operator',
+            field=models.TextField(blank=True, null=True, verbose_name='操作者'),
+        ),
+    ]

+ 55 - 0
operation_log/migrations/0003_operationlog_module_name_operationlog_object_id_and_more.py

@@ -0,0 +1,55 @@
+# Generated by Django 4.1.2 on 2025-10-29 14:28
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('operation_log', '0002_alter_operationlog_operator'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='operationlog',
+            name='module_name',
+            field=models.CharField(default='unknown', max_length=100, verbose_name='模块名称'),
+        ),
+        migrations.AddField(
+            model_name='operationlog',
+            name='object_id',
+            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='操作对象ID'),
+        ),
+        migrations.AddField(
+            model_name='operationlog',
+            name='operation_result',
+            field=models.CharField(choices=[('success', '成功'), ('failure', '失败')], default='success', max_length=10, verbose_name='操作结果'),
+        ),
+        migrations.AddField(
+            model_name='operationlog',
+            name='request_method',
+            field=models.CharField(blank=True, max_length=10, null=True, verbose_name='请求方法'),
+        ),
+        migrations.AddField(
+            model_name='operationlog',
+            name='request_path',
+            field=models.CharField(blank=True, max_length=500, null=True, verbose_name='请求路径'),
+        ),
+        migrations.AlterField(
+            model_name='operationlog',
+            name='operation_level',
+            field=models.CharField(choices=[('view', '查看'), ('update', '更新'), ('new', '新增'), ('delete', '删除'), ('download', '下载'), ('login', '登录'), ('logout', '登出'), ('other', '其他')], max_length=20, verbose_name='操作级别'),
+        ),
+        migrations.AddIndex(
+            model_name='operationlog',
+            index=models.Index(fields=['operation_time'], name='operation_l_operati_c9195f_idx'),
+        ),
+        migrations.AddIndex(
+            model_name='operationlog',
+            index=models.Index(fields=['operator', 'operation_time'], name='operation_l_operato_be7d7f_idx'),
+        ),
+        migrations.AddIndex(
+            model_name='operationlog',
+            index=models.Index(fields=['module_name', 'operation_time'], name='operation_l_module__bcec03_idx'),
+        ),
+    ]

+ 0 - 0
operation_log/migrations/__init__.py


+ 63 - 0
operation_log/models.py

@@ -0,0 +1,63 @@
+from django.db import models
+from django.utils import timezone
+
+class OperationLog(models.Model):
+    OPERATION_LEVELS = (
+        ('view', '查看'),
+        ('update', '更新'),
+        ('new', '新增'),
+        ('delete', '删除'),
+        ('download', '下载'),
+        ('login', '登录'),
+        ('logout', '登出'),
+        ('other', '其他'),
+    )
+    
+    OPERATION_RESULTS = (
+        ('success', '成功'),
+        ('failure', '失败'),
+    )
+    
+    # 操作者信息 - 使用TextField存储用户名或标识
+    operator = models.TextField(
+        null=True, 
+        blank=True,
+        verbose_name='操作者'
+    )
+    operation_content = models.TextField(verbose_name='操作内容')
+    operation_level = models.CharField(
+        max_length=20, 
+        choices=OPERATION_LEVELS, 
+        verbose_name='操作级别'
+    )
+    operation_result = models.CharField(
+        max_length=10, 
+        choices=OPERATION_RESULTS, 
+        default='success',
+        verbose_name='操作结果'
+    )
+    operation_time = models.DateTimeField(default=timezone.now, verbose_name='操作时间')
+    
+    # 请求信息
+    ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP地址')
+    user_agent = models.TextField(null=True, blank=True, verbose_name='用户代理')
+    request_method = models.CharField(max_length=10, null=True, blank=True, verbose_name='请求方法')
+    request_path = models.CharField(max_length=500, null=True, blank=True, verbose_name='请求路径')
+    
+    # 业务信息
+    module_name = models.CharField(max_length=100, default='unknown', verbose_name='模块名称')
+    object_id = models.CharField(max_length=100, null=True, blank=True, verbose_name='操作对象ID')
+    
+    class Meta:
+        verbose_name = '操作日志'
+        verbose_name_plural = '操作日志'
+        ordering = ['-operation_time']
+        db_table = 'operation_log'
+        indexes = [
+            models.Index(fields=['operation_time']),
+            models.Index(fields=['operator', 'operation_time']),
+            models.Index(fields=['module_name', 'operation_time']),
+        ]
+    
+    def __str__(self):
+        return f"{self.operator} - {self.operation_content} - {self.operation_time}"

+ 3 - 0
operation_log/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 141 - 0
operation_log/views.py

@@ -0,0 +1,141 @@
+from django.utils import timezone
+from .models import OperationLog
+
+class OperationLogger:
+    @staticmethod
+    def log_operation(request, operation_content, operation_level, operator=None, 
+                     operation_result='success', module_name=None, object_id=None):
+        """
+        记录操作日志
+        
+        Args:
+            request: HttpRequest对象
+            operation_content: 操作内容描述
+            operation_level: 操作级别
+            operator: 操作者标识(用户名等),如果为None则尝试从request中获取
+            operation_result: 操作结果,'success'或'failure'
+            module_name: 模块名称,用于分类
+            object_id: 操作对象ID
+        """
+        try:
+            # 验证必需参数
+            if not request or not operation_content or not operation_level:
+                raise ValueError("缺少必需的参数: request, operation_content, operation_level")
+            
+            # 获取操作者标识
+            operator_identifier = OperationLogger._get_operator_identifier(request, operator)
+            
+            # 获取模块名称(如果没有提供则从请求路径推断)
+            if not module_name:
+                module_name = OperationLogger._infer_module_name(request.path)
+            
+            # 获取客户端信息
+            ip_address = OperationLogger._get_client_ip(request)
+            user_agent = request.META.get('HTTP_USER_AGENT', '')[:500]  # 限制长度
+            
+            # 创建日志记录
+            OperationLog.objects.create(
+                operator=operator_identifier,
+                operation_content=operation_content,
+                operation_level=operation_level,
+                operation_result=operation_result,
+                ip_address=ip_address,
+                user_agent=user_agent,
+                request_method=request.method,
+                request_path=request.path,
+                module_name=module_name,
+                object_id=object_id
+            )
+            
+        except Exception as e:
+            # 日志记录失败不应影响主要业务逻辑,但需要记录错误
+            import logging
+            logger = logging.getLogger(__name__)
+            logger.error(f"操作日志记录失败: {str(e)}", exc_info=True)
+    
+    @staticmethod
+    def _get_operator_identifier(request, operator):
+        """获取操作者标识"""
+        # 如果提供了operator参数,直接使用
+        if operator is not None:
+            return str(operator)
+        
+        # 尝试从request中获取用户信息
+        if hasattr(request, 'user') and request.user.is_authenticated:
+            # 优先使用username,如果没有则使用其他标识
+            if hasattr(request.user, 'username'):
+                return request.user.username
+            elif hasattr(request.user, 'get_username'):
+                return request.user.get_username()
+            else:
+                return f"用户ID:{request.user.id}" if hasattr(request.user, 'id') else "认证用户"
+        
+        # 如果都没有,返回未知用户
+        return "未知用户"
+    
+    @staticmethod
+    def _infer_module_name(path):
+        """从路径推断模块名称"""
+        if not path:
+            return 'unknown'
+        
+        # 移除首尾斜杠并分割路径
+        path_parts = path.strip('/').split('/')
+        if path_parts:
+            # 使用第一个非空路径部分作为模块名
+            for part in path_parts:
+                if part and part not in ('api', 'v1', 'v2'):  # 跳过常见的API前缀
+                    return part
+        return 'unknown'
+    
+    @staticmethod
+    def _get_client_ip(request):
+        """获取客户端IP地址"""
+        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
+        if x_forwarded_for:
+            ip = x_forwarded_for.split(',')[0].strip()
+        else:
+            ip = request.META.get('REMOTE_ADDR')
+        return ip
+
+# 快捷函数
+def log_operation(request, operation_content, operation_level, operator=None, 
+                operation_result='success', module_name=None, object_id=None):
+    """快捷记录操作日志函数"""
+    OperationLogger.log_operation(
+        request=request, 
+        operation_content=operation_content, 
+        operation_level=operation_level, 
+        operator=operator,
+        operation_result=operation_result,
+        module_name=module_name,
+        object_id=object_id
+    )
+
+# 专门用于成功操作的快捷函数
+def log_success_operation(request, operation_content, operation_level, operator=None, 
+                         module_name=None, object_id=None):
+    """记录成功操作的快捷函数"""
+    log_operation(
+        request=request,
+        operation_content=operation_content,
+        operation_level=operation_level,
+        operator=operator,
+        operation_result='success',
+        module_name=module_name,
+        object_id=object_id
+    )
+
+# 专门用于失败操作的快捷函数
+def log_failure_operation(request, operation_content, operation_level, operator=None, 
+                         module_name=None, object_id=None):
+    """记录失败操作的快捷函数"""
+    log_operation(
+        request=request,
+        operation_content=operation_content,
+        operation_level=operation_level,
+        operator=operator,
+        operation_result='failure',
+        module_name=module_name,
+        object_id=object_id
+    )

+ 25 - 17
staff/views.py

@@ -13,6 +13,8 @@ from .files import FileRenderCN, FileRenderEN
 from rest_framework.settings import api_settings
 from rest_framework import permissions
 from staff.models import ListModel as staff
+from userprofile.models import Users
+from django.utils import timezone
 from utils.md5 import Md5
 import random
 from django.contrib.auth.models import User
@@ -143,6 +145,12 @@ class APIViewSet(viewsets.ModelViewSet):
                 username=str(data['staff_name']),
                 password=str(check_code)
             )
+            ip = request.META.get('HTTP_X_FORWARDED_FOR') if request.META.get(
+            'HTTP_X_FORWARDED_FOR') else request.META.get('REMOTE_ADDR')
+            Users.objects.create(user_id=user.id, name=str(data['name']),
+                                                 openid=app_code, appid=app_code,
+                                                 t_code=Md5.md5(str(timezone.now())),
+                                                 developer=1, ip=ip)
             
             serializer = self.get_serializer(data=data)
             serializer.is_valid(raise_exception=True)
@@ -152,23 +160,23 @@ class APIViewSet(viewsets.ModelViewSet):
 
     def update(self, request, pk):
         qs = self.get_object()
-        if qs.openid != self.request.auth.openid:
-            creator = ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
-            raise APIException({"detail": "该用户不是您创建的,不能修改"})
-        else:
-            data = self.request.data
-            
-            # 更新角色
-            role_name = data.get('role')
-            if role_name:
-                role, created = Role.objects.get_or_create(name=role_name)
-                data['role'] = role.id
-            
-            serializer = self.get_serializer(qs, data=data)
-            serializer.is_valid(raise_exception=True)
-            serializer.save()
-            headers = self.get_success_headers(serializer.data)
-            return Response(serializer.data, status=200, headers=headers)
+        # if qs.openid != self.request.auth.openid:
+        #     creator = ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
+        #     raise APIException({"detail": "该用户不是您创建的,不能修改"})
+        # else:
+        data = self.request.data
+        
+        # 更新角色
+        role_name = data.get('role')
+        if role_name:
+            role, created = Role.objects.get_or_create(name=role_name)
+            data['role'] = role.id
+        
+        serializer = self.get_serializer(qs, data=data)
+        serializer.is_valid(raise_exception=True)
+        serializer.save()
+        headers = self.get_success_headers(serializer.data)
+        return Response(serializer.data, status=200, headers=headers)
 
     def partial_update(self, request, pk):
         qs = self.get_object()

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
templates/dist/spa/index.html


BIN
templates/dist/spa/js/app.1c265b70.js.gz


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
templates/dist/spa/js/app.1c265b70.js


BIN
templates/dist/spa/js/app.3d941df9.js.gz


+ 4 - 4
userregister/views.py

@@ -62,16 +62,16 @@ def register(request, *args, **kwargs):
                             return JsonResponse(err_password_not_same)
                         else:
                             transaction_code = Md5.md5(data['name'])
-                            app_code =Md5.md5(data['name'] + '1')
+                   
                             user = User.objects.create_user(username=str(data['name']), password=str(data['password1']))
                             Users.objects.create(user_id=user.id, name=str(data['name']),
-                                                 openid=transaction_code, appid=app_code,
+                                                 openid=transaction_code, appid=transaction_code,
                                                  t_code=Md5.md5(str(timezone.now())),
                                                  developer=1, ip=ip)
                             auth.login(request, user)
                             check_code = random.randint(1000, 9999)
                             staff.objects.create(staff_name=str(data['name']),
-                                                 staff_type='Admin',
+                                                 staff_type='查看员',
                                                  check_code=check_code,
                                                  openid=transaction_code,
                                                  appid=transaction_code,
@@ -111,7 +111,7 @@ def register(request, *args, **kwargs):
                             
                             
                             user_id = staff.objects.filter(openid=transaction_code, staff_name=str(data['name']),
-                                                 staff_type='Admin', check_code=check_code).first().id
+                                                 staff_type='查看员', check_code=check_code).first().id
                             folder = os.path.exists(os.path.join(settings.BASE_DIR, 'media/' + transaction_code))
                             if not folder:
                                 os.makedirs(os.path.join(settings.BASE_DIR, 'media/' + transaction_code))