fix: added utils to format number & currency

This commit is contained in:
Shariq Ansari 2024-12-25 16:52:20 +05:30
parent fe78ff844c
commit fb2303edf1
2 changed files with 289 additions and 11 deletions

View File

@ -126,17 +126,6 @@ export function secondsToDuration(seconds) {
return `${hours}h ${minutes}m ${_seconds}s`
}
export function formatNumberIntoCurrency(value, currency = 'INR') {
if (value) {
return value.toLocaleString('en-IN', {
maximumFractionDigits: 0,
style: 'currency',
currency: currency ? currency : 'INR',
})
}
return ''
}
export function startCase(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}

View File

@ -0,0 +1,289 @@
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
}
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, df, currency = 'USD') {
if (!value || !df) return ''
let precision
if (typeof df.precision == 'number') {
precision = df.precision
} else {
precision = cint(df.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
}
}
}
value = value == null || value === '' ? '' : value
let format = getNumberFormat()
let symbol = getCurrencySymbol(currency)
if (symbol) {
return __(symbol) + ' ' + formatNumber(value, format, precision)
}
return formatNumber(value, format, precision)
}
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 getNumberFormat() {
return window.sysdefaults.number_format || '#,###.##'
}
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}`)
}
}