P4A 3 Framework: Gestione Fatture 2, un’applicazione completa

Gestione fatture 2

Ho deciso di riscrivere in larga parte il codice dell’applicazione Gestione Fatture che avevo pubblicato in questo articolo, per renderla compatibile con la nuova versione di P4A (disponibile qui) (la 3.8.4) ed anche per correggere diversi bug che sono emersi nell’utilizzo.

Pur mantenendo tutte le caratteristiche della precedente versione, l’attuale non è compatibile con quella precedente per via di alcune modifiche sostanziali che ho dovuto apportare alla struttura del database.

Oltre a poter essere utile proprio per la gestione delle fatture delle ditte individuale e dei professionisti, credo che questo codice possa essere utile anche a tutti coloro che si avvicinano al framework P4A, in quanto all’interno ci sono diversi esempi di come risolvere la maggior parte dei problemi che si incontrano sviluppando con questo framework.

Alcune delle nuove caratteristiche sono:

  • Riscritto lo schema del database (non è più compatibile con la versione precedente)
  • Possibilità di inserire il saldo o il saldo parziale riscosso della fattura emessa per il controllo dei pagamenti
  • Nuova funzionalità per monitorare le fatture saldate interamente o parzialmente (conteggio dei soldi in cassa e dell’importo da riscuotere)
  • Nuova funzionalità per il filtraggio delle fatture
  • Possibilità di emettere note di credito
  • Nuova funzionalità per fatture ricorrenti
  • Corretto il codice della classe ezPDF (i file sono allegati nella dir. Libraries) per la compatibilità con PHP 5.3.x
  • Possibilità di inserire automaticamente la descrizione di prestazioni già inserite in fatture precedenti

A chi è rivolto:

  • Soggetti senza P.IVA che rilasciano la sola ritenuta di acconto
  • Soggetti con P.IVA iscritti alla Camera di Commercio (Ditte individuali)
  • Professionisti senza Cassa previdenziale autonoma
  • Professionisti con Cassa previdenziale autonoma

Per la mancanza di una gestione del magazzino è sconsigliata ai commercianti che hanno a che fare con un magazzino merce, inventario e via dicendo.

L’applicazione viene distribuita con licenza LGPL 3, in questo link potete trovare la traduzione non ufficiale in italiano.

Installazione:

  • Installare il framework p4a 3.8.4 (requisito obbligatorio)
  • Scompattare l’archivio “gestionefatture2.zip” all’interno della directory “p4a/applications”
  •   Generare un database con il nome: “gestionefatture2” ed importare il dump SQL:  “gestionefatture2.sql” che si trova all’interno della directory “_private”
  • Modificare il file “index.php” sostituendo nella riga di codice:

