diff --git a/common/index.js b/common/index.js index 9a7afec7..50e71fba 100644 --- a/common/index.js +++ b/common/index.js @@ -1,17 +1,15 @@ const utils = require('../utils'); -const numberFormat = require('../utils/numberFormat'); const format = require('../utils/format'); const errors = require('./errors'); const BaseDocument = require('frappejs/model/document'); const BaseMeta = require('frappejs/model/meta'); module.exports = { - initLibs(frappe) { - Object.assign(frappe, utils); - Object.assign(frappe, numberFormat); - Object.assign(frappe, format); - frappe.errors = errors; - frappe.BaseDocument = BaseDocument; - frappe.BaseMeta = BaseMeta; - } -} \ No newline at end of file + initLibs(frappe) { + Object.assign(frappe, utils); + Object.assign(frappe, format); + frappe.errors = errors; + frappe.BaseDocument = BaseDocument; + frappe.BaseMeta = BaseMeta; + }, +}; diff --git a/model/document.js b/model/document.js index c816a991..fbaa8eeb 100644 --- a/model/document.js +++ b/model/document.js @@ -2,7 +2,6 @@ const frappe = require('frappejs'); const Observable = require('frappejs/utils/observable'); const naming = require('./naming'); const { isPesa } = require('../utils/index'); -const { round } = require('frappejs/utils/numberFormat'); const { DEFAULT_INTERNAL_PRECISION } = require('../utils/consts'); module.exports = class BaseDocument extends Observable { @@ -696,7 +695,7 @@ module.exports = class BaseDocument extends Observable { } const precision = frappe.SystemSettings.internalPrecision ?? DEFAULT_INTERNAL_PRECISION; - return round(value, precision); + return frappe.pesa(value).clip(precision).float; } isNew() { diff --git a/tests/test_utils.js b/tests/test_utils.js deleted file mode 100644 index 0dc51f9a..00000000 --- a/tests/test_utils.js +++ /dev/null @@ -1,35 +0,0 @@ -const numberFormat = require('frappejs/utils/numberFormat'); -const assert = require('assert'); - -describe('Number Formatting', () => { - it('should format numbers', () => { - assert.equal(numberFormat.formatNumber(100), '100.00'); - assert.equal(numberFormat.formatNumber(1000), '1,000.00'); - assert.equal(numberFormat.formatNumber(10000), '10,000.00'); - assert.equal(numberFormat.formatNumber(100000), '100,000.00'); - assert.equal(numberFormat.formatNumber(1000000), '1,000,000.00'); - assert.equal(numberFormat.formatNumber(100.1234), '100.12'); - assert.equal(numberFormat.formatNumber(1000.1234), '1,000.12'); - }); - - it('should parse numbers', () => { - assert.equal(numberFormat.parseNumber('100.00'), 100); - assert.equal(numberFormat.parseNumber('1,000.00'), 1000); - assert.equal(numberFormat.parseNumber('10,000.00'), 10000); - assert.equal(numberFormat.parseNumber('100,000.00'), 100000); - assert.equal(numberFormat.parseNumber('1,000,000.00'), 1000000); - assert.equal(numberFormat.parseNumber('100.1234'), 100.1234); - assert.equal(numberFormat.parseNumber('1,000.1234'), 1000.1234); - }); - - it('should format lakhs and crores', () => { - assert.equal(numberFormat.formatNumber(100, '#,##,###.##'), '100.00'); - assert.equal(numberFormat.formatNumber(1000, '#,##,###.##'), '1,000.00'); - assert.equal(numberFormat.formatNumber(10000, '#,##,###.##'), '10,000.00'); - assert.equal(numberFormat.formatNumber(100000, '#,##,###.##'), '1,00,000.00'); - assert.equal(numberFormat.formatNumber(1000000, '#,##,###.##'), '10,00,000.00'); - assert.equal(numberFormat.formatNumber(10000000, '#,##,###.##'), '1,00,00,000.00'); - assert.equal(numberFormat.formatNumber(100.1234, '#,##,###.##'), '100.12'); - assert.equal(numberFormat.formatNumber(1000.1234, '#,##,###.##'), '1,000.12'); - }); -}); \ No newline at end of file diff --git a/utils/format.js b/utils/format.js index 932126b7..e00a85ac 100644 --- a/utils/format.js +++ b/utils/format.js @@ -1,6 +1,6 @@ -const numberFormat = require('./numberFormat'); const luxon = require('luxon'); const frappe = require('frappejs'); +const { DEFAULT_DISPLAY_PRECISION, DEFAULT_LOCALE } = require('./consts'); module.exports = { format(value, df, doc) { @@ -13,7 +13,8 @@ module.exports = { } if (df.fieldtype === 'Currency') { - value = formatCurrency(value, df, doc); + const currency = getCurrency(df, doc); + value = formatCurrency(value, currency); } else if (df.fieldtype === 'Date') { let dateFormat; if (!frappe.SystemSettings) { @@ -47,8 +48,62 @@ module.exports = { } return value; }, + formatCurrency, + formatNumber, }; +function formatCurrency(value, currency) { + let valueString; + try { + valueString = formatNumber(value); + } catch (err) { + err.message += ` value: '${value}', type: ${typeof value}`; + throw err; + } + + const currencySymbol = frappe.currencySymbols[currency]; + if (currencySymbol) { + return currencySymbol + ' ' + valueString; + } + + return valueString; +} + +function formatNumber(value) { + const currencyFormatter = getNumberFormatter(); + if (typeof value === 'number') { + return currencyFormatter.format(value); + } + + if (value.round) { + return currencyFormatter.format(value.round()); + } + + const formattedCurrency = currencyFormatter.format(value); + if (formattedCurrency === 'NaN') { + throw Error( + `invalid value passed to formatCurrency: '${value}' of type ${typeof value}` + ); + } + + return formattedCurrency; +} + +function getNumberFormatter() { + if (frappe.currencyFormatter) { + return frappe.currencyFormatter; + } + + const locale = frappe.SystemSettings.locale ?? DEFAULT_LOCALE; + const display = + frappe.SystemSettings.displayPrecision ?? DEFAULT_DISPLAY_PRECISION; + + return (frappe.currencyFormatter = Intl.NumberFormat(locale, { + style: 'decimal', + minimumFractionDigits: display, + })); +} + function getCurrency(df, doc) { if (!(doc && df.getCurrency)) { return df.currency || frappe.AccountingSettings.currency || ''; @@ -60,21 +115,3 @@ function getCurrency(df, doc) { return df.getCurrency(doc); } - -function formatCurrency(value, df, doc) { - const currency = getCurrency(df, doc); - let valueString; - try { - valueString = numberFormat.formatCurrency(value); - } catch (err) { - err.message += ` value: '${value}', type: ${typeof value}`; - console.error(df); - throw err; - } - const currencySymbol = frappe.currencySymbols[currency]; - - if (currencySymbol) { - return currencySymbol + ' ' + valueString; - } - return valueString; -} diff --git a/utils/numberFormat.js b/utils/numberFormat.js deleted file mode 100644 index 7fc8dad5..00000000 --- a/utils/numberFormat.js +++ /dev/null @@ -1,146 +0,0 @@ -const frappe = require('frappejs'); -const { DEFAULT_DISPLAY_PRECISION, DEFAULT_LOCALE } = require('./consts'); -const numberFormats = { - '#,###.##': { fractionSep: '.', groupSep: ',', precision: 2 }, - '#.###,##': { fractionSep: ',', groupSep: '.', precision: 2 }, - '# ###.##': { fractionSep: '.', groupSep: ' ', precision: 2 }, - '# ###,##': { fractionSep: ',', groupSep: ' ', precision: 2 }, - "#'###.##": { fractionSep: '.', groupSep: "'", precision: 2 }, - '#, ###.##': { fractionSep: '.', groupSep: ', ', precision: 2 }, - '#,##,###.##': { fractionSep: '.', groupSep: ',', precision: 2 }, - '#,###.###': { fractionSep: '.', groupSep: ',', precision: 3 }, - '#.###': { fractionSep: '', groupSep: '.', precision: 0 }, - '#,###': { fractionSep: '', groupSep: ',', precision: 0 }, -}; - -module.exports = { - // parse a formatted number string - // from "4,555,000.34" -> 4555000.34 - parseNumber(number, format = '#,###.##') { - if (!number) { - return 0; - } - if (typeof number === 'number') { - return number; - } - const info = this.getFormatInfo(format); - return parseFloat(this.removeSeparator(number, info.groupSep)); - }, - - formatNumber(number, format = '#,###.##', precision = null) { - if (!number) { - number = 0; - } - let info = this.getFormatInfo(format); - if (precision) { - info.precision = precision; - } - let is_negative = false; - - number = this.parseNumber(number); - if (number < 0) { - is_negative = true; - } - number = Math.abs(number); - number = number.toFixed(info.precision); - - var parts = number.split('.'); - - // get group position and parts - var group_position = info.groupSep ? 3 : 0; - - if (group_position) { - var integer = parts[0]; - var str = ''; - - for (var i = integer.length; i >= 0; i--) { - var l = this.removeSeparator(str, info.groupSep).length; - if (format == '#,##,###.##' && str.indexOf(',') != -1) { - // INR - group_position = 2; - l += 1; - } - - str += integer.charAt(i); - - if (l && !((l + 1) % group_position) && i != 0) { - str += info.groupSep; - } - } - parts[0] = str.split('').reverse().join(''); - } - if (parts[0] + '' == '') { - parts[0] = '0'; - } - - // join decimal - parts[1] = parts[1] && info.fractionSep ? info.fractionSep + parts[1] : ''; - - // join - return (is_negative ? '-' : '') + parts[0] + parts[1]; - }, - - getFormatInfo(format) { - let format_info = numberFormats[format]; - - if (!format_info) { - throw new Error(`Unknown number format "${format}"`); - } - - return format_info; - }, - - round(num, precision) { - var is_negative = num < 0 ? true : false; - var d = parseInt(precision || 0); - 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 is_negative ? -r : r; - }, - - removeSeparator(text, sep) { - return text.replace(new RegExp(sep === '.' ? '\\.' : sep, 'g'), ''); - }, - - getDisplayPrecision() { - return frappe.SystemSettings.displayPrecision ?? DEFAULT_DISPLAY_PRECISION; - }, - - getCurrencyFormatter() { - if (frappe.currencyFormatter) { - return frappe.currencyFormatter; - } - - const locale = frappe.SystemSettings.locale ?? DEFAULT_LOCALE; - const display = this.getDisplayPrecision(); - - return (frappe.currencyFormatter = Intl.NumberFormat(locale, { - style: 'decimal', - minimumFractionDigits: display, - })); - }, - - formatCurrency(value) { - const currencyFormatter = this.getCurrencyFormatter(); - if (typeof value === 'number') { - return currencyFormatter.format(value); - } - - if (value.round) { - return currencyFormatter.format(value.round()); - } - - const formattedCurrency = currencyFormatter.format(value); - if (formattedCurrency === 'NaN') { - throw Error( - `invalid value passed to formatCurrency: '${value}' of type ${typeof value}` - ); - } - - return formattedCurrency; - }, -};