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

This commit is contained in:
upstream-mirror
2026-09-18 01:36:01 +05:00
commit db69de1c11
905 changed files with 102713 additions and 0 deletions

348
crm/demo/deals.py Normal file
View File

@@ -0,0 +1,348 @@
from datetime import datetime, timedelta
import frappe
from frappe.query_builder import DocType
from crm.demo.utils import (
backdate,
build_full_names,
fix_auto_records,
insert_comment,
insert_communication,
insert_version,
resolve_owners,
)
def create_demo_deals(lead_names, demo_users):
"""Convert seven leads into deals and add deal-specific activity."""
from crm.fcrm.doctype.crm_lead.crm_lead import convert_to_deal
session_user, owner_1, owner_2, _ = resolve_owners(demo_users)
_full_names = build_full_names(session_user)
# leads[0] Alice, [3] David, [7] Henry, [8] Iris, [9] Jack → 5 active/won deals
# leads[10] Karen, [11] Leo → 2 lost deals
d_alice = convert_to_deal(
lead=lead_names[0],
deal={"status": "Demo/Making", "deal_value": 120000, "probability": 50, "deal_owner": session_user},
)
d_david = convert_to_deal(
lead=lead_names[3],
deal={"status": "Proposal/Quotation", "deal_value": 45000, "probability": 70, "deal_owner": owner_1},
)
d_henry = convert_to_deal(
lead=lead_names[7],
deal={"status": "Negotiation", "deal_value": 85000, "probability": 60, "deal_owner": owner_2},
)
d_iris = convert_to_deal(
lead=lead_names[8],
deal={"status": "Qualification", "deal_value": 60000, "probability": 35, "deal_owner": session_user},
)
d_jack = convert_to_deal(
lead=lead_names[9],
deal={"status": "Won", "deal_value": 175000, "probability": 100, "deal_owner": owner_1},
)
d_karen = convert_to_deal(
lead=lead_names[10],
deal={
"status": "Lost",
"deal_value": 95000,
"probability": 0,
"deal_owner": owner_2,
"lost_reason": "Competition",
"lost_notes": "Prospect chose a competitor offering deeper BI integrations out of the box. Price was not the issue — feature parity was.",
},
)
d_leo = convert_to_deal(
lead=lead_names[11],
deal={
"status": "Lost",
"deal_value": 55000,
"probability": 0,
"deal_owner": session_user,
"lost_reason": "Budget constraints",
"lost_notes": "Q2 budget was cut. Leo said they would revisit in Q4 once headcount hiring is complete. Added to nurture sequence.",
},
)
deal_names_list = [d_alice, d_david, d_henry, d_iris, d_jack, d_karen, d_leo]
# Converted lead indices — must match deal_names_list order
_converted_lead_indices = [0, 3, 7, 8, 9, 10, 11]
# Days ago each deal was created (always after its lead)
_deal_days = [50, 37, 18, 11, 24, 9, 5]
_deal_owners = [session_user, owner_1, owner_2, session_user, owner_1, owner_2, session_user]
# Organization logos keyed by org name (index order: alice, david, henry, iris, jack, karen, leo)
_org_logos = {
"Acme Corp": "/assets/crm/images/demo/acme-corp.png",
"TechStart Inc": "/assets/crm/images/demo/techstart-inc.png",
"PivotTech Solutions": "/assets/crm/images/demo/pivottech-solutions.png",
"ScaleUp Labs": "/assets/crm/images/demo/scaleup-labs.png",
"Meridian Systems": "/assets/crm/images/demo/meridian-systems.png",
"Vertex Analytics": "/assets/crm/images/demo/vertex-analytics.png",
"Forge Digital": "/assets/crm/images/demo/forge-digital.png",
}
now = datetime.now()
for d_name, days, d_owner, li in zip(
deal_names_list, _deal_days, _deal_owners, _converted_lead_indices, strict=False
):
ts = now - timedelta(days=days)
backdate("CRM Deal", d_name, d_owner, ts)
org = frappe.db.get_value("CRM Deal", d_name, "organization")
if org:
logo = _org_logos.get(org)
if logo:
frappe.db.set_value("CRM Organization", org, "organization_logo", logo, update_modified=False)
backdate("CRM Organization", org, d_owner, ts)
contacts = frappe.get_all(
"CRM Contacts", filters={"parent": d_name, "parenttype": "CRM Deal"}, pluck="contact"
)
for contact in contacts:
if contact:
backdate("Contact", contact, d_owner, ts)
backdate("CRM Lead", lead_names[li], d_owner, ts, set_creation=False)
fix_auto_records("CRM Deal", d_name, d_owner, ts)
fix_auto_records("CRM Lead", lead_names[li], d_owner, ts)
comment_names = _create_deal_comments(deal_names_list, session_user, owner_1, owner_2, _full_names, now)
communication_names = _create_deal_communications(
deal_names_list, session_user, owner_1, _full_names, now
)
_create_deal_versions(deal_names_list, session_user, owner_1, owner_2, now)
# Re-backdate modified to last-activity date so active deals sort to the top of the list.
# Index order: alice, david, henry, iris, jack, karen, leo
_deal_last_touched_days = [2, 4, 7, 10, 6, 8, 12]
for d_name, days, d_owner in zip(deal_names_list, _deal_last_touched_days, _deal_owners, strict=False):
backdate("CRM Deal", d_name, d_owner, now - timedelta(days=days), set_creation=False)
return {
"deals": deal_names_list,
"comments": comment_names,
"communications": communication_names,
}
def _create_deal_comments(deal_names, session_user, owner_1, owner_2, full_names, now):
comments_data = [
{
"deal": deal_names[0], # Alice / Acme Corp
"owner": session_user,
"content": (
"<p>Live demo went well — Alice's product team joined and had great questions "
"about workflow automation and bulk import. They want a custom sandbox environment "
"to evaluate with their own data before signing. Following up to arrange access.</p>"
),
"days_ago": 3,
},
{
"deal": deal_names[1], # David / TechStart Inc
"owner": owner_1,
"content": (
"<p>David reviewed the proposal with his co-founder. They're happy with the pricing "
"but want a 3-month pilot before committing to annual. Checking with management on "
"whether we can offer a pilot discount.</p>"
),
"days_ago": 5,
},
{
"deal": deal_names[2], # Henry / PivotTech Solutions
"owner": owner_2,
"content": (
"<p>Negotiation call with Henry went long but productive. He’s pushing for a 15% "
"discount on the annual plan citing budget constraints. Legal team is now reviewing "
"the contract — decision expected by end of week.</p>"
),
"days_ago": 7,
},
{
"deal": deal_names[3], # Iris / ScaleUp Labs
"owner": session_user,
"content": (
"<p>Qualification call completed with Iris and her co-founder. Small but fast-moving "
"team of 8 engineers. Main concerns are API depth and self-serve onboarding. "
"Sending over the developer docs and scheduling a technical deep-dive.</p>"
),
"days_ago": 10,
},
{
"deal": deal_names[4], # Jack / Meridian Systems
"owner": owner_1,
"content": (
"<p>Deal closed — Jack signed the contract this morning. Final value $175k annual. "
"Onboarding is scheduled for next Monday. Handoff notes sent to the customer "
"success team. Great result for the quarter!</p>"
),
"days_ago": 2,
},
{
"deal": deal_names[5], # Karen / Vertex Analytics
"owner": owner_2,
"content": (
"<p>Karen's team went with a competitor — they had deeper BI integrations that we "
"currently don't support. Not a pricing issue. Flagged to product team for roadmap "
"consideration. Karen asked us to follow up in 6 months.</p>"
),
"days_ago": 8,
},
{
"deal": deal_names[6], # Leo / Forge Digital
"owner": session_user,
"content": (
"<p>Budget was cut for the rest of the year — Leo was very apologetic and said the "
"product was exactly what they needed. Re-added to nurture sequence for Q4. "
"Good candidate for a comeback deal.</p>"
),
"days_ago": 12,
},
]
return [
insert_comment(
"CRM Deal",
data["deal"],
data["owner"],
data["content"],
full_names,
now - timedelta(days=data["days_ago"]),
)
for data in comments_data
]
def _create_deal_communications(deal_names, session_user, owner_1, full_names, now):
# Use a placeholder email for recipients where we don't have a real one handy
comms_data = [
{
"deal": deal_names[0],
"owner": session_user,
"sent_or_received": "Sent",
"subject": "Sandbox access details — Acme Corp CRM Evaluation",
"content": (
"<p>Hi Alice,</p>"
"<p>Thanks for joining yesterday's demo — really great session. I've created a "
"dedicated sandbox environment pre-loaded with sample data. Login details and "
"setup instructions are attached.</p>"
"<p>Our onboarding specialist Sarah will also be available for a walkthrough "
"session this week — just let us know a convenient time.</p>"
"<p>Best regards</p>"
),
"sender": session_user,
"recipients": "alice.johnson@example.com",
"days_ago": 2,
},
{
"deal": deal_names[1],
"owner": owner_1,
"sent_or_received": "Received",
"subject": "Re: CRM Proposal — Pilot terms query",
"content": (
"<p>Hi,</p>"
"<p>We've reviewed the proposal and the pricing looks good. Before we proceed, "
"could you clarify what's included in the onboarding package? Specifically, we "
"need help migrating ~4,000 contacts from our existing system.</p>"
"<p>Also, is there flexibility on the contract start date?</p>"
"<p>Thanks,<br>David Lee<br>CTO, TechStart Inc</p>"
),
"sender": "david.lee@example.com",
"recipients": owner_1,
"days_ago": 4,
},
]
return [
insert_communication(
"CRM Deal", data["deal"], data, full_names, now - timedelta(days=data["days_ago"])
)
for data in comms_data
]
def _create_deal_versions(deal_names, session_user, owner_1, owner_2, now):
versions_data = [
{
"deal": deal_names[0], # Alice
"owner": session_user,
"changed": [["probability", "30", "50"]],
"days_ago": 4,
},
{
"deal": deal_names[1], # David
"owner": owner_1,
"changed": [["deal_value", "40000", "45000"]],
"days_ago": 6,
},
{
"deal": deal_names[2], # Henry
"owner": owner_2,
"changed": [["probability", "50", "60"]],
"days_ago": 8,
},
{
"deal": deal_names[3], # Iris
"owner": session_user,
"changed": [["probability", "25", "35"]],
"days_ago": 11,
},
{
"deal": deal_names[4], # Jack
"owner": owner_1,
"changed": [["status", "Ready to Close", "Won"]],
"days_ago": 3,
},
{
"deal": deal_names[5], # Karen
"owner": owner_2,
"changed": [["status", "Proposal/Quotation", "Lost"]],
"days_ago": 9,
},
{
"deal": deal_names[6], # Leo
"owner": session_user,
"changed": [["status", "Demo/Making", "Lost"]],
"days_ago": 13,
},
]
for data in versions_data:
insert_version(
"CRM Deal", data["deal"], data["owner"], data["changed"], now - timedelta(days=data["days_ago"])
)
def delete_demo_deals(deal_data, lead_names):
"""
Delete deals, their linked contacts, organizations, and deal-specific communications.
Comments and Versions are cascade-deleted when the deal is deleted.
"""
communication_names = deal_data.get("communications", [])
deal_names = set(deal_data.get("deals", []))
# Find all deals converted from the provided demo leads
if lead_names:
demo_lead_deals = frappe.get_all("CRM Deal", filters={"lead": ["in", lead_names]}, pluck="name")
deal_names.update(demo_lead_deals)
for name in communication_names:
if frappe.db.exists("Communication", name):
frappe.delete_doc("Communication", name, ignore_permissions=True, force=True)
for name in deal_names:
if not frappe.db.exists("CRM Deal", name):
continue
# Collect linked contacts and organization before deleting deal
contact_names = frappe.get_all(
"CRM Contacts", filters={"parent": name, "parenttype": "CRM Deal"}, pluck="contact"
)
org = frappe.db.get_value("CRM Deal", name, "organization")
frappe.delete_doc("CRM Deal", name, ignore_permissions=True, force=True)
for contact in contact_names:
if contact and frappe.db.exists("Contact", contact):
for child_doctype in ("Contact Email", "Contact Phone", "Dynamic Link"):
Child = DocType(child_doctype)
frappe.qb.from_(Child).delete().where(Child.parent == contact).run()
Contact = DocType("Contact")
frappe.qb.from_(Contact).delete().where(Contact.name == contact).run()
if org and frappe.db.exists("CRM Organization", org):
frappe.delete_doc("CRM Organization", org, ignore_permissions=True, force=True)