define(“P4A_DSN”, ‘mysql://yourdbuser:yourdbpassword@localhost/gestionefatture2’);

yourdbuser = l’utente MySQL con il quale accedere al vostro database

yourdbpassword = la password del suddetto utente.

Infine per accedere all’applicazione i parametri di login sono:

Username = admin

Password = admin

E’ importante mantenere nella directory “/libraries” i file di libreria di ezPdf che sono contenuti nel pacchetto, perché ho dovuto correggere alcune linee di codice (purtroppo ezPdf è stata abbandonata dal suo autore…) per renderla compatibile con PHP 5.3.x.

La applicazione richiede che sia installato il PHP 5.3.x + MySQL 5.1.x o superiore.

DOWNLOAD: Gestione Fatture 2

Sono graditi commenti, suggerimenti e correzioni!

 

 

 

P4A 3 Framework: helper per sincronizzare le tabelle sequence in MySQL

P4A 3 LogoChi utilizza l’RDBMS MySQL in P4A avrà certamente notato che il framework produce in certi casi, delle tabelle il cui nome termina in ‘_seq’. Queste tabelle servono ad avere una compatibilità nella gestione di tutti gli RDBMS supportati (MySQL, PostgreSQL, SQLite, Oracle). PostgreSQL e Oracle, non hanno lo stesso tipo di gestione delle chiavi primarie con l’attributo ‘AUTOINCREMENT‘, rispetto a MySQL e SQLite. La soluzione adottata è di costruire, per ogni tabella con chiave primaria di tipo ‘AUTOINCREMENT’, delle tabelle accessorie che hanno un campo ‘id’ di tipo ‘AUTOINCREMENT’ e di riferirsi a quest’ultime per ottenre il ‘LAST INSERT ID‘. In questo modo viene ricalcato il comportamento delle chiavi primarie di tipo ‘SERIAL’ in PostgreSQL, e anche quelle di Oracle (leggermente diverse).

Prima di tutto una precisazione: questo articolo è riferito esclusivamente agli utenti MySQL.
Se si pensa di utilizzare esclusivamente P4A Framework per la gestione dei dati del nostro database, sarebbe bene in fase di progettazione evitare di usare gli AUTOINCREMENT perché, come detto, non necessari. Nel caso in cui il framework venga utilizzato come ‘backoffice’ per la gestione di dati che vengono alimentati da un form di un sito web, oppure si abbia la necessità di utilizzare una struttura dati pre-esistente con tanto di chiavi primarie ‘AUTOINCREMENT’, potremmo prima o poi trovarci di fronte ad un problema di disallineamento dei valori fra la chiave primaria della tabella e l’id’ della tabella ‘_seq’ associata.

Immaginiamo di avere una tabella “test” con questi due campi:

+----+---------+
| id | name    |
+----+---------+

con ‘id’ chiave primaria, P4A creerà automaticamente una tabella “test_id_seq” con una campo ‘id’ chiave primaria e ‘AUTOINCREMENT’, ed userà i valori forniti da quest’ultima sia per l’inserimento di nuovi record nella tabella “test“, che per altre operazioni sulla chiave primaria. Dunque utilizzando un programma esterno al framework per fare degli inserimenti e volendo mantenere allineate le tabelle principali e le tabelle “_seq” sarà necessario, per ogni “INSERT” nella tabella principale, fare altrettanto nella “_seq”!
Anche utilizzando questo metodo, potrebbe verificarsi prima o poi il problema di un disallineamento, in questo caso ho pensato potesse utile scrivere un metodo della classe “P4A_DB_SOURCE” da utilizzare in extremis, per risistemare le cose.

Per maggiori informazioni sull’utilizzo degli ‘helper’ in P4A suggerisco questo link

Ecco il codice dell’helper, in pratica viene prelevato il valore massimo della chiave primaria ‘AUTOINCREMENT’ della tabella che funge da sorgente dati, quindi viene eliminata la tabella ‘_seq’ associata e successivamente ricostruita con il valore corretto.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 function p4a_db_source_resync_seq($source)
 
 { 
	$table = $source->getTable();
	$pKField = $source->getPk();
	$seqTable =  $table."_".$pKField."_seq";
	$lastId = P4A_DB::singleton()->fetchOne("SELECT MAX($pKField) FROM $table");
	$query = "DROP TABLE IF EXISTS `$seqTable`";
	$resQ1 = P4A_DB::singleton()->query($query);
	$query = "CREATE TABLE `$seqTable` (
	`id` int(11) NOT NULL auto_increment,
	PRIMARY KEY  (`id`)
	) TYPE=MyISAM AUTO_INCREMENT=$lastId";
 
	$resQ2 = P4A_DB::singleton()->query($query);
	$query = "INSERT INTO `$seqTable` VALUES ($lastId)";
	$resQ3 = P4A_DB::singleton()->query($query);
  return ($resQ1 && $resQ2 && $resQ3); 
 
 }

Questo codice deve essere inserito in un file con il nome: “p4a_db_source_resync_seq.php” che deve essere posizionato nella directory “libraries” della nostra applicazione P4A.

Ecco infine un po’ di codice di esempio da aggiungere alla nostra ipotetica maschera di manutenzione del db:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class test extends P4A_Base_Mask
{
	public function __construct()
 
	{
		parent::__construct();
 
		// DB Source
		$this->build("p4a_db_source", "source")
			->setTable("test")
			->setPk("id")
			->load();
		$this->setSource($this->source);
 
		// ....
 
	    $this->build("p4a_button", "btn_resync")
			->setLabel("resync")
			->implement("onclick", $this, "resync_seq_table");
 
		// ....
	}
 
	public function resync_seq_table()
	{
		$res = $this->source->resync_seq();	
	}
 
	// ....
}

Conclusioni

Questo metodo dovrebbe essere usato con una certa cautela, e solo come procedura di emergenza. Non consiglio l’utilizzo se c’è la possibilità di accesso in scrittura alla tabella coinvolta da parte di altri utenti.

Riferimenti ed approfondimenti:

