2026-06-06 19:59:14 +02:00
# erpnext_custom
2026-06-06 19:40:07 +02:00
2026-06-08 14:23:15 +02:00
Custom App für NEXTErp mit zuverlässigem Sync von Custom Fields zu WooCommerce (ACF / Meta Data).
2026-06-07 13:39:51 +02:00
## Funktionen
2026-06-08 14:23:15 +02:00
- Sync beliebiger Custom Fields aus ERPNext-Item nach WooCommerce
- Verwendung von `meta_data` (stabiler als ACF-Block)
- Manueller Sync-Button im Item-Formular
2026-06-07 13:39:51 +02:00
2026-06-08 15:36:33 +02:00
## 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
2026-06-08 14:23:15 +02:00
## Installation
2026-06-07 13:39:51 +02:00
2026-06-08 14:23:15 +02:00
### 1. App erstellen
2026-06-07 13:39:51 +02:00
``` bash
cd ~/frappe-bench
2026-06-08 14:26:34 +02:00
bench new-app erpnext_custom
```
Bei den Fragen:
2026-06-08 15:27:31 +02:00
- App Title: NEXTErp Custom
- App Description: WooCommerce ACF Custom Fields Sync
- App Publisher: Jens Falk
- App Email: service@falk .plus
2026-06-08 14:26:34 +02:00
``` bash
cd ~/frappe-bench/apps/erpnext_custom/erpnext_custom
```
2026-06-08 15:19:19 +02:00
``` 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
2026-06-08 14:26:34 +02:00
2026-06-08 15:19:19 +02:00
```
2026-06-08 15:20:09 +02:00
Danach ausführen
``` bash
bench restart
```
2026-06-08 15:27:31 +02:00
### 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" ) ;
}
} ) ;
```
2026-06-08 16:38:15 +02:00
#### 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 : ` <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" ) ;
}
} ;
```
2026-06-08 15:30:53 +02:00
### 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.msgprint` Meldungen 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
2026-06-08 15:27:31 +02:00
2026-06-08 15:30:53 +02:00
- 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
2026-06-08 15:27:31 +02:00
2026-06-08 15:35:12 +02:00
## Version History
**v0.0.1** (08.06.2026)
- Initiale Version
- Sync via meta_data
- Manueller Sync-Button
- Dynamisches Mapping über DocType