Lezione 9: Creazione stampa fattura

Installazioni per stampa

Una delle problematiche più faticosa da gestire nelle applicazioni gestionali è quella delle stampe. In Genropy viene utilizzato un sistema per creare con poche istruzioni dei layout html anche molto complessi che poi vengono trasformati in PDF da wkhtmltopdf. Per utilizzare quindi le procedure di stampa di Genropy bisogna installare pyPdf e wkhtmltopdf.

Hint

Nel video viene mostrato come procedere per un installazione su Ubuntu 64 bit.

Hint

Installando pyCups è anche possibile gestire delle stampe utilizzando direttamente le stampanti di rete collegate al server, ma questo esula dallo scopo del tutorial.

La risorsa batch di stampa: stampa_fatture.py

Per stampare le fatture procederemo a creare 2 risorse. La prima è il modulo stampa_fatture.py che si deve collocare nella directory resources/tables/fattura/print creando la cartella print. Questo tipo di risorse servono a dare parametri e a personalizzare i vari aspetti relativi al batch di stampa.

  • Nome del batch da visualizzare nel menu di stampa
  • Tag di autorizzazione necessari per eseguire il batch
  • La selezione dei record da stampare
  • Generare il termometro che mostra i progressi del batch
  • Definizione eventuali ulteriori parametri da richiedere al momento dell'esecuzione del batch

nel quale devo definire una classe Main che eredita da BaseResourcePrint.

Nella stampa d'esempio ci limitiamo a definire alcune variabili di classe tra cui la principale è html_res='html_res/ fattura_stampa'.

from gnr.web.batch.btcprint import BaseResourcePrint

caption = 'Stampa Fatture'
tags = 'user'
description = 'Stampa fatture selezionate'


class Main(BaseResourcePrint):
    batch_prefix = 'ft'
    batch_prefix = 'st_fatt'
    batch_title = 'Stampa fattura'
    batch_immediate = 'print'
    batch_delay = 0.5
    html_res = 'html_res/fattura_stampa'

La risorsa layout stampa: fattura_stampa.py

Parliamo quindi della seconda risorsa da definire, che sarà il modulo fattura_stampa.py collocata in resources/tables/fattura/html_res creando, a sua volta, la cartella html_res.

Qui andiamo a definire una classe Main che eredita da TableScriptToHtml.

from gnr.web.gnrbaseclasses import TableScriptToHtml
CURRENCY_FORMAT='#,###.00'

class Main(TableScriptToHtml):
    maintable = 'fatt.fattura'

    def main(self):
        page = self.getNewPage()
        principale = page.layout('principale', top=1, left=1, right=1, bottom=1, border_width=0)

Come variabile globale indichiamo maintable, ovvero la table di database i cui record verrano stampati dalla risorsa. In questo caso è fatt.fattura.

Implementiamo il metodo main che verrà chiamato dalla procedura di stampa per ogni fattura.

Con l'istruzione page=self.getNewPage() otteniamo un oggetto che rappresenta la pagina bianca. In questo oggetto definiamo un layout principale dandone la posizione relativa.

Tutte le misure sono in millimetri e in questo caso, rispetto alla pagina, il layout principale dista 1mm da ogni lato. Ovviamente la pagina ha dei suoi margini che sono definiti esternamente.

La definizione di ogni stampa comincia sempre definendo sulla page un elemento layout

layout, row, cell

Essenzialmente la definizione di una stampa in Genropy si ottiene usando uno dentro l'altro elementi layout, row e cell. 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.

Lanciando la stampa dopo aver definito il layout principale otteniamo un rettangolo che rappresenta lo spazio che useremo.

Creazione struttura

Procediamo ora a definire la struttura creando 3 righe.

  • La prima di altezza prefissata ospiterà la testata
  • la seconda di altezza elastica conterrà le righe
  • la terza costituirà il piede della fattura dove collocheremo i totali.

Per leggibilità definiamo 3 metodi: testata, righe e piede.

def main(self):
    page = self.getNewPage()
    principale = page.layout(
        'principale', top=1, left=1, right=1, bottom=1, border_width=0)
    self.testata(principale.row(height=30))
    self.righe(principale.row())
    self.piede(principale.row(height=10))

La testata della fattura

Andiamo ora a definire meglio il layout della testata che si suddivide in due celle, ciascuna delle quali contiene a sua volta un layout: uno per i dati della fattura e l'altro per i dati del cliente.

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), provvede a leggere il dato e a formattarlo nel modo più opportuno a seconda del dtype.

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 con la seguente sintassi.

@cliente_id.ragione sociale e @cliente_id.@comune_id.denominazione

def testata(self, row):
    self.datiFattura(row.cell(width=80).layout('dati_fattura', top=1, left=1,
                                               right=1, bottom=1, border_width=0))

    row.cell()
    self.datiCliente(row.cell(width=50).layout(
        'dati_cliente', top=1, left=1, right=1, bottom=1, border_width=0))

def datiFattura(self, layout):
    r1 = layout.row(height=8)
    r1.cell('N.Fattura')
    r1.cell(self.field('protocollo'))
    r2 = layout.row(height=8)
    r2.cell('Data Fattura')
    r2.cell(self.field('data'))
    layout.row()

def datiCliente(self, layout):
    layout.row(height=5).cell('Spett.')
    layout.row(height=5).cell(self.field('@cliente_id.ragione_sociale'))
    layout.row(height=5).cell(self.field('@cliente_id.indirizzo'))
    layout.row(height=5).cell('%s (%s)' % (self.field(
        '@cliente_id.@comune_id.denominazione'), self.field('@cliente_id.provincia')))
    layout.row()

