@fabriktor/fx 0.0.72 → 0.0.73

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.
@@ -57,7 +57,7 @@ export type CreateJobProgramResult = {
57
57
  } | {
58
58
  mode: typeof JobProgramCreateMode.Immediate;
59
59
  };
60
- export type FindLocationOverlapsInput = Pick<LocationInsert, "y" | "m" | "d" | "h" | "min" | "tz" | "end_date">;
60
+ export type FindLocationOverlapsInput = Pick<LocationInsert, "y" | "m" | "d" | "h" | "min" | "tz" | "end_date" | "lat" | "lon">;
61
61
  export type FindLocationOverlapsOptions = {
62
62
  in: FindLocationOverlapsInput;
63
63
  source_id?: string;
@@ -19,9 +19,8 @@ const msgProgramStartPast = "Program start date must not be in the past.";
19
19
  const msgJobStartOutsideLocation = "Job start date must fall within the parent location time span.";
20
20
  const msgProgramStartOutsideLocation = "Program job start date must fall within the parent location time span.";
21
21
  const msgLocationMemberAssigned = "Cannot remove a location member while they are assigned to a non-terminated job in that location.";
22
- const msgDateInvalid = "Date contains an invalid UTC value.";
23
22
  const msgLocationRangeInvalid = "Location contains an invalid scheduled time span.";
24
- const msgTimezoneInvalid = "Program timezone is invalid.";
23
+ const msgTimezoneInvalid = "Location timezone is invalid.";
25
24
  const resourceLocation = "location";
26
25
  const resourceJobProgram = "job_program";
27
26
  const resourceJob = "job";
@@ -119,9 +118,11 @@ export class JobsFX {
119
118
  field: "in",
120
119
  msg: msgJobMembersOutsideLocation,
121
120
  });
122
- validateJobStart(opts, this.now());
121
+ const timezone = locationTimezone(opts.location, "location");
123
122
  const input = {
124
123
  ...opts.in,
124
+ tz: timezone,
125
+ timezone,
125
126
  status: opts.start_immediately ? Started : Pending,
126
127
  is_active: opts.start_immediately,
127
128
  auto_start: opts.start_immediately ? true : opts.auto_start,
@@ -131,6 +132,12 @@ export class JobsFX {
131
132
  location_id,
132
133
  recurrent: false,
133
134
  };
135
+ validateJobStart({
136
+ input,
137
+ location: opts.location,
138
+ now: this.now(),
139
+ start_immediately: opts.start_immediately,
140
+ });
134
141
  return await this.jobs.insertOneJob({
135
142
  in: input,
136
143
  });
@@ -158,8 +165,18 @@ export class JobsFX {
158
165
  field: "in",
159
166
  msg: msgProgramMembersOutsideLocation,
160
167
  });
