snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 1s
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 1s
This commit is contained in:
0
crm/patches/v1_0/__init__.py
Normal file
0
crm/patches/v1_0/__init__.py
Normal file
5
crm/patches/v1_0/add_email_in_default_lead_sources.py
Normal file
5
crm/patches/v1_0/add_email_in_default_lead_sources.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from crm.install import add_default_lead_sources
|
||||
|
||||
|
||||
def execute():
|
||||
add_default_lead_sources()
|
||||
78
crm/patches/v1_0/add_enrichment_fields_to_layouts.py
Normal file
78
crm/patches/v1_0/add_enrichment_fields_to_layouts.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""Expose the Domain Enrichment fields on existing sites.
|
||||
|
||||
The enrichment fields (company_description, linkedin, twitter, facebook) were added
|
||||
to the default Lead/Deal/Organization layouts in ``crm/install.py``, but that seeder
|
||||
is skip-if-exists -- so sites installed before this feature never get them and users
|
||||
can't see what enrichment writes. This patch injects any missing enrichment field
|
||||
into the relevant Side Panel / Data Fields / Quick Entry layouts. Idempotent: a
|
||||
field already present anywhere in the layout (or absent from the doctype) is left
|
||||
untouched.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
|
||||
ENRICHMENT_FIELDS = ["company_description", "linkedin", "twitter", "facebook"]
|
||||
|
||||
TARGET_LAYOUTS = [
|
||||
"CRM Lead-Side Panel",
|
||||
"CRM Deal-Side Panel",
|
||||
"CRM Organization-Side Panel",
|
||||
"CRM Lead-Data Fields",
|
||||
"CRM Deal-Data Fields",
|
||||
# Quick Entry = the create-modal layout; enrichment fields must be visible there.
|
||||
"CRM Lead-Quick Entry",
|
||||
"CRM Deal-Quick Entry",
|
||||
"CRM Organization-Quick Entry",
|
||||
]
|
||||
|
||||
|
||||
def _iter_columns(layout):
|
||||
"""Yield every column dict in a layout tree (handles tabbed layouts that nest
|
||||
sections under a top-level section's ``sections`` key)."""
|
||||
for section in layout:
|
||||
yield from section.get("columns", [])
|
||||
for nested in section.get("sections", []):
|
||||
yield from nested.get("columns", [])
|
||||
|
||||
|
||||
def _visible_columns(layout):
|
||||
"""Columns of the first non-hidden section (handles tabbed layouts). Skipping
|
||||
hidden sections matters for Quick Entry, whose first section (organization) is
|
||||
hidden -- fields must land in a section the user can actually see."""
|
||||
for section in layout:
|
||||
if section.get("hidden"):
|
||||
continue
|
||||
if section.get("columns"):
|
||||
return section["columns"]
|
||||
for nested in section.get("sections", []):
|
||||
if not nested.get("hidden") and nested.get("columns"):
|
||||
return nested["columns"]
|
||||
return None
|
||||
|
||||
|
||||
def execute():
|
||||
for name in TARGET_LAYOUTS:
|
||||
if not frappe.db.exists("CRM Fields Layout", name):
|
||||
continue
|
||||
|
||||
doc = frappe.get_doc("CRM Fields Layout", name)
|
||||
try:
|
||||
layout = json.loads(doc.layout or "[]")
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
present = {f for col in _iter_columns(layout) for f in col.get("fields", [])}
|
||||
meta = frappe.get_meta(doc.dt)
|
||||
missing = [f for f in ENRICHMENT_FIELDS if f not in present and meta.has_field(f)]
|
||||
if not missing:
|
||||
continue
|
||||
|
||||
target = _visible_columns(layout)
|
||||
if not target:
|
||||
continue
|
||||
|
||||
target[0].setdefault("fields", []).extend(missing)
|
||||
doc.layout = json.dumps(layout)
|
||||
doc.save()
|
||||
5
crm/patches/v1_0/add_fb_lead_source.py
Normal file
5
crm/patches/v1_0/add_fb_lead_source.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.get_doc({"doctype": "CRM Lead Source", "source_name": "Facebook"}).insert(ignore_if_duplicate=True)
|
||||
9
crm/patches/v1_0/add_fields_in_assignment_rule.py
Normal file
9
crm/patches/v1_0/add_fields_in_assignment_rule.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from crm.install import (
|
||||
add_assignment_rule_property_setters,
|
||||
create_assignment_rule_custom_fields,
|
||||
)
|
||||
|
||||
|
||||
def execute():
|
||||
create_assignment_rule_custom_fields()
|
||||
add_assignment_rule_property_setters()
|
||||
@@ -0,0 +1,9 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.get_single_value("FCRM Settings", "enable_forecasting"):
|
||||
return
|
||||
|
||||
settings = frappe.get_doc("FCRM Settings")
|
||||
settings.add_forecasting_section("Quick Entry")
|
||||
18
crm/patches/v1_0/create_custom_fields_for_erpnext_in_crm.py
Normal file
18
crm/patches/v1_0/create_custom_fields_for_erpnext_in_crm.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
enabled = frappe.db.get_single_value("ERPNext CRM Settings", "enabled")
|
||||
if enabled:
|
||||
custom_fields = {
|
||||
"CRM Deal": [
|
||||
{
|
||||
"fieldname": "erpnext_customer",
|
||||
"fieldtype": "Data",
|
||||
"label": "Customer in ERPNext",
|
||||
"insert_after": "lead_name",
|
||||
}
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields, ignore_validate=True)
|
||||
@@ -0,0 +1,35 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.get_single_value("ERPNext CRM Settings", "enabled"):
|
||||
return
|
||||
|
||||
custom_fields = {
|
||||
"CRM Product": [
|
||||
{
|
||||
"fieldname": "erpnext_item_code",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Code in ERPNext",
|
||||
"read_only": 1,
|
||||
"insert_after": "product_code",
|
||||
}
|
||||
]
|
||||
}
|
||||
if frappe.db.exists("DocType", "Item"):
|
||||
custom_fields["Item"] = [
|
||||
{
|
||||
"fieldname": "crm_product_code",
|
||||
"fieldtype": "Data",
|
||||
"label": "CRM Product",
|
||||
"read_only": 1,
|
||||
"no_copy": 1,
|
||||
"insert_after": "item_code",
|
||||
}
|
||||
]
|
||||
create_custom_fields(custom_fields, ignore_validate=True)
|
||||
|
||||
# Latest ERPNext removed the create_custom_fields_for_frappe_crm API and creates its own
|
||||
# fields automatically so we no longer call into ERPNext here.
|
||||
frappe.get_single("ERPNext CRM Settings").create_custom_fields_in_frappe_crm()
|
||||
5
crm/patches/v1_0/create_default_fields_layout.py
Normal file
5
crm/patches/v1_0/create_default_fields_layout.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from crm.install import add_default_fields_layout
|
||||
|
||||
|
||||
def execute():
|
||||
add_default_fields_layout()
|
||||
5
crm/patches/v1_0/create_default_lost_reasons.py
Normal file
5
crm/patches/v1_0/create_default_lost_reasons.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from crm.install import add_default_lost_reasons
|
||||
|
||||
|
||||
def execute():
|
||||
add_default_lost_reasons()
|
||||
5
crm/patches/v1_0/create_default_quick_filters.py
Normal file
5
crm/patches/v1_0/create_default_quick_filters.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from crm.install import add_default_quick_filters
|
||||
|
||||
|
||||
def execute():
|
||||
add_default_quick_filters()
|
||||
5
crm/patches/v1_0/create_default_scripts.py
Normal file
5
crm/patches/v1_0/create_default_scripts.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from crm.install import add_default_scripts
|
||||
|
||||
|
||||
def execute():
|
||||
add_default_scripts()
|
||||
68
crm/patches/v1_0/create_default_sidebar_fields_layout.py
Normal file
68
crm/patches/v1_0/create_default_sidebar_fields_layout.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("CRM Fields Layout", {"dt": "CRM Lead", "type": "Side Panel"}):
|
||||
create_doctype_fields_layout("CRM Lead")
|
||||
|
||||
if not frappe.db.exists("CRM Fields Layout", {"dt": "CRM Deal", "type": "Side Panel"}):
|
||||
create_doctype_fields_layout("CRM Deal")
|
||||
|
||||
|
||||
def create_doctype_fields_layout(doctype):
|
||||
not_allowed_fieldtypes = [
|
||||
"Section Break",
|
||||
"Column Break",
|
||||
]
|
||||
|
||||
fields = frappe.get_meta(doctype).fields
|
||||
fields = [field for field in fields if field.fieldtype not in not_allowed_fieldtypes]
|
||||
|
||||
sections = {}
|
||||
section_fields = []
|
||||
last_section = None
|
||||
|
||||
for field in fields:
|
||||
if field.fieldtype == "Tab Break" and last_section:
|
||||
sections[last_section]["fields"] = section_fields
|
||||
last_section = None
|
||||
if field.read_only:
|
||||
section_fields = []
|
||||
continue
|
||||
if field.fieldtype == "Tab Break":
|
||||
if field.read_only:
|
||||
section_fields = []
|
||||
continue
|
||||
section_fields = []
|
||||
last_section = field.fieldname
|
||||
sections[field.fieldname] = {
|
||||
"label": field.label,
|
||||
"name": field.fieldname,
|
||||
"opened": True,
|
||||
"fields": [],
|
||||
}
|
||||
if field.fieldname == "contacts_tab":
|
||||
sections[field.fieldname]["editable"] = False
|
||||
sections[field.fieldname]["contacts"] = []
|
||||
else:
|
||||
section_fields.append(field.fieldname)
|
||||
|
||||
section_fields = []
|
||||
for section in sections:
|
||||
if section == "contacts_tab":
|
||||
sections[section]["name"] = "contacts_section"
|
||||
sections[section].pop("fields", None)
|
||||
section_fields.append(sections[section])
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "CRM Fields Layout",
|
||||
"dt": doctype,
|
||||
"type": "Side Panel",
|
||||
"layout": json.dumps(section_fields),
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
return section_fields
|
||||
5
crm/patches/v1_0/create_email_account_custom_field.py
Normal file
5
crm/patches/v1_0/create_email_account_custom_field.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from crm.install import add_email_account_custom_field
|
||||
|
||||
|
||||
def execute():
|
||||
add_email_account_custom_field()
|
||||
5
crm/patches/v1_0/create_email_template_custom_fields.py
Normal file
5
crm/patches/v1_0/create_email_template_custom_fields.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from crm.install import add_email_template_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
add_email_template_custom_fields()
|
||||
32
crm/patches/v1_0/move_crm_note_data_to_fcrm_note.py
Normal file
32
crm/patches/v1_0/move_crm_note_data_to_fcrm_note.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import frappe
|
||||
from frappe.model.rename_doc import rename_doc
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("DocType", "FCRM Note"):
|
||||
frappe.flags.ignore_route_conflict_validation = True
|
||||
rename_doc("DocType", "CRM Note", "FCRM Note")
|
||||
frappe.flags.ignore_route_conflict_validation = False
|
||||
|
||||
frappe.reload_doctype("FCRM Note", force=True)
|
||||
|
||||
if frappe.db.exists("DocType", "FCRM Note") and frappe.db.count("FCRM Note") > 0:
|
||||
return
|
||||
|
||||
notes = frappe.db.sql("SELECT * FROM `tabCRM Note`", as_dict=True)
|
||||
if notes:
|
||||
for note in notes:
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "FCRM Note",
|
||||
"creation": note.get("creation"),
|
||||
"modified": note.get("modified"),
|
||||
"modified_by": note.get("modified_by"),
|
||||
"owner": note.get("owner"),
|
||||
"title": note.get("title"),
|
||||
"content": note.get("content"),
|
||||
"reference_doctype": note.get("reference_doctype"),
|
||||
"reference_docname": note.get("reference_docname"),
|
||||
}
|
||||
)
|
||||
doc.db_insert()
|
||||
27
crm/patches/v1_0/move_twilio_agent_to_telephony_agent.py
Normal file
27
crm/patches/v1_0/move_twilio_agent_to_telephony_agent.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("DocType", "CRM Telephony Agent"):
|
||||
frappe.reload_doctype("CRM Telephony Agent", force=True)
|
||||
|
||||
if frappe.db.exists("DocType", "Twilio Agents") and frappe.db.count("Twilio Agents") == 0:
|
||||
return
|
||||
|
||||
agents = frappe.db.sql("SELECT * FROM `tabTwilio Agents`", as_dict=True)
|
||||
if agents:
|
||||
for agent in agents:
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "CRM Telephony Agent",
|
||||
"creation": agent.get("creation"),
|
||||
"modified": agent.get("modified"),
|
||||
"modified_by": agent.get("modified_by"),
|
||||
"owner": agent.get("owner"),
|
||||
"user": agent.get("user"),
|
||||
"twilio_number": agent.get("twilio_number"),
|
||||
"user_name": agent.get("user_name"),
|
||||
"twilio": True,
|
||||
}
|
||||
)
|
||||
doc.db_insert()
|
||||
@@ -0,0 +1,20 @@
|
||||
import frappe
|
||||
from frappe.model.rename_doc import rename_doc
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.exists("DocType", "Twilio Settings"):
|
||||
frappe.flags.ignore_route_conflict_validation = True
|
||||
rename_doc("DocType", "Twilio Settings", "CRM Twilio Settings")
|
||||
frappe.flags.ignore_route_conflict_validation = False
|
||||
|
||||
frappe.reload_doctype("CRM Twilio Settings", force=True)
|
||||
|
||||
if frappe.db.exists("__Auth", {"doctype": "Twilio Settings"}):
|
||||
Auth = frappe.qb.DocType("__Auth")
|
||||
result = frappe.qb.from_(Auth).select("*").where(Auth.doctype == "Twilio Settings").run(as_dict=True)
|
||||
|
||||
for row in result:
|
||||
frappe.qb.into(Auth).insert(
|
||||
"CRM Twilio Settings", "CRM Twilio Settings", row.fieldname, row.password, row.encrypted
|
||||
).run()
|
||||
59
crm/patches/v1_0/reorder_address_quick_entry_layout.py
Normal file
59
crm/patches/v1_0/reorder_address_quick_entry_layout.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
|
||||
OLD_FIELDS = [
|
||||
"address_title",
|
||||
"address_type",
|
||||
"address_line1",
|
||||
"address_line2",
|
||||
"city",
|
||||
"state",
|
||||
"country",
|
||||
"pincode",
|
||||
]
|
||||
|
||||
NEW_LAYOUT = [
|
||||
{
|
||||
"name": "details_section",
|
||||
"columns": [
|
||||
{
|
||||
"name": "column_uSSG",
|
||||
"fields": ["address_title", "address_type", "address_line1", "address_line2"],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "location_section",
|
||||
"hideBorder": True,
|
||||
"columns": [
|
||||
{"name": "column_TCoZ", "fields": ["country", "city"]},
|
||||
{"name": "column_PqrK", "fields": ["state", "pincode"]},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("CRM Fields Layout", "Address-Quick Entry"):
|
||||
return
|
||||
|
||||
layout = frappe.db.get_value("CRM Fields Layout", "Address-Quick Entry", "layout")
|
||||
if not layout:
|
||||
return
|
||||
|
||||
parsed = json.loads(layout)
|
||||
|
||||
# Only replace the layout if it's still the untouched default (single
|
||||
# section/column with the original field order) — a site that has already
|
||||
# customized this Quick Entry should keep its own arrangement.
|
||||
is_default = (
|
||||
isinstance(parsed, list)
|
||||
and len(parsed) == 1
|
||||
and len(parsed[0].get("columns", [])) == 1
|
||||
and parsed[0]["columns"][0].get("fields") == OLD_FIELDS
|
||||
)
|
||||
if not is_default:
|
||||
return
|
||||
|
||||
frappe.db.set_value("CRM Fields Layout", "Address-Quick Entry", "layout", json.dumps(NEW_LAYOUT))
|
||||
19
crm/patches/v1_0/reset_erpnext_form_script.py
Normal file
19
crm/patches/v1_0/reset_erpnext_form_script.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import frappe
|
||||
|
||||
from crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings import get_crm_form_script
|
||||
|
||||
|
||||
def execute():
|
||||
reset_erpnext_form_script()
|
||||
|
||||
|
||||
def reset_erpnext_form_script():
|
||||
try:
|
||||
if frappe.db.exists("CRM Form Script", "Create Quotation from CRM Deal"):
|
||||
script = get_crm_form_script()
|
||||
frappe.db.set_value("CRM Form Script", "Create Quotation from CRM Deal", "script", script)
|
||||
return True
|
||||
return False
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), "Error while resetting form script")
|
||||
return False
|
||||
@@ -0,0 +1,8 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
# Existing sites already have data by upgrade time; skip onboarding for them.
|
||||
# Fresh installs are still empty here (demo data is created post setup wizard).
|
||||
if frappe.db.count("CRM Lead") or frappe.db.count("CRM Deal"):
|
||||
frappe.db.set_single_value("FCRM Settings", "persona_captured", 1)
|
||||
46
crm/patches/v1_0/set_seen_for_existing_deals_and_leads.py
Normal file
46
crm/patches/v1_0/set_seen_for_existing_deals_and_leads.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.query_builder import DocType
|
||||
|
||||
from crm.api.session import CRM_ALLOWED_ROLES
|
||||
|
||||
DOCTYPES = ("CRM Deal", "CRM Lead")
|
||||
|
||||
|
||||
def execute():
|
||||
"""Backfill `_seen` for CRM Deals/Leads that predate track_seen being enabled.
|
||||
|
||||
Without this, every record created before track_seen was turned on would
|
||||
show up as unvisited (bold) to everyone, since there's no real view
|
||||
history to reconstruct. Default them to already seen by every enabled
|
||||
CRM user instead, so only records created or updated from now on show
|
||||
as unvisited.
|
||||
"""
|
||||
users = get_crm_users()
|
||||
if not users:
|
||||
return
|
||||
|
||||
seen = json.dumps(users)
|
||||
|
||||
for doctype in DOCTYPES:
|
||||
table = DocType(doctype)
|
||||
frappe.qb.update(table).set(table._seen, seen).where(
|
||||
table._seen.isnull() | table._seen.isin(["", "[]"])
|
||||
).run()
|
||||
|
||||
|
||||
def get_crm_users():
|
||||
users_with_crm_role = frappe.get_all(
|
||||
"Has Role",
|
||||
filters={"parenttype": "User", "role": ["in", CRM_ALLOWED_ROLES]},
|
||||
pluck="parent",
|
||||
distinct=True,
|
||||
)
|
||||
users_with_crm_role.append("Administrator")
|
||||
|
||||
return frappe.get_all(
|
||||
"User",
|
||||
filters={"name": ["in", users_with_crm_role], "enabled": 1},
|
||||
pluck="name",
|
||||
)
|
||||
22
crm/patches/v1_0/update_deal_quick_entry_layout.py
Normal file
22
crm/patches/v1_0/update_deal_quick_entry_layout.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("CRM Fields Layout", "CRM Deal-Quick Entry"):
|
||||
return
|
||||
|
||||
deal = frappe.db.get_value("CRM Fields Layout", "CRM Deal-Quick Entry", "layout")
|
||||
|
||||
layout = json.loads(deal)
|
||||
for section in layout:
|
||||
if section.get("label") in [
|
||||
"Select Organization",
|
||||
"Organization Details",
|
||||
"Select Contact",
|
||||
"Contact Details",
|
||||
]:
|
||||
section["editable"] = False
|
||||
|
||||
frappe.db.set_value("CRM Fields Layout", "CRM Deal-Quick Entry", "layout", json.dumps(layout))
|
||||
24
crm/patches/v1_0/update_deal_status_probabilities.py
Normal file
24
crm/patches/v1_0/update_deal_status_probabilities.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
deal_statuses = frappe.get_all("CRM Deal Status", fields=["name", "probability", "deal_status"])
|
||||
|
||||
for status in deal_statuses:
|
||||
if status.probability is None or status.probability == 0:
|
||||
if status.deal_status == "Qualification":
|
||||
probability = 10
|
||||
elif status.deal_status == "Demo/Making":
|
||||
probability = 25
|
||||
elif status.deal_status == "Proposal/Quotation":
|
||||
probability = 50
|
||||
elif status.deal_status == "Negotiation":
|
||||
probability = 70
|
||||
elif status.deal_status == "Ready to Close":
|
||||
probability = 90
|
||||
elif status.deal_status == "Won":
|
||||
probability = 100
|
||||
else:
|
||||
probability = 0
|
||||
|
||||
frappe.db.set_value("CRM Deal Status", status.name, "probability", probability)
|
||||
44
crm/patches/v1_0/update_deal_status_type.py
Normal file
44
crm/patches/v1_0/update_deal_status_type.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
deal_statuses = frappe.get_all("CRM Deal Status", fields=["name", "type", "deal_status"])
|
||||
|
||||
openStatuses = ["New", "Open", "Unassigned", "Qualification"]
|
||||
ongoingStatuses = [
|
||||
"Demo/Making",
|
||||
"Proposal/Quotation",
|
||||
"Negotiation",
|
||||
"Ready to Close",
|
||||
"Demo Scheduled",
|
||||
"Follow Up",
|
||||
]
|
||||
onHoldStatuses = ["On Hold", "Paused", "Stalled", "Awaiting Reply"]
|
||||
wonStatuses = ["Won", "Closed Won", "Successful", "Completed"]
|
||||
lostStatuses = [
|
||||
"Lost",
|
||||
"Closed",
|
||||
"Closed Lost",
|
||||
"Junk",
|
||||
"Unqualified",
|
||||
"Disqualified",
|
||||
"Cancelled",
|
||||
"No Response",
|
||||
]
|
||||
|
||||
for status in deal_statuses:
|
||||
if not status.type or status.type is None or status.type == "Open":
|
||||
if status.deal_status in openStatuses:
|
||||
type = "Open"
|
||||
elif status.deal_status in ongoingStatuses:
|
||||
type = "Ongoing"
|
||||
elif status.deal_status in onHoldStatuses:
|
||||
type = "On Hold"
|
||||
elif status.deal_status in wonStatuses:
|
||||
type = "Won"
|
||||
elif status.deal_status in lostStatuses:
|
||||
type = "Lost"
|
||||
else:
|
||||
type = "Ongoing"
|
||||
|
||||
frappe.db.set_value("CRM Deal Status", status.name, "type", type)
|
||||
101
crm/patches/v1_0/update_layouts_to_new_format.py
Normal file
101
crm/patches/v1_0/update_layouts_to_new_format.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import json
|
||||
from math import ceil
|
||||
|
||||
import frappe
|
||||
from frappe.utils import random_string
|
||||
|
||||
|
||||
def execute():
|
||||
layouts = frappe.get_all("CRM Fields Layout", fields=["name", "layout", "type"])
|
||||
|
||||
for layout in layouts:
|
||||
old_layout = layout.layout
|
||||
new_layout = get_new_layout(old_layout, layout.type)
|
||||
|
||||
frappe.db.set_value("CRM Fields Layout", layout.name, "layout", new_layout)
|
||||
|
||||
|
||||
def get_new_layout(old_layout, type):
|
||||
if isinstance(old_layout, str):
|
||||
old_layout = json.loads(old_layout)
|
||||
new_layout = []
|
||||
already_converted = False
|
||||
|
||||
starts_with_sections = False
|
||||
|
||||
if not old_layout[0].get("sections"):
|
||||
starts_with_sections = True
|
||||
|
||||
if starts_with_sections:
|
||||
old_layout = [{"sections": old_layout}]
|
||||
|
||||
for tab in old_layout:
|
||||
new_tab = tab.copy()
|
||||
if "no_tabs" in new_tab:
|
||||
new_tab.pop("no_tabs")
|
||||
new_tab["sections"] = []
|
||||
new_tab["name"] = "tab_" + str(random_string(4))
|
||||
for section in tab.get("sections"):
|
||||
section["name"] = section.get("name") or "section_" + str(random_string(4))
|
||||
|
||||
if section.get("label") == "Select Organization":
|
||||
section["name"] = "organization_section"
|
||||
section["hidden"] = 1
|
||||
elif section.get("label") == "Organization Details":
|
||||
section["name"] = "organization_details_section"
|
||||
elif section.get("label") == "Select Contact":
|
||||
section["name"] = "contact_section"
|
||||
section["hidden"] = 1
|
||||
elif section.get("label") == "Contact Details":
|
||||
section["name"] = "contact_details_section"
|
||||
|
||||
if "contacts" in section:
|
||||
new_tab["sections"].append(section)
|
||||
continue
|
||||
if isinstance(section.get("columns"), list):
|
||||
already_converted = True
|
||||
break
|
||||
column_count = section.get("columns") or 3
|
||||
if type == "Side Panel":
|
||||
column_count = 1
|
||||
fields = section.get("fields") or []
|
||||
|
||||
new_section = section.copy()
|
||||
|
||||
if "fields" in new_section:
|
||||
new_section.pop("fields")
|
||||
new_section["columns"] = []
|
||||
|
||||
if len(fields) == 0:
|
||||
new_section["columns"].append({"name": "column_" + str(random_string(4)), "fields": []})
|
||||
new_tab["sections"].append(new_section)
|
||||
continue
|
||||
|
||||
if len(fields) == 1 and column_count > 1:
|
||||
new_section["columns"].append(
|
||||
{"name": "column_" + str(random_string(4)), "fields": [fields[0]]}
|
||||
)
|
||||
new_section["columns"].append({"name": "column_" + str(random_string(4)), "fields": []})
|
||||
new_tab["sections"].append(new_section)
|
||||
continue
|
||||
|
||||
fields_per_column = ceil(len(fields) / column_count)
|
||||
for i in range(column_count):
|
||||
new_column = {
|
||||
"name": "column_" + str(random_string(4)),
|
||||
"fields": fields[i * fields_per_column : (i + 1) * fields_per_column],
|
||||
}
|
||||
new_section["columns"].append(new_column)
|
||||
new_tab["sections"].append(new_section)
|
||||
new_layout.append(new_tab)
|
||||
|
||||
if starts_with_sections:
|
||||
new_layout = new_layout[0].get("sections")
|
||||
|
||||
if already_converted:
|
||||
new_layout = old_layout
|
||||
|
||||
if type == "Side Panel" and "sections" in new_layout[0]:
|
||||
new_layout = new_layout[0].get("sections")
|
||||
|
||||
return json.dumps(new_layout)
|
||||
59
crm/patches/v1_0/update_lead_status_type.py
Normal file
59
crm/patches/v1_0/update_lead_status_type.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("CRM Lead Status", "Converted"):
|
||||
status_doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "CRM Lead Status",
|
||||
"lead_status": "Converted",
|
||||
"color": "teal",
|
||||
"type": "Won",
|
||||
"position": 5,
|
||||
}
|
||||
)
|
||||
status_doc.insert()
|
||||
|
||||
lead_statuses = frappe.get_all("CRM Lead Status", fields=["name", "type", "lead_status"])
|
||||
|
||||
openStatuses = ["New", "Open", "Unassigned"]
|
||||
ongoingStatuses = [
|
||||
"Contacted",
|
||||
"Nurture",
|
||||
"Interested",
|
||||
"Demo/Making",
|
||||
"Proposal/Quotation",
|
||||
"Negotiation",
|
||||
"Ready to Close",
|
||||
"Demo Scheduled",
|
||||
"Follow Up",
|
||||
]
|
||||
onHoldStatuses = ["On Hold", "Paused", "Stalled", "Awaiting Reply"]
|
||||
wonStatuses = ["Won", "Closed Won", "Successful", "Completed", "Qualified", "Converted"]
|
||||
lostStatuses = [
|
||||
"Lost",
|
||||
"Closed",
|
||||
"Closed Lost",
|
||||
"Junk",
|
||||
"Unqualified",
|
||||
"Disqualified",
|
||||
"Cancelled",
|
||||
"No Response",
|
||||
]
|
||||
|
||||
for status in lead_statuses:
|
||||
if not status.type or status.type is None or status.type == "Open":
|
||||
if status.lead_status in openStatuses:
|
||||
type = "Open"
|
||||
elif status.lead_status in ongoingStatuses:
|
||||
type = "Ongoing"
|
||||
elif status.lead_status in onHoldStatuses:
|
||||
type = "On Hold"
|
||||
elif status.lead_status in wonStatuses:
|
||||
type = "Won"
|
||||
elif status.lead_status in lostStatuses:
|
||||
type = "Lost"
|
||||
else:
|
||||
type = "Ongoing"
|
||||
|
||||
frappe.db.set_value("CRM Lead Status", status.name, "type", type)
|
||||
Reference in New Issue
Block a user