Skip to content

Commit

Permalink
Merge pull request #215 from frappe/develop
Browse files Browse the repository at this point in the history
chore: Merge develop to main
  • Loading branch information
shariquerik authored Jun 7, 2024
2 parents 41b2df7 + 5cb0386 commit 2a7ad26
Show file tree
Hide file tree
Showing 17 changed files with 429 additions and 556 deletions.
38 changes: 38 additions & 0 deletions crm/api/doc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import frappe
import json
from frappe import _
from frappe.model.document import get_controller
from frappe.model import no_value_fields
Expand Down Expand Up @@ -156,6 +157,43 @@ def get_group_by_fields(doctype: str):
return fields


@frappe.whitelist()
def get_quick_entry_fields(doctype: str):
sections = []
if frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": "Quick Entry"}):
layout = frappe.get_doc("CRM Fields Layout", {"dt": doctype, "type": "Quick Entry"})
else:
return []

if layout.layout:
sections = json.loads(layout.layout)

allowed_fields = []
for section in sections:
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"):
field = next((f for f in fields if f.fieldname == field), None)
if field:
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": ""})
field = {
"label": _(field.label),
"name": field.fieldname,
"type": field.fieldtype,
"options": field.options,
"mandatory": field.reqd,
}
section["fields"][section.get("fields").index(field["name"])] = field

return sections or []