Gestione fatture: un’applicazione completa in P4A

Screenshot di Gestione fatture
Screenshot di Gestione fatture

Questa applicazione web si basa sul framework PHP: P4A 3. Si tratta di una versione migliorata e corretta del codice già presentato in questo post. E’ un’applicazione completa per la gestione delle fatture, pensata soprattutto per chi eroga prestazioni, più che prodotti, in quanto non contempla una gestione del magazzino. Può essere utile per chi, come me, fa consulenze informatiche e realizza software, oppure per piccole aziende di autotrasporto che lavorano principalmente conto terzi o per artigiani e professionisti che non utilizzano un magazzino.
Non può essere utilizzata però da coloro i quali sono iscritti ad un albo professionale, in quanto, da informazioni ricevute da un commercialista, mi risulta che il calcolo della ritenuta d’acconto e delle trattenute INPS sia leggermente differente.

Maschere:

  • Gestione azienda: in questa maschera è possibile definire la ragione sociale, i dati anagrafici e il logo dell’azienda, definire l’utente e la password e impostare il tipo di fatturazione: IVA fissa, IVA variabile, ritenuta d’acconto, contributo INPS (queste ultime due opzioni sono possibili solo con IVA fissa).
  • Gestione clienti: in questa maschera vengono immessi i dati anagrafici dei clienti.
  • Fatture: è la maschera principale per creare e modificare le fatture. E’ divisa in due parti selezionabili attraverso delle ‘linguette’ come per gli schedari. La sezione ‘fatture’ permette di inserire una nuova fattura vuota, indicando la data, il numero, il cliente e la tipologia di pagamento. E’ anche possibile indicare se la fattura è stata pagata attraverso un flag ed inserire eventuali note. Il numero viene suggerito automaticamente seguendo la progressione dell’anno in corso, è però possibile immetterlo manualmente, in questo caso esiste un controllo per evitare duplicati.
    La sezione ‘Dettaglio fattura’ serve per inserire la lista di prestazioni o prodotti da fatturare. E’ possibile raggruppare una lista attraverso il campo ‘sezione’. Gli altri campi da riempire sono la descrizione, la quantità il prezzo e l’IVA (se non si è scelta la modalità IVA fissa). In fondo alla maschera vengono riportati i valori del totale imponibile, totale IVA e totale fattura. (totale ritenuta d’acconto e contributo INPS, solo se lo si è attivato nella Gestione azienda).
    Attraverso il pulsante di stampa, viene generato un documento pdf stampabile con la fattura impaginata in una o più pagine (in questo caso numerate) e la dicitura del destinatario allineata con l’apertura delle buste a ‘finestra’ standard.
  • Lista fatture: attraverso questo report è possibile filtrare le fatture di un certo periodo, visualizzare e stampare su pdf l’elenco e i totali dell’imponibile, delle fatture e i totali di quelle pagate e quelle no.
  • Tipo pagamenti: E’ possibile inserire le varie modalità di pagamento (assegno, bonifico, riba ecc.)
  • Unità di misura: E’ possibile inserire le unità di misura (ore, pezzi, kg, ecc.)

Alcune note sull’utilizzo:
Per aggiungere un nuovo record, è sempre necessario utilizzare il pulsante ‘Inserisci un nuovo elemento’, e una volta compilati i campi, premere il pulsante ‘Conferma e salva’. Nel caso di inserimento di una nuova fattura, dopo aver salvato e prima di inserire la lista delle prestazioni, è necessario selezionare con il puntatore (triangolino nero) la fattura appena creata. Per creare dei blocchi di prestazioni, inserire una dicitura nel campo ‘sezione’, in questo modo, i nuovi record creati con la stessa dicitura nel campo sezione, saranno raggruppati e ordinati alfabeticamente in fattura.

Installazione

Deve innazitutto essere installato il framework P4A versione 3.2.x (dettagli per il download e l’installazione). Poi è sufficiente scompattare l’archivio compresso nella direcory ‘applications’ che si trova sotto la directory ‘P4A’, caricare il dump .sql del database che si trova nella directory ‘_private’ in un server ‘MySQL’ e impostare i parametri per la connessione nel file ‘index.php’ (Nella maggioranza dei casi questo potrebbe non essere necessario). Chi utilizza linux, deve ricordarsi di dare i permessi di scrittura alla directory ‘uploads’ e alle sue subdirectories. Se il server web è installato localmente, per accedere all’applicazione digitare la url ‘http://localhost/p4a/applications/gestionefatture/’
Infine, volendo personalizzare l’aspetto grafico dell’applicazione, suggerisco questo articolo.

