20-02-2026

This commit is contained in:
Kevin Adametz 2026-02-20 17:55:06 +01:00
parent a8b395e20d
commit a00c42e770
252 changed files with 28785 additions and 8907 deletions

View file

@ -3,25 +3,22 @@
namespace App\Services;
use App\Models\Product;
use Yard;
use App\User;
use Carbon\Carbon;
use App\Models\UserAbo;
use App\Models\UserAboItem;
use App\Models\UserAboOrder;
use App\Models\ShoppingOrder;
use App\Models\ShoppingPayment;
use App\Models\ShoppingUser;
use App\Models\UserAbo;
use App\Models\UserAboItem;
use App\Models\UserAboOrder;
use App\User;
use Carbon\Carbon;
class AboHelper
{
public static $txaction_filter_text = [
'paid' => 'paymend_paid',
'appointed' => 'paymend_open',
'failed' => 'paymend_failed',
'extern' => 'extern_open', //offen
'extern' => 'extern_open', // offen
'extern_paid' => 'extern_paid',
'invoice_open' => 'invoice_open',
'invoice_paid' => 'invoice_paid',
@ -29,18 +26,19 @@ class AboHelper
'NULL' => 'no_payment',
];
public static function userHasAbo(User $user)
{
$user = $user ? $user : \Auth::user();
return UserAbo::where('user_id', $user->id)->where('is_for', 'me')->where('status', '>', 1)->first() === null ? false : true;
}
public static function memberHasAbo(ShoppingUser $shopping_user)
{
if (!$shopping_user) {
if (! $shopping_user) {
return false;
}
return UserAbo::where('email', $shopping_user->billing_email)->where('is_for', 'ot')->where('status', '>', 1)->first() === null ? false : true;
}
@ -52,19 +50,20 @@ class AboHelper
public static function setAboStatus(ShoppingOrder $shopping_order, $status, $paid = false)
{
$user_abo = $shopping_order->getUserAbo();
if ($user_abo && $user_abo->status < 2) { //status < 2 is not active
if ($user_abo && $user_abo->status < 2) { // status < 2 is not active
$user_abo->update(['status' => $status]);
}
UserAboOrder::where('user_abo_id', $user_abo->id)->where('shopping_order_id', $shopping_order->id)->update(['status' => $status, 'paid' => $paid]);
}
public static function setAboActive(ShoppingOrder $shopping_order, $status, $paid = false)
{
self::setAboStatus($shopping_order, $status, $paid);
//delete UserAbo is not active status = 1
//is_for = me
// delete UserAbo is not active status = 1
// is_for = me
UserAbo::where('user_id', $shopping_order->auth_user_id)->where('is_for', 'me')->where('status', 1)->delete();
//is_for = ot
// is_for = ot
UserAbo::where('member_id', $shopping_order->member_id)->where('email', $shopping_order->shopping_user->billing_email)->where('is_for', 'ot')->where('status', 1)->delete();
}
@ -80,14 +79,29 @@ class AboHelper
return true;
}
$paidOrdersCount = $user_abo->getCountPaidOrders();
return $paidOrdersCount >= (int) $minDuration;
}
public static function isAddOnlyMode(UserAbo $user_abo, $view = 'user'): bool
{
if ($view === 'admin') {
return false;
}
return ! self::canCancelAbo($user_abo, $view);
}
public static function canEditAbo($user_abo, $view = 'user')
{
if ($view !== 'admin' && ($user_abo->user_id != \Auth::user()->id && $user_abo->member_id != \Auth::user()->id)) {
if ($view === 'portal') {
return true;
}
$user = \Auth::user();
if ($view !== 'admin' && (! $user || ($user_abo->user_id != $user->id && $user_abo->member_id != $user->id))) {
return false;
}
return true;
}
@ -100,8 +114,10 @@ class AboHelper
}
}
}
return false;
}
public static function getAboShowOn(Product $product)
{
$show_on = $product->show_on;
@ -111,16 +127,19 @@ class AboHelper
if (in_array('13', $show_on)) {
return 'upgrade';
}
return false;
}
public static function getAboTypeBadge($abo_type)
{
if ($abo_type === 'base') {
return '<span class="badge badge-pill badge-warning"><i class="fas fa-star"></i> ' . __('abo.' . $abo_type) . '</span></a>';
return '<span class="badge badge-pill badge-warning"><i class="fas fa-star"></i> '.__('abo.'.$abo_type).'</span></a>';
}
if ($abo_type === 'upgrade') {
return '<span class="badge badge-pill badge-info"><i class="far fa-star"></i> ' . __('abo.' . $abo_type) . '</span></a>';
return '<span class="badge badge-pill badge-info"><i class="far fa-star"></i> '.__('abo.'.$abo_type).'</span></a>';
}
return '';
}
@ -128,6 +147,7 @@ class AboHelper
{
$nextDate = Carbon::parse($date)->firstOfMonth();
$nextDate->addDays($abo_interval - 1);
return $nextDate->gt($date) ? $nextDate : $nextDate->addMonth(1);
}
@ -135,19 +155,20 @@ class AboHelper
{
$nextDate = Carbon::parse($date)->firstOfMonth()->addMonth(1);
$nextDate->addDays($abo_interval - 1);
return $nextDate->gt($date) ? $nextDate : $nextDate->addMonth(1);
}
public static function createNewAbo(ShoppingPayment $shopping_payment)
{
//is Abo - create init Abo from PP or else
// is Abo - create init Abo from PP or else
if ($shopping_payment->shopping_order->is_abo && $shopping_payment->shopping_order->abo_interval > 0) {
$payment_transaction = $shopping_payment->payment_transactions->last();
//next_date immer im nächsten Monat starten
//is auth_user_id = Berater bestellung
//is member_id = Kunden bestellung
//is for = me = mich oder ot = kunde
// next_date immer im nächsten Monat starten
// is auth_user_id = Berater bestellung
// is member_id = Kunden bestellung
// is for = me = mich oder ot = kunde
$user_abo = UserAbo::create([
'user_id' => $shopping_payment->shopping_order->auth_user_id,
'member_id' => $shopping_payment->shopping_order->member_id,
@ -188,15 +209,82 @@ class AboHelper
'status' => 1,
]);
}
}
$user_abo->load('user_abo_items');
AboItemHistoryService::logInitialCreation($user_abo, 'system');
}
public static function getTransStatusFilterText()
{
$ret = [];
foreach (self::$txaction_filter_text as $key => $val) {
$ret[$key] = trans('payment.' . $val);
$ret[$key] = trans('payment.'.$val);
}
return $ret;
}
/**
* Prüft effizient, ob ein User im Team eines anderen Users ist (Downline).
* Traversiert die Sponsor-Hierarchie rekursiv (m_sponsor) statt die komplette
* TreeCalcBot-Struktur aufzubauen.
*
* @param int $teamOwnerId ID des Team-Users (Berechtigter)
* @param int $userToCheckId ID des zu prüfenden Users (z.B. Abo-Besitzer)
* @param int $maxDepth Max. Tiefe (Schutz vor zirkulären Referenzen)
* @return bool True wenn userToCheckId im Team von teamOwnerId ist
*/
public static function isUserInTeam(int $teamOwnerId, int $userToCheckId, int $maxDepth = 100): bool
{
if ($teamOwnerId === $userToCheckId) {
return true;
}
$currentId = $userToCheckId;
$depth = 0;
while ($depth < $maxDepth) {
$currentUser = User::where('id', $currentId)->select('m_sponsor')->first();
if (! $currentUser || ! $currentUser->m_sponsor) {
return false;
}
if ($currentUser->m_sponsor === $teamOwnerId) {
return true;
}
$currentId = $currentUser->m_sponsor;
$depth++;
}
return false;
}
/**
* Liefert alle User-IDs im Team (Downline) eines Users.
* Traversiert die Sponsor-Hierarchie rekursiv nach unten statt TreeCalcBot.
*
* @param int $teamOwnerId ID des Team-Users
* @param int $maxDepth Max. Tiefe (Schutz vor Endlosschleifen)
* @return int[]
*/
public static function getTeamUserIds(int $teamOwnerId, int $maxDepth = 50): array
{
$teamUserIds = [];
$toProcess = [$teamOwnerId];
$depth = 0;
while (! empty($toProcess) && $depth < $maxDepth) {
$children = User::whereIn('m_sponsor', $toProcess)
->whereNull('deleted_at')
->pluck('id')
->toArray();
$teamUserIds = array_merge($teamUserIds, $children);
$toProcess = $children;
$depth++;
}
return array_values(array_unique($teamUserIds));
}
}

View file

