Warenwirtschaft: AP-13 API-Bestellungen — Protokoll mit SKU-Fehlermarkierung
Neuer Navigationspunkt „API-Bestellungen": jede wp/order-Übertragung wird protokolliert (api_order_logs) — auch abgelehnte (Kunde unbekannt, Bestellung existiert bereits, Speicherfehler). Positionen ohne Produktzuordnung (unbekannte WP-Nummer) markieren den Eintrag rot; das Modal zeigt den Inhalt der Übertragung je Position (SKU, Produkt, Menge, Preis, Meldung übersetzt). Dazu SKU-Konsistenzprüfung auf der Seite: doppelte WP-Nummern aktiver Produkte (Warnung) und aktive Produkte ohne WP-Nummer (aufklappbar). Behebt die stille Lücke, dass „article not found" nur in der API-Antwort unterging und die Position kommentarlos in Bestellung und Bestandsabzug fehlte (Plan §4). ShoppingUserController per Pint formatiert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
211aeed384
commit
71d5591e7b
8 changed files with 859 additions and 170 deletions
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Inventory;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ApiOrderLog;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* AP-13: Übersicht der über die Partnercenter-API eingegangenen Bestell-Übertragungen.
|
||||
* Einträge mit fehlgeschlagener Produktzuordnung (unbekannte SKU) sind markiert;
|
||||
* das Modal zeigt den Inhalt der Übertragung je Position.
|
||||
*/
|
||||
class ApiOrderLogController extends Controller
|
||||
{
|
||||
private const INDEX_LIMIT = 500;
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$limit = $request->query('all') ? null : self::INDEX_LIMIT;
|
||||
|
||||
$logs = ApiOrderLog::query()
|
||||
->with(['shoppingOrder', 'shoppingUser'])
|
||||
->orderByDesc('id')
|
||||
->when($limit !== null, fn ($query) => $query->limit($limit))
|
||||
->get();
|
||||
|
||||
$errorCount = ApiOrderLog::query()->where('has_errors', true)->count();
|
||||
|
||||
// SKU-Konsistenz (Plan §4): doppelte WP-Nummern brechen die Zuordnung,
|
||||
// aktive Produkte ohne WP-Nummer können nie per API zugeordnet werden.
|
||||
$duplicateNumbers = Product::query()
|
||||
->where('active', true)
|
||||
->whereNotNull('wp_number')
|
||||
->where('wp_number', '!=', 0)
|
||||
->selectRaw('wp_number, COUNT(*) as sku_count')
|
||||
->groupBy('wp_number')
|
||||
->havingRaw('COUNT(*) > 1')
|
||||
->pluck('wp_number');
|
||||
|
||||
$duplicateSkuProducts = $duplicateNumbers->isEmpty()
|
||||
? collect()
|
||||
: Product::query()
|
||||
->where('active', true)
|
||||
->whereIn('wp_number', $duplicateNumbers)
|
||||
->orderBy('wp_number')
|
||||
->orderBy('name')
|
||||
->get(['id', 'name', 'wp_number']);
|
||||
|
||||
$missingSkuProducts = Product::query()
|
||||
->where('active', true)
|
||||
->where(fn ($query) => $query->whereNull('wp_number')->orWhere('wp_number', 0))
|
||||
->orderBy('name')
|
||||
->get(['id', 'name']);
|
||||
|
||||
return view('admin.inventory.api-orders.index', [
|
||||
'logs' => $logs,
|
||||
'listLimit' => $limit,
|
||||
'errorCount' => $errorCount,
|
||||
'duplicateSkuProducts' => $duplicateSkuProducts,
|
||||
'missingSkuProducts' => $missingSkuProducts,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Mail\MailCheckout;
|
||||
use App\Models\ApiOrderLog;
|
||||
use App\Models\Country;
|
||||
use App\Models\Product;
|
||||
use App\Models\ShippingCountry;
|
||||
|
|
@ -10,30 +12,27 @@ use App\Models\ShoppingOrder;
|
|||
use App\Models\ShoppingOrderItem;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Services\CustomerPriority;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Shop;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PHPUnit\Framework\Constraint\Count;
|
||||
use Yard;
|
||||
|
||||
|
||||
class ShoppingUserController extends Controller
|
||||
{
|
||||
|
||||
//protected static API_MAIL = 'api.thomas.krummel@gmail.com';
|
||||
//protected static API_PASS = 'UF(Q<9knap!ev3vH?5~!b8DP';
|
||||
//URL api.gruene-seele.bio
|
||||
|
||||
// protected static API_MAIL = 'api.thomas.krummel@gmail.com';
|
||||
// protected static API_PASS = 'UF(Q<9knap!ev3vH?5~!b8DP';
|
||||
// URL api.gruene-seele.bio
|
||||
|
||||
protected $successStatus = 200;
|
||||
protected $member_id = 2; //thomas.krummel@gmail.com
|
||||
|
||||
protected $member_id = 2; // thomas.krummel@gmail.com
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* wp_order_numbers[1234, 1234]
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @param Request $request
|
||||
* wp_order_numbers[1234, 1234]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function status(Request $request)
|
||||
{
|
||||
|
|
@ -42,24 +41,23 @@ class ShoppingUserController extends Controller
|
|||
'wp_order_numbers' => 'required',
|
||||
]);
|
||||
|
||||
if(!is_array($request->wp_order_numbers)){
|
||||
if (! is_array($request->wp_order_numbers)) {
|
||||
$wp_order_numbers = json_decode($request->wp_order_numbers);
|
||||
|
||||
}else{
|
||||
} else {
|
||||
$wp_order_numbers = $request->wp_order_numbers;
|
||||
}
|
||||
|
||||
|
||||
if(!$wp_order_numbers || !is_array($wp_order_numbers)){
|
||||
if (! $wp_order_numbers || ! is_array($wp_order_numbers)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'wp_order_numbers need as json [1234, 1234] ',
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
|
||||
$status = [];
|
||||
foreach ($wp_order_numbers as $wp_order_number){
|
||||
foreach ($wp_order_numbers as $wp_order_number) {
|
||||
$shopping_user = ShoppingUser::where('wp_order_number', '=', $wp_order_number)->first();
|
||||
$status[] = [
|
||||
'wp_order_number' => $wp_order_number,
|
||||
|
|
@ -72,15 +70,15 @@ class ShoppingUserController extends Controller
|
|||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $status,
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 200);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* wp_order_number [1234]
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @param Request $request
|
||||
* wp_order_number [1234]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function cancel(Request $request)
|
||||
{
|
||||
|
|
@ -88,31 +86,31 @@ class ShoppingUserController extends Controller
|
|||
'wp_order_number' => 'required|int',
|
||||
]);
|
||||
$shopping_user = ShoppingUser::where('wp_order_number', '=', $request->wp_order_number)->first();
|
||||
if (!$shopping_user) {
|
||||
if (! $shopping_user) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' not found',
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' not found',
|
||||
'order' => false,
|
||||
'status' => false,
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
if(!$shopping_user->shopping_order){
|
||||
if (! $shopping_user->shopping_order) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' has no order',
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' has no order',
|
||||
'order' => false,
|
||||
'status' => $shopping_user->getAPIShippedType(),
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
if($shopping_user->shopping_order->shipped > 0){
|
||||
if ($shopping_user->shopping_order->shipped > 0) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' can not cancel',
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' can not cancel',
|
||||
'order' => true,
|
||||
'status' => $shopping_user->getAPIShippedType(),
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
|
||||
|
|
@ -121,19 +119,18 @@ class ShoppingUserController extends Controller
|
|||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' is cancel',
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' is cancel',
|
||||
'order' => true,
|
||||
'status' => $shopping_user->getAPIShippedType(),
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* wp_order_number [1234]
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @param Request $request
|
||||
* wp_order_number [1234]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function open(Request $request)
|
||||
{
|
||||
|
|
@ -141,31 +138,31 @@ class ShoppingUserController extends Controller
|
|||
'wp_order_number' => 'required|int',
|
||||
]);
|
||||
$shopping_user = ShoppingUser::where('wp_order_number', '=', $request->wp_order_number)->first();
|
||||
if (!$shopping_user) {
|
||||
if (! $shopping_user) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' not found',
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' not found',
|
||||
'order' => false,
|
||||
'status' => false,
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
if(!$shopping_user->shopping_order){
|
||||
if (! $shopping_user->shopping_order) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' has no order',
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' has no order',
|
||||
'order' => false,
|
||||
'status' => $shopping_user->getAPIShippedType(),
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
if($shopping_user->shopping_order->shipped !== 10){
|
||||
if ($shopping_user->shopping_order->shipped !== 10) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' can not open',
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' can not open',
|
||||
'order' => true,
|
||||
'status' => $shopping_user->getAPIShippedType(),
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
|
||||
|
|
@ -174,44 +171,43 @@ class ShoppingUserController extends Controller
|
|||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' is open',
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' is open',
|
||||
'order' => true,
|
||||
'status' => $shopping_user->getAPIShippedType(),
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* wp_order_numbers [1234, 1234]
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @param Request $request
|
||||
* wp_order_numbers [1234, 1234]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
//$this->member_id = auth()->user()->m_sponsor;
|
||||
// $this->member_id = auth()->user()->m_sponsor;
|
||||
$request->validate([
|
||||
'wp_order_numbers' => 'required',
|
||||
]);
|
||||
|
||||
if(!is_array($request->wp_order_numbers)){
|
||||
if (! is_array($request->wp_order_numbers)) {
|
||||
$wp_order_numbers = json_decode($request->wp_order_numbers);
|
||||
|
||||
}else{
|
||||
} else {
|
||||
$wp_order_numbers = $request->wp_order_numbers;
|
||||
}
|
||||
|
||||
if(!$wp_order_numbers || !is_array($wp_order_numbers)){
|
||||
if (! $wp_order_numbers || ! is_array($wp_order_numbers)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'wp_order_numbers need as json [1234, 1234] ',
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
$data = [];
|
||||
|
||||
foreach ($wp_order_numbers as $wp_order_number){
|
||||
foreach ($wp_order_numbers as $wp_order_number) {
|
||||
$shopping_user = ShoppingUser::where('wp_order_number', '=', $wp_order_number)->first();
|
||||
$user = false;
|
||||
$order = false;
|
||||
|
|
@ -222,22 +218,22 @@ class ShoppingUserController extends Controller
|
|||
$data[] = [
|
||||
'wp_order_number' => $wp_order_number,
|
||||
'user' => $user,
|
||||
'order' => $order,
|
||||
'order' => $order,
|
||||
'customer_number' => $shopping_user ? $shopping_user->number : false,
|
||||
'member_email' => ($shopping_user && $shopping_user->member) ? $shopping_user->member->email : false,
|
||||
'status' => $shopping_user ? $shopping_user->getAPIShippedType() : false, ];
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 200);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
|
|
@ -264,11 +260,12 @@ class ShoppingUserController extends Controller
|
|||
|
||||
$shopping_user = ShoppingUser::create($data);
|
||||
|
||||
//Kundenhoheit prüfen
|
||||
// Kundenhoheit prüfen
|
||||
$priority = CustomerPriority::checkOne($shopping_user, true, false, true);
|
||||
\App\Services\Shop::newUserOrder($shopping_user->number);
|
||||
//exists //like //update
|
||||
Shop::newUserOrder($shopping_user->number);
|
||||
// exists //like //update
|
||||
$user = $this->prepareForShow($shopping_user);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
|
|
@ -278,14 +275,13 @@ class ShoppingUserController extends Controller
|
|||
'customer_number' => $shopping_user->number,
|
||||
'member_email' => ($shopping_user && $shopping_user->member) ? $shopping_user->member->email : false,
|
||||
],
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 200);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
|
|
@ -293,40 +289,42 @@ class ShoppingUserController extends Controller
|
|||
'wp_order_number' => 'required|int',
|
||||
]);
|
||||
$shopping_user = ShoppingUser::where('wp_order_number', '=', $request->wp_order_number)->first();
|
||||
if (!$shopping_user) {
|
||||
if (! $shopping_user) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' not found',
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' not found',
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
|
||||
$data = $this->prepareForUpdate($request->all());
|
||||
//Kundenhoheit prüfen
|
||||
// Kundenhoheit prüfen
|
||||
$priority = CustomerPriority::checkChangeOne($shopping_user, $data, true);
|
||||
$updated = $shopping_user->fill($data)->save();
|
||||
\App\Services\Shop::newUserOrder($shopping_user->number);
|
||||
Shop::newUserOrder($shopping_user->number);
|
||||
|
||||
if ($updated){
|
||||
if ($updated) {
|
||||
$user = $this->prepareForShow($shopping_user);
|
||||
$order = $this->prepareForShowOrder($shopping_user->shopping_order);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'wp_order_number' => $shopping_user->wp_order_number,
|
||||
'user' => $user,
|
||||
'order' => $order,
|
||||
'order' => $order,
|
||||
'customer_priority' => $priority,
|
||||
'customer_number' => $shopping_user ? $shopping_user->number : false,
|
||||
'member_email' => ($shopping_user && $shopping_user->member) ? $shopping_user->member->email : false,
|
||||
'status' => $shopping_user ? $shopping_user->getAPIShippedType() : false,
|
||||
],
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry could not be updated'
|
||||
'message' => 'Entry could not be updated',
|
||||
], 500);
|
||||
}
|
||||
|
||||
|
|
@ -337,27 +335,31 @@ class ShoppingUserController extends Controller
|
|||
'wp_order' => 'required',
|
||||
]);
|
||||
|
||||
|
||||
$shopping_user = ShoppingUser::where('wp_order_number', '=', $request->wp_order_number)->first();
|
||||
if (!$shopping_user) {
|
||||
if (! $shopping_user) {
|
||||
// AP-13: jede eingehende Bestell-Übertragung protokollieren — auch abgelehnte
|
||||
ApiOrderLog::recordFailure($request->wp_order_number, null, $request->wp_order, __('WP-Bestellnummer unbekannt — keine Kundendaten übertragen'));
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' not found',
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' not found',
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
|
||||
if($shopping_user->shopping_order){
|
||||
if ($shopping_user->shopping_order) {
|
||||
ApiOrderLog::recordFailure($request->wp_order_number, $shopping_user, $request->wp_order, __('Bestellung existiert bereits — Übertragung abgelehnt'));
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Order with wp_order_number ' . $request->wp_order_number . ' exists',
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'message' => 'Order with wp_order_number '.$request->wp_order_number.' exists',
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
if(!is_array($request->wp_order)){
|
||||
if (! is_array($request->wp_order)) {
|
||||
$wp_order = json_decode($request->wp_order);
|
||||
|
||||
}else{
|
||||
} else {
|
||||
$wp_order = $request->wp_order;
|
||||
}
|
||||
|
||||
|
|
@ -372,9 +374,12 @@ class ShoppingUserController extends Controller
|
|||
|
||||
$wp_order = $this->prepareOrder($wp_order, $shopping_user, $wp_invoice_path, $wp_notice);
|
||||
|
||||
if ($wp_order){
|
||||
if ($wp_order) {
|
||||
// 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);
|
||||
$order = $this->prepareForShowOrder($shopping_user->shopping_order);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
|
|
@ -389,12 +394,14 @@ class ShoppingUserController extends Controller
|
|||
'member_email' => ($shopping_user && $shopping_user->member) ? $shopping_user->member->email : false,
|
||||
'status' => $shopping_user->getAPIShippedType(),
|
||||
],
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 200);
|
||||
}
|
||||
ApiOrderLog::recordFailure($request->wp_order_number, $shopping_user, $request->wp_order, __('Bestellung konnte nicht gespeichert werden'));
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Order could not be stored'
|
||||
'message' => 'Order could not be stored',
|
||||
], 500);
|
||||
}
|
||||
|
||||
|
|
@ -404,16 +411,16 @@ class ShoppingUserController extends Controller
|
|||
'wp_order_number' => 'required|int',
|
||||
]);
|
||||
$shopping_user = ShoppingUser::where('wp_order_number', '=', $request->wp_order_number)->where('mode', '=', 'dev')->first();
|
||||
if (!$shopping_user) {
|
||||
if (! $shopping_user) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry with wp_order_number ' . $request->wp_order_number . ' not found or mode != dev',
|
||||
'time' => Carbon::now()->toDateTimeString()
|
||||
'message' => 'Entry with wp_order_number '.$request->wp_order_number.' not found or mode != dev',
|
||||
'time' => Carbon::now()->toDateTimeString(),
|
||||
], 400);
|
||||
}
|
||||
$shopping_order = $shopping_user->shopping_order;
|
||||
if($shopping_order){
|
||||
foreach ($shopping_order->shopping_order_items as $shopping_order_item){
|
||||
if ($shopping_order) {
|
||||
foreach ($shopping_order->shopping_order_items as $shopping_order_item) {
|
||||
$shopping_order_item->delete();
|
||||
}
|
||||
$shopping_order->delete();
|
||||
|
|
@ -422,68 +429,72 @@ class ShoppingUserController extends Controller
|
|||
$shopping_user->save();
|
||||
if ($shopping_user->delete()) {
|
||||
return response()->json([
|
||||
'success' => true
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry could not be deleted'
|
||||
'message' => 'Entry could not be deleted',
|
||||
], 500);
|
||||
}
|
||||
|
||||
private function prepareForShow($shopping_user){
|
||||
private function prepareForShow($shopping_user)
|
||||
{
|
||||
|
||||
if(!$shopping_user){
|
||||
if (! $shopping_user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$shopping_user_data = $shopping_user->toArray();
|
||||
$needs = ['wp_order_number', 'wp_order_date', 'billing_company', 'billing_firstname', 'billing_lastname', 'billing_address', 'billing_address_2', 'billing_zipcode', 'billing_city', 'billing_phone', 'billing_email',
|
||||
'same_as_billing', 'shipping_company', 'shipping_firstname', 'shipping_lastname', 'shipping_address', 'shipping_address_2', 'shipping_zipcode', 'shipping_city', 'shipping_phone',
|
||||
'created_at', 'updated_at', 'user_deleted_at']; //'has_buyed', 'subscribed',
|
||||
'created_at', 'updated_at', 'user_deleted_at']; // 'has_buyed', 'subscribed',
|
||||
|
||||
//$salutation = array('mr' => 1, 'ms' => 2);
|
||||
// $salutation = array('mr' => 1, 'ms' => 2);
|
||||
$ret = [];
|
||||
foreach ($shopping_user_data as $key=>$value){
|
||||
foreach ($shopping_user_data as $key => $value) {
|
||||
|
||||
if($key === 'billing_country_id'){
|
||||
if ($key === 'billing_country_id') {
|
||||
$ret['billing_country_code'] = $shopping_user->billing_country_id ? $shopping_user->billing_country->code : null;
|
||||
}
|
||||
if($key === 'shipping_country_id'){
|
||||
if ($key === 'shipping_country_id') {
|
||||
$ret['shipping_country_code'] = $shopping_user->shipping_country_id ? $shopping_user->shipping_country->code : null;
|
||||
}
|
||||
if($key === 'billing_salutation'){
|
||||
if ($key === 'billing_salutation') {
|
||||
$ret['billing_salutation'] = $shopping_user->billing_salutation === 'ms' ? 2 : 1;
|
||||
}
|
||||
if($key === 'shipping_salutation'){
|
||||
if ($key === 'shipping_salutation') {
|
||||
$ret['shipping_salutation'] = $shopping_user->shipping_salutation === 'ms' ? 2 : 1;
|
||||
}
|
||||
|
||||
if(in_array($key, $needs)){
|
||||
if (in_array($key, $needs)) {
|
||||
$ret[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function prepareForShowOrder($shopping_order){
|
||||
private function prepareForShowOrder($shopping_order)
|
||||
{
|
||||
|
||||
if(!$shopping_order){
|
||||
if (! $shopping_order) {
|
||||
return false;
|
||||
}
|
||||
$ret = [
|
||||
'country' => isset($shopping_order->shipping_country->country->code) ? $shopping_order->shipping_country->country->code : '',
|
||||
'wp_invoice_path' => $shopping_order->wp_invoice_path,
|
||||
'total' => ($shopping_order->total*100),
|
||||
'shipping' => ($shopping_order->shipping*100),
|
||||
'total_net' => ($shopping_order->subtotal*100),
|
||||
'tax_rate' => ($shopping_order->tax_rate*100),
|
||||
'tax' => ($shopping_order->tax*100),
|
||||
'total_with_shipping' => ($shopping_order->total_shipping*100),
|
||||
'total' => ($shopping_order->total * 100),
|
||||
'shipping' => ($shopping_order->shipping * 100),
|
||||
'total_net' => ($shopping_order->subtotal * 100),
|
||||
'tax_rate' => ($shopping_order->tax_rate * 100),
|
||||
'tax' => ($shopping_order->tax * 100),
|
||||
'total_with_shipping' => ($shopping_order->total_shipping * 100),
|
||||
'weight' => $shopping_order->weight,
|
||||
];
|
||||
$ret['items'] = [];
|
||||
foreach ($shopping_order->shopping_order_items as $item){
|
||||
foreach ($shopping_order->shopping_order_items as $item) {
|
||||
$ret['items'][] = [
|
||||
'article' => $item->product->wp_number,
|
||||
'name' => $item->product->name,
|
||||
|
|
@ -491,64 +502,68 @@ class ShoppingUserController extends Controller
|
|||
'price' => ($item->price * 100),
|
||||
];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function prepareForUpdate($data){
|
||||
private function prepareForUpdate($data)
|
||||
{
|
||||
|
||||
//$salutation = array(1 => 'mr', 2 => 'ms', 3=>null);
|
||||
// $salutation = array(1 => 'mr', 2 => 'ms', 3=>null);
|
||||
|
||||
if(isset($data['billing_salutation'])){
|
||||
$data['billing_salutation'] = (int) $data['billing_salutation'];
|
||||
if (isset($data['billing_salutation'])) {
|
||||
$data['billing_salutation'] = (int) $data['billing_salutation'];
|
||||
$data['billing_salutation'] = $data['billing_salutation'] == 2 ? 'ms' : 'mr';
|
||||
}
|
||||
if(isset($data['shipping_salutation'])){
|
||||
$data['shipping_salutation'] = (int) $data['shipping_salutation'];
|
||||
$data['shipping_salutation'] = $data['shipping_salutation'] == 2 ? 'ms' : 'mr';
|
||||
if (isset($data['shipping_salutation'])) {
|
||||
$data['shipping_salutation'] = (int) $data['shipping_salutation'];
|
||||
$data['shipping_salutation'] = $data['shipping_salutation'] == 2 ? 'ms' : 'mr';
|
||||
}
|
||||
|
||||
$ret = [];
|
||||
$needs = [ 'billing_salutation', 'billing_company', 'billing_firstname', 'billing_lastname', 'billing_address', 'billing_address_2', 'billing_zipcode', 'billing_city', 'billing_phone', 'billing_email', 'same_as_billing',
|
||||
$needs = ['billing_salutation', 'billing_company', 'billing_firstname', 'billing_lastname', 'billing_address', 'billing_address_2', 'billing_zipcode', 'billing_city', 'billing_phone', 'billing_email', 'same_as_billing',
|
||||
'shipping_salutation', 'shipping_company', 'shipping_firstname', 'shipping_lastname', 'shipping_address', 'shipping_address_2', 'shipping_zipcode', 'shipping_city', 'shipping_phone'];
|
||||
|
||||
foreach ($data as $key=>$value){
|
||||
if($key === 'billing_country_code' && isset($data['billing_country_code'])) {
|
||||
$ret['billing_country_id'] = Country::getCountryIdByCodeOrOne($data['billing_country_code']);
|
||||
foreach ($data as $key => $value) {
|
||||
if ($key === 'billing_country_code' && isset($data['billing_country_code'])) {
|
||||
$ret['billing_country_id'] = Country::getCountryIdByCodeOrOne($data['billing_country_code']);
|
||||
}
|
||||
if($key === 'shipping_country_code' && isset($data['shipping_country_code']) ) {
|
||||
if ($key === 'shipping_country_code' && isset($data['shipping_country_code'])) {
|
||||
$ret['shipping_country_id'] = Country::getCountryIdByCodeOrOne($data['shipping_country_code']);
|
||||
}
|
||||
if($key === 'billing_phone') {
|
||||
if ($key === 'billing_phone') {
|
||||
$ret['billing_phone'] = strlen($data['billing_phone']) <= 3 ? '' : $data['billing_phone'];
|
||||
}
|
||||
if($key === 'shipping_phone') {
|
||||
if ($key === 'shipping_phone') {
|
||||
$ret['shipping_phone'] = strlen($data['shipping_phone']) <= 3 ? '' : $data['shipping_phone'];
|
||||
}
|
||||
if(in_array($key, $needs)){
|
||||
if (in_array($key, $needs)) {
|
||||
$ret[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function prepareForStore($data){
|
||||
private function prepareForStore($data)
|
||||
{
|
||||
|
||||
//$salutation = array(1 => 'mr', 2 => 'ms', 3=>null);
|
||||
if(isset($data['billing_salutation'])){
|
||||
$data['billing_salutation'] = (int) $data['billing_salutation'];
|
||||
// $salutation = array(1 => 'mr', 2 => 'ms', 3=>null);
|
||||
if (isset($data['billing_salutation'])) {
|
||||
$data['billing_salutation'] = (int) $data['billing_salutation'];
|
||||
$data['billing_salutation'] = $data['billing_salutation'] == 2 ? 'ms' : 'mr';
|
||||
}
|
||||
if(isset($data['shipping_salutation'])){
|
||||
$data['shipping_salutation'] = (int) $data['shipping_salutation'];
|
||||
$data['shipping_salutation'] = $data['shipping_salutation'] == 2 ? 'ms' : 'mr';
|
||||
if (isset($data['shipping_salutation'])) {
|
||||
$data['shipping_salutation'] = (int) $data['shipping_salutation'];
|
||||
$data['shipping_salutation'] = $data['shipping_salutation'] == 2 ? 'ms' : 'mr';
|
||||
}
|
||||
$ret = [];
|
||||
$needs = [ 'billing_salutation', 'billing_company', 'billing_firstname', 'billing_lastname', 'billing_address', 'billing_address_2', 'billing_zipcode', 'billing_city', 'billing_country_id', 'billing_phone', 'billing_email',
|
||||
$needs = ['billing_salutation', 'billing_company', 'billing_firstname', 'billing_lastname', 'billing_address', 'billing_address_2', 'billing_zipcode', 'billing_city', 'billing_country_id', 'billing_phone', 'billing_email',
|
||||
'shipping_salutation', 'shipping_company', 'shipping_firstname', 'shipping_lastname', 'shipping_address', 'shipping_address_2', 'shipping_zipcode', 'shipping_city', 'shipping_country_id', 'shipping_phone',
|
||||
'same_as_billing', //'has_buyed', 'subscribed',
|
||||
'same_as_billing', // 'has_buyed', 'subscribed',
|
||||
'wp_order_number', 'wp_order_date'];
|
||||
|
||||
foreach ($needs as $need){
|
||||
foreach ($needs as $need) {
|
||||
|
||||
$ret[$need] = isset($data[$need]) ? $data[$need] : null;
|
||||
if ($need === 'billing_country_id') {
|
||||
|
|
@ -567,37 +582,38 @@ class ShoppingUserController extends Controller
|
|||
$ret['wp_order_date'] = Carbon::parse($ret['wp_order_date'])->toDateTimeString();
|
||||
}
|
||||
if ($need === 'same_as_billing') {
|
||||
$ret['same_as_billing'] = isset($data['same_as_billing']) ? (int) $data['same_as_billing'] : 1;
|
||||
$ret['same_as_billing'] = isset($data['same_as_billing']) ? (int) $data['same_as_billing'] : 1;
|
||||
}
|
||||
}
|
||||
$ret['has_buyed'] = true;
|
||||
$ret['subscribed'] = false;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function prepareOrder($wp_shopping_order, $shopping_user, $wp_invoice_path, $wp_notice){
|
||||
private function prepareOrder($wp_shopping_order, $shopping_user, $wp_invoice_path, $wp_notice)
|
||||
{
|
||||
Yard::instance('shopping')->destroy();
|
||||
$ret = [];
|
||||
|
||||
if(is_array($wp_shopping_order)){
|
||||
if (is_array($wp_shopping_order)) {
|
||||
foreach ($wp_shopping_order as $order) {
|
||||
//$object = json_decode(json_encode($order), FALSE);
|
||||
// $object = json_decode(json_encode($order), FALSE);
|
||||
$order = (object) $order;
|
||||
$error = [];
|
||||
if (!isset($order->article) || !isset($order->qty) || !isset($order->price)) {
|
||||
$error[] = "article parameter is missing";
|
||||
if (! isset($order->article) || ! isset($order->qty) || ! isset($order->price)) {
|
||||
$error[] = 'article parameter is missing';
|
||||
} else {
|
||||
|
||||
$product = Product::whereWpNumber($order->article)->first();
|
||||
if (!$product) {
|
||||
$error[] = "article not found";
|
||||
if (! $product) {
|
||||
$error[] = 'article not found';
|
||||
} else {
|
||||
if ($order->price != ($product->price * 100)) {
|
||||
$error[] = "different price: " . ($product->price * 100);
|
||||
$error[] = 'different price: '.($product->price * 100);
|
||||
}
|
||||
|
||||
|
||||
Yard::instance('shopping')->add($product->id, $product->name, (int) $order->qty, $product->price, $product->tax, ['image' => [], 'slug' => $product->slug, 'weight' => $product->weight]);
|
||||
Yard::instance('shopping')->add($product->id, $product->name, (int) $order->qty, $product->price, $product->tax, ['image' => [], 'slug' => $product->slug, 'weight' => $product->weight]);
|
||||
}
|
||||
}
|
||||
$order->message = $error;
|
||||
|
|
@ -605,7 +621,7 @@ class ShoppingUserController extends Controller
|
|||
}
|
||||
|
||||
$ShippingCountry = ShippingCountry::whereCountryId($shopping_user->shipping_country_id)->first();
|
||||
if($ShippingCountry){
|
||||
if ($ShippingCountry) {
|
||||
Yard::instance('shopping')->setShippingCountryWithPrice($ShippingCountry->id, 'ot');
|
||||
}
|
||||
$shopping_order = $this->makeShoppingOrder($shopping_user, $wp_invoice_path, $wp_notice);
|
||||
|
|
@ -614,16 +630,18 @@ class ShoppingUserController extends Controller
|
|||
$shopping_user->shopping_order = $shopping_order;
|
||||
Yard::instance('shopping')->destroy();
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function makeShoppingOrder($shopping_user, $wp_invoice_path, $wp_notice){
|
||||
private function makeShoppingOrder($shopping_user, $wp_invoice_path, $wp_notice)
|
||||
{
|
||||
|
||||
$data = [
|
||||
'shopping_user_id' => $shopping_user->id,
|
||||
'auth_user_id' => $shopping_user->auth_user_id,
|
||||
'country_id' => Yard::instance('shopping')->getShippingCountryId(),
|
||||
// 'user_shop_id' => auth()->user()->user_sponsor->shop->id,
|
||||
// 'user_shop_id' => auth()->user()->user_sponsor->shop->id,
|
||||
'member_id' => $shopping_user->member_id,
|
||||
'total' => Yard::instance('shopping')->total(2, '.', ''),
|
||||
'subtotal' => Yard::instance('shopping')->subtotal(2, '.', ''),
|
||||
|
|
@ -641,20 +659,20 @@ class ShoppingUserController extends Controller
|
|||
'mode' => $shopping_user->mode,
|
||||
];
|
||||
$shopping_order = $shopping_user->shopping_order;
|
||||
if($shopping_order){
|
||||
if ($shopping_order) {
|
||||
$shopping_order->fill($data);
|
||||
$shopping_order->save();
|
||||
}else{
|
||||
$shopping_order= ShoppingOrder::create($data);
|
||||
} else {
|
||||
$shopping_order = ShoppingOrder::create($data);
|
||||
}
|
||||
$items = Yard::instance('shopping')->content();
|
||||
|
||||
$shopping_order->shopping_order_items()->each(function($model) use ($items, $shopping_order) {
|
||||
$shopping_order->shopping_order_items()->each(function ($model) use ($items, $shopping_order) {
|
||||
foreach ($items as $item) {
|
||||
if ($model->row_id === $item->rowId) {
|
||||
$model->fill([
|
||||
'shopping_order_id' => $shopping_order->id,
|
||||
'row_id' => $item->rowId,
|
||||
'row_id' => $item->rowId,
|
||||
'product_id' => $item->id,
|
||||
'qty' => $item->qty,
|
||||
'price' => $item->price,
|
||||
|
|
@ -662,42 +680,43 @@ class ShoppingUserController extends Controller
|
|||
'tax_rate' => $item->taxRate,
|
||||
'slug' => $item->options->slug,
|
||||
])->save();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $model->delete();
|
||||
});
|
||||
|
||||
|
||||
foreach ($items as $item) {
|
||||
if (!ShoppingOrderItem::where('shopping_order_id', $shopping_order->id)->where('row_id', $item->rowId)->count())
|
||||
if (! ShoppingOrderItem::where('shopping_order_id', $shopping_order->id)->where('row_id', $item->rowId)->count()) {
|
||||
ShoppingOrderItem::create([
|
||||
'shopping_order_id' => $shopping_order->id,
|
||||
'row_id' => $item->rowId,
|
||||
'row_id' => $item->rowId,
|
||||
'product_id' => $item->id,
|
||||
'qty' => $item->qty,
|
||||
'price' => $item->price,
|
||||
'price_net' => Yard::instance('shopping')->rowPriceNet($item, 3, '.', ''),
|
||||
'tax_rate' => $item->taxRate,
|
||||
'slug' => $item->options->slug
|
||||
'slug' => $item->options->slug,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $shopping_order;
|
||||
}
|
||||
|
||||
|
||||
public function orderStatusSendMail(ShoppingOrder $shopping_order){
|
||||
public function orderStatusSendMail(ShoppingOrder $shopping_order)
|
||||
{
|
||||
|
||||
$bcc = [];
|
||||
$user_mail = ($shopping_order->shopping_user && $shopping_order->shopping_user->member) ? $shopping_order->shopping_user->member->email : false;
|
||||
if($shopping_order->mode === 'dev'){
|
||||
if ($shopping_order->mode === 'dev') {
|
||||
$bcc[] = config('app.checkout_test_mail');
|
||||
}else{
|
||||
} else {
|
||||
$bcc[] = config('app.checkout_mail');
|
||||
}
|
||||
|
||||
Mail::to($user_mail)->bcc($bcc)->send(new MailCheckout($shopping_order->txaction, $shopping_order, null, false, $shopping_order->mode));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
103
app/Models/ApiOrderLog.php
Normal file
103
app/Models/ApiOrderLog.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* AP-13: Protokolleintrag einer Bestell-Übertragung über die Partnercenter-API (wp/order).
|
||||
*
|
||||
* payload['lines'] enthält je Position: article (WP-SKU), qty, price (Cent),
|
||||
* product_id/product_name (Zuordnung im Partnercenter, null = nicht zugeordnet),
|
||||
* messages (API-Rohmeldungen) und failed (Position wurde nicht übernommen).
|
||||
*/
|
||||
class ApiOrderLog extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'shopping_order_id',
|
||||
'shopping_user_id',
|
||||
'wp_order_number',
|
||||
'endpoint',
|
||||
'payload',
|
||||
'has_errors',
|
||||
'error_summary',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'payload' => 'array',
|
||||
'has_errors' => 'bool',
|
||||
];
|
||||
|
||||
public function shoppingOrder(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ShoppingOrder::class, 'shopping_order_id');
|
||||
}
|
||||
|
||||
public function shoppingUser(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ShoppingUser::class, 'shopping_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fehlgeschlagene Übertragung ohne verwertbare Positionen (Kunde unbekannt,
|
||||
* Bestellung existiert bereits, Speicherfehler).
|
||||
*
|
||||
* @param mixed $rawOrder eingehender wp_order-Payload (String oder Array)
|
||||
*/
|
||||
public static function recordFailure(mixed $wpOrderNumber, ?ShoppingUser $shoppingUser, mixed $rawOrder, string $summary): self
|
||||
{
|
||||
return self::query()->create([
|
||||
'shopping_user_id' => $shoppingUser?->id,
|
||||
'wp_order_number' => is_numeric($wpOrderNumber) ? (int) $wpOrderNumber : null,
|
||||
'endpoint' => 'order',
|
||||
'payload' => ['raw' => is_string($rawOrder) ? $rawOrder : json_decode(json_encode($rawOrder), true)],
|
||||
'has_errors' => true,
|
||||
'error_summary' => $summary,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erfolgreich verarbeitete Übertragung: Positionen inkl. Zuordnungsergebnis protokollieren.
|
||||
* Positionen ohne Produktzuordnung (SKU unbekannt / Parameter fehlen) markieren den Eintrag.
|
||||
*
|
||||
* @param array<int, object> $lines Zeilen aus prepareOrder() (article/qty/price/message)
|
||||
*/
|
||||
public static function recordTransfer(mixed $wpOrderNumber, ShoppingUser $shoppingUser, ?ShoppingOrder $shoppingOrder, array $lines): self
|
||||
{
|
||||
$payloadLines = [];
|
||||
$failedCount = 0;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = (object) $line;
|
||||
$article = $line->article ?? null;
|
||||
$product = $article !== null ? Product::whereWpNumber($article)->first() : null;
|
||||
$failed = $product === null;
|
||||
if ($failed) {
|
||||
$failedCount++;
|
||||
}
|
||||
|
||||
$payloadLines[] = [
|
||||
'article' => $article,
|
||||
'qty' => $line->qty ?? null,
|
||||
'price' => $line->price ?? null,
|
||||
'product_id' => $product?->id,
|
||||
'product_name' => $product?->name,
|
||||
'messages' => array_values((array) ($line->message ?? [])),
|
||||
'failed' => $failed,
|
||||
];
|
||||
}
|
||||
|
||||
return self::query()->create([
|
||||
'shopping_order_id' => $shoppingOrder?->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'wp_order_number' => is_numeric($wpOrderNumber) ? (int) $wpOrderNumber : null,
|
||||
'endpoint' => 'order',
|
||||
'payload' => ['lines' => $payloadLines],
|
||||
'has_errors' => $failedCount > 0,
|
||||
'error_summary' => $failedCount > 0
|
||||
? __(':failed von :total Positionen ohne Produktzuordnung', ['failed' => $failedCount, 'total' => count($payloadLines)])
|
||||
: null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* AP-13: Protokoll aller über die Partnercenter-API eingehenden Bestell-Übertragungen
|
||||
* (wp/order). Positionen inkl. SKU-Zuordnungsergebnis liegen als JSON im payload;
|
||||
* fehlgeschlagene Zuordnungen markieren den Eintrag (has_errors).
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('api_order_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
// shopping_orders.id / shopping_users.id sind historisch unsignedInteger (increments)
|
||||
$table->unsignedInteger('shopping_order_id')->nullable();
|
||||
$table->unsignedInteger('shopping_user_id')->nullable();
|
||||
$table->unsignedBigInteger('wp_order_number')->nullable()->index();
|
||||
$table->string('endpoint', 20)->default('order');
|
||||
$table->json('payload')->nullable();
|
||||
$table->boolean('has_errors')->default(false)->index();
|
||||
$table->string('error_summary')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('shopping_order_id')->references('id')->on('shopping_orders')->nullOnDelete();
|
||||
$table->foreign('shopping_user_id')->references('id')->on('shopping_users')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('api_order_logs');
|
||||
}
|
||||
};
|
||||
228
resources/views/admin/inventory/api-orders/index.blade.php
Normal file
228
resources/views/admin/inventory/api-orders/index.blade.php
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
@include('admin.inventory.partials.wawi-ui')
|
||||
|
||||
<div class="wawi-page">
|
||||
<div class="wawi-page-head">
|
||||
<div>
|
||||
<h1 class="wawi-page-head__title">{{ __('API-Bestellungen') }}</h1>
|
||||
<p class="wawi-page-head__subtitle">{{ __('Über die Partnercenter-API übertragene Shop-Bestellungen — Positionen ohne Produktzuordnung sind markiert') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($duplicateSkuProducts->isNotEmpty())
|
||||
<div class="wawi-card mb-3" style="border-left: 4px solid #d63031;">
|
||||
<div class="p-3">
|
||||
<strong class="text-danger"><span class="fas fa-exclamation-triangle mr-1"></span>{{ __('Doppelte WP-Nummern (SKU) bei aktiven Produkten') }}</strong>
|
||||
<p class="text-muted small mb-2">{{ __('Bei doppelten WP-Nummern ist nicht eindeutig, welches Produkt einer API-Position zugeordnet wird — bitte bereinigen.') }}</p>
|
||||
<ul class="mb-0 small">
|
||||
@foreach ($duplicateSkuProducts->groupBy('wp_number') as $wpNumber => $products)
|
||||
<li>
|
||||
<strong>{{ $wpNumber }}</strong>:
|
||||
@foreach ($products as $product)
|
||||
<a href="{{ route('admin_product_edit', $product->id) }}">{{ $product->name }}</a>@if (! $loop->last), @endif
|
||||
@endforeach
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($missingSkuProducts->isNotEmpty())
|
||||
<div class="wawi-card mb-3">
|
||||
<div class="p-3">
|
||||
<details>
|
||||
<summary class="text-muted">
|
||||
{{ __(':n aktive Produkte ohne WP-Nummer (können per API nicht zugeordnet werden)', ['n' => $missingSkuProducts->count()]) }}
|
||||
</summary>
|
||||
<ul class="mb-0 mt-2 small">
|
||||
@foreach ($missingSkuProducts as $product)
|
||||
<li><a href="{{ route('admin_product_edit', $product->id) }}">{{ $product->name }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="wawi-card">
|
||||
<div class="wawi-toolbar">
|
||||
<label class="mb-0 small d-flex align-items-center" style="gap:.4rem;">
|
||||
<input type="checkbox" id="api-log-only-errors">
|
||||
{{ __('Nur Einträge mit Fehlern') }}
|
||||
@if ($errorCount > 0)
|
||||
<span class="wawi-pill wawi-pill--danger">{{ $errorCount }}</span>
|
||||
@endif
|
||||
</label>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table wawi-table" id="api-log-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ __('Eingegangen') }}</th>
|
||||
<th>{{ __('WP-Bestellnr.') }}</th>
|
||||
<th>{{ __('Kunde') }}</th>
|
||||
<th>{{ __('Bestellung') }}</th>
|
||||
<th class="text-right">{{ __('Positionen') }}</th>
|
||||
<th>{{ __('Status') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($logs as $log)
|
||||
@php
|
||||
$lines = $log->payload['lines'] ?? [];
|
||||
$customer = $log->shoppingUser
|
||||
? trim(($log->shoppingUser->billing_firstname ?? '').' '.($log->shoppingUser->billing_lastname ?? ''))
|
||||
: '';
|
||||
@endphp
|
||||
<tr class="api-log-row" data-errors="{{ $log->has_errors ? 1 : 0 }}">
|
||||
<td>{{ $log->created_at->format('d.m.Y H:i') }}</td>
|
||||
<td>{{ $log->wp_order_number ?? '—' }}</td>
|
||||
<td>{{ $customer !== '' ? $customer : '—' }}</td>
|
||||
<td>
|
||||
@if ($log->shopping_order_id)
|
||||
<a href="{{ route('admin_sales_detail', $log->shopping_order_id) }}">#{{ $log->shopping_order_id }}</a>
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-right">{{ count($lines) ?: '—' }}</td>
|
||||
<td>
|
||||
@if ($log->has_errors)
|
||||
<span class="wawi-pill wawi-pill--danger">{{ __('Fehler') }}</span>
|
||||
@if ($log->error_summary)
|
||||
<div class="small text-danger">{{ $log->error_summary }}</div>
|
||||
@endif
|
||||
@else
|
||||
<span class="wawi-pill wawi-pill--ok">{{ __('OK') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary js-api-log-view"
|
||||
data-meta="{{ json_encode([
|
||||
'received' => $log->created_at->format('d.m.Y H:i'),
|
||||
'wp_order_number' => $log->wp_order_number,
|
||||
'customer' => $customer,
|
||||
'summary' => $log->error_summary,
|
||||
'raw' => isset($log->payload['raw']) ? json_encode($log->payload['raw'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : null,
|
||||
]) }}"
|
||||
data-lines="{{ json_encode($lines) }}">
|
||||
<span class="far fa-eye mr-1"></span>{{ __('Ansehen') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<div class="wawi-empty">
|
||||
<div><span class="fas fa-cloud-download-alt"></span></div>
|
||||
{{ __('Noch keine Bestell-Übertragungen eingegangen.') }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if (($listLimit ?? null) !== null && $logs->count() >= $listLimit)
|
||||
<p class="text-muted small px-3 pb-2 mb-0">
|
||||
{{ __('Es werden die neuesten :n Übertragungen angezeigt.', ['n' => $listLimit]) }}
|
||||
<a href="{{ route('admin.inventory.api-orders.index', ['all' => 1]) }}">{{ __('Alle anzeigen') }}</a>
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="api-log-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ __('Inhalt der Bestell-Übertragung') }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="small text-muted mb-2" id="api-log-modal-meta"></p>
|
||||
<p class="small text-danger mb-2 d-none" id="api-log-modal-summary"></p>
|
||||
<div class="table-responsive d-none" id="api-log-modal-lines-wrap">
|
||||
<table class="table table-sm wawi-table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ __('WP-Nr. (SKU)') }}</th>
|
||||
<th>{{ __('Produkt im Partnercenter') }}</th>
|
||||
<th class="text-right">{{ __('Menge') }}</th>
|
||||
<th class="text-right">{{ __('Preis (WP)') }}</th>
|
||||
<th>{{ __('Meldung') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="api-log-modal-lines"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="small bg-light p-2 d-none" id="api-log-modal-raw" style="max-height:300px;overflow:auto;"></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{ __('Schließen') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$('#api-log-only-errors').on('change', function () {
|
||||
var only = $(this).is(':checked');
|
||||
$('#api-log-table tbody tr.api-log-row').each(function () {
|
||||
$(this).toggle(!only || $(this).data('errors') === 1);
|
||||
});
|
||||
});
|
||||
|
||||
function messageLabel(message) {
|
||||
if (message === 'article not found') {
|
||||
return '{{ __('Produkt nicht zugeordnet — WP-Nummer im Partnercenter unbekannt') }}';
|
||||
}
|
||||
if (message === 'article parameter is missing') {
|
||||
return '{{ __('Position unvollständig (Artikel/Menge/Preis fehlt)') }}';
|
||||
}
|
||||
if (message.indexOf('different price:') === 0) {
|
||||
var cents = parseInt(message.replace('different price:', '').trim(), 10);
|
||||
var euro = isNaN(cents) ? '?' : (cents / 100).toFixed(2).replace('.', ',');
|
||||
return '{{ __('Preis weicht ab — Preis im Partnercenter:') }} ' + euro + ' €';
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
$('.js-api-log-view').on('click', function () {
|
||||
var meta = $(this).data('meta') || {};
|
||||
var lines = $(this).data('lines') || [];
|
||||
|
||||
$('#api-log-modal-meta').text(
|
||||
'{{ __('Eingegangen') }}: ' + (meta.received || '—')
|
||||
+ ' · {{ __('WP-Bestellnr.') }}: ' + (meta.wp_order_number || '—')
|
||||
+ (meta.customer ? ' · {{ __('Kunde') }}: ' + meta.customer : '')
|
||||
);
|
||||
$('#api-log-modal-summary').toggleClass('d-none', !meta.summary).text(meta.summary || '');
|
||||
|
||||
var $body = $('#api-log-modal-lines').empty();
|
||||
$('#api-log-modal-lines-wrap').toggleClass('d-none', lines.length === 0);
|
||||
$.each(lines, function (_, line) {
|
||||
var $tr = $('<tr>').toggleClass('table-danger', !!line.failed);
|
||||
$tr.append($('<td>').text(line.article !== null && line.article !== undefined ? line.article : '—'));
|
||||
$tr.append($('<td>').text(line.product_name || '{{ __('— nicht zugeordnet —') }}')
|
||||
.toggleClass('text-danger font-weight-bold', !line.product_id));
|
||||
$tr.append($('<td class="text-right">').text(line.qty !== null && line.qty !== undefined ? line.qty : '—'));
|
||||
var price = parseInt(line.price, 10);
|
||||
$tr.append($('<td class="text-right">').text(isNaN(price) ? '—' : (price / 100).toFixed(2).replace('.', ',') + ' €'));
|
||||
var messages = (line.messages || []).map(messageLabel).join('; ');
|
||||
$tr.append($('<td>').text(messages).addClass(line.failed ? 'text-danger' : 'text-muted'));
|
||||
$body.append($tr);
|
||||
});
|
||||
|
||||
$('#api-log-modal-raw').toggleClass('d-none', !meta.raw).text(meta.raw || '');
|
||||
|
||||
$('#api-log-modal').modal('show');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -256,6 +256,12 @@
|
|||
<div>{{ __('Ausgang / Ausschuss') }}</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="sidenav-item{{ Request::is('admin/inventory/api-orders*') ? ' active' : '' }}">
|
||||
<a href="{{ route('admin.inventory.api-orders.index') }}" class="sidenav-link"><i
|
||||
class="sidenav-icon ion ion-md-cloud-download"></i>
|
||||
<div>{{ __('API-Bestellungen') }}</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="sidenav-item @if (Request::is('admin/inventory/productions*', 'admin/inventory/product-development*')) open @endif">
|
||||
<a href="javascript:void(0)" class="sidenav-link sidenav-toggle">
|
||||
<i class="sidenav-icon ion ion-md-construct"></i>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\Admin\Inventory\ApiOrderLogController as InventoryApiOrderLogController;
|
||||
use App\Http\Controllers\Admin\Inventory\DeliveryTimeController as InventoryDeliveryTimeController;
|
||||
use App\Http\Controllers\Admin\Inventory\DisposalReasonController as InventoryDisposalReasonController;
|
||||
use App\Http\Controllers\Admin\Inventory\GeneralSettingController as InventoryGeneralSettingController;
|
||||
|
|
@ -328,6 +329,7 @@ Route::domain(config('app.domain'))->group(function () {
|
|||
Route::resource('productions', InventoryProductionController::class)->only(['index', 'create', 'store', 'show', 'edit', 'update', 'destroy']);
|
||||
Route::get('productions/{production}/copy', [InventoryProductionController::class, 'copy'])->name('productions.copy');
|
||||
Route::get('product-development', [InventoryProductDevelopmentController::class, 'index'])->name('product-development');
|
||||
Route::get('api-orders', [InventoryApiOrderLogController::class, 'index'])->name('api-orders.index');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
229
tests/Feature/ApiOrderLogTest.php
Normal file
229
tests/Feature/ApiOrderLogTest.php
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
<?php
|
||||
|
||||
use App\Models\ApiOrderLog;
|
||||
use App\Models\Country;
|
||||
use App\Models\Product;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingOrderItem;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAccount;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Laravel\Passport\Passport;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function apilogCountry(): 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 apilogMakeUser(int $adminLevel = 7): User
|
||||
{
|
||||
$user = User::query()->create([
|
||||
'email' => uniqid('apilog_', true).'@test.example',
|
||||
'password' => bcrypt('password'),
|
||||
]);
|
||||
$account = UserAccount::query()->create([
|
||||
'm_first_name' => 'Api',
|
||||
'm_last_name' => 'Partner',
|
||||
'country_id' => apilogCountry()->id,
|
||||
]);
|
||||
$user->forceFill([
|
||||
'admin' => $adminLevel,
|
||||
'confirmed' => true,
|
||||
'active' => true,
|
||||
'wizard' => 100,
|
||||
'blocked' => false,
|
||||
'account_id' => $account->id,
|
||||
])->save();
|
||||
|
||||
return $user->fresh();
|
||||
}
|
||||
|
||||
function apilogMakeShoppingUser(int $wpOrderNumber): ShoppingUser
|
||||
{
|
||||
$country = apilogCountry();
|
||||
$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' => 'Api',
|
||||
'billing_lastname' => 'Kunde',
|
||||
'wp_order_number' => $wpOrderNumber,
|
||||
'member_id' => apilogMakeUser(0)->id,
|
||||
'auth_user_id' => apilogMakeUser(0)->id,
|
||||
]);
|
||||
}
|
||||
|
||||
function apilogMakeProduct(string $name, int $wpNumber, string $price = '19,90'): Product
|
||||
{
|
||||
return Product::query()->create([
|
||||
'name' => $name,
|
||||
'title' => $name,
|
||||
'active' => true,
|
||||
'is_set' => false,
|
||||
'wp_number' => $wpNumber,
|
||||
'price' => $price,
|
||||
'tax' => '19',
|
||||
'weight' => 100,
|
||||
]);
|
||||
}
|
||||
|
||||
test('uebertragung mit unbekannter sku wird protokolliert und markiert', function () {
|
||||
Mail::fake();
|
||||
Passport::actingAs(apilogMakeUser());
|
||||
$product = apilogMakeProduct('Apilog Creme', 100);
|
||||
$shoppingUser = apilogMakeShoppingUser(4711);
|
||||
|
||||
$response = $this->postJson('https://api.'.config('app.api_domain').'/wp/order', [
|
||||
'wp_order_number' => 4711,
|
||||
'wp_order' => [
|
||||
['article' => 100, 'qty' => 2, 'price' => 1990],
|
||||
['article' => 999, 'qty' => 1, 'price' => 500],
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$log = ApiOrderLog::query()->sole();
|
||||
expect($log->has_errors)->toBeTrue()
|
||||
->and($log->error_summary)->toBe('1 von 2 Positionen ohne Produktzuordnung')
|
||||
->and($log->wp_order_number)->toBe(4711)
|
||||
->and($log->shopping_user_id)->toBe($shoppingUser->id)
|
||||
->and($log->shopping_order_id)->not->toBeNull()
|
||||
->and($log->payload['lines'])->toHaveCount(2)
|
||||
->and($log->payload['lines'][0]['product_id'])->toBe($product->id)
|
||||
->and($log->payload['lines'][0]['failed'])->toBeFalse()
|
||||
->and($log->payload['lines'][1]['product_id'])->toBeNull()
|
||||
->and($log->payload['lines'][1]['failed'])->toBeTrue()
|
||||
->and($log->payload['lines'][1]['messages'])->toContain('article not found');
|
||||
|
||||
// Die nicht zugeordnete Position fehlt in der Bestellung (Bestand: kein Abzug möglich)
|
||||
expect(ShoppingOrderItem::query()->where('shopping_order_id', $log->shopping_order_id)->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('fehlerfreie uebertragung wird als ok protokolliert', function () {
|
||||
Mail::fake();
|
||||
Passport::actingAs(apilogMakeUser());
|
||||
apilogMakeProduct('Apilog Seife', 200);
|
||||
apilogMakeShoppingUser(4712);
|
||||
|
||||
$this->postJson('https://api.'.config('app.api_domain').'/wp/order', [
|
||||
'wp_order_number' => 4712,
|
||||
'wp_order' => [
|
||||
['article' => 200, 'qty' => 1, 'price' => 1990],
|
||||
],
|
||||
])->assertOk();
|
||||
|
||||
$log = ApiOrderLog::query()->sole();
|
||||
expect($log->has_errors)->toBeFalse()
|
||||
->and($log->error_summary)->toBeNull()
|
||||
->and($log->shopping_order_id)->not->toBeNull();
|
||||
});
|
||||
|
||||
test('bereits existierende bestellung wird als fehler protokolliert', function () {
|
||||
Mail::fake();
|
||||
Passport::actingAs(apilogMakeUser());
|
||||
$shoppingUser = apilogMakeShoppingUser(4713);
|
||||
ShoppingOrder::query()->forceCreate([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => apilogMakeUser(0)->id,
|
||||
'country_id' => ShippingCountry::query()->first()->id,
|
||||
'shipped' => 0,
|
||||
]);
|
||||
|
||||
$this->postJson('https://api.'.config('app.api_domain').'/wp/order', [
|
||||
'wp_order_number' => 4713,
|
||||
'wp_order' => [['article' => 1, 'qty' => 1, 'price' => 100]],
|
||||
])->assertStatus(400);
|
||||
|
||||
$log = ApiOrderLog::query()->sole();
|
||||
expect($log->has_errors)->toBeTrue()
|
||||
->and($log->error_summary)->toContain('existiert bereits');
|
||||
});
|
||||
|
||||
test('unbekannte wp-bestellnummer wird protokolliert', function () {
|
||||
Mail::fake();
|
||||
Passport::actingAs(apilogMakeUser());
|
||||
|
||||
$this->postJson('https://api.'.config('app.api_domain').'/wp/order', [
|
||||
'wp_order_number' => 9999,
|
||||
'wp_order' => [['article' => 1, 'qty' => 1, 'price' => 100]],
|
||||
])->assertStatus(400);
|
||||
|
||||
$log = ApiOrderLog::query()->sole();
|
||||
expect($log->has_errors)->toBeTrue()
|
||||
->and($log->shopping_user_id)->toBeNull()
|
||||
->and($log->wp_order_number)->toBe(9999);
|
||||
});
|
||||
|
||||
test('uebersicht rendert eintraege mit fehler-markierung', function () {
|
||||
$shoppingUser = apilogMakeShoppingUser(4714);
|
||||
ApiOrderLog::query()->create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'wp_order_number' => 4714,
|
||||
'endpoint' => 'order',
|
||||
'payload' => ['lines' => [['article' => 5, 'qty' => 1, 'price' => 100, 'product_id' => null, 'product_name' => null, 'messages' => ['article not found'], 'failed' => true]]],
|
||||
'has_errors' => true,
|
||||
'error_summary' => '1 von 1 Positionen ohne Produktzuordnung',
|
||||
]);
|
||||
|
||||
$this->actingAs(apilogMakeUser(1))
|
||||
->get(route('admin.inventory.api-orders.index'))
|
||||
->assertOk()
|
||||
->assertSee('API-Bestellungen')
|
||||
->assertSee('4714')
|
||||
->assertSee('1 von 1 Positionen ohne Produktzuordnung');
|
||||
});
|
||||
|
||||
test('doppelte wp-nummern aktiver produkte werden gewarnt', function () {
|
||||
apilogMakeProduct('Apilog Duplikat A', 300);
|
||||
apilogMakeProduct('Apilog Duplikat B', 300);
|
||||
|
||||
$this->actingAs(apilogMakeUser(1))
|
||||
->get(route('admin.inventory.api-orders.index'))
|
||||
->assertOk()
|
||||
->assertSee('Doppelte WP-Nummern')
|
||||
->assertSee('Apilog Duplikat A')
|
||||
->assertSee('Apilog Duplikat B');
|
||||
});
|
||||
|
||||
test('produkte ohne wp-nummer werden gezaehlt', function () {
|
||||
apilogMakeProduct('Apilog Mit Nummer', 400);
|
||||
Product::query()->create([
|
||||
'name' => 'Apilog Ohne Nummer',
|
||||
'title' => 'Apilog Ohne Nummer',
|
||||
'active' => true,
|
||||
'is_set' => false,
|
||||
]);
|
||||
|
||||
$this->actingAs(apilogMakeUser(1))
|
||||
->get(route('admin.inventory.api-orders.index'))
|
||||
->assertOk()
|
||||
->assertSee('aktive Produkte ohne WP-Nummer', false)
|
||||
->assertSee('Apilog Ohne Nummer');
|
||||
});
|
||||
|
||||
test('ohne copyreader-rechte kein zugriff', function () {
|
||||
$this->actingAs(apilogMakeUser(0))
|
||||
->get(route('admin.inventory.api-orders.index'))
|
||||
->assertRedirect();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue