@fabriktor/fx 0.0.149 → 0.0.151

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.
@@ -14,19 +14,24 @@ export function newChatData(type, payload) {
14
14
  type,
15
15
  payload,
16
16
  };
17
- if (!validChatData(data))
18
- throw errValidation({ msg: msgChatData });
19
- return structuredClone(data);
17
+ // JSON snapshots also accept reactive UI objects without structuredClone's
18
+ // Proxy failure. Validation and size limits are shared with sending.
19
+ return JSON.parse(serializeChatData(data));
20
20
  }
21
21
  export function encodeChatContent(text, data) {
22
22
  if (!data)
23
23
  return text;
24
- if (!text.trim() || text.includes(chatDataMarker) || !validChatData(data))
24
+ if (!text.trim() || text.includes(chatDataMarker))
25
+ throw errValidation({ msg: msgChatData });
26
+ return text.trim() + chatDataMarker + serializeChatData(data);
27
+ }
28
+ function serializeChatData(data) {
29
+ if (!validChatData(data))
25
30
  throw errValidation({ msg: msgChatData });
26
31
  const value = JSON.stringify(data);
27
32
  if (new TextEncoder().encode(value).length > chatDataMaximumBytes)
28
33
  throw errValidation({ msg: msgChatData });
29
- return text.trim() + chatDataMarker + value;
34
+ return value;
30
35
  }
