Files
upstream-frappe-crm/crm/demo/deals.py
upstream-mirror db69de1c11
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1m13s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 6s
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
snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:36:01 +05:00

349 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)