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 { installment: { pricelists: { '237': { type: 'loan-fee', fee_type_id: '237', 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) { if (control.canceled) return; bag['setAmountRange'](1000, 15000, 100); if (control.canceled) return; bag['setTermRange'](3, 36, 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 efino loan fee (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 (vars['varterm'] == 3) { vars['varprice'] = 2740; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 4) { vars['varprice'] = 3042; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 5) { vars['varprice'] = 3344; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 6) { vars['varprice'] = 3646; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 7) { vars['varprice'] = 3948; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 8) { vars['varprice'] = 4251; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 9) { vars['varprice'] = 4553; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 10) { vars['varprice'] = 4855; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 11) { vars['varprice'] = 5157; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 12) { vars['varprice'] = 5440; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 13) { vars['varprice'] = 5685; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 14) { vars['varprice'] = 5910; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 15) { vars['varprice'] = 6150; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 16) { vars['varprice'] = 6400; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 17) { vars['varprice'] = 6642; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 18) { vars['varprice'] = 6900; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 19) { vars['varprice'] = 7150; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 20) { vars['varprice'] = 7320; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 21) { vars['varprice'] = 7530; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 22) { vars['varprice'] = 7840; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 23) { vars['varprice'] = 8100; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 24) { vars['varprice'] = 8330; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 25) { vars['varprice'] = 8580; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 26) { vars['varprice'] = 8830; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 27) { vars['varprice'] = 9080; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 28) { vars['varprice'] = 9330; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 29) { vars['varprice'] = 9580; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] == 30) { vars['varprice'] = 9830; if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (vars['varterm'] >= 31) { vars['varprice'] = 9900; if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; control.skip = false; // Stage: final price (function () { // Group: Group if (true) { vars['final'] = ((vars['varprice'] * vars['varamnt']) / 10000); if (control.canceled) return; bag['setPrice'](vars['final']); 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) }; } }, '238': { type: 'principal', fee_type_id: '238', 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) { if (control.canceled) return; bag['setAmountRange'](1000, 15000, 100); if (control.canceled) return; bag['setTermRange'](3, 36, 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) }; } }, '239': { type: 'extension-fee', fee_type_id: '239', 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) { if (control.canceled) return; bag['setAmountRange'](1000, 10000, 100); if (control.canceled) return; bag['setTermRange'](1, 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: Define vars (function () { // Group: that price if (true) { bag['setPrice'](0); 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: { 237: 'loan_fee', 238: 'principal', 239: 'extension_fee', 240: 'interest_fee', 241: 'penalty', 242: 'late_fee', 243: 'payout_fee', 244: 'cash_fee', 255: 'court_fee', 256: 'court_fee', 298: 'court_fee', 299: 'court_fee', 300: 'court_fee', 301: 'court_fee', 302: 'court_fee', 303: 'court_fee', 365: 'sale_fee', 431: 'preparation_fee', }, promotions: { }, 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: Hard Limit+instantor now (function () { // Group: Group if (true) { bag['setAvailableLimit'](15000); if (control.canceled) return; bag['setSliderDefault'](10000, 30, 2); if (control.canceled) return; } if (control.canceled || control.skip) return; // Group: Group if (toDate(bag['6192']).getTime() < (1714237625031-7776000000)) { bag['setIncomeVerifyLimit'](1); 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] = 479; } 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[237].buildDimension(bag); this.pricelists[238].buildDimension(bag); this.pricelists[239].buildDimension(bag); for (var i = 0; i < this.pricelists[238].amounts.length; i++) { var amn = this.pricelists[238].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[238].terms.length; i++) { var day = this.pricelists[238].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[239].terms.length; i++) { var day = this.pricelists[239].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 } }, 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 = {}; return { 'loan-prolong-permitted': false, 'loan-prolong-permitted-to-payday': false, 'loan-prolong-permitted-to-installment': false, 'loan-prolong-permitted-to-creditline': false, 'loan-prolong-error': undefined, 'loan-prolong-to-payday-error': undefined, 'loan-prolong-to-installment-error': undefined, 'loan-prolong-to-creditline-error': undefined, 'prolong-loan-setting-id': undefined, 'loan-feetype-mapping': undefined } }, 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: Customer Complaints (function () { // Group: Replace the loan with fake one after you are done if ((bag[-14] == 1 && bag['1217'] == 72238772326262362362)) { (bag['allowExtension']() * bag['exit']()); if (control.canceled) return; } if (control.canceled || control.skip) return; })(); if (control.canceled) return control; control.skip = false; // Stage: Prohibit ALL (function () { // Group: Group if (true) { bag['prohibitExtension']('Extensions 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 != 239 ) { 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[239]; 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[239], 'loan-extension-duedate': calc_duedate, 'loan-extension-permitted': ibag._loanExtensionPermitted, 'loan-extension-error': ibag._errorMessage, 'loan-original-extension-fees': values.prices[239], 'loan-free-extension': values.total_extension_term, details: details2, }; }, installmentSettings: { 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; }; return control; }, freshBag: function(bag) { var n = { promotableInstallmentsCount: 0, setPromotableInstallmentsCount: function (count) { this.promotableInstallmentsCount = count; }, setInstallmentFee: function (dummy, dummy2) {}, setExtensionPrice: function (dummy) {}, getSum: function (dummy) { return 0; }, getOpen: function (dummy) { return 0; }, getRemaining: function (dummy) { return 0; }, round: function (dummy) { return 0; } }; for (var id in bag) { if (!(id in n)) { n[id] = bag[id]; } } return n; }, evaluatePromotableInstallments: function(amount, term, bag) { var ibag = this.freshBag(bag); ibag.getAmount = function () { return amount; }; ibag.getTerm = function () { return term; }; this.evaluate(ibag); return ibag; }, }, evaluateInstallments: function (bag, promotions) { // bag[-42] => for active recalc v3 var recalc3 = false; if (bag[-42]) { bag[-42].forEach(function (version) { if (version.product_id == 2 && version.recalc_version > 3) { recalc3 = true; } }); } var firstInstallmentDate = bag['first-installment-date']; if (firstInstallmentDate) { firstInstallmentDate.setMinutes(0); firstInstallmentDate.setHours(0); firstInstallmentDate.setSeconds(0); } var amount = parseFloat(bag['loan-amount']); var term = parseFloat(bag['loan-term']); var loan_fee = parseFloat(bag['loan-fee']); var original_amount = parseFloat(bag['original-amount']) || 0; var fast_transfer_fee = 0; var fast_transfer_fee_split = 1; var additional_amount_payout_date = null; var interest_rate = parseFloat(bag['interest-rate']) / 100; const preparation_fee = 0; var promotableInstallmentsCount = parseFloat(bag['promotable-installments-count']); var now = bag['calculation-date'] ? new Date(bag['calculation-date']) : new Date(); // update now without time var now = new Date(now.getFullYear(), now.getMonth(), now.getDate()); var due = new Date(now.getFullYear(), now.getMonth() + term, now.getDate()); // we dont allow all installments to be promotable/promoteable if (promotableInstallmentsCount && (term - promotableInstallmentsCount < 1)) { promotableInstallmentsCount = 0; } var calcTerm = term - promotableInstallmentsCount; 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; } if (bag['additional-payout-date']) { additional_amount_payout_date = new Date(bag['additional-payout-date']); } 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 < 0) { p.installment_index = term + p.installment_index + 1; } 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 (p.installment_index < 0) { p.installment_index = term + p.installment_index + 1; } if (p.installment_index - 1 === currentInstallmentIndex) { totalReduc += p.amount; } else if (p.installment_index === undefined) { totalReduc += p.amount; } }; } return Math.max(0, (feeAmount - totalReduc)); } var freeze_now = dateWithoutTime(now); // Todo up var make_schedule = function(pmt) { var paymentDate = new Date(freeze_now), oldDate = paymentDate, repayment_interval; // adapt repayment interval if (firstInstallmentDate) { firstInstallmentDate.setSeconds(1); repayment_interval = Math.floor((firstInstallmentDate - paymentDate) / (1000 * 60 * 60 * 24)); paymentDate = new Date(firstInstallmentDate); } else { repayment_interval = new Date(paymentDate.getFullYear(), paymentDate.getMonth() + 1, 0, 0, 0, 0, 0).getDate(); paymentDate.setMonth(paymentDate.getMonth() + 1); } var schedule = []; var curr_principal = amount; var loan_fee_per_interval = loan_fee / calcTerm; var interest; var curr_interest; var old_year_days, new_year_days; var interest_before_additional_amount, days_to_additonal_amount; var days_to_additional_amount_in_old_year, days_to_additional_amount_in_new_year; var repayment; if (recalc3) { // recalc 3 installment fees cannot have remaining debt left from rounding or cutting loan_fee_per_interval = Math.floor(loan_fee / calcTerm * 100) / 100; } var target_day = paymentDate.getDate(); for (var i = 0; i < calcTerm; i += 1) { // Check for partial payment if (recalc3 && additional_amount_payout_date && i === 0 && original_amount > 0 && paymentDate > additional_amount_payout_date) { curr_principal = original_amount; days_to_additonal_amount = daysBetween(oldDate, additional_amount_payout_date); if (additional_amount_payout_date.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(additional_amount_payout_date.getFullYear(), 0, 1), new Date(additional_amount_payout_date.getFullYear() + 1, 0, 1)); days_to_additional_amount_in_old_year = daysBetween(oldDate, new Date(oldDate.getFullYear() + 1, 0, 1)); days_to_additional_amount_in_new_year = daysBetween(new Date(additional_amount_payout_date.getFullYear(), 0, 1), additional_amount_payout_date); // this is because recalc 3 fees always start with the next day at 00:00:00 days_to_additional_amount_in_old_year--; days_to_additional_amount_in_new_year++; interest_before_additional_amount = ( (curr_principal * (interest_rate / old_year_days * days_to_additional_amount_in_old_year )) + (curr_principal * (interest_rate / new_year_days * days_to_additional_amount_in_new_year )) ); } else { old_year_days = daysBetween(new Date(oldDate.getFullYear(), 0, 1), new Date(oldDate.getFullYear() + 1, 0, 1)); interest_before_additional_amount = curr_principal * (interest_rate / old_year_days * days_to_additonal_amount); days_to_additional_amount_in_old_year = days_to_additonal_amount; days_to_additional_amount_in_new_year = 0; } oldDate = additional_amount_payout_date; curr_principal = amount; } else { interest_before_additional_amount = 0; days_to_additional_amount_in_old_year = 0; days_to_additional_amount_in_new_year = 0; } // Check for leap year 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)) - days_to_additional_amount_in_old_year, installment_days_in_new_year = daysBetween(new Date(paymentDate.getFullYear(), 0, 1), paymentDate) - days_to_additional_amount_in_new_year; if (recalc3) { // this is because recalc 3 fees always start with the next day at 00:00:00 installment_days_in_old_year--; installment_days_in_new_year++; } var interestRateOldYear = interest_rate / old_year_days * (installment_days_in_old_year - days_to_additional_amount_in_old_year); var interestRateNewYear = interest_rate / new_year_days * (installment_days_in_new_year - days_to_additional_amount_in_new_year); interest = (curr_principal * interestRateOldYear) + (curr_principal * interestRateNewYear) + interest_before_additional_amount; curr_interest = Math.floor(interest * 100) / 100; } else { old_year_days = daysBetween(new Date(oldDate.getFullYear(), 0, 1), new Date(oldDate.getFullYear() + 1, 0, 1)); var interestRate = interest_rate / old_year_days * (repayment_interval - days_to_additional_amount_in_old_year - days_to_additional_amount_in_new_year); interest = (curr_principal * interestRate) + interest_before_additional_amount; curr_interest = Math.floor(interest * 100) / 100; } var principal = Math.max(0, pmt - loan_fee_per_interval - curr_interest); var loanfee = calcPromotionDiscount(loan_fee_per_interval, 237, i, promotions); var interest = calcPromotionDiscount(curr_interest, 240, i, promotions); // --------------- creating the installment -------------------- var og_principal = parseFloat(Math.max(0, pmt - loan_fee_per_interval - curr_interest)); var og_loanfee = parseFloat(loan_fee_per_interval); var og_preparationFee = parseFloat(0); var og_fastTransferFee = parseFloat(0); var og_interest = parseFloat(curr_interest); var disc_principal = parseFloat(principal); var disc_loanfee = parseFloat(loanfee); var disc_preparationFee = parseFloat(og_preparationFee); var disc_fastTransferFee = parseFloat(og_fastTransferFee); var disc_interest = parseFloat(interest); if (i == 0) { og_preparationFee = parseFloat(preparation_fee); disc_preparationFee = Math.min(og_preparationFee, disc_loanfee); disc_loanfee = disc_loanfee - disc_preparationFee; og_fastTransferFee = parseFloat(fast_transfer_fee); disc_fastTransferFee = og_fastTransferFee; } if (i + 1 < fast_transfer_fee_split) { og_fastTransferFee = Math.ceil(fast_transfer_fee / fast_transfer_fee_split * 100) / 100; disc_fastTransferFee = og_fastTransferFee; } else if (i + 1 == fast_transfer_fee_split && i > 0) { og_fastTransferFee = parseFloat(fast_transfer_fee - (Math.ceil(fast_transfer_fee / fast_transfer_fee_split * 100) / 100) * i); disc_fastTransferFee = og_fastTransferFee; } repayment = { date: paymentDate, original: { 238: og_principal, 237: og_loanfee, 240: og_interest }, discounted: { 238: disc_principal, 237: disc_loanfee, 240: disc_interest }, discounts: {}, payment: disc_principal + disc_loanfee + disc_interest + disc_preparationFee + disc_fastTransferFee }; repayment.discounts[238] = parseFloat(repayment.original[238] - repayment.discounted[238]); repayment.discounts[237] = parseFloat(repayment.original[237] - repayment.discounted[237]); repayment.discounts[240] = parseFloat(repayment.original[240] - repayment.discounted[240]); curr_principal -= principal; schedule.push(repayment); oldDate = paymentDate; paymentDate = getNextPaymentDate(paymentDate, target_day, null); repayment_interval = daysBetween(oldDate, paymentDate); } // Return schedule and remaining principal return { schedule: schedule, leftover: curr_principal, }; }; var guessRounding = function(value) { if (recalc3) { return Math.round(((value) / 2) * 100) / 100; } else { return Math.ceil(((value) / 2) * 100) / 100; } } var days_diff = Math.floor((due - now) / (1000 * 60 * 60 * 24)); var min_rep = (amount + loan_fee) / calcTerm; var max_rep = (amount + loan_fee + (amount * (interest_rate / 366 * days_diff))) / calcTerm; var old_guess = undefined; var guess = guessRounding(min_rep + max_rep); var sched = make_schedule(guess); var tries = 0; while (Math.abs(sched.leftover) > 0.00001 && guess !== old_guess && tries < 40) { if (sched.leftover > 0) { min_rep = guess; guess = guessRounding(guess + max_rep); } else { max_rep = guess; guess = guessRounding(guess + min_rep); } sched = make_schedule(guess); tries += 1; } var last = sched.schedule.slice(-1)[0]; last.payment += sched.leftover; last.original[238] += sched.leftover; last.discounted[238] += sched.leftover; var paymentDate = last.date; for (var j = 0; j < promotableInstallmentsCount; j += 1) { paymentDate = new Date(paymentDate.getFullYear(), paymentDate.getMonth() + 1, paymentDate.getDate()); var special_loanfee = calcPromotionDiscount(last.payment, 237, calcTerm + j, promotions); var promotableInstallment = { date: paymentDate, original: { 238: 0, 237: last.payment, 240: 0 }, discounted: { 238: 0, 237: special_loanfee, 240: 0 }, discounts: { }, payment: special_loanfee }; promotableInstallment.discounts[238] = parseFloat(promotableInstallment.original[238] - promotableInstallment.discounted[238]); promotableInstallment.discounts[237] = parseFloat(promotableInstallment.original[237] - promotableInstallment.discounted[237]); promotableInstallment.discounts[240] = parseFloat(promotableInstallment.original[240] - promotableInstallment.discounted[240]); sched.schedule.push(promotableInstallment); } return sched; }, 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; }, 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 = true ? 2 : 1; var recalcVersion = 0; if (bag[-42]) { bag[-42].forEach(function (version) { if (version.product_id == productId) { recalcVersion = version.recalc_version; } }); } // if there are installment settings, call the installment handler var interest_rate = 9.000; var fast_transfer_fee_split = null; var _promotable_installments_count = this.installmentSettings.evaluatePromotableInstallments(amn, term, bag).promotableInstallmentsCount; var additional_loan_fee_rate = null; if (bag[-23]) { // risk appetite / additional interest rate interest_rate = parseFloat(interest_rate) + parseFloat(bag[-23]); } values.feeRates[240] = interest_rate; var subbag = {}; subbag['first-installment-date'] = bag['first-installment-date']; subbag['loan-amount'] = amn; subbag['loan-term'] = term; subbag['loan-fee'] = parseFloat(values.prices[237]); if (bag[-69]) { var additional_loan_fee_rate_setting = bag[-69]; if (additional_loan_fee_rate_setting.type === 0) { additional_loan_fee_rate = amn * parseFloat(additional_loan_fee_rate_setting.value) / 100; values.feeRates[237] += additional_loan_fee_rate_setting.value; } else { additional_loan_fee_rate = parseFloat(additional_loan_fee_rate_setting.value); } subbag['loan-fee'] = subbag['loan-fee'] + additional_loan_fee_rate; } subbag['enable-fast-transfer-fee'] = bag['enable-fast-transfer-fee'] == undefined ? [] : bag['enable-fast-transfer-fee']; subbag['original-amount'] = bag[-25] == undefined ? 0 : parseFloat(bag[-25]); subbag['additional-payout-date'] = bag['additional-payout-date'] == undefined ? null : bag['additional-payout-date']; subbag['interest-rate'] = interest_rate; subbag['promotable-installments-count'] = _promotable_installments_count; if (bag[-42]) { subbag[-42] = bag[-42]; } if (bag['calculation-date']) { subbag['calculation-date'] = bag['calculation-date']; } 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 (239 in values.discounts_per) { for (var di = 0; di < values.discounts_per[239].length; di++) { ext_percent += values.discounts_per[239][di].amount; } } if (239 in values.discounts_amn) { for (var di = 0; di < values.discounts_amn[239].length; di++) { ext_percent += values.discounts_amn[239][di].amount; } } var discount = Math.min(inst[239] * ext_percent / 100 + ext_amount, inst[239]); inst.original[239] = inst[239]; inst.discounts[239] = discount; inst.discounted[239] = inst.original[239] - discount; var installment = { date: inst.date, payment: inst.payment, original: { principal: inst.original[238], interest: inst.original[240], commission: inst.original[237], }, principal: inst.discounted[238], interest: inst.discounted[240], commission: inst.discounted[237] }; var installment_without_tax = { date: inst.date, payment: inst.payment, original: { principal: inst.original[238] / (1 + (this.pricelists[238] && this.pricelists[238].tax_percentage ? parseFloat(this.pricelists[238].tax_percentage) : 0)), interest: inst.original[240] / (1 + (this.pricelists[-240] && this.pricelists[-240].tax_percentage ? parseFloat(this.pricelists[-240].tax_percentage) : 0)), commission: inst.original[237] / (1 + (this.pricelists[237] && this.pricelists[237].tax_percentage ? parseFloat(this.pricelists[237].tax_percentage) : 0)) }, principal: inst.discounted[238] / (1 + (this.pricelists[238] && this.pricelists[238].tax_percentage ? parseFloat(this.pricelists[238].tax_percentage) : 0)), interest: inst.discounted[240] / (1 + (this.pricelists[-240] && this.pricelists[-240].tax_percentage ? parseFloat(this.pricelists[-240].tax_percentage) : 0)), commission: inst.discounted[237] / (1 + (this.pricelists[237] && this.pricelists[237].tax_percentage ? parseFloat(this.pricelists[237].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[238] + values.discounted[237] + values.discounted[240]; 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 == 240) { name = 'interest-fee'; } else if (id == 237) { 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[238], 'loan-total': loan_total, 'loan-free-extension': values.total_extension_term, 'annual-loan-fee-rate': (values.feeRates[237]/ 100).toFixed(10), 'loan-promotable-installments-count': _promotable_installments_count, details: details2, installments: _all_installments, installments_without_tax: _all_installments_without_tax, 'installment-interest-rate': interest_rate, 'loan-duedate': _all_installments[_all_installments.length - 1].date, }; var total_fee_cost_values = 0; if (values.prices['237']) { ret['loan-fees'] = values.discounted[237]; ret['loan-original-fees'] = values.prices[237]; total_fee_cost_values += values.discounted[237]; } if (values.prices['240']) { ret['loan-interest-fees'] = values.discounted['240']; ret['loan-original-interest-fees'] = values.prices['240']; ret['loan-interest-fee-type-id'] = 240; total_fee_cost_values += values.discounted[240]; } 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; } if (apr == true) { var daysInYear = 365; const installmentsForApr = JSON.parse(JSON.stringify(((bag[-15] || false) == false) ? _all_installments : _all_installments_without_tax)); if (excludeFeesFromApr.length > 0) { for (var ifa = 0; ifa < installmentsForApr.length; ifa++) { for (var ftei = 0; ftei < excludeFeesFromApr.length; ftei++) { if (installmentsForApr[ifa][excludeFeesFromApr[ftei]] != undefined) { installmentsForApr[ifa].payment = installmentsForApr[ifa].payment - installmentsForApr[ifa][excludeFeesFromApr[ftei]]; } } } } var forcePayoutDate = bag["calculation-date"] ? bag["calculation-date"] : bag[-16] ? bag[-16] : null; ret['loan-apr'] = this.XIRR(parseFloat(bag[-3]), installmentsForApr, null, forcePayoutDate, daysInYear); if (interest_rate) { ret['loan-rpy'] = this.rpyForInstallment(interest_rate); ret['loan-rpm'] = this.rpmForInstallment(interest_rate); ret['loan-rpd'] = this.rpdForInstallment(interest_rate, daysInYear); } } return ret; }, fields: { 1217: 'Id', 6192: 'Latest Full Instantor Report Date', }, 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']() >= 20) && (bag['daysFromNow']() <= 50))) { 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: ["2025-04-20","2026-04-05","2027-03-28","2025-04-21","2026-04-06","2027-03-29","2024-05-19","2025-06-08","2026-05-24","2027-05-16","2024-05-30","2025-06-19","2026-06-04","2027-05-27"], 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; }, }, }, }; })(); ;