70208d3c76e9377869689c607bee147d72ae4b45
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
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
cd ~/frappe-bench/apps/erpnext_custom/erpnext_custom
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
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
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
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: `<div id="sync-progress" style="margin: 20px 0;">
<div class="progress">
<div class="progress-bar" id="progress-bar" style="width: 0%;">0%</div>
</div>
<p id="progress-text" style="text-align:center; margin-top:10px;">0 von ${selected.length} verarbeitet</p>
</div>`
}
],
primary_action_label: 'Schließen',
primary_action: function() {
progress.hide();
}
});
progress.show();
// Artikel nacheinander syncen
function processNext() {
if (completed >= selected.length) {
progress.hide();
let msg = `${success} erfolgreich, ${failed} fehlgeschlagen`;
if (failed === 0) {
frappe.msgprint(msg, "Erfolg");
} else {
frappe.msgprint(msg, "Warnung");
}
listview.refresh();
return;
}
let item = selected[completed];
frappe.call({
method: 'erpnext_custom.api.sync_custom_fields_to_woocommerce',
args: { item_code: item.name },
callback: function(r) {
completed++;
if (r.message) {
success++;
} else {
failed++;
}
// Fortschritt aktualisieren
let percent = Math.round((completed / selected.length) * 100);
document.getElementById('progress-bar').style.width = percent + '%';
document.getElementById('progress-bar').innerText = percent + '%';
document.getElementById('progress-text').innerText = completed + ' von ' + selected.length + ' verarbeitet';
processNext();
},
error: function() {
completed++;
failed++;
let percent = Math.round((completed / selected.length) * 100);
document.getElementById('progress-bar').style.width = percent + '%';
document.getElementById('progress-bar').innerText = percent + '%';
document.getElementById('progress-text').innerText = completed + ' von ' + selected.length + ' verarbeitet';
processNext();
}
});
}
processNext();
});
}, "WooCommerce");
}
};
Nutzung
- Im Item-Dokument unter WooCommerce ACF Mapping die gewünschten Felder eintragen.
- Im Artikel-Formular auf den Button "Custom Fields zu WooCommerce syncen" klicken.
Bekannte Probleme
- Der Standard-Sync der
woocommerce_fusion-App kann bei manchen Artikeln fehlschlagen (Fehler bei WooCommerce ID Parsing). Deshalb wird aktuell ein separater manueller Button verwendet. frappe.msgprintMeldungen erscheinen manchmal nicht sofort oder nicht in der Console (besser im Browser testen).- Bei neuen Artikeln muss zuerst ein Produkt in WooCommerce existieren und die WooCommerce ID im Item hinterlegt sein.
- ACF-Felder werden derzeit zuverlässig nur über
meta_dataübertragen (direkter ACF-Block war instabil). - Der Sync muss manuell über den Button ausgelöst werden (kein automatischer Sync beim Speichern, um Konflikte mit woocommerce_fusion zu vermeiden).
Zukünftige Erweiterungen
- Automatischer Sync beim Speichern des Items (ohne Konflikt mit woocommerce_fusion)
- Bidirektionaler Sync (WooCommerce → ERPNext)
- Unterstützung für direkten ACF-Block (falls REST API stabil läuft)
- Logging-DocType für Sync-Historie und Fehleranalyse
- Massen-Sync Funktion für mehrere Artikel auf einmal
- Unterstützung für Varianten und Attribute
- Möglichkeit, einzelne Felder vom Sync auszuschließen
- Bessere Fehlermeldungen und Status-Übersicht
Version History
v0.0.1 (08.06.2026)
- Initiale Version
- Sync via meta_data
- Manueller Sync-Button
- Dynamisches Mapping über DocType
Languages
Python
100%