commit 上传excel-人员基本信息部分

This commit is contained in:
彭森 2024-06-17 00:31:49 +08:00
parent 2f63e0981e
commit ba909c0f41
7 changed files with 64 additions and 44 deletions

View File

@ -19,7 +19,7 @@ class ExpenseType(models.Model):
# 费用明细表 # 费用明细表
class ExpenseDetail(models.Model): class ExpenseDetail(models.Model):
detail_id = models.AutoField(primary_key=True, verbose_name='明细ID') detail_id = models.AutoField(primary_key=True, verbose_name='明细ID')
type_id = models.ForeignKey(ExpenseType, on_delete=models.CASCADE, verbose_name='费用类型ID') type_id = models.ForeignKey(ExpenseType, on_delete=models.CASCADE, verbose_name='费用类型名称')
expense_detail = models.CharField(max_length=255, unique=True, verbose_name='费用明细') expense_detail = models.CharField(max_length=255, unique=True, verbose_name='费用明细')
class Meta: class Meta:

View File

@ -9,6 +9,7 @@ from django.urls import reverse
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST, require_http_methods from django.views.decorators.http import require_POST, require_http_methods
from application import pjt_mgnt
from application.fac_mgnt.forms import * from application.fac_mgnt.forms import *
from application.fac_mgnt.models import * from application.fac_mgnt.models import *
from application.hrm_mgnt.models import PerformanceEvaluation from application.hrm_mgnt.models import PerformanceEvaluation
@ -53,9 +54,7 @@ def exp_type_list_view(request):
"parse_url": reverse("common_excel_parse"), "parse_url": reverse("common_excel_parse"),
"save_url": reverse("save_excel_table_data"), "save_url": reverse("save_excel_table_data"),
"fields_preview_config": { "fields_preview_config": {
"type_id": {"type": "text", "width": "180px"},
"expense_type": {"type": "text", "width": "180px"}, "expense_type": {"type": "text", "width": "180px"},
"actions": {"type": "actions", "width": "100px"}
} }
}, },
"query_params": query_params, "query_params": query_params,
@ -152,8 +151,8 @@ def exp_detail_list_view(request):
"table_exclude_field_name": ['detail_id'], "table_exclude_field_name": ['detail_id'],
"excel_upload_config": { "excel_upload_config": {
"template_url": reverse("download_template", kwargs={'template_name': template_name}), "template_url": reverse("download_template", kwargs={'template_name': template_name}),
"parse_url": reverse("common_excel_parse_exp"), "parse_url": reverse("common_excel_parse"),
"save_url": reverse("save_excel_table_data_exp"), "save_url": reverse("save_excel_table_data"),
"fields_preview_config": { "fields_preview_config": {
"type_id": {"type": "text", "width": "180px"}, "type_id": {"type": "text", "width": "180px"},
"expense_detail": {"type": "text", "width": "180px"}, "expense_detail": {"type": "text", "width": "180px"},
@ -280,8 +279,8 @@ def gpb_list_view(request):
"table_exclude_field_name": ['budget_id'], "table_exclude_field_name": ['budget_id'],
"excel_upload_config": { "excel_upload_config": {
"template_url": reverse("download_template", kwargs={'template_name': template_name}), "template_url": reverse("download_template", kwargs={'template_name': template_name}),
"parse_url": reverse("common_excel_parse_gab"), "parse_url": reverse("common_excel_parse"),
"save_url": reverse("save_excel_table_data_gab"), "save_url": reverse("save_excel_table_data"),
"fields_preview_config": { "fields_preview_config": {
"primary_department": {"type": "text", "width": "180px"}, "primary_department": {"type": "text", "width": "180px"},
"year": {"type": "number", "width": "100px"}, "year": {"type": "number", "width": "100px"},
@ -2184,8 +2183,8 @@ def common_excel_parse_pjt(request):
project_name = instance_data.get('project_name') project_name = instance_data.get('project_name')
if project_name: if project_name:
try: try:
project_instance = ProjectLedger.objects.get(project_name=project_name) project_instance = pjt_mgnt.models.ProjectLedger.objects.get(project_name=project_name)
instance_data['project_id'] = project_instance instance_data['project'] = project_instance
except ProjectLedger.DoesNotExist: except ProjectLedger.DoesNotExist:
return JsonResponse({'error': f'找不到名称为 {project_name} 的项目台账记录。'}, status=400) return JsonResponse({'error': f'找不到名称为 {project_name} 的项目台账记录。'}, status=400)

View File

@ -109,14 +109,13 @@ def emp_list_view(request):
"political_affiliation": {"type": "text", "width": "120px"}, "political_affiliation": {"type": "text", "width": "120px"},
"entry_date": {"type": "date", "width": "110px"}, "entry_date": {"type": "date", "width": "110px"},
"regularization_date": {"type": "date", "width": "110px"}, "regularization_date": {"type": "date", "width": "110px"},
"departure_date": {"type": "date", "width": "110px"},
"employment_type": {"type": "text", "width": "100px"}, "employment_type": {"type": "text", "width": "100px"},
"status": {"type": "text", "width": "80px"}, "status": {"type": "text", "width": "80px"},
"primary_department": {"type": "text", "width": "180px"}, "primary_department": {"type": "text", "width": "180px"},
"secondary_department": {"type": "text", "width": "180px"}, "secondary_department": {"type": "text", "width": "180px"},
"position": {"type": "text", "width": "180px"}, "position": {"type": "text", "width": "180px"},
"grade": {"type": "text", "width": "120px"}, "rank": {"type": "text", "width": "180px"},
"contract_end_date": {"type": "date", "width": "110px"}, "contract_end_date": {"type": "text", "width": "180px"},
"mobile_number": {"type": "text", "width": "150px"}, "mobile_number": {"type": "text", "width": "150px"},
"email": {"type": "text", "width": "200px"}, "email": {"type": "text", "width": "200px"},
"mailing_address": {"type": "text", "width": "280px"}, "mailing_address": {"type": "text", "width": "280px"},
@ -131,8 +130,6 @@ def emp_list_view(request):
"base_salary": {"type": "text", "width": "120px"}, "base_salary": {"type": "text", "width": "120px"},
"salary_account_number": {"type": "text", "width": "220px"}, "salary_account_number": {"type": "text", "width": "220px"},
"bank_of_salary_account": {"type": "text", "width": "220px"}, "bank_of_salary_account": {"type": "text", "width": "220px"},
"resignation_type": {"type": "text", "width": "120px"},
"resignation_reason": {"type": "textarea", "width": "300px"}
} }
}, },
# 上下文查询参数 # 上下文查询参数

View File

@ -59,7 +59,6 @@ def proj_ledger_list_view(request):
"parse_url": reverse("common_excel_parse"), "parse_url": reverse("common_excel_parse"),
"save_url": reverse("save_excel_table_data"), "save_url": reverse("save_excel_table_data"),
"fields_preview_config": { "fields_preview_config": {
"project_id": {"type": "text", "width": "180px"},
"project_name": {"type": "text", "width": "180px"}, "project_name": {"type": "text", "width": "180px"},
"start_date": {"type": "date", "width": "180px"}, "start_date": {"type": "date", "width": "180px"},
"end_date": {"type": "date", "width": "180px"}, "end_date": {"type": "date", "width": "180px"},
@ -88,7 +87,6 @@ def proj_ledger_list_view(request):
"actual_net_income": {"type": "number", "width": "180px"}, "actual_net_income": {"type": "number", "width": "180px"},
"outstanding_net_income": {"type": "number", "width": "180px"}, "outstanding_net_income": {"type": "number", "width": "180px"},
"notes": {"type": "text", "width": "180px"}, "notes": {"type": "text", "width": "180px"},
"actions": {"type": "actions", "width": "100px"}
} }
}, },
"query_params": query_params, "query_params": query_params,

View File

@ -1,6 +1,7 @@
from django.urls import path from django.urls import path
from common.views import * from common.views import *
urlpatterns = [ urlpatterns = [
path('error_page/', error_page, name='error_page'), path('error_page/', error_page, name='error_page'),
path('download_excel_template/<str:template_name>/', download_template, name='download_template'), path('download_excel_template/<str:template_name>/', download_template, name='download_template'),

View File

@ -22,6 +22,7 @@ from application.org_mgnt.models import SecondaryDepartment
def error_page(request): def error_page(request):
return render(request, 'error_page.html') return render(request, 'error_page.html')
@login_required @login_required
def download_template(request, template_name): def download_template(request, template_name):
""" """
@ -164,34 +165,45 @@ def common_excel_parse(request):
fields_map = dict(zip(model_fields, header_row)) fields_map = dict(zip(model_fields, header_row))
fields_map_nf = dict(zip(header_row, model_fields)) fields_map_nf = dict(zip(header_row, model_fields))
# 检查表头是否与模型字段名对应
# if not set(header_row).issubset(model_verbose_name):
# return JsonResponse({'error': '表头不匹配请使用正确的Excel上传模板。'}, status=400)
# 创建一个映射将Excel表头映射到模型字段名 # 创建一个映射将Excel表头映射到模型字段名
header_to_field_map = {header: fields_map_nf[header] for header in header_row} header_to_field_map = {header: fields_map_nf[header] for header in header_row}
header_fields = [header_to_field_map[header] for header in header_row] header_fields = [header_to_field_map[header] for header in header_row]
# 动态处理外键关系
def get_related_instance(model, field_name, value):
field = model._meta.get_field(field_name)
if field.is_relation:
related_model = field.related_model
related_field_name_list = related_model._meta.fields # 获取关联模型的第一个字段名
for related_field_name in related_field_name_list:
try:
related_instance = related_model.objects.get(**{related_field_name.name: value})
value = related_instance
return value
except Exception:
continue
return None
else:
return value
for row in sheet.iter_rows(min_row=2, values_only=True): for row in sheet.iter_rows(min_row=2, values_only=True):
if not all(value is None for value in row): if not all(value is None for value in row):
# 使用映射来确保每个Excel单元格的数据对应到正确的模型字段
instance_data = {header_to_field_map[header]: value for header, value in zip(header_row, row)} instance_data = {header_to_field_map[header]: value for header, value in zip(header_row, row)}
preview_data = instance_data.copy()
for field_name, value in instance_data.items():
instance_data[field_name] = get_related_instance(model, field_name, value)
instance = model(**instance_data) instance = model(**instance_data)
try: try:
instance.full_clean() instance.full_clean()
data.append(instance) data.append(preview_data)
except ValidationError as e: except ValidationError as e:
return JsonResponse({'error': f'数据校验错误: {e.message_dict}'}, status=400) return JsonResponse({'error': f'数据校验错误: {e.message_dict}'}, status=400)
# 动态获取序列化器
serializer_class = create_dynamic_serializer(model, include=header_fields)
serializer = serializer_class(data, many=True)
# 清理,删除上传的文件 # 清理,删除上传的文件
os.remove(file_path) os.remove(file_path)
return JsonResponse({"table_data": serializer.data, "fields_map": fields_map}, safe=False) return JsonResponse({"table_data": data, "fields_map": fields_map}, safe=False)
except Exception as e: except Exception as e:
# 清理,删除上传的文件 # 清理,删除上传的文件
os.remove(file_path) os.remove(file_path)
@ -219,34 +231,47 @@ def save_excel_table_data(request):
except (ValueError, LookupError): except (ValueError, LookupError):
return JsonResponse({'error': '无效的 model_config'}, status=400) return JsonResponse({'error': '无效的 model_config'}, status=400)
# 创建模型实例列表
instances = [] instances = []
for row_data in table_data: for row_data in table_data:
instance_data = {}
for field_name, value in row_data.items():
field = Model._meta.get_field(field_name)
if field.is_relation:
related_model = field.related_model
related_field_name_list = related_model._meta.fields
for related_field_name in related_field_name_list:
try: try:
instance = Model(**row_data) related_instance = related_model.objects.get(**{related_field_name.name: value})
instance.full_clean() # 验证数据 value = related_instance
instance_data[field_name] = value
break
except Exception:
continue
else:
instance_data[field_name] = value
instance = Model(**instance_data)
try:
instance.full_clean()
instances.append(instance) instances.append(instance)
except ValidationError as e: except ValidationError as e:
return JsonResponse({'error': f'数据校验错误: {e.message_dict}'}, status=400) return JsonResponse({'error': f'数据校验错误: {e.message_dict}'}, status=400)
except Exception as e:
return JsonResponse({'error': f'创建实例时出错: {str(e)}'}, status=500)
# 批量创建模型实例 # 批量保存数据
try:
Model.objects.bulk_create(instances) Model.objects.bulk_create(instances)
except Exception as e:
return JsonResponse({'error': f'批量保存数据时出错: {str(e)}'}, status=500)
return JsonResponse({'message': '表格数据保存成功'}, status=200) return JsonResponse({'success': '数据保存成功'}, status=201)
except json.JSONDecodeError: except json.JSONDecodeError:
return JsonResponse({'error': '无效的JSON格式'}, status=400) return JsonResponse({'error': '无效的JSON数据'}, status=400)
except Exception as e: except Exception as e:
return JsonResponse({'error': f'服务器内部错误: {str(e)}'}, status=500) return JsonResponse({'error': f'保存数据时出错: {str(e)}'}, status=500)
return JsonResponse({'error': '无效的请求方法'}, status=400)
return JsonResponse({'error': '请求错误'}, status=400)
@login_required @login_required
def load_secondary_departments(request): def load_secondary_departments(request):
primary_department_id = request.GET.get('primary_department_id') primary_department_id = request.GET.get('primary_department_id')
secondary_departments = SecondaryDepartment.objects.filter(primary_department_id=primary_department_id).order_by('secondary_department_name') secondary_departments = SecondaryDepartment.objects.filter(primary_department_id=primary_department_id).order_by(
return JsonResponse(list(secondary_departments.values('secondary_department_id', 'secondary_department_name')), safe=False) 'secondary_department_name')
return JsonResponse(list(secondary_departments.values('secondary_department_id', 'secondary_department_name')),
safe=False)