diff --git a/msp/importers/builder.py b/msp/importers/builder.py index 6fd5ea5..8a1aac5 100644 --- a/msp/importers/builder.py +++ b/msp/importers/builder.py @@ -165,15 +165,16 @@ class DocumentBuilder: if is_return and qty > 0: qty = -qty - # ERPNext berechnet amount = qty * rate. `rate` muss daher der - # tatsächliche Netto-Einzelpreis nach Rabatt sein (EINZELPREIS aus ADN). - # Fallback-Reihenfolge: unit_price → amount/qty → list_price - if row.unit_price is not None: + # WICHTIG: Kundenabrechnungen nutzen den LISTPREIS, nicht den Einkaufspreis. + # Der Lieferanten-Rabatt (z. B. 17,5 % bei ADN) ist unsere Marge und + # darf nicht an den Endkunden durchgereicht werden. + # Fallback-Reihenfolge: list_price → unit_price → amount/qty + if row.list_price is not None: + rate = row.list_price + elif row.unit_price is not None: rate = row.unit_price elif row.amount is not None and qty: rate = row.amount / qty - elif row.list_price is not None: - rate = row.list_price else: rate = 0 @@ -183,11 +184,6 @@ class DocumentBuilder: "rate": rate, "description": self._format_description(row), } - # Listenpreis + Rabatt zur Nachvollziehbarkeit in der UI mitführen - if row.list_price is not None: - item_row["price_list_rate"] = row.list_price - if row.discount_pct: - item_row["discount_percentage"] = row.discount_pct item = doc.append("items", item_row) diff --git a/msp/importers/subscriptions.py b/msp/importers/subscriptions.py index 20f9708..1784c4b 100644 --- a/msp/importers/subscriptions.py +++ b/msp/importers/subscriptions.py @@ -61,15 +61,28 @@ def upsert_supply_subscription(row: CanonicalRow, *, customer: str, def create_event(row: CanonicalRow, *, supply_subscription: str, target_doctype: str, target_name: str, target_row: str | None = None, supplier_import_line_row: str | None = None) -> str: - """Erzeugt ein Supply Subscription Event für eine abgerechnete Periode.""" + """Erzeugt ein Supply Subscription Event für eine abgerechnete Periode. + + ``rate`` und ``amount`` spiegeln den **Kundenpreis** (Listenpreis × Menge), + ``cost_rate`` und ``cost_amount`` den Einkaufspreis bei Lieferant. + Differenz = itsdaves Marge. + """ + qty = int(row.qty) if row.qty else 0 + list_price = row.list_price + customer_rate = list_price if list_price is not None else (row.unit_price or 0) + customer_amount = qty * customer_rate + event = frappe.get_doc({ "doctype": "Supply Subscription Event", "supply_subscription": supply_subscription, "period_start": row.period_start, "period_end": row.period_end, - "qty": int(row.qty) if row.qty else 0, - "rate": row.unit_price or row.list_price or 0, - "amount": row.amount or 0, + "qty": qty, + "rate": customer_rate, + "amount": customer_amount, + "cost_rate": row.unit_price, + "cost_amount": row.amount, + "reseller_discount_pct": row.discount_pct, "booking_type": row.booking_type or "Billing", "sales_invoice": target_name if target_doctype == "Sales Invoice" else None, "sales_invoice_item_row": target_row if target_doctype == "Sales Invoice" else None, diff --git a/msp/msp/doctype/supply_subscription_event/supply_subscription_event.json b/msp/msp/doctype/supply_subscription_event/supply_subscription_event.json index 43442d4..f62461f 100644 --- a/msp/msp/doctype/supply_subscription_event/supply_subscription_event.json +++ b/msp/msp/doctype/supply_subscription_event/supply_subscription_event.json @@ -20,6 +20,12 @@ "qty", "rate", "amount", + "cost_section", + "cost_rate", + "cost_amount", + "column_break_cost", + "reseller_discount_pct", + "margin_amount", "link_section", "sales_invoice", "sales_invoice_item_row", @@ -116,6 +122,42 @@ "in_list_view": 1, "label": "Amount" }, + { + "fieldname": "cost_section", + "fieldtype": "Section Break", + "label": "Einkauf & Marge", + "collapsible": 1, + "description": "Interne Daten (was itsdave an den Lieferanten bezahlt hat) — dient nicht der Kundenabrechnung." + }, + { + "fieldname": "cost_rate", + "fieldtype": "Currency", + "label": "Cost Rate", + "description": "Einzelpreis nach Wiederverkäufer-Rabatt (z. B. ADN EINZELPREIS)" + }, + { + "fieldname": "cost_amount", + "fieldtype": "Currency", + "label": "Cost Amount", + "description": "qty × cost_rate (z. B. ADN POSITIONSPREIS)" + }, + { + "fieldname": "column_break_cost", + "fieldtype": "Column Break" + }, + { + "fieldname": "reseller_discount_pct", + "fieldtype": "Float", + "label": "Reseller Discount (%)", + "description": "Wiederverkäufer-Rabatt gegenüber Listenpreis" + }, + { + "fieldname": "margin_amount", + "fieldtype": "Currency", + "label": "Margin", + "read_only": 1, + "description": "amount − cost_amount" + }, { "fieldname": "link_section", "fieldtype": "Section Break", diff --git a/msp/msp/doctype/supply_subscription_event/supply_subscription_event.py b/msp/msp/doctype/supply_subscription_event/supply_subscription_event.py index ba6f50d..77048a2 100644 --- a/msp/msp/doctype/supply_subscription_event/supply_subscription_event.py +++ b/msp/msp/doctype/supply_subscription_event/supply_subscription_event.py @@ -3,6 +3,11 @@ from frappe.model.document import Document class SupplySubscriptionEvent(Document): + def before_save(self): + # Marge berechnen, sofern beide Seiten bekannt sind + if self.amount is not None and self.cost_amount is not None: + self.margin_amount = float(self.amount) - float(self.cost_amount) + def on_update(self): self._refresh_subscription_denormals()