@openinc/parse-server-opendash 4.1.8 → 4.2.1

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 (57) hide show
  1. package/dist/features/openservice/init.js +4 -0
  2. package/dist/features/openservice/schedules/calendarSync/types/Event.d.ts +2 -0
  3. package/dist/features/openservice/serviceSchedules/acl/assignmentAcl.d.ts +30 -0
  4. package/dist/features/openservice/serviceSchedules/acl/assignmentAcl.js +85 -0
  5. package/dist/features/openservice/serviceSchedules/acl/index.d.ts +1 -0
  6. package/dist/features/openservice/serviceSchedules/acl/index.js +6 -0
  7. package/dist/features/openservice/serviceSchedules/calendarSync/functions/eventToPlan.d.ts +50 -0
  8. package/dist/features/openservice/serviceSchedules/calendarSync/functions/eventToPlan.js +115 -0
  9. package/dist/features/openservice/serviceSchedules/calendarSync/functions/outlookRecurrence.d.ts +59 -0
  10. package/dist/features/openservice/serviceSchedules/calendarSync/functions/outlookRecurrence.js +353 -0
  11. package/dist/features/openservice/serviceSchedules/calendarSync/functions/planToEvent.d.ts +27 -0
  12. package/dist/features/openservice/serviceSchedules/calendarSync/functions/planToEvent.js +145 -0
  13. package/dist/features/openservice/serviceSchedules/calendarSync/index.d.ts +8 -0
  14. package/dist/features/openservice/serviceSchedules/calendarSync/index.js +28 -0
  15. package/dist/features/openservice/serviceSchedules/calendarSync/service/ServiceCalendarSyncPoller.d.ts +54 -0
  16. package/dist/features/openservice/serviceSchedules/calendarSync/service/ServiceCalendarSyncPoller.js +248 -0
  17. package/dist/features/openservice/serviceSchedules/calendarSync/service/serviceCalendarMeta.d.ts +55 -0
  18. package/dist/features/openservice/serviceSchedules/calendarSync/service/serviceCalendarMeta.js +109 -0
  19. package/dist/features/openservice/serviceSchedules/calendarSync/service/syncPlanToCalendar.d.ts +26 -0
  20. package/dist/features/openservice/serviceSchedules/calendarSync/service/syncPlanToCalendar.js +122 -0
  21. package/dist/features/openservice/serviceSchedules/calendarSync/types/ServiceSyncTypes.d.ts +55 -0
  22. package/dist/features/openservice/serviceSchedules/calendarSync/types/ServiceSyncTypes.js +8 -0
  23. package/dist/features/openservice/serviceSchedules/initServiceSchedulesFeature.d.ts +10 -0
  24. package/dist/features/openservice/serviceSchedules/initServiceSchedulesFeature.js +39 -0
  25. package/dist/features/openservice/serviceTickets/alarmActions/functions/alarmTicketFields.d.ts +35 -0
  26. package/dist/features/openservice/serviceTickets/alarmActions/functions/alarmTicketFields.js +103 -0
  27. package/dist/features/openservice/serviceTickets/alarmActions/index.d.ts +7 -0
  28. package/dist/features/openservice/serviceTickets/alarmActions/index.js +16 -0
  29. package/dist/features/openservice/serviceTickets/alarmActions/service/createAlarmTicket.d.ts +33 -0
  30. package/dist/features/openservice/serviceTickets/alarmActions/service/createAlarmTicket.js +179 -0
  31. package/dist/features/openservice/serviceTickets/alarmActions/service/ensureAlarmTicketAction.d.ts +22 -0
  32. package/dist/features/openservice/serviceTickets/alarmActions/service/ensureAlarmTicketAction.js +127 -0
  33. package/dist/features/openservice/serviceTickets/alarmActions/types/AlarmTicketTypes.d.ts +80 -0
  34. package/dist/features/openservice/serviceTickets/alarmActions/types/AlarmTicketTypes.js +28 -0
  35. package/dist/features/permissions/types/Permissions.d.ts +1 -2
  36. package/dist/features/permissions/types/Permissions.js +8 -2
  37. package/dist/functions/openinc-openservice-alarm-create-ticket.d.ts +25 -0
  38. package/dist/functions/openinc-openservice-alarm-create-ticket.js +63 -0
  39. package/dist/functions/openinc-openservice-notify-contacts.d.ts +16 -0
  40. package/dist/functions/openinc-openservice-notify-contacts.js +82 -0
  41. package/dist/functions/openinc-openservice-schedule-calendar-sync.d.ts +15 -0
  42. package/dist/functions/openinc-openservice-schedule-calendar-sync.js +60 -0
  43. package/dist/hooks/Service_Schedule.js +89 -16
  44. package/dist/hooks/Service_Schedule_Template.js +28 -7
  45. package/dist/hooks/Service_Ticket.js +2 -2
  46. package/dist/types/Service_Schedule.d.ts +12 -0
  47. package/dist/types/Service_Schedule.js +21 -0
  48. package/dist/types/Service_Schedule_Execution.d.ts +6 -3
  49. package/dist/types/Service_Schedule_Execution.js +6 -0
  50. package/dist/types/Service_Schedule_Template.d.ts +7 -0
  51. package/dist/types/Service_Schedule_Template.js +12 -0
  52. package/package.json +5 -4
  53. package/schema/Service_Schedule.json +17 -0
  54. package/schema/Service_Schedule_Execution.json +5 -0
  55. package/schema/Service_Schedule_Template.json +9 -0
  56. package/dist/features/ruleset/applyRuleSet.d.ts +0 -41
  57. package/dist/features/ruleset/applyRuleSet.js +0 -81