Il nome utente e la password per l’accesso iniziale sono: user = ‘utente’ e password = ‘utente’

Demo:

La demo è temporaneamente sospesa.

Download

Licenza

La licenza applicata è la LGPLv3

Conclusioni:

Considerando che l’applicazione viene fornita gratuitamente, non garantisco nessun supporto ufficiale e non mi assumo alcuna responsabilità per eventuali anomalie di funzionamento. Sono comunque disponibile a correggere gli errori che mi segnalerete e a valutare eventuali upgrade, compatibilmente con il tempo richiesto.

Riferimenti ed approfondimenti:

 

 

P4A3 framework: il widget Countdown rinnovato e con funzione timer

Widget p4a CountdownQuesto widget per il framework P4A è l’aggiornamento di quello descritto in questo post.
L’aggiornamento deriva dal fatto che la nuova versione del codice originale per jQuery, presenta cambiamenti importanti e ha richiesto la riscrittura di una buona parte del widget. La versione del Countdown for jQuery attualmente installata è la 1.4.3.

Ho pensato di cogliere l’occasione per rendere un poco più interattivo il widget, facendo scattare un evento gestibile dall’utente attraverso un metodo personalizzato, allo scadere del contatempo. Inoltre, ho utilizzato l’integrazione di P4A con i18n, per impostare automaticamente la lingua per gli output delle etichette e il formato della data e dell’ora.

Questo è l’elenco dei metodi fino ad esso implementati:

Elenco dei metodi:
NomeTipoDefaultNote
setSince()integer"null"Imposta la data e l’ora di avvio in formato UNIX timestamp
setUntil()integer"null"Imposta la data e l’ora di stop in formato UNIX timestamp.
setDisplayFormat()string"dHMS"‘Y’ anni, ‘O’ mesi, ‘W’ settimane, ‘D’ giorni, ‘H’ ore, ‘M’ minuti, ‘S’ secondi. I caratteri minuscoli impostano la visualizzazione opzionale
setCompactFormat()string"false"Imposta il formato ridotto.
setDescription()string""Imposta la descrizione del contatempo.
setServerTime()stringnullImposta l’offset per il fuso orario. Valori di esempio: ‘+1’, ‘+2’, ‘+3’, ‘-1’, ‘-2’, ‘-3’. etc.
setLayout()string''Imposta un layout personalizzabile attraverso tag HTML e parametri per esprimere la data e l’ora.
Le singole sezioni sono delimitate da %x…%x, dove x è ‘Y’ per definire gli anni, ‘O’ per i mesi,
‘W’ per le settimane, e ‘D’ per i giorni, ‘H’ per le ore, ‘M’ per i minuti, e ‘S’ per i secondi.
All’interno di queste sezioni, è possibile utilizzare ‘%n’ per determinare il valore del periodo,
‘%nn’ per il valore con un minimo di due caratteri, e ‘%l’ per l’etichetta del periodo (in accordo
con quanto impostato in setCompactFormat()).
setlocale()string'auto'Imposta un valore per la regionalizzazione. Per default viene presa l’impostazione di P4A_LOCALE.
In alternativa è possibile impostarla manualmente. Le traduzioni disponibili sono:
Chinese – Simplified “zh-CN”, Chinese – Traditional “zh-TW”, Czech “cs”, Danish “da”, Dutch “nl”, French “fr”, German “de”, Hebrew “he”, Hungarian “hu”, Indonesian “id”, Italian “it”, Norwegian “nb”, Persian/Farsi “fa”, Polish “pl”, Portuguese/Brazilian “pt-BR”, Romanian “ro”, Russian “ru”, Slovak “sk”, Spanish “es”, Swedish “sv”, Turkish “tr”, Ukranian “uk”
setPauseResumeType()stringnullSe impostato con i valori: “lap” o “pause” mostra un pulsante che ferma o riavvia il contatempo.
“lap” ferma solo la visualizzazione, “pause” ferma anche il conteggio. Se null il pulsante non viene mostrato.
setPauseResumeLabels()string'pause,resume'Imposta le etichette per il pulsante “Pause/resume”. I valori per l’azione toggle devono essere separati da una virgola.
onExpiry()booleanfalseImposta se deve scattare un evento allo scadere del countdown (funziona solo quando è impostato setUntil()).
setMessageOnExpire()string""Imposta il testo del messaggio che viene visualizzato allo scadere del countdown.
messageOnExpire()voidvoidSe è impostato un messaggio, lo mostra e restituisce un handle per l’evento actionOnExpire.
Questo metodo viene attivato quando onExpiry(true)

