2026-06-06 19:59:14 +02:00
|
|
|
# erpnext_custom
|
2026-06-06 19:40:07 +02:00
|
|
|
|
2026-06-07 13:39:51 +02:00
|
|
|
Custom App für NEXTErp mit WooCommerce ACF Sync (Advanced Custom Fields).
|
|
|
|
|
|
|
|
|
|
## Funktionen
|
|
|
|
|
- Übertragung beliebiger Custom Fields aus dem ERPNext-Item nach WooCommerce (ACF)
|
|
|
|
|
- Dynamisches Mapping über das DocType `WooCommerce ACF Mapping`
|
|
|
|
|
- Funktioniert mit mehreren WooCommerce Servern
|
|
|
|
|
|
|
|
|
|
## Installation (empfohlener Weg)
|
|
|
|
|
|
|
|
|
|
### 1. App auf dem Server erstellen
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cd ~/frappe-bench
|
|
|
|
|
bench new-app erpnext_custom
|
2026-06-07 13:43:35 +02:00
|
|
|
`
|
2026-06-07 13:39:51 +02:00
|
|
|
|
|
|
|
|
Bei den Fragen folgendes eingeben:
|
|
|
|
|
|
|
|
|
|
App Title: NEXTErp Custom
|
|
|
|
|
App Description: WooCommerce ACF Custom Fields Sync
|
|
|
|
|
App Publisher: Jens Falk
|
|
|
|
|
App Email: service@falk.plus (oder deine Email)
|
|
|
|
|
Branch Name: main oder einfach Enter drücken
|
|
|
|
|
|
|
|
|
|
cd ~/frappe-bench/apps/erpnext_custom/erpnext_custom
|
|
|
|
|
# Hier die aktuelle api.py einfügen (z.B. per cat > api.py << 'EOF' ...)
|
|
|
|
|
|
|
|
|
|
cat > api.py << 'EOF'
|
|
|
|
|
import frappe
|
|
|
|
|
import requests
|
|
|
|
|
from frappe import _
|
|
|
|
|
|
|
|
|
|
@frappe.whitelist()
|
|
|
|
|
def sync_custom_fields_to_woocommerce(item_code):
|
|
|
|
|
"""Sync Custom Fields vom Item nach WooCommerce ACF (dynamisch aus DocType)"""
|
|
|
|
|
|
|
|
|
|
if not item_code:
|
|
|
|
|
frappe.throw(_("Kein Item Code angegeben"))
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
doc = frappe.get_doc("Item", item_code)
|
|
|
|
|
except Exception:
|
|
|
|
|
frappe.throw(_("Item {0} nicht gefunden").format(item_code))
|
|
|
|
|
|
|
|
|
|
if not doc.get("woocommerce_servers"):
|
|
|
|
|
frappe.msgprint(_("Item hat keine verknüpften WooCommerce Server"), indicator="orange")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Dynamisches Mapping aus dem DocType "WooCommerce ACF Mapping"
|
|
|
|
|
field_mapping = {}
|
|
|
|
|
mappings = frappe.get_all("WooCommerce ACF Mapping",
|
|
|
|
|
filters={"enabled": 1},
|
|
|
|
|
fields=["erp_field", "acf_field"])
|
|
|
|
|
|
|
|
|
|
for m in mappings:
|
|
|
|
|
if m.erp_field and m.acf_field:
|
|
|
|
|
field_mapping[m.erp_field] = m.acf_field
|
|
|
|
|
|
|
|
|
|
if not field_mapping:
|
|
|
|
|
frappe.msgprint(_("Keine aktivierten Mappings im DocType 'WooCommerce ACF Mapping' gefunden."), indicator="orange")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
success_count = 0
|
|
|
|
|
errors = []
|
|
|
|
|
|
|
|
|
|
for wc_link in doc.woocommerce_servers:
|
|
|
|
|
if not wc_link.enable_sync or not wc_link.woocommerce_id:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
wc_server = frappe.get_doc("WooCommerce Server", wc_link.woocommerce_server)
|
|
|
|
|
|
|
|
|
|
if not wc_server.api_key or not wc_server.api_secret:
|
|
|
|
|
errors.append("WooCommerce Server {} hat keine Zugangsdaten".format(wc_link.woocommerce_server))
|
|
|
|
|
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 = "{}/products/{}".format(base_url, wc_link.woocommerce_id)
|
|
|
|
|
auth = (wc_server.api_key, wc_server.api_secret)
|
|
|
|
|
|
|
|
|
|
resp = requests.get(url, auth=auth, timeout=15)
|
|
|
|
|
resp.raise_for_status()
|
|
|
|
|
product = resp.json()
|
|
|
|
|
|
|
|
|
|
acf_data = product.get("acf", {})
|
|
|
|
|
|
|
|
|
|
updated_fields = 0
|
|
|
|
|
for erp_field, acf_field in field_mapping.items():
|
|
|
|
|
value = doc.get(erp_field)
|
|
|
|
|
if value is not None:
|
|
|
|
|
acf_data[acf_field] = value
|
|
|
|
|
updated_fields += 1
|
|
|
|
|
|
|
|
|
|
if updated_fields == 0:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
payload = {"acf": acf_data}
|
|
|
|
|
|
|
|
|
|
update_resp = requests.put(url, json=payload, auth=auth, timeout=15)
|
|
|
|
|
update_resp.raise_for_status()
|
|
|
|
|
|
|
|
|
|
success_count += 1
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
error_msg = "Fehler für Item {} / WC-ID {}: {}".format(item_code, wc_link.woocommerce_id, str(e))
|
|
|
|
|
errors.append(error_msg)
|
|
|
|
|
frappe.log_error(error_msg, "WooCommerce ACF Sync")
|
|
|
|
|
|
|
|
|
|
if success_count > 0:
|
|
|
|
|
msg = "Custom Fields für {} WooCommerce-Produkt(e) erfolgreich synchronisiert".format(success_count)
|
|
|
|
|
if errors:
|
|
|
|
|
msg += " ({} Fehler)".format(len(errors))
|
|
|
|
|
frappe.msgprint(msg, alert=True, indicator="green")
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
if errors:
|
|
|
|
|
frappe.msgprint("Fehler beim Sync:\n" + "\n".join(errors[:3]), indicator="red")
|
|
|
|
|
else:
|
|
|
|
|
frappe.msgprint("Keine Felder wurden synchronisiert.", indicator="orange")
|
|
|
|
|
return False
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
### Installation abschließen
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
bench --site erp.berchmans.de install-app erpnext_custom --force
|
|
|
|
|
bench --site erp.berchmans.de migrate
|
|
|
|
|
bench restart
|
|
|
|
|
|
|
|
|
|
Weiterer Schritt (nach Installation)
|
|
|
|
|
Danach das DocType WooCommerce ACF Mapping anlegen
|