Skip to content

Commit f3d666b

Browse files
committed
refactor: ottimizzazione classi documenti e generazione numero progressivo
1 parent 24d89c9 commit f3d666b

8 files changed

Lines changed: 781 additions & 548 deletions

File tree

lib/common.php

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Modules\Fatture\Fattura;
3333
use Modules\Interventi\Intervento;
3434
use Modules\Ordini\Ordine;
35+
use Util\Generator;
3536

3637
/**
3738
* Esegue una somma precisa tra due interi/array.
@@ -619,3 +620,154 @@ function cleanInvalidBankReferences($anagrafica)
619620
$anagrafica->save();
620621
}
621622
}
623+
624+
/**
625+
* Calcola il prossimo numero progressivo per un documento.
626+
*
627+
* Funzione generica per il calcolo del numero progressivo utilizzata da vari moduli
628+
* (Contratti, Ordini, Interventi, DDT, Fatture, Preventivi, Anagrafiche).
629+
*
630+
* @param string $table Nome della tabella del database
631+
* @param string $field Nome del campo da calcolare (numero, numero_esterno, codice)
632+
* @param string $data Data del documento
633+
* @param int $id_segment ID del segmento
634+
* @param array $options Opzioni aggiuntive:
635+
* - 'data_field': nome del campo data (default 'data')
636+
* - 'direction': direzione del documento (entrata/uscita)
637+
* - 'skip_direction': direzione da saltare (ritorna stringa vuota)
638+
* - 'type_document': tipo di documento per condizioni extra
639+
* - 'type_document_field': campo del tipo documento (es. idtipoddt, idtipoordine)
640+
* - 'type_document_table': tabella del tipo documento
641+
* - 'conditions_extra': array di condizioni extra SQL
642+
* - 'use_setting': usa setting() invece di Generator::getMaschera() (default false)
643+
* - 'setting_key': chiave del setting per la maschera (se use_setting = true)
644+
* - 'date_pattern': pattern della data per Generator::generate()
645+
* - 'use_date_pattern': usa Generator::dateToPattern() per il pattern data
646+
*
647+
* @return string Il prossimo numero progressivo
648+
*/
649+
function getNextNumeroProgressivo($table, $field, $data, $id_segment, $options = [])
650+
{
651+
// Opzioni di default
652+
$defaults = [
653+
'data_field' => 'data',
654+
'direction' => null,
655+
'skip_direction' => null,
656+
'type_document' => null,
657+
'type_document_field' => null,
658+
'type_document_table' => null,
659+
'conditions_extra' => [],
660+
'use_setting' => false,
661+
'setting_key' => null,
662+
'date_pattern' => null,
663+
'use_date_pattern' => false,
664+
];
665+
666+
$options = array_merge($defaults, $options);
667+
668+
// Se la direzione corrisponde a quella da saltare, ritorna stringa vuota
669+
if ($options['skip_direction'] && $options['direction'] == $options['skip_direction']) {
670+
return '';
671+
}
672+
673+
// Ottieni la maschera
674+
if ($options['use_setting']) {
675+
$maschera = setting($options['setting_key']);
676+
} else {
677+
$maschera = Generator::getMaschera($id_segment);
678+
}
679+
680+
// Se la maschera è '#', ritorna '#' (per ordini di vendita)
681+
if ($maschera === '#') {
682+
return '#';
683+
}
684+
685+
// Calcola le condizioni in base alla maschera
686+
$has_month = str_contains($maschera, 'm');
687+
$has_year = str_contains($maschera, 'YYYY') || str_contains($maschera, 'yy');
688+
689+
// Costruisci le condizioni
690+
$conditions = [];
691+
692+
// Condizione per anno (solo se data è specificata)
693+
if ($has_year && $data !== null) {
694+
$data_timestamp = strtotime((string) $data);
695+
$conditions[] = 'YEAR('.$options['data_field'].') = '.prepare(date('Y', $data_timestamp));
696+
}
697+
698+
// Condizione per mese (solo se data è specificata)
699+
if ($has_month && $data !== null) {
700+
$data_timestamp = strtotime((string) $data);
701+
$conditions[] = 'MONTH('.$options['data_field'].') = '.prepare(date('m', $data_timestamp));
702+
}
703+
704+
// Condizione per segmento (se specificato)
705+
if (!empty($id_segment)) {
706+
$conditions[] = 'id_segment = '.prepare($id_segment);
707+
}
708+
709+
// Condizione per direzione/tipo documento
710+
if ($options['direction'] && $options['type_document_field'] && $options['type_document_table']) {
711+
$conditions[] = $options['type_document_field'].' IN (SELECT `id` FROM `'.$options['type_document_table'].'` WHERE `dir` = '.prepare($options['direction']).')';
712+
}
713+
714+
// Aggiungi condizioni extra
715+
if (!empty($options['conditions_extra'])) {
716+
$conditions = array_merge($conditions, $options['conditions_extra']);
717+
}
718+
719+
// Ottieni l'ultimo numero
720+
$ultimo = !empty($conditions)
721+
? Generator::getPreviousFrom($maschera, $table, $field, $conditions, $data)
722+
: Generator::getPreviousFrom($maschera, $table, $field, null, $data);
723+
724+
// Genera il nuovo numero
725+
$date_pattern = ($options['use_date_pattern'] && $data !== null) ? Generator::dateToPattern($data) : ($options['date_pattern'] ?? []);
726+
$numero = Generator::generate($maschera, $ultimo, 1, $date_pattern, $data);
727+
728+
return $numero;
729+
}
730+
731+
/**
732+
* Calcola il prossimo numero secondario progressivo per un documento.
733+
*
734+
* Funzione generica per il calcolo del numero secondario progressivo utilizzata da vari moduli
735+
* (Ordini, DDT, Fatture). Il numero secondario viene solitamente utilizzato per
736+
* i documenti di vendita (entrata).
737+
*
738+
* @param string $table Nome della tabella del database
739+
* @param string $field Nome del campo da calcolare (numero_esterno)
740+
* @param string $data Data del documento
741+
* @param int $id_segment ID del segmento
742+
* @param array $options Opzioni aggiuntive:
743+
* - 'data_field': nome del campo data (default 'data')
744+
* - 'direction': direzione del documento (entrata/uscita)
745+
* - 'skip_direction': direzione da saltare (ritorna stringa vuota, default 'uscita')
746+
* - 'type_document': tipo di documento per condizioni extra
747+
* - 'type_document_field': campo del tipo documento (es. idtipoddt, idtipoordine)
748+
* - 'type_document_table': tabella del tipo documento
749+
* - 'conditions_extra': array di condizioni extra SQL
750+
* - 'date_pattern': pattern della data per Generator::generate()
751+
* - 'use_date_pattern': usa Generator::dateToPattern() per il pattern data
752+
*
753+
* @return string Il prossimo numero secondario progressivo
754+
*/
755+
function getNextNumeroSecondarioProgressivo($table, $field, $data, $id_segment, $options = [])
756+
{
757+
// Opzioni di default
758+
$defaults = [
759+
'data_field' => 'data',
760+
'direction' => null,
761+
'skip_direction' => 'uscita',
762+
'type_document' => null,
763+
'type_document_field' => null,
764+
'type_document_table' => null,
765+
'conditions_extra' => [],
766+
'date_pattern' => null,
767+
'use_date_pattern' => true,
768+
];
769+
770+
$options = array_merge($defaults, $options);
771+
772+
return getNextNumeroProgressivo($table, $field, $data, $id_segment, $options);
773+
}

