@fabriktor/fx 0.0.71 → 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.
- package/dist/src/jobs/jobs.d.ts +1 -1
- package/dist/src/jobs/jobs.js +97 -40
- package/dist/src/users/users.d.ts +16 -1
- package/dist/src/users/users.js +51 -1
- package/package.json +3 -3
package/dist/src/jobs/jobs.d.ts
CHANGED
|
@@ -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;
|
package/dist/src/jobs/jobs.js
CHANGED
|
@@ -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 = "
|
|
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
|
-
|
|
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
|
|
424
|
-
const
|
|
425
|
-
if (
|
|
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
|
-
|
|
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
|
|
440
|
-
if (
|
|
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
|
-
|
|
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
|
|
458
|
-
const
|
|
459
|
-
if (
|
|
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.
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GetByUIDOptions, OperationResultOf, TmpPhonesOperator, TmpUsernamesOperator, UsernameVerification, UsersOperator } from "@fabriktor/client";
|
|
2
|
-
import type
|
|
2
|
+
import { UserTypeEmployee, UserTypeEmployer, type InAddress, type InUser, type User } from "@fabriktor/schema";
|
|
3
3
|
import { type RunSearchOptions, type SearchPage } from "../core/search.js";
|
|
4
4
|
export type ResolveUsernameOptions = Parameters<UsersOperator["resolveUsername"]>[0];
|
|
5
5
|
export type VerifyUsernameOptions = Parameters<TmpUsernamesOperator["verify"]>[0];
|
|
@@ -16,6 +16,17 @@ export type UpdateUserPhoneOptions = GetByUIDOptions & {
|
|
|
16
16
|
phone: string;
|
|
17
17
|
code: string;
|
|
18
18
|
};
|
|
19
|
+
export type WorkAccountIdentity = typeof UserTypeEmployee | typeof UserTypeEmployer;
|
|
20
|
+
export type SwitchActiveModeOptions = GetByUIDOptions & {
|
|
21
|
+
id: string;
|
|
22
|
+
account_identity: User["account_identity"];
|
|
23
|
+
active_mode: User["active_mode"];
|
|
24
|
+
};
|
|
25
|
+
export type EstablishWorkIdentityOptions = GetByUIDOptions & {
|
|
26
|
+
id: string;
|
|
27
|
+
current_account_identity: User["account_identity"];
|
|
28
|
+
account_identity: WorkAccountIdentity;
|
|
29
|
+
};
|
|
19
30
|
export type SendPhoneCodeOptions = Parameters<TmpPhonesOperator["send"]>[0];
|
|
20
31
|
export type VerifyPhoneCodeOptions = Parameters<TmpPhonesOperator["verify"]>[0];
|
|
21
32
|
export type UsersFXOptions = {
|
|
@@ -27,6 +38,8 @@ export interface UsersFXOperator {
|
|
|
27
38
|
searchUsers(opts: SearchUsersOptions): Promise<SearchUsersPage>;
|
|
28
39
|
updateUserProfile(opts: UpdateUserProfileOptions): Promise<User>;
|
|
29
40
|
updateUserPhone(opts: UpdateUserPhoneOptions): Promise<User>;
|
|
41
|
+
switchActiveMode(opts: SwitchActiveModeOptions): Promise<User>;
|
|
42
|
+
establishWorkIdentity(opts: EstablishWorkIdentityOptions): Promise<User>;
|
|
30
43
|
resolveUsername(opts: ResolveUsernameOptions): Promise<string>;
|
|
31
44
|
verifyUsername(opts: VerifyUsernameOptions): Promise<OperationResultOf<UsernameVerification>>;
|
|
32
45
|
generateUsernames(opts: GenerateUsernamesOptions): Promise<OperationResultOf<UsernameVerification>>;
|
|
@@ -41,6 +54,8 @@ export declare class UsersFX implements UsersFXOperator {
|
|
|
41
54
|
searchUsers(opts: SearchUsersOptions): Promise<SearchUsersPage>;
|
|
42
55
|
updateUserProfile(opts: UpdateUserProfileOptions): Promise<User>;
|
|
43
56
|
updateUserPhone(opts: UpdateUserPhoneOptions): Promise<User>;
|
|
57
|
+
switchActiveMode(opts: SwitchActiveModeOptions): Promise<User>;
|
|
58
|
+
establishWorkIdentity(opts: EstablishWorkIdentityOptions): Promise<User>;
|
|
44
59
|
resolveUsername(opts: ResolveUsernameOptions): Promise<string>;
|
|
45
60
|
verifyUsername(opts: VerifyUsernameOptions): Promise<OperationResultOf<UsernameVerification>>;
|
|
46
61
|
generateUsernames(opts: GenerateUsernamesOptions): Promise<OperationResultOf<UsernameVerification>>;
|
package/dist/src/users/users.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
4
4
|
// not use this file except in compliance with the License. You may obtain
|
|
5
5
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
import { UserTypeClient, UserTypeEmployee, UserTypeEmployer, } from "@fabriktor/schema";
|
|
6
7
|
import { errRequired, errResolveFailed, errValidation, } from "../core/err.js";
|
|
7
8
|
import { runSearch, } from "../core/search.js";
|
|
8
9
|
import { normalizePhone, normalizePhoneCode, validatePhone, validatePhoneCode, } from "./phone.js";
|
|
@@ -13,7 +14,10 @@ const msgPhoneCodeRequired = "Verification code is required.";
|
|
|
13
14
|
const msgFirstNameRequired = "First name is required.";
|
|
14
15
|
const msgLastNameRequired = "Last name is required.";
|
|
15
16
|
const msgAddressRequired = "A complete address is required.";
|
|
16
|
-
const msgUserMissingAfterUpdate = "User could not be resolved after the
|
|
17
|
+
const msgUserMissingAfterUpdate = "User could not be resolved after the account update.";
|
|
18
|
+
const msgInvalidActiveMode = "Active mode is not available for this account identity.";
|
|
19
|
+
const msgWorkIdentityRequiresClient = "A work identity can only be established from a client account.";
|
|
20
|
+
const msgInvalidWorkIdentity = "Work identity must be employee or employer.";
|
|
17
21
|
const resourceUsername = "username";
|
|
18
22
|
const resourceUser = "user";
|
|
19
23
|
export class UsersFX {
|
|
@@ -60,6 +64,36 @@ export class UsersFX {
|
|
|
60
64
|
});
|
|
61
65
|
return await this.updatedUser(opts.uid);
|
|
62
66
|
}
|
|
67
|
+
async switchActiveMode(opts) {
|
|
68
|
+
validateActiveMode(opts.account_identity, opts.active_mode);
|
|
69
|
+
await this.users.replaceOneUser({
|
|
70
|
+
id: opts.id,
|
|
71
|
+
in: { active_mode: opts.active_mode },
|
|
72
|
+
});
|
|
73
|
+
return await this.updatedUser(opts.uid);
|
|
74
|
+
}
|
|
75
|
+
async establishWorkIdentity(opts) {
|
|
76
|
+
if (opts.current_account_identity !== UserTypeClient) {
|
|
77
|
+
throw errValidation({
|
|
78
|
+
field: "current_account_identity",
|
|
79
|
+
msg: msgWorkIdentityRequiresClient,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (!isWorkAccountIdentity(opts.account_identity)) {
|
|
83
|
+
throw errValidation({
|
|
84
|
+
field: "account_identity",
|
|
85
|
+
msg: msgInvalidWorkIdentity,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
await this.users.replaceOneUser({
|
|
89
|
+
id: opts.id,
|
|
90
|
+
in: {
|
|
91
|
+
account_identity: opts.account_identity,
|
|
92
|
+
active_mode: opts.account_identity,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
return await this.updatedUser(opts.uid);
|
|
96
|
+
}
|
|
63
97
|
async resolveUsername(opts) {
|
|
64
98
|
const username = normalizeUsername(opts.username);
|
|
65
99
|
const out = await this.users.resolveUsername({
|
|
@@ -211,3 +245,19 @@ function errPhoneCodeRequired(field) {
|
|
|
211
245
|
msg: msgPhoneCodeRequired,
|
|
212
246
|
});
|
|
213
247
|
}
|
|
248
|
+
function isWorkAccountIdentity(value) {
|
|
249
|
+
return value === UserTypeEmployee || value === UserTypeEmployer;
|
|
250
|
+
}
|
|
251
|
+
function validateActiveMode(accountIdentity, activeMode) {
|
|
252
|
+
if ((accountIdentity === UserTypeEmployer &&
|
|
253
|
+
(activeMode === UserTypeEmployer || activeMode === UserTypeClient)) ||
|
|
254
|
+
(accountIdentity === UserTypeEmployee &&
|
|
255
|
+
(activeMode === UserTypeEmployee || activeMode === UserTypeClient)) ||
|
|
256
|
+
(accountIdentity === UserTypeClient && activeMode === UserTypeClient)) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
throw errValidation({
|
|
260
|
+
field: "active_mode",
|
|
261
|
+
msg: msgInvalidActiveMode,
|
|
262
|
+
});
|
|
263
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabriktor/fx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.73",
|
|
4
4
|
"description": "",
|
|
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.
|
|
21
|
-
"@fabriktor/schema": "0.0.
|
|
20
|
+
"@fabriktor/client": "0.0.67",
|
|
21
|
+
"@fabriktor/schema": "0.0.49"
|
|
22
22
|
}
|
|
23
23
|
}
|