diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 4a72255..26e7500 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -25,6 +25,7 @@ Wystawianie faktur dla klientow z NIP poprzez integracje z Fakturownia (app.fakt | 123 | Receipts Export VAT Breakdown | 1/1 | Complete (2026-05-12; manual XLSX smoke pending operator) | | 124 | SMS Templates | 1/1 | Complete (2026-05-13; migration + manual SMS smoke pending operator) | | 125 | invoice_requested Import Fix (shopPRO+Allegro NIP detection, drop legacy is_invoice column) | 1/1 | Complete (2026-05-13; migration + manual smoke pending operator) | +| 126 | Invoice GUS Field Mapping Fix (KRS-based heuristic: JDG → name do "Imię i nazwisko", spółka → "Nazwa firmy") | 1/1 | Complete (2026-05-13; manual smoke pending operator) | Planowane kolejne fazy v3.7 (kandydaci, do rozplanowania): - Eksport XLSX listy wystawionych faktur (analogicznie do paragonow) @@ -507,4 +508,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md` --- *Roadmap created: 2026-03-12* -*Last updated: 2026-05-13 - Phase 125 UNIFY closed* +*Last updated: 2026-05-13 - Phase 126 UNIFY closed* diff --git a/.paul/STATE.md b/.paul/STATE.md index 892d764..65b6be1 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,19 +5,19 @@ See: .paul/PROJECT.md (updated 2026-05-07) **Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami. -**Current focus:** v3.7 Invoices + operational integrations - Phase 125 invoice_requested import fix complete (UNIFY closed). +**Current focus:** v3.7 Invoices + operational integrations - Phase 126 GUS field mapping fix complete (UNIFY closed). ## Current Position Milestone: v3.7 Invoices (Fakturownia integration) - In progress -Phase: 125 of TBD (invoice_requested import fix) - Complete -Plan: 125-01 complete +Phase: 126 of TBD (Invoice GUS field mapping fix) - Complete +Plan: 126-01 complete (SUMMARY.md created) Status: UNIFY complete, transition pending (commit + ROADMAP update) -Last activity: 2026-05-13 - UNIFY closed for .paul/phases/125-invoice-requested-import-fix/125-01-PLAN.md +Last activity: 2026-05-13 - Phase 126-01 UNIFY zakonczony Progress: -- Milestone v3.7: [##########] ~99% (Phase 113-125 complete; transition pending) -- Phase 125: [##########] 100% +- Milestone v3.7: [##########] ~99% (Phase 113-126 complete; transition pending) +- Phase 126: [##########] 100% ## Loop Position @@ -30,8 +30,8 @@ PLAN -> APPLY -> UNIFY ## Session Continuity Last session: 2026-05-13 -Stopped at: Phase 125-01 UNIFY closed; SUMMARY.md created -Next action: Phase transition (commit + ROADMAP update), then pick next v3.7 candidate or transition to v3.8 +Stopped at: Phase 126-01 UNIFY closed; SUMMARY.md created +Next action: Phase transition (commit + ROADMAP update), then manual smoke (AC-1) i wybor kolejnego kandydata v3.7 Resume file: .paul/ROADMAP.md ## Pending parallel work @@ -39,10 +39,9 @@ Resume file: .paul/ROADMAP.md ## Git State -Last phase commit: 522c94a feat(124): sms templates CRUD + order picker -Previous: 360eef1 feat(121+122): smsplanet conversation, notifications, default footer -Branch: main -Note: routes/web.php, DOCS/* i .paul/codebase/* zawierały zmiany z 118+121+122 nakładkowo i wszystkie wpadły do commitu 360eef1. +Last phase commit: 2ab461a feat(125): invoice_requested import fix + drop legacy is_invoice column +Previous: 522c94a feat(124): sms templates CRUD + order picker +Branch: main (4 commits ahead of origin/main) ## Pending Actions @@ -65,6 +64,7 @@ Note: routes/web.php, DOCS/* i .paul/codebase/* zawierały zmiany z 118+121+122 - Phase 125 follow-up: ponowne uruchomienie migracji powinno byc no-op (`ALTER TABLE orders COMMENT = 'phase-125 backfill no-op'`). - Phase 125 follow-up: zaimportuj nowe zamowienie shopPRO z `firm_nip` (bez kluczy w 5-elementowej liscie wczesniejszej heurystyki) -> potwierdz ze UI w zakladce Platnosci pokazuje zaznaczony checkbox „Klient prosi o fakture" i widoczny przycisk „Wystaw fakture". - Phase 121 transition note (rozwiązane): commit 360eef1 obejmuje Phase 121 i Phase 122 razem; per-faza hunk-split nie wykonany ze względu na nakładkowe modyfikacje plików. +- Phase 126 follow-up: manual smoke `/orders/1090/invoice/create` (JDG, NIP 5170167517) -> "Imie i nazwisko"="JACEK PYZIAK", "Nazwa firmy"="Project-Pro Pyziak Jacek" niezmieniona; drugi smoke na zamowieniu spolki z aktywnym KRS; `curl /api/nip/lookup?nip=5170167517` -> `data.is_jdg=true`. ## Deferred to Next Milestones diff --git a/.paul/changelog/2026-05-13.md b/.paul/changelog/2026-05-13.md index df67d79..ce8c15b 100644 --- a/.paul/changelog/2026-05-13.md +++ b/.paul/changelog/2026-05-13.md @@ -12,6 +12,8 @@ - [Phase 125, Plan 01] Allegro rozszerzenie `shouldRequestInvoice()`: detekcja `invoice.naturalPerson=false`, `invoice.address.taxId`, `invoice.companyName` (wczesniej tylko `invoice.required`). - [Phase 125, Plan 01] Migracja `20260513_000113_drop_orders_is_invoice_and_backfill_invoice_requested.sql` — idempotentna (information_schema guard); backfill 7 zamowien + DROP COLUMN `orders.is_invoice` (legacy z Phase 115). - [Phase 125, Plan 01] Cleanup `is_invoice` z `OrderImportRepository` (INSERT/params/docstring) i `OrdersRepository` (SELECT/hydrate); usuniete `shouldRequestInvoice()` z `ShopproOrdersSyncService` (zastapione heurystyka mappera). +- [Phase 126, Plan 01] Bugfix `/orders/{id}/invoice/create` GUS lookup: dla JDG (MF `subject.krs=''`) nazwa z MF trafia do "Imie i nazwisko"; dla spolki z KRS — do "Nazwa firmy". Drugiego pola nie nadpisuje — pre-fill z `order_addresses.name` zachowany (czesto trzyma pelna nazwe firmy JDG, np. "Project-Pro Pyziak Jacek" vs MF "JACEK PYZIAK"). +- [Phase 126, Plan 01] `MfWhitelistApiClient::lookupByNip()` zwraca dodatkowo `krs: string` i `is_jdg: bool` (true gdy `subject.krs === ''`). `InvoiceController::nipLookup` propaguje `is_jdg` w `data` JSON. JS `btn-gus-lookup` warunkowo wybiera target field. ## Zmienione pliki @@ -47,3 +49,8 @@ - `database/migrations/20260513_000113_drop_orders_is_invoice_and_backfill_invoice_requested.sql` - `.paul/phases/125-invoice-requested-import-fix/125-01-PLAN.md` - `.paul/phases/125-invoice-requested-import-fix/125-01-SUMMARY.md` +- `src/Core/Http/MfWhitelistApiClient.php` +- `src/Modules/Accounting/InvoiceController.php` +- `resources/views/accounting/invoice_form.php` +- `.paul/phases/126-invoice-gus-field-mapping/126-01-PLAN.md` +- `.paul/phases/126-invoice-gus-field-mapping/126-01-SUMMARY.md` diff --git a/.paul/codebase/architecture.md b/.paul/codebase/architecture.md index 34a0c22..e067b59 100644 --- a/.paul/codebase/architecture.md +++ b/.paul/codebase/architecture.md @@ -288,6 +288,20 @@ tests/ --- +## Phase 126 — Invoice GUS Field Mapping (KRS heuristic) + +### MfWhitelistApiClient (`src/Core/Http/MfWhitelistApiClient.php`) +- `lookupByNip()` zwraca dodatkowo `krs: string` i `is_jdg: bool` (true gdy `subject.krs === ''`). Pozostaly kontrakt bez zmian. +- Heurystyka: JDG = brak KRS w MF. Spolka = `krs` niepuste. Pattern do reuse w przyszlych formularzach opartych o NIP lookup. + +### InvoiceController::nipLookup (`/api/nip/lookup`) +- JSON `data` rozszerzony o `is_jdg: bool`. Konsumowane przez JS w `accounting/invoice_form.php`. + +### invoice_form.php JS — warunkowe mapowanie pola docelowego +- `d.is_jdg=true` (JDG): MF `name` (osoba fizyczna) -> `#buyer_name` (Imie i nazwisko). `#buyer_company_name` nie ruszane (pre-fill z `order_addresses.name` zachowany — czesto trzyma pelna nazwe firmy JDG). +- `d.is_jdg=false` (spolka): MF `name` (legal name) -> `#buyer_company_name`. `#buyer_name` nie ruszane (pre-fill z zamowienia — np. osoba kontaktowa). +- Pola adresowe (street/postal_code/city) zawsze nadpisywane. + ## Phase 116 - HostedSMS Integration Settings ### HostedSmsIntegrationRepository (`src/Modules/Settings/HostedSmsIntegrationRepository.php`) diff --git a/.paul/codebase/tech_changelog.md b/.paul/codebase/tech_changelog.md index 6579b1e..4c8dfc6 100644 --- a/.paul/codebase/tech_changelog.md +++ b/.paul/codebase/tech_changelog.md @@ -1,5 +1,34 @@ # Technical Changelog +## 2026-05-13 - Phase 126 Plan 01: Invoice GUS Field Mapping Fix (KRS heuristic) + +**Co zrobiono:** +- Bugfix `/orders/{id}/invoice/create`: po kliknieciu "Pobierz z GUS" wartosci "Imie i nazwisko" i "Nazwa firmy" sprawialy wrazenie zamienionych dla JDG. Root cause: MF Biala Lista dla JDG zwraca w `subject.name` osobe fizyczna (np. "JACEK PYZIAK"), a JS bezwarunkowo wpisywal te wartosc do `#buyer_company_name` — pole "Imie i nazwisko" zostawalo z pre-fillem z `order_addresses.name`, ktory dla JDG czesto trzyma pelna nazwe firmy (np. "Project-Pro Pyziak Jacek"). +- `MfWhitelistApiClient::lookupByNip()` — dodane do return array pola `krs: string` i `is_jdg: bool` (true gdy `subject.krs` jest puste). Pozostaly kontrakt (`name`, `tax_no`, address, regon, status_vat, raw) bez zmian. +- `InvoiceController::nipLookup` — propaguje `is_jdg` w JSON response `/api/nip/lookup` jako `data.is_jdg`. +- JS w `resources/views/accounting/invoice_form.php` — wybor pola docelowego dla `d.company_name` zalezy od `d.is_jdg`: + - `is_jdg=true` -> `#buyer_name` (Imie i nazwisko) + - `is_jdg=false` -> `#buyer_company_name` (Nazwa firmy) + Drugie pole nie jest tkniete — operator zachowuje pre-fill z zamowienia. Pola adresowe (street/postal_code/city) nadpisywane jak dotychczas. + +**Dlaczego:** +- MF Biala Lista nie eksponuje "nazwy firmy" dla JDG (pole `name` to wlasciciel — osoba fizyczna). Pre-fill z `order_addresses.name` jest dla JDG bardziej wartosciowy (zawiera pelna nazwe firmy z zamowienia) niz MF `name`, wiec nie powinien byc nadpisany. Dla spolki (krs!=null) MF `name` to legal name — wlasciwe miejsce to "Nazwa firmy". + +**Key decision:** +- Heurystyka JDG = `subject.krs === ''` (sygnal z MF). Pattern do reuse w innych miejscach jesli pojawia sie inny formularz oparty o NIP lookup. + +**Files modified:** +- `src/Core/Http/MfWhitelistApiClient.php` +- `src/Modules/Accounting/InvoiceController.php` +- `resources/views/accounting/invoice_form.php` + +**Pending verification:** +- AC-1: smoke `/orders/1090/invoice/create` (JDG, NIP 5170167517) -> "Imie i nazwisko"="JACEK PYZIAK", "Nazwa firmy"="Project-Pro Pyziak Jacek" niezmieniona. +- AC-2: smoke dla NIP spolki z aktywnym KRS -> "Nazwa firmy" otrzymuje legal name, "Imie i nazwisko" niezmienione. +- AC-3: `curl /api/nip/lookup?nip=5170167517` -> `data.is_jdg=true`. + +--- + ## 2026-05-13 - Phase 125 Plan 01: invoice_requested Import Fix **Co zrobiono:** diff --git a/.paul/phases/126-invoice-gus-field-mapping/126-01-PLAN.md b/.paul/phases/126-invoice-gus-field-mapping/126-01-PLAN.md new file mode 100644 index 0000000..ca818de --- /dev/null +++ b/.paul/phases/126-invoice-gus-field-mapping/126-01-PLAN.md @@ -0,0 +1,190 @@ +--- +phase: 126-invoice-gus-field-mapping +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/Core/Http/MfWhitelistApiClient.php + - src/Modules/Accounting/InvoiceController.php + - resources/views/accounting/invoice_form.php +autonomous: true +delegation: off +--- + + +## Goal +Naprawić mapowanie pól po kliknięciu "Pobierz z GUS" na `/orders/{id}/invoice/create` tak, aby +dane z MF Białej Listy trafiały do właściwych pól zależnie od typu podmiotu (JDG vs spółka z KRS). + +## Purpose +Operator wystawiający fakturę dla JDG widzi obecnie efekt zamiany pól: pole "Imię i nazwisko" +pokazuje pełną nazwę firmy z zamówienia (np. "Project-Pro Pyziak Jacek"), a "Nazwa firmy" jest +nadpisywane samym imieniem i nazwiskiem z MF (np. "JACEK PYZIAK"). Przyczyna: MF Biała Lista dla +JDG zwraca w `subject.name` osobę fizyczną (nie nazwę firmy), a JS bezwarunkowo wpisuje tę wartość +do "Nazwa firmy". Po fixie operator nie musi już ręcznie korygować pól po lookupie. + +## Output +- `MfWhitelistApiClient::lookupByNip()` zwraca dodatkowo flagę `is_jdg` (true gdy `subject.krs` + jest null/pusty). +- `InvoiceController::nipLookup` propaguje flagę w odpowiedzi JSON. +- JS w `invoice_form.php` dla `is_jdg=true` wpisuje MF `name` do `#buyer_name`, a dla `is_jdg=false` + do `#buyer_company_name`; drugiego pola nie nadpisuje. Adres (ulica/kod/miasto/NIP) nadpisywany + jak dotychczas. + + + + +- **Objaw buga** — Co dokładnie pokazuje formularz po kliknięciu "Pobierz z GUS" dla zam. 1090? + → Odpowiedź: Imię/nazw.=nazwa firmy z zamówienia ("Project-Pro Pyziak Jacek"); Nazwa firmy=imię i nazwisko klienta z MF ("JACEK PYZIAK"). +- **Typ podmiotu** — Jaki typ podmiotu zwraca MF dla NIP 5170167517? + → Odpowiedź: Zweryfikowane bezpośrednio — JDG, `subject.krs=null`, `subject.name="JACEK PYZIAK"`. +- **Mapowanie GUS** — Które mapowanie ma realizować GUS lookup? + → Odpowiedź: Heurystyka po KRS — `krs=null` (JDG) → name do "Imię i nazwisko"; `krs!=null` (spółka) → name do "Nazwa firmy". +- **Pole osobowe** — Czy zostawiamy "Imię i nazwisko" jako oddzielne pole? + → Odpowiedź: Tak — dla spółki = pre-fill z zamówienia (osoba kontaktowa); dla JDG = nazwisko z MF. + + +## Project Context +@.paul/PROJECT.md +@.paul/STATE.md +@.paul/codebase/architecture.md + +## Source Files +@src/Core/Http/MfWhitelistApiClient.php +@src/Modules/Accounting/InvoiceController.php +@resources/views/accounting/invoice_form.php + + + + +## AC-1: JDG (krs=null) — name z MF trafia w "Imię i nazwisko", "Nazwa firmy" niezmieniona +```gherkin +Given otwarte /orders/1090/invoice/create +And w polu "NIP nabywcy" wpisany "5170167517" +And pole "Imię i nazwisko" zawiera "Project-Pro Pyziak Jacek" (pre-fill z zamówienia) +And pole "Nazwa firmy" zawiera "Project-Pro Pyziak Jacek" (pre-fill z zamówienia) +When operator klika "Pobierz z GUS" +Then pole "Imię i nazwisko" ma wartość "JACEK PYZIAK" (z MF) +And pole "Nazwa firmy" zachowuje wartość "Project-Pro Pyziak Jacek" +And pola adres (ulica/kod/miejscowość) są nadpisane danymi z MF +``` + +## AC-2: Spółka z KRS — name z MF trafia w "Nazwa firmy", "Imię i nazwisko" niezmieniona +```gherkin +Given otwarte /orders/{id}/invoice/create dla zamówienia ze spółką (NIP z aktywnym KRS) +And pole "Imię i nazwisko" zawiera np. "Jan Kowalski" (osoba kontaktowa z zamówienia) +When operator klika "Pobierz z GUS" +Then pole "Nazwa firmy" otrzymuje legal name z MF (np. "ACME Sp. z o.o.") +And pole "Imię i nazwisko" zachowuje wartość "Jan Kowalski" +And pola adresowe są nadpisane danymi z MF +``` + +## AC-3: Response JSON z /api/nip/lookup zawiera flagę is_jdg +```gherkin +Given autoryzowane GET /api/nip/lookup?nip=5170167517 +When request zwraca 200 OK +Then JSON.data.is_jdg === true +And JSON.data.company_name === "JACEK PYZIAK" +And dla NIP-u spółki z aktywnym KRS JSON.data.is_jdg === false +``` + + + + + + + Task 1: MfWhitelistApiClient — eksponowanie flagi is_jdg + src/Core/Http/MfWhitelistApiClient.php + + W `lookupByNip()` po wczytaniu `$subject`: + - Wyciągnąć `$krs = trim((string) ($subject['krs'] ?? ''));` + - W zwracanym array dodać klucz `'is_jdg' => $krs === '',` (oraz opcjonalnie `'krs' => $krs` dla debugowalności). + - Zaktualizować docblock `@return array{...}` o `is_jdg: bool, krs: string`. + Unikać: zmiany pozostałych pól / kontraktu (`name`, `tax_no`, address, regon, status_vat). + + + Manualnie: w PHP REPL `(new MfWhitelistApiClient(new SslCertificateResolver()))->lookupByNip('5170167517')` zwraca `is_jdg=true`; + dla NIP-u spółki z aktywnym KRS zwraca `is_jdg=false`. + + AC-3 satisfied (kontrakt klienta). + + + + Task 2: InvoiceController::nipLookup — propagacja is_jdg do JSON + src/Modules/Accounting/InvoiceController.php + + W `nipLookup()` w bloku `data` rozszerzyć payload o `'is_jdg' => (bool) ($data['is_jdg'] ?? false),`. + Zachować pozostałe klucze (`company_name`, `tax_number`, `street`, `postal_code`, `city`, `country`, `regon`, `status_vat`). + Unikać: zmiany walidacji NIP, kontraktu błędów (422/502). + + + `curl -s -H "X-Requested-With: XMLHttpRequest" "https://orderpro.projectpro.pl/api/nip/lookup?nip=5170167517"` zwraca JSON z `data.is_jdg=true`. + + AC-3 satisfied (kontrakt endpointu). + + + + Task 3: invoice_form.php — JS warunkowe mapowanie name w zależności od is_jdg + resources/views/accounting/invoice_form.php + + W bloku `