flexbiz-server 12.6.13 → 12.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +439 -4
  2. package/package.json +4 -3
  3. package/server/agents/agents.js +1 -0
  4. package/server/agents/ai_memory_manager.js +6 -0
  5. package/server/agents/ceoAgent.js +43 -0
  6. package/server/agents/chatController.js +6 -0
  7. package/server/agents/cskhAgent.js +43 -0
  8. package/server/agents/flowAgent.js +43 -0
  9. package/server/agents/prompts/ceo.js +2 -0
  10. package/server/agents/prompts/flow.js +2 -0
  11. package/server/agents/staffAgent.js +44 -0
  12. package/server/agents/tools/financeTools.js +57 -0
  13. package/server/agents/tools/flowTools.js +36 -0
  14. package/server/agents/tools/kpiTools.js +104 -0
  15. package/server/agents/tools/masterDataTools.js +74 -0
  16. package/server/agents/tools/okrTools.js +21 -0
  17. package/server/agents/tools/ragTools.js +21 -0
  18. package/server/agents/tools/reportTools.js +43 -0
  19. package/server/agents/tools/staffTools.js +13 -0
  20. package/server/agents/tools/stockTools.js +41 -0
  21. package/server/agents/tools/systemTools.js +52 -0
  22. package/server/agents/tools/taskTools.js +23 -0
  23. package/server/agents/tools/utils.js +57 -0
  24. package/server/app.js +7 -7
  25. package/server/controllers/controller.js +31 -29
  26. package/server/controllers/controllerRPT.js +31 -29
  27. package/server/controllers/findHandler.js +22 -21
  28. package/server/controllers/importFromExcelHandler.js +17 -14
  29. package/server/controllers/importHandler.js +26 -41
  30. package/server/controllers/importHandler_old.js +41 -0
  31. package/server/controllers/rptHandler.js +14 -11
  32. package/server/controllers/viewHandler.js +7 -7
  33. package/server/flow/FlowEngine.js +49 -45
  34. package/server/flow/WebhookManager.js +1 -1
  35. package/server/flow/handlers.js +2 -2
  36. package/server/flow/nodes/userform.js +28 -0
  37. package/server/flow/utils.js +12 -4
  38. package/server/global.js +4 -3
  39. package/server/libs/cdpstk.js +1 -1
  40. package/server/libs/ckvt.js +12 -11
  41. package/server/libs/ckvtcapphat.js +5 -5
  42. package/server/libs/ckvtnpp.js +8 -8
  43. package/server/libs/ckvttt.js +6 -5
  44. package/server/libs/ctvt.js +7 -6
  45. package/server/libs/ctvtnpp.js +7 -6
  46. package/server/libs/ctvttt.js +6 -6
  47. package/server/libs/databanle.js +17 -18
  48. package/server/libs/databanle_old.js +18 -0
  49. package/server/libs/databanletheongay.js +25 -29
  50. package/server/libs/dktk.js +3 -3
  51. package/server/libs/dkvt.js +11 -11
  52. package/server/libs/dkvtnpp.js +7 -6
  53. package/server/libs/dkvttt.js +7 -6
  54. package/server/libs/docs.js +72 -36
  55. package/server/libs/dtbanletheosp.js +16 -19
  56. package/server/libs/fifo_dk.js +8 -0
  57. package/server/libs/fifo_group.js +4 -0
  58. package/server/libs/fifo_trace_query.js +12 -0
  59. package/server/libs/getGiaban.js +59 -70
  60. package/server/libs/getGiaban_old.js +70 -0
  61. package/server/libs/joinData.js +3 -3
  62. package/server/libs/joinData_bk.js +44 -0
  63. package/server/libs/kpistatus.js +24 -17
  64. package/server/libs/permission.js +54 -56
  65. package/server/libs/permission_old.js +58 -0
  66. package/server/libs/post-book.js +1 -1
  67. package/server/libs/post-socai.js +2 -1
  68. package/server/libs/post-sokho.js +11 -11
  69. package/server/libs/prototypes.js +13 -12
  70. package/server/libs/queryBuilding.js +11 -10
  71. package/server/libs/redis-cache.js +22 -10
  72. package/server/libs/sctcnkh.js +19 -17
  73. package/server/libs/socaitk.js +1 -1
  74. package/server/libs/thnxt.js +17 -14
  75. package/server/libs/thnxtnpp.js +8 -8
  76. package/server/libs/thnxttt.js +8 -7
  77. package/server/libs/tinhgia1vt_fifo.js +22 -0
  78. package/server/libs/tinhgiafifo.js +28 -0
  79. package/server/libs/utils.js +37 -19
  80. package/server/models/app.js +1 -1
  81. package/server/models/approve.js +8 -8
  82. package/server/models/cacheDataReport.js +2 -2
  83. package/server/models/cacheSessionReport.js +5 -1
  84. package/server/models/cdvt.js +2 -2
  85. package/server/models/checkin.js +2 -2
  86. package/server/models/chitietluongds.js +1 -1
  87. package/server/models/contract.js +1 -1
  88. package/server/models/csluongtheods.js +2 -2
  89. package/server/models/ctds2.js +7 -6
  90. package/server/models/custom_report.js +1 -0
  91. package/server/models/dmdt.js +2 -2
  92. package/server/models/dmkho.js +5 -4
  93. package/server/models/dmnvt.js +5 -4
  94. package/server/models/fifo_dk.js +2 -0
  95. package/server/models/fifo_lot.js +3 -0
  96. package/server/models/fifo_trace.js +4 -0
  97. package/server/models/flowpending.js +2 -0
  98. package/server/models/group.js +3 -5
  99. package/server/models/listinfo.js +61 -33
  100. package/server/models/schedule.js +4 -3
  101. package/server/models/task.js +1 -1
  102. package/server/models/trangthai.js +2 -2
  103. package/server/modules/lists/bg-fifo_dk.js +8 -0
  104. package/server/modules/lists/ls-checkin.js +1 -1
  105. package/server/modules/lists/ls-customer.js +23 -26
  106. package/server/modules/lists/ls-dmbp.js +5 -5
  107. package/server/modules/lists/ls-dmkho.js +7 -1
  108. package/server/modules/lists/ls-dmnhtask.js +1 -1
  109. package/server/modules/lists/ls-dmvt.js +52 -70
  110. package/server/modules/lists/ls-flowpending.js +3 -0
  111. package/server/modules/lists/ls-participant.js +6 -4
  112. package/server/modules/lists/ls-task.js +6 -6
  113. package/server/modules/reports/calc-tinhgiafifo.js +8 -0
  114. package/server/modules/reports/calc-tinhluong.js +17 -16
  115. package/server/modules/reports/rp-baoCaoFifoKy.js +1 -0
  116. package/server/modules/reports/rp-congviectheobp.js +4 -4
  117. package/server/modules/reports/rp-congviectheodt.js +1 -1
  118. package/server/modules/reports/rp-congviectheokh.js +1 -1
  119. package/server/modules/reports/rp-congviectheonv.js +1 -1
  120. package/server/modules/reports/rp-dtbanletheoct.js +22 -23
  121. package/server/modules/reports/rp-dtbanletheokh.js +17 -20
  122. package/server/modules/reports/rp-dtbanletheokho.js +15 -18
  123. package/server/modules/reports/rp-hoahong.js +1 -1
  124. package/server/modules/reports/rp-sctvt.js +1 -1
  125. package/server/modules/reports/rp-sosanhkhvatt2.js +1 -1
  126. package/server/modules/reports/rp-thnxt.js +8 -8
  127. package/server/modules/reports/rp-tongquannvbh.js +1 -1
  128. package/server/modules/systems/sys-app.js +5 -5
  129. package/server/modules/vouchers/vo-so1.js +14 -14
