diff --git a/.gitignore b/.gitignore index a2bc1a6..19a84d1 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ __pycache__/* *.csv *.xml *.ipynb -*.spec \ No newline at end of file +*.spec +*.wxg diff --git a/README.md b/README.md index 5eee355..c640130 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,13 @@ -# fattureSanRossore +# fattureCCSR [![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 last commit](https://img.shields.io/github/last-commit/Noettore/fattureSanRossore)](https://github.com/Noettore/fattureSanRossore/commit/master) ## The Project - -Here you can find two distinct utilities: -- [`fattureDownloader`](fattureDownloader), a Go script which lets you download invoices in pdf starting from an .xls file; -- [`traf2000Converter`](traf2000Converter), a Python script which converts a .csv file to a TRAF2000 text file for being imported in TeamSystem's accounting software. +This utility has the only purpose of facilitate the tasks of downloading invoices and generating TeamSystem's TRAF2000 record from a CCSR report. +It allows the user to authenticate to the CCSR SQL Server Reporting Services (SSRS) and after specifying a time interval it download the suitable report and lets generate the TRAF2000 record or download and merge all the invoices issued in that period. ## 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 \ No newline at end of file +This project is licensed under the MIT License - see the [LICENSE.md](/LICENSE.md) file for details diff --git a/fattureDownloader/README.md b/fattureDownloader/README.md deleted file mode 100644 index 9741f0a..0000000 --- a/fattureDownloader/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# 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 \ No newline at end of file diff --git a/fattureDownloader/fattureSanRossore.go b/fattureDownloader/fattureSanRossore.go deleted file mode 100644 index a597f30..0000000 --- a/fattureDownloader/fattureSanRossore.go +++ /dev/null @@ -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&id=") { - url := xurls.Strict().FindString(line) - url = strings.ReplaceAll(url, "&", "&") - 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) -} diff --git a/fattureDownloader/go.mod b/fattureDownloader/go.mod deleted file mode 100644 index ae6f420..0000000 --- a/fattureDownloader/go.mod +++ /dev/null @@ -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 -) diff --git a/fattureDownloader/go.sum b/fattureDownloader/go.sum deleted file mode 100644 index 5143524..0000000 --- a/fattureDownloader/go.sum +++ /dev/null @@ -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= diff --git a/fattureDownloader/manual/Manuale.md b/fattureDownloader/manual/Manuale.md deleted file mode 100644 index 33e2962..0000000 --- a/fattureDownloader/manual/Manuale.md +++ /dev/null @@ -1,56 +0,0 @@ -# Manuale operativo `fattureSanRossore.exe` - -L’utility `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 l’indirizzo 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 l’icona di salvataggio - -7. Selezionare l’opzione `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 sull’eseguibile `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 - diff --git a/fattureDownloader/manual/Manuale.pdf b/fattureDownloader/manual/Manuale.pdf deleted file mode 100644 index 2900cf1..0000000 Binary files a/fattureDownloader/manual/Manuale.pdf and /dev/null differ diff --git a/fattureDownloader/manual/img/1.png b/fattureDownloader/manual/img/1.png deleted file mode 100644 index 2f7ce8a..0000000 Binary files a/fattureDownloader/manual/img/1.png and /dev/null differ diff --git a/fattureDownloader/manual/img/2.png b/fattureDownloader/manual/img/2.png deleted file mode 100644 index 188ba66..0000000 Binary files a/fattureDownloader/manual/img/2.png and /dev/null differ diff --git a/fattureDownloader/manual/img/3.png b/fattureDownloader/manual/img/3.png deleted file mode 100644 index 951c781..0000000 Binary files a/fattureDownloader/manual/img/3.png and /dev/null differ diff --git a/fattureDownloader/manual/img/4.png b/fattureDownloader/manual/img/4.png deleted file mode 100644 index 9f6c6b8..0000000 Binary files a/fattureDownloader/manual/img/4.png and /dev/null differ diff --git a/fattureDownloader/manual/img/5.png b/fattureDownloader/manual/img/5.png deleted file mode 100644 index 2d3ae33..0000000 Binary files a/fattureDownloader/manual/img/5.png and /dev/null differ diff --git a/fattureDownloader/manual/img/6.png b/fattureDownloader/manual/img/6.png deleted file mode 100644 index f742ea0..0000000 Binary files a/fattureDownloader/manual/img/6.png and /dev/null differ diff --git a/fatture_ccsr/__init__.py b/fatture_ccsr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fatture_ccsr/downloader.py b/fatture_ccsr/downloader.py new file mode 100644 index 0000000..4f0ef02 --- /dev/null +++ b/fatture_ccsr/downloader.py @@ -0,0 +1,99 @@ +"""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 openpyxl +import PyPDF2 +import wx + +def get_invoices_info(input_file_path: str) -> tuple: + """extract invoices IDs and URLs from xlsx input file""" + xlsx_file = openpyxl.load_workbook(input_file_path) + sheet = xlsx_file.active + invoices = dict() + + owner_name = '_'.join(sheet["B1"].value.split()[2:]) + + for i in range(1, sheet.max_row+1): + invoice_id = sheet["I"+str(i)].value + if invoice_id is not None and "CCSR" in invoice_id: + invoice_id = invoice_id.replace("/", "-") + invoice_url = sheet["BG"+str(i)].hyperlink.target + invoice = { + "id": invoice_id, + "url": invoice_url, + "path": None, + "good": None, + } + invoices[invoice_id] = invoice + invoices_info = (owner_name, invoices) + return invoices_info + +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(parent): + """download invoices from CCSR""" + output_file_path = None + + invoices_info = get_invoices_info(parent.input_file_path) + invoices = invoices_info[1] + + parent.log_dialog.log_text.AppendText("Inizio download fatture dal portale CCSR\n") + wx.Yield() + + tmp_dir = tempfile.mkdtemp() + + invoices_count = len(invoices) + downloaded_count = 0 + + for invoice_id, invoice in invoices.items(): + resp = parent.session.get(invoice["url"]) + if resp.status_code == 200: + with open(tmp_dir+"/"+invoice_id+".pdf", "wb") as output_file: + output_file.write(resp.content) + invoice["path"] = output_file.name + try: + PyPDF2.PdfFileReader(open(invoice["path"], "rb")) + except (PyPDF2.utils.PdfReadError, OSError): + parent.log_dialog.log_text.AppendText("Errore: fattura %s corrotta!\n" % invoice_id) + wx.Yield() + invoice["good"] = False + else: + downloaded_count += 1 + parent.log_dialog.log_text.AppendText("%d/%d scaricata fattura %s in %s\n" % (downloaded_count, invoices_count, invoice_id, invoice["path"])) + wx.Yield() + invoice["good"] = True + else: + parent.log_dialog.log_text.AppendText("Errore: impossibile scaricare fattura %s: %d\n" % (invoice_id, resp.status_code)) + wx.Yield() + invoice["good"] = False + + parent.output_pdf_dialog.SetFilename("fatture_%s.pdf" % invoices_info[0]) + + if parent.output_pdf_dialog.ShowModal() == wx.ID_OK: + output_file_path = parent.output_pdf_dialog.GetPath() + else: + #TODO: avviso errore file output + return + + merger = PyPDF2.PdfFileMerger() + for invoice in invoices.values(): + 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) + + parent.log_dialog.log_text.AppendText("Download terminato.\nIl pdf contenente le fatture si trova in %s\n" % output_file_path) + wx.Yield() diff --git a/fatture_ccsr/exc.py b/fatture_ccsr/exc.py new file mode 100644 index 0000000..dc843b2 --- /dev/null +++ b/fatture_ccsr/exc.py @@ -0,0 +1,41 @@ +"""Define Python user-defined exceptions""" + +class FattureSanRossoreError(Exception): + """Base class for other exceptions""" + +class FileError(FattureSanRossoreError): + """Basic exception for errors raised by files""" + def __init__(self, file_path, msg=None): + if msg is None: + msg = "An error occurred with file %s" % file_path + super(FileError, self).__init__(msg) + self.file_path = file_path + +class NoFileExtensionError(FileError): + """Raised when a file has no exception""" + def __init__(self, file_path): + super(NoFileExtensionError, self).__init__(file_path, msg="File %s has no extension!" % file_path) +class WrongFileExtensionError(FileError): + """Raised when a file extension is not accepted""" + def __init__(self, file_path, file_ext, allowed_ext): + super(WrongFileExtensionError, self).__init__(file_path, msg="Cannot accept file %s extension %s. Allowed extensions are %s" % (file_path, file_ext, allowed_ext)) + self.file_ext = file_ext + +class NoFileError(FileError): + """Raised when file_path is None or an empty string""" + def __init__(self): + super(NoFileError, self).__init__(None, msg="Not setted or empty file path!") + + +class ActionError(FattureSanRossoreError): + """Basic exception for errors raised by actions""" + def __init__(self, action, msg=None): + if msg is None: + msg = "An error occurred with %s action" % action + super(ActionError, self).__init__(msg) + self.action = action + +class InvalidActionError(ActionError): + """Raised when an invalid action is used""" + def __init__(self, action): + super(InvalidActionError, self).__init__(action, "Invalid action %s" % action) diff --git a/fatture_ccsr/fatture_ccsr.py b/fatture_ccsr/fatture_ccsr.py new file mode 100644 index 0000000..5da4069 --- /dev/null +++ b/fatture_ccsr/fatture_ccsr.py @@ -0,0 +1,303 @@ +"""This utility is used for downloading or converting to TRAF2000 invoices from a CCSR .xlsx, .csv or .xml report file""" + +import os +import tempfile +import atexit +import wx +import wx.adv +import requests +import requests_ntlm + +import downloader +import traf2000_converter +import exc + +LOGIN_ACTION = 0 +LOGOUT_ACTION = 1 +DOWNLOAD_ACTION = 10 +CONVERT_ACTION = 20 + +def file_extension(file_path: str, allowed_ext: set = None) -> str: + """Return the file extension if that's in the allowed extension set""" + if file_path in (None, ""): + raise exc.NoFileError() + file_ext = os.path.splitext(file_path)[1] + if file_ext in (None, ""): + raise exc.NoFileExtensionError + if allowed_ext is not None and file_ext not in allowed_ext: + raise exc.WrongFileExtensionError + return file_ext + +class LogDialog(wx.Dialog): + """logging panel""" + def __init__(self, parent, title, action): + super(LogDialog, self).__init__(parent, wx.ID_ANY, title, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + + self.log_text = wx.TextCtrl(self, wx.ID_ANY, size=(500, 200), style=wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL|wx.EXPAND) + log_sizer = wx.BoxSizer(wx.HORIZONTAL) + log_sizer.Add(self.log_text, 1, wx.ALL|wx.EXPAND, 2) + + self.log_text.Bind(wx.EVT_TEXT, self.on_text_update) + + if action == CONVERT_ACTION: + self.nc_text = wx.TextCtrl(self, wx.ID_ANY, size=(300, 200), style=wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL) + self.nc_text.Bind(wx.EVT_TEXT, self.on_text_update) + log_sizer.Add(self.nc_text, 1, wx.ALL|wx.EXPAND, 2) + + main_sizer.Add(log_sizer, 1, wx.ALL|wx.EXPAND, 2) + self.btn = wx.Button(self, wx.ID_OK, "Chiudi") + self.btn.Disable() + main_sizer.Add(self.btn, 0, wx.ALL|wx.CENTER, 2) + + self.SetSizer(main_sizer) + main_sizer.Fit(self) + + self.Layout() + + def on_text_update(self, event): + """autoscroll on text update""" + self.ScrollPages(-1) + event.Skip() + +class LoginDialog(wx.Dialog): + """login dialog for basic auth download""" + def __init__(self, *args, **kwds): + """constructor""" + kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_DIALOG_STYLE + wx.Dialog.__init__(self, *args, **kwds) + self.SetTitle("Login") + + self.logged_in = False + + main_sizer = wx.BoxSizer(wx.VERTICAL) + + user_sizer = wx.BoxSizer(wx.HORIZONTAL) + main_sizer.Add(user_sizer, 1, wx.ALL | wx.EXPAND, 2) + + user_lbl = wx.StaticText(self, wx.ID_ANY, "Username:") + user_sizer.Add(user_lbl, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 2) + + self.username = wx.TextCtrl(self, wx.ID_ANY, "") + self.username.SetMinSize((250, -1)) + user_sizer.Add(self.username, 0, wx.ALL | wx.EXPAND, 2) + + pass_sizer = wx.BoxSizer(wx.HORIZONTAL) + main_sizer.Add(pass_sizer, 1, wx.ALL | wx.EXPAND, 2) + + pass_lbl = wx.StaticText(self, wx.ID_ANY, "Password:") + pass_sizer.Add(pass_lbl, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 2) + + self.password = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_PASSWORD | wx.TE_PROCESS_ENTER) + self.password.SetMinSize((250, -1)) + pass_sizer.Add(self.password, 0, wx.ALL | wx.EXPAND, 2) + + self.login_btn = wx.Button(self, wx.ID_ANY, "Login") + self.login_btn.SetFocus() + self.login_btn.Bind(wx.EVT_BUTTON, self.on_login) + main_sizer.Add(self.login_btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2) + + self.SetSizer(main_sizer) + main_sizer.Fit(self) + + self.Layout() + + def disconnect(self): + """close session and reset input fields""" + self.GetParent().session.close() + self.logged_in = False + + def on_login(self, _): + """check credentials and login""" + if self.username.GetValue() not in ("", None) and self.password.GetValue() not in ("", None): + session = self.GetParent().session + session.auth = requests_ntlm.HttpNtlmAuth("sr\\"+self.username.GetValue(), self.password.GetValue()) + if session.get('https://report.casadicurasanrossore.it:8443/Reports/browse/').status_code == 200: + self.logged_in = True + self.username.SetValue('') + self.password.SetValue('') + self.Close() + +class FattureCCSRFrame(wx.Frame): + """main application frame""" + def __init__(self, *args, **kwds): + atexit.register(self.exit_handler) + + kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE | wx.TAB_TRAVERSAL + wx.Frame.__init__(self, *args, **kwds) + self.SetTitle("fattureCCSR") + + self._initial_locale = wx.Locale(wx.LANGUAGE_DEFAULT, wx.LOCALE_LOAD_DEFAULT) + + self.input_file_path = None + self.input_file_ext = None + self.input_files = list() + self.log_dialog = None + self.session = requests.Session() + + self.panel = wx.Panel(self, wx.ID_ANY, style=wx.BORDER_NONE | wx.FULL_REPAINT_ON_RESIZE | wx.TAB_TRAVERSAL) + self.main_sizer = wx.BoxSizer(wx.VERTICAL) + + title_lbl = wx.StaticText(self.panel, wx.ID_ANY, "Utility Fatture Casa di Cura San Rossore") + title_lbl.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) + self.main_sizer.Add(title_lbl, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2) + + desc_lbl = wx.StaticText(self.panel, wx.ID_ANY, "Effettua il login poi seleziona le date di inizio e fine periodo delle fatture da gestire ed esegui un'azione") + self.main_sizer.Add(desc_lbl, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2) + + self.login_dlg = LoginDialog(self) + self.output_traf2000_dialog = wx.FileDialog(self.panel, "Scegli dove salvare il file TRAF2000", defaultFile="TRAF2000", style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) + self.output_pdf_dialog = wx.FileDialog(self.panel, "Scegli dove salvare il .pdf con le fatture scaricate", defaultFile="fatture.pdf", style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) + + self.login_btn = wx.Button(self.panel, LOGIN_ACTION, "Login") + self.login_btn.SetFocus() + self.login_btn.Bind(wx.EVT_BUTTON, self.btn_onclick) + self.main_sizer.Add(self.login_btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2) + + self.logout_btn = wx.Button(self.panel, LOGOUT_ACTION, "Logout") + self.logout_btn.Hide() + self.logout_btn.Bind(wx.EVT_BUTTON, self.btn_onclick) + self.main_sizer.Add(self.logout_btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2) + + date_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.main_sizer.Add(date_sizer, 0, wx.ALL | wx.EXPAND, 2) + + start_date_lbl = wx.StaticText(self.panel, wx.ID_ANY, "Dal") + date_sizer.Add(start_date_lbl, 0, wx.ALL, 2) + + self.start_date_picker = wx.adv.DatePickerCtrl(self.panel, wx.ID_ANY, dt=wx.DateTime.Today().SetDay(1)) + self.start_date_picker.Enable(False) + date_sizer.Add(self.start_date_picker, 1, wx.ALL, 2) + + end_date_lbl = wx.StaticText(self.panel, wx.ID_ANY, "Al") + date_sizer.Add(end_date_lbl, 0, wx.ALL, 2) + + self.end_date_picker = wx.adv.DatePickerCtrl(self.panel, wx.ID_ANY, dt=wx.DateTime.Today()) + self.end_date_picker.Enable(False) + date_sizer.Add(self.end_date_picker, 1, wx.ALL, 2) + + action_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.main_sizer.Add(action_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2) + + self.download_btn = wx.Button(self.panel, DOWNLOAD_ACTION, "Scarica Fatture") + self.download_btn.Enable(False) + self.download_btn.Bind(wx.EVT_BUTTON, self.btn_onclick) + action_sizer.Add(self.download_btn, 0, wx.ALL, 2) + + self.traf2000_btn = wx.Button(self.panel, CONVERT_ACTION, "Genera TRAF2000") + self.traf2000_btn.Enable(False) + self.traf2000_btn.Bind(wx.EVT_BUTTON, self.btn_onclick) + action_sizer.Add(self.traf2000_btn, 0, wx.ALL, 2) + + self.panel.SetSizer(self.main_sizer) + + self.main_sizer.Fit(self) + self.Layout() + self.Centre() + + def enable_on_login(self): + """enable and show what needed after login""" + self.download_btn.Enable() + self.traf2000_btn.Enable() + self.start_date_picker.Enable() + self.end_date_picker.Enable() + self.login_btn.Hide() + self.logout_btn.Show() + self.main_sizer.Layout() + + def disable_on_logout(self): + """disable and hide what needed after logout""" + self.download_btn.Disable() + self.traf2000_btn.Disable() + self.start_date_picker.Disable() + self.end_date_picker.Disable() + self.login_dlg.disconnect() + self.logout_btn.Hide() + self.login_btn.Show() + self.main_sizer.Layout() + + def btn_onclick(self, event): + """event raised when a button is clicked""" + btn_id = event.GetEventObject().GetId() + + if btn_id not in (LOGIN_ACTION, LOGOUT_ACTION, DOWNLOAD_ACTION, CONVERT_ACTION): + #TODO: error + return + elif btn_id == LOGIN_ACTION: + self.login_dlg.ShowModal() + if self.login_dlg.logged_in: + self.enable_on_login() + return + elif btn_id == LOGOUT_ACTION: + self.disable_on_logout() + return + elif not self.login_dlg.logged_in: + #TODO: error + return + + start_date = self.start_date_picker.GetValue().Format("%d/%m/%Y") + end_date = self.end_date_picker.GetValue().Format("%d/%m/%Y") + input_file_url = 'https://report.casadicurasanrossore.it:8443/reportserver?/STAT_FATTURATO_CTERZI&dataI='+start_date+'&dataF='+end_date+'&rs:Format=' + input_file_url += ('EXCELOPENXML' if btn_id == DOWNLOAD_ACTION else 'XML' if btn_id == CONVERT_ACTION else None) + + downloaded_input_file = self.session.get(input_file_url) + if downloaded_input_file.status_code != 200: + #TODO: error + return + + input_file_descriptor, self.input_file_path = tempfile.mkstemp(suffix=('.xlsx' if btn_id == DOWNLOAD_ACTION else '.xml' if btn_id == CONVERT_ACTION else None)) + self.input_files.append(self.input_file_path) + with open(input_file_descriptor, 'wb') as input_file: + input_file.write(downloaded_input_file.content) + + try: + self.input_file_ext = file_extension(self.input_file_path, (".xml", ".csv", ".xlsx")) + except exc.NoFileError as handled_exception: + print(handled_exception.args[0]) + return + except exc.NoFileExtensionError as handled_exception: + print(handled_exception.args[0]) + return + except exc.WrongFileExtensionError as handled_exception: + print(handled_exception.args[0]) + return + + if btn_id == DOWNLOAD_ACTION: + self.log_dialog = LogDialog(self, "Download delle fatture dal portale CCSR", DOWNLOAD_ACTION) + self.log_dialog.Show() + downloader.download_invoices(self) + self.log_dialog.btn.Enable() + + elif btn_id == CONVERT_ACTION: + self.log_dialog = LogDialog(self, "Conversione delle fatture in TRAF2000", CONVERT_ACTION) + self.log_dialog.Show() + #TODO: error frame + try: + traf2000_converter.convert(self) + except exc.NoFileError as handled_exception: + print(handled_exception.args[0]) + except exc.NoFileExtensionError as handled_exception: + print(handled_exception.args[0]) + except exc.WrongFileExtensionError as handled_exception: + print(handled_exception.args[0]) + self.log_dialog.btn.Enable() + + def exit_handler(self): + """clean the environment befor exiting""" + for input_file in self.input_files: + os.remove(input_file) + + +class FattureCCSR(wx.App): + """main app""" + def OnInit(self): # pylint: disable=invalid-name + """execute on app initialization""" + self.fatture_ccsr_frame = FattureCCSRFrame(None, wx.ID_ANY, "") # pylint: disable=attribute-defined-outside-init + self.SetTopWindow(self.fatture_ccsr_frame) + self.fatture_ccsr_frame.Show() + return True + +if __name__ == "__main__": + fattureCCSR = FattureCCSR(0) + fattureCCSR.MainLoop() diff --git a/fatture_ccsr/traf2000_converter.py b/fatture_ccsr/traf2000_converter.py new file mode 100755 index 0000000..aaa2649 --- /dev/null +++ b/fatture_ccsr/traf2000_converter.py @@ -0,0 +1,299 @@ +"""ask for an input file and an output file and generates the TRAF2000 records from a .csv or .xml""" + +import datetime +import csv +import xml.etree.ElementTree +import unidecode +import wx + +def import_csv(parent) -> dict: + """Return a dict containing the invoices info""" + invoices = dict() + with open(parent.input_file_path, newline="") as csv_file: + csv_reader = csv.reader(csv_file, delimiter=",") + + for _ in range(4): + next(csv_reader) + + for line in csv_reader: + if len(line) == 0: + break + invoice_num = line[1] + invoice_type = line[8] + amount = line[15] + sign = 1 + if invoice_type == "Nota di credito" and '(' not in amount: + sign = -1 + amount = int(line[15].replace("€", "").replace(",", "").replace(".", "").replace("(", "").replace(")", "")) * sign + if invoice_num not in invoices: + invoice = { + "numFattura": invoice_num, + "tipoFattura": invoice_type, + "rifFattura": line[4], + "dataFattura": line[2].replace("/", ""), + "ragioneSociale": unidecode.unidecode(line[6] + " " + " ".join(line[5].split()[0:2])), + "posDivide": str(len(line[6]) + 1), + "cf": line[7], + "importoTotale": 0, + "ritenutaAcconto": 0, + "righe": dict() + } + invoices[invoice_num] = invoice + + if line[14] == "Ritenuta d'acconto": + invoices[invoice_num]["ritenutaAcconto"] = amount + + else: + invoices[invoice_num]["importoTotale"] += amount + invoices[invoice_num]["righe"][line[14]] = amount + parent.log_dialog.log_text.AppendText("Importata fattura n. %s\n" % invoice_num) + wx.Yield() + return invoices + +def import_xml(parent) -> dict: + """Return a dict containing the invoices info""" + invoices = dict() + + tree = xml.etree.ElementTree.parse(parent.input_file_path) + root = tree.getroot() + + for invoice in root.iter('{STAT_FATTURATO_CTERZI}Dettagli'): + lines = dict() + invoice_num = invoice.get('protocollo_fatturatestata') + invoice_type = invoice.get('fat_ndc') + total_amount = 0 + ritenuta_acconto = 0 + + for line in invoice.iter('{STAT_FATTURATO_CTERZI}Dettagli2'): + desc = line.get('descrizione_fatturariga1') + sign = 1 + if invoice_type == 'Nota di credito' and '-' not in line.get('prezzounitario_fatturariga1'): + sign = -1 + amount = int(format(round(float(line.get('prezzounitario_fatturariga1')), 2), '.2f').replace('.', '').replace('-', '')) * sign + if desc == "Ritenuta d'acconto": + ritenuta_acconto = amount + else: + lines[desc] = amount + total_amount += amount + + invoice_elem = { + "numFattura": invoice_num, + "tipoFattura": invoice_type, + "rifFattura": invoice.get('protocollo_fatturatestata1'), + "dataFattura": datetime.datetime.fromisoformat(invoice.get('data_fatturatestata')).strftime("%d%m%Y"), + "ragioneSociale": unidecode.unidecode(invoice.get('cognome_cliente') + ' ' + ' '.join(invoice.get('nome_cliente').split()[0:2])), + "posDivide": str(len(invoice.get('cognome_cliente')) + 1), + "cf": invoice.get('cf_piva_cliente'), + "importoTotale": total_amount, + "ritenutaAcconto": ritenuta_acconto, + "righe": lines, + } + invoices[invoice_num] = invoice_elem + parent.log_dialog.log_text.AppendText("Importata fattura n. %s\n" % invoice_num) + wx.Yield() + return invoices + + +def convert(parent): + """Output to a file the TRAF2000 records""" + output_file_path = None + + if parent.input_file_ext == ".csv": + invoices = import_csv(parent) + + elif parent.input_file_ext == ".xml": + invoices = import_xml(parent) + + if parent.output_traf2000_dialog.ShowModal() == wx.ID_OK: + output_file_path = parent.output_traf2000_dialog.GetPath() + else: + #TODO: avviso errore file output + return + + with open(output_file_path, "w") as traf2000_file: + parent.log_dialog.nc_text.AppendText("Note di credito:\n") + wx.Yield() + + for invoice in invoices.values(): + if invoice["tipoFattura"] != "Fattura" and invoice["tipoFattura"] != "Nota di credito": + parent.log_dialog.log_text.AppendText("Errore: il documento %s può essere FATTURA o NOTA DI CREDITO\n" % invoice["numFattura"]) + wx.Yield() + continue + + if len(invoice["cf"]) != 16 and len(invoice["cf"]) == 11: + parent.log_dialog.log_text.AppendText("Errore: il documento %s non ha cf/piva\n" % invoice["numFattura"]) + wx.Yield() + continue + + if invoice["tipoFattura"] == "Nota di credito": + # As for now this script doesn't handle "Note di credito" + parent.log_dialog.nc_text.AppendText(invoice["numFattura"]+"\n") + wx.Yield() + continue + + line = ["04103", "3", "0", "00000"] # TRF-DITTA + TRF-VERSIONE + TRF-TARC + TRF-COD-CLIFOR + line.append(invoice["ragioneSociale"][:32]+' '*(32-len(invoice["ragioneSociale"]))) # TRF-RASO + line.append(' '*30) # TRF-IND + line.append('00000') # TRF-CAP + line.append(' '*27) # TRF-CITTA + TRF-PROV + + if len(invoice["cf"]) == 16: # se c.f. presente + line.append(invoice["cf"]) # TRF-COFI + line.append('0'*11) # TRF-PIVA + line.append('S') # TRF-PF + else: # se piva presente + line.append(' '*16) # TRF-COFI + line.append(invoice["cf"]) # TRF-PIVA + line.append('N') # TRF-PF + + line.append('0'*(2-len(invoice["posDivide"])) + invoice["posDivide"]) # TRF-DIVIDE + line.append('0000') # TRF-PAESE + line.append(' '*33) # TRF-PIVA-ESTERO + TRF-COFI-ESTERO + TRF-SESSO + line.append('0'*8) # TRF-DTNAS + line.append(' '*64) # TRF-COMNA + TRF-PRVNA + TRF-PREF + TRF-NTELE-NUM + TRF-FAX-PREF + TRF-FAX-NUM + line.append('0'*22) # TRF-CFCONTO + TRF-CFCODPAG + TRF-CFBANCA + TRF-CFAGENZIA + TRF-CFINTERM + + if invoice["tipoFattura"] == "Fattura": + line.append('001') # TRF-CAUSALE + line.append("FATTURA VENDITA") # TRF-CAU-DES + else: + line.append('002') # TRF-CAUSALE + line.append("N.C. A CLIENTE ") # TRF-CAU-DES + + line.append(' '*86) # TRF-CAU-AGG + TRF-CAU-AGG-1 + TRF-CAU-AGG-2 + line.append(invoice["dataFattura"]*2) # TRF-DATA-REGISTRAZIONE + TRF-DATA-DOC + line.append('00000000') # TRF-NUM-DOC-FOR + line.append(invoice["numFattura"][4:9]) # TRF-NDOC + line.append('00') # TRF-SERIE + line.append('0'*72) # TRF-EC-PARTITA + TRF-EC-PARTITA-ANNO + TRF-EC-COD-VAL + TRF-EC-CAMBIO + TRF-EC-DATA-CAMBIO + TRF-EC-TOT-DOC-VAL + TRF-EC-TOT-IVA-VAL + TRF-PLAFOND + + count = 0 + for desc, imponibile in invoice["righe"].items(): + count += 1 + imponibile = str(imponibile) + if '-' in imponibile: + imponibile.replace('-', '') + imponibile = '0'*(11-len(imponibile)) + imponibile + "-" + else: + imponibile = '0'*(11-len(imponibile)) + imponibile + "+" + line.append(imponibile) # TRF-IMPONIB + if desc != "Bollo": + line.append('308') # TRF-ALIQ + else: + line.append('315') # TRF-ALIQ + line.append('0'*16) # TRF-ALIQ-AGRICOLA + TRF-IVA11 + TRF-IMPOSTA + + for _ in range(8-count): + line.append('0'*31) + + total = str(invoice["importoTotale"]) + if '-' in total: + total.replace('-', '') + total = '0'*(11-len(total)) + total + "-" + else: + total = '0'*(11-len(total)) + total + "+" + line.append(total) # TRF-TOT-FAT + + count = 0 + for desc, imponibile in invoice["righe"].items(): + count += 1 + imponibile = str(imponibile) + if '-' in imponibile: + imponibile.replace('-', '') + imponibile = '0'*(11-len(imponibile)) + imponibile + "-" + else: + imponibile = '0'*(11-len(imponibile)) + imponibile + "+" + if desc != "Bollo": + line.append('4004300') # TRF-CONTO-RIC + else: + line.append('4004500') # TRF-CONTO-RIC + line.append(imponibile) # TRF-IMP-RIC + + for _ in range(8-count): + line.append('0'*19) + + line.append('000') # TRF-CAU-PAG + line.append(' '*83) # TRF-CAU-DES-PAGAM + TRF-CAU-AGG-1-PAGAM + TRF-CAU-AGG-2-PAGAM + line.append(('0000000' + ' ' + '0'*12 + ' '*18 + '0'*26)*80) # TRF-CONTO + TRF-DA + TRF-IMPORTO + TRF-CAU-AGGIUNT + TRF-EC-PARTITA-PAG + TRF-EC-PARTITA-ANNO-PAG + TRF-EC-IMP-VAL + line.append((' ' + '0'*18)*10) # TRF-RIFER-TAB + TRF-IND-RIGA + TRF-DT-INI + TRF-DT-FIN + line.append('000000') # TRF-DOC6 + line.append('N' + '0') # TRF-AN-OMONIMI + TRF-AN-TIPO-SOGG + line.append('00'*80) # TRF-EC-PARTITA-SEZ-PAG + line.append('0'*15) # TRF-NUM-DOC-PAG-PROF + TRF-DATA-DOC-PAG-PROF + + if invoice["ritenutaAcconto"] != 0: + imponibile = str(invoice["ritenutaAcconto"]) + imponibile = '0'*(11-len(imponibile)) + imponibile + "+" + line.append(imponibile) # TRF-RIT-ACC + else: + line.append('0'*12) # TRF-RIT-ACC + + line.append('0'*60) # TRF-RIT-PREV + TRF-RIT-1 + TRF-RIT-2 + TRF-RIT-3 + TRF-RIT-4 + line.append('00'*8) # TRF-UNITA-RICAVI + line.append('00'*80) # TRF-UNITA-PAGAM + line.append(' '*24) # TRF-FAX-PREF-1 + TRF-FAX-NUM-1 + line.append(' ' + ' ') # TRF-SOLO-CLIFOR + TRF-80-SEGUENTE + line.append('0000000') # TRF-CONTO-RIT-ACC + line.append('0'*35) # TRF-CONTO-RIT-PREV + TRF-CONTO-RIT-1 + TRF-CONTO-RIT-2 + TRF-CONTO-RIT-3 + TRF-CONTO-RIT-4 + line.append('N' + 'N' + '00000000' + '000') # TRF-DIFFERIMENTO-IVA + TRF-STORICO + TRF-STORICO-DATA + TRF-CAUS-ORI + line.append(' ' + ' ' + '0'*16 + ' ') # TRF-PREV-TIPOMOV + TRF-PREV-RATRIS + TRF-PREV-DTCOMP-INI + TRF-PREV-DTCOMP-FIN + TRF-PREV-FLAG-CONT + line.append(' '*20 + '0'*21 + ' '*44 + '0'*8 + ' ' + '0'*6 + ' ' + '00' + ' ') # TRF-RIFERIMENTO + TRF-CAUS-PREST-ANA + TRF-EC-TIPO-PAGA + TRF-CONTO-IVA-VEN-ACQ + TRF-PIVA-VECCHIA + TRF-PIVA-ESTERO-VECCHIA + # TRF-RISERVATO + TRF-DATA-IVA-AGVIAGGI + TRF-DATI-AGG-ANA-REC4 + TRF-RIF-IVA-NOTE-CRED + TRF-RIF-IVA-ANNO-PREC + TRF-NATURA-GIURIDICA + TRF-STAMPA-ELENCO + line.append('000'*8 + ' '*20 + '0' + ' '*4 + '0'*6 + ' '*20 + 'S' + 'N') # TRF-PERC-FORF + TRF-SOLO-MOV-IVA + TRF-COFI-VECCHIO + TRF-USA-PIVA-VECCHIA + TRF-USA-PIVA-EST-VECCHIA + TRF-USA-COFI-VECCHIO + TRF-ESIGIBILITA-IVA + TRF-TIPO-MOV-RISCONTI + TRF-AGGIORNA-EC + TRF-BLACKLIST-ANAG + TRF-BLACKLIST-IVA-ANNO + TRF-CONTEA-ESTERO + TRF-ART21-ANAG + TRF-ART21-IVA + + if invoice["tipoFattura"] == "Fattura": + line.append('N') # TRF-RIF-FATTURA + else: + line.append('S') # TRF-RIF-FATTURA + line.append('S' + ' '*2 + 'S' + ' '*2) # TRF-RISERVATO-B + TRF-MASTRO-CF + TRF-MOV-PRIVATO + TRF-SPESE-MEDICHE + TRF-FILLER + line.append('\n') + parent.log_dialog.log_text.AppendText("Creato record #0 per fattura n. %s\n" % invoice["numFattura"]) + wx.Yield() + + #RECORD 5 per Tessera Sanitaria + line.append('04103' + '3' + '5') # TRF5-DITTA + TRF5-VERSIONE + TRF5-TARC + line.append(' '*1200) # TRF-ART21-CONTRATTO + line.append('0'*6 + invoice["cf"]) # TRF-A21CO-ANAG + # TRF-A21CO-COFI + total = str(invoice["importoTotale"]) + total = '0'*(13-len(total)) + total + "+" + line.append(invoice["dataFattura"] + 'S' + '000' + total + '0'*14 + '0' + invoice["numFattura"][4:9] + '00' + ' '*40) # TRF-A21CO-DATA + TRF-A21CO-FLAG + TRF-A21CO-ALQ + TRF-A21CO-IMPORTO + TRF-A21CO-IMPOSTA + TRF-A21CO-NDOC + TRF-A21CO-CONTRATTO + line.append(('0'*6 + ' '*16 + '0'*8 + ' ' + '000' + '0'*14 + '0'*14 + '0'*8 + ' '*40)*49) # TRF-A21CO-DATA + TRF-A21CO-FLAG + TRF-A21CO-ALQ + TRF-A21CO-IMPORTO + TRF-A21CO-IMPOSTA + TRF-A21CO-NDOC + TRF-A21CO-CONTRATTO + + if invoice["tipoFattura"] == "Nota di credito": + line.append('000' + invoice["rifFattura"][4:9]) # TRF-RIF-FATT-NDOC + line.append('0'*8) # TRF-RIF-FATT-DDOC + else: + line.append('0'*16) # TRF-RIF-FATT-NDOC + TRF-RIF-FATT-DDOC + line.append('F' + 'SR' + '2') # TRF-A21CO-TIPO + TRF-A21CO-TIPO-SPESA + TRF-A21CO-FLAG-SPESA + line.append((' ' + ' ' + ' ')*49) # TRF-A21CO-TIPO + TRF-A21CO-TIPO-SPESA + TRF-A21CO-FLAG-SPESA + line.append(' ' + 'S' + ' '*76) # TRF-SPESE-FUNEBRI + TRF-A21CO-PAGAM + FILLER + FILLER + line.append('\n') + parent.log_dialog.log_text.AppendText("Creato record #5 per fattura n. %s\n" % invoice["numFattura"]) + wx.Yield() + + #RECORD 1 per num. doc. originale + line.append('04103' + '3' + '1') # TRF1-DITTA + TRF1-VERSIONE + TRF1-TARC + line.append('0'*7 + ' '*3 + '0'*14) # TRF-NUM-AUTOFATT + TRF-SERIE-AUTOFATT + TRF-COD-VAL + TRF-TOTVAL + line.append((' '*8 + '0'*24 + ' ' + '0'*36 + ' '*2 + '0'*9 + ' '*5)*20) # TRF-NOMENCLATURA + TRF-IMP-LIRE + TRF-IMP-VAL + TRF-NATURA + TRF-MASSA + TRF-UN-SUPPL + TRF-VAL-STAT + TRF-REGIME + TRF-TRASPORTO + TRF-PAESE-PROV + TRF-PAESE-ORIG + TRF-PAESE-DEST + TRF-PROV-DEST + TRF-PROV-ORIG + TRF-SEGNO-RET + line.append(' ' + '0'*6 + ' '*173) # TRF-INTRA-TIPO + TRF-MESE-ANNO-RIF + SPAZIO + line.append('0'*45 + ' '*4 + '0'*20 + ' '*28 + '0'*25) # TRF-RITA-TIPO + TRF-RITA-IMPON + TRF-RITA-ALIQ + TRF-RITA-IMPRA + TRF-RITA-PRONS + TRF-RITA-MESE + TRF-RITA-CAUSA + TRF-RITA-TRIBU + TRF-RITA-DTVERS + TRF-RITA-IMPAG + TRF-RITA-TPAG + TRF-RITA-SERIE + TRF-RITA-QUIETANZA + TRF-RITA-NUM-BOLL + TRF-RITA-ABI + TRF-RITA-CAB + TRF-RITA-AACOMP + TRF-RITA-CRED + line.append(' ' + '0'*44 + ' '*11 + '0'*64) #TRF-RITA-SOGG + TRF-RITA-BASEIMP + TRF-RITA-FRANCHIGIA + TRF-RITA-CTO-PERC + TRF-RITA-CTO-DITT + FILLER + TRF-RITA-DATA + TRF-RITA-TOTDOC + TRF-RITA-IMPVERS + TRF-RITA-DATA-I + TRF-RITA-DATA-F + TRF-EMENS-ATT + TRF-EMENS-RAP + TRF-EMENS-ASS + TRF-RITA-TOTIVA + line.append('0'*6 + ' '*178) # TRF-CAUS-PREST-ANA-B + TRF-RITA-CAUSA-B + FILLER + line.append('0'*13 + ' '*30 + '0'*14) # TRF-POR-CODPAG + TRF-POR-BANCA + TRF-POR-AGENZIA + TRF-POR-DESAGENZIA + TRF-POR-TOT-RATE + TRF-POR-TOTDOC + line.append(('0'*65 + ' '*2)*12) # TRF-POR-NUM-RATA + TRF-POR-DATASCAD + TRF-POR-TIPOEFF + TRF-POR-IMPORTO-EFF + TRF-POR-IMPORTO-EFFVAL + TRF-POR-IMPORTO-BOLLI + TRF-POR-IMPORTO-BOLLIVAL + TRF-POR-FLAG + TRF-POR-TIPO-RD + line.append('0'*4 + ' '*336) # TRF-POR-CODAGE + TRF-POR-EFFETTO-SOSP + TRF-POR-CIG + TRF-POR-CUP + SPAZIO + line.append((' '*3 + '0'*16)*20) # TRF-COD-VAL-IV + TRF-IMP-VALUTA-IV + line.append((' '*6 + '0'*35 + ' '*2 + '0'*20 + ' '*19 + '0'*16)*20) # TRF-CODICE-SERVIZIO + TRF-STATO-PAGAMENTO + TRF-SERV-IMP-EURO + TRF-SERV-IMP-VAL + TRF-DATA-DOC-ORIG + TRF-MOD-EROGAZIONE + TRF-MOD-INCASSO + TRF-PROT-REG + TRF-PROG-REG + TRF-COD-SEZ-DOG-RET + TRF-ANNO-REG-RET + TRF-NUM-DOC-ORIG + TRF-SERV-SEGNO-RET + TRF-SERV-COD-VAL-IV + TRF-SERV-IMP-VALUTA-IV + line.append(' '*1 + '0'*6) # TRF-INTRA-TIPO-SERVIZIO + TRF-SERV-MESE-ANNO-RIF + line.append(' '*8) # TRF-CK-RCHARGE + line.append('0'*(15-len(invoice["numFattura"])) + invoice["numFattura"]) # TRF-XNUM-DOC-ORI + line.append(' ' + '00' + ' '*1090) # TRF-MEM-ESIGIB-IVA + TRF-COD-IDENTIFICATIVO + TRF-ID-IMPORTAZIONE + TRF-XNUM-DOC-ORI-20 + SPAZIO + FILLER + parent.log_dialog.log_text.AppendText("Creato record #1 per fattura n. %s\n" % invoice["numFattura"]) + wx.Yield() + + line = ''.join(line) + '\n' + + traf2000_file.write(line) + parent.log_dialog.log_text.AppendText("Convertita fattura n. %s\n" % invoice["numFattura"]) + wx.Yield() + parent.log_dialog.log_text.AppendText("Conversione terminata.\nTracciato TRAF2000 salvato in %s\n" % output_file_path) + wx.Yield() diff --git a/traf2000Converter/manual/imppn.v2019.1.6.pdf b/manual/IMPPN.v2020.1.2.pdf similarity index 57% rename from traf2000Converter/manual/imppn.v2019.1.6.pdf rename to manual/IMPPN.v2020.1.2.pdf index 41d0a26..28a7736 100644 Binary files a/traf2000Converter/manual/imppn.v2019.1.6.pdf and b/manual/IMPPN.v2020.1.2.pdf differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6272773 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +requests_ntlm==1.1.0 +wxPython==4.0.7 +requests==2.24.0 +openpyxl==3.0.5 +Unidecode==1.1.2 +PyPDF2==1.26.0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3ada1b3 --- /dev/null +++ b/setup.py @@ -0,0 +1,32 @@ +import setuptools + +with open('README.md', 'r', encoding='utf-8') as readme: + long_description = readme.read() + +setuptools.setup( + name='fatture_ccsr', + version='0.0.1', + author='Ettore Dreucci', + author_email='ettore.dreucci@gmail.com', + description='Utility to download or convert CCSR invoices to TeamSystem\'s TRAF2000 record', + long_description=long_description, + long_description_content_type='text/markdown', + url='https://github.com/Noettore/fattureSanRossore', + packages=setuptools.find_packages(), + install_requires=[ + 'wxPython', + 'requests', + 'Unidecode', + 'requests_ntlm', + 'openpyxl', + 'PyPDF2', + ], + include_package_data=True, + license='MIT', + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.7', +) diff --git a/traf2000Converter/README.md b/traf2000Converter/README.md deleted file mode 100644 index 5fcab9a..0000000 --- a/traf2000Converter/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# 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 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 - -- [Python3](https://www.python.org/) - -### Run - -To run you can simply execute python script. -It will ask for an input(.csv) and an output(TRAF2000) file. - -## Dependencies - -- [unidecode](https://pypi.org/project/Unidecode/) -- [easygui](https://pypi.org/project/easygui/) - -## 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 \ No newline at end of file diff --git a/traf2000Converter/fatture_import.py b/traf2000Converter/fatture_import.py deleted file mode 100644 index 11f8772..0000000 --- a/traf2000Converter/fatture_import.py +++ /dev/null @@ -1,89 +0,0 @@ -"""This script provides two functions to import invoices from .csv or .xml into a dict""" - -import csv -import xml.etree.ElementTree -import datetime -import unidecode - -def import_csv(csv_file_path): - """Return a dict containing the invoices info""" - fatture = dict() - with open(csv_file_path, newline="") as csv_file: - lettore = csv.reader(csv_file, delimiter=",") - - for _ in range(4): - next(lettore) - - for linea in lettore: - if len(linea) == 0: - break - num_fattura = linea[1] - tipo_fattura = linea[8] - importo = linea[15] - segno = 1 - if tipo_fattura == "Nota di credito" and '(' not in importo: - segno = -1 - importo = int(linea[15].replace("€", "").replace(",", "").replace(".", "").replace("(", "").replace(")", "")) * segno - if num_fattura not in fatture: - fattura = { - "numFattura": num_fattura, - "tipoFattura": tipo_fattura, - "rifFattura": linea[4], - "dataFattura": linea[2].replace("/", ""), - "ragioneSociale": unidecode.unidecode(linea[6] + " " + " ".join(linea[5].split(" ")[0:2])), - "posDivide": str(len(linea[6]) + 1), - "cf": linea[7], - "importoTotale": 0, - "ritenutaAcconto": 0, - "righe": dict() - } - fatture[num_fattura] = fattura - - if linea[14] == "Ritenuta d'acconto": - fatture[num_fattura]["ritenutaAcconto"] = importo - - else: - fatture[num_fattura]["importoTotale"] += importo - fatture[num_fattura]["righe"][linea[14]] = importo - return fatture - -def import_xml(xml_file_path): - """Return a dict containing the invoices info""" - fatture = dict() - - tree = xml.etree.ElementTree.parse(xml_file_path) - root = tree.getroot() - - for fattura in root.iter('{STAT_FATTURATO_CTERZI}Dettagli'): - righe = dict() - num_fattura = fattura.get('protocollo_fatturatestata') - tipo_fattura = fattura.get('fat_ndc') - importo_totale = 0 - ritenuta_acconto = 0 - - for riga in fattura.iter('{STAT_FATTURATO_CTERZI}Dettagli2'): - desc = riga.get('descrizione_fatturariga1') - segno = 1 - if tipo_fattura == 'Nota di credito' and '-' not in riga.get('prezzounitario_fatturariga1'): - segno = -1 - importo = int(format(round(float(riga.get('prezzounitario_fatturariga1')), 2), '.2f').replace('.', '').replace('-', '')) * segno - if desc == "Ritenuta d'acconto": - ritenuta_acconto = importo - else: - righe[desc] = importo - importo_totale += importo - - fattura_elem = { - "numFattura": num_fattura, - "tipoFattura": tipo_fattura, - "rifFattura": fattura.get('protocollo_fatturatestata1'), - "dataFattura": datetime.datetime.fromisoformat(fattura.get('data_fatturatestata')).strftime("%d%m%Y"), - "ragioneSociale": unidecode.unidecode(fattura.get('cognome_cliente') + ' ' + ' '.join(fattura.get('nome_cliente').split(' ')[0:2])), - "posDivide": str(len(fattura.get('cognome_cliente')) + 1), - "cf": fattura.get('cf_piva_cliente'), - "importoTotale": importo_totale, - "ritenutaAcconto": ritenuta_acconto, - "righe": righe, - } - fatture[num_fattura] = fattura_elem - return fatture diff --git a/traf2000Converter/traf2000.py b/traf2000Converter/traf2000.py deleted file mode 100755 index daaf4c9..0000000 --- a/traf2000Converter/traf2000.py +++ /dev/null @@ -1,53 +0,0 @@ -"""This script ask for an input file and an output file and generates the TRAF2000 records from a .csv or .xml""" - -import sys -import os -import wx - -import fatture_import -import traf2000_convert - -def get_input_file(wildcard): - """Return the input file path""" - _ = wx.App(None) - style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST - dialog = wx.FileDialog(None, "Scegli il .csv o .xml contenente le informazioni sulle fatture da importare", wildcard=wildcard, style=style) - if dialog.ShowModal() == wx.ID_OK: - path = dialog.GetPath() - else: - path = None - dialog.Destroy() - return path - -def get_output_file(default_output_filename): - """Return the output file path""" - _ = wx.App(None) - style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT - dialog = wx.FileDialog(None, "Scegli dove salvare il file TRAF2000", defaultFile=default_output_filename, style=style) - if dialog.ShowModal() == wx.ID_OK: - path = dialog.GetPath() - else: - path = None - dialog.Destroy() - return path - -input_file_path = get_input_file("*.csv;*.xml") -if input_file_path is None: - sys.exit("ERROR: No input file selected!") - -fattureFileExtension = os.path.splitext(input_file_path)[1] - -output_file_path = get_output_file("TRAF2000") -if output_file_path is None: - sys.exit("ERROR: No output file selected!") - -if fattureFileExtension == ".csv": - fatture = fatture_import.import_csv(input_file_path) - -elif fattureFileExtension == ".xml": - fatture = fatture_import.import_xml(input_file_path) - -else: - sys.exit("ERROR: file extension not supported") - -traf2000_convert.convert(fatture, output_file_path) diff --git a/traf2000Converter/traf2000_convert.py b/traf2000Converter/traf2000_convert.py deleted file mode 100644 index b12c5dd..0000000 --- a/traf2000Converter/traf2000_convert.py +++ /dev/null @@ -1,177 +0,0 @@ -"""This script provides a function to convert a dict of invoices to a TRAF2000 file""" - -def convert(fatture, out_file_path): - """Output to a file the TRAF2000 records""" - with open(out_file_path, "w") as traf2000_file: - print("Note di credito:\n") - - for fattura in fatture.values(): - if fattura["tipoFattura"] != "Fattura" and fattura["tipoFattura"] != "Nota di credito": - print("Errore: il documento " + fattura["numFattura"] + " può essere FATTURA o NOTA DI CREDITO") - continue - - if len(fattura["cf"]) != 16 and len(fattura["cf"]) == 11: - print("Errore: il documento " + fattura["numFattura"] + " non ha cf/piva") - continue - - if fattura["tipoFattura"] == "Nota di credito": - # As for now this script doesn't handle "Note di credito" - print(fattura["numFattura"]) - continue - - linea = ["04103", "3", "0", "00000"] # TRF-DITTA + TRF-VERSIONE + TRF-TARC + TRF-COD-CLIFOR - linea.append(fattura["ragioneSociale"][:32]+' '*(32-len(fattura["ragioneSociale"]))) # TRF-RASO - linea.append(' '*30) # TRF-IND - linea.append('00000') # TRF-CAP - linea.append(' '*27) # TRF-CITTA + TRF-PROV - - if len(fattura["cf"]) == 16: # se c.f. presente - linea.append(fattura["cf"]) # TRF-COFI - linea.append('0'*11) # TRF-PIVA - linea.append('S') # TRF-PF - else: # se piva presente - linea.append(' '*16) # TRF-COFI - linea.append(fattura["cf"]) # TRF-PIVA - linea.append('N') # TRF-PF - - linea.append('0'*(2-len(fattura["posDivide"])) + fattura["posDivide"]) # TRF-DIVIDE - linea.append('0000') # TRF-PAESE - linea.append(' '*33) # TRF-PIVA-ESTERO + TRF-COFI-ESTERO + TRF-SESSO - linea.append('0'*8) # TRF-DTNAS - linea.append(' '*64) # TRF-COMNA + TRF-PRVNA + TRF-PREF + TRF-NTELE-NUM + TRF-FAX-PREF + TRF-FAX-NUM - linea.append('0'*22) # TRF-CFCONTO + TRF-CFCODPAG + TRF-CFBANCA + TRF-CFAGENZIA + TRF-CFINTERM - - if fattura["tipoFattura"] == "Fattura": - linea.append('001') # TRF-CAUSALE - linea.append("FATTURA VENDITA") # TRF-CAU-DES - else: - linea.append('002') # TRF-CAUSALE - linea.append("N.C. A CLIENTE ") # TRF-CAU-DES - - linea.append(' '*86) # TRF-CAU-AGG + TRF-CAU-AGG-1 + TRF-CAU-AGG-2 - linea.append(fattura["dataFattura"]*2) # TRF-DATA-REGISTRAZIONE + TRF-DATA-DOC - linea.append('00000000') # TRF-NUM-DOC-FOR - linea.append(fattura["numFattura"][4:9]) # TRF-NDOC - linea.append('00') # TRF-SERIE - linea.append('0'*72) # TRF-EC-PARTITA + TRF-EC-PARTITA-ANNO + TRF-EC-COD-VAL + TRF-EC-CAMBIO + TRF-EC-DATA-CAMBIO + TRF-EC-TOT-DOC-VAL + TRF-EC-TOT-IVA-VAL + TRF-PLAFOND - - conta = 0 - for desc, imponibile in fattura["righe"].items(): - conta += 1 - imponibile = str(imponibile) - if '-' in imponibile: - imponibile.replace('-', '') - imponibile = '0'*(11-len(imponibile)) + imponibile + "-" - else: - imponibile = '0'*(11-len(imponibile)) + imponibile + "+" - linea.append(imponibile) # TRF-IMPONIB - if desc != "Bollo": - linea.append('308') # TRF-ALIQ - else: - linea.append('315') # TRF-ALIQ - linea.append('0'*16) # TRF-ALIQ-AGRICOLA + TRF-IVA11 + TRF-IMPOSTA - - for _ in range(8-conta): - linea.append('0'*31) - - totale = str(fattura["importoTotale"]) - if '-' in totale: - totale.replace('-', '') - totale = '0'*(11-len(totale)) + totale + "-" - else: - totale = '0'*(11-len(totale)) + totale + "+" - linea.append(totale) # TRF-TOT-FAT - - conta = 0 - for desc, imponibile in fattura["righe"].items(): - conta += 1 - imponibile = str(imponibile) - if '-' in imponibile: - imponibile.replace('-', '') - imponibile = '0'*(11-len(imponibile)) + imponibile + "-" - else: - imponibile = '0'*(11-len(imponibile)) + imponibile + "+" - if desc != "Bollo": - linea.append('4004300') # TRF-CONTO-RIC - else: - linea.append('4004500') # TRF-CONTO-RIC - linea.append(imponibile) # TRF-IMP-RIC - - for _ in range(8-conta): - linea.append('0'*19) - - linea.append('000') # TRF-CAU-PAG - linea.append(' '*83) # TRF-CAU-DES-PAGAM + TRF-CAU-AGG-1-PAGAM + TRF-CAU-AGG-2-PAGAM - linea.append(('0000000' + ' ' + '0'*12 + ' '*18 + '0'*26)*80) # TRF-CONTO + TRF-DA + TRF-IMPORTO + TRF-CAU-AGGIUNT + TRF-EC-PARTITA-PAG + TRF-EC-PARTITA-ANNO-PAG + TRF-EC-IMP-VAL - linea.append((' ' + '0'*18)*10) # TRF-RIFER-TAB + TRF-IND-RIGA + TRF-DT-INI + TRF-DT-FIN - linea.append('000000') # TRF-DOC6 - linea.append('N' + '0') # TRF-AN-OMONIMI + TRF-AN-TIPO-SOGG - linea.append('00'*80) # TRF-EC-PARTITA-SEZ-PAG - linea.append('0'*15) # TRF-NUM-DOC-PAG-PROF + TRF-DATA-DOC-PAG-PROF - - if fattura["ritenutaAcconto"] != 0: - imponibile = str(fattura["ritenutaAcconto"]) - imponibile = '0'*(11-len(imponibile)) + imponibile + "+" - linea.append(imponibile) # TRF-RIT-ACC - else: - linea.append('0'*12) # TRF-RIT-ACC - - linea.append('0'*60) # TRF-RIT-PREV + TRF-RIT-1 + TRF-RIT-2 + TRF-RIT-3 + TRF-RIT-4 - linea.append('00'*8) # TRF-UNITA-RICAVI - linea.append('00'*80) # TRF-UNITA-PAGAM - linea.append(' '*24) # TRF-FAX-PREF-1 + TRF-FAX-NUM-1 - linea.append(' ' + ' ') # TRF-SOLO-CLIFOR + TRF-80-SEGUENTE - linea.append('0000000') # TRF-CONTO-RIT-ACC - linea.append('0'*35) # TRF-CONTO-RIT-PREV + TRF-CONTO-RIT-1 + TRF-CONTO-RIT-2 + TRF-CONTO-RIT-3 + TRF-CONTO-RIT-4 - linea.append('N' + 'N' + '00000000' + '000') # TRF-DIFFERIMENTO-IVA + TRF-STORICO + TRF-STORICO-DATA + TRF-CAUS-ORI - linea.append(' ' + ' ' + '0'*16 + ' ') # TRF-PREV-TIPOMOV + TRF-PREV-RATRIS + TRF-PREV-DTCOMP-INI + TRF-PREV-DTCOMP-FIN + TRF-PREV-FLAG-CONT - linea.append(' '*20 + '0'*21 + ' '*44 + '0'*8 + ' ' + '0'*6 + ' ' + '00' + ' ') # TRF-RIFERIMENTO + TRF-CAUS-PREST-ANA + TRF-EC-TIPO-PAGA + TRF-CONTO-IVA-VEN-ACQ + TRF-PIVA-VECCHIA + TRF-PIVA-ESTERO-VECCHIA + # TRF-RISERVATO + TRF-DATA-IVA-AGVIAGGI + TRF-DATI-AGG-ANA-REC4 + TRF-RIF-IVA-NOTE-CRED + TRF-RIF-IVA-ANNO-PREC + TRF-NATURA-GIURIDICA + TRF-STAMPA-ELENCO - linea.append('000'*8 + ' '*20 + '0' + ' '*4 + '0'*6 + ' '*20 + 'S' + 'N') # TRF-PERC-FORF + TRF-SOLO-MOV-IVA + TRF-COFI-VECCHIO + TRF-USA-PIVA-VECCHIA + TRF-USA-PIVA-EST-VECCHIA + TRF-USA-COFI-VECCHIO + TRF-ESIGIBILITA-IVA + TRF-TIPO-MOV-RISCONTI + TRF-AGGIORNA-EC + TRF-BLACKLIST-ANAG + TRF-BLACKLIST-IVA-ANNO + TRF-CONTEA-ESTERO + TRF-ART21-ANAG + TRF-ART21-IVA - - if fattura["tipoFattura"] == "Fattura": - linea.append('N') # TRF-RIF-FATTURA - else: - linea.append('S') # TRF-RIF-FATTURA - linea.append('S' + ' '*2 + 'S' + ' '*2) # TRF-RISERVATO-B + TRF-MASTRO-CF + TRF-MOV-PRIVATO + TRF-SPESE-MEDICHE + TRF-FILLER - linea.append('\n') - - #RECORD 5 per Tessera Sanitaria - linea.append('04103' + '3' + '5') # TRF5-DITTA + TRF5-VERSIONE + TRF5-TARC - linea.append(' '*1200) # TRF-ART21-CONTRATTO - linea.append('0'*6 + fattura["cf"]) # TRF-A21CO-ANAG + # TRF-A21CO-COFI - totale = str(fattura["importoTotale"]) - totale = '0'*(13-len(totale)) + totale + "+" - linea.append(fattura["dataFattura"] + 'S' + '000' + totale + '0'*14 + '0' + fattura["numFattura"][4:9] + '00' + ' '*40) # TRF-A21CO-DATA + TRF-A21CO-FLAG + TRF-A21CO-ALQ + TRF-A21CO-IMPORTO + TRF-A21CO-IMPOSTA + TRF-A21CO-NDOC + TRF-A21CO-CONTRATTO - linea.append(('0'*6 + ' '*16 + '0'*8 + ' ' + '000' + '0'*14 + '0'*14 + '0'*8 + ' '*40)*49) # TRF-A21CO-DATA + TRF-A21CO-FLAG + TRF-A21CO-ALQ + TRF-A21CO-IMPORTO + TRF-A21CO-IMPOSTA + TRF-A21CO-NDOC + TRF-A21CO-CONTRATTO - - if fattura["tipoFattura"] == "Nota di credito": - linea.append('000' + fattura["rifFattura"][4:9]) # TRF-RIF-FATT-NDOC - linea.append('0'*8) # TRF-RIF-FATT-DDOC - else: - linea.append('0'*16) # TRF-RIF-FATT-NDOC + TRF-RIF-FATT-DDOC - linea.append('F' + 'SR' + '2') # TRF-A21CO-TIPO + TRF-A21CO-TIPO-SPESA + TRF-A21CO-FLAG-SPESA - linea.append((' ' + ' ' + ' ')*49) # TRF-A21CO-TIPO + TRF-A21CO-TIPO-SPESA + TRF-A21CO-FLAG-SPESA - linea.append(' ' + 'S' + ' '*76) # TRF-SPESE-FUNEBRI + TRF-A21CO-PAGAM + FILLER + FILLER - linea.append('\n') - - #RECORD 1 per num. doc. originale - linea.append('04103' + '3' + '1') # TRF1-DITTA + TRF1-VERSIONE + TRF1-TARC - linea.append('0'*7 + ' '*3 + '0'*14) # TRF-NUM-AUTOFATT + TRF-SERIE-AUTOFATT + TRF-COD-VAL + TRF-TOTVAL - linea.append((' '*8 + '0'*24 + ' ' + '0'*36 + ' '*2 + '0'*9 + ' '*5)*20) # TRF-NOMENCLATURA + TRF-IMP-LIRE + TRF-IMP-VAL + TRF-NATURA + TRF-MASSA + TRF-UN-SUPPL + TRF-VAL-STAT + TRF-REGIME + TRF-TRASPORTO + TRF-PAESE-PROV + TRF-PAESE-ORIG + TRF-PAESE-DEST + TRF-PROV-DEST + TRF-PROV-ORIG + TRF-SEGNO-RET - linea.append(' ' + '0'*6 + ' '*173) # TRF-INTRA-TIPO + TRF-MESE-ANNO-RIF + SPAZIO - linea.append('0'*45 + ' '*4 + '0'*20 + ' '*28 + '0'*25) # TRF-RITA-TIPO + TRF-RITA-IMPON + TRF-RITA-ALIQ + TRF-RITA-IMPRA + TRF-RITA-PRONS + TRF-RITA-MESE + TRF-RITA-CAUSA + TRF-RITA-TRIBU + TRF-RITA-DTVERS + TRF-RITA-IMPAG + TRF-RITA-TPAG + TRF-RITA-SERIE + TRF-RITA-QUIETANZA + TRF-RITA-NUM-BOLL + TRF-RITA-ABI + TRF-RITA-CAB + TRF-RITA-AACOMP + TRF-RITA-CRED - linea.append(' ' + '0'*44 + ' '*11 + '0'*64) #TRF-RITA-SOGG + TRF-RITA-BASEIMP + TRF-RITA-FRANCHIGIA + TRF-RITA-CTO-PERC + TRF-RITA-CTO-DITT + FILLER + TRF-RITA-DATA + TRF-RITA-TOTDOC + TRF-RITA-IMPVERS + TRF-RITA-DATA-I + TRF-RITA-DATA-F + TRF-EMENS-ATT + TRF-EMENS-RAP + TRF-EMENS-ASS + TRF-RITA-TOTIVA - linea.append('0'*6 + ' '*178) # TRF-CAUS-PREST-ANA-B + TRF-RITA-CAUSA-B + FILLER - linea.append('0'*13 + ' '*30 + '0'*14) # TRF-POR-CODPAG + TRF-POR-BANCA + TRF-POR-AGENZIA + TRF-POR-DESAGENZIA + TRF-POR-TOT-RATE + TRF-POR-TOTDOC - linea.append(('0'*65 + ' '*2)*12) # TRF-POR-NUM-RATA + TRF-POR-DATASCAD + TRF-POR-TIPOEFF + TRF-POR-IMPORTO-EFF + TRF-POR-IMPORTO-EFFVAL + TRF-POR-IMPORTO-BOLLI + TRF-POR-IMPORTO-BOLLIVAL + TRF-POR-FLAG + TRF-POR-TIPO-RD - linea.append('0'*4 + ' '*336) # TRF-POR-CODAGE + TRF-POR-EFFETTO-SOSP + TRF-POR-CIG + TRF-POR-CUP + SPAZIO - linea.append((' '*3 + '0'*16)*20) # TRF-COD-VAL-IV + TRF-IMP-VALUTA-IV - linea.append((' '*6 + '0'*35 + ' '*2 + '0'*20 + ' '*19 + '0'*16)*20) # TRF-CODICE-SERVIZIO + TRF-STATO-PAGAMENTO + TRF-SERV-IMP-EURO + TRF-SERV-IMP-VAL + TRF-DATA-DOC-ORIG + TRF-MOD-EROGAZIONE + TRF-MOD-INCASSO + TRF-PROT-REG + TRF-PROG-REG + TRF-COD-SEZ-DOG-RET + TRF-ANNO-REG-RET + TRF-NUM-DOC-ORIG + TRF-SERV-SEGNO-RET + TRF-SERV-COD-VAL-IV + TRF-SERV-IMP-VALUTA-IV - linea.append(' '*1 + '0'*6) # TRF-INTRA-TIPO-SERVIZIO + TRF-SERV-MESE-ANNO-RIF - linea.append(' '*8) # TRF-CK-RCHARGE - linea.append('0'*(15-len(fattura["numFattura"])) + fattura["numFattura"]) # TRF-XNUM-DOC-ORI - linea.append(' ' + '00' + ' '*1090) # TRF-MEM-ESIGIB-IVA + TRF-COD-IDENTIFICATIVO + TRF-ID-IMPORTAZIONE + TRF-XNUM-DOC-ORI-20 + SPAZIO + FILLER - - linea = ''.join(linea) + '\n' - - traf2000_file.write(linea)