diff --git a/apps/hrm/migrations/0029_auto_20260205_1337.py b/apps/hrm/migrations/0029_auto_20260205_1337.py new file mode 100644 index 00000000..60262dc1 --- /dev/null +++ b/apps/hrm/migrations/0029_auto_20260205_1337.py @@ -0,0 +1,103 @@ +# Generated by Django 3.2.12 on 2026-02-05 05:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hrm', '0028_auto_20260126_1445'), + ] + + operations = [ + migrations.AddField( + model_name='employee', + name='address', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='现住址'), + ), + migrations.AddField( + model_name='employee', + name='birthpart', + field=models.CharField(blank=True, max_length=40, null=True, verbose_name='出生地'), + ), + migrations.AddField( + model_name='employee', + name='birthplace', + field=models.CharField(blank=True, max_length=40, null=True, verbose_name='籍贯'), + ), + migrations.AddField( + model_name='employee', + name='card_type', + field=models.CharField(blank=True, max_length=10, null=True, verbose_name='证件类型'), + ), + migrations.AddField( + model_name='employee', + name='categories', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='员工类别'), + ), + migrations.AddField( + model_name='employee', + name='company', + field=models.CharField(blank=True, max_length=30, null=True, verbose_name='所属公司'), + ), + migrations.AddField( + model_name='employee', + name='country', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='国籍'), + ), + migrations.AddField( + model_name='employee', + name='health', + field=models.CharField(blank=True, max_length=10, null=True, verbose_name='健康状况'), + ), + migrations.AddField( + model_name='employee', + name='join_job', + field=models.DateField(blank=True, null=True, verbose_name='参加工作时间'), + ), + migrations.AddField( + model_name='employee', + name='join_partisan_date', + field=models.DateField(blank=True, null=True, verbose_name='入党时间'), + ), + migrations.AddField( + model_name='employee', + name='join_system', + field=models.DateField(blank=True, null=True, verbose_name='入职系统时间'), + ), + migrations.AddField( + model_name='employee', + name='marriage', + field=models.CharField(blank=True, max_length=5, null=True, verbose_name='婚姻状况'), + ), + migrations.AddField( + model_name='employee', + name='nation', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='民族'), + ), + migrations.AddField( + model_name='employee', + name='office_date', + field=models.DateField(blank=True, null=True, verbose_name='任职时间'), + ), + migrations.AddField( + model_name='employee', + name='partisan', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='政治面貌'), + ), + migrations.AddField( + model_name='employee', + name='position', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='职务'), + ), + migrations.AddField( + model_name='employee', + name='rank', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='职级'), + ), + migrations.AddField( + model_name='employee', + name='record_data', + field=models.DateField(blank=True, null=True, verbose_name='录入日期'), + ), + ] diff --git a/apps/hrm/migrations/0030_probation.py b/apps/hrm/migrations/0030_probation.py new file mode 100644 index 00000000..9b957b7d --- /dev/null +++ b/apps/hrm/migrations/0030_probation.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.12 on 2026-02-09 07:59 + +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 = [ + ('system', '0007_alter_dept_create_by_alter_dept_third_info_and_more'), + ('wf', '0006_auto_20251215_1645'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('hrm', '0029_auto_20260205_1337'), + ] + + operations = [ + migrations.CreateModel( + name='Probation', + 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='删除标记')), + ('trial_date', models.DateField(verbose_name='试用日期')), + ('regular_date', models.DateField(verbose_name='转正日期')), + ('content', models.TextField(blank=True, null=True, verbose_name='个人工作内容')), + ('record_file', models.CharField(blank=True, max_length=100, null=True, verbose_name='应聘人员登记表')), + ('apply_file', models.CharField(blank=True, max_length=100, null=True, verbose_name='员工转正申请表')), + ('ppt_file', models.CharField(blank=True, max_length=100, null=True, verbose_name='PPT 汇报考核')), + ('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='probation_belong_dept', to='system.dept', verbose_name='所属部门')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='probation_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('empperson', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hrm.emppersoninfo', verbose_name='人员信息')), + ('reg_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dept', verbose_name='转正部门')), + ('reg_post', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.post', verbose_name='转正岗位')), + ('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='probation_ticket', to='wf.ticket', verbose_name='关联工单')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='probation_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/hrm/migrations/0031_auto_20260227_0933.py b/apps/hrm/migrations/0031_auto_20260227_0933.py new file mode 100644 index 00000000..a9034749 --- /dev/null +++ b/apps/hrm/migrations/0031_auto_20260227_0933.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.12 on 2026-02-27 01:33 + +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', '0006_auto_20251215_1645'), + ('hrm', '0030_probation'), + ] + + operations = [ + migrations.AddField( + model_name='employee', + name='end_contract_date', + field=models.DateField(blank=True, null=True, verbose_name='合同到期日期'), + ), + migrations.CreateModel( + name='EmpContract', + 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='删除标记')), + ('counts', models.PositiveSmallIntegerField(default=1, verbose_name='合同变更次数')), + ('plan_renewal', models.DateField(blank=True, null=True, verbose_name='应续签')), + ('normal_renewal', models.DateField(blank=True, null=True, verbose_name='正常续签')), + ('change_date', models.IntegerField(blank=True, null=True, verbose_name='续签/变更(年)')), + ('change_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='empcontract_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('employee', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hrm.employee', verbose_name='人员信息')), + ('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contract_ticket', to='wf.ticket', verbose_name='关联工单')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='empcontract_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/hrm/migrations/0032_auto_20260302_1530.py b/apps/hrm/migrations/0032_auto_20260302_1530.py new file mode 100644 index 00000000..c7e7f30c --- /dev/null +++ b/apps/hrm/migrations/0032_auto_20260302_1530.py @@ -0,0 +1,207 @@ +# Generated by Django 3.2.12 on 2026-03-02 07:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hrm', '0031_auto_20260227_0933'), + ] + + operations = [ + migrations.RemoveField( + model_name='employee', + name='birthpart', + ), + migrations.RemoveField( + model_name='employee', + name='card_type', + ), + migrations.RemoveField( + model_name='employee', + name='categories', + ), + migrations.RemoveField( + model_name='employee', + name='company', + ), + migrations.RemoveField( + model_name='employee', + name='country', + ), + migrations.RemoveField( + model_name='employee', + name='end_contract_date', + ), + migrations.RemoveField( + model_name='employee', + name='health', + ), + migrations.RemoveField( + model_name='employee', + name='join_job', + ), + migrations.RemoveField( + model_name='employee', + name='join_system', + ), + migrations.RemoveField( + model_name='employee', + name='rank', + ), + migrations.RemoveField( + model_name='employee', + name='record_data', + ), + migrations.AddField( + model_name='employee', + name='age', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='年龄'), + ), + migrations.AddField( + model_name='employee', + name='band_card', + field=models.CharField(blank=True, max_length=30, null=True, verbose_name='银行卡号'), + ), + migrations.AddField( + model_name='employee', + name='contract_end_date', + field=models.DateField(blank=True, null=True, verbose_name='合同到期日'), + ), + migrations.AddField( + model_name='employee', + name='emergency_contact', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='紧急联系人姓名'), + ), + migrations.AddField( + model_name='employee', + name='emergency_phone', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='紧急联系人电话'), + ), + migrations.AddField( + model_name='employee', + name='full_edu', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='全日制最高学历'), + ), + migrations.AddField( + model_name='employee', + name='full_edu_major', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='全日制最高学历专业'), + ), + migrations.AddField( + model_name='employee', + name='full_edu_school', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='全日制最高学历学校名称'), + ), + migrations.AddField( + model_name='employee', + name='full_edu_time', + field=models.DateField(blank=True, null=True, verbose_name='全日制最高学历毕业时间'), + ), + migrations.AddField( + model_name='employee', + name='honor', + field=models.TextField(blank=True, null=True, verbose_name='所获荣誉'), + ), + migrations.AddField( + model_name='employee', + name='hukou_address', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='户籍地址'), + ), + migrations.AddField( + model_name='employee', + name='hukou_type', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='户口性质'), + ), + migrations.AddField( + model_name='employee', + name='part_edu', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='非全日制最高学历'), + ), + migrations.AddField( + model_name='employee', + name='part_edu_major', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='非全日制最高学历专业'), + ), + migrations.AddField( + model_name='employee', + name='part_edu_school', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='非全日制最高学历学校名称'), + ), + migrations.AddField( + model_name='employee', + name='part_edu_time', + field=models.DateField(blank=True, null=True, verbose_name='非全日制最高学历毕业时间'), + ), + migrations.AddField( + model_name='employee', + name='regular_date', + field=models.DateField(blank=True, null=True, verbose_name='转正日期'), + ), + migrations.AddField( + model_name='employee', + name='skill_rank', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='职业技能等级'), + ), + migrations.AddField( + model_name='employee', + name='skill_rank_date', + field=models.DateField(blank=True, null=True, verbose_name='获得技能等级证书日期'), + ), + migrations.AddField( + model_name='employee', + name='work_years', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='工龄'), + ), + migrations.AddField( + model_name='employee', + name='workshop', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='车间'), + ), + migrations.AddField( + model_name='employee', + name='zhuanzhi', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='枣庄市职称'), + ), + migrations.AddField( + model_name='employee', + name='zhuanzhi_date', + field=models.DateField(blank=True, null=True, verbose_name='获得枣庄市职称日期'), + ), + migrations.AddField( + model_name='employee', + name='zhuanzhi_img_1', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='职称证书图片1'), + ), + migrations.AddField( + model_name='employee', + name='zhuanzhi_img_2', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='职称证书图片2'), + ), + migrations.AddField( + model_name='employee', + name='zyjt_zhuanzhi', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='总院/集团职称'), + ), + migrations.AddField( + model_name='employee', + name='zyjt_zhuanzhi_date', + field=models.DateField(blank=True, null=True, verbose_name='获得职称日期'), + ), + migrations.AddField( + model_name='employee', + name='zyjt_zhuanzhi_img_1', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='总院/集团职称证书图片1'), + ), + migrations.AlterField( + model_name='employee', + name='address', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='现住地址'), + ), + migrations.AlterField( + model_name='employee', + name='office_date', + field=models.DateField(blank=True, null=True, verbose_name='职务聘任日期'), + ), + ] diff --git a/apps/hrm/migrations/0033_auto_20260303_0956.py b/apps/hrm/migrations/0033_auto_20260303_0956.py new file mode 100644 index 00000000..acbaa4cd --- /dev/null +++ b/apps/hrm/migrations/0033_auto_20260303_0956.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.12 on 2026-03-03 01:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hrm', '0032_auto_20260302_1530'), + ] + + operations = [ + migrations.AddField( + model_name='employee', + name='first_social_security_date', + field=models.DateField(blank=True, null=True, verbose_name='首次缴纳社保日期'), + ), + migrations.AddField( + model_name='employee', + name='is_veteran', + field=models.BooleanField(default=False, verbose_name='是否为退役军人'), + ), + ] diff --git a/apps/hrm/migrations/0034_auto_20260303_1107.py b/apps/hrm/migrations/0034_auto_20260303_1107.py new file mode 100644 index 00000000..fd44833a --- /dev/null +++ b/apps/hrm/migrations/0034_auto_20260303_1107.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.12 on 2026-03-03 03:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hrm', '0033_auto_20260303_0956'), + ] + + operations = [ + migrations.AlterField( + model_name='employee', + name='full_edu_time', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='全日制最高学历毕业时间'), + ), + migrations.AlterField( + model_name='employee', + name='part_edu_time', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='非全日制最高学历毕业时间'), + ), + ] diff --git a/apps/hrm/migrations/0035_rename_band_card_employee_bank_card.py b/apps/hrm/migrations/0035_rename_band_card_employee_bank_card.py new file mode 100644 index 00000000..05a0acdf --- /dev/null +++ b/apps/hrm/migrations/0035_rename_band_card_employee_bank_card.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2026-03-03 03:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('hrm', '0034_auto_20260303_1107'), + ] + + operations = [ + migrations.RenameField( + model_name='employee', + old_name='band_card', + new_name='bank_card', + ), + ] diff --git a/apps/hrm/models.py b/apps/hrm/models.py index 10f95b47..8ec6dc1b 100755 --- a/apps/hrm/models.py +++ b/apps/hrm/models.py @@ -25,8 +25,7 @@ class Employee(CommonBModel): ('visitor', '访客'), ('driver', '司机') ) - type = models.CharField('人员类型', default='employee', - max_length=10, choices=PEOPLE_TYPE_CHOICES) + user = models.OneToOneField(User, verbose_name='系统账号', related_name='employee', @@ -36,11 +35,8 @@ class Employee(CommonBModel): email = models.EmailField('邮箱号', null=True, blank=True) number = models.CharField('人员编号', max_length=50, null=True, blank=True) photo = models.CharField('证件照', max_length=1000, null=True, blank=True) - id_number = models.CharField( - '身份证号', max_length=100, null=True, blank=True, unique=True) - gender = models.CharField('性别', max_length=10, default='男') signature = models.CharField('签名图片', max_length=200, null=True, blank=True) - birthday = models.DateField('出生年月日', null=True, blank=True) + qualification = models.CharField( '学历', max_length=50, null=True, blank=True) job_state = models.IntegerField( @@ -56,8 +52,52 @@ class Employee(CommonBModel): on_delete=models.SET_NULL, null=True, blank=True) face_data = models.JSONField( '人脸数据', null=True, blank=True) # 存储的是字典(模型名:人脸数据) - start_date = models.DateField('入职日期', null=True, blank=True) end_date = models.DateField('离职日期', null=True, blank=True) + workshop = models.CharField('车间', max_length=20, null=True, blank=True) + position = models.CharField('职务', max_length=20, null=True, blank=True) + office_date = models.DateField('职务聘任日期', null=True, blank=True) + type = models.CharField('人员类型', default='employee', + max_length=10, choices=PEOPLE_TYPE_CHOICES) + gender = models.CharField('性别', max_length=10, default='男') + id_number = models.CharField( + '身份证号', max_length=100, null=True, blank=True, unique=True) + bank_card = models.CharField('银行卡号', max_length=30, null=True, blank=True) + start_date = models.DateField('入职日期', null=True, blank=True) + contract_end_date = models.DateField('合同到期日', null=True, blank=True) + regular_date = models.DateField('转正日期', null=True, blank=True) + birthday = models.DateField('出生年月日', null=True, blank=True) + age = models.PositiveIntegerField('年龄', null=True, blank=True) + work_years = models.PositiveIntegerField('工龄', null=True, blank=True) + partisan = models.CharField('政治面貌', max_length=20, null=True, blank=True) + join_partisan_date = models.DateField('入党时间', null=True, blank=True) + nation = models.CharField('民族', max_length=20, null=True, blank=True) + marriage = models.CharField('婚姻状况', max_length=5, null=True, blank=True) + hukou_type = models.CharField('户口性质', max_length=20, null=True, blank=True) + birthplace = models.CharField('籍贯', max_length=40, null=True, blank=True) + hukou_address = models.CharField('户籍地址', max_length=100, null=True, blank=True) + address = models.CharField('现住地址', max_length=100, null=True, blank=True) + zhuanzhi = models.CharField('枣庄市职称', max_length=50, null=True, blank=True) + zhuanzhi_img_1 = models.CharField('职称证书图片1', max_length=100, null=True, blank=True) + zhuanzhi_img_2 = models.CharField('职称证书图片2', max_length=100, null=True, blank=True) + zhuanzhi_date = models.DateField('获得枣庄市职称日期', null=True, blank=True) + zyjt_zhuanzhi = models.CharField('总院/集团职称', max_length=50, null=True, blank=True) + zyjt_zhuanzhi_img_1 = models.CharField('总院/集团职称证书图片1', max_length=100, null=True, blank=True) + zyjt_zhuanzhi_date = models.DateField('获得职称日期', null=True, blank=True) + skill_rank = models.CharField('职业技能等级', max_length=50, null=True, blank=True) + skill_rank_date = models.DateField('获得技能等级证书日期', null=True, blank=True) + full_edu = models.CharField('全日制最高学历', max_length=50, null=True, blank=True) + full_edu_school = models.CharField('全日制最高学历学校名称', max_length=100, null=True, blank=True) + full_edu_major = models.CharField('全日制最高学历专业', max_length=100, null=True, blank=True) + full_edu_time = models.CharField('全日制最高学历毕业时间', max_length=50, null=True, blank=True) + part_edu = models.CharField('非全日制最高学历', max_length=50, null=True, blank=True) + part_edu_school = models.CharField('非全日制最高学历学校名称', max_length=100, null=True, blank=True) + part_edu_major = models.CharField('非全日制最高学历专业', max_length=100, null=True, blank=True) + part_edu_time = models.CharField('非全日制最高学历毕业时间', max_length=50, null=True, blank=True) + emergency_contact = models.CharField('紧急联系人姓名', max_length=20, null=True, blank=True) + emergency_phone = models.CharField('紧急联系人电话', max_length=20, null=True, blank=True) + honor = models.TextField('所获荣誉', null=True, blank=True) + first_social_security_date = models.DateField('首次缴纳社保日期', null=True, blank=True) + is_veteran = models.BooleanField('是否为退役军人', default=False) class Meta: verbose_name = '员工补充信息' @@ -290,4 +330,38 @@ class EmployeeTransfer(CommonADModel): 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 + on_delete=models.CASCADE, related_name='transfer_ticket', null=True, blank=True) + + +class Probation(CommonBModel): + """ + TN:员工转正 + """ + empperson = models.OneToOneField(EmpPersonInfo, verbose_name='人员信息', on_delete=models.SET_NULL, null=True, blank=True) + ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单', + on_delete=models.CASCADE, related_name='probation_ticket', null=True, blank=True) + reg_dept = models.ForeignKey('system.Dept', verbose_name='转正部门', on_delete=models.SET_NULL, null=True, blank=True) + reg_post = models.ForeignKey('system.Post', verbose_name='转正岗位', on_delete=models.SET_NULL, null=True, blank=True) + trial_date = models.DateField('试用日期') + regular_date = models.DateField('转正日期') + content = models.TextField('个人工作内容', null=True, blank=True) + record_file = models.CharField('应聘人员登记表', null=True, blank=True, max_length=100) + apply_file = models.CharField('员工转正申请表', null=True, blank=True, max_length=100) + ppt_file = models.CharField('PPT 汇报考核', null=True, blank=True, max_length=100) + + +class EmpContract(CommonAModel): + """ + TN:劳动合同 + """ + employee = models.OneToOneField(Employee, verbose_name='人员信息', on_delete=models.SET_NULL, null=True, blank=True) + ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单', + on_delete=models.CASCADE, related_name='contract_ticket', null=True, blank=True) + counts = models.PositiveSmallIntegerField('合同变更次数', default=1) + plan_renewal = models.DateField('应续签', null=True, blank=True) + normal_renewal = models.DateField('正常续签', null=True, blank=True) + change_date = models.IntegerField('续签/变更(年)', null=True, blank=True) + change_reason = models.TextField('缩短续签/变更年限原因', null=True, blank=True) + + + \ No newline at end of file diff --git a/apps/hrm/serializers.py b/apps/hrm/serializers.py index 56466b3e..a3b1ca1d 100755 --- a/apps/hrm/serializers.py +++ b/apps/hrm/serializers.py @@ -8,8 +8,8 @@ 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, EmployeeTransfer) +from apps.hrm.models import (Certificate, ClockRecord, Employee, Probation, + NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, EmpPersonInfo, EmpContract, Leave, EmployeeTransfer) from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer from django.db import transaction from django.core.cache import cache @@ -349,6 +349,7 @@ class EmpNeedSerializer(CustomModelSerializer): model = EmpNeed fields = '__all__' + class EmpJoinSerializer(CustomModelSerializer): ticket_ = TicketSimpleSerializer(source='ticket', read_only=True) dept_name = serializers.CharField(source='dept_need.name', read_only=True) @@ -356,6 +357,7 @@ class EmpJoinSerializer(CustomModelSerializer): model = EmpJoin fields = '__all__' + class EmpPersonInfoSerializer(CustomModelSerializer): class Meta: model = EmpPersonInfo @@ -368,6 +370,7 @@ class EmpPersonInfoSerializer(CustomModelSerializer): 'note', ) + class LeaveSerializer(CustomModelSerializer): ticket_ = TicketSimpleSerializer(source='ticket', read_only=True) employee_name = serializers.CharField(source='employee.name', read_only=True) @@ -377,6 +380,7 @@ 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) @@ -397,4 +401,28 @@ class TransferSerializer(CustomModelSerializer): if validated_data['new_post'] == validated_data['original_post']: raise ParseError('新旧岗位相同,无需调动') - return super().create(validated_data) \ No newline at end of file + return super().create(validated_data) + + +class ProbationSerializer(CustomModelSerializer): + ticket_ = TicketSimpleSerializer(source='ticket', read_only=True) + employee_name = serializers.CharField(source='empperson.name', read_only=True) + post_name = serializers.CharField(source="empperson.reg_post", read_only=True) + reg_dept_name = serializers.CharField(source='empperson.reg_dept.name', read_only=True) + IDcard = serializers.CharField(source="empperson.IDcard", read_only=True) + class Meta: + model = Probation + fields = '__all__' + + +class EmpContractSerializer(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) + dept_name = serializers.CharField(source='employee.belong_dept.name', read_only=True) + gender = serializers.CharField(source="employee.gender", read_only=True) + join_date = serializers.CharField(source="employee.join_date", read_only=True) + end_contract = serializers.CharField(source="employee.end_contract_date", read_only=True) + class Meta: + model = EmpContract + fields = '__all__' \ No newline at end of file diff --git a/apps/hrm/services.py b/apps/hrm/services.py index e03c9778..a8c45a24 100755 --- a/apps/hrm/services.py +++ b/apps/hrm/services.py @@ -11,7 +11,7 @@ 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, EmployeeTransfer +from apps.hrm.models import ClockRecord, Employee, Resignation, EmployeeTransfer, Probation from apps.system.models import UserPost from apps.third.dahua import dhClient from apps.third.models import TDevice @@ -512,10 +512,7 @@ def post_transfer(ticket: Ticket, new_ticket_data: dict, **kwargs): employee = obj.employee user = employee.user - - if not user: - raise ParseError('员工未绑定系统账号,无法执行调岗') - + # 2️⃣ 先更新 EmployeeTransfer(避免 atomic 中用到旧数据) update_fields = [] for k, v in new_ticket_data.items(): @@ -529,47 +526,52 @@ def post_transfer(ticket: Ticket, new_ticket_data: dict, **kwargs): obj.save() - # 3️⃣ 调岗事务(岗位、部门、UserPost 一致性) - with transaction.atomic(): + # 更新 Employee的部门和岗位 + employee.belong_dept = obj.new_dept + employee.post = obj.new_post + employee.save(update_fields=['belong_dept', 'post']) - # 3.1 校验并删除原岗位记录 - old_post_qs = UserPost.objects.filter( - user=user, - post=obj.original_post, - dept=obj.original_dept - ) + if user: + # 3️⃣ 调岗事务(岗位、部门、UserPost 一致性) + with transaction.atomic(): + # 3.0 校验Employee中的user是否存在 + + # 3.1 校验并删除原岗位记录 + old_post_qs = UserPost.objects.filter( + user=user, + post=obj.original_post, + dept=obj.original_dept + ) - if not old_post_qs.exists(): - raise ParseError('原岗位记录不存在,无法调岗') + if not old_post_qs.exists(): + raise ParseError('原岗位记录不存在,无法调岗') - old_post_qs.delete() + old_post_qs.delete() - # 3.2 处理岗位排序(新岗位置顶) - UserPost.objects.filter(user=user).update(sort=F('sort') + 1) - - UserPost.objects.create( - user=user, - post=obj.new_post, - dept=obj.new_dept, - sort=1 - ) - - # 3.3 更新 Employee - employee.belong_dept = obj.new_dept - employee.post = obj.new_post - employee.save(update_fields=['belong_dept', 'post']) - - # 3.4 同步更新 User - User.objects.filter(id=user.id).update( - belong_dept=obj.new_dept, - post=obj.new_post - ) - - return obj + # 3.2 处理岗位排序(新岗位置顶) + UserPost.objects.filter(user=user).update(sort=F('sort') + 1) + UserPost.objects.create( + user=user, + post=obj.new_post, + dept=obj.new_dept, + sort=1 + ) + # 3.4 同步更新 User + User.objects.filter(id=user.id).update( + belong_dept=obj.new_dept, + post=obj.new_post + ) + return obj def validate_userdept(ticket: Ticket, new_ticket_data: dict, **kwargs): obj = EmployeeTransfer.objects.get(id=new_ticket_data['t_id']) exist_dept_post = UserPost.objects.filter(user=obj.employee.user, post=obj.original_post, dept=obj.original_dept) if not exist_dept_post.exists(): - raise ParseError('该用户的原部门或原职务不匹配') \ No newline at end of file + raise ParseError('该用户的原部门或原职务不匹配') + + +def probation_to_employee(ticket: Ticket, new_ticket_data: dict, **kwargs): + obj = Probation.objects.get(id=new_ticket_data['t_id']) + pass + # 需要解析上传的员工信息表,存入 employee表 diff --git a/apps/hrm/urls.py b/apps/hrm/urls.py index b7b037ae..368bcf62 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, TransferViewSet) + AttendanceViewSet, ResignationViewSet, EmpJoinViewSet, EmpPersonInfoViewSet, LeaveViewSet, TransferViewSet, ProbationViewSet, EmpContractViewSet) from django.urls import path, include from rest_framework.routers import DefaultRouter @@ -16,8 +16,11 @@ router.register('attendance', AttendanceViewSet, basename='attendance') router.register('resignation', ResignationViewSet, basename='resignation') router.register('empneed', EmpNeedViewSet, basename='empneed') router.register('empjoin', EmpJoinViewSet, basename='empjoin') +router.register('empperson', EmpPersonInfoViewSet, basename='empperson') router.register('leave', LeaveViewSet, basename='leave') router.register('transfer', TransferViewSet, basename='transfer') +router.register('probation', ProbationViewSet, basename='probation') +router.register('contract', EmpContractViewSet, basename='emp_contract') urlpatterns = [ path(API_BASE_URL, include(router.urls)), ] diff --git a/apps/hrm/views.py b/apps/hrm/views.py index 209b79af..e59ae130 100755 --- a/apps/hrm/views.py +++ b/apps/hrm/views.py @@ -12,7 +12,8 @@ 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, EmployeeTransfer +from apps.hrm.models import (Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation, + EmpNeed, EmpJoin, Leave, EmployeeTransfer, Probation, EmpPersonInfo, EmpContract) from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer, EmpJoinSerializer, ClockRecordListSerializer, EmployeeCreateUpdateSerializer, EmployeeDetailSerializer, EmployeeImproveSerializer, @@ -20,24 +21,87 @@ from apps.hrm.serializers import (CertificateCreateUpdateSerializer, Certificate EmployeeSerializer, ClockRecordSimpleSerializer, ClockRecordCreateSerializer, NotWorkRemarkListSerializer, CorrectSerializer, AttendanceSerializer, - ResignationSerializer, EmpNeedSerializer, LeaveSerializer, TransferSerializer) + ResignationSerializer, EmpNeedSerializer, LeaveSerializer, TransferSerializer, ProbationSerializer, EmpContractSerializer) from apps.hrm.services import HrmService from apps.third.dahua import dhClient from apps.third.tapis import dhapis +from openpyxl import load_workbook +from django.db import transaction +from django.core.exceptions import ValidationError +from datetime import datetime from apps.utils.export import export_excel from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet, EuModelViewSet from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin, RetrieveModelMixin from apps.wf.models import Ticket from apps.wf.mixins import TicketMixin +from apps.system.models import Post, Dept +from django.db.models import DateField +from datetime import datetime, date +from openpyxl.utils.datetime import from_excel +import os +import re epTypeOptions = {'employee': '正式员工', 'remployee': '相关方', 'visitor': '访客', 'driver': '货车司机'} epStateOptions = {10: '在职', 20: '离职', 30: '退休'} crOptions = {10: '上班打卡', 20: '下班打卡', 30: ''} crEoptions = {10: '在岗时间短', 20: '在岗时间长', 30: '缺卡', 40: '加班'} -# Create your views here. +HEAD_MAP = { + "所属部门": "belong_dept", + "车间": "workshop", + "职务": "position", + "职务聘任日期": "office_date", + "类别": "type", + "性别": "gender", + "身份证号码:姓名": "name", + "身份证号码:身份证号": "id_number", + "银行卡号码": "bank_card", + "入职日期": "start_date", + "合同到期日": "contract_end_date", + "转正日期": "regular_date", + "出生日期": "birthday", + "年龄": "age", + "工龄": "work_years", + "政治面貌": "partisan", + "入党时间": "join_partisan_date", + "民族": "nation", + "婚姻状况": "marriage", + "户口性质": "hukou_type", + "籍贯": "birthplace", + "户籍地址": "hukou_address", + "现住地址": "address", + "枣庄市职称": "zhuanzhi", + "枣庄市职称证书:图片_1": "zhuanzhi_img_1", + "枣庄市职称证书:图片_2": "zhuanzhi_img_2", + "获得枣庄市职称日期": "zhuanzhi_date", + "总院/集团职称": "zyjt_zhuanzhi", + "总院/集团职称证书:图片_1": "zyjt_zhuanzhi_img_1", + "获得职称日期": "zyjt_zhuanzhi_date", + "职业技能等级": "skill_rank", + "获得技能等级证书日期": "skill_rank_date", + "全日制最高学历": "full_edu", + "全日制最高学历学校名称": "full_edu_school", + "全日制最高学历所学专业": "full_edu_major", + "全日制最高学历入学时间-毕业时间": "full_edu_time", + "非全日制最高学历": "part_edu", + "非全日制最高学历学校名称": "part_edu_school", + "非全日制最高学历所学专业": "part_edu_major", + "非全日制最高学历入学时间-毕业时间": "part_edu_time", + "本人联系电话": "phone", + "紧急联系人姓名": "emergency_contact", + "紧急联系人电话": "emergency_phone", + "所获荣誉": "honor", + "首次缴纳社保日期": "first_social_security_date", + "是否为退役军人": "is_veteran", + "个人邮箱":"email", +} +def cell_value(cell): + val = cell.value + if isinstance(val, datetime): + return val.date() + return val.strip() if isinstance(val, str) else val class EmployeeViewSet(CustomModelViewSet): """ @@ -244,6 +308,203 @@ class EmployeeViewSet(CustomModelViewSet): ) return Response({'path': export_excel(field_data, data, '人员信息')}) + @action(methods=['post'], detail=False, perms_map={'post': 'employee.import_excel'}, + serializer_class=serializers.Serializer) + def import_excel(self, request, pk=None): + import logging + myLogger = logging.getLogger('log') + """导入excel""" + file_path = request.data.get('file_path') + relative_path = file_path.lstrip('/') + abs_path = os.path.join(settings.BASE_DIR, relative_path) + wb = load_workbook(abs_path, data_only=True) + sheet = wb.active + + rows = list(sheet.iter_rows()) + if len(rows) < 2: + raise ParseError('Excel 没有数据') + + # 获取表头 + headers = [cell_value(c) for c in rows[0]] + field_index = {} + for idx, header in enumerate(headers): + if header in HEAD_MAP: + field_index[HEAD_MAP[header]] = idx + + # 外键缓存 + dept_map = {d.name: d.id for d in Dept.objects.all()} + TYPE_MAPPING = { + '光芯': 'employee', + '外包': 'remployee' + } + success = 0 + errors = [] + + # 获取模型字段 + model_fields = {f.name: f for f in Employee._meta.fields} + + for row_num, row in enumerate(rows[1:], start=2): + try: + data = {} + for field, idx in field_index.items(): + raw_value = cell_value(row[idx]) + model_field = model_fields.get(field) + value = convert_field_value(model_field, raw_value, row_num) + data[field] = value + + # 移除可能的id字段 + if 'id' in data: + del data['id'] + + # 获取唯一标识 + id_number = data.get("id_number") + name = data.get("name") + + if not id_number or not name: + raise ParseError(f'第{row_num}行,身份证号或姓名为空') + + myLogger.info(f"处理第{row_num}行:{name} - {id_number}") + # 处理人员类型 + if 'type' in data and data['type']: + excel_type = data['type'] + if excel_type in TYPE_MAPPING: + data['type'] = TYPE_MAPPING[excel_type] + else: + raise ParseError(f'第{row_num}行,人员类型"{excel_type}"无效,有效类型:{", ".join(TYPE_MAPPING.keys())}') + # 处理部门外键 + if 'belong_dept' in data and data['belong_dept']: + dept_name = data.pop('belong_dept') + if dept_name not in dept_map: + raise ParseError(f'第{row_num}行,部门"{dept_name}"不存在') + data['belong_dept_id'] = dept_map[dept_name] + + # 数据验证 + if data.get('phone'): + if not re.match(r'^1[3-9]\d{9}$', data['phone']): + raise ParseError(f'第{row_num}行,手机号格式不正确') + + if data.get('id_number'): + if not re.match(r'^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$', data['id_number']): + raise ParseError(f'第{row_num}行,身份证号格式不正确') + + # 查找或更新 + try: + with transaction.atomic(): + obj, created = Employee.objects.update_or_create( + id_number=id_number, + name=name, + defaults=data + ) + except Exception as e: + raise + if created: + myLogger.info(f"✅ 第{row_num}行新增成功:{name}") + else: + myLogger.info(f"✅ 第{row_num}行更新成功:{name}") + + success += 1 + + except Exception as e: + error_msg = f'第{row_num}行处理失败:{str(e)}' + myLogger.error(f"❌ {error_msg}") + errors.append({'row': row_num, 'error': str(e)}) + + myLogger.info(f"\n处理完成:成功{success}条,失败{len(errors)}条") + + return Response({'success': success, 'errors': errors}) + + +def convert_field_value(model_field, value, row_num): + """ + 根据模型字段类型自动转换 Excel 值 + """ + from django.db.models import DateField, BooleanField, IntegerField, FloatField, CharField + if value in [None, ""]: + return None + + # ===== 日期字段 ===== + if isinstance(model_field, CharField): + if isinstance(value, str): + if 'e' in value.lower(): + try: + return str(int(float(value))) + except ValueError: + pass + if '.' in value: + try: + return str(int(float(value))) + except ValueError: + pass + return str(value) + + if isinstance(model_field, DateField): + + # 已经是 date + if isinstance(value, date): + return value + + # datetime + if isinstance(value, datetime): + return value.date() + + # Excel 数字日期 + if isinstance(value, (int, float)): + try: + return from_excel(value).date() + except Exception: + raise ParseError(f'第{row_num}行,字段 {model_field.name} 日期格式错误') + + # 字符串日期 + if isinstance(value, str): + value = value.strip() + formats = [ + "%Y-%m-%d", + "%Y/%m/%d", + "%Y.%m.%d", + "%Y%m%d", + "%Y年%m月%d日", + ] + for fmt in formats: + try: + return datetime.strptime(value, fmt).date() + except ValueError: + continue + + raise ParseError(f'第{row_num}行,字段 {model_field.name} 日期格式不正确') + + raise ParseError(f'第{row_num}行,字段 {model_field.name} 日期类型错误') + + # ===== 布尔字段 ===== + if isinstance(model_field, BooleanField): + + if value in [True, 1, '1']: + return True + if value in [False, 0, '0']: + return False + + if isinstance(value, str): + value = value.strip() + if value in ['是', 'Y', 'y', 'yes', 'YES', 'True', 'true']: + return True + if value in ['否', 'N', 'n', 'no', 'NO', 'False', 'false']: + return False + + raise ParseError(f'第{row_num}行,字段 {model_field.name} 只能填写 是 或 否') + + # ===== 整数字段 ===== + if isinstance(model_field, IntegerField): + try: + return int(value) + except: + raise ParseError(f'第{row_num}行,字段 {model_field.name} 必须为整数') + + # ===== 浮点字段 ===== + if isinstance(model_field, FloatField): + try: + return float(value) + except: + raise ParseError(f'第{row_num}行,字段 {model_field.name} 必须为数字') + return value class AttendanceViewSet(CustomModelViewSet): """ @@ -395,6 +656,9 @@ class CertificateViewSet(CustomModelViewSet): class ResignationViewSet(TicketMixin, EuModelViewSet): + """ + 离职记录 + """ select_related_fields = ['employee', 'employee__belong_dept', 'employee__post'] queryset = Resignation.objects.all() serializer_class = ResignationSerializer @@ -426,6 +690,9 @@ class ResignationViewSet(TicketMixin, EuModelViewSet): class EmpNeedViewSet(TicketMixin, EuModelViewSet): + """ + 员工需求 + """ queryset = EmpNeed.objects.all() serializer_class = EmpNeedSerializer filterset_fields = ['dept_need'] @@ -437,6 +704,9 @@ class EmpNeedViewSet(TicketMixin, EuModelViewSet): class EmpJoinViewSet(TicketMixin, EuModelViewSet): + """ + 员工入职 + """ queryset = EmpJoin.objects.all() serializer_class = EmpJoinSerializer workflow_key = "wf_empjoin" @@ -456,6 +726,9 @@ class EmpJoinViewSet(TicketMixin, EuModelViewSet): class LeaveViewSet(TicketMixin, EuModelViewSet): + """ + 员工请假 + """ select_related_fields = [ 'employee', 'employee__belong_dept', @@ -469,8 +742,22 @@ class LeaveViewSet(TicketMixin, EuModelViewSet): def gen_other_ticket_data(self, instance): return {"hour": instance.hour if instance.hour else None} - + + +class EmpPersonInfoViewSet(CustomModelViewSet, EuModelViewSet): + """ + 入职人员信息 + """ + queryset = EmpPersonInfo.objects.all() + serializer_class = EmpPersonInfoSerializer + filterset_fields = ['post'] + search_fields = ['name', 'post'] + + class TransferViewSet(TicketMixin, EuModelViewSet): + """ + 员工岗位调动 + """ select_related_fields = [ 'employee', 'employee__belong_dept', @@ -484,4 +771,38 @@ class TransferViewSet(TicketMixin, EuModelViewSet): 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 + return {"name": instance.employee.name if instance.employee.name else None} + +class ProbationViewSet(TicketMixin, EuModelViewSet): + """ + 员工转正 + """ + select_related_fields = [ + 'empperson', + 'ticket', + 'reg_dept', + 'reg_post' + ] + queryset = Probation.objects.all() + serializer_class = ProbationSerializer + filterset_fields = ['empperson__name', 'reg_dept'] + search_fields = ["empperson__name", "reg_post__name", "reg_dept__name"] + workflow_key = "wf_probation" + + def gen_other_ticket_data(self, instance): + return {"name": instance.empperson.name if instance.empperson.name else None} + + +class EmpContractViewSet(TicketMixin, EuModelViewSet): + """ + 员工合同 + """ + queryset = EmpContract.objects.all() + serializer_class = EmpContractSerializer + filterset_fields = ['employee', 'employee__belong_dept','employee__name', 'employee__post'] + search_fields = ["employee__name", "employee__post", "employee__belong_dept"] + workflow_key = "wf_contract" + + def gen_other_ticket_data(self, instance): + return {"name": instance.employee.name if instance.employee.name else None} + diff --git a/media/default/abc.mp3 b/media/default/abc.mp3 deleted file mode 100644 index e4283950..00000000 Binary files a/media/default/abc.mp3 and /dev/null differ diff --git a/media/default/abc2.mp3 b/media/default/abc2.mp3 deleted file mode 100644 index a1352813..00000000 Binary files a/media/default/abc2.mp3 and /dev/null differ diff --git a/media/default/alarm.mp3 b/media/default/alarm.mp3 deleted file mode 100755 index 2727033d..00000000 Binary files a/media/default/alarm.mp3 and /dev/null differ diff --git a/media/default/avatar.png b/media/default/avatar.png deleted file mode 100755 index 07e80789..00000000 Binary files a/media/default/avatar.png and /dev/null differ diff --git a/media/default/template/material.xlsx b/media/default/template/material.xlsx deleted file mode 100644 index 291a3067..00000000 Binary files a/media/default/template/material.xlsx and /dev/null differ diff --git a/media/default/template/mioitemw_t.xlsx b/media/default/template/mioitemw_t.xlsx deleted file mode 100644 index ad58070f..00000000 Binary files a/media/default/template/mioitemw_t.xlsx and /dev/null differ diff --git a/media/default/template/出入库明细.xlsx b/media/default/template/出入库明细.xlsx deleted file mode 100644 index f7e526e6..00000000 Binary files a/media/default/template/出入库明细.xlsx and /dev/null differ diff --git a/media/default/test.png b/media/default/test.png deleted file mode 100755 index 84c16a87..00000000 Binary files a/media/default/test.png and /dev/null differ diff --git a/media/default/test2.jpg b/media/default/test2.jpg deleted file mode 100755 index b40ddd78..00000000 Binary files a/media/default/test2.jpg and /dev/null differ