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
JavaScript
"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
};