diff --git a/CLAUDE.md b/CLAUDE.md index 179c8a5..93448fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,9 +89,9 @@ Speichert: color, stroke, fontFamily, fontSize, bold, italic, zoom --- ## Phase 3 — PWA MVP ✅ (2026-04-17) -- Repo: `data-it/baustelle-pwa` (SvelteKit-Projekt) +- Repo: `baustelle-pwa`, lokal unter `/mnt/17 - Entwicklungen/30 - Scripts/javascript/baustelle-pwa/repo` - Hosting: `awl.data-it-solution.de/baustelle/` (Apache-Alias) -- Stack: SvelteKit + Workbox + idb-keyval +- Stack: **Vanilla JS** (`app.js`, ~160 KB) + `index.php` + Service Worker + idb-keyval — KEIN SvelteKit (frühere Doku war falsch) - Features: Login → Auftragsliste → Order-Detail → Foto/Sprachnotiz/Materialliste → PDF-Viewing - **PDF-Viewer implementiert**: PDF.js Canvas-Rendering, Datei-Typ-Unterscheidung (Bilder/Audio/PDFs/Dokumente) - **Weitere Dokumente**: Order-Dateien aus `commande//` als Inline-Viewer (PDF.js) oder Download diff --git a/ChangeLog.md b/ChangeLog.md index c8627b8..259530a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,22 @@ # Changelog +## 1.3.0 — 2026-07-18 + +### PWA Auftrag-Anlegen überarbeitet (Formular + Freigabe) + +**Formular (Baustelle-PWA, `openNewOrderModal`)** +- Feld-Reihenfolge/Beschriftung angepasst: **„Ihr Zeichen"** (= `ref_client`) steht jetzt an **erster** Stelle und ist **Pflicht**; **„Auftragsbeschreibung"** (= Extrafield `options_auftragsbeschreibung`) an zweiter Stelle und optional. (Vorher genau umgekehrt: Titel Pflicht, Kunden-Referenz optional.) +- Neues Feld **„Geplanter Liefertermin"** (`datetime-local`, Datum + Uhrzeit in einem Feld) → Auftrag-Spalte `date_livraison`. +- Neue Checkbox **„Auftrag direkt freigeben"**. + +**Backend (`api/orders.php?action=create`)** +- `ref_client` ist jetzt serverseitig **Pflicht** (Geschäftsregel — Dolibarr-Core verlangt nur den Kunden). +- Neuer Parameter `date_livraison` (Unix-TS) → `Commande->delivery_date`. +- Neuer Parameter `validate`: legt vor der Freigabe automatisch eine **Std-Lohn-Zeile** an und ruft dann `Commande::valid()`. + - Produkt: Kundenkarten-Default `options_stundenzettel_default_service`, sonst Fallback-Produkt über Ref `BERICHT_ORDER_DEFAULT_SERVICE_REF` (Default `ELEK-STD-M`, „Stundenlohn Meister" 45 €). + - Preis: kundenspezifischer Preis (`PRODUIT_CUSTOMER_PRICES`) hat Vorrang, sonst Produkt-Standardpreis. Menge 1. +- Response erweitert um `ref_client`, `date_livraison`, `validated`, `added_line`, `validate_warning`. + ## 1.2.0 — 2026-05-27 ### Phase 1.8 Lieferschein-Bestätigung in der PWA diff --git a/README.md b/README.md index cbc0919..6dd815a 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ GET /custom/bericht/api/orders.php GET /custom/bericht/api/orders.php?id= GET /custom/bericht/api/orders.php?id=&action=photos POST /custom/bericht/api/orders.php?action=create + Body: { socid, ref_client, title?, note_private?, date?, date_livraison?, validate? } + ref_client ("Ihr Zeichen") ist PFLICHT. validate=true legt vor der Freigabe automatisch + eine Std-Lohn-Zeile an (Kundenkarten-Default options_stundenzettel_default_service, + sonst Fallback BERICHT_ORDER_DEFAULT_SERVICE_REF; Kundenpreis vor Standardpreis). ``` ### Dateien @@ -141,6 +145,7 @@ Per `admin/setup.php` oder `llx_const`: | `BERICHT_SIGNATURE_BOX_DEFAULT` | JSON | Default-Geometrie für FPDI-Stempel-Fallback (`{"page":"last","x_mm":120,"y_mm":230,"w_mm":70,"h_mm":35,"label":"Unterschrift Kunde"}`) | | `BERICHT_BURN_ANNOTATIONS` | 0 | Annotationen ins PDF einbrennen statt als PDF-Annotation einbetten | | `BERICHT_LIBREOFFICE_BIN` | `soffice` | Pfad zur LibreOffice-Binary (für ODT→PDF) | +| `BERICHT_ORDER_DEFAULT_SERVICE_REF` | `ELEK-STD-M` | Fallback-Produkt-Ref für die Std-Lohn-Zeile beim Direkt-Freigeben (wenn Kunde keinen `stundenzettel_default_service` gesetzt hat) | ## Datenbank diff --git a/api/orders.php b/api/orders.php index 5c39307..3ff9539 100644 --- a/api/orders.php +++ b/api/orders.php @@ -9,7 +9,15 @@ * multipart: file= — fügt ein Foto zum Bericht des Auftrags hinzu * (legt automatisch einen Bericht an wenn keiner existiert) * POST /api/orders.php?action=create - * JSON-Body: { socid, title, ref_client?, note_private?, date? } + * JSON-Body: { socid, ref_client, title?, note_private?, date?, date_livraison?, validate? } + * - ref_client ("Ihr Zeichen") ist PFLICHT (Geschäftsregel; Dolibarr selbst verlangt es nicht). + * - title (Auftragsbeschreibung) optional -> Extrafield options_auftragsbeschreibung. + * - date_livraison (Unix-TS) optional -> geplanter Liefertermin (Spalte date_livraison). + * - validate (bool) optional -> Auftrag direkt freigeben. Vorher wird automatisch eine + * Std-Lohn-Zeile ergänzt (Kundenkarten-Default options_stundenzettel_default_service, + * sonst Fallback-Produkt BERICHT_ORDER_DEFAULT_SERVICE_REF, Default 'ELEK-STD-M'). + * Preis = kundenspezifischer Preis (PRODUIT_CUSTOMER_PRICES) falls vorhanden, + * sonst Produkt-Standardpreis. * Legt einen Draft-Auftrag an und übernimmt Kunden-Defaults (Zahlungsbedingungen, * Zahlart, Bankkonto, Incoterms, Lieferadresse, Default-Ansprechpartner). */ @@ -31,13 +39,17 @@ if ($action === 'create' && $_SERVER['REQUEST_METHOD'] === 'POST') { $in = api_input(); $socid = (int) ($in['socid'] ?? 0); - $title = trim((string) ($in['title'] ?? '')); - $ref_client = trim((string) ($in['ref_client'] ?? '')); + $title = trim((string) ($in['title'] ?? '')); // Auftragsbeschreibung (optional) + $ref_client = trim((string) ($in['ref_client'] ?? '')); // "Ihr Zeichen" (Pflicht) $note_private = trim((string) ($in['note_private'] ?? '')); $date = (int) ($in['date'] ?? 0); if ($date <= 0) $date = dol_now(); + $date_livraison = (int) ($in['date_livraison'] ?? 0); // geplanter Liefertermin (optional) + $do_validate = !empty($in['validate']); // direkt freigeben? if ($socid <= 0) api_fail('socid fehlt'); + // Geschäftsregel: "Ihr Zeichen" (ref_client) ist Pflicht. Dolibarr-Core verlangt nur den Kunden. + if ($ref_client === '') api_fail('Ihr Zeichen (Kunden-Referenz) ist Pflicht'); // Kundenstamm holen, um Defaults zu ziehen (wie Dolibarr-Web beim Auftrag-Anlegen) $soc = new Societe($db); @@ -49,6 +61,7 @@ if ($action === 'create' && $_SERVER['REQUEST_METHOD'] === 'POST') { $cmd->date = $date; $cmd->ref_client = $ref_client; $cmd->note_private = $note_private; + if ($date_livraison > 0) $cmd->delivery_date = $date_livraison; // -> Spalte date_livraison // Kunden-Defaults übernehmen if (!empty($soc->cond_reglement_id)) $cmd->cond_reglement_id = (int) $soc->cond_reglement_id; @@ -76,6 +89,63 @@ if ($action === 'create' && $_SERVER['REQUEST_METHOD'] === 'POST') { $cmd->add_contact((int) $oc->rowid, 'CUSTOMER', 'external'); } + /* ----- Optional: Std-Lohn-Zeile ergänzen und Auftrag direkt freigeben ----- */ + $validated = false; + $validate_warning = ''; + $added_line = null; + if ($do_validate) { + require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; + + // 1) Leistungs-Produkt bestimmen: Kundenkarten-Default, sonst Fallback über Ref + // (Ref statt rowid, da rowids zwischen Test-/Prod-DB verschieden sind) + if (method_exists($soc, 'fetch_optionals')) $soc->fetch_optionals(); + $svc_id = (int) ($soc->array_options['options_stundenzettel_default_service'] ?? 0); + if ($svc_id <= 0) { + $fallback_ref = getDolGlobalString('BERICHT_ORDER_DEFAULT_SERVICE_REF', 'ELEK-STD-M'); + $rp = $db->query("SELECT rowid FROM ".$db->prefix()."product" + ." WHERE ref = '".$db->escape($fallback_ref)."'" + ." AND entity IN (".getEntity('product').") LIMIT 1"); + if ($rp && ($op = $db->fetch_object($rp))) $svc_id = (int) $op->rowid; + } + + if ($svc_id > 0) { + $prod = new Product($db); + if ($prod->fetch($svc_id) > 0) { + $pu_ht = (float) $prod->price; + $tva = (float) $prod->tva_tx; + + // 2) Kundenspezifischer Preis (PRODUIT_CUSTOMER_PRICES) hat Vorrang + if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) { + $rcp = $db->query("SELECT price, tva_tx FROM ".$db->prefix()."product_customer_price" + ." WHERE fk_product = ".((int) $svc_id)." AND fk_soc = ".((int) $socid) + ." ORDER BY rowid DESC LIMIT 1"); + if ($rcp && ($cp = $db->fetch_object($rcp))) { + $pu_ht = (float) $cp->price; + if ($cp->tva_tx !== null && $cp->tva_tx !== '') $tva = (float) $cp->tva_tx; + } + } + + // Menge 1 als Start; Monteur trägt die echten Stunden später ein + $rl = $cmd->addline($prod->label, $pu_ht, 1, $tva, 0, 0, $svc_id, 0, 0, 0, 'HT', 0, '', '', (int) $prod->type); + if ($rl > 0) { + $added_line = array( + 'product' => $prod->ref, + 'label' => $prod->label, + 'pu_ht' => $pu_ht, + 'tva_tx' => $tva, + ); + } + } + } + + // Zeilen neu laden, dann freigeben + $cmd->fetch($newid); + $cmd->fetch_lines(); + $vr = $cmd->valid($user); + if ($vr > 0) $validated = true; + else $validate_warning = 'Freigabe fehlgeschlagen: '.$cmd->error; + } + // Für identische Response-Shape wie GET ?id= neu laden $cmd->fetch($newid); $cmd->fetch_thirdparty(); @@ -85,8 +155,11 @@ if ($action === 'create' && $_SERVER['REQUEST_METHOD'] === 'POST') { 'order' => array( 'id' => (int) $cmd->id, 'ref' => $cmd->ref, + 'ref_client' => $cmd->ref_client, 'date' => $cmd->date_commande, + 'date_livraison' => !empty($cmd->delivery_date) ? (int) $cmd->delivery_date : null, 'status' => (int) $cmd->statut, + 'validated' => $validated, 'total' => (float) $cmd->total_ttc, 'note_private' => $cmd->note_private, 'auftragsbeschreibung' => $cmd->array_options['options_auftragsbeschreibung'] ?? '', @@ -100,6 +173,8 @@ if ($action === 'create' && $_SERVER['REQUEST_METHOD'] === 'POST') { 'phone' => $cmd->thirdparty->phone ?? '', 'email' => $cmd->thirdparty->email ?? '', ), + 'added_line' => $added_line, + 'validate_warning' => $validate_warning, )); }