| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- """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='单价'),
- 'work_date': fields.String(required=True, description='工作日期 (YYYY-MM-DD)'),
- 'quantity': fields.Integer(required=True, description='数量'),
- 'total_price': fields.Float(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')
- })
- # 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='物品名称'),
- '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='物品名称'),
- 'quantity': fields.Integer(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='物品收入明细')
- })
- # 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='年度总收入')
- })
- 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='年度总计')
- })
- @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.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')
-
- # 如果指定了具体日期,使用 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
- )
- 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
|