@@ -0,0 +1,353 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseRRule = parseRRule;
4
+ exports.buildRRule = buildRRule;
5
+ exports.eventToCadence = eventToCadence;
6
+ exports.calendarUnsupportedReason = calendarUnsupportedReason;
7
+ exports.cadenceToRecurrence = cadenceToRecurrence;
8
+ /**
9
+ * Outlook recurrence ↔ RRULE, both directions.
10
+ *
11
+ * **Why this module exists.** v1 stored a recurrence as `{number, unit}` — every
12
+ * Outlook pattern had to be flattened into "every n days/weeks/months", and
13
+ * anything else ("last Friday of the month") silently became something else. v2
14
+ * stores an RFC 5545 rule, which is the same language Outlook's patterns are a
15
+ * subset of, so the mapping can be lossless in both directions.
16
+ *
17
+ * Everything here is **pure** — no Parse, no Graph client — because a wrong
18
+ * mapping is the one bug in a calendar sync you cannot see until someone misses
19
+ * a maintenance date. `pnpm tsx scripts/…` round-trips it (see the module test
20
+ * at the bottom of the file's docs in OPENSERVICE_V2_SCHEDULES.md).
21
+ *
22
+ * **Where the two vocabularies differ**: Outlook's patterns are a subset of the
23
+ * RRULE grammar. A positional weekday (`BYDAY=+2TU`) maps to `relativeMonthly`
24
+ * and back, but `BYSETPOS` over several weekdays ("last workday"), several
25
+ * days of the month, or several months do not exist there at all —
26
+ * {@link calendarUnsupportedReason} names them, and the sync skips such plans
27
+ * instead of approximating a maintenance date.
28
+ */
29
+ const WEEKDAY_TO_RRULE = {
30
+ Sunday: "SU",
31
+ Monday: "MO",
32
+ Tuesday: "TU",
33
+ Wednesday: "WE",
34
+ Thursday: "TH",
35
+ Friday: "FR",
36
+ Saturday: "SA",
37
+ };
38
+ const RRULE_TO_WEEKDAY = {
39
+ SU: "Sunday",
40
+ MO: "Monday",
41
+ TU: "Tuesday",
42
+ WE: "Wednesday",
43
+ TH: "Thursday",
44
+ FR: "Friday",
45
+ SA: "Saturday",
46
+ };
47
+ /** Outlook's `index` ↔ the RRULE ordinal prefix of a `BYDAY` entry. */
48
+ const INDEX_TO_ORDINAL = {
49
+ first: 1,
50
+ second: 2,
51
+ third: 3,
52
+ fourth: 4,
53
+ last: -1,
54
+ };
55
+ const ORDINAL_TO_INDEX = {
56
+ 1: "first",
57
+ 2: "second",
58
+ 3: "third",
59
+ 4: "fourth",
60
+ [-1]: "last",
61
+ };
62
+ /**
63
+ * Read an RRULE string into its parts.
64
+ *
65
+ * Tolerant on purpose: an unknown key is dropped rather than throwing, because
66
+ * a rule that came from Outlook, from the editor, or from a hand-written import
67
+ * all end up here, and a single unexpected part must not stop a sync.
68
+ */
69
+ function parseRRule(rrule) {
70
+ const parts = {};
71
+ if (!rrule)
72
+ return parts;
73
+ for (const chunk of rrule.replace(/^RRULE:/i, "").split(";")) {
74
+ const [rawKey, rawValue] = chunk.split("=");
75
+ if (!rawKey || !rawValue)
76
+ continue;
77
+ const key = rawKey.trim().toUpperCase();
78
+ const value = rawValue.trim().toUpperCase();
79
+ switch (key) {
80
+ case "FREQ":
81
+ if (value === "DAILY" ||
82
+ value === "WEEKLY" ||
83
+ value === "MONTHLY" ||
84
+ value === "YEARLY") {
85
+ parts.freq = value;
86
+ }
87
+ break;
88
+ case "INTERVAL":
89
+ parts.interval = toPositiveInt(value);
90
+ break;
91
+ case "COUNT":
92
+ parts.count = toPositiveInt(value);
93
+ break;
94
+ case "BYDAY":
95
+ parts.byday = value.split(",").filter(Boolean);
96
+ break;
97
+ case "BYMONTHDAY":
98
+ parts.bymonthday = value.split(",").map(Number).filter(Number.isFinite);
99
+ break;
100
+ case "BYMONTH":
101
+ parts.bymonth = value.split(",").map(Number).filter(Number.isFinite);
102
+ break;
103
+ case "WKST":
104
+ parts.wkst = value;
105
+ break;
106
+ case "BYSETPOS": {
107
+ const parsed = Number(value);
108
+ if (Number.isFinite(parsed) && parsed !== 0)
109
+ parts.bysetpos = parsed;
110
+ break;
111
+ }
112
+ }
113
+ }
114
+ return parts;
115
+ }
116
+ /** Write the parts back into an RRULE string, in the canonical part order. */
117
+ function buildRRule(parts) {
118
+ const chunks = [`FREQ=${parts.freq ?? "DAILY"}`];
119
+ if (parts.interval && parts.interval > 1) {
120
+ chunks.push(`INTERVAL=${parts.interval}`);
121
+ }
122
+ if (parts.byday?.length)
123
+ chunks.push(`BYDAY=${parts.byday.join(",")}`);
124
+ if (parts.bymonthday?.length) {
125
+ chunks.push(`BYMONTHDAY=${parts.bymonthday.join(",")}`);
126
+ }
127
+ if (parts.bymonth?.length)
128
+ chunks.push(`BYMONTH=${parts.bymonth.join(",")}`);
129
+ if (parts.bysetpos)
130
+ chunks.push(`BYSETPOS=${parts.bysetpos}`);
131
+ if (parts.count)
132
+ chunks.push(`COUNT=${parts.count}`);
133
+ if (parts.wkst && parts.wkst !== "MO")
134
+ chunks.push(`WKST=${parts.wkst}`);
135
+ return chunks.join(";");
136
+ }
137
+ /**
138
+ * An Outlook event's timing, as a v2 cadence.
139
+ *
140
+ * A non-recurring event becomes a one-off — the same shape the frontend's
141
+ * `oneOffCadence()` produces (`FREQ=DAILY;INTERVAL=1;COUNT=1`), so a plan
142
+ * created from a single Outlook appointment is indistinguishable from one
143
+ * created by hand.
144
+ */
145
+ function eventToCadence(start, recurrence) {
146
+ const dtstart = start.toISOString();
147
+ if (!recurrence?.pattern) {
148
+ return { kind: "rrule", rrule: "FREQ=DAILY;INTERVAL=1;COUNT=1", dtstart };
149
+ }
150
+ const { pattern, range } = recurrence;
151
+ const parts = { interval: pattern.interval };
152
+ switch (pattern.type) {
153
+ case "daily":
154
+ parts.freq = "DAILY";
155
+ break;
156
+ case "weekly":
157
+ parts.freq = "WEEKLY";
158
+ parts.byday = (pattern.daysOfWeek ?? [])
159
+ .map((day) => WEEKDAY_TO_RRULE[day])
160
+ .filter(Boolean);
161
+ if (pattern.firstDayOfWeek) {
162
+ parts.wkst = WEEKDAY_TO_RRULE[pattern.firstDayOfWeek];
163
+ }
164
+ break;
165
+ case "absoluteMonthly":
166
+ parts.freq = "MONTHLY";
167
+ parts.bymonthday = [pattern.dayOfMonth ?? start.getUTCDate()];
168
+ break;
169
+ case "relativeMonthly":
170
+ parts.freq = "MONTHLY";
171
+ parts.byday = positionalByDay(pattern);
172
+ break;
173
+ case "absoluteYearly":
174
+ parts.freq = "YEARLY";
175
+ parts.bymonth = [pattern.month ?? start.getUTCMonth() + 1];
176
+ parts.bymonthday = [pattern.dayOfMonth ?? start.getUTCDate()];
177
+ break;
178
+ case "relativeYearly":
179
+ parts.freq = "YEARLY";
180
+ parts.bymonth = [pattern.month ?? start.getUTCMonth() + 1];
181
+ parts.byday = positionalByDay(pattern);
182
+ break;
183
+ }
184
+ // A weekly series with no explicit days repeats on the start's weekday.
185
+ if (parts.freq === "WEEKLY" && !parts.byday?.length) {
186
+ parts.byday = [rruleWeekday(start)];
187
+ }
188
+ const cadence = {
189
+ kind: "rrule",
190
+ rrule: buildRRule(parts),
191
+ dtstart,
192
+ };
193
+ if (range?.type === "numbered") {
194
+ cadence.rrule = buildRRule({
195
+ ...parts,
196
+ count: range.numberOfOccurrences,
197
+ });
198
+ }
199
+ else if (range?.type === "endDate" && range.endDate) {
200
+ // Outlook's end date is a plain day and inclusive — the series may still
201
+ // occur on it, so the boundary is the end of that day.
202
+ cadence.until = endOfDay(range.endDate).toISOString();
203
+ }
204
+ return cadence;
205
+ }
206
+ /**
207
+ * Why this cadence cannot be written to a calendar — `undefined` when it can.
208
+ *
209
+ * Outlook's recurrence vocabulary is a **subset** of RFC 5545, and the gap is
210
+ * not cosmetic: a rule we cannot express would have to be approximated, and an
211
+ * approximated maintenance date in someone's calendar is worse than no entry
212
+ * at all. The sync therefore leaves such plans alone instead of guessing (see
213
+ * `syncPlanToCalendar`).
214
+ */
215
+ function calendarUnsupportedReason(cadence) {
216
+ if (cadence?.kind !== "rrule" || !cadence.rrule)
217
+ return undefined;
218
+ const parts = parseRRule(cadence.rrule);
219
+ const days = parts.byday?.map(splitByDay) ?? [];
220
+ if (parts.bysetpos !== undefined && days.length > 1) {
221
+ return `BYSETPOS with ${days.length} weekdays has no Outlook equivalent`;
222
+ }
223
+ if (parts.byday && parts.byday.length > 1 && parts.freq !== "WEEKLY") {
224
+ return `several weekdays are only expressible for a weekly series (FREQ=${parts.freq})`;
225
+ }
226
+ if (parts.bymonthday && parts.bymonthday.length > 1) {
227
+ return "several days of the month have no Outlook equivalent";
228
+ }
229
+ if (parts.bymonth && parts.bymonth.length > 1) {
230
+ return "several months have no Outlook equivalent";
231
+ }
232
+ return undefined;
233
+ }
234
+ /**
235
+ * A v2 cadence as an Outlook recurrence — `undefined` for a one-off, which
236
+ * Graph expects as an event without a `recurrence` at all.
237
+ *
238
+ * `startDate` of the range is always the series start: Graph rejects a
239
+ * recurrence whose range starts on a different day than the event.
240
+ */
241
+ function cadenceToRecurrence(cadence, start) {
242
+ if (cadence?.kind !== "rrule" || !cadence.rrule)
243
+ return undefined;
244
+ const parts = parseRRule(cadence.rrule);
245
+ // One occurrence is not a series — and Outlook has no way to say "once".
246
+ if (parts.count === 1)
247
+ return undefined;
248
+ const pattern = recurrencePattern(parts, start);
249
+ if (!pattern)
250
+ return undefined;
251
+ return { pattern, range: recurrenceRange(parts, cadence, start) };
252
+ }
253
+ function recurrencePattern(parts, start) {
254
+ const interval = parts.interval && parts.interval > 0 ? parts.interval : 1;
255
+ const days = parts.byday?.map(splitByDay) ?? [];
256
+ // A position comes either as an ordinal on the weekday (`+2TU`) or as
257
+ // BYSETPOS next to a single weekday — both are one Outlook `index`.
258
+ const positional = days.find((day) => !!day.ordinal) ??
259
+ (parts.bysetpos !== undefined && days.length === 1
260
+ ? { ordinal: parts.bysetpos, weekday: days[0].weekday }
261
+ : undefined);
262
+ switch (parts.freq ?? "DAILY") {
263
+ case "DAILY":
264
+ return { type: "daily", interval };
265
+ case "WEEKLY": {
266
+ const daysOfWeek = (parts.byday ?? [])
267
+ .map((entry) => RRULE_TO_WEEKDAY[splitByDay(entry).weekday])
268
+ .filter(Boolean);
269
+ return {
270
+ type: "weekly",
271
+ interval,
272
+ daysOfWeek: daysOfWeek.length
273
+ ? daysOfWeek
274
+ : [RRULE_TO_WEEKDAY[rruleWeekday(start)]],
275
+ firstDayOfWeek: RRULE_TO_WEEKDAY[parts.wkst ?? "MO"] ?? "Monday",
276
+ };
277
+ }
278
+ case "MONTHLY":
279
+ if (positional) {
280
+ return {
281
+ type: "relativeMonthly",
282
+ interval,
283
+ daysOfWeek: [RRULE_TO_WEEKDAY[positional.weekday]],
284
+ index: ORDINAL_TO_INDEX[positional.ordinal] ?? "first",
285
+ };
286
+ }
287
+ return {
288
+ type: "absoluteMonthly",
289
+ interval,
290
+ dayOfMonth: parts.bymonthday?.[0] ?? start.getUTCDate(),
291
+ };
292
+ case "YEARLY": {
293
+ const month = parts.bymonth?.[0] ?? start.getUTCMonth() + 1;
294
+ if (positional) {
295
+ return {
296
+ type: "relativeYearly",
297
+ interval,
298
+ month,
299
+ daysOfWeek: [RRULE_TO_WEEKDAY[positional.weekday]],
300
+ index: ORDINAL_TO_INDEX[positional.ordinal] ?? "first",
301
+ };
302
+ }
303
+ return {
304
+ type: "absoluteYearly",
305
+ interval,
306
+ month,
307
+ dayOfMonth: parts.bymonthday?.[0] ?? start.getUTCDate(),
308
+ };
309
+ }
310
+ }
311
+ return undefined;
312
+ }
313
+ function recurrenceRange(parts, cadence, start) {
314
+ const startDate = graphDate(start);
315
+ if (parts.count && parts.count > 1) {
316
+ return { type: "numbered", startDate, numberOfOccurrences: parts.count };
317
+ }
318
+ if (cadence.until) {
319
+ return { type: "endDate", startDate, endDate: graphDate(cadence.until) };
320
+ }
321
+ return { type: "noEnd", startDate };
322
+ }
323
+ /** `["+2TU"]` for "second Tuesday" — the RRULE spelling of Outlook's `index`. */
324
+ function positionalByDay(pattern) {
325
+ const weekday = WEEKDAY_TO_RRULE[pattern.daysOfWeek?.[0] ?? "Monday"];
326
+ const ordinal = INDEX_TO_ORDINAL[pattern.index ?? "first"] ?? 1;
327
+ return [`${ordinal > 0 ? "+" : ""}${ordinal}${weekday}`];
328
+ }
329
+ /** `"+2TU"` → `{ ordinal: 2, weekday: "TU" }`, `"TU"` → `{ weekday: "TU" }`. */
330
+ function splitByDay(entry) {
331
+ const match = /^([+-]?\d+)?([A-Z]{2})$/.exec(entry.trim().toUpperCase());
332
+ if (!match)
333
+ return { weekday: "MO" };
334
+ return {
335
+ ordinal: match[1] ? Number(match[1]) : undefined,
336
+ weekday: match[2],
337
+ };
338
+ }
339
+ function rruleWeekday(date) {
340
+ return ["SU", "MO", "TU", "WE", "TH", "FR", "SA"][date.getUTCDay()];
341
+ }
342
+ /** Graph wants a plain `YYYY-MM-DD` in a recurrence range. */
343
+ function graphDate(value) {
344
+ const date = value instanceof Date ? value : new Date(value);
345
+ return date.toISOString().slice(0, 10);
346
+ }
347
+ function endOfDay(day) {
348
+ return new Date(`${day.slice(0, 10)}T23:59:59.999Z`);
349
+ }
350
+ function toPositiveInt(value) {
351
+ const parsed = Number(value);
352
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
353
+ }
@@ -0,0 +1,27 @@
1
+ import type { GraphEvent } from "../../../schedules/calendarSync/types/Event.js";
2
+ import type { ServiceCalendarEventSnapshot } from "../service/serviceCalendarMeta.js";
3
+ import type { ServiceCadence, ServiceNotificationRule } from "../types/ServiceSyncTypes.js";
4
+ /** A step of the plan, as far as the calendar entry shows it. */
5
+ type PlanStep = {
6
+ order?: number;
7
+ title?: string;
8
+ };
9
+ export type PlanEventInput = {
10
+ title?: string;
11
+ cadence?: ServiceCadence;
12
+ steps?: PlanStep[];
13
+ notifications?: ServiceNotificationRule[];
14
+ /** Machine name — becomes the subject prefix. */
15
+ equipmentName?: string;
16
+ /** The event as it currently stands in Outlook, if it exists already. */
17
+ snapshot?: ServiceCalendarEventSnapshot;
18
+ };
19
+ /**
20
+ * A maintenance plan as a calendar event.
21
+ *
22
+ * Returns `undefined` for a plan that has no date to put in a calendar — a
23
+ * manual, sensor- or meter-driven plan has no due date until something happens,
24
+ * and inventing one would show maintenance that nobody scheduled.
25
+ */
26
+ export declare function planToEvent(plan: PlanEventInput): GraphEvent | undefined;
27
+ export {};
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.planToEvent = planToEvent;
7
+ const dayjs_1 = __importDefault(require("dayjs"));
8
+ const outlookRecurrence_js_1 = require("./outlookRecurrence.js");
9
+ /**
10
+ * A maintenance plan as a calendar event.
11
+ *
12
+ * Returns `undefined` for a plan that has no date to put in a calendar — a
13
+ * manual, sensor- or meter-driven plan has no due date until something happens,
14
+ * and inventing one would show maintenance that nobody scheduled.
15
+ */
16
+ function planToEvent(plan) {
17
+ const start = cadenceStart(plan.cadence);
18
+ if (!start)
19
+ return undefined;
20
+ const timed = timeOfDay(plan, start);
21
+ const event = {
22
+ subject: eventSubject(plan),
23
+ body: { contentType: "Text", content: stepsAsText(plan.steps) },
24
+ start: graphStart(start, timed),
25
+ end: graphEnd(start, timed),
26
+ isAllDay: timed ? undefined : true,
27
+ isReminderOn: false,
28
+ };
29
+ const reminder = reminderMinutes(plan.notifications);
30
+ if (reminder !== undefined) {
31
+ event.isReminderOn = true;
32
+ event.reminderMinutesBeforeStart = reminder;
33
+ }
34
+ const recurrence = (0, outlookRecurrence_js_1.cadenceToRecurrence)(plan.cadence, start);
35
+ if (recurrence)
36
+ event.recurrence = recurrence;
37
+ return event;
38
+ }
39
+ /**
40
+ * The instant the series starts.
41
+ *
42
+ * Only an RRULE cadence has one. `until` is not consulted here: it bounds the
43
+ * series (the recurrence range does that), it does not start it.
44
+ */
45
+ function cadenceStart(cadence) {
46
+ if (cadence?.kind !== "rrule" || !cadence.dtstart)
47
+ return undefined;
48
+ const start = new Date(cadence.dtstart);
49
+ return Number.isNaN(start.getTime()) ? undefined : start;
50
+ }
51
+ /**
52
+ * The time of day the event should keep, as `{hour, minute}` — or `undefined`
53
+ * for an all-day event.
54
+ *
55
+ * Two sources, in this order:
56
+ *
57
+ * 1. the plan's own `dtstart`, if it names a time (not UTC midnight) — the plan
58
+ * is the leading side for anything the plan can express;
59
+ * 2. otherwise the time the **event** has in Outlook, so a user who dragged a
60
+ * maintenance appointment to 9:00 keeps it. A plan whose due date shifts
61
+ * then moves the appointment to the new *day* at the same time — which is
62
+ * where v1 went wrong: it kept the whole original start and quietly ignored
63
+ * the new date.
64
+ */
65
+ function timeOfDay(plan, start) {
66
+ if (start.getUTCHours() !== 0 || start.getUTCMinutes() !== 0) {
67
+ return { hour: start.getUTCHours(), minute: start.getUTCMinutes() };
68
+ }
69
+ const snapshot = plan.snapshot;
70
+ if (!snapshot || snapshot.isAllDay || !snapshot.start?.dateTime) {
71
+ return undefined;
72
+ }
73
+ const previous = new Date(snapshot.start.dateTime.endsWith("Z")
74
+ ? snapshot.start.dateTime
75
+ : `${snapshot.start.dateTime.slice(0, 23)}Z`);
76
+ if (Number.isNaN(previous.getTime()))
77
+ return undefined;
78
+ if (previous.getUTCHours() === 0 && previous.getUTCMinutes() === 0) {
79
+ return undefined;
80
+ }
81
+ return { hour: previous.getUTCHours(), minute: previous.getUTCMinutes() };
82
+ }
83
+ function graphStart(start, timed) {
84
+ if (!timed) {
85
+ return { dateTime: dateOnly(start), timeZone: "UTC" };
86
+ }
87
+ return { dateTime: atTime(start, timed), timeZone: "UTC" };
88
+ }
89
+ function graphEnd(start, timed) {
90
+ if (!timed) {
91
+ // An all-day event ends on the following day — Graph's half-open interval.
92
+ return {
93
+ dateTime: dateOnly((0, dayjs_1.default)(start).add(1, "day").toDate()),
94
+ timeZone: "UTC",
95
+ };
96
+ }
97
+ const end = (0, dayjs_1.default)(atTime(start, timed)).add(1, "hour");
98
+ return { dateTime: end.format("YYYY-MM-DDTHH:mm:ss"), timeZone: "UTC" };
99
+ }
100
+ function dateOnly(date) {
101
+ return date.toISOString().slice(0, 10);
102
+ }
103
+ function atTime(date, timed) {
104
+ return `${dateOnly(date)}T${pad(timed.hour)}:${pad(timed.minute)}:00`;
105
+ }
106
+ function pad(value) {
107
+ return String(value).padStart(2, "0");
108
+ }
109
+ /**
110
+ * `"Flex 1" - Ölwechsel` — the convention the inbound direction reads back
111
+ * ([`parseSubject`](./eventToPlan.ts)), so a plan created here and edited in
112
+ * Outlook keeps its machine.
113
+ */
114
+ function eventSubject(plan) {
115
+ const title = plan.title?.trim() || "Wartung";
116
+ return plan.equipmentName ? `"${plan.equipmentName}" - ${title}` : title;
117
+ }
118
+ /**
119
+ * The checklist as the event's note.
120
+ *
121
+ * The point of the appointment is that someone reads it in Outlook and knows
122
+ * what to bring — a title alone rarely says that.
123
+ */
124
+ function stepsAsText(steps) {
125
+ const lines = [...(steps ?? [])]
126
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
127
+ .map((step) => step.title?.trim())
128
+ .filter((title) => !!title)
129
+ .map((title) => `• ${title}`);
130
+ return lines.join("\n");
131
+ }
132
+ /**
133
+ * The plan's earliest warning as Outlook reminder minutes.
134
+ *
135
+ * A plan can warn several groups at different times; a calendar event has one
136
+ * reminder. The earliest one is the useful one — it is the moment the work
137
+ * first needs attention. `0` (reminder on the due date) is deliberate, not
138
+ * "no reminder": `undefined` means the plan warns nobody.
139
+ */
140
+ function reminderMinutes(notifications) {
141
+ if (!notifications?.length)
142
+ return undefined;
143
+ const offsets = notifications.map((rule) => rule.trigger === "before" ? Math.max(0, rule.offsetDays ?? 0) : 0);
144
+ return Math.max(...offsets) * 24 * 60;
145
+ }
@@ -0,0 +1,8 @@
1
+ export { eventToPlanFields, mergeReminderRule, OUTLOOK_REMINDER_RULE_ID, parseSubject, } from "./functions/eventToPlan.js";
2
+ export { buildRRule, cadenceToRecurrence, eventToCadence, parseRRule, } from "./functions/outlookRecurrence.js";
3
+ export { planToEvent } from "./functions/planToEvent.js";
4
+ export { findMetaForEvent, findMetaForPlan, SERVICE_CALENDAR_SYNC_CONTEXT, ServiceCalendarMetaConfig, } from "./service/serviceCalendarMeta.js";
5
+ export { CALENDAR_SYNC_CONTEXT_FLAG, ServiceCalendarSyncPoller, } from "./service/ServiceCalendarSyncPoller.js";
6
+ export { connectionForTenant, syncPlanToCalendar, } from "./service/syncPlanToCalendar.js";
7
+ export { OUTLOOK_CONFIG_KEY } from "./types/ServiceSyncTypes.js";
8
+ export type { ServiceCadence, ServiceNotificationRule, ServiceSyncConfigValues, } from "./types/ServiceSyncTypes.js";
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OUTLOOK_CONFIG_KEY = exports.syncPlanToCalendar = exports.connectionForTenant = exports.ServiceCalendarSyncPoller = exports.CALENDAR_SYNC_CONTEXT_FLAG = exports.ServiceCalendarMetaConfig = exports.SERVICE_CALENDAR_SYNC_CONTEXT = exports.findMetaForPlan = exports.findMetaForEvent = exports.planToEvent = exports.parseRRule = exports.eventToCadence = exports.cadenceToRecurrence = exports.buildRRule = exports.parseSubject = exports.OUTLOOK_REMINDER_RULE_ID = exports.mergeReminderRule = exports.eventToPlanFields = void 0;
4
+ var eventToPlan_js_1 = require("./functions/eventToPlan.js");
5
+ Object.defineProperty(exports, "eventToPlanFields", { enumerable: true, get: function () { return eventToPlan_js_1.eventToPlanFields; } });
6
+ Object.defineProperty(exports, "mergeReminderRule", { enumerable: true, get: function () { return eventToPlan_js_1.mergeReminderRule; } });
7
+ Object.defineProperty(exports, "OUTLOOK_REMINDER_RULE_ID", { enumerable: true, get: function () { return eventToPlan_js_1.OUTLOOK_REMINDER_RULE_ID; } });
8
+ Object.defineProperty(exports, "parseSubject", { enumerable: true, get: function () { return eventToPlan_js_1.parseSubject; } });
9
+ var outlookRecurrence_js_1 = require("./functions/outlookRecurrence.js");
10
+ Object.defineProperty(exports, "buildRRule", { enumerable: true, get: function () { return outlookRecurrence_js_1.buildRRule; } });
11
+ Object.defineProperty(exports, "cadenceToRecurrence", { enumerable: true, get: function () { return outlookRecurrence_js_1.cadenceToRecurrence; } });
12
+ Object.defineProperty(exports, "eventToCadence", { enumerable: true, get: function () { return outlookRecurrence_js_1.eventToCadence; } });
13
+ Object.defineProperty(exports, "parseRRule", { enumerable: true, get: function () { return outlookRecurrence_js_1.parseRRule; } });
14
+ var planToEvent_js_1 = require("./functions/planToEvent.js");
15
+ Object.defineProperty(exports, "planToEvent", { enumerable: true, get: function () { return planToEvent_js_1.planToEvent; } });
16
+ var serviceCalendarMeta_js_1 = require("./service/serviceCalendarMeta.js");
17
+ Object.defineProperty(exports, "findMetaForEvent", { enumerable: true, get: function () { return serviceCalendarMeta_js_1.findMetaForEvent; } });
18
+ Object.defineProperty(exports, "findMetaForPlan", { enumerable: true, get: function () { return serviceCalendarMeta_js_1.findMetaForPlan; } });
19
+ Object.defineProperty(exports, "SERVICE_CALENDAR_SYNC_CONTEXT", { enumerable: true, get: function () { return serviceCalendarMeta_js_1.SERVICE_CALENDAR_SYNC_CONTEXT; } });
20
+ Object.defineProperty(exports, "ServiceCalendarMetaConfig", { enumerable: true, get: function () { return serviceCalendarMeta_js_1.ServiceCalendarMetaConfig; } });
21
+ var ServiceCalendarSyncPoller_js_1 = require("./service/ServiceCalendarSyncPoller.js");
22
+ Object.defineProperty(exports, "CALENDAR_SYNC_CONTEXT_FLAG", { enumerable: true, get: function () { return ServiceCalendarSyncPoller_js_1.CALENDAR_SYNC_CONTEXT_FLAG; } });
23
+ Object.defineProperty(exports, "ServiceCalendarSyncPoller", { enumerable: true, get: function () { return ServiceCalendarSyncPoller_js_1.ServiceCalendarSyncPoller; } });
24
+ var syncPlanToCalendar_js_1 = require("./service/syncPlanToCalendar.js");
25
+ Object.defineProperty(exports, "connectionForTenant", { enumerable: true, get: function () { return syncPlanToCalendar_js_1.connectionForTenant; } });
26
+ Object.defineProperty(exports, "syncPlanToCalendar", { enumerable: true, get: function () { return syncPlanToCalendar_js_1.syncPlanToCalendar; } });
27
+ var ServiceSyncTypes_js_1 = require("./types/ServiceSyncTypes.js");
28
+ Object.defineProperty(exports, "OUTLOOK_CONFIG_KEY", { enumerable: true, get: function () { return ServiceSyncTypes_js_1.OUTLOOK_CONFIG_KEY; } });
@@ -0,0 +1,54 @@
1
+ import { Config } from "../../../../../types/index.js";
2
+ /**
3
+ * Marker on a save that came from the calendar.
4
+ *
5
+ * The outbound side (`syncPlanToCalendar`, called from the `Service_Schedule`
6
+ * afterSave hook) checks it and stays quiet — otherwise every inbound change
7
+ * would be written straight back to Outlook, produce a new `changeKey`, come
8
+ * back through the delta feed as a foreign change, and loop forever. The stored
9
+ * `changeKey` is the second line of defence; this is the first, and unlike v1's
10
+ * `!master` check it does not disable outbound sync for every other
11
+ * master-key writer (the scheduler pausing a plan, a migration, a script).
12
+ */
13
+ export declare const CALENDAR_SYNC_CONTEXT_FLAG = "fromCalendarSync";
14
+ /**
15
+ * Pulls Outlook changes into open.SERVICE **v2** maintenance plans.
16
+ *
17
+ * One poller per configured connection, driven by the Graph *delta* feed: the
18
+ * first call reads the whole calendar, each later call only what changed, and
19
+ * the cursor lives in the Config row so a restart resumes instead of
20
+ * re-importing. The v1 poller does the same for `Maintenance_Schedule` from the
21
+ * same connection — the two keep separate cursors (`delta_link` vs
22
+ * `delta_link_v2`) so neither consumes the other's changes.
23
+ */
24
+ export declare class ServiceCalendarSyncPoller {
25
+ private timer;
26
+ private config;
27
+ constructor(config: Config);
28
+ private get values();
29
+ private get manager();
30
+ start(intervalMs?: number): this;
31
+ stop(): void;
32
+ private poll;
33
+ private processEvent;
34
+ private createPlan;
35
+ private updatePlan;
36
+ /**
37
+ * A deleted appointment archives the plan — it never destroys it.
38
+ *
39
+ * Executions document that work happened; deleting the plan they point at
40
+ * would strand that history (the `beforeDelete` hook refuses it, too). The
41
+ * meta entry goes, so the same plan can be linked to a new event later.
42
+ */
43
+ private handleRemoved;
44
+ /** The machine named in the subject, else the configured fallback. */
45
+ private resolveEquipment;
46
+ private findEquipmentByLabel;
47
+ /**
48
+ * The cursor is re-read from the Config row on every tick, and written back
49
+ * after it: the row is also the admin form's storage, so it can change
50
+ * underneath us.
51
+ */
52
+ private loadDeltaLink;
53
+ private saveDeltaLink;
54
+ }