@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
- package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
- package/dist/generated/entities/customer_interaction/index.js +2 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +1 -1
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
- package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +52 -17
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
- package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
- package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
- package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
- package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
- package/dist/modules/customers/api/activity-types/route.js +151 -0
- package/dist/modules/customers/api/activity-types/route.js.map +7 -0
- package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -0
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/calendar-event-types.js +548 -0
- package/dist/modules/customers/calendar-event-types.js.map +7 -0
- package/dist/modules/customers/commands/dictionaries.js +70 -10
- package/dist/modules/customers/commands/dictionaries.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +187 -22
- package/dist/modules/customers/commands/interactions.js.map +3 -3
- package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
- package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +3 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
- package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
- package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +1 -0
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
- package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +7 -3
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
- package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonCard.js +5 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
- package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
- package/dist/modules/customers/data/entities.js +6 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +39 -10
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/di.js +20 -1
- package/dist/modules/customers/di.js.map +2 -2
- package/dist/modules/customers/extension-points.js +13 -0
- package/dist/modules/customers/extension-points.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
- package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
- package/dist/modules/customers/lib/calendar/timezone.js +111 -0
- package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
- package/dist/modules/customers/lib/interactionReadModel.js +1 -0
- package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +1 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/messages/subscribers/message-notification.js +13 -1
- package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +101 -38
- package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
- package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/generated/entities/customer_dictionary_entry/index.ts +1 -0
- package/generated/entities/customer_interaction/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +9 -8
- package/src/modules/auth/i18n/de.json +3 -0
- package/src/modules/auth/i18n/en.json +3 -0
- package/src/modules/auth/i18n/es.json +3 -0
- package/src/modules/auth/i18n/ko.json +3 -0
- package/src/modules/auth/i18n/pl.json +3 -0
- package/src/modules/catalog/commands/categories.ts +1 -1
- package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
- package/src/modules/catalog/commands/products.ts +58 -17
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
- package/src/modules/communication_channels/i18n/de.json +4 -0
- package/src/modules/communication_channels/i18n/en.json +4 -0
- package/src/modules/communication_channels/i18n/es.json +4 -0
- package/src/modules/communication_channels/i18n/ko.json +4 -0
- package/src/modules/communication_channels/i18n/pl.json +4 -0
- package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
- package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
- package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
- package/src/modules/customers/api/interactions/route.ts +4 -0
- package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
- package/src/modules/customers/calendar-event-types.ts +674 -0
- package/src/modules/customers/commands/dictionaries.ts +84 -8
- package/src/modules/customers/commands/interactions.ts +244 -21
- package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
- package/src/modules/customers/components/DictionarySettings.tsx +5 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
- package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
- package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
- package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
- package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
- package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
- package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
- package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
- package/src/modules/customers/components/detail/DealForm.tsx +7 -3
- package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
- package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
- package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
- package/src/modules/customers/components/detail/types.ts +1 -0
- package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
- package/src/modules/customers/data/entities.ts +8 -1
- package/src/modules/customers/data/validators.ts +42 -7
- package/src/modules/customers/di.ts +21 -0
- package/src/modules/customers/extension-points.ts +13 -0
- package/src/modules/customers/i18n/de.json +104 -8
- package/src/modules/customers/i18n/en.json +104 -8
- package/src/modules/customers/i18n/es.json +104 -8
- package/src/modules/customers/i18n/ko.json +104 -8
- package/src/modules/customers/i18n/pl.json +104 -8
- package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
- package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
- package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
- package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
- package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
- package/src/modules/customers/lib/calendar/timezone.ts +117 -0
- package/src/modules/customers/lib/interactionReadModel.ts +1 -0
- package/src/modules/customers/lib/todoCompatibility.ts +2 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
- package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
- package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
- package/src/modules/messages/subscribers/message-notification.ts +13 -3
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
- package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
- package/src/modules/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
- package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
- package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
|
@@ -1,4 +1,45 @@
|
|
|
1
1
|
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2
|
+
const timezoneFormatters = /* @__PURE__ */ new Map();
|
|
3
|
+
function zonedParts(instant, timezone) {
|
|
4
|
+
let formatter = timezoneFormatters.get(timezone);
|
|
5
|
+
if (!formatter) {
|
|
6
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
7
|
+
timeZone: timezone,
|
|
8
|
+
year: "numeric",
|
|
9
|
+
month: "2-digit",
|
|
10
|
+
day: "2-digit",
|
|
11
|
+
hour: "2-digit",
|
|
12
|
+
minute: "2-digit",
|
|
13
|
+
second: "2-digit",
|
|
14
|
+
hourCycle: "h23"
|
|
15
|
+
});
|
|
16
|
+
if (timezoneFormatters.size >= 64) timezoneFormatters.delete(timezoneFormatters.keys().next().value);
|
|
17
|
+
timezoneFormatters.set(timezone, formatter);
|
|
18
|
+
}
|
|
19
|
+
const parts = formatter.formatToParts(instant);
|
|
20
|
+
const number = (kind) => Number(parts.find((part) => part.type === kind)?.value);
|
|
21
|
+
return { year: number("year"), month: number("month"), day: number("day"), hour: number("hour"), minute: number("minute"), second: number("second") };
|
|
22
|
+
}
|
|
23
|
+
function zonedWallToInstant(wall, timezone) {
|
|
24
|
+
const target = wall.getTime();
|
|
25
|
+
let candidate = target;
|
|
26
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
27
|
+
const parts = zonedParts(new Date(candidate), timezone);
|
|
28
|
+
const displayed = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
|
|
29
|
+
const next = candidate + target - displayed;
|
|
30
|
+
if (next === candidate) return new Date(candidate);
|
|
31
|
+
candidate = next;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function zonedStartOfDay(dayKey, timezone) {
|
|
36
|
+
return zonedWallToInstant(/* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`), timezone);
|
|
37
|
+
}
|
|
38
|
+
function dayKeyForRule(instant, timezone) {
|
|
39
|
+
if (!timezone || timezone === "UTC" || timezone === "Etc/UTC" || timezone === "Etc/GMT") return toDayKey(instant);
|
|
40
|
+
const parts = zonedParts(instant, timezone);
|
|
41
|
+
return `${parts.year}-${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}`;
|
|
42
|
+
}
|
|
2
43
|
function parseRrule(rule) {
|
|
3
44
|
const dtStartMatch = rule.match(/DTSTART[:=](\d{8}T\d{6}Z?)/);
|
|
4
45
|
const durationMatch = rule.match(/DURATION:PT(?:(\d+)H)?(?:(\d+)M)?/);
|
|
@@ -39,10 +80,10 @@ function buildExdateSets(exdates) {
|
|
|
39
80
|
});
|
|
40
81
|
return { dateOnly, dateTime };
|
|
41
82
|
}
|
|
42
|
-
function shouldExcludeOccurrence(startAt, exdates) {
|
|
83
|
+
function shouldExcludeOccurrence(startAt, exdates, timezone) {
|
|
43
84
|
const { dateOnly, dateTime } = buildExdateSets(exdates);
|
|
44
85
|
if (dateTime.has(startAt.getTime())) return true;
|
|
45
|
-
const dayKey = startAt
|
|
86
|
+
const dayKey = dayKeyForRule(startAt, timezone);
|
|
46
87
|
return dateOnly.has(dayKey);
|
|
47
88
|
}
|
|
48
89
|
function toDayKey(value) {
|
|
@@ -51,15 +92,46 @@ function toDayKey(value) {
|
|
|
51
92
|
function startOfDay(value) {
|
|
52
93
|
return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()));
|
|
53
94
|
}
|
|
54
|
-
function
|
|
95
|
+
function expandZonedRule(rule, parsed, range, weeklyScheduleTemplate) {
|
|
96
|
+
const timezone = rule.timezone;
|
|
97
|
+
const anchor = zonedParts(parsed.startAt, timezone);
|
|
98
|
+
const anchorWall = Date.UTC(anchor.year, anchor.month - 1, anchor.day, anchor.hour, anchor.minute, anchor.second);
|
|
99
|
+
const step = parsed.freq === "WEEKLY" ? 7 * DAY_MS : DAY_MS;
|
|
100
|
+
const duration = parsed.durationMinutes * 6e4;
|
|
101
|
+
const windows = [];
|
|
102
|
+
const rangeAnchor = zonedParts(range.start, timezone);
|
|
103
|
+
const rangeStartWall = Date.UTC(rangeAnchor.year, rangeAnchor.month - 1, rangeAnchor.day, rangeAnchor.hour, rangeAnchor.minute, rangeAnchor.second);
|
|
104
|
+
const rangeOffset = Math.floor((rangeStartWall - duration - DAY_MS - anchorWall) / step);
|
|
105
|
+
const skipped = weeklyScheduleTemplate && parsed.freq === "WEEKLY" && parsed.count === void 0 ? rangeOffset : Math.max(0, rangeOffset);
|
|
106
|
+
let wall = new Date(anchorWall + skipped * step);
|
|
107
|
+
let remaining = (parsed.count ?? Number.POSITIVE_INFINITY) - skipped;
|
|
108
|
+
while (wall.getTime() <= range.end.getTime() + DAY_MS && remaining > 0) {
|
|
109
|
+
const start = zonedWallToInstant(wall, timezone);
|
|
110
|
+
if (start && !shouldExcludeOccurrence(start, rule.exdates, timezone)) {
|
|
111
|
+
const end = new Date(start.getTime() + duration);
|
|
112
|
+
if (end > range.start && start < range.end) windows.push({ start, end, ruleId: rule.id });
|
|
113
|
+
}
|
|
114
|
+
wall = new Date(wall.getTime() + step);
|
|
115
|
+
remaining -= 1;
|
|
116
|
+
}
|
|
117
|
+
return windows;
|
|
118
|
+
}
|
|
119
|
+
function expandRule(rule, parsed, range, weeklyScheduleTemplate) {
|
|
55
120
|
const { startAt, durationMinutes, freq, count, repeat } = parsed;
|
|
56
121
|
if (repeat === "once") {
|
|
57
|
-
if (shouldExcludeOccurrence(startAt, rule.exdates)) return [];
|
|
58
|
-
const
|
|
59
|
-
const
|
|
122
|
+
if (shouldExcludeOccurrence(startAt, rule.exdates, rule.timezone)) return [];
|
|
123
|
+
const dayKey = dayKeyForRule(startAt, rule.timezone);
|
|
124
|
+
const start = rule.timezone ? zonedStartOfDay(dayKey, rule.timezone) : startOfDay(startAt);
|
|
125
|
+
if (!start) return [];
|
|
126
|
+
const nextDay = new Date((/* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`)).getTime() + DAY_MS).toISOString().slice(0, 10);
|
|
127
|
+
const end = rule.timezone ? zonedStartOfDay(nextDay, rule.timezone) : new Date(start.getTime() + DAY_MS);
|
|
128
|
+
if (!end) return [];
|
|
60
129
|
if (end <= range.start || start >= range.end) return [];
|
|
61
130
|
return [{ start, end, ruleId: rule.id }];
|
|
62
131
|
}
|
|
132
|
+
if (rule.timezone && !["UTC", "Etc/UTC", "Etc/GMT"].includes(rule.timezone)) {
|
|
133
|
+
return expandZonedRule(rule, parsed, range, weeklyScheduleTemplate);
|
|
134
|
+
}
|
|
63
135
|
const durationMs = durationMinutes * 6e4;
|
|
64
136
|
const windows = [];
|
|
65
137
|
const addWindow = (start) => {
|
|
@@ -68,37 +140,23 @@ function expandRule(rule, parsed, range) {
|
|
|
68
140
|
if (end <= range.start || start >= range.end) return;
|
|
69
141
|
windows.push({ start, end, ruleId: rule.id });
|
|
70
142
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
cursor2 = new Date(cursor2.getTime() + DAY_MS);
|
|
77
|
-
remaining2 -= 1;
|
|
78
|
-
}
|
|
79
|
-
return windows;
|
|
80
|
-
}
|
|
81
|
-
let cursor = new Date(startAt);
|
|
82
|
-
let remaining = count ?? Number.POSITIVE_INFINITY;
|
|
83
|
-
if (cursor < range.start) {
|
|
84
|
-
const diffDays = Math.floor((range.start.getTime() - cursor.getTime()) / DAY_MS);
|
|
85
|
-
const weeksToAdd = Math.floor(diffDays / 7);
|
|
86
|
-
if (weeksToAdd > 0) {
|
|
87
|
-
cursor = new Date(cursor.getTime() + weeksToAdd * 7 * DAY_MS);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
143
|
+
const step = freq === "WEEKLY" ? 7 * DAY_MS : DAY_MS;
|
|
144
|
+
const rangeOffset = Math.floor((range.start.getTime() - durationMs - startAt.getTime()) / step);
|
|
145
|
+
const skipped = weeklyScheduleTemplate && freq === "WEEKLY" && count === void 0 ? rangeOffset : Math.max(0, rangeOffset);
|
|
146
|
+
let cursor = new Date(startAt.getTime() + skipped * step);
|
|
147
|
+
let remaining = (count ?? Number.POSITIVE_INFINITY) - skipped;
|
|
90
148
|
while (cursor < range.end && remaining > 0) {
|
|
91
149
|
addWindow(new Date(cursor));
|
|
92
|
-
cursor = new Date(cursor.getTime() +
|
|
150
|
+
cursor = new Date(cursor.getTime() + step);
|
|
93
151
|
remaining -= 1;
|
|
94
152
|
}
|
|
95
153
|
return windows;
|
|
96
154
|
}
|
|
97
|
-
function expandRules(rules, range) {
|
|
155
|
+
function expandRules(rules, range, weeklyScheduleTemplate) {
|
|
98
156
|
const expanded = rules.flatMap((rule) => {
|
|
99
157
|
const parsed = parseRrule(rule.rrule);
|
|
100
158
|
if (!parsed) return [];
|
|
101
|
-
return expandRule(rule, parsed, range);
|
|
159
|
+
return expandRule(rule, parsed, range, weeklyScheduleTemplate);
|
|
102
160
|
});
|
|
103
161
|
return expanded.sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
104
162
|
}
|
|
@@ -123,32 +181,37 @@ function subtractWindow(window, blockers) {
|
|
|
123
181
|
return segments;
|
|
124
182
|
}
|
|
125
183
|
function getMergedAvailabilityWindows(params) {
|
|
126
|
-
const
|
|
184
|
+
const rules = params.respectTimezone ? params.rules : params.rules.map((rule) => ({ ...rule, timezone: void 0 }));
|
|
185
|
+
const parsedRules = rules.map((rule) => {
|
|
127
186
|
const parsed = parseRrule(rule.rrule);
|
|
128
187
|
if (!parsed) return null;
|
|
129
188
|
return { rule, parsed };
|
|
130
189
|
}).filter((entry) => entry !== null);
|
|
131
190
|
const overrideDays = /* @__PURE__ */ new Map();
|
|
132
191
|
parsedRules.filter(({ parsed }) => parsed.repeat === "once").forEach(({ rule, parsed }) => {
|
|
133
|
-
const dayKey =
|
|
192
|
+
const dayKey = dayKeyForRule(parsed.startAt, rule.timezone);
|
|
193
|
+
const overrideKey = `${rule.timezone ?? "UTC"}|${dayKey}`;
|
|
134
194
|
const kind = rule.kind === "unavailability" ? "unavailability" : "availability";
|
|
135
195
|
if (kind === "unavailability") {
|
|
136
|
-
overrideDays.set(dayKey,
|
|
137
|
-
} else if (!overrideDays.has(
|
|
138
|
-
overrideDays.set(dayKey,
|
|
196
|
+
overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone });
|
|
197
|
+
} else if (!overrideDays.has(overrideKey)) {
|
|
198
|
+
overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone });
|
|
139
199
|
}
|
|
140
200
|
});
|
|
141
201
|
const availabilityRules = parsedRules.filter(({ parsed, rule }) => parsed.repeat !== "once" && rule.kind !== "unavailability").map(({ rule }) => rule);
|
|
142
202
|
const unavailabilityRules = parsedRules.filter(({ parsed, rule }) => parsed.repeat !== "once" && rule.kind === "unavailability").map(({ rule }) => rule);
|
|
143
|
-
const availabilityWindows = expandRules(
|
|
144
|
-
const unavailabilityWindows = expandRules(unavailabilityRules, params.range);
|
|
203
|
+
const availabilityWindows = availabilityRules.flatMap((rule) => expandRules([rule], params.range, params.weeklyScheduleTemplate === true).filter((window) => !overrideDays.has(`${rule.timezone ?? "UTC"}|${dayKeyForRule(window.start, rule.timezone)}`)));
|
|
204
|
+
const unavailabilityWindows = expandRules(unavailabilityRules, params.range, params.weeklyScheduleTemplate === true);
|
|
145
205
|
const merged = unavailabilityWindows.length === 0 ? availabilityWindows : availabilityWindows.flatMap((window) => subtractWindow(window, unavailabilityWindows));
|
|
146
206
|
const overrideWindows = [];
|
|
147
|
-
overrideDays.forEach((kind, dayKey) => {
|
|
207
|
+
overrideDays.forEach(({ kind, dayKey, timezone }) => {
|
|
148
208
|
if (kind !== "availability") return;
|
|
149
|
-
const start = /* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`);
|
|
209
|
+
const start = timezone ? zonedStartOfDay(dayKey, timezone) : /* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`);
|
|
210
|
+
if (!start) return;
|
|
150
211
|
if (Number.isNaN(start.getTime())) return;
|
|
151
|
-
const
|
|
212
|
+
const nextDay = new Date((/* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`)).getTime() + DAY_MS).toISOString().slice(0, 10);
|
|
213
|
+
const end = timezone ? zonedStartOfDay(nextDay, timezone) : new Date(start.getTime() + DAY_MS);
|
|
214
|
+
if (!end) return;
|
|
152
215
|
if (end <= params.range.start || start >= params.range.end) return;
|
|
153
216
|
overrideWindows.push({ start, end });
|
|
154
217
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/planner/lib/availabilityMerge.ts"],
|
|
4
|
-
"sourcesContent": ["type AvailabilityKind = 'availability' | 'unavailability'\n\nexport type AvailabilityRuleLike = {\n id?: string\n rrule: string\n exdates?: string[]\n kind?: AvailabilityKind\n note?: string | null\n}\n\nexport type AvailabilityRange = {\n start: Date\n end: Date\n}\n\nexport type AvailabilityWindow = {\n start: Date\n end: Date\n ruleId?: string\n}\n\ntype ParsedRule = {\n startAt: Date\n durationMinutes: number\n freq: 'DAILY' | 'WEEKLY'\n repeat: 'once' | 'daily' | 'weekly'\n count?: number\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction parseRrule(rule: string): ParsedRule | null {\n const dtStartMatch = rule.match(/DTSTART[:=](\\d{8}T\\d{6}Z?)/)\n const durationMatch = rule.match(/DURATION:PT(?:(\\d+)H)?(?:(\\d+)M)?/)\n const freqMatch = rule.match(/FREQ=([A-Z]+)/)\n if (!dtStartMatch?.[1] || !durationMatch || !freqMatch?.[1]) return null\n const raw = dtStartMatch[1].replace(/Z$/, '')\n const parts = raw.match(/(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})/)\n if (!parts) return null\n const [, year, month, day, hour, minute, second] = parts\n const iso = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`\n const startAt = new Date(iso)\n if (Number.isNaN(startAt.getTime())) return null\n\n const hours = durationMatch[1] ? Number(durationMatch[1]) : 0\n const minutes = durationMatch[2] ? Number(durationMatch[2]) : 0\n const durationMinutes = Math.max(1, hours * 60 + minutes)\n const freq = freqMatch[1]\n if (freq !== 'DAILY' && freq !== 'WEEKLY') return null\n\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const count = countMatch?.[1] ? Number(countMatch[1]) : undefined\n const repeat = freq === 'WEEKLY'\n ? 'weekly'\n : freq === 'DAILY' && count === 1\n ? 'once'\n : 'daily'\n return { startAt, durationMinutes, freq, count, repeat }\n}\n\nfunction buildExdateSets(exdates?: string[]) {\n const dateOnly = new Set<string>()\n const dateTime = new Set<number>()\n ;(exdates ?? []).forEach((value) => {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed) return\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n dateOnly.add(trimmed)\n return\n }\n const parsed = new Date(trimmed)\n if (!Number.isNaN(parsed.getTime())) {\n dateTime.add(parsed.getTime())\n }\n })\n return { dateOnly, dateTime }\n}\n\nfunction shouldExcludeOccurrence(startAt: Date, exdates?: string[]): boolean {\n const { dateOnly, dateTime } = buildExdateSets(exdates)\n if (dateTime.has(startAt.getTime())) return true\n const dayKey = startAt.toISOString().slice(0, 10)\n return dateOnly.has(dayKey)\n}\n\nfunction toDayKey(value: Date): string {\n return value.toISOString().slice(0, 10)\n}\n\nfunction startOfDay(value: Date): Date {\n return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()))\n}\n\nfunction expandRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange): AvailabilityWindow[] {\n const { startAt, durationMinutes, freq, count, repeat } = parsed\n if (repeat === 'once') {\n if (shouldExcludeOccurrence(startAt, rule.exdates)) return []\n const start = startOfDay(startAt)\n const end = new Date(start.getTime() + DAY_MS)\n if (end <= range.start || start >= range.end) return []\n return [{ start, end, ruleId: rule.id }]\n }\n const durationMs = durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const addWindow = (start: Date) => {\n if (shouldExcludeOccurrence(start, rule.exdates)) return\n const end = new Date(start.getTime() + durationMs)\n if (end <= range.start || start >= range.end) return\n windows.push({ start, end, ruleId: rule.id })\n }\n\n if (freq === 'DAILY') {\n let cursor = new Date(startAt)\n let remaining = count ?? Number.POSITIVE_INFINITY\n while (cursor < range.end && remaining > 0) {\n addWindow(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n remaining -= 1\n }\n return windows\n }\n\n let cursor = new Date(startAt)\n let remaining = count ?? Number.POSITIVE_INFINITY\n if (cursor < range.start) {\n const diffDays = Math.floor((range.start.getTime() - cursor.getTime()) / DAY_MS)\n const weeksToAdd = Math.floor(diffDays / 7)\n if (weeksToAdd > 0) {\n cursor = new Date(cursor.getTime() + weeksToAdd * 7 * DAY_MS)\n }\n }\n while (cursor < range.end && remaining > 0) {\n addWindow(new Date(cursor))\n cursor = new Date(cursor.getTime() + 7 * DAY_MS)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRules(rules: AvailabilityRuleLike[], range: AvailabilityRange): AvailabilityWindow[] {\n const expanded = rules.flatMap((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return []\n return expandRule(rule, parsed, range)\n })\n return expanded.sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n\nfunction subtractWindow(window: AvailabilityWindow, blockers: AvailabilityWindow[]): AvailabilityWindow[] {\n let segments = [window]\n blockers.forEach((blocker) => {\n const next: AvailabilityWindow[] = []\n segments.forEach((segment) => {\n if (blocker.end <= segment.start || blocker.start >= segment.end) {\n next.push(segment)\n return\n }\n if (blocker.start > segment.start) {\n next.push({ ...segment, end: new Date(blocker.start) })\n }\n if (blocker.end < segment.end) {\n next.push({ ...segment, start: new Date(blocker.end) })\n }\n })\n segments = next\n })\n return segments\n}\n\nexport function getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n}): AvailabilityWindow[] {\n const parsedRules = params.rules\n .map((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return null\n return { rule, parsed }\n })\n .filter((entry): entry is { rule: AvailabilityRuleLike; parsed: ParsedRule } => entry !== null)\n\n const overrideDays = new Map<string, AvailabilityKind>()\n parsedRules\n .filter(({ parsed }) => parsed.repeat === 'once')\n .forEach(({ rule, parsed }) => {\n const dayKey = toDayKey(parsed.startAt)\n const kind = rule.kind === 'unavailability' ? 'unavailability' : 'availability'\n if (kind === 'unavailability') {\n overrideDays.set(dayKey, 'unavailability')\n } else if (!overrideDays.has(dayKey)) {\n overrideDays.set(dayKey, 'availability')\n }\n })\n\n const availabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind !== 'unavailability')\n .map(({ rule }) => rule)\n const unavailabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind === 'unavailability')\n .map(({ rule }) => rule)\n\n const availabilityWindows = expandRules(availabilityRules, params.range)\n .filter((window) => !overrideDays.has(toDayKey(window.start)))\n const unavailabilityWindows = expandRules(unavailabilityRules, params.range)\n\n const merged = unavailabilityWindows.length === 0\n ? availabilityWindows\n : availabilityWindows.flatMap((window) => subtractWindow(window, unavailabilityWindows))\n\n const overrideWindows: AvailabilityWindow[] = []\n overrideDays.forEach((kind, dayKey) => {\n if (kind !== 'availability') return\n const start = new Date(`${dayKey}T00:00:00Z`)\n if (Number.isNaN(start.getTime())) return\n const end = new Date(start.getTime() + DAY_MS)\n if (end <= params.range.start || start >= params.range.end) return\n overrideWindows.push({ start, end })\n })\n\n return [...merged, ...overrideWindows].sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": [
|
|
4
|
+
"sourcesContent": ["type AvailabilityKind = 'availability' | 'unavailability'\n\nexport type AvailabilityRuleLike = {\n id?: string\n rrule: string\n timezone?: string\n exdates?: string[]\n kind?: AvailabilityKind\n note?: string | null\n}\n\nexport type AvailabilityRange = {\n start: Date\n end: Date\n}\n\nexport type AvailabilityWindow = {\n start: Date\n end: Date\n ruleId?: string\n}\n\ntype ParsedRule = {\n startAt: Date\n durationMinutes: number\n freq: 'DAILY' | 'WEEKLY'\n repeat: 'once' | 'daily' | 'weekly'\n count?: number\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\nconst timezoneFormatters = new Map<string, Intl.DateTimeFormat>()\n\nfunction zonedParts(instant: Date, timezone: string): { year: number; month: number; day: number; hour: number; minute: number; second: number } {\n let formatter = timezoneFormatters.get(timezone)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit',\n hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23',\n })\n if (timezoneFormatters.size >= 64) timezoneFormatters.delete(timezoneFormatters.keys().next().value!)\n timezoneFormatters.set(timezone, formatter)\n }\n const parts = formatter.formatToParts(instant)\n const number = (kind: string) => Number(parts.find((part) => part.type === kind)?.value)\n return { year: number('year'), month: number('month'), day: number('day'), hour: number('hour'), minute: number('minute'), second: number('second') }\n}\n\nfunction zonedWallToInstant(wall: Date, timezone: string): Date | null {\n const target = wall.getTime()\n let candidate = target\n for (let attempt = 0; attempt < 4; attempt += 1) {\n const parts = zonedParts(new Date(candidate), timezone)\n const displayed = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second)\n const next = candidate + target - displayed\n if (next === candidate) return new Date(candidate)\n candidate = next\n }\n return null\n}\n\nfunction zonedStartOfDay(dayKey: string, timezone: string): Date | null {\n return zonedWallToInstant(new Date(`${dayKey}T00:00:00Z`), timezone)\n}\n\nfunction dayKeyForRule(instant: Date, timezone?: string): string {\n if (!timezone || timezone === 'UTC' || timezone === 'Etc/UTC' || timezone === 'Etc/GMT') return toDayKey(instant)\n const parts = zonedParts(instant, timezone)\n return `${parts.year}-${String(parts.month).padStart(2, '0')}-${String(parts.day).padStart(2, '0')}`\n}\n\nfunction parseRrule(rule: string): ParsedRule | null {\n const dtStartMatch = rule.match(/DTSTART[:=](\\d{8}T\\d{6}Z?)/)\n const durationMatch = rule.match(/DURATION:PT(?:(\\d+)H)?(?:(\\d+)M)?/)\n const freqMatch = rule.match(/FREQ=([A-Z]+)/)\n if (!dtStartMatch?.[1] || !durationMatch || !freqMatch?.[1]) return null\n const raw = dtStartMatch[1].replace(/Z$/, '')\n const parts = raw.match(/(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})/)\n if (!parts) return null\n const [, year, month, day, hour, minute, second] = parts\n const iso = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`\n const startAt = new Date(iso)\n if (Number.isNaN(startAt.getTime())) return null\n\n const hours = durationMatch[1] ? Number(durationMatch[1]) : 0\n const minutes = durationMatch[2] ? Number(durationMatch[2]) : 0\n const durationMinutes = Math.max(1, hours * 60 + minutes)\n const freq = freqMatch[1]\n if (freq !== 'DAILY' && freq !== 'WEEKLY') return null\n\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const count = countMatch?.[1] ? Number(countMatch[1]) : undefined\n const repeat = freq === 'WEEKLY'\n ? 'weekly'\n : freq === 'DAILY' && count === 1\n ? 'once'\n : 'daily'\n return { startAt, durationMinutes, freq, count, repeat }\n}\n\nfunction buildExdateSets(exdates?: string[]) {\n const dateOnly = new Set<string>()\n const dateTime = new Set<number>()\n ;(exdates ?? []).forEach((value) => {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed) return\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n dateOnly.add(trimmed)\n return\n }\n const parsed = new Date(trimmed)\n if (!Number.isNaN(parsed.getTime())) {\n dateTime.add(parsed.getTime())\n }\n })\n return { dateOnly, dateTime }\n}\n\nfunction shouldExcludeOccurrence(startAt: Date, exdates?: string[], timezone?: string): boolean {\n const { dateOnly, dateTime } = buildExdateSets(exdates)\n if (dateTime.has(startAt.getTime())) return true\n const dayKey = dayKeyForRule(startAt, timezone)\n return dateOnly.has(dayKey)\n}\n\nfunction toDayKey(value: Date): string {\n return value.toISOString().slice(0, 10)\n}\n\nfunction startOfDay(value: Date): Date {\n return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()))\n}\n\nfunction expandZonedRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const timezone = rule.timezone!\n const anchor = zonedParts(parsed.startAt, timezone)\n const anchorWall = Date.UTC(anchor.year, anchor.month - 1, anchor.day, anchor.hour, anchor.minute, anchor.second)\n const step = parsed.freq === 'WEEKLY' ? 7 * DAY_MS : DAY_MS\n const duration = parsed.durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const rangeAnchor = zonedParts(range.start, timezone)\n const rangeStartWall = Date.UTC(rangeAnchor.year, rangeAnchor.month - 1, rangeAnchor.day, rangeAnchor.hour, rangeAnchor.minute, rangeAnchor.second)\n const rangeOffset = Math.floor((rangeStartWall - duration - DAY_MS - anchorWall) / step)\n const skipped = weeklyScheduleTemplate && parsed.freq === 'WEEKLY' && parsed.count === undefined ? rangeOffset : Math.max(0, rangeOffset)\n let wall = new Date(anchorWall + skipped * step)\n let remaining = (parsed.count ?? Number.POSITIVE_INFINITY) - skipped\n while (wall.getTime() <= range.end.getTime() + DAY_MS && remaining > 0) {\n const start = zonedWallToInstant(wall, timezone)\n if (start && !shouldExcludeOccurrence(start, rule.exdates, timezone)) {\n const end = new Date(start.getTime() + duration)\n if (end > range.start && start < range.end) windows.push({ start, end, ruleId: rule.id })\n }\n wall = new Date(wall.getTime() + step)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const { startAt, durationMinutes, freq, count, repeat } = parsed\n if (repeat === 'once') {\n if (shouldExcludeOccurrence(startAt, rule.exdates, rule.timezone)) return []\n const dayKey = dayKeyForRule(startAt, rule.timezone)\n const start = rule.timezone ? zonedStartOfDay(dayKey, rule.timezone) : startOfDay(startAt)\n if (!start) return []\n const nextDay = new Date(new Date(`${dayKey}T00:00:00Z`).getTime() + DAY_MS).toISOString().slice(0, 10)\n const end = rule.timezone ? zonedStartOfDay(nextDay, rule.timezone) : new Date(start.getTime() + DAY_MS)\n if (!end) return []\n if (end <= range.start || start >= range.end) return []\n return [{ start, end, ruleId: rule.id }]\n }\n if (rule.timezone && !['UTC', 'Etc/UTC', 'Etc/GMT'].includes(rule.timezone)) {\n return expandZonedRule(rule, parsed, range, weeklyScheduleTemplate)\n }\n const durationMs = durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const addWindow = (start: Date) => {\n if (shouldExcludeOccurrence(start, rule.exdates)) return\n const end = new Date(start.getTime() + durationMs)\n if (end <= range.start || start >= range.end) return\n windows.push({ start, end, ruleId: rule.id })\n }\n\n const step = freq === 'WEEKLY' ? 7 * DAY_MS : DAY_MS\n const rangeOffset = Math.floor((range.start.getTime() - durationMs - startAt.getTime()) / step)\n const skipped = weeklyScheduleTemplate && freq === 'WEEKLY' && count === undefined ? rangeOffset : Math.max(0, rangeOffset)\n let cursor = new Date(startAt.getTime() + skipped * step)\n let remaining = (count ?? Number.POSITIVE_INFINITY) - skipped\n while (cursor < range.end && remaining > 0) {\n addWindow(new Date(cursor))\n cursor = new Date(cursor.getTime() + step)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRules(rules: AvailabilityRuleLike[], range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const expanded = rules.flatMap((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return []\n return expandRule(rule, parsed, range, weeklyScheduleTemplate)\n })\n return expanded.sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n\nfunction subtractWindow(window: AvailabilityWindow, blockers: AvailabilityWindow[]): AvailabilityWindow[] {\n let segments = [window]\n blockers.forEach((blocker) => {\n const next: AvailabilityWindow[] = []\n segments.forEach((segment) => {\n if (blocker.end <= segment.start || blocker.start >= segment.end) {\n next.push(segment)\n return\n }\n if (blocker.start > segment.start) {\n next.push({ ...segment, end: new Date(blocker.start) })\n }\n if (blocker.end < segment.end) {\n next.push({ ...segment, start: new Date(blocker.end) })\n }\n })\n segments = next\n })\n return segments\n}\n\nexport function getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n respectTimezone?: boolean\n weeklyScheduleTemplate?: boolean\n}): AvailabilityWindow[] {\n const rules = params.respectTimezone ? params.rules : params.rules.map((rule) => ({ ...rule, timezone: undefined }))\n const parsedRules = rules\n .map((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return null\n return { rule, parsed }\n })\n .filter((entry): entry is { rule: AvailabilityRuleLike; parsed: ParsedRule } => entry !== null)\n\n const overrideDays = new Map<string, { kind: AvailabilityKind; dayKey: string; timezone?: string }>()\n parsedRules\n .filter(({ parsed }) => parsed.repeat === 'once')\n .forEach(({ rule, parsed }) => {\n const dayKey = dayKeyForRule(parsed.startAt, rule.timezone)\n const overrideKey = `${rule.timezone ?? 'UTC'}|${dayKey}`\n const kind = rule.kind === 'unavailability' ? 'unavailability' : 'availability'\n if (kind === 'unavailability') {\n overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone })\n } else if (!overrideDays.has(overrideKey)) {\n overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone })\n }\n })\n\n const availabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind !== 'unavailability')\n .map(({ rule }) => rule)\n const unavailabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind === 'unavailability')\n .map(({ rule }) => rule)\n\n const availabilityWindows = availabilityRules.flatMap((rule) =>\n expandRules([rule], params.range, params.weeklyScheduleTemplate === true).filter((window) =>\n !overrideDays.has(`${rule.timezone ?? 'UTC'}|${dayKeyForRule(window.start, rule.timezone)}`)))\n const unavailabilityWindows = expandRules(unavailabilityRules, params.range, params.weeklyScheduleTemplate === true)\n\n const merged = unavailabilityWindows.length === 0\n ? availabilityWindows\n : availabilityWindows.flatMap((window) => subtractWindow(window, unavailabilityWindows))\n\n const overrideWindows: AvailabilityWindow[] = []\n overrideDays.forEach(({ kind, dayKey, timezone }) => {\n if (kind !== 'availability') return\n const start = timezone ? zonedStartOfDay(dayKey, timezone) : new Date(`${dayKey}T00:00:00Z`)\n if (!start) return\n if (Number.isNaN(start.getTime())) return\n const nextDay = new Date(new Date(`${dayKey}T00:00:00Z`).getTime() + DAY_MS).toISOString().slice(0, 10)\n const end = timezone ? zonedStartOfDay(nextDay, timezone) : new Date(start.getTime() + DAY_MS)\n if (!end) return\n if (end <= params.range.start || start >= params.range.end) return\n overrideWindows.push({ start, end })\n })\n\n return [...merged, ...overrideWindows].sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/planner/services/plannerAvailabilityService.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AvailabilityRange, AvailabilityRuleLike, AvailabilityWindow } from '../lib/availabilityMerge'\nimport { getMergedAvailabilityWindows } from '../lib/availabilityMerge'\n\nexport type { AvailabilityRange, AvailabilityRuleLike, AvailabilityWindow }\n\nexport interface PlannerAvailabilityService {\n getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n }): AvailabilityWindow[]\n}\n\nexport class DefaultPlannerAvailabilityService implements PlannerAvailabilityService {\n getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n }): AvailabilityWindow[] {\n return getMergedAvailabilityWindows(params)\n }\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,oCAAoC;
|
|
4
|
+
"sourcesContent": ["import type { AvailabilityRange, AvailabilityRuleLike, AvailabilityWindow } from '../lib/availabilityMerge'\nimport { getMergedAvailabilityWindows } from '../lib/availabilityMerge'\n\nexport type { AvailabilityRange, AvailabilityRuleLike, AvailabilityWindow }\n\nexport interface PlannerAvailabilityService {\n getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n respectTimezone?: boolean\n weeklyScheduleTemplate?: boolean\n }): AvailabilityWindow[]\n}\n\nexport class DefaultPlannerAvailabilityService implements PlannerAvailabilityService {\n getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n respectTimezone?: boolean\n weeklyScheduleTemplate?: boolean\n }): AvailabilityWindow[] {\n return getMergedAvailabilityWindows(params)\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,oCAAoC;AAatC,MAAM,kCAAwE;AAAA,EACnF,6BAA6B,QAKJ;AACvB,WAAO,6BAA6B,MAAM;AAAA,EAC5C;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -43,7 +43,11 @@ import {
|
|
|
43
43
|
import { E } from "../../../../generated/entities.ids.generated.js";
|
|
44
44
|
import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
|
|
45
45
|
import { useOrganizationScopeDetail } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
formatMoney,
|
|
48
|
+
normalizeNumber,
|
|
49
|
+
resolveCatalogPriceAmount
|
|
50
|
+
} from "./lineItemUtils.js";
|
|
47
51
|
import { prepareShippedLineUpdatePayload } from "./lineItemShipmentLock.js";
|
|
48
52
|
import {
|
|
49
53
|
normalizeCustomFieldSubmitValue,
|
|
@@ -256,6 +260,7 @@ function LineItemDialog({
|
|
|
256
260
|
const [, setLineStatusLoading] = React.useState(false);
|
|
257
261
|
const productOptionsRef = React.useRef(/* @__PURE__ */ new Map());
|
|
258
262
|
const variantOptionsRef = React.useRef(/* @__PURE__ */ new Map());
|
|
263
|
+
const selectedProductIdRef = React.useRef(null);
|
|
259
264
|
const taxRatesRef = React.useRef([]);
|
|
260
265
|
const defaultTaxRateRef = React.useRef(null);
|
|
261
266
|
const dialogContentRef = React.useRef(null);
|
|
@@ -320,6 +325,7 @@ function LineItemDialog({
|
|
|
320
325
|
}
|
|
321
326
|
setInitialValues(base);
|
|
322
327
|
setLineMode(base.lineMode);
|
|
328
|
+
selectedProductIdRef.current = null;
|
|
323
329
|
setProductOption(null);
|
|
324
330
|
setVariantOption(null);
|
|
325
331
|
setPriceOptions([]);
|
|
@@ -618,7 +624,13 @@ function LineItemDialog({
|
|
|
618
624
|
scopeReason: reason,
|
|
619
625
|
scopeTags: tags
|
|
620
626
|
};
|
|
621
|
-
}).filter((entry) => Boolean(entry))
|
|
627
|
+
}).filter((entry) => Boolean(entry)).filter(
|
|
628
|
+
(entry) => resolveCatalogPriceAmount(
|
|
629
|
+
entry.amountNet,
|
|
630
|
+
entry.amountGross,
|
|
631
|
+
entry.displayMode === "excluding-tax" ? "net" : "gross"
|
|
632
|
+
) !== null
|
|
633
|
+
);
|
|
622
634
|
setPriceOptions(mapped);
|
|
623
635
|
return mapped;
|
|
624
636
|
} catch (err) {
|
|
@@ -682,7 +694,11 @@ function LineItemDialog({
|
|
|
682
694
|
if (!setFormValue) return;
|
|
683
695
|
if (selected) {
|
|
684
696
|
const mode = selected.displayMode === "excluding-tax" ? "net" : "gross";
|
|
685
|
-
const amountPerBaseUnit =
|
|
697
|
+
const amountPerBaseUnit = resolveCatalogPriceAmount(
|
|
698
|
+
selected.amountNet,
|
|
699
|
+
selected.amountGross,
|
|
700
|
+
mode
|
|
701
|
+
) ?? 0;
|
|
686
702
|
const factor = resolveUnitPriceFactor(options?.quantityUnit ?? null);
|
|
687
703
|
const amount = Number.isFinite(amountPerBaseUnit * factor) ? amountPerBaseUnit * factor : amountPerBaseUnit;
|
|
688
704
|
setFormValue("priceId", selected.id);
|
|
@@ -702,6 +718,101 @@ function LineItemDialog({
|
|
|
702
718
|
},
|
|
703
719
|
[currencyCode, findTaxRateIdByValue, resolveTaxSelection, resolveUnitPriceFactor]
|
|
704
720
|
);
|
|
721
|
+
const commitVariantSelection = React.useCallback(
|
|
722
|
+
({
|
|
723
|
+
variantId,
|
|
724
|
+
productId,
|
|
725
|
+
product,
|
|
726
|
+
values,
|
|
727
|
+
setFormValue
|
|
728
|
+
}) => {
|
|
729
|
+
const selectedOption = variantId ? variantOptionsRef.current.get(variantId) ?? null : null;
|
|
730
|
+
setVariantOption(selectedOption);
|
|
731
|
+
setFormValue?.("variantId", variantId);
|
|
732
|
+
const existingName = typeof values?.name === "string" ? values.name : "";
|
|
733
|
+
if (!existingName.trim()) {
|
|
734
|
+
setFormValue?.(
|
|
735
|
+
"name",
|
|
736
|
+
selectedOption?.title ?? product?.title ?? existingName
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
const taxSource = hasTaxMetadata(selectedOption) ? selectedOption : hasTaxMetadata(product) ? product : null;
|
|
740
|
+
if (taxSource) {
|
|
741
|
+
const taxSelection = resolveTaxSelection(taxSource);
|
|
742
|
+
setFormValue?.("taxRate", taxSelection.taxRate ?? null);
|
|
743
|
+
setFormValue?.("taxRateId", taxSelection.taxRateId ?? null);
|
|
744
|
+
}
|
|
745
|
+
const prevSnapshot = typeof values?.catalogSnapshot === "object" && values.catalogSnapshot ? values.catalogSnapshot : null;
|
|
746
|
+
if (variantId) {
|
|
747
|
+
setFormValue?.("catalogSnapshot", {
|
|
748
|
+
...prevSnapshot ?? {},
|
|
749
|
+
variant: {
|
|
750
|
+
id: variantId,
|
|
751
|
+
title: selectedOption?.title ?? null,
|
|
752
|
+
sku: selectedOption?.sku ?? null,
|
|
753
|
+
thumbnailUrl: selectedOption?.thumbnailUrl ?? null
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
} else if (prevSnapshot) {
|
|
757
|
+
const snapshot = { ...prevSnapshot };
|
|
758
|
+
if ("variant" in snapshot)
|
|
759
|
+
delete snapshot.variant;
|
|
760
|
+
setFormValue?.(
|
|
761
|
+
"catalogSnapshot",
|
|
762
|
+
Object.keys(snapshot).length ? snapshot : null
|
|
763
|
+
);
|
|
764
|
+
} else {
|
|
765
|
+
setFormValue?.("catalogSnapshot", null);
|
|
766
|
+
}
|
|
767
|
+
if (productId) {
|
|
768
|
+
const currentQuantity = typeof values?.quantity === "string" ? values.quantity : 1;
|
|
769
|
+
const currentQuantityUnit = typeof values?.quantityUnit === "string" ? values.quantityUnit : null;
|
|
770
|
+
void loadPrices(
|
|
771
|
+
productId,
|
|
772
|
+
variantId,
|
|
773
|
+
currentQuantity,
|
|
774
|
+
currentQuantityUnit
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
},
|
|
778
|
+
[hasTaxMetadata, loadPrices, resolveTaxSelection]
|
|
779
|
+
);
|
|
780
|
+
const autoSelectSoleVariant = React.useCallback(
|
|
781
|
+
async ({
|
|
782
|
+
productId,
|
|
783
|
+
product,
|
|
784
|
+
values,
|
|
785
|
+
setFormValue
|
|
786
|
+
}) => {
|
|
787
|
+
let variants = [];
|
|
788
|
+
try {
|
|
789
|
+
variants = await loadVariantOptions(
|
|
790
|
+
productId,
|
|
791
|
+
product?.thumbnailUrl ?? null
|
|
792
|
+
);
|
|
793
|
+
} catch (err) {
|
|
794
|
+
logger.error("sales.document.items.autoSelectVariant", { err });
|
|
795
|
+
}
|
|
796
|
+
if (selectedProductIdRef.current !== productId) return;
|
|
797
|
+
if (variants.length === 1) {
|
|
798
|
+
commitVariantSelection({
|
|
799
|
+
variantId: variants[0].id,
|
|
800
|
+
productId,
|
|
801
|
+
product,
|
|
802
|
+
values,
|
|
803
|
+
setFormValue
|
|
804
|
+
});
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
void loadPrices(
|
|
808
|
+
productId,
|
|
809
|
+
null,
|
|
810
|
+
typeof values.quantity === "string" ? values.quantity : 1,
|
|
811
|
+
typeof values.quantityUnit === "string" ? values.quantityUnit : null
|
|
812
|
+
);
|
|
813
|
+
},
|
|
814
|
+
[commitVariantSelection, loadPrices, loadVariantOptions]
|
|
815
|
+
);
|
|
705
816
|
const loadLineStatuses = React.useCallback(async () => {
|
|
706
817
|
setLineStatusLoading(true);
|
|
707
818
|
try {
|
|
@@ -1032,6 +1143,7 @@ function LineItemDialog({
|
|
|
1032
1143
|
const switchMode = (next) => {
|
|
1033
1144
|
if (next === mode) return;
|
|
1034
1145
|
setValue(next);
|
|
1146
|
+
selectedProductIdRef.current = null;
|
|
1035
1147
|
setLineMode(next);
|
|
1036
1148
|
if (next === "custom") {
|
|
1037
1149
|
setProductOption(null);
|
|
@@ -1143,13 +1255,11 @@ function LineItemDialog({
|
|
|
1143
1255
|
setFormValue?.("priceMode", "gross");
|
|
1144
1256
|
const defaultQuantityUnit = selectedOption?.defaultSalesUnit ?? selectedOption?.defaultUnit ?? null;
|
|
1145
1257
|
setFormValue?.("quantityUnit", defaultQuantityUnit);
|
|
1146
|
-
|
|
1258
|
+
const defaultQuantity = typeof selectedOption?.defaultSalesUnitQuantity === "number" && Number.isFinite(
|
|
1147
1259
|
selectedOption.defaultSalesUnitQuantity
|
|
1148
|
-
) && selectedOption.defaultSalesUnitQuantity > 0)
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
String(selectedOption.defaultSalesUnitQuantity)
|
|
1152
|
-
);
|
|
1260
|
+
) && selectedOption.defaultSalesUnitQuantity > 0 ? String(selectedOption.defaultSalesUnitQuantity) : null;
|
|
1261
|
+
if (defaultQuantity) {
|
|
1262
|
+
setFormValue?.("quantity", defaultQuantity);
|
|
1153
1263
|
}
|
|
1154
1264
|
const taxSelection = selectedOption ? resolveTaxSelection(selectedOption) : { taxRate: null, taxRateId: null };
|
|
1155
1265
|
setFormValue?.("taxRate", taxSelection.taxRate ?? null);
|
|
@@ -1158,27 +1268,33 @@ function LineItemDialog({
|
|
|
1158
1268
|
if (!existingName.trim() && selectedOption?.title) {
|
|
1159
1269
|
setFormValue?.("name", selectedOption.title);
|
|
1160
1270
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
);
|
|
1271
|
+
const productSnapshot = next ? {
|
|
1272
|
+
product: {
|
|
1273
|
+
id: next,
|
|
1274
|
+
title: selectedOption?.title ?? null,
|
|
1275
|
+
sku: selectedOption?.sku ?? null,
|
|
1276
|
+
thumbnailUrl: selectedOption?.thumbnailUrl ?? null,
|
|
1277
|
+
defaultUnit: selectedOption?.defaultUnit ?? null,
|
|
1278
|
+
defaultSalesUnit: selectedOption?.defaultSalesUnit ?? null
|
|
1279
|
+
}
|
|
1280
|
+
} : null;
|
|
1281
|
+
setFormValue?.("catalogSnapshot", productSnapshot);
|
|
1282
|
+
selectedProductIdRef.current = next ?? null;
|
|
1174
1283
|
if (next) {
|
|
1175
1284
|
void loadProductUnits(next, selectedOption);
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1285
|
+
const valuesAfterProductChange = {
|
|
1286
|
+
...values,
|
|
1287
|
+
name: existingName.trim() || selectedOption?.title || "",
|
|
1288
|
+
catalogSnapshot: productSnapshot,
|
|
1289
|
+
quantity: defaultQuantity ?? values?.quantity,
|
|
1290
|
+
quantityUnit: defaultQuantityUnit
|
|
1291
|
+
};
|
|
1292
|
+
void autoSelectSoleVariant({
|
|
1293
|
+
productId: next,
|
|
1294
|
+
product: selectedOption,
|
|
1295
|
+
values: valuesAfterProductChange,
|
|
1296
|
+
setFormValue
|
|
1297
|
+
});
|
|
1182
1298
|
}
|
|
1183
1299
|
},
|
|
1184
1300
|
fetchItems: loadProductOptions,
|
|
@@ -1245,57 +1361,14 @@ function LineItemDialog({
|
|
|
1245
1361
|
{
|
|
1246
1362
|
value: typeof value === "string" ? value : null,
|
|
1247
1363
|
onChange: (next) => {
|
|
1248
|
-
const selectedOption = next ? variantOptionsRef.current.get(next) ?? null : null;
|
|
1249
|
-
setVariantOption(selectedOption);
|
|
1250
1364
|
setValue(next ?? null);
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
}
|
|
1258
|
-
const taxSource = hasTaxMetadata(selectedOption) ? selectedOption : hasTaxMetadata(productOption) ? productOption : null;
|
|
1259
|
-
if (taxSource) {
|
|
1260
|
-
const taxSelection = resolveTaxSelection(taxSource);
|
|
1261
|
-
setFormValue?.("taxRate", taxSelection.taxRate ?? null);
|
|
1262
|
-
setFormValue?.(
|
|
1263
|
-
"taxRateId",
|
|
1264
|
-
taxSelection.taxRateId ?? null
|
|
1265
|
-
);
|
|
1266
|
-
}
|
|
1267
|
-
const prevSnapshot = typeof values?.catalogSnapshot === "object" && values.catalogSnapshot ? values.catalogSnapshot : null;
|
|
1268
|
-
if (next) {
|
|
1269
|
-
setFormValue?.("catalogSnapshot", {
|
|
1270
|
-
...prevSnapshot ?? {},
|
|
1271
|
-
variant: {
|
|
1272
|
-
id: next,
|
|
1273
|
-
title: selectedOption?.title ?? null,
|
|
1274
|
-
sku: selectedOption?.sku ?? null,
|
|
1275
|
-
thumbnailUrl: selectedOption?.thumbnailUrl ?? null
|
|
1276
|
-
}
|
|
1277
|
-
});
|
|
1278
|
-
} else if (prevSnapshot) {
|
|
1279
|
-
const snapshot = { ...prevSnapshot };
|
|
1280
|
-
if ("variant" in snapshot)
|
|
1281
|
-
delete snapshot.variant;
|
|
1282
|
-
setFormValue?.(
|
|
1283
|
-
"catalogSnapshot",
|
|
1284
|
-
Object.keys(snapshot).length ? snapshot : null
|
|
1285
|
-
);
|
|
1286
|
-
} else {
|
|
1287
|
-
setFormValue?.("catalogSnapshot", null);
|
|
1288
|
-
}
|
|
1289
|
-
if (productId) {
|
|
1290
|
-
const currentQuantity = typeof values?.quantity === "string" ? values.quantity : 1;
|
|
1291
|
-
const currentQuantityUnit = typeof values?.quantityUnit === "string" ? values.quantityUnit : null;
|
|
1292
|
-
void loadPrices(
|
|
1293
|
-
productId,
|
|
1294
|
-
next,
|
|
1295
|
-
currentQuantity,
|
|
1296
|
-
currentQuantityUnit
|
|
1297
|
-
);
|
|
1298
|
-
}
|
|
1365
|
+
commitVariantSelection({
|
|
1366
|
+
variantId: next ?? null,
|
|
1367
|
+
productId,
|
|
1368
|
+
product: productOption,
|
|
1369
|
+
values,
|
|
1370
|
+
setFormValue
|
|
1371
|
+
});
|
|
1299
1372
|
},
|
|
1300
1373
|
fetchItems: async (query) => {
|
|
1301
1374
|
if (!productId) return [];
|
|
@@ -1556,9 +1629,10 @@ function LineItemDialog({
|
|
|
1556
1629
|
const selectedTaxRate = resolvedValue ? taxRateMap.get(resolvedValue) ?? null : null;
|
|
1557
1630
|
const handleChange = (event) => {
|
|
1558
1631
|
const nextId = event.target.value || null;
|
|
1559
|
-
|
|
1632
|
+
if (!nextId) return;
|
|
1633
|
+
const option = taxRateMap.get(nextId) ?? null;
|
|
1560
1634
|
setValue(nextId);
|
|
1561
|
-
const rate = normalizeNumber(option?.rate);
|
|
1635
|
+
const rate = normalizeNumber(option?.rate, Number.NaN);
|
|
1562
1636
|
setFormValue?.("taxRate", Number.isFinite(rate) ? rate : null);
|
|
1563
1637
|
};
|
|
1564
1638
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
@@ -1829,6 +1903,8 @@ function LineItemDialog({
|
|
|
1829
1903
|
];
|
|
1830
1904
|
}, [
|
|
1831
1905
|
applyPriceSelection,
|
|
1906
|
+
autoSelectSoleVariant,
|
|
1907
|
+
commitVariantSelection,
|
|
1832
1908
|
convertUnitPriceForUnitChange,
|
|
1833
1909
|
currencyCode,
|
|
1834
1910
|
findTaxRateIdByValue,
|
|
@@ -1871,6 +1947,7 @@ function LineItemDialog({
|
|
|
1871
1947
|
return;
|
|
1872
1948
|
}
|
|
1873
1949
|
let cancelled = false;
|
|
1950
|
+
selectedProductIdRef.current = null;
|
|
1874
1951
|
setDeletedCatalogReference(false);
|
|
1875
1952
|
setEditingId(initialLine.id);
|
|
1876
1953
|
const nextForm = defaultForm(initialLine.currencyCode ?? currencyCode);
|
|
@@ -1896,7 +1973,8 @@ function LineItemDialog({
|
|
|
1896
1973
|
nextForm.lineMode = metaLineMode ?? (initialLine.productId || initialLine.productVariantId ? "catalog" : "custom");
|
|
1897
1974
|
const metaTaxRateId = typeof metaRec?.taxRateId === "string" ? metaRec.taxRateId : null;
|
|
1898
1975
|
const fallbackTaxRateId = findTaxRateIdByValue(nextForm.taxRate);
|
|
1899
|
-
|
|
1976
|
+
const defaultTaxRateId = !Number.isFinite(nextForm.taxRate) && defaultTaxRateRef.current ? defaultTaxRateRef.current.id : null;
|
|
1977
|
+
nextForm.taxRateId = metaTaxRateId ?? fallbackTaxRateId ?? defaultTaxRateId;
|
|
1900
1978
|
if (!Number.isFinite(nextForm.taxRate) && nextForm.taxRateId) {
|
|
1901
1979
|
const matched = taxRatesRef.current.find(
|
|
1902
1980
|
(rate) => rate.id === nextForm.taxRateId
|