Ecco un po’ di codice di esempio per inserire il contatempo in una maschera e utilizzare l’evento actionOnExpire:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class test_countdown extends p4a_base_mask
{
 public function __construct()
 {
   parent::__construct();
   $this->setTitle("test Countdown");
   $this->build("P4A_jCountdown", "countdown")
    ->setStyleProperty("width","400px")
    ->setStyleProperty("float","none")
    ->setStyleProperty("margin","auto")    
    ->setDescription("test countdown")
    ->countdown->setUntil(time()+11)
    ->countdown->onExpiry(true)
    ->countdown->setMessageOnExpire("Il tempo e' scaduto!");
 
    $this->countdown->implement('actionOnExpire',$this,'show');
 
    $this->frame->anchorCenter($this->countdown);
  }
 
	public function show ()
	{
		$this->info("Questa e' una prova!");
		//Enable code below for looping
		//$this->countdown->setUntil(time()+11);
	}
 
}

Il pacchetto completo della maschera di esempio è scaricabile qui

Note:

L’impostazione di setLocale() non è in genere necessaria per il fatto che quando viene “costruito” il contatempo la regionalizzazione viene individuata automaticamente. Nel caso si volesse impostare questo parametro manualmente, si consiglia di non cambiarlo successivamente a run-time. Questo perché è stato scelto di caricare l’intero set di impostazioni per la regionalizzazione tramite codice javascript esterno, mediante files di libreria. Per questo metodo quindi, i cambiamenti a run-time generano codice non pulito, che sconsiglio, almeno fino a che non trovo il tempo di migliorare questa soluzione.
Come nella precedente versione, è consigliabile aggiungere un secondo quando si utilizza:->setUntil() e decrementare di un secondo quando si utilizza ->setSince(time())per evitare una non perfetta sincronizzazione fra il tempo in cui viene mostrato il widget e lo scattare del conteggio.
Infine è possibile rendere invisibile il widget, semplicemente con ->setVisible(false) e mantenere attivo il contatempo. Questo permette di utilizzare comunque l’handle per l’evento actionOnExpire anche senza visualizzare il widget.

Conclusioni:

Questi ultimi giorni sono stati davvero impietosi con il mio (già scarso) tempo libero, e non ho potuto sviluppare il codice come avrei voluto… spero di poterci dedicare ancora del tempo, perché mi sembra un progetto interessante e, spero utile a molti. Ci sono ancora alcune caratteristiche da integrare e miglioramenti da fare nel codice, spero nell’aiuto degli utenti! Nel frattempo cercherò anche di ampliare e migliorare la documentazione (già presente nel pacchetto).

Riferimenti ed approfondimenti:

P4A 3 framework: db_navigator helper per estrarre un ramo da un dbtree

Esempio di db_navigatorQuesto helper deriva naturalmente dalla funzione descritta nel precedente post.P4A mette a disposizione un widget chiamato P4A_DB_Navigator che si basa su archivi di tipo albero a liste di adiacenza e fornisce un output grafico della lista dei nodi, con un buon grado di interattività. Esiste, per esempio, un metodo che si chiama getPath() che restituisce in un array, il percorso dalla root al nodo fornito in input attraverso la chiave primaria. Purtroppo però, non esiste nessun metodo per estrarre l’insieme di nodi figli da un certo nodo parentale, così ho pensato di adottare la funzione presentata nel precedente post.

I parametri di input sono:

  • $navigator – l’oggetto da cui discende (implicito)
  • $id – l’identificativo del nodo da cui partire
  • $table – il nome della tabella che rappresenta l’albero
  • $pk – il nome della chiave primaria
  • $recursor – il nome del campo che identifica il nodo parentale

