@open-mercato/core 0.6.4-develop.4217.1.c9aa050183 → 0.6.4-develop.4236.1.9fa6806b34

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 (370) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/staff_time_entry/index.js +37 -0
  3. package/dist/generated/entities/staff_time_entry/index.js.map +7 -0
  4. package/dist/generated/entities/staff_time_entry_segment/index.js +23 -0
  5. package/dist/generated/entities/staff_time_entry_segment/index.js.map +7 -0
  6. package/dist/generated/entities/staff_time_project/index.js +35 -0
  7. package/dist/generated/entities/staff_time_project/index.js.map +7 -0
  8. package/dist/generated/entities/staff_time_project_member/index.js +29 -0
  9. package/dist/generated/entities/staff_time_project_member/index.js.map +7 -0
  10. package/dist/generated/entities.ids.generated.js +5 -1
  11. package/dist/generated/entities.ids.generated.js.map +2 -2
  12. package/dist/generated/entity-fields-registry.js +64 -0
  13. package/dist/generated/entity-fields-registry.js.map +2 -2
  14. package/dist/helpers/integration/timesheetFixtures.js +50 -0
  15. package/dist/helpers/integration/timesheetFixtures.js.map +7 -0
  16. package/dist/modules/attachments/api/library/[id]/route.js +20 -16
  17. package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
  18. package/dist/modules/attachments/api/route.js +18 -14
  19. package/dist/modules/attachments/api/route.js.map +2 -2
  20. package/dist/modules/auth/api/roles/acl/route.js +10 -4
  21. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  22. package/dist/modules/auth/api/sidebar/preferences/route.js +27 -20
  23. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  24. package/dist/modules/auth/api/users/acl/route.js +16 -11
  25. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  26. package/dist/modules/auth/commands/users.js +87 -71
  27. package/dist/modules/auth/commands/users.js.map +2 -2
  28. package/dist/modules/auth/services/sidebarPreferencesService.js +39 -30
  29. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  30. package/dist/modules/catalog/commands/categories.js +61 -12
  31. package/dist/modules/catalog/commands/categories.js.map +2 -2
  32. package/dist/modules/catalog/commands/products.js +79 -54
  33. package/dist/modules/catalog/commands/products.js.map +2 -2
  34. package/dist/modules/catalog/commands/variants.js +29 -16
  35. package/dist/modules/catalog/commands/variants.js.map +2 -2
  36. package/dist/modules/currencies/commands/currencies.js +15 -8
  37. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/admin/users.js +27 -26
  39. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  40. package/dist/modules/customer_accounts/api/password/reset-confirm.js +5 -5
  41. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
  42. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +11 -10
  43. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  44. package/dist/modules/customers/commands/addresses.js +35 -21
  45. package/dist/modules/customers/commands/addresses.js.map +2 -2
  46. package/dist/modules/customers/commands/companies.js +163 -162
  47. package/dist/modules/customers/commands/companies.js.map +2 -2
  48. package/dist/modules/customers/commands/deals.js +3 -4
  49. package/dist/modules/customers/commands/deals.js.map +2 -2
  50. package/dist/modules/customers/commands/interactions.js +19 -22
  51. package/dist/modules/customers/commands/interactions.js.map +2 -2
  52. package/dist/modules/customers/commands/people.js +18 -15
  53. package/dist/modules/customers/commands/people.js.map +2 -2
  54. package/dist/modules/customers/commands/personCompanyLinks.js +105 -94
  55. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  56. package/dist/modules/customers/commands/pipeline-stages.js +30 -23
  57. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  58. package/dist/modules/customers/commands/pipelines.js +27 -20
  59. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  60. package/dist/modules/customers/commands/tags.js +13 -5
  61. package/dist/modules/customers/commands/tags.js.map +2 -2
  62. package/dist/modules/dashboards/api/users/widgets/route.js +0 -1
  63. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  64. package/dist/modules/dashboards/api/widgets/data/route.js +29 -1
  65. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  66. package/dist/modules/data_sync/lib/sync-engine.js +4 -4
  67. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  68. package/dist/modules/data_sync/lib/sync-run-service.js +51 -27
  69. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  70. package/dist/modules/directory/commands/organizations.js +192 -158
  71. package/dist/modules/directory/commands/organizations.js.map +3 -3
  72. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +22 -16
  73. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
  74. package/dist/modules/messages/commands/messages.js +77 -75
  75. package/dist/modules/messages/commands/messages.js.map +2 -2
  76. package/dist/modules/messages/commands/shared.js +132 -132
  77. package/dist/modules/messages/commands/shared.js.map +2 -2
  78. package/dist/modules/perspectives/api/[tableId]/route.js +37 -26
  79. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  80. package/dist/modules/resources/commands/resources.js +125 -117
  81. package/dist/modules/resources/commands/resources.js.map +2 -2
  82. package/dist/modules/resources/commands/tags.js +7 -3
  83. package/dist/modules/resources/commands/tags.js.map +2 -2
  84. package/dist/modules/sales/api/quotes/send/route.js +12 -11
  85. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  86. package/dist/modules/sales/commands/documents.js +629 -478
  87. package/dist/modules/sales/commands/documents.js.map +2 -2
  88. package/dist/modules/sales/commands/payments.js +146 -146
  89. package/dist/modules/sales/commands/payments.js.map +2 -2
  90. package/dist/modules/sales/commands/returns.js +68 -60
  91. package/dist/modules/sales/commands/returns.js.map +2 -2
  92. package/dist/modules/staff/acl.js +10 -1
  93. package/dist/modules/staff/acl.js.map +2 -2
  94. package/dist/modules/staff/analytics.js +33 -0
  95. package/dist/modules/staff/analytics.js.map +7 -0
  96. package/dist/modules/staff/api/guards.js +31 -0
  97. package/dist/modules/staff/api/guards.js.map +7 -0
  98. package/dist/modules/staff/api/interceptors.js +96 -0
  99. package/dist/modules/staff/api/interceptors.js.map +7 -0
  100. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +170 -0
  101. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +7 -0
  102. package/dist/modules/staff/api/timesheets/my-projects/route.js +103 -0
  103. package/dist/modules/staff/api/timesheets/my-projects/route.js.map +7 -0
  104. package/dist/modules/staff/api/timesheets/projects/kpis/route.js +147 -0
  105. package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +7 -0
  106. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +171 -0
  107. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +7 -0
  108. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +180 -0
  109. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +7 -0
  110. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +155 -0
  111. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +7 -0
  112. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +173 -0
  113. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +7 -0
  114. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +260 -0
  115. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +7 -0
  116. package/dist/modules/staff/api/timesheets/time-entries/route.js +188 -0
  117. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +7 -0
  118. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +159 -0
  119. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +7 -0
  120. package/dist/modules/staff/api/timesheets/time-projects/route.js +230 -0
  121. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +7 -0
  122. package/dist/modules/staff/backend/staff/timesheets/page.js +710 -0
  123. package/dist/modules/staff/backend/staff/timesheets/page.js.map +7 -0
  124. package/dist/modules/staff/backend/staff/timesheets/page.meta.js +22 -0
  125. package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +7 -0
  126. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +125 -0
  127. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +7 -0
  128. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +16 -0
  129. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +7 -0
  130. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +418 -0
  131. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +7 -0
  132. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +16 -0
  133. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +7 -0
  134. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +79 -0
  135. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +7 -0
  136. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +16 -0
  137. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +7 -0
  138. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +602 -0
  139. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +7 -0
  140. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +25 -0
  141. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +7 -0
  142. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +123 -0
  143. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +7 -0
  144. package/dist/modules/staff/cli.js +38 -1
  145. package/dist/modules/staff/cli.js.map +2 -2
  146. package/dist/modules/staff/commands/index.js +2 -0
  147. package/dist/modules/staff/commands/index.js.map +2 -2
  148. package/dist/modules/staff/commands/leave-requests.js +30 -28
  149. package/dist/modules/staff/commands/leave-requests.js.map +3 -3
  150. package/dist/modules/staff/commands/team-members.js +21 -20
  151. package/dist/modules/staff/commands/team-members.js.map +2 -2
  152. package/dist/modules/staff/commands/timesheets-entries.js +409 -0
  153. package/dist/modules/staff/commands/timesheets-entries.js.map +7 -0
  154. package/dist/modules/staff/commands/timesheets-projects.js +618 -0
  155. package/dist/modules/staff/commands/timesheets-projects.js.map +7 -0
  156. package/dist/modules/staff/data/enrichers.js +104 -0
  157. package/dist/modules/staff/data/enrichers.js.map +7 -0
  158. package/dist/modules/staff/data/entities.js +226 -1
  159. package/dist/modules/staff/data/entities.js.map +2 -2
  160. package/dist/modules/staff/data/validators.js +113 -1
  161. package/dist/modules/staff/data/validators.js.map +2 -2
  162. package/dist/modules/staff/events.js +13 -1
  163. package/dist/modules/staff/events.js.map +2 -2
  164. package/dist/modules/staff/lib/crud.js +7 -1
  165. package/dist/modules/staff/lib/crud.js.map +2 -2
  166. package/dist/modules/staff/lib/staffMemberResolver.js +15 -0
  167. package/dist/modules/staff/lib/staffMemberResolver.js.map +7 -0
  168. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js +60 -0
  169. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js.map +7 -0
  170. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js +260 -0
  171. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js.map +7 -0
  172. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js +41 -0
  173. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js.map +7 -0
  174. package/dist/modules/staff/lib/timesheets-projects/initials.js +10 -0
  175. package/dist/modules/staff/lib/timesheets-projects/initials.js.map +7 -0
  176. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js +12 -0
  177. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js.map +7 -0
  178. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js +55 -0
  179. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js.map +7 -0
  180. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js +66 -0
  181. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js.map +7 -0
  182. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +81 -0
  183. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +7 -0
  184. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js +58 -0
  185. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js.map +7 -0
  186. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js +152 -0
  187. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js.map +7 -0
  188. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +37 -0
  189. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +7 -0
  190. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js +57 -0
  191. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js.map +7 -0
  192. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js +50 -0
  193. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js.map +7 -0
  194. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js +163 -0
  195. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js.map +7 -0
  196. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js +209 -0
  197. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js.map +7 -0
  198. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js +52 -0
  199. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js.map +7 -0
  200. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +77 -0
  201. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +7 -0
  202. package/dist/modules/staff/lib/timesheets-ui/ListView.js +173 -0
  203. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +7 -0
  204. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js +32 -0
  205. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js.map +7 -0
  206. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +270 -0
  207. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +7 -0
  208. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js +57 -0
  209. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js.map +7 -0
  210. package/dist/modules/staff/lib/timesheets-ui/colors.js +43 -0
  211. package/dist/modules/staff/lib/timesheets-ui/colors.js.map +7 -0
  212. package/dist/modules/staff/migrations/Migration20260326135612.js +24 -0
  213. package/dist/modules/staff/migrations/Migration20260326135612.js.map +7 -0
  214. package/dist/modules/staff/migrations/Migration20260413102715.js +23 -0
  215. package/dist/modules/staff/migrations/Migration20260413102715.js.map +7 -0
  216. package/dist/modules/staff/migrations/Migration20260413111602.js +13 -0
  217. package/dist/modules/staff/migrations/Migration20260413111602.js.map +7 -0
  218. package/dist/modules/staff/migrations/Migration20260511112759.js +19 -0
  219. package/dist/modules/staff/migrations/Migration20260511112759.js.map +7 -0
  220. package/dist/modules/staff/search.js +35 -0
  221. package/dist/modules/staff/search.js.map +2 -2
  222. package/dist/modules/staff/setup.js +15 -1
  223. package/dist/modules/staff/setup.js.map +2 -2
  224. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js +16 -0
  225. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js.map +7 -0
  226. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js +126 -0
  227. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js.map +7 -0
  228. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js +26 -0
  229. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js.map +7 -0
  230. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js +15 -0
  231. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js.map +7 -0
  232. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +238 -0
  233. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +7 -0
  234. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js +26 -0
  235. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js.map +7 -0
  236. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +145 -0
  237. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +7 -0
  238. package/dist/modules/staff/widgets/injection-table.js +12 -0
  239. package/dist/modules/staff/widgets/injection-table.js.map +7 -0
  240. package/dist/modules/sync_excel/api/import/route.js +19 -17
  241. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  242. package/dist/modules/translations/commands/translations.js +22 -19
  243. package/dist/modules/translations/commands/translations.js.map +2 -2
  244. package/generated/entities/staff_time_entry/index.ts +17 -0
  245. package/generated/entities/staff_time_entry_segment/index.ts +10 -0
  246. package/generated/entities/staff_time_project/index.ts +16 -0
  247. package/generated/entities/staff_time_project_member/index.ts +13 -0
  248. package/generated/entities.ids.generated.ts +5 -1
  249. package/generated/entity-fields-registry.ts +64 -0
  250. package/package.json +7 -7
  251. package/src/helpers/integration/timesheetFixtures.ts +61 -0
  252. package/src/modules/attachments/api/library/[id]/route.ts +24 -17
  253. package/src/modules/attachments/api/route.ts +20 -14
  254. package/src/modules/auth/api/roles/acl/route.ts +11 -5
  255. package/src/modules/auth/api/sidebar/preferences/route.ts +33 -24
  256. package/src/modules/auth/api/users/acl/route.ts +17 -12
  257. package/src/modules/auth/commands/users.ts +96 -80
  258. package/src/modules/auth/services/sidebarPreferencesService.ts +40 -32
  259. package/src/modules/catalog/commands/categories.ts +61 -12
  260. package/src/modules/catalog/commands/products.ts +93 -60
  261. package/src/modules/catalog/commands/variants.ts +29 -16
  262. package/src/modules/currencies/commands/currencies.ts +27 -14
  263. package/src/modules/customer_accounts/api/admin/users.ts +31 -26
  264. package/src/modules/customer_accounts/api/password/reset-confirm.ts +5 -6
  265. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +14 -13
  266. package/src/modules/customers/commands/addresses.ts +35 -23
  267. package/src/modules/customers/commands/companies.ts +166 -165
  268. package/src/modules/customers/commands/deals.ts +2 -4
  269. package/src/modules/customers/commands/interactions.ts +20 -26
  270. package/src/modules/customers/commands/people.ts +18 -15
  271. package/src/modules/customers/commands/personCompanyLinks.ts +109 -100
  272. package/src/modules/customers/commands/pipeline-stages.ts +31 -27
  273. package/src/modules/customers/commands/pipelines.ts +29 -23
  274. package/src/modules/customers/commands/tags.ts +13 -5
  275. package/src/modules/dashboards/api/users/widgets/route.ts +0 -1
  276. package/src/modules/dashboards/api/widgets/data/route.ts +36 -1
  277. package/src/modules/data_sync/lib/sync-engine.ts +4 -5
  278. package/src/modules/data_sync/lib/sync-run-service.ts +57 -28
  279. package/src/modules/directory/commands/organizations.ts +203 -166
  280. package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +26 -18
  281. package/src/modules/messages/commands/messages.ts +82 -80
  282. package/src/modules/messages/commands/shared.ts +138 -133
  283. package/src/modules/perspectives/api/[tableId]/route.ts +38 -27
  284. package/src/modules/resources/commands/resources.ts +127 -117
  285. package/src/modules/resources/commands/tags.ts +7 -3
  286. package/src/modules/sales/api/quotes/send/route.ts +17 -12
  287. package/src/modules/sales/commands/documents.ts +673 -481
  288. package/src/modules/sales/commands/payments.ts +158 -152
  289. package/src/modules/sales/commands/returns.ts +74 -63
  290. package/src/modules/staff/acl.ts +11 -0
  291. package/src/modules/staff/analytics.ts +30 -0
  292. package/src/modules/staff/api/guards.ts +59 -0
  293. package/src/modules/staff/api/interceptors.ts +122 -0
  294. package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +191 -0
  295. package/src/modules/staff/api/timesheets/my-projects/route.ts +115 -0
  296. package/src/modules/staff/api/timesheets/projects/kpis/route.ts +159 -0
  297. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +187 -0
  298. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +191 -0
  299. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +168 -0
  300. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +191 -0
  301. package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +292 -0
  302. package/src/modules/staff/api/timesheets/time-entries/route.ts +193 -0
  303. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +167 -0
  304. package/src/modules/staff/api/timesheets/time-projects/route.ts +244 -0
  305. package/src/modules/staff/backend/staff/timesheets/page.meta.ts +20 -0
  306. package/src/modules/staff/backend/staff/timesheets/page.tsx +899 -0
  307. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +12 -0
  308. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +141 -0
  309. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +12 -0
  310. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +579 -0
  311. package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +12 -0
  312. package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +90 -0
  313. package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +23 -0
  314. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +765 -0
  315. package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +138 -0
  316. package/src/modules/staff/cli.ts +40 -1
  317. package/src/modules/staff/commands/index.ts +2 -0
  318. package/src/modules/staff/commands/leave-requests.ts +37 -29
  319. package/src/modules/staff/commands/team-members.ts +25 -20
  320. package/src/modules/staff/commands/timesheets-entries.ts +504 -0
  321. package/src/modules/staff/commands/timesheets-projects.ts +699 -0
  322. package/src/modules/staff/data/enrichers.ts +134 -0
  323. package/src/modules/staff/data/entities.ts +198 -0
  324. package/src/modules/staff/data/validators.ts +129 -0
  325. package/src/modules/staff/events.ts +13 -0
  326. package/src/modules/staff/i18n/de.json +209 -1
  327. package/src/modules/staff/i18n/en.json +209 -1
  328. package/src/modules/staff/i18n/es.json +209 -1
  329. package/src/modules/staff/i18n/pl.json +209 -1
  330. package/src/modules/staff/lib/crud.ts +8 -0
  331. package/src/modules/staff/lib/staffMemberResolver.ts +22 -0
  332. package/src/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.ts +89 -0
  333. package/src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts +311 -0
  334. package/src/modules/staff/lib/timesheets-projects/dateBuckets.ts +37 -0
  335. package/src/modules/staff/lib/timesheets-projects/initials.ts +6 -0
  336. package/src/modules/staff/lib/timesheets-projects/kpiMath.ts +8 -0
  337. package/src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts +83 -0
  338. package/src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx +75 -0
  339. package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +110 -0
  340. package/src/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.tsx +73 -0
  341. package/src/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.tsx +185 -0
  342. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +53 -0
  343. package/src/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.tsx +63 -0
  344. package/src/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.ts +63 -0
  345. package/src/modules/staff/lib/timesheets-ui/AddRowDropdown.tsx +188 -0
  346. package/src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx +229 -0
  347. package/src/modules/staff/lib/timesheets-ui/ColorPicker.tsx +65 -0
  348. package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +99 -0
  349. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +230 -0
  350. package/src/modules/staff/lib/timesheets-ui/ProjectColorDot.tsx +40 -0
  351. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +327 -0
  352. package/src/modules/staff/lib/timesheets-ui/ViewSwitcher.tsx +60 -0
  353. package/src/modules/staff/lib/timesheets-ui/colors.ts +58 -0
  354. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1148 -0
  355. package/src/modules/staff/migrations/Migration20260326135612.ts +26 -0
  356. package/src/modules/staff/migrations/Migration20260413102715.ts +25 -0
  357. package/src/modules/staff/migrations/Migration20260413111602.ts +13 -0
  358. package/src/modules/staff/migrations/Migration20260511112759.ts +21 -0
  359. package/src/modules/staff/search.ts +35 -0
  360. package/src/modules/staff/setup.ts +15 -0
  361. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.ts +17 -0
  362. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.tsx +158 -0
  363. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.ts +25 -0
  364. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/config.ts +15 -0
  365. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +297 -0
  366. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.ts +25 -0
  367. package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +161 -0
  368. package/src/modules/staff/widgets/injection-table.ts +10 -0
  369. package/src/modules/sync_excel/api/import/route.ts +23 -18
  370. package/src/modules/translations/commands/translations.ts +49 -41