@ -0,0 +1,361 @@
<?php
namespace App\Services;
use App\Models\Product;
use App\Models\UserAbo;
use App\Models\UserAboItem;
use App\Models\UserAboItemHistory;
use Illuminate\Support\Str;
class AboItemHistoryService
{
/**
* Log alle Items eines neu erstellten Abos als initial-Einträge.
*/
public static function logInitialCreation(UserAbo $userAbo, string $channel = 'system'): void
{
$batchId = Str::uuid()->toString();
$actor = self::resolveActor();
foreach ($userAbo->user_abo_items as $item) {
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_INITIAL,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => null,
'qty_after' => $item->qty,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
'batch_id' => $batchId,
'is_initial' => true,
]);
}
}
/**
* Log: Produkt hinzugefügt oder Menge erhöht (bei bereits vorhandenem Produkt).
*/
public static function logProductAdded(UserAbo $userAbo, UserAboItem $item, int $qtyBefore, string $view): void
{
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
$actor = self::resolveActor();
$channel = self::resolveChannel($view);
$action = $qtyBefore === 0
? UserAboItemHistory::ACTION_ADDED
: UserAboItemHistory::ACTION_QTY_CHANGED;
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => $action,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => $qtyBefore > 0 ? $qtyBefore : null,
'qty_after' => $item->qty,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
]);
}
/**
* Log: Menge eines Produkts geändert.
*/
public static function logQtyChanged(UserAbo $userAbo, UserAboItem $item, int $qtyBefore, int $qtyAfter, string $view): void
{
if ($qtyBefore === $qtyAfter) {
return;
}
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
$actor = self::resolveActor();
$channel = self::resolveChannel($view);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_QTY_CHANGED,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $qtyAfter,
'qty_before' => $qtyBefore,
'qty_after' => $qtyAfter,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
]);
}
/**
* Log: Produkt entfernt (vor dem delete aufrufen!).
*/
public static function logProductRemoved(UserAbo $userAbo, UserAboItem $item, string $view): void
{
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
$actor = self::resolveActor();
$channel = self::resolveChannel($view);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_REMOVED,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => $item->qty,
'qty_after' => 0,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
]);
}
/**
* Log: Comp-Produkt getauscht.
*/
public static function logCompProductChanged(UserAbo $userAbo, UserAboItem $item, Product $oldProduct, Product $newProduct, string $view): void
{
// Item hat bereits das neue Produkt — getPrice() liefert den korrekten Anzeige-Preis
$unitPrice = self::resolveItemPrice($item);
$actor = self::resolveActor();
$channel = self::resolveChannel($view);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $newProduct->id,
'action' => UserAboItemHistory::ACTION_COMP_CHANGED,
'product_name' => $newProduct->getLang('name'),
'product_number' => $newProduct->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'old_product_id' => $oldProduct->id,
'old_product_name' => $oldProduct->getLang('name'),
'comp' => $item->comp,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
]);
}
/**
* Log: System hat Comp-Produkt hinzugefügt.
*/
public static function logSystemCompAdded(UserAbo $userAbo, UserAboItem $item): void
{
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_COMP_ADDED,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_after' => $item->qty,
'comp' => $item->comp,
'changed_by_user_id' => null,
'changed_by_name' => 'System',
'channel' => UserAboItemHistory::CHANNEL_SYSTEM,
]);
}
/**
* Log: System hat Comp-Produkt entfernt (vor dem delete aufrufen!).
*/
public static function logSystemCompRemoved(UserAbo $userAbo, UserAboItem $item): void
{
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_COMP_REMOVED,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => $item->qty,
'qty_after' => 0,
'comp' => $item->comp,
'changed_by_user_id' => null,
'changed_by_name' => 'System',
'channel' => UserAboItemHistory::CHANNEL_SYSTEM,
]);
}
/**
* Setzt das Abo auf den Ursprungszustand zurück.
*/
public static function rollbackToInitial(UserAbo $userAbo): bool
{
$initialItems = $userAbo->user_abo_item_histories()
->where('is_initial', true)
->get();
if ($initialItems->isEmpty()) {
return false;
}
$batchId = Str::uuid()->toString();
$actor = self::resolveActor();
// Aktuelle Items loggen und löschen
foreach ($userAbo->user_abo_items as $item) {
$unitPrice = self::resolveItemPrice($item);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $item->product_id,
'action' => UserAboItemHistory::ACTION_ROLLBACK,
'product_name' => $item->product->getLang('name'),
'product_number' => $item->product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => $item->qty,
'qty_after' => 0,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => UserAboItemHistory::CHANNEL_ADMIN,
'batch_id' => $batchId,
]);
}
// Alle aktuellen Items löschen
UserAboItem::where('user_abo_id', $userAbo->id)->delete();
// Neue Items aus Initial-History erstellen
foreach ($initialItems as $historyItem) {
UserAboItem::create([
'user_abo_id' => $userAbo->id,
'product_id' => $historyItem->product_id,
'comp' => $historyItem->comp,
'qty' => $historyItem->qty_after,
'status' => 1,
]);
}
return true;
}
/**
* Ermittelt den exakten Anzeige-Preis des UserAboItems.
* Nutzt UserAboItem::getPrice() das ist genau der Preis, der dem User
* in der Produktliste und im Warenkorb angezeigt wird (netto+UserFactor
* für Berater, brutto für Kunden, länderspezifisch).
*
* Fallback-Kette wenn Yard nicht verfügbar:
* 1. getPriceWith() ohne Country (netto/ufactor je nach is_for)
* 2. Rohpreis des Produkts
*/
private static function resolveItemPrice(UserAboItem $item): float
{
// 1. Exakter Anzeige-Preis via UserAboItem::getPrice() (nutzt Yard)
try {
return (float) $item->getPrice();
} catch (\Throwable $e) {
// Yard nicht initialisiert — weiter zum Fallback
}
// 2. Manuelle Berechnung ohne Yard (ohne Country-spezifische Preise)
try {
$isMe = $item->user_abo->is_for === 'me';
return (float) $item->product->getPriceWith($isMe, $isMe);
} catch (\Throwable $e) {
// Letzter Fallback
}
// 3. Roher Produktpreis
return (float) ($item->product->price ?? 0);
}
/**
* Aktuellen Benutzer ermitteln (CRM Auth oder Kunden-Guard).
*/
private static function resolveActor(): array
{
// CRM User (Admin/Berater)
$user = \Auth::user();
if ($user) {
$name = '';
if ($user->account) {
$name = trim($user->account->first_name.' '.$user->account->last_name);
}
if (! $name) {
$name = $user->email ?? 'User #'.$user->id;
}
return ['id' => $user->id, 'name' => $name];
}
// Portal Kunde
$customer = \Auth::guard('customers')->user();
if ($customer) {
return ['id' => null, 'name' => $customer->name ?? $customer->email ?? 'Kunde'];
}
return ['id' => null, 'name' => 'System'];
}
/**
* Channel aus View-Parameter und Auth-Kontext ableiten.
*/
private static function resolveChannel(string $view): string
{
if ($view === 'portal') {
return UserAboItemHistory::CHANNEL_PORTAL;
}
if ($view === 'admin') {
return UserAboItemHistory::CHANNEL_ADMIN;
}
// Bei user views: Prüfen ob Admin die Änderung vornimmt
$user = \Auth::user();
if ($user && $user->isAdmin()) {
return UserAboItemHistory::CHANNEL_ADMIN;
}
if ($view === 'me') {
return UserAboItemHistory::CHANNEL_USER_ME;
}
if ($view === 'ot') {
return UserAboItemHistory::CHANNEL_USER_OT;
}
return UserAboItemHistory::CHANNEL_SYSTEM;
}
}

View file

