const NUMBER_FORMAT_INFO = { '#,###.##': { decimalStr: '.', groupSep: ',' }, '#.###,##': { decimalStr: ',', groupSep: '.' }, '# ###.##': { decimalStr: '.', groupSep: ' ' }, '# ###,##': { decimalStr: ',', groupSep: ' ' }, "#'###.##": { decimalStr: '.', groupSep: "'" }, '#, ###.##': { decimalStr: '.', groupSep: ', ' }, '#,##,###.##': { decimalStr: '.', groupSep: ',' }, '#,###.###': { decimalStr: '.', groupSep: ',' }, '#.###': { decimalStr: '', groupSep: '.' }, '#,###': { decimalStr: '', groupSep: ',' }, } export function replaceAll(s, t1, t2) { return s.split(t1).join(t2) } export function strip(s, chars) { if (s) { s = lstrip(s, chars) s = rstrip(s, chars) return s } } export function lstrip(s, chars) { if (!chars) chars = ['\n', '\t', ' '] // strip left let first_char = s.substr(0, 1) while (chars.includes(first_char)) { s = s.substr(1) first_char = s.substr(0, 1) } return s } export function rstrip(s, chars) { if (!chars) chars = ['\n', '\t', ' '] let last_char = s.substr(s.length - 1) while (chars.includes(last_char)) { s = s.substr(0, s.length - 1) last_char = s.substr(s.length - 1) } return s } export function cstr(s) { if (s == null) return '' return s + '' } export function cint(v, def) { if (v === true) return 1 if (v === false) return 0 v = v + '' if (v !== '0') v = lstrip(v, ['0']) v = parseInt(v) // eslint-ignore-line if (isNaN(v)) v = def === undefined ? 0 : def return v } export function flt(v, decimals, numberFormat, roundingMethod) { if (v == null || v == '') return 0 if (typeof v !== 'number') { v = v + '' // strip currency symbol if exists if (v.indexOf(' ') != -1) { // using slice(1).join(" ") because space could also be a group separator var parts = v.split(' ') v = isNaN(parseFloat(parts[0])) ? parts.slice(parts.length - 1).join(' ') : v } v = stripNumberGroups(v, numberFormat) v = parseFloat(v) if (isNaN(v)) v = 0 } if (decimals != null) return roundNumber(v, decimals, roundingMethod) return v } function stripNumberGroups(v, numberFormat) { if (!numberFormat) numberFormat = getNumberFormat() var info = getNumberFormatInfo(numberFormat) // strip groups (,) var groupRegex = new RegExp( info.groupSep === '.' ? '\\.' : info.groupSep, 'g', ) v = v.replace(groupRegex, '') // replace decimal separator with (.) if (info.decimalStr !== '.' && info.decimalStr !== '') { var decimal_regex = new RegExp(info.decimalStr, 'g') v = v.replace(decimal_regex, '.') } return v } export function formatNumber(v, format, decimals) { if (!format) { format = getNumberFormat() if (decimals == null) decimals = cint(window.sysdefaults.float_precision || 3) } let info = getNumberFormatInfo(format) // Fix the decimal first, toFixed will auto fill trailing zero. if (decimals == null) decimals = info.precision v = flt(v, decimals, format) let isNegative = false if (v < 0) isNegative = true v = Math.abs(v) v = v.toFixed(decimals) let part = v.split('.') // get group position and parts let groupPosition = info.groupSep ? 3 : 0 if (groupPosition) { let integer = part[0] let str = '' for (let i = integer.length; i >= 0; i--) { let l = replaceAll(str, info.groupSep, '').length if (format == '#,##,###.##' && str.indexOf(',') != -1) { // INR groupPosition = 2 l += 1 } str += integer.charAt(i) if (l && !((l + 1) % groupPosition) && i != 0) { str += info.groupSep } } part[0] = str.split('').reverse().join('') } if (part[0] + '' == '') { part[0] = '0' } // join decimal part[1] = part[1] && info.decimalStr ? info.decimalStr + part[1] : '' // join return (isNegative ? '-' : '') + part[0] + part[1] } export function formatCurrency(value, format, currency = 'USD', precision = 2) { value = value == null || value === '' ? 0 : value if (typeof precision != 'number') { precision = cint(precision || window.sysdefaults.currency_precision || 2) } // If you change anything below, it's going to hurt a company in UAE, a bit. // if (precision > 2) { // let parts = cstr(value).split('.') // should be minimum 2, comes from the DB // let decimals = parts.length > 1 ? parts[1] : '' // parts.length == 2 ??? // if (decimals.length < 3 || decimals.length < precision) { // const fraction = 100 // if (decimals.length < cstr(fraction).length) { // precision = cstr(fraction).length - 1 // } // } // } format = getNumberFormat(format) if (currency) { let symbol = getCurrencySymbol(currency) if (symbol) { return __(symbol) + ' ' + formatNumber(value, format, precision) } } return formatNumber(value, format, precision) } function getNumberFormat(format = null) { return format || window.sysdefaults.number_format || '#,###.##' } function getCurrencySymbol(currencyCode) { try { const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: currencyCode, minimumFractionDigits: 0, maximumFractionDigits: 0, }) // Extract the currency symbol from the formatted string const parts = formatter.formatToParts(1) const symbol = parts.find((part) => part.type === 'currency') return symbol ? symbol.value : null } catch (error) { console.error(`Invalid currency code: ${currencyCode}`) return null } } function getNumberFormatInfo(format) { let info = NUMBER_FORMAT_INFO[format] if (!info) { info = { decimalStr: '.', groupSep: ',' } } // get the precision from the number format info.precision = format.split(info.decimalStr).slice(1)[0].length return info } function roundNumber(num, precision, roundingMethod) { roundingMethod = roundingMethod || window.sysdefaults.rounding_method || "Banker's Rounding (legacy)" let isNegative = num < 0 ? true : false if (roundingMethod == "Banker's Rounding (legacy)") { var d = cint(precision) var m = Math.pow(10, d) var n = +(d ? Math.abs(num) * m : Math.abs(num)).toFixed(8) // Avoid rounding errors var i = Math.floor(n), f = n - i var r = !precision && f == 0.5 ? (i % 2 == 0 ? i : i + 1) : Math.round(n) r = d ? r / m : r return isNegative ? -r : r } else if (roundingMethod == "Banker's Rounding") { if (num == 0) return 0.0 precision = cint(precision) let multiplier = Math.pow(10, precision) num = Math.abs(num) * multiplier let floorNum = Math.floor(num) let decimalPart = num - floorNum // For explanation of this method read python flt implementation notes. let epsilon = 2.0 ** (Math.log2(Math.abs(num)) - 52.0) if (Math.abs(decimalPart - 0.5) < epsilon) { num = floorNum % 2 == 0 ? floorNum : floorNum + 1 } else { num = Math.round(num) } num = num / multiplier return isNegative ? -num : num } else if (roundingMethod == 'Commercial Rounding') { if (num == 0) return 0.0 let digits = cint(precision) let multiplier = Math.pow(10, digits) num = num * multiplier // For explanation of this method read python flt implementation notes. let epsilon = 2.0 ** (Math.log2(Math.abs(num)) - 52.0) if (isNegative) { epsilon = -1 * epsilon } num = Math.round(num + epsilon) return num / multiplier } else { throw new Error(`Unknown rounding method ${roundingMethod}`) } }