Files
orderPRO/.paul/phases/123-receipts-export-vat-breakdown/123-01-PLAN.md
Jacek Pyziak 0227f2d072 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>
2026-05-12 21:06:53 +02:00

9.0 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, delegation
phase plan type wave depends_on files_modified autonomous delegation
123-receipts-export-vat-breakdown 01 execute 1
src/Modules/Accounting/AccountingController.php
src/Modules/Accounting/ReceiptService.php
true off
## 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).
- **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`.

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

<acceptance_criteria>

AC-1: Nagłówki XLSX zgodne z wymaganiem

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

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%

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)

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>

Task 1: Rozszerz items_json snapshot o stawkę VAT per pozycja src/Modules/Accounting/ReceiptService.php 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).
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`. AC-4 satisfied: nowe paragony zawierają `vat` w items_json. Task 2: Przepisz AccountingController::export() — nowe nagłówki + multi-rate breakdown src/Modules/Accounting/AccountingController.php 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`).
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. AC-1 + AC-2 + AC-3 satisfied: eksport ma nowe kolumny i poprawne rozbicie per stawka.

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.
- [ ] 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

<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>
Po zakończeniu utwórz `.paul/phases/123-receipts-export-vat-breakdown/123-01-SUMMARY.md`