@fabriktor/fx 0.0.135 → 0.0.137
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.
|
@@ -28,6 +28,7 @@ export type MultiCreateEntry<Input, Result> = {
|
|
|
28
28
|
readonly created_id?: string;
|
|
29
29
|
readonly result?: Result;
|
|
30
30
|
readonly error?: unknown;
|
|
31
|
+
readonly skipped?: boolean;
|
|
31
32
|
};
|
|
32
33
|
export type MultiCreateSnapshot<Input, Result> = {
|
|
33
34
|
readonly entries: readonly MultiCreateEntry<Input, Result>[];
|
|
@@ -48,7 +49,7 @@ export declare class MultiCreatePool {
|
|
|
48
49
|
export type MultiCreateOptions<Input, Result> = {
|
|
49
50
|
run: (input: Input, attempt: MultiCreateAttempt) => Promise<Result>;
|
|
50
51
|
pool?: MultiCreatePool;
|
|
51
|
-
check?: (inputs: readonly Input[], signal: AbortSignal) => Promise<void
|
|
52
|
+
check?: (inputs: readonly Input[], signal: AbortSignal) => Promise<void | ReadonlyMap<Input, unknown>>;
|
|
52
53
|
on_blocked?: (entry: MultiCreateEntry<Input, Result>) => void;
|
|
53
54
|
retry_same_request?: boolean;
|
|
54
55
|
before_request?: () => void;
|
|
@@ -68,6 +69,7 @@ export declare class MultiCreator<Input, Result> {
|
|
|
68
69
|
subscribe(listener: EventListener<MultiCreateSnapshot<Input, Result>>): () => void;
|
|
69
70
|
add(input: Input): string;
|
|
70
71
|
edit(id: string, input: Input): boolean;
|
|
72
|
+
skip(id: string, skipped: boolean): boolean;
|
|
71
73
|
remove(id: string): boolean;
|
|
72
74
|
start(): Promise<void>;
|
|
73
75
|
private begin;
|
|
@@ -84,7 +84,22 @@ export class MultiCreator {
|
|
|
84
84
|
const entry = this.entries.find((entry) => entry.id === id);
|
|
85
85
|
if (this.running || entry?.status !== MultiCreateStatus.Pending)
|
|
86
86
|
return false;
|
|
87
|
-
this.replace({
|
|
87
|
+
this.replace({
|
|
88
|
+
...entry,
|
|
89
|
+
input: structuredClone(input),
|
|
90
|
+
error: undefined,
|
|
91
|
+
skipped: false,
|
|
92
|
+
});
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
skip(id, skipped) {
|
|
96
|
+
const entry = this.entries.find((entry) => entry.id === id);
|
|
97
|
+
if (this.running ||
|
|
98
|
+
entry?.status !== MultiCreateStatus.Pending ||
|
|
99
|
+
entry.attempted ||
|
|
100
|
+
entry.created_id)
|
|
101
|
+
return false;
|
|
102
|
+
this.replace({ ...entry, skipped });
|
|
88
103
|
return true;
|
|
89
104
|
}
|
|
90
105
|
remove(id) {
|
|
@@ -98,7 +113,7 @@ export class MultiCreator {
|
|
|
98
113
|
start() {
|
|
99
114
|
if (this.running)
|
|
100
115
|
return this.completion;
|
|
101
|
-
const pending = this.entries.filter((entry) => entry.status === MultiCreateStatus.Pending);
|
|
116
|
+
const pending = this.entries.filter((entry) => entry.status === MultiCreateStatus.Pending && !entry.skipped);
|
|
102
117
|
return this.begin(pending);
|
|
103
118
|
}
|
|
104
119
|
begin(pending) {
|
|
@@ -142,8 +157,21 @@ export class MultiCreator {
|
|
|
142
157
|
try {
|
|
143
158
|
signal.throwIfAborted();
|
|
144
159
|
this.opts.before_request?.();
|
|
145
|
-
await this.opts.check?.(pending
|
|
160
|
+
const issues = await this.opts.check?.(pending
|
|
161
|
+
.filter((entry) => !entry.created_id)
|
|
162
|
+
.map((entry) => entry.input), signal);
|
|
146
163
|
signal.throwIfAborted();
|
|
164
|
+
for (const entry of pending) {
|
|
165
|
+
if (issues?.has(entry.input))
|
|
166
|
+
this.reject(entry, issues.get(entry.input));
|
|
167
|
+
else if (entry.error !== undefined)
|
|
168
|
+
this.replace({ ...entry, error: undefined });
|
|
169
|
+
}
|
|
170
|
+
// Leave unaffected entries ready for the user's next selection.
|
|
171
|
+
if (issues?.size) {
|
|
172
|
+
this.settle(pending);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
147
175
|
}
|
|
148
176
|
catch (error) {
|
|
149
177
|
for (const entry of pending)
|
|
@@ -155,6 +183,7 @@ export class MultiCreator {
|
|
|
155
183
|
let current = {
|
|
156
184
|
...entry,
|
|
157
185
|
status: MultiCreateStatus.Creating,
|
|
186
|
+
error: undefined,
|
|
158
187
|
};
|
|
159
188
|
this.replace(current);
|
|
160
189
|
try {
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { PaymentEntityTypeBill, PaymentEntityTypePayroll, type Bill, type Payroll, PSPPaymentStatusPending, PSPPaymentStatusPartial, PSPPaymentStatusFull, type CreditLedgerEntry, type Payment, type PmtAccount, type PmtBankAccount, type PmtCard, type PmtCustomer, type PmtPublicSession, type PmtRefund, type SortStripeBalanceResult } from "@fabriktor/schema";
|
|
2
2
|
import { type PaymentsOperator, type BillableOperator, type PayrollsOperator } from "@fabriktor/client";
|
|
3
|
+
import { PreActionBlocked } from "../core/pre-action.js";
|
|
3
4
|
import { type SearchOptions, type OwnerSearchOptions, type SearchPage } from "../core/search.js";
|
|
4
5
|
import { EventBus } from "../core/events.js";
|
|
5
6
|
import type { MultiCreateAttempt, MultiCreateCheckpoint } from "../core/multi-create.js";
|
|
6
7
|
export declare const PaymentsPreActionReason: {
|
|
7
8
|
readonly RecipientNotOnboarded: "recipient_not_onboarded";
|
|
8
9
|
};
|
|
10
|
+
export declare class PaymentRecipientNotReady extends PreActionBlocked {
|
|
11
|
+
readonly recipient_id: string;
|
|
12
|
+
constructor(recipient_id: string);
|
|
13
|
+
}
|
|
9
14
|
type PurchaseCreditsPayload = Parameters<PaymentsOperator["purchaseCredits"]>[0];
|
|
10
15
|
type OpenPublicSessionPayload = Parameters<PaymentsOperator["openPmtPublicSession"]>[0];
|
|
11
16
|
type PaymentFromPublicSessionPayload = Parameters<PaymentsOperator["paymentFromPublicSession"]>[0];
|
|
@@ -195,7 +200,7 @@ export type PaymentsFXOptions = {
|
|
|
195
200
|
events?: EventBus;
|
|
196
201
|
};
|
|
197
202
|
export interface PaymentsFXOperator {
|
|
198
|
-
checkEntityPaymentRecipients(entities: readonly ResolvePayableEntityOptions[], signal?: AbortSignal): Promise<
|
|
203
|
+
checkEntityPaymentRecipients(entities: readonly ResolvePayableEntityOptions[], signal?: AbortSignal): Promise<ReadonlyMap<ResolvePayableEntityOptions, PaymentRecipientNotReady>>;
|
|
199
204
|
resolvePayableEntity(opts: ResolvePayableEntityOptions): Promise<PayableEntityBalance>;
|
|
200
205
|
searchPayableEntities(opts: SearchPayableEntitiesOptions): Promise<SearchPage<PayableEntityBalance>>;
|
|
201
206
|
createEntityPayment(opts: CreateEntityPaymentOptions, attempt: MultiCreateAttempt): Promise<Payment>;
|
|
@@ -249,7 +254,7 @@ export declare class PaymentsFX implements PaymentsFXOperator {
|
|
|
249
254
|
searchPayableEntities(opts: SearchPayableEntitiesOptions): Promise<SearchPage<PayableEntityBalance>>;
|
|
250
255
|
resolvePayableEntity(opts: ResolvePayableEntityOptions): Promise<PayableEntityBalance>;
|
|
251
256
|
private findPayableEntity;
|
|
252
|
-
checkEntityPaymentRecipients(entities: readonly ResolvePayableEntityOptions[], signal?: AbortSignal): Promise<
|
|
257
|
+
checkEntityPaymentRecipients(entities: readonly ResolvePayableEntityOptions[], signal?: AbortSignal): Promise<ReadonlyMap<ResolvePayableEntityOptions, PaymentRecipientNotReady>>;
|
|
253
258
|
private checkPaymentRecipient;
|
|
254
259
|
createEntityPayment(opts: CreateEntityPaymentOptions, attempt: MultiCreateAttempt): Promise<Payment>;
|
|
255
260
|
createPayment(opts: CreatePaymentOptions): Promise<Payment>;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
6
|
import { CreditLedgerSourceAndroidIAP, CreditLedgerSourceAppleIAP, CreditLedgerSourceStripe, ZeroID, PaymentKindMain, PaymentEntityTypeBill, PaymentEntityTypePayroll, PSPActionableStatusSucceeded, PSPActionableStatusFailed, PSPActionableStatusCanceled, PSPPaymentStatusPending, PSPPaymentStatusPartial, PSPPaymentStatusFull, } from "@fabriktor/schema";
|
|
7
7
|
import { queryParams, } from "@fabriktor/client";
|
|
8
|
-
import { runPreActionCheck } from "../core/pre-action.js";
|
|
8
|
+
import { PreActionBlocked, isPreActionBlocked, runPreActionCheck, } from "../core/pre-action.js";
|
|
9
9
|
import { errResolveFailed, errValidation } from "../core/err.js";
|
|
10
10
|
import { normalizeIDs, requiredOperationID, resolvedID } from "../core/id.js";
|
|
11
11
|
import { runOwnerSearch, runSearch, } from "../core/search.js";
|
|
@@ -13,6 +13,13 @@ import { createEvent, EventBus } from "../core/events.js";
|
|
|
13
13
|
export const PaymentsPreActionReason = {
|
|
14
14
|
RecipientNotOnboarded: "recipient_not_onboarded",
|
|
15
15
|
};
|
|
16
|
+
export class PaymentRecipientNotReady extends PreActionBlocked {
|
|
17
|
+
recipient_id;
|
|
18
|
+
constructor(recipient_id) {
|
|
19
|
+
super(PaymentsPreActionReason.RecipientNotOnboarded);
|
|
20
|
+
this.recipient_id = recipient_id;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
16
23
|
const entityPaymentQuerySize = 30;
|
|
17
24
|
const publicAmountPattern = /^\d+$/;
|
|
18
25
|
const resourcePayment = "payment";
|
|
@@ -347,31 +354,54 @@ export class PaymentsFX {
|
|
|
347
354
|
}
|
|
348
355
|
// Check the whole selection before starting a batch; never retain readiness across actions.
|
|
349
356
|
async checkEntityPaymentRecipients(entities, signal) {
|
|
350
|
-
const recipients = new
|
|
357
|
+
const recipients = new Map();
|
|
358
|
+
const issues = new Map();
|
|
351
359
|
for (const opts of entities) {
|
|
352
360
|
signal?.throwIfAborted();
|
|
353
361
|
const entity = await this.findPayableEntity({ ...opts, signal });
|
|
354
|
-
recipients.
|
|
362
|
+
const entries = recipients.get(entity.recipient_id) ?? [];
|
|
363
|
+
entries.push(opts);
|
|
364
|
+
recipients.set(entity.recipient_id, entries);
|
|
355
365
|
}
|
|
356
|
-
for (const recipient_id of recipients)
|
|
357
|
-
|
|
366
|
+
for (const [recipient_id, entries] of recipients) {
|
|
367
|
+
try {
|
|
368
|
+
await this.checkPaymentRecipient(recipient_id, signal);
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
if (!(error instanceof PaymentRecipientNotReady))
|
|
372
|
+
throw error;
|
|
373
|
+
for (const entry of entries)
|
|
374
|
+
issues.set(entry, error);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return issues;
|
|
358
378
|
}
|
|
359
379
|
async checkPaymentRecipient(recipient_id, signal) {
|
|
360
380
|
if (!resolvedID(recipient_id))
|
|
361
381
|
throw errValidation({ msg: msgPaymentEntityInvalid });
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
msg: msgPmtAccountMissing,
|
|
382
|
+
try {
|
|
383
|
+
await runPreActionCheck({
|
|
384
|
+
reason: PaymentsPreActionReason.RecipientNotOnboarded,
|
|
385
|
+
allowed: async () => {
|
|
386
|
+
signal?.throwIfAborted();
|
|
387
|
+
const out = await this.payments.onboardingStatus({
|
|
388
|
+
recipient_id,
|
|
389
|
+
signal,
|
|
371
390
|
});
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
391
|
+
if (typeof out.value !== "boolean")
|
|
392
|
+
throw errResolveFailed({
|
|
393
|
+
resource: resourcePmtAccount,
|
|
394
|
+
msg: msgPmtAccountMissing,
|
|
395
|
+
});
|
|
396
|
+
return out.value;
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
if (isPreActionBlocked(error))
|
|
402
|
+
throw new PaymentRecipientNotReady(recipient_id);
|
|
403
|
+
throw error;
|
|
404
|
+
}
|
|
375
405
|
}
|
|
376
406
|
async createEntityPayment(opts, attempt) {
|
|
377
407
|
if (attempt.created_id)
|
|
@@ -470,7 +470,7 @@ export class PayrollsWorkspace {
|
|
|
470
470
|
source.employee_id === input.employee_id &&
|
|
471
471
|
source.currency === input.currency, "source");
|
|
472
472
|
}
|
|
473
|
-
await this.adjustments(actor, input.
|
|
473
|
+
await this.adjustments(actor, input.adjustment_ref, input.currency, signal);
|
|
474
474
|
break;
|
|
475
475
|
}
|
|
476
476
|
case PayrollResource.EmployeeConfigs:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabriktor/fx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.137",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"typescript": "^6.0.3"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@fabriktor/client": "0.0.
|
|
20
|
+
"@fabriktor/client": "0.0.95",
|
|
21
21
|
"@fabriktor/fx": "0.0.94",
|
|
22
|
-
"@fabriktor/schema": "0.0.
|
|
22
|
+
"@fabriktor/schema": "0.0.67"
|
|
23
23
|
}
|
|
24
24
|
}
|