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.


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 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 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 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 ( 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.

_images/stampa_fatture.png

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.

_images/2019-09-30-152938.gif

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.


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.

_images/2019-09-30-153851.gif

Allegati: