Neue Anforderungen (docs/) interpretiert und als Entwicklungsplan V5.0 (AP-20 bis AP-28) aufgenommen; erste drei Pakete umgesetzt: AP-26 Ausschuss-Gründe konfigurierbar: - Stammdaten-Tabelle disposal_reasons + CRUD unter Einstellungen → Allgemein - StockDisposalController liest aktive DB-Gründe statt hartkodierter Liste - Seeder übernimmt die bisherigen 6 Gründe idempotent AP-25 Lieferbestand — Datum statt Tage: - "Nicht vorrätig" wird über Datepicker "Wieder lieferbar ab" gepflegt; Resttage-Hinweis zählt täglich automatisch herunter - Interne Bestellliste wieder kaufbar: Hinweis erscheint zusätzlich zu den Mengen-Buttons (VP entscheidet selbst) AP-22 Produktbestand-Erweiterungen: - Default-Sortierung nach Dringlichkeit, Status-Kopf toggelt - Alle vier Status-Kacheln als Filter klickbar - Neue Spalte "Verbrauch/Monat" (Ø Abgänge der letzten 6 Monate) - Produkt-Flag "Im Produktbestand anzeigen" (products.show_in_product_stock) Tests: 77 grün (DisposalReasonSettings 8, ProductOutOfStock 8, ProductStock 13 + Regression). Hinweise-Doku + Plan-Protokoll fortgeschrieben; nächster Schritt laut Plan: AP-21 (INCI-Erweiterungen). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
820 lines
26 KiB
PHP
820 lines
26 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use App\Services\Type;
|
||
use App\Services\Util;
|
||
use Cviebrock\EloquentSluggable\Sluggable;
|
||
use Illuminate\Database\Eloquent\Builder;
|
||
use Illuminate\Database\Eloquent\Collection;
|
||
use Illuminate\Database\Eloquent\Model;
|
||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||
use Illuminate\Support\Carbon;
|
||
|
||
/**
|
||
* App\Models\Product
|
||
*
|
||
* @property int $id
|
||
* @property string $name
|
||
* @property array|null $trans_name
|
||
* @property string $title
|
||
* @property array|null $trans_title
|
||
* @property string|null $copy
|
||
* @property array|null $trans_copy
|
||
* @property float|null $price
|
||
* @property float|null $price_ek
|
||
* @property float|null $tax
|
||
* @property float|null $price_old
|
||
* @property string|null $contents
|
||
* @property string|null $number
|
||
* @property array|null $icons
|
||
* @property string|null $description
|
||
* @property array|null $trans_description
|
||
* @property string|null $usage
|
||
* @property array|null $trans_usage
|
||
* @property string|null $ingredients
|
||
* @property array|null $trans_ingredients
|
||
* @property int|null $pos
|
||
* @property int $active
|
||
* @property int|null $amount
|
||
* @property Carbon|null $created_at
|
||
* @property Carbon|null $updated_at
|
||
* @property Carbon|null $deleted_at
|
||
* @property-read Collection|ProductAttribute[] $attributes
|
||
* @property-read Collection|ProductCategory[] $categories
|
||
* @property-read Collection|ProductImage[] $images
|
||
* @property-write mixed $price_vk
|
||
*
|
||
* @method static bool|null forceDelete()
|
||
* @method static \Illuminate\Database\Query\Builder|\App\Models\Product onlyTrashed()
|
||
* @method static bool|null restore()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereActive($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereAmount($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereContents($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereCopy($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereCreatedAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereDeletedAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereDescription($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereIcons($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereId($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereIngredients($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereName($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereNumber($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product wherePos($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product wherePrice($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product wherePriceEk($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product wherePriceOld($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereTax($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereTitle($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereTransCopy($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereTransDescription($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereTransIngredients($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereTransName($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereTransTitle($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereTransUsage($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUpdatedAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUsage($value)
|
||
* @method static \Illuminate\Database\Query\Builder|\App\Models\Product withTrashed()
|
||
* @method static \Illuminate\Database\Query\Builder|\App\Models\Product withoutTrashed()
|
||
*
|
||
* @property string|null $slug
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product findSimilarSlugs($attribute, $config, $slug)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereSlug($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product newModelQuery()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product newQuery()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product query()
|
||
*
|
||
* @property int|null $weight
|
||
* @property int|null $show_at
|
||
* @property array|null $action
|
||
* @property-read int|null $attributes_count
|
||
* @property-read int|null $categories_count
|
||
* @property-read int|null $images_count
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereAction($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereShowAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereWeight($value)
|
||
*
|
||
* @property int|null $points
|
||
* @property-read Collection|ProductImage[] $imagesActive
|
||
* @property-read int|null $images_active_count
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product wherePoints($value)
|
||
*
|
||
* @property string|null $identifier
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereIdentifier($value)
|
||
*
|
||
* @property int|null $upgrade_to_id
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUpgradeToId($value)
|
||
*
|
||
* @property int|null $contents_total
|
||
* @property int|null $unit
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereContentsTotal($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUnit($value)
|
||
*
|
||
* @property-read Collection|CountryPrice[] $country_prices
|
||
* @property-read int|null $country_prices_count
|
||
* @property int|null $wp_number
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereWpNumber($value)
|
||
*
|
||
* @property bool|null $single_commission
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereShippingAddon($value)
|
||
*
|
||
* @property-read int|null $ingredients_count
|
||
* @property-read Collection|ProductIngredient[] $product_ingredients
|
||
* @property-read int|null $product_ingredients_count
|
||
* @property-read Collection|Ingredient[] $p_ingredients
|
||
* @property-read int|null $p_ingredients_count
|
||
* @property bool $amount_commission
|
||
* @property string|null $value_commission
|
||
* @property string|null $partner_commission
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereAmountCommission($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product wherePartnerCommission($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereSingleCommission($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereValueCommission($value)
|
||
*
|
||
* @property bool|null $shipping_addon
|
||
* @property bool|null $max_buy
|
||
* @property int|null $max_buy_num
|
||
* @property-read Collection|ProductBuy[] $product_buys
|
||
* @property-read int|null $product_buys_count
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereMaxBuy($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereMaxBuyNum($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product withUniqueSlugConstraints(\Illuminate\Database\Eloquent\Model $model, string $attribute, array $config, string $slug)
|
||
*
|
||
* @property string|null $short_copy
|
||
* @property array|null $show_on
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereShortCopy($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereShowOn($value)
|
||
*
|
||
* @property bool $exclude_stats_sales
|
||
* @property bool|null $whitelabel
|
||
* @property string|null $whitelabel_name
|
||
* @property-read Collection<int, ProductAttribute> $attribute_variants
|
||
* @property-read int|null $attribute_variants_count
|
||
* @property-read Collection<int, ProductImage> $whitelabel_images
|
||
* @property-read int|null $whitelabel_images_count
|
||
*
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereExcludeStatsSales($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereWhitelabel($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|Product whereWhitelabelName($value)
|
||
*
|
||
* @mixin \Eloquent
|
||
*/
|
||
class Product extends Model
|
||
{
|
||
/*identifiers
|
||
show_upgrade # in membership payment can upgrade this package to show order
|
||
show_order # in membership payment show always
|
||
upgrade # need upgrade_to_id set user->payment_order_id to the package in the payment api
|
||
*/
|
||
|
||
protected $table = 'products';
|
||
|
||
protected $casts = [
|
||
'trans_name' => 'array',
|
||
'trans_title' => 'array',
|
||
'trans_copy' => 'array',
|
||
'icons' => 'array',
|
||
'trans_description' => 'array',
|
||
'trans_usage' => 'array',
|
||
'trans_ingredients' => 'array',
|
||
'show_on' => 'array',
|
||
'action' => 'array',
|
||
'wp_number' => 'int',
|
||
'single_commission' => 'bool',
|
||
'amount_commission' => 'bool',
|
||
'exclude_stats_sales' => 'bool',
|
||
'active' => 'bool',
|
||
'shipping_addon' => 'bool',
|
||
'max_buy' => 'bool',
|
||
'max_buy_num' => 'int',
|
||
'whitelabel' => 'bool',
|
||
'no_recipe_required' => 'bool',
|
||
'is_set' => 'bool',
|
||
'main_product_id' => 'int',
|
||
'main_product_quantity' => 'int',
|
||
'out_of_stock_until' => 'date',
|
||
'out_of_stock_indefinite' => 'bool',
|
||
'min_product_stock' => 'int',
|
||
'critical_product_stock' => 'int',
|
||
'show_in_product_stock' => 'bool',
|
||
];
|
||
|
||
use Sluggable;
|
||
use SoftDeletes;
|
||
|
||
protected $dates = ['deleted_at'];
|
||
|
||
protected $fillable = [
|
||
'name',
|
||
'whitelabel',
|
||
'whitelabel_name',
|
||
'title',
|
||
'copy',
|
||
'short_copy',
|
||
'price',
|
||
'price_ek',
|
||
'tax',
|
||
'price_old',
|
||
'points',
|
||
'weight',
|
||
'contents',
|
||
'contents_total',
|
||
'unit',
|
||
'number',
|
||
'wp_number',
|
||
'icons',
|
||
'description',
|
||
'usage',
|
||
'ingredients',
|
||
'pos',
|
||
'amount',
|
||
'active',
|
||
'show_at',
|
||
'show_on',
|
||
'single_commission',
|
||
'amount_commission',
|
||
'value_commission',
|
||
'partner_commission',
|
||
'exclude_stats_sales',
|
||
'identifier',
|
||
'action',
|
||
'upgrade_to_id',
|
||
'shipping_addon',
|
||
'max_buy',
|
||
'max_buy_num',
|
||
'shelf_life_type',
|
||
'shelf_life_months',
|
||
'no_recipe_required',
|
||
'is_set',
|
||
'main_product_id',
|
||
'main_product_quantity',
|
||
'out_of_stock_until',
|
||
'out_of_stock_indefinite',
|
||
'min_product_stock',
|
||
'critical_product_stock',
|
||
'show_in_product_stock',
|
||
|
||
];
|
||
|
||
public $identifiers_types = [
|
||
'' => '-',
|
||
'show_upgrade' => 'Kann geupdatet werden',
|
||
'show_order' => 'Wird immer als Option angezeigt',
|
||
'upgrade' => 'Produktupgrade zur Produkt ID',
|
||
'upgrade_member' => 'Vertriebspartnerupgrade zur Karriere ID',
|
||
// 'proportional_voucher' => 'Anteiliger Gutschein Vertriebspartner',
|
||
];
|
||
|
||
public $unitTypes = [
|
||
0 => '',
|
||
1 => 'ml',
|
||
2 => 'g',
|
||
3 => 'Liter',
|
||
4 => 'KG',
|
||
];
|
||
|
||
public $actions = [
|
||
0 => 'payment_for_account',
|
||
1 => 'charging_credits',
|
||
// 1 => 'payment_for_shop',
|
||
// 2 => 'payment_for_shop_upgrade',
|
||
// 4 => 'payment_for_lead_upgrade',
|
||
|
||
];
|
||
|
||
public function sluggable(): array
|
||
{
|
||
return [
|
||
'slug' => [
|
||
'source' => 'name',
|
||
],
|
||
];
|
||
}
|
||
|
||
public function attributes()
|
||
{
|
||
return $this->hasMany(ProductAttribute::class, 'product_id', 'id')->where('type_id', '!=', 1);
|
||
}
|
||
|
||
public function attribute_variants()
|
||
{
|
||
return $this->hasMany(ProductAttribute::class, 'product_id', 'id')->where('type_id', '=', 1);
|
||
}
|
||
|
||
public function categories()
|
||
{
|
||
return $this->hasMany('App\Models\ProductCategory', 'product_id', 'id');
|
||
}
|
||
|
||
public function images()
|
||
{
|
||
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'product')->orderBy('pos');
|
||
}
|
||
|
||
public function imagesActive()
|
||
{
|
||
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'product')->where('active', true)->orderBy('pos');
|
||
}
|
||
|
||
public function whitelabel_images()
|
||
{
|
||
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'wllogo')->orderBy('pos');
|
||
}
|
||
|
||
public function country_prices()
|
||
{
|
||
return $this->hasMany(CountryPrice::class, 'product_id');
|
||
}
|
||
|
||
public function product_buys()
|
||
{
|
||
return $this->hasMany(ProductBuy::class, 'product_id');
|
||
}
|
||
|
||
public function p_ingredients()
|
||
{
|
||
return $this->belongsToMany(Ingredient::class, 'product_ingredients')
|
||
->withPivot('id', 'pos', 'gram', 'factor', 'recipe_type')
|
||
->withTimestamps()
|
||
->wherePivot('recipe_type', 'product')
|
||
->orderByPivot('pos');
|
||
}
|
||
|
||
public function manufacturer_ingredients()
|
||
{
|
||
return $this->belongsToMany(Ingredient::class, 'product_ingredients')
|
||
->withPivot('id', 'pos', 'gram', 'factor', 'recipe_type')
|
||
->withTimestamps()
|
||
->wherePivot('recipe_type', 'manufacturer')
|
||
->orderByPivot('pos');
|
||
}
|
||
|
||
public function product_ingredients()
|
||
{
|
||
return $this->hasMany(ProductIngredient::class, 'product_id');
|
||
}
|
||
|
||
/**
|
||
* Verpackungs-Stückliste (BOM) am Produkt mit Menge und Reihenfolge.
|
||
*
|
||
* @return BelongsToMany<PackagingItem, $this>
|
||
*/
|
||
public function packagings()
|
||
{
|
||
return $this->belongsToMany(PackagingItem::class, 'product_packagings')
|
||
->withPivot('quantity', 'pos')
|
||
->withTimestamps()
|
||
->orderByPivot('pos');
|
||
}
|
||
|
||
/**
|
||
* @return HasMany<Production, $this>
|
||
*/
|
||
public function productions(): HasMany
|
||
{
|
||
return $this->hasMany(Production::class);
|
||
}
|
||
|
||
/**
|
||
* Produktbestands-Bewegungen (Eingang/Ausgang).
|
||
*
|
||
* @return HasMany<ProductStockMovement, $this>
|
||
*/
|
||
public function stockMovements(): HasMany
|
||
{
|
||
return $this->hasMany(ProductStockMovement::class);
|
||
}
|
||
|
||
/**
|
||
* Set-Bestandteile (Einzelprodukte) dieses Sets mit Menge und Reihenfolge.
|
||
*
|
||
* @return BelongsToMany<Product, $this>
|
||
*/
|
||
public function setItems(): BelongsToMany
|
||
{
|
||
return $this->belongsToMany(Product::class, 'product_set_items', 'set_product_id', 'component_product_id')
|
||
->withPivot('quantity', 'pos')
|
||
->withTimestamps()
|
||
->orderByPivot('pos');
|
||
}
|
||
|
||
/**
|
||
* Sets, in denen dieses Produkt als Bestandteil enthalten ist.
|
||
*
|
||
* @return BelongsToMany<Product, $this>
|
||
*/
|
||
public function partOfSets(): BelongsToMany
|
||
{
|
||
return $this->belongsToMany(Product::class, 'product_set_items', 'component_product_id', 'set_product_id')
|
||
->withPivot('quantity', 'pos')
|
||
->withTimestamps();
|
||
}
|
||
|
||
/**
|
||
* Übergeordnetes Hauptprodukt (z. B. „50 × 15 ml").
|
||
*
|
||
* @return BelongsTo<Product, $this>
|
||
*/
|
||
public function mainProduct(): BelongsTo
|
||
{
|
||
return $this->belongsTo(Product::class, 'main_product_id');
|
||
}
|
||
|
||
/**
|
||
* Untergeordnete Varianten, die auf dieses Produkt als Hauptprodukt zeigen.
|
||
*
|
||
* @return HasMany<Product, $this>
|
||
*/
|
||
public function variants(): HasMany
|
||
{
|
||
return $this->hasMany(Product::class, 'main_product_id');
|
||
}
|
||
|
||
/**
|
||
* Nur Einzelprodukte (keine Sets).
|
||
*
|
||
* @param Builder<Product> $query
|
||
* @return Builder<Product>
|
||
*/
|
||
public function scopeSingleProducts(Builder $query): Builder
|
||
{
|
||
return $query->where('is_set', false);
|
||
}
|
||
|
||
/**
|
||
* Sets.
|
||
*
|
||
* @param Builder<Product> $query
|
||
* @return Builder<Product>
|
||
*/
|
||
public function scopeSets(Builder $query): Builder
|
||
{
|
||
return $query->where('is_set', true);
|
||
}
|
||
|
||
/**
|
||
* Haupt-/Einzelprodukte, die keinem übergeordneten Hauptprodukt zugeordnet sind.
|
||
*
|
||
* @param Builder<Product> $query
|
||
* @return Builder<Product>
|
||
*/
|
||
public function scopeMainProducts(Builder $query): Builder
|
||
{
|
||
return $query->whereNull('main_product_id');
|
||
}
|
||
|
||
public function getShortCopy($clean = false, $len = false)
|
||
{
|
||
$ret = $this->short_copy ? $this->short_copy : $this->description;
|
||
if ($len && $clean) {
|
||
return substr_ellipsis($ret, $len, $clean);
|
||
}
|
||
|
||
return $ret;
|
||
}
|
||
|
||
public function getActionName($id = 0)
|
||
{
|
||
if (isset($this->actions[$id])) {
|
||
return $this->actions[$id];
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
public function getWhiteLableName($id = 0)
|
||
{
|
||
return $this->whitelabel_name ? $this->whitelabel_name : $this->name;
|
||
}
|
||
|
||
public function _format_number($value)
|
||
{
|
||
return preg_replace('/[^0-9,]/', '', $value);
|
||
}
|
||
|
||
public function setPriceAttribute($value)
|
||
{
|
||
|
||
$this->attributes['price'] = $value ? Util::reFormatNumber($value) : null;
|
||
}
|
||
|
||
public function setPriceEkAttribute($value)
|
||
{
|
||
|
||
$this->attributes['price_ek'] = $value ? Util::reFormatNumber($value) : null;
|
||
}
|
||
|
||
public function setTaxAttribute($value)
|
||
{
|
||
|
||
$this->attributes['tax'] = $value != '' ? Util::reFormatNumber($value) : null;
|
||
}
|
||
|
||
public function setPriceOldAttribute($value)
|
||
{
|
||
|
||
$this->attributes['price_old'] = $value ? Util::reFormatNumber($value) : null;
|
||
}
|
||
|
||
public function setValueCommissionAttribute($value)
|
||
{
|
||
$this->attributes['value_commission'] = $value ? Util::reFormatNumber($value) : 0;
|
||
}
|
||
|
||
public function setPartnerCommissionAttribute($value)
|
||
{
|
||
$this->attributes['partner_commission'] = $value ? Util::reFormatNumber($value) : 0;
|
||
}
|
||
|
||
public function getFormattedPrice()
|
||
{
|
||
return isset($this->attributes['price']) ? Util::formatNumber($this->attributes['price']) : '';
|
||
}
|
||
|
||
public function isOutOfStock(): bool
|
||
{
|
||
if ($this->out_of_stock_indefinite) {
|
||
return true;
|
||
}
|
||
|
||
return $this->out_of_stock_until !== null && $this->out_of_stock_until->endOfDay()->isFuture();
|
||
}
|
||
|
||
public function outOfStockRemainingDays(): ?int
|
||
{
|
||
if ($this->out_of_stock_indefinite || $this->out_of_stock_until === null) {
|
||
return null;
|
||
}
|
||
|
||
if (! $this->out_of_stock_until->endOfDay()->isFuture()) {
|
||
return null;
|
||
}
|
||
|
||
$diffSeconds = $this->out_of_stock_until->copy()->startOfDay()->getTimestamp() - now()->startOfDay()->getTimestamp();
|
||
|
||
return (int) max(0, (int) round($diffSeconds / 86400));
|
||
}
|
||
|
||
public function outOfStockNotice(): ?string
|
||
{
|
||
if ($this->out_of_stock_indefinite) {
|
||
return __('Zur Zeit nicht vorrätig');
|
||
}
|
||
|
||
$days = $this->outOfStockRemainingDays();
|
||
|
||
if ($days === null) {
|
||
return null;
|
||
}
|
||
|
||
if ($days <= 0) {
|
||
return __('In Kürze wieder verfügbar');
|
||
}
|
||
|
||
if ($days === 1) {
|
||
return __('In ca. 1 Tag wieder da!');
|
||
}
|
||
|
||
return __('In ca. :days Tagen wieder da!', ['days' => $days]);
|
||
}
|
||
|
||
public function getFormattedPriceEk()
|
||
{
|
||
return isset($this->attributes['price_ek']) ? Util::formatNumber($this->attributes['price_ek']) : '';
|
||
}
|
||
|
||
public function getFormattedTax($country = null)
|
||
{
|
||
return isset($this->attributes['tax']) ? Util::formatNumber($this->getTaxWith($country), 0) : '';
|
||
}
|
||
|
||
public function getFormattedPriceOld()
|
||
{
|
||
return isset($this->attributes['price_old']) ? Util::formatNumber($this->attributes['price_old']) : '';
|
||
}
|
||
|
||
public function getFormattedValueCommission()
|
||
{
|
||
return isset($this->attributes['value_commission']) ? Util::formatNumber($this->attributes['value_commission']) : 0;
|
||
}
|
||
|
||
public function getFormattedPartnerCommission()
|
||
{
|
||
return isset($this->attributes['partner_commission']) ? Util::formatNumber($this->attributes['partner_commission']) : 0;
|
||
}
|
||
|
||
/* price by user Factor */
|
||
private function calcPriceUserFactor($price)
|
||
{
|
||
/*
|
||
Nicht in benutzung, die margin errechnet sich im Warenkorb, wegen der Staffelprovision
|
||
if(\Auth::user() && \Auth::user()->user_level){
|
||
$margin = ((\Auth::user()->user_level->margin -100)*-1) / 100;
|
||
$price = $price * $margin;
|
||
}
|
||
*/
|
||
return $price;
|
||
}
|
||
|
||
/* price net */
|
||
private function calcPriceNet($price, $country = null)
|
||
{
|
||
$tax = $this->getTaxWith($country);
|
||
$tax_rate = ($tax + 100) / 100;
|
||
|
||
return $price / $tax_rate;
|
||
}
|
||
|
||
// price calu with
|
||
public function getPriceWith(bool $net = true, bool $ufactor = true, $country = null, $commission = false)
|
||
{
|
||
$price = isset($this->attributes['price']) ? $this->attributes['price'] : null;
|
||
|
||
/*$cprice = $country ? $this->getCPrice($country) : null; //eigener Preis für Land
|
||
$price = $cprice ? $cprice : $price; */
|
||
|
||
$price = $net ? $this->calcPriceNet($price, $country) : $price;
|
||
$price = $ufactor ? $this->calcPriceUserFactor($price) : $price;
|
||
$price = $commission ? $this->calcPriceUserCommission($price) : $price;
|
||
|
||
return round($price, 2);
|
||
}
|
||
|
||
/* out */
|
||
public function getFormattedPriceWith(bool $net = true, bool $ufactor = true)
|
||
{
|
||
return isset($this->attributes['price']) ? Util::formatNumber($this->getPriceWith($net, $ufactor)) : '';
|
||
}
|
||
|
||
public function getTaxWith($country = null)
|
||
{
|
||
$tax = isset($this->attributes['tax']) ? $this->attributes['tax'] : null;
|
||
$ctax = $country ? $this->getCTax($country) : null;
|
||
|
||
return $ctax !== null ? $ctax : $tax;
|
||
}
|
||
|
||
public function getBasePriceFormattedFull()
|
||
{
|
||
if ($price = $this->getBasePrice()) {
|
||
$unit = $this->attributes['unit'];
|
||
// ml g
|
||
if ($unit === 1 || $unit === 2) {
|
||
return Util::formatNumber($price).' € / 100 '.$this->getUnitType();
|
||
}
|
||
// l kg
|
||
if ($unit === 3 || $unit === 4) {
|
||
return Util::formatNumber($price).' € / 1 '.$this->getUnitType();
|
||
}
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
public function getBasePriceFormatted()
|
||
{
|
||
if ($price = $this->getBasePrice()) {
|
||
return Util::formatNumber($price);
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
public function getBasePrice()
|
||
{
|
||
if (isset($this->attributes['unit']) && isset($this->attributes['contents_total']) && $this->attributes['contents_total'] != 0) {
|
||
$unit = $this->attributes['unit'];
|
||
// ml g
|
||
if ($unit === 1 || $unit === 2) {
|
||
return $this->attributes['price'] * 100 / $this->attributes['contents_total'];
|
||
}
|
||
// l kg
|
||
if ($unit === 3 || $unit === 4) {
|
||
return $this->attributes['price'] * 1000 / $this->attributes['contents_total'];
|
||
}
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
public function getUnitType()
|
||
{
|
||
return isset($this->unitTypes[$this->unit]) ? $this->unitTypes[$this->unit] : '-';
|
||
}
|
||
|
||
public function getShowAtType()
|
||
{
|
||
return isset(Type::$showATs[$this->show_at]) ? Type::$showATs[$this->show_at] : '-';
|
||
}
|
||
|
||
public function getShowOnTypes($seperator = false)
|
||
{
|
||
$ret = [];
|
||
if ($this->show_on && is_array($this->show_on)) {
|
||
foreach ($this->show_on as $show) {
|
||
$ret[] = isset(Type::$showONs[$show]) ? Type::$showONs[$show] : '-';
|
||
}
|
||
}
|
||
|
||
return $seperator ? implode($seperator, $ret) : $ret;
|
||
}
|
||
|
||
public function setPosAttribute($value)
|
||
{
|
||
$this->attributes['pos'] = is_numeric($value) ? $value : null;
|
||
|
||
}
|
||
|
||
public function getLang($key)
|
||
{
|
||
$lang = \App::getLocale();
|
||
if ($lang == 'de') {
|
||
return $this->{$key};
|
||
}
|
||
$trans = $this->getTrans($key, $lang);
|
||
if (! $trans || $trans == '') {
|
||
return $this->{$key};
|
||
}
|
||
|
||
return $trans;
|
||
}
|
||
|
||
public function getTrans($key, $lang)
|
||
{
|
||
$key = 'trans_'.$key;
|
||
if (! empty($this->{$key}[$lang])) {
|
||
return $this->{$key}[$lang];
|
||
}
|
||
}
|
||
|
||
public function getTranNames()
|
||
{
|
||
$ret = '';
|
||
foreach ((array) $this->trans_name as $value) {
|
||
$ret .= $value.', ';
|
||
}
|
||
|
||
return rtrim($ret, ', ');
|
||
}
|
||
|
||
public function getCountryPrice($country_id)
|
||
{
|
||
return $this->country_prices->where('country_id', '=', $country_id)->first() ?: new CountryPrice;
|
||
}
|
||
|
||
public function getCPrice($country_id)
|
||
{
|
||
return $this->getCountryPrice($country_id)->c_price;
|
||
}
|
||
|
||
public function getCTax($country_id)
|
||
{
|
||
return $this->getCountryPrice($country_id)->c_tax;
|
||
}
|
||
|
||
public function getCPriceOld($country_id)
|
||
{
|
||
return $this->getCountryPrice($country_id)->c_price_old;
|
||
}
|
||
|
||
public function getCCurrency($country_id)
|
||
{
|
||
return $this->getCountryPrice($country_id)->c_currency;
|
||
}
|
||
|
||
public function getRealPrice(Country $country)
|
||
{
|
||
if ($country->own_eur && $this->getCPrice($country->id)) {
|
||
return $this->getCPrice($country->id);
|
||
}
|
||
|
||
return $this->price;
|
||
}
|
||
|
||
public function getFormattedPriceCurrencyWith(bool $net = true, bool $ufactor = true, ?Country $country = null, $commission = false)
|
||
{
|
||
$ret = '';
|
||
if ($country && isset($country->currency) && $country->currency) {
|
||
$price = $this->getPriceWith($net, $ufactor, $country, $commission);
|
||
$ret = formatNumber($price * $country->currency_faktor).' '.$country->currency_unit;
|
||
|
||
return '<br><span class="small">~'.$ret.'<span>';
|
||
}
|
||
|
||
return '';
|
||
}
|
||
}
|