@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
@@ -42,6 +42,11 @@ import {
42
42
  } from "../lib/interactionStatus.js";
43
43
  import { canChangeEmailVisibility } from "../lib/visibilityFilter.js";
44
44
  import { createLogger } from "@open-mercato/shared/lib/logger";
45
+ import { loadCustomFieldDefinitionIndex } from "@open-mercato/shared/lib/crud/custom-fields";
46
+ import { resolveCatalogEventType, resolveScopedCalendarEventTypes } from "../lib/calendar/eventTypeResolver.js";
47
+ import { clearInapplicableCoreFields, findInapplicableCoreFields, omitHiddenCoreClears, preserveHiddenCoreValuesOnSameTypeEdit } from "../lib/calendar/interactionApplicability.js";
48
+ import { calendarEventTypes } from "../calendar-event-types.js";
49
+ import { canonicalCalendarTimezone } from "../lib/calendar/timezone.js";
45
50
  const logger = createLogger("customers");
46
51
  const INTERACTION_ENTITY_ID = "customers:customer_interaction";
47
52
  const interactionCrudIndexer = {
@@ -100,6 +105,7 @@ async function loadInteractionSnapshot(em, id) {
100
105
  appearanceColor: interaction.appearanceColor ?? null,
101
106
  source: interaction.source ?? null,
102
107
  durationMinutes: interaction.durationMinutes ?? null,
108
+ timezone: interaction.timezone ?? null,
103
109
  location: interaction.location ?? null,
104
110
  allDay: interaction.allDay ?? null,
105
111
  recurrenceRule: interaction.recurrenceRule ?? null,
@@ -125,6 +131,61 @@ async function setInteractionCustomFields(dataEngine, interactionId, organizatio
125
131
  notify: false
126
132
  });
127
133
  }
