diff --git a/apps/hrm/migrations/0017_auto_20231117_1700.py b/apps/hrm/migrations/0017_auto_20231117_1700.py new file mode 100644 index 00000000..89d706b9 --- /dev/null +++ b/apps/hrm/migrations/0017_auto_20231117_1700.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.12 on 2023-11-17 09:00 + +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), + ('mtm', '0024_auto_20231116_1416'), + ('hrm', '0016_clockrecord_clock_time'), + ] + + operations = [ + migrations.CreateModel( + name='Attendance', + 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='删除标记')), + ('work_date', models.DateField(verbose_name='工作日期')), + ('work_time_start', models.DateTimeField(verbose_name='工作开始时间')), + ('work_time_end', models.DateTimeField(verbose_name='工作结束时间')), + ('state', models.CharField(choices=[('pending', '待定'), ('normal', '正常'), ('late', '迟到'), ('early_leave', '早退'), ('absent', '未到岗'), ('leave', '请假')], default='pending', help_text="[('pending', '待定'), ('normal', '正常'), ('late', '迟到'), ('early_leave', '早退'), ('absent', '未到岗'), ('leave', '请假')]", max_length=20, verbose_name='状态')), + ('note', models.TextField(default='', verbose_name='备注信息')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='attendance_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hrm.employee', verbose_name='关联人员')), + ('shift', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.shift', verbose_name='班次')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='attendance_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='clockrecord', + name='attendance', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hrm.attendance', verbose_name='关联到岗记录'), + ), + ] diff --git a/apps/hrm/models.py b/apps/hrm/models.py index d34df090..ea3afbf2 100755 --- a/apps/hrm/models.py +++ b/apps/hrm/models.py @@ -82,6 +82,32 @@ class NotWorkRemark(CommonADModel): remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200) +class Attendance(CommonADModel): + """ + 到岗记录 + """ + ATT_STATE_CHOICES = [ + ('pending', '待定'), + ('normal', '正常'), + ('late', '迟到'), + ('early_leave', '早退'), + ('absent', '未到岗'), + ('leave', '请假'), + # 可以根据需要添加更多状态 + ] + + employee = models.ForeignKey( + Employee, verbose_name='关联人员', on_delete=models.CASCADE) + work_date = models.DateField('工作日期') + shift = models.ForeignKey('mtm.shift', verbose_name='班次', + on_delete=models.CASCADE) + work_time_start = models.DateTimeField('工作开始时间') + work_time_end = models.DateTimeField('工作结束时间') + state = models.CharField('状态', max_length=20, + choices=ATT_STATE_CHOICES, default='pending', help_text=str(ATT_STATE_CHOICES)) + note = models.TextField('备注信息', default='') + + class ClockRecord(BaseModel): """ 打卡记录 @@ -116,6 +142,8 @@ class ClockRecord(BaseModel): exception_type = models.PositiveSmallIntegerField( '异常类型', choices=E_TYPE_CHOISE, null=True, blank=True) note = models.CharField('备注', max_length=20, default='') + attendance = models.ForeignKey( + Attendance, on_delete=models.SET_NULL, verbose_name='关联到岗记录', null=True, blank=True) class Certificate(CommonAModel): diff --git a/apps/hrm/serializers.py b/apps/hrm/serializers.py index d42fea24..53facc2c 100755 --- a/apps/hrm/serializers.py +++ b/apps/hrm/serializers.py @@ -8,13 +8,14 @@ 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 +from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer from django.db import transaction from django.core.cache import cache from apps.utils.tools import check_id_number_e, get_info_from_id from rest_framework.exceptions import ParseError from django.conf import settings +import datetime class EmployeeShortSerializer(CustomModelSerializer): @@ -261,3 +262,41 @@ class CertificateSerializer(CustomModelSerializer): class CorrectSerializer(serializers.Serializer): start_time = serializers.DateTimeField() end_time = serializers.DateTimeField() + + +class AttendanceSerializer(CustomModelSerializer): + employee_name = serializers.CharField( + source='employee.name', read_only=True) + post_name = serializers.CharField( + source='employee.post.name', read_only=True) + shift_name = serializers.CharField(source='shift.name', read_only=True) + work_time_start = serializers.DateTimeField(read_only=True) + work_time_end = serializers.DateTimeField(read_only=True) + + class Meta: + model = Attendance + fields = '__all__' + + def create(self, validated_data): + shift = validated_data['shift'] + work_date = validated_data['work_date'] + + start_time_o = shift.start_time_o + end_time_o = shift.end_time_o + if end_time_o >= start_time_o: + validated_data['work_time_start'] = datetime.datetime.combine( + work_date, start_time_o) + validated_data['work_time_end'] = datetime.datetime.combine( + work_date, end_time_o) + else: + validated_data['work_time_start'] = datetime.datetime.combine( + work_date, start_time_o) - datetime.timedelta(days=1) + validated_data['work_time_end'] = datetime.datetime.combine( + work_date, end_time_o) + return super().create(validated_data) + + def update(self, instance, validated_data): + new_data = {} + new_data['note'] = validated_data.get('note', '') + new_data['state'] = validated_data['state'] + return super().update(instance, validated_data) diff --git a/apps/hrm/urls.py b/apps/hrm/urls.py index 667efa39..77e2157e 100755 --- a/apps/hrm/urls.py +++ b/apps/hrm/urls.py @@ -1,4 +1,4 @@ -from apps.hrm.views import CertificateViewSet, ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet +from apps.hrm.views import CertificateViewSet, ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet, AttendanceViewSet from django.urls import path, include from rest_framework.routers import DefaultRouter @@ -8,8 +8,10 @@ HTML_BASE_URL = 'hrm/' router = DefaultRouter() router.register('employee', EmployeeViewSet, basename='employee') router.register('clock_record', ClockRecordViewSet, basename='clock_record') -router.register('not_work_remark', NotWorkRemarkViewSet, basename='not_work_reamrk') +router.register('not_work_remark', NotWorkRemarkViewSet, + basename='not_work_reamrk') router.register('certificate', CertificateViewSet, basename='certificate') +router.register('attendance', AttendanceViewSet, basename='attendance') urlpatterns = [ path(API_BASE_URL, include(router.urls)), ] diff --git a/apps/hrm/views.py b/apps/hrm/views.py index 95ce00e0..b3a5ef81 100755 --- a/apps/hrm/views.py +++ b/apps/hrm/views.py @@ -12,14 +12,14 @@ 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 +from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer, ClockRecordListSerializer, EmployeeCreateUpdateSerializer, EmployeeDetailSerializer, EmployeeImproveSerializer, EmployeeNotWorkRemarkSerializer, EmployeeSerializer, ClockRecordSimpleSerializer, ClockRecordCreateSerializer, - NotWorkRemarkListSerializer, CorrectSerializer) + NotWorkRemarkListSerializer, CorrectSerializer, AttendanceSerializer) from apps.hrm.services import HrmService from apps.third.dahua import dhClient @@ -243,6 +243,20 @@ class EmployeeViewSet(CustomModelViewSet): return Response({'path': export_excel(field_data, data, '人员信息')}) +class AttendanceViewSet(CustomModelViewSet): + """ + list: 到岗记录 + + 到岗记录 + """ + queryset = Attendance.objects.all() + serializer_class = AttendanceSerializer + select_related_fields = ['employee', + 'employee__belong_dept', 'employee__post', 'shift'] + filterset_fields = ['employee', 'employee__belong_dept', 'state'] + ordering = ['-create_time'] + + class ClockRecordViewSet(BulkCreateModelMixin, ListModelMixin, BulkDestroyModelMixin, CustomGenericViewSet): """ 打卡记录