From 7dc91b5a86051424213aa0370abb8ee7ccafe052 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 18 Oct 2021 11:00:38 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E5=88=A4=E5=AE=9A=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mtm/migrations/0023_auto_20211018_1057.py | 49 +++++++++++++++++++ hb_server/apps/mtm/models.py | 15 ++++++ hb_server/apps/mtm/serializers.py | 4 +- hb_server/apps/wf/models.py | 18 ++++++- hb_server/apps/wf/services.py | 4 +- hb_server/apps/wf/views.py | 6 +-- 6 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 hb_server/apps/mtm/migrations/0023_auto_20211018_1057.py diff --git a/hb_server/apps/mtm/migrations/0023_auto_20211018_1057.py b/hb_server/apps/mtm/migrations/0023_auto_20211018_1057.py new file mode 100644 index 0000000..9cf0179 --- /dev/null +++ b/hb_server/apps/mtm/migrations/0023_auto_20211018_1057.py @@ -0,0 +1,49 @@ +# Generated by Django 3.2.6 on 2021-10-18 02:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0022_auto_20211014_0944'), + ] + + operations = [ + migrations.AddField( + model_name='recordformfield', + name='high_limit', + field=models.FloatField(blank=True, null=True, verbose_name='上限值'), + ), + migrations.AddField( + model_name='recordformfield', + name='high_rule', + field=models.IntegerField(blank=True, choices=[(1, '小于'), (2, '小于等于')], null=True, verbose_name='上限规则'), + ), + migrations.AddField( + model_name='recordformfield', + name='low_limit', + field=models.FloatField(blank=True, null=True, verbose_name='下限值'), + ), + migrations.AddField( + model_name='recordformfield', + name='low_rule', + field=models.IntegerField(blank=True, choices=[(1, '大于'), (2, '大于等于')], null=True, verbose_name='下限规则'), + ), + migrations.AddField( + model_name='recordformfield', + name='need_judge', + field=models.BooleanField(default=False, verbose_name='需要判定'), + ), + migrations.AddField( + model_name='recordformfield', + name='rule_expression', + field=models.JSONField(default=list, help_text='判定表达式, 格式为[{"expression":"{value} > 3 and {value}<10"}] 其中{}用于填充工单的字段key,运算时会换算成实际的值,符合条件返回true,表达式只支持简单的运算或datetime/time运算.以首次匹配成功的条件为准,所以多个条件不要有冲突', verbose_name='判定表达式'), + ), + migrations.AlterField( + model_name='usedstep', + name='step', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='usedstep', to='mtm.step', verbose_name='子工序'), + ), + ] diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py index 7675957..6ebe98a 100644 --- a/hb_server/apps/mtm/models.py +++ b/hb_server/apps/mtm/models.py @@ -110,6 +110,14 @@ class RecordFormField(CommonAModel): ('selects', '多选下拉'), ('textarea', '文本域'), ) + high_rule_choices = ( + (1, '小于'), + (2, '小于等于'), + ) + low_rule_choices = ( + (1, '大于'), + (2, '大于等于'), + ) form = models.ForeignKey(RecordForm, on_delete=models.CASCADE, verbose_name='关联表格') field_type = models.CharField('类型', max_length=50, choices=field_type_choices) field_key = models.CharField('字段标识', max_length=50, help_text='字段类型请尽量特殊,避免与系统中关键字冲突') @@ -119,6 +127,13 @@ class RecordFormField(CommonAModel): field_choice = models.JSONField('radio、checkbox、select的选项', default=dict, blank=True, null=True, help_text='radio,checkbox,select,multiselect类型可供选择的选项,格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号') sort = models.IntegerField('排序号', default=1) + need_judge = models.BooleanField('需要判定', default=False) + high_limit = models.FloatField('上限值', null=True, blank=True) + high_rule = models.IntegerField('上限规则', choices=high_rule_choices, null=True, blank=True) + low_limit = models.FloatField('下限值', null=True, blank=True) + low_rule = models.IntegerField('下限规则', choices=low_rule_choices, null=True, blank=True) + rule_expression = models.JSONField('判定表达式', default=list, help_text='判定表达式, 格式为[{"expression":"{value} > 3 and {value}<10"}] 其中{}用于填充的字段key,运算时会换算成实际的值,符合条件返回true,表达式只支持简单的运算或datetime/time运算.以首次匹配成功的条件为准,所以多个条件不要有冲突' ) + class Meta: verbose_name = '记录表格字段' verbose_name_plural = verbose_name diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py index 5da68a4..315e464 100644 --- a/hb_server/apps/mtm/serializers.py +++ b/hb_server/apps/mtm/serializers.py @@ -175,7 +175,7 @@ class RecordFormFieldSerializer(serializers.ModelSerializer): class RecordFormFieldCreateSerializer(serializers.ModelSerializer): class Meta: model = RecordFormField - fields = ['form', 'field_type', 'field_key', 'field_name', 'boolean_field_display', 'field_choice', 'sort'] + fields = ['form', 'field_type', 'field_key', 'field_name', 'boolean_field_display', 'field_choice', 'sort', 'need_judge', 'high_limit', 'high_rule', 'low_limit', 'low_rule', 'rule_expression'] def validate(self, data): if RecordFormField.objects.filter(field_key=data['field_key'], form=data['form'], is_deleted=False).exists(): @@ -185,7 +185,7 @@ class RecordFormFieldCreateSerializer(serializers.ModelSerializer): class RecordFormFieldUpdateSerializer(serializers.ModelSerializer): class Meta: model = RecordFormField - fields = ['field_type', 'field_name', 'boolean_field_display', 'field_choice', 'sort'] + fields = ['field_type', 'field_name', 'boolean_field_display', 'field_choice', 'sort', 'need_judge', 'high_limit', 'high_rule', 'low_limit', 'low_rule', 'rule_expression'] class RecordFormFieldSimpleSerializer(serializers.ModelSerializer): class Meta: diff --git a/hb_server/apps/wf/models.py b/hb_server/apps/wf/models.py index 4540eb0..5427ed2 100644 --- a/hb_server/apps/wf/models.py +++ b/hb_server/apps/wf/models.py @@ -207,4 +207,20 @@ class TicketFlow(BaseModel): participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True, related_name='ticketflow_participant') state = models.ForeignKey(State, verbose_name='当前状态', default=0, blank=True, on_delete=models.CASCADE) ticket_data = models.JSONField('工单数据', default=dict, blank=True, help_text='可以用于记录当前表单数据,json格式') - intervene_type = models.IntegerField('干预类型', default=0, help_text='流转类型', choices=Transition.intervene_type_choices) \ No newline at end of file + intervene_type = models.IntegerField('干预类型', default=0, help_text='流转类型', choices=Transition.intervene_type_choices) + + +class WfScript(CommonAModel): + """ + 执行脚本 + """ + usage_choices =( + (1, '获取处理人'), + (2, '执行操作'), + ) + usage = models.IntegerField('脚本用途', default=1, choices=usage_choices) + wait = models.BooleanField('是否等待执行完成', default=True) + name = models.CharField('脚本名称', max_length=100) + workflow = models.ForeignKey(Workflow, verbose_name='关联工作流', null=True, blank=True, on_delete=models.SET_NULL) + content = models.TextField('脚本内容') + diff --git a/hb_server/apps/wf/services.py b/hb_server/apps/wf/services.py index a6ec576..0ddfec3 100644 --- a/hb_server/apps/wf/services.py +++ b/hb_server/apps/wf/services.py @@ -141,8 +141,8 @@ class WfService(object): multi_all_person_dict = {} destination_participant_type, destination_participant = state.participant_type, state.participant if destination_participant_type == State.PARTICIPANT_TYPE_FIELD: - destination_participant = ticket_data.get(destination_participant, None) if destination_participant in ticket_data else Ticket.ticket_data.get(destination_participant, None) - + destination_participant = ticket_data.get(destination_participant, 0) if destination_participant in ticket_data \ + else Ticket.ticket_data.get(destination_participant, 0) elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT:#单部门 destination_participant = list(User.objects.filter(dept=destination_participant).values_list('id', flat=True)) diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py index eab077e..3d3f439 100644 --- a/hb_server/apps/wf/views.py +++ b/hb_server/apps/wf/views.py @@ -136,7 +136,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin next_state = WfService.get_next_state_by_transition_and_ticket_info(ticket=ticket, transition=transition) participant_info = WfService.get_ticket_state_participant_info(state=next_state, ticket=ticket, ticket_data=ticket.ticket_data) destination_participant_type = participant_info.get('destination_participant_type', 0) - destination_participant = participant_info.get('destination_participant', None) + destination_participant = participant_info.get('destination_participant', 0) multi_all_person = participant_info.get('multi_all_person', {}) # 多人需要全部处理情况 sn = WfService.get_ticket_sn(ticket.workflow) # 流水号 if next_state.type == State.STATE_TYPE_END: @@ -197,7 +197,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin if WfService.check_dict_has_all_same_value(multi_all_person): participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, data['ticket_data']) destination_participant_type = participant_info.get('destination_participant_type', 0) - destination_participant = participant_info.get('destination_participant', None) + destination_participant = participant_info.get('destination_participant', 0) multi_all_person = {} else: # 处理人没有没有全部处理完成或者处理动作不一致 @@ -211,7 +211,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin # 当前处理人类型非全部处理 participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, data['ticket_data']) destination_participant_type = participant_info.get('destination_participant_type', 0) - destination_participant = participant_info.get('destination_participant', None) + destination_participant = participant_info.get('destination_participant', 0) multi_all_person = participant_info.get('multi_all_person', {}) # 更新工单信息:基础字段及自定义字段, add_relation字段 需要下个处理人是部门、角色等的情况 From 7509ff0e1582feff089f940bf7a7bb8f0c3d06fa Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 18 Oct 2021 14:18:11 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E4=BA=BA=E8=84=B8=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hb_client/.env.production | 3 +- .../hrm/migrations/0003_employee_face_data.py | 18 ++++ hb_server/apps/hrm/models.py | 5 +- hb_server/apps/hrm/serializers.py | 5 +- hb_server/apps/hrm/urls.py | 3 +- hb_server/apps/hrm/views.py | 96 ++++++++++++++++++- hb_server/apps/system/serializers.py | 2 - hb_server/apps/system/urls.py | 5 +- hb_server/apps/system/views.py | 43 +-------- 9 files changed, 128 insertions(+), 52 deletions(-) create mode 100644 hb_server/apps/hrm/migrations/0003_employee_face_data.py diff --git a/hb_client/.env.production b/hb_client/.env.production index 484e806..b79ed31 100644 --- a/hb_client/.env.production +++ b/hb_client/.env.production @@ -2,5 +2,6 @@ ENV = 'production' # base api -VUE_APP_BASE_API = 'http://47.95.0.242:2222/api' +#VUE_APP_BASE_API = 'http://47.95.0.242:2222/api' +VUE_APP_BASE_API = 'http://127.0.0.1:8000/api' diff --git a/hb_server/apps/hrm/migrations/0003_employee_face_data.py b/hb_server/apps/hrm/migrations/0003_employee_face_data.py new file mode 100644 index 0000000..1823ad2 --- /dev/null +++ b/hb_server/apps/hrm/migrations/0003_employee_face_data.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-10-18 05:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hrm', '0002_auto_20210924_1127'), + ] + + operations = [ + migrations.AddField( + model_name='employee', + name='face_data', + field=models.JSONField(blank=True, null=True, verbose_name='人脸识别数据'), + ), + ] diff --git a/hb_server/apps/hrm/models.py b/hb_server/apps/hrm/models.py index c448e64..1176416 100644 --- a/hb_server/apps/hrm/models.py +++ b/hb_server/apps/hrm/models.py @@ -28,9 +28,12 @@ class Employee(CommonAModel): academic = models.CharField('学历', max_length=50, null=True, blank=True) jobstate = models.IntegerField('在职状态', choices=jobstate_choices, default=1) job = models.ForeignKey(Position, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='岗位') + face_data = models.JSONField('人脸识别数据', null=True, blank=True) class Meta: verbose_name = '员工补充信息' verbose_name_plural = verbose_name def __str__(self): - return self.name \ No newline at end of file + return self.name + + diff --git a/hb_server/apps/hrm/serializers.py b/hb_server/apps/hrm/serializers.py index 96ab4df..7a14400 100644 --- a/hb_server/apps/hrm/serializers.py +++ b/hb_server/apps/hrm/serializers.py @@ -1,6 +1,6 @@ from apps.system.models import User from rest_framework.serializers import ModelSerializer - +from rest_framework import serializers from .models import Employee from apps.system.serializers import UserListSerializer, UserSimpleSerializer from django.db.models.query import Prefetch @@ -20,3 +20,6 @@ class EmployeeSerializer(ModelSerializer): # queryset=User.objects.filter(employee_user__isnull=True)) # ) # return queryset + +class FaceLoginSerializer(serializers.Serializer): + base64 = serializers.CharField() diff --git a/hb_server/apps/hrm/urls.py b/hb_server/apps/hrm/urls.py index bd532d0..293124c 100644 --- a/hb_server/apps/hrm/urls.py +++ b/hb_server/apps/hrm/urls.py @@ -1,12 +1,13 @@ from django.db.models import base from rest_framework import urlpatterns -from apps.hrm.views import EmployeeViewSet +from apps.hrm.views import EmployeeViewSet, FaceLogin from django.urls import path, include from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('employee', EmployeeViewSet, basename='employee') urlpatterns = [ + path('facelogin/', FaceLogin.as_view()), path('', include(router.urls)), ] diff --git a/hb_server/apps/hrm/views.py b/hb_server/apps/hrm/views.py index 843fa96..ffb8df3 100644 --- a/hb_server/apps/hrm/views.py +++ b/hb_server/apps/hrm/views.py @@ -1,9 +1,34 @@ from django.shortcuts import render +from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet, GenericViewSet from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from apps.hrm.models import Employee -from apps.hrm.serializers import EmployeeSerializer +from apps.hrm.serializers import EmployeeSerializer, FaceLoginSerializer +import face_recognition +from django.conf import settings +from django.core.cache import cache +import logging +from rest_framework.generics import CreateAPIView +from rest_framework import status +from rest_framework_simplejwt.tokens import RefreshToken + +from apps.system.models import User +logger = logging.getLogger('log') + + +def load_face_data(username:int, path:str): + """ + 将某用户face_encoding加载进缓存 + """ + face_datas = cache.get_or_set('face_datas', {}, timeout=None) + photo_path = settings.BASE_DIR + path + picture_of_me = face_recognition.load_image_file(photo_path) + my_face_encoding = face_recognition.face_encodings(picture_of_me)[0] + face_datas[username] = my_face_encoding + cache.set('face_datas', face_datas, timeout=None) + return my_face_encoding + # Create your views here. class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMixin, RetrieveModelMixin, GenericViewSet): """ @@ -12,4 +37,71 @@ class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMix perms_map = {'get': '*', 'put': 'employee_update'} queryset = Employee.objects.all() serializer_class = EmployeeSerializer - ordering = ['-pk'] \ No newline at end of file + ordering = ['-pk'] + + def perform_update(self, serializer): + instance = serializer.save(update_by = self.request.user) + try: + photo_path = settings.BASE_DIR + instance.photo + picture_of_me = face_recognition.load_image_file(photo_path) + my_face_encoding = face_recognition.face_encodings(picture_of_me)[0] + instance.face_data = my_face_encoding.tolist() + instance.save() + except: + logger.error('人脸识别出错') + +import uuid +import base64 +import os + +def tran64(s): + missing_padding = len(s) % 4 + if missing_padding != 0: + s = s+'='* (4 - missing_padding) + return s + +class FaceLogin(CreateAPIView): + authentication_classes = [] + permission_classes = [] + serializer_class = FaceLoginSerializer + + + def create(self, request, *args, **kwargs): + """ + 人脸识别登录 + """ + # serializer = FaceLoginSerializer(data=request.data) + # serializer.is_valid(raise_exception=True) + filename = str(uuid.uuid4()) + filepath = settings.BASE_DIR +'/temp/' + filename +'.png' + with open(filepath, 'wb') as f: + data = tran64(request.data.get('base64').replace(' ', '+')) + f.write(base64.urlsafe_b64decode(data)) + # picture_of_me = face_recognition.load_image_file(settings.BASE_DIR +'/temp/me.png') + # my_face_encoding = face_recognition.face_encodings(picture_of_me)[0] + #results = face_recognition.compare_faces([my_face_encoding], unknown_face_encoding, tolerance=0.2) + try: + unknown_picture = face_recognition.load_image_file(filepath) + unknown_face_encoding = face_recognition.face_encodings(unknown_picture)[0] + os.remove(filepath) + except: + logger.error('解码失败') + + # 匹配人脸库 + user_faces = Employee.objects.filter(face_data__isnull=False, user__is_active=True).values('user', 'face_data') + user_l = [] + face_l = [] + for i in user_faces: + user_l.append(i['user']) + face_l.append(i['face_data']) + + results = face_recognition.compare_faces(face_l, unknown_face_encoding, tolerance=0.2) + for index, value in enumerate(results): + if value: + # 识别成功 + refresh = RefreshToken.for_user(User.objects.get(id=user_l[index])) + return Response({ + 'refresh': str(refresh), + 'access': str(refresh.access_token), + }) + return Response('未找到对应用户', status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/hb_server/apps/system/serializers.py b/hb_server/apps/system/serializers.py index a39e88b..10636ea 100644 --- a/hb_server/apps/system/serializers.py +++ b/hb_server/apps/system/serializers.py @@ -199,5 +199,3 @@ class UserCreateSerializer(serializers.ModelSerializer): return phone -class FaceLoginSerializer(serializers.Serializer): - base64 = serializers.CharField() \ No newline at end of file diff --git a/hb_server/apps/system/urls.py b/hb_server/apps/system/urls.py index 55e1420..02a4ecb 100644 --- a/hb_server/apps/system/urls.py +++ b/hb_server/apps/system/urls.py @@ -1,5 +1,5 @@ from django.urls import path, include -from .views import FaceLogin, TaskList, UserViewSet, OrganizationViewSet, PermissionViewSet, RoleViewSet, PositionViewSet, TestView, DictTypeViewSet, DictViewSet, PTaskViewSet +from .views import TaskList, UserViewSet, OrganizationViewSet, PermissionViewSet, RoleViewSet, PositionViewSet, TestView, DictTypeViewSet, DictViewSet, PTaskViewSet from rest_framework import routers @@ -15,6 +15,5 @@ router.register('ptask', PTaskViewSet, basename="ptask") urlpatterns = [ path('', include(router.urls)), path('task/', TaskList.as_view()), - path('test/', TestView.as_view()), - path('facelogin/', FaceLogin.as_view()) + path('test/', TestView.as_view()) ] diff --git a/hb_server/apps/system/views.py b/hb_server/apps/system/views.py index 2ee96a6..65a1320 100644 --- a/hb_server/apps/system/views.py +++ b/hb_server/apps/system/views.py @@ -21,6 +21,7 @@ from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework_simplejwt.tokens import RefreshToken from rest_framework.exceptions import ValidationError, ParseError +from apps.hrm.models import Employee from utils.queryset import get_child_queryset2 from .filters import UserFilter @@ -29,7 +30,7 @@ from .models import (Dict, DictType, File, Organization, Permission, Position, Role, User) from .permission import RbacPermission, get_permission_list from .permission_data import RbacFilterSet -from .serializers import (DictSerializer, DictTypeSerializer, FaceLoginSerializer, FileSerializer, +from .serializers import (DictSerializer, DictTypeSerializer, FileSerializer, OrganizationSerializer, PermissionSerializer, PositionSerializer, RoleSerializer, PTaskSerializer,PTaskCreateUpdateSerializer, UserCreateSerializer, UserListSerializer, @@ -352,43 +353,3 @@ class FileViewSet(CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListM instance.save() - -#import face_recognition -import uuid -import base64 -import os - -def tran64(s): - missing_padding = len(s) % 4 - if missing_padding != 0: - s = s+'='* (4 - missing_padding) - return s - -class FaceLogin(CreateAPIView): - authentication_classes = [] - permission_classes = [] - serializer_class = FaceLoginSerializer - - - def create(self, request, *args, **kwargs): - """ - 人脸识别登录 - """ - # serializer = FaceLoginSerializer(data=request.data) - # serializer.is_valid(raise_exception=True) - filename = str(uuid.uuid4()) - filepath = settings.BASE_DIR +'/temp/' + filename +'.png' - with open(filepath, 'wb') as f: - data = tran64(request.data.get('base64').replace(' ', '+')) - f.write(base64.urlsafe_b64decode(data)) - # picture_of_me = face_recognition.load_image_file(settings.BASE_DIR +'/temp/me.png') - # my_face_encoding = face_recognition.face_encodings(picture_of_me)[0] - # unknown_picture = face_recognition.load_image_file(filepath) - # unknown_face_encoding = face_recognition.face_encodings(unknown_picture)[0] - #results = face_recognition.compare_faces([my_face_encoding], unknown_face_encoding, tolerance=0.2) - os.remove(filepath) - # if results[0] == True: - # return Response('这是曹前明') - # else: - # return Response('这不是曹前明') - From 7b6f5008ce43fc01675db1b2fa2f4da83c53abb1 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 18 Oct 2021 14:46:05 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E4=BA=BA=E8=84=B8=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E7=B2=BE=E5=BA=A6=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hb_server/apps/hrm/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hb_server/apps/hrm/views.py b/hb_server/apps/hrm/views.py index ffb8df3..595285d 100644 --- a/hb_server/apps/hrm/views.py +++ b/hb_server/apps/hrm/views.py @@ -95,13 +95,15 @@ class FaceLogin(CreateAPIView): user_l.append(i['user']) face_l.append(i['face_data']) - results = face_recognition.compare_faces(face_l, unknown_face_encoding, tolerance=0.2) + results = face_recognition.compare_faces(face_l, unknown_face_encoding, tolerance=0.5) for index, value in enumerate(results): if value: # 识别成功 - refresh = RefreshToken.for_user(User.objects.get(id=user_l[index])) + user = User.objects.get(id=user_l[index]) + refresh = RefreshToken.for_user(user) return Response({ 'refresh': str(refresh), 'access': str(refresh.access_token), + 'username':user.username }) return Response('未找到对应用户', status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file From 1d056a775f88284e71b0b796eae13496b9f9adcc Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 19 Oct 2021 09:46:44 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AD=90=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E8=BF=9B=E5=BA=A6=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hb_client/.env.production | 4 +- hb_server/apps/hrm/views.py | 2 +- ...4_alter_recordformfield_rule_expression.py | 18 +++++++++ .../pm/migrations/0005_auto_20211019_0944.py | 38 +++++++++++++++++++ hb_server/apps/pm/models.py | 27 ++++++++++--- hb_server/apps/pm/serializers.py | 7 +++- hb_server/apps/pm/views.py | 17 +++++++-- 7 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 hb_server/apps/mtm/migrations/0024_alter_recordformfield_rule_expression.py create mode 100644 hb_server/apps/pm/migrations/0005_auto_20211019_0944.py diff --git a/hb_client/.env.production b/hb_client/.env.production index b79ed31..d214255 100644 --- a/hb_client/.env.production +++ b/hb_client/.env.production @@ -2,6 +2,6 @@ ENV = 'production' # base api -#VUE_APP_BASE_API = 'http://47.95.0.242:2222/api' -VUE_APP_BASE_API = 'http://127.0.0.1:8000/api' +VUE_APP_BASE_API = 'http://47.95.0.242:2222/api' +#VUE_APP_BASE_API = 'http://127.0.0.1:8000/api' diff --git a/hb_server/apps/hrm/views.py b/hb_server/apps/hrm/views.py index 595285d..151fa06 100644 --- a/hb_server/apps/hrm/views.py +++ b/hb_server/apps/hrm/views.py @@ -85,7 +85,7 @@ class FaceLogin(CreateAPIView): unknown_face_encoding = face_recognition.face_encodings(unknown_picture)[0] os.remove(filepath) except: - logger.error('解码失败') + return Response('头像解码失败', status=status.HTTP_400_BAD_REQUEST) # 匹配人脸库 user_faces = Employee.objects.filter(face_data__isnull=False, user__is_active=True).values('user', 'face_data') diff --git a/hb_server/apps/mtm/migrations/0024_alter_recordformfield_rule_expression.py b/hb_server/apps/mtm/migrations/0024_alter_recordformfield_rule_expression.py new file mode 100644 index 0000000..f31fbc4 --- /dev/null +++ b/hb_server/apps/mtm/migrations/0024_alter_recordformfield_rule_expression.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-10-19 01:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0023_auto_20211018_1057'), + ] + + operations = [ + migrations.AlterField( + model_name='recordformfield', + name='rule_expression', + field=models.JSONField(default=list, help_text='判定表达式, 格式为[{"expression":"{value} > 3 and {value}<10"}] 其中{}用于填充的字段key,运算时会换算成实际的值,符合条件返回true,表达式只支持简单的运算或datetime/time运算.以首次匹配成功的条件为准,所以多个条件不要有冲突', verbose_name='判定表达式'), + ), + ] diff --git a/hb_server/apps/pm/migrations/0005_auto_20211019_0944.py b/hb_server/apps/pm/migrations/0005_auto_20211019_0944.py new file mode 100644 index 0000000..46824e4 --- /dev/null +++ b/hb_server/apps/pm/migrations/0005_auto_20211019_0944.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.6 on 2021-10-19 01:44 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0024_alter_recordformfield_rule_expression'), + ('pm', '0004_subproductionplan_steps'), + ] + + operations = [ + migrations.AddField( + model_name='subproductionplan', + name='state', + field=models.IntegerField(default=0, verbose_name='状态'), + ), + migrations.CreateModel( + name='SubProductionProgress', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('type', models.IntegerField(default=1, verbose_name='物料应用类型')), + ('count', models.IntegerField(verbose_name='应出入数')), + ('count_real', models.IntegerField(verbose_name='实际出入数')), + ('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='关联物料')), + ('subproduction_plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='progress_subplan', to='pm.subproductionplan', verbose_name='关联子生产计划')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py index 5aab5f3..418e4dc 100644 --- a/hb_server/apps/pm/models.py +++ b/hb_server/apps/pm/models.py @@ -31,6 +31,13 @@ class SubProductionPlan(CommonAModel): """ 子生产计划 """ + state_choices=( + (0, '制定中'), + (1, '已下达'), + (2, '已接收'), + (3, '生产中'), + (4, '已完成') + ) production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE) subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE) start_date = models.DateField('计划开工日期') @@ -38,13 +45,21 @@ class SubProductionPlan(CommonAModel): workshop = models.ForeignKey(Organization, verbose_name='生产车间', on_delete=models.CASCADE) process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE) steps = models.JSONField('工艺步骤', default=list) + state = models.IntegerField('状态', default=0) class Meta: verbose_name = '子生产计划' verbose_name_plural = verbose_name -# class ProductionProgress(BaseModel): -# """ -# 子计划生产进度 -# """ -# subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE) -# material = models. \ No newline at end of file +class SubProductionProgress(BaseModel): + """ + 子计划生产进度统计表 + """ + type_choices=( + (1, '输入物料'), + (2, '输出物料') + ) + subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE, related_name='progress_subplan') + material = models.ForeignKey(Material, verbose_name='关联物料', on_delete=models.CASCADE) + type = models.IntegerField('物料应用类型', default=1) + count = models.IntegerField('应出入数') + count_real = models.IntegerField('实际出入数') diff --git a/hb_server/apps/pm/serializers.py b/hb_server/apps/pm/serializers.py index 1950bce..7c79bc0 100644 --- a/hb_server/apps/pm/serializers.py +++ b/hb_server/apps/pm/serializers.py @@ -1,4 +1,4 @@ -from apps.pm.models import ProductionPlan, SubProductionPlan +from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress from rest_framework import serializers from apps.sam.serializers import OrderSerializer from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer @@ -38,3 +38,8 @@ class SubProductionPlanUpdateSerializer(serializers.ModelSerializer): class GenSubPlanSerializer(serializers.Serializer): pass + +class SubProductionProgressSerializer(serializers.ModelSerializer): + material_ = MaterialSimpleSerializer(source='material', read_only=True) + class Meta: + model = SubProductionProgress \ No newline at end of file diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index a9856cd..4805099 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -2,11 +2,11 @@ from rest_framework import serializers from rest_framework.views import APIView from apps.em.models import Equipment from apps.em.serializers import EquipmentSerializer -from apps.mtm.models import InputMaterial, Step, SubProduction, UsedStep +from apps.mtm.models import InputMaterial, OutputMaterial, Step, SubProduction, UsedStep from apps.system.mixins import CreateUpdateModelAMixin -from apps.pm.serializers import GenSubPlanSerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer +from apps.pm.serializers import GenSubPlanSerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer from rest_framework.mixins import CreateModelMixin, ListModelMixin, UpdateModelMixin -from apps.pm.models import ProductionPlan, SubProductionPlan +from apps.pm.models import ProductionPlan, SubProductionProgress, SubProductionPlan from rest_framework.viewsets import GenericViewSet, ModelViewSet from django.shortcuts import render from apps.sam.models import Order @@ -72,10 +72,14 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel for i in subps: steps = Step.objects.filter(usedstep__subproduction=i, usedstep__subproduction__is_deleted=False, usedstep__is_deleted=False, is_deleted=False).values('id', 'number', 'name', 'usedstep__remark') - SubProductionPlan.objects.create(production_plan=production_plan, subproduction=i, + instance = SubProductionPlan.objects.create(production_plan=production_plan, subproduction=i, start_date=production_plan.start_date, end_date=production_plan.end_date, workshop=i.process.workshop, process=i.process, create_by=request.user, steps = list(steps)) + for m in InputMaterial.objects.filter(subproduction=i, is_delete=False).order_by('sort'): + SubProductionProgress.objects.create(material=m.material, type=1, count=m.count, subproduction_plan=instance) + for m in OutputMaterial.objects.filter(subproduction=i, is_delete=False).order_by('sort'): + SubProductionProgress.objects.create(material=m.material, type=2, count=m.count, subproduction_plan=instance) production_plan.is_planed=True production_plan.save() return Response() @@ -98,6 +102,11 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo return SubProductionPlanUpdateSerializer return SubProductionPlanListSerializer + @action(methods=['get'], detail=True, perms_map={'get':'*'}, serializer_class=SubProductionProgressSerializer) + def progress(self, request, pk=None): + obj = self.get_object() + serializer = SubProductionProgressSerializer(instance=obj.progress_subplan, many=True) + return Response(serializer.data) class ResourceViewSet(GenericViewSet): From da1bb4e320552b3ecc047a12e0a769830d170213 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 19 Oct 2021 09:54:45 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E5=AD=90=E8=AE=A1=E5=88=92=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hb_server/apps/pm/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index 4805099..d50ffae 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -76,9 +76,9 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel start_date=production_plan.start_date, end_date=production_plan.end_date, workshop=i.process.workshop, process=i.process, create_by=request.user, steps = list(steps)) - for m in InputMaterial.objects.filter(subproduction=i, is_delete=False).order_by('sort'): + for m in InputMaterial.objects.filter(subproduction=i, is_deleted=False).order_by('sort'): SubProductionProgress.objects.create(material=m.material, type=1, count=m.count, subproduction_plan=instance) - for m in OutputMaterial.objects.filter(subproduction=i, is_delete=False).order_by('sort'): + for m in OutputMaterial.objects.filter(subproduction=i, is_deleted=False).order_by('sort'): SubProductionProgress.objects.create(material=m.material, type=2, count=m.count, subproduction_plan=instance) production_plan.is_planed=True production_plan.save() From ac8bf48a91c6202531e117bdda0672d4a969bffa Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 19 Oct 2021 09:59:55 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E5=AD=90=E8=BF=9B=E5=BA=A6=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E6=8E=A5=E5=8F=A3bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._alter_subproductionprogress_count_real.py | 18 ++++++++++ hb_server/apps/pm/models.py | 2 +- hb_server/apps/wf/migrations/0014_wfscript.py | 36 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 hb_server/apps/pm/migrations/0006_alter_subproductionprogress_count_real.py create mode 100644 hb_server/apps/wf/migrations/0014_wfscript.py diff --git a/hb_server/apps/pm/migrations/0006_alter_subproductionprogress_count_real.py b/hb_server/apps/pm/migrations/0006_alter_subproductionprogress_count_real.py new file mode 100644 index 0000000..a4924dd --- /dev/null +++ b/hb_server/apps/pm/migrations/0006_alter_subproductionprogress_count_real.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-10-19 01:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0005_auto_20211019_0944'), + ] + + operations = [ + migrations.AlterField( + model_name='subproductionprogress', + name='count_real', + field=models.IntegerField(default=0, verbose_name='实际出入数'), + ), + ] diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py index 418e4dc..8e9299d 100644 --- a/hb_server/apps/pm/models.py +++ b/hb_server/apps/pm/models.py @@ -62,4 +62,4 @@ class SubProductionProgress(BaseModel): material = models.ForeignKey(Material, verbose_name='关联物料', on_delete=models.CASCADE) type = models.IntegerField('物料应用类型', default=1) count = models.IntegerField('应出入数') - count_real = models.IntegerField('实际出入数') + count_real = models.IntegerField('实际出入数', default=0) diff --git a/hb_server/apps/wf/migrations/0014_wfscript.py b/hb_server/apps/wf/migrations/0014_wfscript.py new file mode 100644 index 0000000..2b88441 --- /dev/null +++ b/hb_server/apps/wf/migrations/0014_wfscript.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.6 on 2021-10-19 01:58 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('wf', '0013_alter_ticketflow_transition'), + ] + + operations = [ + migrations.CreateModel( + name='WfScript', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('usage', models.IntegerField(choices=[(1, '获取处理人'), (2, '执行操作')], default=1, verbose_name='脚本用途')), + ('wait', models.BooleanField(default=True, verbose_name='是否等待执行完成')), + ('name', models.CharField(max_length=100, verbose_name='脚本名称')), + ('content', models.TextField(verbose_name='脚本内容')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wfscript_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wfscript_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ('workflow', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wf.workflow', verbose_name='关联工作流')), + ], + options={ + 'abstract': False, + }, + ), + ] From 1f21df79e5d78b2c62b1a403dbab3b3837773cdf Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 19 Oct 2021 10:26:29 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E5=AD=90=E8=BF=9B=E5=BA=A6=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hb_server/apps/pm/serializers.py | 3 ++- hb_server/apps/pm/views.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/hb_server/apps/pm/serializers.py b/hb_server/apps/pm/serializers.py index 7c79bc0..e117b8b 100644 --- a/hb_server/apps/pm/serializers.py +++ b/hb_server/apps/pm/serializers.py @@ -42,4 +42,5 @@ class GenSubPlanSerializer(serializers.Serializer): class SubProductionProgressSerializer(serializers.ModelSerializer): material_ = MaterialSimpleSerializer(source='material', read_only=True) class Meta: - model = SubProductionProgress \ No newline at end of file + model = SubProductionProgress + fields = '__all__' \ No newline at end of file diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index d50ffae..f869c2b 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -108,6 +108,7 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo serializer = SubProductionProgressSerializer(instance=obj.progress_subplan, many=True) return Response(serializer.data) + class ResourceViewSet(GenericViewSet): perms_map = {'*': '*'} From 6ecc0f6e57e9f9e0b62a666b366aab2b6e8b57a7 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 19 Oct 2021 11:18:32 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E5=BE=85=E5=8A=9E?= =?UTF-8?q?=E8=81=9A=E5=90=88=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hb_server/apps/pm/views.py | 15 ++++++++++++++- hb_server/apps/wf/views.py | 14 +++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index f869c2b..e8c35cb 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -104,11 +104,24 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo @action(methods=['get'], detail=True, perms_map={'get':'*'}, serializer_class=SubProductionProgressSerializer) def progress(self, request, pk=None): + """ + 生产进度详情 + """ obj = self.get_object() serializer = SubProductionProgressSerializer(instance=obj.progress_subplan, many=True) return Response(serializer.data) - + @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=serializers.Serializer) + def issue(self, request, pk=None): + """ + 下达任务 + """ + obj = self.get_object() + if obj.state == 0: + obj.state = 1 + obj.save() + return Response() + raise APIException('计划状态有误') class ResourceViewSet(GenericViewSet): perms_map = {'*': '*'} diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py index 3d3f439..a86e3f3 100644 --- a/hb_server/apps/wf/views.py +++ b/hb_server/apps/wf/views.py @@ -1,3 +1,4 @@ +from django.db.models import query from apps.system.models import User from apps.wf.filters import TicketFilterSet from django.core.exceptions import AppRegistryNotReady @@ -13,7 +14,7 @@ from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, from apps.wf.services import WfService from rest_framework.exceptions import APIException, PermissionDenied from rest_framework import status - +from django.db.models import Count # Create your views here. class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet): perms_map = {'get': '*', 'post': 'workflow_create', @@ -165,6 +166,17 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin participant=ticket.create_by, transition=transition) return Response(TicketSerializer(instance=ticket).data) + @action(methods=['get'], detail=False, perms_map={'get':'*'}) + def duty_agg(self, request, pk=None): + """ + 工单待办聚合 + """ + ret = {} + queryset = Ticket.objects.filter(participant__contains=request.user.id, is_deleted=False)\ + .exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED]) + ret['total_count'] = queryset.count() + ret['details'] = list(queryset.annotate(count = Count('workflow')).values('workflow', 'workflow__name', 'count')) + return Response(ret) @action(methods=['post'], detail=True, perms_map={'post':'*'}) def handle(self, request, pk=None):