Merge pull request #1086 from frappe/main-hotfix
This commit is contained in:
commit
c8a54e4d9f
@ -84,6 +84,14 @@ The motivation behind building Frappe CRM stems from the need for a simple, cust
|
||||
- [Frappe Framework](https://github.com/frappe/frappe): A full-stack web application framework.
|
||||
- [Frappe UI](https://github.com/frappe/frappe-ui): A Vue-based UI library, to provide a modern user interface.
|
||||
|
||||
### Compatibility
|
||||
This app is compatible with the following versions of Frappe and ERPNext:
|
||||
|
||||
| CRM branch | Stability | Frappe branch | ERPNext branch |
|
||||
| :-------------------- | :-------- | :------------------- | :------------------- |
|
||||
| main - v1.x | stable | v15.x | v15.x |
|
||||
| develop - future/v2.x | unstable | develop - future/v16 | develop - future/v16 |
|
||||
|
||||
## Getting Started (Production)
|
||||
|
||||
### Managed Hosting
|
||||
|
||||
@ -7,10 +7,8 @@ from frappe.desk.form.assign_to import add as assign
|
||||
from frappe.model.document import Document
|
||||
|
||||
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 (
|
||||
add_status_change_log,
|
||||
)
|
||||
from crm.utils import get_exchange_rate
|
||||
from crm.fcrm.doctype.crm_status_change_log.crm_status_change_log import add_status_change_log
|
||||
from crm.fcrm.doctype.fcrm_settings.fcrm_settings import get_exchange_rate
|
||||
|
||||
|
||||
class CRMDeal(Document):
|
||||
@ -177,7 +175,7 @@ class CRMDeal(Document):
|
||||
system_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD"
|
||||
exchange_rate = 1
|
||||
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)
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
import frappe
|
||||
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):
|
||||
@ -16,7 +16,7 @@ class CRMOrganization(Document):
|
||||
system_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD"
|
||||
exchange_rate = 1
|
||||
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)
|
||||
|
||||
|
||||
@ -8,7 +8,12 @@
|
||||
"defaults_tab",
|
||||
"restore_defaults",
|
||||
"enable_forecasting",
|
||||
"currency_tab",
|
||||
"currency",
|
||||
"exchange_rate_provider_section",
|
||||
"service_provider",
|
||||
"column_break_vqck",
|
||||
"access_key",
|
||||
"branding_tab",
|
||||
"brand_name",
|
||||
"brand_logo",
|
||||
@ -72,13 +77,42 @@
|
||||
"in_list_view": 1,
|
||||
"label": "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,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-13 11:58:34.857638",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2025-07-28 17:04:24.585768",
|
||||
"modified_by": "shariq@frappe.io",
|
||||
"module": "FCRM",
|
||||
"name": "FCRM Settings",
|
||||
"owner": "Administrator",
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
import requests
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.property_setter.property_setter import delete_property_setter, make_property_setter
|
||||
from frappe.model.document import Document
|
||||
@ -132,3 +133,76 @@ def get_forecasting_script():
|
||||
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)
|
||||
)
|
||||
|
||||
@ -35,7 +35,7 @@ def set_default_calling_medium(medium):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "CRM Telephony Agent",
|
||||
"agent": frappe.session.user,
|
||||
"user": frappe.session.user,
|
||||
"default_medium": medium,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
146
crm/locale/bs.po
146
crm/locale/bs.po
@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: shariq@frappe.io\n"
|
||||
"POT-Creation-Date: 2025-07-20 09:37+0000\n"
|
||||
"PO-Revision-Date: 2025-07-23 05:28\n"
|
||||
"PO-Revision-Date: 2025-07-26 06:34\n"
|
||||
"Last-Translator: shariq@frappe.io\n"
|
||||
"Language-Team: Bosnian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -271,7 +271,7 @@ msgstr "Dodaj opis..."
|
||||
|
||||
#: frontend/src/components/Modals/AddExistingUserModal.vue:12
|
||||
msgid "Add existing system users to this CRM. Assign them a role to grant access with their current credentials."
|
||||
msgstr ""
|
||||
msgstr "Dodaj postojeće korisnike sistema CRM-a. Dodijeii im ulogu kako biste im odobrili pristup s njihovim trenutnim akreditivima."
|
||||
|
||||
#: frontend/src/components/ViewControls.vue:104
|
||||
msgid "Add filter"
|
||||
@ -431,7 +431,7 @@ msgstr "Jeste li sigurni da želite izbrisati ovaj zadatak?"
|
||||
|
||||
#: frontend/src/components/DeleteLinkedDocModal.vue:230
|
||||
msgid "Are you sure you want to delete {0} linked item(s)?"
|
||||
msgstr ""
|
||||
msgstr "Jeste li sigurni da želite izbrisati {0} povezanih artikala?"
|
||||
|
||||
#: frontend/src/composables/frappecloud.js:24
|
||||
msgid "Are you sure you want to login to your Frappe Cloud dashboard?"
|
||||
@ -439,15 +439,15 @@ msgstr "Jeste li sigurni da se želite prijaviti na svoju Frappe Cloud Nadzornu
|
||||
|
||||
#: crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.js:9
|
||||
msgid "Are you sure you want to reset 'Create Quotation from CRM Deal' Form Script?"
|
||||
msgstr "Jeste li sigurni da želite poništiti skriptu forme 'Kreiraj Ponudu iz Dogovora'?"
|
||||
msgstr "Jeste li sigurni da želite poništiti skriptu forme 'Kreiraj Ponudu iz Posla'?"
|
||||
|
||||
#: frontend/src/components/Settings/General/GeneralSettings.vue:137
|
||||
msgid "Are you sure you want to set the currency as {0}? This cannot be changed later."
|
||||
msgstr ""
|
||||
msgstr "Jeste li sigurni da želite postaviti valutu kao {0}? Ovo se kasnije ne može promijeniti."
|
||||
|
||||
#: frontend/src/components/DeleteLinkedDocModal.vue:243
|
||||
msgid "Are you sure you want to unlink {0} linked item(s)?"
|
||||
msgstr ""
|
||||
msgstr "Jeste li sigurni da želite ukloniti vezu sa {0} povezanih artikala?"
|
||||
|
||||
#: frontend/src/components/ListBulkActions.vue:184
|
||||
#: frontend/src/components/Modals/AssignmentModal.vue:5
|
||||
@ -479,7 +479,7 @@ msgstr "Zadatak je uspješno očišćen"
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:577
|
||||
msgid "Assignment rule"
|
||||
msgstr ""
|
||||
msgstr "Pravilo dodjeljivanja"
|
||||
|
||||
#: frontend/src/components/Controls/GridFieldsEditorModal.vue:176
|
||||
msgid "At least one field is required"
|
||||
@ -506,23 +506,23 @@ msgstr "Auth Token"
|
||||
|
||||
#: crm/api/dashboard.py:238
|
||||
msgid "Average deal value of non won/lost deals"
|
||||
msgstr ""
|
||||
msgstr "Prosječna vrijednost izgubljenih poslova"
|
||||
|
||||
#: crm/api/dashboard.py:411
|
||||
msgid "Average deal value of ongoing & won deals"
|
||||
msgstr ""
|
||||
msgstr "Prosječna vrijednost tekućih i dobijenih poslova"
|
||||
|
||||
#: crm/api/dashboard.py:354
|
||||
msgid "Average deal value of won deals"
|
||||
msgstr ""
|
||||
msgstr "Prosječna vrijednost dobijenih poslova"
|
||||
|
||||
#: crm/api/dashboard.py:518
|
||||
msgid "Average time taken from deal creation to deal closure"
|
||||
msgstr ""
|
||||
msgstr "Prosječno vrijeme potrebno od kreiranja posla do njegovog zaključenja"
|
||||
|
||||
#: crm/api/dashboard.py:464
|
||||
msgid "Average time taken from lead creation to deal closure"
|
||||
msgstr ""
|
||||
msgstr "Prosječno vrijeme potrebno od kreiranja potencijalnog klijenta do zaključenja posla"
|
||||
|
||||
#: frontend/src/components/Dashboard/AddChartModal.vue:81
|
||||
msgid "Avg deal value"
|
||||
@ -646,12 +646,12 @@ msgstr "Nadzorna Tabla"
|
||||
#. Name of a DocType
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.json
|
||||
msgid "CRM Deal"
|
||||
msgstr "Dogovor"
|
||||
msgstr "Posao"
|
||||
|
||||
#. Name of a DocType
|
||||
#: crm/fcrm/doctype/crm_deal_status/crm_deal_status.json
|
||||
msgid "CRM Deal Status"
|
||||
msgstr "Status Dogovora"
|
||||
msgstr "Status Posla"
|
||||
|
||||
#. Name of a DocType
|
||||
#: crm/fcrm/doctype/crm_dropdown_item/crm_dropdown_item.json
|
||||
@ -795,7 +795,7 @@ msgstr "Postavke Prikaza"
|
||||
|
||||
#: frontend/src/components/Settings/General/GeneralSettings.vue:47
|
||||
msgid "CRM currency for all monetary values. Once set, cannot be edited."
|
||||
msgstr ""
|
||||
msgstr "CRM valuta za sve novčane vrijednosti. Nakon što se postavi, ne može se uređivati."
|
||||
|
||||
#: frontend/src/components/ViewControls.vue:272
|
||||
msgid "CSV"
|
||||
@ -873,7 +873,7 @@ msgstr "Otkazano"
|
||||
|
||||
#: frontend/src/components/Settings/Users.vue:124
|
||||
msgid "Cannot change role of user with Admin access"
|
||||
msgstr ""
|
||||
msgstr "Nije moguće promijeniti ulogu korisnika s administratorskim pristupom"
|
||||
|
||||
#: crm/fcrm/doctype/fcrm_settings/fcrm_settings.py:33
|
||||
msgid "Cannot delete standard items {0}"
|
||||
@ -937,7 +937,7 @@ msgstr "Odaberi Postojeću Organizaciju"
|
||||
|
||||
#: frontend/src/components/Settings/EmailAdd.vue:9
|
||||
msgid "Choose the email service provider you want to configure."
|
||||
msgstr ""
|
||||
msgstr "Odaberite pružatelja usluga e-pošte kojeg želite konfigurirati."
|
||||
|
||||
#: frontend/src/components/Controls/Link.vue:62
|
||||
msgid "Clear"
|
||||
@ -968,12 +968,12 @@ msgstr "Zatvori"
|
||||
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.py:163
|
||||
msgid "Close Date is required."
|
||||
msgstr ""
|
||||
msgstr "Datum zaključenja je obavezan."
|
||||
|
||||
#. Label of the closed_date (Date) field in DocType 'CRM Deal'
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.json
|
||||
msgid "Closed Date"
|
||||
msgstr ""
|
||||
msgstr "Datum zaključenja"
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:107
|
||||
msgid "Collapse"
|
||||
@ -1021,7 +1021,7 @@ msgstr "Komentari"
|
||||
|
||||
#: crm/api/dashboard.py:884
|
||||
msgid "Common reasons for losing deals"
|
||||
msgstr ""
|
||||
msgstr "Uobičajeni razlozi za gubitak poslova"
|
||||
|
||||
#. Label of the communication_status (Link) field in DocType 'CRM Deal'
|
||||
#. Label of the communication_status (Link) field in DocType 'CRM Lead'
|
||||
@ -1057,11 +1057,11 @@ msgstr "Uslov"
|
||||
|
||||
#: frontend/src/components/Settings/General/GeneralSettings.vue:8
|
||||
msgid "Configure general settings for your CRM"
|
||||
msgstr ""
|
||||
msgstr "Konfigurišite opšte postavke za CRM"
|
||||
|
||||
#: frontend/src/components/Settings/TelephonySettings.vue:17
|
||||
msgid "Configure telephony settings for your CRM"
|
||||
msgstr ""
|
||||
msgstr "Konfigurišite postavke telefonije za CRM"
|
||||
|
||||
#: frontend/src/composables/frappecloud.js:29
|
||||
msgid "Confirm"
|
||||
@ -1121,7 +1121,7 @@ msgstr "Kontakt već postoji sa {0}"
|
||||
|
||||
#: frontend/src/pages/Contact.vue:282 frontend/src/pages/MobileContact.vue:255
|
||||
msgid "Contact image updated"
|
||||
msgstr ""
|
||||
msgstr "Slika kontakta ažurirana"
|
||||
|
||||
#: frontend/src/pages/Deal.vue:697 frontend/src/pages/MobileDeal.vue:578
|
||||
msgid "Contact removed"
|
||||
@ -1134,7 +1134,7 @@ msgstr "Kontakt je uklonjen"
|
||||
#: frontend/src/pages/MobileContact.vue:461
|
||||
#: frontend/src/pages/MobileContact.vue:471
|
||||
msgid "Contact updated"
|
||||
msgstr ""
|
||||
msgstr "Kontakt ažuriran"
|
||||
|
||||
#. Label of the contacts_tab (Tab Break) field in DocType 'CRM Deal'
|
||||
#. Label of the contacts (Table) field in DocType 'CRM Deal'
|
||||
@ -1185,7 +1185,7 @@ msgstr "Pretvori potencijalnog klijenta u posao"
|
||||
#: frontend/src/components/Modals/ConvertToDealModal.vue:19
|
||||
#: frontend/src/pages/Lead.vue:53 frontend/src/pages/MobileLead.vue:107
|
||||
msgid "Convert to Deal"
|
||||
msgstr "Pretvori u Dogovor"
|
||||
msgstr "Pretvori u Posao"
|
||||
|
||||
#. Label of the converted (Check) field in DocType 'CRM Lead'
|
||||
#: crm/fcrm/doctype/crm_lead/crm_lead.json
|
||||
@ -1236,7 +1236,7 @@ msgstr "Kreiraj zapisnik poziva"
|
||||
|
||||
#: frontend/src/components/Modals/DealModal.vue:8
|
||||
msgid "Create Deal"
|
||||
msgstr "Kreiraj Dogovor"
|
||||
msgstr "Kreiraj Posao"
|
||||
|
||||
#: frontend/src/components/Modals/LeadModal.vue:8
|
||||
msgid "Create Lead"
|
||||
@ -1298,11 +1298,11 @@ msgstr "Valuta"
|
||||
|
||||
#: frontend/src/components/Settings/General/GeneralSettings.vue:151
|
||||
msgid "Currency set as {0} successfully"
|
||||
msgstr ""
|
||||
msgstr "Valuta je uspješno postavljena na {0}"
|
||||
|
||||
#: crm/api/dashboard.py:839
|
||||
msgid "Current pipeline distribution"
|
||||
msgstr ""
|
||||
msgstr "Trenutna distribucija cjevovoda"
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:586
|
||||
msgid "Custom actions"
|
||||
@ -1310,7 +1310,7 @@ msgstr "Prilagođene radnje"
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:536
|
||||
msgid "Custom branding"
|
||||
msgstr ""
|
||||
msgstr "Prilagođeno brendiranje"
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:585
|
||||
msgid "Custom fields"
|
||||
@ -1366,7 +1366,7 @@ msgstr "Datum"
|
||||
#: frontend/src/components/Telephony/ExotelCallUI.vue:205
|
||||
#: frontend/src/pages/Tasks.vue:129
|
||||
msgid "Deal"
|
||||
msgstr "Dogovor"
|
||||
msgstr "Posao"
|
||||
|
||||
#. Label of the deal_owner (Link) field in DocType 'CRM Deal'
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.json
|
||||
@ -1376,12 +1376,12 @@ msgstr "Odgovorni"
|
||||
#. Label of the deal_status (Link) field in DocType 'ERPNext CRM Settings'
|
||||
#: crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
|
||||
msgid "Deal Status"
|
||||
msgstr "Status Dogovora"
|
||||
msgstr "Status Posla"
|
||||
|
||||
#. Label of a shortcut in the Frappe CRM Workspace
|
||||
#: crm/fcrm/workspace/frappe_crm/frappe_crm.json
|
||||
msgid "Deal Statuses"
|
||||
msgstr "Status Dogovora"
|
||||
msgstr "Status Posla"
|
||||
|
||||
#. Label of the deal_value (Currency) field in DocType 'CRM Deal'
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.json
|
||||
@ -1400,11 +1400,11 @@ msgstr "Analiza kanala generisanja poslova"
|
||||
#: frontend/src/pages/MobileOrganization.vue:475
|
||||
#: frontend/src/pages/Organization.vue:484
|
||||
msgid "Deal owner"
|
||||
msgstr "Odgovorni Dogovora"
|
||||
msgstr "Odgovorni Posla"
|
||||
|
||||
#: frontend/src/pages/Deal.vue:513 frontend/src/pages/MobileDeal.vue:388
|
||||
msgid "Deal updated"
|
||||
msgstr "Dogovor ažuriran"
|
||||
msgstr "Posao ažuriran"
|
||||
|
||||
#: crm/api/dashboard.py:1030 crm/api/dashboard.py:1087
|
||||
msgid "Deal value"
|
||||
@ -1416,7 +1416,7 @@ msgstr "Vrijednost posla"
|
||||
#: frontend/src/pages/MobileDeal.vue:407
|
||||
#: frontend/src/pages/MobileOrganization.vue:328
|
||||
msgid "Deals"
|
||||
msgstr "Dogovori"
|
||||
msgstr "Poslovi"
|
||||
|
||||
#: crm/api/dashboard.py:788
|
||||
#: frontend/src/components/Dashboard/AddChartModal.vue:97
|
||||
@ -1441,7 +1441,7 @@ msgstr "Poslovi po fazama"
|
||||
#: crm/api/dashboard.py:1019
|
||||
#: frontend/src/components/Dashboard/AddChartModal.vue:99
|
||||
msgid "Deals by territory"
|
||||
msgstr "Poslvi po distriktu"
|
||||
msgstr "Poslovi po distriktu"
|
||||
|
||||
#: frontend/src/components/Settings/EmailTemplate/EditEmailTemplate.vue:115
|
||||
#: frontend/src/components/Settings/EmailTemplate/NewEmailTemplate.vue:115
|
||||
@ -1494,7 +1494,7 @@ msgstr "Standard medij za pozivanje za prijavljenog korisnika"
|
||||
|
||||
#: frontend/src/components/Telephony/CallUI.vue:112
|
||||
msgid "Default calling medium set successfully to {0}"
|
||||
msgstr ""
|
||||
msgstr "Standard medij za pozivanje uspješno je postavljen na {0}"
|
||||
|
||||
#: frontend/src/components/Settings/TelephonySettings.vue:280
|
||||
msgid "Default calling medium updated successfully"
|
||||
@ -1569,11 +1569,11 @@ msgstr "Izbriši povezani artikal"
|
||||
|
||||
#: frontend/src/components/DeleteLinkedDocModal.vue:11
|
||||
msgid "Delete or unlink linked documents"
|
||||
msgstr ""
|
||||
msgstr "Izbriši ili prekini veze povezanih dokumenata"
|
||||
|
||||
#: frontend/src/components/DeleteLinkedDocModal.vue:23
|
||||
msgid "Delete or unlink these linked documents before deleting this document"
|
||||
msgstr ""
|
||||
msgstr "Izbriši ili prekini vezu ovih povezanih dokumenata prije brisanja ovog dokumenta"
|
||||
|
||||
#: frontend/src/pages/MobileOrganization.vue:263
|
||||
msgid "Delete organization"
|
||||
@ -1664,7 +1664,7 @@ msgstr "Dokument nije pronađen"
|
||||
|
||||
#: frontend/src/data/document.js:23
|
||||
msgid "Document updated successfully"
|
||||
msgstr ""
|
||||
msgstr "Dokument je uspješno ažuriran"
|
||||
|
||||
#: frontend/src/components/Modals/AboutModal.vue:62
|
||||
msgid "Documentation"
|
||||
@ -1971,7 +1971,7 @@ msgstr "Greška pri pretvaranju u posao: {0}"
|
||||
|
||||
#: frontend/src/pages/MobileDeal.vue:392
|
||||
msgid "Error updating deal"
|
||||
msgstr "Greška pri ažuriranju dogovora"
|
||||
msgstr "Greška pri ažuriranju posla"
|
||||
|
||||
#: frontend/src/pages/Deal.vue:517
|
||||
msgid "Error updating deal: {0}"
|
||||
@ -2119,7 +2119,7 @@ msgstr "Brisanje šablona nije uspjelo"
|
||||
|
||||
#: crm/utils/__init__.py:285
|
||||
msgid "Failed to fetch historical exchange rate from external API. Please try again later."
|
||||
msgstr ""
|
||||
msgstr "Nije uspjelo preuzimanje historijskog kurs iz eksternog API-ja. Pokušaj ponovo kasnije."
|
||||
|
||||
#: frontend/src/data/script.js:110
|
||||
msgid "Failed to load form controller: {0}"
|
||||
@ -2319,7 +2319,7 @@ msgstr "Puno Ime"
|
||||
#: crm/api/dashboard.py:728
|
||||
#: frontend/src/components/Dashboard/AddChartModal.vue:96
|
||||
msgid "Funnel conversion"
|
||||
msgstr "Konverzija prodajnog toka"
|
||||
msgstr "Konverzija lijevka"
|
||||
|
||||
#. Label of the gender (Link) field in DocType 'CRM Contacts'
|
||||
#. Label of the gender (Link) field in DocType 'CRM Deal'
|
||||
@ -2450,19 +2450,19 @@ msgstr "Ikona"
|
||||
|
||||
#: frontend/src/components/Settings/emailConfig.js:55
|
||||
msgid "If enabled, all outgoing emails will be sent from this account. Note: Only one account can be default outgoing."
|
||||
msgstr ""
|
||||
msgstr "Ako je omogućeno, sve odlazne e-poruke će biti poslane s ovog računa. Napomena: Samo jedan račun može biti standard odlazni."
|
||||
|
||||
#: frontend/src/components/Settings/emailConfig.js:47
|
||||
msgid "If enabled, all replies to your company (eg: replies@yourcomany.com) will come to this account. Note: Only one account can be default incoming."
|
||||
msgstr ""
|
||||
msgstr "Ako je omogućeno, svi odgovori vašoj kompaniji (npr.: odgovori@vašakompanija.com) će stizati na ovaj račun. Napomena: Samo jedan račun može biti standard dolazni."
|
||||
|
||||
#: frontend/src/components/Settings/emailConfig.js:39
|
||||
msgid "If enabled, outgoing emails can be sent from this account."
|
||||
msgstr ""
|
||||
msgstr "Ako je omogućeno, odlazna e-pošta može se slati s ovog računa."
|
||||
|
||||
#: frontend/src/components/Settings/emailConfig.js:31
|
||||
msgid "If enabled, records can be created from the incoming emails on this account."
|
||||
msgstr ""
|
||||
msgstr "Ako je omogućeno, zapisi se mogu kreirati iz dolazne e-pošte na ovom računu."
|
||||
|
||||
#. Label of the image (Attach Image) field in DocType 'CRM Lead'
|
||||
#. Label of the image (Attach Image) field in DocType 'CRM Product'
|
||||
@ -2534,7 +2534,7 @@ msgstr "Pokretanje poziva..."
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:593
|
||||
msgid "Integration"
|
||||
msgstr ""
|
||||
msgstr "Integracija"
|
||||
|
||||
#: crm/integrations/exotel/handler.py:73
|
||||
msgid "Integration Not Enabled"
|
||||
@ -2565,7 +2565,7 @@ msgstr "Nevažeći Exotel Broj"
|
||||
|
||||
#: crm/api/dashboard.py:73
|
||||
msgid "Invalid chart name"
|
||||
msgstr ""
|
||||
msgstr "Nevažeći naziv grafikona"
|
||||
|
||||
#: crm/fcrm/doctype/crm_exotel_settings/crm_exotel_settings.py:25
|
||||
msgid "Invalid credentials"
|
||||
@ -2573,15 +2573,15 @@ msgstr "Nevažeći akreditivi"
|
||||
|
||||
#: frontend/src/components/Settings/emailConfig.js:172
|
||||
msgid "Invalid email ID"
|
||||
msgstr ""
|
||||
msgstr "Nevažeća e-pošta"
|
||||
|
||||
#: frontend/src/components/Settings/Users.vue:25
|
||||
msgid "Invite New User"
|
||||
msgstr ""
|
||||
msgstr "Pozovi novog korisnika"
|
||||
|
||||
#: frontend/src/components/Settings/Settings.vue:101
|
||||
msgid "Invite User"
|
||||
msgstr ""
|
||||
msgstr "Pozovi korisnika"
|
||||
|
||||
#: frontend/src/components/Settings/InviteUserPage.vue:56
|
||||
msgid "Invite as"
|
||||
@ -2597,11 +2597,11 @@ msgstr "Pozovi Korisnike"
|
||||
|
||||
#: frontend/src/components/Settings/InviteUserPage.vue:10
|
||||
msgid "Invite users to access CRM. Specify their roles to control access and permissions"
|
||||
msgstr ""
|
||||
msgstr "Pozovi korisnike da pristupe CRM-u. Navedi njihove uloge kako biste kontrolisali pristup i dozvole"
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:354
|
||||
msgid "Invite your team"
|
||||
msgstr ""
|
||||
msgstr "Pozovi tim"
|
||||
|
||||
#. Label of the invited_by (Link) field in DocType 'CRM Invitation'
|
||||
#: crm/fcrm/doctype/crm_invitation/crm_invitation.json
|
||||
@ -2654,7 +2654,7 @@ msgstr "Je standardno"
|
||||
#. Settings'
|
||||
#: crm/fcrm/doctype/fcrm_settings/fcrm_settings.json
|
||||
msgid "It will make deal's \"Expected Closure Date\" & \"Expected Deal Value\" mandatory to get accurate forecasting insights"
|
||||
msgstr ""
|
||||
msgstr "\"Očekivani datum zaključenja\" i \"Očekivana vrijednost posla\" će biti obavezni za dobijanje tačnih uvida u predviđanja"
|
||||
|
||||
#. Label of the json (JSON) field in DocType 'CRM Global Settings'
|
||||
#: crm/fcrm/doctype/crm_global_settings/crm_global_settings.json
|
||||
@ -2753,7 +2753,7 @@ msgstr "Zadnja izmjena"
|
||||
|
||||
#: frontend/src/components/Settings/ProfileSettings.vue:83
|
||||
msgid "Last name"
|
||||
msgstr ""
|
||||
msgstr "Prezime"
|
||||
|
||||
#. Label of the layout (Code) field in DocType 'CRM Dashboard'
|
||||
#. Label of the layout (Code) field in DocType 'CRM Fields Layout'
|
||||
@ -2804,15 +2804,15 @@ msgstr "Status Potencijalnog Klijenta"
|
||||
|
||||
#: crm/api/dashboard.py:935
|
||||
msgid "Lead generation channel analysis"
|
||||
msgstr ""
|
||||
msgstr "Analiza kanala generisanja potencijalnih klijenata"
|
||||
|
||||
#: crm/api/dashboard.py:729
|
||||
msgid "Lead to deal conversion pipeline"
|
||||
msgstr ""
|
||||
msgstr "Konverzija potencijalnih klijenata u poslove"
|
||||
|
||||
#: frontend/src/pages/Lead.vue:395 frontend/src/pages/MobileLead.vue:282
|
||||
msgid "Lead updated successfully"
|
||||
msgstr ""
|
||||
msgstr "Potencijalni klijent uspješno ažuriran"
|
||||
|
||||
#. Label of a shortcut in the Frappe CRM Workspace
|
||||
#: crm/fcrm/workspace/frappe_crm/frappe_crm.json
|
||||
@ -2823,7 +2823,7 @@ msgstr "Potencijalni Klijenti"
|
||||
#: crm/api/dashboard.py:934
|
||||
#: frontend/src/components/Dashboard/AddChartModal.vue:106
|
||||
msgid "Leads by source"
|
||||
msgstr ""
|
||||
msgstr "Potencijalni klijenti po izvoru"
|
||||
|
||||
#. Label of the lft (Int) field in DocType 'CRM Territory'
|
||||
#: crm/fcrm/doctype/crm_territory/crm_territory.json
|
||||
@ -2927,7 +2927,7 @@ msgstr "Bilješke Izgubljenog Posla"
|
||||
|
||||
#: frontend/src/components/Modals/LostReasonModal.vue:83
|
||||
msgid "Lost notes are required when lost reason is \"Other\""
|
||||
msgstr "Bilješke izgubljene posla su potrebne kada je razlog gubitka \"Ostalo\""
|
||||
msgstr "Bilješke izgubljenog posla su potrebne kada je razlog gubitka \"Ostalo\""
|
||||
|
||||
#: frontend/src/components/Modals/LostReasonModal.vue:4
|
||||
#: frontend/src/components/Modals/LostReasonModal.vue:14
|
||||
@ -2992,15 +2992,15 @@ msgstr "Postavi {0} kao standard medij za pozivanje"
|
||||
|
||||
#: frontend/src/components/Settings/General/GeneralSettings.vue:23
|
||||
msgid "Makes \"Close Date\" and \"Deal Value\" mandatory for deal value forecasting"
|
||||
msgstr ""
|
||||
msgstr "\"Datum zaključenja\" i \"Vrijednost posla\" čine obaveznim za predviđanje vrijednosti posla"
|
||||
|
||||
#: frontend/src/components/Settings/Users.vue:11
|
||||
msgid "Manage CRM users by adding or inviting them, and assign roles to control their access and permissions"
|
||||
msgstr ""
|
||||
msgstr "Upravljaj CRM korisnicima dodavanjem ili pozivanjem i dodijelite uloge za kontrolu njihovog pristupa i dozvola"
|
||||
|
||||
#: frontend/src/components/Settings/EmailAccountList.vue:11
|
||||
msgid "Manage your email accounts to send and receive emails directly from CRM. You can add multiple accounts and set one as default for incoming and outgoing emails."
|
||||
msgstr ""
|
||||
msgstr "Upravljaj računima e-pošte kako biste slali i primali e-poštu direktno iz CRM-a. Možete dodati više računa i postaviti jedan kao standard za dolazne i odlazne e-pošte."
|
||||
|
||||
#: frontend/src/components/Modals/AddExistingUserModal.vue:91
|
||||
#: frontend/src/components/Settings/InviteUserPage.vue:171
|
||||
@ -3014,7 +3014,7 @@ msgstr "Upravitelj"
|
||||
|
||||
#: frontend/src/data/document.js:34
|
||||
msgid "Mandatory field error: {0}"
|
||||
msgstr ""
|
||||
msgstr "Greška obaveznog polja: {0}"
|
||||
|
||||
#. Option for the 'Telephony Medium' (Select) field in DocType 'CRM Call Log'
|
||||
#: crm/fcrm/doctype/crm_call_log/crm_call_log.json
|
||||
@ -3080,7 +3080,7 @@ msgstr "Nedostaje broj mobilnog telefona"
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:606
|
||||
msgid "Mobile app installation"
|
||||
msgstr ""
|
||||
msgstr "Instalacija mobilne aplikacije"
|
||||
|
||||
#: frontend/src/pages/Contact.vue:528 frontend/src/pages/MobileContact.vue:526
|
||||
#: frontend/src/pages/MobileOrganization.vue:470
|
||||
@ -3117,7 +3117,7 @@ msgstr "Pređi na prethodnu karticu"
|
||||
|
||||
#: frontend/src/components/Modals/ViewModal.vue:40
|
||||
msgid "My Open Deals"
|
||||
msgstr "Moji Otvoreni Dogovori"
|
||||
msgstr "Moji Otvoreni Poslovi"
|
||||
|
||||
#. Label of the title (Data) field in DocType 'CRM Dashboard'
|
||||
#. Label of the name1 (Data) field in DocType 'CRM Dropdown Item'
|
||||
@ -3419,15 +3419,15 @@ msgstr "Nije dozvoljeno dodavanje kontakta u ponudu"
|
||||
|
||||
#: crm/fcrm/doctype/crm_lead/crm_lead.py:408
|
||||
msgid "Not allowed to convert Lead to Deal"
|
||||
msgstr "Nije dozvoljeno pretvaranje Potencijalnog Klijenta u Dogovor"
|
||||
msgstr "Nije dozvoljeno pretvaranje Potencijalnog Klijenta u Posao"
|
||||
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.py:273
|
||||
msgid "Not allowed to remove contact from Deal"
|
||||
msgstr "Nije dozvoljeno uklanjanje kontakta iz Dogovora"
|
||||
msgstr "Nije dozvoljeno uklanjanje kontakta iz Posla"
|
||||
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.py:284
|
||||
msgid "Not allowed to set primary contact for Deal"
|
||||
msgstr "Nije dozvoljeno postavljanje primarnog kontakta za Dogovor"
|
||||
msgstr "Nije dozvoljeno postavljanje primarnog kontakta za Posao"
|
||||
|
||||
#: frontend/src/pages/Deal.vue:458 frontend/src/pages/Lead.vue:362
|
||||
msgid "Not permitted"
|
||||
@ -3532,7 +3532,7 @@ msgstr "Otvori"
|
||||
#: frontend/src/components/Modals/NoteModal.vue:18
|
||||
#: frontend/src/components/Modals/TaskModal.vue:25
|
||||
msgid "Open Deal"
|
||||
msgstr "Otvori Dogovor"
|
||||
msgstr "Otvori Posao"
|
||||
|
||||
#: frontend/src/components/Modals/NoteModal.vue:19
|
||||
#: frontend/src/components/Modals/TaskModal.vue:26
|
||||
@ -5176,7 +5176,7 @@ msgstr "Širina može biti u broju, pikselima ili remima (npr. 3, 30px, 10rem)"
|
||||
#. Option for the 'Type' (Select) field in DocType 'CRM Deal Status'
|
||||
#: crm/fcrm/doctype/crm_deal_status/crm_deal_status.json
|
||||
msgid "Won"
|
||||
msgstr "Dobiven"
|
||||
msgstr "Dobiveni"
|
||||
|
||||
#: crm/api/dashboard.py:296
|
||||
#: frontend/src/components/Dashboard/AddChartModal.vue:79
|
||||
@ -5349,7 +5349,7 @@ msgstr "za {0} dana"
|
||||
|
||||
#: frontend/src/utils/index.js:101
|
||||
msgid "in {0} h"
|
||||
msgstr "za {0} h"
|
||||
msgstr "za {0} s"
|
||||
|
||||
#: frontend/src/utils/index.js:148
|
||||
msgid "in {0} hours"
|
||||
|
||||
538
crm/locale/hr.po
538
crm/locale/hr.po
File diff suppressed because it is too large
Load Diff
1930
crm/locale/id.po
1930
crm/locale/id.po
File diff suppressed because it is too large
Load Diff
@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Frappe CRM VERSION\n"
|
||||
"Report-Msgid-Bugs-To: shariq@frappe.io\n"
|
||||
"POT-Creation-Date: 2025-07-20 09:37+0000\n"
|
||||
"PO-Revision-Date: 2025-07-20 09:37+0000\n"
|
||||
"POT-Creation-Date: 2025-07-27 09:38+0000\n"
|
||||
"PO-Revision-Date: 2025-07-27 09:38+0000\n"
|
||||
"Last-Translator: shariq@frappe.io\n"
|
||||
"Language-Team: shariq@frappe.io\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -964,10 +964,6 @@ msgstr ""
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.py:163
|
||||
msgid "Close Date is required."
|
||||
msgstr ""
|
||||
|
||||
#. Label of the closed_date (Date) field in DocType 'CRM Deal'
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.json
|
||||
msgid "Closed Date"
|
||||
@ -1226,12 +1222,6 @@ msgstr ""
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Activities/Activities.vue:795
|
||||
#: frontend/src/components/Activities/ActivityHeader.vue:137
|
||||
#: frontend/src/components/Activities/ActivityHeader.vue:180
|
||||
msgid "Create Call Log"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/DealModal.vue:8
|
||||
msgid "Create Deal"
|
||||
msgstr ""
|
||||
@ -1386,10 +1376,6 @@ msgstr ""
|
||||
msgid "Deal Value"
|
||||
msgstr ""
|
||||
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.py:161
|
||||
msgid "Deal Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: crm/api/dashboard.py:977
|
||||
msgid "Deal generation channel analysis"
|
||||
msgstr ""
|
||||
@ -1660,7 +1646,7 @@ msgstr ""
|
||||
msgid "Document not found"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/data/document.js:23
|
||||
#: frontend/src/data/document.js:24
|
||||
msgid "Document updated successfully"
|
||||
msgstr ""
|
||||
|
||||
@ -1975,7 +1961,7 @@ msgstr ""
|
||||
msgid "Error updating deal: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/data/document.js:26
|
||||
#: frontend/src/data/document.js:27
|
||||
msgid "Error updating document"
|
||||
msgstr ""
|
||||
|
||||
@ -2053,11 +2039,19 @@ msgstr ""
|
||||
msgid "Expected Closure Date"
|
||||
msgstr ""
|
||||
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.py:163
|
||||
msgid "Expected Closure Date is required."
|
||||
msgstr ""
|
||||
|
||||
#. Label of the expected_deal_value (Currency) field in DocType 'CRM Deal'
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.json
|
||||
msgid "Expected Deal Value"
|
||||
msgstr ""
|
||||
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.py:161
|
||||
msgid "Expected Deal Value is required."
|
||||
msgstr ""
|
||||
|
||||
#. Option for the 'Status' (Select) field in DocType 'CRM Invitation'
|
||||
#: crm/fcrm/doctype/crm_invitation/crm_invitation.json
|
||||
msgid "Expired"
|
||||
@ -2115,10 +2109,6 @@ msgstr ""
|
||||
msgid "Failed to delete template"
|
||||
msgstr ""
|
||||
|
||||
#: crm/utils/__init__.py:285
|
||||
msgid "Failed to fetch historical exchange rate from external API. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/data/script.js:110
|
||||
msgid "Failed to load form controller: {0}"
|
||||
msgstr ""
|
||||
@ -2887,6 +2877,12 @@ msgstr ""
|
||||
msgid "Log"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Activities/Activities.vue:795
|
||||
#: frontend/src/components/Activities/ActivityHeader.vue:137
|
||||
#: frontend/src/components/Activities/ActivityHeader.vue:180
|
||||
msgid "Log a Call"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/composables/frappecloud.js:23
|
||||
msgid "Login to Frappe Cloud?"
|
||||
msgstr ""
|
||||
@ -3010,7 +3006,7 @@ msgstr ""
|
||||
msgid "Manager"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/data/document.js:34
|
||||
#: frontend/src/data/document.js:35
|
||||
msgid "Mandatory field error: {0}"
|
||||
msgstr ""
|
||||
|
||||
|
||||
416
crm/locale/sr.po
416
crm/locale/sr.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: shariq@frappe.io\n"
|
||||
"POT-Creation-Date: 2025-07-20 09:37+0000\n"
|
||||
"PO-Revision-Date: 2025-07-23 05:28\n"
|
||||
"PO-Revision-Date: 2025-07-26 06:34\n"
|
||||
"Last-Translator: shariq@frappe.io\n"
|
||||
"Language-Team: Swedish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -5176,7 +5176,7 @@ msgstr "Bredd kan anges i antal, pixel eller rem (t.ex. 3, 30px, 10rem)"
|
||||
#. Option for the 'Type' (Select) field in DocType 'CRM Deal Status'
|
||||
#: crm/fcrm/doctype/crm_deal_status/crm_deal_status.json
|
||||
msgid "Won"
|
||||
msgstr "Vann"
|
||||
msgstr "Vunnen"
|
||||
|
||||
#: crm/api/dashboard.py:296
|
||||
#: frontend/src/components/Dashboard/AddChartModal.vue:79
|
||||
|
||||
@ -267,24 +267,3 @@ def sales_user_only(fn):
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
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']
|
||||
CreateDocumentModal: typeof import('./src/components/Modals/CreateDocumentModal.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']
|
||||
DashboardGrid: typeof import('./src/components/Dashboard/DashboardGrid.vue')['default']
|
||||
DashboardIcon: typeof import('./src/components/Icons/DashboardIcon.vue')['default']
|
||||
|
||||
@ -792,7 +792,7 @@ function scroll(hash) {
|
||||
const callActions = computed(() => {
|
||||
let actions = [
|
||||
{
|
||||
label: __('Create Call Log'),
|
||||
label: __('Log a Call'),
|
||||
onClick: () => modalRef.value.createCallLog(),
|
||||
},
|
||||
{
|
||||
|
||||
@ -134,7 +134,7 @@ const defaultActions = computed(() => {
|
||||
},
|
||||
{
|
||||
icon: h(PhoneIcon, { class: 'h-4 w-4' }),
|
||||
label: __('Create Call Log'),
|
||||
label: __('Log a Call'),
|
||||
onClick: () => props.modalRef.createCallLog(),
|
||||
},
|
||||
{
|
||||
@ -177,7 +177,7 @@ function getTabIndex(name) {
|
||||
const callActions = computed(() => {
|
||||
let actions = [
|
||||
{
|
||||
label: __('Create Call Log'),
|
||||
label: __('Log a Call'),
|
||||
icon: 'plus',
|
||||
onClick: () => props.modalRef.createCallLog(),
|
||||
},
|
||||
|
||||
@ -90,12 +90,12 @@ const { document: _contact, triggerOnBeforeCreate } = useDocument('Contact')
|
||||
|
||||
async function createContact() {
|
||||
if (_contact.doc.email_id) {
|
||||
_contact.doc.email_ids = [{ email_id: _contact.doc.email_id }]
|
||||
_contact.doc.email_ids = [{ email_id: _contact.doc.email_id, is_primary: 1 }]
|
||||
delete _contact.doc.email_id
|
||||
}
|
||||
|
||||
if (_contact.doc.mobile_no) {
|
||||
_contact.doc.phone_nos = [{ phone: _contact.doc.mobile_no }]
|
||||
_contact.doc.phone_nos = [{ phone: _contact.doc.mobile_no, is_primary_mobile_no: 1 }]
|
||||
delete _contact.doc.mobile_no
|
||||
}
|
||||
|
||||
|
||||
@ -25,25 +25,22 @@
|
||||
</template>
|
||||
</Button>
|
||||
<Dropdown
|
||||
v-show="showDropdown"
|
||||
v-if="showDropdown"
|
||||
:options="parsedOptions"
|
||||
size="sm"
|
||||
class="flex-1 [&>div>div>div]:w-full"
|
||||
placement="right"
|
||||
>
|
||||
<template v-slot="{ togglePopover }">
|
||||
<Button
|
||||
:variant="$attrs.variant"
|
||||
@click="togglePopover"
|
||||
icon="chevron-down"
|
||||
class="!w-6 justify-start rounded-bl-none rounded-tl-none border-0 pr-0 text-xs"
|
||||
/>
|
||||
</template>
|
||||
</Dropdown>
|
||||
:button="{
|
||||
icon: 'chevron-down',
|
||||
variant: $attrs.variant,
|
||||
size: $attrs.size,
|
||||
class:
|
||||
'!w-6 justify-start rounded-bl-none rounded-tl-none border-0 pr-0 text-xs',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Dropdown } from 'frappe-ui'
|
||||
import { DropdownOption } from '@/utils'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
@ -57,13 +54,18 @@ const showDropdown = ref(props.options?.length > 1)
|
||||
const activeButton = ref(props.options?.[0] || {})
|
||||
|
||||
const parsedOptions = computed(() => {
|
||||
debugger
|
||||
return (
|
||||
props.options?.map((option) => {
|
||||
return {
|
||||
label: option.label,
|
||||
onClick: () => {
|
||||
activeButton.value = option
|
||||
},
|
||||
component: (props) =>
|
||||
DropdownOption({
|
||||
option: option.label,
|
||||
active: props.active,
|
||||
selected: option.label === activeButton.value.label,
|
||||
onClick: () => (activeButton.value = option),
|
||||
}),
|
||||
}
|
||||
}) || []
|
||||
)
|
||||
|
||||
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">
|
||||
{{ __('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">
|
||||
{{
|
||||
__(
|
||||
'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>
|
||||
@ -35,37 +35,6 @@
|
||||
</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 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">
|
||||
<li
|
||||
class="flex items-center justify-between p-3 cursor-pointer hover:bg-surface-menu-bar rounded"
|
||||
@ -93,17 +62,20 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import { getSettings } from '@/stores/settings'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { Switch, toast } from 'frappe-ui'
|
||||
|
||||
const emit = defineEmits(['updateStep'])
|
||||
|
||||
const { _settings: settings } = getSettings()
|
||||
const { $dialog } = globalStore()
|
||||
|
||||
const settingsList = [
|
||||
{
|
||||
name: 'currency-settings',
|
||||
label: 'Currency & Exchange rate provider',
|
||||
description:
|
||||
'Configure the currency and exchange rate provider for your CRM',
|
||||
},
|
||||
{
|
||||
name: '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>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
<script setup>
|
||||
import GeneralSettings from './GeneralSettings.vue'
|
||||
import CurrencySettings from './CurrencySettings.vue'
|
||||
import BrandSettings from './BrandSettings.vue'
|
||||
import HomeActions from './HomeActions.vue'
|
||||
import { ref } from 'vue'
|
||||
@ -20,6 +21,8 @@ function getComponent(step) {
|
||||
switch (step) {
|
||||
case 'general-settings':
|
||||
return GeneralSettings
|
||||
case 'currency-settings':
|
||||
return CurrencySettings
|
||||
case 'brand-settings':
|
||||
return BrandSettings
|
||||
case 'home-actions':
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { getScript } from '@/data/script'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { showSettings, activeSettingsPage } from '@/composables/settings'
|
||||
import { runSequentially, parseAssignees } from '@/utils'
|
||||
import { createDocumentResource, createResource, toast } from 'frappe-ui'
|
||||
import { reactive } from 'vue'
|
||||
@ -24,7 +26,8 @@ export function useDocument(doctype, docname) {
|
||||
toast.success(__('Document updated successfully'))
|
||||
},
|
||||
onError: (err) => {
|
||||
let errorMessage = __('Error updating document')
|
||||
triggerOnError(err)
|
||||
|
||||
if (err.exc_type == 'MandatoryError') {
|
||||
const fieldName = err.messages
|
||||
.map((msg) => {
|
||||
@ -32,9 +35,18 @@ export function useDocument(doctype, docname) {
|
||||
return arr[arr.length - 1].trim()
|
||||
})
|
||||
.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)
|
||||
},
|
||||
},
|
||||
@ -76,8 +88,21 @@ export function useDocument(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(
|
||||
documentsCache[doctype][docname || ''],
|
||||
helpers,
|
||||
)
|
||||
|
||||
if (!controllersArray || controllersArray.length === 0) return
|
||||
@ -133,6 +158,13 @@ export function useDocument(doctype, docname) {
|
||||
await trigger(handler)
|
||||
}
|
||||
|
||||
async function triggerOnError() {
|
||||
const handler = async function () {
|
||||
await (this.onError?.() || this.on_error?.())
|
||||
}
|
||||
await trigger(handler)
|
||||
}
|
||||
|
||||
async function triggerOnRefresh() {
|
||||
const handler = async function () {
|
||||
await this.refresh?.()
|
||||
@ -234,6 +266,7 @@ export function useDocument(doctype, docname) {
|
||||
triggerOnLoad,
|
||||
triggerOnBeforeCreate,
|
||||
triggerOnSave,
|
||||
triggerOnError,
|
||||
triggerOnRefresh,
|
||||
triggerOnChange,
|
||||
triggerOnRowAdd,
|
||||
|
||||
@ -38,7 +38,7 @@ export function getScript(doctype, view = 'Form') {
|
||||
let scriptDefs = doctypeScripts[doctype]
|
||||
if (!scriptDefs || Object.keys(scriptDefs).length === 0) return null
|
||||
|
||||
const { $dialog, $socket, makeCall } = globalStore()
|
||||
const { $dialog, $socket } = globalStore()
|
||||
|
||||
helpers.createDialog = $dialog
|
||||
helpers.toast = toast
|
||||
@ -51,10 +51,6 @@ export function getScript(doctype, view = 'Form') {
|
||||
throw new Error(message || __('An error occurred'))
|
||||
}
|
||||
|
||||
helpers.crm = {
|
||||
makePhoneCall: makeCall,
|
||||
}
|
||||
|
||||
return setupMultipleFormControllers(scriptDefs, document, helpers)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user