Files
upstream-frappe-crm/crm/domain_enrichment/cross_record.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

113 lines
4.2 KiB
Python

# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
"""Link-time Organization -> Lead/Deal enrichment copy.
The ONLY cross-record behaviour in Domain Enrichment: when a Lead or Deal links an
already-enriched ``CRM Organization``, synchronously copy that Organization's stored
enriched fields onto the *empty* fields of the Lead/Deal (fill-empty). The direction
is strictly Organization -> Lead/Deal, in-request, on the origin save. There is no
background job, no fan-out, and no write back to the Organization.
The actual field writing reuses :func:`crm.domain_enrichment.mapper.apply_to_document`
-- the single result->field authority -- by wrapping the Organization's stored values
in an :class:`~crm.domain_enrichment.result.EnrichmentResult`. No crawling happens
here; this is a pure record-to-record copy.
"""
from __future__ import annotations
import frappe
from crm.domain_enrichment import mapper
from crm.domain_enrichment.config import get_config
from crm.domain_enrichment.result import (
EnrichmentResult,
Field,
SocialProfile,
)
# Native enriched fields stored on a CRM Organization. If at least one of these is
# populated (or the org has a completed enrichment run) we treat the org as enriched
# and worth copying from. Empty source values never overwrite anything (mapper guards).
_ORG_ENRICHED_FIELDS = (
"company_description",
"organization_logo",
"industry",
"linkedin",
"twitter",
"facebook",
)
def _is_org_enriched(org) -> bool:
"""Has this Organization actually been enriched?
Cleanest signal: a completed ``CRM Enrichment Run`` for the org. We also accept
any populated native enriched field as a fallback (an org may carry enriched data
without a run row, e.g. seeded/imported data).
"""
if frappe.db.exists(
"CRM Enrichment Run",
{
"reference_doctype": "CRM Organization",
"reference_name": org.name,
"status": "Completed",
},
):
return True
return any(org.get(fieldname) for fieldname in _ORG_ENRICHED_FIELDS)
def _result_from_organization(org) -> EnrichmentResult:
"""Wrap an Organization's stored enriched fields in an EnrichmentResult so the
shared mapper can copy them onto the target via the same Field Mapping records."""
result = EnrichmentResult(website=org.get("website") or "")
result.company_name = Field(value=org.get("organization_name") or "")
result.description = Field(value=org.get("company_description") or "")
result.logo = Field(value=org.get("organization_logo") or "")
result.industry = Field(value=org.get("industry") or "")
for network in ("linkedin", "twitter", "facebook"):
value = org.get(network)
if value:
result.social_profiles[network] = SocialProfile(value=value)
return result
def copy_enrichment_from_organization(target_doc) -> list[str]:
"""Copy enriched fields from the linked CRM Organization onto ``target_doc``.
``target_doc`` is a CRM Lead or CRM Deal that has just linked an Organization
(Lead: ``organization`` is free-text resolved to an org name; Deal: ``organization``
is a Link to CRM Organization). If that Organization is actually enriched, its
stored enriched fields are copied onto the *empty* fields of ``target_doc`` using
the shared mapper with fill-empty semantics, so user-entered data is never touched.
Best-effort: never raises, never blocks the Lead/Deal save. Returns the list of
filled field labels (empty when nothing was copied).
"""
try:
org_name = target_doc.get("organization")
if not org_name or not frappe.db.exists("CRM Organization", org_name):
return []
org = frappe.get_cached_doc("CRM Organization", org_name)
if not _is_org_enriched(org):
return []
cfg = get_config()
result = _result_from_organization(org)
# Fill-empty only: force fill-empty semantics for every mapping (even ones
# configured "Always refresh") so the link-time copy never
# overwrites or clears user-entered data. apply_to_document mutates target_doc
# in place and does NOT save (the caller's save persists it).
return mapper.apply_to_document(target_doc, result, cfg, fill_empty_only=True)
except Exception:
frappe.log_error(
title="Domain Enrichment: Org -> Lead/Deal copy failed",
message=f"{target_doc.doctype} {target_doc.name}: {frappe.get_traceback()}",
)
return []