@open-mercato/core 0.9.0 → 0.9.1-develop.7459.1.99e3accd30

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 (243) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
  3. package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
  4. package/dist/generated/entities/customer_interaction/index.js +2 -0
  5. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +2 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/commands/categories.js +1 -1
  9. package/dist/modules/catalog/commands/categories.js.map +2 -2
  10. package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
  11. package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
  12. package/dist/modules/catalog/commands/products.js +52 -17
  13. package/dist/modules/catalog/commands/products.js.map +2 -2
  14. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
  15. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
  16. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
  17. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
  18. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
  19. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
  20. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
  21. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
  22. package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
  23. package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
  24. package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
  25. package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
  26. package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
  27. package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
  28. package/dist/modules/customers/api/activity-types/route.js +151 -0
  29. package/dist/modules/customers/api/activity-types/route.js.map +7 -0
  30. package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
  31. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  32. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
  33. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
  34. package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
  35. package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
  36. package/dist/modules/customers/api/interactions/route.js +3 -0
  37. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  38. package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
  39. package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
  40. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
  41. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
  42. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
  43. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
  44. package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
  45. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  46. package/dist/modules/customers/calendar-event-types.js +548 -0
  47. package/dist/modules/customers/calendar-event-types.js.map +7 -0
  48. package/dist/modules/customers/commands/dictionaries.js +70 -10
  49. package/dist/modules/customers/commands/dictionaries.js.map +2 -2
  50. package/dist/modules/customers/commands/interactions.js +187 -22
  51. package/dist/modules/customers/commands/interactions.js.map +3 -3
  52. package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
  53. package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
  54. package/dist/modules/customers/components/DictionarySettings.js +3 -1
  55. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  56. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
  57. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
  58. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
  59. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  60. package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
  61. package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
  62. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
  63. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
  64. package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
  65. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
  66. package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
  67. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  68. package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
  69. package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
  70. package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
  71. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
  72. package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
  73. package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
  74. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
  75. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
  76. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
  77. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
  78. package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
  79. package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
  80. package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
  81. package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
  82. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
  83. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
  84. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
  85. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/types.js +1 -0
  87. package/dist/modules/customers/components/calendar/types.js.map +2 -2
  88. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
  89. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  90. package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
  91. package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
  92. package/dist/modules/customers/components/detail/DealForm.js +7 -3
  93. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  94. package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
  95. package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
  96. package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
  97. package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
  98. package/dist/modules/customers/components/detail/PersonCard.js +5 -2
  99. package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
  100. package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
  101. package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
  102. package/dist/modules/customers/data/entities.js +6 -0
  103. package/dist/modules/customers/data/entities.js.map +2 -2
  104. package/dist/modules/customers/data/validators.js +39 -10
  105. package/dist/modules/customers/data/validators.js.map +2 -2
  106. package/dist/modules/customers/di.js +20 -1
  107. package/dist/modules/customers/di.js.map +2 -2
  108. package/dist/modules/customers/extension-points.js +13 -0
  109. package/dist/modules/customers/extension-points.js.map +2 -2
  110. package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
  111. package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
  112. package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
  113. package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
  114. package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
  115. package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
  116. package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
  117. package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
  118. package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
  119. package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
  120. package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
  121. package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
  122. package/dist/modules/customers/lib/calendar/timezone.js +111 -0
  123. package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
  124. package/dist/modules/customers/lib/interactionReadModel.js +1 -0
  125. package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
  126. package/dist/modules/customers/lib/todoCompatibility.js +1 -0
  127. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  128. package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
  129. package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
  130. package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
  131. package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
  132. package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
  133. package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
  134. package/dist/modules/messages/subscribers/message-notification.js +13 -1
  135. package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
  136. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
  137. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
  138. package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
  139. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  140. package/dist/modules/planner/lib/availabilityMerge.js +101 -38
  141. package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
  142. package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
  143. package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
  144. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  145. package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
  146. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  147. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
  148. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  149. package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
  150. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  151. package/generated/entities/customer_dictionary_entry/index.ts +1 -0
  152. package/generated/entities/customer_interaction/index.ts +1 -0
  153. package/generated/entity-fields-registry.ts +2 -0
  154. package/package.json +9 -8
  155. package/src/modules/auth/i18n/de.json +3 -0
  156. package/src/modules/auth/i18n/en.json +3 -0
  157. package/src/modules/auth/i18n/es.json +3 -0
  158. package/src/modules/auth/i18n/ko.json +3 -0
  159. package/src/modules/auth/i18n/pl.json +3 -0
  160. package/src/modules/catalog/commands/categories.ts +1 -1
  161. package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
  162. package/src/modules/catalog/commands/products.ts +58 -17
  163. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
  164. package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
  165. package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
  166. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
  167. package/src/modules/communication_channels/i18n/de.json +4 -0
  168. package/src/modules/communication_channels/i18n/en.json +4 -0
  169. package/src/modules/communication_channels/i18n/es.json +4 -0
  170. package/src/modules/communication_channels/i18n/ko.json +4 -0
  171. package/src/modules/communication_channels/i18n/pl.json +4 -0
  172. package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
  173. package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
  174. package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
  175. package/src/modules/customers/api/activity-types/route.ts +154 -0
  176. package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
  177. package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
  178. package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
  179. package/src/modules/customers/api/interactions/route.ts +4 -0
  180. package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
  181. package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
  182. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
  183. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
  184. package/src/modules/customers/calendar-event-types.ts +674 -0
  185. package/src/modules/customers/commands/dictionaries.ts +84 -8
  186. package/src/modules/customers/commands/interactions.ts +244 -21
  187. package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
  188. package/src/modules/customers/components/DictionarySettings.tsx +5 -3
  189. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
  190. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
  191. package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
  192. package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
  193. package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
  194. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
  195. package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
  196. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
  197. package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
  198. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
  199. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
  200. package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
  201. package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
  202. package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
  203. package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
  204. package/src/modules/customers/components/calendar/types.ts +1 -0
  205. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
  206. package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
  207. package/src/modules/customers/components/detail/DealForm.tsx +7 -3
  208. package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
  209. package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
  210. package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
  211. package/src/modules/customers/components/detail/types.ts +1 -0
  212. package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
  213. package/src/modules/customers/data/entities.ts +8 -1
  214. package/src/modules/customers/data/validators.ts +42 -7
  215. package/src/modules/customers/di.ts +21 -0
  216. package/src/modules/customers/extension-points.ts +13 -0
  217. package/src/modules/customers/i18n/de.json +104 -8
  218. package/src/modules/customers/i18n/en.json +104 -8
  219. package/src/modules/customers/i18n/es.json +104 -8
  220. package/src/modules/customers/i18n/ko.json +104 -8
  221. package/src/modules/customers/i18n/pl.json +104 -8
  222. package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
  223. package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
  224. package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
  225. package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
  226. package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
  227. package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
  228. package/src/modules/customers/lib/calendar/timezone.ts +117 -0
  229. package/src/modules/customers/lib/interactionReadModel.ts +1 -0
  230. package/src/modules/customers/lib/todoCompatibility.ts +2 -0
  231. package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
  232. package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
  233. package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
  234. package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
  235. package/src/modules/messages/subscribers/message-notification.ts +13 -3
  236. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
  237. package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
  238. package/src/modules/planner/lib/availabilityMerge.ts +106 -41
  239. package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
  240. package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
  241. package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
  242. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
  243. package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
@@ -1,4 +1,6 @@
1
+ import { calendarEventTypeKeySchema, resolveCalendarEventType } from "../../calendar-event-types.js";
1
2
  import { parseRecurrenceRule } from "./recurrence.js";
3
+ import { calendarDayEndInstant, calendarDayStartInstant, calendarInstantToWallTime, calendarWallTimeToInstant, defaultCalendarTimezone, isCalendarTimezone } from "./timezone.js";
2
4
  const EDITOR_KINDS = ["meeting", "call", "email", "note", "event", "task"];
