Aggiunta sistema di gestione Combinazioni Articoli

Introduzione moduli Combinazioni e Attributi Combinazioni, al fine di gestire le combinazioni di articoli (varianti) sulla base di Attributi liberamente impostabili.
Introduzione plugin dedicato alla visualizzazione delle Varianti Articolo.
Modifica del sistema di modifica degli Articoli per sincronizzare in automatico alcuni campi condivisi tra le varianti.
This commit is contained in:
Dasc3er 2021-08-31 15:46:14 +02:00
parent d2d1de95d4
commit 667b343f62
22 changed files with 1032 additions and 51 deletions

View File

@ -70,7 +70,9 @@
"Update\\": "update/",
"Modules\\Aggiornamenti\\": ["modules/aggiornamenti/custom/src/", "modules/aggiornamenti/src/"],
"Modules\\Anagrafiche\\": ["modules/anagrafiche/custom/src/", "modules/anagrafiche/src/"],
"Modules\\AttributiCombinazioni\\": ["modules/attributi_combinazioni/custom/src/", "modules/attributi_combinazioni/src/"],
"Modules\\Backups\\": ["modules/backups/custom/src/", "modules/backups/src/"],
"Modules\\CombinazioniArticoli\\": ["modules/combinazioni_articoli/custom/src/", "modules/combinazioni_articoli/src/"],
"Modules\\Emails\\": ["modules/emails/custom/src/", "modules/emails/src/"],
"Modules\\Articoli\\": ["modules/articoli/custom/src/", "modules/articoli/src/"],
"Modules\\Checklists\\": ["modules/checklists/custom/src/", "modules/checklists/src/"],

View File

@ -20,6 +20,7 @@
use Carbon\Carbon;
use Modules\Articoli\Articolo;
use Modules\Articoli\Categoria;
use Modules\CombinazioniArticoli\Combinazione;
use Util\Ini;
include_once __DIR__.'/../../core.php';
@ -61,6 +62,9 @@ switch (post('op')) {
$articolo->setPrezzoVendita(post('prezzo_vendita'), post('idiva_vendita'));
$articolo->save();
// Aggiornamento delle varianti per i campi comuni
Combinazione::sincronizzaVarianti($articolo);
if (!empty(post('qta'))) {
$data_movimento = new Carbon();
$articolo->movimenta(post('qta'), tr('Carico manuale'), $data_movimento->format('Y-m-d'), true);
@ -132,6 +136,9 @@ switch (post('op')) {
$articolo->save();
// Aggiornamento delle varianti per i campi comuni
Combinazione::sincronizzaVarianti($articolo);
// Leggo la quantità attuale per capire se l'ho modificata
$old_qta = $record['qta'];
$movimento = $qta - $old_qta;

View File

@ -19,6 +19,14 @@
include_once __DIR__.'/../../core.php';
// Messaggio informativo per variante
if ($articolo->isVariante()) {
echo '
<div class="badge badge-info">'.tr('Variante: _NAME_', [
'_NAME_' => $articolo->nome_variante,
]).'</div>';
}
echo '
<button type="button" class="btn btn-primary" onclick="duplicaArticolo()">
<i class="fa fa-copy"></i> '.tr('Duplica articolo').'

View File

@ -17,8 +17,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Util\Ini;
include_once __DIR__.'/../../core.php';
?><form action="" method="post" id="edit-form" enctype="multipart/form-data">
@ -265,55 +263,7 @@ echo '
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"><?php echo tr('Aggiungi informazioni componente personalizzato'); ?></h3>
</div>
<div class="panel-body">
<?php
echo '
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label for="componente_filename">'.tr('Seleziona un componente').':</label>';
echo "
<select class=\"form-control superselect\" id=\"componente_filename\" name=\"componente_filename\" onchange=\"$.post('".base_path()."/modules/impianti/actions.php', {op: 'load_componente', idarticolo: '".$id_record."', filename: $(this).find('option:selected').val() }, function(response){ $('#info_componente').html( response ); start_superselect(); $('.datepicker').datetimepicker({ locale: globals.locale, format: 'L' } ); } );\">\n";
echo '
<option value="0">'.tr('Nessuno').'</option>';
$cmp = Ini::getList(base_dir().'/files/impianti/');
if (count($cmp) > 0) {
for ($c = 0; $c < count($cmp); ++$c) {
($record['componente_filename'] == $cmp[$c][0]) ? $attr = 'selected="selected"' : $attr = '';
echo '
<option value="'.$cmp[$c][0]."\" $attr>".$cmp[$c][1]."</option>\n";
}
}
echo '
</select>
</div>
</div>
</div>';
echo '
<div id="info_componente">';
crea_form_componente($record['contenuto']);
echo '
</div>';
echo '
</div>
</div>
</form>';
?>
</form>
{( "name": "filelist_and_upload", "id_module": "$id_module$", "id_record": "$id_record$" )}

View File

@ -23,6 +23,7 @@ use Modules\Articoli\Articolo;
if (isset($id_record)) {
$articolo = Articolo::withTrashed()->find($id_record);
$articolo->nome_variante;
$record = $dbo->fetchOne('SELECT *, (SELECT COUNT(id) FROM mg_prodotti WHERE id_articolo = mg_articoli.id) AS serial FROM mg_articoli WHERE id='.prepare($id_record));
}

View File

@ -23,6 +23,8 @@ use Common\SimpleModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Modules;
use Modules\AttributiCombinazioni\ValoreAttributo;
use Modules\CombinazioniArticoli\Combinazione;
use Modules\Interventi\Components\Articolo as ArticoloIntervento;
use Modules\Iva\Aliquota;
use Plugins\ListinoFornitori\DettaglioFornitore;
@ -139,6 +141,16 @@ class Articolo extends Model
}
}
/**
* Verifica se l'articolo corrente è una variante per una Combinazione.
*
* @return bool
*/
public function isVariante()
{
return !empty($this->id_combinazione);
}
// Attributi Eloquent
public function getImmagineUploadAttribute()
@ -178,6 +190,22 @@ class Articolo extends Model
return 'Articoli';
}
public function getNomeVarianteAttribute()
{
$valori = database()->fetchArray("SELECT CONCAT(`mg_attributi`.`titolo`, ': ', `mg_valori_attributi`.`nome`) AS nome
FROM `mg_articolo_attributo`
INNER JOIN `mg_valori_attributi` ON `mg_valori_attributi`.`id` = `mg_articolo_attributo`.`id_valore`
INNER JOIN `mg_attributi` ON `mg_attributi`.`id` = `mg_valori_attributi`.`id_attributo`
INNER JOIN `mg_articoli` ON `mg_articoli`.`id` = `mg_articolo_attributo`.`id_articolo`
INNER JOIN `mg_combinazioni` ON `mg_combinazioni`.`id` = `mg_articoli`.`id_combinazione`
INNER JOIN `mg_attributo_combinazione` ON `mg_attributo_combinazione`.`id_combinazione` = `mg_combinazioni`.`id` AND `mg_attributo_combinazione`.`id_attributo` = `mg_attributi`.`id`
WHERE `mg_articoli`.`id` = ".prepare($this->id).'
ORDER BY `mg_attributo_combinazione`.`order`');
return implode(', ', array_column($valori, 'nome'));
}
// Relazioni Eloquent
public function articoli()
@ -185,6 +213,16 @@ class Articolo extends Model
return $this->hasMany(ArticoloIntervento::class, 'idarticolo');
}
public function combinazione()
{
return $this->belongsTo(Combinazione::class, 'id_combinazione');
}
public function attributi()
{
return $this->belongsToMany(ValoreAttributo::class, 'mg_articolo_attributo', 'id_articolo', 'id_valore');
}
/**
* Restituisce i movimenti di magazzino dell'articolo.
*

View File

@ -0,0 +1,70 @@
<?php
use Modules\AttributiCombinazioni\Attributo;
use Modules\AttributiCombinazioni\ValoreAttributo;
include_once __DIR__.'/../../core.php';
switch (filter('op')) {
case 'add':
$nome = post('nome');
$esistente = Attributo::where('nome', '=', $nome)->count() !== 0;
if (!$esistente) {
$attributo = Attributo::build();
$attributo->nome = $nome;
$attributo->titolo = post('titolo');
$attributo->save();
$id_record = $attributo->id;
flash()->info(tr('Nuovo attributo creato correttamente!'));
} else {
flash()->error(tr('Attributo esistente con lo stesso nome!'));
}
break;
case 'update':
$attributo->titolo = post('titolo');
$attributo->save();
flash()->info(tr('Attributo aggiornato correttamente!'));
break;
case 'delete':
$attributo->delete();
flash()->info(tr('Attributo rimosso correttamente!'));
break;
case 'gestione-valore':
$id_valore = filter('id_valore');
$nome = post('nome');
if (!empty($id_valore)) {
$valore = ValoreAttributo::find($id_valore);
$valore->nome = $nome;
$valore->save();
} else {
$valore = ValoreAttributo::build($attributo, $nome);
}
flash()->info(tr('Valore aggiornato correttamente!'));
break;
case 'rimuovi-valore':
$id_valore = filter('id_valore');
if (!empty($id_valore)) {
$valore = ValoreAttributo::find($id_valore);
$valore->delete();
}
flash()->info(tr('Valore rimosso correttamente!'));
break;
}

View File

@ -0,0 +1,28 @@
<?php
include_once __DIR__.'/init.php';
echo '
<form action="" method="post" id="add-form">
<input type="hidden" name="op" value="add">
<input type="hidden" name="backto" value="record-edit">
<div class="row">
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Nome').'", "name": "nome", "required": 1, "help": "'.tr("Nome univoco dell'attributo").'" ]}
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Titolo').'", "name": "titolo", "required": 1, "help": "'.tr("Nome visibile dell'attributo").'" ]}
</div>
</div>
<!-- PULSANTI -->
<div class="row">
<div class="col-md-12 text-right">
<button type="submit" class="btn btn-primary">
<i class="fa fa-plus"></i> '.tr('Aggiungi').'
</button>
</div>
</div>
</form>';

View File

@ -0,0 +1,105 @@
<?php
include_once __DIR__.'/../../core.php';
echo '
<form action="" method="post" id="edit-form" enctype="multipart/form-data">
<input type="hidden" name="backto" value="record-edit">
<input type="hidden" name="op" value="update">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">'.tr('Dati').'</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
{[ "type": "span", "label": "'.tr('Nome').'", "name": "nome", "value": "'.$attributo->nome.'", "help": "'.tr("Nome univoco dell'attributo").'" ]}
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Titolo').'", "name": "titolo", "value": "'.$attributo->titolo.'", "required": 1, "help": "'.tr("Nome visibile dell'attributo").'" ]}
</div>
</div>
</div>
</div>
</form>
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">'.tr('Valori attributo').'</h3>
</div>
<div class="box-body">
<button type="button" class="btn btn-primary pull-right" onclick="aggiungiValore(this)">
<i class="fa fa-plus"></i> '.tr('Aggiungi valore').'
</button>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>'.tr('Valore').'</th>
<th width="10%">#</th>
</tr>
</thead>
<tbody>';
$valori = $attributo->valori;
foreach ($valori as $valore) {
echo '
<tr data-id="'.$valore->id.'">
<td>'.$valore->nome.'</td>
<td class="text-center">
<button type="button" class="btn btn-warning btn-xs" onclick="modificaValore(this)">
<i class="fa fa-edit"></i>
</button>
<button type="button" class="btn btn-danger btn-xs" onclick="rimuoviValore(this)">
<i class="fa fa-trash-o"></i>
</button>
</td>
</tr>';
}
echo '
</tbody>
</table>
</div>
</div>
<a class="btn btn-danger ask" data-backto="record-list">
<i class="fa fa-trash"></i> '.tr('Elimina').'
</a>
<script>
function aggiungiValore(button) {
let riga = $(button).closest("tr");
// Apertura modal
openModal("'.tr('Aggiungi valore').'", "'.$module->fileurl('gestione-valore.php').'?id_module=" + globals.id_module + "&id_record=" + globals.id_record);
}
function modificaValore(button) {
let riga = $(button).closest("tr");
let id = riga.data("id");
// Apertura modal
openModal("'.tr('Modifica valore').'", "'.$module->fileurl('gestione-valore.php').'?id_module=" + globals.id_module + "&id_record=" + globals.id_record + "&id_valore=" + id);
}
function rimuoviValore(button) {
let riga = $(button).closest("tr");
let id = riga.data("id");
// Redirect
redirect(globals.rootdir + "/editor.php", {
id_module: globals.id_module,
id_record: globals.id_record,
id_valore: id,
op: "rimuovi-valore",
backto: "record-edit",
});
}
</script>';

View File

@ -0,0 +1,34 @@
<?php
use Modules\AttributiCombinazioni\ValoreAttributo;
include_once __DIR__.'/../../core.php';
$id_valore = filter('id_valore');
$testo_valore = '';
if (!empty($id_valore)) {
$valore = ValoreAttributo::find($id_valore);
$testo_valore = $valore->nome;
}
echo '
<form action="" method="post" id="form-valore">
<input type="hidden" name="op" value="gestione-valore">
<input type="hidden" name="backto" value="record-edit">
<input type="hidden" name="id_valore" value="'.$id_valore.'">
<div class="row">
<div class="col-md-12">
{[ "type": "text", "label": "'.tr('Valore').'", "name": "nome", "value": "'.$testo_valore.'", "required": 1 ]}
</div>
</div>
<!-- PULSANTI -->
<div class="row">
<div class="col-md-12 text-right">
<button type="submit" class="btn btn-primary">
<i class="fa fa-save"></i> '.tr('Salva').'
</button>
</div>
</div>
</form>';

View File

@ -0,0 +1,11 @@
<?php
use Modules\AttributiCombinazioni\Attributo;
include_once __DIR__.'/../../core.php';
if (!empty($id_record)) {
$attributo = Attributo::find($id_record);
$record = $attributo->toArray();
}

View File

@ -0,0 +1,21 @@
<?php
namespace Modules\AttributiCombinazioni;
use Common\SimpleModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Attributo extends Model
{
use SimpleModelTrait;
use SoftDeletes;
protected $table = 'mg_attributi';
/* Relazioni Eloquent */
public function valori()
{
return $this->hasMany(ValoreAttributo::class, 'id_attributo');
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Modules\AttributiCombinazioni;
use Common\SimpleModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class ValoreAttributo extends Model
{
use SimpleModelTrait;
use SoftDeletes;
protected $table = 'mg_valori_attributi';
public static function build(Attributo $attributo, $valore)
{
$model = new self();
$model->attributo()->associate($attributo);
$model->nome = $valore;
$model->save();
return $model;
}
/* Relazioni Eloquent */
public function attributo()
{
return $this->belongsTo(Attributo::class, 'id_attributo');
}
}

View File

@ -0,0 +1,61 @@
<?php
use Modules\CombinazioniArticoli\Combinazione;
include_once __DIR__.'/../../core.php';
switch (filter('op')) {
case 'add':
case 'update':
$nome = post('nome');
// Ricerca combinazione con nome indicato
$esistente = Combinazione::where('nome', '=', $nome);
if (isset($combinazione)) {
$esistente = $esistente->where('id', '!=', $combinazione->id);
}
$esistente = $esistente->count() !== 0;
if (!$esistente) {
$combinazione = $combinazione ?: Combinazione::build();
$combinazione->nome = $nome;
$combinazione->codice = post('codice');
$combinazione->id_categoria = post('id_categoria');
$combinazione->id_sottocategoria = post('id_sottocategoria');
$combinazione->save();
$id_record = $combinazione->id;
// Selezione attributi per la combinazione
if ($combinazione->articoli()->count() == 0) {
$combinazione->attributi()->sync((array) post('attributi'));
}
flash()->info(tr('Combinazione aggiornata correttamente!'));
} else {
flash()->error(tr('Combinazione esistente con lo stesso nome!'));
}
break;
case 'delete':
$combinazione->delete();
flash()->info(tr('Combinazione rimossa correttamente!'));
break;
case 'gestione-variante':
$combinazione->generaVariante((array) filter('attributo'));
flash()->info(tr('Variante aggiunta correttamente!'));
break;
case 'genera-varianti':
$combinazione->generaTutto();
flash()->info(tr('Varianti generate correttamente!'));
break;
}

View File

@ -0,0 +1,45 @@
<?php
include_once __DIR__.'/../../core.php';
echo '
<form action="" method="post" id="add-form">
<input type="hidden" name="op" value="add">
<input type="hidden" name="backto" value="record-edit">
<div class="row">
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Codice').'", "name": "codice", "required": 1, "help": "'.tr('Codice di base per la combinazione: alla generazione variante vengono aggiunti i valore degli Attributi relativi').'" ]}
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Nome').'", "name": "nome", "required": 1, "help": "'.tr('Nome univoco della combinazione').'" ]}
</div>
</div>
<div class="row">
<div class="col-md-6">
'.Modules::link('Categorie articoli', $record['id_categoria'], null, null, 'class="pull-right"').'
{[ "type": "select", "label": "'.tr('Categoria').'", "name": "id_categoria", "required": 0, "value": "$id_categoria$", "ajax-source": "categorie", "icon-after": "add|'.Modules::get('Categorie articoli')['id'].'" ]}
</div>
<div class="col-md-6">
{[ "type": "select", "label": "'.tr('Sottocategoria').'", "name": "id_sottocategoria", "value": "$id_sottocategoria$", "ajax-source": "sottocategorie", "select-options": '.json_encode(['id_categoria' => $record['id_categoria']]).' ]}
</div>
</div>
<div class="row">
<div class="col-md-12">
{[ "type": "select", "label": "'.tr('Attributi').'", "name": "attributi[]", "values": "query=SELECT id, nome AS descrizione FROM mg_attributi WHERE deleted_at IS NULL", "required": 1, "multiple": 1, "help": "'.tr('Attributi abilitati per la combinazione corrente').'" ]}
</div>
</div>
<!-- PULSANTI -->
<div class="row">
<div class="col-md-12 text-right">
<button type="submit" class="btn btn-primary">
<i class="fa fa-plus"></i> '.tr('Aggiungi').'
</button>
</div>
</div>
</form>';

View File

@ -0,0 +1,110 @@
<?php
include_once __DIR__.'/../../core.php';
$numero_varianti = $combinazione->articoli()->count();
echo '
<form action="" method="post" id="edit-form" enctype="multipart/form-data">
<input type="hidden" name="backto" value="record-edit">
<input type="hidden" name="op" value="update">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">'.tr('Dati').'</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Codice').'", "name": "codice", "value": "'.$combinazione->codice.'", "required": 1, "help": "'.tr('Codice di base per la combinazione: alla generazione variante vengono aggiunti i valore degli Attributi relativi').'" ]}
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Nome').'", "name": "nome", "value": "'.$combinazione->nome.'", "required": 1, "help": "'.tr('Nome univoco della combinazione').'" ]}
</div>
</div>
<div class="row">
<div class="col-md-6">
'.Modules::link('Categorie articoli', $record['id_categoria'], null, null, 'class="pull-right"').'
{[ "type": "select", "label": "'.tr('Categoria').'", "name": "id_categoria", "required": 0, "value": "$id_categoria$", "ajax-source": "categorie", "icon-after": "add|'.Modules::get('Categorie articoli')['id'].'" ]}
</div>
<div class="col-md-6">
{[ "type": "select", "label": "'.tr('Sottocategoria').'", "name": "id_sottocategoria", "value": "$id_sottocategoria$", "ajax-source": "sottocategorie", "select-options": '.json_encode(['id_categoria' => $record['id_categoria']]).' ]}
</div>
</div>
<div class="row">
<div class="col-md-12">
{[ "type": "select", "label": "'.tr('Attributi').'", "name": "attributi[]", "value": "'.implode(',', $combinazione->attributi->pluck('id')->all()).'", "values": "query=SELECT id, nome AS descrizione FROM mg_attributi WHERE deleted_at IS NULL", "required": 1, "multiple": 1, "help": "'.tr('Attributi abilitati per la combinazione corrente').'", "disabled": "'.($numero_varianti !== 0).'" ]}
</div>
</div>
</div>
</div>
</form>
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">'.tr('Varianti disponibili (Articoli)').'</h3>
</div>
<div class="box-body">
<button type="button" class="btn btn-primary" onclick="aggiungiVariante(this)">
<i class="fa fa-plus"></i> '.tr('Aggiungi variante').'
</button>
<div class="tip pull-right " data-toggle="tooltip" title="'.tr('Genera tutte le varianti sulla base degli Attributi associati alla Combinazione corrente').'. '.tr('Disponibile solo se non sono giò presenti delle varianti').'.">
<button type="button" class="btn btn-warning '.($numero_varianti === 0 ? '' : 'disabled').'" onclick="generaVarianti(this)">
<i class="fa fa-refresh"></i> '.tr('Genera tutte le varianti').'
</button>
</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th width="10%">'.tr('Foto').'</th>
<th>'.tr('Variante').'</th>
<th>'.tr('Articolo').'</th>
</tr>
</thead>
<tbody>';
$articoli = $combinazione->articoli;
foreach ($articoli as $articolo) {
echo '
<tr data-id="'.$articolo->id.'">
<td><img class="img-thumbnail img-responsive" src="'.$articolo->image.'"></td>
<td>'.$articolo->nome_variante.'</td>
<td>'.Modules::link('Articoli', $articolo->id, $articolo->codice.' - '.$articolo->descrizione).'</td>
</tr>';
}
echo '
</tbody>
</table>
</div>
</div>
<a class="btn btn-danger ask" data-backto="record-list">
<i class="fa fa-trash"></i> '.tr('Elimina').'
</a>
<script>
function aggiungiVariante(button) {
// Apertura modal
openModal("'.tr('Aggiungi variante').'", "'.$module->fileurl('gestione-variante.php').'?id_module=" + globals.id_module + "&id_record=" + globals.id_record);
}
function generaVarianti(button) {
// Redirect
redirect(globals.rootdir + "/editor.php", {
id_module: globals.id_module,
id_record: globals.id_record,
op: "genera-varianti",
backto: "record-edit",
});
}
</script>';

