diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f065f39
--- /dev/null
+++ b/README.md
@@ -0,0 +1,40 @@
+# GOFATQR
+Small package to parse the strings contained the QR codes in portuguese
+invoices.
+Returns a struct containing go idiomatic types accordingly to the spec sheet.
+
+
+# USAGE
+```go
+package main
+
+import (
+ "github.com/ESilva15/gofatqr"
+ "log"
+)
+
+func main() {
+ input := "A:123456789*B:999999990*C:PT*D:FT*E:N*F:20191231*" +
+ "G:FT AB2019/0035*H:CSDF7T5H-0035*I1:PT*I2:12000.00*I3:15000.00*" +
+ "I4:900.00*I5:50000.00*I6:6500.00*I7:80000.00*I8:18400.00*J1:PT-AC*" +
+ "J2:10000.00*J3:25000.56*J4:1000.02*J5:75000.00*J6:6750.00*" +
+ "J7:100000.00*J8:18000.00*K1:PT-MA*K2:5000.00*K3:12500.00*K4:625.00*" +
+ "K5:25000.00*K6:3000.00*K7:40000.00*K8:8800.00*L:100.00*M:25.00*" +
+ "N:64000.02*O:513600.58*P:100.00*Q:kLp0*R:9999*" +
+ "S:TB;PT00000000000000000000000;513500.58"
+
+ var qr gofatqr.FatQR
+ err := qr.Scan(input, 0)
+ if err != nil {
+ log.Println("Oopsie:", err)
+ }
+
+ log.Printf("%+v\n", qr)
+}
+```
+
+
+# TODO - Improvements
+- [ ] NIF (portuguese and international) validation
+- [ ] Make it as compliant with the types from SAF-T as possible
+- [ ] Scan modes for stricter scanning
diff --git a/api_test.go b/api_test.go
index 95b27e3..ffe0a5d 100644
--- a/api_test.go
+++ b/api_test.go
@@ -1,7 +1,7 @@
package gofatqr_test
import (
- "gofatqr"
+ "github.com/ESilva15/gofatqr"
"testing"
)
diff --git a/coverage.html b/coverage.html
deleted file mode 100644
index 86572c1..0000000
--- a/coverage.html
+++ /dev/null
@@ -1,806 +0,0 @@
-
-
-
-
-
-
-
package gofatqr
-
-// TODO
-// Check what we can take form SAF-T to put here, maybe work on a SAF-T
-// implementation too, who knows amirite
-
-import (
- "fmt"
- "strconv"
- "strings"
- "time"
-
- dec "github.com/shopspring/decimal"
-)
-
-type FiscalSpace struct {
- TaxCountryRegion string // 1 - fiscal space
- TaxableBase *dec.Decimal // 2
- TaxableReduced *dec.Decimal // 3
- VatTotalReducedTax *dec.Decimal // 4
- TaxableIntermediateBase *dec.Decimal // 5
- VatTotalIntermediateTax *dec.Decimal // 6
- TaxableNormalBase *dec.Decimal // 7
- VatTotalNormalBase *dec.Decimal // 8
-}
-
-type FatQR struct {
- TaxRegistrationNumber string // A
- CustomerTaxID string // B
- Country string // C customer country ISO 3166-1 alpha2
- InvoiceType string // D
- InvoiceStatus string // E
- InvoiceDate time.Time // F
- InvoiceNo string // G
- ATCUD string // H
- IFiscalSpace FiscalSpace // I1-I8
- JFiscalSpace FiscalSpace // I1-I8
- KFiscalSpace FiscalSpace // I1-I8
- NotTaxable *dec.Decimal // L
- StampDuty *dec.Decimal // M "Imposto de Selo"
- TaxPayable *dec.Decimal // N
- GrossTotal *dec.Decimal // O
- WithholdingTaxAmount *dec.Decimal // P
- HashQuartet string // Q
- SoftwareCertificateNumber int64 // R
- OtherInfo string // S
-}
-
-type FieldCodec struct {
- Required bool
- Parse func(f *FatQR, val string) error
- String func(f *FatQR) string
- Empty func(f *FatQR) bool
-}
-
-var (
- fieldOrder = []string{
- "A", "B", "C", "D", "E", "F", "G", "H",
- "I1", "I2", "I3", "I4", "I5", "I6", "I7", "I8",
- "J1", "J2", "J3", "J4", "J5", "J6", "J7", "J8",
- "K1", "K2", "K3", "K4", "K5", "K6", "K7", "K8",
- "L", "M", "N", "O", "P", "Q", "R", "S",
- }
- // lastKey = fieldOrder[len(fieldOrder)-1]
-)
-
-const (
- decCount = 2
- separator = "*"
-)
-
-var fatQRFieldMap = map[string]FieldCodec{
- "A": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- // if !isValidNIF(val) {
- // return fmt.Errorf("invalid NIF `%s`", val)
- // }
-
- f.TaxRegistrationNumber = val
- return nil
- },
- String: func(f *FatQR) string {
- return "A:" + f.TaxRegistrationNumber
- },
- Empty: func(f *FatQR) bool {
- return f.TaxRegistrationNumber == ""
- },
- },
- "B": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- // if !isValidNIF(val) {
- // return fmt.Errorf("invalid NIF `%s`", val)
- // }
-
- f.CustomerTaxID = val
- return nil
- },
- String: func(f *FatQR) string {
- return "B:" + f.CustomerTaxID
- },
- Empty: func(f *FatQR) bool {
- return f.CustomerTaxID == ""
- },
- },
- "C": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- // We can use biter777/countries package around here for verification
- // but this comes from a code, so eeehhh
- f.Country = val
- return nil
- },
- String: func(f *FatQR) string {
- return "C:" + f.Country
- },
- Empty: func(f *FatQR) bool {
- return f.Country == ""
- },
- },
- "D": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- f.InvoiceType = val
- return nil
- },
- String: func(f *FatQR) string {
- return "D:" + f.InvoiceType
- },
- Empty: func(f *FatQR) bool {
- return f.InvoiceType == ""
- },
- },
- "E": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- f.InvoiceStatus = val
- return nil
- },
- String: func(f *FatQR) string {
- return "E:" + f.InvoiceStatus
- },
- Empty: func(f *FatQR) bool {
- return f.InvoiceStatus == ""
- },
- },
- "F": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- tz, err := time.LoadLocation("Europe/Lisbon")
- if err != nil {
- return err
- }
-
- f.InvoiceDate, err = time.ParseInLocation("20060102", val, tz)
- if err != nil {
- return err
- }
-
- return nil
- },
- String: func(f *FatQR) string {
- return "F:" + f.InvoiceDate.Format("20060102")
- },
- Empty: func(f *FatQR) bool {
- return f.InvoiceStatus == ""
- },
- },
- "G": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- f.InvoiceNo = val
- return nil
- },
- String: func(f *FatQR) string {
- return "G:" + f.InvoiceNo
- },
- Empty: func(f *FatQR) bool {
- return f.InvoiceNo == ""
- },
- },
- "H": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- // TODO
- // Add some ATCUD validation
- f.ATCUD = val
- return nil
- },
- String: func(f *FatQR) string {
- return "H:" + f.ATCUD
- },
- Empty: func(f *FatQR) bool {
- return f.ATCUD == ""
- },
- },
- "I1": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- f.IFiscalSpace.TaxCountryRegion = val
- return nil
- },
- String: func(f *FatQR) string {
- return "I1:" + f.IFiscalSpace.TaxCountryRegion
- },
- Empty: func(f *FatQR) bool {
- return f.IFiscalSpace.TaxCountryRegion == ""
- },
- },
- "I2": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.IFiscalSpace.TaxableBase, val)
- },
- String: func(f *FatQR) string {
- return "I2:" + stringDecimal(f.IFiscalSpace.TaxableBase)
- },
- Empty: func(f *FatQR) bool {
- return f.IFiscalSpace.TaxableBase == nil
- },
- },
- "I3": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.IFiscalSpace.TaxableReduced, val)
- },
- String: func(f *FatQR) string {
- return "I3:" + stringDecimal(f.IFiscalSpace.TaxableReduced)
- },
- Empty: func(f *FatQR) bool {
- return f.IFiscalSpace.TaxableReduced == nil
- },
- },
- "I4": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.IFiscalSpace.VatTotalReducedTax, val)
- },
- String: func(f *FatQR) string {
- return "I4:" + stringDecimal(f.IFiscalSpace.VatTotalReducedTax)
- },
- Empty: func(f *FatQR) bool {
- return f.IFiscalSpace.VatTotalReducedTax == nil
- },
- },
- "I5": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.IFiscalSpace.TaxableIntermediateBase, val)
- },
- String: func(f *FatQR) string {
- return "I5:" + stringDecimal(f.IFiscalSpace.TaxableIntermediateBase)
- },
- Empty: func(f *FatQR) bool {
- return f.IFiscalSpace.TaxableIntermediateBase == nil
- },
- },
- "I6": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.IFiscalSpace.VatTotalIntermediateTax, val)
- },
- String: func(f *FatQR) string {
- return "I6:" + stringDecimal(f.IFiscalSpace.VatTotalIntermediateTax)
- },
- Empty: func(f *FatQR) bool {
- return f.IFiscalSpace.VatTotalIntermediateTax == nil
- },
- },
- "I7": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.IFiscalSpace.TaxableNormalBase, val)
- },
- String: func(f *FatQR) string {
- return "I7:" + stringDecimal(f.IFiscalSpace.TaxableNormalBase)
- },
- Empty: func(f *FatQR) bool {
- return f.IFiscalSpace.TaxableNormalBase == nil
- },
- },
- "I8": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.IFiscalSpace.VatTotalNormalBase, val)
- },
- String: func(f *FatQR) string {
- return "I8:" + stringDecimal(f.IFiscalSpace.VatTotalNormalBase)
- },
- Empty: func(f *FatQR) bool {
- return f.IFiscalSpace.VatTotalNormalBase == nil
- },
- },
- "J1": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- f.JFiscalSpace.TaxCountryRegion = val
- return nil
- },
- String: func(f *FatQR) string {
- return "J1:" + f.JFiscalSpace.TaxCountryRegion
- },
- Empty: func(f *FatQR) bool {
- return f.JFiscalSpace.TaxCountryRegion == ""
- },
- },
- "J2": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.JFiscalSpace.TaxableBase, val)
- },
- String: func(f *FatQR) string {
- return "J2:" + stringDecimal(f.JFiscalSpace.TaxableBase)
- },
- Empty: func(f *FatQR) bool {
- return f.JFiscalSpace.TaxableBase == nil
- },
- },
- "J3": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.JFiscalSpace.TaxableReduced, val)
- },
- String: func(f *FatQR) string {
- return "J3:" + stringDecimal(f.JFiscalSpace.TaxableReduced)
- },
- Empty: func(f *FatQR) bool {
- return f.JFiscalSpace.TaxableReduced == nil
- },
- },
- "J4": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.JFiscalSpace.VatTotalReducedTax, val)
- },
- String: func(f *FatQR) string {
- return "J4:" + stringDecimal(f.JFiscalSpace.VatTotalReducedTax)
- },
- Empty: func(f *FatQR) bool {
- return f.JFiscalSpace.VatTotalReducedTax == nil
- },
- },
- "J5": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.JFiscalSpace.TaxableIntermediateBase, val)
- },
- String: func(f *FatQR) string {
- return "J5:" + stringDecimal(f.JFiscalSpace.TaxableIntermediateBase)
- },
- Empty: func(f *FatQR) bool {
- return f.JFiscalSpace.TaxableIntermediateBase == nil
- },
- },
- "J6": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.JFiscalSpace.VatTotalIntermediateTax, val)
- },
- String: func(f *FatQR) string {
- return "J6:" + stringDecimal(f.JFiscalSpace.VatTotalIntermediateTax)
- },
- Empty: func(f *FatQR) bool {
- return f.JFiscalSpace.VatTotalIntermediateTax == nil
- },
- },
- "J7": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.JFiscalSpace.TaxableNormalBase, val)
- },
- String: func(f *FatQR) string {
- return "J7:" + stringDecimal(f.JFiscalSpace.TaxableNormalBase)
- },
- Empty: func(f *FatQR) bool {
- return f.JFiscalSpace.TaxableNormalBase == nil
- },
- },
- "J8": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.JFiscalSpace.VatTotalNormalBase, val)
- },
- String: func(f *FatQR) string {
- return "J8:" + stringDecimal(f.JFiscalSpace.VatTotalNormalBase)
- },
- Empty: func(f *FatQR) bool {
- return f.JFiscalSpace.VatTotalNormalBase == nil
- },
- },
- "K1": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- f.KFiscalSpace.TaxCountryRegion = val
- return nil
- },
- String: func(f *FatQR) string {
- return "K1:" + f.KFiscalSpace.TaxCountryRegion
- },
- Empty: func(f *FatQR) bool {
- return f.KFiscalSpace.TaxCountryRegion == ""
- },
- },
- "K2": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.KFiscalSpace.TaxableBase, val)
- },
- String: func(f *FatQR) string {
- return "K2:" + stringDecimal(f.KFiscalSpace.TaxableBase)
- },
- Empty: func(f *FatQR) bool {
- return f.KFiscalSpace.TaxableNormalBase == nil
- },
- },
- "K3": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.KFiscalSpace.TaxableReduced, val)
- },
- String: func(f *FatQR) string {
- return "K3:" + stringDecimal(f.KFiscalSpace.TaxableReduced)
- },
- Empty: func(f *FatQR) bool {
- return f.KFiscalSpace.TaxableReduced == nil
- },
- },
- "K4": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.KFiscalSpace.VatTotalReducedTax, val)
- },
- String: func(f *FatQR) string {
- return "K4:" + stringDecimal(f.KFiscalSpace.VatTotalReducedTax)
- },
- Empty: func(f *FatQR) bool {
- return f.KFiscalSpace.VatTotalReducedTax == nil
- },
- },
- "K5": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.KFiscalSpace.TaxableIntermediateBase, val)
- },
- String: func(f *FatQR) string {
- return "K5:" + stringDecimal(f.KFiscalSpace.TaxableIntermediateBase)
- },
- Empty: func(f *FatQR) bool {
- return f.KFiscalSpace.TaxableIntermediateBase == nil
- },
- },
- "K6": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.KFiscalSpace.VatTotalIntermediateTax, val)
- },
- String: func(f *FatQR) string {
- return "K6:" + stringDecimal(f.KFiscalSpace.VatTotalIntermediateTax)
- },
- Empty: func(f *FatQR) bool {
- return f.KFiscalSpace.VatTotalIntermediateTax == nil
- },
- },
- "K7": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.KFiscalSpace.TaxableNormalBase, val)
- },
- String: func(f *FatQR) string {
- return "K7:" + stringDecimal(f.KFiscalSpace.TaxableNormalBase)
- },
- Empty: func(f *FatQR) bool {
- return f.KFiscalSpace.TaxableNormalBase == nil
- },
- },
- "K8": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.KFiscalSpace.VatTotalNormalBase, val)
- },
- String: func(f *FatQR) string {
- return "K8:" + stringDecimal(f.KFiscalSpace.VatTotalNormalBase)
- },
- Empty: func(f *FatQR) bool {
- return f.KFiscalSpace.VatTotalNormalBase == nil
- },
- },
- "L": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.NotTaxable, val)
- },
- String: func(f *FatQR) string {
- return "L:" + stringDecimal(f.NotTaxable)
- },
- Empty: func(f *FatQR) bool {
- return f.NotTaxable == nil
- },
- },
- "M": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.StampDuty, val)
- },
- String: func(f *FatQR) string {
- return "M:" + stringDecimal(f.StampDuty)
- },
- Empty: func(f *FatQR) bool {
- return f.StampDuty == nil
- },
- },
- "N": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.TaxPayable, val)
- },
- String: func(f *FatQR) string {
- return "N:" + stringDecimal(f.TaxPayable)
- },
- Empty: func(f *FatQR) bool {
- return f.TaxPayable == nil
- },
- },
- "O": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.GrossTotal, val)
- },
- String: func(f *FatQR) string {
- return "O:" + stringDecimal(f.GrossTotal)
- },
- Empty: func(f *FatQR) bool {
- return f.GrossTotal == nil
- },
- },
- "P": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- return parseDecimal(&f.WithholdingTaxAmount, val)
- },
- String: func(f *FatQR) string {
- return "P:" + stringDecimal(f.WithholdingTaxAmount)
- },
- Empty: func(f *FatQR) bool {
- return f.WithholdingTaxAmount == nil
- },
- },
- "Q": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- f.HashQuartet = val
- return nil
- },
- String: func(f *FatQR) string {
- return "Q:" + f.HashQuartet
- },
- Empty: func(f *FatQR) bool {
- return f.HashQuartet == ""
- },
- },
- "R": {
- Required: true,
- Parse: func(f *FatQR, val string) error {
- var err error
- f.SoftwareCertificateNumber, err = strconv.ParseInt(val, 10, 16)
- if err != nil {
- return err
- }
-
- return nil
- },
- String: func(f *FatQR) string {
- return "R:" + fmt.Sprintf("%04d", f.SoftwareCertificateNumber)
- },
- Empty: func(f *FatQR) bool {
- return f.SoftwareCertificateNumber < 1
- },
- },
- "S": {
- Required: false,
- Parse: func(f *FatQR, val string) error {
- f.OtherInfo = val
- return nil
- },
- String: func(f *FatQR) string {
- return "S:" + f.OtherInfo
- },
- Empty: func(f *FatQR) bool {
- return f.OtherInfo == ""
- },
- },
-}
-
-func parseDecimal(f **dec.Decimal, val string) error {
- if *f == nil {
- *f = new(dec.Decimal)
- }
- return (*f).Scan(val)
-}
-
-func stringDecimal(d *dec.Decimal) string {
- return d.Truncate(decCount).StringFixed(decCount)
-}
-
-func (fq *FatQR) scanPart(part []string) error {
- val, ok := fatQRFieldMap[part[0]]
- if !ok {
- return fmt.Errorf("invalid key `%s`", part[0])
- }
-
- err := val.Parse(fq, part[1])
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (fq *FatQR) scanParts(parts []string) error {
- for k := range parts {
- keyValuePair := strings.Split(parts[k], ":")
- if len(keyValuePair) != 2 {
- return fmt.Errorf("failed to parse key value pair: %s", parts[k])
- }
-
- err := fq.scanPart(keyValuePair)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// TODO
-// Use flags like STRICT | VALIDATE to do conditional stuff
-func (fq *FatQR) Scan(s string) error {
- parts := strings.Split(s, "*")
-
- err := fq.scanParts(parts)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (fq *FatQR) String() string {
- parts := make([]string, 0, len(fieldOrder))
-
- for _, key := range fieldOrder {
- codec, ok := fatQRFieldMap[key]
- if !ok {
- panic("invalid key in order")
- }
-
- // TODO
- // Using the flags I have to add, use it to enforce or not obligatory fields
- // For now we are only going to stringify the data we have
- if !codec.Empty(fq) {
- parts = append(parts, codec.String(fq))
- }
- }
-
- return strings.Join(parts, separator)
-}
-
-
-
package gofatqr
-
-func Parse(data string) {
-}
-
-
-
package gofatqr
-
-import (
- "unicode"
-)
-
-// TODO
-// Add a way to check with an API or whatever like nif.pt
-// Also need to be able to indentify other countries NIFs I guess
-// some validation at the end
-
-func isValidNIF(n string) bool {
- if len(n) != 9 {
- return false
- }
-
- for _, c := range n {
- if !unicode.IsDigit(c) {
- return false
- }
- }
-
- return true
-}
-
-
-
-
-
-
diff --git a/fatQR.go b/fatQR.go
index eb0ad29..d3e4a46 100644
--- a/fatQR.go
+++ b/fatQR.go
@@ -641,8 +641,7 @@ func (fq *FatQR) scanParts(parts []string) error {
return nil
}
-// TODO
-// Use flags like STRICT | VALIDATE to do conditional stuff
+// Scan scans a given string and applies the rules of `mode` (to be defined)
func (fq *FatQR) Scan(s string, mode ScanMode) error {
parts := strings.Split(s, "*")
diff --git a/go.mod b/go.mod
index d2cd057..76e87e9 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module gofatqr
+module github.com/ESilva15/gofatqr
go 1.24.6
diff --git a/main.go b/main.go
deleted file mode 100644
index 606219e..0000000
--- a/main.go
+++ /dev/null
@@ -1,4 +0,0 @@
-package gofatqr
-
-func Parse(data string) {
-}