@@ -0,0 +1,104 @@
1
+ const {tool}=require("ai"),{z}=require("zod"),{kpistatus,calcSoCai,calcDoanhThu}=require("../../libs/kpistatus"),{evalute2:evalute}=require("../../libs/utils"),moment=require("moment"),dktk=require("../../libs/dktk"),cktk=require("../../libs/cktk"),dkcn=require("../../libs/dkcn"),ckcn=require("../../libs/ckcn"),dkvt=require("../../libs/dkvt"),ckvt=require("../../libs/ckvt");
2
+ function createKpiTools($id_app$$,$user_id$$){return{businessPlanningTool:tool({description:'C\u00f4ng c\u1ee5 L\u1eacP K\u1ebe HO\u1ea0CH, D\u1ef0 TO\u00c1N & GIAO CH\u1ec8 TI\u00caU KPI (Business Planner).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp y\u00eau c\u1ea7u l\u1eadp k\u1ebf ho\u1ea1ch kinh doanh, ng\u00e2n s\u00e1ch chi ph\u00ed, giao ch\u1ec9 ti\u00eau doanh s\u1ed1/KPI,\n HO\u1eb6C l\u1eadp d\u1ef1 to\u00e1n/k\u1ebf ho\u1ea1ch chi ph\u00ed cho M\u1ed8T V\u1ee4 VI\u1ec6C/D\u1ef0 \u00c1N c\u1ee5 th\u1ec3.\n \n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n \ud83c\udd70\ufe0f MODE A: K\u1ebe HO\u1ea0CH KINH DOANH TO\u00c0N C\u00d4NG TY (P&L theo k\u1ef3, c\u00f3 t\u0103ng tr\u01b0\u1edfng %)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n \ud83d\udd25 T\u01af DUY K\u1ebe TO\u00c1N QU\u1ea2N TR\u1eca (FINANCIAL LOGIC - B\u1eaeT BU\u1ed8C):\n - B\u1ee9c tranh kinh doanh (P&L) lu\u00f4n g\u1eafn li\u1ec1n v\u1edbi ph\u01b0\u01a1ng tr\u00ecnh: L\u1ee2I NHU\u1eacN = DOANH THU - CHI PH\u00cd.\n - N\u1ebeU S\u1ebfp y\u00eau c\u1ea7u "L\u1eadp k\u1ebf ho\u1ea1ch kinh doanh" nh\u01b0ng ch\u1ec9 \u0111\u01b0a ra ch\u1ec9 ti\u00eau cho 2 y\u1ebfu t\u1ed1 (V\u00ed d\u1ee5: Doanh thu t\u0103ng 10%, L\u1ee3i nhu\u1eadn t\u0103ng 5%), B\u1ea0N PH\u1ea2I T\u1ef0 \u0110\u1ed8NG t\u00ednh to\u00e1n v\u00e0 B\u1ed4 SUNG y\u1ebfu t\u1ed1 th\u1ee9 3 (Chi ph\u00ed) v\u00e0o b\u1ea3n k\u1ebf ho\u1ea1ch.\n - C\u00f4ng th\u1ee9c suy lu\u1eadn: M\u1ee5c ti\u00eau Chi Ph\u00ed = M\u1ee5c ti\u00eau Doanh Thu - M\u1ee5c ti\u00eau L\u1ee3i Nhu\u1eadn.\n - Tuy\u1ec7t \u0111\u1ed1i kh\u00f4ng \u0111\u01b0\u1ee3c \u0111\u1ec3 khuy\u1ebft Chi ph\u00ed khi l\u1eadp k\u1ebf ho\u1ea1ch t\u1ed5ng th\u1ec3.\n \n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n \ud83c\udd71\ufe0f MODE B: K\u1ebe HO\u1ea0CH CHI PH\u00cd V\u1ee4 VI\u1ec6C/D\u1ef0 \u00c1N (PROJECT COST PLAN) \u2014 M\u1edaI\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n K\u00cdCH HO\u1ea0T KHI: S\u1ebfp n\u00f3i "l\u1eadp d\u1ef1 to\u00e1n chi ph\u00ed cho d\u1ef1 \u00e1n X", "k\u1ebf ho\u1ea1ch chi ph\u00ed v\u1ee5 vi\u1ec7c Y",\n "thi\u1ebft k\u1ebf ng\u00e2n s\u00e1ch d\u1ef1 \u00e1n", ho\u1eb7c nh\u1eafc \u0111\u1ebfn T\u00caN/M\u00c3 m\u1ed9t v\u1ee5 vi\u1ec7c/d\u1ef1 \u00e1n c\u1ee5 th\u1ec3 \u0111i k\u00e8m "chi ph\u00ed", "ng\u00e2n s\u00e1ch", "d\u1ef1 to\u00e1n".\n \n QUY TR\u00ccNH T\u01af DUY B\u1eaeT BU\u1ed8C (5 B\u01af\u1edaC):\n 1. **X\u00c1C \u0110\u1ecaNH D\u1ef0 \u00c1N (ma_dt):** N\u1ebfu S\u1ebfp ch\u1ec9 nh\u1eafc T\u00caN d\u1ef1 \u00e1n (kh\u00f4ng c\u00f3 m\u00e3), B\u1eaeT BU\u1ed8C g\u1ecdi\n lookupMasterDataTool (type: "dmdt") \u0111\u1ec3 tra ra m\u00e3 chu\u1ea9n TR\u01af\u1edaC khi vi\u1ebft script.\n N\u1ebfu d\u1ef1 \u00e1n CH\u01afA T\u1ed2N T\u1ea0I trong h\u1ec7 th\u1ed1ng, h\u1ecfi S\u1ebfp c\u00f3 mu\u1ed1n t\u1ea1o m\u1edbi qua\n manageMasterDataDocumentTool (type: "dmdt") kh\u00f4ng, r\u1ed3i m\u1edbi ti\u1ebfp t\u1ee5c.\n \n 2. **L\u1ea4Y DANH M\u1ee4C CHI PH\u00cd TH\u1eacT \u2014 TUY\u1ec6T \u0110\u1ed0I KH\u00d4NG B\u1ecaA LO\u1ea0I CHI PH\u00cd:**\n B\u00ean trong script (param \'script\'), g\u1ecdi TR\u1ef0C TI\u1ebeP:\n `const danhMucChiPhi = await getModel(\'dmphi\').find({ status: true });`\n \u0110\u00e2y l\u00e0 danh s\u00e1ch C\u00d3 TH\u1eacT c\u00e1c lo\u1ea1i chi ph\u00ed (ma_phi, ten_phi, ma_phi_me - m\u00e3 cha n\u1ebfu c\u00f3\n ph\u00e2n c\u1ea5p). CH\u1ec8 d\u00f9ng c\u00e1c ma_phi t\u1ed3n t\u1ea1i trong danh s\u00e1ch n\u00e0y khi g\u00e1n cho t\u1eebng d\u00f2ng k\u1ebf\n ho\u1ea1ch. N\u1ebfu S\u1ebfp y\u00eau c\u1ea7u m\u1ed9t lo\u1ea1i chi ph\u00ed kh\u00f4ng c\u00f3 trong danh m\u1ee5c, h\u1ecfi S\u1ebfp c\u00f3 mu\u1ed1n t\u1ea1o\n m\u1edbi danh m\u1ee5c chi ph\u00ed \u0111\u00f3 kh\u00f4ng (qua manageMasterDataDocumentTool type:"dmphi") tr\u01b0\u1edbc\n khi \u0111\u01b0a v\u00e0o k\u1ebf ho\u1ea1ch.\n \n 3. **(T\u00d9Y CH\u1eccN) THAM KH\u1ea2O S\u1ed0 LI\u1ec6U L\u1ecaCH S\u1eec:** N\u1ebfu S\u1ebfp c\u00f3 d\u1ef1 \u00e1n t\u01b0\u01a1ng t\u1ef1 tr\u01b0\u1edbc \u0111\u00f3 mu\u1ed1n\n d\u00f9ng l\u00e0m c\u01a1 s\u1edf, d\u00f9ng calcSoCai({ ma_dt, ma_phi, tk_no: [...] }, { ngay_ct: {...} })\n \u0111\u1ec3 l\u1ea5y s\u1ed1 chi ph\u00ed th\u1ef1c t\u1ebf \u0111\u00e3 ph\u00e1t sinh c\u1ee7a d\u1ef1 \u00e1n tham kh\u1ea3o, l\u00e0m n\u1ec1n t\u00ednh d\u1ef1 to\u00e1n m\u1edbi.\n \n 4. **X\u00c2Y D\u1ef0NG T\u1eeaNG D\u00d2NG CHI PH\u00cd:** M\u1ed7i d\u00f2ng trong \'chi_tiet\' l\u00e0 M\u1ed8T lo\u1ea1i chi ph\u00ed c\u1ee7a d\u1ef1 \u00e1n:\n - ma_dt: B\u1eaeT BU\u1ed8C = m\u00e3 d\u1ef1 \u00e1n \u0111\u00e3 x\u00e1c \u0111\u1ecbnh \u1edf b\u01b0\u1edbc 1 (m\u1ecdi d\u00f2ng \u0111\u1ec1u g\u1eafn ma_dt n\u00e0y).\n - ma_phi: m\u00e3 lo\u1ea1i chi ph\u00ed (l\u1ea5y t\u1eeb danh m\u1ee5c th\u1eadt \u1edf b\u01b0\u1edbc 2).\n - ct_theo: th\u01b0\u1eddng = "nam" (k\u1ebf ho\u1ea1ch t\u00ednh tr\u1ecdn theo hi\u1ec7u l\u1ef1c d\u1ef1 \u00e1n, kh\u00f4ng l\u1eb7p h\u00e0ng th\u00e1ng).\n - hl_tu_ngay / hl_den_ngay: ng\u00e0y b\u1eaft \u0111\u1ea7u/k\u1ebft th\u00fac d\u1ef1 \u00e1n (KH\u00d4NG ph\u1ea3i ng\u00e0y h\u00f4m nay).\n - cach_tinh: "1" (Theo s\u1ed5 c\u00e1i) n\u1ebfu mu\u1ed1n so s\u00e1nh \u0111\u01b0\u1ee3c v\u1edbi chi ph\u00ed th\u1ef1c t\u1ebf ph\u00e1t sinh sau\n n\u00e0y (y\u00eau c\u1ea7u k\u00e8m tk_no = c\u00e1c TK chi ph\u00ed li\u00ean quan, VD: ["621","622","627","641","642"]).\n - chi_tieu: s\u1ed1 ti\u1ec1n d\u1ef1 to\u00e1n cho lo\u1ea1i chi ph\u00ed n\u00e0y.\n \n 5. **KI\u1ec2M TRA TR\u1ea6N NG\u00c2N S\u00c1CH:** N\u1ebfu S\u1ebfp cho bi\u1ebft t\u1ed5ng m\u1ee9c \u0111\u1ea7u t\u01b0 cho ph\u00e9p (tong_muc_dau_tu),\n B\u1eaeT BU\u1ed8C t\u00ednh t\u1ed5ng c\u00e1c d\u00f2ng chi_tieu v\u00e0 SO S\u00c1NH. N\u1ebfu v\u01b0\u1ee3t tr\u1ea7n, \u0111\u01b0a c\u1ea3nh b\u00e1o r\u00f5 r\u00e0ng\n trong RESULT (tr\u01b0\u1eddng \'canh_bao\') \u2014 KH\u00d4NG t\u1ef1 \u00fd c\u1eaft gi\u1ea3m s\u1ed1 li\u1ec7u c\u1ee7a S\u1ebfp.\n \n \ud83d\udd25 C\u1ea4U TR\u00daC RESULT CHO MODE B (b\u1ed5 sung so v\u1edbi Mode A):\n {\n group_name: "K\u1ebf ho\u1ea1ch chi ph\u00ed - [M\u00e3/T\u00ean d\u1ef1 \u00e1n]",\n mo_ta: "...",\n plan_type: "CPDA", // \u26a0 B\u1eaeT BU\u1ed8C \u0111\u1ec3 \u0111\u00e1nh d\u1ea5u \u0111\u00e2y l\u00e0 k\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n\n tong_ngan_sach: Number, // t\u1ed5ng c\u1ed9ng t\u1ea5t c\u1ea3 chi_tieu\n canh_bao: String | null, // c\u1ea3nh b\u00e1o v\u01b0\u1ee3t tr\u1ea7n ng\u00e2n s\u00e1ch n\u1ebfu c\u00f3\n chi_tiet: [ {stt, ma_chi_tieu, nhan_chi_tieu, ma_dt, ma_phi, ct_theo, hl_tu_ngay, hl_den_ngay, cach_tinh, tk_no, chi_tieu, ps_cu} ]\n }\n \n \ud83d\udd25 SAU KHI THI\u1ebeT K\u1ebe XONG (c\u1ea3 2 Mode): Tr\u00ecnh b\u00e0y b\u1ea3ng nh\u00e1p cho S\u1ebfp xem, h\u1ecfi S\u1ebfp c\u00f3 mu\u1ed1n\n TH\u00caM/B\u1edaT/S\u1eecA lo\u1ea1i chi ph\u00ed n\u00e0o kh\u00f4ng (n\u1ebfu c\u00f3, ch\u1ec9 c\u1ea7n \u0111i\u1ec1u ch\u1ec9nh script v\u00e0 g\u1ecdi l\u1ea1i tool n\u00e0y -\n CH\u01afA c\u1ea7n l\u01b0u DB \u1edf giai \u0111o\u1ea1n n\u00e0y). Khi S\u1ebfp h\u00e0i l\u00f2ng, h\u1ecfi c\u00f3 mu\u1ed1n l\u01b0u ch\u00ednh th\u1ee9c kh\u00f4ng\n (g\u1ecdi saveBusinessPlanTool, nh\u1edb truy\u1ec1n plan_type t\u01b0\u01a1ng \u1ee9ng).\n \n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n \ud83d\udd25 T\u01af DUY T\u1ea0O M\u00c3 & NH\u00c3N CH\u1ec8 TI\u00caU (CRITICAL RULE - \u00e1p d\u1ee5ng c\u1ea3 2 Mode):\n 1. \'stt\': B\u1eaeT BU\u1ed8C \u0111\u00e1nh s\u1ed1 th\u1ee9 t\u1ef1 (1, 2, 3...) cho t\u1eebng d\u00f2ng \u0111\u1ec3 s\u1eafp x\u1ebfp l\u00ean b\u00e1o c\u00e1o.\n 2. \'ma_chi_tieu\': B\u1eaeT BU\u1ed8C sinh ra m\u00e3 \u0111\u1ecbnh danh DUY NH\u1ea4T cho m\u1ed7i d\u00f2ng (VD: "DT_01", "CP_MKT", "LN_THUAN"). Tuy\u1ec7t \u0111\u1ed1i kh\u00f4ng \u0111\u01b0\u1ee3c tr\u00f9ng l\u1eb7p.\n 3. \'nhan_chi_tieu\': B\u1eaeT BU\u1ed8C ph\u1ea3i r\u00f5 ngh\u0129a v\u00e0 KH\u00c1C NHAU ho\u00e0n to\u00e0n gi\u1eefa c\u00e1c d\u00f2ng \u0111\u1ec3 ph\u1ee5c v\u1ee5 l\u00ean c\u1ed9t B\u00e1o c\u00e1o (VD: "Doanh thu", "Chi ph\u00ed", "L\u1ee3i nhu\u1eadn").\n \n \ud83d\udd25 QUY T\u1eaeC L\u1ef0A CH\u1eccN C\u00c1CH T\u00cdNH (cach_tinh):\n - "1": T\u00ednh theo S\u1ed5 c\u00e1i (Chi ph\u00ed, L\u1ee3i nhu\u1eadn).\n - "2": T\u00ednh theo Doanh thu b\u00e1n h\u00e0ng (Ti\u1ec1n).\n - "3": T\u00ednh theo Doanh s\u1ed1 b\u00e1n h\u00e0ng (S\u1ed1 l\u01b0\u1ee3ng).\n - "9" (C\u00c1CH T\u00cdNH KH\u00c1C - \u0110\u1ed8NG): D\u00f9ng cho c\u00e1c ch\u1ec9 s\u1ed1 \u0111\u1eb7c th\u00f9 KH\u00d4NG PH\u1ea2I K\u1ebf to\u00e1n (V\u00ed d\u1ee5: S\u1ed1 kh\u00e1ch h\u00e0ng m\u1edbi, S\u1ed1 l\u01b0\u1ee3ng Task ho\u00e0n th\u00e0nh, S\u1ed1 \u0111\u01a1n h\u00e0ng x\u1eed l\u00fd...).\n \n \ud83d\udd25 N\u1ebeU CH\u1eccN CACH_TINH L\u00c0 "9", B\u1ea0N B\u1eaeT BU\u1ed8C PH\u1ea2I L\u00c0M 2 VI\u1ec6C:\n 1. Trong Sandbox l\u1eadp k\u1ebf ho\u1ea1ch: T\u1ef1 d\u00f9ng getModel(model_name).countDocuments() \u0111\u1ec3 t\u00ednh s\u1ed1 li\u1ec7u l\u1ecbch s\u1eed.\n 2. Trong Object Output \'ctds2\': B\u1eaeT BU\u1ed8C ph\u1ea3i sinh ra tr\u01b0\u1eddng \'kpi_script\' ch\u1ee9a \u0111o\u1ea1n code JS m\u00e0 Backend s\u1ebd d\u00f9ng \u0111\u1ec3 t\u00ednh s\u1ed1 Ph\u00e1t sinh (Th\u1ef1c t\u1ebf) sau n\u00e0y.\n L\u01b0u \u00fd trong \'kpi_script\': B\u1ea1n c\u00f3 th\u1ec3 d\u00f9ng bi\u1ebfn \'id_app\', \'query.ngay_ct\' (ch\u1ee9a $gte v\u00e0 $lte) v\u00e0 h\u00e0m \'getModel(model_name)\'. B\u1eaft bu\u1ed9c ph\u1ea3i \'return\' m\u1ed9t con s\u1ed1.\n \n \ud83d\udd25 T\u1eea \u0110I\u1ec2N H\u00c0M L\u00d5I TRONG SANDBOX (CH\u1ec8 TRUY\u1ec0N THAM S\u1ed0, H\u1ec6 TH\u1ed0NG T\u1ef0 LO ID_APP):\n \n [NH\u00d3M A: T\u00cdNH T\u1ed4NG PH\u00c1T SINH (DOANH THU / CHI PH\u00cd)]\n - await calcSoCai(ct, query): D\u00f9ng t\u00ednh Chi ph\u00ed, L\u1ee3i nhu\u1eadn. Tr\u1ea3 v\u1ec1 1 s\u1ed1 ti\u1ec1n (Number). VD: await calcSoCai({ tk_co: ["511"] }, { ngay_ct: { $gte: tu_ngay, $lte: den_ngay } })\n - await calcDoanhThu(ct, query): D\u00f9ng t\u00ednh doanh s\u1ed1 b\u00e1n l\u1ebb. Tr\u1ea3 v\u1ec1 Object { t_dt: Doanh_thu, t_ds: Doanh_so }.t_dt l\u00e0 doanh thu(ti\u1ec1n),t_ds: Doanh s\u1ed1 (s\u1ed1 l\u01b0\u1ee3ng)\n \n [NH\u00d3M B: T\u00cdNH S\u1ed0 D\u01af (T\u1ed2N KHO / C\u00d4NG N\u1ee2 / TI\u1ec0N T\u1ec6)]\n - \u0110\u1ea7u/Cu\u1ed1i k\u1ef3 T\u00e0i kho\u1ea3n: await dktk({ tk, ngay }) | await cktk({ tk, ngay })\n - \u0110\u1ea7u/Cu\u1ed1i k\u1ef3 C\u00f4ng n\u1ee3: await dkcn({ ma_kh, tk, ngay, groupBy }) | await ckcn({ ma_kh, tk, ngay, groupBy })\n - \u0110\u1ea7u/Cu\u1ed1i k\u1ef3 V\u1eadt t\u01b0: await dkvt({ ma_vt, ma_kho, ngay }) | await ckvt({ ma_vt, ma_kho, ngay })\n \n [NH\u00d3M C: \u0110\u1eccC DANH M\u1ee4C TH\u1eacT (D\u00d9NG CHO MODE B)]\n - await getModel(\'dmphi\').find({ status: true }): L\u1ea5y TO\u00c0N B\u1ed8 danh m\u1ee5c chi ph\u00ed th\u1eadt (ma_phi, ten_phi, ma_phi_me).\n - await getModel(\'dmdt\').findOne({ ma_dt }): Ki\u1ec3m tra/\u0111\u1ecdc th\u00f4ng tin 1 d\u1ef1 \u00e1n (ng\u00e0y b\u1eaft \u0111\u1ea7u, k\u1ebft th\u00fac n\u1ebfu model c\u00f3 l\u01b0u).\n \n \ud83d\udd25 T\u01af DUY T\u00cdNH TO\u00c1N CH\u00c9O P&L (Mode A):\n - N\u1ebfu S\u1ebfp ch\u1ec9 giao 2 bi\u1ebfn (Doanh thu, L\u1ee3i nhu\u1eadn), b\u1ea1n PH\u1ea2I t\u1ef1 \u0111\u1ed9ng sinh th\u00eam bi\u1ebfn th\u1ee9 3 (Chi ph\u00ed).\n - \u0110\u1ed1i v\u1edbi c\u00e1c ch\u1ec9 ti\u00eau ph\u1ee5 thu\u1ed9c (Nh\u01b0 L\u1ee3i nhu\u1eadn = Doanh thu - Chi ph\u00ed), b\u1ea1n PH\u1ea2I:\n + \u0110\u1eb7t cach_tinh = "9".\n + T\u00ednh \'chi_tieu\' (K\u1ebf ho\u1ea1ch) b\u1eb1ng c\u00e1ch c\u1ed9ng/tr\u1eeb c\u00e1c \'chi_tieu\' c\u1ee7a m\u00e3 t\u01b0\u01a1ng \u1ee9ng.\n + Vi\u1ebft \'kpi_script\' \u0111\u1ec3 Backend t\u00ednh \'Ph\u00e1t sinh\' (Th\u1ef1c t\u1ebf) b\u1eb1ng c\u00e1ch t\u00ecm c\u00e1c ma_chi_tieu trong m\u1ea3ng \'cts\' (danh s\u00e1ch c\u00e1c ch\u1ec9 ti\u00eau c\u00f9ng k\u1ef3).\n \n \ud83d\udd25 C\u1ea4U TR\u00daC \u0110\u1ea6U RA B\u1eaeT BU\u1ed8C (Mode A - gi\u1eef nguy\u00ean):\n Bi\u1ebfn \'RESULT\' B\u1eaeT BU\u1ed8C ph\u1ea3i l\u00e0 m\u1ed9t Object c\u00f3 c\u1ea5u tr\u00fac Master-Detail nh\u01b0 sau:\n {\n group_name: "T\u00ean nh\u00f3m k\u1ebf ho\u1ea1ch",\n mo_ta: "M\u00f4 t\u1ea3 gi\u1ea3 \u0111\u1ecbnh t\u00ednh to\u00e1n",\n chi_tiet: [ {stt,ma_chi_tieu,nhan_chi_tieu, ct_theo: "thang/quy/nam", hl_tu_ngay: Date, hl_den_ngay: Date, cach_tinh: "1"|"2"|"3"|"9",ps_cu:Number,ty_le_tang_truong:Number, chi_tieu: Number,kpi_script:"...", ma_bp: "...", ma_nv: "..." } ]\n }',inputSchema:z.object({params:z.record(z.any()),
3
+ script:z.string().describe('\n \u0110o\u1ea1n m\u00e3 JS. VD K\u1ecbch b\u1ea3n Mode A - All-in-One (Doanh thu, Chi ph\u00ed, L\u1ee3i nhu\u1eadn, Kh\u00e1ch h\u00e0ng m\u1edbi):\n const tu_ngay = moment(params.tu_ngay_qk).toDate();\n const den_ngay = moment(params.den_ngay_qk).toDate();\n \n const tyLeTangDT = 1.10; // T\u0103ng 10%\n const tyLeTangLN = 1.05; // T\u0103ng 5%\n const tyLeTangKH = 1.10; // T\u0103ng 10%\n \n // 1. T\u00cdNH QU\u00c1 KH\u1ee8 (S\u1ed1 c\u00e1i, B\u00e1n h\u00e0ng & Model ngo\u00e0i)\n const dtCu = await calcDoanhThu({ ma_bp: params.ma_bp }, { ngay_ct: { $gte: tu_ngay, $lte: den_ngay } });\n const cpCu = await calcSoCai({ tk_no: ["641", "642", "632"] }, { ngay_ct: { $gte: tu_ngay, $lte: den_ngay } });\n \n const Customer = getModel(\'customer\');\n const khCu = await Customer.countDocuments({ date_created: { $gte: tu_ngay, $lte: den_ngay } });\n \n // 2. SUY LU\u1eacN K\u1ebe HO\u1ea0CH M\u1edaI\n const mucTieuDT = dtCu.t_dt * tyLeTangDT;\n const mucTieuLN = (dtCu.t_dt - cpCu) * tyLeTangLN;\n const mucTieuCP = mucTieuDT - mucTieuLN; // T\u1ef1 suy lu\u1eadn chi ph\u00ed\n const mucTieuKH = Math.ceil(khCu * tyLeTangKH);\n \n let chiTietKeHoach = [];\n \n chiTietKeHoach.push({\n stt: 1, ma_chi_tieu: "DT_01", nhan_chi_tieu: "Doanh thu t\u1ed5ng", \n cach_tinh: "2", tk_co: ["511"],\n hl_tu_ngay: moment(params.tu_ngay_kh).toDate(), hl_den_ngay: moment(params.den_ngay_kh).toDate(),\n chi_tieu: mucTieuDT, ps_cu:dtCu.t_dt, ty_le_tang_truong:tyLeTangDT\n });\n \n chiTietKeHoach.push({\n stt: 2, ma_chi_tieu: "CP_01", nhan_chi_tieu: "T\u1ed5ng chi ph\u00ed",\n cach_tinh: "1", tk_no: ["641", "642", "632"],\n hl_tu_ngay: moment(params.tu_ngay_kh).toDate(), hl_den_ngay: moment(params.den_ngay_kh).toDate(),\n chi_tieu: mucTieuCP, ps_cu:cpCu\n });\n \n chiTietKeHoach.push({\n stt: 3, ma_chi_tieu: "LN_01", nhan_chi_tieu: "L\u1ee3i nhu\u1eadn thu\u1ea7n",\n cach_tinh: "9", \n hl_tu_ngay: moment(params.tu_ngay_kh).toDate(), hl_den_ngay: moment(params.den_ngay_kh).toDate(),\n chi_tieu: mucTieuLN, ps_cu:(dtCu.t_dt - cpCu), ty_le_tang_truong:tyLeTangLN,\n kpi_script: `\n const dt = cts.find(c => c.ma_chi_tieu === \'DT_01\');\n const cp = cts.find(c => c.ma_chi_tieu === \'CP_01\');\n return (dt ? (dt.ps || 0) : 0) - (cp ? (cp.ps || 0) : 0);\n `\n });\n \n chiTietKeHoach.push({\n stt: 4, ma_chi_tieu: "KH_01", nhan_chi_tieu: "Kh\u00e1ch h\u00e0ng m\u1edbi",\n cach_tinh: "9", \n hl_tu_ngay: moment(params.tu_ngay_kh).toDate(), hl_den_ngay: moment(params.den_ngay_kh).toDate(),\n chi_tieu: mucTieuKH, ps_cu:khCu, ty_le_tang_truong:tyLeTangKH,\n kpi_script: `\n const KH = getModel(\'customer\');\n return await KH.countDocuments({ id_app: id_app, date_created: query.ngay_ct });\n `\n });\n \n RESULT = { group_name: "K\u1ebf ho\u1ea1ch Kinh doanh To\u00e0n di\u1ec7n", chi_tiet: chiTietKeHoach };\n \n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // VD K\u1ecbch b\u1ea3n Mode B - D\u1ef1 to\u00e1n chi ph\u00ed D\u1ef1 \u00e1n (M\u1edaI):\n //\n // const maDuAn = params.ma_dt; // \u0111\u00e3 resolve tr\u01b0\u1edbc qua lookupMasterDataTool\n // const ngayBatDau = moment(params.ngay_bd).toDate();\n // const ngayKetThuc = moment(params.ngay_kt).toDate();\n //\n // // 1. L\u1ea4Y DANH M\u1ee4C CHI PH\u00cd TH\u1eacT - KH\u00d4NG B\u1ecaA\n // const danhMucChiPhi = await getModel(\'dmphi\').find({ status: true });\n //\n // // 2. \u01af\u1edaC T\u00cdNH D\u1ef0 TO\u00c1N CHO T\u1eeaNG LO\u1ea0I (theo y\u00eau c\u1ea7u c\u1ee7a S\u1ebfp)\n // const duToan = { "NVL": 450000000, "NC": 220000000, "MTC": 80000000, "QLDA": 60000000, "DUPHONG": 35000000 };\n //\n // let chiTietKeHoach = [];\n // let stt = 1;\n // for (const ma_phi in duToan) {\n // const phiInfo = danhMucChiPhi.find(p => p.ma_phi === ma_phi);\n // if (!phiInfo) continue; // b\u1ecf qua n\u1ebfu m\u00e3 kh\u00f4ng t\u1ed3n t\u1ea1i trong danh m\u1ee5c th\u1eadt\n // chiTietKeHoach.push({\n // stt: stt++, ma_chi_tieu: `CP_${ma_phi}`, nhan_chi_tieu: phiInfo.ten_phi,\n // ma_dt: maDuAn, ma_phi: ma_phi,\n // ct_theo: "nam", hl_tu_ngay: ngayBatDau, hl_den_ngay: ngayKetThuc,\n // cach_tinh: "1", tk_no: ["621","622","627","641","642"],\n // chi_tieu: duToan[ma_phi]\n // });\n // }\n //\n // const tongNganSach = chiTietKeHoach.reduce((a,b)=>a+b.chi_tieu,0);\n // let canhBao = null;\n // if (params.tong_muc_dau_tu && tongNganSach > params.tong_muc_dau_tu) {\n // canhBao = `T\u1ed5ng d\u1ef1 to\u00e1n (${tongNganSach.toLocaleString()}) V\u01af\u1ee2T tr\u1ea7n ng\u00e2n s\u00e1ch (${params.tong_muc_dau_tu.toLocaleString()})`;\n // }\n //\n // RESULT = {\n // group_name: `K\u1ebf ho\u1ea1ch chi ph\u00ed - ${maDuAn}`,\n // mo_ta: `D\u1ef1 to\u00e1n chi ph\u00ed d\u1ef1 \u00e1n ${maDuAn}`,\n // plan_type: "CPDA",\n // tong_ngan_sach: tongNganSach,\n // canh_bao: canhBao,\n // chi_tiet: chiTietKeHoach\n // };\n '),
4
+ rationale:z.string().describe("Gi\u1ea3i th\u00edch logic l\u1ea5y d\u1eef li\u1ec7u v\u00e0 \u00e1p d\u1ee5ng c\u00f4ng th\u1ee9c.")}),execute:async({params:$cleanData_params$$,script:$isProjectCostPlan_script$$,rationale:$rationale$$})=>{Logger.warn("[businessPlanningTool] Rationale:",$rationale$$);Logger.info("[businessPlanningTool] Params nh\u1eadn v\u00e0o:",JSON.stringify($cleanData_params$$));try{const $executionResult$$=await evalute(`
5
+ ${$isProjectCostPlan_script$$}
6
+ return typeof RESULT !== 'undefined' ? RESULT : null;
7
+ `,{id_app:$id_app$$,moment,params:$cleanData_params$$||{},RESULT:null,console:{log:(...$args$$)=>Logger.info("[AI Sandbox Log]:",...$args$$),error:(...$args$$)=>Logger.error("[AI Sandbox Error]:",...$args$$)},dktk:async($opts$$={})=>await dktk({...$opts$$,id_app:$id_app$$}),cktk:async($opts$$={})=>await cktk({...$opts$$,id_app:$id_app$$}),dkcn:async($opts$$={})=>await dkcn({...$opts$$,id_app:$id_app$$}),ckcn:async($opts$$={})=>await ckcn({...$opts$$,id_app:$id_app$$}),dkvt:async($opts$$=
8
+ {})=>await dkvt({...$opts$$,id_app:$id_app$$}),ckvt:async($opts$$={})=>await ckvt({...$opts$$,id_app:$id_app$$}),calcSoCai:async($ct$$,$query$$={})=>await calcSoCai($ct$$,{...$query$$,id_app:$id_app$$}),calcDoanhThu:async($ct$$,$query$$={})=>await calcDoanhThu($ct$$,{...$query$$,id_app:$id_app$$}),getModel:$cleanModelName_modelName$$=>{$cleanModelName_modelName$$=$cleanModelName_modelName$$.toLowerCase().trim();const $Model$$=global.getModel($cleanModelName_modelName$$);if(!$Model$$)throw Error(`Model '${$cleanModelName_modelName$$}' kh\u00f4ng t\u1ed3n t\u1ea1i.`);
9
+ const $enforceIdApp$$=($cond$$={})=>({...$cond$$,id_app:$id_app$$}),$enforcePipeline$$=($newPipeline_pipeline$$=[])=>{$newPipeline_pipeline$$=[...$newPipeline_pipeline$$];$newPipeline_pipeline$$.length>0&&$newPipeline_pipeline$$[0].$match?$newPipeline_pipeline$$[0].$match.id_app=$id_app$$:$newPipeline_pipeline$$.unshift({$match:{id_app:$id_app$$}});return $newPipeline_pipeline$$};return{find:($cond$$,$proj$$,$opts$$)=>$Model$$.find($enforceIdApp$$($cond$$),$proj$$,$opts$$),findOne:($cond$$,$proj$$,
10
+ $opts$$)=>$Model$$.findOne($enforceIdApp$$($cond$$),$proj$$,$opts$$),aggregate:$pipeline$$=>$Model$$.aggregate($enforcePipeline$$($pipeline$$)),countDocuments:$cond$$=>$Model$$.countDocuments($enforceIdApp$$($cond$$))}}},2E4);$cleanData_params$$=null;$executionResult$$!==void 0&&$executionResult$$!==null&&($cleanData_params$$=JSON.parse(JSON.stringify($executionResult$$)));if(JSON.stringify($cleanData_params$$).length>6E4)return{status:"error",message:"L\u1ed6I TR\u00c0N B\u1ed8 NH\u1eda: B\u1ea3n k\u1ebf ho\u1ea1ch sinh ra qu\u00e1 nhi\u1ec1u d\u00f2ng chi ti\u1ebft. H\u00e3y y\u00eau c\u1ea7u S\u1ebfp gom nh\u00f3m theo c\u1ea5p \u0111\u1ed9 cao h\u01a1n (VD: Ph\u00f2ng ban thay v\u00ec nh\u00e2n vi\u00ean, ho\u1eb7c Nh\u00f3m chi ph\u00ed cha thay v\u00ec chi ti\u1ebft t\u1eebng kho\u1ea3n nh\u1ecf)."};
11
+ $isProjectCostPlan_script$$=$cleanData_params$$?.plan_type==="CPDA";return{status:"success",message:$isProjectCostPlan_script$$?"L\u1eadp d\u1ef1 to\u00e1n chi ph\u00ed d\u1ef1 \u00e1n th\u00e0nh c\u00f4ng.":"L\u1eadp d\u1ef1 ph\u00f3ng k\u1ebf ho\u1ea1ch kinh doanh th\u00e0nh c\u00f4ng.",data:$cleanData_params$$,hint:$isProjectCostPlan_script$$?'QUY T\u1eaeC TR\u1ea2 L\u1edcI S\u1ebeP B\u1eaeT BU\u1ed8C (K\u1ebe HO\u1ea0CH CHI PH\u00cd D\u1ef0 \u00c1N):\n 1. D\u00f9ng b\u1ea3ng Markdown tr\u00ecnh b\u00e0y t\u1eebng d\u00f2ng chi ph\u00ed: M\u00e3 | N\u1ed9i dung | S\u1ed1 ti\u1ec1n (VN\u0110).\n 2. Hi\u1ec7n d\u00f2ng T\u1ed4NG NG\u00c2N S\u00c1CH \u1edf cu\u1ed1i b\u1ea3ng (in \u0111\u1eadm).\n 3. N\u1ebfu c\u00f3 \'canh_bao\' (v\u01b0\u1ee3t tr\u1ea7n ng\u00e2n s\u00e1ch), hi\u1ec3n th\u1ecb NGAY sau b\u1ea3ng, in \u0111\u1eadm, r\u00f5 r\u00e0ng.\n 4. D\u00f9ng th\u1ebb HTML <details><summary>\ud83d\udd0d Xem gi\u1ea3 \u0111\u1ecbnh t\u00ednh to\u00e1n</summary>...${rationale}...</details>.\n 5. H\u1eceI S\u1ebeP: "S\u1ebfp c\u00f3 mu\u1ed1n em TH\u00caM/S\u1eecA/B\u1edaT lo\u1ea1i chi ph\u00ed n\u00e0o tr\u01b0\u1edbc khi l\u01b0u kh\u00f4ng, hay l\u01b0u lu\u00f4n \u1ea1?"\n 6. N\u1ebfu S\u1ebfp \u0111\u1ed3ng \u00fd l\u01b0u, g\u1ecdi saveBusinessPlanTool V\u1edaI plan_type: "CPDA" (kh\u00f4ng ph\u1ea3i "KPI").':
12
+ 'QUY T\u1eaeC TR\u1ea2 L\u1edcI S\u1ebeP B\u1eaeT BU\u1ed8C:\n 1. D\u00f9ng b\u1ea3ng Markdown tr\u00ecnh b\u00e0y c\u00e1c ch\u1ec9 ti\u00eau k\u1ebf ho\u1ea1ch r\u00f5 r\u00e0ng.\n 2. D\u00f9ng th\u1ebb HTML <details><summary>\ud83d\udd0d Xem th\u00f4ng s\u1ed1 gi\u1ea3 l\u1eadp</summary>...gi\u1ea3i th\u00edch...</details> \u0111\u1ec3 hi\u1ec3n th\u1ecb c\u00e1c tham s\u1ed1 \u0111\u1ea7u v\u00e0o v\u00e0 logic t\u00ednh.\n 3. CU\u1ed0I C\u00d9NG, B\u1eaeT BU\u1ed8C H\u1eceI S\u1ebeP: "D\u1ea1, em \u0111\u00e3 l\u1eadp xong b\u1ea3n nh\u00e1p. S\u1ebfp c\u00f3 mu\u1ed1n em g\u1ecdi saveBusinessPlanTool \u0111\u1ec3 l\u01b0u v\u00e0o h\u1ec7 th\u1ed1ng ch\u1edd duy\u1ec7t kh\u00f4ng \u1ea1?"'}}catch($error$$){return console.error("[L\u1ed7i businessPlanningTool]:",
13
+ $error$$),{status:"error",message:`L\u1ed7i Script: ${$error$$.message}. AI h\u00e3y \u0111\u1ecdc k\u1ef9 l\u1ea1i c\u1ea5u tr\u00fac h\u00e0m v\u00e0 g\u1ecdi l\u1ea1i.`,failed_script:$isProjectCostPlan_script$$}}}}),saveBusinessPlanTool:tool({description:"C\u00f4ng c\u1ee5 L\u01afU B\u1ea2N NH\u00c1P K\u1ebe HO\u1ea0CH (Master-Detail Save).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp ch\u1ed1t l\u01b0u b\u1ea3n k\u1ebf ho\u1ea1ch KPI ho\u1eb7c b\u1ea3n d\u1ef1 to\u00e1n chi ph\u00ed d\u1ef1 \u00e1n v\u1eeba \u0111\u01b0\u1ee3c sinh ra t\u1eeb businessPlanningTool.",
14
+ inputSchema:z.object({group_name:z.string().describe("T\u00ean nh\u00f3m k\u1ebf ho\u1ea1ch/KPI (VD: K\u1ebf ho\u1ea1ch b\u00e1n h\u00e0ng 2026, ho\u1eb7c K\u1ebf ho\u1ea1ch chi ph\u00ed D\u1ef1 \u00e1n Nh\u00e0 m\u00e1y A)."),mo_ta:z.string().describe("Ghi ch\u00fa, gi\u1ea3 \u0111\u1ecbnh t\u00ednh to\u00e1n."),chi_tiet:z.array(z.any()).describe("M\u1ea3ng d\u1eef li\u1ec7u ctds2."),plan_type:z.enum(["KPI","CPDA"]).optional().describe('Lo\u1ea1i k\u1ebf ho\u1ea1ch: "KPI" (m\u1eb7c \u0111\u1ecbnh, k\u1ebf ho\u1ea1ch kinh doanh/ch\u1ec9 ti\u00eau) ho\u1eb7c "CPDA" (k\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n/v\u1ee5 vi\u1ec7c \u2014 d\u00f9ng khi businessPlanningTool tr\u1ea3 v\u1ec1 plan_type:"CPDA").'),
15
+ replace_confirm:z.boolean().optional().describe("C\u1edd x\u00e1c nh\u1eadn ghi \u0111\u00e8. M\u1eb7c \u0111\u1ecbnh KH\u00d4NG truy\u1ec1n. CH\u1ec8 TRUY\u1ec0N true n\u1ebfu S\u1ebfp \u0111\u00e3 ra l\u1ec7nh X\u00d3A/GHI \u0110\u00c8 l\u00ean k\u1ebf ho\u1ea1ch c\u0169 tr\u00f9ng t\u00ean.")}),execute:async({group_name:$group_name$$,mo_ta:$mo_ta$$,chi_tiet:$chi_tiet$$,plan_type:$finalPlanType_plan_type$$,replace_confirm:$replace_confirm$$})=>{try{$finalPlanType_plan_type$$=$finalPlanType_plan_type$$||
16
+ "KPI";const $Group$$=global.getModel("group"),$CTDS2$$=global.getModel("ctds2"),$existingGroup$$=await $Group$$.findOne({id_app:$id_app$$,group_type:{$in:["KPI","CPDA"]},group_name:{$regex:new RegExp(`^${$group_name$$}$`,"i")}}).lean();if($existingGroup$$){if($replace_confirm$$===!0){const $groupId$$=$existingGroup$$._id.toString();await $CTDS2$$.deleteMany({id_app:$id_app$$,group_id:$groupId$$});Logger.info(`[saveBusinessPlanTool] \u0110\u00e3 x\u00f3a c\u00e1c ch\u1ec9 ti\u00eau c\u0169 c\u1ee7a group ${$groupId$$}`);
17
+ await global.controllers.GROUP.updateData($user_id$$,$existingGroup$$._id.toString(),{description:$mo_ta$$,is_draft_ai:!0,group_type:$finalPlanType_plan_type$$});const $draftRecords$$=$chi_tiet$$.map($item$$=>({...$item$$,id_app:$id_app$$,group_id:$groupId$$,status:!0,is_draft_ai:!0}));for(let $data$$ of $draftRecords$$)await global.controllers.CTDS2.createData($user_id$$,$data$$);return{status:"success",message:`\u0110\u00e3 GHI \u0110\u00c8 k\u1ebf ho\u1ea1ch '${$group_name$$}' th\u00e0nh c\u00f4ng. \u0110\u00e3 x\u00f3a d\u1eef li\u1ec7u c\u0169 v\u00e0 l\u01b0u ${$draftRecords$$.length} ch\u1ec9 ti\u00eau m\u1edbi.`,
18
+ hint:`B\u00e1o c\u00e1o S\u1ebfp: "D\u1ea1 em \u0111\u00e3 ghi \u0111\u00e8 v\u00e0 c\u1eadp nh\u1eadt xong b\u1ea3ng K\u1ebf ho\u1ea1ch '${$existingGroup$$.group_name}'. S\u1ebfp c\u00f3 th\u1ec3 xem l\u1ea1i s\u1ed1 li\u1ec7u m\u1edbi r\u1ed3i \u1ea1."`}}return{status:"need_confirmation",message:`H\u1ec7 th\u1ed1ng ph\u00e1t hi\u1ec7n K\u1ebf ho\u1ea1ch mang t\u00ean '${$existingGroup$$.group_name}' \u0110\u00c3 T\u1ed2N T\u1ea0I.\nH\u01af\u1edaNG D\u1eaaN AI: H\u00e3y d\u1eebng l\u1ea1i v\u00e0 n\u00f3i v\u1edbi S\u1ebfp: "D\u1ea1 anh \u01a1i, t\u00ean k\u1ebf ho\u1ea1ch '${$existingGroup$$.group_name}' \u0111\u00e3 t\u1ed3n t\u1ea1i tr\u00ean h\u1ec7 th\u1ed1ng. Anh mu\u1ed1n em GHI \u0110\u00c8 (x\u00f3a ch\u1ec9 ti\u00eau c\u0169, thay b\u1eb1ng s\u1ed1 m\u1edbi) hay anh mu\u1ed1n L\u01afU V\u1edaI M\u1ed8T T\u00caN KH\u00c1C \u1ea1?".`}}const $newGroupId$$=
19
+ (await global.controllers.GROUP.createData($user_id$$,{id_app:$id_app$$,group_type:$finalPlanType_plan_type$$,group_name:$group_name$$,description:$mo_ta$$,status:!0,is_draft_ai:!0}))._id.toString(),$draftRecords$$=$chi_tiet$$.map($item$$=>({...$item$$,id_app:$id_app$$,group_id:$newGroupId$$,status:!0,is_draft_ai:!0}));for(let $data$$ of $draftRecords$$)await global.controllers.CTDS2.createData($user_id$$,$data$$);return{status:"success",message:`\u0110\u00e3 t\u1ea1o m\u1edbi nh\u00f3m k\u1ebf ho\u1ea1ch '${$group_name$$}' (lo\u1ea1i: ${$finalPlanType_plan_type$$===
20
+ "CPDA"?"Chi ph\u00ed d\u1ef1 \u00e1n":"KPI"}) v\u1edbi ${$draftRecords$$.length} ch\u1ec9 ti\u00eau chi ti\u1ebft.`,hint:`B\u00e1o c\u00e1o S\u1ebfp: "D\u1ea1 em \u0111\u00e3 t\u1ea1o xong ${$finalPlanType_plan_type$$==="CPDA"?"K\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n":"Nh\u00f3m k\u1ebf ho\u1ea1ch"} '${$group_name$$}' r\u1ed3i \u1ea1. S\u1ebfp c\u00f3 mu\u1ed1n em duy\u1ec7t lu\u00f4n, hay \u0111i\u1ec1u ch\u1ec9nh th\u00eam d\u00f2ng chi ph\u00ed n\u00e0o kh\u00f4ng?"`}}catch($error$$){return Logger.error("[AI Agent][saveBusinessPlanTool]",
21
+ $error$$),{status:"error",message:$error$$.message||$error$$.error||$error$$}}}}),updateDraftBusinessPlanTool:tool({description:"C\u00f4ng c\u1ee5 C\u1eacP NH\u1eacT/CH\u1ec8NH S\u1eecA TO\u00c0N B\u1ed8 K\u1ebe HO\u1ea0CH (Update Draft Business Plan - thay th\u1ebf HO\u00c0N TO\u00c0N m\u1ea3ng chi ti\u1ebft).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp y\u00eau c\u1ea7u thay \u0111\u1ed5i L\u1edaN, t\u00ednh l\u1ea1i to\u00e0n b\u1ed9 ch\u1ec9 ti\u00eau c\u1ee7a m\u1ed9t k\u1ebf ho\u1ea1ch \u0110\u00c3 L\u01afU tr\u01b0\u1edbc \u0111\u00f3 (KPI ho\u1eb7c Chi ph\u00ed d\u1ef1 \u00e1n).\n \n \u26a0 L\u01afU \u00dd: N\u1ebfu S\u1ebfp ch\u1ec9 mu\u1ed1n TH\u00caM 1 d\u00f2ng / X\u00d3A 1 d\u00f2ng / S\u1eecA 1 d\u00f2ng chi ph\u00ed \u0111\u01a1n l\u1ebb trong k\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n,\n h\u00e3y d\u00f9ng manageProjectBudgetItemTool thay v\u00ec tool n\u00e0y (nh\u1eb9 h\u01a1n, kh\u00f4ng c\u1ea7n g\u1eedi l\u1ea1i to\u00e0n b\u1ed9 m\u1ea3ng).\n \n LU\u1eacT HO\u1ea0T \u0110\u1ed8NG:\n 1. C\u1ea7n t\u00ednh to\u00e1n l\u1ea1i m\u1ea3ng 'chi_tiet_moi' d\u1ef1a tr\u00ean y\u00eau c\u1ea7u c\u1ee7a S\u1ebfp (c\u00f3 th\u1ec3 t\u1ef1 t\u00ednh ho\u1eb7c d\u00f9ng codeInterpreterTool \u0111\u1ec3 l\u1ea5y s\u1ed1 li\u1ec7u c\u0169 ra t\u00ednh).\n 2. Truy\u1ec1n t\u1eeb kh\u00f3a t\u00ean k\u1ebf ho\u1ea1ch v\u00e0 m\u1ea3ng d\u1eef li\u1ec7u m\u1edbi v\u00e0o Tool n\u00e0y \u0111\u1ec3 h\u1ec7 th\u1ed1ng t\u1ef1 qu\u00e9t v\u00e0 ghi \u0111\u00e8 TO\u00c0N B\u1ed8.",
22
+ inputSchema:z.object({group_keyword:z.string().describe('T\u00ean ho\u1eb7c t\u1eeb kh\u00f3a c\u1ee7a Nh\u00f3m k\u1ebf ho\u1ea1ch c\u1ea7n c\u1eadp nh\u1eadt (VD: "K\u1ebf ho\u1ea1ch b\u00e1n h\u00e0ng Q3", "K\u1ebf ho\u1ea1ch chi ph\u00ed D\u1ef1 \u00e1n Nh\u00e0 m\u00e1y A").'),chi_tiet_moi:z.array(z.any()).describe("M\u1ea3ng d\u1eef li\u1ec7u ctds2 \u0110\u00c3 \u0110\u01af\u1ee2C T\u00cdNH TO\u00c1N L\u1ea0I CH\u1ec8 TI\u00caU (AI xu\u1ea5t ra).")}),execute:async({group_keyword:$group_keyword$$,
23
+ chi_tiet_moi:$chi_tiet_moi$$})=>{try{const $Group$$=global.getModel("group"),$Ctds2$$=global.getModel("ctds2"),$foundGroups$$=await $Group$$.find({id_app:$id_app$$,group_type:{$in:["KPI","CPDA"]},group_name:{$regex:$group_keyword$$,$options:"i"}}).lean();if($foundGroups$$.length===0)return{status:"error",message:`H\u1ec7 th\u1ed1ng kh\u00f4ng t\u00ecm th\u1ea5y Nh\u00f3m k\u1ebf ho\u1ea1ch n\u00e0o kh\u1edbp v\u1edbi t\u1eeb kh\u00f3a '${$group_keyword$$}'.`};if($foundGroups$$.length>1){const $danhSachKeHoach$$=
24
+ $foundGroups$$.map(($g$$,$index$$)=>`${$index$$+1}. **${$g$$.group_name}** (${$g$$.group_type==="CPDA"?"Chi ph\u00ed d\u1ef1 \u00e1n":"KPI"})`).join("\n");return{status:"need_clarification",message:`[C\u1ea2NH B\u00c1O TR\u00d9NG L\u1eb6P]: H\u1ec7 th\u1ed1ng t\u00ecm th\u1ea5y ${$foundGroups$$.length} k\u1ebf ho\u1ea1ch c\u00f3 ch\u1ee9a t\u1eeb kh\u00f3a '${$group_keyword$$}'.\n\nH\u01af\u1edaNG D\u1eaaN AI: H\u00e3y d\u1eebng l\u1ea1i v\u00e0 h\u1ecfi S\u1ebfp mu\u1ed1n C\u1eacP NH\u1eacT b\u1ea3n k\u1ebf ho\u1ea1ch n\u00e0o trong danh s\u00e1ch sau:\n${$danhSachKeHoach$$}`}}const $groupDraft$$=
25
+ $foundGroups$$[0],$group_id$$=$groupDraft$$._id.toString();if($groupDraft$$.status===!0&&$groupDraft$$.is_draft_ai!==!0)return{status:"error",message:"L\u1ed6I QUY TR\u00ccNH: K\u1ebf ho\u1ea1ch n\u00e0y \u0110\u00c3 \u0110\u01af\u1ee2C PH\u00ca DUY\u1ec6T B\u1edeI QU\u1ea2N L\u00dd (\u00c1p d\u1ee5ng ch\u00ednh th\u1ee9c). AI kh\u00f4ng c\u00f3 quy\u1ec1n s\u1eeda s\u1ed1 li\u1ec7u. H\u00e3y b\u00e1o c\u00e1o S\u1ebfp y\u00eau c\u1ea7u g\u1ee1 duy\u1ec7t tr\u00ean ph\u1ea7n m\u1ec1m tr\u01b0\u1edbc."};
26
+ await $Ctds2$$.deleteMany({id_app:$id_app$$,group_id:$group_id$$});const $updatedRecords$$=$chi_tiet_moi$$.map($item$$=>{$item$$={...$item$$};delete $item$$._id;delete $item$$.date_created;delete $item$$.date_updated;$item$$.id_app=$id_app$$;$item$$.group_id=$group_id$$;$item$$.status=!0;$item$$.is_draft_ai=!0;$item$$.user_updated="AI_Agent";return $item$$});await $Ctds2$$.insertMany($updatedRecords$$);return{status:"success",message:`\u0110\u00e3 c\u1eadp nh\u1eadt th\u00e0nh c\u00f4ng K\u1ebf ho\u1ea1ch '${$groupDraft$$.group_name}'. \u0110\u00e3 ghi \u0111\u00e8 ${$updatedRecords$$.length} ch\u1ec9 ti\u00eau chi ti\u1ebft.`,
27
+ hint:`B\u00e1o c\u00e1o S\u1ebfp: "D\u1ea1 em \u0111\u00e3 c\u1eadp nh\u1eadt l\u1ea1i c\u00e1c ch\u1ec9 ti\u00eau trong k\u1ebf ho\u1ea1ch **${$groupDraft$$.group_name}** theo y\u00eau c\u1ea7u c\u1ee7a S\u1ebfp r\u1ed3i \u1ea1!"`}}catch($error$$){return console.error("[L\u1ed7i updateDraftBusinessPlanTool]:",$error$$),{status:"error",message:$error$$.message}}}}),manageProjectBudgetItemTool:tool({description:'C\u00f4ng c\u1ee5 TH\u00caM / S\u1eecA / X\u00d3A T\u1eeaNG D\u00d2NG CHI PH\u00cd trong K\u1ebf ho\u1ea1ch (Project Budget Item Editor).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp \u0111\u00e3 c\u00f3 m\u1ed9t b\u1ea3n k\u1ebf ho\u1ea1ch (KPI ho\u1eb7c Chi ph\u00ed d\u1ef1 \u00e1n) \u0110ANG NH\u00c1P \u0111\u00e3 l\u01b0u tr\u01b0\u1edbc \u0111\u00f3\n (qua saveBusinessPlanTool) v\u00e0 mu\u1ed1n:\n - TH\u00caM m\u1ed9t lo\u1ea1i chi ph\u00ed/ch\u1ec9 ti\u00eau m\u1edbi v\u00e0o k\u1ebf ho\u1ea1ch (action: "ADD")\n - X\u00d3A b\u1ecf m\u1ed9t d\u00f2ng chi ph\u00ed/ch\u1ec9 ti\u00eau kh\u1ecfi k\u1ebf ho\u1ea1ch (action: "REMOVE")\n - S\u1eecA s\u1ed1 ti\u1ec1n ho\u1eb7c th\u00f4ng tin c\u1ee7a m\u1ed9t d\u00f2ng \u0111\u00e3 c\u00f3 (action: "UPDATE")\n \n KH\u00d4NG D\u00d9NG khi S\u1ebfp mu\u1ed1n thay \u0111\u1ed5i TO\u00c0N B\u1ed8 c\u1ea5u tr\u00fac k\u1ebf ho\u1ea1ch c\u00f9ng l\u00fac (h\u00e3y d\u00f9ng\n updateDraftBusinessPlanTool ho\u1eb7c g\u1ecdi l\u1ea1i businessPlanningTool \u0111\u1ec3 thi\u1ebft k\u1ebf t\u1eeb \u0111\u1ea7u).\n \n \u26a1 L\u01afU \u00dd QUAN TR\u1eccNG: B\u1ea1n KH\u00d4NG c\u1ea7n t\u1ef1 l\u1ea5y v\u00e0 truy\u1ec1n l\u1ea1i to\u00e0n b\u1ed9 m\u1ea3ng chi ti\u1ebft c\u0169.\n Tool n\u00e0y t\u1ef1 \u0111\u1ed9ng \u0111\u1ecdc d\u1eef li\u1ec7u hi\u1ec7n c\u00f3 trong h\u1ec7 th\u1ed1ng, \u00e1p d\u1ee5ng \u0111\u00fang thay \u0111\u1ed5i \u0111\u01b0\u1ee3c y\u00eau c\u1ea7u,\n r\u1ed3i ghi l\u1ea1i. B\u1ea1n ch\u1ec9 c\u1ea7n cung c\u1ea5p \u0111\u00fang 1 d\u00f2ng chi ph\u00ed c\u1ea7n th\u00eam/s\u1eeda/x\u00f3a.',
28
+ inputSchema:z.object({group_keyword:z.string().describe('T\u00ean ho\u1eb7c t\u1eeb kh\u00f3a c\u1ee7a K\u1ebf ho\u1ea1ch c\u1ea7n ch\u1ec9nh s\u1eeda (VD: "K\u1ebf ho\u1ea1ch chi ph\u00ed D\u1ef1 \u00e1n Nh\u00e0 m\u00e1y A").'),action:z.enum(["ADD","UPDATE","REMOVE"]).describe("ADD: th\u00eam d\u00f2ng m\u1edbi. UPDATE: s\u1eeda d\u00f2ng \u0111\u00e3 c\u00f3 (t\u00ecm theo ma_chi_tieu). REMOVE: x\u00f3a d\u00f2ng (t\u00ecm theo ma_chi_tieu)."),ma_chi_tieu:z.string().describe("M\u00e3 \u0111\u1ecbnh danh d\u00f2ng c\u1ea7n S\u1eecA ho\u1eb7c X\u00d3A. N\u1ebfu action l\u00e0 ADD, \u0111\u00e2y l\u00e0 m\u00e3 m\u1edbi AI t\u1ef1 sinh (B\u1eaeT BU\u1ed8C kh\u00f4ng tr\u00f9ng v\u1edbi m\u00e3 \u0111\u00e3 c\u00f3 trong k\u1ebf ho\u1ea1ch)."),
29
+ item_data:z.record(z.any()).optional().describe("D\u1eef li\u1ec7u \u0111\u1ea7y \u0111\u1ee7 c\u1ee7a d\u00f2ng (theo c\u1ea5u tr\u00fac ctds2: nhan_chi_tieu, ma_phi, ma_dt, chi_tieu, cach_tinh, ct_theo, hl_tu_ngay, hl_den_ngay, tk_no...). B\u1eaeT BU\u1ed8C khi action l\u00e0 ADD ho\u1eb7c UPDATE. B\u1ecf tr\u1ed1ng khi action l\u00e0 REMOVE.")}),execute:async({group_keyword:$group_keyword$$,action:$action$$,ma_chi_tieu:$ma_chi_tieu$$,item_data:$item_data$$})=>{Logger.warn("[manageProjectBudgetItemTool]",
30
+ JSON.stringify({group_keyword:$group_keyword$$,action:$action$$,ma_chi_tieu:$ma_chi_tieu$$},null,2));try{const $Group$$=global.getModel("group"),$Ctds2$$=global.getModel("ctds2"),$foundGroups$$=await $Group$$.find({id_app:$id_app$$,group_type:{$in:["KPI","CPDA"]},group_name:{$regex:$group_keyword$$,$options:"i"}}).lean();if($foundGroups$$.length===0)return{status:"error",message:`Kh\u00f4ng t\u00ecm th\u1ea5y k\u1ebf ho\u1ea1ch n\u00e0o kh\u1edbp v\u1edbi t\u1eeb kh\u00f3a '${$group_keyword$$}'.`};
31
+ if($foundGroups$$.length>1){const $list$$=$foundGroups$$.map(($g$$,$i$$)=>`${$i$$+1}. **${$g$$.group_name}** (${$g$$.group_type==="CPDA"?"Chi ph\u00ed d\u1ef1 \u00e1n":"KPI"})`).join("\n");return{status:"need_clarification",message:`T\u00ecm th\u1ea5y ${$foundGroups$$.length} k\u1ebf ho\u1ea1ch kh\u1edbp v\u1edbi t\u1eeb kh\u00f3a '${$group_keyword$$}'.\nH\u01af\u1edaNG D\u1eaaN AI: H\u1ecfi S\u1ebfp mu\u1ed1n s\u1eeda k\u1ebf ho\u1ea1ch n\u00e0o:\n${$list$$}`}}const $group$$=$foundGroups$$[0],$group_id$$=
32
+ $group$$._id.toString();if($group$$.status===!0&&$group$$.is_draft_ai!==!0)return{status:"error",message:"L\u1ed6I QUY TR\u00ccNH: K\u1ebf ho\u1ea1ch n\u00e0y \u0110\u00c3 \u0110\u01af\u1ee2C PH\u00ca DUY\u1ec6T ch\u00ednh th\u1ee9c. AI kh\u00f4ng c\u00f3 quy\u1ec1n s\u1eeda tr\u1ef1c ti\u1ebfp. H\u00e3y b\u00e1o S\u1ebfp y\u00eau c\u1ea7u g\u1ee1 duy\u1ec7t tr\u00ean ph\u1ea7n m\u1ec1m tr\u01b0\u1edbc khi \u0111i\u1ec1u ch\u1ec9nh."};const $currentItems$$=await $Ctds2$$.find({id_app:$id_app$$,group_id:$group_id$$}).lean();
33
+ if($action$$==="REMOVE"){const $target$$=$currentItems$$.find($i$$=>$i$$.ma_chi_tieu===$ma_chi_tieu$$);if(!$target$$)return{status:"error",message:`Kh\u00f4ng t\u00ecm th\u1ea5y d\u00f2ng c\u00f3 m\u00e3 '${$ma_chi_tieu$$}' trong k\u1ebf ho\u1ea1ch '${$group$$.group_name}'.`};await $Ctds2$$.deleteOne({id_app:$id_app$$,group_id:$group_id$$,ma_chi_tieu:$ma_chi_tieu$$});const $remainingItems$$=$currentItems$$.filter($i$$=>$i$$.ma_chi_tieu!==$ma_chi_tieu$$),$tongMoi$$=$remainingItems$$.reduce(($a$$,$b$$)=>
34
+ $a$$+($b$$.chi_tieu||0),0);return{status:"success",message:`\u0110\u00e3 x\u00f3a d\u00f2ng '${$target$$.nhan_chi_tieu}' (${$ma_chi_tieu$$}) kh\u1ecfi k\u1ebf ho\u1ea1ch '${$group$$.group_name}'.`,tong_ngan_sach_moi:$tongMoi$$,so_dong_con_lai:$remainingItems$$.length,hint:`B\u00e1o S\u1ebfp: "D\u1ea1 em \u0111\u00e3 x\u00f3a d\u00f2ng **${$target$$.nhan_chi_tieu}** kh\u1ecfi k\u1ebf ho\u1ea1ch. T\u1ed5ng ng\u00e2n s\u00e1ch hi\u1ec7n t\u1ea1i c\u00f2n l\u1ea1i l\u00e0 **${$tongMoi$$.toLocaleString("vi-VN")} VN\u0110** (${$remainingItems$$.length} d\u00f2ng). S\u1ebfp c\u1ea7n \u0111i\u1ec1u ch\u1ec9nh th\u00eam g\u00ec kh\u00f4ng?"`}}if($action$$===
35
+ "ADD"){if(!$item_data$$)return{status:"error",message:"Thi\u1ebfu item_data khi action l\u00e0 ADD."};const $finalMaChiTieu$$=$item_data$$.ma_chi_tieu||$ma_chi_tieu$$,$dup$$=$currentItems$$.find($i$$=>$i$$.ma_chi_tieu===$finalMaChiTieu$$);if($dup$$)return{status:"error",message:`M\u00e3 ch\u1ec9 ti\u00eau '${$finalMaChiTieu$$}' \u0111\u00e3 t\u1ed3n t\u1ea1i trong k\u1ebf ho\u1ea1ch n\u00e0y (d\u00f2ng '${$dup$$.nhan_chi_tieu}'). H\u00e3y \u0111\u1ed5i m\u00e3 kh\u00e1c ho\u1eb7c d\u00f9ng action UPDATE n\u1ebfu mu\u1ed1n s\u1eeda d\u00f2ng \u0111\u00f3.`};
36
+ const $newItem$$={...$item_data$$,ma_chi_tieu:$finalMaChiTieu$$,stt:$item_data$$.stt||$currentItems$$.length+1,id_app:$id_app$$,group_id:$group_id$$,status:!0,is_draft_ai:!0};await global.controllers.CTDS2.createData($user_id$$,$newItem$$);const $tongMoi$$=$currentItems$$.reduce(($a$$,$b$$)=>$a$$+($b$$.chi_tieu||0),0)+($newItem$$.chi_tieu||0);return{status:"success",message:`\u0110\u00e3 th\u00eam d\u00f2ng m\u1edbi '${$newItem$$.nhan_chi_tieu}' (${$finalMaChiTieu$$}) v\u00e0o k\u1ebf ho\u1ea1ch '${$group$$.group_name}'.`,
37
+ tong_ngan_sach_moi:$tongMoi$$,so_dong_hien_tai:$currentItems$$.length+1,hint:`B\u00e1o S\u1ebfp: "D\u1ea1 em \u0111\u00e3 th\u00eam d\u00f2ng **${$newItem$$.nhan_chi_tieu}** (${($newItem$$.chi_tieu||0).toLocaleString("vi-VN")} VN\u0110) v\u00e0o k\u1ebf ho\u1ea1ch. T\u1ed5ng ng\u00e2n s\u00e1ch hi\u1ec7n t\u1ea1i: **${$tongMoi$$.toLocaleString("vi-VN")} VN\u0110**."`}}if($action$$==="UPDATE"){if(!$item_data$$)return{status:"error",message:"Thi\u1ebfu item_data khi action l\u00e0 UPDATE."};const $target$$=
38
+ $currentItems$$.find($i$$=>$i$$.ma_chi_tieu===$ma_chi_tieu$$);if(!$target$$)return{status:"error",message:`Kh\u00f4ng t\u00ecm th\u1ea5y d\u00f2ng c\u00f3 m\u00e3 '${$ma_chi_tieu$$}' trong k\u1ebf ho\u1ea1ch '${$group$$.group_name}' \u0111\u1ec3 s\u1eeda.`};const $updatePayload$$={...$item_data$$,date_updated:new Date};delete $updatePayload$$._id;delete $updatePayload$$.id_app;delete $updatePayload$$.group_id;await $Ctds2$$.updateOne({id_app:$id_app$$,group_id:$group_id$$,ma_chi_tieu:$ma_chi_tieu$$},
39
+ {$set:$updatePayload$$});const $tongMoi$$=$currentItems$$.reduce(($a$$,$b$$)=>$b$$.ma_chi_tieu===$ma_chi_tieu$$?$a$$+($item_data$$.chi_tieu??$b$$.chi_tieu??0):$a$$+($b$$.chi_tieu||0),0);return{status:"success",message:`\u0110\u00e3 c\u1eadp nh\u1eadt d\u00f2ng '${$ma_chi_tieu$$}' trong k\u1ebf ho\u1ea1ch '${$group$$.group_name}'.`,tong_ngan_sach_moi:$tongMoi$$,hint:`B\u00e1o S\u1ebfp: "D\u1ea1 em \u0111\u00e3 c\u1eadp nh\u1eadt d\u00f2ng **${$item_data$$.nhan_chi_tieu||$target$$.nhan_chi_tieu}**. T\u1ed5ng ng\u00e2n s\u00e1ch hi\u1ec7n t\u1ea1i: **${$tongMoi$$.toLocaleString("vi-VN")} VN\u0110**."`}}}catch($error$$){return console.error("[L\u1ed7i manageProjectBudgetItemTool]:",
40
+ $error$$),{status:"error",message:`L\u1ed7i khi \u0111i\u1ec1u ch\u1ec9nh d\u00f2ng chi ph\u00ed: ${$error$$.message}`}}}}),exportProjectBudgetTableTool:tool({description:'C\u00f4ng c\u1ee5 XU\u1ea4T B\u1ea2NG D\u1ef0 TO\u00c1N CHI PH\u00cd D\u1ef0 \u00c1N CHUY\u00caN NGHI\u1ec6P (Export Project Budget Table).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp y\u00eau c\u1ea7u "xu\u1ea5t b\u1ea3ng ng\u00e2n s\u00e1ch d\u1ef1 \u00e1n X", "cho xem b\u1ea3ng d\u1ef1 to\u00e1n \u0111\u1ea7y \u0111\u1ee7",\n "t\u1ed5ng h\u1ee3p chi ph\u00ed d\u1ef1 \u00e1n", "in b\u00e1o c\u00e1o ng\u00e2n s\u00e1ch", sau khi \u0111\u00e3 c\u00f3 m\u1ed9t K\u1ebf ho\u1ea1ch Chi ph\u00ed D\u1ef1 \u00e1n\n (group_type: "CPDA") \u0111\u01b0\u1ee3c thi\u1ebft k\u1ebf qua businessPlanningTool v\u00e0 l\u01b0u qua saveBusinessPlanTool\n (c\u00f3 th\u1ec3 \u0111\u00e3 \u0111\u01b0\u1ee3c ch\u1ec9nh s\u1eeda th\u00eam qua manageProjectBudgetItemTool).\n \n Tool t\u1ef1 \u0111\u1ed9ng GOM NH\u00d3M c\u00e1c d\u00f2ng chi ph\u00ed theo NH\u00d3M CHI PH\u00cd CHA (d\u1ef1a v\u00e0o ph\u00e2n c\u1ea5p ma_phi_me\n trong danh m\u1ee5c dmphi) \u0111\u1ec3 ra m\u1ed9t b\u1ea3ng c\u00f3 c\u1ea5u tr\u00fac 2 c\u1ea5p (Nh\u00f3m cha \u2192 c\u00e1c d\u00f2ng chi ph\u00ed con),\n c\u00f3 subtotal t\u1eebng nh\u00f3m v\u00e0 t\u1ed5ng c\u1ed9ng to\u00e0n d\u1ef1 \u00e1n \u2014 gi\u1ed1ng m\u1ed9t b\u1ea3ng d\u1ef1 to\u00e1n chuy\u00ean nghi\u1ec7p th\u1eadt.\n \n KH\u00d4NG D\u00d9NG \u0111\u1ec3 T\u1ea0O ho\u1eb7c S\u1eecA k\u1ebf ho\u1ea1ch (d\u00f9ng businessPlanningTool / manageProjectBudgetItemTool).\n Tool n\u00e0y CH\u1ec8 \u0110\u1eccC d\u1eef li\u1ec7u \u0111\u00e3 c\u00f3 v\u00e0 tr\u00ecnh b\u00e0y l\u1ea1i.',
41
+ inputSchema:z.object({ma_dt:z.string().optional().describe("M\u00e3 d\u1ef1 \u00e1n/v\u1ee5 vi\u1ec7c (\u0111\u00e3 resolve qua lookupMasterDataTool n\u1ebfu S\u1ebfp ch\u1ec9 nh\u1eafc t\u00ean). D\u00f9ng khi S\u1ebfp nh\u1eafc \u0111\u1ebfn d\u1ef1 \u00e1n b\u1eb1ng m\u00e3/t\u00ean, kh\u00f4ng nh\u1edb t\u00ean k\u1ebf ho\u1ea1ch."),group_keyword:z.string().optional().describe('T\u00ean ho\u1eb7c t\u1eeb kh\u00f3a c\u1ee7a K\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n (VD: "K\u1ebf ho\u1ea1ch chi ph\u00ed Nh\u00e0 m\u00e1y A"). D\u00f9ng khi S\u1ebfp nh\u1eafc tr\u1ef1c ti\u1ebfp t\u00ean k\u1ebf ho\u1ea1ch, ho\u1eb7c khi 1 d\u1ef1 \u00e1n c\u00f3 nhi\u1ec1u b\u1ea3n d\u1ef1 to\u00e1n kh\u00e1c nhau c\u1ea7n ph\u00e2n bi\u1ec7t.'),
42
+ so_sanh_thuc_te:z.boolean().optional().describe("true n\u1ebfu S\u1ebfp mu\u1ed1n so s\u00e1nh D\u1ef1 to\u00e1n v\u1edbi CHI PH\u00cd TH\u1ef0C T\u1ebe \u0111\u00e3 ph\u00e1t sinh (Budget vs Actual). M\u1eb7c \u0111\u1ecbnh false \u2014 ch\u1ec9 xu\u1ea5t b\u1ea3ng d\u1ef1 to\u00e1n thu\u1ea7n.")}),execute:async({ma_dt:$ma_dt_tenDuAn$$,group_keyword:$group_keyword$$,so_sanh_thuc_te:$so_sanh_thuc_te$$})=>{Logger.warn("[exportProjectBudgetTableTool]",JSON.stringify({ma_dt:$ma_dt_tenDuAn$$,group_keyword:$group_keyword$$,
43
+ so_sanh_thuc_te:$so_sanh_thuc_te$$},null,2));try{if(!$ma_dt_tenDuAn$$&&!$group_keyword$$)return{status:"error",message:"C\u1ea7n cung c\u1ea5p \u00edt nh\u1ea5t m\u00e3 d\u1ef1 \u00e1n (ma_dt) ho\u1eb7c t\u00ean k\u1ebf ho\u1ea1ch (group_keyword) \u0111\u1ec3 xu\u1ea5t b\u1ea3ng."};const $Group$$=global.getModel("group"),$Ctds2$$=global.getModel("ctds2"),$Dmphi$$=global.getModel("dmphi");var $Dmdt_data$$=global.getModel("dmdt");let $targetGroup$$=null;if($group_keyword$$){const $found$$=await $Group$$.find({id_app:$id_app$$,
44
+ group_type:"CPDA",group_name:{$regex:$group_keyword$$,$options:"i"}}).lean();if($found$$.length===0)return{status:"error",message:`Kh\u00f4ng t\u00ecm th\u1ea5y K\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n n\u00e0o kh\u1edbp v\u1edbi t\u1eeb kh\u00f3a '${$group_keyword$$}'.`};if($found$$.length>1){const $list$$=$found$$.map(($g$$,$i$$)=>`${$i$$+1}. **${$g$$.group_name}**${$g$$.is_draft_ai?" (nh\u00e1p)":" (\u0111\u00e3 duy\u1ec7t)"}`).join("\n");return{status:"need_clarification",message:`T\u00ecm th\u1ea5y ${$found$$.length} k\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n kh\u1edbp v\u1edbi t\u1eeb kh\u00f3a '${$group_keyword$$}'.\nH\u01af\u1edaNG D\u1eaaN AI: H\u1ecfi S\u1ebfp mu\u1ed1n xu\u1ea5t b\u1ea3ng c\u1ee7a k\u1ebf ho\u1ea1ch n\u00e0o:\n${$list$$}`}}$targetGroup$$=
45
+ $found$$[0]}else{const $rowsWithMaDt$$=await $Ctds2$$.find({id_app:$id_app$$,ma_dt:$ma_dt_tenDuAn$$}).lean();if($rowsWithMaDt$$.length===0)return{status:"error",message:`Kh\u00f4ng t\u00ecm th\u1ea5y d\u00f2ng chi ph\u00ed n\u00e0o g\u1eafn v\u1edbi m\u00e3 d\u1ef1 \u00e1n '${$ma_dt_tenDuAn$$}'. Ki\u1ec3m tra l\u1ea1i m\u00e3 d\u1ef1 \u00e1n ho\u1eb7c d\u00f9ng group_keyword.`};const $candidateGroupIds$$=[...(new Set($rowsWithMaDt$$.map($r$$=>$r$$.group_id)))],$candidateGroups$$=await $Group$$.find({id_app:$id_app$$,
46
+ group_type:"CPDA",_id:{$in:$candidateGroupIds$$}}).lean();if($candidateGroups$$.length===0)return{status:"error",message:`D\u1ef1 \u00e1n '${$ma_dt_tenDuAn$$}' ch\u01b0a c\u00f3 K\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n (CPDA) n\u00e0o \u0111\u01b0\u1ee3c l\u01b0u.`};if($candidateGroups$$.length>1){const $list$$=$candidateGroups$$.map(($g$$,$i$$)=>`${$i$$+1}. **${$g$$.group_name}**${$g$$.is_draft_ai?" (nh\u00e1p)":" (\u0111\u00e3 duy\u1ec7t)"}`).join("\n");return{status:"need_clarification",
47
+ message:`D\u1ef1 \u00e1n '${$ma_dt_tenDuAn$$}' c\u00f3 ${$candidateGroups$$.length} b\u1ea3n k\u1ebf ho\u1ea1ch chi ph\u00ed kh\u00e1c nhau.\nH\u01af\u1edaNG D\u1eaaN AI: H\u1ecfi S\u1ebfp mu\u1ed1n xu\u1ea5t b\u1ea3ng c\u1ee7a k\u1ebf ho\u1ea1ch n\u00e0o:\n${$list$$}`}}$targetGroup$$=$candidateGroups$$[0]}const $rows$$=await $Ctds2$$.find({id_app:$id_app$$,group_id:$targetGroup$$._id.toString()}).lean();if($rows$$.length===0)return{status:"success_empty",message:`K\u1ebf ho\u1ea1ch '${$targetGroup$$.group_name}' ch\u01b0a c\u00f3 d\u00f2ng chi ph\u00ed n\u00e0o.`};
48
+ const $usedMaPhi$$=[...(new Set($rows$$.map($r$$=>$r$$.ma_phi).filter(Boolean)))],$leafDocs$$=$usedMaPhi$$.length?await $Dmphi$$.find({id_app:$id_app$$,ma_phi:{$in:$usedMaPhi$$}}).lean():[],$leafMap$$=new Map($leafDocs$$.map($d$$=>[$d$$.ma_phi,$d$$])),$parentCodes$$=[...(new Set($leafDocs$$.map($d$$=>$d$$.ma_phi_me).filter(Boolean)))],$parentDocs$$=$parentCodes$$.length?await $Dmphi$$.find({id_app:$id_app$$,ma_phi:{$in:$parentCodes$$}}).lean():[],$parentMap$$=new Map($parentDocs$$.map($d$$=>[$d$$.ma_phi,
49
+ $d$$])),$actualByRowId$$=new Map;$so_sanh_thuc_te$$&&await Promise.all($rows$$.map(async $row$$=>{try{const $ngayCt$$={$gte:moment($row$$.hl_tu_ngay).startOf("day").toDate(),$lte:moment($row$$.hl_den_ngay).endOf("day").toDate()};if($row$$.cach_tinh==="1"&&($row$$.tk_no&&$row$$.tk_no.length||$row$$.tk_co&&$row$$.tk_co.length)){const $ps$$=await calcSoCai({ma_phi:$row$$.ma_phi,ma_dt:$row$$.ma_dt,ma_bp:$row$$.ma_bp,ma_kho:$row$$.ma_kho,tk_no:$row$$.tk_no,tk_no_gt:$row$$.tk_no_gt,tk_co:$row$$.tk_co,tk_co_gt:$row$$.tk_co_gt},
50
+ {id_app:$id_app$$,ngay_ct:$ngayCt$$});$actualByRowId$$.set($row$$._id.toString(),$ps$$)}else if($row$$.cach_tinh==="2"||$row$$.cach_tinh==="3"){const $dt$$=await calcDoanhThu({ma_phi:$row$$.ma_phi,ma_dt:$row$$.ma_dt,ma_bp:$row$$.ma_bp,ma_kho:$row$$.ma_kho},{id_app:$id_app$$,ngay_ct:$ngayCt$$});$actualByRowId$$.set($row$$._id.toString(),$row$$.cach_tinh==="2"?$dt$$.t_dt:$dt$$.t_ds)}else $actualByRowId$$.set($row$$._id.toString(),null)}catch($e$$){Logger.error(`[exportProjectBudgetTableTool] L\u1ed7i t\u00ednh th\u1ef1c t\u1ebf cho d\u00f2ng ${$row$$.ma_chi_tieu}:`,
51
+ $e$$),$actualByRowId$$.set($row$$._id.toString(),null)}}));const $groupsMap$$=new Map;$rows$$.forEach($row$$=>{var $JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$=$leafMap$$.get($row$$.ma_phi);$JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$=$JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$?.ma_phi_me&&$parentMap$$.has($JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$.ma_phi_me)?{key:$JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$.ma_phi_me,label:$parentMap$$.get($JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$.ma_phi_me).ten_phi}:
52
+ {key:$row$$.ma_phi||`__row_${$row$$._id}`,label:$JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$?.ten_phi||$row$$.nhan_chi_tieu||"Kh\u00e1c"};const {key:$key$$,label:$label$$}=$JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$;$groupsMap$$.has($key$$)||$groupsMap$$.set($key$$,{ten_nhom:$label$$,min_stt:$row$$.stt??9999,items:[]});$JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$=$groupsMap$$.get($key$$);$JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$.min_stt=Math.min($JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$.min_stt,
53
+ $row$$.stt??9999);const $item$$={stt:$row$$.stt??null,ma_chi_tieu:$row$$.ma_chi_tieu,ma_phi:$row$$.ma_phi||null,ten_chi_phi:$row$$.nhan_chi_tieu,du_toan:$row$$.chi_tieu||0};$so_sanh_thuc_te$$&&($row$$=$actualByRowId$$.get($row$$._id.toString()),$item$$.thuc_te=$row$$,$item$$.con_lai=$row$$===null||$row$$===void 0?null:$item$$.du_toan-$row$$);$JSCompiler_inline_result$jscomp$0_g$jscomp$4_leaf$$.items.push($item$$)});const $tongNganSach$$=$rows$$.reduce(($a$$,$b$$)=>$a$$+($b$$.chi_tieu||0),0),$tongThucTe$$=
54
+ $so_sanh_thuc_te$$?[...$actualByRowId$$.values()].filter($v$$=>$v$$!==null&&$v$$!==void 0).reduce(($a$$,$b$$)=>$a$$+$b$$,0):null,$nhomChiPhi$$=[...$groupsMap$$.entries()].map(([$key$$,$g$$])=>{$g$$.items.sort(($a$$,$b$$)=>($a$$.stt??9999)-($b$$.stt??9999));const $tongNhom$$=$g$$.items.reduce(($a$$,$b$$)=>$a$$+($b$$.du_toan||0),0);$key$$={ma_nhom:$key$$,ten_nhom:$g$$.ten_nhom,items:$g$$.items,tong_nhom:$tongNhom$$,ty_le_tren_tong:$tongNganSach$$>0?Math.round($tongNhom$$/$tongNganSach$$*1E3)/10:0};
55
+ if($so_sanh_thuc_te$$){const $tongThucTeNhom$$=$g$$.items.reduce(($a$$,$b$$)=>$a$$+($b$$.thuc_te||0),0);$key$$.tong_thuc_te_nhom=$tongThucTeNhom$$;$key$$.con_lai_nhom=$tongNhom$$-$tongThucTeNhom$$}return{...$key$$,_min_stt:$g$$.min_stt}}).sort(($a$$,$b$$)=>$a$$._min_stt-$b$$._min_stt).map(({_min_stt:$_min_stt$$,...$rest$$})=>$rest$$),$projectMaDt$$=$rows$$.find($r$$=>$r$$.ma_dt)?.ma_dt||$ma_dt_tenDuAn$$||null;$ma_dt_tenDuAn$$=null;$projectMaDt$$&&$Dmdt_data$$&&($ma_dt_tenDuAn$$=(await $Dmdt_data$$.findOne({id_app:$id_app$$,
56
+ ma_dt:$projectMaDt$$}).lean())?.ten_dt||null);const $hlTuNgay$$=$rows$$.reduce(($min$$,$r$$)=>!$min$$||new Date($r$$.hl_tu_ngay)<new Date($min$$)?$r$$.hl_tu_ngay:$min$$,null),$hlDenNgay$$=$rows$$.reduce(($max$$,$r$$)=>!$max$$||new Date($r$$.hl_den_ngay)>new Date($max$$)?$r$$.hl_den_ngay:$max$$,null);$Dmdt_data$$={nhom_chi_phi:$nhomChiPhi$$,tong_ngan_sach:$tongNganSach$$};$so_sanh_thuc_te$$&&($Dmdt_data$$.tong_thuc_te=$tongThucTe$$,$Dmdt_data$$.con_lai=$tongNganSach$$-($tongThucTe$$||0),$Dmdt_data$$.ty_le_giai_ngan=
57
+ $tongNganSach$$>0?Math.round(($tongThucTe$$||0)/$tongNganSach$$*1E3)/10:0);return{status:"success",group_info:{code:$targetGroup$$._id.toString(),group_name:$targetGroup$$.group_name,mo_ta:$targetGroup$$.mo_ta||$targetGroup$$.description||null,da_duyet:$targetGroup$$.is_draft_ai!==!0,ma_dt:$projectMaDt$$,ten_du_an:$ma_dt_tenDuAn$$,hl_tu_ngay:$hlTuNgay$$,hl_den_ngay:$hlDenNgay$$},data:$Dmdt_data$$,hint:`QUY T\u1eaeC TR\u00ccNH B\u00c0Y B\u1eaeT BU\u1ed8C (B\u1ea2NG D\u1ef0 TO\u00c1N CHI PH\u00cd D\u1ef0 \u00c1N CHUY\u00caN NGHI\u1ec6P):
58
+
59
+ 1. TI\u00caU \u0110\u1ec0: M\u1edf \u0111\u1ea7u b\u1eb1ng heading "\ud83d\udcca B\u1ea2NG D\u1ef0 TO\u00c1N CHI PH\u00cd D\u1ef0 \u00c1N" k\u00e8m t\u00ean d\u1ef1 \u00e1n (ten_du_an n\u1ebfu c\u00f3, kh\u00f4ng th\u00ec d\u00f9ng ma_dt).
60
+
61
+ 2. B\u1ea2NG TH\u00d4NG TIN D\u1ef0 \u00c1N (markdown table 2 c\u1ed9t "Th\u00f4ng tin" | "Chi ti\u1ebft"), g\u1ed3m c\u00e1c d\u00f2ng:
62
+ T\u00ean d\u1ef1 \u00e1n, M\u00e3 d\u1ef1 \u00e1n, T\u00ean k\u1ebf ho\u1ea1ch (group_name), Tr\u1ea1ng th\u00e1i (da_duyet: true \u2192 "\u0110\u00e3 duy\u1ec7t ch\u00ednh th\u1ee9c", false \u2192 "Nh\u00e1p - ch\u1edd duy\u1ec7t"),
63
+ Th\u1eddi gian hi\u1ec7u l\u1ef1c (hl_tu_ngay \u2192 hl_den_ngay), v\u00e0 T\u1ed4NG NG\u00c2N S\u00c1CH (in \u0111\u1eadm, \u0111\u1ecbnh d\u1ea1ng s\u1ed1 c\u00f3 ph\u00e2n c\u00e1ch h\u00e0ng ngh\u00ecn + " VN\u0110").
64
+
65
+ 3. B\u1ea2NG CHI TI\u1ebeT THEO NH\u00d3M CHI PH\u00cd: V\u1edbi M\u1ed6I nh\u00f3m trong 'nhom_chi_phi' (gi\u1eef \u0111\u00fang th\u1ee9 t\u1ef1 \u0111\u00e3 tr\u1ea3 v\u1ec1,
66
+ KH\u00d4NG t\u1ef1 s\u1eafp x\u1ebfp l\u1ea1i), tr\u00ecnh b\u00e0y:
67
+ a. D\u00f2ng ti\u00eau \u0111\u1ec1 nh\u00f3m in \u0111\u1eadm: "**[ten_nhom]**"
68
+ b. B\u1ea3ng con li\u1ec7t k\u00ea t\u1eebng d\u00f2ng trong 'items': c\u1ed9t M\u00e3 | T\u00ean chi ph\u00ed | D\u1ef1 to\u00e1n (VN\u0110)${$so_sanh_thuc_te$$?" | Th\u1ef1c t\u1ebf (VN\u0110) | C\u00f2n l\u1ea1i (VN\u0110)":""}.
69
+ N\u1ebfu 'thuc_te' l\u00e0 null, ghi "\u2014" (kh\u00f4ng c\u00f3 d\u1eef li\u1ec7u theo d\u00f5i t\u1ef1 \u0111\u1ed9ng cho d\u00f2ng n\u00e0y).
70
+ c. D\u00f2ng SUBTOTAL in \u0111\u1eadm ngay sau b\u1ea3ng con: "C\u1ed9ng nh\u00f3m [ten_nhom]: [tong_nhom] VN\u0110 (chi\u1ebfm [ty_le_tren_tong]% t\u1ed5ng ng\u00e2n s\u00e1ch)"${$so_sanh_thuc_te$$?", k\u00e8m Th\u1ef1c t\u1ebf nh\u00f3m = tong_thuc_te_nhom, C\u00f2n l\u1ea1i nh\u00f3m = con_lai_nhom":""}.
71
+
72
+ 4. D\u00d2NG T\u1ed4NG C\u1ed8NG TO\u00c0N D\u1ef0 \u00c1N: Sau khi li\u1ec7t k\u00ea h\u1ebft c\u00e1c nh\u00f3m, in 1 d\u00f2ng l\u1edbn, in \u0111\u1eadm, n\u1ed5i b\u1eadt:
73
+ "T\u1ed4NG NG\u00c2N S\u00c1CH D\u1ef0 \u00c1N: [tong_ngan_sach] VN\u0110"${$so_sanh_thuc_te$$?'. K\u00e8m th\u00eam "\u0110\u00e3 th\u1ef1c hi\u1ec7n: [tong_thuc_te] VN\u0110 (t\u1ef7 l\u1ec7 gi\u1ea3i ng\u00e2n: [ty_le_giai_ngan]%)" v\u00e0 "C\u00f2n l\u1ea1i ch\u01b0a chi: [con_lai] VN\u0110".':"."}
74
+
75
+ ${$so_sanh_thuc_te$$?"5. C\u1ea2NH B\u00c1O: N\u1ebfu nh\u00f3m ho\u1eb7c d\u00f2ng n\u00e0o c\u00f3 Th\u1ef1c t\u1ebf V\u01af\u1ee2T D\u1ef1 to\u00e1n (con_lai ho\u1eb7c con_lai_nhom < 0), in \u0111\u1eadm/n\u00eau r\u00f5 c\u1ea3nh b\u00e1o ri\u00eang ngay sau b\u1ea3ng, KH\u00d4NG ch\u1ec9 \u0111\u1ec3 \u1ea9n trong s\u1ed1 li\u1ec7u.\n\n6":"5"}. K\u1ebeT TH\u00daC b\u1eb1ng c\u00e2u h\u1ecfi: "S\u1ebfp c\u00f3 mu\u1ed1n em \u0111i\u1ec1u ch\u1ec9nh th\u00eam d\u00f2ng chi ph\u00ed n\u00e0o trong k\u1ebf ho\u1ea1ch n\u00e0y kh\u00f4ng, ho\u1eb7c g\u1eedi b\u1ea3ng n\u00e0y qua email cho ai kh\u00f4ng \u1ea1?"
76
+
77
+ \u26a0\ufe0f TUY\u1ec6T \u0110\u1ed0I KH\u00d4NG b\u1ecba th\u00eam d\u00f2ng/nh\u00f3m/s\u1ed1 li\u1ec7u kh\u00f4ng c\u00f3 trong 'data' tr\u1ea3 v\u1ec1. N\u1ebfu S\u1ebfp mu\u1ed1n th\u00eam/s\u1eeda/x\u00f3a d\u00f2ng chi ph\u00ed, \u0111\u00f3 l\u00e0 vi\u1ec7c c\u1ee7a manageProjectBudgetItemTool, KH\u00d4NG x\u1eed l\u00fd trong c\u00e2u tr\u1ea3 l\u1eddi n\u00e0y.`}}catch($error$$){return console.error("[L\u1ed7i exportProjectBudgetTableTool]:",$error$$),{status:"error",message:`L\u1ed7i khi xu\u1ea5t b\u1ea3ng ng\u00e2n s\u00e1ch d\u1ef1 \u00e1n: ${$error$$.message}`}}}}),
78
+ reviewDraftPlansTool:tool({description:"C\u00f4ng c\u1ee5 XEM V\u00c0 TR\u00ccNH K\u00dd K\u1ebe HO\u1ea0CH NH\u00c1P (Review Draft Plans).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp y\u00eau c\u1ea7u xem danh s\u00e1ch c\u00e1c k\u1ebf ho\u1ea1ch (KPI ho\u1eb7c Chi ph\u00ed d\u1ef1 \u00e1n) \u0111ang ch\u1edd duy\u1ec7t, HO\u1eb6C xem chi ti\u1ebft c\u00e1c con s\u1ed1 c\u1ee7a m\u1ed9t k\u1ebf ho\u1ea1ch c\u1ee5 th\u1ec3 tr\u01b0\u1edbc khi quy\u1ebft \u0111\u1ecbnh duy\u1ec7t hay s\u1eeda.",inputSchema:z.object({action:z.enum(["LIST",
79
+ "VIEW_DETAIL"]).describe("LIST: S\u1ebfp mu\u1ed1n xem c\u00f3 bao nhi\u00eau b\u1ea3n nh\u00e1p \u0111ang ch\u1edd. VIEW_DETAIL: S\u1ebfp mu\u1ed1n xem chi ti\u1ebft c\u00e1c ch\u1ec9 ti\u00eau c\u1ee7a 1 k\u1ebf ho\u1ea1ch c\u1ee5 th\u1ec3."),group_keyword:z.string().optional().describe('B\u1eaeT BU\u1ed8C c\u00f3 n\u1ebfu action l\u00e0 VIEW_DETAIL. (VD: "K\u1ebf ho\u1ea1ch b\u00e1n h\u00e0ng Q3")'),plan_type:z.enum(["KPI","CPDA","ALL"]).optional().describe('L\u1ecdc theo lo\u1ea1i k\u1ebf ho\u1ea1ch khi action=LIST. "KPI": ch\u1ec9 k\u1ebf ho\u1ea1ch kinh doanh/ch\u1ec9 ti\u00eau. "CPDA": ch\u1ec9 k\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n. "ALL" ho\u1eb7c b\u1ecf tr\u1ed1ng: xem c\u1ea3 2 lo\u1ea1i.')}),
80
+ execute:async({action:$action$$,group_keyword:$group_keyword$$,plan_type:$plan_type$$})=>{try{const $Group$$=global.getModel("group"),$Ctds2$$=global.getModel("ctds2");if($action$$==="LIST"){const $draftGroups$$=await $Group$$.find({id_app:$id_app$$,group_type:{$in:$plan_type$$&&$plan_type$$!=="ALL"?[$plan_type$$]:["KPI","CPDA"]},is_draft_ai:!0}).lean();return $draftGroups$$.length===0?{status:"success_empty",message:"Hi\u1ec7n kh\u00f4ng c\u00f3 k\u1ebf ho\u1ea1ch n\u00e0o \u0111ang ch\u1edd duy\u1ec7t."}:
81
+ {status:"success",data:$draftGroups$$.map($g$$=>({...$g$$,loai_ke_hoach:$g$$.group_type==="CPDA"?"K\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n":"K\u1ebf ho\u1ea1ch KPI"})),hint:'QUY T\u1eaeC B\u00c1O C\u00c1O: \n 1. Li\u1ec7t k\u00ea danh s\u00e1ch c\u00e1c nh\u00f3m k\u1ebf ho\u1ea1ch \u0111ang ch\u1edd duy\u1ec7t (T\u00ean k\u1ebf ho\u1ea1ch, Lo\u1ea1i k\u1ebf ho\u1ea1ch, ng\u00e0y t\u1ea1o).\n 2. H\u1eceI S\u1ebeP: "S\u1ebfp mu\u1ed1n xem chi ti\u1ebft con s\u1ed1 c\u1ee7a k\u1ebf ho\u1ea1ch n\u00e0o \u0111\u1ec3 duy\u1ec7t \u1ea1?"'}}if($action$$===
82
+ "VIEW_DETAIL"){if(!$group_keyword$$)return{status:"error",message:"Thi\u1ebfu t\u1eeb kh\u00f3a t\u00ean k\u1ebf ho\u1ea1ch."};const $foundGroup$$=await $Group$$.findOne({id_app:$id_app$$,group_type:{$in:["KPI","CPDA"]},is_draft_ai:!0,group_name:{$regex:$group_keyword$$,$options:"i"}}).lean();if(!$foundGroup$$)return{status:"error",message:`Kh\u00f4ng t\u00ecm th\u1ea5y b\u1ea3n nh\u00e1p n\u00e0o t\u00ean '${$group_keyword$$}'.`};const $details$$=await $Ctds2$$.find({id_app:$id_app$$,group_id:$foundGroup$$._id.toString()}).lean();
83
+ return{status:"success",group_info:$foundGroup$$,data:$details$$,hint:`QUY T\u1eaeC B\u00c1O C\u00c1O:
84
+ 1. Tr\u00ecnh b\u00e0y b\u1ea3ng ch\u1ec9 ti\u00eau chi ti\u1ebft c\u1ee7a k\u1ebf ho\u1ea1ch '${$foundGroup$$.group_name}'.
85
+ 2. G\u1ee2I \u00dd C\u00c1C L\u1ef0A CH\u1eccN: S\u1ebfp c\u00f3 3 l\u1ef1a ch\u1ecdn l\u00fac n\u00e0y:
86
+ - N\u1ebfu s\u1ed1 li\u1ec7u OK: B\u1ea5m "Duy\u1ec7t / \u00c1p d\u1ee5ng".
87
+ - N\u1ebfu s\u1ed1 li\u1ec7u ch\u01b0a OK: Y\u00eau c\u1ea7u AI s\u1eeda (VD: "Gi\u1ea3m 10%", ho\u1eb7c d\u00f9ng manageProjectBudgetItemTool n\u1ebfu ch\u1ec9 s\u1eeda 1 d\u00f2ng).
88
+ - Ho\u1eb7c b\u1ecf qua.`}}}catch($e$$){return{status:"error",message:$e$$.message}}}}),approveBusinessPlanTool:tool({description:'C\u00f4ng c\u1ee5 PH\u00ca DUY\u1ec6T \u00c1P D\u1ee4NG K\u1ebe HO\u1ea0CH (Approve Plan).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp ra l\u1ec7nh "Duy\u1ec7t", "Ch\u1ed1t", "\u0110\u1ed3ng \u00fd", "\u00c1p d\u1ee5ng ch\u00ednh th\u1ee9c" cho m\u1ed9t k\u1ebf ho\u1ea1ch KPI ho\u1eb7c Chi ph\u00ed d\u1ef1 \u00e1n c\u1ee5 th\u1ec3.',inputSchema:z.object({group_keyword:z.string().describe('T\u1eeb kh\u00f3a t\u00ean c\u1ee7a K\u1ebf ho\u1ea1ch S\u1ebfp mu\u1ed1n duy\u1ec7t (VD: "K\u1ebf ho\u1ea1ch Sale", "K\u1ebf ho\u1ea1ch chi ph\u00ed D\u1ef1 \u00e1n A").')}),
89
+ execute:async({group_keyword:$group_keyword$$})=>{try{const $Group$$=global.getModel("group"),$Ctds2$$=global.getModel("ctds2"),$draftGroups$$=await $Group$$.find({id_app:$id_app$$,group_type:{$in:["KPI","CPDA"]},is_draft_ai:!0,group_name:{$regex:$group_keyword$$,$options:"i"}}).lean();if($draftGroups$$.length===0)return{status:"error",message:`Kh\u00f4ng t\u00ecm th\u1ea5y k\u1ebf ho\u1ea1ch NH\u00c1P (ch\u1edd duy\u1ec7t) n\u00e0o kh\u1edbp v\u1edbi t\u1eeb kh\u00f3a '${$group_keyword$$}'. C\u00f3 th\u1ec3 k\u1ebf ho\u1ea1ch kh\u00f4ng t\u1ed3n t\u1ea1i, sai t\u00ean, ho\u1eb7c \u0110\u00c3 \u0110\u01af\u1ee2C DUY\u1ec6T tr\u01b0\u1edbc \u0111\u00f3.`};
90
+ if($draftGroups$$.length>1){const $danhSachKeHoach$$=$draftGroups$$.map(($g$$,$index$$)=>`${$index$$+1}. **${$g$$.group_name}** (${$g$$.group_type==="CPDA"?"Chi ph\u00ed d\u1ef1 \u00e1n":"KPI"})`).join("\n");return{status:"need_clarification",message:`[C\u1ea2NH B\u00c1O TR\u00d9NG L\u1eb6P]: H\u1ec7 th\u1ed1ng t\u00ecm th\u1ea5y ${$draftGroups$$.length} k\u1ebf ho\u1ea1ch nh\u00e1p \u0111ang ch\u1edd duy\u1ec7t c\u00f3 ch\u1ee9a t\u1eeb kh\u00f3a '${$group_keyword$$}'.\n\nH\u01af\u1edaNG D\u1eaaN AI: H\u00e3y d\u1eebng l\u1ea1i v\u00e0 y\u00eau c\u1ea7u S\u1ebfp x\u00e1c nh\u1eadn mu\u1ed1n DUY\u1ec6T CH\u00cdNH TH\u1ee8C b\u1ea3n k\u1ebf ho\u1ea1ch n\u00e0o trong danh s\u00e1ch sau:\n${$danhSachKeHoach$$}`}}const $groupDraft$$=
91
+ $draftGroups$$[0];await $Group$$.updateOne({_id:$groupDraft$$._id},{$set:{status:!0,is_draft_ai:!1,date_updated:new Date}});const $updateResult$$=await $Ctds2$$.updateMany({id_app:$id_app$$,group_id:$groupDraft$$._id.toString()},{$set:{status:!0,is_draft_ai:!1,date_updated:new Date}});return{status:"success",message:`\u0110\u00e3 duy\u1ec7t th\u00e0nh c\u00f4ng Nh\u00f3m k\u1ebf ho\u1ea1ch '${$groupDraft$$.group_name}'. C\u1eadp nh\u1eadt ${$updateResult$$.modifiedCount} ch\u1ec9 ti\u00eau.`,hint:`B\u00e1o c\u00e1o S\u1ebfp: "D\u1ea1 em \u0111\u00e3 ph\u00ea duy\u1ec7t v\u00e0 \u00e1p d\u1ee5ng ch\u00ednh th\u1ee9c ${$groupDraft$$.group_type===
92
+ "CPDA"?"k\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n":"k\u1ebf ho\u1ea1ch"} **${$groupDraft$$.group_name}**. ${$groupDraft$$.group_type==="CPDA"?"D\u1ef1 \u00e1n s\u1ebd b\u1eaft \u0111\u1ea7u theo d\u00f5i chi ph\u00ed th\u1ef1c t\u1ebf so v\u1edbi d\u1ef1 to\u00e1n n\u00e0y \u1ea1!":"T\u1eeb b\u00e2y gi\u1edd c\u00e1c ph\u00f2ng ban s\u1ebd b\u1eaft \u0111\u1ea7u ch\u1ea1y theo b\u1ed9 ch\u1ec9 ti\u00eau m\u1edbi n\u00e0y \u1ea1!"}"`}}catch($e$$){return console.error("[L\u1ed7i approveBusinessPlanTool]:",
93
+ $e$$),{status:"error",message:`L\u1ed7i khi duy\u1ec7t k\u1ebf ho\u1ea1ch: ${$e$$.message}`}}}}),checkKpiStatusTool:tool({description:"C\u00f4ng c\u1ee5 KI\u1ec2M TRA TI\u1ebeN \u0110\u1ed8 KPI V\u00c0 K\u1ebe HO\u1ea0CH (Actual vs Planned).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp h\u1ecfi v\u1ec1 t\u1ef7 l\u1ec7 ho\u00e0n th\u00e0nh ch\u1ec9 ti\u00eau, ti\u1ebfn \u0111\u1ed9 doanh s\u1ed1, ki\u1ec3m tra ng\u00e2n s\u00e1ch c\u1ee7a m\u1ed9t Nh\u00f3m K\u1ebf ho\u1ea1ch c\u1ee5 th\u1ec3 (ho\u1eb7c to\u00e0n c\u00f4ng ty),\n HO\u1eb6C h\u1ecfi chi ph\u00ed th\u1ef1c t\u1ebf c\u1ee7a m\u1ed9t D\u1ef0 \u00c1N/V\u1ee4 VI\u1ec6C \u0111\u00e3 chi bao nhi\u00eau so v\u1edbi d\u1ef1 to\u00e1n.",
94
+ inputSchema:z.object({tu_thang:z.number().optional().describe("T\u1eeb th\u00e1ng (VD: 1). N\u1ebfu S\u1ebfp kh\u00f4ng n\u00f3i, \u0111\u1ec3 tr\u1ed1ng \u0111\u1ec3 t\u1ef1 \u0111\u1ed9ng l\u1ea5y th\u00e1ng hi\u1ec7n t\u1ea1i."),den_thang:z.number().optional().describe("\u0110\u1ebfn th\u00e1ng (VD: 12)."),nam:z.number().optional().describe("N\u0103m (VD: 2026)."),group_keyword:z.string().optional().describe('T\u00ean ho\u1eb7c t\u1eeb kh\u00f3a c\u1ee7a Nh\u00f3m k\u1ebf ho\u1ea1ch S\u1ebfp nh\u1eafc \u0111\u1ebfn (VD: "K\u1ebf ho\u1ea1ch b\u00e1n h\u00e0ng Q3"). N\u1ebfu S\u1ebfp h\u1ecfi chung chung, h\u00e3y \u0111\u1ec3 tr\u1ed1ng.'),
95
+ ma_dt:z.string().optional().describe('M\u00e3 v\u1ee5 vi\u1ec7c/d\u1ef1 \u00e1n \u2014 d\u00f9ng khi S\u1ebfp h\u1ecfi tr\u1ef1c ti\u1ebfp ti\u1ebfn \u0111\u1ed9 chi ph\u00ed c\u1ee7a M\u1ed8T D\u1ef0 \u00c1N c\u1ee5 th\u1ec3 b\u1eb1ng m\u00e3, kh\u00f4ng c\u1ea7n bi\u1ebft t\u00ean k\u1ebf ho\u1ea1ch (VD: S\u1ebfp h\u1ecfi "D\u1ef1 \u00e1n DA001 \u0111\u00e3 chi bao nhi\u00eau r\u1ed3i?").'),pivot_column:z.string().optional().describe('Gom nh\u00f3m b\u00e1o c\u00e1o theo: "nhan_chi_tieu" (Nh\u00e3n ch\u1ec9 ti\u00eau), "ma_bp" (Ph\u00f2ng ban), "ma_nv" (Nh\u00e2n vi\u00ean), "ma_phi" (Lo\u1ea1i chi ph\u00ed \u2014 h\u1eefu \u00edch cho k\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n), "thang" (Th\u00e1ng), "quy" (Qu\u00fd)...')}),
96
+ execute:async $payload$$=>{try{Logger.warn("[checkKpiStatusTool]",JSON.stringify($payload$$,null,2));const $condition$$={id_app:$id_app$$,...$payload$$};$condition$$.pivot_column=$condition$$.pivot_column||"nhan_chi_tieu";let $foundGroupName$$="To\u00e0n c\u00f4ng ty (T\u1ea5t c\u1ea3 k\u1ebf ho\u1ea1ch)";if($payload$$.group_keyword){const $foundGroups$$=await global.getModel("group").find({id_app:$id_app$$,group_type:{$in:["KPI","CPDA"]},group_name:{$regex:$payload$$.group_keyword,$options:"i"}}).lean();
97
+ if($foundGroups$$.length===0)return{status:"error",message:`H\u1ec7 th\u1ed1ng kh\u00f4ng t\u00ecm th\u1ea5y Nh\u00f3m k\u1ebf ho\u1ea1ch n\u00e0o c\u00f3 t\u00ean kh\u1edbp v\u1edbi t\u1eeb kh\u00f3a '${$payload$$.group_keyword}'. AI h\u00e3y b\u00e1o l\u1ea1i S\u1ebfp ki\u1ec3m tra l\u1ea1i t\u00ean k\u1ebf ho\u1ea1ch.`};if($foundGroups$$.length>1){const $danhSachKeHoach$$=$foundGroups$$.map(($g$$,$index$$)=>`${$index$$+1}. **${$g$$.group_name}** (${$g$$.group_type==="CPDA"?"Chi ph\u00ed d\u1ef1 \u00e1n":
98
+ "KPI"})`).join("\n");return{status:"need_clarification",message:`[C\u1ea2NH B\u00c1O TR\u00d9NG L\u1eb6P]: H\u1ec7 th\u1ed1ng t\u00ecm th\u1ea5y ${$foundGroups$$.length} k\u1ebf ho\u1ea1ch c\u00f3 ch\u1ee9a t\u1eeb kh\u00f3a '${$payload$$.group_keyword}'.\n\nH\u01af\u1edaNG D\u1eaaN AI: H\u00e3y d\u1eebng l\u1ea1i, in danh s\u00e1ch d\u01b0\u1edbi \u0111\u00e2y ra cho S\u1ebfp xem v\u00e0 h\u1ecfi S\u1ebfp mu\u1ed1n ki\u1ec3m tra ti\u1ebfn \u0111\u1ed9 c\u1ee7a k\u1ebf ho\u1ea1ch n\u00e0o:\n${$danhSachKeHoach$$}`}}$condition$$.group_id=
99
+ $foundGroups$$[0]._id.toString();$foundGroupName$$=$foundGroups$$[0].group_name;Logger.info(`[checkKpiStatusTool] \u0110\u00e3 map t\u1eeb kh\u00f3a '${$payload$$.group_keyword}' th\u00e0nh group_id: ${$condition$$.group_id}`)}else $payload$$.ma_dt&&($foundGroupName$$=`D\u1ef1 \u00e1n ${$payload$$.ma_dt}`);const $report$$=await kpistatus($condition$$);return{status:"success",target_group:$foundGroupName$$,message:"Truy xu\u1ea5t b\u00e1o c\u00e1o ti\u1ebfn \u0111\u1ed9 KPI/Chi ph\u00ed th\u00e0nh c\u00f4ng.",
100
+ data:$report$$.rows,hint:`QUY T\u1eaeC B\u00c1O C\u00c1O:
101
+ 1. M\u1edf \u0111\u1ea7u b\u1eb1ng: "D\u1ea1, \u0111\u00e2y l\u00e0 ti\u1ebfn \u0111\u1ed9 c\u1ee7a [${$foundGroupName$$}]..."
102
+ 2. V\u1ebd b\u1ea3ng Markdown. Ch\u00fa \u00fd hi\u1ec3n th\u1ecb r\u00f5 3 c\u1ed9t quan tr\u1ecdng: Ph\u00e1t sinh (Th\u1ef1c t\u1ebf), Ch\u1ec9 ti\u00eau (K\u1ebf ho\u1ea1ch), v\u00e0 T\u1ef7 l\u1ec7 ho\u00e0n th\u00e0nh (%).
103
+ 3. N\u1ebfu c\u00f3 ph\u00f2ng ban/nh\u00e2n vi\u00ean/lo\u1ea1i chi ph\u00ed n\u00e0o t\u1ef7 l\u1ec7 ho\u00e0n th\u00e0nh qu\u00e1 th\u1ea5p (< 50%) ho\u1eb7c v\u01b0\u1ee3t xa ch\u1ec9 ti\u00eau (>100% v\u1edbi chi ph\u00ed = \u0110ANG V\u01af\u1ee2T NG\u00c2N S\u00c1CH), h\u00e3y vi\u1ebft th\u00eam 1 d\u00f2ng nh\u1eadn x\u00e9t ng\u1eafn \u1edf d\u01b0\u1edbi b\u1ea3ng.
104
+ 4. N\u1ebfu \u0111\u00e2y l\u00e0 k\u1ebf ho\u1ea1ch chi ph\u00ed d\u1ef1 \u00e1n (ma_dt ho\u1eb7c nh\u00f3m CPDA), nh\u1ea5n m\u1ea1nh ph\u1ea7n C\u00d2N L\u1ea0I C\u00d3 TH\u1ec2 CHI (Ch\u1ec9 ti\u00eau - Ph\u00e1t sinh) cho S\u1ebfp d\u1ec5 theo d\u00f5i.`}}catch($e$$){return console.error("[L\u1ed7i checkKpiStatusTool]:",$e$$),{status:"error",message:`L\u1ed7i h\u1ec7 th\u1ed1ng khi ch\u1ea1y b\u00e1o c\u00e1o: ${$e$$.message}`}}}})}}module.exports={createKpiTools};
@@ -0,0 +1,74 @@
1
+ const {tool}=require("ai"),{z}=require("zod"),{searchActiveListInfo,translateDocToText,resolveDirectRefFilter,resolveRefMetaByKeyword,resolveHopChain,normalizeHops,bangTuDienDanhMuc}=require("./utils"),moment=require("moment");global.__aiConfigCache=global.__aiConfigCache||new Map;
2
+ function pushUnionCondition($conditions$$,$field$$,$values$$){$field$$.startsWith("ma_nvt")?$conditions$$.push({$or:[{ma_nvt:{$in:$values$$}},{ma_nvt2:{$in:$values$$}},{ma_nvt3:{$in:$values$$}},{ma_nvt4:{$in:$values$$}}]}):$field$$.startsWith("nh_kh")?$conditions$$.push({$or:[{nh_kh:{$in:$values$$}},{nh_kh2:{$in:$values$$}},{nh_kh3:{$in:$values$$}},{nh_kh4:{$in:$values$$}},{nh_kh5:{$in:$values$$}}]}):$conditions$$.push({[$field$$]:{$in:$values$$}})}
3
+ async function buildUniversalRefConditions($controllerCode$$,$hops_pathDef_ref_filters_resolved_resolved$$,$id_app$$){let $conditions$$=[];if(!$hops_pathDef_ref_filters_resolved_resolved$$||$hops_pathDef_ref_filters_resolved_resolved$$.length===0)return $conditions$$;var $ctrl_jsonSchema$$=global.controllers[$controllerCode$$.toUpperCase()];if(!$ctrl_jsonSchema$$||typeof $ctrl_jsonSchema$$.getJsonSchema!=="function")throw Error(`[System Error] Controller ${$controllerCode$$} kh\u00f4ng h\u1ed7 tr\u1ee3 h\u00e0m getJsonSchema.`);
4
+ Logger.info("[buildUniversalRefConditions] \u0111ang l\u1ea5y schema c\u1ee7a",$controllerCode$$);$ctrl_jsonSchema$$=await $ctrl_jsonSchema$$.getJsonSchema($id_app$$);if(!$ctrl_jsonSchema$$)throw Error(`[System Error] H\u00e0m getJsonSchema c\u1ee7a controller ${$controllerCode$$} \u0111ang tr\u1ea3 v\u1ec1 r\u1ed7ng (undefined)!`);for(let $filter$$ of $hops_pathDef_ref_filters_resolved_resolved$$)if($hops_pathDef_ref_filters_resolved_resolved$$=normalizeHops($filter$$)){$hops_pathDef_ref_filters_resolved_resolved$$=
5
+ await resolveHopChain($hops_pathDef_ref_filters_resolved_resolved$$,$filter$$.keyword,$id_app$$);if($hops_pathDef_ref_filters_resolved_resolved$$.error)throw Error(`EMPTY_HOP|D\u1ea1 S\u1ebfp, ${$hops_pathDef_ref_filters_resolved_resolved$$.error}`);pushUnionCondition($conditions$$,$filter$$.field,$hops_pathDef_ref_filters_resolved_resolved$$.values)}else{$hops_pathDef_ref_filters_resolved_resolved$$=$ctrl_jsonSchema$$[$filter$$.field];if(!$hops_pathDef_ref_filters_resolved_resolved$$)throw Error(`EMPTY_REF|D\u1ea1 S\u1ebfp, em kh\u00f4ng t\u00ecm th\u1ea5y tr\u01b0\u1eddng '${$filter$$.field}' trong d\u1eef li\u1ec7u '${$controllerCode$$}' \u2014 c\u00f3 th\u1ec3 t\u00ean field b\u1ecb sai, em c\u1ea7n ki\u1ec3m tra l\u1ea1i.`);
6
+ if($hops_pathDef_ref_filters_resolved_resolved$$.ref&&$hops_pathDef_ref_filters_resolved_resolved$$.ref.ref_model){const $resolved$$=await resolveRefMetaByKeyword($hops_pathDef_ref_filters_resolved_resolved$$.ref,$filter$$.keyword,$id_app$$);if($resolved$$.error)throw Error(`EMPTY_REF|D\u1ea1 S\u1ebfp, ${$resolved$$.error}`);let $matchedValues$$=$resolved$$.values;if($hops_pathDef_ref_filters_resolved_resolved$$.ref.ref_model==="dmnvt"&&$filter$$.field.startsWith("ma_nvt")||$hops_pathDef_ref_filters_resolved_resolved$$.ref.ref_model===
7
+ "dmnhkh"&&$filter$$.field.startsWith("nh_kh"))$matchedValues$$=await Promise.all($matchedValues$$.map($v$$=>$resolved$$.refModel.getAllChildrenGroup($id_app$$,$v$$)));pushUnionCondition($conditions$$,$filter$$.field,$matchedValues$$);Logger.warn("[buildUniversalRefConditions] d\u1eef li\u1ec7u li\u00ean k\u1ebft",$controllerCode$$,JSON.stringify($conditions$$,null,2))}else if($hops_pathDef_ref_filters_resolved_resolved$$.options&&Array.isArray($hops_pathDef_ref_filters_resolved_resolved$$.options.options)){$hops_pathDef_ref_filters_resolved_resolved$$=
8
+ await resolveDirectRefFilter($hops_pathDef_ref_filters_resolved_resolved$$,$filter$$.keyword,$id_app$$);if($hops_pathDef_ref_filters_resolved_resolved$$.error)throw Error(`EMPTY_REF|D\u1ea1 S\u1ebfp, ph\u00e2n lo\u1ea1i '${$filter$$.keyword}' kh\u00f4ng h\u1ee3p l\u1ec7.`);$conditions$$.push({[$filter$$.field]:{$in:$hops_pathDef_ref_filters_resolved_resolved$$.values}})}}return $conditions$$}
9
+ async function resolveControllerConfig($config_listInfoDoc_searchResult_type$$,$controllerCode$$){var $choices_configCacheKey_mapping$$={flow:{title:"Quy tr\u00ecnh",controllerCode:"FLOW",model:"flow",idField:"_id",codeField:"title",searchFields:["title"],selectFields:{_id:1,title:1}},account:{title:"T\u00e0i kho\u1ea3n",controllerCode:"ACCOUNT",model:"account",idField:"_id",codeField:"tk",searchFields:["tk","ten_tk"],selectFields:{tk:1,ten_tk:1}},customer:{title:"Kh\u00e1ch h\u00e0ng",controllerCode:"CUSTOMER",
10
+ model:"customer",idField:"_id",codeField:"ma_kh",searchFields:["ten_kh","dien_thoai","ma_kh"],selectFields:{ma_kh:1,ten_kh:1,dien_thoai:1,dia_chi:1}},product:{title:"S\u1ea3n ph\u1ea9m/V\u1eadt t\u01b0",controllerCode:"DMVT",model:"dmvt",idField:"_id",codeField:"ma_vt",searchFields:["ten_vt","ma_vt"],selectFields:{ma_vt:1,ten_vt:1}},product_group:{title:"Nh\u00f3m s\u1ea3n ph\u1ea9m/V\u1eadt t\u01b0",controllerCode:"PRODUCT_GROUP",model:"dmnvt",idField:"_id",codeField:"_id",searchFields:["ten_nvt"],
11
+ selectFields:{_id:1,ten_nvt:1}},ecomproductlines:{title:"Ng\u00e0nh h\u00e0ng",controllerCode:"ECOMPRODUCTLINES",model:"ecomproductlines",idField:"_id",codeField:"ma_nganh_hang",searchFields:["ma_nganh_hang","ten_nganh_hang"],selectFields:{ma_nganh_hang:1,ten_nganh_hang:1}},employee:{title:"Nh\u00e2n s\u1ef1",controllerCode:"DMNV",model:"dmnv",idField:"_id",codeField:"ma_nv",searchFields:["ten_nv","ma_nv"],selectFields:{ma_nv:1,ten_nv:1}},department:{title:"Ph\u00f2ng ban",controllerCode:"DMBP",model:"dmbp",
12
+ idField:"_id",codeField:"ma_bp",searchFields:["ten_bp","ma_bp"],selectFields:{ma_bp:1,ten_bp:1}},project:{title:"D\u1ef1 \u00e1n",controllerCode:"DMDT",model:"dmdt",idField:"_id",codeField:"ma_dt",searchFields:["ten_dt","ma_dt"],selectFields:{ma_dt:1,ten_dt:1}},fee:{title:"Kho\u1ea3n ph\u00ed",controllerCode:"DMPHI",model:"dmphi",idField:"_id",codeField:"ma_phi",searchFields:["ten_phi","ma_phi"],selectFields:{ma_phi:1,ten_phi:1}},contract:{title:"H\u1ee3p \u0111\u1ed3ng",controllerCode:"CONTRACT",
13
+ model:"contract",idField:"_id",codeField:"so_hd",searchFields:["ten_hd","so_hd"],selectFields:{so_hd:1,ten_hd:1}},cost_factor:{title:"Y\u1ebfu t\u1ed1 chi ph\u00ed",controllerCode:"DMYT",model:"dmyt",idField:"_id",codeField:"ma_yt",searchFields:["ten_yt","ma_yt"],selectFields:{ma_yt:1,ten_yt:1}}};if($choices_configCacheKey_mapping$$[$config_listInfoDoc_searchResult_type$$])return{config:$choices_configCacheKey_mapping$$[$config_listInfoDoc_searchResult_type$$],isDynamic:!1};$choices_configCacheKey_mapping$$=
14
+ `config_${$config_listInfoDoc_searchResult_type$$}`;if(global.__aiConfigCache&&global.__aiConfigCache.has($choices_configCacheKey_mapping$$)){var $cachedData_controller_dynamicSearchFields_formFields$$=global.__aiConfigCache.get($choices_configCacheKey_mapping$$);if(Date.now()-$cachedData_controller_dynamicSearchFields_formFields$$.timestamp<(global.CONFIG_CACHE_TTL||36E5))return{config:$cachedData_controller_dynamicSearchFields_formFields$$.config,isDynamic:!0};global.__aiConfigCache.delete($choices_configCacheKey_mapping$$)}const $searchKeywords$$=
15
+ [$config_listInfoDoc_searchResult_type$$,...($controllerCode$$||[])];$config_listInfoDoc_searchResult_type$$=await searchActiveListInfo({keywords:$searchKeywords$$,include_fields_schema:!1,limit:5});if(!$config_listInfoDoc_searchResult_type$$||$config_listInfoDoc_searchResult_type$$.docs.length===0)throw Error(`Kh\u00f4ng t\u00ecm th\u1ea5y ch\u1ee9c n\u0103ng kh\u1edbp v\u1edbi t\u1eeb kho\u00e1: [${$searchKeywords$$.join(", ")}].`);if($config_listInfoDoc_searchResult_type$$.docs.length>1)if($controllerCode$$=
16
+ $config_listInfoDoc_searchResult_type$$.docs.find($d$$=>{const $dTitle$$=$d$$.title.toLowerCase().trim(),$dCode$$=$d$$.finalCode.toLowerCase().trim();return $searchKeywords$$.some($kw$$=>$kw$$.toLowerCase().trim()===$dTitle$$||$kw$$.toLowerCase().trim()===$dCode$$)}))$config_listInfoDoc_searchResult_type$$.docs=[$controllerCode$$];else throw $choices_configCacheKey_mapping$$=$config_listInfoDoc_searchResult_type$$.docs.map($doc$$=>`- **${$doc$$.title}** (M\u00e3: ${$doc$$.finalCode})`),Error(`MULTIPLE_MATCHES|${JSON.stringify($config_listInfoDoc_searchResult_type$$.docs)}|D\u1ea1 S\u1ebfp, em t\u00ecm th\u1ea5y v\u00e0i ch\u1ee9c n\u0103ng kh\u1edbp: \n${$choices_configCacheKey_mapping$$.join("\n")}\n S\u1ebfp mu\u1ed1n tra c\u1ee9u \u1edf \u0111\u00e2u \u1ea1?`);
17
+ $config_listInfoDoc_searchResult_type$$=$config_listInfoDoc_searchResult_type$$.docs[0];$controllerCode$$=$config_listInfoDoc_searchResult_type$$.finalCode.toUpperCase();$cachedData_controller_dynamicSearchFields_formFields$$=global.controllers[$controllerCode$$];if(!$cachedData_controller_dynamicSearchFields_formFields$$||typeof $cachedData_controller_dynamicSearchFields_formFields$$.findData!=="function")throw Error(`Ch\u1ee9c n\u0103ng '${$controllerCode$$}' thi\u1ebfu h\u00e0m findData().`);const $Model$$=
18
+ $cachedData_controller_dynamicSearchFields_formFields$$.model;$cachedData_controller_dynamicSearchFields_formFields$$=[];if($config_listInfoDoc_searchResult_type$$.search_form){const $searchFormDoc$$=await global.getModel("listinfo").findOne({code:$config_listInfoDoc_searchResult_type$$.search_form}).lean();$searchFormDoc$$&&Array.isArray($searchFormDoc$$.fields)&&($cachedData_controller_dynamicSearchFields_formFields$$=$searchFormDoc$$.fields.map($f$$=>$f$$.name).filter(Boolean),$cachedData_controller_dynamicSearchFields_formFields$$=
19
+ $Model$$&&$Model$$.schema&&$Model$$.schema.paths?$cachedData_controller_dynamicSearchFields_formFields$$.filter($f$$=>$Model$$.schema.paths[$f$$]&&$Model$$.schema.paths[$f$$].instance==="String"):$cachedData_controller_dynamicSearchFields_formFields$$)}$cachedData_controller_dynamicSearchFields_formFields$$.length===0&&$Model$$&&$Model$$.schema&&$Model$$.schema.paths&&($cachedData_controller_dynamicSearchFields_formFields$$=Object.keys($Model$$.schema.paths).filter($p$$=>$Model$$.schema.paths[$p$$].instance===
20
+ "String"));$config_listInfoDoc_searchResult_type$$={title:$config_listInfoDoc_searchResult_type$$.title,controllerCode:$controllerCode$$,model:$Model$$?.modelName||$controllerCode$$.toLowerCase(),idField:"_id",codeField:$cachedData_controller_dynamicSearchFields_formFields$$.includes(`ma_${$controllerCode$$.toLowerCase()}`)?`ma_${$controllerCode$$.toLowerCase()}`:$cachedData_controller_dynamicSearchFields_formFields$$[0]||"_id",searchFields:$cachedData_controller_dynamicSearchFields_formFields$$.length>
21
+ 0?$cachedData_controller_dynamicSearchFields_formFields$$:["title","name","code","so_ct"],selectFields:{}};global.__aiConfigCache&&global.__aiConfigCache.set($choices_configCacheKey_mapping$$,{timestamp:Date.now(),config:$config_listInfoDoc_searchResult_type$$});return{config:$config_listInfoDoc_searchResult_type$$,isDynamic:!0}}
22
+ function buildTimeCondition($tu_ngay$$,$den_ngay$$,$Model$$){let $dateField$$="date_created";if(($Model$$=global.getModel($Model$$))&&$Model$$.schema&&$Model$$.schema.paths)for(var $field$$ of["ngay_ct","start_date","date_created"])if($Model$$.schema.paths[$field$$]){$dateField$$=$field$$;break}$field$$={};let $start$$,$end$$;if($tu_ngay$$||$den_ngay$$)$end$$=$den_ngay$$?new Date($den_ngay$$):new Date,$start$$=$tu_ngay$$?new Date($tu_ngay$$):new Date($end$$.getFullYear(),$end$$.getMonth(),1),$field$$[$dateField$$]=
23
+ {$gte:moment($start$$).startOf("date").toDate(),$lte:moment($end$$).endOf("date").toDate()};return{timeCondition:$field$$,dateField:$dateField$$,start:$start$$,end:$end$$}}
24
+ function buildKeywordConditionLadder($id_ladder$$,$code$$,$keywords$$,$config$$){if($id_ladder$$)return[{condition:{[$config$$.idField]:$id_ladder$$},matchedBy:"id"}];const $fieldsToSearch$$=Array.from(new Set([...$config$$.searchFields,$config$$.codeField])).filter($f$$=>$f$$!=="_id");$id_ladder$$=[];$code$$&&$config$$.codeField&&$config$$.codeField!=="_id"&&$id_ladder$$.push({condition:{[$config$$.codeField]:{$regex:`^${$code$$}$`,$options:"i"}},matchedBy:"exact_code"});let $allSearchTerms$$=[];
25
+ $code$$&&$allSearchTerms$$.push($code$$);$keywords$$&&$keywords$$.length>0&&$allSearchTerms$$.push(...$keywords$$);if($allSearchTerms$$.length===0)return $id_ladder$$;$allSearchTerms$$.length===1?$id_ladder$$.push({condition:{$or:$fieldsToSearch$$.map($f$$=>({[$f$$]:{$regex:$allSearchTerms$$[0],$options:"i"}}))},matchedBy:"keyword_matched"}):($id_ladder$$.push({condition:{$and:$allSearchTerms$$.map($kw$$=>({$or:$fieldsToSearch$$.map($f$$=>({[$f$$]:{$regex:$kw$$,$options:"i"}}))}))},matchedBy:"all_keywords_matched"}),
26
+ $id_ladder$$.push({condition:{$or:$allSearchTerms$$.flatMap($kw$$=>$fieldsToSearch$$.map($f$$=>({[$f$$]:{$regex:$kw$$,$options:"i"}})))},matchedBy:"any_keyword_matched"}));$code$$=[...(new Set($allSearchTerms$$.flatMap($kw$$=>String($kw$$).split(/\s+/)).filter($w$$=>$w$$.length>1)))];$code$$.length>0&&$code$$.length!==$allSearchTerms$$.length&&$id_ladder$$.push({condition:{$or:$code$$.flatMap($w$$=>$fieldsToSearch$$.map($f$$=>({[$f$$]:{$regex:$w$$,$options:"i"}})))},matchedBy:"fuzzy_single_word"});
27
+ return $id_ladder$$}
28
+ async function fetchItemRelations($item$$,$contacts_controllerCode$$,$secureFind$$){const $docIdStr$$=$item$$._id,$getRel$$=async($ctrl$$,$cond$$,$l$$)=>{$ctrl$$=await $secureFind$$($ctrl$$,$cond$$,{limit:$l$$});return Array.isArray($ctrl$$)?$ctrl$$:$ctrl$$?.data||$ctrl$$?.rows||[]};var $appts_notes_tasks$$=await $getRel$$("NOTE",{id_link:$docIdStr$$},10);$appts_notes_tasks$$.length&&($item$$.cac_ghi_chu=await Promise.all($appts_notes_tasks$$.map($n$$=>translateDocToText($n$$,"note"))));$appts_notes_tasks$$=
29
+ await $getRel$$("TASK",{$or:[{id_link:$docIdStr$$},{id_kh:$docIdStr$$},{id_bp:$docIdStr$$},{id_dt:$docIdStr$$}]},10);$appts_notes_tasks$$.length>0&&($item$$.cong_viec=await Promise.all($appts_notes_tasks$$.map($t$$=>translateDocToText($t$$,"task"))));$appts_notes_tasks$$=await $getRel$$("DATLICH",{id_link:$docIdStr$$},5);$appts_notes_tasks$$.length>0&&($item$$.lich_lam_viec=await Promise.all($appts_notes_tasks$$.map($a$$=>translateDocToText($a$$,"datlich"))));$contacts_controllerCode$$==="CUSTOMER"&&
30
+ ($contacts_controllerCode$$=await $getRel$$("LIENHE",{id_kh:$docIdStr$$},10),$contacts_controllerCode$$.length&&($item$$.thong_tin_lien_he=await Promise.all($contacts_controllerCode$$.map($c$$=>translateDocToText($c$$,"lienhe")))));return $item$$}const extractPagination=($r$$,$dp$$,$dl$$)=>$r$$&&$r$$.pagination?{total:$r$$.pagination.total||0,totalPages:$r$$.pagination.totalPages||1,page:$r$$.pagination.page||$dp$$,limit:$r$$.pagination.limit||$dl$$}:{total:0,totalPages:1,page:$dp$$,limit:$dl$$};
31
+ function createMasterDataTools($id_app$$,$user_id$$){return{lookupMasterDataTool:tool({description:`C\u00f4ng c\u1ee5 TRA C\u1ee8U danh m\u1ee5c, h\u1ed3 s\u01a1, ch\u1ee9ng t\u1eeb (CH\u1ec8 tra c\u1ee9u \u2014 KH\u00d4NG d\u00f9ng \u0111\u1ec3 th\u1ed1ng k\u00ea/\u0111\u1ebfm s\u1ed1 l\u01b0\u1ee3ng, c\u00e1c tool b\u00e1o c\u00e1o/ph\u00e2n t\u00edch kh\u00e1c \u0111\u00e3 \u0111\u1ea3m nhi\u1ec7m vi\u1ec7c \u0111\u00f3).
32
+ S\u1eec D\u1ee4NG KHI:
33
+ 1. T\u00ccM KI\u1ebeM CHI TI\u1ebeT: T\u00ecm h\u1ed3 s\u01a1, xem chi ti\u1ebft, C\u00d3 H\u1ed6 TR\u1ee2 PH\u00c2N TRANG. N\u1ebfu k\u1ebft qu\u1ea3 tr\u1ea3 v\u1ec1 c\u00f3 'totalPages' > 1, h\u00e3y ch\u1ee7 \u0111\u1ed9ng b\u00e1o cho S\u1ebfp bi\u1ebft.
34
+ 2. L\u1eccC THEO NH\u00d3M: N\u1ebfu S\u1ebfp y\u00eau c\u1ea7u t\u00ecm trong m\u1ed9t "nh\u00f3m" (V\u00ed d\u1ee5: "s\u1ea3n ph\u1ea9m nh\u00f3m G\u1ea1ch", "kh\u00e1ch h\u00e0ng nh\u00f3m VIP"), B\u1eaeT BU\u1ed8C d\u00f9ng 'ref_filters' v\u1edbi field t\u01b0\u01a1ng \u1ee9ng (VD: field: "nh_kh" cho nh\u00f3m kh\u00e1ch h\u00e0ng, field: "ma_nvt" cho nh\u00f3m s\u1ea3n ph\u1ea9m) v\u00e0 keyword = t\u00ean nh\u00f3m.
35
+ \ud83d\udd25 B\u1ea2NG T\u1eea \u0110I\u1ec2N M\u00c3 CHU\u1ea8N (CORE DICTIONARY) B\u1eaeT BU\u1ed8C D\u00d9NG:
36
+ N\u1ebfu S\u1ebfp h\u1ecfi v\u1ec1 c\u00e1c \u0111\u1ed1i t\u01b0\u1ee3ng c\u1ed1t l\u00f5i d\u01b0\u1edbi \u0111\u00e2y, B\u1eaeT BU\u1ed8C truy\u1ec1n ch\u00ednh x\u00e1c M\u00c3 CODE v\u00e0o tham s\u1ed1 'type':
37
+ ${bangTuDienDanhMuc("type")}
38
+ (KHI V\u00c0 CH\u1ec8 KHI \u0111\u1ed1i t\u01b0\u1ee3ng S\u1ebfp h\u1ecfi KH\u00d4NG C\u00d3 trong danh s\u00e1ch tr\u00ean, b\u1ea1n m\u1edbi \u0111\u01b0\u1ee3c d\u00f9ng tham s\u1ed1 'keywords' \u0111\u1ec3 t\u00ecm ki\u1ebfm t\u01b0\u01a1ng \u0111\u1ed1i)..
39
+ \u26a1 KH\u00d4NG T\u00ccM TH\u1ea4Y D\u1eee LI\u1ec6U: N\u1ebfu tra c\u1ee9u theo 'code'/'keywords' c\u1ee5 th\u1ec3 m\u00e0 KH\u00d4NG c\u00f3 k\u1ebft qu\u1ea3 n\u00e0o kh\u1edbp (status: 'success_empty', suggest_create: true), B\u1eaeT BU\u1ed8C h\u1ecfi S\u1ebfp c\u00f3 mu\u1ed1n T\u1ea0O M\u1edaI h\u1ed3 s\u01a1 \u0111\u00f3 kh\u00f4ng. CH\u1ec8 khi S\u1ebfp x\u00e1c nh\u1eadn \u0111\u1ed3ng \u00fd, g\u1ecdi "manageMasterDataDocumentTool" \u0111\u1ec3 t\u1ea1o m\u1edbi \u2014 TUY\u1ec6T \u0110\u1ed0I KH\u00d4NG t\u1ef1 t\u1ea1o khi ch\u01b0a \u0111\u01b0\u1ee3c S\u1ebfp x\u00e1c nh\u1eadn, v\u00e0 KH\u00d4NG b\u1ecba th\u00eam tr\u01b0\u1eddng d\u1eef li\u1ec7u S\u1ebfp ch\u01b0a cung c\u1ea5p.`,
40
+ inputSchema:z.object({type:z.string().describe("Lo\u1ea1i danh m\u1ee5c chu\u1ea9n (customer, account...)"),type_keywords:z.array(z.string()).optional().describe('T\u1eeb \u0111\u1ed3ng ngh\u0129a do AI t\u1ef1 suy lu\u1eadn (V\u00ed d\u1ee5: "B\u00e1o c\u00f3", "pt1").'),keywords:z.array(z.string()).optional().describe("(Mode search) M\u1ea3ng t\u1eeb kh\u00f3a t\u00ecm d\u1eef li\u1ec7u (T\u00ean kh\u00e1ch, s\u1ed1 ti\u1ec1n...). H\u1ec7 th\u1ed1ng t\u1ef1 d\u00f2 t\u1eeb CH\u1eb6T (kh\u1edbp h\u1ebft c\u00e1c t\u1eeb) \u0111\u1ebfn L\u1eceNG (kh\u1edbp 1 t\u1eeb \u0111\u01a1n) n\u1ebfu c\u1ea7n, kh\u00f4ng c\u1ea7n S\u1ebfp/AI t\u1ef1 lo vi\u1ec7c n\u1edbi l\u1ecfng."),
41
+ ref_filters:z.array(z.object({field:z.string().describe('M\u00e3 tr\u01b0\u1eddng (field) \u1edf ch\u1ee9c n\u0103ng HI\u1ec6N T\u1ea0I (VD: "details.ma_vt" trong \u0110\u01a1n h\u00e0ng).'),keyword:z.string().describe('T\u1eeb kh\u00f3a S\u1ebfp mu\u1ed1n l\u1ecdc (VD: "C\u00e0 ph\u00ea nh\u00e2n xanh").'),hop_controller:z.string().optional().describe('N\u1ebeU C\u1ea6N B\u1eaeC C\u1ea6U (JOIN): M\u00e3 Controller c\u1ee7a b\u1ea3ng trung gian (VD: "DMVT").'),hop_field:z.string().optional().describe('N\u1ebeU C\u1ea6N B\u1eaeC C\u1ea6U: M\u00e3 tr\u01b0\u1eddng ch\u1ee9a t\u1eeb kh\u00f3a \u1edf b\u1ea3ng trung gian (VD: "ma_nvt").'),
42
+ hop_extract_field:z.string().optional().describe('N\u1ebeU C\u1ea6N B\u1eaeC C\u1ea6U: Tr\u01b0\u1eddng gi\u00e1 tr\u1ecb c\u1ea7n l\u1ea5y t\u1eeb b\u1ea3ng trung gian mang v\u1ec1 \u0111\u1eafp v\u00e0o b\u1ea3ng hi\u1ec7n t\u1ea1i (VD: "ma_vt").')})).optional().describe("B\u1ed8 L\u1eccC \u0110\u1ed8NG V\u1ea0N N\u0102NG: D\u00f9ng khi S\u1ebfp mu\u1ed1n l\u1ecdc theo Kh\u00f3a Ngo\u1ea1i. C\u00f3 h\u1ed7 tr\u1ee3 b\u1eafc c\u1ea7u 2 nh\u1ecbp."),id:z.string().optional().describe("ID (_id) c\u1ee5 th\u1ec3."),
43
+ code:z.string().optional().describe('M\u00e3 c\u1ee5 th\u1ec3 (V\u00ed d\u1ee5: "PT001"). H\u1ec7 th\u1ed1ng \u01b0u ti\u00ean kh\u1edbp CH\u00cdNH X\u00c1C m\u00e3 n\u00e0y tr\u01b0\u1edbc khi th\u1eed c\u00e1c c\u00e1ch t\u00ecm kh\u00e1c.'),include_relations:z.boolean().default(!1).describe("B\u1eadt TRUE \u0111\u1ec3 moi l\u1ecbch s\u1eed l\u00e0m vi\u1ec7c, ghi ch\u00fa, cu\u1ed9c g\u1ecdi."),page:z.number().optional().default(1).describe("Trang k\u1ebft qu\u1ea3 c\u1ea7n xem. M\u1eb7c \u0111\u1ecbnh 1."),
44
+ limit:z.number().optional().default(15).describe("S\u1ed1 l\u01b0\u1ee3ng k\u1ebft qu\u1ea3 tr\u00ean m\u1ed7i trang."),tu_ngay:z.string().optional().describe("T\u1eeb ng\u00e0y (YYYY-MM-DD)."),den_ngay:z.string().optional().describe("\u0110\u1ebfn ng\u00e0y (YYYY-MM-DD).")}),execute:async $finalAndConditions_query$$=>{try{Logger.warn("[AI Agent][lookupMasterDataTool]: ",JSON.stringify($finalAndConditions_query$$,null,2));let {ref_filters:$ref_filters$$,type:$type$$,type_keywords:$type_keywords$$,
45
+ keywords:$keywords$$,id:$id$$,code:$code$$,include_relations:$include_relations$$,page:$page$$,limit:$limit$$,tu_ngay:$tu_ngay$$,den_ngay:$den_ngay$$}=$finalAndConditions_query$$;const {config:$config$$,isDynamic:$isDynamic$$}=await resolveControllerConfig($type$$,$type_keywords$$,$id_app$$),{title:$functionName$$,controllerCode:$controllerCode$$,model:$modelName$$}=$config$$,$secureFind$$=async($ctrl$$,$condition$$,$options$$={})=>{$ctrl$$=global.controllers[$ctrl$$.toUpperCase()];if(!$ctrl$$||typeof $ctrl$$.findData!==
46
+ "function")return null;$ctrl$$.require_id_app===!0&&($condition$$.id_app=$id_app$$);$ctrl$$.model.schema.paths.id_apps&&$ctrl$$.model.schema.paths.id_apps.instance==="Array"&&($condition$$.$and||($condition$$.$and=[]),$condition$$.$and.push({$or:[{id_apps:$id_app$$},{id_apps:null},{"id_apps.0":{$exists:!1}}]}));return await $ctrl$$.findData($user_id$$,$condition$$,{limit:$options$$.limit||20,page:$options$$.page||1,fields:$options$$.fields,sort:$options$$.sort,pagination:$options$$.pagination||0,
47
+ count:$options$$.count||0})};var {timeCondition:$docs_timeCondition$$}=buildTimeCondition($tu_ngay$$,$den_ngay$$,$modelName$$);$finalAndConditions_query$$=[];let $matchedBy$$="all_records",$successfulKeyword$$=$keywords$$?.join(", ")||$code$$||"";Object.keys($docs_timeCondition$$).length>0&&$finalAndConditions_query$$.push($docs_timeCondition$$);const $refConditions$$=await buildUniversalRefConditions($controllerCode$$,$ref_filters$$,$id_app$$);$refConditions$$.length>0&&($finalAndConditions_query$$.push(...$refConditions$$),
48
+ $matchedBy$$=$matchedBy$$==="all_records"?"dynamic_refs":`${$matchedBy$$}_and_refs`,$successfulKeyword$$+=` (\u0110\u00e3 \u00e1p d\u1ee5ng ${$refConditions$$.length} b\u1ed9 l\u1ecdc li\u00ean k\u1ebft)`);var $ladder$jscomp$1_message$$=buildKeywordConditionLadder($id$$,$code$$,$keywords$$,$config$$);const $targetFields$$=Object.keys($config$$.selectFields).length>0?$config$$.selectFields:void 0;$docs_timeCondition$$=[];let $paginationInfo$$,$res$$;if($ladder$jscomp$1_message$$.length===0){if($res$$=
49
+ await $secureFind$$($controllerCode$$,$finalAndConditions_query$$.length>0?{$and:$finalAndConditions_query$$}:{},{limit:$limit$$,page:$page$$,pagination:1,fields:$targetFields$$}),$docs_timeCondition$$=$res$$?.data?.length>0?$res$$.data:[],$paginationInfo$$=extractPagination($res$$,$page$$,$limit$$),$tu_ngay$$||$den_ngay$$)$matchedBy$$=$matchedBy$$==="all_records"?"time_only":$matchedBy$$}else for(const $tier$$ of $ladder$jscomp$1_message$$){const $queryCondition$$={$and:[...$finalAndConditions_query$$,
50
+ $tier$$.condition]};$res$$=await $secureFind$$($controllerCode$$,$queryCondition$$,{limit:$limit$$,page:$page$$,pagination:1,fields:$targetFields$$});const $candidateDocs$$=$res$$?.data?.length>0?$res$$.data:[];if($candidateDocs$$.length>0){$docs_timeCondition$$=$candidateDocs$$;$paginationInfo$$=extractPagination($res$$,$page$$,$limit$$);$matchedBy$$=$tier$$.matchedBy;break}}if($docs_timeCondition$$.length===0){const $hadSearchCriteria$$=$ladder$jscomp$1_message$$.length>0,$searchedTerm$$=$code$$||
51
+ ($keywords$$&&$keywords$$.length>0?$keywords$$.join(", "):null),$isVoucher$$=global.controllers[$controllerCode$$.toUpperCase()]?.isVoucher===!0;return{status:"success_empty",function_used:{name:$functionName$$,code:$controllerCode$$},message:`\u0110\u00e3 truy c\u1eadp ch\u1ee9c n\u0103ng '${$functionName$$}' nh\u01b0ng KH\u00d4NG C\u00d3 d\u1eef li\u1ec7u kh\u1edbp.`,suggest_create:$hadSearchCriteria$$,hint:$hadSearchCriteria$$?`B\u00e1o S\u1ebfp: "D\u1ea1, em \u0111\u00e3 t\u00ecm trong **${$functionName$$}** nh\u01b0ng kh\u00f4ng th\u1ea5y${$searchedTerm$$?
52
+ ` '${$searchedTerm$$}'`:""} n\u00e0o kh\u1edbp \u1ea1. S\u1ebfp c\u00f3 mu\u1ed1n em t\u1ea1o m\u1edbi ${$isVoucher$$?"ch\u1ee9ng t\u1eeb":"danh m\u1ee5c"} n\u00e0y kh\u00f4ng?" \u2014 N\u1ebeU S\u1ebfp \u0111\u1ed3ng \u00fd, B\u1eaeT BU\u1ed8C g\u1ecdi "manageMasterDataDocumentTool" (type: "${$type$$}") v\u1edbi \u0111\u00fang d\u1eef li\u1ec7u S\u1ebfp \u0111\u00e3 cung c\u1ea5p, KH\u00d4NG t\u1ef1 b\u1ecba th\u00eam tr\u01b0\u1eddng ch\u01b0a \u0111\u01b0\u1ee3c x\u00e1c nh\u1eadn.`:`B\u00e1o S\u1ebfp: "D\u1ea1, em \u0111\u00e3 l\u1ecdc trong **${$functionName$$}** theo y\u00eau c\u1ea7u nh\u01b0ng kh\u00f4ng t\u00ecm th\u1ea5y d\u1eef li\u1ec7u n\u00e0o kh\u1edbp \u1ea1!".`}}const $data$$=
53
+ await Promise.all($docs_timeCondition$$.map(async $doc$$=>{let $item$$={...(typeof $doc$$.toObject==="function"?$doc$$.toObject():$doc$$)};$item$$._id=$item$$._id?$item$$._id.toString():void 0;$item$$._id&&($item$$.link_chi_tiet=`/#/page/list/${$controllerCode$$.toLowerCase()}?_id=${$item$$._id}`);$item$$.mieu_ta=await translateDocToText($doc$$,$modelName$$);$include_relations$$&&($item$$=await fetchItemRelations($item$$,$controllerCode$$,$secureFind$$));return $item$$}));return{status:"success",
54
+ function_used:{name:$functionName$$,code:$controllerCode$$},pagination:$paginationInfo$$,current_filters:{type:$type$$,keywords:$keywords$$||[],tu_ngay:$tu_ngay$$,den_ngay:$den_ngay$$},count:$data$$.length,data:JSON.parse(JSON.stringify($data$$)),isDynamic:$isDynamic$$,successfulKeyword:$successfulKeyword$$,matchedBy:$matchedBy$$,hint:`AI L\u01afU \u00dd:
55
+ 1. M\u1edf \u0111\u1ea7u: "D\u1ea1, em \u0111ang tra c\u1ee9u trong ch\u1ee9c n\u0103ng **${$functionName$$} (${$controllerCode$$})**...".
56
+ 2. B\u1eaeT BU\u1ed8C t\u1ea1o link Markdown cu\u1ed1i m\u1ed7i d\u00f2ng: [M\u1edf xem chi ti\u1ebft]({link_chi_tiet}).
57
+ 3. ${$paginationInfo$$.totalPages>$paginationInfo$$.page?`\u0110ang \u1edf trang ${$paginationInfo$$.page}/${$paginationInfo$$.totalPages}. H\u1ecfi S\u1ebfp c\u00f3 mu\u1ed1n xem trang ti\u1ebfp kh\u00f4ng.`:"\u0110\u00e2y l\u00e0 trang cu\u1ed1i c\u00f9ng."}`}}catch($error$$){$ladder$jscomp$1_message$$=$error$$.message||$error$$.error||$error$$.toString();if($ladder$jscomp$1_message$$?.includes("|")){const [$errCode$$,$payload$$,$hint$$]=$ladder$jscomp$1_message$$.split("|");
58
+ if($errCode$$==="MULTIPLE_MATCHES")return{status:"multiple_matches",choices:JSON.parse($payload$$),hint:$hint$$};if($errCode$$==="EMPTY_GROUP"||$errCode$$==="EMPTY_CATEGORY"||$errCode$$==="EMPTY_DEPARTMENT"||$errCode$$==="EMPTY_HOP"||$errCode$$==="EMPTY_REF")return{status:"success_empty",hint:$payload$$}}Logger.info("[L\u1ed7i t\u1eeb lookupMasterDataTool]:",$ladder$jscomp$1_message$$||$error$$);return{status:"error",message:$ladder$jscomp$1_message$$||String($error$$)}}}}),manageMasterDataDocumentTool:tool({description:"C\u00f4ng c\u1ee5 TH\u00caM M\u1edaI (Create) ho\u1eb7c C\u1eacP NH\u1eacT (Update) ch\u1ee9ng t\u1eeb, danh m\u1ee5c \u0111a t\u1ea7ng.\n [QUY T\u1eaeC C\u1ea6U DAO AN TO\u00c0N - B\u1eaeT BU\u1ed8C G\u1ed2M 2 B\u01af\u1edaC]:\n - B\u01af\u1edaC 1 (L\u00ean nh\u00e1p): Lu\u00f4n g\u1ecdi tool n\u00e0y v\u1edbi 'is_confirmed: false'. Tr\u00ecnh b\u00e0y b\u1ea3n nh\u00e1p cho ng\u01b0\u1eddi d\u00f9ng v\u00e0 h\u1ecfi: \"S\u1ebfp c\u00f3 \u0111\u1ed3ng \u00fd l\u01b0u kh\u00f4ng?\".\n - B\u01af\u1edaC 2 (Ghi s\u1ed5): CH\u1ec8 KHI ng\u01b0\u1eddi d\u00f9ng x\u00e1c nh\u1eadn \"\u0110\u1ed3ng \u00fd\", m\u1edbi g\u1ecdi l\u1ea1i l\u1ea7n n\u1eefa v\u1edbi 'is_confirmed: true'.",
59
+ inputSchema:z.object({action:z.enum(["create","update"]).describe('H\u00e0nh \u0111\u1ed9ng: "create" (T\u1ea1o m\u1edbi) ho\u1eb7c "update" (C\u1eadp nh\u1eadt).'),type:z.string().describe('T\u00ean lo\u1ea1i ch\u1ee9ng t\u1eeb/danh m\u1ee5c (V\u00ed d\u1ee5: "s\u1ea3n ph\u1ea9m", "kh\u00e1ch h\u00e0ng", "Phi\u1ebfu thu").'),type_keywords:z.array(z.string()).optional().describe("T\u1eeb \u0111\u1ed3ng ngh\u0129a do AI suy lu\u1eadn."),document_id:z.string().optional().describe('B\u1eaeT BU\u1ed8C n\u1ebfu action="update": L\u00e0 chu\u1ed7i _id c\u1ee7a MongoDB. TUY\u1ec6T \u0110\u1ed0I KH\u00d4NG truy\u1ec1n m\u00e3 th\u00f4 v\u00e0o \u0111\u00e2y.'),
60
+ raw_data:z.record(z.any()).describe("D\u1eef li\u1ec7u JSON ch\u1ee9a c\u00e1c tr\u01b0\u1eddng c\u1ea7n t\u1ea1o/c\u1eadp nh\u1eadt."),foreign_key_lookups:z.array(z.object({field:z.string().describe('T\u00ean tr\u01b0\u1eddng c\u1ea7n \u0111i\u1ec1n Kh\u00f3a ngo\u1ea1i (VD: "ma_nvt", "nh_kh").'),target_type:z.string().describe('T\u00ean danh m\u1ee5c ch\u1ee9a kh\u00f3a \u0111\u00f3 (VD: "nh\u00f3m v\u1eadt t\u01b0", "nh\u00f3m kh\u00e1ch h\u00e0ng").'),keyword:z.string().describe('T\u1eeb kh\u00f3a/T\u00ean nh\u00f3m ng\u01b0\u1eddi d\u00f9ng g\u1ecdi (VD: "Cao c\u1ea5p", "VIP").')})).optional().describe("D\u00f9ng khi S\u1ebfp y\u00eau c\u1ea7u th\u00eam d\u1eef li\u1ec7u v\u00e0o m\u1ed9t Nh\u00f3m/Ng\u00e0nh ch\u01b0a bi\u1ebft ID. AI truy\u1ec1n t\u00ean v\u00e0o \u0111\u00e2y \u0111\u1ec3 h\u1ec7 th\u1ed1ng t\u1ef1 d\u00f2."),
61
+ is_confirmed:z.boolean().describe("C\u1ea6U DAO B\u1ea2O M\u1eacT: M\u1eb7c \u0111\u1ecbnh l\u00e0 false. CH\u1ec8 \u0110\u1eb6T L\u00c0 true N\u1ebeU S\u1ebeP \u0110\u00c3 \u0110\u1ed2NG \u00dd.")}),execute:async({action:$action$$,type:$type$$,type_keywords:$type_keywords$$,document_id:$document_id$$,raw_data:$raw_data$$,foreign_key_lookups:$foreign_key_lookups$$,is_confirmed:$is_confirmed$$})=>{try{if($action$$==="update"&&(!$document_id$$||$document_id$$.trim()===""))return{status:"error",message:"Thi\u1ebfu '_id'. H\u00e3y g\u1ecdi 'lookupMasterDataTool' t\u00ecm '_id' tr\u01b0\u1edbc!"};
62
+ const $searchKeywords$$=[$type$$];$type_keywords$$&&$type_keywords$$.length>0&&$searchKeywords$$.push(...$type_keywords$$);const $searchResult$$=await searchActiveListInfo({keywords:$searchKeywords$$,include_fields_schema:!0,limit:5});if(!$searchResult$$||$searchResult$$.docs.length===0)return{status:"error",message:`Kh\u00f4ng t\u00ecm th\u1ea5y ch\u1ee9c n\u0103ng '${$type$$}'.`};if($searchResult$$.docs.length>1){const $exactMatch$$=$searchResult$$.docs.find($d$$=>{const $dTitle$$=$d$$.title.toLowerCase().trim(),
63
+ $dCode$$=$d$$.finalCode.toLowerCase().trim();return $searchKeywords$$.some($kw$$=>$kw$$.toLowerCase().trim()===$dTitle$$||$kw$$.toLowerCase().trim()===$dCode$$)});if($exactMatch$$)$searchResult$$.docs=[$exactMatch$$];else return{status:"multiple_matches",hint:`AI D\u1eeaNG L\u1ea0I V\u00c0 H\u1eceI S\u1ebeP: "D\u1ea1 S\u1ebfp, c\u00f3 v\u00e0i ch\u1ee9c n\u0103ng kh\u1edbp: \n${$searchResult$$.docs.map($doc$$=>`- **${$doc$$.title}** (M\u00e3: ${$doc$$.finalCode})`).join("\n")}\n S\u1ebfp mu\u1ed1n thao t\u00e1c v\u00e0o \u0111\u00e2u \u1ea1?"`}}const $listInfoDoc$$=
64
+ $searchResult$$.docs[0],$controllerCode$$=$listInfoDoc$$.finalCode.toUpperCase(),$controller$$=global.controllers[$controllerCode$$];if(!$controller$$)return{status:"error",message:`Ch\u1ee9c n\u0103ng '${$controllerCode$$}' kh\u00f4ng t\u1ed3n t\u1ea1i trong Backend.`};if($foreign_key_lookups$$&&$foreign_key_lookups$$.length>0)for(let $lookup$$ of $foreign_key_lookups$$){const $targetSearch$$=await searchActiveListInfo({keywords:[$lookup$$.target_type],limit:1});if(!$targetSearch$$||$targetSearch$$.docs.length===
65
+ 0)continue;const $targetCtrlCode$$=$targetSearch$$.docs[0].finalCode.toUpperCase(),$targetController$$=global.controllers[$targetCtrlCode$$];if(!$targetController$$||!$targetController$$.model)continue;const $TargetModel$$=$targetController$$.model;let $orCond$$=Object.keys($TargetModel$$.schema.paths).filter($p$$=>$TargetModel$$.schema.paths[$p$$].instance==="String"&&!["id_app","user_created"].includes($p$$)).map($f$$=>({[$f$$]:{$regex:$lookup$$.keyword,$options:"i"}}));if($orCond$$.length===0)continue;
66
+ const $foundData$$=await $TargetModel$$.find({id_app:$id_app$$,$or:$orCond$$}).select("_id").limit(1).lean();if($foundData$$.length>0)$raw_data$$[$lookup$$.field]=$foundData$$[0]._id.toString(),$raw_data$$[`_${$lookup$$.field}_resolved_name`]=$lookup$$.keyword;else return{status:"reference_not_found",hint:`AI B\u1eaeT BU\u1ed8C D\u1eeaNG L\u1ea0I V\u00c0 H\u1eceI S\u1ebeP C\u00c2U N\u00c0Y: "D\u1ea1 S\u1ebfp, em \u0111\u1ecbnh th\u00eam v\u00e0o nh\u01b0ng h\u1ec7 th\u1ed1ng hi\u1ec7n ch\u01b0a c\u00f3 [${$lookup$$.target_type}] n\u00e0o mang t\u00ean l\u00e0 **${$lookup$$.keyword}**. S\u1ebfp c\u00f3 mu\u1ed1n em t\u1ea1o m\u1edbi danh m\u1ee5c n\u00e0y tr\u01b0\u1edbc kh\u00f4ng \u1ea1?"`}}const $Model$$=
67
+ $controller$$.model;let $finalDataToProcess$$={...$raw_data$$,listinfo_code:$listInfoDoc$$.code};Object.keys($finalDataToProcess$$).forEach($k$$=>{$k$$.startsWith("_")&&$k$$.endsWith("_resolved_name")&&delete $finalDataToProcess$$[$k$$]});$controller$$.require_id_app!==!1&&($finalDataToProcess$$.id_app=$id_app$$);$action$$==="create"&&$Model$$&&$Model$$.schema&&$Model$$.schema.paths&&($Model$$.schema.paths.ngay_ct&&!$finalDataToProcess$$.ngay_ct&&($finalDataToProcess$$.ngay_ct=new Date),$Model$$.schema.paths.date_created&&
68
+ !$finalDataToProcess$$.date_created&&($finalDataToProcess$$.date_created=new Date));if($Model$$&&$Model$$.schema&&$Model$$.schema.paths){const $castDataTypes$$=($dataObj_pathDef$$,$schemaPaths$$,$parentKey$$="")=>{if(Array.isArray($dataObj_pathDef$$))return $dataObj_pathDef$$.map($item$$=>$castDataTypes$$($item$$,$schemaPaths$$,$parentKey$$));if($dataObj_pathDef$$&&typeof $dataObj_pathDef$$==="object"){let $result$$={};for(let [$key$$,$val$$]of Object.entries($dataObj_pathDef$$)){let $fullPath$$=
69
+ $parentKey$$?`${$parentKey$$}.${$key$$}`:$key$$;$dataObj_pathDef$$=$schemaPaths$$[$fullPath$$];Array.isArray($val$$)?$result$$[$key$$]=$val$$.map($item$$=>$castDataTypes$$($item$$,$schemaPaths$$,$fullPath$$)):$val$$!==null&&typeof $val$$==="object"?$result$$[$key$$]=$castDataTypes$$($val$$,$schemaPaths$$,$fullPath$$):($dataObj_pathDef$$&&($dataObj_pathDef$$.instance==="Number"&&typeof $val$$==="string"?$val$$=Number($val$$.replace(/[^0-9.-]/g,"")):$dataObj_pathDef$$.instance==="Date"&&typeof $val$$===
70
+ "string"&&($val$$=new Date($val$$))),$result$$[$key$$]=$val$$)}return $result$$}return $dataObj_pathDef$$};$finalDataToProcess$$=$castDataTypes$$($finalDataToProcess$$,$Model$$.schema.paths)}if(!$is_confirmed$$)return{status:"pending_confirmation",message:"D\u1eef li\u1ec7u \u0111\u00e3 \u0111\u01b0\u1ee3c chu\u1ea9n h\u00f3a. \u0110ang ch\u1edd x\u00e1c nh\u1eadn.",data_preview:$finalDataToProcess$$,hint:`AI L\u00c0M \u0110\u00daNG 2 VI\u1ec6C:
71
+ 1. Tr\u00ecnh b\u00e0y d\u1eef li\u1ec7u chu\u1ea9n b\u1ecb ${$action$$==="create"?"T\u1ea0O M\u1edaI":"C\u1eacP NH\u1eacT"} th\u00e0nh B\u1ea2NG (c\u00f3 nh\u1eafc \u0111\u1ebfn c\u00e1c Nh\u00f3m \u0111\u00e3 \u0111\u01b0\u1ee3c t\u1ef1 \u0111\u1ed9ng map th\u00e0nh c\u00f4ng n\u1ebfu c\u00f3).
72
+ 2. H\u1ecfi ch\u1ed1t: "D\u1ea1 S\u1ebfp, S\u1ebfp xem th\u00f4ng tin \u0111\u00e3 chu\u1ea9n ch\u01b0a, ph\u1ea3n h\u1ed3i '\u0110\u1ed3ng \u00fd' \u0111\u1ec3 em ghi s\u1ed5 nh\u00e9!"`};let $resultDoc$$;$resultDoc$$=$action$$==="create"?await $controller$$.createData($user_id$$,$finalDataToProcess$$):await $controller$$.updateData($user_id$$,$document_id$$,$finalDataToProcess$$);const $plainResultDoc$$=typeof $resultDoc$$.toObject==="function"?$resultDoc$$.toObject():
73
+ $resultDoc$$,$safeResultData$$=JSON.parse(JSON.stringify($plainResultDoc$$)),$link_chi_tiet$$=`/#/page/list/${$listInfoDoc$$.code}?_id=${$safeResultData$$._id}`;return{status:"success",link_chi_tiet:$link_chi_tiet$$,hint:`B\u00e1o S\u1ebfp: "D\u1ea1 S\u1ebfp, em \u0111\u00e3 ${$action$$==="create"?"T\u1ea1o m\u1edbi":"C\u1eadp nh\u1eadt"} th\u00e0nh c\u00f4ng trong **${$listInfoDoc$$.title}**. [M\u1edf xem chi ti\u1ebft](${$link_chi_tiet$$})"`}}catch($error$$){return console.error("[L\u1ed7i thao t\u00e1c d\u1eef li\u1ec7u]:",
74
+ $error$$),{status:"error",message:$error$$.message||String($error$$)}}}})}}module.exports={createMasterDataTools};
@@ -0,0 +1,21 @@
1
+ const {tool}=require("ai"),{z}=require("zod"),moment=require("moment");
2
+ function createOkrTools($id_app$$,$user_id$$){return{createOkrTool:tool({description:"C\u00f4ng c\u1ee5 T\u1ea0O M\u1edaI OKR (Objective and Key Results).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp y\u00eau c\u1ea7u l\u1eadp m\u1ee5c ti\u00eau m\u1edbi, thi\u1ebft l\u1eadp OKR cho c\u00e1 nh\u00e2n ho\u1eb7c ph\u00f2ng ban.\n \n \ud83d\udd25 T\u01af DUY THI\u1ebeT L\u1eacP OKR (B\u1eaeT BU\u1ed8C):\n - 1 Objective (O) th\u01b0\u1eddng \u0111i k\u00e8m 2 \u0111\u1ebfn 5 Key Results (KRs).\n - M\u1ee5c ti\u00eau (O) ph\u1ea3i truy\u1ec1n c\u1ea3m h\u1ee9ng. K\u1ebft qu\u1ea3 then ch\u1ed1t (KR) ph\u1ea3i \u0110O L\u01af\u1edcNG \u0110\u01af\u1ee2C b\u1eb1ng s\u1ed1.\n - N\u1ebfu S\u1ebfp kh\u00f4ng n\u00f3i r\u00f5 ng\u01b0\u1eddi ph\u1ee5 tr\u00e1ch, t\u1ef1 \u0111\u1ed9ng m\u1eb7c \u0111\u1ecbnh l\u00e0 S\u1ebfp.\n \n \ud83d\udd25 QUY T\u1eaeC MAPPING M\u00c3 H\u1ec6 TH\u1ed0NG CHO KEY RESULT:\n 1. 'update_interval' (Chu k\u1ef3): 1 (Ng\u00e0y), 2 (Tu\u1ea7n), 3 (Th\u00e1ng), 4 (Qu\u00fd). M\u1eb7c \u0111\u1ecbnh \u01b0u ti\u00ean 2 ho\u1eb7c 3.\n 2. 'update_source' (Ngu\u1ed3n c\u1ea5p s\u1ed1): \n - \"1\": Ng\u01b0\u1eddi ph\u1ee5 tr\u00e1ch t\u1ef1 g\u00f5 tay c\u1eadp nh\u1eadt.\n - \"2\": T\u1ef1 \u0111\u1ed9ng l\u1ea5y t\u1eeb S\u1ed5 k\u1ebf to\u00e1n (N\u1ebfu ch\u1ecdn \"2\", AI B\u1eaeT BU\u1ed8C ph\u1ea3i truy\u1ec1n th\u00eam m\u1ea3ng 'tk_no' ho\u1eb7c 'tk_co' cho KR \u0111\u00f3).\n - \"7\": T\u1ef1 \u0111\u1ed9ng l\u1ea5y t\u1eeb Task/C\u00f4ng vi\u1ec7c.\n 3. 'measure_as' (\u0110\u01a1n v\u1ecb \u0111o): 1 (S\u1ed1 l\u01b0\u1ee3ng), 2 (Ti\u1ec1n), 3 (T\u1ef7 l\u1ec7 %).\n 4. 'aim' (H\u01b0\u1edbng m\u1ee5c ti\u00eau): 1 (T\u0103ng l\u00ean), 2 (Gi\u1ea3m xu\u1ed1ng), 3 (\u0110\u00fang b\u1eb1ng).\n ",inputSchema:z.object({objective:z.object({title:z.string().describe("Ti\u00eau \u0111\u1ec1 M\u1ee5c ti\u00eau (VD: X\u00e2m chi\u1ebfm th\u1ecb tr\u01b0\u1eddng mi\u1ec1n Nam)."),
3
+ description:z.string().optional().describe("Mi\u00eau t\u1ea3 chi ti\u1ebft m\u1ee5c ti\u00eau."),start_date:z.string().describe("Ng\u00e0y b\u1eaft \u0111\u1ea7u (YYYY-MM-DD)."),due_date:z.string().describe("Ng\u00e0y k\u1ebft th\u00fac (YYYY-MM-DD)."),ma_bp:z.string().optional().describe("M\u00e3 b\u1ed9 ph\u1eadn (N\u1ebfu \u00e1p d\u1ee5ng cho ph\u00f2ng ban)."),phu_trach:z.string().optional().describe("Email ng\u01b0\u1eddi ph\u1ee5 tr\u00e1ch (\u0110\u1ec3 tr\u1ed1ng n\u1ebfu AI t\u1ef1 suy lu\u1eadn l\u00e0 S\u1ebfp).")}),
4
+ key_results:z.array(z.object({name:z.string().describe("T\u00ean K\u1ebft qu\u1ea3 then ch\u1ed1t (VD: \u0110\u1ea1t 5 t\u1ef7 doanh thu m\u1edbi)."),phu_trach:z.string().optional(),update_interval:z.number(),update_source:z.string(),measure_as:z.number(),target_value:z.number().describe("Con s\u1ed1 m\u1ee5c ti\u00eau c\u1ea7n \u0111\u1ea1t c\u1ee7a KR."),aim:z.number(),tk_no:z.array(z.string()).optional().describe('T\u00e0i kho\u1ea3n N\u1ee3 (VD: ["641"]). D\u00f9ng khi update_source="2".'),tk_co:z.array(z.string()).optional().describe('T\u00e0i kho\u1ea3n C\u00f3 (VD: ["511"]). D\u00f9ng khi update_source="2".')})).min(1).describe("Danh s\u00e1ch c\u00e1c KRs chi ti\u1ebft \u0111o l\u01b0\u1eddng cho M\u1ee5c ti\u00eau n\u00e0y.")}),
5
+ execute:async({objective:$objective$$,key_results:$key_results$$})=>{try{const $defaultUser$$=$objective$$.phu_trach||$user_id$$,$objData$$={id_app:$id_app$$,title:$objective$$.title,description:$objective$$.description||"",start_date:moment($objective$$.start_date).toDate(),due_date:moment($objective$$.due_date).toDate(),phu_trach:$defaultUser$$,ma_bp:$objective$$.ma_bp||""},$okrId$$=(await global.controllers.OKR.createData($user_id$$,$objData$$))._id.toString(),$krPromises$$=$key_results$$.map(async $kr$$=>
6
+ await global.controllers.KEYRESULT.createData($user_id$$,{id_app:$id_app$$,id_link:$okrId$$,name:$kr$$.name,phu_trach:$kr$$.phu_trach||$defaultUser$$,update_interval:$kr$$.update_interval,update_source:$kr$$.update_source,measure_as:$kr$$.measure_as,target_value:$kr$$.target_value,aim:$kr$$.aim,start_date:$objData$$.start_date,due_date:$objData$$.due_date,tk_no:$kr$$.tk_no||[],tk_co:$kr$$.tk_co||[]}));await Promise.all($krPromises$$);return{status:"success",message:`\u0110\u00e3 t\u1ea1o OKR th\u00e0nh c\u00f4ng. Objective: '${$objData$$.title}' c\u00f9ng v\u1edbi ${$key_results$$.length} Key Results.`,
7
+ hint:`B\u00e1o c\u00e1o S\u1ebfp: "D\u1ea1 em \u0111\u00e3 thi\u1ebft l\u1eadp xong b\u1ed9 OKR **${$objData$$.title}** v\u1edbi ${$key_results$$.length} k\u1ebft qu\u1ea3 then ch\u1ed1t (KRs) r\u1ed3i \u1ea1. C\u00e1c ph\u00f2ng ban c\u00f3 th\u1ec3 truy c\u1eadp v\u00e0o Module OKR \u0111\u1ec3 b\u1eaft \u0111\u1ea7u c\u1eadp nh\u1eadt ti\u1ebfn \u0111\u1ed9 nh\u00e9!"`}}catch($error$$){return console.error("[L\u1ed7i createOkrTool]:",$error$$),{status:"error",message:`L\u1ed7i khi l\u01b0u OKR: ${$error$$.message||
8
+ $error$$}`}}}}),updateOkrTool:tool({description:"C\u00f4ng c\u1ee5 C\u1eacP NH\u1eacT/CH\u1ec8NH S\u1eecA OKR (Update OKR).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp y\u00eau c\u1ea7u thay \u0111\u1ed5i ti\u00eau \u0111\u1ec1, th\u1eddi h\u1ea1n, ng\u01b0\u1eddi ph\u1ee5 tr\u00e1ch, ho\u1eb7c t\u0103ng/gi\u1ea3m ch\u1ec9 ti\u00eau c\u1ee7a c\u00e1c Key Results (KRs) thu\u1ed9c m\u1ed9t OKR \u0110\u00c3 T\u1ed2N T\u1ea0I.\n \n LU\u1eacT HO\u1ea0T \u0110\u1ed8NG:\n 1. T\u00ecm ki\u1ebfm OKR b\u1eb1ng 'okr_keyword'.\n 2. N\u1ebfu c\u1ea7n s\u1eeda th\u00f4ng tin chung (O), truy\u1ec1n v\u00e0o object 'objective'.\n 3. N\u1ebfu c\u1ea7n s\u1eeda K\u1ebft qu\u1ea3 then ch\u1ed1t (KRs), B\u1eaeT BU\u1ed8C ph\u1ea3i truy\u1ec1n TO\u00c0N B\u1ed8 m\u1ea3ng 'key_results' m\u1edbi. H\u1ec7 th\u1ed1ng s\u1ebd x\u00f3a c\u00e1c KRs c\u0169 v\u00e0 ghi \u0111\u00e8 b\u1eb1ng m\u1ea3ng m\u1edbi n\u00e0y (Gi\u1ed1ng quy tr\u00ecnh c\u1eadp nh\u1eadt KPI).",
9
+ inputSchema:z.object({okr_keyword:z.string().describe('T\u00ean ho\u1eb7c t\u1eeb kh\u00f3a c\u1ee7a Ti\u00eau \u0111\u1ec1 OKR c\u1ea7n c\u1eadp nh\u1eadt (VD: "M\u1edf r\u1ed9ng th\u1ecb tr\u01b0\u1eddng").'),objective:z.object({title:z.string().optional(),description:z.string().optional(),start_date:z.string().optional().describe("Ng\u00e0y b\u1eaft \u0111\u1ea7u (YYYY-MM-DD)."),due_date:z.string().optional().describe("Ng\u00e0y k\u1ebft th\u00fac (YYYY-MM-DD)."),phu_trach:z.string().optional()}).optional().describe("Th\u00f4ng tin M\u1ee5c ti\u00eau (O) c\u1ea7n c\u1eadp nh\u1eadt. Ch\u1ec9 truy\u1ec1n c\u00e1c tr\u01b0\u1eddng c\u1ea7n thay \u0111\u1ed5i."),
10
+ key_results:z.array(z.object({name:z.string(),phu_trach:z.string().optional(),update_interval:z.number(),update_source:z.string(),measure_as:z.number(),target_value:z.number(),aim:z.number(),tk_no:z.array(z.string()).optional(),tk_co:z.array(z.string()).optional()})).optional().describe("M\u1ea3ng d\u1eef li\u1ec7u KRs M\u1edaI HO\u00c0N TO\u00c0N \u0111\u1ec3 thay th\u1ebf KRs c\u0169. N\u1ebfu S\u1ebfp ch\u1ec9 s\u1eeda (O) m\u00e0 kh\u00f4ng \u0111\u1ed5i (KRs), h\u00e3y \u0111\u1ec3 tr\u1ed1ng.")}),
11
+ execute:async({okr_keyword:$krCount_okr_keyword_updateData$$,objective:$objective$$,key_results:$key_results$$})=>{try{const $OkrModel$$=global.getModel("okr"),$KeyResultModel$$=global.getModel("keyresult"),$foundOkrs$$=await $OkrModel$$.find({id_app:$id_app$$,title:{$regex:$krCount_okr_keyword_updateData$$,$options:"i"}}).lean();if($foundOkrs$$.length===0)return{status:"error",message:`H\u1ec7 th\u1ed1ng kh\u00f4ng t\u00ecm th\u1ea5y OKR n\u00e0o kh\u1edbp v\u1edbi t\u1eeb kh\u00f3a '${$krCount_okr_keyword_updateData$$}'.`};
12
+ if($foundOkrs$$.length>1){const $danhSachOkr$$=$foundOkrs$$.map(($o$$,$index$$)=>`${$index$$+1}. **${$o$$.title}** (Ph\u1ee5 tr\u00e1ch: ${$o$$.phu_trach||"Ch\u01b0a g\u00e1n"})`).join("\n");return{status:"need_clarification",message:`[C\u1ea2NH B\u00c1O TR\u00d9NG L\u1eb6P]: H\u1ec7 th\u1ed1ng t\u00ecm th\u1ea5y ${$foundOkrs$$.length} OKR c\u00f3 ch\u1ee9a t\u1eeb kh\u00f3a '${$krCount_okr_keyword_updateData$$}'.\n\nH\u01af\u1edaNG D\u1eaaN AI: H\u00e3y d\u1eebng l\u1ea1i v\u00e0 h\u1ecfi S\u1ebfp mu\u1ed1n C\u1eacP NH\u1eacT OKR n\u00e0o trong danh s\u00e1ch sau:\n${$danhSachOkr$$}`}}const $targetOkr$$=
13
+ $foundOkrs$$[0],$okrId$$=$targetOkr$$._id.toString();$objective$$&&Object.keys($objective$$).length>0&&($krCount_okr_keyword_updateData$$={},$objective$$.title&&($krCount_okr_keyword_updateData$$.title=$objective$$.title),$objective$$.description&&($krCount_okr_keyword_updateData$$.description=$objective$$.description),$objective$$.start_date&&($krCount_okr_keyword_updateData$$.start_date=moment($objective$$.start_date).toDate()),$objective$$.due_date&&($krCount_okr_keyword_updateData$$.due_date=
14
+ moment($objective$$.due_date).toDate()),$objective$$.phu_trach&&($krCount_okr_keyword_updateData$$.phu_trach=$objective$$.phu_trach),await global.controllers.OKR.updateData($user_id$$,{_id:$targetOkr$$._id},$krCount_okr_keyword_updateData$$));$krCount_okr_keyword_updateData$$=0;if($key_results$$&&$key_results$$.length>0){await $KeyResultModel$$.deleteMany({id_app:$id_app$$,id_link:$okrId$$});const $krPromises$$=$key_results$$.map(async $kr$jscomp$1_krData$$=>{$kr$jscomp$1_krData$$={id_app:$id_app$$,
15
+ id_link:$okrId$$,name:$kr$jscomp$1_krData$$.name,phu_trach:$kr$jscomp$1_krData$$.phu_trach||$objective$$?.phu_trach||$targetOkr$$.phu_trach,update_interval:$kr$jscomp$1_krData$$.update_interval,update_source:$kr$jscomp$1_krData$$.update_source,measure_as:$kr$jscomp$1_krData$$.measure_as,target_value:$kr$jscomp$1_krData$$.target_value,aim:$kr$jscomp$1_krData$$.aim,start_date:$objective$$?.start_date?moment($objective$$.start_date).toDate():$targetOkr$$.start_date,due_date:$objective$$?.due_date?moment($objective$$.due_date).toDate():
16
+ $targetOkr$$.due_date,tk_no:$kr$jscomp$1_krData$$.tk_no||[],tk_co:$kr$jscomp$1_krData$$.tk_co||[]};return await global.controllers.KEYRESULT.createData($user_id$$,$kr$jscomp$1_krData$$)});await Promise.all($krPromises$$);$krCount_okr_keyword_updateData$$=$key_results$$.length}const $finalTitle$$=$objective$$?.title||$targetOkr$$.title;return{status:"success",message:`\u0110\u00e3 c\u1eadp nh\u1eadt th\u00e0nh c\u00f4ng OKR '${$finalTitle$$}'. ${$krCount_okr_keyword_updateData$$>0?`\u0110\u00e3 thay th\u1ebf/c\u1eadp nh\u1eadt ${$krCount_okr_keyword_updateData$$} Key Results.`:
17
+ "Ch\u1ec9 c\u1eadp nh\u1eadt th\u00f4ng tin chung."}`,hint:`B\u00e1o c\u00e1o S\u1ebfp: "D\u1ea1 em \u0111\u00e3 c\u1eadp nh\u1eadt l\u1ea1i OKR **${$finalTitle$$}** theo y\u00eau c\u1ea7u c\u1ee7a S\u1ebfp r\u1ed3i \u1ea1!"`}}catch($error$$){return console.error("[L\u1ed7i updateOkrTool]:",$error$$),{status:"error",message:`L\u1ed7i khi c\u1eadp nh\u1eadt OKR: ${$error$$.message||$error$$}`}}}}),checkOkrProgressTool:tool({description:"C\u00f4ng c\u1ee5 KI\u1ec2M TRA TI\u1ebeN \u0110\u1ed8 V\u00c0 K\u1ebeT QU\u1ea2 OKR (View OKR Progress).\n S\u1eec D\u1ee4NG KHI: S\u1ebfp y\u00eau c\u1ea7u xem t\u00ecnh h\u00ecnh th\u1ef1c hi\u1ec7n OKR, ti\u1ebfn \u0111\u1ed9 (score) c\u1ee7a c\u00e1c M\u1ee5c ti\u00eau (Objectives), ho\u1eb7c li\u1ec7t k\u00ea danh s\u00e1ch OKR c\u1ee7a m\u1ed9t ph\u00f2ng ban/c\u00e1 nh\u00e2n c\u1ee5 th\u1ec3.",
18
+ inputSchema:z.object({keyword:z.string().optional().describe('T\u1eeb kh\u00f3a t\u00ean M\u1ee5c ti\u00eau (VD: "M\u1edf r\u1ed9ng th\u1ecb tr\u01b0\u1eddng").'),phu_trach:z.string().optional().describe("Email c\u1ee7a ng\u01b0\u1eddi ph\u1ee5 tr\u00e1ch OKR. \u0110\u1ec3 tr\u1ed1ng n\u1ebfu xem to\u00e0n c\u00f4ng ty."),tu_ngay:z.string().optional().describe("Ng\u00e0y b\u1eaft \u0111\u1ea7u (YYYY-MM-DD)."),den_ngay:z.string().optional().describe("Ng\u00e0y k\u1ebft th\u00fac (YYYY-MM-DD).")}),
19
+ execute:async({keyword:$keyword$$,phu_trach:$phu_trach$$,tu_ngay:$tu_ngay$$,den_ngay:$den_ngay$$})=>{try{const $query$$={id_app:$id_app$$};$keyword$$&&($query$$.title={$regex:$keyword$$,$options:"i"});$phu_trach$$&&($query$$.phu_trach=$phu_trach$$);$tu_ngay$$&&$den_ngay$$&&($query$$.start_date={$gte:moment($tu_ngay$$).toDate()},$query$$.due_date={$lte:moment($den_ngay$$).toDate()});const $result$$=await global.controllers.OKR.findData($user_id$$,$query$$),$okrList$$=Array.isArray($result$$)?$result$$:
20
+ $result$$.rows||[];return $okrList$$.length===0?{status:"error",message:"Kh\u00f4ng t\u00ecm th\u1ea5y OKR n\u00e0o kh\u1edbp v\u1edbi \u0111i\u1ec1u ki\u1ec7n t\u00ecm ki\u1ebfm. H\u00e3y b\u00e1o c\u00e1o S\u1ebfp thay \u0111\u1ed5i t\u1eeb kh\u00f3a ho\u1eb7c kho\u1ea3ng th\u1eddi gian."}:{status:"success",message:`T\u00ecm th\u1ea5y ${$okrList$$.length} OKR.`,data:$okrList$$,hint:"QUY T\u1eaeC B\u00c1O C\u00c1O OKR:\n 1. Tr\u00ecnh b\u00e0y d\u01b0\u1edbi d\u1ea1ng Danh s\u00e1ch ho\u1eb7c B\u1ea3ng (T\u00ean M\u1ee5c ti\u00eau, Ph\u1ee5 tr\u00e1ch, Th\u1eddi h\u1ea1n, \u0110i\u1ec3m s\u1ed1/Ti\u1ebfn \u0111\u1ed9).\n 2. D\u1ef1a v\u00e0o tr\u01b0\u1eddng 'score' (K\u1ebft qu\u1ea3) c\u1ee7a d\u1eef li\u1ec7u tr\u1ea3 v\u1ec1 \u0111\u1ec3 \u0111\u00e1nh gi\u00e1 ti\u1ebfn \u0111\u1ed9.\n 3. N\u1ebfu OKR n\u00e0o c\u00f3 ti\u1ebfn \u0111\u1ed9 (score) th\u1ea5p ho\u1eb7c ch\u1eadm ti\u1ebfn \u0111\u1ed9 so v\u1edbi th\u1eddi gian hi\u1ec7n t\u1ea1i, B\u1eaeT BU\u1ed8C ph\u1ea3i vi\u1ebft 1 c\u00e2u c\u1ea3nh b\u00e1o in \u0111\u1eadm \u1edf cu\u1ed1i b\u00e1o c\u00e1o \u0111\u1ec3 S\u1ebfp n\u1eafm t\u00ecnh h\u00ecnh v\u00e0 \u0111\u00f4n \u0111\u1ed1c."}}catch($error$$){return console.error("[L\u1ed7i checkOkrProgressTool]:",
21
+ $error$$),{status:"error",message:`L\u1ed7i khi truy xu\u1ea5t d\u1eef li\u1ec7u OKR: ${$error$$.message}`}}}})}}module.exports={createOkrTools};
@@ -0,0 +1,21 @@
1
+ const {tool}=require("ai"),{z}=require("zod"),{generateEmbedding}=require("../../libs/vectorService"),emailService=require("../../libs/email");
2
+ function calculateCosineSimilarity($vecA$$,$vecB$$){if(!$vecA$$||!$vecB$$||$vecA$$.length!==$vecB$$.length)return 0;let $dotProduct$$=0,$normA$$=0,$normB$$=0;for(let $i$$=0;$i$$<$vecA$$.length;$i$$++)$dotProduct$$+=$vecA$$[$i$$]*$vecB$$[$i$$],$normA$$+=$vecA$$[$i$$]*$vecA$$[$i$$],$normB$$+=$vecB$$[$i$$]*$vecB$$[$i$$];return $normA$$===0||$normB$$===0?0:$dotProduct$$/(Math.sqrt($normA$$)*Math.sqrt($normB$$))}
3
+ function createRagTools($id_app$$,$user_id$$,$session_id$$,$files_of_message$$=[]){return{searchKnowledgeBaseTool:tool({description:"C\u00f4ng c\u1ee5 \u0110\u1eccC, PH\u00c2N T\u00cdCH V\u00c0 TR\u00cdCH XU\u1ea4T FILE T\u00c0I LI\u1ec6U/H\u00d3A \u0110\u01a0N.\n S\u1eec D\u1ee4NG B\u1eaeT BU\u1ed8C KHI: S\u1ebfp y\u00eau c\u1ea7u \u0111\u1ecdc, t\u00f3m t\u1eaft, l\u1ea5y th\u00f4ng tin ho\u1eb7c tra c\u1ee9u n\u1ed9i dung t\u1eeb c\u00e1c file (H\u00f3a \u0111\u01a1n, PDF, Word, Excel) g\u1eedi k\u00e8m.\n [QUY T\u1eaeC S\u1ed0NG C\u00d2N]: Kh\u00f4ng t\u1ef1 b\u1ecba n\u1ed9i dung file. B\u1eaft bu\u1ed9c g\u1ecdi tool \u0111\u1ec3 l\u1ea5y d\u1eef li\u1ec7u th\u1ef1c t\u1ebf!",inputSchema:z.object({action:z.enum(["search",
4
+ "summarize"]).describe('CH\u1eccN "summarize" n\u1ebfu S\u1ebfp y\u00eau c\u1ea7u tr\u00edch xu\u1ea5t to\u00e0n b\u1ed9 th\u00f4ng tin, \u0111\u1ecdc l\u01b0\u1edbt ho\u1eb7c t\u00f3m t\u1eaft t\u1ed5ng quan file/h\u00f3a \u0111\u01a1n. CH\u1eccN "search" n\u1ebfu S\u1ebfp mu\u1ed1n t\u00ecm m\u1ed9t chi ti\u1ebft nh\u1ecf c\u1ee5 th\u1ec3 trong file.'),file_scope:z.enum(["latest","all"]).describe('CH\u1eccN "latest" n\u1ebfu S\u1ebfp n\u00f3i "h\u00f3a \u0111\u01a1n n\u00e0y", "file v\u1eeba g\u1eedi", "file n\u00e0y". CH\u1eccN "all" n\u1ebfu mu\u1ed1n qu\u00e9t to\u00e0n b\u1ed9 l\u1ecbch s\u1eed file trong ph\u00f2ng chat.'),
5
+ search_query:z.string().optional().describe('Ch\u1ec9 b\u1eaft bu\u1ed9c khi action="search". T\u1eeb kh\u00f3a c\u1ea7n t\u00ecm (VD: "th\u1eddi h\u1ea1n thanh to\u00e1n").')}),execute:async({action:$action$$,file_scope:$file_scope_targetScope$$,search_query:$search_query$$})=>{try{var $FileChunk_combinedDocsArray_combinedDocsArray$$=global.getModel("filechunk");const $filter$$={id_app:$id_app$$};$file_scope_targetScope$$=$file_scope_targetScope$$||"latest";let $fileIds$$=[];if($file_scope_targetScope$$===
6
+ "latest")if($files_of_message$$&&$files_of_message$$.length>0)$fileIds$$=$files_of_message$$;else{var $finalContext_latestMsgWithFile$$=await global.getModel("chat_message").findOne({id_app:$id_app$$,session_id:$session_id$$,"files.0":{$exists:!0}}).sort({_id:-1}).select({files:1}).lean();$finalContext_latestMsgWithFile$$&&$finalContext_latestMsgWithFile$$.files&&($fileIds$$=$finalContext_latestMsgWithFile$$.files)}else $fileIds$$=(await global.getModel("chat_message").find({id_app:$id_app$$,session_id:$session_id$$,
7
+ "files.0":{$exists:!0}},{files:1}).lean()).map($r$$=>$r$$.files||[]).reduce(($a$$,$b$$)=>$a$$.concat($b$$),[]);$fileIds$$=[...(new Set($fileIds$$.filter($id$$=>$id$$)))];if($fileIds$$.length>0)$filter$$.file_id={$in:$fileIds$$};else return{status:"error",hint:'AI B\u1eaeT BU\u1ed8C TR\u1ea2 L\u1edcI: "D\u1ea1 S\u1ebfp, em ch\u01b0a nh\u1eadn \u0111\u01b0\u1ee3c file h\u00f3a \u0111\u01a1n n\u00e0o \u0111i k\u00e8m ho\u1eb7c g\u1eedi g\u1ea7n \u0111\u00e2y. S\u1ebfp g\u1eedi l\u1ea1i file \u0111\u1ec3 em \u0111\u1ecdc nh\u00e9!"'};
8
+ $finalContext_latestMsgWithFile$$="";if($action$$==="summarize"){var $body_maxChunks$$=$file_scope_targetScope$$==="latest"?100:15;const $chunks$$=await $FileChunk_combinedDocsArray_combinedDocsArray$$.find($filter$$).sort({file_id:1,chunk_index:1}).limit($body_maxChunks$$).lean();if(!$chunks$$||$chunks$$.length===0)return{status:"error",hint:"D\u1ea1 S\u1ebfp, em th\u1ea5y file r\u1ed3i nh\u01b0ng h\u1ec7 th\u1ed1ng b\u0103m d\u1eef li\u1ec7u ch\u01b0a xong. S\u1ebfp \u0111\u1ee3i em 2 gi\u00e2y r\u1ed3i g\u00f5 l\u1ea1i nh\u00e9!"};
9
+ const $groupedResults$$={};$chunks$$.forEach($doc$$=>{const $fileId$$=$doc$$.file_id||"T\u00e0i li\u1ec7u kh\u00f4ng t\u00ean";$groupedResults$$[$fileId$$]||($groupedResults$$[$fileId$$]=[]);$groupedResults$$[$fileId$$].push(`(\u0110o\u1ea1n ${$doc$$.chunk_index}):\n${$doc$$.content}`)});$FileChunk_combinedDocsArray_combinedDocsArray$$=[];for(const [$fileId$$,$bodyChunks$$]of Object.entries($groupedResults$$))$FileChunk_combinedDocsArray_combinedDocsArray$$.push(`[\ud83d\uddc2\ufe0f NGU\u1ed2N T\u00c0I LI\u1ec6U | File ID: ${$fileId$$}]\n${$bodyChunks$$.join("\n\n")}`);
10
+ $finalContext_latestMsgWithFile$$=$FileChunk_combinedDocsArray_combinedDocsArray$$.join("\n\n====================\n\n");return{status:"success",context:$finalContext_latestMsgWithFile$$,hint:"AI L\u01afU \u00dd: \u0110\u00e2y l\u00e0 to\u00e0n b\u1ed9 n\u1ed9i dung th\u00f4 tr\u00edch xu\u1ea5t t\u1eeb h\u00f3a \u0111\u01a1n/file m\u1edbi nh\u1ea5t S\u1ebfp g\u1eedi. H\u00e3y \u0111\u1ecdc th\u1eadt k\u1ef9 v\u00e0 li\u1ec7t k\u00ea to\u00e0n b\u1ed9 c\u00e1c th\u00f4ng tin quan tr\u1ecdng nh\u01b0: T\u00ean nh\u00e0 cung c\u1ea5p, M\u00e3 s\u1ed1 thu\u1ebf, Danh s\u00e1ch m\u1eb7t h\u00e0ng, S\u1ed1 l\u01b0\u1ee3ng, \u0110\u01a1n gi\u00e1, Th\u00e0nh ti\u1ec1n v\u00e0 T\u1ed5ng ti\u1ec1n cu\u1ed1i c\u00f9ng cho S\u1ebfp xem."}}if($action$$===
11
+ "search"&&$search_query$$){const $queryVector$$=await generateEmbedding($search_query$$),$candidates$$=await $FileChunk_combinedDocsArray_combinedDocsArray$$.find($filter$$,{content:1,metadata:1,embedding:1,file_id:1,chunk_index:1}).lean();if(!$candidates$$||$candidates$$.length===0)return{status:"success",message:"Kh\u00f4ng t\u00ecm th\u1ea5y n\u1ed9i dung kh\u1edbp trong file."};let $scoredDocs$$=$candidates$$.map($doc$$=>({...$doc$$,score:calculateCosineSimilarity($queryVector$$,$doc$$.embedding)}));
12
+ $scoredDocs$$.sort(($a$$,$b$$)=>$b$$.score-$a$$.score);let $topResults$$=$scoredDocs$$.filter($doc$$=>$doc$$.score>=.75).slice(0,5);$topResults$$.length===0&&($topResults$$=$scoredDocs$$.slice(0,3));const $groupedResults$$={};$topResults$$.forEach($doc$$=>{const $fileId$$=$doc$$.file_id||"T\u00e0i li\u1ec7u kh\u00f4ng t\u00ean";$groupedResults$$[$fileId$$]||($groupedResults$$[$fileId$$]=[]);$groupedResults$$[$fileId$$].push({index:$doc$$.chunk_index,content:$doc$$.content,score:$doc$$.score})});$FileChunk_combinedDocsArray_combinedDocsArray$$=
13
+ [];for(const [$fileId$$,$chunks$$]of Object.entries($groupedResults$$))$chunks$$.sort(($a$$,$b$$)=>$a$$.index-$b$$.index),$body_maxChunks$$=$chunks$$.map($c$$=>`(\u0110o\u1ea1n ${$c$$.index} | Kh\u1edbp ${($c$$.score*100).toFixed(1)}%):\n${$c$$.content}`).join("\n\n[... ph\u1ea7n v\u0103n b\u1ea3n b\u1ecb b\u1ecf qua ...]\n\n"),$FileChunk_combinedDocsArray_combinedDocsArray$$.push(`[\ud83d\uddc2\ufe0f NGU\u1ed2N T\u00c0I LI\u1ec6U | File ID: ${$fileId$$}]\n${$body_maxChunks$$}`);$finalContext_latestMsgWithFile$$=
14
+ $FileChunk_combinedDocsArray_combinedDocsArray$$.join("\n\n====================\n\n");return{status:"success",context:$finalContext_latestMsgWithFile$$,hint:"AI B\u1eaeT BU\u1ed8C L\u01afU \u00dd: Tr\u1ea3 l\u1eddi tr\u1ecdng t\u00e2m v\u00e0o c\u00e2u h\u1ecfi chi ti\u1ebft d\u1ef1a tr\u00ean d\u1eef li\u1ec7u file \u0111\u00e3 qu\u00e9t."}}return{status:"error",hint:"AI L\u1ed6I: C\u1ea7n truy\u1ec1n action h\u1ee3p l\u1ec7."}}catch($error$$){return console.error("[L\u1ed7i Node.js Vector Search]:",
15
+ $error$$),{status:"error",message:"L\u1ed7i h\u1ec7 th\u1ed1ng thi\u1ebft b\u1ecb qu\u00e9t vector."}}}}),sendEmailTool:tool({description:"C\u00f4ng c\u1ee5 d\u00f9ng \u0111\u1ec3 g\u1eedi email tr\u1ef1c ti\u1ebfp cho Kh\u00e1ch h\u00e0ng, \u0110\u1ed1i t\u00e1c, Nh\u00e2n vi\u00ean ho\u1eb7c S\u1ebfp.\n S\u1eec D\u1ee4NG KHI: Ng\u01b0\u1eddi d\u00f9ng y\u00eau c\u1ea7u g\u1eedi b\u00e1o c\u00e1o, g\u1eedi th\u00f4ng b\u00e1o, x\u00e1c nh\u1eadn l\u1ecbch h\u1eb9n, g\u1eedi h\u1ed3 s\u01a1 c\u00f4ng n\u1ee3 ho\u1eb7c g\u1eedi t\u00e0i li\u1ec7u qua email.\n AI L\u01afU \u00dd: N\u1ed9i dung email (tr\u01b0\u1eddng html) c\u1ea7n \u0111\u01b0\u1ee3c tr\u00ecnh b\u00e0y chuy\u00ean nghi\u1ec7p b\u1eb1ng \u0111\u1ecbnh d\u1ea1ng HTML (c\u00f3 th\u1ebb p, b, br, ho\u1eb7c table n\u1ebfu l\u00e0 b\u1ea3ng s\u1ed1 li\u1ec7u) \u0111\u1ec3 ng\u01b0\u1eddi nh\u1eadn d\u1ec5 \u0111\u1ecdc.",
16
+ inputSchema:z.object({to_address:z.string().email().describe("\u0110\u1ecba ch\u1ec9 email ch\u00ednh x\u00e1c c\u1ee7a ng\u01b0\u1eddi nh\u1eadn (V\u00ed d\u1ee5: nguyenvan@gmail.com)."),subject:z.string().describe("Ti\u00eau \u0111\u1ec1 c\u1ee7a email. C\u1ea7n ng\u1eafn g\u1ecdn, r\u00f5 r\u00e0ng v\u00e0 trang tr\u1ecdng."),html:z.string().describe("N\u1ed9i dung chi ti\u1ebft c\u1ee7a email d\u01b0\u1edbi \u0111\u1ecbnh d\u1ea1ng HTML. \n H\u00e3y vi\u1ebft m\u1ed9t b\u1ee9c th\u01b0 ho\u00e0n ch\u1ec9nh bao g\u1ed3m: L\u1eddi ch\u00e0o (K\u00ednh g\u1eedi...), n\u1ed9i dung ch\u00ednh (s\u1eed d\u1ee5ng c\u00e1c th\u1ebb HTML nh\u01b0 <b> \u0111\u1ec3 nh\u1ea5n m\u1ea1nh, <table> \u0111\u1ec3 k\u1ebb b\u1ea3ng n\u1ebfu g\u1eedi s\u1ed1 li\u1ec7u b\u00e1o c\u00e1o, <br/> \u0111\u1ec3 xu\u1ed1ng d\u00f2ng), v\u00e0 l\u1eddi ch\u00fac/k\u00fd t\u00ean \u1edf cu\u1ed1i th\u01b0.")}),
17
+ execute:async({to_address:$to_address$$,subject:$subject$$,html:$html$$})=>{try{if(!$to_address$$||!$subject$$||!$html$$)return{status:"error",message:"Thi\u1ebfu th\u00f4ng tin b\u1eaft bu\u1ed9c: to_address, subject ho\u1eb7c html."};const $result$$=await emailService.sendHtml({to:{address:$to_address$$},subject:$subject$$,html:$html$$,id_app:$id_app$$});return{status:"success",message:`\u0110\u00e3 g\u1eedi email th\u00e0nh c\u00f4ng t\u1edbi \u0111\u1ecba ch\u1ec9 ${$to_address$$}.`,details:$result$$}}catch($error$$){return console.error("[L\u1ed7i t\u1eeb sendEmailTool]:",
18
+ $error$$),{status:"error",message:`Kh\u00f4ng th\u1ec3 g\u1eedi email do l\u1ed7i h\u1ec7 th\u1ed1ng: ${$error$$.message||String($error$$)}`}}}}),getCompanyInfoTool:tool({description:"C\u00f4ng c\u1ee5 l\u1ea5y th\u00f4ng tin chi ti\u1ebft v\u00e0 c\u1ea5u h\u00ecnh ph\u00e1p l\u00fd c\u1ee7a c\u00f4ng ty/doanh nghi\u1ec7p hi\u1ec7n t\u1ea1i (t\u1eeb model app).\n S\u1eec D\u1ee4NG KHI: \n 1. AI c\u1ea7n bi\u1ebft T\u00ean c\u00f4ng ty, M\u00e3 s\u1ed1 thu\u1ebf, \u0110\u1ecba ch\u1ec9, ho\u1eb7c Hotline \u0111\u1ec3 ph\u1ee5c v\u1ee5 vi\u1ec7c so\u1ea1n th\u1ea3o h\u1ee3p \u0111\u1ed3ng, v\u0103n b\u1ea3n.\n 2. AI c\u1ea7n l\u1ea5y th\u00f4ng tin b\u1ea3n quy\u1ec1n, t\u00ean doanh nghi\u1ec7p \u0111\u1ec3 ch\u00e8n v\u00e0o ph\u1ea7n Ch\u00e2n trang (Footer) c\u1ee7a b\u00e1o c\u00e1o ho\u1eb7c ph\u1ea7n K\u00fd t\u00ean c\u1ee7a Email g\u1eedi cho kh\u00e1ch h\u00e0ng nh\u1eb1m t\u0103ng t\u00ednh chuy\u00ean nghi\u1ec7p.",
19
+ inputSchema:z.object({}),execute:async()=>{try{const $AppModel$$=global.getModel("app");if(!$AppModel$$)return{status:"error",message:"Model 'app' kh\u00f4ng t\u1ed3n t\u1ea1i tr\u00ean h\u1ec7 th\u1ed1ng."};const $companyInfo$$=await $AppModel$$.findOne({_id:$id_app$$},{name:1,name_en:1,short_name:1,address:1,address_en:1,factory_address:1,factory_address_en:1,city:1,province:1,country:1,phone:1,fax:1,email:1,website:1,facebook:1,zalo:1,messager:1,ma_so_thue:1,dia_chi_xuat_hoa_don:1,nguoi_nop_thue:1,
20
+ giam_doc:1,ke_toan_truong:1,ma_nganh_kd:1,ten_nganh_kd:1}).lean();if(!$companyInfo$$)return{status:"not_found",message:`Kh\u00f4ng t\u00ecm th\u1ea5y th\u00f4ng tin c\u1ea5u h\u00ecnh cho c\u00f4ng ty c\u00f3 ID: ${$id_app$$}.`};for(let $key$$ in $companyInfo$$)$companyInfo$$[$key$$]||delete $companyInfo$$[$key$$];return{status:"success",data:$companyInfo$$,hint:"AI l\u01b0u \u00fd: \u0110\u00e2y l\u00e0 th\u00f4ng tin ch\u00ednh th\u1ee9c c\u1ee7a c\u00f4ng ty s\u1ebfp. H\u00e3y s\u1eed d\u1ee5ng c\u00e1c tr\u01b0\u1eddng nh\u01b0 t\u00ean c\u00f4ng ty, \u0111\u1ecba ch\u1ec9, m\u00e3 s\u1ed1 thu\u1ebf n\u00e0y \u0111\u1ec3 c\u00e1 nh\u00e2n h\u00f3a c\u00e1c v\u0103n b\u1ea3n, email, ho\u1eb7c b\u00e1o c\u00e1o t\u00e0i ch\u00ednh khi xu\u1ea5t d\u1eef li\u1ec7u."}}catch($error$$){return console.error("[L\u1ed7i t\u1eeb getCompanyInfoTool]:",
21
+ $error$$),{status:"error",message:`Kh\u00f4ng th\u1ec3 l\u1ea5y th\u00f4ng tin c\u00f4ng ty do l\u1ed7i h\u1ec7 th\u1ed1ng: ${$error$$.message||String($error$$)}`}}}})}}module.exports={createRagTools};