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""" """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 shutil
import tempfile import tempfile
import requests import requests
@@ -30,6 +33,13 @@ def get_invoices_info(input_file_path: str) -> dict:
return invoices 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): def download_invoices(input_file_path: str, output_file_path: str, username: str, password: str):
"""download invoices from CCSR""" """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() tmp_dir = tempfile.mkdtemp()
invoices_count = len(invoices) invoices_count = len(invoices)
processed_count = 0 downloaded_count = 0
for invoice_id, invoice in invoices.items(): for invoice_id, invoice in invoices.items():
resp = session.get(invoice["url"]) resp = session.get(invoice["url"])
processed_count += 1
if resp.status_code == 200: if resp.status_code == 200:
with open(tmp_dir+"/"+invoice_id+".pdf", "wb") as output_file: with open(tmp_dir+"/"+invoice_id+".pdf", "wb") as output_file:
output_file.write(resp.content) output_file.write(resp.content)
@@ -55,13 +64,14 @@ def download_invoices(input_file_path: str, output_file_path: str, username: str
try: try:
PyPDF2.PdfFileReader(open(invoice["path"], "rb")) PyPDF2.PdfFileReader(open(invoice["path"], "rb"))
except (PyPDF2.utils.PdfReadError, OSError): 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 invoice["good"] = False
else: 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 invoice["good"] = True
else: 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 invoice["good"] = False
merger = PyPDF2.PdfFileMerger() merger = PyPDF2.PdfFileMerger()
@@ -70,6 +80,8 @@ def download_invoices(input_file_path: str, output_file_path: str, username: str
merger.append(PyPDF2.PdfFileReader(open(invoice["path"], "rb"))) merger.append(PyPDF2.PdfFileReader(open(invoice["path"], "rb")))
merger.write(output_file_path) merger.write(output_file_path)
open_file(output_file_path)
shutil.rmtree(tmp_dir, ignore_errors=True) 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) 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): def file_picker_changed(self, event):
"""event raised when the input file path is changed""" """event raised when the input file path is changed"""
self.input_file_path = event.GetEventObject().GetPath() self.input_file_path = event.GetEventObject().GetPath()
#TODO: error frame
try: try:
self.input_file_ext = utils.file_extension(self.input_file_path, (".xml", ".csv", ".xlsx")) self.input_file_ext = utils.file_extension(self.input_file_path, (".xml", ".csv", ".xlsx"))
except exc.NoFileError as handled_exception: except exc.NoFileError as handled_exception:
@@ -185,6 +186,7 @@ class FattureCCSRFrame(wx.Frame):
return return
self.log_dialog = LogDialog(self, "Conversione delle fatture in TRAF2000", CONVERT_ACTION) self.log_dialog = LogDialog(self, "Conversione delle fatture in TRAF2000", CONVERT_ACTION)
self.log_dialog.Show() self.log_dialog.Show()
#TODO: error frame
try: try:
traf2000_converter.convert(self.input_file_path, self.output_file_path) traf2000_converter.convert(self.input_file_path, self.output_file_path)
except exc.NoFileError as handled_exception: except exc.NoFileError as handled_exception: