project_quota.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. from typing import Optional
  2. import tools.utils as utils
  3. from core.dtos import ProjectQuotaDto
  4. from core.models import ProjectQuotaModel
  5. from stores import ProjectQuotaStore, ChapterStore
  6. from core.log.log_record import LogRecordHelper
  7. from core.enum import OperationModule, OperationType, SendStatusEnum
  8. import executor
  9. class ProjectQuotaService:
  10. def __init__(self):
  11. self.store = ProjectQuotaStore()
  12. self.chapter_store = ChapterStore()
  13. self._logger = utils.get_logger()
  14. def get_quotas_paginated(
  15. self,
  16. budget_id: int,
  17. project_id: str,
  18. item_code: str,
  19. page: int = 1,
  20. page_size: int = 10,
  21. keyword: Optional[str] = None,
  22. send_status: Optional[int] = None,
  23. ):
  24. """获取项目定额列表
  25. Args:
  26. budget_id: 概算序号
  27. project_id: 项目编号
  28. item_code: 条目编号
  29. page: 页码
  30. page_size: 每页数量
  31. keyword: 关键字
  32. send_status: 发送状态
  33. Returns:
  34. dict: 包含总数和定额列表的字典
  35. """
  36. try:
  37. data = self.store.get_quotas_paginated(
  38. budget_id=budget_id,
  39. project_id=project_id,
  40. item_code=item_code,
  41. page=page,
  42. page_size=page_size,
  43. keyword=keyword,
  44. send_status=send_status,
  45. )
  46. return [
  47. ProjectQuotaDto.from_model(quota).to_dict()
  48. for quota in data.get("data", [])
  49. ], data.get("total", 0)
  50. except Exception as e:
  51. # 记录错误日志
  52. print(f"获取项目定额列表失败: {str(e)}")
  53. raise
  54. def get_quotas_by_task_paginated(
  55. self,
  56. task_id: int,
  57. budget_id,
  58. project_id,
  59. item_code,
  60. page: int = 1,
  61. page_size: int = 10,
  62. keyword: Optional[str] = None,
  63. send_status: Optional[int] = None,
  64. ):
  65. try:
  66. data = self.store.get_quotas_by_task_paginated(
  67. task_id=task_id,
  68. budget_id=budget_id,
  69. project_id=project_id,
  70. item_code=item_code,
  71. page=page,
  72. page_size=page_size,
  73. send_status=send_status,
  74. keyword=keyword,
  75. )
  76. return [
  77. ProjectQuotaDto.from_model(quota).to_dict()
  78. for quota in data.get("data", [])
  79. ], data.get("total", 0)
  80. except Exception as e:
  81. self._logger.error(f"获取任务项目定额列表失败: {str(e)}")
  82. raise
  83. def get_quota_dto(self, quota_id: int):
  84. try:
  85. return self.store.get_quota_dto(quota_id)
  86. except Exception as e:
  87. self._logger.error(f"获取定额条目DTO失败: {str(e)}")
  88. raise
  89. def get_quota(self, quota_id: int) -> Optional[ProjectQuotaModel]:
  90. """获取单个项目定额
  91. Args:
  92. quota_id: 定额ID
  93. Returns:
  94. Optional[ProjectQuotaDto]: 项目定额DTO对象
  95. """
  96. try:
  97. return self.store.get_quota(quota_id)
  98. except Exception as e:
  99. self._logger.error(f"获取定额条目失败: {str(e)}")
  100. raise
  101. def save_quota(self, quota_dto: ProjectQuotaDto) -> Optional[ProjectQuotaDto]:
  102. """保存定额"""
  103. try:
  104. # 业务验证
  105. if quota_dto.id == 0:
  106. quota_dto = self.create_quota(quota_dto)
  107. else:
  108. quota_dto = self.update_quota(quota_dto)
  109. if quota_dto.send_status != SendStatusEnum.NEW.value:
  110. self.update_send_status(quota_dto.id, SendStatusEnum.CHANGE.value)
  111. return quota_dto
  112. except Exception as e:
  113. self._logger.error(f"保存定额条目失败: {str(e)}")
  114. raise
  115. def create_quota(self, quota_dto: ProjectQuotaDto) -> ProjectQuotaDto:
  116. """创建项目定额
  117. Args:
  118. quota_dto: 定额DTO对象
  119. Returns:
  120. ProjectQuotaDto: 创建后的定额DTO对象
  121. """
  122. try:
  123. # 业务验证
  124. if not quota_dto.project_id or not quota_dto.budget_id:
  125. raise ValueError("项目编号和概算序号不能为空")
  126. LogRecordHelper.log_success(
  127. OperationType.CREATE,
  128. OperationModule.QUOTA,
  129. f"新增定额条目: {quota_dto.entry_name}",
  130. utils.to_str(quota_dto.to_dict()),
  131. )
  132. return self.store.create_quota(quota_dto)
  133. except Exception as e:
  134. self._logger.error(f"创建项目定额失败: {str(e)}")
  135. LogRecordHelper.log_fail(
  136. OperationType.CREATE,
  137. OperationModule.QUOTA,
  138. f"新增定额条目失败: {str(e)}",
  139. utils.to_str(quota_dto.to_dict()),
  140. )
  141. raise
  142. def update_quota(self, quota_dto: ProjectQuotaDto) -> Optional[ProjectQuotaDto]:
  143. """更新项目定额
  144. Args:
  145. quota_dto: 定额DTO对象
  146. Returns:
  147. Optional[ProjectQuotaDto]: 更新后的定额DTO对象
  148. """
  149. try:
  150. # 业务验证
  151. if not quota_dto.id:
  152. raise ValueError("定额ID不能为空")
  153. quota = self.get_quota_dto(quota_dto.id)
  154. log_data = utils.to_str(quota.to_dict())
  155. if quota:
  156. quota_dto.id = quota.id
  157. quota_dto.project_id = quota.project_id
  158. quota_dto.budget_id = quota.budget_id
  159. quota_dto.item_id = quota.item_id
  160. quota_dto.item_code = quota.item_code
  161. quota_dto.quota_id = quota.quota_id
  162. quota_dto.ex_file = quota.ex_file
  163. quota_dto.ex_row = quota.ex_row
  164. quota_dto.ex_cell = quota.ex_cell
  165. quota_dto.ex_unit = quota.ex_unit
  166. quota_dto.ex_amount = quota.ex_amount
  167. quota_dto.note = quota.note
  168. # self.update_process_status(quota_dto.id,4)
  169. LogRecordHelper.log_success(
  170. OperationType.UPDATE,
  171. OperationModule.QUOTA,
  172. f"修改定额条目: {quota_dto.entry_name}",
  173. log_data,
  174. utils.to_str(quota_dto.to_dict()),
  175. )
  176. return self.store.update_quota(quota_dto)
  177. except Exception as e:
  178. self._logger.error(f"更新项目定额失败: {str(e)}")
  179. LogRecordHelper.log_fail(
  180. OperationType.UPDATE,
  181. OperationModule.QUOTA,
  182. f"修改定额条目失败: {str(e)}",
  183. utils.to_str(quota_dto.to_dict()),
  184. )
  185. raise
  186. def edit_by_key(self, id: int, key: str, value: str):
  187. try:
  188. # 业务验证
  189. # dict_data = {"id": id, key: value}
  190. # quota_dto = ProjectQuotaDto(**dict_data)
  191. quota_dto = self.get_quota_dto(id)
  192. if not quota_dto:
  193. raise ValueError("未查询到定额条目")
  194. log_data = utils.to_str(quota_dto.to_dict())
  195. setattr(quota_dto, key, value)
  196. if quota_dto.is_change == 0 and key == "entry_name":
  197. quota_dto.is_change = 0
  198. else:
  199. quota_dto.is_change = 1
  200. # self.update_process_status(quota_dto.id,4)
  201. LogRecordHelper.log_success(
  202. OperationType.UPDATE,
  203. OperationModule.QUOTA,
  204. f"修改定额条目[{key}]: {value}",
  205. log_data,
  206. utils.to_str({"id": id, key: value}),
  207. )
  208. self.store.update_quota(quota_dto)
  209. return None
  210. except Exception as e:
  211. self._logger.error(f"更新项目定额失败[{key}]: {value}, {str(e)}")
  212. LogRecordHelper.log_fail(
  213. OperationType.UPDATE,
  214. OperationModule.QUOTA,
  215. f"修改定额条目失败[{key}]: {value}, {str(e)}",
  216. utils.to_str({"id": id, key: value}),
  217. )
  218. raise
  219. def edit_quota_code_batch(self, ids: str, quota_code: str):
  220. try:
  221. if not ids:
  222. return "请选择要变更的章节"
  223. ids = ids.split(",")
  224. msg = ""
  225. for id in ids:
  226. try:
  227. quota_dto = self.get_quota_dto(int(id))
  228. if not quota_dto:
  229. msg += f"{id}不存在;"
  230. continue
  231. quota_dto.quota_code = quota_code
  232. quota_dto.is_change = 1
  233. self.store.update_quota(quota_dto)
  234. except Exception as e:
  235. msg += f"{id}: {str(e)};"
  236. LogRecordHelper.log_fail(
  237. OperationType.UPDATE,
  238. OperationModule.QUOTA,
  239. f"批量修改定额: {ids}",
  240. utils.to_str({"ids": ids, "quota_code": quota_code}),
  241. )
  242. return None if msg == "" else msg
  243. except Exception as e:
  244. msg = f"批量修改定额失败: {str(e)}"
  245. self._logger.error(msg)
  246. LogRecordHelper.log_fail(
  247. OperationType.UPDATE,
  248. OperationModule.QUOTA,
  249. f"批量修改定额失败: {str(e)}",
  250. utils.to_str({"ids": ids, "quota_code": quota_code}),
  251. )
  252. return msg
  253. def delete_quota(self, quota_id: int) -> bool:
  254. """删除项目定额
  255. Args:
  256. quota_id: 定额ID
  257. Returns:
  258. bool: 删除是否成功
  259. """
  260. try:
  261. flag = self.store.delete_quota(quota_id)
  262. LogRecordHelper.log_success(
  263. OperationType.DELETE,
  264. OperationModule.QUOTA,
  265. f"删除定额条目: {quota_id}",
  266. f"{quota_id}",
  267. )
  268. return flag
  269. except Exception as e:
  270. self._logger.error(f"删除项目定额失败: {str(e)}")
  271. LogRecordHelper.log_fail(
  272. OperationType.DELETE,
  273. OperationModule.QUOTA,
  274. f"删除定额条目失败: {quota_id}",
  275. f"{quota_id}",
  276. )
  277. raise
  278. def save_change_chapter(self, project_id, ids, new_id):
  279. try:
  280. if not ids:
  281. return "请选择要变更的章节"
  282. chapter = self.chapter_store.get_chapter_dto(project_id, new_id)
  283. if not chapter:
  284. return "章节不存在"
  285. ids_arr = ids.split(",")
  286. err = ""
  287. for id_str in ids_arr:
  288. try:
  289. self.store.update_quota_chapter(
  290. int(id_str), chapter.item_id, chapter.item_code
  291. )
  292. except Exception as e:
  293. err += str(e) + ";"
  294. continue
  295. if err:
  296. LogRecordHelper.log_fail(
  297. OperationType.UPDATE,
  298. OperationModule.QUOTA,
  299. f"修改定额条目章节失败: {err}",
  300. ids,
  301. )
  302. return err
  303. LogRecordHelper.log_success(
  304. OperationType.UPDATE,
  305. OperationModule.QUOTA,
  306. f"修改定额条目章节",
  307. ids,
  308. )
  309. return None
  310. except Exception as e:
  311. msg = f"章节变更失败: {str(e)}"
  312. self._logger.error(msg)
  313. LogRecordHelper.log_fail(
  314. OperationType.UPDATE,
  315. OperationModule.QUOTA,
  316. f"修改定额条目章节失败: {str(e)}",
  317. ids,
  318. )
  319. return msg
  320. def update_send_status(self, quota_id: int, status: int, err: str = None) -> bool:
  321. """更新发送状态
  322. Args:
  323. quota_id: 定额ID
  324. status: 状态值
  325. err: 错误信息
  326. Returns:
  327. bool: 更新是否成功
  328. """
  329. try:
  330. return self.store.update_send_status(quota_id, status, err)
  331. except Exception as e:
  332. self._logger.error(f"更新项目定额发送状态失败: {str(e)}")
  333. raise
  334. # def start_process(self, quota_id: int) -> Optional[str]:
  335. # """启动处理"""
  336. # quota = self.get_quota_dto(quota_id)
  337. # if quota:
  338. # self.update_process_status(quota_id, 1)
  339. # thread = threading.Thread(target=self._process_quota, args=(quota,))
  340. # thread.start()
  341. # else:
  342. # return "定额条目没有查询到"
  343. #
  344. # def _process_quota(self, quota: ProjectQuotaDto):
  345. # try:
  346. # msg = executor.process_quota(quota)
  347. # if not msg:
  348. # self.start_send(quota.id)
  349. # except Exception as e:
  350. # self._logger.error(f"处理定额条目失败: {str(e)}")
  351. # self.update_process_status(quota.id, 3, str(e))
  352. # raise
  353. def start_send_by_ids(self, ids: str, is_cover: bool = False):
  354. try:
  355. id_list = ids.split(",")
  356. err = ""
  357. for _id in id_list:
  358. msg = self.start_send(int(_id), is_cover, False)
  359. if msg:
  360. err += f"{msg}[{_id}],"
  361. if err:
  362. LogRecordHelper.log_fail(
  363. OperationType.SEND,
  364. OperationModule.QUOTA,
  365. f"批量推送定额条目,部分失败: {err}",
  366. ids,
  367. )
  368. LogRecordHelper.log_success(
  369. OperationType.SEND,
  370. OperationModule.QUOTA,
  371. f"批量推送定额条目",
  372. ids,
  373. )
  374. return err
  375. except Exception as e:
  376. LogRecordHelper.log_fail(
  377. OperationType.SEND,
  378. OperationModule.QUOTA,
  379. f"批量推送定额条目失败: {str(e)}",
  380. ids,
  381. )
  382. self._logger.error(f"批量启动定额条目发送失败: {str(e)}")
  383. raise
  384. def start_send(
  385. self, _id: int, is_cover: bool = False, is_log: bool = True
  386. ) -> Optional[str]:
  387. """启动发送"""
  388. quota = self.get_quota_dto(_id)
  389. if quota:
  390. self.update_send_status(_id, SendStatusEnum.SUCCESS.value)
  391. if not is_cover:
  392. quota.quota_id = 0
  393. # thread = threading.Thread(target=self._send_quota, args=(quota,))
  394. # thread.start()
  395. if is_log:
  396. LogRecordHelper.log_success(
  397. OperationType.SEND,
  398. OperationModule.QUOTA,
  399. f"推送定额条目",
  400. f"ID:{_id},是否覆盖:{is_cover}",
  401. )
  402. if self._send_quota(quota):
  403. return None
  404. else:
  405. return f"{_id}发送失败;"
  406. else:
  407. return "定额条目没有查询到"
  408. def _send_quota(self, quota: ProjectQuotaDto):
  409. try:
  410. executor.send_quota(quota)
  411. return True
  412. except Exception as e:
  413. self._logger.error(f"发送定额条目失败: {str(e)}")
  414. self.update_send_status(quota.id, 3, str(e))
  415. return False