# erpnext_custom Custom App für NEXTErp mit zuverlässigem Sync von Custom Fields zu WooCommerce (ACF / Meta Data). ## Funktionen - Sync beliebiger Custom Fields aus ERPNext-Item nach WooCommerce - Verwendung von `meta_data` (stabiler als ACF-Block) - Manueller Sync-Button im Item-Formular ## Voraussetzungen - ERPNext Version 16 (getestet mit 16.17.x) - WooCommerce + Advanced Custom Fields (ACF) Plugin - App **woocommerce_fusion** installiert und konfiguriert - Mindestens ein **WooCommerce Server** mit gültigen API-Keys (`api_consumer_key` + `api_consumer_secret`) - Im Item muss eine **WooCommerce ID** hinterlegt sein (Produkt muss in WooCommerce bereits existieren) - DocType **WooCommerce ACF Mapping** muss angelegt sein ## Installation ### 1. App erstellen ```bash cd ~/frappe-bench bench new-app erpnext_custom ``` Bei den Fragen: - App Title: NEXTErp Custom - App Description: WooCommerce ACF Custom Fields Sync - App Publisher: Jens Falk - App Email: service@falk.plus ```bash cd ~/frappe-bench/apps/erpnext_custom/erpnext_custom ``` ```bash cat > api.py << 'EOF' import frappe import requests from frappe import _ @frappe.whitelist() def sync_custom_fields_to_woocommerce(item_code): """Sync Custom Fields via meta_data nach WooCommerce""" frappe.msgprint("Sync gestartet fuer Artikel " + str(item_code), indicator="blue", alert=1) doc = frappe.get_doc("Item", item_code) if not doc.get("woocommerce_servers"): frappe.msgprint("Kein WooCommerce Server verknuepft", indicator="red", alert=1) return False # Mappings aus DocType laden mappings = frappe.get_all("WooCommerce ACF Mapping", filters={"enabled": 1}, fields=["erp_field", "acf_field"]) field_mapping = {m.erp_field: m.acf_field for m in mappings if m.erp_field and m.acf_field} if not field_mapping: frappe.msgprint("KEIN Mapping gefunden! Bitte im DocType 'WooCommerce ACF Mapping' Eintraege anlegen.", indicator="red", alert=1) return False for wc_link in doc.woocommerce_servers: if not wc_link.get("woocommerce_id"): continue try: wc_server = frappe.get_doc("WooCommerce Server", wc_link.woocommerce_server) api_key = wc_server.get("api_consumer_key") api_secret = wc_server.get("api_consumer_secret") if not api_key or not api_secret: continue base_url = wc_server.woocommerce_server_url.rstrip("/") if not base_url.endswith("/wp-json/wc/v3"): base_url += "/wp-json/wc/v3" url = base_url + "/products/" + str(wc_link.woocommerce_id) auth = (api_key, api_secret) # Produkt holen resp = requests.get(url, auth=auth, timeout=15) product = resp.json() meta_data = product.get("meta_data", []) for erp_field, acf_field in field_mapping.items(): value = doc.get(erp_field) or "" meta_data = [m for m in meta_data if m.get("key") != acf_field] meta_data.append({"key": acf_field, "value": value}) payload = {"meta_data": meta_data} update_resp = requests.put(url, json=payload, auth=auth, timeout=15) update_resp.raise_for_status() frappe.msgprint("Custom Fields erfolgreich gesendet!", indicator="green", alert=1) except Exception as e: frappe.msgprint("Fehler: " + str(e), indicator="red", alert=1) return True EOF ``` Danach ausführen ```bash bench restart ``` ### 2. DocType anlegen DocType Name: WooCommerce ACF Mapping **Felder:** - erp_field → Data → Pflicht - acf_field → Data → Pflicht - enabled → Check → Default = 1 - description → Small Text **Title Field:** erp_field ### Client Script mit Button Custom Script → New - DocType: Item - Script Type: Client Script ```js frappe.ui.form.on('Item', { refresh: function(frm) { frm.add_custom_button('Custom Fields zu WooCommerce syncen', function() { if (!frm.doc.woocommerce_servers || frm.doc.woocommerce_servers.length === 0) { frappe.msgprint("Kein WooCommerce Server verknüpft!", "Warnung"); return; } frappe.call({ method: 'erpnext_custom.api.sync_custom_fields_to_woocommerce', args: { item_code: frm.doc.name } }); }, "WooCommerce"); } }); ``` #### Client Script mit Button mehrer Artikel ```js frappe.ui.form.on('Item', { refresh: function(frm) { // Einzelner Button im Formular frm.add_custom_button('Custom Fields zu WooCommerce syncen', function() { if (!frm.doc.woocommerce_servers || frm.doc.woocommerce_servers.length === 0) { frappe.msgprint("Kein WooCommerce Server verknüpft!", "Warnung"); return; } frappe.call({ method: 'erpnext_custom.api.sync_custom_fields_to_woocommerce', args: { item_code: frm.doc.name } }); }, "WooCommerce"); } }); // ==================== MASSEN-SYNC MIT FORTSCHRITT ==================== frappe.listview_settings['Item'] = { onload: function(listview) { listview.page.add_inner_button('Alle Custom Fields zu WooCommerce syncen', function() { let selected = listview.get_checked_items(); if (selected.length === 0) { frappe.msgprint("Bitte wählen Sie mindestens einen Artikel aus.", "Warnung"); return; } frappe.confirm(`Möchten Sie wirklich ${selected.length} Artikel synchronisieren?`, function() { let completed = 0; let success = 0; let failed = 0; // Fortschrittsdialog let progress = new frappe.ui.Dialog({ title: 'Synchronisiere Custom Fields', fields: [ { fieldtype: 'HTML', fieldname: 'progress_html', options: `
0 von ${selected.length} verarbeitet