diff --git a/crm/api/doc.py b/crm/api/doc.py index a8627584..8caf0d04 100644 --- a/crm/api/doc.py +++ b/crm/api/doc.py @@ -537,7 +537,7 @@ def get_records_based_on_order(doctype, rows, filters, page_length, order): @frappe.whitelist() -def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False): +def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False, only_required=False): not_allowed_fieldtypes = [ "Tab Break", "Section Break", @@ -572,6 +572,9 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False): if not restricted_fieldtypes or field.get("fieldtype") not in restricted_fieldtypes: fields.append(field) + if only_required: + fields = [field for field in fields if field.get("reqd")] + if as_array: return fields diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json index 43019c5c..c61189e2 100644 --- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json +++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json @@ -27,7 +27,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Type", - "options": "Quick Entry\nSide Panel\nData Fields\nGrid Row" + "options": "Quick Entry\nSide Panel\nData Fields\nGrid Row\nRequired Fields" }, { "fieldname": "section_break_ttpm", @@ -46,7 +46,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-01-02 22:12:51.663011", + "modified": "2025-02-21 13:09:49.573515", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Fields Layout", diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py index 271a5da9..e44e3e20 100644 --- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py +++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py @@ -24,7 +24,7 @@ def get_fields_layout(doctype: str, type: str, parent_doctype: str | None = None if layout and layout.layout: tabs = json.loads(layout.layout) - if not tabs: + if not tabs and type != "Required Fields": tabs = get_default_layout(doctype) has_tabs = tabs[0].get("sections") if tabs and tabs[0] else False diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.py b/crm/fcrm/doctype/crm_lead/crm_lead.py index bdf0885f..a7a41d34 100644 --- a/crm/fcrm/doctype/crm_lead/crm_lead.py +++ b/crm/fcrm/doctype/crm_lead/crm_lead.py @@ -197,8 +197,8 @@ class CRMLead(Document): return False - def create_deal(self, contact, organization): - deal = frappe.new_doc("CRM Deal") + def create_deal(self, contact, organization, deal=None): + new_deal = frappe.new_doc("CRM Deal") lead_deal_map = { "lead_owner": "deal_owner", @@ -245,13 +245,13 @@ class CRMLead(Document): if field.fieldname in lead_deal_map: fieldname = lead_deal_map[field.fieldname] - if hasattr(deal, fieldname): + if hasattr(new_deal, fieldname): if fieldname == "organization": - deal.update({fieldname: organization}) + new_deal.update({fieldname: organization}) else: - deal.update({fieldname: self.get(field.fieldname)}) + new_deal.update({fieldname: self.get(field.fieldname)}) - deal.update( + new_deal.update( { "lead": self.name, "contacts": [{"contact": contact}], @@ -259,7 +259,7 @@ class CRMLead(Document): ) if self.first_responded_on: - deal.update( + new_deal.update( { "sla_creation": self.sla_creation, "response_by": self.response_by, @@ -270,8 +270,11 @@ class CRMLead(Document): } ) - deal.insert(ignore_permissions=True) - return deal.name + if deal: + new_deal.update(deal) + + new_deal.insert(ignore_permissions=True) + return new_deal.name def set_sla(self): """ @@ -297,8 +300,8 @@ class CRMLead(Document): if sla: sla.apply(self) - def convert_to_deal(self): - return convert_to_deal(lead=self.name, doc=self) + def convert_to_deal(self, deal=None): + return convert_to_deal(lead=self.name, doc=self, deal=deal) @staticmethod def get_non_filterable_fields(): @@ -380,7 +383,7 @@ class CRMLead(Document): @frappe.whitelist() -def convert_to_deal(lead, doc=None): +def convert_to_deal(lead, doc=None, deal=None): if not (doc and doc.flags.get("ignore_permissions")) and not frappe.has_permission( "CRM Lead", "write", lead ): @@ -394,5 +397,5 @@ def convert_to_deal(lead, doc=None): lead.db_set("communication_status", "Replied") contact = lead.create_contact(False) organization = lead.create_organization() - deal = lead.create_deal(contact, organization) - return deal + _deal = lead.create_deal(contact, organization, deal) + return _deal diff --git a/frontend/package.json b/frontend/package.json index 1ac29f78..88fa0c76 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,7 @@ "@vueuse/core": "^10.3.0", "@vueuse/integrations": "^10.3.0", "feather-icons": "^4.28.0", - "frappe-ui": "^0.1.105", + "frappe-ui": "^0.1.110", "gemoji": "^8.1.0", "lodash": "^4.17.21", "mime": "^4.0.1", diff --git a/frontend/src/components/FieldLayoutEditor.vue b/frontend/src/components/FieldLayoutEditor.vue index 5dcff8e5..8804b920 100644 --- a/frontend/src/components/FieldLayoutEditor.vue +++ b/frontend/src/components/FieldLayoutEditor.vue @@ -226,6 +226,10 @@ import { ref, computed, watch } from 'vue' const props = defineProps({ tabs: Object, doctype: String, + onlyRequired: { + type: Boolean, + default: false, + }, }) const tabIndex = ref(0) @@ -249,6 +253,7 @@ const params = computed(() => { doctype: props.doctype, restricted_fieldtypes: restrictedFieldTypes, as_array: true, + only_required: props.onlyRequired, } }) diff --git a/frontend/src/components/Modals/DealModal.vue b/frontend/src/components/Modals/DealModal.vue index 9c38f1ef..7bd0b592 100644 --- a/frontend/src/components/Modals/DealModal.vue +++ b/frontend/src/components/Modals/DealModal.vue @@ -190,6 +190,13 @@ function createDeal() { if (deal.website && !deal.website.startsWith('http')) { deal.website = 'https://' + deal.website } + if (chooseExistingContact.value) { + deal['first_name'] = null + deal['last_name'] = null + deal['email'] = null + deal['mobile_no'] = null + } else deal['contact'] = null + createResource({ url: 'crm.fcrm.doctype.crm_deal.crm_deal.create_deal', params: { args: deal }, diff --git a/frontend/src/components/Modals/QuickEntryModal.vue b/frontend/src/components/Modals/QuickEntryModal.vue index ec237520..7c3af8a8 100644 --- a/frontend/src/components/Modals/QuickEntryModal.vue +++ b/frontend/src/components/Modals/QuickEntryModal.vue @@ -35,6 +35,7 @@ v-if="!preview" :tabs="tabs.data" :doctype="_doctype" + :onlyRequired="onlyRequired" /> @@ -55,6 +56,10 @@ const props = defineProps({ type: String, default: 'CRM Lead', }, + onlyRequired: { + type: Boolean, + default: false, + }, }) const show = defineModel() @@ -64,12 +69,13 @@ const dirty = ref(false) const preview = ref(false) function getParams() { - return { doctype: _doctype.value, type: 'Quick Entry' } + let type = props.onlyRequired ? 'Required Fields' : 'Quick Entry' + return { doctype: _doctype.value, type } } const tabs = createResource({ url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', - cache: ['QuickEntryModal', _doctype.value], + cache: ['QuickEntryModal', _doctype.value, props.onlyRequired], params: getParams(), onSuccess(data) { tabs.originalData = JSON.parse(JSON.stringify(data)) @@ -106,11 +112,12 @@ function saveChanges() { }) }) loading.value = true + let type = props.onlyRequired ? 'Required Fields' : 'Quick Entry' call( 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.save_fields_layout', { doctype: _doctype.value, - type: 'Quick Entry', + type: type, layout: JSON.stringify(_tabs), }, ).then(() => { diff --git a/frontend/src/components/SignupBanner.vue b/frontend/src/components/SignupBanner.vue index 7586f463..1faed676 100644 --- a/frontend/src/components/SignupBanner.vue +++ b/frontend/src/components/SignupBanner.vue @@ -21,6 +21,7 @@ diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index 347935eb..1e8d728b 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -186,7 +186,6 @@ + + { + createToast({ + title: __('Error converting to deal'), + text: __(err.messages?.[0]), + icon: 'x', + iconClasses: 'text-ink-red-4', + }) + }) + if (_deal) { capture('convert_lead_to_deal') if (updated) { await contacts.reload() } - router.push({ name: 'Deal', params: { dealId: deal } }) + router.push({ name: 'Deal', params: { dealId: _deal } }) } } } @@ -620,4 +672,50 @@ const activities = ref(null) function openEmailBox() { activities.value.emailBox.show = true } + +const deal = reactive({}) + +const dealStatuses = computed(() => { + let statuses = statusOptions('deal') + if (!deal.status) { + deal.status = statuses[0].value + } + return statuses +}) + +const dealTabs = createResource({ + url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', + cache: ['RequiredFields', 'CRM Deal'], + params: { doctype: 'CRM Deal', type: 'Required Fields' }, + auto: true, + transform: (_tabs) => { + let hasFields = false + let parsedTabs = _tabs.forEach((tab) => { + tab.sections.forEach((section) => { + section.columns.forEach((column) => { + column.fields.forEach((field) => { + hasFields = true + if (field.fieldname == 'status') { + field.fieldtype = 'Select' + field.options = dealStatuses.value + field.prefix = getDealStatus(deal.status).color + } + + if (field.fieldtype === 'Table') { + deal[field.fieldname] = [] + } + }) + }) + }) + }) + return hasFields ? parsedTabs : [] + }, +}) + +const showQuickEntryModal = ref(false) + +function openQuickEntryModal() { + showQuickEntryModal.value = true + showConvertToDealModal.value = false +} diff --git a/yarn.lock b/yarn.lock index 7b7baaa4..25bbca56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1136,10 +1136,10 @@ dependencies: mini-svg-data-uri "^1.2.3" -"@tailwindcss/typography@^0.5.0": - version "0.5.15" - resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.15.tgz#007ab9870c86082a1c76e5b3feda9392c7c8d648" - integrity sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA== +"@tailwindcss/typography@^0.5.16": + version "0.5.16" + resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.16.tgz#a926c8f44d5c439b2915e231cad80058850047c6" + integrity sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA== dependencies: lodash.castarray "^4.4.0" lodash.isplainobject "^4.0.6" @@ -2388,15 +2388,15 @@ 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.105: - version "0.1.105" - resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.105.tgz#3bdf3c458ba27f27ff2f2a28cf7eb6f9ed872367" - integrity sha512-9bZ/hj/HhQ9vp7DxE8aOKS8HqwETZrKT3IhSzjpYOk21efK8QwdbQ9sp0t4m3UII+HaUTSOTHnFzF7y9EhRZxg== +frappe-ui@^0.1.110: + version "0.1.110" + resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.110.tgz#dbe02d294cb0aeb0a4c1b3000682093c309450ae" + integrity sha512-kFah6SoPauULXaeSbljNUq595/82VmY4k4+KA8zi4sXxpn4sXYi12qUl/1I8GOBhsCQQizmoh46DO7e/uU2M1A== dependencies: "@headlessui/vue" "^1.7.14" "@popperjs/core" "^2.11.2" "@tailwindcss/forms" "^0.5.3" - "@tailwindcss/typography" "^0.5.0" + "@tailwindcss/typography" "^0.5.16" "@tiptap/extension-color" "^2.0.3" "@tiptap/extension-highlight" "^2.0.3" "@tiptap/extension-image" "^2.0.3"