commit 08-2025

This commit is contained in:
Kevin Adametz 2025-08-12 15:51:04 +02:00
parent 9b54eb0512
commit 02f2a4c23e
184 changed files with 31653 additions and 22327 deletions

View file

@ -2,6 +2,7 @@
namespace App\Services;
use App\Mail\MailInvoice;
use App\Mail\MailLogistic;
use App\Services\Util;
use App\Models\Setting;
use App\Models\ShoppingOrder;
@ -118,4 +119,9 @@ class Invoice
}
Mail::to($billing_email)->bcc($bcc)->send(new MailInvoice($shopping_order));
}
public static function sendLogisticMail(ShoppingOrder $shopping_order){
$to = [config('app.logistic_mail')]; //['versand@aloe-vera.bio'];
Mail::to($to)->send(new MailLogistic($shopping_order));
}
}

View file

@ -51,7 +51,7 @@ class PDFMerger {
* Construct and initialize a new instance
* @param Filesystem $oFilesystem
*/
public function __construct(Filesystem $oFilesystem = null){
public function __construct($oFilesystem = null){
$this->oFilesystem = $oFilesystem;
$this->oFPDI = new FPDI();
$this->tmpFiles = collect([]);

View file

@ -4,7 +4,6 @@ namespace App\Services;
use App\User;
use App\Models\Product;
use App\Models\Setting;
use App\Mail\MailCheckout;
use App\Models\ProductBuy;
use App\Models\ShoppingOrder;
@ -19,15 +18,16 @@ class Payment
public static $txaction_text = [
'paid' => "bezahlt",
'appointed' => "offen",
'open' => "offen",
'appointed' => "offen (appointed)",
'failed' => "abbruch",
'extern' => "extern",
'open' => "offen",
'invoice_open' => "Re. offen",
'invoice_paid' => "Re. bezahlt",
'invoice_non' => "Re. keine Zahlung",
'non' => "keine Zahlung",
'non' => "keine Zahlung (non)",
'NULL' => 'keine Zahlung',
'prev' => "keine Zahlung (prev)",
];
public static $txaction_invoice = [
@ -56,10 +56,10 @@ class Payment
'invoice_open' => "warning",
'invoice_paid' => "success",
'invoice_non' => "danger",
'prev' => "warning",
];
public static function getFormattedTxaction($txaction){
if($txaction && isset(self::$txaction_text[$txaction])){
return self::$txaction_text[$txaction];
@ -360,26 +360,60 @@ class Payment
}
public static function paymentStatusSendMail(ShoppingOrder $shopping_order, $shopping_payment, $data){
$bcc = [];
$billing_email = $shopping_order->shopping_user->billing_email;
if(!$billing_email){
if($data['mode'] === 'test'){
$billing_email = config('app.checkout_test_mail');
}else{
$billing_email = config('app.checkout_mail');
}
public static function paymentStatusSendMail(ShoppingOrder $shopping_order, $shopping_payment, $data)
{
$billing_email = self::determineBillingEmail($shopping_order, $data);
$bcc = self::determineBccRecipients($shopping_order, $data);
try {
Mail::to($billing_email)
->bcc($bcc)
->send(new MailCheckout(
$data['txaction'],
$shopping_order,
$shopping_payment,
$data['send_link'],
$data['mode']
));
} catch (\Exception $e) {
\Log::error('Fehler beim E-Mail-Versand: ' . $e->getMessage());
}
if($data['mode'] === 'test'){
$bcc[] = config('app.checkout_test_mail');
}else{
$bcc[] = config('app.checkout_mail');
}
private static function determineBillingEmail($shopping_order, $data)
{
if (Util::isTestSystem()) {
return config('app.checkout_test_mail');
}
if(!$shopping_order->shopping_user->is_like && $shopping_order->shopping_user->member){
$billing_email = $shopping_order->shopping_user->billing_email;
if (!$billing_email) {
return $data['mode'] === 'test'
? config('app.checkout_test_mail')
: config('app.checkout_mail');
}
return $billing_email;
}
private static function determineBccRecipients($shopping_order, $data)
{
$bcc = [];
// Add checkout email to BCC
$bcc[] = $data['mode'] === 'test'
? config('app.checkout_test_mail')
: config('app.checkout_mail');
// Add member email to BCC if applicable
if ($data['mode'] !== 'test'
&& !Util::isTestSystem()
&& !$shopping_order->shopping_user->is_like
&& $shopping_order->shopping_user->member
) {
$bcc[] = $shopping_order->shopping_user->member->email;
}
Mail::to($billing_email)->bcc($bcc)->send(new MailCheckout($data['txaction'], $shopping_order, $shopping_payment, $data['send_link'], $data['mode']));
return $bcc;
}
}

View file

@ -1,269 +0,0 @@
<?php
namespace App\Services\Stats;
use Carbon\Carbon;
use App\Services\Util;
use App\Models\ShoppingOrder;
class Sales
{
private $month;
private $year;
private $products;
private $objects;
public function __construct()
{
$this->month = 0;
$this->year = 0;
$this->products = [];
$this->objects = [];
}
public function setFilterVars($month = null, $year = null, $products = null){
$this->month = $month ? $month : intval(date('m'));
$this->year = $year ? $year : intval(date('Y'));
$this->products = $products;
}
public function setFilterProducts(){
$ShoppingOrders = $this->getShoppingOrdersBy($this->month, $this->year);
$products = [];
foreach($ShoppingOrders as $ShoppingOrder){
foreach($ShoppingOrder->shopping_order_items as $shopping_order_item){
if($shopping_order_item->product && !$shopping_order_item->product->exclude_stats_sales && !isset($products[$shopping_order_item->product->id])){
$products[$shopping_order_item->product->id] = $shopping_order_item->product->name.' # '.
($shopping_order_item->product->single_commission ? $shopping_order_item->product->value_commission.' / '.$shopping_order_item->product->partner_commission : 'Staffelrabatt');
}
}
}
return $products;
}
private function getShoppingOrdersBy($month, $year){
if($month == '13'){ //all the year
$date_start = Carbon::parse('01.01.'.$year)->format('Y-m-d H:i:s');
$date_end = Carbon::parse('31.12.'.$year)->endOfMonth()->format('Y-m-d H:i:s');
}else{
$date_start = Carbon::parse('01.'.$month.'.'.$year)->format('Y-m-d H:i:s');
$date_end = Carbon::parse('01.'.$month.'.'.$year)->endOfMonth()->format('Y-m-d H:i:s');
}
return ShoppingOrder::where('paid', 1)->where('mode', 'live')->whereBetween('created_at', [$date_start, $date_end])->get();
}
public function getCollection(){
$this->getObjects();
$collection = collect();
foreach($this->objects as $key => $obj){
$collection->push([
'id' => $key,
'name' => $obj['name'],
'number' => $obj['number'],
'qty' => $obj['qty'],
'total' => $obj['total'],
'pre_qty' => $obj['pre_qty'],
'pre_total' => $obj['pre_total'],
'single_commission' => $obj['single_commission'],
'value_commission' => $obj['value_commission'],
'partner_commission' => $obj['partner_commission'],
]);
}
return $collection;
}
public function getObjects(){
$this->readObjects();
$this->readObjectsPreview();
return $this->objects;
}
private function readObjects()
{
$shoppingOrders = $this->getShoppingOrdersBy($this->month, $this->year);
$this->objects = [];
$subtotal_full = 0; // gesamtumsatz
$subtotal = 0; // gesamtumsatz ohne rabatte
$discount = 0; // gesamtrabatte
$subtotal_hide = 0; // ausgeschlossene Produkte
foreach($shoppingOrders as $ShoppingOrder){
$subtotal_full += $ShoppingOrder->subtotal_full;
$subtotal += $ShoppingOrder->subtotal;
$discount += $ShoppingOrder->discount;
foreach($ShoppingOrder->shopping_order_items as $shopping_order_item){
if($shopping_order_item->product){
if(!in_array($shopping_order_item->product->id, $this->products) && !$shopping_order_item->product->exclude_stats_sales){ //ausschließen der Produkte über filter und exclude_stats_sales
if(isset($this->objects[$shopping_order_item->product->id])){
$qty = intval($this->objects[$shopping_order_item->product->id]['qty'] + $shopping_order_item->qty);
$total = round($this->objects[$shopping_order_item->product->id]['total'] + ($shopping_order_item->price_net * $shopping_order_item->qty), 3);
$this->objects[$shopping_order_item->product->id]['qty'] = $qty;
$this->objects[$shopping_order_item->product->id]['total'] = $total;
}else{
$this->objects[$shopping_order_item->product->id] = [
'name' => $shopping_order_item->product->name,
'number' => $shopping_order_item->product->number,
'qty' => $shopping_order_item->qty,
'total' => round($shopping_order_item->price_net * $shopping_order_item->qty, 3),
'pre_qty' => 0,
'pre_total' => 0,
'single_commission' => $shopping_order_item->product->single_commission ? 'Ja' : 'Nein',
'value_commission' => $shopping_order_item->product->single_commission ? $shopping_order_item->product->value_commission : '',
'partner_commission' => $shopping_order_item->product->single_commission ? $shopping_order_item->product->partner_commission : '',
];
}
}else{
$subtotal_hide += $shopping_order_item->price_net * $shopping_order_item->qty;
}
}
}
}
$this->objects[9990] = [
'name' => 'Angezeigter Umsatz netto €',
'number' => '',
'qty' => '',
'total' => round($subtotal_full - $subtotal_hide, 2),
'pre_qty' => 0,
'pre_total' => 0,
'single_commission' => '',
'value_commission' => '',
'partner_commission' => '',
];
$this->objects[9991] = [
'name' => 'Ausgeblendeter Umsatz netto €',
'number' => '',
'qty' => '',
'total' => $subtotal_hide,
'pre_qty' => 0,
'pre_total' => 0,
'single_commission' => '',
'value_commission' => '',
'partner_commission' => '',
];
$this->objects[9992] = [
'name' => 'Gesamter Umsatz netto € (alle Verkäufe)',
'number' => '',
'qty' => '',
'total' => $subtotal_full,
'pre_qty' => 0,
'pre_total' => 0,
'single_commission' => '',
'value_commission' => '',
'partner_commission' => '',
];
$this->objects[9998] = [
'name' => 'Gesamte Rabatte netto € (alle Verkäufe)',
'number' => '',
'qty' => '',
'total' => ($discount),
'pre_qty' => 0,
'pre_total' => 0,
'single_commission' => '',
'value_commission' => '',
'partner_commission' => '',
];
$this->objects[9999] = [
'name' => 'Gesamt netto € (alle Verkäufe)',
'number' => '',
'qty' => '',
'total' => ($subtotal),
'pre_qty' => 0,
'pre_total' => 0,
'single_commission' => '',
'value_commission' => '',
'partner_commission' => '',
];
//format total
foreach($this->objects as $key => $obj){
$this->objects[$key]['total'] = formatNumber($obj['total']);
}
}
private function readObjectsPreview(){
$shoppingOrders = $this->getShoppingOrdersBy($this->month, $this->year-1);
$subtotal_full = 0; // gesamtumsatz
$subtotal = 0; // gesamtumsatz ohne rabatte
$discount = 0; // gesamtrabatte
$subtotal_hide = 0; // ausgeschlossene Produkte
foreach($shoppingOrders as $ShoppingOrder){
$subtotal_full += $ShoppingOrder->subtotal_full;
$subtotal += $ShoppingOrder->subtotal;
$discount += $ShoppingOrder->discount;
foreach($ShoppingOrder->shopping_order_items as $shopping_order_item){
if($shopping_order_item->product){
if(!in_array($shopping_order_item->product->id, $this->products) && !$shopping_order_item->product->exclude_stats_sales){ //ausschließen der Produkte über filter und exclude_stats_sales
if(isset($this->objects[$shopping_order_item->product->id])){ //einsetzen der Zahlen, wenn vorhanden
$qty = intval($this->objects[$shopping_order_item->product->id]['pre_qty'] + $shopping_order_item->qty);
$total = round($this->objects[$shopping_order_item->product->id]['pre_total'] + ($shopping_order_item->price_net * $shopping_order_item->qty), 3);
$this->objects[$shopping_order_item->product->id]['pre_qty'] = $qty;
$this->objects[$shopping_order_item->product->id]['pre_total'] = $total;
}else{ // nicht vorhanden, anlegen
$this->objects[$shopping_order_item->product->id] = [
'name' => $shopping_order_item->product->name,
'number' => $shopping_order_item->product->number,
'qty' => 0,
'total' => 0,
'pre_qty' => $shopping_order_item->qty,
'pre_total' => round($shopping_order_item->price_net * $shopping_order_item->qty, 3),
'single_commission' => $shopping_order_item->product->single_commission ? 'Ja' : 'Nein',
'value_commission' => $shopping_order_item->product->single_commission ? $shopping_order_item->product->value_commission : '',
'partner_commission' => $shopping_order_item->product->single_commission ? $shopping_order_item->product->partner_commission : '',
];
}
}else{
//ausgeschlossene Produkte
$subtotal_hide += $shopping_order_item->price_net * $shopping_order_item->qty;
}
}
}
}
$this->objects[9990]['pre_total'] = round($subtotal_full - $subtotal_hide, 2);
$this->objects[9991]['pre_total'] = $subtotal_hide;
$this->objects[9992]['pre_total'] = $subtotal_full;
$this->objects[9998]['pre_total'] = ($discount);
$this->objects[9999]['pre_total'] = ($subtotal);
//format total
foreach($this->objects as $key => $obj){
$this->objects[$key]['pre_total'] = formatNumber($obj['pre_total']);
}
}
}

View file

@ -30,6 +30,7 @@ class UserBot
//user die manuelle Gutschriften haben
$usersWithCreditMargin = $this->getUsersWithCreditMargin();
//user die Shop Provisionen haben
$usersWithShopCommission = $this->getUsersWithShopCommission(false);
@ -181,9 +182,9 @@ class UserBot
$entry->badge = \App\Services\Payment::getPaymentForTypeBadge($shoppingOrderMargin->shopping_order);
if ($shoppingOrderMargin->shopping_order->payment_for === 7 || $shoppingOrderMargin->shopping_order->payment_for === 8) {
$entry->link = route('admin_sales_customers_detail', [$shoppingOrderMargin->shopping_order->id]);
$entry->link = route('admin_sales_detail', [$shoppingOrderMargin->shopping_order->id]);
} else {
$entry->link = route('admin_sales_users_detail', [$shoppingOrderMargin->shopping_order->id]);
$entry->link = route('admin_sales_detail', [$shoppingOrderMargin->shopping_order->id]);
}
$entry->name = $shoppingOrderMargin->shopping_order->shopping_user->billing_firstname . " " .
@ -258,7 +259,8 @@ class UserBot
->whereOutPaid(false)
->whereCancellation(false)
->whereMarginPaid(false)
->whereNotNull('margin_pending_to');
->whereNotNull('margin_pending_to')
->whereIn('status', [7,8]);
if ($isPending) {
$query->where('margin_pending_to', '>=', Carbon::now());

View file

@ -0,0 +1,13 @@
<?php
namespace App\Services;
class PaymentHelper
{
public static $txaction_art = [
'user_order' => "Vertriebspartner",
'customer_order' => "Kundenbestellung",
'user_for_customer' => "VP.Kundenbestellung",
];
}

View file

@ -0,0 +1,453 @@
<?php
namespace App\Services;
use App\Models\Logger;
use App\Models\PaymentReminder;
use App\Models\ShoppingPayment;
use App\Mail\PaymentReminderEmail;
use App\Services\Util;
use Carbon\Carbon;
use Illuminate\Support\Facades\Mail;
class PaymentReminderService
{
protected $clearingtypes = [];
public function __construct()
{
$this->clearingtypes = PaymentReminder::returnClearingtypes();
}
/**
* Erstellt einen Log-Eintrag für Payment Reminder Aktivitäten
*/
private function createLog($action, $message, $model = null, $modelId = null, $level = 2)
{
return Logger::create([
'user_id' => null, // System-Aktion
'model_id' => $modelId,
'model' => $model,
'action' => $action,
'channel' => 'payment_reminder',
'message' => $message,
'level' => $level
]);
}
/**
* Hole alle aktiven Intervalle für Zahlungserinnerungen
*/
public function getActiveIntervals()
{
$intervals = [];
$payment_reminders = PaymentReminder::where('active', true)->get();
foreach($payment_reminders as $reminder) {
$intervals[$reminder->clearingtype] = $reminder->interval;
}
return $intervals;
}
/**
* Hole alle offenen Zahlungen für einen bestimmten clearingtype
*/
public function getOpenPaymentsForClearingType($clearingtype, $interval)
{
$date = Carbon::now()->subDays($interval);
$payments = ShoppingPayment::join('shopping_orders', 'shopping_payments.shopping_order_id', '=', 'shopping_orders.id')
->where('shopping_payments.clearingtype', '=', $clearingtype)
->where('shopping_payments.txaction', '=', 'open')
->where('shopping_payments.mode', '=', 'live')
->where('shopping_payments.created_at', '<', $date)
->where('shopping_payments.amount', '>', 0)
->whereNull('shopping_orders.deleted_at')
->whereIn('shopping_payments.id', function($query) use ($clearingtype, $date) {
$query->selectRaw('MAX(shopping_payments.id)')
->from('shopping_payments')
->join('shopping_orders', 'shopping_payments.shopping_order_id', '=', 'shopping_orders.id')
->where('shopping_payments.clearingtype', '=', $clearingtype)
->where('shopping_payments.txaction', '=', 'open')
->where('shopping_payments.mode', '=', 'live')
->where('shopping_payments.created_at', '<', $date)
->where('shopping_payments.amount', '>', 0)
->whereNull('shopping_orders.deleted_at')
->groupBy('shopping_payments.shopping_order_id');
})
->select('shopping_payments.*')
->get();
return $payments;
}
/**
* Hole alle offenen Zahlungen für alle clearingtypes
*/
public function getAllOpenPayments()
{
$intervals = $this->getActiveIntervals();
$results = [];
foreach($intervals as $clearingtype => $interval){
$date = Carbon::now()->subDays($interval);
$payments = $this->getOpenPaymentsForClearingType($clearingtype, $interval);
$results[$clearingtype] = [
'interval' => $interval,
'date_limit' => $date,
'payments' => $payments,
'count' => $payments->count()
];
}
return $results;
}
/**
* Hole detaillierte Daten für Tabellen-Ansicht
*/
public function getDetailedPaymentsData()
{
$intervals = $this->getActiveIntervals();
$detailedData = [];
foreach($intervals as $clearingtype => $interval){
$date = Carbon::now()->subDays($interval);
$payments = $this->getOpenPaymentsForClearingType($clearingtype, $interval);
foreach($payments as $payment){
$name = !isset($payment->shopping_order->shopping_user) ? 'Kein Name' : $payment->shopping_order->shopping_user->billing_firstname.' '.$payment->shopping_order->shopping_user->billing_lastname;
$email = !isset($payment->shopping_order->shopping_user) ? 'Keine Email' : $payment->shopping_order->shopping_user->billing_email;
$shipped = '<span class="badge badge-pill badge-'.$payment->shopping_order->getShippedColor().'">'.$payment->shopping_order->getShippedType().'</span>';
// Countdown für nächste Erinnerung berechnen
$countdown = $this->getNextReminderCountdown($payment);
$detailedData[] = [
'clearingtype' => $clearingtype,
'clearingtype_name' => $this->getClearingtype($clearingtype),
'interval_days' => $interval,
'date_limit' => $date->format('d.m.Y H:i:s'),
'order_id' => $payment->shopping_order_id,
'payment_id' => $payment->id,
'amount' => $payment->amount,
'created_at' => $payment->created_at->format('d.m.Y H:i:s'),
'days_old' => $payment->created_at->diffInDays(now()),
'payment' => $payment, // Vollständiges Payment-Objekt für weitere Verarbeitung
'name' => $name,
'email' => $email,
'shipped' => $shipped,
'reminder' => $payment->reminder,
'reminder_date' => $payment->reminder_date ? $payment->reminder_date->format('d.m.Y H:i:s') : null,
'countdown' => $countdown,
];
}
}
return $detailedData;
}
/**
* Sende die nächste Zahlungserinnerung
* noch kein reminder gesendet = 1. Zahlungserinnerung
* reminder > 0 die nächste zahlungserinnerung aus der liste holen
*/
public function sendReminder($payment)
{
//holen der nächsten zahlungserinnerung
$payment_reminder = $this->getReminder((int) $payment->reminder, $payment->clearingtype);
if(!$payment_reminder){
return false;
}
//zahlungserinnerung Platzhalter ersetzen.
$payment_reminder = $this->replacePlaceholder($payment, $payment_reminder);
//zahlungserinnerung senden
$emailSent = $this->sendReminderEmail($payment, $payment_reminder);
if ($emailSent) {
$this->createLog(
'email_sent',
"Zahlungserinnerung E-Mail gesendet an: {$payment->shopping_order->shopping_user->billing_email}, Subject: {$payment_reminder->subject}",
'ShoppingOrder',
$payment->shopping_order_id,
3
);
}
//action ausführen
if($payment_reminder->action === 'set_order_status_cancelled'){
$this->setNoNPayment($payment);
$payment->shopping_order->shipped = 10;
$payment->shopping_order->save();
$this->createLog(
'action_completed',
"Action abgeschlossen: Bestellung auf 'Storniert' gesetzt, Payment auf 'non' gesetzt",
'ShoppingOrder',
$payment->shopping_order_id,
3
);
}
//reminder setzen +1
$payment->reminder = (int) $payment->reminder + 1;
$payment->reminder_date = Carbon::now();
$payment->save();
$this->createLog(
'reminder_completed',
"Zahlungserinnerung für Payment ID: {$payment->id}, Order ID: {$payment->shopping_order_id}",
'ShoppingOrder',
$payment->shopping_order_id,
4
);
return true;
}
public function setNoNPayment($payment)
{
$this->createLog(
'set_non_payment',
"Setze Payment ID: {$payment->id} auf 'non' Status",
'ShoppingOrder',
$payment->shopping_order_id,
4
);
PaymentService::updateTransactionStatus($payment->shopping_order_id, 'non', $payment->id);
}
public function getClearingtype($clearingtype)
{
return isset($this->clearingtypes[$clearingtype]) ? $this->clearingtypes[$clearingtype] : $clearingtype;
}
public function getReminder($reminder, $clearingtype)
{
$payment_reminders = PaymentReminder::where('active', true)
->where('clearingtype', $clearingtype)
->orderBy('interval', 'asc')
->get();
if($payment_reminders->isEmpty()) {
return false;
}
// Wenn reminder größer ist als Anzahl der Erinnerungen
if($reminder >= $payment_reminders->count()) {
return false;
}
// Hole die Erinnerung an Position $reminder (0,1,2,3...)
return $payment_reminders[$reminder];
}
public function replacePlaceholder($payment, $payment_reminder)
{
$shopping_order = $payment->shopping_order;
$shopping_user = $shopping_order->shopping_user;
$replacements = [
'{billing_first_name}' => $shopping_user->billing_firstname,
'{billing_last_name}' => $shopping_user->billing_lastname,
'{order_number}' => '<b>'.$shopping_order->getLastShoppingPayment('reference').'</b>',
'{order_date}' => '<b>'.$shopping_order->created_at->format('d.m.Y').'</b>',
'{order_total}' => '<b>'.$shopping_order->getFormattedTotalShipping().'</b>'
];
$payment_reminder->subject = str_replace(
array_keys($replacements),
array_values($replacements),
$payment_reminder->subject
);
$payment_reminder->message = str_replace(
array_keys($replacements),
array_values($replacements),
$payment_reminder->message
);
return $payment_reminder;
}
public function sendReminderEmail($payment, $payment_reminder)
{
try {
$email = $payment->shopping_order->shopping_user->billing_email;
$subject = $payment_reminder->subject;
$message = $payment_reminder->message;
if(Util::isTestSystem()){
$email = config('app.checkout_test_mail');
}
if($payment->shopping_order->mode === 'test' || Util::isTestSystem()){
$bcc[] = config('app.checkout_test_mail');
}else{
$bcc[] = config('app.checkout_mail');
}
Mail::to($email)->bcc($bcc)->send(new PaymentReminderEmail($subject, $message, $payment->shopping_order));
return true;
} catch (\Exception $e) {
\Log::error('Fehler beim E-Mail-Versand: ' . $e->getMessage());
$this->createLog(
'email_exception',
"E-Mail Exception: " . $e->getMessage() . " für Payment ID: {$payment->id}",
'ShoppingOrder',
$payment->shopping_order_id,
5
);
return false;
}
}
/**
* Berechnet den Countdown bis zur nächsten Zahlungserinnerung
*/
public function getNextReminderCountdown($payment)
{
// Wenn noch keine Erinnerung gesendet wurde
if ($payment->reminder == 0) {
return null;
}
// Hole alle aktiven Erinnerungen für diesen Clearingtype
$payment_reminders = PaymentReminder::where('active', true)
->where('clearingtype', $payment->clearingtype)
->orderBy('interval', 'asc')
->get();
if ($payment_reminders->isEmpty()) {
return null;
}
// Wenn alle Erinnerungen bereits gesendet wurden
if ($payment->reminder >= $payment_reminders->count()) {
return [
'type' => 'completed',
'message' => 'Alle Erinnerungen gesendet',
'days_left' => 0
];
}
// Hole die nächste Erinnerung
$next_reminder = $payment_reminders[$payment->reminder];
// Berechne die Differenz zwischen aktuellem und nächstem Reminder
$current_reminder = $payment_reminders[$payment->reminder - 1];
$interval_difference = $next_reminder->interval - $current_reminder->interval;
// Berechne das Datum der nächsten Erinnerung
$next_reminder_date = Carbon::parse($payment->reminder_date)->addDays($interval_difference);
// Berechne die verbleibenden Tage
$days_left = Carbon::now()->diffInDays($next_reminder_date, false);
// Wenn die nächste Erinnerung bereits fällig ist
if ($days_left <= 0) {
return [
'type' => 'overdue',
'message' => 'Nächste Erinnerung fällig',
'days_left' => 0,
'next_reminder_date' => $next_reminder_date
];
}
return [
'type' => 'countdown',
'message' => 'Nächste Erinnerung in ' . $days_left . ' Tagen',
'days_left' => $days_left,
'next_reminder_date' => $next_reminder_date,
'next_reminder_interval' => $interval_difference
];
}
/**
* Hole alle Logs für Payment Reminder
*/
public function getPaymentReminderLogs($limit = 100, $paymentId = null, $action = null)
{
$query = Logger::where('channel', 'payment_reminder')
->orderBy('created_at', 'desc');
if ($paymentId) {
$query->where('model_id', $paymentId);
}
if ($action) {
$query->where('action', $action);
}
return $query->limit($limit)->get();
}
/**
* Hole Logs für einen spezifischen Payment
*/
public function getLogsForPayment($orderId)
{
return Logger::where('channel', 'payment_reminder')
->where('model_id', $orderId)
->orderBy('created_at', 'desc')
->get();
}
/**
* Hole Logs für einen spezifischen Zeitraum
*/
public function getLogsForDateRange($startDate, $endDate)
{
return Logger::where('channel', 'payment_reminder')
->whereBetween('created_at', [$startDate, $endDate])
->orderBy('created_at', 'desc')
->get();
}
/**
* Hole Statistiken für Payment Reminder Logs
*/
public function getLogStatistics($days = 30)
{
$startDate = Carbon::now()->subDays($days);
$stats = Logger::where('channel', 'payment_reminder')
->where('created_at', '>=', $startDate)
->selectRaw('action, level, COUNT(*) as count')
->groupBy('action', 'level')
->get();
$summary = [
'total_logs' => Logger::where('channel', 'payment_reminder')
->where('created_at', '>=', $startDate)
->count(),
'emails_sent' => Logger::where('channel', 'payment_reminder')
->where('action', 'email_sent')
->where('created_at', '>=', $startDate)
->count(),
'emails_failed' => Logger::where('channel', 'payment_reminder')
->where('action', 'email_exception')
->where('created_at', '>=', $startDate)
->count(),
'reminders_completed' => Logger::where('channel', 'payment_reminder')
->where('action', 'reminder_completed')
->where('created_at', '>=', $startDate)
->count(),
'actions_executed' => Logger::where('channel', 'payment_reminder')
->where('action', 'action_completed')
->where('created_at', '>=', $startDate)
->count(),
];
return [
'summary' => $summary,
'detailed_stats' => $stats
];
}
}

View file

@ -0,0 +1,201 @@
<?php
namespace App\Services;
use App\Models\ShoppingOrder;
use App\Models\ShoppingPayment;
use App\Models\UserPayCredit;
use App\Models\PaymentTransaction;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class PaymentService
{
public static $txaction_art = [
'user_order' => "Vertriebspartner",
'customer_order' => "Kundenbestellung",
'user_for_customer' => "VP.Kundenbestellung",
];
// Konstanten für bessere Wartbarkeit
const CREDIT_STATUS_DEDUCTION = 2;
const CREDIT_STATUS_RETURN = 4;
const CREDIT_STATUS_CHARGED = 7;
const CREDIT_STATUS_REMOVED = 8;
const TRANSACTION_REQUEST = 'transaction';
const DEFAULT_TXID = 0;
const DEFAULT_USERID = 0;
/**
* Aktualisiert den Transaktionsstatus einer Bestellung
*
* @param int $orderId
* @param string $txaction
* @param int $paymentId
* @return bool
* @throws \Exception
*/
public static function updateTransactionStatus($orderId, $txaction, $paymentId)
{
// Validierung der Eingabeparameter
if (empty($orderId) || empty($txaction) || empty($paymentId)) {
throw new \InvalidArgumentException('Alle Parameter müssen angegeben werden');
}
return DB::transaction(function () use ($orderId, $txaction, $paymentId) {
$shopping_order = ShoppingOrder::findOrFail($orderId);
$shopping_payment = ShoppingPayment::findOrFail($paymentId);
// Prüfen ob sich der Status tatsächlich geändert hat
if ($shopping_payment->txaction === $txaction) {
return false;
}
// Guthaben-Logik für Partner-Center Bestellungen
self::handlePartnerCenterCredits($shopping_order, $txaction);
// PaymentTransaction erstellen
$paymentTransaction = self::createPaymentTransaction($shopping_payment, $txaction);
// Bestellung und Payment aktualisieren
self::updateOrderAndPayment($shopping_order, $shopping_payment, $txaction, $paymentTransaction);
// Paid-Action ausführen falls nötig
self::handlePaidAction($paymentTransaction, $shopping_order);
// Credit-Loading Logik
self::handleCreditLoading($shopping_order, $txaction);
// E-Mail versenden
self::sendStatusEmail($shopping_order, $shopping_payment, $paymentTransaction);
return true;
});
}
/**
* Behandelt Guthaben-Logik für Partner-Center Bestellungen
*/
private static function handlePartnerCenterCredits(ShoppingOrder $shoppingOrder, string $txaction): void
{
if (!$shoppingOrder->shopping_order_margin || $shoppingOrder->shopping_order_margin->from_payment_credit <= 0) {
return;
}
$lastUserPayCredit = self::getLastUserPayCredit($shoppingOrder->id, [self::CREDIT_STATUS_DEDUCTION, self::CREDIT_STATUS_RETURN]);
if (!$lastUserPayCredit) {
return;
}
// Status Keine Zahlung, Guthaben zurückführen
if ($txaction === 'non' && $lastUserPayCredit->status === self::CREDIT_STATUS_DEDUCTION) {
Payment::handelUserPayCredits($shoppingOrder, 'return');
}
// Status Zahlung, vorher gab es eine Storno, Guthaben abziehen
if ($lastUserPayCredit->status === self::CREDIT_STATUS_RETURN && in_array($txaction, ['open', 'paid'])) {
Payment::handelUserPayCredits($shoppingOrder, 'deduction');
}
}
/**
* Erstellt eine neue PaymentTransaction
*/
private static function createPaymentTransaction(ShoppingPayment $shoppingPayment, string $txaction): PaymentTransaction
{
return PaymentTransaction::create([
'shopping_payment_id' => $shoppingPayment->id,
'request' => self::TRANSACTION_REQUEST,
'txid' => self::DEFAULT_TXID,
'userid' => self::DEFAULT_USERID,
'status' => $shoppingPayment->clearingtype,
'transmitted_data' => null,
'txaction' => $txaction,
'mode' => $shoppingPayment->mode,
]);
}
/**
* Aktualisiert Bestellung und Payment
*/
private static function updateOrderAndPayment(ShoppingOrder $shoppingOrder, ShoppingPayment $shoppingPayment, string $txaction, PaymentTransaction $paymentTransaction): void
{
$shoppingOrder->txaction = $txaction;
$shoppingOrder->paid = $paymentTransaction->txaction === 'paid';
$shoppingOrder->save();
$shoppingPayment->txaction = $txaction;
$shoppingPayment->save();
}
/**
* Führt Paid-Action aus falls nötig
*/
private static function handlePaidAction(PaymentTransaction $paymentTransaction, ShoppingOrder $shoppingOrder): void
{
if ($paymentTransaction->status === 'vor' && $paymentTransaction->txaction === 'paid') {
Payment::paymentStatusPaidAction($shoppingOrder, true);
}
}
/**
* Behandelt Credit-Loading Logik
*/
private static function handleCreditLoading(ShoppingOrder $shoppingOrder, string $txaction): void
{
if (!$shoppingOrder->shopping_user || $shoppingOrder->shopping_user->is_for !== 'cr') {
return;
}
$lastUserPayCredit = self::getLastUserPayCredit($shoppingOrder->id, [self::CREDIT_STATUS_CHARGED, self::CREDIT_STATUS_REMOVED]);
if (!$lastUserPayCredit) {
return;
}
// Status Keine Zahlung, Guthaben abziehen
if ($txaction === 'non' && $lastUserPayCredit->status === self::CREDIT_STATUS_CHARGED) {
Payment::handelUserPayChargingCredits($shoppingOrder, 'remove');
}
// Status Zahlung, vorher gab es eine Storno, Guthaben wieder aufladen
if ($lastUserPayCredit->status === self::CREDIT_STATUS_REMOVED && $txaction === 'paid') {
Payment::handelUserPayChargingCredits($shoppingOrder, 'add');
}
}
/**
* Sendet Status-E-Mail
*/
private static function sendStatusEmail(ShoppingOrder $shoppingOrder, ShoppingPayment $shoppingPayment, PaymentTransaction $paymentTransaction): void
{
$emailData = [
'mode' => $paymentTransaction->mode,
'txaction' => $paymentTransaction->txaction,
'send_link' => false,
];
try {
Payment::paymentStatusSendMail($shoppingOrder, $shoppingPayment, $emailData);
} catch (\Exception $e) {
Log::error('Fehler beim Senden der Status-E-Mail', [
'order_id' => $shoppingOrder->id,
'payment_id' => $shoppingPayment->id,
'error' => $e->getMessage()
]);
}
}
/**
* Holt den letzten UserPayCredit Eintrag
*/
private static function getLastUserPayCredit(int $orderId, array $statuses): ?UserPayCredit
{
return UserPayCredit::where('shopping_order_id', $orderId)
->whereIn('status', $statuses)
->orderBy('id', 'DESC')
->first();
}
}

View file

@ -44,7 +44,11 @@ class UserMarign
return $sum_net_amount;
}
public static function getMontlyAmount(User $user, $date = null, $format = false){
public static function getMontlyAmount($user, $date = null, $format = false){
if(!$user instanceof User){
return 0;
}
$now = $date ? Carbon::parse($date) : Carbon::now();
$startDay = $now->startOfMonth()->toDateString();
@ -202,7 +206,8 @@ class UserMarign
->whereOutPaid(false)
->whereCancellation(false)
->whereMarginPaid(false)
->whereNotNull('margin_pending_to');
->whereNotNull('margin_pending_to')
->whereIn('status', [7,8]);
if ($isPending) {
$query->where('margin_pending_to', '>=', Carbon::now());

View file

@ -3,6 +3,7 @@ namespace App\Services;
use Yard;
use App\User;
use Illuminate\Support\Str;
use App\Models\PromotionUser;
use App\Models\ShippingCountry;
@ -18,7 +19,7 @@ class UserService
public static function createConfirmationCode() {
$unique = false;
do{
$confirmation_code = str_random(30);
$confirmation_code = Str::random(30);
if(User::where('confirmation_code', '=', $confirmation_code)->count() == 0){
$unique = true;
}

View file

@ -13,7 +13,7 @@ class Util
public static function getToken()
{
return hash_hmac('sha256', str_random(40), config('app.key'));
return hash_hmac('sha256', Str::random(40), config('app.key'));
}
public static function uuidToken()
@ -127,7 +127,7 @@ class Util
return false;
}
public static function setUserHistoryValue($values = [], $identifier){
public static function setUserHistoryValue($values, $identifier){
if($user_history = self::getUserHistory($identifier)){
foreach ($values as $key=>$val){
$user_history->{$key} = $val;
@ -171,6 +171,13 @@ class Util
return false;
}
public static function isTestSystem(){
if(config('app.debug')){
return true;
}
return false;
}
public static function isPromotionUrl($debug = false){
if($debug && config('app.debug')){
return false;