. */ namespace Common\Components; use Common\Document; use Common\RowReference; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use InvalidArgumentException; /** * Classe dedicata alla gestione delle informazioni di base dei componenti dei Documenti, e in particolare di: * - Collegamento con il Documento do origine * - Riferimento di origine * - Riferimenti informativi tra righe di altri documenti * - Importazione tra documenti distinti di un componente. * * @property string original_type * @property string original_id * * @template T * * @since 2.4.18 */ abstract class Component extends Model { /** * Componente di origine da cui il compontente corrente deriva. * * @var Component|null */ protected $original_model = null; protected $guarded = []; protected $appends = [ 'max_qta', ]; protected $hidden = [ 'document', ]; public function hasOriginalComponent() { return !empty($this->original_type) && !empty($this->getOriginalComponent()); } public function getOriginalComponent() { if (!isset($this->original_model) && !empty($this->original_type)) { $class = $this->original_type; $this->original_model = $class::find($this->original_id); } return $this->original_model; } public function referenceSources() { $class = get_class($this); return $this->hasMany(RowReference::class, 'target_id') ->where('target_type', $class); } public function referenceTargets() { $class = get_class($this); return $this->hasMany(RowReference::class, 'source_id') ->where('source_type', $class); } /** * Restituisce l'eventuale limite sulla quantità massima derivato dal componente di origine. * * @return mixed|null */ public function getMaxQtaAttribute() { if (!$this->hasOriginalComponent()) { return null; } $original = $this->getOriginalComponent(); return $original->qta_rimanente + $this->qta; } /** * Modifica la quantità del componente. * * @param float $value * * @return float */ public function setQtaAttribute($value) { $previous = $this->qta; $diff = $value - $previous; if ($this->hasOriginalComponent()) { $original = $this->getOriginalComponent(); if ($original->qta_rimanente < $diff) { $diff = $original->qta_rimanente; $value = $previous + $diff; } } $this->attributes['qta'] = $value; if ($this->hasOriginalComponent()) { $original = $this->getOriginalComponent(); $original->qta_evasa += $diff; $original->save(); } return $diff; } /** * Restituisce la quantità rimanente del componente. * * @return float */ public function getQtaRimanenteAttribute() { return $this->qta - $this->qta_evasa; } /** * Gestisce la possibilità di eliminare il componente. * * @return bool */ public function canDelete() { return true; } public function delete() { if (!$this->canDelete()) { throw new InvalidArgumentException(); } if ($this->hasOriginalComponent()) { $original = $this->getOriginalComponent(); } $this->qta = 0; $result = parent::delete(); // Trigger per la modifica delle righe $this->getDocument()->triggerComponent($this); // Trigger per l'evasione delle quantità if ($this->hasOriginalComponent()) { $original->getDocument()->triggerEvasione($this); } reorderRows($this->table, $this->getDocumentID(), $this->getDocument()['id']); return $result; } /** * Copia l'oggetto (articolo, riga, descrizione) nel corrispettivo per il documento indicato. * * @param Document $document Documento di destinazione * @param float|null $qta Quantità da riportare * * @return self */ public function copiaIn(Document $document, $qta = null) { // Individuazione classe di destinazione $class = get_class($document); $namespace = implode('\\', explode('\\', $class, -1)); $current = get_class($this); $pieces = explode('\\', $current); $type = end($pieces); $object = $namespace.'\\Components\\'.$type; // Attributi dell'oggetto da copiare $attributes = $this->getAttributes(); unset($attributes['id']); unset($attributes['order']); if ($qta !== null) { $attributes['qta'] = $qta; } $attributes['qta_evasa'] = 0; // Creazione del nuovo oggetto $model = new $object(); // Rimozione attributo in conflitto unset($attributes[$model->getDocumentID()]); // Riferimento di origine per l'evasione automatica della riga $is_evasione = true; if ($is_evasione) { // Mantenimento dell'origine della riga precedente $model->original_id = $attributes['original_id']; $model->original_type = $attributes['original_type']; // Aggiornamento dei riferimenti list($riferimento_precedente, $nuovo_riferimento) = $model->impostaOrigine($current, $this->id); // Correzione della descrizione $attributes['descrizione'] = str_replace($riferimento_precedente, '', $attributes['descrizione']); $attributes['descrizione'] .= $nuovo_riferimento; } unset($attributes['original_id']); unset($attributes['original_type']); // Impostazione del genitore $model->setDocument($document); // Azioni specifiche di inizializzazione $model->customInitCopiaIn($this); $model->save(); // Impostazione degli attributi $model = $object::find($model->id); $accepted = $model->getAttributes(); // Azioni specifiche precedenti $model->customBeforeDataCopiaIn($this); $attributes = array_intersect_key($attributes, $accepted); $model->fill($attributes); // Azioni specifiche successive $model->customAfterDataCopiaIn($this); $model->save(); return $model; } /** * Imposta l'origine del componente, restituendo un array contenente i replace da effettuare per modificare la descrizione in modo coerente. * * @param string $type * @param string $id * * @return array */ public function impostaOrigine($type, $id) { $riferimento_precedente = null; $nuovo_riferimento = null; // Rimozione del riferimento precedente dalla descrizione if ($this->hasOriginalComponent()) { $riferimento = $this->getOriginalComponent()->getDocument()->getReference(); $riferimento_precedente = "\nRif. ".strtolower($riferimento); } $this->original_id = $id; $this->original_type = $type; // Aggiunta del riferimento nella descrizione $origine = $type::find($id); if (!empty($origine)) { $riferimento = $origine->getDocument()->getReference(); $nuovo_riferimento = "\nRif. ".strtolower($riferimento); } return [$riferimento_precedente, $nuovo_riferimento]; } /** * Imposta il proprietario dell'oggetto e l'ordine relativo all'interno delle righe. * * @param Document $document Documento di riferimento * @psalm-param T $document */ public function setDocument(Document $document) { $this->document()->associate($document); // Ordine delle righe if (empty($this->disableOrder)) { $this->order = orderValue($this->table, $this->getDocumentID(), $document->id); } } /** * @return Document * @psalm-return T */ public function getDocument() { return $this->document; } abstract public function document(); /** * @return string */ abstract public function getDocumentID(); public function save(array $options = []) { $result = parent::save($options); // Trigger per la modifica delle righe $this->getDocument()->triggerComponent($this); // Trigger per l'evasione delle quantità if ($this->hasOriginalComponent()) { $original = $this->getOriginalComponent(); $original->getDocument()->triggerEvasione($this); } return $result; } /** * Azione personalizzata per la copia dell'oggetto (inizializzazione della copia). * * @param $original */ protected function customInitCopiaIn($original) { } /** * Azione personalizzata per la copia dell'oggetto (dopo la copia). * * @param $original */ protected function customBeforeDataCopiaIn($original) { } /** * Azione personalizzata per la copia dell'oggetto (dopo la copia). * * @param $original */ protected function customAfterDataCopiaIn($original) { } protected static function boot() { // Pre-caricamento Documento static::addGlobalScope('document', function (Builder $builder) { $builder->with('document'); }); parent::boot(); } }