commit 8160bc3c232be4e6bc761ddaf24fd59a2bda5a10 Author: David Ali Date: Thu Jan 8 18:17:47 2026 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96571bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Pliki generowane przez Mavena +target/ +pom.xml.bak + +# Skompilowane pliki (jeśli kompilujesz ręcznie) +bin/ + +# Pliki wynikowe generowane przez aplikację +output/ + +# Ignorowanie ręcznie dodanych bibliotek JAR (zależności obsługuje Maven) +*.jar + +# Pliki logów +*.log +logs/ + +# Pliki konfiguracyjne IDE +.idea/ +*.iml +*.ipr +*.iws +.classpath +.project +.settings/ +.vscode/ + +# Pliki systemowe (macOS, Windows) +.DS_Store +Thumbs.db + +README.html +src/main/java/*.bak* + +REJESTR_SPRZEDAZY_VAT_DOKUMENTACJA.txt \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..08bed57 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# 🚀 Konwerter CSV do Optima XML + +Cześć! 👋 + +To jest małe narzędzie backendowe (napisane w Javie), które konwertuje pliki CSV do formatu XML zgodnego z Comarch ERP Optima. + +Twoim zadaniem będzie dorobienie do tego fajnego UI/UX, a to jest na razie sam "silnik", który możesz sobie uruchomić i testować. + +## 🤓 Co jest potrzebne? + +1. **Java (JDK)**: Wersja 11 lub nowsza. +2. **Maven** (Zalecane): Projekt jest skonfigurowany przy użyciu Mavena. To najprostszy sposób, bo sam pobierze wszystkie potrzebne biblioteki (w tym `opencsv` do czytania CSV). + * Aby sprawdzić, czy masz Mavena, wpisz w terminalu: `mvn -version`. + +--- + +## 📂 Struktura Projektu + +Struktura katalogów jest bardzo prosta: + +``` +. +├── input/ <-- TU WRZUCASZ PLIKI WEJŚCIOWE +│ ├── kontrahenci.csv +│ └── rejestr_sprzedazy.csv +├── output/ <-- TUTAJ POJAWI SIĘ WYNIKOWY XML +│ └── (na razie pusty) +├── src/main/java/ <-- CAŁY KOD ŹRÓDŁOWY JAVY +│ ├── CsvToOptimaXmlConverter.java (główny plik z logiką) +│ └── ValidationException.java (nasz specjalny plik do błędów) +└── pom.xml <-- "Mózg" projektu (plik konfiguracyjny Mavena) +``` + +--- + +## ▶️ Jak to uruchomić (Sposób 1 - Zalecany: Maven) + +To najłatwiejszy sposób. Będąc w głównym katalogu projektu (tam, gdzie jest plik `pom.xml`): + +### 1. Kompilacja i pobranie bibliotek + +```bash +mvn compile +``` +*Za pierwszym razem Maven połączy się z internetem i pobierze bibliotekę `opencsv`. Następnie skompiluje cały kod.* + +### 2. Uruchomienie programu + +```bash +mvn exec:java +``` +*To polecenie uruchomi główną klasę. Jeśli wszystko pójdzie dobrze, w terminalu zobaczysz komunikat o sukcesie, a w katalogu `output/` pojawi się plik `optima_import.xml`.* + +### (Opcjonalnie) Czyszczenie projektu + +```bash +mvn clean +``` +*Usuwa skompilowane pliki (katalog `target`), jeśli chcesz zacząć "na czysto".* + +--- + +## ▶️ Jak to uruchomić (Sposób 2 - Ręcznie, bez Mavena) + +Jeśli nie masz lub nie chcesz instalować Mavena, możesz to zrobić "klasycznie". + +### 1. Ręczne pobranie biblioteki + +Musisz ręcznie pobrać plik `.jar` biblioteki `opencsv`. +* **Wersja:** `5.12.0` (lub nowsza) +* **Pobierz z:** [Maven Central (kliknij `jar` obok "Downloads")](https://repo1.maven.org/maven2/com/opencsv/opencsv/5.12.0/opencsv-5.12.0.jar) +* **Co zrobić:** Pobrany plik `opencsv-5.12.0.jar` umieść w głównym katalogu projektu (obok `pom.xml` i `input`). + +### 2. Ręczna kompilacja + +Będąc w głównym katalogu, utwórz katalog na skompilowane klasy: +```bash +mkdir bin +``` +Następnie skompiluj kod, wskazując bibliotekę i katalog docelowy: +```bash +javac -d bin -cp .:opencsv-5.12.0.jar src/main/java/*.java +``` + +### 3. Ręczne uruchomienie + +Teraz uruchom program, wskazując w "classpath" zarówno bibliotekę, jak i katalog `bin`: +```bash +java -cp .:opencsv-5.12.0.jar:bin CsvToOptimaXmlConverter +``` +*(Uwaga: na Windowsie zamiast `:` użyj `;` jako separatora w `classpath`)* + +--- + +## 🚦 Ważne informacje + +### Separator CSV + +Program jest obecnie ustawiony na domyślny separator CSV, czyli **przecinek (`,`)**. Wszystkie pliki `.csv` w katalogu `input/` muszą używać przecinków, aby zostały poprawnie wczytane. + +### Walidacja ("Test Pani Marisi") + +Program nie jest "głupi". Zanim zacznie konwersję, sprawdza pliki CSV: +* Weryfikuje, czy wszystkie **obowiązkowe kolumny** (np. `AKRONIM` u kontrahenta, `POZ_NETTO` na fakturze) są wypełnione. +* Jeśli czegoś brakuje, program **zatrzyma się** i wyświetli w terminalu bardzo czytelny, "ludzki" komunikat o błędzie (np. `Błąd w danych (w fakturze o numerze 'FV/2/2024')! Brakuje wartości w kolumnie: 'POZ_NETTO'.`). + +Dzięki temu od razu wiesz, co i gdzie poprawić w pliku CSV. + +--- + +## 🗒️ TODO (Co dalej?) + +Lista zadań do zrobienia, aby ten projekt był jeszcze lepszy: + +* [ ] **Zbudować graficzny interfejs (UI)**, który będzie zawierał: + * [ ] Przycisk do wyboru pliku `kontrahenci.csv`. + * [ ] Przycisk do wyboru pliku `rejestr_sprzedazy.csv`. + * [ ] Przycisk "Start", który uruchamia konwersję. + * [ ] Czytelny komunikat (labelka, okienko pop-up) informujący o sukcesie lub wyświetlający błąd walidacji. + +* [ ] **Rozbudować walidację** danych wejściowych (zgodnie z dokumentacją Comarch): + * [ ] **Walidacja typów danych** (np. czy `POZ_NETTO` jest liczbą, a `DATA_WYSTAWIENIA` poprawną datą). + * [ ] **Walidacja długości pól** (np. czy `AKRONIM` nie przekracza 20 znaków). + * [ ] **Walidacja logiki biznesowej** (np. sprawdzenie, czy w rejestrze sprzedaży `POZ_NETTO * (POZ_STAWKA_VAT / 100)` faktycznie równa się `POZ_VAT`, z uwzględnieniem zaokrągleń). + +Powodzenia! Daj znać, jakbyś miała jakieś problemy. \ No newline at end of file diff --git a/Recording 2026-01-07 151248.mp4 b/Recording 2026-01-07 151248.mp4 new file mode 100644 index 0000000..4e7a054 Binary files /dev/null and b/Recording 2026-01-07 151248.mp4 differ diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 0000000..32b7c76 --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + com.twojafirma + csv-konwerter + 1.0.0 + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + OptimaConverterApp + + + + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + OptimaConverterApp + + + + + + + + + + 1.8 + 1.8 + UTF-8 + + diff --git a/input/kontrahenci.csv b/input/kontrahenci.csv new file mode 100644 index 0000000..ee90c1b --- /dev/null +++ b/input/kontrahenci.csv @@ -0,0 +1,14 @@ +ID_ZRODLA,AKRONIM,FINALNY,ADRES_STATUS,ADRES_NAZWA1,ADRES_NAZWA2,ADRES_KRAJ,ADRES_MIASTO,ADRES_ULICA,ADRES_NR_DOMU,ADRES_NR_LOKALU,ADRES_KOD_POCZTOWY,ADRES_POCZTA,ADRES_NIP,ADRES_TELEFON1,ADRES_EMAIL +KH-001,ABC-POL,Nie,aktualny,ABC Polska Sp. z o.o.,,Polska,Warszawa,Aleje Jerozolimskie,120,,00-001,Warszawa,7531116070,221234567,biuro@abcpolska.pl +KH-002,MEBLOMIX,Nie,aktualny,MEBLOMIX Jan Kowalski,,Polska,Kraków,Wielicka,40,12,30-552,Kraków,7532350589,129876543,jan.kowalski@meblomix.pl +KH-003,NOWAK,Tak,aktualny,Adam Nowak,,Polska,Nysa,Rynek,5,,48-300,Nysa,7531493878,774123123,adam.nowak@prywatny.pl +KH-004,TRANSPOL,Nie,aktualny,Trans-Pol S.A.,Oddział Zachodni,Polska,Wrocław,Logistyczna,1,,55-040,Bielany Wrocławskie,7551831137,713334455,logistyka@transpol.com.pl +KH-005,ZIELINSKA,Tak,aktualny,Ewa Zielińska,,Polska,Gdańsk,Długa,22,3,80-831,Gdańsk,6891156409,589998877,ewa.zielinska@gmail.com +KH-006,BUDMAX,Nie,aktualny,BUDMAX Hurtownia Budowlana,Piotr Nowakowski,Polska,Poznań,Przemysłowa,50,,61-500,Poznań,7532200602,612223344,zamowienia@budmax-poznan.pl +KH-007,TECH-MAX,Nie,aktualny,Technologie Max Sp.k.,,Polska,Warszawa,Prosta,10,,00-850,Warszawa,7532443405,, +KH-008,MED-CENTRUM,Nie,aktualny,Centrum Medyczne Zdrowie,,Polska,Łódź,Piotrkowska,5,,90-004,Łódź,7532022419,, +KH-009,KOWALSKI,Tak,aktualny,Jan Kowalski Detal,,Polska,Gdynia,Morska,1,,81-001,Gdynia,7471778441,, +KH-010,BUD-REM,Nie,aktualny,Usługi Remontowe,,Polska,Katowice,Węglowa,3,,40-100,Katowice,7541436623,, +KH-011,EXP-IM,Nie,aktualny,Export Import Ltd,,Polska,Kraków,Floriańska,12,,31-021,Kraków,7531927549,, +KH-012,SZKOLA,Nie,aktualny,Szkoła Językowa,,Polska,Poznań,Szkolna,3,,61-832,Poznań,7532439272,, +KH-013,LOGISTYK,Nie,aktualny,Logistyka i Transport,,Polska,Wrocław,Autostradowa,99,,54-424,Wrocław,7532446177,, \ No newline at end of file diff --git a/input/rejestr_sprzedazy.csv b/input/rejestr_sprzedazy.csv new file mode 100644 index 0000000..168f8ed --- /dev/null +++ b/input/rejestr_sprzedazy.csv @@ -0,0 +1,18 @@ +REJESTR,NUMER,DATA_WYSTAWIENIA,DATA_SPRZEDAZY,TERMIN,KONTRAHENT_KOD,KONTRAHENT_NAZWA,KONTRAHENT_NIP,ADRES_MIASTO,ADRES_ULICA,POZYCJE_NETTO,POZYCJE_VAT,POZYCJE_STAWKA,POZYCJE_RODZAJ,PLATNOSCI_FORMA,PLATNOSCI_KWOTA,PLATNOSCI_TERMIN,KOLUMNA_KPR +SPRZEDAŻ,FS/1/01/2026,2026-01-02,2026-01-02,2026-01-09,ABC-POL,ABC Polska Sp. z o.o.,7531116070,Warszawa,Aleje Jerozolimskie 120,1000.00,230.00,23,usługi,przelew,1230.00,2026-01-09,Sprzedaż +SPRZEDAŻ,FS/2/01/2026,2026-01-03,2026-01-03,2026-01-03,MEBLOMIX,MEBLOMIX Jan Kowalski,7532350589,Kraków,Wielicka 40,200.00|50.00,46.00|11.50,23|23,towary|towary,gotówka,307.50,2026-01-03,Sprzedaż +SPRZEDAŻ,FS/3/01/2026,2026-01-05,2026-01-05,2026-01-19,BUDMAX,BUDMAX Hurtownia Budowlana,7532200602,Poznań,Przemysłowa 50,500.00|1000.00|250.00|100.00|50.00,115.00|230.00|57.50|23.00|11.50,23|23|23|23|23,towary|towary|towary|usługi|usługi,przelew|gotówka,1737.00|600.00,2026-01-19|2026-01-05,Sprzedaż +SPRZEDAŻ,FS/4/01/2026,2026-01-07,2026-01-07,2026-01-14,TRANSPOL,Trans-Pol S.A.,7551831137,Wrocław,Logistyczna 1,100.00|100.00,5.00|8.00,5|8,towary|towary,przelew,213.00,2026-01-14,Sprzedaż +SPRZEDAŻ,FS/5/01/2026,2026-01-08,2026-01-08,2026-01-08,ZIELINSKA,Ewa Zielińska,6891156409,Gdańsk,Długa 22,3000.00|500.00|150.00,690.00|115.00|34.50,23|23|23,usługi|towary|towary,karta,4489.50,2026-01-08,Sprzedaż +SPRZEDAŻ,FS/6/01/2026,2026-01-10,2026-01-10,2026-01-24,ABC-POL,ABC Polska Sp. z o.o.,7531116070,Warszawa,Aleje Jerozolimskie 120,5000.00|200.00|100.00|50.00,1150.00|46.00|23.00|11.50,23|23|23|23,usługi|towary|towary|towary,przelew,6580.50,2026-01-24,Sprzedaż +SPRZEDAŻ,FS/7/01/2026,2026-01-12,2026-01-12,2026-01-12,NOWAK,Adam Nowak,7531493878,Nysa,Rynek 5,1000.00,80.00,8,towary,gotówka,1080.00,2026-01-12,Sprzedaż +SPRZEDAŻ,FS/8/01/2026,2026-01-15,2026-01-15,2026-01-29,BUDMAX,BUDMAX Hurtownia Budowlana,7532200602,Poznań,Przemysłowa 50,50.00|50.00|50.00|50.00|50.00|50.00,11.50|11.50|11.50|11.50|11.50|11.50,23|23|23|23|23|23,towary|towary|towary|towary|towary|towary,przelew|gotówka,300.00|69.00,2026-01-29|2026-01-15,Sprzedaż +SPRZEDAŻ,FS/9/01/2026,2026-01-18,2026-01-18,2026-01-25,MEBLOMIX,MEBLOMIX Jan Kowalski,7532350589,Kraków,Wielicka 40,1200.00|300.00,276.00|69.00,23|23,towary|usługi,przelew,1845.00,2026-01-25,Sprzedaż +SPRZEDAŻ,FS/10/01/2026,2026-01-20,2026-01-20,2026-01-27,ZIELINSKA,Ewa Zielińska,6891156409,Gdańsk,Długa 22,400.00|400.00|400.00|400.00|400.00,92.00|92.00|92.00|92.00|92.00,23|23|23|23|23,usługi|usługi|usługi|usługi|usługi,przelew,2460.00,2026-01-27,Sprzedaż +SPRZEDAŻ,FS/11/01/2026,2026-01-21,2026-01-21,2026-01-28,TECH-MAX,Technologie Max Sp.k.,7532443405,Warszawa,Prosta 10,100.00|200.00|300.00,23.00|16.00|0.00,23|8|0,towary|towary|towary,przelew,639.00,2026-01-28,Sprzedaż +SPRZEDAŻ,FS/12/01/2026,2026-01-22,2026-01-22,2026-02-05,MED-CENTRUM,Centrum Medyczne Zdrowie,7532022419,Łódź,Piotrkowska 5,1500.00|50.00,0.00|11.50,zw|23,usługi|towary,przelew,1561.50,2026-02-05,Sprzedaż +SPRZEDAŻ,FS/13/01/2026,2026-01-23,2026-01-23,2026-01-23,KOWALSKI,Jan Kowalski Detal,7471778441,Gdynia,Morska 1,123.45|67.89,28.39|15.61,23|23,towary|towary,gotówka|karta,100.00|135.34,2026-01-23|2026-01-23,Sprzedaż +SPRZEDAŻ,FS/14/01/2026,2026-01-24,2026-01-24,2026-02-24,BUD-REM,Usługi Remontowe,7541436623,Katowice,Węglowa 3,10000.00,2300.00,23,usługi,przelew|przelew,6150.00|6150.00,2026-01-31|2026-02-28,Sprzedaż +SPRZEDAŻ,FS/15/01/2026,2026-01-25,2026-01-25,2026-01-25,EXP-IM,Export Import Ltd,7531927549,Kraków,Floriańska 12,2000.00|100.00,0.00|23.00,np|23,towary|usługi,przelew,2123.00,2026-01-25,Sprzedaż +SPRZEDAŻ,FS/16/01/2026,2026-01-26,2026-01-26,2026-01-26,SZKOLA,Szkoła Językowa,7532439272,Poznań,Szkolna 3,800.00|45.00,0.00|2.25,zw|5,usługi|towary,gotówka,847.25,2026-01-26,Sprzedaż +SPRZEDAŻ,FS/17/01/2026,2026-01-27,2026-01-27,2026-02-10,LOGISTYK,Logistyka i Transport,7532446177,Wrocław,Autostradowa 99,50.00|50.00|50.00|50.00,11.50|4.00|2.50|0.00,23|8|5|0,usługi|usługi|usługi|usługi,przelew,218.00,2026-02-10,Sprzedaż \ No newline at end of file diff --git a/optima_import.xml b/optima_import.xml new file mode 100644 index 0000000..3f42dec --- /dev/null +++ b/optima_import.xml @@ -0,0 +1,1085 @@ + + + + 2.00 + CSV_IMPORT + KSIEG + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.00 + CSV_IMPORT + KSIEG + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7320165 --- /dev/null +++ b/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + com.twojafirma + csv-konwerter + 1.0.0 + + + 1.8 + 1.8 + UTF-8 + + + + + com.opencsv + opencsv + 5.12.0 + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + OptimaConverterApp + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + OptimaConverterApp + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/OptimaConverterApp.java b/src/main/java/OptimaConverterApp.java new file mode 100644 index 0000000..9e92cb2 --- /dev/null +++ b/src/main/java/OptimaConverterApp.java @@ -0,0 +1,615 @@ +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import java.awt.*; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +// Importy OpenCSV +import com.opencsv.CSVParser; +import com.opencsv.CSVParserBuilder; +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import com.opencsv.exceptions.CsvValidationException; + +// Importy XML +import org.w3c.dom.CDATASection; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +public class OptimaConverterApp extends JFrame { + + // --- Klasa wyjątku wewnętrznego --- + public static class ValidationException extends Exception { + public ValidationException(String message) { super(message); } + } + + // Pola interfejsu + private JTextField txtKontrahenci; + private JTextField txtRejestr; + private JTextArea txtLogi; + private JButton btnKonwertuj; + + // Zmienne na ścieżki + private File fileKontrahenci; + private File fileRejestr; + + // Stałe Optima + private static final String WERSJA_STRUKTURY = "2.00"; + private static final String BAZA_ZRD_ID = "CSV_IMPORT"; + private static final String BAZA_DOC_ID = "KSIEG"; + + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + // ignorujemy błędy wyglądu + } + new OptimaConverterApp().setVisible(true); + }); + } + + public OptimaConverterApp() { + super("David Ali - s26567@st.pans.nysa.pl - Konwerter CSV do Optimy"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(800, 650); + setLocationRelativeTo(null); + setLayout(new BorderLayout(10, 10)); + + // Panel górny + JPanel pnlInput = new JPanel(new GridBagLayout()); + pnlInput.setBorder(BorderFactory.createTitledBorder("Krok 1: Wybierz pliki")); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(5, 5, 5, 5); + + // Wiersz 1: Kontrahenci + gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.1; + pnlInput.add(new JLabel("Kontrahenci (CSV):"), gbc); + + txtKontrahenci = new JTextField(); + txtKontrahenci.setEditable(false); + gbc.gridx = 1; gbc.weightx = 0.8; + pnlInput.add(txtKontrahenci, gbc); + + JButton btnWybierzKontr = new JButton("Wybierz..."); + gbc.gridx = 2; gbc.weightx = 0.1; + pnlInput.add(btnWybierzKontr, gbc); + + // Wiersz 2: Rejestr + gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 0.1; + pnlInput.add(new JLabel("Rejestr VAT (CSV):"), gbc); + + txtRejestr = new JTextField(); + txtRejestr.setEditable(false); + gbc.gridx = 1; gbc.weightx = 0.8; + pnlInput.add(txtRejestr, gbc); + + JButton btnWybierzRejestr = new JButton("Wybierz..."); + gbc.gridx = 2; gbc.weightx = 0.1; + pnlInput.add(btnWybierzRejestr, gbc); + + add(pnlInput, BorderLayout.NORTH); + + // Panel środkowy + txtLogi = new JTextArea(); + txtLogi.setEditable(false); + txtLogi.setFont(new Font("Monospaced", Font.PLAIN, 12)); + JScrollPane scrollPane = new JScrollPane(txtLogi); + scrollPane.setBorder(BorderFactory.createTitledBorder("Logi przetwarzania")); + add(scrollPane, BorderLayout.CENTER); + + // Panel dolny + JPanel pnlButton = new JPanel(new FlowLayout(FlowLayout.CENTER)); + btnKonwertuj = new JButton("KROK 2: GENERUJ XML"); + btnKonwertuj.setFont(new Font("Arial", Font.BOLD, 14)); + btnKonwertuj.setBackground(new Color(200, 240, 200)); + btnKonwertuj.setEnabled(false); + pnlButton.add(btnKonwertuj); + add(pnlButton, BorderLayout.SOUTH); + + // --- AKCJE --- + + btnWybierzKontr.addActionListener(e -> { + File f = chooseFile("Plik Kontrahentów"); + if (f != null) { + fileKontrahenci = f; + txtKontrahenci.setText(f.getAbsolutePath()); + log("Kontrahenci: " + f.getName()); + checkReady(); + } + }); + + btnWybierzRejestr.addActionListener(e -> { + File f = chooseFile("Plik Rejestru VAT"); + if (f != null) { + fileRejestr = f; + txtRejestr.setText(f.getAbsolutePath()); + log("Rejestr VAT: " + f.getName()); + checkReady(); + } + }); + + btnKonwertuj.addActionListener(e -> { + txtLogi.setText(""); + runConversion(); + }); + + log("Witaj. Wybierz pliki CSV."); + } + + private void checkReady() { + if (fileKontrahenci != null || fileRejestr != null) { + btnKonwertuj.setEnabled(true); + } + } + + private File chooseFile(String title) { + JFileChooser fc = new JFileChooser(new File(System.getProperty("user.dir"))); + fc.setDialogTitle(title); + fc.setFileFilter(new FileNameExtensionFilter("Pliki CSV", "csv")); + if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + return fc.getSelectedFile(); + } + return null; + } + + private void log(String msg) { + txtLogi.append(msg + "\n"); + txtLogi.setCaretPosition(txtLogi.getDocument().getLength()); + } + + // --- GŁÓWNA LOGIKA KONWERSJI --- + + private void runConversion() { + JFileChooser fc = new JFileChooser(new File(System.getProperty("user.dir"))); + fc.setDialogTitle("Zapisz plik XML"); + fc.setSelectedFile(new File("optima_import.xml")); + fc.setFileFilter(new FileNameExtensionFilter("Plik XML", "xml")); + + if (fc.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return; + File outputFile = fc.getSelectedFile(); + + new Thread(() -> { + try { + SwingUtilities.invokeLater(() -> btnKonwertuj.setEnabled(false)); + log(">>> Start konwersji..."); + + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Element rootElement = doc.createElement("ROOT"); + rootElement.setAttribute("xmlns", "http://www.comarch.pl/cdn/optima/offline"); + doc.appendChild(rootElement); + + // 1. Kontrahenci + if (fileKontrahenci != null) { + processKontrahenci(doc, rootElement, fileKontrahenci); + } + + // 2. Rejestr VAT + if (fileRejestr != null) { + processRejestrSprzedazy(doc, rootElement, fileRejestr); + } + + writeXml(doc, outputFile); + + log(">>> SUKCES! Plik zapisany: " + outputFile.getName()); + JOptionPane.showMessageDialog(this, "Gotowe!", "Sukces", JOptionPane.INFORMATION_MESSAGE); + + } catch (Exception e) { + log("!!! BŁĄD: " + e.getMessage()); + e.printStackTrace(); + JOptionPane.showMessageDialog(this, "Wystąpił błąd:\n" + e.getMessage(), "Błąd", JOptionPane.ERROR_MESSAGE); + } finally { + SwingUtilities.invokeLater(() -> btnKonwertuj.setEnabled(true)); + } + }).start(); + } + + // --- CZYTANIE CSV --- + private List> readCsvFile(File file) throws IOException, CsvValidationException, ValidationException { + List> records = new ArrayList<>(); + + CSVParser parser = new CSVParserBuilder().withSeparator(',').build(); + + try (CSVReader reader = new CSVReaderBuilder(new FileReader(file, StandardCharsets.UTF_8)) + .withCSVParser(parser) + .build()) { + + String[] header = reader.readNext(); + if (header == null) return records; + + // Usuwanie BOM + if (header.length > 0 && header[0].startsWith("\uFEFF")) { + header[0] = header[0].substring(1); + } + + int lineNumber = 1; // Licznik linii (1 to nagłówek) + String[] line; + while ((line = reader.readNext()) != null) { + lineNumber++; + + // WALIDACJA LICZBY KOLUMN + if (line.length != header.length) { + throw new ValidationException( + "Błąd krytyczny w wierszu " + lineNumber + ": " + + "Nieprawidłowa liczba kolumn. Oczekiwano: " + header.length + + ", otrzymano: " + line.length + ". Sprawdź separatory w pliku CSV." + ); + } + + Map row = new HashMap<>(); + for (int i = 0; i < header.length; i++) { + row.put(header[i].trim(), line[i].trim()); + } + records.add(row); + } + } + return records; + } + + // --- PROCESOWANIE KONTRAHENTÓW --- + private void processKontrahenci(Document doc, Element rootElement, File csvFile) throws Exception { + log("Przetwarzam kontrahentów..."); + Element kontrahenciNode = doc.createElement("KONTRAHENCI"); + addRequiredHeaderTags(doc, kontrahenciNode); + + List> rows = readCsvFile(csvFile); + + int count = 0; + for (Map row : rows) { + count++; + String nazwa = row.get("ADRES_NAZWA1"); + log("Przetwarzam: " + nazwa); + + Element kontrahent = doc.createElement("KONTRAHENT"); + + createCdataElement(doc, kontrahent, "ID_ZRODLA", row.get("ID_ZRODLA")); + createCdataElement(doc, kontrahent, "AKRONIM", row.get("AKRONIM")); + createCdataElement(doc, kontrahent, "FINALNY", row.get("FINALNY")); + createCdataElement(doc, kontrahent, "OPIS", row.get("OPIS")); + + Element adresy = doc.createElement("ADRESY"); + Element adres = doc.createElement("ADRES"); + + createCdataElement(doc, adres, "STATUS", row.get("ADRES_STATUS")); + createCdataElement(doc, adres, "NAZWA1", row.get("ADRES_NAZWA1")); + createCdataElement(doc, adres, "NAZWA2", row.get("ADRES_NAZWA2")); + createCdataElement(doc, adres, "KRAJ", row.get("ADRES_KRAJ")); + createCdataElement(doc, adres, "MIASTO", row.get("ADRES_MIASTO")); + createCdataElement(doc, adres, "ULICA", row.get("ADRES_ULICA")); + createCdataElement(doc, adres, "NR_DOMU", row.get("ADRES_NR_DOMU")); + createCdataElement(doc, adres, "NR_LOKALU", row.get("ADRES_NR_LOKALU")); + createCdataElement(doc, adres, "KOD_POCZTOWY", row.get("ADRES_KOD_POCZTOWY")); + createCdataElement(doc, adres, "POCZTA", row.get("ADRES_POCZTA")); + createCdataElement(doc, adres, "NIP", row.get("ADRES_NIP")); + createCdataElement(doc, adres, "TELEFON1", row.get("ADRES_TELEFON1")); + createCdataElement(doc, adres, "EMAIL", row.get("ADRES_EMAIL")); + + adresy.appendChild(adres); + kontrahent.appendChild(adresy); + kontrahenciNode.appendChild(kontrahent); + } + + if (kontrahenciNode.hasChildNodes()) { + rootElement.appendChild(kontrahenciNode); + } + log("-> Kontrahenci OK: " + rows.size() + " szt."); + } + + // --- PROCESOWANIE FAKTUR --- + private void processRejestrSprzedazy(Document doc, Element rootElement, File csvFile) throws Exception { + Element rejestryNode = doc.createElement("REJESTRY_SPRZEDAZY_VAT"); + addRequiredHeaderTags(doc, rejestryNode); + + // Tu wywoła się nasz nowy, bezpieczny readCsvFile + List> rows = readCsvFile(csvFile); + + if (rows.isEmpty()) { + log("!!! UWAGA: Pusty plik rejestru lub zły separator."); + } + + int count = 0; + for (Map row : rows) { + count++; // To jest numer wiersza danych (nie linii w pliku) + String numerFaktury = row.get("NUMER"); + + // --- SEKCJA WALIDACJI (Nowość) --- + + // 1. Sprawdzenie czy kluczowe pola istnieją + if (getSafe(row, "NUMER", "").isEmpty()) { + throw new ValidationException("Błąd w wierszu " + count + ": Brak numeru faktury (kolumna NUMER)."); + } + + // 2. Walidacja dat [cite: 12, 55] + validateDate(row.get("DATA_WYSTAWIENIA"), "DATA_WYSTAWIENIA", count); + validateDate(row.get("DATA_SPRZEDAZY"), "DATA_SPRZEDAZY", count); + validateDate(row.get("TERMIN"), "TERMIN", count); + + // 3. Walidacja liczb w pozycjach (obsługa znaku "|") [cite: 88, 89, 90] + validateSplitNumeric(row.get("POZYCJE_NETTO"), "POZYCJE_NETTO", count); + validateSplitNumeric(row.get("POZYCJE_VAT"), "POZYCJE_VAT", count); + //validateSplitNumeric(row.get("POZYCJE_STAWKA"), "POZYCJE_STAWKA", count); + validateSplitVatRates(row.get("POZYCJE_STAWKA"), count); + + // 4. Walidacja płatności [cite: 126] + validateSplitNumeric(row.get("PLATNOSCI_KWOTA"), "PLATNOSCI_KWOTA", count); + + // --- KONIEC WALIDACJI --- + + log("Przetwarzam: " + numerFaktury); + + Element rejestr = doc.createElement("REJESTR_SPRZEDAZY_VAT"); + + // ... (reszta kodu bez zmian: tworzenie XML) ... + // Skopiuj tutaj resztę oryginalnego kodu z tej metody, + // zaczynając od: createCdataElement(doc, rejestr, "ID_ZRODLA", ... + + createCdataElement(doc, rejestr, "ID_ZRODLA", UUID.randomUUID().toString()); + createCdataElement(doc, rejestr, "MODUL", "Rejestr Vat"); + createCdataElement(doc, rejestr, "TYP", "Rejestr sprzedazy"); + createCdataElement(doc, rejestr, "REJESTR", getSafe(row, "REJESTR", "SPRZEDAŻ")); + + createCdataElement(doc, rejestr, "DATA_WYSTAWIENIA", row.get("DATA_WYSTAWIENIA")); + createCdataElement(doc, rejestr, "DATA_SPRZEDAZY", row.get("DATA_SPRZEDAZY")); + createCdataElement(doc, rejestr, "TERMIN", row.get("TERMIN")); + createCdataElement(doc, rejestr, "DATA_DATAOBOWIAZKUPODATKOWEGO", row.get("DATA_SPRZEDAZY")); + createCdataElement(doc, rejestr, "NUMER", numerFaktury); + + createCdataElement(doc, rejestr, "KOREKTA", "Nie"); + createCdataElement(doc, rejestr, "FINALNY", "Tak"); + createCdataElement(doc, rejestr, "PODATNIK_CZYNNY", "Tak"); + createCdataElement(doc, rejestr, "TYP_PODMIOTU", "kontrahent"); + + createCdataElement(doc, rejestr, "PODMIOT", row.get("KONTRAHENT_KOD")); + createCdataElement(doc, rejestr, "PODMIOT_NIP", row.get("KONTRAHENT_NIP")); + createCdataElement(doc, rejestr, "NAZWA1", row.get("KONTRAHENT_NAZWA")); + createCdataElement(doc, rejestr, "NIP", row.get("KONTRAHENT_NIP")); + createCdataElement(doc, rejestr, "MIASTO", row.get("ADRES_MIASTO")); + createCdataElement(doc, rejestr, "ULICA", row.get("ADRES_ULICA")); + + createCdataElement(doc, rejestr, "TYP_PLATNIKA", "kontrahent"); + createCdataElement(doc, rejestr, "PLATNIK", row.get("KONTRAHENT_KOD")); + + createCdataElement(doc, rejestr, "WALUTA", "PLN"); + createCdataElement(doc, rejestr, "KURS_WALUTY", "NBP"); + createCdataElement(doc, rejestr, "NOTOWANIE_WALUTY_ILE", "1"); + createCdataElement(doc, rejestr, "NOTOWANIE_WALUTY_ZA_ILE", "1"); + createCdataElement(doc, rejestr, "DATA_KURSU", row.get("DATA_WYSTAWIENIA")); + + // --- POZYCJE (SPLIT) --- + Element pozycjeNode = doc.createElement("POZYCJE"); + + String sNettoAll = getSafe(row, "POZYCJE_NETTO", "0"); + String sVatAll = getSafe(row, "POZYCJE_VAT", "0"); + String sStawkaAll = getSafe(row, "POZYCJE_STAWKA", "23"); + String sRodzajAll = getSafe(row, "POZYCJE_RODZAJ", "towary"); + String sKolumnaKpr = getSafe(row, "KOLUMNA_KPR", "Sprzedaż"); + + List listNetto = splitValues(sNettoAll); + List listVat = splitValues(sVatAll); + List listStawka = splitValues(sStawkaAll); + List listRodzaj = splitValues(sRodzajAll); + + int countPos = listNetto.size(); + + for (int i = 0; i < countPos; i++) { + Element poz = doc.createElement("POZYCJA"); + + String valNetto = getListVal(listNetto, i); + String valVat = getListVal(listVat, i); + String valStawkaRaw = getListVal(listStawka, i); // Oryginalna wartość z CSV + String valRodzaj = getListVal(listRodzaj, i); + + createCdataElement(doc, poz, "LP", String.valueOf(i + 1)); + + // --- LOGIKA OBSŁUGI ZW / NP --- + String stawkaDoXml = valStawkaRaw; + String statusVat = "opodatkowana"; + + if ("zw".equalsIgnoreCase(valStawkaRaw)) { + // Dla "zw": Stawka w XML to 0.00, Status to "zwolniona" + stawkaDoXml = "0.00"; + statusVat = "zwolniona"; + } else if ("np".equalsIgnoreCase(valStawkaRaw)) { + // Dla "np": Stawka w XML to 0.00, Status to "nie podlega" + stawkaDoXml = "0.00"; + statusVat = "nie podlega"; + } + + // Zapisujemy przetworzone wartości + createCdataElement(doc, poz, "STAWKA_VAT", stawkaDoXml); // + createCdataElement(doc, poz, "STATUS_VAT", statusVat); // + // -------------------------------- + + createCdataElement(doc, poz, "NETTO", valNetto); + createCdataElement(doc, poz, "VAT", valVat); + createCdataElement(doc, poz, "NETTO_SYS", valNetto); + createCdataElement(doc, poz, "VAT_SYS", valVat); + createCdataElement(doc, poz, "NETTO_SYS2", valNetto); + createCdataElement(doc, poz, "VAT_SYS2", valVat); + createCdataElement(doc, poz, "RODZAJ_SPRZEDAZY", valRodzaj); + createCdataElement(doc, poz, "UWZ_W_PROPORCJI", "tak"); + createCdataElement(doc, poz, "KOLUMNA_KPR", sKolumnaKpr); + + pozycjeNode.appendChild(poz); + } + rejestr.appendChild(pozycjeNode); + + // --- PŁATNOŚCI (SPLIT) --- + Element platnosciNode = doc.createElement("PLATNOSCI"); + + String sPlatForma = getSafe(row, "PLATNOSCI_FORMA", "gotówka"); + String sPlatKwota = getSafe(row, "PLATNOSCI_KWOTA", "0"); + String sPlatTermin = getSafe(row, "PLATNOSCI_TERMIN", row.get("TERMIN")); + + List listPlatForma = splitValues(sPlatForma); + List listPlatKwota = splitValues(sPlatKwota); + List listPlatTermin = splitValues(sPlatTermin); + + int countPlat = listPlatKwota.size(); + + for (int j = 0; j < countPlat; j++) { + Element plat = doc.createElement("PLATNOSC"); + + String valForma = getListVal(listPlatForma, j); + String valKwota = getListVal(listPlatKwota, j); + String valTermin = getListVal(listPlatTermin, j); + + createCdataElement(doc, plat, "TERMIN_PLAT", valTermin); + createCdataElement(doc, plat, "FORMA_PLATNOSCI_PLAT", valForma); + createCdataElement(doc, plat, "KWOTA_PLAT", valKwota); + createCdataElement(doc, plat, "WALUTA_PLAT", "PLN"); + createCdataElement(doc, plat, "WALUTA_DOK", "PLN"); + createCdataElement(doc, plat, "KURS_WALUTY_PLAT", "NBP"); + createCdataElement(doc, plat, "NOTOWANIE_WALUTY_ILE_PLAT", "1"); + createCdataElement(doc, plat, "NOTOWANIE_WALUTY_ZA_ILE_PLAT", "1"); + createCdataElement(doc, plat, "KWOTA_PLN_PLAT", valKwota); + createCdataElement(doc, plat, "KIERUNEK", "przychód"); + createCdataElement(doc, plat, "PODLEGA_ROZLICZENIU", "tak"); + + createCdataElement(doc, plat, "PLATNOSC_TYP_PODMIOTU", "kontrahent"); + createCdataElement(doc, plat, "PLATNOSC_PODMIOT", row.get("KONTRAHENT_KOD")); + + platnosciNode.appendChild(plat); + } + rejestr.appendChild(platnosciNode); + rejestryNode.appendChild(rejestr); + } + + rootElement.appendChild(rejestryNode); + log("-> Rejestr VAT OK: " + count + " dok."); + } + + // --- METODY POMOCNICZE --- + + private List splitValues(String raw) { + if (raw == null || raw.isEmpty()) return new ArrayList<>(); + String[] parts = raw.split("\\|"); + List result = new ArrayList<>(); + for (String p : parts) { + result.add(p.trim()); + } + return result; + } + + private void validateDate(String dateStr, String fieldName, int rowNum) throws ValidationException { + if (dateStr == null || dateStr.trim().isEmpty()) return; // Puste pola pomijamy (chyba że są wymagane, to sprawdzamy osobno) + + // Wymagany format RRRR-MM-DD + if (!dateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { + throw new ValidationException( + "Błąd w wierszu " + rowNum + ", kolumna '" + fieldName + "': " + + "Nieprawidłowy format daty ('" + dateStr + "'). Wymagane: RRRR-MM-DD." + ); + } + } + + private void validateNumeric(String numStr, String fieldName, int rowNum) throws ValidationException { + if (numStr == null || numStr.trim().isEmpty()) return; + + try { + // Optima oczekuje kropki jako separatora dziesiętnego w XML, ale w CSV użytkownik może mieć przecinek + String safeNum = numStr.replace(",", "."); + Double.parseDouble(safeNum); + } catch (NumberFormatException e) { + throw new ValidationException( + "Błąd w wierszu " + rowNum + ", kolumna '" + fieldName + "': " + + "Wartość '" + numStr + "' nie jest liczbą." + ); + } + } + + // Do walidacji pól, które są rozdzielane znakiem pipe "|" (np. netto|netto|netto) + private void validateSplitNumeric(String rawValue, String fieldName, int rowNum) throws ValidationException { + List parts = splitValues(rawValue); + for (String part : parts) { + validateNumeric(part, fieldName, rowNum); + } + } + + // Specjalna walidacja dla stawek VAT (akceptuje liczby oraz "zw" i "np") + private void validateSplitVatRates(String rawValue, int rowNum) throws ValidationException { + List parts = splitValues(rawValue); + for (String part : parts) { + String val = part.toLowerCase(); + // Jeśli to "zw" lub "np" - jest OK, idziemy dalej + if (val.equals("zw") || val.equals("np")) { + continue; + } + // W przeciwnym razie musi to być liczba + try { + Double.parseDouble(val.replace(",", ".")); + } catch (NumberFormatException e) { + throw new ValidationException( + "Błąd w wierszu " + rowNum + ", kolumna 'POZYCJE_STAWKA': " + + "Wartość '" + part + "' jest nieprawidłowa. Dozwolone: liczby (np. 23), 'zw' lub 'np'." + ); + } + } + } + + private String getListVal(List list, int index) { + if (list == null || list.isEmpty()) return ""; + if (index >= list.size()) return list.get(list.size() - 1); + return list.get(index); + } + + private String getSafe(Map row, String key, String defaultVal) { + String val = row.get(key); + if (val == null || val.trim().isEmpty()) return defaultVal; + return val.trim(); + } + + private void addRequiredHeaderTags(Document doc, Element parentNode) { + Element wersja = doc.createElement("WERSJA"); + wersja.setTextContent(WERSJA_STRUKTURY); + parentNode.appendChild(wersja); + + Element bazaZrd = doc.createElement("BAZA_ZRD_ID"); + bazaZrd.setTextContent(BAZA_ZRD_ID); + parentNode.appendChild(bazaZrd); + + Element bazaDoc = doc.createElement("BAZA_DOC_ID"); + bazaDoc.setTextContent(BAZA_DOC_ID); + parentNode.appendChild(bazaDoc); + } + + private void createCdataElement(Document doc, Element parent, String tagName, String text) { + Element element = doc.createElement(tagName); + if (text != null && !text.isEmpty()) { + CDATASection cdata = doc.createCDATASection(text); + element.appendChild(cdata); + } + parent.appendChild(element); + } + + private void writeXml(Document doc, File file) throws Exception { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(file); + transformer.transform(source, result); + } +} \ No newline at end of file diff --git a/src/main/java/ValidationException.java b/src/main/java/ValidationException.java new file mode 100644 index 0000000..288b80a --- /dev/null +++ b/src/main/java/ValidationException.java @@ -0,0 +1,9 @@ +/** + * Nasz własny, prosty typ wyjątku (błędu), który będzie używany + * do przekazywania "przyjaznych dla użytkownika" komunikatów walidacji. + */ +public class ValidationException extends Exception { + public ValidationException(String message) { + super(message); + } +} \ No newline at end of file