import_routes.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. """Import API routes for XLSX file import operations."""
  2. from flask import request, send_file
  3. from flask_restx import Namespace, Resource, fields
  4. from werkzeug.datastructures import FileStorage
  5. from app.services.import_service import ImportService
  6. from app.utils.auth_decorator import require_auth
  7. import_ns = Namespace('import', description='数据导入接口')
  8. # File upload parser
  9. upload_parser = import_ns.parser()
  10. upload_parser.add_argument(
  11. 'file',
  12. location='files',
  13. type=FileStorage,
  14. required=True,
  15. help='XLSX文件'
  16. )
  17. # Response models for Swagger documentation
  18. error_response = import_ns.model('ImportErrorResponse', {
  19. 'success': fields.Boolean(description='操作是否成功'),
  20. 'error': fields.String(description='错误信息'),
  21. 'errors': fields.List(fields.String, description='错误信息列表'),
  22. 'code': fields.String(description='错误代码')
  23. })
  24. success_response = import_ns.model('ImportSuccessResponse', {
  25. 'success': fields.Boolean(description='操作是否成功'),
  26. 'count': fields.Integer(description='成功导入的记录数')
  27. })
  28. @import_ns.route('/template')
  29. class ImportTemplate(Resource):
  30. """Resource for downloading import template."""
  31. @import_ns.doc('download_template')
  32. @import_ns.response(200, 'XLSX模板文件下载')
  33. @import_ns.response(500, '服务器错误', error_response)
  34. @import_ns.produces(['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'])
  35. @require_auth
  36. def get(self):
  37. """下载导入模板
  38. 下载包含以下列的XLSX模板文件:
  39. - 人员姓名
  40. - 物品名称
  41. - 工作日期 (格式: YYYY-MM-DD)
  42. - 数量
  43. Requirements: 3.6
  44. """
  45. try:
  46. template_file = ImportService.generate_template()
  47. return send_file(
  48. template_file,
  49. mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  50. as_attachment=True,
  51. download_name='import_template.xlsx'
  52. )
  53. except Exception as e:
  54. return {
  55. 'success': False,
  56. 'error': f'生成模板失败: {str(e)}',
  57. 'code': 'SERVER_ERROR'
  58. }, 500
  59. @import_ns.route('/upload')
  60. class ImportUpload(Resource):
  61. """Resource for uploading and importing XLSX files."""
  62. @import_ns.doc('upload_import')
  63. @import_ns.expect(upload_parser)
  64. @import_ns.response(200, '导入成功', success_response)
  65. @import_ns.response(400, '验证错误', error_response)
  66. @import_ns.response(500, '服务器错误', error_response)
  67. @require_auth
  68. def post(self):
  69. """上传并导入XLSX文件
  70. 上传XLSX文件并批量创建工作记录。
  71. 文件大小限制: 5MB
  72. 验证规则:
  73. - 文件必须是XLSX格式
  74. - 必须包含所有必需列
  75. - 人员姓名必须匹配系统中已存在的人员
  76. - 物品名称必须匹配系统中已存在的物品
  77. - 工作日期格式必须为 YYYY-MM-DD
  78. - 数量必须为正数
  79. Requirements: 4.9, 4.11, 4.12
  80. """
  81. # Check if file is present
  82. if 'file' not in request.files:
  83. return {
  84. 'success': False,
  85. 'error': '请选择要上传的文件',
  86. 'code': 'VALIDATION_ERROR'
  87. }, 400
  88. file = request.files['file']
  89. # Check if file is selected
  90. if file.filename == '':
  91. return {
  92. 'success': False,
  93. 'error': '请选择要上传的文件',
  94. 'code': 'VALIDATION_ERROR'
  95. }, 400
  96. # Check file extension
  97. if not file.filename.lower().endswith('.xlsx'):
  98. return {
  99. 'success': False,
  100. 'error': '文件格式错误,请上传XLSX文件',
  101. 'code': 'VALIDATION_ERROR'
  102. }, 400
  103. # Read file content and check size
  104. file_content = file.read()
  105. # Validate file size (5MB limit)
  106. if len(file_content) > ImportService.MAX_FILE_SIZE:
  107. return {
  108. 'success': False,
  109. 'error': '文件大小超过限制(最大5MB)',
  110. 'code': 'VALIDATION_ERROR'
  111. }, 400
  112. try:
  113. # Parse and validate file content
  114. valid_records, errors = ImportService.parse_and_validate(file_content)
  115. # If there are validation errors, return them (atomic operation - no records created)
  116. if errors:
  117. return {
  118. 'success': False,
  119. 'errors': errors,
  120. 'code': 'VALIDATION_ERROR'
  121. }, 400
  122. # If no valid records found
  123. if not valid_records:
  124. return {
  125. 'success': False,
  126. 'error': '文件中没有有效的数据行',
  127. 'code': 'VALIDATION_ERROR'
  128. }, 400
  129. # Import the validated records
  130. count = ImportService.import_records(valid_records)
  131. return {
  132. 'success': True,
  133. 'count': count
  134. }, 200
  135. except Exception as e:
  136. return {
  137. 'success': False,
  138. 'error': f'导入失败: {str(e)}',
  139. 'code': 'SERVER_ERROR'
  140. }, 500