Warenwirtschaft: AP-32-Brückenlösung — DHL-Label aus WooCommerce übernehmen
Bestell-Übertragung (wp/order) akzeptiert wp_label_url + wp_tracking_number: Label-PDF wird als lokale Kopie im privaten Storage gespeichert (Shop-URL ist öffentlich erratbar; Kopie überlebt Löschung im Shop), Sendungsnummer landet in shopping_orders.tracking. Neuer Endpoint wp/label zum Nachreichen (Connector-Hook pr_save_dhl_label_tracking), wp/status liefert die Sendungsnummer an den Shop zurück. Abwicklungs-Board + Bestell-Details zeigen Label-Download (copyreader-Dokumentroute type=label, Fallback Shop-Link) und Sendungsnummer. Download-Fehler blockieren die Übertragung nie. Doku: Connector-Update-Anleitung (dev/product management), Hinweise, Testleitfaden §11, Plan-Protokoll. pint.json schließt dev/ (Fremdcode) aus. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a1f94ea427
commit
0eed974576
13 changed files with 611 additions and 25 deletions
|
|
@ -166,9 +166,20 @@ class OrderFulfillmentController extends Controller
|
|||
*/
|
||||
public function document(int $id, string $type)
|
||||
{
|
||||
abort_unless(in_array($type, ['invoice', 'delivery', 'combined'], true), 404);
|
||||
abort_unless(in_array($type, ['invoice', 'delivery', 'combined', 'label'], true), 404);
|
||||
|
||||
$order = ShoppingOrder::query()->findOrFail($id);
|
||||
|
||||
// AP-32 Brückenlösung: lokal gespeicherte Kopie des DHL-Labels aus dem
|
||||
// WooCommerce-Shop (privater Storage — bewusst nicht die öffentliche Shop-URL)
|
||||
if ($type === 'label') {
|
||||
abort_unless($order->wp_label_path && Storage::disk('local')->exists($order->wp_label_path), 404);
|
||||
|
||||
return Response::make(Storage::disk('local')->get($order->wp_label_path), 200)
|
||||
->header('Content-Type', 'application/pdf')
|
||||
->header('Content-disposition', 'inline; filename="DHL-Label-Bestellung-'.$order->id.'.pdf"');
|
||||
}
|
||||
|
||||
abort_unless(Invoice::isInvoice($order), 404);
|
||||
|
||||
if ($type === 'combined') {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ use App\Services\Shop;
|
|||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Yard;
|
||||
|
||||
class ShoppingUserController extends Controller
|
||||
|
|
@ -64,6 +67,7 @@ class ShoppingUserController extends Controller
|
|||
'user' => $shopping_user ? true : false,
|
||||
'order' => ($shopping_user && $shopping_user->shopping_order) ? true : false,
|
||||
'status' => $shopping_user ? $shopping_user->getAPIShippedType() : false,
|
||||
'tracking_number' => ($shopping_user && $shopping_user->shopping_order) ? $shopping_user->shopping_order->tracking : null,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -364,6 +368,8 @@ class ShoppingUserController extends Controller
|
|||
}
|
||||
|
||||
$wp_invoice_path = isset($request->wp_invoice_path) ? $request->wp_invoice_path : null;
|
||||
$wp_label_url = isset($request->wp_label_url) ? $request->wp_label_url : null;
|
||||
$wp_tracking_number = isset($request->wp_tracking_number) ? $request->wp_tracking_number : null;
|
||||
$wp_advertising = isset($request->wp_advertising) ? $request->wp_advertising : '';
|
||||
$wp_incentives = isset($request->wp_incentives) ? $request->wp_incentives : '';
|
||||
|
||||
|
|
@ -375,6 +381,9 @@ class ShoppingUserController extends Controller
|
|||
$wp_order = $this->prepareOrder($wp_order, $shopping_user, $wp_invoice_path, $wp_notice);
|
||||
|
||||
if ($wp_order) {
|
||||
// AP-32 Brückenlösung: DHL-Label aus dem Shop übernehmen (Kopie lokal speichern)
|
||||
$this->storeWpLabel($shopping_user->shopping_order, $wp_label_url, $wp_tracking_number);
|
||||
|
||||
// AP-13: Übertragung inkl. SKU-Zuordnungsergebnis je Position protokollieren
|
||||
ApiOrderLog::recordTransfer($request->wp_order_number, $shopping_user, $shopping_user->shopping_order, $wp_order);
|
||||
$user = $this->prepareForShow($shopping_user);
|
||||
|
|
@ -385,6 +394,9 @@ class ShoppingUserController extends Controller
|
|||
'data' => [
|
||||
'wp_order_number' => $shopping_user->wp_order_number,
|
||||
'wp_invoice_path' => $wp_invoice_path,
|
||||
'wp_label_url' => $shopping_user->shopping_order->wp_label_url,
|
||||
'wp_label_stored' => (bool) $shopping_user->shopping_order->wp_label_path,
|
||||
'wp_tracking_number' => $shopping_user->shopping_order->tracking,
|
||||
'wp_advertising' => $wp_advertising,
|
||||
'wp_incentives' => $wp_incentives,
|
||||
'wp_order' => $wp_order,
|
||||
|
|
@ -405,6 +417,90 @@ class ShoppingUserController extends Controller
|
|||
], 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* AP-32 Brückenlösung: DHL-Label nachreichen bzw. aktualisieren — für den Fall,
|
||||
* dass das Label im Shop erst NACH der Bestell-Übertragung erstellt (oder neu
|
||||
* erstellt) wird. Der Connector kann dafür den Plugin-Hook
|
||||
* `pr_save_dhl_label_tracking` von "DHL for WooCommerce" nutzen.
|
||||
*/
|
||||
public function label(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'wp_order_number' => 'required|int',
|
||||
'wp_label_url' => 'nullable|url',
|
||||
'wp_tracking_number' => 'nullable',
|
||||
]);
|
||||
|
||||
$shopping_user = ShoppingUser::where('wp_order_number', '=', $request->wp_order_number)->first();
|
||||
if (! $shopping_user || ! $shopping_user->shopping_order) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Order with wp_order_number '.$request->wp_order_number.' not found',
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
|
||||
$shopping_order = $shopping_user->shopping_order;
|
||||
$this->storeWpLabel($shopping_order, $request->wp_label_url, $request->wp_tracking_number);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'wp_order_number' => $shopping_user->wp_order_number,
|
||||
'wp_label_url' => $shopping_order->wp_label_url,
|
||||
'wp_label_stored' => (bool) $shopping_order->wp_label_path,
|
||||
'wp_tracking_number' => $shopping_order->tracking,
|
||||
],
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Label-URL + Sendungsnummer aus dem WooCommerce-Shop an der Bestellung speichern.
|
||||
* Das PDF wird als lokale Kopie in den privaten Storage geladen (Auslieferung nur
|
||||
* über die Admin-Route) — so bleibt es verfügbar, auch wenn es im Shop gelöscht
|
||||
* wird, und die öffentliche Upload-URL muss nicht verlinkt werden. Ein
|
||||
* fehlgeschlagener Download darf die Übertragung nie scheitern lassen.
|
||||
*/
|
||||
private function storeWpLabel($shopping_order, $wp_label_url, $wp_tracking_number): void
|
||||
{
|
||||
if (! $shopping_order || (! $wp_label_url && ! $wp_tracking_number)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($wp_tracking_number)) {
|
||||
$wp_tracking_number = implode(', ', array_filter($wp_tracking_number));
|
||||
}
|
||||
if ($wp_tracking_number) {
|
||||
$shopping_order->tracking = $wp_tracking_number;
|
||||
}
|
||||
|
||||
if ($wp_label_url) {
|
||||
$shopping_order->wp_label_url = $wp_label_url;
|
||||
try {
|
||||
$response = Http::timeout(20)->get($wp_label_url);
|
||||
if ($response->successful() && str_starts_with($response->body(), '%PDF')) {
|
||||
$path = 'wp-labels/label-'.$shopping_order->id.'.pdf';
|
||||
Storage::disk('local')->put($path, $response->body());
|
||||
$shopping_order->wp_label_path = $path;
|
||||
} else {
|
||||
Log::warning('WP-Label-Download fehlgeschlagen (kein PDF oder HTTP-Fehler)', [
|
||||
'shopping_order_id' => $shopping_order->id,
|
||||
'wp_label_url' => $wp_label_url,
|
||||
'status' => $response->status(),
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('WP-Label-Download fehlgeschlagen: '.$e->getMessage(), [
|
||||
'shopping_order_id' => $shopping_order->id,
|
||||
'wp_label_url' => $wp_label_url,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$shopping_order->save();
|
||||
}
|
||||
|
||||
public function delete(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
|
|
|
|||
|
|
@ -142,6 +142,12 @@ use Illuminate\Support\Carbon;
|
|||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShoppingOrder whereCancellationInvoice($value)
|
||||
*
|
||||
* @property string|null $wp_label_url
|
||||
* @property string|null $wp_label_path
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShoppingOrder whereWpLabelUrl($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShoppingOrder whereWpLabelPath($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ShoppingOrder extends Model
|
||||
|
|
@ -181,6 +187,8 @@ class ShoppingOrder extends Model
|
|||
'invoice_number',
|
||||
'txaction',
|
||||
'wp_invoice_path',
|
||||
'wp_label_url',
|
||||
'wp_label_path',
|
||||
'wp_notice',
|
||||
'mode',
|
||||
'shipped',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* AP-32 Brückenlösung (17.07.2026): DHL-Label wird im WooCommerce-Shop erstellt
|
||||
* (Plugin "DHL for WooCommerce") und bei der API-Übertragung mitgeliefert.
|
||||
* wp_label_url = Original-URL im Shop (Fallback), wp_label_path = lokal
|
||||
* gespeicherte Kopie (privater Storage, Auslieferung nur über Admin-Route).
|
||||
* Die Sendungsnummer landet in der bestehenden Spalte `tracking`.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('shopping_orders', function (Blueprint $table) {
|
||||
$table->string('wp_label_url', 500)->nullable()->after('wp_invoice_path');
|
||||
$table->string('wp_label_path', 255)->nullable()->after('wp_label_url');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('shopping_orders', function (Blueprint $table) {
|
||||
$table->dropColumn(['wp_label_url', 'wp_label_path']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# AP-32 Brückenlösung — DHL-Label aus WooCommerce übernehmen (17.07.2026)
|
||||
|
||||
## Kontext
|
||||
|
||||
Die vollständige DHL-API-Anbindung (AP-32) ist nachgelagert (Kundenentscheid 16.07.2026).
|
||||
Bis dahin: Das Label wird wie bisher **im WooCommerce-Shop** über das Plugin
|
||||
**„DHL for WooCommerce"** erstellt; bei der Übertragung der Bestellung ins
|
||||
Partnercenter kommen **Label-URL und Sendungsnummer mit**.
|
||||
|
||||
Das Partnercenter lädt die Label-PDF **einmalig herunter** und speichert sie im
|
||||
privaten Storage (`storage/app/wp-labels/`) — Gründe:
|
||||
|
||||
1. Die Shop-URL liegt unter `wp-content/uploads/woocommerce_dhl_label/` und ist
|
||||
**öffentlich erreichbar + erratbar** (Bestellnummer im Dateinamen, Kundenadresse
|
||||
auf dem Label). Die lokale Kopie wird nur über die eingeloggte Admin-Route
|
||||
ausgeliefert.
|
||||
2. Die Kopie bleibt verfügbar, auch wenn das Label im Shop gelöscht/storniert wird.
|
||||
|
||||
## Was das Partnercenter jetzt kann (Laravel-Seite, umgesetzt 17.07.2026)
|
||||
|
||||
- `POST /api/wp/order` akzeptiert zusätzlich (beides optional):
|
||||
- `wp_label_url` — öffentliche URL der Label-PDF im Shop
|
||||
- `wp_tracking_number` — Sendungsnummer (String **oder** Array bei Mehrpaket-Sendung)
|
||||
- **Neu** `POST /api/wp/label` (Passport-Auth wie die übrigen `wp/*`-Endpoints) —
|
||||
Label **nachreichen/aktualisieren**, wenn es erst nach der Übertragung erstellt wird:
|
||||
`{ wp_order_number, wp_label_url, wp_tracking_number }`
|
||||
- `POST /api/wp/status` liefert je Bestellung zusätzlich `tracking_number` zurück
|
||||
(für den Tracking-Link an den Endkunden im Shop).
|
||||
- Anzeige: Abwicklungs-Board + Bestell-Details zeigen „Label"-Download
|
||||
(lokale Kopie; Fallback: Shop-Link) und die Sendungsnummer.
|
||||
- Ein fehlgeschlagener Label-Download blockiert die Bestell-Übertragung **nie**
|
||||
(URL wird gespeichert, Warnung im Laravel-Log).
|
||||
|
||||
## Nötiges Update im Verbindungs-Plugin (WooCommerce-Seite)
|
||||
|
||||
Das DHL-Plugin speichert an der Bestellung das Meta-Feld
|
||||
`_pr_shipment_dhl_label_tracking` mit `label_url`, `label_path`, `tracking_number`
|
||||
(+ ggf. `return_label_url`). Zwei Ergänzungen im Connector:
|
||||
|
||||
### 1. Label bei der Bestell-Übertragung mitschicken
|
||||
|
||||
Im bestehenden Code, der den `POST /api/wp/order` aufbaut:
|
||||
|
||||
```php
|
||||
// DHL-Label (Plugin "DHL for WooCommerce") mitgeben, falls vorhanden
|
||||
$label_meta = $order->get_meta( '_pr_shipment_dhl_label_tracking' );
|
||||
if ( is_array( $label_meta ) && ! empty( $label_meta['label_url'] ) ) {
|
||||
$body['wp_label_url'] = $label_meta['label_url'];
|
||||
$body['wp_tracking_number'] = isset( $label_meta['tracking_number'] )
|
||||
? $label_meta['tracking_number'] // String oder Array — beides ok
|
||||
: null;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Label automatisch nachreichen (wenn es nach der Übertragung erstellt wird)
|
||||
|
||||
Das DHL-Plugin feuert beim Speichern eines Labels den Hook
|
||||
`pr_save_dhl_label_tracking` (das Meta-Feld ist zu diesem Zeitpunkt bereits
|
||||
gespeichert). Darauf lauschen und an den neuen Endpoint senden:
|
||||
|
||||
```php
|
||||
add_action( 'pr_save_dhl_label_tracking', 'gs_push_dhl_label_to_partnercenter', 10, 2 );
|
||||
|
||||
function gs_push_dhl_label_to_partnercenter( $order_id, $tracking_details ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( ! $order ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$label_meta = $order->get_meta( '_pr_shipment_dhl_label_tracking' );
|
||||
if ( ! is_array( $label_meta ) || empty( $label_meta['label_url'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Auth-Token/Basis-URL wie bei den bestehenden API-Aufrufen des Connectors
|
||||
wp_remote_post( gs_partnercenter_api_url( '/wp/label' ), array(
|
||||
'timeout' => 15,
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . gs_partnercenter_api_token(),
|
||||
'Accept' => 'application/json',
|
||||
),
|
||||
'body' => array(
|
||||
'wp_order_number' => $order->get_order_number(),
|
||||
'wp_label_url' => $label_meta['label_url'],
|
||||
'wp_tracking_number' => isset( $label_meta['tracking_number'] )
|
||||
? $label_meta['tracking_number']
|
||||
: null,
|
||||
),
|
||||
) );
|
||||
}
|
||||
```
|
||||
|
||||
Hinweise:
|
||||
|
||||
- `gs_partnercenter_api_url()` / `gs_partnercenter_api_token()` stehen für die
|
||||
im Connector bereits vorhandene Basis-URL- und Token-Logik — bitte die
|
||||
dortigen Funktionsnamen verwenden.
|
||||
- Der Hook feuert **je Sendungsnummer**; der Endpoint ist idempotent
|
||||
(überschreibt Label + Sendungsnummer), Mehrfachaufrufe sind unkritisch.
|
||||
- Ist die Bestellung noch nicht übertragen, antwortet `/wp/label` mit 400
|
||||
(„not found") — das ist ok: beim späteren `wp/order` kommt das Label ohnehin mit.
|
||||
- Wichtig: Die Label-Erstellung im Shop **vor** dem Übertragen ist der
|
||||
Normalfall (ein Request weniger); das Nachreichen ist das Sicherheitsnetz.
|
||||
|
||||
## Offene Punkte / bewusst nicht enthalten
|
||||
|
||||
- **Retourenlabel** (`return_label_url` im Meta): bei Bedarf analog übertragbar —
|
||||
bräuchte ein weiteres Feld an `shopping_orders`.
|
||||
- **Etiketten-Sammel-PDF** (AP-33c) und **Label-Erstellung im Partnercenter**
|
||||
(AP-32) bleiben nachgelagert; die neuen Spalten (`tracking`,
|
||||
`wp_label_path`) sind dafür wiederverwendbar.
|
||||
|
|
@ -39,7 +39,7 @@ V6.0 basiert auf einer **vollständigen Code-Verifikation vom 06.07.2026** (alle
|
|||
| AP-27 | Wareneingang: Charge zusammenführen | ✅ fertig 06.07.2026 (`7a5b5e0`) | Batch-Check-Endpoint + Vorbelegung, Chargen-Gruppierung Detail/Produktion, FEFO-`expandBatchLines()`, `StockEntryChargeMergeTest` (7 grün) |
|
||||
| AP-28 | Neue Produktions-Maske + Produktionsergebnis | ✅ fertig 06.07.2026 (`99a6c92`) | `production_outputs` + Backfill (Migration `2026_07_06_153907`), Buchung je Output-Produkt, Phasen-Maske + INCI-Liste + Ergebnis-Repeater, `ProductionOutputTest` (8 grün) |
|
||||
| AP-13 | Shop-Bestandsabzug bei Versand (inkl. Sets) + API-Bestellungen-Protokoll | ✅ **fertig 16.07.2026** (`211aeed`, `71d5591`) | `ProductStockService::syncSaleStock()`, `api_order_logs`, Menüpunkt „API-Bestellungen"; `SaleStockDeductionTest` (10), `ApiOrderLogTest` (8) |
|
||||
| AP-32 | Versand im System: DHL-Etiketten per API | ⏸ **nachgelagert** (Kundenentscheid 16.07.2026: erst Testphase) | Umsetzungsansatz in §4; Andockpunkt = Versand-Statuswechsel (wie AP-13) |
|
||||
| AP-32 | Versand im System: DHL-Etiketten per API | ⏸ **nachgelagert** (Kundenentscheid 16.07.2026: erst Testphase) — **Brückenlösung aktiv seit 17.07.2026** (Label aus dem Shop, §4/§6) | Umsetzungsansatz in §4; Andockpunkt = Versand-Statuswechsel (wie AP-13) |
|
||||
| AP-33a | Bestell-Abwicklung: Nav „Bestellungen" + Fulfillment-Board | ✅ **fertig 16.07.2026** (`a367d85`) | `OrderFulfillmentController`, `OrderStatusService`, `shipped_status_locked`; `OrderFulfillmentTest` (10) |
|
||||
| AP-33b | Sammel-Dokumente (Lieferscheine/Rechnungen als ein PDF) | ✅ **fertig 16.07.2026** (`1252c30`) | Checkbox-Auswahl im Board, `batchDocuments()` via `PDFMerger`; `OrderFulfillmentTest` (14) |
|
||||
| AP-33c | Etiketten-Stapel | ⏸ nachgelagert (mit AP-32) | Ansatz in §4 |
|
||||
|
|
@ -186,6 +186,8 @@ Der Warenkorb kommt also **per Knopfdruck** (Fulfillment-Übergabe), nicht autom
|
|||
|
||||
**Aufwand:** 3–5 Tage (ohne Retourenlabel). **Reihenfolge:** direkt nach oder gemeinsam mit AP-13 — gleicher Andockpunkt am Versand-Statuswechsel, gemeinsamer Test-Aufbau.
|
||||
|
||||
> ➡️ **Brückenlösung umgesetzt 17.07.2026** (Protokoll §6): Das im WooCommerce-Shop erstellte DHL-Label wird bei der API-Übertragung übernommen (lokale PDF-Kopie + Sendungsnummer an der Bestellung, „Label"-Download im Abwicklungs-Board und in den Bestell-Details, Nachreich-Endpoint `wp/label`, Sendungsnummer im `wp/status`-Poll). Damit ist der Versand komplett über das Partnercenter abwickelbar, bis die DHL-Klärungspunkte beantwortet sind. Connector-Update-Anleitung: `ap-32-brueckenloesung-connector-update.md`.
|
||||
|
||||
### Geklärte Punkte mit dem Kunden (beantwortet 06.07.2026)
|
||||
1. **Lagerort-Modell (AP-21):** ✅ **drei getrennte Stammdaten-Tabellen** (Raum/Regal/Fach) — wie empfohlen.
|
||||
2. **Chargen-Nr.-Datumsformat (AP-24):** ✅ `TP1` + `ddmmyy` bestätigt (Beispiel TP1150626 = 15.06.26). **Bei AP-24-Umsetzung in der Hinweise-Doku (AP-18) hinterlegen.**
|
||||
|
|
@ -270,7 +272,8 @@ Der Warenkorb kommt also **per Knopfdruck** (Fulfillment-Übergabe), nicht autom
|
|||
| 16.07.2026 | **AP-13-Nachtrag: Stichtags-Schutz für Alt-Bestellungen** | Beim Blick auf Bestellung 1687 (versendet 12/2025) entdeckt: erneutes Speichern des Versandstatus einer **vor AP-13 versendeten** Bestellung hätte rückwirkend vom heutigen Bestand abgezogen. `syncSaleStock()` überspringt jetzt Bestellungen mit `shipped_at` vor dem Stichtag `ProductStockService::SALE_STOCK_SINCE` (16.07.2026). Kundenentscheid dokumentiert: Self-Shipping ohne Abzug bestätigt. | `SaleStockDeductionTest` +1 (11 grün). Suite: **258 grün, 944 Assertions** |
|
||||
| 16.07.2026 | **AP-13 (API-Bestellungen — Protokoll + SKU-Fehlermarkierung, Kundenanforderung 16.07.)** | Neue Tabelle **`api_order_logs`** (Migration `2026_07_16_090000`) + Model `ApiOrderLog`; `ShoppingUserController` protokolliert **jede** `wp/order`-Übertragung — erfolgreiche inkl. Zuordnungsergebnis je Position (SKU, Produkt, Menge, Preis, API-Meldungen), abgelehnte mit Grund (Kunde unbekannt, Bestellung existiert bereits, Speicherfehler). Positionen ohne Produktzuordnung ⇒ `has_errors` + Zusammenfassung („x von y Positionen ohne Produktzuordnung"). **Neuer Menüpunkt „API-Bestellungen"** (copyreader, `admin/inventory/api-orders`): Liste (Limit 500/`?all=1`, Filter „nur Fehler"), rote Markierung, **Modal „Ansehen"** mit Positionstabelle (Meldungen deutsch übersetzt, Roh-Payload bei abgelehnten). Dazu SKU-Konsistenz (Plan §4 „mitzulösen"): Warnkarte **doppelte WP-Nummern** aktiver Produkte, aufklappbare Liste **aktive Produkte ohne WP-Nummer**. Commit `71d5591`. | Neuer `ApiOrderLogTest` (8 grün, Ende-zu-Ende über `wp/order` mit `Passport::actingAs`). Suite: **257 grün, 940 Assertions** |
|
||||
| 16.07.2026 | **Bugfix Logistik-Mail (Kundenmeldung)** | Fehlversand an die Rechnungsadresse: die Versand-Mail (`MailLogistic`) hängte den **Lieferschein nur bei White-Label-Bestellungen** an. Jetzt wird er **immer** mitgesendet (unabhängig von Logo/White-Label); fehlt die PDF-Datei auf der Platte, wird sie mit bestehender Rechnungsnummer regeneriert. Dazu **tote Duplikat-Klasse `app/Mail/MailLogitic.php` entfernt** (Tippfehler-Dateiname, definierte `MailLogistic` in einer Altversion ohne Lieferschein-Anhang — Kollisionsrisiko im Classmap-Autoload). Commit `7d77314`. **Nachtrag:** gleiche Regeneration auch für die **Rechnungs**-PDF — vorher brach `build()` bei fehlender Rechnungsdatei still ab (Mail ohne Anhänge trotz Erfolgsmeldung im UI). | Neuer `LogisticMailTest` (4 grün: Anhang ohne White-Label, Regeneration Lieferschein + Rechnung, Versand-Button). Volle Suite: **276 grün, 1016 Assertions** |
|
||||
| _offen_ | AP-32 | Versand im System: DHL-Etiketten per API — **neu aufgenommen + Ansatz dokumentiert (§4, 15.07.2026)**; Klärungspunkte (DHL-Konto, Versandprodukte, Druck-Setup) für nächste Anforderungsrunde notiert | – |
|
||||
| 17.07.2026 | **AP-32-Brückenlösung (DHL-Label aus WooCommerce übernehmen)** | Bis zur echten DHL-API-Anbindung: Das im Shop erstellte Label (Plugin „DHL for WooCommerce", Meta `_pr_shipment_dhl_label_tracking`) kommt **bei der Bestell-Übertragung mit**. `POST wp/order` akzeptiert `wp_label_url` + `wp_tracking_number` (String oder Array); das System lädt die Label-PDF als **lokale Kopie** in den privaten Storage (`wp-labels/`, Migration `2026_07_17_090000`: `shopping_orders.wp_label_url/wp_label_path`; Sendungsnummer in bestehender Spalte `tracking`) — Begründung: Shop-URL ist öffentlich + erratbar (Kundenadresse!), Kopie überlebt Löschung im Shop; Download-Fehler blockieren die Übertragung nie. **Neuer Endpoint `POST wp/label`** zum Nachreichen (Connector-Hook `pr_save_dhl_label_tracking`); `wp/status` liefert je Bestellung `tracking_number` zurück (Tracking-Link Endkunde). **Anzeige:** Abwicklungs-Board + Bestell-Details mit „Label"-Download (lokale Kopie über copyreader-Dokumentroute `type=label`, Fallback Shop-Link) + Sendungsnummer. Nötiges Connector-Update (WP-Seite) dokumentiert in `dev/product management /ap-32-brueckenloesung-connector-update.md`. Hinweise-Doku + Testleitfaden (§11) fortgeschrieben. Nebenbei: `pint.json` schließt `dev/` (Fremdcode-Referenzen) von der Formatierung aus. | Neuer `WpLabelTransferTest` (10 grün: Übertragung mit Label/Sendungsnr., Download-Fehler + Nicht-PDF blockieren nicht, Mehrpaket-Nummern, Nachreichen, unbekannte Nummer, Status-Poll, Board-Anzeige, Label-Download + 404). Volle Suite: **286 grün, 1052 Assertions** |
|
||||
| _offen_ | AP-32 | Versand im System: DHL-Etiketten per API — **neu aufgenommen + Ansatz dokumentiert (§4, 15.07.2026)**; Klärungspunkte (DHL-Konto, Versandprodukte, Druck-Setup) für nächste Anforderungsrunde notiert. **Zwischenstand 17.07.2026: Brückenlösung aktiv** (Label aus dem Shop, s. Protokollzeile) — deckt den Versand-Flow ab, bis die DHL-Klärungen da sind | – |
|
||||
| _offen_ | AP-14/15/16/17 | Audit-Trail, Blockrechte, 2FA, WaWi-Einstellungen | – |
|
||||
|
||||
> **✅ BLOCK ABGESCHLOSSEN (06.07.2026).** Alle fachlichen Pakete der Anforderungsrunde vom 12.06.2026 sind umgesetzt (OPT-1 … OPT-4, AP-20/21/23/24/27/28); die Quelldateien `docs/nächsten Anforderungen an die WaWi.md` und `docs/Todos.md` sind vollständig eingearbeitet. Suite: 201 Tests grün, 731 Assertions.
|
||||
|
|
|
|||
6
pint.json
Normal file
6
pint.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"preset": "laravel",
|
||||
"exclude": [
|
||||
"dev"
|
||||
]
|
||||
}
|
||||
|
|
@ -124,10 +124,22 @@
|
|||
im Tabellenkopf = alle sichtbaren) und mit **„Lieferscheine drucken"** bzw.
|
||||
**„+ Rechnungen"** ein einziges PDF für den Stapeldruck erzeugen — fehlende
|
||||
Dokumente werden dabei automatisch miterstellt (ohne E-Mail).
|
||||
- **DHL-Label aus dem Shop (Brückenlösung, neu 17.07.2026):** Wird das
|
||||
Versandetikett im WooCommerce-Shop erstellt (Plugin „DHL for WooCommerce")
|
||||
und die Bestellung danach übertragen, kommen **Label und Sendungsnummer
|
||||
mit**: Das System lädt eine **eigene Kopie der Label-PDF** herunter (bleibt
|
||||
verfügbar, auch wenn das Label im Shop gelöscht wird) und zeigt in der
|
||||
Abwicklung und den Bestell-Details einen **„Label"-Download** plus die
|
||||
Sendungsnummer. Wird das Label im Shop erst später (neu) erstellt, reicht
|
||||
der Shop es automatisch nach. Die Sendungsnummer wird über die
|
||||
Status-Abfrage auch an den Shop zurückgemeldet (Tracking-Link für den
|
||||
Endkunden). Die vollständige DHL-Anbindung im Partnercenter (Labels direkt
|
||||
hier erzeugen, AP-32) bleibt geplant.
|
||||
|
||||
### In Vorbereitung / geplant
|
||||
|
||||
- **Versand im System** (DHL-Etiketten per API, AP-32), **Audit-Trail**,
|
||||
- **Versand im System** (DHL-Etiketten per API, AP-32 — bis dahin gilt die
|
||||
Brückenlösung „DHL-Label aus dem Shop", s. o.), **Audit-Trail**,
|
||||
**blockbasierte Rechte**, **2FA für Admins**,
|
||||
**WaWi-Einstellungsseite** (Schwellwerte/Defaults zentral).
|
||||
- **Produktentwicklung:** Menüpunkt ist als Platzhalter angelegt – das genaue
|
||||
|
|
|
|||
|
|
@ -157,6 +157,20 @@
|
|||
|
||||
---
|
||||
|
||||
## 11. DHL-Label aus dem Shop (Brückenlösung, neu 17.07.2026)
|
||||
|
||||
**Wo:** Menü **[Bestellungen → Abwicklung](/admin/inventory/orders)** und Bestell-Details unter **Verkäufe**
|
||||
|
||||
**Neu:**
|
||||
- Wird das DHL-Etikett **im WooCommerce-Shop** erstellt und die Bestellung danach übertragen, speichert das System eine **eigene Kopie der Label-PDF** und die **Sendungsnummer** an der Bestellung.
|
||||
- In der Abwicklung erscheint bei der Bestellung ein **„Label"-Download** (neben dem externen Rechnungslink) samt Sendungsnummer; ebenso in den Bestell-Details.
|
||||
- Wird das Label im Shop erst **nach** der Übertragung erstellt (oder neu erstellt), reicht der Shop es automatisch nach — es taucht dann ohne weiteres Zutun in der Abwicklung auf.
|
||||
- Voraussetzung: das kleine Update des Verbindungs-Plugins im WooCommerce-Shop ist eingespielt.
|
||||
|
||||
**So testen:** Im Shop eine Testbestellung mit DHL-Label versehen → Bestellung ins Partnercenter übertragen → in der **Abwicklung** bei der Bestellung auf **„Label"** klicken (PDF öffnet sich) und die Sendungsnummer prüfen. Danach einmal andersherum: Bestellung zuerst übertragen, Label im Shop erst danach erstellen → Seite neu laden, der Label-Download muss nachträglich erscheinen.
|
||||
|
||||
---
|
||||
|
||||
## Hinweis am Rande
|
||||
|
||||
Die Listen **Einkauf & Wareneingang** und **Produktionen** zeigen aus Performance-Gründen die neuesten 500 Einträge; darüber erscheint ein Link **„Alle anzeigen"**. Offene Bestellungen sind immer vollständig gelistet.
|
||||
|
|
|
|||
|
|
@ -185,6 +185,19 @@
|
|||
@else
|
||||
<span class="text-muted small">{{ __('extern') }}</span>
|
||||
@endif
|
||||
{{-- AP-32 Brückenlösung: DHL-Label aus dem Shop (lokale Kopie, sonst Shop-Link) --}}
|
||||
@if ($order->wp_label_path)
|
||||
<a href="{{ route('admin.inventory.orders.document', [$order->id, 'label']) }}" target="_blank" title="{{ __('DHL-Label') }}" class="ml-1">
|
||||
<span class="fas fa-tag"></span> {{ __('Label') }}
|
||||
</a>
|
||||
@elseif ($order->wp_label_url)
|
||||
<a href="{{ $order->wp_label_url }}" target="_blank" rel="noopener" title="{{ __('DHL-Label (extern im Shop)') }}" class="ml-1">
|
||||
<span class="fas fa-tag"></span> {{ __('Label') }}
|
||||
</a>
|
||||
@endif
|
||||
@if ($order->tracking)
|
||||
<div class="small text-muted" title="{{ __('Sendungsnummer') }}">{{ $order->tracking }}</div>
|
||||
@endif
|
||||
@elseif (\App\Services\Invoice::isInvoice($order))
|
||||
<a href="{{ route('admin.inventory.orders.document', [$order->id, 'invoice']) }}" target="_blank" title="{{ __('Rechnung') }}" class="mr-1"><span class="far fa-file-alt"></span> {{ __('Re.') }}</a>
|
||||
<a href="{{ route('admin.inventory.orders.document', [$order->id, 'delivery']) }}" target="_blank" title="{{ __('Lieferschein') }}" class="mr-1"><span class="fas fa-dolly"></span> {{ __('LS') }}</a>
|
||||
|
|
|
|||
|
|
@ -618,11 +618,24 @@
|
|||
|
||||
@if ($shopping_order->txaction === 'extern')
|
||||
<div class="card-body">
|
||||
@if ($shopping_order->wp_invoice_path)
|
||||
@if ($shopping_order->wp_invoice_path || $shopping_order->wp_label_path || $shopping_order->wp_label_url)
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<a href="{{ $shopping_order->wp_invoice_path }}" target="_blank"
|
||||
class="btn btn-secondary btn-sm">Link zur Rechnung</a>
|
||||
@if ($shopping_order->wp_invoice_path)
|
||||
<a href="{{ $shopping_order->wp_invoice_path }}" target="_blank"
|
||||
class="btn btn-secondary btn-sm">Link zur Rechnung</a>
|
||||
@endif
|
||||
{{-- AP-32 Brückenlösung: DHL-Label aus dem WooCommerce-Shop --}}
|
||||
@if ($shopping_order->wp_label_path)
|
||||
<a href="{{ route('admin.inventory.orders.document', [$shopping_order->id, 'label']) }}" target="_blank"
|
||||
class="btn btn-secondary btn-sm"><span class="fas fa-tag"></span> DHL-Label</a>
|
||||
@elseif ($shopping_order->wp_label_url)
|
||||
<a href="{{ $shopping_order->wp_label_url }}" target="_blank" rel="noopener"
|
||||
class="btn btn-secondary btn-sm"><span class="fas fa-tag"></span> DHL-Label (Shop)</a>
|
||||
@endif
|
||||
@if ($shopping_order->tracking)
|
||||
<span class="text-muted small ml-2">Sendungsnummer: {{ $shopping_order->tracking }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Api\AuthController;
|
||||
use App\Http\Controllers\Api\ShoppingUserController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
@ -16,43 +15,44 @@ use App\Http\Controllers\Api\ShoppingUserController;
|
|||
|
|
||||
*/
|
||||
|
||||
//Route::post('login', [App\Http\Controllers\API\UserController::class, 'login']);
|
||||
//Route::post('register', [App\Http\Controllers\API\UserController::class, 'register']);
|
||||
// Route::post('login', [App\Http\Controllers\API\UserController::class, 'login']);
|
||||
// Route::post('register', [App\Http\Controllers\API\UserController::class, 'register']);
|
||||
|
||||
//Route::post('login', [App\Http\Controllers\Api\APIController::class, 'login']);
|
||||
// Route::post('login', [App\Http\Controllers\Api\APIController::class, 'login']);
|
||||
|
||||
//Route::get('/payment/status', [App\Http\Controllers\Api\PayoneController::class, 'paymentStatus'])->name('api.payment_status');
|
||||
//Route::post('/payment/status', [App\Http\Controllers\Api\PayoneController::class, 'paymentStatus'])->name('api.payment_status');
|
||||
//Route::post('login', [App\Http\Controllers\Api\AuthController::class, 'login']);
|
||||
// Route::get('/payment/status', [App\Http\Controllers\Api\PayoneController::class, 'paymentStatus'])->name('api.payment_status');
|
||||
// Route::post('/payment/status', [App\Http\Controllers\Api\PayoneController::class, 'paymentStatus'])->name('api.payment_status');
|
||||
// Route::post('login', [App\Http\Controllers\Api\AuthController::class, 'login']);
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'auth'
|
||||
'prefix' => 'auth',
|
||||
], function () {
|
||||
Route::post('login', [AuthController::class, 'login']);
|
||||
//Route::post('signup', [AuthController::class, 'signup']);
|
||||
// Route::post('signup', [AuthController::class, 'signup']);
|
||||
Route::get('test', [AuthController::class, 'test']);
|
||||
|
||||
Route::group([
|
||||
'middleware' => 'auth:api'
|
||||
], function() {
|
||||
|
||||
Route::group([
|
||||
'middleware' => 'auth:api',
|
||||
], function () {
|
||||
Route::post('logout', [AuthController::class, 'logout']);
|
||||
Route::post('checked', [AuthController::class, 'checked']);
|
||||
});
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'wp'
|
||||
'prefix' => 'wp',
|
||||
], function () {
|
||||
Route::group([
|
||||
'middleware' => 'auth:api'
|
||||
], function() {
|
||||
'middleware' => 'auth:api',
|
||||
], function () {
|
||||
Route::post('show', [ShoppingUserController::class, 'show']);
|
||||
Route::post('store', [ShoppingUserController::class, 'store']);
|
||||
Route::post('update', [ShoppingUserController::class, 'update']);
|
||||
Route::post('order', [ShoppingUserController::class, 'order']);
|
||||
Route::post('label', [ShoppingUserController::class, 'label']);
|
||||
Route::post('status', [ShoppingUserController::class, 'status']);
|
||||
Route::post('delete', [ShoppingUserController::class, 'delete']);
|
||||
Route::post('cancel', [ShoppingUserController::class, 'cancel']);
|
||||
Route::post('open', [ShoppingUserController::class, 'open']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
269
tests/Feature/WpLabelTransferTest.php
Normal file
269
tests/Feature/WpLabelTransferTest.php
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Product;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAccount;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Laravel\Passport\Passport;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function wplabelCountry(): Country
|
||||
{
|
||||
return Country::query()->where('code', 'DE')->first() ?? Country::query()->forceCreate([
|
||||
'code' => 'DE',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Germanija',
|
||||
]);
|
||||
}
|
||||
|
||||
function wplabelMakeUser(int $adminLevel = 7): User
|
||||
{
|
||||
$user = User::query()->create([
|
||||
'email' => uniqid('wplabel_', true).'@test.example',
|
||||
'password' => bcrypt('password'),
|
||||
]);
|
||||
$account = UserAccount::query()->create([
|
||||
'm_first_name' => 'Label',
|
||||
'm_last_name' => 'Partner',
|
||||
'country_id' => wplabelCountry()->id,
|
||||
]);
|
||||
$user->forceFill([
|
||||
'admin' => $adminLevel,
|
||||
'confirmed' => true,
|
||||
'active' => true,
|
||||
'wizard' => 100,
|
||||
'blocked' => false,
|
||||
'account_id' => $account->id,
|
||||
])->save();
|
||||
|
||||
return $user->fresh();
|
||||
}
|
||||
|
||||
function wplabelMakeShoppingUser(int $wpOrderNumber): ShoppingUser
|
||||
{
|
||||
$country = wplabelCountry();
|
||||
$shipping = Shipping::query()->forceCreate(['name' => 'Standard']);
|
||||
ShippingCountry::query()->forceCreate([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
return ShoppingUser::query()->forceCreate([
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_firstname' => 'Label',
|
||||
'billing_lastname' => 'Kunde',
|
||||
'wp_order_number' => $wpOrderNumber,
|
||||
'member_id' => wplabelMakeUser(0)->id,
|
||||
'auth_user_id' => wplabelMakeUser(0)->id,
|
||||
]);
|
||||
}
|
||||
|
||||
function wplabelMakeProduct(int $wpNumber): Product
|
||||
{
|
||||
return Product::query()->create([
|
||||
'name' => 'Label Creme '.$wpNumber,
|
||||
'title' => 'Label Creme '.$wpNumber,
|
||||
'active' => true,
|
||||
'is_set' => false,
|
||||
'wp_number' => $wpNumber,
|
||||
'price' => '19,90',
|
||||
'tax' => '19',
|
||||
'weight' => 100,
|
||||
]);
|
||||
}
|
||||
|
||||
function wplabelTransfer(int $wpOrderNumber, array $extra = [])
|
||||
{
|
||||
return test()->postJson('https://api.'.config('app.api_domain').'/wp/order', array_merge([
|
||||
'wp_order_number' => $wpOrderNumber,
|
||||
'wp_order' => [['article' => 100, 'qty' => 1, 'price' => 1990]],
|
||||
], $extra));
|
||||
}
|
||||
|
||||
test('uebertragung mit label-url speichert pdf-kopie und sendungsnummer', function () {
|
||||
Mail::fake();
|
||||
Storage::fake('local');
|
||||
Http::fake(['*' => Http::response('%PDF-1.4 label-inhalt', 200)]);
|
||||
Passport::actingAs(wplabelMakeUser());
|
||||
wplabelMakeProduct(100);
|
||||
wplabelMakeShoppingUser(5711);
|
||||
|
||||
$response = wplabelTransfer(5711, [
|
||||
'wp_label_url' => 'https://shop.example/wp-content/uploads/woocommerce_dhl_label/dhl-label-5711.pdf',
|
||||
'wp_tracking_number' => '00340434161094000001',
|
||||
]);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('success', true)
|
||||
->assertJsonPath('data.wp_label_stored', true)
|
||||
->assertJsonPath('data.wp_tracking_number', '00340434161094000001');
|
||||
|
||||
$order = ShoppingOrder::query()->sole();
|
||||
expect($order->tracking)->toBe('00340434161094000001')
|
||||
->and($order->wp_label_url)->toContain('dhl-label-5711.pdf')
|
||||
->and($order->wp_label_path)->toBe('wp-labels/label-'.$order->id.'.pdf');
|
||||
Storage::disk('local')->assertExists($order->wp_label_path);
|
||||
});
|
||||
|
||||
test('fehlgeschlagener label-download blockiert die uebertragung nicht', function () {
|
||||
Mail::fake();
|
||||
Storage::fake('local');
|
||||
Http::fake(['*' => Http::response('Serverfehler', 500)]);
|
||||
Passport::actingAs(wplabelMakeUser());
|
||||
wplabelMakeProduct(100);
|
||||
wplabelMakeShoppingUser(5712);
|
||||
|
||||
wplabelTransfer(5712, [
|
||||
'wp_label_url' => 'https://shop.example/label.pdf',
|
||||
'wp_tracking_number' => '00340434161094000002',
|
||||
])->assertOk()->assertJsonPath('data.wp_label_stored', false);
|
||||
|
||||
$order = ShoppingOrder::query()->sole();
|
||||
expect($order->wp_label_path)->toBeNull()
|
||||
->and($order->wp_label_url)->toBe('https://shop.example/label.pdf')
|
||||
->and($order->tracking)->toBe('00340434161094000002');
|
||||
});
|
||||
|
||||
test('antwort ohne pdf-inhalt wird nicht als label gespeichert', function () {
|
||||
Mail::fake();
|
||||
Storage::fake('local');
|
||||
Http::fake(['*' => Http::response('<html>Login</html>', 200)]);
|
||||
Passport::actingAs(wplabelMakeUser());
|
||||
wplabelMakeProduct(100);
|
||||
wplabelMakeShoppingUser(5713);
|
||||
|
||||
wplabelTransfer(5713, ['wp_label_url' => 'https://shop.example/label.pdf'])
|
||||
->assertOk()->assertJsonPath('data.wp_label_stored', false);
|
||||
|
||||
expect(ShoppingOrder::query()->sole()->wp_label_path)->toBeNull();
|
||||
});
|
||||
|
||||
test('mehrere sendungsnummern werden zusammengefasst', function () {
|
||||
Mail::fake();
|
||||
Storage::fake('local');
|
||||
Passport::actingAs(wplabelMakeUser());
|
||||
wplabelMakeProduct(100);
|
||||
wplabelMakeShoppingUser(5714);
|
||||
|
||||
wplabelTransfer(5714, ['wp_tracking_number' => ['00340001', '00340002']])->assertOk();
|
||||
|
||||
expect(ShoppingOrder::query()->sole()->tracking)->toBe('00340001, 00340002');
|
||||
});
|
||||
|
||||
test('label kann per wp label nachgereicht werden', function () {
|
||||
Mail::fake();
|
||||
Storage::fake('local');
|
||||
Http::fake(['*' => Http::response('%PDF-1.4 nachgereicht', 200)]);
|
||||
Passport::actingAs(wplabelMakeUser());
|
||||
wplabelMakeProduct(100);
|
||||
wplabelMakeShoppingUser(5715);
|
||||
|
||||
wplabelTransfer(5715)->assertOk();
|
||||
$order = ShoppingOrder::query()->sole();
|
||||
expect($order->wp_label_path)->toBeNull();
|
||||
|
||||
test()->postJson('https://api.'.config('app.api_domain').'/wp/label', [
|
||||
'wp_order_number' => 5715,
|
||||
'wp_label_url' => 'https://shop.example/label-5715.pdf',
|
||||
'wp_tracking_number' => '00340434161094000005',
|
||||
])->assertOk()->assertJsonPath('data.wp_label_stored', true);
|
||||
|
||||
$order->refresh();
|
||||
expect($order->wp_label_path)->toBe('wp-labels/label-'.$order->id.'.pdf')
|
||||
->and($order->tracking)->toBe('00340434161094000005');
|
||||
Storage::disk('local')->assertExists($order->wp_label_path);
|
||||
});
|
||||
|
||||
test('wp label mit unbekannter bestellnummer wird abgelehnt', function () {
|
||||
Passport::actingAs(wplabelMakeUser());
|
||||
|
||||
test()->postJson('https://api.'.config('app.api_domain').'/wp/label', [
|
||||
'wp_order_number' => 99999,
|
||||
'wp_label_url' => 'https://shop.example/label.pdf',
|
||||
])->assertStatus(400);
|
||||
});
|
||||
|
||||
test('status-poll liefert die sendungsnummer zurueck', function () {
|
||||
Mail::fake();
|
||||
Storage::fake('local');
|
||||
Passport::actingAs(wplabelMakeUser());
|
||||
wplabelMakeProduct(100);
|
||||
wplabelMakeShoppingUser(5716);
|
||||
|
||||
wplabelTransfer(5716, ['wp_tracking_number' => '00340434161094000006'])->assertOk();
|
||||
|
||||
test()->postJson('https://api.'.config('app.api_domain').'/wp/status', [
|
||||
'wp_order_numbers' => [5716],
|
||||
])->assertOk()->assertJsonPath('data.0.tracking_number', '00340434161094000006');
|
||||
});
|
||||
|
||||
test('abwicklungs-board zeigt label-download und sendungsnummer', function () {
|
||||
Storage::fake('local');
|
||||
$shoppingUser = wplabelMakeShoppingUser(5717);
|
||||
$order = ShoppingOrder::query()->forceCreate([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $shoppingUser->auth_user_id,
|
||||
'country_id' => ShippingCountry::query()->first()->id,
|
||||
'shipped' => 0,
|
||||
'txaction' => 'extern',
|
||||
'paid' => true,
|
||||
'wp_label_path' => 'wp-labels/label-1.pdf',
|
||||
'tracking' => '00340434161094000007',
|
||||
]);
|
||||
|
||||
$this->actingAs(wplabelMakeUser(1))
|
||||
->get(route('admin.inventory.orders.index'))
|
||||
->assertOk()
|
||||
->assertSee(route('admin.inventory.orders.document', [$order->id, 'label']), false)
|
||||
->assertSee('00340434161094000007');
|
||||
});
|
||||
|
||||
test('label-download liefert die lokale pdf-kopie aus', function () {
|
||||
Storage::fake('local');
|
||||
$shoppingUser = wplabelMakeShoppingUser(5718);
|
||||
$order = ShoppingOrder::query()->forceCreate([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $shoppingUser->auth_user_id,
|
||||
'country_id' => ShippingCountry::query()->first()->id,
|
||||
'shipped' => 0,
|
||||
'txaction' => 'extern',
|
||||
'wp_label_path' => 'wp-labels/label-test.pdf',
|
||||
]);
|
||||
Storage::disk('local')->put('wp-labels/label-test.pdf', '%PDF-1.4 test');
|
||||
|
||||
$this->actingAs(wplabelMakeUser(1))
|
||||
->get(route('admin.inventory.orders.document', [$order->id, 'label']))
|
||||
->assertOk()
|
||||
->assertHeader('Content-Type', 'application/pdf');
|
||||
});
|
||||
|
||||
test('label-download ohne gespeichertes label liefert 404', function () {
|
||||
Storage::fake('local');
|
||||
$shoppingUser = wplabelMakeShoppingUser(5719);
|
||||
$order = ShoppingOrder::query()->forceCreate([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $shoppingUser->auth_user_id,
|
||||
'country_id' => ShippingCountry::query()->first()->id,
|
||||
'shipped' => 0,
|
||||
'txaction' => 'extern',
|
||||
]);
|
||||
|
||||
$this->actingAs(wplabelMakeUser(1))
|
||||
->get(route('admin.inventory.orders.document', [$order->id, 'label']))
|
||||
->assertNotFound();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue