Ultima rev. 04/08/2008

Installazione di Oratio

Contenuti:

Premessa

Descrizione del Software

Distribuzione linux Debian (ed Ubuntu)

Installazione di Oratio – S.O.

Installazione di Oratio - Archivio .tar

Configurazione del Server (post-installazione)

Conclusioni installazione

Il backup automatizzato

Configurare le stampe in pdf

Uso di CygWin per connessioni remote da Win

Interfacciarci usando il PgAdmin direttamente al PostgreSQL

Interfacciarci usando OpenOffice direttamente al PostgreSQL

Creare un piccolo modulo all'interno di Oratio

Aprile 2007 Riconciliare le giacenze di magazzino con l'inventario

Sincronizzazione DB (gestione simult. di due DB)

Ordinare le scadenze delle soluzioni di pagamento

Premessa

Il presente tutorial serve da piccola introduzione all'installazione ed integrazione di Oratio(R) 2.1.6 seguito indicato per brevità come Oratio, e non al suo utilizzo. Il tutto è pensato per le aziende piccole (molto piccole), dove i dati devono comunque essere condivisi fra i dipendenti. Presso il sito http://www.oratio.it è possibile provare una demo online e consultare la documentazione relativa.

Tutto quanto segue è stato scritto pensando ad una persona “capace ad usare il computer” ma che non conosce o conosce molto poco il Linux (per quanto riguarda l'installazione) ed Oratio (per la sua configurazione).

Avviso subito che non ho riportato nessuna indicazione sull'utilizzo di Oratio per due motivi:

  1. Non mi intendo di contabilità e di tante altre cose: potrei comunque dare solo indicazioni generali, approssimative e poco affidabili.

  2. Il pacchetto Oratio è stato creato da gente che ha lavorato gratuitamente: questo significa che non ha preso soldi ma che deve comunque “mantenersi”. Dal mio punto di vista questo è il modo più onesto di vendere: il cliente paga il servizio che le persone gli offrono, non il software (che nichilisticamente neppure esiste!). Raccomando quindi a non disdegnare l'abbonamento all'assistenza, anche perché questo garantirà il futuro evolversi di Oratio.

Specifico che non ho nessun legame con la Proxima Centauri, e che tutto quanto segue è da realizzarsi a rischio e pericolo di chi intende provarlo, quindi saranno bene accette critiche (costruttive) sul presente documento, e nelle mie possibilità vedrò di rispondere ad eventuali domande, ma escludo di potere dare qualsiasi forma di assistenza professionale sia per questioni di tempo che di competenza (n.b: l'articolo è stato scritto nel tempo libero (cioè di notte!) e ripreso più e più volte: facilmente risulteranno visibili dei cambiamenti sia di stile che di tono).

La configurazione di prova è la seguente:

Software necessario:

N.B: tutti i software citati, ad eccezione di M$Win, sono liberamente scaricabili ed utilizzabili in qualsiasi azienda.



Gli obiettivi finali che cercherò di illustrare sono:

  1. Installare Oratio

  2. configurare le stampe in pdf

  3. interfacciarci usando il PgAdmin direttamente al PostgreSQL (poi vedremmo perché potrebbe tornare utile)

  4. interfacciarci usando OpenOffice direttamente al PostgreSQL (idem c.s.)

  5. creare un piccolo modulo all'interno di Oratio

Disclaimer: il presente è da considerarsi rilasciato sotto la GNU FDL (http://it.wikipedia.org/wiki/GNU_Free_Documentation_License http://www.gnu.org/copyleft/fdl.html). Tutti i marchi citati sono di proprietà dei rispettivi produttori. Tutte le operazioni indicate sono sotto la responsabilità di chi le esegue e l'autore non si assume nessuna responsabilità per danneggiamenti e/o malfunzionamenti. Il tutto deve considerarsi altamente sperimentale e privo di garanzie di funzionamento. Buon lavoro a tutti!

2007 Pittau Mosè aka Mox, moxmox@infinito.it.

Descrizione del software

Il software Oratio è un fork (cioè è un progetto nato da un'altro applicativo in sviluppo che poi ha proseguito per una strada tutta sua – la pratica è perfettamente legale nel mondo dei programmi OpenSource) di Sql-Ledger(TM), ma attualmente le due distribuzioni sono sufficientemente differenti per potere essere ritenute due prodotti diversi, tuttavia se provate uno dei numerosi demo on-line di quest'ultimo noterete non poche rassomiglianze. Rispetto alla sua base canadese, Oratio si distingue per un ottima traduzione ed adattamento al mondo Italiano. Pur non avendo tutta la modularità di altri prodotti OpenSource come TinyErp(TM), Oratio può benissimo essere adottato “di base” alle necessità delle piccole aziende, e sinceramente lo ritengo molto meglio di tanti prodotti realizzati “su misura” dietro cospicuo pagamento e di tanti commerciali.

Il suo funzionamento si basa su uno schema piuttosto semplice:



  1. Nel server (indicherò con server il computer che mette a disposizione i suoi servizi) gira un database (PostgreSQL) che è in sostanza un sistema che riesce a gestire in maniera sorprendentemente efficiente una serie di dati (sorprendentemente sicuramente per tutti i piccoli uffici abituati ad usare Excel per

  2. registrare ordini, fatture, ecc.). Il database gestisce oltre ai suoi dati anche un sistema di accesso con utenti abilitati alla connessione, alla sola lettura, ecc., nonché molte altre cose più complesse (es. i trigger: funzioni che vengono eseguite automaticamente “dal database” in particolari circostanze).

  3. Il Database, oltre che accessibile direttamente (disponendo delle opportune “credenziali”) può essere interfacciato ad Oratio, che è una serie di script realizzati in Perl (e Javascript). Oratio necessita quindi di accedere al database. Alla configurazione di Oratio verrà stabilita una connessione, che resterà poi memorizzata, e verrà creato uno o più dataset da Oratio all'interno del Database. Il dataset sarà una serie di “tabelle” vuote corredate da opportune restrizioni (es. non si può cancellare un fornitore se il fornitore è presente nella tabella degli ordini) in maniera tale da garantire “l'integrità referenziale” dei dati (cioè come dice il termine, le restrizioni gestite dal database – se ben congeniate - saranno tali da garantire che le tabelle non facciano mai riferimento a qualcosa di inesistente, di non più esistente o che non volutamente è cambiato nel tempo) e di alleggerire anche il programma esterno (es. sulla tabella degli articoli non sarà possibile inserire due codici uguali, ma il controllo sarà fatto direttamente dal database e non da Oratio). Di per se stesso il PostgreSQL è un “servizio” disponibile anche dall'esterno del computer che noi denominiamo server, cosa che come vedremmo avanti ci tornerà utile! Il fatto che le “tabelle” siano gestite direttamente dal “servizio” e non da noi comporta molti vantaggi in velocità, in particolare per l'uso di strutture ad albero, che nei calcolatori sono molto usate. Quest'ultima informazione ci dice anche un'altra cosa importante: i dati conservati nel database non sono direttamente accessibili ad occhio nudo “saltando” il server!

  4. Il Perl è un linguaggio dinamicamente per molti versi simile al Java: viene precompilato al volo ed eseguito molto, dico molto, velocemente. Rispetto ad altri linguaggi compilati il Perl ha qualche marcia in più: è fondamentalmente dedicato alla manipolazione del testo, e noi di testo ne inseriremmo parecchio. Sostanzialmente Oratio è diviso in moduli (sono leggibili i nomi dei moduli nella barra del firefox in basso a sinistra quando si scorrono i diversi comandi del menu).

  5. Oratio a sua volta registrerà un certo numero di utenti: noi ci collegheremmo ad Oratio con uno di questi utenti, ed Oratio si collegherà al database con l'utente autorizzato all'inizio della configurazione (notare come adesso si sia “smistato il sistema di accesso: dobbiamo essere autorizzati ad accedere ad Oratio, che a sua volta deve essere autorizzato con un utente particolare ad accedere al database – questo meccanismo sarà di fondamentale importanza quando dovremmo capire come configurare gli accessi al database e ad Oratio, in quanto quando accederemo direttamente al database “salteremmo” Oratio!).

  6. Come riportato nello schema di seguito, si possono individuare per comodità 3 livelli di moduli Oratio: quelli che accedono direttamente al PostgreSQL

  7. usando l'utente Oratio di PostgreSQL (che ricordo essere uno solo ma usato da tutti i moduli) distinti con il “.1” finale nello schema, quello che gestisce gli accessi agli utenti (multipli) di Oratio e quelli “al centro” che fanno le elaborazioni e che si riallacciano da entrambe le parti in un modulo di uscita se verso gli utenti di Oratio, in più moduli se verso l'utente di PostgreSQL. Nativamente Oratio (cioè SQL Ledger) era predisposto per girare su altri database commerciali (ricordo che PostgreSql ha licenze che permettono il suo utilizzo gratuito), e parte di quel codice sopravvive al suo interno (anche se non sono sicuro vada ancora bene!), perchè PostgreSql a quei tempi era piuttosto “immaturo” e poco affidabile.

  8. Apache è un web server, che può essere pensato come un programma che accetta le connessioni tipo “internet” e che restituisce pagine html (cioè adatte ad essere visualizzate con un programma Browser tipo Firefox). A sua volta, apache deve essere abilitato ad accettare le richieste dirette al computer sul quale gira Oratio, deve sapere inoltre che Oratio è installato e deve sapere come passargli i dati. In sostanza è (ancora!) un altro servizio che agisce da tramite fra Oratio e gli utenti. Il perchè della sua necessità è evidente: gli utenti possono essere (e nel nostro caso lo sono) fisicamente fuori dal server, ed Apache è un prodotto che riesce a gestire autorizzazioni, collegamenti ed altro in maniera autonoma (ed efficiente!) per evitare che sia il codice di Oratio a fare tutto.

  9. Il browser (io consiglio firefox) si collega ad Apache, che gestisce le richieste di tutti gli utenti connessi e le passa ad Oratio. Oratio convalida gli utenti, manipola le richieste, si connette al database per ricevere/aggiornare/inserire i dati. Quindi costruisce una pagina html “al volo” con i dati appropriati, li rende ad apache e che a sua volta provvede a renderli al computer “X” esterno al server dal quale siamo connessi. Il file “html” creato da Oratio (e per questo dicevo che il Perl è molto efficiente per lo scopo in quanto pensato per lavorare con il testo, ed il file html è un file di testo!) viene letto ed interpretato dal nostro browser Firefox, che poi ce lo mostra.

  10. E il Javascript, dirà qualcuno? Dunque, adesso dobbiamo fare solo una piccola considerazione: i dati che immettiamo nella maschera del nostro browser in realtà non sono memorizzati nel server dove gira Oratio, ma vengono conservati nella pagina html che stiamo visualizzando (sono messi in maniera tale da essere “fuori campo visivo”, cioè il browser li ignora e non li rappresenta a video anche se li memorizza). Ogni volta che chiediamo un aggiornamento i dati “rimbalzano indietro al server”, che li elabora e ci restituisce la pagina elaborata (con i nostri dati opportunamente nascosti al suo interno). Il motivo di tutto questo rimpallo è semplice: i dati vengono “mantenuti” dai client ed alleggeriscono il server (naturalmente questo ha senso se si parla di 1000 client, non due, ma è una filosofia vincente e quindi vale pena tenerla per tutte le situazioni). Quando capita che sia più opportuno fare girare il nostro computer direttamente (il client) e non Oratio, il codice deve essere eseguito sottoforma di Javascript (non tutti i computer nascono con il Perl, ma i browser hanno tutti il supporto Javascript!). Infatti se farete attenzione noterete che alcuni form in Javascript hanno un funzionamento differente dal resto (es. quello dei movimenti di magazzino). In teoria il Javascript non è mai indispensabile, ma in realtà è molto comodo per fare alcune cose, quindi sappiate che a volte quello che fate gira sul Server ed a volte no.

Spero che il tutto sia abbastanza comprensibile.

I due risvolti della medaglia di questa struttura sono:

  1. Non bisogna installare nessun software sui client: posto che abbiate un sistema operativo Da M$Win M.E. in poi, od una distribuzione linux, il sistema girerà comunque. Questo significa anche che una volta installato e configurato il sever, qualsiasi client non avrà bisogno di configurazioni e/o ulteriori installazioni e/o aggiornamenti (molto comodo per chi si incarica di “sovraintendere i computer”, anche perché l'aggiunta o la rimozione di un computer avviene in maniera trasparente senza bisogno di fare nessuna modifica). Utile anche se il responsabile del server non è sempre disponibile per operazioni di manutenzione straordinaria: o il server funziona (e la distribuzione Debian offre ottime garanzie in questa prospettiva), o non funziona Oratio su nessuno dei client (ed in un piccolo ambiente è all'ordine del giorno che accadano problemi e blocchi sui client M$Win). Oltre al fatto che in un piccolo ambiente (dove non ci si può fermare!) se un client salta (es. si brucia l'alimentatore) è sufficiente portare un altro computer (magari da casa, in attesa della riparazione del primo)e configurare la rete.

  2. Tutto il carico del lavoro viene eseguito quasi esclusivamente dal server: un computer non troppo nuovo va bene per un numero ridotto di client (direi ad occhio da un 700MHz in su, io uso un 1400 e funziona egregiamente con una scheda di rete 10/100, 256MB di ram ed un HD da 20GB (naturalmente è un computer recuperato), ma una soluzione dedicata al funzionamento in contemporanea di molti utenti richiede un server specializzato, veloce, con una discreta quantità di memoria e magari una rete cablata ad un 1G e qualche HD montato in Raid.

Distribuzione linux Debian (ed Ubuntu)

La distribuzione linux Debian è una delle meglio supportate dal punto della sicurezza d'uso e della stabilità. Questo è dovuto alla sua concezione ed al suo particolare flusso di sviluppo (tutti i programmi della distribuzione sono attentamente testati). Sicuramente esistono anche altre distribuzioni più semplici ed amichevoli da installare, ma poche garantiscono molti dei vantaggi di Debian, che a mio avviso possono essere indicati in:

Viceversa, i maggiori svantaggi sono:

Ubuntu

Comunque io adesso ho una Ubuntu Desktop, ma solo perchè ero convinto di risparmiare tempo rispetto all'installazione Debian.

Finiti i preamboli è ora di passare all'installazione!

Installazione di Oratio - Installazione di Debian (o Ubuntu)

Dal sito www.debian.org (Ubuntu.org) è possibile scaricare una versione minima di Debian alla pagina http://www.debian.org/distrib/netinst che propone due soluzioni: il cd o il floppy. Posto che abbiate un cd anche riscrivibile e che abbiate un normalissimo PC, da http://www.debian.org/CD/netinst/ scegliete l'immagine <<netinst>> stable cliccando su [i386] e masterizzandovi il file con il vostro programma preferito (il formato del file è .ISO, ed è leggibile praticamente da tutti i programmi di masterizzazione).

Adesso potete partire con l'installazione, ma se non avete mai visto un linux, il consiglio è di dare uno sguardo a http://www.debian.org/intro/about che contiene informazioni generali su cosa sia Debian e naturalmente i documenti vari consigliati proprio in questa pagina (di cui consiglio http://www.debian.org/doc/manuals/reference/index.it.html (in versione html, ma è disponibile anche in .pdf) che non copre l'installazione ma copre tutta la base Debian). Anche una ricerca su http://www.google.it con “installare debian” sicuramente non guasta: c'è solo l'imbarazzo della scelta....

Lanciando il cd avrete da seguire i passi di installazione (l'importante è che nel bios sia attivata la funzione che permette al cd di partire e che disco fisso non siano presenti dati da recuperare perché verranno cancellati) che si spiegano abbastanza bene da soli. La tastiera italiana è normalmente la “it” da 105 tasti (questa informazione vi verrà richiesta durante l'installazione e sarà molto importante per lavorare con le configurazioni).

Altro problema è quello di capire come configurare la scheda di rete per la vostra lan (la lan è la rete interna del vostro ufficio)? Se avete sottomano un M$Win seguite il percorso del menù “start->pannello di controllo->connessioni di rete” e fate click sulla vostra connessione e di seguito sulla linguetta supporto:




Nel mio caso l'indirizzo è assegnato da DHCP (che in un piccolo ufficio difficilmente si possedeva - una volta, oggi le cose sono cambiate - perché da più problemi che benefici), ma le cose importanti sono gli altri tre dati: primo, se non avete un caso come il mio dovrete scegliere un indirizzo IP diverso da tutti quelli dei computer presenti (altrimenti l'indirizzo verrà scelto per voi) in modo tale che se la subnet mask è 255.255.255.0 (come nella stragrande maggioranza delle sottoreti) l'unico numero differente sia solo il quarto (es. 192.168.1.1, allora potrò scegliere un numero che inizi con 192.168.1 e che finisca con uno compreso tra .2 e .254, quindi se avrò tre computer già collegati dovrò probabilmente scegliere il 192.168.1.4). La subnet mask ed il gateway dovranno restare uguali. Oggi come oggi, almeno la Ubuntu si configura egregiamente da sola.

La procedura di installazione richiede del tempo, ed in particolare se selezionate molti pacchetti sarà richiesto molto tempo perché la maggior parte dei pacchetti saranno scaricati dalla rete all'occorrenza (quindi scordatevi di fare fronte ad un'installazione standard usando l'ISDN o simili!). Se avete dei problemi durante l'installazione (es. se non vi viene riconosciuta la scheda di rete e quindi non potete collegarvi ad internet per scaricare o avete problemi a collegarvi perché non riuscite a saltare il firewall) potete sempre scaricare le immagini DVD (sono 3), solo che in questo caso durante l'installazione avrete un bel da fare a cambiare i DVD di volta in volta che vi verranno richiesti! Durante l'installazione vi verranno richiesti 2 utenti, uno dei quali è chiamato “root” ed è amministratore del sistema (nel vero senso, non come magari siete abituati sotto M$Win, nel senso che solo, e sottolineo solo “root” può fare tutto) ed uno è per uso normale, quindi fate attenzione alla password e appuntatevela BENE, perché se perdete quella di root bisogna fare tanti di quei giri per recuperarla che neanche immaginate!

Dunque, se alla fine siete arrivati ad un login, che esso sia su una schermata di solo testo o di grafica non importa (anzi, tante volte il server “X” che sotto linux gestisce la grafica crea problemi con le schede più nuove), avrete fatto il vostro lavoro.

Se è comparso anche il cursore del mouse significa che siete in modalità grafica, quindi spostiamoci su una shell (la schermata nera con le scritte in bianco) e prepariamoci il sistema per le nostre necessità come raccomandato da http://www.oratio-project.org/documents/it/install_oratio_GNULinux.html

Per andare su una shell di testo bisogna premere i pulsanti ctrl+alt+f1 (o f2 fino ad f6: sono aperte al login fino a 6 shell e per spostarci dall'una all'altra abbiamo bisogno di premere questi tasti, così potete avere aperte più applicazioni su più shell anche senza un sistema grafico), inseriamo il nome utente e la password (che mentre la scriviamo non vedremmo apparire neppure sotto forma di asterischi) e riceviamo le varie informazioni sul sistema e sul copyright. Per le operazioni che dobbiamo compiere abbiamo necessità di “loggarci” come utente “root”, e se avrete letto le istruzioni saprete che l'utente “root”(amministratore) non dovrebbe essere mai usato salvo che per operazioni di manutenzione straordinaria. Ultima nota sulla tastiera per i caratteri speciali: la parentesi graffa la si ottiene premendo “Alt Gr”+”[“ e “Alt Gr”+”]”, e molti altri caratteri speciali sono disponibili sempre premendo “Alt Gr” con uno degli altri tasti della tastiera.

Nel caso di Ubuntu, ricordo che l'utente root non esiste. Per eseguire un comando in modalità amministratore è necessario essere un utente abilitato all'amministrazione del sistema (e quello creato durante l'installazione lo è), e nel caso di utlizzo della shell è indispensabile ogni volta precedere il comando con “sudo ”, o se avete parecchio lavoro da fare, con “sudo -i” che vi trasforma in root.



Installazione dei pacchetti Debian

Lanciare aptitude scrivendo appunto “aptitude”: aptitude gestisce i pacchetti debian e ci permette di installare/rimuovere i pacchetti. Lo potete lanciare solo da utente “root”

    Non fate caso al fatto che sulla figura di aptitude che ho messo io ci sia una finestra di M$Win attorno alla schermata.

    Aptitude funziona anche da Ubuntu, ma in Ubuntu consiglio l'uso dei tool grafici sotto “X” Synaptic o Adept.

  1. Il Perl: dentro la voce “pacchetti installati” (premete invio per espanderla) andate a cercare “perl”

    e qui all'interno di “main” verificate di avere installato “perl”, “perl-base” e “perl-modules” (lo capirete dalla “i” alla estrema sinistra che significa “installato”).

    Se non fosse presente la “i”, allora dovrete ricercare questi pacchetti in “Pacchetti non installati” e premere il “+” in loro corrispondenza, e la riga del pacchetto selezionato diventerà verde. Finito di selezionare i pacchetti da installare premete “g” per procedere. In una nuova schermata vi verrà mostrato quali pacchetti andrete ad installare e quali saranno installati perché necessari anche se non selezionati direttamente. Premete nuovamente “g” ed attendete che il tutto sia scaricato ed installato.

  2. Installiamo Apache: come prima verifichiamo se è installato “Apache2-common” seguendo il percorso “Pacchetti installati”->”net”->”main”, e nel caso non sia installato procedete come al punto precedente.

  3. PostgreSQL: come sempre verifichiamo se è installato “postgresql” seguendo il percorso “Pacchetti installati”->”misc”->”main”, e nel caso non sia installato procedete come indicato prima.

  4. Installiamo le librerie “libdbd-pg-perl” e “libdbi-pg-perl”: le trovate dentro “Pacchetti installati”->“perl”->”main”.

  5. Installiamo per ultimo il LaTeX: cerchiamo dentro “Pacchetti installati”->“tex”->”main” i pacchetti denominati “tex-base”, “tex-bin”, “tex-extra”, e se non sono installati come sempre facciamo la procedura di cui sopra.

  6. Premete “q” e confermate per uscire da aptitude.

    Importante per la versione Oratio 2.2.9: è indispensabile installare un pacchetto aggiuntivo come raccomandato durante lo scaricamento di Oratio, il persistent-connection.

Installazione di Oratio

Adesso avrete da scaricare Oratio seguendo la procedura indicata sul sito per ottenere il pacchetto .tar.gz (un archivio compresso che può essere pensato simile al formato .zip). Come normale soluzione, se avete installato “X” dentro il server linux potete scaricare il pacchetto navigando con uno dei tanti browser disponibili al suo interno come siete abituati in M$Win (ne indico due giusto per completezza: epiphany di Gnome o konqueror di Kde, anche se consiglio l'uso sia sotto Win che sotto Linux di Firefox), altrimenti potete sempre scaricarlo usando un'altro pc e trasferendolo su un cd (riscrivibile per non buttarlo via). Se usate il cd, sappiate che questo normalmente deve essere “montato” usando un comando da dentro una shell dell'utente “root” (sotto interfaccia grafica può essere montato anche automaticamente, in particolare se usate Ubuntu): il comando normalmente è “mount /media/cdrom” o “mount /media/cdrom0” o “mount /cdrom”. Resta anche le penna usb per effettuare il trasferimento al posto del cd, ma se non avete installato “X” vi verranno a mancare sicuramente una serie di automazioni che rendono il suo uso non proprio intuitivo per i non “linuxofili”, ed il suo mount può essere una cosa molto più complicata del previsto. Se state usando “X” (cioè siete in modalità grafica) potete sempre arrangiarvi tra le finestre come fate normalmente con M$Win. Se invece avete scelto di non installare il servizio “X” sul server (che tra l'altro serve a ben poco nel caso in cui il computer funzioni propriamente solo da server) procedete come segue: copiate il file dentro la cartella usr/local con la seguente procedura avviata da utente root nella shell: cd “nome cartella” (es. da cd: “cd /media/cdrom0/” o dalla cartella root “cd /root/desktop/scaricati/); copiate il file di oratio (con “cp oratio-2.1.6.tar.gz /usr/local”). Se volete operare usando un sistema più comodo, potete valutare una valida alternativa ai comandi digitati da shell: il Midnight Commander

(da installare usando aptitude: il pacchetto si chiama “mc” e si lancia come il comando “mc” da una shell), il cui uso è piuttosto semplice: per fare la copia con mc dovete selezionare il(i) file(s) usando “ins”, per spostarvi nelle directory usate “invio” battendolo sulle dir o sul collegamento alla directory precedente (“../”) e per spostarvi tra le liste di sinistra e destra usate il “tab”.

Comunque lo abbiate copiato, adesso è meglio procedere con la shell dell'utente “root”(sotto debian: “su root”, sotto Ubuntu: “sudo -i”): spostatevi su /usr/local (“cd /usr/local”) e decomprimete l'archivio (“tar xzvf oratio-2.1.6.tar.gz”) (o se proprio volete potete usare Midnight Commander anche per questo dal menu richiamato con “F2”).

Configurazione del Server (post-installazione)

E' il momento di iniziare con le configurazioni, e per questo potrete servirvi di un editor (disponibile in tutte le distribuzioni Debian) da richiamare da shell con il comando “nano”

(la schermata standard di linux appare con i colori invertiti, cioè con il fondo nero, ma non abbiate a preoccuparvene).

Il suo uso, come per gli altri programmi visti fin'ora è abbastanza intuitivo per essere solo accennato: il simbolo ^ indica la pressione del tasto “ctrl” in combinazione con il tasto indicato alla sua destra, ed allora “ctrl”+”X” permette di uscire dal programma e “ctrl”+”R” serve per caricare un file precedentemente salvato con “ctrl”+”O”.

In Debian, come nella maggior parte delle distribuzioni Linux, tutti i file di configurazione sono registrati nella directory “/etc”. Non ne ho parlato prima, ma come avete visto i riferimenti alle directory iniziano tutti con “/” perché questa è la radice di tutte le directory e tutto si trova sotto di lei, mentre per paragone con il mondo M$win possiamo avere più unità chiamate “C:\”, “D:\”, ecc che corrisponderebbero ognuno ad un “/” directory-radice separata. Altra peculiarità dei sistemi linux è la differenza tra maiuscole e minuscole in tutto quello che scriviamo (file di testo, comandi da shell, ecc.) quindi occhio a ricopiare esattamente tutto uguale, sia i simboli che le parentesi. Altra caratteristica del Debian (ma non di tutti i Linux) è che in generale, una volta configurato un servizio come Apache o PostgreSQL, in Linux non è mai necessario spegnere, basta riavviare il servizio come indicato più avanti.

  1. Configurazione di Apache: come indicato in precedenza Apache deve essere “informato” della presenza di Oratio. Sono necessari solo pochi cambiamenti, ma è bene notare che qualcosa cambia dalle istruzioni “originali” di Oratio. Innanzitutto abbiamo installato Apache 2 e non Apache, quindi i file corretti sotto Debian da editare sono (usando nano) “nano /etc/apache2/httpd.conf”, di cui potete vedere le modifiche di seguito (in questo caso potete vedere che ho aggiunto un riferimento alla modifica per Oratio aggiungendo il riferimento ad un suo file di configurazione).

    Se siete “anglofoni” avrete letto che questo file è presente per questioni di compatibilità con il vecchio Apache sui commenti al suo interno (tutte le righe che iniziano con le croci-incrociate (“#”) sono dei commenti, quindi sentitevi liberi di aggiungere che avete apportato dei cambiamenti come ho fatto io)

    In sostanza il contenuto di questo file dice solamente che è necessario andare a leggere un altro file chiamato oratio-httpd.conf.....

    Dobbiamo quindi creare il file :“nano /etc/apache2/oratio-httpd.conf”

    Cambiamogli i permessi (vedete avanti per il suo significato) con il comando “chmod +rwrr /etc/apache2/oratio-httpd.conf”. Per verifica lanciamo il comando “apache2ctl -t” che dovrebbe rispondere con un “Syntax OK” (è un test dei files di configurazione). Ora non resta che riavviare il servizio con il comando “/etc/init.d/apache2 restart” a cui ci verrà risposto “Forcing reload of web server: Apache2.” (ricordatevi sotto Ubuntu che tutti o quasi i comandi sotto Ubuntu richiedono i permessi di amministratore: anteponente sempre “sudo “ o non funzioneranno) e tornerà il cursore. Se così non fosse controllate bene cosa avete modificato e dove lo avete salvato (e mi raccomando che non cambi neppure una virgola o una maiuscola, il linux è intransigente su questo!)

  2. Configurazione di PostgreSQL: idem come al primo punto, ci sono alcune modifiche da apportare alla configurazione di questo servizio, modifiche che differiscono da quanto riportato sulle istruzioni. In particolare c'è una verifica importante da valutare sui permessi di accesso a PostgreSQL. Lanciamo “nano /etc/postgresql/postgresql.conf” e decommentiamo la linea dove vedete di seguito il cursore rimuovendo il “#”

    Questo ci servirà più avanti per accedere al PostgreSQL anche senza passare da Oratio, in particolare ci permetterà di usare connessioni “tcp” che possono essere fatte fuori dal Server. Lasciate tutto il resto inalterato (sconsiglio di fare test a questo punto), salvate, uscite e lanciate un altro “nano /etc/postgresql/pg_hba.conf”. Questo file stabilisce chi e come si può connettere al servizio PostgreSQL, quindi è giusto che avvisi prima che la soluzione da me presentata non garantisce sicurezza per gli ambienti a rischio, ma per un ufficio piccolo, dove la rete interna è ben protetta è una soluzione veloce e pratica. Spostatevi avanti nel file fino a raggiungere la sezione riportata ed osservate le seguenti modifiche della mia configurazione:

    come potete notare ci sono delle righe che iniziano con “# >>>>” che avevo inserito ai tempi della configurazione nella sezione delle connessioni da “IPv4”: queste righe sono relative a due sistemi di identificazione in cui la prima garantisce l'accesso dalla rete 192.168.10.x a tutti i computer e la seconda (quella che termina con “ident sameuser” ed è qui inutile) serve per un accesso più sicuro, ma richiede un'opportuna configurazione di un altro file (se avete bisogno di più sicurezza perché magari non avete un firewall fatto da un computer apposito (o peggio ancora lo avete sotto M$Win) vi consiglio di controllare la documentazione ufficiale per capire come operare).

    Diciamo che per effettuare delle prove la prima soluzione è più che sufficiente, quindi dovrete inserire solo la riga che finisce con “trust” (quella sotto il commento che dice che garantisce l'accesso a TUTTI). Il numero della sottorete (nel mio caso 192.168.10.1) finisce per tutti con “.1” e si tratta dello stesso che abbiamo in precedenza ricavato da un computer M$Win durante l'installazione del Debian, e quella affianco è la subnet mask. Cambiate questi numeri con il vostro caso (per esempio “host all all 192.168.1.1 255.255.255.0 trust”).

    Non ci resta che riavviare il servizio con “ /etc/init.d/postgresql restart” a cui ci verrà risposto prima “Stopping PostgreSQL database server: autovacuum postmaster.” e poi “Starting PostgreSQL database server: postmaster autovacuum.”.

    Nel caso di Ubuntu e Postgresql 8.3, ho avuto problemi aggiuntivi perché è stato introdotto anche un altro identificatore per il funzionamento in localhost, quindi il file nuovo e correttamente funzioanante è composta dalle seguenti tre voci non commentate:

    host all all 127.0.0.1 255.255.255.255 trust

    host all all 127.0.1.1 255.255.255.255 trust

    host all all 192.168.10.1 255.255.255.0 trust

  3. Creazione di un utente: abbiamo necessità di creare un utente, ma attenzione: per i nostri scopi è meglio creare anche un utente Debian, non solo quello di PostgreSQL! Eseguite il comando “adduser oratio” per creare il nuovo utente (verrete informati che verrà creata una cartella dedicata al nuovo utente e vi verrà richiesta la password due volte).

    Adesso creiamo un utente postgresql come indicato sul manuale Oratio: “su postgres”, seguito da “createuser -d -P oratio” a cui risponderete con la stessa password dell'utente di Debian e con “y” sia per la possibilità di fargli creare nuovi utenti (solo di PostgreSQL) che di fargli creare nuovi dataset. Questo sistema non funziona sotto Ubuntu (il “su postgres”, perché l'utente postgres non ha inizialmente password!), ma bisogna fare prima “sudo -i” per trasformarsi in root, e poi si può procedere come da manuale usando p.es. anche i comandi riportati nel manuale “su - .....”. Adesso facciamo una prova con il comando “su oratio” a cui dovremmo rispondere con la sua password, poi facendo un “psql template1”. Se non avete ricevuto messaggi tipo che non è accettata la connessione dovreste vedere una schermata dove vi viene suggerito di scrivere “\q” per uscire, cosa che faremmo immediatamente! Ultimo dettaglio: cos'è “template1”? Quando viene installato PostgreSQL viene sempre creato un database vuoto che si chiama così, perché serve sempre un database a cui connettersi: se non avessimo un database dentro PostgreSQL non potremmo MAI connetterci a lui per crearne altri!

  4. Configurazione di Oratio: se tutto è proceduto come dovrebbe, è sufficiente recarsi sul computer client (M$Win), aprire il browser Firefox e nella barra degli indirizzi digitare il numero ip del Server seguito da “/oratio/admin.pl”:

    Nel mio caso è riportato “debian” che è il nome host del computer (stabilito durante l'installazione di Debian), ma se non avete un server DNS dovete sostituire la parola Debian con il numero ip, es http://192.168.1.4/oratio/admin.pl come nel seguente es.

    (Per capire quale è il numero ip del server ho digitato dalla shell del server “ifconfig”, viceversa “hostname” mi dice il nome del Server)

    Se invece avete problemi come “host is unreachable”, allora consiglio come prima cosa verificare che il server sia raggiungibile usando il comando dal prompt dei comandi del client M$Win “ping numero ip del server”

    Se perdete il 100% dei pacchetti allora avete un problema di rete a basso livello (cavo o indirizzo sbagliato), altrimenti il problema risiede in qualche permesso di Apache2.

    La password dell'utente amministratore di Oratio è quella dell'utente “oratio” di PostgreSQL, quindi inseritela e premete invio. Vi verrà richiesto dal browser se deve ricordare la password per questo sito, a cui consiglio di rispondere caldamente con “Mai”. Andando su “amministrazione database” avrete la seguente schermata:

    Probabilmente voi avrete la schermata in inglese (Oratio è un programma multilingue, e la scelta della lingua è legata all'utente: fino a quando non entrate con un utente Oratio non può sapere che lingua usare, quindi sceglie di default inglese – io ho fatto una modifica che indicherò più avanti per avere come lingua di default l'italiano). Inseriamo nei campi host il suo numero ip (es. 192.168.1.4), nel campo porta il numero “5432” (è un numero standard per il servizio PostgreSQL, ma può essere cambiato), il nome utente è “oratio”, la sua password è quella che gli avete assegnato, ed il campo “connetti a” deve essere “template1” (vi ricordo ancora che se non ci colleghiamo ad un database esistente non possiamo fare niente!). Andate avanti e create un nuovo dataset (una serie di tabelle correlate) chiamandola come meglio credete. Procedete poi come da indicazioni della documentazione di Oratio, creando i gruppi e gli utenti.

  1. Connessione di prova: sulla barra degli indirizzi digitate “http://vostro_numero_ip/oratio/login.pl”.

    Se arrivate alla seguente schermata la prima parte del nostro lavoro è finita. Visto che ci siete trascinate il simbolo affianco all'indirizzo di oratio (nella figura il pallino della barra degli indirizzi) nella riga subito sotto, così da crearvi un collegamento, e la prossima volta che dovrete accedere ad Oratio vi basterà cliccare sulla voce della barra degli indirizzi.

Cambio dei permessi sui files

Il sistema “linux” è piuttosto “blindato” e per ogni file è necessario avere dei permessi per poterlo leggere (“r”) scrivere (“w”) o eseguire (“x”) per una delle tre categorie: utente proprietario del file, gruppi di utenti o altri utenti. Il gruppo di utenti che possono utilizzare Apache sotto Debian sono il “www-data” (se avete pazienza di leggervi il “/etc/apache2/apache2.conf” troverete che questo è il gruppo abilitato da Apache), quindi cambieremmo i propietari alle cartelle di Oratio “users” e “data” spostandoci prima sulla sua cartella con “cd /usr/local/oratio” e poi dando “chown www-data:www-data users templates”. Per verifica dando il comando “ls -l users” ed “ls -l templates” vedrete apparire nella terza e quarta colonna il riferimento a “www-data”, il che significa che adesso Apache può accedere a queste cartelle.

Conclusioni installazione

Spedizione via e-mail:

Una cosa strana che mi è capitata è quella di avere mandato qualche ordine di prova via mail e di avere scoperto che la mail su qualche ordine non è arrivata come previsto. Per prima cosa è necessario configurare sendmail, ma è tutt'altro che intuitivo (di default il Debian propone Exim, ma non credo che i due sistemi siano pienamente compatibili). Seconda cosa bisogna “falsificare” l'indirizzo di spedizione modificando il file “oratio.conf” presente nella directory principale


come vedete dovete indicare sul parametro -f del comando sendmail l'indirizzo e-mail che verrà usato per spedire le mail (fate attenzione a scrivere “\” prima di “@”, altrimenti le mail non usciranno!).

E visto che ci siete modificate anche anche la variabile “$language” sotto, per avere tutti i menù in italiano.



Il backup

Io ho aggirato il problema in questo modo:

uno script, ad orari prefissati crea una copia di backup e la ripone in una cartella condivisa via Samba (un programma che permette di condividere cartelle fra linux e M$Win).

un client M$Win, ad altri intervalli, legge dalla cartella condivisa e copia un backup del suo contenuto nel suo interno.

La soluzione non è delle più corrette, ma funziona. Unico consiglio è quello di effettuare il backup usando il comando “pgdump -Fc dbname > filedibackup” ed eventualmente di usare per il recupero “pg_restore –clean -d dbname filedibackup” (dbname indica il nome del dataset da voi scelto per PostgreSQL), in quanto altri sistemi mi hanno portato a grossi problemi per il recupero (questo sistema però può essere usato solo per recuperare il contenuto del database, non la struttura, quindi se volete fare una copia identica che “giri” dovete prima creare un database usando il browser (http://numeroip/oratio/admin.pl) per poi copiarci sopra con questi comandi).

Si può risolvere anche dal server linux, tramite cron

A titolo esemplificativo, ecco come fare un ipotetico backup di 2 db (nomePGdb1 e nomePGdb2 sul percorso bckpath del computer chiamato Debian)

#! /bin/sh
# lista dei backups
#questa per il postgre
data=$(date +%Y%m%d)
ora=$(date +%H%M)
#nomePG="pgdump.sql"
endnomePGdb="_compresso.backup"
nomePGdb1="dboratio1"
nomePGdb2="dboratio2"
bckpath="/mnt/backup/Backup"
bckfile1=$data"_"$ora"_"$nomePGdb1$endnomePGdb
bckfile2=$data"_"$ora"_"$nomePGdb2$endnomePGdb
#echo $bckfile
#pg_dumpall > /home/backup/$bckfile
#pg_dump -Fc $nomePGdb1 > /home/backup/$bckfile
pg_dump -i -h Debian -p 5432 -U postgres -F c -b -D -v -f "$bckpath$bckfile1" $nomePGdb1
pg_dump -i -h Debian -p 5432 -U postgres -F c -b -D -v -f "$bckpath$bckfile2" $nomePGdb2
#cancella tutti gli archivi pi�ù vecchi di 12 giorni 
 find "$bckpath" -ctime +12 -type f -name '*$endnomePGdb' \
 -exec rm -f '{}' \;

questo file lo salviamo dentro la nostra cartella home come backup.sh, quindi gli cambiamo i permessi con chmod+rxrxrx backup.sh. Ora editiamo cron (che è il “demone” linux che si occupa di fare girare ad orari prefissati vari programmi) con il comando “export EDITOR=nano” per selezionare il nostro editor preferito, quindi il comando “crontab -e” per editare i processi, ed inserire la seguente riga

00 09,11,13,15,17 * * * /home/mose/backup.sh

questa riga si occupa di lanciare il nostro file ad intervalli regolari: tutti i giorni, alle 9:00, 11:00, 13:00, 15:00, 17:00 (quando il computer è acceso), e chiudendo il nano il file verrà automaticamente salvato ed il crontab aggiornato.

Configurare le stampe in pdf

Dunque, adesso se avete installato tutto avrete già personalizzato diverse cose. Resta un problema: le stampe proposte da Oratio non sono proprio funzionali, quindi rimbocchiamoci le maniche...

Sconsiglio l'utilizzo delle stampe in formato html: è vero che sono veloci, ma purtroppo hanno problemi legati proprio alla loro natura, ossia sono sensibili ai font installati sul client, al programma usato come browser installato sul client ed alle sue impostazioni. Se vi dovesse capitare di stampare depliant scaricando le pagine internet (a me accade sempre!) avrete sempre da rimettere al suo posto la configurazione di stampa ogni volta che dovrete usare una stampa di Oratio. Il consiglio è quello di sistemare una volta per tutte la stampa dei pdf.

Dentro la schermata di amministrazione di Oratio, nelle opzioni di ogni utente potrete assegnare un “usa modelli” da un menu a tendina. Questo nome verrà usato per creare una cartella dentro Oratio nella cartella Templates nel server (“/usr/local/oratio/Templates/Nome_Modello_Di_Stampa”). Dentro questa cartella verranno posizionati diversi files che terminano in “.html” o “.tex”. Quelli che terminano in “.tex” sono solo cinque e sono i modelli per le stampe in formato pdf:

Il loro contenuto può essere modificato usando l'editor “nano”, anche se altri editor sarebbero più indicati perché evidenziando la sintassi permettono meglio di capire eventuali errori come parentesi mancanti o simili. Nelle schermate seguenti mostrerò emacs, un editor molto complicato e molto distante dagli editor classici, quindi eviterò di spiegare come fare le normali operazioni, ma se avete installato “X” potrete fare queste modifiche da un editor come “kate” o simili.

Struttura di un file .tex di oratio

Un file .tex in generale è un file che racchiude un certo numero di comandi e di macro assieme al testo da stampare. Il testo è leggibile normalmente, mentre è un programma esterno che interpreta il tutto e che crea il documento finale in base ai comandi ed alle macro contenute nel file. I comandi in generale sono cose tipo: numera le pagine con numeri romani, salta una pagina prima di iniziare un nuovo capitolo, centra le figure o disponi il testo alla laro sinistra, ecc. Usando questo sistema sono stati scritti numerosi libri, quindi il risultato professionale è garantito.

Oratio si poggia sui “fancy-headers” che sono una raccolta di macro per stabilire come deve essere ottenuta l'impaginazione del documento finale, di cui utilizza le sezioni “header” e “footer” (intestazione e piè pagina). In particolare queste due sezioni hanno delle dimensioni: viene specificato nel documento quanto deve essere largo il bordo, quanto deve essere alto l'header e quanto il corpo del testo (dalla differenza si ottiene quanto deve essere alto il footer) e non bisogna assolutamente eccedere la dimensione del foglio (o della sezione che si sta considerando), quindi armatevi subito di righello...

Per quanto riguarda Oratio, tutti i suoi file .tex iniziano con comandi uguali

Subito una cosa importante: se facendo delle prove sbagliate anche solo un carattere (o eccedete in una misura) facilmente incorrete in un errore che quando viene visualizzato sul client M$Win si traduce in una finestra grandissima piena di scritte che non può essere chiusa usando il mouse! Il trucco sta nel premere “Alt”+”F4”.... Se invece state facendo delle prove direttamente da ambiente grafico “X”, il tasto “Alt” premuto assieme al bottone sinistro del mouse normalmente permette di spostare la finestra agganciandola in qualsiasi punto.

Altro consiglio è quello di creare una copia della riga che si sta modificando facendo un copia ed incolla subito sopra o sotto ed iniziando la riga con “%%” che per il tex equivale ad un commento.

Ultimo consiglio è di partire indentando correttamente tutto il file (aggiungendo le tabulazioni per capire dove iniziano e finiscono i blocchi).

Io ho già apportato alcune modifiche sostanziali, ma in particolare potete vedere un comando (a mio parere) molto importante: il “\renewcommand{\normalsize}{\footnotesize}” che trasforma tutto il testo da grandezza normale a grandezza “footnote” (nota a piè pagina) per recuperare spazio preziosissimo. Il comando precedente invece imposta il tipo di carattere a sans-serif (senza decori), la prima riga stabilisce che stiamo scrivendo un documento A4 di tipo articolo su una facciata singola del foglio, e la seconda che stiamo scrivendo in italiano.

Poi inizia la sezione “myHeader” che come vedete ho evidenziato tabulando opportunamente il suo corpo. Ogni gruppo di scritte inizia e finisce con i comandi “\begin{center}” e “\end{center}” che come si capisce indicano di centrare il contenuto, mentre le scritte in grassetto sono ottenute con il comando “\tmstrong{....}” che può essere eventualmente contenuto dentro il comando “\Large{...}”

modifichiamo il logo

Il logo è stampato leggendo il file “intestazione.png” nella stessa cartella in cui state operando, controllate solo che sia della misura opportuna o modificate in relazione i parametri di larghezza/altezza per evitare che ogni volta che generate un documento il server si debba “ricalcolare” l'immagine per renderla della grandezza richiesta (facendovi perdere diversi secondi). Io per fare questo ho usato il gimp (lo potete installare sia dentro il Debian usando aptitude che dentro un client M$Win scaricandolo da Internet, ma potete usare anche qualsiasi programma di fotoritocco) che nel menu “immagine->Dimensione superficie” mostra una comoda finestra

La qualità finale è data in relazione ai dpi che si leggono sotto “dimensione superficie” (qui 72 dpi). Diciamo che una buona stampa si ottiene dai 200dpi in su, quindi se usate uno scanner per leggere usate una buona risoluzione, e che se avete bisogno di modificare il “dpi” in Gimp lo potete fare da un'altra finestra raggiungibile sempre da menu “Immagine” alla voce “scala immagine” modificando i parametri di risoluzione (con unità di misura “Pixel/In” ossia “dots per Inch” da cui la sigla “d.p.i.” da tradursi in italiano come “punti per pollice”).

Il logo posto in basso si trova nella sezione “myFooter” e dovrete comportarvi alla stessa maniera.

Modifichiamo l'intestazione

Abbiamo già visto come inizia l'intestazione. Possiamo aggiungere del testo fisso, tipo “non autorizziamo i pagamenti oltre la scadenza” in maniera che appaia dentro questa sezione (che verrà riportata identica in tutte le pagine del documento) o possiamo fare alcune modifiche come ho fatto io: mi sono accorto che nel formato html venivano inserite delle informazioni come i numeri di telefono, ed ecco che sono stati inseriti anche qui:

il “\begin{tabular}” indica che stiamo inserendo una tabella, ed affianco abbiamo per due volte la “p” che significa due colonne con la larghezza specificata, mentre sotto ogni riga della tabella viene terminata da un “\\” ed il passaggio tra una colonna e la successiva avviene tramite il carattere “&”. Quello che vedete all'interno dei caratteri “<%...%>” è un comando di campo il cui testo sarà sostituito di volta in volta da Oratio.

L'inizio della tabella sotto ci dice, con il carattere “|” prima della “p” della colonna, che andrà tracciata una riga verticale, ed il comando “\hline” ne traccerà una orizzontale in corrispondenza della riga (in questo caso all'inizio).

Sempre nella sezione di intestazione potete notare che ho modificato sensibilmente la larghezza di tutte le colonne, in particolare nell'ultima tabella (l'intestazione delle righe) in cui ho ridotto gli spazi all'osso.

Modifichiamo il piè pagina

Il mio piè pagina inizia con una frase “** La lista voci si conclude quando....”, questo perché non sono riuscito a trovare un sistema per stampare da quante pagine doveva essere composto il documento e quindi non potevo indicare altrimenti quando il documento si interrompeva.

In questo blocco troviamo il primo blocco “<%foreach...%>” che mi ha posto un serio problema in seguito: non sappiamo quante righe verranno inserite al suo interno. Poi vedremmo perché questo sarà un problema!

Fin qui credo che non ci siano cambiamenti di sorta.

In queste ultime righe, invece ho inserito un paragrafetto con all'interno un “Pagina \thepage \\” che mi stampa sotto a sinistra correttamente il numero di pagina.

Modifichiamo il corpo

Adesso si vedono le misure del layout del foglio, e possiamo notare subito due cose: la prima è che ho modificato opportunamente il “{\headheight}” stampandolo prima abbondante (tipo sui 16cm), poi misurando col righello sulla carta il suo reale spazio occupato e quindi correggendo il tiro; la seconda è che compare un campo “<%textheight%>” che sarà calcolato da Oratio durante l'elaborazione, ma avendo io cambiato la dimensione dei font questo dato “sballa”. La mia prima mossa è stata di mettere un valore fisso, che è andato bene per tutti i moduli tranne che per questo, perché nelle fatture vengono aggiunte un certo numero di righe nel piè pagina che non possiamo sapere a priori quanto occuperanno! Alla fine ho dovuto fare una ricerca (vedi più avanti) per correggere la formula sulla grandezza dei font da me usati, in maniera che fosse calcolato opportunamente questo dato.

Il corpo inizia in corrispondenza del “\begin{document}”, subito dopo finito il layout.

Se osservate bene noterete che ho creato per il corpo una struttura a griglia, senza bordi, in cui ogni riga viene terminata da una riga disegnata

e che infine viene terminato il corpo con una bella riga “** -----.... FINE LISTA ....” per indicare che abbiamo terminato il documento.

Gli altri moduli sono pressoché uguali.

Listato completo:

\documentclass[oneside,a4paper]{report}
\usepackage[italian]{babel}
\usepackage{epsfig}
\usepackage{fancyhdr}

\usepackage{isolatin1}
\renewcommand{\familydefault}{\sfdefault}
\renewcommand{\normalsize}{\footnotesize}

\newcommand{\tmstrong}[1]{\textbf{#1}}

\newcommand\myHeader{

  \begin{center}
    \epsfig{file=../<%templates%>/intestazione.png,height=3.6cm,width=18cm}
  \end{center}

  \begin{center}
    \Large{\tmstrong{FATTURA}}
  \end{center}

  \begin{center}
    \begin{tabular}{p{10.9cm}p{6.9cm}}
      {\tmstrong{CLIENTE:}} & {\tmstrong{DESTINAZIONE:}}\\
      <%name%> & <%shiptoname%>\\
      <%addr1%> & <%shiptoaddr1%>\\
      <%addr2%> & <%shiptoaddr2%>\\
      <%addr3%> & <%shiptoaddr3%>\\
      <%addr4%> & <%shiptoaddr4%>\\
      P.I.: <%customernumber%> & <%shiptocustomernumber%>\\
      CD.CLI.: <%accno_customer%> & \\
        & \\
      {\tmstrong{Rif.to:}}<%contact%><%end contact%> & {\tmstrong{Rif.to:}}<%shiptocontact%><%end shiptocontact%>\\
      {\tmstrong{Tel:}}<%customerphone%><%end customerphone%> & {\tmstrong{Tel:}}<%shiptophone%><%end shiptophone%>\\
      {\tmstrong{Fax:}}<%customerfax%><%end customerfax%> & {\tmstrong{Fax:}}<%shiptofax%><%end shiptofax%>\\
        & \\
    \end{tabular}

    \begin{tabular}{|p{4.0cm}|p{5.8cm}|p{7.8cm}|}
      \hline
      {\tmstrong{<%tipofatt_desc%> N.}} & {\tmstrong{Data}} & {\tmstrong{Riferimento}}\\
      <%invnumber%> & <%invdate%> & <%riferimento%>\\
      \hline
    \end{tabular}

    \begin{tabular}{|p{7.9cm}|p{9.9cm}|}
      \hline
      {\tmstrong{Pagamento}} & {\tmstrong{Banca Appoggio}}\\
      <%tipopag%> - <%solpag_descr%> & <%bank_name%> - <%field_bank%>\\
      \hline
    \end{tabular}\\
    \vspace{0.2cm}
    \begin{tabular}{|p{3.0cm}|p{6.6cm}|p{0.6cm}|p{0.9cm}|p{1.5cm}|p{0.8cm}|p{1.7cm}|p{1.5cm}|}
      \hline
      \tmstrong{Codice} & \tmstrong{Descrizione} & \tmstrong{UM} & \tmstrong{Qta'} & \tmstrong{P.U.} & \tmstrong{Sc.} & \tmstrong{Totale} & \tmstrong{IVA}\\
      \hline
    \end{tabular}

\end{center}

}

\newcommand\myFooter{
  \begin{center}
    ** La lista voci si conclude quando viene stampato "FINE LISTA" **
    \begin{tabular}{|p{2.9cm}|p{6.7cm}|p{3.9cm}|p{3.9cm}|}
      \hline
      \tmstrong{Aliquota} & \tmstrong{Descrizione} & \tmstrong{Imponibile} & \tmstrong{IVA}\\
      \begin{tabular}{l}
        <%foreach taxrate%>
          <%taxrate%>\\
        <%end taxrate%>
      \end{tabular}
      &
      \begin{tabular}{l}
        <%foreach taxdescription%>
          <%taxdescription%>\\
        <%end taxdescription%>
      \end{tabular}
      &
      \begin{tabular}{l}
        <%foreach taxbase%>
          <%taxbase%>\\
        <%end taxbase%>
      \end{tabular}
      &
      \begin{tabular}{l}
        <%foreach tax%>
          <%tax%>\\
        <%end tax%>
      \end{tabular}\\
      \hline
    \end{tabular}

    \begin{tabular}{|p{5.9cm}|p{5.8cm}|p{5.9cm}|}
      \hline
      \tmstrong{Tot. Imponibile} & \tmstrong{Tot. IVA} & \tmstrong{Tot. Fattura (<%currency%>)}\\
      <%subtotal%> & <%totaleiva_detr%> & <%invtotal%>\\
      \hline
    \end{tabular}

    \begin{tabular}{|p{8.9cm}p{8.9cm}|}
      \hline
      \tmstrong{Scadenze} & \\
      \tmstrong{Data Scadenza} & \tmstrong{Importo Scadenza}\\
      <%foreach scad%>
        <%data_scad%> & <%importo_scad%>\\
      <%end scad%>
      \hline
    \end{tabular}

    \begin{tabular}{|p{18cm}|}
      \hline
      \tmstrong{Note}\\
      <%notes%>\\
      \hline
    \end{tabular}

  <%if accomp%>

    \begin{tabular}{|p{1.9cm}|p{4.9cm}|p{1.8cm}|p{2.8cm}|p{2.8cm}|p{2.8cm}|}
      \hline
      \tmstrong{Porto} & \tmstrong{Asp. esteriore dei beni} & \tmstrong{N. colli} & \tmstrong{Peso lordo} & \tmstrong{Peso Netto} & \tmstrong{Volume}\\
      <%shippingpoint%> & <%aspettobeni_descr%> & <%colli%> & <%pesolordo%> & <%pesonetto%> & <%volume%>\\
      \hline
    \end{tabular}

    \begin{tabular}{|p{4.4cm}|p{4.4cm}|p{4.3cm}|p{4.3cm}|}
      \hline
      \tmstrong{Trasporto a mezzo} & \tmstrong{Data/Ora trasporto} & \tmstrong{Firma Vettore} & \tmstrong{Firma Destinatario}\\
      <%trasporto_descr%> &  &  & \\
      \hline
    \end{tabular}

    \begin{tabular}{|p{18cm}|}
      \hline
      \tmstrong{Vettore}\\
      <%vettore%>\\
      \hline
    \end{tabular}

  <%end accomp%>
    \begin{tabular}{p{18cm}}
      Pagina \thepage \\
    \end{tabular}      

  \end{center}
}

\setlength{\topmargin}{-1.5cm}
\setlength{\oddsidemargin}{-1.5cm}
\setlength{\evensidemargin}{-1.5cm}
\setlength{\textheight}{<%textheight%>cm}
\setlength{\textwidth}{18cm}
\setlength{\tabcolsep}{0.1cm}
\setlength{\headheight}{13.4cm}
\setlength{\headsep}{0cm}
\setlength{\footskip}{0cm}

\renewcommand{\headrulewidth}{0pt}
%%\renewcommand{\footrulewidth}{0pt}

\pagestyle{fancy}

\chead{\myHeader}
\cfoot{\myFooter}

\begin{document}

\begin{center}

  <%foreach number%>
  \begin{tabular}{p{3.0cm}|p{6.6cm}|p{0.6cm}|p{0.9cm}|p{1.5cm}|p{0.8cm}|p{1.7cm}|p{1.5cm}}
    <%number%> & <%description%> & <%unit%> & <%qty%> & <%sellprice%> & <%discount%> & <%linetotal%> & <%iva_descr%>\\
    \hline
  \end{tabular}
  <%end number%>
  ** ------------------------------------------ FINE LISTA ------------------------------------------ **
\end{center}

\end{document}

Dimensione dei caratteri

Per la modifica del corpo occorre editare i files di Oratio: per sapere quali editare ci dobbiamo spostare sulla cartella “/usr/local/oratio/bin/mozilla”, e quindi con il comando “grep -n textheight *” vedremmo in quali moduli perl viene usato il campo “textheight”: dal seguente output dovete considerare che i file che iniziano con “#” sono commenti (cioè erano le righe originali prima o dopo le modifiche che ho riportato ma che ho voluto conservare per cautela), e che il file che finisce con la tilde è un file di backup, quindi voi avrete meno della metà di quanto mostrato:

A sinistra abbiamo il nome del file, dopo i due punti la riga in cui appare e dopo la riga in cui è stato trovato il testo. E' necessario editare tutti questi file nei punti ottenuti per riportare le cose alla loro dimensione.

Settare di default il formato pdf

Se avete fatto tutto questo lavoro, avrete sicuramente voglia di selezionare di default il formato “pdf” di stampa (è il radio-button presente nei moduli di oratio). Digitando dalla cartella “/usr/local/oratio/bin/mozilla” il comando “grep -n “$form{format} = /”html/”” *” potremmo vedere dove è necessario agire (attenzione a digitarlo correttamente). Nel seguente esempio ho visualizzato le modifiche apportate (con il campo “pdf” nella ricerca del comando “grep”) visualizzando le due righe precedenti e seguenti al testo (con l'opzione “-C2”), e come potete vedere ho sempre commentato l'originale e sotto ho solo sostituito il contenuto di “html” con “pdf”.

Voi dovreste visualizzare solo quattro righe, non tutte queste!

Uso di CygWin per connessioni remote da Win

Se dovete lavorare dal client è bene che lo facciate senza spostarvi sul server, anche nel caso in cui dobbiate modificare delle configurazioni.

Per fare questo è bene collegarsi usando CygWin: l'installazione avviene “via rete” anche per questo programma, che crea un piccolo ambiente linux all'interno del vostro computer M$Win. Tramite questo sistema potrete innanzitutto usare alcuni dei comandi unix più classici anche sotto M$Win, ed in secondo caso avrete a disposizione una connessione ssh verso il server. Il protocollo ssh è un sistema che permette di trasmettere i dati sulla rete usando sistemi crittografici di comprovata qualità, e se avete installato il servizio ssh-daemon sul vostro server (di default lo è) potrete accedervi tramite il vostro M$Win come se ci foste davanti.

Dal sito CygWin potete scaricare un piccolo eseguibile che lanciato si presenterà nella seguente maniera:

Cliccando su avanti potrete scegliere come procedere (consiglio di lasciare la prima opzione attiva che scaricherà i pacchetti sul vostro pc e li lascerà a disposizione per eventuali aggiornamenti)

Avrete poi da scegliere varie cose come: come collegarvi ad internet, dove salvare i pacchetti, quale mirror dei pacchetti usare. Verrà poi scaricato un pacchetto che contiene la lista diquanto è possibile installare. Dentro questa lista potrete scegliere diverse opzioni, ma sono essenziali almeno:

  1. dalla sezione net il “openssh”

e tutta la sezione X11 (fate click due volte sulle frecce per cambiare tutta la sezione su install)

Procedete ed attendete che scarichi tutto. Quando avrete finito eseguite il comando dalla cartella “cygwin->usr->X11R6->bin->startxwin.bat” (o meglio createvi un collegamento sul desktop).

Avrete lanciato così una shell sotto il server grafico emulato “X”. Non usate quella che vi viene creata come collegamento sul desktop (chiamata semplicemente “CygWin”), perché quando vi connetterete al server vi creerà grossi problemi con la tastiera. Come avrete notato questa shell assomiglia a quella di un linux (a parte lo sfondo bianco) solo che non vi chiede di autenticarvi ma prende per buono il nome utente e nome host di M$Win. Digitando il comando “ssh nomeutente@indirizzoip” avvierete il tentativo di connessione a quel computer (usando l'ssh, appunto!). Una nota particolare sul nome dell'utente utilizzato per la connessione: durante il tentativo di connessione sarebbe possibile riuscire a decifrare sia il nome utente che la sua password (perché il canale ssh non è stato ancora stabilito), quindi bisognerebbe connettersi con un normale utente e solo dopo richiedere i privilegi di amministratore usando il comando “su root” e digitando la sua password (che viaggerà nel canale ssh protetto già stabilito). Io nella schermata seguente ho fatto il contrario sia perché lavoro in un ambiente relativamente sicuro e sia perché così non rivelo a tutti uno dei nomi dei miei utenti del mio server (è chiaro che root esiste dappertutto!). Il nome del server può essere anche nella forma di indirizzo ip (tipo “168.10.1.4”)

E così ho svelato anche perché nelle schermate precedenti si intravede la finestra di M$Win attorno alla shell! Non ho specificato prima che di norma in qualunque distribuzione Debian è attivo il server Opnessh. Se non dovesse esserlo, lo trovate nella stessa sezione di aptitude dove è presente apache2.

Sotto Ubuntu consiglio l'uso del comando “ssh -X nomeutente@indirizzo.ip”. Apparentemente non cambia niente, ma se avete bisogno di modificare un file con un editor un attimino più avanzato, allora precedendo il comando con “sudo “ potrete aprire anche editor grafici come kate, gedit o emacs per X.

Interfacciarci usando il PgAdmin direttamente al PostgreSQL

L'utility PgAdmin III, è un programma che si collega direttamente (senza usare driver di sistema di M$Win) al nostro server e che ci permette di interagire direttamente con il PostgreSQL. Abbiamo parlato di interagire: ma come si interagisce con il PostgreSQL? Naturalmente usando comandi SQL....

Una soluzione alternativa per ottenere lo stesso effetto è quello di collegarci via ssh e di usare il comando “psql” (il “psql” è un ambiente testuale dove interagiamo con il PostgreSQL) ma fare le stesse cose via PgAdmin è, soprattutto se alle prime armi, molto più semplice e proficuo (non bisogna conoscere tutti i comandi!), oltre che visivamente più accattivante.

Per prima cosa è necessario stabilire una connessione. Quindi se nella configurazione di PostgreSQL abbiamo abilitato le connessioni via “tcp” con le credenziali “trust” possiamo procedere.

Nel mio caso ho creato due connessioni, nelle quali ho chiamato il server sia con il nome host (perchè ho un DNS) ed in una ho indicato nel campo “address” il suo numero ip. La porta è di norma sempre la 5432 (salvo che modifichiate la configurazione di PostgreSQL), il “Maintenance DB” è il database al quale ci colleghiamo (che ricordo ancora, deve essere un database esistente! Quindi provate a collegarvi al “template1”), lo “username” è il nome di un utente PostgreSQL (“oratio” va bene se avete fatto l'installazione standard) e la password la conoscete voi. Il flag “store password” vi permette di memorizzare la password per usi futuri.

I problemi principali di connessione sono sempre due: o non viene trovato il PostgreSQL (in questo caso fate una prova con il “ping” per essere sicuri di avere in mano l'indirizzo giusto) o l'utente non è abilitato alla connessione (ed allora modificate i permessi di PostgreSQL come indicato in questo stesso tutorial e riavviate il servizio).

Appena connessi vedrete i nomi dei Database registrati, senza il “template1” che vi ricordo essere un database di sistema che non deve essere mai cancellato (ma in realtà c'è anche sul mio PostgreSQL). I database con la croce rossa sopra, sono registrati (“db1”,”db2” e “db3”), ma se non ci cliccate dentro il PgAdmin non li ispeziona automaticamente e quindi non vi mostra informazioni in merito: la croce rossa scompare quando fate il doppio click sul loro nome (se avete le credenziali per accedervi). In basso a sinistra (della ramificazione) potete vedere anche gli utenti abilitati (“Users”, che vi ricordo non sono necessariamente gli stessi di Oratio, ma che deve contenere almeno quello usato durante la configurazione di Oratio, e quindi nel nostro caso quello chiamato “oratio”). Dentro ogni database potete seguire il seguente percorso “Schemas->public” e vedere che ci sono due ramificazioni che contengono oggetti: una è “Sequences” che contiene il conteggio delle sequenze (es. il “id_part” contiene il numero attuale del progressivo che viene memorizzato nella tabella “parts” (quella degli articoli) per rendere ogni riga unica) e salvo situazioni catastrofiche NON VANNO MAI MODIFICATI; l'altra ramificazione è tables, e qui dentro avrete accesso alle tabelle contenenti i dati (es. “parts” è la tabella dei codici degli articoli).

L'utilità di PgAdmin è principalmente quella di:

  1. modificare/leggere direttamente le tabelle (una volta posizionati direttamente sul nome tabella da modificare si illumineranno le due icone in alto che rappresentano una griglia). La cosa può risultare utile se vogliamo, per esempio, modificare manualmente il prezzo di una serie di articoli che possiamo ricercare usando i filtri. Per accedere ai filtri, una volta dentro la tabella, dobbiamo premere l'icona dell'imbuto, e nella prima pagina selezionare il tipo di ordinamento (selezionando il nome della colonna da utilizzare e premendo “ascending” o “descending”). Un avviso importante riguarda il modo in cui vengono registrati i cambiamenti: anche se a prima vista ci troviamo in un normale foglio di calcolo (con le nostre cellette ordinate in righe e colonne) i cambiamenti sono immediati ed effettivi da quando vi sposterete di cella in cella, e non sono da registrare a foglio completamente modificato, quindi attenzione ad ogni cosa che fate perché per più cambiamenti non è possibile tornare indietro. PgAdmin offre anche la possibilità di fare il backup/ripristino di una singola tabella, ma lo dovete fare prima di fare operazioni di un certo rilievo.

  2. eseguire del codice SQL (bisogna premere l'icona con il foglio con scritto “SQL” e la matita). Si aprirà un editor dove avrete diverse icone: salva/carica il testo dei comandi (query), esegui i comandi, analizza i comandi ecc.

Qualche comando SQL

Il linguaggio SQL prevede pochi semplici comandi che hanno la loro forza nelle capacità di essere innestati uno dentro l'altro per ottenere risultati complessi. Io ne utilizzo normalmente solo due: SELECT ed UPDATE, ma talvolta mi è ritornato utile anche INSERT. I nomi delle tabelle fanno tutti capo al ramo “public”, quindi la tabella “parts” sarà meglio indicata nelle query come “public.parts”, ed i nomi delle colonne li potrete leggere all'interno delle tabelle stesse (resta a voi vedere quali sono le tabelle che dovrete analizzare).

Per prima cosa specifichiamo che i commenti iniziano con il doppio trattino meno, che il SQL non distingue tra i comandi scritti in maiuscolo e minuscolo (ma il risultato si) e che per usanza i comandi SQL si scrivono sempre tutti in maiuscolo, e che i comandi si concludono con il punto e virgola (e possono essere spezzati su più righe), e partiamo a vedere il primo comando:

  1. SELECT: il comando SELECT seleziona una serie di dati “ripescandoli” da una o più tabelle. Se volgiamo “stampare” solo i codici degli articoli, la nostra query sarà: “SELECT part_id FROM public.parts;”. Da questa query possiamo capire che dopo il SELECT abbiamo specificato il nome della colonna che ci interessava, poi abbiamo aggiunto un'altra parte della sintassi SQL con la parola “FROM” e di seguito abbiamo indicato quale tabella esaminare. Se volessimo stampare anche il prezzo di listino degli articoli e la loro descrizione potremmo aggiungere i nomi delle colonne dopo il SELECT separandole con una virgola (“SELECT partnumber, listprice, description FROM public.parts;”) per ottenere un listino di vendita da stampare usando il nostro programma preferito (OpenOffice, mi raccomando!) facendo un copia incolla del risultato mostrato nella finestra sottostante. Andando avanti con la stessa query potremmo fare un miglioramento aggiungendo un ordinamento dei risultati, ponendo alla fine un “ORDER BY nomecolonna ASC (per ascendente o DES per discendente)” (“SELECT partnumber, listprice, description FROM public.parts ORDER BY description ASC;”). Il penultimo miglioramento fondamentale è quello di selezionare i risultati, aggiungendo una clausola “WHERE (nomecolonna condizione altronomecolonna/numero)” eventualmente raggruppate con AND o OR: le condizioni sono quelle normalmente usate in matematica se confrontiamo dei numeri: “>” per “maggiore di”, “<” per “minore di”, ecc (quindi per ottenere solo gli articoli che hanno un costo superiore o uguale a 100€, avremmo:“SELECT partnumber, listprice, description FROM public.parts WHERE (listprice >= 100) ORDER BY description ASC;”) mentre per il testo si usa il “LIKE” e le wildcard. I caratteri di wildcard in SQL sono solo due: uno è il percento e l'altro l'underscore, e rappresentano rispettivamente “un carattere qualsiasi in un numero da 0 in poi di volte” e “un carattere (e solo uno, non zero e non due o più) qualsiasi”. Per cercare un testo contenuto “all'interno” specificheremmo dentro gli apici singoli '%testo%' (che inizia e/o che finisce con qualsiasi serie di caratteri), mentre 'testo%' e '%testo' troveranno rispettivamente tutti quelli che iniziano con 'testo' e che finiscono con 'testo' e 'testo%testo' tutti quelli che inizieranno e finiranno con 'testo'. Per affinare la nostra ricerca di un listino potremmo ricercare solo gli articoli che iniziano con la lettera F usando “SELECT partnumber, listprice, description FROM public.parts WHERE (listprice >= 100 AND description LIKE 'F%') ORDER BY description ASC;” (notare come le parentesi aiutino a capire quale siano i campi del WHERE). Ultimo miglioramento è il JOIN: avrete sicuramente notato che alcune colonne che normalmente visualizziamo in Oratio come testo sono presenti in alcune tabelle come numeri: questi numeri sono il riferimento interno di altre tabelle, in modo tale che se modifichiamo il contenuto di uno di questi riferimenti automaticamente abbiamo modificato il contenuto di tutti (ed abbiamo evitato di ripetere questo contenuto tutte le volte), come per esempio per le categorie degli articoli che nella tabella appaiono come numeri, dove il numero indica l'identificativo della tabella “public.categoryparts”. Così se volgiamo visualizzare correttamente una colonna aggiuntiva alla nostra query per aggiungere la categoria, dobbiamo usare un comando che specifichi quale colonna visualizzare al posto dei numeri quando le occorrenze coincidono usando un “FROM tabella1 INNER JOIN tabella2 ON colonnaRif1=colonnaRif2” ed aggiungendo la colonna da riportare a quelle subito dopo il comando SELECT: ”SELECT partnumber, listprice, public.parts.description, public.categoryparts.description FROM public.parts INNER JOIN public.categoryparts ON public.categoryparts.cd_cat = public.parts.cd_cat WHERE (listprice >= 100 AND public.parts.description LIKE 'F%') ORDER BY public.parts.description ASC;”

    (notare come prima delle colonne sia stato indicato il nome completo della tabella per dare maniera di capire, nel caso di colonne con lo stesso nome su tabelle differenti, a cosa esattamente ci stiamo riferendo). Sono possibili anche JOIN multipli (e di diversi tipi) ma si inizia a sconfinare nel SQL puro...

  2. UPDATE: il comando UPDATE permette di modificare dei valori. Il suo uso più semplice è “UPDATE tabella SET colonna = valore WHERE condizione” (il WHERE e la condizione possono essere omessi). Il valore può essere anche una formula che faccia riferimento ad una colonna di un'altra tabella, per esempio per aumentare il prezzo di listino del 10% di tutti gli articoli il cui codice inizi con 'FFF' faremmo: “UPDATE public.parts SET listprice=listprice*1.1 WHERE (part_id LIKE 'FFF%');” (moltiplichiamo per 1,1 per avere il 110%). Altri esempi possono nascere solo da esigenze specifiche che vanno risolte volta per volta (es. per modificare l'iva di tutti gli articoli al 20% “UPDATE parts SET cdvat=20;” in pochi millisecondi può sostituire il lavoro di oltre mezz'ora se fatto con Oratio, di una decina di minuti se fatto con PgAdmin).

  3. INSERT: il comando INSERT permette di inserire dati che ancora non esistono nella tabella specificando i nomi delle colonne, seguiti dai nomi dei valori e da TABLE nometabella, ma poiché rischiamo di non inserire delle colonne che possono essere indispensabili per fare funzionare correttamente Oratio il consiglio è di non usarlo per le normali operazioni, pena malfunzionamenti del sistema.

In generale l'uso più indicato è quello del comando UPDATE per gli aggiornamenti, perchè anche se le SELECT sono molto utili, usando OpenOffice Base è più semplice estrarre dati (ed utilizzarli, per. es., per colmare una delle mancanze più generiche di Oratio: i grafici).

Wildcard SQL usate da Oratio

Come spiegato prima le wildcard sono solo due, ed Oratio in diversi campi permette di fare ricerche molto flessibili. Innanzitutto qualsiasi campo di ricerca verrà sempre considerato come case-insensitive (senza differenziare maiuscolo e minuscolo) e sempre come se fosse circondato da due simboli “%” all'inizio ed alla fine a meno che un simbolo “%” non venga da voi specificato all'interno del campo: 'testo' verrà trasformato in '%testo%', mentre '%testo', 'testo%' o 'tes%to' non verranno modificati e verranno analizzati esattamente così come sono. Come seconda cosa, se disponete di due campi (di solito codice e descrizione) normalmente Oratio cercherà le soluzioni che sono soddisfatte da entrambe il contenuto dei campi, sempre seguendo le regole precedenti applicate al singolo campo. Quindi per trovare un articolo il cui codice termina con il penultimo carattere 'a' e la cui descrizione contiene 'prova' e che termina con 'nuova' digiteremmo nel campo codice '%a_' e nel campo descrizione '%prova%nuova' e poi premeremmo invio.

Interfacciarci usando OpenOffice direttamente al PostgreSQL

Sicuramente l'integrazione di Openoffice con i dati di Oratio costituisce il fondamento per un sistema personale e dinamico: le ricerche vengono create usando semplici sistemi grafici, trascinando colonne, definendo collegamenti tra tabelle, creando eleganti report, il tutto dentro un ambiente molto user-friendly e facilmente gestibile in base alle richieste del momento.

L'utilizzo di OpenOffice in combinazione con il PostgreSQL porta ad avere due tipi di scenari:

  1. soluzioni basate su OpenOffice Base, per avere query da stampare facilmente ed elegantemente, e maschere per l'inserimento e la modifica di dati (e queste ultime, purtroppo, non funzionano in maniera ottimale);

  2. risultati da utilizzarsi all'interno di altri documenti di OpenOffice (ad. es. la lista dei clienti per stampare la stessa lettera con solo il nominativo del cliente diverso).

Alcuni programmi possono a loro volta interfacciarsi al PostgreSQL (per es. Hylafax permette di gestire in ingresso ed uscita i FAX, e numerosi programmi client a lui dedicati come FrogMAIL, sono in grado di collegarsi direttamente al PostrgreSQL per leggere il numero fax e l'email sia dei clienti che dei fornitori).

Il primo passo è quello di installare OpenOffice scaricando il pacchetto dal sito principale, o meglio scaricando la versione localizzata seguendo i collegamenti per la versione italiana dalla pagina principale. In passato bisognava prima installare la Java Virtual Machine scaricandola dal sito della Sun Microsistem, altrimenti il funzionamento di OpenOffice risultava “limitato” in alcune funzionalità, oggi i pacchetti contengono la “jvm”. Fatti questi due passi fondamentali è indispensabile scaricare il driver seguente http://dba.openoffice.org/drivers/postgresql/index.html pena un accesso ai database funzionante in maniera piuttosto incompleta. Per installare questo driver dovete raggiungere il pannello seguendo il percorso “Strumenti->Gestione pacchetti” ed aggiungendo il driver appena scaricato senza scompattarlo (cosa che vi impedirebbe una corretta installazione). Se il driver viene accettato potrete visualizzarlo nella lista dei pacchetti, quindi dovrete chiudere tutte le applicazioni di OpenOffice, compreso il quickstart (la bandierina nera che appare in basso a destra nella barra degli strumenti di M$Win) per rendere operativo e funzionante il driver

Aprendo OpenOffice Base passeremo ad una procedura guidata dove nella prima pagina dovrete rispondere di collegarvi ad un database esistente usando il driver “postgresql” (in fondo alla lista dei driver). Nella pagina seguente dovremmo specificare i parametri “dbname=nomeDb host=numeroip” (separati da uno spazio, in cui il nome host può essere anche il nome vero e proprio se avete un DNS). Nella pagina seguente dovremmo specificare un nome utente (“oratio”) (senza spuntare la casella della richiesta della password, in quanto abbiamo settato a “trust” le credenziali per l'accesso) e cliccare sulla connessione di prova. Se tutto è andato bene, potremmo andare avanti e registrare il database. Un consiglio spassionato è di non creare mai tabelle usando OpenOffice Base, quindi non andate a selezionare la procedura guidata apposita di quest'ultima pagina. Se ritenete di avere abbastanza familiarità con i database, le tabelle createle solo usando il comando “psql” o il PgAdmin, e fate particolare attenzione a ciò che fate (io ho avuto necessità di avere delle informazioni supplementari da intrecciare con i dati di Oratio, e dopo diversi tentativi ho raggiunto una situazione soddisfacente con delle tabelle che vengono gestite con una maschera di OpenOffice Base).

Completata questa procedura avrete una schermata con una colonna a sinistra con quattro icone, ma presumibilmente ci interesseremmo solo alla seconda, le ricerche, che in pratica sono delle query memorizzate in formato OpenOffice. Il design di un documento con OpenOffice Base è una procedura poco “spontanea”, per cui vi rimando ad un documento molto ben fatto (ed approfondito) al seguente indirizzo: http://www.softcombn.com/04_docfree_05.htm che spiega anche come realizzare tabelle aggiuntive, ma che essendo nato tra l'OpenOffice 1.x ed il 2.0 ha qualche indicazione poco corretta sulla registrazione dei database.

Le ricerche possono essere salvate dentro il documento di OpenOffice Base, ma indubbiamente un utilizzo più elastico lo si raggiunge registrando il database in maniera che anche tutte le altre applicazioni della suite di OpenOffice possano avere accesso alle tabelle ed alle ricerche. Per completare la registrazione è possibile raggiungere la cartella “Strumenti->Opzioni” da un menù di una qualsiasi applicazione di OpenOffice, andando poi ad espandere il ramo “Database” di “OpenOffice.org Base” e cliccando su nuovo ed indicando il percorso completo del documento di OpenOffice Base salvato precedentemente ed un nome generico. Fatto questo, quando saremmo in qualsiasi documento di OpenOffice, premendo il pulsante “F4” farete apparire una nuova sezione tra il menu ed il documento, dove potrete avere accesso immediato ai database registrati. Vedrete quindi due nomi (il primo, bibliography, è inserito durante l'installazione di OpenOffice) che potrete espandere cliccando sul simbolo “+”, e potrete visualizzare alla sua destra come se aveste aperto la scheda di modifica della tabella di PgAdmin. Quello che in realtà vi può essere utile è l'importazione di una singolo campo, di un'intera riga o colonna o dell'intera tabella selezionata (prima selezionate cosa vi interessa copiare e poi trascinate la selezione dentro il vostro documento). La cosa più semplice è quella di importare una tabella dentro un foglio di calcolo: attenzione però che i dati “importati” dentro un documento di OpenOffice non possono poi venire esportati, quindi utilizzate questa procedura solo per operazioni come la creazione di grafici o l'esportazione di listini, il cui compito può essere facilitato se memorizzate i dati per voi utili dentro una ricerca. Io per esempio uso spesso una ricerca degli ultimi articoli arrivati, dove mi appaiono solo il codice e la descrizione, in maniera da potere stampare delle etichette per il materiale semplicemente trascinando questi dati dentro un foglio di calcolo con le etichette disegnate al suo interno. Anche in questo caso non scendo troppo nei particolari perché le esigenze sono a mio parere strettamente personali e legate alla propria attività.

A titolo esemplificativo:

ricerca per vedere il dettaglio delle offerte (copia/incolla il seguente testo dentro una nuova ricerca in modalità sql)

SELECT "quote"."year" AS "Anno", "quote"."transdate" AS "Data", "quote"."quotenumber" AS "Offerta", "sales_quote_types"."description" AS "TipoOff", "quote"."status" AS "A/C", "customer"."name" AS "Cliente", "customer"."addr3" AS "Localita'", "quote"."reference" AS "Oggetto", "parts"."partnumber" AS "Art", "quoteitems"."description" AS "Desc", "quoteitems"."qty", "quoteitems"."unit" AS "UM", "quoteitems"."sellprice" AS "pr/Uni", "quoteitems"."discount" AS "Sc", "vendor"."name" AS "Agente", "economic_category"."description" AS "Categoria" FROM { OJ "public"."quote" AS "quote" LEFT OUTER JOIN "public"."vendor" AS "vendor" ON "quote"."agent_id" = "vendor"."id" }, { OJ "public"."customer" AS "customer" RIGHT OUTER JOIN "public"."economic_category" AS "economic_category" ON "customer"."economic_category_id" = "economic_category"."id" }, "public"."sales_quote_types" AS "sales_quote_types", "public"."quoteitems" AS "quoteitems", "public"."parts" AS "parts" WHERE "quote"."customer_id" = "customer"."id" AND "quote"."quote_type" = "sales_quote_types"."cd_type" AND "quoteitems"."trans_id" = "quote"."id" AND "quoteitems"."parts_id" = "parts"."id"

ricerca per vedere il dettaglio dei fornitori

SELECT "name", "addr1", "addr2", "addr3", "addr4", "contact", "phone", "fax", "mobile", "email", "notes" FROM "public"."vendor" AS "vendor"

Inventario - ho diviso in 2 parti la ricerca, di cui la prima si chiama “ric_inventarioData”

SELECT "part_id", SUM( "qty") FROM "public"."waremove" AS "waremove" WHERE ( "date_mov" < '2008-01-01' ) GROUP BY "part_id" ORDER BY "part_id" ASC

SELECT "parts"."partnumber" AS "codice", "parts"."description" AS "descrizione", "parts"."listprice" AS "PrezzoAcquisto", "ric_inventarioData"."sum" AS "Q.ta'" FROM "public"."parts" AS "parts", "ric_inventarioData" AS "ric_inventarioData" WHERE ( "parts"."id" = "parts"."id" AND "parts"."id" = "ric_inventarioData"."part_id" ) AND ( ( "ric_inventarioData"."sum" > 0 ) ) ORDER BY "codice" ASC

Ricordatevi di aggiornare la data della prima ricerca – dopo di che potete esportare con un click tutto l'inventario su di un foglio di OO Calc, ma il prezzo è quello di listino acquisti quindi gli sconti vanno inseriti a mano.

Ricerca di listini di vendita

SELECT "parts"."id", "parts"."partnumber" AS "Codice", "parts"."description" AS "Descrizione", "categoryparts"."code" AS "Categoria", "subcategoryparts"."cd_subcat" AS "Sottocategoria", "parts"."sellprice" AS "Pr. Vendita", "parts"."onhand" AS "Magazzino" FROM "public"."parts" "parts", "public"."categoryparts" "categoryparts", "public"."subcategoryparts" "subcategoryparts" WHERE ( "parts"."cd_cat" = "categoryparts"."cd_cat" AND "parts"."subcategoryparts_id" = "subcategoryparts"."id" )

Ricerca per vedere le tabelle Iban dei clienti

SELECT "customer"."name", "customer"."addr1", "customer"."addr3", "customer"."cd_typepay", "other_bank"."abi", "other_bank"."cab", "other_bank"."name", "other_bank"."agency", "cv_banks"."swift", "other_bank"."iban", "cv_banks"."account", "cv_banks"."cin" FROM "public"."cv_banks" "cv_banks", "public"."customer" "customer", "public"."other_bank" "other_bank" WHERE ( "cv_banks"."customer_id" = "customer"."id" AND "cv_banks"."id_bank" = "other_bank"."id" ) ORDER BY "customer"."name" ASC

Creare un piccolo modulo all'interno di Oratio

La funzione di un modulo di Oratio è quella di avere qualcosa che si usa sempre e che è strettamente legata all'ambiente di Oratio dentro il programma. In teoria sarebbe possibile avere anche “tutto Oratio” dentro OpenOffice Base, ma le sue maschere non sono funzionali come quelle fatte con il perl, quindi è meglio fare due conti prima per capire come operare. Il giusto rapporto tra i due è che una maschera in Base è molto facile da realizzare ma poco funzionale e distaccata da Oratio (quindi non perfettamente integrata), mentre un modulo Oratio richiede tempi di preparazione di gran lunga maggiori e difficoltà superiori per modifiche durante il suo utilizzo, ma sicuramente gode di maggiore velocità e stabilità. Il consiglio e di cimentarsi in un modulo solo se veramente esistono necessità che lo richiedano.

Il menù delle operazioni di Oratio che appare a sinistra viene definito da un file di testo che si trova nella directory principale di Oratio chiamato menu.ini

Questo file definisce quale modulo viene richiamato dalla selezione di una voce dal menù, ed il modulo richiamato viene riferito sempre alla directory principale (/usr/local/oratio). I file presenti in questa directory sono in realtà dei collegamenti al file am.pl, che gestisce i nomi degli utenti, le variabili globali e lo stack delle chiamate, che poi a sua volta richiama il file con lo stesso nome del collegamento da cui gli è arrivata la chiamata, presente nella sottodirectory bin/mozilla, ma il tutto deve passare per i file di configurazione presenti nella sottodirectory locale/it. I moduli presenti in bin/mozilla, a loro volta, si rifanno per l'accesso ai dati ad altri moduli presenti nella sottodirectory di Oratio chiamata SL. Quindi possiamo renderci conto che per realizzare anche un semplice modulo è indispensabile mettere mano a diversi file.

In questo esempio illustrerò come realizzare un modulo che aggiunga una voce al menù di Oratio, che richiami una pagina con un bottone, che una volta cliccato compia una certa operazione sul database. L'operazione sul database consisterà nella creazione di due listini di vendita: uno chiamato “Fornitore” che includa una sola volta tutti gli articoli presenti nella tabella “parts” e che abbia un prezzo pari a quello di listino, ed uno chiamato “Cliente” come quello precedente ma che abbia il prezzo identico a quello di vendita, naturalmente senza modificare altri listini eventualmente presenti. Chiaramente questa semplice miglioria serve per potere utilizzare il prezzo di listino e di vendita immesso nella schermata degli articoli dentro i moduli di acquisto/vendita senza dovere ricreare manualmente i listini ogni volta (l'aggiornamento di questi listini richiede una frazione di secondo sul mio sistema, mentre una simile operazione realizzata a mano richiede un tempo di circa 3 minuti e deve essere eseguita manualmente cancellando un listino e ricreandolo da zero ogni volta).

Il punto di partenza è un modulo già realizzato dentro Oratio, che per mia scelta personale è quello delle riparazioni del database, ma che per voi risulterà “scritto da zero”.

  1. Modifica del menù

  2. Creazione del collegamento

  3. Creazione del file di localizzazione

  4. Creazione del modulo

  5. Creazione del file di interfacciamento con PostgreSQL

Qui potete vedere il menù con l'aggiunta

Ed ecco in che punto aggiungere i dati per creare la voce nel menù

(Per potere rileggere il menù dovremmo ricaricare la pagina web di Oratio dal client e riautenticarci)

Adesso con il comando “ln -s am.pl aggiornalistini.pl” digitato nella directory principale di Oratio creeremmo il collegamento al modulo am.pl (il finale “.pl” indica che stiamo parlando di Perl!)

Usando un editor dovremmo adesso creare un file di localizzazione che abbia lo stesso nome del modulo perl richiamato dal menù, ma senza il “.pl” finale, e che sia posizionato dentro la directory “locale/it/”. Nella prima sezione del file di localizzazione ci sono i testi da tradurre, nella seconda ci sono i nomi delle funzioni (così premendo un bottone il cui testo è stato tradotto faremmo l'operazione inversa cercando la funzione a lui associata)

$self{texts} = {
  'Cannot update listprices!'    => 'Impossibile aggiornare i listini!',
#  'Inventory Duplicate'         => 'Inventario Duplicato',
  'Listprices updated!'         => 'Listini aggiornati!',
  'Listprices Updating'         => 'Aggiornamento Listini',
  'Update general-custom listprices on parts changes'   =>      'Aggiorna i listini generali/custom sui cambiamenti degli articoli',
  'Update Listprices'          => 'Aggiorna listini',
};

$self{subs} = {
#  'list_duplicate'              => 'list_duplicate',
  'repair'                      => 'repair',
  'aggiorna_listini'            => 'update_listprices',
};

1;

Per fare una cosa ben fatta dovremmo creare un file di localizzazione dentro tutte le cartelle di tutte le lingue.

Adesso dobbiamo preoccuparci di scrivere il modulo “bin/mozilla/aggiornalistini.pl”

#######################################################################
#
# Mox module for Oratio 
# 
# This program is a fork of
# SQL-Ledger Accounting (Version 2.0.5)
# Copyright (c) 1998-2002 Dieter Simader
# 
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# A copy of the GNU General Public Licence is also available at URL
# http://www.gnu.org/copyleft/gpl.html
# 
# The right to distribute this software or to use it for any purpose does 
# not give you the right to use Servicemarks (sm) or Trademarks (tm) of 
# Proxima Centauri S.r.l.. Use of them is covered in a separate agreement 
# (see http://www.oratio-project.org/trademarks/trademarks.htm ).
#######################################################################
#
# Listprices Updates
#
#======================================================================

use SL::Html;
use SL::UpdateListprices;

1;
# end of main


sub repair {
  my $myHtml = new Html($form); 
  
  $form->{title} = $locale->text('Listprices Updating');
  
  $myHtml->title($form->{title});
  $myHtml->begin_form("form1", $form->{script});

  $myHtml->set_numcols(1);

  $myHtml->text(qq|<table width="100%" border="0" class="border_table"><tr><td width="80%">|.$locale->text('Update general-custom listprices on parts changes').qq|</td><td align="right"><input type="submit" name="action" value="|.$locale->text("Update Listprices").qq|"></td></tr></table>|);     

  @button = (
        {type => "hidden", name => "login", value => "$form->{login}"},
        {type => "hidden", name => "password", value => "$form->{password}"},
        {type => "hidden", name => "path", value => "$form->{path}"}    
        );
         
  $myHtml->buttons(\@button);
  
  $myHtml->end_form();
  $myHtml->display();           
        
}

sub update_listprices {
  if (UpdateListprices->update_listprices(\%myconfig, \%$form)) {
        $form->header unless ($form->{header_html}); 
        $form->{header_html}++ unless ($form->{header_html});            
        print qq|<script language=javascript>alert ("|.$locale->text('Listprices updated!').qq|");</script>|;
#        &list_duplicate;
        &repair;
  }else{
                $form->error($locale->text('Cannot update listprices!'));        
  }             
}               



Anche in questo caso, per fare bene le cose avrei dovuto implementare anche un altro modulo nella cartella “bin/lynx/” (che verrebbe usato se ci collegassimo con un browser testuale anziché grafico)

Sotto la prima parte commentata appaiono due comandi che iniziano con “use” e che fanno riferimento alla directory “SL”: il primo si riferisce ad un modulo per la gestione dell'html (già presente) mentre l'altro si riferisce ad un modulo “UpdateListprices” ancora da implementare. Questo modulo in realtà terminerà con l'estensione “.pm” (ad indicare che si tratta di un modulo Perl), ae sarà posizionato dentro la sottocartella “SL/” di Oratio

#######################################################################
# 
# Mox custom module for Oratio
# 
# 
# This program is a fork of
# SQL-Ledger Accounting (Version 2.0.5)
# Copyright (c) 1998-2002 Dieter Simader
# 
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# A copy of the GNU General Public Licence is also available at URL
# http://www.gnu.org/copyleft/gpl.html
# 
# The right to distribute this software or to use it for any purpose does 
# not give you the right to use Servicemarks (sm) or Trademarks (tm) of 
# Proxima Centauri S.r.l.. Use of them is covered in a separate agreement 
# (see http://www.oratio-project.org/trademarks/trademarks.htm ).
#######################################################################
#
# UpdateListprices
#
#======================================================================


package UpdateListprices;

sub update_listprices {
  my ($self, $myconfig, $form) = @_;

  $form->{inventory_duplicate} = "";    

  # connect to database
  my $dbh = $form->dbconnect_noauto($myconfig);

    $query1 = qq|INSERT INTO pricelists(type_list,code,parts_id,start_date,end_date,price,discount1,discount2,discount3,discount4,discount5)
        SELECT 'V','Fornitore',parts.id,'2006-01-01','2010-01-01',0,0,0,0,0,0 
                FROM parts
                WHERE 
                        parts.id NOT IN(
                                SELECT parts_id 
                                        FROM pricelists
                                        WHERE pricelists.code LIKE 'Fornitore'
                                        )|;
$query2 = qq|UPDATE pricelists 
        SET price = parts.listprice
        Where 
                pricelists.parts_id = parts.id AND
                code LIKE 'Fornitore'|;

$query3 = qq|INSERT INTO pricelists(type_list,code,parts_id,start_date,end_date,price,discount1,discount2,discount3,discount4,discount5)
        SELECT 'C','ClienteA',parts.id,'2006-01-01','2010-01-01',0,0,0,0,0,0 
                FROM parts
                WHERE 
                        parts.id NOT IN(
                                SELECT parts_id 
                                        FROM pricelists
                                        WHERE pricelists.code LIKE 'ClienteA'
                                        )|;
$query4 = qq|UPDATE pricelists 
        SET price = parts.sellprice
        Where 
                pricelists.parts_id = parts.id AND
                code LIKE 'ClienteA'|;

$query5 = qq|INSERT INTO pricelists(type_list,code,parts_id,start_date,end_date,price,discount1,discount2,discount3,discount4,discount5)
        SELECT 'C','ClienteB',parts.id,'2006-01-01','2010-01-01',0,0,0,0,0,0 
                FROM parts
                WHERE 
                        parts.id NOT IN(
                                SELECT parts_id 
                                        FROM pricelists
                                        WHERE pricelists.code LIKE 'ClienteB'
                        )|;

$query6 = qq|UPDATE pricelists 
        SET price = parts.sellprice,
                discount1=-15
        Where 
                pricelists.parts_id = parts.id AND
                code LIKE 'ClienteB'|;

  $sth1 = $dbh->prepare($query1);
  $sth1->execute || $form->dberror($query1);
  $sth1->finish;

  $sth2 = $dbh->prepare($query2);
  $sth2->execute || $form->dberror($query2);
  $sth2->finish;

  $sth3 = $dbh->prepare($query3);
  $sth3->execute || $form->dberror($query3);
  $sth3->finish;

  $sth4 = $dbh->prepare($query4);
  $sth4->execute || $form->dberror($query4);
  $sth4->finish;

  $sth5 = $dbh->prepare($query5);
  $sth5->execute || $form->dberror($query5);
  $sth5->finish;

  $sth6 = $dbh->prepare($query6);
  $sth6->execute || $form->dberror($query6);
  $sth6->finish;

  my $rc = $dbh->commit;
  $dbh->disconnect;

  $rc;      
}


1;



Come potete notare questo modulo si preoccupa solo di eseguire le operazioni sul database usando codice SQL. Per prima cosa, le istruzioni SQL sono quattro (memorizzate dentro $query1, $query2, $query3 e $query4) e sono scritte in puro codice SQL e racchiuse tra i delimitatori del Perl “qq|......|”. Queste query sono uguali a due a due, quindi per completezza spiegherò cosa fanno le prime due:

Query1: la parte finale è una “SELECT” che trova tutti gli articoli presenti dentro il listino fornitore prima della modifica; la parte appena più esterna invece trova tutti gli articoli presenti dentro la tabella “parts” (quella generale degli articoli) ed elimina quelli che sono stati restituiti dalla query interna (quindi otteniamo la differenza: gli articoli che ancora non sono presenti); l'ultima parte si occupa di inserire questi articoli dentro il listino.

Query2: semplicemente copia il prezzo di listino (o di vendita) dentro il listino, leggendolo dalla tabella “parts”.

Per essere precisi ho dimenticato una cosa fondamentale: di cancellare dal listino gli articoli cancellati (cosa non difficile, visto il lavoro fatto con la query1), quindi saltuariamente a mano cancelleremmo tutti i listini, e richiamando la voce del menù di Oratio questi saranno correttamente ricreati.

A questo punto non vi resta che modificare questo ultimo modulo per fare quello di cui avete bisogno (oppure aggiungete un'altra funzione ed un altro bottone).

Aprile 2007 Riconciliare le giacenze di magazzino con l'inventario

Per riconciliare il magazzino con l'inventario (cioè per fare in modo che richiamando l'inventario e che controllando le giacenze dagli articoli si abbia lo stesso risultato) ho scritto i seguenti comandi sql, che ho salvato e che lancio ogni tanto usando Pg-Admin:

UPDATE parts
        SET onhand = 0
        WHERE parts.id NOT IN (SELECT id FROM parts)
;

UPDATE parts
        SET onhand = 
                (SELECT (SUM(waremove.qty)) 
                        FROM waremove 
                        WHERE part_id = parts.id 
                        GROUP BY part_id
)
where exists (SELECT (SUM(waremove.qty)) 
                        FROM waremove 
                        WHERE part_id = parts.id 
                        GROUP BY part_id

)
;

In sostanza il primo UPDATE mette a zero tutti gli articoli che non risultano movimentati nel magazzino, mentre il secondo risistema tutti quelli che sono stati movimentati. Tenete presente che la lettura delle giacenze dagli articoli viene fatta su una colonna della tabella “parts”, mentre l'inventario viene correttamente calcolato ogni volta dalla somma di tutti i movimenti della tabella “waremove”: ogni movimento genera una o più righe e per evitare di dovere ogni volta fare una query viene tenuto un contatore anche nella tabella degli articoli. Purtroppo alcuni movimenti sono tutt'altro che perfetti, e quindi i contatori “sballano”, ma tenendo presente che i movimenti in “waremove” mi sono sembrati affidabili in questa maniera possiamo rigenerare gli indici delle giacenze di tutti gli articoli.

Andando avanti ho automatizzato il processo inserendo questo script in cron (come descritto per il backup) in file chiamato Allineainventario.sql che da cron viene eseguito ogni ora

00 09,10,11,12,13,14,15,16,17 * * * /usr/bin/psql -f /home/mose/AllineaInventario.sql nomedb oratio

(oratio è un utente abilitato ad accedere a PostgreSQL)

Sincronizzazione DB

a causa dei template di stampa, per riuscire ad avere due aziende non ho trovato altro sistema funzionale che avere due db separati (es. db1 e db2) che pero' purtroppo hanno separate anche le tabelle dei clienti, dei venditori e degli articoli. Per questo motivo ho creato un programmino in perl da lanciare sotto linux che mi pare funzioni abbastanza bene e che effettui una riconciliazione "guidata" tra i due database e le tabelle summenzionate (o comunque quelle di vostra necessita' - il programma e' in realtà qualcosa di molto generico che può servire a "sincronizzare" qualsiasi coppia di db).

Il programma è auto esplicativo (ma non ho avuto tempo per ripulirlo), e manco a dirlo, è da usare a vostro completo rischio e pericolo.

Per ogni modifica chiede se copiare dal db1 al db2 o viceversa (e non opera mai da solo).

La mancanza maggiore e' che non gestisce le join, perché nel frattempo mi sono messo a fare la versione che gira sotto apache (per renderlo fruibile anche a chi non usa ssh come me, ma se guardate bene dentro ci sono anche le poche istruzioni per lanciarlo sotto consolle win) che ho appena finito di trasportare, quindi ricordatevi che per Oratio non vengono aggiornate le tabelle collegate, es. nei clienti non vengono aggiornati gli indirizzi secondari, le banche e simili.

Nel file SincroDb.pl dovete registrare dentro i nomi da voi usati per accedere ai db (nomi db, utente e password) in quanto, lo ripeto, questo programmino è general purpose e non accede ne si integra con Oratio.

file DbStruct.pm (da mettere nella stessa dir del prossimo file)

#######################################################################
# Package To hold data and functions for a Db
# Since there where many and many changes during develope....
# it's become necessary to hold all data for the db to be initialized
# into a single instance
#######################################################################

package DbStruct;
use strict;

#constructor
sub new {
        my (
                $class,
                $dba,               # name of db
                $dbb,               # name of db
                $dbha,              # db connection
                $dbhb,              # db connection
                $tbname,            # name of table
                $indexname,         # name of index row
                $descname,          # name of description row
                $colskip_arrayref   # reference to array of cols to skip from operations
        ) = @_;

        my $self = {
                dba              => $dba,
                dbb              => $dbb,
                dbha             => $dbha,
                dbhb             => $dbhb,
                tbname           => $tbname,
                indexname        => $indexname,
                descname         => $descname,
                colskip_arrayref => $colskip_arrayref
        };

         bless $self, $class;
        return $self;
}

sub swapDbs{
        my($self) = @_;
        my $temp = $self->{dba};
        $self->{dba} = $self->{dbb};
        $self->{dbb} = $temp;
        my $temp = $self->{dbha};
        $self->{dbha} = $self->{dbhb};
        $self->{dbhb} = $temp;
        
        return $self;
}
#sub $dba {
#       my ($self) = @_;
#       return $self->{_dba};
#}

1;

file SincroDb.pl

#!/usr/bin/perl -T
# Perl script per sincronizzare database
# (c) Pittau Mosè - Mox
# First release: 02.2007
#
# questo script è utile per sincronizzare delle tabelle scelte a piacere
# contenute in due diversi db creati a partire dallo stesso schema (le tabelle
# devono avere stesso nome e devono avere le stesse colonne con gli stessi
# tipi di dato). Le tabelle devono avere una colonna  di riferimento con un
# indice unico (su cui si effettueranno i controlli per sapere se nelle tabelle
# sono presenti lo stesso numero e tipo di record), una colonna "descrittiva"
# (per aiutare quando vengono mostrate le informazioni relative alla colonna
# indice, e per cercare di capire se è variato solo il valore dell'indice
# quando la descrizione è identica). Si possono anche fornite una serie di
# colonne sulle quali NON OPERARE confronti o copie (in pratica vengono
# ignorate da tutte le operazioni)
#
use lib "/home/mose/";
use strict;
use DBI;
use DbStruct;

my $testing = 0;
#uncomment to view all the sql
#my $testing = 1;

##########################
# db access
# set these to your's db's
my $db_host = 'localhost';    # hostname of db server
my $dba     = 'db1';          # name of 1st db
my $dbb     = 'db2';          # name of 2nd db
my $db_user = 'XXXXXXXXXX';          # username to gain access into db
my $db_pass = 'XXXXXXXXXX';    # password for db server
# end of db access
##########################

my $separator    = ( '-' x 80 ) . "\n";
my $endseparator = ( '*' x 80 ) . "\n";

my $db   = "dbi:Pg:dbname=${dba};host=${db_host}";
#my $db   = "dbi:PgPP:dbname=${dba};host=${db_host}";
my $dbha = connectDB( $db, $db_user, $db_pass );
my $db   = "dbi:Pg:dbname=${dbb};host=${db_host}";
#my $db   = "dbi:PgPP:dbname=${dbb};host=${db_host}";
my $dbhb = connectDB( $db, $db_user, $db_pass );

############################################################
# now call yours subs to check every pair of tables you want
# the subs are in the form of:
# Subname
# {
#       my @coltoskip = ("row1","row2",...);                    #name of columns to not be considered
#   tableCheck( $dbname1, $dbname2,                             #plain name (only for showing clearly differences)
#                               $db1_connection, $db2_connection,       #connections in the form of $db_connection = connectDB();
#                               $tablename,                                                     #name of table
#                               $indexname,                                                     #column name of unique indexes
#                               $descname,                                                      #column name of description (for showing clearly differences AND OTHER)
#                               \@coltoskip                                                     #pointer to array containing names
#                               );
############################################################

parts();
#payway();
vendor();
customer();



$dbha->disconnect;
$dbhb->disconnect;


sub payway {
        my @coltoskip = ("");
        tableCheck( $dba, $dbb, $dbha, $dbhb, "payway", "cd_type", "description", \@coltoskip );

        return;
}

sub vendor {
        my @coltoskip = ('id');
my $data = new DbStruct($dba, $dbb, $dbha, $dbhb, "vendor", "vendornumber", "name", \@coltoskip);
tableCheck($data);

        return;
}

sub customer {
        my @coltoskip = ('id');
        my $data = new DbStruct($dba, $dbb, $dbha, $dbhb, "customer", "customernumber", "name", \@coltoskip);
        tableCheck($data);

        return;
}

sub parts {

        my @coltoskip = ('id','onhand','priceupdate');
        my $data = new DbStruct($dba, $dbb, $dbha, $dbhb, "parts", "partnumber", "description", \@coltoskip);
        tableCheck($data);
        
        return;
}

## Other routines (internals)

sub keyPresent {
        my (
                $key_arrayref,    # reference to array
                $key              # key string
        ) = @_;
        my $retcode = 0;

        #print "@{$key_arrayref} ## $key_arrayref ## $key\n";
        foreach ( @{$key_arrayref} ) {
                $retcode++ if ( $_ eq $key );

                #print "$_ - $key - $retcode \n";
        }
        return $retcode;
}

sub insertSQLargs {
        my (
                $h_ref,                # hash reference to values
                $dbh,                  # db connection
                $keytoskip_arrayref    # reference to array containing keys to skip
        ) = @_;

        my $key   = "";
        my $value = "";
        my $hpos  = 0;
        foreach ( keys %{$h_ref} ) {
                if ( !keyPresent( $keytoskip_arrayref, $_ ) ) {
                        $key   = $key . '"' . $_ . '",';
                        $value = $value . $dbh->quote( $$h_ref{$_} ) . ',';
                }
        }
        ($key)   = $key   =~ m/(.*),/s;
        ($value) = $value =~ m/(.*),/s;
        return ( $key, $value );
}

sub tableIndexCheck {
my ($data) = @_;
        print $endseparator;
        print
          "Controllo indici mancanti tra [$data->{dba}] (Sorgente) e [$data->{dbb}] (Destinazione)\n";
        print "Tabella: [$data->{tbname}]\n";
        print $endseparator;

        my $query = qq|SELECT * FROM $data->{tbname}|;
        my $h     = doSQLquery( $data->{dbha}, $query );

        while ( my $h_ref = $h->fetchrow_hashref ) {
                my $query2 = qq|SELECT count(*) FROM $data->{tbname}|
                  . qq| WHERE( "$data->{indexname}" LIKE |
                  . $dbha->quote( $h_ref->{$data->{indexname}} ) . qq|)|;
                my $h2 = doSQLquery( $data->{dbhb}, $query2 );
                
                my $h2_ref = $h2->fetchrow_hashref;
                if ( $h2_ref->{count} < 1 ) {

                        #non ci sono corrispondenze tra i db
                        print $separator;
                        print "\[$h_ref->{$data->{indexname}}\]-[$h_ref->{$data->{descname}}]"
                          . " sembra non essere presente in [$data->{dbb}]\n";
                        my $query3 = qq|SELECT count(*) FROM $data->{tbname}|
                          . qq| WHERE( "$data->{descname}" LIKE |
                          . $dbha->quote( $h_ref->{$data->{descname}} ) . qq|)|;
                        my $h3 = doSQLquery( $data->{dbhb}, $query3 );
                        my $h3_ref = $h3->fetchrow_hashref;
                        if ( $h3_ref->{count} < 1 ) {

                                #non ci sono nomi simili
                                print " ... e nessuno sembra assomigliargli\n";
                                my $temp = input(
                                        ">> \'a\' per aggiungere su $data->{dbb}\n"
                                          . ">> \'i\' per ignorare\n" . ">>",
                                        "a|i"
                                );
                                if ( $temp eq 'a' ) {
                                        print "aggiunta ... ";          
                                        my ( $key, $var ) =
                                          insertSQLargs( $h_ref, $data->{dbhb}, $data->{colskip_arrayref} );
                                        my $query4 = qq|INSERT INTO $data->{tbname}($key) VALUES($var)|;
                                        my $h4 = doSQLquery( $data->{dbhb}, $query4, 1 );
                                        $h4->finish;
                                        print "fatto!\n";
                                }
                                else {
                                        print "ignorato!\n";
                                }
                        }
                        elsif ( $h3_ref->{count} == 1 ) {

                                #c'è un nome simile
                        my $query4 = qq|SELECT * FROM $data->{tbname}|
                          . qq| WHERE( "$data->{descname}" LIKE |
                          . $dbha->quote( $h_ref->{$data->{descname}} ) . qq|)|;
                        my $h4 = doSQLquery( $data->{dbhb}, $query4 );
                        my $h4_ref = $h4->fetchrow_hashref;

#                               $h3_ref = $h3->fetchrow_hashref;
                                print "\[$h4_ref->{$data->{indexname}}\]-\[$h4_ref->{$data->{descname}}\]"
                                  . " sembra assomigliargli\n";
                                my $sel = input(
                                        ">> \'s\' per sovrascrivere \[$h_ref->{$data->{indexname}}\]".
                                         "su \[$h4_ref->{$data->{indexname}}\] da \[$data->{dba}\]\n"
                                          . ">> \'a\' per aggiungere come nuovo record su \[$data->{dbb}\]\n"
                                          . ">> \'i\' per ignorare\n" . ">>",
                                        "i|s|a"
                                );
                                if ( $sel eq 's' ) {
                                        print "sovrascrittura ...";
                                        my $query5 =
                                            qq|UPDATE $data->{tbname} SET "$data->{indexname}" = |
                                          . $dbhb->quote( $h_ref->{$data->{indexname}} )
                                          . qq| WHERE("$data->{descname}" LIKE |
                                          . $dbhb->quote( $h4_ref->{$data->{descname}} ). qq|)|;
                                        my $h5 = doSQLquery( $data->{dbhb}, $query5 );
                                        $h5->finish;
                                        print "fatto!\n";
                                }
                                elsif ( $sel eq 'a' ) {
                                        print "aggiunta ... ";
                                        my ( $key, $var ) =
                                          insertSQLargs( $h_ref, $data->{dbhb}, $data->{colskip_arrayref} );
                                        my $query4 = qq|INSERT INTO $data->{tbname}($key) VALUES($var)|;
                                        my $h4 = doSQLquery( $data->{dbhb}, $query4, 1 );
                                        $h4->finish;
                                        print "fatto!\n";
                                }
                                else {
                                        print "ignorato!\n";
                                }
                        }
                        else {

                                #ci sono più nomi simili
                                print "ci sono piu' campi in $data->{desc} simili\n";
                                while ( $h3_ref = $h3->fetchrow_hashref ) {
                                        print "[$h3_ref->{$data->{indexname}}]-[$h3_ref->{$data->{descname}}]\n";
                                }
                                print "Puoi comunque aggiungerla come nuova riga\n";
                                my $temp = input(
                                        ">> \'a\' per aggiungere su [$data->{dbb}]\n"
                                          . ">> \'i\' per ignorare\n" . ">>",
                                        "a|i"
                                );
                                if ( $temp eq 'a' ) {
                                        print "aggiunta ... ";
                                        my ( $key, $var ) =
                                          insertSQLargs( $h_ref, $data->{dbhb}, $data->{colskip_arrayref} );
                                        my $query4 = qq|INSERT INTO $data->{tbname}($key) VALUES($var)|;
                                        my $h4 = doSQLquery( $data->{dbhb}, $query4, 1 );
                                        $h4->finish;
                                        print "fatto!\n";
                                }
                                else {
                                        print "ignorato!\n";
                                }
                        }
                        $h3->finish;
                }
                $h2->finish;
        }
        $h->finish();

        print $endseparator;
        print "Controllo terminato\n";
        print $endseparator;
}

sub tableContsCheck {
my ($data) = @_;
        print $endseparator;
        print "Controllo campi tra [$data->{dba}] (Sorgente) e [$data->{dbb}] (Destinazione)\n";
        print "Tabella: [$data->{tbname}]\n";   
        print $endseparator;

        my $query = qq|SELECT * FROM $data->{tbname}|;
        my $h     = doSQLquery( $data->{dbha}, $query );

        while ( my $h_ref = $h->fetchrow_hashref ) {
                my $query2 = qq|SELECT count(*) FROM $data->{tbname}|
                  . qq| WHERE( "$data->{indexname}" LIKE |
                  . $dbha->quote( $h_ref->{$data->{indexname}} ) . qq|)|;
                my $h2 = doSQLquery( $data->{dbhb}, $query2 );

my $h2_ref = $h2->fetchrow_hashref;
                if ( $h2_ref->{count} == 1 ) {
        my $query3 = $query
                  . qq| WHERE( "$data->{indexname}" LIKE |
                  . $dbha->quote( $h_ref->{$data->{indexname}} ) . qq|)|;
                my $h3     = doSQLquery( $data->{dbhb}, $query3 );
                my $h3_ref = $h3->fetchrow_hashref;

                        foreach ( keys %{$h_ref} ) {

                                #print "key ",$_," - ",$h_ref->{$_}," . ", $h2_ref->{$_},"\n";
                                if ( !defined( $h_ref->{$_} )
                                        && ( $h3_ref->{$_} eq '0' || $h3_ref->{$_} eq '1' ) )
                                {
                                        $h_ref->{$_} = 0;

                                        #print "h_ref HIT\n";
                                }
                                if ( !defined( $h3_ref->{$_} )
                                        && ( $h_ref->{$_} eq '0' || $h_ref->{$_} eq '1' ) )
                                {
                                        $h3_ref->{$_} = 0;

                                        #print "h2_ref HIT\n";
                                }

                 #print "evaluate key ",$_," - ",$h_ref->{$_}," . ", $h2_ref->{$_},"\n";
                                if ( !keyPresent( $data->{colskip_arrayref}, $_ ) ) {

                 #print "evaluate key ",$_," - ",$h_ref->{$_}," . ", $h2_ref->{$_},"\n";
                 #print "**",$h2_ref->{$_}," - ",$h_ref->{$_},"\n";
                                        if ( $h3_ref->{$_} ne $h_ref->{$_} ) {
                                                print $separator;
                                                print "Differenza nel record: "
                                                  . "\[$h_ref->{$data->{indexname}}\]-\[$h_ref->{$data->{descname}}\]\n";
                                                print "nel campo \[$_\] (segue \[$data->{dba}\] - \[$data->{dbb}\])\n";
                                                print "\[$h_ref->{$_}\]\n\[$h3_ref->{$_}\]\n";
                                                my $sel = input(
                                                            ">> \'s\' per copiare il codice da "
                                                          . "[$data->{dba}]([$h_ref->{$_}]) a [$data->{dbb}] ([$h3_ref->{$_}])\n"
                                                          . ">> \'d\' per copiare il codice da "
                                                          . "[$data->{dbb}] ([$h3_ref->{$_}]) a [$data->{dba}] ([$h_ref->{$_}])\n"
                                                          . ">> \'i\' per ignorare\n"
                                                          . ">>",    
                                                        "s|d|i"
                                                );
                                                if ( $sel eq 's' ) {
                                                        print "copia da [$data->{dba}] di [$h_ref->{$_}] ...";
                                                        my $query4 =
                                                            qq|UPDATE $data->{tbname} SET "$_" = |
                                                          . $dbhb->quote( $h_ref->{$_} )
                                                          . qq| WHERE("$data->{indexname}" LIKE |
                                                          . $dbhb->quote( $h_ref->{$data->{indexname}} ) . qq|)|;
                                                        my $h4 = doSQLquery( $data->{dbhb}, $query4 );
                                                        $h4->finish;
                                                        print "fatto!\n";
                                                }
                                                elsif ( $sel eq 'd' ) {
                                                        print "copia da [$data->{dbb}] di [$h3_ref->{$_}] ...";
                                                        my $query4 =
                                                            qq|UPDATE $data->{tbname} SET "$_" = |
                                                          . $dbhb->quote( $h3_ref->{$_} )
                                                          . qq| WHERE("$data->{indexname}" LIKE |
                                                          . $dbhb->quote( $h3_ref->{$data->{indexname}} ) . qq|)|;
                                                        my $h4 = doSQLquery( $data->{dbha}, $query4 );
                                                        $h4->finish;
                                                        print "fatto!\n";
                                                }
                                                else {
                                                        print "ignorato!\n";
                                                }
                                        }
                                }
                        }
                }
                $h2->finish;
        }
        $h->finish();

        print $endseparator;
        print "Controllo terminato\n";
        print $endseparator;
}

sub tableCheck {
        my ($data) = @_;
        print "CONTROLLO TABELLA\n";

tableIndexCheck( $data);
$data->swapDbs();
tableIndexCheck( $data);
$data->swapDbs();
tableContsCheck( $data);
}

sub input {
        my ( $text, $keys ) = @_;
        my $input = "";
        while ( !( $input =~ /^($keys)/ ) ) {
                print $text, ":";
                $input = lc(<STDIN>);
        }
        return substr( $input, 0, 1 );
}

sub doSQLquery {
        my ( $conn, $command ) = @_;

        #    print $command, "\n";
        if ($testing) {
                print "Connessione: $conn\n";
                print "Comando: $command\n";
        }
        my $sth = $conn->prepare($command);
        print "status is ", $conn->errstr, "\n" if $conn->err;

        if ($testing) {
                print "prepare completato\n";
        }
        my $nrows = $sth->execute;
        if ($testing) {
                print "query eseguita\n";
        }
        print "status is ",      $conn->err,    "\n" if $conn->err;
        print "error message: ", $conn->errstr, "\n" if $conn->err;

        #    print "number of rows returned (unreliable) = ",$sth->rows,"\n";
        #    print "number of fields returned = ",$sth->{NUM_OF_FIELDS},"\n";
        #    print "fields: ", join(" ",@{$sth->{NAME}}), "\n";
        return $sth;
}

sub connectDB {
        my ( $db, $db_user, $db_pass ) = @_;
        my $dbh =
          DBI->connect( $db, $db_user, $db_pass,
                { RaiseError => 1, AutoCommit => 1 } )
          || die "Error connecting to the database: $DBI::errstr\n";
        printf "Connected to %s: State is %s\n", $db, $dbh->state || "OK";
        return ($dbh);
}

Ordinare le scadenze delle soluzioni di pagamento

Può capitare di aggiungere diverse scadenze di pagamento a quelle standard, ed è possibile arrivare ad un punto in cui sarebbe molto più semplice avere un criterio per poterle mettere in ordine secondo le nostre esigenze (30/60/... visioni, acconti, ecc).

Prima di partire bisogna fare giusto due considerazioni:

Detto questo illustrerò come aggiungere un ulteriore indice che si occupi solo dell'ordinamento.

Il file che si occupa di visualizzare i pagamenti è il “oratio/bin/mozilla/tables.pl”, che a sua volta si serve di un altro modulo dentro “oratio/SL/Tables.pm”

Per prima cosa ci spostiamo nella dir. Di lavoro:

- cd /usr/local/oratio/bin/mozilla

creiamo una copia di sicurezza

- sudo cp tables.pl tables.pl.bck

Di seguito riporto il risultato delle differenze tra il file originale e di quello da me modificato tramite il comando unix “diff -w tables.pl.original tables.pl.modificato” che riporta le differenze ed il numero di linea per trasformare un file in un altro (nell'ordine da me richiamato: quello originale in quello modificato). Quello che segue va interpretato in questa maniera:

num linea file originale, operazione da compiere (è un carattere), numero di linea file modificato

linee file originale

---

linee file modificato

Il carattere deve essere interpretato come: “c” sovrascrivere l'originale con la dest (o modificarlo per renderlo uguale); “a” aggiungere in quel punto quanto riportato in dest. (in questo caso, non essendoci una differenza ma solo una mancanza, non vengono riportate le righe del file originale seguite dai 3 trattini).

Partendo dalla considerazione che io ho solo modificato o aggiunto e non cancellato, non guardate il numero di linea del file creato da me (ho riformattato il tutto) ma controllate solo quello dell'originale, e fate le modifiche tramite il vostro editor (nano?).

511a507,509
>       <td>&nbsp;</td>
>       <th align=left nowrap class=label>|.$locale->text('Seq').qq|</th> 
>
 
519a518,519
>       <td>&nbsp;</td>
>     <td><input name=seq class=field size=10 value="$form->{seq}"></td>
1174c1171
<   map { $form->{$_} = $form->unescape($form->{$_}) } (cd_sol, description, finemese, giorni);
---
>     map { $form->{$_} = $form->unescape($form->{$_}) } (cd_sol, description, finemese, giorni, seq);

1180,1181c1177,1178
< 
<   @column_index = $form->sort_columns(qw(cd_sol description finemese giorni));
---
>     #mox
>     @column_index = $form->sort_columns(qw(cd_sol description finemese giorni seq));
1186a1184
>     $column_header{seq} = qq|<th><a class=listheading href=$href&sort=seq>|.$locale->text('Seq').qq|</a></th>|;
1243a1243,1244
>         $column_data{seq} = qq|<td class="field">$ref->{seq}</td>|;
>
 
1247c1248
<       $column_data{cd_sol} = qq|<td class="field"><a href=$form->{script}?action=edit&cd_sol=$ref->{cd_sol}&path=$form->{path}&db=$form->{db}&login=$form->{login}&password=$form->{password}&callback=$callback&description=$description&finemese=$ref->{finemese}&byhand=$ref->{byhand}&rowcount=$ref->{rowcount}&$giorni{"$ref->{cd_sol}"}>$ref->{cd_sol}</a></td>|; 
---
>       $column_data{cd_sol} = qq|<td class="field"><a href=$form->{script}?action=edit&cd_sol=$ref->{cd_sol}&path=$form->{path}&db=$form->{db}&login=$form->{login}&password=$form->{password}&callback=$callback&description=$description&finemese=$ref->{finemese}&byhand=$ref->{byhand}&rowcount=$ref->{rowcount}&seq=$ref->{seq}&$giorni{"$ref->{cd_sol}"}>$ref->{cd_sol}</a></td>|; 

1250a1252,1253
>         $column_data{seq} = qq|<td class="field">$ref->{seq}</td>|;
> 

come potete vedere ho aggiunto dei riferimenti con “seq” solitamente a fine linea (sono ciò che per me è una sequenza).

Fatto questo ci spostiamo nella dir SL

- cd /usr/local/oratio/SL

creiamo una copia di sicurezza

- sudo cp Tables.pm Tables.pm.original

e

- sudo cp Payments.pm Payments.pm.original

(qust'ultimo viene usato dagli altri moduli, come le offerte, per visualizzare i pagamenti)

A questo punto, sempre tramite il nostro editor preferito apportiamo le seguenti modifiche: (il risultato è ottenuto tramite less -N, che mostra il numero di linea, ma come è facilmente intuibile dal risultato ho solo fatto poche modifiche e questo listato rispetto al precedente è molto più corto).

Dentro Tables.pm

171 my $query = qq|SELECT payterms.cd_term AS cd_sol,payterms.description,

172 payterms.endmonth AS finemese,payterms.byhand,

173 payterms_det.days AS giorni, cd_term1 AS seq

174 FROM payterms FULL OUTER JOIN payterms_det

175 ON payterms.cd_term=payterms_det.cd_term

176 ORDER BY $ord|;



412 $query = qq|INSERT INTO payterms

413 (cd_term, description, endmonth, byhand, cd_term1)

414 VALUES ($form->{cd_sol}, '$form->{description}',

415 '$form->{finemese}', '$form->{byhand}', '$form->{seq}')|;



Fatte queste modifiche dovreste avere una nuova colonna dentro la voce “Strumenti->Tabelle->Soluzioni di pagamento” chiamata “seq”, e compilando questa colonna (facendo la modifica delle voci) avrete un nuovo ordine (consiglio di saltare sempre di 10 in 10 per avere spazio per mettere altre voci mano a mano che occorrono) che verrà richiamato dagli altri moduli modificando il file Payments.pm come segue

56 $query = qq|SELECT cd_term AS cd_sol, description, byhand, cd_term1 AS seq

57 FROM payterms ORDER BY cd_term1|;

58 # FROM payterms ORDER BY cd_term|;

Ultima considerazione: non ho realmente creato una nuova colonna, ma ho sfruttato una colonna esistente dentro la table payments che mi è sembrata inutilizzata (“cd_term1”) come mia colonna per creare una sequenza. Potevo anche aggiungerne una, ma mi è sembrato che ci siano numerose altre colonne inutilizzate, così non gli ho neppure cambiato il nome: è bastato aggiungere un “cd_term1 AS seq” nella sezione dell SQL per ottenere la lista dei risultati come se fossero appartenuti ad una ipotetica colonna chiamata seq....