482 lines
17 KiB
PHP
482 lines
17 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\Departaimants\DepartaimantCollectionType;
|
|
use App\Enums\Departaimants\DepartaimantCurrent;
|
|
use App\Enums\Departaimants\DepartaimantStatus;
|
|
use App\Enums\Departaimants\DepartaimantVintage;
|
|
use App\Enums\Departaimants\IDepartaimant;
|
|
use Illuminate\Contracts\Database\Query\Builder;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
/**
|
|
* Departaimants Model
|
|
* Has functions to store departaimants on multiple statuses.
|
|
*
|
|
* @property \App\Enums\Departaimants\DepartaimantCollectionType $type
|
|
* @property integer $customer_id
|
|
*
|
|
* @property-read \Carbon\Carbon $created_at Created date.
|
|
* @property-read \Carbon\Carbon $updated_at Updated date.
|
|
*
|
|
* @property-read \App\Models\Customer $customer Customer BelongsTo relationship.
|
|
*
|
|
* @uses App\Enums\Departaimant
|
|
* @uses App\Enums\DepartaimantStatus
|
|
* @uses App\Enums\DepartaimantCollectionType
|
|
*/
|
|
class DepartaimantsCollection extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
/**
|
|
* The attributes that should be cast.
|
|
*
|
|
* @var array<string,string>
|
|
*/
|
|
protected $casts = [
|
|
'type' => DepartaimantCollectionType::class
|
|
];
|
|
|
|
/**
|
|
* The table associated with the model.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $table = 'departaimants';
|
|
|
|
/**
|
|
* The model's attributes.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $attributes = [
|
|
'type' => DepartaimantCollectionType::current
|
|
];
|
|
|
|
/**
|
|
* The attributes that are mass assignable.
|
|
*
|
|
* @var array<integer,string>
|
|
*/
|
|
public $fillable = [
|
|
'type'
|
|
];
|
|
|
|
/**
|
|
* Columns prefix.
|
|
*
|
|
* @var string
|
|
*/
|
|
private static $columnTableNamePrefix = 'col';
|
|
|
|
/**
|
|
* Le prefix d'un table departaimant.
|
|
*
|
|
* @var integer
|
|
*/
|
|
private static $tableFields = 4;
|
|
|
|
/**
|
|
* Le nombre de colonnes des departaimants.
|
|
*
|
|
* * La largeur en bits d'un départaimant
|
|
* * to be set in construct
|
|
*
|
|
* @var integer
|
|
*/
|
|
private static $departementWidth = 2;
|
|
|
|
|
|
/**
|
|
* Handle dynamic static method calls into the model.
|
|
*
|
|
* @param string $name
|
|
* @param array $arguments
|
|
* @return mixed
|
|
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
|
|
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
|
|
*/
|
|
public static function __callStatic($name, $arguments)
|
|
{
|
|
// phpcs:enable
|
|
if ($name === 'defaultType') {
|
|
return DepartaimantCollectionType::current;
|
|
}
|
|
|
|
return parent::__callStatic($name, $arguments);
|
|
}
|
|
|
|
/**
|
|
* Create a new Eloquent model instance.
|
|
*
|
|
* @param array $attributes
|
|
*/
|
|
public function __construct(array $attributes = [])
|
|
{
|
|
$this->type = DepartaimantCollectionType::current;
|
|
// Set Default 0 to cols instead of null.
|
|
for ($i = 1; $i < $this->tableFields; $i++) {
|
|
$this->attributes[sprintf('col%d', $i)] = 0;
|
|
}
|
|
if (\array_key_exists('type', $attributes)) {
|
|
$this->attributes['type'] = DepartaimantCollectionType::make($attributes['type']);
|
|
}
|
|
parent::__construct($attributes);
|
|
}
|
|
|
|
// * RELATIONSHIPS
|
|
|
|
/**
|
|
* Get the customer that owns the DepartaimantsCollection.
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
*/
|
|
public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
{
|
|
return $this->belongsTo(Customer::class);
|
|
}
|
|
|
|
// * METHODS
|
|
|
|
/**
|
|
* Add a Departaiment to this collection.
|
|
*
|
|
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
|
|
* @return void
|
|
*/
|
|
public function add(IDepartaimant $departaimant): void
|
|
{
|
|
$this->set($departaimant, DepartaimantStatus::has);
|
|
}
|
|
|
|
/**
|
|
* Removes a Departaime from this collection.
|
|
*
|
|
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
|
|
* @return void
|
|
*/
|
|
public function remove(IDepartaimant $departaimant): void
|
|
{
|
|
$this->reset($departaimant);
|
|
}
|
|
|
|
/**
|
|
* Checks if this collection has a Departaimant.
|
|
*
|
|
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
|
|
* @return boolean
|
|
*/
|
|
public function has(IDepartaimant $departaimant): bool
|
|
{
|
|
return $this->status($departaimant, DepartaimantStatus::has);
|
|
}
|
|
|
|
/**
|
|
* Set the Departaimant to a specific status.
|
|
*
|
|
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
|
|
* @param \App\Enums\Departaimants\DepartaimantStatus $status
|
|
* @return void
|
|
*/
|
|
public function set(IDepartaimant $departaimant, DepartaimantStatus $status): void
|
|
{
|
|
$depCol = static::depColInTable($this->type, $departaimant);
|
|
$tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($this->type, $departaimant));
|
|
$this->reset($departaimant);
|
|
// * OR
|
|
$this->$tableName |= ($status->value << $depCol);
|
|
}
|
|
|
|
/**
|
|
* Test the Departaimant status.
|
|
*
|
|
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
|
|
* @param \App\Enums\Departaimants\DepartaimantStatus $status
|
|
* @return boolean
|
|
*/
|
|
public function status(IDepartaimant $departaimant, DepartaimantStatus $status): bool
|
|
{
|
|
$depCol = static::depColInTable($this->type, $departaimant);
|
|
$tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($this->type, $departaimant));
|
|
/**
|
|
* On créer le masque, puis on le décale vers la gauche de $depCol
|
|
* On fait un & binaire pour récupérer la valeur
|
|
* Puis on la redécale vers la droite
|
|
* Enfin on la compare à l'énum DepartaimantStatus::has()
|
|
*/
|
|
$mask = bindec(str_replace('0', '1', decbin(self::$departementWidth))) << $depCol;
|
|
return ($status->value << $depCol) === ($this->$tableName & $mask);
|
|
}
|
|
|
|
/**
|
|
* Get all Customer which has their departaimant with a status.
|
|
*
|
|
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
|
|
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
|
|
* @param \App\Enums\Departaimants\DepartaimantStatus $status
|
|
* @return \Illuminate\Database\Query\Builder
|
|
*/
|
|
public static function getCustomersWithStatusAndDepartaimant(
|
|
DepartaimantCollectionType $depColtype,
|
|
IDepartaimant $departaimant,
|
|
DepartaimantStatus $status
|
|
): \Illuminate\Database\Query\Builder {
|
|
$depCol = self::depColInTable($depColtype, $departaimant);
|
|
$tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($depColtype, $departaimant));
|
|
/**
|
|
* On créer le masque, puis on le décale vers la gauche de $depCol
|
|
* On fait un & binaire pour récupérer la valeur
|
|
* Puis on la redécale vers la droite
|
|
* Enfin on la compare à l'énum DepartaimantStatus::has()->value
|
|
*/
|
|
$mask = bindec(str_replace('0', '1', decbin(self::$departementWidth))) << $depCol;
|
|
return Customer::query()->with('departaimantsCollections')
|
|
->whereHas('departaimantsCollections', function (Builder $query) use ($tableName, $mask, $status, $depCol) {
|
|
$query->whereRaw(sprintf('((%d << %d) = (%s & %d))', $status->value, $depCol, $tableName, $mask));
|
|
})->getQuery();
|
|
}
|
|
|
|
/**
|
|
* Get all Customer which has their departaimant with a status.
|
|
*
|
|
* @param array $departaimants
|
|
* @param \App\Enums\Departaimants\DepartaimantStatus $status
|
|
* @param \App\Enums\Departaimants\DepartaimantCollectionType $type
|
|
* @return \Illuminate\Database\Eloquent\Builder
|
|
*/
|
|
public static function getDepartaimantsCollectionWithStatus(
|
|
array $departaimants,
|
|
DepartaimantStatus $status,
|
|
DepartaimantCollectionType $type
|
|
): \Illuminate\Database\Eloquent\Builder {
|
|
$req = static::query()->where('type', $type->value);
|
|
$whereReq = '';
|
|
$i = 0;
|
|
|
|
foreach ($departaimants as $departaimant) {
|
|
$depCol = self::depColInTable($type, $departaimant);
|
|
$tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($type, $departaimant));
|
|
/**
|
|
* On créer le masque, puis on le décale vers la gauche de $depCol
|
|
* On fait un & binaire pour récupérer la valeur
|
|
* Puis on la redécale vers la droite
|
|
* Enfin on la compare à l'énum DepartaimantStatus::has()->value
|
|
*/
|
|
$mask = bindec(str_replace('0', '1', decbin(self::$departementWidth))) << $depCol;
|
|
$whereReq .= sprintf('((%d << %d) = (%s & %d)) ', $status->value, $depCol, $tableName, $mask);
|
|
if ($i++ !== count($departaimants) - 1) {
|
|
$whereReq .= " OR ";
|
|
}
|
|
}
|
|
if (strlen($whereReq)) {
|
|
$req->whereRaw("($whereReq)");
|
|
}
|
|
return $req;
|
|
}
|
|
|
|
/**
|
|
* Get the Departaimant status.
|
|
*
|
|
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
|
|
* @return \App\Enums\Departaimants\DepartaimantStatus
|
|
*/
|
|
public function getStatus(IDepartaimant $departaimant): \App\Enums\Departaimants\DepartaimantStatus
|
|
{
|
|
$depCol = static::depColInTable($this->type, $departaimant);
|
|
$tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($this->type, $departaimant));
|
|
/**
|
|
* On créer le masque, puis on le décale vers la gauche de $depCol
|
|
* On fait un & binaire pour récupérer la valeur
|
|
* Puis on la redécale vers la droite
|
|
* Enfin on la compare à l'énum DepartaimantStatus::has()
|
|
*/
|
|
$mask = bindec(str_replace('0', '1', decbin(self::$departementWidth))) << $depCol;
|
|
return DepartaimantStatus::make(($this->$tableName & $mask) >> $depCol);
|
|
}
|
|
|
|
/**
|
|
* Get a list of all the departaimants and their status.
|
|
*
|
|
* @uses \App\Enums\Departaimants\IDepartaimant
|
|
* @uses \App\Enums\Departaimants\DepartaimantStatus
|
|
* @return array<object> with
|
|
* departaimant: \App\Enums\Departaimants\IDepartaimant,
|
|
* status: \App\Enums\Departaimants\DepartaimantStatus
|
|
*/
|
|
public function getAllDepartaimants(): array
|
|
{
|
|
$deps = [];
|
|
$className = self::getDepTypeClass($this->type);
|
|
foreach (self::getDepKeys($this->type) as $depKey) {
|
|
/** @var \App\Enums\Departaimants\IDepartaimant */
|
|
$dep = \forward_static_call([$className, 'make'], $depKey);
|
|
$deps[$depKey] = (object) [
|
|
'departaimant' => $dep,
|
|
'status' => $this->getStatus($dep)
|
|
];
|
|
}
|
|
return $deps;
|
|
}
|
|
|
|
/**
|
|
* Get a list of all the departaimants and their status.
|
|
*
|
|
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
|
|
* @param \App\Enums\Departaimants\DepartaimantStatus $status
|
|
* @uses \App\Enums\Departaimants\IDepartaimant
|
|
* @uses \App\Enums\Departaimants\DepartaimantStatus
|
|
* @return array<object> with
|
|
* departaimant: \App\Enums\Departaimants\IDepartaimant,
|
|
* status: \App\Enums\Departaimants\DepartaimantStatus
|
|
*/
|
|
public function getAllDepartaimantsWithStatus(
|
|
DepartaimantCollectionType $depColtype,
|
|
DepartaimantStatus $status
|
|
): array {
|
|
$deps = [];
|
|
$className = self::getDepTypeClass($depColtype);
|
|
foreach (self::getDepKeys($depColtype) as $depKey) {
|
|
/** @var \App\Enums\Departaimants\IDepartaimant */
|
|
$dep = \forward_static_call([$className, 'make'], $depKey);
|
|
if ($status->equals($this->getStatus($dep))) {
|
|
$deps[$depKey] = (object) [
|
|
'departaimant' => $dep,
|
|
'status' => $this->getStatus($dep)
|
|
];
|
|
}
|
|
}
|
|
return $deps;
|
|
}
|
|
|
|
/**
|
|
* Set the Departaimant column to 0.
|
|
*
|
|
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
|
|
* @return void
|
|
*/
|
|
private function reset(IDepartaimant $departaimant): void
|
|
{
|
|
$depCol = self::depColInTable($this->type, $departaimant);
|
|
$tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($this->type, $departaimant));
|
|
// AND max enum value.
|
|
$this->$tableName &= ~(DepartaimantStatus::wants->value << $depCol);
|
|
}
|
|
|
|
/**
|
|
* Donne la 'table' (colonne SQL) dans laquelle se trouve le département.
|
|
*
|
|
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
|
|
* @param \App\Enums\Departaimants\IDepartaimant $dep
|
|
* @return integer
|
|
*/
|
|
public static function depField(DepartaimantCollectionType $depColtype, IDepartaimant $dep): int
|
|
{
|
|
$fieldCols = self::getTableColumnWith();
|
|
$totalFieldCols = self::$tableFields * $fieldCols;
|
|
$depsPerField = floor($totalFieldCols / self::$tableFields);
|
|
return (int) floor((array_search(
|
|
'.' . $dep->value(),
|
|
array_keys(self::getDeps($depColtype))
|
|
) + $depsPerField) / $depsPerField);
|
|
}
|
|
|
|
/**
|
|
* Donne la colonne (binaire) dans laquelle se trouve le département
|
|
* dans sa 'table' (colonne SQL).
|
|
*
|
|
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
|
|
* @param \App\Enums\Departaimants\IDepartaimant $dep
|
|
* @return integer
|
|
*/
|
|
public static function depColInTable(DepartaimantCollectionType $depColtype, IDepartaimant $dep): int
|
|
{
|
|
$tableColumnWith = self::getTableColumnWith();
|
|
$depsKeys = self::getDepKeys($depColtype);
|
|
$i = 0;
|
|
foreach ($depsKeys as $value) {
|
|
if ($dep->value() == $value) {
|
|
return $i * self::$departementWidth;
|
|
}
|
|
$i = $i >= ($tableColumnWith - 1) ? 0 : $i + 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Donne la valeur (masque binaire) d'un département.
|
|
*
|
|
* @param \App\Enums\Departaimants\IDepartaimant $dep
|
|
* @return integer
|
|
*/
|
|
public function depVal(IDepartaimant $dep): int
|
|
{
|
|
return self::getDeps($this->type)['.' . $dep->value()];
|
|
}
|
|
|
|
/**
|
|
* Genère la liste des departements avec leur masques.
|
|
*
|
|
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
|
|
* @return array
|
|
*/
|
|
private static function getDeps(DepartaimantCollectionType $depColtype): array
|
|
{
|
|
$tableColumnWith = self::getTableColumnWith();
|
|
$o = [];
|
|
$i = 0;
|
|
$depsKeys = self::getDepKeys($depColtype);
|
|
foreach ($depsKeys as $a => $value) {
|
|
// Appending '.' as a hack to prevent PHP from casting numeric indexes.
|
|
$o['.' . $depsKeys[$a]] = 1 << $i * self::$departementWidth;
|
|
$i = $i >= ($tableColumnWith - 1) ? 0 : $i + 1;
|
|
}
|
|
return $o;
|
|
}
|
|
|
|
/**
|
|
* Get the with in bit length for a column
|
|
* that can be used to store data.
|
|
*
|
|
* @return float
|
|
*/
|
|
private static function getTableColumnWith(): float
|
|
{
|
|
// * bigInt = 8 * 8 octets.
|
|
return floor(8 * 8 / self::$departementWidth) - self::$departementWidth;
|
|
}
|
|
|
|
/**
|
|
* Get departaimant enum values.
|
|
*
|
|
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
|
|
* @return array
|
|
*/
|
|
private static function getDepKeys(DepartaimantCollectionType $depColtype): array
|
|
{
|
|
$className = self::getDepTypeClass($depColtype);
|
|
return \forward_static_call([$className, 'toValues']);
|
|
}
|
|
|
|
/**
|
|
* Get a departaiment enum from a collection type.
|
|
*
|
|
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
|
|
* @return string
|
|
* @throws \ErrorException If the type is not handled.
|
|
*/
|
|
private static function getDepTypeClass(DepartaimantCollectionType $depColtype): string
|
|
{
|
|
return match ($depColtype) {
|
|
DepartaimantCollectionType::current => DepartaimantCurrent::class,
|
|
DepartaimantCollectionType::vintage => DepartaimantVintage::class,
|
|
// @phpstan-ignore-next-line
|
|
default => throw new \ErrorException(sprintf(
|
|
'DepartaimantCollection::type %s is not handled in DepartaimantCollection Model',
|
|
$depColtype
|
|
))
|
|
};
|
|
}
|
|
}
|