View File

@ -0,0 +1,93 @@
<?php
include_once __DIR__.'/init.php';
$varianti_esistenti = $database->fetchArray('SELECT GROUP_CONCAT(`variazioni`.`id_valore`) AS variante
FROM (SELECT `mg_articolo_attributo`.`id_valore`, `mg_articolo_attributo`.`id_articolo`
FROM `mg_articolo_attributo`
INNER JOIN `mg_valori_attributi` ON `mg_valori_attributi`.`id` = `mg_articolo_attributo`.`id_valore`
INNER JOIN `mg_attributi` ON `mg_attributi`.`id` = `mg_valori_attributi`.`id_attributo`
INNER JOIN `mg_articoli` ON `mg_articoli`.`id` = `mg_articolo_attributo`.`id_articolo`
INNER JOIN `mg_combinazioni` ON `mg_combinazioni`.`id` = `mg_articoli`.`id_combinazione`
INNER JOIN `mg_attributo_combinazione` ON `mg_attributo_combinazione`.`id_combinazione` = `mg_combinazioni`.`id` AND `mg_attributo_combinazione`.`id_attributo` = `mg_attributi`.`id`
WHERE `mg_articoli`.`deleted_at` IS NULL AND `mg_articoli`.`id_combinazione` = '.prepare($combinazione->id).'
ORDER BY `mg_attributo_combinazione`.`order`
) AS variazioni
GROUP BY `variazioni`.`id_articolo`');
$varianti_esistenti = array_column($varianti_esistenti, 'variante');
echo '
<form action="" method="post" id="form-variante">
<input type="hidden" name="op" value="gestione-variante">
<input type="hidden" name="backto" value="record-edit">
<p>'.tr('Completa le informazioni dei diversi Attributi per generare una variante della Combinazione').'.</p>
<div class="row">';
$attributi = $combinazione->attributi;
foreach ($attributi as $key => $attributo) {
echo '
<div class="col-md-4">
{[ "type": "select", "label": "'.$attributo->nome.'", "name": "attributo['.$key.']", "values": "query=SELECT id, nome AS descrizione FROM mg_valori_attributi WHERE id_attributo = '.prepare($attributo->id).' AND deleted_at IS NULL", "required": 1 ]}
</div>';
}
echo '
</div>
<div class="alert alert-info hidden" id="variante-esistente">
<i class="fa fa-info-circle"></i> '.tr('La variante indicata è già presente per la combinazione corrente').'.
</div>
<!-- PULSANTI -->
<div class="row">
<div class="col-md-12 text-right">
<button type="submit" class="btn btn-primary">
<i class="fa fa-plus"></i> '.tr('Aggiungi').'
</button>
</div>
</div>
</form>
<script>
var varianti_esistenti = '.json_encode($varianti_esistenti).';
var form = $("#form-variante");
form.find("select").on("change", function () {
const inputs = form.serializeArray();
// Individuazione variante e completezza
let incompleto = false;
let valori = [];
for (const {name, value} of inputs) {
if (name.startsWith("attributo")){
valori.push(value);
incompleto |= !value;
}
}
// Completamento informazioni
const variante = valori.join(",");
const variante_esistente = varianti_esistenti.includes(variante);
// Disabilitazione/abilitazione pulsante di aggiunta
const button = form.find("button");
if (variante_esistente || incompleto){
button.addClass("disabled").attr("disabled", true);
} else {
button.removeClass("disabled").attr("disabled", false);
}
// Messaggio informativo
const info = form.find("#variante-esistente");
if (variante_esistente) {
info.removeClass("hidden");
} else {
info.addClass("hidden");
}
});
$(document).ready(init);
</script>';

View File

@ -0,0 +1,11 @@
<?php
use Modules\CombinazioniArticoli\Combinazione;
include_once __DIR__.'/../../core.php';
if (!empty($id_record)) {
$combinazione = Combinazione::withTrashed()->find($id_record);
$record = $combinazione->toArray();
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Genera il prodotto cartesiano tra gli elementi dell'array di input.
*
* @param $input
*
* @return array|array[]
*/
function cartesian($input)
{
$result = [[]];
foreach ($input as $key => $values) {
$append = [];
foreach ($result as $product) {
foreach ($values as $item) {
$product[$key] = $item;
$append[] = $product;
}
}
$result = $append;
}
return $result;
}

View File

@ -0,0 +1,179 @@
<?php
namespace Modules\CombinazioniArticoli;
use Common\SimpleModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Modules\Articoli\Articolo;
use Modules\AttributiCombinazioni\Attributo;
use Modules\AttributiCombinazioni\ValoreAttributo;
use Traits\RecordTrait;
class Combinazione extends Model
{
use SimpleModelTrait;
use SoftDeletes;
use RecordTrait;
protected $table = 'mg_combinazioni';
/**
* Elenco dei campi della combinazione da sincronizzare da e verso gli Articoli della combinazione.
*
* @var string[]
*/
protected static $campi_combinazione = [
'id_categoria',
'id_sottocategoria',
];
/**
* Elenco dei campi degli Articoli da sincronizzare da e verso gli Articoli della combinazione.
*
* @var string[]
*/
protected static $campi_varianti = [
'id_categoria',
'id_sottocategoria',
//'descrizione',
'um',
'gg_garanzia',
'servizio',
];
public function delete()
{
// Rimozione articoli collegati
$articoli = $this->articoli;
foreach ($articoli as $articolo) {
$articolo->delete();
}
return parent::delete();
}
public function save(array $options = [])
{
$result = parent::save($options);
// Sincronizzazione dei campi condivisi con la Combinazione
$sincro = collect($this->toArray())->filter(function ($value, $key) {
return in_array($key, self::$campi_combinazione);
});
$this->sincronizzaCampi($sincro->toArray());
return $result;
}
public function getModuleAttribute()
{
return 'Combinazioni';
}
/**
* Metodo per generare dinamicamente una variante dell'articolo per la combinazione corrente.
*
* @param $valori_attributi
*/
public function generaVariante($valori_attributi)
{
$database = database();
// Generazione nome variante
$variante = ValoreAttributo::findMany($valori_attributi)->pluck('nome')->all();
// Generazione Articolo di base
$articoli = $this->articoli;
if ($articoli->isEmpty()) {
$articolo = Articolo::build($this->nome, $this->nome);
$articolo->id_combinazione = $this->id;
} else {
$articolo = $articoli->first()->replicate();
}
$articolo->descrizione = $this->nome.' ['.implode(', ', $variante).']';
$articolo->codice = $this->codice.'-'.implode('|', $variante);
$articolo->save();
// Associazione valori della variante
foreach ($valori_attributi as $id => $id_valore) {
$database->insert('mg_articolo_attributo', [
'id_articolo' => $articolo->id,
'id_valore' => $id_valore,
]);
}
}
/**
* Metodo per la generazione di tutte le varianti disponibili per la combinazione corrente.
*/
public function generaTutto()
{
if ($this->articoli()->count() !== 0) {
return;
}
// Individuazione valori disponibili per gli attributi
$valori = [];
$attributi = $this->attributi;
foreach ($attributi as $attributo) {
$valori[] = $attributo->valori->pluck('id')->all();
}
// Generazione di tutte le combinazioni
$varianti = cartesian($valori);
// Generazione delle singole varianti
foreach ($varianti as $variante) {
$this->generaVariante($variante);
}
}
/**
* Funzione dedicata a mantenere sincronizzati i campi condivisi delle varianti di uno specifico articolo.
*/
public static function sincronizzaVarianti(Articolo $articolo)
{
$combinazione = $articolo->combinazione;
if (empty($combinazione)) {
return;
}
$sincro = collect($articolo->toArray())->filter(function ($value, $key) {
return in_array($key, self::$campi_varianti);
});
$combinazione->sincronizzaCampi($sincro->toArray());
}
/* Relazioni Eloquent */
public function attributi()
{
return $this->belongsToMany(Attributo::class, 'mg_attributo_combinazione', 'id_combinazione', 'id_attributo')
->orderBy('order', 'ASC');
}
public function articoli()
{
return $this->hasMany(Articolo::class, 'id_combinazione');
}
/**
* Funzione per sincronizzare i campi condivisi dagli Articoli di tipo Variante.
*
* @param $values
*/
protected function sincronizzaCampi($values)
{
$articoli = $this->articoli->pluck('id')->all();
database()->table('mg_articoli')
->whereIn('id', $articoli)
->update($values);
database()->table('mg_combinazioni')
->where('id', $this->id)
->update($values);
}
}

View File

@ -0,0 +1,73 @@
<?php
use Modules\Articoli\Articolo;
include_once __DIR__.'/../../core.php';
$articolo_originale = Articolo::find($id_record);
$combinazione = $articolo_originale->combinazione;
if (empty($combinazione)) {
echo '
<script>
$(document).ready(function (){
$("#link-tab_'.$id_plugin.'").addClass("disabled");
})
</script>';
return;
}
echo '
<button type="button" class="btn btn-warning pull-right" onclick="visualizzaCombinazione(this)">
<i class="fa fa-external-link"></i> '.tr('Visualizza combinazione').'
</button>
<div class="clearfix"></div>
<br>
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">'.tr('Varianti disponibili (Articoli)').'</h3>
</div>
<div class="box-body">
<table class="table table-hover table-striped">
<thead>
<tr>
<th width="10%">'.tr('Foto').'</th>
<th>'.tr('Variante').'</th>
<th>'.tr('Articolo').'</th>
</tr>
</thead>
<tbody>';
$articoli = $combinazione->articoli;
foreach ($articoli as $articolo) {
echo '
<tr data-id="'.$articolo->id.'">
<td><img class="img-thumbnail img-responsive" src="'.$articolo->image.'"></td>
<td>'.$articolo->nome_variante.'</td>
<td>
'.Modules::link('Articoli', $articolo->id, $articolo->codice.' - '.$articolo->descrizione).'
'.($articolo->id == $articolo_originale->id ? '<span class="badge pull-right">'.tr('Articolo corrente').'</span>' : '').'
</td>
</tr>';
}
echo '
</tbody>
</table>
</div>
</div>
<script>
function visualizzaCombinazione(button) {
// Redirect
redirect(globals.rootdir + "/editor.php", {
id_module: "'.$combinazione->getModule()->id.'",
id_record: "'.$combinazione->id.'",
});
}
</script>';

View File

@ -66,3 +66,76 @@ INSERT INTO `zz_views` (`id`, `id_module`, `name`, `query`, `order`, `search`, `
-- Miglioramento supporto autenticazione OAuth 2
ALTER TABLE `em_accounts` ADD `oauth2_config` TEXT;
-- Aggiunto sistema di gestione Combinazioni Articoli
CREATE TABLE IF NOT EXISTS `mg_attributi` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nome` varchar(255) NOT NULL,
`titolo` varchar(255) NOT NULL,
`ordine` int(11) NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY(`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `mg_valori_attributi` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_attributo` int(11) NOT NULL,
`nome` varchar(255) NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY(`id`),
FOREIGN KEY (`id_attributo`) REFERENCES `mg_attributi`(`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `mg_combinazioni` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`codice` varchar(255) NOT NULL,
`nome` varchar(255) NOT NULL,
`id_categoria` int(11),
`id_sottocategoria` int(11),
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY(`id`),
FOREIGN KEY (`id_categoria`) REFERENCES `mg_categorie`(`id`) ON DELETE SET NULL,
FOREIGN KEY (`id_sottocategoria`) REFERENCES `mg_categorie`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `mg_attributo_combinazione` (
`id_combinazione` int(11) NOT NULL,
`id_attributo` int(11) NOT NULL,
`order` int(11) NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`id_attributo`, `id_combinazione`),
FOREIGN KEY (`id_attributo`) REFERENCES `mg_attributi`(`id`),
FOREIGN KEY (`id_combinazione`) REFERENCES `mg_combinazioni`(`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `mg_articolo_attributo` (
`id_articolo` int(11) NOT NULL,
`id_valore` int(11) NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (`id_articolo`) REFERENCES `mg_articoli`(`id`),
FOREIGN KEY (`id_valore`) REFERENCES `mg_valori_attributi`(`id`)
) ENGINE=InnoDB;
ALTER TABLE mg_articoli ADD `id_combinazione` int(11), ADD FOREIGN KEY (`id_combinazione`) REFERENCES `mg_combinazioni`(`id`);
INSERT INTO `zz_modules` (`id`, `name`, `title`, `directory`, `options`, `options2`, `icon`, `version`, `compatibility`, `order`, `parent`, `default`, `enabled`) VALUES
(NULL, 'Attributi Combinazioni', 'Attributi Combinazioni', 'attributi_combinazioni', 'SELECT |select| FROM mg_attributi WHERE mg_attributi.deleted_at IS NULL AND 1=1 HAVING 2=2', NULL, 'fa fa-angle-right', '1.0', '2.*', '100', '20', '1', '1'),
(NULL, 'Combinazioni', 'Combinazioni', 'combinazioni_articoli', 'SELECT |select| FROM mg_combinazioni WHERE mg_combinazioni.deleted_at IS NULL AND 1=1 HAVING 2=2', NULL, 'fa fa-angle-right', '1.0', '2.*', '100', '20', '1', '1');
INSERT INTO `zz_plugins` (`id`, `name`, `title`, `idmodule_from`, `idmodule_to`, `position`, `directory`, `options`) VALUES
(NULL, 'Varianti Articolo', 'Varianti Articolo', (SELECT `id` FROM `zz_modules` WHERE `name`='Articoli'), (SELECT `id` FROM `zz_modules` WHERE `name`='Articoli'), 'tab', 'varianti_articolo', 'custom');
-- Aggiunta colonne per il nuovo modulo Attributi Combinazioni
INSERT INTO `zz_views` (`id`, `id_module`, `name`, `query`, `order`, `visible`, `format`, `default`) VALUES
(NULL, (SELECT `id` FROM `zz_modules` WHERE `name` = 'Attributi Combinazioni'), 'id', 'mg_attributi.id', 1, 0, 0, 1),
(NULL, (SELECT `id` FROM `zz_modules` WHERE `name` = 'Attributi Combinazioni'), 'Nome', 'mg_attributi.nome', 2, 1, 0, 1),
(NULL, (SELECT `id` FROM `zz_modules` WHERE `name` = 'Combinazioni'), 'id', 'mg_combinazioni.id', 1, 0, 0, 1),
(NULL, (SELECT `id` FROM `zz_modules` WHERE `name` = 'Combinazioni'), 'Nome', 'mg_combinazioni.nome', 2, 1, 0, 1);