emberflow 1.4.3 → 1.4.4
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/lib/index-utils.d.ts +3 -6
- package/lib/index-utils.js +17 -160
- package/lib/index-utils.js.map +1 -1
- package/lib/index.js +311 -297
- package/lib/index.js.map +1 -1
- package/lib/logics/view-logics.js +1 -1
- package/lib/logics/view-logics.js.map +1 -1
- package/lib/sample-custom/business-logics.js +1 -1
- package/lib/sample-custom/business-logics.js.map +1 -1
- package/lib/tests/index-utils.test.js +32 -487
- package/lib/tests/index-utils.test.js.map +1 -1
- package/lib/tests/index.test.js +90 -80
- package/lib/tests/index.test.js.map +1 -1
- package/lib/tests/logics/view-logics.test.js +1 -1
- package/lib/tests/logics/view-logics.test.js.map +1 -1
- package/lib/tests/utils/distribution.test.js +13 -13
- package/lib/tests/utils/distribution.test.js.map +1 -1
- package/lib/tests/utils/forms.test.js +0 -23
- package/lib/tests/utils/forms.test.js.map +1 -1
- package/lib/types.d.ts +13 -3
- package/lib/utils/distribution.d.ts +2 -1
- package/lib/utils/distribution.js +53 -53
- package/lib/utils/distribution.js.map +1 -1
- package/lib/utils/forms.js +0 -4
- package/lib/utils/forms.js.map +1 -1
- package/package.json +1 -1
- package/src/sample-custom/business-logics.ts +1 -1
package/lib/index.js
CHANGED
|
@@ -242,92 +242,104 @@ async function onFormSubmit(event) {
|
|
|
242
242
|
await formRef.update({ "@status": "validation-error", "@messages": validationResult });
|
|
243
243
|
return;
|
|
244
244
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
245
|
+
let runStatus = { status: "running", logicResults: [] };
|
|
246
|
+
let errorMessage = "";
|
|
247
|
+
const actionRef = exports._mockable.initActionRef(formId);
|
|
248
|
+
await exports.db.runTransaction(async (txn) => {
|
|
249
|
+
const docRef = exports.db.doc(docPath);
|
|
250
|
+
const document = (await txn.get(docRef)).data() || {};
|
|
251
|
+
const formModifiedFields = (0, index_utils_1.getFormModifiedFields)(form, document);
|
|
252
|
+
let user;
|
|
253
|
+
if (!isServiceAccount) {
|
|
254
|
+
const userRef = exports.db.doc(`users/${userId}`);
|
|
255
|
+
user = (await txn.get(userRef)).data();
|
|
256
|
+
if (!user) {
|
|
257
|
+
const message = "No user data found";
|
|
258
|
+
console.warn(message);
|
|
259
|
+
await formRef.update({ "@status": "error", "@messages": message });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
255
262
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
user = { "@id": userId };
|
|
259
|
-
}
|
|
260
|
-
console.info("Validating Security");
|
|
261
|
-
const securityFn = (0, index_utils_1.getSecurityFn)(entity);
|
|
262
|
-
if (securityFn) {
|
|
263
|
-
const securityFnStart = performance.now();
|
|
264
|
-
const securityResult = await securityFn(entity, docPath, document, actionType, formModifiedFields, user);
|
|
265
|
-
const securityFnEnd = performance.now();
|
|
266
|
-
const securityLogicResult = {
|
|
267
|
-
name: "securityFn",
|
|
268
|
-
status: "finished",
|
|
269
|
-
documents: [],
|
|
270
|
-
execTime: securityFnEnd - securityFnStart,
|
|
271
|
-
};
|
|
272
|
-
logicResults.push(securityLogicResult);
|
|
273
|
-
if (securityResult.status === "rejected") {
|
|
274
|
-
console.log(`Security check failed: ${securityResult.message}`);
|
|
275
|
-
await formRef.update({ "@status": "security-error", "@messages": securityResult.message });
|
|
276
|
-
return;
|
|
263
|
+
else {
|
|
264
|
+
user = { "@id": userId };
|
|
277
265
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
266
|
+
console.info("Validating Security");
|
|
267
|
+
const securityFn = (0, index_utils_1.getSecurityFn)(entity);
|
|
268
|
+
if (securityFn) {
|
|
269
|
+
const securityFnStart = performance.now();
|
|
270
|
+
const securityResult = await securityFn(entity, docPath, document, actionType, formModifiedFields, user);
|
|
271
|
+
const securityFnEnd = performance.now();
|
|
272
|
+
const securityLogicResult = {
|
|
273
|
+
name: "securityFn",
|
|
274
|
+
status: "finished",
|
|
275
|
+
documents: [],
|
|
276
|
+
execTime: securityFnEnd - securityFnStart,
|
|
277
|
+
};
|
|
278
|
+
logicResults.push(securityLogicResult);
|
|
279
|
+
if (securityResult.status === "rejected") {
|
|
280
|
+
console.log(`Security check failed: ${securityResult.message}`);
|
|
281
|
+
await formRef.update({ "@status": "security-error", "@messages": securityResult.message });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Check for delay
|
|
286
|
+
console.info("Checking for delay");
|
|
287
|
+
const delay = form["@delay"];
|
|
288
|
+
if (delay) {
|
|
289
|
+
const delayStart = performance.now();
|
|
290
|
+
const cancelled = await (0, index_utils_1.delayFormSubmissionAndCheckIfCancelled)(delay, formRef);
|
|
291
|
+
const delayEnd = performance.now();
|
|
292
|
+
const delayLogicResult = {
|
|
293
|
+
name: "delayFormSubmission",
|
|
294
|
+
status: "finished",
|
|
295
|
+
documents: [],
|
|
296
|
+
execTime: delayEnd - delayStart,
|
|
297
|
+
};
|
|
298
|
+
logicResults.push(delayLogicResult);
|
|
299
|
+
if (cancelled) {
|
|
300
|
+
await formRef.update({ "@status": "cancelled" });
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
await formRef.update({ "@status": "processing" });
|
|
305
|
+
const status = "processing";
|
|
306
|
+
const timeCreated = exports._mockable.createNowTimestamp();
|
|
307
|
+
console.info("Creating Action");
|
|
308
|
+
const eventContext = {
|
|
309
|
+
id: event.id,
|
|
310
|
+
uid: userId,
|
|
311
|
+
formId,
|
|
312
|
+
docId: entityId,
|
|
313
|
+
docPath,
|
|
314
|
+
entity,
|
|
315
|
+
};
|
|
316
|
+
const action = {
|
|
317
|
+
eventContext,
|
|
318
|
+
actionType,
|
|
319
|
+
document,
|
|
320
|
+
status,
|
|
321
|
+
timeCreated,
|
|
322
|
+
modifiedFields: formModifiedFields,
|
|
323
|
+
user,
|
|
324
|
+
};
|
|
325
|
+
await formRef.update({ "@status": "submitted" });
|
|
326
|
+
console.info("Running Business Logics");
|
|
327
|
+
const businessLogicStart = performance.now();
|
|
328
|
+
runStatus = await (0, index_utils_1.runBusinessLogics)(txn.get, action);
|
|
329
|
+
const businessLogicEnd = performance.now();
|
|
330
|
+
const businessLogicLogicResult = {
|
|
331
|
+
name: "runBusinessLogics",
|
|
288
332
|
status: "finished",
|
|
289
333
|
documents: [],
|
|
290
|
-
execTime:
|
|
334
|
+
execTime: businessLogicEnd - businessLogicStart,
|
|
291
335
|
};
|
|
292
|
-
logicResults.push(
|
|
293
|
-
|
|
294
|
-
await formRef.update({ "@status": "cancelled" });
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
await formRef.update({ "@status": "processing" });
|
|
299
|
-
const status = "processing";
|
|
300
|
-
const timeCreated = exports._mockable.createNowTimestamp();
|
|
301
|
-
console.info("Creating Action");
|
|
302
|
-
const eventContext = {
|
|
303
|
-
id: event.id,
|
|
304
|
-
uid: userId,
|
|
305
|
-
formId,
|
|
306
|
-
docId: entityId,
|
|
307
|
-
docPath,
|
|
308
|
-
entity,
|
|
309
|
-
};
|
|
310
|
-
const action = {
|
|
311
|
-
eventContext,
|
|
312
|
-
actionType,
|
|
313
|
-
document,
|
|
314
|
-
status,
|
|
315
|
-
timeCreated,
|
|
316
|
-
modifiedFields: formModifiedFields,
|
|
317
|
-
user,
|
|
318
|
-
};
|
|
319
|
-
const actionRef = exports._mockable.initActionRef(formId);
|
|
320
|
-
await actionRef.set(action);
|
|
321
|
-
await formRef.update({ "@status": "submitted" });
|
|
322
|
-
console.info("Running Business Logics");
|
|
323
|
-
let errorMessage = "";
|
|
324
|
-
const businessLogicStart = performance.now();
|
|
325
|
-
const runStatus = await (0, index_utils_1.runBusinessLogics)(actionRef, action, async (actionRef, logicResults, page) => {
|
|
336
|
+
logicResults.push(businessLogicLogicResult);
|
|
337
|
+
await actionRef.set(action);
|
|
326
338
|
async function saveLogicResults() {
|
|
327
|
-
for (let i = 0; i < logicResults.length; i++) {
|
|
328
|
-
const _a = logicResults[i], { documents } = _a, logicResult = __rest(_a, ["documents"]);
|
|
339
|
+
for (let i = 0; i < runStatus.logicResults.length; i++) {
|
|
340
|
+
const _a = runStatus.logicResults[i], { documents } = _a, logicResult = __rest(_a, ["documents"]);
|
|
329
341
|
const logicResultsRef = actionRef.collection("logicResults")
|
|
330
|
-
.doc(`${actionRef.id}-${
|
|
342
|
+
.doc(`${actionRef.id}-${i}`);
|
|
331
343
|
await logicResultsRef.set(logicResult);
|
|
332
344
|
const documentsRef = logicResultsRef.collection("documents");
|
|
333
345
|
for (let j = 0; j < documents.length; j++) {
|
|
@@ -337,228 +349,42 @@ async function onFormSubmit(event) {
|
|
|
337
349
|
}
|
|
338
350
|
await saveLogicResults();
|
|
339
351
|
function updateErrorMessage() {
|
|
340
|
-
const errorLogicResults = logicResults.filter((result) => result.status === "error");
|
|
352
|
+
const errorLogicResults = runStatus.logicResults.filter((result) => result.status === "error");
|
|
341
353
|
if (errorLogicResults.length > 0) {
|
|
342
354
|
errorMessage = errorMessage + errorLogicResults.map((result) => result.message).join("\n");
|
|
343
355
|
}
|
|
344
356
|
}
|
|
345
357
|
updateErrorMessage();
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const dstPathLogicDocsWithJournalEntriesMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(logicResultDocsWithJournalEntries);
|
|
357
|
-
for (const [dstPath, logicDocs] of dstPathLogicDocsWithJournalEntriesMap) {
|
|
358
|
-
const docId = dstPath.split("/").pop();
|
|
359
|
-
if (!docId) {
|
|
360
|
-
console.error("Dst path has no docId");
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
for (const logicDoc of logicDocs) {
|
|
364
|
-
const { doc, instructions, journalEntries, action, skipRunViewLogics, } = logicDoc;
|
|
365
|
-
if (!journalEntries) {
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
const accounts = new Set(journalEntries.map((entry) => entry.ledgerEntries).flat()
|
|
369
|
-
.map((entry) => entry.account));
|
|
370
|
-
if (Object.keys(doc || {}).some((key) => accounts.has(key))) {
|
|
371
|
-
console.error("Doc cannot have keys that are the same as account names");
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
if (Object.keys(instructions || {}).some((key) => accounts.has(key))) {
|
|
375
|
-
console.error("Instructions cannot have keys that are the same as account names");
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
for (let i = 0; i < journalEntries.length; i++) {
|
|
379
|
-
const { ledgerEntries, recordEntry, equation, date, } = journalEntries[i];
|
|
380
|
-
const consolidatedPerAccount = ledgerEntries
|
|
381
|
-
.reduce((acc, entry) => {
|
|
382
|
-
const { account } = entry;
|
|
383
|
-
if (acc[account]) {
|
|
384
|
-
acc[account].debit += entry.debit;
|
|
385
|
-
acc[account].credit += entry.credit;
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
acc[account] = {
|
|
389
|
-
debit: entry.debit,
|
|
390
|
-
credit: entry.credit,
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
return acc;
|
|
394
|
-
}, {});
|
|
395
|
-
// loop through keys of consolidatedPerAccount
|
|
396
|
-
const totalCreditDebit = Object.entries(consolidatedPerAccount)
|
|
397
|
-
.reduce((acc, [account, { debit, credit }]) => {
|
|
398
|
-
return {
|
|
399
|
-
debit: acc.debit + debit,
|
|
400
|
-
credit: acc.credit + credit,
|
|
401
|
-
};
|
|
402
|
-
}, { debit: 0, credit: 0 });
|
|
403
|
-
if (totalCreditDebit.debit !== totalCreditDebit.credit) {
|
|
404
|
-
console.error("Debit and credit should be equal");
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
await exports.db.runTransaction(async (transaction) => {
|
|
408
|
-
const docRef = exports.db.doc(dstPath);
|
|
409
|
-
const currData = (await transaction.get(docRef)).data();
|
|
410
|
-
let instructionsDbValues;
|
|
411
|
-
if (instructions) {
|
|
412
|
-
instructionsDbValues = await (0, distribution_1.convertInstructionsToDbValues)(instructions);
|
|
413
|
-
}
|
|
414
|
-
const finalDoc = Object.assign(Object.assign({}, (doc ? doc : {})), (instructionsDbValues ? instructionsDbValues : {}));
|
|
415
|
-
if (currData) {
|
|
416
|
-
transaction.update(docRef, Object.assign(Object.assign({}, finalDoc), { "@forDeletionLater": true }));
|
|
417
|
-
}
|
|
418
|
-
else {
|
|
419
|
-
transaction.set(docRef, Object.assign(Object.assign({}, finalDoc), { "@forDeletionLater": true }));
|
|
420
|
-
}
|
|
421
|
-
const [leftSide, ..._] = equation.split("=");
|
|
422
|
-
Object.entries(consolidatedPerAccount).forEach(([account, { debit, credit }]) => {
|
|
423
|
-
const increment = leftSide.includes(account) ? debit - credit : credit - debit;
|
|
424
|
-
if (increment === 0) {
|
|
425
|
-
transaction.update(docRef, {
|
|
426
|
-
"@forDeletionLater": FieldValue.delete(),
|
|
427
|
-
});
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
const accountVal = ((currData === null || currData === void 0 ? void 0 : currData[account]) || 0) + increment;
|
|
431
|
-
if (accountVal < 0) {
|
|
432
|
-
throw new Error("Account value cannot be negative");
|
|
433
|
-
}
|
|
434
|
-
finalDoc[account] = accountVal;
|
|
435
|
-
transaction.update(docRef, {
|
|
436
|
-
[account]: accountVal,
|
|
437
|
-
"@forDeletionLater": FieldValue.delete(),
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
if (Object.keys(finalDoc).length > 0 && !skipRunViewLogics &&
|
|
441
|
-
["create", "merge"].includes(action)) {
|
|
442
|
-
logicDoc.doc = finalDoc;
|
|
443
|
-
await (0, view_logics_1.queueRunViewLogics)(logicDoc);
|
|
444
|
-
}
|
|
445
|
-
if (recordEntry) {
|
|
446
|
-
for (let j = 0; j < ledgerEntries.length; j++) {
|
|
447
|
-
const { account, debit, credit, description } = ledgerEntries[j];
|
|
448
|
-
const journalEntryId = docId + i;
|
|
449
|
-
const ledgerEntryId = journalEntryId + j;
|
|
450
|
-
const ledgerEntryDoc = Object.assign({ journalEntryId,
|
|
451
|
-
account,
|
|
452
|
-
credit,
|
|
453
|
-
debit,
|
|
454
|
-
equation,
|
|
455
|
-
date }, (description && { description }));
|
|
456
|
-
const entryRef = docRef.collection("@ledgers").doc(ledgerEntryId);
|
|
457
|
-
transaction.set(entryRef, ledgerEntryDoc);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
await writeJournalEntriesFirst();
|
|
466
|
-
async function distributeTransactionalLogicResults() {
|
|
467
|
-
const transactionalResults = logicResults.filter((result) => result.transactional);
|
|
468
|
-
if (transactionalResults.length === 0) {
|
|
469
|
-
console.info("No transactional logic results to distribute");
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
// We always distribute transactional results first
|
|
473
|
-
const transactionalDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(transactionalResults.map((result) => result.documents).flat().filter((doc) => !doc.journalEntries || doc.journalEntries && doc.action === "delete"));
|
|
474
|
-
// Write to firestore in one transaction
|
|
475
|
-
await exports.db.runTransaction(async (transaction) => {
|
|
476
|
-
for (const [dstPath, logicDocs] of transactionalDstPathLogicDocsMap) {
|
|
477
|
-
for (const logicDoc of logicDocs) {
|
|
478
|
-
const docRef = exports.db.doc(dstPath);
|
|
479
|
-
const { action } = logicDoc;
|
|
480
|
-
if (action === "create") {
|
|
481
|
-
transaction.set(docRef, logicDoc.doc);
|
|
482
|
-
}
|
|
483
|
-
else if (action === "merge") {
|
|
484
|
-
transaction.update(docRef, logicDoc.doc);
|
|
485
|
-
}
|
|
486
|
-
else if (action === "delete") {
|
|
487
|
-
transaction.delete(docRef);
|
|
488
|
-
}
|
|
489
|
-
if (logicDoc.instructions) {
|
|
490
|
-
const { updateData, removeData } = await (0, distribution_1.convertInstructionsToDbValues)(logicDoc.instructions);
|
|
491
|
-
if (Object.keys(updateData).length > 0) {
|
|
492
|
-
transaction.update(docRef, updateData);
|
|
493
|
-
}
|
|
494
|
-
if (Object.keys(removeData).length > 0) {
|
|
495
|
-
transaction.update(docRef, removeData);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
await distributeTransactionalLogicResults();
|
|
503
|
-
async function distributeNonTransactionalLogicResults() {
|
|
504
|
-
const nonTransactionalResults = logicResults.filter((result) => !result.transactional);
|
|
505
|
-
console.info(`Group logic docs by priority: ${nonTransactionalResults.length}`);
|
|
506
|
-
const { highPriorityDocs, normalPriorityDocs, lowPriorityDocs } = nonTransactionalResults
|
|
507
|
-
.map((result) => result.documents)
|
|
508
|
-
.flat()
|
|
509
|
-
.filter((doc) => !doc.journalEntries || doc.journalEntries && doc.action === "delete")
|
|
510
|
-
.reduce((acc, doc) => {
|
|
511
|
-
if (doc.priority === "high") {
|
|
512
|
-
acc.highPriorityDocs.push(doc);
|
|
513
|
-
}
|
|
514
|
-
else if (!doc.priority || doc.priority === "normal") {
|
|
515
|
-
acc.normalPriorityDocs.push(doc);
|
|
516
|
-
}
|
|
517
|
-
else {
|
|
518
|
-
acc.lowPriorityDocs.push(doc);
|
|
519
|
-
}
|
|
520
|
-
return acc;
|
|
521
|
-
}, {
|
|
522
|
-
highPriorityDocs: [],
|
|
523
|
-
normalPriorityDocs: [],
|
|
524
|
-
lowPriorityDocs: [],
|
|
525
|
-
});
|
|
526
|
-
console.info(`Consolidating and Distributing High Priority Logic Results: ${highPriorityDocs.length}`);
|
|
527
|
-
const highPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(highPriorityDocs);
|
|
528
|
-
const { docsByDocPath: highPriorityDocsByDocPath, otherDocsByDocPath: highPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(highPriorityDstPathLogicDocsMap, docPath);
|
|
529
|
-
await (0, index_utils_1.distribute)(highPriorityDocsByDocPath);
|
|
530
|
-
await (0, index_utils_1.distribute)(highPriorityOtherDocsByDocPath);
|
|
531
|
-
if (page === 0) {
|
|
532
|
-
await formRef.update({ "@status": "finished" });
|
|
533
|
-
}
|
|
534
|
-
console.info(`Consolidating and Distributing Normal Priority Logic Results: ${normalPriorityDocs.length}`);
|
|
535
|
-
const normalPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(normalPriorityDocs);
|
|
536
|
-
const { docsByDocPath: normalPriorityDocsByDocPath, otherDocsByDocPath: normalPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(normalPriorityDstPathLogicDocsMap, docPath);
|
|
537
|
-
await (0, index_utils_1.distribute)(normalPriorityDocsByDocPath);
|
|
538
|
-
await (0, index_utils_1.distributeLater)(normalPriorityOtherDocsByDocPath);
|
|
539
|
-
console.info(`Consolidating and Distributing Low Priority Logic Results: ${lowPriorityDocs.length}`);
|
|
540
|
-
const lowPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(lowPriorityDocs);
|
|
541
|
-
const { docsByDocPath: lowPriorityDocsByDocPath, otherDocsByDocPath: lowPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(lowPriorityDstPathLogicDocsMap, docPath);
|
|
542
|
-
await (0, index_utils_1.distributeLater)(lowPriorityDocsByDocPath);
|
|
543
|
-
await (0, index_utils_1.distributeLater)(lowPriorityOtherDocsByDocPath);
|
|
544
|
-
}
|
|
545
|
-
await distributeNonTransactionalLogicResults();
|
|
358
|
+
const distributeTransactionalLogicResultsStart = performance.now();
|
|
359
|
+
await distributeFnTransactional(txn, runStatus.logicResults);
|
|
360
|
+
const distributeTransactionalLogicResultsEnd = performance.now();
|
|
361
|
+
const distributeTransactionalLogicResults = {
|
|
362
|
+
name: "distributeTransactionalLogicResults",
|
|
363
|
+
status: "finished",
|
|
364
|
+
documents: [],
|
|
365
|
+
execTime: distributeTransactionalLogicResultsEnd - distributeTransactionalLogicResultsStart,
|
|
366
|
+
};
|
|
367
|
+
logicResults.push(distributeTransactionalLogicResults);
|
|
546
368
|
});
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
369
|
+
const distributeNonTransactionalLogicResultsStart = performance.now();
|
|
370
|
+
await distributeNonTransactionalLogicResults(runStatus.logicResults, docPath);
|
|
371
|
+
const distributeNonTransactionalLogicResultsEnd = performance.now();
|
|
372
|
+
const distributeNonTransactionalPerfLogicResults = {
|
|
373
|
+
name: "distributeNonTransactionalLogicResults",
|
|
550
374
|
status: "finished",
|
|
551
375
|
documents: [],
|
|
552
|
-
execTime:
|
|
376
|
+
execTime: distributeNonTransactionalLogicResultsEnd - distributeNonTransactionalLogicResultsStart,
|
|
553
377
|
};
|
|
554
|
-
logicResults.push(
|
|
555
|
-
|
|
556
|
-
await formRef.update({ "@status": "cancelled", "@messages": "cancel-then-retry received " +
|
|
557
|
-
"from business logic" });
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
378
|
+
logicResults.push(distributeNonTransactionalPerfLogicResults);
|
|
379
|
+
await formRef.update({ "@status": "finished" });
|
|
560
380
|
const end = performance.now();
|
|
561
381
|
const execTime = end - start;
|
|
382
|
+
if (errorMessage) {
|
|
383
|
+
await actionRef.update({ status: "finished-with-error", message: errorMessage, execTime: execTime });
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
await actionRef.update({ status: "finished", execTime: execTime });
|
|
387
|
+
}
|
|
562
388
|
const onFormSubmitLogicResult = {
|
|
563
389
|
name: "onFormSubmit",
|
|
564
390
|
status: "finished",
|
|
@@ -566,12 +392,7 @@ async function onFormSubmit(event) {
|
|
|
566
392
|
execTime,
|
|
567
393
|
};
|
|
568
394
|
logicResults.push(onFormSubmitLogicResult);
|
|
569
|
-
|
|
570
|
-
await actionRef.update({ status: "finished-with-error", message: errorMessage, execTime: execTime });
|
|
571
|
-
}
|
|
572
|
-
else {
|
|
573
|
-
await actionRef.update({ status: "finished", execTime: execTime });
|
|
574
|
-
}
|
|
395
|
+
await index_utils_1._mockable.createMetricExecution(logicResults);
|
|
575
396
|
console.info("Finished");
|
|
576
397
|
}
|
|
577
398
|
catch (error) {
|
|
@@ -587,9 +408,202 @@ async function onFormSubmit(event) {
|
|
|
587
408
|
logicResults.push(onFormSubmitLogicResult);
|
|
588
409
|
await formRef.update({ "@status": "error", "@messages": error, "execTime": execTime });
|
|
589
410
|
}
|
|
590
|
-
await index_utils_1._mockable.createMetricExecution(logicResults);
|
|
591
411
|
}
|
|
592
412
|
exports.onFormSubmit = onFormSubmit;
|
|
413
|
+
async function distributeNonTransactionalLogicResults(logicResults, docPath) {
|
|
414
|
+
const nonTransactionalResults = logicResults.filter((result) => !result.transactional);
|
|
415
|
+
console.info(`Group logic docs by priority: ${nonTransactionalResults.length}`);
|
|
416
|
+
const { highPriorityDocs, normalPriorityDocs, lowPriorityDocs } = nonTransactionalResults
|
|
417
|
+
.map((result) => result.documents)
|
|
418
|
+
.flat()
|
|
419
|
+
.filter((doc) => !doc.journalEntries || doc.journalEntries && doc.action === "delete")
|
|
420
|
+
.reduce((acc, doc) => {
|
|
421
|
+
if (doc.priority === "high") {
|
|
422
|
+
acc.highPriorityDocs.push(doc);
|
|
423
|
+
}
|
|
424
|
+
else if (!doc.priority || doc.priority === "normal") {
|
|
425
|
+
acc.normalPriorityDocs.push(doc);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
acc.lowPriorityDocs.push(doc);
|
|
429
|
+
}
|
|
430
|
+
return acc;
|
|
431
|
+
}, {
|
|
432
|
+
highPriorityDocs: [],
|
|
433
|
+
normalPriorityDocs: [],
|
|
434
|
+
lowPriorityDocs: [],
|
|
435
|
+
});
|
|
436
|
+
console.info(`Consolidating and Distributing High Priority Logic Results: ${highPriorityDocs.length}`);
|
|
437
|
+
const highPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(highPriorityDocs);
|
|
438
|
+
const { docsByDocPath: highPriorityDocsByDocPath, otherDocsByDocPath: highPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(highPriorityDstPathLogicDocsMap, docPath);
|
|
439
|
+
await (0, index_utils_1.distributeFnNonTransactional)(highPriorityDocsByDocPath);
|
|
440
|
+
await (0, index_utils_1.distributeFnNonTransactional)(highPriorityOtherDocsByDocPath);
|
|
441
|
+
console.info(`Consolidating and Distributing Normal Priority Logic Results: ${normalPriorityDocs.length}`);
|
|
442
|
+
const normalPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(normalPriorityDocs);
|
|
443
|
+
const { docsByDocPath: normalPriorityDocsByDocPath, otherDocsByDocPath: normalPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(normalPriorityDstPathLogicDocsMap, docPath);
|
|
444
|
+
await (0, index_utils_1.distributeFnNonTransactional)(normalPriorityDocsByDocPath);
|
|
445
|
+
await (0, index_utils_1.distributeLater)(normalPriorityOtherDocsByDocPath);
|
|
446
|
+
console.info(`Consolidating and Distributing Low Priority Logic Results: ${lowPriorityDocs.length}`);
|
|
447
|
+
const lowPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(lowPriorityDocs);
|
|
448
|
+
const { docsByDocPath: lowPriorityDocsByDocPath, otherDocsByDocPath: lowPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(lowPriorityDstPathLogicDocsMap, docPath);
|
|
449
|
+
await (0, index_utils_1.distributeLater)(lowPriorityDocsByDocPath);
|
|
450
|
+
await (0, index_utils_1.distributeLater)(lowPriorityOtherDocsByDocPath);
|
|
451
|
+
}
|
|
452
|
+
async function distributeFnTransactional(txn, logicResults) {
|
|
453
|
+
async function writeJournalEntriesFirst() {
|
|
454
|
+
// Gather all logicResultDoc with journalEntries
|
|
455
|
+
const logicResultDocsWithJournalEntries = logicResults
|
|
456
|
+
.map((result) => result.documents).flat()
|
|
457
|
+
.filter((logicResultDoc) => logicResultDoc.journalEntries &&
|
|
458
|
+
logicResultDoc.action !== "delete");
|
|
459
|
+
if (logicResultDocsWithJournalEntries.length === 0) {
|
|
460
|
+
console.info("No journal entries to write");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const dstPathLogicDocsWithJournalEntriesMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(logicResultDocsWithJournalEntries);
|
|
464
|
+
for (const [dstPath, logicDocs] of dstPathLogicDocsWithJournalEntriesMap) {
|
|
465
|
+
const docId = dstPath.split("/").pop();
|
|
466
|
+
if (!docId) {
|
|
467
|
+
console.error("Dst path has no docId");
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
for (const logicDoc of logicDocs) {
|
|
471
|
+
const { doc, instructions, journalEntries, action, skipRunViewLogics, } = logicDoc;
|
|
472
|
+
if (!journalEntries) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
const accounts = new Set(journalEntries.map((entry) => entry.ledgerEntries).flat()
|
|
476
|
+
.map((entry) => entry.account));
|
|
477
|
+
if (Object.keys(doc || {}).some((key) => accounts.has(key))) {
|
|
478
|
+
console.error("Doc cannot have keys that are the same as account names");
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (Object.keys(instructions || {}).some((key) => accounts.has(key))) {
|
|
482
|
+
console.error("Instructions cannot have keys that are the same as account names");
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
for (let i = 0; i < journalEntries.length; i++) {
|
|
486
|
+
const { ledgerEntries, recordEntry, equation, date, } = journalEntries[i];
|
|
487
|
+
const consolidatedPerAccount = ledgerEntries
|
|
488
|
+
.reduce((acc, entry) => {
|
|
489
|
+
const { account } = entry;
|
|
490
|
+
if (acc[account]) {
|
|
491
|
+
acc[account].debit += entry.debit;
|
|
492
|
+
acc[account].credit += entry.credit;
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
acc[account] = {
|
|
496
|
+
debit: entry.debit,
|
|
497
|
+
credit: entry.credit,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return acc;
|
|
501
|
+
}, {});
|
|
502
|
+
// loop through keys of consolidatedPerAccount
|
|
503
|
+
const totalCreditDebit = Object.entries(consolidatedPerAccount)
|
|
504
|
+
.reduce((acc, [account, { debit, credit }]) => {
|
|
505
|
+
return {
|
|
506
|
+
debit: acc.debit + debit,
|
|
507
|
+
credit: acc.credit + credit,
|
|
508
|
+
};
|
|
509
|
+
}, { debit: 0, credit: 0 });
|
|
510
|
+
if (totalCreditDebit.debit !== totalCreditDebit.credit) {
|
|
511
|
+
console.error("Debit and credit should be equal");
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
const docRef = exports.db.doc(dstPath);
|
|
515
|
+
const currData = (await txn.get(docRef)).data();
|
|
516
|
+
let instructionsDbValues;
|
|
517
|
+
if (instructions) {
|
|
518
|
+
instructionsDbValues = await (0, distribution_1.convertInstructionsToDbValues)(txn, instructions);
|
|
519
|
+
}
|
|
520
|
+
const finalDoc = Object.assign(Object.assign({}, (doc ? doc : {})), (instructionsDbValues ? instructionsDbValues : {}));
|
|
521
|
+
if (currData) {
|
|
522
|
+
txn.update(docRef, Object.assign(Object.assign({}, finalDoc), { "@forDeletionLater": true }));
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
txn.set(docRef, Object.assign(Object.assign({}, finalDoc), { "@forDeletionLater": true }));
|
|
526
|
+
}
|
|
527
|
+
const [leftSide, ..._] = equation.split("=");
|
|
528
|
+
Object.entries(consolidatedPerAccount).forEach(([account, { debit, credit }]) => {
|
|
529
|
+
const increment = leftSide.includes(account) ? debit - credit : credit - debit;
|
|
530
|
+
if (increment === 0) {
|
|
531
|
+
txn.update(docRef, {
|
|
532
|
+
"@forDeletionLater": FieldValue.delete(),
|
|
533
|
+
});
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const accountVal = ((currData === null || currData === void 0 ? void 0 : currData[account]) || 0) + increment;
|
|
537
|
+
if (accountVal < 0) {
|
|
538
|
+
throw new Error("Account value cannot be negative");
|
|
539
|
+
}
|
|
540
|
+
finalDoc[account] = accountVal;
|
|
541
|
+
txn.update(docRef, {
|
|
542
|
+
[account]: accountVal,
|
|
543
|
+
"@forDeletionLater": FieldValue.delete(),
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
if (Object.keys(finalDoc).length > 0 && !skipRunViewLogics &&
|
|
547
|
+
["create", "merge"].includes(action)) {
|
|
548
|
+
logicDoc.doc = finalDoc;
|
|
549
|
+
await (0, view_logics_1.queueRunViewLogics)(logicDoc);
|
|
550
|
+
}
|
|
551
|
+
if (recordEntry) {
|
|
552
|
+
for (let j = 0; j < ledgerEntries.length; j++) {
|
|
553
|
+
const { account, debit, credit, description } = ledgerEntries[j];
|
|
554
|
+
const journalEntryId = docId + i;
|
|
555
|
+
const ledgerEntryId = journalEntryId + j;
|
|
556
|
+
const ledgerEntryDoc = Object.assign({ journalEntryId,
|
|
557
|
+
account,
|
|
558
|
+
credit,
|
|
559
|
+
debit,
|
|
560
|
+
equation,
|
|
561
|
+
date }, (description && { description }));
|
|
562
|
+
const entryRef = docRef.collection("@ledgers").doc(ledgerEntryId);
|
|
563
|
+
txn.set(entryRef, ledgerEntryDoc);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
await writeJournalEntriesFirst();
|
|
571
|
+
async function distributeTransactionalLogicResults() {
|
|
572
|
+
const transactionalResults = logicResults.filter((result) => result.transactional);
|
|
573
|
+
if (transactionalResults.length === 0) {
|
|
574
|
+
console.info("No transactional logic results to distribute");
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
// We always distribute transactional results first
|
|
578
|
+
const transactionalDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(transactionalResults.map((result) => result.documents).flat().filter((doc) => !doc.journalEntries || doc.journalEntries && doc.action === "delete"));
|
|
579
|
+
// Write to firestore in one transaction
|
|
580
|
+
for (const [dstPath, logicDocs] of transactionalDstPathLogicDocsMap) {
|
|
581
|
+
for (const logicDoc of logicDocs) {
|
|
582
|
+
const docRef = exports.db.doc(dstPath);
|
|
583
|
+
const { action } = logicDoc;
|
|
584
|
+
if (action === "create") {
|
|
585
|
+
txn.set(docRef, logicDoc.doc);
|
|
586
|
+
}
|
|
587
|
+
else if (action === "merge") {
|
|
588
|
+
txn.update(docRef, logicDoc.doc);
|
|
589
|
+
}
|
|
590
|
+
else if (action === "delete") {
|
|
591
|
+
txn.delete(docRef);
|
|
592
|
+
}
|
|
593
|
+
if (logicDoc.instructions) {
|
|
594
|
+
const { updateData, removeData } = await (0, distribution_1.convertInstructionsToDbValues)(txn, logicDoc.instructions);
|
|
595
|
+
if (Object.keys(updateData).length > 0) {
|
|
596
|
+
txn.update(docRef, updateData);
|
|
597
|
+
}
|
|
598
|
+
if (Object.keys(removeData).length > 0) {
|
|
599
|
+
txn.update(docRef, removeData);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
await distributeTransactionalLogicResults();
|
|
606
|
+
}
|
|
593
607
|
const onUserRegister = async (user) => {
|
|
594
608
|
await exports.db.doc(`users/${user.uid}`).set({
|
|
595
609
|
"@id": user.uid,
|