Statistik-Änderungen 2026-08-19 (dev/2026-07-22/entwicklungsplan-2026-08-19.md, alle 8 Pakete gemäß beantworteten F-Fragen): - N1 Monatsstatistik: Team-Cluster (Firstlines klickbar -> neue Seite user_team_firstlines, Teampartner gesamt/Payline [nur aktive], Abos Payline mit +N) + Level-Kachel (Quali-Stufe, fehlende Payline-/persönliche Punkte, Karriere-Level); LiveCommissionService-Snapshot v3 reicht Level-/Quali-Daten durch - N2 Abo-Kacheln auf Home monatsbezogen (aboStockCount-Rückrechnung über cancel/deleted/Status-Historie; VIP-Statistik bleibt tagesaktuell) - N3 VIP-Statistik 'Recruiter des Monats' (Neupartner-Definition wie KPI, rekursives Ketten-Volumen als Gesamtpunkte inkl. Starterpaket) + Drilldown/CSV-Spalte 'Punkte gesamt' - N4 Prognose-Block 'Level & Provision laut Prognose (Stand 25.)' mit unverbindlich-Kennzeichnung (ebenengenaue offene Abo-Punkte + TP-Laufstände) - N5 Checkout-Herkunft: Dropdown raus, Pflicht-Freitext (lokalisierte Fehlermeldung) - N6 Berater-Profilfoto (user_accounts.profile_image, Einwilligung als Sprachkey-Platzhalter, DSGVO-Löschung) + teaminterne Top-3-Karte (TopResultsService, Scope-Schalter) - N7 USt-Optionen: 'nicht umsatzsteuerpflichtig' zuerst + '§ 19 UStG'-Textfix - N8 Jahresgebühr-Nachfassliste je Berater (AnnualFeeDueService, überfällig/1-3 Monate, Sidenav) Kontroll-Review (8 Perspektiven) angewendet: next-Level-Fallback im Steady-State (resolveNextLevel — sonst 'Höchstes Level erreicht' für Normalfälle), _calculated-Punktebasis bei mehr Paylines, keine Wiederbelebung deaktivierter Abos in der Monatsrückrechnung, Payline-Abo-Kachel monatskonsistent, Top-3-Nachrücker; Performance: BackofficeDashboardService als Singleton mit lineBuckets-Memo (+flushLineBucketsMemo), level_forecast 6h-Cache; Reuse: User-Scopes firstlinesOf/newPartnerIn, SalesVolumePoints::totalPointsByUser, Slim::storeCroppedImage (3 Upload-Kopien vereinigt), activeAbo-Scope statt Inline-Prädikate, Form::open-Konvention, Return-Types Repair-Härtung (Kundenentscheide): VP-Nachtrepair dedupliziert über shopping_order_id (Live-Pfad schreibt sie jetzt mit), zählende Alt-Zeilen-Erkennung, Vormonats-Scan für Monatswechsel, Firmenaccount-Buchungen (config mivita.company_user_id) werden automatisch auf den Berater umgebucht (in place bzw. per Gegenbuchung bei geschlossenem Monat; Echtfall Anna Schall Order 58797 -> Christina Schuder), Berater-zu-Berater nur Warnung; Incentive-Repair: m_sponsor=NULL löscht nicht mehr, FK-Repair vor Stale-Bereinigung, gebatchte Lookups, zentrales Incentive::qualificationWindow(); .env aus dem Git-Tracking entfernt (Pflege per SFTP) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
460 lines
14 KiB
PHP
460 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Attribute;
|
|
use App\Models\Category;
|
|
use App\Models\Country;
|
|
use App\Models\Ingredient;
|
|
use App\Models\Product;
|
|
use App\Models\ShippingCountry;
|
|
use App\Models\ShoppingUser;
|
|
use App\Models\UserAbo;
|
|
use App\Models\UserLevel;
|
|
use App\User;
|
|
|
|
class HTMLHelper
|
|
{
|
|
public static $months = [
|
|
1 => 'January',
|
|
2 => 'February',
|
|
3 => 'March',
|
|
4 => 'April',
|
|
5 => 'May',
|
|
6 => 'June',
|
|
7 => 'July',
|
|
8 => 'August',
|
|
9 => 'September',
|
|
10 => 'October',
|
|
11 => 'November',
|
|
12 => 'December',
|
|
];
|
|
|
|
private static $roles = [
|
|
0 => 'Berater',
|
|
1 => 'VIP',
|
|
2 => 'Admin',
|
|
3 => 'SuperAdmin',
|
|
4 => 'SySAdmin',
|
|
];
|
|
|
|
public static function getMonth($i)
|
|
{
|
|
return trans('cal.months.'.self::$months[intval($i)]);
|
|
}
|
|
|
|
public static function getTransMonths()
|
|
{
|
|
$ret = [];
|
|
foreach (self::$months as $key => $val) {
|
|
$ret[$key] = trans('cal.months.'.$val);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getYearRange($start = 2021)
|
|
{
|
|
$end = date('Y');
|
|
|
|
return array_reverse(range($start, $end));
|
|
}
|
|
|
|
public static function getRoleLabel($role_id = 0)
|
|
{
|
|
return '<span class="badge badge-pill '.self::getLabel($role_id).'">'.self::$roles[$role_id].'</span>';
|
|
}
|
|
|
|
public static function getLabel($id)
|
|
{
|
|
switch ($id) {
|
|
case 0:
|
|
return 'badge-default';
|
|
break;
|
|
case 1:
|
|
return 'badge-secondary';
|
|
break;
|
|
case 2:
|
|
return 'badge-warning';
|
|
break;
|
|
case 3:
|
|
return 'badge-primary';
|
|
break;
|
|
case 4:
|
|
return 'badge-primary';
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static function getRolesOptions()
|
|
{
|
|
$ret = '';
|
|
foreach (self::$roles as $role_id => $value) {
|
|
$ret .= '<option value="'.$role_id.'">'.$value.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getYearSelectOptions()
|
|
{
|
|
$start = date('Y', strtotime('-5 years', time()));
|
|
$end = date('Y', strtotime('+1 years', time()));
|
|
$values = range($start, $end);
|
|
$now = date('Y', time());
|
|
$ret = '';
|
|
foreach ($values as $value) {
|
|
$attr = ($value == $now) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getAboDeliveryOptions($default = 5)
|
|
{
|
|
$values = UserAbo::$aboDeliveryDays;
|
|
$ret = '';
|
|
foreach ($values as $value) {
|
|
$attr = ($value == $default) ? 'selected="selected"' : '';
|
|
$str = self::getAboStrLang($value);
|
|
$nextDate = AboHelper::getFirstAboDate(now(), $value);
|
|
$daysUntil = AboHelper::calendarDaysUntil(now(), $nextDate);
|
|
$ret .= '<option value="'.$value.'" data-days="'.$daysUntil.'" data-date="'.$nextDate->format('d.m.Y').'" '.$attr.'>'.$str.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getAboStrLang($num)
|
|
{
|
|
return $num.'. '.__('abo.of_month');
|
|
}
|
|
|
|
public static function getAboFirstExecutionDate($date, $interval)
|
|
{
|
|
return AboHelper::getFirstAboDate($date, $interval)->format('d.m.Y');
|
|
}
|
|
|
|
public static function getAttributesWithoutParents($id = false, $sameId = false, $all = true)
|
|
{
|
|
$values = Attribute::where('parent_id', null)->get();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('none').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
if ($sameId == $value->id) {
|
|
continue;
|
|
}
|
|
$attr = ($value->id == $id) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getCategoriesWithoutParents($id = false, $sameId = false, $all = true)
|
|
{
|
|
$values = Category::where('parent_id', null)->get();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('none').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
if ($sameId == $value->id) {
|
|
continue;
|
|
}
|
|
$attr = ($value->id == $id) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getProductsOptions($ids = [], $all = true)
|
|
{
|
|
if ($ids == null) {
|
|
$ids = [];
|
|
}
|
|
$values = Product::all();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('none').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.' ('.($value->active ? 'on' : 'off').')</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getCategoriesOptions($ids = [], $all = true)
|
|
{
|
|
$values = Category::where('active', 1)->orderBy('pos', 'DESC')->get();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('none').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getProductIngredientsOptions($has_ids = [], $all = true)
|
|
{
|
|
$values = Ingredient::where('active', 1)->get();
|
|
$ret = '';
|
|
$attr = '';
|
|
foreach ($values as $value) {
|
|
if (! in_array($value->id, $has_ids)) {
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Erzeugt Options für Bundle-Produkt-Auswahl
|
|
* Filtert bereits enthaltene Produkte und das aktuelle Produkt selbst aus
|
|
*
|
|
* @param array $has_ids IDs der bereits enthaltenen Produkte
|
|
* @param int|null $exclude_product_id ID des aktuellen Produkts (um Selbst-Referenz zu vermeiden)
|
|
* @return string HTML Options
|
|
*/
|
|
public static function getProductBundleOptions($has_ids = [], $exclude_product_id = null)
|
|
{
|
|
$values = Product::where('active', 1)->orderBy('name')->get();
|
|
$ret = '';
|
|
foreach ($values as $value) {
|
|
// Überspringe bereits enthaltene Produkte und das Produkt selbst
|
|
if (! in_array($value->id, $has_ids) && $value->id != $exclude_product_id) {
|
|
$label = $value->name;
|
|
if ($value->number) {
|
|
$label = $value->number.' - '.$value->name;
|
|
}
|
|
$ret .= '<option value="'.$value->id.'">'.htmlspecialchars($label).'</option>\n';
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getAttributesOptions($ids = [], $all = true)
|
|
{
|
|
$values = Attribute::where('active', 1)->get();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('none').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getUserLevelOptions($id = false, $all = true)
|
|
{
|
|
$values = UserLevel::where('active', 1)->get();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('none').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
$attr = ($value->id == $id) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getCompanyOptions($company)
|
|
{
|
|
$options = [1 => __('business'), 0 => __('private')];
|
|
$ret = '';
|
|
foreach ($options as $id => $value) {
|
|
$attr = ($id == $company) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$id.'" '.$attr.'>'.$value.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getContriesWithMore($id, $all = true)
|
|
{ //
|
|
$values = Country::all();
|
|
$counter = 1;
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('please select').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
if ($counter == 7) {
|
|
$ret .= '<optgroup label="'.__('further countrie').'">';
|
|
}
|
|
$attr = ($value->id == $id) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->getLocated().'</option>\n';
|
|
|
|
$counter++;
|
|
}
|
|
$ret .= '</optgroup>';
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getContriesCodes($id, $all = true)
|
|
{ //
|
|
$values = Country::all();
|
|
$counter = 1;
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('please select').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
|
|
if (! $value->phone) {
|
|
continue;
|
|
}
|
|
if ($counter == 7) {
|
|
$ret .= '<optgroup label="'.__('further countrie').'">';
|
|
}
|
|
$attr = ($value->id == $id) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->phone.'('.$value->getLocated().')</option>\n';
|
|
|
|
$counter++;
|
|
}
|
|
$ret .= '</optgroup>';
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getCountriesWithoutUsedShippings($all = true)
|
|
{ //
|
|
$values = Country::all();
|
|
$country_ids = ShippingCountry::all()->pluck('country_id')->toArray();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('please select').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
if (! in_array($value->id, $country_ids)) {
|
|
$ret .= '<option value="'.$value->id.'">'.$value->getLocated().'</option>\n';
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getCountryNameFormShipping($id)
|
|
{
|
|
$value = ShippingCountry::find($id);
|
|
if ($value) {
|
|
return $value->country->getLocated();
|
|
}
|
|
|
|
return 'not defined';
|
|
}
|
|
|
|
public static function getCountriesForShipping($id, $all = false)
|
|
{ //
|
|
$values = ShippingCountry::all();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('please select').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
$attr = ($value->id == $id) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->country->getLocated().'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getSalutation($id)
|
|
{
|
|
$values = ['mr' => __('MR'), 'ms' => __('MS'), 'di' => __('DIV')];
|
|
$ret = '';
|
|
$ret .= '<option value="">'.__('please select').'</option>\n';
|
|
foreach ($values as $key => $value) {
|
|
$attr = ($key == $id) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getSalutationLang($id)
|
|
{
|
|
$values = ['mr' => __('MR'), 'ms' => __('MS'), 'di' => __('DIV')];
|
|
|
|
return ! empty($values[$id]) ? $values[$id] : '';
|
|
}
|
|
|
|
public static function getTaxSaleOptions($id)
|
|
{
|
|
$values = ['2' => __('account.taxable_sales_2'), '1' => __('account.taxable_sales_1')];
|
|
$ret = '';
|
|
$ret .= '<option value="">'.__('please select').'</option>\n';
|
|
foreach ($values as $key => $value) {
|
|
$attr = ($key == $id) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getMembersOptions($id, $all = false)
|
|
{
|
|
$values = User::where('active', '=', true)->where('blocked', '=', false)->where('payment_account', '>=', now())->get();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('please select').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
$attr = ($value->id == $id) ? 'selected="selected"' : '';
|
|
$to = '';
|
|
if ($value->account) {
|
|
$to = $value->account->first_name.' '.$value->account->last_name.' | ';
|
|
}
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$to.$value->email.' #'.$value->account->m_account.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getUserCustomerOptions($id, $all = false)
|
|
{
|
|
$values = ShoppingUser::select(['id', 'billing_firstname', 'billing_lastname', 'billing_email', 'number'])
|
|
->where('shopping_users.member_id', '=', \Auth::user()->id)->get();
|
|
$ret = '';
|
|
if ($all) {
|
|
$ret .= '<option value="">'.__('please select').'</option>\n';
|
|
}
|
|
foreach ($values as $value) {
|
|
$attr = ($value->id == $id) ? 'selected="selected"' : '';
|
|
$to = $value->billing_firstname.' '.$value->billing_lastname.' | '.$value->billing_email;
|
|
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$to.' #'.$value->number.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getOptionRange($select, $from = 1, $to = 50)
|
|
{
|
|
$values = range($from, $to);
|
|
$ret = '';
|
|
foreach ($values as $value) {
|
|
$attr = ($value == $select) ? 'selected="selected"' : '';
|
|
$ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
}
|