Ecco il codice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// File: P4A_db_navigator_getBranch.php
<?php
function P4A_db_navigator_getBranch ($navigator, $params)
{
  list($id, $table, $pk, $recursor) = $params;
  $arr = p4a_db::singleton()->getAll("SELECT * FROM $table");
  $pksArr = array();
  foreach ($arr as $rec) {
    $pksArr[$rec[$pk]]=$rec[$recursor];
  }
  $branchIds = array($id);
  $i=0;
  while ($i<count($branchIds)) {
    $newKeys = array_keys($pksArr,$branchIds[$i]);
    if (!empty($newKeys)) {
      foreach ($newKeys as $newKey){
        array_push($branchIds, $newKey);
      }
    }
    ++$i;
  }
  $res = array();
  foreach ($arr as $child) {
    if (in_array($child[$pk], $branchIds)) {
      $res[] = $child;
    }
  }
  return $res;
}
?>

Come esempio ho costruito una semplicissima maschera nella quale, ogni volta che viene selezionato un nodo, viene mostrato un messaggio con l’array dei nodi figli.

Ecco il codice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class dbtree extends P4A_Base_Mask
{
  public function __construct()
  {
    parent::__construct();
    $this->setTitle('Test dbTree');
    //Source
    $this->build("p4a_db_source","dbtree")
      ->setTable("tree")
      ->setPk("id")
      ->load()
      ->firstRow();
    $this->setSource($this->dbtree);
    // db_navigator
    $this->build("p4a_db_navigator","navigator")
      ->setSource($this->dbtree)
      ->setRecursor("parent_id")
      ->setDescription("nome")
      ->setStyleProperty("height","85%")
      ->setStyleProperty("overflow","auto")
      ->collapse(true);
    // Display
    $this->display("sidebar_left",$this->navigator);
    // Intercept action afterMoveRow
    $this->intercept($this->dbtree,"afterMoveRow","showBranch");
  }
	public function showBranch()
  {
    $idCommessa = $this->dbtree->fields->id->getNewValue();
    $arr = $this->navigator->getBranch ($idCommessa, "tree", "id", "parent_id");
    $this->info(print_r($arr,true));
  }
}

L’esempio completo è scaricabile qui.

Conclusioni

Lancio l’idea di mettere questa funzione fra i metodi del P4A_db_navigator, sempre che non esca fuori qualche dannato baco :-).

Riferimenti ed approfondimenti:

P4A 3 Framework: helper per la gestione degli errori in saveRow() e deleteRow()

Errore di chiave duplicataHo scritto questi due helper per facilitare la gestione degli errori dovuti a query di aggiornamento e di cancellazione record. Gli helper sono una comodissima feature del framework P4A, si tratta dell’opportunità di scrivere funzioni personalizzate che possono essere raggiunte da qualsiasi maschera dell’applicazione. Questa caratteristica consente quindi un efficiente riutilizzo del codice, con grande vantaggio per la leggibilità dello stesso. Per costruire un helper, richiamabile da qualsiasi maschera, è necessario scrivere un file nella directory libraries, all’interno della propria applicazione salvandolo con un nome a piacere preceduto dal suffisso p4a_mask_. All’interno del file deve essere presente una funzione nominata nello stesso modo. Vediamo un esempio di albero di directory:

P4A
 |
 -- applications
      |
      -- gestione_fatture
          |
          -- objects
          |
          -- uploads
          |
          -- libraries  <-- qui! 

All’interno di questa directory posizioniamo i due file:

  • p4a_mask_savewithoutpain.php
  • p4a_mask_deletebreezily.php

Le due funzioni richiamano i metodi saveRow() e deleteRow() all’interno del costrutto PHP: try ... catch, intercettando gli eventuali errori e fornendo un messaggio di avviso senza interrompere il flusso del programma. Quando si utilizzano query di aggiornamento e cancellazione, è sempre bene fare dei controlli perché se si utilizzano tabelle InnoDB si rischia di rompere l’integrità referenziale, se si utilizzano indici di tipo UNIQUE si rischia la duplicazione, e così via. D’altra parte la ricerca tramite SELECT dei vincoli e/o delle chiavi duplicate è spesso troppo laboriosa, anche se in certi casi necessaria.
NOTA: try ... catch funziona solo con P4A 3.x, in quanto è disponibile a partire da PHP5.

