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.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
- const document = (await exports.db.doc(docPath).get()).data() || {};
246
- const formModifiedFields = (0, index_utils_1.getFormModifiedFields)(form, document);
247
- let user;
248
- if (!isServiceAccount) {
249
- user = (await exports.db.doc(`users/${userId}`).get()).data();
250
- if (!user) {
251
- const message = "No user data found";
252
- console.warn(message);
253
- await formRef.update({ "@status": "error", "@messages": message });
254
- return;
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
- else {
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
- // Check for delay
280
- console.info("Checking for delay");
281
- const delay = form["@delay"];
282
- if (delay) {
283
- const delayStart = performance.now();
284
- const cancelled = await (0, index_utils_1.delayFormSubmissionAndCheckIfCancelled)(delay, formRef);
285
- const delayEnd = performance.now();
286
- const delayLogicResult = {
287
- name: "delayFormSubmission",
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: delayEnd - delayStart,
334
+ execTime: businessLogicEnd - businessLogicStart,
291
335
  };
292
- logicResults.push(delayLogicResult);
293
- if (cancelled) {
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}-${page}-${i}`);
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
- async function writeJournalEntriesFirst() {
347
- // Gather all logicResultDoc with journalEntries
348
- const logicResultDocsWithJournalEntries = logicResults
349
- .map((result) => result.documents).flat()
350
- .filter((logicResultDoc) => logicResultDoc.journalEntries &&
351
- logicResultDoc.action !== "delete");
352
- if (logicResultDocsWithJournalEntries.length === 0) {
353
- console.info("No journal entries to write");
354
- return;
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 businessLogicEnd = performance.now();
548
- const businessLogicLogicResult = {
549
- name: "runBusinessLogics",
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: businessLogicEnd - businessLogicStart,
376
+ execTime: distributeNonTransactionalLogicResultsEnd - distributeNonTransactionalLogicResultsStart,
553
377
  };
554
- logicResults.push(businessLogicLogicResult);
555
- if (runStatus === "cancel-then-retry") {
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
- if (errorMessage) {
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,