From 3003a477dc3223306c1ae0689db387c735600777 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 19 Dec 2024 22:28:33 +0100 Subject: [PATCH 1/3] feat: Improvements to Pappers search --- .../js/utils/contact_address_quick_entry.js | 6 ++-- .../regional/france/extensions/supplier.py | 28 ++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/utils/contact_address_quick_entry.js b/erpnext/public/js/utils/contact_address_quick_entry.js index 6b5254c84c3..54dd4155020 100644 --- a/erpnext/public/js/utils/contact_address_quick_entry.js +++ b/erpnext/public/js/utils/contact_address_quick_entry.js @@ -35,8 +35,10 @@ frappe.ui.form.ContactAddressQuickEntryForm = class ContactAddressQuickEntryForm const selected_company = selection[0] me.dialog.set_values({ customer_name: selected_company.label, - siren_number: selected_company.value - }) + supplier_name: selected_company.label, + tax_id: selected_company.value, + }); + me.dialog.refresh(); } } } diff --git a/erpnext/regional/france/extensions/supplier.py b/erpnext/regional/france/extensions/supplier.py index fc07a925cdb..4c5c3e0531d 100644 --- a/erpnext/regional/france/extensions/supplier.py +++ b/erpnext/regional/france/extensions/supplier.py @@ -23,13 +23,13 @@ def get_info_from_pappers(doc): if not global_defauts.pappers_api_key: return - if not doc.siren_number: + if not doc.siren_number or (doc.tax_id and doc.tax_id.startswith("FR")): return if date_diff(nowdate(), doc.last_pappers_update) > cint(global_defauts.pappers_update_interval): return - data = PappersEntreprise().get({"siren": doc.siren_number}) + data = PappersEntreprise().get({"siren": doc.siren_number or doc.tax_id[4:]}) if data and not data.get("statusCode"): update_tax_id(doc, data.get("numero_tva_intracommunautaire")) @@ -49,7 +49,7 @@ def update_tax_id(doc, vat_number): elif doc.tax_id != vat_number: frappe.msgprint( _( - "The VAT number registered in this document doesn't match the VAT number available publicly for this company: {}".format( + "The VAT number registered in this document doesn't match the VAT number available publicly for this company: {}".format( # noqa: UP032 vat_number ) ) @@ -59,6 +59,13 @@ def update_tax_id(doc, vat_number): @frappe.whitelist() def company_query(txt): if txt: + if txt.startswith("FR") and len(txt) == 13 and txt[4:].isdigit(): + if res := get_company_by_siren(txt[4:]): + return res + if len(txt.replace(" ", "")) == 9 and txt.replace(" ", "").isdigit(): + if res := get_company_by_siren(txt.replace(" ", "")): + return res + res = PappersRecherche().get( {"q": txt, "cibles": "nom_entreprise,siren,siret", "longueur": 100, "api_token": None} ) @@ -71,10 +78,23 @@ def company_query(txt): return list( { "label": r.get("nom_entreprise"), - "value": r.get("siren"), + "value": r.get("numero_tva_intracommunautaire"), "description": f"SIREN: {r.get('siren_formate')}
{r.get('siege', {}).get('adresse_ligne_1', '')} {r.get('siege', {}).get('code_postal', '')} {r.get('siege', {}).get('ville', '')}", } for r in res.get("resultats_nom_entreprise", []) ) return [] + +def get_company_by_siren(search): + res = PappersEntreprise().get({"siren": search}) + if not res.get("statusCode"): + return list([ + { + "label": res.get("nom_entreprise"), + "value": res.get("numero_tva_intracommunautaire"), + "description": f"SIREN: {res.get('siren_formate')}
{res.get('siege', {}).get('adresse_ligne_1', '')} {res.get('siege', {}).get('code_postal', '')} {res.get('siege', {}).get('ville', '')}", + } + ]) + + return [] -- GitLab From 63d1c6ab865adde3c0cfa7e9108f40d3a215e65c Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 20 Dec 2024 17:32:19 +0100 Subject: [PATCH 2/3] fix: cleanup code --- erpnext/regional/france/extensions/supplier.py | 4 ++-- erpnext/regional/france/pappers/entreprise.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/france/extensions/supplier.py b/erpnext/regional/france/extensions/supplier.py index 4c5c3e0531d..61f43432681 100644 --- a/erpnext/regional/france/extensions/supplier.py +++ b/erpnext/regional/france/extensions/supplier.py @@ -86,8 +86,8 @@ def company_query(txt): return [] -def get_company_by_siren(search): - res = PappersEntreprise().get({"siren": search}) +def get_company_by_siren(siren): + res = PappersEntreprise().get({"siren": siren}) if not res.get("statusCode"): return list([ { diff --git a/erpnext/regional/france/pappers/entreprise.py b/erpnext/regional/france/pappers/entreprise.py index 8860e3712f7..ed262fa6430 100644 --- a/erpnext/regional/france/pappers/entreprise.py +++ b/erpnext/regional/france/pappers/entreprise.py @@ -3,5 +3,5 @@ from erpnext.regional.france.pappers.api import PappersAPI class PappersEntreprise(PappersAPI): def __init__(self): - super(PappersEntreprise, self).__init__() + super().__init__() self.url = f"{self.base_url.rstrip('/')}/entreprise" -- GitLab From 83f9f19b392f1bd054122e899f99deb283eea190 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 20 Dec 2024 22:02:27 +0100 Subject: [PATCH 3/3] feat: add default billing address --- .../doctype/supplier/regional/france.js | 13 +++--- erpnext/patches.txt | 1 + .../dokos/v4_0/check_enable_pappers.py | 7 +++ .../regional/france/extensions/customer.py | 6 +-- .../regional/france/extensions/supplier.py | 46 +++++++++++++++---- erpnext/regional/france/pappers/api.py | 4 +- .../doctype/customer/regional/france.js | 9 ++-- .../global_defaults/global_defaults.json | 18 +++++++- .../global_defaults/global_defaults.py | 1 + 9 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 erpnext/patches/dokos/v4_0/check_enable_pappers.py diff --git a/erpnext/buying/doctype/supplier/regional/france.js b/erpnext/buying/doctype/supplier/regional/france.js index 2ffa764d0b2..72aaf6f6a4d 100644 --- a/erpnext/buying/doctype/supplier/regional/france.js +++ b/erpnext/buying/doctype/supplier/regional/france.js @@ -1,6 +1,6 @@ frappe.ui.form.on("Supplier", { - setup: function (frm) { - frm.set_query("company_search", function(doc) { + setup(frm) { + frm.set_query("company_search", function (doc) { return { query: "erpnext.regional.france.extensions.supplier.company_query" }; @@ -11,10 +11,11 @@ frappe.ui.form.on("Supplier", { if (frm.get_field("company_search")._data.length) { const selection = frm.get_field("company_search")._data.filter(f => f.value == frm.doc.company_search) - if (selection.length){ - const selected_company = selection[0] - frm.set_value("supplier_name", selected_company.label) - frm.set_value("siren_number", selected_company.value) + if (selection.length) { + const selected_company = selection[0]; + frm.set_value("supplier_name", selected_company.label); + frm.set_value("siren_number", selected_company.value.substring(4)); + frm.set_value("tax_id", selected_company.value); } } } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2a93c3eb9ce..a4867df4366 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -387,6 +387,7 @@ erpnext.accounts.doctype.mode_of_payment.patches.migrate_fees_and_cost_center_to execute:frappe.delete_doc_if_exists("DocType", "Integration References") execute:frappe.delete_doc_if_exists("DocType", "Social Media Post") erpnext.patches.dokos.v4_0.update_advance_for_down_payments #2024-05-207 +erpnext.patches.dokos.v4_0.check_enable_pappers # @dokos diff --git a/erpnext/patches/dokos/v4_0/check_enable_pappers.py b/erpnext/patches/dokos/v4_0/check_enable_pappers.py new file mode 100644 index 00000000000..e8cb4fa60de --- /dev/null +++ b/erpnext/patches/dokos/v4_0/check_enable_pappers.py @@ -0,0 +1,7 @@ +import frappe + + +def execute(): + global_defauts = frappe.get_single("Global Defaults") + if global_defauts.pappers_api_key: + frappe.db.set_single_value("Global Defaults", "enable_pappers", 1) diff --git a/erpnext/regional/france/extensions/customer.py b/erpnext/regional/france/extensions/customer.py index 56fe6b5677d..336d39c1e87 100644 --- a/erpnext/regional/france/extensions/customer.py +++ b/erpnext/regional/france/extensions/customer.py @@ -1,9 +1,7 @@ from erpnext.regional.france.extensions.supplier import ( - get_info_from_pappers, - get_siren_from_tax_id, + get_pappers_data, ) def validate(doc, method): - get_siren_from_tax_id(doc) - get_info_from_pappers(doc) + get_pappers_data(doc) diff --git a/erpnext/regional/france/extensions/supplier.py b/erpnext/regional/france/extensions/supplier.py index 61f43432681..b2cb46441ae 100644 --- a/erpnext/regional/france/extensions/supplier.py +++ b/erpnext/regional/france/extensions/supplier.py @@ -1,5 +1,6 @@ import frappe from frappe import _ +from frappe.contacts.doctype.address.address import get_default_address from frappe.utils import cint, date_diff, nowdate from erpnext.regional.france.pappers.entreprise import PappersEntreprise @@ -7,29 +8,32 @@ from erpnext.regional.france.pappers.recherche import PappersRecherche def validate(doc, method): + get_pappers_data(doc) + +def get_pappers_data(doc): meta = frappe.get_meta(doc.doctype) if meta.has_field("siren_number"): - get_siren_from_tax_id(doc) + if doc.tax_id and not doc.get("siren_number"): + get_siren_from_tax_id(doc.tax_id) get_info_from_pappers(doc) -def get_siren_from_tax_id(doc): - if doc.tax_id and not doc.get("siren_number"): - doc.siren_number = doc.tax_id[4:] +def get_siren_from_tax_id(tax_id): + return tax_id[4:] def get_info_from_pappers(doc): global_defauts = frappe.get_single("Global Defaults") - if not global_defauts.pappers_api_key: + if not global_defauts.enable_pappers or not global_defauts.pappers_api_key: return - if not doc.siren_number or (doc.tax_id and doc.tax_id.startswith("FR")): + if not doc.siren_number or not (doc.tax_id and doc.tax_id.startswith("FR")): return if date_diff(nowdate(), doc.last_pappers_update) > cint(global_defauts.pappers_update_interval): return - data = PappersEntreprise().get({"siren": doc.siren_number or doc.tax_id[4:]}) + data = PappersEntreprise().get({"siren": doc.siren_number or get_siren_from_tax_id(doc.tax_id)}) if data and not data.get("statusCode"): update_tax_id(doc, data.get("numero_tva_intracommunautaire")) @@ -39,6 +43,8 @@ def get_info_from_pappers(doc): if meta.has_field(key): doc.set(key, value) + update_billing_address(doc, data) + doc.last_pappers_update = nowdate() @@ -56,11 +62,30 @@ def update_tax_id(doc, vat_number): ) +def update_billing_address(doc, pappers_data): + data = frappe._dict(pappers_data.get("siege", {})) + if not get_default_address(doc.doctype, doc.name, sort_key="is_primary_address"): + if data.get("ville") and frappe.db.exists("Country", data.get("pays")): + address = frappe.new_doc("Address") + address.update( + { + "address_title": pappers_data.get("denomination"), + "address_type": "Billing", + "address_line1": data.get("adresse_ligne_1"), + "address_line2": data.get("adresse_ligne_2"), + "city": data.get("ville"), + "country": data.get("pays"), + } + ) + address.append("links", {"link_doctype": doc.doctype, "link_name": doc.name}) + address.insert() + + @frappe.whitelist() def company_query(txt): if txt: if txt.startswith("FR") and len(txt) == 13 and txt[4:].isdigit(): - if res := get_company_by_siren(txt[4:]): + if res := get_company_by_siren(get_siren_from_tax_id(txt)): return res if len(txt.replace(" ", "")) == 9 and txt.replace(" ", "").isdigit(): if res := get_company_by_siren(txt.replace(" ", "")): @@ -78,7 +103,7 @@ def company_query(txt): return list( { "label": r.get("nom_entreprise"), - "value": r.get("numero_tva_intracommunautaire"), + "value": r.get("numero_tva_intracommunautaire") or calculated_vat_number(r.get('siren')), "description": f"SIREN: {r.get('siren_formate')}
{r.get('siege', {}).get('adresse_ligne_1', '')} {r.get('siege', {}).get('code_postal', '')} {r.get('siege', {}).get('ville', '')}", } for r in res.get("resultats_nom_entreprise", []) @@ -98,3 +123,6 @@ def get_company_by_siren(siren): ]) return [] + +def calculated_vat_number(siren): + return f"FR{(12+3*int(siren)%97)%97}{siren}" diff --git a/erpnext/regional/france/pappers/api.py b/erpnext/regional/france/pappers/api.py index 51486af13c7..592d187de05 100644 --- a/erpnext/regional/france/pappers/api.py +++ b/erpnext/regional/france/pappers/api.py @@ -27,6 +27,8 @@ def setup_pappers(doc, method): def setup_custom_fields(): + # _("The search tool uses Pappers API") + pappers_fields = [ dict( fieldname="public_information_tab", @@ -249,7 +251,7 @@ def setup_custom_fields(): label="Company Search", fieldtype="Autocomplete", insert_after="naming_series", - description="The search tool uses Pappers API.
You are limited to 100 searches per day.", + description="The search tool uses Pappers API", allow_in_quick_entry=True, ), ] diff --git a/erpnext/selling/doctype/customer/regional/france.js b/erpnext/selling/doctype/customer/regional/france.js index b1f9686c43b..7e56e6dea86 100644 --- a/erpnext/selling/doctype/customer/regional/france.js +++ b/erpnext/selling/doctype/customer/regional/france.js @@ -11,10 +11,11 @@ frappe.ui.form.on("Customer", { if (frm.get_field("company_search")._data.length) { const selection = frm.get_field("company_search")._data.filter(f => f.value == frm.doc.company_search) - if (selection.length){ - const selected_company = selection[0] - frm.set_value("customer_name", selected_company.label) - frm.set_value("siren_number", selected_company.value) + if (selection.length) { + const selected_company = selection[0]; + frm.set_value("customer_name", selected_company.label); + frm.set_value("siren_number", selected_company.value.substring(4)); + frm.set_value("tax_id", selected_company.value); } } } diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.json b/erpnext/setup/doctype/global_defaults/global_defaults.json index 640f6669748..7bd158b2cda 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.json +++ b/erpnext/setup/doctype/global_defaults/global_defaults.json @@ -15,6 +15,8 @@ "disable_in_words", "pappers_tab", "pappers_help", + "enable_pappers", + "section_break_cdgg", "pappers_api_key", "pappers_update_interval" ], @@ -88,15 +90,27 @@ "label": "Pappers Help" }, { + "depends_on": "eval:doc.enable_pappers", "fieldname": "pappers_api_key", "fieldtype": "Password", "label": "API Key" }, { "default": "90", + "depends_on": "eval:doc.enable_pappers", "fieldname": "pappers_update_interval", "fieldtype": "Int", "label": "Update data every (Days)" + }, + { + "default": "0", + "fieldname": "enable_pappers", + "fieldtype": "Check", + "label": "Enable Pappers" + }, + { + "fieldname": "section_break_cdgg", + "fieldtype": "Section Break" } ], "icon": "fa fa-cog", @@ -104,7 +118,7 @@ "in_create": 1, "issingle": 1, "links": [], - "modified": "2023-09-02 16:42:42.943877", + "modified": "2024-12-20 21:19:40.872287", "modified_by": "Administrator", "module": "Setup", "name": "Global Defaults", @@ -122,4 +136,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} +} \ No newline at end of file diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index 2e7fdd759e5..8c26cf1f859 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -37,6 +37,7 @@ class GlobalDefaults(Document): default_distance_unit: DF.Link | None disable_in_words: DF.Check disable_rounded_total: DF.Check + enable_pappers: DF.Check hide_currency_symbol: DF.Literal["", "No", "Yes"] pappers_api_key: DF.Password | None pappers_update_interval: DF.Int -- GitLab