From c9089eeb2712066f5bf54ded8ebd8097bd43470d Mon Sep 17 00:00:00 2001 From: TianyangZhang Date: Mon, 26 Jan 2026 11:00:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20hrm-=E6=96=B0=E5=A2=9E=E4=BA=BA?= =?UTF-8?q?=E5=91=98=E5=B2=97=E4=BD=8D=E8=B0=83=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hrm/migrations/0026_employeetransfer.py | 46 +++++++++++++++++++ .../migrations/0027_alter_leave_leave_type.py | 18 ++++++++ apps/hrm/models.py | 24 +++++++++- apps/hrm/serializers.py | 20 +++++++- apps/hrm/services.py | 20 +++++++- apps/hrm/urls.py | 3 +- apps/hrm/views.py | 22 +++++++-- 7 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 apps/hrm/migrations/0026_employeetransfer.py create mode 100644 apps/hrm/migrations/0027_alter_leave_leave_type.py diff --git a/apps/hrm/migrations/0026_employeetransfer.py b/apps/hrm/migrations/0026_employeetransfer.py new file mode 100644 index 00000000..35364fff --- /dev/null +++ b/apps/hrm/migrations/0026_employeetransfer.py @@ -0,0 +1,46 @@ +# Generated by Django 3.2.12 on 2026-01-23 08:03 + +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 = [ + ('wf', '0006_auto_20251215_1645'), + ('system', '0006_auto_20241213_1249'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('hrm', '0025_leave'), + ] + + operations = [ + migrations.CreateModel( + name='EmployeeTransfer', + 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='删除标记')), + ('is_change', models.BooleanField(default=False, verbose_name='是否跨部门调动')), + ('is_promotion', models.BooleanField(default=False, verbose_name='是否晋升')), + ('transfer_date', models.DateField(verbose_name='调岗日期')), + ('original_slary', models.PositiveIntegerField(blank=True, null=True, verbose_name='原岗位薪资')), + ('new_slary', models.PositiveIntegerField(blank=True, null=True, verbose_name='调岗后薪资')), + ('content', models.TextField(blank=True, null=True, verbose_name='个人工作内容')), + ('reason', models.TextField(blank=True, null=True, verbose_name='调动原因')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employeetransfer_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hrm.employee', verbose_name='员工')), + ('new_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transfer_new_dept', to='system.dept', verbose_name='新部门')), + ('new_post', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transfer_new_post', to='system.post', verbose_name='新岗位')), + ('original_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transfer_original_dept', to='system.dept', verbose_name='原部门')), + ('original_post', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transfer_original_post', to='system.post', verbose_name='原岗位')), + ('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transfer_ticket', to='wf.ticket', verbose_name='关联工单')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employeetransfer_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/hrm/migrations/0027_alter_leave_leave_type.py b/apps/hrm/migrations/0027_alter_leave_leave_type.py new file mode 100644 index 00000000..a2d2cf87 --- /dev/null +++ b/apps/hrm/migrations/0027_alter_leave_leave_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2026-01-23 08:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hrm', '0026_employeetransfer'), + ] + + operations = [ + migrations.AlterField( + model_name='leave', + name='leave_type', + field=models.PositiveSmallIntegerField(choices=[(10, '事假'), (20, '病假'), (30, '婚假'), (40, '丧假'), (50, '公假'), (60, '工伤'), (70, '产假'), (80, '护理假'), (90, '其他')], default=10, verbose_name='请假类型'), + ), + ] diff --git a/apps/hrm/models.py b/apps/hrm/models.py index 29707d9f..842e4d7a 100755 --- a/apps/hrm/models.py +++ b/apps/hrm/models.py @@ -265,9 +265,29 @@ class Leave(CommonADModel): employee = models.ForeignKey(Employee, verbose_name='员工', on_delete=models.CASCADE) start_date = models.DateTimeField('开始日期') end_date = models.DateTimeField('结束日期') - leave_type = models.PositiveSmallIntegerField('请假类型', choices=E_TYPE_CHOISE, null=True, blank=True) + leave_type = models.PositiveSmallIntegerField('请假类型', choices=E_TYPE_CHOISE, default=10) reason = models.TextField('请假事由') hour = models.PositiveSmallIntegerField('请假时长', null=True, blank=True) file = models.TextField('证明', null=True, blank=True) ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单', - on_delete=models.CASCADE, related_name='leave_ticket', null=True, blank=True) \ No newline at end of file + on_delete=models.CASCADE, related_name='leave_ticket', null=True, blank=True) + + +class EmployeeTransfer(CommonADModel): + """ + TN:员工岗位调动 + """ + employee = models.ForeignKey(Employee, verbose_name='员工', on_delete=models.CASCADE) + is_change = models.BooleanField('是否跨部门调动', default=False) + is_promotion = models.BooleanField('是否晋升', default=False) + new_dept = models.ForeignKey('system.Dept', verbose_name='新部门', related_name="transfer_new_dept", on_delete=models.CASCADE, null=True, blank=True) + new_post = models.ForeignKey('system.Post', verbose_name='新岗位', related_name="transfer_new_post", on_delete=models.CASCADE, null=True, blank=True) + original_dept = models.ForeignKey('system.Dept', verbose_name='原部门', related_name="transfer_original_dept", on_delete=models.CASCADE, null=True, blank=True) + original_post = models.ForeignKey('system.Post', verbose_name='原岗位', related_name="transfer_original_post", on_delete=models.CASCADE, null=True, blank=True) + transfer_date = models.DateField('调岗日期') + original_slary = models.PositiveIntegerField('原岗位薪资', null=True, blank=True) + new_slary = models.PositiveIntegerField('调岗后薪资', null=True, blank=True) + content = models.TextField('个人工作内容', null=True, blank=True) + reason = models.TextField('调动原因', null=True, blank=True) + ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单', + on_delete=models.CASCADE, related_name='transfer_ticket', null=True, blank=True) \ No newline at end of file diff --git a/apps/hrm/serializers.py b/apps/hrm/serializers.py index 1ff98ace..bac62b84 100755 --- a/apps/hrm/serializers.py +++ b/apps/hrm/serializers.py @@ -9,7 +9,7 @@ from django.utils import timezone from apps.utils.serializers import CustomModelSerializer from apps.utils.constants import EXCLUDE_FIELDS from apps.hrm.models import (Certificate, ClockRecord, Employee, - NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, EmpPersonInfo, Leave) + NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, EmpPersonInfo, Leave, EmployeeTransfer) from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer from django.db import transaction from django.core.cache import cache @@ -377,3 +377,21 @@ class LeaveSerializer(CustomModelSerializer): model = Leave fields = '__all__' +class TransferSerializer(CustomModelSerializer): + ticket_ = TicketSimpleSerializer(source='ticket', read_only=True) + employee_name = serializers.CharField(source='employee.name', read_only=True) + post_name = serializers.CharField(source="employee.post.name", read_only=True) + belong_dept_name = serializers.CharField(source='employee.belong_dept.name', read_only=True) + class Meta: + model = EmployeeTransfer + fields = '__all__' + + def create(self, validated_data): + is_change = validated_data['is_change'] + if is_change: + if validated_data['new_dept'] == validated_data['original_dept']: + raise ParseError('新旧部门相同,无需调动') + elif validated_data['new_post'] == validated_data['original_post']: + raise ParseError('新旧岗位相同,无需调动') + else: + return super().create(validated_data) \ No newline at end of file diff --git a/apps/hrm/services.py b/apps/hrm/services.py index 3d53fd72..0fe45fa7 100755 --- a/apps/hrm/services.py +++ b/apps/hrm/services.py @@ -11,7 +11,8 @@ from django.core.cache import cache from rest_framework.exceptions import ParseError from apps.system.models import User -from apps.hrm.models import ClockRecord, Employee, Resignation +from apps.hrm.models import ClockRecord, Employee, Resignation, EmployeeTransfer +from apps.system.models import UserPost from apps.third.dahua import dhClient from apps.third.models import TDevice from apps.third.tapis import dhapis @@ -497,4 +498,21 @@ class HrmService: print(f'{ep.name}-办公室打卡权限已删除') +# 人员调岗申请 +def post_transfer(ticket: Ticket, transitons, new_ticket_data:dict): + try: + obj = EmployeeTransfer.objects.get(id=new_ticket_data['t_id']) + except EmployeeTransfer.DoesNotExist: + raise ParseError('调岗申请不存在') + data_save = {k: v for k, v in new_ticket_data.items() if k not in ['t_model', 't_id']} + # 需要删除UserPost中的记录,然后新增一条调到的部门和岗位记录,排序为1 + try: + UserPost.objects.filter(post=obj.original_post, dept=obj.original_dept, user=obj.employee.user).delete() + except UserPost.DoesNotExist: + raise ParseError('原岗位记录不存在') + UserPost.objects.create(post=obj.new_post, dept=obj.new_dept, user=obj.employee.user, sort=1) + # 如果有调薪并有领导申请觉得,那么可以从new_ticket_data 获取调薪金额 + for k, v in data_save.items(): + setattr(obj, k, v) + obj.save() diff --git a/apps/hrm/urls.py b/apps/hrm/urls.py index da6516a2..b7b037ae 100755 --- a/apps/hrm/urls.py +++ b/apps/hrm/urls.py @@ -1,5 +1,5 @@ from apps.hrm.views import (CertificateViewSet, ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet, EmpNeedViewSet, - AttendanceViewSet, ResignationViewSet, EmpJoinViewSet, LeaveViewSet) + AttendanceViewSet, ResignationViewSet, EmpJoinViewSet, LeaveViewSet, TransferViewSet) from django.urls import path, include from rest_framework.routers import DefaultRouter @@ -17,6 +17,7 @@ router.register('resignation', ResignationViewSet, basename='resignation') router.register('empneed', EmpNeedViewSet, basename='empneed') router.register('empjoin', EmpJoinViewSet, basename='empjoin') router.register('leave', LeaveViewSet, basename='leave') +router.register('transfer', TransferViewSet, basename='transfer') urlpatterns = [ path(API_BASE_URL, include(router.urls)), ] diff --git a/apps/hrm/views.py b/apps/hrm/views.py index 4ffbe71b..209b79af 100755 --- a/apps/hrm/views.py +++ b/apps/hrm/views.py @@ -12,7 +12,7 @@ from rest_framework.response import Response from apps.hrm.errors import NO_NEED_LEVEL_REMARK from apps.hrm.filters import (CertificateFilterSet, ClockRecordFilterSet, EmployeeFilterSet, NotWorkRemarkFilterSet) -from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, Leave +from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, Leave, EmployeeTransfer from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer, EmpJoinSerializer, ClockRecordListSerializer, EmployeeCreateUpdateSerializer, EmployeeDetailSerializer, EmployeeImproveSerializer, @@ -20,7 +20,7 @@ from apps.hrm.serializers import (CertificateCreateUpdateSerializer, Certificate EmployeeSerializer, ClockRecordSimpleSerializer, ClockRecordCreateSerializer, NotWorkRemarkListSerializer, CorrectSerializer, AttendanceSerializer, - ResignationSerializer, EmpNeedSerializer, LeaveSerializer) + ResignationSerializer, EmpNeedSerializer, LeaveSerializer, TransferSerializer) from apps.hrm.services import HrmService from apps.third.dahua import dhClient @@ -468,4 +468,20 @@ class LeaveViewSet(TicketMixin, EuModelViewSet): workflow_key = "wf_leave" def gen_other_ticket_data(self, instance): - return {"hour": instance.hour if instance.hour else None} \ No newline at end of file + return {"hour": instance.hour if instance.hour else None} + +class TransferViewSet(TicketMixin, EuModelViewSet): + select_related_fields = [ + 'employee', + 'employee__belong_dept', + 'employee__post', + 'ticket', + ] + queryset = EmployeeTransfer.objects.all() + serializer_class = TransferSerializer + filterset_fields = ['employee__belong_dept'] + search_fields = ["employee__name", "employee__post", "employee__belong_dept"] + workflow_key = "wf_transfer" + + def gen_other_ticket_data(self, instance): + return {"name": instance.employee.name if instance.employee.name else None} \ No newline at end of file