720 lines
24 KiB
PHP
720 lines
24 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\Discount\DiscountType;
|
|
use App\Enums\Product\DefaultProductType;
|
|
use App\Enums\Product\TaxRate;
|
|
use App\Lib\Helpers\FileStorageHelper;
|
|
use App\Lib\Helpers\ToolboxHelper;
|
|
use App\Traits\Models\MiniCrudModel;
|
|
use Gloudemans\Shoppingcart\CanBeBought;
|
|
use Gloudemans\Shoppingcart\Contracts\Buyable;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Str;
|
|
use Kwaadpepper\Enum\Traits\CastsEnums;
|
|
|
|
/**
|
|
* Product
|
|
*
|
|
* By default engine supports product base and inco products
|
|
*
|
|
* @property string $reference The product reference.
|
|
* @property object $type The product type can be value
|
|
* one of enum \App\Enums\Product\DefaultProductType.
|
|
* @property string $name The product slug.
|
|
* @property string $slug The product description html.
|
|
* @property string $description The product picture path.
|
|
* @property string $picture The product picture path.
|
|
* @property int $price The product price as cents.
|
|
* @property string $sell_unit The sell unit item for this product
|
|
* (pair of gloves, box, piece...)
|
|
* @property int $weight The weight using grams.
|
|
* @property \App\Enums\Product\TaxRate $tax_rate Tax rate as enum.
|
|
* @property boolean $star The product is star (more visibility on pages).
|
|
* @property boolean $status The product is published visible on front.
|
|
* @property boolean $orderable The product can be cadded to carts and ordered.
|
|
* @property \Carbon\Carbon $orderable_at The product orderable start date.
|
|
* @property \Carbon\Carbon $unorderable_at The product orderable end date.
|
|
* @property \Carbon\Carbon $publish_at The product status start date.
|
|
* @property \Carbon\Carbon $unpublish_at The product status end date.
|
|
* @property array $data The product minicrud inputs depending on theme config,
|
|
* some of them can be found in App\Enums\DefaultProductType,
|
|
* onthers in resources/front/theme.config.php.
|
|
*
|
|
* @property-read integer $tax Product tax rate as cents.
|
|
* @property-read integer $discount_value Product discount as cents.
|
|
* @property-read integer $discounted_price Base Price after discount as cents.
|
|
* @property-read integer $discount_rate Discount rate, as percent or as reduction integer in cents.
|
|
* @property-read integer $price_ttc Price VAT included with discount as cents.
|
|
* @property-read integer $undiscounted_price_ttc Price VAT included without discount as cents.
|
|
*
|
|
*
|
|
* @property array $data The overlapped data like defined in template.
|
|
* @property array $dataObject The overlapped data like defined in template as object.
|
|
*
|
|
* @package App\Models
|
|
*/
|
|
class Product extends Model implements Buyable
|
|
{
|
|
use CanBeBought;
|
|
use CastsEnums;
|
|
use HasFactory;
|
|
use MiniCrudModel;
|
|
use SoftDeletes;
|
|
|
|
/**
|
|
* The attributes that should be cast to native types.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $casts = [
|
|
'star' => 'bool',
|
|
'status' => 'bool',
|
|
'orderable' => 'bool',
|
|
'publish_at' => 'datetime',
|
|
'unpublish_at' => 'datetime',
|
|
'orderable_at' => 'datetime',
|
|
'unorderable_at' => 'datetime',
|
|
'data' => 'array'
|
|
];
|
|
|
|
/**
|
|
* The attributes that should be cast to Enums.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $enumCasts = [
|
|
'tax_rate' => TaxRate::class,
|
|
];
|
|
|
|
/**
|
|
* The attributes that are mass assignable.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $fillable = [
|
|
'reference', 'type',
|
|
'name', 'description',
|
|
'picture', 'price',
|
|
'sell_unit', 'weight',
|
|
'tax_rate', 'star',
|
|
'status', 'orderable',
|
|
'orderable_at', 'unorderable_at',
|
|
'publish_at', 'unpublish_at',
|
|
'data', 'range_id',
|
|
];
|
|
|
|
/**
|
|
* Perform any actions required after the model boots.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected static function booted()
|
|
{
|
|
// * EVENT CLOSURES.
|
|
static::creating(function (self $model) {
|
|
// * ASSERTIONS
|
|
static::assertNameIsUnique($model->name);
|
|
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
|
|
self::assertIsAboveZero('price', trans('Prix'), $model->price);
|
|
self::assertIsAboveZero('weight', trans('Poids'), $model->weight);
|
|
|
|
// * TRANSFORMATIONS
|
|
$model->reference = $model->reference ?: ToolboxHelper::getUniqueProductReference();
|
|
$model->slug = Str::slug($model->name);
|
|
OldSlugable::removeSameSlugs($model, $model->slug);
|
|
$model->description = clean($model->description);
|
|
$model->description = FileStorageHelper::storeModelFilesFromHtml($model, $model->description);
|
|
if ($model->picture) {
|
|
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
|
}
|
|
$model->makeStatusSense();
|
|
$model->makeOrderableSense();
|
|
$model->minicrudCreating();
|
|
});
|
|
static::created(function (self $model) {
|
|
$model->flashMessages();
|
|
});
|
|
static::updating(function (self $model) {
|
|
// * ASSERTIONS
|
|
static::assertNameIsUnique($model->name, $model->id);
|
|
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
|
|
self::assertIsAboveZero('price', trans('Prix'), $model->price);
|
|
self::assertIsAboveZero('weight', trans('Poids'), $model->weight);
|
|
|
|
// * TRANSFORMATIONS
|
|
$model->reference = $model->reference ?: ToolboxHelper::getUniqueProductReference();
|
|
$model->slug = Str::slug($model->name);
|
|
if ($model->slug != $model->getOriginal('slug')) {
|
|
OldSlugable::removeSameSlugs($model, $model->getOriginal('slug'));
|
|
}
|
|
OldSlugable::insertSlug($model);
|
|
$model->description = clean($model->description);
|
|
$model->description = FileStorageHelper::storeModelFilesFromHtml($model, $model->description);
|
|
if ($model->picture) {
|
|
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
|
}
|
|
$model->makeStatusSense();
|
|
$model->makeOrderableSense();
|
|
$model->minicrudUpdating();
|
|
});
|
|
static::updated(function (self $model) {
|
|
FileStorageHelper::removeOldFile($model, 'picture');
|
|
FileStorageHelper::removeOldModelFilesFromHtml($model->description, $model->getOriginal('description'));
|
|
$model->flashMessages();
|
|
$model->minicrudUpdated();
|
|
});
|
|
static::deleted(function (self $model) {
|
|
$model->oldSlugs()->delete();
|
|
FileStorageHelper::removeFile($model, 'picture');
|
|
FileStorageHelper::removeModelFilesOfHtml($model->description);
|
|
$model->minicrudDeleted();
|
|
});
|
|
}
|
|
|
|
// * RELATIONSHIPS
|
|
|
|
/**
|
|
* BelongsToMany products relation
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
|
*/
|
|
public function associatedProducts()
|
|
{
|
|
return $this->belongsToMany(self::class, 'product_product', 'associated_id', 'product_id');
|
|
}
|
|
|
|
/**
|
|
* BelongsTo range relation
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
*/
|
|
public function range()
|
|
{
|
|
return $this->belongsTo(Range::class);
|
|
}
|
|
|
|
/**
|
|
* MorphMany old_slugable relation
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
|
|
*/
|
|
public function oldSlugs()
|
|
{
|
|
return $this->morphMany(OldSlugable::class, 'old_slugable');
|
|
}
|
|
|
|
/**
|
|
* BelongsTo dicount relation
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
*/
|
|
public function discount()
|
|
{
|
|
return $this->belongsTo(Discount::class);
|
|
}
|
|
|
|
/**
|
|
* belongsToMany tags relation
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
|
*/
|
|
public function tags()
|
|
{
|
|
return $this->belongsToMany(Tag::class);
|
|
}
|
|
|
|
/**
|
|
* belongsTo xml_inco relation
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
*/
|
|
public function xmlInco()
|
|
{
|
|
return $this->belongsTo(XmlInco::class);
|
|
}
|
|
|
|
// * METHODS
|
|
|
|
/**
|
|
* Flash boolean attributes modification messages
|
|
*
|
|
* @param boolean $old Ignore old attribute comparison.
|
|
* @return void
|
|
*/
|
|
public function flashMessages(bool $old = true)
|
|
{
|
|
$booleanChecks = [
|
|
'status' => [
|
|
'info' => trans(':model est maintenant publié', [
|
|
'model' => Str::singular(trans('models.classes.products'))
|
|
]),
|
|
'warning' => trans(':model est maintenant dépublié', [
|
|
'model' => Str::singular(trans('models.classes.products'))
|
|
])
|
|
],
|
|
'star' => [
|
|
'info' => trans(':model est maintenant mis en avant', [
|
|
'model' => Str::singular(trans('models.classes.products'))
|
|
]),
|
|
'warning' => trans(':model n\'est maintenant plus mis en avant', [
|
|
'model' => Str::singular(trans('models.classes.products'))
|
|
])
|
|
],
|
|
'orderable' => [
|
|
'warning' => trans(':model est maintenant disponible à la vente', [
|
|
'model' => Str::singular(trans('models.classes.products'))
|
|
]),
|
|
'error' => trans(':model n\'est maintenant plus disponible à la vente', [
|
|
'model' => Str::singular(trans('models.classes.products'))
|
|
])
|
|
]
|
|
];
|
|
$sessionMessages = [];
|
|
|
|
// * Merge messages
|
|
foreach ($booleanChecks as $attribute => $booleanCheck) {
|
|
if ($old and $this->getOriginal($attribute) == $this->{$attribute}) {
|
|
continue;
|
|
}
|
|
/** @var string $flashType */
|
|
$flashType = \array_keys($booleanCheck)[$this->{$attribute} ? 0 : 1];
|
|
if (!\array_key_exists($flashType, $sessionMessages)) {
|
|
$sessionMessages[$flashType] = [];
|
|
}
|
|
$sessionMessages[$flashType][] = $booleanCheck[$flashType];
|
|
}
|
|
|
|
foreach ($sessionMessages as $flashType => $messages) {
|
|
session()->flash($flashType, collect($messages)->implode("\n"));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check this product an inco sheet associated and its type is inco
|
|
* @return boolean
|
|
*/
|
|
public function hasAndIsInco(): bool
|
|
{
|
|
return $this->type->value === DefaultProductType::inco()->value and
|
|
$this->xmlInco;
|
|
}
|
|
|
|
/**
|
|
* Getter data_object, the object overloaded attributes from theme
|
|
*
|
|
* @return object
|
|
*/
|
|
public function getDataObjectAttribute(): object
|
|
{
|
|
return ToolboxHelper::arrayToObject($this->data);
|
|
}
|
|
|
|
/**
|
|
* Get inco nutritionnal values, if product is inco.
|
|
*
|
|
* @return object|null
|
|
*/
|
|
public function getIncoNutritionalValues(): ?object
|
|
{
|
|
if (!$this->hasAndIsInco()) {
|
|
return null;
|
|
}
|
|
|
|
return $this->xmlInco->fetchIncoNutritionalValues();
|
|
}
|
|
|
|
/**
|
|
* Asserts using validation that the name is unique
|
|
*
|
|
* @param mixed $name
|
|
* @param integer|null $id
|
|
* @return void
|
|
* @throws \Illuminate\Validation\ValidationException If an actu name already exists.
|
|
*/
|
|
private static function assertNameIsUnique($name, ?int $id = null)
|
|
{
|
|
$table = (new self())->getTable();
|
|
ToolboxHelper::assertFieldIsUnique($table, 'name', $name, $id);
|
|
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($name), $id);
|
|
}
|
|
|
|
/**
|
|
* Adjusts model status using current status and publish dates
|
|
*
|
|
* @return void
|
|
*/
|
|
public function makeStatusSense(): void
|
|
{
|
|
/** @var \Carbon\Carbon|null */
|
|
$publish_at = $this->publish_at;
|
|
/** @var \Carbon\Carbon|null */
|
|
$unpublish_at = $this->unpublish_at;
|
|
// * Ignore unpublish_at in the past
|
|
if ($unpublish_at and $unpublish_at->lessThan(now())) {
|
|
return;
|
|
}
|
|
// * Ignore publish_at in the futur
|
|
if ($publish_at and $publish_at->greaterThan(now())) {
|
|
return;
|
|
}
|
|
if ($publish_at or $unpublish_at) {
|
|
$this->status = $this->planificated_status;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Getter the current status following planification dates
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function getPlanificatedStatusAttribute(): bool
|
|
{
|
|
/** @var \Carbon\Carbon|null */
|
|
$publish_at = $this->publish_at;
|
|
/** @var \Carbon\Carbon|null */
|
|
$unpublish_at = $this->unpublish_at;
|
|
if ($publish_at and $unpublish_at) {
|
|
return $unpublish_at->greaterThanOrEqualTo(now()) and $publish_at->lessThan(now());
|
|
}
|
|
if ($publish_at) {
|
|
return $publish_at->lessThan(now());
|
|
}
|
|
if ($unpublish_at) {
|
|
return $unpublish_at->greaterThanOrEqualTo(now());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Adjusts model status using current orderable status and orderable dates
|
|
*
|
|
* @return void
|
|
*/
|
|
public function makeOrderableSense(): void
|
|
{
|
|
/** @var \Carbon\Carbon|null */
|
|
$orderable_at = $this->orderable_at;
|
|
/** @var \Carbon\Carbon|null */
|
|
$unorderable_at = $this->unorderable_at;
|
|
// * Ignore unorderable_at in the past
|
|
if ($unorderable_at and $orderable_at->lessThan(now())) {
|
|
return;
|
|
}
|
|
// * Ignore orderable_at in the futur
|
|
if ($orderable_at and $orderable_at->greaterThan(now())) {
|
|
return;
|
|
}
|
|
if ($orderable_at or $unorderable_at) {
|
|
$this->status = $this->order_planificated_status;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Getter the current orderable status following planification dates
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function getOrderPlanificatedStatusAttribute(): bool
|
|
{
|
|
/** @var \Carbon\Carbon|null */
|
|
$orderable_at = $this->orderable_at;
|
|
/** @var \Carbon\Carbon|null */
|
|
$unorderable_at = $this->unorderable_at;
|
|
if ($orderable_at and $unorderable_at) {
|
|
return $unorderable_at->greaterThanOrEqualTo(now()) and $orderable_at->lessThan(now());
|
|
}
|
|
if ($orderable_at) {
|
|
return $orderable_at->lessThan(now());
|
|
}
|
|
if ($unorderable_at) {
|
|
return $unorderable_at->greaterThanOrEqualTo(now());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Assert Is Above Zero
|
|
*
|
|
* @param string $attribute
|
|
* @param string $name
|
|
* @param integer $value
|
|
* @return void
|
|
* @throws \Illuminate\Validation\ValidationException If the value is less or equals to 0.
|
|
*/
|
|
private static function assertIsAboveZero(string $attribute, string $name, int $value): void
|
|
{
|
|
Validator::make([
|
|
$attribute => $value
|
|
], [
|
|
$attribute => 'numeric|min:0'
|
|
], [], [
|
|
$attribute => $name
|
|
])->validate();
|
|
}
|
|
|
|
// * Dynamic Minicrud inputs
|
|
|
|
/**
|
|
* Fetch Product Types
|
|
* @return array<string,object>
|
|
* @throws \RuntimeException If the theme does not have a valid 'types' definition.
|
|
*/
|
|
public static function fetchTypes(): array
|
|
{
|
|
return collect(\array_replace_recursive(
|
|
\collect(DefaultProductType::toArray())->mapWithKeys(function (DefaultProductType $type) {
|
|
return [$type->value => (object)$type->jsonSerialize()];
|
|
})->all(),
|
|
\collect(config('theme.types'))->mapWithKeys(function (array $typeDefinition) {
|
|
if (!array_key_exists('type', $typeDefinition)) {
|
|
throw new \RuntimeException('Theme type definition must have a type defined');
|
|
}
|
|
return [$typeDefinition['type']['value'] => (object)$typeDefinition['type']];
|
|
})->all()
|
|
))->unique()->all();
|
|
}
|
|
|
|
/**
|
|
* Fetch Product Types Inputs
|
|
* @return array<string,array>
|
|
* @throws \RuntimeException If the theme does not have a valid 'types' definition.
|
|
*/
|
|
public static function fetchTypesInputs(): array
|
|
{
|
|
$themeTypes = \collect(config('theme.types'))->keyBy('type.value');
|
|
return \collect(self::fetchTypes())->mapWithKeys(function (object $type) use ($themeTypes) {
|
|
switch (true) {
|
|
case $type->value === DefaultProductType::default()->value:
|
|
return [$type->value => []];
|
|
case $type->value === DefaultProductType::inco()->value:
|
|
return [$type->value => DefaultProductType::incoInputs()];
|
|
case $themeTypes->has($type->value):
|
|
$typeDefinition = $themeTypes->get($type->value);
|
|
if (!\is_array($typeDefinition)) {
|
|
throw new \RuntimeException('Theme type definition must be an array');
|
|
}
|
|
if (!array_key_exists('type', $typeDefinition)) {
|
|
throw new \RuntimeException('Theme type definition must have a type defined');
|
|
}
|
|
if (!array_key_exists('data', $typeDefinition)) {
|
|
throw new \RuntimeException('Theme type definition must have data defined');
|
|
}
|
|
return (object)[$typeDefinition['type']['value'] => $typeDefinition['data']];
|
|
default:
|
|
throw new \RuntimeException("Unhandled default type {$type->value}");
|
|
}
|
|
})->all();
|
|
}
|
|
|
|
/**
|
|
* Get Minicrud data definition for a product type
|
|
*
|
|
* @param string $typeValue
|
|
* @return mixed
|
|
* @throws \RuntimeException In the $typeValue does not have a minicrud data definition.
|
|
*/
|
|
public static function getMinicrudInputsFromType(string $typeValue)
|
|
{
|
|
if (!($minicrudData = \collect(self::fetchTypesInputs())->get($typeValue))) {
|
|
throw new \RuntimeException("Undefined minicrud data definition for type {$typeValue}");
|
|
}
|
|
return $minicrudData;
|
|
}
|
|
|
|
// * --- GETTERS ---
|
|
|
|
/**
|
|
* Getter type
|
|
*
|
|
* @return object
|
|
* @throws \RuntimeException If the product type is undefined in config.
|
|
*/
|
|
public function getTypeAttribute(): object
|
|
{
|
|
$types = self::fetchTypes();
|
|
$type = \strval($this->attributes['type']);
|
|
if (!\array_key_exists($type, $types)) {
|
|
throw new \RuntimeException("Undefined product type '$type'");
|
|
}
|
|
return $types[$type];
|
|
}
|
|
|
|
/**
|
|
* * Calcul par unité de produit
|
|
* https://blog.clever-age.com/fr/2022/04/28/tva%E2%80%AF-comment-bien-lappliquer-sur-un-site-e-commerce-ou-une-marketplace-en-europe%E2%80%AF/
|
|
*
|
|
* Les taux de TVA sont arrondis au centimes supérieurs.
|
|
*/
|
|
|
|
/**
|
|
* Getter tax
|
|
*
|
|
* * USED
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getTaxAttribute(): int
|
|
{
|
|
return round($this->discounted_price * ($this->tax_rate->rate / 100));
|
|
}
|
|
|
|
/**
|
|
* Getter price_ttc
|
|
*
|
|
* * USED
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getPriceTtcAttribute(): int
|
|
{
|
|
return $this->discounted_price + $this->tax;
|
|
}
|
|
|
|
/**
|
|
* Getter discount_rate as cents or percent
|
|
*
|
|
* * USED
|
|
*
|
|
* @return integer|float
|
|
*/
|
|
public function getDiscountRateAttribute()
|
|
{
|
|
/** @var \App\Models\Discount|null */
|
|
$applicableDiscount = $this->discount;
|
|
if (\is_null($applicableDiscount) or !$applicableDiscount->status) {
|
|
return 0;
|
|
}
|
|
return $applicableDiscount->discount_calculation;
|
|
}
|
|
|
|
/**
|
|
* Getter discount_value
|
|
*
|
|
* * USED
|
|
*
|
|
* @return integer
|
|
* @throws \RuntimeException If unhandled discount type.
|
|
*/
|
|
public function getDiscountValueAttribute(): int
|
|
{
|
|
switch (\optional($this->discount)->type) {
|
|
// * Remise en numeraire sur un produit
|
|
case DiscountType::amount():
|
|
$value = $this->price - ($this->price - $this->discount_rate);
|
|
return max($value, 0);
|
|
// * Remise en pourcentage
|
|
case DiscountType::percent():
|
|
return round($this->price * $this->discount_rate, 0, PHP_ROUND_HALF_DOWN);
|
|
// * Pas de remise
|
|
case null:
|
|
return 0;
|
|
default:
|
|
throw new \RuntimeException('Unhandled discount type');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Getter discounted_price
|
|
*
|
|
* * USED
|
|
*
|
|
* @return integer
|
|
* @throws \RuntimeException If unhandled discount type.
|
|
*/
|
|
public function getDiscountedPriceAttribute(): int
|
|
{
|
|
return \max($this->price - $this->discount_value, 0);
|
|
}
|
|
|
|
/**
|
|
* Getter undiscounted_price_ttc
|
|
*
|
|
* * USED
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getUndiscountedPriceTtcAttribute()
|
|
{
|
|
return $this->price + round($this->price * ($this->tax_rate->rate / 100));
|
|
}
|
|
|
|
/**
|
|
* Getter is_discounted attribute
|
|
*
|
|
* * USED
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function getIsDiscountedAttribute(): bool
|
|
{
|
|
return (bool)\optional($this->discount)->status;
|
|
}
|
|
|
|
// * --- END GETTERS ---
|
|
// * Implement Buyable For Cart
|
|
/**
|
|
* buyablePrice attribute alias
|
|
*
|
|
* ! Overload Gloudemans\Shoppingcart\Contracts\Buyable::getBuyablePrice()
|
|
*
|
|
* @param mixed $options
|
|
* @return integer
|
|
*
|
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
|
*/
|
|
public function getBuyablePrice($options = null)
|
|
{
|
|
// @phpcs:enable
|
|
return $this->price;
|
|
}
|
|
|
|
/**
|
|
* buyableWeight attribute alias
|
|
*
|
|
* ! Overload Gloudemans\Shoppingcart\Contracts\Buyable::getBuyableWeight()
|
|
*
|
|
* @param mixed $options
|
|
* @return integer
|
|
*
|
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
|
*/
|
|
public function getBuyableWeight($options = null)
|
|
{
|
|
// @phpcs:enable
|
|
return $this->weight;
|
|
}
|
|
|
|
// * METHODS
|
|
|
|
/**
|
|
* Get displayable price value
|
|
*
|
|
* @param mixed $value
|
|
* @param string $suffix
|
|
* @return string
|
|
*/
|
|
public static function fmtCurrency($value, string $suffix = 'TTC'): string
|
|
{
|
|
return sprintf(
|
|
'%s € %s',
|
|
number_format(\intval($value) / 100, 2, ',', ' '),
|
|
$suffix
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get displayable price value
|
|
*
|
|
* @param mixed $value
|
|
* @return string
|
|
*/
|
|
public static function fmtSellUnit($value): string
|
|
{
|
|
return !empty($value) ? "/ {$value}" : '';
|
|
}
|
|
}
|