@fabriktor/fx 0.0.31 → 0.0.33
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/billable/billable.d.ts +12 -10
- package/dist/src/billable/billable.js +1 -2
- package/dist/src/calls/calls.d.ts +5 -5
- package/dist/src/calls/calls.js +10 -4
- package/dist/src/chat/chat.d.ts +90 -0
- package/dist/src/chat/chat.js +194 -0
- package/dist/src/contacts/contacts.d.ts +67 -0
- package/dist/src/contacts/contacts.js +179 -0
- package/dist/src/core/err.d.ts +0 -10
- package/dist/src/core/err.js +0 -38
- package/dist/src/core/pull.d.ts +39 -0
- package/dist/src/core/pull.js +101 -0
- package/dist/src/feed/feed.d.ts +167 -0
- package/dist/src/feed/feed.js +642 -0
- package/dist/src/fx.d.ts +62 -8
- package/dist/src/fx.js +137 -9
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +13 -0
- package/dist/src/jobs/jobs.d.ts +74 -0
- package/dist/src/jobs/jobs.js +375 -0
- package/dist/src/marketplace/marketplace.d.ts +35 -0
- package/dist/src/marketplace/marketplace.js +159 -0
- package/dist/src/notifications/notifications.d.ts +20 -0
- package/dist/src/notifications/notifications.js +45 -0
- package/dist/src/payments/payments.d.ts +59 -0
- package/dist/src/payments/payments.js +266 -0
- package/dist/src/payrolls/payrolls.d.ts +52 -0
- package/dist/src/payrolls/payrolls.js +54 -0
- package/dist/src/privacy/privacy.d.ts +21 -0
- package/dist/src/privacy/privacy.js +78 -0
- package/dist/src/routes/routes.d.ts +22 -0
- package/dist/src/routes/routes.js +134 -0
- package/dist/src/session/session.d.ts +29 -13
- package/dist/src/session/session.js +21 -14
- package/dist/src/storage/storage.d.ts +79 -0
- package/dist/src/storage/storage.js +240 -0
- package/dist/src/timesheets/timesheets.d.ts +91 -0
- package/dist/src/timesheets/timesheets.js +407 -0
- package/dist/src/users/users.d.ts +15 -11
- package/dist/src/users/users.js +33 -7
- package/package.json +3 -3
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// Copyright (C) Fabriktor, Inc. 2025-present.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
4
|
+
// not use this file except in compliance with the License. You may obtain
|
|
5
|
+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
import { CreditLedgerSourceAndroidIAP, CreditLedgerSourceAppleIAP, CreditLedgerSourceStripe, ZeroID, } from "@fabriktor/schema";
|
|
7
|
+
import { errResolveFailed, errValidation } from "../core/err.js";
|
|
8
|
+
const publicAmountPattern = /^\d+$/;
|
|
9
|
+
const resourceStripeCreditPayment = "stripe credit payment";
|
|
10
|
+
const resourcePublicPaymentSession = "public payment session";
|
|
11
|
+
const msgStripeCreditPaymentIDMissing = "Stripe credit payment ID could not be resolved.";
|
|
12
|
+
const msgStripeCreditPaymentMissing = "Stripe credit payment could not be resolved.";
|
|
13
|
+
const msgStripeCreditPaymentAlreadyConfirmed = "Stripe credit payment is already confirmed.";
|
|
14
|
+
const msgPublicSessionMissing = "Public payment session could not be resolved.";
|
|
15
|
+
const msgPublicSessionInternal = "Internal clients cannot use the public payment flow.";
|
|
16
|
+
const msgPublicSessionConsumed = "Public payment session has already been consumed.";
|
|
17
|
+
const msgPublicAmountInvalid = "Public payment amount must be a positive integer.";
|
|
18
|
+
const msgPublicBalanceInvalid = "Public payment session balance is invalid.";
|
|
19
|
+
const msgPublicAmountExceedsBalance = "Public payment amount cannot exceed the session balance.";
|
|
20
|
+
export const PublicPaymentSessionStatus = {
|
|
21
|
+
NotFound: "not_found",
|
|
22
|
+
Ready: "ready",
|
|
23
|
+
InternalClient: "internal_client",
|
|
24
|
+
Consumed: "consumed",
|
|
25
|
+
};
|
|
26
|
+
export class PaymentsFX {
|
|
27
|
+
payments;
|
|
28
|
+
submittedPublicSessionIDs;
|
|
29
|
+
constructor(opts) {
|
|
30
|
+
this.payments = opts.payments;
|
|
31
|
+
this.submittedPublicSessionIDs = new Set();
|
|
32
|
+
}
|
|
33
|
+
async purchaseAppleCredits(opts) {
|
|
34
|
+
await this.payments.purchaseCredits({
|
|
35
|
+
src: CreditLedgerSourceAppleIAP,
|
|
36
|
+
pkg: opts.pkg,
|
|
37
|
+
iap_apple: opts.iap_apple,
|
|
38
|
+
iap_google: zeroGoogleIAP(),
|
|
39
|
+
payer_payment_method_psp_id: ZeroID,
|
|
40
|
+
return_url: "",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async purchaseGoogleCredits(opts) {
|
|
44
|
+
await this.payments.purchaseCredits({
|
|
45
|
+
src: CreditLedgerSourceAndroidIAP,
|
|
46
|
+
pkg: opts.pkg,
|
|
47
|
+
iap_apple: zeroAppleIAP(),
|
|
48
|
+
iap_google: opts.iap_google,
|
|
49
|
+
payer_payment_method_psp_id: ZeroID,
|
|
50
|
+
return_url: "",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async prepareStripeCreditPurchase(opts) {
|
|
54
|
+
const out = await this.payments.purchaseCredits({
|
|
55
|
+
src: CreditLedgerSourceStripe,
|
|
56
|
+
pkg: opts.pkg,
|
|
57
|
+
iap_apple: zeroAppleIAP(),
|
|
58
|
+
iap_google: zeroGoogleIAP(),
|
|
59
|
+
payer_payment_method_psp_id: opts.payer_payment_method_psp_id,
|
|
60
|
+
return_url: opts.return_url,
|
|
61
|
+
});
|
|
62
|
+
const id = operationID(out);
|
|
63
|
+
if (id === undefined) {
|
|
64
|
+
throw errResolveFailed({
|
|
65
|
+
resource: resourceStripeCreditPayment,
|
|
66
|
+
msg: msgStripeCreditPaymentIDMissing,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const payment = await this.payments.findOnePayment({
|
|
70
|
+
id,
|
|
71
|
+
});
|
|
72
|
+
if (payment.value === undefined) {
|
|
73
|
+
throw errResolveFailed({
|
|
74
|
+
resource: resourceStripeCreditPayment,
|
|
75
|
+
msg: msgStripeCreditPaymentMissing,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return payment.value;
|
|
79
|
+
}
|
|
80
|
+
async confirmStripeCreditPurchase(opts) {
|
|
81
|
+
const id = resolvedID(opts.payment.id);
|
|
82
|
+
if (id === undefined) {
|
|
83
|
+
throw errValidation({
|
|
84
|
+
field: "payment",
|
|
85
|
+
msg: msgStripeCreditPaymentIDMissing,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (opts.payment.psp_confirm) {
|
|
89
|
+
throw errValidation({
|
|
90
|
+
field: "payment",
|
|
91
|
+
msg: msgStripeCreditPaymentAlreadyConfirmed,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const input = {
|
|
95
|
+
id,
|
|
96
|
+
in: {
|
|
97
|
+
psp_confirm: true,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
return await this.payments.replaceOnePayment(input);
|
|
101
|
+
}
|
|
102
|
+
async openDirectPublicPaymentSession(opts) {
|
|
103
|
+
return await this.openPublicPaymentSession({
|
|
104
|
+
is_direct: true,
|
|
105
|
+
entity_id: opts.entity_id,
|
|
106
|
+
username: "",
|
|
107
|
+
type: opts.type,
|
|
108
|
+
entity_number: "",
|
|
109
|
+
amount: "",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async openLookupPublicPaymentSession(opts) {
|
|
113
|
+
return await this.openPublicPaymentSession({
|
|
114
|
+
is_direct: false,
|
|
115
|
+
entity_id: ZeroID,
|
|
116
|
+
username: opts.username,
|
|
117
|
+
type: opts.type,
|
|
118
|
+
entity_number: opts.entity_number,
|
|
119
|
+
amount: opts.amount,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async payPublicSession(opts) {
|
|
123
|
+
validatePublicSession(opts);
|
|
124
|
+
const id = publicSessionID(opts.session);
|
|
125
|
+
if (this.submittedPublicSessionIDs.has(id)) {
|
|
126
|
+
throw errValidation({
|
|
127
|
+
field: "session",
|
|
128
|
+
msg: msgPublicSessionConsumed,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const input = {
|
|
132
|
+
pmt_public_session_id: id,
|
|
133
|
+
amount: publicPaymentAmount(opts),
|
|
134
|
+
payment_method_psp_id: opts.payment_method_psp_id,
|
|
135
|
+
payer_email: opts.payer_email,
|
|
136
|
+
payer_name: opts.payer_name,
|
|
137
|
+
return_url: opts.return_url,
|
|
138
|
+
};
|
|
139
|
+
this.submittedPublicSessionIDs.add(id);
|
|
140
|
+
try {
|
|
141
|
+
return await this.payments.paymentFromPublicSession(input);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
this.submittedPublicSessionIDs.delete(id);
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async openPublicPaymentSession(input) {
|
|
149
|
+
const out = await this.payments.openPmtPublicSession(input);
|
|
150
|
+
if (out === undefined) {
|
|
151
|
+
return {
|
|
152
|
+
status: PublicPaymentSessionStatus.NotFound,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const session = out.value;
|
|
156
|
+
if (session === undefined) {
|
|
157
|
+
throw errResolveFailed({
|
|
158
|
+
resource: resourcePublicPaymentSession,
|
|
159
|
+
msg: msgPublicSessionMissing,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (session.is_internal_client) {
|
|
163
|
+
return {
|
|
164
|
+
status: PublicPaymentSessionStatus.InternalClient,
|
|
165
|
+
session,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const id = publicSessionID(session);
|
|
169
|
+
if (session.is_consumed || this.submittedPublicSessionIDs.has(id)) {
|
|
170
|
+
return {
|
|
171
|
+
status: PublicPaymentSessionStatus.Consumed,
|
|
172
|
+
session,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
status: PublicPaymentSessionStatus.Ready,
|
|
177
|
+
session,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
export function newPaymentsFX(opts) {
|
|
182
|
+
return new PaymentsFX(opts);
|
|
183
|
+
}
|
|
184
|
+
function validatePublicSession(opts) {
|
|
185
|
+
if (opts.session.is_internal_client) {
|
|
186
|
+
throw errValidation({
|
|
187
|
+
field: "session",
|
|
188
|
+
msg: msgPublicSessionInternal,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (opts.session.is_consumed) {
|
|
192
|
+
throw errValidation({
|
|
193
|
+
field: "session",
|
|
194
|
+
msg: msgPublicSessionConsumed,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function publicPaymentAmount(opts) {
|
|
199
|
+
const raw = opts.amount.trim();
|
|
200
|
+
if (!publicAmountPattern.test(raw)) {
|
|
201
|
+
throw errValidation({
|
|
202
|
+
field: "amount",
|
|
203
|
+
msg: msgPublicAmountInvalid,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const amount = Number(raw);
|
|
207
|
+
if (!Number.isSafeInteger(amount) || amount <= 0) {
|
|
208
|
+
throw errValidation({
|
|
209
|
+
field: "amount",
|
|
210
|
+
msg: msgPublicAmountInvalid,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
const balance = opts.session.balance_amount;
|
|
214
|
+
if (!Number.isSafeInteger(balance) || balance < 0) {
|
|
215
|
+
throw errValidation({
|
|
216
|
+
field: "session",
|
|
217
|
+
msg: msgPublicBalanceInvalid,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (amount > balance) {
|
|
221
|
+
throw errValidation({
|
|
222
|
+
field: "amount",
|
|
223
|
+
msg: msgPublicAmountExceedsBalance,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return amount.toString();
|
|
227
|
+
}
|
|
228
|
+
function publicSessionID(session) {
|
|
229
|
+
const id = resolvedID(session.id);
|
|
230
|
+
if (id === undefined) {
|
|
231
|
+
throw errResolveFailed({
|
|
232
|
+
resource: resourcePublicPaymentSession,
|
|
233
|
+
msg: msgPublicSessionMissing,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return id;
|
|
237
|
+
}
|
|
238
|
+
function operationID(out) {
|
|
239
|
+
return resolvedID(out.id);
|
|
240
|
+
}
|
|
241
|
+
function resolvedID(v) {
|
|
242
|
+
const out = resolvedValue(v);
|
|
243
|
+
if (out === undefined || out === ZeroID) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
function resolvedValue(v) {
|
|
249
|
+
const out = v?.trim();
|
|
250
|
+
if (out === undefined || out.length === 0) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
function zeroAppleIAP() {
|
|
256
|
+
return {
|
|
257
|
+
transaction_id: "",
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function zeroGoogleIAP() {
|
|
261
|
+
return {
|
|
262
|
+
product_id: "",
|
|
263
|
+
purchase_token: "",
|
|
264
|
+
kind: "",
|
|
265
|
+
};
|
|
266
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PayrollsOperator } from "@fabriktor/client";
|
|
2
|
+
type AdjustmentReplaceOptions = Parameters<PayrollsOperator["replaceOneAdjustment"]>[0];
|
|
3
|
+
type AdjustmentFolderReplaceOptions = Parameters<PayrollsOperator["replaceOneAdjustmentFolder"]>[0];
|
|
4
|
+
type PayConfigReplaceOptions = Parameters<PayrollsOperator["replaceOnePayConfig"]>[0];
|
|
5
|
+
type EmployeePayConfigReplaceOptions = Parameters<PayrollsOperator["replaceOneEmployeePayConfig"]>[0];
|
|
6
|
+
type PayrollProgramReplaceOptions = Parameters<PayrollsOperator["replaceOnePayrollProgram"]>[0];
|
|
7
|
+
export type SetAdjustmentArchivedOptions = {
|
|
8
|
+
id: AdjustmentReplaceOptions["id"];
|
|
9
|
+
is_archived: NonNullable<AdjustmentReplaceOptions["in"]["is_archived"]>;
|
|
10
|
+
};
|
|
11
|
+
export type SetAdjustmentFolderArchivedOptions = {
|
|
12
|
+
id: AdjustmentFolderReplaceOptions["id"];
|
|
13
|
+
is_archived: NonNullable<AdjustmentFolderReplaceOptions["in"]["is_archived"]>;
|
|
14
|
+
};
|
|
15
|
+
export type SetPayConfigArchivedOptions = {
|
|
16
|
+
id: PayConfigReplaceOptions["id"];
|
|
17
|
+
is_archived: NonNullable<PayConfigReplaceOptions["in"]["is_archived"]>;
|
|
18
|
+
};
|
|
19
|
+
export type SetEmployeePayConfigArchivedOptions = {
|
|
20
|
+
id: EmployeePayConfigReplaceOptions["id"];
|
|
21
|
+
is_archived: NonNullable<EmployeePayConfigReplaceOptions["in"]["is_archived"]>;
|
|
22
|
+
};
|
|
23
|
+
export type SetPayrollProgramEnabledOptions = {
|
|
24
|
+
id: PayrollProgramReplaceOptions["id"];
|
|
25
|
+
is_enabled: NonNullable<PayrollProgramReplaceOptions["in"]["is_enabled"]>;
|
|
26
|
+
};
|
|
27
|
+
export type SetAdjustmentArchivedResult = Awaited<ReturnType<PayrollsOperator["replaceOneAdjustment"]>>;
|
|
28
|
+
export type SetAdjustmentFolderArchivedResult = Awaited<ReturnType<PayrollsOperator["replaceOneAdjustmentFolder"]>>;
|
|
29
|
+
export type SetPayConfigArchivedResult = Awaited<ReturnType<PayrollsOperator["replaceOnePayConfig"]>>;
|
|
30
|
+
export type SetEmployeePayConfigArchivedResult = Awaited<ReturnType<PayrollsOperator["replaceOneEmployeePayConfig"]>>;
|
|
31
|
+
export type SetPayrollProgramEnabledResult = Awaited<ReturnType<PayrollsOperator["replaceOnePayrollProgram"]>>;
|
|
32
|
+
export type PayrollsFXOptions = {
|
|
33
|
+
payrolls: PayrollsOperator;
|
|
34
|
+
};
|
|
35
|
+
export interface PayrollsFXOperator {
|
|
36
|
+
setAdjustmentArchived(opts: SetAdjustmentArchivedOptions): Promise<SetAdjustmentArchivedResult>;
|
|
37
|
+
setAdjustmentFolderArchived(opts: SetAdjustmentFolderArchivedOptions): Promise<SetAdjustmentFolderArchivedResult>;
|
|
38
|
+
setPayConfigArchived(opts: SetPayConfigArchivedOptions): Promise<SetPayConfigArchivedResult>;
|
|
39
|
+
setEmployeePayConfigArchived(opts: SetEmployeePayConfigArchivedOptions): Promise<SetEmployeePayConfigArchivedResult>;
|
|
40
|
+
setPayrollProgramEnabled(opts: SetPayrollProgramEnabledOptions): Promise<SetPayrollProgramEnabledResult>;
|
|
41
|
+
}
|
|
42
|
+
export declare class PayrollsFX implements PayrollsFXOperator {
|
|
43
|
+
private payrolls;
|
|
44
|
+
constructor(opts: PayrollsFXOptions);
|
|
45
|
+
setAdjustmentArchived(opts: SetAdjustmentArchivedOptions): Promise<SetAdjustmentArchivedResult>;
|
|
46
|
+
setAdjustmentFolderArchived(opts: SetAdjustmentFolderArchivedOptions): Promise<SetAdjustmentFolderArchivedResult>;
|
|
47
|
+
setPayConfigArchived(opts: SetPayConfigArchivedOptions): Promise<SetPayConfigArchivedResult>;
|
|
48
|
+
setEmployeePayConfigArchived(opts: SetEmployeePayConfigArchivedOptions): Promise<SetEmployeePayConfigArchivedResult>;
|
|
49
|
+
setPayrollProgramEnabled(opts: SetPayrollProgramEnabledOptions): Promise<SetPayrollProgramEnabledResult>;
|
|
50
|
+
}
|
|
51
|
+
export declare function newPayrollsFX(opts: PayrollsFXOptions): PayrollsFX;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Copyright (C) Fabriktor, Inc. 2025-present.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
4
|
+
// not use this file except in compliance with the License. You may obtain
|
|
5
|
+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
export class PayrollsFX {
|
|
7
|
+
payrolls;
|
|
8
|
+
constructor(opts) {
|
|
9
|
+
this.payrolls = opts.payrolls;
|
|
10
|
+
}
|
|
11
|
+
async setAdjustmentArchived(opts) {
|
|
12
|
+
return await this.payrolls.replaceOneAdjustment({
|
|
13
|
+
id: opts.id,
|
|
14
|
+
in: {
|
|
15
|
+
is_archived: opts.is_archived,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async setAdjustmentFolderArchived(opts) {
|
|
20
|
+
return await this.payrolls.replaceOneAdjustmentFolder({
|
|
21
|
+
id: opts.id,
|
|
22
|
+
in: {
|
|
23
|
+
is_archived: opts.is_archived,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async setPayConfigArchived(opts) {
|
|
28
|
+
return await this.payrolls.replaceOnePayConfig({
|
|
29
|
+
id: opts.id,
|
|
30
|
+
in: {
|
|
31
|
+
is_archived: opts.is_archived,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async setEmployeePayConfigArchived(opts) {
|
|
36
|
+
return await this.payrolls.replaceOneEmployeePayConfig({
|
|
37
|
+
id: opts.id,
|
|
38
|
+
in: {
|
|
39
|
+
is_archived: opts.is_archived,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async setPayrollProgramEnabled(opts) {
|
|
44
|
+
return await this.payrolls.replaceOnePayrollProgram({
|
|
45
|
+
id: opts.id,
|
|
46
|
+
in: {
|
|
47
|
+
is_enabled: opts.is_enabled,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function newPayrollsFX(opts) {
|
|
53
|
+
return new PayrollsFX(opts);
|
|
54
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type AccountLock } from "@fabriktor/schema";
|
|
2
|
+
import type { PrivacyOperator } from "@fabriktor/client";
|
|
3
|
+
export type PrivacyFXOptions = {
|
|
4
|
+
privacy: PrivacyOperator;
|
|
5
|
+
};
|
|
6
|
+
export type WatchAccountLockOptions = {
|
|
7
|
+
owner_id: string;
|
|
8
|
+
on_lock: (lock: AccountLock) => void;
|
|
9
|
+
on_error?: (err: unknown) => void;
|
|
10
|
+
};
|
|
11
|
+
export type StopAccountLockWatch = () => void;
|
|
12
|
+
export interface PrivacyFXOperator {
|
|
13
|
+
watchAccountLock(opts: WatchAccountLockOptions): Promise<StopAccountLockWatch>;
|
|
14
|
+
}
|
|
15
|
+
export declare class PrivacyFX implements PrivacyFXOperator {
|
|
16
|
+
private privacy;
|
|
17
|
+
constructor(opts: PrivacyFXOptions);
|
|
18
|
+
watchAccountLock(opts: WatchAccountLockOptions): Promise<StopAccountLockWatch>;
|
|
19
|
+
private findAccountLock;
|
|
20
|
+
}
|
|
21
|
+
export declare function newPrivacyFX(opts: PrivacyFXOptions): PrivacyFX;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Copyright (C) Fabriktor, Inc. 2025-present.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
4
|
+
// not use this file except in compliance with the License. You may obtain
|
|
5
|
+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
import { ZeroID } from "@fabriktor/schema";
|
|
7
|
+
import { errRequired } from "../core/err.js";
|
|
8
|
+
const accountLockIntervalMs = 60 * 60 * 1000;
|
|
9
|
+
const msgOwnerIDRequired = "Owner ID is required.";
|
|
10
|
+
export class PrivacyFX {
|
|
11
|
+
privacy;
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
this.privacy = opts.privacy;
|
|
14
|
+
}
|
|
15
|
+
async watchAccountLock(opts) {
|
|
16
|
+
const owner_id = opts.owner_id.trim();
|
|
17
|
+
if (owner_id === "" || owner_id === ZeroID) {
|
|
18
|
+
throw errRequired({
|
|
19
|
+
field: "owner_id",
|
|
20
|
+
msg: msgOwnerIDRequired,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const initial_lock = await this.findAccountLock(owner_id);
|
|
24
|
+
if (initial_lock !== undefined) {
|
|
25
|
+
opts.on_lock(initial_lock);
|
|
26
|
+
return () => { };
|
|
27
|
+
}
|
|
28
|
+
let stopped = false;
|
|
29
|
+
let timer;
|
|
30
|
+
const schedule = () => {
|
|
31
|
+
timer = globalThis.setTimeout(() => {
|
|
32
|
+
void check();
|
|
33
|
+
}, accountLockIntervalMs);
|
|
34
|
+
};
|
|
35
|
+
const check = async () => {
|
|
36
|
+
if (stopped) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const lock = await this.findAccountLock(owner_id);
|
|
41
|
+
if (stopped) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (lock !== undefined) {
|
|
45
|
+
stopped = true;
|
|
46
|
+
opts.on_lock(lock);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (!stopped) {
|
|
52
|
+
opts.on_error?.(err);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!stopped) {
|
|
56
|
+
schedule();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
schedule();
|
|
60
|
+
return () => {
|
|
61
|
+
stopped = true;
|
|
62
|
+
if (timer !== undefined) {
|
|
63
|
+
globalThis.clearTimeout(timer);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async findAccountLock(owner_id) {
|
|
68
|
+
const out = await this.privacy.queryAccountLocks({
|
|
69
|
+
query: {
|
|
70
|
+
owner_id,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
return out.value?.[0];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function newPrivacyFX(opts) {
|
|
77
|
+
return new PrivacyFX(opts);
|
|
78
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RoutesOperator } from "@fabriktor/client";
|
|
2
|
+
type RouteInsertOptions = Parameters<RoutesOperator["insertOneRoute"]>[0];
|
|
3
|
+
type RouteInsert = RouteInsertOptions["in"];
|
|
4
|
+
type RouteAvailability = NonNullable<Awaited<ReturnType<RoutesOperator["getAvailability"]>>["value"]>;
|
|
5
|
+
export type CreateRouteOptions = {
|
|
6
|
+
in: RouteInsert;
|
|
7
|
+
availability: RouteAvailability;
|
|
8
|
+
};
|
|
9
|
+
export type CreateRouteResult = Awaited<ReturnType<RoutesOperator["insertOneRoute"]>>;
|
|
10
|
+
export type RoutesFXOptions = {
|
|
11
|
+
routes: RoutesOperator;
|
|
12
|
+
};
|
|
13
|
+
export interface RoutesFXOperator {
|
|
14
|
+
createRoute(opts: CreateRouteOptions): Promise<CreateRouteResult>;
|
|
15
|
+
}
|
|
16
|
+
export declare class RoutesFX implements RoutesFXOperator {
|
|
17
|
+
private routes;
|
|
18
|
+
constructor(opts: RoutesFXOptions);
|
|
19
|
+
createRoute(opts: CreateRouteOptions): Promise<CreateRouteResult>;
|
|
20
|
+
}
|
|
21
|
+
export declare function newRoutesFX(opts: RoutesFXOptions): RoutesFX;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Copyright (C) Fabriktor, Inc. 2025-present.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
4
|
+
// not use this file except in compliance with the License. You may obtain
|
|
5
|
+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
import { errValidation } from "../core/err.js";
|
|
7
|
+
const maxHour = 23;
|
|
8
|
+
const maxMinute = 59;
|
|
9
|
+
const maxMinuteOfDay = 1440;
|
|
10
|
+
const msgGlobalWindowSameDay = "The global route window must stay within one calendar day.";
|
|
11
|
+
const msgGlobalWindowInvalid = "The global route window is invalid.";
|
|
12
|
+
const msgEmployeeDuplicate = "An employee cannot be selected in more than one vehicle.";
|
|
13
|
+
const msgEmployeeUnavailable = "A selected employee is not available for this route.";
|
|
14
|
+
const msgLocationDuplicate = "A location cannot be selected more than once.";
|
|
15
|
+
const msgLocationUnavailable = "A selected location is not available for this route.";
|
|
16
|
+
const msgDestinationWindowInvalid = "A destination time window is invalid.";
|
|
17
|
+
const msgDestinationWindowOutsideGlobal = "A destination time window must stay within the global route window.";
|
|
18
|
+
export class RoutesFX {
|
|
19
|
+
routes;
|
|
20
|
+
constructor(opts) {
|
|
21
|
+
this.routes = opts.routes;
|
|
22
|
+
}
|
|
23
|
+
async createRoute(opts) {
|
|
24
|
+
validateRoute(opts.in, opts.availability);
|
|
25
|
+
return await this.routes.insertOneRoute({
|
|
26
|
+
in: opts.in,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function newRoutesFX(opts) {
|
|
31
|
+
return new RoutesFX(opts);
|
|
32
|
+
}
|
|
33
|
+
function validateRoute(input, availability) {
|
|
34
|
+
if (!sameDay(input.from, input.to)) {
|
|
35
|
+
throw errValidation({
|
|
36
|
+
field: "to",
|
|
37
|
+
msg: msgGlobalWindowSameDay,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const fromMinute = minuteOfDay(input.from);
|
|
41
|
+
const toMinute = minuteOfDay(input.to);
|
|
42
|
+
if (fromMinute === undefined ||
|
|
43
|
+
toMinute === undefined ||
|
|
44
|
+
fromMinute >= toMinute) {
|
|
45
|
+
throw errValidation({
|
|
46
|
+
field: "to",
|
|
47
|
+
msg: msgGlobalWindowInvalid,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
validateVehicles(input, availability);
|
|
51
|
+
validateDestinations(input, availability, fromMinute, toMinute);
|
|
52
|
+
}
|
|
53
|
+
function validateVehicles(input, availability) {
|
|
54
|
+
const availableEmployeeIDs = new Set(availability.availables.employees.map((id) => id.trim()));
|
|
55
|
+
const employeeIDs = new Set();
|
|
56
|
+
for (const vehicle of input.vehicles) {
|
|
57
|
+
for (const rawEmployeeID of vehicle.employee_ids) {
|
|
58
|
+
const employeeID = rawEmployeeID.trim();
|
|
59
|
+
if (employeeID === "") {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (employeeIDs.has(employeeID)) {
|
|
63
|
+
throw errValidation({
|
|
64
|
+
field: "vehicles",
|
|
65
|
+
msg: msgEmployeeDuplicate,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (!availableEmployeeIDs.has(employeeID)) {
|
|
69
|
+
throw errValidation({
|
|
70
|
+
field: "vehicles",
|
|
71
|
+
msg: msgEmployeeUnavailable,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
employeeIDs.add(employeeID);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function validateDestinations(input, availability, fromMinute, toMinute) {
|
|
79
|
+
const availableLocationIDs = new Set(availability.availables.locations.map((id) => id.trim()));
|
|
80
|
+
const locationIDs = new Set();
|
|
81
|
+
for (const destination of input.destinations) {
|
|
82
|
+
const locationID = destination.location_id.trim();
|
|
83
|
+
if (locationID !== "") {
|
|
84
|
+
if (locationIDs.has(locationID)) {
|
|
85
|
+
throw errValidation({
|
|
86
|
+
field: "destinations",
|
|
87
|
+
msg: msgLocationDuplicate,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (!availableLocationIDs.has(locationID)) {
|
|
91
|
+
throw errValidation({
|
|
92
|
+
field: "destinations",
|
|
93
|
+
msg: msgLocationUnavailable,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
locationIDs.add(locationID);
|
|
97
|
+
}
|
|
98
|
+
if (!destination.use_time_window) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const start = destination.time_window_start_hour_in_minutes;
|
|
102
|
+
const end = destination.time_window_end_hour_in_minutes;
|
|
103
|
+
if (!Number.isInteger(start) ||
|
|
104
|
+
!Number.isInteger(end) ||
|
|
105
|
+
start < 0 ||
|
|
106
|
+
end > maxMinuteOfDay ||
|
|
107
|
+
start >= end) {
|
|
108
|
+
throw errValidation({
|
|
109
|
+
field: "destinations",
|
|
110
|
+
msg: msgDestinationWindowInvalid,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (start < fromMinute || end > toMinute) {
|
|
114
|
+
throw errValidation({
|
|
115
|
+
field: "destinations",
|
|
116
|
+
msg: msgDestinationWindowOutsideGlobal,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function sameDay(a, b) {
|
|
122
|
+
return a.y === b.y && a.m === b.m && a.d === b.d;
|
|
123
|
+
}
|
|
124
|
+
function minuteOfDay(v) {
|
|
125
|
+
if (!Number.isInteger(v.h) ||
|
|
126
|
+
!Number.isInteger(v.min) ||
|
|
127
|
+
v.h < 0 ||
|
|
128
|
+
v.h > maxHour ||
|
|
129
|
+
v.min < 0 ||
|
|
130
|
+
v.min > maxMinute) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
return v.h * 60 + v.min;
|
|
134
|
+
}
|