Corpo fattura : le righe

def righe(self, row):

    layout = row.cell().layout('righe_fattura', top=1, left=1, right=1, bottom=1,
                               border_width=.3, border_color='silver', row_border=False)
    testata_righe = layout.row(
        height=4)
    testata_righe.cell('Prodotto')
    testata_righe.cell(u'Quantità', width=20)
    testata_righe.cell('Pr.Unitario', width=20)
    testata_righe.cell('Pr.Totale', width=20)
    testata_righe.cell('Aliquota', width=20)
    testata_righe.cell('IVA', width=20)
    for righe_fattura in self.record['@righe'].values():
        r = layout.row(height=5)
        r.cell(righe_fattura['@prodotto_id.descrizione'])
        r.cell(righe_fattura['quantita'], width=20)
        r.cell(self.toText(
            righe_fattura['prezzo_unitario'], format=CURRENCY_FORMAT), width=20)
        r.cell(self.toText(
            righe_fattura['prezzo_totale'], format=CURRENCY_FORMAT), width=20)
        r.cell(self.toText(
            righe_fattura['aliquota_iva'], format=CURRENCY_FORMAT), width=20)
        r.cell(self.toText(
            righe_fattura['iva'], format=CURRENCY_FORMAT), width=20)
    testata_righe = layout.row()
    testata_righe.cell()
    testata_righe.cell(width=20)
    testata_righe.cell(width=20)
    testata_righe.cell(width=20)
    testata_righe.cell(width=20)
    testata_righe.cell(width=20)

Prepariamo per prima cosa una row alta 5 mm per le intestazioni ed in essa le celle con i titoli della griglia. Per la colonna prodotto non indichiamo alcuna larghezza così da renderla elastica.

Dopo aver creato la prima riga di intestazione definiamo un ciclo for sulle righe_fattura che leggiamo con l'istruzione self.record['@righe'].values()

Per ogni riga creiamo le celle con i dati necessari e specifichiamo le larghezze per tutte le celle tranne che per il prodotto. Al termine delle righe aggiungiamo una riga ulteriore senza altezza e ne definiamo delle celle vuote. Otterremo così che le colonne si estendano fino al piede fattura.

Aggiungiamo classi css

Vediamo ora che si può usare l'elemento style per definire delle classi css da utilizzare nella risorsa. In questo caso con l'attributo content_class.

def righe(self, row):
    self.body.style(""".grigliaRigheHeader{
                background:whitesmoke;
                color:gray;
                font-size:8pt;
                text-align:center;
            }
            .grigliaRigheCell{
                font-size:9pt;
                padding-left:1mm;
                padding-right:1mm;
            }
            .alignRight{
                text-align:right;
            }

            """)
        layout = row.cell().layout('righe_fattura', top=1, left=1, right=1, bottom=1,
                                   border_width=.3, border_color='silver',
                                   content_class='grigliaRigheCell', row_border=False)
        testata_righe = layout.row(
            height=4, content_class='grigliaRigheHeader')
        testata_righe.cell('Prodotto')
        testata_righe.cell(u'Quantità', width=20)
        testata_righe.cell('Pr.Unitario', width=20)
        testata_righe.cell('Pr.Totale', width=20)
        testata_righe.cell('Aliquota', width=20)
        testata_righe.cell('IVA', width=20)
        for righe_fattura in self.record['@righe'].values():
            r = layout.row(height=5)
            r.cell(righe_fattura['@prodotto_id.descrizione'])
            r.cell(righe_fattura['quantita'], width=20, _class='alignRight')
            r.cell(self.toText(
                righe_fattura['prezzo_unitario'], format=CURRENCY_FORMAT), width=20, _class='alignRight')
            r.cell(self.toText(
                righe_fattura['prezzo_totale'], format=CURRENCY_FORMAT), width=20, _class='alignRight')
            r.cell(self.toText(
                righe_fattura['aliquota_iva'], format=CURRENCY_FORMAT), width=20, _class='alignRight')
            r.cell(self.toText(
                righe_fattura['iva'], format=CURRENCY_FORMAT), width=20, _class='alignRight')
        testata_righe = layout.row()
        testata_righe.cell()
        testata_righe.cell(width=20)
        testata_righe.cell(width=20)
        testata_righe.cell(width=20)
        testata_righe.cell(width=20)
        testata_righe.cell(width=20)

Piede fattura

Analogamente a quanto fatto per la testata prepariamo le celle per i totali.

def piede(self, row):
    row.cell()
    l = row.cell(width=70).layout('totali_fattura', top=1, left=1, right=1, bottom=1,
                                  border_width=.3, border_color='silver', lbl_class='grigliaRigheHeader',
                                  content_class='grigliaRigheCell alignRight')
    r = l.row()
    r.cell(self.field('totale_imponibile',
                      format=CURRENCY_FORMAT), lbl='Imponibile')
    r.cell(self.field('totale_iva', format=CURRENCY_FORMAT), lbl='IVA')
    r.cell(self.field('totale_fattura', format=CURRENCY_FORMAT), lbl='Totale')

Carta Intestata

Genropy offre nel package adm un comodo modo di generare carte intestate. Nello screencast viene mostrato come generare una carta intestata da utilizzare nella fattura. Al termine di questa creazione assegniamo la carta intestata alla fattura e procediamo con la stampa finale.

Hint

A differenza di quanto mostrato nel video, la creazione della carta intestata avviene accendendo alla pagina Intestazione del menu Amministrazione sistema/ App Utility

_images/2019-05-23-122548.gif

Attachments: