var global_pricelist = (function () { function toTimeString(date) { if (date instanceof Date) { return date.toISOString().replace('T', ' ').replace('Z', ''); } throw new Error('Expected Date'); } function toDate(str) { if (str instanceof Date) { return str; } else if (typeof str == 'string') { var mainparts = str.split(' '); var dparts = mainparts[0].split('-'); var tparts = mainparts[1].split(':'); return new Date(dparts[0], parseInt(dparts[1]) - 1, dparts[2], tparts[0], tparts[1], tparts[2], 0); } return new Date(0); } return { creditline: { pricelists: { '285': { type: 'principal', fee_type_id: '285', tax_percentage: '0.000', data: {}, dimension: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: essi crl (function () { // Group: Group if (true) { bag['setAmountRange'](300, 2000, 50); if (control.canceled) return; bag['setTermRange'](12, 12, 1); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, price: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if (true) { vars['amount'] = bag['getAmount'](); bag['setPrice'](vars['amount']); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, buildDimension: function (bag = {}) { if (!this.amounts) { var amounts = []; var terms = []; bag = Object.assign({ setAmountRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (amounts.indexOf(i) === -1) { amounts.push(i); } } }, setTermRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (terms.indexOf(i) === -1) { terms.push(i); } } } }, bag); this.dimension.evaluate(bag); amounts.sort(function (a, b) { return a - b; }); terms.sort(function (a, b) { return a - b; }); this.amounts = amounts; this.terms = terms; } return { amounts: this.amounts, terms: this.terms }; }, buildPrice: function (amount, term, pricelist_bag) { var price = null; var rate = 0; var pricebag = pricelist_bag || {}; pricebag.getAmount = function () { return parseFloat(amount); }; pricebag.getTerm = function () { return parseFloat(term); }; pricebag.setPrice = function (data) { price = data; }; pricebag.setRate = function (data) { rate = data; }; pricebag.parseFloat = parseFloat; this.price.evaluate(pricebag); return { price: parseFloat(price).toFixed(2), rate: parseFloat(rate).toFixed(10) }; } }, '286': { type: 'extension-fee', fee_type_id: '286', tax_percentage: '0.000', data: {}, dimension: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: essi crl (function () { // Group: Group if (true) { bag['setAmountRange'](300, 2000, 50); if (control.canceled) return; bag['setTermRange'](3, 3, 1); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, price: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: crl ext (function () { // Group: Group if ([1,3].indexOf(parseInt(bag[460])) !== -1) { vars['price'] = (bag[3256] * 0.3); bag['setPrice'](vars['price']); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if ([17,4].indexOf(parseInt(bag[460])) !== -1) { vars['price'] = (bag[4102] * 0.3); bag['setPrice'](vars['price']); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, buildDimension: function (bag = {}) { if (!this.amounts) { var amounts = []; var terms = []; bag = Object.assign({ setAmountRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (amounts.indexOf(i) === -1) { amounts.push(i); } } }, setTermRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (terms.indexOf(i) === -1) { terms.push(i); } } } }, bag); this.dimension.evaluate(bag); amounts.sort(function (a, b) { return a - b; }); terms.sort(function (a, b) { return a - b; }); this.amounts = amounts; this.terms = terms; } return { amounts: this.amounts, terms: this.terms }; }, buildPrice: function (amount, term, pricelist_bag) { var price = null; var rate = 0; var pricebag = pricelist_bag || {}; pricebag.getAmount = function () { return parseFloat(amount); }; pricebag.getTerm = function () { return parseFloat(term); }; pricebag.setPrice = function (data) { price = data; }; pricebag.setRate = function (data) { rate = data; }; pricebag.parseFloat = parseFloat; this.price.evaluate(pricebag); return { price: parseFloat(price).toFixed(2), rate: parseFloat(rate).toFixed(10) }; } }, }, fee_types: { 284: 'loan_fee', 285: 'principal', 286: 'extension_fee', 287: 'court_fee', 288: 'penalty', 289: 'late_fee', 290: 'payout_fee', 291: 'cash_fee', 292: 'interest_fee', 297: 'extension_fee_star', 368: 'sale_fee', 453: 'preparation_fee', }, promotions: { 3633: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if ([4,6,7,11,12].indexOf(parseInt(bag['460'])) !== -1) { (bag['addDiscountPercent'](288, 100) * bag['addDiscountPercent'](289, 100)); bag['stopProcessing'](); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, }, loanlimit: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: FailSafe (function () { // Group: Group if (true) { bag['setAvailableLimit'](2000); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; control.skip = false; // Stage: Stage (function () { // Group: CRL if (bag[799] >= 0) { bag['setVisibleLimit'](0); bag['setAvailableLimit'](0); if (control.canceled) return; bag['setDayLimit'](0); bag['setAvailableDayLimit'](0); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, freshBagLimit: function (bag) { var n = { _available_limit: null, _day_lower_bound: null, _visible_limit: null, _day_limit: null, _available_day_limit: null, _lowerbound: null, _income_verify_limit: null, _reject_message: null, _slider_default: null, setSliderDefault: function (amount, term, product) { if (Number.isInteger(amount) && Number.isInteger(term) && Number.isInteger(product)) { this._slider_default = { "amount" : parseFloat(amount).toFixed(2), "term" : parseFloat(term).toFixed(0), "default_product" : product }; } }, setIncomeVerifyLimit: function (limit) { this._income_verify_limit = limit; }, setLimit: function (limit) { this._visible_limit = limit; }, setVisibleLimit: function (limit) { this._visible_limit = limit; }, setAvailableLimit: function (limit) { this._available_limit = limit; }, setHardLimit: function (limit) { this._available_limit = limit; }, getRegistrationField: function (field) { return this[field] || 0; }, getAmount: function () { return bag[-3]; }, getMaximumRepayableAmount: function () { return bag[-22]; }, setDayLowerBound: function (amount) { this._day_lower_bound = amount; }, setDayLimit: function (limit) { this._day_limit = limit; }, setAvailableDayLimit: function (limit) { this._available_day_limit = limit; }, setInstallmentsCount: function (limit) { this._day_limit = limit; }, getInstallmentsCount: function (limit) { return this._day_limit; }, setRejectLoan: function (message) { this._reject_message = message; }, setExtensionLimit: function (limit) { this._extension_limit = limit; }, setLowerBound: function (limit) { this._lowerbound = limit; }, getMonthlyPayment: function (initialPrincipal, annualInterestRate, term) { var annualInterestRate = annualInterestRate / 100; var monthlyInterestRate = annualInterestRate / 12; var divident = initialPrincipal * monthlyInterestRate; var divisor = 1 - (1 / Math.pow((1 + monthlyInterestRate), term)); return divident / divisor; }, getOverpaymentCoefficient: function (annualInterestRate, term) { var initialPrincipal = 1000; var monthlyPayment = this.getMonthlyPayment(initialPrincipal, annualInterestRate, term); var totalRepayableAmount = monthlyPayment * term; return totalRepayableAmount / initialPrincipal; }, getInterestRateApproximationFromAnnuity: function (termMonths) { var termDays = termMonths * 30; var constant = 1.000018; var resultBase = constant + (termDays * 0.0007 + 1) / termMonths; var resultLog = Math.log2(1 + 1 / termMonths); resultBase = Math.pow(resultBase, (1 / resultLog)) - 1; var interestRateApproximationFromAnnuity = ((Math.pow(resultBase, resultLog) - constant) * 12) * 100; var flooredResult = Math.floor(interestRateApproximationFromAnnuity * 100) / 100; return flooredResult; }, calculateTermUpperLimit: function (amount, maxRepayable, interest) { let monthlyInterest = (interest/100) / 12; return (isNaN(Math.ceil(Math.log(1 / (1 - ((amount * monthlyInterest) / maxRepayable))) / Math.log1p(monthlyInterest))) ? 0 : Math.ceil(Math.log(1 / (1 - ((amount * monthlyInterest) / maxRepayable))) / Math.log1p(monthlyInterest))); }, }; for (var id in bag) { if (!(id in n)) { n[id] = bag[id]; } } if ( ! (-8 in n)) { n[-8] = 508; } n["round"] = this.round return n; }, filterAvailableTerms: function (bag, checkFunction) { var limits = this.evaluateLimit(bag); var lowestAvailableTerm = null; for (var index = 0; index < limits.terms.length; index++) { var t = limits.terms[index]; bag['-2'] = t; var currentTermLimits = this.evaluateLimit(bag); if (checkFunction) { if (checkFunction(currentTermLimits)) { lowestAvailableTerm = t; break; } } else if (currentTermLimits.amounts_available.length > 0) { lowestAvailableTerm = t; break; } } return limits.terms.slice(limits.terms.indexOf(lowestAvailableTerm)); }, evaluateLimit: function (bag) { var finalAmounts_available = []; var finalAmounts_visible = []; var finalDays = []; var finalDays_available = []; var finalExtDays = []; var ibag = this.freshBagLimit(bag); var result = this.loanlimit.evaluate(ibag); this.pricelists[285].buildDimension(bag); this.pricelists[286].buildDimension(bag); for (var i = 0; i < this.pricelists[285].amounts.length; i++) { var amn = this.pricelists[285].amounts[i]; if ((ibag._available_limit == null || parseFloat(amn) <= ibag._available_limit) && (ibag._lowerbound == null || parseFloat(amn) >= ibag._lowerbound)) { finalAmounts_available.push(parseFloat(amn).toFixed(2)); } if ((ibag._visible_limit == null || parseFloat(amn) <= ibag._visible_limit) && (ibag._lowerbound == null || parseFloat(amn) >= ibag._lowerbound)) { finalAmounts_visible.push(parseFloat(amn).toFixed(2)); } } for (var i = 0; i < this.pricelists[285].terms.length; i++) { var day = this.pricelists[285].terms[i]; if ((ibag._day_limit == null || parseFloat(day) <= ibag._day_limit) && (ibag._day_lower_bound == null || parseFloat(day) >= ibag._day_lower_bound)) { finalDays.push(parseFloat(day).toFixed(0)); } if ((ibag._available_day_limit == null || parseFloat(day) <= ibag._available_day_limit) && (ibag._day_lower_bound == null || parseFloat(day) >= ibag._day_lower_bound)) { finalDays_available.push(parseFloat(day).toFixed(0)); } } for (var i = 0; i < this.pricelists[286].terms.length; i++) { var day = this.pricelists[286].terms[i]; if (ibag._extension_limit == null || parseFloat(day) <= ibag._extension_limit) { finalExtDays.push(parseFloat(day).toFixed(0)) } } return { 'pricelist-amount-limit': ibag._visible_limit, 'pricelist-amount-hard-limit': ibag._available_limit, 'pricelist-amount-lower-bound': ibag._lowerbound, 'pricelist-day-limit': ibag._day_limit, 'pricelist-available-day-limit': ibag._available_day_limit, 'pricelist-income-verify-limit': ibag._income_verify_limit, 'pricelist-day-lower-bound': ibag._day_lower_bound, 'reject_message': ibag._reject_message, amounts_available: finalAmounts_available, amounts_visible: finalAmounts_visible, terms: finalDays, terms_available: finalDays_available, extterms: finalExtDays, slider_default: ibag._slider_default } }, prolongPermissionRules: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if ([1,3].indexOf(parseInt(bag['3141'])) !== -1) { bag['prohibitProlongToCreditline'](null); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, freshBagProlongPermissionRule: function (bag) { var n = { _loanProlongPermitted: false, _loanProlongPermittedToPayday: false, _loanProlongPermittedToInstallment: false, _loanProlongPermittedToCreditline: false, _errorMessage: null, _errorMessageProlongToPayday: null, _errorMessageProlongToInstallment: null, _errorMessageProlongToCreditline: null, _loanSettingId: null, _feetypes: [], allowProlong: function () { this._loanProlongPermitted = true; this._errorMessage = null; }, allowProlongToPayday: function () { this._loanProlongPermittedToPayday = true; this._errorMessageProlongToPayday = null; }, allowProlongToInstallment: function () { this._loanProlongPermittedToInstallment = true; this._errorMessageProlongToInstallment = null; }, allowProlongToCreditline: function () { this._loanProlongPermittedToCreditline = true; this._errorMessageProlongToCreditline = null; }, prohibitProlong: function (msg) { this._loanProlongPermitted = false; this._errorMessage = msg; }, setLoanSettings: function (id) { this._loanSettingId = id; }, prohibitProlongToPayday: function (msg) { this._loanProlongPermittedToPayday = false; this._errorMessageProlongToPayday = msg; }, prohibitProlongToInstallment: function (msg) { this._loanProlongPermittedToInstallment = false; this._errorMessageProlongToInstallment = msg; }, prohibitProlongToCreditline: function (msg) { this._loanProlongPermittedToCreditline = false; this._errorMessageProlongToCreditline = msg; }, feetypeToNewLoan: function (feetype, percentage, maxvalue) { this._feetypes.push({ "feetype": feetype, "percentage": percentage, "maxvalue": maxvalue }); }, hasLoanOpenInAllLenders: function () { return bag[-33]; }, isRequestForLoanFromPlaton: function () { return bag["is-backend"] || false; }, isRequestForLoanFromClientzone: function () { return !bag["is-backend"]; }, }; for (var id in bag) { if ((typeof bag[id] === 'string') && !isNaN(bag[id]) && isFinite(bag[id]) && typeof bag[id] !== "boolean") { bag[id] = parseFloat(bag[id]); } if (!(id in n)) { n[id] = bag[id]; } } return n; }, evaluateProlongPermisson: function (bag) { var ibag = this.freshBagProlongPermissionRule(bag); var result = this.prolongPermissionRules.evaluate(ibag); return { 'loan-prolong-permitted': ibag._loanProlongPermitted, 'loan-prolong-permitted-to-payday': ibag._loanProlongPermittedToPayday, 'loan-prolong-permitted-to-installment': ibag._loanProlongPermittedToInstallment, 'loan-prolong-permitted-to-creditline': ibag._loanProlongPermittedToCreditline, 'loan-prolong-error': ibag._errorMessage, 'loan-prolong-to-payday-error': ibag._errorMessageProlongToPayday, 'loan-prolong-to-installment-error': ibag._errorMessageProlongToInstallment, 'loan-prolong-to-creditline-error': ibag._errorMessageProlongToCreditline, 'prolong-loan-setting-id': ibag._loanSettingId, 'loan-feetype-mapping': ibag._feetypes } }, evaluateExtensionPromotions: function (now, bag, values) { var ibag = {}; var result = null; var existingpromo = null; var percentpromo = null; var percentpromos = []; var usedPromotions = []; if (bag['existing-promotions']) { var existingPromos = bag['existing-promotions'].sort(function (a, b) { return b.priority - a.priority; }); ibag = this.freshBag(bag, null); for (var i = 0; i < existingPromos.length; i++) { existingpromo = existingPromos[i]; if (existingpromo['type'] == 'percent') { percentpromo = { 'amount': parseFloat(existingpromo['amount']), 'id': existingpromo['promoId'] }; if (existingpromo['installmentIndex'] !== undefined) { // installment index can be 0 and then it wouldnt go in percentpromo['installment_index'] = existingpromo['installmentIndex'] + 1; // + 1 because for promostaging the first installment is index 1 and not 0 } if (!percentpromos[existingpromo['feeTypeId']]) { percentpromos[existingpromo['feeTypeId']] = []; } percentpromos[existingpromo['feeTypeId']].push(percentpromo); usedPromotions.push(existingpromo['promoId'].toString()); } } ibag['discounts_per'] = percentpromos; this.fillDiscounts(ibag, values); } }, extensionPermissionRules: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Prohibit extension if force settledate is set (function () { // Group: Group if ((bag['7560'] == 1 && bag['3141'] == 3)) { bag['prohibitExtension']('force settledate set - no extension possible'); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; control.skip = false; // Stage: Allow Extensions for CRL (function () { // Group: Group if ((bag['3141'] == 3 && (bag[-14] == 1 && bag['3353'] <= 15))) { bag['allowExtension'](); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; }, freshBag: function (bag, extensionPrice) { var n = { _loanExtensionPermitted: false, _errorMessage: null, allowExtension: function () { this._loanExtensionPermitted = true; this._errorMessage = null; }, prohibitExtension: function (msg) { this._loanExtensionPermitted = false; this._errorMessage = msg; } }; for (var id in bag) { if ((typeof bag[id] === 'string') && !isNaN(bag[id]) && isFinite(bag[id])) { bag[id] = parseFloat(bag[id]); } if (!(id in n)) { n[id] = bag[id]; } } n[-11] = extensionPrice; return n; } }, evaluateExtension: function (bag) { bag[-3] = bag[-3] || bag['loan-amount'] bag[-2] = bag[-2] || bag['loan-term'] bag[-1] = bag[-1] || bag['loan-extension-term'] var values = { discounts_per: {}, discounts_amn: {}, extensions: {}, total_extension_term: 0, prices: {}, feeRates: {}, discounts: {}, discounted: {} } var that = this; var now = new Date().getTime(); var amn = parseFloat(bag[-3]).toFixed(2); var term = parseFloat(bag[-2]).toFixed(0); var extterm = parseFloat(bag[-1]).toFixed(0); if (bag[-7] && (bag[-7] == '-1' || bag[-7] == '1')) { aleg: for (var id in this.pricelists) { if (!('buildPrice' in this.pricelists[id])) { if (!(amn in this.pricelists[id].data)) { var pamn = '0.0'; for (var iamn in this.pricelists[id].data) { if (parseFloat(iamn) > parseFloat(amn)) { if (bag[-7] == '1') { amn = iamn; break aleg; } else { if (parseFloat(pamn) == 0.0) { throw new Error('There is no smaller amount than ' + amn + ' to round down to for pricelist with id ' + id); } amn = pamn; break aleg; } } pamn = iamn; } if (parseFloat(pamn) < parseFloat(amn) && bag[-7] == '1') { amn = pamn; break aleg; } if (parseFloat(pamn) < parseFloat(amn) && bag[-7] == '-1') { amn = pamn; break aleg; } } } } } for (var id in this.pricelists) { if ( id != 286 ) { continue; } if ('buildPrice' in this.pricelists[id]) { var save = bag['pricelist_bag']; bag['pricelist_bag'] = {}; for (var x in save) { bag['pricelist_bag'][x] = save[x]; } for (var x in bag) { if (x != 'pricelist_bag') { bag['pricelist_bag'][x] = bag[x]; } } var pricelist_bag = {}; for (var item in bag['pricelist_bag']) { pricelist_bag[item] = bag['pricelist_bag'][item]; } var pricelistResult = this.pricelists[id].buildPrice(amn, extterm, pricelist_bag); var price = parseFloat(pricelistResult.price); var feeRate = parseFloat(pricelistResult.rate); values.prices[id] = price; values.feeRates[id] = feeRate; } else { if (!this.pricelists[id].data) continue; if (!(amn in this.pricelists[id].data)) { throw new Error('Invalid Amount ' + amn + ' for pricelist with id ' + id); } if (!extterm in this.pricelists[id].data[amn]) { throw new Error('Invalid Term ' + extterm + ' for pricelist with id ' + id + ' for amount ' + amn); } values.prices[id] = parseFloat(this.pricelists[id].data[amn][extterm]); values.feeRates[id] = 0; } } if ('existing-fees' in bag) { for (var id in bag['existing-fees']) { if (bag['existing-fees'][id].amount == 0) { continue; } if (!values.prices[id]) { values.prices[id] = 0; } values.prices[id] += bag['existing-fees'][id].amount; } } this.evaluateExtensionPromotions(now, bag, values); var details2 = { 'percent-discounts' : {}, 'amount-discounts' : {}, 'extensions': {}, original: {}, discount: {}, final: {} }; for (var id in values.prices) { var feeType = null; if (id in this.pricelists) { feeType = this.pricelists[id].type; } else if (id in bag['existing-fees']) { feeType = bag['existing-fees'][id].type; } var discountPercentAr = values.discounts_per[id]; var discountPercent = 0; if (discountPercentAr) { details2['percent-discounts'][feeType] = discountPercentAr; for (var i = 0; i < discountPercentAr.length; i++) { discountPercent += parseFloat(discountPercentAr[i].amount); } } var discountAmountAr = values.discounts_amn[id]; var discountAmount = 0; if (discountAmountAr) { details2['amount-discounts'][feeType] = discountAmountAr; for (var i = 0; i < discountAmountAr.length; i++) { discountAmount += parseFloat(discountAmountAr[i].amount); } } var discount = Math.min(values.prices[id] * discountPercent / 100 + discountAmount, values.prices[id]); var finalAmount = values.prices[id] - discount; values.discounts[id] = parseFloat(discount.toFixed(2)); values.discounted[id] = parseFloat(finalAmount.toFixed(2)); details2.original[feeType] = values.prices[id]; details2.discount[feeType] = values.discounts[id]; details2.final[feeType] = values.discounted[id]; } var ibag = null; var extensionPrice = values.discounted[286]; ibag = this.extensionPermissionRules.freshBag(bag, extensionPrice); this.extensionPermissionRules.evaluate(ibag); var calc_loan_duedate = bag['loan_duedate'] ? new Date(Date.parse(bag['loan_duedate'])) : null; if (calc_loan_duedate) { var calc_loan_is_payday = bag['loan_is_payday']; var calc_extension_permitted = ibag == null || (!ibag._errorMessage && ibag._loanExtensionPermitted); var calc_term = parseInt(extterm); var calc_duedate = null; var calc_now = new Date(); var calc_late = calc_loan_duedate < calc_now; var calc_actDuedate = calc_late ? calc_now : calc_loan_duedate; if (calc_extension_permitted) { calc_duedate = calc_actDuedate; if (calc_loan_is_payday) { calc_duedate.setDate(calc_duedate.getDate() + calc_term); } else { calc_duedate.setMonth(calc_duedate.getMonth() + calc_term); } calc_duedate = (calc_duedate.toISOString()).substr(0, 10); } } return { 'loan-amount': bag[-3], 'loan-term': bag[-2], 'loan-extension-term': bag[-1], 'loan-extension-fees': values.discounted[286], 'loan-extension-duedate': calc_duedate, 'loan-extension-permitted': ibag._loanExtensionPermitted, 'loan-extension-error': ibag._errorMessage, 'loan-original-extension-fees': values.prices[286], 'loan-free-extension': values.total_extension_term, details: details2, }; }, evaluateInstallments: function (bag, promotions) { function daysBetween(t1, t2) { var oneDay = 24 * 60 * 60 * 1000; return Math.round(Math.abs((t1.getTime() - t2.getTime())/oneDay)); } function dateWithoutTime(date) { var newdate = date ? new Date(date) : new Date(); newdate.setMinutes(0); newdate.setHours(0); newdate.setSeconds(0); return newdate; } function getIntervalJson(interval) { var yearsSplit = interval.split('y'); var monthsSplit = yearsSplit[1].split('m'); var daysSplit = monthsSplit[1].split('d'); return { years: parseInt(yearsSplit[0]), months: parseInt(monthsSplit[0]), days: parseInt(daysSplit[0]) }; } function getIntervalNewDate(paymentDate, interval) { var intervalJson = getIntervalJson(interval); var intervalNewDate = new Date(paymentDate); intervalNewDate.setFullYear(paymentDate.getFullYear() + intervalJson.years); intervalNewDate.setMonth(paymentDate.getMonth() + intervalJson.months); intervalNewDate.setDate(paymentDate.getDate() + intervalJson.days); return intervalNewDate; } function getMonthlyPaymentDate(date, bag) { if (!bag || !bag['grace-period-days']) { return date; } var monthlyPaymentDate = new Date(date); monthlyPaymentDate.setDate(date.getDate() + parseInt(bag['grace-period-days'])); return monthlyPaymentDate; } function getNextPaymentDate(paymentDate, targetDay, bag) { if (bag && 'use-interval' in bag && bag['use-interval']) { return getIntervalNewDate(paymentDate, bag['interval']); } var followingMonth, followingYear, lastOfFollowingMonth, newDate; if (paymentDate.getMonth() === 11) { followingMonth = 0; followingYear = paymentDate.getFullYear() + 1; } else { followingMonth = paymentDate.getMonth() + 1; followingYear = paymentDate.getFullYear(); } lastOfFollowingMonth = new Date(followingYear, followingMonth + 1, 0); newDate = dateWithoutTime(paymentDate); if(lastOfFollowingMonth.getDate() < targetDay) { newDate.setMonth(lastOfFollowingMonth.getMonth()); newDate.setDate(0); } else { newDate.setMonth(paymentDate.getMonth() + 1); newDate.setDate(targetDay); } return newDate; } function calcPromotionDiscount(feeAmount, feeTypeId, currentInstallmentIndex, promotions) { var totalReduc = 0; if (promotions.discounts_per[feeTypeId]) { var promos = promotions.discounts_per[feeTypeId]; for (var i = 0; i < promos.length; i++) { var p = promos[i]; var a = (p.amount / 100) * feeAmount; if (p.installment_index - 1 === currentInstallmentIndex) { totalReduc += a; } else if (p.installment_index === undefined) { totalReduc += a; } } } if (promotions.discounts_amn[feeTypeId]) { var promos = promotions.discounts_amn[feeTypeId]; for (var z = 0; z < promos.length; z++) { var p = promos[z]; if (! ('used' in p)) { p.used = 0; } var a = Math.min(p.amount - p.used, feeAmount - totalReduc); if (p.installment_index - 1 === currentInstallmentIndex) { totalReduc += a; } else if (p.installment_index === undefined) { totalReduc += a; } p.used += a; }; } return Math.max(0, (feeAmount - totalReduc)); } var amount = parseFloat(bag['loan-amount']); var term = parseFloat(bag['loan-term']); var commission_rate = parseFloat(bag['creditline-percent-creditline']) / 100 / 360; var fallback_rate = parseFloat(bag['creditline-percent-creditline-fallback']) / 100 / 360; var principal_repay_rate = bag['creditline-percent-principal-repay']; var interest_rate = parseFloat(bag['interest-rate']) / 100 / 360; var interest_rate_full = parseFloat(bag['interest-rate']); var min_repay_amount = parseFloat(bag['creditline-min-monthly-payment']); var use_equal_principal_distribution = bag['use-equal-principal-distribution']; var frontend_calculate_sample_term = bag['frontend-calculate-sample-term']; var now = bag['loan-issue-date']; var paymentDate = dateWithoutTime(now); var oldDate = dateWithoutTime(now); var sampleTermDate = dateWithoutTime(now); var schedule = []; var remaining_amount = amount; var commission_carry_over = 0; var interest_carry_over = 0; var repayment; var recalc3 = 0; if (bag['-42']) { bag['-42'].forEach(function (version) { if (version.product_id == 3) { recalc3 = version.recalc_version; } }); } // adapt repayment interval if (bag['first-installment-date']) { paymentDate = dateWithoutTime(bag['first-installment-date']); } else { paymentDate = getNextPaymentDate(paymentDate, paymentDate.getDate(), bag); } // set sample term date if (frontend_calculate_sample_term && frontend_calculate_sample_term != '0y0m0d') { sampleTermDate = getIntervalNewDate(sampleTermDate, frontend_calculate_sample_term); } else { sampleTermDate = null; } var target_day = paymentDate.getDate(); for (var i = 0; i < term; i += 1) { if (i == (term - 1) && sampleTermDate !== null) { if (sampleTermDate > paymentDate) { term++; } else { paymentDate = sampleTermDate; } } var days = daysBetween(oldDate, paymentDate), fallback = Math.floor((fallback_rate * amount * days) * 100) / 100, interest = Math.floor((interest_rate * remaining_amount * days) * 100) / 100, commission = Math.floor((commission_rate * amount * days) * 100) / 100, principal = use_equal_principal_distribution ? Math.floor((amount / term) * 100) / 100 : Math.floor(principal_repay_rate * remaining_amount) / 100, total_payment, old_year_days, new_year_days, schedule_entry; if (recalc3 > 3) { if (paymentDate.getFullYear() > oldDate.getFullYear()) { old_year_days = daysBetween(new Date(oldDate.getFullYear(), 0, 1), new Date(oldDate.getFullYear() + 1, 0, 1)); new_year_days = daysBetween(new Date(paymentDate.getFullYear(), 0, 1), new Date(paymentDate.getFullYear() + 1, 0, 1)); var installment_days_in_old_year = daysBetween(oldDate, new Date(oldDate.getFullYear() + 1, 0, 1)) + 1, installment_days_in_new_year = daysBetween(new Date(paymentDate.getFullYear(), 0, 1), paymentDate) - 1; interest = Math.floor( ( (remaining_amount * (interest_rate_full / 100 / old_year_days * installment_days_in_old_year )) + (remaining_amount * (interest_rate_full / 100 / new_year_days * installment_days_in_new_year )) ) * 100) / 100; fallback = Math.floor( ( (parseFloat(bag['creditline-percent-creditline-fallback']) / 100 / old_year_days * amount * installment_days_in_old_year ) + (parseFloat(bag['creditline-percent-creditline-fallback']) / 100 / new_year_days * amount * installment_days_in_new_year ) ) * 100) / 100; commission = Math.floor( ( (parseFloat(bag['creditline-percent-creditline']) / 100 / old_year_days * amount * installment_days_in_old_year ) + (parseFloat(bag['creditline-percent-creditline']) / 100 / new_year_days * amount * installment_days_in_new_year ) ) * 100) / 100; } else { old_year_days = daysBetween(new Date(oldDate.getFullYear(), 0, 1), new Date(oldDate.getFullYear() + 1, 0, 1)); interest = Math.floor(remaining_amount * (interest_rate_full / 100 / old_year_days * days) * 100) / 100; fallback = Math.floor(parseFloat(bag['creditline-percent-creditline-fallback']) / 100 / old_year_days * amount * days * 100) / 100; commission = Math.floor(parseFloat(bag['creditline-percent-creditline']) / 100 / old_year_days * amount * days * 100) / 100; } } commission_carry_over += commission - (Math.floor(commission * 100) / 100); interest_carry_over += (interest_rate * remaining_amount * days) - interest; if (recalc3 == 0) { if (commission_carry_over >= 0.01) { commission_carry_over -= 0.01; commission += 0.01; } if (interest_carry_over >= 0.01) { interest_carry_over -= 0.01; interest += 0.01; } } if (fallback > (interest + commission)) { commission = fallback; interest = 0; } total_payment = commission + interest + principal; if (total_payment < min_repay_amount) { principal = Math.min(principal + (min_repay_amount - total_payment), remaining_amount); } else if (i == term - 1) { principal = remaining_amount; } if (interest > 0 && bag['additional-interest-per-installment'] && !isNaN(bag['additional-interest-per-installment'])) { interest += parseFloat(bag['additional-interest-per-installment']); } schedule_entry = { date: paymentDate, monthlyPaymentDate: getMonthlyPaymentDate(paymentDate, bag), original: { 285: principal, 284: commission, 292: interest }, discounted: { 285: principal, 284: calcPromotionDiscount(commission, 284, i, promotions), 292: calcPromotionDiscount(interest, 292, i, promotions), }, discounts: {} }; schedule_entry.payment = schedule_entry.discounted[285] + schedule_entry.discounted[284] + schedule_entry.discounted[292]; schedule_entry.discounts[285] = parseFloat(schedule_entry.original[285] - schedule_entry.discounted[285]); schedule_entry.discounts[284] = parseFloat(schedule_entry.original[284] - schedule_entry.discounted[284]); schedule_entry.discounts[292] = parseFloat(schedule_entry.original[292] - schedule_entry.discounted[292]); schedule.push(schedule_entry); oldDate = new Date(paymentDate); paymentDate = getNextPaymentDate(paymentDate, target_day, bag); remaining_amount -= principal; } return { schedule: schedule }; }, calculateAPR: function (startAmount, values) { var PRECISION = 0.00001; var MAX_ATTEMPTS = 1000; var bsMinB = -0.999999999999; var bsMaxB = 2000000000; var principal = startAmount; var attempts = 0; var repaymentTotal = 0; var APRRate = 1; while (Math.abs(repaymentTotal - principal) > PRECISION) { if (attempts >= MAX_ATTEMPTS || (bsMaxB == bsMinB) && (bsMaxB == APRRate)) { return NaN; } attempts++; if (attempts > 1) { if (repaymentTotal < principal) { bsMaxB = APRRate; } else { bsMinB = APRRate; } APRRate = bsMaxB - (bsMaxB - bsMinB) / 2; } repaymentTotal = 0; for (var i = 0; i < values.length; i++) { var rt = Math.pow(1 + APRRate, (i + 1) / 12); // 12 = compounding period repaymentTotal += values[i].payment / rt; } } return APRRate * 100; }, DaysBetween: function(date1, date2) { var oneDay = 24*60*60*1000; return Math.round(Math.abs((date1.getTime() - date2.getTime())/oneDay)); }, XNPV: function(rate, values, daysInYear) { var payment_fee_percentage = 0.00 / 100; var xnpv = 0.0; var firstDate = new Date(values[0].date); for (var i = 0, max = values.length; i < max; i += 1) { var tmp = values[i]; var value; if (tmp.payment < 0) { value = tmp.payment * (1 - payment_fee_percentage); } else { value = tmp.payment * (1 + payment_fee_percentage); } var date = new Date(tmp.date); xnpv += value / Math.pow(1 + rate, this.DaysBetween(firstDate, date)/daysInYear); } return xnpv; }, aprForPayday: function(initialAmount, term, toRepay, daysInYear) { var payment_fee_percentage = 0.00 / 100; return Math.round((Math.pow(toRepay * (1 + payment_fee_percentage) / (initialAmount * (1 - payment_fee_percentage)), daysInYear / term) - 1) * 10000) / 100; }, arForPayday: function (initialAmount, term, toRepay, daysInYear) { var price = toRepay - initialAmount; return Math.round(price / initialAmount * (daysInYear / term) * 10000) / 100; }, rpyForPayday: function (initialAmount, term, toRepay, daysInYear) { var price = toRepay - initialAmount; return Math.round((price / initialAmount / term * daysInYear) * 10000) / 100; }, rpmForPayday: function (initialAmount, term, toRepay, daysInYear) { var price = toRepay - initialAmount; return Math.round((price / initialAmount / term * daysInYear / 12) * 10000) / 100; }, rpdForPayday: function (initialAmount, term, toRepay) { var price = toRepay - initialAmount; return Math.round((price / initialAmount / term) * 10000) / 100; }, XIRR: function(initialAmount, values, guess, forcePayoutDate, daysInYear) { if (!guess) guess = 0.1; var payoutDate = forcePayoutDate ? new Date(forcePayoutDate) : new Date(); payoutDate.setMinutes(0); payoutDate.setHours(0); payoutDate.setSeconds(0); values = values.slice(0); values.unshift({payment:-initialAmount, date:payoutDate}); var x1 = 0.0; var x2 = guess; var f1 = this.XNPV(x1, values, daysInYear); var f2 = this.XNPV(x2, values, daysInYear); for (var i = 0; i < 100; i++) { if ((f1 * f2) < 0.0) break; if (Math.abs(f1) < Math.abs(f2)) { f1 = this.XNPV(x1 += 1.6 * (x1 - x2), values, daysInYear); } else { f2 = this.XNPV(x2 += 1.6 * (x2 - x1), values, daysInYear); } }; if ((f1 * f2) > 0.0) return null; var f = this.XNPV(x1, values, daysInYear); if (f < 0.0) { var rtb = x1; var dx = x2 - x1; } else { var rtb = x2; var dx = x1 - x2; }; for (var i = 0; i < 100; i++) { dx *= 0.5; var x_mid = rtb + dx; var f_mid = this.XNPV(x_mid, values, daysInYear); if (f_mid <= 0.0) rtb = x_mid; if ((Math.abs(f_mid) < 1.0e-6) || (Math.abs(dx) < 1.0e-6)) return Math.max(0, x_mid * 100); }; return null; }, rpyForInstallment: function (interestRate) { return interestRate; }, rpmForInstallment: function (interestRate) { return interestRate / 12; }, rpdForInstallment: function (interestRate, daysInYear) { return interestRate / daysInYear; }, // add base functions fillDiscounts: function (ibag, values) { for (var id in ibag.discounts_per) { if (!(id in values.discounts_per)) { values.discounts_per[id] = []; } for (var i = 0; i < ibag.discounts_per[id].length; i++) { values.discounts_per[id].push(ibag.discounts_per[id][i]); } } for (var id in ibag.discounts_amn) { if (!(id in values.discounts_amn)) { values.discounts_amn[id] = []; } for (var i = 0; i < ibag.discounts_amn[id].length; i++) { values.discounts_amn[id].push(ibag.discounts_amn[id][i]); } } if (!('designations' in values)) { values.designations = {}; } for (var promo_id in ibag.designations) { values.designations[promo_id] = ibag.designations[promo_id]; } for (var promo_id in ibag.customer_data) { if (!('customer_data' in values)) { values.customer_data = {}; } values.customer_data[promo_id] = ibag.customer_data[promo_id]; } var totalTerm = 0; for (var i = 0; i < ibag.extensions.length; i++) { var promo_id = ibag.extensions[i].id; if (!(promo_id in values.extensions)) { values.extensions[promo_id] = 0; } values.extensions[promo_id] += ibag.extensions[i].term; values.total_extension_term += ibag.extensions[i].term; } }, round: function (value, interval, mode) { var returnValue = null; var value = value / interval; switch (mode) { case 0: //FUNCTION_ROUND_FLOOR returnValue = Math.floor(value); break; case 1: //FUNCTION_ROUND_CEIL returnValue = Math.ceil(value); break; case 3: //FUNCTION_ROUND_HALF_DOWN returnValue = -Math.round(-value); break; case 2: //FUNCTION_ROUND_HALF_UP returnValue = Math.round(value); break; } return returnValue ? returnValue / (1 / interval) : returnValue; }, freshBag: function (bag, promo_id) { var that = this; var n = { discounts_per: {}, discounts_amn: {}, extensions: [], designations: {}, customer_data: {}, addInstallmentPromotionPercent: function (feetype, amount, installment_index) { if (!(feetype in this.discounts_per)) { this.discounts_per[feetype] = []; } this.discounts_per[feetype].push({ amount: amount, id: promo_id, installment_index: installment_index }); }, addInstallmentPromotion: function (feetype, amount, installment_index) { if (!(feetype in this.discounts_amn)) { this.discounts_amn[feetype] = []; } this.discounts_amn[feetype].push({ amount: amount, id: promo_id, installment_index: installment_index }); }, addDiscountPercent: function (feetype, amount) { if (!(feetype in this.discounts_per)) { this.discounts_per[feetype] = []; } this.discounts_per[feetype].push({ amount: amount, id: promo_id }); }, addDiscount: function (feetype, amount) { if (!(feetype in this.discounts_amn)) { this.discounts_amn[feetype] = []; } this.discounts_amn[feetype].push({ amount: amount, id: promo_id }); }, setDesignation: function (designation) { this.designations[promo_id] = designation; }, addFreeExtension: function (term) { this.extensions.push({ term: term, id: promo_id }); }, getCustomerData: function (type) { if ('customer-data' in bag && type in bag['customer-data']) { return bag['customer-data'][type]; } else { return null; } }, setCustomerData: function (type, data) { if (!(promo_id in this.customer_data)) { this.customer_data[promo_id] = []; } this.customer_data[promo_id].push({ type: type, data: data }); }, round: that.round, }; for (var id in bag) { if (!(id in n)) { n[id] = bag[id]; } } return n; }, evaluateLoanPromotions: function (now, bag, values) { var ibag = {}; var existingpromo = null; var percentpromo = null; var percentpromos = []; var usedPromotions = []; if (bag['existing-promotions']) { var existingPromos = bag['existing-promotions'].sort(function (a, b) { return b.priority - a.priority; }); ibag = this.freshBag(bag, null); for (var i = 0; i < existingPromos.length; i++) { existingpromo = existingPromos[i]; if (existingpromo['type'] == 'percent') { percentpromo = { 'amount': parseFloat(existingpromo['amount']), 'id': existingpromo['promoId'] }; if (existingpromo['installmentIndex'] !== undefined) { // installment index can be 0 and then it wouldnt go in percentpromo['installment_index'] = existingpromo['installmentIndex'] + 1; // + 1 because for promostaging the first installment is index 1 and not 0 } if (!percentpromos[existingpromo['feeTypeId']]) { percentpromos[existingpromo['feeTypeId']] = []; } percentpromos[existingpromo['feeTypeId']].push(percentpromo); usedPromotions.push(existingpromo['promoId'].toString()); } } ibag['discounts_per'] = percentpromos; this.fillDiscounts(ibag, values); } var quit = false; var use_limit = 0; if (!usedPromotions.includes('3633') && (use_limit == 0 || !bag['customer-promotions'] || !bag['customer-promotions']['3633'] || !bag['customer-promotions']['3633']['times-used'] || bag['customer-promotions']['3633']['times-used'] < use_limit ) ) { if (bag['bag_promotion_id']) { if (3633 == bag['bag_promotion_id']) { quit = this.addPromotionResult(now, bag, values, 1574347620000, 7980389220000, 3633); } } else { quit = this.addPromotionResult(now, bag, values, 1574347620000, 7980389220000, 3633); } } if (quit) { return; } }, addPromotionResult: function (now, bag, values, start, end, id) { var ibag = {}; var result = null; if (start < now && now < end) { ibag = this.freshBag(bag, id); result = this.promotions[id].evaluate(ibag); this.fillDiscounts(ibag, values); return result.quit; } return false; }, evaluate: function (bag) { bag[-3] = bag[-3] || bag['loan-amount']; bag[-2] = bag[-2] || bag['loan-term']; var values = { discounts_per: {}, discounts_amn: {}, extensions: {}, total_extension_term: 0, prices: {}, feeRates: {}, discounts: {}, discounted: {}, prices_without_tax: {}, discounted_without_tax: {}, discounts_without_tax: {} }; var that = this; var now = new Date().getTime(); var amn = parseFloat(bag[-3]).toFixed(2); var term = parseFloat(bag[-2]).toFixed(0); var extterm = bag[-1] == undefined ? term : parseFloat(bag[-1]).toFixed(0); // apr calculation is by default true, unless apr is set var apr = bag[-13] == undefined ? true : bag[-13]; if (bag[-7] && (bag[-7] == '-1' || bag[-7] == '1')) { aleg: for (var id in this.pricelists) { if (!('buildPrice' in this.pricelists[id])) { if (!(amn in this.pricelists[id].data)) { var pamn = '0.0'; for (var iamn in this.pricelists[id].data) { if (parseFloat(iamn) > parseFloat(amn)) { if (bag[-7] == '1') { amn = iamn; break aleg; } else { if (parseFloat(pamn) == 0.0) { throw new Error('There is no smaller amount than ' + amn + ' to round down to for pricelist with id ' + id); } amn = pamn; break aleg; } } pamn = iamn; } if (parseFloat(pamn) < parseFloat(amn) && bag[-7] == '1') { amn = pamn; break aleg; } if (parseFloat(pamn) < parseFloat(amn) && bag[-7] == '-1') { amn = pamn; break aleg; } } } } } for (var id in this.pricelists) { if ('buildPrice' in this.pricelists[id]) { bag['pricelist_bag'] = bag['pricelist_bag'] || {}; for (var x in bag) { if (x != 'pricelist_bag') { bag['pricelist_bag'][x] = bag[x]; } } var pricelistResult = this.pricelists[id].buildPrice(amn, term, bag['pricelist_bag']); values.prices[id] = pricelistResult.price; values.feeRates[id] = pricelistResult.rate; } else { if (!(amn in this.pricelists[id].data)) { throw new Error('Invalid Amount ' + amn + ' for pricelist with id ' + id); } if (!term in this.pricelists[id].data[amn]) { throw new Error('Invalid Term ' + term + ' for pricelist with id ' + id + ' for amount ' + amn); } values.prices[id] = this.pricelists[id].data[amn][term]; values.feeRates[id] = 0; } if (this.pricelists[id].tax_percentage && this.pricelists[id].tax_percentage > 0) { values.prices_without_tax[id] = (values.prices[id] / (1 + parseFloat(this.pricelists[id].tax_percentage))).toFixed(2); } } var interest_rate = 264.00; values.feeRates[292] = interest_rate; values.feeRates[284] = 0.00; this.evaluateLoanPromotions(now, bag, values); var details2 = { 'percent-discounts': {}, 'amount-discounts': {}, extensions: {}, original: {}, discount: {}, final: {}, tax_percentage: {}, without_tax: { original: {}, final: {}, discount: {} } }; var subbag = {}; subbag['additional-interest-per-installment'] = bag['additional-interest-per-installment']; subbag['first-installment-date'] = bag['first-installment-date']; subbag['loan-amount'] = amn; subbag['loan-term'] = term; subbag['loan-fee'] = values.prices[284]; subbag['creditline-percent-creditline'] = 0.00; subbag['creditline-percent-creditline-fallback'] = 0.00; subbag['creditline-percent-principal-repay'] = 0.00; subbag['creditline-percent-principal'] = 264.00; subbag['creditline-min-monthly-payment'] = 0.00; subbag['use-equal-principal-distribution'] = false; subbag['frontend-calculate-sample-term'] = bag[-43] ? '0y0m0d' : null; subbag['loan-issue-date'] = bag[-16] ? new Date(bag[-16]) : new Date(); if (bag['calculation-date']) { subbag['loan-issue-date'] = new Date(bag['calculation-date']); } subbag['interval'] = '0y1m0d'; subbag['use-interval'] = false; subbag['interest-rate'] = interest_rate; subbag['grace-period-days'] = ''; if (bag[-42]) { subbag[-42] = bag[-42]; } var _pre_all_installments = this.evaluateInstallments(subbag, values).schedule; var _all_installments = []; var _all_installments_without_tax = []; values.prices = {}; for (var i = 0, max = _pre_all_installments.length; i < max; i += 1) { var inst = _pre_all_installments[i]; var ext_percent = 0; var ext_amount = 0; if (286 in values.discounts_per) { for (var di = 0; di < values.discounts_per[286].length; di++) { ext_percent += values.discounts_per[286][di].amount; } } if (286 in values.discounts_amn) { for (var di = 0; di < values.discounts_amn[286].length; di++) { ext_percent += values.discounts_amn[286][di].amount; } } var discount = Math.min(inst[286] * ext_percent / 100 + ext_amount, inst[286]); inst.original[286] = inst[286]; inst.discounts[286] = discount; inst.discounted[286] = inst.original[286] - discount; var installment = { date: inst.date, monthlyPaymentDate: inst.monthlyPaymentDate, payment: inst.payment, original: { principal: inst.original[285], interest: inst.original[292], commission: inst.original[284], }, principal: inst.discounted[285], interest: inst.discounted[292], commission: inst.discounted[284] }; var installment_without_tax = { date: inst.date, monthlyPaymentDate: inst.monthlyPaymentDate, payment: inst.payment, original: { principal: inst.original[285] / (1 + (this.pricelists[285] && this.pricelists[285].tax_percentage ? parseFloat(this.pricelists[285].tax_percentage) : 0 )), interest: inst.original[292] / (1 + parseFloat(0.000)), commission: inst.original[284] / (1 + (this.pricelists[284] && this.pricelists[284].tax_percentage ? parseFloat(this.pricelists[284].tax_percentage): 0 )) }, principal: inst.discounted[285] / (1 + (this.pricelists[285] && this.pricelists[285].tax_percentage ? parseFloat(this.pricelists[285].tax_percentage) : 0 )), interest: inst.discounted[292] / (1 + parseFloat(0.000)), commission: inst.discounted[284] / (1 + (this.pricelists[284] && this.pricelists[284].tax_percentage ? parseFloat(this.pricelists[284].tax_percentage) : 0)) }; installment_without_tax.payment = installment_without_tax.principal + installment_without_tax.interest + installment_without_tax.commission; _all_installments.push(installment); _all_installments_without_tax.push(installment_without_tax); for (var id in inst.original) { if (!isNaN(id)) { if (!(id in values.discounted)) { values.discounted[id] = 0; } values.discounted[id] += inst.discounted[id]; if (!(id in values.prices)) { values.prices[id] = 0; } values.prices[id] += inst.original[id]; if (!(id in values.discounts)) { values.discounts[id] = 0; } values.discounts[id] += inst.discounts[id]; } } } var loan_total = values.discounted[285] + values.discounted[284] + values.discounted[292]; var loan_total_without_tax = (values.discounted_without_tax[285] || values.discounted[285]) + (values.discounted_without_tax[284] || values.discounted[284]); for (var id in values.prices) { var discountPercentAr = values.discounts_per[id]; var name = ''; if (id in this.pricelists) { name = this.pricelists[id].type; } else if (id == 292) { name = 'interest-fee'; } else if (id == 284) { name = 'loan-fee'; } if (discountPercentAr) { details2['percent-discounts'][name] = discountPercentAr; } var discountAmountAr = values.discounts_amn[id]; if (discountAmountAr) { details2['amount-discounts'][name] = discountAmountAr; } if (values.prices_without_tax[id]) { values.discounted_without_tax[id] = parseFloat((values.discounted[id] / (1 + parseFloat(this.pricelists[id].tax_percentage))).toFixed(2)); values.discounts_without_tax[id] = parseFloat((values.prices_without_tax[id] - values.discounted_without_tax[id]).toFixed(2)); } } var creditLimitResult = null; for (var id in values.prices) { var name = ''; if (id in this.pricelists) { name = this.pricelists[id].type; } else if (id == 292) { name = 'interest-fee'; } else if (id == 284) { name = 'loan-fee'; } details2.original[name] = values.prices[id]; details2.discount[name] = values.discounts[id]; details2.final[name] = values.discounted[id]; details2.without_tax.original[name] = values.prices_without_tax[id] || values.prices[id]; details2.without_tax.discount[name] = values.discounts_without_tax[id] || values.discounts[id]; details2.without_tax.final[name] = values.discounted_without_tax[id] || values.discounted[id]; if (id in this.pricelists) { details2.tax_percentage[name] = parseFloat(this.pricelists[id].tax_percentage || 0); details2.without_tax.original[name] = values.prices_without_tax[id] || values.prices[id]; details2.without_tax.discount[name] = values.discounts_without_tax[id] || values.discounts[id]; details2.without_tax.final[name] = values.discounted_without_tax[id] || values.discounted[id]; } else { var interest_tax_percantage = parseFloat(0.000); details2.tax_percentage[name] = parseFloat(interest_tax_percantage || 0); details2.without_tax.original[name] = parseFloat(values.prices[id] / (1 + interest_tax_percantage)).toFixed(2); details2.without_tax.discount[name] = parseFloat(values.discounts[id] / (1 + interest_tax_percantage)).toFixed(2); details2.without_tax.final[name] = parseFloat(values.discounted[id] / (1 + interest_tax_percantage)).toFixed(2); } } if ((bag[-15] || false) == false) { delete details2.without_tax; delete details2.tax_percentage; } var ret = { 'loan-amount': bag[-3], 'loan-term': bag[-2], 'loan-repay-amount': values.discounted[285], 'loan-fees': values.discounted[284], 'loan-original-fees': values.prices[284], 'loan-total': loan_total, 'loan-free-extension': values.total_extension_term, 'loan-interest-fees': values.discounted['292'], 'loan-original-interest-fees': values.prices['292'], 'creditline-percent-principal-repay-month': 0.0000000000, 'creditline-percent-principal-month': 0.2200000000, 'creditline-percent-creditline-month': 0.0000000000, 'creditline-percent-creditline-fallback-month': 0.0000000000, 'creditline-min-monthly-payment': 0.00, 'creditline-annual-interest-rate': (values.feeRates[292]/ 100).toFixed(10), 'creditline-monthly-interest-rate': (values.feeRates[292]/ 100 / 12).toFixed(10), details: details2, installments: _all_installments, installments_without_tax: _all_installments_without_tax, 'loan-duedate': _all_installments[_all_installments.length - 1].date }; if (apr == true) { var daysInYear = 365; var forcePayoutDate = bag["calculation-date"] ? bag["calculation-date"] : bag[-16] ? bag[-16] : null; ret['loan-apr'] = this.XIRR(parseFloat(bag[-3]), ((bag[-15] || false) == false) ? _all_installments : _all_installments_without_tax, null, forcePayoutDate, daysInYear); } if (values.customer_data) { ret['customer-data'] = values.customer_data; } if (values.feeRates) { ret['fee-rates'] = values.feeRates; } if (creditLimitResult) { ret['loan-credit-limit'] = creditLimitResult.limit; } return ret; }, additional_data: { distribute_equal: false, }, fields: { 460: 'Status', 799: 'Loan count', 3141: 'Product', 3256: 'Next installment remaining debt', 3353: 'Installment Delay', 4102: 'Late installment remaining debt (w/o LF)', 7560: 'is_force_settled', }, dateValidationDefinition: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if (((bag['daysFromNow']() >= 5) && (bag['daysFromNow']() <= 31))) { bag['isValid'](); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; }, freshBag: function (bag, date_to_check) { var n = { date_to_check: date_to_check, holidays: [], valid: false, confirmDate: function (checkDate) { var resultDate = null; if (checkDate === null) { checkDate = "1900-01-01"; } // Check if it is a correct Date object if (checkDate instanceof Date && !isNaN(checkDate.getMonth())) { resultDate = checkDate; } if (typeof checkDate === 'string') { resultDate = new Date(checkDate); // Check if it is a correct date if (isNaN(resultDate.getMonth())) { resultDate = new Date("1900-01-01"); } } if (resultDate === null) { resultDate = new Date("1900-01-01"); } resultDate = new Date(this.getDateToString(resultDate)); resultDate.setMinutes(resultDate.getMinutes() + resultDate.getTimezoneOffset()); return resultDate; }, getDateToString: function (date) { return date.getFullYear() + '-' + ("0" + (date.getMonth() + 1 )).slice(-2) + '-' + ("0" + date.getDate()).slice(-2); }, getValidDateToString: function () { if (!this.valid) { return null; } var date = this.confirmDate(this.date_to_check); return this.getDateToString(date); }, getDay: function () { var date = this.confirmDate(this.date_to_check); return date.getDate(); }, getMonth: function () { var date = this.confirmDate(this.date_to_check); // date.getMonth is 0-indexed return (date.getMonth() + 1); }, getYear: function () { var date = this.confirmDate(this.date_to_check); return date.getFullYear(); }, getDayOfWeek: function () { var date = this.confirmDate(this.date_to_check); return date.getDay(); }, daysInCurrentMonth: function () { var date = this.confirmDate(new Date()); return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); }, isHoliday: function () { var date = this.confirmDate(this.date_to_check); for(var i = 0; i < this.holidays.length; ++i) { holiday = this.confirmDate(this.holidays[i]); if (Date.parse(date) === Date.parse(holiday)) { return true; } } return false; }, daysFromNow: function () { var now = this.confirmDate(new Date()); var dateToCheck = this.confirmDate(this.date_to_check); var diff = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) - Date.UTC(dateToCheck.getFullYear(), dateToCheck.getMonth(), dateToCheck.getDate()); return (Math.floor(diff / (24 * 60 * 60 * 1000)) * -1); }, isValid: function () { this.valid = true; } }; for (var id in bag) { if (!(id in n)) { n[id] = bag[id]; } } return n; }, getRange: function (wished_range) { var max_range = 50; if (wished_range === null || wished_range > max_range || wished_range < 1) { return max_range; } return wished_range; }, getOffset: function (wished_offset) { var min_offset = 0; if (wished_offset === null || wished_offset < min_offset) { return min_offset; } return wished_offset; }, getValidDates: function (bag, offset, range) { range = this.getRange(range); var date = new Date(); date.setDate(date.getDate() + parseInt(this.getOffset(offset))); var validDays = []; for (var i = 0; i < range; i++) { var _date = new Date(date); _date.setDate(_date.getDate() + i); var ibag = this.freshBag(bag, _date); this.evaluate(ibag); if (ibag.valid) { validDays.push(ibag.getValidDateToString()); } } return validDays; }, }, calculateCreditLimitPayoutFees: function(bag) { return this.evaluate(bag); }, }, payday: { pricelists: { '285': { type: 'principal', fee_type_id: '285', tax_percentage: '0.000', data: {}, dimension: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if (true) { bag['setAmountRange'](50, 1000, 10); if (control.canceled) return; bag['setTermRange'](7, 30, 1); if (control.canceled) return; bag['setTermRange'](62, 62, 1); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, price: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if (true) { vars['varterm'] = bag['getTerm'](); if (control.canceled) return; vars['varamnt'] = bag['getAmount'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (true) { bag['setPrice'](vars['varamnt']); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, buildDimension: function (bag = {}) { if (!this.amounts) { var amounts = []; var terms = []; bag = Object.assign({ setAmountRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (amounts.indexOf(i) === -1) { amounts.push(i); } } }, setTermRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (terms.indexOf(i) === -1) { terms.push(i); } } } }, bag); this.dimension.evaluate(bag); amounts.sort(function (a, b) { return a - b; }); terms.sort(function (a, b) { return a - b; }); this.amounts = amounts; this.terms = terms; } return { amounts: this.amounts, terms: this.terms }; }, buildPrice: function (amount, term, pricelist_bag) { var price = null; var rate = 0; var pricebag = pricelist_bag || {}; pricebag.getAmount = function () { return parseFloat(amount); }; pricebag.getTerm = function () { return parseFloat(term); }; pricebag.setPrice = function (data) { price = data; }; pricebag.setRate = function (data) { rate = data; }; pricebag.parseFloat = parseFloat; this.price.evaluate(pricebag); return { price: parseFloat(price).toFixed(2), rate: parseFloat(rate).toFixed(10) }; } }, '286': { type: 'extension-fee', fee_type_id: '286', tax_percentage: '0.000', data: {}, dimension: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if (true) { bag['setAmountRange'](50, 1000, 10); if (control.canceled) return; bag['setTermRange'](7, 15, 1); if (control.canceled) return; bag['setTermRange'](15, 30, 15); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, price: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Pricing (function () { // Group: Variable if (true) { vars['varterm'] = bag['getTerm'](); if (control.canceled) return; vars['open_principal'] = bag['2788']; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: FL & CL if ((bag['799'] >= 1 && vars['varterm'] < 30)) { vars['varlf'] = (vars['open_principal'] * (vars['varterm'] * 0.0175)); if (control.canceled) return; } else { vars['varlf'] = (vars['open_principal'] * (vars['varterm'] * 0.013)); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (true) { bag['setPrice'](vars['varlf']); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, buildDimension: function (bag = {}) { if (!this.amounts) { var amounts = []; var terms = []; bag = Object.assign({ setAmountRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (amounts.indexOf(i) === -1) { amounts.push(i); } } }, setTermRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (terms.indexOf(i) === -1) { terms.push(i); } } } }, bag); this.dimension.evaluate(bag); amounts.sort(function (a, b) { return a - b; }); terms.sort(function (a, b) { return a - b; }); this.amounts = amounts; this.terms = terms; } return { amounts: this.amounts, terms: this.terms }; }, buildPrice: function (amount, term, pricelist_bag) { var price = null; var rate = 0; var pricebag = pricelist_bag || {}; pricebag.getAmount = function () { return parseFloat(amount); }; pricebag.getTerm = function () { return parseFloat(term); }; pricebag.setPrice = function (data) { price = data; }; pricebag.setRate = function (data) { rate = data; }; pricebag.parseFloat = parseFloat; this.price.evaluate(pricebag); return { price: parseFloat(price).toFixed(2), rate: parseFloat(rate).toFixed(10) }; } }, '453': { type: 'preparation-fee', fee_type_id: '453', tax_percentage: '0.000', data: {}, dimension: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if (true) { bag['setAmountRange'](50, 1000, 10); if (control.canceled) return; bag['setTermRange'](7, 30, 1); if (control.canceled) return; bag['setTermRange'](62, 62, 1); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, price: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if (true) { vars['varterm'] = bag['getTerm'](); if (control.canceled) return; vars['varamnt'] = bag['getAmount'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: FL if ((bag['799'] == 0 || [1].indexOf(bag['2438'] ? 1 : 0) !== -1)) { vars['varlf'] = (vars['varamnt'] * 0.1); bag['setPrice'](vars['varlf']); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: CL if ((bag['799'] >= 1 || [0].indexOf(bag['2438'] ? 1 : 0) !== -1)) { vars['varlf'] = (vars['varamnt'] * 0.1); bag['setPrice'](vars['varlf']); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, buildDimension: function (bag = {}) { if (!this.amounts) { var amounts = []; var terms = []; bag = Object.assign({ setAmountRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (amounts.indexOf(i) === -1) { amounts.push(i); } } }, setTermRange: function (min, max, step) { if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step) || step <= 0 || min > max) { throw new Error('Parameters provided are not defined or have a wrong value'); } for (var i = min; i <= max; i += step) { if (terms.indexOf(i) === -1) { terms.push(i); } } } }, bag); this.dimension.evaluate(bag); amounts.sort(function (a, b) { return a - b; }); terms.sort(function (a, b) { return a - b; }); this.amounts = amounts; this.terms = terms; } return { amounts: this.amounts, terms: this.terms }; }, buildPrice: function (amount, term, pricelist_bag) { var price = null; var rate = 0; var pricebag = pricelist_bag || {}; pricebag.getAmount = function () { return parseFloat(amount); }; pricebag.getTerm = function () { return parseFloat(term); }; pricebag.setPrice = function (data) { price = data; }; pricebag.setRate = function (data) { rate = data; }; pricebag.parseFloat = parseFloat; this.price.evaluate(pricebag); return { price: parseFloat(price).toFixed(2), rate: parseFloat(rate).toFixed(10) }; } }, }, fee_types: { 284: 'loan_fee', 285: 'principal', 286: 'extension_fee', 287: 'court_fee', 288: 'penalty', 289: 'late_fee', 290: 'payout_fee', 291: 'cash_fee', 292: 'interest_fee', 297: 'extension_fee_star', 368: 'sale_fee', 453: 'preparation_fee', }, promotions: { 2471: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if ((bag[799] <= 0 && (bag[-3] <= 300 && bag['190'] == 856))) { bag['addDiscountPercent'](284, 50); bag['stopProcessing'](); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, 3633: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if ([4,6,7,11,12].indexOf(parseInt(bag['460'])) !== -1) { (bag['addDiscountPercent'](288, 100) * bag['addDiscountPercent'](289, 100)); bag['stopProcessing'](); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, }, loanlimit: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: failsafe (function () { // Group: Group if (true) { bag['setAvailableLimit'](1000); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; control.skip = false; // Stage: day limits (function () { // Group: only 30 days CL regulation if (bag[799] > 0) { bag['setDayLimit'](30); bag['setAvailableDayLimit'](30); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: 62 days only for the contante.com.es affiliate if ((bag[799] == 0 && [856].indexOf(bag[-8]) === -1)) { bag['setDayLimit'](30); bag['setAvailableDayLimit'](30); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if ((bag['799'] == 0 && [856].indexOf(bag[-8]) !== -1)) { bag['setDayLimit'](62); bag['setAvailableDayLimit'](62); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if ((bag[799] == 0 && [856].indexOf(parseInt(bag[190])) !== -1)) { bag['setDayLimit'](62); bag['setAvailableDayLimit'](62); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; control.skip = false; // Stage: Amount Limits (function () { // Group: FL if (bag[799] == 0) { bag['setAvailableLimit'](300); bag['setVisibleLimit'](300); bag['setSliderDefault'](300, 30, 1); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: CL if (bag[799] == 1) { bag['setAvailableLimit'](500); bag['setVisibleLimit'](500); bag['setSliderDefault'](500, 30, 1); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: CL if (bag[799] == 2) { bag['setAvailableLimit'](600); bag['setVisibleLimit'](600); bag['setSliderDefault'](600, 30, 1); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: CL if (bag[799] == 3) { bag['setAvailableLimit'](700); bag['setVisibleLimit'](700); bag['setSliderDefault'](700, 30, 1); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: CL if (bag[799] == 4) { bag['setAvailableLimit'](800); bag['setVisibleLimit'](800); bag['setSliderDefault'](800, 30, 1); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: CL if (bag[799] == 5) { bag['setAvailableLimit'](900); bag['setVisibleLimit'](900); bag['setSliderDefault'](900, 30, 1); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: CL if (bag[799] >= 6) { bag['setAvailableLimit'](1000); bag['setVisibleLimit'](1000); bag['setSliderDefault'](1000, 30, 1); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, freshBagLimit: function (bag) { var n = { _available_limit: null, _day_lower_bound: null, _visible_limit: null, _day_limit: null, _available_day_limit: null, _lowerbound: null, _income_verify_limit: null, _reject_message: null, _slider_default: null, setSliderDefault: function (amount, term, product) { if (Number.isInteger(amount) && Number.isInteger(term) && Number.isInteger(product)) { this._slider_default = { "amount" : parseFloat(amount).toFixed(2), "term" : parseFloat(term).toFixed(0), "default_product" : product }; } }, setIncomeVerifyLimit: function (limit) { this._income_verify_limit = limit; }, setLimit: function (limit) { this._visible_limit = limit; }, setVisibleLimit: function (limit) { this._visible_limit = limit; }, setAvailableLimit: function (limit) { this._available_limit = limit; }, setHardLimit: function (limit) { this._available_limit = limit; }, getRegistrationField: function (field) { return this[field] || 0; }, getAmount: function () { return bag[-3]; }, getMaximumRepayableAmount: function () { return bag[-22]; }, setDayLowerBound: function (amount) { this._day_lower_bound = amount; }, setDayLimit: function (limit) { this._day_limit = limit; }, setAvailableDayLimit: function (limit) { this._available_day_limit = limit; }, setInstallmentsCount: function (limit) { this._day_limit = limit; }, getInstallmentsCount: function (limit) { return this._day_limit; }, setRejectLoan: function (message) { this._reject_message = message; }, setExtensionLimit: function (limit) { this._extension_limit = limit; }, setLowerBound: function (limit) { this._lowerbound = limit; }, getMonthlyPayment: function (initialPrincipal, annualInterestRate, term) { var annualInterestRate = annualInterestRate / 100; var monthlyInterestRate = annualInterestRate / 12; var divident = initialPrincipal * monthlyInterestRate; var divisor = 1 - (1 / Math.pow((1 + monthlyInterestRate), term)); return divident / divisor; }, getOverpaymentCoefficient: function (annualInterestRate, term) { var initialPrincipal = 1000; var monthlyPayment = this.getMonthlyPayment(initialPrincipal, annualInterestRate, term); var totalRepayableAmount = monthlyPayment * term; return totalRepayableAmount / initialPrincipal; }, getInterestRateApproximationFromAnnuity: function (termMonths) { var termDays = termMonths * 30; var constant = 1.000018; var resultBase = constant + (termDays * 0.0007 + 1) / termMonths; var resultLog = Math.log2(1 + 1 / termMonths); resultBase = Math.pow(resultBase, (1 / resultLog)) - 1; var interestRateApproximationFromAnnuity = ((Math.pow(resultBase, resultLog) - constant) * 12) * 100; var flooredResult = Math.floor(interestRateApproximationFromAnnuity * 100) / 100; return flooredResult; }, calculateTermUpperLimit: function (amount, maxRepayable, interest) { let monthlyInterest = (interest/100) / 12; return (isNaN(Math.ceil(Math.log(1 / (1 - ((amount * monthlyInterest) / maxRepayable))) / Math.log1p(monthlyInterest))) ? 0 : Math.ceil(Math.log(1 / (1 - ((amount * monthlyInterest) / maxRepayable))) / Math.log1p(monthlyInterest))); }, }; for (var id in bag) { if (!(id in n)) { n[id] = bag[id]; } } if ( ! (-8 in n)) { n[-8] = 508; } n["round"] = this.round return n; }, filterAvailableTerms: function (bag, checkFunction) { var limits = this.evaluateLimit(bag); var lowestAvailableTerm = null; for (var index = 0; index < limits.terms.length; index++) { var t = limits.terms[index]; bag['-2'] = t; var currentTermLimits = this.evaluateLimit(bag); if (checkFunction) { if (checkFunction(currentTermLimits)) { lowestAvailableTerm = t; break; } } else if (currentTermLimits.amounts_available.length > 0) { lowestAvailableTerm = t; break; } } return limits.terms.slice(limits.terms.indexOf(lowestAvailableTerm)); }, evaluateLimit: function (bag) { var finalAmounts_available = []; var finalAmounts_visible = []; var finalDays = []; var finalDays_available = []; var finalExtDays = []; var ibag = this.freshBagLimit(bag); var result = this.loanlimit.evaluate(ibag); this.pricelists[285].buildDimension(bag); this.pricelists[286].buildDimension(bag); this.pricelists[453].buildDimension(bag); for (var i = 0; i < this.pricelists[285].amounts.length; i++) { var amn = this.pricelists[285].amounts[i]; if ((ibag._available_limit == null || parseFloat(amn) <= ibag._available_limit) && (ibag._lowerbound == null || parseFloat(amn) >= ibag._lowerbound)) { finalAmounts_available.push(parseFloat(amn).toFixed(2)); } if ((ibag._visible_limit == null || parseFloat(amn) <= ibag._visible_limit) && (ibag._lowerbound == null || parseFloat(amn) >= ibag._lowerbound)) { finalAmounts_visible.push(parseFloat(amn).toFixed(2)); } } for (var i = 0; i < this.pricelists[285].terms.length; i++) { var day = this.pricelists[285].terms[i]; if ((ibag._day_limit == null || parseFloat(day) <= ibag._day_limit) && (ibag._day_lower_bound == null || parseFloat(day) >= ibag._day_lower_bound)) { finalDays.push(parseFloat(day).toFixed(0)); } if ((ibag._available_day_limit == null || parseFloat(day) <= ibag._available_day_limit) && (ibag._day_lower_bound == null || parseFloat(day) >= ibag._day_lower_bound)) { finalDays_available.push(parseFloat(day).toFixed(0)); } } for (var i = 0; i < this.pricelists[286].terms.length; i++) { var day = this.pricelists[286].terms[i]; if (ibag._extension_limit == null || parseFloat(day) <= ibag._extension_limit) { finalExtDays.push(parseFloat(day).toFixed(0)) } } return { 'pricelist-amount-limit': ibag._visible_limit, 'pricelist-amount-hard-limit': ibag._available_limit, 'pricelist-amount-lower-bound': ibag._lowerbound, 'pricelist-day-limit': ibag._day_limit, 'pricelist-available-day-limit': ibag._available_day_limit, 'pricelist-income-verify-limit': ibag._income_verify_limit, 'pricelist-day-lower-bound': ibag._day_lower_bound, 'reject_message': ibag._reject_message, amounts_available: finalAmounts_available, amounts_visible: finalAmounts_visible, terms: finalDays, terms_available: finalDays_available, extterms: finalExtDays, slider_default: ibag._slider_default } }, prolongPermissionRules: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if ([1,3].indexOf(parseInt(bag['3141'])) !== -1) { bag['prohibitProlongToCreditline'](null); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; } }, freshBagProlongPermissionRule: function (bag) { var n = { _loanProlongPermitted: false, _loanProlongPermittedToPayday: false, _loanProlongPermittedToInstallment: false, _loanProlongPermittedToCreditline: false, _errorMessage: null, _errorMessageProlongToPayday: null, _errorMessageProlongToInstallment: null, _errorMessageProlongToCreditline: null, _loanSettingId: null, _feetypes: [], allowProlong: function () { this._loanProlongPermitted = true; this._errorMessage = null; }, allowProlongToPayday: function () { this._loanProlongPermittedToPayday = true; this._errorMessageProlongToPayday = null; }, allowProlongToInstallment: function () { this._loanProlongPermittedToInstallment = true; this._errorMessageProlongToInstallment = null; }, allowProlongToCreditline: function () { this._loanProlongPermittedToCreditline = true; this._errorMessageProlongToCreditline = null; }, prohibitProlong: function (msg) { this._loanProlongPermitted = false; this._errorMessage = msg; }, setLoanSettings: function (id) { this._loanSettingId = id; }, prohibitProlongToPayday: function (msg) { this._loanProlongPermittedToPayday = false; this._errorMessageProlongToPayday = msg; }, prohibitProlongToInstallment: function (msg) { this._loanProlongPermittedToInstallment = false; this._errorMessageProlongToInstallment = msg; }, prohibitProlongToCreditline: function (msg) { this._loanProlongPermittedToCreditline = false; this._errorMessageProlongToCreditline = msg; }, feetypeToNewLoan: function (feetype, percentage, maxvalue) { this._feetypes.push({ "feetype": feetype, "percentage": percentage, "maxvalue": maxvalue }); }, hasLoanOpenInAllLenders: function () { return bag[-33]; }, isRequestForLoanFromPlaton: function () { return bag["is-backend"] || false; }, isRequestForLoanFromClientzone: function () { return !bag["is-backend"]; }, }; for (var id in bag) { if ((typeof bag[id] === 'string') && !isNaN(bag[id]) && isFinite(bag[id]) && typeof bag[id] !== "boolean") { bag[id] = parseFloat(bag[id]); } if (!(id in n)) { n[id] = bag[id]; } } return n; }, evaluateProlongPermisson: function (bag) { var ibag = this.freshBagProlongPermissionRule(bag); var result = this.prolongPermissionRules.evaluate(ibag); return { 'loan-prolong-permitted': ibag._loanProlongPermitted, 'loan-prolong-permitted-to-payday': ibag._loanProlongPermittedToPayday, 'loan-prolong-permitted-to-installment': ibag._loanProlongPermittedToInstallment, 'loan-prolong-permitted-to-creditline': ibag._loanProlongPermittedToCreditline, 'loan-prolong-error': ibag._errorMessage, 'loan-prolong-to-payday-error': ibag._errorMessageProlongToPayday, 'loan-prolong-to-installment-error': ibag._errorMessageProlongToInstallment, 'loan-prolong-to-creditline-error': ibag._errorMessageProlongToCreditline, 'prolong-loan-setting-id': ibag._loanSettingId, 'loan-feetype-mapping': ibag._feetypes } }, evaluateExtensionPromotions: function (now, bag, values) { var ibag = {}; var result = null; var existingpromo = null; var percentpromo = null; var percentpromos = []; var usedPromotions = []; if (bag['existing-promotions']) { var existingPromos = bag['existing-promotions'].sort(function (a, b) { return b.priority - a.priority; }); ibag = this.freshBag(bag, null); for (var i = 0; i < existingPromos.length; i++) { existingpromo = existingPromos[i]; if (existingpromo['type'] == 'percent') { percentpromo = { 'amount': parseFloat(existingpromo['amount']), 'id': existingpromo['promoId'] }; if (existingpromo['installmentIndex'] !== undefined) { // installment index can be 0 and then it wouldnt go in percentpromo['installment_index'] = existingpromo['installmentIndex'] + 1; // + 1 because for promostaging the first installment is index 1 and not 0 } if (!percentpromos[existingpromo['feeTypeId']]) { percentpromos[existingpromo['feeTypeId']] = []; } percentpromos[existingpromo['feeTypeId']].push(percentpromo); usedPromotions.push(existingpromo['promoId'].toString()); } } ibag['discounts_per'] = percentpromos; this.fillDiscounts(ibag, values); } }, extensionPermissionRules: { evaluate: function (bag) { var vars = {}; var control = { canceled: false, quit: false }; bag['exit'] = function() { control.canceled = true; }; bag['stopProcessing'] = function() { bag['exit'](); control.quit = true; }; bag['skip'] = function() { control.skip = true; }; control.skip = false; // Stage: Stage (function () { // Group: Group if (([1].indexOf(bag['2160'] ? 1 : 0) !== -1 && (bag['2697'] !== null && (bag['9463'] == 0 && bag['9462'] == 0)))) { bag['prohibitExtension']('Loan is sold to a buyer'); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Prohibit for Waivers if ([13].indexOf(parseInt(bag['460'])) !== -1) { bag['prohibitExtension']('Loan in status In Waiving'); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Prohibit when Loan has Credit Limit Payout in progress if (bag['9572'] == 1) { bag['prohibitExtension']('Loan has Credit Limit payout in progress'); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Prohibit for bailiff status if ([7].indexOf(parseInt(bag[460])) !== -1) { bag['prohibitExtension']('Loan is in bailiff'); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Delay rule if (([4].indexOf(parseInt(bag[460])) !== -1 && bag[802] > 90)) { bag['prohibitExtension']('Delay > 90 days'); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Everything from Platon if (bag[-14] == 1) { bag['allowExtension'](); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Allow ext until 90DPD if (([1,3,4].indexOf(parseInt(bag['460'])) !== -1 && bag['802'] <= 90)) { bag['allowExtension'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Terms in CLZ if (((bag[-1] == 10 || bag[-1] == 15) || bag[-1] == 30)) { bag['allowExtension'](); bag['exit'](); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Covid prohibit rest if (true) { bag['prohibitExtension']('Prohibited'); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; return control; }, freshBag: function (bag, extensionPrice) { var n = { _loanExtensionPermitted: false, _errorMessage: null, allowExtension: function () { this._loanExtensionPermitted = true; this._errorMessage = null; }, prohibitExtension: function (msg) { this._loanExtensionPermitted = false; this._errorMessage = msg; } }; for (var id in bag) { if ((typeof bag[id] === 'string') && !isNaN(bag[id]) && isFinite(bag[id])) { bag[id] = parseFloat(bag[id]); } if (!(id in n)) { n[id] = bag[id]; } } n[-11] = extensionPrice; return n; } }, evaluateExtension: function (bag) { bag[-3] = bag[-3] || bag['loan-amount'] bag[-2] = bag[-2] || bag['loan-term'] bag[-1] = bag[-1] || bag['loan-extension-term'] var values = { discounts_per: {}, discounts_amn: {}, extensions: {}, total_extension_term: 0, prices: {}, feeRates: {}, discounts: {}, discounted: {} } var that = this; var now = new Date().getTime(); var amn = parseFloat(bag[-3]).toFixed(2); var term = parseFloat(bag[-2]).toFixed(0); var extterm = parseFloat(bag[-1]).toFixed(0); if (bag[-7] && (bag[-7] == '-1' || bag[-7] == '1')) { aleg: for (var id in this.pricelists) { if (!('buildPrice' in this.pricelists[id])) { if (!(amn in this.pricelists[id].data)) { var pamn = '0.0'; for (var iamn in this.pricelists[id].data) { if (parseFloat(iamn) > parseFloat(amn)) { if (bag[-7] == '1') { amn = iamn; break aleg; } else { if (parseFloat(pamn) == 0.0) { throw new Error('There is no smaller amount than ' + amn + ' to round down to for pricelist with id ' + id); } amn = pamn; break aleg; } } pamn = iamn; } if (parseFloat(pamn) < parseFloat(amn) && bag[-7] == '1') { amn = pamn; break aleg; } if (parseFloat(pamn) < parseFloat(amn) && bag[-7] == '-1') { amn = pamn; break aleg; } } } } } for (var id in this.pricelists) { if ( id != 286 ) { continue; } if ('buildPrice' in this.pricelists[id]) { var save = bag['pricelist_bag']; bag['pricelist_bag'] = {}; for (var x in save) { bag['pricelist_bag'][x] = save[x]; } for (var x in bag) { if (x != 'pricelist_bag') { bag['pricelist_bag'][x] = bag[x]; } } var pricelist_bag = {}; for (var item in bag['pricelist_bag']) { pricelist_bag[item] = bag['pricelist_bag'][item]; } var pricelistResult = this.pricelists[id].buildPrice(amn, extterm, pricelist_bag); var price = parseFloat(pricelistResult.price); var feeRate = parseFloat(pricelistResult.rate); values.prices[id] = price; values.feeRates[id] = feeRate; } else { if (!this.pricelists[id].data) continue; if (!(amn in this.pricelists[id].data)) { throw new Error('Invalid Amount ' + amn + ' for pricelist with id ' + id); } if (!extterm in this.pricelists[id].data[amn]) { throw new Error('Invalid Term ' + extterm + ' for pricelist with id ' + id + ' for amount ' + amn); } values.prices[id] = parseFloat(this.pricelists[id].data[amn][extterm]); values.feeRates[id] = 0; } } if ('existing-fees' in bag) { for (var id in bag['existing-fees']) { if (bag['existing-fees'][id].amount == 0) { continue; } if (!values.prices[id]) { values.prices[id] = 0; } values.prices[id] += bag['existing-fees'][id].amount; } } this.evaluateExtensionPromotions(now, bag, values); var details2 = { 'percent-discounts' : {}, 'amount-discounts' : {}, 'extensions': {}, original: {}, discount: {}, final: {} }; for (var id in values.prices) { var feeType = null; if (id in this.pricelists) { feeType = this.pricelists[id].type; } else if (id in bag['existing-fees']) { feeType = bag['existing-fees'][id].type; } var discountPercentAr = values.discounts_per[id]; var discountPercent = 0; if (discountPercentAr) { details2['percent-discounts'][feeType] = discountPercentAr; for (var i = 0; i < discountPercentAr.length; i++) { discountPercent += parseFloat(discountPercentAr[i].amount); } } var discountAmountAr = values.discounts_amn[id]; var discountAmount = 0; if (discountAmountAr) { details2['amount-discounts'][feeType] = discountAmountAr; for (var i = 0; i < discountAmountAr.length; i++) { discountAmount += parseFloat(discountAmountAr[i].amount); } } var discount = Math.min(values.prices[id] * discountPercent / 100 + discountAmount, values.prices[id]); var finalAmount = values.prices[id] - discount; values.discounts[id] = parseFloat(discount.toFixed(2)); values.discounted[id] = parseFloat(finalAmount.toFixed(2)); details2.original[feeType] = values.prices[id]; details2.discount[feeType] = values.discounts[id]; details2.final[feeType] = values.discounted[id]; } var ibag = null; var extensionPrice = values.discounted[286]; ibag = this.extensionPermissionRules.freshBag(bag, extensionPrice); this.extensionPermissionRules.evaluate(ibag); var calc_loan_duedate = bag['loan_duedate'] ? new Date(Date.parse(bag['loan_duedate'])) : null; if (calc_loan_duedate) { var calc_loan_is_payday = bag['loan_is_payday']; var calc_extension_permitted = ibag == null || (!ibag._errorMessage && ibag._loanExtensionPermitted); var calc_term = parseInt(extterm); var calc_duedate = null; var calc_now = new Date(); var calc_late = calc_loan_duedate < calc_now; var calc_actDuedate = calc_late ? calc_now : calc_loan_duedate; if (calc_extension_permitted) { calc_duedate = calc_actDuedate; if (calc_loan_is_payday) { calc_duedate.setDate(calc_duedate.getDate() + calc_term); } else { calc_duedate.setMonth(calc_duedate.getMonth() + calc_term); } calc_duedate = (calc_duedate.toISOString()).substr(0, 10); } } return { 'loan-amount': bag[-3], 'loan-term': bag[-2], 'loan-extension-term': bag[-1], 'loan-extension-fees': values.discounted[286], 'loan-extension-duedate': calc_duedate, 'loan-extension-permitted': ibag._loanExtensionPermitted, 'loan-extension-error': ibag._errorMessage, 'loan-original-extension-fees': values.prices[286], 'loan-free-extension': values.total_extension_term, details: details2, }; }, evaluateAdditionalPayout: function (bag) { bag[-3] = bag[-3] || bag['loan-amount'] bag[-2] = bag[-2] || bag['loan-term'] bag[-1] = bag[-1] || bag['loan-additional-payout-amount'] var prices = {} var that = this; var now = new Date().getTime(); var amn = parseFloat(bag[-3]).toFixed(2); var term = parseFloat(bag[-2]).toFixed(0); var additionalPayoutAmount = parseFloat(bag[-1]).toFixed(2); if ('buildPrice' in this.additional_payout_pricelist) { var save = bag['pricelist_bag']; bag['pricelist_bag'] = {}; for (var x in save) { bag['pricelist_bag'][x] = save[x]; } for (var x in bag) { if (x != 'pricelist_bag') { bag['pricelist_bag'][x] = bag[x]; } } prices = this.additional_payout_pricelist.buildPrice(additionalPayoutAmount, term, bag['pricelist_bag']); } else { if (!(additionalPayoutAmount in this.additional_payout_pricelist.data)) { throw new Error('Invalid Amount ' + additionalPayoutAmount + ' for additionalPayoutPricelist with id ' + this.additional_payout_pricelist.id); } if (!term in this.additional_payout_pricelist.data[additionalPayoutAmount]) { throw new Error('Invalid Term ' + term + ' for additionalPayoutPricelist with id ' + this.additional_payout_pricelist.id + ' for amount ' + additionalPayoutAmount); } prices = this.additional_payout_pricelist.data[additionalPayoutAmount][term]; } return { 'loan-amount': bag[-3], 'loan-term': bag[-2], 'loan-additional-payout-amount': bag[-1], 'loan-additional-payout-fees': prices, }; }, calculateAPR: function (startAmount, values) { var PRECISION = 0.00001; var MAX_ATTEMPTS = 1000; var bsMinB = -0.999999999999; var bsMaxB = 2000000000; var principal = startAmount; var attempts = 0; var repaymentTotal = 0; var APRRate = 1; while (Math.abs(repaymentTotal - principal) > PRECISION) { if (attempts >= MAX_ATTEMPTS || (bsMaxB == bsMinB) && (bsMaxB == APRRate)) { return NaN; } attempts++; if (attempts > 1) { if (repaymentTotal < principal) { bsMaxB = APRRate; } else { bsMinB = APRRate; } APRRate = bsMaxB - (bsMaxB - bsMinB) / 2; } repaymentTotal = 0; for (var i = 0; i < values.length; i++) { var rt = Math.pow(1 + APRRate, (i + 1) / 12); // 12 = compounding period repaymentTotal += values[i].payment / rt; } } return APRRate * 100; }, DaysBetween: function(date1, date2) { var oneDay = 24*60*60*1000; return Math.round(Math.abs((date1.getTime() - date2.getTime())/oneDay)); }, XNPV: function(rate, values, daysInYear) { var payment_fee_percentage = 0.00 / 100; var xnpv = 0.0; var firstDate = new Date(values[0].date); for (var i = 0, max = values.length; i < max; i += 1) { var tmp = values[i]; var value; if (tmp.payment < 0) { value = tmp.payment * (1 - payment_fee_percentage); } else { value = tmp.payment * (1 + payment_fee_percentage); } var date = new Date(tmp.date); xnpv += value / Math.pow(1 + rate, this.DaysBetween(firstDate, date)/daysInYear); } return xnpv; }, aprForPayday: function(initialAmount, term, toRepay, daysInYear) { var payment_fee_percentage = 0.00 / 100; return Math.round((Math.pow(toRepay * (1 + payment_fee_percentage) / (initialAmount * (1 - payment_fee_percentage)), daysInYear / term) - 1) * 10000) / 100; }, arForPayday: function (initialAmount, term, toRepay, daysInYear) { var price = toRepay - initialAmount; return Math.round(price / initialAmount * (daysInYear / term) * 10000) / 100; }, rpyForPayday: function (initialAmount, term, toRepay, daysInYear) { var price = toRepay - initialAmount; return Math.round((price / initialAmount / term * daysInYear) * 10000) / 100; }, rpmForPayday: function (initialAmount, term, toRepay, daysInYear) { var price = toRepay - initialAmount; return Math.round((price / initialAmount / term * daysInYear / 12) * 10000) / 100; }, rpdForPayday: function (initialAmount, term, toRepay) { var price = toRepay - initialAmount; return Math.round((price / initialAmount / term) * 10000) / 100; }, XIRR: function(initialAmount, values, guess, forcePayoutDate, daysInYear) { if (!guess) guess = 0.1; var payoutDate = forcePayoutDate ? new Date(forcePayoutDate) : new Date(); payoutDate.setMinutes(0); payoutDate.setHours(0); payoutDate.setSeconds(0); values = values.slice(0); values.unshift({payment:-initialAmount, date:payoutDate}); var x1 = 0.0; var x2 = guess; var f1 = this.XNPV(x1, values, daysInYear); var f2 = this.XNPV(x2, values, daysInYear); for (var i = 0; i < 100; i++) { if ((f1 * f2) < 0.0) break; if (Math.abs(f1) < Math.abs(f2)) { f1 = this.XNPV(x1 += 1.6 * (x1 - x2), values, daysInYear); } else { f2 = this.XNPV(x2 += 1.6 * (x2 - x1), values, daysInYear); } }; if ((f1 * f2) > 0.0) return null; var f = this.XNPV(x1, values, daysInYear); if (f < 0.0) { var rtb = x1; var dx = x2 - x1; } else { var rtb = x2; var dx = x1 - x2; }; for (var i = 0; i < 100; i++) { dx *= 0.5; var x_mid = rtb + dx; var f_mid = this.XNPV(x_mid, values, daysInYear); if (f_mid <= 0.0) rtb = x_mid; if ((Math.abs(f_mid) < 1.0e-6) || (Math.abs(dx) < 1.0e-6)) return Math.max(0, x_mid * 100); }; return null; }, rpyForInstallment: function (interestRate) { return interestRate; }, rpmForInstallment: function (interestRate) { return interestRate / 12; }, rpdForInstallment: function (interestRate, daysInYear) { return interestRate / daysInYear; }, // add base functions fillDiscounts: function (ibag, values) { for (var id in ibag.discounts_per) { if (!(id in values.discounts_per)) { values.discounts_per[id] = []; } for (var i = 0; i < ibag.discounts_per[id].length; i++) { values.discounts_per[id].push(ibag.discounts_per[id][i]); } } for (var id in ibag.discounts_amn) { if (!(id in values.discounts_amn)) { values.discounts_amn[id] = []; } for (var i = 0; i < ibag.discounts_amn[id].length; i++) { values.discounts_amn[id].push(ibag.discounts_amn[id][i]); } } if (!('designations' in values)) { values.designations = {}; } for (var promo_id in ibag.designations) { values.designations[promo_id] = ibag.designations[promo_id]; } for (var promo_id in ibag.customer_data) { if (!('customer_data' in values)) { values.customer_data = {}; } values.customer_data[promo_id] = ibag.customer_data[promo_id]; } var totalTerm = 0; for (var i = 0; i < ibag.extensions.length; i++) { var promo_id = ibag.extensions[i].id; if (!(promo_id in values.extensions)) { values.extensions[promo_id] = 0; } values.extensions[promo_id] += ibag.extensions[i].term; values.total_extension_term += ibag.extensions[i].term; } }, round: function (value, interval, mode) { var returnValue = null; var value = value / interval; switch (mode) { case 0: //FUNCTION_ROUND_FLOOR returnValue = Math.floor(value); break; case 1: //FUNCTION_ROUND_CEIL returnValue = Math.ceil(value); break; case 3: //FUNCTION_ROUND_HALF_DOWN returnValue = -Math.round(-value); break; case 2: //FUNCTION_ROUND_HALF_UP returnValue = Math.round(value); break; } return returnValue ? returnValue / (1 / interval) : returnValue; }, freshBag: function (bag, promo_id) { var that = this; var n = { discounts_per: {}, discounts_amn: {}, extensions: [], designations: {}, customer_data: {}, addInstallmentPromotionPercent: function (feetype, amount, installment_index) { if (!(feetype in this.discounts_per)) { this.discounts_per[feetype] = []; } this.discounts_per[feetype].push({ amount: amount, id: promo_id, installment_index: installment_index }); }, addInstallmentPromotion: function (feetype, amount, installment_index) { if (!(feetype in this.discounts_amn)) { this.discounts_amn[feetype] = []; } this.discounts_amn[feetype].push({ amount: amount, id: promo_id, installment_index: installment_index }); }, addDiscountPercent: function (feetype, amount) { if (!(feetype in this.discounts_per)) { this.discounts_per[feetype] = []; } this.discounts_per[feetype].push({ amount: amount, id: promo_id }); }, addDiscount: function (feetype, amount) { if (!(feetype in this.discounts_amn)) { this.discounts_amn[feetype] = []; } this.discounts_amn[feetype].push({ amount: amount, id: promo_id }); }, setDesignation: function (designation) { this.designations[promo_id] = designation; }, addFreeExtension: function (term) { this.extensions.push({ term: term, id: promo_id }); }, getCustomerData: function (type) { if ('customer-data' in bag && type in bag['customer-data']) { return bag['customer-data'][type]; } else { return null; } }, setCustomerData: function (type, data) { if (!(promo_id in this.customer_data)) { this.customer_data[promo_id] = []; } this.customer_data[promo_id].push({ type: type, data: data }); }, round: that.round, }; for (var id in bag) { if (!(id in n)) { n[id] = bag[id]; } } return n; }, evaluateLoanPromotions: function (now, bag, values) { var ibag = {}; var existingpromo = null; var percentpromo = null; var percentpromos = []; var usedPromotions = []; if (bag['existing-promotions']) { var existingPromos = bag['existing-promotions'].sort(function (a, b) { return b.priority - a.priority; }); ibag = this.freshBag(bag, null); for (var i = 0; i < existingPromos.length; i++) { existingpromo = existingPromos[i]; if (existingpromo['type'] == 'percent') { percentpromo = { 'amount': parseFloat(existingpromo['amount']), 'id': existingpromo['promoId'] }; if (existingpromo['installmentIndex'] !== undefined) { // installment index can be 0 and then it wouldnt go in percentpromo['installment_index'] = existingpromo['installmentIndex'] + 1; // + 1 because for promostaging the first installment is index 1 and not 0 } if (!percentpromos[existingpromo['feeTypeId']]) { percentpromos[existingpromo['feeTypeId']] = []; } percentpromos[existingpromo['feeTypeId']].push(percentpromo); usedPromotions.push(existingpromo['promoId'].toString()); } } ibag['discounts_per'] = percentpromos; this.fillDiscounts(ibag, values); } var quit = false; var use_limit = 1; if (!usedPromotions.includes('2471') && (use_limit == 0 || !bag['customer-promotions'] || !bag['customer-promotions']['2471'] || !bag['customer-promotions']['2471']['times-used'] || bag['customer-promotions']['2471']['times-used'] < use_limit ) ) { if (bag['bag_promotion_id']) { if (2471 == bag['bag_promotion_id']) { quit = this.addPromotionResult(now, bag, values, 1549366980000, 1855153740000, 2471); } } else { quit = this.addPromotionResult(now, bag, values, 1549366980000, 1855153740000, 2471); } } if (quit) { return; } var use_limit = 0; if (!usedPromotions.includes('3633') && (use_limit == 0 || !bag['customer-promotions'] || !bag['customer-promotions']['3633'] || !bag['customer-promotions']['3633']['times-used'] || bag['customer-promotions']['3633']['times-used'] < use_limit ) ) { if (bag['bag_promotion_id']) { if (3633 == bag['bag_promotion_id']) { quit = this.addPromotionResult(now, bag, values, 1574347620000, 7980389220000, 3633); } } else { quit = this.addPromotionResult(now, bag, values, 1574347620000, 7980389220000, 3633); } } if (quit) { return; } }, addPromotionResult: function (now, bag, values, start, end, id) { var ibag = {}; var result = null; if (start < now && now < end) { ibag = this.freshBag(bag, id); result = this.promotions[id].evaluate(ibag); this.fillDiscounts(ibag, values); return result.quit; } return false; }, evaluate: function (bag) { bag[-3] = bag[-3] || bag['loan-amount'] bag[-2] = bag[-2] || bag['loan-term'] var values = { discounts_per: {}, discounts_amn: {}, extensions: {}, total_extension_term: 0, prices: {}, feeRates: {}, discounts: {}, discounted: {}, prices_without_tax: {}, discounted_without_tax: {}, discounts_without_tax: {} } var that = this; var now = new Date().getTime(); var amn = parseFloat(bag[-3]).toFixed(2); var term = parseFloat(bag[-2]).toFixed(0); var extterm = bag[-1] == undefined ? term : parseFloat(bag[-1]).toFixed(0); // apr calculation is by default true, unless apr is set var apr = bag[-13] == undefined ? true : bag[-13]; var excludeFeesFromApr = bag['exclude-fees-from-apr'] == undefined ? [] : bag['exclude-fees-from-apr']; if (bag[-7] && (bag[-7] == '-1' || bag[-7] == '1')) { aleg: for (var id in this.pricelists) { if (!('buildPrice' in this.pricelists[id])) { if (!(amn in this.pricelists[id].data)) { var pamn = '0.0'; for (var iamn in this.pricelists[id].data) { if (parseFloat(iamn) > parseFloat(amn)) { if (bag[-7] == '1') { amn = iamn; break aleg; } else { if (parseFloat(pamn) == 0.0) { throw new Error('There is no smaller amount than ' + amn + ' to round down to for pricelist with id ' + id); } amn = pamn; break aleg; } } pamn = iamn; } if (parseFloat(pamn) < parseFloat(amn) && bag[-7] == '1') { amn = pamn; break aleg; } if (parseFloat(pamn) < parseFloat(amn) && bag[-7] == '-1') { amn = pamn; break aleg; } } } } } for (var id in this.pricelists) { if (bag['ignore-fee'] && bag['ignore-fee'][this.fee_types[id]]) { values.prices[id] = 0; continue; } if ('buildPrice' in this.pricelists[id]) { bag['pricelist_bag'] = bag['pricelist_bag'] || {}; for (var x in bag) { if (x != 'pricelist_bag') { bag['pricelist_bag'][x] = bag[x]; } } var pricelistResult = this.pricelists[id].buildPrice(amn, term, bag['pricelist_bag']); values.prices[id] = pricelistResult.price; values.feeRates[id] = pricelistResult.rate; } else { if (!(amn in this.pricelists[id].data)) { throw new Error('Invalid Amount ' + amn + ' for pricelist with id ' + id); } if (!term in this.pricelists[id].data[amn]) { throw new Error('Invalid Term ' + term + ' for pricelist with id ' + id + ' for amount ' + amn); } values.prices[id] = this.pricelists[id].data[amn][term]; values.feeRates[id] = 0; } if (this.pricelists[id].tax_percentage && this.pricelists[id].tax_percentage > 0) { values.prices_without_tax[id] = (values.prices[id] / (1 + parseFloat(this.pricelists[id].tax_percentage))).toFixed(2); } } this.evaluateLoanPromotions(now, bag, values); var details2 = { 'percent-discounts': {}, 'amount-discounts': {}, extensions: {}, original: {}, discount: {}, final: {}, tax_percentage: {}, without_tax: { original: {}, final: {}, discount: {} } }; var productId = false ? 2 : 1; var recalcVersion = 0; if (bag[-42]) { bag[-42].forEach(function (version) { if (version.product_id == productId) { recalcVersion = version.recalc_version; } }); } if (recalcVersion >= 3.1) { function calculateInterestFee(amount, interestRate, daysInYear, term) { return (amount * interestRate / 100 / daysInYear * term).toFixed(2); } function calculateLoanFee(amount, loanFeeRate, daysInYear, term) { return (parseFloat(loanFeeRate) / 100 / daysInYear * amount * term).toFixed(2); } var amount = bag[-3]; if (!values.feeRates[292]) { values.feeRates[292] = 0; } values.feeRates[284] = 329.4000000000; var daysPerYear = 366; var interestRate = values.feeRates[292]; var loanFeeRate = values.feeRates[284]; var interest = calculateInterestFee(amount, interestRate, daysPerYear, term); var loanFee = calculateLoanFee(amount, loanFeeRate, daysPerYear, term); values.prices[292] = interest; values.prices[284] = loanFee; values.feeRates['interest-fee-rate'] = interestRate; values.feeRates['loan-fee-rate'] = loanFeeRate; if (0.000 > 0) { values.prices_without_tax[292] = (values.prices[292] / (1 + parseFloat(0.000))).toFixed(2); } if (0.000 > 0) { values.prices_without_tax[284] = (values.prices[284] / (1 + parseFloat(0.000))).toFixed(2); } } for (var id in values.prices) { var discountPercentAr = values.discounts_per[id]; var discountPercent = 0; var name = ''; if (id in this.pricelists) { name = this.pricelists[id].type; } else if (id == 292) { name = 'interest-fee'; } else if (id == 284) { name = 'loan-fee'; } if (discountPercentAr) { if (discountPercentAr) { details2['percent-discounts'][name] = discountPercentAr; } for (var i = 0; i < discountPercentAr.length; i++) { discountPercent += parseFloat(discountPercentAr[i].amount); } } var discountAmountAr = values.discounts_amn[id]; var discountAmount = 0; if (discountAmountAr) { details2['amount-discounts'][name] = discountAmountAr; for (var i = 0; i < discountAmountAr.length; i++) { discountAmount += parseFloat(discountAmountAr[i].amount); } } var discount = Math.min(values.prices[id] * discountPercent / 100 + discountAmount, values.prices[id]); var finalAmount = values.prices[id] - discount; values.discounts[id] = parseFloat(discount.toFixed(2)); values.discounted[id] = parseFloat(finalAmount.toFixed(2)); if (values.prices_without_tax[id]) { if (id in this.pricelists) { values.discounted_without_tax[id] = parseFloat((values.discounted[id] / (1 + parseFloat(this.pricelists[id].tax_percentage))).toFixed(2)); } else if (id == 292) { values.discounted_without_tax[id] = parseFloat((values.discounted[id] / (1 + parseFloat(0.000))).toFixed(2)); } else if (id == 284) { values.discounted_without_tax[id] = parseFloat((values.discounted[id] / (1 + parseFloat(0.000))).toFixed(2)); } else { values.discounted_without_tax[id] = values.discounted[id]; } values.discounts_without_tax[id] = parseFloat((values.prices_without_tax[id] - values.discounted_without_tax[id]).toFixed(2)); } } var loan_total = values.discounted[285] + values.discounted[284]; loan_total += values.discounted[453]; if (values.discounted[292]) { loan_total += values.discounted[292]; } var loan_total_without_tax = (values.discounted_without_tax[285] || values.discounted[285]) + (values.discounted_without_tax[284] || values.discounted[284]); loan_total_without_tax += (values.discounted_without_tax[453] || values.discounted[453]); if (values.discounted_without_tax[292] || values.discounted[292]) { loan_total_without_tax += (values.discounted_without_tax[292] || values.discounted[292]); } for (var id in values.prices) { if (id < 0) { continue; } var name = ''; if (id in this.pricelists) { name = this.pricelists[id].type; } else if (id == 292) { name = 'interest-fee'; } else if (id == 284) { name = 'loan-fee'; } details2.original[name] = values.prices[id]; details2.discount[name] = values.discounts[id]; details2.final[name] = values.discounted[id]; details2.without_tax.original[name] = values.prices_without_tax[id] || values.prices[id]; details2.without_tax.discount[name] = values.discounts_without_tax[id] || values.discounts[id]; details2.without_tax.final[name] = values.discounted_without_tax[id] || values.discounted[id]; if (id in this.pricelists) { details2.tax_percentage[name] = parseFloat(this.pricelists[id].tax_percentage || 0); details2.without_tax.original[name] = values.prices_without_tax[id] || values.prices[id]; details2.without_tax.discount[name] = values.discounts_without_tax[id] || values.discounts[id]; details2.without_tax.final[name] = values.discounted_without_tax[id] || values.discounted[id]; } else { var interest_tax_percantage = parseFloat(0.000); details2.tax_percentage[name] = parseFloat(interest_tax_percantage || 0); details2.without_tax.original[name] = parseFloat(values.prices[id] / (1 + interest_tax_percantage)).toFixed(2); details2.without_tax.discount[name] = parseFloat(values.discounts[id] / (1 + interest_tax_percantage)).toFixed(2); details2.without_tax.final[name] = parseFloat(values.discounted[id] / (1 + interest_tax_percantage)).toFixed(2); } } if ((bag[-15] || false) == false) { delete details2.without_tax; delete details2.tax_percentage; } var ret = { 'loan-amount': bag[-3], 'loan-term': bag[-2], 'loan-repay-amount': values.discounted[285], 'loan-total': loan_total, 'loan-free-extension': values.total_extension_term, 'annual-loan-fee-rate': (values.feeRates[284]/ 100).toFixed(10), details: details2, }; var total_fee_cost_values = 0; if (values.prices['284']) { ret['loan-fees'] = values.discounted[284]; ret['loan-original-fees'] = values.prices[284]; total_fee_cost_values += values.discounted[284]; } if (values.prices['292']) { ret['loan-interest-fees'] = values.discounted['292']; ret['loan-original-interest-fees'] = values.prices['292']; ret['loan-interest-fee-type-id'] = 292; total_fee_cost_values += values.discounted[292]; } if (values.prices['453']) { ret['loan-preparation-fees'] = values.discounted[453]; ret['loan-original-preparation-fees'] = values.prices[453]; ret['loan-preparation-fee-type-id'] = 453; total_fee_cost_values += values.discounted[453]; } ret['loan-total-fee-cost'] = total_fee_cost_values; if (values.customer_data) { ret['customer-data'] = values.customer_data; } if (values.feeRates) { ret['fee-rates'] = values.feeRates; } var loanFee = 284; var interestFee = 292; var preparationFee = 453; var principalFee = 285; if ((bag[-15] || false) != false) { ret['without_tax'] = { 'loan-repay-amount': values.discounted_without_tax[principalFee] || values.discounted[principalFee], 'loan-fees': values.discounted_without_tax[loanFee] || values.discounted[loanFee], 'loan-original-fees': values.prices_without_tax[loanFee] || values.prices[loanFee], 'loan-total': loan_total_without_tax, }; } if (apr == true) { //calculate apr for Payday var recalcVersion = 0; if (bag[-42]) { bag[-42].forEach(function (version) { if (version.product_id == 1) { recalcVersion = version.recalc_version; } }); } var daysInYear = recalcVersion >= 3.1 ? 366 : 365; var discounted_params = parseFloat(bag[-3]) + parseFloat(bag['additional-apr-calculation-cost'] || 0) + parseFloat(values.discounted[loanFee] || 0) + parseFloat(values.discounted[interestFee] || 0); if (preparationFee) { discounted_params = discounted_params + parseFloat(values.discounted[preparationFee] || 0); } ret['loan-apr'] = this.aprForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), discounted_params, daysInYear); ret['loan-ar'] = this.arForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), discounted_params, daysInYear); ret['loan-rpy'] = this.rpyForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), discounted_params, daysInYear); ret['loan-rpm'] = this.rpmForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), discounted_params, daysInYear); ret['loan-rpd'] = this.rpdForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), discounted_params); var original_params = parseFloat(bag[-3]) + parseFloat(bag['additional-apr-calculation-cost'] || 0) + parseFloat(values.prices[loanFee] || 0) + parseFloat(values.prices[interestFee] || 0); if (preparationFee) { original_params = original_params + parseFloat(values.prices[preparationFee] || 0); } ret['loan-apr-original'] = this.aprForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), original_params, daysInYear); ret['loan-ar-original'] = this.arForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), original_params, daysInYear); ret['loan-rpy-original'] = this.rpyForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), original_params, daysInYear); ret['loan-rpm-original'] = this.rpmForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), original_params, daysInYear); ret['loan-rpd-original'] = this.rpdForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), original_params); if ((bag[-15] || false) != false) { discounted_params = parseFloat(bag[-3]) + parseFloat(bag['additional-apr-calculation-cost'] || 0) + (!values.discounted_without_tax[loanFee] ? parseFloat(values.discounted[loanFee] || 0) : parseFloat(values.discounted_without_tax[loanFee] || 0)) + (!values.discounted_without_tax[interestFee] ? parseFloat(values.discounted[interestFee] || 0) : parseFloat(values.discounted_without_tax[interestFee] || 0)); if (preparationFee) { discounted_params = discounted_params + (!values.discounted_without_tax[preparationFee] ? parseFloat(values.discounted[preparationFee] || 0) : parseFloat(values.discounted_without_tax[preparationFee] || 0)); } ret['loan-apr'] = this.aprForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), discounted_params, daysInYear); original_params = parseFloat(bag[-3]) + parseFloat(bag['additional-apr-calculation-cost'] || 0) + (!values.prices_without_tax[loanFee] ? parseFloat(values.prices[loanFee] || 0) : parseFloat(values.prices_without_tax[loanFee] || 0)) + (!values.prices_without_tax[interestFee] ? parseFloat(values.prices[interestFee] || 0) : parseFloat(values.prices_without_tax[interestFee] || 0)); if (preparationFee) { original_params = original_params + (!values.prices_without_tax[preparationFee] ? parseFloat(values.prices[preparationFee] || 0) : parseFloat(values.prices_without_tax[preparationFee] || 0)); } ret['loan-apr-original'] = this.aprForPayday(parseFloat(bag[-3]), parseFloat(bag[-2]), original_params, daysInYear); } } let baseDate = bag['calculation-date'] ? new Date(bag['calculation-date']) : new Date(); baseDate.setDate(baseDate.getDate() + bag[-2]); ret['loan-duedate'] = new Date(baseDate); return ret; }, fields: { 190: 'Affiliate', 460: 'Status', 799: 'Loan count', 802: 'Delay', 2160: 'Sold', 2438: 'Is First Loan', 2697: 'Buyer', 2788: 'Open principal', 3141: 'Product', 3256: 'Next installment remaining debt', 3353: 'Installment Delay', 4102: 'Late installment remaining debt (w/o LF)', 7560: 'is_force_settled', 9462: 'Buyer is investor', 9463: 'Buyer is internal', 9572: 'Has Credit Limit Payout In Progress', }, dateValidationDefinition: { evaluate: function (bag) { var control = { canceled: false, quit: false }; (function () { bag["isValid"](); })(); return control; }, freshBag: function (bag, date_to_check) { var n = { date_to_check: date_to_check, holidays: [], valid: false, confirmDate: function (checkDate) { var resultDate = null; if (checkDate === null) { checkDate = "1900-01-01"; } // Check if it is a correct Date object if (checkDate instanceof Date && !isNaN(checkDate.getMonth())) { resultDate = checkDate; } if (typeof checkDate === 'string') { resultDate = new Date(checkDate); // Check if it is a correct date if (isNaN(resultDate.getMonth())) { resultDate = new Date("1900-01-01"); } } if (resultDate === null) { resultDate = new Date("1900-01-01"); } resultDate = new Date(this.getDateToString(resultDate)); resultDate.setMinutes(resultDate.getMinutes() + resultDate.getTimezoneOffset()); return resultDate; }, getDateToString: function (date) { return date.getFullYear() + '-' + ("0" + (date.getMonth() + 1 )).slice(-2) + '-' + ("0" + date.getDate()).slice(-2); }, getValidDateToString: function () { if (!this.valid) { return null; } var date = this.confirmDate(this.date_to_check); return this.getDateToString(date); }, getDay: function () { var date = this.confirmDate(this.date_to_check); return date.getDate(); }, getMonth: function () { var date = this.confirmDate(this.date_to_check); // date.getMonth is 0-indexed return (date.getMonth() + 1); }, getYear: function () { var date = this.confirmDate(this.date_to_check); return date.getFullYear(); }, getDayOfWeek: function () { var date = this.confirmDate(this.date_to_check); return date.getDay(); }, daysInCurrentMonth: function () { var date = this.confirmDate(new Date()); return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); }, isHoliday: function () { var date = this.confirmDate(this.date_to_check); for(var i = 0; i < this.holidays.length; ++i) { holiday = this.confirmDate(this.holidays[i]); if (Date.parse(date) === Date.parse(holiday)) { return true; } } return false; }, daysFromNow: function () { var now = this.confirmDate(new Date()); var dateToCheck = this.confirmDate(this.date_to_check); var diff = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) - Date.UTC(dateToCheck.getFullYear(), dateToCheck.getMonth(), dateToCheck.getDate()); return (Math.floor(diff / (24 * 60 * 60 * 1000)) * -1); }, isValid: function () { this.valid = true; } }; for (var id in bag) { if (!(id in n)) { n[id] = bag[id]; } } return n; }, getRange: function (wished_range) { var max_range = 50; if (wished_range === null || wished_range > max_range || wished_range < 1) { return max_range; } return wished_range; }, getOffset: function (wished_offset) { var min_offset = 0; if (wished_offset === null || wished_offset < min_offset) { return min_offset; } return wished_offset; }, getValidDates: function (bag, offset, range) { range = this.getRange(range); var date = new Date(); date.setDate(date.getDate() + parseInt(this.getOffset(offset))); var validDays = []; for (var i = 0; i < range; i++) { var _date = new Date(date); _date.setDate(_date.getDate() + i); var ibag = this.freshBag(bag, _date); this.evaluate(ibag); if (ibag.valid) { validDays.push(ibag.getValidDateToString()); } } return validDays; }, }, }, }; })(); ;