From 8f256843f206e81ed7dd0f853bd30cac9d150082 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 9 Nov 2023 12:47:08 +0530 Subject: [PATCH 01/51] fix: removed contact creation/updation code from lead --- crm/fcrm/doctype/crm_lead/crm_lead.json | 16 +---- crm/fcrm/doctype/crm_lead/crm_lead.py | 84 +------------------------ 2 files changed, 4 insertions(+), 96 deletions(-) diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.json b/crm/fcrm/doctype/crm_lead/crm_lead.json index 8257a2e5f..269a0e79d 100644 --- a/crm/fcrm/doctype/crm_lead/crm_lead.json +++ b/crm/fcrm/doctype/crm_lead/crm_lead.json @@ -37,9 +37,7 @@ "column_break_sijm", "mobile_no", "column_break_sjtw", - "phone", - "section_break_jyxr", - "contacts" + "phone" ], "fields": [ { @@ -203,16 +201,6 @@ "fieldtype": "Tab Break", "label": "Contact" }, - { - "fieldname": "contacts", - "fieldtype": "Table", - "label": "Contacts", - "options": "CRM Contacts" - }, - { - "fieldname": "section_break_jyxr", - "fieldtype": "Section Break" - }, { "fieldname": "organization", "fieldtype": "Link", @@ -231,7 +219,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-06 21:53:32.542503", + "modified": "2023-11-09 12:44:18.770076", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Lead", diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.py b/crm/fcrm/doctype/crm_lead/crm_lead.py index 56436a7c6..82a822d72 100644 --- a/crm/fcrm/doctype/crm_lead/crm_lead.py +++ b/crm/fcrm/doctype/crm_lead/crm_lead.py @@ -14,9 +14,7 @@ def validate(self): self.set_lead_name() self.set_title() self.validate_email() - if not self.is_new(): - self.validate_contact() - + def set_full_name(self): if self.first_name: self.lead_name = " ".join( @@ -37,7 +35,7 @@ def set_lead_name(self): def set_title(self): self.title = self.organization or self.lead_name - + def validate_email(self): if self.email: if not self.flags.ignore_email_validation: @@ -48,84 +46,6 @@ def validate_email(self): if self.is_new() or not self.image: self.image = has_gravatar(self.email) - - def validate_contact(self): - link = frappe.db.exists("Dynamic Link", {"link_doctype": "CRM Lead", "link_name": self.name}) - - if link: - for field in ["first_name", "last_name", "email", "mobile_no", "phone", "salutation", "image"]: - if self.has_value_changed(field): - contact = frappe.db.get_value("Dynamic Link", link, "parent") - contact_doc = frappe.get_doc("Contact", contact) - contact_doc.update({ - "first_name": self.first_name or self.lead_name, - "last_name": self.last_name, - "salutation": self.salutation, - "image": self.image or "", - }) - if self.has_value_changed("email"): - contact_doc.email_ids = [] - contact_doc.append("email_ids", {"email_id": self.email, "is_primary": 1}) - - if self.has_value_changed("phone"): - contact_doc.append("phone_nos", {"phone": self.phone, "is_primary_phone": 1}) - - if self.has_value_changed("mobile_no"): - contact_doc.phone_nos = [] - contact_doc.append("phone_nos", {"phone": self.mobile_no, "is_primary_mobile_no": 1}) - - contact_doc.save() - break - else: - self.contact_doc = self.create_contact() - self.link_to_contact() - - def before_insert(self): - self.contact_doc = None - self.contact_doc = self.create_contact() - - def after_insert(self): - self.link_to_contact() - - def link_to_contact(self): - # update contact links - if self.contact_doc: - self.contact_doc.append( - "links", {"link_doctype": "CRM Lead", "link_name": self.name, "link_title": self.lead_name} - ) - self.contact_doc.save() - - def create_contact(self): - if not self.lead_name: - self.set_full_name() - self.set_lead_name() - - contact = frappe.new_doc("Contact") - contact.update( - { - "first_name": self.first_name or self.lead_name, - "last_name": self.last_name, - "salutation": self.salutation, - "gender": self.gender, - "designation": self.job_title, - "company_name": self.organization, - "image": self.image or "", - } - ) - - if self.email: - contact.append("email_ids", {"email_id": self.email, "is_primary": 1}) - - if self.phone: - contact.append("phone_nos", {"phone": self.phone, "is_primary_phone": 1}) - - if self.mobile_no: - contact.append("phone_nos", {"phone": self.mobile_no, "is_primary_mobile_no": 1}) - - contact.insert(ignore_permissions=True) - contact.reload() # load changes by hooks on contact - - return contact @staticmethod def sort_options(): From 86fb7dbafa6c8372768d69fca861da7e08ba8c39 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 9 Nov 2023 14:31:12 +0530 Subject: [PATCH 02/51] fix: router to contact after creation --- frontend/src/components/Modals/ContactModal.vue | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/components/Modals/ContactModal.vue b/frontend/src/components/Modals/ContactModal.vue index 5fd0d6cec..f8d91a2bc 100644 --- a/frontend/src/components/Modals/ContactModal.vue +++ b/frontend/src/components/Modals/ContactModal.vue @@ -74,6 +74,7 @@ diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index d8759212d..1f25a4119 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -18,7 +18,7 @@ - +
@@ -54,7 +50,7 @@ > About this lead
- + - - - { createToast({ @@ -459,11 +427,6 @@ const tabs = [ }, ] -function changeLeadImage(file) { - lead.data.image = file.file_url - updateLead('image', file.file_url) -} - function validateFile(file) { let extn = file.name.split('.').pop().toLowerCase() if (!['png', 'jpg', 'jpeg'].includes(extn)) { @@ -519,10 +482,7 @@ const detailSections = computed(() => { { label: 'Web', value: 'Web' }, { label: 'Others', value: 'Others' }, ], - change: (data) => { - lead.data.source = data.value - updateLead('source', data.value) - }, + change: (data) => updateField('source', data.value), }, { label: 'Industry', @@ -535,10 +495,7 @@ const detailSections = computed(() => { { label: 'Banking', value: 'Banking' }, { label: 'Others', value: 'Others' }, ], - change: (data) => { - lead.data.industry = data.value - updateLead('industry', data.value) - }, + change: (data) => updateField('industry', data.value), }, ], }, @@ -562,10 +519,7 @@ const detailSections = computed(() => { { label: 'Madam', value: 'Madam' }, { label: 'Miss', value: 'Miss' }, ], - change: (data) => { - lead.data.salutation = data.value - updateLead('salutation', data.value) - }, + change: (data) => updateField('salutation', data.value), }, { label: 'First name', @@ -597,9 +551,11 @@ const organization = computed(() => { }) function convertToDeal() { - lead.data.status = 'Qualified' - lead.data.converted = 1 - createDeal(lead.data) + updateLead({ status: 'Qualified', converted: 1 }, '', () => { + lead.data.status = 'Qualified' + lead.data.converted = 1 + createDeal(lead.data) + }) } async function createDeal(lead) { @@ -618,8 +574,9 @@ async function createDeal(lead) { } function updateField(name, value) { - lead.data[name] = value - updateLead(name, value) + updateLead(name, value, () => { + lead.data[name] = value + }) } diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index 6467cd014..97ec41c7c 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -78,7 +78,6 @@ export function statusDropdownOptions(data, doctype, action) { label: statuses[status].label, icon: () => h(IndicatorIcon, { class: statuses[status].color }), onClick: () => { - data.status = statuses[status].label action && action('status', statuses[status].label) }, }) From 602a71c25c4ee3c0de4de97366107e7a0319e6ef Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 9 Nov 2023 19:41:31 +0530 Subject: [PATCH 07/51] fix: get contacts along with email_ids & phone_nos --- crm/api/session.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/crm/api/session.py b/crm/api/session.py index 758c53b6c..3d11961f8 100644 --- a/crm/api/session.py +++ b/crm/api/session.py @@ -23,12 +23,37 @@ def get_contacts(): if frappe.session.user == "Guest": frappe.throw("Authentication failed", exc=frappe.AuthenticationError) - contacts = frappe.qb.get_query( + contacts = frappe.get_all( "Contact", - fields=['name', 'first_name', 'last_name', 'full_name', 'image', 'email_id', 'mobile_no', 'phone', 'salutation', 'company_name', 'modified'], + fields=[ + "name", + "salutation", + "first_name", + "last_name", + "full_name", + "image", + "email_id", + "mobile_no", + "phone", + "company_name", + "modified" + ], order_by="first_name asc", distinct=True, - ).run(as_dict=1) + ) + + for contact in contacts: + contact["email_ids"] = frappe.get_all( + "Contact Email", + filters={"parenttype": "Contact", "parent": contact.name}, + fields=["email_id", "is_primary"], + ) + + contact["phone_nos"] = frappe.get_all( + "Contact Phone", + filters={"parenttype": "Contact", "parent": contact.name}, + fields=["phone", "is_primary_phone", "is_primary_mobile_no"], + ) return contacts From d2e4d3683d0a45209f6a923d3a2b1f26872091c8 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 9 Nov 2023 19:43:40 +0530 Subject: [PATCH 08/51] fix: changed frappeui dropdown component to handle footer (for now made a copy) --- .../src/components/frappe-ui/Dropdown.vue | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 frontend/src/components/frappe-ui/Dropdown.vue diff --git a/frontend/src/components/frappe-ui/Dropdown.vue b/frontend/src/components/frappe-ui/Dropdown.vue new file mode 100644 index 000000000..0974ce64d --- /dev/null +++ b/frontend/src/components/frappe-ui/Dropdown.vue @@ -0,0 +1,157 @@ + + + From 67f38c993f71b6c48353a4fd1525c72e4784ff7c Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 9 Nov 2023 19:44:44 +0530 Subject: [PATCH 09/51] fix: create new email/phone no using dropdown also show all emails/phones --- crm/api/contact.py | 48 +++++++ frontend/src/components/DropdownItem.vue | 41 ++++++ frontend/src/pages/Contact.vue | 160 +++++++++++++++++++++-- 3 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 crm/api/contact.py create mode 100644 frontend/src/components/DropdownItem.vue diff --git a/crm/api/contact.py b/crm/api/contact.py new file mode 100644 index 000000000..5711f869b --- /dev/null +++ b/crm/api/contact.py @@ -0,0 +1,48 @@ +import frappe + + +@frappe.whitelist() +def create_new(contact, field, value): + """Create new email or phone for a contact""" + if not frappe.has_permission("Contact", "write", contact): + frappe.throw("Not permitted", frappe.PermissionError) + + contact = frappe.get_doc("Contact", contact) + + if field == "email": + contact.append("email_ids", {"email_id": value}) + elif field in ("mobile_no", "phone"): + contact.append("phone_nos", {"phone": value}) + else: + frappe.throw("Invalid field") + + contact.save() + return True + + +@frappe.whitelist() +def set_as_primary(contact, field, value): + """Set email or phone as primary for a contact""" + if not frappe.has_permission("Contact", "write", contact): + frappe.throw("Not permitted", frappe.PermissionError) + + contact = frappe.get_doc("Contact", contact) + + if field == "email": + for email in contact.email_ids: + if email.email_id == value: + email.is_primary = 1 + else: + email.is_primary = 0 + elif field in ("mobile_no", "phone"): + name = "is_primary_mobile_no" if field == "mobile_no" else "is_primary_phone" + for phone in contact.phone_nos: + if phone.phone == value: + phone.set(name, 1) + else: + phone.set(name, 0) + else: + frappe.throw("Invalid field") + + contact.save() + return True diff --git a/frontend/src/components/DropdownItem.vue b/frontend/src/components/DropdownItem.vue new file mode 100644 index 000000000..5460ff4b4 --- /dev/null +++ b/frontend/src/components/DropdownItem.vue @@ -0,0 +1,41 @@ + + diff --git a/frontend/src/pages/Contact.vue b/frontend/src/pages/Contact.vue index b64589f5d..fe80f88bc 100644 --- a/frontend/src/pages/Contact.vue +++ b/frontend/src/pages/Contact.vue @@ -95,14 +95,40 @@ {{ field.label }}
- + + + +
+ + + From f1f6c911496c48286c9757031ee2bfe5ce617117 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 9 Nov 2023 21:56:26 +0530 Subject: [PATCH 10/51] fix: include contacts with deal --- crm/fcrm/doctype/crm_deal/api.py | 7 +++++++ crm/fcrm/doctype/crm_deal/crm_deal.json | 11 +++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crm/fcrm/doctype/crm_deal/api.py b/crm/fcrm/doctype/crm_deal/api.py index 8f34ea33f..6da461069 100644 --- a/crm/fcrm/doctype/crm_deal/api.py +++ b/crm/fcrm/doctype/crm_deal/api.py @@ -18,4 +18,11 @@ def get_deal(name): frappe.throw(_("Deal not found"), frappe.DoesNotExistError) deal = deal.pop() + + deal["contacts"] = frappe.get_all( + "CRM Contacts", + filters={"parenttype": "CRM Deal", "parent": deal.name}, + fields=["contact"], + ) + return deal diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json index 41289ef09..1363f8099 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.json +++ b/crm/fcrm/doctype/crm_deal/crm_deal.json @@ -21,7 +21,8 @@ "column_break_bqvs", "contacts_tab", "email", - "mobile_no" + "mobile_no", + "contacts" ], "fields": [ { @@ -114,11 +115,17 @@ "options": "Qualification\nDemo/Making\nProposal/Quotation\nNegotiation\nReady to Close\nWon\nLost", "reqd": 1, "search_index": 1 + }, + { + "fieldname": "contacts", + "fieldtype": "Table", + "label": "Contacts", + "options": "CRM Contacts" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-06 21:53:50.442404", + "modified": "2023-11-09 19:58:15.620483", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Deal", From 5cc0430874943356533a9ddcd5be65d71acc9112 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 9 Nov 2023 22:03:37 +0530 Subject: [PATCH 11/51] fix: added contacts section in deal --- frontend/src/pages/Deal.vue | 211 +++++++++++++++--------------------- 1 file changed, 85 insertions(+), 126 deletions(-) diff --git a/frontend/src/pages/Deal.vue b/frontend/src/pages/Deal.vue index 9aa220560..38e326be5 100644 --- a/frontend/src/pages/Deal.vue +++ b/frontend/src/pages/Deal.vue @@ -103,12 +103,12 @@ >
{{ section.label }} @@ -123,6 +123,7 @@ >
- - - - - - - - - -
+
+
+
+ +
+
+ + {{ getContactByName(contact.name).full_name }} +
+
+ + +
+
+ +
+
+ + {{ getContactByName(contact.name).email_id }} +
+
+ + {{ getContactByName(contact.name).mobile_no }} +
+
+
+
+
+
+
+
@@ -336,7 +330,7 @@ import { ref, computed } from 'vue' import { useRouter } from 'vue-router' const { getUser } = usersStore() -const { contacts } = contactsStore() +const { getContactByName, contacts } = contactsStore() const { getOrganization, getOrganizationOptions } = organizationsStore() const router = useRouter() @@ -479,46 +473,12 @@ const detailSections = computed(() => { { label: 'Contacts', opened: true, - fields: [ - { - label: 'Salutation', - type: 'link', - name: 'salutation', - placeholder: 'Mr./Mrs./Ms.', - options: [ - { label: 'Dr', value: 'Dr' }, - { label: 'Mr', value: 'Mr' }, - { label: 'Mrs', value: 'Mrs' }, - { label: 'Ms', value: 'Ms' }, - { label: 'Mx', value: 'Mx' }, - { label: 'Prof', value: 'Prof' }, - { label: 'Master', value: 'Master' }, - { label: 'Madam', value: 'Madam' }, - { label: 'Miss', value: 'Miss' }, - ], - change: (data) => updateField('salutation', data.value), - }, - { - label: 'First name', - type: 'data', - name: 'first_name', - }, - { - label: 'Last name', - type: 'data', - name: 'last_name', - }, - { - label: 'Email', - type: 'email', - name: 'email', - }, - { - label: 'Mobile no.', - type: 'tel', - name: 'mobile_no', - }, - ], + contacts: deal.data.contacts.map((contact) => { + return { + name: contact.contact, + opened: false, + } + }), }, ] }) @@ -536,7 +496,6 @@ function updateField(name, value) {