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