Merge pull request #1082 from shariquerik/exchange_rate_fix
This commit is contained in:
commit
c866d1e836
@ -7,10 +7,8 @@ from frappe.desk.form.assign_to import add as assign
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
from crm.fcrm.doctype.crm_service_level_agreement.utils import get_sla
|
from crm.fcrm.doctype.crm_service_level_agreement.utils import get_sla
|
||||||
from crm.fcrm.doctype.crm_status_change_log.crm_status_change_log import (
|
from crm.fcrm.doctype.crm_status_change_log.crm_status_change_log import add_status_change_log
|
||||||
add_status_change_log,
|
from crm.fcrm.doctype.fcrm_settings.fcrm_settings import get_exchange_rate
|
||||||
)
|
|
||||||
from crm.utils import get_exchange_rate
|
|
||||||
|
|
||||||
|
|
||||||
class CRMDeal(Document):
|
class CRMDeal(Document):
|
||||||
@ -177,7 +175,7 @@ class CRMDeal(Document):
|
|||||||
system_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD"
|
system_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD"
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
if self.currency and self.currency != system_currency:
|
if self.currency and self.currency != system_currency:
|
||||||
exchange_rate = get_exchange_rate(self.currency, system_currency, frappe.utils.nowdate())
|
exchange_rate = get_exchange_rate(self.currency, system_currency)
|
||||||
|
|
||||||
self.db_set("exchange_rate", exchange_rate)
|
self.db_set("exchange_rate", exchange_rate)
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
from crm.utils import get_exchange_rate
|
from crm.fcrm.doctype.fcrm_settings.fcrm_settings import get_exchange_rate
|
||||||
|
|
||||||
|
|
||||||
class CRMOrganization(Document):
|
class CRMOrganization(Document):
|
||||||
@ -16,7 +16,7 @@ class CRMOrganization(Document):
|
|||||||
system_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD"
|
system_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD"
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
if self.currency and self.currency != system_currency:
|
if self.currency and self.currency != system_currency:
|
||||||
exchange_rate = get_exchange_rate(self.currency, system_currency, frappe.utils.nowdate())
|
exchange_rate = get_exchange_rate(self.currency, system_currency)
|
||||||
|
|
||||||
self.db_set("exchange_rate", exchange_rate)
|
self.db_set("exchange_rate", exchange_rate)
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,12 @@
|
|||||||
"defaults_tab",
|
"defaults_tab",
|
||||||
"restore_defaults",
|
"restore_defaults",
|
||||||
"enable_forecasting",
|
"enable_forecasting",
|
||||||
|
"currency_tab",
|
||||||
"currency",
|
"currency",
|
||||||
|
"exchange_rate_provider_section",
|
||||||
|
"service_provider",
|
||||||
|
"column_break_vqck",
|
||||||
|
"access_key",
|
||||||
"branding_tab",
|
"branding_tab",
|
||||||
"brand_name",
|
"brand_name",
|
||||||
"brand_logo",
|
"brand_logo",
|
||||||
@ -72,13 +77,42 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Currency",
|
"label": "Currency",
|
||||||
"options": "Currency"
|
"options": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "currency_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "exchange_rate_provider_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Exchange Rate Provider"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "frankfurter.app",
|
||||||
|
"fieldname": "service_provider",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Service Provider",
|
||||||
|
"options": "frankfurter.app\nexchangerate.host",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.service_provider == 'exchangerate.host';",
|
||||||
|
"fieldname": "access_key",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Access Key",
|
||||||
|
"mandatory_depends_on": "eval:doc.service_provider == 'exchangerate.host';"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_vqck",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-07-13 11:58:34.857638",
|
"modified": "2025-07-28 17:04:24.585768",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "shariq@frappe.io",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "FCRM Settings",
|
"name": "FCRM Settings",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
import requests
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.custom.doctype.property_setter.property_setter import delete_property_setter, make_property_setter
|
from frappe.custom.doctype.property_setter.property_setter import delete_property_setter, make_property_setter
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
@ -132,3 +133,76 @@ def get_forecasting_script():
|
|||||||
this.doc.probability = status.probability
|
this.doc.probability = status.probability
|
||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_exchange_rate(from_currency, to_currency, date=None):
|
||||||
|
if not date:
|
||||||
|
date = "latest"
|
||||||
|
|
||||||
|
api_used = "frankfurter"
|
||||||
|
|
||||||
|
api_endpoint = f"https://api.frankfurter.app/{date}?from={from_currency}&to={to_currency}"
|
||||||
|
res = requests.get(api_endpoint, timeout=5)
|
||||||
|
if res.ok:
|
||||||
|
data = res.json()
|
||||||
|
return data["rates"][to_currency]
|
||||||
|
|
||||||
|
# Fallback to exchangerate.host if Frankfurter API fails
|
||||||
|
settings = FCRMSettings("FCRM Settings")
|
||||||
|
if settings and settings.service_provider == "exchangerate.host":
|
||||||
|
api_used = "exchangerate.host"
|
||||||
|
if not settings.access_key:
|
||||||
|
frappe.throw(
|
||||||
|
_("Access Key is required for Service Provider: {0}").format(
|
||||||
|
frappe.bold(settings.service_provider)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"access_key": settings.access_key,
|
||||||
|
"from": from_currency,
|
||||||
|
"to": to_currency,
|
||||||
|
"amount": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if date != "latest":
|
||||||
|
params["date"] = date
|
||||||
|
|
||||||
|
api_endpoint = "https://api.exchangerate.host/convert"
|
||||||
|
|
||||||
|
res = requests.get(api_endpoint, params=params, timeout=5)
|
||||||
|
if res.ok:
|
||||||
|
data = res.json()
|
||||||
|
return data["result"]
|
||||||
|
|
||||||
|
frappe.log_error(
|
||||||
|
title="Exchange Rate Fetch Error",
|
||||||
|
message=f"Failed to fetch exchange rate from {from_currency} to {to_currency} using {api_used} API.",
|
||||||
|
)
|
||||||
|
|
||||||
|
if api_used == "frankfurter":
|
||||||
|
user = frappe.session.user
|
||||||
|
is_manager = (
|
||||||
|
"System Manager" in frappe.get_roles(user)
|
||||||
|
or "Sales Manager" in frappe.get_roles(user)
|
||||||
|
or user == "Administrator"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not is_manager:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Ask your manager to set up the Exchange Rate Provider, as default provider does not support currency conversion for {0} to {1}."
|
||||||
|
).format(from_currency, to_currency)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Setup the Exchange Rate Provider as 'Exchangerate Host' in settings, as default provider does not support currency conversion for {0} to {1}."
|
||||||
|
).format(from_currency, to_currency)
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Failed to fetch exchange rate from {0} to {1} on {2}. Please check your internet connection or try again later."
|
||||||
|
).format(from_currency, to_currency, date)
|
||||||
|
)
|
||||||
|
|||||||
@ -267,24 +267,3 @@ def sales_user_only(fn):
|
|||||||
return fn(*args, **kwargs)
|
return fn(*args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_rate(from_currency, to_currency, date=None):
|
|
||||||
if not date:
|
|
||||||
date = "latest"
|
|
||||||
|
|
||||||
url = f"https://api.frankfurter.app/{date}?from={from_currency}&to={to_currency}"
|
|
||||||
|
|
||||||
for _i in range(3):
|
|
||||||
response = requests.get(url)
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
rate = data["rates"].get(to_currency)
|
|
||||||
if rate:
|
|
||||||
return rate
|
|
||||||
|
|
||||||
frappe.log_error(
|
|
||||||
f"Failed to fetch exchange rate from {from_currency} to {to_currency} on {date}",
|
|
||||||
title="Exchange Rate Fetch Error",
|
|
||||||
)
|
|
||||||
return 1.0 # Default exchange rate if API call fails or no rate found
|
|
||||||
|
|||||||
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@ -62,6 +62,7 @@ declare module 'vue' {
|
|||||||
CountUpTimer: typeof import('./src/components/CountUpTimer.vue')['default']
|
CountUpTimer: typeof import('./src/components/CountUpTimer.vue')['default']
|
||||||
CreateDocumentModal: typeof import('./src/components/Modals/CreateDocumentModal.vue')['default']
|
CreateDocumentModal: typeof import('./src/components/Modals/CreateDocumentModal.vue')['default']
|
||||||
CRMLogo: typeof import('./src/components/Icons/CRMLogo.vue')['default']
|
CRMLogo: typeof import('./src/components/Icons/CRMLogo.vue')['default']
|
||||||
|
CurrencySettings: typeof import('./src/components/Settings/General/CurrencySettings.vue')['default']
|
||||||
CustomActions: typeof import('./src/components/CustomActions.vue')['default']
|
CustomActions: typeof import('./src/components/CustomActions.vue')['default']
|
||||||
DashboardGrid: typeof import('./src/components/Dashboard/DashboardGrid.vue')['default']
|
DashboardGrid: typeof import('./src/components/Dashboard/DashboardGrid.vue')['default']
|
||||||
DashboardIcon: typeof import('./src/components/Icons/DashboardIcon.vue')['default']
|
DashboardIcon: typeof import('./src/components/Icons/DashboardIcon.vue')['default']
|
||||||
|
|||||||
197
frontend/src/components/Settings/General/CurrencySettings.vue
Normal file
197
frontend/src/components/Settings/General/CurrencySettings.vue
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex h-full flex-col gap-6 px-6 py-8 text-ink-gray-8">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex px-2 justify-between">
|
||||||
|
<div class="flex items-center gap-1 -ml-4 w-9/12">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
icon-left="chevron-left"
|
||||||
|
:label="__('Currency & Exchange Rate Provider')"
|
||||||
|
size="md"
|
||||||
|
@click="() => emit('updateStep', 'general-settings')"
|
||||||
|
class="text-xl !h-7 font-semibold hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5"
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
v-if="settings.isDirty"
|
||||||
|
:label="__('Not Saved')"
|
||||||
|
variant="subtle"
|
||||||
|
theme="orange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex item-center space-x-2 w-3/12 justify-end">
|
||||||
|
<Button
|
||||||
|
:label="__('Update')"
|
||||||
|
icon-left="plus"
|
||||||
|
variant="solid"
|
||||||
|
:disabled="!settings.isDirty"
|
||||||
|
:loading="settings.loading"
|
||||||
|
@click="updateSettings"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fields -->
|
||||||
|
<div class="flex flex-1 flex-col overflow-y-auto">
|
||||||
|
<div class="flex items-center justify-between gap-8 p-3">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="text-p-base font-medium text-ink-gray-7 truncate">
|
||||||
|
{{ __('Currency') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-p-sm text-ink-gray-5">
|
||||||
|
{{
|
||||||
|
__(
|
||||||
|
'CRM currency for all monetary values. Once set, cannot be edited.',
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div v-if="settings.doc?.currency" class="text-base text-ink-gray-8">
|
||||||
|
{{ settings.doc.currency }}
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
v-else
|
||||||
|
class="form-control flex-1 truncate w-40"
|
||||||
|
:value="settings.doc?.currency"
|
||||||
|
doctype="Currency"
|
||||||
|
@change="(v) => setCurrency(v)"
|
||||||
|
:placeholder="__('Select currency')"
|
||||||
|
placement="bottom-end"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-px border-t mx-2 border-outline-gray-modals" />
|
||||||
|
<div class="flex items-center justify-between gap-8 p-3">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="text-p-base font-medium text-ink-gray-7 truncate">
|
||||||
|
{{ __('Exchange Rate Provider') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-p-sm text-ink-gray-5">
|
||||||
|
{{ __('Configure the exchange rate provider for your CRM') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<FormControl
|
||||||
|
type="select"
|
||||||
|
class="w-44"
|
||||||
|
v-model="settings.doc.service_provider"
|
||||||
|
:options="[
|
||||||
|
{ label: 'Frankfurter', value: 'frankfurter.app' },
|
||||||
|
{ label: 'Exchangerate Host', value: 'exchangerate.host' },
|
||||||
|
]"
|
||||||
|
:placeholder="__('Select provider')"
|
||||||
|
:disabled="!settings.doc?.currency"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="settings.doc.service_provider === 'exchangerate.host'"
|
||||||
|
class="h-px border-t mx-2 border-outline-gray-modals"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="settings.doc.service_provider === 'exchangerate.host'"
|
||||||
|
class="flex items-center justify-between gap-8 p-3"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="text-p-base font-medium text-ink-gray-7 truncate">
|
||||||
|
{{ __('Exchangerate Host Access Key') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-p-sm text-ink-gray-5">
|
||||||
|
{{
|
||||||
|
__(
|
||||||
|
'Access key for Exchangerate Host. Required for fetching exchange rates.',
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="text-p-sm text-ink-gray-5">
|
||||||
|
{{ __('You can get your access key from ') }}
|
||||||
|
<a
|
||||||
|
class="hover:underline text-ink-gray-7"
|
||||||
|
href="https://exchangerate.host/#/docs/access_key"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ __('exchangerate.host') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<FormControl
|
||||||
|
type="text"
|
||||||
|
class="w-44"
|
||||||
|
v-model="settings.doc.access_key"
|
||||||
|
:placeholder="__('Enter access key')"
|
||||||
|
:disabled="!settings.doc?.currency"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="errorMessage" class="px-3">
|
||||||
|
<ErrorMessage :message="__(errorMessage)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ErrorMessage } from 'frappe-ui'
|
||||||
|
import { getSettings } from '@/stores/settings'
|
||||||
|
import { globalStore } from '@/stores/global'
|
||||||
|
import { showSettings } from '@/composables/settings'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import FormControl from 'frappe-ui/src/components/FormControl/FormControl.vue'
|
||||||
|
|
||||||
|
const { _settings: settings } = getSettings()
|
||||||
|
const { $dialog } = globalStore()
|
||||||
|
|
||||||
|
const emit = defineEmits(['updateStep'])
|
||||||
|
const errorMessage = ref('')
|
||||||
|
|
||||||
|
function updateSettings() {
|
||||||
|
settings.save.submit(null, {
|
||||||
|
validate: () => {
|
||||||
|
errorMessage.value = ''
|
||||||
|
if (!settings.doc?.currency) {
|
||||||
|
errorMessage.value = __('Please select a currency before saving.')
|
||||||
|
return errorMessage.value
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
settings.doc.service_provider === 'exchangerate.host' &&
|
||||||
|
!settings.doc.access_key
|
||||||
|
) {
|
||||||
|
errorMessage.value = __(
|
||||||
|
'Please enter the Exchangerate Host access key.',
|
||||||
|
)
|
||||||
|
return errorMessage.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
showSettings.value = false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCurrency(value) {
|
||||||
|
$dialog({
|
||||||
|
title: __('Set currency'),
|
||||||
|
message: __(
|
||||||
|
'Are you sure you want to set the currency as {0}? This cannot be changed later.',
|
||||||
|
[value],
|
||||||
|
),
|
||||||
|
variant: 'solid',
|
||||||
|
theme: 'blue',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: __('Save'),
|
||||||
|
variant: 'solid',
|
||||||
|
onClick: (close) => {
|
||||||
|
settings.doc.currency = value
|
||||||
|
settings.save.submit(null, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(__('Currency set as {0} successfully', [value]))
|
||||||
|
close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<div class="text-p-sm text-ink-gray-5 truncate">
|
<div class="text-p-sm text-ink-gray-5 truncate">
|
||||||
{{
|
{{
|
||||||
__(
|
__(
|
||||||
'Makes "Close Date" and "Deal Value" mandatory for deal value forecasting',
|
'Makes "Expected Closure Date" and "Expected Deal Value" mandatory for deal value forecasting',
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
@ -35,37 +35,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-px border-t mx-2 border-outline-gray-modals" />
|
<div class="h-px border-t mx-2 border-outline-gray-modals" />
|
||||||
<div
|
|
||||||
class="flex items-center justify-between gap-8 p-3 cursor-pointer hover:bg-surface-menu-bar rounded"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="text-p-base font-medium text-ink-gray-7 truncate">
|
|
||||||
{{ __('Currency') }}
|
|
||||||
</div>
|
|
||||||
<div class="text-p-sm text-ink-gray-5">
|
|
||||||
{{
|
|
||||||
__(
|
|
||||||
'CRM currency for all monetary values. Once set, cannot be edited.',
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div v-if="settings.doc.currency" class="text-base text-ink-gray-8">
|
|
||||||
{{ settings.doc.currency }}
|
|
||||||
</div>
|
|
||||||
<Link
|
|
||||||
v-else
|
|
||||||
class="form-control flex-1 truncate w-40"
|
|
||||||
:value="settings.doc.currency"
|
|
||||||
doctype="Currency"
|
|
||||||
@change="(v) => setCurrency(v)"
|
|
||||||
:placeholder="__('Select currency')"
|
|
||||||
placement="bottom-end"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="h-px border-t mx-2 border-outline-gray-modals" />
|
|
||||||
<template v-for="(setting, i) in settingsList" :key="setting.name">
|
<template v-for="(setting, i) in settingsList" :key="setting.name">
|
||||||
<li
|
<li
|
||||||
class="flex items-center justify-between p-3 cursor-pointer hover:bg-surface-menu-bar rounded"
|
class="flex items-center justify-between p-3 cursor-pointer hover:bg-surface-menu-bar rounded"
|
||||||
@ -93,17 +62,20 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import Link from '@/components/Controls/Link.vue'
|
|
||||||
import { getSettings } from '@/stores/settings'
|
import { getSettings } from '@/stores/settings'
|
||||||
import { globalStore } from '@/stores/global'
|
|
||||||
import { Switch, toast } from 'frappe-ui'
|
import { Switch, toast } from 'frappe-ui'
|
||||||
|
|
||||||
const emit = defineEmits(['updateStep'])
|
const emit = defineEmits(['updateStep'])
|
||||||
|
|
||||||
const { _settings: settings } = getSettings()
|
const { _settings: settings } = getSettings()
|
||||||
const { $dialog } = globalStore()
|
|
||||||
|
|
||||||
const settingsList = [
|
const settingsList = [
|
||||||
|
{
|
||||||
|
name: 'currency-settings',
|
||||||
|
label: 'Currency & Exchange Rate Provider',
|
||||||
|
description:
|
||||||
|
'Configure the currency and exchange rate provider for your CRM',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'brand-settings',
|
name: 'brand-settings',
|
||||||
label: 'Brand settings',
|
label: 'Brand settings',
|
||||||
@ -130,31 +102,4 @@ function toggleForecasting(value) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCurrency(value) {
|
|
||||||
$dialog({
|
|
||||||
title: __('Set currency'),
|
|
||||||
message: __(
|
|
||||||
'Are you sure you want to set the currency as {0}? This cannot be changed later.',
|
|
||||||
[value],
|
|
||||||
),
|
|
||||||
variant: 'solid',
|
|
||||||
theme: 'blue',
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: __('Save'),
|
|
||||||
variant: 'solid',
|
|
||||||
onClick: (close) => {
|
|
||||||
settings.doc.currency = value
|
|
||||||
settings.save.submit(null, {
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success(__('Currency set as {0} successfully', [value]))
|
|
||||||
close()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import GeneralSettings from './GeneralSettings.vue'
|
import GeneralSettings from './GeneralSettings.vue'
|
||||||
|
import CurrencySettings from './CurrencySettings.vue'
|
||||||
import BrandSettings from './BrandSettings.vue'
|
import BrandSettings from './BrandSettings.vue'
|
||||||
import HomeActions from './HomeActions.vue'
|
import HomeActions from './HomeActions.vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
@ -20,6 +21,8 @@ function getComponent(step) {
|
|||||||
switch (step) {
|
switch (step) {
|
||||||
case 'general-settings':
|
case 'general-settings':
|
||||||
return GeneralSettings
|
return GeneralSettings
|
||||||
|
case 'currency-settings':
|
||||||
|
return CurrencySettings
|
||||||
case 'brand-settings':
|
case 'brand-settings':
|
||||||
return BrandSettings
|
return BrandSettings
|
||||||
case 'home-actions':
|
case 'home-actions':
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { getScript } from '@/data/script'
|
import { getScript } from '@/data/script'
|
||||||
|
import { globalStore } from '@/stores/global'
|
||||||
|
import { showSettings, activeSettingsPage } from '@/composables/settings'
|
||||||
import { runSequentially, parseAssignees } from '@/utils'
|
import { runSequentially, parseAssignees } from '@/utils'
|
||||||
import { createDocumentResource, createResource, toast } from 'frappe-ui'
|
import { createDocumentResource, createResource, toast } from 'frappe-ui'
|
||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
@ -24,7 +26,8 @@ export function useDocument(doctype, docname) {
|
|||||||
toast.success(__('Document updated successfully'))
|
toast.success(__('Document updated successfully'))
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
let errorMessage = __('Error updating document')
|
triggerOnError(err)
|
||||||
|
|
||||||
if (err.exc_type == 'MandatoryError') {
|
if (err.exc_type == 'MandatoryError') {
|
||||||
const fieldName = err.messages
|
const fieldName = err.messages
|
||||||
.map((msg) => {
|
.map((msg) => {
|
||||||
@ -32,9 +35,18 @@ export function useDocument(doctype, docname) {
|
|||||||
return arr[arr.length - 1].trim()
|
return arr[arr.length - 1].trim()
|
||||||
})
|
})
|
||||||
.join(', ')
|
.join(', ')
|
||||||
errorMessage = __('Mandatory field error: {0}', [fieldName])
|
toast.error(__('Mandatory field error: {0}', [fieldName]))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
toast.error(errorMessage)
|
|
||||||
|
err.messages?.forEach((msg) => {
|
||||||
|
toast.error(msg)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (err.messages?.length === 0) {
|
||||||
|
toast.error(__('An error occurred while updating the document'))
|
||||||
|
}
|
||||||
|
|
||||||
console.error(err)
|
console.error(err)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -76,8 +88,21 @@ export function useDocument(doctype, docname) {
|
|||||||
|
|
||||||
controllersCache[doctype][docname || ''] = {}
|
controllersCache[doctype][docname || ''] = {}
|
||||||
|
|
||||||
|
const { makeCall } = globalStore()
|
||||||
|
|
||||||
|
let helpers = {}
|
||||||
|
|
||||||
|
helpers.crm = {
|
||||||
|
makePhoneCall: makeCall,
|
||||||
|
openSettings: (page) => {
|
||||||
|
showSettings.value = true
|
||||||
|
activeSettingsPage.value = page
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const controllersArray = await setupScript(
|
const controllersArray = await setupScript(
|
||||||
documentsCache[doctype][docname || ''],
|
documentsCache[doctype][docname || ''],
|
||||||
|
helpers,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!controllersArray || controllersArray.length === 0) return
|
if (!controllersArray || controllersArray.length === 0) return
|
||||||
@ -133,6 +158,13 @@ export function useDocument(doctype, docname) {
|
|||||||
await trigger(handler)
|
await trigger(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function triggerOnError() {
|
||||||
|
const handler = async function () {
|
||||||
|
await (this.onError?.() || this.on_error?.())
|
||||||
|
}
|
||||||
|
await trigger(handler)
|
||||||
|
}
|
||||||
|
|
||||||
async function triggerOnRefresh() {
|
async function triggerOnRefresh() {
|
||||||
const handler = async function () {
|
const handler = async function () {
|
||||||
await this.refresh?.()
|
await this.refresh?.()
|
||||||
@ -234,6 +266,7 @@ export function useDocument(doctype, docname) {
|
|||||||
triggerOnLoad,
|
triggerOnLoad,
|
||||||
triggerOnBeforeCreate,
|
triggerOnBeforeCreate,
|
||||||
triggerOnSave,
|
triggerOnSave,
|
||||||
|
triggerOnError,
|
||||||
triggerOnRefresh,
|
triggerOnRefresh,
|
||||||
triggerOnChange,
|
triggerOnChange,
|
||||||
triggerOnRowAdd,
|
triggerOnRowAdd,
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export function getScript(doctype, view = 'Form') {
|
|||||||
let scriptDefs = doctypeScripts[doctype]
|
let scriptDefs = doctypeScripts[doctype]
|
||||||
if (!scriptDefs || Object.keys(scriptDefs).length === 0) return null
|
if (!scriptDefs || Object.keys(scriptDefs).length === 0) return null
|
||||||
|
|
||||||
const { $dialog, $socket, makeCall } = globalStore()
|
const { $dialog, $socket } = globalStore()
|
||||||
|
|
||||||
helpers.createDialog = $dialog
|
helpers.createDialog = $dialog
|
||||||
helpers.toast = toast
|
helpers.toast = toast
|
||||||
@ -51,10 +51,6 @@ export function getScript(doctype, view = 'Form') {
|
|||||||
throw new Error(message || __('An error occurred'))
|
throw new Error(message || __('An error occurred'))
|
||||||
}
|
}
|
||||||
|
|
||||||
helpers.crm = {
|
|
||||||
makePhoneCall: makeCall,
|
|
||||||
}
|
|
||||||
|
|
||||||
return setupMultipleFormControllers(scriptDefs, document, helpers)
|
return setupMultipleFormControllers(scriptDefs, document, helpers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user