From 2b06594532473c4a9c67888183de10d50d5ee30d Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 25 Dec 2024 16:49:27 +0530 Subject: [PATCH 01/10] fix: side panel breaks if added a field --- frontend/src/components/Modals/SidePanelModal.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Modals/SidePanelModal.vue b/frontend/src/components/Modals/SidePanelModal.vue index 6bd24b18..53a2a938 100644 --- a/frontend/src/components/Modals/SidePanelModal.vue +++ b/frontend/src/components/Modals/SidePanelModal.vue @@ -151,7 +151,7 @@ function saveChanges() { { doctype: _doctype.value, type: 'Side Panel', - layout: JSON.stringify(_tabs), + layout: JSON.stringify(_tabs[0].sections), }, ).then(() => { loading.value = false From fe78ff844c88f4c899be85fbb6f865167e817fbd Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 25 Dec 2024 16:50:47 +0530 Subject: [PATCH 02/10] fix: get meta for doctype --- frontend/src/stores/meta.js | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 frontend/src/stores/meta.js diff --git a/frontend/src/stores/meta.js b/frontend/src/stores/meta.js new file mode 100644 index 00000000..221b6357 --- /dev/null +++ b/frontend/src/stores/meta.js @@ -0,0 +1,49 @@ +import { createResource } from 'frappe-ui' +import { formatCurrency } from '@/utils/numberFormat.js' +import { reactive } from 'vue' + +const doctypeMeta = reactive({}) + +export function getMeta(doctype) { + const meta = createResource({ + url: 'frappe.desk.form.load.getdoctype', + params: { + doctype: doctype, + with_parent: 1, + cached_timestamp: null, + }, + cache: ['Meta', doctype], + onSuccess: (res) => { + let dtMetas = res.docs + for (let dtMeta of dtMetas) { + doctypeMeta[dtMeta.name] = dtMeta + } + }, + }) + + if (!doctypeMeta[doctype]) { + meta.fetch() + } + + function getFormattedCurrency(fieldname, doc) { + let currency = window.sysdefaults.currency || 'USD' + + let df = doctypeMeta[doctype]?.fields.find((f) => f.fieldname == fieldname) + + if (df && df.options) { + if (df.options.indexOf(':') != -1) { + currency = currency + } else if (doc && doc[df.options]) { + currency = doc[df.options] + } + } + + return formatCurrency(doc[fieldname], df, currency) + } + + return { + meta, + doctypeMeta, + getFormattedCurrency, + } +} From fb2303edf1c7133c2bc84326ad663ce6e35990af Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 25 Dec 2024 16:52:20 +0530 Subject: [PATCH 03/10] fix: added utils to format number & currency --- frontend/src/utils/index.js | 11 -- frontend/src/utils/numberFormat.js | 289 +++++++++++++++++++++++++++++ 2 files changed, 289 insertions(+), 11 deletions(-) create mode 100644 frontend/src/utils/numberFormat.js diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index 744b5579..557e8588 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -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) } diff --git a/frontend/src/utils/numberFormat.js b/frontend/src/utils/numberFormat.js new file mode 100644 index 00000000..9e2962bc --- /dev/null +++ b/frontend/src/utils/numberFormat.js @@ -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}`) + } +} From 9b8662de14e32d431be95f1399e954fed2c6fec8 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 25 Dec 2024 16:54:10 +0530 Subject: [PATCH 04/10] feat: show formatted currency fields in all list views --- frontend/src/pages/Contact.vue | 15 +++++---------- frontend/src/pages/Contacts.vue | 6 ++++++ frontend/src/pages/Deals.vue | 19 +++++++------------ frontend/src/pages/EmailTemplates.vue | 7 +++++++ frontend/src/pages/Leads.vue | 6 ++++++ frontend/src/pages/MobileContact.vue | 15 +++++---------- frontend/src/pages/MobileOrganization.vue | 17 ++++++----------- frontend/src/pages/Organization.vue | 17 ++++++----------- frontend/src/pages/Organizations.vue | 14 ++++++++------ frontend/src/pages/Tasks.vue | 6 ++++++ frontend/src/utils/callLog.js | 6 ++++++ 11 files changed, 68 insertions(+), 60 deletions(-) diff --git a/frontend/src/pages/Contact.vue b/frontend/src/pages/Contact.vue index e3e64c51..5eea6170 100644 --- a/frontend/src/pages/Contact.vue +++ b/frontend/src/pages/Contact.vue @@ -211,13 +211,9 @@ import DealsIcon from '@/components/Icons/DealsIcon.vue' import DealsListView from '@/components/ListViews/DealsListView.vue' import SidePanelModal from '@/components/Modals/SidePanelModal.vue' import AddressModal from '@/components/Modals/AddressModal.vue' -import { - formatDate, - timeAgo, - formatNumberIntoCurrency, - createToast, -} from '@/utils' +import { formatDate, timeAgo, createToast } from '@/utils' import { getView } from '@/utils/view' +import { getMeta } from '@/stores/meta' import { globalStore } from '@/stores/global.js' import { usersStore } from '@/stores/users.js' import { organizationsStore } from '@/stores/organizations.js' @@ -601,6 +597,8 @@ async function updateField(fieldname, value) { contact.reload() } +const { getFormattedCurrency } = getMeta('CRM Deal') + const columns = computed(() => dealColumns) function getDealRowObject(deal) { @@ -610,10 +608,7 @@ function getDealRowObject(deal) { label: deal.organization, logo: getOrganization(deal.organization)?.organization_logo, }, - annual_revenue: formatNumberIntoCurrency( - deal.annual_revenue, - deal.currency, - ), + annual_revenue: getFormattedCurrency('annual_revenue', deal), status: { label: deal.status, color: getDealStatus(deal.status)?.iconColorClass, diff --git a/frontend/src/pages/Contacts.vue b/frontend/src/pages/Contacts.vue index dca88f30..172d2565 100644 --- a/frontend/src/pages/Contacts.vue +++ b/frontend/src/pages/Contacts.vue @@ -70,10 +70,12 @@ import LayoutHeader from '@/components/LayoutHeader.vue' import ContactModal from '@/components/Modals/ContactModal.vue' import ContactsListView from '@/components/ListViews/ContactsListView.vue' import ViewControls from '@/components/ViewControls.vue' +import { getMeta } from '@/stores/meta' import { organizationsStore } from '@/stores/organizations.js' import { formatDate, timeAgo } from '@/utils' import { ref, computed } from 'vue' +const { getFormattedCurrency } = getMeta('Contact') const { getOrganization } = organizationsStore() const showContactModal = ref(false) @@ -110,6 +112,10 @@ const rows = computed(() => { _rows[row] = formatDate(contact[row], '', true, fieldType == 'Datetime') } + if (fieldType && fieldType == 'Currency') { + _rows[row] = getFormattedCurrency(row, contact) + } + if (row == 'full_name') { _rows[row] = { label: contact.full_name, diff --git a/frontend/src/pages/Deals.vue b/frontend/src/pages/Deals.vue index 7a3162b8..3f6f4e68 100644 --- a/frontend/src/pages/Deals.vue +++ b/frontend/src/pages/Deals.vue @@ -281,22 +281,18 @@ import NoteModal from '@/components/Modals/NoteModal.vue' import TaskModal from '@/components/Modals/TaskModal.vue' import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue' import ViewControls from '@/components/ViewControls.vue' +import { getMeta } from '@/stores/meta' import { globalStore } from '@/stores/global' import { usersStore } from '@/stores/users' import { organizationsStore } from '@/stores/organizations' import { statusesStore } from '@/stores/statuses' import { callEnabled } from '@/composables/settings' -import { - formatDate, - timeAgo, - website, - formatNumberIntoCurrency, - formatTime, -} from '@/utils' +import { formatDate, timeAgo, website, formatTime } from '@/utils' import { Tooltip, Avatar, Dropdown } from 'frappe-ui' import { useRoute } from 'vue-router' import { ref, reactive, computed, h } from 'vue' +const { getFormattedCurrency } = getMeta('CRM Deal') const { makeCall } = globalStore() const { getUser } = usersStore() const { getOrganization } = organizationsStore() @@ -402,6 +398,10 @@ function parseRows(rows, columns = []) { _rows[row] = formatDate(deal[row], '', true, fieldType == 'Datetime') } + if (fieldType && fieldType == 'Currency') { + _rows[row] = getFormattedCurrency(row, deal) + } + if (row == 'organization') { _rows[row] = { label: deal.organization, @@ -409,11 +409,6 @@ function parseRows(rows, columns = []) { } } else if (row === 'website') { _rows[row] = website(deal.website) - } else if (row == 'annual_revenue') { - _rows[row] = formatNumberIntoCurrency( - deal.annual_revenue, - deal.currency, - ) } else if (row == 'status') { _rows[row] = { label: deal.status, diff --git a/frontend/src/pages/EmailTemplates.vue b/frontend/src/pages/EmailTemplates.vue index 73e3ec5f..cdbc14ee 100644 --- a/frontend/src/pages/EmailTemplates.vue +++ b/frontend/src/pages/EmailTemplates.vue @@ -75,9 +75,12 @@ import LayoutHeader from '@/components/LayoutHeader.vue' import ViewControls from '@/components/ViewControls.vue' import EmailTemplatesListView from '@/components/ListViews/EmailTemplatesListView.vue' import EmailTemplateModal from '@/components/Modals/EmailTemplateModal.vue' +import { getMeta } from '@/stores/meta' import { formatDate, timeAgo } from '@/utils' import { computed, ref } from 'vue' +const { getFormattedCurrency } = getMeta('Email Template') + const emailTemplatesListView = ref(null) // emailTemplates data is loaded in the ViewControls component @@ -115,6 +118,10 @@ const rows = computed(() => { ) } + if (fieldType && fieldType == 'Currency') { + _rows[row] = getFormattedCurrency(row, emailTemplate) + } + if (['modified', 'creation'].includes(row)) { _rows[row] = { label: formatDate(emailTemplate[row]), diff --git a/frontend/src/pages/Leads.vue b/frontend/src/pages/Leads.vue index 8be6fa7b..d08d5758 100644 --- a/frontend/src/pages/Leads.vue +++ b/frontend/src/pages/Leads.vue @@ -303,6 +303,7 @@ import NoteModal from '@/components/Modals/NoteModal.vue' import TaskModal from '@/components/Modals/TaskModal.vue' import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue' import ViewControls from '@/components/ViewControls.vue' +import { getMeta } from '@/stores/meta' import { globalStore } from '@/stores/global' import { usersStore } from '@/stores/users' import { statusesStore } from '@/stores/statuses' @@ -312,6 +313,7 @@ import { Avatar, Tooltip, Dropdown } from 'frappe-ui' import { useRoute } from 'vue-router' import { ref, computed, reactive, h } from 'vue' +const { getFormattedCurrency } = getMeta('CRM Lead') const { makeCall } = globalStore() const { getUser } = usersStore() const { getLeadStatus } = statusesStore() @@ -416,6 +418,10 @@ function parseRows(rows, columns = []) { _rows[row] = formatDate(lead[row], '', true, fieldType == 'Datetime') } + if (fieldType && fieldType == 'Currency') { + _rows[row] = getFormattedCurrency(row, lead) + } + if (row == 'lead_name') { _rows[row] = { label: lead.lead_name, diff --git a/frontend/src/pages/MobileContact.vue b/frontend/src/pages/MobileContact.vue index c39d6bc3..9508d3ee 100644 --- a/frontend/src/pages/MobileContact.vue +++ b/frontend/src/pages/MobileContact.vue @@ -186,13 +186,9 @@ import CameraIcon from '@/components/Icons/CameraIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue' import DealsListView from '@/components/ListViews/DealsListView.vue' import AddressModal from '@/components/Modals/AddressModal.vue' -import { - formatDate, - timeAgo, - formatNumberIntoCurrency, - createToast, -} from '@/utils' +import { formatDate, timeAgo, createToast } from '@/utils' import { getView } from '@/utils/view' +import { getMeta } from '@/stores/meta' import { globalStore } from '@/stores/global.js' import { usersStore } from '@/stores/users.js' import { organizationsStore } from '@/stores/organizations.js' @@ -581,6 +577,8 @@ async function updateField(fieldname, value) { contact.reload() } +const { getFormattedCurrency } = getMeta('CRM Deal') + const columns = computed(() => dealColumns) function getDealRowObject(deal) { @@ -590,10 +588,7 @@ function getDealRowObject(deal) { label: deal.organization, logo: getOrganization(deal.organization)?.organization_logo, }, - annual_revenue: formatNumberIntoCurrency( - deal.annual_revenue, - deal.currency, - ), + annual_revenue: getFormattedCurrency('annual_revenue', deal), status: { label: deal.status, color: getDealStatus(deal.status)?.iconColorClass, diff --git a/frontend/src/pages/MobileOrganization.vue b/frontend/src/pages/MobileOrganization.vue index 58bd77d0..d73ce09f 100644 --- a/frontend/src/pages/MobileOrganization.vue +++ b/frontend/src/pages/MobileOrganization.vue @@ -176,16 +176,12 @@ import DetailsIcon from '@/components/Icons/DetailsIcon.vue' import CameraIcon from '@/components/Icons/CameraIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue' import ContactsIcon from '@/components/Icons/ContactsIcon.vue' +import { getMeta } from '@/stores/meta' import { globalStore } from '@/stores/global' import { usersStore } from '@/stores/users' import { statusesStore } from '@/stores/statuses' import { getView } from '@/utils/view' -import { - formatDate, - timeAgo, - formatNumberIntoCurrency, - createToast, -} from '@/utils' +import { formatDate, timeAgo, createToast } from '@/utils' import { Breadcrumbs, Avatar, @@ -441,6 +437,8 @@ const rows = computed(() => { }) }) +const { getFormattedCurrency } = getMeta('CRM Deal') + const columns = computed(() => { return tabIndex.value === 0 ? dealColumns : contactColumns }) @@ -450,12 +448,9 @@ function getDealRowObject(deal) { name: deal.name, organization: { label: deal.organization, - logo: props.organization?.organization_logo, + logo: organization.doc?.organization_logo, }, - annual_revenue: formatNumberIntoCurrency( - deal.annual_revenue, - deal.currency, - ), + annual_revenue: getFormattedCurrency('annual_revenue', deal), status: { label: deal.status, color: getDealStatus(deal.status)?.iconColorClass, diff --git a/frontend/src/pages/Organization.vue b/frontend/src/pages/Organization.vue index d84fd232..e4920855 100644 --- a/frontend/src/pages/Organization.vue +++ b/frontend/src/pages/Organization.vue @@ -211,16 +211,12 @@ import EditIcon from '@/components/Icons/EditIcon.vue' import CameraIcon from '@/components/Icons/CameraIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue' import ContactsIcon from '@/components/Icons/ContactsIcon.vue' +import { getMeta } from '@/stores/meta' import { globalStore } from '@/stores/global' import { usersStore } from '@/stores/users' import { statusesStore } from '@/stores/statuses' import { getView } from '@/utils/view' -import { - formatDate, - timeAgo, - formatNumberIntoCurrency, - createToast, -} from '@/utils' +import { formatDate, timeAgo, createToast } from '@/utils' import { Tooltip, Breadcrumbs, @@ -476,6 +472,8 @@ const rows = computed(() => { }) }) +const { getFormattedCurrency } = getMeta('CRM Deal') + const columns = computed(() => { return tabIndex.value === 0 ? dealColumns : contactColumns }) @@ -485,12 +483,9 @@ function getDealRowObject(deal) { name: deal.name, organization: { label: deal.organization, - logo: props.organization?.organization_logo, + logo: organization.doc?.organization_logo, }, - annual_revenue: formatNumberIntoCurrency( - deal.annual_revenue, - deal.currency, - ), + annual_revenue: getFormattedCurrency('annual_revenue', deal), status: { label: deal.status, color: getDealStatus(deal.status)?.iconColorClass, diff --git a/frontend/src/pages/Organizations.vue b/frontend/src/pages/Organizations.vue index 60a2204b..5db5a8c3 100644 --- a/frontend/src/pages/Organizations.vue +++ b/frontend/src/pages/Organizations.vue @@ -69,9 +69,12 @@ import LayoutHeader from '@/components/LayoutHeader.vue' import OrganizationModal from '@/components/Modals/OrganizationModal.vue' import OrganizationsListView from '@/components/ListViews/OrganizationsListView.vue' import ViewControls from '@/components/ViewControls.vue' -import { formatDate, timeAgo, website, formatNumberIntoCurrency } from '@/utils' +import { getMeta } from '@/stores/meta' +import { formatDate, timeAgo, website } from '@/utils' import { ref, computed } from 'vue' +const { getFormattedCurrency } = getMeta('CRM Organization') + const organizationsListView = ref(null) const showOrganizationModal = ref(false) @@ -110,6 +113,10 @@ const rows = computed(() => { ) } + if (fieldType && fieldType == 'Currency') { + _rows[row] = getFormattedCurrency(row, organization) + } + if (row === 'organization_name') { _rows[row] = { label: organization.organization_name, @@ -117,11 +124,6 @@ const rows = computed(() => { } } else if (row === 'website') { _rows[row] = website(organization.website) - } else if (row === 'annual_revenue') { - _rows[row] = formatNumberIntoCurrency( - organization.annual_revenue, - organization.currency, - ) } else if (['modified', 'creation'].includes(row)) { _rows[row] = { label: formatDate(organization[row]), diff --git a/frontend/src/pages/Tasks.vue b/frontend/src/pages/Tasks.vue index 0349c876..1d892c7a 100644 --- a/frontend/src/pages/Tasks.vue +++ b/frontend/src/pages/Tasks.vue @@ -204,12 +204,14 @@ import ViewControls from '@/components/ViewControls.vue' import TasksListView from '@/components/ListViews/TasksListView.vue' import KanbanView from '@/components/Kanban/KanbanView.vue' import TaskModal from '@/components/Modals/TaskModal.vue' +import { getMeta } from '@/stores/meta' import { usersStore } from '@/stores/users' import { formatDate, timeAgo } from '@/utils' import { Tooltip, Avatar, TextEditor, Dropdown, call } from 'frappe-ui' import { computed, ref } from 'vue' import { useRouter } from 'vue-router' +const { getFormattedCurrency } = getMeta('CRM Task') const { getUser } = usersStore() const router = useRouter() @@ -271,6 +273,10 @@ function parseRows(rows, columns = []) { _rows[row] = formatDate(task[row], '', true, fieldType == 'Datetime') } + if (fieldType && fieldType == 'Currency') { + _rows[row] = getFormattedCurrency(row, task) + } + if (['modified', 'creation'].includes(row)) { _rows[row] = { label: formatDate(task[row]), diff --git a/frontend/src/utils/callLog.js b/frontend/src/utils/callLog.js index ee006d1a..35388c20 100644 --- a/frontend/src/utils/callLog.js +++ b/frontend/src/utils/callLog.js @@ -1,7 +1,9 @@ import { secondsToDuration, formatDate, timeAgo } from '@/utils' +import { getMeta } from '@/stores/meta' import { usersStore } from '@/stores/users' import { contactsStore } from '@/stores/contacts' +const { getFormattedCurrency } = getMeta('CRM Call Log') const { getUser } = usersStore() const { getContact, getLeadContact } = contactsStore() @@ -58,6 +60,10 @@ export function getCallLogDetail(row, log, columns = []) { return formatDate(log[row], '', true, fieldType == 'Datetime') } + if (fieldType && fieldType == 'Currency') { + return getFormattedCurrency(row, log) + } + return log[row] } From 0d2ca4cb4a2688974a0f62204c9f42772b26009f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 25 Dec 2024 17:57:38 +0530 Subject: [PATCH 05/10] feat: show formatted currency fields in Modal & data fields --- .../src/components/Activities/DataFields.vue | 7 ++++++- frontend/src/components/FieldLayout.vue | 21 +++++++++++++++++-- .../src/components/Modals/AddressModal.vue | 4 ++-- .../src/components/Modals/ContactModal.vue | 6 +++++- frontend/src/components/Modals/DealModal.vue | 1 + .../components/Modals/OrganizationModal.vue | 6 +++++- .../src/components/Settings/SettingsPage.vue | 7 ++++++- frontend/src/components/SidePanelLayout.vue | 18 ++++++++++++++++ frontend/src/pages/Contact.vue | 1 + frontend/src/pages/Deal.vue | 1 + frontend/src/pages/MobileContact.vue | 1 + frontend/src/pages/MobileDeal.vue | 1 + frontend/src/pages/MobileOrganization.vue | 3 ++- frontend/src/pages/Organization.vue | 3 ++- frontend/src/utils/numberFormat.js | 2 +- 15 files changed, 71 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Activities/DataFields.vue b/frontend/src/components/Activities/DataFields.vue index 287d4a8a..cee88cf0 100644 --- a/frontend/src/components/Activities/DataFields.vue +++ b/frontend/src/components/Activities/DataFields.vue @@ -32,7 +32,12 @@ {{ __('Loading...') }}
- +
+ !props.tabs[0].no_tabs) const tabIndex = ref(0) diff --git a/frontend/src/components/Modals/AddressModal.vue b/frontend/src/components/Modals/AddressModal.vue index fd7cd25a..8b92f58e 100644 --- a/frontend/src/components/Modals/AddressModal.vue +++ b/frontend/src/components/Modals/AddressModal.vue @@ -23,7 +23,7 @@
- +
@@ -54,7 +54,7 @@ import FieldLayout from '@/components/FieldLayout.vue' import EditIcon from '@/components/Icons/EditIcon.vue' import { usersStore } from '@/stores/users' import { capture } from '@/telemetry' -import { call, FeatherIcon, createResource, ErrorMessage } from 'frappe-ui' +import { FeatherIcon, createResource, ErrorMessage } from 'frappe-ui' import { ref, nextTick, watch, computed } from 'vue' const props = defineProps({ diff --git a/frontend/src/components/Modals/ContactModal.vue b/frontend/src/components/Modals/ContactModal.vue index a3515e8b..7cfa190b 100644 --- a/frontend/src/components/Modals/ContactModal.vue +++ b/frontend/src/components/Modals/ContactModal.vue @@ -23,7 +23,11 @@
- +
diff --git a/frontend/src/components/Modals/DealModal.vue b/frontend/src/components/Modals/DealModal.vue index 2ccadec6..abb7f22f 100644 --- a/frontend/src/components/Modals/DealModal.vue +++ b/frontend/src/components/Modals/DealModal.vue @@ -38,6 +38,7 @@ v-if="filteredSections.length" :tabs="filteredSections" :data="deal" + doctype="CRM Deal" />
diff --git a/frontend/src/components/Modals/OrganizationModal.vue b/frontend/src/components/Modals/OrganizationModal.vue index 585b9b78..0ea5e159 100644 --- a/frontend/src/components/Modals/OrganizationModal.vue +++ b/frontend/src/components/Modals/OrganizationModal.vue @@ -23,7 +23,11 @@
- +
diff --git a/frontend/src/components/Settings/SettingsPage.vue b/frontend/src/components/Settings/SettingsPage.vue index e8d6c876..1d6bfdc3 100644 --- a/frontend/src/components/Settings/SettingsPage.vue +++ b/frontend/src/components/Settings/SettingsPage.vue @@ -12,7 +12,12 @@ />
- +
diff --git a/frontend/src/components/SidePanelLayout.vue b/frontend/src/components/SidePanelLayout.vue index e18d65fc..be646727 100644 --- a/frontend/src/components/SidePanelLayout.vue +++ b/frontend/src/components/SidePanelLayout.vue @@ -168,6 +168,17 @@ @change="(data) => emit('update', field.name, data)" />
+ diff --git a/frontend/src/pages/Deal.vue b/frontend/src/pages/Deal.vue index ba464d01..9c2b3db9 100644 --- a/frontend/src/pages/Deal.vue +++ b/frontend/src/pages/Deal.vue @@ -171,6 +171,7 @@ v-if="section.fields" :fields="section.fields" :isLastSection="i == fieldsLayout.data.length - 1" + doctype="CRM Deal" v-model="deal.data" @update="updateField" /> diff --git a/frontend/src/pages/MobileContact.vue b/frontend/src/pages/MobileContact.vue index 9508d3ee..41fb08d1 100644 --- a/frontend/src/pages/MobileContact.vue +++ b/frontend/src/pages/MobileContact.vue @@ -145,6 +145,7 @@ diff --git a/frontend/src/pages/MobileDeal.vue b/frontend/src/pages/MobileDeal.vue index f2c784c2..8936921f 100644 --- a/frontend/src/pages/MobileDeal.vue +++ b/frontend/src/pages/MobileDeal.vue @@ -102,6 +102,7 @@ v-if="section.fields" :fields="section.fields" :isLastSection="i == fieldsLayout.data.length - 1" + doctype="CRM Deal" v-model="deal.data" @update="updateField" /> diff --git a/frontend/src/pages/MobileOrganization.vue b/frontend/src/pages/MobileOrganization.vue index d73ce09f..abf52307 100644 --- a/frontend/src/pages/MobileOrganization.vue +++ b/frontend/src/pages/MobileOrganization.vue @@ -125,9 +125,10 @@ >
diff --git a/frontend/src/pages/Organization.vue b/frontend/src/pages/Organization.vue index e4920855..011e8da9 100644 --- a/frontend/src/pages/Organization.vue +++ b/frontend/src/pages/Organization.vue @@ -125,9 +125,10 @@ diff --git a/frontend/src/utils/numberFormat.js b/frontend/src/utils/numberFormat.js index 9e2962bc..061e7f70 100644 --- a/frontend/src/utils/numberFormat.js +++ b/frontend/src/utils/numberFormat.js @@ -59,7 +59,7 @@ export function cint(v, def) { return v } -function flt(v, decimals, numberFormat, roundingMethod) { +export function flt(v, decimals, numberFormat, roundingMethod) { if (v == null || v == '') return 0 if (typeof v !== 'number') { From a91d8e449f597cb3452bf94b3da8207905d60345 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 25 Dec 2024 18:20:00 +0530 Subject: [PATCH 06/10] feat: show formatted float fields in Modal & data fields --- frontend/src/components/FieldLayout.vue | 14 ++++++++++---- frontend/src/components/SidePanelLayout.vue | 15 +++++++++++---- frontend/src/stores/meta.js | 9 ++++++++- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/FieldLayout.vue b/frontend/src/components/FieldLayout.vue index a9fbc721..6fbd07af 100644 --- a/frontend/src/components/FieldLayout.vue +++ b/frontend/src/components/FieldLayout.vue @@ -190,15 +190,21 @@ :placeholder="getPlaceholder(field)" v-model="data[field.name]" /> + !props.tabs[0].no_tabs) diff --git a/frontend/src/components/SidePanelLayout.vue b/frontend/src/components/SidePanelLayout.vue index be646727..e919ae46 100644 --- a/frontend/src/components/SidePanelLayout.vue +++ b/frontend/src/components/SidePanelLayout.vue @@ -168,6 +168,15 @@ @change="(data) => emit('update', field.name, data)" />
+ f.fieldname == fieldname) + let precision = df?.precision || null + return formatNumber(doc[fieldname], "", precision) + } + function getFormattedCurrency(fieldname, doc) { let currency = window.sysdefaults.currency || 'USD' @@ -44,6 +50,7 @@ export function getMeta(doctype) { return { meta, doctypeMeta, + getFormattedFloat, getFormattedCurrency, } } From 538885cffba891a1b88b47da71df6c7b687250a8 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 25 Dec 2024 18:42:59 +0530 Subject: [PATCH 07/10] fix: align right - currency, int, float & percent fields --- crm/fcrm/doctype/crm_deal/crm_deal.py | 1 + frontend/src/components/ColumnSettings.vue | 6 +++++- frontend/src/components/ListViews/CallLogsListView.vue | 2 +- frontend/src/components/ListViews/ContactsListView.vue | 2 +- frontend/src/components/ListViews/DealsListView.vue | 2 +- .../src/components/ListViews/EmailTemplatesListView.vue | 2 +- frontend/src/components/ListViews/LeadsListView.vue | 2 +- frontend/src/components/ListViews/OrganizationsListView.vue | 2 +- frontend/src/components/ListViews/TasksListView.vue | 2 +- 9 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py index dc18c133..04bf74fd 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.py +++ b/crm/fcrm/doctype/crm_deal/crm_deal.py @@ -139,6 +139,7 @@ class CRMDeal(Document): 'label': 'Amount', 'type': 'Currency', 'key': 'annual_revenue', + 'align': 'right', 'width': '9rem', }, { diff --git a/frontend/src/components/ColumnSettings.vue b/frontend/src/components/ColumnSettings.vue index d91ce735..438e7eaf 100644 --- a/frontend/src/components/ColumnSettings.vue +++ b/frontend/src/components/ColumnSettings.vue @@ -49,7 +49,9 @@ -
+
{ }) function addColumn(c) { + let align = ['Float', 'Int', 'Percent', 'Currency'].includes(c.type) ? 'right' : 'left' let _column = { label: c.label, type: c.type, key: c.value, width: '10rem', + align, } columns.value.push(_column) rows.value.push(c.value) diff --git a/frontend/src/components/ListViews/CallLogsListView.vue b/frontend/src/components/ListViews/CallLogsListView.vue index 4331237b..1522e0a5 100644 --- a/frontend/src/components/ListViews/CallLogsListView.vue +++ b/frontend/src/components/ListViews/CallLogsListView.vue @@ -38,7 +38,7 @@ v-slot="{ idx, column, item }" doctype="CRM Call Log" > - +