450 lines
16 KiB
PHP
450 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\Products\CertificationEnum;
|
|
use App\Enums\Products\ClaimEnum;
|
|
use App\Enums\Products\DistributionNetworkEnum;
|
|
use App\Enums\Products\GeoAreaEnum;
|
|
use App\Enums\Products\OriginEnum;
|
|
use App\Enums\Products\RangeEnum;
|
|
use App\Enums\Products\RayEnum;
|
|
use App\Enums\Products\SourceEnum;
|
|
use App\Http\Resources\ProductResource;
|
|
use App\Lib\Helpers\FileStorageHelper;
|
|
use App\Lib\Helpers\ToolboxHelper;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Mccarlosen\LaravelMpdf\LaravelMpdf;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
/**
|
|
* FIXME: using migration, do not set nullable props without a real need.
|
|
*
|
|
* @property integer $id
|
|
* @property integer $user_id
|
|
* @property integer $brand_id
|
|
* @property string $name
|
|
* @property \Carbon\Carbon $seen_at
|
|
* @property \App\Enums\Products\SourceEnum $source
|
|
* @property string|null $fair
|
|
* @property string|null $shop
|
|
* @property string|null $catalogue
|
|
* @property string|null $gencod
|
|
* @property string|null $picture
|
|
* @property string $picture_alt
|
|
* @property string $picture_title
|
|
* @property array|null $docs
|
|
* @property string|null $url
|
|
* @property \App\Enums\Products\CertificationEnum|null $certification
|
|
* @property array<integer>|null $origins
|
|
* @property \Illuminate\Support\Collection<\App\Enums\Products\OriginEnum> $origins_enum
|
|
* @property array<integer>|null $claims
|
|
* @property \Illuminate\Support\Collection<\App\Enums\Products\OriginEnum> $claims_enum
|
|
* @property string|null $specified_claim
|
|
* @property \App\Enums\Products\DistributionNetworkEnum $distribution_network
|
|
* @property \App\Enums\Products\RayEnum $ray
|
|
* @property \App\Enums\Products\RangeEnum $product_range
|
|
* @property \App\Enums\Products\GeoAreaEnum $geo area
|
|
* @property integer|null $weight
|
|
* @property integer|null $pvc
|
|
* @property string|null $comment
|
|
* @property boolean $is_tasted
|
|
* @property integer|null $tasters_number
|
|
* @property \Carbon\Carbon|null $created_at
|
|
* @property \Carbon\Carbon|null $updated_at
|
|
* @property \Carbon\Carbon|null $deleted_at
|
|
*
|
|
* * RELATIONSHIPS
|
|
* @property-read \App\Models\Brand $brand
|
|
* @property-read \App\Models\User $user
|
|
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\ProductComment> $productComments
|
|
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Keyword> $productKeywords
|
|
*/
|
|
class Product extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $fillable = [
|
|
'user_id',
|
|
'brand_id',
|
|
'name',
|
|
// ? FIXME: ?why is there brand here, column is not in DB.
|
|
'brand',
|
|
'seen_at',
|
|
'source',
|
|
'fair',
|
|
'shop',
|
|
'catalogue',
|
|
'gencod',
|
|
'picture',
|
|
'picture_alt',
|
|
'picture_title',
|
|
'docs',
|
|
'url',
|
|
'certification',
|
|
'origins',
|
|
'claims',
|
|
'specified_claim',
|
|
'distribution_network',
|
|
'ray',
|
|
'product_range',
|
|
'geo_area',
|
|
'weight',
|
|
'pvc',
|
|
'comment',
|
|
'is_tasted',
|
|
'tasters_number',
|
|
'created_at',
|
|
'updated_at',
|
|
'deleted_at'
|
|
];
|
|
|
|
/**
|
|
* The attributes that should be cast.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $casts = [
|
|
'source' => SourceEnum::class,
|
|
'docs' => 'json',
|
|
'certification' => CertificationEnum::class,
|
|
'origins' => 'json',
|
|
'claims' => 'json',
|
|
'distribution_network' => DistributionNetworkEnum::class,
|
|
'ray' => RayEnum::class,
|
|
'product_range' => RangeEnum::class,
|
|
'geo_area' => GeoAreaEnum::class,
|
|
'is_tasted' => 'boolean',
|
|
'seen_at' => 'datetime',
|
|
'created_at' => 'datetime',
|
|
'updated_at' => 'datetime',
|
|
'deleted_at' => 'datetime',
|
|
];
|
|
|
|
// * EVENTS
|
|
|
|
/**
|
|
* The "booted" method of the model.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected static function booted(): void
|
|
{
|
|
static::creating(function (self $model) {
|
|
self::assertFieldIsUnique('name', $model->name);
|
|
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
|
self::updateSource($model);
|
|
// FIXME: lier le produit à l'utilisateur connecté lors de l'ajout du système de connexion.
|
|
$model->user_id = 1;
|
|
});
|
|
static::updating(function (self $model) {
|
|
self::deleteOldDocuments($model, true);
|
|
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
|
self::updateSource($model);
|
|
self::updateSpecifiedClaim($model);
|
|
});
|
|
static::updated(function (self $model) {
|
|
FileStorageHelper::removeOldFile($model, 'picture');
|
|
});
|
|
static::deleted(function (self $model) {
|
|
self::deleteOldDocuments($model);
|
|
FileStorageHelper::removeFile($model, 'picture');
|
|
});
|
|
}
|
|
|
|
// * METHODS
|
|
|
|
/**
|
|
* Asserts using validation that the field is unique.
|
|
*
|
|
* @param string $field
|
|
* @param string $value
|
|
* @param integer|null $id
|
|
* @return void
|
|
* @throws \Illuminate\Validation\ValidationException If field already exists.
|
|
*/
|
|
private static function assertFieldIsUnique(string $field, string $value, ?int $id = null)
|
|
{
|
|
$table = (new self())->getTable();
|
|
ToolboxHelper::assertFieldIsUnique($table, $field, $value, $id);
|
|
}
|
|
|
|
/**
|
|
* Update data specified claim.
|
|
*
|
|
* @param \App\Models\Product $model
|
|
* @return void
|
|
*/
|
|
private static function updateSpecifiedClaim(Product $model)
|
|
{
|
|
$check = false;
|
|
foreach ($model->claims as $claimValue) {
|
|
$check = (intval($claimValue) === \App\Enums\Products\ClaimEnum::others->value) ? true : false;
|
|
}
|
|
$model->specified_claim = ($check) ? $model->specified_claim : null;
|
|
}
|
|
|
|
/**
|
|
* Update data source.
|
|
*
|
|
* @param self $model
|
|
* @return void
|
|
*/
|
|
private static function updateSource(self $model)
|
|
{
|
|
if (
|
|
$model->source === \App\Enums\Products\SourceEnum::showConsumer ||
|
|
$model->source === \App\Enums\Products\SourceEnum::showCustomer ||
|
|
$model->source === \App\Enums\Products\SourceEnum::showTrade
|
|
) {
|
|
$model->shop = null;
|
|
$model->catalogue = null;
|
|
} elseif ($model->source === \App\Enums\Products\SourceEnum::magazin) {
|
|
$model->fair = null;
|
|
$model->catalogue = null;
|
|
} elseif ($model->source === \App\Enums\Products\SourceEnum::catalogArgumentary) {
|
|
$model->fair = null;
|
|
$model->shop = null;
|
|
} else {
|
|
$model->fair = null;
|
|
$model->shop = null;
|
|
$model->catalogue = null;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Delete old documents.
|
|
*
|
|
* @param self $model
|
|
* @param boolean $onlyIfUpdated
|
|
* @return void
|
|
*/
|
|
public static function deleteOldDocuments(self $model, bool $onlyIfUpdated = false)
|
|
{
|
|
$original = \collect($model->getOriginal('docs'))->map(fn ($document) => strval($document));
|
|
$new = \collect($model->docs)->map(fn ($document) => strval($document))->flip();
|
|
foreach ($original as $document) {
|
|
// * Ignore if document is to keep.
|
|
if ($onlyIfUpdated and $new->has($document)) {
|
|
continue;
|
|
}
|
|
|
|
$originalPath = \ltrim(\urldecode($document), '/');
|
|
if (\strpos($originalPath, 'storage') !== 0) {
|
|
continue;
|
|
}
|
|
$originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath);
|
|
if (Storage::disk('public')->exists($originalPath)) {
|
|
Storage::disk('public')->delete($originalPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate product sheet PDF.
|
|
*
|
|
* @param self $model
|
|
* @param string $title
|
|
* @return \Mccarlosen\LaravelMpdf\LaravelMpdf
|
|
*/
|
|
public static function generateProductPdf(self $model, string $title): LaravelMpdf
|
|
{
|
|
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */
|
|
$mpdf = app('laravel-mpdf');
|
|
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */
|
|
$pdf = $mpdf->loadView(
|
|
'documents.product',
|
|
// * Dirty convert to object.
|
|
[
|
|
'title' => $title,
|
|
'product' => \json_decode(\json_encode(new ProductResource($model)))
|
|
],
|
|
[],
|
|
[
|
|
'mode' => 'utf-8',
|
|
'format' => 'A4',
|
|
'default_font_size' => '12',
|
|
'default_font' => 'sans-serif',
|
|
'mirrorMargins' => true,
|
|
'margin_left' => 10,
|
|
'margin_right' => 10,
|
|
'margin_top' => 10,
|
|
'margin_bottom' => 10,
|
|
'margin_header' => 10,
|
|
'margin_footer' => 10,
|
|
'margin_top' => 10,
|
|
'orientation' => 'L',
|
|
'title' => $title,
|
|
'author' => \config('app.name', 'Startcoq'),
|
|
'watermark' => '',
|
|
'show_watermark' => false,
|
|
'watermark_font' => 'sans-serif',
|
|
'display_mode' => 'fullpage',
|
|
'watermark_text_alpha' => 0.1,
|
|
'custom_font_dir' => '',
|
|
'custom_font_data' => [],
|
|
'auto_language_detection' => false,
|
|
'temp_dir' => rtrim(
|
|
ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir(),
|
|
DIRECTORY_SEPARATOR
|
|
),
|
|
'pdfa' => false,
|
|
'pdfaauto' => false,
|
|
]
|
|
);
|
|
return $pdf;
|
|
}
|
|
|
|
/**
|
|
* Generate a pdf with multiple products.
|
|
*
|
|
* @param array<integer> $printMultiProduct
|
|
* @param string $title
|
|
* @return \Mccarlosen\LaravelMpdf\LaravelMpdf
|
|
*/
|
|
public static function generateMultipleProductPdf(array $printMultiProduct, string $title)
|
|
{
|
|
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdf|null */
|
|
$pdf = null;
|
|
// * Loop all Model ids.
|
|
foreach ($printMultiProduct as $productId) {
|
|
/** @var \App\Models\Product */
|
|
$product = Product::query()->where('id', $productId)->firstOrFail();
|
|
// * If not yet defined, set the first page.
|
|
if (is_null($pdf)) {
|
|
$pdf = Product::generateProductPdf($product, $title);
|
|
}
|
|
|
|
// * Add another page and add HTML from view to this.
|
|
$pdf->getMpdf()->AddPage();
|
|
$pdf->getMpdf()->WriteHTML((string)view(
|
|
'documents.product-body',
|
|
['product' => (object)(new ProductResource($product))->toArray()],
|
|
));
|
|
}
|
|
return $pdf;
|
|
}
|
|
|
|
/**
|
|
* Fetch and stream export file.
|
|
*
|
|
* @param string $filepath
|
|
* @return \Symfony\Component\HttpFoundation\StreamedResponse
|
|
* @throws \Error If file storage config is wrong.
|
|
*/
|
|
public static function downloadPrivateExportFile(string $filepath): StreamedResponse
|
|
{
|
|
/** @var \Illuminate\Filesystem\FilesystemAdapter */
|
|
$disk = Storage::disk('export');
|
|
|
|
if (!($disk instanceof \Illuminate\Filesystem\FilesystemAdapter)) {
|
|
throw new \Error('Le disque de stockage doit être un système de fichiers local');
|
|
}
|
|
|
|
if (!$disk->exists($filepath)) {
|
|
\abort(400, trans('Le fichier demandé est périmé et n\'existe plus'));
|
|
}
|
|
|
|
return \response()->stream(function () use ($disk, $filepath) {
|
|
echo $disk->get($filepath);
|
|
}, 200, [
|
|
'Cache-Control' => 'public, must-revalidate, max-age=0',
|
|
'Content-Description' => 'File Transfer',
|
|
'Content-Type' => $disk->mimeType($filepath),
|
|
'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
|
|
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT'
|
|
])->send();
|
|
}
|
|
|
|
// * GETTERS
|
|
|
|
/**
|
|
* Get origins_enum attribute
|
|
*
|
|
* @return \Illuminate\Support\Collection<\App\Enums\Ingredients\OriginEnum>
|
|
*/
|
|
public function getOriginsEnumAttribute(): Collection
|
|
{
|
|
return \collect($this->getAttribute('origins'))->filter()
|
|
->map(fn (int $originEnumValue) => OriginEnum::make($originEnumValue));
|
|
}
|
|
|
|
/**
|
|
* Set origins_enum attribute
|
|
*
|
|
* @param mixed $value
|
|
* @return void
|
|
*/
|
|
public function setOriginsEnumAttribute(mixed $value): void
|
|
{
|
|
$this->setAttribute('origins', \collect($value)->filter()
|
|
->map(fn ($originEnumValue) => OriginEnum::make($originEnumValue)->value())
|
|
->all());
|
|
}
|
|
|
|
/**
|
|
* Get claims_enum attribute
|
|
*
|
|
* @return \Illuminate\Support\Collection<\App\Enums\Ingredients\ClaimEnum>
|
|
*/
|
|
public function getClaimsEnumAttribute(): Collection
|
|
{
|
|
return \collect($this->getAttribute('claims'))->filter()
|
|
->map(fn (int $originEnumValue) => ClaimEnum::make($originEnumValue));
|
|
}
|
|
|
|
/**
|
|
* Set claims_enum attribute
|
|
*
|
|
* @param mixed $value
|
|
* @return void
|
|
*/
|
|
public function setClaimsEnumAttribute(mixed $value): void
|
|
{
|
|
$this->setAttribute('claims', \collect($value)->filter()
|
|
->map(fn ($claimEnumValue) => OriginEnum::make($claimEnumValue)->value())
|
|
->all());
|
|
}
|
|
|
|
// * RELATIONSHIPS
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
|
*/
|
|
public function productComments()
|
|
{
|
|
return $this->hasMany(ProductComment::class);
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
*/
|
|
public function user()
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
*/
|
|
public function brand()
|
|
{
|
|
return $this->belongsTo(Brand::class);
|
|
}
|
|
|
|
/**
|
|
* MorphToMany keywords relation.
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
|
|
*/
|
|
public function productKeywords()
|
|
{
|
|
return $this->morphToMany(Keyword::class, 'keywordable');
|
|
}
|
|
}
|