Merge pull request #487 from shariquerik/currency-format
This commit is contained in:
commit
4fab05ee04
@ -139,6 +139,7 @@ class CRMDeal(Document):
|
||||
'label': 'Amount',
|
||||
'type': 'Currency',
|
||||
'key': 'annual_revenue',
|
||||
'align': 'right',
|
||||
'width': '9rem',
|
||||
},
|
||||
{
|
||||
|
||||
@ -32,7 +32,12 @@
|
||||
<span>{{ __('Loading...') }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<FieldLayout v-if="tabs.data" :tabs="tabs.data" :data="data.doc" />
|
||||
<FieldLayout
|
||||
v-if="tabs.data"
|
||||
:tabs="tabs.data"
|
||||
:data="data.doc"
|
||||
:doctype="doctype"
|
||||
/>
|
||||
</div>
|
||||
<DataFieldsModal
|
||||
v-if="showDataFieldsModal"
|
||||
|
||||
@ -49,7 +49,9 @@
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
<div class="mt-1.5 flex flex-col gap-1 border-t border-outline-gray-modals pt-1.5">
|
||||
<div
|
||||
class="mt-1.5 flex flex-col gap-1 border-t border-outline-gray-modals pt-1.5"
|
||||
>
|
||||
<Autocomplete
|
||||
value=""
|
||||
:options="fields"
|
||||
@ -213,11 +215,13 @@ const fields = computed(() => {
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
@ -190,6 +190,30 @@
|
||||
:placeholder="getPlaceholder(field)"
|
||||
v-model="data[field.name]"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'Percent'"
|
||||
type="text"
|
||||
:value="getFormattedPercent(field.name, data)"
|
||||
:placeholder="getPlaceholder(field)"
|
||||
:disabled="Boolean(field.read_only)"
|
||||
@change="data[field.name] = flt($event.target.value)"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'Float'"
|
||||
type="text"
|
||||
:value="getFormattedFloat(field.name, data)"
|
||||
:placeholder="getPlaceholder(field)"
|
||||
:disabled="Boolean(field.read_only)"
|
||||
@change="data[field.name] = flt($event.target.value)"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'Currency'"
|
||||
type="text"
|
||||
:value="getFormattedCurrency(field.name, data)"
|
||||
:placeholder="getPlaceholder(field)"
|
||||
:disabled="Boolean(field.read_only)"
|
||||
@change="data[field.name] = flt($event.target.value)"
|
||||
/>
|
||||
<FormControl
|
||||
v-else
|
||||
type="text"
|
||||
@ -213,22 +237,30 @@ import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import { getMeta } from '../stores/meta'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { getFormat } from '@/utils'
|
||||
import { flt } from '@/utils/numberFormat.js'
|
||||
import { Tabs, Tooltip, DatePicker, DateTimePicker } from 'frappe-ui'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const { getUser } = usersStore()
|
||||
|
||||
const props = defineProps({
|
||||
tabs: Array,
|
||||
data: Object,
|
||||
doctype: {
|
||||
type: String,
|
||||
default: 'CRM Lead',
|
||||
},
|
||||
modal: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
||||
getMeta(props.doctype)
|
||||
const { getUser } = usersStore()
|
||||
|
||||
const hasTabs = computed(() => !props.tabs[0].no_tabs)
|
||||
|
||||
const tabIndex = ref(0)
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
v-slot="{ idx, column, item }"
|
||||
doctype="CRM Call Log"
|
||||
>
|
||||
<ListRowItem :item="item">
|
||||
<ListRowItem :item="item" :align="column.align">
|
||||
<template #prefix>
|
||||
<div v-if="['caller', 'receiver'].includes(column.key)">
|
||||
<Avatar
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
v-slot="{ idx, column, item }"
|
||||
doctype="Contact"
|
||||
>
|
||||
<ListRowItem :item="item">
|
||||
<ListRowItem :item="item" :align="column.align">
|
||||
<template #prefix>
|
||||
<div v-if="column.key === 'full_name'">
|
||||
<Avatar
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<ListRowItem v-else :item="item">
|
||||
<ListRowItem v-else :item="item" :align="column.align">
|
||||
<template #prefix>
|
||||
<div v-if="column.key === 'status'">
|
||||
<IndicatorIcon :class="item.color" />
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
v-slot="{ idx, column, item }"
|
||||
doctype="Email Template"
|
||||
>
|
||||
<ListRowItem :item="item">
|
||||
<ListRowItem :item="item" :align="column.align">
|
||||
<!-- <template #prefix>
|
||||
|
||||
</template> -->
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<ListRowItem v-else :item="item">
|
||||
<ListRowItem v-else :item="item" :align="column.align">
|
||||
<template #prefix>
|
||||
<div v-if="column.key === 'status'">
|
||||
<IndicatorIcon :class="item.color" />
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
v-slot="{ idx, column, item }"
|
||||
doctype="CRM Organization"
|
||||
>
|
||||
<ListRowItem :item="item">
|
||||
<ListRowItem :item="item" :align="column.align">
|
||||
<template #prefix>
|
||||
<div v-if="column.key === 'organization_name'">
|
||||
<Avatar
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<ListRowItem v-else :item="item">
|
||||
<ListRowItem v-else :item="item" :align="column.align">
|
||||
<template #prefix>
|
||||
<div v-if="column.key === 'status'">
|
||||
<TaskStatusIcon :status="item" />
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabs.data">
|
||||
<FieldLayout :tabs="tabs.data" :data="_address" />
|
||||
<FieldLayout :tabs="tabs.data" :data="_address" doctype="Address" />
|
||||
<ErrorMessage class="mt-2" :message="error" />
|
||||
</div>
|
||||
</div>
|
||||
@ -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({
|
||||
|
||||
@ -23,7 +23,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="filteredSections.length">
|
||||
<FieldLayout :tabs="filteredSections" :data="_contact" />
|
||||
<FieldLayout
|
||||
:tabs="filteredSections"
|
||||
:data="_contact"
|
||||
doctype="Contact"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 pb-7 pt-4 sm:px-6">
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
v-if="filteredSections.length"
|
||||
:tabs="filteredSections"
|
||||
:data="deal"
|
||||
doctype="CRM Deal"
|
||||
/>
|
||||
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
|
||||
</div>
|
||||
|
||||
@ -23,7 +23,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="filteredSections.length">
|
||||
<FieldLayout :tabs="filteredSections" :data="_organization" />
|
||||
<FieldLayout
|
||||
:tabs="filteredSections"
|
||||
:data="_organization"
|
||||
doctype="CRM Organization"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 pb-7 pt-4 sm:px-6">
|
||||
|
||||
@ -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
|
||||
|
||||
@ -12,7 +12,12 @@
|
||||
/>
|
||||
</h2>
|
||||
<div v-if="!data.get.loading" class="flex-1 overflow-y-auto">
|
||||
<FieldLayout v-if="data?.doc && tabs" :tabs="tabs" :data="data.doc" />
|
||||
<FieldLayout
|
||||
v-if="data?.doc && tabs"
|
||||
:tabs="tabs"
|
||||
:data="data.doc"
|
||||
:doctype="doctype"
|
||||
/>
|
||||
<ErrorMessage class="mt-2" :message="error" />
|
||||
</div>
|
||||
<div v-else class="flex flex-1 items-center justify-center">
|
||||
|
||||
@ -168,6 +168,33 @@
|
||||
@change="(data) => emit('update', field.name, data)"
|
||||
/>
|
||||
</div>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'percent'"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:value="getFormattedPercent(field.name, data)"
|
||||
:placeholder="field.placeholder"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, flt($event.target.value))"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'float'"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:value="getFormattedFloat(field.name, data)"
|
||||
:placeholder="field.placeholder"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, flt($event.target.value))"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'currency'"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:value="getFormattedCurrency(field.name, data)"
|
||||
:placeholder="field.placeholder"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, flt($event.target.value))"
|
||||
/>
|
||||
<FormControl
|
||||
v-else
|
||||
class="form-control"
|
||||
@ -203,8 +230,10 @@ import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import { getMeta } from '@/stores/meta'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { getFormat } from '@/utils'
|
||||
import { flt } from '@/utils/numberFormat.js'
|
||||
import { Tooltip, DateTimePicker, DatePicker } from 'frappe-ui'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@ -212,12 +241,18 @@ const props = defineProps({
|
||||
fields: {
|
||||
type: Object,
|
||||
},
|
||||
doctype: {
|
||||
type: String,
|
||||
default: 'CRM Lead',
|
||||
},
|
||||
isLastSection: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
||||
getMeta(props.doctype)
|
||||
const { getUser } = usersStore()
|
||||
|
||||
const emit = defineEmits(['update'])
|
||||
|
||||
@ -142,6 +142,7 @@
|
||||
v-if="section.fields"
|
||||
:fields="section.fields"
|
||||
:isLastSection="i == fieldsLayout.data.length - 1"
|
||||
doctype="Contact"
|
||||
v-model="contact.data"
|
||||
@update="updateField"
|
||||
/>
|
||||
@ -211,13 +212,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 +598,8 @@ async function updateField(fieldname, value) {
|
||||
contact.reload()
|
||||
}
|
||||
|
||||
const { getFormattedCurrency } = getMeta('CRM Deal')
|
||||
|
||||
const columns = computed(() => dealColumns)
|
||||
|
||||
function getDealRowObject(deal) {
|
||||
@ -610,10 +609,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,
|
||||
@ -640,6 +636,7 @@ const dealColumns = [
|
||||
{
|
||||
label: __('Amount'),
|
||||
key: 'annual_revenue',
|
||||
align: 'right',
|
||||
width: '9rem',
|
||||
},
|
||||
{
|
||||
|
||||
@ -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 { getFormattedPercent, getFormattedFloat, getFormattedCurrency } = getMeta('Contact')
|
||||
const { getOrganization } = organizationsStore()
|
||||
|
||||
const showContactModal = ref(false)
|
||||
@ -110,6 +112,18 @@ const rows = computed(() => {
|
||||
_rows[row] = formatDate(contact[row], '', true, fieldType == 'Datetime')
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Currency') {
|
||||
_rows[row] = getFormattedCurrency(row, contact)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Float') {
|
||||
_rows[row] = getFormattedFloat(row, contact)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Percent') {
|
||||
_rows[row] = getFormattedPercent(row, contact)
|
||||
}
|
||||
|
||||
if (row == 'full_name') {
|
||||
_rows[row] = {
|
||||
label: contact.full_name,
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -281,22 +281,19 @@ 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 { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
||||
getMeta('CRM Deal')
|
||||
const { makeCall } = globalStore()
|
||||
const { getUser } = usersStore()
|
||||
const { getOrganization } = organizationsStore()
|
||||
@ -402,6 +399,18 @@ function parseRows(rows, columns = []) {
|
||||
_rows[row] = formatDate(deal[row], '', true, fieldType == 'Datetime')
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Currency') {
|
||||
_rows[row] = getFormattedCurrency(row, deal)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Float') {
|
||||
_rows[row] = getFormattedFloat(row, deal)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Percent') {
|
||||
_rows[row] = getFormattedPercent(row, deal)
|
||||
}
|
||||
|
||||
if (row == 'organization') {
|
||||
_rows[row] = {
|
||||
label: deal.organization,
|
||||
@ -409,11 +418,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,
|
||||
|
||||
@ -75,9 +75,13 @@ 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 { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
||||
getMeta('Email Template')
|
||||
|
||||
const emailTemplatesListView = ref(null)
|
||||
|
||||
// emailTemplates data is loaded in the ViewControls component
|
||||
@ -115,6 +119,18 @@ const rows = computed(() => {
|
||||
)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Currency') {
|
||||
_rows[row] = getFormattedCurrency(row, emailTemplate)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Float') {
|
||||
_rows[row] = getFormattedFloat(row, emailTemplate)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Percent') {
|
||||
_rows[row] = getFormattedPercent(row, emailTemplate)
|
||||
}
|
||||
|
||||
if (['modified', 'creation'].includes(row)) {
|
||||
_rows[row] = {
|
||||
label: formatDate(emailTemplate[row]),
|
||||
|
||||
@ -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,8 @@ import { Avatar, Tooltip, Dropdown } from 'frappe-ui'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, computed, reactive, h } from 'vue'
|
||||
|
||||
const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
||||
getMeta('CRM Lead')
|
||||
const { makeCall } = globalStore()
|
||||
const { getUser } = usersStore()
|
||||
const { getLeadStatus } = statusesStore()
|
||||
@ -416,6 +419,18 @@ function parseRows(rows, columns = []) {
|
||||
_rows[row] = formatDate(lead[row], '', true, fieldType == 'Datetime')
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Currency') {
|
||||
_rows[row] = getFormattedCurrency(row, lead)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Float') {
|
||||
_rows[row] = getFormattedFloat(row, lead)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Percent') {
|
||||
_rows[row] = getFormattedPercent(row, lead)
|
||||
}
|
||||
|
||||
if (row == 'lead_name') {
|
||||
_rows[row] = {
|
||||
label: lead.lead_name,
|
||||
|
||||
@ -145,6 +145,7 @@
|
||||
<SidePanelLayout
|
||||
:fields="section.fields"
|
||||
:isLastSection="i == fieldsLayout.data.length - 1"
|
||||
doctype="Contact"
|
||||
v-model="contact.data"
|
||||
@update="updateField"
|
||||
/>
|
||||
@ -186,13 +187,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 +578,8 @@ async function updateField(fieldname, value) {
|
||||
contact.reload()
|
||||
}
|
||||
|
||||
const { getFormattedCurrency } = getMeta('CRM Deal')
|
||||
|
||||
const columns = computed(() => dealColumns)
|
||||
|
||||
function getDealRowObject(deal) {
|
||||
@ -590,10 +589,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,
|
||||
@ -620,6 +616,7 @@ const dealColumns = [
|
||||
{
|
||||
label: __('Amount'),
|
||||
key: 'annual_revenue',
|
||||
align: 'right',
|
||||
width: '9rem',
|
||||
},
|
||||
{
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -125,9 +125,10 @@
|
||||
>
|
||||
<Section :label="section.label" :opened="section.opened">
|
||||
<SidePanelLayout
|
||||
v-model="organization.doc"
|
||||
:fields="section.fields"
|
||||
:isLastSection="i == fieldsLayout.data.length - 1"
|
||||
v-model="organization.doc"
|
||||
doctype="CRM Organization"
|
||||
@update="updateField"
|
||||
/>
|
||||
</Section>
|
||||
@ -176,16 +177,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 +438,8 @@ const rows = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const { getFormattedCurrency } = getMeta('CRM Deal')
|
||||
|
||||
const columns = computed(() => {
|
||||
return tabIndex.value === 0 ? dealColumns : contactColumns
|
||||
})
|
||||
@ -450,12 +449,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,
|
||||
@ -485,7 +481,7 @@ function getContactRowObject(contact) {
|
||||
mobile_no: contact.mobile_no,
|
||||
company_name: {
|
||||
label: contact.company_name,
|
||||
logo: props.organization?.organization_logo,
|
||||
logo: organization.doc?.organization_logo,
|
||||
},
|
||||
modified: {
|
||||
label: formatDate(contact.modified),
|
||||
@ -503,6 +499,7 @@ const dealColumns = [
|
||||
{
|
||||
label: __('Amount'),
|
||||
key: 'annual_revenue',
|
||||
align: 'right',
|
||||
width: '9rem',
|
||||
},
|
||||
{
|
||||
|
||||
@ -125,9 +125,10 @@
|
||||
</template>
|
||||
<SidePanelLayout
|
||||
v-if="section.fields"
|
||||
v-model="organization.doc"
|
||||
:fields="section.fields"
|
||||
:isLastSection="i == fieldsLayout.data.length - 1"
|
||||
v-model="organization.doc"
|
||||
doctype="CRM Organization"
|
||||
@update="updateField"
|
||||
/>
|
||||
</Section>
|
||||
@ -211,16 +212,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 +473,8 @@ const rows = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const { getFormattedCurrency } = getMeta('CRM Deal')
|
||||
|
||||
const columns = computed(() => {
|
||||
return tabIndex.value === 0 ? dealColumns : contactColumns
|
||||
})
|
||||
@ -485,12 +484,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,
|
||||
@ -520,7 +516,7 @@ function getContactRowObject(contact) {
|
||||
mobile_no: contact.mobile_no,
|
||||
company_name: {
|
||||
label: contact.company_name,
|
||||
logo: props.organization?.organization_logo,
|
||||
logo: organization.doc?.organization_logo,
|
||||
},
|
||||
modified: {
|
||||
label: formatDate(contact.modified),
|
||||
@ -538,6 +534,7 @@ const dealColumns = [
|
||||
{
|
||||
label: __('Amount'),
|
||||
key: 'annual_revenue',
|
||||
align: 'right',
|
||||
width: '9rem',
|
||||
},
|
||||
{
|
||||
|
||||
@ -69,9 +69,13 @@ 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 { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
||||
getMeta('CRM Organization')
|
||||
|
||||
const organizationsListView = ref(null)
|
||||
const showOrganizationModal = ref(false)
|
||||
|
||||
@ -110,6 +114,18 @@ const rows = computed(() => {
|
||||
)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Currency') {
|
||||
_rows[row] = getFormattedCurrency(row, organization)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Float') {
|
||||
_rows[row] = getFormattedFloat(row, organization)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Percent') {
|
||||
_rows[row] = getFormattedPercent(row, organization)
|
||||
}
|
||||
|
||||
if (row === 'organization_name') {
|
||||
_rows[row] = {
|
||||
label: organization.organization_name,
|
||||
@ -117,11 +133,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]),
|
||||
|
||||
@ -204,12 +204,15 @@ 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 { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
||||
getMeta('CRM Task')
|
||||
const { getUser } = usersStore()
|
||||
|
||||
const router = useRouter()
|
||||
@ -271,6 +274,18 @@ function parseRows(rows, columns = []) {
|
||||
_rows[row] = formatDate(task[row], '', true, fieldType == 'Datetime')
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Currency') {
|
||||
_rows[row] = getFormattedCurrency(row, task)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Float') {
|
||||
_rows[row] = getFormattedFloat(row, task)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Percent') {
|
||||
_rows[row] = getFormattedPercent(row, task)
|
||||
}
|
||||
|
||||
if (['modified', 'creation'].includes(row)) {
|
||||
_rows[row] = {
|
||||
label: formatDate(task[row]),
|
||||
|
||||
62
frontend/src/stores/meta.js
Normal file
62
frontend/src/stores/meta.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { createResource } from 'frappe-ui'
|
||||
import { formatCurrency, formatNumber } 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 getFormattedPercent(fieldname, doc) {
|
||||
let value = getFormattedFloat(fieldname, doc)
|
||||
return value + '%'
|
||||
}
|
||||
|
||||
function getFormattedFloat(fieldname, doc) {
|
||||
let df = doctypeMeta[doctype]?.fields.find((f) => f.fieldname == fieldname)
|
||||
let precision = df?.precision || null
|
||||
return formatNumber(doc[fieldname], '', precision)
|
||||
}
|
||||
|
||||
function getFormattedCurrency(fieldname, doc) {
|
||||
let currency = window.sysdefaults.currency || 'USD'
|
||||
let df = doctypeMeta[doctype]?.fields.find((f) => f.fieldname == fieldname)
|
||||
let precision = df?.precision || null
|
||||
|
||||
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], '', currency, precision)
|
||||
}
|
||||
|
||||
return {
|
||||
meta,
|
||||
doctypeMeta,
|
||||
getFormattedFloat,
|
||||
getFormattedPercent,
|
||||
getFormattedCurrency,
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
import { secondsToDuration, formatDate, timeAgo } from '@/utils'
|
||||
import { getMeta } from '@/stores/meta'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
|
||||
const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
||||
getMeta('CRM Call Log')
|
||||
const { getUser } = usersStore()
|
||||
const { getContact, getLeadContact } = contactsStore()
|
||||
|
||||
@ -58,6 +61,18 @@ export function getCallLogDetail(row, log, columns = []) {
|
||||
return formatDate(log[row], '', true, fieldType == 'Datetime')
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Currency') {
|
||||
return getFormattedCurrency(row, log)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Float') {
|
||||
return getFormattedFloat(row, log)
|
||||
}
|
||||
|
||||
if (fieldType && fieldType == 'Percent') {
|
||||
return getFormattedPercent(row, log)
|
||||
}
|
||||
|
||||
return log[row]
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
286
frontend/src/utils/numberFormat.js
Normal file
286
frontend/src/utils/numberFormat.js
Normal file
@ -0,0 +1,286 @@
|
||||
import { get } from '@vueuse/core'
|
||||
|
||||
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)
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user