Skip to content

Commit

Permalink
support for downloading invoices from KSeF
Browse files Browse the repository at this point in the history
  • Loading branch information
toudi committed Dec 15, 2023
1 parent 4ce59a2 commit 73df65e
Show file tree
Hide file tree
Showing 18 changed files with 643 additions and 151 deletions.
43 changes: 24 additions & 19 deletions cmd/ksef/commands/download-pdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ package commands
import (
"flag"
"fmt"
"ksef/internal/registry"
"ksef/internal/sei/api/client"
"ksef/internal/sei/api/pdf"
"ksef/internal/sei/api/status"
"ksef/internal/sei/api/upload/interactive"
"path/filepath"
"strings"
)

type downloadPDFCommand struct {
Command
}
type downloadPDFArgsType struct {
path string
output string
invoiceNo string
internalArgs registry.DownloadPDFArgs
issuerToken string
path string
}

var DownloadPDFCommand *downloadPDFCommand
Expand All @@ -32,40 +33,44 @@ func init() {
},
}

DownloadPDFCommand.FlagSet.StringVar(&downloadPDFArgs.path, "p", "", "ścieżka do pliku statusu")
DownloadPDFCommand.FlagSet.StringVar(&downloadPDFArgs.output, "o", "", "ścieżka do zapisu PDF (domyślnie katalog pliku statusu + {nrRef}.pdf)")
DownloadPDFCommand.FlagSet.StringVar(&downloadPDFArgs.invoiceNo, "i", "", "numer faktury do pobrania")
DownloadPDFCommand.FlagSet.StringVar(&downloadPDFArgs.path, "p", "", "ścieżka do pliku rejestru")
DownloadPDFCommand.FlagSet.StringVar(&downloadPDFArgs.internalArgs.Output, "o", "", "ścieżka do zapisu PDF (domyślnie katalog pliku statusu + {nrRef}.pdf)")
DownloadPDFCommand.FlagSet.StringVar(&downloadPDFArgs.internalArgs.Invoice, "i", "", "numer faktury do pobrania. Wartość * oznacza pobranie wszystkich faktur z rejestru")
DownloadPDFCommand.FlagSet.StringVar(&downloadPDFArgs.issuerToken, "token", "", "Token sesji interaktywnej lub nazwa zmiennej środowiskowej która go zawiera")
// DownloadPDFCommand.FlagSet.StringVar(&downloadPDFArgs.internalArgs.Token, "token", "", "token sesji")
DownloadPDFCommand.FlagSet.BoolVar(&downloadPDFArgs.internalArgs.SaveXml, "xml", false, "zapisz źródłowy plik XML")

registerCommand(&DownloadPDFCommand.Command)
}

