Compare commits

..

10 Commits

Author SHA1 Message Date
ee52080eba Initial commit: KSEF PDF Generator from GitHub 2026-03-18 22:42:13 +01:00
40c787e660 Initial commit: KSEF PDF Generator from GitHub 2026-03-18 22:39:06 +01:00
b22c5a5cd2 Initial commit: KSEF PDF Generator from GitHub 2026-03-18 22:38:57 +01:00
a4ca013811 Initial commit: KSEF PDF Generator from GitHub 2026-03-18 22:37:34 +01:00
4e49c625bf Initial commit: KSEF PDF Generator from GitHub 2026-03-18 22:35:33 +01:00
Michał Chudy
f48680a65f chore(): 1.0.0 2026-01-30 20:06:43 +01:00
Michał Chudy
5fb5a624b8 UPO 4.3, fixes for invoices 2026-01-16 08:35:43 +01:00
Michał Chudy
1912b5b48d fix build config & fixes for invoice FA1/FA2/FA3 2025-12-03 13:42:56 +01:00
Michał Chudy
b9972746aa add source code 2025-11-21 10:22:39 +01:00
Michał Chudy
df0ef23857 init 2025-11-01 14:00:48 +01:00
240 changed files with 33748 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
node_modules/
dist/
*.log
*.lock
.DS_Store
.vite/
coverage
.vitest
.cache
temp
out
*.tsbuildinfo
.idea
.vscode
*.swp

9
.prettierignore Normal file
View File

@@ -0,0 +1,9 @@
# Ignore artifacts:
build
/dist
/coverage
.angular
/api
README.md
/src/app/core/services/api-deprecated.service.ts
/src/app/core/services/api.service.ts

10
.prettierrc.json Normal file
View File

@@ -0,0 +1,10 @@
{
"trailingComma": "es5",
"singleQuote": true,
"singleAttributePerLine": true,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"semi": true,
"printWidth": 110
}

179
DEPLOYMENT-PRODUCTION.md Normal file
View File

@@ -0,0 +1,179 @@
# Wdrażanie KSEF na produkcję (www.sic.pl/ksef/)
## Krok 1: Przygotuj artefakty na maszynie lokalnej
```bash
cd /home/ms/projekty/fv-ksef-nodejs/ksef-pdf-generator
# Zbuduj aplikację SPA
npm run build:app
# Powinnien powstać folder dist-app/ z zawartością:
ls -la dist-app/
# index.html
# assets/index-HASH.js
```
---
## Krok 2: Prześlij pliki na serwer docelowy (10.1.1.3)
### 2a. Skopiuj artefakty aplikacji
```bash
# Z lokalnej maszyny prześlij dist-app/* do /var/www/ksef/
scp -r dist-app/* ms@10.1.1.3:/home/ms/projekty/ksef/
# LUB jeśli bezpośrednio na serwerze, skopiuj z repo:
scp -r dist-app/* ms@10.1.1.3:/var/www/ksef/
```
### 2b. Na serwerze docelowym, ustaw uprawnienia
```bash
# Zaloguj się na serwer:
ssh ms@10.1.1.3
# Stwórz folder jeśli go nie ma
sudo mkdir -p /var/www/ksef
# Ustaw właściciela i uprawnienia
sudo chown -R www-data:www-data /var/www/ksef
sudo chmod -R 755 /var/www/ksef
```
---
## Krok 3: Zaktualizuj nginx config
### 3a. Prześlij nowy default.conf
```bash
# Z lokalnego repo
scp default.conf ms@10.1.1.3:/home/ms/
# Lub na serwerze, utwórz plik:
nano /etc/nginx/conf.d/default.conf
```
### 3b. Na serwerze docelowym, zastąp config
```bash
# Backup starego configu
sudo cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.backup
# Wstaw nowy config (z wdrażanego repo)
sudo cp /home/ms/default.conf /etc/nginx/conf.d/default.conf
# LUB ręcznie: skopiuj zawartość pliku z repo i wklej do /etc/nginx/conf.d/default.conf
```
---
## Krok 4: Zweryfikuj nginx config i restartuj
```bash
# Na serwerze: sprawdź składnię
sudo nginx -t
# Jeśli "syntax is ok, test successful":
sudo systemctl restart nginx
# Sprawdź status
sudo systemctl status nginx
```
---
## Krok 5: Testowanie
Otwórz w przeglądarce:
- **http://www.sic.pl/ksef/** ← aplikacja PDF Generator
- **http://www.sic.pl/** ← pozostała zawartość www.sic.pl (bez zmian)
---
## Struktura na serwerze docelowym
```
/var/www/
├── sic/ ← istniejąca zawartość www.sic.pl
├── esbr/ ← istniejąca zawartość www.esbr.pl
├── sobczak/ ← istniejąca zawartość www.sobczak.org
└── ksef/ ← NOWA: aplikacja KSEF
├── index.html (punkt wejścia, no-cache)
└── assets/
└── index-HASH.js (cached)
```
---
## Logi i debugowanie
Na serwerze:
```bash
# Logi dostępu
sudo tail -f /var/log/nginx/access.log | grep ksef
# Logi błędów
sudo tail -f /var/log/nginx/error.log
# Test zaraz po starcie
curl -I http://localhost/ksef/
```
---
## Aktualizacja aplikacji (dla przyszłych wersji)
```bash
# Lokalnie: zbuduj nową wersję
npm run build:app
# Prześlij do serwera
scp -r dist-app/* ms@10.1.1.3:/var/www/ksef/
# nginx automatycznie serwuje nową wersję
# (index.html nie ma cache, assety mają nowe hashe)
```
---
## Jeśli coś nie działa
### Problem: 404 na `/ksef/`
**Powody:**
1. Folder `/var/www/ksef/` nie istnieje lub nie ma contentu
2. `index.html` nie jest w `/var/www/ksef/`
3. Błędy uprawnienia (nginx nie może czytać)
**Rozwiązanie:**
```bash
# Sprawdź zawartość
ls -la /var/www/ksef/
# Sprawdź uprawnienia
stat /var/www/ksef/
# Ustaw uprawnienia na nowo
sudo chown -R www-data:www-data /var/www/ksef/
sudo chmod -R 755 /var/www/ksef/
```
### Problem: assety (JS/CSS) nie ładują się
**Powód:** Zła ścieżka w `alias` w nginx config
**Sprawdzenie:**
```bash
# Sprawdź co jest w dist-app/assets/
ls -la /var/www/ksef/assets/
# Jeśli puste, znaczy że dist-app/ nie został skopiowany poprawnie
```
---
**Gotowe!** Po wykonaniu powyższych kroków, aplikacja powinna być dostępna na 🎉
**http://www.sic.pl/ksef/**

159
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,159 @@
# Instrukcja Deployment aplikacji KSEF PDF Generator na nginx
## 1. Przygotowanie artefaktów
Aplikacja została już zbudowana do folderu `dist-app/`:
```bash
cd /home/ms/projekty/fv-ksef-nodejs/ksef-pdf-generator
# Jeśli potrzebujesz ponownie zbudować:
npm run build:app
```
Zawartość `dist-app/`:
- `index.html` - główny plik HTML (punkt wejścia SPA)
- `assets/` - folder ze skonkatenowanymi JS, CSS i zasobami
---
## 2. Deployment na serwer docelowy
### a) Zkopuj artefakty do serwera
```bash
# Lokalnie, na maszynie z kodem:
scp -r dist-app/ user@www.sic.pl:/var/www/ksef/
# LUB jeśli deployment robisz z serwera:
scp -r /home/ms/projekty/fv-ksef-nodejs/ksef-pdf-generator/dist-app/ /var/www/ksef/
```
### b) Upewnij się, że nginx ma uprawnienia
```bash
sudo chown -R www-data:www-data /var/www/ksef/
sudo chmod -R 755 /var/www/ksef/
```
---
## 3. Konfiguracja nginx
### Opcja A: Dodaj blok do istniejącego `/etc/nginx/nginx.conf`
1. Otwórz `/etc/nginx/nginx.conf`:
```bash
sudo nano /etc/nginx/nginx.conf
```
2. W sekcji `http {}` dodaj:
```nginx
include /etc/nginx/conf.d/ksef.conf;
```
### Opcja B: Utwórz osobny plik konfigu
1. Zkopuj `nginx-ksef.conf` z repozytorium:
```bash
sudo cp nginx-ksef.conf /etc/nginx/conf.d/ksef.conf
```
2. **WAŻNE:** Edytuj `/etc/nginx/conf.d/ksef.conf` i zmień scieżki:
```nginx
# ZMIEŃ TĘ LINIĘ:
root /home/ms/projekty/fv-ksef-nodejs/ksef-pdf-generator/dist-app;
alias /home/ms/projekty/fv-ksef-nodejs/ksef-pdf-generator/dist-app/;
# NA TWOJE ŚCIEŻKI W PRODUKCJI, np:
root /var/www/ksef;
alias /var/www/ksef/;
```
---
## 4. Testowanie konfigu i restartowanie nginx
```bash
# Sprawdź składnię konfigu
sudo nginx -t
# Jeśli OK, restartuj nginx
sudo systemctl restart nginx
# Lub reload (bez drop current connections):
sudo systemctl reload nginx
```
---
## 5. Weryfikacja
Otwórz w przeglądarce: **http://www.sic.pl/ksef/**
Powinieneś zobaczyć interfejs aplikacji z polami do wgrania XML-ów.
---
## 6. Logi i debugowanie
Logi nginx:
```bash
sudo tail -f /var/log/nginx/ksef-access.log
sudo tail -f /var/log/nginx/ksef-error.log
```
Jeśli widzisz błędy 404 na asetach, sprawdź:
- Ścieżkę w `alias` / `root`
- Uprawnienia folderu `/var/www/ksef/`
- Że `dist-app/index.html` istnieje
---
## 7. HTTPS (SSL/TLS)
Aby używać HTTPS:
1. Uzyskaj certyfikat (np. Let's Encrypt):
```bash
sudo certbot certonly --webroot -w /var/www/ksef -d www.sic.pl
```
2. Odkomentuj blok `server { listen 443 ssl ... }` w `nginx-ksef.conf` i wstaw ścieżki certyfikatu.
3. Konfiguruj HTTP → HTTPS redirect.
---
## 8. Aktualizacja aplikacji (CI/CD)
Podczas deployment nowej wersji:
1. Zbuduj aplikację lokalnie:
```bash
npm run build:app
```
2. Zkopuj do serwera (zastępując starą wersję):
```bash
scp -r dist-app/* user@www.sic.pl:/var/www/ksef/
```
3. Cache przeglądarki: ponieważ `index.html` nie ma cache (`max-age=0`),
przeglądarki zawsze pobiorą najnowszą wersję przy następnej wizycie.
---
## Struktura produkcji
```
/var/www/ksef/
├── index.html (punkt wejścia, no cache)
├── assets/
│ ├── index-HASH.js (bundled app, cached)
│ └── index-HASH.css (bundled styles, cached)
```
--
**Gotowe!** Aplikacja powinna być dostępna na `http://www.sic.pl/ksef/` 🚀

257
assets/invoice.xml Normal file
View File

@@ -0,0 +1,257 @@
<?xml version="1.0" encoding="utf-8"?>
<Faktura
xmlns="http://crd.gov.pl/wzor/2025/06/25/13775/">
<Naglowek>
<KodFormularza kodSystemowy="FA (3)" wersjaSchemy="1-0E">FA</KodFormularza>
<WariantFormularza>3</WariantFormularza>
<DataWytworzeniaFa>2026-03-22T03:27:54.0792168Z</DataWytworzeniaFa>
<SystemInfo>Generator danych</SystemInfo>
</Naglowek>
<Podmiot1>
<DaneIdentyfikacyjne>
<NIP>5265877635</NIP>
<Nazwa>Bartkowiak, Dróżdż and Górecki</Nazwa>
</DaneIdentyfikacyjne>
<Adres>
<KodKraju>PL</KodKraju>
<AdresL1>ul. Kępa 332</AdresL1>
<AdresL2>20-892 Tyszowce</AdresL2>
</Adres>
<DaneKontaktowe>
<Email>Prokop77@example.com</Email>
<Telefon>17-756-90-14</Telefon>
</DaneKontaktowe>
</Podmiot1>
<Podmiot2>
<DaneIdentyfikacyjne>
<NIP>6808208874</NIP>
<Nazwa>Strzelczyk LLC</Nazwa>
</DaneIdentyfikacyjne>
<Adres>
<KodKraju>PL</KodKraju>
<AdresL1>ul. Wójcik 7255</AdresL1>
<AdresL2>28-068 Pobiedziska</AdresL2>
</Adres>
<DaneKontaktowe>
<Email>Lidia.Olszewski77@yahoo.com</Email>
<Telefon>76-113-42-55</Telefon>
</DaneKontaktowe>
<NrKlienta>KL-7615</NrKlienta>
<JST>2</JST>
<GV>2</GV>
</Podmiot2>
<Fa>
<KodWaluty>PLN</KodWaluty>
<P_1>2025-10-31</P_1>
<P_1M>Czarnków</P_1M>
<P_2>FA/YUCFO-4342344706/03/2026</P_2>
<P_6>2025-10-03</P_6>
<P_13_1>27292.28</P_13_1>
<P_14_1>6277.22</P_14_1>
<P_15>33569.50</P_15>
<Adnotacje>
<P_16>2</P_16>
<P_17>2</P_17>
<P_18>2</P_18>
<P_18A>2</P_18A>
<Zwolnienie>
<P_19N>1</P_19N>
</Zwolnienie>
<NoweSrodkiTransportu>
<P_22N>1</P_22N>
</NoweSrodkiTransportu>
<P_23>2</P_23>
<PMarzy>
<P_PMarzyN>1</P_PMarzyN>
</PMarzy>
</Adnotacje>
<RodzajFaktury>VAT</RodzajFaktury>
<FaWiersz>
<NrWierszaFa>1</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>1</P_8B>
<P_9A>20.41</P_9A>
<P_11>20.41</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>2</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>7</P_8B>
<P_9A>130.83</P_9A>
<P_11>915.81</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>3</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>1</P_8B>
<P_9A>637.15</P_9A>
<P_11>637.15</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>4</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>7</P_8B>
<P_9A>944.48</P_9A>
<P_11>6611.36</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>5</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>2</P_8B>
<P_9A>660.60</P_9A>
<P_11>1321.20</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>6</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>4</P_8B>
<P_9A>558.93</P_9A>
<P_11>2235.72</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>7</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>1</P_8B>
<P_9A>670.77</P_9A>
<P_11>670.77</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>8</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>1</P_8B>
<P_9A>698.02</P_9A>
<P_11>698.02</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>9</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>6</P_8B>
<P_9A>298.84</P_9A>
<P_11>1793.04</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>10</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>6</P_8B>
<P_9A>336.65</P_9A>
<P_11>2019.90</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>11</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>8</P_8B>
<P_9A>408.55</P_9A>
<P_11>3268.40</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>12</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>6</P_8B>
<P_9A>13.51</P_9A>
<P_11>81.06</P_11>
<P_12>23</P_12>
</FaWiersz>
<FaWiersz>
<NrWierszaFa>13</NrWierszaFa>
<P_7>Handcrafted Wooden Gloves Handcrafted Rubber Bike Incredible Granite Shirt Ergonomic Soft Soap Licensed
Metal Soap Sleek Wooden Chips Licensed Soft Soap Unbranded Steel Towels Ergonomic Concrete Keyboard
Awesome Metal Computer Refined Metal Chair Generic Wooden Shoes Fantastic Plastic Sausages Generic
Plastic Computer Gorgeous Plastic Gloves Fantastic Frozen Ball Fantastic Granite Sausages Rustic Soft
Shoes Gorgeous Plastic Cheese Small Granite Gloves Practical Concrete Fish Licensed Concrete Ba
</P_7>
<P_8A>szt.</P_8A>
<P_8B>8</P_8B>
<P_9A>877.43</P_9A>
<P_11>7019.44</P_11>
<P_12>23</P_12>
</FaWiersz>
<Platnosc>
<Zaplacono>1</Zaplacono>
<DataZaplaty>2025-10-31</DataZaplaty>
<FormaPlatnosci>7</FormaPlatnosci>
</Platnosc>
</Fa>
</Faktura>

24
assets/upo.xml Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Potwierdzenie xmlns="http://upo.schematy.mf.gov.pl/KSeF/v4-3">
<NazwaPodmiotuPrzyjmujacego>Ministerstwo Finansów</NazwaPodmiotuPrzyjmujacego>
<NumerReferencyjnySesji>36950822-93-9D5A28BFDA-47C899773E-5C</NumerReferencyjnySesji>
<Uwierzytelnienie>
<IdKontekstu>
<IdZlozonyVatUE>5265877635-ATU12345678</IdZlozonyVatUE>
</IdKontekstu>
<SkrotDokumentuUwierzytelniajacego>kyqH+QUgP8ATWd/95IY632mP4uqibwG66Oqclq9+qno=
</SkrotDokumentuUwierzytelniajacego>
</Uwierzytelnienie>
<NazwaStrukturyLogicznej>1-0E</NazwaStrukturyLogicznej>
<KodFormularza>FA (3)</KodFormularza>
<Dokument>
<NipSprzedawcy>5265877635</NipSprzedawcy>
<NumerKSeFDokumentu>5265877635-20250916-0200A0D6723E-C2</NumerKSeFDokumentu>
<NumerFaktury>FA/XVQUD-9997622510/04/2027</NumerFaktury>
<DataWystawieniaFaktury>2025-09-16</DataWystawieniaFaktury>
<DataPrzeslaniaDokumentu>2025-09-16T11:05:40.841+02:00</DataPrzeslaniaDokumentu>
<DataNadaniaNumeruKSeF>2025-09-16T11:05:41.045+02:00</DataNadaniaNumeruKSeF>
<SkrotDokumentu>GZMGNVzs3krF6URKgvaw77OOeG3nJ+WGziT5xguliQ8=</SkrotDokumentu>
<TrybWysylki>Offline</TrybWysylki>
</Dokument>
</Potwierdzenie>

62
default.conf Normal file
View File

@@ -0,0 +1,62 @@
# Serwer www.sic.pl z projektem KSEF
server {
listen 80;
server_name www.sic.pl;
root /var/www/sic;
index index.html index.htm;
# Kompresja
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
# Lokacja /ksef/ - aplikacja PDF Generator
location /ksef/ {
alias /var/www/ksef/;
# Obsługa SPA: spróbuj plik, jeśli nie istnieje spróbuj index.html
try_files $uri $uri/ /ksef/index.html;
# Buforowanie: index.html bez cache
location = /ksef/index.html {
add_header Cache-Control "public, max-age=0, must-revalidate";
add_header X-Content-Type-Options "nosniff";
}
# Buforowanie: statyczne assety z cache (1 rok)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
# Zabezpieczenia
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
}
# Serwer www.esbr.pl
server {
listen 80;
server_name www.esbr.pl;
root /var/www/esbr;
index index.html index.htm;
}
# Serwer www.sobczak.org
server {
listen 80;
server_name www.sobczak.org;
root /var/www/sobczak;
index index.html index.php;
auth_basic "Dostep zastrzezony"; # Tekst, który pojawi się w oknie logowania
auth_basic_user_file /etc/nginx/.htpasswd; # Ścieżka do pliku z hasłami
location ~ \.php$ {
include snippets/fastcgi-php.conf;
}
}

77
deploy.sh Executable file
View File

@@ -0,0 +1,77 @@
#!/bin/bash
# Skrypt deploymentu KSEF na serwer produkcji
# Użycie: ./deploy.sh <user@host> <remote_dir>
set -e # Exit on error
# Kolory dla output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Parametry
REMOTE_HOST="${1:-ms@10.1.1.3}"
REMOTE_DIR="${2:-/var/www/ksef}"
echo -e "${YELLOW}🚀 KSEF Deployment Script${NC}"
echo "Remote host: $REMOTE_HOST"
echo "Remote dir: $REMOTE_DIR"
echo ""
# Krok 1: Build aplikacji
echo -e "${YELLOW}1⃣ Building application...${NC}"
if [ ! -d "src/app-public" ]; then
echo -e "${RED}❌ Error: src/app-public not found. Run from project root!${NC}"
exit 1
fi
npm run build:app
if [ ! -d "dist-app" ]; then
echo -e "${RED}❌ Error: dist-app not created!${NC}"
exit 1
fi
echo -e "${GREEN}✓ Build complete${NC}"
echo ""
# Krok 2: Sprawdzenie SSH dostępu
echo -e "${YELLOW}2⃣ Checking SSH access...${NC}"
if ! ssh -q "$REMOTE_HOST" "test -d /var/www"; then
echo -e "${RED}❌ SSH connection failed or /var/www not found${NC}"
exit 1
fi
echo -e "${GREEN}✓ SSH OK${NC}"
echo ""
# Krok 3: Stworzenie folderu
echo -e "${YELLOW}3⃣ Creating remote directory...${NC}"
ssh "$REMOTE_HOST" "mkdir -p $REMOTE_DIR"
echo -e "${GREEN}✓ Directory ready${NC}"
echo ""
# Krok 4: Upload artefaktów
echo -e "${YELLOW}4⃣ Uploading files...${NC}"
scp -r dist-app/* "$REMOTE_HOST:$REMOTE_DIR/"
echo -e "${GREEN}✓ Files uploaded${NC}"
echo ""
# Krok 5: Ustaw uprawnienia
echo -e "${YELLOW}5⃣ Setting permissions...${NC}"
ssh "$REMOTE_HOST" "sudo chown -R www-data:www-data $REMOTE_DIR && sudo chmod -R 755 $REMOTE_DIR"
echo -e "${GREEN}✓ Permissions set${NC}"
echo ""
# Krok 6: Reload nginx
echo -e "${YELLOW}6⃣ Reloading nginx...${NC}"
ssh "$REMOTE_HOST" "sudo nginx -t && sudo systemctl reload nginx"
echo -e "${GREEN}✓ Nginx reloaded${NC}"
echo ""
echo -e "${GREEN}✅ Deployment complete!${NC}"
echo -e "Application running at: ${YELLOW}http://www.sic.pl/ksef/${NC}"
echo ""
echo "Logs:"
echo " Errors: ssh $REMOTE_HOST 'sudo tail -f /var/log/nginx/error.log'"
echo " Access: ssh $REMOTE_HOST 'sudo tail -f /var/log/nginx/access.log | grep ksef'"

File diff suppressed because one or more lines are too long

26
dist-app/index.html Normal file
View File

@@ -0,0 +1,26 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>XML Parser</title>
<script type="module" crossorigin src="/ksef/assets/index-BOCrZ8tn.js"></script>
</head>
<body>
<h1>📄 XML Parser</h1>
<h2>Wygeneruj fakture:</h2>
<input
accept=".xml"
id="xmlInput"
type="file"
/>
<h2>Wygeneruj UPO:</h2>
<input
accept=".xml"
id="xmlInputUPO"
type="file"
/>
</body>
</html>

60
eslint.config.mts Normal file
View File

@@ -0,0 +1,60 @@
// @ts-check
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import stylistics from '@stylistic/eslint-plugin';
module.exports = tseslint.config({
ignores: [
'package.json',
'node_modules',
'package-lock.json',
'dist',
'angular.json',
'tsconfig.json',
'tsconfig.app.json',
'tsconfig.spec.json',
'src/types**/*.ts',
],
files: ['**/*.ts'],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
eslintPluginPrettierRecommended,
],
plugins: {
'@stylistic': stylistics,
},
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
'@typescript-eslint/explicit-member-accessibility': [
'warn',
{
accessibility: 'explicit',
overrides: {
constructors: 'no-public',
accessors: 'off',
},
},
],
'@typescript-eslint/member-ordering': ['warn'],
'@typescript-eslint/explicit-function-return-type': ['error'],
curly: ['error', 'all'],
'@stylistic/padding-line-between-statements': [
'error',
{ blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' },
{
blankLine: 'any',
prev: ['const', 'let', 'var'],
next: ['const', 'let', 'var'],
},
],
},
});

111
git-init.sh Executable file
View File

@@ -0,0 +1,111 @@
#!/bin/bash
# Skrypt commitowania projektu na Gitea
# Użycie: ./git-init.sh [gitea_user] [gitea_password] [gitea_repo]
set -e
# set -x # Odkomentuj dla debugowania
# Parametry
GITEA_USER="${1:-ms}"
GITEA_PASS="${2:-}"
GITEA_REPO="${3:-ksef-pdf-generator}"
GITEA_HOST="10.1.1.1:30008"
GITEA_URL="https://${GITEA_USER}:${GITEA_PASS}@${GITEA_HOST}/${GITEA_USER}/${GITEA_REPO}.git"
GITEA_URL_DISPLAY="https://${GITEA_HOST}/${GITEA_USER}/${GITEA_REPO}.git"
# Kolory
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
# Sprawdzenie hasła
if [ -z "$GITEA_PASS" ]; then
echo -e "${RED}❌ Error: Gitea password required${NC}"
echo "Użycie: ./git-init.sh [user] [password] [repo]"
echo ""
echo "Przykład: ./git-init.sh ms YOUR_PASSWORD ksef-pdf-generator"
exit 1
fi
echo -e "${YELLOW}🚀 Git initialization for Gitea${NC}"
echo "Host: https://${GITEA_HOST}"
echo "User: ${GITEA_USER}"
echo "Repo: ${GITEA_REPO}"
echo ""
# Krok 1: Konfiguracja git (ignorowanie cert dla self-signed)
echo -e "${YELLOW}1⃣ Configuring git...${NC}"
git config --global http.sslVerify false
git config user.name "Mirek SIC"
git config user.email "mirek@sic.pl"
git config --global credential.helper store # Cache credentials
echo -e "${GREEN}✓ Git configured${NC}"
echo ""
# Krok 2: Inicjalizacja repozytorium
echo -e "${YELLOW}2⃣ Initializing repository...${NC}"
if [ ! -d ".git" ]; then
git init
git checkout -b main
else
echo "Repository already initialized"
fi
echo -e "${GREEN}✓ Repository ready${NC}"
echo ""
# Krok 3: Dodanie remote
echo -e "${YELLOW}3⃣ Adding remote origin...${NC}"
if git remote | grep -q origin; then
echo "Removing existing origin..."
git remote remove origin
fi
# Używaj URL z hasłem dla push, ale wyświetlaj bez hasła
git remote add origin "$GITEA_URL"
echo -e "${GREEN}✓ Remote added: $GITEA_URL_DISPLAY${NC}"
echo ""
# Krok 4: Stage i commit
echo -e "${YELLOW}4⃣ Staging and committing...${NC}"
git add .
git commit -m "Initial commit: KSEF PDF Generator from GitHub" --allow-empty || true
echo -e "${GREEN}✓ Changes committed${NC}"
echo ""
# Krok 5: Push do Gitea (z timeout)
echo -e "${YELLOW}5⃣ Pushing to Gitea...${NC}"
echo "Uwaga: pierwsze push może potrwać kilka sekund..."
if timeout 60 git push -u origin main 2>&1; then
echo -e "${GREEN}✓ Push successful${NC}"
echo ""
echo -e "${GREEN}✅ Git setup complete!${NC}"
echo ""
echo "Repository: $GITEA_URL_DISPLAY"
echo ""
echo "Dalsze komendy:"
echo " git status"
echo " git log --oneline"
echo " git push origin main"
echo " git pull origin main"
else
EXIT_CODE=$?
echo ""
if [ $EXIT_CODE -eq 124 ]; then
echo -e "${RED}❌ Error: Push timeout (60 sekund)${NC}"
else
echo -e "${RED}❌ Error: Push failed (exit code: $EXIT_CODE)${NC}"
fi
echo ""
echo "Debug info:"
echo " URL: $GITEA_URL_DISPLAY"
echo " User: $GITEA_USER"
echo ""
echo "Opcje debugowania:"
echo " GIT_TRACE=1 ./git-init.sh $GITEA_USER $GITEA_PASS $GITEA_REPO"
echo " git remote -v"
echo " git log --oneline"
exit 1
fi
echo " git push origin main"
echo " git pull origin main"

63
nginx-ksef.conf Normal file
View File

@@ -0,0 +1,63 @@
# Konfiguracja nginx dla aplikacji KSEF PDF Generator
# Umieść ten blok w konfigu nginx w sekcji http lub server
server {
listen 80;
server_name www.sic.pl;
# Ścieżka do zbudowanej aplikacji
root /home/ms/projekty/fv-ksef-nodejs/ksef-pdf-generator/dist-app;
# Kompresja
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
# Lokacja /ksef/
location /ksef/ {
# Alias do dist-app
alias /home/ms/projekty/fv-ksef-nodejs/ksef-pdf-generator/dist-app/;
# Obsługa SPA: spróbuj plik, jeśli nie istnieje spróbuj index.html
try_files $uri $uri/ /ksef/index.html;
# Buforowanie: index.html bez cache
location = /ksef/index.html {
add_header Cache-Control "public, max-age=0, must-revalidate";
add_header X-Content-Type-Options "nosniff";
}
# Buforowanie: statyczne assety z cache
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
# Zabezpieczenia
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Logi
access_log /var/log/nginx/ksef-access.log;
error_log /var/log/nginx/ksef-error.log;
}
# HTTPS (opcjonalne, wymaga certyfikatu)
# server {
# listen 443 ssl http2;
# server_name www.sic.pl;
#
# ssl_certificate /path/to/cert.pem;
# ssl_certificate_key /path/to/key.pem;
#
# # ... reszta konfigu jak wyżej
# }
#
# # Redirect HTTP -> HTTPS
# server {
# listen 80;
# server_name www.sic.pl;
# return 301 https://$server_name$request_uri;
# }

5655
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

57
package.json Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "@akmf/ksef-fe-invoice-converter",
"version": "1.0.0",
"scripts": {
"dev": "vite --mode public --config vite.config.ts",
"build": "vite build --mode production",
"build:app": "vite build --config vite.app.config.ts",
"type": "tsc -p src/app-public/tsconfig.json --noEmit",
"test": "vitest",
"test:ui": "vitest --ui",
"test:ci": "vitest run --coverage"
},
"main": "./ksef-fe-invoice-converter.umd.cjs",
"module": "./ksef-fe-invoice-converter.js",
"types": "./index.d.ts",
"type": "module",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./ksef-fe-invoice-converter.js",
"require": "./ksef-fe-invoice-converter.umd.cjs"
}
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.50.1"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
"@stylistic/eslint-plugin": "^5.2.3",
"@types/pdfmake": "^0.2.11",
"@typescript-eslint/eslint-plugin": "^8.40.0",
"@typescript-eslint/parser": "^8.40.0",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4",
"eslint": "^9.33.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-no-relative-import-paths": "^1.6.1",
"eslint-plugin-prettier": "^5.5.4",
"globals": "^16.3.0",
"jiti": "^2.5.1",
"jsdom": "^26.1.0",
"prettier": "^3.6.2",
"typescript": "^5.8.3",
"typescript-eslint": "^8.40.0",
"vite": "^7.0.6",
"vite-plugin-dts": "^4.5.4",
"vitest": "^3.2.4"
},
"dependencies": {
"pdfmake": "^0.2.20",
"xml-js": "^1.6.11"
}
}

127
readme.md Normal file
View File

@@ -0,0 +1,127 @@
# Biblioteka do generowania wizualizacji PDF faktur i UPO
Biblioteka do generowania wizualizacji PDF faktur oraz UPO na podstawie plików XML po stronie klienta.
---
## 1. Główne ustalenia
Biblioteka zawiera następujące funkcjonalności:
- Generowanie wizualizacji PDF faktur
- Generowanie wizualizacji PDF UPO
---
## 2. Jak uruchomić aplikację pokazową
1. Zainstaluj Node.js w wersji **22.14.0**
Możesz pobrać Node.js z oficjalnej strony: [https://nodejs.org](https://nodejs.org)
2. Sklonuj repozytorium i przejdź do folderu projektu:
```bash
git clone https://github.com/CIRFMF/ksef-pdf-generator#
cd ksef-pdf-generator
```
3. Zainstaluj zależności:
```bash
npm install
```
4. Uruchom aplikację:
```bash
npm run dev
```
Aplikacja uruchomi się domyślnie pod adresem: [http://localhost:5173/](http://localhost:5173/)
## 2.1 Budowanie bibliotki
1. Jak zbudować bibliotekę produkcyjnie:
```bash
npm run build
```
## 3. Jak wygenerować fakturę
1. Po uruchomieniu aplikacji przejdź do **Wygeneruj wizualizacje faktury PDF**.
2. Wybierz plik XML zgodny ze schemą **FA(1), FA(2) lub FA(3)**.
3. Przykładowy plik znajduje się w folderze:
```
examples/invoice.xml
```
4. Po wybraniu pliku, PDF zostanie wygenerowany.
---
## 4. Jak wygenerować UPO
1. Po uruchomieniu aplikacji przejdź do **Wygeneruj wizualizacje UPO PDF**.
2. Wybierz plik XML zgodny ze schemą **UPO v4_2**.
3. Przykładowy plik znajduje się w folderze:
```
examples/upo.xml
```
4. Po wybraniu pliku, PDF zostanie wygenerowany.
---
## 5. Testy jednostkowe
Aplikacja zawiera zestaw testów napisanych w **TypeScript**, które weryfikują poprawność działania aplikacji.
Projekt wykorzystuje **Vite** do bundlowania i **Vitest** jako framework testowy.
### Uruchamianie testów
1. Uruchom wszystkie testy:
```bash
npm run test
```
2. Uruchom testy z interfejsem graficznym:
```bash
npm run test:ui
```
3. Uruchom testy w trybie CI z raportem pokrycia:
```bash
npm run test:ci
```
---
Raport: /coverage/index.html
---
### 1. Nazewnictwo zmiennych i metod
- **Polsko-angielskie nazwy** stosowane w zmiennych, typach i metodach wynikają bezpośrednio ze struktury pliku schemy
faktury.
Takie podejście zapewnia spójność i ujednolicenie nazewnictwa z definicją danych zawartą w schemie XML.
### 2. Struktura danych
- Struktura danych interfejsu FA odzwierciedla strukturę danych źródłowych pliku XML, zachowując ich logiczne powiązania
i hierarchię
w bardziej czytelnej formie.
### 3. Typy i interfejsy
- Typy odzwierciedlają strukturę danych pobieranych z XML faktur oraz ułatwiają generowanie PDF
- Typy i interfejsy są definiowane w folderze types oraz plikach z rozszerzeniem types.ts.
---
## Dokumentacja używanych narzędzi
- Vitest Docs — https://vitest.dev/guide/
- Vite Docs — https://vitejs.dev/guide/
- TypeScript Handbook — https://www.typescriptlang.org/docs/
---
## Uwagi
- Upewnij się, że pliki XML są poprawnie sformatowane zgodnie z odpowiednią schemą.
- W przypadku problemów z Node.js, rozważ użycie menedżera wersji Node, np. [nvm](https://github.com/nvm-sh/nvm).

29
src/app-public/index.html Normal file
View File

@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>XML Parser</title>
</head>
<body>
<h1>📄 XML Parser</h1>
<h2>Wygeneruj fakture:</h2>
<input
accept=".xml"
id="xmlInput"
type="file"
/>
<h2>Wygeneruj UPO:</h2>
<input
accept=".xml"
id="xmlInputUPO"
type="file"
/>
<script
src="./main.ts"
type="module"
></script>
</body>
</html>

55
src/app-public/main.ts Normal file
View File

@@ -0,0 +1,55 @@
import { generateInvoice, generatePDFUPO } from '../lib-public';
import { AdditionalDataTypes } from '../lib-public/types/common.types';
const inputInvoice: HTMLInputElement = document.getElementById('xmlInput') as HTMLInputElement;
const inputUPO: HTMLInputElement = document.getElementById('xmlInputUPO') as HTMLInputElement;
inputInvoice.addEventListener('change', async (): Promise<void> => {
const file: File | undefined = inputInvoice.files?.[0];
if (!file) {
return;
}
const additionalData: AdditionalDataTypes = {
nrKSeF: '5555555555-20250808-9231003CA67B-BE',
qrCode:
'https://ksef-test.mf.gov.pl/invoice/5265877635/26-10-2025/HS5E1zrA8WVjDNq_xMVIN5SD6nyRymmQ-BcYHReUAa0',
};
generateInvoice(file, additionalData, 'blob').then((data: Blob): void => {
const url: string = URL.createObjectURL(data);
const a: HTMLAnchorElement = document.createElement('a');
a.href = url;
a.download = 'test.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
});
inputUPO.addEventListener('change', async (): Promise<void> => {
const file: File | undefined = inputUPO.files?.[0];
if (!file) {
return;
}
generatePDFUPO(file).then((blob) => {
const url: string = URL.createObjectURL(blob);
const a: HTMLAnchorElement = document.createElement('a');
a.href = url;
a.download = 'test.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
});

View File

@@ -0,0 +1,6 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../dist/types/public"
}
}

8
src/index.ts Normal file
View File

@@ -0,0 +1,8 @@
export { generateInvoice, generatePDFUPO } from './lib-public';
export { generateFA1 } from './lib-public/FA1-generator';
export { generateFA2 } from './lib-public/FA2-generator';
export { generateFA3 } from './lib-public/FA3-generator';
export { generateNaglowekUPO } from './lib-public/generators/UPO4_3/Naglowek';
export { generateDokumentUPO } from './lib-public/generators/UPO4_3/Dokumenty';
export { generateStyle } from './shared/PDF-functions';
export * from './shared/enums/common.enum';

View File

@@ -0,0 +1,94 @@
import pdfMake from 'pdfmake/build/pdfmake';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generateFA1 } from './FA1-generator';
import { Faktura } from './types/fa1.types';
import { AdditionalDataTypes } from './types/common.types';
vi.mock('./generators/FA1/Adnotacje', () => ({ generateAdnotacje: vi.fn(() => ({ example: 'adnotacje' })) }));
vi.mock('./generators/FA1/DodatkoweInformacje', () => ({
generateDodatkoweInformacje: vi.fn(() => ({ example: 'dodatkowe' })),
}));
vi.mock('./generators/FA1/Platnosc', () => ({ generatePlatnosc: vi.fn(() => ({ example: 'platnosc' })) }));
vi.mock('./generators/FA1/Podmioty', () => ({ generatePodmioty: vi.fn(() => [{ example: 'podmioty' }]) }));
vi.mock('./generators/FA1/PodsumowanieStawekPodatkuVat', () => ({
generatePodsumowanieStawekPodatkuVat: vi.fn(() => ({ example: 'podsumowanie' })),
}));
vi.mock('./generators/FA1/Rabat', () => ({ generateRabat: vi.fn(() => ({ example: 'rabat' })) }));
vi.mock('./generators/FA1/Szczegoly', () => ({ generateSzczegoly: vi.fn(() => ({ example: 'szczegoly' })) }));
vi.mock('./generators/FA1/WarunkiTransakcji', () => ({
generateWarunkiTransakcji: vi.fn(() => ({ example: 'warunki' })),
}));
vi.mock('./generators/FA1/Wiersze', () => ({ generateWiersze: vi.fn(() => ({ example: 'wiersze' })) }));
vi.mock('./generators/FA1/Zamowienie', () => ({
generateZamowienie: vi.fn(() => ({ example: 'zamowienie' })),
}));
vi.mock('./generators/common/DaneFaKorygowanej', () => ({
generateDaneFaKorygowanej: vi.fn(() => ({ example: 'daneKorygowanej' })),
}));
vi.mock('./generators/common/Naglowek', () => ({ generateNaglowek: vi.fn(() => [{ example: 'naglowek' }]) }));
vi.mock('./generators/common/Rozliczenie', () => ({
generateRozliczenie: vi.fn(() => ({ example: 'rozliczenie' })),
}));
vi.mock('./generators/common/Stopka', () => ({ generateStopka: vi.fn(() => [{ example: 'stopka' }]) }));
vi.mock('./PDF-functions', () => ({
generateStyle: vi.fn(() => ({ styles: {}, defaultStyle: {} })),
hasValue: vi.fn(() => true),
}));
describe('generateFA1', () => {
const mockCreatePdfReturn = { example: 'pdfCreatedObject' };
beforeEach(() => {
vi.restoreAllMocks();
});
it('calls pdfMake.createPdf and returns result (KOR with OkresFaKorygowanej, calls generateRabat)', () => {
const invoice: Faktura = {
Fa: {
RodzajFaktury: { _text: 'KOR' },
OkresFaKorygowanej: { _text: 'someValue' },
Zamowienie: {},
P_15: { _text: '15' },
KodWaluty: { _text: 'PLN' },
Adnotacje: {},
Rozliczenie: {},
Platnosc: {},
WarunkiTransakcji: {},
},
Stopka: {},
Naglowek: {},
} as any;
const additionalData: AdditionalDataTypes = { nrKSeF: 'nrKSeF' };
const createPdfSpy = vi.spyOn(pdfMake, 'createPdf').mockReturnValue(mockCreatePdfReturn as any);
const result = generateFA1(invoice, additionalData);
expect(createPdfSpy).toHaveBeenCalled();
expect(result).toBe(mockCreatePdfReturn);
});
it('calls pdfMake.createPdf and returns result (non-KOR, calls generateWiersze)', () => {
const invoice: Faktura = {
Fa: {
RodzajFaktury: { _text: 'VAT' },
Zamowienie: {},
P_15: { _text: '15' },
KodWaluty: { _text: 'PLN' },
},
Stopka: {},
Naglowek: {},
} as any;
const additionalData: AdditionalDataTypes = { nrKSeF: 'nrKSeF' };
const createPdfSpy = vi.spyOn(pdfMake, 'createPdf').mockReturnValue(mockCreatePdfReturn as any);
const result = generateFA1(invoice, additionalData);
expect(createPdfSpy).toHaveBeenCalled();
expect(result).toBe(mockCreatePdfReturn);
});
});

View File

@@ -0,0 +1,57 @@
import pdfMake, { TCreatedPdf } from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';
import { Content, TDocumentDefinitions } from 'pdfmake/interfaces';
import { generateStyle, getValue, hasValue } from '../shared/PDF-functions';
import { TRodzajFaktury } from '../shared/consts/const';
import { generateAdnotacje } from './generators/FA1/Adnotacje';
import { generateDodatkoweInformacje } from './generators/FA1/DodatkoweInformacje';
import { generatePlatnosc } from './generators/FA1/Platnosc';
import { generatePodmioty } from './generators/FA1/Podmioty';
import { generatePodsumowanieStawekPodatkuVat } from './generators/FA1/PodsumowanieStawekPodatkuVat';
import { generateRabat } from './generators/FA1/Rabat';
import { generateSzczegoly } from './generators/FA1/Szczegoly';
import { generateWarunkiTransakcji } from './generators/FA1/WarunkiTransakcji';
import { generateWiersze } from './generators/FA1/Wiersze';
import { generateZamowienie } from './generators/FA1/Zamowienie';
import { generateDaneFaKorygowanej } from './generators/common/DaneFaKorygowanej';
import { generateNaglowek } from './generators/common/Naglowek';
import { generateRozliczenie } from './generators/common/Rozliczenie';
import { generateStopka } from './generators/common/Stopka';
import { Faktura } from './types/fa1.types';
import { ZamowienieKorekta } from './enums/invoice.enums';
import { AdditionalDataTypes } from './types/common.types';
pdfMake.vfs = pdfFonts.vfs;
export function generateFA1(invoice: Faktura, additionalData: AdditionalDataTypes): TCreatedPdf {
const isKOR_RABAT: boolean =
invoice.Fa?.RodzajFaktury?._text == TRodzajFaktury.KOR && hasValue(invoice.Fa?.OkresFaKorygowanej);
const rabatOrRowsInvoice: Content = isKOR_RABAT ? generateRabat(invoice.Fa!) : generateWiersze(invoice.Fa!);
const docDefinition: TDocumentDefinitions = {
content: [
...generateNaglowek(invoice.Fa, additionalData),
generateDaneFaKorygowanej(invoice.Fa),
...generatePodmioty(invoice),
generateSzczegoly(invoice.Fa!),
rabatOrRowsInvoice,
generateZamowienie(
invoice.Fa?.Zamowienie,
ZamowienieKorekta.Order,
invoice.Fa?.P_15?._text ?? '',
invoice.Fa?.RodzajFaktury?._text ?? '',
invoice.Fa?.KodWaluty?._text ?? '',
getValue(invoice.Fa?.Adnotacje?.P_PMarzy) as string | undefined
),
generatePodsumowanieStawekPodatkuVat(invoice),
generateAdnotacje(invoice.Fa?.Adnotacje),
generateDodatkoweInformacje(invoice.Fa!),
generateRozliczenie(invoice.Fa?.Rozliczenie, invoice.Fa?.KodWaluty?._text ?? ''),
generatePlatnosc(invoice.Fa?.Platnosc),
generateWarunkiTransakcji(invoice.Fa?.WarunkiTransakcji),
...generateStopka(additionalData, invoice.Stopka, invoice.Naglowek, invoice.Fa?.WZ),
],
...generateStyle(),
};
return pdfMake.createPdf(docDefinition);
}

View File

@@ -0,0 +1,93 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import pdfMake from 'pdfmake/build/pdfmake';
import { Faktura } from './types/fa2.types';
import { generateFA2 } from './FA2-generator';
import { AdditionalDataTypes } from './types/common.types';
vi.mock('./generators/FA2/Adnotacje', () => ({ generateAdnotacje: vi.fn(() => ({ example: 'adnotacje' })) }));
vi.mock('./generators/FA2/DodatkoweInformacje', () => ({
generateDodatkoweInformacje: vi.fn(() => ({ example: 'dodatkowe' })),
}));
vi.mock('./generators/FA2/Platnosc', () => ({ generatePlatnosc: vi.fn(() => ({ example: 'platnosc' })) }));
vi.mock('./generators/FA2/Podmioty', () => ({ generatePodmioty: vi.fn(() => [{ example: 'podmioty' }]) }));
vi.mock('./generators/FA2/PodsumowanieStawekPodatkuVat', () => ({
generatePodsumowanieStawekPodatkuVat: vi.fn(() => ({ example: 'podsumowanie' })),
}));
vi.mock('./generators/FA2/Rabat', () => ({ generateRabat: vi.fn(() => ({ example: 'rabat' })) }));
vi.mock('./generators/FA2/Szczegoly', () => ({ generateSzczegoly: vi.fn(() => ({ example: 'szczegoly' })) }));
vi.mock('./generators/FA2/WarunkiTransakcji', () => ({
generateWarunkiTransakcji: vi.fn(() => ({ example: 'warunki' })),
}));
vi.mock('./generators/FA2/Wiersze', () => ({ generateWiersze: vi.fn(() => ({ example: 'wiersze' })) }));
vi.mock('./generators/FA2/Zamowienie', () => ({
generateZamowienie: vi.fn(() => ({ example: 'zamowienie' })),
}));
vi.mock('./generators/common/DaneFaKorygowanej', () => ({
generateDaneFaKorygowanej: vi.fn(() => ({ example: 'daneKorygowanej' })),
}));
vi.mock('./generators/common/Naglowek', () => ({ generateNaglowek: vi.fn(() => [{ example: 'naglowek' }]) }));
vi.mock('./generators/common/Rozliczenie', () => ({
generateRozliczenie: vi.fn(() => ({ example: 'rozliczenie' })),
}));
vi.mock('./generators/common/Stopka', () => ({ generateStopka: vi.fn(() => [{ example: 'stopka' }]) }));
vi.mock('./PDF-functions', () => ({
generateStyle: vi.fn(() => ({ styles: {}, defaultStyle: {} })),
hasValue: vi.fn(() => true),
}));
describe('generateFA2', () => {
const mockCreatePdfReturn = { example: 'pdfCreatedObject' };
beforeEach(() => {
vi.restoreAllMocks();
});
it('calls pdfMake.createPdf and returns result (KOR with OkresFaKorygowanej, uses generateRabat)', () => {
const invoice: Faktura = {
Fa: {
RodzajFaktury: { _text: 'KOR' },
OkresFaKorygowanej: { _text: 'someValue' },
Zamowienie: {},
P_15: { _text: '15' },
KodWaluty: { _text: 'PLN' },
Adnotacje: {},
Rozliczenie: {},
Platnosc: {},
WarunkiTransakcji: {},
},
Stopka: {},
Naglowek: {},
} as any;
const additionalData: AdditionalDataTypes = { nrKSeF: 'nrKSeF' };
const createPdfSpy = vi.spyOn(pdfMake, 'createPdf').mockReturnValue(mockCreatePdfReturn as any);
const result = generateFA2(invoice, additionalData);
expect(createPdfSpy).toHaveBeenCalled();
expect(result).toBe(mockCreatePdfReturn);
});
it('calls pdfMake.createPdf and returns result (non-KOR, uses generateWiersze)', () => {
const invoice: Faktura = {
Fa: {
RodzajFaktury: { _text: 'VAT' },
Zamowienie: {},
P_15: { _text: '15' },
KodWaluty: { _text: 'PLN' },
},
Stopka: {},
Naglowek: {},
} as any;
const additionalData: AdditionalDataTypes = { nrKSeF: 'nrKSeF' };
const createPdfSpy = vi.spyOn(pdfMake, 'createPdf').mockReturnValue(mockCreatePdfReturn as any);
const result = generateFA2(invoice, additionalData);
expect(createPdfSpy).toHaveBeenCalled();
expect(result).toBe(mockCreatePdfReturn);
});
});

View File

@@ -0,0 +1,57 @@
import pdfMake, { TCreatedPdf } from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';
import { Content, TDocumentDefinitions } from 'pdfmake/interfaces';
import { generateStyle, getValue, hasValue } from '../shared/PDF-functions';
import { TRodzajFaktury } from '../shared/consts/const';
import { generateAdnotacje } from './generators/FA2/Adnotacje';
import { generateDodatkoweInformacje } from './generators/FA2/DodatkoweInformacje';
import { generatePlatnosc } from './generators/FA2/Platnosc';
import { generatePodmioty } from './generators/FA2/Podmioty';
import { generatePodsumowanieStawekPodatkuVat } from './generators/FA2/PodsumowanieStawekPodatkuVat';
import { generateRabat } from './generators/FA2/Rabat';
import { generateSzczegoly } from './generators/FA2/Szczegoly';
import { generateWarunkiTransakcji } from './generators/FA2/WarunkiTransakcji';
import { generateWiersze } from './generators/FA2/Wiersze';
import { generateZamowienie } from './generators/FA2/Zamowienie';
import { generateDaneFaKorygowanej } from './generators/common/DaneFaKorygowanej';
import { generateNaglowek } from './generators/common/Naglowek';
import { generateRozliczenie } from './generators/common/Rozliczenie';
import { generateStopka } from './generators/common/Stopka';
import { Faktura } from './types/fa2.types';
import { ZamowienieKorekta } from './enums/invoice.enums';
import { AdditionalDataTypes } from './types/common.types';
pdfMake.vfs = pdfFonts.vfs;
export function generateFA2(invoice: Faktura, additionalData: AdditionalDataTypes): TCreatedPdf {
const isKOR_RABAT: boolean =
invoice.Fa?.RodzajFaktury?._text == TRodzajFaktury.KOR && hasValue(invoice.Fa?.OkresFaKorygowanej);
const rabatOrRowsInvoice: Content = isKOR_RABAT ? generateRabat(invoice.Fa!) : generateWiersze(invoice.Fa!);
const docDefinition: TDocumentDefinitions = {
content: [
...generateNaglowek(invoice.Fa, additionalData),
generateDaneFaKorygowanej(invoice.Fa),
...generatePodmioty(invoice),
generateSzczegoly(invoice.Fa!),
rabatOrRowsInvoice,
generateZamowienie(
invoice.Fa?.Zamowienie,
ZamowienieKorekta.Order,
invoice.Fa?.P_15?._text ?? '',
invoice.Fa?.RodzajFaktury?._text ?? '',
invoice.Fa?.KodWaluty?._text ?? '',
getValue(invoice.Fa?.Adnotacje?.PMarzy?.P_PMarzy) as string | undefined
),
generatePodsumowanieStawekPodatkuVat(invoice),
generateAdnotacje(invoice.Fa?.Adnotacje),
generateDodatkoweInformacje(invoice.Fa!),
generateRozliczenie(invoice.Fa?.Rozliczenie, invoice.Fa?.KodWaluty?._text ?? ''),
generatePlatnosc(invoice.Fa?.Platnosc),
generateWarunkiTransakcji(invoice.Fa?.WarunkiTransakcji),
...generateStopka(additionalData, invoice.Stopka, invoice.Naglowek, invoice.Fa?.WZ),
],
...generateStyle(),
};
return pdfMake.createPdf(docDefinition);
}

View File

@@ -0,0 +1,100 @@
import pdfMake, { TCreatedPdf } from 'pdfmake/build/pdfmake';
import { beforeEach, describe, expect, it, MockInstance, vi } from 'vitest';
import { generateFA3 } from './FA3-generator';
import { Faktura } from './types/fa3.types';
import { AdditionalDataTypes } from './types/common.types';
vi.mock('./generators/FA3/Adnotacje', () => ({ generateAdnotacje: vi.fn(() => ({ example: 'adnotacje' })) }));
vi.mock('./generators/FA3/DodatkoweInformacje', () => ({
generateDodatkoweInformacje: vi.fn(() => ({ example: 'dodatkowe' })),
}));
vi.mock('./generators/FA3/Platnosc', () => ({ generatePlatnosc: vi.fn(() => ({ example: 'platnosc' })) }));
vi.mock('./generators/FA3/Podmioty', () => ({ generatePodmioty: vi.fn(() => [{ example: 'podmioty' }]) }));
vi.mock('./generators/FA3/PodsumowanieStawekPodatkuVat', () => ({
generatePodsumowanieStawekPodatkuVat: vi.fn(() => ({ example: 'podsumowanie' })),
}));
vi.mock('./generators/FA3/Rabat', () => ({ generateRabat: vi.fn(() => ({ example: 'rabat' })) }));
vi.mock('./generators/FA3/Szczegoly', () => ({ generateSzczegoly: vi.fn(() => ({ example: 'szczegoly' })) }));
vi.mock('./generators/FA3/WarunkiTransakcji', () => ({
generateWarunkiTransakcji: vi.fn(() => ({ example: 'warunki' })),
}));
vi.mock('./generators/FA3/Wiersze', () => ({ generateWiersze: vi.fn(() => ({ example: 'wiersze' })) }));
vi.mock('./generators/FA3/Zamowienie', () => ({
generateZamowienie: vi.fn(() => ({ example: 'zamowienie' })),
}));
vi.mock('./generators/common/DaneFaKorygowanej', () => ({
generateDaneFaKorygowanej: vi.fn(() => ({ example: 'daneKorygowanej' })),
}));
vi.mock('./generators/common/Naglowek', () => ({ generateNaglowek: vi.fn(() => [{ example: 'naglowek' }]) }));
vi.mock('./generators/common/Rozliczenie', () => ({
generateRozliczenie: vi.fn(() => ({ example: 'rozliczenie' })),
}));
vi.mock('./generators/common/Stopka', () => ({ generateStopka: vi.fn(() => [{ example: 'stopka' }]) }));
vi.mock('./PDF-functions', () => ({
generateStyle: vi.fn(() => ({ styles: {}, defaultStyle: {} })),
hasValue: vi.fn(() => true),
}));
describe('generateFA3', (): void => {
const mockCreatePdfReturn = { example: 'pdfCreatedObject' };
beforeEach((): void => {
vi.restoreAllMocks();
});
it('should call pdfMake.createPdf and return its result (KOR with OkresFaKorygowanej, call generateRabat)', () => {
const invoice: Faktura = {
Fa: {
RodzajFaktury: { _text: 'KOR' },
OkresFaKorygowanej: { _text: 'someValue' },
Zamowienie: {},
P_15: { _text: '15' },
KodWaluty: { _text: 'PLN' },
Adnotacje: {},
Rozliczenie: {},
Platnosc: {},
WarunkiTransakcji: {},
},
Zalacznik: {},
Stopka: {},
Naglowek: {},
} as any;
const additionalData: AdditionalDataTypes = { nrKSeF: 'nrKSeF' };
const createPdfSpy: MockInstance = vi
.spyOn(pdfMake, 'createPdf')
.mockReturnValue(mockCreatePdfReturn as any);
const result: TCreatedPdf = generateFA3(invoice, additionalData);
expect(createPdfSpy).toHaveBeenCalled();
expect(result).toBe(mockCreatePdfReturn);
});
it('should call pdfMake.createPdf and return its result (non-KOR, call generateWiersze)', () => {
const invoice: Faktura = {
Fa: {
RodzajFaktury: { _text: 'VAT' },
Zamowienie: {},
P_15: { _text: '15' },
KodWaluty: { _text: 'PLN' },
},
Zalacznik: {},
Stopka: {},
Naglowek: {},
} as any;
const additionalData: AdditionalDataTypes = { nrKSeF: 'nrKSeF' };
const createPdfSpy: MockInstance = vi
.spyOn(pdfMake, 'createPdf')
.mockReturnValue(mockCreatePdfReturn as any);
const result: TCreatedPdf = generateFA3(invoice, additionalData);
expect(createPdfSpy).toHaveBeenCalled();
expect(result).toBe(mockCreatePdfReturn);
});
});

View File

@@ -0,0 +1,57 @@
import pdfMake, { TCreatedPdf } from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';
import { Content, TDocumentDefinitions } from 'pdfmake/interfaces';
import { generateStyle, getValue, hasValue } from '../shared/PDF-functions';
import { TRodzajFaktury } from '../shared/consts/const';
import { generateAdnotacje } from './generators/FA3/Adnotacje';
import { generateDodatkoweInformacje } from './generators/FA3/DodatkoweInformacje';
import { generatePlatnosc } from './generators/FA3/Platnosc';
import { generatePodmioty } from './generators/FA3/Podmioty';
import { generatePodsumowanieStawekPodatkuVat } from './generators/FA3/PodsumowanieStawekPodatkuVat';
import { generateRabat } from './generators/FA3/Rabat';
import { generateSzczegoly } from './generators/FA3/Szczegoly';
import { generateWarunkiTransakcji } from './generators/FA3/WarunkiTransakcji';
import { generateWiersze } from './generators/FA3/Wiersze';
import { generateZamowienie } from './generators/FA3/Zamowienie';
import { generateDaneFaKorygowanej } from './generators/common/DaneFaKorygowanej';
import { generateNaglowek } from './generators/common/Naglowek';
import { generateRozliczenie } from './generators/common/Rozliczenie';
import { generateStopka } from './generators/common/Stopka';
import { Faktura } from './types/fa3.types';
import { ZamowienieKorekta } from './enums/invoice.enums';
import { AdditionalDataTypes } from './types/common.types';
pdfMake.vfs = pdfFonts.vfs;
export function generateFA3(invoice: Faktura, additionalData: AdditionalDataTypes): TCreatedPdf {
const isKOR_RABAT: boolean =
invoice.Fa?.RodzajFaktury?._text == TRodzajFaktury.KOR && hasValue(invoice.Fa?.OkresFaKorygowanej);
const rabatOrRowsInvoice: Content = isKOR_RABAT ? generateRabat(invoice.Fa!) : generateWiersze(invoice.Fa!);
const docDefinition: TDocumentDefinitions = {
content: [
...generateNaglowek(invoice.Fa, additionalData, invoice.Zalacznik),
generateDaneFaKorygowanej(invoice.Fa),
...generatePodmioty(invoice),
generateSzczegoly(invoice.Fa!),
rabatOrRowsInvoice,
generateZamowienie(
invoice.Fa?.Zamowienie,
ZamowienieKorekta.Order,
invoice.Fa?.P_15?._text ?? '',
invoice.Fa?.RodzajFaktury?._text ?? '',
invoice.Fa?.KodWaluty?._text ?? '',
getValue(invoice.Fa?.Adnotacje?.PMarzy?.P_PMarzy) as string | undefined
),
generatePodsumowanieStawekPodatkuVat(invoice),
generateAdnotacje(invoice.Fa?.Adnotacje),
generateDodatkoweInformacje(invoice.Fa!),
generateRozliczenie(invoice.Fa?.Rozliczenie, invoice.Fa?.KodWaluty?._text ?? ''),
generatePlatnosc(invoice.Fa?.Platnosc),
generateWarunkiTransakcji(invoice.Fa?.WarunkiTransakcji),
...generateStopka(additionalData, invoice.Stopka, invoice.Naglowek, invoice.Fa?.WZ, invoice.Zalacznik),
],
...generateStyle(),
};
return pdfMake.createPdf(docDefinition);
}

View File

@@ -0,0 +1,65 @@
import pdfMake from 'pdfmake/build/pdfmake';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { generatePDFUPO } from './UPO-4_2-generators';
import * as XMLParser from '../shared/XML-parser';
describe('generatePDFUPO', () => {
const dummyFile = new File(['dummy'], 'dummy.xml', { type: 'text/xml' });
const dummyUpo = {
Potwierdzenie: {
field1: 'value1',
field2: 'value2',
},
};
beforeEach(() => {
vi.spyOn(XMLParser, 'parseXML').mockResolvedValue(dummyUpo);
vi.spyOn(pdfMake, 'createPdf').mockImplementation(
() =>
({
getBlob: (callback: (blob: Blob | null) => void) => {
const blob = new Blob(['PDF content'], { type: 'application/pdf' });
callback(blob);
},
}) as any
);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('successfully generates a PDF blob', async () => {
const blob = await generatePDFUPO(dummyFile);
expect(blob).toBeInstanceOf(Blob);
const text = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = (): void => resolve(reader.result as string);
reader.onerror = (): void => reject(reader.error);
reader.readAsText(blob);
});
expect(text).toContain('PDF content');
});
it('rejects promise if pdfMake returns null blob', async () => {
vi.spyOn(pdfMake, 'createPdf').mockReturnValue({
getBlob: (callback: (blob: Blob | null) => void) => {
callback(null);
},
} as any);
await expect(generatePDFUPO(dummyFile)).rejects.toEqual('Error');
});
it('calls parseXML with the input file', async () => {
const parseXMLSpy = vi.spyOn(XMLParser, 'parseXML');
await generatePDFUPO(dummyFile);
expect(parseXMLSpy).toHaveBeenCalledWith(dummyFile);
});
});

View File

@@ -0,0 +1,35 @@
import pdfMake from 'pdfmake/build/pdfmake';
import { Upo } from './types/upo-v4_2.types';
import { TDocumentDefinitions } from 'pdfmake/interfaces';
import { generateStyle } from '../shared/PDF-functions';
import { generateNaglowekUPO } from './generators/UPO4_2/Naglowek';
import { generateDokumnetUPO } from './generators/UPO4_2/Dokumenty';
import { parseXML } from '../shared/XML-parser';
import { Position } from '../shared/enums/common.enum';
export async function generatePDFUPO(file: File): Promise<Blob> {
const upo = (await parseXML(file)) as Upo;
const docDefinition: TDocumentDefinitions = {
content: [generateNaglowekUPO(upo.Potwierdzenie!), generateDokumnetUPO(upo.Potwierdzenie!)],
...generateStyle(),
pageSize: 'A4',
pageOrientation: 'landscape',
footer: function (currentPage: number, pageCount: number) {
return {
text: currentPage.toString() + ' z ' + pageCount,
alignment: Position.RIGHT,
margin: [0, 0, 20, 0],
};
},
};
return new Promise((resolve, reject): void => {
pdfMake.createPdf(docDefinition).getBlob((blob: Blob): void => {
if (blob) {
resolve(blob);
} else {
reject('Error');
}
});
});
}

View File

@@ -0,0 +1,65 @@
import pdfMake from 'pdfmake/build/pdfmake';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { generatePDFUPO } from './UPO-generator';
import * as XMLParser from '../shared/XML-parser';
describe('generatePDFUPO', () => {
const dummyFile = new File(['dummy'], 'dummy.xml', { type: 'text/xml' });
const dummyUpo = {
Potwierdzenie: {
field1: 'value1',
field2: 'value2',
},
};
beforeEach(() => {
vi.spyOn(XMLParser, 'parseXML').mockResolvedValue(dummyUpo);
vi.spyOn(pdfMake, 'createPdf').mockImplementation(
() =>
({
getBlob: (callback: (blob: Blob | null) => void) => {
const blob = new Blob(['PDF content'], { type: 'application/pdf' });
callback(blob);
},
}) as any
);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('successfully generates a PDF blob', async () => {
const blob = await generatePDFUPO(dummyFile);
expect(blob).toBeInstanceOf(Blob);
const text = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = (): void => resolve(reader.result as string);
reader.onerror = (): void => reject(reader.error);
reader.readAsText(blob);
});
expect(text).toContain('PDF content');
});
it('rejects promise if pdfMake returns null blob', async () => {
vi.spyOn(pdfMake, 'createPdf').mockReturnValue({
getBlob: (callback: (blob: Blob | null) => void) => {
callback(null);
},
} as any);
await expect(generatePDFUPO(dummyFile)).rejects.toEqual('Error');
});
it('calls parseXML with the input file', async () => {
const parseXMLSpy = vi.spyOn(XMLParser, 'parseXML');
await generatePDFUPO(dummyFile);
expect(parseXMLSpy).toHaveBeenCalledWith(dummyFile);
});
});

View File

@@ -0,0 +1,35 @@
import pdfMake from 'pdfmake/build/pdfmake';
import { Upo } from './types/upo-v4_2.types';
import { TDocumentDefinitions } from 'pdfmake/interfaces';
import { generateStyle } from '../shared/PDF-functions';
import { parseXML } from '../shared/XML-parser';
import { Position } from '../shared/enums/common.enum';
import { generateDokumentUPO } from './generators/UPO4_3/Dokumenty';
import { generateNaglowekUPO } from './generators/UPO4_3/Naglowek';
export async function generatePDFUPO(file: File): Promise<Blob> {
const upo = (await parseXML(file)) as Upo;
const docDefinition: TDocumentDefinitions = {
content: [generateNaglowekUPO(upo.Potwierdzenie!), generateDokumentUPO(upo.Potwierdzenie!)],
...generateStyle(),
pageSize: 'A4',
pageOrientation: 'landscape',
footer: function (currentPage: number, pageCount: number) {
return {
text: currentPage.toString() + ' z ' + pageCount,
alignment: Position.RIGHT,
margin: [0, 0, 20, 0],
};
},
};
return new Promise((resolve, reject): void => {
pdfMake.createPdf(docDefinition).getBlob((blob: Blob): void => {
if (blob) {
resolve(blob);
} else {
reject('Error');
}
});
});
}

View File

@@ -0,0 +1,5 @@
export enum ZamowienieKorekta {
BeforeCorrection = 'Zamówienie przed korektą',
AfterCorrection = 'Zamówienie po korekcie',
Order = 'Zamówienie',
}

View File

@@ -0,0 +1,102 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import * as FA1Generator from './FA1-generator';
import * as FA2Generator from './FA2-generator';
import * as FA3Generator from './FA3-generator';
import { generateInvoice } from './generate-invoice';
import * as XMLParser from '../shared/XML-parser';
import { AdditionalDataTypes } from './types/common.types';
describe('generateInvoice', () => {
const mockBlob = new Blob(['mock pdf content'], { type: 'application/pdf' });
beforeEach(() => {
vi.resetAllMocks();
});
const additionalData: AdditionalDataTypes = {
nrKSeF: 'testKSeF',
qrCode: 'qrCodeValue',
isMobile: false,
};
it('should call generateFA1 and resolve with blob for version FA (1)', async () => {
const fakeXml = {
Faktura: {
Naglowek: {
KodFormularza: {
_attributes: { kodSystemowy: 'FA (1)' },
},
},
},
};
vi.spyOn(XMLParser, 'parseXML').mockResolvedValue(fakeXml);
const getBlobMock = vi.fn().mockImplementation((cb) => cb(mockBlob));
vi.spyOn(FA1Generator, 'generateFA1').mockReturnValue({ getBlob: getBlobMock } as any);
const file = new File([], 'test.xml');
const result = await generateInvoice(file, additionalData, 'blob');
expect(result).toBe(mockBlob);
expect(XMLParser.parseXML).toHaveBeenCalledWith(file);
expect(FA1Generator.generateFA1).toHaveBeenCalledWith(fakeXml.Faktura, additionalData);
expect(getBlobMock).toHaveBeenCalled();
});
it('should call generateFA2 and resolve with blob for version FA (2)', async () => {
const fakeXml = {
Faktura: {
Naglowek: {
KodFormularza: {
_attributes: { kodSystemowy: 'FA (2)' },
},
},
},
};
vi.spyOn(XMLParser, 'parseXML').mockResolvedValue(fakeXml);
const getBlobMock = vi.fn().mockImplementation((cb) => cb(mockBlob));
vi.spyOn(FA2Generator, 'generateFA2').mockReturnValue({ getBlob: getBlobMock } as any);
const file = new File([], 'test.xml');
const result = await generateInvoice(file, additionalData, 'blob');
expect(result).toBe(mockBlob);
expect(XMLParser.parseXML).toHaveBeenCalledWith(file);
expect(FA2Generator.generateFA2).toHaveBeenCalledWith(fakeXml.Faktura, additionalData);
expect(getBlobMock).toHaveBeenCalled();
});
it('should call generateFA3 and resolve with blob for version FA (3)', async () => {
const fakeXml = {
Faktura: {
Naglowek: {
KodFormularza: {
_attributes: { kodSystemowy: 'FA (3)' },
},
},
},
};
vi.spyOn(XMLParser, 'parseXML').mockResolvedValue(fakeXml);
const getBlobMock = vi.fn().mockImplementation((cb) => cb(mockBlob));
vi.spyOn(FA3Generator, 'generateFA3').mockReturnValue({ getBlob: getBlobMock } as any);
const file = new File([], 'test.xml');
const result = await generateInvoice(file, additionalData, 'blob');
expect(result).toBe(mockBlob);
expect(XMLParser.parseXML).toHaveBeenCalledWith(file);
expect(FA3Generator.generateFA3).toHaveBeenCalledWith(fakeXml.Faktura, additionalData);
expect(getBlobMock).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,59 @@
import { generateFA1 } from './FA1-generator';
import { Faktura as Faktura1 } from './types/fa1.types';
import { generateFA2 } from './FA2-generator';
import { Faktura as Faktura2 } from './types/fa2.types';
import { generateFA3 } from './FA3-generator';
import { Faktura as Faktura3 } from './types/fa3.types';
import { parseXML } from '../shared/XML-parser';
import { TCreatedPdf } from 'pdfmake/build/pdfmake';
import { AdditionalDataTypes } from './types/common.types';
export async function generateInvoice(
file: File,
additionalData: AdditionalDataTypes,
formatType: 'blob'
): Promise<Blob>;
export async function generateInvoice(
file: File,
additionalData: AdditionalDataTypes,
formatType: 'base64'
): Promise<string>;
export async function generateInvoice(
file: File,
additionalData: AdditionalDataTypes,
formatType: FormatType = 'blob'
): Promise<FormatTypeResult> {
const xml: unknown = await parseXML(file);
const wersja: any = (xml as any)?.Faktura?.Naglowek?.KodFormularza?._attributes?.kodSystemowy;
let pdf: TCreatedPdf;
return new Promise((resolve): void => {
switch (wersja) {
case 'FA (1)':
pdf = generateFA1((xml as any).Faktura as Faktura1, additionalData);
break;
case 'FA (2)':
pdf = generateFA2((xml as any).Faktura as Faktura2, additionalData);
break;
case 'FA (3)':
pdf = generateFA3((xml as any).Faktura as Faktura3, additionalData);
break;
}
switch (formatType) {
case 'blob':
pdf.getBlob((blob: Blob): void => {
resolve(blob);
});
break;
case 'base64':
default:
pdf.getBase64((base64: string): void => {
resolve(base64);
});
}
});
}
type FormatType = 'blob' | 'base64';
type FormatTypeResult = Blob | string;

View File

@@ -0,0 +1,123 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generateAdnotacje } from './Adnotacje';
import { Adnotacje } from '../../types/fa1.types';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn().mockImplementation((label) => ({ text: `HEADER:${label}` })),
createLabelText: vi.fn().mockImplementation((label, value) => [{ text: `LABEL:${label}${value}` }]),
formatText: vi.fn().mockImplementation((text) => ({ text })),
hasValue: vi.fn((val) => Boolean(val && val._text)),
verticalSpacing: vi.fn().mockImplementation((size) => ({ text: `SPACING:${size}` })),
generateColumns: vi.fn((left, right) => ({ columns: [left, right] })),
}));
describe(generateAdnotacje.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('returns an empty array when argument is undefined', () => {
const result = generateAdnotacje(undefined);
expect(result).toEqual([]);
});
it('adds annotation P_19 and also P_19A, P_19B, P_19C', () => {
const adnotacje: Adnotacje = {
P_19: { _text: '1' },
P_19A: { _text: 'tekst19A' },
P_19B: { _text: 'tekst19B' },
P_19C: { _text: 'tekst19C' },
};
const result = generateAdnotacje(adnotacje);
expect(result.some((el) => JSON.stringify(el).includes('HEADER:Adnotacje'))).toBeTruthy();
expect(
result.some((el) =>
JSON.stringify(el).includes('LABEL:Przepis ustawy albo aktu wydanego na podstawie ustawy: tekst19A')
)
).toBeTruthy();
expect(
result.some((el) => JSON.stringify(el).includes('LABEL:Przepis dyrektywy: tekst19B'))
).toBeTruthy();
expect(
result.some((el) => JSON.stringify(el).includes('LABEL:Inna podstawa prawna: tekst19C'))
).toBeTruthy();
});
it('adds "Split payment mechanism" annotation when P_18A = 1', () => {
const adnotacje: Adnotacje = { P_18A: { _text: '1' } };
const result = generateAdnotacje(adnotacje);
expect(result.some((el) => JSON.stringify(el).includes('Mechanizm podzielonej płatności'))).toBeTruthy();
});
it('adds correct texts for cash method/reverse charge/simplified procedure', () => {
const adnotacje: Adnotacje = {
P_16: { _text: '1' },
P_18: { _text: '1' },
P_23: { _text: '1' },
};
const result = generateAdnotacje(adnotacje);
expect(JSON.stringify(result)).toContain('Metoda kasowa');
expect(JSON.stringify(result)).toContain('Odwrotne obciążenie');
expect(JSON.stringify(result)).toContain('Procedura trójstronna uproszczona');
});
it('adds "Self-billing" when P_17=1', () => {
const adnotacje: Adnotacje = { P_17: { _text: '1' } };
const result = generateAdnotacje(adnotacje);
expect(JSON.stringify(result)).toContain('Samofakturowanie');
});
it('adds margin annotation based on subtypes', () => {
const adnotacje: Adnotacje = {
P_PMarzy: { _text: '1' },
P_PMarzy_3_2: { _text: '1' }, // works of art
};
const result = generateAdnotacje(adnotacje);
expect(JSON.stringify(result)).toContain('dzieła sztuki');
});
});
describe('generateAdnotacje - additional coverage', () => {
it('adds correct margin annotation for all subtypes', () => {
const variants = [
{ key: 'P_PMarzy_3_1', expected: 'towary używane' },
{ key: 'P_PMarzy_2', expected: 'biura podróży' },
{ key: 'P_PMarzy_3_3', expected: 'przedmioty kolekcjonerskie i antyki' },
];
variants.forEach(({ key, expected }) => {
const adnotacje: any = {
P_PMarzy: { _text: '1' },
[key]: { _text: '1' },
};
const result = generateAdnotacje(adnotacje);
expect(JSON.stringify(result)).toContain(expected);
});
});
it('handles P_22 and calls generateDostawy', () => {
const adnotacje: any = { P_22: { _text: '1' } };
const result = generateAdnotacje(adnotacje);
expect(JSON.stringify(result)).toContain('Wewnątrzwspólnotowe dostawy nowych środków transportu');
});
it('returns empty array if adnotacje has no valid fields', () => {
const adnotacje: any = { P_99: { _text: '' } };
const result = generateAdnotacje(adnotacje);
expect(result).toEqual([]);
});
it('adds header and spacing when result has items', () => {
const adnotacje: any = { P_16: { _text: '1' } };
const result = generateAdnotacje(adnotacje) as any;
const resultString = JSON.stringify(result);
expect(resultString).toContain('HEADER:Adnotacje');
expect(resultString).toContain('SPACING:1');
});
});

View File

@@ -0,0 +1,302 @@
import { Content, ContentTable } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
formatText,
generateColumns,
hasValue,
verticalSpacing,
} from '../../../shared/PDF-functions';
import { Adnotacje } from '../../types/fa1.types';
import FormatTyp from '../../../shared/enums/common.enum';
import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const';
export function generateAdnotacje(adnotacje?: Adnotacje): Content[] {
const result: Content[] = [];
let firstColumn: Content[] = [];
const secondColumn: Content[] = [];
if (adnotacje) {
if (adnotacje?.P_19?._text === '1') {
addToColumn(
firstColumn,
secondColumn,
{
text: 'Dostawa towarów lub świadczenie usług zwolnionych od podatku na podstawie art. 43 ust. 1, art. 113 ust. 1 i 9 albo przepisów wydanych na podstawie art. 82 ust. 3 lub na podstawie innych przepisów',
},
true
);
if (adnotacje.P_19A?._text) {
addToColumn(
firstColumn,
secondColumn,
createLabelText(
'Podstawa zwolnienia od podatku: ',
'Przepis ustawy albo aktu wydanego na podstawie ustawy, na podstawie którego podatnik stosuje adnotacje od podatku'
),
true
);
addToColumn(
firstColumn,
secondColumn,
createLabelText('Przepis ustawy albo aktu wydanego na podstawie ustawy: ', adnotacje.P_19A._text),
true
);
}
if (adnotacje.P_19B?._text) {
addToColumn(
firstColumn,
secondColumn,
createLabelText(
'Podstawa zwolnienia od podatku: ',
'Przepis dyrektywy 2006/112/WE, który zwalnia od podatku taką dostawę towarów lub takie świadczenie usług'
),
true
);
addToColumn(
firstColumn,
secondColumn,
createLabelText('Przepis dyrektywy: ', adnotacje.P_19B._text),
true
);
}
if (adnotacje.P_19C?._text) {
addToColumn(
firstColumn,
secondColumn,
createLabelText(
'Podstawa zwolnienia od podatku: ',
'Inna podstawa prawna wskazującą na to, że dostawa towarów lub świadczenie usług korzysta ze zwolnienia'
),
true
);
addToColumn(
firstColumn,
secondColumn,
createLabelText('Inna podstawa prawna: ', adnotacje.P_19C._text),
true
);
}
}
if (adnotacje.P_18A?._text === '1') {
addToColumn(firstColumn, secondColumn, { text: 'Mechanizm podzielonej płatności' });
}
if (adnotacje.P_16?._text === '1') {
addToColumn(firstColumn, secondColumn, { text: 'Metoda kasowa' });
}
if (adnotacje.P_18?._text === '1') {
addToColumn(firstColumn, secondColumn, { text: 'Odwrotne obciążenie' });
}
if (adnotacje.P_23?._text === '1') {
addToColumn(firstColumn, secondColumn, { text: 'Procedura trójstronna uproszczona' });
}
if (adnotacje.P_PMarzy?._text === '1') {
let valueMarzy = '';
if (adnotacje.P_PMarzy_3_1?._text === '1') {
valueMarzy = 'towary używane';
} else if (adnotacje.P_PMarzy_3_2?._text === '1') {
valueMarzy = 'dzieła sztuki';
} else if (adnotacje.P_PMarzy_2?._text === '1') {
valueMarzy = 'biura podróży';
} else if (adnotacje.P_PMarzy_3_3?._text === '1') {
valueMarzy = 'przedmioty kolekcjonerskie i antyki';
}
addToColumn(firstColumn, secondColumn, createLabelText('Procedura marży: ', valueMarzy));
}
if (adnotacje.P_17?._text === '1') {
addToColumn(firstColumn, secondColumn, { text: 'Samofakturowanie' });
}
if (adnotacje.P_22?._text === '1') {
let obowiazekVAT: Content[] = [];
obowiazekVAT = [...createLabelText('Wewnątrzwspólnotowe dostawy nowych środków transportu', ' ')];
if (obowiazekVAT) {
firstColumn = [firstColumn, ...obowiazekVAT];
}
}
if (firstColumn.length || secondColumn.length) {
result.push(generateColumns([firstColumn, secondColumn]));
}
if (result.length) {
result.unshift(verticalSpacing(1));
result.unshift(createHeader('Adnotacje'));
result.unshift(verticalSpacing(1));
result.push(verticalSpacing(1));
}
if (adnotacje.P_22?._text === '1') {
result.push(generateDostawy(adnotacje));
}
}
return result;
}
export function generateDostawy(adnotacje: Adnotacje): Content[] {
const result: Content[] = [];
const table: Content[][] = [];
const anyP22B =
hasValue(adnotacje.P_22B) ||
hasValue(adnotacje.P_22BT) ||
hasValue(adnotacje.P_22B1) ||
hasValue(adnotacje.P_22B2) ||
hasValue(adnotacje.P_22B3) ||
hasValue(adnotacje.P_22B4);
const anyP22C: boolean = hasValue(adnotacje.P_22C) || hasValue(adnotacje.P_22C1);
const anyP22D: boolean = hasValue(adnotacje.P_22D) || hasValue(adnotacje.P_22D1);
if (hasValue(adnotacje.P_22A)) {
table.push([
formatText('Data dopuszczenia nowego środka transportu do użytku', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22A?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22BMK)) {
table.push([
formatText('Marka nowego środka transportu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22BMK?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22BMD)) {
table.push([
formatText('Model nowego środka transportu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22BMD?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22BK)) {
table.push([
formatText('Kolor nowego środka transportu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22BK?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22BNR)) {
table.push([
formatText('Numer rejestracyjny nowego środka transportu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22BNR?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22BRP)) {
table.push([
formatText('Rok produkcji nowego środka transportu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22BRP?._text, FormatTyp.Default),
]);
}
if (anyP22B) {
table.push([
formatText('Rodzaj pojazdu', FormatTyp.GrayBoldTitle),
formatText(
'Dostawa dotyczy pojazdów lądowych, o których mowa w art. 2 pkt 10 lit. a ustawy',
FormatTyp.Default
),
]);
if (hasValue(adnotacje.P_22B)) {
table.push([
formatText('Przebieg pojazdu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22B?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22B1)) {
table.push([
formatText('Numer VIN', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22B1?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22B2)) {
table.push([
formatText('Numer nadwozia', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22B2?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22B3)) {
table.push([
formatText('Numer podwozia', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22B3?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22B4)) {
table.push([
formatText('Numer ramy', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22B4?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22BT)) {
table.push([
formatText('Typ nowego środka transportu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22BT?._text, FormatTyp.Default),
]);
}
} else if (anyP22C) {
table.push([
formatText('Rodzaj pojazdu', FormatTyp.GrayBoldTitle),
formatText(
'Dostawa dotyczy jednostek pływających, o których mowa w art. 2 pkt 10 lit. b ustawy',
FormatTyp.Default
),
]);
if (hasValue(adnotacje.P_22C)) {
table.push([
formatText('Przebieg pojazdu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22C?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22C1)) {
table.push([
formatText('Numer kadłuba nowego środka transportu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22C1?._text, FormatTyp.Default),
]);
}
} else if (anyP22D) {
table.push([
formatText('Rodzaj pojazdu', FormatTyp.GrayBoldTitle),
formatText(
'Dostawa dotyczy statków powietrznych, o których mowa w art. 2 pkt 10 lit. c ustawy',
FormatTyp.Default
),
]);
if (hasValue(adnotacje.P_22D)) {
table.push([
formatText('Przebieg pojazdu', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22D?._text, FormatTyp.Default),
]);
}
if (hasValue(adnotacje.P_22D1)) {
table.push([
formatText('Numer fabryczny nowego środka transportu<', FormatTyp.GrayBoldTitle),
formatText(adnotacje.P_22D1?._text, FormatTyp.Default),
]);
}
}
if (table.length) {
result.push([
{
unbreakable: true,
table: {
body: table,
widths: ['*', '*'],
},
layout: DEFAULT_TABLE_LAYOUT,
} as ContentTable,
]);
}
return result;
}
function addToColumn(
firstColumn: Content[],
secondColumn: Content[],
content: Content,
isFirstColumn?: boolean
): void {
if (firstColumn.length > secondColumn.length && isFirstColumn) {
secondColumn.push(content);
return;
}
firstColumn.push(content);
}

View File

@@ -0,0 +1,43 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generateDostawy } from './Adnotacje';
import { Adnotacje } from '../../types/fa1.types';
vi.mock('../../../shared/PDF-functions', () => ({
formatText: vi.fn((text, format) => ({ text, format })),
hasValue: vi.fn((val) => Boolean(val && val._text)),
}));
const formatText = vi.fn((text, format) => ({ text, format }));
const hasValue = vi.fn((val) => Boolean(val && val._text));
describe(generateDostawy.name, () => {
beforeEach(() => {
vi.clearAllMocks();
hasValue.mockImplementation((val) => Boolean(val && val._text));
});
it('returns empty array when no values', () => {
const adnotacje: Adnotacje = {};
const result = generateDostawy(adnotacje);
expect(result).toEqual([]);
});
it('creates table for P_22AP_22BRP fields', () => {
const adnotacje: Adnotacje = {
P_22A: { _text: '2024-01-01' },
P_22BMK: { _text: 'Ford' },
P_22BMD: { _text: 'Focus' },
P_22BK: { _text: 'Red' },
P_22BNR: { _text: 'ABC123' },
P_22BRP: { _text: '2023' },
};
const result = generateDostawy(adnotacje);
expect(result).toHaveLength(1);
});
it('does not create table when no valid values found', () => {
const adnotacje: Adnotacje = { P_22D: { _text: '' } };
hasValue.mockReturnValue(false);
const result = generateDostawy(adnotacje);
expect(result).toEqual([]);
});
});

View File

@@ -0,0 +1,105 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Adres } from '../../types/fa1.types';
import { generateAdres } from './Adres';
vi.mock('../../../shared/PDF-functions', () => ({
createLabelText: vi.fn().mockImplementation((label, value) => [{ text: `LABEL:${label}${value._text}` }]),
}));
describe('generateAdres', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('returns an empty array when both AdresPol and AdresZagr are undefined', () => {
const adres: Adres = {};
const result = generateAdres(adres);
expect(result).toEqual([]);
});
it('creates correct fields for foreign address (AdresZagr)', () => {
const adres: Adres = {
AdresZagr: {
KodKraju: { _text: 'PL' },
Ulica: { _text: 'Example Street' },
NrDomu: { _text: '123' },
NrLokalu: { _text: '4A' },
KodPocztowy: { _text: '00-123' },
Miejscowosc: { _text: 'Warsaw' },
GLN: { _text: '123456789' },
},
};
const result = generateAdres(adres);
expect(result).toEqual(
expect.arrayContaining([
[{ text: 'LABEL:Kraj: PL' }],
[{ text: 'LABEL:Ulica: Example Street' }],
[{ text: 'LABEL:Numer domu: 123' }],
[{ text: 'LABEL:Numer lokalu: 4A' }],
[{ text: 'LABEL:Kod pocztowy: 00-123' }],
[{ text: 'LABEL:Miejscowość: Warsaw' }],
[{ text: 'LABEL:GLN: 123456789' }],
])
);
});
it('creates correct fields for Polish address (AdresPol)', () => {
const adres: Adres = {
AdresPol: {
Wojewodztwo: { _text: 'Mazowieckie' },
Powiat: { _text: 'Warszawa' },
Gmina: { _text: 'Centrum' },
Ulica: { _text: 'Main' },
NrDomu: { _text: '5' },
NrLokalu: { _text: '12B' },
KodPocztowy: { _text: '01-234' },
Miejscowosc: { _text: 'Warsaw' },
Poczta: { _text: 'Warsaw 1' },
GLN: { _text: '987654321' },
},
};
const result = generateAdres(adres);
expect(result).toEqual(
expect.arrayContaining([
[{ text: 'LABEL:Województwo: Mazowieckie' }],
[{ text: 'LABEL:Powiat: Warszawa' }],
[{ text: 'LABEL:Gmina: Centrum' }],
[{ text: 'LABEL:Ulica: Main' }],
[{ text: 'LABEL:Numer domu: 5' }],
[{ text: 'LABEL:Numer lokalu: 12B' }],
[{ text: 'LABEL:Kod pocztowy: 01-234' }],
[{ text: 'LABEL:Miejscowość: Warsaw' }],
[{ text: 'LABEL:Poczta: Warsaw 1' }],
[{ text: 'LABEL:GLN: 987654321' }],
])
);
});
it('works correctly when both AdresPol and AdresZagr exist', () => {
const adres: Adres = {
AdresPol: { Miejscowosc: { _text: 'Katowice' } },
AdresZagr: { Miejscowosc: { _text: 'Berlin' } },
};
const result = generateAdres(adres);
expect(result).toEqual(
expect.arrayContaining([
[{ text: 'LABEL:Miejscowość: Katowice' }],
[{ text: 'LABEL:Miejscowość: Berlin' }],
])
);
});
it('skips undefined fields in AdresPol and AdresZagr', () => {
const adres: Adres = {
AdresPol: {},
AdresZagr: {},
};
const result = generateAdres(adres);
expect(result).toEqual([]);
});
});

View File

@@ -0,0 +1,69 @@
import { Content } from 'pdfmake/interfaces';
import { createLabelText } from '../../../shared/PDF-functions';
import { Adres, FP } from '../../types/fa1.types';
export function generateAdres(adres: Adres): Content[] {
const result: Content[] = [];
if (adres.AdresZagr) {
const adresZagr: Record<string, FP> = adres.AdresZagr;
if (adresZagr.KodKraju) {
result.push(createLabelText('Kraj: ', adresZagr.KodKraju));
}
if (adresZagr.Ulica) {
result.push(createLabelText('Ulica: ', adresZagr.Ulica));
}
if (adresZagr.NrDomu) {
result.push(createLabelText('Numer domu: ', adresZagr.NrDomu));
}
if (adresZagr.NrLokalu) {
result.push(createLabelText('Numer lokalu: ', adresZagr.NrLokalu));
}
if (adresZagr.KodPocztowy) {
result.push(createLabelText('Kod pocztowy: ', adresZagr.KodPocztowy));
}
if (adresZagr.Miejscowosc) {
result.push(createLabelText('Miejscowość: ', adresZagr.Miejscowosc));
}
if (adresZagr.GLN) {
result.push(createLabelText('GLN: ', adresZagr.GLN));
}
}
if (adres.AdresPol) {
const adresPol: Record<string, FP> = adres.AdresPol;
if (adresPol.Wojewodztwo) {
result.push(createLabelText('Województwo: ', adresPol.Wojewodztwo));
}
if (adresPol.Powiat) {
result.push(createLabelText('Powiat: ', adresPol.Powiat));
}
if (adresPol.Gmina) {
result.push(createLabelText('Gmina: ', adresPol.Gmina));
}
if (adresPol.Ulica) {
result.push(createLabelText('Ulica: ', adresPol.Ulica));
}
if (adresPol.NrDomu) {
result.push(createLabelText('Numer domu: ', adresPol.NrDomu));
}
if (adresPol.NrLokalu) {
result.push(createLabelText('Numer lokalu: ', adresPol.NrLokalu));
}
if (adresPol.KodPocztowy) {
result.push(createLabelText('Kod pocztowy: ', adresPol.KodPocztowy));
}
if (adresPol.Miejscowosc) {
result.push(createLabelText('Miejscowość: ', adresPol.Miejscowosc));
}
if (adresPol.Poczta) {
result.push(createLabelText('Poczta: ', adresPol.Poczta));
}
if (adresPol.GLN) {
result.push(createLabelText('GLN: ', adresPol.GLN));
}
}
return result;
}

View File

@@ -0,0 +1,47 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { DodatkowyOpi, Fa } from '../../types/fa1.types';
import { generateDodatkoweInformacje } from './DodatkoweInformacje';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: () => [{ text: 'HEADER:Dodatkowe informacje' }],
createSubHeader: () => [{ text: 'SUBHEADER:Dodatkowy opis' }],
formatText: (text: string) => ({ text }),
createSection: (content: any, _: boolean) => content,
getValue: (val: any) => val?._text || '',
getTable: (data: any) => data,
getContentTable: () => ({ content: { table: 'FAKE_TABLE' } }),
}));
describe('generateDodatkoweInformacje', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('returns an empty array when no triggers present', () => {
const faVat: Fa = {};
const result = generateDodatkoweInformacje(faVat);
expect(result).toEqual([]);
});
it('returns correct table when DodatkowyOpis provided', () => {
const faVat: Fa = {
DodatkowyOpis: [
{ Klucz: { _text: 'RODZAJ' }, Wartosc: { _text: 'TRESC' } },
{ Klucz: { _text: 'INNY' }, Wartosc: { _text: 'OPIS' } },
] as DodatkowyOpi[],
};
const result = generateDodatkoweInformacje(faVat);
expect(result).toContainEqual({ text: 'HEADER:Dodatkowe informacje' });
expect(result).toContainEqual({ text: 'SUBHEADER:Dodatkowy opis' });
expect(result).toContainEqual({ table: 'FAKE_TABLE' });
});
it('includes section when any element is present', () => {
const faVat: Fa = { TP: { _text: '1' }, ZwrotAkcyzy: { _text: '1' } };
const result = generateDodatkoweInformacje(faVat);
expect(result.length).toBeGreaterThan(0);
});
});

View File

@@ -0,0 +1,92 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createSection,
createSubHeader,
formatText,
getContentTable,
getTable,
getValue,
} from '../../../shared/PDF-functions';
import { HeaderDefine } from '../../../shared/types/pdf-types';
import { DodatkowyOpi, Fa } from '../../types/fa1.types';
import FormatTyp from '../../../shared/enums/common.enum';
import { FakturaZaliczkowa, TableWithFields } from '../../types/fa1-additional-types';
export function generateDodatkoweInformacje(faVat: Fa): Content[] {
const tpLabel: Content[] = [];
if (getValue(faVat.TP) === '1') {
tpLabel.push(
formatText('- Istniejące powiązania między nabywcą a dokonującym dostawy towarów lub usługodawcą')
);
}
const fpLabel: Content[] = [];
if (getValue(faVat.FP) === '1') {
fpLabel.push(formatText('- Faktura, o której mowa w art. 109 ust. 3d ustawy'));
}
const zwrotAkcyzyLabel: Content[] = [];
if (getValue(faVat.ZwrotAkcyzy) === '1') {
zwrotAkcyzyLabel.push(
formatText(
'- Informacja dodatkowa związana ze zwrotem podatku akcyzowego zawartego w cenie oleju napędowego'
)
);
}
const labels: Content[][] = [tpLabel, fpLabel, zwrotAkcyzyLabel].filter((el) => el.length > 0);
const table: Content[] = [
...createHeader('Dodatkowe informacje'),
...labels,
...generateDodatkowyOpis(faVat.DodatkowyOpis),
];
return table.length > 1 ? createSection(table, true) : [];
}
function generateDodatkowyOpis(fakturaZaliczkowaData: DodatkowyOpi[] | undefined): Content[] {
if (!fakturaZaliczkowaData) {
return [];
}
const fakturaZaliczkowa: FakturaZaliczkowa[] = getTable(fakturaZaliczkowaData)?.map((item, index) => ({
...item,
lp: { _text: index + 1 },
}));
const table: Content[] = createSubHeader('Dodatkowy opis');
const fakturaZaliczkowaHeader: HeaderDefine[] = [
{
name: 'lp',
title: 'Lp.',
format: FormatTyp.Default,
width: 'auto',
},
{
name: 'Klucz',
title: 'Rodzaj informacji',
format: FormatTyp.Default,
width: 'auto',
},
{
name: 'Wartosc',
title: 'Treść informacji',
format: FormatTyp.Default,
width: '*',
},
];
const tableFakturaZaliczkowa: TableWithFields = getContentTable<(typeof fakturaZaliczkowa)[0]>(
fakturaZaliczkowaHeader,
fakturaZaliczkowa,
'*',
[0, 0, 0, 0]
);
if (tableFakturaZaliczkowa.content) {
table.push(tableFakturaZaliczkowa.content);
}
return table;
}

View File

@@ -0,0 +1,122 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generatePlatnosc } from './Platnosc';
import { Platnosc } from '../../types/fa1.types';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label: string, _pad?: any) => [{ text: `HEADER:${label}` }]),
createLabelText: vi.fn((label: string, value: any) => ({
text: `LABEL:${label}${typeof value === 'object' && value?._text ? value._text : value}`,
})),
generateLine: vi.fn(() => ({ text: 'LINE' })),
generateTwoColumns: vi.fn((left, right, pad?) => ({ left, right, pad, type: '2COL' })),
getContentTable: vi.fn((_header, data, _width) => ({
content: Array.isArray(data) && data.length ? { data } : undefined,
})),
getTable: vi.fn((data) => data || []),
hasValue: vi.fn((fp: any) => Boolean(fp && fp._text && fp._text !== '')),
}));
vi.mock('../../../shared/generators/common/functions', () => ({
getFormaPlatnosciString: vi.fn((fp: any) => (fp?._text ? 'Przelew' : '')),
}));
vi.mock('./RachunekBankowy', () => ({
generujRachunekBankowy: vi.fn((table, label) => [{ text: `ACCOUNT:${label}` }]),
}));
describe('generatePlatnosc', () => {
beforeEach(() => vi.clearAllMocks());
it('returns empty array when no payment info given', () => {
expect(generatePlatnosc(undefined)).toEqual([]);
});
it('handles case: Platnosc.Zaplacono = 1', () => {
const p: Platnosc = { Zaplacono: { _text: '1' }, DataZaplaty: { _text: '2025-10-10' } };
const result = generatePlatnosc(p);
expect(result).toEqual(
expect.arrayContaining([
{ text: 'LINE' },
{ text: 'HEADER:Płatność' },
{ text: 'LABEL:Informacja o płatności: Zapłacono' },
{ text: 'LABEL:Data zapłaty: 2025-10-10' },
])
);
});
it('handles case: Platnosc.ZaplataCzesciowa = 1', () => {
const p: Platnosc = { ZaplataCzesciowa: { _text: '1' } };
const result = generatePlatnosc(p);
expect(result).toEqual(
expect.arrayContaining([{ text: 'LABEL:Informacja o płatności: Zapłata częściowa' }])
);
});
it('handles case: not paid', () => {
const p: Platnosc = {};
const result = generatePlatnosc(p);
expect(result).toEqual(expect.arrayContaining([{ text: 'LABEL:Informacja o płatności: Brak zapłaty' }]));
});
it('adds "Forma płatności" with getFormaPlatnosciString when present', () => {
const p: Platnosc = { FormaPlatnosci: { _text: '1' } };
const result = generatePlatnosc(p);
expect(result).toEqual(expect.arrayContaining([{ text: 'LABEL:Forma płatności: Przelew' }]));
});
it('falls back on "Brak zapłaty" and inna/opis if FormaPlatnosci undefined but OpisPlatnosci present', () => {
const p: Platnosc = { OpisPlatnosci: { _text: 'Gotówka przy odbiorze' } };
const result = generatePlatnosc(p);
expect(result).toEqual(
expect.arrayContaining([
{ text: 'LABEL:Informacja o płatności: Brak zapłaty' },
{ text: 'LABEL:Forma płatności: Płatność inna' },
{ text: 'LABEL:Opis płatności innej: Gotówka przy odbiorze' },
])
);
});
it('renders tables for partials and terms if present', () => {
const p: Platnosc = {
PlatnosciCzesciowe: [
{ DataZaplatyCzesciowej: { _text: '2025-01-01' }, KwotaZaplatyCzesciowej: { _text: '100' } },
],
TerminyPlatnosci: [
{ TerminPlatnosci: { _text: '2025-02-01' }, TerminPlatnosciOpis: { _text: 'Do miesiąca' } },
],
};
const result: any = generatePlatnosc(p);
expect(result.some((el: any) => el.type === '2COL')).toBe(true);
});
it('renders Skonto info if present', () => {
const p: Platnosc = { Skonto: { WarunkiSkonta: { _text: '30 dni' }, WysokoscSkonta: { _text: '5%' } } };
const result = generatePlatnosc(p);
expect(result).toEqual(
expect.arrayContaining([
[{ text: 'HEADER:Skonto' }],
{ text: 'LABEL:Warunki skonta: 30 dni' },
{ text: 'LABEL:Wysokość skonta: 5%' },
])
);
});
it('always outputs column with bank accounts', () => {
const p: Platnosc = { RachunekBankowy: [{}], RachunekBankowyFaktora: [{}] };
const result: any = generatePlatnosc(p);
const accCol = result.find(
(el: any) =>
Array.isArray(el.left) &&
Array.isArray(el.right) &&
el.left[0]?.text?.startsWith('ACCOUNT:') &&
el.right[0]?.text?.startsWith('ACCOUNT:')
);
expect(accCol).toBeTruthy();
});
});

View File

@@ -0,0 +1,137 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
generateLine,
generateTwoColumns,
getContentTable,
getTable,
hasValue,
} from '../../../shared/PDF-functions';
import { HeaderDefine } from '../../../shared/types/pdf-types';
import { FP, Platnosc, PlatnosciCzesciowe, TerminyPlatnosci } from '../../types/fa1.types';
import { getFormaPlatnosciString } from '../../../shared/generators/common/functions';
import { generujRachunekBankowy } from './RachunekBankowy';
import FormatTyp from '../../../shared/enums/common.enum';
import { TableWithFields, TerminPlatnosciContent } from '../../types/fa1-additional-types';
export function generatePlatnosc(platnosc: Platnosc | undefined): Content {
if (!platnosc) {
return [];
}
const terminPlatnosci: TerminyPlatnosci[] = getTable(platnosc.TerminyPlatnosci);
const zaplataCzesciowaHeader: HeaderDefine[] = [
{
name: 'TerminPlatnosci',
title: 'Termin płatności',
format: FormatTyp.Default,
},
];
if (terminPlatnosci.some((termin: TerminyPlatnosci): FP | undefined => termin.TerminPlatnosciOpis)) {
zaplataCzesciowaHeader.push({
name: 'TerminPlatnosciOpis',
title: 'Opis płatności',
format: FormatTyp.Default,
});
}
const zaplataCzesciowaNaglowek: HeaderDefine[] = [
{
name: 'DataZaplatyCzesciowej',
title: 'Data zapłaty częściowej',
format: FormatTyp.Default,
},
{ name: 'KwotaZaplatyCzesciowej', title: 'Kwota zapłaty częściowej', format: FormatTyp.Currency },
{ name: 'FormaPlatnosci', title: 'Forma płatności', format: FormatTyp.FormOfPayment },
];
const table: Content[] = [generateLine(), ...createHeader('Płatność')];
if (platnosc.Zaplacono?._text === '1') {
table.push(createLabelText('Informacja o płatności: ', 'Zapłacono'));
table.push(createLabelText('Data zapłaty: ', platnosc.DataZaplaty, FormatTyp.Date));
} else if (platnosc.ZaplataCzesciowa?._text === '1') {
table.push(createLabelText('Informacja o płatności: ', 'Zapłata częściowa'));
} else {
table.push(createLabelText('Informacja o płatności: ', 'Brak zapłaty'));
}
if (hasValue(platnosc.FormaPlatnosci)) {
table.push(createLabelText('Forma płatności: ', getFormaPlatnosciString(platnosc.FormaPlatnosci)));
} else {
if (platnosc.OpisPlatnosci?._text) {
table.push(createLabelText('Forma płatności: ', 'Płatność inna'));
table.push(createLabelText('Opis płatności innej: ', platnosc.OpisPlatnosci));
}
}
const zaplataCzesciowa: PlatnosciCzesciowe[] = getTable(platnosc.PlatnosciCzesciowe);
const tableZaplataCzesciowa: TableWithFields = getContentTable<(typeof zaplataCzesciowa)[0]>(
zaplataCzesciowaNaglowek,
zaplataCzesciowa,
'*'
);
const terminPatnosciContent: (TerminyPlatnosci | TerminPlatnosciContent)[] = terminPlatnosci.map(
(platnosc: TerminyPlatnosci): TerminyPlatnosci | TerminPlatnosciContent => {
if (!terminPlatnosci.some((termin: TerminyPlatnosci): FP | undefined => termin.TerminPlatnosciOpis)) {
return platnosc;
} else {
return {
...platnosc,
TerminPlatnosciOpis: {
_text: `${platnosc.TerminPlatnosciOpis?._text ?? ''}`,
} as any,
};
}
}
);
const tableTerminPlatnosci = getContentTable<(typeof terminPlatnosci)[0]>(
zaplataCzesciowaHeader,
terminPatnosciContent as TerminyPlatnosci[],
'*'
);
if (zaplataCzesciowa.length > 0 && terminPlatnosci.length > 0) {
table.push(
generateTwoColumns(
tableZaplataCzesciowa.content ?? [],
tableTerminPlatnosci.content ?? [],
[0, 4, 0, 0]
)
);
} else if (terminPlatnosci.length > 0) {
if (tableTerminPlatnosci.content) {
table.push(generateTwoColumns([], tableTerminPlatnosci.content));
}
} else if (zaplataCzesciowa.length > 0 && tableZaplataCzesciowa.content) {
table.push(tableZaplataCzesciowa.content);
}
const rachunekBankowy: Content[][] = getTable(platnosc.RachunekBankowy).map((rachunek) =>
generujRachunekBankowy([rachunek], 'Numer rachunku bankowego')
);
const rachunekBankowyFaktora: Content[][] = getTable(platnosc.RachunekBankowyFaktora).map((rachunek) =>
generujRachunekBankowy([rachunek], 'Numer rachunku bankowego faktora')
);
const rachunkiBankowe: Content[][] = [...rachunekBankowy, ...rachunekBankowyFaktora];
if (rachunkiBankowe.length > 0) {
rachunkiBankowe.forEach((rachunek, index) => {
if (index % 2 === 0) {
table.push(generateTwoColumns(rachunek, rachunkiBankowe[index + 1] ?? []));
}
});
}
if (platnosc.Skonto) {
table.push(createHeader('Skonto', [0, 0]));
table.push(createLabelText('Warunki skonta: ', platnosc.Skonto.WarunkiSkonta));
table.push(createLabelText('Wysokość skonta: ', platnosc.Skonto.WysokoscSkonta));
}
return table;
}

View File

@@ -0,0 +1,94 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Podmiot1 } from '../../types/fa1.types';
import { generatePodmiot1 } from './Podmiot1';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label: string) => [{ text: `HEADER:${label}` }]),
createLabelText: vi.fn((label: string, value: any) => ({
text: `LABEL:${label}${typeof value === 'object' && value?._text ? value._text : value}`,
})),
formatText: vi.fn((text: string, _args?: any) => ({ text: `FMT:${text}` })),
getTable: vi.fn((data) => data || []),
getValue: vi.fn((val) => val?._text || ''),
hasValue: vi.fn((val) => Boolean(val && val._text)),
}));
vi.mock('./PodmiotAdres', () => ({
generatePodmiotAdres: vi.fn((adres: any, label: string) => ({ adr: `${label}` })),
}));
vi.mock('./PodmiotDaneIdentyfikacyjne', () => ({
generateDaneIdentyfikacyjne: vi.fn((daneId: any) => [{ id: 'ID' }]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn((email: any, tel: any) => [{ contact: 'KONTAKT' }]),
}));
describe('generatePodmiot1', () => {
beforeEach(() => vi.clearAllMocks());
it('renders header and base labels', () => {
const podmiot1: Podmiot1 = {
NrEORI: { _text: 'EORI123' },
PrefiksPodatnika: { _text: 'PL' },
};
const result = generatePodmiot1(podmiot1);
expect(result).toEqual(
expect.arrayContaining([
{ text: 'HEADER:Sprzedawca' },
{ text: 'LABEL:Numer EORI: EORI123' },
{ text: 'LABEL:Prefiks VAT: PL' },
])
);
});
it('calls generateDaneIdentyfikacyjne when present', () => {
const podmiot1: Podmiot1 = {
DaneIdentyfikacyjne: { NIP: { _text: '1234' } },
NrEORI: { _text: 'xxx' },
PrefiksPodatnika: { _text: 'PL' },
};
const result = generatePodmiot1(podmiot1);
expect(result).toEqual(expect.arrayContaining([{ id: 'ID' }]));
});
it('renders PodmiotAdres if adres present', () => {
const podmiot1: Podmiot1 = {
NrEORI: { _text: 'xxx' },
PrefiksPodatnika: { _text: 'PL' },
Adres: { AdresPol: { Miasto: { _text: 'Katowice' } } },
};
const result = generatePodmiot1(podmiot1);
expect(result).toEqual(expect.arrayContaining([{ adr: 'Adres' }]));
});
it('renders contact section and status if Email/Telefon present', () => {
const podmiot1: Podmiot1 = {
NrEORI: { _text: 'xxx' },
PrefiksPodatnika: { _text: 'PL' },
Email: { _text: 'a@b.pl' },
StatusInfoPodatnika: { _text: '2' },
};
const result = generatePodmiot1(podmiot1);
expect(result).toEqual(
expect.arrayContaining([
{ text: 'FMT:Dane kontaktowe' },
{ contact: 'KONTAKT' },
{ text: 'LABEL:Status podatnika: Postępowanie restrukturyzacyjne' },
])
);
});
it('renders only status if no Email/Telefon but StatusInfoPodatnika present', () => {
const podmiot1: Podmiot1 = {
NrEORI: { _text: 'xxx' },
PrefiksPodatnika: { _text: 'PL' },
StatusInfoPodatnika: { _text: '1' },
};
const result = generatePodmiot1(podmiot1);
expect(result).toEqual(expect.arrayContaining([{ text: 'LABEL:Status podatnika: Stan likwidacji' }]));
});
});

View File

@@ -0,0 +1,48 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
formatText,
getTable,
getValue,
hasValue,
} from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { Podmiot1 } from '../../types/fa1.types';
import { generatePodmiotAdres } from './PodmiotAdres';
import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
import { TAXPAYER_STATUS } from '../../../shared/consts/const';
export function generatePodmiot1(podmiot1: Podmiot1): Content[] {
const result: Content[] = createHeader('Sprzedawca');
result.push(
createLabelText('Numer EORI: ', podmiot1.NrEORI),
createLabelText('Prefiks VAT: ', podmiot1.PrefiksPodatnika)
);
if (podmiot1.DaneIdentyfikacyjne) {
result.push(...generateDaneIdentyfikacyjne(podmiot1.DaneIdentyfikacyjne));
}
if (podmiot1.Adres) {
result.push(generatePodmiotAdres(podmiot1.Adres, 'Adres', true, [0, 12, 0, 1.3]));
}
if (podmiot1.AdresKoresp) {
result.push(
...generatePodmiotAdres(podmiot1.AdresKoresp, 'Adres do korespondencji', true, [0, 12, 0, 1.3])
);
}
if (podmiot1.Email || podmiot1.Telefon) {
result.push(
formatText('Dane kontaktowe', [FormatTyp.Label, FormatTyp.LabelMargin]),
...generateDaneKontaktowe(podmiot1.Email, getTable(podmiot1.Telefon))
);
}
if (hasValue(podmiot1.StatusInfoPodatnika)) {
const statusInfo: string = TAXPAYER_STATUS[getValue(podmiot1.StatusInfoPodatnika)!];
result.push(createLabelText('Status podatnika: ', statusInfo));
}
return result;
}

View File

@@ -0,0 +1,67 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Podmiot1, Podmiot1K } from '../../types/fa1.types';
import { generatePodmiot1Podmiot1K } from './Podmiot1Podmiot1K';
import { Content } from 'pdfmake/interfaces';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label: string) => [{ text: `HEADER:${label}` }]),
createLabelText: vi.fn((label: string, value: any) => ({
text: `LABEL:${label}${value && value._text ? value._text : value}`,
})),
createSubHeader: vi.fn((label: string) => ({ text: `SUBHEADER:${label}` })),
verticalSpacing: vi.fn((v: number) => ({ text: `SPACING:${v}` })),
getTable: vi.fn((data) => data || []),
generateColumns: vi.fn((left, right) => ({ columns: [left, right] })),
getValue: vi.fn(() => '1'),
}));
vi.mock('./PodmiotAdres', () => ({
generatePodmiotAdres: vi.fn((adres: any, label: string) => ({ adr: label })),
}));
vi.mock('./PodmiotDaneIdentyfikacyjne', () => ({
generateDaneIdentyfikacyjne: vi.fn(() => [{ id: 'ID' }]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn(() => ({ contact: 'KONTAKT' })),
}));
describe('generatePodmiot1Podmiot1K', () => {
beforeEach(() => vi.clearAllMocks());
it('puts DaneIdentyfikacyjne & status in firstColumn', () => {
const podmiot1: Podmiot1 = {
NrEORI: { _text: 'EORI' },
DaneIdentyfikacyjne: { NIP: { _text: '777' } },
StatusInfoPodatnika: { _text: '1' },
};
const podmiot1K: Podmiot1K = {};
const result: any = generatePodmiot1Podmiot1K(podmiot1, podmiot1K);
const firstRow: Content = result[1];
expect(firstRow).toEqual([
{ text: 'SUBHEADER:Dane identyfikacyjne' },
{ text: 'LABEL:Numer EORI: EORI' },
{ id: 'ID' },
{ text: `LABEL:Status podatnika: Stan likwidacji` },
]);
});
it('adds contact if Email present', () => {
const podmiot1: Podmiot1 = {
NrEORI: { _text: 'EORI' },
Email: { _text: 'mail@ex.pl' },
};
const podmiot1K: Podmiot1K = {};
const result: any = generatePodmiot1Podmiot1K(podmiot1, podmiot1K);
const firstRow: Content = result[1];
expect(firstRow).toEqual(expect.arrayContaining([{ contact: 'KONTAKT' }]));
});
it('adds verticalSpacing at the end', () => {
const podmiot1: Podmiot1 = { NrEORI: { _text: 'EORI' } };
const podmiot1K: Podmiot1K = {};
const result: Content[] = generatePodmiot1Podmiot1K(podmiot1, podmiot1K);
expect(result[result.length - 1]).toEqual({ text: 'SPACING:1' });
});
});

View File

@@ -0,0 +1,70 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
createSubHeader,
generateColumns,
getTable,
getValue,
verticalSpacing,
} from '../../../shared/PDF-functions';
import { Podmiot1, Podmiot1K } from '../../types/fa1.types';
import { generatePodmiotAdres } from './PodmiotAdres';
import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
import { TAXPAYER_STATUS } from '../../../shared/consts/const';
export function generatePodmiot1Podmiot1K(podmiot1: Podmiot1, podmiot1K: Podmiot1K): Content[] {
const result: Content[] = createHeader('Sprzedawca');
let firstColumn: Content[] = [];
let secondColumn: Content[] = [];
firstColumn.push(createSubHeader('Dane identyfikacyjne'), createLabelText('Numer EORI: ', podmiot1.NrEORI));
if (podmiot1.DaneIdentyfikacyjne) {
firstColumn.push(...generateDaneIdentyfikacyjne(podmiot1.DaneIdentyfikacyjne));
}
if (podmiot1.Email || podmiot1.Telefon) {
firstColumn.push(generateDaneKontaktowe(podmiot1.Email, getTable(podmiot1.Telefon)));
}
if (podmiot1.StatusInfoPodatnika) {
const statusInfo: string = TAXPAYER_STATUS[getValue(podmiot1.StatusInfoPodatnika)!];
firstColumn.push(createLabelText('Status podatnika: ', statusInfo));
}
if (firstColumn.length) {
result.push(firstColumn);
}
firstColumn = generateCorrectedContent(podmiot1K, 'Treść korygowana');
secondColumn = generateCorrectedContent(podmiot1, 'Treść korygująca');
if (podmiot1.AdresKoresp) {
secondColumn.push(
generatePodmiotAdres(podmiot1.AdresKoresp, 'Adres do korespondencji', true, [0, 12, 0, 1.3])
);
}
if (firstColumn.length || secondColumn.length) {
result.push(generateColumns([firstColumn, secondColumn]));
}
if (result.length) {
result.push(verticalSpacing(1));
}
return result;
}
export function generateCorrectedContent(podmiot: Podmiot1 | Podmiot1K, headerText: string): Content[] {
const result: Content[] = [];
result.push(createSubHeader(headerText));
if (podmiot.PrefiksPodatnika?._text) {
result.push(createLabelText('Prefiks VAT: ', podmiot.PrefiksPodatnika));
}
if (podmiot.DaneIdentyfikacyjne) {
result.push(...generateDaneIdentyfikacyjne(podmiot.DaneIdentyfikacyjne));
}
if (podmiot.Adres) {
result.push(generatePodmiotAdres(podmiot.Adres, 'Adres', true, [0, 12, 0, 1.3]));
}
return result;
}

View File

@@ -0,0 +1,68 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Podmiot2 } from '../../types/fa1.types';
import { generatePodmiot2 } from './Podmiot2';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label: string) => [{ text: `HEADER:${label}` }]),
createLabelText: vi.fn((label: string, value: any) => ({
text: `LABEL:${label}${typeof value === 'object' && value?._text ? value._text : value}`,
})),
formatText: vi.fn((text: string, _args?: any) => ({ text: `FMT:${text}` })),
getTable: vi.fn((data) => data || []),
getValue: vi.fn((val) => val?._text || ''),
hasValue: vi.fn((val) => Boolean(val && val._text)),
}));
vi.mock('./PodmiotAdres', () => ({
generatePodmiotAdres: vi.fn((adres: any, label: string) => ({ adr: label })),
}));
vi.mock('./PodmiotDaneIdentyfikacyjne', () => ({
generateDaneIdentyfikacyjne: vi.fn(() => [{ id: 'ID' }]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn(() => [{ contact: 'KONTAKT' }]),
}));
describe('generatePodmiot2', () => {
beforeEach(() => vi.clearAllMocks());
it('renders header and base fields', () => {
const podmiot2: Podmiot2 = {
NrEORI: { _text: 'EORI777' },
};
const result = generatePodmiot2(podmiot2);
expect(result).toEqual(
expect.arrayContaining([{ text: 'HEADER:Nabywca' }, { text: 'LABEL:Numer EORI: EORI777' }])
);
});
it('adds PrefiksNabywcy if hasValue true', () => {
const podmiot2: Podmiot2 = {
NrEORI: { _text: 'EORI1' },
PrefiksNabywcy: { _text: 'PN1' },
};
const result = generatePodmiot2(podmiot2);
expect(result).toEqual(expect.arrayContaining([{ text: 'LABEL:Prefiks VAT: PN1' }]));
});
it('includes contact info when Email or Telefon present', () => {
const podmiot2: Podmiot2 = {
NrEORI: { _text: 'EORI4' },
Email: { _text: 'test@mail.com' },
};
const result = generatePodmiot2(podmiot2);
expect(result).toEqual(expect.arrayContaining([{ text: 'FMT:Dane kontaktowe' }, { contact: 'KONTAKT' }]));
});
it('includes NrKlienta label if present', () => {
const podmiot2: Podmiot2 = {
NrEORI: { _text: 'EORI5' },
NrKlienta: { _text: 'NR1234' },
};
const result = generatePodmiot2(podmiot2);
expect(result).toEqual(expect.arrayContaining([{ text: 'LABEL:Numer klienta: NR1234' }]));
});
});

View File

@@ -0,0 +1,54 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
formatText,
getTable,
getValue,
hasValue,
} from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { Podmiot2 } from '../../types/fa1.types';
import { generatePodmiotAdres } from './PodmiotAdres';
import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
import { DaneIdentyfikacyjneTPodmiot2Dto } from '../../types/fa2-additional-types';
export function generatePodmiot2(podmiot2: Podmiot2): Content[] {
const result: Content[] = createHeader('Nabywca');
result.push(createLabelText('Numer EORI: ', podmiot2.NrEORI));
if (hasValue(podmiot2.PrefiksNabywcy)) {
result.push(createLabelText('Prefiks VAT: ', podmiot2.PrefiksNabywcy));
}
if (podmiot2.DaneIdentyfikacyjne) {
if (hasValue(podmiot2.DaneIdentyfikacyjne.NrID)) {
result.push(createLabelText('Identyfikator podatkowy inny: ', podmiot2.DaneIdentyfikacyjne.NrID));
}
if (getValue(podmiot2.DaneIdentyfikacyjne.BrakID) === '1') {
result.push(createLabelText('Brak identyfikatora ', ' '));
}
result.push(
...generateDaneIdentyfikacyjne(podmiot2.DaneIdentyfikacyjne as DaneIdentyfikacyjneTPodmiot2Dto)
);
}
if (podmiot2.Adres) {
result.push(generatePodmiotAdres(podmiot2.Adres, 'Adres', true, [0, 12, 0, 1.3]));
}
if (podmiot2.AdresKoresp) {
result.push(
...generatePodmiotAdres(podmiot2.AdresKoresp, 'Adres do korespondencji', true, [0, 12, 0, 1.3])
);
}
if (podmiot2.Email || podmiot2.Telefon) {
result.push(
formatText('Dane kontaktowe', [FormatTyp.Label, FormatTyp.LabelMargin]),
...generateDaneKontaktowe(podmiot2.Email, getTable(podmiot2.Telefon))
);
}
if (podmiot2.NrKlienta) {
result.push(createLabelText('Numer klienta: ', podmiot2.NrKlienta));
}
return result;
}

View File

@@ -0,0 +1,107 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Podmiot2, Podmiot2K } from '../../types/fa1.types';
import { generatePodmiot2Podmiot2K } from './Podmiot2Podmiot2k';
import { Content } from 'pdfmake/interfaces';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label) => [{ text: `HEADER:${label}` }]),
createLabelText: vi.fn((label, value) => ({
text: `LABEL:${label}${value && value._text ? value._text : ''}`,
})),
createSubHeader: vi.fn((label) => ({ text: `SUBHEADER:${label}` })),
verticalSpacing: vi.fn((num) => ({ text: `SPACING:${num}` })),
getTable: vi.fn((arr) => arr || []),
getValue: vi.fn((val) => (val && val._text ? val._text : '')),
hasValue: vi.fn((val) => Boolean(val && val._text)),
generateColumns: vi.fn((left, right) => ({ columns: [left, right] })),
generateLine: vi.fn((): Content[] => [{ line: true } as any]),
}));
vi.mock('./PodmiotAdres', () => ({
generatePodmiotAdres: vi.fn((adres, label) => ({ adr: label })),
}));
vi.mock('./PodmiotDaneIdentyfikacyjne', () => ({
generateDaneIdentyfikacyjne: vi.fn(() => [{ id: 'ID' }]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn(() => ({ contact: 'KONTAKT' })),
}));
describe('generatePodmiot2Podmiot2K', () => {
beforeEach(() => vi.clearAllMocks());
function hasColumns(r: unknown): r is { columns: unknown[] } {
return (
typeof r === 'object' &&
r !== null &&
'columns' in r &&
Array.isArray((r as { columns: unknown[] }).columns)
);
}
it('renders new line and at least one columns object', () => {
const podmiot2: Podmiot2 = { NrEORI: { _text: 'A' } };
const podmiot2K: Podmiot2K = {};
const result: Content[] = generatePodmiot2Podmiot2K(podmiot2, podmiot2K);
expect(result[0]).toEqual([{
"line": true,
}]);
expect(
result.some((r: Content) => {
if (hasColumns(r)) {
return Array.isArray(r.columns);
}
return false;
})
).toBeTruthy();
});
it('builds firstColumn with full data', () => {
const podmiot2: Podmiot2 = {
NrEORI: { _text: 'EORI-X' },
DaneIdentyfikacyjne: { NrID: { _text: 'FOO' } },
Email: { _text: 'xx@a.pl' },
NrKlienta: { _text: 'CUSTX' },
Telefon: [{ _text: '600100200' }],
};
const podmiot2K: Podmiot2K = {};
const result: any = generatePodmiot2Podmiot2K(podmiot2, podmiot2K);
const firstCol: Content = result.find(hasColumns)?.columns[0];
expect(firstCol).toEqual(
expect.arrayContaining([
{ text: 'SUBHEADER:Dane identyfikacyjne' },
{ text: 'LABEL:Numer EORI: EORI-X' },
{ id: 'ID' },
{ contact: 'KONTAKT' },
{ text: 'LABEL:Numer klienta: CUSTX' },
])
);
});
it('renders "corrected content" for both cols', () => {
const podmiot2: Podmiot2 = {
PrefiksNabywcy: { _text: 'PN2' },
DaneIdentyfikacyjne: { BrakID: { _text: '1' }, NrID: { _text: '123' } },
Adres: { AdresPol: { Miasto: { _text: 'CITY' } } },
};
const podmiot2K: Podmiot2K = {
PrefiksNabywcy: { _text: 'NNK' },
DaneIdentyfikacyjne: { NrID: { _text: 'XYZ' } },
Adres: { AdresZagr: { Kraj: { _text: 'UK' } } },
};
const result: any = generatePodmiot2Podmiot2K(podmiot2, podmiot2K);
const cols: Content[] = result.find(hasColumns)?.columns;
expect(cols[1]).toEqual([]);
});
it('ends with verticalSpacing', () => {
const podmiot2: Podmiot2 = { NrEORI: { _text: 'END' } };
const podmiot2K: Podmiot2K = {};
const result: Content[] = generatePodmiot2Podmiot2K(podmiot2, podmiot2K);
expect(result[result.length - 1]).toEqual({ text: 'SPACING:1' });
});
});

View File

@@ -0,0 +1,84 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
createSubHeader,
generateColumns,
generateLine,
getTable,
getValue,
hasValue,
verticalSpacing,
} from '../../../shared/PDF-functions';
import { Podmiot2, Podmiot2K } from '../../types/fa1.types';
import { generatePodmiotAdres } from './PodmiotAdres';
import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
export function generatePodmiot2Podmiot2K(podmiot2: Podmiot2, podmiot2K: Podmiot2K): Content[] {
const result: Content[] = [];
result.push(generateLine());
result.push(createHeader('Nabywca'));
let firstColumn: Content[] = [];
let secondColumn: Content[] = [];
firstColumn.push(createSubHeader('Dane identyfikacyjne'), createLabelText('Numer EORI: ', podmiot2.NrEORI));
if (podmiot2.DaneIdentyfikacyjne) {
firstColumn.push(...generateDaneIdentyfikacyjne(podmiot2.DaneIdentyfikacyjne));
}
if (podmiot2.Email || podmiot2.Telefon) {
firstColumn.push(generateDaneKontaktowe(podmiot2.Email, getTable(podmiot2.Telefon)));
}
if (podmiot2.NrKlienta) {
firstColumn.push(createLabelText('Numer klienta: ', podmiot2.NrKlienta));
}
if (firstColumn.length) {
result.push({
columns: [firstColumn, []],
columnGap: 20,
});
}
if (podmiot2K.DaneIdentyfikacyjne) {
firstColumn = generateCorrectedContent(podmiot2K, 'Treść korygowana');
secondColumn = generateCorrectedContent(podmiot2, 'Treść korygująca');
}
if (podmiot2.AdresKoresp) {
secondColumn.push(
generatePodmiotAdres(podmiot2.AdresKoresp, 'Adres do korespondencji', true, [0, 12, 0, 1.3])
);
}
if (firstColumn.length || secondColumn.length) {
result.push(generateColumns([firstColumn, secondColumn]));
}
if (result.length) {
result.push(verticalSpacing(1));
}
return result;
}
export function generateCorrectedContent(podmiot: Podmiot2 | Podmiot2K, headerText: string): Content[] {
const result: Content[] = [];
result.push(createSubHeader(headerText));
if (hasValue(podmiot.PrefiksNabywcy)) {
result.push(createLabelText('Prefiks VAT: ', podmiot.PrefiksNabywcy));
}
if (podmiot.DaneIdentyfikacyjne) {
if (hasValue(podmiot.DaneIdentyfikacyjne.NrID)) {
result.push(createLabelText('Identyfikator podatkowy inny: ', podmiot.DaneIdentyfikacyjne.NrID));
}
if (getValue(podmiot.DaneIdentyfikacyjne.BrakID) === '1') {
result.push(createLabelText('Brak identyfikatora ', ' '));
}
result.push(...generateDaneIdentyfikacyjne(podmiot.DaneIdentyfikacyjne));
}
if (podmiot.Adres) {
result.push(generatePodmiotAdres(podmiot.Adres, 'Adres', true, [0, 12, 0, 1.3]));
}
return result;
}

View File

@@ -0,0 +1,95 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Podmiot3 } from '../../types/fa1.types';
import { generatePodmiot3 } from './Podmiot3';
import { Content } from 'pdfmake/interfaces';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label) => [{ text: `HEADER:${label}` }]),
createLabelText: vi.fn((label, value) => ({
text: `LABEL:${label}${value && value._text ? value._text : (value ?? '')}`,
})),
createSection: vi.fn((arr) => arr),
formatText: vi.fn((txt, _args) => ({ text: `FMT:${txt}` })),
generateTwoColumns: vi.fn((left, right) => ({ type: '2COL', left, right })),
getTable: vi.fn((arr) => arr || []),
getValue: vi.fn((val) => (val && val._text ? val._text : '')),
hasValue: vi.fn((val) => !!(val && val._text)),
}));
vi.mock('../../../shared/generators/common/functions', () => ({
getRolaString: vi.fn((rola, idx) => (rola && rola._text ? 'SPRZEDAWCA' : '')),
}));
vi.mock('./PodmiotAdres', () => ({
generatePodmiotAdres: vi.fn((adres, label) => ({ adr: label })),
}));
vi.mock('./PodmiotDaneIdentyfikacyjne', () => ({
generateDaneIdentyfikacyjne: vi.fn(() => [{ id: 'ID' }]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn(() => [{ contact: 'KONTAKT' }]),
}));
describe('generatePodmiot3', () => {
beforeEach(() => vi.clearAllMocks());
it('renders minimal column structure', () => {
const podmiot: Podmiot3 = { NrEORI: { _text: '999' }, Rola: { _text: 'X' } };
const result: Content[] = generatePodmiot3(podmiot, 0);
const last: any = result[result.length - 1] || {};
expect(last.type).toBe('2COL');
expect(last.left).toEqual(
expect.arrayContaining([
{ text: 'HEADER:Podmiot inny 1' },
{ text: 'LABEL:Numer EORI: 999' },
[{ text: 'LABEL:Rola: SPRZEDAWCA' }, { text: 'LABEL:Rola inna: ' }, { text: 'LABEL:Udział: ' }],
])
);
});
it('renders column1 with all tax ID and role info', () => {
const podmiot: Podmiot3 = {
NrEORI: { _text: '1000' },
DaneIdentyfikacyjne: { NrID: { _text: 'TAXX' }, BrakID: { _text: '1' } },
Rola: { _text: 'S' },
OpisRoli: { _text: 'operator' },
Udzial: { _text: '50%' },
};
const result: any = generatePodmiot3(podmiot, 1);
const col1: Content = result[result.length - 1].left;
expect(col1).toEqual(
expect.arrayContaining([
{ text: 'HEADER:Podmiot inny 2' },
{ text: 'LABEL:Numer EORI: 1000' },
{ text: 'LABEL:Identyfikator podatkowy inny: TAXX' },
{ text: 'LABEL:Brak identyfikatora ' },
{ id: 'ID' },
[
{ text: 'LABEL:Rola: SPRZEDAWCA' },
{ text: 'LABEL:Rola inna: operator' },
{ text: 'LABEL:Udział: 50%' },
],
])
);
});
it('adds Numer klienta in right column if present', () => {
const podmiot: Podmiot3 = {
NrEORI: { _text: 'foo' },
Rola: { _text: 'ROLA' },
NrKlienta: { _text: 'KLIENTX' },
};
const result: any = generatePodmiot3(podmiot, 2);
const col2: Content = result[result.length - 1].right;
expect(col2).toEqual(expect.arrayContaining([{ text: 'LABEL:Numer klienta: KLIENTX' }]));
});
it('always returns results wrapped in sections', () => {
const podmiot: Podmiot3 = { NrEORI: { _text: 'A' }, Rola: { _text: 'B' } };
const result: any = generatePodmiot3(podmiot, 1);
expect(Array.isArray(result)).toBe(true);
expect(result.some((r: any) => r.type === '2COL')).toBeTruthy();
});
});

View File

@@ -0,0 +1,62 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
createSection,
formatText,
generateTwoColumns,
getTable,
getValue,
hasValue,
} from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { Podmiot3 } from '../../types/fa1.types';
import { getRolaString } from '../../../shared/generators/common/functions';
import { generatePodmiotAdres } from './PodmiotAdres';
import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
export function generatePodmiot3(podmiot: Podmiot3, index: number): Content[] {
const result: Content[] = [];
const column1: Content[] = [
...createHeader(`Podmiot inny ${index + 1}`),
createLabelText('Numer EORI: ', podmiot.NrEORI),
];
if (hasValue(podmiot.DaneIdentyfikacyjne?.NrID)) {
column1.push(createLabelText('Identyfikator podatkowy inny: ', podmiot.DaneIdentyfikacyjne?.NrID));
}
if (getValue(podmiot.DaneIdentyfikacyjne?.BrakID) === '1') {
column1.push(createLabelText('Brak identyfikatora ', ' '));
}
if (podmiot.DaneIdentyfikacyjne) {
column1.push(...generateDaneIdentyfikacyjne(podmiot.DaneIdentyfikacyjne));
}
column1.push([
createLabelText('Rola: ', getRolaString(podmiot.Rola, 1)),
createLabelText('Rola inna: ', podmiot.OpisRoli),
createLabelText('Udział: ', podmiot.Udzial, [FormatTyp.Percentage]),
]);
const column2: Content[] = [];
if (podmiot.Adres) {
column2.push(generatePodmiotAdres(podmiot.Adres, 'Adres', true, [0, 12, 0, 1.3]));
}
if (podmiot.AdresKoresp) {
column2.push(
...generatePodmiotAdres(podmiot.AdresKoresp, 'Adres do korespondencji', true, [0, 12, 0, 1.3])
);
}
if (podmiot.Email || podmiot.Telefon) {
column2.push(
formatText('Dane kontaktowe', [FormatTyp.Label, FormatTyp.LabelMargin]),
...generateDaneKontaktowe(podmiot.Email, getTable(podmiot.Telefon))
);
}
if (podmiot.NrKlienta) {
column2.push(createLabelText('Numer klienta: ', podmiot.NrKlienta));
}
result.push(generateTwoColumns(column1, column2));
return createSection(result, true);
}

View File

@@ -0,0 +1,83 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generateDaneIdentyfikacyjneTPodmiot3Dto } from './Podmiot3Podmiot2k';
import { Podmiot3Podmiot2KDto } from '../../types/fa1-additional-types';
import { Content } from 'pdfmake/interfaces';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label) => [{ text: `HEADER:${label}` }]),
createLabelText: vi.fn((label, value) => ({
text: `LABEL:${label}${value && value._text ? value._text : (value ?? '')}`,
})),
createSubHeader: vi.fn((label) => [{ text: `SUBHEADER:${label}` }]),
generateTwoColumns: vi.fn((left, right) => ({ type: '2COL', left, right })),
getTable: vi.fn((arr) => arr || []),
getValue: vi.fn((val) => (val && val._text ? val._text : '')),
hasValue: vi.fn((val) => !!(val && val._text)),
}));
vi.mock('../../../shared/generators/common/functions', () => ({
getRolaString: vi.fn((rola) => (rola && rola._text ? 'SPRZEDAWCA' : '')),
}));
vi.mock('./PodmiotAdres', () => ({
generatePodmiotAdres: vi.fn((adres, label) => ({ adr: label })),
}));
vi.mock('./PodmiotDaneIdentyfikacyjne', () => ({
generateDaneIdentyfikacyjne: vi.fn(() => [{ id: 'ID' }]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn(() => ({ contact: 'KONTAKT' })),
}));
describe('generateDaneIdentyfikacyjneTPodmiot3Dto', () => {
beforeEach(() => vi.clearAllMocks());
it('returns empty array for undefined input', () => {
expect(generateDaneIdentyfikacyjneTPodmiot3Dto(undefined, 0)).toEqual([]);
});
it('creates header and main fields for full identification', () => {
const podmiot2KDto: Podmiot3Podmiot2KDto = {
fakturaPodmiotNDto: {
NrEORI: { _text: 'EXX' },
Rola: { _text: 'SPRZEDAWCA' },
OpisRoli: { _text: 'rola inna' },
Udzial: { _text: '51%' },
Email: { _text: 'a@b.pl' },
NrKlienta: { _text: 'KLIENTX' },
DaneIdentyfikacyjne: { NrID: { _text: 'TID' } },
Adres: { AdresPol: { Miasto: { _text: 'Warsaw' } } },
AdresKoresp: { AdresPol: { Miasto: { _text: 'Gdansk' } } },
},
podmiot2KDto: {
DaneIdentyfikacyjne: { NrID: { _text: 'IDK' } },
Adres: { AdresZagr: { Kraj: { _text: 'PL' } } },
},
};
const result = generateDaneIdentyfikacyjneTPodmiot3Dto(podmiot2KDto, 1);
expect(result).toEqual(
expect.arrayContaining([
{ text: 'HEADER:Podmiot inny 2' },
{ text: 'SUBHEADER:Dane identyfikacyjne' },
{ text: 'LABEL:Numer EORI: EXX' },
{ text: 'LABEL:Rola: SPRZEDAWCA' },
{ text: 'LABEL:Rola inna: rola inna' },
{ text: 'LABEL:Udział: 51%' },
{ contact: 'KONTAKT' },
{ text: 'LABEL:Numer klienta: KLIENTX' },
])
);
});
it('renders Adres korespondencyjny last in right column', () => {
const podmiot2KDto: Podmiot3Podmiot2KDto = {
fakturaPodmiotNDto: {
AdresKoresp: { AdresPol: { Miasto: { _text: 'Gdynia' } } },
},
podmiot2KDto: {},
};
const result: Content[] = generateDaneIdentyfikacyjneTPodmiot3Dto(podmiot2KDto, 2);
const twoCol: any = result.find((r: any): boolean => r.type === '2COL');
expect(twoCol.right[twoCol.right.length - 1]).toEqual({ adr: 'Adres korespondencyjny' });
});
});

View File

@@ -0,0 +1,87 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
createSubHeader,
generateTwoColumns,
getTable,
getValue,
hasValue,
} from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { Podmiot3Podmiot2KDto } from '../../types/fa1-additional-types';
import { getRolaString } from '../../../shared/generators/common/functions';
import { generatePodmiotAdres } from './PodmiotAdres';
import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
import { Podmiot2K, Podmiot3 } from '../../types/fa1.types';
export function generateDaneIdentyfikacyjneTPodmiot3Dto(
podmiot2KDto: Podmiot3Podmiot2KDto | undefined,
index: number
): Content[] {
if (!podmiot2KDto) {
return [];
}
const podmiot1: Podmiot3 = podmiot2KDto.fakturaPodmiotNDto;
const podmiot1K: Podmiot2K | undefined = podmiot2KDto.podmiot2KDto;
const result: Content[] = createHeader(`Podmiot inny ${index + 1}`);
if (
hasValue(podmiot1.NrEORI) ||
hasValue(podmiot1.Rola) ||
hasValue(podmiot1.OpisRoli) ||
hasValue(podmiot1?.Udzial)
) {
result.push(
...createSubHeader('Dane identyfikacyjne'),
createLabelText('Numer EORI: ', podmiot1.NrEORI),
createLabelText('Rola: ', getRolaString(podmiot1.Rola, 1)),
createLabelText('Rola inna: ', podmiot1.OpisRoli),
createLabelText('Udział: ', podmiot1.Udzial, FormatTyp.Percentage)
);
}
if (podmiot1.Email || podmiot1.Telefon) {
result.push(generateDaneKontaktowe(podmiot1.Email, getTable(podmiot1.Telefon)));
}
if (hasValue(podmiot1.NrKlienta)) {
result.push(createLabelText('Numer klienta: ', podmiot1.NrKlienta));
}
const columns1: Content[] = [...createSubHeader('Treść korygowana')];
if (hasValue(podmiot1K?.DaneIdentyfikacyjne?.NrID)) {
columns1.push(createLabelText('Identyfikator podatkowy inny: ', podmiot1K?.DaneIdentyfikacyjne?.NrID));
}
if (getValue(podmiot1K?.DaneIdentyfikacyjne?.BrakID) === '1') {
columns1.push(createLabelText('Brak identyfikatora ', ' '));
}
if (podmiot1K?.DaneIdentyfikacyjne) {
columns1.push(generateDaneIdentyfikacyjne(podmiot1K.DaneIdentyfikacyjne));
}
if (podmiot1K?.Adres) {
columns1.push(generatePodmiotAdres(podmiot1K.Adres, 'Adres', true));
}
const columns2: Content[] = [...createSubHeader('Treść korygująca')];
if (hasValue(podmiot1.DaneIdentyfikacyjne?.NrID)) {
columns2.push(createLabelText('Identyfikator podatkowy inny: ', podmiot1.DaneIdentyfikacyjne?.NrID));
}
if (getValue(podmiot1.DaneIdentyfikacyjne?.BrakID) === '1') {
columns2.push(createLabelText('Brak identyfikatora ', ' '));
}
if (podmiot1?.DaneIdentyfikacyjne) {
columns2.push(generateDaneIdentyfikacyjne(podmiot1.DaneIdentyfikacyjne));
}
if (podmiot1?.Adres) {
columns2.push(generatePodmiotAdres(podmiot1.Adres, 'Adres', true));
}
if (podmiot1.AdresKoresp != null) {
columns2.push(generatePodmiotAdres(podmiot1.AdresKoresp, 'Adres korespondencyjny', true));
}
result.push(generateTwoColumns(columns1, columns2));
return result;
}

View File

@@ -0,0 +1,48 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { generatePodmiotAdres } from './PodmiotAdres';
import { Adres } from '../../types/fa1.types';
import { Margins } from 'pdfmake/interfaces';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((title, margin) => [{ text: `HEADER:${title}`, margin }]),
createSubHeader: vi.fn((title, margin) => [{ text: `SUBHEADER:${title}`, margin }]),
}));
vi.mock('./Adres', () => ({
generateAdres: vi.fn((adres) => [{ text: `ADRES:${adres?.AdresPol?.Miasto?._text ?? ''}` }]),
}));
describe('generatePodmiotAdres', () => {
beforeEach(() => vi.clearAllMocks());
it('returns empty array for undefined address', () => {
expect(generatePodmiotAdres(undefined)).toEqual([]);
});
it('renders with HEADER when isSubheader is false (default)', () => {
const adres: Adres = { AdresPol: { Miasto: { _text: 'Katowice' } } };
const result = generatePodmiotAdres(adres, 'Adres główny');
expect(result[0]).toEqual({ text: 'HEADER:Adres główny', margin: undefined });
expect(result[1]).toEqual({ text: 'ADRES:Katowice' });
});
it('renders with SUBHEADER and propagates headerMargin', () => {
const adres: Adres = { AdresPol: { Miasto: { _text: 'Wrocław' } } };
const margin: Margins = [2, 4, 6, 8];
const result = generatePodmiotAdres(adres, 'Nagłówek podrzędny', true, margin);
expect(result[0]).toEqual({ text: 'SUBHEADER:Nagłówek podrzędny', margin });
expect(result[1]).toEqual({ text: 'ADRES:Wrocław' });
});
it('returns both header/subheader content and address output', () => {
const adres: Adres = { AdresPol: { Miasto: { _text: 'Warszawa' } } };
const resultHeader = generatePodmiotAdres(adres, 'Adres', false);
const resultSub = generatePodmiotAdres(adres, 'Adres', true);
expect(resultHeader[0]).toEqual({ text: 'HEADER:Adres', margin: undefined });
expect(resultSub[0]).toEqual({ text: 'SUBHEADER:Adres', margin: undefined });
expect(resultHeader[1]).toEqual({ text: 'ADRES:Warszawa' });
expect(resultSub[1]).toEqual({ text: 'ADRES:Warszawa' });
});
});

View File

@@ -0,0 +1,19 @@
import { Content, Margins } from 'pdfmake/interfaces';
import { createHeader, createSubHeader } from '../../../shared/PDF-functions';
import { Adres } from '../../types/fa1.types';
import { generateAdres } from './Adres';
export function generatePodmiotAdres(
podmiotAdres: Adres | undefined,
headerTitle = 'Adres',
isSubheader = false,
headerMargin?: Margins
): Content[] {
if (!podmiotAdres) {
return [];
}
return [
...(isSubheader ? createSubHeader(headerTitle, headerMargin) : createHeader(headerTitle, headerMargin)),
...generateAdres(podmiotAdres),
];
}

View File

@@ -0,0 +1,62 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne';
import { Content } from 'pdfmake/interfaces';
import { DaneIdentyfikacyjneTPodmiot2Dto } from '../../types/fa2-additional-types';
vi.mock('../../../shared/PDF-functions', () => ({
createLabelText: vi.fn((label, value) => ({
text: `LABEL:${label}${typeof value === 'object' && value?._text ? value._text : (value ?? '')}`,
})),
getValue: vi.fn((val) => (val && val._text ? val._text : '')),
hasValue: vi.fn((val) => !!(val && val._text)),
}));
describe('generateDaneIdentyfikacyjne', () => {
beforeEach(() => vi.clearAllMocks());
it('adds NIP label always', () => {
const dane: DaneIdentyfikacyjneTPodmiot2Dto = { NIP: { _text: '1111111111' } };
const result = generateDaneIdentyfikacyjne(dane);
expect(result[0]).toEqual({ text: 'LABEL:NIP: 1111111111' });
});
it('skips name/last name label for empty values', () => {
const dane: DaneIdentyfikacyjneTPodmiot2Dto = { NIP: { _text: 'xx' } };
const result: Content[] = generateDaneIdentyfikacyjne(dane);
expect(result.some((r: any): boolean => r.text === 'LABEL: ')).toBeFalsy();
});
it('adds Pełna nazwa label when present', () => {
const dane: DaneIdentyfikacyjneTPodmiot2Dto = {
NIP: { _text: '99' },
PelnaNazwa: { _text: 'INSTRUMENTS INC.' },
};
const result = generateDaneIdentyfikacyjne(dane);
expect(result).toEqual(expect.arrayContaining([{ text: 'LABEL:Pełna nazwa: INSTRUMENTS INC.' }]));
});
it('adds Nazwa handlowa when surname is present', () => {
const dane: DaneIdentyfikacyjneTPodmiot2Dto = {
NIP: { _text: '43210' },
Nazwisko: { _text: 'Smith' },
NazwaHandlowa: { _text: 'Smithy PLC' },
};
const result = generateDaneIdentyfikacyjne(dane);
expect(result).toEqual(expect.arrayContaining([{ text: 'LABEL:Nazwa handlowa: Smithy PLC' }]));
});
it('returns array including only expected fields', () => {
const dane: DaneIdentyfikacyjneTPodmiot2Dto = {
NIP: { _text: '11' },
PelnaNazwa: { _text: 'Best Corp' },
};
const result = generateDaneIdentyfikacyjne(dane);
expect(result.length).toBe(2);
expect(result).toEqual([{ text: 'LABEL:NIP: 11' }, { text: 'LABEL:Pełna nazwa: Best Corp' }]);
});
});

View File

@@ -0,0 +1,24 @@
import { Content } from 'pdfmake/interfaces';
import { createLabelText, getValue, hasValue } from '../../../shared/PDF-functions';
import { DaneIdentyfikacyjneTPodmiot2Dto } from '../../types/fa2-additional-types';
export function generateDaneIdentyfikacyjne(daneIdentyfikacyjne: DaneIdentyfikacyjneTPodmiot2Dto): Content[] {
const result: Content[] = [];
result.push(createLabelText('NIP: ', daneIdentyfikacyjne.NIP));
if (hasValue(daneIdentyfikacyjne.ImiePierwsze) || hasValue(daneIdentyfikacyjne.Nazwisko)) {
result.push(
createLabelText(
'Imię i nazwisko: ',
`${getValue(daneIdentyfikacyjne.ImiePierwsze)} ${getValue(daneIdentyfikacyjne.Nazwisko)}`
)
);
}
if (daneIdentyfikacyjne.PelnaNazwa) {
result.push(createLabelText('Pełna nazwa: ', daneIdentyfikacyjne.PelnaNazwa));
}
if (daneIdentyfikacyjne.Nazwisko) {
result.push(createLabelText('Nazwa handlowa: ', daneIdentyfikacyjne.NazwaHandlowa));
}
return result;
}

View File

@@ -0,0 +1,49 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
import { FP } from '../../types/fa1.types';
vi.mock('../../../shared/PDF-functions', () => ({
createLabelText: vi.fn((label: string, value: any) => ({
text: `LABEL:${label}${typeof value === 'object' && value?._text ? value._text : (value ?? '')}`,
})),
}));
describe('generateDaneKontaktowe', () => {
beforeEach(() => vi.clearAllMocks());
it('returns empty array for no input', () => {
expect(generateDaneKontaktowe()).toEqual([]);
});
it('returns only email entry when only email is present', () => {
const email: FP = { _text: 'aa@bb.cc' };
const result = generateDaneKontaktowe(email, undefined);
expect(result).toEqual([{ text: 'LABEL:Email: aa@bb.cc' }]);
});
it('returns only tel entry when only single phone is present', () => {
const tel = [{ _text: '123456789' }];
const result = generateDaneKontaktowe(undefined, tel);
expect(result).toEqual([{ text: 'LABEL:Tel.: 123456789\n' }]);
});
it('returns email and all telephones in order', () => {
const email: FP = { _text: 'z@x.io' };
const telo = [{ _text: '101' }, { _text: '202' }];
const result = generateDaneKontaktowe(email, telo);
expect(result[0]).toEqual({ text: 'LABEL:Email: z@x.io' });
expect(result.length).toBe(3);
expect(result[1]).toEqual({ text: 'LABEL:Tel.: 101\n' });
expect(result[2]).toEqual({ text: 'LABEL:Tel.: 202\n' });
});
it('handles multiple telephones', () => {
const tel = [{ _text: '666111222' }, { _text: '555222111' }];
const result = generateDaneKontaktowe(undefined, tel);
expect(result.length).toBe(2);
});
});

View File

@@ -0,0 +1,17 @@
import { Content } from 'pdfmake/interfaces';
import { createLabelText } from '../../../shared/PDF-functions';
import { FP } from '../../types/fa1.types';
export function generateDaneKontaktowe(email?: FP, telefon?: FP[]): Content[] {
const result: Content[] = [];
if (email) {
result.push(createLabelText('Email: ', email));
}
if (telefon) {
telefon.forEach((item) => {
result.push(createLabelText('Tel.: ', `${item._text}\n`));
});
}
return result;
}

View File

@@ -0,0 +1,46 @@
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label) => [{ text: `HEADER:${label}` }]),
createLabelText: vi.fn((label, value) => ({
text: `LABEL:${label}${value && value._text ? value._text : (value ?? '')}`,
})),
formatText: vi.fn((txt, _args) => ({ text: `FMT:${txt}` })),
getTable: vi.fn((arr) => arr || []),
getValue: vi.fn((val) => (val && val._text ? val._text : '')),
hasValue: vi.fn((val) => Boolean(val && val._text)),
generateTwoColumns: vi.fn((left, right) => ({ type: '2COL', left, right })),
}));
vi.mock('../../../shared/generators/common/functions', () => ({
getRolaUpowaznionegoString: vi.fn(() => 'Rola string'),
}));
vi.mock('./PodmiotAdres', () => ({
generatePodmiotAdres: vi.fn((adres, label) => ({ adr: label })),
}));
vi.mock('./PodmiotDaneIdentyfikacyjne', () => ({
generateDaneIdentyfikacyjne: vi.fn(() => [{ id: 'ID' }]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn(() => ({ contact: 'KONTAKT' })),
}));
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generatePodmiotUpowazniony } from './PodmiotUpowazniony';
import { PodmiotUpowazniony } from '../../types/fa1.types';
describe(generatePodmiotUpowazniony.name, () => {
beforeEach(() => vi.clearAllMocks());
it('returns empty if input undefined', () => {
expect(generatePodmiotUpowazniony(undefined)).toEqual([]);
});
it('renders header and basic role and EORI if present', () => {
const podmiot: PodmiotUpowazniony = {
RolaPU: { _text: 'X' },
NrEORI: { _text: 'EORI123' },
};
const res = generatePodmiotUpowazniony(podmiot);
expect(res[0]).toEqual({ text: 'HEADER:Podmiot upoważniony' });
expect(res.some((r: any) => r.type === '2COL')).toBe(true);
});
});

View File

@@ -0,0 +1,56 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
formatText,
generateTwoColumns,
getTable,
getValue,
hasValue,
} from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { PodmiotUpowazniony } from '../../types/fa1.types';
import { generatePodmiotAdres } from './PodmiotAdres';
import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
import { getRolaUpowaznionegoString } from '../../../shared/generators/common/functions';
export function generatePodmiotUpowazniony(podmiot: PodmiotUpowazniony | undefined): Content[] {
if (!podmiot) {
return [];
}
const result: Content[] = createHeader('Podmiot upoważniony');
const columnLeft: Content[] = [];
const columnRight: Content[] = [];
if (hasValue(podmiot.RolaPU)) {
columnLeft.push(createLabelText('Rola: ', getRolaUpowaznionegoString(podmiot.RolaPU, 1)));
}
if (hasValue(podmiot.NrEORI)) {
columnLeft.push(createLabelText('Numer EORI: ', podmiot.NrEORI));
}
if (podmiot.DaneIdentyfikacyjne) {
if (hasValue(podmiot.DaneIdentyfikacyjne.NrID)) {
columnLeft.push(createLabelText('Identyfikator podatkowy inny: ', podmiot.DaneIdentyfikacyjne.NrID));
}
if (getValue(podmiot.DaneIdentyfikacyjne.BrakID) === '1') {
columnLeft.push(createLabelText('Brak identyfikatora ', ' '));
}
columnLeft.push(generateDaneIdentyfikacyjne(podmiot.DaneIdentyfikacyjne));
}
if (podmiot.Adres) {
columnRight.push(generatePodmiotAdres(podmiot.Adres, 'Adres', true));
}
if (podmiot.AdresKoresp) {
columnRight.push(generatePodmiotAdres(podmiot.AdresKoresp, 'Adres do korespondencji', true));
}
if (podmiot.EmailPU || podmiot.TelefonPU) {
columnRight.push(
formatText('Dane kontaktowe', [FormatTyp.Label]),
...generateDaneKontaktowe(podmiot.EmailPU, getTable(podmiot.TelefonPU))
);
}
result.push(generateTwoColumns(columnLeft, columnRight));
return result;
}

View File

@@ -0,0 +1,119 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generatePodmioty } from './Podmioty';
import { Faktura } from '../../types/fa1.types';
vi.mock('../../../shared/PDF-functions', () => ({
createSection: vi.fn((arr) => arr),
getTable: vi.fn((obj) => (obj ? (Array.isArray(obj) ? obj : [obj]) : [])),
getValue: vi.fn((val) => (val && val._text ? val._text : '')),
hasValue: vi.fn((val) => Boolean(val && val._text)),
generateColumns: vi.fn((left, right) => ({ columns: [left, right] })),
}));
vi.mock('./Podmiot1', () => ({
generatePodmiot1: vi.fn(() => [{ podmiot1: true }]),
}));
vi.mock('./Podmiot1Podmiot1K', () => ({
generatePodmiot1Podmiot1K: vi.fn(() => [{ podmiot1K: true }]),
}));
vi.mock('./Podmiot2', () => ({
generatePodmiot2: vi.fn(() => [{ podmiot2: true }]),
}));
vi.mock('./Podmiot2Podmiot2k', () => ({
generatePodmiot2Podmiot2K: vi.fn(() => [{ podmiot2K: true }]),
}));
vi.mock('./Podmiot3', () => ({
generatePodmiot3: vi.fn(() => [{ podmiot3: true }]),
}));
vi.mock('./Podmiot3Podmiot2k', () => ({
generateDaneIdentyfikacyjneTPodmiot3Dto: vi.fn(() => [{ daneIdentyfikacyjne: true }]),
}));
vi.mock('./PodmiotUpowazniony', () => ({
generatePodmiotUpowazniony: vi.fn(() => [{ upowazniony: true }]),
}));
vi.mock('../../../shared/generators/common/functions', () => ({
getValue: vi.fn((val) => (val && val._text ? val._text : '')),
}));
describe(generatePodmioty.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('calls generatePodmiot1Podmiot1K when Podmiot1K exists', () => {
const invoice: Faktura = {
Podmiot1: {},
Podmiot2: {},
Fa: {
Podmiot1K: {},
Podmiot2K: [{}],
},
};
const result = generatePodmioty(invoice);
expect(result).toContainEqual(expect.arrayContaining([{ podmiot1K: true }]));
});
it('calls generatePodmiot1 when Podmiot1K missing', () => {
const invoice: Faktura = {
Podmiot1: {},
Podmiot2: {},
Fa: {
Podmiot2K: [{}],
},
};
const result = generatePodmioty(invoice);
expect(result).toContainEqual(expect.arrayContaining([{ podmiot1: true }]));
});
it('calls generatePodmiot2Podmiot2K when Podmiot2K available', () => {
const invoice: Faktura = {
Podmiot2: {},
Fa: {
Podmiot2K: [{}, {}],
},
};
const result = generatePodmioty(invoice);
expect(result).toContainEqual(expect.arrayContaining([{ podmiot2K: true }]));
});
it('renders Podmiot1 and Podmiot2 side by side if no Fa Podmiot1K or Podmiot2K', () => {
const invoice: Faktura = {
Podmiot1: {},
Podmiot2: {},
};
const result = generatePodmioty(invoice);
expect(result).toEqual(
expect.arrayContaining([
expect.arrayContaining([
expect.objectContaining({
columns: expect.any(Array),
}),
]),
])
);
});
it('calls generateDaneIdentyfikacyjneTPodmiot3Dto or generatePodmiot3 accordingly', () => {
const invoice: Faktura = {
Podmiot3: [{ Rola: { _text: '4' } }],
Fa: {
Podmiot2K: [{}, {}],
},
};
const result = generatePodmioty(invoice);
expect(result.some((e: any) => e.some((o: any) => o.daneIdentyfikacyjne || o.podmiot3))).toBe(true);
});
it('calls generatePodmiotUpowazniony last', () => {
const invoice: Faktura = {
PodmiotUpowazniony: {},
};
const result = generatePodmioty(invoice);
expect(result).toContainEqual(expect.arrayContaining([{ upowazniony: true }]));
});
});

View File

@@ -0,0 +1,95 @@
import { Content } from 'pdfmake/interfaces';
import { createSection, generateColumns, getTable, getValue } from '../../../shared/PDF-functions';
import { Faktura, Podmiot2K, Podmiot3 } from '../../types/fa1.types';
import { Podmiot3Podmiot2KDto } from '../../types/fa1-additional-types';
import { generatePodmiot1 } from './Podmiot1';
import { generatePodmiot1Podmiot1K } from './Podmiot1Podmiot1K';
import { generatePodmiot2 } from './Podmiot2';
import { generatePodmiot2Podmiot2K } from './Podmiot2Podmiot2k';
import { generatePodmiot3 } from './Podmiot3';
import { generateDaneIdentyfikacyjneTPodmiot3Dto } from './Podmiot3Podmiot2k';
import { generatePodmiotUpowazniony } from './PodmiotUpowazniony';
export function generatePodmioty(invoice: Faktura): Content[] {
const result: Content[] = [];
const podmiot2K: Podmiot2K[] = getTable(invoice.Fa?.Podmiot2K);
const podmiot3: Podmiot3[] = getTable(invoice.Podmiot3);
if (invoice.Fa?.Podmiot1K || podmiot2K.length > 0) {
if (invoice.Fa?.Podmiot1K) {
result.push(generatePodmiot1Podmiot1K(invoice.Podmiot1 ?? {}, invoice.Fa?.Podmiot1K));
} else if (invoice.Podmiot1 != null) {
result.push(generatePodmiot1(invoice.Podmiot1));
}
if (podmiot2K.length > 0) {
podmiot2K.forEach((podmiot2K: Podmiot2K): void => {
result.push(generatePodmiot2Podmiot2K(invoice.Podmiot2 ?? {}, podmiot2K));
});
} else if (invoice.Podmiot2) {
result.push(createSection(generatePodmiot2(invoice.Podmiot2!), true));
}
} else {
result.push([
generateColumns([generatePodmiot1(invoice.Podmiot1!), generatePodmiot2(invoice.Podmiot2!)], {
margin: [0, 0, 0, 8],
columnGap: 20,
}),
]);
}
if (podmiot3.length > 0) {
const podmiot3Podmiot2KDto: Podmiot3Podmiot2KDto[] = getPodmiot3Podmiot2KDto(podmiot2K, podmiot3);
if (podmiot3Podmiot2KDto.length > 0) {
podmiot3Podmiot2KDto.forEach((pdm2KDto: Podmiot3Podmiot2KDto, i: number): void => {
if (pdm2KDto.podmiot2KDto) {
result.push(generateDaneIdentyfikacyjneTPodmiot3Dto(pdm2KDto, i));
} else {
result.push(generatePodmiot3(pdm2KDto.fakturaPodmiotNDto, i));
}
if (i < podmiot3Podmiot2KDto.length - 1) {
result.push({ margin: [0, 8, 0, 0], text: '' });
}
});
} else {
podmiot3.forEach((podmiot: Podmiot3, index: number): void => {
result.push(generatePodmiot3(podmiot, index));
if (index < podmiot3.length - 1) {
result.push({ margin: [0, 8, 0, 0], text: '' });
}
});
}
}
result.push(generatePodmiotUpowazniony(invoice.PodmiotUpowazniony));
return createSection(result, true);
}
function getPodmiot3Podmiot2KDto(podmioty2K: Podmiot2K[], podmioty3: Podmiot3[]): Podmiot3Podmiot2KDto[] {
const result: Podmiot3Podmiot2KDto[] = [];
if (
podmioty2K.length > 1 &&
podmioty3.filter((p: Podmiot3): boolean => getValue(p.Rola) === '4').length > 0
) {
let idx: number = 1;
podmioty3.forEach((podmiot3) => {
if (getValue(podmiot3.Rola) === '4') {
if (podmioty2K.length > idx) {
result.push({
fakturaPodmiotNDto: podmiot3,
podmiot2KDto: podmioty2K[idx],
});
}
idx++;
} else {
result.push({
fakturaPodmiotNDto: podmiot3,
});
}
});
}
return result;
}

View File

@@ -0,0 +1,96 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Fa, Faktura, FP } from '../../types/fa1.types';
import { generatePodsumowanieStawekPodatkuVat, getSummaryTaxRate } from './PodsumowanieStawekPodatkuVat';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label) => [{ text: `HEADER:${label}` }]),
createSection: vi.fn((input) => input),
formatText: vi.fn((text, style) => ({ text, style })),
getValue: vi.fn((val) =>
val && val._text ? (typeof val._text === 'string' ? val._text : val._text.toString()) : ''
),
hasValue: vi.fn((val) => !!(val && val._text)),
getNumberRounded: vi.fn((val) => (typeof val === 'number' ? val : parseFloat(val?._text ?? '0') || 0)),
}));
describe('generatePodsumowanieStawekPodatkuVat', () => {
beforeEach(() => vi.clearAllMocks());
it('returns empty array if no relevant Fa data', () => {
const faktura: Faktura = { Fa: {} as Fa };
expect(generatePodsumowanieStawekPodatkuVat(faktura)).toEqual([]);
});
it('creates table with header and data rows', () => {
const fa: Fa = {
P_13_1: { _text: '100' },
P_14_1: { _text: '23' },
P_14_1W: { _text: '23' },
};
const faktura: Faktura = { Fa: fa };
const result: any = generatePodsumowanieStawekPodatkuVat(faktura);
expect(result.some((r: any) => r.table && r.table.body)).toBeTruthy();
expect(result[0].text).toContain('Podsumowanie stawek podatku'); // header present
});
});
describe('getSummaryTaxRate', () => {
beforeEach(() => vi.clearAllMocks());
it('returns empty summarization if no values', () => {
const fa: Fa = {};
expect(getSummaryTaxRate(fa)).toEqual([]);
});
it('returns correct summary entries for each tax rate', () => {
const floatText = (num: number): FP => ({ _text: num.toString() });
const fa: Fa = {
P_13_1: floatText(100),
P_14_1: floatText(23),
P_14_1W: floatText(23),
P_13_2: floatText(200),
P_14_2: floatText(16),
P_14_2W: floatText(16),
P_13_3: floatText(300),
P_14_3: floatText(15),
P_14_3W: floatText(15),
P_13_4: floatText(400),
P_14_4: floatText(12),
P_14_4W: floatText(12),
P_13_5: floatText(500),
P_14_5: floatText(0),
P_13_7: floatText(600),
};
const summary = getSummaryTaxRate(fa);
expect(summary.length).toBe(6);
expect(summary[0]).toMatchObject({
no: 1,
taxRateString: '23% lub 22%',
net: '100.00',
tax: '23.00',
taxPLN: '23.00',
gross: '123.00',
});
expect(summary[1].taxRateString).toBe('8% lub 7%');
expect(summary[3].taxRateString).toBe('4% lub 3%');
expect(summary[4].taxRateString).toBe('');
expect(summary[5].taxRateString).toBe('zwolnione z opodatkowania');
});
it('includes only tax rates with non-zero values', () => {
const fa: Fa = {
P_13_1: { _text: '0' },
P_14_1: { _text: '0' },
P_14_1W: { _text: '0' },
};
expect(getSummaryTaxRate(fa)).toHaveLength(0);
});
});

View File

@@ -0,0 +1,213 @@
import { Content, ContentTable, ContentText, TableCell } from 'pdfmake/interfaces';
import {
createHeader,
createSection,
formatText,
getNumberRounded,
getValue,
hasValue,
} from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { Fa, Faktura, FP } from '../../types/fa1.types';
import { TaxSummaryTypes } from '../../types/tax-summary.types';
import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const';
export function generatePodsumowanieStawekPodatkuVat(faktura: Faktura): Content[] {
const AnyP13P14_5Diff0: boolean =
hasValue(faktura.Fa?.P_13_1) ||
hasValue(faktura.Fa?.P_13_2) ||
hasValue(faktura.Fa?.P_13_3) ||
hasValue(faktura.Fa?.P_13_4) ||
(hasValue(faktura.Fa?.P_13_5) && (!hasValue(faktura.Fa?.P_14_5) || getValue(faktura.Fa?.P_14_5) == 0)) ||
hasValue(faktura.Fa?.P_13_6) ||
hasValue(faktura.Fa?.P_13_7);
const AnyP13: boolean =
hasValue(faktura.Fa?.P_13_1) ||
hasValue(faktura.Fa?.P_13_2) ||
hasValue(faktura.Fa?.P_13_3) ||
hasValue(faktura.Fa?.P_13_4) ||
hasValue(faktura.Fa?.P_13_5) ||
hasValue(faktura.Fa?.P_13_7);
const AnyP_14xW: boolean =
hasValue(faktura.Fa?.P_14_1W) ||
hasValue(faktura.Fa?.P_14_2W) ||
hasValue(faktura.Fa?.P_14_3W) ||
hasValue(faktura.Fa?.P_14_4W);
let tableBody: TableCell[] = [];
const table: ContentTable = {
table: {
headerRows: 1,
widths: [],
body: [] as TableCell[][],
},
layout: DEFAULT_TABLE_LAYOUT,
};
const definedHeader: Content[] = [
...[{ text: 'Lp.', style: FormatTyp.GrayBoldTitle }],
...(AnyP13P14_5Diff0 || hasValue(faktura.Fa?.P_14_5)
? [
{
text: 'Stawka podatku',
style: FormatTyp.GrayBoldTitle,
},
]
: []),
...(AnyP13 ? [{ text: 'Kwota netto', style: FormatTyp.GrayBoldTitle }] : []),
...(AnyP13P14_5Diff0 || hasValue(faktura.Fa?.P_14_5)
? [
{
text: 'Kwota podatku',
style: FormatTyp.GrayBoldTitle,
},
]
: []),
...(AnyP13 ? [{ text: 'Kwota brutto', style: FormatTyp.GrayBoldTitle }] : []),
...(AnyP_14xW ? [{ text: 'Kwota podatku PLN', style: FormatTyp.GrayBoldTitle }] : []),
];
const widths: Content[] = [
...['auto'],
...(AnyP13P14_5Diff0 || hasValue(faktura.Fa?.P_14_5) ? ['*'] : []),
...(AnyP13 ? ['*'] : []),
...(AnyP13P14_5Diff0 || hasValue(faktura.Fa?.P_14_5) ? ['*'] : []),
...(AnyP13 ? ['*'] : []),
...(AnyP_14xW ? ['*'] : []),
];
if (faktura?.Fa) {
const summary: TaxSummaryTypes[] = getSummaryTaxRate(faktura.Fa);
tableBody = summary.map((item: TaxSummaryTypes): (string | number | ContentText)[] => {
const data: (string | number | ContentText)[] = [];
data.push(item.no);
if (AnyP13P14_5Diff0) {
if (item.taxRateString) {
data.push(item.taxRateString);
} else if (getValue(faktura.Fa?.P_13_5)) {
data.push('OSS');
} else {
data.push('');
}
} else if (hasValue(faktura.Fa?.P_14_5)) {
data.push('OSS');
}
if (AnyP13) {
data.push(formatText(item.net, FormatTyp.Currency));
}
if (AnyP13P14_5Diff0) {
data.push(formatText(item.tax, FormatTyp.Currency));
} else if (hasValue(faktura.Fa?.P_14_5)) {
data.push(getValue(faktura.Fa?.P_14_5) as string);
}
if (AnyP13) {
data.push(formatText(item.gross, FormatTyp.Currency));
}
if (AnyP_14xW) {
data.push(formatText(item.taxPLN, FormatTyp.Currency));
}
return data;
});
}
table.table.body = [[...definedHeader], ...tableBody] as TableCell[][];
table.table.widths = [...widths] as never[];
return tableBody.length
? createSection([...createHeader('Podsumowanie stawek podatku', [0, 0, 0, 8]), table], false)
: [];
}
export function getSummaryTaxRate(fa: Fa): TaxSummaryTypes[] {
const summary: TaxSummaryTypes[] = [];
const AnyP13_1P14_1P14_1WDiff0 =
hasValueAndDiff0(fa?.P_13_1) || hasValueAndDiff0(fa?.P_14_1) || hasValueAndDiff0(fa?.P_14_1W);
const AnyP13_2P14_2P14_2WDiff0 =
hasValueAndDiff0(fa?.P_13_2) || hasValueAndDiff0(fa?.P_14_2) || hasValueAndDiff0(fa?.P_14_2W);
const AnyP13_3P14_3P14_3WDiff0 =
hasValueAndDiff0(fa?.P_13_3) || hasValueAndDiff0(fa?.P_14_3) || hasValueAndDiff0(fa?.P_14_3W);
const AnyP13_4P14_4P14_4WDiff0 =
hasValueAndDiff0(fa?.P_13_4) || hasValueAndDiff0(fa?.P_14_4) || hasValueAndDiff0(fa?.P_14_4W);
const AnyP13_5P14_5Diff0 = hasValueAndDiff0(fa?.P_13_5) || hasValueAndDiff0(fa?.P_14_5);
const AnyP13_7Diff0 = hasValueAndDiff0(fa?.P_13_7);
let no = 1;
if (AnyP13_1P14_1P14_1WDiff0) {
summary.push({
no,
net: getNumberRounded(fa.P_13_1).toFixed(2),
gross: (getNumberRounded(fa.P_13_1) + getNumberRounded(fa.P_14_1)).toFixed(2),
tax: getNumberRounded(fa.P_14_1).toFixed(2),
taxPLN: getNumberRounded(fa.P_14_1W).toFixed(2),
taxRateString: '23% lub 22%',
});
no++;
}
if (AnyP13_2P14_2P14_2WDiff0) {
summary.push({
no,
net: getNumberRounded(fa.P_13_2).toFixed(2),
gross: (getNumberRounded(fa.P_13_2) + getNumberRounded(fa.P_14_2)).toFixed(2),
tax: getNumberRounded(fa.P_14_2).toFixed(2),
taxPLN: getNumberRounded(fa.P_14_2W).toFixed(2),
taxRateString: '8% lub 7%',
});
no++;
}
if (AnyP13_3P14_3P14_3WDiff0) {
summary.push({
no,
net: getNumberRounded(fa.P_13_3).toFixed(2),
gross: (getNumberRounded(fa.P_13_3) + getNumberRounded(fa.P_14_3)).toFixed(2),
tax: getNumberRounded(fa.P_14_3).toFixed(2),
taxPLN: getNumberRounded(fa.P_14_3W).toFixed(2),
taxRateString: '5%',
});
no++;
}
if (AnyP13_4P14_4P14_4WDiff0) {
summary.push({
no,
net: getNumberRounded(fa.P_13_4).toFixed(2),
gross: (getNumberRounded(fa.P_13_4) + getNumberRounded(fa.P_14_4)).toFixed(2),
tax: getNumberRounded(fa.P_14_4).toFixed(2),
taxPLN: getNumberRounded(fa.P_14_4W).toFixed(2),
taxRateString: '4% lub 3%',
});
no++;
}
if (AnyP13_5P14_5Diff0) {
summary.push({
no,
net: getNumberRounded(fa.P_13_5).toFixed(2),
gross: (getNumberRounded(fa.P_13_5) + getNumberRounded(fa.P_14_5)).toFixed(2),
tax: getNumberRounded(fa.P_14_5).toFixed(2),
taxPLN: '',
taxRateString: getValue(fa.P_14_5) != 0 ? 'OSS' : '',
});
no++;
}
if (AnyP13_7Diff0) {
summary.push({
no,
net: getNumberRounded(fa.P_13_7).toFixed(2),
gross: getNumberRounded(fa.P_13_7).toFixed(2),
tax: '0.00',
taxPLN: '',
taxRateString: 'zwolnione z opodatkowania',
});
no++;
}
return summary;
}
function hasValueAndDiff0(value: FP | string | number | undefined): boolean {
return hasValue(value) && getValue(value) != 0;
}

View File

@@ -0,0 +1,34 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generatePrzewoznik } from './Przewoznik';
import { Przewoznik } from '../../types/fa1.types';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((label) => [{ text: `HEADER:${label}` }]),
generateTwoColumns: vi.fn((left, right, margin) => ({ left, right, margin, type: '2COL' })),
}));
vi.mock('./PodmiotAdres', () => ({
generatePodmiotAdres: vi.fn(() => ({ adr: 'AdresPrzewoznika' })),
}));
vi.mock('./PodmiotDaneIdentyfikacyjne', () => ({
generateDaneIdentyfikacyjne: vi.fn(() => [{ id: 'DaneIdentyfikacyjne' }]),
}));
describe('generatePrzewoznik', () => {
beforeEach(() => vi.clearAllMocks());
it('returns empty array if przewoznik is undefined', () => {
expect(generatePrzewoznik(undefined)).toEqual([]);
});
it('returns header and twoColumns for valid przewoznik', () => {
const przewoznik: Przewoznik = {
DaneIdentyfikacyjne: { NIP: { _text: '12345' } },
AdresPrzewoznika: { AdresPol: { Miasto: { _text: 'Warsaw' } } },
};
const result: any = generatePrzewoznik(przewoznik);
expect(result[0]).toEqual({ text: 'HEADER:Przewoźnik' });
});
});

View File

@@ -0,0 +1,21 @@
import { Content } from 'pdfmake/interfaces';
import { createHeader, generateTwoColumns } from '../../../shared/PDF-functions';
import { Przewoznik } from '../../types/fa1.types';
import { generatePodmiotAdres } from './PodmiotAdres';
import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne';
export function generatePrzewoznik(przewoznik: Przewoznik | undefined): Content {
if (!przewoznik) {
return [];
}
return [
...createHeader('Przewoźnik'),
[
generateTwoColumns(
generateDaneIdentyfikacyjne(przewoznik.DaneIdentyfikacyjne as any),
generatePodmiotAdres(przewoznik.AdresPrzewoznika, 'Adres przewoźnika', true, [0, 0, 0, 0]),
[0, 0, 0, 8]
),
],
];
}

View File

@@ -0,0 +1,239 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generateRabat } from './Rabat';
import * as PDFFunctions from '../../../shared/PDF-functions';
import { Fa } from '../../types/fa1.types';
import FormatTyp, { Position } from '../../../shared/enums/common.enum';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn(),
createLabelText: vi.fn(),
createSection: vi.fn(),
formatText: vi.fn(),
generateTwoColumns: vi.fn(),
getContentTable: vi.fn(),
getTable: vi.fn(),
}));
describe(generateRabat.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
const mockInvoice: Fa = {
FaWiersz: [
{
NrWierszaFa: { _text: '1' },
P_7: { _text: 'Product 1' },
P_8B: { _text: '5' },
P_8A: { _text: 'szt' },
},
],
P_15: { _text: '100.00' },
} as any;
beforeEach(() => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any);
vi.mocked(PDFFunctions.createLabelText).mockReturnValue(['label'] as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any);
vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted' as any);
vi.mocked(PDFFunctions.generateTwoColumns).mockReturnValue('columns' as any);
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: null,
fieldsWithValue: [],
});
});
it('should call createHeader with "Rabat"', () => {
generateRabat(mockInvoice);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Rabat');
});
it('should call createSection and return result', () => {
const mockSection = 'section';
vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any);
const result = generateRabat(mockInvoice);
expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.any(Array), true);
expect(result).toEqual(mockSection);
});
it('should call getTable with FaWiersz', () => {
generateRabat(mockInvoice);
expect(PDFFunctions.getTable).toHaveBeenCalledWith(mockInvoice.FaWiersze);
});
it('should call createLabelText with discount value', () => {
generateRabat(mockInvoice);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith(
'Wartość rabatu ogółem: ',
mockInvoice.P_15,
FormatTyp.Currency,
{ alignment: Position.RIGHT }
);
});
it('should call getContentTable with correct headers', () => {
generateRabat(mockInvoice);
expect(PDFFunctions.getContentTable).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({ name: 'NrWierszaFa', title: 'Lp.' }),
expect.objectContaining({ name: 'P_7', title: 'Nazwa towaru lub usługi' }),
expect.objectContaining({ name: 'P_8B', title: 'Ilość' }),
expect.objectContaining({ name: 'P_8A', title: 'Miara' }),
]),
[],
'*'
);
});
describe('discount text generation', () => {
it('should generate text "nie dotyczy" when NrWierszaFa is in fieldsWithValue', () => {
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: null,
fieldsWithValue: ['NrWierszaFa', 'P_7'],
});
generateRabat(mockInvoice);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(
'Rabat nie dotyczy wszystkich dostaw towarów i wykonanych usług na rzecz tego nabywcy w danym okresie.',
FormatTyp.Default
);
});
it('should generate text "dotyczy" when NrWierszaFa is not in fieldsWithValue', () => {
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: null,
fieldsWithValue: ['P_7', 'P_8B'],
});
generateRabat(mockInvoice);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(
'Rabat dotyczy wszystkich dostaw towarów i wykonanych usług na rzecz tego nabywcy w danym okresie.',
FormatTyp.Default
);
});
it('should call generateTwoColumns with formatted text and empty string', () => {
vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted-text' as any);
generateRabat(mockInvoice);
expect(PDFFunctions.generateTwoColumns).toHaveBeenCalledWith('formatted-text', '');
});
});
describe('table content', () => {
it('should add table content when fieldsWithValue is not empty and content exists', () => {
const mockTable = { table: 'mock-table' };
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: mockTable as any,
fieldsWithValue: ['P_7', 'P_8B'],
});
generateRabat(mockInvoice);
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall).toContain(mockTable);
});
it('should not add table content when fieldsWithValue is empty', () => {
const mockTable = { table: 'mock-table' };
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: mockTable as any,
fieldsWithValue: [],
});
generateRabat(mockInvoice);
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall).not.toContain(mockTable);
});
it('should not add table content when content is null', () => {
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: null,
fieldsWithValue: ['P_7', 'P_8B'],
});
generateRabat(mockInvoice);
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall.every((item: any) => item !== null)).toBe(true);
});
it('should not add table content when both fieldsWithValue is empty and content is null', () => {
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: null,
fieldsWithValue: [],
});
generateRabat(mockInvoice);
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall.length).toBeGreaterThan(0);
});
});
describe('result structure', () => {
it('should include header in result', () => {
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header-content'] as any);
generateRabat(mockInvoice);
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall).toContain('header-content');
});
it('should include label text in result', () => {
vi.mocked(PDFFunctions.createLabelText).mockReturnValue(['label-content'] as any);
generateRabat(mockInvoice);
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall).toContain('label-content');
});
it('should include two columns in result', () => {
vi.mocked(PDFFunctions.generateTwoColumns).mockReturnValue('columns-content' as any);
generateRabat(mockInvoice);
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall).toContain('columns-content');
});
});
describe('complete integration', () => {
it('should generate complete structure with all elements', () => {
const mockTable = { table: 'mock-table' };
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: mockTable as any,
fieldsWithValue: ['NrWierszaFa', 'P_7'],
});
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any);
vi.mocked(PDFFunctions.createLabelText).mockReturnValue(['label'] as any);
vi.mocked(PDFFunctions.generateTwoColumns).mockReturnValue('columns' as any);
generateRabat(mockInvoice);
expect(PDFFunctions.createHeader).toHaveBeenCalled();
expect(PDFFunctions.createLabelText).toHaveBeenCalled();
expect(PDFFunctions.formatText).toHaveBeenCalled();
expect(PDFFunctions.generateTwoColumns).toHaveBeenCalled();
expect(PDFFunctions.getContentTable).toHaveBeenCalled();
expect(PDFFunctions.createSection).toHaveBeenCalled();
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall.length).toBeGreaterThan(3);
});
});
});

View File

@@ -0,0 +1,46 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
createSection,
formatText,
generateTwoColumns,
getContentTable,
getTable,
} from '../../../shared/PDF-functions';
import { HeaderDefine } from '../../../shared/types/pdf-types';
import { Fa, FP } from '../../types/fa1.types';
import FormatTyp, { Position } from '../../../shared/enums/common.enum';
import { TableWithFields } from '../../types/fa1-additional-types';
export function generateRabat(invoice: Fa): Content[] {
const faRows: Record<string, FP>[] = getTable(invoice!.FaWiersze?.FaWiersz);
const result: Content[] = [];
const definedHeader: HeaderDefine[] = [
{ name: 'NrWierszaFa', title: 'Lp.', format: FormatTyp.Default },
{ name: 'P_7', title: 'Nazwa towaru lub usługi', format: FormatTyp.Default },
{ name: 'P_8B', title: 'Ilość', format: FormatTyp.Default },
{ name: 'P_8A', title: 'Miara', format: FormatTyp.Default },
];
const tabRabat: TableWithFields = getContentTable<(typeof faRows)[0]>(definedHeader, faRows, '*');
const isNrWierszaFa: boolean = tabRabat.fieldsWithValue.includes('NrWierszaFa');
result.push(
...createHeader('Rabat'),
...createLabelText('Wartość rabatu ogółem: ', invoice.P_15, FormatTyp.Currency, {
alignment: Position.RIGHT,
}),
generateTwoColumns(
formatText(
`Rabat ${isNrWierszaFa ? 'nie ' : ''}dotyczy wszystkich dostaw towarów i wykonanych usług na rzecz tego nabywcy w danym okresie.`,
FormatTyp.Default
),
''
)
);
if (tabRabat.fieldsWithValue.length > 0 && tabRabat.content) {
result.push(tabRabat.content);
}
return createSection(result, true);
}

View File

@@ -0,0 +1,235 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generujRachunekBankowy } from './RachunekBankowy';
import * as PDFFunctions from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import * as CommonFunctions from '../../../shared/generators/common/functions';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn(),
createSection: vi.fn(),
formatText: vi.fn(),
}));
vi.mock('../../../shared/generators/common/functions', () => ({
getTypRachunkowWlasnych: vi.fn(),
}));
describe(generujRachunekBankowy.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
const mockAccount: any = {
NrRB: { _text: '12345678901234567890123456' },
SWIFT: { _text: 'BPKOPLPW' },
RachunekWlasnyBanku: { _text: '1' },
NazwaBanku: { _text: 'PKO Bank Polski' },
OpisRachunku: { _text: 'Rachunek główny' },
} as any;
beforeEach(() => {
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any);
vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted' as any);
vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak');
});
describe('when accounts is undefined or empty', () => {
it('should return empty array when accounts is undefined', () => {
const result = generujRachunekBankowy(undefined, 'Rachunek bankowy');
expect(result).toEqual([]);
});
it('should return empty array when accounts is empty array', () => {
const result = generujRachunekBankowy([], 'Rachunek bankowy');
expect(result).toEqual([]);
});
});
describe('when accounts exist', () => {
it('should call createHeader with provided title', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Rachunek bankowy ', [0, 12, 0, 8]);
});
it('should call createHeader with empty string when title is not provided', () => {
generujRachunekBankowy([mockAccount]);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('', [0, 12, 0, 8]);
});
it('should call createSection and return result', () => {
const mockSection = 'section';
vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any);
const result = generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.any(Array), false);
expect(result).toEqual(mockSection);
});
it('should format "Kod SWIFT" field', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Kod SWIFT', FormatTyp.GrayBoldTitle);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(mockAccount.SWIFT?._text, FormatTyp.Default);
});
it('should format "Rachunek własny banku" field', () => {
vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak');
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Rachunek własny banku', FormatTyp.GrayBoldTitle);
expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku);
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Tak', FormatTyp.Default);
});
it('should format "Nazwa banku" field', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Nazwa banku', FormatTyp.GrayBoldTitle);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(mockAccount.NazwaBanku?._text, FormatTyp.Default);
});
it('should create table structure with correct widths', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent).toHaveProperty('table');
expect(tableContent.table).toHaveProperty('widths', ['*', 'auto']);
});
it('should create table structure with unbreakable property', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent).toHaveProperty('unbreakable', true);
});
it('should create table with correct layout', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent).toHaveProperty('layout');
expect(tableContent.layout).toHaveProperty('hLineWidth');
expect(tableContent.layout).toHaveProperty('hLineColor');
expect(tableContent.layout).toHaveProperty('vLineWidth');
expect(tableContent.layout).toHaveProperty('vLineColor');
});
it('should create table with correct line colors', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent.layout.hLineColor()).toBe('#BABABA');
expect(tableContent.layout.vLineColor()).toBe('#BABABA');
});
it('should create table with correct line widths', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent.layout.hLineWidth()).toBe(1);
expect(tableContent.layout.vLineWidth()).toBe(1);
});
it('should handle account with undefined fields', () => {
const accountWithUndefined: any = {
NrRB: { _text: undefined },
SWIFT: { _text: undefined },
RachunekWlasnyBanku: { _text: undefined },
NazwaBanku: { _text: undefined },
OpisRachunku: { _text: undefined },
} as any;
generujRachunekBankowy([accountWithUndefined], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith(undefined, FormatTyp.Default);
});
});
describe('when multiple accounts exist', () => {
it('should create multiple tables for multiple accounts', () => {
const account2: any = {
NrRB: { _text: '98765432109876543210987654' },
SWIFT: { _text: 'PKOPPLPW' },
RachunekWlasnyBanku: { _text: '0' },
NazwaBanku: { _text: 'mBank' },
OpisRachunku: { _text: 'Rachunek pomocniczy' },
} as any;
generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall.length).toBe(2);
});
it('should call getTypRachunkowWlasnych for each account', () => {
const account2: any = {
NrRB: { _text: '98765432109876543210987654' },
SWIFT: { _text: 'PKOPPLPW' },
RachunekWlasnyBanku: { _text: '0' },
NazwaBanku: { _text: 'mBank' },
OpisRachunku: { _text: 'Rachunek pomocniczy' },
} as any;
generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy');
expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledTimes(2);
expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku);
expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(account2.RachunekWlasnyBanku);
});
it('should format all fields for all accounts', () => {
const account2: any = {
NrRB: { _text: '98765432109876543210987654' },
SWIFT: { _text: 'PKOPPLPW' },
RachunekWlasnyBanku: { _text: '0' },
NazwaBanku: { _text: 'mBank' },
OpisRachunku: { _text: 'Rachunek pomocniczy' },
} as any;
generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Kod SWIFT', FormatTyp.GrayBoldTitle);
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Kod SWIFT', FormatTyp.GrayBoldTitle);
});
});
describe('table structure', () => {
it('should create table with 5 rows', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent.table.body.length).toBe(3);
});
it('should include header in result array', () => {
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header-content'] as any);
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall[0][0]).toBe('header-content');
});
});
});

View File

@@ -0,0 +1,242 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generujRachunekBankowy } from './RachunekBankowy';
import * as PDFFunctions from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import * as CommonFunctions from '../../../shared/generators/common/functions';
import { makeBreakable } from '../../../shared/PDF-functions';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn(),
createSection: vi.fn(),
formatText: vi.fn(),
makeBreakable: vi.fn(),
getValue: vi.fn((val) => (val && val._text ? val._text : '')),
hasValue: vi.fn((val) => Boolean(val && val._text)),
}));
vi.mock('../../../shared/generators/common/functions', () => ({
getTypRachunkowWlasnych: vi.fn(),
}));
describe(generujRachunekBankowy.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
const mockAccount: any = {
NrRB: { _text: '12345678901234567890123456' },
SWIFT: { _text: 'BPKOPLPW' },
RachunekWlasnyBanku: { _text: '1' },
NazwaBanku: { _text: 'PKO Bank Polski' },
OpisRachunku: { _text: 'Rachunek główny' },
} as any;
beforeEach(() => {
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any);
vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted' as any);
vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak');
});
describe('when accounts is undefined or empty', () => {
it('should return empty array when accounts is undefined', () => {
const result = generujRachunekBankowy(undefined, 'Rachunek bankowy');
expect(result).toEqual([]);
});
it('should return empty array when accounts is empty array', () => {
const result = generujRachunekBankowy([], 'Rachunek bankowy');
expect(result).toEqual([]);
});
});
describe('when accounts exist', () => {
it('should call createHeader with provided title', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Rachunek bankowy ', [0, 12, 0, 8]);
});
it('should call createHeader with empty string when title is not provided', () => {
generujRachunekBankowy([mockAccount]);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('', [0, 12, 0, 8]);
});
it('should call createSection and return result', () => {
const mockSection = 'section';
vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any);
const result = generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.any(Array), false);
expect(result).toEqual(mockSection);
});
it('should format "Kod SWIFT" field', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Kod SWIFT', FormatTyp.GrayBoldTitle);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(mockAccount.SWIFT?._text, FormatTyp.Default);
});
it('should format "Rachunek własny banku" field', () => {
vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak');
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Rachunek własny banku', FormatTyp.GrayBoldTitle);
expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(makeBreakable('Tak'), FormatTyp.Default);
});
it('should format "Nazwa banku" field', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Nazwa banku', FormatTyp.GrayBoldTitle);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(
makeBreakable(mockAccount.NazwaBanku?._text),
FormatTyp.Default
);
});
it('should create table structure with correct widths', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent).toHaveProperty('table');
expect(tableContent.table).toHaveProperty('widths', ['*', 'auto']);
});
it('should create table structure with unbreakable property', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent).toHaveProperty('unbreakable', true);
});
it('should create table with correct layout', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent).toHaveProperty('layout');
expect(tableContent.layout).toHaveProperty('hLineWidth');
expect(tableContent.layout).toHaveProperty('hLineColor');
expect(tableContent.layout).toHaveProperty('vLineWidth');
expect(tableContent.layout).toHaveProperty('vLineColor');
});
it('should create table with correct line colors', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent.layout.hLineColor()).toBe('#BABABA');
expect(tableContent.layout.vLineColor()).toBe('#BABABA');
});
it('should create table with correct line widths', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent.layout.hLineWidth()).toBe(1);
expect(tableContent.layout.vLineWidth()).toBe(1);
});
it('should handle account with undefined fields', () => {
const accountWithUndefined: any = {
NrRB: { _text: undefined },
SWIFT: { _text: undefined },
RachunekWlasnyBanku: { _text: undefined },
NazwaBanku: { _text: undefined },
OpisRachunku: { _text: undefined },
} as any;
generujRachunekBankowy([accountWithUndefined], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith(undefined, FormatTyp.Default);
});
});
describe('when multiple accounts exist', () => {
it('should create multiple tables for multiple accounts', () => {
const account2: any = {
NrRB: { _text: '98765432109876543210987654' },
SWIFT: { _text: 'PKOPPLPW' },
RachunekWlasnyBanku: { _text: '0' },
NazwaBanku: { _text: 'mBank' },
OpisRachunku: { _text: 'Rachunek pomocniczy' },
} as any;
generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall.length).toBe(2);
});
it('should call getTypRachunkowWlasnych for each account', () => {
const account2: any = {
NrRB: { _text: '98765432109876543210987654' },
SWIFT: { _text: 'PKOPPLPW' },
RachunekWlasnyBanku: { _text: '0' },
NazwaBanku: { _text: 'mBank' },
OpisRachunku: { _text: 'Rachunek pomocniczy' },
} as any;
generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy');
expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledTimes(2);
expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku);
expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(account2.RachunekWlasnyBanku);
});
it('should format all fields for all accounts', () => {
const account2: any = {
NrRB: { _text: '98765432109876543210987654' },
SWIFT: { _text: 'PKOPPLPW' },
RachunekWlasnyBanku: { _text: '0' },
NazwaBanku: { _text: 'mBank' },
OpisRachunku: { _text: 'Rachunek pomocniczy' },
} as any;
generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy');
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Kod SWIFT', FormatTyp.GrayBoldTitle);
expect(PDFFunctions.formatText).toHaveBeenCalledWith('Kod SWIFT', FormatTyp.GrayBoldTitle);
});
});
describe('table structure', () => {
it('should create table with 5 rows', () => {
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
const tableContent = sectionCall[0][1];
expect(tableContent.table.body.length).toBe(3);
});
it('should include header in result array', () => {
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header-content'] as any);
generujRachunekBankowy([mockAccount], 'Rachunek bankowy');
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[];
expect(sectionCall[0][0]).toBe('header-content');
});
});
});

View File

@@ -0,0 +1,81 @@
import { Content, ContentTable } from 'pdfmake/interfaces';
import {
createHeader,
createSection,
formatText,
getValue,
hasValue,
makeBreakable,
} from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { FP } from '../../types/fa1.types';
import { getTypRachunkowWlasnych } from '../../../shared/generators/common/functions';
import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const';
export const generujRachunekBankowy: (accounts?: Record<string, FP>[], title?: string) => Content[] = (
accounts?: Record<string, FP>[],
title?: string
): Content[] => {
const result: Content[] = [];
if (!accounts?.length) {
return [];
}
accounts.forEach((account: Record<string, FP>, index: number): void => {
const table: Content[][] = [];
const base: Content[] = createHeader(
title ? `${title} ${accounts?.length > 1 ? ++index : ''}` : '',
[0, 12, 0, 8]
);
if (hasValue(account.NrRBZagr)) {
table.push([
formatText('Format rachunku', FormatTyp.GrayBoldTitle),
formatText('Zagraniczny', FormatTyp.Default),
]);
} else if (hasValue(account.NrRBPL)) {
table.push([
formatText('Format rachunku', FormatTyp.GrayBoldTitle),
formatText('Polski', FormatTyp.Default),
]);
}
if (hasValue(account.NrRBPL)) {
table.push([
formatText('Pełny numer rachunku w standardzie NRB', FormatTyp.GrayBoldTitle),
formatText(getValue(account.NrRBPL), FormatTyp.Default),
]);
}
if (hasValue(account.NrRBZagr)) {
table.push([
formatText('Pełny numer rachunku zagranicznego', FormatTyp.GrayBoldTitle),
formatText(getValue(account.NrRBZagr), FormatTyp.Default),
]);
}
table.push([
formatText('Kod SWIFT', FormatTyp.GrayBoldTitle),
formatText(getValue(account.SWIFT), FormatTyp.Default),
]);
table.push([
formatText('Rachunek własny banku', FormatTyp.GrayBoldTitle),
formatText(makeBreakable(getTypRachunkowWlasnych(account.RachunekWlasnyBanku), 20), FormatTyp.Default),
]);
table.push([
formatText('Nazwa banku', FormatTyp.GrayBoldTitle),
formatText(makeBreakable(getValue(account.NazwaBanku), 20), FormatTyp.Default),
]);
result.push([
...base,
{
unbreakable: true,
table: {
body: table,
widths: ['*', 'auto'],
},
layout: DEFAULT_TABLE_LAYOUT,
} as ContentTable,
]);
});
return createSection(result, false);
};

View File

@@ -0,0 +1,175 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { generateSzczegoly } from './Szczegoly';
import * as PDFFunctions from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { TRodzajFaktury } from '../../../shared/consts/const';
import { Fa } from '../../types/fa1.types';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn(),
createLabelText: vi.fn(),
createLabelTextArray: vi.fn(),
createSection: vi.fn(),
generateTwoColumns: vi.fn(),
getContentTable: vi.fn(),
getDifferentColumnsValue: vi.fn(),
getTable: vi.fn(),
getValue: vi.fn(),
hasColumnsValue: vi.fn(),
hasValue: vi.fn(),
}));
describe(generateSzczegoly.name, () => {
let mockFaVat: Fa;
beforeEach(() => {
vi.clearAllMocks();
mockFaVat = {
FaWiersze: [],
Zamowienie: { ZamowienieWiersz: [] },
RodzajFaktury: TRodzajFaktury.VAT,
OkresFa: { P_6_Od: { _text: '' }, P_6_Do: { _text: '' } },
P_1: { _text: '2024-01-01' },
P_1M: { _text: 'Warsaw' },
OkresFaKorygowanej: { _text: '' },
P_6: { _text: '2024-01-15' },
KodWaluty: { _text: 'PLN' },
} as any;
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any);
vi.mocked(PDFFunctions.createLabelText).mockReturnValue('label' as any);
vi.mocked(PDFFunctions.createLabelTextArray).mockReturnValue('labelArray' as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any);
vi.mocked(PDFFunctions.generateTwoColumns).mockReturnValue('columns' as any);
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ content: null, fieldsWithValue: [] });
vi.mocked(PDFFunctions.getDifferentColumnsValue).mockReturnValue([]);
vi.mocked(PDFFunctions.getValue).mockReturnValue('PLN');
vi.mocked(PDFFunctions.hasColumnsValue).mockReturnValue(false);
vi.mocked(PDFFunctions.hasValue).mockReturnValue(false);
});
it('should create header and section', () => {
const result = generateSzczegoly(mockFaVat);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Szczegóły');
expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.any(Array), true);
expect(result).toEqual('section');
});
it('should call getTable for FaWiersze and ZamowienieWiersz', () => {
generateSzczegoly(mockFaVat);
expect(PDFFunctions.getTable).toHaveBeenCalledWith(mockFaVat.FaWiersze?.FaWiersz);
expect(PDFFunctions.getTable).toHaveBeenCalledWith(mockFaVat.Zamowienie?.ZamowienieWiersz);
});
describe('P_6 label', () => {
it('uses "Data otrzymania zapłaty" for ZAL', () => {
generateSzczegoly({ ...mockFaVat, RodzajFaktury: TRodzajFaktury.ZAL } as any);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith(
'Data otrzymania zapłaty: ',
mockFaVat.P_6,
FormatTyp.Date
);
});
it('uses "Data dokonania lub zakończenia dostawy" for VAT', () => {
generateSzczegoly(mockFaVat);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith(
'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: ',
mockFaVat.P_6,
FormatTyp.Date
);
});
});
describe('P_6 scope', () => {
it('creates scope array when P_6_Od and P_6_Do exist', () => {
const data = {
...mockFaVat,
OkresFa: { P_6_Od: { _text: '2024-01-01' }, P_6_Do: { _text: '2024-01-31' } },
} as any;
vi.mocked(PDFFunctions.hasValue).mockImplementation(
(value: any) => value === data.OkresFa.P_6_Od || value === data.OkresFa.P_6_Do
);
generateSzczegoly(data);
expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([
{ value: 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ' },
{ value: data.OkresFa.P_6_Od, formatTyp: FormatTyp.Value },
{ value: ' do ' },
{ value: data.OkresFa.P_6_Do, formatTyp: FormatTyp.Value },
]);
});
});
describe('ceny labels', () => {
it('adds netto label when P_11 exists', () => {
vi.mocked(PDFFunctions.hasColumnsValue).mockImplementation((col) => col === 'P_11');
generateSzczegoly(mockFaVat);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'netto');
});
it('adds brutto label when P_11 does not exist', () => {
vi.mocked(PDFFunctions.hasColumnsValue).mockReturnValue(false);
generateSzczegoly(mockFaVat);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'brutto');
});
it('adds currency label', () => {
generateSzczegoly(mockFaVat);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Kod waluty: ', mockFaVat.KodWaluty);
});
});
describe('P_12_XII label', () => {
it('adds OSS label when P_12_XII exists', () => {
vi.mocked(PDFFunctions.hasColumnsValue).mockImplementation((col) => col === 'P_12_XII');
generateSzczegoly(mockFaVat);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Procedura One Stop Shop', ' ');
});
});
describe('kurs waluty', () => {
it('adds currency rate when currency is not PLN and common rate exists', () => {
const data = { ...mockFaVat, KodWaluty: { _text: 'EUR' }, Zamowienie: { ZamowienieWiersz: [] } } as any;
vi.mocked(PDFFunctions.hasValue).mockReturnValue(true);
vi.mocked(PDFFunctions.getValue).mockReturnValue('EUR');
vi.mocked(PDFFunctions.getDifferentColumnsValue).mockReturnValue([{ value: '4.50' }]);
generateSzczegoly(data);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith(
'Kurs waluty wspólny dla wszystkich wierszy faktury',
' '
);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Kurs waluty: ', '4.50', FormatTyp.Currency6);
});
it('does not add currency rate when PLN', () => {
vi.mocked(PDFFunctions.getValue).mockReturnValue('PLN');
generateSzczegoly(mockFaVat);
const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls;
const currencyCall = calls.find((call) => call[0] === 'Kurs waluty: ');
expect(currencyCall).toBeUndefined();
});
});
describe('columns distribution', () => {
it('calls generateTwoColumns with labels split', () => {
generateSzczegoly(mockFaVat);
expect(PDFFunctions.generateTwoColumns).toHaveBeenCalledWith(expect.any(Array), expect.any(Array));
});
});
describe('faktura zaliczkowa', () => {
it('generates table when data exists', () => {
const data = { ...mockFaVat, NrFaZaliczkowej: [{ _text: 'FA001' }] } as any;
vi.mocked(PDFFunctions.getTable).mockReturnValueOnce([{ _text: 'FA001' }] as any);
generateSzczegoly(data);
expect(PDFFunctions.getContentTable).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,172 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
createLabelTextArray,
createSection,
generateTwoColumns,
getContentTable,
getDifferentColumnsValue,
getTable,
getValue,
hasColumnsValue,
hasValue,
} from '../../../shared/PDF-functions';
import { HeaderDefine } from '../../../shared/types/pdf-types';
import { TRodzajFaktury } from '../../../shared/consts/const';
import { Fa, FP } from '../../types/fa1.types';
import { DifferentValues, TypesOfValues } from '../../../shared/types/universal.types';
import FormatTyp from '../../../shared/enums/common.enum';
import { TableWithFields } from '../../types/fa1-additional-types';
export function generateSzczegoly(faVat: Fa): Content[] {
const faWiersze: Record<string, FP>[] = getTable(faVat.FaWiersze?.FaWiersz);
const zamowieniaWiersze: Record<string, FP>[] = getTable(faVat.Zamowienie?.ZamowienieWiersz);
const LabelP_6: string =
faVat.RodzajFaktury == TRodzajFaktury.ZAL || faVat.RodzajFaktury == TRodzajFaktury.KOR_ZAL
? 'Data otrzymania zapłaty: '
: 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: ';
const P_6Scope: Content[] = generateP_6Scope(faVat.OkresFa?.P_6_Od, faVat.OkresFa?.P_6_Do);
const cenyLabel1: Content[] = [];
const cenyLabel2: Content[] = [];
if (!(faWiersze.length > 0 || zamowieniaWiersze.length > 0)) {
const Any_P_11: boolean =
hasColumnsValue('P_11', faWiersze) || hasColumnsValue('P_11', zamowieniaWiersze);
if (Any_P_11) {
cenyLabel1.push(createLabelText('Faktura wystawiona w cenach: ', 'netto'));
} else {
cenyLabel1.push(createLabelText('Faktura wystawiona w cenach: ', 'brutto'));
}
cenyLabel2.push(createLabelText('Kod waluty: ', faVat.KodWaluty));
}
const P_12_XIILabel: Content[] = [];
if (hasColumnsValue('P_12_XII', faWiersze) || hasColumnsValue('P_12_XII', zamowieniaWiersze)) {
P_12_XIILabel.push(createLabelText('Procedura One Stop Shop', ' '));
}
const kodWalutyLabel1: Content[] = [];
const kodWalutyLabel2: Content[] = [];
if (hasValue(faVat.KodWaluty) && getValue(faVat.KodWaluty) != 'PLN') {
if (faVat.Zamowienie?.ZamowienieWiersz?.length) {
const Common_KursWaluty: DifferentValues[] = getDifferentColumnsValue(
'KursWalutyZ',
faVat.Zamowienie?.ZamowienieWiersz
);
if (Common_KursWaluty.length === 1) {
kodWalutyLabel1.push(createLabelText('Kurs waluty wspólny dla wszystkich wierszy faktury', ' '));
kodWalutyLabel2.push(
createLabelText('Kurs waluty: ', Common_KursWaluty[0].value, FormatTyp.Currency6)
);
}
} else {
const Common_KursWaluty: DifferentValues[] = getDifferentColumnsValue('KursWaluty', faWiersze);
if (Common_KursWaluty.length === 1) {
kodWalutyLabel1.push(createLabelText('Kurs waluty wspólny dla wszystkich wierszy faktury', ' '));
kodWalutyLabel2.push(
createLabelText('Kurs waluty: ', Common_KursWaluty[0].value, FormatTyp.Currency6)
);
}
}
}
const tpLabel1: Content[] = [];
const tpLabel2: Content[] = [];
const forColumns: Content[][] = [
createLabelText('Numer faktury: ', faVat.P_2),
createLabelText(
'Data wystawienia, z zastrzeżeniem art. 106na ust. 1 ustawy: ',
faVat.P_1,
FormatTyp.Date
),
createLabelText('Miejsce wystawienia: ', faVat.P_1M),
createLabelText('Okres, którego dotyczy rabat: ', faVat.OkresFaKorygowanej),
createLabelText(LabelP_6, faVat.P_6, FormatTyp.Date),
P_6Scope,
cenyLabel1,
cenyLabel2,
P_12_XIILabel,
kodWalutyLabel1,
kodWalutyLabel2,
tpLabel1,
tpLabel2,
].filter((el: Content[]): boolean => el.length > 0);
const columns1: Content[] = [];
const columns2: Content[] = [];
forColumns.forEach((tab: Content[], index: number): void => {
if (index % 2) {
columns2.push(tab);
} else {
columns1.push(tab);
}
});
const table: Content[] = [
...createHeader('Szczegóły'),
generateTwoColumns(columns1, columns2),
...generateFakturaZaliczkowa(getTable(faVat.NrFaZaliczkowej)),
];
return createSection(table, true);
}
function generateP_6Scope(P_6_Od: TypesOfValues, P_6_Do: TypesOfValues): Content[] {
const table: Content[] = [];
if (hasValue(P_6_Od) && hasValue(P_6_Do)) {
table.push(
createLabelTextArray([
{
value: 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ',
},
{ value: P_6_Od, formatTyp: FormatTyp.Value },
{ value: ' do ' },
{ value: P_6_Do, formatTyp: FormatTyp.Value },
])
);
} else if (hasValue(P_6_Od)) {
table.push(
createLabelText('Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', P_6_Od)
);
} else if (hasValue(P_6_Do)) {
table.push(
createLabelText('Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: do ', P_6_Do)
);
}
return table;
}
function generateFakturaZaliczkowa(fakturaZaliczkowa: FP[] | undefined): Content[] {
if (!fakturaZaliczkowa) {
return [];
}
const table: Content[] = [];
const fakturaZaliczkowaHeader: HeaderDefine[] = [
{
name: '',
title: 'Numery wcześniejszych faktur zaliczkowych',
format: FormatTyp.Default,
},
];
const tableFakturaZaliczkowa: TableWithFields = getContentTable<(typeof fakturaZaliczkowa)[0]>(
fakturaZaliczkowaHeader,
fakturaZaliczkowa,
'50%',
[0, 4, 0, 0]
);
if (tableFakturaZaliczkowa.content) {
table.push(tableFakturaZaliczkowa.content);
}
return table;
}

View File

@@ -0,0 +1,170 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generateTransport } from './Transport';
import * as PDFFunctions from '../../../shared/PDF-functions';
import * as PrzewoznikModule from './Przewoznik';
import * as CommonFunctions from '../../../shared/generators/common/functions';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn(),
createLabelText: vi.fn(),
createSection: vi.fn(),
createSubHeader: vi.fn(),
generateTwoColumns: vi.fn(),
getTable: vi.fn(),
hasValue: vi.fn(),
}));
vi.mock('./Przewoznik', () => ({
generatePrzewoznik: vi.fn(),
}));
vi.mock('../../../shared/generators/common/functions', () => ({
getDateTimeWithoutSeconds: vi.fn(),
getRodzajTransportuString: vi.fn(),
getOpisTransportuString: vi.fn(),
}));
describe(generateTransport.name, () => {
let mockTransport: any;
beforeEach(() => {
vi.clearAllMocks();
mockTransport = {
RodzajTransportu: { _text: '1' },
TransportInny: { _text: '0' },
OpisInnegoTransportu: { _text: '' },
NrZleceniaTransportu: { _text: 'TR001' },
OpisLadunku: { _text: 'Goods' },
LadunekInny: { _text: '0' },
OpisInnegoLadunku: { _text: '' },
JednostkaOpakowania: { _text: 'Box' },
DataGodzRozpTransportu: { _text: '2024-01-01T10:00:00' },
DataGodzZakTransportu: { _text: '2024-01-02T15:00:00' },
Przewoznik: {},
WysylkaZ: {
AdresL1: { _text: 'Street 1' },
AdresL2: { _text: 'City 1' },
KodKraju: { _text: 'PL' },
GLN: { _text: '123456' },
},
WysylkaDo: {
AdresL1: { _text: 'Street 2' },
AdresL2: { _text: 'City 2' },
KodKraju: { _text: 'DE' },
GLN: { _text: '789012' },
},
WysylkaPrzez: [],
};
vi.mocked(PDFFunctions.createHeader).mockReturnValue('header' as any);
vi.mocked(PDFFunctions.createLabelText).mockReturnValue('label' as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any);
vi.mocked(PDFFunctions.createSubHeader).mockReturnValue('subheader' as any);
vi.mocked(PDFFunctions.generateTwoColumns).mockReturnValue('columns' as any);
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
vi.mocked(PDFFunctions.hasValue).mockReturnValue(true);
vi.mocked(PrzewoznikModule.generatePrzewoznik).mockReturnValue('przewoznik' as any);
vi.mocked(CommonFunctions.getDateTimeWithoutSeconds).mockReturnValue('2024-01-01 10:00');
vi.mocked(CommonFunctions.getRodzajTransportuString).mockReturnValue('Road');
vi.mocked(CommonFunctions.getOpisTransportuString).mockReturnValue('Opis');
});
it('should return a section', () => {
const result = generateTransport(mockTransport);
expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.any(Array), true);
expect(result).toBe('section');
});
it('should handle RodzajTransportu', () => {
generateTransport(mockTransport);
expect(CommonFunctions.getRodzajTransportuString).toHaveBeenCalledWith(mockTransport.RodzajTransportu);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Road');
});
it('should handle TransportInny', () => {
const data = {
...mockTransport,
RodzajTransportu: { _text: '' },
TransportInny: { _text: '1' },
OpisInnegoTransportu: { _text: 'Custom transport' },
};
generateTransport(data);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Transport inny');
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Transport inny');
});
it('should handle Dane transportu', () => {
generateTransport(mockTransport);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith(
'Numer zlecenia transportu: ',
mockTransport.NrZleceniaTransportu
);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Opis ładunku: ', 'Opis');
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith(
'Jednostka opakowania: ',
mockTransport.JednostkaOpakowania
);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith(
'Data i godzina rozpoczęcia transportu: ',
'2024-01-01 10:00'
);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith(
'Data i godzina zakończenia transportu: ',
'2024-01-01 10:00'
);
expect(PDFFunctions.createSubHeader).toHaveBeenCalledWith('Dane transportu', [0, 0, 0, 0]);
});
it('should call generatePrzewoznik', () => {
generateTransport(mockTransport);
expect(PrzewoznikModule.generatePrzewoznik).toHaveBeenCalledWith(mockTransport.Przewoznik);
});
it('should handle WysylkaZ and WysylkaDo', () => {
generateTransport(mockTransport);
expect(PDFFunctions.createSubHeader).toHaveBeenCalledWith('Adres miejsca wysyłki', [0, 0, 0, 0]);
expect(PDFFunctions.createSubHeader).toHaveBeenCalledWith(
'Adres miejsca docelowego, do którego został zlecony transport',
[0, 0, 0, 0]
);
});
it('should handle WysylkaPrzez', () => {
const data = {
...mockTransport,
WysylkaPrzez: [
{
AdresL1: { _text: 'Street 3' },
AdresL2: { _text: 'City 3' },
KodKraju: { _text: 'FR' },
GLN: { _text: '345678' },
},
],
};
vi.mocked(PDFFunctions.getTable).mockReturnValue(data.WysylkaPrzez as any);
generateTransport(data);
expect(PDFFunctions.createSubHeader).toHaveBeenCalledWith('Adres pośredni wysyłki', [0, 4, 0, 0]);
});
it('should call generateTwoColumns for main sections and wysylka', () => {
generateTransport(mockTransport);
expect(PDFFunctions.generateTwoColumns).toHaveBeenCalled();
});
it('should add header for Transport and Wysyłka', () => {
generateTransport(mockTransport);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Transport');
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Wysyłka');
});
it('should handle full integration with all fields', () => {
const data = {
...mockTransport,
LadunekInny: { _text: '1' },
OpisInnegoLadunku: { _text: 'Custom cargo' },
};
generateTransport(data);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Opis ładunku: ', 'Ładunek inny');
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Road');
});
});

View File

@@ -0,0 +1,97 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
createSection,
createSubHeader,
generateTwoColumns,
getTable,
hasValue,
} from '../../../shared/PDF-functions';
import { Transport } from '../../types/fa1.types';
import {
getDateTimeWithoutSeconds,
getOpisTransportuString,
getRodzajTransportuString,
} from '../../../shared/generators/common/functions';
import { generateAdres } from './Adres';
import { generatePrzewoznik } from './Przewoznik';
export function generateTransport(transport: Transport, index?: number | null): Content {
const table: Content[] = [];
const columns = {
transport: [] as Content[],
dane: [] as Content[],
wysylkaZ: [] as Content[],
wysylkaDo: [] as Content[],
wysylkaPrzez: [] as Content[],
};
table.push(createHeader(index ? `Transport ${index}` : 'Transport'));
if (transport.RodzajTransportu?._text) {
columns.transport.push(
createLabelText('Rodzaj transportu: ', getRodzajTransportuString(transport.RodzajTransportu))
);
} else if (transport.TransportInny?._text == '1' && transport.OpisInnegoTransportu?._text) {
columns.transport.push(createLabelText('Rodzaj transportu: ', 'Transport inny'));
columns.transport.push(
createLabelText('Opis innego rodzaju transportu: ', transport.OpisInnegoTransportu)
);
}
columns.dane.push(createLabelText('Numer zlecenia transportu: ', transport.NrZleceniaTransportu));
if (hasValue(transport.OpisLadunku)) {
columns.dane.push(createLabelText('Opis ładunku: ', getOpisTransportuString(transport.OpisLadunku)));
if (transport.LadunekInny?._text === '1' && transport.OpisInnegoLadunku?._text) {
columns.dane.push(createLabelText('Opis ładunku: ', 'Ładunek inny'));
columns.dane.push(createLabelText('Opis innego ładunku: ', transport.OpisInnegoLadunku));
}
}
columns.dane.push(createLabelText('Jednostka opakowania: ', transport.JednostkaOpakowania));
columns.dane.push(
createLabelText(
'Data i godzina rozpoczęcia transportu: ',
getDateTimeWithoutSeconds(transport.DataGodzRozpTransportu)
)
);
columns.dane.push(
createLabelText(
'Data i godzina zakończenia transportu: ',
getDateTimeWithoutSeconds(transport.DataGodzZakTransportu)
)
);
if (columns.dane.length > 0) {
columns.dane.unshift(createSubHeader('Dane transportu', [0, 0, 0, 0]));
}
table.push(generateTwoColumns(columns.transport, columns.dane));
table.push(generatePrzewoznik(transport.Przewoznik));
if (transport.WysylkaZ) {
columns.wysylkaZ.push(createSubHeader('Adres miejsca wysyłki', [0, 0, 0, 0]));
columns.wysylkaZ.push(generateAdres(transport.WysylkaZ));
}
if (transport.WysylkaDo) {
columns.wysylkaDo.push(
createSubHeader('Adres miejsca docelowego, do którego został zlecony transport', [0, 0, 0, 0])
);
columns.wysylkaDo.push(generateAdres(transport.WysylkaDo));
}
const wysylkaPrzez = getTable(transport.WysylkaPrzez);
wysylkaPrzez.forEach((adres, index) => {
if (index) {
columns.wysylkaPrzez.push('\n');
}
columns.wysylkaPrzez.push(createSubHeader('Adres pośredni wysyłki', [0, 4, 0, 0]));
columns.wysylkaPrzez.push(generateAdres(adres));
});
if (transport.WysylkaZ || transport.WysylkaDo || transport.WysylkaPrzez?.length) {
table.push(createHeader('Wysyłka'));
table.push(generateTwoColumns(columns.wysylkaZ, columns.wysylkaDo));
table.push(generateTwoColumns(columns.wysylkaPrzez, []));
}
return createSection(table, true);
}

View File

@@ -0,0 +1,517 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generateWarunkiTransakcji } from './WarunkiTransakcji';
import * as PDFFunctions from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { WarunkiTransakcji } from '../../types/fa1.types';
import * as TransportModule from './Transport';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn(),
createLabelText: vi.fn(),
createSection: vi.fn(),
createSubHeader: vi.fn(),
formatText: vi.fn(),
generateTwoColumns: vi.fn(),
getContentTable: vi.fn(),
getTable: vi.fn(),
}));
vi.mock('./Transport', () => ({
generateTransport: vi.fn(),
}));
describe(generateWarunkiTransakcji.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('when warunkiTransakcji is undefined', () => {
it('should return empty array', () => {
const result = generateWarunkiTransakcji(undefined);
expect(result).toEqual([]);
});
});
describe('when warunkiTransakcji is defined', () => {
const mockWarunkiTransakcji: WarunkiTransakcji = {
Umowy: [],
Zamowienia: [],
NrPartiiTowaru: [],
WalutaUmowna: { _text: '' },
KursUmowny: { _text: '' },
WarunkiDostawy: { _text: '' },
PodmiotPosredniczacy: { _text: '0' },
} as any;
beforeEach(() => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
vi.mocked(PDFFunctions.createHeader).mockReturnValue('header' as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any);
vi.mocked(PDFFunctions.createLabelText).mockReturnValue('label' as any);
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: null,
fieldsWithValue: [],
});
});
it('should call createHeader with "Warunki transakcji"', () => {
generateWarunkiTransakcji(mockWarunkiTransakcji);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Warunki transakcji', [0, 8, 0, 4]);
});
it('should call getTable for Umowy, Zamowienia and NrPartiiTowaru', () => {
const data = {
...mockWarunkiTransakcji,
Umowy: [{ DataUmowy: { _text: '2024-01-01' } }],
Zamowienia: [{ DataZamowienia: { _text: '2024-01-02' } }],
NrPartiiTowaru: [{ _text: 'BATCH001' }],
} as any;
generateWarunkiTransakcji(data);
expect(PDFFunctions.getTable).toHaveBeenCalledWith(data.Umowy);
expect(PDFFunctions.getTable).toHaveBeenCalledWith(data.Zamowienia);
expect(PDFFunctions.getTable).toHaveBeenCalledWith(data.NrPartiiTowaru);
});
it('should call createSection with table array', () => {
generateWarunkiTransakcji(mockWarunkiTransakcji);
expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.any(Array), true);
});
it('should return result from createSection', () => {
const mockSection = { section: 'test' };
vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any);
const result = generateWarunkiTransakcji(mockWarunkiTransakcji);
expect(result).toEqual(mockSection);
});
describe('umowy section', () => {
it('should create umowy section when umowy exist', () => {
const data = {
...mockWarunkiTransakcji,
Umowy: [{ DataUmowy: { _text: '2024-01-01' }, NrUmowy: { _text: 'U001' } }],
} as any;
vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => {
if (field === data.Umowy)
return [
{
DataUmowy: { _text: '2024-01-01' },
NrUmowy: { _text: 'U001' },
},
] as any;
return [];
});
vi.mocked(PDFFunctions.getContentTable).mockReturnValueOnce({
content: { table: {} } as any,
fieldsWithValue: ['DataUmowy', 'NrUmowy'],
});
vi.mocked(PDFFunctions.createSubHeader).mockReturnValue('subheader' as any);
generateWarunkiTransakcji(data);
expect(PDFFunctions.createSubHeader).toHaveBeenCalledWith('Umowa');
expect(PDFFunctions.getContentTable).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({ name: 'DataUmowy', title: 'Data umowy' }),
expect.objectContaining({ name: 'NrUmowy', title: 'Numer umowy' }),
]),
expect.any(Array),
'*'
);
});
it('should not create umowy section when umowy are empty', () => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
vi.mocked(PDFFunctions.createSubHeader).mockClear();
generateWarunkiTransakcji(mockWarunkiTransakcji);
expect(PDFFunctions.createSubHeader).not.toHaveBeenCalledWith('Umowa');
});
});
describe('zamowienia section', () => {
it('should create zamowienia section when zamowienia exist', () => {
const data = {
...mockWarunkiTransakcji,
Zamowienia: [{ DataZamowienia: { _text: '2024-01-02' }, NrZamowienia: { _text: 'Z001' } }],
} as any;
vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => {
if (field === data.Zamowienia)
return [
{
DataZamowienia: { _text: '2024-01-02' },
NrZamowienia: { _text: 'Z001' },
},
] as any;
return [];
});
vi.mocked(PDFFunctions.getContentTable).mockReturnValueOnce({
content: { table: {} } as any,
fieldsWithValue: ['DataZamowienia', 'NrZamowienia'],
});
vi.mocked(PDFFunctions.createSubHeader).mockReturnValue('subheader' as any);
generateWarunkiTransakcji(data);
expect(PDFFunctions.createSubHeader).toHaveBeenCalledWith('Zamówienie');
expect(PDFFunctions.getContentTable).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({ name: 'DataZamowienia', title: 'Data zamówienia' }),
expect.objectContaining({ name: 'NrZamowienia', title: 'Numer zamówienia' }),
]),
expect.any(Array),
'*'
);
});
it('should not create zamowienia section when zamowienia are empty', () => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
vi.mocked(PDFFunctions.createSubHeader).mockClear();
generateWarunkiTransakcji(mockWarunkiTransakcji);
expect(PDFFunctions.createSubHeader).not.toHaveBeenCalledWith('Zamówienie');
});
it('should not create zamowienia section when fieldsWithValue is empty', () => {
const data = {
...mockWarunkiTransakcji,
Zamowienia: [{ DataZamowienia: { _text: '' } }],
} as any;
vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => {
if (field === data.Zamowienia) return [{ DataZamowienia: { _text: '' } }] as any;
return [];
});
vi.mocked(PDFFunctions.getContentTable).mockReturnValueOnce({
content: { table: {} } as any,
fieldsWithValue: [],
});
generateWarunkiTransakcji(data);
expect(PDFFunctions.generateTwoColumns).not.toHaveBeenCalled();
});
});
describe('two columns generation', () => {
it('should generate two columns when umowy exist', () => {
const data = {
...mockWarunkiTransakcji,
Umowy: [{ DataUmowy: { _text: '2024-01-01' } }],
} as any;
vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => {
if (field === data.Umowy) return [{ DataUmowy: { _text: '2024-01-01' } }] as any;
return [];
});
vi.mocked(PDFFunctions.getContentTable).mockReturnValueOnce({
content: { table: 'umowy' } as any,
fieldsWithValue: ['DataUmowy'],
});
vi.mocked(PDFFunctions.createSubHeader).mockReturnValue('subheader' as any);
vi.mocked(PDFFunctions.generateTwoColumns).mockReturnValue('twoColumns' as any);
generateWarunkiTransakcji(data);
expect(PDFFunctions.generateTwoColumns).toHaveBeenCalledWith(
expect.arrayContaining(['subheader', { table: 'umowy' }]),
[]
);
});
it('should generate two columns when zamowienia exist', () => {
const data = {
...mockWarunkiTransakcji,
Zamowienia: [{ DataZamowienia: { _text: '2024-01-02' } }],
} as any;
vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => {
if (field === data.Zamowienia) return [{ DataZamowienia: { _text: '2024-01-02' } }] as any;
return [];
});
vi.mocked(PDFFunctions.getContentTable).mockReturnValueOnce({
content: { table: 'zamowienia' } as any,
fieldsWithValue: ['DataZamowienia'],
});
vi.mocked(PDFFunctions.createSubHeader).mockReturnValue('subheader' as any);
vi.mocked(PDFFunctions.generateTwoColumns).mockReturnValue('twoColumns' as any);
generateWarunkiTransakcji(data);
expect(PDFFunctions.generateTwoColumns).toHaveBeenCalledWith(
[],
expect.arrayContaining(['subheader', { table: 'zamowienia' }])
);
});
it('should not generate two columns when both umowy and zamowienia are empty', () => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
generateWarunkiTransakcji(mockWarunkiTransakcji);
expect(PDFFunctions.generateTwoColumns).not.toHaveBeenCalled();
});
});
describe('waluta umowna section', () => {
it('should create waluta umowna section when WalutaUmowna exists', () => {
const data = {
...mockWarunkiTransakcji,
WalutaUmowna: { _text: 'EUR' },
KursUmowny: { _text: '' },
} as any;
generateWarunkiTransakcji(data);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Waluta umowna i kurs umowny', [0, 8, 0, 4]);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Waluta umowna: ', data.WalutaUmowna);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Kurs umowny: ', data.KursUmowny);
});
it('should create waluta umowna section when KursUmowny exists', () => {
const data = {
...mockWarunkiTransakcji,
WalutaUmowna: { _text: '' },
KursUmowny: { _text: '4.50' },
} as any;
generateWarunkiTransakcji(data);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Waluta umowna i kurs umowny', [0, 8, 0, 4]);
});
it('should not create waluta umowna section when both are empty', () => {
const data = {
...mockWarunkiTransakcji,
WalutaUmowna: { _text: '' },
KursUmowny: { _text: '' },
} as any;
vi.mocked(PDFFunctions.createHeader).mockClear();
generateWarunkiTransakcji(data);
expect(PDFFunctions.createHeader).toHaveBeenCalledTimes(1);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Warunki transakcji', [0, 8, 0, 4]);
});
});
describe('partia towaru section', () => {
it('should create partia towaru section when NrPartiiTowaru exists', () => {
const data = {
...mockWarunkiTransakcji,
NrPartiiTowaru: [{ _text: 'BATCH001' }],
} as any;
vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => {
if (field === data.NrPartiiTowaru) return [{ _text: 'BATCH001' }] as any;
return [];
});
vi.mocked(PDFFunctions.getContentTable).mockReturnValueOnce({
content: { table: 'partia' } as any,
fieldsWithValue: ['_text'],
});
vi.mocked(PDFFunctions.generateTwoColumns).mockReturnValue('twoColumns' as any);
generateWarunkiTransakcji(data);
expect(PDFFunctions.getContentTable).toHaveBeenCalledWith(
expect.arrayContaining([expect.objectContaining({ name: '', title: 'Numer partii towaru' })]),
expect.any(Array),
'*',
[0, 4]
);
expect(PDFFunctions.generateTwoColumns).toHaveBeenCalledWith({ table: 'partia' }, ' ');
});
it('should not create partia towaru section when NrPartiiTowaru is empty', () => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
const callCountBefore = vi.mocked(PDFFunctions.generateTwoColumns).mock.calls.length;
generateWarunkiTransakcji(mockWarunkiTransakcji);
expect(vi.mocked(PDFFunctions.generateTwoColumns).mock.calls.length).toBe(callCountBefore);
});
});
describe('warunki dostawy', () => {
it('should add warunki dostawy label', () => {
const data = {
...mockWarunkiTransakcji,
WarunkiDostawy: { _text: 'FOB' },
} as any;
generateWarunkiTransakcji(data);
expect(PDFFunctions.createLabelText).toHaveBeenCalledWith(
'Warunki dostawy towarów: ',
data.WarunkiDostawy,
FormatTyp.MarginTop4
);
});
});
describe('podmiot posredniczacy', () => {
it('should add podmiot posredniczacy info when value is "1"', () => {
const data = {
...mockWarunkiTransakcji,
PodmiotPosredniczacy: { _text: '1' },
} as any;
generateWarunkiTransakcji(data);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(
expect.stringContaining('Dostawa dokonana przez podmiot, o którym mowa w art. 22 ust. 2d ustawy'),
[FormatTyp.Label, FormatTyp.MarginTop4]
);
});
it('should not add podmiot posredniczacy info when value is not "1"', () => {
const data = {
...mockWarunkiTransakcji,
PodmiotPosredniczacy: { _text: '0' },
} as any;
vi.mocked(PDFFunctions.formatText).mockClear();
generateWarunkiTransakcji(data);
expect(PDFFunctions.formatText).not.toHaveBeenCalled();
});
it('should not add podmiot posredniczacy info when value is empty', () => {
const data = {
...mockWarunkiTransakcji,
PodmiotPosredniczacy: { _text: '' },
} as any;
vi.mocked(PDFFunctions.formatText).mockClear();
generateWarunkiTransakcji(data);
expect(PDFFunctions.formatText).not.toHaveBeenCalled();
});
});
describe('transport section', () => {
it('should call generateTransport for each transport item', () => {
const data = {
...mockWarunkiTransakcji,
Transport: [{ RodzajTransportu: { _text: 'Road' } }, { RodzajTransportu: { _text: 'Air' } }],
} as any;
vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => {
if (field === data.Transport) {
return [{ RodzajTransportu: { _text: 'Road' } }, { RodzajTransportu: { _text: 'Air' } }] as any;
}
return [];
});
vi.mocked(TransportModule.generateTransport).mockReturnValue('transport' as any);
generateWarunkiTransakcji(data);
expect(TransportModule.generateTransport).toHaveBeenCalledTimes(2);
expect(TransportModule.generateTransport).toHaveBeenCalledWith(
{
RodzajTransportu: { _text: 'Road' },
},
1
);
expect(TransportModule.generateTransport).toHaveBeenCalledWith(
{
RodzajTransportu: { _text: 'Air' },
},
2
);
});
it('should not call generateTransport when Transport is undefined', () => {
const data = {
...mockWarunkiTransakcji,
Transport: undefined,
} as any;
generateWarunkiTransakcji(data);
expect(TransportModule.generateTransport).not.toHaveBeenCalled();
});
it('should not call generateTransport when Transport is empty', () => {
const data = {
...mockWarunkiTransakcji,
Transport: [],
} as any;
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
generateWarunkiTransakcji(data);
expect(TransportModule.generateTransport).not.toHaveBeenCalled();
});
});
describe('complete integration', () => {
it('should handle all sections when fully populated', () => {
const data = {
Umowy: [{ DataUmowy: { _text: '2024-01-01' }, NrUmowy: { _text: 'U001' } }],
Zamowienia: [{ DataZamowienia: { _text: '2024-01-02' }, NrZamowienia: { _text: 'Z001' } }],
NrPartiiTowaru: [{ _text: 'BATCH001' }],
WalutaUmowna: { _text: 'EUR' },
KursUmowny: { _text: '4.50' },
WarunkiDostawy: { _text: 'FOB' },
PodmiotPosredniczacy: { _text: '1' },
Transport: [{ RodzajTransportu: { _text: 'Road' } }],
} as any;
vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => {
if (field === data.Umowy) return [{ DataUmowy: { _text: '2024-01-01' } }] as any;
if (field === data.Zamowienia) return [{ DataZamowienia: { _text: '2024-01-02' } }] as any;
if (field === data.NrPartiiTowaru) return [{ _text: 'BATCH001' }] as any;
if (field === data.Transport) return [{ RodzajTransportu: { _text: 'Road' } }] as any;
return [];
});
vi.mocked(PDFFunctions.getContentTable)
.mockReturnValueOnce({ content: { table: 'umowy' } as any, fieldsWithValue: ['DataUmowy'] })
.mockReturnValueOnce({
content: { table: 'zamowienia' } as any,
fieldsWithValue: ['DataZamowienia'],
})
.mockReturnValueOnce({ content: { table: 'partia' } as any, fieldsWithValue: ['_text'] });
generateWarunkiTransakcji(data);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Warunki transakcji', [0, 8, 0, 4]);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Waluta umowna i kurs umowny', [0, 8, 0, 4]);
expect(PDFFunctions.createSubHeader).toHaveBeenCalledWith('Umowa');
expect(PDFFunctions.createSubHeader).toHaveBeenCalledWith('Zamówienie');
expect(PDFFunctions.formatText).toHaveBeenCalled();
expect(TransportModule.generateTransport).toHaveBeenCalled();
expect(PDFFunctions.createSection).toHaveBeenCalled();
});
});
});
});

View File

@@ -0,0 +1,105 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
createSection,
createSubHeader,
formatText,
generateTwoColumns,
getContentTable,
getTable,
} from '../../../shared/PDF-functions';
import { HeaderDefine } from '../../../shared/types/pdf-types';
import { FP, Umowy, WarunkiTransakcji, Zamowienia } from '../../types/fa1.types';
import { generateTransport } from './Transport';
import FormatTyp from '../../../shared/enums/common.enum';
import { FormContentState } from '../../../shared/types/additional-data.types';
export function generateWarunkiTransakcji(warunkiTransakcji: WarunkiTransakcji | undefined): Content {
if (!warunkiTransakcji) {
return [];
}
const table: Content[] = [];
const Kolumny = { umowy: [] as Content[], zamowienia: [] as Content[] };
const umowy: Umowy[] = getTable(warunkiTransakcji?.Umowy);
const zamowienia: Zamowienia[] = getTable(warunkiTransakcji?.Zamowienia);
const partiaTowaru: FP[] = getTable(warunkiTransakcji?.NrPartiiTowaru);
const definedHeaderUmowy: HeaderDefine[] = [
{ name: 'DataUmowy', title: 'Data umowy', format: FormatTyp.Default },
{ name: 'NrUmowy', title: 'Numer umowy', format: FormatTyp.Default },
];
const definedHeaderZamowienia: HeaderDefine[] = [
{ name: 'DataZamowienia', title: 'Data zamówienia', format: FormatTyp.Default },
{ name: 'NrZamowienia', title: 'Numer zamówienia', format: FormatTyp.Default },
];
const definedHeaderPartiaTowaru: HeaderDefine[] = [
{ name: '', title: 'Numer partii towaru', format: FormatTyp.Default },
];
table.push(createHeader('Warunki transakcji', [0, 8, 0, 4]));
if (umowy.length > 0) {
const tabUmowy: FormContentState = getContentTable<(typeof umowy)[0]>(definedHeaderUmowy, umowy, '*');
if (tabUmowy.content) {
Kolumny.umowy = [createSubHeader('Umowa'), tabUmowy.content];
}
}
if (zamowienia.length > 0) {
const tabZamowienia: FormContentState = getContentTable<(typeof zamowienia)[0]>(
definedHeaderZamowienia,
zamowienia,
'*'
);
if (tabZamowienia.content && tabZamowienia.fieldsWithValue.length > 0) {
Kolumny.zamowienia = [createSubHeader('Zamówienie'), tabZamowienia.content];
}
}
if (Kolumny.zamowienia.length > 0 || Kolumny.umowy.length > 0) {
table.push(generateTwoColumns(Kolumny.umowy, Kolumny.zamowienia));
}
if (warunkiTransakcji.WalutaUmowna?._text || warunkiTransakcji.KursUmowny?._text) {
table.push(createHeader('Waluta umowna i kurs umowny', [0, 8, 0, 4]));
table.push(createLabelText('Waluta umowna: ', warunkiTransakcji.WalutaUmowna));
table.push(createLabelText('Kurs umowny: ', warunkiTransakcji.KursUmowny));
}
if (partiaTowaru.length > 0) {
const tabPartiaTowaru: FormContentState = getContentTable<(typeof partiaTowaru)[0]>(
definedHeaderPartiaTowaru,
partiaTowaru,
'*',
[0, 4]
);
if (tabPartiaTowaru.content) {
table.push(generateTwoColumns(tabPartiaTowaru.content, ' '));
}
}
table.push(
createLabelText('Warunki dostawy towarów: ', warunkiTransakcji.WarunkiDostawy, FormatTyp.MarginTop4)
);
if (warunkiTransakcji.PodmiotPosredniczacy?._text === '1') {
table.push(
formatText(
'Dostawa dokonana przez podmiot, o którym mowa w art. 22 ust. 2d ustawy. Pole dotyczy sytuacji, w której podmiot uczestniczy w transakcji łańcuchowej innej niż procedura trójstronna uproszczona, o której mowa w art. 135 ust. 1 pkt 4 ustawy',
[FormatTyp.Label, FormatTyp.MarginTop4]
)
);
}
if (warunkiTransakcji.Transport) {
getTable(warunkiTransakcji.Transport).forEach((transport, index) => {
table.push(
generateTransport(transport, getTable(warunkiTransakcji.Transport).length !== 0 ? index + 1 : null)
);
});
}
return createSection(table, true);
}

View File

@@ -0,0 +1,391 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import * as PDFFunctions from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { TRodzajFaktury } from '../../../shared/consts/const';
import { Fa } from '../../types/fa1.types';
import { generateWiersze } from './Wiersze';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn(),
createLabelTextArray: vi.fn(),
createSection: vi.fn(),
formatText: vi.fn(),
getContentTable: vi.fn(),
getTable: vi.fn(),
getValue: vi.fn(),
getTStawkaPodatku: vi.fn(),
getDifferentColumnsValue: vi.fn(),
}));
describe(generateWiersze.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
const mockFaVat: Fa = {
FaWiersz: [
{
NrWierszaFa: { _text: '1' },
P_7: { _text: 'Product 1' },
P_9A: { _text: '100' },
P_8B: { _text: '2' },
P_12: { _text: '23' },
},
],
KodWaluty: { _text: 'PLN' },
P_15: { _text: '200' },
RodzajFaktury: { _text: TRodzajFaktury.VAT },
} as any;
const setupBasicMocks = (p15Value: string, rodzajFakturyValue: string, currencyValue: string = 'PLN') => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([
{
NrWierszaFa: { _text: '1' },
P_7: { _text: 'Product 1' },
P_9A: { _text: '100' },
},
] as any);
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: { table: {} } as any,
fieldsWithValue: ['P_11', 'P_7'],
});
vi.mocked(PDFFunctions.getValue).mockImplementation((field: any) => {
if (field === mockFaVat.P_15 || field?._text === p15Value) return p15Value;
if (field === mockFaVat.RodzajFaktury || field?._text === rodzajFakturyValue) return rodzajFakturyValue;
if (field === mockFaVat.KodWaluty || field?._text === currencyValue) return currencyValue;
return undefined;
});
vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted text' as any);
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['Header'] as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue({ section: 'content' } as any);
vi.mocked(PDFFunctions.createLabelTextArray).mockReturnValue(['Label', 'Value'] as any);
vi.mocked(PDFFunctions.getDifferentColumnsValue).mockReturnValue([]);
};
describe('when no invoice lines exist', () => {
it('should return empty array when table is empty', () => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([]);
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: null,
fieldsWithValue: [],
});
vi.mocked(PDFFunctions.getValue).mockReturnValue('0');
vi.mocked(PDFFunctions.getDifferentColumnsValue).mockReturnValue([]);
const result = generateWiersze(mockFaVat);
expect(result).toEqual([]);
});
});
describe('when invoice lines exist', () => {
it('should call getTable with FaWiersz', () => {
setupBasicMocks('200', TRodzajFaktury.VAT);
generateWiersze(mockFaVat);
expect(PDFFunctions.getTable).toHaveBeenCalledWith(mockFaVat.FaWiersze);
});
it('should call createHeader with "Pozycje"', () => {
setupBasicMocks('200', TRodzajFaktury.VAT);
generateWiersze(mockFaVat);
expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Pozycje');
});
describe('price text formatting', () => {
it('should display "netto" when P_11 is in fieldsWithValue', () => {
setupBasicMocks('200', TRodzajFaktury.VAT);
generateWiersze(mockFaVat);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(expect.stringContaining('netto'), [
FormatTyp.Label,
FormatTyp.MarginBottom8,
]);
});
it('should display "brutto" when P_11 is not in fieldsWithValue', () => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([
{ NrWierszaFa: { _text: '1' }, P_12: { _text: '23' } },
] as any);
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: { table: {} } as any,
fieldsWithValue: ['P_11A', 'P_7'],
});
vi.mocked(PDFFunctions.getValue).mockReturnValue('0');
vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted text' as any);
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['Header'] as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue({ section: 'content' } as any);
vi.mocked(PDFFunctions.getTStawkaPodatku).mockReturnValue('23');
generateWiersze(mockFaVat);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(expect.stringContaining('brutto'), [
FormatTyp.Label,
FormatTyp.MarginBottom8,
]);
});
it('should include currency code in price text', () => {
setupBasicMocks('200', TRodzajFaktury.VAT);
generateWiersze(mockFaVat);
expect(PDFFunctions.formatText).toHaveBeenCalledWith(expect.stringContaining('PLN'), [
FormatTyp.Label,
FormatTyp.MarginBottom8,
]);
});
});
describe('table generation', () => {
it('should generate single table when fieldsWithValue.length <= 8', () => {
setupBasicMocks('200', TRodzajFaktury.VAT);
generateWiersze(mockFaVat);
expect(PDFFunctions.getContentTable).toHaveBeenCalledTimes(1);
});
it('should generate two tables when fieldsWithValue.length > 8', () => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([
{ NrWierszaFa: { _text: '1' }, P_12: { _text: '23' } },
] as any);
vi.mocked(PDFFunctions.getContentTable)
.mockReturnValueOnce({
content: null,
fieldsWithValue: Array(9).fill('field'),
})
.mockReturnValueOnce({
content: { table: 'table1' } as any,
fieldsWithValue: ['field1', 'field2'],
})
.mockReturnValueOnce({
content: { table: 'table2' } as any,
fieldsWithValue: ['field3', 'field4'],
});
vi.mocked(PDFFunctions.getValue).mockReturnValue('0');
vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted text' as any);
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['Header'] as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue({ section: 'content' } as any);
generateWiersze(mockFaVat);
expect(PDFFunctions.getContentTable).toHaveBeenCalledTimes(3);
});
it('should not add second table if it has only 1 field with value', () => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([
{ NrWierszaFa: { _text: '1' }, P_12: { _text: '23' } },
] as any);
vi.mocked(PDFFunctions.getContentTable)
.mockReturnValueOnce({
content: null,
fieldsWithValue: Array(9).fill('field'),
})
.mockReturnValueOnce({
content: { table: 'table1' } as any,
fieldsWithValue: ['field1', 'field2'],
})
.mockReturnValueOnce({
content: { table: 'table2' } as any,
fieldsWithValue: ['field1'],
});
vi.mocked(PDFFunctions.getValue).mockReturnValue('0');
vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted text' as any);
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['Header'] as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue({ section: 'content' } as any);
generateWiersze(mockFaVat);
const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0];
expect(sectionCall).not.toContain('\n');
expect(sectionCall.filter((item: any) => item?.table === 'table2')).toHaveLength(0);
});
});
describe('payment amount description', () => {
it('should add "Kwota pozostała do zapłaty" for ROZ invoice type when P_15 > 0', () => {
setupBasicMocks('150', TRodzajFaktury.ROZ, 'EUR');
generateWiersze(mockFaVat);
expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([
{ value: 'Kwota pozostała do zapłaty: ', formatTyp: FormatTyp.LabelGreater },
{
value: '150',
formatTyp: FormatTyp.CurrencyGreater,
currency: 'EUR',
},
]);
});
it('should not add description for ROZ invoice when P_15 = 0', () => {
setupBasicMocks('0', TRodzajFaktury.ROZ, 'EUR');
vi.mocked(PDFFunctions.createLabelTextArray).mockClear();
generateWiersze(mockFaVat);
expect(PDFFunctions.createLabelTextArray).not.toHaveBeenCalled();
});
it('should add "Kwota należności ogółem" for VAT invoice when P_15 > 0', () => {
setupBasicMocks('200', TRodzajFaktury.VAT, 'PLN');
generateWiersze(mockFaVat);
expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([
{ value: 'Kwota należności ogółem: ', formatTyp: FormatTyp.LabelGreater },
{
value: '200',
formatTyp: [FormatTyp.CurrencyGreater],
currency: 'PLN',
},
]);
});
it('should add description for KOR invoice when P_15 > 0', () => {
setupBasicMocks('300', TRodzajFaktury.KOR, 'USD');
generateWiersze(mockFaVat);
expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([
{ value: 'Kwota należności ogółem: ', formatTyp: FormatTyp.LabelGreater },
{
value: '300',
formatTyp: [FormatTyp.CurrencyGreater],
currency: 'USD',
},
]);
});
it('should add description for KOR_ROZ invoice when P_15 > 0', () => {
setupBasicMocks('250', TRodzajFaktury.KOR_ROZ, 'PLN');
generateWiersze(mockFaVat);
expect(PDFFunctions.createLabelTextArray).toHaveBeenCalled();
});
it('should add description for UPR invoice when P_15 > 0', () => {
setupBasicMocks('180', TRodzajFaktury.UPR, 'PLN');
generateWiersze(mockFaVat);
expect(PDFFunctions.createLabelTextArray).toHaveBeenCalled();
});
it('should not add description for VAT invoice when P_15 = 0', () => {
setupBasicMocks('0', TRodzajFaktury.VAT, 'PLN');
vi.mocked(PDFFunctions.createLabelTextArray).mockClear();
generateWiersze(mockFaVat);
expect(PDFFunctions.createLabelTextArray).not.toHaveBeenCalled();
});
it('should use empty string for currency if KodWaluty is undefined', () => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([{ NrWierszaFa: { _text: '1' } }] as any);
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: { table: {} } as any,
fieldsWithValue: ['P_11', 'P_7'],
});
vi.mocked(PDFFunctions.getValue).mockImplementation((field: any) => {
if (field === mockFaVat.P_15) return '200';
if (field === mockFaVat.RodzajFaktury) return TRodzajFaktury.VAT;
if (field === mockFaVat.KodWaluty) return undefined;
return undefined;
});
vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted text' as any);
vi.mocked(PDFFunctions.createHeader).mockReturnValue(['Header'] as any);
vi.mocked(PDFFunctions.createSection).mockReturnValue({ section: 'content' } as any);
vi.mocked(PDFFunctions.createLabelTextArray).mockReturnValue(['Label', 'Value'] as any);
const faVatNoCurrency = { ...mockFaVat, KodWaluty: undefined } as any;
generateWiersze(faVatNoCurrency);
expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
currency: '',
}),
])
);
});
});
describe('createSection call', () => {
it('should call createSection with correct parameters', () => {
setupBasicMocks('200', TRodzajFaktury.VAT);
generateWiersze(mockFaVat);
expect(PDFFunctions.createSection).toHaveBeenCalledWith(
expect.arrayContaining(['Header', 'formatted text']),
true
);
});
it('should return the result of createSection', () => {
const mockSection = { section: 'test' };
setupBasicMocks('200', TRodzajFaktury.VAT);
vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any);
const result = generateWiersze(mockFaVat);
expect(result).toEqual(mockSection);
});
});
describe('header definitions', () => {
it('should include all required headers in first table', () => {
setupBasicMocks('200', TRodzajFaktury.VAT);
generateWiersze(mockFaVat);
const firstCall = vi.mocked(PDFFunctions.getContentTable).mock.calls[0];
const headers = firstCall[0];
expect(headers).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'NrWierszaFa', title: 'Lp.' }),
expect.objectContaining({ name: 'P_7', title: 'Nazwa towaru lub usługi' }),
expect.objectContaining({ name: 'P_9A', title: 'Cena jedn. netto' }),
expect.objectContaining({ name: 'P_11', title: 'Wartość sprzedaży netto' }),
])
);
});
it('should use correct format types for currency fields', () => {
setupBasicMocks('200', TRodzajFaktury.VAT);
generateWiersze(mockFaVat);
const firstCall = vi.mocked(PDFFunctions.getContentTable).mock.calls[0];
const headers = firstCall[0];
const currencyHeaders = headers.filter(
(h: any) => h.format === FormatTyp.Currency || h.format === FormatTyp.Currency6
);
expect(currencyHeaders.length).toBeGreaterThan(0);
});
});
});
});

View File

@@ -0,0 +1,144 @@
import { Content, ContentStack, ContentText } from 'pdfmake/interfaces';
import {
createHeader,
createLabelTextArray,
createSection,
formatText,
getContentTable,
getDifferentColumnsValue,
getTable,
getTStawkaPodatku,
getValue,
} from '../../../shared/PDF-functions';
import { HeaderDefine } from '../../../shared/types/pdf-types';
import { Procedura, TRodzajFaktury } from '../../../shared/consts/const';
import { Fa, FP } from '../../types/fa1.types';
import FormatTyp, { Position } from '../../../shared/enums/common.enum';
import { FormContentState } from '../../../shared/types/additional-data.types';
import { addMarza } from '../common/Wiersze';
export function generateWiersze(faVat: Fa): Content {
const table: Content[] = [];
const rodzajFaktury: string | number | undefined = getValue(faVat.RodzajFaktury);
const isP_PMarzy = Boolean(Number(getValue(faVat.Adnotacje?.P_PMarzy)));
const faWiersze: Record<string, FP>[] = getTable(faVat.FaWiersze?.FaWiersz).map(
(wiersz: Record<string, FP>): Record<string, FP> => {
const marza: Record<string, FP> = addMarza(rodzajFaktury, isP_PMarzy, wiersz)!;
if (getValue(wiersz.P_12)) {
wiersz.P_12._text = getTStawkaPodatku(getValue(wiersz.P_12) as string, 1);
}
return { ...wiersz, ...marza };
}
);
const definedHeaderLp: HeaderDefine[] = [
{ name: 'NrWierszaFa', title: 'Lp.', format: FormatTyp.Default, width: 'auto' },
];
const definedHeader1: HeaderDefine[] = [
{ name: 'UU_ID', title: 'Unikalny numer wiersza', format: FormatTyp.Default, width: 'auto' },
{ name: 'P_7', title: 'Nazwa towaru lub usługi', format: FormatTyp.Default, width: '*' },
{ name: 'P_9A', title: 'Cena jedn. netto', format: FormatTyp.Currency, width: 'auto' },
{ name: 'P_9B', title: 'Cena jedn. brutto', format: FormatTyp.Currency, width: 'auto' },
{ name: 'P_8B', title: 'Ilość', format: FormatTyp.Number, width: 'auto' },
{ name: 'P_8A', title: 'Miara', format: FormatTyp.Default, width: 'auto' },
{ name: 'P_10', title: 'Rabat', format: FormatTyp.Currency, width: 'auto' },
{ name: 'P_12', title: 'Stawka podatku', format: FormatTyp.Default, width: 'auto' },
{ name: 'P_12_XII', title: 'Stawka podatku OSS', format: FormatTyp.Percentage, width: 'auto' },
{ name: 'P_11', title: 'Wartość sprzedaży netto', format: FormatTyp.Currency, width: 'auto' },
{ name: 'P_11A', title: 'Wartość sprzedaży brutto', format: FormatTyp.Currency, width: 'auto' },
];
if (getDifferentColumnsValue('KursWaluty', faWiersze).length !== 1) {
definedHeader1.push({
name: 'KursWaluty',
title: 'Kurs waluty',
format: FormatTyp.Currency6,
width: 'auto',
});
}
const definedHeader2: HeaderDefine[] = [
{ name: 'GTIN', title: 'GTIN', format: FormatTyp.Default, width: 'auto' },
{ name: 'PKWiU', title: 'PKWiU', format: FormatTyp.Default, width: 'auto' },
{ name: 'CN', title: 'CN', format: FormatTyp.Default, width: 'auto' },
{ name: 'PKOB', title: 'PKOB', format: FormatTyp.Default, width: 'auto' },
{ name: 'DodatkoweInfo', title: 'Dodatkowe informacje', format: FormatTyp.Default, width: 'auto' },
{
name: 'P_12_Procedura',
title: 'Procedura',
format: FormatTyp.Default,
mappingData: Procedura,
width: '*',
},
{ name: 'KwotaAkcyzy', title: 'KwotaAkcyzy', format: FormatTyp.Default, width: 'auto' },
{ name: 'GTU', title: 'GTU', format: FormatTyp.Default, width: 'auto' },
{ name: 'Procedura', title: 'Oznaczenia dotyczące procedur', format: FormatTyp.Default, width: '*' },
{ name: 'P_6A', title: 'Data dostawy / wykonania', format: FormatTyp.Default, width: 'auto' },
];
let content: FormContentState = getContentTable<(typeof faWiersze)[0]>(
[...definedHeaderLp, ...definedHeader1, ...definedHeader2],
faWiersze,
'*'
);
const ceny: string | ContentText = formatText(
`Faktura wystawiona w cenach ${content.fieldsWithValue.includes('P_11') ? 'netto' : 'brutto'} w walucie ${faVat.KodWaluty?._text}`,
[FormatTyp.Label, FormatTyp.MarginBottom8]
);
const p_15: string | number | undefined = getValue(faVat.P_15);
let opis: ContentStack[] = [];
if (rodzajFaktury == TRodzajFaktury.ROZ && Number(p_15) !== 0) {
opis = [
{
stack: createLabelTextArray([
{ value: 'Kwota pozostała do zapłaty: ', formatTyp: FormatTyp.LabelGreater },
{
value: p_15,
formatTyp: FormatTyp.CurrencyGreater,
currency: getValue(faVat.KodWaluty)?.toString() ?? '',
},
]),
alignment: Position.RIGHT,
margin: [0, 8, 0, 0],
},
];
} else if (
(rodzajFaktury == TRodzajFaktury.VAT ||
rodzajFaktury == TRodzajFaktury.KOR ||
rodzajFaktury == TRodzajFaktury.KOR_ROZ ||
rodzajFaktury == TRodzajFaktury.UPR) &&
Number(p_15) !== 0
) {
opis = [
{
stack: createLabelTextArray([
{ value: 'Kwota należności ogółem: ', formatTyp: FormatTyp.LabelGreater },
{
value: p_15,
formatTyp: [FormatTyp.CurrencyGreater],
currency: getValue(faVat.KodWaluty)?.toString() ?? '',
},
]),
alignment: Position.RIGHT,
margin: [0, 8, 0, 0],
},
];
}
if (content.fieldsWithValue.length <= 9 && content.content) {
table.push(content.content);
} else {
content = getContentTable<(typeof faWiersze)[0]>([...definedHeaderLp, ...definedHeader1], faWiersze, '*');
if (content.content) {
table.push(content.content);
}
content = getContentTable<(typeof faWiersze)[0]>([...definedHeaderLp, ...definedHeader2], faWiersze, '*');
if (content.content && content.fieldsWithValue.length > 1) {
table.push('\n');
table.push(content.content);
}
}
if (table.length < 1) {
return [];
}
return createSection([...createHeader('Pozycje'), ceny, ...table, ...opis], true);
}

View File

@@ -0,0 +1,290 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import * as PDFFunctions from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { TRodzajFaktury } from '../../../shared/consts/const';
import { Zamowienie } from '../../types/fa1.types';
import { generateZamowienie } from './Zamowienie';
import { ZamowienieKorekta } from '../../enums/invoice.enums';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn(),
createLabelTextArray: vi.fn(),
formatText: vi.fn(),
getContentTable: vi.fn(),
getTable: vi.fn(),
getValue: vi.fn(),
getTStawkaPodatku: vi.fn()
}));
describe(generateZamowienie.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('when orderData is undefined', () => {
it('should return an empty array', () => {
const result = generateZamowienie(
undefined,
ZamowienieKorekta.BeforeCorrection,
'100',
TRodzajFaktury.ZAL,
'PLN'
);
expect(result).toEqual([]);
});
});
describe('when orderData is defined', () => {
const mockOrderData: Zamowienie = {
ZamowienieWiersz: [
{
NrWierszaZam: { _text: '1' },
P_7Z: { _text: 'Towar 1' },
P_9AZ: { _text: '100' },
P_8BZ: { _text: '2' },
},
],
WartoscZamowienia: { _text: '200' },
} as any;
beforeEach(() => {
vi.mocked(PDFFunctions.getTable).mockReturnValue([
{
NrWierszaZam: { _text: '1' },
P_7Z: { _text: 'Towar 1' },
P_9AZ: { _text: '100' },
P_8BZ: { _text: '2' },
},
] as any);
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: { table: {} } as any,
fieldsWithValue: ['P_11'],
});
vi.mocked(PDFFunctions.createHeader).mockReturnValue('Header' as any);
vi.mocked(PDFFunctions.formatText).mockReturnValue('200 PLN' as any);
});
it('should call getTable with ZamowienieWiersz', () => {
generateZamowienie(mockOrderData, ZamowienieKorekta.BeforeCorrection, '100', TRodzajFaktury.ZAL, 'PLN');
expect(PDFFunctions.getTable).toHaveBeenCalledWith(mockOrderData.ZamowienieWiersz);
});
it('should fill NrWierszaZam if it is empty', () => {
const dataWithEmptyNr = {
...mockOrderData,
ZamowienieWiersz: [
{
NrWierszaZam: { _text: '' },
P_7Z: { _text: 'Towar 1' },
},
],
} as any;
vi.mocked(PDFFunctions.getTable).mockReturnValue([
{
NrWierszaZam: { _text: '' },
P_7Z: { _text: 'Towar 1' },
},
] as any);
generateZamowienie(
dataWithEmptyNr,
ZamowienieKorekta.BeforeCorrection,
'100',
TRodzajFaktury.ZAL,
'PLN'
);
const tableCall = vi.mocked(PDFFunctions.getTable).mock.results[0].value;
expect(tableCall[0].NrWierszaZam._text).toBe('1');
});
describe('price formatting', () => {
it('should use FormatTyp.CurrencyAbs for BeforeCorrection', () => {
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: { table: {} } as any,
fieldsWithValue: ['field1', 'field2', 'field3'],
});
generateZamowienie(
mockOrderData,
ZamowienieKorekta.BeforeCorrection,
'100',
TRodzajFaktury.ZAL,
'PLN'
);
const calls = vi.mocked(PDFFunctions.getContentTable).mock.calls;
const header = calls[0][0];
const cenaNetto = header.find((h: any) => h.name === 'P_9AZ');
expect(cenaNetto?.format).toBe(FormatTyp.CurrencyAbs);
});
it('should use FormatTyp.Currency when not BeforeCorrection', () => {
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: { table: {} } as any,
fieldsWithValue: ['field1', 'field2', 'field3'],
});
generateZamowienie(
mockOrderData,
ZamowienieKorekta.AfterCorrection,
'100',
TRodzajFaktury.KOR_ZAL,
'PLN'
);
const calls = vi.mocked(PDFFunctions.getContentTable).mock.calls;
const header = calls[0][0];
const cenaNetto = header.find((h: any) => h.name === 'P_9AZ');
expect(cenaNetto?.format).toBe(FormatTyp.Currency);
});
});
describe('table generation', () => {
it('should generate one table when fieldsWithValue.length <= 8', () => {
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: { table: {} } as any,
fieldsWithValue: ['field1', 'field2', 'field3'],
});
const result = generateZamowienie(
mockOrderData,
ZamowienieKorekta.BeforeCorrection,
'100',
TRodzajFaktury.ZAL,
'PLN'
);
expect(PDFFunctions.getContentTable).toHaveBeenCalledTimes(1);
expect(result[0]).toBeDefined();
});
it('should generate two tables when fieldsWithValue.length > 8', () => {
vi.mocked(PDFFunctions.getContentTable)
.mockReturnValueOnce({
content: null,
fieldsWithValue: Array(9).fill('field'),
})
.mockReturnValueOnce({
content: { table: {} } as any,
fieldsWithValue: ['field1', 'field2'],
})
.mockReturnValueOnce({
content: { table: {} } as any,
fieldsWithValue: ['field3', 'field4'],
});
generateZamowienie(
mockOrderData,
ZamowienieKorekta.BeforeCorrection,
'100',
TRodzajFaktury.ZAL,
'PLN'
);
expect(PDFFunctions.getContentTable).toHaveBeenCalledTimes(1);
});
});
describe('payment amount description', () => {
it('should add description for advance invoice (ZAL) when p_15 > 0', () => {
vi.mocked(PDFFunctions.createLabelTextArray).mockReturnValue(['Label', 'Value'] as any);
const result = generateZamowienie(
mockOrderData,
ZamowienieKorekta.BeforeCorrection,
'100',
TRodzajFaktury.ZAL,
'PLN'
);
expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([
{ value: 'Otrzymana kwota zapłaty (zaliczki): ', formatTyp: FormatTyp.LabelGreater },
{ value: '100', formatTyp: FormatTyp.CurrencyGreater },
]);
});
it('should not add description for ZAL invoice when p_15 = 0', () => {
vi.mocked(PDFFunctions.createLabelTextArray).mockClear();
generateZamowienie(mockOrderData, ZamowienieKorekta.BeforeCorrection, '0', TRodzajFaktury.ZAL, 'PLN');
expect(PDFFunctions.createLabelTextArray).not.toHaveBeenCalled();
});
it('should add description for advance correction (KOR_ZAL) when not BeforeCorrection', () => {
vi.mocked(PDFFunctions.createLabelTextArray).mockReturnValue(['Label', 'Value'] as any);
generateZamowienie(
mockOrderData,
ZamowienieKorekta.AfterCorrection,
'150',
TRodzajFaktury.KOR_ZAL,
'PLN'
);
expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([
{ value: 'Kwota należności ogółem: ', formatTyp: FormatTyp.LabelGreater },
{ value: '150', formatTyp: FormatTyp.CurrencyGreater },
]);
});
});
describe('price text', () => {
it('should display "netto" when P_11 is in fieldsWithValue', () => {
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: { table: {} } as any,
fieldsWithValue: ['P_11', 'P_7Z'],
});
const result = generateZamowienie(
mockOrderData,
ZamowienieKorekta.BeforeCorrection,
'100',
TRodzajFaktury.ZAL,
'EUR'
);
const stack = (result[0] as any).stack;
expect(stack[1]).toContain('netto');
expect(stack[1]).toContain('EUR');
});
it('should display "brutto" when P_11 is not in fieldsWithValue', () => {
vi.mocked(PDFFunctions.getContentTable).mockReturnValue({
content: { table: {} } as any,
fieldsWithValue: ['P_7Z', 'P_9AZ'],
});
const result = generateZamowienie(
mockOrderData,
ZamowienieKorekta.BeforeCorrection,
'100',
TRodzajFaktury.ZAL,
'USD'
);
const stack = (result[0] as any).stack;
expect(stack[1]).toContain('brutto');
expect(stack[1]).toContain('USD');
});
});
it('should call createHeader with correct parameter', () => {
generateZamowienie(mockOrderData, ZamowienieKorekta.BeforeCorrection, '100', TRodzajFaktury.ZAL, 'PLN');
expect(PDFFunctions.createHeader).toHaveBeenCalledWith(ZamowienieKorekta.BeforeCorrection);
});
it('should format order value', () => {
generateZamowienie(mockOrderData, ZamowienieKorekta.BeforeCorrection, '100', TRodzajFaktury.ZAL, 'PLN');
expect(PDFFunctions.formatText).toHaveBeenCalledWith('200', FormatTyp.Currency);
});
});
});

View File

@@ -0,0 +1,147 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelTextArray,
formatText,
getContentTable,
getTable,
getTStawkaPodatku,
getValue,
} from '../../../shared/PDF-functions';
import { HeaderDefine } from '../../../shared/types/pdf-types';
import { Procedura, TRodzajFaktury } from '../../../shared/consts/const';
import { FP, Zamowienie } from '../../types/fa1.types';
import FormatTyp, { Position } from '../../../shared/enums/common.enum';
import { ZamowienieKorekta } from '../../enums/invoice.enums';
import { FormContentState } from '../../../shared/types/additional-data.types';
export function generateZamowienie(
orderData: Zamowienie | undefined,
zamowienieKorekta: ZamowienieKorekta,
p_15: string,
rodzajFaktury: string,
KodWaluty: string,
P_PMarzy?: string
): Content[] {
if (!orderData) {
return [];
}
const formatAbs: FormatTyp.Currency | FormatTyp.CurrencyAbs =
zamowienieKorekta === ZamowienieKorekta.BeforeCorrection ? FormatTyp.CurrencyAbs : FormatTyp.Currency;
const orderTable: Record<string, FP>[] = getTable(orderData?.ZamowienieWiersz).map((el, index) => {
if (!el.NrWierszaZam._text) {
el.NrWierszaZam._text = (index + 1).toString();
}
el.P_12Z = { _text: getTStawkaPodatku(getValue(el.P_12Z) as string, 1, P_PMarzy) };
return el;
});
const definedHeaderLp: HeaderDefine[] = [
{ name: 'NrWierszaZam', title: 'Lp.', format: FormatTyp.Default, width: 'auto' },
];
const definedHeader1: HeaderDefine[] = [
{ name: 'UU_IDZ', title: 'Unikalny numer wiersza', format: FormatTyp.Default, width: 'auto' },
{ name: 'P_7Z', title: 'Nazwa towaru lub usługi', format: FormatTyp.Default, width: '*' },
{
name: 'P_9AZ',
title: 'Cena jedn. netto',
format: formatAbs,
},
{ name: 'P_8BZ', title: 'Ilość', format: FormatTyp.Right, width: 'auto' },
{ name: 'P_8AZ', title: 'Miara', format: FormatTyp.Default, width: 'auto' },
{ name: 'P_12Z', title: 'Stawka podatku', format: FormatTyp.Default, width: 'auto' },
{ name: 'P_12Z_XII', title: 'Stawka podatku OSS', format: FormatTyp.Percentage, width: 'auto' },
{ name: 'P_11NettoZ', title: 'Wartość sprzedaży netto', format: formatAbs, width: 'auto' },
{ name: 'P_11VatZ', title: 'Kwota podatku', format: formatAbs, width: 'auto' },
{ name: 'KursWalutyZ', title: 'Kwota podatku', format: formatAbs, width: 'auto' },
];
const definedHeader2: HeaderDefine[] = [
{ name: 'GTINZ', title: 'GTIN', format: FormatTyp.Default, width: 'auto' },
{ name: 'PKWiUZ', title: 'PKWiU', format: FormatTyp.Default, width: 'auto' },
{ name: 'CNZ', title: 'CN', format: FormatTyp.Default, width: 'auto' },
{ name: 'PKOBZ', title: 'PKOB', format: FormatTyp.Default, width: 'auto' },
{ name: 'DodatkoweInfoZ', title: 'Dodatkowe informacje', format: FormatTyp.Default, width: '*' },
{
name: 'P_12Z_Procedura',
title: 'Procedura',
format: FormatTyp.Default,
mappingData: Procedura,
width: '*',
},
{ name: 'KwotaAkcyzyZ', title: 'Kwota podatku akcyzowego', format: FormatTyp.Currency, width: 'auto' },
{ name: 'GTUZ', title: 'GTU', format: FormatTyp.Default, width: 'auto' },
{ name: 'ProceduraZ', title: 'Oznaczenia dotyczące procedur', format: FormatTyp.Default, width: '*' },
];
let content: FormContentState = getContentTable<(typeof orderTable)[0]>(
[...definedHeaderLp, ...definedHeader1, ...definedHeader2],
orderTable,
'*'
);
const table: Content[] = [];
if (content.fieldsWithValue.length <= 9) {
if (content.content) {
table.push(content.content);
}
} else {
content = getContentTable<(typeof orderTable)[0]>(
[...definedHeaderLp, ...definedHeader1],
orderTable,
'*'
);
if (content.content) {
table.push(content.content);
}
content = getContentTable<(typeof orderTable)[0]>(
[...definedHeaderLp, ...definedHeader2],
orderTable,
'*'
);
if (content.content && content.fieldsWithValue.length > 1) {
table.push(content.content);
}
}
const ceny = `Faktura wystawiona w cenach ${content.fieldsWithValue.includes('P_11') ? 'netto' : 'brutto'} w walucie ${KodWaluty}`;
let opis: Content = '';
if (Number(p_15) > 0 && rodzajFaktury == TRodzajFaktury.ZAL) {
opis = {
stack: createLabelTextArray([
{ value: 'Otrzymana kwota zapłaty (zaliczki): ', formatTyp: FormatTyp.LabelGreater },
{ value: p_15, formatTyp: FormatTyp.CurrencyGreater },
]),
alignment: Position.RIGHT,
margin: [0, 8, 0, 0],
};
} else if (
zamowienieKorekta !== ZamowienieKorekta.BeforeCorrection &&
rodzajFaktury == TRodzajFaktury.KOR_ZAL &&
Number(p_15) >= 0
) {
opis = {
stack: createLabelTextArray([
{ value: 'Kwota należności ogółem: ', formatTyp: FormatTyp.LabelGreater },
{ value: p_15, formatTyp: FormatTyp.CurrencyGreater },
]),
alignment: Position.RIGHT,
margin: [0, 8, 0, 0],
};
}
return [
{
stack: [
createHeader(zamowienieKorekta),
ceny,
{
text: [
'Wartość zamówienia lub umowy z uwzględnieniem kwoty podatku: ',
formatText(orderData.WartoscZamowienia?._text, FormatTyp.Currency),
],
marginBottom: 4,
},
...table,
opis,
],
},
];
}

View File

@@ -0,0 +1,187 @@
import { beforeEach, describe, expect, it, test, vi } from 'vitest';
import { createHeader, createLabelText, formatText, getTable } from '../../../shared/PDF-functions';
import { generateAdnotacje, generateDostawy } from './Adnotacje';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((text: string) => ({ text, style: 'header' })),
createLabelText: vi.fn((label: string, value: string) => [{ text: label + value }]),
formatText: vi.fn((text: string) => ({ text })),
getTable: vi.fn(() => []),
hasValue: vi.fn((v) => !!v?._text),
getValue: vi.fn((v) => v?._text || v),
verticalSpacing: vi.fn((n: number) => ({ text: `space-${n}` })),
generateColumns: vi.fn((left, right) => ({ columns: [left, right] })),
}));
describe(generateAdnotacje.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('zwraca pustą tablicę jeśli brak adnotacji', () => {
const result = generateAdnotacje(undefined);
expect(result).toEqual([]);
});
test.each([
[{ Zwolnienie: { P_19: { _text: '1' } } }, true, 'powinien dodać nagłówek i sekcję "Adnotacje"'],
[{ P_18A: { _text: '1' } }, true, 'powinien dodać "Mechanizm podzielonej płatności"'],
[{ P_16: { _text: '1' } }, true, 'powinien dodać "Metoda kasowa"'],
[{ P_18: { _text: '1' } }, true, 'powinien dodać "Odwrotne obciążenie"'],
[{ P_23: { _text: '1' } }, true, 'powinien dodać "Procedura trójstronna uproszczona"'],
[{ P_17: { _text: '1' } }, true, 'powinien dodać "Samofakturowanie"'],
])('dla adnotacji %s %s (%s)', (adnotacje, expected, desc) => {
const result = generateAdnotacje(adnotacje as any);
if (expected) {
expect(result.length).toBeGreaterThan(0);
expect(createHeader).toHaveBeenCalledWith('Adnotacje');
} else {
expect(result).toEqual([]);
}
});
it('dodaje adnotacje dla NoweSrodkiTransportu z VAT-22', () => {
const adnotacje = {
NoweSrodkiTransportu: { P_42_5: { _text: '1' } },
};
const result = generateAdnotacje(adnotacje as any);
expect(result.length).toBeGreaterThan(0);
});
it('dodaje procedurę marży', () => {
const adnotacje = {
PMarzy: { P_PMarzy: { _text: '1' }, P_PMarzy_3_1: { _text: '1' } },
};
const result = generateAdnotacje(adnotacje as any);
expect(result.length).toBeGreaterThan(0);
expect(createLabelText).toHaveBeenCalledWith('Procedura marży: ', 'towary używane');
});
});
describe(generateDostawy.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('zwraca pustą tablicę jeśli brak danych', () => {
(getTable as any).mockReturnValueOnce([]);
const result = generateDostawy({} as any);
expect(result).toEqual([]);
});
it('generuje tabelę jeśli są dane', () => {
(getTable as any).mockReturnValueOnce([
{
P_22A: { _text: '2025-01-01' },
P_22B: { _text: '123' },
DetailsString: { _text: 'Test opis' },
},
]);
const result = generateDostawy({ NowySrodekTransportu: [] } as any);
expect(result.length).toBeGreaterThan(0);
expect(result[0]).toHaveProperty('table');
expect(formatText).toHaveBeenCalledWith('2025-01-01');
});
test.each([
['P_22B', 'Dostawa dotyczy pojazdów lądowych, o których mowa w art. 2 pkt 10 lit. a ustawy'],
['P_22C', 'Dostawa dotyczy jednostek pływających, o których mowa w art. 2 pkt 10 lit. b ustawy'],
['P_22D', 'Dostawa dotyczy statków powietrznych, o których mowa w art. 2 pkt 10 lit. c ustawy'],
])('poprawnie rozpoznaje typ środka transportu (%s)', (field, expected) => {
(getTable as any).mockReturnValueOnce([{ [field]: { _text: '1' } }]);
const result = generateDostawy({ NowySrodekTransportu: [] } as any);
const textOutput = JSON.stringify(result);
expect(textOutput).toContain(expected);
});
it('adds all Zwolnienie fields together (P_19A, P_19B, P_19C)', () => {
const annotations = {
Zwolnienie: {
P_19: { _text: '1' },
P_19A: { _text: 'Law' },
P_19B: { _text: 'Directive' },
P_19C: { _text: 'OtherBasis' },
},
} as any;
const result = generateAdnotacje(annotations);
const flat = result.flatMap((r: any) => (Array.isArray(r.columns) ? r.columns.flat() : [r]))?.flat();
expect(
flat.flatMap((c) => (Array.isArray(c) ? c : [c])).some((item) => item?.text?.includes('Directive'))
).toBe(true);
expect(flat.some((c: any) => c.text?.includes('Dostawa towarów lub świadczenie'))).toBe(true);
expect(
flat
.flatMap((c) => (Array.isArray(c) ? c : [c]))
.some((item) => item?.text?.includes('Podstawa zwolnienia od podatku'))
).toBe(true);
});
it('adds VAT-22 and calls generateDostawy', () => {
const annotations = {
NoweSrodkiTransportu: { P_42_5: { _text: '1' }, NowySrodekTransportu: [] },
} as any;
const result = generateAdnotacje(annotations);
expect(result.some((r: any) => r?.table || r?.text?.includes('space'))).toBe(true);
});
it('adds full Marża procedure with P_PMarzy_3_2', () => {
const annotations = {
PMarzy: { P_PMarzy: { _text: '1' }, P_PMarzy_3_2: { _text: '1' } },
} as any;
const result = generateAdnotacje(annotations);
expect(result.flat().some((c: any) => c?.text?.includes('Adnotacje'))).toBe(true);
});
it('generates all transport type combinations', () => {
(getTable as any).mockReturnValueOnce([
{
P_22A: { _text: '2025-01-01' },
P_22B: { _text: '1' },
P_22B1: { _text: 'VIN' },
P_22B2: { _text: 'Chassis' },
P_22B3: { _text: 'Underbody' },
P_22B4: { _text: 'Frame' },
P_22C: { _text: 'C' },
P_22C1: { _text: 'Hull' },
P_22D: { _text: 'D' },
P_22D1: { _text: 'Serial' },
DetailsString: { _text: 'Description' },
P_22BT: { _text: 'BT' },
},
]);
const result = generateDostawy({ NowySrodekTransportu: [] } as any);
const textOutput = JSON.stringify(result);
expect(textOutput).toContain('VIN');
expect(textOutput).toContain('Chassis');
expect(textOutput).toContain('Underbody');
expect(textOutput).toContain('Frame');
expect(textOutput).toContain('Hull');
expect(textOutput).toContain('Serial');
expect(textOutput).toContain('Description');
expect(textOutput).toContain('BT');
});
it('generates only header when no details exist', () => {
(getTable as any).mockReturnValueOnce([{ P_22A: { _text: '2025-01-01' } }]);
const result: any = generateDostawy({ NowySrodekTransportu: [] } as any);
expect(result[0].table.body[0][0].text).toBe('Data dopuszczenia do użytku');
});
});

View File

@@ -0,0 +1,221 @@
import { Content, ContentTable, ContentText, TableCell } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
formatText,
generateColumns,
getTable,
getValue,
hasValue,
verticalSpacing,
} from '../../../shared/PDF-functions';
import { Adnotacje, NoweSrodkiTransportu, Zwolnienie } from '../../types/fa2.types';
import FormatTyp from '../../../shared/enums/common.enum';
import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const';
import { FP } from '../../types/fa1.types';
export function generateAdnotacje(adnotacje?: Adnotacje): Content[] {
const result: Content[] = [];
let firstColumn: Content[] = [];
const secondColumn: Content[] = [];
if (adnotacje) {
const zwolnienie: Zwolnienie | undefined = adnotacje.Zwolnienie;
if (zwolnienie?.P_19?._text === '1') {
firstColumn.push({
text: 'Dostawa towarów lub świadczenie usług zwolnionych od podatku na podstawie art. 43 ust. 1, art. 113 ust. 1 i 9 albo przepisów wydanych na podstawie art. 82 ust. 3 lub na podstawie innych przepisów',
});
if (zwolnienie.P_19A?._text) {
firstColumn.push(
createLabelText(
'Podstawa zwolnienia od podatku: ',
'Przepis ustawy albo aktu wydanego na podstawie ustawy, na podstawie którego podatnik stosuje zwolnienie od podatku'
)
);
firstColumn.push(
createLabelText('Przepis ustawy albo aktu wydanego na podstawie ustawy: ', zwolnienie.P_19A._text)
);
}
if (zwolnienie.P_19B?._text) {
firstColumn.push(
createLabelText(
'Podstawa zwolnienia od podatku: ',
'Przepis dyrektywy 2006/112/WE, który zwalnia od podatku taką dostawę towarów lub takie świadczenie usług'
)
);
firstColumn.push(createLabelText('Przepis dyrektywy: ', zwolnienie.P_19B._text));
}
if (zwolnienie.P_19C?._text) {
firstColumn.push(
createLabelText(
'Podstawa zwolnienia od podatku: ',
'Inna podstawa prawna wskazującą na to, że dostawa towarów lub świadczenie usług korzysta ze zwolnienia'
)
);
firstColumn.push(createLabelText('Inna podstawa prawna: ', zwolnienie.P_19C._text));
}
}
if (
adnotacje.NoweSrodkiTransportu?.P_42_5?._text === '1' ||
adnotacje.NoweSrodkiTransportu?.P_42_5?._text === '2'
) {
let obowiazekVAT: Content[] = [];
let value: string = ' ';
if (adnotacje.NoweSrodkiTransportu.P_42_5?._text === '1') {
value = 'Istnieje obowiązek wystawienia dokumentu VAT-22';
} else if (adnotacje.NoweSrodkiTransportu.P_42_5?._text === '2') {
value = 'Nie istnieje obowiązek wystawienia dokumentu VAT-22';
}
obowiazekVAT = [
...createLabelText('Wewnątrzwspólnotowe dostawy nowych środków transportu: ', value ?? ''),
];
if (obowiazekVAT) {
firstColumn = [firstColumn, ...obowiazekVAT];
}
}
if (adnotacje.P_18A?._text === '1') {
secondColumn.push({ text: 'Mechanizm podzielonej płatności' });
}
if (adnotacje.P_16?._text === '1') {
secondColumn.push({ text: 'Metoda kasowa' });
}
if (adnotacje.P_18?._text === '1') {
secondColumn.push({ text: 'Odwrotne obciążenie' });
}
if (adnotacje.P_23?._text === '1') {
secondColumn.push({ text: 'Procedura trójstronna uproszczona' });
}
if (adnotacje.PMarzy?.P_PMarzy?._text === '1') {
let valueMarzy: string = '';
if (adnotacje.PMarzy.P_PMarzy_3_1?._text === '1') {
valueMarzy = 'towary używane';
} else if (adnotacje.PMarzy.P_PMarzy_3_2?._text === '1') {
valueMarzy = 'dzieła sztuki';
} else if (adnotacje.PMarzy.P_PMarzy_2?._text === '1') {
valueMarzy = 'biura podróży';
} else if (adnotacje.PMarzy.P_PMarzy_3_3?._text === '1') {
valueMarzy = 'przedmioty kolekcjonerskie i antyki';
}
secondColumn.push(createLabelText('Procedura marży: ', valueMarzy));
}
if (adnotacje.P_17?._text === '1') {
secondColumn.push({ text: 'Samofakturowanie' });
}
if (firstColumn.length || secondColumn.length) {
result.push(generateColumns([firstColumn, secondColumn]));
}
if (result.length) {
result.unshift(verticalSpacing(1));
result.unshift(createHeader('Adnotacje'));
result.unshift(verticalSpacing(1));
result.push(verticalSpacing(1));
}
if (
adnotacje.NoweSrodkiTransportu?.P_42_5?._text === '1' ||
adnotacje.NoweSrodkiTransportu?.P_42_5?._text === '2'
) {
result.push(generateDostawy(adnotacje.NoweSrodkiTransportu));
}
}
return result;
}
export function generateDostawy(noweSrodkiTransportu: NoweSrodkiTransportu): Content[] {
const nowySrodekTransportu: Record<string, FP>[] = getTable(noweSrodkiTransportu.NowySrodekTransportu);
let tableBody: TableCell[] = [];
const table: ContentTable = {
table: {
headerRows: 1,
widths: [100, '*'],
body: [] as TableCell[][],
},
layout: DEFAULT_TABLE_LAYOUT,
marginTop: 4,
};
if (nowySrodekTransportu?.length) {
const definedHeader: Content[] = [
{ text: 'Data dopuszczenia do użytku', style: FormatTyp.GrayBoldTitle },
{ text: 'Opis', style: FormatTyp.GrayBoldTitle },
];
tableBody = nowySrodekTransportu.map((item: Record<string, FP>): (string | ContentText)[] => {
const value: string[] = [];
const anyP22B =
hasValue(item.P_22B) ||
hasValue(item.P_22BT) ||
hasValue(item.P_22B1) ||
hasValue(item.P_22B2) ||
hasValue(item.P_22B3) ||
hasValue(item.P_22B4);
const anyP22C: boolean = hasValue(item.P_22C) || hasValue(item.P_22C1);
const anyP22D: boolean = hasValue(item.P_22D) || hasValue(item.P_22D1);
const anyP22N: boolean =
hasValue(item.P_22B1) || hasValue(item.P_22B2) || hasValue(item.P_22B3) || hasValue(item.P_22B4);
if (item.P_NrWierszaNST?._text) {
value.push(item.P_NrWierszaNST._text);
}
if (anyP22B) {
value.push('Dostawa dotyczy pojazdów lądowych, o których mowa w art. 2 pkt 10 lit. a ustawy');
} else if (anyP22C) {
value.push('Dostawa dotyczy jednostek pływających, o których mowa w art. 2 pkt 10 lit. b ustawy');
} else if (anyP22D) {
value.push('Dostawa dotyczy statków powietrznych, o których mowa w art. 2 pkt 10 lit. c ustawy');
}
const transportProperties = [
getValue(item.P_22BMK),
getValue(item.P_22BMD),
getValue(item.P_22BK),
getValue(item.P_22BNR),
getValue(item.P_22BRP),
].filter((prop) => !!prop);
if (transportProperties.length) {
value.push(transportProperties.join(', '));
}
if (item.DetailsString?._text) {
value.push(item.DetailsString._text);
}
if (anyP22B || anyP22C || anyP22D) {
value.push(item.P_22B?._text ?? item.P_22C?._text ?? item.P_22D?._text ?? '');
}
if (item.P_22C1?._text) {
value.push(`Numer kadłuba nowego środka transportu: ${item.P_22C1._text}`);
}
if (item.P_22D1?._text) {
value.push(`Numer fabryczny nowego środka transportu: ${item.P_22D1._text}`);
}
if (anyP22N) {
if (item.P_22B1?._text) {
value.push(`Numer VIN: ${item.P_22B1._text}`);
}
if (item.P_22B2?._text) {
value.push(`Numer nadwozia: ${item.P_22B2._text}`);
}
if (item.P_22B3?._text) {
value.push(`Numer podwozia: ${item.P_22B3._text}`);
}
if (item.P_22B4?._text) {
value.push(`Numer ramy: ${item.P_22B4._text}`);
}
}
if (item.P_22BT?._text) {
value.push(item.P_22BT._text);
}
return [formatText(item.P_22A?._text), { text: value.join('\n') }];
});
table.table.body = [[...definedHeader], ...tableBody] as TableCell[][];
}
return tableBody.length ? [table, verticalSpacing(1)] : [];
}

View File

@@ -0,0 +1,46 @@
import { describe, it, expect, vi, beforeEach, test } from 'vitest';
import { generateAdres } from './Adres';
import FormatTyp from '../../../shared/enums/common.enum';
vi.mock('../../../shared/PDF-functions', () => ({
formatText: vi.fn((text: string, style: string) => ({ text, style })),
getKraj: vi.fn((code: string) => `Kraj: ${code}`),
createLabelText: vi.fn((label: string, value: any) => [{ text: `${label}${value ?? ''}` }]),
}));
import { formatText, createLabelText } from '../../../shared/PDF-functions';
describe(generateAdres.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
test.each([
[{ AdresL1: { _text: 'Ulica Testowa 1' } }, ['Ulica Testowa 1']],
[{ AdresL2: { _text: '00-001 Warszawa' } }, ['00-001 Warszawa']],
[{ KodKraju: { _text: 'PL' } }, ['Kraj: PL']],
[
{ AdresL1: { _text: 'Ulica 1' }, AdresL2: { _text: 'Miasto' }, KodKraju: { _text: 'DE' } },
['Ulica 1', 'Miasto', 'Kraj: DE'],
],
])('generuje dane adresowe dla %s', (adres, expectedTexts) => {
const result = generateAdres(adres as any);
expect(formatText).toHaveBeenCalledTimes(expectedTexts.length);
expectedTexts.forEach((text) => {
expect(formatText).toHaveBeenCalledWith(text, FormatTyp.Value);
});
expect(createLabelText).toHaveBeenCalledWith('GLN: ', (adres as any).GLN);
expect((result[result.length - 1] as any).text).toContain('GLN:');
});
it('zwraca tylko GLN gdy brak innych pól', () => {
const adres = { GLN: '1234567890' };
const result = generateAdres(adres as any);
expect(formatText).not.toHaveBeenCalled();
expect(createLabelText).toHaveBeenCalledWith('GLN: ', '1234567890');
expect(result).toHaveLength(1);
expect((result[0] as any).text).toContain('GLN:');
});
});

View File

@@ -0,0 +1,20 @@
import { Content } from 'pdfmake/interfaces';
import { createLabelText, formatText, getKraj } from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { Adres } from '../../types/fa2.types';
export function generateAdres(adres: Adres): Content[] {
const result: Content[] = [];
if (adres?.AdresL1) {
result.push(formatText(adres.AdresL1._text, FormatTyp.Value));
}
if (adres?.AdresL2) {
result.push(formatText(adres.AdresL2._text, FormatTyp.Value));
}
if (adres?.KodKraju) {
result.push(formatText(getKraj(adres.KodKraju._text ?? ''), FormatTyp.Value));
}
result.push(...createLabelText('GLN: ', adres.GLN));
return result;
}

View File

@@ -0,0 +1,90 @@
import { describe, it, expect, vi, beforeEach, test } from 'vitest';
import { generateDodatkoweInformacje } from './DodatkoweInformacje';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((text: string) => [{ text, style: 'header' }]),
createSection: vi.fn((content: any, bordered: boolean) => [{ section: content, bordered }]),
createSubHeader: vi.fn((text: string) => [{ text, style: 'subheader' }]),
formatText: vi.fn((text: string) => ({ text, style: 'value' })),
getValue: vi.fn((obj: any) => obj?._text ?? obj ?? ''),
getTable: vi.fn((data: any) => data ?? []),
getContentTable: vi.fn(() => ({ content: { text: 'mockTable' } })),
}));
import {
createSection,
createSubHeader,
formatText,
getTable,
getContentTable,
} from '../../../shared/PDF-functions';
describe(generateDodatkoweInformacje.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
test.each([
[{ TP: { _text: '1' } }, true],
[{ ZwrotAkcyzy: { _text: '1' } }, true],
[{ TP: { _text: '0' }, ZwrotAkcyzy: { _text: '0' } }, false],
])('dla danych %o', (faVat, shouldHaveContent) => {
const result = generateDodatkoweInformacje(faVat as any);
if (shouldHaveContent) {
expect(result.length).toBeGreaterThan(0);
} else {
expect(result).toEqual([]);
}
});
it('dodaje dodatkowy opis jeśli istnieje DodatkowyOpis', () => {
(getTable as any).mockReturnValueOnce([
{ NrWiersza: { _text: '1' }, Klucz: { _text: 'Info' }, Wartosc: { _text: 'Opis' } },
]);
const faVat = {
TP: { _text: '1' },
DodatkowyOpis: [{ NrWiersza: { _text: '1' }, Klucz: { _text: 'Info' }, Wartosc: { _text: 'Opis' } }],
};
const result = generateDodatkoweInformacje(faVat as any);
expect(createSubHeader).toHaveBeenCalledWith('Dodatkowy opis');
expect(getContentTable).toHaveBeenCalled();
expect(result.length).toBeGreaterThan(0);
expect(createSection).toHaveBeenCalled();
});
it('zwraca pustą tablicę gdy brak danych wejściowych', () => {
const result = generateDodatkoweInformacje({} as any);
expect(result).toEqual([]);
});
it('poprawnie dodaje sekcję TP i ZwrotAkcyzy razem', () => {
const faVat = {
TP: { _text: '1' },
ZwrotAkcyzy: { _text: '1' },
};
const result = generateDodatkoweInformacje(faVat as any);
expect(formatText).toHaveBeenCalledWith(
'- Istniejące powiązania między nabywcą a dokonującym dostawy towarów lub usługodawcą'
);
expect(formatText).toHaveBeenCalledWith(
'- Informacja dodatkowa związana ze zwrotem podatku akcyzowego zawartego w cenie oleju napędowego'
);
expect(createSection).toHaveBeenCalled();
expect(result.length).toBeGreaterThan(0);
});
it('poprawnie dodaje sekcję FP', () => {
const faVat = {
FP: { _text: '1' },
};
const result = generateDodatkoweInformacje(faVat as any);
expect(formatText).toHaveBeenCalledWith('- Faktura, o której mowa w art. 109 ust. 3d ustawy');
expect(createSection).toHaveBeenCalled();
expect(result.length).toBeGreaterThan(0);
});
});

View File

@@ -0,0 +1,102 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createSection,
createSubHeader,
formatText,
getContentTable,
getTable,
getValue,
} from '../../../shared/PDF-functions';
import { HeaderDefine } from '../../../shared/types/pdf-types';
import { DodatkowyOpi, Fa } from '../../types/fa2.types';
import FormatTyp from '../../../shared/enums/common.enum';
import { FormContentState } from '../../../shared/types/additional-data.types';
export function generateDodatkoweInformacje(faVat: Fa): Content[] {
const tpLabel: Content[] = [];
if (getValue(faVat.TP) === '1') {
tpLabel.push(
formatText('- Istniejące powiązania między nabywcą a dokonującym dostawy towarów lub usługodawcą')
);
}
const fpLabel: Content[] = [];
if (getValue(faVat.FP) === '1') {
fpLabel.push(formatText('- Faktura, o której mowa w art. 109 ust. 3d ustawy'));
}
const zwrotAkcyzyLabel: Content[] = [];
if (getValue(faVat.ZwrotAkcyzy) === '1') {
zwrotAkcyzyLabel.push(
formatText(
'- Informacja dodatkowa związana ze zwrotem podatku akcyzowego zawartego w cenie oleju napędowego'
)
);
}
const labels: Content[][] = [tpLabel, fpLabel, zwrotAkcyzyLabel].filter(
(el: Content[]): boolean => el.length > 0
);
const table: Content[] = [
...createHeader('Dodatkowe informacje'),
...labels,
...generateDodatkowyOpis(faVat.DodatkowyOpis),
];
return table.length > 1 ? createSection(table, true) : [];
}
function generateDodatkowyOpis(fakturaZaliczkowaData: DodatkowyOpi[] | undefined): Content[] {
if (!fakturaZaliczkowaData) {
return [];
}
const fakturaZaliczkowa: DodatkowyOpi[] = getTable(fakturaZaliczkowaData)?.map(
(item: DodatkowyOpi, index: number) => ({
...item,
lp: { _text: index + 1 },
})
);
const table: Content[] = createSubHeader('Dodatkowy opis');
const fakturaZaliczkowaHeader: HeaderDefine[] = [
{
name: 'lp',
title: 'Lp.',
format: FormatTyp.Default,
width: 'auto',
},
{
name: 'NrWiersza',
title: 'Numer wiersza',
format: FormatTyp.Default,
width: 'auto',
},
{
name: 'Klucz',
title: 'Rodzaj informacji',
format: FormatTyp.Default,
width: 'auto',
},
{
name: 'Wartosc',
title: 'Treść informacji',
format: FormatTyp.Default,
width: '*',
},
];
const tableFakturaZaliczkowa: FormContentState = getContentTable<(typeof fakturaZaliczkowa)[0]>(
fakturaZaliczkowaHeader,
fakturaZaliczkowa,
'*',
[0, 0, 0, 0]
);
if (tableFakturaZaliczkowa.content) {
table.push(tableFakturaZaliczkowa.content);
}
return table;
}

View File

@@ -0,0 +1,123 @@
import { beforeEach, describe, expect, it, test, vi } from 'vitest';
import { generatePlatnosc } from './Platnosc';
import type { Platnosc, RachunekBankowy, Skonto } from '../../types/fa2.types';
import type { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
generateLine,
generateTwoColumns,
getContentTable,
hasValue,
} from '../../../shared/PDF-functions';
import { getFormaPlatnosciString } from '../../../shared/generators/common/functions';
import { generujRachunekBankowy } from './RachunekBankowy';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]),
createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]),
generateLine: vi.fn((): Content[] => [{ line: true } as any]),
generateTwoColumns: vi.fn((left: any[], right: any[], margins?: number[]): Content[] => [
{ twoColumns: { left, right }, margins } as any,
]),
getTable: vi.fn((data: any): any[] => data ?? []),
getValue: vi.fn((obj: any) => obj?._text ?? obj ?? ''),
getContentTable: vi.fn(() => ({ content: [{ text: 'mockTable' }] })),
hasValue: vi.fn((v: any) => !!v),
}));
vi.mock('../../../shared/generators/common/functions', () => ({
getFormaPlatnosciString: vi.fn((v: any) => `Forma: ${v}`),
}));
vi.mock('./RachunekBankowy', () => ({
generujRachunekBankowy: vi.fn((data: any, label: string): Content[] => [{ text: label }]),
}));
const mockedCreateLabelText = vi.mocked(createLabelText);
describe(generatePlatnosc.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
test.each([
[
{
Zaplacono: { _text: '1' },
DataZaplaty: '2025-10-01',
FormaPlatnosci: 'Przelew',
} as Partial<Platnosc>,
'Zapłacono',
],
[
{ ZnacznikZaplatyCzesciowej: { _text: '1' }, FormaPlatnosci: 'Gotówka' } as Partial<Platnosc>,
'Zapłata częściowa',
],
[
{ ZnacznikZaplatyCzesciowej: { _text: '2' }, FormaPlatnosci: 'Karta' } as Partial<Platnosc>,
'Zapłata częściowa',
],
[{} as Partial<Platnosc>, 'Brak zapłaty'],
])(
'generuje poprawnie informacje o płatności dla %o',
(platnosc: Partial<Platnosc>, expected: string): void => {
const result: Content = generatePlatnosc(platnosc as Platnosc);
expect(generateLine).toHaveBeenCalled();
expect(createHeader).toHaveBeenCalledWith('Płatność');
const labelCall = mockedCreateLabelText.mock.calls.find(
([label, value]): boolean => value === expected
) || ['defaultLabel', 'defaultValue'];
expect(labelCall).toBeDefined();
expect((result as Content[]).length).toBeGreaterThan(0);
}
);
it('dodaje informacje o formie płatności jeśli hasValue zwraca true', () => {
const platnosc: Partial<Platnosc> = { FormaPlatnosci: { _text: 'Karta' } };
const result = generatePlatnosc(platnosc as Platnosc);
expect(hasValue).toHaveBeenCalledWith({ _text: 'Karta' });
expect(getFormaPlatnosciString).toHaveBeenCalledWith({ _text: 'Karta' });
});
it('generuje tabelę zapłaty częściowej i terminów płatności', () => {
const platnosc: Partial<Platnosc> = {
ZnacznikZaplatyCzesciowej: { _text: '1' },
ZaplataCzesciowa: [
{ DataZaplatyCzesciowej: { _text: '2025-10-01' }, KwotaZaplatyCzesciowej: { _text: '100' } },
],
TerminPlatnosci: [{ Termin: { _text: '2025-10-15' } }],
};
(getContentTable as any).mockReturnValueOnce({ content: [{ text: 'mockTable1' }] });
(getContentTable as any).mockReturnValueOnce({ content: [{ text: 'mockTable2' }] });
const result = generatePlatnosc(platnosc as Platnosc);
expect(generateTwoColumns).toHaveBeenCalled();
expect((result as Content[]).length).toBeGreaterThan(0);
});
it('dodaje rachunki bankowe i skonto', () => {
const platnosc: Partial<Platnosc> = {
RachunekBankowy: ['123'] as RachunekBankowy[],
RachunekBankowyFaktora: ['456'] as RachunekBankowy[],
Skonto: { WarunkiSkonta: '7 dni', WysokoscSkonta: '2%' } as Skonto,
};
const result = generatePlatnosc(platnosc as Platnosc);
expect(generujRachunekBankowy).toHaveBeenCalledTimes(2);
expect(createHeader).toHaveBeenCalledWith('Płatność');
expect(createLabelText).toHaveBeenCalledWith('Warunki skonta: ', '7 dni');
expect(createLabelText).toHaveBeenCalledWith('Wysokość skonta: ', '2%');
});
it('zwraca pustą tablicę jeśli platnosc undefined', () => {
const result = generatePlatnosc(undefined);
expect(result).toEqual([]);
});
});

View File

@@ -0,0 +1,134 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
generateLine,
generateTwoColumns,
getContentTable,
getTable,
hasValue,
} from '../../../shared/PDF-functions';
import { HeaderDefine } from '../../../shared/types/pdf-types';
import { Platnosc, TerminPlatnosci, ZaplataCzesciowa } from '../../types/fa2.types';
import { getFormaPlatnosciString } from '../../../shared/generators/common/functions';
import { generujRachunekBankowy } from './RachunekBankowy';
import FormatTyp from '../../../shared/enums/common.enum';
import { FP } from '../../types/fa1.types';
import { FormContentState } from '../../../shared/types/additional-data.types';
export function generatePlatnosc(platnosc: Platnosc | undefined): Content {
if (!platnosc) {
return [];
}
const terminPlatnosci: TerminPlatnosci[] = getTable(platnosc.TerminPlatnosci);
const zaplataCzesciowaHeader: HeaderDefine[] = [
{
name: 'Termin',
title: 'Termin płatności',
format: FormatTyp.Default,
},
];
if (terminPlatnosci.some((termin: TerminPlatnosci): FP | undefined => termin.TerminOpis)) {
zaplataCzesciowaHeader.push({ name: 'TerminOpis', title: 'Opis płatności', format: FormatTyp.Default });
}
const zaplataCzesciowaNaglowek: HeaderDefine[] = [
{
name: 'DataZaplatyCzesciowej',
title: 'Data zapłaty częściowej',
format: FormatTyp.Default,
},
{ name: 'KwotaZaplatyCzesciowej', title: 'Kwota zapłaty częściowej', format: FormatTyp.Currency },
{ name: 'FormaPlatnosci', title: 'Forma płatności', format: FormatTyp.FormOfPayment },
];
const table: Content[] = [generateLine(), ...createHeader('Płatność')];
if (platnosc.Zaplacono?._text === '1') {
table.push(createLabelText('Informacja o płatności: ', 'Zapłacono'));
table.push(createLabelText('Data zapłaty: ', platnosc.DataZaplaty, FormatTyp.Date));
} else if (platnosc.ZnacznikZaplatyCzesciowej?._text === '1') {
table.push(createLabelText('Informacja o płatności: ', 'Zapłata częściowa'));
} else {
table.push(createLabelText('Informacja o płatności: ', 'Brak zapłaty'));
}
if (hasValue(platnosc.FormaPlatnosci)) {
table.push(createLabelText('Forma płatności: ', getFormaPlatnosciString(platnosc.FormaPlatnosci)));
} else {
if (platnosc.OpisPlatnosci?._text) {
table.push(createLabelText('Forma płatności: ', 'Płatność inna'));
table.push(createLabelText('Opis płatności innej: ', platnosc.OpisPlatnosci));
}
}
const zaplataCzesciowa: ZaplataCzesciowa[] = getTable(platnosc.ZaplataCzesciowa);
const tableZaplataCzesciowa: FormContentState = getContentTable<(typeof zaplataCzesciowa)[0]>(
zaplataCzesciowaNaglowek,
zaplataCzesciowa,
'*'
);
const terminPatnosciContent: TerminPlatnosci[] = terminPlatnosci.map(
(platnosc: TerminPlatnosci): TerminPlatnosci => {
if (!terminPlatnosci.some((termin: TerminPlatnosci): FP | undefined => termin.TerminOpis)) {
return platnosc;
} else {
return {
...platnosc,
TerminOpis: {
_text: `${platnosc.Termin?._text ?? ''}`,
} as any,
};
}
}
);
const tableTerminPlatnosci: FormContentState = getContentTable<(typeof terminPlatnosci)[0]>(
zaplataCzesciowaHeader,
terminPatnosciContent,
'*'
);
if (zaplataCzesciowa.length > 0 && terminPlatnosci.length > 0) {
table.push(
generateTwoColumns(
tableZaplataCzesciowa.content ?? [],
tableTerminPlatnosci.content ?? [],
[0, 4, 0, 0]
)
);
} else if (terminPlatnosci.length > 0) {
if (tableTerminPlatnosci.content) {
table.push(generateTwoColumns([], tableTerminPlatnosci.content));
}
} else if (zaplataCzesciowa.length > 0 && tableZaplataCzesciowa.content) {
table.push(tableZaplataCzesciowa.content);
}
const rachunekBankowy: Content[][] = getTable(platnosc.RachunekBankowy as Record<string, FP>[]).map(
(rachunek) => generujRachunekBankowy([rachunek], 'Numer rachunku bankowego')
);
const rachunekBankowyFaktora: Content[][] = getTable(
platnosc.RachunekBankowyFaktora as Record<string, FP>[]
).map((rachunek) => generujRachunekBankowy([rachunek], 'Numer rachunku bankowego faktora'));
const rachunkiBankowe: Content[][] = [...rachunekBankowy, ...rachunekBankowyFaktora];
if (rachunkiBankowe.length > 0) {
rachunkiBankowe.forEach((rachunek, index) => {
if (index % 2 === 0) {
table.push(generateTwoColumns(rachunek, rachunkiBankowe[index + 1] ?? []));
}
});
}
if (platnosc.Skonto) {
table.push(createHeader('Skonto', [0, 0]));
table.push(createLabelText('Warunki skonta: ', platnosc.Skonto.WarunkiSkonta));
table.push(createLabelText('Wysokość skonta: ', platnosc.Skonto.WysokoscSkonta));
}
return table;
}

View File

@@ -0,0 +1,100 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { generatePodmiot1 } from './Podmiot1';
import type { Podmiot1 } from '../../types/fa2.types';
import type { Content } from 'pdfmake/interfaces';
import { createHeader, createLabelText, formatText } from '../../../shared/PDF-functions';
import { generateAdres } from './Adres';
import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]),
createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]),
formatText: vi.fn((text: string, style?: any): Content => ({ text, style })),
getValue: vi.fn((val) => val?._text || ''),
hasValue: vi.fn((val) => Boolean(val && val._text)),
}));
vi.mock('./Adres', () => ({
generateAdres: vi.fn((adres: any): Content[] => [{ text: 'mockAdres' }]),
}));
vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot1Dto', () => ({
generateDaneIdentyfikacyjneTPodmiot1Dto: vi.fn((data: any): Content[] => [
{ text: 'mockDaneIdentyfikacyjne' },
]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn((data: any): Content[] => [{ text: 'mockDaneKontaktowe' }]),
}));
describe(generatePodmiot1.name, (): void => {
beforeEach(() => {
vi.clearAllMocks();
});
it('generates basic seller data', (): void => {
const podmiot: Partial<Podmiot1> = {
NrEORI: 'EORI123' as any,
PrefiksPodatnika: 'PL' as any,
};
const result: Content[] = generatePodmiot1(podmiot as Podmiot1);
expect(createHeader).toHaveBeenCalledWith('Sprzedawca');
expect(createLabelText).toHaveBeenCalledWith('Numer EORI: ', 'EORI123');
expect(createLabelText).toHaveBeenCalledWith('Prefiks VAT: ', 'PL');
expect((result as any[]).some((c) => c?.[0]?.text.includes('EORI123'))).toBe(true);
});
it('generates identification data if present', (): void => {
const podmiot: Partial<Podmiot1> = {
DaneIdentyfikacyjne: { NIP: { _text: 'nip' }, Nazwa: { _text: 'Nazwa' } },
};
const result: Content[] = generatePodmiot1(podmiot as Podmiot1);
expect(generateDaneIdentyfikacyjneTPodmiot1Dto).toHaveBeenCalledWith({
NIP: { _text: 'nip' },
Nazwa: { _text: 'Nazwa' },
});
expect(result.some((c: Content): boolean => (c as any).text === 'mockDaneIdentyfikacyjne')).toBe(true);
});
it('generates address and correspondence address', (): void => {
const podmiot: Partial<Podmiot1> = {
Adres: { KodKraju: { _text: 'Ulica 1' } },
AdresKoresp: { KodKraju: { _text: 'Ulica 2' } },
};
const result: Content[] = generatePodmiot1(podmiot as Podmiot1);
expect(generateAdres).toHaveBeenCalledWith({ KodKraju: { _text: 'Ulica 1' } });
expect(generateAdres).toHaveBeenCalledWith({ KodKraju: { _text: 'Ulica 2' } });
expect(result.some((c) => (c as any).text === 'mockAdres')).toBe(true);
expect(formatText).toHaveBeenCalledWith('Adres', ['Label', 'LabelMargin']);
expect(formatText).toHaveBeenCalledWith('Adres do korespondencji', ['Label', 'LabelMargin']);
});
it('generates contact data', (): void => {
const podmiot: Partial<Podmiot1> = {
DaneKontaktowe: [{ Telefon: { _text: '123' } }],
};
const result = generatePodmiot1(podmiot as Podmiot1);
expect(generateDaneKontaktowe).toHaveBeenCalledWith([{ Telefon: { _text: '123' } }]);
expect(result.some((c: Content): boolean => (c as any).text === 'mockDaneKontaktowe')).toBe(true);
});
it('generates taxpayer status ', () => {
const podmiot: Partial<Podmiot1> = {
StatusInfoPodatnika: { _text: '1' },
};
const result: Content[] = generatePodmiot1(podmiot as Podmiot1);
expect(createLabelText).toHaveBeenCalledWith(
expect.stringContaining('Status podatnika'),
expect.stringContaining('Stan likwidacji')
);
expect(result.some((c: Content): boolean => (c as any).text === 'mockDaneKontaktowe')).toBe(false);
});
});

View File

@@ -0,0 +1,42 @@
import { Content } from 'pdfmake/interfaces';
import { createHeader, createLabelText, formatText, getValue, hasValue } from '../../../shared/PDF-functions';
import { Podmiot1 } from '../../types/fa2.types';
import { generateAdres } from './Adres';
import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
import FormatTyp from '../../../shared/enums/common.enum';
import { TAXPAYER_STATUS } from '../../../shared/consts/const';
export function generatePodmiot1(podmiot1: Podmiot1): Content[] {
const result: Content[] = createHeader('Sprzedawca');
result.push(
createLabelText('Numer EORI: ', podmiot1.NrEORI),
createLabelText('Prefiks VAT: ', podmiot1.PrefiksPodatnika)
);
if (podmiot1.DaneIdentyfikacyjne) {
result.push(...generateDaneIdentyfikacyjneTPodmiot1Dto(podmiot1.DaneIdentyfikacyjne));
}
if (podmiot1.Adres) {
result.push(formatText('Adres', [FormatTyp.Label, FormatTyp.LabelMargin]), generateAdres(podmiot1.Adres));
}
if (podmiot1.AdresKoresp) {
result.push(
formatText('Adres do korespondencji', [FormatTyp.Label, FormatTyp.LabelMargin]),
...generateAdres(podmiot1.AdresKoresp)
);
}
if (podmiot1.DaneKontaktowe) {
result.push(
formatText('Dane kontaktowe', [FormatTyp.Label, FormatTyp.LabelMargin]),
...generateDaneKontaktowe(podmiot1.DaneKontaktowe)
);
}
if (hasValue(podmiot1.StatusInfoPodatnika)) {
const statusInfo: string = TAXPAYER_STATUS[getValue(podmiot1.StatusInfoPodatnika)!];
result.push(createLabelText('Status podatnika: ', statusInfo));
}
return result;
}

View File

@@ -0,0 +1,92 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Content } from 'pdfmake/interfaces';
import type { Podmiot1, Podmiot1K } from '../../types/fa2.types';
import { generatePodmiot1Podmiot1K } from './Podmiot1Podmiot1K';
import { generateAdres } from './Adres';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]),
createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]),
getTable: vi.fn((data: any) => data || []),
formatText: vi.fn((text: string, style?: any): Content => ({ text, style })),
verticalSpacing: vi.fn((margin: number) => ({ margin })),
generateColumns: vi.fn((left, right) => ({ columns: [left, right] })),
getValue: vi.fn(() => '1'),
}));
vi.mock('./Adres', () => ({
generateAdres: vi.fn((adres: any): Content[] => [{ text: 'mockAddress' }]),
}));
vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot1Dto', async () => {
const actual = await vi.importActual('./PodmiotDaneIdentyfikacyjneTPodmiot1Dto');
return {
...actual,
generateDaneIdentyfikacyjneTPodmiot1Dto: vi.fn((data: any): Content[] => [
{ text: 'mockDaneIdentyfikacyjne' },
]),
};
});
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn((data: any): Content[] => [{ text: 'mockDaneKontaktowe' }]),
}));
vi.mock('./Podmiot1Podmiot1K', async () => {
const original: any = await vi.importActual('./Podmiot1Podmiot1K');
return {
...original,
generateCorrectedContent: vi.fn((podmiot: any): Content[] => [
{ text: `mockCorrectedContent-${podmiot?.PrefiksPodatnika?._text || 'noPrefix'}` },
]),
};
});
describe(generatePodmiot1Podmiot1K.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('generates main header and columns', () => {
const podmiot1: Podmiot1 = { NrEORI: '123' } as any;
const podmiot1K: Podmiot1K = {} as any;
const result: any = generatePodmiot1Podmiot1K(podmiot1, podmiot1K);
expect(result[0]).toEqual({ text: 'Sprzedawca', style: 'header' });
expect(Array.isArray(result[1][0])).toBe(true);
expect(Array.isArray(result[1][1])).toBe(true);
expect(result[1].length).toBeGreaterThan(0);
expect(result[1].length).toBe(2);
expect(result[2]).toHaveProperty('columns');
expect(Array.isArray(result[2].columns[0])).toBe(true);
expect(Array.isArray(result[2].columns[1])).toBe(false);
expect(result[2].columns[0].length).toBeGreaterThan(0);
expect(result[3]).toEqual({ margin: 1 });
});
it('calls generateAdres if AdresKoresp exists', () => {
const podmiot1: Podmiot1 = { NrEORI: '123', AdresKoresp: { Ulica: 'Test' } } as any;
const podmiot1K: Podmiot1K = {} as any;
generatePodmiot1Podmiot1K(podmiot1, podmiot1K);
expect(generateAdres).toHaveBeenCalledWith(podmiot1.AdresKoresp);
});
it('handles all fields together', () => {
const podmiot1: Podmiot1 = {
NrEORI: '123',
DaneIdentyfikacyjne: { NIP: '123', Nazwa: 'Firma' } as any,
DaneKontaktowe: [{ Telefon: '123' }] as any,
StatusInfoPodatnika: 'active',
AdresKoresp: { Ulica: 'Test' },
} as any;
const podmiot1K: Podmiot1K = { PrefiksPodatnika: { _text: 'PL' } } as any;
const result: any = generatePodmiot1Podmiot1K(podmiot1, podmiot1K);
expect(result.length).toBe(4);
expect(result[0]).toEqual({ text: 'Sprzedawca', style: 'header' });
expect(result[1][0]).toBeInstanceOf(Array);
expect(result[1][1]).toBeInstanceOf(Array);
expect(result[2].columns[0]).toBeInstanceOf(Array);
expect(result[3]).toEqual({ margin: 1 });
});
});

View File

@@ -0,0 +1,72 @@
import { Content } from 'pdfmake/interfaces';
import {
createHeader,
createLabelText,
formatText,
generateColumns,
getTable,
getValue,
verticalSpacing,
} from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { Podmiot1, Podmiot1K } from '../../types/fa2.types';
import { generateAdres } from './Adres';
import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
import { TAXPAYER_STATUS } from '../../../shared/consts/const';
export function generatePodmiot1Podmiot1K(podmiot1: Podmiot1, podmiot1K: Podmiot1K): Content[] {
const result: Content[] = createHeader('Sprzedawca');
let firstColumn: Content[] = [];
let secondColumn: Content[] = [];
firstColumn.push(createHeader('Dane identyfikacyjne'), createLabelText('Numer EORI: ', podmiot1.NrEORI));
if (podmiot1.DaneIdentyfikacyjne) {
firstColumn.push(...generateDaneIdentyfikacyjneTPodmiot1Dto(podmiot1.DaneIdentyfikacyjne));
}
if (podmiot1.DaneKontaktowe) {
firstColumn.push(generateDaneKontaktowe(getTable(podmiot1.DaneKontaktowe)));
}
if (podmiot1.StatusInfoPodatnika) {
const statusInfo: string = TAXPAYER_STATUS[getValue(podmiot1.StatusInfoPodatnika)!];
firstColumn.push(createLabelText('Status podatnika: ', statusInfo));
}
if (firstColumn.length) {
result.push(firstColumn);
}
firstColumn = generateCorrectedContent(podmiot1K, 'Treść korygowana');
secondColumn = generateCorrectedContent(podmiot1, 'Treść korygująca');
if (podmiot1.AdresKoresp) {
secondColumn.push(
formatText('Adres do korespondencji', [FormatTyp.Label, FormatTyp.LabelMargin]),
generateAdres(podmiot1.AdresKoresp)
);
}
if (firstColumn.length || secondColumn.length) {
result.push(generateColumns([firstColumn, secondColumn]));
}
if (result.length) {
result.push(verticalSpacing(1));
}
return result;
}
export function generateCorrectedContent(podmiot: Podmiot1 | Podmiot1K, header: string): Content[] {
const result: Content[] = [];
result.push(createHeader(header));
if (podmiot.PrefiksPodatnika?._text) {
result.push(createLabelText('Prefiks VAT: ', podmiot.PrefiksPodatnika));
}
if (podmiot.DaneIdentyfikacyjne) {
result.push(...generateDaneIdentyfikacyjneTPodmiot1Dto(podmiot.DaneIdentyfikacyjne));
}
if (podmiot.Adres) {
result.push(formatText('Adres', [FormatTyp.Label, FormatTyp.LabelMargin]), generateAdres(podmiot.Adres));
}
return result;
}

View File

@@ -0,0 +1,87 @@
import { generateCorrectedContent } from './Podmiot1Podmiot1K';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Content } from 'pdfmake/interfaces';
import { FP, Podmiot1K } from '../../types/fa2.types';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]),
createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]),
formatText: vi.fn((text: string, style?: any): Content => ({ text, style })),
verticalSpacing: vi.fn().mockImplementation((size) => ({ margin: size })),
generateColumns: vi.fn((left, right) => ({ columns: [left, right] })),
}));
vi.mock('./Adres', () => ({
generateAdres: vi.fn((adres: any): Content[] => [{ text: 'mockAdres' }]),
}));
vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot1Dto', () => ({
generateDaneIdentyfikacyjneTPodmiot1Dto: vi.fn((data: any): Content[] => [
{ text: 'mockDaneIdentyfikacyjne' },
]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn((data: any): Content[] => [{ text: 'mockDaneKontaktowe' }]),
}));
describe(generateCorrectedContent.name, () => {
const header = 'Treść korygowana';
beforeEach(() => {
vi.clearAllMocks();
});
it('should generate corrected content base values ', () => {
const podmiot: Podmiot1K = {};
const result: Content[] = generateCorrectedContent(podmiot, header);
expect((result[0] as any).some((c: any) => c.text === header)).toBe(true);
});
it('should generate corrected content with prefixPodatnika ', () => {
const podmiot: Podmiot1K = { PrefiksPodatnika: { _text: 'tekst' } };
const result: Content[] = generateCorrectedContent(podmiot, header);
expect(result.length).equal(2);
expect((result[0] as any).some((c: any) => c.text === header)).toBe(true);
expect((result[1] as any).some((c: any) => c.text.includes('Prefiks VAT: '))).toBe(true);
expect((result[1] as any).some((c: any) => c.text.includes('[object Object]'))).toBe(true);
});
it('should generate corrected content with id data ', () => {
const podmiot: Podmiot1K = { DaneIdentyfikacyjne: { NIP: 'NIP' as FP, Nazwa: 'nazwa' as FP } };
const result: Content[] = generateCorrectedContent(podmiot, header);
expect(result.length).equal(2);
expect((result[0] as any).some((c: any) => c.text === header)).toBe(true);
expect((result[1] as any).text.includes('mockDaneIdentyfikacyjne')).toBe(true);
});
it('should generate corrected content with address ', () => {
const podmiot: Podmiot1K = { Adres: { KodKraju: 'PL' as FP } };
const result: any = generateCorrectedContent(podmiot, header);
expect(result.length).equal(3);
expect((result[0] as any).some((c: any) => c.text === header)).toBe(true);
expect((result[1] as any).text.includes('Adres')).toBe(true);
expect((result[2] as any).some((c: { text: string }) => c.text === 'mockAdres')).toBe(true);
});
it('handles all fields together', () => {
const podmiot: Podmiot1K = {
PrefiksPodatnika: { _text: 'PL' },
DaneIdentyfikacyjne: { NIP: '123' as FP, Nazwa: 'Firma' as FP },
Adres: { KodKraju: 'PL' as FP },
};
const result: any = generateCorrectedContent(podmiot, header);
expect((result[0] as any).some((c: any) => c.text === header)).toBe(true);
expect((result[1] as any).some((c: any) => c.text.includes('Prefiks VAT: '))).toBe(true);
expect((result[1] as any).some((c: any) => c.text.includes('[object Object]'))).toBe(true);
expect((result[2] as any).text.includes('mockDaneIdentyfikacyjne')).toBe(true);
expect((result[3] as any).text.includes('Adres')).toBe(true);
expect((result[4] as any).some((c: { text: string }) => c.text === 'mockAdres')).toBe(true);
expect(result.length).toBe(5);
});
});

View File

@@ -0,0 +1,112 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Content } from 'pdfmake/interfaces';
import type { FP, Podmiot2 } from '../../types/fa2.types';
import { generatePodmiot2 } from './Podmiot2';
import { createHeader, createLabelText, formatText } from '../../../shared/PDF-functions';
import { generateAdres } from './Adres';
import { generateDaneIdentyfikacyjneTPodmiot2Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot2Dto';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]),
createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]),
formatText: vi.fn((text: string, style?: any): Content => ({ text, style })),
}));
vi.mock('./Adres', async () => {
const actual = await vi.importActual('./Adres');
return {
...actual,
generateAdres: vi.fn((adres: any): Content[] => [{ text: 'mockAddress' }]),
};
});
vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot2Dto', async () => {
const actual = await vi.importActual('./PodmiotDaneIdentyfikacyjneTPodmiot2Dto');
return {
...actual,
generateDaneIdentyfikacyjneTPodmiot2Dto: vi.fn((data: any): Content[] => [
{ text: 'mockDaneIdentyfikacyjne' },
]),
};
});
vi.mock('./PodmiotDaneKontaktowe', async () => {
const actual = await vi.importActual('./PodmiotDaneKontaktowe');
return {
...actual,
generateDaneKontaktowe: vi.fn((data: any): Content[] => [{ text: 'mockDaneKontaktowe' }]),
};
});
describe(generatePodmiot2.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('generates basic header and identifiers', () => {
const podmiot: Partial<Podmiot2> = { IDNabywcy: 'ID123' as FP, NrEORI: 'EORI123' as FP };
const result = generatePodmiot2(podmiot as Podmiot2);
expect(createHeader).toHaveBeenCalledWith('Nabywca');
expect(createLabelText).toHaveBeenCalledWith('Identyfikator nabywcy: ', 'ID123');
expect(createLabelText).toHaveBeenCalledWith('Numer EORI: ', 'EORI123');
expect(result[0]).toEqual({ text: 'Nabywca', style: 'header' });
});
it('generates identification data if provided', () => {
const podmiot: Partial<Podmiot2> = {
DaneIdentyfikacyjne: { NIP: '123', Nazwa: 'Firma' } as any,
};
const result = generatePodmiot2(podmiot as Podmiot2);
expect(generateDaneIdentyfikacyjneTPodmiot2Dto).toHaveBeenCalledWith(podmiot.DaneIdentyfikacyjne);
expect(result.some((c: any) => c.text === 'mockDaneIdentyfikacyjne')).toBe(true);
});
it('generates address and correspondence address', () => {
const podmiot: Partial<Podmiot2> = {
Adres: { KodKraju: 'PL' } as any,
AdresKoresp: { KodKraju: 'PL-K' } as any,
};
const result = generatePodmiot2(podmiot as Podmiot2);
expect(generateAdres).toHaveBeenCalledWith(podmiot.Adres);
expect(generateAdres).toHaveBeenCalledWith(podmiot.AdresKoresp);
expect(formatText).toHaveBeenCalledWith('Adres', ['Label', 'LabelMargin']);
expect(formatText).toHaveBeenCalledWith('Adres do korespondencji', ['Label', 'LabelMargin']);
expect(result.some((c: any) => c.text === 'mockAddress')).toBe(true);
});
it('generates contact data and client number if provided', () => {
const podmiot: Partial<Podmiot2> = {
DaneKontaktowe: [{ Telefon: '123' }] as any,
NrKlienta: 'CL123' as FP,
};
const result = generatePodmiot2(podmiot as Podmiot2);
expect(generateDaneKontaktowe).toHaveBeenCalledWith(podmiot.DaneKontaktowe);
expect(createLabelText).toHaveBeenCalledWith('Numer klienta: ', 'CL123');
expect(result.some((c: any) => c.text === 'mockDaneKontaktowe')).toBe(true);
});
it('handles all fields together', () => {
const podmiot: Partial<Podmiot2> = {
DaneIdentyfikacyjne: { NIP: '123', Nazwa: 'Firma' } as any,
Adres: { KodKraju: 'PL' } as any,
AdresKoresp: { KodKraju: 'PL-K' } as any,
DaneKontaktowe: [{ Telefon: '123' }] as any,
};
const result: Content = generatePodmiot2(podmiot as Podmiot2);
expect(result[0]).toEqual({ text: 'Nabywca', style: 'header' });
expect(result.some((c: any): boolean => c.text === 'mockDaneIdentyfikacyjne')).toBe(true);
expect(
result.filter((c: any): boolean => c.text === 'mockAddress').length + (result as any)[5].length
).toBe(2);
expect(result.some((c: any): boolean => c.text === 'mockDaneKontaktowe')).toBe(true);
});
});

View File

@@ -0,0 +1,42 @@
import { Content } from 'pdfmake/interfaces';
import { createHeader, createLabelText, formatText } from '../../../shared/PDF-functions';
import FormatTyp from '../../../shared/enums/common.enum';
import { Podmiot2 } from '../../types/fa2.types';
import { generateAdres } from './Adres';
import { generateDaneIdentyfikacyjneTPodmiot2Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot2Dto';
import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe';
import { DaneIdentyfikacyjneTPodmiot2Dto } from '../../types/fa2-additional-types';
export function generatePodmiot2(podmiot2: Podmiot2): Content[] {
const result: Content[] = createHeader('Nabywca');
result.push(
createLabelText('Identyfikator nabywcy: ', podmiot2.IDNabywcy),
createLabelText('Numer EORI: ', podmiot2.NrEORI)
);
if (podmiot2.DaneIdentyfikacyjne) {
result.push(
...generateDaneIdentyfikacyjneTPodmiot2Dto(
podmiot2.DaneIdentyfikacyjne as DaneIdentyfikacyjneTPodmiot2Dto
)
);
}
if (podmiot2.Adres) {
result.push(formatText('Adres', [FormatTyp.Label, FormatTyp.LabelMargin]), generateAdres(podmiot2.Adres));
}
if (podmiot2.AdresKoresp) {
result.push(
formatText('Adres do korespondencji', [FormatTyp.Label, FormatTyp.LabelMargin]),
...generateAdres(podmiot2.AdresKoresp)
);
}
if (podmiot2.DaneKontaktowe) {
result.push(
formatText('Dane kontaktowe', [FormatTyp.Label, FormatTyp.LabelMargin]),
...generateDaneKontaktowe(podmiot2.DaneKontaktowe),
createLabelText('Numer klienta: ', podmiot2.NrKlienta)
);
}
return result;
}

View File

@@ -0,0 +1,111 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Content } from 'pdfmake/interfaces';
import type { Podmiot2, Podmiot2K } from '../../types/fa2.types';
import { generateAdres } from './Adres';
import { generatePodmiot2Podmiot2K } from './Podmiot2Podmiot2k';
vi.mock('../../../shared/PDF-functions', () => ({
createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]),
createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]),
formatText: vi.fn((text: string, style?: any): Content => ({ text, style })),
getTable: vi.fn((data: any) => data || []),
hasValue: vi.fn((value: any) => value !== undefined && value !== null),
verticalSpacing: vi.fn((margin: number) => ({ margin })),
generateColumns: vi.fn((left, right) => ({ columns: [left, right] })),
generateLine: vi.fn((): Content[] => [{ line: true } as any]),
}));
vi.mock('./Adres', () => ({
generateAdres: vi.fn((adres: any): Content[] => [{ text: 'mockAddress' }]),
}));
vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot1Dto', () => ({
generateDaneIdentyfikacyjneTPodmiot1Dto: vi.fn((data: any): Content[] => [
{ text: 'mockDaneIdentyfikacyjne' },
]),
}));
vi.mock('./PodmiotDaneKontaktowe', () => ({
generateDaneKontaktowe: vi.fn((data: any): Content[] => [{ text: 'mockDaneKontaktowe' }]),
}));
describe(generatePodmiot2Podmiot2K.name, () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('generates header and first column with ID, contact, and client number', () => {
const podmiot2: Podmiot2 = {
NrEORI: 'EORI123',
DaneKontaktowe: [{ Telefon: '123' }],
NrKlienta: 'CL123',
} as any;
const podmiot2K: Podmiot2K = { IDNabywcy: 'ID123' } as any;
const result = generatePodmiot2Podmiot2K(podmiot2, podmiot2K) as any;
expect(result[0]).toEqual([{
"line": true,
}]);
expect(result[1][0]).toHaveProperty('text');
expect(result[1][0]).toHaveProperty('style');
expect(result[1][0]).toEqual({ text: 'Nabywca', style: 'header' });
expect(Array.isArray(result[2].columns[0])).toBe(true);
expect(Array.isArray(result[2].columns[1])).toBe(true);
expect(result[2].columns[0].length).toBeGreaterThan(0);
expect(result[2].columns[1].length).toBe(0);
expect(result[2]).toHaveProperty('columns');
expect(Array.isArray(result[2].columns[0])).toBe(true);
expect(Array.isArray(result[2].columns[1])).toBe(true);
expect(result[2].columns[0].length).toBeGreaterThan(0);
expect(result[4]).toEqual({ margin: 1 });
});
it('calls generateAdres if AdresKoresp exists', () => {
const podmiot2: Podmiot2 = { NrEORI: 'EORI123', AdresKoresp: { Ulica: 'Test' } } as any;
const podmiot2K: Podmiot2K = {} as any;
generatePodmiot2Podmiot2K(podmiot2, podmiot2K);
expect(generateAdres).toHaveBeenCalledWith(podmiot2.AdresKoresp);
});
it('generates corrected content columns', () => {
const podmiot2: Podmiot2 = { NrEORI: 'EORI123' } as any;
const podmiot2K: Podmiot2K = { IDNabywcy: 'ID123' } as any;
const result = generatePodmiot2Podmiot2K(podmiot2, podmiot2K) as any;
expect(Array.isArray(result[2].columns[0])).toBe(true);
expect(Array.isArray(result[2].columns[1])).toBe(true);
expect(result[2].columns[0].length).toBeGreaterThanOrEqual(0);
});
it('adds vertical spacing at the end', () => {
const podmiot2: Podmiot2 = { NrEORI: 'EORI123' } as any;
const podmiot2K: Podmiot2K = {} as any;
const result = generatePodmiot2Podmiot2K(podmiot2, podmiot2K);
expect(result[result.length - 1]).toEqual({ margin: 1 });
});
it('handles all fields together', () => {
const podmiot2: Podmiot2 = {
NrEORI: 'EORI123',
DaneIdentyfikacyjne: { NIP: '123' } as any,
DaneKontaktowe: [{ Telefon: '123' }],
NrKlienta: 'CL123',
AdresKoresp: { Ulica: 'Test' },
} as any;
const podmiot2K: Podmiot2K = { IDNabywcy: 'ID123' } as any;
const result = generatePodmiot2Podmiot2K(podmiot2, podmiot2K) as any;
expect(result.length).toBeGreaterThan(3);
expect(result[0]).toEqual([{
"line": true,
}]);
expect(result[2]).toHaveProperty('columns');
expect(Array.isArray(result[2].columns[0])).toBe(true);
expect(Array.isArray(result[2].columns[1])).toBe(true);
expect(result[2].columns[0].length).toBeGreaterThanOrEqual(0);
expect(result[result.length - 1]).toHaveProperty('margin');
});
});

Some files were not shown because too many files have changed in this diff Show More