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

View File

@@ -0,0 +1,86 @@
// Domain Enrichment — desk form button + live progress.
//
// Adds an "Enrich from Website" button to CRM Lead / CRM Organization / CRM Deal.
// Clicking it queues a background job; the progress steps stream back over realtime
// and are shown to the user. When done, the form reloads to show the enriched fields.
frappe.provide("crm.domain_enrichment");
crm.domain_enrichment.setup = function (frm) {
if (frm.is_new()) return;
frm.add_custom_button(__("Enrich from Website"), () => {
const website = (frm.doc.website || "").trim();
if (!website) {
frappe.msgprint({
title: __("No Website"),
message: __("Set a Website on this record before enriching."),
indicator: "orange",
});
return;
}
crm.domain_enrichment.run(frm, website);
});
};
crm.domain_enrichment.run = function (frm, website) {
// A determinate progress dialog driven by realtime events.
const total = 7; // pipeline.PROGRESS_STEPS; each event also carries data.total
frappe.show_progress(__("Domain Enrichment"), 0, total, __("Queued…"));
const event = "domain_enrichment_progress";
const handler = (data) => {
if (
!data ||
data.reference_doctype !== frm.doctype ||
data.reference_name !== frm.docname
) {
return; // not for this document
}
frappe.show_progress(
__("Domain Enrichment"),
data.step,
data.total,
data.message
);
if (data.status === "completed") {
frappe.realtime.off(event, handler);
frappe.hide_progress();
frappe.show_alert(
{ message: __("Enrichment complete"), indicator: "green" },
5
);
frm.reload_doc();
} else if (data.status === "error") {
frappe.realtime.off(event, handler);
frappe.hide_progress();
frappe.msgprint({
title: __("Enrichment Failed"),
message: data.message || __("See Error Log."),
indicator: "red",
});
}
};
frappe.realtime.on(event, handler);
frappe.call({
method: "crm.domain_enrichment.api.enrich",
args: {
reference_doctype: frm.doctype,
reference_name: frm.docname,
},
error: () => {
frappe.realtime.off(event, handler);
frappe.hide_progress();
},
});
};
["CRM Lead", "CRM Organization", "CRM Deal"].forEach((doctype) => {
frappe.ui.form.on(doctype, {
refresh: crm.domain_enrichment.setup,
});
});

View File

@@ -0,0 +1,40 @@
frappe.ui.form.on('Quotation', {
refresh(frm) {
const field = frm.get_field('crm_deal')
if (!field) return
// Render as a link only works for read only display an editable data
field.df.read_only = 1
field.df.formatter = (value) =>
value
? `<a href="${window.location.origin}/crm/deals/${encodeURIComponent(value)}" target="_blank" rel="noopener">${frappe.utils.escape_html(value)}</a>`
: value
field.refresh()
},
onload(frm) {
const crm_deal = frm.doc.crm_deal || frappe.route_options?.crm_deal
if (!crm_deal) return
if ((frm.doc.items || []).some((i) => i.item_code)) return
frappe.call({
method: 'crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.prefill_quotation_items',
args: { crm_deal },
callback: async (r) => {
if (!r.message || !r.message.length) return
if (!frm.doc.price_list_currency && frm.doc.currency) {
await frm.set_value('price_list_currency', frm.doc.currency)
}
frm.clear_table('items')
for (const item of r.message) {
const row = frm.add_child('items')
await frappe.model.set_value(row.doctype, row.name, 'item_code', item.item_code)
await frappe.model.set_value(row.doctype, row.name, 'qty', item.qty)
// price_list_rate must be set before discount_percentage: ERPNext
// resets the discount to 0 when price_list_rate is falsy.
await frappe.model.set_value(row.doctype, row.name, 'price_list_rate', item.price_list_rate)
await frappe.model.set_value(row.doctype, row.name, 'discount_percentage', item.discount_percentage)
}
frm.refresh_field('items')
},
})
},
})

View File

@@ -0,0 +1,17 @@
frappe.ui.form.on('Sales Order', {
refresh(frm) {
// When a Sales Order is created from a CRM Deal quotation that has no
// customer yet, create/fetch the customer and fill the mandatory field.
if (frm.doc.docstatus !== 0 || frm.doc.customer || frm.__crm_customer_checked) return
const item = (frm.doc.items || []).find((i) => i.prevdoc_docname)
if (!item) return
frm.__crm_customer_checked = true
frappe.call({
method: 'crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.check_customer_for_quotation',
args: { quotation: item.prevdoc_docname },
callback: (r) => {
if (r.message) frm.set_value('customer', r.message)
},
})
},
})