Vediamo il codice di p4a_mask_savewithoutpain.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Funzione: Salva senza soffrire :-)
function P4A_Mask_saveWithoutPain($mask, $params)
{    
    list($source) = $params;
    $mask->source = $source; 
	  try{ 
      $mask->source->saveRow();       
    } catch (Exception $e) {  
 
      $res = $e->getMessage();
      preg_match("/\SQLSTATE\[(.+)\]/",$res, $results);
 
      if ($results[1] == '23000') {
        $msg = "Errore: chiave duplicata!";
      }
      else {
        $msg = "Si e' verificato un errore nel salvataggio dei dati";
      }    
      $mask->warning($msg);  
    }
}

Le prime due righe di codice che ho spudoratamente copiato dagli helper di sistema, servono a ricevere gli oggetti: maschera chiamante e data source. Con try si tenta una saveRow(), se fallisce viene valutato il messaggio di errore per lo sbaglio più frequente: chiave duplicata. Negli altri casi viene segnalato un errore generico.

Questo, invece è il codice di p4a_mask_deletebreezily.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Funzione: cancella allegramente... :-)
function P4A_Mask_deleteBreezily($mask, $params)
{
    list($source) = $params;
    $mask->source = $source;
	  try{ 
      $mask->source->deleteRow();       
      } 
    catch (Exception $e) {  
      $res = $e->getMessage();
      $pos = strpos($res, "General error: 1451"); // Errore di rottura integrita'
                                                  // referenziale
      if ($pos==false) {
        $msg = "Si e' verificato un errore nella cancellazione";
      }
      else {
        $msg = "Errore: impossibile cancellare un record correlato";
      }
      $mask->warning($msg);  
    }
}

In questo caso ho valutato solo errori di cancellazione di record che abbiano un vincolo di relazione. Non ho ricercato nella descrizione il codice SQLSTATE perché in SQL lo stesso codice accomuna diversi casi. E’ quindi necessario utilizzare il numero di errore MySQL, che nel caso di rottura dell’integrità referenziale è 1451.

Ecco infine il codice da aggiungere alla nostra ipotetica maschera: (gli helper vengono richiamati omettendo il suffisso p4a_mask_ )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class test_fatture extends p4a_base_mask
{
 public function __construct()
  {
    parent::__construct();
    // DB Source
    $this->build("p4a_db_source", "source")
    // .....
    // codice della maschera...
    // .....
  }
  public function saveRow ()
  {
    $this->saveWithoutPain($this->source);
  }
  public function deleteRow()
  {
    $this->deleteBreezily($this->source);
  }
}

Infine vorrei ricordare gli helper di sistema che, grazie agli autori, ci fanno risparmiare parecchio lavoro!

  • P4A_Field_loadSelectByArray – Imposta un campo di tipo select da un array.
  • P4A_Field_loadSelectByTable – Imposta un campo di tipo select da una tabella.
  • P4A_Frame_anchorTabPane – Ancora un tab pane ad una maschera.
  • P4A_Mask_useToolbar – Imposta una toolbar e la ancora alla topbar.
  • P4A_Mask_setTableAsSource – Imposta un DB_source da una tabella.
  • P4A_Mask_constructSimpleEdit – Costruisce un’intera maschera! (disponibile in P4A 3.2.0)

Conclusioni

Credo che, grazie a strumenti di questo tipo, si può contribuire nello sviluppo Open Source, con poco sforzo e molto vantaggio per tutta la comunità!

Riferimenti ed approfondimenti:


Andrea ha scritto:

Ciao Mario,
complimenti per i gli helper, molto utili :)

Solo un appunto, io eviterei questa linea di codice
$mask->source = $source;
In pratica cosi’ facendo rifai un’assegnazione della variabile source della maschera che non serve: nel tuo esempio quando chiami $this->build(“p4a_db_source”, “source”) hai gia’ popolato la variabile source con un db_source.
Tra l’altro nel tuo esempio non da’ alcun fastidio ma in un caso teorico potrebbe essere un problema perche’ se l’utente inizializzasse la variabile source con un oggetto diverso chiamando quella riga di codice lo andresti a sovrascrivere con risultati del tutto inaspettati (immagina un utente che nella maschera abbia scritto $this->build(‘p4a_table’,’source’)).

Per risolvere il tutto basterebbe cambiare il codice in questa maniera:

try{
$source->saveRow();

Spero di esserti stato utile, a presto
Andrea

ps spero di non aver postato questo commento due volte, la prima volta il server mi ha dato errore… nel caso ignorami ;)
30.12.08 12:04
Mario Spada Author Profile Page ha scritto:

Grazie Andrea per la correzione, hai perfettamente ragione!
30.12.08 19:55