work_record.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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. # Yearly summary models
  89. yearly_summary_person = work_record_ns.model('YearlySummaryPerson', {
  90. 'person_id': fields.Integer(description='人员ID'),
  91. 'person_name': fields.String(description='人员姓名'),
  92. 'monthly_earnings': fields.List(fields.Float, description='12个月的收入数组'),
  93. 'yearly_total': fields.Float(description='年度总收入')
  94. })
  95. yearly_summary_response = work_record_ns.model('YearlySummaryResponse', {
  96. 'year': fields.Integer(description='年份'),
  97. 'persons': fields.List(fields.Nested(yearly_summary_person), description='人员汇总列表'),
  98. 'monthly_totals': fields.List(fields.Float, description='每月所有人的合计'),
  99. 'grand_total': fields.Float(description='年度总计')
  100. })
  101. @work_record_ns.route('')
  102. class WorkRecordList(Resource):
  103. """Resource for listing work records with filters."""
  104. @work_record_ns.doc('list_work_records')
  105. @work_record_ns.param('person_id', '按人员ID筛选', type=int)
  106. @work_record_ns.param('date', '按具体日期筛选 (YYYY-MM-DD)', type=str)
  107. @work_record_ns.param('year', '按年份筛选 (如 2024)', type=int)
  108. @work_record_ns.param('month', '按月份筛选 (1-12)', type=int)
  109. @work_record_ns.param('start_date', '开始日期 (YYYY-MM-DD)', type=str)
  110. @work_record_ns.param('end_date', '结束日期 (YYYY-MM-DD)', type=str)
  111. @work_record_ns.response(200, 'Success', success_response)
  112. @require_auth
  113. def get(self):
  114. """获取工作记录列表(支持筛选)"""
  115. person_id = request.args.get('person_id', type=int)
  116. date = request.args.get('date')
  117. year = request.args.get('year', type=int)
  118. month = request.args.get('month', type=int)
  119. start_date = request.args.get('start_date')
  120. end_date = request.args.get('end_date')
  121. # 如果指定了具体日期,使用 start_date 和 end_date 来筛选同一天
  122. if date:
  123. start_date = date
  124. end_date = date
  125. work_records = WorkRecordService.get_all(
  126. person_id=person_id,
  127. start_date=start_date,
  128. end_date=end_date,
  129. year=year,
  130. month=month
  131. )
  132. return {
  133. 'success': True,
  134. 'data': work_records,
  135. 'message': 'Work records retrieved successfully'
  136. }, 200
  137. @work_record_ns.route('/<int:id>')
  138. @work_record_ns.param('id', '记录ID')
  139. class WorkRecordDetail(Resource):
  140. """Resource for getting a single work record."""
  141. @work_record_ns.doc('get_work_record')
  142. @work_record_ns.response(200, 'Success', success_response)
  143. @work_record_ns.response(404, 'Work record not found', error_response)
  144. @require_auth
  145. def get(self, id):
  146. """根据ID获取工作记录"""
  147. work_record, error = WorkRecordService.get_by_id(id)
  148. if error:
  149. return {
  150. 'success': False,
  151. 'error': error,
  152. 'code': 'NOT_FOUND'
  153. }, 404
  154. return {
  155. 'success': True,
  156. 'data': work_record,
  157. 'message': 'Work record retrieved successfully'
  158. }, 200
  159. @work_record_ns.route('/create')
  160. class WorkRecordCreate(Resource):
  161. """Resource for creating a work record."""
  162. @work_record_ns.doc('create_work_record')
  163. @work_record_ns.expect(work_record_input)
  164. @work_record_ns.response(200, 'Success', success_response)
  165. @work_record_ns.response(400, 'Validation error', error_response)
  166. @work_record_ns.response(404, 'Reference not found', error_response)
  167. @require_auth
  168. def post(self):
  169. """创建新工作记录"""
  170. data = work_record_ns.payload
  171. person_id = data.get('person_id')
  172. item_id = data.get('item_id')
  173. work_date = data.get('work_date')
  174. quantity = data.get('quantity')
  175. work_record, error = WorkRecordService.create(
  176. person_id=person_id,
  177. item_id=item_id,
  178. work_date=work_date,
  179. quantity=quantity
  180. )
  181. if error:
  182. if 'not found' in error.lower():
  183. return {
  184. 'success': False,
  185. 'error': error,
  186. 'code': 'REFERENCE_ERROR'
  187. }, 404
  188. return {
  189. 'success': False,
  190. 'error': error,
  191. 'code': 'VALIDATION_ERROR'
  192. }, 400
  193. return {
  194. 'success': True,
  195. 'data': work_record,
  196. 'message': 'Work record created successfully'
  197. }, 200
  198. @work_record_ns.route('/update')
  199. class WorkRecordUpdate(Resource):
  200. """Resource for updating a work record."""
  201. @work_record_ns.doc('update_work_record')
  202. @work_record_ns.expect(work_record_update)
  203. @work_record_ns.response(200, 'Success', success_response)
  204. @work_record_ns.response(400, 'Validation error', error_response)
  205. @work_record_ns.response(404, 'Not found', error_response)
  206. @require_auth
  207. def post(self):
  208. """更新工作记录"""
  209. data = work_record_ns.payload
  210. work_record_id = data.get('id')
  211. if not work_record_id:
  212. return {
  213. 'success': False,
  214. 'error': 'Work record ID is required',
  215. 'code': 'VALIDATION_ERROR'
  216. }, 400
  217. work_record, error = WorkRecordService.update(
  218. work_record_id=work_record_id,
  219. person_id=data.get('person_id'),
  220. item_id=data.get('item_id'),
  221. work_date=data.get('work_date'),
  222. quantity=data.get('quantity')
  223. )
  224. if error:
  225. if 'not found' in error.lower():
  226. return {
  227. 'success': False,
  228. 'error': error,
  229. 'code': 'NOT_FOUND'
  230. }, 404
  231. return {
  232. 'success': False,
  233. 'error': error,
  234. 'code': 'VALIDATION_ERROR'
  235. }, 400
  236. return {
  237. 'success': True,
  238. 'data': work_record,
  239. 'message': 'Work record updated successfully'
  240. }, 200
  241. @work_record_ns.route('/delete')
  242. class WorkRecordDelete(Resource):
  243. """Resource for deleting a work record."""
  244. @work_record_ns.doc('delete_work_record')
  245. @work_record_ns.expect(work_record_delete)
  246. @work_record_ns.response(200, 'Success', success_response)
  247. @work_record_ns.response(404, 'Work record not found', error_response)
  248. @require_auth
  249. def post(self):
  250. """删除工作记录"""
  251. data = work_record_ns.payload
  252. work_record_id = data.get('id')
  253. if not work_record_id:
  254. return {
  255. 'success': False,
  256. 'error': 'Work record ID is required',
  257. 'code': 'VALIDATION_ERROR'
  258. }, 400
  259. success, error = WorkRecordService.delete(work_record_id)
  260. if error:
  261. return {
  262. 'success': False,
  263. 'error': error,
  264. 'code': 'NOT_FOUND'
  265. }, 404
  266. return {
  267. 'success': True,
  268. 'data': None,
  269. 'message': 'Work record deleted successfully'
  270. }, 200
  271. @work_record_ns.route('/daily-summary')
  272. class WorkRecordDailySummary(Resource):
  273. """Resource for getting daily summary."""
  274. @work_record_ns.doc('get_daily_summary')
  275. @work_record_ns.param('date', '日期 (YYYY-MM-DD)', required=True, type=str)
  276. @work_record_ns.param('person_id', '按人员ID筛选', type=int)
  277. @work_record_ns.response(200, 'Success')
  278. @work_record_ns.response(400, 'Validation error', error_response)
  279. @require_auth
  280. def get(self):
  281. """获取每日工作汇总"""
  282. work_date = request.args.get('date')
  283. person_id = request.args.get('person_id', type=int)
  284. if not work_date:
  285. return {
  286. 'success': False,
  287. 'error': 'Date parameter is required',
  288. 'code': 'VALIDATION_ERROR'
  289. }, 400
  290. try:
  291. summary = WorkRecordService.get_daily_summary(
  292. work_date=work_date,
  293. person_id=person_id
  294. )
  295. return {
  296. 'success': True,
  297. 'data': summary,
  298. 'message': 'Daily summary retrieved successfully'
  299. }, 200
  300. except ValueError as e:
  301. return {
  302. 'success': False,
  303. 'error': str(e),
  304. 'code': 'VALIDATION_ERROR'
  305. }, 400
  306. @work_record_ns.route('/monthly-summary')
  307. class WorkRecordMonthlySummary(Resource):
  308. """Resource for getting monthly summary."""
  309. @work_record_ns.doc('get_monthly_summary')
  310. @work_record_ns.param('year', '年份 (如 2024)', required=True, type=int)
  311. @work_record_ns.param('month', '月份 (1-12)', required=True, type=int)
  312. @work_record_ns.response(200, 'Success')
  313. @work_record_ns.response(400, 'Validation error', error_response)
  314. @require_auth
  315. def get(self):
  316. """获取月度工作汇总"""
  317. year = request.args.get('year', type=int)
  318. month = request.args.get('month', type=int)
  319. if not year or not month:
  320. return {
  321. 'success': False,
  322. 'error': 'Year and month parameters are required',
  323. 'code': 'VALIDATION_ERROR'
  324. }, 400
  325. if month < 1 or month > 12:
  326. return {
  327. 'success': False,
  328. 'error': 'Month must be between 1 and 12',
  329. 'code': 'VALIDATION_ERROR'
  330. }, 400
  331. try:
  332. summary = WorkRecordService.get_monthly_summary(
  333. year=year,
  334. month=month
  335. )
  336. return {
  337. 'success': True,
  338. 'data': summary,
  339. 'message': 'Monthly summary retrieved successfully'
  340. }, 200
  341. except ValueError as e:
  342. return {
  343. 'success': False,
  344. 'error': str(e),
  345. 'code': 'VALIDATION_ERROR'
  346. }, 400
  347. @work_record_ns.route('/yearly-summary')
  348. class WorkRecordYearlySummary(Resource):
  349. """Resource for getting yearly summary with monthly breakdown."""
  350. @work_record_ns.doc('get_yearly_summary')
  351. @work_record_ns.param('year', '年份 (如 2024)', required=True, type=int)
  352. @work_record_ns.response(200, 'Success')
  353. @work_record_ns.response(400, 'Validation error', error_response)
  354. @require_auth
  355. def get(self):
  356. """获取年度工作汇总(按月份分解)"""
  357. year = request.args.get('year', type=int)
  358. if year is None:
  359. return {
  360. 'success': False,
  361. 'error': '缺少年份参数',
  362. 'code': 'VALIDATION_ERROR'
  363. }, 400
  364. if not isinstance(year, int) or year < 1900 or year > 9999:
  365. return {
  366. 'success': False,
  367. 'error': '年份无效,必须在 1900 到 9999 之间',
  368. 'code': 'VALIDATION_ERROR'
  369. }, 400
  370. try:
  371. summary = WorkRecordService.get_yearly_summary(year=year)
  372. return {
  373. 'success': True,
  374. 'data': summary,
  375. 'message': 'Yearly summary retrieved successfully'
  376. }, 200
  377. except Exception as e:
  378. return {
  379. 'success': False,
  380. 'error': str(e),
  381. 'code': 'INTERNAL_ERROR'
  382. }, 500