fattureCCSR-downloader: auto-open pdf after merge

Signed-off-by: Ettore Dreucci <ettore.dreucci@gmail.com>
This commit is contained in:
2020-12-01 00:52:58 +01:00
parent 0e80834b44
commit 559c29612d
5 changed files with 20 additions and 376 deletions

View File

@@ -1,5 +1,8 @@
"""ask for an input file (.xlsx) and an output file (.pdf) and downloads and unite every invoice"""
import sys
import os
import subprocess
import shutil
import tempfile
import requests
@@ -30,6 +33,13 @@ def get_invoices_info(input_file_path: str) -> dict:
return invoices
def open_file(file_path):
"""open a file with the default software"""
if sys.platform == "win32":
os.startfile(file_path) # pylint: disable=maybe-no-member
else:
opener = "open" if sys.platform == "darwin" else "xdg-open"
subprocess.call([opener, file_path])
def download_invoices(input_file_path: str, output_file_path: str, username: str, password: str):
"""download invoices from CCSR"""
@@ -42,11 +52,10 @@ def download_invoices(input_file_path: str, output_file_path: str, username: str
tmp_dir = tempfile.mkdtemp()
invoices_count = len(invoices)
processed_count = 0
downloaded_count = 0
for invoice_id, invoice in invoices.items():
resp = session.get(invoice["url"])
processed_count += 1
if resp.status_code == 200:
with open(tmp_dir+"/"+invoice_id+".pdf", "wb") as output_file:
output_file.write(resp.content)
@@ -55,13 +64,14 @@ def download_invoices(input_file_path: str, output_file_path: str, username: str
try:
PyPDF2.PdfFileReader(open(invoice["path"], "rb"))
except (PyPDF2.utils.PdfReadError, OSError):
logger.downloader_logger.error("%d/%d fattura %s corrotta!", processed_count, invoices_count, invoice_id)
logger.downloader_logger.error("fattura %s corrotta!", invoice_id)
invoice["good"] = False
else:
logger.downloader_logger.info("%d/%d scaricata fattura %s in %s", processed_count, invoices_count, invoice_id, invoice["path"])
downloaded_count += 1
logger.downloader_logger.info("%d/%d scaricata fattura %s in %s", downloaded_count, invoices_count, invoice_id, invoice["path"])
invoice["good"] = True
else:
logger.downloader_logger.error("%d/%d impossibile scaricare fattura %s: %d", processed_count, invoices_count, invoice_id, resp.status_code)
logger.downloader_logger.error("impossibile scaricare fattura %s: %d", invoice_id, resp.status_code)
invoice["good"] = False
merger = PyPDF2.PdfFileMerger()
@@ -69,7 +79,9 @@ def download_invoices(input_file_path: str, output_file_path: str, username: str
if invoice["good"]:
merger.append(PyPDF2.PdfFileReader(open(invoice["path"], "rb")))
merger.write(output_file_path)
open_file(output_file_path)
shutil.rmtree(tmp_dir, ignore_errors=True)
logger.downloader_logger.info("Download terminato. Il pdf contenente le fatture si trova in %s", output_file_path)

View File

@@ -1,313 +0,0 @@
package main
import (
"bufio"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"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 := 0; i <= int(sheet.MaxRow); i++ {
row := sheet.Row(i)
if strings.Contains(row.Col(8), "CCSR") {
id := strings.ReplaceAll(row.Col(8), "/", "-")
invoiceIDs = append(invoiceIDs, id)
}
}
return invoiceIDs
}
func convertXLStoFODS(fileName string) string {
var sofficePath = "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.Printf("Il numero di fatture da scaricare non corrisponde al numero di URL individuati nel file. IDs: %d/URLs: %d", len(ids), len(urls))
}
wg := sync.WaitGroup{}
sem := make(chan bool, 30)
mu := sync.Mutex{}
invoiceNum := minOf(len(ids), len(urls))
downloadCount := 0
downloadedFiles := make([]string, 0)
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 if api.ValidateFile(out, nil) != nil {
log.Printf("Errore nella validazione del file %v\n", out)
} else {
mu.Lock()
downloadedFiles = append(downloadedFiles, out)
downloadCount++
mu.Unlock()
}
<-sem
wg.Done()
}(i)
}
wg.Wait()
log.Printf("Scaricate %d/%d fatture\n", downloadCount, invoiceNum)
sort.Strings(downloadedFiles)
return downloadedFiles
}
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 minOf(vars ...int) int {
min := vars[0]
for _, i := range vars {
if min > i {
min = i
}
}
return min
}
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)
}

View File

@@ -1,12 +0,0 @@
module github.com/Noettore/fattureSanRossore/fattureDownloader
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
)

View File

@@ -1,45 +0,0 @@
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

@@ -147,6 +147,7 @@ class FattureCCSRFrame(wx.Frame):
def file_picker_changed(self, event):
"""event raised when the input file path is changed"""
self.input_file_path = event.GetEventObject().GetPath()
#TODO: error frame
try:
self.input_file_ext = utils.file_extension(self.input_file_path, (".xml", ".csv", ".xlsx"))
except exc.NoFileError as handled_exception:
@@ -185,6 +186,7 @@ class FattureCCSRFrame(wx.Frame):
return
self.log_dialog = LogDialog(self, "Conversione delle fatture in TRAF2000", CONVERT_ACTION)
self.log_dialog.Show()
#TODO: error frame
try:
traf2000_converter.convert(self.input_file_path, self.output_file_path)
except exc.NoFileError as handled_exception: