diff --git a/apps/ecm/migrations/0009_auto_20230323_1740.py b/apps/ecm/migrations/0009_auto_20230323_1740.py new file mode 100644 index 00000000..2311bad2 --- /dev/null +++ b/apps/ecm/migrations/0009_auto_20230323_1740.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.12 on 2023-03-23 09:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('third', '0007_tdevice_mtask_uid'), + ('ecm', '0008_alter_algochannel_always_on'), + ] + + operations = [ + migrations.AlterField( + model_name='algochannel', + name='algo', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ac_algo', to='ecm.eventcate', verbose_name='关联算法'), + ), + migrations.AlterField( + model_name='algochannel', + name='vchannel', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ac_vchannel', to='third.tdevice', verbose_name='视频通道'), + ), + ] diff --git a/apps/rpm/migrations/0008_auto_20230323_1740.py b/apps/rpm/migrations/0008_auto_20230323_1740.py new file mode 100644 index 00000000..5e8d21a1 --- /dev/null +++ b/apps/rpm/migrations/0008_auto_20230323_1740.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.12 on 2023-03-23 09:40 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('rpm', '0007_alter_rcertificate_number'), + ] + + operations = [ + migrations.AddField( + model_name='rpjmember', + name='note', + field=models.CharField(blank=True, help_text='后入厂/提前离厂', max_length=20, null=True, verbose_name='备注'), + ), + migrations.CreateModel( + name='RpjLog', + fields=[ + ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')), + ('operation', models.CharField(help_text='member_come/member_leave', max_length=20, verbose_name='操作类型')), + ('reason', models.CharField(blank=True, max_length=100, null=True, verbose_name='原因')), + ('remployee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rpm.remployee', verbose_name='关联人员')), + ('rpj', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rpm.rpj', verbose_name='关联项目')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/rpm/models.py b/apps/rpm/models.py index 5706cc23..f79b13a5 100644 --- a/apps/rpm/models.py +++ b/apps/rpm/models.py @@ -124,6 +124,7 @@ class Rpjmember(BaseModel): duty = models.CharField('职责', max_length=20, null=True, blank=True) is_manager = models.BooleanField('是否项目负责人', default=False) rcertificates = models.ManyToManyField(Rcertificate, through='rpm.rpjcertificate', blank=True) + note = models.CharField('备注', max_length=20, null=True, blank=True, help_text='后入厂/提前离厂') class Rpjcertificate(BaseModel): @@ -140,3 +141,15 @@ class Rpjcertificate(BaseModel): expiration_date = models.DateField('有效期') review_date = models.DateField('下一次复审日期') file = models.CharField('文件地址', max_length=1000, null=True, blank=True) + + +class RpjLog(BaseModel): + """项目变更日志 + + Args: + BaseModel (_type_): _description_ + """ + rpj = models.ForeignKey(Rpj, verbose_name='关联项目', on_delete=models.CASCADE) + operation = models.CharField('操作类型', max_length=20, help_text='member_come/member_leave') + remployee = models.ForeignKey(Remployee, verbose_name='关联人员', on_delete=models.SET_NULL, null=True, blank=True) + reason = models.CharField('原因', max_length=100, null=True, blank=True) diff --git a/apps/rpm/serializers.py b/apps/rpm/serializers.py index da5a85f9..da2251c0 100644 --- a/apps/rpm/serializers.py +++ b/apps/rpm/serializers.py @@ -14,6 +14,8 @@ from apps.third.dahua import dhClient from apps.third.tapis import dhapis from apps.utils.tools import check_id_number_e, check_phone_e from apps.wf.serializers import TicketSimpleSerializer +from apps.rpm.services import rpj_member_come, rpj_certificate_in +from apps.rpm.models import RpjLog class RpartyCreateUpdateSerializer(CustomModelSerializer): @@ -158,6 +160,11 @@ class RemployeeUpdateSerializer(CustomModelSerializer): dhClient.request(**dhapis['person_img_upload'], file_path_rela=validated_data['photo']) return super().update(instance, validated_data) +class RemployeeSimpleSerializer(CustomModelSerializer): + class Meta: + model = Remployee + fields = ['id', 'name', 'photo', 'id_number'] + class RemployeeSerializer(CustomModelSerializer): rparty_name = serializers.CharField(source='rparty.name', read_only=True) @@ -204,10 +211,10 @@ class RpjmemberCreatesSerializer(CustomModelSerializer): class RpjmemberCreateSerializer(CustomModelSerializer): rcertificates = serializers.PrimaryKeyRelatedField(label='证书ID', many=True, queryset=Rcertificate.objects.all()) - + reason = serializers.CharField(label='后入厂原因', required=False, allow_blank=True) class Meta: model = Rpjmember - fields = ['remployee', 'rpj', 'duty', 'is_manager', 'rcertificates'] + fields = ['remployee', 'rpj', 'duty', 'is_manager', 'rcertificates', 'reason'] def create(self, validated_data): rpj = validated_data['rpj'] @@ -239,7 +246,12 @@ class RpjmemberCreateSerializer(CustomModelSerializer): if ins.is_manager: Rpjmember.objects.exclude(id=ins.id).update(is_manager=False) if rpj.state in [Rpj.RPJ_ENTER, Rpj.RPJ_WORKING]: # 如果是待入厂后添加的 - pass + # 需要执行入厂操作 + ins.note = '后入厂' + ins.save() + rpj_member_come(ins) + rpj_certificate_in(ins) + RpjLog.objects.create(rpj=rpj, operation='member_come', remployee=remployee, create_by=self.context['request'].user, reason=validated_data.get('reason', '')) return ins @@ -272,9 +284,15 @@ class RpjmemberUpdateSerializer(CustomModelSerializer): if ins.is_manager: Rpjmember.objects.exclude(id=ins.id).update(is_manager=False) if rpj.state in [Rpj.RPJ_ENTER, Rpj.RPJ_WORKING]: # 如果是待入厂后更改的 - pass + rpj_certificate_in(ins) return ins +class RpjLogSerializer(CustomModelSerializer): + remployee_ = RemployeeSimpleSerializer(source='remployee', read_only=True) + create_by_ = UserSimpleSerializer(source='create_by', read_only=True) + class Meta: + model = RpjLog + fields = '__all__' class RpjcertificateSerializer(CustomModelSerializer): file_f = MyFilePathField(source='file', read_only=True) @@ -310,4 +328,4 @@ class RpjfileSerializer(CustomModelSerializer): class RpjfileUpdateSerializer(CustomModelSerializer): class Meta: model = Rpjfile - fields = ['files'] + fields = ['files'] \ No newline at end of file diff --git a/apps/rpm/services.py b/apps/rpm/services.py index cdcb6810..ff0c5de3 100644 --- a/apps/rpm/services.py +++ b/apps/rpm/services.py @@ -8,6 +8,10 @@ from apps.wf.models import Ticket, Transition from apps.rpm.models import Remployee, Rfile, Rparty, Rpj, Rpjcertificate, Rpjfile, Rpjmember from django.contrib.auth.hashers import make_password from rest_framework.exceptions import ParseError +from dateutil import tz +from django.utils import timezone +from datetime import datetime, timedelta +from django.db.models import Max, Min def sync_to_rep(ep: Employee): @@ -41,68 +45,142 @@ def bind_rpj(ticket: Ticket, transition: Transition, new_ticket_data: dict): ticket.create_by = rpj.create_by ticket.save() +def rpj_member_leave(i: Rpjmember): + """人员离开项目 + """ + rpj = i.rpj + rep = i.remployee + ep = rep.employee + + # 查找本人除此之外还在的项目 + rpj_qs = Rpj.objects.filter(remployees=rep, state__in = [Rpj.RPJ_WORKING, Rpj.RPJ_ENTER]).exclude(id=rpj.id) + if rpj_qs.exists(): + leave_time__max = rpj_qs.aggregate(Max('leave_time'))['leave_time__max'] + come_time_min = rpj_qs.aggregate(Min('come_time'))['come_time__min'] + rpj_ = rpj_qs.order_by('-create_time').first() + rpj_dept = rpj_.rparty.dept + if ep: + ep.belong_dept = rpj_dept + ep.save() + if ep.user: + ep.user.belong_dept = rpj_dept + ep.user.save() + # 同步至大华人员库并下发人脸 + HrmService.sync_dahua_employee(ep, ep.photo, come_time_min, leave_time__max) + else: + if ep: + ep.job_state = Employee.JOB_OFF + ep.save() + HrmService.sync_dahua_employee(ep, ep.photo, rpj.come_time, timezone.now() + timedelta(hours=8)) + if ep.user: + ep.user.is_active = False + ep.user.save() + +def rpj_member_come(i: Rpjmember): + """人员入厂 + """ + # tzinfo = tz.gettz('Asia/Shanghai') + rep = i.remployee + rpj = i.rpj + rpj_dept = rpj.rparty.dept + post = Post.objects.get(code='remployee') + # 尝试找到人员 + ep = Employee.objects.get_queryset(all=True).filter(id_number=rep.id_number).first() + ep2 = Employee.objects.get_queryset(all=True).filter(phone=rep.phone).first() + if ep: + pass + elif ep2: + ep = ep2 + else: + ep = Employee() + ep.id_number = rep.id_number + + ep.name = rep.name + ep.id_number = rep.id_number + ep.gender = get_info_from_id(rep.id_number).get('gender', '男') + ep.phone = rep.phone + old_photo = ep.photo + ep.photo = rep.photo + ep.job_state = Employee.JOB_ON + ep.type = 'remployee' + ep.belong_dept = rpj.rparty.dept + ep.is_deleted = False + ep.job_state = 10 + ep.save() + # 给相关方人员创建账户 + user_e = ep.user + if user_e: # 如果该人员有账户 + pass + else: + user_e = User.objects.get_queryset(all=True).filter(phone=rep.phone).first() # 看看有没有存在的账户 + if user_e: + user_e.is_active = True + user_e.is_deleted = False + else: + user_e = User() + user_e.username = 'RE_' + ranstr(6) + user_e.password = make_password('0000') + user_e.name = rep.name + user_e.phone = rep.phone + user_e.type = 'remployee' + user_e.belong_dept = rpj_dept + user_e.post = post + user_e.save() + # 账号划给部门 + UserPost.objects.get_or_create(user=user_e, dept=rpj_dept, + defaults={ + 'user': user_e, + 'dept': rpj_dept, + 'post': post + }) + ep.user = user_e + ep.save() + # 回写 + rep.employee = ep + rep.rpj = rpj # 更新当前入厂项目 + rep.save() + + # come_time = rpj.come_time + # leave_time = rpj.leave_time + # dh_face_card_start = ep.third_info.get('dh_face_card_start', None) + # dh_face_card_end = ep.third_info.get('dh_face_card_end', None) + # if dh_face_card_start: + # dh_face_card_start = datetime.strptime(dh_face_card_start, "%Y-%m-%d %H:%M:%S").replace(tzinfo=tzinfo) + # if dh_face_card_start < come_time: + # come_time = dh_face_card_start + # if dh_face_card_end: + # dh_face_card_end = datetime.strptime(dh_face_card_end, "%Y-%m-%d %H:%M:%S").replace(tzinfo=tzinfo) + # if dh_face_card_end > leave_time: + # leave_time = dh_face_card_end + rpj_qs = Rpj.objects.filter(remployees=rep, state__in = [Rpj.RPJ_WORKING, Rpj.RPJ_ENTER])|Rpj.objects.filter(id=rpj.id) + leave_time__max = rpj_qs.aggregate(Max('leave_time'))['leave_time__max'] + come_time_min = rpj_qs.aggregate(Min('come_time'))['come_time__min'] + # 同步至大华人员库并下发人脸 + HrmService.sync_dahua_employee(ep, old_photo, come_time_min, leave_time__max) + + +def rpj_certificate_in(i: Rpjmember): + for i in Rpjcertificate.objects.filter(rpj_member=i): + ct = Certificate.objects.filter(number=i.number, name=i.name).first() + if ct: + pass + else: + ct = Certificate() + ct.employee = i.rpj_member.remployee.employee + ct.name = i.name + ct.number = i.number + ct.type = i.type + ct.issue_date = i.issue_date + ct.expiration_date = i.expiration_date + ct.review_date = i.review_date + ct.file = i.file + ct.save() def rpj_audit_end(ticket): rpj = Rpj.objects.get(ticket=ticket) - rpj_dept = rpj.rparty.dept - post = Post.objects.get(code='remployee') # 更新入厂项目人员库 for i in Rpjmember.objects.filter(rpj=rpj): - rep = i.remployee - # 尝试找到人员 - ep = Employee.objects.get_queryset(all=True).filter(id_number=rep.id_number).first() - ep2 = Employee.objects.get_queryset(all=True).filter(phone=rep.phone).first() - if ep: - pass - elif ep2: - ep = ep2 - else: - ep = Employee() - ep.id_number = rep.id_number - ep.name = rep.name - ep.id_number = rep.id_number - ep.gender = get_info_from_id(rep.id_number).get('gender', '男') - ep.phone = rep.phone - ep.photo = rep.photo - ep.type = 'remployee' - ep.belong_dept = rpj.rparty.dept - ep.is_deleted = False - ep.job_state = 10 - ep.save() - # 给相关方人员创建账户 - user_e = ep.user - if user_e: # 如果该人员有账户 - pass - else: - user_e = User.objects.get_queryset(all=True).filter(phone=rep.phone).first() # 看看有没有存在的账户 - if user_e: - user_e.is_active = True - user_e.is_deleted = False - else: - user_e = User() - user_e.username = 'RE_' + ranstr(6) - user_e.password = make_password('0000') - user_e.name = rep.name - user_e.phone = rep.phone - user_e.type = 'remployee' - user_e.belong_dept = rpj_dept - user_e.post = post - user_e.save() - # 账号划给部门 - UserPost.objects.get_or_create(user=user_e, dept=rpj_dept, - defaults={ - 'user': user_e, - 'dept': rpj_dept, - 'post': post - }) - ep.user = user_e - ep.save() - # 回写 - rep.employee = ep - rep.rpj = rpj # 更新当前入厂项目 - rep.save() - # 同步至大华人员库并下发人脸 - HrmService.sync_dahua_employee(ep, '', rpj.come_time, rpj.leave_time) + rpj_member_come(i) # 更新入厂项目人员证书库 for i in Rpjcertificate.objects.filter(rpj_member__rpj=rpj): ct = Certificate.objects.filter(number=i.number, name=i.name).first() diff --git a/apps/rpm/urls.py b/apps/rpm/urls.py index 601f6a28..e53aed9c 100644 --- a/apps/rpm/urls.py +++ b/apps/rpm/urls.py @@ -1,4 +1,5 @@ -from apps.rpm.views import RcertificateViewSet, RpartyViewSet, RemployeeViewSet, RfileViewSet, RpjViewSet, RpjfileViewSet, RpjmemberViewSet +from apps.rpm.views import (RcertificateViewSet, RpartyViewSet, RemployeeViewSet, RfileViewSet, RpjViewSet, + RpjfileViewSet, RpjmemberViewSet, RpjLogViewSet) from django.urls import path, include from rest_framework.routers import DefaultRouter @@ -12,6 +13,7 @@ router.register('rcertificate', RcertificateViewSet, basename='rcertificate') router.register('rfile', RfileViewSet, basename='rfile') router.register('rpj', RpjViewSet, basename='rpj') router.register('rpj_member', RpjmemberViewSet, basename='rpj_member') +router.register('rpj_log', RpjLogViewSet, basename='rpj_log') router.register('rpj_file', RpjfileViewSet, basename='rpj_file') urlpatterns = [ path(API_BASE_URL, include(router.urls)), diff --git a/apps/rpm/views.py b/apps/rpm/views.py index 5763540c..3b05e084 100644 --- a/apps/rpm/views.py +++ b/apps/rpm/views.py @@ -1,12 +1,12 @@ from apps.rpm.filters import RpjFilterSet -from apps.rpm.models import Rcertificate, Remployee, Rparty, Rfile, Rpjfile, Rpjmember, Rpj +from apps.rpm.models import Rcertificate, Remployee, Rparty, Rfile, Rpjfile, Rpjmember, Rpj, RpjLog from apps.rpm.serializers import (RcertificateCreateUpdateSerializer, RcertificateSerializer, RemployeeCreateSerializer, RemployeeSerializer, RemployeeUpdateSerializer, RpartyAssignSerializer, RpartyCreateUpdateSerializer, RfileListSerializer, RpartySerializer, RpjListSerializer, RpjfileSerializer, - RpjfileUpdateSerializer, RpjmemberCreateSerializer, + RpjfileUpdateSerializer, RpjmemberCreateSerializer, RpjLogSerializer, RpjCreateSerializer, RpjUpdateSerializer, RpjmemberSerializer, RpjmemberUpdateSerializer) -from apps.rpm.services import sync_to_rep +from apps.rpm.services import sync_to_rep, rpj_member_leave from apps.system.models import Dictionary, Post, User, UserPost from apps.system.serializers import UserCreateSerializer from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet @@ -224,10 +224,31 @@ class RpjmemberViewSet(CustomModelViewSet): def destroy(self, request, *args, **kwargs): obj = self.get_object() - if obj.rpj.state != Rpj.RPJ_CREATE: - raise ParseError('项目非创建状态不可删除') + if obj.rpj.state in [Rpj.RPJ_DONE, Rpj.RPJ_CLOSE]: + raise ParseError('成员在该状态不可删除') + else: + pass return super().destroy(request, *args, **kwargs) + @action(methods=['post'], detail=True, perms_map={'post': 'rpj.update'}) + @transaction.atomic + def leave(self, request, pk=None): + """提前离厂 + + 提前离厂 + """ + obj = self.get_object() + rpj_member_leave(obj) + RpjLog.objects.create(rpj=obj.rpj, operation='member_leave', remployee=obj.remployee, create_by=request.user, reason=request.data.get('reason', '')) + return Response() + +class RpjLogViewSet(ListModelMixin, CustomGenericViewSet): + perms_map = {'get': '*'} + queryset = RpjLog.objects.all() + serializer_class = RpjLogSerializer + select_related_fields = ['rpj', 'remployee', 'create_by'] + filterset_fields = ['rpj', 'remployee'] + ordering = ['-create_time'] class RpjfileViewSet(UpdateModelMixin, DestroyModelMixin, ListModelMixin, CustomGenericViewSet): perms_map = {'get': '*', 'put': 'rpj.update', 'delete': 'rpj.update'} diff --git a/server/settings.py b/server/settings.py index e5e319dd..ff11ffd8 100755 --- a/server/settings.py +++ b/server/settings.py @@ -29,7 +29,7 @@ DEBUG = conf.DEBUG ALLOWED_HOSTS = ['*'] SYS_NAME = 'XT_EHS' -SYS_VERSION = '2.00.12' +SYS_VERSION = '2.00.13' PROJECT_NAME = conf.PROJECT_NAME