Phase 08 — DB Foundation: - 3 new tables: receipt_configs, receipts, receipt_number_counters - company_settings extended with BDO, REGON, KRS, logo fields Phase 09 — Receipt Config: - CRUD for receipt configurations (Settings > Accounting) - ReceiptConfigController + ReceiptConfigRepository Phase 10 — Receipt Issuing: - ReceiptRepository with atomic numbering (INSERT ON DUPLICATE KEY UPDATE) - ReceiptController with snapshot pattern (seller/buyer/items as JSON) - "Wystaw paragon" button in order view - Documents tab showing both receipts and marketplace documents - Activity log entry on receipt creation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
321 lines
15 KiB
Markdown
321 lines
15 KiB
Markdown
---
|
|
phase: 10-receipt-issue
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: ["09-01"]
|
|
files_modified:
|
|
- src/Modules/Accounting/ReceiptRepository.php
|
|
- src/Modules/Accounting/ReceiptController.php
|
|
- src/Modules/Orders/OrdersController.php
|
|
- resources/views/orders/show.php
|
|
- resources/views/orders/receipt-create.php
|
|
- routes/web.php
|
|
- resources/lang/pl.php
|
|
- resources/scss/shared/_ui-components.scss
|
|
- public/assets/css/app.css
|
|
- DOCS/ARCHITECTURE.md
|
|
autonomous: false
|
|
---
|
|
|
|
<objective>
|
|
## Goal
|
|
Umozliwienie wystawiania paragonow z poziomu widoku zamowienia — przycisk "Wystaw paragon", formularz wyboru konfiguracji z podgladem pozycji, zapis do bazy z atomowym numerowaniem i snapshotem danych.
|
|
|
|
## Purpose
|
|
Kluczowa funkcjonalnosc modulu Ksiegowosci (v0.3) — sprzedawca moze wystawic paragon bezposrednio z zamowienia, bez opuszczania widoku zamowienia.
|
|
|
|
## Output
|
|
- `ReceiptRepository` — CRUD na `receipts` + atomowe numerowanie przez `receipt_number_counters`
|
|
- `ReceiptController` — formularz tworzenia paragonu + zapis
|
|
- Przycisk "Wystaw paragon" w widoku zamowienia
|
|
- Lista wystawionych paragonow w zakladce "Dokumenty"
|
|
</objective>
|
|
|
|
<context>
|
|
## Project Context
|
|
@.paul/PROJECT.md
|
|
@.paul/ROADMAP.md
|
|
@.paul/STATE.md
|
|
|
|
## Prior Work
|
|
@.paul/phases/08-db-foundation/08-01-SUMMARY.md — schemat tabel receipts, receipt_configs, receipt_number_counters
|
|
@.paul/phases/09-receipt-config/09-01-SUMMARY.md — CRUD konfiguracji, ReceiptConfigRepository, wzorce Request/Csrf
|
|
|
|
## Source Files
|
|
@database/migrations/20260315_000051_create_receipts_table.sql
|
|
@database/migrations/20260315_000052_create_receipt_number_counters_table.sql
|
|
@src/Modules/Settings/ReceiptConfigRepository.php
|
|
@src/Modules/Settings/CompanySettingsRepository.php
|
|
@src/Modules/Orders/OrdersController.php
|
|
@resources/views/orders/show.php
|
|
@routes/web.php
|
|
</context>
|
|
|
|
<skills>
|
|
## Required Skills (from SPECIAL-FLOWS.md)
|
|
|
|
| Skill | Priority | When to Invoke | Loaded? |
|
|
|-------|----------|----------------|---------|
|
|
| sonar-scanner | required | Po APPLY, przed UNIFY | ○ |
|
|
|
|
## Skill Invocation Checklist
|
|
- [ ] sonar-scanner uruchomiony po APPLY
|
|
</skills>
|
|
|
|
<acceptance_criteria>
|
|
|
|
## AC-1: Przycisk "Wystaw paragon" w widoku zamowienia
|
|
```gherkin
|
|
Given uzytkownik jest na stronie szczegulow zamowienia /orders/{id}
|
|
When sa aktywne konfiguracje paragonow
|
|
Then widoczny jest przycisk "Wystaw paragon" w sekcji akcji
|
|
And przycisk prowadzi do formularza /orders/{id}/receipt/create
|
|
```
|
|
|
|
## AC-2: Formularz wystawiania paragonu
|
|
```gherkin
|
|
Given uzytkownik otwiera /orders/{id}/receipt/create
|
|
When formularz sie laduje
|
|
Then widoczny jest select z aktywnymi konfiguracjami paragonow
|
|
And wyswietlona jest tabela pozycji zamowienia (nazwa, ilosc, cena, suma)
|
|
And wyswietlone sa pola: data wystawienia (domyslnie dzis), data sprzedazy (wg konfiguracji)
|
|
And widoczny jest podglad danych sprzedawcy z company_settings
|
|
And przycisk "Wystaw paragon" submituje formularz
|
|
```
|
|
|
|
## AC-3: Zapis paragonu z atomowym numerowaniem
|
|
```gherkin
|
|
Given uzytkownik wypelnia formularz i klika "Wystaw paragon"
|
|
When POST /orders/{id}/receipt/store jest wysylany
|
|
Then tworzony jest rekord w tabeli receipts z:
|
|
- receipt_number wygenerowanym atomowo z receipt_number_counters (INSERT ON DUPLICATE KEY UPDATE)
|
|
- seller_data_json jako snapshot company_settings
|
|
- items_json jako snapshot pozycji zamowienia
|
|
- total_net i total_gross obliczone z pozycji
|
|
- sale_date okreslona wg sale_date_source z konfiguracji
|
|
- order_reference_value wypelnione wg order_reference z konfiguracji
|
|
And uzytkownik jest przekierowany na /orders/{id} z flash success
|
|
```
|
|
|
|
## AC-4: Lista paragonow w zakladce Dokumenty
|
|
```gherkin
|
|
Given zamowienie ma wystawione paragony
|
|
When uzytkownik klika zakladke "Dokumenty"
|
|
Then wyswietlona jest tabela paragonow (numer, data wystawienia, kwota brutto, konfiguracja)
|
|
And kazdy paragon ma link do podgladu (na razie placeholder — faza 11)
|
|
```
|
|
|
|
## AC-5: Walidacja — brak duplikatow i brak pustych konfiguracji
|
|
```gherkin
|
|
Given uzytkownik probuje wystawic paragon
|
|
When nie ma aktywnych konfiguracji
|
|
Then przycisk "Wystaw paragon" nie jest widoczny w widoku zamowienia
|
|
|
|
Given uzytkownik submituje formularz bez wybranej konfiguracji
|
|
When POST jest wysylany
|
|
Then zwracany jest blad walidacji i paragon nie jest tworzony
|
|
```
|
|
|
|
</acceptance_criteria>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: ReceiptRepository — CRUD + atomowe numerowanie</name>
|
|
<files>src/Modules/Accounting/ReceiptRepository.php</files>
|
|
<action>
|
|
Utworz nowy modul `src/Modules/Accounting/` z klasa `ReceiptRepository`:
|
|
|
|
1. **Konstruktor:** przyjmuje `PDO $pdo`
|
|
2. **getNextNumber(int $configId, string $numberFormat, string $numberingType): string**
|
|
- Atomowe numerowanie przez `INSERT INTO receipt_number_counters (config_id, year, month, last_number) VALUES (:config_id, :year, :month, 1) ON DUPLICATE KEY UPDATE last_number = last_number + 1`
|
|
- Odczyt `last_number` przez `SELECT last_number FROM receipt_number_counters WHERE config_id = :config_id AND year = :year AND month = :month`
|
|
- Dla `numbering_type = 'yearly'`: month = NULL (w unique key)
|
|
- Dla `numbering_type = 'monthly'`: month = biezacy miesiac
|
|
- Podmiana w formacie: `%N` → numer (z zerem wiodacym min 3 cyfry), `%M` → miesiac (2 cyfry), `%Y` → rok (4 cyfry)
|
|
3. **create(array $data): int**
|
|
- INSERT do `receipts` ze wszystkimi polami
|
|
- Zwraca `lastInsertId()`
|
|
4. **findByOrderId(int $orderId): array**
|
|
- SELECT receipts + LEFT JOIN receipt_configs (na nazwe konfiguracji)
|
|
- ORDER BY created_at DESC
|
|
5. **findById(int $id): ?array**
|
|
- SELECT * WHERE id = :id
|
|
|
|
Wzorzec: analogicznie do ReceiptConfigRepository (PDO, prepared statements, strict types).
|
|
Namespace: `App\Modules\Accounting`
|
|
</action>
|
|
<verify>Klasa parsuje sie bez bledow: `php -l src/Modules/Accounting/ReceiptRepository.php`</verify>
|
|
<done>AC-3 backend spelnione: atomowe numerowanie i zapis paragonu</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: ReceiptController + routing + widok formularza</name>
|
|
<files>
|
|
src/Modules/Accounting/ReceiptController.php,
|
|
resources/views/orders/receipt-create.php,
|
|
routes/web.php,
|
|
resources/lang/pl.php
|
|
</files>
|
|
<action>
|
|
1. **ReceiptController** (`App\Modules\Accounting`):
|
|
- Konstruktor: `Template, Translator, AuthService, ReceiptRepository, ReceiptConfigRepository, CompanySettingsRepository, OrdersRepository`
|
|
- **create(Request $request): Response** — GET /orders/{id}/receipt/create
|
|
- Pobierz zamowienie przez OrdersRepository::findDetails($orderId)
|
|
- Pobierz aktywne konfiguracje: ReceiptConfigRepository::listAll() + filtruj is_active = 1
|
|
- Pobierz dane sprzedawcy: CompanySettingsRepository::getSettings()
|
|
- Renderuj widok `orders/receipt-create`
|
|
- **store(Request $request): Response** — POST /orders/{id}/receipt/store
|
|
- Walidacja: config_id wymagane, zamowienie istnieje
|
|
- CSRF: Csrf::validate()
|
|
- Pobierz konfiguracje (findById), zamowienie (findDetails), company settings
|
|
- Oblicz sale_date wg `sale_date_source`: order_date → ordered_at, payment_date → z payments, issue_date → dzis
|
|
- Oblicz order_reference_value wg `order_reference`: none → NULL, orderpro → internal_order_number, integration → external_order_id
|
|
- Zbuduj seller_data_json z company_settings (company_name, tax_number, street, city, postal_code, phone, email, bank_account, bdo_number, regon, court_register)
|
|
- Zbuduj buyer_data_json z address (invoice lub customer)
|
|
- Zbuduj items_json z pozycji zamowienia (original_name, quantity, original_price_with_tax, sku, ean)
|
|
- Oblicz total_gross = suma(qty * price), total_net = total_gross (paragony nie rozdzielaja netto/brutto — wartosc taka sama)
|
|
- Wygeneruj numer przez ReceiptRepository::getNextNumber()
|
|
- ReceiptRepository::create() ze wszystkimi danymi
|
|
- Flash::set('order.success', 'Paragon wystawiony: ' . $receiptNumber)
|
|
- Redirect /orders/{id}
|
|
|
|
2. **Widok receipt-create.php:**
|
|
- Layout z naglowkiem "Wystaw paragon" + link powrotny do zamowienia
|
|
- Select z aktywnymi konfiguracjami (name + number_format)
|
|
- Tabela pozycji zamowienia (readonly): nazwa, ilosc, cena, suma
|
|
- Podsumowanie kwoty brutto
|
|
- Podglad danych sprzedawcy (readonly z company_settings)
|
|
- Pole daty wystawienia (input date, domyslnie dzis)
|
|
- Przycisk "Wystaw paragon" + CSRF token
|
|
|
|
3. **routes/web.php:**
|
|
- Dodaj use statements: `ReceiptController`, `ReceiptRepository` (z `App\Modules\Accounting`)
|
|
- Utworz instancje: `$receiptRepository = new ReceiptRepository($app->db())`
|
|
- Utworz kontroler: `$receiptController = new ReceiptController($template, $translator, $auth, $receiptRepository, $receiptConfigRepository, $companySettingsRepository, new OrdersRepository($app->db()))`
|
|
- Zarejestruj trasy:
|
|
- `GET /orders/{id}/receipt/create` → `[$receiptController, 'create']`
|
|
- `POST /orders/{id}/receipt/store` → `[$receiptController, 'store']`
|
|
|
|
4. **resources/lang/pl.php:**
|
|
- Dodaj klucze `receipts.create.title`, `receipts.create.select_config`, `receipts.create.issue_date`, `receipts.create.submit`, `receipts.create.seller_data`, `receipts.create.items`, `receipts.create.total`, `receipts.create.back`
|
|
|
|
Wzorzec kontrolera: analogicznie do ReceiptConfigController (Csrf::token(), Csrf::validate(), Flash::set/get, Request::input()).
|
|
NIE uzywaj natywnych alert()/confirm() — OrderProAlerts juz jest w uzyciu.
|
|
</action>
|
|
<verify>
|
|
- `php -l src/Modules/Accounting/ReceiptController.php`
|
|
- `php -l resources/views/orders/receipt-create.php`
|
|
- Otworz /orders/{id}/receipt/create w przegladarce — formularz sie wyswietla
|
|
</verify>
|
|
<done>AC-2, AC-3, AC-5 spelnione: formularz, zapis, walidacja</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 3: Integracja z widokiem zamowienia — przycisk + zakladka Dokumenty</name>
|
|
<files>
|
|
src/Modules/Orders/OrdersController.php,
|
|
resources/views/orders/show.php,
|
|
routes/web.php
|
|
</files>
|
|
<action>
|
|
1. **OrdersController::show():**
|
|
- Dodaj zaleznosc: `ReceiptConfigRepository` i `ReceiptRepository` (przez konstruktor lub przekazanie w widoku)
|
|
- UWAGA: OrdersController ma juz 5 parametrow konstruktora. Zamiast rozszerzac konstruktor, przekaz dane przez nowe instancje w routes/web.php:
|
|
- W web.php: pobierz aktywne configs i receipts w zamknieciu routera GET /orders/{id} LUB
|
|
- Prostsze: dodaj `?ReceiptRepository` i `?ReceiptConfigRepository` do konstruktora OrdersController
|
|
- W metodzie show(): pobierz `$receiptConfigs = $this->receiptConfigs->listAll()` (filtruj aktywne)
|
|
- Pobierz `$receipts = $this->receipts->findByOrderId($orderId)`
|
|
- Przekaz do widoku: `'receiptConfigs' => $activeConfigs, 'receipts' => $receipts`
|
|
|
|
2. **orders/show.php — przycisk "Wystaw paragon":**
|
|
- W sekcji `.order-details-actions` (linia ~47-54):
|
|
- Dodaj przycisk miedzy "Przygotuj przesylke" a "Platnosc":
|
|
```php
|
|
<?php if (($receiptConfigs ?? []) !== []): ?>
|
|
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/receipt/create" class="btn btn--secondary">Wystaw paragon</a>
|
|
<?php endif; ?>
|
|
```
|
|
- Przycisk widoczny tylko gdy sa aktywne konfiguracje (AC-1, AC-5)
|
|
|
|
3. **orders/show.php — zakladka Dokumenty:**
|
|
- Zastap pusty placeholder w `data-order-tab-panel="documents"` (linia ~516-521):
|
|
- Tabela paragonow: Numer, Data wystawienia, Kwota brutto, Konfiguracja, Akcje
|
|
- Jesli brak paragonow: "Brak dokumentow"
|
|
- Akcja: link "Podglad" (na razie `#` — placeholder do fazy 11)
|
|
- Zaktualizuj licznik w zakladce: `Dokumenty (N)` gdzie N = count($receipts)
|
|
|
|
4. **routes/web.php:**
|
|
- Dodaj `use App\Modules\Accounting\ReceiptRepository;` na gorze
|
|
- Przekaz `$receiptRepository` i `$receiptConfigRepository` do konstruktora `$ordersController`
|
|
|
|
5. **DOCS/ARCHITECTURE.md:**
|
|
- Dodaj modul Accounting z klasami ReceiptRepository, ReceiptController
|
|
- Dodaj trasy GET/POST /orders/{id}/receipt/*
|
|
</action>
|
|
<verify>
|
|
- Otworz /orders/{id} — przycisk "Wystaw paragon" widoczny (jesli sa aktywne configs)
|
|
- Kliknij zakladke "Dokumenty" — tabela paragonow wyswietla sie
|
|
- Po wystawieniu paragonu — pojawia sie na liscie w zakladce Dokumenty
|
|
</verify>
|
|
<done>AC-1, AC-4 spelnione: przycisk w widoku zamowienia + lista paragonow w Dokumentach</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<what-built>Wystawianie paragonow z poziomu zamowienia — pelny flow: przycisk → formularz → zapis → lista w Dokumentach</what-built>
|
|
<how-to-verify>
|
|
1. Otworz dowolne zamowienie /orders/{id}
|
|
2. Sprawdz: przycisk "Wystaw paragon" jest widoczny w akcjach
|
|
3. Kliknij "Wystaw paragon" — formularz sie otwiera
|
|
4. Wybierz konfiguracje, sprawdz podglad pozycji i danych sprzedawcy
|
|
5. Kliknij "Wystaw paragon" w formularzu
|
|
6. Sprawdz: redirect na zamowienie z flash "Paragon wystawiony: PAR/001/03/2026"
|
|
7. Kliknij zakladke "Dokumenty" — paragon widoczny na liscie
|
|
8. Wystaw drugi paragon — numer powinien byc PAR/002/03/2026
|
|
</how-to-verify>
|
|
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<boundaries>
|
|
|
|
## DO NOT CHANGE
|
|
- database/migrations/* (schemat zablokowany — tabele juz istnieja z fazy 08)
|
|
- src/Modules/Settings/ReceiptConfigRepository.php (gotowe z fazy 09)
|
|
- src/Modules/Settings/ReceiptConfigController.php (gotowe z fazy 09)
|
|
- resources/views/settings/accounting.php (gotowe z fazy 09)
|
|
|
|
## SCOPE LIMITS
|
|
- Brak podgladu/wydruku paragonu — to faza 11
|
|
- Brak edycji/anulowania paragonu — poza zakresem v0.3
|
|
- Brak generowania PDF — to faza 11
|
|
- Paragony nie rozdzielaja netto/brutto (total_net = total_gross) — uproszczenie dla paragonow
|
|
|
|
</boundaries>
|
|
|
|
<verification>
|
|
Before declaring plan complete:
|
|
- [ ] `php -l src/Modules/Accounting/ReceiptRepository.php` — brak bledow
|
|
- [ ] `php -l src/Modules/Accounting/ReceiptController.php` — brak bledow
|
|
- [ ] Przycisk "Wystaw paragon" widoczny w /orders/{id} gdy sa aktywne konfiguracje
|
|
- [ ] Formularz /orders/{id}/receipt/create wyswietla pozycje zamowienia i konfiguracje
|
|
- [ ] Po wystawieniu paragonu: redirect + flash + rekord w bazie
|
|
- [ ] Numer paragonu generowany atomowo (kolejne numery nie powtarzaja sie)
|
|
- [ ] Zakladka Dokumenty wyswietla wystawione paragony
|
|
- [ ] Brak bledow w konsoli PHP
|
|
- [ ] All acceptance criteria met
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Wszystkie 3 taski auto + 1 checkpoint ukonczone
|
|
- Wszystkie AC-1 do AC-5 spelnione
|
|
- Brak bledow PHP (php -l na wszystkich nowych plikach)
|
|
- Weryfikacja manualna przez uzytkownika (checkpoint)
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.paul/phases/10-receipt-issue/10-01-SUMMARY.md`
|
|
</output>
|