def get_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fields):
parent = "parent" if DocField._table_name == "tabDocField" else "dt"
return (
Expand Down
Empty file.
8 changes: 8 additions & 0 deletions crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

// frappe.ui.form.on("CRM Fields Layout", {
// refresh(frm) {

// },
// });
73 changes: 73 additions & 0 deletions crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:{dt}-{type}",
"creation": "2024-06-07 16:42:05.495324",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"dt",
"column_break_post",
"type",
"section_break_ttpm",
"layout"
],
"fields": [
{
"fieldname": "dt",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Document Type",
"options": "DocType",
"unique": 1
},
{
"fieldname": "type",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Type",
"options": "Quick Entry\nSide Panel"
},
{
"fieldname": "section_break_ttpm",
"fieldtype": "Section Break"
},
{
"fieldname": "layout",
"fieldtype": "Code",
"label": "Layout",
"options": "JS"
},
{
"fieldname": "column_break_post",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-06-07 17:01:20.250697",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Fields Layout",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
9 changes: 9 additions & 0 deletions crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document


class CRMFieldsLayout(Document):
pass
9 changes: 9 additions & 0 deletions crm/fcrm/doctype/crm_fields_layout/test_crm_fields_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt

# import frappe
from frappe.tests.utils import FrappeTestCase


class TestCRMFieldsLayout(FrappeTestCase):
pass
11 changes: 9 additions & 2 deletions crm/fcrm/doctype/crm_status_change_log/crm_status_change_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@

import frappe
from datetime import datetime
from frappe.utils import add_to_date
from frappe.utils import add_to_date, get_datetime
from frappe.model.document import Document


class CRMStatusChangeLog(Document):
pass

def get_duration(from_date, to_date):
if not isinstance(from_date, datetime):
from_date = get_datetime(from_date)
if not isinstance(to_date, datetime):
to_date = get_datetime(to_date)
duration = to_date - from_date
return duration.total_seconds()

def add_status_change_log(doc):
if not doc.is_new():
Expand All @@ -27,7 +34,7 @@ def add_status_change_log(doc):
last_status_change.to = doc.status
last_status_change.to_date = datetime.now()
last_status_change.log_owner = frappe.session.user
last_status_change.duration = (last_status_change.to_date - last_status_change.from_date).total_seconds()
last_status_change.duration = get_duration(last_status_change.from_date, last_status_change.to_date)

doc.append("status_change_log", {
"from": doc.status,
Expand Down
31 changes: 31 additions & 0 deletions crm/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def after_install():
add_default_lead_statuses()
add_default_deal_statuses()
add_default_communication_statuses()
add_default_fields_layout()
add_property_setter()
add_email_template_custom_fields()
frappe.db.commit()
Expand Down Expand Up @@ -108,6 +109,36 @@ def add_default_communication_statuses():
doc.status = status
doc.insert()

def add_default_fields_layout():
layouts = {
"CRM Lead-Quick Entry": {
"doctype": "CRM Lead",
"layout": '[\n{\n"label": "Person",\n\t"fields": ["salutation", "first_name", "last_name", "email", "mobile_no", "gender"]\n},\n{\n"label": "Organization",\n\t"fields": ["organization", "website", "no_of_employees", "territory", "annual_revenue", "industry"]\n},\n{\n"label": "Other",\n"columns": 2,\n\t"fields": ["status", "lead_owner"]\n}\n]'
},
"CRM Deal-Quick Entry": {
"doctype": "CRM Deal",
"layout": '[\n{\n"label": "Select Organization",\n\t"fields": ["organization"]\n},\n{\n"label": "Organization Details",\n\t"fields": [{"label": "Organization Name", "name": "organization_name", "type": "Data"}, "website", "no_of_employees", "territory", "annual_revenue", {"label": "Industry", "name": "industry", "type": "Link", "options": "CRM Industry"}]\n},\n{\n"label": "Select Contact",\n\t"fields": [{"label": "Contact", "name": "contact", "type": "Link", "options": "Contact"}]\n},\n{\n"label": "Contact Details",\n\t"fields": [{"label": "Salutation", "name": "salutation", "type": "Link", "options": "Salutation"}, {"label": "First Name", "name": "first_name", "type": "Data"}, {"label": "Last Name", "name": "last_name", "type": "Data"}, "email", "mobile_no", {"label": "Gender", "name": "gender", "type": "Link", "options": "Gender"}]\n},\n{\n"label": "Other",\n"columns": 2,\n\t"fields": ["status", "deal_owner"]\n}\n]'
},
"Contact-Quick Entry": {
"doctype": "Contact",
"layout": '[\n{\n"label": "Salutation",\n"columns": 1,\n"fields": ["salutation"]\n},\n{\n"label": "Full Name",\n"columns": 2,\n"hideBorder": true,\n"fields": ["first_name", "last_name"]\n},\n{\n"label": "Email",\n"columns": 1,\n"hideBorder": true,\n"fields": ["email_id"]\n},\n{\n"label": "Mobile No. & Gender",\n"columns": 2,\n"hideBorder": true,\n"fields": ["mobile_no", "gender"]\n},\n{\n"label": "Organization",\n"columns": 1,\n"hideBorder": true,\n"fields": ["company_name"]\n},\n{\n"label": "Designation",\n"columns": 1,\n"hideBorder": true,\n"fields": ["designation"]\n}\n]'
},
"Organization-Quick Entry": {
"doctype": "CRM Organization",
"layout": '[\n{\n"label": "Organization Name",\n"columns": 1,\n"fields": ["organization_name"]\n},\n{\n"label": "Website & Revenue",\n"columns": 2,\n"hideBorder": true,\n"fields": ["website", "annual_revenue"]\n},\n{\n"label": "Territory",\n"columns": 1,\n"hideBorder": true,\n"fields": ["territory"]\n},\n{\n"label": "No of Employees & Industry",\n"columns": 2,\n"hideBorder": true,\n"fields": ["no_of_employees", "industry"]\n}\n]'
},
}

for layout in layouts:
if frappe.db.exists("CRM Fields Layout", layout):
continue

doc = frappe.new_doc("CRM Fields Layout")
doc.type = "Quick Entry"
doc.dt = layouts[layout]["doctype"]
doc.layout = 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")
Expand Down
3 changes: 2 additions & 1 deletion crm/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ 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_email_template_custom_fields
crm.patches.v1_0.create_default_fields_layout
5 changes: 5 additions & 0 deletions crm/patches/v1_0/create_default_fields_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

from crm.install import add_default_fields_layout

def execute():
add_default_fields_layout()
27 changes: 15 additions & 12 deletions frontend/src/components/Fields.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
<div class="flex flex-col gap-4">
<div
v-for="section in sections"
:key="section.section"
:key="section.label"
class="first:border-t-0 first:pt-0"
:class="section.hideBorder ? '' : 'border-t pt-4'"
>
<div
class="grid gap-4"
:class="
section.columns ? 'grid-cols-' + section.columns : 'grid-cols-3'
section.columns
? 'grid-cols-' + section.columns
: 'grid-cols-2 sm:grid-cols-3'
"
>
<div v-for="field in section.fields" :key="field.name">
Expand All @@ -18,34 +20,34 @@
<span class="text-red-500" v-if="field.mandatory">*</span>
</div>
<FormControl
v-if="field.type === 'select'"
v-if="field.type === 'Select'"
type="select"
class="form-control"
:class="field.prefix ? 'prefix' : ''"
:options="field.options"
v-model="data[field.name]"
:placeholder="__(field.placeholder)"
:placeholder="__(field.placeholder || field.label)"
>
<template v-if="field.prefix" #prefix>
<IndicatorIcon :class="field.prefix" />
</template>
</FormControl>
<Link
v-else-if="field.type === 'link'"
v-else-if="field.type === 'Link'"
class="form-control"
:value="data[field.name]"
:doctype="field.doctype"
:doctype="field.options"
@change="(v) => (data[field.name] = v)"
:placeholder="__(field.placeholder)"
:placeholder="__(field.placeholder || field.label)"
:onCreate="field.create"
/>
<Link
v-else-if="field.type === 'user'"
v-else-if="field.type === 'User'"
class="form-control"
:value="getUser(data[field.name]).full_name"
:doctype="field.doctype"
:doctype="field.options"
@change="(v) => (data[field.name] = v)"
:placeholder="__(field.placeholder)"
:placeholder="__(field.placeholder || field.label)"
:hideMe="true"
>
<template #prefix>
Expand All @@ -62,7 +64,7 @@
</Tooltip>
</template>
</Link>
<div v-else-if="field.type === 'dropdown'">
<div v-else-if="field.type === 'Dropdown'">
<NestedPopover>
<template #target="{ open }">
<Button
Expand Down Expand Up @@ -114,7 +116,7 @@
<FormControl
v-else
type="text"
:placeholder="__(field.placeholder)"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
/>
</div>
Expand All @@ -131,6 +133,7 @@ import UserAvatar from '@/components/UserAvatar.vue'
import Link from '@/components/Controls/Link.vue'
import { usersStore } from '@/stores/users'
import { Tooltip } from 'frappe-ui'
import { isMobileView } from '@/composables/settings'
const { getUser } = usersStore()
Expand Down
Loading

0 comments on commit 2a7ad26

Please sign in to comment.