func downloadPDFRun(c *Command) error {
if downloadPDFArgs.path == "" || downloadPDFArgs.invoiceNo == "" {
if downloadPDFArgs.path == "" || downloadPDFArgs.internalArgs.Invoice == "" {
fmt.Printf("downloadPDFArgs: %+v\n", downloadPDFArgs)
DownloadPDFCommand.FlagSet.Usage()
return nil
}

statusInfo, err := status.StatusFromFile(downloadPDFArgs.path)
registry, err := registry.LoadRegistry(downloadPDFArgs.path)
if err != nil {
return fmt.Errorf("unable to load status from file: %v", err)
return fmt.Errorf("unable to load registry from file: %v", err)
}

if statusInfo.Environment == "" || statusInfo.SessionID == "" {
return fmt.Errorf("file deserialized correctly, but either environment or referenceNo are empty: %+v", statusInfo)
if registry.Environment == "" {
return fmt.Errorf("file deserialized correctly, but environment is empty")
}

gateway, err := client.APIClient_Init(statusInfo.Environment)
gateway, err := client.APIClient_Init(registry.Environment)
if err != nil {
return fmt.Errorf("cannot initialize gateway: %v", err)
}

if downloadPDFArgs.output == "" {
downloadPDFArgs.output = filepath.Dir(downloadPDFArgs.path)
if downloadPDFArgs.internalArgs.Output == "" {
downloadPDFArgs.internalArgs.Output = filepath.Dir(downloadPDFArgs.path)
}

if err = pdf.DownloadPDF(gateway, statusInfo, downloadPDFArgs.invoiceNo, downloadPDFArgs.output); err != nil {
return fmt.Errorf("unable to download PDF: %v", err)
if strings.HasSuffix(strings.ToLower(downloadPDFArgs.internalArgs.Invoice), ".xml") {
return registry.DownloadPDF(gateway, &downloadPDFArgs.internalArgs)
}

return nil
return interactive.DownloadPDFFromAPI(gateway, &downloadPDFArgs.internalArgs, registry)
}
97 changes: 97 additions & 0 deletions cmd/ksef/commands/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package commands

import (
"flag"
"fmt"
registryPkg "ksef/internal/registry"
"ksef/internal/sei/api/client"
"ksef/internal/sei/api/sync"
"path"
"time"
)

type syncInvoicesCommand struct {
Command
}

type syncInvoicesArgsType struct {
params sync.SyncInvoicesConfig
startDate string
testGateway bool
refresh string
}

var SyncInvoicesCommand *syncInvoicesCommand
var syncInvoicesArgs = &syncInvoicesArgsType{}

func init() {
SyncInvoicesCommand = &syncInvoicesCommand{
Command: Command{
Name: "download",
FlagSet: flag.NewFlagSet("download", flag.ExitOnError),
Description: "Synchronizuje listę faktur z KSeF do katalogu lokalnego",
Run: syncInvoicesRun,
Args: syncInvoicesArgs,
},
}

SyncInvoicesCommand.FlagSet.BoolVar(&syncInvoicesArgs.testGateway, "t", false, "użyj bramki testowej")
SyncInvoicesCommand.FlagSet.StringVar(&syncInvoicesArgs.params.DestPath, "d", "", "Katalog docelowy")
SyncInvoicesCommand.FlagSet.BoolVar(&syncInvoicesArgs.params.Income, "income", false, "Synchronizuj faktury przychodowe (Podmiot1)")
SyncInvoicesCommand.FlagSet.BoolVar(&syncInvoicesArgs.params.Cost, "cost", false, "Synchronizuj faktury kosztowe (Podmiot2)")
SyncInvoicesCommand.FlagSet.BoolVar(&syncInvoicesArgs.params.Subject3, "subject3", false, "Synchronizuj faktury podmiotu innego (Podmiot3)")
SyncInvoicesCommand.FlagSet.BoolVar(&syncInvoicesArgs.params.SubjectAuthorized, "subjectAuthorized", false, "Synchronizuj faktury podmiotu upoważnionego (???)")
SyncInvoicesCommand.FlagSet.StringVar(&syncInvoicesArgs.params.SubjectTIN, "nip", "", "Numer NIP podmiotu")
SyncInvoicesCommand.FlagSet.StringVar(&syncInvoicesArgs.startDate, "start-date", "", "Data początkowa")
SyncInvoicesCommand.FlagSet.StringVar(&syncInvoicesArgs.params.IssuerToken, "token", "", "Token sesji interaktywnej lub nazwa zmiennej środowiskowej która go zawiera")
// SyncInvoicesCommand.FlagSet.StringVar(&syncInvoicesArgs.params.Token, "token", "", "Token sesji")
SyncInvoicesCommand.FlagSet.StringVar(&syncInvoicesArgs.refresh, "refresh", "", "odświeża istniejący rejestr faktur według istniejącego pliku")

registerCommand(&SyncInvoicesCommand.Command)
}

func syncInvoicesRun(c *Command) error {
var err error

// is it a refresh operation?
if syncInvoicesArgs.refresh != "" {
registry, err := registryPkg.LoadRegistry(syncInvoicesArgs.refresh)
if err != nil {
return fmt.Errorf("nie udało się załadować pliku rejestru: %v", err)
}
syncInvoicesArgs.params.DestPath = path.Dir(syncInvoicesArgs.refresh)

apiClient, err := client.APIClient_Init(registry.Environment)
if err != nil {
return fmt.Errorf("nieznane środowisko: %v", registry.Environment)
}

return sync.SyncInvoices(apiClient, &syncInvoicesArgs.params, registry)
}

// is it a new request?
if syncInvoicesArgs.params.DestPath == "" || syncInvoicesArgs.startDate == "" ||
(!syncInvoicesArgs.params.Income &&
!syncInvoicesArgs.params.Cost &&
!syncInvoicesArgs.params.Subject3 &&
!syncInvoicesArgs.params.SubjectAuthorized) {
c.FlagSet.Usage()
return nil
}

if syncInvoicesArgs.params.StartDate, err = time.ParseInLocation("2006-01-02", syncInvoicesArgs.startDate, time.Now().Location()); err != nil {
return fmt.Errorf("invalid date supplied: %s", syncInvoicesArgs.startDate)
}

environment := client.ProductionEnvironment
if syncInvoicesArgs.testGateway {
environment = client.TestEnvironment
}

gateway, err := client.APIClient_Init(environment)
if err != nil {
return fmt.Errorf("nieznane środowisko: %v", environment)
}

return sync.SyncInvoices(gateway, &syncInvoicesArgs.params, nil)
}
14 changes: 7 additions & 7 deletions cmd/ksef/commands/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package commands
import (
"flag"
"fmt"
registryPkg "ksef/internal/registry"
"ksef/internal/sei/api/client"
"ksef/internal/sei/api/status"
"ksef/internal/sei/api/upo"
"path/filepath"
)
Expand Down Expand Up @@ -45,16 +45,16 @@ func statusRun(c *Command) error {
return nil
}

statusInfo, err := status.StatusFromFile(statusArgs.path)
registry, err := registryPkg.LoadRegistry(statusArgs.path)
if err != nil {
return fmt.Errorf("unable to load status from file: %v", err)
}

if statusInfo.Environment == "" || statusInfo.SessionID == "" {
return fmt.Errorf("file deserialized correctly, but either environment or referenceNo are empty: %+v", statusInfo)
if registry.Environment == "" || registry.SessionID == "" {
return fmt.Errorf("file deserialized correctly, but either environment or referenceNo are empty: %+v", registry)
}

gateway, err := client.APIClient_Init(statusInfo.Environment)
gateway, err := client.APIClient_Init(registry.Environment)
if err != nil {
return fmt.Errorf("cannot initialize gateway: %v", err)
}
Expand All @@ -65,10 +65,10 @@ func statusRun(c *Command) error {
}

if statusArgs.output == "" {
statusArgs.output = filepath.Join(filepath.Dir(statusArgs.path), fmt.Sprintf("%s.%s", statusInfo.SessionID, outputFormat))
statusArgs.output = filepath.Join(filepath.Dir(statusArgs.path), fmt.Sprintf("%s.%s", registry.SessionID, outputFormat))
}

if err = upo.DownloadUPO(gateway, statusInfo, outputFormat, statusArgs.output); err != nil {
if err = upo.DownloadUPO(gateway, registry, outputFormat, statusArgs.output); err != nil {
return fmt.Errorf("unable to download UPO: %v", err)
}

Expand Down
12 changes: 2 additions & 10 deletions cmd/ksef/commands/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"ksef/internal/sei/api/client"
"ksef/internal/sei/api/status"
"ksef/internal/sei/api/upload/batch"
"ksef/internal/sei/api/upload/interactive"
)
Expand All @@ -18,7 +17,6 @@ type uploadArgsType struct {
path string
interactive bool
issuerToken string
statusJSON bool
}

var UploadCommand *uploadCommand
Expand All @@ -39,7 +37,6 @@ func init() {
UploadCommand.FlagSet.BoolVar(&uploadArgs.interactive, "i", false, "użyj sesji interaktywnej")
UploadCommand.FlagSet.StringVar(&uploadArgs.issuerToken, "token", "", "Token sesji interaktywnej lub nazwa zmiennej środowiskowej która go zawiera")
UploadCommand.FlagSet.StringVar(&uploadArgs.path, "p", "", "ścieżka do katalogu z wygenerowanymi fakturami")
UploadCommand.FlagSet.BoolVar(&uploadArgs.statusJSON, "sj", false, "użyj formatu JSON do zapisu pliku statusu (domyślnie YAML)")

registerCommand(&UploadCommand.Command)
}
Expand All @@ -60,20 +57,15 @@ func uploadRun(c *Command) error {
return fmt.Errorf("nieznane środowisko: %v", environment)
}

var statusFileFormat = status.StatusFileFormatYAML
if uploadArgs.statusJSON {
statusFileFormat = status.StatusFileFormatJSON
}

if uploadArgs.interactive {
interactiveSession := interactive.InteractiveSessionInit(gateway)
if uploadArgs.issuerToken != "" {
interactiveSession.SetIssuerToken(uploadArgs.issuerToken)
}
return interactiveSession.UploadInvoices(uploadArgs.path, statusFileFormat)
return interactiveSession.UploadInvoices(uploadArgs.path)
}

batchSession := batch.BatchSessionInit(gateway)
return batchSession.UploadInvoices(uploadArgs.path, statusFileFormat)
return batchSession.UploadInvoices(uploadArgs.path)

}
1 change: 1 addition & 0 deletions docs/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default defineConfig({
{text: 'Sesja wsadowa (batch)', link: '/content/komendy/upload/batch'},
{text: 'Sesja interaktywna', link: '/content/komendy/upload/interaktywna'},
]},
{ text: 'Pobieranie faktur', link: '/content/komendy/download'},
{ text: 'Pobieranie UPO', link: '/content/komendy/upo'},
{ text: 'Wizualizacja PDF', link: '/content/komendy/wizualizacja-pdf'},
]
Expand Down
62 changes: 62 additions & 0 deletions docs/content/komendy/download.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Pobieranie faktur

```text
./ksef download
Usage of download:
-cost
Synchronizuj faktury kosztowe (Podmiot2)
-d string
Katalog docelowy
-income
Synchronizuj faktury przychodowe (Podmiot1)
-nip string
Numer NIP podmiotu
-refresh string
odświeża istniejący rejestr faktur według istniejącego pliku
-start-date string
Data początkowa
-subject3
Synchronizuj faktury podmiotu innego (Podmiot3)
-subjectAuthorized
Synchronizuj faktury podmiotu upoważnionego (???)
-t użyj bramki testowej
```

Istnieje kilka przewidzianych trybów dla tej komendy.

## Pobieranie listy faktur

Najpierw wybierz rodzaj faktur który Cię interesuje:

| przełącznik | znaczenie |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `-income` | Faktury przychodowe, tj. wystawione przez Ciebie |
| `-cost` | Faktury kosztowe |
| `-subject3` | Faktury gdzie występujesz jako strona 3. Z tego co się orientuję jest to sytuacja taka kiedy jesteś zamawiającym ale nie płatnikiem |
| `-subjectAuthorized` | Nie mam zielonego pojęcia do czego to służy. Czyżby jakaś opcja dla księgowych żeby mogli pobierać faktury swoich klientów? |

parametr `-start-date` określa datę początkową filtrowania faktur. Jest to o tyle istotne, że ta wartość zostanie zapisana do pliku rejestru (więcej o tym w sekcji poniżej). Data końcowa to zawsze czas bieżący

Przykładowe wywołanie:

```shell
./ksef download -t -nip 1111111111 -cost -d kosztowe/2023-12 -start-date 2023-12-01
```

## Synchronizowanie już pobranej listy faktur

Ta opcja przewidziana jest dla sytuacji w której chcesz kilka razy w miesiącu synchronizować faktury. Czyli przykładowo jeśli w kroku poprzednim stworzyłeś katalog `kosztowe/2023-12` to możesz teraz go odświeżyć / zsynchronizować:

```shell
./ksef download -refresh kosztowe/2023-12/registry.yaml
```

Jak widzisz, ilość parametrów jest znacznie mniejsza ponieważ wszystkie potrzebne dane znajdują się w pliku `registry.yaml`. Jeśli program zauważy, że jakaś faktura już znajduje się w rejestrze to ją pominie

::: info
KSeF udostępnia całkiem sporo danych w nagłówkach faktur więc serializuję je do pliku rejestru. Są tam takie informacje jak data wystawienia, rodzaj faktury, dane firmy itd itd.
:::

::: warning
Ta opcja **nie** pobiera źródłowych faktur w formie XML i/lub PDF - od tego jest komenda `download-pdf`. Przyczyna jest prozaiczna - faktur może być sporo i wolałem rozdzielić te komendy na dwie osobne
:::
Loading

0 comments on commit 73df65e

Please sign in to comment.