31
36
  export function decodeChatContent(value) {
32
37
  const boundary = value.indexOf(chatDataMarker);
@@ -1,9 +1,16 @@
1
1
  import type { User } from "@fabriktor/schema";
2
2
  import type { EmploymentActor } from "../contacts/scope.js";
3
+ import { type ChatData, type ChatJSON } from "./data.js";
3
4
  export type ChatFormRecipient = Pick<User, "id" | "account_identity">;
4
5
  export type ChatFormPolicy = {
5
6
  readonly sender_modes: readonly User["active_mode"][];
6
7
  readonly recipient_modes: readonly User["active_mode"][];
7
8
  readonly allow_group: boolean;
8
9
  };
10
+ export type ChatFormContextDefinition<T extends ChatJSON> = {
11
+ readonly version: number;
12
+ readonly read: (value: ChatJSON | undefined) => T | undefined;
13
+ };
14
+ export declare function newChatFormData<T extends ChatJSON>(type: string, payload: ChatData["payload"], definition: ChatFormContextDefinition<T>, context: T): ChatData;
15
+ export declare function readChatFormContext<T extends ChatJSON>(data: ChatData, definition: ChatFormContextDefinition<T>): T;
9
16
  export declare function canSendChatForm(policy: ChatFormPolicy, actor: EmploymentActor, recipients: readonly ChatFormRecipient[]): boolean;
@@ -1,5 +1,29 @@
1
+ import { errValidation } from "../core/err.js";
1
2
  import { normalizeIDs, resolvedID } from "../core/id.js";
3
+ import { newChatData } from "./data.js";
2
4
  import { isFabriktorUser, isOtherUserID, userSupportsMode, } from "../users/users.js";
5
+ const msgFormContext = "The form details are missing or unsupported. Ask the sender to send a new form.";
6
+ export function newChatFormData(type, payload, definition, context) {
7
+ const value = definition.read(context);
8
+ if (value === undefined)
9
+ throw errValidation({ msg: msgFormContext });
10
+ return newChatData(type, {
11
+ ...payload,
12
+ form_context: { version: definition.version, data: value },
13
+ });
14
+ }
15
+ export function readChatFormContext(data, definition) {
16
+ const context = data.payload.form_context;
17
+ if (context &&
18
+ typeof context === "object" &&
19
+ !Array.isArray(context) &&
20
+ context.version === definition.version) {
21
+ const value = definition.read(context.data);
22
+ if (value !== undefined)
23
+ return value;
24
+ }
25
+ throw errValidation({ msg: msgFormContext });
26
+ }
3
27
  // Recipients are all other members of the conversation, resolved before use.
4
28
  // Sender modes apply now; recipient modes describe what their identity supports.
5
29
  // Do not depend on the recipient's current mode, which Public() omits.
@@ -12,6 +12,7 @@ import { runPreActionCheck } from "../core/pre-action.js";
12
12
  import { runOwnerSearch, runSearch, withOwnerQuery, } from "../core/search.js";
13
13
  const msgJobIDRequired = "Job ID is required.";
14
14
  const msgLocationIDRequired = "Location ID is required.";
15
+ const msgLocationClientRequired = "Choose a client for this location.";
15
16
  const msgOwnerIDRequired = "Owner ID is required.";
16
17
  const msgMemberIDRequired = "Member ID is required.";
17
18
  const msgJobLocationMismatch = "Job location does not match the provided parent location.";
@@ -59,9 +60,11 @@ export class JobsFX {
59
60
  this.now = opts.now ?? (() => new globalThis.Date());
60
61
  }
61
62
  async createLocation(opts) {
63
+ const client_id = requiredID(opts.in.client_id, "client_id", msgLocationClientRequired);
62
64
  const out = await this.jobs.insertOneLocation({
63
65
  in: {
64
66
  ...opts.in,
67
+ client_id,
65
68
  is_archived: false,
66
69
  current_members: normalizeIDs(opts.in.current_members),
67
70
  },
@@ -72,8 +75,12 @@ export class JobsFX {
72
75
  async updateLocation(opts) {
73
76
  const owner_id = requiredID(opts.owner_id, "owner_id", msgOwnerIDRequired);
74
77
  const id = requiredID(opts.location.id, "location", msgLocationIDRequired);
78
+ const client_id = requiredID(opts.in.client_id === undefined
79
+ ? opts.location.client_id
80
+ : opts.in.client_id, "client_id", msgLocationClientRequired);
75
81
  const input = {
76
82
  ...opts.in,
83
+ client_id,
77
84
  ...locationAddressInput(opts.location),
78
85
  };
79
86
  if (opts.in.current_members !== undefined) {
@@ -2,7 +2,7 @@ import type { JobsOperator, RoutesOperator, UsersOperator } from "@fabriktor/cli
2
2
  import { type ClientAvailability, type InClientAvailability, type Location, type PrefCompany, type User } from "@fabriktor/schema";
3
3
  import type { PreferencesFXOperator } from "../preferences/preferences.js";
4
4
  import { type ChatData } from "../chat/data.js";
5
- import { type ChatFormRecipient } from "../chat/forms.js";
5
+ import { type ChatFormContextDefinition, type ChatFormRecipient } from "../chat/forms.js";
6
6
  import { type RoutesActor } from "./workspace.js";
7
7
  export declare const clientAvailabilityMinimumWindows = 3;
8
8
  export declare const availabilityRequestPolicy: {
@@ -21,23 +21,39 @@ export type AvailabilityContext = {
21
21
  location: Location;
22
22
  employer: User;
23
23
  client: User;
24
- company: PrefCompany;
24
+ working_hours?: AvailabilityWorkingHours;
25
25
  timezone: string;
26
26
  };
27
27
  export type AvailabilityReaders = {
28
28
  jobs: Pick<JobsOperator, "findOneLocation">;
29
29
  users: Pick<UsersOperator, "findOneUser">;
30
- preferences: Pick<PreferencesFXOperator, "getPrefCompany">;
30
+ };
31
+ export type AvailabilityWorkingHours = Pick<PrefCompany, "work_day_start_hour" | "work_day_end_hour">;
32
+ export type AvailabilityFormContext = {
33
+ working_hours: AvailabilityWorkingHours;
34
+ timezone: Location["address_timezone"];
35
+ };
36
+ export declare const availabilityFormContextDefinition: ChatFormContextDefinition<AvailabilityFormContext>;
37
+ export type AvailabilityContextOptions = {
38
+ request?: ChatData;
39
+ signal?: AbortSignal;
31
40
  };
32
41
  export declare function availabilityReference(data: ChatData): AvailabilityReference | undefined;
33
42
  export declare function availabilityRequest(location: Location, actor: RoutesActor): ChatData;
34
43
  export declare function canSendAvailabilityRequest(data: ChatData, actor: RoutesActor, recipients: readonly ChatFormRecipient[]): boolean;
44
+ export declare function loadAvailabilityRequestContext(readers: AvailabilityReaders & {
45
+ preferences: Pick<PreferencesFXOperator, "getPrefCompany">;
46
+ }, actor: RoutesActor, data: ChatData): Promise<AvailabilityFormContext>;
35
47
  export declare function availabilitySubmission(reference: AvailabilityReference, request_id: string, ids: readonly string[]): ChatData;
36
- export declare function loadAvailabilityContext(readers: AvailabilityReaders, actor: RoutesActor, reference: AvailabilityReference, signal?: AbortSignal): Promise<AvailabilityContext>;
48
+ export declare function loadAvailabilityContext(readers: AvailabilityReaders, actor: RoutesActor, reference: AvailabilityReference, { request, signal }?: AvailabilityContextOptions): Promise<AvailabilityContext>;
49
+ type AvailabilityWindow = Pick<InClientAvailability, "from" | "to" | "timezone">;
50
+ export declare function isValidAvailabilityWindow(window: AvailabilityWindow): boolean;
51
+ export declare function availabilityWindowConflicts(inputs: readonly AvailabilityWindow[]): number[];
37
52
  export declare function validateAvailabilityWindows(inputs: readonly InClientAvailability[], context: AvailabilityContext, minimum?: number): void;
38
53
  export declare function loadAvailabilitySelection(readers: AvailabilityReaders & {
39
54
  routes: Pick<RoutesOperator, "findOneClientAvailability">;
40
- }, actor: RoutesActor, reference: AvailabilityReference, ids: readonly string[], signal?: AbortSignal): Promise<{
55
+ }, actor: RoutesActor, reference: AvailabilityReference, ids: readonly string[], options?: AvailabilityContextOptions): Promise<{
41
56
  context: AvailabilityContext;
42
57
  windows: ClientAvailability[];
43
58
  }>;
59
+ export {};
@@ -1,11 +1,11 @@
1
1
  import { UserTypeClient, UserTypeEmployer, } from "@fabriktor/schema";
2
2
  import { isFabriktorUser } from "../users/users.js";
3
3
  import { addressTimezone } from "../core/address.js";
4
- import { hoursPerDay, minutesOfDay, minutesPerHour, sameCalendarDay, } from "../core/date.js";
4
+ import { hoursPerDay, isValidInputDate, minutesOfDay, minutesPerHour, sameCalendarDay, } from "../core/date.js";
5
5
  import { errValidation } from "../core/err.js";
6
6
  import { normalizeIDs, resolvedID } from "../core/id.js";
7
7
  import { newChatData } from "../chat/data.js";
8
- import { canSendChatForm, } from "../chat/forms.js";
8
+ import { canSendChatForm, readChatFormContext, } from "../chat/forms.js";
9
9
  import { routeCanPlan } from "./workspace.js";
10
10
  import { validateClientAvailabilityInput } from "./validation.js";
11
11
  export const clientAvailabilityMinimumWindows = 3;
@@ -18,10 +18,37 @@ export const RoutesChatDataType = {
18
18
  AvailabilityRequest: "routes.availability.request",
19
19
  AvailabilitySubmitted: "routes.availability.submitted",
20
20
  };
21
+ export const availabilityFormContextDefinition = {
22
+ version: 1,
23
+ read: (value) => {
24
+ if (!value || typeof value !== "object" || Array.isArray(value))
25
+ return;
26
+ const { working_hours, timezone } = value;
27
+ if (!working_hours ||
28
+ typeof working_hours !== "object" ||
29
+ Array.isArray(working_hours) ||
30
+ typeof timezone !== "string" ||
31
+ !timezone.trim())
32
+ return;
33
+ const { work_day_start_hour: start, work_day_end_hour: end } = working_hours;
34
+ if (typeof start !== "number" ||
35
+ typeof end !== "number" ||
36
+ ![start, end].every((hour) => Number.isInteger(hour) && hour >= 0 && hour < hoursPerDay) ||
37
+ start >= end)
38
+ return;
39
+ return {
40
+ timezone,
41
+ working_hours: { work_day_start_hour: start, work_day_end_hour: end },
42
+ };
43
+ },
44
+ };
21
45
  const msgAvailabilityAccess = "This availability does not match the client and employer of the location.";
22
46
  const msgAvailabilityTimezone = "The location needs a valid saved address timezone.";
47
+ const msgAvailabilityTimezoneChanged = "The location timezone has changed. Ask the sender to send a new form.";
23
48
  const msgAvailabilityHours = "The employer's working hours must form a valid same-day window.";
24
- const msgAvailabilityWindows = "Provide enough distinct, non-overlapping availability windows within the employer's working hours.";
49
+ const msgAvailabilityMinimumWindows = "Choose at least {count} possible visit times.";
50
+ const msgAvailabilityWindows = "Some visit times overlap. Choose another day or change their hours.";
51
+ const msgAvailabilityOutsideHours = "Choose visit times within the working hours shown.";
25
52
  const msgAvailabilityClient = "Visit times can only be requested from clients with a Fabriktor account. Arrange external contacts’ visit times directly.";
26
53
  const msgAvailabilityData = "The availability request is invalid.";
27
54
  export function availabilityReference(data) {
@@ -69,6 +96,30 @@ export function canSendAvailabilityRequest(data, actor, recipients) {
69
96
  canSendChatForm(availabilityRequestPolicy, actor, recipients) &&
70
97
  reference.client_id === recipients[0].id);
71
98
  }
99
+ // Registry pre-action: only the employer preparing the request reads their
100
+ // preferences. Transfer the two hours, never the complete preferences record.
101
+ export async function loadAvailabilityRequestContext(readers, actor, data) {
102
+ const reference = availabilityReference(data);
103
+ if (data.type !== RoutesChatDataType.AvailabilityRequest ||
104
+ !reference ||
105
+ !routeCanPlan(actor) ||
106
+ reference.employer_id !== actor.owner_id)
107
+ throw errValidation({ msg: msgAvailabilityAccess });
108
+ const [context, company] = await Promise.all([
109
+ loadAvailabilityContext(readers, actor, reference),
110
+ readers.preferences.getPrefCompany({ owner_id: actor.owner_id }),
111
+ ]);
112
+ const supplied = availabilityFormContextDefinition.read({
113
+ timezone: context.timezone,
114
+ working_hours: {
115
+ work_day_start_hour: company.work_day_start_hour,
116
+ work_day_end_hour: company.work_day_end_hour,
117
+ },
118
+ });
119
+ if (!supplied)
120
+ throw errValidation({ msg: msgAvailabilityHours });
121
+ return supplied;
122
+ }
72
123
  export function availabilitySubmission(reference, request_id, ids) {
73
124
  const data = newChatData(RoutesChatDataType.AvailabilitySubmitted, {
74
125
  ...reference,
@@ -79,7 +130,19 @@ export function availabilitySubmission(reference, request_id, ids) {
79
130
  throw errValidation({ msg: msgAvailabilityData });
80
131
  return data;
81
132
  }
82
- export async function loadAvailabilityContext(readers, actor, reference, signal) {
133
+ export async function loadAvailabilityContext(readers, actor, reference, { request, signal } = {}) {
134
+ const supplied = request
135
+ ? readChatFormContext(request, availabilityFormContextDefinition)
136
+ : undefined;
137
+ if (request) {
138
+ const requestReference = availabilityReference(request);
139
+ if (request.type !== RoutesChatDataType.AvailabilityRequest ||
140
+ !requestReference ||
141
+ requestReference.employer_id !== reference.employer_id ||
142
+ requestReference.client_id !== reference.client_id ||
143
+ requestReference.location_id !== reference.location_id)
144
+ throw errValidation({ msg: msgAvailabilityAccess });
145
+ }
83
146
  const location = (await readers.jobs.findOneLocation({ id: reference.location_id, signal })).value;
84
147
  if (!location ||
85
148
  location.id !== reference.location_id ||
@@ -90,10 +153,9 @@ export async function loadAvailabilityContext(readers, actor, reference, signal)
90
153
  ? actor.owner_id !== reference.client_id
91
154
  : !routeCanPlan(actor) || actor.owner_id !== reference.employer_id))
92
155
  throw errValidation({ msg: msgAvailabilityAccess });
93
- const [result, clientResult, company] = await Promise.all([
156
+ const [result, clientResult] = await Promise.all([
94
157
  readers.users.findOneUser({ id: reference.employer_id, signal }),
95
158
  readers.users.findOneUser({ id: reference.client_id, signal }),
96
- readers.preferences.getPrefCompany({ owner_id: reference.employer_id }),
97
159
  ]);
98
160
  const employer = result.value;
99
161
  if (!employer ||
@@ -108,41 +170,77 @@ export async function loadAvailabilityContext(readers, actor, reference, signal)
108
170
  const timezone = addressTimezone(location);
109
171
  if (!timezone)
110
172
  throw errValidation({ msg: msgAvailabilityTimezone });
111
- const { work_day_start_hour: start, work_day_end_hour: end } = company;
112
- if (![start, end].every((hour) => Number.isInteger(hour) && hour >= 0 && hour < hoursPerDay) ||
113
- start >= end)
114
- throw errValidation({ msg: msgAvailabilityHours });
115
- return { location, employer, client, company, timezone };
173
+ if (supplied && supplied.timezone !== timezone)
174
+ throw errValidation({ msg: msgAvailabilityTimezoneChanged });
175
+ return {
176
+ location,
177
+ employer,
178
+ client,
179
+ working_hours: supplied?.working_hours,
180
+ timezone,
181
+ };
182
+ }
183
+ export function isValidAvailabilityWindow(window) {
184
+ return (isValidInputDate(window.from) &&
185
+ isValidInputDate(window.to) &&
186
+ window.from.tz === window.timezone &&
187
+ window.to.tz === window.timezone &&
188
+ sameCalendarDay(window.from, window.to) &&
189
+ minutesOfDay(window.from) < minutesOfDay(window.to));
190
+ }
191
+ // Return every affected row, including duplicates. Incomplete rows are handled
192
+ // by the inputs' required/date/time rules, not by the overlap rule.
193
+ export function availabilityWindowConflicts(inputs) {
194
+ const conflicts = new Set();
195
+ inputs.forEach((input, index) => {
196
+ if (!isValidAvailabilityWindow(input))
197
+ return;
198
+ inputs.slice(0, index).forEach((other, previous) => {
199
+ if (isValidAvailabilityWindow(other) &&
200
+ input.timezone === other.timezone &&
201
+ sameCalendarDay(input.from, other.from) &&
202
+ minutesOfDay(input.from) < minutesOfDay(other.to) &&
203
+ minutesOfDay(input.to) > minutesOfDay(other.from)) {
204
+ conflicts.add(previous);
205
+ conflicts.add(index);
206
+ }
207
+ });
208
+ });
209
+ return inputs.flatMap((_, index) => (conflicts.has(index) ? [index] : []));
116
210
  }
117
211
  export function validateAvailabilityWindows(inputs, context, minimum = clientAvailabilityMinimumWindows) {
118
212
  if (inputs.length < minimum)
119
- throw errValidation({ msg: msgAvailabilityWindows });
120
- for (const [index, input] of inputs.entries()) {
213
+ throw errValidation({
214
+ msg: msgAvailabilityMinimumWindows.replace("{count}", String(minimum)),
215
+ });
216
+ for (const input of inputs) {
121
217
  validateClientAvailabilityInput(input);
122
218
  if (input.employer_id !== context.employer.id ||
123
219
  input.location_id !== context.location.id ||
124
- input.timezone !== context.timezone ||
125
- minutesOfDay(input.from) <
126
- context.company.work_day_start_hour * minutesPerHour ||
127
- minutesOfDay(input.to) >
128
- context.company.work_day_end_hour * minutesPerHour ||
129
- inputs
130
- .slice(0, index)
131
- .some((other) => sameCalendarDay(input.from, other.from) &&
132
- minutesOfDay(input.from) < minutesOfDay(other.to) &&
133
- minutesOfDay(input.to) > minutesOfDay(other.from)))
134
- throw errValidation({ msg: msgAvailabilityWindows });
220
+ input.timezone !== context.timezone)
221
+ throw errValidation({ msg: msgAvailabilityAccess });
222
+ if (context.working_hours &&
223
+ (minutesOfDay(input.from) <
224
+ context.working_hours.work_day_start_hour * minutesPerHour ||
225
+ minutesOfDay(input.to) >
226
+ context.working_hours.work_day_end_hour * minutesPerHour))
227
+ throw errValidation({ msg: msgAvailabilityOutsideHours });
135
228
  }
229
+ if (availabilityWindowConflicts(inputs).length)
230
+ throw errValidation({ msg: msgAvailabilityWindows });
136
231
  }
137
232
  // A chat response contains references, never authority or an appointment confirmation.
138
233
  // Reload each chosen record before preparing the response, including after a resumed batch.
139
- export async function loadAvailabilitySelection(readers, actor, reference, ids, signal) {
234
+ export async function loadAvailabilitySelection(readers, actor, reference, ids, options = {}) {
140
235
  if (ids.length < clientAvailabilityMinimumWindows ||
141
236
  normalizeIDs(ids).length !== ids.length)
142
237
  throw errValidation({ msg: msgAvailabilityData });
143
- const context = await loadAvailabilityContext(readers, actor, reference, signal);
238
+ const context = await loadAvailabilityContext(readers, actor, reference, options);
144
239
  const windows = await Promise.all(ids.map(async (id) => {
145
- const window = (await readers.routes.findOneClientAvailability({ id, signal })).value;
240
+ const window = (await readers.routes.findOneClientAvailability({
241
+ id,
242
+ signal: options.signal,
243
+ })).value;
146
244
  if (!window ||
147
245
  window.id !== id ||
148
246
  window.owner_id !== reference.client_id)
@@ -30,5 +30,12 @@ export type RouteAvailabilityWindow = {
30
30
  start: number;
31
31
  end: number;
32
32
  };
33
+ export declare function routeAvailabilityMatch(input: Pick<InRoute, "from" | "to" | "timezone">, availability: ClientAvailability): RouteAvailabilityWindow | undefined;
34
+ export type RouteAvailabilityOverview = {
35
+ saved: ClientAvailability[];
36
+ matching: RouteAvailabilityWindow[];
37
+ on_day: number;
38
+ };
39
+ export declare function routeAvailabilityOverview(input: Pick<InRoute, "from" | "to" | "timezone">, availability: RPRoutesAvailability, location_id: string, client_id: string): RouteAvailabilityOverview;
33
40
  export declare function routeAvailabilityWindows(input: Pick<InRoute, "from" | "to" | "timezone">, availability: RPRoutesAvailability, location_id: string, client_id: string): RouteAvailabilityWindow[];
34
41
  export {};
@@ -1,8 +1,9 @@
1
- import { isValidInputDate, minutesOfDay, sameCalendarDay, } from "../core/date.js";
1
+ import { minutesOfDay, sameCalendarDay } from "../core/date.js";
2
2
  import { errValidation } from "../core/err.js";
3
3
  import { normalizeIDs } from "../core/id.js";
4
4
  import { routeInput, validateRouteTimeWindow } from "./validation.js";
5
5
  import { validateRoutePlanLocation } from "./departure.js";
6
+ import { isValidAvailabilityWindow } from "./availability.js";
6
7
  import { PreActionBlocked } from "../core/pre-action.js";
7
8
  const routePreparationBlocked = "route_preparation";
8
9
  const msgPlanningEmployees = "Choose available employees who are not already in another vehicle.";
@@ -71,25 +72,37 @@ export function validateRouteEmployees(ids, allowed, occupied = []) {
71
72
  msg: msgPlanningEmployees,
72
73
  });
73
74
  }
74
- // Suggestions are child data in an availability response. Intersect the client's
75
- // window with the route window; never widen the hours offered by either party.
76
- export function routeAvailabilityWindows(input, availability, location_id, client_id) {
75
+ // Intersect the client's window with the route hours; never widen either.
76
+ export function routeAvailabilityMatch(input, availability) {
77
+ if (!isValidAvailabilityWindow(availability) ||
78
+ availability.timezone !== input.timezone ||
79
+ !sameCalendarDay(availability.from, input.from))
80
+ return;
81
+ const start = Math.max(minutesOfDay(availability.from), minutesOfDay(input.from));
82
+ const end = Math.min(minutesOfDay(availability.to), minutesOfDay(input.to));
83
+ return start < end ? { availability, start, end } : undefined;
84
+ }
85
+ // These are child windows returned by get_availability, not a filtered parent
86
+ // query. Keep other days visible and preserve the backend's order.
87
+ export function routeAvailabilityOverview(input, availability, location_id, client_id) {
77
88
  validateRouteTimeWindow(input);
78
- return availability.client_availabilities
89
+ const saved = availability.client_availabilities
79
90
  .flatMap((group) => group.location_id === location_id ? group.availabilities : [])
80
- .flatMap((window) => {
81
- if (window.owner_id !== client_id ||
82
- window.location_id !== location_id ||
83
- window.timezone !== input.timezone ||
84
- window.from.tz !== input.timezone ||
85
- window.to.tz !== input.timezone ||
86
- !isValidInputDate(window.from) ||
87
- !isValidInputDate(window.to) ||
88
- !sameCalendarDay(window.from, input.from) ||
89
- !sameCalendarDay(window.to, input.to))
90
- return [];
91
- const start = Math.max(minutesOfDay(window.from), minutesOfDay(input.from));
92
- const end = Math.min(minutesOfDay(window.to), minutesOfDay(input.to));
93
- return start < end ? [{ availability: window, start, end }] : [];
94
- });
91
+ .filter((window) => window.owner_id === client_id &&
92
+ window.location_id === location_id &&
93
+ window.timezone === input.timezone &&
94
+ isValidAvailabilityWindow(window));
95
+ return {
96
+ saved,
97
+ matching: saved.flatMap((window) => {
98
+ const match = routeAvailabilityMatch(input, window);
99
+ return match ? [match] : [];
100
+ }),
101
+ on_day: saved.filter((window) => sameCalendarDay(window.from, input.from))
102
+ .length,
103
+ };
104
+ }
105
+ export function routeAvailabilityWindows(input, availability, location_id, client_id) {
106
+ return routeAvailabilityOverview(input, availability, location_id, client_id)
107
+ .matching;
95
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabriktor/fx",
3
- "version": "0.0.149",
3
+ "version": "0.0.151",
4
4
  "description": "![Fabriktor Logo](./img/fabriktor-character.gif)",
5
5
  "type": "module",
6
6
  "exports": {