modules/anagrafiche/src/Anagrafica.php

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -366,16 +366,14 @@ public function interventi()
366366
*/
367367
public static function getNextCodice()
368368
{
369-
// Recupero maschera per le anagrafiche
370-
$maschera = setting('Formato codice anagrafica');
371-
372-
$ultimo = Generator::getPreviousFrom($maschera, 'an_anagrafiche', 'codice', [
373-
"codice != ''",
374-
'deleted_at IS NULL',
369+
return getNextNumeroProgressivo('an_anagrafiche', 'codice', null, null, [
370+
'use_setting' => true,
371+
'setting_key' => 'Formato codice anagrafica',
372+
'conditions_extra' => [
373+
"codice != ''",
374+
'deleted_at IS NULL',
375+
],
375376
]);
376-
$codice = Generator::generate($maschera, $ultimo);
377-
378-
return $codice;
379377
}
380378

381379
protected static function creaConto(Anagrafica $anagrafica, $campo)

modules/contratti/src/Contratto.php

Lines changed: 84 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ class Contratto extends Document
4343
*/
4444
public static $movimenta_magazzino = false;
4545

46+
/**
47+
* @var bool Flag per evitare ricorsioni durante il salvataggio
48+
*/
49+
protected $is_saving = false;
50+
4651
protected $table = 'co_contratti';
4752

4853
/**
@@ -100,13 +105,21 @@ public static function build(Anagrafica $anagrafica, $nome, $id_segment = null)
100105

101106
public function fixTipiSessioni()
102107
{
103-
$database = database();
108+
// Ottieni i tipi di intervento già associati al contratto
109+
$presenti = database()->fetchArray('SELECT idtipointervento FROM co_contratti_tipiintervento WHERE idcontratto = '.prepare($this->id));
110+
$id_presenti = array_column($presenti, 'idtipointervento');
104111

105-
$presenti = $database->fetchArray('SELECT idtipointervento FROM co_contratti_tipiintervento WHERE idcontratto = '.prepare($this->id));
112+
// Aggiunta associazioni costi unitari al contratto per i tipi non presenti
113+
$tipi = TipoSessione::whereNull('deleted_at')
114+
->whereNotIn('id', $id_presenti)
115+
->get(['id', 'costo_orario', 'costo_km', 'costo_diritto_chiamata', 'costo_orario_tecnico', 'costo_km_tecnico', 'costo_diritto_chiamata_tecnico']);
106116

107-
// Aggiunta associazioni costi unitari al contratto
108-
$tipi = TipoSessione::whereNotIn('id', array_column($presenti, 'idtipointervento'))->where('deleted_at', null)->get();
117+
if ($tipi->isEmpty()) {
118+
return;
119+
}
109120

121+
// Costruisci l'array di inserimento in un'unica operazione
122+
$database = database();
110123
foreach ($tipi as $tipo) {
111124
$database->insert('co_contratti_tipiintervento', [
112125
'idcontratto' => $this->id,
@@ -213,12 +226,23 @@ public function fixDataConclusione()
213226

214227
public function save(array $options = [])
215228
{
216-
$this->fixBudget();
217-
$this->fixDataConclusione();
229+
// Evita ricorsioni
230+
if ($this->is_saving) {
231+
return parent::save($options);
232+
}
218233

219-
$result = parent::save($options);
234+
$this->is_saving = true;
220235

221-
$this->fixTipiSessioni();
236+
try {
237+
$this->fixBudget();
238+
$this->fixDataConclusione();
239+
240+
$result = parent::save($options);
241+
242+
$this->fixTipiSessioni();
243+
} finally {
244+
$this->is_saving = false;
245+
}
222246

223247
return $result;
224248
}
@@ -231,42 +255,64 @@ public function triggerEvasione(Component $trigger)
231255
{
232256
parent::triggerEvasione($trigger);
233257

234-
if (setting('Cambia automaticamente stato contratti fatturati')) {
235-
// Non modificare lo stato se il contratto è già in uno stato bloccato, non fatturabile e non pianificabile
236-
if ($this->stato && $this->stato->is_bloccato && !$this->stato->is_fatturabile && !$this->stato->is_pianificabile) {
237-
return;
238-
}
258+
if (!setting('Cambia automaticamente stato contratti fatturati')) {
259+
return;
260+
}
239261

240-
$righe = $this->getRighe();
241-
$qta_evasa = $righe->sum('qta_evasa');
242-
$qta = $righe->sum('qta');
243-
$parziale = $qta != $qta_evasa;
244-
245-
// Impostazione del nuovo stato
246-
if ($qta_evasa == 0) {
247-
$descrizione = 'In lavorazione';
248-
$codice_intervento = 'OK';
249-
} else {
250-
$descrizione = $parziale ? 'Parzialmente fatturato' : 'Fatturato';
251-
$codice_intervento = 'FAT';
252-
}
262+
// Non modificare lo stato se il contratto è già in uno stato bloccato, non fatturabile e non pianificabile
263+
if ($this->stato && $this->stato->is_bloccato && !$this->stato->is_fatturabile && !$this->stato->is_pianificabile) {
264+
return;
265+
}
266+
267+
$righe = $this->getRighe();
268+
$qta_evasa = $righe->sum('qta_evasa');
269+
$qta = $righe->sum('qta');
270+
$parziale = $qta != $qta_evasa;
253271

254-
$stato = Stato::where('name', $descrizione)->first()->id;
255-
$this->stato()->associate($stato);
256-
$this->save();
272+
// Impostazione del nuovo stato
273+
if ($qta_evasa == 0) {
274+
$descrizione = 'In lavorazione';
275+
$codice_intervento = 'OK';
276+
} else {
277+
$descrizione = $parziale ? 'Parzialmente fatturato' : 'Fatturato';
278+
$codice_intervento = 'FAT';
279+
}
257280

258-
// Trasferimento degli interventi collegati
259-
$interventi = $this->interventi;
260-
$stato_intervento = \Modules\Interventi\Stato::where('codice', $codice_intervento)->first();
281+
// Ottieni il nuovo stato
282+
$stato = Stato::where('name', $descrizione)->first();
283+
if (!$stato) {
284+
return;
285+
}
286+
287+
// Aggiorna lo stato solo se è diverso
288+
if ($this->idstato != $stato->id) {
289+
$this->idstato = $stato->id;
290+
$this->saveQuietly();
291+
}
292+
293+
// Ottieni lo stato intervento e trasferisci agli interventi collegati
294+
$stato_intervento = \Modules\Interventi\Stato::where('codice', $codice_intervento)->first();
295+
if ($stato_intervento) {
296+
// Carica solo gli interventi con stato bloccato
297+
$interventi = $this->interventi()->with('stato')->get();
261298
foreach ($interventi as $intervento) {
262-
if ($intervento->stato->is_bloccato == 1) {
263-
$intervento->stato()->associate($stato_intervento);
264-
$intervento->save();
299+
if ($intervento->stato && $intervento->stato->is_bloccato == 1 && $intervento->idstato != $stato_intervento->id) {
300+
$intervento->idstato = $stato_intervento->id;
301+
$intervento->saveQuietly();
265302
}
266303
}
267304
}
268305
}
269306

307+
/**
308+
* Salva il modello senza eseguire i trigger e le operazioni automatiche.
309+
* Utile per evitare ricorsioni durante il salvataggio.
310+
*/
311+
public function saveQuietly(array $options = [])
312+
{
313+
return parent::save($options);
314+
}
315+
270316
// Metodi statici
271317

272318
/**
@@ -276,24 +322,9 @@ public function triggerEvasione(Component $trigger)
276322
*/
277323
public static function getNextNumero($data, $id_segment)
278324
{
279-
$maschera = Generator::getMaschera($id_segment);
280-
281-
if (str_contains($maschera, 'm')) {
282-
$ultimo = Generator::getPreviousFrom($maschera, 'co_contratti', 'numero', [
283-
'YEAR(data_bozza) = '.prepare(date('Y', strtotime((string) $data))),
284-
'MONTH(data_bozza) = '.prepare(date('m', strtotime((string) $data))),
285-
]);
286-
} elseif (str_contains($maschera, 'YYYY') or str_contains($maschera, 'yy')) {
287-
$ultimo = Generator::getPreviousFrom($maschera, 'co_contratti', 'numero', [
288-
'YEAR(data_bozza) = '.prepare(date('Y', strtotime((string) $data))),
289-
]);
290-
} else {
291-
$ultimo = Generator::getPreviousFrom($maschera, 'co_contratti', 'numero');
292-
}
293-
294-
$numero = Generator::generate($maschera, $ultimo);
295-
296-
return $numero;
325+
return getNextNumeroProgressivo('co_contratti', 'numero', $data, $id_segment, [
326+
'data_field' => 'data_bozza',
327+
]);
297328
}
298329

299330
// Opzioni di riferimento

0 commit comments

Comments
 (0)