From b831ea3c47755de61924dc24457477c607e4fedd Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 19 May 2025 17:58:17 +0530 Subject: [PATCH 1/7] fix: allow read permission for form script --- .../doctype/crm_form_script/crm_form_script.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.json b/crm/fcrm/doctype/crm_form_script/crm_form_script.json index 9246913a..f261f564 100644 --- a/crm/fcrm/doctype/crm_form_script/crm_form_script.json +++ b/crm/fcrm/doctype/crm_form_script/crm_form_script.json @@ -65,7 +65,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-09-16 19:40:19.340948", + "modified": "2025-05-19 17:57:24.610295", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Form Script", @@ -83,9 +83,19 @@ "role": "Sales Manager", "share": 1, "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1 } ], + "row_format": "Dynamic", "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} From e3f007957887330bc93a286956b526e52077d504 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 19 May 2025 18:02:30 +0530 Subject: [PATCH 2/7] fix: only set FCRM module if user is Sales User --- crm/fcrm/doctype/crm_invitation/crm_invitation.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crm/fcrm/doctype/crm_invitation/crm_invitation.py b/crm/fcrm/doctype/crm_invitation/crm_invitation.py index f4c9a8f9..625cd882 100644 --- a/crm/fcrm/doctype/crm_invitation/crm_invitation.py +++ b/crm/fcrm/doctype/crm_invitation/crm_invitation.py @@ -21,7 +21,7 @@ class CRMInvitation(Document): if frappe.local.dev_server: print(f"Invite link for {self.email}: {invite_link}") - title = f"Frappe CRM" + title = "Frappe CRM" template = "crm_invitation" frappe.sendmail( @@ -44,12 +44,24 @@ class CRMInvitation(Document): user = self.create_user_if_not_exists() user.append_roles(self.role) + if self.role == "Sales User": + self.update_module_in_user(user, "FCRM") user.save(ignore_permissions=True) self.status = "Accepted" self.accepted_at = frappe.utils.now() self.save(ignore_permissions=True) + def update_module_in_user(self, 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) + def create_user_if_not_exists(self): if not frappe.db.exists("User", self.email): first_name = self.email.split("@")[0].title() From f5a3fccad319ef2100e4ef9a674481f6f7073b0b Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 19 May 2025 19:05:37 +0530 Subject: [PATCH 3/7] fix: do not show contacts in dropdown in invite member page --- crm/api/__init__.py | 6 +++++ .../Controls/MultiSelectEmailInput.vue | 25 +++++++++++++++++-- .../components/Settings/InviteMemberPage.vue | 14 ++++++++--- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/crm/api/__init__.py b/crm/api/__init__.py index cb56e3bd..cc2fda67 100644 --- a/crm/api/__init__.py +++ b/crm/api/__init__.py @@ -123,6 +123,12 @@ def invite_by_email(emails: str, role: str): for email in to_invite: frappe.get_doc(doctype="CRM Invitation", email=email, role=role).insert(ignore_permissions=True) + return { + "existing_members": existing_members, + "existing_invites": existing_invites, + "to_invite": to_invite, + } + @frappe.whitelist() def get_file_uploader_defaults(doctype: str): diff --git a/frontend/src/components/Controls/MultiSelectEmailInput.vue b/frontend/src/components/Controls/MultiSelectEmailInput.vue index bf1d734b..b5ba3f84 100644 --- a/frontend/src/components/Controls/MultiSelectEmailInput.vue +++ b/frontend/src/components/Controls/MultiSelectEmailInput.vue @@ -58,6 +58,21 @@ class="p-1.5 max-h-[12rem] overflow-y-auto" static > +
+ + {{ + fetchContacts + ? __('No results found') + : __('Type an email address to add') + }} +
`${value} is an Invalid value`, }, + fetchContacts: { + type: Boolean, + default: true, + }, }) const values = defineModel() @@ -191,17 +210,19 @@ const filterOptions = createResource({ }) const options = computed(() => { - let searchedContacts = filterOptions.data || [] + let searchedContacts = props.fetchContacts ? filterOptions.data : [] if (!searchedContacts.length && query.value) { searchedContacts.push({ label: query.value, value: query.value, }) } - return searchedContacts + return searchedContacts || [] }) function reload(val) { + if (!props.fetchContacts) return + filterOptions.update({ params: { txt: val }, }) diff --git a/frontend/src/components/Settings/InviteMemberPage.vue b/frontend/src/components/Settings/InviteMemberPage.vue index e81a1630..3f0cac8d 100644 --- a/frontend/src/components/Settings/InviteMemberPage.vue +++ b/frontend/src/components/Settings/InviteMemberPage.vue @@ -20,6 +20,7 @@ :error-message=" (value) => __('{0} is an invalid email address', [value]) " + :fetchContacts="false" /> Date: Mon, 19 May 2025 20:40:11 +0530 Subject: [PATCH 4/7] fix: use toast.create api instead of createToast --- .../erpnext_crm_settings.py | 2 +- frontend/components.d.ts | 3 + .../FilesUploader/FilesUploader.vue | 9 +-- .../FilesUploader/FilesUploaderArea.vue | 33 ++++------ frontend/src/components/ListBulkActions.vue | 28 +++----- frontend/src/components/Settings/EmailAdd.vue | 9 +-- .../src/components/Settings/EmailEdit.vue | 15 +---- .../components/Settings/ProfileSettings.vue | 9 +-- .../src/components/Settings/SettingsPage.vue | 17 ++--- .../components/Settings/TelephonySettings.vue | 38 ++--------- frontend/src/components/Telephony/CallUI.vue | 17 ++--- .../src/components/Telephony/ExotelCallUI.vue | 10 +-- frontend/src/components/ViewControls.vue | 10 +-- frontend/src/data/document.js | 18 ++---- frontend/src/data/script.js | 5 +- frontend/src/pages/Contact.vue | 33 ++-------- frontend/src/pages/Deal.vue | 64 +++++-------------- frontend/src/pages/Lead.vue | 53 ++++----------- frontend/src/pages/MobileContact.vue | 33 ++-------- frontend/src/pages/MobileDeal.vue | 50 ++++----------- frontend/src/pages/MobileLead.vue | 40 +++--------- frontend/src/pages/MobileOrganization.vue | 21 +----- frontend/src/pages/Organization.vue | 21 +----- frontend/src/utils/index.js | 29 ++------- frontend/vite.config.js | 8 +-- 25 files changed, 138 insertions(+), 437 deletions(-) diff --git a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py index f530a65d..7a004387 100644 --- a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py +++ b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py @@ -264,7 +264,7 @@ def create_customer_in_remote_site(customer, erpnext_crm_settings): @frappe.whitelist() def get_crm_form_script(): return """ -async function setupForm({ doc, call, $dialog, updateField, createToast }) { +async function setupForm({ doc, call, $dialog, updateField, toast }) { let actions = []; let is_erpnext_integration_enabled = await call("frappe.client.get_single_value", {doctype: "ERPNext CRM Settings", field: "enabled"}); if (!["Lost", "Won"].includes(doc?.status) && is_erpnext_integration_enabled) { diff --git a/frontend/components.d.ts b/frontend/components.d.ts index bfacdcf1..d824c8a7 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -150,6 +150,8 @@ 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'] + LucidePlus: typeof import('~icons/lucide/plus')['default'] MarkAsDoneIcon: typeof import('./src/components/Icons/MarkAsDoneIcon.vue')['default'] MaximizeIcon: typeof import('./src/components/Icons/MaximizeIcon.vue')['default'] MenuIcon: typeof import('./src/components/Icons/MenuIcon.vue')['default'] @@ -173,6 +175,7 @@ declare module 'vue' { OrganizationsIcon: typeof import('./src/components/Icons/OrganizationsIcon.vue')['default'] OrganizationsListView: typeof import('./src/components/ListViews/OrganizationsListView.vue')['default'] OutboundCallIcon: typeof import('./src/components/Icons/OutboundCallIcon.vue')['default'] + Password: typeof import('./src/components/Controls/Password.vue')['default'] PauseIcon: typeof import('./src/components/Icons/PauseIcon.vue')['default'] PhoneIcon: typeof import('./src/components/Icons/PhoneIcon.vue')['default'] PinIcon: typeof import('./src/components/Icons/PinIcon.vue')['default'] diff --git a/frontend/src/components/FilesUploader/FilesUploader.vue b/frontend/src/components/FilesUploader/FilesUploader.vue index 2daff30e..8fd5a08e 100644 --- a/frontend/src/components/FilesUploader/FilesUploader.vue +++ b/frontend/src/components/FilesUploader/FilesUploader.vue @@ -104,7 +104,7 @@ import FilesUploaderArea from '@/components/FilesUploader/FilesUploaderArea.vue' import FilesUploadHandler from './filesUploaderHandler' import { isMobileView } from '@/composables/settings' -import { createToast } from '@/utils' +import { toast } from 'frappe-ui' import { ref, computed } from 'vue' const props = defineProps({ @@ -165,12 +165,7 @@ function attachFiles() { function uploadViaWebLink() { let fileUrl = filesUploaderArea.value.webLink if (!fileUrl) { - createToast({ - title: __('Error'), - title: __('Please enter a valid URL'), - icon: 'x', - iconClasses: 'text-ink-red-4', - }) + toast.error(__('Please enter a valid URL')) return } fileUrl = decodeURI(fileUrl) diff --git a/frontend/src/components/FilesUploader/FilesUploaderArea.vue b/frontend/src/components/FilesUploader/FilesUploaderArea.vue index cf69c2fb..d78d0846 100644 --- a/frontend/src/components/FilesUploader/FilesUploaderArea.vue +++ b/frontend/src/components/FilesUploader/FilesUploaderArea.vue @@ -126,8 +126,13 @@ import FileTextIcon from '@/components/Icons/FileTextIcon.vue' import FileAudioIcon from '@/components/Icons/FileAudioIcon.vue' import FileVideoIcon from '@/components/Icons/FileVideoIcon.vue' -import { createToast, formatDate, convertSize } from '@/utils' -import { FormControl, CircularProgressBar, createResource } from 'frappe-ui' +import { formatDate, convertSize } from '@/utils' +import { + FormControl, + CircularProgressBar, + createResource, + toast, +} from 'frappe-ui' import { ref, onMounted, watch, onUnmounted } from 'vue' const props = defineProps({ @@ -324,24 +329,18 @@ function checkRestrictions(file) { if (!isCorrectType) { console.warn('File skipped because of invalid file type', file) - createToast({ - title: __('File "{0}" was skipped because of invalid file type', [ - file.name, - ]), - icon: 'alert-circle', - iconClasses: 'text-orange-600', - }) + toast.warning( + __('File "{0}" was skipped because of invalid file type', [file.name]), + ) } if (!validFileSize) { console.warn('File skipped because of invalid file size', file.size, file) - createToast({ - title: __('File "{0}" was skipped because size exceeds {1} MB', [ + toast.warning( + __('File "{0}" was skipped because size exceeds {1} MB', [ file.name, maxFileSize / (1024 * 1024), ]), - icon: 'alert-circle', - iconClasses: 'text-orange-600', - }) + ) } return isCorrectType && validFileSize @@ -363,11 +362,7 @@ function showMaxFilesNumberWarning(file, maxNumberOfFiles) { ) } - createToast({ - title: message, - icon: 'alert-circle', - iconClasses: 'text-orange-600', - }) + toast.warning(message) } function removeFile(name) { diff --git a/frontend/src/components/ListBulkActions.vue b/frontend/src/components/ListBulkActions.vue index f5d58845..76b41650 100644 --- a/frontend/src/components/ListBulkActions.vue +++ b/frontend/src/components/ListBulkActions.vue @@ -19,10 +19,10 @@ diff --git a/frontend/src/components/FieldLayout/Field.vue b/frontend/src/components/FieldLayout/Field.vue index 64ba37e7..a190817f 100644 --- a/frontend/src/components/FieldLayout/Field.vue +++ b/frontend/src/components/FieldLayout/Field.vue @@ -159,6 +159,13 @@ :description="field.description" @change="fieldChange($event.target.value, field)" /> +