@@ -0,0 +1,260 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
+ import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
6
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
8
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
9
+ import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
10
+ import { emitCrudSideEffects, flushCrudSideEffects } from "@open-mercato/shared/lib/commands/helpers";
11
+ import { StaffTimeEntry, StaffTeamMember, StaffTimeProject } from "../../../../data/entities.js";
12
+ import { staffTimeEntryBulkSaveSchema } from "../../../../data/validators.js";
13
+ import { staffTimeEntryCrudEvents } from "../../../../lib/crud.js";
14
+ import {
15
+ resolveUserFeatures,
16
+ runStaffMutationGuardAfterSuccess,
17
+ runStaffMutationGuards
18
+ } from "../../../guards.js";
19
+ const metadata = {
20
+ POST: { requireAuth: true, requireFeatures: ["staff.timesheets.manage_own"] }
21
+ };
22
+ async function POST(req) {
23
+ try {
24
+ const container = await createRequestContainer();
25
+ const auth = await getAuthFromRequest(req);
26
+ const { translate } = await resolveTranslations();
27
+ if (!auth) throw new CrudHttpError(401, { error: translate("staff.errors.unauthorized", "Unauthorized") });
28
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
29
+ const tenantId = scope?.tenantId ?? auth.tenantId ?? null;
30
+ const organizationId = scope?.selectedId ?? auth.orgId ?? null;
31
+ if (!tenantId || !organizationId) {
32
+ throw new CrudHttpError(400, { error: translate("staff.errors.missingScope", "Missing tenant or organization scope.") });
33
+ }
34
+ const body = await readJsonSafe(req, {});
35
+ const parsed = staffTimeEntryBulkSaveSchema.safeParse(body);
36
+ if (!parsed.success) {
37
+ const errors = parsed.error.issues.map((issue) => ({
38
+ path: issue.path.join("."),
39
+ message: issue.message
40
+ }));
41
+ return NextResponse.json({ ok: false, errors }, { status: 422 });
42
+ }
43
+ const { entries } = parsed.data;
44
+ const em = container.resolve("em").fork();
45
+ const scopeCtx = { tenantId, organizationId };
46
+ const staffMember = await findOneWithDecryption(
47
+ em,
48
+ StaffTeamMember,
49
+ { userId: auth.sub, tenantId, organizationId, deletedAt: null },
50
+ {},
51
+ scopeCtx
52
+ );
53
+ if (!staffMember) {
54
+ throw new CrudHttpError(403, { error: translate("staff.timesheets.errors.noStaffMember", "No staff member linked to your account.") });
55
+ }
56
+ const staffMemberId = staffMember.id;
57
+ const referencedProjectIds = [
58
+ ...new Set(
59
+ entries.map((e) => e.timeProjectId).filter((id) => typeof id === "string" && id.length > 0)
60
+ )
61
+ ];
62
+ if (referencedProjectIds.length > 0) {
63
+ const validProjects = await em.find(StaffTimeProject, {
64
+ id: { $in: referencedProjectIds },
65
+ tenantId,
66
+ organizationId,
67
+ deletedAt: null
68
+ }, { fields: ["id"] });
69
+ const validIds = new Set(validProjects.map((p) => p.id));
70
+ const invalidIds = referencedProjectIds.filter((id) => !validIds.has(id));
71
+ if (invalidIds.length > 0) {
72
+ return NextResponse.json(
73
+ {
74
+ ok: false,
75
+ errors: invalidIds.map((id) => ({
76
+ path: "entries[].timeProjectId",
77
+ message: translate("staff.timesheets.errors.projectNotFound", "Time project not found or not accessible."),
78
+ value: id
79
+ }))
80
+ },
81
+ { status: 422 }
82
+ );
83
+ }
84
+ }
85
+ const guardInput = {
86
+ tenantId,
87
+ organizationId,
88
+ userId: auth.sub ?? "",
89
+ resourceKind: "staff.timesheets.time_entry",
90
+ resourceId: staffMemberId,
91
+ operation: "update",
92
+ requestMethod: req.method,
93
+ requestHeaders: req.headers,
94
+ mutationPayload: parsed.data
95
+ };
96
+ const guardResult = await runStaffMutationGuards(container, guardInput, resolveUserFeatures(auth));
97
+ if (!guardResult.ok) {
98
+ return NextResponse.json(
99
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
100
+ { status: guardResult.errorStatus ?? 422 }
101
+ );
102
+ }
103
+ const existingIds = entries.map((entry) => entry.id).filter((id) => typeof id === "string" && id.length > 0);
104
+ if (existingIds.length > 0) {
105
+ const resolvedExisting = await em.find(
106
+ StaffTimeEntry,
107
+ { id: { $in: existingIds }, tenantId, organizationId, staffMemberId, deletedAt: null },
108
+ { fields: ["id"] }
109
+ );
110
+ const resolvedIdSet = new Set(resolvedExisting.map((entry) => entry.id));
111
+ const invalidIds = existingIds.filter((id) => !resolvedIdSet.has(id));
112
+ if (invalidIds.length > 0) {
113
+ return NextResponse.json(
114
+ {
115
+ ok: false,
116
+ errors: invalidIds.map((id) => ({
117
+ path: "entries[].id",
118
+ message: translate(
119
+ "staff.timesheets.errors.entryNotFound",
120
+ "Time entry not found, deleted, or not owned by you."
121
+ ),
122
+ value: id
123
+ }))
124
+ },
125
+ { status: 422 }
126
+ );
127
+ }
128
+ }
129
+ const { counts, pendingChanges } = await em.transactional(async (trx) => {
130
+ let created = 0;
131
+ let updated = 0;
132
+ let deleted = 0;
133
+ const changes = [];
134
+ const existingEntries = existingIds.length > 0 ? await findWithDecryption(
135
+ trx,
136
+ StaffTimeEntry,
137
+ { id: { $in: existingIds }, tenantId, organizationId, staffMemberId, deletedAt: null },
138
+ {},
139
+ scopeCtx
140
+ ) : [];
141
+ const existingMap = new Map(existingEntries.map((entry) => [entry.id, entry]));
142
+ for (const entry of entries) {
143
+ if (entry.id && existingMap.has(entry.id)) {
144
+ const existing = existingMap.get(entry.id);
145
+ if (entry.durationMinutes === 0) {
146
+ existing.deletedAt = /* @__PURE__ */ new Date();
147
+ deleted++;
148
+ changes.push({ action: "deleted", entity: existing });
149
+ } else {
150
+ existing.date = entry.date;
151
+ existing.timeProjectId = entry.timeProjectId;
152
+ existing.durationMinutes = entry.durationMinutes;
153
+ existing.notes = entry.notes ?? existing.notes;
154
+ existing.updatedAt = /* @__PURE__ */ new Date();
155
+ updated++;
156
+ changes.push({ action: "updated", entity: existing });
157
+ }
158
+ } else {
159
+ const now = /* @__PURE__ */ new Date();
160
+ const newEntry = trx.create(StaffTimeEntry, {
161
+ tenantId,
162
+ organizationId,
163
+ staffMemberId,
164
+ date: entry.date,
165
+ timeProjectId: entry.timeProjectId,
166
+ durationMinutes: entry.durationMinutes,
167
+ notes: entry.notes ?? null,
168
+ source: "manual",
169
+ createdAt: now,
170
+ updatedAt: now
171
+ });
172
+ created++;
173
+ changes.push({ action: "created", entity: newEntry });
174
+ }
175
+ }
176
+ await trx.flush();
177
+ return { counts: { created, updated, deleted }, pendingChanges: changes };
178
+ });
179
+ const dataEngine = container.resolve("dataEngine");
180
+ for (const change of pendingChanges) {
181
+ await emitCrudSideEffects({
182
+ dataEngine,
183
+ action: change.action,
184
+ entity: change.entity,
185
+ identifiers: {
186
+ id: change.entity.id,
187
+ organizationId: change.entity.organizationId,
188
+ tenantId: change.entity.tenantId
189
+ },
190
+ events: staffTimeEntryCrudEvents
191
+ });
192
+ }
193
+ await flushCrudSideEffects(dataEngine);
194
+ if (guardResult.afterSuccessCallbacks.length) {
195
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
196
+ tenantId,
197
+ organizationId,
198
+ userId: auth.sub ?? "",
199
+ resourceKind: "staff.timesheets.time_entry",
200
+ resourceId: staffMemberId,
201
+ operation: "update",
202
+ requestMethod: req.method,
203
+ requestHeaders: req.headers
204
+ });
205
+ }
206
+ return NextResponse.json({ ok: true, ...counts }, { status: 200 });
207
+ } catch (err) {
208
+ if (err instanceof CrudHttpError) {
209
+ return NextResponse.json(err.body, { status: err.status });
210
+ }
211
+ const { translate } = await resolveTranslations();
212
+ console.error("staff.timesheets.time-entries.bulk failed", err);
213
+ return NextResponse.json(
214
+ { error: translate("staff.timesheets.errors.bulkSave", "Failed to bulk save time entries.") },
215
+ { status: 400 }
216
+ );
217
+ }
218
+ }
219
+ const openApi = {
220
+ tag: "Staff",
221
+ summary: "Bulk save time entries",
222
+ methods: {
223
+ POST: {
224
+ summary: "Bulk save time entries",
225
+ description: "Creates, updates, or soft-deletes multiple time entries in a single request. Entries with durationMinutes=0 and an existing id are soft-deleted.",
226
+ requestBody: {
227
+ contentType: "application/json",
228
+ schema: staffTimeEntryBulkSaveSchema
229
+ },
230
+ responses: [
231
+ {
232
+ status: 200,
233
+ description: "Bulk save completed",
234
+ schema: z.object({
235
+ ok: z.literal(true),
236
+ created: z.number(),
237
+ updated: z.number(),
238
+ deleted: z.number()
239
+ })
240
+ },
241
+ {
242
+ status: 422,
243
+ description: "Validation error",
244
+ schema: z.object({
245
+ ok: z.literal(false),
246
+ errors: z.array(z.object({ path: z.string(), message: z.string() }))
247
+ })
248
+ },
249
+ { status: 401, description: "Unauthorized", schema: z.object({ error: z.string() }) },
250
+ { status: 403, description: "Forbidden", schema: z.object({ error: z.string() }) }
251
+ ]
252
+ }
253
+ }
254
+ };
255
+ export {
256
+ POST,
257
+ metadata,
258
+ openApi
259
+ };
260
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/staff/api/timesheets/time-entries/bulk/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { emitCrudSideEffects, flushCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTimeEntry, StaffTeamMember, StaffTimeProject } from '../../../../data/entities'\nimport { staffTimeEntryBulkSaveSchema } from '../../../../data/validators'\nimport { staffTimeEntryCrudEvents } from '../../../../lib/crud'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../../guards'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.timesheets.manage_own'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, { error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.') })\n }\n\n const body = await readJsonSafe(req, {})\n const parsed = staffTimeEntryBulkSaveSchema.safeParse(body)\n if (!parsed.success) {\n const errors = parsed.error.issues.map((issue) => ({\n path: issue.path.join('.'),\n message: issue.message,\n }))\n return NextResponse.json({ ok: false, errors }, { status: 422 })\n }\n\n const { entries } = parsed.data\n\n const em = (container.resolve('em') as EntityManager).fork()\n const scopeCtx = { tenantId, organizationId }\n\n const staffMember = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, tenantId, organizationId, deletedAt: null },\n {},\n scopeCtx,\n )\n if (!staffMember) {\n throw new CrudHttpError(403, { error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.') })\n }\n const staffMemberId = staffMember.id\n\n // Validate that all referenced timeProjectIds exist and are in-scope\n const referencedProjectIds = [\n ...new Set(\n entries\n .map((e) => e.timeProjectId)\n .filter((id): id is string => typeof id === 'string' && id.length > 0),\n ),\n ]\n if (referencedProjectIds.length > 0) {\n const validProjects = await em.find(StaffTimeProject, {\n id: { $in: referencedProjectIds },\n tenantId,\n organizationId,\n deletedAt: null,\n }, { fields: ['id'] })\n const validIds = new Set(validProjects.map((p) => p.id))\n const invalidIds = referencedProjectIds.filter((id) => !validIds.has(id))\n if (invalidIds.length > 0) {\n return NextResponse.json(\n {\n ok: false,\n errors: invalidIds.map((id) => ({\n path: 'entries[].timeProjectId',\n message: translate('staff.timesheets.errors.projectNotFound', 'Time project not found or not accessible.'),\n value: id,\n })),\n },\n { status: 422 },\n )\n }\n }\n\n const guardInput = {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: staffMemberId,\n operation: 'update' as const,\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n }\n const guardResult = await runStaffMutationGuards(container, guardInput, resolveUserFeatures(auth))\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const existingIds = entries\n .map((entry) => entry.id)\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n\n // Validate referenced entry IDs upfront: a stale or foreign UUID would\n // otherwise fall through to the create branch in the loop below and insert\n // a duplicate row with that ID-less new identity. Reject as 422 instead.\n if (existingIds.length > 0) {\n const resolvedExisting = await em.find(\n StaffTimeEntry,\n { id: { $in: existingIds }, tenantId, organizationId, staffMemberId, deletedAt: null },\n { fields: ['id'] },\n )\n const resolvedIdSet = new Set(resolvedExisting.map((entry) => entry.id))\n const invalidIds = existingIds.filter((id) => !resolvedIdSet.has(id))\n if (invalidIds.length > 0) {\n return NextResponse.json(\n {\n ok: false,\n errors: invalidIds.map((id) => ({\n path: 'entries[].id',\n message: translate(\n 'staff.timesheets.errors.entryNotFound',\n 'Time entry not found, deleted, or not owned by you.',\n ),\n value: id,\n })),\n },\n { status: 422 },\n )\n }\n }\n\n type PendingChange = {\n action: 'created' | 'updated' | 'deleted'\n entity: StaffTimeEntry\n }\n\n const { counts, pendingChanges } = await em.transactional(async (trx) => {\n let created = 0\n let updated = 0\n let deleted = 0\n const changes: PendingChange[] = []\n\n const existingEntries = existingIds.length > 0\n ? await findWithDecryption(\n trx,\n StaffTimeEntry,\n { id: { $in: existingIds }, tenantId, organizationId, staffMemberId, deletedAt: null },\n {},\n scopeCtx,\n )\n : []\n\n const existingMap = new Map(existingEntries.map((entry) => [entry.id, entry]))\n\n for (const entry of entries) {\n if (entry.id && existingMap.has(entry.id)) {\n const existing = existingMap.get(entry.id)!\n if (entry.durationMinutes === 0) {\n existing.deletedAt = new Date()\n deleted++\n changes.push({ action: 'deleted', entity: existing })\n } else {\n existing.date = entry.date\n existing.timeProjectId = entry.timeProjectId\n existing.durationMinutes = entry.durationMinutes\n existing.notes = entry.notes ?? existing.notes\n existing.updatedAt = new Date()\n updated++\n changes.push({ action: 'updated', entity: existing })\n }\n } else {\n const now = new Date()\n const newEntry = trx.create(StaffTimeEntry, {\n tenantId,\n organizationId,\n staffMemberId,\n date: entry.date,\n timeProjectId: entry.timeProjectId,\n durationMinutes: entry.durationMinutes,\n notes: entry.notes ?? null,\n source: 'manual',\n createdAt: now,\n updatedAt: now,\n })\n created++\n changes.push({ action: 'created', entity: newEntry })\n }\n }\n\n await trx.flush()\n return { counts: { created, updated, deleted }, pendingChanges: changes }\n })\n\n const dataEngine = container.resolve<DataEngine>('dataEngine')\n for (const change of pendingChanges) {\n await emitCrudSideEffects({\n dataEngine,\n action: change.action,\n entity: change.entity,\n identifiers: {\n id: change.entity.id,\n organizationId: change.entity.organizationId,\n tenantId: change.entity.tenantId,\n },\n events: staffTimeEntryCrudEvents,\n })\n }\n await flushCrudSideEffects(dataEngine)\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: staffMemberId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n return NextResponse.json({ ok: true, ...counts }, { status: 200 })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('staff.timesheets.time-entries.bulk failed', err)\n return NextResponse.json(\n { error: translate('staff.timesheets.errors.bulkSave', 'Failed to bulk save time entries.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Bulk save time entries',\n methods: {\n POST: {\n summary: 'Bulk save time entries',\n description: 'Creates, updates, or soft-deletes multiple time entries in a single request. Entries with durationMinutes=0 and an existing id are soft-deleted.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTimeEntryBulkSaveSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Bulk save completed',\n schema: z.object({\n ok: z.literal(true),\n created: z.number(),\n updated: z.number(),\n deleted: z.number(),\n }),\n },\n {\n status: 422,\n description: 'Validation error',\n schema: z.object({\n ok: z.literal(false),\n errors: z.array(z.object({ path: z.string(), message: z.string() })),\n }),\n },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB,4BAA4B;AAI1D,SAAS,gBAAgB,iBAAiB,wBAAwB;AAClE,SAAS,oCAAoC;AAC7C,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,6BAA6B,EAAE;AAC9E;AAEA,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,6BAA6B,cAAc,EAAE,CAAC;AAEzG,UAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,UAAM,WAAW,OAAO,YAAY,KAAK,YAAY;AACrD,UAAM,iBAAiB,OAAO,cAAc,KAAK,SAAS;AAC1D,QAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,6BAA6B,uCAAuC,EAAE,CAAC;AAAA,IACzH;AAEA,UAAM,OAAO,MAAM,aAAa,KAAK,CAAC,CAAC;AACvC,UAAM,SAAS,6BAA6B,UAAU,IAAI;AAC1D,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,SAAS,OAAO,MAAM,OAAO,IAAI,CAAC,WAAW;AAAA,QACjD,MAAM,MAAM,KAAK,KAAK,GAAG;AAAA,QACzB,SAAS,MAAM;AAAA,MACjB,EAAE;AACF,aAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACjE;AAEA,UAAM,EAAE,QAAQ,IAAI,OAAO;AAE3B,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC3D,UAAM,WAAW,EAAE,UAAU,eAAe;AAE5C,UAAM,cAAc,MAAM;AAAA,MACxB;AAAA,MACA;AAAA,MACA,EAAE,QAAQ,KAAK,KAAK,UAAU,gBAAgB,WAAW,KAAK;AAAA,MAC9D,CAAC;AAAA,MACD;AAAA,IACF;AACA,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,yCAAyC,yCAAyC,EAAE,CAAC;AAAA,IACvI;AACA,UAAM,gBAAgB,YAAY;AAGlC,UAAM,uBAAuB;AAAA,MAC3B,GAAG,IAAI;AAAA,QACL,QACG,IAAI,CAAC,MAAM,EAAE,aAAa,EAC1B,OAAO,CAAC,OAAqB,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC;AAAA,MACzE;AAAA,IACF;AACA,QAAI,qBAAqB,SAAS,GAAG;AACnC,YAAM,gBAAgB,MAAM,GAAG,KAAK,kBAAkB;AAAA,QACpD,IAAI,EAAE,KAAK,qBAAqB;AAAA,QAChC;AAAA,QACA;AAAA,QACA,WAAW;AAAA,MACb,GAAG,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;AACrB,YAAM,WAAW,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACvD,YAAM,aAAa,qBAAqB,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;AACxE,UAAI,WAAW,SAAS,GAAG;AACzB,eAAO,aAAa;AAAA,UAClB;AAAA,YACE,IAAI;AAAA,YACJ,QAAQ,WAAW,IAAI,CAAC,QAAQ;AAAA,cAC9B,MAAM;AAAA,cACN,SAAS,UAAU,2CAA2C,2CAA2C;AAAA,cACzG,OAAO;AAAA,YACT,EAAE;AAAA,UACJ;AAAA,UACA,EAAE,QAAQ,IAAI;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA,QAAQ,KAAK,OAAO;AAAA,MACpB,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,MACpB,iBAAiB,OAAO;AAAA,IAC1B;AACA,UAAM,cAAc,MAAM,uBAAuB,WAAW,YAAY,oBAAoB,IAAI,CAAC;AACjG,QAAI,CAAC,YAAY,IAAI;AACnB,aAAO,aAAa;AAAA,QAClB,YAAY,aAAa,EAAE,OAAO,6BAA6B;AAAA,QAC/D,EAAE,QAAQ,YAAY,eAAe,IAAI;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,cAAc,QACjB,IAAI,CAAC,UAAU,MAAM,EAAE,EACvB,OAAO,CAAC,OAAqB,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC;AAKvE,QAAI,YAAY,SAAS,GAAG;AAC1B,YAAM,mBAAmB,MAAM,GAAG;AAAA,QAChC;AAAA,QACA,EAAE,IAAI,EAAE,KAAK,YAAY,GAAG,UAAU,gBAAgB,eAAe,WAAW,KAAK;AAAA,QACrF,EAAE,QAAQ,CAAC,IAAI,EAAE;AAAA,MACnB;AACA,YAAM,gBAAgB,IAAI,IAAI,iBAAiB,IAAI,CAAC,UAAU,MAAM,EAAE,CAAC;AACvE,YAAM,aAAa,YAAY,OAAO,CAAC,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC;AACpE,UAAI,WAAW,SAAS,GAAG;AACzB,eAAO,aAAa;AAAA,UAClB;AAAA,YACE,IAAI;AAAA,YACJ,QAAQ,WAAW,IAAI,CAAC,QAAQ;AAAA,cAC9B,MAAM;AAAA,cACN,SAAS;AAAA,gBACP;AAAA,gBACA;AAAA,cACF;AAAA,cACA,OAAO;AAAA,YACT,EAAE;AAAA,UACJ;AAAA,UACA,EAAE,QAAQ,IAAI;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAOA,UAAM,EAAE,QAAQ,eAAe,IAAI,MAAM,GAAG,cAAc,OAAO,QAAQ;AACvE,UAAI,UAAU;AACd,UAAI,UAAU;AACd,UAAI,UAAU;AACd,YAAM,UAA2B,CAAC;AAElC,YAAM,kBAAkB,YAAY,SAAS,IACzC,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,EAAE,IAAI,EAAE,KAAK,YAAY,GAAG,UAAU,gBAAgB,eAAe,WAAW,KAAK;AAAA,QACrF,CAAC;AAAA,QACD;AAAA,MACF,IACA,CAAC;AAEL,YAAM,cAAc,IAAI,IAAI,gBAAgB,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;AAE7E,iBAAW,SAAS,SAAS;AAC3B,YAAI,MAAM,MAAM,YAAY,IAAI,MAAM,EAAE,GAAG;AACzC,gBAAM,WAAW,YAAY,IAAI,MAAM,EAAE;AACzC,cAAI,MAAM,oBAAoB,GAAG;AAC/B,qBAAS,YAAY,oBAAI,KAAK;AAC9B;AACA,oBAAQ,KAAK,EAAE,QAAQ,WAAW,QAAQ,SAAS,CAAC;AAAA,UACtD,OAAO;AACL,qBAAS,OAAO,MAAM;AACtB,qBAAS,gBAAgB,MAAM;AAC/B,qBAAS,kBAAkB,MAAM;AACjC,qBAAS,QAAQ,MAAM,SAAS,SAAS;AACzC,qBAAS,YAAY,oBAAI,KAAK;AAC9B;AACA,oBAAQ,KAAK,EAAE,QAAQ,WAAW,QAAQ,SAAS,CAAC;AAAA,UACtD;AAAA,QACF,OAAO;AACL,gBAAM,MAAM,oBAAI,KAAK;AACrB,gBAAM,WAAW,IAAI,OAAO,gBAAgB;AAAA,YAC1C;AAAA,YACA;AAAA,YACA;AAAA,YACA,MAAM,MAAM;AAAA,YACZ,eAAe,MAAM;AAAA,YACrB,iBAAiB,MAAM;AAAA,YACvB,OAAO,MAAM,SAAS;AAAA,YACtB,QAAQ;AAAA,YACR,WAAW;AAAA,YACX,WAAW;AAAA,UACb,CAAC;AACD;AACA,kBAAQ,KAAK,EAAE,QAAQ,WAAW,QAAQ,SAAS,CAAC;AAAA,QACtD;AAAA,MACF;AAEA,YAAM,IAAI,MAAM;AAChB,aAAO,EAAE,QAAQ,EAAE,SAAS,SAAS,QAAQ,GAAG,gBAAgB,QAAQ;AAAA,IAC1E,CAAC;AAED,UAAM,aAAa,UAAU,QAAoB,YAAY;AAC7D,eAAW,UAAU,gBAAgB;AACnC,YAAM,oBAAoB;AAAA,QACxB;AAAA,QACA,QAAQ,OAAO;AAAA,QACf,QAAQ,OAAO;AAAA,QACf,aAAa;AAAA,UACX,IAAI,OAAO,OAAO;AAAA,UAClB,gBAAgB,OAAO,OAAO;AAAA,UAC9B,UAAU,OAAO,OAAO;AAAA,QAC1B;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,UAAM,qBAAqB,UAAU;AAErC,QAAI,YAAY,sBAAsB,QAAQ;AAC5C,YAAM,kCAAkC,YAAY,uBAAuB;AAAA,QACzE;AAAA,QACA;AAAA,QACA,QAAQ,KAAK,OAAO;AAAA,QACpB,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,GAAG,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnE,SAAS,KAAK;AACZ,QAAI,eAAe,eAAe;AAChC,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,YAAQ,MAAM,6CAA6C,GAAG;AAC9D,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,UAAU,oCAAoC,mCAAmC,EAAE;AAAA,MAC5F,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO;AAAA,YACf,IAAI,EAAE,QAAQ,IAAI;AAAA,YAClB,SAAS,EAAE,OAAO;AAAA,YAClB,SAAS,EAAE,OAAO;AAAA,YAClB,SAAS,EAAE,OAAO;AAAA,UACpB,CAAC;AAAA,QACH;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO;AAAA,YACf,IAAI,EAAE,QAAQ,KAAK;AAAA,YACnB,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;AAAA,UACrE,CAAC;AAAA,QACH;AAAA,QACA,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACpF,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,188 @@
1
+ import { z } from "zod";
2
+ import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
3
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
4
+ import { resolveCrudRecordId, parseScopedCommandInput } from "@open-mercato/shared/lib/api/scoped";
5
+ import { StaffTimeEntry } from "../../../data/entities.js";
6
+ import { staffTimeEntryCreateSchema, staffTimeEntryUpdateSchema } from "../../../data/validators.js";
7
+ import { createStaffCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from "../../openapi.js";
8
+ const F = {
9
+ id: "id",
10
+ tenant_id: "tenant_id",
11
+ organization_id: "organization_id",
12
+ staff_member_id: "staff_member_id",
13
+ date: "date",
14
+ duration_minutes: "duration_minutes",
15
+ started_at: "started_at",
16
+ ended_at: "ended_at",
17
+ notes: "notes",
18
+ time_project_id: "time_project_id",
19
+ customer_id: "customer_id",
20
+ deal_id: "deal_id",
21
+ order_id: "order_id",
22
+ source: "source",
23
+ created_at: "created_at",
24
+ updated_at: "updated_at",
25
+ deleted_at: "deleted_at"
26
+ };
27
+ const routeMetadata = {
28
+ GET: { requireAuth: true, requireFeatures: ["staff.timesheets.view"] },
29
+ POST: { requireAuth: true, requireFeatures: ["staff.timesheets.manage_own"] },
30
+ PUT: { requireAuth: true, requireFeatures: ["staff.timesheets.manage_own"] },
31
+ DELETE: { requireAuth: true, requireFeatures: ["staff.timesheets.manage_own"] }
32
+ };
33
+ const metadata = routeMetadata;
34
+ const rawBodySchema = z.object({}).passthrough();
35
+ const listSchema = z.object({
36
+ page: z.coerce.number().min(1).default(1),
37
+ pageSize: z.coerce.number().min(1).max(100).default(50),
38
+ staffMemberId: z.string().uuid().optional(),
39
+ from: z.string().optional(),
40
+ to: z.string().optional(),
41
+ projectId: z.string().uuid().optional(),
42
+ ids: z.string().optional(),
43
+ sortField: z.string().optional(),
44
+ sortDir: z.enum(["asc", "desc"]).optional()
45
+ }).passthrough();
46
+ const crud = makeCrudRoute({
47
+ metadata: routeMetadata,
48
+ orm: {
49
+ entity: StaffTimeEntry,
50
+ idField: "id",
51
+ orgField: "organizationId",
52
+ tenantField: "tenantId",
53
+ softDeleteField: "deletedAt"
54
+ },
55
+ indexer: { entityType: "staff:staff_time_entry" },
56
+ list: {
57
+ schema: listSchema,
58
+ entityId: "staff:staff_time_entry",
59
+ fields: [
60
+ F.id,
61
+ F.organization_id,
62
+ F.tenant_id,
63
+ F.staff_member_id,
64
+ F.date,
65
+ F.duration_minutes,
66
+ F.started_at,
67
+ F.ended_at,
68
+ F.notes,
69
+ F.time_project_id,
70
+ F.customer_id,
71
+ F.deal_id,
72
+ F.order_id,
73
+ F.source,
74
+ F.created_at,
75
+ F.updated_at
76
+ ],
77
+ sortFieldMap: {
78
+ date: F.date,
79
+ createdAt: F.created_at,
80
+ updatedAt: F.updated_at,
81
+ durationMinutes: F.duration_minutes
82
+ },
83
+ buildFilters: async (query) => {
84
+ const filters = {};
85
+ if (typeof query.ids === "string" && query.ids.trim().length > 0) {
86
+ const ids = query.ids.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
87
+ if (ids.length > 0) {
88
+ filters[F.id] = { $in: ids };
89
+ }
90
+ }
91
+ if (typeof query.staffMemberId === "string" && query.staffMemberId.length > 0) {
92
+ filters[F.staff_member_id] = query.staffMemberId;
93
+ }
94
+ if (typeof query.from === "string" && query.from.length > 0) {
95
+ filters[F.date] = { ...filters[F.date] ?? {}, $gte: query.from };
96
+ }
97
+ if (typeof query.to === "string" && query.to.length > 0) {
98
+ filters[F.date] = { ...filters[F.date] ?? {}, $lte: query.to };
99
+ }
100
+ if (typeof query.projectId === "string" && query.projectId.length > 0) {
101
+ filters[F.time_project_id] = query.projectId;
102
+ }
103
+ return filters;
104
+ }
105
+ },
106
+ actions: {
107
+ create: {
108
+ commandId: "staff.timesheets.time_entries.create",
109
+ schema: rawBodySchema,
110
+ mapInput: async ({ raw, ctx }) => {
111
+ const { translate } = await resolveTranslations();
112
+ return parseScopedCommandInput(staffTimeEntryCreateSchema, raw ?? {}, ctx, translate);
113
+ },
114
+ response: ({ result }) => ({ id: result?.timeEntryId ?? null }),
115
+ status: 201
116
+ },
117
+ update: {
118
+ commandId: "staff.timesheets.time_entries.update",
119
+ schema: rawBodySchema,
120
+ mapInput: async ({ raw, ctx }) => {
121
+ const { translate } = await resolveTranslations();
122
+ return parseScopedCommandInput(staffTimeEntryUpdateSchema, raw ?? {}, ctx, translate);
123
+ },
124
+ response: () => ({ ok: true })
125
+ },
126
+ delete: {
127
+ commandId: "staff.timesheets.time_entries.delete",
128
+ schema: rawBodySchema,
129
+ mapInput: async ({ parsed, ctx }) => {
130
+ const { translate } = await resolveTranslations();
131
+ const id = resolveCrudRecordId(parsed, ctx, translate);
132
+ return { id };
133
+ },
134
+ response: () => ({ ok: true })
135
+ }
136
+ }
137
+ });
138
+ const GET = crud.GET;
139
+ const POST = crud.POST;
140
+ const PUT = crud.PUT;
141
+ const DELETE = crud.DELETE;
142
+ const timeEntryListItemSchema = z.object({
143
+ id: z.string().uuid().nullable().optional(),
144
+ organization_id: z.string().uuid().nullable().optional(),
145
+ tenant_id: z.string().uuid().nullable().optional(),
146
+ staff_member_id: z.string().uuid().nullable().optional(),
147
+ date: z.string().nullable().optional(),
148
+ duration_minutes: z.number().nullable().optional(),
149
+ started_at: z.string().nullable().optional(),
150
+ ended_at: z.string().nullable().optional(),
151
+ notes: z.string().nullable().optional(),
152
+ time_project_id: z.string().uuid().nullable().optional(),
153
+ customer_id: z.string().uuid().nullable().optional(),
154
+ deal_id: z.string().uuid().nullable().optional(),
155
+ order_id: z.string().uuid().nullable().optional(),
156
+ source: z.string().nullable().optional(),
157
+ created_at: z.string().nullable().optional(),
158
+ updated_at: z.string().nullable().optional()
159
+ });
160
+ const openApi = createStaffCrudOpenApi({
161
+ resourceName: "TimeEntry",
162
+ pluralName: "TimeEntries",
163
+ querySchema: listSchema,
164
+ listResponseSchema: createPagedListResponseSchema(timeEntryListItemSchema),
165
+ create: {
166
+ schema: staffTimeEntryCreateSchema,
167
+ description: "Creates a time entry for a staff member."
168
+ },
169
+ update: {
170
+ schema: staffTimeEntryUpdateSchema,
171
+ responseSchema: defaultOkResponseSchema,
172
+ description: "Updates a time entry by id."
173
+ },
174
+ del: {
175
+ schema: z.object({ id: z.string().uuid() }),
176
+ responseSchema: defaultOkResponseSchema,
177
+ description: "Deletes a time entry by id."
178
+ }
179
+ });
180
+ export {
181
+ DELETE,
182
+ GET,
183
+ POST,
184
+ PUT,
185
+ metadata,
186
+ openApi
187
+ };
188
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/staff/api/timesheets/time-entries/route.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveCrudRecordId, parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { StaffTimeEntry } from '../../../data/entities'\nimport { staffTimeEntryCreateSchema, staffTimeEntryUpdateSchema } from '../../../data/validators'\nimport { createStaffCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from '../../openapi'\n\nconst F = {\n id: 'id',\n tenant_id: 'tenant_id',\n organization_id: 'organization_id',\n staff_member_id: 'staff_member_id',\n date: 'date',\n duration_minutes: 'duration_minutes',\n started_at: 'started_at',\n ended_at: 'ended_at',\n notes: 'notes',\n time_project_id: 'time_project_id',\n customer_id: 'customer_id',\n deal_id: 'deal_id',\n order_id: 'order_id',\n source: 'source',\n created_at: 'created_at',\n updated_at: 'updated_at',\n deleted_at: 'deleted_at',\n} as const\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['staff.timesheets.view'] },\n POST: { requireAuth: true, requireFeatures: ['staff.timesheets.manage_own'] },\n PUT: { requireAuth: true, requireFeatures: ['staff.timesheets.manage_own'] },\n DELETE: { requireAuth: true, requireFeatures: ['staff.timesheets.manage_own'] },\n}\n\nexport const metadata = routeMetadata\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n staffMemberId: z.string().uuid().optional(),\n from: z.string().optional(),\n to: z.string().optional(),\n projectId: z.string().uuid().optional(),\n ids: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: StaffTimeEntry,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: { entityType: 'staff:staff_time_entry' },\n list: {\n schema: listSchema,\n entityId: 'staff:staff_time_entry',\n fields: [\n F.id,\n F.organization_id,\n F.tenant_id,\n F.staff_member_id,\n F.date,\n F.duration_minutes,\n F.started_at,\n F.ended_at,\n F.notes,\n F.time_project_id,\n F.customer_id,\n F.deal_id,\n F.order_id,\n F.source,\n F.created_at,\n F.updated_at,\n ],\n sortFieldMap: {\n date: F.date,\n createdAt: F.created_at,\n updatedAt: F.updated_at,\n durationMinutes: F.duration_minutes,\n },\n buildFilters: async (query) => {\n const filters: Record<string, unknown> = {}\n if (typeof query.ids === 'string' && query.ids.trim().length > 0) {\n const ids = query.ids\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0)\n if (ids.length > 0) {\n filters[F.id] = { $in: ids }\n }\n }\n if (typeof query.staffMemberId === 'string' && query.staffMemberId.length > 0) {\n filters[F.staff_member_id] = query.staffMemberId\n }\n if (typeof query.from === 'string' && query.from.length > 0) {\n filters[F.date] = { ...((filters[F.date] as Record<string, unknown>) ?? {}), $gte: query.from }\n }\n if (typeof query.to === 'string' && query.to.length > 0) {\n filters[F.date] = { ...((filters[F.date] as Record<string, unknown>) ?? {}), $lte: query.to }\n }\n if (typeof query.projectId === 'string' && query.projectId.length > 0) {\n filters[F.time_project_id] = query.projectId\n }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'staff.timesheets.time_entries.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(staffTimeEntryCreateSchema, raw ?? {}, ctx, translate)\n },\n response: ({ result }) => ({ id: result?.timeEntryId ?? null }),\n status: 201,\n },\n update: {\n commandId: 'staff.timesheets.time_entries.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(staffTimeEntryUpdateSchema, raw ?? {}, ctx, translate)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'staff.timesheets.time_entries.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst timeEntryListItemSchema = z.object({\n id: z.string().uuid().nullable().optional(),\n organization_id: z.string().uuid().nullable().optional(),\n tenant_id: z.string().uuid().nullable().optional(),\n staff_member_id: z.string().uuid().nullable().optional(),\n date: z.string().nullable().optional(),\n duration_minutes: z.number().nullable().optional(),\n started_at: z.string().nullable().optional(),\n ended_at: z.string().nullable().optional(),\n notes: z.string().nullable().optional(),\n time_project_id: z.string().uuid().nullable().optional(),\n customer_id: z.string().uuid().nullable().optional(),\n deal_id: z.string().uuid().nullable().optional(),\n order_id: z.string().uuid().nullable().optional(),\n source: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n})\n\nexport const openApi = createStaffCrudOpenApi({\n resourceName: 'TimeEntry',\n pluralName: 'TimeEntries',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(timeEntryListItemSchema),\n create: {\n schema: staffTimeEntryCreateSchema,\n description: 'Creates a time entry for a staff member.',\n },\n update: {\n schema: staffTimeEntryUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates a time entry by id.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a time entry by id.',\n },\n})\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,qBAAqB,+BAA+B;AAC7D,SAAS,sBAAsB;AAC/B,SAAS,4BAA4B,kCAAkC;AACvE,SAAS,wBAAwB,+BAA+B,+BAA+B;AAE/F,MAAM,IAAI;AAAA,EACR,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,MAAM;AAAA,EACN,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AACd;AAEA,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,uBAAuB,EAAE;AAAA,EACrE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,6BAA6B,EAAE;AAAA,EAC5E,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,6BAA6B,EAAE;AAAA,EAC3E,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,6BAA6B,EAAE;AAChF;AAEO,MAAM,WAAW;AAExB,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,aAAa,EAChB,OAAO;AAAA,EACN,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EACxC,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EACtD,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC1C,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,IAAI,EAAE,OAAO,EAAE,SAAS;AAAA,EACxB,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACtC,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA,EACzB,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS;AAC5C,CAAC,EACA,YAAY;AAEf,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,SAAS,EAAE,YAAY,yBAAyB;AAAA,EAChD,MAAM;AAAA,IACJ,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,MACN,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,MACZ,MAAM,EAAE;AAAA,MACR,WAAW,EAAE;AAAA,MACb,WAAW,EAAE;AAAA,MACb,iBAAiB,EAAE;AAAA,IACrB;AAAA,IACA,cAAc,OAAO,UAAU;AAC7B,YAAM,UAAmC,CAAC;AAC1C,UAAI,OAAO,MAAM,QAAQ,YAAY,MAAM,IAAI,KAAK,EAAE,SAAS,GAAG;AAChE,cAAM,MAAM,MAAM,IACf,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AACrC,YAAI,IAAI,SAAS,GAAG;AAClB,kBAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,IAAI;AAAA,QAC7B;AAAA,MACF;AACA,UAAI,OAAO,MAAM,kBAAkB,YAAY,MAAM,cAAc,SAAS,GAAG;AAC7E,gBAAQ,EAAE,eAAe,IAAI,MAAM;AAAA,MACrC;AACA,UAAI,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,SAAS,GAAG;AAC3D,gBAAQ,EAAE,IAAI,IAAI,EAAE,GAAK,QAAQ,EAAE,IAAI,KAAiC,CAAC,GAAI,MAAM,MAAM,KAAK;AAAA,MAChG;AACA,UAAI,OAAO,MAAM,OAAO,YAAY,MAAM,GAAG,SAAS,GAAG;AACvD,gBAAQ,EAAE,IAAI,IAAI,EAAE,GAAK,QAAQ,EAAE,IAAI,KAAiC,CAAC,GAAI,MAAM,MAAM,GAAG;AAAA,MAC9F;AACA,UAAI,OAAO,MAAM,cAAc,YAAY,MAAM,UAAU,SAAS,GAAG;AACrE,gBAAQ,EAAE,eAAe,IAAI,MAAM;AAAA,MACrC;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,eAAO,wBAAwB,4BAA4B,OAAO,CAAC,GAAG,KAAK,SAAS;AAAA,MACtF;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO,EAAE,IAAI,QAAQ,eAAe,KAAK;AAAA,MAC7D,QAAQ;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,eAAO,wBAAwB,4BAA4B,OAAO,CAAC,GAAG,KAAK,SAAS;AAAA,MACtF;AAAA,MACA,UAAU,OAAO,EAAE,IAAI,KAAK;AAAA,IAC9B;AAAA,IACA,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,QAAQ,IAAI,MAAM;AACnC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,KAAK,oBAAoB,QAAQ,KAAK,SAAS;AACrD,eAAO,EAAE,GAAG;AAAA,MACd;AAAA,MACA,UAAU,OAAO,EAAE,IAAI,KAAK;AAAA,IAC9B;AAAA,EACF;AACF,CAAC;AAEM,MAAM,MAAM,KAAK;AACjB,MAAM,OAAO,KAAK;AAClB,MAAM,MAAM,KAAK;AACjB,MAAM,SAAS,KAAK;AAE3B,MAAM,0BAA0B,EAAE,OAAO;AAAA,EACvC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EAC1C,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACvD,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACjD,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACvD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACrC,kBAAkB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACjD,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC3C,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACzC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACtC,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACvD,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACnD,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EAC/C,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EAChD,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACvC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC3C,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAC7C,CAAC;AAEM,MAAM,UAAU,uBAAuB;AAAA,EAC5C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,oBAAoB,8BAA8B,uBAAuB;AAAA,EACzE,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aAAa;AAAA,EACf;AAAA,EACA,KAAK;AAAA,IACH,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,IAC1C,gBAAgB;AAAA,IAChB,aAAa;AAAA,EACf;AACF,CAAC;",
6
+ "names": []
7
+ }