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>
This commit is contained in:
@@ -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) |
|
| 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) |
|
| 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) |
|
| 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):
|
Planowane kolejne fazy v3.7 (kandydaci, do rozplanowania):
|
||||||
- Eksport XLSX listy wystawionych faktur (analogicznie do paragonow)
|
- Eksport XLSX listy wystawionych faktur (analogicznie do paragonow)
|
||||||
@@ -507,4 +508,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Roadmap created: 2026-03-12*
|
*Roadmap created: 2026-03-12*
|
||||||
*Last updated: 2026-05-13 - Phase 125 UNIFY closed*
|
*Last updated: 2026-05-13 - Phase 126 UNIFY closed*
|
||||||
|
|||||||
@@ -5,19 +5,19 @@
|
|||||||
See: .paul/PROJECT.md (updated 2026-05-07)
|
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.
|
**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
|
## Current Position
|
||||||
|
|
||||||
Milestone: v3.7 Invoices (Fakturownia integration) - In progress
|
Milestone: v3.7 Invoices (Fakturownia integration) - In progress
|
||||||
Phase: 125 of TBD (invoice_requested import fix) - Complete
|
Phase: 126 of TBD (Invoice GUS field mapping fix) - Complete
|
||||||
Plan: 125-01 complete
|
Plan: 126-01 complete (SUMMARY.md created)
|
||||||
Status: UNIFY complete, transition pending (commit + ROADMAP update)
|
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:
|
Progress:
|
||||||
- Milestone v3.7: [##########] ~99% (Phase 113-125 complete; transition pending)
|
- Milestone v3.7: [##########] ~99% (Phase 113-126 complete; transition pending)
|
||||||
- Phase 125: [##########] 100%
|
- Phase 126: [##########] 100%
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ PLAN -> APPLY -> UNIFY
|
|||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-05-13
|
Last session: 2026-05-13
|
||||||
Stopped at: Phase 125-01 UNIFY closed; SUMMARY.md created
|
Stopped at: Phase 126-01 UNIFY closed; SUMMARY.md created
|
||||||
Next action: Phase transition (commit + ROADMAP update), then pick next v3.7 candidate or transition to v3.8
|
Next action: Phase transition (commit + ROADMAP update), then manual smoke (AC-1) i wybor kolejnego kandydata v3.7
|
||||||
Resume file: .paul/ROADMAP.md
|
Resume file: .paul/ROADMAP.md
|
||||||
|
|
||||||
## Pending parallel work
|
## Pending parallel work
|
||||||
@@ -39,10 +39,9 @@ Resume file: .paul/ROADMAP.md
|
|||||||
|
|
||||||
## Git State
|
## Git State
|
||||||
|
|
||||||
Last phase commit: 522c94a feat(124): sms templates CRUD + order picker
|
Last phase commit: 2ab461a feat(125): invoice_requested import fix + drop legacy is_invoice column
|
||||||
Previous: 360eef1 feat(121+122): smsplanet conversation, notifications, default footer
|
Previous: 522c94a feat(124): sms templates CRUD + order picker
|
||||||
Branch: main
|
Branch: main (4 commits ahead of origin/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.
|
|
||||||
|
|
||||||
## Pending Actions
|
## 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: 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 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 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
|
## Deferred to Next Milestones
|
||||||
|
|
||||||
|
|||||||
@@ -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] 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] 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 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
|
## Zmienione pliki
|
||||||
|
|
||||||
@@ -47,3 +49,8 @@
|
|||||||
- `database/migrations/20260513_000113_drop_orders_is_invoice_and_backfill_invoice_requested.sql`
|
- `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-PLAN.md`
|
||||||
- `.paul/phases/125-invoice-requested-import-fix/125-01-SUMMARY.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`
|
||||||
|
|||||||
@@ -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
|
## Phase 116 - HostedSMS Integration Settings
|
||||||
|
|
||||||
### HostedSmsIntegrationRepository (`src/Modules/Settings/HostedSmsIntegrationRepository.php`)
|
### HostedSmsIntegrationRepository (`src/Modules/Settings/HostedSmsIntegrationRepository.php`)
|
||||||
|
|||||||
@@ -1,5 +1,34 @@
|
|||||||
# Technical Changelog
|
# 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
|
## 2026-05-13 - Phase 125 Plan 01: invoice_requested Import Fix
|
||||||
|
|
||||||
**Co zrobiono:**
|
**Co zrobiono:**
|
||||||
|
|||||||
190
.paul/phases/126-invoice-gus-field-mapping/126-01-PLAN.md
Normal file
190
.paul/phases/126-invoice-gus-field-mapping/126-01-PLAN.md
Normal file
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<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 250–270) 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 13–18).
|
||||||
|
|
||||||
|
## 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>
|
||||||
118
.paul/phases/126-invoice-gus-field-mapping/126-01-SUMMARY.md
Normal file
118
.paul/phases/126-invoice-gus-field-mapping/126-01-SUMMARY.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
phase: 126-invoice-gus-field-mapping
|
||||||
|
plan: 01
|
||||||
|
subsystem: accounting
|
||||||
|
tags: [invoice, gus, mf-biala-lista, nip-lookup, jdg, krs]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 115-invoice-issuance
|
||||||
|
provides: invoice_form.php GUS lookup wiring, MfWhitelistApiClient base contract
|
||||||
|
provides:
|
||||||
|
- MfWhitelistApiClient `is_jdg`/`krs` flags
|
||||||
|
- /api/nip/lookup JSON response with `data.is_jdg`
|
||||||
|
- invoice_form.php JS conditional name field mapping (JDG vs spółka)
|
||||||
|
affects: [future-nip-lookup-forms, invoice-issuance-ux]
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "KRS-empty heuristic for JDG detection in MF Biała Lista responses"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- src/Core/Http/MfWhitelistApiClient.php
|
||||||
|
- src/Modules/Accounting/InvoiceController.php
|
||||||
|
- resources/views/accounting/invoice_form.php
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "JDG = MF subject.krs === '' (KRS-empty signal)"
|
||||||
|
- "JS mapuje warunkowo wg is_jdg; pre-fill drugiego pola nigdy nie jest nadpisywany przez GUS"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "MF Biała Lista API client exposes is_jdg flag — reusable for any NIP lookup formularz"
|
||||||
|
|
||||||
|
duration: ~20min
|
||||||
|
started: 2026-05-13T22:15:00Z
|
||||||
|
completed: 2026-05-13T22:35:00Z
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 126 Plan 01: Invoice GUS Field Mapping Fix Summary
|
||||||
|
|
||||||
|
**Po kliknięciu "Pobierz z GUS" na /orders/{id}/invoice/create dane MF Białej Listy trafiają teraz do właściwego pola w zależności od typu podmiotu (JDG → "Imię i nazwisko", spółka z KRS → "Nazwa firmy"); drugie pole zachowuje pre-fill z zamówienia.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~20 min |
|
||||||
|
| Tasks | 3/3 PASS |
|
||||||
|
| Files modified | 3 |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: JDG → name z MF do "Imię i nazwisko", "Nazwa firmy" niezmieniona | Code-level PASS | Manual smoke `/orders/1090/invoice/create` pending operator |
|
||||||
|
| AC-2: Spółka z KRS → name z MF do "Nazwa firmy", "Imię i nazwisko" niezmieniona | Code-level PASS | Wymaga zamówienia z NIP-em spółki z aktywnym KRS — manual smoke pending |
|
||||||
|
| AC-3: `/api/nip/lookup` response zawiera `data.is_jdg` | Code-level PASS | Lint clean; live `curl` pending operator (offline env) |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Root cause zdiagnozowany przez bezpośrednie odpytanie produkcyjnej DB (`order_addresses` order_id=1090) i live MF API call dla NIP 5170167517 — potwierdzony JDG response.
|
||||||
|
- 3 pliki zmodyfikowane minimalnie: 1 nowe pole w return array klienta MF, 1 nowe pole w JSON response endpointu, 1 warunek w JS bloku GUS lookup.
|
||||||
|
- Pre-fill operatora z `order_addresses` jest teraz chroniony — GUS lookup nigdy nie nadpisuje obu pól osobowych równocześnie.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `src/Core/Http/MfWhitelistApiClient.php` | Modified | Dodane `krs: string`, `is_jdg: bool` w return array `lookupByNip()`; zaktualizowany docblock @return |
|
||||||
|
| `src/Modules/Accounting/InvoiceController.php` | Modified | `nipLookup()` propaguje `is_jdg` w `data` JSON response |
|
||||||
|
| `resources/views/accounting/invoice_form.php` | Modified | JS `btn-gus-lookup` handler: `var nameTargetId = d.is_jdg ? 'buyer_name' : 'buyer_company_name';` — drugiego pola nie rusza; adres (street/postal_code/city) nadpisywany bez zmian |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| Heurystyka JDG = `subject.krs === ''` | MF Biała Lista zwraca `krs=null` dla osób fizycznych prowadzących działalność; spółki mają KRS niepusty | Stabilny sygnał z MF; zero zewnętrznych zależności |
|
||||||
|
| GUS NIE nadpisuje "Nazwa firmy" dla JDG | MF `name` dla JDG to osoba fizyczna, a `order_addresses.name` często trzyma pełną nazwę firmy (np. "Project-Pro Pyziak Jacek") — wartościowsza dla faktury | Operator zachowuje sensowny pre-fill, nie musi przepisywać po lookupie |
|
||||||
|
| Backend (`InvoiceService::resolveBuyerSnapshot`) nie zmieniany | Server-side bierze wartości z requestu — operator ma pełną kontrolę przed submitem | Zero ryzyka regresji na server-side merge |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Type | Count | Impact |
|
||||||
|
|------|-------|--------|
|
||||||
|
| Auto-fixed | 0 | — |
|
||||||
|
| Scope additions | 0 | — |
|
||||||
|
| Deferred | 0 | — |
|
||||||
|
|
||||||
|
**Total impact:** Plan wykonany 1:1, brak odstępstw.
|
||||||
|
|
||||||
|
### Deferred Items
|
||||||
|
|
||||||
|
Brak.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
| Issue | Resolution |
|
||||||
|
|-------|------------|
|
||||||
|
| `vendor/autoload.php` nieobecny w lokalnym env — niemożliwa live weryfikacja Task 1 przez REPL | Pominięta live verification; pole `is_jdg` to mechaniczny field-add, lint clean wystarczy. Live `curl /api/nip/lookup` pozostaje do manual smoke przez operatora |
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- Fix wdrożony na 3 plikach, lint clean, zero zmian backendu poza dodaniem 1 pola w JSON.
|
||||||
|
- Pattern KRS-heuristic gotowy do reuse w innych formularzach opartych o NIP lookup (np. ewentualny edit invoice config buyer presets).
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- AC-1/AC-2/AC-3 nie zweryfikowane na żywym środowisku — wymaga manual smoke operatora po deploy (lokalny env bez vendor/, prod live test odłożony).
|
||||||
|
|
||||||
|
**Blockers:**
|
||||||
|
- None.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 126-invoice-gus-field-mapping, Plan: 01*
|
||||||
|
*Completed: 2026-05-13*
|
||||||
@@ -254,15 +254,19 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
|||||||
}
|
}
|
||||||
var d = res.data.data || {};
|
var d = res.data.data || {};
|
||||||
if (d.tax_number) { nipInput.value = d.tax_number; }
|
if (d.tax_number) { nipInput.value = d.tax_number; }
|
||||||
var fields = {
|
// MF zwraca w `name` osobe fizyczna dla JDG (krs=null) lub legal name dla spolki (krs!=null).
|
||||||
buyer_company_name: d.company_name,
|
// Wpisujemy do wlasciwego pola wg flagi is_jdg; drugiego pola nie ruszamy.
|
||||||
|
var nameTargetId = d.is_jdg ? 'buyer_name' : 'buyer_company_name';
|
||||||
|
var addressFields = {
|
||||||
buyer_street: d.street,
|
buyer_street: d.street,
|
||||||
buyer_postal_code: d.postal_code,
|
buyer_postal_code: d.postal_code,
|
||||||
buyer_city: d.city
|
buyer_city: d.city
|
||||||
};
|
};
|
||||||
Object.keys(fields).forEach(function (key) {
|
var nameTargetEl = document.getElementById(nameTargetId);
|
||||||
|
if (nameTargetEl && d.company_name) { nameTargetEl.value = d.company_name; }
|
||||||
|
Object.keys(addressFields).forEach(function (key) {
|
||||||
var el = document.getElementById(key);
|
var el = document.getElementById(key);
|
||||||
if (el && fields[key]) { el.value = fields[key]; }
|
if (el && addressFields[key]) { el.value = addressFields[key]; }
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ final class MfWhitelistApiClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{name: string, tax_no: string, regon: string, street: string, postal_code: string, city: string, country: string, status_vat: string, raw: array<string, mixed>}
|
* @return array{name: string, tax_no: string, regon: string, street: string, postal_code: string, city: string, country: string, status_vat: string, krs: string, is_jdg: bool, raw: array<string, mixed>}
|
||||||
*/
|
*/
|
||||||
public function lookupByNip(string $nip, ?string $date = null): array
|
public function lookupByNip(string $nip, ?string $date = null): array
|
||||||
{
|
{
|
||||||
@@ -65,6 +65,8 @@ final class MfWhitelistApiClient
|
|||||||
$address = trim((string) ($subject['workingAddress'] ?? $subject['residenceAddress'] ?? ''));
|
$address = trim((string) ($subject['workingAddress'] ?? $subject['residenceAddress'] ?? ''));
|
||||||
['street' => $street, 'postal_code' => $postalCode, 'city' => $city] = $this->parseAddress($address);
|
['street' => $street, 'postal_code' => $postalCode, 'city' => $city] = $this->parseAddress($address);
|
||||||
|
|
||||||
|
$krs = trim((string) ($subject['krs'] ?? ''));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'tax_no' => trim((string) ($subject['nip'] ?? $cleanNip)),
|
'tax_no' => trim((string) ($subject['nip'] ?? $cleanNip)),
|
||||||
@@ -74,6 +76,8 @@ final class MfWhitelistApiClient
|
|||||||
'city' => $city,
|
'city' => $city,
|
||||||
'country' => 'PL',
|
'country' => 'PL',
|
||||||
'status_vat' => trim((string) ($subject['statusVat'] ?? '')),
|
'status_vat' => trim((string) ($subject['statusVat'] ?? '')),
|
||||||
|
'krs' => $krs,
|
||||||
|
'is_jdg' => $krs === '',
|
||||||
'raw' => $subject,
|
'raw' => $subject,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ final class InvoiceController
|
|||||||
'country' => $data['country'],
|
'country' => $data['country'],
|
||||||
'regon' => $data['regon'],
|
'regon' => $data['regon'],
|
||||||
'status_vat' => $data['status_vat'],
|
'status_vat' => $data['status_vat'],
|
||||||
|
'is_jdg' => (bool) ($data['is_jdg'] ?? false),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user