Der Mitarbeiter exportiert entweder das OFFERT-Format oder das Merged+Additional-Format — nie beide zusammen
OFFERT-Variante (Format A) — 12 Sheets: 6 Kalkulations-Sheets (intern) + 6 Output-Sheets (OFFERT 1–5 + Uw). Das ist das primäre Gateway-Format. OFFERT 1 liefert fertige Position-Descriptions, OFFERT 2 liefert gerundete Endmengen + finale Preise, OFFERT 4/5 liefern per-Position UK-Mapping.
Merged+Additional-Variante (Format B) (2 Sheets) — das Format das
import-bridge.js heute bereits liest. Weiterhin unterstützt als Fallback.Konsequenz für das Gateway: Beim Upload wird das Format automatisch erkannt. Empfehlung: OFFERT-Sheets 8 (OFFERT 1) + 9 (OFFERT 2) als primäre Parse-Quelle nutzen — sie liefern produktionsbereite, aufbereitete Daten direkt aus der Excel-Kalkulation.
Das Gateway erkennt selbst welches Format hochgeladen wurde — der Mitarbeiter muss nichts auswählen
detectFormat(wb)ELEMENTE oder ELEMENT enthält (case-insensitive) UND Sheet mit MONTAGE enthält → 12-Sheet-OFFERT-Export.Zusatzprüfung: Anzahl Sheets ≥ 6. Wenn auch Sheets
OFFERT 1 / OFFERT1 vorhanden → bestätigt 12-Sheet-Vollexport (OFFERT-Sheets als Primärquelle nutzen).
merged oder erste Datenspalte einer Zeile enthält das Muster base-price|PWS oder surcharge|PWS.Alternativ: Sheet-Name enthält
additional oder additional data.
Die Output-Sheets 8–12 enthalten bereits aufbereitete, produktionsbereite Daten — direkt nutzbar für HERO ohne komplexe Assemblierung
add_product_position.description verwendbar. Keine Assemblierung aus ELEMENTE + GLAS nötig.| Inhalt (Zeile/Zelle) | Beispielwert | → HERO-Feld | Status |
|---|---|---|---|
| Vorbeschrieb (Zeile 6, Zelle C) | «Sky-Frame 3, das rahmenlose Schiebefenster, 2- oder 3-gleisig…» | add_text vor Positionen (System-Vorbeschrieb) | KEY |
| Pos. (Spalte B, ab Zeile 8) | 1.1, 1.2 | add_product_position · Join-Key | neu |
| Description-Block (Spalte C, Zeile unter Pos.) | «OG 01 / Sky-Frame 3 Classic / Rahmenaussenmass: 1301 x 3911mm / 2-gleisig, 3-fach Isolierglas / Glas: ESG-H 6 / ESG-H 6 / ESG-H 6 / Ug-Wert: 0.5 W/m2K Lt / g-Wert: 73 / 54 / Oberfläche: Colinal E6 20/25my» | add_product_position.description (vollständig, als HTML formatieren) | ⭐ KEY |
| EUR/Einheit (Spalte D) | - (leer = ohne Preis in Ausgabe) | Preis kommt aus ELEMENTE Sheet 1 | aus ELEMENTE |
| ZZZ-Position (Spalte B = «ZZZ») | «Preisanpassung Pauschal für Oberflächenvariante: RAL/NCS Qualicoat 1»; Total: -600,00 | add_product_position (Pauschal-Anpassung, negativer Preis) | ZZZ |
add_text.create_supply_service verwendbar — keine Kombination aus MONTAGE + OPTION nötig.| Spalte | Beispielwert | → HERO-Feld | Status |
|---|---|---|---|
| Pos. (Spalte B) | 100, 101, 102 … 114 | UK-Position ID (Join-Key) | neu |
| Titel (Spalte C, Titelzeile) | «Unterkonstruktion Sockel», «Elektrischer Antrieb» | create_supply_service.name | KEY |
| Langtext (Spalte C, Beschreibungszeile) | «Örtliche Befestigung mittels höhenverstellbaren Chromstahl-Konsolen…» | create_supply_service.description (HTML) | KEY |
| Einheit (Spalte D) | lm, Stk., m2, Pa., - | create_supply_service.unit_type (→ m, Stk, m², pauschal) | neu |
| Menge (Spalte E) | 11,2 / 7,8 / 11,2 / 2,0 / 1,0 | create_supply_service.quantity ← gerundeter Endwert | ⭐ KEY |
| EUR/Einheit (Spalte F) | 147,00 / 74,00 / 85,00 / 282,00 / 4.910,00 | create_supply_service.net_price_per_unit | KEY |
| Total EUR (Spalte G) | 1.646,40 / 577,20 / 3.158,40 | Plausibilitätsprüfung (Menge × Preis) | Prüfung |
| «per» in Menge-Spalte | Pos. 101, 107, 108, 109, 113: Menge = «per» | → Position vorhanden aber nicht einkalkuliert → quantity = 0, in Preview warnen | Edge Case |
| Total-Zeile (letzte Zeile) | «Total Sky-Frame Optionen»; 20.615,00 | Validierungs-Summe | Validierung |
| Inhalt | Beispielwert | → HERO-Feld | Status |
|---|---|---|---|
| Positions-Block (identisch mit OFFERT 1) | Pos. 1.1, 1.2 mit Descriptions | Identisch mit OFFERT 1 — bevorzuge Sheet 8 | via OFFERT 1 |
| UK-Block (identisch mit OFFERT 2) | Pos. 100–114 mit Mengen + Preisen | Identisch mit OFFERT 2 — bevorzuge Sheet 9 | via OFFERT 2 |
| ZZZ-Position | «Preisanpassung Pauschal…»; Total: -600,00 | add_product_position (Pauschal, negativer Preis) — auch in OFFERT 1 | ZZZ |
| Spalte | Beispielwert (Pos 1.1 / 1.2) | → HERO-Feld | Status |
|---|---|---|---|
| Pos. (Spalte B) | 1.1, 1.2 | Join-Key zur Position | Join |
| Text (Spalte C) | «OG 01 1301 x 3911mm» | Positions-Label (Referenz) | info |
| Stk. (Spalte D) | 1, 1 | Stückzahl der Position | info |
| m2 Total (Spalte E) | 5,1 / 38,6 | Glasfläche pro Position (Summe) | info |
| UK-Option-Spalten (E+) | Sockel: 1,3 / 9,9 · Wand: 3,9 / 3,9 · Sturz: 1,3 / 9,9 · Rinne: 1,3 / 9,9 · Antrieb: - / 2,0 · TOUCH: - / 1,0 | Per-Positions-Mengen pro UK-Option | Backup |
| OFFERT 5 zusätzlich: Transport | - / 1,0 (nur Pos 1.2) | Transport-Menge pro Position | neu |
| Summen-Zeile (letzte Zeile) | Sockel: 11,2 · Wand: 7,8 · Sturz: 11,2 · Rinne: 11,2 · Antrieb: 2,0 · TOUCH: 1,0 | ∑-Summen = identisch mit OFFERT 2 Mengen — Kreuzvalidierung | Validierung |
Sheets 1–6 als Backup/Ergänzung — Primärquellen sind OFFERT 1 (Sheet 8) und OFFERT 2 (Sheet 9)
| Spalte (Header Zeile 4) | Format / Beispiel | → HERO-Feld | Status |
|---|---|---|---|
| Pos. Nr. | 1.1, 1.2 | add_product_position · Positionsbezeichnung + Join-Key | neu |
| Positionsbez. Kunde | «OG 01» | add_product_position.name (Präfix) | neu |
| Stk. | 1 | add_product_position.quantity | neu |
| Breite mm | 1.301, 9.871 | add_product_position.description (B×H) | neu |
| Höhe mm | 3.911 | add_product_position.description (B×H) | neu |
| Preis Stk. EUR | 1.260, 2.615 | add_product_position.net_price | KEY |
| 2=2-Gleis / 3=3Gleis | 2, 3 | add_product_position.description | neu |
| Öffnungsart | f, flrf, L, R | add_product_position.description | neu |
| Glasaufbau (3-IV) | 6 / 6 / 6 | → Lookup-Key in 6 GLAS | Lookup |
| Glasbeschichtung | W | → Lookup-Key in 6 GLAS | Lookup |
| Text (Anmerkung) | «Optional Ug 0,5…» | add_product_position.description (Anmerkung) | neu |
| Mehrpreis (EUR) | 170 | Optional: in description erwähnen | optional |
| Spalte / Zeile | Format / Beispiel | → HERO-Feld | Status |
|---|---|---|---|
| Pos. Nr. (Zeile 6 Kol 2) | 1.1, 1.2 | Join-Key → gruppiert per Position | neu |
| b-Code + Menge (Spalte ~22ff) | b; 1,3 → Sockel 1.3 lm | Mengen-Quelle pro Position pro Option | neu |
| ∑-Totale (Zeile 3, Spalte ~72ff) | 11,172 (Sockel), 7,822 (Wand) | create_supply_service.quantity | KEY |
| Elektrischer Antrieb 2=2,0 | 2; 2,0 | create_supply_service.quantity = 2 Stk | neu |
| TOUCH 1=1,0 | 1; 1,0 | create_supply_service.quantity = 1 Stk | neu |
| h Montagezeit (Kol 10) | 9 h, 70.29 h | Montage-Info (Referenz, nicht direkt an HERO) | info |
| Spalte | Beispielwert | → HERO-Feld | Status |
|---|---|---|---|
| Pos. (Zeile 2) | 100, 102, 104, 105… | Katalog-Key (Join von MONTAGE b-code) | neu |
| Titel | «Unterkonstruktion Sockel» | create_supply_service.name | KEY |
| Text (Langtext) | «Örtliche Befestigung mittels…» | create_supply_service.description (HTML) | KEY |
| Einheit | lm, Stk., m2, Pa. | create_supply_service.unit_type (→ m, Stk, m², pauschal) | neu |
| Preis EUR | 147,00 / 74,00 / 4.910,00 | create_supply_service.net_price_per_unit | KEY |
| ∑ (Zeile 2) | 11,172 / 7,822 / 2 | create_supply_service.quantity (Backup wenn MONTAGE unklar) | neu |
| Einkauf EUR | 113,08 | Intern — nicht an HERO | ignorieren |
| Spalte | Beispielwert | → HERO-Feld | Status |
|---|---|---|---|
| Glasaufbau-Code | 6 / 6 / 6 | Lookup-Key (kommt aus ELEMENTE) | Key |
| Glasbeschichtung | W | Lookup-Key (kommt aus ELEMENTE) | Key |
| Glasaufbau-Volltext | ESG-H 6 / ESG-H 6 / ESG-H 6 | add_product_position.description (Glas-Block) | neu |
| Ug-Wert | 0,5 W/m²K | add_product_position.description | neu |
| Lt / g-Wert | 73 / 54 | add_product_position.description | neu |
| SG-Nr. | SG-31 | add_product_position.description | neu |
Beide Formate laufen durch unterschiedliche Parser, erzeugen aber dasselbe interne Datenobjekt
beliebiges Format
→ «offert» oder
«merged»
OFFERT
⭐ OFFERT 2 (mengen)
+ OPTION · GLAS
parseOffertFormat()
MERGED
Additional Data
parseMergedFormat()
positionen[]
optionen[]
meta }
+ Preview
HERO Projekt
Auswahl
→ Angebot
Entwurf in HERO
GatewayData-Objekt. Payload-Builder, Validierung und Preview-UI arbeiten nur damit — sie müssen das ursprüngliche Format nicht kennen. Format B (Merged) hat weniger Daten → fehlende Felder (UK-Texte, Glasdetails) werden mit Templates gefüllt.Jede Phase liefert einen lauffähigen Stand · Gesamtaufwand 20–24h
kalkulator/gateway.html anlegen — eigenständige SeitedetectFormat(wb) implementierenbase-price| → Format B. Unbekannt → Fehlermeldung mit Sheet-Namen-Hinweis.parseOffert1Sheet(rows) ⭐ Primärquelle DescriptionsMap<posNr, {description, vorbeschrieb}>. Falls Sheet 8 nicht vorhanden → Fallback auf parseElementeSheet(rows) für Assemblierung aus ELEMENTE + GLAS.parseOffert2Sheet(rows) ⭐ Primärquelle UK-MengenMap<posNr, {name, description, einheit, menge, preis}>. Falls Sheet 9 nicht vorhanden → Fallback auf parseMontageSheet() + parseOptionSheet() Kombination.parseOptionSheet(rows) — Preise-Backuplm→m, Stk.→Stk, Pa.→pauschal, m2→m². Gibt Map<posNr, optionData>.parseGlasSheet(rows) — Glas-Lookup BackupaufbauCode+beschichtung → { volltext, ug, lt, g, sgNr }. Primär nicht nötig — OFFERT 1 enthält bereits «Glas: ESG-H 6 / ESG-H 6 / ESG-H 6\nUg-Wert: 0.5 W/m2K». Nur als Fallback wenn Sheet 8 fehlt und Glasaufbau-Codes aufgelöst werden müssen.GatewayData-Objekt zusammenbauen{ positionen: [{posNr, description, stk, netPreis}], optionen: [{posNr, name, description, einheit, menge, preis}], glasLookup: {...}, meta: { projektNr, system, waehrung }, zzz: {text, betrag} }. Primärpfad: OFFERT 1 + OFFERT 2. Console-Log nach Upload zeigt vollständiges Objekt — Milestone.project_matches(search:) Query via Proxy → Treffer-Liste (Projektname, Kunde, Adresse) → Auswahl speichert project_match_id. Empfänger-Daten (first_name, street, company etc.) werden mitgeladen und zur Kontrolle angezeigt. Kein Überschreiben — HubSpot hat die Daten angelegt, Gateway zeigt sie nur. Mitarbeiter bestätigt «Daten stimmen».buildHeroPayload(gatewayData)set_recipient mit neuen Daten — Empfänger bereits via HubSpot in HERO gepflegt. Nur: add_text (editierbares Anschreiben-Template) → add_title «Sky-Frame Elemente» → pro Position ein add_product_position mit fertigem Glas-Block (aus OFFERT 1). publish: false — immer Entwurf, niemals direkt publiziert.add_title «Unterkonstruktion und Ausrüstung» → pro aktiver (Checkbox ✓) Option: create_supply_service mit name (aus OPTION Titel), description (Langtext als HTML), unit_type, quantity (aus MONTAGE ∑), net_price_per_unit (aus OPTION Preis EUR), vat_percent.create_document via Proxy. Bei Erfolg: HERO-Dokument-Link anzeigen, Toast «Angebot erstellt». Bei Fehler: GraphQL-Error-Message anzeigen, Payload-JSON zum Debuggen einblendbar. publish: false = Entwurf.parseMergedFormat(wb): bestehende importFromWorkbook()-Logik in GatewayData überführen. UK-Texte und Glasdetails mit Fix-Templates füllen (da nicht im Merged Sheet). Fehlende Felder in Validierung als Warnung markieren (nicht als Fehler).gw.* Keys in i18n.jsproject-output-language (aus Additional Data in Format B, oder aus SPRACHE-Sheet in Format A wenn eingebaut).+1–2h gegenüber Single-Format wegen Dual-Format-Erkennung und Format-B-Nachziehen
| Phase | Kerninhalt | Aufwand | Neues vs. Wiederverwendung |
|---|---|---|---|
| 1 | gateway.html · detectFormat() · 4 OFFERT-Parser · GatewayData-Objekt | 7–8h | ~80% neu |
| 2 | Preview-Tabellen (Positionen + UK) · HERO Projekt-Suche · Validierung | 5–6h | ~60% neu |
| 3 | Payload-Builder · API-Senden · AGB-Text · Format B (Merged) nachziehen | 6–7h | ~50% Wiederverwendung |
| 4 | i18n · Fehlertoleranz · Sende-History · optionale SPRACHE-Sheet | 3–4h | ~70% Wiederverwendung |
| Total MVP (Phase 1–3) | 18–21h | Beide Formate · vollständiger HERO-Export | |
| Total inkl. Phase 4 | 21–25h | Produktionsreif | |
| Modul | Datei | Nutzung |
|---|---|---|
| SheetJS-Lader + Workbook-Parsing | import-bridge.js | 100% — Gateway bindet selbes SheetJS ein |
| parseColNum / parseColStr / _norm | import-bridge.js | 100% — direkt nutzbar für OFFERT-Parser |
| parseMergedSheet / parseAdditionalData | import-bridge.js | 100% — in parseMergedFormat() einbinden |
| HERO GraphQL Proxy-Client | hero-test.html | 100% — extrahieren nach gateway.js |
| i18n System (I18N.t, applyTranslations) | i18n.js | 100% — neue gw.* Keys ergänzen |
| Toast + Design CSS-Tokens | index.html | ~90% — kopieren, leicht anpassen |
| SYSTEM_TO_GEWERK Mapping | import-bridge.js | 100% — für Gewerk-Zuweisung |
| Lessons Learned / API-Referenz | docs/hero-api-referenz.md | 100% — document_type_id, unit_types etc. |
Alle offenen Fragen beantwortet — keine Blockierungs-Punkte für Phase 1
set_recipient mit neuen Daten — nur Anzeige zur Kontrolle.partnerZip / Projektland aus Additional Data oder OPTION-Sheet. AT → 20%, CH → 8.1%. Falls nicht erkennbar → Warnung + manuell wählbar.
publish: false — niemals direkt publiziert. Entwurf in HERO, Mitarbeiter prüft und publiziert dort manuell.
kalkulator/gateway.html — separates Tool, keine Einbindung in Kalkulator-Navigation.
gateway.html hoch. Format wird automatisch erkannt.
publish: false). Link zum Entwurf anzeigen.