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
113 lines
4.2 KiB
Python
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 []
|