commit 上传excel-人员基本信息部分
This commit is contained in:
parent
2f63e0981e
commit
ba909c0f41
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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"}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
# 上下文查询参数
|
# 上下文查询参数
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue