Files
orderPRO/.paul/phases/126-invoice-gus-field-mapping/126-01-PLAN.md
Jacek Pyziak c758ec7c92 feat(126): invoice GUS field mapping fix (JDG/KRS heuristic)
MfWhitelistApiClient.lookupByNip() exposes is_jdg/krs from MF Biala Lista.
InvoiceController.nipLookup propagates is_jdg in JSON response.
invoice_form.php JS conditionally targets buyer_name (JDG) or
buyer_company_name (spolka z KRS); second field keeps zamowienie pre-fill.

Fixes apparent field swap on /orders/{id}/invoice/create after GUS lookup
for JDG (sole trader) where MF returns natural person in subject.name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:29:55 +02:00

191 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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
---
<objective>
## 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.
</objective>
<context>
<clarifications>
- **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.
</clarifications>
## 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
</context>
<acceptance_criteria>
## 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
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: MfWhitelistApiClient — eksponowanie flagi is_jdg</name>
<files>src/Core/Http/MfWhitelistApiClient.php</files>
<action>
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).
</action>
<verify>
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`.
</verify>
<done>AC-3 satisfied (kontrakt klienta).</done>
</task>
<task type="auto">
<name>Task 2: InvoiceController::nipLookup — propagacja is_jdg do JSON</name>
<files>src/Modules/Accounting/InvoiceController.php</files>
<action>
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).
</action>
<verify>
`curl -s -H "X-Requested-With: XMLHttpRequest" "https://orderpro.projectpro.pl/api/nip/lookup?nip=5170167517"` zwraca JSON z `data.is_jdg=true`.
</verify>
<done>AC-3 satisfied (kontrakt endpointu).</done>
</task>
<task type="auto">
<name>Task 3: invoice_form.php — JS warunkowe mapowanie name w zależności od is_jdg</name>
<files>resources/views/accounting/invoice_form.php</files>
<action>
W bloku `<script>` (handler `btn-gus-lookup`, ok. linii 250270) zmienić obecne bezwarunkowe
`buyer_company_name: d.company_name`:
1. Wydzielić mapowanie adresowe (street/postal/city) jako stałe.
2. Po pobraniu `d`:
- `var nameTargetId = d.is_jdg ? 'buyer_name' : 'buyer_company_name';`
- Wpisać `d.company_name` do `document.getElementById(nameTargetId)` (jeśli `d.company_name` niepuste).
- Pole `buyer_tax_number` nadpisywać jak dotychczas (`d.tax_number`).
- Pól `buyer_name`/`buyer_company_name` poza wybranym targetem NIE dotykać.
3. Pola adresowe (street/postal_code/city) nadpisywać niezmieniście.
Unikać:
- tworzenia nowych zależności JS / bibliotek,
- czyszczenia drugiego pola (operator może mieć tam istotne pre-fill / własne dopiski),
- zmiany etykiet / kolejności pól HTML.
</action>
<verify>
Manualnie w przeglądarce: `/orders/1090/invoice/create` → "Pobierz z GUS" → "Imię i nazwisko" = "JACEK PYZIAK",
"Nazwa firmy" pozostaje "Project-Pro Pyziak Jacek". Drugi smoke test na NIP spółki z aktywnym KRS:
"Nazwa firmy" otrzymuje legal name, "Imię i nazwisko" niezmienione.
</verify>
<done>AC-1 i AC-2 satisfied (UI mapuje pola zgodnie z heurystyką KRS).</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Kontrakt `MfWhitelistApiClient` poza dodaniem `is_jdg`/`krs` (pozostałe pola muszą zostać dla `InvoiceService::extractBuyerTaxNumber` i ewentualnych innych konsumentów w przyszłości).
- Server-side merge w `InvoiceService::resolveBuyerSnapshot()` — to operator decyduje finalnie co wpisuje w polach przed submitem; backend bierze wartości z requestu.
- Server walidacja `nipLookup` (regex 10 cyfr, kody błędów 422/502).
- Etykiety i kolejność pól w HTML formularza.
- Pre-fill logiki w `invoice_form.php` (linie 1318).
## SCOPE LIMITS
- Bez zmian w `ShopproOrderMapper` / `Allegro` mapperach — to nie jest fix importu adresów (`order_addresses.name` ma zostać tak jak jest, bo trzyma legalne dane firmy JDG).
- Bez zmian w `InvoiceService::issue()` / snapshot pattern.
- Bez automatycznego splitu `name`/`company_name` po stronie backendu — fix jest tylko o mapowanie UI po GUS.
- Bez nowych eventów automatyzacji.
</boundaries>
<verification>
Przed UNIFY:
- [ ] Manualny smoke `/orders/1090/invoice/create` — GUS lookup, pola wg AC-1.
- [ ] Manualny smoke na zamówieniu spółki z KRS — pola wg AC-2 (jeśli brak — symulować poprzez bezpośredni call API z NIP spółki, np. 5252344078 — PKP Cargo lub inny).
- [ ] `curl /api/nip/lookup?nip=5170167517` zwraca `is_jdg=true` (AC-3).
- [ ] Brak nowych błędów w `storage/logs/app.log` przy lookupie.
- [ ] Pola adresowe (ulica/kod/miasto) nadal nadpisywane.
</verification>
<success_criteria>
- AC-1, AC-2, AC-3 spełnione manualnym smoke testem.
- Brak regresji w istniejących lookupach NIP (dotychczasowy klucz `company_name` zachowany).
- Zero zmian w backendzie poza dodaniem 1 pola w JSON response.
</success_criteria>
<output>
Po zakończeniu utworzyć `.paul/phases/126-invoice-gus-field-mapping/126-01-SUMMARY.md`.
</output>