Merge pull request #3 from Noettore/pythonRewrite

Complete python rewrite
This commit is contained in:
2021-01-07 23:04:37 +01:00
committed by GitHub
26 changed files with 786 additions and 831 deletions

3
.gitignore vendored
View File

@@ -24,4 +24,5 @@ __pycache__/*
*.csv
*.xml
*.ipynb
*.spec
*.spec
*.wxg

View File

@@ -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
This project is licensed under the MIT License - see the [LICENSE.md](/LICENSE.md) file for details

View File

@@ -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

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

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

0
fatture_ccsr/__init__.py Normal file
View File

View File

@@ -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()

41
fatture_ccsr/exc.py Normal file
View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

6
requirements.txt Normal file
View File

@@ -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

32
setup.py Normal file
View File

@@ -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',
)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)