TRAF2000: CSV to TeamSystem TRAF2000 python converter

Signed-off-by: Ettore Dreucci <ettore.dreucci@gmail.com>
This commit is contained in:
2020-11-06 09:03:41 +01:00
parent 2f438a5f95
commit cc8c309109
16 changed files with 208 additions and 55 deletions

View File

@@ -0,0 +1,48 @@
# fattureSanRossore Downloader
[![MIT License](https://img.shields.io/badge/license-MIT-blue)](../LICENSE.md) [![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/Noettore/fattureSanRossore)](#) [![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/Noettore/fattureSanRossore?filename=fattureDownloader%2Fgo.mod)](#) [![GitHub last commit](https://img.shields.io/github/last-commit/Noettore/fattureSanRossore)](https://github.com/Noettore/fattureSanRossore/commit/master)
## Getting Started
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
### Prerequisites
- [Go](https://golang.org/) v1.14 or greater
- [LibreOffice](https://www.libreoffice.org)
### Installing
To download and install follow this steps:
1. Clone this repo (or download it):
`$ git clone https://github.com/Noettore/fattureSanRossore`
2. Download dependencies, build and install:
```
$ cd path/to/fattureSanRossore/fattureDownloader
$ go install
```
### Run
To run you can simply execute the built binary.
Follow the instruction in the [manual](manual/Manuale.md) (only in italian)
## Dependencies
- [github.com/extrame/xls](https://github.com/extrame/xls)
- [github.com/pdfcpu/pdfcpu](https://github.com/pdfcpu/pdfcpu/pkg/api)
- [github.com/sqweek/dialog](https://github.com/sqweek/dialog)
- [mvdan.cc/xurls](https://mvdan.cc/xurls/v2)
## Author
- [**Ettore Dreucci**](https://ettore.dreucci.it)
## License
This project is licensed under the MIT License - see the [LICENSE.md](../LICENSE.md) file for details

View File

@@ -0,0 +1,297 @@
package main
import (
"bufio"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/extrame/xls"
"github.com/pdfcpu/pdfcpu/pkg/api"
"github.com/sqweek/dialog"
"mvdan.cc/xurls/v2"
)
var tmpDir string
var outDir string
var mw io.Writer
var logPath string
var exitWithError bool
func getInvoiceIDs(fileName string) []string {
xlFile, err := xls.Open(fileName, "utf-8")
if err != nil {
exitWithError = true
log.Panicf("Impossibile aprire il file xls: %v\n", err)
}
sheet := xlFile.GetSheet(0)
if sheet == nil {
exitWithError = true
log.Panicf("Impossibile aprire il foglio nell'xls: %v\n", err)
}
var invoiceIDs []string
for i := 4; i <= int(sheet.MaxRow); i++ {
row := sheet.Row(i)
if row.Col(8) != "" {
id := strings.ReplaceAll(row.Col(8), "/", "-")
invoiceIDs = append(invoiceIDs, id)
}
}
return invoiceIDs
}
func convertXLStoFODS(fileName string) string {
var sofficePath string = "libreoffice"
if runtime.GOOS == "windows" {
sofficePath = filepath.FromSlash("C:/Program Files/LibreOffice/program/soffice.exe")
}
cmd := exec.Command(sofficePath, "--convert-to", "fods", "--outdir", outDir, fileName)
cmd.Stdout = mw
cmd.Stderr = mw
err := cmd.Run()
if err != nil {
exitWithError = true
log.Panicf("Impossibile convertire l'XLS in FODS: %v\n", err)
}
return (outDir + "/" + strings.TrimSuffix(filepath.Base(fileName), filepath.Ext(fileName)) + ".fods")
}
func getInvoiceURLs(fileName string) []string {
fods := convertXLStoFODS(fileName)
f, err := os.Open(fods)
if err != nil {
exitWithError = true
log.Panicf("Impossibile aprire il FODS convertito: %v\n", err)
}
defer func() {
err = f.Close()
if err != nil {
log.Printf("Impossibile chiudere il file %v: %v\n", fods, err)
}
}()
var invoiceURLs []string
s := bufio.NewScanner(f)
for s.Scan() {
line := s.Text()
if strings.Contains(line, "http://report.casadicurasanrossore.it:9146/files/get?type=invoice&amp;id=") {
url := xurls.Strict().FindString(line)
url = strings.ReplaceAll(url, "&amp;", "&")
invoiceURLs = append(invoiceURLs, url)
}
}
if err := s.Err(); err != nil {
exitWithError = true
log.Panicf("Impossibile leggere dal file %v: %v\n", fods, err)
}
return invoiceURLs
}
func checkFile(fileName string) string {
_, err := os.Stat(fileName)
if err != nil {
exitWithError = true
log.Panicf("Errore nell'apertura del file %v: %v\n", fileName, err)
}
absPath, err := filepath.Abs(fileName)
if err != nil {
exitWithError = true
log.Panicf("Impossibile recuperare il percorso assoluto del file %v: %v\n", fileName, err)
}
return absPath
}
func downloadFile(fileName string, url string) error {
resp, err := http.Get(url)
if err != nil {
resp.Body.Close()
return err
}
out, err := os.Create(fileName)
if err != nil {
out.Close()
return err
}
_, err = io.Copy(out, resp.Body)
out.Close()
resp.Body.Close()
return err
}
func createTmpDir() string {
dir := filepath.FromSlash(tmpDir + "/fattureSanRossore")
if _, err := os.Stat(dir); err == nil {
log.Printf("Pulizia della directory temporanea pre-esistente")
err := os.RemoveAll(dir)
if err != nil {
exitWithError = true
log.Panicf("Impossibile eliminare la directory temporanea pre-esistente %v: %v\n", dir, err)
}
} else if !os.IsNotExist(err) {
exitWithError = true
log.Panicf("Impossibile eseguire lo stat sulla directory temporanea %v: %v\n", dir, err)
}
err := os.Mkdir(dir, os.ModePerm)
if err != nil {
exitWithError = true
log.Panicf("Impossibile creare la directory temporanea di salvataggio %v: %v\n", dir, err)
}
return dir
}
func downloadInvoices(ids []string, urls []string) []string {
if len(ids) != len(urls) {
exitWithError = true
log.Panicf("Il numero di fatture da scaricare non corrisponde al numero di URL individuati nel file")
}
wg := sync.WaitGroup{}
sem := make(chan bool, 30)
mu := sync.Mutex{}
invoiceNum := len(ids)
downloadCount := 0
downloadedFiles := make([]string, invoiceNum)
log.Printf("Inizio il download di %d fatture\n", invoiceNum)
for i := 0; i < invoiceNum; i++ {
id := ids[i]
url := urls[i]
out := filepath.FromSlash(outDir + "/" + id + ".pdf")
wg.Add(1)
go func(i int) {
sem <- true
log.Printf("Scaricamento di %v\n", id)
err := downloadFile(out, url)
if err != nil {
log.Printf("Impossibile scaricare il file %v: %v\n", url, err)
} else {
downloadedFiles[i] = out
mu.Lock()
downloadCount++
mu.Unlock()
}
<-sem
wg.Done()
}(i)
}
wg.Wait()
log.Printf("Scaricate %d/%d fatture\n", downloadCount, invoiceNum)
return downloadedFiles[:downloadCount]
}
func mergeInvoices(files []string) string {
out, err := dialog.File().Filter("PDF files", "pdf").Title("Scegli dove salvare le fatture unite").Save()
if err != nil {
exitWithError = true
log.Panicf("Impossibile recuperare il file selezionato: %v\n", err)
}
if filepath.Ext(out) == "" {
out += ".pdf"
}
err = api.MergeFile(files, out, nil)
if err != nil {
exitWithError = true
log.Panicf("Impossibile unire i pdf: %v\nFatture singole non rimosse\n", err)
}
return out
}
func openPDF(fileName string) {
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("cmd", "/C start "+fileName)
} else {
cmd = exec.Command("xdg-open", fileName)
}
cmd.Stdout = mw
cmd.Stderr = mw
err := cmd.Start()
if err != nil {
exitWithError = true
log.Panicf("Impossibile aprire il pdf con le fatture unite: %v\n", err)
}
}
func cleanTmpDir() {
files, err := ioutil.ReadDir(outDir)
if err != nil {
exitWithError = true
log.Panicf("Impossibile recuperare la lista di file creati nella directory temporanea: %v\n", err)
}
for _, file := range files {
err = os.Remove(filepath.FromSlash(outDir + "/" + file.Name()))
if err != nil {
log.Printf("Impossibile eliminare la fattura singola %v: %v\n", file, err)
}
}
err = os.Remove(outDir)
if err != nil {
log.Printf("Impossibile eliminare la directory temporanea %v: %v\n", outDir, err)
}
}
func main() {
exitWithError = false
args := os.Args
tmpDir = os.TempDir()
if runtime.GOOS == "linux" {
logPath = tmpDir + "/log_fattureSanRossore.log"
} else {
logPath = filepath.FromSlash(tmpDir + "/log_fattureSanRossore.txt")
}
logFile, err := os.OpenFile(filepath.FromSlash(logPath), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
if err != nil {
exitWithError = true
log.Panicf("Impossibile creare il file di log: %v\n", err)
}
mw = io.MultiWriter(os.Stderr, logFile)
log.SetOutput(mw)
defer func() {
if !exitWithError {
cleanTmpDir()
} else {
log.Println("I file temporanei non sono stati eliminati per poterli riesaminare")
}
log.Printf("Log file salvato in %v\n", logPath)
}()
outDir = createTmpDir()
var fileName string
if len(args) < 2 {
var err error
fileName, err = dialog.File().Filter("XLS files", "xls").Load()
if err != nil {
exitWithError = true
log.Panicf("Impossibile recuperare il file selezionato: %v\n", err)
}
} else {
fileName = args[1]
}
filePath := checkFile(fileName)
IDs := getInvoiceIDs(filePath)
URLs := getInvoiceURLs(filePath)
dlFiles := downloadInvoices(IDs, URLs)
pdf := mergeInvoices(dlFiles)
openPDF(pdf)
}

12
fattureDownloader/go.mod Normal file
View File

@@ -0,0 +1,12 @@
module github.com/Noettore/fattureSanRossore
go 1.14
require (
github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7 // indirect
github.com/extrame/xls v0.0.1
github.com/gotk3/gotk3 v0.4.0 // indirect
github.com/pdfcpu/pdfcpu v0.3.2
github.com/sqweek/dialog v0.0.0-20200304031853-0dcd55bfe06a
mvdan.cc/xurls/v2 v2.2.0
)

45
fattureDownloader/go.sum Normal file
View File

@@ -0,0 +1,45 @@
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=
github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf h1:FPsprx82rdrX2jiKyS17BH6IrTmUBYqZa/CXT4uvb+I=
github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I=
github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7 h1:n+nk0bNe2+gVbRI8WRbLFVwwcBQ0rr5p+gzkKb6ol8c=
github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7/go.mod h1:GPpMrAfHdb8IdQ1/R2uIRBsNfnPnwsYE9YYI5WyY1zw=
github.com/extrame/xls v0.0.1 h1:jI7L/o3z73TyyENPopsLS/Jlekm3nF1a/kF5hKBvy/k=
github.com/extrame/xls v0.0.1/go.mod h1:iACcgahst7BboCpIMSpnFs4SKyU9ZjsvZBfNbUxZOJI=
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/gotk3/gotk3 v0.4.0 h1:TIuhyQitGeRTxOQIV3AJlYtEWWJpC74JHwAIsxlH8MU=
github.com/gotk3/gotk3 v0.4.0/go.mod h1:Eew3QBwAOBTrfFFDmsDE5wZWbcagBL1NUslj1GhRveo=
github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk=
github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ=
github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk=
github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEdDpxIrtFHTgnvYzA8sCQz8luv94=
github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-gtk v0.0.0-20180216084204-5a311a1830ab/go.mod h1:PwzwfeB5syFHXORC3MtPylVcjIoTDT/9cvkKpEndGVI=
github.com/mattn/go-pointer v0.0.0-20171114154726-1d30dc4b6f28/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
github.com/pdfcpu/pdfcpu v0.3.2 h1:oHnvW3KUed/jVLnNcN5FyJsmInXAyyfoZ4yG3mxJdk8=
github.com/pdfcpu/pdfcpu v0.3.2/go.mod h1:/ULj8B76ZnB4445B0yuSASQqlN0kEO+khtEnmPdEoXU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/skelterjohn/go.wde v0.0.0-20180104102407-a0324cbf3ffe/go.mod h1:zXxNsJHeUYIqpg890APBNEn9GoCbA4Cdnvuv3mx4fBk=
github.com/sqweek/dialog v0.0.0-20200304031853-0dcd55bfe06a h1:BHv3lo0aZg2IPfeBfgYFjq48DoKehP+JC9dtACUEmT4=
github.com/sqweek/dialog v0.0.0-20200304031853-0dcd55bfe06a/go.mod h1:QSrNdZLZB8VoFPGlZ2vDuA2oNaVdhld3g0PZLc7soX8=
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
github.com/tealeg/xlsx/v2 v2.0.1 h1:RP+VEscpPFjH2FnpKh1p9HVLAk1htqb9Urcxi2AU1ns=
github.com/tealeg/xlsx/v2 v2.0.1/go.mod h1:l9GvhCCjdaIGkAyZcFedDALcYcXUOei55f6umRMOz9c=
golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52 h1:2fktqPPvDiVEEVT/vSTeoUPXfmRxRaGy6GU8jypvEn0=
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
mvdan.cc/xurls v1.1.0 h1:kj0j2lonKseISJCiq1Tfk+iTv65dDGCl0rTbanXJGGc=
mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=

View File

@@ -0,0 +1,56 @@
# Manuale operativo `fattureSanRossore.exe`
Lutility `fattureSanRossore` ha come unico obiettivo quello di scaricare automaticamente le fatture emesse dalla Casa di Cura San Rossore per conto di un medico.
Per utilizzarla è necessario essere in possesso di un file xls (Microsoft Excel) generato e scaricato dal portale `STAT_FATTURATO_CTERZI` raggiungibile qui:
[https://report.casadicurasanrossore.it:8443/Reports/Pages/Folder.aspx](https://report.casadicurasanrossore.it:8443/Reports/Pages/Folder.aspx).
Ecco in dettaglio la procedura da seguire
## 1. Download del report in formato `xls`
Al termine di questa procedura avremo a disposizione il report in formato `xls` delle fatture emesse dalla Casa di Cura San Rossore per conto terzi. Il report verrà poi utilizzato nella fase seguente per il download delle fatture.
1. Aprire lindirizzo del portale di generazione del report:
[https://report.casadicurasanrossore.it:8443/Reports/Pages/Folder.aspx](https://report.casadicurasanrossore.it:8443/Reports/Pages/Folder.aspx)
2. Effettuare il login con le credenziali del medico accreditato
3. Nella pagina fare click su `STAT_FATTURATO_CTERZI` per aprire la procedura di generazione dei report
![](img/1.png)
4. Modificare le date nei campi `data dal` e `data al` in modo che rispecchino il periodo di interesse
5. Fare click sul pulsante `Visualizza report` presente nella parte alta della pagina, sulla destra
![2](img/2.png)
6. Fare click sul pulsante raffigurante licona di salvataggio
7. Selezionare lopzione `Excel` nel menù a tendina
![3](img/3.png)
8. Salvare il file in una posizione nota
## 2. Avvio della utility e download delle fatture
1. Avviare la utility facendo doppio click sulleseguibile `fattureSanRossore-win-0.1.exe`
2. Nella finestra di dialogo selezionare il report in formato `xls` generato nella fase precedente
![4](img/4.png)
3. Attendere il completamento dei download
![5](img/5.png)
4. Nella finestra di dialogo selezionare la cartella e dare un nome al file `pdf` che conterrà le fatture scaricate
![6](img/6.png)
5. Si aprirà automaticamente il lettore pdf con il file generato

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB