-
{ owner }
+
+ { owner }
{ _('mentioned you in {0}').format(doctype) }
- { name }
+ { name }
"""
- notify_user({
- "owner": doc.owner,
- "assigned_to": mention.email,
- "notification_type": "Mention",
- "message": doc.content,
- "notification_text": notification_text,
- "reference_doctype": "Comment",
- "reference_docname": doc.name,
- "redirect_to_doctype": doc.reference_doctype,
- "redirect_to_docname": doc.reference_name,
- })
+ notify_user(
+ {
+ "owner": doc.owner,
+ "assigned_to": mention.email,
+ "notification_type": "Mention",
+ "message": doc.content,
+ "notification_text": notification_text,
+ "reference_doctype": "Comment",
+ "reference_docname": doc.name,
+ "redirect_to_doctype": doc.reference_doctype,
+ "redirect_to_docname": doc.reference_name,
+ }
+ )
def extract_mentions(html):
@@ -56,39 +63,42 @@ def extract_mentions(html):
)
return mentions
+
@frappe.whitelist()
def add_attachments(name: str, attachments: Iterable[str | dict]) -> None:
- """Add attachments to the given Comment
+ """Add attachments to the given Comment
- :param name: Comment name
- :param attachments: File names or dicts with keys "fname" and "fcontent"
- """
- # loop through attachments
- for a in attachments:
- if isinstance(a, str):
- attach = frappe.db.get_value("File", {"name": a}, ["file_url", "is_private"], as_dict=1)
- file_args = {
- "file_url": attach.file_url,
- "is_private": attach.is_private,
- }
- elif isinstance(a, dict) and "fcontent" in a and "fname" in a:
- # dict returned by frappe.attach_print()
- file_args = {
- "file_name": a["fname"],
- "content": a["fcontent"],
- "is_private": 1,
- }
- else:
- continue
+ :param name: Comment name
+ :param attachments: File names or dicts with keys "fname" and "fcontent"
+ """
+ # loop through attachments
+ for a in attachments:
+ if isinstance(a, str):
+ attach = frappe.db.get_value(
+ "File", {"name": a}, ["file_url", "is_private"], as_dict=1
+ )
+ file_args = {
+ "file_url": attach.file_url,
+ "is_private": attach.is_private,
+ }
+ elif isinstance(a, dict) and "fcontent" in a and "fname" in a:
+ # dict returned by frappe.attach_print()
+ file_args = {
+ "file_name": a["fname"],
+ "content": a["fcontent"],
+ "is_private": 1,
+ }
+ else:
+ continue
- file_args.update(
- {
- "attached_to_doctype": "Comment",
- "attached_to_name": name,
- "folder": "Home/Attachments",
- }
- )
+ file_args.update(
+ {
+ "attached_to_doctype": "Comment",
+ "attached_to_name": name,
+ "folder": "Home/Attachments",
+ }
+ )
- _file = frappe.new_doc("File")
- _file.update(file_args)
- _file.save(ignore_permissions=True)
\ No newline at end of file
+ _file = frappe.new_doc("File")
+ _file.update(file_args)
+ _file.save(ignore_permissions=True)
diff --git a/crm/api/doc.py b/crm/api/doc.py
index 576127a1..b7e3e463 100644
--- a/crm/api/doc.py
+++ b/crm/api/doc.py
@@ -78,12 +78,7 @@ def get_filterable_fields(doctype: str):
# append standard fields (getting error when using frappe.model.std_fields)
standard_fields = [
{"fieldname": "name", "fieldtype": "Link", "label": "ID", "options": doctype},
- {
- "fieldname": "owner",
- "fieldtype": "Link",
- "label": "Created By",
- "options": "User"
- },
+ {"fieldname": "owner", "fieldtype": "Link", "label": "Created By", "options": "User"},
{
"fieldname": "modified_by",
"fieldtype": "Link",
@@ -98,10 +93,7 @@ def get_filterable_fields(doctype: str):
{"fieldname": "modified", "fieldtype": "Datetime", "label": "Last Updated On"},
]
for field in standard_fields:
- if (
- field.get("fieldname") not in restricted_fields and
- field.get("fieldtype") in allowed_fieldtypes
- ):
+ if field.get("fieldname") not in restricted_fields and field.get("fieldtype") in allowed_fieldtypes:
field["name"] = field.get("fieldname")
res.append(field)
@@ -128,7 +120,11 @@ def get_group_by_fields(doctype: str):
]
fields = frappe.get_meta(doctype).fields
- fields = [field for field in fields if field.fieldtype not in no_value_fields and field.fieldtype in allowed_fieldtypes]
+ fields = [
+ field
+ for field in fields
+ if field.fieldtype not in no_value_fields and field.fieldtype in allowed_fieldtypes
+ ]
fields = [
{
"label": _(field.label),
@@ -176,6 +172,7 @@ def get_doctype_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fi
.run(as_dict=True)
)
+
@frappe.whitelist()
def get_quick_filters(doctype: str):
meta = frappe.get_meta(doctype)
@@ -183,23 +180,25 @@ def get_quick_filters(doctype: str):
quick_filters = []
for field in fields:
-
if field.fieldtype == "Select":
field.options = field.options.split("\n")
field.options = [{"label": option, "value": option} for option in field.options]
field.options.insert(0, {"label": "", "value": ""})
- quick_filters.append({
- "label": _(field.label),
- "name": field.fieldname,
- "type": field.fieldtype,
- "options": field.options,
- })
+ quick_filters.append(
+ {
+ "label": _(field.label),
+ "name": field.fieldname,
+ "type": field.fieldtype,
+ "options": field.options,
+ }
+ )
if doctype == "CRM Lead":
quick_filters = [filter for filter in quick_filters if filter.get("name") != "converted"]
return quick_filters
+
@frappe.whitelist()
def get_data(
doctype: str,
@@ -223,9 +222,9 @@ def get_data(
kanban_fields = frappe.parse_json(kanban_fields or "[]")
kanban_columns = frappe.parse_json(kanban_columns or "[]")
- custom_view_name = view.get('custom_view_name') if view else None
- view_type = view.get('view_type') if view else None
- group_by_field = view.get('group_by_field') if view else None
+ custom_view_name = view.get("custom_view_name") if view else None
+ view_type = view.get("view_type") if view else None
+ group_by_field = view.get("group_by_field") if view else None
for key in filters:
value = filters[key]
@@ -268,7 +267,7 @@ def get_data(
default_view_filters = {
"dt": doctype,
- "type": view_type or 'list',
+ "type": view_type or "list",
"is_default": 1,
"user": frappe.session.user,
}
@@ -295,13 +294,16 @@ def get_data(
if group_by_field and group_by_field not in rows:
rows.append(group_by_field)
- data = frappe.get_list(
- doctype,
- fields=rows,
- filters=filters,
- order_by=order_by,
- page_length=page_length,
- ) or []
+ data = (
+ frappe.get_list(
+ doctype,
+ fields=rows,
+ filters=filters,
+ order_by=order_by,
+ page_length=page_length,
+ )
+ or []
+ )
if view_type == "kanban":
if not rows:
@@ -336,9 +338,9 @@ def get_data(
rows.append(field)
for kc in kanban_columns:
- column_filters = { column_field: kc.get('name') }
+ column_filters = {column_field: kc.get("name")}
order = kc.get("order")
- if column_field in filters and filters.get(column_field) != kc.name or kc.get('delete'):
+ if column_field in filters and filters.get(column_field) != kc.name or kc.get("delete"):
column_data = []
else:
column_filters.update(filters.copy())
@@ -348,7 +350,9 @@ def get_data(
page_length = kc.get("page_length")
if order:
- column_data = get_records_based_on_order(doctype, rows, column_filters, page_length, order)
+ column_data = get_records_based_on_order(
+ doctype, rows, column_filters, page_length, order
+ )
else:
column_data = frappe.get_list(
doctype,
@@ -359,9 +363,11 @@ def get_data(
)
new_filters = filters.copy()
- new_filters.update({ column_field: kc.get('name') })
+ new_filters.update({column_field: kc.get("name")})
- all_count = len(frappe.get_list(doctype, filters=convert_filter_to_tuple(doctype, new_filters)))
+ all_count = len(
+ frappe.get_list(doctype, filters=convert_filter_to_tuple(doctype, new_filters))
+ )
kc["all_count"] = all_count
kc["count"] = len(column_data)
@@ -371,8 +377,8 @@ def get_data(
if order:
column_data = sorted(
- column_data, key=lambda x: order.index(x.get("name"))
- if x.get("name") in order else len(order)
+ column_data,
+ key=lambda x: order.index(x.get("name")) if x.get("name") in order else len(order),
)
data.append({"column": kc, "fields": kanban_fields, "data": column_data})
@@ -406,8 +412,8 @@ def get_data(
]
for field in std_fields:
- if field.get('value') not in rows:
- rows.append(field.get('value'))
+ if field.get("value") not in rows:
+ rows.append(field.get("value"))
if field not in fields:
field["label"] = _(field["label"])
fields.append(field)
@@ -416,6 +422,7 @@ def get_data(
is_default = frappe.db.get_value("CRM View Settings", custom_view_name, "load_default_columns")
if group_by_field and view_type == "group_by":
+
def get_options(type, options):
if type == "Select":
return [option for option in options.split("\n")]
@@ -428,7 +435,9 @@ def get_data(
if order_by and group_by_field in order_by:
order_by_fields = order_by.split(",")
- order_by_fields = [(field.split(" ")[0], field.split(" ")[1]) for field in order_by_fields]
+ order_by_fields = [
+ (field.split(" ")[0], field.split(" ")[1]) for field in order_by_fields
+ ]
if (group_by_field, "asc") in order_by_fields:
options.sort()
elif (group_by_field, "desc") in order_by_fields:
@@ -467,6 +476,7 @@ def get_data(
"view_type": view_type,
}
+
def convert_filter_to_tuple(doctype, filters):
if isinstance(filters, dict):
filters_items = filters.items()
@@ -504,6 +514,7 @@ def get_records_based_on_order(doctype, rows, filters, page_length, order):
return records
+
@frappe.whitelist()
def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
not_allowed_fieldtypes = [
@@ -521,12 +532,7 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
standard_fields = [
{"fieldname": "name", "fieldtype": "Link", "label": "ID", "options": doctype},
- {
- "fieldname": "owner",
- "fieldtype": "Link",
- "label": "Created By",
- "options": "User"
- },
+ {"fieldname": "owner", "fieldtype": "Link", "label": "Created By", "options": "User"},
{
"fieldname": "modified_by",
"fieldtype": "Link",
@@ -542,7 +548,7 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
]
for field in standard_fields:
- 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)
if as_array:
@@ -550,10 +556,11 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
fields_meta = {}
for field in fields:
- fields_meta[field.get('fieldname')] = field
+ fields_meta[field.get("fieldname")] = field
return fields_meta
+
@frappe.whitelist()
def get_sidebar_fields(doctype, name):
if not frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": "Side Panel"}):
@@ -562,7 +569,7 @@ def get_sidebar_fields(doctype, name):
if not layout:
return []
-
+
layout = json.loads(layout)
not_allowed_fieldtypes = [
@@ -600,6 +607,7 @@ def get_sidebar_fields(doctype, name):
return layout
+
def get_field_obj(field):
obj = {
"label": field.label,
@@ -641,6 +649,7 @@ def get_type(field):
return "read_only"
return field.fieldtype.lower()
+
def get_assigned_users(doctype, name, default_assigned_to=None):
assigned_users = frappe.get_all(
"ToDo",
@@ -671,32 +680,55 @@ def get_fields(doctype: str, allow_all_fieldtypes: bool = False):
_fields = []
for field in fields:
- if (
- field.fieldtype not in not_allowed_fieldtypes
- and field.fieldname
- ):
- _fields.append({
- "label": field.label,
- "type": field.fieldtype,
- "value": field.fieldname,
- "options": field.options,
- "mandatory": field.reqd,
- "read_only": field.read_only,
- "hidden": field.hidden,
- "depends_on": field.depends_on,
- "mandatory_depends_on": field.mandatory_depends_on,
- "read_only_depends_on": field.read_only_depends_on,
- "link_filters": field.get("link_filters"),
- "placeholder": field.get("placeholder"),
- })
+ if field.fieldtype not in not_allowed_fieldtypes and field.fieldname:
+ _fields.append(
+ {
+ "label": field.label,
+ "type": field.fieldtype,
+ "value": field.fieldname,
+ "options": field.options,
+ "mandatory": field.reqd,
+ "read_only": field.read_only,
+ "hidden": field.hidden,
+ "depends_on": field.depends_on,
+ "mandatory_depends_on": field.mandatory_depends_on,
+ "read_only_depends_on": field.read_only_depends_on,
+ "link_filters": field.get("link_filters"),
+ "placeholder": field.get("placeholder"),
+ }
+ )
return _fields
def getCounts(d, doctype):
- d["_email_count"] = frappe.db.count("Communication", filters={"reference_doctype": doctype, "reference_name": d.get("name"), "communication_type": "Communication"}) or 0
- d["_email_count"] = d["_email_count"] + frappe.db.count("Communication", filters={"reference_doctype": doctype, "reference_name": d.get("name"), "communication_type": "Automated Message"})
- d["_comment_count"] = frappe.db.count("Comment", filters={"reference_doctype": doctype, "reference_name": d.get("name"), "comment_type": "Comment"})
- d["_task_count"] = frappe.db.count("CRM Task", filters={"reference_doctype": doctype, "reference_docname": d.get("name")})
- d["_note_count"] = frappe.db.count("FCRM Note", filters={"reference_doctype": doctype, "reference_docname": d.get("name")})
- return d
\ No newline at end of file
+ d["_email_count"] = (
+ frappe.db.count(
+ "Communication",
+ filters={
+ "reference_doctype": doctype,
+ "reference_name": d.get("name"),
+ "communication_type": "Communication",
+ },
+ )
+ or 0
+ )
+ d["_email_count"] = d["_email_count"] + frappe.db.count(
+ "Communication",
+ filters={
+ "reference_doctype": doctype,
+ "reference_name": d.get("name"),
+ "communication_type": "Automated Message",
+ },
+ )
+ d["_comment_count"] = frappe.db.count(
+ "Comment",
+ filters={"reference_doctype": doctype, "reference_name": d.get("name"), "comment_type": "Comment"},
+ )
+ d["_task_count"] = frappe.db.count(
+ "CRM Task", filters={"reference_doctype": doctype, "reference_docname": d.get("name")}
+ )
+ d["_note_count"] = frappe.db.count(
+ "FCRM Note", filters={"reference_doctype": doctype, "reference_docname": d.get("name")}
+ )
+ return d
diff --git a/crm/api/todo.py b/crm/api/todo.py
index f30e19f4..dc9dcf28 100644
--- a/crm/api/todo.py
+++ b/crm/api/todo.py
@@ -2,102 +2,131 @@ import frappe
from frappe import _
from crm.fcrm.doctype.crm_notification.crm_notification import notify_user
-def after_insert(doc, method):
- if doc.reference_type in ["CRM Lead", "CRM Deal"] and doc.reference_name and doc.allocated_to:
- fieldname = "lead_owner" if doc.reference_type == "CRM Lead" else "deal_owner"
- lead_owner = frappe.db.get_value(doc.reference_type, doc.reference_name, fieldname)
- if not lead_owner:
- frappe.db.set_value(doc.reference_type, doc.reference_name, fieldname, doc.allocated_to)
- if doc.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"] and doc.reference_name and doc.allocated_to:
- notify_assigned_user(doc)
+def after_insert(doc, method):
+ if (
+ doc.reference_type in ["CRM Lead", "CRM Deal"]
+ and doc.reference_name
+ and doc.allocated_to
+ ):
+ fieldname = "lead_owner" if doc.reference_type == "CRM Lead" else "deal_owner"
+ lead_owner = frappe.db.get_value(
+ doc.reference_type, doc.reference_name, fieldname
+ )
+ if not lead_owner:
+ frappe.db.set_value(
+ doc.reference_type, doc.reference_name, fieldname, doc.allocated_to
+ )
+
+ if (
+ doc.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"]
+ and doc.reference_name
+ and doc.allocated_to
+ ):
+ notify_assigned_user(doc)
+
def on_update(doc, method):
- if doc.has_value_changed("status") and doc.status == "Cancelled" and doc.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"] and doc.reference_name and doc.allocated_to:
- notify_assigned_user(doc, is_cancelled=True)
+ if (
+ doc.has_value_changed("status")
+ and doc.status == "Cancelled"
+ and doc.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"]
+ and doc.reference_name
+ and doc.allocated_to
+ ):
+ notify_assigned_user(doc, is_cancelled=True)
+
def notify_assigned_user(doc, is_cancelled=False):
- _doc = frappe.get_doc(doc.reference_type, doc.reference_name)
- owner = frappe.get_cached_value("User", frappe.session.user, "full_name")
- notification_text = get_notification_text(owner, doc, _doc, is_cancelled)
+ _doc = frappe.get_doc(doc.reference_type, doc.reference_name)
+ owner = frappe.get_cached_value("User", frappe.session.user, "full_name")
+ notification_text = get_notification_text(owner, doc, _doc, is_cancelled)
- message = _("Your assignment on {0} {1} has been removed by {2}").format(
- doc.reference_type,
- doc.reference_name,
- owner
- ) if is_cancelled else _("{0} assigned a {1} {2} to you").format(
- owner,
- doc.reference_type,
- doc.reference_name
- )
+ message = (
+ _("Your assignment on {0} {1} has been removed by {2}").format(
+ doc.reference_type, doc.reference_name, owner
+ )
+ if is_cancelled
+ else _("{0} assigned a {1} {2} to you").format(
+ owner, doc.reference_type, doc.reference_name
+ )
+ )
- redirect_to_doctype, redirect_to_name = get_redirect_to_doc(doc)
+ redirect_to_doctype, redirect_to_name = get_redirect_to_doc(doc)
+
+ notify_user(
+ {
+ "owner": frappe.session.user,
+ "assigned_to": doc.allocated_to,
+ "notification_type": "Assignment",
+ "message": message,
+ "notification_text": notification_text,
+ "reference_doctype": doc.reference_type,
+ "reference_docname": doc.reference_name,
+ "redirect_to_doctype": redirect_to_doctype,
+ "redirect_to_docname": redirect_to_name,
+ }
+ )
- notify_user({
- "owner": frappe.session.user,
- "assigned_to": doc.allocated_to,
- "notification_type": "Assignment",
- "message": message,
- "notification_text": notification_text,
- "reference_doctype": doc.reference_type,
- "reference_docname": doc.reference_name,
- "redirect_to_doctype": redirect_to_doctype,
- "redirect_to_docname": redirect_to_name,
- })
def get_notification_text(owner, doc, reference_doc, is_cancelled=False):
- name = doc.reference_name
- doctype = doc.reference_type
+ name = doc.reference_name
+ doctype = doc.reference_type
- if doctype.startswith("CRM "):
- doctype = doctype[4:].lower()
+ if doctype.startswith("CRM "):
+ doctype = doctype[4:].lower()
- if doctype in ["lead", "deal"]:
- name = reference_doc.lead_name or name if doctype == "lead" else reference_doc.organization or reference_doc.lead_name or name
+ if doctype in ["lead", "deal"]:
+ name = (
+ reference_doc.lead_name or name
+ if doctype == "lead"
+ else reference_doc.organization or reference_doc.lead_name or name
+ )
- if is_cancelled:
- return f"""
-
- { _('Your assignment on {0} {1} has been removed by {2}').format(
- doctype,
- f'{ name } ',
- f'{ owner } '
- ) }
-
- """
+ if is_cancelled:
+ return f"""
+
+ { _('Your assignment on {0} {1} has been removed by {2}').format(
+ doctype,
+ f'{ name } ',
+ f'{ owner } '
+ ) }
+
+ """
- return f"""
-
- { owner }
- { _('assigned a {0} {1} to you').format(
- doctype,
- f'{ name } '
- ) }
-
- """
+ return f"""
+
+ { owner }
+ { _('assigned a {0} {1} to you').format(
+ doctype,
+ f'{ name } '
+ ) }
+
+ """
+
+ if doctype == "task":
+ if is_cancelled:
+ return f"""
+
+ { _('Your assignment on task {0} has been removed by {1}').format(
+ f'{ reference_doc.title } ',
+ f'{ owner } '
+ ) }
+
+ """
+ return f"""
+
+ { owner }
+ { _('assigned a new task {0} to you').format(
+ f'{ reference_doc.title } '
+ ) }
+
+ """
- if doctype == "task":
- if is_cancelled:
- return f"""
-
- { _('Your assignment on task {0} has been removed by {1}').format(
- f'{ reference_doc.title } ',
- f'{ owner } '
- ) }
-
- """
- return f"""
-
- { owner }
- { _('assigned a new task {0} to you').format(
- f'{ reference_doc.title } '
- ) }
-
- """
def get_redirect_to_doc(doc):
- if doc.reference_type == "CRM Task":
- reference_doc = frappe.get_doc(doc.reference_type, doc.reference_name)
- return reference_doc.reference_doctype, reference_doc.reference_docname
+ if doc.reference_type == "CRM Task":
+ reference_doc = frappe.get_doc(doc.reference_type, doc.reference_name)
+ return reference_doc.reference_doctype, reference_doc.reference_docname
- return doc.reference_type, doc.reference_name
+ return doc.reference_type, doc.reference_name
diff --git a/crm/api/whatsapp.py b/crm/api/whatsapp.py
index a38c1194..3e04b5b6 100644
--- a/crm/api/whatsapp.py
+++ b/crm/api/whatsapp.py
@@ -30,25 +30,27 @@ def notify_agent(doc):
if doctype.startswith("CRM "):
doctype = doctype[4:].lower()
notification_text = f"""
-
-
{ _('You') }
+
+ { _('You') }
{ _('received a whatsapp message in {0}').format(doctype) }
- { doc.reference_name }
+ { doc.reference_name }
"""
assigned_users = get_assigned_users(doc.reference_doctype, doc.reference_name)
for user in assigned_users:
- notify_user({
- "owner": doc.owner,
- "assigned_to": user,
- "notification_type": "WhatsApp",
- "message": doc.message,
- "notification_text": notification_text,
- "reference_doctype": "WhatsApp Message",
- "reference_docname": doc.name,
- "redirect_to_doctype": doc.reference_doctype,
- "redirect_to_docname": doc.reference_name,
- })
+ notify_user(
+ {
+ "owner": doc.owner,
+ "assigned_to": user,
+ "notification_type": "WhatsApp",
+ "message": doc.message,
+ "notification_text": notification_text,
+ "reference_doctype": "WhatsApp Message",
+ "reference_docname": doc.name,
+ "redirect_to_doctype": doc.reference_doctype,
+ "redirect_to_docname": doc.reference_name,
+ }
+ )
def get_lead_or_deal_from_number(number):
@@ -92,6 +94,7 @@ def is_whatsapp_enabled():
return False
return frappe.get_cached_value("WhatsApp Settings", "WhatsApp Settings", "enabled")
+
@frappe.whitelist()
def is_whatsapp_installed():
if not frappe.db.exists("DocType", "WhatsApp Settings"):
@@ -105,8 +108,8 @@ def get_whatsapp_messages(reference_doctype, reference_name):
return []
messages = []
- if reference_doctype == 'CRM Deal':
- lead = frappe.db.get_value(reference_doctype, reference_name, 'lead')
+ if reference_doctype == "CRM Deal":
+ lead = frappe.db.get_value(reference_doctype, reference_name, "lead")
if lead:
messages = frappe.get_all(
"WhatsApp Message",
diff --git a/crm/fcrm/doctype/crm_deal/api.py b/crm/fcrm/doctype/crm_deal/api.py
index 76a764ae..7cc776bd 100644
--- a/crm/fcrm/doctype/crm_deal/api.py
+++ b/crm/fcrm/doctype/crm_deal/api.py
@@ -1,38 +1,19 @@
import frappe
-from frappe import _
-from crm.api.doc import get_fields_meta, get_assigned_users
+from crm.api.doc import get_assigned_users, get_fields_meta
from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script
+
@frappe.whitelist()
def get_deal(name):
- Deal = frappe.qb.DocType("CRM Deal")
+ deal = frappe.get_doc("CRM Deal", name).as_dict()
- query = (
- frappe.qb.from_(Deal)
- .select("*")
- .where(Deal.name == name)
- .limit(1)
- )
-
- deal = query.run(as_dict=True)
- if not len(deal):
- 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", "is_primary"],
- )
-
- deal["doctype"] = "CRM Deal"
- deal["fields_meta"] = get_fields_meta("CRM Deal")
- deal["_form_script"] = get_form_script('CRM Deal')
+ deal["fields_meta"] = get_fields_meta("CRM Deal")
+ deal["_form_script"] = get_form_script("CRM Deal")
deal["_assign"] = get_assigned_users("CRM Deal", deal.name, deal.owner)
return deal
+
@frappe.whitelist()
def get_deal_contacts(name):
contacts = frappe.get_all(
@@ -44,16 +25,19 @@ def get_deal_contacts(name):
for contact in contacts:
is_primary = contact.is_primary
contact = frappe.get_doc("Contact", contact.contact).as_dict()
+
def get_primary_email(contact):
for email in contact.email_ids:
if email.is_primary:
return email.email_id
return contact.email_ids[0].email_id if contact.email_ids else ""
+
def get_primary_mobile_no(contact):
for phone in contact.phone_nos:
if phone.is_primary:
return phone.phone
return contact.phone_nos[0].phone if contact.phone_nos else ""
+
_contact = {
"name": contact.name,
"image": contact.image,
@@ -63,4 +47,4 @@ def get_deal_contacts(name):
"is_primary": is_primary,
}
deal_contacts.append(_contact)
- return deal_contacts
\ No newline at end of file
+ return deal_contacts
diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json
index 34a22e35..133630ce 100644
--- a/crm/fcrm/doctype/crm_deal/crm_deal.json
+++ b/crm/fcrm/doctype/crm_deal/crm_deal.json
@@ -73,7 +73,7 @@
"fetch_from": ".annual_revenue",
"fieldname": "annual_revenue",
"fieldtype": "Currency",
- "label": "Amount",
+ "label": "Annual Revenue",
"options": "currency"
},
{
@@ -338,7 +338,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-09-17 18:34:15.873610",
+ "modified": "2024-12-11 14:31:41.058895",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Deal",
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 00012770..d31df954 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"
+ "options": "Quick Entry\nSide Panel\nData Fields"
},
{
"fieldname": "section_break_ttpm",
@@ -46,7 +46,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-06-13 15:10:01.612851",
+ "modified": "2024-12-05 13:29:37.021412",
"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 6926d0b2..0d06ca43 100644
--- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
+++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
@@ -2,6 +2,7 @@
# For license information, please see license.txt
import json
+
import frappe
from frappe import _
from frappe.model.document import Document
@@ -10,46 +11,54 @@ from frappe.model.document import Document
class CRMFieldsLayout(Document):
pass
+
@frappe.whitelist()
def get_fields_layout(doctype: str, type: str):
- sections = []
+ tabs = []
if frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": type}):
layout = frappe.get_doc("CRM Fields Layout", {"dt": doctype, "type": type})
else:
return []
if layout.layout:
- sections = json.loads(layout.layout)
+ tabs = json.loads(layout.layout)
+
+ has_tabs = tabs[0].get("sections") if tabs and tabs[0] else False
+
+ if not has_tabs:
+ tabs = [{"no_tabs": True, "sections": tabs}]
allowed_fields = []
- for section in sections:
- if not section.get("fields"):
- continue
- allowed_fields.extend(section.get("fields"))
+ for tab in tabs:
+ for section in tab.get("sections"):
+ if not section.get("fields"):
+ continue
+ allowed_fields.extend(section.get("fields"))
fields = frappe.get_meta(doctype).fields
fields = [field for field in fields if field.fieldname in allowed_fields]
- for section in sections:
- for field in section.get("fields") if section.get("fields") else []:
- field = next((f for f in fields if f.fieldname == field), None)
- if field:
- if field.fieldtype == "Select" and field.options:
- field.options = field.options.split("\n")
- field.options = [{"label": _(option), "value": option} for option in field.options]
- field.options.insert(0, {"label": "", "value": ""})
- field = {
- "label": _(field.label),
- "name": field.fieldname,
- "type": field.fieldtype,
- "options": field.options,
- "mandatory": field.reqd,
- "placeholder": field.get("placeholder"),
- "filters": field.get("link_filters")
- }
- section["fields"][section.get("fields").index(field["name"])] = field
+ for tab in tabs:
+ for section in tab.get("sections"):
+ for field in section.get("fields") if section.get("fields") else []:
+ field = next((f for f in fields if f.fieldname == field), None)
+ if field:
+ if field.fieldtype == "Select" and field.options:
+ field.options = field.options.split("\n")
+ field.options = [{"label": _(option), "value": option} for option in field.options]
+ field.options.insert(0, {"label": "", "value": ""})
+ field = {
+ "label": _(field.label),
+ "name": field.fieldname,
+ "type": field.fieldtype,
+ "options": field.options,
+ "mandatory": field.reqd,
+ "placeholder": field.get("placeholder"),
+ "filters": field.get("link_filters"),
+ }
+ section["fields"][section.get("fields").index(field["name"])] = field
- return sections or []
+ return tabs or []
@frappe.whitelist()
@@ -59,11 +68,13 @@ def save_fields_layout(doctype: str, type: str, layout: str):
else:
doc = frappe.new_doc("CRM Fields Layout")
- doc.update({
- "dt": doctype,
- "type": type,
- "layout": layout,
- })
+ doc.update(
+ {
+ "dt": doctype,
+ "type": type,
+ "layout": layout,
+ }
+ )
doc.save(ignore_permissions=True)
return doc.layout
diff --git a/crm/fcrm/doctype/crm_lead/api.py b/crm/fcrm/doctype/crm_lead/api.py
index e1bb4a4f..2ecaf112 100644
--- a/crm/fcrm/doctype/crm_lead/api.py
+++ b/crm/fcrm/doctype/crm_lead/api.py
@@ -1,22 +1,14 @@
import frappe
-from frappe import _
-from crm.api.doc import get_fields_meta, get_assigned_users
+from crm.api.doc import get_assigned_users, get_fields_meta
from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script
+
@frappe.whitelist()
def get_lead(name):
- Lead = frappe.qb.DocType("CRM Lead")
+ lead = frappe.get_doc("CRM Lead", name).as_dict()
- query = frappe.qb.from_(Lead).select("*").where(Lead.name == name).limit(1)
-
- lead = query.run(as_dict=True)
- if not len(lead):
- frappe.throw(_("Lead not found"), frappe.DoesNotExistError)
- lead = lead.pop()
-
- lead["doctype"] = "CRM Lead"
lead["fields_meta"] = get_fields_meta("CRM Lead")
- lead["_form_script"] = get_form_script('CRM Lead')
+ lead["_form_script"] = get_form_script("CRM Lead")
lead["_assign"] = get_assigned_users("CRM Lead", lead.name, lead.owner)
return lead
diff --git a/crm/install.py b/crm/install.py
index 594b1ea5..4da0c359 100644
--- a/crm/install.py
+++ b/crm/install.py
@@ -1,14 +1,14 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
import click
import frappe
-
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
def before_install():
pass
+
def after_install(force=False):
add_default_lead_statuses()
add_default_deal_statuses()
@@ -20,6 +20,7 @@ def after_install(force=False):
add_default_lead_sources()
frappe.db.commit()
+
def add_default_lead_statuses():
statuses = {
"New": {
@@ -58,6 +59,7 @@ def add_default_lead_statuses():
doc.position = statuses[status]["position"]
doc.insert()
+
def add_default_deal_statuses():
statuses = {
"Qualification": {
@@ -100,6 +102,7 @@ def add_default_deal_statuses():
doc.position = statuses[status]["position"]
doc.insert()
+
def add_default_communication_statuses():
statuses = ["Open", "Replied"]
@@ -111,46 +114,58 @@ def add_default_communication_statuses():
doc.status = status
doc.insert()
+
def add_default_fields_layout(force=False):
quick_entry_layouts = {
"CRM Lead-Quick Entry": {
"doctype": "CRM Lead",
- "layout": '[{"label":"Person","fields":["salutation","first_name","last_name","email","mobile_no", "gender"],"hideLabel":true},{"label":"Organization","fields":["organization","website","no_of_employees","territory","annual_revenue","industry"],"hideLabel":true,"hideBorder":false},{"label":"Other","columns":2,"fields":["status","lead_owner"],"hideLabel":true,"hideBorder":false}]'
+ "layout": '[{"label":"Person","fields":["salutation","first_name","last_name","email","mobile_no", "gender"],"hideLabel":true},{"label":"Organization","fields":["organization","website","no_of_employees","territory","annual_revenue","industry"],"hideLabel":true,"hideBorder":false},{"label":"Other","columns":2,"fields":["status","lead_owner"],"hideLabel":true,"hideBorder":false}]',
},
"CRM Deal-Quick Entry": {
"doctype": "CRM Deal",
- "layout": '[{"label": "Select Organization", "fields": ["organization"], "hideLabel": true, "editable": true}, {"label": "Organization Details", "fields": ["organization_name", "website", "no_of_employees", "territory", "annual_revenue", "industry"], "hideLabel": true, "editable": true}, {"label": "Select Contact", "fields": ["contact"], "hideLabel": true, "editable": true}, {"label": "Contact Details", "fields": ["salutation", "first_name", "last_name", "email", "mobile_no", "gender"], "hideLabel": true, "editable": true}, {"label": "Other", "columns": 2, "fields": ["status", "deal_owner"], "hideLabel": true}]'
+ "layout": '[{"label": "Select Organization", "fields": ["organization"], "hideLabel": true, "editable": true}, {"label": "Organization Details", "fields": ["organization_name", "website", "no_of_employees", "territory", "annual_revenue", "industry"], "hideLabel": true, "editable": true}, {"label": "Select Contact", "fields": ["contact"], "hideLabel": true, "editable": true}, {"label": "Contact Details", "fields": ["salutation", "first_name", "last_name", "email", "mobile_no", "gender"], "hideLabel": true, "editable": true}, {"label": "Other", "columns": 2, "fields": ["status", "deal_owner"], "hideLabel": true}]',
},
"Contact-Quick Entry": {
"doctype": "Contact",
- "layout": '[{"label":"Salutation","columns":1,"fields":["salutation"],"hideLabel":true},{"label":"Full Name","columns":2,"hideBorder":true,"fields":["first_name","last_name"],"hideLabel":true},{"label":"Email","columns":1,"hideBorder":true,"fields":["email_id"],"hideLabel":true},{"label":"Mobile No. & Gender","columns":2,"hideBorder":true,"fields":["mobile_no","gender"],"hideLabel":true},{"label":"Organization","columns":1,"hideBorder":true,"fields":["company_name"],"hideLabel":true},{"label":"Designation","columns":1,"hideBorder":true,"fields":["designation"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]'
+ "layout": '[{"label":"Salutation","columns":1,"fields":["salutation"],"hideLabel":true},{"label":"Full Name","columns":2,"hideBorder":true,"fields":["first_name","last_name"],"hideLabel":true},{"label":"Email","columns":1,"hideBorder":true,"fields":["email_id"],"hideLabel":true},{"label":"Mobile No. & Gender","columns":2,"hideBorder":true,"fields":["mobile_no","gender"],"hideLabel":true},{"label":"Organization","columns":1,"hideBorder":true,"fields":["company_name"],"hideLabel":true},{"label":"Designation","columns":1,"hideBorder":true,"fields":["designation"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]',
},
"CRM Organization-Quick Entry": {
"doctype": "CRM Organization",
- "layout": '[{"label":"Organization Name","columns":1,"fields":["organization_name"],"hideLabel":true},{"label":"Website & Revenue","columns":2,"hideBorder":true,"fields":["website","annual_revenue"],"hideLabel":true},{"label":"Territory","columns":1,"hideBorder":true,"fields":["territory"],"hideLabel":true},{"label":"No of Employees & Industry","columns":2,"hideBorder":true,"fields":["no_of_employees","industry"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]'
+ "layout": '[{"label":"Organization Name","columns":1,"fields":["organization_name"],"hideLabel":true},{"label":"Website & Revenue","columns":2,"hideBorder":true,"fields":["website","annual_revenue"],"hideLabel":true},{"label":"Territory","columns":1,"hideBorder":true,"fields":["territory"],"hideLabel":true},{"label":"No of Employees & Industry","columns":2,"hideBorder":true,"fields":["no_of_employees","industry"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]',
},
"Address-Quick Entry": {
"doctype": "Address",
- "layout": '[{"label":"Address","columns":1,"fields":["address_title","address_type","address_line1","address_line2","city","state","country","pincode"],"hideLabel":true}]'
+ "layout": '[{"label":"Address","columns":1,"fields":["address_title","address_type","address_line1","address_line2","city","state","country","pincode"],"hideLabel":true}]',
},
}
sidebar_fields_layouts = {
"CRM Lead-Side Panel": {
"doctype": "CRM Lead",
- "layout": '[{"label": "Details", "name": "details", "opened": true, "fields": ["organization", "website", "territory", "industry", "job_title", "source", "lead_owner"]}, {"label": "Person", "name": "person_tab", "opened": true, "fields": ["salutation", "first_name", "last_name", "email", "mobile_no"]}]'
+ "layout": '[{"label": "Details", "name": "details", "opened": true, "fields": ["organization", "website", "territory", "industry", "job_title", "source", "lead_owner"]}, {"label": "Person", "name": "person_tab", "opened": true, "fields": ["salutation", "first_name", "last_name", "email", "mobile_no"]}]',
},
"CRM Deal-Side Panel": {
"doctype": "CRM Deal",
- "layout": '[{"label":"Contacts","name":"contacts_section","opened":true,"editable":false,"contacts":[]},{"label":"Organization Details","name":"organization_tab","opened":true,"fields":["organization","website","territory","annual_revenue","close_date","probability","next_step","deal_owner"]}]'
+ "layout": '[{"label":"Contacts","name":"contacts_section","opened":true,"editable":false,"contacts":[]},{"label":"Organization Details","name":"organization_tab","opened":true,"fields":["organization","website","territory","annual_revenue","close_date","probability","next_step","deal_owner"]}]',
},
"Contact-Side Panel": {
"doctype": "Contact",
- "layout": '[{"label":"Details","name":"details","opened":true,"fields":["salutation","first_name","last_name","email_id","mobile_no","gender","company_name","designation","address"]}]'
+ "layout": '[{"label":"Details","name":"details","opened":true,"fields":["salutation","first_name","last_name","email_id","mobile_no","gender","company_name","designation","address"]}]',
},
"CRM Organization-Side Panel": {
"doctype": "CRM Organization",
- "layout": '[{"label":"Details","name":"details","opened":true,"fields":["organization_name","website","territory","industry","no_of_employees","address"]}]'
+ "layout": '[{"label":"Details","name":"details","opened":true,"fields":["organization_name","website","territory","industry","no_of_employees","address"]}]',
+ },
+ }
+
+ data_fields_layouts = {
+ "CRM Lead-Data Fields": {
+ "doctype": "CRM Lead",
+ "layout": '[{"no_tabs":true,"sections":[{"label": "Details", "name": "details", "opened": true, "fields": ["organization", "website", "territory", "industry", "job_title", "source", "lead_owner"]}, {"label": "Person", "name": "person_tab", "opened": true, "fields": ["salutation", "first_name", "last_name", "email", "mobile_no"]}]}]',
+ },
+ "CRM Deal-Data Fields": {
+ "doctype": "CRM Deal",
+ "layout": '[{"no_tabs":true,"sections":[{"label":"Organization Details","name":"organization_tab","opened":true,"fields":["organization","website","territory","annual_revenue","close_date","probability","next_step","deal_owner"]}]}]',
},
}
@@ -180,6 +195,20 @@ def add_default_fields_layout(force=False):
doc.layout = sidebar_fields_layouts[layout]["layout"]
doc.insert()
+ for layout in data_fields_layouts:
+ if frappe.db.exists("CRM Fields Layout", layout):
+ if force:
+ frappe.delete_doc("CRM Fields Layout", layout)
+ else:
+ continue
+
+ doc = frappe.new_doc("CRM Fields Layout")
+ doc.type = "Data Fields"
+ doc.dt = data_fields_layouts[layout]["doctype"]
+ doc.layout = data_fields_layouts[layout]["layout"]
+ doc.insert()
+
+
def add_property_setter():
if not frappe.db.exists("Property Setter", {"name": "Contact-main-search_fields"}):
doc = frappe.new_doc("Property Setter")
@@ -190,6 +219,7 @@ def add_property_setter():
doc.value = "email_id"
doc.insert()
+
def add_email_template_custom_fields():
if not frappe.get_meta("Email Template").has_field("enabled"):
click.secho("* Installing Custom Fields in Email Template")
@@ -219,7 +249,59 @@ def add_email_template_custom_fields():
def add_default_industries():
- industries = ["Accounting", "Advertising", "Aerospace", "Agriculture", "Airline", "Apparel & Accessories", "Automotive", "Banking", "Biotechnology", "Broadcasting", "Brokerage", "Chemical", "Computer", "Consulting", "Consumer Products", "Cosmetics", "Defense", "Department Stores", "Education", "Electronics", "Energy", "Entertainment & Leisure, Executive Search", "Financial Services", "Food", "Beverage & Tobacco", "Grocery", "Health Care", "Internet Publishing", "Investment Banking", "Legal", "Manufacturing", "Motion Picture & Video", "Music", "Newspaper Publishers", "Online Auctions", "Pension Funds", "Pharmaceuticals", "Private Equity", "Publishing", "Real Estate", "Retail & Wholesale", "Securities & Commodity Exchanges", "Service", "Soap & Detergent", "Software", "Sports", "Technology", "Telecommunications", "Television", "Transportation", "Venture Capital"]
+ industries = [
+ "Accounting",
+ "Advertising",
+ "Aerospace",
+ "Agriculture",
+ "Airline",
+ "Apparel & Accessories",
+ "Automotive",
+ "Banking",
+ "Biotechnology",
+ "Broadcasting",
+ "Brokerage",
+ "Chemical",
+ "Computer",
+ "Consulting",
+ "Consumer Products",
+ "Cosmetics",
+ "Defense",
+ "Department Stores",
+ "Education",
+ "Electronics",
+ "Energy",
+ "Entertainment & Leisure, Executive Search",
+ "Financial Services",
+ "Food",
+ "Beverage & Tobacco",
+ "Grocery",
+ "Health Care",
+ "Internet Publishing",
+ "Investment Banking",
+ "Legal",
+ "Manufacturing",
+ "Motion Picture & Video",
+ "Music",
+ "Newspaper Publishers",
+ "Online Auctions",
+ "Pension Funds",
+ "Pharmaceuticals",
+ "Private Equity",
+ "Publishing",
+ "Real Estate",
+ "Retail & Wholesale",
+ "Securities & Commodity Exchanges",
+ "Service",
+ "Soap & Detergent",
+ "Software",
+ "Sports",
+ "Technology",
+ "Telecommunications",
+ "Television",
+ "Transportation",
+ "Venture Capital",
+ ]
for industry in industries:
if frappe.db.exists("CRM Industry", industry):
@@ -231,7 +313,18 @@ def add_default_industries():
def add_default_lead_sources():
- lead_sources = ["Existing Customer", "Reference", "Advertisement", "Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing", "Customer's Vendor", "Campaign", "Walk In"]
+ lead_sources = [
+ "Existing Customer",
+ "Reference",
+ "Advertisement",
+ "Cold Calling",
+ "Exhibition",
+ "Supplier Reference",
+ "Mass Mailing",
+ "Customer's Vendor",
+ "Campaign",
+ "Walk In",
+ ]
for source in lead_sources:
if frappe.db.exists("CRM Lead Source", source):
diff --git a/crm/integrations/twilio/api.py b/crm/integrations/twilio/api.py
index 1ea4d67d..5ed4c28e 100644
--- a/crm/integrations/twilio/api.py
+++ b/crm/integrations/twilio/api.py
@@ -130,14 +130,14 @@ def update_call_status_info(**kwargs):
def get_datetime_from_timestamp(timestamp):
from datetime import datetime
- from pytz import timezone
+ from zoneinfo import ZoneInfo
if not timestamp: return None
datetime_utc_tz_str = timestamp.strftime('%Y-%m-%d %H:%M:%S%z')
datetime_utc_tz = datetime.strptime(datetime_utc_tz_str, '%Y-%m-%d %H:%M:%S%z')
system_timezone = frappe.utils.get_system_timezone()
- converted_datetime = datetime_utc_tz.astimezone(timezone(system_timezone))
+ converted_datetime = datetime_utc_tz.astimezone(ZoneInfo(system_timezone))
return frappe.utils.format_datetime(converted_datetime, 'yyyy-MM-dd HH:mm:ss')
@frappe.whitelist()
@@ -179,4 +179,4 @@ def get_lead_or_deal_from_number(call):
if not doc:
doc = find_record(doctype, number)
- return doc, doctype
\ No newline at end of file
+ return doc, doctype
diff --git a/crm/patches.txt b/crm/patches.txt
index b14faf13..a0e91a3d 100644
--- a/crm/patches.txt
+++ b/crm/patches.txt
@@ -6,6 +6,6 @@ crm.patches.v1_0.move_crm_note_data_to_fcrm_note
[post_model_sync]
# Patches added in this section will be executed after doctypes are migrated
crm.patches.v1_0.create_email_template_custom_fields
-crm.patches.v1_0.create_default_fields_layout #31/10/2024
+crm.patches.v1_0.create_default_fields_layout #10/12/2024
crm.patches.v1_0.create_default_sidebar_fields_layout
crm.patches.v1_0.update_deal_quick_entry_layout
\ No newline at end of file
diff --git a/crm/www/crm.py b/crm/www/crm.py
index 22b2ed58..04b14037 100644
--- a/crm/www/crm.py
+++ b/crm/www/crm.py
@@ -3,7 +3,14 @@
from __future__ import unicode_literals
import frappe
+from frappe.utils import cint, get_system_timezone
from frappe.utils.telemetry import capture
+from frappe.locale import (
+ get_date_format,
+ get_first_day_of_the_week,
+ get_number_format,
+ get_time_format,
+)
no_cache = 1
@@ -32,6 +39,20 @@ def get_boot():
"site_name": frappe.local.site,
"read_only_mode": frappe.flags.read_only,
"csrf_token": frappe.sessions.get_csrf_token(),
+ "setup_complete": cint(frappe.get_system_settings("setup_complete")),
+ "sysdefaults": {
+ "float_precision": cint(frappe.get_system_settings("float_precision"))
+ or 3,
+ "date_format": get_date_format(),
+ "time_format": get_time_format(),
+ "first_day_of_the_week": get_first_day_of_the_week(),
+ "number_format": get_number_format().string,
+ },
+ "timezone": {
+ "system": get_system_timezone(),
+ "user": frappe.db.get_value("User", frappe.session.user, "time_zone")
+ or get_system_timezone(),
+ },
}
)
diff --git a/frappe-ui b/frappe-ui
index b2dbd419..5a4f3c8d 160000
--- a/frappe-ui
+++ b/frappe-ui
@@ -1 +1 @@
-Subproject commit b2dbd41936905aa46b18d3c22e5d09a7b08a9b98
+Subproject commit 5a4f3c8d4f12efba37b9a83a51a59b53fa758be0
diff --git a/frontend/package.json b/frontend/package.json
index 56683963..2782be37 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.71",
+ "frappe-ui": "^0.1.91",
"gemoji": "^8.1.0",
"lodash": "^4.17.21",
"mime": "^4.0.1",
@@ -22,7 +22,6 @@
"socket.io-client": "^4.7.2",
"sortablejs": "^1.15.0",
"tailwindcss": "^3.3.3",
- "vite": "^4.4.9",
"vue": "^3.4.12",
"vue-router": "^4.2.2",
"vuedraggable": "^4.1.0"
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 61e774c9..02c42926 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -9,14 +9,14 @@
diff --git a/frontend/src/assets/Inter/Inter-Black.woff b/frontend/src/assets/Inter/Inter-Black.woff
deleted file mode 100644
index c7737ed3..00000000
Binary files a/frontend/src/assets/Inter/Inter-Black.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Black.woff2 b/frontend/src/assets/Inter/Inter-Black.woff2
deleted file mode 100644
index b16b995b..00000000
Binary files a/frontend/src/assets/Inter/Inter-Black.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-BlackItalic.woff b/frontend/src/assets/Inter/Inter-BlackItalic.woff
deleted file mode 100644
index b5f14476..00000000
Binary files a/frontend/src/assets/Inter/Inter-BlackItalic.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-BlackItalic.woff2 b/frontend/src/assets/Inter/Inter-BlackItalic.woff2
deleted file mode 100644
index a3f1b70c..00000000
Binary files a/frontend/src/assets/Inter/Inter-BlackItalic.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Bold.woff b/frontend/src/assets/Inter/Inter-Bold.woff
deleted file mode 100644
index e3845558..00000000
Binary files a/frontend/src/assets/Inter/Inter-Bold.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Bold.woff2 b/frontend/src/assets/Inter/Inter-Bold.woff2
deleted file mode 100644
index 835dd497..00000000
Binary files a/frontend/src/assets/Inter/Inter-Bold.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-BoldItalic.woff b/frontend/src/assets/Inter/Inter-BoldItalic.woff
deleted file mode 100644
index ffac3f59..00000000
Binary files a/frontend/src/assets/Inter/Inter-BoldItalic.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-BoldItalic.woff2 b/frontend/src/assets/Inter/Inter-BoldItalic.woff2
deleted file mode 100644
index 1a41a14f..00000000
Binary files a/frontend/src/assets/Inter/Inter-BoldItalic.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ExtraBold.woff b/frontend/src/assets/Inter/Inter-ExtraBold.woff
deleted file mode 100644
index 885ac94f..00000000
Binary files a/frontend/src/assets/Inter/Inter-ExtraBold.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ExtraBold.woff2 b/frontend/src/assets/Inter/Inter-ExtraBold.woff2
deleted file mode 100644
index ae956b15..00000000
Binary files a/frontend/src/assets/Inter/Inter-ExtraBold.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff b/frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff
deleted file mode 100644
index d6cf8623..00000000
Binary files a/frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff2 b/frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff2
deleted file mode 100644
index 86578995..00000000
Binary files a/frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ExtraLight.woff b/frontend/src/assets/Inter/Inter-ExtraLight.woff
deleted file mode 100644
index ff769193..00000000
Binary files a/frontend/src/assets/Inter/Inter-ExtraLight.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ExtraLight.woff2 b/frontend/src/assets/Inter/Inter-ExtraLight.woff2
deleted file mode 100644
index 694b2df9..00000000
Binary files a/frontend/src/assets/Inter/Inter-ExtraLight.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ExtraLightItalic.woff b/frontend/src/assets/Inter/Inter-ExtraLightItalic.woff
deleted file mode 100644
index c6ed13a4..00000000
Binary files a/frontend/src/assets/Inter/Inter-ExtraLightItalic.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ExtraLightItalic.woff2 b/frontend/src/assets/Inter/Inter-ExtraLightItalic.woff2
deleted file mode 100644
index 9a7bd110..00000000
Binary files a/frontend/src/assets/Inter/Inter-ExtraLightItalic.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Italic.woff b/frontend/src/assets/Inter/Inter-Italic.woff
deleted file mode 100644
index 4fdb59dc..00000000
Binary files a/frontend/src/assets/Inter/Inter-Italic.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Italic.woff2 b/frontend/src/assets/Inter/Inter-Italic.woff2
deleted file mode 100644
index deca637d..00000000
Binary files a/frontend/src/assets/Inter/Inter-Italic.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Light.woff b/frontend/src/assets/Inter/Inter-Light.woff
deleted file mode 100644
index 42850acc..00000000
Binary files a/frontend/src/assets/Inter/Inter-Light.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Light.woff2 b/frontend/src/assets/Inter/Inter-Light.woff2
deleted file mode 100644
index 65a7dadd..00000000
Binary files a/frontend/src/assets/Inter/Inter-Light.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-LightItalic.woff b/frontend/src/assets/Inter/Inter-LightItalic.woff
deleted file mode 100644
index c4ed9a94..00000000
Binary files a/frontend/src/assets/Inter/Inter-LightItalic.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-LightItalic.woff2 b/frontend/src/assets/Inter/Inter-LightItalic.woff2
deleted file mode 100644
index 555fc559..00000000
Binary files a/frontend/src/assets/Inter/Inter-LightItalic.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Medium.woff b/frontend/src/assets/Inter/Inter-Medium.woff
deleted file mode 100644
index 495faef7..00000000
Binary files a/frontend/src/assets/Inter/Inter-Medium.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Medium.woff2 b/frontend/src/assets/Inter/Inter-Medium.woff2
deleted file mode 100644
index 871ce4ce..00000000
Binary files a/frontend/src/assets/Inter/Inter-Medium.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-MediumItalic.woff b/frontend/src/assets/Inter/Inter-MediumItalic.woff
deleted file mode 100644
index 389c7a2b..00000000
Binary files a/frontend/src/assets/Inter/Inter-MediumItalic.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-MediumItalic.woff2 b/frontend/src/assets/Inter/Inter-MediumItalic.woff2
deleted file mode 100644
index aa805799..00000000
Binary files a/frontend/src/assets/Inter/Inter-MediumItalic.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Regular.woff b/frontend/src/assets/Inter/Inter-Regular.woff
deleted file mode 100644
index fa7715d1..00000000
Binary files a/frontend/src/assets/Inter/Inter-Regular.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Regular.woff2 b/frontend/src/assets/Inter/Inter-Regular.woff2
deleted file mode 100644
index b52dd0a0..00000000
Binary files a/frontend/src/assets/Inter/Inter-Regular.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-SemiBold.woff b/frontend/src/assets/Inter/Inter-SemiBold.woff
deleted file mode 100644
index 18d7749f..00000000
Binary files a/frontend/src/assets/Inter/Inter-SemiBold.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-SemiBold.woff2 b/frontend/src/assets/Inter/Inter-SemiBold.woff2
deleted file mode 100644
index ece5204a..00000000
Binary files a/frontend/src/assets/Inter/Inter-SemiBold.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-SemiBoldItalic.woff b/frontend/src/assets/Inter/Inter-SemiBoldItalic.woff
deleted file mode 100644
index 8ee64396..00000000
Binary files a/frontend/src/assets/Inter/Inter-SemiBoldItalic.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-SemiBoldItalic.woff2 b/frontend/src/assets/Inter/Inter-SemiBoldItalic.woff2
deleted file mode 100644
index b32c0ba3..00000000
Binary files a/frontend/src/assets/Inter/Inter-SemiBoldItalic.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Thin.woff b/frontend/src/assets/Inter/Inter-Thin.woff
deleted file mode 100644
index 1a22286f..00000000
Binary files a/frontend/src/assets/Inter/Inter-Thin.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-Thin.woff2 b/frontend/src/assets/Inter/Inter-Thin.woff2
deleted file mode 100644
index c56bc7ca..00000000
Binary files a/frontend/src/assets/Inter/Inter-Thin.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ThinItalic.woff b/frontend/src/assets/Inter/Inter-ThinItalic.woff
deleted file mode 100644
index d8ec8373..00000000
Binary files a/frontend/src/assets/Inter/Inter-ThinItalic.woff and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-ThinItalic.woff2 b/frontend/src/assets/Inter/Inter-ThinItalic.woff2
deleted file mode 100644
index eca5608c..00000000
Binary files a/frontend/src/assets/Inter/Inter-ThinItalic.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-italic.var.woff2 b/frontend/src/assets/Inter/Inter-italic.var.woff2
deleted file mode 100644
index 1f5d9261..00000000
Binary files a/frontend/src/assets/Inter/Inter-italic.var.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter-roman.var.woff2 b/frontend/src/assets/Inter/Inter-roman.var.woff2
deleted file mode 100644
index 05621d8d..00000000
Binary files a/frontend/src/assets/Inter/Inter-roman.var.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/Inter.var.woff2 b/frontend/src/assets/Inter/Inter.var.woff2
deleted file mode 100644
index 46bb5153..00000000
Binary files a/frontend/src/assets/Inter/Inter.var.woff2 and /dev/null differ
diff --git a/frontend/src/assets/Inter/inter.css b/frontend/src/assets/Inter/inter.css
deleted file mode 100644
index 3ca1bbf6..00000000
--- a/frontend/src/assets/Inter/inter.css
+++ /dev/null
@@ -1,152 +0,0 @@
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 100;
- font-display: swap;
- src: url("Inter-Thin.woff2?v=3.12") format("woff2"),
- url("Inter-Thin.woff?v=3.12") format("woff");
-}
-@font-face {
- font-family: 'Inter';
- font-style: italic;
- font-weight: 100;
- font-display: swap;
- src: url("Inter-ThinItalic.woff2?v=3.12") format("woff2"),
- url("Inter-ThinItalic.woff?v=3.12") format("woff");
-}
-
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 200;
- font-display: swap;
- src: url("Inter-ExtraLight.woff2?v=3.12") format("woff2"),
- url("Inter-ExtraLight.woff?v=3.12") format("woff");
-}
-@font-face {
- font-family: 'Inter';
- font-style: italic;
- font-weight: 200;
- font-display: swap;
- src: url("Inter-ExtraLightItalic.woff2?v=3.12") format("woff2"),
- url("Inter-ExtraLightItalic.woff?v=3.12") format("woff");
-}
-
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 300;
- font-display: swap;
- src: url("Inter-Light.woff2?v=3.12") format("woff2"),
- url("Inter-Light.woff?v=3.12") format("woff");
-}
-@font-face {
- font-family: 'Inter';
- font-style: italic;
- font-weight: 300;
- font-display: swap;
- src: url("Inter-LightItalic.woff2?v=3.12") format("woff2"),
- url("Inter-LightItalic.woff?v=3.12") format("woff");
-}
-
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url("Inter-Regular.woff2?v=3.12") format("woff2"),
- url("Inter-Regular.woff?v=3.12") format("woff");
-}
-@font-face {
- font-family: 'Inter';
- font-style: italic;
- font-weight: 400;
- font-display: swap;
- src: url("Inter-Italic.woff2?v=3.12") format("woff2"),
- url("Inter-Italic.woff?v=3.12") format("woff");
-}
-
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url("Inter-Medium.woff2?v=3.12") format("woff2"),
- url("Inter-Medium.woff?v=3.12") format("woff");
-}
-@font-face {
- font-family: 'Inter';
- font-style: italic;
- font-weight: 500;
- font-display: swap;
- src: url("Inter-MediumItalic.woff2?v=3.12") format("woff2"),
- url("Inter-MediumItalic.woff?v=3.12") format("woff");
-}
-
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url("Inter-SemiBold.woff2?v=3.12") format("woff2"),
- url("Inter-SemiBold.woff?v=3.12") format("woff");
-}
-@font-face {
- font-family: 'Inter';
- font-style: italic;
- font-weight: 600;
- font-display: swap;
- src: url("Inter-SemiBoldItalic.woff2?v=3.12") format("woff2"),
- url("Inter-SemiBoldItalic.woff?v=3.12") format("woff");
-}
-
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url("Inter-Bold.woff2?v=3.12") format("woff2"),
- url("Inter-Bold.woff?v=3.12") format("woff");
-}
-@font-face {
- font-family: 'Inter';
- font-style: italic;
- font-weight: 700;
- font-display: swap;
- src: url("Inter-BoldItalic.woff2?v=3.12") format("woff2"),
- url("Inter-BoldItalic.woff?v=3.12") format("woff");
-}
-
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 800;
- font-display: swap;
- src: url("Inter-ExtraBold.woff2?v=3.12") format("woff2"),
- url("Inter-ExtraBold.woff?v=3.12") format("woff");
-}
-@font-face {
- font-family: 'Inter';
- font-style: italic;
- font-weight: 800;
- font-display: swap;
- src: url("Inter-ExtraBoldItalic.woff2?v=3.12") format("woff2"),
- url("Inter-ExtraBoldItalic.woff?v=3.12") format("woff");
-}
-
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 900;
- font-display: swap;
- src: url("Inter-Black.woff2?v=3.12") format("woff2"),
- url("Inter-Black.woff?v=3.12") format("woff");
-}
-@font-face {
- font-family: 'Inter';
- font-style: italic;
- font-weight: 900;
- font-display: swap;
- src: url("Inter-BlackItalic.woff2?v=3.12") format("woff2"),
- url("Inter-BlackItalic.woff?v=3.12") format("woff");
-}
diff --git a/frontend/src/components/Activities/Activities.vue b/frontend/src/components/Activities/Activities.vue
index 2a3f3dd6..baf28de0 100644
--- a/frontend/src/components/Activities/Activities.vue
+++ b/frontend/src/components/Activities/Activities.vue
@@ -16,7 +16,7 @@
>
{{ __('Loading...') }}
@@ -50,13 +50,13 @@
class="activity grid grid-cols-[30px_minmax(auto,_1fr)] gap-2 px-3 sm:gap-4 sm:px-10"
>
@@ -72,15 +72,15 @@
class="activity grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-3 sm:px-10"
>
@@ -185,10 +185,10 @@
>
{{ activity.owner_name }}
-
{{ __(activity.data.type) }}
+
{{ __(activity.data.type) }}
-
-
+
+
{{ __(timeAgo(activity.creation)) }}
@@ -225,7 +225,7 @@
{{ activity.show_others ? __('Hide') : __('Show') }}
+{{ activity.other_versions.length + 1 }}
@@ -243,22 +243,22 @@
-
+
{{ activity.owner_name }}
{{ __(activity.type) }}
{{ __(activity.data.field_label) }}
{{ __(activity.value) }}
{{ __('to') }}
-
-
+
+
{{ __(timeAgo(activity.creation)) }}
@@ -305,23 +305,23 @@
v-for="activity in [activity, ...activity.other_versions]"
class="flex items-start justify-stretch gap-2 py-1.5 text-base"
>
-
+
{{ __(activity.data.field_label) }}
{{ startCase(__(activity.type)) }}
{{ __('to') }}
-
-
+
+
{{ __(timeAgo(activity.creation)) }}
@@ -366,9 +364,12 @@
+
+
+
{{ __(emptyText) }}
@@ -456,9 +457,11 @@ import CallArea from '@/components/Activities/CallArea.vue'
import NoteArea from '@/components/Activities/NoteArea.vue'
import TaskArea from '@/components/Activities/TaskArea.vue'
import AttachmentArea from '@/components/Activities/AttachmentArea.vue'
+import DataFields from '@/components/Activities/DataFields.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue'
+import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue'
@@ -481,13 +484,7 @@ import CommunicationArea from '@/components/CommunicationArea.vue'
import WhatsappTemplateSelectorModal from '@/components/Modals/WhatsappTemplateSelectorModal.vue'
import AllModals from '@/components/Activities/AllModals.vue'
import FilesUploader from '@/components/FilesUploader/FilesUploader.vue'
-import {
- timeAgo,
- dateFormat,
- dateTooltipFormat,
- secondsToDuration,
- startCase,
-} from '@/utils'
+import { timeAgo, formatDate, secondsToDuration, startCase } from '@/utils'
import { globalStore } from '@/stores/global'
import { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts'
@@ -727,6 +724,8 @@ const emptyText = computed(() => {
text = 'No Email Communications'
} else if (title.value == 'Comments') {
text = 'No Comments'
+ } else if (title.value == 'Data') {
+ text = 'No Data'
} else if (title.value == 'Calls') {
text = 'No Call Logs'
} else if (title.value == 'Notes') {
@@ -747,6 +746,8 @@ const emptyTextIcon = computed(() => {
icon = Email2Icon
} else if (title.value == 'Comments') {
icon = CommentIcon
+ } else if (title.value == 'Data') {
+ icon = DetailsIcon
} else if (title.value == 'Calls') {
icon = PhoneIcon
} else if (title.value == 'Notes') {
@@ -758,7 +759,7 @@ const emptyTextIcon = computed(() => {
} else if (title.value == 'WhatsApp') {
icon = WhatsAppIcon
}
- return h(icon, { class: 'text-gray-500' })
+ return h(icon, { class: 'text-ink-gray-4' })
})
function timelineIcon(activity_type, is_lead) {
diff --git a/frontend/src/components/Activities/ActivityHeader.vue b/frontend/src/components/Activities/ActivityHeader.vue
index 9f734d4e..78ad93a7 100644
--- a/frontend/src/components/Activities/ActivityHeader.vue
+++ b/frontend/src/components/Activities/ActivityHeader.vue
@@ -1,8 +1,9 @@
-
+
{{ __(title) }}
-
+
{{ attachment.file_name }}
-
+
{{ convertSize(attachment.file_size) }}
-
-
+
+
{{ __(timeAgo(attachment.creation)) }}
@@ -51,7 +51,7 @@
>
@@ -60,7 +60,7 @@
class="!size-5"
@click.stop="() => deleteAttachment(attachment.name)"
>
-
+
@@ -68,7 +68,7 @@
@@ -79,13 +79,7 @@ import FileTextIcon from '@/components/Icons/FileTextIcon.vue'
import FileVideoIcon from '@/components/Icons/FileVideoIcon.vue'
import { globalStore } from '@/stores/global'
import { call, Tooltip } from 'frappe-ui'
-import {
- dateFormat,
- timeAgo,
- dateTooltipFormat,
- convertSize,
- isImage,
-} from '@/utils'
+import { formatDate, timeAgo, convertSize, isImage } from '@/utils'
const props = defineProps({
attachments: Array,
diff --git a/frontend/src/components/Activities/AudioPlayer.vue b/frontend/src/components/Activities/AudioPlayer.vue
index 8ede3c02..71b28b1c 100644
--- a/frontend/src/components/Activities/AudioPlayer.vue
+++ b/frontend/src/components/Activities/AudioPlayer.vue
@@ -1,17 +1,17 @@
-
+
-
-
+
+
-
+
-
+
{{ activity.caller.label }}
{{
@@ -17,15 +17,15 @@
}}
-
-
+
+
{{ __(timeAgo(activity.creation)) }}
-
+
@@ -97,7 +97,7 @@ import DurationIcon from '@/components/Icons/DurationIcon.vue'
import MultipleAvatar from '@/components/MultipleAvatar.vue'
import AudioPlayer from '@/components/Activities/AudioPlayer.vue'
import { statusLabelMap, statusColorMap } from '@/utils/callLog.js'
-import { dateFormat, timeAgo, dateTooltipFormat } from '@/utils'
+import { formatDate, timeAgo } from '@/utils'
import { Avatar, Badge, Tooltip } from 'frappe-ui'
const props = defineProps({
diff --git a/frontend/src/components/Activities/CommentArea.vue b/frontend/src/components/Activities/CommentArea.vue
index 0adb51ed..f4708719 100644
--- a/frontend/src/components/Activities/CommentArea.vue
+++ b/frontend/src/components/Activities/CommentArea.vue
@@ -1,26 +1,26 @@
-
+
-
+
{{ activity.owner_name }}
{{ __('added a') }}
-
+
{{ __('comment') }}
-
-
+
+
{{ __(timeAgo(activity.creation)) }}
@@ -38,7 +38,7 @@
import UserAvatar from '@/components/UserAvatar.vue'
import AttachmentItem from '@/components/AttachmentItem.vue'
import { Tooltip } from 'frappe-ui'
-import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils'
+import { timeAgo, formatDate } from '@/utils'
const props = defineProps({
activity: Object,
})
diff --git a/frontend/src/components/Activities/DataFields.vue b/frontend/src/components/Activities/DataFields.vue
new file mode 100644
index 00000000..287d4a8a
--- /dev/null
+++ b/frontend/src/components/Activities/DataFields.vue
@@ -0,0 +1,108 @@
+
+
+
+ {{ __('Data') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ __('Loading...') }}
+
+
+
+
+ {
+ tabs.reload()
+ data.reload()
+ }
+ "
+ />
+
+
+
diff --git a/frontend/src/components/Activities/EmailArea.vue b/frontend/src/components/Activities/EmailArea.vue
index 33e11f16..61ee2f69 100644
--- a/frontend/src/components/Activities/EmailArea.vue
+++ b/frontend/src/components/Activities/EmailArea.vue
@@ -1,11 +1,11 @@
-
+
{{ activity.data.sender_full_name }}
-
+
{{ '<' + activity.data.sender + '>' }}
-
-
+
+
{{ __(timeAgo(activity.creation)) }}
@@ -32,7 +32,7 @@
@@ -45,7 +45,7 @@
@@ -57,24 +57,24 @@
-
+
{{ activity.data.subject }}
- {{ __('To') }}:
+ {{ __('To') }}:
{{ activity.data.recipients }}
,
-
+
{{ __('CC') }}:
{{ activity.data.cc }}
,
-
+
{{ __('BCC') }}:
{{ activity.data.bcc }}
-
+
@@ -20,7 +20,7 @@
@@ -28,21 +28,21 @@
v-if="note.content"
:content="note.content"
:editable="false"
- editor-class="!prose-sm max-w-none !text-sm text-gray-600 focus:outline-none"
+ editor-class="!prose-sm max-w-none !text-sm text-ink-gray-5 focus:outline-none"
class="flex-1 overflow-hidden"
/>
{{ getUser(note.owner).full_name }}
-
-
+
+
{{ __(timeAgo(note.modified)) }}
@@ -51,7 +51,7 @@
diff --git a/frontend/src/components/CommunicationArea.vue b/frontend/src/components/CommunicationArea.vue
index 384b75fc..2bfb77ab 100644
--- a/frontend/src/components/CommunicationArea.vue
+++ b/frontend/src/components/CommunicationArea.vue
@@ -4,7 +4,7 @@
@@ -15,7 +15,7 @@
diff --git a/frontend/src/components/Controls/Link.vue b/frontend/src/components/Controls/Link.vue
index 3bcb3d7f..6f995f70 100644
--- a/frontend/src/components/Controls/Link.vue
+++ b/frontend/src/components/Controls/Link.vue
@@ -1,5 +1,5 @@
-
+
{{ __(attrs.label) }}
@@ -34,7 +34,7 @@
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
- @click="attrs.onCreate(value, close)"
+ @click="() => attrs.onCreate(value, close)"
>
@@ -70,8 +70,8 @@ const props = defineProps({
required: true,
},
filters: {
- type: Array,
- default: () => [],
+ type: [Array, String],
+ default: [],
},
modelValue: {
type: String,
@@ -110,13 +110,13 @@ watchDebounced(
text.value = val
reload(val)
},
- { debounce: 300, immediate: true }
+ { debounce: 300, immediate: true },
)
watchDebounced(
() => props.doctype,
() => reload(''),
- { debounce: 300, immediate: true }
+ { debounce: 300, immediate: true },
)
const options = createResource({
@@ -174,7 +174,7 @@ const labelClasses = computed(() => {
sm: 'text-xs',
md: 'text-base',
}[attrs.size || 'sm'],
- 'text-gray-600',
+ 'text-ink-gray-5',
]
})
diff --git a/frontend/src/components/Controls/MultiValueInput.vue b/frontend/src/components/Controls/MultiValueInput.vue
index d2f385ee..9574c378 100644
--- a/frontend/src/components/Controls/MultiValueInput.vue
+++ b/frontend/src/components/Controls/MultiValueInput.vue
@@ -1,7 +1,7 @@
@@ -23,9 +23,9 @@
-
{
}
function setFocus() {
- search.value.el.focus()
+ search.value.focus()
}
defineExpose({ setFocus })
diff --git a/frontend/src/components/Controls/MultiselectInput.vue b/frontend/src/components/Controls/MultiselectInput.vue
index a5cb6928..c3b77dd5 100644
--- a/frontend/src/components/Controls/MultiselectInput.vue
+++ b/frontend/src/components/Controls/MultiselectInput.vue
@@ -25,7 +25,7 @@
-
+
-
+
{{ option.label }}
-
@@ -146,16 +148,15 @@ const filterOptions = createResource({
cache: [text.value, 'Contact'],
params: { txt: text.value },
transform: (data) => {
- let allData = data
- .map((option) => {
- let fullName = option[0]
- let email = option[1]
- let name = option[2]
- return {
- label: fullName || name || email,
- value: email,
- }
- })
+ let allData = data.map((option) => {
+ let fullName = option[0]
+ let email = option[1]
+ let name = option[2]
+ return {
+ label: fullName || name || email,
+ value: email,
+ }
+ })
return allData
},
})
diff --git a/frontend/src/components/DropdownItem.vue b/frontend/src/components/DropdownItem.vue
index f292d902..8f2ed2da 100644
--- a/frontend/src/components/DropdownItem.vue
+++ b/frontend/src/components/DropdownItem.vue
@@ -1,6 +1,6 @@
{{ option.value }}
@@ -20,7 +20,7 @@
variant="ghost"
:label="__('Save')"
size="sm"
- class="opacity-0 hover:bg-gray-300 group-hover:opacity-100"
+ class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
@click="saveOption"
/>
@@ -28,7 +28,7 @@
@@ -40,7 +40,7 @@
@@ -53,7 +53,7 @@
variant="ghost"
icon="x"
size="sm"
- class="opacity-0 hover:bg-gray-300 group-hover:opacity-100"
+ class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
@click="() => option.onDelete(option, isNew)"
/>
@@ -61,7 +61,7 @@
-
+
diff --git a/frontend/src/components/EmailEditor.vue b/frontend/src/components/EmailEditor.vue
index b33f22df..f2451539 100644
--- a/frontend/src/components/EmailEditor.vue
+++ b/frontend/src/components/EmailEditor.vue
@@ -19,7 +19,7 @@
- {{ __('TO') }}:
+ {{ __('TO') }}:
- {{ __('CC') }}:
+ {{ __('CC') }}:
- {{ __('BCC') }}:
+ {{ __('BCC') }}:
- {{ __('SUBJECT') }}:
- {{ __('SUBJECT') }}:
+
diff --git a/frontend/src/components/FieldLayout.vue b/frontend/src/components/FieldLayout.vue
new file mode 100644
index 00000000..e28a3436
--- /dev/null
+++ b/frontend/src/components/FieldLayout.vue
@@ -0,0 +1,271 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ __(field.label) }}
+ *
+
+
+
+
+
+
+
+
+ (data[field.name] = e.target.checked)"
+ :disabled="Boolean(field.read_only)"
+ />
+
+ {{ __(field.label) }}
+ *
+
+
+
+ (data[field.name] = v)"
+ :placeholder="getPlaceholder(field)"
+ :onCreate="field.create"
+ />
+
+
+
+
+
+
+
+
(data[field.name] = v)"
+ :placeholder="getPlaceholder(field)"
+ :hideMe="true"
+ >
+
+
+
+
+
+
+
+
+
+ {{ getUser(option.value).full_name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/FieldLayoutEditor.vue b/frontend/src/components/FieldLayoutEditor.vue
new file mode 100644
index 00000000..4421199f
--- /dev/null
+++ b/frontend/src/components/FieldLayoutEditor.vue
@@ -0,0 +1,366 @@
+
+
+
+
+
(tabIndex = e.newIndex)"
+ >
+
+
+
(tab.editingLabel = true)">
+
+ {{ __(tab.label) || __('Untitled') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
(section.editingLabel = true)"
+ >
+
+ {{ __(section.label) || __('Untitled') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
addField(section, e)"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
{{ option.label }}
+
+ {{ `${option.fieldname} - ${option.fieldtype}` }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/Fields.vue b/frontend/src/components/Fields.vue
deleted file mode 100644
index 2eba0a77..00000000
--- a/frontend/src/components/Fields.vue
+++ /dev/null
@@ -1,212 +0,0 @@
-
-
-
-
- {{ section.label }}
-
-
-
-
-
- {{ __(field.label) }}
- *
-
-
-
-
-
-
-
-
- (data[field.name] = e.target.checked)"
- :disabled="Boolean(field.read_only)"
- />
-
- {{ __(field.label) }}
- *
-
-
-
- (data[field.name] = v)"
- :placeholder="getPlaceholder(field)"
- :onCreate="field.create"
- />
-
-
-
-
-
-
-
-
(data[field.name] = v)"
- :placeholder="getPlaceholder(field)"
- :hideMe="true"
- >
-
-
-
-
-
-
-
-
-
- {{ getUser(option.value).full_name }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/components/FilesUploader/FilesUploader.vue b/frontend/src/components/FilesUploader/FilesUploader.vue
index c5fa957c..2daff30e 100644
--- a/frontend/src/components/FilesUploader/FilesUploader.vue
+++ b/frontend/src/components/FilesUploader/FilesUploader.vue
@@ -169,7 +169,7 @@ function uploadViaWebLink() {
title: __('Error'),
title: __('Please enter a valid URL'),
icon: 'x',
- iconClasses: 'text-red-600',
+ iconClasses: 'text-ink-red-4',
})
return
}
diff --git a/frontend/src/components/FilesUploader/FilesUploaderArea.vue b/frontend/src/components/FilesUploader/FilesUploaderArea.vue
index 2f940298..8768f4e8 100644
--- a/frontend/src/components/FilesUploader/FilesUploaderArea.vue
+++ b/frontend/src/components/FilesUploader/FilesUploaderArea.vue
@@ -13,14 +13,14 @@
-
+
{{ __('Drag and drop files here or upload from') }}
-
-
+
+
{{ file.name }}
@@ -85,7 +85,7 @@
{{ filters.size }}
@@ -27,7 +27,9 @@
-
+
-
+
{{ i == 0 ? __('Where') : __('And') }}
-
+
{{ i == 0 ? __('Where') : __('And') }}
@@ -117,7 +119,7 @@
{{ __('Empty - Choose a field to filter by') }}
@@ -130,7 +132,7 @@
>
(emoji = r) && togglePopover()"
@@ -26,9 +26,12 @@
@click.stop="() => (reaction = false)"
/>
-