"""Work Record API routes.""" from flask import request from flask_restx import Namespace, Resource, fields from app.services.work_record_service import WorkRecordService from app.utils.auth_decorator import require_auth work_record_ns = Namespace('work-records', description='工作记录管理接口') # API models for Swagger documentation work_record_model = work_record_ns.model('WorkRecord', { 'id': fields.Integer(readonly=True, description='记录ID'), 'person_id': fields.Integer(required=True, description='人员ID'), 'person_name': fields.String(readonly=True, description='人员姓名'), 'item_id': fields.Integer(required=True, description='物品ID'), 'item_name': fields.String(readonly=True, description='物品名称'), 'unit_price': fields.Float(readonly=True, description='单价'), 'supplier_id': fields.Integer(readonly=True, description='供应商ID'), 'supplier_name': fields.String(readonly=True, description='供应商名称'), 'work_date': fields.String(required=True, description='工作日期 (YYYY-MM-DD)'), 'quantity': fields.Integer(required=True, description='数量'), 'total_price': fields.Float(readonly=True, description='总价'), 'is_settled': fields.Boolean(readonly=True, description='结算状态'), 'created_at': fields.String(readonly=True, description='创建时间'), 'updated_at': fields.String(readonly=True, description='更新时间') }) work_record_input = work_record_ns.model('WorkRecordInput', { 'person_id': fields.Integer(required=True, description='人员ID'), 'item_id': fields.Integer(required=True, description='物品ID'), 'work_date': fields.String(required=True, description='工作日期 (YYYY-MM-DD)'), 'quantity': fields.Integer(required=True, description='数量') }) work_record_update = work_record_ns.model('WorkRecordUpdate', { 'id': fields.Integer(required=True, description='记录ID'), 'person_id': fields.Integer(description='人员ID'), 'item_id': fields.Integer(description='物品ID'), 'work_date': fields.String(description='工作日期 (YYYY-MM-DD)'), 'quantity': fields.Integer(description='数量') }) work_record_delete = work_record_ns.model('WorkRecordDelete', { 'id': fields.Integer(required=True, description='记录ID') }) # Settlement models batch_settlement_input = work_record_ns.model('BatchSettlementInput', { 'person_id': fields.Integer(description='人员ID(可选)'), 'year': fields.Integer(required=True, description='年份'), 'month': fields.Integer(required=True, description='月份 (1-12)'), 'supplier_id': fields.Integer(description='供应商ID(可选)'), 'is_settled': fields.Boolean(required=True, description='结算状态') }) batch_settlement_response = work_record_ns.model('BatchSettlementResponse', { 'updated_count': fields.Integer(description='更新的记录数'), 'total_matched': fields.Integer(description='匹配的总记录数') }) # Pagination model pagination_model = work_record_ns.model('Pagination', { 'total': fields.Integer(description='总记录数'), 'page': fields.Integer(description='当前页码'), 'page_size': fields.Integer(description='每页数量'), 'total_pages': fields.Integer(description='总页数') }) # Response models success_response = work_record_ns.model('SuccessResponse', { 'success': fields.Boolean(description='操作是否成功'), 'data': fields.Raw(description='返回数据'), 'message': fields.String(description='消息') }) paginated_response = work_record_ns.model('PaginatedResponse', { 'success': fields.Boolean(description='操作是否成功'), 'data': fields.List(fields.Nested(work_record_model), description='工作记录列表'), 'pagination': fields.Nested(pagination_model, description='分页信息'), 'message': fields.String(description='消息') }) error_response = work_record_ns.model('ErrorResponse', { 'success': fields.Boolean(description='操作是否成功'), 'error': fields.String(description='错误信息'), 'code': fields.String(description='错误代码') }) # Daily summary models daily_summary_item = work_record_ns.model('DailySummaryItem', { 'item_name': fields.String(description='物品名称'), 'supplier_name': fields.String(description='供应商名称'), 'unit_price': fields.Float(description='单价'), 'quantity': fields.Integer(description='数量'), 'total_price': fields.Float(description='总价') }) daily_summary_person = work_record_ns.model('DailySummaryPerson', { 'person_id': fields.Integer(description='人员ID'), 'person_name': fields.String(description='人员姓名'), 'total_items': fields.Integer(description='总数量'), 'total_value': fields.Float(description='总金额'), 'items': fields.List(fields.Nested(daily_summary_item), description='物品明细') }) daily_summary_response = work_record_ns.model('DailySummaryResponse', { 'date': fields.String(description='日期'), 'summary': fields.List(fields.Nested(daily_summary_person), description='人员汇总'), 'grand_total_items': fields.Integer(description='总数量'), 'grand_total_value': fields.Float(description='总金额') }) # Monthly summary models monthly_top_performer = work_record_ns.model('MonthlyTopPerformer', { 'person_id': fields.Integer(description='人员ID'), 'person_name': fields.String(description='人员姓名'), 'earnings': fields.Float(description='收入') }) monthly_item_breakdown = work_record_ns.model('MonthlyItemBreakdown', { 'item_id': fields.Integer(description='物品ID'), 'item_name': fields.String(description='物品名称'), 'supplier_name': fields.String(description='供应商名称'), 'quantity': fields.Integer(description='数量'), 'earnings': fields.Float(description='收入') }) monthly_supplier_breakdown = work_record_ns.model('MonthlySupplierBreakdown', { 'person_id': fields.Integer(description='人员ID'), 'person_name': fields.String(description='人员姓名'), 'supplier_name': fields.String(description='供应商名称'), 'earnings': fields.Float(description='收入') }) monthly_summary_response = work_record_ns.model('MonthlySummaryResponse', { 'year': fields.Integer(description='年份'), 'month': fields.Integer(description='月份'), 'total_records': fields.Integer(description='总记录数'), 'total_earnings': fields.Float(description='总收入'), 'top_performers': fields.List(fields.Nested(monthly_top_performer), description='业绩排名'), 'item_breakdown': fields.List(fields.Nested(monthly_item_breakdown), description='物品收入明细'), 'supplier_breakdown': fields.List(fields.Nested(monthly_supplier_breakdown), description='人员按供应商收入明细') }) # Yearly summary models yearly_summary_person = work_record_ns.model('YearlySummaryPerson', { 'person_id': fields.Integer(description='人员ID'), 'person_name': fields.String(description='人员姓名'), 'monthly_earnings': fields.List(fields.Float, description='12个月的收入数组'), 'yearly_total': fields.Float(description='年度总收入'), 'settled_total': fields.Float(description='已结算总收入'), 'unsettled_total': fields.Float(description='未结算总收入') }) yearly_summary_response = work_record_ns.model('YearlySummaryResponse', { 'year': fields.Integer(description='年份'), 'persons': fields.List(fields.Nested(yearly_summary_person), description='人员汇总列表'), 'monthly_totals': fields.List(fields.Float, description='每月所有人的合计'), 'grand_total': fields.Float(description='年度总计'), 'settled_grand_total': fields.Float(description='已结算年度总计'), 'unsettled_grand_total': fields.Float(description='未结算年度总计') }) @work_record_ns.route('') class WorkRecordList(Resource): """Resource for listing work records with filters.""" @work_record_ns.doc('list_work_records') @work_record_ns.param('person_id', '按人员ID筛选', type=int) @work_record_ns.param('date', '按具体日期筛选 (YYYY-MM-DD)', type=str) @work_record_ns.param('year', '按年份筛选 (如 2024)', type=int) @work_record_ns.param('month', '按月份筛选 (1-12)', type=int) @work_record_ns.param('start_date', '开始日期 (YYYY-MM-DD)', type=str) @work_record_ns.param('end_date', '结束日期 (YYYY-MM-DD)', type=str) @work_record_ns.param('is_settled', '按结算状态筛选 (true/false)', type=str) @work_record_ns.param('supplier_id', '按供应商ID筛选,使用 "none" 筛选无供应商的记录', type=str) @work_record_ns.param('page', '页码,从1开始', type=int, default=1) @work_record_ns.param('page_size', '每页数量,默认20,最大100', type=int, default=20) @work_record_ns.response(200, 'Success', paginated_response) @require_auth def get(self): """获取工作记录列表(支持筛选和分页)""" person_id = request.args.get('person_id', type=int) date = request.args.get('date') year = request.args.get('year', type=int) month = request.args.get('month', type=int) start_date = request.args.get('start_date') end_date = request.args.get('end_date') page = request.args.get('page', 1, type=int) page_size = request.args.get('page_size', 20, type=int) # Parse is_settled parameter is_settled_param = request.args.get('is_settled') is_settled = None if is_settled_param is not None: is_settled = is_settled_param.lower() == 'true' # Parse supplier_id parameter supplier_id_param = request.args.get('supplier_id') supplier_id = None if supplier_id_param is not None: if supplier_id_param.lower() == 'none': supplier_id = 'none' else: try: supplier_id = int(supplier_id_param) except (ValueError, TypeError): pass # 如果指定了具体日期,使用 start_date 和 end_date 来筛选同一天 if date: start_date = date end_date = date result = WorkRecordService.get_all( person_id=person_id, start_date=start_date, end_date=end_date, year=year, month=month, is_settled=is_settled, supplier_id=supplier_id, page=page, page_size=page_size ) return { 'success': True, 'data': result['data'], 'pagination': { 'total': result['total'], 'page': result['page'], 'page_size': result['page_size'], 'total_pages': result['total_pages'] }, 'message': 'Work records retrieved successfully' }, 200 @work_record_ns.route('/') @work_record_ns.param('id', '记录ID') class WorkRecordDetail(Resource): """Resource for getting a single work record.""" @work_record_ns.doc('get_work_record') @work_record_ns.response(200, 'Success', success_response) @work_record_ns.response(404, 'Work record not found', error_response) @require_auth def get(self, id): """根据ID获取工作记录""" work_record, error = WorkRecordService.get_by_id(id) if error: return { 'success': False, 'error': error, 'code': 'NOT_FOUND' }, 404 return { 'success': True, 'data': work_record, 'message': 'Work record retrieved successfully' }, 200 @work_record_ns.route('/create') class WorkRecordCreate(Resource): """Resource for creating a work record.""" @work_record_ns.doc('create_work_record') @work_record_ns.expect(work_record_input) @work_record_ns.response(200, 'Success', success_response) @work_record_ns.response(400, 'Validation error', error_response) @work_record_ns.response(404, 'Reference not found', error_response) @require_auth def post(self): """创建新工作记录""" data = work_record_ns.payload person_id = data.get('person_id') item_id = data.get('item_id') work_date = data.get('work_date') quantity = data.get('quantity') work_record, error = WorkRecordService.create( person_id=person_id, item_id=item_id, work_date=work_date, quantity=quantity ) if error: if 'not found' in error.lower(): return { 'success': False, 'error': error, 'code': 'REFERENCE_ERROR' }, 404 return { 'success': False, 'error': error, 'code': 'VALIDATION_ERROR' }, 400 return { 'success': True, 'data': work_record, 'message': 'Work record created successfully' }, 200 @work_record_ns.route('/update') class WorkRecordUpdate(Resource): """Resource for updating a work record.""" @work_record_ns.doc('update_work_record') @work_record_ns.expect(work_record_update) @work_record_ns.response(200, 'Success', success_response) @work_record_ns.response(400, 'Validation error', error_response) @work_record_ns.response(404, 'Not found', error_response) @require_auth def post(self): """更新工作记录""" data = work_record_ns.payload work_record_id = data.get('id') if not work_record_id: return { 'success': False, 'error': 'Work record ID is required', 'code': 'VALIDATION_ERROR' }, 400 work_record, error = WorkRecordService.update( work_record_id=work_record_id, person_id=data.get('person_id'), item_id=data.get('item_id'), work_date=data.get('work_date'), quantity=data.get('quantity') ) if error: if 'not found' in error.lower(): return { 'success': False, 'error': error, 'code': 'NOT_FOUND' }, 404 return { 'success': False, 'error': error, 'code': 'VALIDATION_ERROR' }, 400 return { 'success': True, 'data': work_record, 'message': 'Work record updated successfully' }, 200 @work_record_ns.route('/delete') class WorkRecordDelete(Resource): """Resource for deleting a work record.""" @work_record_ns.doc('delete_work_record') @work_record_ns.expect(work_record_delete) @work_record_ns.response(200, 'Success', success_response) @work_record_ns.response(404, 'Work record not found', error_response) @require_auth def post(self): """删除工作记录""" data = work_record_ns.payload work_record_id = data.get('id') if not work_record_id: return { 'success': False, 'error': 'Work record ID is required', 'code': 'VALIDATION_ERROR' }, 400 success, error = WorkRecordService.delete(work_record_id) if error: return { 'success': False, 'error': error, 'code': 'NOT_FOUND' }, 404 return { 'success': True, 'data': None, 'message': 'Work record deleted successfully' }, 200 @work_record_ns.route('/daily-summary') class WorkRecordDailySummary(Resource): """Resource for getting daily summary.""" @work_record_ns.doc('get_daily_summary') @work_record_ns.param('date', '日期 (YYYY-MM-DD)', required=True, type=str) @work_record_ns.param('person_id', '按人员ID筛选', type=int) @work_record_ns.response(200, 'Success') @work_record_ns.response(400, 'Validation error', error_response) @require_auth def get(self): """获取每日工作汇总""" work_date = request.args.get('date') person_id = request.args.get('person_id', type=int) if not work_date: return { 'success': False, 'error': 'Date parameter is required', 'code': 'VALIDATION_ERROR' }, 400 try: summary = WorkRecordService.get_daily_summary( work_date=work_date, person_id=person_id ) return { 'success': True, 'data': summary, 'message': 'Daily summary retrieved successfully' }, 200 except ValueError as e: return { 'success': False, 'error': str(e), 'code': 'VALIDATION_ERROR' }, 400 @work_record_ns.route('/monthly-summary') class WorkRecordMonthlySummary(Resource): """Resource for getting monthly summary.""" @work_record_ns.doc('get_monthly_summary') @work_record_ns.param('year', '年份 (如 2024)', required=True, type=int) @work_record_ns.param('month', '月份 (1-12)', required=True, type=int) @work_record_ns.response(200, 'Success') @work_record_ns.response(400, 'Validation error', error_response) @require_auth def get(self): """获取月度工作汇总""" year = request.args.get('year', type=int) month = request.args.get('month', type=int) if not year or not month: return { 'success': False, 'error': 'Year and month parameters are required', 'code': 'VALIDATION_ERROR' }, 400 if month < 1 or month > 12: return { 'success': False, 'error': 'Month must be between 1 and 12', 'code': 'VALIDATION_ERROR' }, 400 try: summary = WorkRecordService.get_monthly_summary( year=year, month=month ) return { 'success': True, 'data': summary, 'message': 'Monthly summary retrieved successfully' }, 200 except ValueError as e: return { 'success': False, 'error': str(e), 'code': 'VALIDATION_ERROR' }, 400 @work_record_ns.route('/yearly-summary') class WorkRecordYearlySummary(Resource): """Resource for getting yearly summary with monthly breakdown.""" @work_record_ns.doc('get_yearly_summary') @work_record_ns.param('year', '年份 (如 2024)', required=True, type=int) @work_record_ns.response(200, 'Success') @work_record_ns.response(400, 'Validation error', error_response) @require_auth def get(self): """获取年度工作汇总(按月份分解)""" year = request.args.get('year', type=int) if year is None: return { 'success': False, 'error': '缺少年份参数', 'code': 'VALIDATION_ERROR' }, 400 if not isinstance(year, int) or year < 1900 or year > 9999: return { 'success': False, 'error': '年份无效,必须在 1900 到 9999 之间', 'code': 'VALIDATION_ERROR' }, 400 try: summary = WorkRecordService.get_yearly_summary(year=year) return { 'success': True, 'data': summary, 'message': 'Yearly summary retrieved successfully' }, 200 except Exception as e: return { 'success': False, 'error': str(e), 'code': 'INTERNAL_ERROR' }, 500 @work_record_ns.route('//settlement') @work_record_ns.param('id', '记录ID') class WorkRecordSettlement(Resource): """Resource for toggling settlement status of a single work record.""" @work_record_ns.doc('toggle_settlement') @work_record_ns.response(200, 'Success', success_response) @work_record_ns.response(404, 'Work record not found', error_response) @require_auth def put(self, id): """切换单条工作记录的结算状态""" work_record, error = WorkRecordService.toggle_settlement(id) if error: return { 'success': False, 'error': error, 'code': 'NOT_FOUND' }, 404 return { 'success': True, 'data': work_record, 'message': 'Settlement status toggled successfully' }, 200 @work_record_ns.route('/batch-settlement') class WorkRecordBatchSettlement(Resource): """Resource for batch updating settlement status.""" @work_record_ns.doc('batch_settlement') @work_record_ns.expect(batch_settlement_input) @work_record_ns.response(200, 'Success', success_response) @work_record_ns.response(400, 'Validation error', error_response) @require_auth def post(self): """批量更新工作记录的结算状态""" data = work_record_ns.payload year = data.get('year') month = data.get('month') is_settled = data.get('is_settled') person_id = data.get('person_id') supplier_id = data.get('supplier_id') # Handle "none" string for items without supplier if supplier_id == 'none': pass # Keep as string "none" elif supplier_id is not None: # Convert to int if it's a valid number try: supplier_id = int(supplier_id) except (ValueError, TypeError): supplier_id = None # Validate required fields if year is None or month is None: return { 'success': False, 'error': '年份和月份为必填参数', 'code': 'VALIDATION_ERROR' }, 400 if is_settled is None: return { 'success': False, 'error': '结算状态为必填参数', 'code': 'VALIDATION_ERROR' }, 400 if month < 1 or month > 12: return { 'success': False, 'error': '月份必须在 1 到 12 之间', 'code': 'VALIDATION_ERROR' }, 400 result, error = WorkRecordService.batch_update_settlement( year=year, month=month, is_settled=is_settled, person_id=person_id, supplier_id=supplier_id ) if error: return { 'success': False, 'error': error, 'code': 'VALIDATION_ERROR' }, 400 return { 'success': True, 'data': result, 'message': f'成功更新 {result["updated_count"]} 条记录的结算状态' }, 200