code/app/Models/Concerns/ReducibleTrait.php
<?php
/*
Questa è la classe essenziale che permette la "riduzione" delle informazioni
all'interno di un ordine e delle sue prenotazioni.
Aggregati, ordini, prenotazioni, prodotti prenotati e loro varianti fanno
tutti capo a questa struttura dati, che riassume in modo omogeneo quantità e
prezzi. Più precisamente: un aggregato è la somma delle riduzioni dei suoi
ordini, i quali sono la somma delle riduzioni delle sue prenotazioni, le
quali sono la somma delle riduzioni dei loro prodotti, i quali possono
eventualmente essere la somma delle riduzione delle loro varianti.
Questo per semplificare il calcolo dei valori complessivi, e dunque
l'applicazione dei modificatori o la generazione delle esportazioni.
Le classi la cui riduzione non dipende da altri elementi (tipicamente: le
foglie dell'albero di riduzione, ovvero i prodotti senza varianti o le
varianti) devono sovrascrivere la funzione reduxData() per restituire
direttamente la loro propria rappresentazione, che andrà a essere sommata a
tutte le altre.
*/
namespace App\Models\Concerns;
use Log;
use App\Aggregate;
use App\Order;
use App\Booking;
use App\BookedProduct;
trait ReducibleTrait
{
/*
Questi sono gli attributi essenziali che ci si aspetta di trovare nella
riduzione di un oggetto, e che vengono tra loro sommati per ottenere i
valori totali per l'oggetto padre
*/
protected function describingAttributes()
{
return [
/*
Dati relativi al prenotato
*/
'price',
'weight',
'quantity',
'quantity_pieces',
/*
Dati relativi al consegnato
*/
'price_delivered',
'weight_delivered',
'delivered',
'delivered_pieces',
/*
Se la prenotazione/prodotto è consegnata qui vengono accumulati
i valori del consegnato, altrimenti quelli del prenotato.
Queste informazioni relative, che variano durante la fase di
consegna dell'ordine, servono a calcolare nel modo più accurato
possibile il valore dinamico dei modificatori trasversali
*/
'relative_price',
'relative_weight',
'relative_quantity',
'relative_pieces',
];
}
/*
In fase di popolamento dell'array "merged" della riduzione, questi sono
i sotto-array che devono (se presenti) essere a loro volta essere
mergiati
*/
protected function subArrayMerge()
{
return [
'variants',
];
}
/*
Date due riduzioni (ad esempio, di due prenotazioni) questa funzione
provvede a sommare tra di loro i valori enumerati in
describingAttributes() per ottenere la riduzione complessiva
*/
protected function describingAttributesMerge($first, $second)
{
if (is_null($first)) {
return clone $second;
}
if (is_null($second)) {
return $first;
}
foreach ($this->describingAttributes() as $attr) {
if (!isset($first->$attr)) {
$first->$attr = 0;
}
if (!isset($second->$attr)) {
continue;
}
$first->$attr += $second->$attr;
}
return $first;
}
/*
Come describingAttributesMerge(), ma in più condensa anche i sotto-array
enumerati in subArrayMerge() delle due riduzioni.
Funzione introdotta per condensare le varianti dei prodotti presenti in
diverse prenotazioni (che vengono ridotte indipendentemente tra loro)
*/
protected function deepMergingAttributes($child, $first, $second)
{
$ret = $this->describingAttributesMerge($first, $second);
foreach ($this->subArrayMerge() as $subarray) {
if (!isset($first->$subarray) && !isset($second->$subarray)) {
continue;
}
$first_subarray = $first->$subarray ?? [];
$second_subarray = $second->$subarray ?? [];
$final = [];
$ids = array_unique(array_merge(array_keys($first_subarray), array_keys($second_subarray)));
foreach($ids as $id) {
$final[$id] = $this->describingAttributesMerge($first_subarray[$id] ?? null, $second_subarray[$id] ?? null);
}
$ret->$subarray = $final;
}
return $ret;
}
protected function descendReduction($ret, $filters)
{
foreach ($this->describingAttributes() as $attr) {
if (!isset($ret->$attr)) {
$ret->$attr = 0;
}
}
$behaviours = $this->reduxBehaviour();
$collected = $behaviours->collected;
$children = ($behaviours->children)($this, $filters);
foreach($children as $child) {
$child = ($behaviours->optimize)($this, $child);
$reduxed_child = $child->reduxData($filters);
$ret->$collected[$reduxed_child->id] = $this->describingAttributesMerge($ret->$collected[$reduxed_child->id] ?? null, $reduxed_child);
$ret = $this->describingAttributesMerge($ret, $reduxed_child);
$merged = $behaviours->merged ?? '';
if (!empty($merged)) {
foreach($reduxed_child->$merged as $to_merge) {
$ret->$merged[$to_merge->id] = $this->deepMergingAttributes($child, $ret->$merged[$to_merge->id] ?? null, $to_merge);
}
}
}
return $ret;
}
protected function emptyReduxBehaviour()
{
return (object) [
'master_key' => 'id',
'merged' => '',
'optimize' => function($master, $child) {
return $child;
},
];
}
/*
Questa funzione permette di ricostruire la riduzione in funzione di una
collezione di modificatori, che abbisognano solo di determinati dati (e
non dell'intero albero dell'aggregato)
*/
public function minimumRedux($modifiers)
{
if ($modifiers->isEmpty()) {
return [];
}
$aggregate = null;
$order = null;
$booking = null;
switch(get_class($this)) {
case Aggregate::class:
$aggregate = $this;
$order = null;
$booking = null;
break;
case Order::class:
$aggregate = $this->aggregate;
$order = $this;
$booking = null;
break;
case Booking::class:
$aggregate = $this->order->aggregate;
$order = $this->order;
$booking = $this;
break;
case BookedProduct::class:
$aggregate = $this->booking->order->aggregate;
$order = $this->booking->order;
$booking = $this->booking;
break;
default:
\Log::error('Unrecognized class calling minimum reduction: ' . get_class($this));
break;
}
$priority = ['product', 'booking', 'order', 'aggregate'];
$target_priority = -1;
$aggregate_data = null;
$faster = true;
foreach($modifiers as $mod) {
$p = array_search($mod->applies_target, $priority);
if ($p > $target_priority) {
$target_priority = $p;
}
if ($mod->value != 'percentage' || $mod->distribution_type != 'price') {
$faster = false;
}
}
if (($faster && $target_priority <= 1) && ($booking && $order)) {
$aggregate_data = $aggregate->reduxData([
'orders' => [$order],
'bookings' => [$booking]
]);
}
else {
$target_priority = 2;
}
if (is_null($aggregate_data)) {
if ($target_priority == 2 && $order) {
$aggregate_data = $aggregate->reduxData([
'orders' => [$order]
]);
}
else {
$target_priority = 3;
}
if ($target_priority == 3) {
$aggregate_data = $aggregate->reduxData();
}
}
return $aggregate_data;
}
/*
Reminder: è sconsigliato cachare i risultati dell'operazione di
riduzione, esistendo diverse variabili (incluso il GAS attualmente
attivo nel GlobalScopeHub)
*/
public function reduxData($filters = null)
{
$behaviours = $this->reduxBehaviour();
$master_key = $behaviours->master_key;
$ret = (object) [
'id' => $this->$master_key,
];
if (isset($behaviours->collected)) {
$collected = $behaviours->collected;
$ret->$collected = [];
}
$merged = $behaviours->merged ?? '';
if (!empty($merged)) {
$ret->$merged = [];
}
return $this->descendReduction($ret, $filters);
}
abstract protected function reduxBehaviour();
}