.. _tutorial_fatturazione/print: Lezione 9: Creazione stampa fattura =================================== In questa lezione vedremo come definire la stampa della fattura e come rendere questa stampa eseguibile direttamente dalla pagina delle fatture. .. raw:: html
Una delle problematiche più faticose da gestire nelle applicazioni gestionali è quella delle stampe. In Genropy viene utilizzato un sistema per creare con poche istruzioni delle stampe html che poi vengono trasformate in PDF da **wkhtmltopdf**. Per utilizzare quindi le procedure di stampa di Genropy accertarsi di aver fatto le :ref:`installazioni necessarie` Ma prima di incominciare è necessario illustrare in estrema sintesi la sintassi di **Genropy** per definire le stampe html. GnrHtml: layout, row, cell --------------------------- Essenzialmente la definizione di una stampa in **Genropy** si ottiene usando uno dentro l'altro elementi ``layout``, ``row`` e ``cell``. La sintassi è del tutto simile a quella che abbiamo già visto nella definizione del model del database o nella creazione delle pagine :ref:`Classe View` . :: layout = mypage.layout() row = layout.row(height = 10) cell = row.cell(value='Ciao', width = 20) Vediamo in breve le regole per usare questi elementi. - Un elemento ``layout`` è un oggetto che può contenere solo elementi ``row``. Cioè un layout può essere diviso orizzontalmente in fasce orizzontali delle quali si indica l'altezza con il parametro ``height``. - Se si omette l'altezza di una ``row`` questa sarà considerata *elastica* andando ad occupare tutto lo spazio disponibile calcolato per differenza rispetto a quello delle altre righe. - Qualora più righe fossero prive di altezza lo spazio libero verrà ripartito in parti uguali suddiviso sul numero di righe *elastiche*. - Ogni elemento ``row`` puo contenere solo elementi ``cell``. - Agli elementi ``cell`` bisogna assegnare una larghezza con il parametro ``width``. Così come per l'altezza delle rows, se non viene indicata la ``width`` di una ``cell``, questa sarà considerata *elastica* ed occuperà lo spazio disponibile nella row. Qualora più celle non abbiano una larghezza assegnata lo spazio disponibile verrà ripartito in parti uguali fra tutte le celle senza larghezza della stessa riga. - Ogni cell a sua volta può contenere testo, un pezzo di html oppure a sua volta un altro elemento ``layout``. In questo caso il layout, a sua volta, sarà ripartito in rows e via dicendo in una modalità frattale. La risorsa stampa: mia_fattura.py ------------------------------------ Le stampe che rappresentano i dati delle table del nostro applicativo vengono definite come risorse, pertanto dovremo creare un modulo ``mia_fattura.py`` al path ``resources/tables/fattura/html_res`` creando quindi la cartella *html_res*. L'oggetto stampa lo definiamo implementando la ``Main`` che eredita da ``TableScriptToHtml``. :: from gnr.web.gnrbaseclasses import TableScriptToHtml class Main(TableScriptToHtml): maintable = 'fatt.fattura' doc_header_height = 32 doc_footer_height = 12 grid_header_height = 5 Come attributi di classe indichiamo: - **maintable** , ovvero la table di database i cui record verrano stampati dalla risorsa. In questo caso è ``fatt.fattura`` - **doc_header_height**, ovvero l'altezza in millimetri della testata del documento - **doc_footer_height**, ovvero l'altezza in millimetri del piede del documento - **grid_header_height**, ovvero l'altezza in millimetri della testata della griglia, dove cioè metteremo i nomi delle colonne Nella classe ``TableScriptToHtml`` sono definiti già molti valori di default per questi attributi, pertanto nella nostra risorsa andiamo a impostare solo quelli che desideriamo ridefinire. La testata della fattura ------------------------- Cominciamo dalla parte superiore della stampa, ovvero la testata del documento che viene definita implementando il metodo di hook ``docHeader``. :: def docHeader(self, header): layout = header.layout(name='doc_header', margin='5mm', border_width=0) row = layout.row() left_cell = row.cell(width=80) center_cell = row.cell() right_cell = row.cell(width=80) self.datiFattura(left_cell) self.datiCliente(right_cell) Vediamo quindi che il metodo docHeader riceve un elemento cell, già dell'altezza giusta e nella posizione giusta e noi andremo a riempirlo di contenuto grafico. Iniziamo quindi subito a definire un *layout* a cui diamo ``border_width=0`` poiché non vogliamo vederne il bordo. Qui definiamo dunque una ``row`` che andremo a suddividere in 3 spazi creando 3 elementi ``cell``: - quella a sinistra la useremo per mettere i dati della fattura, cioè la data e il numero di protocollo - quella al centro non le passiamo il parametro della larghezza per far sì che si espanda al massimo facendo da elemento spaziatore - quella a destra invece andrà a contenere i dati di intestazione relativi al cliente :: def datiFattura(self, c): l = c.layout('dati_fattura', lbl_class='cell_label', border_width=0) r = l.row(height=8) r.cell(self.field('data'), lbl='Data') r = l.row(height=8) r.cell(self.field('protocollo'), lbl='N.Fattura') def datiCliente(self, c): l = c.layout('dati_cliente', border_width=0) l.row(height=5).cell('Spett.') l.row(height=5).cell(self.field('@cliente_id.ragione_sociale')) l.row(height=5).cell(self.field('@cliente_id.indirizzo')) comune = self.field('@cliente_id.@comune_id.denominazione') provincia = self.field('@cliente_id.provincia') l.row(height=5).cell('%s (%s)' % (comune, provincia)) def defineCustomStyles(self): self.body.style(""".cell_label{ font-size:8pt; text-align:left; color:gray; text-indent:1mm;} .footer_content{ text-align:right; margin:2mm; } """) In ciascuno di questi metodi creaiamo dei *layout* che ci permetteranno di organizzare i nostri dati in righe e celle. Nel blocco dei dati fattura creiamo due righe, ciascuna delle quali contiene una cella. In queste celle andremo a scrivere la data della fattura e il protocollo. Da notare che si accede ai dati del record di fattura usando il metodo ``field``, che in analogia con quello omonimo usato nel formbuilder (nella definizione delle form :ref:`Modifiche a th_cliente` ), provvede a leggere il dato e a formattarlo nel modo più opportuno a seconda del *dtype*. Si può notare che ``cell``, proprio come il field del formbuilder accetta un parametro ``lbl`` che permette di dare un'etichetta alla cella. La classe css associata alle etichette di un layout è stata passata nel parametro ``lbl_class`` quando abbiamo definito il layout. Nel metodo di hook ``defineCustomStyles`` posso definire con l'elemento ``style`` tutte le classi **css** di cui avrò bisogno nella stampa. Nel blocco **datiCliente** creaiamo invece tre righe e nell'ultima andiamo a scrivere su tre righe i dati del cliente. Si può anche vedere che con il metodo ``field`` non solo possiamo accedere ai dati del record fattura, ma possiamo leggere anche i campi in relazione usando il path separato da punti che percorre le relazioni ascendenti con la sintassi. - ``@cliente_id.ragione sociale`` - ``@cliente_id.@comune_id.denominazione`` Definizione della griglia delle righe fattura ---------------------------------------------- Questo tipo di stampa, che eredita da ``TableScriptToHtml`` prevede di riferirsi ad un record principale, che in questo caso è quello della fattura stampata e ad una selezione di righe ad esso collegate. Questa selezione viene rappresentata con la griglia centrale. Nel nostro caso questa conterrà le righe fattura. Nella definizione di questa griglia è innanzitutto fondamentale fornire una descrizione delle colonne. Per fare questo si usa una sintassi del tutto simile a quella che abbiamo già incontrato nella definizione delle *view* ( :ref:`Lezione 2` ) dei ``tablehandler``. Si deve infatti implementare il metodo ``gridStruct`` in modo analogo a come veniva implementato il metodo ``th_struct``. :: def gridStruct(self,struct): r = struct.view().rows() r.fieldcell('prodotto_id',mm_width=0, name='Prodotto') r.fieldcell('quantita',mm_width=10) r.fieldcell('prezzo_unitario',mm_width=20) r.fieldcell('aliquota_iva',mm_width=20) r.fieldcell('prezzo_totale',mm_width=20, name='Totale') r.fieldcell('iva',mm_width=20) La sola differenza è che qui si specifica come parametro ``mm_width`` che indica la larghezza della colonna invece di ``width``. Questo a ricordarci che le larghezze qui sono sempre espresse in millimetri. L'altra cosa da fare è definire quale selezione di record deve essere letta dal database. Il modo più semplice per farlo è passare i parametri di query necessari per leggere i record desiderati. Questo si fa implementando il metodo ``gridQueryParameters``, il quale ritorna il dizionario dei parametri necessari. :: def gridQueryParameters(self): return dict(relation='@righe') Questo caso è particolarmente semplice perché ci basta indicare nel parametro ``relation`` il relation_name che collega la table ``fattura`` a quella ``fattura_riga``. In alternativa avrei potuto essere più esplicito e fornire la *table* sulla quale fare la query e la *condition*. :: def gridQueryParameters(self): return dict(table='fatt.fattura_riga', condition='$fattura_id = :f_id', f_id=self.record['id']) Noterete che in questi parametri non ho definito **columns**, infatti le colonne sono già implicitamente definite da ``gridStruct``, nel quale ho già dichiarato quali colonne voglio visualizzare. Piede fattura -------------- Questo tipo di stampa, prevede anche la possibiltà di definire sotto la griglia una zona a fondo pagina. Nel nostro caso la useremo per scrivere i totali della fattura. Per fare questo è sufficiente implementare il metodo di hook ``docFooter`` :: def docFooter(self, footer, lastPage=None): l = footer.layout('totali_fattura',top=1, lbl_class='cell_label', content_class = 'footer_content') r = l.row(height=12) r.cell() r.cell(self.field('totale_imponibile'),lbl='Imponibile', width=20) r.cell(self.field('totale_iva'),lbl='IVA', width=20) r.cell(self.field('totale_fattura'),lbl='Totale', width=20) Come abbiamo già fatto nella testata definiamo un layout, con una riga e delle celle, nelle quali scriveremo i totali. Notiamo che nel parametro ``content_class`` del layout, utilizziamo una delle due classi definite in precedenza. Il parametro ``content_class`` serve a dire quali classi css devono essere associate al contenuto delle celle che si trovano nel layout. Abbiamo dunque definito la nostra risorsa di stampa, adesso per poterla vedere dobbiamo definire anche una risorsa di tipo *batch* che ci permetterà di eseguire la stampa dalla pagina delle fatture. La risorsa batch di stampa: stampa_fatture.py ---------------------------------------------- Andiamo a definire il modulo ``stampa_fatture.py`` che si deve collocare nella directory ``resources/tables/fattura/print`` creando quindi la cartella *print*. Nella directory *print* vanno collocate quelle risorse che servono a lanciare l'esecuzione delle stampe. :: from gnr.web.batch.btcprint import BaseResourcePrint caption = 'Stampa Fatture' class Main(BaseResourcePrint): batch_title = 'Stampa fattura' html_res = 'html_res/fattura_stampa' Questa classe eredita da ``BaseResourcePrint`` che, a meno di non ridefinire alcuni metodi di hook, si occupa di invocare la stampa definita nel parametro ``html_res`` su ciascun record alla selezione corrente. E nel caso, se sulla selezione corrente a video ci fossero elementi evidenziati, solo ad essi. La variabile globale ``caption`` serve a fornire il nome con cui questo batch di stampa verrà mostrato dal menu delle stampe disponibili. L'attributo ``batch_title`` indica invece il titolo che sarà attribuito al batch nel monitor laterale dei batch in esecuzione. .. image:: /_static/images/stampa_fatture.png :width: 500px :align: center In questo esempio non vogliamo ridefinire alcun metodo di hook e ci atteniamo al comportamento del batch di stampa generico. Proviamo a lanciare la nostra stampa su una selezione di fatture dalla pagina delle fatture. .. image:: /_static/images/2019-09-30-152938.gif :width: 900px :align: center Carta Intestata ----------------- **Genropy** offre nel package adm un comodo modo di generare carte intestate. Nel video viene mostrato come generare una carta intestata da utilizzare nella fattura. .. raw:: html
Chiamiamo la nostra carta intestata con il nome **carta_intestata** e poi andiamo ad aggiungere alla nostra riga di stampa la seguente riga :: templates = 'carta_intestata' Proviamo a lanciare dunque la stampa e vediamo che ha la carta intestata che abbiamo creato. .. image:: /_static/images/2019-09-30-153851.gif :width: 900px :align: center .. raw:: html
**Allegati:** - `mia_fattura `_ - `stampa_fatture `_