3
5
  const KIND_CONFIG = {
4
6
  meeting: { dateLabel: "starts", hasEnd: true, hasAllDay: true, hasRepeat: true, location: "location", people: "attendees", hasPriority: false },
@@ -21,6 +23,9 @@ const KIND_BY_INTERACTION_TYPE = {
21
23
  deadline: "task"
22
24
  };
23
25
  function editorKindOfInteractionType(interactionType) {
26
+ const parsedKey = calendarEventTypeKeySchema.safeParse(interactionType);
27
+ const effectiveKind = parsedKey.success ? resolveCalendarEventType(parsedKey.data)?.behavior.baseKind : void 0;
28
+ if (effectiveKind) return effectiveKind;
24
29
  return KIND_BY_INTERACTION_TYPE[interactionType] ?? "meeting";
25
30
  }
26
31
  const EDITOR_KIND_ICONS = {
@@ -113,6 +118,7 @@ function createDefaultFormState(defaultDate, now = /* @__PURE__ */ new Date(), r
113
118
  }
114
119
  return {
115
120
  kind: "meeting",
121
+ timezone: defaultCalendarTimezone(),
116
122
  title: "",
117
123
  relatedTo: null,
118
124
  dealId: null,
@@ -151,22 +157,27 @@ function buildRecurrenceRule(state) {
151
157
  }
152
158
  if (state.repeatEndType === "count") rule += `;COUNT=${state.repeatCount}`;
153
159
  if (state.repeatEndType === "date" && state.repeatUntilDate) {
154
- rule += `;UNTIL=${state.repeatUntilDate.replace(/-/g, "")}T235959Z`;
160
+ const until = state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : null;
161
+ rule += `;UNTIL=${until ? until.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z") : `${state.repeatUntilDate.replace(/-/g, "")}T235959Z`}`;
155
162
  }
156
163
  return rule;
157
164
  }
158
165
  function computeDurationMinutes(state) {
159
- const start = /* @__PURE__ */ new Date(`${state.date}T${state.startTime}:00`);
160
- const end = /* @__PURE__ */ new Date(`${state.endDate}T${state.endTime}:00`);
161
- if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null;
166
+ const start = state.timezone ? calendarWallTimeToInstant(state.date, state.startTime, state.timezone) : /* @__PURE__ */ new Date(`${state.date}T${state.startTime}:00`);
167
+ const end = state.timezone ? calendarWallTimeToInstant(state.endDate, state.endTime, state.timezone) : /* @__PURE__ */ new Date(`${state.endDate}T${state.endTime}:00`);
168
+ if (!start || !end || Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null;
162
169
  const minutes = Math.round((end.getTime() - start.getTime()) / 6e4);
163
170
  return minutes > 0 ? minutes : null;
164
171
  }
165
172
  function buildInteractionPayload(state, options) {
166
- const config = KIND_CONFIG[state.kind];
173
+ const config = options.config ?? KIND_CONFIG[state.kind];
167
174
  const time = state.allDay && config.hasAllDay ? "00:00" : state.startTime;
168
- const scheduledAt = (/* @__PURE__ */ new Date(`${state.date}T${time}:00`)).toISOString();
175
+ const instant = state.timezone ? state.allDay && config.hasAllDay ? calendarDayStartInstant(state.date, state.timezone) : calendarWallTimeToInstant(state.date, time, state.timezone) : /* @__PURE__ */ new Date(`${state.date}T${time}:00`);
176
+ if (!instant || !Number.isFinite(instant.getTime())) throw new Error("[internal] Invalid calendar local time");
177
+ const scheduledAt = instant.toISOString();
169
178
  const recurrenceRule = config.hasRepeat ? buildRecurrenceRule(state) : null;
179
+ const recurrenceEnd = recurrenceRule && state.repeatEndType === "date" && state.repeatUntilDate ? state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : new Date(state.repeatUntilDate) : null;
180
+ if (recurrenceRule && state.repeatEndType === "date" && state.repeatUntilDate && !recurrenceEnd) throw new Error("[internal] Invalid calendar recurrence day");
170
181
  const payload = {
171
182
  ...options.mode === "edit" && options.id ? { id: options.id } : {},
172
183
  entityId: state.relatedTo?.id ?? null,
@@ -178,11 +189,12 @@ function buildInteractionPayload(state, options) {
178
189
  date: state.date,
179
190
  time,
180
191
  scheduledAt,
192
+ ...state.timezone ? { timezone: state.timezone } : {},
181
193
  durationMinutes: config.hasEnd && !(state.allDay && config.hasAllDay) ? computeDurationMinutes(state) : null,
182
194
  allDay: config.hasAllDay ? state.allDay : null,
183
195
  location: config.location ? state.location.trim() || null : null,
184
196
  recurrenceRule,
185
- recurrenceEnd: recurrenceRule && state.repeatEndType === "date" && state.repeatUntilDate ? new Date(state.repeatUntilDate).toISOString() : null,
197
+ recurrenceEnd: recurrenceEnd?.toISOString() ?? null,
186
198
  participants: config.people && config.people !== "assignee" && state.participants.length > 0 ? state.participants.map((participant) => ({
187
199
  userId: participant.userId,
188
200
  name: participant.name,
@@ -209,10 +221,11 @@ function readUnknownString(value) {
209
221
  function readUnknownNumber(value) {
210
222
  return typeof value === "number" && Number.isFinite(value) ? value : null;
211
223
  }
212
- function parseRepeatFromRule(rawRule, start) {
224
+ function parseRepeatFromRule(rawRule, start, timezone) {
225
+ const wallStart = timezone ? /* @__PURE__ */ new Date(`${calendarInstantToWallTime(start, timezone).date}T12:00:00`) : start;
213
226
  const fallback = {
214
227
  repeatFreq: "none",
215
- repeatDays: defaultRepeatDays(start),
228
+ repeatDays: defaultRepeatDays(wallStart),
216
229
  repeatEndType: "never",
217
230
  repeatCount: 8,
218
231
  repeatUntilDate: ""
@@ -221,7 +234,7 @@ function parseRepeatFromRule(rawRule, start) {
221
234
  if (!ruleText) return fallback;
222
235
  const parsed = parseRecurrenceRule(ruleText);
223
236
  if (!parsed) return fallback;
224
- const repeatDays = defaultRepeatDays(start);
237
+ const repeatDays = defaultRepeatDays(wallStart);
225
238
  if (parsed.byDay) {
226
239
  repeatDays.fill(false);
227
240
  for (const jsWeekday of parsed.byDay) repeatDays[(jsWeekday + 6) % 7] = true;
@@ -234,7 +247,7 @@ function parseRepeatFromRule(rawRule, start) {
234
247
  repeatCount = parsed.count;
235
248
  } else if (parsed.until) {
236
249
  repeatEndType = "date";
237
- repeatUntilDate = `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`;
250
+ repeatUntilDate = timezone ? calendarInstantToWallTime(parsed.until, timezone).date : `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`;
238
251
  }
239
252
  return {
240
253
  repeatFreq: parsed.freq === "DAILY" ? "daily" : "weekly",
@@ -260,6 +273,9 @@ function parseItemToFormState(item) {
260
273
  const kind = editorKindOfInteractionType(item.interactionType);
261
274
  const raw = item.raw;
262
275
  const { resources, preservedLinkedEntities } = parseLinkedEntities(raw.linkedEntities);
276
+ const timezone = isCalendarTimezone(raw.timezone) ? raw.timezone : defaultCalendarTimezone();
277
+ const startWall = calendarInstantToWallTime(item.start, timezone);
278
+ const endWall = calendarInstantToWallTime(item.end, timezone);
263
279
  return {
264
280
  kind,
265
281
  title: item.title,
@@ -267,11 +283,13 @@ function parseItemToFormState(item) {
267
283
  dealId: item.dealId,
268
284
  dealLabel: null,
269
285
  allDay: item.allDay,
270
- date: formatLocalDateInput(item.start),
271
- startTime: formatLocalTimeInput(item.start),
272
- endDate: formatLocalDateInput(item.end),
273
- endTime: formatLocalTimeInput(item.end),
274
- ...parseRepeatFromRule(item.raw.recurrenceRule, item.start),
286
+ timezone,
287
+ date: startWall.date,
288
+ startTime: startWall.time,
289
+ endDate: endWall.date,
290
+ endTime: endWall.time,
291
+ ...parseRepeatFromRule(item.raw.recurrenceRule, item.start, isCalendarTimezone(raw.timezone) ? timezone : void 0),
292
+ ...item.raw.recurrenceEnd && isCalendarTimezone(raw.timezone) ? { repeatUntilDate: calendarInstantToWallTime(new Date(item.raw.recurrenceEnd), timezone).date } : {},
275
293
  category: item.interactionType,
276
294
  location: item.location ?? "",
277
295
  participants: parseParticipants(item),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/lib/calendar/editorPayload.ts"],
4
- "sourcesContent": ["import type { CalendarItem } from '../../components/calendar/types'\nimport { parseRecurrenceRule } from './recurrence'\n\nexport type EditorKind = 'meeting' | 'call' | 'email' | 'note' | 'event' | 'task'\n\nexport const EDITOR_KINDS: EditorKind[] = ['meeting', 'call', 'email', 'note', 'event', 'task']\n\nexport type EditorDateLabel = 'starts' | 'when' | 'sent' | 'logged' | 'due'\n\nexport type EditorPeopleMode = 'attendees' | 'participants' | 'to' | 'assignee'\n\nexport type EditorKindConfig = {\n dateLabel: EditorDateLabel\n hasEnd: boolean\n hasAllDay: boolean\n hasRepeat: boolean\n location: 'location' | 'phoneLink' | null\n people: EditorPeopleMode | null\n hasPriority: boolean\n}\n\nexport const KIND_CONFIG: Record<EditorKind, EditorKindConfig> = {\n meeting: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },\n call: { dateLabel: 'when', hasEnd: false, hasAllDay: true, hasRepeat: true, location: 'phoneLink', people: 'participants', hasPriority: false },\n email: { dateLabel: 'sent', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'to', hasPriority: false },\n note: { dateLabel: 'logged', hasEnd: false, hasAllDay: false, hasRepeat: false, location: null, people: null, hasPriority: false },\n event: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },\n task: { dateLabel: 'due', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'assignee', hasPriority: true },\n}\n\nconst KIND_BY_INTERACTION_TYPE: Record<string, EditorKind> = {\n meeting: 'meeting',\n call: 'call',\n 'video-call': 'call',\n email: 'email',\n note: 'note',\n event: 'event',\n webinar: 'event',\n task: 'task',\n todo: 'task',\n deadline: 'task',\n}\n\nexport function editorKindOfInteractionType(interactionType: string): EditorKind {\n return KIND_BY_INTERACTION_TYPE[interactionType] ?? 'meeting'\n}\n\nexport type EditorTypeOption = { value: string; label: string; icon: string | null }\n\n/** Fallback switcher icons matching the seeded dictionary appearance icons. */\nexport const EDITOR_KIND_ICONS: Record<EditorKind, string> = {\n meeting: 'lucide:users',\n call: 'lucide:phone-call',\n email: 'lucide:mail',\n note: 'lucide:notebook',\n event: 'lucide:calendar',\n task: 'lucide:check-square',\n}\n\n/**\n * Builds the editor's type-switcher options from the tenant `activity-types`\n * dictionary (#3552) so tenant-added types are first-class calendar event types,\n * not just Category values. Field morphology per option resolves through\n * `editorKindOfInteractionType` (custom types get the meeting-shaped default).\n * Falls back to the built-in kinds when the dictionary is empty/unavailable,\n * and always includes the currently selected value (e.g. a since-deleted type\n * on an existing event). Icons come from the dictionary entry appearance with\n * the seeded kind icons as fallback.\n */\nexport function buildEditorTypeOptions(params: {\n typeLabels: Record<string, string>\n typeIcons?: Record<string, string | null>\n selectedValue: string\n kindLabels: Record<EditorKind, string>\n}): EditorTypeOption[] {\n const { typeLabels, typeIcons, selectedValue, kindLabels } = params\n const iconOf = (value: string): string | null =>\n typeIcons?.[value] ?? EDITOR_KIND_ICONS[editorKindOfInteractionType(value)] ?? null\n const dictionaryEntries = Object.entries(typeLabels)\n const options: EditorTypeOption[] = dictionaryEntries.length\n ? dictionaryEntries.map(([value, label]) => ({ value, label, icon: iconOf(value) }))\n : EDITOR_KINDS.map((kind) => ({ value: kind, label: kindLabels[kind], icon: EDITOR_KIND_ICONS[kind] }))\n if (!options.some((option) => option.value === selectedValue)) {\n options.unshift({\n value: selectedValue,\n label: typeLabels[selectedValue] ?? kindLabels[editorKindOfInteractionType(selectedValue)] ?? selectedValue,\n icon: iconOf(selectedValue),\n })\n }\n return options\n}\n\nexport type EditorRepeatFreq = 'none' | 'daily' | 'weekly'\n\nexport type EditorRepeatEndType = 'never' | 'date' | 'count'\n\nexport type EditorPriority = 'low' | 'medium' | 'high'\n\nexport const PRIORITY_NUMBER: Record<EditorPriority, number> = { low: 10, medium: 50, high: 90 }\n\nexport function priorityFromNumber(value: number | null | undefined): EditorPriority {\n if (typeof value !== 'number' || Number.isNaN(value)) return 'medium'\n if (value <= 33) return 'low'\n if (value <= 66) return 'medium'\n return 'high'\n}\n\nexport type EditorRelatedTo = {\n id: string\n kind: 'person' | 'company' | 'unknown'\n label: string\n}\n\nexport type EditorParticipant = {\n userId?: string\n name: string\n email?: string\n isCustomer: boolean\n status?: string\n}\n\nconst CUSTOMER_PARTICIPANT_STATUS = 'customer'\n\nfunction resolveParticipantStatus(participant: EditorParticipant): string {\n if (participant.isCustomer) return CUSTOMER_PARTICIPANT_STATUS\n if (participant.status && participant.status !== CUSTOMER_PARTICIPANT_STATUS) return participant.status\n return 'pending'\n}\n\n/** linkedEntities `type` marker for resource assignments (FK-id + label snapshot). */\nexport const RESOURCE_LINK_TYPE = 'resource'\n\nexport type EditorLinkedEntity = { id: string; type: string; label: string }\n\nexport type EditorResource = { id: string; label: string }\n\nexport function parseLinkedEntities(raw: unknown): {\n resources: EditorResource[]\n preservedLinkedEntities: EditorLinkedEntity[]\n} {\n const resources: EditorResource[] = []\n const preservedLinkedEntities: EditorLinkedEntity[] = []\n if (!Array.isArray(raw)) return { resources, preservedLinkedEntities }\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, type, label } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id.length || typeof type !== 'string' || !type.length) continue\n const safeLabel = typeof label === 'string' ? label : ''\n if (type === RESOURCE_LINK_TYPE) resources.push({ id, label: safeLabel || id })\n else preservedLinkedEntities.push({ id, type, label: safeLabel })\n }\n return { resources, preservedLinkedEntities }\n}\n\nexport type EditorFormState = {\n kind: EditorKind\n title: string\n relatedTo: EditorRelatedTo | null\n dealId: string | null\n dealLabel: string | null\n allDay: boolean\n date: string\n startTime: string\n endDate: string\n endTime: string\n repeatFreq: EditorRepeatFreq\n repeatDays: boolean[]\n repeatEndType: EditorRepeatEndType\n repeatCount: number\n repeatUntilDate: string\n category: string | null\n location: string\n participants: EditorParticipant[]\n resources: EditorResource[]\n preservedLinkedEntities: EditorLinkedEntity[]\n assigneeUserId: string | null\n assigneeName: string | null\n priority: EditorPriority\n description: string\n status: 'planned' | 'done' | 'canceled'\n}\n\nconst MO_FIRST_DAY_TOKENS = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'] as const\n\nfunction padDatePart(value: number): string {\n return String(value).padStart(2, '0')\n}\n\nexport function formatLocalDateInput(date: Date): string {\n return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`\n}\n\nexport function formatLocalTimeInput(date: Date): string {\n return `${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`\n}\n\nfunction moFirstIndexOfDate(date: Date): number {\n return (date.getDay() + 6) % 7\n}\n\nexport function defaultRepeatDaysForDateInput(dateInput: string): boolean[] {\n const parsed = new Date(`${dateInput}T00:00:00`)\n return defaultRepeatDays(Number.isNaN(parsed.getTime()) ? new Date() : parsed)\n}\n\nfunction defaultRepeatDays(start: Date): boolean[] {\n const days = [false, false, false, false, false, false, false]\n days[moFirstIndexOfDate(start)] = true\n return days\n}\n\n/**\n * Resolves the `ownerUserId` an interaction will actually be SAVED with, so the\n * editor's conflict probe keys on the same owner the grid sees after save (see\n * buildInteractionPayload + the create/update command):\n * - Task (people === 'assignee'): owner = the assignee.\n * - Other kinds: the payload omits owner, so edit preserves the existing owner\n * and create is ownerless (null) \u2014 conflicts then come from participants only.\n */\nexport function resolveSavedOwnerUserId(\n config: EditorKindConfig,\n form: EditorFormState,\n isEdit: boolean,\n existingOwnerUserId: string | null,\n): string | null {\n if (config.people === 'assignee') return form.assigneeUserId ?? null\n return isEdit ? existingOwnerUserId : null\n}\n\nexport function createDefaultFormState(\n defaultDate?: Date | null,\n now: Date = new Date(),\n range?: { start: Date; end: Date } | null,\n): EditorFormState {\n let base: Date\n let end: Date\n if (range) {\n base = new Date(range.start)\n end = new Date(range.end)\n } else {\n base = defaultDate ? new Date(defaultDate) : new Date(now)\n base.setHours(now.getHours(), 0, 0, 0)\n base.setHours(base.getHours() + 1)\n end = new Date(base.getTime() + 90 * 60_000)\n }\n return {\n kind: 'meeting',\n title: '',\n relatedTo: null,\n dealId: null,\n dealLabel: null,\n allDay: false,\n date: formatLocalDateInput(base),\n startTime: formatLocalTimeInput(base),\n endDate: formatLocalDateInput(end),\n endTime: formatLocalTimeInput(end),\n repeatFreq: 'none',\n repeatDays: defaultRepeatDays(base),\n repeatEndType: 'never',\n repeatCount: 8,\n repeatUntilDate: '',\n category: null,\n location: '',\n participants: [],\n resources: [],\n preservedLinkedEntities: [],\n assigneeUserId: null,\n assigneeName: null,\n priority: 'medium',\n description: '',\n status: 'planned',\n }\n}\n\nexport function buildRecurrenceRule(state: EditorFormState): string | null {\n if (state.repeatFreq === 'none') return null\n let rule: string\n if (state.repeatFreq === 'daily') {\n rule = 'FREQ=DAILY'\n } else {\n const selected = MO_FIRST_DAY_TOKENS.filter((_, index) => state.repeatDays[index])\n const tokens = selected.length > 0\n ? selected\n : [MO_FIRST_DAY_TOKENS[moFirstIndexOfDate(new Date(`${state.date}T00:00:00`))]]\n rule = `FREQ=WEEKLY;BYDAY=${tokens.join(',')}`\n }\n if (state.repeatEndType === 'count') rule += `;COUNT=${state.repeatCount}`\n if (state.repeatEndType === 'date' && state.repeatUntilDate) {\n rule += `;UNTIL=${state.repeatUntilDate.replace(/-/g, '')}T235959Z`\n }\n return rule\n}\n\nexport function computeDurationMinutes(state: EditorFormState): number | null {\n const start = new Date(`${state.date}T${state.startTime}:00`)\n const end = new Date(`${state.endDate}T${state.endTime}:00`)\n if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null\n const minutes = Math.round((end.getTime() - start.getTime()) / 60_000)\n return minutes > 0 ? minutes : null\n}\n\nexport type BuildPayloadOptions = {\n mode: 'create' | 'edit'\n id?: string\n /**\n * When the resources module is loaded the payload owns `linkedEntities`\n * (preserved non-resource links + current resource assignments). When it is\n * not, the key is omitted entirely so edits never clobber links written by\n * other surfaces.\n */\n resourcesEnabled?: boolean\n /**\n * When the staff module is absent the Assignee field is hidden, so the\n * payload omits `ownerUserId` (edit keeps the stored owner untouched).\n */\n staffEnabled?: boolean\n}\n\nexport function buildInteractionPayload(state: EditorFormState, options: BuildPayloadOptions): Record<string, unknown> {\n const config = KIND_CONFIG[state.kind]\n const time = state.allDay && config.hasAllDay ? '00:00' : state.startTime\n const scheduledAt = new Date(`${state.date}T${time}:00`).toISOString()\n const recurrenceRule = config.hasRepeat ? buildRecurrenceRule(state) : null\n const payload: Record<string, unknown> = {\n ...(options.mode === 'edit' && options.id ? { id: options.id } : {}),\n entityId: state.relatedTo?.id ?? null,\n dealId: state.dealId ?? null,\n interactionType: state.category ?? state.kind,\n title: state.title.trim(),\n body: state.description.trim() || null,\n status: options.mode === 'create' ? 'planned' : state.status,\n date: state.date,\n time,\n scheduledAt,\n durationMinutes: config.hasEnd && !(state.allDay && config.hasAllDay) ? computeDurationMinutes(state) : null,\n allDay: config.hasAllDay ? state.allDay : null,\n location: config.location ? state.location.trim() || null : null,\n recurrenceRule,\n recurrenceEnd:\n recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate\n ? new Date(state.repeatUntilDate).toISOString()\n : null,\n participants:\n config.people && config.people !== 'assignee' && state.participants.length > 0\n ? state.participants.map((participant) => ({\n userId: participant.userId,\n name: participant.name,\n email: participant.email,\n status: resolveParticipantStatus(participant),\n }))\n : null,\n }\n if (config.people === 'assignee' && (options.staffEnabled ?? true)) {\n payload.ownerUserId = state.assigneeUserId ?? null\n }\n if (config.hasPriority) payload.priority = PRIORITY_NUMBER[state.priority]\n if (options.resourcesEnabled) {\n const linkedEntities: EditorLinkedEntity[] = [\n ...state.preservedLinkedEntities,\n ...state.resources.map((resource) => ({ id: resource.id, type: RESOURCE_LINK_TYPE, label: resource.label })),\n ]\n payload.linkedEntities = linkedEntities.length > 0 ? linkedEntities : null\n }\n return payload\n}\n\nfunction readUnknownString(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\nfunction readUnknownNumber(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null\n}\n\ntype ParsedRepeat = Pick<EditorFormState, 'repeatFreq' | 'repeatDays' | 'repeatEndType' | 'repeatCount' | 'repeatUntilDate'>\n\nfunction parseRepeatFromRule(rawRule: unknown, start: Date): ParsedRepeat {\n const fallback: ParsedRepeat = {\n repeatFreq: 'none',\n repeatDays: defaultRepeatDays(start),\n repeatEndType: 'never',\n repeatCount: 8,\n repeatUntilDate: '',\n }\n const ruleText = readUnknownString(rawRule)\n if (!ruleText) return fallback\n const parsed = parseRecurrenceRule(ruleText)\n if (!parsed) return fallback\n const repeatDays = defaultRepeatDays(start)\n if (parsed.byDay) {\n repeatDays.fill(false)\n for (const jsWeekday of parsed.byDay) repeatDays[(jsWeekday + 6) % 7] = true\n }\n let repeatEndType: EditorRepeatEndType = 'never'\n let repeatCount = 8\n let repeatUntilDate = ''\n if (parsed.count !== null) {\n repeatEndType = 'count'\n repeatCount = parsed.count\n } else if (parsed.until) {\n repeatEndType = 'date'\n repeatUntilDate = `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`\n }\n return {\n repeatFreq: parsed.freq === 'DAILY' ? 'daily' : 'weekly',\n repeatDays,\n repeatEndType,\n repeatCount,\n repeatUntilDate,\n }\n}\n\nfunction parseParticipants(item: CalendarItem): EditorParticipant[] {\n return item.participants.map((participant) => {\n const status = readUnknownString(participant.status)\n return {\n userId: participant.userId,\n name: participant.name ?? participant.email ?? participant.userId ?? '',\n email: participant.email,\n isCustomer: Boolean(participant.userId) && status === CUSTOMER_PARTICIPANT_STATUS,\n ...(status ? { status } : {}),\n }\n })\n}\n\nexport function parseItemToFormState(item: CalendarItem): EditorFormState {\n const kind = editorKindOfInteractionType(item.interactionType)\n const raw = item.raw as Record<string, unknown>\n const { resources, preservedLinkedEntities } = parseLinkedEntities(raw.linkedEntities)\n return {\n kind,\n title: item.title,\n relatedTo: item.entityId ? { id: item.entityId, kind: 'unknown', label: '' } : null,\n dealId: item.dealId,\n dealLabel: null,\n allDay: item.allDay,\n date: formatLocalDateInput(item.start),\n startTime: formatLocalTimeInput(item.start),\n endDate: formatLocalDateInput(item.end),\n endTime: formatLocalTimeInput(item.end),\n ...parseRepeatFromRule(item.raw.recurrenceRule, item.start),\n category: item.interactionType,\n location: item.location ?? '',\n participants: parseParticipants(item),\n resources,\n preservedLinkedEntities,\n assigneeUserId: item.ownerUserId,\n assigneeName: null,\n priority: priorityFromNumber(readUnknownNumber(raw.priority)),\n description: readUnknownString(raw.body) ?? '',\n status: item.status,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { CalendarItem } from '../../components/calendar/types'\nimport { calendarEventTypeKeySchema, resolveCalendarEventType } from '../../calendar-event-types'\nimport { parseRecurrenceRule } from './recurrence'\nimport { calendarDayEndInstant, calendarDayStartInstant, calendarInstantToWallTime, calendarWallTimeToInstant, defaultCalendarTimezone, isCalendarTimezone } from './timezone'\n\nexport type EditorKind = 'meeting' | 'call' | 'email' | 'note' | 'event' | 'task'\n\nexport const EDITOR_KINDS: EditorKind[] = ['meeting', 'call', 'email', 'note', 'event', 'task']\n\nexport type EditorDateLabel = 'starts' | 'when' | 'sent' | 'logged' | 'due'\n\nexport type EditorPeopleMode = 'attendees' | 'participants' | 'to' | 'assignee'\n\nexport type EditorKindConfig = {\n dateLabel: EditorDateLabel\n hasEnd: boolean\n hasAllDay: boolean\n hasRepeat: boolean\n location: 'location' | 'phoneLink' | null\n people: EditorPeopleMode | null\n hasPriority: boolean\n}\n\nexport const KIND_CONFIG: Record<EditorKind, EditorKindConfig> = {\n meeting: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },\n call: { dateLabel: 'when', hasEnd: false, hasAllDay: true, hasRepeat: true, location: 'phoneLink', people: 'participants', hasPriority: false },\n email: { dateLabel: 'sent', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'to', hasPriority: false },\n note: { dateLabel: 'logged', hasEnd: false, hasAllDay: false, hasRepeat: false, location: null, people: null, hasPriority: false },\n event: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },\n task: { dateLabel: 'due', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'assignee', hasPriority: true },\n}\n\nconst KIND_BY_INTERACTION_TYPE: Record<string, EditorKind> = {\n meeting: 'meeting',\n call: 'call',\n 'video-call': 'call',\n email: 'email',\n note: 'note',\n event: 'event',\n webinar: 'event',\n task: 'task',\n todo: 'task',\n deadline: 'task',\n}\n\n/** @deprecated Resolve the effective calendar event type and read `behavior.baseKind`. */\nexport function editorKindOfInteractionType(interactionType: string): EditorKind {\n const parsedKey = calendarEventTypeKeySchema.safeParse(interactionType)\n const effectiveKind = parsedKey.success ? resolveCalendarEventType(parsedKey.data)?.behavior.baseKind : undefined\n if (effectiveKind) return effectiveKind\n return KIND_BY_INTERACTION_TYPE[interactionType] ?? 'meeting'\n}\n\nexport type EditorTypeOption = { value: string; label: string; icon: string | null }\n\n/** Fallback switcher icons matching the seeded dictionary appearance icons. */\nexport const EDITOR_KIND_ICONS: Record<EditorKind, string> = {\n meeting: 'lucide:users',\n call: 'lucide:phone-call',\n email: 'lucide:mail',\n note: 'lucide:notebook',\n event: 'lucide:calendar',\n task: 'lucide:check-square',\n}\n\n/**\n * Builds the editor's type-switcher options from the tenant `activity-types`\n * dictionary (#3552) so tenant-added types are first-class calendar event types,\n * not just Category values. Field morphology per option resolves through\n * `editorKindOfInteractionType` (custom types get the meeting-shaped default).\n * Falls back to the built-in kinds when the dictionary is empty/unavailable,\n * and always includes the currently selected value (e.g. a since-deleted type\n * on an existing event). Icons come from the dictionary entry appearance with\n * the seeded kind icons as fallback.\n */\nexport function buildEditorTypeOptions(params: {\n typeLabels: Record<string, string>\n typeIcons?: Record<string, string | null>\n selectedValue: string\n kindLabels: Record<EditorKind, string>\n}): EditorTypeOption[] {\n const { typeLabels, typeIcons, selectedValue, kindLabels } = params\n const iconOf = (value: string): string | null =>\n typeIcons?.[value] ?? EDITOR_KIND_ICONS[editorKindOfInteractionType(value)] ?? null\n const dictionaryEntries = Object.entries(typeLabels)\n const options: EditorTypeOption[] = dictionaryEntries.length\n ? dictionaryEntries.map(([value, label]) => ({ value, label, icon: iconOf(value) }))\n : EDITOR_KINDS.map((kind) => ({ value: kind, label: kindLabels[kind], icon: EDITOR_KIND_ICONS[kind] }))\n if (!options.some((option) => option.value === selectedValue)) {\n options.unshift({\n value: selectedValue,\n label: typeLabels[selectedValue] ?? kindLabels[editorKindOfInteractionType(selectedValue)] ?? selectedValue,\n icon: iconOf(selectedValue),\n })\n }\n return options\n}\n\nexport type EditorRepeatFreq = 'none' | 'daily' | 'weekly'\n\nexport type EditorRepeatEndType = 'never' | 'date' | 'count'\n\nexport type EditorPriority = 'low' | 'medium' | 'high'\n\nexport const PRIORITY_NUMBER: Record<EditorPriority, number> = { low: 10, medium: 50, high: 90 }\n\nexport function priorityFromNumber(value: number | null | undefined): EditorPriority {\n if (typeof value !== 'number' || Number.isNaN(value)) return 'medium'\n if (value <= 33) return 'low'\n if (value <= 66) return 'medium'\n return 'high'\n}\n\nexport type EditorRelatedTo = {\n id: string\n kind: 'person' | 'company' | 'unknown'\n label: string\n}\n\nexport type EditorParticipant = {\n userId?: string\n name: string\n email?: string\n isCustomer: boolean\n status?: string\n}\n\nconst CUSTOMER_PARTICIPANT_STATUS = 'customer'\n\nfunction resolveParticipantStatus(participant: EditorParticipant): string {\n if (participant.isCustomer) return CUSTOMER_PARTICIPANT_STATUS\n if (participant.status && participant.status !== CUSTOMER_PARTICIPANT_STATUS) return participant.status\n return 'pending'\n}\n\n/** linkedEntities `type` marker for resource assignments (FK-id + label snapshot). */\nexport const RESOURCE_LINK_TYPE = 'resource'\n\nexport type EditorLinkedEntity = { id: string; type: string; label: string }\n\nexport type EditorResource = { id: string; label: string }\n\nexport function parseLinkedEntities(raw: unknown): {\n resources: EditorResource[]\n preservedLinkedEntities: EditorLinkedEntity[]\n} {\n const resources: EditorResource[] = []\n const preservedLinkedEntities: EditorLinkedEntity[] = []\n if (!Array.isArray(raw)) return { resources, preservedLinkedEntities }\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, type, label } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id.length || typeof type !== 'string' || !type.length) continue\n const safeLabel = typeof label === 'string' ? label : ''\n if (type === RESOURCE_LINK_TYPE) resources.push({ id, label: safeLabel || id })\n else preservedLinkedEntities.push({ id, type, label: safeLabel })\n }\n return { resources, preservedLinkedEntities }\n}\n\nexport type EditorFormState = {\n kind: EditorKind\n title: string\n relatedTo: EditorRelatedTo | null\n dealId: string | null\n dealLabel: string | null\n allDay: boolean\n date: string\n startTime: string\n timezone?: string\n endDate: string\n endTime: string\n repeatFreq: EditorRepeatFreq\n repeatDays: boolean[]\n repeatEndType: EditorRepeatEndType\n repeatCount: number\n repeatUntilDate: string\n category: string | null\n location: string\n participants: EditorParticipant[]\n resources: EditorResource[]\n preservedLinkedEntities: EditorLinkedEntity[]\n assigneeUserId: string | null\n assigneeName: string | null\n priority: EditorPriority\n description: string\n status: 'planned' | 'done' | 'canceled'\n}\n\nconst MO_FIRST_DAY_TOKENS = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'] as const\n\nfunction padDatePart(value: number): string {\n return String(value).padStart(2, '0')\n}\n\nexport function formatLocalDateInput(date: Date): string {\n return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`\n}\n\nexport function formatLocalTimeInput(date: Date): string {\n return `${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`\n}\n\nfunction moFirstIndexOfDate(date: Date): number {\n return (date.getDay() + 6) % 7\n}\n\nexport function defaultRepeatDaysForDateInput(dateInput: string): boolean[] {\n const parsed = new Date(`${dateInput}T00:00:00`)\n return defaultRepeatDays(Number.isNaN(parsed.getTime()) ? new Date() : parsed)\n}\n\nfunction defaultRepeatDays(start: Date): boolean[] {\n const days = [false, false, false, false, false, false, false]\n days[moFirstIndexOfDate(start)] = true\n return days\n}\n\n/**\n * Resolves the `ownerUserId` an interaction will actually be SAVED with, so the\n * editor's conflict probe keys on the same owner the grid sees after save (see\n * buildInteractionPayload + the create/update command):\n * - Task (people === 'assignee'): owner = the assignee.\n * - Other kinds: the payload omits owner, so edit preserves the existing owner\n * and create is ownerless (null) \u2014 conflicts then come from participants only.\n */\nexport function resolveSavedOwnerUserId(\n config: EditorKindConfig,\n form: EditorFormState,\n isEdit: boolean,\n existingOwnerUserId: string | null,\n): string | null {\n if (config.people === 'assignee') return form.assigneeUserId ?? null\n return isEdit ? existingOwnerUserId : null\n}\n\nexport function createDefaultFormState(\n defaultDate?: Date | null,\n now: Date = new Date(),\n range?: { start: Date; end: Date } | null,\n): EditorFormState {\n let base: Date\n let end: Date\n if (range) {\n base = new Date(range.start)\n end = new Date(range.end)\n } else {\n base = defaultDate ? new Date(defaultDate) : new Date(now)\n base.setHours(now.getHours(), 0, 0, 0)\n base.setHours(base.getHours() + 1)\n end = new Date(base.getTime() + 90 * 60_000)\n }\n return {\n kind: 'meeting',\n timezone: defaultCalendarTimezone(),\n title: '',\n relatedTo: null,\n dealId: null,\n dealLabel: null,\n allDay: false,\n date: formatLocalDateInput(base),\n startTime: formatLocalTimeInput(base),\n endDate: formatLocalDateInput(end),\n endTime: formatLocalTimeInput(end),\n repeatFreq: 'none',\n repeatDays: defaultRepeatDays(base),\n repeatEndType: 'never',\n repeatCount: 8,\n repeatUntilDate: '',\n category: null,\n location: '',\n participants: [],\n resources: [],\n preservedLinkedEntities: [],\n assigneeUserId: null,\n assigneeName: null,\n priority: 'medium',\n description: '',\n status: 'planned',\n }\n}\n\nexport function buildRecurrenceRule(state: EditorFormState): string | null {\n if (state.repeatFreq === 'none') return null\n let rule: string\n if (state.repeatFreq === 'daily') {\n rule = 'FREQ=DAILY'\n } else {\n const selected = MO_FIRST_DAY_TOKENS.filter((_, index) => state.repeatDays[index])\n const tokens = selected.length > 0\n ? selected\n : [MO_FIRST_DAY_TOKENS[moFirstIndexOfDate(new Date(`${state.date}T00:00:00`))]]\n rule = `FREQ=WEEKLY;BYDAY=${tokens.join(',')}`\n }\n if (state.repeatEndType === 'count') rule += `;COUNT=${state.repeatCount}`\n if (state.repeatEndType === 'date' && state.repeatUntilDate) {\n const until = state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : null\n rule += `;UNTIL=${until ? until.toISOString().replace(/[-:]/g, '').replace(/\\.\\d{3}Z$/, 'Z') : `${state.repeatUntilDate.replace(/-/g, '')}T235959Z`}`\n }\n return rule\n}\n\nexport function computeDurationMinutes(state: EditorFormState): number | null {\n const start = state.timezone ? calendarWallTimeToInstant(state.date, state.startTime, state.timezone) : new Date(`${state.date}T${state.startTime}:00`)\n const end = state.timezone ? calendarWallTimeToInstant(state.endDate, state.endTime, state.timezone) : new Date(`${state.endDate}T${state.endTime}:00`)\n if (!start || !end || Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null\n const minutes = Math.round((end.getTime() - start.getTime()) / 60_000)\n return minutes > 0 ? minutes : null\n}\n\nexport type BuildPayloadOptions = {\n mode: 'create' | 'edit'\n config?: EditorKindConfig\n id?: string\n /**\n * When the resources module is loaded the payload owns `linkedEntities`\n * (preserved non-resource links + current resource assignments). When it is\n * not, the key is omitted entirely so edits never clobber links written by\n * other surfaces.\n */\n resourcesEnabled?: boolean\n /**\n * When the staff module is absent the Assignee field is hidden, so the\n * payload omits `ownerUserId` (edit keeps the stored owner untouched).\n */\n staffEnabled?: boolean\n}\n\nexport function buildInteractionPayload(state: EditorFormState, options: BuildPayloadOptions): Record<string, unknown> {\n const config = options.config ?? KIND_CONFIG[state.kind]\n const time = state.allDay && config.hasAllDay ? '00:00' : state.startTime\n const instant = state.timezone ? (state.allDay && config.hasAllDay ? calendarDayStartInstant(state.date, state.timezone) : calendarWallTimeToInstant(state.date, time, state.timezone)) : new Date(`${state.date}T${time}:00`)\n if (!instant || !Number.isFinite(instant.getTime())) throw new Error('[internal] Invalid calendar local time')\n const scheduledAt = instant.toISOString()\n const recurrenceRule = config.hasRepeat ? buildRecurrenceRule(state) : null\n const recurrenceEnd = recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate\n ? state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : new Date(state.repeatUntilDate)\n : null\n if (recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate && !recurrenceEnd) throw new Error('[internal] Invalid calendar recurrence day')\n const payload: Record<string, unknown> = {\n ...(options.mode === 'edit' && options.id ? { id: options.id } : {}),\n entityId: state.relatedTo?.id ?? null,\n dealId: state.dealId ?? null,\n interactionType: state.category ?? state.kind,\n title: state.title.trim(),\n body: state.description.trim() || null,\n status: options.mode === 'create' ? 'planned' : state.status,\n date: state.date,\n time,\n scheduledAt,\n ...(state.timezone ? { timezone: state.timezone } : {}),\n durationMinutes: config.hasEnd && !(state.allDay && config.hasAllDay) ? computeDurationMinutes(state) : null,\n allDay: config.hasAllDay ? state.allDay : null,\n location: config.location ? state.location.trim() || null : null,\n recurrenceRule,\n recurrenceEnd: recurrenceEnd?.toISOString() ?? null,\n participants:\n config.people && config.people !== 'assignee' && state.participants.length > 0\n ? state.participants.map((participant) => ({\n userId: participant.userId,\n name: participant.name,\n email: participant.email,\n status: resolveParticipantStatus(participant),\n }))\n : null,\n }\n if (config.people === 'assignee' && (options.staffEnabled ?? true)) {\n payload.ownerUserId = state.assigneeUserId ?? null\n }\n if (config.hasPriority) payload.priority = PRIORITY_NUMBER[state.priority]\n if (options.resourcesEnabled) {\n const linkedEntities: EditorLinkedEntity[] = [\n ...state.preservedLinkedEntities,\n ...state.resources.map((resource) => ({ id: resource.id, type: RESOURCE_LINK_TYPE, label: resource.label })),\n ]\n payload.linkedEntities = linkedEntities.length > 0 ? linkedEntities : null\n }\n return payload\n}\n\nfunction readUnknownString(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\nfunction readUnknownNumber(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null\n}\n\ntype ParsedRepeat = Pick<EditorFormState, 'repeatFreq' | 'repeatDays' | 'repeatEndType' | 'repeatCount' | 'repeatUntilDate'>\n\nfunction parseRepeatFromRule(rawRule: unknown, start: Date, timezone?: string): ParsedRepeat {\n const wallStart = timezone ? new Date(`${calendarInstantToWallTime(start, timezone).date}T12:00:00`) : start\n const fallback: ParsedRepeat = {\n repeatFreq: 'none',\n repeatDays: defaultRepeatDays(wallStart),\n repeatEndType: 'never',\n repeatCount: 8,\n repeatUntilDate: '',\n }\n const ruleText = readUnknownString(rawRule)\n if (!ruleText) return fallback\n const parsed = parseRecurrenceRule(ruleText)\n if (!parsed) return fallback\n const repeatDays = defaultRepeatDays(wallStart)\n if (parsed.byDay) {\n repeatDays.fill(false)\n for (const jsWeekday of parsed.byDay) repeatDays[(jsWeekday + 6) % 7] = true\n }\n let repeatEndType: EditorRepeatEndType = 'never'\n let repeatCount = 8\n let repeatUntilDate = ''\n if (parsed.count !== null) {\n repeatEndType = 'count'\n repeatCount = parsed.count\n } else if (parsed.until) {\n repeatEndType = 'date'\n repeatUntilDate = timezone ? calendarInstantToWallTime(parsed.until, timezone).date : `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`\n }\n return {\n repeatFreq: parsed.freq === 'DAILY' ? 'daily' : 'weekly',\n repeatDays,\n repeatEndType,\n repeatCount,\n repeatUntilDate,\n }\n}\n\nfunction parseParticipants(item: CalendarItem): EditorParticipant[] {\n return item.participants.map((participant) => {\n const status = readUnknownString(participant.status)\n return {\n userId: participant.userId,\n name: participant.name ?? participant.email ?? participant.userId ?? '',\n email: participant.email,\n isCustomer: Boolean(participant.userId) && status === CUSTOMER_PARTICIPANT_STATUS,\n ...(status ? { status } : {}),\n }\n })\n}\n\nexport function parseItemToFormState(item: CalendarItem): EditorFormState {\n const kind = editorKindOfInteractionType(item.interactionType)\n const raw = item.raw as Record<string, unknown>\n const { resources, preservedLinkedEntities } = parseLinkedEntities(raw.linkedEntities)\n const timezone = isCalendarTimezone(raw.timezone) ? raw.timezone : defaultCalendarTimezone()\n const startWall = calendarInstantToWallTime(item.start, timezone)\n const endWall = calendarInstantToWallTime(item.end, timezone)\n return {\n kind,\n title: item.title,\n relatedTo: item.entityId ? { id: item.entityId, kind: 'unknown', label: '' } : null,\n dealId: item.dealId,\n dealLabel: null,\n allDay: item.allDay,\n timezone,\n date: startWall.date,\n startTime: startWall.time,\n endDate: endWall.date,\n endTime: endWall.time,\n ...parseRepeatFromRule(item.raw.recurrenceRule, item.start, isCalendarTimezone(raw.timezone) ? timezone : undefined),\n ...(item.raw.recurrenceEnd && isCalendarTimezone(raw.timezone) ? { repeatUntilDate: calendarInstantToWallTime(new Date(item.raw.recurrenceEnd), timezone).date } : {}),\n category: item.interactionType,\n location: item.location ?? '',\n participants: parseParticipants(item),\n resources,\n preservedLinkedEntities,\n assigneeUserId: item.ownerUserId,\n assigneeName: null,\n priority: priorityFromNumber(readUnknownNumber(raw.priority)),\n description: readUnknownString(raw.body) ?? '',\n status: item.status,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,197 @@
1
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
2
+ import { waitForAsyncRegistration } from "@open-mercato/shared/lib/bootstrap/factory";
3
+ import {
4
+ getInjectionRegistryVersion,
5
+ loadInjectionWidgetsForSpot
6
+ } from "@open-mercato/shared/modules/widgets/injection-loader";
7
+ import {
8
+ calendarEventTypeBehaviorSchema,
9
+ calendarEventTypes,
10
+ getCalendarEventTypes,
11
+ getCalendarEventTypeTombstones,
12
+ resolveCalendarEventType,
13
+ registerWidgetCalendarEventTypeContributions
14
+ } from "../../calendar-event-types.js";
15
+ import { CustomerDictionaryEntry } from "../../data/entities.js";
16
+ import { extensionPoints } from "../../extension-points.js";
17
+ function deepFreeze(value) {
18
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
19
+ Object.freeze(value);
20
+ for (const child of Object.values(value)) deepFreeze(child);
21
+ }
22
+ return value;
23
+ }
24
+ function serializeStaticType(type) {
25
+ return deepFreeze({
26
+ key: type.key,
27
+ label: type.label,
28
+ ...type.labelKey ? { labelKey: type.labelKey } : {},
29
+ icon: type.icon ?? null,
30
+ color: type.color ?? null,
31
+ behavior: type.behavior,
32
+ selectable: type.behavior.selectable,
33
+ ...type.panelKey ? { panelKey: type.panelKey } : {},
34
+ source: type.provenance.key?.moduleId ?? "customers",
35
+ provenance: type.provenance,
36
+ historical: type.historical,
37
+ ...type.fallbackReason ? { fallbackReason: type.fallbackReason } : {},
38
+ adminConfigurable: type.adminConfigurable !== false,
39
+ isInherited: false,
40
+ isLocalOverride: false,
41
+ updatedAt: null,
42
+ missingCustomFieldsetIds: []
43
+ });
44
+ }
45
+ function resolveBaselineCalendarEventTypes() {
46
+ const types = getCalendarEventTypes();
47
+ return deepFreeze({ items: types.map(serializeStaticType), fallbackKey: "meeting" });
48
+ }
49
+ let loadedWidgetVersion = -1;
50
+ let loadingWidgets = null;
51
+ async function ensureCalendarEventTypeWidgetsLoaded() {
52
+ await waitForAsyncRegistration();
53
+ const version = getInjectionRegistryVersion();
54
+ if (loadedWidgetVersion === version) return;
55
+ if (loadingWidgets) return loadingWidgets;
56
+ const pending = (async () => {
57
+ const widgets = await loadInjectionWidgetsForSpot(extensionPoints.hosts.calendarEventTypes.spotId);
58
+ registerWidgetCalendarEventTypeContributions(widgets.map((widget) => {
59
+ return {
60
+ moduleId: widget.moduleId,
61
+ widgetId: widget.metadata.id,
62
+ priority: typeof widget.placement?.priority === "number" ? widget.placement.priority : 0,
63
+ definitions: "eventTypes" in widget ? widget.eventTypes : null,
64
+ overrides: "eventTypeOverrides" in widget ? widget.eventTypeOverrides : void 0,
65
+ patches: "eventTypePatches" in widget ? widget.eventTypePatches : void 0
66
+ };
67
+ }));
68
+ loadedWidgetVersion = version;
69
+ })();
70
+ loadingWidgets = pending;
71
+ try {
72
+ await pending;
73
+ } finally {
74
+ if (loadingWidgets === pending) loadingWidgets = null;
75
+ }
76
+ }
77
+ function selectDictionaryRows(rows, organizationId, readableOrganizationIds) {
78
+ const orderedOrganizationIds = Array.from(/* @__PURE__ */ new Set([organizationId, ...readableOrganizationIds]));
79
+ const rank = new Map(orderedOrganizationIds.map((id, index) => [id, index]));
80
+ const sorted = [...rows].sort((left, right) => {
81
+ const rankDelta = (rank.get(left.organizationId) ?? Number.MAX_SAFE_INTEGER) - (rank.get(right.organizationId) ?? Number.MAX_SAFE_INTEGER);
82
+ return rankDelta || left.normalizedValue.localeCompare(right.normalizedValue);
83
+ });
84
+ const selected = /* @__PURE__ */ new Map();
85
+ for (const row of sorted) {
86
+ const key = row.normalizedValue.trim() || row.value.trim().toLowerCase();
87
+ if (key && !selected.has(key)) selected.set(key, row);
88
+ }
89
+ return selected;
90
+ }
91
+ function isUnchangedShippedDictionaryRow(row, key) {
92
+ const shipped = calendarEventTypes.find((entry) => entry.key === key);
93
+ return !!shipped && !row.activityTypeBehavior && row.label === shipped.label && row.icon === shipped.icon && row.color === shipped.color;
94
+ }
95
+ async function resolveScopedCalendarEventTypes(input) {
96
+ await ensureCalendarEventTypeWidgetsLoaded();
97
+ const scopeOrganizationIds = Array.from(/* @__PURE__ */ new Set([input.organizationId, ...input.readableOrganizationIds]));
98
+ const rows = await findWithDecryption(
99
+ input.em,
100
+ CustomerDictionaryEntry,
101
+ {
102
+ tenantId: input.tenantId,
103
+ organizationId: { $in: scopeOrganizationIds },
104
+ kind: "activity_type"
105
+ },
106
+ { orderBy: { label: "asc" } },
107
+ { tenantId: input.tenantId, organizationId: input.organizationId }
108
+ );
109
+ const dictionaryRows = selectDictionaryRows(rows, input.organizationId, scopeOrganizationIds);
110
+ for (const [key, row] of dictionaryRows) {
111
+ if (isUnchangedShippedDictionaryRow(row, key)) dictionaryRows.delete(key);
112
+ }
113
+ const staticCatalog = resolveBaselineCalendarEventTypes();
114
+ const availableFieldsets = input.availableCustomFieldsetIdsFromEntitiesBoundary ? new Set(input.availableCustomFieldsetIdsFromEntitiesBoundary) : null;
115
+ const staticByKey = new Map(staticCatalog.items.map((item) => [item.key, item]));
116
+ const tombstonedKeys = new Set(getCalendarEventTypeTombstones().map((entry) => entry.key));
117
+ const keys = /* @__PURE__ */ new Set([...staticByKey.keys(), ...[...dictionaryRows.keys()].filter((key) => !tombstonedKeys.has(key))]);
118
+ const fallbackBehavior = calendarEventTypes[0].behavior;
119
+ const items = [...keys].map((key) => {
120
+ const base = staticByKey.get(key);
121
+ const row = dictionaryRows.get(key);
122
+ if (!row) {
123
+ if (!availableFieldsets) return base;
124
+ return deepFreeze({
125
+ ...base,
126
+ missingCustomFieldsetIds: base.behavior.customFieldsetIds.filter((id) => !availableFieldsets.has(id))
127
+ });
128
+ }
129
+ if (base?.adminConfigurable === false) {
130
+ return deepFreeze({
131
+ ...base,
132
+ inactiveDictionaryOverride: true
133
+ });
134
+ }
135
+ const parsedBehavior = row.activityTypeBehavior ? calendarEventTypeBehaviorSchema.safeParse(row.activityTypeBehavior) : null;
136
+ const behavior = parsedBehavior?.success ? parsedBehavior.data : base?.behavior ?? { ...fallbackBehavior, selectable: true };
137
+ const isLocalOverride = row.organizationId === input.organizationId;
138
+ const defaultLabel = calendarEventTypes.find((entry) => entry.key === key)?.label ?? base?.label;
139
+ const hasCustomLabel = !!row.label && row.label !== defaultLabel;
140
+ return deepFreeze({
141
+ key,
142
+ label: hasCustomLabel ? row.label : base?.label || row.label || row.value,
143
+ ...!hasCustomLabel && base?.labelKey ? { labelKey: base.labelKey } : {},
144
+ icon: row.icon ?? base?.icon ?? null,
145
+ color: row.color ?? base?.color ?? null,
146
+ behavior,
147
+ selectable: behavior.selectable,
148
+ ...base?.panelKey ? { panelKey: base.panelKey } : {},
149
+ source: base?.source ?? "dictionary",
150
+ provenance: base?.provenance ?? {},
151
+ historical: false,
152
+ adminConfigurable: true,
153
+ isInherited: !isLocalOverride,
154
+ isLocalOverride,
155
+ updatedAt: row.updatedAt instanceof Date ? row.updatedAt.toISOString() : null,
156
+ missingCustomFieldsetIds: availableFieldsets ? behavior.customFieldsetIds.filter((id) => !availableFieldsets.has(id)) : []
157
+ });
158
+ });
159
+ items.sort((left, right) => left.behavior.order - right.behavior.order || left.key.localeCompare(right.key));
160
+ return deepFreeze({ items, fallbackKey: "meeting" });
161
+ }
162
+ function resolveCatalogEventType(catalog, key, options = {}) {
163
+ const normalizedKey = key.trim().toLowerCase();
164
+ const item = catalog.items.find((entry) => entry.key === normalizedKey);
165
+ if (item || !options.includeHistorical) return item;
166
+ const registryHistorical = resolveCalendarEventType(key, { includeHistorical: true });
167
+ if (registryHistorical) {
168
+ const serialized = serializeStaticType(registryHistorical);
169
+ return deepFreeze({
170
+ ...serialized,
171
+ selectable: false,
172
+ behavior: { ...serialized.behavior, selectable: false },
173
+ historical: true
174
+ });
175
+ }
176
+ const fallback = catalog.items.find((entry) => entry.key === catalog.fallbackKey);
177
+ if (!fallback) return void 0;
178
+ return deepFreeze({
179
+ ...fallback,
180
+ key,
181
+ label: key,
182
+ labelKey: void 0,
183
+ behavior: { ...fallback.behavior, selectable: false },
184
+ selectable: false,
185
+ historical: true,
186
+ source: "historical-fallback",
187
+ isInherited: false,
188
+ isLocalOverride: false,
189
+ updatedAt: null
190
+ });
191
+ }
192
+ export {
193
+ resolveBaselineCalendarEventTypes,
194
+ resolveCatalogEventType,
195
+ resolveScopedCalendarEventTypes
196
+ };
197
+ //# sourceMappingURL=eventTypeResolver.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customers/lib/calendar/eventTypeResolver.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { waitForAsyncRegistration } from '@open-mercato/shared/lib/bootstrap/factory'\nimport {\n getInjectionRegistryVersion,\n loadInjectionWidgetsForSpot,\n} from '@open-mercato/shared/modules/widgets/injection-loader'\nimport {\n calendarEventTypeBehaviorSchema,\n calendarEventTypes,\n getCalendarEventTypes,\n getCalendarEventTypeTombstones,\n resolveCalendarEventType,\n registerWidgetCalendarEventTypeContributions,\n type CalendarEventTypeBehavior,\n type CalendarEventTypeProvenance,\n type EffectiveCalendarEventType,\n} from '../../calendar-event-types'\nimport { CustomerDictionaryEntry } from '../../data/entities'\nimport { extensionPoints } from '../../extension-points'\n\nexport type ScopedCalendarEventType = Readonly<{\n key: string\n label: string\n labelKey?: string\n icon?: string | null\n color?: string | null\n behavior: CalendarEventTypeBehavior\n selectable: boolean\n panelKey?: string\n source: string\n provenance: Readonly<Record<string, CalendarEventTypeProvenance>>\n historical: boolean\n fallbackReason?: 'tombstoned' | 'module-unavailable' | 'unknown'\n adminConfigurable: boolean\n isInherited: boolean\n isLocalOverride: boolean\n updatedAt: string | null\n missingCustomFieldsetIds: readonly string[]\n inactiveDictionaryOverride?: boolean\n}>\n\nexport type CalendarEventTypeCatalog = Readonly<{\n items: readonly ScopedCalendarEventType[]\n fallbackKey: 'meeting'\n}>\n\ntype ResolveCalendarEventTypesInput = {\n em: EntityManager\n tenantId: string\n organizationId: string\n readableOrganizationIds: readonly string[]\n availableCustomFieldsetIdsFromEntitiesBoundary?: readonly string[]\n}\n\nfunction deepFreeze<T>(value: T): T {\n if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.freeze(value)\n for (const child of Object.values(value as Record<string, unknown>)) deepFreeze(child)\n }\n return value\n}\n\nfunction serializeStaticType(type: EffectiveCalendarEventType): ScopedCalendarEventType {\n return deepFreeze({\n key: type.key,\n label: type.label,\n ...(type.labelKey ? { labelKey: type.labelKey } : {}),\n icon: type.icon ?? null,\n color: type.color ?? null,\n behavior: type.behavior,\n selectable: type.behavior.selectable,\n ...(type.panelKey ? { panelKey: type.panelKey } : {}),\n source: type.provenance.key?.moduleId ?? 'customers',\n provenance: type.provenance,\n historical: type.historical,\n ...(type.fallbackReason ? { fallbackReason: type.fallbackReason } : {}),\n adminConfigurable: type.adminConfigurable !== false,\n isInherited: false,\n isLocalOverride: false,\n updatedAt: null,\n missingCustomFieldsetIds: [],\n })\n}\n\nexport function resolveBaselineCalendarEventTypes(): CalendarEventTypeCatalog {\n const types: readonly EffectiveCalendarEventType[] = getCalendarEventTypes()\n return deepFreeze({ items: types.map(serializeStaticType), fallbackKey: 'meeting' })\n}\n\nlet loadedWidgetVersion = -1\nlet loadingWidgets: Promise<void> | null = null\n\nasync function ensureCalendarEventTypeWidgetsLoaded(): Promise<void> {\n await waitForAsyncRegistration()\n const version = getInjectionRegistryVersion()\n if (loadedWidgetVersion === version) return\n if (loadingWidgets) return loadingWidgets\n const pending = (async () => {\n const widgets = await loadInjectionWidgetsForSpot(extensionPoints.hosts.calendarEventTypes.spotId)\n registerWidgetCalendarEventTypeContributions(widgets.map((widget) => {\n return {\n moduleId: widget.moduleId,\n widgetId: widget.metadata.id,\n priority: typeof widget.placement?.priority === 'number' ? widget.placement.priority : 0,\n definitions: 'eventTypes' in widget ? widget.eventTypes : null,\n overrides: 'eventTypeOverrides' in widget ? widget.eventTypeOverrides : undefined,\n patches: 'eventTypePatches' in widget ? widget.eventTypePatches : undefined,\n }\n }))\n loadedWidgetVersion = version\n })()\n loadingWidgets = pending\n try {\n await pending\n } finally {\n if (loadingWidgets === pending) loadingWidgets = null\n }\n}\n\nfunction selectDictionaryRows(\n rows: readonly CustomerDictionaryEntry[],\n organizationId: string,\n readableOrganizationIds: readonly string[],\n): Map<string, CustomerDictionaryEntry> {\n const orderedOrganizationIds = Array.from(new Set([organizationId, ...readableOrganizationIds]))\n const rank = new Map(orderedOrganizationIds.map((id, index) => [id, index]))\n const sorted = [...rows].sort((left, right) => {\n const rankDelta = (rank.get(left.organizationId) ?? Number.MAX_SAFE_INTEGER) -\n (rank.get(right.organizationId) ?? Number.MAX_SAFE_INTEGER)\n return rankDelta || left.normalizedValue.localeCompare(right.normalizedValue)\n })\n const selected = new Map<string, CustomerDictionaryEntry>()\n for (const row of sorted) {\n const key = row.normalizedValue.trim() || row.value.trim().toLowerCase()\n if (key && !selected.has(key)) selected.set(key, row)\n }\n return selected\n}\n\nfunction isUnchangedShippedDictionaryRow(row: CustomerDictionaryEntry, key: string): boolean {\n const shipped = calendarEventTypes.find((entry) => entry.key === key)\n return !!shipped && !row.activityTypeBehavior && row.label === shipped.label &&\n row.icon === shipped.icon && row.color === shipped.color\n}\n\nexport async function resolveScopedCalendarEventTypes(\n input: ResolveCalendarEventTypesInput,\n): Promise<CalendarEventTypeCatalog> {\n await ensureCalendarEventTypeWidgetsLoaded()\n const scopeOrganizationIds = Array.from(new Set([input.organizationId, ...input.readableOrganizationIds]))\n const rows = await findWithDecryption(\n input.em,\n CustomerDictionaryEntry,\n {\n tenantId: input.tenantId,\n organizationId: { $in: scopeOrganizationIds },\n kind: 'activity_type',\n },\n { orderBy: { label: 'asc' } },\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n const dictionaryRows = selectDictionaryRows(rows, input.organizationId, scopeOrganizationIds)\n for (const [key, row] of dictionaryRows) {\n if (isUnchangedShippedDictionaryRow(row, key)) dictionaryRows.delete(key)\n }\n const staticCatalog = resolveBaselineCalendarEventTypes()\n const availableFieldsets = input.availableCustomFieldsetIdsFromEntitiesBoundary\n ? new Set(input.availableCustomFieldsetIdsFromEntitiesBoundary)\n : null\n const staticByKey = new Map(staticCatalog.items.map((item) => [item.key, item]))\n const tombstonedKeys = new Set(getCalendarEventTypeTombstones().map((entry) => entry.key))\n const keys = new Set([...staticByKey.keys(), ...[...dictionaryRows.keys()].filter((key) => !tombstonedKeys.has(key))])\n const fallbackBehavior = calendarEventTypes[0]!.behavior\n const items = [...keys].map((key): ScopedCalendarEventType => {\n const base = staticByKey.get(key)\n const row = dictionaryRows.get(key)\n if (!row) {\n if (!availableFieldsets) return base!\n return deepFreeze({\n ...base!,\n missingCustomFieldsetIds: base!.behavior.customFieldsetIds.filter((id) => !availableFieldsets.has(id)),\n })\n }\n if (base?.adminConfigurable === false) {\n return deepFreeze({\n ...base,\n inactiveDictionaryOverride: true,\n })\n }\n const parsedBehavior = row.activityTypeBehavior\n ? calendarEventTypeBehaviorSchema.safeParse(row.activityTypeBehavior)\n : null\n const behavior = parsedBehavior?.success\n ? parsedBehavior.data\n : base?.behavior ?? { ...fallbackBehavior, selectable: true }\n const isLocalOverride = row.organizationId === input.organizationId\n const defaultLabel = calendarEventTypes.find((entry) => entry.key === key)?.label ?? base?.label\n const hasCustomLabel = !!row.label && row.label !== defaultLabel\n return deepFreeze({\n key,\n label: hasCustomLabel ? row.label : base?.label || row.label || row.value,\n ...(!hasCustomLabel && base?.labelKey ? { labelKey: base.labelKey } : {}),\n icon: row.icon ?? base?.icon ?? null,\n color: row.color ?? base?.color ?? null,\n behavior,\n selectable: behavior.selectable,\n ...(base?.panelKey ? { panelKey: base.panelKey } : {}),\n source: base?.source ?? 'dictionary',\n provenance: base?.provenance ?? {},\n historical: false,\n adminConfigurable: true,\n isInherited: !isLocalOverride,\n isLocalOverride,\n updatedAt: row.updatedAt instanceof Date ? row.updatedAt.toISOString() : null,\n missingCustomFieldsetIds: availableFieldsets\n ? behavior.customFieldsetIds.filter((id) => !availableFieldsets.has(id))\n : [],\n })\n })\n items.sort((left, right) => left.behavior.order - right.behavior.order || left.key.localeCompare(right.key))\n return deepFreeze({ items, fallbackKey: 'meeting' })\n}\n\nexport function resolveCatalogEventType(\n catalog: CalendarEventTypeCatalog,\n key: string,\n options: { includeHistorical?: boolean } = {},\n): ScopedCalendarEventType | undefined {\n const normalizedKey = key.trim().toLowerCase()\n const item = catalog.items.find((entry) => entry.key === normalizedKey)\n if (item || !options.includeHistorical) return item\n const registryHistorical = resolveCalendarEventType(key, { includeHistorical: true })\n if (registryHistorical) {\n const serialized = serializeStaticType(registryHistorical)\n return deepFreeze({\n ...serialized,\n selectable: false,\n behavior: { ...serialized.behavior, selectable: false },\n historical: true,\n })\n }\n const fallback = catalog.items.find((entry) => entry.key === catalog.fallbackKey)\n if (!fallback) return undefined\n return deepFreeze({\n ...fallback,\n key,\n label: key,\n labelKey: undefined,\n behavior: { ...fallback.behavior, selectable: false },\n selectable: false,\n historical: true,\n source: 'historical-fallback',\n isInherited: false,\n isLocalOverride: false,\n updatedAt: null,\n })\n}\n"],
5
+ "mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,+BAA+B;AACxC,SAAS,uBAAuB;AAoChC,SAAS,WAAc,OAAa;AAClC,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG;AAC1E,WAAO,OAAO,KAAK;AACnB,eAAW,SAAS,OAAO,OAAO,KAAgC,EAAG,YAAW,KAAK;AAAA,EACvF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,MAA2D;AACtF,SAAO,WAAW;AAAA,IAChB,KAAK,KAAK;AAAA,IACV,OAAO,KAAK;AAAA,IACZ,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,IACnD,MAAM,KAAK,QAAQ;AAAA,IACnB,OAAO,KAAK,SAAS;AAAA,IACrB,UAAU,KAAK;AAAA,IACf,YAAY,KAAK,SAAS;AAAA,IAC1B,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,IACnD,QAAQ,KAAK,WAAW,KAAK,YAAY;AAAA,IACzC,YAAY,KAAK;AAAA,IACjB,YAAY,KAAK;AAAA,IACjB,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;AAAA,IACrE,mBAAmB,KAAK,sBAAsB;AAAA,IAC9C,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,WAAW;AAAA,IACX,0BAA0B,CAAC;AAAA,EAC7B,CAAC;AACH;AAEO,SAAS,oCAA8D;AAC5E,QAAM,QAA+C,sBAAsB;AAC3E,SAAO,WAAW,EAAE,OAAO,MAAM,IAAI,mBAAmB,GAAG,aAAa,UAAU,CAAC;AACrF;AAEA,IAAI,sBAAsB;AAC1B,IAAI,iBAAuC;AAE3C,eAAe,uCAAsD;AACnE,QAAM,yBAAyB;AAC/B,QAAM,UAAU,4BAA4B;AAC5C,MAAI,wBAAwB,QAAS;AACrC,MAAI,eAAgB,QAAO;AAC3B,QAAM,WAAW,YAAY;AAC3B,UAAM,UAAU,MAAM,4BAA4B,gBAAgB,MAAM,mBAAmB,MAAM;AACjG,iDAA6C,QAAQ,IAAI,CAAC,WAAW;AACnE,aAAO;AAAA,QACL,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO,SAAS;AAAA,QAC1B,UAAU,OAAO,OAAO,WAAW,aAAa,WAAW,OAAO,UAAU,WAAW;AAAA,QACvF,aAAa,gBAAgB,SAAS,OAAO,aAAa;AAAA,QAC1D,WAAW,wBAAwB,SAAS,OAAO,qBAAqB;AAAA,QACxE,SAAS,sBAAsB,SAAS,OAAO,mBAAmB;AAAA,MACpE;AAAA,IACF,CAAC,CAAC;AACF,0BAAsB;AAAA,EACxB,GAAG;AACH,mBAAiB;AACjB,MAAI;AACF,UAAM;AAAA,EACR,UAAE;AACA,QAAI,mBAAmB,QAAS,kBAAiB;AAAA,EACnD;AACF;AAEA,SAAS,qBACP,MACA,gBACA,yBACsC;AACtC,QAAM,yBAAyB,MAAM,KAAK,oBAAI,IAAI,CAAC,gBAAgB,GAAG,uBAAuB,CAAC,CAAC;AAC/F,QAAM,OAAO,IAAI,IAAI,uBAAuB,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,CAAC;AAC3E,QAAM,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,UAAU;AAC7C,UAAM,aAAa,KAAK,IAAI,KAAK,cAAc,KAAK,OAAO,qBACxD,KAAK,IAAI,MAAM,cAAc,KAAK,OAAO;AAC5C,WAAO,aAAa,KAAK,gBAAgB,cAAc,MAAM,eAAe;AAAA,EAC9E,CAAC;AACD,QAAM,WAAW,oBAAI,IAAqC;AAC1D,aAAW,OAAO,QAAQ;AACxB,UAAM,MAAM,IAAI,gBAAgB,KAAK,KAAK,IAAI,MAAM,KAAK,EAAE,YAAY;AACvE,QAAI,OAAO,CAAC,SAAS,IAAI,GAAG,EAAG,UAAS,IAAI,KAAK,GAAG;AAAA,EACtD;AACA,SAAO;AACT;AAEA,SAAS,gCAAgC,KAA8B,KAAsB;AAC3F,QAAM,UAAU,mBAAmB,KAAK,CAAC,UAAU,MAAM,QAAQ,GAAG;AACpE,SAAO,CAAC,CAAC,WAAW,CAAC,IAAI,wBAAwB,IAAI,UAAU,QAAQ,SACrE,IAAI,SAAS,QAAQ,QAAQ,IAAI,UAAU,QAAQ;AACvD;AAEA,eAAsB,gCACpB,OACmC;AACnC,QAAM,qCAAqC;AAC3C,QAAM,uBAAuB,MAAM,KAAK,oBAAI,IAAI,CAAC,MAAM,gBAAgB,GAAG,MAAM,uBAAuB,CAAC,CAAC;AACzG,QAAM,OAAO,MAAM;AAAA,IACjB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,MACE,UAAU,MAAM;AAAA,MAChB,gBAAgB,EAAE,KAAK,qBAAqB;AAAA,MAC5C,MAAM;AAAA,IACR;AAAA,IACA,EAAE,SAAS,EAAE,OAAO,MAAM,EAAE;AAAA,IAC5B,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,eAAe;AAAA,EACnE;AACA,QAAM,iBAAiB,qBAAqB,MAAM,MAAM,gBAAgB,oBAAoB;AAC5F,aAAW,CAAC,KAAK,GAAG,KAAK,gBAAgB;AACvC,QAAI,gCAAgC,KAAK,GAAG,EAAG,gBAAe,OAAO,GAAG;AAAA,EAC1E;AACA,QAAM,gBAAgB,kCAAkC;AACxD,QAAM,qBAAqB,MAAM,iDAC7B,IAAI,IAAI,MAAM,8CAA8C,IAC5D;AACJ,QAAM,cAAc,IAAI,IAAI,cAAc,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;AAC/E,QAAM,iBAAiB,IAAI,IAAI,+BAA+B,EAAE,IAAI,CAAC,UAAU,MAAM,GAAG,CAAC;AACzF,QAAM,OAAO,oBAAI,IAAI,CAAC,GAAG,YAAY,KAAK,GAAG,GAAG,CAAC,GAAG,eAAe,KAAK,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,eAAe,IAAI,GAAG,CAAC,CAAC,CAAC;AACrH,QAAM,mBAAmB,mBAAmB,CAAC,EAAG;AAChD,QAAM,QAAQ,CAAC,GAAG,IAAI,EAAE,IAAI,CAAC,QAAiC;AAC5D,UAAM,OAAO,YAAY,IAAI,GAAG;AAChC,UAAM,MAAM,eAAe,IAAI,GAAG;AAClC,QAAI,CAAC,KAAK;AACR,UAAI,CAAC,mBAAoB,QAAO;AAChC,aAAO,WAAW;AAAA,QAChB,GAAG;AAAA,QACH,0BAA0B,KAAM,SAAS,kBAAkB,OAAO,CAAC,OAAO,CAAC,mBAAmB,IAAI,EAAE,CAAC;AAAA,MACvG,CAAC;AAAA,IACH;AACA,QAAI,MAAM,sBAAsB,OAAO;AACrC,aAAO,WAAW;AAAA,QAChB,GAAG;AAAA,QACH,4BAA4B;AAAA,MAC9B,CAAC;AAAA,IACH;AACA,UAAM,iBAAiB,IAAI,uBACvB,gCAAgC,UAAU,IAAI,oBAAoB,IAClE;AACJ,UAAM,WAAW,gBAAgB,UAC7B,eAAe,OACf,MAAM,YAAY,EAAE,GAAG,kBAAkB,YAAY,KAAK;AAC9D,UAAM,kBAAkB,IAAI,mBAAmB,MAAM;AACrD,UAAM,eAAe,mBAAmB,KAAK,CAAC,UAAU,MAAM,QAAQ,GAAG,GAAG,SAAS,MAAM;AAC3F,UAAM,iBAAiB,CAAC,CAAC,IAAI,SAAS,IAAI,UAAU;AACpD,WAAO,WAAW;AAAA,MAChB;AAAA,MACA,OAAO,iBAAiB,IAAI,QAAQ,MAAM,SAAS,IAAI,SAAS,IAAI;AAAA,MACpE,GAAI,CAAC,kBAAkB,MAAM,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,MACvE,MAAM,IAAI,QAAQ,MAAM,QAAQ;AAAA,MAChC,OAAO,IAAI,SAAS,MAAM,SAAS;AAAA,MACnC;AAAA,MACA,YAAY,SAAS;AAAA,MACrB,GAAI,MAAM,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,MACpD,QAAQ,MAAM,UAAU;AAAA,MACxB,YAAY,MAAM,cAAc,CAAC;AAAA,MACjC,YAAY;AAAA,MACZ,mBAAmB;AAAA,MACnB,aAAa,CAAC;AAAA,MACd;AAAA,MACA,WAAW,IAAI,qBAAqB,OAAO,IAAI,UAAU,YAAY,IAAI;AAAA,MACzE,0BAA0B,qBACtB,SAAS,kBAAkB,OAAO,CAAC,OAAO,CAAC,mBAAmB,IAAI,EAAE,CAAC,IACrE,CAAC;AAAA,IACP,CAAC;AAAA,EACH,CAAC;AACD,QAAM,KAAK,CAAC,MAAM,UAAU,KAAK,SAAS,QAAQ,MAAM,SAAS,SAAS,KAAK,IAAI,cAAc,MAAM,GAAG,CAAC;AAC3G,SAAO,WAAW,EAAE,OAAO,aAAa,UAAU,CAAC;AACrD;AAEO,SAAS,wBACd,SACA,KACA,UAA2C,CAAC,GACP;AACrC,QAAM,gBAAgB,IAAI,KAAK,EAAE,YAAY;AAC7C,QAAM,OAAO,QAAQ,MAAM,KAAK,CAAC,UAAU,MAAM,QAAQ,aAAa;AACtE,MAAI,QAAQ,CAAC,QAAQ,kBAAmB,QAAO;AAC/C,QAAM,qBAAqB,yBAAyB,KAAK,EAAE,mBAAmB,KAAK,CAAC;AACpF,MAAI,oBAAoB;AACtB,UAAM,aAAa,oBAAoB,kBAAkB;AACzD,WAAO,WAAW;AAAA,MAChB,GAAG;AAAA,MACH,YAAY;AAAA,MACZ,UAAU,EAAE,GAAG,WAAW,UAAU,YAAY,MAAM;AAAA,MACtD,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AACA,QAAM,WAAW,QAAQ,MAAM,KAAK,CAAC,UAAU,MAAM,QAAQ,QAAQ,WAAW;AAChF,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,WAAW;AAAA,IAChB,GAAG;AAAA,IACH;AAAA,IACA,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU,EAAE,GAAG,SAAS,UAAU,YAAY,MAAM;AAAA,IACpD,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,WAAW;AAAA,EACb,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,67 @@
1
+ const CORE_FIELD_RULES = [
2
+ ["endTime", ["durationMinutes"]],
3
+ ["allDay", ["allDay"]],
4
+ ["recurrence", ["recurrenceRule", "recurrenceEnd"]],
5
+ ["priority", ["priority"]]
6
+ ];
7
+ function hasValue(value) {
8
+ if (value === null || value === void 0 || value === "" || value === false) return false;
9
+ if (Array.isArray(value)) return value.length > 0;
10
+ return true;
11
+ }
12
+ function hasResourceLinks(value) {
13
+ return Array.isArray(value) && value.some((link) => link !== null && typeof link === "object" && "type" in link && link.type === "resource");
14
+ }
15
+ function findInapplicableCoreFields(behavior, values) {
16
+ const fields = [];
17
+ for (const [rule, names] of CORE_FIELD_RULES) {
18
+ if (behavior.fields[rule]) continue;
19
+ for (const name of names) if (hasValue(values[name])) fields.push(name);
20
+ }
21
+ if (behavior.fields.location === "none" && hasValue(values.location)) fields.push("location");
22
+ if (behavior.fields.people === "none" && hasValue(values.participants)) fields.push("participants");
23
+ if (!behavior.fields.resources && hasResourceLinks(values.linkedEntities)) fields.push("linkedEntities");
24
+ return fields;
25
+ }
26
+ function clearInapplicableCoreFields(behavior, values) {
27
+ const cleared = {};
28
+ for (const field of findInapplicableCoreFields(behavior, values)) {
29
+ if (field === "linkedEntities") {
30
+ cleared.linkedEntities = values.linkedEntities.filter((link) => link.type !== "resource");
31
+ } else {
32
+ cleared[field] = null;
33
+ }
34
+ }
35
+ return cleared;
36
+ }
37
+ function omitHiddenCoreClears(behavior, patch) {
38
+ const result = { ...patch };
39
+ for (const [rule, names] of CORE_FIELD_RULES) {
40
+ if (behavior.fields[rule]) continue;
41
+ for (const name of names) if (result[name] === null) delete result[name];
42
+ }
43
+ if (behavior.fields.location === "none" && result.location === null) delete result.location;
44
+ if (behavior.fields.people === "none" && result.participants === null) delete result.participants;
45
+ return result;
46
+ }
47
+ function preserveHiddenCoreValuesOnSameTypeEdit(behavior, current, patch, options = { ignoreHiddenClears: true }) {
48
+ const result = options.ignoreHiddenClears !== false ? omitHiddenCoreClears(behavior, patch) : { ...patch };
49
+ if (!behavior.fields.resources && result.linkedEntities !== void 0) {
50
+ const existingLinks = Array.isArray(current.linkedEntities) ? current.linkedEntities : [];
51
+ const retainedResources = existingLinks.filter((link) => link !== null && typeof link === "object" && "type" in link && link.type === "resource");
52
+ const nextLinks = Array.isArray(result.linkedEntities) ? result.linkedEntities : [];
53
+ const sentResourceIds = new Set(nextLinks.flatMap((link) => link !== null && typeof link === "object" && "type" in link && link.type === "resource" && "id" in link ? [link.id] : []));
54
+ result.linkedEntities = [
55
+ ...nextLinks,
56
+ ...retainedResources.filter((link) => !("id" in link) || !sentResourceIds.has(link.id))
57
+ ];
58
+ }
59
+ return result;
60
+ }
61
+ export {
62
+ clearInapplicableCoreFields,
63
+ findInapplicableCoreFields,
64
+ omitHiddenCoreClears,
65
+ preserveHiddenCoreValuesOnSameTypeEdit
66
+ };
67
+ //# sourceMappingURL=interactionApplicability.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customers/lib/calendar/interactionApplicability.ts"],
4
+ "sourcesContent": ["import type { CalendarEventTypeBehavior } from '../../calendar-event-types'\n\nexport type InteractionCoreValues = Readonly<Record<string, unknown>>\n\nconst CORE_FIELD_RULES: readonly [keyof CalendarEventTypeBehavior['fields'], readonly string[]][] = [\n ['endTime', ['durationMinutes']],\n ['allDay', ['allDay']],\n ['recurrence', ['recurrenceRule', 'recurrenceEnd']],\n ['priority', ['priority']],\n]\n\nfunction hasValue(value: unknown): boolean {\n if (value === null || value === undefined || value === '' || value === false) return false\n if (Array.isArray(value)) return value.length > 0\n return true\n}\n\nfunction hasResourceLinks(value: unknown): boolean {\n return Array.isArray(value) && value.some((link) =>\n link !== null && typeof link === 'object' && 'type' in link && link.type === 'resource')\n}\n\nexport function findInapplicableCoreFields(\n behavior: CalendarEventTypeBehavior,\n values: InteractionCoreValues,\n): string[] {\n const fields: string[] = []\n for (const [rule, names] of CORE_FIELD_RULES) {\n if (behavior.fields[rule]) continue\n for (const name of names) if (hasValue(values[name])) fields.push(name)\n }\n if (behavior.fields.location === 'none' && hasValue(values.location)) fields.push('location')\n if (behavior.fields.people === 'none' && hasValue(values.participants)) fields.push('participants')\n if (!behavior.fields.resources && hasResourceLinks(values.linkedEntities)) fields.push('linkedEntities')\n return fields\n}\n\nexport function clearInapplicableCoreFields(\n behavior: CalendarEventTypeBehavior,\n values: InteractionCoreValues,\n): Record<string, unknown> {\n const cleared: Record<string, unknown> = {}\n for (const field of findInapplicableCoreFields(behavior, values)) {\n if (field === 'linkedEntities') {\n cleared.linkedEntities = (values.linkedEntities as readonly Record<string, unknown>[])\n .filter((link) => link.type !== 'resource')\n } else {\n cleared[field] = null\n }\n }\n return cleared\n}\n\n/**\n * Under the selectable-type opt-in an explicit `null` for a field the type hides\n * means \"not shown\", not \"clear it\". Dropping it lets a type change judge the\n * stored value, so the destructive clear is confirmed instead of silent.\n */\nexport function omitHiddenCoreClears(\n behavior: CalendarEventTypeBehavior,\n patch: InteractionCoreValues,\n): Record<string, unknown> {\n const result = { ...patch }\n for (const [rule, names] of CORE_FIELD_RULES) {\n if (behavior.fields[rule]) continue\n for (const name of names) if (result[name] === null) delete result[name]\n }\n if (behavior.fields.location === 'none' && result.location === null) delete result.location\n if (behavior.fields.people === 'none' && result.participants === null) delete result.participants\n return result\n}\n\n/**\n * `ignoreHiddenClears` belongs to the selectable-type opt-in: only a caller that\n * declares `enforceSelectableType` has its explicit `null` clears of fields the\n * type hides dropped. Retaining stored resource links is unconditional \u2014 it\n * protects bookings a partial payload never meant to release.\n */\nexport function preserveHiddenCoreValuesOnSameTypeEdit(\n behavior: CalendarEventTypeBehavior,\n current: InteractionCoreValues,\n patch: InteractionCoreValues,\n options: { ignoreHiddenClears?: boolean } = { ignoreHiddenClears: true },\n): Record<string, unknown> {\n const result = options.ignoreHiddenClears !== false ? omitHiddenCoreClears(behavior, patch) : { ...patch }\n if (!behavior.fields.resources && result.linkedEntities !== undefined) {\n const existingLinks = Array.isArray(current.linkedEntities) ? current.linkedEntities : []\n const retainedResources = existingLinks.filter((link) =>\n link !== null && typeof link === 'object' && 'type' in link && link.type === 'resource')\n const nextLinks = Array.isArray(result.linkedEntities) ? result.linkedEntities : []\n const sentResourceIds = new Set(nextLinks.flatMap((link) =>\n link !== null && typeof link === 'object' && 'type' in link && link.type === 'resource' && 'id' in link ? [link.id] : []))\n result.linkedEntities = [\n ...nextLinks,\n ...retainedResources.filter((link) => !('id' in link) || !sentResourceIds.has(link.id)),\n ]\n }\n return result\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -3,6 +3,7 @@ import { endOfDay } from "date-fns/endOfDay";
3
3
  import { startOfDay } from "date-fns/startOfDay";
4
4
  import { categoryOf } from "./categories.js";
5
5
  import { participantActorKey } from "./participantIdentity.js";
6
+ import { calendarDayEndInstant, calendarDayStartInstant, calendarInstantToWallTime, isCalendarTimezone } from "./timezone.js";
6
7
  const DEFAULT_DURATION_MINUTES = 30;
7
8
  function narrowStatus(status) {
8
9
  if (status === "done") return "done";
@@ -52,8 +53,10 @@ function mapInteractionToCalendarItem(payload, typeColorByType) {
52
53
  if (Number.isNaN(parsedStart.getTime())) return null;
53
54
  const allDay = payload.allDay === true;
54
55
  const durationMinutes = payload.durationMinutes ?? DEFAULT_DURATION_MINUTES;
55
- const start = allDay ? startOfDay(parsedStart) : parsedStart;
56
- const end = allDay ? endOfDay(parsedStart) : addMinutes(parsedStart, durationMinutes);
56
+ const eventDate = allDay && isCalendarTimezone(payload.timezone) ? calendarInstantToWallTime(parsedStart, payload.timezone).date : null;
57
+ const start = eventDate ? calendarDayStartInstant(eventDate, payload.timezone) : allDay ? startOfDay(parsedStart) : parsedStart;
58
+ const end = eventDate ? calendarDayEndInstant(eventDate, payload.timezone) : allDay ? endOfDay(parsedStart) : addMinutes(parsedStart, durationMinutes);
59
+ if (!start || !end) return null;
57
60
  const location = payload.location ?? null;
58
61
  const platform = detectPlatform(location);
59
62
  return {