@ -2,24 +2,20 @@
namespace App\Services;
use Yard;
use App\User;
use Carbon\Carbon;
use App\Models\Product;
use App\Models\UserAbo;
use App\Models\UserAboItem;
use App\Models\ShoppingUser;
use App\Models\UserAboOrder;
use App\Models\ShoppingOrder;
use App\Models\ShippingCountry;
use App\Models\ShoppingPayment;
use App\Models\ShoppingUser;
use App\Models\UserAboItem;
use App\User;
use Yard;
class AboOrderCart
{
private static $user_abo;
private static $is_for;
private static $customer_detail;
private static $is_for;
private static $customer_detail;
public static function initYard($user_abo)
{
@ -37,12 +33,12 @@ class AboOrderCart
\Log::info('AboOrderCart::initYard: Yard geleert', [
'abo_id' => $user_abo->id,
'items_vor_destroy' => $itemsBeforeDestroy
'items_vor_destroy' => $itemsBeforeDestroy,
]);
$itemsAfterDestroy = $yard->content()->count();
\Log::info('AboOrderCart::initYard: Yard geleert', [
'abo_id' => $user_abo->id,
'items_after_destroy' => $itemsAfterDestroy
'items_after_destroy' => $itemsAfterDestroy,
]);
self::$customer_detail = self::makeCustomerDetail($user_abo);
@ -56,6 +52,7 @@ class AboOrderCart
if ($country_id && $shipping_country = ShippingCountry::whereCountryId($country_id)->first()) {
if ($shipping_country->shipping && $shipping_country->shipping->active) {
UserService::initUserYard($user_abo->user, $shipping_country->id, 'abo-me');
return true;
}
}
@ -64,6 +61,7 @@ class AboOrderCart
if ($user_abo->is_for === 'ot') {
self::$is_for = 'abo-ot-customer';
UserService::initCustomerYard(self::$customer_detail, 'abo-ot-customer');
return true;
}
@ -90,7 +88,7 @@ class AboOrderCart
if ($itemsBefore > 0) {
\Log::warning('AboOrderCart::makeOrderYard: Yard war nicht leer vor makeOrderYard und wurde geleert', [
'abo_id' => $user_abo->id,
'items_before' => $itemsBefore
'items_before' => $itemsBefore,
]);
}
@ -105,9 +103,9 @@ class AboOrderCart
'id' => $item->id,
'product_id' => $item->product_id,
'qty' => $item->qty,
'comp' => $item->comp
'comp' => $item->comp,
];
})->toArray()
})->toArray(),
]);
foreach ($abo_items as $abo_item) {
@ -141,10 +139,11 @@ class AboOrderCart
'weight' => 0,
'points' => 0,
'comp' => $item->comp,
'product_id' => $product->id
'product_id' => $product->id,
]
);
Yard::setTax($cartItem->rowId, 0);
return true;
}
if (self::$is_for === 'ot-customer' || self::$is_for === 'abo-ot-customer') {
@ -153,10 +152,10 @@ class AboOrderCart
$product->id,
$product->getLang('name'),
$item->qty,
round($product->getPriceWith($tax_free, false, $user_country, false, self::$user_abo->user), 1),
round($product->getPriceWith($tax_free, false, $user_country, false, self::$user_abo->user), 1),
false,
false,
['image' => '', 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'show_on' => $product->show_on]
['image' => '', 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'no_free_shipping' => $product->no_free_shipping, 'show_on' => $product->show_on]
);
} else {
$cartItem = Yard::instance('shopping')
@ -164,10 +163,10 @@ class AboOrderCart
$product->id,
$product->getLang('name'),
$item->qty,
$product->getPriceWith($tax_free, true, $user_country, false, self::$user_abo->user),
$product->getPriceWith($tax_free, true, $user_country, false, self::$user_abo->user),
false,
false,
['image' => '', 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'show_on' => $product->show_on]
['image' => '', 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'no_free_shipping' => $product->no_free_shipping, 'show_on' => $product->show_on]
);
}
if ($tax_free) {
@ -189,7 +188,7 @@ class AboOrderCart
return true;
}
//need to add
// need to add
if (count($UserAboItems) < $needNumComp) {
$product = Product::whereActive(true)->where('shipping_addon', true)->whereJsonContains('show_on', '12')->orderBy('pos', 'DESC')->first();
for ($i = count($UserAboItems); $i <= $needNumComp; $i++) {
@ -200,13 +199,15 @@ class AboOrderCart
'qty' => 1,
'status' => 1,
]);
AboItemHistoryService::logSystemCompAdded($user_abo, $UserAboItem);
self::addProductToCart($UserAboItem);
}
}
//need to remove
// need to remove
if (count($UserAboItems) > $needNumComp) {
foreach ($UserAboItems as $UserAboItem) {
if ($UserAboItem->comp > $needNumComp) {
AboItemHistoryService::logSystemCompRemoved($user_abo, $UserAboItem);
$UserAboItem->delete();
}
}
@ -230,20 +231,20 @@ class AboOrderCart
{
if ($user_abo->is_for === 'me') {
//only on Abo!
// only on Abo!
$user = $user_abo->user;
// WICHTIG: Wenn bereits ein shopping_user existiert, diesen replizieren um alle Felder zu behalten
// Ansonsten neues Objekt erstellen
if ($user_abo->shopping_user) {
$shopping_user = $user_abo->shopping_user->replicate();
\Log::info('AboOrderCart::makeCustomerDetail: ShoppingUser repliziert für Abo ID: ' . $user_abo->id, [
\Log::info('AboOrderCart::makeCustomerDetail: ShoppingUser repliziert für Abo ID: '.$user_abo->id, [
'abo_id' => $user_abo->id,
'original_shopping_user_id' => $user_abo->shopping_user->id
'original_shopping_user_id' => $user_abo->shopping_user->id,
]);
} else {
$shopping_user = new ShoppingUser();
\Log::info('AboOrderCart::makeCustomerDetail: Neuer ShoppingUser erstellt für Abo ID: ' . $user_abo->id);
$shopping_user = new ShoppingUser;
\Log::info('AboOrderCart::makeCustomerDetail: Neuer ShoppingUser erstellt für Abo ID: '.$user_abo->id);
}
// Account-Daten überschreiben/aktualisieren
@ -258,9 +259,10 @@ class AboOrderCart
$shopping_user->billing_country_id = $user->account->country_id;
$shopping_user->billing_phone = $user->account->phone;
$shopping_user->billing_email = $user->email ?? null;
$shopping_user->language = $user->account->getLocale();
// Auth User ID setzen falls noch nicht gesetzt
if (!$shopping_user->auth_user_id) {
if (! $shopping_user->auth_user_id) {
$shopping_user->auth_user_id = $user->id;
}
if ($user->account->same_as_billing) {
@ -293,9 +295,10 @@ class AboOrderCart
}
if ($user_abo->is_for === 'ot') {
//look for the primary user of this abo
// look for the primary user of this abo
$shopping_user = $user_abo->shopping_user->replicate();
}
return $shopping_user;
}
}

View file

@ -34,12 +34,12 @@ class SalesPointsVolume
$form_user = User::find($form_user_id);
$shoppingOrder->user_sales_volume->user_id = $to_user_id;
$shoppingOrder->user_sales_volume->message = 'zugewiesen: ' . date('d.m.Y');
$shoppingOrder->user_sales_volume->message = 'zugewiesen: '.date('d.m.Y');
$syslog = $shoppingOrder->user_sales_volume->syslog;
$from_email = $form_user ? $form_user->email : '';
$to_email = $to_user ? $to_user->email : '';
$syslog[date('d.m.Y-h:i:s')] = 'change form: #' . $form_user_id . ' ' . $from_email . ' to: #' . $to_user_id . ' ' . $to_email;
$syslog[date('d.m.Y-h:i:s')] = 'change form: #'.$form_user_id.' '.$from_email.' to: #'.$to_user_id.' '.$to_email;
$shoppingOrder->user_sales_volume->syslog = $syslog;
$shoppingOrder->user_sales_volume->save();
@ -100,6 +100,17 @@ class SalesPointsVolume
case 5: // Registrierung
$month_points = self::add_KP_TP_Points($userSalesVolume, $month_points);
$month_total_net += $userSalesVolume->total_net;
break;
case 6: // Storno - negative Punkte und Beträge
$month_points = self::add_KP_TP_Points($userSalesVolume, $month_points);
if ($userSalesVolume->status_turnover === 2) {
$month_shop_points += $userSalesVolume->points; // bereits negativ
$month_shop_total_net += $userSalesVolume->total_net; // bereits negativ
} else {
$month_total_net += $userSalesVolume->total_net; // bereits negativ
}
break;
}
$userSalesVolume->month_shop_points = $month_shop_points;
@ -200,13 +211,13 @@ class SalesPointsVolume
$user_sales_volume->total_net = Util::reFormatNumber($data['total_net']);
$user_sales_volume->points = Util::reFormatNumber($data['points']);
$user_sales_volume->message = 'geändert: ' . date('d.m.Y');
$user_sales_volume->message = 'geändert: '.date('d.m.Y');
$user_sales_volume->info = $data['info'];
$user_sales_volume->status_points = $data['status_points'];
$user_sales_volume->status_turnover = isset($data['status_turnover']) ? intval($data['status_turnover']) : null;
$syslog = $user_sales_volume->syslog;
$syslog[date('d.m.Y-h:i:s')] = 'edit points: #' . $old_points . ' ' . $user_sales_volume->points . ' total: #' . $old_total_net . ' ' . $user_sales_volume->total_net;
$syslog[date('d.m.Y-h:i:s')] = 'edit points: #'.$old_points.' '.$user_sales_volume->points.' total: #'.$old_total_net.' '.$user_sales_volume->total_net;
$user_sales_volume->syslog = $syslog;
$user_sales_volume->save();
@ -231,7 +242,7 @@ class SalesPointsVolume
$total_net = isset($data['total_net']) ? Util::reFormatNumber($data['total_net']) : 0;
$points = isset($data['points']) ? Util::reFormatNumber($data['points']) : 0;
$syslog[date('d.m.Y-h:i:s')] = 'add points: #' . $points . ' total: #' . $total_net;
$syslog[date('d.m.Y-h:i:s')] = 'add points: #'.$points.' total: #'.$total_net;
$status = isset($data['status']) ? intval($data['status']) : 4;
$status_turnover = isset($data['status_turnover']) ? intval($data['status_turnover']) : null;
@ -245,7 +256,7 @@ class SalesPointsVolume
'status_points' => $data['status_points'],
'status_turnover' => $status_turnover,
'total_net' => $total_net,
'message' => 'hinzugefügt: ' . date('d.m.Y'),
'message' => 'hinzugefügt: '.date('d.m.Y'),
'info' => $data['info'],
'syslog' => $syslog,
'status' => $status,
@ -255,4 +266,61 @@ class SalesPointsVolume
\Session()->flash('alert-success', 'Points hinzugefügt');
}
/**
* Erstellt einen Storno-Eintrag für eine stornierte Rechnung
* Negative Punkte werden dem aktuellen Monat zugeordnet
*
* @param UserSalesVolume $original_sales_volume Der ursprüngliche Sales Volume Eintrag
* @param int $cancellation_invoice_id Die ID der Stornorechnung
* @return UserSalesVolume
*/
public static function cancelSalesPointsVolume(UserSalesVolume $original_sales_volume, $cancellation_invoice_id)
{
$month = date('m');
$year = date('Y');
$date = date('d.m.Y');
$syslog = [
date('d.m.Y H:i:s') => 'Stornorechnung erstellt - Punkte korrigiert (Original Invoice: #'.$original_sales_volume->user_invoice_id.')',
];
// Negativen Eintrag erstellen (alle Werte negativ)
$cancellation_sales_volume = UserSalesVolume::create([
'user_id' => $original_sales_volume->user_id,
'shopping_order_id' => $original_sales_volume->shopping_order_id,
'user_invoice_id' => $cancellation_invoice_id,
'month' => $month,
'year' => $year,
'date' => $date,
'points' => -$original_sales_volume->points, // Negativ!
'month_points' => 0, // Wird durch reCalculate gesetzt
'month_KP_points' => 0,
'month_TP_points' => 0,
'month_shop_points' => 0,
'total_net' => -$original_sales_volume->total_net, // Negativ!
'month_total_net' => 0,
'status' => 6, // Status 6 = cancelled/storniert
'status_points' => $original_sales_volume->status_points,
'status_turnover' => $original_sales_volume->status_turnover,
'message' => 'Storniert am '.date('d.m.Y'),
'info' => 'Storno für Invoice #'.$original_sales_volume->user_invoice_id,
'syslog' => $syslog,
]);
// Neuberechnung für aktuellen Monat
self::reCalculateSalesPointsVolume($original_sales_volume->user_id, $month, $year);
\Log::info('Punktekorrektur für Stornorechnung durchgeführt', [
'original_invoice_id' => $original_sales_volume->user_invoice_id,
'cancellation_invoice_id' => $cancellation_invoice_id,
'original_points' => $original_sales_volume->points,
'cancellation_points' => $cancellation_sales_volume->points,
'user_id' => $original_sales_volume->user_id,
'month' => $month,
'year' => $year,
]);
return $cancellation_sales_volume;
}
}

View file

@ -1,90 +1,116 @@
<?php
namespace App\Services;
use App\Services\Util;
use App\Models\Setting;
use App\Mail\MailCredit;
use App\Models\Setting;
use App\Models\UserCredit;
use Illuminate\Support\Facades\Mail;
class Credit
{
public static function getCreditNumber(){
//return (int) Setting::getContentBySlug('credit-number');
public static function getCreditNumber()
{
// return (int) Setting::getContentBySlug('credit-number');
return (int) Setting::getContentBySlug('invoice-number');
}
public static function makeNextCreditNumber(){
public static function makeNextCreditNumber()
{
$credit_number = self::getCreditNumber();
$credit_number = $credit_number+1;
//Setting::setContentBySlug('credit-number', $credit_number, 'int');
$credit_number = $credit_number + 1;
// Setting::setContentBySlug('credit-number', $credit_number, 'int');
Setting::setContentBySlug('invoice-number', $credit_number, 'int');
return $credit_number;
}
public static function createCreditNumber($credit_number, $credit_date){
public static function createCreditNumber($credit_number, $credit_date)
{
$prefix = "GS".\Carbon::parse($credit_date)->format('Y');
$prefix = 'GS'.\Carbon::parse($credit_date)->format('Y');
$credit_number = str_pad($credit_number, 5, '0', STR_PAD_LEFT);
return $prefix.$credit_number;
}
public static function getCreditStorageDir($credit_date){
return "/credit/".\Carbon::parse($credit_date)->format('Y/m/');
public static function getCreditStorageDir($credit_date)
{
return '/credit/'.\Carbon::parse($credit_date)->format('Y/m/');
}
public static function getCreditDetailStorageDir($credit_date){
return "/credit_details/".\Carbon::parse($credit_date)->format('Y/m/');
public static function getCreditDetailStorageDir($credit_date)
{
return '/credit_details/'.\Carbon::parse($credit_date)->format('Y/m/');
}
public static function makeCreditFilename($credit_number){
return $credit_number."-MIVITA-Gutschrift.pdf";
public static function makeCreditFilename($credit_number)
{
return $credit_number.'-MIVITA-Gutschrift.pdf';
}
public static function makeCreditDetailFilename($credit_number){
return $credit_number."-MIVITA-Report.pdf";
public static function makeCreditDetailFilename($credit_number)
{
return $credit_number.'-MIVITA-Report.pdf';
}
public static function isCredit(UserCredit $user_credit){
/**
* Erstellt den Dateinamen für eine lokalisierte Gutschrift.
* Deutsch (de) ist das Original ohne Suffix.
*
* @param string $credit_number
* @param string $locale
* @return string
*/
public static function makeCreditFilenameLocale($credit_number, $locale)
{
if ($locale === 'de' || ! $locale) {
return self::makeCreditFilename($credit_number);
}
return $credit_number.'-MIVITA-Gutschrift-'.$locale.'.pdf';
}
public static function isCredit(UserCredit $user_credit)
{
return $user_credit->isCredit();
}
/*public static function getFilename(UserCredit $user_credit){
return $user_credit->filename;
/*public static function getFilename(UserCredit $user_credit){
return $user_credit->filename;
}
public static function getDir(UserCredit $user_credit){
return $user_credit->dir;
return $user_credit->dir;
}
public static function getDownloadURL(UserCredit $user_credit, $do = false){
return route('storage_file', [$user_credit->id, 'cms_download_file', $do]);
return route('storage_file', [$user_credit->id, 'cms_download_file', $do]);
}
public static function getDownloadPath(UserCredit $user_credit, $full = false){
$dir = self::getDir($user_credit);
$filename = self::getFilename($user_credit);
if(!$full){
return $dir.$filename;
}
return \Storage::disk('public')->path($dir.$filename);
$dir = self::getDir($user_credit);
$filename = self::getFilename($user_credit);
if(!$full){
return $dir.$filename;
}
return \Storage::disk('public')->path($dir.$filename);
}*/
public static function sendCreditMail(UserCredit $user_credit){
public static function sendCreditMail(UserCredit $user_credit)
{
$bcc = [];
$email = $user_credit->user->email;
if(!$email){
if($user_credit->user->mode === 'test'){
}else{
if (! $email) {
if ($user_credit->user->mode === 'test') {
} else {
$email = config('app.checkout_mail');
}
}
if($user_credit->user->mode === 'test'){
if ($user_credit->user->mode === 'test') {
$bcc[] = config('app.checkout_test_mail');
}else{
} else {
$bcc[] = config('app.checkout_mail');
}
Mail::to($email)->bcc($bcc)->locale($user_credit->user->getLocale())->send(new MailCredit($user_credit));

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,11 @@
namespace App\Services;
use Acme\Dhl\Models\DhlShipment;
use Acme\Dhl\Models\DhlTrackingEvent;
use App\Http\Controllers\SettingController;
use App\Jobs\TrackShipmentJob;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
@ -42,6 +44,7 @@ class DhlTrackingService
Log::info('[DHL Tracking Service] Tracking shipment with Unified API', [
'tracking_number' => $trackingNumber,
'is_sandbox' => $this->isSandbox,
'has_api_key' => ! empty($this->apiKey),
]);
$response = Http::withHeaders([
@ -60,16 +63,21 @@ class DhlTrackingService
CURLOPT_CONNECTTIMEOUT => config('dhl.ssl.connect_timeout', 10),
CURLOPT_TIMEOUT => config('dhl.ssl.timeout', 30),
CURLOPT_USERAGENT => 'acme-laravel-dhl/1.0',
]
],
])
->get('https://api.dhl.com/track/shipments', [
->get('https://api-eu.dhl.com/track/shipments', [
'trackingNumber' => $trackingNumber,
'service' => 'express,parcel',
'requesterCountryCode' => 'DE',
'originCountryCode' => 'DE',
'language' => 'de',
]);
Log::info('[DHL Tracking Service] Unified API response', [
'tracking_number' => $trackingNumber,
'status_code' => $response->status(),
'successful' => $response->successful(),
]);
if ($response->successful()) {
$data = $response->json();
@ -80,8 +88,8 @@ class DhlTrackingService
'success' => true,
'tracking_number' => $shipment['id'],
'status' => $shipment['status']['statusCode'] ?? 'unknown',
'status_text' => $shipment['status']['status'] ?? 'Unbekannt',
'description' => $shipment['status']['description'] ?? '',
'status_text' => $shipment['status']['description'] ?? ($shipment['status']['status'] ?? 'Unbekannt'),
'description' => $shipment['status']['remark'] ?? ($shipment['status']['description'] ?? ''),
'last_update' => $shipment['status']['timestamp'] ?? null,
'origin' => $shipment['origin']['address']['addressLocality'] ?? null,
'destination' => $shipment['destination']['address']['addressLocality'] ?? null,
@ -91,6 +99,12 @@ class DhlTrackingService
}
}
Log::warning('[DHL Tracking Service] Unified API did not find shipment, trying Parcel DE API', [
'tracking_number' => $trackingNumber,
'status_code' => $response->status(),
'response_snippet' => mb_substr($response->body(), 0, 500),
]);
// If Unified API fails, try Parcel DE API
return $this->trackShipmentDE($trackingNumber, $options);
} catch (Exception $e) {
@ -113,13 +127,14 @@ class DhlTrackingService
Log::info('[DHL Tracking Service] Tracking shipment with Parcel DE API', [
'tracking_number' => $trackingNumber,
'is_sandbox' => $this->isSandbox,
'has_api_key' => ! empty($this->apiKey),
'has_api_secret' => ! empty($this->apiSecret),
]);
$response = Http::withBasicAuth($this->apiKey, $this->apiSecret)
->withHeaders([
'Accept' => 'application/json',
'dhl-api-key' => $this->apiKey,
])
$response = Http::withHeaders([
'DHL-API-Key' => $this->apiKey,
'Accept' => 'application/json',
])
->withOptions([
'verify' => config('dhl.ssl.verify_peer', true),
'http_errors' => false,
@ -132,13 +147,20 @@ class DhlTrackingService
CURLOPT_CONNECTTIMEOUT => config('dhl.ssl.connect_timeout', 10),
CURLOPT_TIMEOUT => config('dhl.ssl.timeout', 30),
CURLOPT_USERAGENT => 'acme-laravel-dhl/1.0',
]
],
])
->get('https://api.dhl.com/parcel/de/tracking/v1/shipments', [
'trackingNumber' => $trackingNumber,
->get('https://api-eu.dhl.com/parcel/de/tracking/v0/shipments', [
'shipmentId' => $trackingNumber,
'language' => 'de',
]);
Log::info('[DHL Tracking Service] Parcel DE API response', [
'tracking_number' => $trackingNumber,
'status_code' => $response->status(),
'successful' => $response->successful(),
'response_body' => $response->body(),
]);
if ($response->successful()) {
$data = $response->json();
@ -158,21 +180,33 @@ class DhlTrackingService
}
}
// Log detailed error information
Log::warning('[DHL Tracking Service] Shipment not found or not yet tracked', [
'tracking_number' => $trackingNumber,
'status_code' => $response->status(),
'response_body' => $response->body(),
]);
return [
'success' => false,
'message' => 'Sendung nicht gefunden oder noch nicht im System erfasst.',
'message' => 'Sendung nicht gefunden oder noch nicht im System erfasst. HTTP Status: '.$response->status(),
'tracking_number' => $trackingNumber,
'api_used' => 'parcel_de',
'debug_info' => [
'status_code' => $response->status(),
'response' => $response->json(),
],
];
} catch (Exception $e) {
Log::error('[DHL Tracking Service] Parcel DE API failed', [
'tracking_number' => $trackingNumber,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return [
'success' => false,
'message' => 'Fehler beim Abrufen der Tracking-Informationen: ' . $e->getMessage(),
'message' => 'Fehler beim Abrufen der Tracking-Informationen: '.$e->getMessage(),
'tracking_number' => $trackingNumber,
'api_used' => 'parcel_de',
];
@ -208,11 +242,10 @@ class DhlTrackingService
CURLOPT_CONNECTTIMEOUT => config('dhl.ssl.connect_timeout', 10),
CURLOPT_TIMEOUT => config('dhl.ssl.timeout', 30),
CURLOPT_USERAGENT => 'acme-laravel-dhl/1.0',
]
],
])
->get('https://api.dhl.com/track/shipments', [
->get('https://api-eu.dhl.com/track/shipments', [
'trackingNumber' => implode(',', $trackingNumbers),
'service' => 'parcel',
'requesterCountryCode' => 'DE',
'language' => 'de',
]);
@ -250,7 +283,7 @@ class DhlTrackingService
return [
'success' => false,
'message' => 'Fehler beim Abrufen der Tracking-Informationen: ' . $e->getMessage(),
'message' => 'Fehler beim Abrufen der Tracking-Informationen: '.$e->getMessage(),
];
}
}
@ -302,7 +335,7 @@ class DhlTrackingService
return [
'success' => false,
'message' => 'Fehler beim Einreihen des Tracking-Updates: ' . $e->getMessage(),
'message' => 'Fehler beim Einreihen des Tracking-Updates: '.$e->getMessage(),
'queued' => false,
];
}
@ -333,17 +366,31 @@ class DhlTrackingService
$result = $this->trackShipment($shipment->dhl_shipment_no);
if ($result['success']) {
$internalStatus = $this->mapDhlStatusToInternal($result['status']);
// Update shipment with tracking data
$shipment->update([
'status' => $this->mapDhlStatusToInternal($result['status']),
$updateData = [
'status' => $internalStatus,
'tracking_status' => $result['status_text'],
'last_tracked_at' => now(),
]);
];
// Mark tracking as completed if terminal status reached
if (in_array($internalStatus, DhlShipment::TERMINAL_STATUSES)) {
$updateData['tracking_completed_at'] = now();
}
$shipment->update($updateData);
// Save tracking events
$this->saveTrackingEvents($shipment, $result['events'] ?? []);
Log::info('[DHL Tracking Service] Tracking updated successfully (sync)', [
'shipment_id' => $shipment->id,
'dhl_shipment_no' => $shipment->dhl_shipment_no,
'tracking_status' => $result['status'],
'tracking_completed' => in_array($internalStatus, DhlShipment::TERMINAL_STATUSES),
'events_count' => count($result['events'] ?? []),
'api_used' => $result['api_used'],
]);
@ -353,6 +400,7 @@ class DhlTrackingService
'queued' => false,
'shipment_id' => $shipment->id,
'tracking_status' => $result['status'],
'tracking_completed' => in_array($internalStatus, DhlShipment::TERMINAL_STATUSES),
'tracking_details' => $result,
];
} else {
@ -371,13 +419,177 @@ class DhlTrackingService
return [
'success' => false,
'message' => 'Fehler beim Aktualisieren der Tracking-Informationen: ' . $e->getMessage(),
'message' => 'Fehler beim Aktualisieren der Tracking-Informationen: '.$e->getMessage(),
'queued' => false,
'shipment_id' => $shipment->id,
];
}
}
/**
* Update tracking for a batch of DHL shipments using the multi-tracking API.
* Processes shipments in chunks of 10 (DHL API limit) with rate-limiting pauses.
*
* @param Collection<DhlShipment> $shipments
* @return array{updated: int, failed: int, completed: int, results: array}
*/
public function updateTrackingBatch(Collection $shipments): array
{
$stats = [
'updated' => 0,
'failed' => 0,
'completed' => 0,
'results' => [],
];
// Process in chunks of 10 (DHL API limit)
$chunks = $shipments->chunk(10);
$chunkIndex = 0;
foreach ($chunks as $chunk) {
// Rate limiting: pause 1 second between batch API calls
if ($chunkIndex > 0) {
sleep(1);
}
$chunkIndex++;
// Build tracking number => shipment mapping
$shipmentMap = [];
foreach ($chunk as $shipment) {
$shipmentMap[$shipment->dhl_shipment_no] = $shipment;
}
$trackingNumbers = array_keys($shipmentMap);
try {
$batchResult = $this->trackMultipleShipments($trackingNumbers);
if ($batchResult['success'] && ! empty($batchResult['shipments'])) {
// Process each result from the batch API
foreach ($batchResult['shipments'] as $trackingResult) {
$trackingNo = $trackingResult['tracking_number'];
$shipment = $shipmentMap[$trackingNo] ?? null;
if (! $shipment) {
Log::warning('[DHL Tracking Service] Batch: tracking number not mapped', [
'tracking_number' => $trackingNo,
]);
continue;
}
// Remove from map so we can detect missing ones later
unset($shipmentMap[$trackingNo]);
$internalStatus = $this->mapDhlStatusToInternal($trackingResult['status']);
$updateData = [
'status' => $internalStatus,
'tracking_status' => $trackingResult['status_text'],
'last_tracked_at' => now(),
];
// Mark tracking as completed if terminal status reached
$isCompleted = in_array($internalStatus, DhlShipment::TERMINAL_STATUSES);
if ($isCompleted) {
$updateData['tracking_completed_at'] = now();
$stats['completed']++;
}
$shipment->update($updateData);
// Save tracking events
$this->saveTrackingEvents($shipment, $trackingResult['events'] ?? []);
$stats['updated']++;
$stats['results'][] = [
'shipment_id' => $shipment->id,
'tracking_number' => $trackingNo,
'status' => $internalStatus,
'completed' => $isCompleted,
'success' => true,
];
}
// Any remaining shipments in the map were not returned by the API
foreach ($shipmentMap as $trackingNo => $shipment) {
// Update last_tracked_at so we don't immediately retry
$shipment->update(['last_tracked_at' => now()]);
$stats['failed']++;
$stats['results'][] = [
'shipment_id' => $shipment->id,
'tracking_number' => $trackingNo,
'success' => false,
'message' => 'Nicht in Batch-Antwort enthalten',
];
}
} else {
// Entire batch failed - fall back to individual tracking
Log::warning('[DHL Tracking Service] Batch tracking failed, falling back to individual tracking', [
'tracking_numbers' => $trackingNumbers,
'message' => $batchResult['message'] ?? 'Unknown error',
]);
foreach ($chunk as $shipment) {
try {
$result = $this->updateTracking($shipment, ['auto_retrack' => false]);
if ($result['success']) {
$stats['updated']++;
if (! empty($result['tracking_completed'])) {
$stats['completed']++;
}
} else {
$stats['failed']++;
}
$stats['results'][] = [
'shipment_id' => $shipment->id,
'tracking_number' => $shipment->dhl_shipment_no,
'success' => $result['success'],
'fallback' => true,
];
} catch (Exception $e) {
$stats['failed']++;
$stats['results'][] = [
'shipment_id' => $shipment->id,
'tracking_number' => $shipment->dhl_shipment_no,
'success' => false,
'message' => $e->getMessage(),
'fallback' => true,
];
}
}
}
} catch (Exception $e) {
Log::error('[DHL Tracking Service] Batch tracking exception', [
'tracking_numbers' => $trackingNumbers,
'error' => $e->getMessage(),
]);
// Mark all as failed but update last_tracked_at
foreach ($chunk as $shipment) {
$shipment->update(['last_tracked_at' => now()]);
$stats['failed']++;
$stats['results'][] = [
'shipment_id' => $shipment->id,
'tracking_number' => $shipment->dhl_shipment_no,
'success' => false,
'message' => $e->getMessage(),
];
}
}
}
Log::info('[DHL Tracking Service] Batch tracking completed', [
'total' => $shipments->count(),
'updated' => $stats['updated'],
'failed' => $stats['failed'],
'completed' => $stats['completed'],
'chunks' => $chunks->count(),
]);
return $stats;
}
/**
* Map DHL status codes to internal status
*/
@ -414,6 +626,39 @@ class DhlTrackingService
return $descriptions[$statusCode] ?? 'Unbekannter Status';
}
/**
* Save tracking events from API response to database
*/
private function saveTrackingEvents(DhlShipment $shipment, array $events): void
{
if (empty($events)) {
return;
}
foreach ($events as $event) {
$eventTime = isset($event['timestamp']) ? \Carbon\Carbon::parse($event['timestamp']) : now();
// Upsert: avoid duplicates based on shipment + event_time + status_code
DhlTrackingEvent::updateOrCreate(
[
'shipment_id' => $shipment->id,
'event_time' => $eventTime,
'status_code' => $event['statusCode'] ?? ($event['status'] ?? 'unknown'),
],
[
'status_text' => $event['description'] ?? ($event['remark'] ?? 'Unbekannt'),
'location' => $event['location']['address']['addressLocality'] ?? null,
'raw' => $event,
]
);
}
Log::info('[DHL Tracking Service] Tracking events saved', [
'shipment_id' => $shipment->id,
'events_saved' => count($events),
]);
}
/**
* Get SSL version constant based on configuration
*/

View file

@ -1,4 +1,5 @@
<?php
namespace App\Services;
use App\Mail\MailInvoice;
@ -8,60 +9,171 @@ use Illuminate\Support\Facades\Mail;
class Invoice
{
public static function getInvoiceNumber(){
public static function getInvoiceNumber()
{
return (int) Setting::getContentBySlug('invoice-number');
}
public static function makeNextInvoiceNumber(){
$invoice_number = self::getInvoiceNumber();
$invoice_number = $invoice_number+1;
Setting::setContentBySlug('invoice-number', $invoice_number, 'int');
return $invoice_number;
/**
* Atomically get and increment the invoice number with database locking
* to prevent race conditions when multiple invoices are created simultaneously
*/
public static function makeNextInvoiceNumber()
{
return \DB::transaction(function () {
// Lock the setting row for update to prevent concurrent reads
$setting = Setting::where('slug', 'invoice-number')
->lockForUpdate()
->first();
if (! $setting) {
// Create if not exists
$setting = Setting::create([
'slug' => 'invoice-number',
'type' => 'int',
'int' => 1,
]);
return 1;
}
$invoice_number = (int) $setting->int;
$invoice_number = $invoice_number + 1;
// Update directly on the locked row
$setting->int = $invoice_number;
$setting->save();
return $invoice_number;
});
}
public static function createInvoiceNumber($invoice_number, $invoice_date){
public static function createInvoiceNumber($invoice_number, $invoice_date)
{
$prefix = \Carbon::parse($invoice_date)->format('Y');
$invoice_number = str_pad($invoice_number, 5, '0', STR_PAD_LEFT);
return $prefix.$invoice_number;
}
public static function getInvoiceStorageDir($invoice_date){
return "invoice/".\Carbon::parse($invoice_date)->format('Y/m/');
public static function getInvoiceStorageDir($invoice_date)
{
return 'invoice/'.\Carbon::parse($invoice_date)->format('Y/m/');
}
public static function getDeliveryStorageDir($invoice_date){
return "delivery/".\Carbon::parse($invoice_date)->format('Y/m/');
public static function getDeliveryStorageDir($invoice_date)
{
return 'delivery/'.\Carbon::parse($invoice_date)->format('Y/m/');
}
public static function makeInvoiceFilename($invoice_number){
return $invoice_number."-MIVITA-Rechnung.pdf";
public static function makeInvoiceFilename($invoice_number)
{
return $invoice_number.'-MIVITA-Rechnung.pdf';
}
public static function makeDeliveryFilename($invoice_number){
return $invoice_number."-MIVITA-Lieferschein.pdf";
public static function makeDeliveryFilename($invoice_number)
{
return $invoice_number.'-MIVITA-Lieferschein.pdf';
}
public static function isInvoice(ShoppingOrder $shopping_order){
/**
* Erstellt den Dateinamen für eine Stornorechnung
*
* @param string $invoice_number
* @return string
*/
public static function makeCancellationFilename($invoice_number)
{
return $invoice_number.'-MIVITA-Stornorechnung.pdf';
}
/**
* Erstellt den Dateinamen für einen Storno-Lieferschein
*
* @param string $invoice_number
* @return string
*/
public static function makeCancellationDeliveryFilename($invoice_number)
{
return $invoice_number.'-MIVITA-Storno-Lieferschein.pdf';
}
/**
* Erstellt den Dateinamen für eine lokalisierte Rechnung.
* Deutsch (de) ist das Original ohne Suffix.
*
* @param string $invoice_number
* @param string $locale
* @return string
*/
public static function makeInvoiceFilenameLocale($invoice_number, $locale)
{
if ($locale === 'de' || ! $locale) {
return self::makeInvoiceFilename($invoice_number);
}
return $invoice_number.'-MIVITA-Rechnung-'.$locale.'.pdf';
}
/**
* Erstellt den Dateinamen für einen lokalisierten Lieferschein.
* Deutsch (de) ist das Original ohne Suffix.
*
* @param string $invoice_number
* @param string $locale
* @return string
*/
public static function makeDeliveryFilenameLocale($invoice_number, $locale)
{
if ($locale === 'de' || ! $locale) {
return self::makeDeliveryFilename($invoice_number);
}
return $invoice_number.'-MIVITA-Lieferschein-'.$locale.'.pdf';
}
public static function isInvoice(ShoppingOrder $shopping_order)
{
return $shopping_order->isInvoice();
}
public static function sendInvoiceMail($shopping_order, $user_invoice){
/**
* Holt die Rechnungsnummer einer Bestellung
*
* @return string|null
*/
public static function getNumber(ShoppingOrder $shopping_order)
{
return $shopping_order->user_invoice ? $shopping_order->user_invoice->full_number : null;
}
/**
* Holt das Rechnungsdatum einer Bestellung
*
* @return string|null
*/
public static function getDate(ShoppingOrder $shopping_order)
{
return $shopping_order->user_invoice ? $shopping_order->user_invoice->date : null;
}
public static function sendInvoiceMail($shopping_order, $user_invoice)
{
$bcc = [];
$billing_email = $shopping_order->shopping_user->billing_email;
if(!$billing_email){
if($shopping_order->mode === 'test'){
if (! $billing_email) {
if ($shopping_order->mode === 'test') {
$billing_email = config('app.checkout_test_mail');
}else{
} else {
$billing_email = config('app.checkout_mail');
}
}
if($shopping_order->mode === 'test'){
if ($shopping_order->mode === 'test') {
$bcc[] = config('app.checkout_test_mail');
}else{
} else {
$bcc[] = config('app.checkout_mail');
}
Mail::to($billing_email)->bcc($bcc)->locale($shopping_order->getLocale())->send(new MailInvoice($shopping_order, $user_invoice));
}
}

View file

@ -2,19 +2,18 @@
namespace App\Services;
use App\Models\ShoppingUser;
use App\Models\ShoppingInstance;
use App\Models\ShoppingUser;
use Yard;
class OrderPaymentService
{
public static function deleteInstance($identifier)
{
Yard::instance('shopping')->deleteStoredCart($identifier);
\App\Models\ShoppingInstance::where('identifier', $identifier)->delete();
//delete session
\App\Models\ShoppingInstance::where('identifier', $identifier)->delete();
// delete session
/* if(\Session::has('user_shop_payment') && \Session::get('user_shop_payment') === 6){
$user_shop_identifier = \Session::get('user_shop_identifier');
Yard::instance('shopping')->deleteStoredCart($identifier);
@ -24,7 +23,7 @@ class OrderPaymentService
public static function updateInstanceStatus($identifier, $status, $lower = true)
{
if (!ShoppingInstance::where('identifier', $identifier)->exists()) {
if (! ShoppingInstance::where('identifier', $identifier)->exists()) {
return false;
}
if ($lower) {
@ -39,9 +38,10 @@ class OrderPaymentService
public static function getInstanceStatus($identifier)
{
$shopping_instance = ShoppingInstance::where('identifier', $identifier)->first();
if (!$shopping_instance) {
if (! $shopping_instance) {
return false;
}
return $shopping_instance->getStatus();
}
@ -49,33 +49,39 @@ class OrderPaymentService
{
$isFor = $shoppingInstance->shopping_data['is_for'] ?? '-';
if ($isFor === 'abo-ot-customer') {
return ' <span class="badge badge-pill badge-warning">' . __('abo.abo') . '</span>';
return ' <span class="badge badge-pill badge-warning">'.__('abo.abo').'</span>';
}
if ($isFor === 'ot-customer') {
return ' <span class="badge badge-pill badge-secondary">' . __('order.order') . '</span>';
return ' <span class="badge badge-pill badge-secondary">'.__('order.order').'</span>';
}
return "";
return '';
}
public static function getStatusBadgeClasses()
{
return [
'link_sent' => 'default',
'link_openly' => 'info',
'link_check' => 'warning',
'link_pending' => 'warning',
'link_appointed' => 'warning',
'link_paid' => 'secondary',
'link_failed' => 'danger',
'link_canceled' => 'danger',
];
}
public static function getStatusBadge(ShoppingInstance $shoppingInstance)
{
$status = $shoppingInstance->getStatus();
$badgeClasses = [
'link_sent' => 'info',
'link_openly' => 'info',
'link_paid' => 'secondary',
'link_check' => 'warning',
'link_pending' => 'warning',
'link_appointed' => 'warning',
'link_failed' => 'danger',
'link_canceled' => 'danger'
];
$badgeClasses = self::getStatusBadgeClasses();
if (isset($badgeClasses[$status])) {
return sprintf(
' <span class="badge badge-pill badge-%s">%s</span>',
$badgeClasses[$status],
__('payment.' . $status)
__('payment.'.$status)
);
}
@ -100,7 +106,7 @@ class OrderPaymentService
return sprintf(
' <div class="alert alert-%s">%s</div>',
$badgeClasses[$status],
__('payment.alert_' . $status)
__('payment.alert_'.$status)
);
}
@ -111,12 +117,12 @@ class OrderPaymentService
{
$shopping_instance = ShoppingInstance::where('identifier', $identifier)->first();
if (!$shopping_instance) {
if (! $shopping_instance) {
abort(403, __('msg.shopping_instance_not_found'));
}
$shopping_data = $shopping_instance->shopping_data;
$shopping_user = $shopping_data['shopping_user_id'] ? ShoppingUser::find($shopping_data['shopping_user_id']) : null;
if (!$shopping_user) {
if (! $shopping_user) {
abort(403, __('msg.shopping_user_not_found'));
}
$yard_shopping_items = self::getRestoredYardShoppingItems($shopping_instance);
@ -130,6 +136,7 @@ class OrderPaymentService
'is_for' => $shopping_instance->shopping_data['is_for'] ?? false,
'backlink' => false,
];
return $data;
}
@ -145,7 +152,6 @@ class OrderPaymentService
Yard::instance('shopping')->setUserPriceInfos($shopping_instance->shopping_data['user_price_infos']);
Yard::instance('shopping')->setShippingCountryWithPrice($shopping_instance->country_id, $is_for);
$rows = Yard::instance('shopping')->getContentByOrder();
$ret = [];
$ret['items'] = [];
@ -154,12 +160,12 @@ class OrderPaymentService
foreach ($rows as $row) {
$product = \App\Models\Product::find($row->id);
$item = new \stdClass();
$item = new \stdClass;
$item->image = $row->options->has('image') ? $row->options->image : null;
$item->price_net = (float) Yard::instance('shopping')->rowPriceNet($row, 3, '.', '');
$item->price_net_total = (float) Yard::instance('shopping')->rowSubtotalNet($row, 2, '.', '');
$item->price_currency = $is_currency ? "~" . Yard::instance('shopping')->getCurrencyByKey('rowPriceNetCurrency', $row, 3) . " " . Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$item->price_currency_total = $is_currency ? "~" . Yard::instance('shopping')->getCurrencyByKey('rowSubtotalCurrency', $row, 3) . " " . Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$item->price_currency = $is_currency ? '~'.Yard::instance('shopping')->getCurrencyByKey('rowPriceNetCurrency', $row, 3).' '.Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$item->price_currency_total = $is_currency ? '~'.Yard::instance('shopping')->getCurrencyByKey('rowSubtotalCurrency', $row, 3).' '.Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$item->price = $row->price;
$item->price_total = ($row->qty * $row->price);
$item->qty = $row->qty;
@ -174,16 +180,16 @@ class OrderPaymentService
$ret['tax_free'] = $tax_free;
$ret['total']['subtotal'] = Yard::instance('shopping')->subtotal();
$ret['total']['subtotal_currency'] = $is_currency ? "~" . Yard::instance('shopping')->getCurrencyByKey('subtotal') . " " . Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$ret['total']['subtotal_currency'] = $is_currency ? '~'.Yard::instance('shopping')->getCurrencyByKey('subtotal').' '.Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$ret['total']['shippingCountryName'] = Yard::instance('shopping')->getShippingCountryName();
$ret['total']['shippingNet'] = Yard::instance('shopping')->shippingNet();
$ret['total']['shippingNet currency'] = $is_currency ? "~" . Yard::instance('shopping')->getCurrencyByKey('shippingNet') . " " . Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$ret['total']['shippingNet currency'] = $is_currency ? '~'.Yard::instance('shopping')->getCurrencyByKey('shippingNet').' '.Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$ret['total']['subtotalWithShipping'] = Yard::instance('shopping')->subtotalWithShipping();
$ret['total']['subtotalWithShipping_currency'] = $is_currency ? "~" . Yard::instance('shopping')->getCurrencyByKey('subtotalWithShipping') . " " . Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$ret['total']['subtotalWithShipping_currency'] = $is_currency ? '~'.Yard::instance('shopping')->getCurrencyByKey('subtotalWithShipping').' '.Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$ret['total']['taxWithShipping'] = Yard::instance('shopping')->taxWithShipping();
$ret['total']['taxWithShipping_currency'] = $is_currency ? "~" . Yard::instance('shopping')->getCurrencyByKey('taxWithShipping') . " " . Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$ret['total']['taxWithShipping_currency'] = $is_currency ? '~'.Yard::instance('shopping')->getCurrencyByKey('taxWithShipping').' '.Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$ret['total']['totalWithShipping'] = Yard::instance('shopping')->totalWithShipping();
$ret['total']['totalWithShipping_currency'] = $is_currency ? "~" . Yard::instance('shopping')->getCurrencyByKey('totalWithShipping') . " " . Yard::instance('shopping')->getPriceCurrencyUnit() : null;
$ret['total']['totalWithShipping_currency'] = $is_currency ? '~'.Yard::instance('shopping')->getCurrencyByKey('totalWithShipping').' '.Yard::instance('shopping')->getPriceCurrencyUnit() : null;
return $ret;
}

View file

@ -2,7 +2,6 @@
namespace App\Services;
use App\Mail\MailCheckout;
use App\Models\ProductBuying;
use App\Models\ShoppingOrder;
@ -10,26 +9,22 @@ use App\Models\ShoppingPayment;
use App\Models\UserCreditItem;
use App\Models\UserLevel;
use App\Repositories\InvoiceRepository;
use App\Services\AboHelper;
use App\Services\BusinessPlan\SalesPointsVolume;
use App\Services\ShopApiOrderCart;
use App\Services\UserUtil;
use App\Services\Util;
use App\User;
use Illuminate\Support\Facades\Mail;
class Payment
{
public static $txaction_text = [
'paid' => 'paid',
'appointed' => 'open',
'failed' => 'failed',
'extern' => 'open', //offen
'extern' => 'open', // offen
'extern_paid' => 'paid',
'invoice_open' => 'open',
'invoice_paid' => 'paid',
'invoice_non' => 'no_payment',
'cancelled' => 'cancelled',
'NULL' => 'no_payment',
];
@ -37,11 +32,12 @@ class Payment
'paid' => 'paymend_paid',
'appointed' => 'paymend_open',
'failed' => 'paymend_failed',
'extern' => 'extern_open', //offen
'extern' => 'extern_open', // offen
'extern_paid' => 'extern_paid',
'invoice_open' => 'invoice_open',
'invoice_paid' => 'invoice_paid',
'invoice_non' => 'invoice_no_payment',
'cancelled' => 'cancelled',
'NULL' => 'no_payment',
];
@ -60,15 +56,16 @@ class Payment
'invoice_open' => 'warning',
'invoice_paid' => 'success',
'invoice_non' => 'failed',
'cancelled' => 'danger',
];
public static function getFormattedTxaction($txaction)
{
if ($txaction && isset(self::$txaction_text[$txaction])) {
return __('payment.' . self::$txaction_text[$txaction]);
return __('payment.'.self::$txaction_text[$txaction]);
}
return __('payment.' . self::$txaction_text['NULL']);
return __('payment.'.self::$txaction_text['NULL']);
}
public static function getFormattedTxactionColor($txaction)
@ -76,15 +73,17 @@ class Payment
if ($txaction && isset(self::$txaction_color[$txaction])) {
return self::$txaction_color[$txaction];
}
return "warning";
return 'warning';
}
public static function getTransTxactionFilterText()
{
$ret = [];
foreach (self::$txaction_filter_text as $key => $val) {
$ret[$key] = trans('payment.' . $val);
$ret[$key] = trans('payment.'.$val);
}
return $ret;
}
@ -92,8 +91,9 @@ class Payment
{
$ret = [];
foreach (self::$txaction_invoice as $key => $val) {
$ret[$key] = trans('payment.' . $val);
$ret[$key] = trans('payment.'.$val);
}
return $ret;
}
@ -101,29 +101,32 @@ class Payment
{
if ($shopping_order->mode === 'test') {
return '<span class="badge badge-pill badge-default">' . strtoupper($shopping_order->mode) . ' - ' . self::getFormattedTxaction($shopping_order->txaction) . '</span>';
return '<span class="badge badge-pill badge-default">'.strtoupper($shopping_order->mode).' - '.self::getFormattedTxaction($shopping_order->txaction).'</span>';
}
if ($shopping_order->mode === 'dev') {
return '<span class="badge badge-pill badge-info">' . strtoupper($shopping_order->mode) . ' - ' . self::getFormattedTxaction($shopping_order->txaction) . '</span>';
return '<span class="badge badge-pill badge-info">'.strtoupper($shopping_order->mode).' - '.self::getFormattedTxaction($shopping_order->txaction).'</span>';
}
return '<span class="badge badge-pill badge-' . self::getFormattedTxactionColor($shopping_order->txaction) . '">' . self::getFormattedTxaction($shopping_order->txaction) . '</span>';
return '<span class="badge badge-pill badge-'.self::getFormattedTxactionColor($shopping_order->txaction).'">'.self::getFormattedTxaction($shopping_order->txaction).'</span>';
}
public static function getPaymentForBadge(ShoppingOrder $shopping_order)
{
$abo = '';
if ($shopping_order->is_abo) {
$abo = ' <span class="badge badge-pill badge-success">' . __('abo.abo') . '</span>';
$abo = ' <span class="badge badge-pill badge-success">'.__('abo.abo').'</span>';
}
return '<span class="badge badge-pill badge-' . $shopping_order->getPaymentForColor() . '">' . $shopping_order->getPaymentForType() . '</span>' . $abo;
return '<span class="badge badge-pill badge-'.$shopping_order->getPaymentForColor().'">'.$shopping_order->getPaymentForType().'</span>'.$abo;
}
public static function getShoppingPaymentBadge(ShoppingPayment $shopping_payment)
{
if ($shopping_payment->mode === 'test') {
return '<span class="badge badge-pill badge-default">' . strtoupper($shopping_payment->mode) . ' - ' . self::getFormattedTxaction($shopping_payment->txaction) . '</span>';
return '<span class="badge badge-pill badge-default">'.strtoupper($shopping_payment->mode).' - '.self::getFormattedTxaction($shopping_payment->txaction).'</span>';
}
return '<span class="badge badge-pill badge-' . self::getFormattedTxactionColor($shopping_payment->txaction) . '">' . self::getFormattedTxaction($shopping_payment->txaction) . '</span>';
return '<span class="badge badge-pill badge-'.self::getFormattedTxactionColor($shopping_payment->txaction).'">'.self::getFormattedTxaction($shopping_payment->txaction).'</span>';
}
public static function addUserCreditMargin(User $user, $credit, $status, $message)
@ -133,7 +136,7 @@ class Payment
'credit' => $credit,
'message' => $message,
'from_month' => date('n'),
'from_year' => date('Y'),
'from_year' => date('Y'),
'status' => $status,
]);
}
@ -143,7 +146,7 @@ class Payment
ProductBuying::create([
'user_id' => $user->id,
'product_id' => $product_id,
'amount' => 1
'amount' => 1,
]);
}
@ -155,9 +158,9 @@ class Payment
'user_id' => $user->user_sponsor->id,
'total_net' => 0,
'points' => $product->sponsor_buying_points_amount,
'info' => 'VP: ' . $user->getFullName(false) . ' | ' . $product->name,
'info' => 'VP: '.$user->getFullName(false).' | '.$product->name,
'status_points' => 2,
'status' => 5
'status' => 5,
];
SalesPointsVolume::addSalesPointsVolume($data);
}
@ -165,7 +168,7 @@ class Payment
public static function updateUserLevel(User $user, $to_level_id)
{
//nur updaten, wenn der user->m_level kleiner ist als $to_level_id
// nur updaten, wenn der user->m_level kleiner ist als $to_level_id
if ($user->user_level) {
$ToUserLevel = UserLevel::find($to_level_id);
if ($user->user_level->pos < $ToUserLevel->pos) {
@ -190,7 +193,7 @@ class Payment
$shopping_order->paid = $paid;
$shopping_order->save();
//if product has actions
// if product has actions
if ($shopping_order->shopping_order_items && $shopping_order->auth_user_id) {
foreach ($shopping_order->shopping_order_items as $shopping_order_item) {
if ($shopping_order_item->product) {
@ -204,17 +207,19 @@ class Payment
}
if ($shopping_order_item->product->action) {
$send_link = true;
//new date
// new date
$date = \Carbon::now()->modify('1 year');
if ($user->payment_account && $user->daysActiveAccount() > 0) {
$date = \Carbon::parse($user->payment_account)->modify('1 year');
}
foreach ($shopping_order_item->product->action as $do) {
// bzw. product_id 34 = 0 => payment_for_account => payment_order_id = 35 = 0 => payment_for_account, 1 => payment_for_shop, 2 => payment_for_shop_upgrade
// 0 => payment_for_account, 1 => payment_for_shop, 2 => payment_for_shop_upgrade
if ($shopping_order_item->product->getActionName($do) === 'payment_for_account') {
$user->payment_order_id = $shopping_order_item->product->id; //34
$user->payment_order_id = $shopping_order_item->product->id;
$user->payment_account = $date;
$user->wizard = 100;
//only date is > now and acount is deactive.
// only date is > now and acount is deactive.
if ($date > \Carbon::now()) {
if ($user->active === 0) {
$user->active = true;
@ -224,19 +229,22 @@ class Payment
$shopping_order->setUserHistoryValue(['status' => 9]);
}
// 1 => payment_for_shop
if ($shopping_order_item->product->getActionName($do) === 'payment_for_shop') {
$user->payment_order_id = $shopping_order_item->product->id; //35
$user->payment_order_id = $shopping_order_item->product->id; // 35
$user->payment_shop = $date;
$user->wizard = 100;
$shopping_order->setUserHistoryValue(['status' => 9]);
}
// 2 => payment_for_shop_upgrade
if ($shopping_order_item->product->getActionName($do) === 'payment_for_shop_upgrade') {
if ($shopping_order_item->product->upgrade_to_id) {
$user->payment_order_id = $shopping_order_item->product->upgrade_to_id;
}
$user->payment_shop = $user->payment_account; //same Date, is upgrade
$user->payment_shop = $user->payment_account; // same Date, is upgrade
$shopping_order->setUserHistoryValue(['status' => 9]);
}
// 4 => payment_for_lead_upgrade
if ($shopping_order_item->product->getActionName($do) === 'payment_for_lead_upgrade') {
if ($shopping_order_item->product->upgrade_to_id) {
self::updateUserLevel($user, $shopping_order_item->product->upgrade_to_id);
@ -259,20 +267,26 @@ class Payment
$shopping_order->setUserHistoryValue(['status' => 9]);
ShopApiOrderCart::finishOrder($shopping_order->shopping_collect_order);
}
//the Order is Pay, so we can set the Status in the Abo
// Set payment link status to paid for all orders
if ($shopping_payment) {
Util::setInstanceStatusByPayment($shopping_payment, 10); // link_paid
$shopping_payment->identifier = null;
$shopping_payment->save();
}
// the Order is Pay, so we can set the Status in the Abo
if ($shopping_order->is_abo) {
if ($shopping_payment) {
Util::setInstanceStatusByPayment($shopping_payment, 10); //link_paid
$shopping_payment->identifier = null;
$shopping_payment->save();
}
AboHelper::setAboActive($shopping_order, 2, true);
}
//make Invoice is not exist and is live
// make Invoice is not exist and is live
if ($shopping_order->mode === 'live' || Util::isTestSystem(true)) {
$invoice_repo = new InvoiceRepository($shopping_order);
if (!$shopping_order->isInvoice()) {
// Reload the shopping order to check for invoice again (defense against race conditions)
$shopping_order->refresh();
if (! $shopping_order->isInvoice()) {
$invoice_repo = new InvoiceRepository($shopping_order);
$invoice_repo->createAndSalesVolume();
}
}
@ -287,15 +301,15 @@ class Payment
// Überprüfung der Billing-E-Mail-Adresse
if (!$billing_email) {
if (! $billing_email) {
if ($data['mode'] === 'test') {
$billing_email = config('app.checkout_test_mail');
} else {
$billing_email = config('app.checkout_mail');
}
}
if (!filter_var($billing_email, FILTER_VALIDATE_EMAIL)) {
\Log::channel('payment')->error("Invalid billing email at shopping_order " . $shopping_order->id, ['billing_email' => $billing_email]);
if (! filter_var($billing_email, FILTER_VALIDATE_EMAIL)) {
\Log::channel('payment')->error('Invalid billing email at shopping_order '.$shopping_order->id, ['billing_email' => $billing_email]);
$billing_email = config('app.checkout_mail');
}
@ -305,7 +319,7 @@ class Payment
$bcc[] = config('app.checkout_mail');
}
if (!$shopping_order->shopping_user->is_like && $shopping_order->shopping_user->member) {
if (! $shopping_order->shopping_user->is_like && $shopping_order->shopping_user->member) {
$bcc[] = $shopping_order->shopping_user->member->email;
}
$data['payment_error'] = isset($data['payment_error']) ? $data['payment_error'] : false;

View file

@ -2,24 +2,20 @@
namespace App\Services;
use Yard;
use App\User;
use App\Models\UserHistory;
use App\Models\ShoppingUser;
use App\Http\Controllers\Pay\PayoneController;
use App\Models\PaymentTransaction;
use App\Models\ShoppingOrder;
use App\Models\ShoppingOrderItem;
use App\Models\PaymentTransaction;
use App\Http\Controllers\Pay\PayoneController;
use App\Models\ShoppingUser;
use App\Models\UserHistory;
use Yard;
class PaymentHelper
{
public function setProduct($product)
{
Yard::instance('shopping')->destroy();
Yard::instance('shopping')->add($product->id, $product->getLang('name'), 1, $product->price, false, false, ['image' => "", 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'show_on' => $product->show_on]);
Yard::instance('shopping')->add($product->id, $product->getLang('name'), 1, $product->price, false, false, ['image' => '', 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'no_free_shipping' => $product->no_free_shipping, 'show_on' => $product->show_on]);
}
public function initELVPayment($user)
@ -28,17 +24,16 @@ class PaymentHelper
$shopping_user = $this->makeShoppingUser($user);
$shopping_order = $this->makeShoppingOrder($user, $shopping_user);
$pay = new PayoneController();
$pay = new PayoneController;
$pay->init($shopping_user, $shopping_order);
$amount = Yard::instance('shopping')->totalWithShipping(2, '.', '') * 100;
$payment_method = 'elv';
$ret['elv']['mandate_identification'] = isset($user->account->payment_data['mandate_identification']) ? $user->account->payment_data['mandate_identification'] : "";
$ret['elv']['creditor_identifier'] = isset($user->account->payment_data['creditor_identifier']) ? $user->account->payment_data['creditor_identifier'] : "";
$ret['elv']['iban'] = isset($user->account->payment_data['iban']) ? $user->account->payment_data['iban'] : "";
$ret['elv']['bic'] = isset($user->account->payment_data['bic']) ? $user->account->payment_data['bic'] : "";
$ret['elv']['bankaccountholder'] = isset($user->account->payment_data['bankaccountholder']) ? $user->account->payment_data['bankaccountholder'] : "";
$ret['elv']['mandate_identification'] = isset($user->account->payment_data['mandate_identification']) ? $user->account->payment_data['mandate_identification'] : '';
$ret['elv']['creditor_identifier'] = isset($user->account->payment_data['creditor_identifier']) ? $user->account->payment_data['creditor_identifier'] : '';
$ret['elv']['iban'] = isset($user->account->payment_data['iban']) ? $user->account->payment_data['iban'] : '';
$ret['elv']['bic'] = isset($user->account->payment_data['bic']) ? $user->account->payment_data['bic'] : '';
$ret['elv']['bankaccountholder'] = isset($user->account->payment_data['bankaccountholder']) ? $user->account->payment_data['bankaccountholder'] : '';
$reference = $pay->setPrePayment($payment_method, $amount, 'EUR', $ret);
$pay->setPersonalData();
@ -71,12 +66,12 @@ class PaymentHelper
if ($response['status'] === 'APPROVED') {
$payT = PaymentTransaction::create([
'shopping_payment_id' => $shopping_payment->id,
'request' => 'authorization',
'request' => 'authorization',
'txid' => $response['txid'],
'userid' => $response['userid'],
'status' => $response['status'],
'transmitted_data' => $response,
'mode' => $shopping_payment->mode
'mode' => $shopping_payment->mode,
]);
UserHistory::create(['user_id' => $user->id, 'shopping_order_id' => $shopping_order->id, 'action' => 'abo_open_payment', 'referenz' => $payT->id, 'identifier' => $user->payment_account, 'status' => 5]);
}
@ -84,7 +79,7 @@ class PaymentHelper
public function makeShoppingUser($user, $is_from = 'membership', $is_for = 'me')
{
$shopping_user = new ShoppingUser();
$shopping_user = new ShoppingUser;
$shopping_user->auth_user_id = $user->id;
$shopping_user->mode = 'prev';
$shopping_user->language = $user->getLocale();
@ -99,6 +94,7 @@ class PaymentHelper
$shopping_user->billing_country_id = $user->account->country_id;
$shopping_user->billing_phone = $user->account->phone;
$shopping_user->billing_email = $user->email;
$shopping_user->language = $user->getLocale();
$shopping_user->faker_mail = false;
$shopping_user->shipping_email = $user->email;
$shopping_user->accepted_data_checkbox = 1;
@ -118,6 +114,7 @@ class PaymentHelper
$shopping_user->shipping_phone = $user->account->shipping_phone;
$shopping_user->shipping_postnumber = $user->account->shipping_postnumber;
$shopping_user->save();
return $shopping_user;
}
@ -147,12 +144,12 @@ class PaymentHelper
$shopping_order = ShoppingOrder::create($data);
$items = Yard::instance('shopping')->getContentByOrder();
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()) {
$price_net = Yard::instance('shopping')->rowPriceNet($item, 2, '.', '');
$tax = $item->price - $price_net;
$data = [
'shopping_order_id' => $shopping_order->id,
'row_id' => $item->rowId,
'row_id' => $item->rowId,
'product_id' => $item->id,
'comp' => $item->options->comp,
'qty' => $item->qty,
@ -163,12 +160,13 @@ class PaymentHelper
'price_vk_net' => $shopping_order->getPriceVkNetBy($item->id),
'discount' => $item->options->no_commission ? 0 : $shopping_order->getUserDiscount(),
'points' => $item->options->points,
'slug' => $item->options->slug
'slug' => $item->options->slug,
];
$shopping_order_item = ShoppingOrderItem::create($data);
}
}
$shopping_order->makeTaxSplit();
return $shopping_order;
}
}

View file

@ -2,7 +2,6 @@
namespace App\Services;
use App\Http\Controllers\Api\KasController;
use App\Models\ShoppingUser;
use App\Models\ShoppingUserMemberLog;
@ -12,8 +11,6 @@ use App\User;
class UserUtil
{
public static function setShoppingUserToNewMember($pre_member_id, $new_member_id)
{
$ShoppingUsers = ShoppingUser::where('member_id', $pre_member_id)->get();
@ -21,7 +18,7 @@ class UserUtil
ShoppingUserMemberLog::create([
'pre_member_id' => $shopping_user->member_id,
'shopping_user_id' => $shopping_user->id,
'new_member_id' => $new_member_id
'new_member_id' => $new_member_id,
]);
$shopping_user->member_id = $new_member_id;
$shopping_user->save();
@ -30,8 +27,8 @@ class UserUtil
public static function setNewSponsorToChilds($inactive_sponsor_id, $new_sponsor_id)
{
//alle User die diesen inaktivien Sponsor haben
$child_users = User::where('m_sponsor', $inactive_sponsor_id)->get(); //auch deaktiverte
// alle User die diesen inaktivien Sponsor haben
$child_users = User::where('m_sponsor', $inactive_sponsor_id)->get(); // auch deaktiverte
foreach ($child_users as $child_user) {
UserCleanUpLog::create([
'inactive_sponsor_id' => $inactive_sponsor_id,
@ -45,12 +42,12 @@ class UserUtil
public static function resetChildsToSponsor($re_sponsor_id)
{
//alle alten Childs vom re_sponsor_id / User wieder herstellen
// alle alten Childs vom re_sponsor_id / User wieder herstellen
$UserCleanUpUsers = UserCleanUpLog::where('inactive_sponsor_id', $re_sponsor_id)->get();
foreach ($UserCleanUpUsers as $UserCleanUpUser) {
$child_user = User::find($UserCleanUpUser->child_user_id);
if ($child_user) {
//delete Logs from user child where is newer then this
// delete Logs from user child where is newer then this
$deleteUserCleanUpLogs = UserCleanUpLog::where('child_user_id', $UserCleanUpUser->child_user_id)->where('created_at', '>', $UserCleanUpUser->created_at)->get();
foreach ($deleteUserCleanUpLogs as $deleteUserCleanUpLog) {
$deleteUserCleanUpLog->delete();
@ -58,11 +55,11 @@ class UserUtil
if ($child_user->m_sponsor) { // child is active
$child_user->m_sponsor = $re_sponsor_id;
}
if ($child_user->pre_sponsor) { //child is inactive
if ($child_user->pre_sponsor) { // child is inactive
$child_user->pre_sponsor = $re_sponsor_id;
}
$child_user->save();
//delete this log
// delete this log
$UserCleanUpUser->delete();
}
}
@ -72,10 +69,22 @@ class UserUtil
{
$user = User::find($user_id);
if ($user) {
if (! $user) {
\Log::channel('cleanup')->error('setUserToClient: User not found, user_id: '.$user_id);
return false;
}
if (! $user->account) {
\Log::channel('cleanup')->error('setUserToClient: User has no account data, user_id: '.$user_id);
return false;
}
try {
$data = [
'member_id' => $sponsor_id,
'language' => $user->lang ? $user->lang : 'de',
'language' => $user->lang ?? 'de',
'billing_salutation' => $user->account->salutation,
'billing_company' => $user->account->company,
'billing_firstname' => $user->account->first_name,
@ -87,6 +96,7 @@ class UserUtil
'billing_country_id' => $user->account->country_id,
'billing_phone' => $user->account->getPhoneNumber(),
'billing_email' => $user->email,
'language' => $user->account->getLocale(),
'same_as_billing' => $user->account->same_as_billing,
'shipping_salutation' => $user->account->shipping_salutation,
'shipping_company' => $user->account->shipping_company,
@ -100,11 +110,17 @@ class UserUtil
'shipping_phone' => $user->account->getShippingPhoneFull(),
'shipping_postnumber' => $user->account->shipping_postnumber,
];
ShoppingUser::create($data);
return true;
} catch (\Exception $e) {
\Log::channel('cleanup')->error('setUserToClient failed for user_id: '.$user_id.' | Error: '.$e->getMessage());
return false;
}
}
/*
find next activ sponsor on user id
first $sponsor_id can user_id, looks has m_sponsor or pre_sponsor.
@ -113,27 +129,28 @@ class UserUtil
{
$user = User::withTrashed()->find($sponsor_id);
if (!$user) { //kein User unter der ID - to root
if (! $user) { // kein User unter der ID - to root
return User::find(6);
}
//user ist aktiv
// user ist aktiv
if ($user->isActiveAccount()) {
return $user;
}
if ($user->m_sponsor) { //hat der User einen m_sponsor
if ($user->m_sponsor) { // hat der User einen m_sponsor
return self::findNextActiveSponsor($user->m_sponsor);
}
if ($user->pre_sponsor) { //hat der User einen pre_sponsor - schon inaktiv
if ($user->pre_sponsor) { // hat der User einen pre_sponsor - schon inaktiv
return self::findNextActiveSponsor($user->pre_sponsor);
}
//dump('not sponsor');
// dump('not sponsor');
return $user;
}
public static function deactiveUser($user)
{
$user->pre_sponsor = $user->m_sponsor; //den sponsor speichern für wiederherstellung
$user->pre_sponsor = $user->m_sponsor; // den sponsor speichern für wiederherstellung
$user->m_sponsor = null;
$user->active = false;
$user->save();
@ -143,7 +160,7 @@ class UserUtil
{
if ($user->pre_sponsor) {
$pre_sponsor = self::findNextActiveSponsor($user->pre_sponsor);
$user->m_sponsor = $pre_sponsor->id; //den sponsor wiederherstellen
$user->m_sponsor = $pre_sponsor->id; // den sponsor wiederherstellen
$user->pre_sponsor = null;
}
$user->active = true;
@ -152,14 +169,14 @@ class UserUtil
public static function deleteUser(User $user, $complete = false)
{
//shop wird gelöscht
// shop wird gelöscht
if ($user->shop) {
// $subdomain_name = $user->shop->slug . '.mivita.care';
$user->shop->name = "delete" . $user->shop->id;
$user->shop->slug = "delete" . $user->shop->id;
$user->shop->name = 'delete'.$user->shop->id;
$user->shop->slug = 'delete'.$user->shop->id;
$user->shop->save();
$user->shop->delete();
//isset KAS - delete Subdomain
// isset KAS - delete Subdomain
/*if (!Util::isTestSystem()) {
$kas = new KasController();
$pra = array(
@ -169,12 +186,12 @@ class UserUtil
}*/
}
//user soll nicht komplett gelöscht werden
$user->email = "delete-" . $user->email;
//password wird gelöscht
$user->password = "delete" . time();
// user soll nicht komplett gelöscht werden
$user->email = 'delete-'.$user->email;
// password wird gelöscht
$user->password = 'delete'.time();
$user->confirmed = 0;
$user->confirmation_code = "delete" . time();
$user->confirmation_code = 'delete'.time();
$user->confirmation_date = null;
$user->confirmation_code_to = null;
$user->confirmation_code_remider = 2;
@ -185,9 +202,9 @@ class UserUtil
$user->admin = 0;
$user->deleted_at = now();
$user->pre_deleted_at = now();
//user soll komplett gelöscht werden
// user soll komplett gelöscht werden
if ($complete) {
$user->email = "delete-" . time() . "-" . rand(1000, 9999);
$user->email = 'delete-'.time().'-'.rand(1000, 9999);
if ($user->account) {
$user->account->delete();
}
@ -198,14 +215,16 @@ class UserUtil
return true;
}
public static function checkEmailExists($user)
{
$email = str_replace("delete-", "", $user->email);
$email = str_replace('delete-', '', $user->email);
$user = User::where('email', $email)->first();
if ($user) {
return 'Der Account kann nicht wieder hergestellt werden, da die E-Mail-Adresse <b>' . $email . '</b> bereits in Verwendung ist.';
return 'Der Account kann nicht wieder hergestellt werden, da die E-Mail-Adresse <b>'.$email.'</b> bereits in Verwendung ist.';
}
return null;
}
@ -213,11 +232,11 @@ class UserUtil
{
if ($user->pre_sponsor) {
$pre_sponsor = self::findNextActiveSponsor($user->pre_sponsor);
$user->m_sponsor = $pre_sponsor->id; //den sponsor wiederherstellen
$user->m_sponsor = $pre_sponsor->id; // den sponsor wiederherstellen
$user->pre_sponsor = null;
}
$user->email = str_replace("delete-", "", $user->email);
$user->email = str_replace('delete-', '', $user->email);
$user->confirmed = 1;
$user->confirmation_date = now();
$user->confirmation_code = null;
@ -242,13 +261,13 @@ class UserUtil
}
}
public static function reactiveUserResetChilds($user_id, $info = '')
{
$user = User::find($user_id);
if (!$user) {
\Log::channel('cleanup')->error('reactiveUserResetChilds find no user by user_id:' . $user_id);
if (! $user) {
\Log::channel('cleanup')->error('reactiveUserResetChilds find no user by user_id:'.$user_id);
return 0;
}
$data = [
@ -258,7 +277,7 @@ class UserUtil
'm_first_name' => $user->account ? $user->account->m_first_name : '',
'm_last_name' => $user->account ? $user->account->m_last_name : '',
];
\Log::channel('cleanup')->info('reactiveUserResetChilds ' . $info . ' : ' . json_encode($data));
\Log::channel('cleanup')->info('reactiveUserResetChilds '.$info.' : '.json_encode($data));
self::reactiveUser($user);
self::resetChildsToSponsor($user->id);
@ -268,8 +287,9 @@ class UserUtil
{
$user = User::find($user_id);
if (!$user) {
\Log::channel('cleanup')->error('deactiveUserNewSponsorChilds find no user by user_id:' . $user_id);
if (! $user) {
\Log::channel('cleanup')->error('deactiveUserNewSponsorChilds find no user by user_id:'.$user_id);
return 0;
}
$data = [
@ -284,9 +304,9 @@ class UserUtil
if ($active_sponsor) {
self::setNewSponsorToChilds($user->id, $active_sponsor->id);
} else {
\Log::channel('cleanup')->error('cleanUpInActiveUser find no active_sponsor by inactive_user:' . $user->id);
\Log::channel('cleanup')->error('cleanUpInActiveUser find no active_sponsor by inactive_user:'.$user->id);
}
\Log::channel('cleanup')->info('deactiveUserNewSponsorChilds ' . $info . ' : ' . json_encode($data));
\Log::channel('cleanup')->info('deactiveUserNewSponsorChilds '.$info.' : '.json_encode($data));
self::deactiveUser($user);
}
}

View file

@ -257,7 +257,7 @@ class Yard extends Cart
if (! $shipping_price) {
return;
}
if ($this->weight() == 0) {
if ($this->allItemsFreeShippingConsultant() || $this->weight() == 0) {
$shipping_price->price = 0;
$shipping_price->price_comp = 0;
} else {
@ -416,6 +416,18 @@ class Yard extends Cart
return $total;
}
public function allItemsFreeShippingConsultant()
{
$content = $this->getContent();
if ($content->isEmpty()) {
return false;
}
return $content->every(function (CartItem $cartItem) {
return (bool) $cartItem->options->free_shipping_consultant;
});
}
public function points()
{
$content = $this->getContent();
@ -510,7 +522,7 @@ class Yard extends Cart
}
$price = $product->price;
if ($set_price === 'with') {
$cartItem = $this->getCartItem($product->id, $product->getLang('name'), 1, $price, ['image' => $image, 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'show_on' => $product->show_on]);
$cartItem = $this->getCartItem($product->id, $product->getLang('name'), 1, $price, ['image' => $image, 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'no_free_shipping' => $product->no_free_shipping, 'show_on' => $product->show_on]);
$price = $product->getPriceWith(false, true, $this->getUserCountry());
}
if ($set_price === 'withTaxFree') {