Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First version #1

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Lint
on:
push:
tags:
- v*
branches:
- main
pull_request:
jobs:
lint:
name: golangci-lint
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version-file: "go.mod"
id: go

- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.59.1
24 changes: 24 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Test Go
on: [push]
jobs:
test:
name: Test
runs-on: ubuntu-latest

steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: "1.22.3"
id: go

- name: Check out code
uses: actions/checkout@v2

- name: Install Dependencies
env:
GOPROXY: https://proxy.golang.org,direct
run: go mod download

- name: Test
run: go test -tags unit -race ./...
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@
# Go workspace file
go.work
go.work.sum

/prueba

.env
15 changes: 15 additions & 0 deletions .golangci.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[run]
timeout = "120s"

[output]
format = "colored-line-number"

[linters]
enable = [
"gocyclo", "unconvert", "goimports", "unused",
"vetshadow", "misspell", "nakedret", "errcheck", "revive", "ineffassign",
"goconst", "vet", "unparam", "gofmt"
]

[issues]
exclude-use-default = false
173 changes: 172 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,173 @@
# gobl.hu-nav
Convert GOBL into Hungarian NAV XML documents
Go library to convert [GOBL](https://github.com/invopop/gobl) invoices into TicketBAI declarations and send them to the Hungarian web services.

Copyright [Invopop Ltd.](https://invopop.com) 2023. Released publicly under the [Apache License v2.0](LICENSE). For commercial licenses please contact the [dev team at invopop](mailto:[email protected]). For contributions to this library to be accepted, we will require you to accept transferring your copyright to Invopop Ltd.

## Usage

### Go package

#### Conversion
Usage of the XInvoice conversion library is quite straight forward. You must first have a GOBL Envelope including an invoice ready to convert.

```go
package main

import (
"os"

"github.com/invopop/gobl"
nav "github.com/invopop/gobl.hu-nav"
)

func main() {
data, _ := os.ReadFile("./test/data/invoice-test.json")

env := new(gobl.Envelope)
if err := json.Unmarshal(data, env); err != nil {
panic(err)
}

// Prepare the Nav document
doc, err := nav.NewDocument(env)
if err != nil {
panic(err)
}

// Create the XML output
out, err := nav.BytesIndent(doc)
if err != nil {
panic(err)
}

// TODO: do something with the output
}
```

#### Invoice Reporting

Once the invoice is generated, it can be reported to the Hungarian authoritites. You must first have a technical user created in the [Online Szamla](https://onlineszamla.nav.gov.hu/home).

```go
package main

import (
"os"

"github.com/invopop/gobl"
nav "github.com/invopop/gobl.hu-nav"
)

func main() {

// Software is the information regarding the system used to report the invoices
software := nav.NewSoftware(
tax.Identity{Country: l10n.ES.Tax(), Code: cbc.Code("B12345678")},
"Invopop",
"ONLINE_SERVICE",
"1.0.0",
"TestDev",
"[email protected]",
)

// User is all the data obtained from the technical user that it is needed to report the invoices
user := nav.NewUser(
"username",
"password",
"signature_key",
"exchange_key",
"taxID",
)

// Create a new client with the user and software data and choose if you want to issue the invoices in the testing or production environment
navClient := nav.NewNav(user, software, nav.InTesting())

//We load the invoice
invoice, err := os.ReadFile("test/data/out/output.xml")
if err != nil {
panic(err)
}

// Report the invoice
transactionId, err := navClient.ReportInvoice(invoice, "CREATE")
if err != nil {
panic(err)
}

// Keep the transaction ID for the status query
}
```

#### Invoice Status
Once an invoice is reported, you can query the status of the invoice at any time.

```go
package main

import (
"os"

"github.com/invopop/gobl"
nav "github.com/invopop/gobl.hu-nav"
)

func main(){

// To query the status of an invoice, you need the transaction ID, which is returned by the ReportInvoice function.
transactionId := "4Q220PNVP43MOU5G"

// Create a new client with the user and software data and choose if you want to issue the invoices in the testing or production environment
navClient := nav.NewNav(user, software, nav.InTesting())

// Query the status of the invoice
resultsList, err := navClient.GetTransactionStatus(transactionId)
if err != nil {
panic(err)
}

// resultsList is a list of ProcessingResult, which contains the status of each invoice in the transaction
// You can access the status of each invoice by iterating through the list
for _, r := range resultsList {
fmt.Println(r.InvoiceStatus)
}

// If you want to see the detailed messages, you can access the TechnicalValidationMessages and BusinessValidationMessages fields, that are also lists
for _, r := range resultsList {
for _, m := range r.TechnicalValidationMessages {
fmt.Println(m.Message)
}
for _, m := range r.BusinessValidationMessages {
fmt.Println(m.Message)
}
}
}
```
### Command Line
#### Conversion

The GOBL NAV package tool also includes a command line helper. You can install manually in your Go environment with:

```bash
go install ./cmd/gobl.nav
```

Usage is very straightforward:

```bash
gobl.nav convert ./test/data/invoice.json
```


## Limitations/Things to do

### Invoice Modification
- For invoice modification the only step left is to get the line number of the invoice that we want to modify and include it in the field `LineModificationReference`

### Doc Conversion
- Batch invoicing not supported
- Support fiscal representatives
- Aggregate invoices not supported
- Product refund charges not supported (Field Product Fee Summary in the Invoice)
- Nav supports 100 invoice creation/modification in the same request. For the moment, we only support 1 invoice at each request.


70 changes: 70 additions & 0 deletions cmd/gobl.nav/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Package main provides the command line interface to the NAV package.
package main

import (
"bytes"
"encoding/json"
"fmt"

"github.com/invopop/gobl"
nav "github.com/invopop/gobl.hu-nav"
"github.com/spf13/cobra"
)

type convertOpts struct {
*rootOpts
}

func convert(o *rootOpts) *convertOpts {
return &convertOpts{rootOpts: o}
}

func (c *convertOpts) cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "convert [infile] [outfile]",
Short: "Convert a GOBL JSON into a NAV XML",
RunE: c.runE,
}

return cmd
}

func (c *convertOpts) runE(cmd *cobra.Command, args []string) error {
input, err := openInput(cmd, args)
if err != nil {
return err
}
defer input.Close() // nolint:errcheck

out, err := c.openOutput(cmd, args)
if err != nil {
return err
}
defer out.Close() // nolint:errcheck

buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(input); err != nil {
return fmt.Errorf("reading input: %w", err)
}

env := new(gobl.Envelope)
if err := json.Unmarshal(buf.Bytes(), env); err != nil {
return fmt.Errorf("unmarshaling gobl envelope: %w", err)
}

doc, err := nav.NewDocument(env)
if err != nil {
panic(err)
}

data, err := nav.BytesIndent(doc)
if err != nil {
return fmt.Errorf("generating nav xml: %w", err)
}

if _, err = out.Write(append(data, '\n')); err != nil {
return fmt.Errorf("writing nav xml: %w", err)
}

return nil
}
36 changes: 36 additions & 0 deletions cmd/gobl.nav/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
)

// build data provided by goreleaser and mage setup
var (
version = "dev"
date = ""
)

func main() {
if err := run(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func run() error {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()

return root().cmd().ExecuteContext(ctx)
}

func inputFilename(args []string) string {
if len(args) > 0 && args[0] != "-" {
return args[0]
}
return ""
}
Loading
Loading