diff --git a/crm/api/__init__.py b/crm/api/__init__.py index a5b1d846..cd92264b 100644 --- a/crm/api/__init__.py +++ b/crm/api/__init__.py @@ -71,7 +71,7 @@ def check_app_permission(): roles = frappe.get_roles() if any( - role in ["System Manager", "Sales User", "Sales Manager", "Sales Master Manager"] for role in roles + role in ["System Manager", "Sales User", "Sales Manager"] for role in roles ): return True @@ -99,7 +99,11 @@ def accept_invitation(key: str | None = None): @frappe.whitelist() def invite_by_email(emails: str, role: str): - frappe.only_for("Sales Manager") + frappe.only_for(["Sales Manager", "System Manager"]) + + if role not in ["System Manager", "Sales Manager", "Sales User"]: + frappe.throw("Cannot invite for this role") + if not emails: return email_string = validate_email_address(emails, throw=False) @@ -109,7 +113,10 @@ def invite_by_email(emails: str, role: str): existing_members = frappe.db.get_all("User", filters={"email": ["in", email_list]}, pluck="email") existing_invites = frappe.db.get_all( "CRM Invitation", - filters={"email": ["in", email_list], "role": ["in", ["Sales Manager", "Sales User"]]}, + filters={ + "email": ["in", email_list], + "role": ["in", ["System Manager", "Sales Manager", "Sales User"]], + }, pluck="email", ) diff --git a/crm/api/session.py b/crm/api/session.py index 3c12947c..add14c37 100644 --- a/crm/api/session.py +++ b/crm/api/session.py @@ -23,11 +23,35 @@ def get_users(): if frappe.session.user == user.name: user.session_user = True - user.is_manager = "Sales Manager" in frappe.get_roles(user.name) or user.name == "Administrator" + user.is_manager = "Sales Manager" in frappe.get_roles(user.name) + user.is_admin = user.name == "Administrator" + + user.roles = frappe.get_roles(user.name) + + user.role = "" + + if "System Manager" in user.roles: + user.role = "System Manager" + elif "Sales Manager" in user.roles: + user.role = "Sales Manager" + elif "Sales User" in user.roles: + user.role = "Sales User" + elif "Guest" in user.roles: + user.role = "Guest" + + if frappe.session.user == user.name: + user.session_user = True user.is_agent = frappe.db.exists("CRM Telephony Agent", {"user": user.name}) - return users + crm_users = [] + + # crm users are users with role Sales User or Sales Manager + for user in users: + if "Sales User" in user.roles or "Sales Manager" in user.roles: + crm_users.append(user) + + return users, crm_users @frappe.whitelist() diff --git a/crm/api/user.py b/crm/api/user.py new file mode 100644 index 00000000..caae05f3 --- /dev/null +++ b/crm/api/user.py @@ -0,0 +1,84 @@ +import frappe + + +@frappe.whitelist() +def add_existing_users(users, role="Sales User"): + """ + Add existing users to the CRM by assigning them a role (Sales User or Sales Manager). + :param users: List of user names to be added + """ + frappe.only_for(["System Manager", "Sales Manager"]) + users = frappe.parse_json(users) + + for user in users: + add_user(user, role) + + +@frappe.whitelist() +def update_user_role(user, new_role): + """ + Update the role of the user to Sales Manager, Sales User, or System Manager. + :param user: The name of the user + :param new_role: The new role to assign (Sales Manager or Sales User) + """ + + frappe.only_for(["System Manager", "Sales Manager"]) + + if new_role not in ["System Manager", "Sales Manager", "Sales User"]: + frappe.throw("Cannot assign this role") + + user_doc = frappe.get_doc("User", user) + + if new_role == "System Manager": + user_doc.append_roles("System Manager", "Sales Manager", "Sales User") + user_doc.set("block_modules", []) + if new_role == "Sales Manager": + user_doc.append_roles("Sales Manager", "Sales User") + user_doc.remove_roles("System Manager") + if new_role == "Sales User": + user_doc.append_roles("Sales User") + user_doc.remove_roles("Sales Manager", "System Manager") + update_module_in_user(user_doc, "FCRM") + + user_doc.save(ignore_permissions=True) + + +@frappe.whitelist() +def add_user(user, role): + """ + Add a user means adding role (Sales User or/and Sales Manager) to the user. + :param user: The name of the user to be added + :param role: The role to be assigned (Sales User or Sales Manager) + """ + update_user_role(user, role) + + +@frappe.whitelist() +def remove_user(user): + """ + Remove a user means removing Sales User & Sales Manager roles from the user. + :param user: The name of the user to be removed + """ + frappe.only_for(["System Manager", "Sales Manager"]) + + user_doc = frappe.get_doc("User", user) + roles = [d.role for d in user_doc.roles] + + if "Sales User" in roles: + user_doc.remove_roles("Sales User") + if "Sales Manager" in roles: + user_doc.remove_roles("Sales Manager") + + user_doc.save(ignore_permissions=True) + frappe.msgprint(f"User {user} has been removed from CRM roles.") + + +def update_module_in_user(user, module): + block_modules = frappe.get_all( + "Module Def", + fields=["name as module"], + filters={"name": ["!=", module]}, + ) + + if block_modules: + user.set("block_modules", block_modules) diff --git a/crm/api/whatsapp.py b/crm/api/whatsapp.py index 2a6c698c..3e3889be 100644 --- a/crm/api/whatsapp.py +++ b/crm/api/whatsapp.py @@ -335,5 +335,5 @@ def get_from_name(message): else: from_name = doc.get("lead_name") else: - from_name = doc.get("first_name") + " " + doc.get("last_name") + from_name = " ".join(filter(None, [doc.get("first_name"), doc.get("last_name")])) return from_name diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json index 67f441c3..bbef186d 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.json +++ b/crm/fcrm/doctype/crm_deal/crm_deal.json @@ -11,11 +11,14 @@ "naming_series", "organization", "next_step", - "probability", "column_break_ijan", "status", - "close_date", "deal_owner", + "section_break_jgpm", + "probability", + "deal_value", + "column_break_kpxa", + "close_date", "contacts_tab", "contacts", "contact", @@ -374,12 +377,26 @@ "label": "Net Total", "options": "currency", "read_only": 1 + }, + { + "fieldname": "section_break_jgpm", + "fieldtype": "Section Break" + }, + { + "fieldname": "deal_value", + "fieldtype": "Currency", + "label": "Deal Value", + "options": "currency" + }, + { + "fieldname": "column_break_kpxa", + "fieldtype": "Column Break" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-05-12 12:30:55.415282", + "modified": "2025-06-16 11:42:49.413483", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Deal", diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py index b650301e..b7e4ec83 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.py +++ b/crm/fcrm/doctype/crm_deal/crm_deal.py @@ -24,6 +24,7 @@ class CRMDeal(Document): self.assign_agent(self.deal_owner) if self.has_value_changed("status"): add_status_change_log(self) + self.update_close_date() def after_insert(self): if self.deal_owner: @@ -133,6 +134,13 @@ class CRMDeal(Document): if sla: sla.apply(self) + def update_close_date(self): + """ + Update the close date based on the "Won" status. + """ + if self.status == "Won" and not self.close_date: + self.close_date = frappe.utils.nowdate() + @staticmethod def default_list_data(): columns = [ diff --git a/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json b/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json index b0374ca1..ae026c74 100644 --- a/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json +++ b/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json @@ -8,7 +8,8 @@ "field_order": [ "deal_status", "color", - "position" + "position", + "probability" ], "fields": [ { @@ -32,11 +33,17 @@ "fieldtype": "Int", "in_list_view": 1, "label": "Position" + }, + { + "fieldname": "probability", + "fieldtype": "Percent", + "label": "Probability" } ], + "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-01-19 21:56:44.552134", + "modified": "2025-06-11 13:00:34.518808", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Deal Status", @@ -68,7 +75,8 @@ "write": 1 } ], + "row_format": "Dynamic", "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/crm/fcrm/doctype/crm_invitation/crm_invitation.json b/crm/fcrm/doctype/crm_invitation/crm_invitation.json index f5902d6e..826a2b33 100644 --- a/crm/fcrm/doctype/crm_invitation/crm_invitation.json +++ b/crm/fcrm/doctype/crm_invitation/crm_invitation.json @@ -27,7 +27,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Role", - "options": "\nSales User\nSales Manager", + "options": "\nSales User\nSales Manager\nSystem Manager", "reqd": 1 }, { @@ -66,7 +66,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-09-03 14:59:29.450018", + "modified": "2025-06-17 17:20:18.935395", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Invitation", @@ -106,7 +106,8 @@ "share": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/crm/fcrm/doctype/crm_invitation/crm_invitation.py b/crm/fcrm/doctype/crm_invitation/crm_invitation.py index 625cd882..965bf4d7 100644 --- a/crm/fcrm/doctype/crm_invitation/crm_invitation.py +++ b/crm/fcrm/doctype/crm_invitation/crm_invitation.py @@ -35,7 +35,7 @@ class CRMInvitation(Document): @frappe.whitelist() def accept_invitation(self): - frappe.only_for("System Manager") + frappe.only_for(["System Manager", "Sales Manager"]) self.accept() def accept(self): diff --git a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json index 250c8c29..de27e245 100644 --- a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json +++ b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json @@ -7,6 +7,7 @@ "field_order": [ "defaults_tab", "restore_defaults", + "enable_forecasting", "branding_tab", "brand_name", "brand_logo", @@ -28,7 +29,7 @@ { "fieldname": "defaults_tab", "fieldtype": "Tab Break", - "label": "Defaults" + "label": "Settings" }, { "fieldname": "branding_tab", @@ -56,12 +57,19 @@ "fieldname": "favicon", "fieldtype": "Attach", "label": "Favicon" + }, + { + "default": "0", + "description": "It will make deal's \"Expected Closure Date\" mandatory to get accurate forecasting insights", + "fieldname": "enable_forecasting", + "fieldtype": "Check", + "label": "Enable Forecasting" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-02-20 12:38:38.088477", + "modified": "2025-06-11 19:12:16.762499", "modified_by": "Administrator", "module": "FCRM", "name": "FCRM Settings", @@ -95,7 +103,8 @@ "share": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py index 53c8c77c..f22114f1 100644 --- a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py +++ b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py @@ -3,6 +3,7 @@ import frappe from frappe import _ +from frappe.custom.doctype.property_setter.property_setter import delete_property_setter, make_property_setter from frappe.model.document import Document from crm.install import after_install @@ -15,6 +16,7 @@ class FCRMSettings(Document): def validate(self): self.do_not_allow_to_delete_if_standard() + self.setup_forecasting() def do_not_allow_to_delete_if_standard(self): if not self.has_value_changed("dropdown_items"): @@ -29,6 +31,23 @@ class FCRMSettings(Document): return frappe.throw(_("Cannot delete standard items {0}").format(", ".join(deleted_standard_items))) + def setup_forecasting(self): + if self.has_value_changed("enable_forecasting"): + if not self.enable_forecasting: + delete_property_setter( + "CRM Deal", + "reqd", + "close_date", + ) + else: + make_property_setter( + "CRM Deal", + "close_date", + "reqd", + 1 if self.enable_forecasting else 0, + "Check", + ) + def get_standard_dropdown_items(): return [item.get("name1") for item in frappe.get_hooks("standard_dropdown_items")] @@ -57,3 +76,36 @@ def sync_table(key, hook): crm_settings.set(key, items) crm_settings.save() + + +def create_forecasting_script(): + if not frappe.db.exists("CRM Form Script", "Forecasting Script"): + script = get_forecasting_script() + frappe.get_doc( + { + "doctype": "CRM Form Script", + "name": "Forecasting Script", + "dt": "CRM Deal", + "view": "Form", + "script": script, + "enabled": 1, + "is_standard": 1, + } + ).insert() + + +def get_forecasting_script(): + return """class CRMDeal { + async status() { + await this.doc.trigger('updateProbability') + } + async updateProbability() { + let status = await call("frappe.client.get_value", { + doctype: "CRM Deal Status", + fieldname: "probability", + filters: { name: this.doc.status }, + }) + + this.doc.probability = status.probability + } +}""" diff --git a/crm/install.py b/crm/install.py index 5f25e7d0..5ffa5a67 100644 --- a/crm/install.py +++ b/crm/install.py @@ -359,5 +359,8 @@ def add_standard_dropdown_items(): def add_default_scripts(): + from crm.fcrm.doctype.fcrm_settings.fcrm_settings import create_forecasting_script + for doctype in ["CRM Lead", "CRM Deal"]: create_product_details_script(doctype) + create_forecasting_script() diff --git a/crm/locale/main.pot b/crm/locale/main.pot index 149bccdf..cd121a1b 100644 --- a/crm/locale/main.pot +++ b/crm/locale/main.pot @@ -7,14 +7,14 @@ msgid "" msgstr "" "Project-Id-Version: Frappe CRM VERSION\n" "Report-Msgid-Bugs-To: shariq@frappe.io\n" -"POT-Creation-Date: 2025-06-08 09:36+0000\n" -"PO-Revision-Date: 2025-06-08 09:36+0000\n" +"POT-Creation-Date: 2025-06-22 09:36+0000\n" +"PO-Revision-Date: 2025-06-22 09:36+0000\n" "Last-Translator: shariq@frappe.io\n" "Language-Team: shariq@frappe.io\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.13.1\n" +"Generated-By: Babel 2.16.0\n" #: frontend/src/components/ViewControls.vue:1217 msgid " (New)" @@ -25,23 +25,23 @@ msgstr "" msgid "01/04/2024 11:30 PM" msgstr "" -#: frontend/src/utils/index.js:122 +#: frontend/src/utils/index.js:170 msgid "1 hour ago" msgstr "" -#: frontend/src/utils/index.js:118 +#: frontend/src/utils/index.js:166 msgid "1 minute ago" msgstr "" -#: frontend/src/utils/index.js:136 +#: frontend/src/utils/index.js:184 msgid "1 month ago" msgstr "" -#: frontend/src/utils/index.js:132 +#: frontend/src/utils/index.js:180 msgid "1 week ago" msgstr "" -#: frontend/src/utils/index.js:140 +#: frontend/src/utils/index.js:188 msgid "1 year ago" msgstr "" @@ -196,8 +196,8 @@ msgstr "" msgid "Actions" msgstr "" -#: frontend/src/pages/Deal.vue:533 frontend/src/pages/Lead.vue:517 -#: frontend/src/pages/MobileDeal.vue:436 frontend/src/pages/MobileLead.vue:337 +#: frontend/src/pages/Deal.vue:552 frontend/src/pages/Lead.vue:541 +#: frontend/src/pages/MobileDeal.vue:443 frontend/src/pages/MobileLead.vue:344 msgid "Activity" msgstr "" @@ -205,7 +205,7 @@ msgstr "" msgid "Add Account" msgstr "" -#: frontend/src/components/ColumnSettings.vue:65 +#: frontend/src/components/ColumnSettings.vue:69 #: frontend/src/components/Kanban/KanbanView.vue:157 msgid "Add Column" msgstr "" @@ -213,7 +213,7 @@ msgstr "" #: frontend/src/components/Controls/GridFieldsEditorModal.vue:58 #: frontend/src/components/FieldLayoutEditor.vue:173 #: frontend/src/components/Kanban/KanbanSettings.vue:84 -#: frontend/src/components/SidePanelLayoutEditor.vue:96 +#: frontend/src/components/SidePanelLayoutEditor.vue:98 msgid "Add Field" msgstr "" @@ -221,12 +221,12 @@ msgstr "" msgid "Add Filter" msgstr "" -#: frontend/src/components/Controls/Grid.vue:317 +#: frontend/src/components/Controls/Grid.vue:321 msgid "Add Row" msgstr "" #: frontend/src/components/FieldLayoutEditor.vue:200 -#: frontend/src/components/SidePanelLayoutEditor.vue:128 +#: frontend/src/components/SidePanelLayoutEditor.vue:130 msgid "Add Section" msgstr "" @@ -316,6 +316,10 @@ msgstr "" msgid "Amount after discount" msgstr "" +#: frontend/src/data/script.js:50 frontend/src/data/script.js:51 +msgid "An error occurred" +msgstr "" + #. Description of the 'Favicon' (Attach) field in DocType 'FCRM Settings' #: crm/fcrm/doctype/fcrm_settings/fcrm_settings.json msgid "An icon file with .ico extension. Should be 16 x 16 px. Generated using a favicon generator. [favicon-generator.org]" @@ -339,8 +343,8 @@ msgstr "" msgid "Annual Revenue" msgstr "" -#: frontend/src/components/Modals/DealModal.vue:195 -#: frontend/src/components/Modals/LeadModal.vue:137 +#: frontend/src/components/Modals/DealModal.vue:199 +#: frontend/src/components/Modals/LeadModal.vue:141 msgid "Annual Revenue should be a number" msgstr "" @@ -437,12 +441,12 @@ msgstr "" msgid "Attach" msgstr "" -#: frontend/src/pages/Deal.vue:120 frontend/src/pages/Lead.vue:170 +#: frontend/src/pages/Deal.vue:133 frontend/src/pages/Lead.vue:183 msgid "Attach a file" msgstr "" -#: frontend/src/pages/Deal.vue:568 frontend/src/pages/Lead.vue:552 -#: frontend/src/pages/MobileDeal.vue:472 frontend/src/pages/MobileLead.vue:373 +#: frontend/src/pages/Deal.vue:587 frontend/src/pages/Lead.vue:576 +#: frontend/src/pages/MobileDeal.vue:479 frontend/src/pages/MobileLead.vue:380 msgid "Attachments" msgstr "" @@ -705,8 +709,8 @@ msgstr "" msgid "Calling..." msgstr "" -#: frontend/src/pages/Deal.vue:553 frontend/src/pages/Lead.vue:537 -#: frontend/src/pages/MobileDeal.vue:456 frontend/src/pages/MobileLead.vue:357 +#: frontend/src/pages/Deal.vue:572 frontend/src/pages/Lead.vue:561 +#: frontend/src/pages/MobileDeal.vue:463 frontend/src/pages/MobileLead.vue:364 msgid "Calls" msgstr "" @@ -714,7 +718,7 @@ msgstr "" msgid "Camera" msgstr "" -#: frontend/src/components/ColumnSettings.vue:128 +#: frontend/src/components/ColumnSettings.vue:132 #: frontend/src/components/Modals/AssignmentModal.vue:9 #: frontend/src/components/Telephony/TwilioCallUI.vue:77 #: frontend/src/components/ViewControls.vue:56 @@ -729,7 +733,7 @@ msgstr "" msgid "Canceled" msgstr "" -#: crm/fcrm/doctype/fcrm_settings/fcrm_settings.py:30 +#: crm/fcrm/doctype/fcrm_settings/fcrm_settings.py:32 msgid "Cannot delete standard items {0}" msgstr "" @@ -754,23 +758,23 @@ msgstr "" msgid "Change deal status" msgstr "" -#: frontend/src/pages/Contact.vue:41 frontend/src/pages/Lead.vue:94 +#: frontend/src/pages/Contact.vue:41 frontend/src/pages/Lead.vue:101 #: frontend/src/pages/MobileContact.vue:34 #: frontend/src/pages/MobileOrganization.vue:37 #: frontend/src/pages/Organization.vue:41 msgid "Change image" msgstr "" -#: frontend/src/pages/Lead.vue:253 frontend/src/pages/Lead.vue:279 -#: frontend/src/pages/MobileLead.vue:115 frontend/src/pages/MobileLead.vue:142 +#: frontend/src/pages/Lead.vue:272 frontend/src/pages/Lead.vue:298 +#: frontend/src/pages/MobileLead.vue:122 frontend/src/pages/MobileLead.vue:149 msgid "Choose Existing" msgstr "" -#: frontend/src/components/Modals/DealModal.vue:41 +#: frontend/src/components/Modals/DealModal.vue:45 msgid "Choose Existing Contact" msgstr "" -#: frontend/src/components/Modals/DealModal.vue:34 +#: frontend/src/components/Modals/DealModal.vue:38 msgid "Choose Existing Organization" msgstr "" @@ -849,8 +853,8 @@ msgstr "" msgid "Comment" msgstr "" -#: frontend/src/pages/Deal.vue:543 frontend/src/pages/Lead.vue:527 -#: frontend/src/pages/MobileDeal.vue:446 frontend/src/pages/MobileLead.vue:347 +#: frontend/src/pages/Deal.vue:562 frontend/src/pages/Lead.vue:551 +#: frontend/src/pages/MobileDeal.vue:453 frontend/src/pages/MobileLead.vue:354 msgid "Comments" msgstr "" @@ -899,7 +903,7 @@ msgstr "" #: crm/fcrm/doctype/crm_contacts/crm_contacts.json #: crm/fcrm/doctype/crm_deal/crm_deal.json #: frontend/src/components/Layouts/AppSidebar.vue:541 -#: frontend/src/pages/Lead.vue:275 frontend/src/pages/MobileLead.vue:138 +#: frontend/src/pages/Lead.vue:294 frontend/src/pages/MobileLead.vue:145 msgid "Contact" msgstr "" @@ -907,7 +911,7 @@ msgstr "" msgid "Contact Already Exists" msgstr "" -#: frontend/src/components/Modals/AboutModal.vue:93 +#: frontend/src/components/Modals/AboutModal.vue:77 msgid "Contact Support" msgstr "" @@ -915,11 +919,11 @@ msgstr "" msgid "Contact Us" msgstr "" -#: frontend/src/pages/Deal.vue:647 frontend/src/pages/MobileDeal.vue:550 +#: frontend/src/pages/Deal.vue:666 frontend/src/pages/MobileDeal.vue:557 msgid "Contact added" msgstr "" -#: frontend/src/pages/Deal.vue:637 frontend/src/pages/MobileDeal.vue:540 +#: frontend/src/pages/Deal.vue:656 frontend/src/pages/MobileDeal.vue:547 msgid "Contact already added" msgstr "" @@ -931,7 +935,7 @@ msgstr "" msgid "Contact not found" msgstr "" -#: frontend/src/pages/Deal.vue:658 frontend/src/pages/MobileDeal.vue:561 +#: frontend/src/pages/Deal.vue:677 frontend/src/pages/MobileDeal.vue:568 msgid "Contact removed" msgstr "" @@ -968,8 +972,8 @@ msgstr "" #: frontend/src/components/Layouts/AppSidebar.vue:369 #: frontend/src/components/ListBulkActions.vue:70 -#: frontend/src/pages/Lead.vue:213 frontend/src/pages/MobileLead.vue:54 -#: frontend/src/pages/MobileLead.vue:101 +#: frontend/src/pages/Lead.vue:228 frontend/src/pages/MobileLead.vue:61 +#: frontend/src/pages/MobileLead.vue:108 msgid "Convert" msgstr "" @@ -980,8 +984,8 @@ msgstr "" #: frontend/src/components/ListBulkActions.vue:62 #: frontend/src/components/ListBulkActions.vue:192 -#: frontend/src/pages/Lead.vue:45 frontend/src/pages/Lead.vue:224 -#: frontend/src/pages/MobileLead.vue:97 +#: frontend/src/pages/Lead.vue:52 frontend/src/pages/Lead.vue:239 +#: frontend/src/pages/MobileLead.vue:104 msgid "Convert to Deal" msgstr "" @@ -994,19 +998,19 @@ msgstr "" msgid "Converted successfully" msgstr "" -#: frontend/src/utils/index.js:294 +#: frontend/src/utils/index.js:342 msgid "Copied to clipboard" msgstr "" -#: frontend/src/components/Modals/AddressModal.vue:92 -#: frontend/src/components/Modals/CallLogModal.vue:94 -#: frontend/src/components/Modals/ContactModal.vue:37 -#: frontend/src/components/Modals/CreateDocumentModal.vue:88 -#: frontend/src/components/Modals/DealModal.vue:63 +#: frontend/src/components/Modals/AddressModal.vue:96 +#: frontend/src/components/Modals/CallLogModal.vue:98 +#: frontend/src/components/Modals/ContactModal.vue:41 +#: frontend/src/components/Modals/CreateDocumentModal.vue:92 +#: frontend/src/components/Modals/DealModal.vue:67 #: frontend/src/components/Modals/EmailTemplateModal.vue:9 -#: frontend/src/components/Modals/LeadModal.vue:34 +#: frontend/src/components/Modals/LeadModal.vue:38 #: frontend/src/components/Modals/NoteModal.vue:6 -#: frontend/src/components/Modals/OrganizationModal.vue:38 +#: frontend/src/components/Modals/OrganizationModal.vue:42 #: frontend/src/components/Modals/TaskModal.vue:6 #: frontend/src/components/Modals/ViewModal.vue:16 #: frontend/src/pages/CallLogs.vue:11 frontend/src/pages/Contacts.vue:13 @@ -1041,7 +1045,7 @@ msgstr "" #: frontend/src/components/Controls/Link.vue:48 #: frontend/src/components/Modals/EmailTemplateSelectorModal.vue:55 #: frontend/src/components/Modals/WhatsappTemplateSelectorModal.vue:45 -#: frontend/src/components/SidePanelLayout.vue:135 +#: frontend/src/components/SidePanelLayout.vue:140 msgid "Create New" msgstr "" @@ -1066,7 +1070,7 @@ msgstr "" msgid "Create customer on status change" msgstr "" -#: frontend/src/components/Modals/CallLogDetailModal.vue:148 +#: frontend/src/components/Modals/CallLogDetailModal.vue:152 msgid "Create lead" msgstr "" @@ -1109,7 +1113,7 @@ msgstr "" msgid "Custom statuses" msgstr "" -#: frontend/src/pages/Deal.vue:438 +#: frontend/src/pages/Deal.vue:457 msgid "Customer created successfully" msgstr "" @@ -1123,8 +1127,8 @@ msgstr "" #: frontend/src/components/Activities/DataFields.vue:6 #: frontend/src/components/Layouts/AppSidebar.vue:569 -#: frontend/src/pages/Deal.vue:548 frontend/src/pages/Lead.vue:532 -#: frontend/src/pages/MobileDeal.vue:451 frontend/src/pages/MobileLead.vue:352 +#: frontend/src/pages/Deal.vue:567 frontend/src/pages/Lead.vue:556 +#: frontend/src/pages/MobileDeal.vue:458 frontend/src/pages/MobileLead.vue:359 msgid "Data" msgstr "" @@ -1159,20 +1163,25 @@ msgstr "" msgid "Deal Statuses" msgstr "" +#. Label of the deal_value (Currency) field in DocType 'CRM Deal' +#: crm/fcrm/doctype/crm_deal/crm_deal.json +msgid "Deal Value" +msgstr "" + #: frontend/src/pages/Contact.vue:604 frontend/src/pages/MobileContact.vue:579 #: frontend/src/pages/MobileOrganization.vue:481 #: frontend/src/pages/Organization.vue:513 msgid "Deal owner" msgstr "" -#: frontend/src/pages/Deal.vue:474 frontend/src/pages/MobileDeal.vue:371 +#: frontend/src/pages/Deal.vue:493 frontend/src/pages/MobileDeal.vue:378 msgid "Deal updated" msgstr "" #. Label of a shortcut in the Frappe CRM Workspace #: crm/fcrm/workspace/frappe_crm/frappe_crm.json -#: frontend/src/pages/Deal.vue:493 frontend/src/pages/MobileContact.vue:310 -#: frontend/src/pages/MobileDeal.vue:390 +#: frontend/src/pages/Deal.vue:512 frontend/src/pages/MobileContact.vue:310 +#: frontend/src/pages/MobileDeal.vue:397 #: frontend/src/pages/MobileOrganization.vue:334 msgid "Deals" msgstr "" @@ -1242,16 +1251,11 @@ msgstr "" msgid "Default statuses, custom fields and layouts restored successfully." msgstr "" -#. Label of the defaults_tab (Tab Break) field in DocType 'FCRM Settings' -#: crm/fcrm/doctype/fcrm_settings/fcrm_settings.json -msgid "Defaults" -msgstr "" - #: frontend/src/components/Activities/AttachmentArea.vue:135 #: frontend/src/components/Activities/NoteArea.vue:12 #: frontend/src/components/Activities/TaskArea.vue:55 #: frontend/src/components/Activities/TaskArea.vue:63 -#: frontend/src/components/Controls/Grid.vue:312 +#: frontend/src/components/Controls/Grid.vue:316 #: frontend/src/components/Kanban/KanbanView.vue:225 #: frontend/src/components/ListBulkActions.vue:92 #: frontend/src/components/ListBulkActions.vue:100 @@ -1261,7 +1265,7 @@ msgstr "" #: frontend/src/pages/Contact.vue:105 frontend/src/pages/Contact.vue:313 #: frontend/src/pages/MobileContact.vue:81 #: frontend/src/pages/MobileContact.vue:285 -#: frontend/src/pages/MobileDeal.vue:521 +#: frontend/src/pages/MobileDeal.vue:528 #: frontend/src/pages/MobileOrganization.vue:72 #: frontend/src/pages/MobileOrganization.vue:270 #: frontend/src/pages/Notes.vue:40 frontend/src/pages/Organization.vue:83 @@ -1319,7 +1323,7 @@ msgstr "" #: crm/fcrm/doctype/crm_lead/crm_lead.json #: crm/fcrm/doctype/crm_lead_source/crm_lead_source.json #: frontend/src/pages/MobileContact.vue:305 -#: frontend/src/pages/MobileDeal.vue:430 frontend/src/pages/MobileLead.vue:331 +#: frontend/src/pages/MobileDeal.vue:437 frontend/src/pages/MobileLead.vue:338 #: frontend/src/pages/MobileOrganization.vue:329 msgid "Details" msgstr "" @@ -1381,7 +1385,7 @@ msgstr "" msgid "Document updated successfully" msgstr "" -#: frontend/src/components/Modals/AboutModal.vue:78 +#: frontend/src/components/Modals/AboutModal.vue:62 msgid "Documentation" msgstr "" @@ -1475,7 +1479,7 @@ msgstr "" msgid "Edit" msgstr "" -#: frontend/src/components/Modals/CallLogModal.vue:90 +#: frontend/src/components/Modals/CallLogModal.vue:94 msgid "Edit Call Log" msgstr "" @@ -1586,8 +1590,8 @@ msgstr "" msgid "Email template" msgstr "" -#: frontend/src/pages/Deal.vue:538 frontend/src/pages/Lead.vue:522 -#: frontend/src/pages/MobileDeal.vue:441 frontend/src/pages/MobileLead.vue:342 +#: frontend/src/pages/Deal.vue:557 frontend/src/pages/Lead.vue:546 +#: frontend/src/pages/MobileDeal.vue:448 frontend/src/pages/MobileLead.vue:349 msgid "Emails" msgstr "" @@ -1607,6 +1611,11 @@ msgstr "" msgid "Enable" msgstr "" +#. Label of the enable_forecasting (Check) field in DocType 'FCRM Settings' +#: crm/fcrm/doctype/fcrm_settings/fcrm_settings.json +msgid "Enable Forecasting" +msgstr "" + #: frontend/src/components/Settings/emailConfig.js:28 msgid "Enable Incoming" msgstr "" @@ -1655,15 +1664,15 @@ msgstr "" msgid "Equals" msgstr "" -#: frontend/src/pages/Lead.vue:644 +#: frontend/src/pages/Lead.vue:666 msgid "Error converting to deal: {0}" msgstr "" -#: frontend/src/pages/MobileDeal.vue:375 +#: frontend/src/pages/MobileDeal.vue:382 msgid "Error updating deal" msgstr "" -#: frontend/src/pages/Deal.vue:478 +#: frontend/src/pages/Deal.vue:497 msgid "Error updating deal: {0}" msgstr "" @@ -1671,7 +1680,7 @@ msgstr "" msgid "Error updating document" msgstr "" -#: frontend/src/pages/Lead.vue:462 +#: frontend/src/pages/Lead.vue:486 msgid "Error updating lead" msgstr "" @@ -1778,7 +1787,7 @@ msgstr "" msgid "Failed to create email account, Invalid credentials" msgstr "" -#: frontend/src/data/script.js:105 +#: frontend/src/data/script.js:110 msgid "Failed to load form controller: {0}" msgstr "" @@ -1831,14 +1840,14 @@ msgstr "" #. Label of the first_name (Data) field in DocType 'CRM Lead' #: crm/fcrm/doctype/crm_deal/crm_deal.json #: crm/fcrm/doctype/crm_lead/crm_lead.json -#: frontend/src/components/ColumnSettings.vue:108 +#: frontend/src/components/ColumnSettings.vue:112 #: frontend/src/components/Filter.vue:58 frontend/src/components/Filter.vue:89 #: frontend/src/components/SortBy.vue:6 frontend/src/components/SortBy.vue:106 #: frontend/src/components/SortBy.vue:140 msgid "First Name" msgstr "" -#: frontend/src/components/Modals/LeadModal.vue:130 +#: frontend/src/components/Modals/LeadModal.vue:134 msgid "First Name is mandatory" msgstr "" @@ -1945,11 +1954,11 @@ msgstr "" msgid "General" msgstr "" -#: frontend/src/components/Modals/AboutModal.vue:73 +#: frontend/src/components/Modals/AboutModal.vue:57 msgid "GitHub Repository" msgstr "" -#: frontend/src/pages/Deal.vue:106 frontend/src/pages/Lead.vue:156 +#: frontend/src/pages/Deal.vue:117 frontend/src/pages/Lead.vue:167 msgid "Go to website" msgstr "" @@ -2143,6 +2152,7 @@ msgid "Integration Not Enabled" msgstr "" #: frontend/src/components/Settings/Settings.vue:115 +msgctxt "FCRM" msgid "Integrations" msgstr "" @@ -2155,8 +2165,8 @@ msgstr "" msgid "Invalid Account SID or Auth Token." msgstr "" -#: frontend/src/components/Modals/DealModal.vue:207 -#: frontend/src/components/Modals/LeadModal.vue:149 +#: frontend/src/components/Modals/DealModal.vue:211 +#: frontend/src/components/Modals/LeadModal.vue:153 msgid "Invalid Email" msgstr "" @@ -2239,6 +2249,12 @@ msgstr "" msgid "Is Standard" msgstr "" +#. Description of the 'Enable Forecasting' (Check) field in DocType 'FCRM +#. Settings' +#: crm/fcrm/doctype/fcrm_settings/fcrm_settings.json +msgid "It will make deal's \"Expected Closure Date\" mandatory to get accurate forecasting insights" +msgstr "" + #. Label of the json (JSON) field in DocType 'CRM Global Settings' #: crm/fcrm/doctype/crm_global_settings/crm_global_settings.json msgid "JSON" @@ -2289,7 +2305,7 @@ msgstr "" #. Label of the label (Data) field in DocType 'CRM View Settings' #: crm/fcrm/doctype/crm_dropdown_item/crm_dropdown_item.json #: crm/fcrm/doctype/crm_view_settings/crm_view_settings.json -#: frontend/src/components/ColumnSettings.vue:105 +#: frontend/src/components/ColumnSettings.vue:109 msgid "Label" msgstr "" @@ -2376,13 +2392,13 @@ msgstr "" msgid "Lead Statuses" msgstr "" -#: frontend/src/pages/Lead.vue:458 frontend/src/pages/MobileLead.vue:272 +#: frontend/src/pages/Lead.vue:482 frontend/src/pages/MobileLead.vue:279 msgid "Lead updated successfully" msgstr "" #. Label of a shortcut in the Frappe CRM Workspace #: crm/fcrm/workspace/frappe_crm/frappe_crm.json -#: frontend/src/pages/Lead.vue:477 frontend/src/pages/MobileLead.vue:291 +#: frontend/src/pages/Lead.vue:501 frontend/src/pages/MobileLead.vue:298 msgid "Leads" msgstr "" @@ -2436,8 +2452,8 @@ msgid "Load More" msgstr "" #: frontend/src/components/Activities/Activities.vue:22 -#: frontend/src/components/Activities/DataFields.vue:35 -#: frontend/src/pages/Deal.vue:185 frontend/src/pages/MobileDeal.vue:116 +#: frontend/src/components/Activities/DataFields.vue:37 +#: frontend/src/pages/Deal.vue:200 frontend/src/pages/MobileDeal.vue:123 msgid "Loading..." msgstr "" @@ -2482,7 +2498,7 @@ msgstr "" msgid "Make a Call" msgstr "" -#: frontend/src/pages/Deal.vue:85 frontend/src/pages/Lead.vue:127 +#: frontend/src/pages/Deal.vue:92 frontend/src/pages/Lead.vue:134 msgid "Make a call" msgstr "" @@ -2515,6 +2531,10 @@ msgstr "" msgid "Manager Access" msgstr "" +#: frontend/src/data/document.js:31 +msgid "Mandatory field error: {0}" +msgstr "" + #. Option for the 'Telephony Medium' (Select) field in DocType 'CRM Call Log' #: crm/fcrm/doctype/crm_call_log/crm_call_log.json msgid "Manual" @@ -2561,8 +2581,8 @@ msgstr "" msgid "Mobile No" msgstr "" -#: frontend/src/components/Modals/DealModal.vue:203 -#: frontend/src/components/Modals/LeadModal.vue:145 +#: frontend/src/components/Modals/DealModal.vue:207 +#: frontend/src/components/Modals/LeadModal.vue:149 msgid "Mobile No should be a number" msgstr "" @@ -2650,11 +2670,11 @@ msgstr "" msgid "New" msgstr "" -#: frontend/src/components/Modals/AddressModal.vue:87 +#: frontend/src/components/Modals/AddressModal.vue:91 msgid "New Address" msgstr "" -#: frontend/src/components/Modals/CallLogModal.vue:90 +#: frontend/src/components/Modals/CallLogModal.vue:94 msgid "New Call Log" msgstr "" @@ -2689,7 +2709,7 @@ msgid "New Organization" msgstr "" #: frontend/src/components/FieldLayoutEditor.vue:203 -#: frontend/src/components/SidePanelLayoutEditor.vue:131 +#: frontend/src/components/SidePanelLayoutEditor.vue:133 msgid "New Section" msgstr "" @@ -2708,15 +2728,15 @@ msgstr "" msgid "New WhatsApp Message" msgstr "" -#: frontend/src/pages/Lead.vue:291 frontend/src/pages/MobileLead.vue:155 +#: frontend/src/pages/Lead.vue:310 frontend/src/pages/MobileLead.vue:162 msgid "New contact will be created based on the person's details" msgstr "" -#: frontend/src/pages/Lead.vue:266 frontend/src/pages/MobileLead.vue:129 +#: frontend/src/pages/Lead.vue:285 frontend/src/pages/MobileLead.vue:136 msgid "New organization will be created based on the data in details section" msgstr "" -#: frontend/src/components/Modals/CreateDocumentModal.vue:84 +#: frontend/src/components/Modals/CreateDocumentModal.vue:88 msgid "New {0}" msgstr "" @@ -2754,7 +2774,7 @@ msgstr "" msgid "No Answer" msgstr "" -#: frontend/src/components/Controls/Grid.vue:305 +#: frontend/src/components/Controls/Grid.vue:309 msgid "No Data" msgstr "" @@ -2769,11 +2789,11 @@ msgid "No changes made" msgstr "" #: frontend/src/components/Modals/SidePanelModal.vue:51 -#: frontend/src/pages/Deal.vue:270 frontend/src/pages/MobileDeal.vue:204 +#: frontend/src/pages/Deal.vue:289 frontend/src/pages/MobileDeal.vue:211 msgid "No contacts added" msgstr "" -#: frontend/src/pages/Deal.vue:100 frontend/src/pages/Lead.vue:150 +#: frontend/src/pages/Deal.vue:108 frontend/src/pages/Lead.vue:158 msgid "No email set" msgstr "" @@ -2781,7 +2801,7 @@ msgstr "" msgid "No label" msgstr "" -#: frontend/src/pages/Deal.vue:697 +#: frontend/src/pages/Deal.vue:716 msgid "No mobile number set" msgstr "" @@ -2790,11 +2810,11 @@ msgstr "" msgid "No new notifications" msgstr "" -#: frontend/src/pages/Lead.vue:135 +#: frontend/src/pages/Lead.vue:142 msgid "No phone number set" msgstr "" -#: frontend/src/pages/Deal.vue:692 +#: frontend/src/pages/Deal.vue:711 msgid "No primary contact set" msgstr "" @@ -2812,11 +2832,11 @@ msgstr "" msgid "No website found" msgstr "" -#: frontend/src/pages/Deal.vue:114 frontend/src/pages/Lead.vue:164 +#: frontend/src/pages/Deal.vue:124 frontend/src/pages/Lead.vue:174 msgid "No website set" msgstr "" -#: frontend/src/components/SidePanelLayout.vue:126 +#: frontend/src/components/SidePanelLayout.vue:131 msgid "No {0} Available" msgstr "" @@ -2878,7 +2898,7 @@ msgstr "" msgid "Not Saved" msgstr "" -#: crm/fcrm/doctype/crm_deal/crm_deal.py:214 +#: crm/fcrm/doctype/crm_deal/crm_deal.py:222 msgid "Not allowed to add contact to Deal" msgstr "" @@ -2886,16 +2906,16 @@ msgstr "" msgid "Not allowed to convert Lead to Deal" msgstr "" -#: crm/fcrm/doctype/crm_deal/crm_deal.py:225 +#: crm/fcrm/doctype/crm_deal/crm_deal.py:233 msgid "Not allowed to remove contact from Deal" msgstr "" -#: crm/fcrm/doctype/crm_deal/crm_deal.py:236 +#: crm/fcrm/doctype/crm_deal/crm_deal.py:244 msgid "Not allowed to set primary contact for Deal" msgstr "" -#: frontend/src/pages/Contact.vue:252 frontend/src/pages/Deal.vue:423 -#: frontend/src/pages/Lead.vue:425 frontend/src/pages/Organization.vue:237 +#: frontend/src/pages/Contact.vue:252 frontend/src/pages/Deal.vue:442 +#: frontend/src/pages/Lead.vue:449 frontend/src/pages/Organization.vue:237 msgid "Not permitted" msgstr "" @@ -2905,8 +2925,8 @@ msgstr "" msgid "Note" msgstr "" -#: frontend/src/pages/Deal.vue:563 frontend/src/pages/Lead.vue:547 -#: frontend/src/pages/MobileDeal.vue:467 frontend/src/pages/MobileLead.vue:368 +#: frontend/src/pages/Deal.vue:582 frontend/src/pages/Lead.vue:571 +#: frontend/src/pages/MobileDeal.vue:474 frontend/src/pages/MobileLead.vue:375 msgid "Notes" msgstr "" @@ -2953,11 +2973,11 @@ msgstr "" msgid "Old Parent" msgstr "" -#: frontend/src/utils/index.js:400 +#: frontend/src/utils/index.js:448 msgid "Only image files are allowed" msgstr "" -#: crm/fcrm/doctype/crm_deal/crm_deal.py:56 +#: crm/fcrm/doctype/crm_deal/crm_deal.py:57 #: crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.py:23 msgid "Only one {0} can be set as primary." msgstr "" @@ -3008,9 +3028,9 @@ msgstr "" #: crm/fcrm/doctype/crm_deal/crm_deal.json #: crm/fcrm/doctype/crm_lead/crm_lead.json #: frontend/src/components/Layouts/AppSidebar.vue:542 -#: frontend/src/pages/Contact.vue:578 frontend/src/pages/Lead.vue:249 +#: frontend/src/pages/Contact.vue:578 frontend/src/pages/Lead.vue:268 #: frontend/src/pages/MobileContact.vue:553 -#: frontend/src/pages/MobileLead.vue:111 +#: frontend/src/pages/MobileLead.vue:118 #: frontend/src/pages/MobileOrganization.vue:455 #: frontend/src/pages/MobileOrganization.vue:509 #: frontend/src/pages/Organization.vue:487 @@ -3037,7 +3057,7 @@ msgstr "" msgid "Organization Name" msgstr "" -#: frontend/src/pages/Deal.vue:68 +#: frontend/src/pages/Deal.vue:75 msgid "Organization logo" msgstr "" @@ -3164,11 +3184,11 @@ msgstr "" msgid "Please enter a valid URL" msgstr "" -#: frontend/src/pages/Lead.vue:615 frontend/src/pages/MobileLead.vue:430 +#: frontend/src/pages/Lead.vue:637 frontend/src/pages/MobileLead.vue:437 msgid "Please select an existing contact" msgstr "" -#: frontend/src/pages/Lead.vue:620 frontend/src/pages/MobileLead.vue:435 +#: frontend/src/pages/Lead.vue:642 frontend/src/pages/MobileLead.vue:442 msgid "Please select an existing organization" msgstr "" @@ -3183,11 +3203,11 @@ msgstr "" msgid "Position" msgstr "" -#: frontend/src/pages/Deal.vue:214 frontend/src/pages/MobileDeal.vue:148 +#: frontend/src/pages/Deal.vue:229 frontend/src/pages/MobileDeal.vue:155 msgid "Primary" msgstr "" -#: frontend/src/pages/Deal.vue:669 frontend/src/pages/MobileDeal.vue:572 +#: frontend/src/pages/Deal.vue:688 frontend/src/pages/MobileDeal.vue:579 msgid "Primary contact set" msgstr "" @@ -3209,7 +3229,9 @@ msgid "Private" msgstr "" #. Label of the probability (Percent) field in DocType 'CRM Deal' +#. Label of the probability (Percent) field in DocType 'CRM Deal Status' #: crm/fcrm/doctype/crm_deal/crm_deal.json +#: crm/fcrm/doctype/crm_deal_status/crm_deal_status.json msgid "Probability" msgstr "" @@ -3358,7 +3380,7 @@ msgstr "" msgid "Reject" msgstr "" -#: frontend/src/pages/Deal.vue:618 +#: frontend/src/pages/Deal.vue:637 msgid "Remove" msgstr "" @@ -3374,7 +3396,7 @@ msgstr "" msgid "Remove column" msgstr "" -#: frontend/src/pages/Contact.vue:47 frontend/src/pages/Lead.vue:100 +#: frontend/src/pages/Contact.vue:47 frontend/src/pages/Lead.vue:107 #: frontend/src/pages/MobileContact.vue:40 #: frontend/src/pages/MobileOrganization.vue:43 #: frontend/src/pages/Organization.vue:47 @@ -3398,7 +3420,7 @@ msgstr "" msgid "Reply All" msgstr "" -#: frontend/src/components/Modals/AboutModal.vue:88 +#: frontend/src/components/Modals/AboutModal.vue:72 msgid "Report an Issue" msgstr "" @@ -3415,7 +3437,7 @@ msgstr "" msgid "Reset" msgstr "" -#: frontend/src/components/ColumnSettings.vue:78 +#: frontend/src/components/ColumnSettings.vue:82 msgid "Reset Changes" msgstr "" @@ -3423,7 +3445,7 @@ msgstr "" msgid "Reset ERPNext Form Script" msgstr "" -#: frontend/src/components/ColumnSettings.vue:89 +#: frontend/src/components/ColumnSettings.vue:93 msgid "Reset to Default" msgstr "" @@ -3604,8 +3626,8 @@ msgstr "" #: frontend/src/components/Controls/GridFieldsEditorModal.vue:87 #: frontend/src/components/Controls/GridRowFieldsModal.vue:26 #: frontend/src/components/DropdownItem.vue:21 -#: frontend/src/components/Modals/AddressModal.vue:92 -#: frontend/src/components/Modals/CallLogModal.vue:94 +#: frontend/src/components/Modals/AddressModal.vue:96 +#: frontend/src/components/Modals/CallLogModal.vue:98 #: frontend/src/components/Modals/DataFieldsModal.vue:26 #: frontend/src/components/Modals/QuickEntryModal.vue:26 #: frontend/src/components/Modals/SidePanelModal.vue:26 @@ -3662,7 +3684,7 @@ msgstr "" msgid "Send Template" msgstr "" -#: frontend/src/pages/Deal.vue:92 frontend/src/pages/Lead.vue:142 +#: frontend/src/pages/Deal.vue:101 frontend/src/pages/Lead.vue:151 msgid "Send an email" msgstr "" @@ -3692,11 +3714,11 @@ msgstr "" msgid "Set all as public" msgstr "" -#: frontend/src/pages/Deal.vue:79 +#: frontend/src/pages/Deal.vue:86 msgid "Set an organization" msgstr "" -#: frontend/src/pages/Deal.vue:626 frontend/src/pages/MobileDeal.vue:529 +#: frontend/src/pages/Deal.vue:645 frontend/src/pages/MobileDeal.vue:536 msgid "Set as Primary Contact" msgstr "" @@ -3704,7 +3726,7 @@ msgstr "" msgid "Set as default" msgstr "" -#: frontend/src/pages/Lead.vue:121 +#: frontend/src/pages/Lead.vue:128 msgid "Set first name" msgstr "" @@ -3752,6 +3774,8 @@ msgid "" "\t\t and app specific passwords. Read more " msgstr "" +#. Label of the defaults_tab (Tab Break) field in DocType 'FCRM Settings' +#: crm/fcrm/doctype/fcrm_settings/fcrm_settings.json #: frontend/src/components/Layouts/AppSidebar.vue:526 #: frontend/src/components/Settings/Settings.vue:11 #: frontend/src/components/Settings/Settings.vue:81 @@ -3880,8 +3904,8 @@ msgstr "" msgid "Status Change Log" msgstr "" -#: frontend/src/components/Modals/DealModal.vue:211 -#: frontend/src/components/Modals/LeadModal.vue:153 +#: frontend/src/components/Modals/DealModal.vue:215 +#: frontend/src/components/Modals/LeadModal.vue:157 msgid "Status is required" msgstr "" @@ -3945,12 +3969,12 @@ msgstr "" msgid "Task" msgstr "" -#: frontend/src/pages/Deal.vue:558 frontend/src/pages/Lead.vue:542 -#: frontend/src/pages/MobileDeal.vue:462 frontend/src/pages/MobileLead.vue:363 +#: frontend/src/pages/Deal.vue:577 frontend/src/pages/Lead.vue:566 +#: frontend/src/pages/MobileDeal.vue:469 frontend/src/pages/MobileLead.vue:370 msgid "Tasks" msgstr "" -#: frontend/src/components/Modals/AboutModal.vue:83 +#: frontend/src/components/Modals/AboutModal.vue:67 msgid "Telegram Channel" msgstr "" @@ -3999,8 +4023,8 @@ msgstr "" msgid "There can only be one default priority in Priorities table" msgstr "" -#: frontend/src/components/Modals/AddressModal.vue:120 -#: frontend/src/components/Modals/CallLogModal.vue:122 +#: frontend/src/components/Modals/AddressModal.vue:124 +#: frontend/src/components/Modals/CallLogModal.vue:126 msgid "These fields are required: {0}" msgstr "" @@ -4020,7 +4044,7 @@ msgstr "" msgid "This Year" msgstr "" -#: frontend/src/components/SidePanelLayoutEditor.vue:117 +#: frontend/src/components/SidePanelLayoutEditor.vue:119 msgid "This section is not editable" msgstr "" @@ -4214,7 +4238,7 @@ msgstr "" msgid "Untitled" msgstr "" -#: frontend/src/components/ColumnSettings.vue:134 +#: frontend/src/components/ColumnSettings.vue:138 #: frontend/src/components/Modals/AssignmentModal.vue:17 #: frontend/src/components/Modals/EmailTemplateModal.vue:9 #: frontend/src/components/Modals/NoteModal.vue:6 @@ -4258,7 +4282,7 @@ msgstr "" msgid "Upload Video" msgstr "" -#: frontend/src/pages/Contact.vue:42 frontend/src/pages/Lead.vue:95 +#: frontend/src/pages/Contact.vue:42 frontend/src/pages/Lead.vue:102 #: frontend/src/pages/MobileContact.vue:35 #: frontend/src/pages/MobileOrganization.vue:38 #: frontend/src/pages/Organization.vue:42 @@ -4311,7 +4335,7 @@ msgstr "" #: crm/fcrm/doctype/crm_deal/crm_deal.json #: crm/fcrm/doctype/crm_lead/crm_lead.json #: crm/fcrm/doctype/crm_organization/crm_organization.json -#: frontend/src/components/Modals/AboutModal.vue:68 +#: frontend/src/components/Modals/AboutModal.vue:52 msgid "Website" msgstr "" @@ -4341,8 +4365,8 @@ msgstr "" #: crm/fcrm/doctype/crm_notification/crm_notification.json #: frontend/src/components/Layouts/AppSidebar.vue:592 #: frontend/src/components/Settings/Settings.vue:124 -#: frontend/src/pages/Deal.vue:573 frontend/src/pages/Lead.vue:557 -#: frontend/src/pages/MobileDeal.vue:477 frontend/src/pages/MobileLead.vue:378 +#: frontend/src/pages/Deal.vue:592 frontend/src/pages/Lead.vue:581 +#: frontend/src/pages/MobileDeal.vue:484 frontend/src/pages/MobileLead.vue:385 msgid "WhatsApp" msgstr "" @@ -4354,11 +4378,11 @@ msgstr "" msgid "Where" msgstr "" -#: frontend/src/components/ColumnSettings.vue:113 +#: frontend/src/components/ColumnSettings.vue:117 msgid "Width" msgstr "" -#: frontend/src/components/ColumnSettings.vue:118 +#: frontend/src/components/ColumnSettings.vue:122 msgid "Width can be in number, pixel or rem (eg. 3, 30px, 10rem)" msgstr "" @@ -4496,11 +4520,71 @@ msgstr "" msgid "here" msgstr "" +#: frontend/src/utils/index.js:144 +msgid "in 1 hour" +msgstr "" + +#: frontend/src/utils/index.js:140 +msgid "in 1 minute" +msgstr "" + +#: frontend/src/utils/index.js:158 +msgid "in 1 year" +msgstr "" + +#: frontend/src/utils/index.js:110 +msgid "in {0} M" +msgstr "" + +#: frontend/src/utils/index.js:106 +msgid "in {0} d" +msgstr "" + +#: frontend/src/utils/index.js:152 +msgid "in {0} days" +msgstr "" + +#: frontend/src/utils/index.js:100 +msgid "in {0} h" +msgstr "" + +#: frontend/src/utils/index.js:146 +msgid "in {0} hours" +msgstr "" + +#: frontend/src/utils/index.js:98 +msgid "in {0} m" +msgstr "" + +#: frontend/src/utils/index.js:142 +msgid "in {0} minutes" +msgstr "" + +#: frontend/src/utils/index.js:156 +msgid "in {0} months" +msgstr "" + +#: frontend/src/utils/index.js:108 +msgid "in {0} w" +msgstr "" + +#: frontend/src/utils/index.js:154 +msgid "in {0} weeks" +msgstr "" + +#: frontend/src/utils/index.js:112 +msgid "in {0} y" +msgstr "" + +#: frontend/src/utils/index.js:160 +msgid "in {0} years" +msgstr "" + #: frontend/src/components/Settings/InviteMemberPage.vue:17 msgid "john@doe.com" msgstr "" -#: frontend/src/utils/index.js:116 +#: frontend/src/utils/index.js:138 frontend/src/utils/index.js:164 msgid "just now" msgstr "" @@ -4527,7 +4611,7 @@ msgstr "" msgid "next" msgstr "" -#: frontend/src/utils/index.js:95 +#: frontend/src/utils/index.js:96 frontend/src/utils/index.js:116 msgid "now" msgstr "" @@ -4587,6 +4671,10 @@ msgstr "" msgid "to" msgstr "" +#: frontend/src/utils/index.js:104 frontend/src/utils/index.js:150 +msgid "tomorrow" +msgstr "" + #. Option for the 'Color' (Select) field in DocType 'CRM Deal Status' #. Option for the 'Color' (Select) field in DocType 'CRM Lead Status' #: crm/fcrm/doctype/crm_deal_status/crm_deal_status.json @@ -4601,11 +4689,11 @@ msgstr "" msgid "yellow" msgstr "" -#: frontend/src/utils/index.js:128 +#: frontend/src/utils/index.js:176 msgid "yesterday" msgstr "" -#: frontend/src/utils/index.js:107 +#: frontend/src/utils/index.js:128 msgid "{0} M" msgstr "" @@ -4613,24 +4701,24 @@ msgstr "" msgid "{0} assigned a {1} {2} to you" msgstr "" -#: frontend/src/utils/index.js:103 +#: frontend/src/utils/index.js:124 msgid "{0} d" msgstr "" -#: frontend/src/utils/index.js:130 +#: frontend/src/utils/index.js:178 msgid "{0} days ago" msgstr "" -#: frontend/src/utils/index.js:99 +#: frontend/src/utils/index.js:120 msgid "{0} h" msgstr "" -#: frontend/src/utils/index.js:124 +#: frontend/src/utils/index.js:172 msgid "{0} hours ago" msgstr "" -#: frontend/src/pages/Deal.vue:486 frontend/src/pages/Lead.vue:470 -#: frontend/src/pages/MobileDeal.vue:383 frontend/src/pages/MobileLead.vue:284 +#: frontend/src/pages/Deal.vue:505 frontend/src/pages/Lead.vue:494 +#: frontend/src/pages/MobileDeal.vue:390 frontend/src/pages/MobileLead.vue:291 msgid "{0} is a required field" msgstr "" @@ -4641,51 +4729,51 @@ msgstr "" msgid "{0} is an invalid email address" msgstr "" -#: frontend/src/utils/index.js:97 +#: frontend/src/utils/index.js:118 msgid "{0} m" msgstr "" -#: frontend/src/utils/index.js:120 +#: frontend/src/utils/index.js:168 msgid "{0} minutes ago" msgstr "" -#: frontend/src/utils/index.js:138 +#: frontend/src/utils/index.js:186 msgid "{0} months ago" msgstr "" -#: frontend/src/utils/index.js:105 +#: frontend/src/utils/index.js:126 msgid "{0} w" msgstr "" -#: frontend/src/utils/index.js:134 +#: frontend/src/utils/index.js:182 msgid "{0} weeks ago" msgstr "" -#: frontend/src/utils/index.js:109 +#: frontend/src/utils/index.js:130 msgid "{0} y" msgstr "" -#: frontend/src/utils/index.js:142 +#: frontend/src/utils/index.js:190 msgid "{0} years ago" msgstr "" -#: frontend/src/data/script.js:325 +#: frontend/src/data/script.js:330 msgid "⚠️ Avoid using \"trigger\" as a field name — it conflicts with the built-in trigger() method." msgstr "" -#: frontend/src/data/script.js:337 +#: frontend/src/data/script.js:342 msgid "⚠️ Method \"{0}\" not found in class." msgstr "" -#: frontend/src/data/script.js:82 +#: frontend/src/data/script.js:87 msgid "⚠️ No class found for doctype: {0}, it is mandatory to have a class for the parent doctype. it can be empty, but it should be present." msgstr "" -#: frontend/src/data/script.js:179 +#: frontend/src/data/script.js:184 msgid "⚠️ No data found for parent field: {0}" msgstr "" -#: frontend/src/data/script.js:187 +#: frontend/src/data/script.js:192 msgid "⚠️ No row found for idx: {0} in parent field: {1}" msgstr "" diff --git a/crm/patches.txt b/crm/patches.txt index 59f37bfb..ee56be84 100644 --- a/crm/patches.txt +++ b/crm/patches.txt @@ -12,4 +12,4 @@ crm.patches.v1_0.create_default_sidebar_fields_layout crm.patches.v1_0.update_deal_quick_entry_layout crm.patches.v1_0.update_layouts_to_new_format crm.patches.v1_0.move_twilio_agent_to_telephony_agent -crm.patches.v1_0.create_default_scripts \ No newline at end of file +crm.patches.v1_0.create_default_scripts # 13-06-2025 \ No newline at end of file diff --git a/frappe-ui b/frappe-ui index 8b615c0e..883bb643 160000 --- a/frappe-ui +++ b/frappe-ui @@ -1 +1 @@ -Subproject commit 8b615c0e899d75b99c7d36ec6df97b5d0386b2ca +Subproject commit 883bb643d1e662d6467925927e347dd28376960f diff --git a/frontend/components.d.ts b/frontend/components.d.ts index c83cddab..6a8cbbaf 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -12,6 +12,7 @@ declare module 'vue' { Activities: typeof import('./src/components/Activities/Activities.vue')['default'] ActivityHeader: typeof import('./src/components/Activities/ActivityHeader.vue')['default'] ActivityIcon: typeof import('./src/components/Icons/ActivityIcon.vue')['default'] + AddExistingUserModal: typeof import('./src/components/Modals/AddExistingUserModal.vue')['default'] AddressIcon: typeof import('./src/components/Icons/AddressIcon.vue')['default'] AddressModal: typeof import('./src/components/Modals/AddressModal.vue')['default'] AllModals: typeof import('./src/components/Activities/AllModals.vue')['default'] @@ -38,6 +39,7 @@ declare module 'vue' { CallUI: typeof import('./src/components/Telephony/CallUI.vue')['default'] CameraIcon: typeof import('./src/components/Icons/CameraIcon.vue')['default'] CertificateIcon: typeof import('./src/components/Icons/CertificateIcon.vue')['default'] + ChangePasswordModal: typeof import('./src/components/Modals/ChangePasswordModal.vue')['default'] CheckCircleIcon: typeof import('./src/components/Icons/CheckCircleIcon.vue')['default'] CheckIcon: typeof import('./src/components/Icons/CheckIcon.vue')['default'] CollapseSidebar: typeof import('./src/components/Icons/CollapseSidebar.vue')['default'] @@ -136,10 +138,11 @@ declare module 'vue' { InboxIcon: typeof import('./src/components/Icons/InboxIcon.vue')['default'] IndicatorIcon: typeof import('./src/components/Icons/IndicatorIcon.vue')['default'] InviteIcon: typeof import('./src/components/Icons/InviteIcon.vue')['default'] - InviteMemberPage: typeof import('./src/components/Settings/InviteMemberPage.vue')['default'] + InviteUserPage: typeof import('./src/components/Settings/InviteUserPage.vue')['default'] KanbanIcon: typeof import('./src/components/Icons/KanbanIcon.vue')['default'] KanbanSettings: typeof import('./src/components/Kanban/KanbanSettings.vue')['default'] KanbanView: typeof import('./src/components/Kanban/KanbanView.vue')['default'] + KeyboardShortcut: typeof import('./src/components/KeyboardShortcut.vue')['default'] LayoutHeader: typeof import('./src/components/LayoutHeader.vue')['default'] LeadModal: typeof import('./src/components/Modals/LeadModal.vue')['default'] LeadsIcon: typeof import('./src/components/Icons/LeadsIcon.vue')['default'] @@ -151,7 +154,6 @@ declare module 'vue' { ListIcon: typeof import('./src/components/Icons/ListIcon.vue')['default'] ListRows: typeof import('./src/components/ListViews/ListRows.vue')['default'] LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default'] - LucideCalendar: typeof import('~icons/lucide/calendar')['default'] LucideInfo: typeof import('~icons/lucide/info')['default'] LucidePlus: typeof import('~icons/lucide/plus')['default'] LucideSearch: typeof import('~icons/lucide/search')['default'] @@ -167,6 +169,7 @@ declare module 'vue' { MultiActionButton: typeof import('./src/components/MultiActionButton.vue')['default'] MultipleAvatar: typeof import('./src/components/MultipleAvatar.vue')['default'] MultiSelectEmailInput: typeof import('./src/components/Controls/MultiSelectEmailInput.vue')['default'] + MultiSelectUserInput: typeof import('./src/components/Controls/MultiSelectUserInput.vue')['default'] MuteIcon: typeof import('./src/components/Icons/MuteIcon.vue')['default'] NestedPopover: typeof import('./src/components/NestedPopover.vue')['default'] NoteArea: typeof import('./src/components/Activities/NoteArea.vue')['default'] @@ -231,6 +234,7 @@ declare module 'vue' { UnpinIcon: typeof import('./src/components/Icons/UnpinIcon.vue')['default'] UserAvatar: typeof import('./src/components/UserAvatar.vue')['default'] UserDropdown: typeof import('./src/components/UserDropdown.vue')['default'] + Users: typeof import('./src/components/Settings/Users.vue')['default'] ViewBreadcrumbs: typeof import('./src/components/ViewBreadcrumbs.vue')['default'] ViewControls: typeof import('./src/components/ViewControls.vue')['default'] ViewModal: typeof import('./src/components/Modals/ViewModal.vue')['default'] diff --git a/frontend/package.json b/frontend/package.json index 40e1f47a..51a6ff41 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,7 @@ "@tiptap/extension-paragraph": "^2.12.0", "@twilio/voice-sdk": "^2.10.2", "@vueuse/integrations": "^10.3.0", - "frappe-ui": "^0.1.145", + "frappe-ui": "^0.1.156", "gemoji": "^8.1.0", "lodash": "^4.17.21", "mime": "^4.0.1", diff --git a/frontend/src/components/Activities/Activities.vue b/frontend/src/components/Activities/Activities.vue index d4b670e1..1c420e04 100644 --- a/frontend/src/components/Activities/Activities.vue +++ b/frontend/src/components/Activities/Activities.vue @@ -806,5 +806,5 @@ const callActions = computed(() => { ) }) -defineExpose({ emailBox, all_activities }) +defineExpose({ emailBox, all_activities, changeTabTo }) diff --git a/frontend/src/components/Activities/DataFields.vue b/frontend/src/components/Activities/DataFields.vue index b3aecff4..88119f80 100644 --- a/frontend/src/components/Activities/DataFields.vue +++ b/frontend/src/components/Activities/DataFields.vue @@ -16,7 +16,9 @@ v-if="isManager() && !isMobileView" @click="showDataFieldsModal = true" > - + + + {{ __('Loading...') }} diff --git a/frontend/src/components/ColumnSettings.vue b/frontend/src/components/ColumnSettings.vue index b358ca4f..b98792c7 100644 --- a/frontend/src/components/ColumnSettings.vue +++ b/frontend/src/components/ColumnSettings.vue @@ -30,20 +30,24 @@ {{ __(element.label) }} - + - + + + - + + + @@ -215,7 +219,9 @@ const fields = computed(() => { }) function addColumn(c) { - let align = ['Float', 'Int', 'Percent', 'Currency'].includes(c.type) ? 'right' : 'left' + let align = ['Float', 'Int', 'Percent', 'Currency'].includes(c.type) + ? 'right' + : 'left' let _column = { label: c.label, type: c.fieldtype, diff --git a/frontend/src/components/CommentBox.vue b/frontend/src/components/CommentBox.vue index 3fcfc82c..86074750 100644 --- a/frontend/src/components/CommentBox.vue +++ b/frontend/src/components/CommentBox.vue @@ -149,7 +149,7 @@ function removeAttachment(attachment) { const users = computed(() => { return ( - usersList.data + usersList.data?.crmUsers ?.filter((user) => user.enabled) .map((user) => ({ label: user.full_name.trimEnd(), diff --git a/frontend/src/components/Controls/Grid.vue b/frontend/src/components/Controls/Grid.vue index 54bc29dc..cce8b245 100644 --- a/frontend/src/components/Controls/Grid.vue +++ b/frontend/src/components/Controls/Grid.vue @@ -58,7 +58,9 @@ variant="outline" @click="showGridFieldsEditorModal = true" > - + + + @@ -281,7 +283,9 @@ variant="outline" @click="showRowList[index] = true" > - + + + {}) +const triggerOnRowAdd = inject('triggerOnRowAdd', () => {}) +const triggerOnRowRemove = inject('triggerOnRowRemove', () => {}) const { getGridViewSettings, @@ -389,7 +393,7 @@ const { getGridSettings, } = getMeta(props.doctype) getMeta(props.parentDoctype) -const { getUser } = usersStore() +const { users, getUser } = usersStore() const rows = defineModel() const parentDoc = defineModel('parent') @@ -434,6 +438,14 @@ function getFieldObj(field) { } } + if (field.fieldtype === 'Link' && field.options === 'User') { + field.fieldtype = 'User' + field.link_filters = JSON.stringify({ + ...(field.link_filters ? JSON.parse(field.link_filters) : {}), + name: ['in', users.data.crmUsers?.map((user) => user.name)], + }) + } + return { ...field, filters: field.link_filters && JSON.parse(field.link_filters), @@ -509,8 +521,7 @@ const deleteRows = () => { } function fieldChange(value, field, row) { - row[field.fieldname] = value - triggerOnChange(field.fieldname, row) + triggerOnChange(field.fieldname, value, row) } function getDefaultValue(defaultValue, fieldtype) { diff --git a/frontend/src/components/Controls/GridRowModal.vue b/frontend/src/components/Controls/GridRowModal.vue index ef2bc854..e38dd87c 100644 --- a/frontend/src/components/Controls/GridRowModal.vue +++ b/frontend/src/components/Controls/GridRowModal.vue @@ -15,10 +15,14 @@ class="w-7" @click="openGridRowFieldsModal" > - + + + - + + + diff --git a/frontend/src/components/Controls/MultiSelectEmailInput.vue b/frontend/src/components/Controls/MultiSelectEmailInput.vue index 182ed938..133bfea4 100644 --- a/frontend/src/components/Controls/MultiSelectEmailInput.vue +++ b/frontend/src/components/Controls/MultiSelectEmailInput.vue @@ -70,7 +70,7 @@ {{ fetchContacts ? __('No results found') - : __('Type an email address to add') + : __('Type an email address to invite') }} [], + }, }) const values = defineModel() @@ -205,6 +209,14 @@ const filterOptions = createResource({ value: email, } }) + + // Filter out existing emails + if (props.existingEmails?.length) { + allData = allData.filter((option) => { + return !props.existingEmails.includes(option.value) + }) + } + return allData }, }) diff --git a/frontend/src/components/Controls/MultiSelectUserInput.vue b/frontend/src/components/Controls/MultiSelectUserInput.vue new file mode 100644 index 00000000..3a554c2e --- /dev/null +++ b/frontend/src/components/Controls/MultiSelectUserInput.vue @@ -0,0 +1,278 @@ + + + + + + + + + + + + + { + query = e.target.value + showOptions = true + } + " + autocomplete="off" + @focus="() => togglePopover()" + @keydown.delete.capture.stop="removeLastValue" + /> + + + + + + + + {{ + fetchUsers + ? __('No results found') + : __('Type an email address to invite') + }} + + + + + + + {{ option.label }} + + + {{ option.value }} + + + + + + + + + + + + + + + {{ info }} + + + + + diff --git a/frontend/src/components/Controls/Password.vue b/frontend/src/components/Controls/Password.vue index 6a636039..2346fc93 100644 --- a/frontend/src/components/Controls/Password.vue +++ b/frontend/src/components/Controls/Password.vue @@ -3,17 +3,47 @@ :type="show ? 'text' : 'password'" :value="modelValue || value" v-bind="$attrs" + @keydown.meta.i.prevent="show = !show" + @keydown.ctrl.i.prevent="show = !show" > + + + - - - + + + + + {{ show ? __('Hide Password') : __('Show Password') }} + + +I + + + + + + + + diff --git a/frontend/src/components/Layouts/AppSidebar.vue b/frontend/src/components/Layouts/AppSidebar.vue index f7e87c4f..629ceb4f 100644 --- a/frontend/src/components/Layouts/AppSidebar.vue +++ b/frontend/src/components/Layouts/AppSidebar.vue @@ -172,6 +172,7 @@ import { import { usersStore } from '@/stores/users' import { sessionStore } from '@/stores/session' import { showSettings, activeSettingsPage } from '@/composables/settings' +import { showChangePasswordModal } from '@/composables/modals' import { FeatherIcon, call } from 'frappe-ui' import { SignupBanner, @@ -329,8 +330,7 @@ const steps = reactive([ completed: false, onClick: () => { minimize.value = true - showSettings.value = true - activeSettingsPage.value = 'Profile' + showChangePasswordModal.value = true }, }, { @@ -351,7 +351,7 @@ const steps = reactive([ onClick: () => { minimize.value = true showSettings.value = true - activeSettingsPage.value = 'Invite Members' + activeSettingsPage.value = 'Invite User' }, condition: () => isManager(), }, @@ -529,7 +529,7 @@ const articles = ref([ { name: 'profile', title: __('Profile') }, { name: 'custom-branding', title: __('Custom branding') }, { name: 'home-actions', title: __('Home actions') }, - { name: 'invite-members', title: __('Invite members') }, + { name: 'invite-users', title: __('Invite users') }, ], }, { diff --git a/frontend/src/components/Modals/AddExistingUserModal.vue b/frontend/src/components/Modals/AddExistingUserModal.vue new file mode 100644 index 00000000..d11c2f61 --- /dev/null +++ b/frontend/src/components/Modals/AddExistingUserModal.vue @@ -0,0 +1,112 @@ + + + + + + + {{ + __( + 'Add existing system users to this CRM. Assign them a role to grant access with their current credentials.', + ) + }} + + + + + {{ __('Users') }} + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/Modals/AddressModal.vue b/frontend/src/components/Modals/AddressModal.vue index 6428c9a4..e7c4543f 100644 --- a/frontend/src/components/Modals/AddressModal.vue +++ b/frontend/src/components/Modals/AddressModal.vue @@ -15,10 +15,14 @@ class="w-7" @click="openQuickEntryModal" > - + + + - + + + diff --git a/frontend/src/components/Modals/AssignmentModal.vue b/frontend/src/components/Modals/AssignmentModal.vue index 05cea454..c6fc6dfd 100644 --- a/frontend/src/components/Modals/AssignmentModal.vue +++ b/frontend/src/components/Modals/AssignmentModal.vue @@ -33,6 +33,7 @@ doctype="User" @change="(option) => addValue(option) && ($refs.input.value = '')" :placeholder="__('John Doe')" + :filters="{ name: ['in', users.data.crmUsers?.map((user) => user.name)] }" :hideMe="true" > @@ -105,7 +106,7 @@ const oldAssignees = ref([]) const error = ref('') -const { getUser } = usersStore() +const { users, getUser } = usersStore() const removeValue = (value) => { assignees.value = assignees.value.filter( diff --git a/frontend/src/components/Modals/CallLogDetailModal.vue b/frontend/src/components/Modals/CallLogDetailModal.vue index 04a30d5c..739f78ef 100644 --- a/frontend/src/components/Modals/CallLogDetailModal.vue +++ b/frontend/src/components/Modals/CallLogDetailModal.vue @@ -39,10 +39,14 @@ class="w-7" @click="openCallLogModal" > - + + + - + + + diff --git a/frontend/src/components/Modals/CallLogModal.vue b/frontend/src/components/Modals/CallLogModal.vue index d61dbc45..8ab881cd 100644 --- a/frontend/src/components/Modals/CallLogModal.vue +++ b/frontend/src/components/Modals/CallLogModal.vue @@ -16,10 +16,14 @@ class="w-7" @click="openQuickEntryModal" > - + + + - + + + diff --git a/frontend/src/components/Modals/ChangePasswordModal.vue b/frontend/src/components/Modals/ChangePasswordModal.vue new file mode 100644 index 00000000..d6c03399 --- /dev/null +++ b/frontend/src/components/Modals/ChangePasswordModal.vue @@ -0,0 +1,129 @@ + + + + + + + + + + + + {{ newPasswordMessage }} + + + + + + + + + + {{ confirmPasswordMessage }} + + + + + + + + + + + + + + + diff --git a/frontend/src/components/Modals/ContactModal.vue b/frontend/src/components/Modals/ContactModal.vue index b275d574..25a0c022 100644 --- a/frontend/src/components/Modals/ContactModal.vue +++ b/frontend/src/components/Modals/ContactModal.vue @@ -15,10 +15,14 @@ class="w-7" @click="openQuickEntryModal" > - + + + - + + + diff --git a/frontend/src/components/Modals/CreateDocumentModal.vue b/frontend/src/components/Modals/CreateDocumentModal.vue index 05e277b9..be5bbf01 100644 --- a/frontend/src/components/Modals/CreateDocumentModal.vue +++ b/frontend/src/components/Modals/CreateDocumentModal.vue @@ -15,10 +15,14 @@ class="w-7" @click="openQuickEntryModal" > - + + + - + + + diff --git a/frontend/src/components/Modals/DealModal.vue b/frontend/src/components/Modals/DealModal.vue index 1c516407..8bf9498b 100644 --- a/frontend/src/components/Modals/DealModal.vue +++ b/frontend/src/components/Modals/DealModal.vue @@ -15,10 +15,14 @@ class="w-7" @click="openQuickEntryModal" > - + + + - + + + @@ -94,7 +98,7 @@ const show = defineModel() const router = useRouter() const error = ref(null) -const { document: deal } = useDocument('CRM Deal') +const { document: deal, triggerOnChange } = useDocument('CRM Deal') const hasOrganizationSections = ref(true) const hasContactSections = ref(true) @@ -164,7 +168,7 @@ const tabs = createResource({ }) const dealStatuses = computed(() => { - let statuses = statusOptions('deal') + let statuses = statusOptions('deal', null, [], triggerOnChange) if (!deal.doc.status) { deal.doc.status = statuses[0].value } diff --git a/frontend/src/components/Modals/GlobalModals.vue b/frontend/src/components/Modals/GlobalModals.vue index 310b3d5d..d4328e6c 100644 --- a/frontend/src/components/Modals/GlobalModals.vue +++ b/frontend/src/components/Modals/GlobalModals.vue @@ -16,9 +16,14 @@ v-model="showAddressModal" v-bind="addressProps" /> + diff --git a/frontend/src/components/Modals/LeadModal.vue b/frontend/src/components/Modals/LeadModal.vue index bf1461d1..d82dbe52 100644 --- a/frontend/src/components/Modals/LeadModal.vue +++ b/frontend/src/components/Modals/LeadModal.vue @@ -15,10 +15,14 @@ class="w-7" @click="openQuickEntryModal" > - + + + - + + + @@ -70,10 +74,10 @@ const router = useRouter() const error = ref(null) const isLeadCreating = ref(false) -const { document: lead } = useDocument('CRM Lead') +const { document: lead, triggerOnChange } = useDocument('CRM Lead') const leadStatuses = computed(() => { - let statuses = statusOptions('lead') + let statuses = statusOptions('lead', null, [], triggerOnChange) if (!lead.doc.status) { lead.doc.status = statuses?.[0]?.value } diff --git a/frontend/src/components/Modals/OrganizationModal.vue b/frontend/src/components/Modals/OrganizationModal.vue index 1c8cf711..a0d12887 100644 --- a/frontend/src/components/Modals/OrganizationModal.vue +++ b/frontend/src/components/Modals/OrganizationModal.vue @@ -15,10 +15,14 @@ class="w-7" @click="openQuickEntryModal" > - + + + - + + + diff --git a/frontend/src/components/Modals/TaskModal.vue b/frontend/src/components/Modals/TaskModal.vue index a172b51e..98c2cd71 100644 --- a/frontend/src/components/Modals/TaskModal.vue +++ b/frontend/src/components/Modals/TaskModal.vue @@ -1,23 +1,32 @@ - + {{ editMode ? __('Edit Task') : __('Create Task') }} - + @@ -27,17 +36,29 @@ - + {{ __('Description') }} - (_task.description = val)" :placeholder="__('Took a call with John Doe and discussed the new project.') - " /> + :bubbleMenu="true" + :content="_task.description" + @change="(val) => (_task.description = val)" + :placeholder=" + __('Took a call with John Doe and discussed the new project.') + " + /> @@ -47,24 +68,38 @@ - (_task.assigned_to = option)" :placeholder="__('John Doe')" :hideMe="true"> - - - - - - - - - - {{ getUser(option.value).full_name }} - - - + (_task.assigned_to = option)" + :placeholder="__('John Doe')" + :filters="{ + name: ['in', users.data.crmUsers?.map((user) => user.name)], + }" + :hideMe="true" + > + + + + + + + + + + {{ getUser(option.value).full_name }} + + + - + @@ -114,7 +149,7 @@ const tasks = defineModel('reloadTasks') const emit = defineEmits(['updateTask', 'after']) const router = useRouter() -const { getUser } = usersStore() +const { users, getUser } = usersStore() const { updateOnboardingStep } = useOnboarding('frappecrm') const error = ref(null) @@ -164,20 +199,24 @@ async function updateTask() { emit('after', d) } } else { - let d = await call('frappe.client.insert', { - doc: { - doctype: 'CRM Task', - reference_doctype: props.doctype, - reference_docname: props.doc || null, - ..._task.value, + let d = await call( + 'frappe.client.insert', + { + doc: { + doctype: 'CRM Task', + reference_doctype: props.doctype, + reference_docname: props.doc || null, + ..._task.value, + }, }, - }, { - onError: (err) => { - if (err.error.exc_type == 'MandatoryError') { - error.value = "Title is mandatory" - } - } - }) + { + onError: (err) => { + if (err.error.exc_type == 'MandatoryError') { + error.value = 'Title is mandatory' + } + }, + }, + ) if (d.name) { updateOnboardingStep('create_first_task') capture('task_created') diff --git a/frontend/src/components/Settings/ERPNextSettings.vue b/frontend/src/components/Settings/ERPNextSettings.vue index 697a5503..16db1e5c 100644 --- a/frontend/src/components/Settings/ERPNextSettings.vue +++ b/frontend/src/components/Settings/ERPNextSettings.vue @@ -1,8 +1,8 @@ diff --git a/frontend/src/components/Settings/EmailAccountCard.vue b/frontend/src/components/Settings/EmailAccountCard.vue index 2890d43b..e7f693e4 100644 --- a/frontend/src/components/Settings/EmailAccountCard.vue +++ b/frontend/src/components/Settings/EmailAccountCard.vue @@ -1,19 +1,19 @@ - + {{ emailAccount.email_account_name }} - {{ emailAccount.email_id }} + {{ emailAccount.email_id }} - + diff --git a/frontend/src/components/Settings/EmailAccountList.vue b/frontend/src/components/Settings/EmailAccountList.vue index cd6a19ac..3b62ae45 100644 --- a/frontend/src/components/Settings/EmailAccountList.vue +++ b/frontend/src/components/Settings/EmailAccountList.vue @@ -1,22 +1,30 @@ - - - {{ __('Email Accounts') }} - - - - - - + + + + {{ __('Email accounts') }} + + + {{ + __( + '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.', + ) + }} + + + + + + - + {{ __('Please add an email account to continue.') }} diff --git a/frontend/src/components/Settings/EmailAdd.vue b/frontend/src/components/Settings/EmailAdd.vue index 19a0ac29..b4f13e54 100644 --- a/frontend/src/components/Settings/EmailAdd.vue +++ b/frontend/src/components/Settings/EmailAdd.vue @@ -2,10 +2,10 @@ - + {{ __('Setup Email') }} - + {{ __('Choose the email service provider you want to configure.') }} @@ -27,14 +27,15 @@ {{ selectedService.info }} - here. + + {{ __('here') }} + + . @@ -66,23 +67,22 @@ :name="field.name" :type="field.type" /> - {{ field.description }} + {{ field.description }} - + - + {{ __('Edit Email') }} @@ -14,16 +14,16 @@ - + {{ info.description }} - {{ - __('here') - }} + + {{ __('here') }} + . @@ -56,7 +56,7 @@ :name="field.name" :type="field.type" /> - {{ field.description }} + {{ field.description }} diff --git a/frontend/src/components/Settings/EmailProviderIcon.vue b/frontend/src/components/Settings/EmailProviderIcon.vue index 3d902bf9..047ee8ce 100644 --- a/frontend/src/components/Settings/EmailProviderIcon.vue +++ b/frontend/src/components/Settings/EmailProviderIcon.vue @@ -1,14 +1,11 @@ - + {{ serviceName }} @@ -29,5 +26,3 @@ defineProps({ }, }) - - diff --git a/frontend/src/components/Settings/GeneralSettings.vue b/frontend/src/components/Settings/GeneralSettings.vue index b8a3f328..14a78780 100644 --- a/frontend/src/components/Settings/GeneralSettings.vue +++ b/frontend/src/components/Settings/GeneralSettings.vue @@ -1,14 +1,29 @@ - - - {{ __('General') }} - - + + + + + {{ __('General') }} + + + + {{ __('Configure general settings for your CRM') }} + + + + + + @@ -16,14 +31,14 @@ type="text" class="w-1/2" v-model="settings.doc.brand_name" - :label="__('Brand Name')" + :label="__('Brand name')" /> - + {{ __('Logo') }} @@ -58,7 +73,7 @@ - + {{ __('Favicon') }} @@ -93,7 +108,7 @@ - + {{ __('Home actions') }} @@ -107,15 +122,7 @@ - - - - + diff --git a/frontend/src/components/Settings/ProfileSettings.vue b/frontend/src/components/Settings/ProfileSettings.vue index 68a0128f..8ddd496b 100644 --- a/frontend/src/components/Settings/ProfileSettings.vue +++ b/frontend/src/components/Settings/ProfileSettings.vue @@ -1,140 +1,160 @@ - - + + - - - - {{ - profile.full_name - }} - {{ profile.email }} - - - - updateImage(file.file_url)" + :validateFile="validateIsImageFile" > - - + + + + + + + + + + + + + {{ profile.full_name }} + + + {{ profile.email }} + + + + - - - - + + + - - - - - + + + + - diff --git a/frontend/src/components/SidePanelLayout.vue b/frontend/src/components/SidePanelLayout.vue index 3fe8fd1a..c8dcaaab 100644 --- a/frontend/src/components/SidePanelLayout.vue +++ b/frontend/src/components/SidePanelLayout.vue @@ -25,7 +25,9 @@ class="w-7 mr-2" @click="showSidePanelModal = true" > - + + + @@ -44,18 +46,21 @@ > - {{ __(field.label) }} - + {{ __(field.label) }} + + * + * + @@ -245,6 +250,7 @@ " :placeholder="field.placeholder" placement="left-start" + :hideIcon="true" @change="(v) => fieldChange(v, field)" /> @@ -258,6 +264,7 @@ :formatter="(date) => getFormat(date, '', true)" :placeholder="field.placeholder" placement="left-start" + :hideIcon="true" @change="(v) => fieldChange(v, field)" /> @@ -422,7 +429,7 @@ const emit = defineEmits(['afterFieldChange', 'reload']) const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } = getMeta(props.doctype) -const { isManager, getUser } = usersStore() +const { users, isManager, getUser } = usersStore() const showSidePanelModal = ref(false) @@ -464,8 +471,11 @@ function parsedField(field) { } if (field.fieldtype === 'Link' && field.options === 'User') { - field.options = field.options field.fieldtype = 'User' + field.link_filters = JSON.stringify({ + ...(field.link_filters ? JSON.parse(field.link_filters) : {}), + name: ['in', users.data?.crmUsers?.map((user) => user.name)], + }) } let _field = { @@ -489,9 +499,7 @@ function parsedField(field) { async function fieldChange(value, df) { if (props.preview) return - document.doc[df.fieldname] = value - - await triggerOnChange(df.fieldname) + await triggerOnChange(df.fieldname, value) document.save.submit(null, { onSuccess: () => { diff --git a/frontend/src/components/SidePanelLayoutEditor.vue b/frontend/src/components/SidePanelLayoutEditor.vue index 3b7ab0a3..09493c61 100644 --- a/frontend/src/components/SidePanelLayoutEditor.vue +++ b/frontend/src/components/SidePanelLayoutEditor.vue @@ -41,7 +41,9 @@ variant="ghost" @click="section.editingLabel = true" > - + + + (task.assigned_to = option)" :placeholder="__('John Doe')" + :filters="{ + name: ['in', users.data?.crmUsers?.map((user) => user.name)], + }" :hideMe="true" > @@ -94,7 +97,7 @@ const props = defineProps({ }, }) -const { getUser } = usersStore() +const { users, getUser } = usersStore() function updateTaskStatus(status) { props.task.status = status diff --git a/frontend/src/composables/modals.js b/frontend/src/composables/modals.js index 4fb354c7..bb7e3986 100644 --- a/frontend/src/composables/modals.js +++ b/frontend/src/composables/modals.js @@ -6,4 +6,6 @@ export const quickEntryProps = ref({}); export const showAddressModal = ref(false); export const addressProps = ref({}); -export const showAboutModal = ref(false); \ No newline at end of file +export const showAboutModal = ref(false); + +export const showChangePasswordModal = ref(false); \ No newline at end of file diff --git a/frontend/src/data/document.js b/frontend/src/data/document.js index 7d456676..41bdea34 100644 --- a/frontend/src/data/document.js +++ b/frontend/src/data/document.js @@ -23,7 +23,14 @@ export function useDocument(doctype, docname) { toast.success(__('Document updated successfully')) }, onError: (err) => { - toast.error(__('Error updating document')) + let errorMessage = __('Error updating document') + if (err.exc_type == 'MandatoryError') { + const fieldName = err.messages + .map((msg) => msg.split(': ')[2].trim()) + .join(', ') + errorMessage = __('Mandatory field error: {0}', [fieldName]) + } + toast.error(errorMessage) console.error(err) }, }, @@ -117,20 +124,26 @@ export function useDocument(doctype, docname) { await trigger(handler) } - async function triggerOnChange(fieldname, row) { + async function triggerOnChange(fieldname, value, row) { + const oldValue = documentsCache[doctype][docname || ''].doc[fieldname] + documentsCache[doctype][docname || ''].doc[fieldname] = value + const handler = async function () { + this.value = value + this.oldValue = oldValue if (row) { this.currentRowIdx = row.idx - this.value = row[fieldname] - this.oldValue = getOldValue(fieldname, row) - } else { - this.value = documentsCache[doctype][docname || ''].doc[fieldname] - this.oldValue = getOldValue(fieldname) } await this[fieldname]?.() } - await trigger(handler, row) + try { + await trigger(handler, row) + } catch (error) { + documentsCache[doctype][docname || ''].doc[fieldname] = oldValue + console.error(handler) + throw error + } } async function triggerOnRowAdd(row) { diff --git a/frontend/src/data/script.js b/frontend/src/data/script.js index b809f744..47185bab 100644 --- a/frontend/src/data/script.js +++ b/frontend/src/data/script.js @@ -46,6 +46,11 @@ export function getScript(doctype, view = 'Form') { helpers.router = router helpers.call = call + helpers.throwError = (message) => { + toast.error(message || __('An error occurred')) + throw new Error(message || __('An error occurred')) + } + helpers.crm = { makePhoneCall: makeCall, } diff --git a/frontend/src/pages/Deal.vue b/frontend/src/pages/Deal.vue index e3d09fa7..d8b82f02 100644 --- a/frontend/src/pages/Deal.vue +++ b/frontend/src/pages/Deal.vue @@ -23,7 +23,14 @@ /> @@ -85,42 +92,50 @@ - + + + - - + + + + - - + + + + - + + + @@ -232,14 +247,18 @@ }) " > - + + + - + + + @@ -361,7 +380,7 @@ import { toast, } from 'frappe-ui' import { useOnboarding } from 'frappe-ui/frappe' -import { ref, computed, h, onMounted, onBeforeUnmount } from 'vue' +import { ref, computed, h, onMounted, onBeforeUnmount, nextTick } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useActiveTabManager } from '@/composables/useActiveTabManager' @@ -723,10 +742,17 @@ async function deleteDeal(name) { const activities = ref(null) function openEmailBox() { - activities.value.emailBox.show = true + let currentTab = tabs.value[tabIndex.value] + if (!['Emails', 'Comments', 'Activities'].includes(currentTab.name)) { + activities.value.changeTabTo('emails') + } + nextTick(() => (activities.value.emailBox.show = true)) } -const { assignees, document } = useDocument('CRM Deal', props.dealId) +const { assignees, document, triggerOnChange } = useDocument( + 'CRM Deal', + props.dealId, +) function reloadAssignees(data) { if (data?.hasOwnProperty('deal_owner')) { diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index d02d5af9..ac0ded63 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -23,7 +23,14 @@ /> @@ -135,42 +142,50 @@ : toast.error(__('No phone number set')) " > - + + + - - + + + + - - + + + + - + + + @@ -231,14 +246,18 @@ class="w-7" @click="openQuickEntryModal" > - + + + - + + + @@ -379,7 +398,7 @@ import { toast, } from 'frappe-ui' import { useOnboarding } from 'frappe-ui/frappe' -import { ref, reactive, computed, onMounted, watch } from 'vue' +import { ref, reactive, computed, onMounted, watch, nextTick } from 'vue' import { useRouter, useRoute } from 'vue-router' import { useActiveTabManager } from '@/composables/useActiveTabManager' @@ -610,10 +629,8 @@ const existingOrganizationChecked = ref(false) const existingContact = ref('') const existingOrganization = ref('') -const { triggerConvertToDeal, assignees, document } = useDocument( - 'CRM Lead', - props.leadId, -) +const { triggerConvertToDeal, triggerOnChange, assignees, document } = + useDocument('CRM Lead', props.leadId) async function convertToDeal() { if (existingContactChecked.value && !existingContact.value) { @@ -665,7 +682,11 @@ async function convertToDeal() { const activities = ref(null) function openEmailBox() { - activities.value.emailBox.show = true + let currentTab = tabs.value[tabIndex.value] + if (!['Emails', 'Comments', 'Activities'].includes(currentTab.name)) { + activities.value.changeTabTo('emails') + } + nextTick(() => (activities.value.emailBox.show = true)) } const deal = reactive({}) diff --git a/frontend/src/pages/MobileDeal.vue b/frontend/src/pages/MobileDeal.vue index 71995d88..1920266f 100644 --- a/frontend/src/pages/MobileDeal.vue +++ b/frontend/src/pages/MobileDeal.vue @@ -11,7 +11,14 @@ @@ -612,7 +619,10 @@ async function deleteDeal(name) { router.push({ name: 'Deals' }) } -const { assignees, document } = useDocument('CRM Deal', props.dealId) +const { assignees, document, triggerOnChange } = useDocument( + 'CRM Deal', + props.dealId, +) function reloadAssignees(data) { if (data?.hasOwnProperty('deal_owner')) { diff --git a/frontend/src/pages/MobileLead.vue b/frontend/src/pages/MobileLead.vue index da7574dd..21743037 100644 --- a/frontend/src/pages/MobileLead.vue +++ b/frontend/src/pages/MobileLead.vue @@ -11,7 +11,14 @@ @@ -461,7 +468,10 @@ async function convertToDeal() { } } -const { assignees, document } = useDocument('CRM Lead', props.leadId) +const { assignees, document, triggerOnChange } = useDocument( + 'CRM Lead', + props.leadId, +) function reloadAssignees(data) { if (data?.hasOwnProperty('lead_owner')) { diff --git a/frontend/src/stores/statuses.js b/frontend/src/stores/statuses.js index 9a63da26..0bcdbf3d 100644 --- a/frontend/src/stores/statuses.js +++ b/frontend/src/stores/statuses.js @@ -77,7 +77,12 @@ export const statusesStore = defineStore('crm-statuses', () => { return communicationStatuses[name] } - function statusOptions(doctype, document, statuses = []) { + function statusOptions( + doctype, + document, + statuses = [], + triggerOnChange = null, + ) { let statusesByName = doctype == 'deal' ? dealStatusesByName : leadStatusesByName @@ -85,7 +90,7 @@ export const statusesStore = defineStore('crm-statuses', () => { statuses = document.statuses } - if (statuses.length) { + if (statuses?.length) { statusesByName = statuses.reduce((acc, status) => { acc[status] = statusesByName[status] return acc @@ -98,10 +103,10 @@ export const statusesStore = defineStore('crm-statuses', () => { label: statusesByName[status]?.name, value: statusesByName[status]?.name, icon: () => h(IndicatorIcon, { class: statusesByName[status]?.color }), - onClick: () => { + onClick: async () => { capture('status_changed', { doctype, status }) if (document) { - document.doc.status = statusesByName[status]?.name + await triggerOnChange?.('status', statusesByName[status]?.name) document.save.submit() } }, diff --git a/frontend/src/stores/users.js b/frontend/src/stores/users.js index 5965c537..3614bda5 100644 --- a/frontend/src/stores/users.js +++ b/frontend/src/stores/users.js @@ -12,17 +12,17 @@ export const usersStore = defineStore('crm-users', () => { const users = createResource({ url: 'crm.api.session.get_users', - cache: 'Users', + cache: 'crm-users', initialData: [], auto: true, - transform(users) { - for (let user of users) { + transform([allUsers, crmUsers]) { + for (let user of allUsers) { usersByName[user.name] = user if (user.name === 'Administrator') { usersByName[user.email] = user } } - return users + return { allUsers, crmUsers } }, onError(error) { if (error && error.exc_type === 'AuthenticationError') { @@ -49,6 +49,10 @@ export const usersStore = defineStore('crm-users', () => { return usersByName[email] } + function isAdmin(email) { + return getUser(email).is_admin + } + function isManager(email) { return getUser(email).is_manager } @@ -57,10 +61,20 @@ export const usersStore = defineStore('crm-users', () => { return getUser(email).is_agent } + function getUserRole(email) { + const user = getUser(email) + if (user && user.role) { + return user.role + } + return null + } + return { users, getUser, + isAdmin, isManager, isAgent, + getUserRole, } }) diff --git a/yarn.lock b/yarn.lock index 8862db5d..7a442893 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1320,6 +1320,11 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.10.3.tgz#bf8efb3a580c75b86dce505a63f1ca7450a9aaea" integrity sha512-AlxXXPCWIvw8hQUDFRskasj32iMNB8Sb19VgyFWqwvntGs2/UffNu8VdsVqxD2HpZ0g5rLYCYtSW4wigs9R3og== +"@tiptap/extension-heading@^2.12.0": + version "2.14.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.14.0.tgz#c5a9dc761712e9c87073ba8446548cbe4d403360" + integrity sha512-vM//6G3Ox3mxPv9eilhrDqylELCc8kEP1aQ4xUuOw7vCidjNtGggOa1ERnnpV2dCa2A9E8y4FHtN4Xh29stXQg== + "@tiptap/extension-highlight@^2.0.3": version "2.10.3" resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.10.3.tgz#d94667d435d9dc556b06e7b764449dc2a6c18743" @@ -2565,10 +2570,10 @@ fraction.js@^4.3.7: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== -frappe-ui@^0.1.145: - version "0.1.145" - resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.145.tgz#19ec429badf85f3f2c45a85ec13c3c462ec11ee9" - integrity sha512-DnnSJREu/EpUAJGNFaXEUF3re0hQMmLBOX/MSW9AsQtnCJwXkO5VbH/dyVHAZjqdb9Do3CNQF33/HB4NibNI8Q== +frappe-ui@^0.1.156: + version "0.1.156" + resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.156.tgz#1a476aec80b0e0f72470f9dc3990bb023b2ebb09" + integrity sha512-JsIODLL7YYFhKSYfWJJ9M1+VMmj8M0xZ1D5M7Cx0c+OWg5Qm0xda1592Tr+om1a7u0zWcfjuQnW9mHN1lW5HIA== dependencies: "@floating-ui/vue" "^1.1.6" "@headlessui/vue" "^1.7.14" @@ -2579,6 +2584,7 @@ frappe-ui@^0.1.145: "@tiptap/extension-code-block" "^2.11.9" "@tiptap/extension-code-block-lowlight" "^2.11.5" "@tiptap/extension-color" "^2.0.3" + "@tiptap/extension-heading" "^2.12.0" "@tiptap/extension-highlight" "^2.0.3" "@tiptap/extension-image" "^2.0.3" "@tiptap/extension-link" "^2.0.3" @@ -2599,6 +2605,7 @@ frappe-ui@^0.1.145: dayjs "^1.11.13" echarts "^5.6.0" feather-icons "^4.28.0" + highlight.js "^11.11.1" idb-keyval "^6.2.0" lowlight "^3.3.0" lucide-static "^0.479.0" @@ -2806,7 +2813,7 @@ hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" -highlight.js@~11.11.0: +highlight.js@^11.11.1, highlight.js@~11.11.0: version "11.11.1" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585" integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==
+ {{ + __( + 'Add existing system users to this CRM. Assign them a role to grant access with their current credentials.', + ) + }} +
+ {{ newPasswordMessage }} +
+ {{ confirmPasswordMessage }} +
+
{{ emailAccount.email_account_name }}
+ {{ + __( + '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.', + ) + }} +
{{ __('Choose the email service provider you want to configure.') }}
{{ field.description }}
{{ serviceName }}
+ {{ __('Configure general settings for your CRM') }} +