168
+ const timezone = locationTimezone(opts.location, "location");
161
169
  const input = {
162
170
  ...opts.in,
171
+ timezone,
172
+ job_start_date: {
173
+ ...opts.in.job_start_date,
174
+ tz: timezone,
175
+ },
176
+ recurrence_end_date: {
177
+ ...opts.in.recurrence_end_date,
178
+ tz: timezone,
179
+ },
163
180
  job_current_members,
164
181
  job_foremen,
165
182
  job_location_id,
@@ -189,6 +206,8 @@ export class JobsFX {
189
206
  }
190
207
  async findLocationOverlaps(opts) {
191
208
  const input = {
209
+ lat: opts.in.lat,
210
+ lon: opts.in.lon,
192
211
  start_date: {
193
212
  y: opts.in.y,
194
213
  m: opts.in.m,
@@ -420,31 +439,44 @@ function validateLocationMembers(opts) {
420
439
  });
421
440
  }
422
441
  }
423
- function validateJobStart(opts, now) {
424
- const start_ms = utcMilliseconds(opts.in.utc, "in");
425
- if (!opts.start_immediately && start_ms < now.getTime()) {
442
+ function validateJobStart(opts) {
443
+ const now = localDateParts(opts.now, opts.input.timezone);
444
+ if (now === undefined) {
445
+ throw errValidation({
446
+ field: "in",
447
+ msg: msgTimezoneInvalid,
448
+ });
449
+ }
450
+ if (!opts.start_immediately && compareLocalDate(opts.input, now) < 0) {
426
451
  throw errValidation({
427
452
  field: "in",
428
453
  msg: msgJobStartPast,
429
454
  });
430
455
  }
431
456
  validateLocationStart({
432
- start_ms,
457
+ start: opts.input,
433
458
  location: opts.location,
434
459
  field: "in",
435
460
  msg: msgJobStartOutsideLocation,
436
461
  });
437
462
  }
438
463
  function validateProgramStart(opts) {
439
- const start_ms = utcMilliseconds(opts.input.job_start_date.utc, "in");
440
- if (!opts.current_day && start_ms < opts.now.getTime()) {
464
+ const now = localDateParts(opts.now, opts.input.timezone);
465
+ if (now === undefined) {
466
+ throw errValidation({
467
+ field: "in",
468
+ msg: msgTimezoneInvalid,
469
+ });
470
+ }
471
+ if (!opts.current_day &&
472
+ compareLocalDate(opts.input.job_start_date, now) < 0) {
441
473
  throw errValidation({
442
474
  field: "in",
443
475
  msg: msgProgramStartPast,
444
476
  });
445
477
  }
446
478
  validateLocationStart({
447
- start_ms,
479
+ start: opts.input.job_start_date,
448
480
  location: opts.location,
449
481
  field: "in",
450
482
  msg: msgProgramStartOutsideLocation,
@@ -454,17 +486,16 @@ function validateLocationStart(opts) {
454
486
  if (opts.location.type === LocationTypePersistent) {
455
487
  return;
456
488
  }
457
- const location_start_ms = parsedUTC(opts.location.utc);
458
- const location_end_ms = parsedUTC(opts.location.end_date.utc);
459
- if (location_start_ms === undefined ||
460
- location_end_ms === undefined ||
461
- location_end_ms < location_start_ms) {
489
+ const start = localDateFromLocation(opts.location);
490
+ const end = opts.location.end_date;
491
+ if (compareLocalDate(end, start) < 0) {
462
492
  throw errValidation({
463
493
  field: opts.field,
464
494
  msg: msgLocationRangeInvalid,
465
495
  });
466
496
  }
467
- if (opts.start_ms < location_start_ms || opts.start_ms > location_end_ms) {
497
+ if (compareLocalDate(opts.start, start) < 0 ||
498
+ compareLocalDate(opts.start, end) > 0) {
468
499
  throw errValidation({
469
500
  field: opts.field,
470
501
  msg: opts.msg,
@@ -472,7 +503,7 @@ function validateLocationStart(opts) {
472
503
  }
473
504
  }
474
505
  function isCurrentProgramDay(input, now) {
475
- const day = localDayParts(now, input.timezone);
506
+ const day = localDateParts(now, input.timezone);
476
507
  if (day === undefined) {
477
508
  throw errValidation({
478
509
  field: "in",
@@ -483,7 +514,26 @@ function isCurrentProgramDay(input, now) {
483
514
  input.job_start_date.m === day.m &&
484
515
  input.job_start_date.d === day.d);
485
516
  }
486
- function localDayParts(now, timezone) {
517
+ function locationTimezone(location, field) {
518
+ const out = location.address_timezone.trim();
519
+ if (out === "") {
520
+ throw errValidation({
521
+ field,
522
+ msg: msgTimezoneInvalid,
523
+ });
524
+ }
525
+ return out;
526
+ }
527
+ function localDateFromLocation(location) {
528
+ return {
529
+ y: location.y,
530
+ m: location.m,
531
+ d: location.d,
532
+ h: location.h,
533
+ min: location.min,
534
+ };
535
+ }
536
+ function localDateParts(now, timezone) {
487
537
  const time_zone = timezone.trim();
488
538
  if (time_zone === "") {
489
539
  return undefined;
@@ -495,6 +545,9 @@ function localDayParts(now, timezone) {
495
545
  year: "numeric",
496
546
  month: "numeric",
497
547
  day: "numeric",
548
+ hour: "numeric",
549
+ minute: "numeric",
550
+ hourCycle: "h23",
498
551
  }).formatToParts(now);
499
552
  }
500
553
  catch {
@@ -503,15 +556,40 @@ function localDayParts(now, timezone) {
503
556
  const y = datePart(parts, "year");
504
557
  const m = datePart(parts, "month");
505
558
  const d = datePart(parts, "day");
506
- if (y === undefined || m === undefined || d === undefined) {
559
+ const h = datePart(parts, "hour");
560
+ const min = datePart(parts, "minute");
561
+ if (y === undefined ||
562
+ m === undefined ||
563
+ d === undefined ||
564
+ h === undefined ||
565
+ min === undefined) {
507
566
  return undefined;
508
567
  }
509
568
  return {
510
569
  y,
511
570
  m,
512
571
  d,
572
+ h,
573
+ min,
513
574
  };
514
575
  }
576
+ function compareLocalDate(a, b) {
577
+ for (const [left, right] of [
578
+ [a.y, b.y],
579
+ [a.m, b.m],
580
+ [a.d, b.d],
581
+ [a.h, b.h],
582
+ [a.min, b.min],
583
+ ]) {
584
+ if (left < right) {
585
+ return -1;
586
+ }
587
+ if (left > right) {
588
+ return 1;
589
+ }
590
+ }
591
+ return 0;
592
+ }
515
593
  function datePart(parts, type) {
516
594
  const raw = parts.find((part) => part.type === type)?.value;
517
595
  if (raw === undefined) {
@@ -523,27 +601,6 @@ function datePart(parts, type) {
523
601
  }
524
602
  return out;
525
603
  }
526
- function utcMilliseconds(v, field) {
527
- const out = parsedUTC(v);
528
- if (out === undefined) {
529
- throw errValidation({
530
- field,
531
- msg: msgDateInvalid,
532
- });
533
- }
534
- return out;
535
- }
536
- function parsedUTC(v) {
537
- const raw = v.trim();
538
- if (raw === "") {
539
- return undefined;
540
- }
541
- const out = globalThis.Date.parse(raw);
542
- if (!Number.isFinite(out)) {
543
- return undefined;
544
- }
545
- return out;
546
- }
547
604
  function requiredID(v, field, msg) {
548
605
  const out = resolvedID(v);
549
606
  if (out === undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabriktor/fx",
3
- "version": "0.0.72",
3
+ "version": "0.0.73",
4
4
  "description": "![Fabriktor Logo](./img/fabriktor-character.gif)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,7 +17,7 @@
17
17
  "typescript": "^6.0.3"
18
18
  },
19
19
  "dependencies": {
20
- "@fabriktor/client": "0.0.65",
21
- "@fabriktor/schema": "0.0.47"
20
+ "@fabriktor/client": "0.0.67",
21
+ "@fabriktor/schema": "0.0.49"
22
22
  }
23
23
  }