| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 |
- """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='匹配的总记录数')
- })
- # Response models
- success_response = work_record_ns.model('SuccessResponse', {
- 'success': fields.Boolean(description='操作是否成功'),
- 'data': fields.Raw(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.response(200, 'Success', success_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')
-
- # 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'
-
- # 如果指定了具体日期,使用 start_date 和 end_date 来筛选同一天
- if date:
- start_date = date
- end_date = date
-
- work_records = WorkRecordService.get_all(
- person_id=person_id,
- start_date=start_date,
- end_date=end_date,
- year=year,
- month=month,
- is_settled=is_settled
- )
- return {
- 'success': True,
- 'data': work_records,
- 'message': 'Work records retrieved successfully'
- }, 200
- @work_record_ns.route('/<int:id>')
- @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('/<int:id>/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
|