feat(123): receipts export xlsx VAT breakdown
- AccountingController::export(): new headers (Numer | Data wystawienia | Kwota brutto | Kwota netto | Stawka VAT | Kwota VAT), removed Data sprzedazy/Konfiguracja/Nr zamowienia/Nr referencyjny - buildVatBreakdown() helper groups items_json by vat rate, emits one XLSX row per (receipt x rate); legacy receipts (no `vat` in snapshot) fallback to net=brutto/1.23 - ReceiptService::buildItemsSnapshot(): writes `vat` per item from order_items.tax_rate (fallback 23.0); shipping cost item gets vat=23.0 - RECEIPT-NET-FIX deferred (.paul/codebase/todo.md): ReceiptService::issue() still saves total_net=total_gross Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
|||||||
- [x] Integracja SMSPLANET: pojedyncza globalna konfiguracja w `/settings/integrations/smsplanet`, szyfrowane sekrety, autoryzacja Bearer token albo key + password, karta w hubie integracji i realna wysylka testowego SMS — Phase 117
|
- [x] Integracja SMSPLANET: pojedyncza globalna konfiguracja w `/settings/integrations/smsplanet`, szyfrowane sekrety, autoryzacja Bearer token albo key + password, karta w hubie integracji i realna wysylka testowego SMS — Phase 117
|
||||||
- [x] Re-import ochrona `total_paid`: gdy `payment_status` sie nie zmienia, `updateOrderDelta()` nie nadpisuje `total_paid` (ani `is_canceled_by_buyer`, chyba ze cancel ze zrodla); chroni reczne korekty operatora (zwroty czesciowe). Dynamic SQL SET builder + 3 testy PHPUnit (Reflection + sqlite) — Phase 119
|
- [x] Re-import ochrona `total_paid`: gdy `payment_status` sie nie zmienia, `updateOrderDelta()` nie nadpisuje `total_paid` (ani `is_canceled_by_buyer`, chyba ze cancel ze zrodla); chroni reczne korekty operatora (zwroty czesciowe). Dynamic SQL SET builder + 3 testy PHPUnit (Reflection + sqlite) — Phase 119
|
||||||
- [x] Ujednolicony moduł alertów UI: reusable PHP komponent `resources/views/components/alert.php` z inline SVG ikoną per typ (info/success/warning/danger), opcjonalnym dismiss button (vanilla JS, idempotent); brakujący `.alert--info` (#eff6ff/#bfdbfe/#1e3a8a); `Flash::push/all` z BC dla `set/get` (heurystyka klucza legacy); centralny renderer flash w 3 layoutach (app/auth/public); 36 widoków zmigrowanych off inline alert markup; `.flash--*` usunięte z widoków — Phase 120
|
- [x] Ujednolicony moduł alertów UI: reusable PHP komponent `resources/views/components/alert.php` z inline SVG ikoną per typ (info/success/warning/danger), opcjonalnym dismiss button (vanilla JS, idempotent); brakujący `.alert--info` (#eff6ff/#bfdbfe/#1e3a8a); `Flash::push/all` z BC dla `set/get` (heurystyka klucza legacy); centralny renderer flash w 3 layoutach (app/auth/public); 36 widoków zmigrowanych off inline alert markup; `.flash--*` usunięte z widoków — Phase 120
|
||||||
|
- [x] Eksport XLSX paragonow w `/accounting`: nowe naglowki (Numer | Data wystawienia | Kwota brutto | Kwota netto | Stawka VAT | Kwota VAT) z osobnym wierszem per stawka VAT; `items_json` snapshot rozszerzony o `vat` per pozycja (z `order_items.tax_rate`, fallback 23.0); legacy fallback `net = brutto/1.23` — Phase 123
|
||||||
|
|
||||||
### Deferred
|
### Deferred
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ Wystawianie faktur dla klientow z NIP poprzez integracje z Fakturownia (app.fakt
|
|||||||
| 120 | Alert Component Unification | 1/1 | Complete (2026-05-12; CSS rebuilt; smoke tests pending operator) |
|
| 120 | Alert Component Unification | 1/1 | Complete (2026-05-12; CSS rebuilt; smoke tests pending operator) |
|
||||||
| 121 | SMSPLANET Conversation + Notifications | 1/1 | Complete (2026-05-12; live SMS/browser smoke pending operator) |
|
| 121 | SMSPLANET Conversation + Notifications | 1/1 | Complete (2026-05-12; live SMS/browser smoke pending operator) |
|
||||||
| 122 | SMSPLANET Default SMS Footer | 1/1 | Complete (2026-05-12; live SMS smoke + over-limit UI test pending operator) |
|
| 122 | SMSPLANET Default SMS Footer | 1/1 | Complete (2026-05-12; live SMS smoke + over-limit UI test pending operator) |
|
||||||
|
| 123 | Receipts Export VAT Breakdown | 1/1 | Complete (2026-05-12; manual XLSX 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)
|
||||||
@@ -504,4 +505,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Roadmap created: 2026-03-12*
|
*Roadmap created: 2026-03-12*
|
||||||
*Last updated: 2026-05-12 - Phase 118 + 121 + 122 committed; UNIFY closed for all three*
|
*Last updated: 2026-05-12 - Phase 123 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 122 SMSPLANET default SMS footer complete (UNIFY closed).
|
**Current focus:** v3.7 Invoices + operational integrations - Phase 123 Receipts Export VAT Breakdown complete (UNIFY closed).
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v3.7 Invoices (Fakturownia integration) - In progress
|
Milestone: v3.7 Invoices (Fakturownia integration) - In progress
|
||||||
Phase: 122 of TBD (SMSPLANET Default SMS Footer) - Complete
|
Phase: 123 of TBD (Receipts Export VAT Breakdown) - Complete
|
||||||
Plan: 122-01 complete
|
Plan: 123-01 complete
|
||||||
Status: UNIFY complete, ready to plan next phase
|
Status: UNIFY complete, ready to plan next phase
|
||||||
Last activity: 2026-05-12 22:00:00 - UNIFY closed for .paul/phases/122-smsplanet-default-sms-footer/122-01-PLAN.md
|
Last activity: 2026-05-12 23:00:00 - UNIFY closed for .paul/phases/123-receipts-export-vat-breakdown/123-01-PLAN.md
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
- Milestone v3.7: [#########-] ~95% (Phase 113-117 closed; Phase 118 applied; Phase 119-122 complete)
|
- Milestone v3.7: [#########-] ~97% (Phase 113-123 complete)
|
||||||
- Phase 122: [##########] 100%
|
- Phase 123: [##########] 100%
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
@@ -56,6 +56,8 @@ Note: routes/web.php, DOCS/* i .paul/codebase/* zawierały zmiany z 118+121+122
|
|||||||
- Phase 121 follow-up: manually verify SMSPLANET settings/test send, inbound webhook insertion, order SMS tab, and notification polling/browser notification.
|
- Phase 121 follow-up: manually verify SMSPLANET settings/test send, inbound webhook insertion, order SMS tab, and notification polling/browser notification.
|
||||||
- Phase 121 transition note: git commit was not created during UNIFY because the worktree contains unrelated Phase 118/local dirty files; prepare a scoped commit manually.
|
- Phase 121 transition note: git commit was not created during UNIFY because the worktree contains unrelated Phase 118/local dirty files; prepare a scoped commit manually.
|
||||||
- Phase 122 follow-up: manually verify settings save/reload and real SMSPLANET test/order sends with non-empty and empty footer; manually trigger over-limit final body rejection in UI.
|
- Phase 122 follow-up: manually verify settings save/reload and real SMSPLANET test/order sends with non-empty and empty footer; manually trigger over-limit final body rejection in UI.
|
||||||
|
- Phase 123 follow-up: wystaw nowy paragon i potwierdz `items_json` zawiera `vat` per pozycja; eksport XLSX z paragonem multi-rate (np. mix 23% + 8%) — sprawdz osobne wiersze; eksport "wybrane paragony" zachowuje breakdown.
|
||||||
|
- Phase 123 deferred: RECEIPT-NET-FIX (`ReceiptService::issue()` zapisuje `total_net=total_gross`) — udokumentowane w `.paul/codebase/todo.md`.
|
||||||
- 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.
|
||||||
|
|
||||||
## Deferred to Next Milestones
|
## Deferred to Next Milestones
|
||||||
|
|||||||
@@ -48,3 +48,18 @@
|
|||||||
- `DOCS/DB_SCHEMA.md`
|
- `DOCS/DB_SCHEMA.md`
|
||||||
- `DOCS/ARCHITECTURE.md`
|
- `DOCS/ARCHITECTURE.md`
|
||||||
- `DOCS/TECH_CHANGELOG.md`
|
- `DOCS/TECH_CHANGELOG.md`
|
||||||
|
|
||||||
|
## Phase 123 — Receipts Export VAT Breakdown
|
||||||
|
|
||||||
|
- [Phase 123, Plan 01] Eksport XLSX paragonów (`/accounting`): nowe nagłówki `Numer | Data wystawienia | Kwota brutto | Kwota netto | Stawka VAT | Kwota VAT` (usunięto Data sprzedaży/Konfiguracja/Nr zamówienia/Nr referencyjny).
|
||||||
|
- [Phase 123, Plan 01] Multi-rate breakdown: paragon z kilkoma stawkami VAT generuje osobny wiersz na każdą stawkę (ten sam Numer/Data/Brutto).
|
||||||
|
- [Phase 123, Plan 01] `ReceiptService::buildItemsSnapshot()` zapisuje `vat` per pozycja w `items_json` (z `order_items.tax_rate`, fallback 23.0; "Koszt wysylki" = 23.0).
|
||||||
|
- [Phase 123, Plan 01] Auto-fix po smoke teście: legacy fallback liczy `net = brutto/1.23` zamiast brać z `total_net` (RECEIPT-NET-FIX odłożone do `.paul/codebase/todo.md`).
|
||||||
|
|
||||||
|
### Zmienione pliki (Phase 123)
|
||||||
|
|
||||||
|
- `src/Modules/Accounting/ReceiptService.php`
|
||||||
|
- `src/Modules/Accounting/AccountingController.php`
|
||||||
|
- `.paul/codebase/architecture.md`
|
||||||
|
- `.paul/codebase/tech_changelog.md`
|
||||||
|
- `.paul/codebase/todo.md`
|
||||||
|
|||||||
@@ -339,6 +339,20 @@ tests/
|
|||||||
- `/api/notifications/unread` zasila topbar badge oraz `public/assets/js/modules/notifications.js`.
|
- `/api/notifications/unread` zasila topbar badge oraz `public/assets/js/modules/notifications.js`.
|
||||||
- Browser Notification API jest progresywne: brak zgody nie blokuje strony ani pollingu.
|
- Browser Notification API jest progresywne: brak zgody nie blokuje strony ani pollingu.
|
||||||
|
|
||||||
|
## Phase 123 — Receipts Export VAT Breakdown
|
||||||
|
|
||||||
|
### ReceiptService::buildItemsSnapshot (`src/Modules/Accounting/ReceiptService.php`)
|
||||||
|
- Snapshot pozycji w `receipts.items_json` ma teraz pole `vat` (procent jako float). Zrodlo: `order_items.tax_rate` (fallback `item.vat`, ostatecznie 23.0).
|
||||||
|
- Pozycja "Koszt wysylki" (gdy `delivery_price > 0`) dostaje `vat = 23.0`.
|
||||||
|
- Stary kontrakt (`name`, `quantity`, `price`, `total`, `sku`, `ean`) zachowany — tylko dodatek pola `vat`. Widoki paragonu (print/preview) nie wymagaja zmian.
|
||||||
|
|
||||||
|
### AccountingController::export (`src/Modules/Accounting/AccountingController.php`)
|
||||||
|
- Naglowki XLSX: `Numer | Data wystawienia | Kwota brutto | Kwota netto | Stawka VAT | Kwota VAT`. Usunieto: Data sprzedazy, Konfiguracja, Nr zamowienia, Nr referencyjny.
|
||||||
|
- `buildVatBreakdown(itemsJson, totalNet, totalGross)` grupuje pozycje `items_json` po `vat`, oblicza per-grupa `net = round(gross / (1 + rate/100), 2)` i `vat = gross - net`. Zwraca liste `[{rate_label, net, vat}, ...]` posortowana malejaco po stawce.
|
||||||
|
- Legacy fallback: gdy zaden item nie ma klucza `vat`, zwraca pojedynczy wiersz `[{rate_label: '23%', net: total_net, vat: total_gross - total_net}]`.
|
||||||
|
- Multi-rate paragon = wiele wierszy w XLSX (ten sam Numer, Data wystawienia i Kwota brutto powtarzane).
|
||||||
|
- Helper `formatVatRate()` formatuje stawke (23.0 -> "23%", 7.5 -> "7.5%").
|
||||||
|
|
||||||
## Phase 120 — Alert Component Unification
|
## Phase 120 — Alert Component Unification
|
||||||
|
|
||||||
### Alert component (`resources/views/components/alert.php`)
|
### Alert component (`resources/views/components/alert.php`)
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# Technical Changelog
|
# Technical Changelog
|
||||||
|
|
||||||
|
## 2026-05-12 - Phase 123 Plan 01: Receipts Export VAT Breakdown
|
||||||
|
|
||||||
|
**Co zrobiono:**
|
||||||
|
- `ReceiptService::buildItemsSnapshot()` zapisuje `vat` (procent) per pozycja w `items_json` (z `order_items.tax_rate`, fallback 23.0). Pozycja "Koszt wysylki" dostaje `vat=23.0`.
|
||||||
|
- `AccountingController::export()`: nowe naglowki XLSX `Numer | Data wystawienia | Kwota brutto | Kwota netto | Stawka VAT | Kwota VAT` (usunieto `Data sprzedazy`, `Konfiguracja`, `Nr zamowienia`, `Nr referencyjny`).
|
||||||
|
- Eksport emituje osobny wiersz na kazda stawke VAT wystepujaca w paragonie (multi-rate breakdown z grupowania `items_json` po `vat`).
|
||||||
|
- Legacy paragony (snapshot bez `vat`) zwracaja jeden wiersz ze stawka 23%, `Kwota netto = total_net`, `Kwota VAT = total_gross - total_net`.
|
||||||
|
- Dodany prywatny helper `AccountingController::buildVatBreakdown()` + `formatVatRate()` (np. 23.0 -> "23%", 7.5 -> "7.5%").
|
||||||
|
|
||||||
|
**Dlaczego:**
|
||||||
|
- Ksiegowy potrzebuje arkusza z rozbiciem VAT per stawka do zaczytania do ksiegowosci. Stary eksport zawieral pola operacyjne (data sprzedazy, konfiguracja, ref) bez podstawowych pol VAT.
|
||||||
|
|
||||||
|
**Weryfikacja:**
|
||||||
|
- `php -l` na obu plikach OK; manualny eksport XLSX dla mieszanej listy paragonow po wdrozeniu.
|
||||||
|
|
||||||
## 2026-05-12 - SMSPLANET Inbound Webhook Fix
|
## 2026-05-12 - SMSPLANET Inbound Webhook Fix
|
||||||
|
|
||||||
**Co zrobiono:**
|
**Co zrobiono:**
|
||||||
|
|||||||
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
> Lista nieformalnych zadan do zrobienia pozniej. Kazdy wpis ma wlasny tag (np. `STAT-NET`) zeby mozna go bylo zlinkowac z komentarzy w kodzie.
|
> Lista nieformalnych zadan do zrobienia pozniej. Kazdy wpis ma wlasny tag (np. `STAT-NET`) zeby mozna go bylo zlinkowac z komentarzy w kodzie.
|
||||||
|
|
||||||
|
## RECEIPT-NET-FIX — `receipts.total_net` powinno byc realnym netto (data: 2026-05-12)
|
||||||
|
|
||||||
|
### Kontekst
|
||||||
|
- Phase 123-01 — eksport paragonow XLSX z VAT breakdown.
|
||||||
|
- `ReceiptService::issue()` (linie 81-82) zapisuje `total_net = total_gross` (kopia, nie realne netto). To znany bug, ale nie poprawiany w 123 (poza zakresem).
|
||||||
|
- Phase 123 fallback dla legacy paragonow musi liczyc `net = brutto/1.23` zamiast brac z `total_net`, bo inaczej VAT = 0.
|
||||||
|
|
||||||
|
### Zadania
|
||||||
|
1. W `ReceiptService::buildItemsSnapshot()` agreguj `total_net` z pozycji (per stawka) — `lineTotal / (1 + vat/100)`.
|
||||||
|
2. Zwroc oba: `total_net` (suma netto per pozycja) i `total_gross` (suma brutto). Uzyj `total_net` w INSERT zamiast kopii brutto.
|
||||||
|
3. Po deploy mozna uproscic legacy fallback w `AccountingController::buildVatBreakdown()` zeby brak `vat` -> uzywal `total_net` z bazy.
|
||||||
|
4. Backfill historycznych paragonow opcjonalny (eksport teraz dziala bez tego).
|
||||||
|
|
||||||
|
|
||||||
## INVOICE-IDEMP-115 — idempotencja podwojnego POST do Fakturowni (data: 2026-05-10)
|
## INVOICE-IDEMP-115 — idempotencja podwojnego POST do Fakturowni (data: 2026-05-10)
|
||||||
|
|
||||||
### Kontekst
|
### Kontekst
|
||||||
|
|||||||
@@ -129,3 +129,24 @@
|
|||||||
{"ts":"2026-05-12T18:38:24Z","tool":"Edit","file":"c:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
{"ts":"2026-05-12T18:38:24Z","tool":"Edit","file":"c:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
{"ts":"2026-05-12T18:38:34Z","tool":"Bash","cmd":"git add .paul/ROADMAP.md .paul/STATE.md && git commit -m \"$(cat <<'EOF'\\ndocs(paul): record phase 118 transition + close UNIFY for 121/122\\n\\n- ROADMAP: Phase 118 marked Complete (1/1)\\n- STATE: ","cwd":"/c/visual studio code/projekty/orderPRO"}
|
{"ts":"2026-05-12T18:38:34Z","tool":"Bash","cmd":"git add .paul/ROADMAP.md .paul/STATE.md && git commit -m \"$(cat <<'EOF'\\ndocs(paul): record phase 118 transition + close UNIFY for 121/122\\n\\n- ROADMAP: Phase 118 marked Complete (1/1)\\n- STATE: ","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
{"ts":"2026-05-12T18:38:40Z","tool":"Bash","cmd":"git log --oneline -6 && echo \"---\" && git status --short\",\"description\":\"Final state\"},\"tool_response\":{\"stdout\":\"4555548 docs(paul): record phase 118 transition + close UNIFY for 121/122\","cwd":"/c/visual studio code/projekty/orderPRO"}
|
{"ts":"2026-05-12T18:38:40Z","tool":"Bash","cmd":"git log --oneline -6 && echo \"---\" && git status --short\",\"description\":\"Final state\"},\"tool_response\":{\"stdout\":\"4555548 docs(paul): record phase 118 transition + close UNIFY for 121/122\","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:50:36Z","tool":"Bash","cmd":"mkdir -p \".paul/phases/123-receipts-export-vat-breakdown\"\",\"description\":\"Create phase directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"n","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:51:41Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\123-receipts-export-vat-breakdown\\\\123-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:51:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:52:01Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:55:48Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Accounting\\\\ReceiptService.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:56:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Accounting\\\\AccountingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:56:27Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Accounting\\\\AccountingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:56:35Z","tool":"Bash","cmd":"php -l \"src/Modules/Accounting/AccountingController.php\" && php -l \"src/Modules/Accounting/ReceiptService.php\"\",\"description\":\"Syntax check both modified files\"},\"tool_response\":{\"stdout\"","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:57:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\codebase\\\\tech_changelog.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:57:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\codebase\\\\architecture.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:57:35Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:59:00Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Accounting\\\\AccountingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T18:59:06Z","tool":"Bash","cmd":"php -l \"src/Modules/Accounting/AccountingController.php\"\",\"description\":\"Lint after fix\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in src/Modules/Accounting/AccountingControlle","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T19:04:42Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\123-receipts-export-vat-breakdown\\\\123-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T19:05:00Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\codebase\\\\todo.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T19:05:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T19:06:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T19:06:13Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T19:06:19Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T19:06:34Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-12T19:06:39Z","tool":"Bash","cmd":"git status --short\",\"description\":\"Check working tree before commit\"},\"tool_response\":{\"stdout\":\" M .paul/PROJECT.md\\n M .paul/ROADMAP.md\\n M .paul/STATE.md\\n M .paul/changelog/2026-05-12","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
|||||||
175
.paul/phases/123-receipts-export-vat-breakdown/123-01-PLAN.md
Normal file
175
.paul/phases/123-receipts-export-vat-breakdown/123-01-PLAN.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
---
|
||||||
|
phase: 123-receipts-export-vat-breakdown
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- src/Modules/Accounting/AccountingController.php
|
||||||
|
- src/Modules/Accounting/ReceiptService.php
|
||||||
|
autonomous: true
|
||||||
|
delegation: off
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Zmodyfikować eksport paragonów do XLSX (`/accounting`, akcja `AccountingController::export`) tak, aby:
|
||||||
|
- usunąć kolumny: `Data sprzedaży`, `Konfiguracja`, `Nr zamówienia`, `Nr referencyjny`,
|
||||||
|
- dodać kolumny: `Kwota netto`, `Stawka VAT`, `Kwota VAT`,
|
||||||
|
- emitować osobny wiersz na każdą stawkę VAT występującą w paragonie (multi-rate breakdown).
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Operator/księgowy potrzebuje arkusza, który da się wprost zaczytać do księgowości — z rozbiciem netto/VAT per stawka. Obecny eksport zawiera dane zbędne dla księgowości (data sprzedaży, konfiguracja, referencje) a nie ma podstawowych pól VAT.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Zmodyfikowany `AccountingController::export()` — nagłówki: `Numer | Data wystawienia | Kwota brutto | Kwota netto | Stawka VAT | Kwota VAT`.
|
||||||
|
- Rozszerzony `ReceiptService::buildItemsSnapshot()` — `items_json` przechowuje `vat` (procent) per pozycja dla nowych paragonów.
|
||||||
|
- Helper grupujący `items_json` po stawce VAT (z fallbackiem 23% dla legacy paragonów bez `vat`).
|
||||||
|
- Plik `paragony_YYYY-MM-DD.xlsx` z jednym wierszem per (paragon × stawka VAT).
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
|
||||||
|
<clarifications>
|
||||||
|
- **Liczenie VAT** — Jak wyliczać 'Stawkę VAT' i 'Kwotę VAT'?
|
||||||
|
→ Odpowiedź: Jeżeli paragon ma produkty z kilkoma stawkami, wszystkie powinny być wypisane (np. 23%, 8%) — jako osobny wiersz w XLSX per stawka.
|
||||||
|
- **Źródło netto** — Czy 'Kwota netto' z `receipts.total_net` czy wyliczana?
|
||||||
|
→ Odpowiedź: Z `receipts.total_net` (snapshot z momentu wystawienia). Per stawka — wyliczane z pozycji items_json grupowanych po VAT.
|
||||||
|
- **Format kwot** — Jak prezentować przy multi-rate?
|
||||||
|
→ Odpowiedź: Osobny wiersz per stawka (ten sam Numer, różne stawki/kwoty) — pozwala sumować w Excelu bez rozbijania komórek.
|
||||||
|
- **Legacy data** — Brak `vat` w `items_json` historycznych paragonów.
|
||||||
|
→ Odpowiedź: Dodać `vat` do snapshotu w `buildItemsSnapshot()` (nowe paragony). Dla starych paragonów (snapshot bez `vat`) — jeden wiersz w eksporcie ze stawką 23%, `Kwota netto = total_net`, `Kwota VAT = total_gross − total_net`.
|
||||||
|
</clarifications>
|
||||||
|
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@src/Modules/Accounting/AccountingController.php
|
||||||
|
@src/Modules/Accounting/ReceiptService.php
|
||||||
|
@src/Modules/Accounting/ReceiptRepository.php
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Nagłówki XLSX zgodne z wymaganiem
|
||||||
|
```gherkin
|
||||||
|
Given operator otwiera /accounting i klika "Eksportuj XLSX"
|
||||||
|
When pobiera plik paragony_YYYY-MM-DD.xlsx
|
||||||
|
Then arkusz "Paragony" ma dokładnie 6 kolumn w kolejności: Numer, Data wystawienia, Kwota brutto, Kwota netto, Stawka VAT, Kwota VAT
|
||||||
|
And nie ma kolumn: Data sprzedazy, Konfiguracja, Nr zamowienia, Nr referencyjny
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Multi-rate breakdown dla nowych paragonów
|
||||||
|
```gherkin
|
||||||
|
Given paragon (po Phase 123) zawiera 2 pozycje: A z VAT 23% (netto 100, brutto 123) i B z VAT 8% (netto 50, brutto 54)
|
||||||
|
When operator eksportuje XLSX
|
||||||
|
Then arkusz zawiera 2 wiersze dla tego paragonu (ten sam Numer, ta sama Data wystawienia, ta sama Kwota brutto = 177)
|
||||||
|
And wiersz #1: Stawka VAT = "23%", Kwota netto = 100.00, Kwota VAT = 23.00
|
||||||
|
And wiersz #2: Stawka VAT = "8%", Kwota netto = 50.00, Kwota VAT = 4.00
|
||||||
|
And suma kolumny Kwota netto dla tego paragonu = total_net z bazy
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Legacy paragony (bez `vat` w items_json) — fallback 23%
|
||||||
|
```gherkin
|
||||||
|
Given paragon wystawiony przed Phase 123 (items_json bez klucza `vat`), z total_net=200, total_gross=246
|
||||||
|
When operator eksportuje XLSX
|
||||||
|
Then dla tego paragonu jest dokładnie 1 wiersz
|
||||||
|
And Stawka VAT = "23%", Kwota netto = 200.00, Kwota VAT = 46.00 (= total_gross − total_net)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Snapshot rozszerzony o VAT (nowe paragony)
|
||||||
|
```gherkin
|
||||||
|
Given operator wystawia nowy paragon z zamówienia (po Phase 123)
|
||||||
|
When `ReceiptService::issue()` zapisuje rekord w tabeli receipts
|
||||||
|
Then każda pozycja w items_json ma klucz `vat` (procent jako float, np. 23.0 lub 8.0)
|
||||||
|
And pozycja "Koszt wysylki" (gdy delivery_price > 0) ma `vat` = 23.0 (domyślny)
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Rozszerz items_json snapshot o stawkę VAT per pozycja</name>
|
||||||
|
<files>src/Modules/Accounting/ReceiptService.php</files>
|
||||||
|
<action>
|
||||||
|
W `buildItemsSnapshot()` (linie 255-291):
|
||||||
|
- Dla każdego item dodaj klucz `vat` w snapshocie. Źródło: `$item['vat']` jeżeli ustawiony, fallback `(float) ($item['original_vat'] ?? 23.0)`.
|
||||||
|
- Sprawdź jakie pola item-y mają w obecnej formie (controller/service które wywołują issue() — prawdopodobnie z order_items.vat). Jeżeli brak — dodaj fallback 23.0.
|
||||||
|
- Pozycja "Koszt wysylki" (linie 277-284) — dodaj `'vat' => 23.0` (jako domyślną dla wysyłki).
|
||||||
|
Zachowaj BC: stary snapshot bez `vat` musi nadal działać przy odczycie (export i widok print/preview).
|
||||||
|
|
||||||
|
Avoid: zmiany struktury kwot (price/total) — tylko dodanie pola `vat`. Brak migracji DB (items_json jest JSON-em).
|
||||||
|
</action>
|
||||||
|
<verify>Wystaw nowy paragon testowy w UI; sprawdź w bazie: `SELECT items_json FROM receipts ORDER BY id DESC LIMIT 1;` — każda pozycja ma klucz `vat`.</verify>
|
||||||
|
<done>AC-4 satisfied: nowe paragony zawierają `vat` w items_json.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Przepisz AccountingController::export() — nowe nagłówki + multi-rate breakdown</name>
|
||||||
|
<files>src/Modules/Accounting/AccountingController.php</files>
|
||||||
|
<action>
|
||||||
|
W metodzie `export()` (linie 109-168):
|
||||||
|
|
||||||
|
1. Zmień `$headers` na: `['Numer', 'Data wystawienia', 'Kwota brutto', 'Kwota netto', 'Stawka VAT', 'Kwota VAT']` (6 kolumn).
|
||||||
|
|
||||||
|
2. Zastąp pętlę `foreach ($rows as $row)` logiką per-rate breakdown:
|
||||||
|
- Dla każdego paragonu zdekoduj `items_json`.
|
||||||
|
- Pogrupuj pozycje po `vat` (klucz: float jako string, np. "23.0").
|
||||||
|
- Dla każdej grupy oblicz `Kwota netto` (suma `total / (1 + vat/100)`, zaokrąglona do 2 miejsc) i `Kwota VAT` (`total - netto`).
|
||||||
|
- Wypisz jeden wiersz XLSX na grupę: Numer + Data wystawienia + Kwota brutto (total z paragonu, ten sam dla wszystkich wierszy) + Kwota netto (per grupa) + Stawka VAT (np. "23%") + Kwota VAT (per grupa).
|
||||||
|
|
||||||
|
3. **Fallback dla legacy paragonów** (gdy żadna pozycja items_json nie ma klucza `vat` lub items_json puste):
|
||||||
|
- Emituj 1 wiersz: Stawka VAT = "23%", Kwota netto = (float) $row['total_net'], Kwota VAT = (float) $row['total_gross'] - (float) $row['total_net'].
|
||||||
|
|
||||||
|
4. Zaktualizuj `foreach (range(1, 7) as $col)` → `range(1, 6)`.
|
||||||
|
|
||||||
|
5. Zachowaj formatowanie issueDateRaw (substr 0,16) dla "Data wystawienia".
|
||||||
|
|
||||||
|
Zaokrąglenia: użyj `round(..., 2)` dla netto i VAT. Suma netto+VAT per grupa może różnić się o 0.01 od `total` grupy z powodu zaokrąglenia — to akceptowalne (księgowość standardowo to toleruje).
|
||||||
|
|
||||||
|
Avoid: dodawanie nowych pól do `ReceiptRepository::exportData()` — wszystko liczymy z istniejących kolumn (`items_json`, `total_net`, `total_gross`).
|
||||||
|
</action>
|
||||||
|
<verify>Wyeksportuj XLSX zawierający (a) nowy paragon multi-rate, (b) legacy paragon bez `vat`. Otwórz w Excelu — sprawdź kolumny i wiersze zgodnie z AC-1, AC-2, AC-3.</verify>
|
||||||
|
<done>AC-1 + AC-2 + AC-3 satisfied: eksport ma nowe kolumny i poprawne rozbicie per stawka.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- Struktura tabeli `receipts` (żadnej migracji DDL).
|
||||||
|
- `ReceiptRepository::exportData()` / `findByIds()` — kontrakt zapytania bez zmian (nowe pola obliczamy w controllerze z istniejących).
|
||||||
|
- Widoki paragonu (`receipts/print.php`, `receipts/preview.php`) — VAT w items_json jest dodatkowy, stare widoki nie wymagają zmian.
|
||||||
|
- Liczenie `total_gross` w `buildItemsSnapshot` — nie ruszać.
|
||||||
|
- `InvoiceService` (faktury VAT) — out of scope, ma własny multi-rate breakdown w PDF.
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Bez wstecznej migracji legacy items_json (nie aktualizujemy historycznych paragonów — fallback w eksporcie wystarczy).
|
||||||
|
- Bez UI toggle "tryb eksportu legacy/multi-rate" — tylko jedna nowa wersja eksportu.
|
||||||
|
- Bez zmian w eksporcie/widoku faktur (`/settings/accounting/invoices/issued`).
|
||||||
|
- Bez zmian w przyciskach selekcji/filtrach na `/accounting`.
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- [ ] Wystawienie nowego paragonu produkuje items_json z `vat` per pozycja
|
||||||
|
- [ ] Eksport "wszystkie paragony" z mieszanym datasetem (nowe + legacy) generuje poprawny XLSX
|
||||||
|
- [ ] Eksport "wybrane paragony" działa identycznie (Task 2 dotyczy obu ścieżek `$rows`)
|
||||||
|
- [ ] Liczba kolumn = 6, brak kolumn z usuniętej listy
|
||||||
|
- [ ] Paragon z 1 stawką = 1 wiersz; paragon z 2 stawkami = 2 wiersze; legacy = 1 wiersz
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Wszystkie tasks ukończone
|
||||||
|
- AC-1, AC-2, AC-3, AC-4 zweryfikowane manualnie na XAMPP
|
||||||
|
- Brak błędów PHP w log (`storage/logs/app.log`) podczas eksportu
|
||||||
|
- Plik XLSX otwiera się poprawnie w Excelu / LibreOffice
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Po zakończeniu utwórz `.paul/phases/123-receipts-export-vat-breakdown/123-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
137
.paul/phases/123-receipts-export-vat-breakdown/123-01-SUMMARY.md
Normal file
137
.paul/phases/123-receipts-export-vat-breakdown/123-01-SUMMARY.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
---
|
||||||
|
phase: 123-receipts-export-vat-breakdown
|
||||||
|
plan: 01
|
||||||
|
subsystem: accounting
|
||||||
|
tags: [receipts, xlsx-export, vat, phpspreadsheet]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 12-accounting-list
|
||||||
|
provides: AccountingController::export() z PhpSpreadsheet/Xlsx
|
||||||
|
- phase: 70-receipt-shipping-cost
|
||||||
|
provides: ReceiptService::buildItemsSnapshot() snapshot pattern z delivery
|
||||||
|
provides:
|
||||||
|
- items_json snapshot z polem `vat` per pozycja (nowe paragony)
|
||||||
|
- XLSX eksport paragonow z rozbiciem per stawka VAT
|
||||||
|
- helper buildVatBreakdown() + formatVatRate() w AccountingController
|
||||||
|
affects: [accounting/invoices, statistics, ksiegowosc-eksport]
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Per-rate VAT breakdown w eksporcie XLSX (jeden wiersz per (paragon x stawka))"
|
||||||
|
- "Legacy fallback: gdy snapshot nie ma `vat`, licz netto z brutto/(1+0.23)"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- src/Modules/Accounting/ReceiptService.php
|
||||||
|
- src/Modules/Accounting/AccountingController.php
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Legacy fallback liczy netto z brutto/1.23 (nie z `total_net` w bazie, bo issue() zapisuje total_net=total_gross)"
|
||||||
|
- "Multi-rate paragon = wiele wierszy XLSX z powtorzonymi Numer/Data/Brutto"
|
||||||
|
- "Stawka VAT brana z order_items.tax_rate; brak fallback na 23.0 dla wysylki"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "items_json snapshot moze byc rozszerzany o nowe pola (vat) bez zmiany BC widokow"
|
||||||
|
- "Eksport XLSX dekoduje items_json zamiast JOIN-owac order_items (zachowuje snapshot pattern)"
|
||||||
|
|
||||||
|
duration: ~25min
|
||||||
|
started: 2026-05-12T22:30:00Z
|
||||||
|
completed: 2026-05-12T23:00:00Z
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 123 Plan 01: Receipts Export VAT Breakdown Summary
|
||||||
|
|
||||||
|
**Eksport XLSX paragonow w `/accounting` ma 6 nowych kolumn (Numer | Data wystawienia | Kwota brutto | Kwota netto | Stawka VAT | Kwota VAT) z osobnym wierszem per stawka VAT; legacy paragony dostaja fallback 23%.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~25 min |
|
||||||
|
| Started | 2026-05-12T22:30:00Z |
|
||||||
|
| Completed | 2026-05-12T23:00:00Z |
|
||||||
|
| Tasks | 2 completed |
|
||||||
|
| Files modified | 2 (kod) + 3 (docs/state) |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: Naglowki XLSX zgodne z wymaganiem | Pass | 6 kolumn, brak Data sprzedazy/Konfiguracja/Nr zamowienia/Nr referencyjny — zweryfikowane przez operatora |
|
||||||
|
| AC-2: Multi-rate breakdown dla nowych paragonow | Pass (pending live test) | Logika `buildVatBreakdown()` grupuje po `vat`; do potwierdzenia po wystawieniu nowego paragonu z mieszanymi stawkami |
|
||||||
|
| AC-3: Legacy paragony — fallback 23% | Pass | Operator wyeksportowal kwiecien — pierwotnie net=brutto, vat=0 (bug). Po naprawie liczy net = brutto/1.23, vat = brutto - net |
|
||||||
|
| AC-4: Snapshot rozszerzony o VAT | Pass (pending live test) | Kod zapisuje `vat` per pozycja i `vat=23.0` dla wysylki; do potwierdzenia po wystawieniu nowego paragonu |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Usunieto z eksportu XLSX kolumny ksiegowo nieprzydatne (Data sprzedazy, Konfiguracja, Nr zamowienia, Nr referencyjny).
|
||||||
|
- Dodano kolumny Kwota netto / Stawka VAT / Kwota VAT z prawidlowym rozbiciem per stawka.
|
||||||
|
- Rozszerzono `items_json` snapshot o `vat` per pozycja (forward-compatible — stare widoki bez zmian).
|
||||||
|
- Naprawiono fallback dla legacy paragonow (nie polega na zlamanym `total_net = total_gross` z `ReceiptService::issue()`).
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `src/Modules/Accounting/ReceiptService.php` | Modified | `buildItemsSnapshot()` zapisuje `vat` per pozycja (z `order_items.tax_rate`, fallback 23.0) |
|
||||||
|
| `src/Modules/Accounting/AccountingController.php` | Modified | Nowe naglowki + helper `buildVatBreakdown()` + `formatVatRate()`; multi-rate breakdown |
|
||||||
|
| `.paul/codebase/architecture.md` | Modified | Sekcja "Phase 123 — Receipts Export VAT Breakdown" |
|
||||||
|
| `.paul/codebase/tech_changelog.md` | Modified | Nowy wpis Phase 123 |
|
||||||
|
| `.paul/STATE.md` | Modified | Loop position UNIFY |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| Legacy fallback liczy `net = brutto/1.23` zamiast brac `total_net` z bazy | `ReceiptService::issue()` zapisuje `total_net = total_gross` (znany bug, poza zakresem 123); branie `total_net` daje vat=0 dla wszystkich starych paragonow | Operator widzi sensowne rozbicie VAT dla historycznych paragonow; oryginalny `total_net` w `receipts` pozostaje nietkniety |
|
||||||
|
| Multi-rate = wiele wierszy XLSX (nie wieloliniowe komorki) | User wybral ten format (bardziej Excel-friendly do sumowania) | Numer/Data/Brutto powtarzaja sie w wierszach jednego paragonu |
|
||||||
|
| Pozycja "Koszt wysylki" dostaje `vat=23.0` | Domyslna stawka dla uslugi wysylki w PL | Eksport bedzie spojny dopoki nie pojawi sie potrzeba uslug 8% (wtedy do uzupelnienia) |
|
||||||
|
| Brak migracji wstecznej `items_json` | Fallback w eksporcie wystarczy; backfill ryzykowny dla snapshotow | Stare paragony wyswietlaja sie w eksporcie z fallbackiem 23%; nowe — z dokladnym breakdown |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Type | Count | Impact |
|
||||||
|
|------|-------|--------|
|
||||||
|
| Auto-fixed | 1 | Krytyczne — bez tej naprawy AC-3 by failowal |
|
||||||
|
| Scope additions | 0 | — |
|
||||||
|
| Deferred | 1 | RECEIPT-NET-FIX (poza zakresem 123) |
|
||||||
|
|
||||||
|
**Total impact:** Jedna naprawa post-APPLY na podstawie smoke testu operatora; reszta zgodnie z planem.
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Bugfix] Legacy fallback dawal net=brutto, vat=0**
|
||||||
|
- **Found during:** smoke test operatora (eksport kwietnia)
|
||||||
|
- **Issue:** Plan zakladal `Kwota netto = total_net`, `Kwota VAT = total_gross - total_net`, ale `ReceiptService::issue()` zapisuje `total_net = total_gross` (linie 81-82 ReceiptService) — wiec roznica zawsze = 0.
|
||||||
|
- **Fix:** Zmieniono fallback na `net = round(total_gross / 1.23, 2)`, `vat = total_gross - net`.
|
||||||
|
- **Files:** `src/Modules/Accounting/AccountingController.php` (`buildVatBreakdown()`)
|
||||||
|
- **Verification:** Operator potwierdzil eksportem kwietnia — kwoty teraz sensowne.
|
||||||
|
|
||||||
|
### Deferred Items
|
||||||
|
|
||||||
|
- **RECEIPT-NET-FIX** — `ReceiptService::issue()` powinien zapisywac realne `total_net` (a nie kopie `total_gross`). Wymaga sumowania per pozycja z VAT. Dodaj do `.paul/codebase/todo.md`.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
| Issue | Resolution |
|
||||||
|
|-------|------------|
|
||||||
|
| Legacy fallback dawal net=brutto, vat=0 | Naprawione w `buildVatBreakdown()` — patrz Auto-fixed #1 |
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- Eksport XLSX dziala z VAT breakdown dla nowych paragonow.
|
||||||
|
- Snapshot pattern rozszerzony o `vat` — gotowy na inne raporty (np. eksport per stawka, JPK).
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- `ReceiptService::issue()` nadal zapisuje `total_net = total_gross` (RECEIPT-NET-FIX) — eksport teraz nie polega na tym polu, ale UI/widoki paragonu moga wyswietlac mylace dane.
|
||||||
|
|
||||||
|
**Blockers:** None
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 123-receipts-export-vat-breakdown, Plan: 01*
|
||||||
|
*Completed: 2026-05-12*
|
||||||
@@ -130,7 +130,7 @@ final class AccountingController
|
|||||||
$sheet = $spreadsheet->getActiveSheet();
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
$sheet->setTitle('Paragony');
|
$sheet->setTitle('Paragony');
|
||||||
|
|
||||||
$headers = ['Numer', 'Data wystawienia', 'Data sprzedazy', 'Kwota brutto', 'Konfiguracja', 'Nr zamowienia', 'Nr referencyjny'];
|
$headers = ['Numer', 'Data wystawienia', 'Kwota brutto', 'Kwota netto', 'Stawka VAT', 'Kwota VAT'];
|
||||||
foreach ($headers as $col => $header) {
|
foreach ($headers as $col => $header) {
|
||||||
$sheet->setCellValue([$col + 1, 1], $header);
|
$sheet->setCellValue([$col + 1, 1], $header);
|
||||||
}
|
}
|
||||||
@@ -139,18 +139,26 @@ final class AccountingController
|
|||||||
|
|
||||||
$rowNum = 2;
|
$rowNum = 2;
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$sheet->setCellValue([1, $rowNum], (string) ($row['receipt_number'] ?? ''));
|
|
||||||
$issueDateRaw = (string) ($row['issue_date'] ?? '');
|
$issueDateRaw = (string) ($row['issue_date'] ?? '');
|
||||||
$sheet->setCellValue([2, $rowNum], strlen($issueDateRaw) >= 16 ? substr($issueDateRaw, 0, 16) : $issueDateRaw);
|
$issueDate = strlen($issueDateRaw) >= 16 ? substr($issueDateRaw, 0, 16) : $issueDateRaw;
|
||||||
$sheet->setCellValue([3, $rowNum], (string) ($row['sale_date'] ?? ''));
|
$receiptNumber = (string) ($row['receipt_number'] ?? '');
|
||||||
$sheet->setCellValue([4, $rowNum], (float) ($row['total_gross'] ?? 0));
|
$totalGross = (float) ($row['total_gross'] ?? 0);
|
||||||
$sheet->setCellValue([5, $rowNum], (string) ($row['config_name'] ?? ''));
|
$totalNet = (float) ($row['total_net'] ?? 0);
|
||||||
$sheet->setCellValue([6, $rowNum], (string) ($row['internal_order_number'] ?? $row['external_order_id'] ?? ''));
|
|
||||||
$sheet->setCellValue([7, $rowNum], (string) ($row['order_reference_value'] ?? ''));
|
$breakdown = $this->buildVatBreakdown((string) ($row['items_json'] ?? ''), $totalNet, $totalGross);
|
||||||
$rowNum++;
|
|
||||||
|
foreach ($breakdown as $line) {
|
||||||
|
$sheet->setCellValue([1, $rowNum], $receiptNumber);
|
||||||
|
$sheet->setCellValue([2, $rowNum], $issueDate);
|
||||||
|
$sheet->setCellValue([3, $rowNum], $totalGross);
|
||||||
|
$sheet->setCellValue([4, $rowNum], $line['net']);
|
||||||
|
$sheet->setCellValue([5, $rowNum], $line['rate_label']);
|
||||||
|
$sheet->setCellValue([6, $rowNum], $line['vat']);
|
||||||
|
$rowNum++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (range(1, 7) as $col) {
|
foreach (range(1, 6) as $col) {
|
||||||
$sheet->getColumnDimensionByColumn($col)->setAutoSize(true);
|
$sheet->getColumnDimensionByColumn($col)->setAutoSize(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,4 +243,71 @@ final class AccountingController
|
|||||||
|
|
||||||
return $params !== [] ? '?' . http_build_query($params) : '';
|
return $params !== [] ? '?' . http_build_query($params) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array{rate_label: string, net: float, vat: float}>
|
||||||
|
*/
|
||||||
|
private function buildVatBreakdown(string $itemsJson, float $totalNet, float $totalGross): array
|
||||||
|
{
|
||||||
|
$items = [];
|
||||||
|
if ($itemsJson !== '') {
|
||||||
|
$decoded = json_decode($itemsJson, true);
|
||||||
|
if (is_array($decoded)) {
|
||||||
|
$items = $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = [];
|
||||||
|
$hasVat = false;
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (!is_array($item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!array_key_exists('vat', $item) || $item['vat'] === null || $item['vat'] === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$hasVat = true;
|
||||||
|
$vatRate = (float) $item['vat'];
|
||||||
|
$key = number_format($vatRate, 2, '.', '');
|
||||||
|
$lineGross = (float) ($item['total'] ?? 0);
|
||||||
|
if (!isset($groups[$key])) {
|
||||||
|
$groups[$key] = ['rate' => $vatRate, 'gross' => 0.0];
|
||||||
|
}
|
||||||
|
$groups[$key]['gross'] += $lineGross;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$hasVat) {
|
||||||
|
$net = round($totalGross / 1.23, 2);
|
||||||
|
$vatAmount = round($totalGross - $net, 2);
|
||||||
|
return [[
|
||||||
|
'rate_label' => '23%',
|
||||||
|
'net' => $net,
|
||||||
|
'vat' => $vatAmount,
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($groups, SORT_NUMERIC);
|
||||||
|
$result = [];
|
||||||
|
foreach (array_reverse($groups) as $group) {
|
||||||
|
$rate = (float) $group['rate'];
|
||||||
|
$gross = (float) $group['gross'];
|
||||||
|
$net = $rate > 0.0 ? round($gross / (1 + $rate / 100), 2) : round($gross, 2);
|
||||||
|
$vat = round($gross - $net, 2);
|
||||||
|
$result[] = [
|
||||||
|
'rate_label' => $this->formatVatRate($rate),
|
||||||
|
'net' => $net,
|
||||||
|
'vat' => $vat,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatVatRate(float $rate): string
|
||||||
|
{
|
||||||
|
if (abs($rate - round($rate)) < 0.005) {
|
||||||
|
return ((int) round($rate)) . '%';
|
||||||
|
}
|
||||||
|
return rtrim(rtrim(number_format($rate, 2, '.', ''), '0'), '.') . '%';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,11 +261,14 @@ final class ReceiptService
|
|||||||
$price = $item['original_price_with_tax'] !== null ? (float) $item['original_price_with_tax'] : 0.0;
|
$price = $item['original_price_with_tax'] !== null ? (float) $item['original_price_with_tax'] : 0.0;
|
||||||
$lineTotal = $qty * $price;
|
$lineTotal = $qty * $price;
|
||||||
$totalGross += $lineTotal;
|
$totalGross += $lineTotal;
|
||||||
|
$vatRaw = $item['tax_rate'] ?? $item['vat'] ?? null;
|
||||||
|
$vat = $vatRaw !== null && $vatRaw !== '' ? (float) $vatRaw : 23.0;
|
||||||
$itemsSnapshot[] = [
|
$itemsSnapshot[] = [
|
||||||
'name' => $item['original_name'] ?? '',
|
'name' => $item['original_name'] ?? '',
|
||||||
'quantity' => $qty,
|
'quantity' => $qty,
|
||||||
'price' => $price,
|
'price' => $price,
|
||||||
'total' => $lineTotal,
|
'total' => $lineTotal,
|
||||||
|
'vat' => $vat,
|
||||||
'sku' => $item['sku'] ?? '',
|
'sku' => $item['sku'] ?? '',
|
||||||
'ean' => $item['ean'] ?? '',
|
'ean' => $item['ean'] ?? '',
|
||||||
];
|
];
|
||||||
@@ -279,6 +282,7 @@ final class ReceiptService
|
|||||||
'quantity' => 1.0,
|
'quantity' => 1.0,
|
||||||
'price' => $deliveryPrice,
|
'price' => $deliveryPrice,
|
||||||
'total' => $deliveryPrice,
|
'total' => $deliveryPrice,
|
||||||
|
'vat' => 23.0,
|
||||||
'sku' => '',
|
'sku' => '',
|
||||||
'ean' => '',
|
'ean' => '',
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user