Merge branch 'develop' into trial-banner-1
This commit is contained in:
commit
93a61f9468
@ -276,7 +276,7 @@ def get_data(
|
|||||||
default_view_filters = {
|
default_view_filters = {
|
||||||
"dt": doctype,
|
"dt": doctype,
|
||||||
"type": view_type or "list",
|
"type": view_type or "list",
|
||||||
"is_default": 1,
|
"is_standard": 1,
|
||||||
"user": frappe.session.user,
|
"user": frappe.session.user,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,7 +537,7 @@ def get_records_based_on_order(doctype, rows, filters, page_length, order):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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 = [
|
not_allowed_fieldtypes = [
|
||||||
"Tab Break",
|
"Tab Break",
|
||||||
"Section 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:
|
if not restricted_fieldtypes or field.get("fieldtype") not in restricted_fieldtypes:
|
||||||
fields.append(field)
|
fields.append(field)
|
||||||
|
|
||||||
|
if only_required:
|
||||||
|
fields = [field for field in fields if field.get("reqd")]
|
||||||
|
|
||||||
if as_array:
|
if as_array:
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|||||||
@ -14,8 +14,3 @@ def get_views(doctype):
|
|||||||
query = query.where(View.dt == doctype)
|
query = query.where(View.dt == doctype)
|
||||||
views = query.run(as_dict=True)
|
views = query.run(as_dict=True)
|
||||||
return views
|
return views
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_default_view():
|
|
||||||
return frappe.db.get_single_value("FCRM Settings", "default_view") or None
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Type",
|
"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",
|
"fieldname": "section_break_ttpm",
|
||||||
@ -46,7 +46,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-02 22:12:51.663011",
|
"modified": "2025-02-21 13:09:49.573515",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM Fields Layout",
|
"name": "CRM Fields Layout",
|
||||||
|
|||||||
@ -24,7 +24,7 @@ def get_fields_layout(doctype: str, type: str, parent_doctype: str | None = None
|
|||||||
if layout and layout.layout:
|
if layout and layout.layout:
|
||||||
tabs = json.loads(layout.layout)
|
tabs = json.loads(layout.layout)
|
||||||
|
|
||||||
if not tabs:
|
if not tabs and type != "Required Fields":
|
||||||
tabs = get_default_layout(doctype)
|
tabs = get_default_layout(doctype)
|
||||||
|
|
||||||
has_tabs = tabs[0].get("sections") if tabs and tabs[0] else False
|
has_tabs = tabs[0].get("sections") if tabs and tabs[0] else False
|
||||||
|
|||||||
@ -197,8 +197,8 @@ class CRMLead(Document):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_deal(self, contact, organization):
|
def create_deal(self, contact, organization, deal=None):
|
||||||
deal = frappe.new_doc("CRM Deal")
|
new_deal = frappe.new_doc("CRM Deal")
|
||||||
|
|
||||||
lead_deal_map = {
|
lead_deal_map = {
|
||||||
"lead_owner": "deal_owner",
|
"lead_owner": "deal_owner",
|
||||||
@ -245,13 +245,13 @@ class CRMLead(Document):
|
|||||||
if field.fieldname in lead_deal_map:
|
if field.fieldname in lead_deal_map:
|
||||||
fieldname = lead_deal_map[field.fieldname]
|
fieldname = lead_deal_map[field.fieldname]
|
||||||
|
|
||||||
if hasattr(deal, fieldname):
|
if hasattr(new_deal, fieldname):
|
||||||
if fieldname == "organization":
|
if fieldname == "organization":
|
||||||
deal.update({fieldname: organization})
|
new_deal.update({fieldname: organization})
|
||||||
else:
|
else:
|
||||||
deal.update({fieldname: self.get(field.fieldname)})
|
new_deal.update({fieldname: self.get(field.fieldname)})
|
||||||
|
|
||||||
deal.update(
|
new_deal.update(
|
||||||
{
|
{
|
||||||
"lead": self.name,
|
"lead": self.name,
|
||||||
"contacts": [{"contact": contact}],
|
"contacts": [{"contact": contact}],
|
||||||
@ -259,7 +259,7 @@ class CRMLead(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.first_responded_on:
|
if self.first_responded_on:
|
||||||
deal.update(
|
new_deal.update(
|
||||||
{
|
{
|
||||||
"sla_creation": self.sla_creation,
|
"sla_creation": self.sla_creation,
|
||||||
"response_by": self.response_by,
|
"response_by": self.response_by,
|
||||||
@ -270,8 +270,11 @@ class CRMLead(Document):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
deal.insert(ignore_permissions=True)
|
if deal:
|
||||||
return deal.name
|
new_deal.update(deal)
|
||||||
|
|
||||||
|
new_deal.insert(ignore_permissions=True)
|
||||||
|
return new_deal.name
|
||||||
|
|
||||||
def set_sla(self):
|
def set_sla(self):
|
||||||
"""
|
"""
|
||||||
@ -297,8 +300,8 @@ class CRMLead(Document):
|
|||||||
if sla:
|
if sla:
|
||||||
sla.apply(self)
|
sla.apply(self)
|
||||||
|
|
||||||
def convert_to_deal(self):
|
def convert_to_deal(self, deal=None):
|
||||||
return convert_to_deal(lead=self.name, doc=self)
|
return convert_to_deal(lead=self.name, doc=self, deal=deal)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_non_filterable_fields():
|
def get_non_filterable_fields():
|
||||||
@ -380,7 +383,7 @@ class CRMLead(Document):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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(
|
if not (doc and doc.flags.get("ignore_permissions")) and not frappe.has_permission(
|
||||||
"CRM Lead", "write", lead
|
"CRM Lead", "write", lead
|
||||||
):
|
):
|
||||||
@ -394,5 +397,5 @@ def convert_to_deal(lead, doc=None):
|
|||||||
lead.db_set("communication_status", "Replied")
|
lead.db_set("communication_status", "Replied")
|
||||||
contact = lead.create_contact(False)
|
contact = lead.create_contact(False)
|
||||||
organization = lead.create_organization()
|
organization = lead.create_organization()
|
||||||
deal = lead.create_deal(contact, organization)
|
_deal = lead.create_deal(contact, organization, deal)
|
||||||
return deal
|
return _deal
|
||||||
|
|||||||
@ -8,7 +8,8 @@ from frappe.model.document import Document
|
|||||||
|
|
||||||
class CRMNotification(Document):
|
class CRMNotification(Document):
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
frappe.publish_realtime("crm_notification")
|
if self.to_user:
|
||||||
|
frappe.publish_realtime("crm_notification", user= self.to_user)
|
||||||
|
|
||||||
def notify_user(args):
|
def notify_user(args):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"label",
|
"label",
|
||||||
"icon",
|
"icon",
|
||||||
"user",
|
"user",
|
||||||
|
"is_standard",
|
||||||
"is_default",
|
"is_default",
|
||||||
"column_break_zacm",
|
"column_break_zacm",
|
||||||
"type",
|
"type",
|
||||||
@ -112,12 +113,6 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Public"
|
"label": "Public"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "is_default",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Default"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "icon",
|
"fieldname": "icon",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@ -174,15 +169,26 @@
|
|||||||
"label": "Kanban Fields"
|
"label": "Kanban Fields"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "name",
|
|
||||||
"fieldname": "title_field",
|
"fieldname": "title_field",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Title Field"
|
"label": "Title Field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_standard",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Standard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_default",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Default"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-06-25 19:40:12.067788",
|
"modified": "2025-02-20 15:36:55.059065",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM View Settings",
|
"name": "CRM View Settings",
|
||||||
|
|||||||
@ -37,7 +37,7 @@ def create(view):
|
|||||||
doc.icon = view.icon
|
doc.icon = view.icon
|
||||||
doc.dt = view.doctype
|
doc.dt = view.doctype
|
||||||
doc.user = frappe.session.user
|
doc.user = frappe.session.user
|
||||||
doc.route_name = view.route_name or ""
|
doc.route_name = view.route_name or get_route_name(view.doctype)
|
||||||
doc.load_default_columns = view.load_default_columns or False
|
doc.load_default_columns = view.load_default_columns or False
|
||||||
doc.filters = json.dumps(view.filters)
|
doc.filters = json.dumps(view.filters)
|
||||||
doc.order_by = view.order_by
|
doc.order_by = view.order_by
|
||||||
@ -70,7 +70,7 @@ def update(view):
|
|||||||
doc.label = view.label
|
doc.label = view.label
|
||||||
doc.type = view.type or "list"
|
doc.type = view.type or "list"
|
||||||
doc.icon = view.icon
|
doc.icon = view.icon
|
||||||
doc.route_name = view.route_name or ""
|
doc.route_name = view.route_name or get_route_name(view.doctype)
|
||||||
doc.load_default_columns = view.load_default_columns or False
|
doc.load_default_columns = view.load_default_columns or False
|
||||||
doc.filters = json.dumps(filters)
|
doc.filters = json.dumps(filters)
|
||||||
doc.order_by = view.order_by
|
doc.order_by = view.order_by
|
||||||
@ -147,13 +147,23 @@ def sync_default_columns(view):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def set_as_default(name=None, type=None, doctype=None):
|
def set_as_default(name=None, type=None, doctype=None):
|
||||||
if not name:
|
if name:
|
||||||
name = type + "_" + doctype
|
frappe.db.set_value("CRM View Settings", name, "is_default", 1)
|
||||||
frappe.db.set_single_value("FCRM Settings", "default_view", name)
|
else:
|
||||||
|
doc = create_or_update_standard_view({"type": type, "doctype": doctype, "is_default": 1})
|
||||||
|
name = doc.name
|
||||||
|
|
||||||
|
# remove default from other views of same user
|
||||||
|
frappe.db.set_value(
|
||||||
|
"CRM View Settings",
|
||||||
|
{"name": ("!=", name), "user": frappe.session.user, "is_default": 1},
|
||||||
|
"is_default",
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_or_update_default_view(view):
|
def create_or_update_standard_view(view):
|
||||||
view = frappe._dict(view)
|
view = frappe._dict(view)
|
||||||
|
|
||||||
filters = parse_json(view.filters) or {}
|
filters = parse_json(view.filters) or {}
|
||||||
@ -161,6 +171,7 @@ def create_or_update_default_view(view):
|
|||||||
rows = parse_json(view.rows or "[]")
|
rows = parse_json(view.rows or "[]")
|
||||||
kanban_columns = parse_json(view.kanban_columns or "[]")
|
kanban_columns = parse_json(view.kanban_columns or "[]")
|
||||||
kanban_fields = parse_json(view.kanban_fields or "[]")
|
kanban_fields = parse_json(view.kanban_fields or "[]")
|
||||||
|
view.column_field = view.column_field or "status"
|
||||||
|
|
||||||
default_rows = sync_default_rows(view.doctype, view.type)
|
default_rows = sync_default_rows(view.doctype, view.type)
|
||||||
rows = rows + default_rows if default_rows else rows
|
rows = rows + default_rows if default_rows else rows
|
||||||
@ -173,42 +184,63 @@ def create_or_update_default_view(view):
|
|||||||
|
|
||||||
doc = frappe.db.exists(
|
doc = frappe.db.exists(
|
||||||
"CRM View Settings",
|
"CRM View Settings",
|
||||||
{"dt": view.doctype, "type": view.type or "list", "is_default": True, "user": frappe.session.user},
|
{"dt": view.doctype, "type": view.type or "list", "is_standard": True, "user": frappe.session.user},
|
||||||
)
|
)
|
||||||
if doc:
|
if doc:
|
||||||
doc = frappe.get_doc("CRM View Settings", doc)
|
doc = frappe.get_doc("CRM View Settings", doc)
|
||||||
doc.label = view.label
|
doc.label = view.label
|
||||||
doc.type = view.type or "list"
|
doc.type = view.type or "list"
|
||||||
doc.route_name = view.route_name or ""
|
doc.route_name = view.route_name or get_route_name(view.doctype)
|
||||||
doc.load_default_columns = view.load_default_columns or False
|
doc.load_default_columns = view.load_default_columns or False
|
||||||
doc.filters = json.dumps(filters)
|
doc.filters = json.dumps(filters)
|
||||||
doc.order_by = view.order_by
|
doc.order_by = view.order_by or "modified desc"
|
||||||
doc.group_by_field = view.group_by_field
|
doc.group_by_field = view.group_by_field or "owner"
|
||||||
doc.column_field = view.column_field
|
doc.column_field = view.column_field
|
||||||
doc.title_field = view.title_field
|
doc.title_field = view.title_field
|
||||||
doc.kanban_columns = json.dumps(kanban_columns)
|
doc.kanban_columns = json.dumps(kanban_columns)
|
||||||
doc.kanban_fields = json.dumps(kanban_fields)
|
doc.kanban_fields = json.dumps(kanban_fields)
|
||||||
doc.columns = json.dumps(columns)
|
doc.columns = json.dumps(columns)
|
||||||
doc.rows = json.dumps(rows)
|
doc.rows = json.dumps(rows)
|
||||||
|
doc.is_default = view.is_default or False
|
||||||
doc.save()
|
doc.save()
|
||||||
else:
|
else:
|
||||||
doc = frappe.new_doc("CRM View Settings")
|
doc = frappe.new_doc("CRM View Settings")
|
||||||
label = "Group By View" if view.type == "group_by" else "List View"
|
|
||||||
|
label = "List"
|
||||||
|
if view.type == "group_by":
|
||||||
|
label = "Group By"
|
||||||
|
elif view.type == "kanban":
|
||||||
|
label = "Kanban"
|
||||||
|
|
||||||
doc.name = view.label or label
|
doc.name = view.label or label
|
||||||
doc.label = view.label or label
|
doc.label = view.label or label
|
||||||
doc.type = view.type or "list"
|
doc.type = view.type or "list"
|
||||||
doc.dt = view.doctype
|
doc.dt = view.doctype
|
||||||
doc.user = frappe.session.user
|
doc.user = frappe.session.user
|
||||||
doc.route_name = view.route_name or ""
|
doc.route_name = view.route_name or get_route_name(view.doctype)
|
||||||
doc.load_default_columns = view.load_default_columns or False
|
doc.load_default_columns = view.load_default_columns or False
|
||||||
doc.filters = json.dumps(filters)
|
doc.filters = json.dumps(filters)
|
||||||
doc.order_by = view.order_by
|
doc.order_by = view.order_by or "modified desc"
|
||||||
doc.group_by_field = view.group_by_field
|
doc.group_by_field = view.group_by_field or "owner"
|
||||||
doc.column_field = view.column_field
|
doc.column_field = view.column_field
|
||||||
doc.title_field = view.title_field
|
doc.title_field = view.title_field
|
||||||
doc.kanban_columns = json.dumps(kanban_columns)
|
doc.kanban_columns = json.dumps(kanban_columns)
|
||||||
doc.kanban_fields = json.dumps(kanban_fields)
|
doc.kanban_fields = json.dumps(kanban_fields)
|
||||||
doc.columns = json.dumps(columns)
|
doc.columns = json.dumps(columns)
|
||||||
doc.rows = json.dumps(rows)
|
doc.rows = json.dumps(rows)
|
||||||
doc.is_default = True
|
doc.is_standard = True
|
||||||
|
doc.is_default = view.is_default or False
|
||||||
doc.insert()
|
doc.insert()
|
||||||
|
|
||||||
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
def get_route_name(doctype):
|
||||||
|
# Example: "CRM Lead" -> "Leads"
|
||||||
|
if doctype.startswith("CRM "):
|
||||||
|
doctype = doctype[4:]
|
||||||
|
|
||||||
|
if doctype[-1] != "s":
|
||||||
|
doctype += "s"
|
||||||
|
|
||||||
|
return doctype
|
||||||
|
|||||||
@ -6,8 +6,6 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"defaults_tab",
|
"defaults_tab",
|
||||||
"default_view",
|
|
||||||
"column_break_jeeh",
|
|
||||||
"restore_defaults",
|
"restore_defaults",
|
||||||
"branding_tab",
|
"branding_tab",
|
||||||
"brand_name",
|
"brand_name",
|
||||||
@ -58,21 +56,12 @@
|
|||||||
"fieldname": "favicon",
|
"fieldname": "favicon",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"label": "Favicon"
|
"label": "Favicon"
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "default_view",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Default View"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_jeeh",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-02-18 17:05:39.440396",
|
"modified": "2025-02-20 12:38:38.088477",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "FCRM Settings",
|
"name": "FCRM Settings",
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
"@vueuse/core": "^10.3.0",
|
"@vueuse/core": "^10.3.0",
|
||||||
"@vueuse/integrations": "^10.3.0",
|
"@vueuse/integrations": "^10.3.0",
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "^4.28.0",
|
||||||
"frappe-ui": "^0.1.105",
|
"frappe-ui": "^0.1.110",
|
||||||
"gemoji": "^8.1.0",
|
"gemoji": "^8.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mime": "^4.0.1",
|
"mime": "^4.0.1",
|
||||||
|
|||||||
@ -25,7 +25,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item-label="{ active, selected, option }">
|
<template #item-label="{ active, selected, option }">
|
||||||
<slot name="item-label" v-bind="{ active, selected, option }" />
|
<slot name="item-label" v-bind="{ active, selected, option }">
|
||||||
|
<div v-if="option.description" class="flex flex-col gap-1">
|
||||||
|
<div class="flex-1 font-semibold truncate text-ink-gray-7">
|
||||||
|
{{ option.label }}
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-sm truncate text-ink-gray-5">
|
||||||
|
{{ option.description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex-1 truncate text-ink-gray-7">
|
||||||
|
{{ option.label }}
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer="{ value, close }">
|
<template #footer="{ value, close }">
|
||||||
@ -131,8 +143,9 @@ const options = createResource({
|
|||||||
transform: (data) => {
|
transform: (data) => {
|
||||||
let allData = data.map((option) => {
|
let allData = data.map((option) => {
|
||||||
return {
|
return {
|
||||||
label: option.value,
|
label: option.label || option.value,
|
||||||
value: option.value,
|
value: option.value,
|
||||||
|
description: option.description,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (!props.hideMe && props.doctype == 'User') {
|
if (!props.hideMe && props.doctype == 'User') {
|
||||||
|
|||||||
@ -226,6 +226,10 @@ import { ref, computed, watch } from 'vue'
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
tabs: Object,
|
tabs: Object,
|
||||||
doctype: String,
|
doctype: String,
|
||||||
|
onlyRequired: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabIndex = ref(0)
|
const tabIndex = ref(0)
|
||||||
@ -249,6 +253,7 @@ const params = computed(() => {
|
|||||||
doctype: props.doctype,
|
doctype: props.doctype,
|
||||||
restricted_fieldtypes: restrictedFieldTypes,
|
restricted_fieldtypes: restrictedFieldTypes,
|
||||||
as_array: true,
|
as_array: true,
|
||||||
|
only_required: props.onlyRequired,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -190,6 +190,13 @@ function createDeal() {
|
|||||||
if (deal.website && !deal.website.startsWith('http')) {
|
if (deal.website && !deal.website.startsWith('http')) {
|
||||||
deal.website = 'https://' + deal.website
|
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({
|
createResource({
|
||||||
url: 'crm.fcrm.doctype.crm_deal.crm_deal.create_deal',
|
url: 'crm.fcrm.doctype.crm_deal.crm_deal.create_deal',
|
||||||
params: { args: deal },
|
params: { args: deal },
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
v-if="!preview"
|
v-if="!preview"
|
||||||
:tabs="tabs.data"
|
:tabs="tabs.data"
|
||||||
:doctype="_doctype"
|
:doctype="_doctype"
|
||||||
|
:onlyRequired="onlyRequired"
|
||||||
/>
|
/>
|
||||||
<FieldLayout v-else :tabs="tabs.data" :data="{}" :preview="true" />
|
<FieldLayout v-else :tabs="tabs.data" :data="{}" :preview="true" />
|
||||||
</div>
|
</div>
|
||||||
@ -55,6 +56,10 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'CRM Lead',
|
default: 'CRM Lead',
|
||||||
},
|
},
|
||||||
|
onlyRequired: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
@ -64,12 +69,13 @@ const dirty = ref(false)
|
|||||||
const preview = ref(false)
|
const preview = ref(false)
|
||||||
|
|
||||||
function getParams() {
|
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({
|
const tabs = createResource({
|
||||||
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout',
|
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(),
|
params: getParams(),
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
tabs.originalData = JSON.parse(JSON.stringify(data))
|
tabs.originalData = JSON.parse(JSON.stringify(data))
|
||||||
@ -106,11 +112,12 @@ function saveChanges() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
let type = props.onlyRequired ? 'Required Fields' : 'Quick Entry'
|
||||||
call(
|
call(
|
||||||
'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.save_fields_layout',
|
'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.save_fields_layout',
|
||||||
{
|
{
|
||||||
doctype: _doctype.value,
|
doctype: _doctype.value,
|
||||||
type: 'Quick Entry',
|
type: type,
|
||||||
layout: JSON.stringify(_tabs),
|
layout: JSON.stringify(_tabs),
|
||||||
},
|
},
|
||||||
).then(() => {
|
).then(() => {
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import LightningIcon from '@/components/Icons/LightningIcon.vue'
|
import LightningIcon from '@/components/Icons/LightningIcon.vue'
|
||||||
|
import { capture } from '@/telemetry'
|
||||||
import { createResource } from 'frappe-ui'
|
import { createResource } from 'frappe-ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ const props = defineProps({
|
|||||||
const showBanner = ref(window.is_demo_site)
|
const showBanner = ref(window.is_demo_site)
|
||||||
|
|
||||||
function signupNow() {
|
function signupNow() {
|
||||||
|
capture('signup_from_demo_site')
|
||||||
window.open('https://frappecloud.com/crm/signup', '_blank')
|
window.open('https://frappecloud.com/crm/signup', '_blank')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -310,13 +310,13 @@ const currentView = computed(() => {
|
|||||||
label:
|
label:
|
||||||
_view?.label || props.options?.defaultViewName || getViewType().label,
|
_view?.label || props.options?.defaultViewName || getViewType().label,
|
||||||
icon: _view?.icon || getViewType().icon,
|
icon: _view?.icon || getViewType().icon,
|
||||||
is_default: !_view || _view.is_default,
|
is_standard: !_view || _view.is_standard,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
usePageMeta(() => {
|
usePageMeta(() => {
|
||||||
let label = currentView.value.label
|
let label = currentView.value.label
|
||||||
if (currentView.value.is_default) {
|
if (currentView.value.is_standard) {
|
||||||
let routeName = route.name
|
let routeName = route.name
|
||||||
label = `${routeName} - ${label}`
|
label = `${routeName} - ${label}`
|
||||||
}
|
}
|
||||||
@ -480,11 +480,11 @@ async function exportRows() {
|
|||||||
export_type.value = 'Excel'
|
export_type.value = 'Excel'
|
||||||
}
|
}
|
||||||
|
|
||||||
let defaultViews = []
|
let standardViews = []
|
||||||
let allowedViews = props.options.allowedViews || ['list']
|
let allowedViews = props.options.allowedViews || ['list']
|
||||||
|
|
||||||
if (allowedViews.includes('list')) {
|
if (allowedViews.includes('list')) {
|
||||||
defaultViews.push({
|
standardViews.push({
|
||||||
name: 'list',
|
name: 'list',
|
||||||
label: __(props.options?.defaultViewName) || __('List'),
|
label: __(props.options?.defaultViewName) || __('List'),
|
||||||
icon: markRaw(ListIcon),
|
icon: markRaw(ListIcon),
|
||||||
@ -495,7 +495,7 @@ if (allowedViews.includes('list')) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (allowedViews.includes('kanban')) {
|
if (allowedViews.includes('kanban')) {
|
||||||
defaultViews.push({
|
standardViews.push({
|
||||||
name: 'kanban',
|
name: 'kanban',
|
||||||
label: __(props.options?.defaultViewName) || __('Kanban'),
|
label: __(props.options?.defaultViewName) || __('Kanban'),
|
||||||
icon: markRaw(KanbanIcon),
|
icon: markRaw(KanbanIcon),
|
||||||
@ -506,7 +506,7 @@ if (allowedViews.includes('kanban')) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (allowedViews.includes('group_by')) {
|
if (allowedViews.includes('group_by')) {
|
||||||
defaultViews.push({
|
standardViews.push({
|
||||||
name: 'group_by',
|
name: 'group_by',
|
||||||
label: __(props.options?.defaultViewName) || __('Group By'),
|
label: __(props.options?.defaultViewName) || __('Group By'),
|
||||||
icon: markRaw(GroupByIcon),
|
icon: markRaw(GroupByIcon),
|
||||||
@ -531,9 +531,9 @@ function getIcon(icon, type) {
|
|||||||
const viewsDropdownOptions = computed(() => {
|
const viewsDropdownOptions = computed(() => {
|
||||||
let _views = [
|
let _views = [
|
||||||
{
|
{
|
||||||
group: __('Default Views'),
|
group: __('Standard Views'),
|
||||||
hideLabel: true,
|
hideLabel: true,
|
||||||
items: defaultViews,
|
items: standardViews,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -558,7 +558,7 @@ const viewsDropdownOptions = computed(() => {
|
|||||||
})
|
})
|
||||||
let publicViews = list.value.data.views.filter((v) => v.public)
|
let publicViews = list.value.data.views.filter((v) => v.public)
|
||||||
let savedViews = list.value.data.views.filter(
|
let savedViews = list.value.data.views.filter(
|
||||||
(v) => !v.pinned && !v.public && !v.is_default,
|
(v) => !v.pinned && !v.public && !v.is_standard,
|
||||||
)
|
)
|
||||||
let pinnedViews = list.value.data.views.filter((v) => v.pinned)
|
let pinnedViews = list.value.data.views.filter((v) => v.pinned)
|
||||||
|
|
||||||
@ -662,7 +662,7 @@ function updateFilter(filters) {
|
|||||||
list.value.reload()
|
list.value.reload()
|
||||||
|
|
||||||
if (!route.query.view) {
|
if (!route.query.view) {
|
||||||
create_or_update_default_view()
|
createOrUpdateStandardView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,7 +677,7 @@ function updateSort(order_by) {
|
|||||||
list.value.reload()
|
list.value.reload()
|
||||||
|
|
||||||
if (!route.query.view) {
|
if (!route.query.view) {
|
||||||
create_or_update_default_view()
|
createOrUpdateStandardView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,7 +692,7 @@ function updateGroupBy(group_by_field) {
|
|||||||
list.value.reload()
|
list.value.reload()
|
||||||
|
|
||||||
if (!route.query.view) {
|
if (!route.query.view) {
|
||||||
create_or_update_default_view()
|
createOrUpdateStandardView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -726,7 +726,7 @@ function updateColumns(obj) {
|
|||||||
viewUpdated.value = true
|
viewUpdated.value = true
|
||||||
|
|
||||||
if (!route.query.view) {
|
if (!route.query.view) {
|
||||||
create_or_update_default_view()
|
createOrUpdateStandardView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -768,7 +768,7 @@ async function updateKanbanSettings(data) {
|
|||||||
list.value.reload()
|
list.value.reload()
|
||||||
|
|
||||||
if (!route.query.view) {
|
if (!route.query.view) {
|
||||||
create_or_update_default_view()
|
createOrUpdateStandardView()
|
||||||
} else if (!data.column_field) {
|
} else if (!data.column_field) {
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
$dialog({
|
$dialog({
|
||||||
@ -780,14 +780,14 @@ async function updateKanbanSettings(data) {
|
|||||||
label: __('Update'),
|
label: __('Update'),
|
||||||
variant: 'solid',
|
variant: 'solid',
|
||||||
onClick: (close) => {
|
onClick: (close) => {
|
||||||
update_custom_view()
|
updateCustomView()
|
||||||
close()
|
close()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
update_custom_view()
|
updateCustomView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -811,11 +811,11 @@ function loadMoreKanban(columnName) {
|
|||||||
list.value.reload()
|
list.value.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_or_update_default_view() {
|
function createOrUpdateStandardView() {
|
||||||
if (route.query.view) return
|
if (route.query.view) return
|
||||||
view.value.doctype = props.doctype
|
view.value.doctype = props.doctype
|
||||||
call(
|
call(
|
||||||
'crm.fcrm.doctype.crm_view_settings.crm_view_settings.create_or_update_default_view',
|
'crm.fcrm.doctype.crm_view_settings.crm_view_settings.create_or_update_standard_view',
|
||||||
{
|
{
|
||||||
view: view.value,
|
view: view.value,
|
||||||
},
|
},
|
||||||
@ -842,7 +842,7 @@ function create_or_update_default_view() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_custom_view() {
|
function updateCustomView() {
|
||||||
viewUpdated.value = false
|
viewUpdated.value = false
|
||||||
view.value = {
|
view.value = {
|
||||||
doctype: props.doctype,
|
doctype: props.doctype,
|
||||||
@ -897,6 +897,7 @@ const viewActions = (view) => {
|
|||||||
|
|
||||||
if (!_view) {
|
if (!_view) {
|
||||||
_view = {
|
_view = {
|
||||||
|
label: view.label,
|
||||||
type: view.name,
|
type: view.name,
|
||||||
dt: props.doctype,
|
dt: props.doctype,
|
||||||
}
|
}
|
||||||
@ -904,7 +905,7 @@ const viewActions = (view) => {
|
|||||||
|
|
||||||
let actions = [
|
let actions = [
|
||||||
{
|
{
|
||||||
group: __('Default Views'),
|
group: __('Actions'),
|
||||||
hideLabel: true,
|
hideLabel: true,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@ -916,7 +917,7 @@ const viewActions = (view) => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
if (!isStandardView(_view, isStandard)) {
|
if (!isDefaultView(_view, isStandard)) {
|
||||||
actions[0].items.unshift({
|
actions[0].items.unshift({
|
||||||
label: __('Set as default'),
|
label: __('Set as default'),
|
||||||
icon: () => h(CheckIcon, { class: 'h-4 w-4' }),
|
icon: () => h(CheckIcon, { class: 'h-4 w-4' }),
|
||||||
@ -981,16 +982,12 @@ const viewActions = (view) => {
|
|||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStandardView(v, isStandard) {
|
function isDefaultView(v, isStandard) {
|
||||||
let defaultView = getDefaultView()
|
let defaultView = getDefaultView()
|
||||||
|
|
||||||
if (!defaultView) return false
|
if (!defaultView || (isStandard && !v.name)) return false
|
||||||
|
|
||||||
if (isStandard && !v.name) {
|
return defaultView.name == v.name
|
||||||
return defaultView == v.type + '_' + v.dt
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultView == v.name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewModalObj = ref({})
|
const viewModalObj = ref({})
|
||||||
|
|||||||
@ -186,7 +186,6 @@
|
|||||||
<Dialog
|
<Dialog
|
||||||
v-model="showConvertToDealModal"
|
v-model="showConvertToDealModal"
|
||||||
:options="{
|
:options="{
|
||||||
title: __('Convert to Deal'),
|
|
||||||
size: 'xl',
|
size: 'xl',
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
@ -197,12 +196,38 @@
|
|||||||
],
|
],
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<template #body-header>
|
||||||
|
<div class="mb-6 flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||||
|
{{ __('Convert to Deal') }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
v-if="isManager() && !isMobileView"
|
||||||
|
variant="ghost"
|
||||||
|
class="w-7"
|
||||||
|
@click="openQuickEntryModal"
|
||||||
|
>
|
||||||
|
<EditIcon class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
class="w-7"
|
||||||
|
@click="showConvertToDealModal = false"
|
||||||
|
>
|
||||||
|
<FeatherIcon name="x" class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template #body-content>
|
<template #body-content>
|
||||||
<div class="mb-4 flex items-center gap-2 text-ink-gray-5">
|
<div class="mb-4 flex items-center gap-2 text-ink-gray-5">
|
||||||
<OrganizationsIcon class="h-4 w-4" />
|
<OrganizationsIcon class="h-4 w-4" />
|
||||||
<label class="block text-base">{{ __('Organization') }}</label>
|
<label class="block text-base">{{ __('Organization') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-6">
|
<div class="ml-6 text-ink-gray-9">
|
||||||
<div class="flex items-center justify-between text-base">
|
<div class="flex items-center justify-between text-base">
|
||||||
<div>{{ __('Choose Existing') }}</div>
|
<div>{{ __('Choose Existing') }}</div>
|
||||||
<Switch v-model="existingOrganizationChecked" />
|
<Switch v-model="existingOrganizationChecked" />
|
||||||
@ -210,7 +235,6 @@
|
|||||||
<Link
|
<Link
|
||||||
v-if="existingOrganizationChecked"
|
v-if="existingOrganizationChecked"
|
||||||
class="form-control mt-2.5"
|
class="form-control mt-2.5"
|
||||||
variant="outline"
|
|
||||||
size="md"
|
size="md"
|
||||||
:value="existingOrganization"
|
:value="existingOrganization"
|
||||||
doctype="CRM Organization"
|
doctype="CRM Organization"
|
||||||
@ -229,7 +253,7 @@
|
|||||||
<ContactsIcon class="h-4 w-4" />
|
<ContactsIcon class="h-4 w-4" />
|
||||||
<label class="block text-base">{{ __('Contact') }}</label>
|
<label class="block text-base">{{ __('Contact') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-6">
|
<div class="ml-6 text-ink-gray-9">
|
||||||
<div class="flex items-center justify-between text-base">
|
<div class="flex items-center justify-between text-base">
|
||||||
<div>{{ __('Choose Existing') }}</div>
|
<div>{{ __('Choose Existing') }}</div>
|
||||||
<Switch v-model="existingContactChecked" />
|
<Switch v-model="existingContactChecked" />
|
||||||
@ -237,7 +261,6 @@
|
|||||||
<Link
|
<Link
|
||||||
v-if="existingContactChecked"
|
v-if="existingContactChecked"
|
||||||
class="form-control mt-2.5"
|
class="form-control mt-2.5"
|
||||||
variant="outline"
|
|
||||||
size="md"
|
size="md"
|
||||||
:value="existingContact"
|
:value="existingContact"
|
||||||
doctype="Contact"
|
doctype="Contact"
|
||||||
@ -247,8 +270,23 @@
|
|||||||
{{ __("New contact will be created based on the person's details") }}
|
{{ __("New contact will be created based on the person's details") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="dealTabs.data?.length" class="h-px w-full border-t my-6" />
|
||||||
|
|
||||||
|
<FieldLayout
|
||||||
|
v-if="dealTabs.data?.length"
|
||||||
|
:tabs="dealTabs.data"
|
||||||
|
:data="deal"
|
||||||
|
doctype="CRM Deal"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
<QuickEntryModal
|
||||||
|
v-if="showQuickEntryModal"
|
||||||
|
v-model="showQuickEntryModal"
|
||||||
|
doctype="CRM Deal"
|
||||||
|
:onlyRequired="true"
|
||||||
|
/>
|
||||||
<FilesUploader
|
<FilesUploader
|
||||||
v-if="lead.data?.name"
|
v-if="lead.data?.name"
|
||||||
v-model="showFilesUploader"
|
v-model="showFilesUploader"
|
||||||
@ -280,12 +318,15 @@ import LinkIcon from '@/components/Icons/LinkIcon.vue'
|
|||||||
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
|
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
|
||||||
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
|
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
|
||||||
import AttachmentIcon from '@/components/Icons/AttachmentIcon.vue'
|
import AttachmentIcon from '@/components/Icons/AttachmentIcon.vue'
|
||||||
|
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||||
import Activities from '@/components/Activities/Activities.vue'
|
import Activities from '@/components/Activities/Activities.vue'
|
||||||
import AssignTo from '@/components/AssignTo.vue'
|
import AssignTo from '@/components/AssignTo.vue'
|
||||||
import FilesUploader from '@/components/FilesUploader/FilesUploader.vue'
|
import FilesUploader from '@/components/FilesUploader/FilesUploader.vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import SidePanelLayout from '@/components/SidePanelLayout.vue'
|
import SidePanelLayout from '@/components/SidePanelLayout.vue'
|
||||||
|
import FieldLayout from '@/components/FieldLayout/FieldLayout.vue'
|
||||||
|
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
|
||||||
import SLASection from '@/components/SLASection.vue'
|
import SLASection from '@/components/SLASection.vue'
|
||||||
import CustomActions from '@/components/CustomActions.vue'
|
import CustomActions from '@/components/CustomActions.vue'
|
||||||
import {
|
import {
|
||||||
@ -298,10 +339,15 @@ import {
|
|||||||
} from '@/utils'
|
} from '@/utils'
|
||||||
import { getView } from '@/utils/view'
|
import { getView } from '@/utils/view'
|
||||||
import { getSettings } from '@/stores/settings'
|
import { getSettings } from '@/stores/settings'
|
||||||
|
import { usersStore } from '@/stores/users'
|
||||||
import { globalStore } from '@/stores/global'
|
import { globalStore } from '@/stores/global'
|
||||||
import { contactsStore } from '@/stores/contacts'
|
import { contactsStore } from '@/stores/contacts'
|
||||||
import { statusesStore } from '@/stores/statuses'
|
import { statusesStore } from '@/stores/statuses'
|
||||||
import { whatsappEnabled, callEnabled } from '@/composables/settings'
|
import {
|
||||||
|
whatsappEnabled,
|
||||||
|
callEnabled,
|
||||||
|
isMobileView,
|
||||||
|
} from '@/composables/settings'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import {
|
import {
|
||||||
createResource,
|
createResource,
|
||||||
@ -315,14 +361,15 @@ import {
|
|||||||
call,
|
call,
|
||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useActiveTabManager } from '@/composables/useActiveTabManager'
|
import { useActiveTabManager } from '@/composables/useActiveTabManager'
|
||||||
|
|
||||||
const { brand } = getSettings()
|
const { brand } = getSettings()
|
||||||
|
const { isManager } = usersStore()
|
||||||
const { $dialog, $socket, makeCall } = globalStore()
|
const { $dialog, $socket, makeCall } = globalStore()
|
||||||
const { getContactByName, contacts } = contactsStore()
|
const { getContactByName, contacts } = contactsStore()
|
||||||
const { statusOptions, getLeadStatus } = statusesStore()
|
const { statusOptions, getLeadStatus, getDealStatus } = statusesStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@ -599,18 +646,23 @@ async function convertToDeal(updated) {
|
|||||||
)
|
)
|
||||||
showConvertToDealModal.value = false
|
showConvertToDealModal.value = false
|
||||||
} else {
|
} else {
|
||||||
let deal = await call(
|
let _deal = await call(
|
||||||
'crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal',
|
'crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal',
|
||||||
{
|
{ lead: lead.data.name, deal },
|
||||||
lead: lead.data.name,
|
).catch((err) => {
|
||||||
},
|
createToast({
|
||||||
)
|
title: __('Error converting to deal'),
|
||||||
if (deal) {
|
text: __(err.messages?.[0]),
|
||||||
|
icon: 'x',
|
||||||
|
iconClasses: 'text-ink-red-4',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (_deal) {
|
||||||
capture('convert_lead_to_deal')
|
capture('convert_lead_to_deal')
|
||||||
if (updated) {
|
if (updated) {
|
||||||
await contacts.reload()
|
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() {
|
function openEmailBox() {
|
||||||
activities.value.emailBox.show = true
|
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
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -113,23 +113,22 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
isLoggedIn && (await userResource.promise)
|
isLoggedIn && (await userResource.promise)
|
||||||
|
|
||||||
if (to.name === 'Home' && isLoggedIn) {
|
if (to.name === 'Home' && isLoggedIn) {
|
||||||
const { getDefaultView, defaultView } = viewsStore()
|
const { views, getDefaultView } = viewsStore()
|
||||||
await defaultView.promise
|
await views.promise
|
||||||
|
|
||||||
let _defaultView = getDefaultView(true)
|
let defaultView = getDefaultView()
|
||||||
|
if (!defaultView) {
|
||||||
if (!_defaultView) {
|
|
||||||
next({ name: 'Leads' })
|
next({ name: 'Leads' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let { name, type, view } = _defaultView
|
let { route_name, type, name, is_standard } = defaultView
|
||||||
name = name || 'Leads'
|
route_name = route_name || 'Leads'
|
||||||
|
|
||||||
if (view) {
|
if (name && !is_standard) {
|
||||||
next({ name, params: { viewType: type }, query: { view } })
|
next({ name: route_name, params: { viewType: type }, query: { name } })
|
||||||
} else {
|
} else {
|
||||||
next({ name, params: { viewType: type } })
|
next({ name: route_name, params: { viewType: type } })
|
||||||
}
|
}
|
||||||
} else if (!isLoggedIn) {
|
} else if (!isLoggedIn) {
|
||||||
window.location.href = '/login?redirect-to=/crm'
|
window.location.href = '/login?redirect-to=/crm'
|
||||||
|
|||||||
@ -7,13 +7,7 @@ export const viewsStore = defineStore('crm-views', (doctype) => {
|
|||||||
let pinnedViews = ref([])
|
let pinnedViews = ref([])
|
||||||
let publicViews = ref([])
|
let publicViews = ref([])
|
||||||
let standardViews = ref({})
|
let standardViews = ref({})
|
||||||
|
const defaultView = ref(null)
|
||||||
// Default view
|
|
||||||
const defaultView = createResource({
|
|
||||||
url: 'crm.api.views.get_default_view',
|
|
||||||
cache: 'crm-default-view',
|
|
||||||
auto: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
const views = createResource({
|
const views = createResource({
|
||||||
@ -34,48 +28,19 @@ export const viewsStore = defineStore('crm-views', (doctype) => {
|
|||||||
if (view.public) {
|
if (view.public) {
|
||||||
publicViews.value?.push(view)
|
publicViews.value?.push(view)
|
||||||
}
|
}
|
||||||
if (view.is_default && view.dt) {
|
if (view.is_standard && view.dt) {
|
||||||
standardViews.value[view.dt + ' ' + view.type] = view
|
standardViews.value[view.dt + ' ' + view.type] = view
|
||||||
}
|
}
|
||||||
|
if (view.is_default) {
|
||||||
|
defaultView.value = view
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return views
|
return views
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function getDefaultView(routeName = false) {
|
function getDefaultView() {
|
||||||
let view = defaultView.data
|
return defaultView.value
|
||||||
if (!view) return null
|
|
||||||
|
|
||||||
if (typeof view === 'string' && !isNaN(view)) {
|
|
||||||
view = parseInt(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (routeName) {
|
|
||||||
let viewObj = getView(view) || {
|
|
||||||
type: view.split('_')[0],
|
|
||||||
dt: view.split('_')[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
let routeName = viewObj.dt
|
|
||||||
|
|
||||||
if (routeName.startsWith('CRM ')) {
|
|
||||||
routeName = routeName.slice(4)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!routeName.endsWith('s')) {
|
|
||||||
routeName += 's'
|
|
||||||
}
|
|
||||||
|
|
||||||
let viewName = viewObj.is_default ? null : viewObj.name
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: routeName,
|
|
||||||
type: viewObj.type,
|
|
||||||
view: viewName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getView(view, type, doctype = null) {
|
function getView(view, type, doctype = null) {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { markRaw } from 'vue'
|
|||||||
|
|
||||||
const { getView: getViewDetails } = viewsStore()
|
const { getView: getViewDetails } = viewsStore()
|
||||||
|
|
||||||
function defaultView(type) {
|
function standardView(type) {
|
||||||
let types = {
|
let types = {
|
||||||
list: {
|
list: {
|
||||||
label: __('List'),
|
label: __('List'),
|
||||||
@ -29,7 +29,7 @@ export function getView(view, type, doctype) {
|
|||||||
let viewType = type || 'list'
|
let viewType = type || 'list'
|
||||||
let viewDetails = getViewDetails(view, viewType, doctype)
|
let viewDetails = getViewDetails(view, viewType, doctype)
|
||||||
if (viewDetails && !viewDetails.icon) {
|
if (viewDetails && !viewDetails.icon) {
|
||||||
viewDetails.icon = defaultView(viewType).icon
|
viewDetails.icon = standardView(viewType).icon
|
||||||
}
|
}
|
||||||
return viewDetails || defaultView(viewType)
|
return viewDetails || standardView(viewType)
|
||||||
}
|
}
|
||||||
|
|||||||
18
yarn.lock
18
yarn.lock
@ -1136,10 +1136,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
mini-svg-data-uri "^1.2.3"
|
mini-svg-data-uri "^1.2.3"
|
||||||
|
|
||||||
"@tailwindcss/typography@^0.5.0":
|
"@tailwindcss/typography@^0.5.16":
|
||||||
version "0.5.15"
|
version "0.5.16"
|
||||||
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.15.tgz#007ab9870c86082a1c76e5b3feda9392c7c8d648"
|
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.16.tgz#a926c8f44d5c439b2915e231cad80058850047c6"
|
||||||
integrity sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==
|
integrity sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.castarray "^4.4.0"
|
lodash.castarray "^4.4.0"
|
||||||
lodash.isplainobject "^4.0.6"
|
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"
|
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
||||||
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
||||||
|
|
||||||
frappe-ui@^0.1.105:
|
frappe-ui@^0.1.110:
|
||||||
version "0.1.105"
|
version "0.1.110"
|
||||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.105.tgz#3bdf3c458ba27f27ff2f2a28cf7eb6f9ed872367"
|
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.110.tgz#dbe02d294cb0aeb0a4c1b3000682093c309450ae"
|
||||||
integrity sha512-9bZ/hj/HhQ9vp7DxE8aOKS8HqwETZrKT3IhSzjpYOk21efK8QwdbQ9sp0t4m3UII+HaUTSOTHnFzF7y9EhRZxg==
|
integrity sha512-kFah6SoPauULXaeSbljNUq595/82VmY4k4+KA8zi4sXxpn4sXYi12qUl/1I8GOBhsCQQizmoh46DO7e/uU2M1A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@headlessui/vue" "^1.7.14"
|
"@headlessui/vue" "^1.7.14"
|
||||||
"@popperjs/core" "^2.11.2"
|
"@popperjs/core" "^2.11.2"
|
||||||
"@tailwindcss/forms" "^0.5.3"
|
"@tailwindcss/forms" "^0.5.3"
|
||||||
"@tailwindcss/typography" "^0.5.0"
|
"@tailwindcss/typography" "^0.5.16"
|
||||||
"@tiptap/extension-color" "^2.0.3"
|
"@tiptap/extension-color" "^2.0.3"
|
||||||
"@tiptap/extension-highlight" "^2.0.3"
|
"@tiptap/extension-highlight" "^2.0.3"
|
||||||
"@tiptap/extension-image" "^2.0.3"
|
"@tiptap/extension-image" "^2.0.3"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user