work_record.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. """Work Record API routes."""
  2. from flask import request
  3. from flask_restx import Namespace, Resource, fields
  4. from app.services.work_record_service import WorkRecordService
  5. from app.utils.auth_decorator import require_auth
  6. work_record_ns = Namespace('work-records', description='工作记录管理接口')
  7. # API models for Swagger documentation
  8. work_record_model = work_record_ns.model('WorkRecord', {
  9. 'id': fields.Integer(readonly=True, description='记录ID'),
  10. 'person_id': fields.Integer(required=True, description='人员ID'),
  11. 'person_name': fields.String(readonly=True, description='人员姓名'),
  12. 'item_id': fields.Integer(required=True, description='物品ID'),
  13. 'item_name': fields.String(readonly=True, description='物品名称'),
  14. 'unit_price': fields.Float(readonly=True, description='单价'),
  15. 'work_date': fields.String(required=True, description='工作日期 (YYYY-MM-DD)'),
  16. 'quantity': fields.Integer(required=True, description='数量'),
  17. 'total_price': fields.Float(readonly=True, description='总价'),
  18. 'created_at': fields.String(readonly=True, description='创建时间'),
  19. 'updated_at': fields.String(readonly=True, description='更新时间')
  20. })
  21. work_record_input = work_record_ns.model('WorkRecordInput', {
  22. 'person_id': fields.Integer(required=True, description='人员ID'),
  23. 'item_id': fields.Integer(required=True, description='物品ID'),
  24. 'work_date': fields.String(required=True, description='工作日期 (YYYY-MM-DD)'),
  25. 'quantity': fields.Integer(required=True, description='数量')
  26. })
  27. work_record_update = work_record_ns.model('WorkRecordUpdate', {
  28. 'id': fields.Integer(required=True, description='记录ID'),
  29. 'person_id': fields.Integer(description='人员ID'),
  30. 'item_id': fields.Integer(description='物品ID'),
  31. 'work_date': fields.String(description='工作日期 (YYYY-MM-DD)'),
  32. 'quantity': fields.Integer(description='数量')
  33. })
  34. work_record_delete = work_record_ns.model('WorkRecordDelete', {
  35. 'id': fields.Integer(required=True, description='记录ID')
  36. })
  37. # Response models
  38. success_response = work_record_ns.model('SuccessResponse', {
  39. 'success': fields.Boolean(description='操作是否成功'),
  40. 'data': fields.Raw(description='返回数据'),
  41. 'message': fields.String(description='消息')
  42. })
  43. error_response = work_record_ns.model('ErrorResponse', {
  44. 'success': fields.Boolean(description='操作是否成功'),
  45. 'error': fields.String(description='错误信息'),
  46. 'code': fields.String(description='错误代码')
  47. })
  48. # Daily summary models
  49. daily_summary_item = work_record_ns.model('DailySummaryItem', {
  50. 'item_name': fields.String(description='物品名称'),
  51. 'unit_price': fields.Float(description='单价'),
  52. 'quantity': fields.Integer(description='数量'),
  53. 'total_price': fields.Float(description='总价')
  54. })
  55. daily_summary_person = work_record_ns.model('DailySummaryPerson', {
  56. 'person_id': fields.Integer(description='人员ID'),
  57. 'person_name': fields.String(description='人员姓名'),
  58. 'total_items': fields.Integer(description='总数量'),
  59. 'total_value': fields.Float(description='总金额'),
  60. 'items': fields.List(fields.Nested(daily_summary_item), description='物品明细')
  61. })
  62. daily_summary_response = work_record_ns.model('DailySummaryResponse', {
  63. 'date': fields.String(description='日期'),
  64. 'summary': fields.List(fields.Nested(daily_summary_person), description='人员汇总'),
  65. 'grand_total_items': fields.Integer(description='总数量'),
  66. 'grand_total_value': fields.Float(description='总金额')
  67. })
  68. # Monthly summary models
  69. monthly_top_performer = work_record_ns.model('MonthlyTopPerformer', {
  70. 'person_id': fields.Integer(description='人员ID'),
  71. 'person_name': fields.String(description='人员姓名'),
  72. 'earnings': fields.Float(description='收入')
  73. })
  74. monthly_item_breakdown = work_record_ns.model('MonthlyItemBreakdown', {
  75. 'item_id': fields.Integer(description='物品ID'),
  76. 'item_name': fields.String(description='物品名称'),
  77. 'quantity': fields.Integer(description='数量'),
  78. 'earnings': fields.Float(description='收入')
  79. })
  80. monthly_summary_response = work_record_ns.model('MonthlySummaryResponse', {
  81. 'year': fields.Integer(description='年份'),
  82. 'month': fields.Integer(description='月份'),
  83. 'total_records': fields.Integer(description='总记录数'),
  84. 'total_earnings': fields.Float(description='总收入'),
  85. 'top_performers': fields.List(fields.Nested(monthly_top_performer), description='业绩排名'),
  86. 'item_breakdown': fields.List(fields.Nested(monthly_item_breakdown), description='物品收入明细')
  87. })
  88. @work_record_ns.route('')
  89. class WorkRecordList(Resource):
  90. """Resource for listing work records with filters."""
  91. @work_record_ns.doc('list_work_records')
  92. @work_record_ns.param('person_id', '按人员ID筛选', type=int)
  93. @work_record_ns.param('date', '按具体日期筛选 (YYYY-MM-DD)', type=str)
  94. @work_record_ns.param('year', '按年份筛选 (如 2024)', type=int)
  95. @work_record_ns.param('month', '按月份筛选 (1-12)', type=int)
  96. @work_record_ns.param('start_date', '开始日期 (YYYY-MM-DD)', type=str)
  97. @work_record_ns.param('end_date', '结束日期 (YYYY-MM-DD)', type=str)
  98. @work_record_ns.response(200, 'Success', success_response)
  99. @require_auth
  100. def get(self):
  101. """获取工作记录列表(支持筛选)"""
  102. person_id = request.args.get('person_id', type=int)
  103. date = request.args.get('date')
  104. year = request.args.get('year', type=int)
  105. month = request.args.get('month', type=int)
  106. start_date = request.args.get('start_date')
  107. end_date = request.args.get('end_date')
  108. # 如果指定了具体日期,使用 start_date 和 end_date 来筛选同一天
  109. if date:
  110. start_date = date
  111. end_date = date
  112. work_records = WorkRecordService.get_all(
  113. person_id=person_id,
  114. start_date=start_date,
  115. end_date=end_date,
  116. year=year,
  117. month=month
  118. )
  119. return {
  120. 'success': True,
  121. 'data': work_records,
  122. 'message': 'Work records retrieved successfully'
  123. }, 200
  124. @work_record_ns.route('/<int:id>')
  125. @work_record_ns.param('id', '记录ID')
  126. class WorkRecordDetail(Resource):
  127. """Resource for getting a single work record."""
  128. @work_record_ns.doc('get_work_record')
  129. @work_record_ns.response(200, 'Success', success_response)
  130. @work_record_ns.response(404, 'Work record not found', error_response)
  131. @require_auth
  132. def get(self, id):
  133. """根据ID获取工作记录"""
  134. work_record, error = WorkRecordService.get_by_id(id)
  135. if error:
  136. return {
  137. 'success': False,
  138. 'error': error,
  139. 'code': 'NOT_FOUND'
  140. }, 404
  141. return {
  142. 'success': True,
  143. 'data': work_record,
  144. 'message': 'Work record retrieved successfully'
  145. }, 200
  146. @work_record_ns.route('/create')
  147. class WorkRecordCreate(Resource):
  148. """Resource for creating a work record."""
  149. @work_record_ns.doc('create_work_record')
  150. @work_record_ns.expect(work_record_input)
  151. @work_record_ns.response(200, 'Success', success_response)
  152. @work_record_ns.response(400, 'Validation error', error_response)
  153. @work_record_ns.response(404, 'Reference not found', error_response)
  154. @require_auth
  155. def post(self):
  156. """创建新工作记录"""
  157. data = work_record_ns.payload
  158. person_id = data.get('person_id')
  159. item_id = data.get('item_id')
  160. work_date = data.get('work_date')
  161. quantity = data.get('quantity')
  162. work_record, error = WorkRecordService.create(
  163. person_id=person_id,
  164. item_id=item_id,
  165. work_date=work_date,
  166. quantity=quantity
  167. )
  168. if error:
  169. if 'not found' in error.lower():
  170. return {
  171. 'success': False,
  172. 'error': error,
  173. 'code': 'REFERENCE_ERROR'
  174. }, 404
  175. return {
  176. 'success': False,
  177. 'error': error,
  178. 'code': 'VALIDATION_ERROR'
  179. }, 400
  180. return {
  181. 'success': True,
  182. 'data': work_record,
  183. 'message': 'Work record created successfully'
  184. }, 200
  185. @work_record_ns.route('/update')
  186. class WorkRecordUpdate(Resource):
  187. """Resource for updating a work record."""
  188. @work_record_ns.doc('update_work_record')
  189. @work_record_ns.expect(work_record_update)
  190. @work_record_ns.response(200, 'Success', success_response)
  191. @work_record_ns.response(400, 'Validation error', error_response)
  192. @work_record_ns.response(404, 'Not found', error_response)
  193. @require_auth
  194. def post(self):
  195. """更新工作记录"""
  196. data = work_record_ns.payload
  197. work_record_id = data.get('id')
  198. if not work_record_id:
  199. return {
  200. 'success': False,
  201. 'error': 'Work record ID is required',
  202. 'code': 'VALIDATION_ERROR'
  203. }, 400
  204. work_record, error = WorkRecordService.update(
  205. work_record_id=work_record_id,
  206. person_id=data.get('person_id'),
  207. item_id=data.get('item_id'),
  208. work_date=data.get('work_date'),
  209. quantity=data.get('quantity')
  210. )
  211. if error:
  212. if 'not found' in error.lower():
  213. return {
  214. 'success': False,
  215. 'error': error,
  216. 'code': 'NOT_FOUND'
  217. }, 404
  218. return {
  219. 'success': False,
  220. 'error': error,
  221. 'code': 'VALIDATION_ERROR'
  222. }, 400
  223. return {
  224. 'success': True,
  225. 'data': work_record,
  226. 'message': 'Work record updated successfully'
  227. }, 200
  228. @work_record_ns.route('/delete')
  229. class WorkRecordDelete(Resource):
  230. """Resource for deleting a work record."""
  231. @work_record_ns.doc('delete_work_record')
  232. @work_record_ns.expect(work_record_delete)
  233. @work_record_ns.response(200, 'Success', success_response)
  234. @work_record_ns.response(404, 'Work record not found', error_response)
  235. @require_auth
  236. def post(self):
  237. """删除工作记录"""
  238. data = work_record_ns.payload
  239. work_record_id = data.get('id')
  240. if not work_record_id:
  241. return {
  242. 'success': False,
  243. 'error': 'Work record ID is required',
  244. 'code': 'VALIDATION_ERROR'
  245. }, 400
  246. success, error = WorkRecordService.delete(work_record_id)
  247. if error:
  248. return {
  249. 'success': False,
  250. 'error': error,
  251. 'code': 'NOT_FOUND'
  252. }, 404
  253. return {
  254. 'success': True,
  255. 'data': None,
  256. 'message': 'Work record deleted successfully'
  257. }, 200
  258. @work_record_ns.route('/daily-summary')
  259. class WorkRecordDailySummary(Resource):
  260. """Resource for getting daily summary."""
  261. @work_record_ns.doc('get_daily_summary')
  262. @work_record_ns.param('date', '日期 (YYYY-MM-DD)', required=True, type=str)
  263. @work_record_ns.param('person_id', '按人员ID筛选', type=int)
  264. @work_record_ns.response(200, 'Success')
  265. @work_record_ns.response(400, 'Validation error', error_response)
  266. @require_auth
  267. def get(self):
  268. """获取每日工作汇总"""
  269. work_date = request.args.get('date')
  270. person_id = request.args.get('person_id', type=int)
  271. if not work_date:
  272. return {
  273. 'success': False,
  274. 'error': 'Date parameter is required',
  275. 'code': 'VALIDATION_ERROR'
  276. }, 400
  277. try:
  278. summary = WorkRecordService.get_daily_summary(
  279. work_date=work_date,
  280. person_id=person_id
  281. )
  282. return {
  283. 'success': True,
  284. 'data': summary,
  285. 'message': 'Daily summary retrieved successfully'
  286. }, 200
  287. except ValueError as e:
  288. return {
  289. 'success': False,
  290. 'error': str(e),
  291. 'code': 'VALIDATION_ERROR'
  292. }, 400
  293. @work_record_ns.route('/monthly-summary')
  294. class WorkRecordMonthlySummary(Resource):
  295. """Resource for getting monthly summary."""
  296. @work_record_ns.doc('get_monthly_summary')
  297. @work_record_ns.param('year', '年份 (如 2024)', required=True, type=int)
  298. @work_record_ns.param('month', '月份 (1-12)', required=True, type=int)
  299. @work_record_ns.response(200, 'Success')
  300. @work_record_ns.response(400, 'Validation error', error_response)
  301. @require_auth
  302. def get(self):
  303. """获取月度工作汇总"""
  304. year = request.args.get('year', type=int)
  305. month = request.args.get('month', type=int)
  306. if not year or not month:
  307. return {
  308. 'success': False,
  309. 'error': 'Year and month parameters are required',
  310. 'code': 'VALIDATION_ERROR'
  311. }, 400
  312. if month < 1 or month > 12:
  313. return {
  314. 'success': False,
  315. 'error': 'Month must be between 1 and 12',
  316. 'code': 'VALIDATION_ERROR'
  317. }, 400
  318. try:
  319. summary = WorkRecordService.get_monthly_summary(
  320. year=year,
  321. month=month
  322. )
  323. return {
  324. 'success': True,
  325. 'data': summary,
  326. 'message': 'Monthly summary retrieved successfully'
  327. }, 200
  328. except ValueError as e:
  329. return {
  330. 'success': False,
  331. 'error': str(e),
  332. 'code': 'VALIDATION_ERROR'
  333. }, 400