UNPKG

daniel-san

Version:

a node-based budget-projection engine that helps your routines and finances find balance. The program features aggregates, terminal and file-based reporting output, multi-currency conversion capability and multi-frequency accounting triggers, including: o

284 lines (247 loc) 10.7 kB
"use strict"; var _require = require('../utility/errorHandling'), errorDisc = _require.errorDisc; var _require2 = require('../utility/validation'), isUndefinedOrNull = _require2.isUndefinedOrNull; var _require3 = require('../timeZone'), convertTimeZone = _require3.convertTimeZone; var _require4 = require('../standardEvents'), buildStandardEvent = _require4.buildStandardEvent; var _require5 = require('../specialEvents'), nthWeekdaysOfMonth = _require5.nthWeekdaysOfMonth, weekdayOnDate = _require5.weekdayOnDate; var _require6 = require('./obliterate'), flagRuleForRetirement = _require6.flagRuleForRetirement, retireRules = _require6.retireRules; var _require7 = require('../specialAdjustments'), moveThisProcessDateBeforeTheseWeekdays = _require7.moveThisProcessDateBeforeTheseWeekdays, moveThisProcessDateBeforeTheseDates = _require7.moveThisProcessDateBeforeTheseDates, moveThisProcessDateAfterTheseWeekdays = _require7.moveThisProcessDateAfterTheseWeekdays, moveThisProcessDateAfterTheseDates = _require7.moveThisProcessDateAfterTheseDates, adjustAmountOnTheseDates = _require7.adjustAmountOnTheseDates; var _require8 = require('../constants'), OBSERVER_SOURCE = _require8.OBSERVER_SOURCE, DATE_FORMAT_STRING = _require8.DATE_FORMAT_STRING, STANDARD_EVENT = _require8.STANDARD_EVENT, NTH_WEEKDAYS_OF_MONTH = _require8.NTH_WEEKDAYS_OF_MONTH, NTH_WEEKDAYS_OF_MONTH_ROUTINE = _require8.NTH_WEEKDAYS_OF_MONTH_ROUTINE, NTH_WEEKDAYS_OF_MONTH_REMINDER = _require8.NTH_WEEKDAYS_OF_MONTH_REMINDER, WEEKDAY_ON_DATE = _require8.WEEKDAY_ON_DATE, WEEKDAY_ON_DATE_ROUTINE = _require8.WEEKDAY_ON_DATE_ROUTINE, WEEKDAY_ON_DATE_REMINDER = _require8.WEEKDAY_ON_DATE_REMINDER, MOVE_THIS_PROCESS_DATE_BEFORE_THESE_WEEKDAYS = _require8.MOVE_THIS_PROCESS_DATE_BEFORE_THESE_WEEKDAYS, MOVE_THIS_PROCESS_DATE_BEFORE_THESE_DATES = _require8.MOVE_THIS_PROCESS_DATE_BEFORE_THESE_DATES, PRE_PAY = _require8.PRE_PAY, MOVE_THIS_PROCESS_DATE_AFTER_THESE_WEEKDAYS = _require8.MOVE_THIS_PROCESS_DATE_AFTER_THESE_WEEKDAYS, MOVE_THIS_PROCESS_DATE_AFTER_THESE_DATES = _require8.MOVE_THIS_PROCESS_DATE_AFTER_THESE_DATES, POST_PAY = _require8.POST_PAY, ADJUST_AMOUNT_ON_THESE_DATES = _require8.ADJUST_AMOUNT_ON_THESE_DATES, ADJUST_AMOUNT = _require8.ADJUST_AMOUNT, DISCOVERING_EVENT_TYPE = _require8.DISCOVERING_EVENT_TYPE, EXECUTING_SPECIAL_ADJUSTMENT = _require8.EXECUTING_SPECIAL_ADJUSTMENT, MODIFIED = _require8.MODIFIED, RETIRING_RULES = _require8.RETIRING_RULES, STANDARD_EVENT_ROUTINE = _require8.STANDARD_EVENT_ROUTINE, STANDARD_EVENT_REMINDER = _require8.STANDARD_EVENT_REMINDER; var buildEvents = function buildEvents(_ref) { var danielSan = _ref.danielSan, date = _ref.date, _ref$options = _ref.options, options = _ref$options === void 0 ? {} : _ref$options; var skipTimeTravel = options.skipTimeTravel; var processPhase; var convertedDate; var ruleTracker; // for errorDisc var indexTracker; // for errorDisc try { danielSan.rules.forEach(function (rule, index) { ruleTracker = rule; indexTracker = index; convertedDate = !skipTimeTravel ? convertTimeZone({ timeZone: rule.timeZone, timeZoneType: rule.timeZoneType, date: date }).date : date; if (isUndefinedOrNull(rule.effectiveDateStart) || rule.effectiveDateStart <= convertedDate.format(DATE_FORMAT_STRING)) { processPhase = DISCOVERING_EVENT_TYPE; switch (rule.type) { case STANDARD_EVENT: case STANDARD_EVENT_ROUTINE: case STANDARD_EVENT_REMINDER: processPhase = buildStandardEvent({ danielSan: danielSan, rule: rule, date: convertedDate, skipTimeTravel: skipTimeTravel }); break; case NTH_WEEKDAYS_OF_MONTH: case NTH_WEEKDAYS_OF_MONTH_ROUTINE: case NTH_WEEKDAYS_OF_MONTH_REMINDER: processPhase = nthWeekdaysOfMonth({ danielSan: danielSan, rule: rule, date: convertedDate, skipTimeTravel: skipTimeTravel }); break; case WEEKDAY_ON_DATE: case WEEKDAY_ON_DATE_ROUTINE: case WEEKDAY_ON_DATE_REMINDER: processPhase = weekdayOnDate({ danielSan: danielSan, rule: rule, date: convertedDate, skipTimeTravel: skipTimeTravel }); break; default: break; } if (processPhase === MODIFIED && danielSan.events[danielSan.events.length - 1].specialAdjustments) { // note: execute specialAdjustments if exists processPhase = EXECUTING_SPECIAL_ADJUSTMENT; danielSan.events[danielSan.events.length - 1].specialAdjustments.forEach(function (specialAdjustment) { switch (specialAdjustment.type) { case MOVE_THIS_PROCESS_DATE_BEFORE_THESE_WEEKDAYS: moveThisProcessDateBeforeTheseWeekdays({ danielSan: danielSan, event: danielSan.events[danielSan.events.length - 1], specialAdjustment: specialAdjustment, date: convertedDate, skipTimeTravel: skipTimeTravel }); break; case MOVE_THIS_PROCESS_DATE_BEFORE_THESE_DATES: case PRE_PAY: moveThisProcessDateBeforeTheseDates({ danielSan: danielSan, event: danielSan.events[danielSan.events.length - 1], specialAdjustment: specialAdjustment, date: convertedDate, skipTimeTravel: skipTimeTravel }); break; case MOVE_THIS_PROCESS_DATE_AFTER_THESE_WEEKDAYS: moveThisProcessDateAfterTheseWeekdays({ danielSan: danielSan, event: danielSan.events[danielSan.events.length - 1], specialAdjustment: specialAdjustment, date: convertedDate, skipTimeTravel: skipTimeTravel }); break; case MOVE_THIS_PROCESS_DATE_AFTER_THESE_DATES: case POST_PAY: moveThisProcessDateAfterTheseDates({ danielSan: danielSan, event: danielSan.events[danielSan.events.length - 1], specialAdjustment: specialAdjustment, date: convertedDate, skipTimeTravel: skipTimeTravel }); break; case ADJUST_AMOUNT_ON_THESE_DATES: case ADJUST_AMOUNT: if (!isUndefinedOrNull(danielSan.events[danielSan.events.length - 1].amount)) { adjustAmountOnTheseDates({ danielSan: danielSan, event: danielSan.events[danielSan.events.length - 1], specialAdjustment: specialAdjustment, date: convertedDate }); } break; default: break; } }); } processPhase = RETIRING_RULES; flagRuleForRetirement({ danielSan: danielSan, rule: rule, date: convertedDate, index: index }); } if (rule.ruleModification) { var ruleModification = rule.ruleModification; ruleModification({ danielSan: danielSan, rule: rule, date: date, convertedDate: convertedDate }); } }); retireRules(danielSan); } catch (err) { throw errorDisc({ err: err, data: { date: date, processPhase: processPhase, convertedDate: convertedDate, rule: ruleTracker, indexTracker: indexTracker, options: options } }); } }; var executeEvents = function executeEvents(danielSan) { danielSan.events.forEach(function (event, index) { try { event.balanceBeginning = index === 0 ? danielSan.config.balanceBeginning : danielSan.events[index - 1].balanceEnding; event.balanceEnding = event.balanceBeginning; // default value in case there is no amount field on the rule with which to adjust it var observerSourceCurrencyAmount = 0; if (!isUndefinedOrNull(event.amount)) { event.eventSourceCurrencyAmount = event.amount; if (event.eventSourceCurrencyAmount !== 0) { observerSourceCurrencyAmount = danielSan.config.currencySymbol && event.currencySymbol && danielSan.config.currencySymbol !== event.currencySymbol ? danielSan.config.currencyConversion({ amount: event.eventSourceCurrencyAmount, inputSymbol: event.currencySymbol, outputSymbol: danielSan.config.currencySymbol }) : event.eventSourceCurrencyAmount; } event.context = OBSERVER_SOURCE; // simply so the user understands the context event.eventSourceCurrencySymbol = event.currencySymbol; // for future convenience event.eventSourceCurrencyAmount = event.amount; // for future convenience event.balanceEnding = event.balanceBeginning + observerSourceCurrencyAmount; // routine types like STANDARD_EVENT_ROUTINE do not require an amount field event.currencySymbol = danielSan.config.currencySymbol; event.observerSourceCurrencyAmount = observerSourceCurrencyAmount; event.amount = observerSourceCurrencyAmount; } } catch (err) { throw errorDisc({ err: err, data: { event: event, index: index } }); } }); }; var cleanUpEvents = function cleanUpEvents(danielSan) { // the idea is that we should only provide useful data that makes sense in the context of each event // as it may relate to timezone or currency conversion // any other information that may be required can be gathered from the original rule (matched via name or custom id property) danielSan.events.forEach(function (event) { delete event.specialAdjustments; delete event.exclusions; delete event.processDate; delete event.ruleModification; delete event.transientData; delete event.observerSourceCurrencyAmount; if (typeof event.frequency !== 'string') { delete event.frequency; } }); }; module.exports = { buildEvents: buildEvents, executeEvents: executeEvents, cleanUpEvents: cleanUpEvents };