134
+ function requestsSelectableTypeEnforcement(input) {
135
+ return input !== null && typeof input === "object" && "enforceSelectableType" in input && input.enforceSelectableType === true;
136
+ }
137
+ async function resolveInteractionTypeBehavior(ctx, em, tenantId, organizationId, key, unchangedType, enforceSelectableType) {
138
+ const hierarchy = ctx.container.resolve("organizationHierarchyService");
139
+ const ancestors = await hierarchy.resolveAncestorIds({ tenantId, organizationId });
140
+ if (ancestors === null) throw notFound("Organization not found");
141
+ const organizationIds = [organizationId, ...ancestors.slice().reverse()];
142
+ let catalog;
143
+ try {
144
+ catalog = await resolveScopedCalendarEventTypes({
145
+ em,
146
+ tenantId,
147
+ organizationId,
148
+ readableOrganizationIds: organizationIds
149
+ });
150
+ } catch {
151
+ const { translate: translate2 } = await resolveTranslations();
152
+ throw new CrudHttpError(503, {
153
+ error: translate2("customers.calendar.activityTypes.errors.loadFailed", "Failed to load activity types"),
154
+ code: "activity_type_catalog_unavailable",
155
+ retryable: true
156
+ });
157
+ }
158
+ const selected = resolveCatalogEventType(catalog, key, { includeHistorical: unchangedType || !enforceSelectableType });
159
+ if (selected && (unchangedType || !enforceSelectableType || selected.selectable)) {
160
+ return { behavior: selected.behavior, organizationIds };
161
+ }
162
+ if (unchangedType) return { behavior: calendarEventTypes[0].behavior, organizationIds };
163
+ const { translate } = await resolveTranslations();
164
+ throw new CrudHttpError(400, {
165
+ error: translate("customers.calendar.activityTypes.errors.unavailable", "Activity type is unavailable"),
166
+ code: "activity_type_unavailable"
167
+ });
168
+ }
169
+ async function loadApplicableCustomFieldKeys(em, tenantId, organizationIds, behavior) {
170
+ const options = { em, entityIds: [INTERACTION_ENTITY_ID], tenantId, organizationIds };
171
+ const all = await loadCustomFieldDefinitionIndex(options);
172
+ const fieldsets = behavior.customFieldsetIds.map((id) => id === "__general__" ? "" : id);
173
+ const applicable = fieldsets.length ? await loadCustomFieldDefinitionIndex({ ...options, fieldset: fieldsets }) : all;
174
+ return {
175
+ defined: new Set([...all.keys()].map((key) => key.toLowerCase())),
176
+ allowed: new Set([...applicable.keys()].map((key) => key.toLowerCase()))
177
+ };
178
+ }
179
+ function inapplicableCustomFieldKeys(values, keys) {
180
+ return Object.entries(values).filter(([key, value]) => keys.defined.has(key.toLowerCase()) && !keys.allowed.has(key.toLowerCase()) && value !== null && value !== void 0 && value !== "" && (!Array.isArray(value) || value.length > 0)).map(([key]) => key);
181
+ }
182
+ function inapplicableFieldError(fields, message) {
183
+ return new CrudHttpError(400, {
184
+ error: message,
185
+ code: "activity_type_field_not_applicable",
186
+ fields
187
+ });
188
+ }
128
189
  async function emitLifecycleEvent(ctx, eventId, payload) {
129
190
  let bus = null;
130
191
  try {
@@ -159,13 +220,18 @@ function createTransactionalDataEngine(ctx, em) {
159
220
  }
160
221
  return new DefaultDataEngine(em, ctx.container);
161
222
  }
162
- async function runInTransaction(em, operation) {
223
+ async function runInTransaction(em, operation, beforeWrite, input) {
224
+ if (typeof em.isInTransaction === "function" && em.isInTransaction()) {
225
+ await beforeWrite?.(em, input);
226
+ return operation(em);
227
+ }
163
228
  const supportsBegin = typeof em.begin === "function" && typeof em.commit === "function" && typeof em.rollback === "function";
164
229
  if (!supportsBegin) {
165
230
  return operation(em);
166
231
  }
167
232
  await em.begin();
168
233
  try {
234
+ await beforeWrite?.(em, input);
169
235
  const result = await operation(em);
170
236
  await em.commit();
171
237
  return result;
@@ -212,6 +278,7 @@ function buildInteractionGraph(em, values) {
212
278
  appearanceIcon: values.appearanceIcon,
213
279
  appearanceColor: values.appearanceColor,
214
280
  durationMinutes: values.durationMinutes,
281
+ timezone: values.timezone ?? null,
215
282
  location: values.location,
216
283
  allDay: values.allDay,
217
284
  recurrenceRule: values.recurrenceRule,
@@ -229,7 +296,7 @@ const createInteractionCommand = {
229
296
  id: "customers.interactions.create",
230
297
  async execute(rawInput, ctx) {
231
298
  const { parsed, custom } = parseWithCustomFields(interactionCreateSchema, rawInput);
232
- const em = ctx.container.resolve("em").fork();
299
+ const em = ctx.transactionalEm ?? ctx.container.resolve("em").fork();
233
300
  const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId ?? null, ctx.auth);
234
301
  const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
235
302
  const entity = await requireTimelineParentEntity(trx, parsed.entityId, { tenantId: parsed.tenantId, organizationId: parsed.organizationId });
@@ -238,6 +305,26 @@ const createInteractionCommand = {
238
305
  if (parsed.dealId) {
239
306
  await requireDealInScope(trx, parsed.dealId, entity.tenantId, entity.organizationId);
240
307
  }
308
+ const enforceSelectableType = requestsSelectableTypeEnforcement(rawInput);
309
+ const { behavior, organizationIds } = await resolveInteractionTypeBehavior(
310
+ ctx,
311
+ trx,
312
+ entity.tenantId,
313
+ entity.organizationId,
314
+ parsed.interactionType,
315
+ false,
316
+ enforceSelectableType
317
+ );
318
+ const inapplicableCore = findInapplicableCoreFields(behavior, parsed);
319
+ const customKeys = Object.keys(custom).length ? await loadApplicableCustomFieldKeys(trx, entity.tenantId, organizationIds, behavior) : null;
320
+ const inapplicableCustom = customKeys ? inapplicableCustomFieldKeys(custom, customKeys) : [];
321
+ if (enforceSelectableType && (inapplicableCore.length || inapplicableCustom.length)) {
322
+ const { translate } = await resolveTranslations();
323
+ throw inapplicableFieldError(
324
+ [...inapplicableCore, ...inapplicableCustom.map((key) => `cf_${key}`)],
325
+ translate("customers.calendar.activityTypes.errors.fieldNotApplicable", "Field is not applicable to this activity type")
326
+ );
327
+ }
241
328
  const interaction2 = buildInteractionGraph(trx, {
242
329
  ...parsed.id ? { id: parsed.id } : {},
243
330
  organizationId: entity.organizationId,
@@ -257,6 +344,7 @@ const createInteractionCommand = {
257
344
  appearanceIcon: parsed.appearanceIcon ?? null,
258
345
  appearanceColor: parsed.appearanceColor ?? null,
259
346
  durationMinutes: parsed.durationMinutes ?? null,
347
+ timezone: canonicalCalendarTimezone(parsed.timezone) ?? null,
260
348
  location: parsed.location ?? null,
261
349
  allDay: parsed.allDay ?? null,
262
350
  recurrenceRule: parsed.recurrenceRule ?? null,
@@ -282,7 +370,7 @@ const createInteractionCommand = {
282
370
  entityId: entity.id,
283
371
  nextInteractionId: projection.nextInteractionId
284
372
  };
285
- });
373
+ }, ctx.beforeTransactionalWrite, rawInput);
286
374
  const de = ctx.container.resolve("dataEngine");
287
375
  await emitCrudSideEffects({
288
376
  dataEngine: de,
@@ -305,7 +393,7 @@ const createInteractionCommand = {
305
393
  return { interactionId: interaction.id, entityId };
306
394
  },
307
395
  captureAfter: async (_input, result, ctx) => {
308
- const em = ctx.container.resolve("em").fork();
396
+ const em = ctx.transactionalEm ?? ctx.container.resolve("em").fork();
309
397
  return await loadInteractionSnapshot(em, result.interactionId);
310
398
  },
311
399
  buildLog: async ({ result, snapshots }) => {
@@ -383,6 +471,7 @@ const createInteractionCommand = {
383
471
  appearanceIcon: after.interaction.appearanceIcon,
384
472
  appearanceColor: after.interaction.appearanceColor,
385
473
  durationMinutes: after.interaction.durationMinutes,
474
+ timezone: after.interaction.timezone ?? null,
386
475
  location: after.interaction.location,
387
476
  allDay: after.interaction.allDay,
388
477
  recurrenceRule: after.interaction.recurrenceRule,
@@ -411,6 +500,7 @@ const createInteractionCommand = {
411
500
  interaction2.appearanceIcon = after.interaction.appearanceIcon;
412
501
  interaction2.appearanceColor = after.interaction.appearanceColor;
413
502
  interaction2.durationMinutes = after.interaction.durationMinutes;
503
+ interaction2.timezone = after.interaction.timezone ?? null;
414
504
  interaction2.location = after.interaction.location;
415
505
  interaction2.allDay = after.interaction.allDay;
416
506
  interaction2.recurrenceRule = after.interaction.recurrenceRule;
@@ -462,17 +552,19 @@ const createInteractionCommand = {
462
552
  return { interactionId: interaction.id, entityId: after.interaction.entityId };
463
553
  }
464
554
  };
555
+ async function prepareUpdateInteractionSnapshot(rawInput, ctx) {
556
+ const { parsed } = parseWithCustomFields(interactionUpdateSchema, rawInput);
557
+ const em = ctx.transactionalEm ?? ctx.container.resolve("em").fork();
558
+ const snapshot = await loadInteractionSnapshot(em, parsed.id);
559
+ return snapshot ? { before: snapshot } : {};
560
+ }
465
561
  const updateInteractionCommand = {
466
562
  id: "customers.interactions.update",
467
- async prepare(rawInput, ctx) {
468
- const { parsed } = parseWithCustomFields(interactionUpdateSchema, rawInput);
469
- const em = ctx.container.resolve("em").fork();
470
- const snapshot = await loadInteractionSnapshot(em, parsed.id);
471
- return snapshot ? { before: snapshot } : {};
472
- },
563
+ prepare: prepareUpdateInteractionSnapshot,
564
+ prepareSnapshotInsideTransaction: true,
473
565
  async execute(rawInput, ctx) {
474
566
  const { parsed, custom } = parseWithCustomFields(interactionUpdateSchema, rawInput);
475
- const em = ctx.container.resolve("em").fork();
567
+ const em = ctx.transactionalEm ?? ctx.container.resolve("em").fork();
476
568
  const { interaction, projections } = await runInTransaction(em, async (trx) => {
477
569
  const interaction2 = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null });
478
570
  if (!interaction2) {
@@ -487,6 +579,64 @@ const updateInteractionCommand = {
487
579
  current: interaction2.updatedAt,
488
580
  request: ctx.request ?? null
489
581
  });
582
+ const nextType = parsed.interactionType ?? interaction2.interactionType;
583
+ const changingType = nextType !== interaction2.interactionType;
584
+ const enforceSelectableType = requestsSelectableTypeEnforcement(rawInput);
585
+ const { behavior, organizationIds } = await resolveInteractionTypeBehavior(
586
+ ctx,
587
+ trx,
588
+ interaction2.tenantId,
589
+ interaction2.organizationId,
590
+ nextType,
591
+ !changingType,
592
+ enforceSelectableType
593
+ );
594
+ const currentCoreValues = {
595
+ durationMinutes: interaction2.durationMinutes,
596
+ timezone: interaction2.timezone ?? null,
597
+ allDay: interaction2.allDay,
598
+ recurrenceRule: interaction2.recurrenceRule,
599
+ recurrenceEnd: interaction2.recurrenceEnd,
600
+ location: interaction2.location,
601
+ participants: interaction2.participants,
602
+ priority: interaction2.priority,
603
+ linkedEntities: interaction2.linkedEntities
604
+ };
605
+ const typeChangeFields = changingType && enforceSelectableType ? omitHiddenCoreClears(behavior, parsed) : parsed;
606
+ const coreValues = { ...currentCoreValues, ...typeChangeFields };
607
+ const inapplicableCore = findInapplicableCoreFields(behavior, changingType ? coreValues : parsed);
608
+ const confirmsDiscard = parsed.confirmDiscardInapplicableValues === true;
609
+ const currentCustom = changingType && (enforceSelectableType || confirmsDiscard) ? await loadCustomFieldSnapshot(trx, {
610
+ entityId: INTERACTION_ENTITY_ID,
611
+ recordId: interaction2.id,
612
+ tenantId: interaction2.tenantId,
613
+ organizationId: interaction2.organizationId
614
+ }) : {};
615
+ const customValues = changingType ? { ...currentCustom, ...custom } : custom;
616
+ const customKeys = Object.keys(customValues).length ? await loadApplicableCustomFieldKeys(trx, interaction2.tenantId, organizationIds, behavior) : null;
617
+ const inapplicableCustom = customKeys ? inapplicableCustomFieldKeys(customValues, customKeys) : [];
618
+ if (!changingType && enforceSelectableType && (inapplicableCore.length || inapplicableCustom.length)) {
619
+ const { translate } = await resolveTranslations();
620
+ throw inapplicableFieldError(
621
+ [...inapplicableCore, ...inapplicableCustom.map((key) => `cf_${key}`)],
622
+ translate("customers.calendar.activityTypes.errors.fieldNotApplicable", "Field is not applicable to this activity type")
623
+ );
624
+ }
625
+ if (changingType && enforceSelectableType && (inapplicableCore.length || inapplicableCustom.length) && !confirmsDiscard) {
626
+ const { translate } = await resolveTranslations();
627
+ throw new CrudHttpError(409, {
628
+ error: translate("customers.calendar.activityTypes.errors.changeConfirmationRequired", "Changing type will clear existing values"),
629
+ code: "calendar_type_change_confirmation_required",
630
+ fields: [...inapplicableCore, ...inapplicableCustom.map((key) => `cf_${key}`)]
631
+ });
632
+ }
633
+ const discardsInapplicableValues = changingType && confirmsDiscard;
634
+ const clearedCore = discardsInapplicableValues ? clearInapplicableCoreFields(behavior, coreValues) : {};
635
+ const clearedCustom = discardsInapplicableValues ? Object.fromEntries(inapplicableCustom.map((key) => [key, Array.isArray(customValues[key]) ? [] : null])) : {};
636
+ const sameTypeFields = changingType ? typeChangeFields : preserveHiddenCoreValuesOnSameTypeEdit(behavior, currentCoreValues, parsed, {
637
+ ignoreHiddenClears: enforceSelectableType
638
+ });
639
+ const sameTypeCustom = changingType || !customKeys || !enforceSelectableType ? custom : Object.fromEntries(Object.entries(custom).filter(([key]) => !customKeys.defined.has(key.toLowerCase()) || customKeys.allowed.has(key.toLowerCase())));
490
640
  if (parsed.visibility !== void 0 && interaction2.interactionType === "email" && (parsed.visibility ?? null) !== (interaction2.visibility ?? null)) {
491
641
  const actorUserId = ctx.auth?.sub ?? null;
492
642
  if (!canChangeEmailVisibility({
@@ -519,23 +669,34 @@ const updateInteractionCommand = {
519
669
  if (parsed.body !== void 0) interaction2.body = parsed.body ?? null;
520
670
  if (parsed.status !== void 0) interaction2.status = parsed.status;
521
671
  if (parsed.scheduledAt !== void 0) interaction2.scheduledAt = parsed.scheduledAt ?? null;
672
+ if (parsed.timezone !== void 0) interaction2.timezone = canonicalCalendarTimezone(parsed.timezone) ?? null;
522
673
  if (parsed.occurredAt !== void 0) interaction2.occurredAt = parsed.occurredAt ?? null;
523
- if (parsed.priority !== void 0) interaction2.priority = parsed.priority ?? null;
674
+ if (sameTypeFields.priority !== void 0) interaction2.priority = sameTypeFields.priority;
524
675
  if (parsed.authorUserId !== void 0) interaction2.authorUserId = parsed.authorUserId ?? null;
525
676
  if (parsed.ownerUserId !== void 0) interaction2.ownerUserId = parsed.ownerUserId ?? null;
526
677
  if (parsed.appearanceIcon !== void 0) interaction2.appearanceIcon = parsed.appearanceIcon ?? null;
527
678
  if (parsed.appearanceColor !== void 0) interaction2.appearanceColor = parsed.appearanceColor ?? null;
528
679
  if (parsed.pinned !== void 0) interaction2.pinned = parsed.pinned;
529
- if (parsed.durationMinutes !== void 0) interaction2.durationMinutes = parsed.durationMinutes ?? null;
530
- if (parsed.location !== void 0) interaction2.location = parsed.location ?? null;
531
- if (parsed.allDay !== void 0) interaction2.allDay = parsed.allDay ?? null;
532
- if (parsed.recurrenceRule !== void 0) interaction2.recurrenceRule = parsed.recurrenceRule ?? null;
533
- if (parsed.recurrenceEnd !== void 0) interaction2.recurrenceEnd = parsed.recurrenceEnd ?? null;
534
- if (parsed.participants !== void 0) interaction2.participants = parsed.participants ?? null;
680
+ if (sameTypeFields.durationMinutes !== void 0) interaction2.durationMinutes = sameTypeFields.durationMinutes;
681
+ if (sameTypeFields.location !== void 0) interaction2.location = sameTypeFields.location;
682
+ if (sameTypeFields.allDay !== void 0) interaction2.allDay = sameTypeFields.allDay;
683
+ if (sameTypeFields.recurrenceRule !== void 0) interaction2.recurrenceRule = sameTypeFields.recurrenceRule;
684
+ if (sameTypeFields.recurrenceEnd !== void 0) interaction2.recurrenceEnd = sameTypeFields.recurrenceEnd;
685
+ if (sameTypeFields.participants !== void 0) interaction2.participants = sameTypeFields.participants;
535
686
  if (parsed.reminderMinutes !== void 0) interaction2.reminderMinutes = parsed.reminderMinutes ?? null;
536
687
  if (parsed.visibility !== void 0) interaction2.visibility = parsed.visibility ?? null;
537
- if (parsed.linkedEntities !== void 0) interaction2.linkedEntities = parsed.linkedEntities ?? null;
688
+ if (sameTypeFields.linkedEntities !== void 0) interaction2.linkedEntities = sameTypeFields.linkedEntities;
538
689
  if (parsed.guestPermissions !== void 0) interaction2.guestPermissions = parsed.guestPermissions ?? null;
690
+ for (const [field, value] of Object.entries(clearedCore)) {
691
+ if (field === "linkedEntities") interaction2.linkedEntities = value;
692
+ else if (field === "durationMinutes") interaction2.durationMinutes = null;
693
+ else if (field === "allDay") interaction2.allDay = null;
694
+ else if (field === "recurrenceRule") interaction2.recurrenceRule = null;
695
+ else if (field === "recurrenceEnd") interaction2.recurrenceEnd = null;
696
+ else if (field === "location") interaction2.location = null;
697
+ else if (field === "participants") interaction2.participants = null;
698
+ else if (field === "priority") interaction2.priority = null;
699
+ }
539
700
  await trx.flush();
540
701
  const entityId = typeof interaction2.entity === "string" ? interaction2.entity : interaction2.entity.id;
541
702
  await setInteractionCustomFields(
@@ -543,7 +704,7 @@ const updateInteractionCommand = {
543
704
  interaction2.id,
544
705
  interaction2.organizationId,
545
706
  interaction2.tenantId,
546
- custom
707
+ { ...sameTypeCustom, ...clearedCustom }
547
708
  );
548
709
  const projection = await recomputeNextInteraction(trx, entityId);
549
710
  const projections2 = [
@@ -554,7 +715,7 @@ const updateInteractionCommand = {
554
715
  projections2.push({ entityId: previousEntityId, nextInteractionId: previousProjection.nextInteractionId });
555
716
  }
556
717
  return { interaction: interaction2, projections: projections2 };
557
- });
718
+ }, ctx.beforeTransactionalWrite, rawInput);
558
719
  const de = ctx.container.resolve("dataEngine");
559
720
  await emitCrudSideEffects({
560
721
  dataEngine: de,
@@ -579,7 +740,7 @@ const updateInteractionCommand = {
579
740
  return { interactionId: interaction.id };
580
741
  },
581
742
  captureAfter: async (_input, result, ctx) => {
582
- const em = ctx.container.resolve("em").fork();
743
+ const em = ctx.transactionalEm ?? ctx.container.resolve("em").fork();
583
744
  return await loadInteractionSnapshot(em, result.interactionId);
584
745
  },
585
746
  buildLog: async ({ snapshots }) => {
@@ -633,6 +794,7 @@ const updateInteractionCommand = {
633
794
  appearanceIcon: before.interaction.appearanceIcon,
634
795
  appearanceColor: before.interaction.appearanceColor,
635
796
  durationMinutes: before.interaction.durationMinutes,
797
+ timezone: before.interaction.timezone ?? null,
636
798
  location: before.interaction.location,
637
799
  allDay: before.interaction.allDay,
638
800
  recurrenceRule: before.interaction.recurrenceRule,
@@ -662,6 +824,7 @@ const updateInteractionCommand = {
662
824
  interaction2.appearanceIcon = before.interaction.appearanceIcon;
663
825
  interaction2.appearanceColor = before.interaction.appearanceColor;
664
826
  interaction2.durationMinutes = before.interaction.durationMinutes;
827
+ interaction2.timezone = before.interaction.timezone ?? null;
665
828
  interaction2.location = before.interaction.location;
666
829
  interaction2.allDay = before.interaction.allDay;
667
830
  interaction2.recurrenceRule = before.interaction.recurrenceRule;
@@ -1077,6 +1240,7 @@ const deleteInteractionCommand = {
1077
1240
  appearanceIcon: before.interaction.appearanceIcon,
1078
1241
  appearanceColor: before.interaction.appearanceColor,
1079
1242
  durationMinutes: before.interaction.durationMinutes,
1243
+ timezone: before.interaction.timezone ?? null,
1080
1244
  location: before.interaction.location,
1081
1245
  allDay: before.interaction.allDay,
1082
1246
  recurrenceRule: before.interaction.recurrenceRule,
@@ -1107,6 +1271,7 @@ const deleteInteractionCommand = {
1107
1271
  interaction2.appearanceIcon = before.interaction.appearanceIcon;
1108
1272
  interaction2.appearanceColor = before.interaction.appearanceColor;
1109
1273
  interaction2.durationMinutes = before.interaction.durationMinutes;
1274
+ interaction2.timezone = before.interaction.timezone ?? null;
1110
1275
  interaction2.location = before.interaction.location;
1111
1276
  interaction2.allDay = before.interaction.allDay;
1112
1277
  interaction2.recurrenceRule = before.interaction.recurrenceRule;