UPDATE
This commit is contained in:
@@ -133,7 +133,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
||||
- [x] Tracking i automatyzacje Erli: lokalny provider tracking jak w Allegro, retry niekrytycznej rejestracji paczki zewnetrznej Erli z `shipment_tracking_sync`, wspolny kontekst `shipment.created`/`shipment.status_changed` dla regul e-mail/SMS/statystyk — Phase 131
|
||||
- [x] Hardening Erli: spojna diagnostyka importu/ACK w `integration_order_sync_state.last_error`, brak ACK po blednym batchu, testy jednostkowe import/status sync i dokumentacja obserwowalnosci bez nowej migracji — Phase 132
|
||||
- [x] Parytet Erli w powierzchniach wspolnych: filtr zrodla zamowien, kanaly statystyk dziennych/podsumowania, warunek integracji automatyzacji, menu integracji i etykiety `zrodlo` uzywaja wspolnego rejestru zrodel — Phase 133
|
||||
- [x] Backlog Reality Check: `.paul/codebase/todo.md` i `.paul/codebase/concerns.md` sklasyfikowane przeciw aktualnemu kodowi/docs, z dowodami w `BACKLOG-AUDIT.md` i pierwotnym routingiem do kolejnych faz dlugu; nieaktualne fazy 140+ usuniete 2026-05-18 decyzja operatora — Phase 134
|
||||
- [x] Backlog Reality Check: reczne wpisy z `.paul/codebase/todo.md` i dawny raport ryzyk sklasyfikowane przeciw aktualnemu kodowi/docs, z dowodami w `BACKLOG-AUDIT.md` i pierwotnym routingiem do kolejnych faz dlugu; nieaktualne fazy 140+ usuniete 2026-05-18 decyzja operatora — Phase 134
|
||||
- [x] Accounting Net Correctness: nowe paragony zapisuja VAT-aware `receipts.total_net`, a statystyki dzienne preferuja source-level net, potem `order_items` VAT fallback, z gross `/1.23` tylko jako legacy fallback — Phase 135
|
||||
- [x] Fakturownia Invoice Idempotency: delegowane faktury uzywaja stabilnego `oid=orders.internal_order_number`, lookup-first `GET /invoices.json?oid=...`, lokalnego stanu `pending_external`/`failed_retryable` i auto-attach po timeoutach — Phase 136
|
||||
- [x] Delivery Status Backlog Verification: `DELIVERY-STATUS-MGMT` zamkniete jako wdrozone; runtime korzysta z DB-driven statusow, a read-only DB check nie wykazal starych ani niepoprawnych kluczy automatyzacji — Phase 137
|
||||
@@ -325,9 +325,10 @@ Quick Reference:
|
||||
- /code-review → Przegląd kodu przed UNIFY (optional)
|
||||
- /frontend-design → Komponenty UI i widoki (optional)
|
||||
- /simplify → Refaktoryzacja po implementacji (optional)
|
||||
- SonarQube / `sonar-scanner` → reczny skan na zadanie operatora; nie jest wymagany w PLAN/APPLY/UNIFY
|
||||
|
||||
---
|
||||
*PROJECT.md — Updated when requirements or context change*
|
||||
*Last updated: 2026-05-18 after Phase 145 closure*
|
||||
*Last updated: 2026-05-18 after Sonar workflow policy update*
|
||||
|
||||
|
||||
|
||||
707
.paul/ROADMAP.md
707
.paul/ROADMAP.md
@@ -1,707 +0,0 @@
|
||||
# Roadmap: orderPRO
|
||||
|
||||
## Overview
|
||||
|
||||
orderPRO to narzedzie do wielokanalowego zarzadzania sprzedaza. Projekt przechodzi od podstawowych integracji z marketplace'ami i generowania etykiet, przez rozbudowe o nowe zrodla zamowien i przewoznikow, az do pelnego zarzadzania produktami i stanami magazynowymi.
|
||||
|
||||
## Current Milestone
|
||||
|
||||
v3.14 Polkurier COD Return Time Hotfix - Complete
|
||||
|
||||
Pilny hotfix tworzenia przesylek pobraniowych Polkurier: API odrzuca `create_order`, bo payload wysyla bledna wartosc czasu zwrotu pobrania. `codtype` ma uzywac kodu terminu (`S`, `1D`, `4D`, `16D`), a `return_cod` kodu sposobu zwrotu (`BA`, `PO`, `MB`).
|
||||
|
||||
Progress: 1 of 1 phases complete (100%).
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 145 | Polkurier COD Return Time Hotfix | 1/1 | Complete (2026-05-18; PHPUnit/Sonar/live smoke follow-up pending) |
|
||||
|
||||
### Phase 145: Polkurier COD Return Time Hotfix
|
||||
|
||||
Focus: Naprawic payload COD w `PolkurierShipmentService`, aby przesylki pobraniowe wysylaly `codtype='S'` jako standardowy termin zwrotu pobrania oraz `return_cod='BA'` jako przelew na konto bankowe. Brak zmian DB i brak UI konfiguracji terminow w tym hotfixie.
|
||||
Plans: 145-01 (complete; `.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md`)
|
||||
|
||||
## Previous Milestone
|
||||
|
||||
v3.13 Imported Notes Badge Count Hotfix - Complete
|
||||
|
||||
Pilny hotfix dla listy zamowien: notatki zaimportowane ze zrodla, np. shopPRO, maja byc zliczane razem z notatkami autorskimi w badge `[N]` przy numerze zamowienia.
|
||||
|
||||
Progress: 1 of 1 phases complete (100%).
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 144 | Imported Notes Badge Count Hotfix | 1/1 | Complete (2026-05-18; PHPUnit/Sonar env gaps documented) |
|
||||
|
||||
### Phase 144: Imported Notes Badge Count Hotfix
|
||||
|
||||
Focus: Zmienic licznik badge notatek na `/orders/list`, aby uzywal wszystkich rekordow `order_notes` dla zamowienia, a nie tylko `note_type='user'`. Zamowienie `1034` z importowana notatka shopPRO powinno pokazac cyfre na liscie.
|
||||
Plans: 144-01 (complete; `.paul/phases/144-imported-notes-badge-count/144-01-SUMMARY.md`)
|
||||
|
||||
## Earlier Milestone
|
||||
|
||||
v3.12 Orders List Sidebar UI Hotfix - Complete
|
||||
|
||||
Maly hotfix UX dla ekranu operacyjnego: usuniecie opisowego boksu z listy zamowien oraz likwidacja widocznego "pokaz i schowaj" przy odswiezaniu strony ze zwinietym sidebarem.
|
||||
|
||||
Progress: 1 of 1 phases complete (100%).
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 143 | Orders List Sidebar UI Hotfix | 1/1 | Complete (2026-05-18; manual UI/Sonar follow-up pending) |
|
||||
|
||||
### Phase 143: Orders List Sidebar UI Hotfix
|
||||
|
||||
Focus: Usunac boks "Zamowienia / Kompaktowa lista zamowien oparta o lokalna baze orderPRO." z `/orders/list` i zastosowac zapisany stan zwinietego sidebaru przed pierwszym renderem strony.
|
||||
Plans: 143-01 (complete; `.paul/phases/143-orders-list-sidebar-ui-hotfix/143-01-SUMMARY.md`)
|
||||
|
||||
## Earlier Milestone
|
||||
|
||||
v3.11 Polkurier Shipment Prepare Hotfix - Complete
|
||||
|
||||
Pilny hotfix po Phase 140: mapowanie shopPRO -> Polkurier zapisuje sie poprawnie, ale formularz `/orders/{id}/shipment/prepare` nie podstawia providera i uslugi Polkuriera.
|
||||
|
||||
Progress: 1 of 1 phases complete (100%).
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 142 | Polkurier Shipment Prepare Prefill | 1/1 | Complete (2026-05-18; PHPUnit/Sonar/manual live smoke follow-up pending) |
|
||||
|
||||
### Phase 142: Polkurier Shipment Prepare Prefill
|
||||
|
||||
Focus: Naprawic preselect w formularzu przygotowania przesylki, aby `carrier_delivery_method_mappings.provider='polkurier'` wybieral przewoznika Polkurier, zaznaczal zapisana usluge i ustawial hidden fields wymagane przez `PolkurierShipmentService`.
|
||||
Plans: 142-01 (complete; `.paul/phases/142-polkurier-shipment-prepare-prefill/142-01-SUMMARY.md`)
|
||||
|
||||
## Earlier Milestone
|
||||
|
||||
v3.10 Integrations UI Polish - Complete
|
||||
|
||||
Maly milestone porzadkujacy ekran `/settings/integrations`, aby rosnaca liczba integracji byla latwiejsza do skanowania bez zmiany backendowych kontraktow providerow.
|
||||
|
||||
Progress: 1 of 1 phases complete (100%).
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 141 | Integrations Hub Grouped Sections | 1/1 | Complete (2026-05-18; manual UI/Sonar follow-up pending) |
|
||||
|
||||
### Phase 141: Integrations Hub Grouped Sections
|
||||
|
||||
Focus: Podzielic `/settings/integrations` na lekkie sekcje/kafelki: marketplace (Allegro Sandbox, Allegro Production, shopPRO, Erli), kurierzy (InPost, Apaczka, polkurier.pl) i pozostale (Fakturownia, HostedSMS, SMSPLANET), usuwajac niepotrzebny naglowek/opis "Wspolny panel konfiguracji wszystkich providerow."
|
||||
Plans: 141-01 (complete; `.paul/phases/141-integrations-hub-grouped-sections/141-01-SUMMARY.md`)
|
||||
|
||||
## Earlier Milestone
|
||||
|
||||
v3.9 Stabilizacja i splata dlugu technicznego - Complete
|
||||
|
||||
Milestone porzadkujacy zbudowany z `.paul/codebase/todo.md` i `.paul/codebase/concerns.md`: poprawa znanych bugow, weryfikacja stalych ryzyk, domkniecie security/performance oraz ograniczenie dlugu technicznego, ktory utrudnia kolejne wdrozenia.
|
||||
|
||||
Progress: 7 of 7 phases complete (100%).
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 134 | Backlog Reality Check | 1/1 | Complete (2026-05-16; documentation-only audit, Sonar CLI gap documented) |
|
||||
| 135 | Accounting Net Correctness | 1/1 | Complete (2026-05-16; VAT-aware receipt/stat net, PHPUnit/Sonar env gaps documented) |
|
||||
| 136 | Fakturownia Invoice Idempotency | 1/1 | Complete (2026-05-17; Fakturownia oid idempotency, migration/PHPUnit/Sonar env gaps documented) |
|
||||
| 137 | Delivery Status Backlog Verification | 1/1 | Complete (2026-05-17; verification-only closure, no stale automation keys found) |
|
||||
| 138 | Security and Legacy Hardening | 1/1 | Complete (2026-05-17; SMTP TLS/template/session/view hardening, PHPUnit/Sonar env gaps documented) |
|
||||
| 139 | Sonar Critical/Major Cleanup | 2/2 | Complete (2026-05-17; Sonar BLOCKER/CRITICAL/MAJOR reduced 648 -> 495 across two cleanup slices) |
|
||||
| 140 | shopPRO Polkurier Delivery Mapping | 1/1 | Complete (2026-05-18; shopPRO delivery mapping supports Polkurier, manual UI/Sonar follow-up pending) |
|
||||
|
||||
### Phase 134: Backlog Reality Check
|
||||
|
||||
Focus: Zweryfikowac wszystkie wpisy z `.paul/codebase/todo.md` i `.paul/codebase/concerns.md` przeciw aktualnemu kodowi oraz dokumentacji. Dla kazdego wpisu oznaczyc: nadal aktywne, juz wdrozone, nieaktualne albo wymaga decyzji operatora. Wynik ma stac sie wejsciem do planow kolejnych faz.
|
||||
Plans: 134-01 (complete; `.paul/phases/134-backlog-reality-check/134-01-SUMMARY.md`)
|
||||
|
||||
### Phase 135: Accounting Net Correctness
|
||||
|
||||
Focus: Poprawic znane rozbieznosci kwot netto: `RECEIPT-NET-FIX` dla `receipts.total_net` oraz `STAT-NET` dla statystyk zamowien bez stalego zalozenia 23% VAT. Zakres obejmuje ustalenie zrodla prawdy, ewentualny backfill i testy eksportow/statystyk.
|
||||
Plans: 135-01 (complete; `.paul/phases/135-accounting-net-correctness/135-01-SUMMARY.md`)
|
||||
|
||||
### Phase 136: Fakturownia Invoice Idempotency
|
||||
|
||||
Focus: Domknac `INVOICE-IDEMP-115`: zabezpieczyc delegowane wystawianie faktur przed podwojnym POST do Fakturowni po timeoutach lub utracie odpowiedzi, z weryfikacja mozliwosci `Idempotency-Key` albo deduplikacji po referencji.
|
||||
Plans: 136-01 (complete; `.paul/phases/136-fakturownia-invoice-idempotency/136-01-SUMMARY.md`)
|
||||
|
||||
### Phase 137: Delivery Status Backlog Verification
|
||||
|
||||
Focus: Zweryfikowac wpis `DELIVERY-STATUS-MGMT` z todo oraz breaking changes po Phase 108: statusy DB-driven, stare klucze grup statusow, usuniecie `SHIPMENT_STATUS_OPTION_MAP` i realny wplyw na reguly automatyzacji. Jezeli funkcjonalnosc jest juz wdrozona, zamknac/oczyscic backlog i zostawic tylko potwierdzone luki.
|
||||
Plans: 137-01 (complete; `.paul/phases/137-delivery-status-backlog-verification/137-01-SUMMARY.md`)
|
||||
|
||||
### Phase 138: Security and Legacy Hardening
|
||||
|
||||
Focus: Sprawdzic i naprawic po potwierdzeniu: szyfrowanie `print_api_keys.api_key`, `fsockopen('ssl://...')` w tescie skrzynki e-mail, injection przez zmienne szablonow, brakujacy import `RuntimeException`, stare `require` w widokach, raw `$_SESSION` i pozostale legacy patterns wskazane w concerns.
|
||||
Plans: 138-01 (complete; `.paul/phases/138-security-and-legacy-hardening/138-01-SUMMARY.md`)
|
||||
|
||||
### Phase 139: Sonar Critical/Major Cleanup
|
||||
|
||||
Focus: Zmniejszyc potwierdzone problemy SonarQube z `concerns.md`: generic exceptions, zbyt wiele returnow, powtarzajace sie literaly, cognitive complexity, unused parameters, use-namespace-import oraz accessibility (`aria-label`, `<output>`). Przed kazda grupa zmian odswiezyc stan skanu albo lokalnie potwierdzic wystepowanie problemu.
|
||||
Plans: 139-01 (complete; `.paul/phases/139-sonar-critical-major-cleanup/139-01-SUMMARY.md`); 139-02 (complete; `.paul/phases/139-sonar-critical-major-cleanup/139-02-SUMMARY.md`)
|
||||
|
||||
### Phase 140: shopPRO Polkurier Delivery Mapping
|
||||
|
||||
Focus: Dodac Polkurier do zakladki Dostawy w ustawieniach integracji shopPRO, aby formy dostawy z zamowien shopPRO mogly byc mapowane na lokalna usluge Polkurier i pozniej automatycznie preselectowane przy przygotowaniu przesylki.
|
||||
Plans: 140-01 (complete; `.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-SUMMARY.md`)
|
||||
|
||||
## Earlier Milestone
|
||||
|
||||
v3.8 Erli Marketplace Integration - Complete in code
|
||||
|
||||
Pelna integracja z erli.pl wzorowana na istniejacej integracji Allegro: konfiguracja konta/API, pobieranie zamowien, mapowanie i synchronizacja statusow, generowanie etykiet, tracking oraz wlaczenie Erli w istniejace przeplywy automatyzacji, statystyk i obslugi zamowien.
|
||||
|
||||
Progress: 7 of 7 phases complete (100%).
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 127 | Erli Integration Foundation | 1/1 | Complete (2026-05-15; migration/manual Erli API smoke pending operator) |
|
||||
| 128 | Erli Orders Import | 1/1 | Complete (2026-05-15; migration/manual Erli import smoke pending operator) |
|
||||
| 129 | Erli Status Mapping + Sync | 1/1 | Complete (2026-05-16; migration/manual Erli status smoke pending operator) |
|
||||
| 130 | Erli Shipments + Labels | 1/1 | Complete (2026-05-16; migration/manual Erli shipping smoke pending operator) |
|
||||
| 131 | Erli Tracking + Automation Hooks | 1/1 | Complete (2026-05-16; manual Erli tracking/automation smoke pending operator) |
|
||||
| 132 | Erli Hardening, Observability + Docs | 1/1 | Complete (2026-05-16; PHPUnit/Sonar env gaps documented) |
|
||||
| 133 | Erli Cross-Surface Parity | 1/1 | Complete (2026-05-16; PHPUnit/Sonar env gaps documented) |
|
||||
|
||||
### Phase 127: Erli Integration Foundation
|
||||
|
||||
Focus: Dodac podstawowy typ integracji Erli: migracje konfiguracji, szyfrowanie sekretow, klient API, test polaczenia, karta w hubie integracji i routing/settings zgodne z wzorcami Allegro/shopPRO.
|
||||
Plans: 127-01 (complete)
|
||||
|
||||
### Phase 128: Erli Orders Import
|
||||
|
||||
Focus: Pobieranie nowych zamowien Erli przez cron i import reczny, mapper do wspolnego modelu orderPRO, state cursor, delta-only re-import, adresy/pozycje/platnosci/notatki oraz flaga faktury/NIP tam, gdzie API Erli daje dane firmowe.
|
||||
Plans: 128-01 (complete)
|
||||
|
||||
### Phase 129: Erli Status Mapping + Sync
|
||||
|
||||
Focus: Osobne mapowanie pull/push statusow Erli, auto-discovery nieznanych statusow, cron synchronizacji orderPRO -> Erli i ochrona lokalnych statusow przy re-imporcie analogicznie do Allegro/shopPRO.
|
||||
Plans: 129-01 (complete)
|
||||
|
||||
### Phase 130: Erli Shipments + Labels
|
||||
|
||||
Focus: Generowanie etykiet dla zamowien Erli, mapowanie metod dostawy Erli na dostepne providery, zapis paczek w `shipment_packages`, pobieranie labeli i integracja z kolejka zdalnego druku.
|
||||
Plans: 130-01 (complete)
|
||||
|
||||
### Phase 131: Erli Tracking + Automation Hooks
|
||||
|
||||
Focus: Tracking przesylek Erli, aktualizacja delivery statusow, zdarzenia automatyzacji (`order.imported`, `shipment.created`, `shipment.status_changed`) i zachowanie kompatybilnosci z szablonami e-mail/SMS oraz statystykami.
|
||||
Plans: 131-01 (complete)
|
||||
|
||||
### Phase 132: Erli Hardening, Observability + Docs
|
||||
|
||||
Focus: Testy jednostkowe mapperow/klientow, logi integracji i bledow API, retry/idempotencja, manual smoke checklist na zywej konfiguracji oraz aktualizacja `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md` i `DOCS/TECH_CHANGELOG.md`.
|
||||
Plans: 132-01 (complete)
|
||||
|
||||
### Phase 133: Erli Cross-Surface Parity
|
||||
|
||||
Focus: Domknac Erli jako pelnoprawny kanal w istniejacych wspolnych powierzchniach: filtr `Zrodlo` na liscie zamowien, kanaly sprzedazy w `/statistics/orders` i `/statistics/summary`, warunki integracji w automatyzacjach oraz aktywne menu integracji. Wprowadzic maly wspolny rejestr zrodel, zeby ograniczyc kolejne lokalne pominiecia Erli.
|
||||
Plans: 133-01 (complete)
|
||||
|
||||
## Earlier Recent Milestone (transition pending)
|
||||
|
||||
v3.7 Invoices (Fakturownia integration) — Complete in code, transition/follow-ups pending
|
||||
|
||||
Wystawianie faktur dla klientow z NIP poprzez integracje z Fakturownia (app.fakturownia.pl). Numeracja lokalna z opcja delegacji do Fakturowni, rozdzielenie przyciskow "Wystaw paragon" / "Wystaw fakture", osobne podstrony edycji konfiguracji paragonow i faktur.
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 113 | Fakturownia Integration Foundation | 1/1 | Complete (2026-05-10) |
|
||||
| 114 | Accounting Configs Refactor (hub + osobne podstrony receipts/invoices) | 1/1 | Complete (2026-05-10) |
|
||||
| 115 | Wystawianie faktury z zamowienia (lokalne + delegacja Fakturownia + NIP lookup MF Biala Lista) | 1/1 | Complete (2026-05-10) |
|
||||
| 116 | HostedSMS Integration Settings + Test SMS | 1/1 | Complete (2026-05-12) |
|
||||
| 117 | SMSPLANET Integration Settings + Test SMS | 1/1 | Complete (2026-05-12; migration/manual SMS verification pending) |
|
||||
| 118 | Fakturownia Single Instance | 1/1 | Complete (2026-05-12; migration/manual Fakturownia verification pending) |
|
||||
| 119 | Re-import total_paid Protection | 1/1 | Complete (2026-05-12; phpunit run + manual shoppro smoke pending env) |
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 127 | polkurier Integration Foundation (single-instance settings + Token API + realny test polaczenia; obok Apaczki) | 1/1 | Complete (2026-05-14; live API verified — `Autoryzacja: 1`) |
|
||||
| 128 | polkurier ShipmentService + TrackingService + UI prepare panel + delivery_status_mappings seed (live test na #114/#115) | 1/1 | Complete (2026-05-14; live test passed po 4 iteracjach; migracja + cron tracking weryfikacja pending) |
|
||||
| 129 | Order User Notes module (extend `order_notes` o user_id/author_name/note_type='user' + pelen CRUD restricted to author + badge `[N]` na liscie zamowien) | 1/1 | Complete (2026-05-14; migracja + manualny smoke pending operator) |
|
||||
| 130 | polkurier delivery status mappings UI (hardcoded POLKURIER_MAP/DESCRIPTIONS + dropdown w `/settings/delivery-statuses?tab=mapping` + badge counter) | 1/1 | Complete (2026-05-14; manualny smoke pending operator) |
|
||||
|
||||
Planowane kolejne fazy v3.7 (kandydaci, do rozplanowania):
|
||||
- polkurier TrackingService + `delivery_status_mappings` (provider='polkurier')
|
||||
- polkurier paczkomaty (`InpostParcelMachines` / `PocztexPostOffices` / `Kurier48PostOffices` z SDK polkuriera)
|
||||
- Eksport XLSX listy wystawionych faktur (analogicznie do paragonow)
|
||||
- Event automatyzacji `invoice.created` (jezeli operator chce wysylac faktury mailem)
|
||||
- Automatyzacje SMS / odbior odpowiedzi SMS po aktywacji HostedSMS
|
||||
- SMSPLANET conversation mode: wybor nadpis/numer 2WAY, odbior odpowiedzi, historia SMS w zamowieniu i notification center - Phase 121 planning
|
||||
- Manualne potwierdzenie SMSPLANET na zywej bazie i danych produkcyjnych
|
||||
- Backfill `curl_close()` w `ShopproIntegrationsRepository` (PHP 8.5 compat, poza zakresem 115)
|
||||
|
||||
## Next Milestone
|
||||
|
||||
Kandydaci w kolejce (po v3.8):
|
||||
- Mobile Orders List / Mobile Order Details / Mobile Settings
|
||||
- Zarzadzanie produktami
|
||||
- Zarzadzanie stanami magazynowymi
|
||||
- Historical receipt net backfill, only if operator later wants old `receipts.total_net` corrected
|
||||
- Phase 68 — Code Deduplication Refactor
|
||||
|
||||
## Completed Milestones
|
||||
|
||||
<details>
|
||||
<summary>v3.6 Re-import Data Protection - 2026-05-07 (1 phase, 1 plan)</summary>
|
||||
|
||||
Re-import istniejacego zamowienia (Allegro + shopPRO) jest delta-only: `replaceAddresses/Items/Notes` wywolywane wylacznie przy pierwszym imporcie, nowy `updateOrderDelta()` aktualizuje tylko pola realnie zmieniajace sie ze zrodla. Stabilne `order_items.id` chronia `project_generated` (Phase 97) i flow generowania PSD. Dodatkowo: propagacja anulowania ze zrodla (override niezalezny od statusOverwriteAllowed) i identical-payload no-op guard. Naprawa case #882 (znikajaca flaga "Projekt" po re-imporcie wymuszanym przez `payment.status_changed` z Phase 111).
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 112 | Re-import Data Protection | 1/1 | Complete |
|
||||
|
||||
Archive: `.paul/phases/112-reimport-data-protection/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v3.5 Payment Transition Event - 2026-05-05 (1 phase, 1 plan)</summary>
|
||||
|
||||
Naprawa luki w re-imporcie zamowien Allegro/shopPRO: po potwierdzeniu platnosci re-import emituje `payment.status_changed`, co przez chain reguly #7 zmienia status na `w_realizacji`. Eliminuje przypadki zamowien zaimportowanych przed potwierdzeniem platnosci utykajacych w `nieoplacone` (case #864).
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 111 | Payment Transition Event | 1/1 | Complete |
|
||||
|
||||
Archive: `.paul/phases/111-payment-transition-event/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v3.4 Statistics Summary - 2026-04-28 (1 phase, 1 plan)</summary>
|
||||
|
||||
Dodano pierwsza pozycje `Statystyki -> Podsumowanie` z miesiecznymi wykresami liczby i wartosci zamowien. Kazda integracja ma osobna serie, a dodatkowa seria `Razem` sumuje miesiac. Domyslny start historii to `04-2026`.
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 110 | Statistics Summary | 1/1 | Complete |
|
||||
|
||||
Archive: `.paul/phases/110-statistics-summary/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v3.3 UI Filters - 2026-04-28 (1 phase, 1 plan)</summary>
|
||||
|
||||
Usprawnienie wielokrotnego wyboru w filtrach: natywne selecty multiple na `/statistics/orders` zostaly zastapione kompaktowym dropdownem z checkboxami, bez zmiany kontraktu GET i backendu statystyk.
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 109 | Checkbox Multiselect Filters | 1/1 | Complete |
|
||||
|
||||
Archive: `.paul/phases/109-checkbox-multiselect-filters/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v3.2 Delivery Status Management - 2026-04-27 (1 phase, 2 plans)</summary>
|
||||
|
||||
Wyniesienie znormalizowanych statusow przesylek do tabeli DB z CRUD panelem oraz pelna integracja DB-driven w dropdownach automatyzacji.
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 108 | Delivery Status Management | 2/2 | Complete |
|
||||
|
||||
Archive: `.paul/phases/108-delivery-status-management/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v3.1 Operational Enhancements - 2026-04-27 (2 phases, 2 plans)</summary>
|
||||
|
||||
Usprawnienia operacyjne: alert o kliencie z historia zwrotow oraz idempotentna jednorazowa wysylka e-mail per zamowienie.
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 106 | Customer Return Alert | 1/1 | Complete |
|
||||
| 107 | Automation Email Send Once | 1/1 | Complete |
|
||||
|
||||
Archive: `.paul/phases/106-customer-return-alert/`, `.paul/phases/107-automation-email-send-once/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v3.0 Mobile Responsive - 2026-04-19 (52 phases shipped, 55 plans)</summary>
|
||||
|
||||
Wersja mobilna aplikacji plus pelny zestaw usprawnien operacyjnych: automation events, tracking push/pull, personalizacja pozycji, resilient print pipeline, project generation PSD oraz raport statystyk zamowien.
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 52 | Mobile Main Menu | 1/1 | Complete |
|
||||
| 53 | Mobile Status Panel Toggle | 1/1 | Complete |
|
||||
| 54 | Order Detail Image Hover | 1/1 | Complete |
|
||||
| 55 | Desktop Collapsed Sidebar Fix | 1/1 | Complete |
|
||||
| 56 | Order Payments | 1/1 | Complete |
|
||||
| 57 | Payment Automation Event | 1/1 | Complete |
|
||||
| 58 | Automation Form Preserve | 1/1 | Complete |
|
||||
| 59 | Order Status Automation Event | 1/1 | Complete |
|
||||
| 60 | Order Status Aged Event | 1/1 | Complete |
|
||||
| 61 | Payment Button Activation | 1/1 | Complete |
|
||||
| 62 | Import Re-import Safety | 1/1 | Complete |
|
||||
| 63 | Order Item Personalization | 1/1 | Complete |
|
||||
| 64 | Receipt Datetime Precision | 1/1 | Complete |
|
||||
| 65 | PAUL Delegated Apply | 1/1 | Complete |
|
||||
| 66 | Allegro Delivery Tracking | 2/2 | Complete |
|
||||
| 67 | PAUL Codex Executor | 1/1 | Complete |
|
||||
| 68 | Code Deduplication Refactor | 0/2 | Deferred |
|
||||
| 69 | Allegro Tracking English Statuses | 1/1 | Complete |
|
||||
| 70 | Receipt Shipping Cost | 1/1 | Complete |
|
||||
| 71 | Attributes Import | 1/1 | Complete |
|
||||
| 72 | Per Page Persistence | 1/1 | Complete |
|
||||
| 73 | Search by Product | 1/1 | Complete |
|
||||
| 74 | Reverse Status Mapping | 1/1 | Complete |
|
||||
| 75 | Pull Status Mapping | 1/1 | Complete |
|
||||
| 76 | Shipment Receiver Fallback | 1/1 | Complete |
|
||||
| 77 | COD Amount Fix | 1/1 | Complete |
|
||||
| 78 | Preset Auto Submit | 1/1 | Complete |
|
||||
| 79 | Personalization Message Field | 1/1 | Complete |
|
||||
| 80 | Status Change Reload | 1/1 | Complete |
|
||||
| 81 | Global Search Bar | 1/1 | Complete |
|
||||
| 82 | Product Title Tooltip | 1/1 | Complete |
|
||||
| 83 | Allegro Pull Status Mapping | 1/1 | Complete |
|
||||
| 84 | Order Imported Automation Event | 1/1 | Complete |
|
||||
| 85 | Status Group Filter | 1/1 | Complete |
|
||||
| 86 | Apaczka COD Bank Account | 1/1 | Complete |
|
||||
| 87 | Shipment Delete | 1/1 | Complete |
|
||||
| 88 | Allegro User-Agent | 1/1 | Complete |
|
||||
| 89 | Allegro Info Page | 1/1 | Complete |
|
||||
| 90 | Delivery Price Import Fix | 1/1 | Complete |
|
||||
| 91 | Print Client Timeout Resilience | 1/1 | Complete |
|
||||
| 92 | Buyer Name Copy | 1/1 | Complete |
|
||||
| 93 | Remember Me Login | 1/1 | Complete |
|
||||
| 94 | Order Preview Popup | 1/1 | Complete |
|
||||
| 95 | AJAX Table Refresh | 1/1 | Complete |
|
||||
| 96 | Automation Payment Method Condition | 1/1 | Complete |
|
||||
| 97 | Project Generation | 1/1 | Complete |
|
||||
| 98 | Order Imported First Only | 1/1 | Complete |
|
||||
| 99 | Order Delivery & Payment Edit | 0/1 | Cancelled |
|
||||
| 100 | Preset Scope & Print UX | 1/1 | Complete |
|
||||
| 101 | Aged Orders Row Highlight | 1/1 | Complete |
|
||||
| 102 | Apaczka Receiver Street Length | 1/1 | Complete |
|
||||
| 103 | Print Autoclick Fix | 1/1 | Complete |
|
||||
| 104 | Apaczka Weekend Delivery | 1/1 | Complete |
|
||||
| 105 | Orders Statistics | 1/1 | Complete |
|
||||
|
||||
Archive: `.paul/milestones/v3.0-ROADMAP.md`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v2.3 Email HTML Layout - 2026-03-28 (1 phase, 1 plan)</summary>
|
||||
|
||||
HTML header/footer per skrzynka pocztowa z dual-mode edytorem (Quill WYSIWYG + HTML source) i kompozycja email header+body+footer.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 51 | Email HTML Layout | 1/1 | 2026-03-28 |
|
||||
|
||||
Archive: `.paul/phases/51-email-html-layout/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v2.2 Allegro Shipment Waybill Push - 2026-03-28 (1 phase, 1 plan)</summary>
|
||||
|
||||
Automatyczne przekazywanie waybilla do Allegro checkout forms przy tworzeniu przesylki, ograniczone do zamowien `source=allegro` i odporne na bledy API Allegro.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 50 | Allegro Shipment Waybill Push | 1/1 | 2026-03-28 |
|
||||
|
||||
Archive: `.paul/phases/50-allegro-shipment-waybill-push/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v2.1 Automation History & Observability - 2026-03-28 (1 phase, 1 plan)</summary>
|
||||
|
||||
Rozdzielenie Ustawienia > Zadania automatyczne na taby Ustawienia i Historia, wdrozenie audytu wykonan regul (filtry + paginacja), retencja 30 dni oraz akcja update_order_status.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 49 | Automation History Tab | 1/1 | 2026-03-28 |
|
||||
|
||||
Archive: .paul/phases/49-automation-history-tab/
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>v2.0 Email Template Shipment Variables - 2026-03-28 (1 phase, 1 plan)</summary>
|
||||
|
||||
Rozszerzenie szablonow e-mail o zmienne przesylki (`przesylka.numer`, `przesylka.link_sledzenia`) oraz provider-aware budowanie linku sledzenia.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 48 | Email Template Shipment Variables | 1/1 | 2026-03-28 |
|
||||
|
||||
Archive: `.paul/phases/48-email-template-shipment-variables/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.9 Shipment Automation Immediate Trigger - 2026-03-28 (1 phase, 1 plan)</summary>
|
||||
|
||||
Wdrozenie natychmiastowego eventu automatyzacji po utworzeniu przesylki oraz nowej akcji automatyzacji do zmiany statusu przesylki.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 47 | Shipment Creation Automation | 1/1 | 2026-03-28 |
|
||||
|
||||
Archive: `.paul/phases/47-shipment-created-automation/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.8 Allegro Status Push - 2026-03-28 (1 phase, 1 plan)</summary>
|
||||
|
||||
Wdrozenie synchronizacji statusow zamowien w kierunku orderPRO -> Allegro oraz aktywacja opcji kierunku w ustawieniach integracji Allegro.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 46 | Allegro Status Push | 1/1 | 2026-03-28 |
|
||||
|
||||
Archive: `.paul/phases/46-allegro-status-push/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.7 ShopPRO Status Push - 2026-03-27 (1 phase, 1 plan)</summary>
|
||||
|
||||
Implementacja synchronizacji statusow zamowien w kierunku orderPRO -> shopPRO. Cron pushuje zmiany statusow do shopPRO API (PUT /api.php?endpoint=orders&action=change_status).
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 45 | ShopPRO Status Push | 1/1 | 2026-03-27 |
|
||||
|
||||
Archive: `.paul/phases/45-shoppro-status-push/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.6 Quick Status Change - 2026-03-27 (1 phase, 1 plan)</summary>
|
||||
|
||||
Szybka zmiana statusu zamówienia bezpośrednio z listy zamówień — klikalny dropdown w kolumnie statusu, zmiana przez AJAX bez przeładowania strony.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 44 | Inline Status Change | 1/1 | 2026-03-27 |
|
||||
|
||||
Archive: `.paul/phases/44-inline-status-change/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.5 Operational Workflow Cleanup - 2026-03-25 (4 phases, 4 plans)</summary>
|
||||
|
||||
Usprawnienia operacyjne: usunięcie bulk print, ograniczenie szumu logów importu Allegro, automatyzacja shipment.status_changed, usuwanie wpisów z kolejki druku.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 40 | Remove Order List Bulk Print | 1/1 | 2026-03-25 |
|
||||
| 41 | Allegro Import Log Rationalization | 1/1 | 2026-03-25 |
|
||||
| 42 | Automation Shipment Status Event | 1/1 | 2026-03-25 |
|
||||
| 43 | Print Queue Entry Removal | 1/1 | 2026-03-25 |
|
||||
|
||||
Archive: `.paul/phases/40-*`, `.paul/phases/41-*`, `.paul/phases/42-*`, `.paul/phases/43-*`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.4 UI Readability Tweaks - 2026-03-25 (1 phase, 1 plan)</summary>
|
||||
|
||||
Rozdzielenie semantyki kolorow UI: glowny kolor przyciskow akcji zostal oddzielony od koloru naglowkow sekcji, aby poprawic czytelnosc i szybkosc skanowania interfejsu.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 30 | Button Primary Color Distinction | 1/1 | 2026-03-25 |
|
||||
|
||||
Archive: `.paul/phases/30-button-primary-color/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.3 Konfiguracja śledzenia przesyłek — 2026-03-23 (1 phase, 1 plan)</summary>
|
||||
|
||||
Konfiguracja mapowania statusów dostawy z API przewoźników na znormalizowane statusy widoczne w aplikacji. Użytkownik może dostosować tłumaczenia i przypisania statusów bez zmian w kodzie.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 29 | Delivery Status Mapping UI | 1/1 | 2026-03-23 |
|
||||
|
||||
Archive: `.paul/phases/29-delivery-status-mapping-ui/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.2 Śledzenie przesyłek — 2026-03-23 (2 phases, 2 plans)</summary>
|
||||
|
||||
Automatyczne śledzenie statusu dostawy przesyłek przez API przewoźników (InPost ShipX, Apaczka, Allegro WZA). Cykliczne odpytywanie przez cron z konfigurowalnym interwałem. Dwupoziomowy system statusów: znormalizowany + surowy z API. Badge'e w UI, linki śledzenia, ustawienia interwału.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 27 | Shipment Tracking Backend | 1/1 | 2026-03-23 |
|
||||
| 28 | Shipment Tracking UI + Settings | 1/1 | 2026-03-23 |
|
||||
|
||||
Archive: `.paul/phases/27-shipment-tracking-backend/`, `.paul/phases/28-shipment-tracking-ui/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.1 Ręczny numer przesyłki — 2026-03-23 (1 phase, 1 plan)</summary>
|
||||
|
||||
Możliwość ręcznego dodania numeru śledzenia przesyłki do zamówienia (bez tworzenia przesyłki przez API przewoźnika).
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 26 | Manual Tracking Number | 1/1 | 2026-03-23 |
|
||||
|
||||
Archive: `.paul/phases/26-manual-tracking-number/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.0 Presety przesyłek — 2026-03-22 (3 phases, 3 plans)</summary>
|
||||
|
||||
Customowe przyciski szybkiego wypełniania formularza przygotowania przesyłki. Presety globalne z nazwą i kolorem — tworzenie, autofill, edycja, usuwanie.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 23 | Shipment Presets Backend | 1/1 | 2026-03-22 |
|
||||
| 24 | Shipment Presets UI | 1/1 | 2026-03-22 |
|
||||
| 25 | Shipment Presets Management | 1/1 | 2026-03-22 |
|
||||
|
||||
Archive: `.paul/phases/23-shipment-presets-backend/`, `.paul/phases/24-shipment-presets-ui/`, `.paul/phases/25-shipment-presets-management/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.9 Poprawki ustawień firmy — 2026-03-22 (1 phase, 1 plan)</summary>
|
||||
|
||||
Naprawa buga: pola REGON, BDO, KRS i logo nie zapisywały się w ustawieniach firmy (kontroler nie przekazywał ich do repozytorium).
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 22 | REGON Save Fix | 1/1 | 2026-03-22 |
|
||||
|
||||
Archive: `.paul/phases/22-regon-save-fix/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.8 Poprawki wyświetlania źródła zamówień — 2026-03-22 (1 phase, 1 plan)</summary>
|
||||
|
||||
Na liście zamówień i stronie szczegółów: wyświetlanie nazwy konkretnej integracji (z tabeli `integrations`) zamiast generycznego "shopPRO". Korekta kolejności source/ID.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 21 | Order Source Display | 1/1 | 2026-03-22 |
|
||||
|
||||
Archive: `.paul/phases/21-order-source-display/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.7 Zdalne drukowanie etykiet — 2026-03-22 (3 phases, 3 plans)</summary>
|
||||
|
||||
System zdalnego drukowania etykiet przesyłek na drukarce termicznej. Aplikacja Windows w system tray odpytuje API orderPRO, pobiera zlecenia i drukuje etykiety A6.
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 18 | Print Queue Backend | 1/1 | 2026-03-22 |
|
||||
| 19 | UI Integration | 1/1 | 2026-03-22 |
|
||||
| 20 | Windows Client (C# WinForms) | 1/1 | 2026-03-22 |
|
||||
|
||||
Archive: `.paul/phases/18-print-queue-backend/`, `.paul/phases/19-ui-integration/`, `.paul/phases/20-windows-client/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.6 Poprawki UX — 2026-03-22 (1 phase, 1 plan)</summary>
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 17 | Receipt duplicate guard | 1/1 | 2026-03-22 |
|
||||
|
||||
Archive: `.paul/phases/17-receipt-duplicate-guard/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.5 Moduł Automatyzacji — 2026-03-18 (1 phase, 2 plans)</summary>
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 16 | Zadania automatyczne | 2/2 | 2026-03-18 |
|
||||
|
||||
Archive: `.paul/phases/16-automated-tasks/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.4 Moduł E-mail — 2026-03-17 (3 phases, 4 plans)</summary>
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 13 | DB + Skrzynki pocztowe | 1/1 | 2026-03-17 |
|
||||
| 14 | Szablony wiadomości | 2/2 | 2026-03-17 |
|
||||
| 15 | Wysyłka e-mail z zamówień | 1/1 | 2026-03-17 |
|
||||
|
||||
Archive: `.paul/milestones/v0.4-ROADMAP.md`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.3 Moduł Paragonów — 2026-03-15 (5 phases, 5 plans)</summary>
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 8 | DB Foundation + Company Settings | 1/1 | 2026-03-15 |
|
||||
| 9 | Konfiguracja paragonĂłw (Ustawienia) | 1/1 | 2026-03-15 |
|
||||
| 10 | Wystawianie paragonĂłw z zamĂłwienia | 1/1 | 2026-03-15 |
|
||||
| 11 | PodglÄ…d i wydruk paragonu (HTML+PDF) | 1/1 | 2026-03-15 |
|
||||
| 12 | Sekcja Księgowość — lista + eksport XLSX | 1/1 | 2026-03-15 |
|
||||
|
||||
Archive: `.paul/milestones/v0.3-ROADMAP.md`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.2 Pre-Expansion Fixes — 2026-03-15 (1 phase, 5 plans)</summary>
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 7 | Pre-Expansion Fixes | 5/5 | 2026-03-15 |
|
||||
|
||||
Plans:
|
||||
- 07-01: Performance (N+1 subqueries, DB indexes, information_schema cache)
|
||||
- 07-02: Stability (SSL verification, cron throttle DB, migration 000014b)
|
||||
- 07-03: UX (orderpro_to_allegro disable, lista zamówień fixes)
|
||||
- 07-04: Tests (AllegroTokenManager + AllegroOrderImportService — 12 testów)
|
||||
- 07-05: InPost ShipmentProviderInterface (natywne ShipX API)
|
||||
|
||||
Archive: `.paul/phases/07-pre-expansion-fixes/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.1 Initial Release — 2026-03-13 (6 phases, 15 plans)</summary>
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 1 | Tech Debt | 2/2 | 2026-03-12 |
|
||||
| 2 | Bug Fixes | 4/4 | 2026-03-13 |
|
||||
| 3 | Tech Debt 2 | 1/1 | 2026-03-13 |
|
||||
| 4 | Schema Docs | 1/1 | 2026-03-13 |
|
||||
| 5 | Tech Debt 3 | 1/1 | 2026-03-13 |
|
||||
| 6 | SonarQube Quality | 6/6 | 2026-03-13 |
|
||||
|
||||
Archive: `.paul/milestones/v0.1-ROADMAP.md`
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-03-12*
|
||||
*Last updated: 2026-05-18 - Phase 145 complete; v3.14 Polkurier COD Return Time Hotfix complete*
|
||||
@@ -4,28 +4,28 @@
|
||||
|
||||
| Typ pracy | Skill/Komenda | Priorytet | Kiedy |
|
||||
|-----------|---------------|-----------|-------|
|
||||
| Nowe funkcjonalności (integracje marketplace, przewoźnicy, moduły) | /feature-dev | optional | Przed implementacją każdej nowej funkcji lub integracji |
|
||||
| Przegląd kodu przed zamknięciem planu (bezpieczeństwo, jakość, SQL) | /code-review | optional | Po implementacji, przed UNIFY |
|
||||
| Skanowanie jakości kodu po każdym zakończonym planie | `sonar-scanner` (CLI w katalogu projektu) | required | Po APPLY, przed UNIFY — wyniki na https://sonar.project-pro.pl/dashboard?id=orderPRO |
|
||||
| Nowe funkcjonalnosci (integracje marketplace, przewoznicy, moduly) | /feature-dev | optional | Przed implementacja kazdej nowej funkcji lub integracji |
|
||||
| Przeglad kodu przed zamknieciem planu (bezpieczenstwo, jakosc, SQL) | /code-review | optional | Po implementacji, przed UNIFY |
|
||||
| Komponenty UI (listy zamowien, dashboard, formularze, modale) | /frontend-design | optional | Przy tworzeniu nowych widokow lub redesignie istniejacych |
|
||||
| Refaktoryzacja i upraszczanie kodu po implementacji | /simplify | optional | Po zakonczeniu APPLY, gdy kod wymaga porzadkowania |
|
||||
| Skanowanie jakosci SonarQube | `sonar-scanner` / `$paul-quality-gate` | manual | Tylko na wyrazne zadanie operatora |
|
||||
|
||||
## SonarQube — procedura po skanowaniu
|
||||
## SonarQube - tryb reczny
|
||||
|
||||
Po każdym uruchomieniu `sonar-scanner`:
|
||||
1. Odpytaj nowe issues przez MCP (`mcp__sonarqube__issues`, project_key: `orderPRO`, tylko `issueStatuses: OPEN`)
|
||||
2. Porównaj z tym co już jest w `DOCS/todo.md` — dopisz tylko **nowe** issues (których jeszcze nie ma na liście)
|
||||
3. Format wpisu w `DOCS/todo.md`: `[] [Sonar {data}] {rule} — {opis problemu} ({liczba wystąpień}x)`
|
||||
4. Grupuj pod nagłówkiem `## SonarQube — {data skanu}`
|
||||
| Komponenty UI (listy zamówień, dashboard, formularze, modale) | /frontend-design | optional | Przy tworzeniu nowych widoków lub redesignie istniejących |
|
||||
| Refaktoryzacja i upraszczanie kodu po implementacji | /simplify | optional | Po zakończeniu APPLY, gdy kod wymaga porządkowania |
|
||||
SonarQube nie jest czescia automatycznego workflow PAUL dla orderPRO.
|
||||
|
||||
- Nie uruchamiaj `sonar-scanner` automatycznie po APPLY ani przed UNIFY.
|
||||
- Nie oznaczaj braku `sonar-scanner` jako gap w SUMMARY/STATE.
|
||||
- Uruchamiaj SonarQube tylko po wyraznym poleceniu operatora, np. `uruchom Sonar`, `$paul-quality-gate` albo recznie wskazane `sonar-scanner`.
|
||||
- Jezeli Sonar zostanie uruchomiony recznie, wyniki mozna dopisac do `DOCS/todo.md` albo `.paul/codebase/quality_risks.md` zgodnie z celem danego skanu.
|
||||
|
||||
## Phase Overrides
|
||||
|
||||
Brak — domyślne skille wystarczają dla wszystkich faz.
|
||||
Brak - domyslne skille wystarczaja dla wszystkich nowych planow.
|
||||
|
||||
## Templates & Assets
|
||||
|
||||
Brak — cały projekt dostępny bezpośrednio w środowisku.
|
||||
Brak - caly projekt dostepny bezposrednio w srodowisku.
|
||||
|
||||
---
|
||||
*SPECIAL-FLOWS.md — Created: 2026-03-12*
|
||||
*Updated: 2026-03-12*
|
||||
*SPECIAL-FLOWS.md - Updated: 2026-05-18*
|
||||
|
||||
@@ -5,15 +5,17 @@
|
||||
See: .paul/PROJECT.md (updated 2026-05-18)
|
||||
|
||||
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
|
||||
**Current focus:** v3.14 Polkurier COD Return Time Hotfix complete; Phase 145 unified.
|
||||
**Current focus:** Polish UI Copy plan unified; ready for next PLAN.
|
||||
|
||||
## Current Position
|
||||
|
||||
Milestone: v3.14 Polkurier COD Return Time Hotfix
|
||||
Phase: 145 of 145 (Polkurier COD Return Time Hotfix) - Complete
|
||||
Plan: 145-01 complete
|
||||
Status: Milestone complete, ready for next milestone or release decision
|
||||
Last activity: 2026-05-18 13:28 - Unified .paul/phases/145-polkurier-cod-return-time-hotfix/145-01-PLAN.md
|
||||
Current plan: 20260518-2305-polskie-tlumaczenia - Poprawne polskie tlumaczenia UI - complete
|
||||
Status: Ready for next PLAN
|
||||
Last activity: 2026-05-18 23:40 - Unified .paul/plans/20260518-2305-polskie-tlumaczenia/PLAN.md
|
||||
|
||||
Previous milestone: v3.14 Polkurier COD Return Time Hotfix
|
||||
Previous phase: 145 of 145 (Polkurier COD Return Time Hotfix) - Complete
|
||||
Previous plan: 145-01 complete
|
||||
|
||||
Progress:
|
||||
- Milestone v3.14: [##########] 100% (1 of 1 phases complete)
|
||||
@@ -23,16 +25,16 @@ Progress:
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN -> APPLY -> UNIFY
|
||||
done done done [Loop complete - milestone complete]
|
||||
PLAN --> APPLY --> UNIFY
|
||||
x x x [Loop complete - ready for next PLAN]
|
||||
```
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-05-18 13:28
|
||||
Stopped at: Phase 145 complete; v3.14 milestone complete
|
||||
Next action: Run $paul-complete-milestone or start next milestone planning
|
||||
Resume file: .paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md
|
||||
Last session: 2026-05-18 23:40
|
||||
Stopped at: Plan 20260518-2305-polskie-tlumaczenia UNIFY complete
|
||||
Next action: `$paul-plan [next work]` or `$paul-verify .paul/plans/20260518-2305-polskie-tlumaczenia/SUMMARY.md`
|
||||
Resume file: .paul/plans/20260518-2305-polskie-tlumaczenia/SUMMARY.md
|
||||
|
||||
## Pending parallel work
|
||||
- None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1).
|
||||
@@ -87,6 +89,13 @@ Branch: main
|
||||
|----------|---------|-------|
|
||||
| `sonar-scanner` | gap documented | Attempted after APPLY with `sonar-scanner --version`; CLI is not available in PATH. |
|
||||
|
||||
### Skill Audit (20260518-2305 Polish UI Copy)
|
||||
|
||||
| Expected | Invoked | Notes |
|
||||
|----------|---------|-------|
|
||||
| `sonar-scanner` | gap documented | Attempted after APPLY with `sonar-scanner --version`; CLI is not available in PATH. |
|
||||
| `jscpd` | invoked | `npx --yes jscpd` succeeded for the targeted UI-copy scope and wrote `.paul/codebase/radar/jscpd-i18n-post-apply/jscpd-report.json`. |
|
||||
|
||||
### Skill Audit (Phase 129)
|
||||
|
||||
| Expected | Invoked | Notes |
|
||||
@@ -166,6 +175,7 @@ Branch: main
|
||||
|
||||
### Blockers / Concerns
|
||||
|
||||
- Polish UI Copy APPLY: `sonar-scanner` is unavailable in PATH and manual browser smoke was not run in this session. PHP lint, translation array load check, residual placeholder/mojibake scans, targeted `jscpd`, and `git diff --check` passed.
|
||||
- Phase 145 APPLY: `vendor/bin/phpunit` is missing, so `tests/Unit/PolkurierShipmentServiceTest.php` was linted and covered by an ad-hoc runtime smoke instead of PHPUnit; `sonar-scanner` is unavailable in PATH.
|
||||
- Phase 144 APPLY: `vendor/bin/phpunit` is missing, so `tests/Unit/OrdersRepositoryNotesCountTest.php` was linted and covered by an ad-hoc SQLite runtime smoke instead of PHPUnit; `sonar-scanner` is unavailable in PATH.
|
||||
- Phase 134: `sonar-scanner` is still unavailable in PATH.
|
||||
@@ -185,22 +195,18 @@ Branch: main
|
||||
|
||||
## Pending Actions
|
||||
|
||||
- Polish UI Copy follow-up: manual smoke main pages with changed copy (`/orders/list`, order detail, settings integrations/templates, accounting, shipments prepare). Sonar is now manual on-demand only.
|
||||
- Phase 145 follow-up: after restoring `vendor/`, run `vendor/bin/phpunit tests/Unit/PolkurierShipmentServiceTest.php`.
|
||||
- Phase 145 follow-up: run SonarQube scan after restoring `sonar-scanner` in PATH.
|
||||
- Phase 145 follow-up: with explicit operator intent, create one live Polkurier COD shipment and confirm API accepts `codtype='S'` / `return_cod='BA'` without the previous return-time error.
|
||||
- Phase 138 follow-up: run `vendor/bin/phpunit tests/Unit/SmtpSecurityContextFactoryTest.php tests/Unit/TemplateVariableCatalogTest.php` after dependencies are installed.
|
||||
- Phase 139 follow-up: split `OrdersStatisticsRepository` (`php:S1448`, 43 methods) in a future god-class refactor if still relevant.
|
||||
- Phase 139 follow-up: continue with confirmed groups `php:S1142`, `php:S3776`, `php:S1172`, `php:S1192`, `php:S112`, plus Web table/accessibility issues. `php:S4833` is now only 3 core framework require issues.
|
||||
- Phase 140 follow-up: manual smoke `/settings/integrations/shoppro?tab=delivery` -> wybierz Polkurier -> zapisz -> odswiez -> mapowanie pozostaje; potem przygotuj przesylke shopPRO i potwierdz preselect `provider='polkurier'`.
|
||||
- Phase 140 follow-up: uruchom SonarQube scan po przywroceniu `sonar-scanner` w PATH albo ponownym pobraniu oficjalnego scanner fallback.
|
||||
- Phase 141 follow-up: manual smoke `/settings/integrations` -> potwierdz sekcje marketplace/kurierzy/pozostale, osobne wiersze Allegro Sandbox/Production i poprawne linki Ustawienia.
|
||||
- Phase 141 follow-up: uruchom SonarQube scan po przywroceniu `sonar-scanner` w PATH albo ponownym pobraniu oficjalnego scanner fallback.
|
||||
- Phase 142 follow-up: manual smoke `/orders/1164/shipment/prepare` -> potwierdz, ze mapowanie shopPRO -> Polkurier preselectuje przewoznika i usluge.
|
||||
- Phase 142 follow-up: `composer install` / przywroc `vendor/`, potem uruchom `vendor/bin/phpunit tests/Unit/ShipmentPreparePolkurierMappingTest.php`.
|
||||
- Phase 142 follow-up: uruchom SonarQube scan po przywroceniu `sonar-scanner` w PATH albo ponownym pobraniu oficjalnego scanner fallback.
|
||||
- Phase 143 follow-up: manual smoke `/orders/list` -> potwierdz brak boksu "Zamowienia / Kompaktowa lista..." oraz status panel/table layout.
|
||||
- Phase 143 follow-up: manual smoke desktop sidebar -> ustaw `localStorage.sidebarCollapsed='1'`, odswiez strone i potwierdz brak widocznego expanded-to-collapsed flash.
|
||||
- Phase 143 follow-up: uruchom SonarQube scan po przywroceniu `sonar-scanner` w PATH albo ponownym pobraniu oficjalnego scanner fallback.
|
||||
- Phase 138 manual smoke: test a real SMTP SSL/STARTTLS mailbox in strict mode; test invalid and valid e-mail/SMS template saves in UI.
|
||||
- Manualne testy AC-1..AC-7 dla Phase 112 na zywej bazie (XAMPP online).
|
||||
- Backfill zamowienia #882 - operator robi recznie po wdrozeniu (poza zakresem planu).
|
||||
@@ -260,4 +266,11 @@ Branch: main
|
||||
|
||||
## Skill Requirements
|
||||
|
||||
- `sonar-scanner` required after APPLY; Phase 139 used the official Windows x64 scanner from `%TEMP%` because the CLI is still not available in PATH. Earlier Phase 116, 117, 121, 122, 128, 129, 130, 131, 132, 133, 134, 135, 136 and 138 gaps remain historical.
|
||||
- SonarQube / `sonar-scanner` is no longer part of the automatic PAUL PLAN/APPLY/UNIFY workflow for orderPRO. Use it only on explicit operator request via `$paul-quality-gate`, `uruchom Sonar`, or a manually provided `sonar-scanner` command.
|
||||
- Historical Sonar gaps in older phase summaries remain archival context and should not be carried into new plans as required checks.
|
||||
|
||||
### Codebase Mapped
|
||||
|
||||
Date: 2026-05-18
|
||||
Documents: `.paul/codebase/`
|
||||
Quality Radar: partial (`jscpd` ok through `npx`; `codebase-memory-mcp` installed globally at 0.6.1, enabled in Codex MCP config, and initial index completed in a fresh Codex process: 8165 nodes / 13610 edges; `ast-grep` installed globally and working after Windows wrapper repair)
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
|
||||
## Co zrobiono
|
||||
|
||||
- [Policy] Dodano opcje autokompresji `.paul/STATE.md` dla PAUL w Codex i Claude Code.
|
||||
- Nowa preferencja `.paul/config.md`: `state_autocompress: true` oraz `state_autocompress_max_lines: 500`.
|
||||
- Workflowy PAUL po aktualizacji `STATE.md` maja teraz sprawdzac liczbe linii i kompresowac tylko historyczne/narastajace sekcje, zachowujac biezaca pozycje, `Next action`, `Resume file`, aktywne blokery i aktywne pending actions.
|
||||
- [Policy] Oznaczono `.paul/ROADMAP.md` jako opcjonalny legacy/release context; `map-codebase` w Codex i Claude Code nie ma go tworzyc ani odswiezac.
|
||||
- [Plan 20260518-2305-polskie-tlumaczenia] Znormalizowano polskie teksty UI do UTF-8 z polskimi znakami.
|
||||
- Poprawiono centralny słownik `resources/lang/pl.php`, widoczne hardcoded teksty w widokach, modułach JS i wybranych komunikatach backendu.
|
||||
- Zachowano techniczne kontrakty: placeholdery `{{zamowienie.*}}` / `{{przesylka.*}}`, routy, form field names, CSS/JS selectors, provider/status codes i API payload keys.
|
||||
- Quality Radar: `codebase-memory-mcp` i `npx jscpd` działały; globalne `jscpd`, `ast-grep`/`sg` oraz `sonar-scanner` są niedostępne w PATH. jscpd targeted: 226 plików, 397 klonów, 4754 zduplikowane linie.
|
||||
- Gap: manualny smoke UI Polish UI Copy pozostaje do wykonania po uruchomieniu app/browser session.
|
||||
- [Policy] Usunieto SonarQube z automatycznego workflow PAUL dla orderPRO. `sonar-scanner` / `$paul-quality-gate` sa teraz reczne, tylko na wyrazne zadanie operatora; brak Sonara nie ma byc raportowany jako gap w nowych PLAN/APPLY/UNIFY.
|
||||
- [Phase 140, Plan 01] Dodano mapowanie form dostawy shopPRO na uslugi Polkurier.
|
||||
- Rozszerzono zakladke `Dostawy` integracji shopPRO o przewoznika Polkurier i wyszukiwalna liste uslug.
|
||||
- Zapis mapowan shopPRO obsluguje `provider='polkurier'` oraz zapis service code i nazwy uslugi w `carrier_delivery_method_mappings`.
|
||||
@@ -34,7 +44,15 @@
|
||||
- `.paul/PROJECT.md`
|
||||
- `.paul/ROADMAP.md`
|
||||
- `.paul/STATE.md`
|
||||
- `.paul/config.md`
|
||||
- `.paul/changelog/2026-05-18.md`
|
||||
- `.paul/plans/20260518-2305-polskie-tlumaczenia/PLAN.md`
|
||||
- `.paul/plans/20260518-2305-polskie-tlumaczenia/SUMMARY.md`
|
||||
- `.paul/plans/20260518-2305-polskie-tlumaczenia/translation-audit.txt`
|
||||
- `.paul/plans/20260518-2305-polskie-tlumaczenia/hardcoded-ui-files.txt`
|
||||
- `.paul/plans/20260518-2305-polskie-tlumaczenia/residual-ui-scan.txt`
|
||||
- `.paul/codebase/radar/jscpd-i18n-post-apply/jscpd-report.json`
|
||||
- `.paul/codebase/radar/codebase-memory-post-apply-polish-ui-copy.txt`
|
||||
- `.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-PLAN.md`
|
||||
- `.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-SUMMARY.md`
|
||||
- `DOCS/ARCHITECTURE.md`
|
||||
|
||||
14
.paul/changelog/2026-05-19.md
Normal file
14
.paul/changelog/2026-05-19.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# 2026-05-19
|
||||
|
||||
## Co zrobiono
|
||||
|
||||
- [Policy] Dodano cleanup handoffow po `$paul-unify` w PAUL dla Codex i Claude Code.
|
||||
- Po udanym UNIFY usuwane sa tylko aktywne pliki `.paul/handoffs/*-handoff.md` / `*-pause.md` zwiazane z wlasnie zamknietym planem albo wskazane w pre-UNIFY `Resume file`.
|
||||
- `.paul/handoffs/archive/` pozostaje nietkniety.
|
||||
- [Policy] Przestawiono PAUL Quality Radar na lekki tryb automatyczny: `codebase-memory-mcp` zostaje wlaczony, a `jscpd` i `ast-grep` sa manual/on-demand.
|
||||
- Zaktualizowano konfiguracje orderPRO oraz frameworki PAUL dla Codex i Claude Code, aby `jscpd`/`ast-grep` nie byly sprawdzane, instalowane ani uruchamiane automatycznie.
|
||||
- [Policy] Odchudzono generowany zestaw `.paul/codebase/*.md`: usunieto `index.md`, `structure.md`, `concerns.md`, `domain_duplicates.md` i `tech_changelog.md`.
|
||||
- `todo.md` pozostaje recznym plikiem operatora i nie bedzie nadpisywany przez `$paul-init` ani `$paul-map-codebase`.
|
||||
- Zaktualizowano workflowy PAUL dla Codex i Claude Code, zeby nowe mapowanie tworzylo 9 generowanych dokumentow oraz przenosilo ryzyka duplikacji/source-of-truth do `quality_risks.md`.
|
||||
- [Policy] Dodano globalna zasade, ze tresc dokumentow Markdown generowanych przez PAUL ma byc pisana po polsku; sciezki, komendy, klucze konfiguracji i identyfikatory kodu pozostaja bez tlumaczenia.
|
||||
- [Maintenance] Przejrzano komendy PAUL dla Codex i Claude Code: dodano lokalny `language_policy` do wszystkich komend, spolszczono `$paul-help` i `$paul-config`, usunieto artefakty starego `CONCERNS.md` / `13 documents`, oraz potwierdzono zgodnosc wrapperow Claude z frameworkiem.
|
||||
@@ -1,616 +1,59 @@
|
||||
# Architecture
|
||||
|
||||
Last refresh: 2026-05-18.
|
||||
|
||||
orderPRO is a PHP 8.4 custom MVC-style application with manual routing and manual dependency wiring.
|
||||
|
||||
## Entry Points
|
||||
|
||||
- HTTP: `public/index.php`.
|
||||
- Root forwarder: `index.php`.
|
||||
- Bootstrap: `bootstrap/app.php`.
|
||||
- CLI migrations: `bin/migrate.php`.
|
||||
- CLI cron: `bin/cron.php`.
|
||||
|
||||
## Request Flow
|
||||
|
||||
```
|
||||
HTTP Request
|
||||
→ public/index.php
|
||||
→ bootstrap/app.php (loads config, registers PDO, services)
|
||||
→ Application::boot() (loads routes/web.php)
|
||||
→ Router::dispatch(Request) (matches URL, runs middleware pipeline)
|
||||
→ [Middleware] (AuthMiddleware, ApiKeyMiddleware)
|
||||
→ Controller::method() (parse input → call repository/service → render)
|
||||
→ Template::render() (PHP native, layout composition)
|
||||
→ Response::send()
|
||||
```
|
||||
|
||||
## Layer Map
|
||||
|
||||
| Layer | Location | Responsibility |
|
||||
|-------|----------|----------------|
|
||||
| Entry | `public/index.php` | Bootstrap only |
|
||||
| Routes | `routes/web.php` (581 lines) | All ~80 routes; manual DI wiring |
|
||||
| Core | `src/Core/` (25 files) | Framework infrastructure |
|
||||
| Controllers | `src/Modules/*/Controller.php` | Request parsing → response |
|
||||
| Services | `src/Modules/*/Service.php` | Business logic |
|
||||
| Repositories | `src/Modules/*/Repository.php` | PDO data access (34+ repos) |
|
||||
| Views | `resources/views/` | PHP templates with `$e()` / `$t()` |
|
||||
| Components | `resources/views/components/` | Reusable UI blocks |
|
||||
| Frontend modules | `public/assets/js/modules/` | Small vanilla JS enhancements loaded by layout |
|
||||
|
||||
## Module Inventory (`src/Modules/`)
|
||||
|
||||
| Module | Files | Key Classes | Purpose |
|
||||
|--------|-------|-------------|---------|
|
||||
| **Auth** | 3 | `AuthController`, `AuthMiddleware`, `AuthService` | Login/logout, session |
|
||||
| **Users** | 2 | `UserController`, `UserRepository` | User CRUD |
|
||||
| **Orders** | 3 | `OrdersController` (1187 LOC), `OrdersRepository` (1221 LOC) | Order list, detail, status, payment, correlated subquery for return-risk |
|
||||
| **Shipments** | 17 | `ShipmentController`, provider services + tracking services | Shipment creation, label download, tracking polling |
|
||||
| **Accounting** | 5 | `AccountingController`, `ReceiptService`, `ReceiptRepository` | Receipts, invoices, PDF, Excel export |
|
||||
| **Email** | 3 | `EmailSendingService`, `VariableResolver`, `AttachmentGenerator` | Template-based email with PDF attachments |
|
||||
| **Automation** | 6 | `AutomationService` (834 LOC), `AutomationRepository`, `AutomationExecutionLogRepository` | Event→condition→action rules, email triggers |
|
||||
| **Settings** | 54+ | Integration controllers, OAuth clients, API clients (Fakturownia incl.), mappers | Allegro/shopPRO/Apaczka/InPost/Fakturownia config, status mappings |
|
||||
| **Sms** | 3 | `SmsMessageRepository`, `SmsConversationService`, `SmsplanetWebhookController` | SMSPLANET outbound order SMS, inbound webhook parsing, order matching |
|
||||
| **Notifications** | 3 | `NotificationRepository`, `NotificationController`, `NotificationApiController` | Global notification history, unread polling API, mark-read actions |
|
||||
| **Cron** | 12 | `CronRepository`, `CronHandlerFactory`, handler classes | Scheduled imports, syncs, token refresh |
|
||||
| **Printing** | 4 | `PrintApiController`, `PrintJobRepository`, `ApiKeyMiddleware` | REST API for Windows print client |
|
||||
| **Statistics** | 3 | `OrdersStatisticsController`, `OrdersStatisticsRepository`, `statistics-summary-charts.js` | Daily order statistics and monthly summary charts |
|
||||
| **Info** | 1 | `InfoController` | Health check |
|
||||
|
||||
## Frontend Enhancement Modules
|
||||
|
||||
### Checkbox Multiselect (`public/assets/js/modules/checkbox-multiselect.js`)
|
||||
- Loaded globally from `resources/views/layouts/app.php`.
|
||||
- Enhances native `<select multiple data-checkbox-multiselect>` controls after `DOMContentLoaded`.
|
||||
- Keeps the original select in the form, synchronizes option `selected` state, and preserves native GET/POST names such as `channels[]` and `status_groups[]`.
|
||||
- Used by `/statistics/orders` and `/statistics/summary` filters to display a compact trigger, checkbox dropdown, "Wszystkie" bulk toggle, and selected count.
|
||||
- Progressive enhancement: if JavaScript fails, the native multi-select remains visible.
|
||||
|
||||
### Statistics Summary Charts (`public/assets/js/modules/statistics-summary-charts.js`)
|
||||
- Loaded globally from `resources/views/layouts/app.php` after Chart.js 4.4.8 CDN; activates only when `#js-statistics-summary-data` exists.
|
||||
- Reads JSON produced by `OrdersStatisticsController::summary()` and renders two interactive Chart.js line charts on `/statistics/summary`.
|
||||
- Chart 1 displays monthly order counts per selected integration plus a `Razem` line.
|
||||
- Chart 2 displays monthly gross order values per selected integration plus a `Razem` line.
|
||||
- The PHP view keeps table fallbacks under both charts, so the data remains visible if JavaScript fails.
|
||||
|
||||
## Key Data Flows
|
||||
|
||||
### Order Lifecycle
|
||||
1. **Import** — Cron handler → API client → `OrderImportService` → `OrdersRepository::insertOrder()` → `AutomationService::executeForNewOrder()`
|
||||
2. **Re-import (Phase 111 + 112 + 119)** — `OrderImportRepository::upsertOrderAggregate` wykrywa tranzycje `payment_status` z 0/1 na 2 i zwraca `payment_transition=true`. `AllegroOrderImportService` i `ShopproOrdersSyncService` na tej fladze emituja `payment.status_changed`, co przez chain reguly automatyzacji #7 zmienia `status_code` na `w_realizacji`. Logika preservacji `status_code` z Phase 62 pozostaje rozdzielona (`statusOverwriteAllowed` = `currentStatus='nieoplacone' && newPaymentStatus===2`). **Phase 112-01 (delta-only re-import):** przy `created=false` repo nie wywoluje `replaceAddresses/replaceItems/replaceNotes/replaceShipments/replaceStatusHistory` — `order_items.id` i flagi lokalne (np. `project_generated` z Phase 97) pozostaja stabilne. `updateOrderDelta()` aktualizuje wylacznie `status_code` (warunkowo, z propagacja anulowania), `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`. Anulowanie ze zrodla (`is_canceled_by_buyer=1` lub zmapowany pull `status_code='anulowane'`) nadpisuje preservacje statusu. Identical-payload guard (`normalizePayloadJson`) pomija UPDATE gdy znormalizowany payload nie rozni sie od DB i brak innych tranzycji. **Phase 119-01 (total_paid protection):** gdy `paymentStatusUnchanged=true` (`oldPaymentStatus === newPaymentStatus`), `updateOrderDelta()` nie dolacza `total_paid` do UPDATE — chroni reczne korekty kwoty (np. zwroty czesciowe). `is_canceled_by_buyer` jest pomijane analogicznie, chyba ze `cancelledBySource=true` (cancel propagation ze zrodla zawsze wymusza wpis flagi). Pozostale pola (`status_code`, `payment_status`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`) zachowuja niezmieniony kontrakt z Phase 112-01.
|
||||
3. **Status update** — `OrdersController::updateStatus()` → `OrdersRepository::updateStatus()` → automation check
|
||||
4. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` / `ErliStatusSyncService` → marketplace API
|
||||
|
||||
### Statistics Summary
|
||||
1. **Request** — `/statistics/summary` → `OrdersStatisticsController::summary()`
|
||||
2. **Filters** — controller reuses statistics filter semantics: date range, `channels[]`, `status_groups[]`, default status groups excluding cancelled; default history starts at `2026-04-01`.
|
||||
3. **Aggregation** — `OrdersStatisticsRepository::aggregateByMonth()` groups existing `orders` rows by `YYYY-MM` and channel key, using the same effective date/channel/status/gross amount SQL helpers as the daily report.
|
||||
4. **View model** — controller builds per-integration series and total series for order count and gross value charts.
|
||||
5. **Render** — `resources/views/statistics/summary.php` renders filters, chart JSON, two canvas targets, and table fallbacks.
|
||||
|
||||
### Shipment Flow
|
||||
1. **Create** — `ShipmentController::create()` → `ShipmentProviderRegistry` → carrier `ShipmentService::createShipment()` → `ShipmentPackageRepository::insert()`
|
||||
2. **Track** — Cron `ShipmentTrackingHandler` → `ShipmentTrackingRegistry` → carrier tracking API → `ShipmentPackageRepository::updateDeliveryStatus()`
|
||||
|
||||
### Receipt / Invoice
|
||||
1. **Generate** — `ReceiptController::store()` → `ReceiptService::generateReceipt()` → `ReceiptRepository::insert()` + Dompdf PDF
|
||||
2. **Email** — `EmailSendingService::send()` → `VariableResolver::resolve()` → `AttachmentGenerator::generatePdf()` → PHPMailer SMTP
|
||||
|
||||
### Automation Rules
|
||||
1. **Setup** — `AutomationController` → `AutomationRepository::insertRule()`
|
||||
2. **Trigger** — `AutomationService::executeForOrder()` → evaluates trigger (`order_status_changed`, `order_status_aged`) → runs action (send email, update status)
|
||||
3. **Log** — `AutomationExecutionLogRepository` tracks every run
|
||||
|
||||
### Cron Jobs
|
||||
|
||||
| Handler | Task |
|
||||
|---------|------|
|
||||
| `AllegroOrdersImportHandler` | Fetch new Allegro orders |
|
||||
| `AllegroStatusSyncHandler` | Push status changes to Allegro |
|
||||
| `ErliStatusSyncHandler` | Pull Erli status events via inbox or push manual local status changes to Erli |
|
||||
| `AllegroTokenRefreshHandler` | OAuth token refresh (24h expiry) |
|
||||
| `ShopproOrdersImportHandler` | Fetch new shopPRO orders |
|
||||
| `ShopproStatusSyncHandler` | Push status to shopPRO |
|
||||
| `ShopproPaymentStatusSyncHandler` | Sync payment statuses |
|
||||
| `ShipmentTrackingHandler` | Poll carrier tracking APIs |
|
||||
| `OrderStatusAgedHandler` | Trigger automation for stuck statuses |
|
||||
| `AutomationHistoryCleanupHandler` | Purge old automation logs |
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
Manual constructor injection in `routes/web.php` — no DI container library. Example:
|
||||
|
||||
```php
|
||||
$ordersController = new OrdersController(
|
||||
$template, $translator, $auth,
|
||||
$app->orders(), $shipmentPackageRepository,
|
||||
$receiptRepository, $receiptConfigRepository, ...
|
||||
);
|
||||
```
|
||||
|
||||
All production classes are `final` — prevents accidental inheritance.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
bootstrap/ app.php (service wiring, config loading)
|
||||
bin/ migrate.php, cron.php (CLI entry points)
|
||||
config/ app.php, database.php
|
||||
database/
|
||||
migrations/ 84 SQL files (YYYYMMDD_NNNNNN_description.sql)
|
||||
drafts/ WIP migrations
|
||||
public/
|
||||
index.php HTTP entry point
|
||||
.htaccess Apache rewrite rules
|
||||
assets/css/ Compiled CSS (app.css, login.css, modules/)
|
||||
assets/js/ jquery-alerts.js, global-search.js, automation-form.js
|
||||
resources/
|
||||
views/ PHP templates by module + components/ layouts/
|
||||
scss/ SCSS sources (app.scss, login.scss, modules/_*.scss)
|
||||
modules/ jquery-alerts JS+SCSS source
|
||||
lang/pl/ Polish translations
|
||||
routes/
|
||||
web.php All routes (581 lines)
|
||||
src/
|
||||
Core/ Framework (25 files)
|
||||
Modules/ 13 feature modules (~200+ PHP files)
|
||||
storage/
|
||||
logs/ app.log
|
||||
sessions/ PHP session files
|
||||
cache/ PHPUnit cache, etc.
|
||||
tests/
|
||||
Unit/ PHPUnit tests (7+ service test files)
|
||||
bootstrap.php PSR-4 autoloader for tests
|
||||
```
|
||||
|
||||
## Phase 108 — Delivery Status Management
|
||||
|
||||
### DeliveryStatusRepository (`src/Modules/Shipments/DeliveryStatusRepository.php`)
|
||||
- CRUD dla tabeli `delivery_statuses`
|
||||
- Per-request static cache (`private static ?array $cache`)
|
||||
- Blokuje edycję/usunięcie statusów systemowych (`is_system=1`)
|
||||
- Blokuje usunięcie statusów używanych w `delivery_status_mappings` lub `shipment_packages`
|
||||
|
||||
### DeliveryStatusesController (`src/Modules/Settings/DeliveryStatusesController.php`)
|
||||
- Panel `/settings/delivery-statuses`
|
||||
- Dwie zakładki via `?tab=` param: `statuses` (CRUD) i `mapping` (embed mapowania)
|
||||
- Wstrzykuje `DeliveryStatusRepository` i `DeliveryStatusMappingRepository`
|
||||
|
||||
### DeliveryStatus::setRepository() (dynamic loading)
|
||||
- Wywoływane raz w `routes/web.php` po bootstrap
|
||||
- `label()`, `getAllOptions()`, `getAllStatuses()`, `getColor()` ładują z DB gdy repo ustawione
|
||||
- Fallback na hardcoded stałe gdy repo niedostępne
|
||||
|
||||
### AutomationController + AutomationService (Phase 108 Plan 02)
|
||||
- `AutomationController::buildShipmentStatusOptions()` — buduje listę opcji `[key => ['label' => ...]]` z `DeliveryStatus::getAllOptions()` (DB-driven)
|
||||
- Walidacja `shipment_status` warunku i `update_shipment_status` akcji w `parseConditionValue()`/`parseActionConfig()` używa `DeliveryStatus::getAllStatuses()`
|
||||
- `AutomationService::evaluateShipmentStatusCondition()` — bezpośrednie porównanie kluczy DB (usunięto mapping grupowy `SHIPMENT_STATUS_OPTION_MAP`)
|
||||
- `AutomationService::resolveStatusFromActionKey()` — bezpośredni klucz statusu z DB jako target
|
||||
- BREAKING: stare reguły z grupowymi kluczami (`registered`, `courier_pickup`, `dropped_at_point`, `unclaimed`, `picked_up_return`) nie matchują się — operator musi je odtworzyć przy użyciu nowych kluczy DB
|
||||
|
||||
## Phase 113 — Fakturownia Integration Foundation
|
||||
|
||||
### Schema (Plan 113-01)
|
||||
- Tabele `invoice_configs`, `invoices`, `invoice_number_counters` (mirror `receipt_configs`/`receipts`/`receipt_number_counters` plus delegation fields: `invoice_configs.integration_id`, `is_delegated`; `invoices.external_invoice_id`, `external_pdf_url`).
|
||||
- Tabela `fakturownia_integration_settings` (multi-account: `integration_id INT UNSIGNED NOT NULL UNIQUE` FK -> `integrations(id)`).
|
||||
- `orders.invoice_requested TINYINT(1) NOT NULL DEFAULT 0` z indexem `idx_orders_invoice_requested`.
|
||||
|
||||
### FakturowniaIntegrationRepository (`src/Modules/Settings/FakturowniaIntegrationRepository.php`)
|
||||
- `findAll()` JOIN `integrations` + `fakturownia_integration_settings` zwraca listę kont Fakturowni.
|
||||
- `findByIntegrationId(int)` zwraca jedno konto (z resolved `api_token_encrypted` z `integrations.api_key_encrypted` z fallbackiem na settings).
|
||||
- `save(?int $integrationId, array $payload)` - upsert (insert do `integrations` przez `IntegrationsRepository::ensureIntegration` gdy `$integrationId=null`; w przeciwnym razie update name/is_active). Token szyfrowany przez `IntegrationSecretCipher` i zapisywany do `integrations.api_key_encrypted` (źródło prawdy) oraz settings.api_token_encrypted (cache).
|
||||
- `delete(int $integrationId)` — blokuje usunięcie gdy `invoice_configs.integration_id = X` (FK SET NULL chroniony aplikacyjnie przez `IntegrationConfigException`).
|
||||
- `getDecryptedToken(int $integrationId)` — dla użycia w przyszłych planach (createInvoice/downloadPdf).
|
||||
|
||||
### FakturowniaApiClient (`src/Modules/Settings/FakturowniaApiClient.php`)
|
||||
- `testConnection(string $prefix, string $apiToken): array` — GET `https://{prefix}.fakturownia.pl/account.json?api_token=...` z cURL + `SslCertificateResolver::resolve()`. Zwraca `['ok' => bool, 'http_code' => int, 'message' => string]`.
|
||||
- `createInvoice()` i `downloadPdf()` — STUB-y rzucające `RuntimeException` do implementacji w kolejnym planie.
|
||||
|
||||
### IntegrationsRepository::updateTestResult()
|
||||
- Nowa metoda zapisująca `last_test_status / last_test_http_code / last_test_message / last_test_at` po wywołaniu API test. Używana przez `FakturowniaIntegrationController::test()` (i będzie reuse'owana w przyszłych integracjach).
|
||||
|
||||
### FakturowniaIntegrationController (`src/Modules/Settings/FakturowniaIntegrationController.php`)
|
||||
- Routy `/settings/integrations/fakturownia` (lista), `.../edit`, `.../save`, `.../test`, `.../delete` (POST z `_token` CSRF).
|
||||
- Wykorzystuje `Flash::set('fakturownia.save'|'fakturownia.test'|'fakturownia.error')` i `RedirectPathResolver`.
|
||||
|
||||
### IntegrationsHubController
|
||||
- Nowy parametr konstruktora `FakturowniaIntegrationRepository $fakturownia` i nowa metoda `buildFakturowniaRow()` agregująca status wszystkich kont (count instancji, configured/active counts, ostatni test).
|
||||
|
||||
## Phase 118 — Fakturownia Single Instance
|
||||
|
||||
### FakturowniaIntegrationRepository
|
||||
- Zarzadza jedna globalna konfiguracja `fakturownia_integration_settings.id=1` i jednym rekordem `integrations.type='fakturownia'`.
|
||||
- `getSettings()` zasila formularz i hub integracji; `saveSettings()` zapisuje prefix, token, department/defaults i aktywnosc.
|
||||
- `getIntegrationId()` jest zrodlem prawdy dla delegowanych `invoice_configs.integration_id`.
|
||||
- `findAll()` zostaje kompatybilnym wrapperem zwracajacym liste z jednym elementem.
|
||||
|
||||
### FakturowniaIntegrationController + UI
|
||||
- `/settings/integrations/fakturownia` pokazuje jeden formularz i test polaczenia.
|
||||
- Legacy `/new` i `/edit` przekierowuja do globalnej konfiguracji; delete nie jest oferowany w UI.
|
||||
- Hub integracji pokazuje jedna instancje Fakturowni, bez licznika kont.
|
||||
|
||||
### Invoice Config Delegation
|
||||
- `InvoiceConfigRepository::save()` przy `is_delegated=1` ignoruje wieloinstancyjny wybor i ustawia globalny Fakturownia integration id.
|
||||
- UI konfiguracji faktury pokazuje status globalnej konfiguracji zamiast selecta kont.
|
||||
- `invoice_configs.integration_id` zostaje dla kompatybilnosci z `InvoiceService` i istniejaca historia faktur.
|
||||
|
||||
### Migration 20260512_000109
|
||||
- Wybiera aktywna instancje Fakturowni; fallback: uzywana w `invoice_configs`, potem najnizsze id.
|
||||
- Przepina delegowane `invoice_configs.integration_id` na zachowany rekord i usuwa nadmiarowe rekordy Fakturowni po przepieciu zaleznosci.
|
||||
|
||||
## Phase 115 — Wystawianie faktury z zamowienia
|
||||
|
||||
### InvoiceService (`src/Modules/Accounting/InvoiceService.php`)
|
||||
- `issue(array $params): array` — orchestrator. Walidacja config (active), order details fetch, build snapshots (seller z `company_settings`, buyer merged z payload_json+addresses+manual override, items z VAT-aware netto/brutto split), routing do `issueLocal()` lub `issueDelegated()` zaleznie od `invoice_configs.is_delegated`.
|
||||
- `issueLocal()` — `InvoiceRepository::nextLocalNumber()` (atomowy counter z `invoice_number_counters`) -> `insertLocal()` -> zwraca `{invoice_id, invoice_number, total_gross, mode='local'}`.
|
||||
- `issueDelegated()` — Phase 136 retry-safe flow dla Fakturowni. Serwis uzywa stabilnego `oid` z `orders.internal_order_number`, sprawdza `GET /invoices.json?oid=...` przed POST, zapisuje lokalny wiersz `pending_external` przed POST, finalizuje go po sukcesie albo auto-podpina dokument znaleziony po timeoutcie. `invoice_number_counters` NIE jest dotykany dla delegated.
|
||||
- Static `extractBuyerTaxNumber($order, $buyerAddress)` — parsuje NIP z payload_json sciezki: `invoice.address.taxId` (Allegro), `invoice.taxId/nip`, `buyer.tax_number/nip`, `client.nip/tax_number`, top-level `nip/tax_number`. Fallback na `order_addresses.company_tax_number`.
|
||||
|
||||
### InvoiceRepository (`src/Modules/Accounting/InvoiceRepository.php`)
|
||||
- `findByOrderId/findById` — JOIN `invoices` + `invoice_configs` + `integrations` (type='fakturownia') + `fakturownia_integration_settings` (LEFT JOIN dla `account_prefix`).
|
||||
- `insertLocal/insertDelegated` — wspolny prywatny `insert()` z roznymi NULL-amizacjami `external_*` pol.
|
||||
- `nextLocalNumber()` — `INSERT ... ON DUPLICATE KEY UPDATE last_number = last_number + 1` na `invoice_number_counters`, mirror `ReceiptRepository::getNextNumber`.
|
||||
- `paginate()` — filtry: `search` (numer/order ref), `config_id`, `mode` (local/delegated rozroznia po `external_invoice_id IS NULL`), `date_from`/`date_to`.
|
||||
|
||||
### FakturowniaApiClient (rozszerzony)
|
||||
- `createInvoice(array $settings, array $invoice)` — POST `https://{prefix}.fakturownia.pl/invoices.json` z body `{api_token, invoice}`. cURL z `SslCertificateResolver`, timeout `$timeoutSeconds`. On 2xx parsuje JSON na `{id, number, view_url, pdf_url, raw}`. On non-2xx rzuca `RuntimeException("HTTP {code}: {error}")`.
|
||||
- `findInvoiceByOid(array $settings, string $oid)` — GET `https://{prefix}.fakturownia.pl/invoices.json?oid=...&api_token=...`; uzywane do reconciliacji po niepewnym POST i przed retry, bo Fakturownia dokumentuje lookup po `oid`, a nie dokumentuje `Idempotency-Key`.
|
||||
- `buildPdfUrl(prefix, invoiceId, apiToken)` — string-builder dla `https://{prefix}.fakturownia.pl/invoices/{id}.pdf?api_token=...`. Bez fetcha; uzywany w redirect 302.
|
||||
- Dodany `httpPostJson()` (private) odpowiednik istniejacego `httpGet()`.
|
||||
|
||||
### InvoiceController (`src/Modules/Accounting/InvoiceController.php`)
|
||||
- `create(Request)` — GET `/orders/{id}/invoice/create`. Walidacja `orders.invoice_requested=1` (przekierowanie z flash error gdy 0). Active configs (filter `is_active=1`). NIP auto-prefill via `InvoiceService::extractBuyerTaxNumber()`. Renderuje `accounting/invoice_form`.
|
||||
- `store(Request)` — POST `/orders/{id}/invoice/store`. CSRF, `config_id` walidacja. Wywoluje `InvoiceService::issue()` z buyer overrides z formularza. On success: `OrdersRepository::recordActivity('invoice_issued')`, flash success, redirect na `/orders/{id}/invoice/{invoiceId}`. On `InvoiceIssueException`: flash do `invoice.error`, redirect z powrotem na form.
|
||||
- `show(Request)` — GET `/orders/{id}/invoice/{invoiceId}`. HTML preview z snapshotow.
|
||||
- `pdf(Request)` — GET `/orders/{id}/invoice/{invoiceId}/pdf`. Gdy `external_pdf_url` istnieje -> redirect 302; inaczej Dompdf inline z templatu `accounting/invoice_pdf`.
|
||||
- `issuedList(Request)` — GET `/settings/accounting/invoices/issued`. Filtry GET, paginacja 50/strona.
|
||||
|
||||
### orders.invoice_requested toggle
|
||||
- `OrdersRepository::setInvoiceRequested(int, bool)` — UPDATE z `updated_at = NOW()`.
|
||||
- `OrdersController::toggleInvoiceRequested` — POST `/orders/{id}/invoice-requested/toggle`. CSRF, JSON response `{success, invoice_requested}`. Loguje `order_activity_log` z `event_type='invoice_requested_changed'`.
|
||||
- `public/assets/js/modules/invoice-requested-toggle.js` — vanilla JS, idempotent guard `dataset.bound='1'`. AJAX POST przy `change`, optimistic show/hide `[data-invoice-button-wrap]`. Rollback checkbox przy HTTP/network blad.
|
||||
|
||||
### Auto-import flagi invoice_requested (zaktualizowane Phase 125-01)
|
||||
- **shopPRO:** `ShopproOrderMapper::resolveInvoiceRequested($payload)` jest jedynym zrodlem heurystyki — sprawdza top-level klucze payloadu: `is_invoice`, `invoice.required`, `invoice` (bool), oraz obecnosc danych firmowych (`firm_name`/`firm_nip`/`invoice.company_name`/`invoice.tax_id`/`invoice.nip`/`company_name`/`tax_id`/`nip` etc.). Wynik eksponowany w `mapOrderAggregate()` jako top-level klucz `invoice_detected` (transient, nie pisany do DB). `ShopproOrdersSyncService::importOne` propaguje `!empty($aggregate['invoice_detected'])` do `setInvoiceRequested(true)` tylko przy `wasCreated=true`. Stara metoda `shouldRequestInvoice` w sync service usunieta (zastapiona heurystyka mappera).
|
||||
- **Allegro:** `AllegroOrderImportService::shouldRequestInvoice($payload)` sprawdza w kolejnosci: `invoice.required` (truthy), `invoice.naturalPerson === false` (klient firmowy), `invoice.address.taxId` (NIP w adresie faktury), `invoice.companyName`/`invoice.address.company.name`. Wywolane tylko przy `wasCreated=true` w `importSingleOrder`. Rozszerzenie heurystyki naprawia luke gdy klient Allegro podaje NIP bez ustawiania `invoice.required=true`.
|
||||
- **Kontrakt Phase 115/112 zachowany:** auto-set TYLKO przy `wasCreated=true`. Delta-only re-import nie nadpisuje manualnej flagi operatora (manualny toggle przez `/orders/{id}/invoice-requested/toggle` przezywa kolejne synchronizacje).
|
||||
- **Legacy:** kolumna `orders.is_invoice` (Phase 115) usunieta migracja `20260513_000113_drop_orders_is_invoice_and_backfill_invoice_requested.sql`. Backfill: 7 zamowien gdzie `is_invoice=1 AND invoice_requested=0` dostalo `invoice_requested=1` przed DROP COLUMN.
|
||||
|
||||
### View hierarchy
|
||||
- `accounting/invoice_form.php` — formularz wystawiania.
|
||||
- `accounting/invoice_preview.php` — HTML preview po wystawieniu.
|
||||
- `accounting/invoice_pdf.php` — template Dompdf, mirror `receipts/print.php` z dodatkowymi polami faktury VAT (parties, netto/VAT/brutto per stawka, termin platnosci).
|
||||
- `accounting/invoices_issued_list.php` — lista pod `/settings/accounting/invoices/issued`.
|
||||
- `orders/show.php` — checkbox toggle + warunkowy przycisk "Wystaw fakture" + sekcja "Faktury" w tabie documents.
|
||||
|
||||
### DI wiring (`routes/web.php`)
|
||||
- `$invoiceRepository = new InvoiceRepository($app->db());` (po `InvoiceConfigRepository`).
|
||||
- `$invoiceService = new InvoiceService($invoiceRepository, $invoiceConfigRepository, $companySettingsRepository, new OrdersRepository(...), $fakturowniaIntegrationRepository, $fakturowniaApiClient);`
|
||||
- `$invoiceController = new InvoiceController($template, $translator, $auth, $invoiceRepository, $invoiceConfigRepository, $companySettingsRepository, new OrdersRepository(...), $invoiceService);`
|
||||
- `$ordersController` rozszerzony o 2 trailing params: `$invoiceRepository`, `$invoiceConfigRepository`.
|
||||
|
||||
### BREAKING / migration
|
||||
- Zadnych nowych migracji — Phase 113-01 dostarczyla `orders.invoice_requested`, `invoice_configs/invoices/invoice_number_counters` i `fakturownia_integration_settings`.
|
||||
- `OrdersController` ctor dostal 2 NEW optional params (default null) — backwards compatible.
|
||||
|
||||
### Edge cases / known limits
|
||||
- INVOICE-IDEMP-115 (`.paul/codebase/todo.md`) — resolved in Phase 136 przez `external_oid`, `pending_external`, lookup po `oid` i auto-attach po timeoutcie.
|
||||
- Brak `invoice.created` event automatyzacji (per Phase 113 decision).
|
||||
- Brak download+cache PDF z Fakturowni — tylko redirect 302 (kazdy klik na PDF dla delegated faktury fetchuje PDF z Fakturowni).
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
### HostedSmsIntegrationRepository (`src/Modules/Settings/HostedSmsIntegrationRepository.php`)
|
||||
- Zarzadza pojedynczym rekordem `hostedsms_integration_settings` (`id=1`) i bazowym wpisem `integrations` typu `hostedsms`.
|
||||
- Szyfruje haslo przez `IntegrationSecretCipher`; formularz widzi tylko flage `has_password`.
|
||||
- Udostepnia `getCredentials()` dla kontrolera testowej wysylki SMS.
|
||||
|
||||
### HostedSmsApiClient (`src/Modules/Settings/HostedSmsApiClient.php`)
|
||||
- Wykonuje `POST https://api.hostedsms.pl/SimpleApi` jako `application/x-www-form-urlencoded`.
|
||||
- Wysyla `UserEmail`, `Password`, `Sender`, `Phone`, `Message` oraz opcjonalnie `ConvertMessageToGSM7`.
|
||||
- Traktuje `MessageId` jako sukces, a `ErrorMessage` jako blad biznesowy nawet przy HTTP 200.
|
||||
|
||||
### HostedSmsIntegrationController (`src/Modules/Settings/HostedSmsIntegrationController.php`)
|
||||
- Endpointy: `GET /settings/integrations/hostedsms`, `POST /settings/integrations/hostedsms/save`, `POST /settings/integrations/hostedsms/test`.
|
||||
- `test` realnie wysyla SMS z edytowalna trescia i zapisuje wynik w `integrations.last_test_*`.
|
||||
|
||||
### IntegrationsHubController
|
||||
- Dodaje wiersz HostedSMS do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.
|
||||
|
||||
## Phase 117 - SMSPLANET Integration Settings
|
||||
|
||||
### SmsplanetIntegrationRepository (`src/Modules/Settings/SmsplanetIntegrationRepository.php`)
|
||||
- Zarzadza pojedynczym rekordem `smsplanet_integration_settings` (`id=1`) i bazowym wpisem `integrations` typu `smsplanet`.
|
||||
- Obsluguje dwie metody autoryzacji: Bearer token oraz `key` + `password`.
|
||||
- Szyfruje token, klucz API i haslo przez `IntegrationSecretCipher`; formularz widzi tylko flagi `has_api_token`, `has_api_key` i `has_api_password`.
|
||||
- Udostepnia `getCredentials()` tylko dla kompletnej i aktywnej konfiguracji testowej wysylki SMS, razem z opcjonalna `default_footer`.
|
||||
|
||||
### SmsplanetApiClient (`src/Modules/Settings/SmsplanetApiClient.php`)
|
||||
- Wykonuje `POST https://api2.smsplanet.pl/sms` jako `application/x-www-form-urlencoded`.
|
||||
- Dla Bearer token wysyla naglowek `Authorization: Bearer ...`; dla `key_password` wysyla parametry `key` i `password`.
|
||||
- Wysyla `from`, `to`, `msg` oraz opcjonalnie `clear_polish` i `transactional`; test nie ustawia `test=1`, wiec wysyla realny SMS.
|
||||
- Traktuje `messageId` jako sukces, a `errorMsg`/`errorCode` jako blad biznesowy.
|
||||
|
||||
### SmsplanetIntegrationController (`src/Modules/Settings/SmsplanetIntegrationController.php`)
|
||||
- Endpointy: `GET /settings/integrations/smsplanet`, `POST /settings/integrations/smsplanet/save`, `POST /settings/integrations/smsplanet/test`.
|
||||
- `test` realnie wysyla SMS z edytowalna trescia, dopisuje `default_footer` gdy jest skonfigurowana i zapisuje wynik w `integrations.last_test_*`.
|
||||
|
||||
### IntegrationsHubController
|
||||
- Dodaje wiersz SMSPLANET do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.
|
||||
|
||||
## Phase 127 — polkurier Integration Settings
|
||||
|
||||
### Schema
|
||||
- Tabela `polkurier_integration_settings` (fixed `id=1`, `integration_id INT UNSIGNED NULL UNIQUE FK -> integrations(id) CASCADE`, `login`, `api_token_encrypted`, `default_label_format`).
|
||||
- Pojedynczy rekord `integrations.type='polkurier'`, `name='polkurier'`, `base_url='https://api.polkurier.pl/'` (mirror Apaczki/HostedSMS/SMSPLANET).
|
||||
- Migracja `20260514_000114_create_polkurier_integration_settings.sql` jest idempotentna (`CREATE TABLE IF NOT EXISTS` + `INSERT ... ON DUPLICATE KEY UPDATE`).
|
||||
|
||||
### PolkurierIntegrationRepository (`src/Modules/Settings/PolkurierIntegrationRepository.php`)
|
||||
- Konstruktor `(PDO $pdo, string $secret)` — buduje wewnetrznie `IntegrationsRepository` i `IntegrationSecretCipher` (mirror `HostedSmsIntegrationRepository`).
|
||||
- `getSettings()` zwraca `login`, `default_label_format`, flage `has_api_token: bool` (NIE plaintext), `is_active`, `last_test_*`.
|
||||
- `saveSettings($payload)` waliduje `login` (<=190 znakow) i `default_label_format` (PDF/ZPL/EPL), szyfruje Token API; gdy token w payloadzie jest pusty -> nie nadpisuje istniejacego (BC).
|
||||
- `getCredentials()` zwraca odszyfrowany `login + api_token + default_label_format` TYLKO gdy `is_active=1` i token istnieje; inaczej `null`. Konsumowane przez `PolkurierApiClient::testConnection()` i przyszly `PolkurierShipmentService`.
|
||||
- `getIntegrationId()` — single source of truth dla przyszlych modulow.
|
||||
|
||||
### PolkurierApiClient (`src/Modules/Settings/PolkurierApiClient.php`)
|
||||
- Kontrakt API zweryfikowany na podstawie oficjalnego SDK (https://github.com/Polkurier/polkurier-sdk): jedno publiczne POST endpoint `https://api.polkurier.pl/`, JSON body `{"authorization": {"login", "token"}, "apimetod": "<method>", "data": {...}}`.
|
||||
- `testConnection(login, apiToken)` wywoluje `apimetod="test_auth_api"` z `data={platform: 'orderPRO', platform_version: '1.0'}`; sukces gdy `status='ok'` lub `response.authorization` niepusta.
|
||||
- cURL z `SslCertificateResolver::resolve()`, `CURLOPT_TIMEOUT=$timeoutSeconds` (default 15), `CURLOPT_SSL_VERIFYPEER=true`, `Content-Type: application/json`. PHP 8.5 compatible (brak `curl_close()`).
|
||||
- Stuby `createShipment()`, `getLabel()`, `getStatus()`, `cancelOrder()` rzucaja `RuntimeException("Not implemented in Phase 127")` — dolozone w kolejnych fazach.
|
||||
|
||||
### PolkurierIntegrationController (`src/Modules/Settings/PolkurierIntegrationController.php`)
|
||||
- Endpointy: `GET /settings/integrations/polkurier`, `POST /settings/integrations/polkurier/save`, `POST /settings/integrations/polkurier/test`.
|
||||
- `test` realnie wywoluje API polkurier i zapisuje wynik w `integrations.last_test_*` przez `IntegrationsRepository::updateTestResult()`.
|
||||
- Flash przez legacy `Flash::set('settings_success'|'settings_error'|'polkurier_test', ...)` — spojnie z HostedSMS/SMSPLANET; renderer flash w `layouts/app.php` (Phase 120) obsluguje BC mapping przez `Flash::all()`.
|
||||
- Widok `resources/views/settings/polkurier.php` uzywa wylacznie komponentu `resources/views/components/alert.php` (Phase 120 contract).
|
||||
|
||||
### IntegrationsHubController (Phase 127 patch)
|
||||
- Dodany parametr `PolkurierIntegrationRepository $polkurier`.
|
||||
- Metoda `buildPolkurierRow()` zwraca te same klucze co `buildApaczkaRow()` (`provider`, `instance`, `authorization_status`, `secret_status`, `is_active`, `last_test_at`, `configure_url`).
|
||||
- Wiersz polkurier wstawiony zaraz po Apaczka (sasiednio — semantycznie oba to brokery kurierskie).
|
||||
|
||||
### Boundaries / co NIE zostalo dotkniete
|
||||
- `ShipmentProviderRegistry` i `src/Modules/Shipments/*` — `PolkurierShipmentService` nie istnieje w Phase 127. Tworzenie przesylek, etykiety, tracking i mapowania metod dostawy beda dodane w kolejnej fazie.
|
||||
- `apaczka_integration_settings`, `ApaczkaShipmentService`, `ApaczkaTrackingService` — Apaczka netknieta, dziala rownolegle.
|
||||
- `delivery_status_mappings` — brak nowych wpisow `provider='polkurier'` (dolozone razem z tracking service w kolejnej fazie).
|
||||
|
||||
## Phase 128 — polkurier ShipmentService + Tracking + UI prepare
|
||||
|
||||
### PolkurierApiClient (Phase 128 extension)
|
||||
- 6 nowych metod publicznych obok zachowanego `testConnection()`:
|
||||
- `getAvailableCarriers($login, $token)` → `apimetod=available_carriers`. Zwraca tablice przewoznikow z polami `servicecode`, `name`, `additional_data`, `foreign_shipments`. Konsumowane przez `PolkurierShipmentService::getDeliveryServices()`.
|
||||
- `createShipment($login, $token, $payload)` → `apimetod=create_order`. Payload zgodny z oficjalna doca PDF v1.11 (zweryfikowany): `shipmenttype` (lowercase: box/envelope/palette/small_parcel/parcel_size_20), `courier` (servicecode), `description`, `sender`/`recipient` (company/person/street/housenumber/flatnumber/postcode/city/email/phone/country/point_id), `packs[]` (length/width/height/weight/amount/type), `pickup` (pickupdate/pickuptimefrom/pickuptimeto/nocourierorder), opcjonalnie `COD` i `insurance`.
|
||||
- `getLabel($login, $token, $orderno)` → `apimetod=get_label`. **API przyjmuje WYLACZNIE `orderno: Array<String>`** (zweryfikowane w dokumentacji PDF). Rozmiar etykiety A4 vs A6 sterowany jest w panelu klienta polkurier.pl (Ustawienia konta → Preferencje etykiet), NIE przez API. Odpowiedz: `{file: <base64 PDF>}`.
|
||||
- `getStatus($login, $token, $orderno)` → `apimetod=get_status`. Zwraca `{url, status_date, status, status_code, delivered_date}`. Kody w tabeli ORDER_STATUS (O/P/A/WP/D/Z/W).
|
||||
- `cancelOrder($login, $token, $orderno)` → `apimetod=cancel_order`. Zwraca `{cancellation: true}`. Nie wywolywane przez nasz kod w Phase 128 (operator anuluje w panelu polkuriera).
|
||||
- `getInpostParcelMachines` + `getCourierPoints` — stuby na przyszle rozszerzenie UI (panel paczkomatow). Aktualnie nie wykorzystywane w UI (operator wpisuje `receiver_point_id` recznie w sekcji Adres odbiorcy).
|
||||
- Wspolny prywatny `call($apimetod, $data, $login, $token): mixed` parsuje envelope `{status, response}`. Sukces -> zwraca tresc `response`. Blad -> rzuca `RuntimeException` z trescia z `response` (string albo zserializowany JSON dla tablic).
|
||||
|
||||
### PolkurierShipmentService (`src/Modules/Shipments/PolkurierShipmentService.php`)
|
||||
- `final class implements ShipmentProviderInterface` (`code()='polkurier'`). DI: `PolkurierIntegrationRepository`, `PolkurierApiClient`, `ShipmentPackageRepository`, `CompanySettingsRepository`, `OrdersRepository`.
|
||||
- `getDeliveryServices()` cache'uje per-request liste z `available_carriers`, normalizuje do `[{id, name, supports_pickup_point, point_courier, foreign_shipments, raw}]`. Pole `supports_pickup_point` to heurystyka po `servicecode`/`name` (paczkomat/parcel/inpost/orlen/pocztex/kurier48/punkt).
|
||||
- `createShipment($orderId, $formData)` orchestruje pelny flow:
|
||||
1. Walidacja: order istnieje, `service_code`/`delivery_method_id` niepusty, credentials aktywne, sender ma street/city/postcode + name|company.
|
||||
2. Mapowanie `package_type` przez `normalizeShipmentType()` na zbior `[box, envelope, palette, small_parcel, parcel_size_20]` (lowercase, aliases dla PACKAGE/PARCEL/PACZKA/...).
|
||||
3. Buduje `recipient` z `order_addresses` (delivery → fallback customer) + override z formularza. Splituje ulice na `street`/`housenumber`/`flatnumber` regexem (`Marszalkowska 10/5` → street="Marszalkowska", house="10", flat="5").
|
||||
4. `pickup` default: `nextBusinessDay()` + 10:00-16:00, `nocourierorder=false` (override mozliwy przez formularz: `pickup_date`, `pickup_time_from`, `pickup_time_to`, `no_courier_order`).
|
||||
5. `COD` jezeli `cod_amount > 0` (codtype='transfer', codbankaccount z `company_settings.bank_account` po stripowaniu nie-cyfr; throw `ShipmentException` jezeli pusty).
|
||||
6. INSERT do `shipment_packages` (provider='polkurier', status='pending', payload_json z pelnym requestem).
|
||||
7. Wywolanie `apiClient->createShipment()`. On success: parsing przez `extractOrderNumber()` (priorytet `number` → Order entity z SDK, fallback `orderno`/`order_no`/`order_number`/`order_id`/`id`, obsluga wrappera `{order:{...}}` i list) i `extractTrackingNumber()` (priorytet `waybills[0].number` → OrderWaybill entity, fallback top-level klucze).
|
||||
8. UPDATE `shipment_packages`: `status='created'`, `shipment_id=command_id=orderno`, `tracking_number`.
|
||||
9. Synchroniczna proba `downloadLabel()` (niekrytyczna — przy bledzie ignoruje, operator klikni "Pobierz" pozniej).
|
||||
10. Diagnostyka: gdy `orderno=''` mimo `status=success`, zapisuje fragment surowej odpowiedzi (400 znakow) do `shipment_packages.error_message` dla debug.
|
||||
- `downloadLabel($packageId, $storagePath)`: wywoluje `get_label`, parsuje `extractLabelBase64()` (priorytet klucz `file` — zweryfikowane w SDK GetLabel.php, fallback `label`/`pdf`/`data`/`content`/`zpl`/`epl`), `base64_decode`, zapis do `storage/labels/polkurier_{packageId}_{orderno}.pdf`, UPDATE `label_path`.
|
||||
- `checkCreationStatus($packageId)`: graceful — przy bledzie API zwraca zachowany `tracking_number` z DB (cron sam zaktualizuje przez TrackingService).
|
||||
|
||||
### PolkurierTrackingService (`src/Modules/Shipments/PolkurierTrackingService.php`)
|
||||
- `final class implements ShipmentTrackingInterface`. DI: `PolkurierApiClient`, `PolkurierIntegrationRepository`, `DeliveryStatusMappingRepository`.
|
||||
- `supports('polkurier')`. `getDeliveryStatus($package)` woła `get_status` po `shipment_id`/`command_id` (orderno), parsuje `status_code` z `response` (z obsluga listy w response[0]).
|
||||
- Mapowanie surowego `status_code` (O/P/A/WP/D/Z/W) → znormalizowany przez `DeliveryStatus::normalizeWithOverrides('polkurier', $rawStatus, $overrides)` z DB. Seed mapowan w migracji `20260514_000115`.
|
||||
- Odporny na bledy: brak credentials → `null` (skip), wyjatek API → `null`, brak `status_code` → `null`. Cron nie crashuje.
|
||||
|
||||
### DeliveryStatus::trackingUrl (Phase 128 patch)
|
||||
- Carrier_id routing (DPD/UPS/GLS/InPost/Pocztex/...) dziala dla polkuriera automatycznie przez istniejacy `matchCarrierByName($encoded, $carrier)` (carrier_id ustawiany na servicecode z polkuriera, np. "INPOST", "DPD" — pasuje do substring matchu).
|
||||
- Fallback dla provider='polkurier' bez carrier matchu: `https://polkurier.pl/sledz-paczke/<tracking>`.
|
||||
|
||||
### Wiring
|
||||
- `routes/web.php`: `new PolkurierShipmentService(...)` zarejestrowany w `ShipmentProviderRegistry` obok Apaczki/InPost/AllegroWZA.
|
||||
- `src/Modules/Cron/CronHandlerFactory.php`: `new PolkurierTrackingService(...)` w `ShipmentTrackingRegistry` w `shipment_tracking_sync` handler.
|
||||
- `src/Modules/Shipments/ShipmentController.php`: `prepare()` fetchuje `polkurierServices` przez registry i przekazuje do widoku. `create()` rozszerzony o `service_code`/`pickup_date`/`pickup_time_from`/`pickup_time_to` przekazywane do `createShipment()`.
|
||||
|
||||
### UI prepare panel (`resources/views/shipments/prepare.php`)
|
||||
- Opcja "polkurier" w dropdownie `#shipment-carrier-select` (obok Allegro/InPost/Apaczka).
|
||||
- `<div id="shipment-polkurier-panel">` z dynamicznym `<select id="shipment-polkurier-select">` (lista uslug z `available_carriers`).
|
||||
- `<input type="hidden" name="service_code">` synchronizowany z polkurier select przez `syncPolkurierFields()`.
|
||||
- Brak dedykowanego selektora punktu odbioru — operator wpisuje `receiver_point_id` w istniejacy text input w sekcji Adres odbiorcy (np. `POP-RZE54`). Format string `POP-RZE54 | Lukasiewicza 78, 35-604 Rzeszow` z importu zamowienia nie jest parsowany — operator skraca recznie.
|
||||
- JS toggle widocznosci paneli rozszerzony o polkurier; `clearHiddenFields()` czysci `service_code`; `showPanel('polkurier')` ustawia `provider_code='polkurier'`.
|
||||
|
||||
### Rozmiar etykiety A4 vs A6
|
||||
- API polkurier nie udostepnia parametru sterowania rozmiarem etykiety w `get_label` ani `create_order` (zweryfikowane w PDF v1.11).
|
||||
- Domyslny rozmiar ustawiany jest w **panelu klienta polkurier.pl → Ustawienia konta → Preferencje etykiet** (per-konto, globalnie dla wszystkich `get_label` calli).
|
||||
- `polkurier_integration_settings.default_label_format` (PDF/ZPL/EPL) sluzy tylko typowi pliku, NIE rozmiarowi.
|
||||
|
||||
### Seed delivery_status_mappings (`20260514_000115_seed_polkurier_delivery_status_mappings.sql`)
|
||||
- 7 wpisow `provider='polkurier'` (kody z oficjalnej tabeli ORDER_STATUS w PDF v1.11):
|
||||
- `O` → `created` (Oczekuje na platnosc)
|
||||
- `P` → `confirmed` (Potwierdzone, list wygenerowany)
|
||||
- `A` → `cancelled` (Anulowane)
|
||||
- `WP` → `in_transit` (W przewozie)
|
||||
- `D` → `delivered` (Dostarczona)
|
||||
- `Z` → `returned` (Zwrot do nadawcy)
|
||||
- `W` → `problem` (Wyjatek)
|
||||
- Idempotentne: `ON DUPLICATE KEY UPDATE normalized_status / description / updated_at`.
|
||||
|
||||
### Boundaries / co NIE zostalo zmienione
|
||||
- Apaczka (`ApaczkaShipmentService`, `ApaczkaTrackingService`, `apaczka_integration_settings`) niezalezna, dziala obok polkuriera.
|
||||
- `ShipmentProviderInterface` i `ShipmentTrackingInterface` kontrakty niezmienione.
|
||||
- `getInpostParcelMachines`/`getCourierPoints` w API client zaimplementowane ale nieuzywane przez UI w Phase 128 (operator wpisuje punkt recznie).
|
||||
- `cancelOrder` zaimplementowane w API client ale nie wywolywane z UI/cron — operator anuluje w panelu polkuriera.
|
||||
- Brak presetow przesylek dla polkuriera (`shipment_presets.provider_code='polkurier'`) — kolejna faza.
|
||||
|
||||
## Phase 121 - SMSPLANET Conversation + Notifications
|
||||
|
||||
### SmsConversationService (`src/Modules/Sms/SmsConversationService.php`)
|
||||
- Wysyla SMS z poziomu zamowienia przez `SmsplanetApiClient`, dopisuje `default_footer` gdy jest skonfigurowana, zapisuje finalna tresc w `sms_messages` i uzywa `sender_mode` do wyboru nadpisu albo numeru 2WAY.
|
||||
- Parsuje publiczny webhook `/webhooks/smsplanet/inbound`, normalizuje telefony i dopasowuje przychodzacy SMS do najnowszego zamowienia po telefonie klienta/adresu.
|
||||
- Endpoint inbound akceptuje POST i GET; format 2WAY `message=<JSON>` jest dekodowany, sukces zwraca plain `OK`, a dopasowanie zamowienia korzysta z `order_addresses.phone`.
|
||||
- Tworzy `notifications.type='sms_inbound'` z linkiem do `/orders/{id}?tab=sms`.
|
||||
|
||||
### Notifications module
|
||||
- `/notifications` pokazuje historie powiadomien i pozwala oznaczac wpisy jako przeczytane.
|
||||
- `/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.
|
||||
|
||||
## 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 135 — Accounting Net Correctness
|
||||
|
||||
### ReceiptService::buildItemsSnapshot (`src/Modules/Accounting/ReceiptService.php`)
|
||||
- Dla nowych paragonow snapshot nadal zachowuje kontrakt `name`, `quantity`, `price`, `total`, `vat`, `sku`, `ean`.
|
||||
- Metoda zwraca teraz takze `total_net`; `ReceiptService::issue()` zapisuje `receipts.total_net` z sumy netto per linia zamiast kopiowac brutto.
|
||||
- Netto pozycji: `lineGross / (1 + vat/100)` z VAT z `tax_rate`/`vat`; brak stawki oznacza fallback 23.0.
|
||||
- Koszt wysylki pozostaje osobna pozycja "Koszt wysylki" z VAT 23.0; operator zdecydowal, ze historyczne paragony nie sa backfillowane.
|
||||
|
||||
### OrdersStatisticsRepository::netAmountSql (`src/Modules/Statistics/OrdersStatisticsRepository.php`)
|
||||
- Statystyki dzienne preferuja `orders.total_without_tax`, potem `orders.total_net`, jezeli wartosc jest dodatnia.
|
||||
- Gdy net z zamowienia jest pusty, repozytorium liczy fallback z `order_items`: najpierw `original_price_without_tax * quantity`, potem `original_price_with_tax * quantity / (1 + tax_rate/100)`.
|
||||
- Dla pozycji bez VAT fallback stawki wynosi 23.0; dostawa bez osobnej stawki jest doliczana jako `delivery_price / 1.23`.
|
||||
- Stare `gross / 1.23` pozostaje tylko jako ostatni fallback dla legacy zamowien bez uzywalnych pozycji.
|
||||
|
||||
## Phase 120 — Alert Component Unification
|
||||
|
||||
### Alert component (`resources/views/components/alert.php`)
|
||||
- Reusable alert renderer with params: `$type` (info|success|warning|danger; fallback 'info'), `$message` (escaped) lub `$messageHtml` (trusted), `$dismissible` (default true), `$role` ('alert'|'status').
|
||||
- Renders inline SVG icon per type + body + optional dismiss button. Markup: `<div class="alert alert--TYPE" data-alert>...<button data-alert-dismiss>...</button></div>`.
|
||||
- Used via `include __DIR__ . '/../components/alert.php'` po ustawieniu lokalnych `$type/$message/$dismissible`.
|
||||
|
||||
### SCSS — `.alert` w `resources/scss/shared/_ui-components.scss`
|
||||
- `.alert` jest teraz flex (icon + body + dismiss). Dodane: `.alert__icon`, `.alert__body`, `.alert__dismiss`.
|
||||
- Nowy wariant `.alert--info` (blue: border #bfdbfe, bg #eff6ff, color #1e3a8a) — wczesniej brakowal i renderowal sie jako czarny tekst na bialym tle.
|
||||
- Wariantow `--success/--warning/--danger` nie zmieniono kolorystycznie.
|
||||
- Wrapper `.alerts-stack` (gap 8px) do stackowania wielu alertow z layoutu.
|
||||
|
||||
### JS — `public/assets/js/modules/alert-dismiss.js`
|
||||
- Vanilla JS, idempotent guard (`window.__alertDismissBound`).
|
||||
- Delegated click handler na `[data-alert-dismiss]` — usuwa najblizszy `[data-alert]` z DOM bez przeladowania.
|
||||
- Ladowany globalnie w `layouts/app.php`, `layouts/auth.php`, `layouts/public.php`.
|
||||
|
||||
### Flash — `App\Core\Support\Flash` rozszerzenie
|
||||
- Nowa kolejka typowana `$_SESSION['_flash_queue']` z entries `{type, message}`.
|
||||
- `Flash::push(string $type, string $message): void` — append do kolejki (whitelist info/success/warning/danger, fallback info).
|
||||
- `Flash::all(): array` — zwraca i czysci kolejke + skanuje legacy `_flash` (heurystyka klucza: `error/fail/danger` → danger, `warning` → warning, `success/.save/.created/.deleted/.toggled` → success, reszta → info). BC zachowany: `Flash::set/get` dziala bez zmian.
|
||||
|
||||
### Centralny renderer flash w layoutach
|
||||
- `layouts/app.php`, `layouts/auth.php`, `layouts/public.php` na poczatku glownego content area iteruja `Flash::all()` i wlaczaja komponent `alert.php` per wpis (wrap `.alerts-stack`).
|
||||
- Kontrolery NIE wymagaly zmian — pre-fetched `Flash::get('module.key', '')` przekazany do widoku jako lokalna zmienna jest dalej renderowany inline przez widok (przez ten sam komponent). Centralny renderer przejmuje wpisy `Flash::push(...)` oraz nieskonsumowane legacy entries.
|
||||
|
||||
### Migracja widokow
|
||||
- Wszystkie inline `<div class="alert alert--TYPE">...</div>` w widokach (36 plikow razem ze `shipments/prepare.php` i `orders/show.php`) zastapione przez `<?php $type=...; $message=...; $dismissible=...; include dirname(__DIR__) . '/components/alert.php'; ?>`.
|
||||
- `.flash--error` / `.flash--success` w `orders/show.php` i `shipments/prepare.php` zastapione komponentem (klasa `.flash--*` w SCSS pozostaje bez uzycia, deferred cleanup).
|
||||
- Wyjatek: `settings/email-mailboxes.php` ma JS-generowane alerty (`resultDiv.className = 'mt-12 alert alert--success'`) z dynamicznej odpowiedzi AJAX test polaczenia SMTP — uzywaja klas SCSS bez markupu komponentu (out of scope dla tej fazy).
|
||||
|
||||
## Phase 114 — Accounting Configs Refactor
|
||||
|
||||
### Sekcja Ksiegowosc — struktura URL
|
||||
- `/settings/accounting` — hub-rozdroze z 2 kartami: "Paragony" i "Faktury". `ReceiptConfigController::hub()`.
|
||||
- `/settings/accounting/receipts` — lista konfiguracji paragonow. `ReceiptConfigController::list()`.
|
||||
- `/settings/accounting/receipts/new`, `/edit?id=N` — formularz na osobnej podstronie. `ReceiptConfigController::edit()`.
|
||||
- `/settings/accounting/receipts/save|toggle|delete` — POST actions.
|
||||
- **Legacy aliasy:** `/settings/accounting/save|toggle|delete` (POST) zostaja jako duplicate routes (wsteczna kompatybilnosc z `<form action>` w starszych szablonach/bookmarkach).
|
||||
- `/settings/accounting/invoices` + `/new`, `/edit`, `/save`, `/toggle`, `/delete` — analogicznie dla `invoice_configs`. `InvoiceConfigController`.
|
||||
|
||||
### InvoiceConfigRepository (`src/Modules/Settings/InvoiceConfigRepository.php`)
|
||||
- `listAll()` JOIN `invoice_configs LEFT JOIN integrations` (`type='fakturownia'`) — zwraca `integration_name` gdy `is_delegated=1`.
|
||||
- `save(array $data): int` — walidacja serwerowa wszystkich pol. Krytyczna regula: gdy `is_delegated=1` musi byc `integration_id > 0` wskazujacy na `integrations.type='fakturownia'`, inaczej rzuca `IntegrationConfigException`. Gdy `is_delegated=0`, ignoruje `integration_id` (NULL).
|
||||
- `toggleStatus(int $id)` przez `ToggleableRepositoryTrait::toggleActive()`.
|
||||
- `delete(int $id)` — pre-check `SELECT 1 FROM invoices WHERE config_id` zeby zwrocic czytelny PL komunikat zamiast brzydkiego SQLSTATE z FK RESTRICT.
|
||||
|
||||
### Seed
|
||||
- Migracja `20260511_000107_seed_default_invoice_config.sql` — idempotentny insert `Domyslny VAT` (NOT EXISTS guard, `invoice_configs.name` nie jest UNIQUE).
|
||||
|
||||
### invoice-config-form.js (`public/assets/js/modules/invoice-config-form.js`)
|
||||
- Vanilla JS modul ladowany globalnie przez `layouts/app.php`.
|
||||
- Toggle widocznosci `[data-invoice-delegation]` wrappera w zaleznosci od stanu `[data-invoice-delegated]` checkboxa.
|
||||
- Ustawia `select[name=integration_id].required` zgodnie ze stanem checkboxa; przy unchecked czysci `value`.
|
||||
|
||||
### Ujednolicony wyglad list paragonow/faktur
|
||||
- Tabela `table.table` w `table-wrap`, badge `badge--{success,muted}` na statusy.
|
||||
- Edycja przez `<a href=".../edit?id=N">`, toggle/delete przez `<form>` z `_token` i `js-confirm-delete`.
|
||||
- Wspolny pattern miedzy `accounting-receipts.php` i `accounting-invoices.php` (faktury maja dodatkowe kolumny: Tryb, Konto Fakturowni).
|
||||
|
||||
|
||||
## Phase 124 — SMS Templates
|
||||
|
||||
### SmsTemplateRepository (`src/Modules/Sms/SmsTemplateRepository.php`)
|
||||
- CRUD na `sms_templates` (PDO prepared statements, ToggleableRepositoryTrait).
|
||||
- `listAll()` (cala lista alfabetycznie po `name`), `listActive()` (tylko is_active=1, kolumny `id|name|body` do dropdownu w UI).
|
||||
- `save(array): int` waliduje wymagane `name` + `body` (rzuca `RuntimeException` gdy puste); wykonuje INSERT albo UPDATE wg obecnosci `id` w payloadzie; zwraca id rekordu.
|
||||
- `delete(int)`, `toggleStatus(int)` przez `toggleActive('sms_templates', $id)`.
|
||||
|
||||
### SmsVariableResolver (`src/Modules/Sms/SmsVariableResolver.php`)
|
||||
- Wydzielony z `Email\VariableResolver` — wspolna logika zmiennych dla Email i SMS.
|
||||
- `buildVariableMap(order, addresses, companySettings)` zwraca mape placeholderow: `zamowienie.*`, `kupujacy.*`, `adres.*`, `firma.*`, `przesylka.*` (`przesylka.numer`/`przesylka.link_sledzenia` z najnowszej paczki przez `ShipmentPackageRepository::findLatestByOrderId` + `DeliveryStatus::trackingUrl`).
|
||||
- `resolve(template, variableMap)` zastepuje `{{group.var}}` wartoscia z mapy (puste gdy brak klucza).
|
||||
|
||||
### Email\VariableResolver (refaktor)
|
||||
- Pozostaje final class z tym samym API publicznym (`buildVariableMap`/`resolve`) — `EmailSendingService` niezmieniony.
|
||||
- Konstruktor: `(ShipmentPackageRepository $repo, ?SmsVariableResolver $inner = null)`. Gdy `$inner` nie podany, sam tworzy SmsVariableResolver — backward compat dla starego wiringu.
|
||||
- Metody publiczne deleguja do `$this->inner` — zero duplikacji logiki zmiennych.
|
||||
|
||||
### SmsTemplateController (`src/Modules/Settings/SmsTemplateController.php`)
|
||||
- Mirror `EmailTemplateController` bez Quill/skrzynki/zalacznika/duplikacji.
|
||||
- Akcje: `index` (lista), `create`/`edit`/`save` (form CRUD), `delete`, `toggleStatus` (AJAX JSON), `getVariables` (JSON paleta dla ewentualnego dynamic palette).
|
||||
- `VARIABLE_GROUPS` jako stala klasy — pelne 5 grup (zamowienie/kupujacy/adres/firma/przesylka) zgodnie ze wspolnym SmsVariableResolver.
|
||||
- Routy: `/settings/sms-templates`, `/create`, `/edit`, `/save`, `/delete`, `/toggle`, `/variables`. CSRF `_token` na POST. Flash `settings.sms_templates.success|error`.
|
||||
|
||||
### OrdersController (rozszerzenie)
|
||||
- Dodane optional params konstruktora: `?SmsTemplateRepository $smsTemplates`, `?SmsVariableResolver $smsVariableResolver`, `?CompanySettingsRepository $companySettingsRepo` (po istniejacych SMS params; default null = backward compat).
|
||||
- `show()` przekazuje `$smsTemplates` (list active) do widoku jako `smsTemplates`.
|
||||
- Nowa metoda `smsTemplate(Request)` -> `GET /orders/{id}/sms/template?template_id=N` -> JSON `{ok, body, name}` z rozwinietymi zmiennymi. 400/404/500 dla nieprawidlowych parametrow/braku rekordu.
|
||||
|
||||
### Widok `orders/show.php`
|
||||
- Nad textarea `name="message"` (`#js-sms-message`) dodany conditional `<select data-sms-template-picker data-order-id data-message-target="js-sms-message">` z opcja domyslna + aktywne szablony (renderowany tylko gdy `$smsTemplatesList !== []`).
|
||||
- Textarea ma teraz `id="js-sms-message"` — JS target.
|
||||
|
||||
### Frontend module `public/assets/js/modules/sms-template-picker.js`
|
||||
- Vanilla JS, idempotent guard `window.__smsTemplatePickerBound` + per-element `dataset.smsPickerBound`.
|
||||
- Na `change` selecta: fetch `/orders/{id}/sms/template?template_id=N`, podstaw body do textarea, fire `input` event.
|
||||
- Gdy textarea ma juz tresc -> `OrderProAlerts.confirm({...})` options-object API (Phase 114 pattern). Po zatwierdzeniu nadpisuje, po anulowaniu resetuje select. Fallback na natywny `confirm()`.
|
||||
- Ladowany globalnie z `layouts/app.php` (linia po `notifications.js`).
|
||||
|
||||
### Wspolny resolver — wiring DI (`routes/web.php`)
|
||||
- `$smsVariableResolver = new SmsVariableResolver($shipmentPackageRepositoryForOrders);`
|
||||
- `$variableResolver = new VariableResolver($shipmentPackageRepositoryForOrders, $smsVariableResolver);` (drugi argument opcjonalny dla BC).
|
||||
- `$smsTemplateRepository = new SmsTemplateRepository($app->db());`
|
||||
- `$smsTemplateController = new SmsTemplateController($template, $translator, $auth, $smsTemplateRepository);`
|
||||
- `$ordersController` rozszerzony o 3 trailing params (smsTemplateRepository, smsVariableResolver, companySettingsRepository).
|
||||
|
||||
### SCSS — `_sms-templates.scss`
|
||||
- Nowy partial `resources/scss/modules/_sms-templates.scss` z klasami `.sms-template-*` (active label, counter, body grid) oraz `.sms-var-panel/.sms-var-group/.sms-var-item` dla palety zmiennych.
|
||||
- Import w `app.scss` po `customer-risk-alert`.
|
||||
|
||||
### Stopka — preserved Phase 122 contract
|
||||
- Szablony SMS NIE zawieraja `default_footer` — operator wpisuje sama tresc.
|
||||
- `SmsConversationService::buildFinalOutboundBody()` dokleja stopke raz przy `sendFromOrder()` (po wstawieniu szablonu i ewentualnej edycji przez operatora). Walidacja `MAX_SMS_LENGTH = 918` obowiazuje na finalnej tresci.
|
||||
|
||||
### BREAKING / migration
|
||||
- Migracja `20260512_000112_create_sms_templates.sql` — `CREATE TABLE IF NOT EXISTS sms_templates` (DDL, brak SELECT no-op).
|
||||
- Brak innych zmian schematu. `OrdersController` ctor: 3 NEW optional params (default null) — backwards compatible.
|
||||
`public/index.php` -> `bootstrap/app.php` -> `src/Core/Application.php` -> `routes/web.php` -> `src/Core/Routing/Router.php` -> middleware -> controller -> service/repository -> `src/Core/View/Template.php` -> response.
|
||||
|
||||
## Core Layers
|
||||
|
||||
- `src/Core/` contains application boot, routing, request/response, database connection/migrations, sessions, CSRF, flash, views, translation, logging, and HTTP helpers.
|
||||
- `routes/web.php` owns route registration and constructor wiring; there is no route auto-discovery.
|
||||
- `src/Modules/*` contains feature modules with controllers, services, repositories, API clients, and cron handlers.
|
||||
- `resources/views/` contains native PHP templates and shared components.
|
||||
- `resources/scss/` contains SCSS sources; compiled CSS is under `public/assets/css/`.
|
||||
- `public/assets/js/modules/` contains browser modules loaded by layouts/views.
|
||||
|
||||
## Main Modules
|
||||
|
||||
- Auth/session: `src/Modules/Auth/`.
|
||||
- Users: `src/Modules/Users/`.
|
||||
- Orders: `src/Modules/Orders/`.
|
||||
- Statistics: `src/Modules/Statistics/`.
|
||||
- Settings/integrations/mappings: `src/Modules/Settings/`.
|
||||
- Shipments/tracking/delivery statuses: `src/Modules/Shipments/`.
|
||||
- Accounting/receipts/invoices: `src/Modules/Accounting/`.
|
||||
- Email: `src/Modules/Email/`.
|
||||
- Automation: `src/Modules/Automation/`.
|
||||
- Cron: `src/Modules/Cron/`.
|
||||
- Printing: `src/Modules/Printing/`.
|
||||
- SMS: `src/Modules/Sms/`.
|
||||
- Notifications: `src/Modules/Notifications/`.
|
||||
- Info pages: `src/Modules/Info/`.
|
||||
|
||||
## Key Flows
|
||||
|
||||
- Order import: cron/manual integration controller -> marketplace sync service -> `src/Modules/Orders/OrderImportRepository.php` -> order aggregate tables -> automation.
|
||||
- Order UI: order route in `routes/web.php` -> `src/Modules/Orders/OrdersController.php` -> `src/Modules/Orders/OrdersRepository.php` -> `resources/views/orders/`.
|
||||
- Shipment creation: route -> `src/Modules/Shipments/ShipmentController.php` -> provider registry/service -> `src/Modules/Shipments/ShipmentPackageRepository.php` -> tracking sync.
|
||||
- Accounting: route -> `src/Modules/Accounting/ReceiptService.php` or `src/Modules/Accounting/InvoiceService.php` -> repository -> PDF/export/email flow.
|
||||
- Cron: `bin/cron.php` or web cron -> `src/Modules/Cron/CronHandlerFactory.php` -> `src/Modules/Cron/CronRunner.php` -> handler class.
|
||||
- View rendering: controller -> `src/Core/View/Template.php` -> layout in `resources/views/layouts/` -> view/component.
|
||||
|
||||
## Architectural Hotspots
|
||||
|
||||
- `routes/web.php` is large and central; route changes can affect DI wiring for many modules.
|
||||
- `src/Modules/Settings/` is the largest boundary and mixes integration configs, API clients, mapping controllers, and sync services.
|
||||
- `src/Modules/Orders/OrdersController.php` and `src/Modules/Orders/OrdersRepository.php` are central to most user workflows.
|
||||
- `resources/views/orders/show.php` and `resources/views/shipments/prepare.php` are large view surfaces with embedded JS behavior.
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
# Technical Concerns & Debt
|
||||
|
||||
## Status audytu Phase 134 (2026-05-16)
|
||||
|
||||
Szczegoly i dowody: `.paul/phases/134-backlog-reality-check/BACKLOG-AUDIT.md`.
|
||||
|
||||
| Group / item | Status po audycie | Krotki wniosek |
|
||||
|--------------|-------------------|----------------|
|
||||
| God Classes | **Active** | Klasy nadal sa duze; stare LOC/method counts sa nieaktualne, ale Phase 141 pozostaje zasadny. |
|
||||
| SonarQube Issues | **Fresh baseline / active patterns** | Phase 139 odswiezyl baseline i zredukowal dwie fale issue; pozostale grupy sa aktualne po skanie 139-02 z 2026-05-17. |
|
||||
| Breaking: delivery status group keys | **Closed in Phase 137** | DB-driven statusy sa wdrozone, a read-only DB check nie znalazl starych ani niepoprawnych kluczy automatyzacji. |
|
||||
| Breaking: `SHIPMENT_STATUS_OPTION_MAP` | **Implemented / stale** | Symbol nie wystepuje juz w runtime source. |
|
||||
| Breaking: `_csrf_token` -> `_token` | **Implemented / stale** | Formularze/kontrolery uzywaja `_token`; wewnetrzny session key w `Csrf` nie jest problemem formularzy. |
|
||||
| Known Bugs: `STAT-NET` | **Resolved in Phase 135** | Runtime statystyk liczy net z source-level net albo item-level VAT; `RECEIPT-NET-FIX` naprawiony dla nowych paragonow bez backfillu historii. |
|
||||
| Deferred Indexes | **Active / deferred** | Indeksy nadal nie sa w migracjach; wykonac po decyzji operatora w Phase 140. |
|
||||
| Security: print API keys | **Implemented / stale** | Przechowywany jest hash i prefix, nie raw `api_key`. |
|
||||
| Security: mailbox TLS | **Resolved in Phase 138** | Test SMTP uzywa strict peer/name verification dla `ssl` i STARTTLS; self-signed/unverified tylko przez lokalny `SMTP_ALLOW_SELF_SIGNED_DEV`. |
|
||||
| Security: template variables | **Resolved in Phase 138** | Nowe/edytowane szablony e-mail/SMS blokuja nieznane `{{grupa.zmienna}}` przez wspolny `TemplateVariableCatalog`. |
|
||||
| Architecture Concerns | **Active / low impact** | Zostawic do decyzji w Phase 142. |
|
||||
| Duplication Areas | **Mixed** | `SslCertificateResolver` i `RedirectPathResolver` sa czesciowo wdrozone; reszta wymaga selektywnej decyzji. |
|
||||
| Legacy patterns | **Mostly resolved in Phase 139-02** | Raw `$_SESSION` jest izolowany w `Session`; targetowane hard `require`/alert includes i inline FQCN w widokach sa usuniete przez `$component()` i lokalne importy. |
|
||||
| Performance Risks | **Active / needs profiling** | Return-risk indexes i cron backoff aktywne; `findDetails()` najpierw profilowac. |
|
||||
|
||||
## God Classes (Priority Refactor Targets)
|
||||
|
||||
| Class | LOC | Methods | Issue |
|
||||
|-------|-----|---------|-------|
|
||||
| `src/Modules/Orders/OrdersRepository.php` | 1,221 | 29 | Query building spread across 29 methods; SonarQube S1448 |
|
||||
| `src/Modules/Orders/OrdersController.php` | 1,187 | 22 | UI + AJAX + list + detail + search combined; S1448 |
|
||||
| `src/Modules/Automation/AutomationService.php` | 834 | 24 | All action handlers in one class; S1448 |
|
||||
| `src/Modules/Settings/ShopproOrderMapper.php` | 867 | 25 | 25+ transformation methods; S1448 |
|
||||
| `src/Modules/Statistics/OrdersStatisticsRepository.php` | 901 | 43 | Reporting SQL, schema detection and row mapping combined; S1448 remains after Phase 139-01 |
|
||||
| `src/Modules/Settings/ApaczkaShipmentService.php` | 1,044 | — | API payload deeply nested |
|
||||
| `src/Modules/Settings/ShopproIntegrationsController.php` | 1,044 | — | OAuth + mapping + sync combined |
|
||||
|
||||
**Planned fix:** Phase 68 (Code Deduplication Refactor) — deferred, never started.
|
||||
|
||||
## SonarQube Issues (new code since 2026-03-28)
|
||||
|
||||
Fresh Phase 139 baseline after plan 139-02: **495 OPEN BLOCKER/CRITICAL/MAJOR issues** (BLOCKER=0, CRITICAL=178, MAJOR=317).
|
||||
|
||||
| Rule | Count | Severity | Examples |
|
||||
|------|-------|----------|---------|
|
||||
| `php:S1142` — Excess return statements | 148 | MAJOR | Many service/controller methods still have 4+ returns |
|
||||
| `php:S1192` - Duplicated string literals | 98 | CRITICAL | Route paths, SQL fragments, status strings, HTTP headers |
|
||||
| `php:S4833` - Use namespace import / direct include patterns | 3 | MAJOR | Remaining issues are core framework `require` calls in Application/Translator/Template |
|
||||
| `php:S3776` — Cognitive complexity > 15 | 54 | CRITICAL | Mapper/service/reporting methods needing focused refactor |
|
||||
| `php:S1172` — Unused parameters | 41 | MAJOR | Handler payload/request params |
|
||||
| `php:S112` - Generic exceptions | 23 | MAJOR | Remaining generic exceptions outside selected compact Settings/Automation clusters |
|
||||
| `php:S1448` — Class too large | 16 | MAJOR | See god classes above |
|
||||
| `php:S4423` — Weak TLS protocol | stale | **CRITICAL** | Resolved in Phase 138: `EmailMailboxController::testConnection()` uzywa strict SSL context i STARTTLS |
|
||||
| `Web:TableHeaderHasIdOrScopeCheck` | 16 | MAJOR | Tables without explicit header scope/id |
|
||||
| `Web:S6819` — Accessibility | 5 | MAJOR | Use semantic output/status elements where applicable |
|
||||
|
||||
Phase 139-01 reduced the fresh total by 43 issues and cleared all selected delivery-status files. Phase 139-02 reduced the post-139-01 total by 110, mainly through broad `$component()` alert rendering and typed exceptions. Remaining detailed baseline: `.paul/phases/139-sonar-critical-major-cleanup/SONAR-BASELINE.md`.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
| Change | Phase | Impact | Migration |
|
||||
|--------|-------|--------|-----------|
|
||||
| Delivery status group keys przeniesione do DB | Phase 108 (2026-04-27), verified Phase 137 (2026-05-17) | Stare reguly automation z kluczami `registered`, `courier_pickup`, `dropped_at_point`, `unclaimed`, `picked_up_return` byly ryzykiem po breaking change | Zamkniete: read-only DB check znalazl 0 starych i 0 niepoprawnych kluczy |
|
||||
| `SHIPMENT_STATUS_OPTION_MAP` usunięty | Phase 108 (2026-04-27) | `AutomationService` porównuje klucze statusów bezpośrednio z DB | Brak wpływu po odtworzeniu reguł |
|
||||
| `_csrf_token` → `_token` | Phase 105 (2026-04-19) | Stare nazwy pól formularzy | Sprawdzić czy nie ma starych referencji `_csrf_token` w widokach |
|
||||
|
||||
## Known Bugs & Issues
|
||||
|
||||
| Issue | Location | Status |
|
||||
|-------|----------|--------|
|
||||
| `STAT-NET`: hardcoded 23% VAT fallback for net calculations | `src/Modules/Statistics/OrdersStatisticsRepository.php` | Fixed in Phase 135; gross `/1.23` remains only as legacy fallback without usable items |
|
||||
| Missing net amounts for shopPRO orders | `.paul/codebase/todo.md` (STAT-NET) | Runtime fallback fixed in Phase 135 through `order_items` VAT calculation |
|
||||
| `order.status_aged` condition fallback | `AutomationService` | Fixed 2026-04-25 |
|
||||
|
||||
## Deferred Indexes (Phase 106)
|
||||
|
||||
After Phase 106 (customer return alert), two indexes should be added before dataset exceeds ~50k orders:
|
||||
|
||||
```sql
|
||||
-- INDEX-106-01 (deferred in SUMMARY.md)
|
||||
CREATE INDEX idx_order_addresses_order_type ON order_addresses(order_id, address_type);
|
||||
CREATE INDEX idx_shipment_packages_order_delivery ON shipment_packages(order_id, delivery_status);
|
||||
```
|
||||
|
||||
These support the correlated subquery in `OrdersRepository` used for return-risk detection.
|
||||
|
||||
## Security Items to Verify
|
||||
|
||||
| Item | Risk | Action |
|
||||
|------|------|--------|
|
||||
| `print_api_keys.api_key` encryption | MEDIUM | Verify column is encrypted (same as `integrations.api_key_encrypted`) |
|
||||
| SMTP TLS in `EmailMailboxController::testConnection()` | MEDIUM | Resolved in Phase 138: strict certificate verification by default; local dev override via `SMTP_ALLOW_SELF_SIGNED_DEV`. |
|
||||
| Email/SMS variable injection via `{{var}}` templates | LOW | Resolved in Phase 138: `TemplateVariableCatalog` blocks unknown placeholders on save. |
|
||||
|
||||
## Architecture Concerns
|
||||
|
||||
| Concern | Impact | Notes |
|
||||
|---------|--------|-------|
|
||||
| No repository interfaces | LOW | Cannot mock repositories cleanly in tests without `bypass-finals` workaround |
|
||||
| String-typed event/action names | LOW | `event_type = 'order.status_changed'` — typos not caught at compile time |
|
||||
| No validation layer | LOW | Validation scattered across controllers and repositories |
|
||||
| No HTTP caching headers | LOW | Responses don't set ETag, Cache-Control; acceptable for low-concurrency use |
|
||||
| No query caching | LOW | Every request re-queries; no Redis/Memcached layer |
|
||||
|
||||
## Duplication Areas (Phase 68 scope)
|
||||
|
||||
- `SslCertificateResolver` — pattern duplicated across multiple API client files
|
||||
- `ToggleableRepositoryTrait` — mixes query building + toggling
|
||||
- `RedirectPathResolver` — similar redirect logic in 4+ controllers
|
||||
- `ReceiptService` vs accounting logic — overlapping responsibilities
|
||||
|
||||
## Legacy / Deprecated Patterns
|
||||
|
||||
| Pattern | Location | Status |
|
||||
|---------|----------|--------|
|
||||
| `fsockopen('ssl://')` / weak SMTP TLS | `EmailMailboxController::testConnection()` | Resolved in Phase 138; strict stream context + STARTTLS, local dev override only. |
|
||||
| `require` / direct alert includes in targeted views | `resources/views/...` targeted by Phase 139-02 | Resolved through `$component()` helper. Remaining `php:S4833` issues are core framework file loading, not alert components. |
|
||||
| Raw `$_SESSION` access | Auth/Flash/Csrf/OAuth before Phase 138 | Resolved in Phase 138; raw access is isolated in `App\Core\Support\Session`. |
|
||||
|
||||
## Performance Risks
|
||||
|
||||
1. **Correlated subquery for return-risk** (per-row COUNT) — slow at >50k orders without `INDEX-106-01`
|
||||
2. **N+1 potential in order details** — `findDetails()` queries orders + addresses + items separately (not verified as actual problem)
|
||||
3. **Cron queue growth** — no exponential backoff if queue grows; may pile up on slow syncs
|
||||
@@ -1,183 +1,37 @@
|
||||
# Conventions & Patterns
|
||||
# Conventions
|
||||
|
||||
## Naming
|
||||
Last refresh: 2026-05-18.
|
||||
|
||||
| Element | Convention | Example |
|
||||
|---------|-----------|---------|
|
||||
| Classes | PascalCase | `OrdersController`, `AllegroApiClient` |
|
||||
| Methods / variables | camelCase | `findDetails()`, `$statusCode` |
|
||||
| Constants | UPPER_SNAKE_CASE | `SESSION_KEY`, `OPTION_KEYS` |
|
||||
| DB columns | snake_case | `source_order_id`, `payment_status` |
|
||||
| PHP files | Match class name | `OrdersController.php` |
|
||||
| View files | kebab-case | `table-list.php`, `order-status-panel.php` |
|
||||
| SCSS partials | `_kebab-case.scss` | `_automation.scss` |
|
||||
| No abbreviations | Full names | `$translatedText` not `$t` (except loop indices) |
|
||||
## PHP
|
||||
|
||||
## Code Constraints (CLAUDE.md)
|
||||
- App code lives under `src/` with PSR-4 namespace `App\\`; tests use `Tests\\` from `tests/`.
|
||||
- Use `declare(strict_types=1);` and `final class` for PHP classes, matching files such as `src/Core/View/Template.php`.
|
||||
- Naming: classes PascalCase, methods/variables camelCase, constants UPPER_SNAKE_CASE, DB columns snake_case, view filenames kebab-case.
|
||||
- Keep controllers thin: parse `Request`, validate, call repository/service, prepare view data, return `Response`.
|
||||
- Keep data access in repositories/services and use prepared statements for dynamic values.
|
||||
- Do not wire `DB_HOST_REMOTE` into runtime; runtime database config uses `DB_HOST`.
|
||||
|
||||
- Max **~50 lines** per method; longer → split
|
||||
- Max **3 nesting levels** (if/foreach); deeper → extract to method
|
||||
- Single Responsibility: one class = one job
|
||||
- All classes are `final` (no accidental inheritance)
|
||||
- `declare(strict_types=1)` in every file
|
||||
- Comments only for **WHY**, never for WHAT
|
||||
## Views and UI
|
||||
|
||||
## Database Pattern
|
||||
- Views are native PHP under `resources/views/`.
|
||||
- Escape output in views with `$e()`.
|
||||
- Use `$t()` for translations where applicable.
|
||||
- Reuse PHP components through `$component()` from `src/Core/View/Template.php`; shared components live in `resources/views/components/`.
|
||||
- Put styles in SCSS under `resources/scss/` or module sources under `resources/modules/`; compiled assets belong in `public/assets/`.
|
||||
- Use `window.OrderProAlerts` from `resources/modules/jquery-alerts` / `public/assets/js/modules/jquery-alerts.js` for alerts and confirmations.
|
||||
- Treat inline styles/scripts in older views such as `resources/views/orders/show.php`, `resources/views/shipments/prepare.php`, and `resources/views/receipts/print.php` as legacy, not a pattern for new work.
|
||||
|
||||
**PDO prepared statements only — no ORM, no string concatenation.**
|
||||
## Configuration and Security
|
||||
|
||||
```php
|
||||
// Correct
|
||||
$stmt = $pdo->prepare('SELECT * FROM orders WHERE id = :id');
|
||||
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
- Environment parsing is in `src/Core/Support/Env.php`.
|
||||
- CSRF handling is in `src/Core/Security/Csrf.php`; forms should include valid CSRF tokens.
|
||||
- Session behavior is handled through `src/Core/Support/Session.php` and auth middleware.
|
||||
- SMTP TLS verification is the default; `SMTP_ALLOW_SELF_SIGNED_DEV=true` is dev-only.
|
||||
|
||||
// Never
|
||||
$pdo->query("SELECT * FROM orders WHERE id = $id"); // forbidden
|
||||
```
|
||||
## Reusable Patterns
|
||||
|
||||
- `ATTR_EMULATE_PREPARES = false` (real server-side preparation)
|
||||
- `ATTR_ERRMODE = ERRMODE_EXCEPTION`
|
||||
- Parameter type hints: `PDO::PARAM_INT` for integers
|
||||
|
||||
## Security Patterns
|
||||
|
||||
### CSRF
|
||||
|
||||
```php
|
||||
// Generate (in controller)
|
||||
'csrfToken' => Csrf::token() // stores in $_SESSION['_csrf_token']
|
||||
|
||||
// In view
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken) ?>">
|
||||
|
||||
// Validate (in controller)
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) { ... }
|
||||
```
|
||||
|
||||
Field name is always `_token`. Uses `hash_equals()` for timing-safe comparison.
|
||||
|
||||
### XSS Escaping
|
||||
|
||||
All user-controlled output escaped with `$e()` helper (available in all views):
|
||||
|
||||
```php
|
||||
$e = fn(mixed $v): string => htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// Usage
|
||||
<?= $e($order['customer_name']) ?>
|
||||
<?= $e($t('orders.status.label')) ?>
|
||||
```
|
||||
|
||||
**Never** output raw variables without `$e()`.
|
||||
|
||||
### Session
|
||||
|
||||
Configured with: `cookie_httponly=true`, `cookie_secure=true`, `cookie_samesite=Lax`, `use_strict_mode=true`.
|
||||
Access via `Session::get()` / `Session::set()` helpers — not raw `$_SESSION` in business logic.
|
||||
|
||||
## Controller Pattern
|
||||
|
||||
```php
|
||||
final class OrdersController {
|
||||
public function __construct(
|
||||
private readonly Template $template,
|
||||
private readonly Translator $translator,
|
||||
private readonly OrdersRepository $orders,
|
||||
// ...
|
||||
) {}
|
||||
|
||||
public function index(Request $request): Response {
|
||||
// 1. Parse & validate input
|
||||
$filters = ['search' => trim((string) $request->input('search', ''))];
|
||||
|
||||
// 2. Call repository
|
||||
$result = $this->orders->paginate($filters);
|
||||
|
||||
// 3. Prepare view data
|
||||
$rows = array_map(fn($row) => $this->toTableRow($row), $result['items']);
|
||||
|
||||
// 4. Render
|
||||
return Response::html(
|
||||
$this->template->render('orders/index', ['rows' => $rows], 'layouts/app')
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## View Pattern
|
||||
|
||||
Views use two magic helpers injected by `Template::renderFile()`:
|
||||
- `$e($value)` — HTML-escape
|
||||
- `$t($key, $replace)` — translate
|
||||
|
||||
Layout composition:
|
||||
```php
|
||||
$this->template->render('orders/index', $data, 'layouts/app')
|
||||
// renders views/orders/index.php, wraps in views/layouts/app.php via $content
|
||||
```
|
||||
|
||||
## UI Rules
|
||||
|
||||
### Alerts & Confirmations
|
||||
- **Always** use `window.OrderProAlerts.confirm({message, onConfirm})` from `jquery-alerts.js`
|
||||
- **Never** use native `alert()` or `confirm()`
|
||||
|
||||
### CSS / SCSS
|
||||
- All styles go in `resources/scss/` — never inline `<style>` or `style=""` attributes in PHP templates
|
||||
- CSS custom properties for dynamic colors: `style="--status-color: <?= $e($color) ?>"` → used via `var(--status-color)` in SCSS
|
||||
- Build: `npm run build:assets`
|
||||
- UI must be **compact** — maximize info density, minimize whitespace
|
||||
|
||||
### Reusable Components
|
||||
- Extract repeated UI blocks to `resources/views/components/`
|
||||
- Current components: `table-list.php`, `order-status-panel.php`
|
||||
- Changes to a component must be verified in **all** places it is used
|
||||
|
||||
## Flash Messages
|
||||
|
||||
```php
|
||||
// Set (in controller)
|
||||
Flash::set('error', $this->translator->get('auth.errors.csrf_expired'));
|
||||
return Response::redirect('/login');
|
||||
|
||||
// Read (in view)
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger"><?= $e($errorMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
## Exception Hierarchy
|
||||
|
||||
```
|
||||
OrderProException (base)
|
||||
├── AllegroApiException
|
||||
├── AllegroOAuthException
|
||||
├── ApaczkaApiException
|
||||
├── IntegrationConfigException
|
||||
└── ShipmentException
|
||||
```
|
||||
|
||||
Throw specific domain exceptions, not generic `\Exception`.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Global exception handler in `Application::registerErrorHandlers()`:
|
||||
- Always logs to `storage/logs/app.log` with JSON context
|
||||
- Shows `message` in debug mode, `"Internal server error"` in production
|
||||
|
||||
Log format: `[2026-04-26 14:30:00] ERROR message {"context":"value"}`
|
||||
|
||||
## Routing Convention
|
||||
|
||||
```php
|
||||
// Public
|
||||
$router->get('/login', [AuthController::class, 'showLogin']);
|
||||
$router->post('/login', [AuthController::class, 'login']);
|
||||
|
||||
// Authenticated
|
||||
$router->get('/orders', [OrdersController::class, 'index'], [$authMiddleware]);
|
||||
|
||||
// JSON API with API key
|
||||
$router->post('/api/print-jobs', [PrintApiController::class, 'store'], [$apiKeyMiddleware]);
|
||||
```
|
||||
- Alert/confirmation module source: `resources/modules/jquery-alerts/jquery-alerts.js`.
|
||||
- Shared delete confirm wrapper: `public/assets/js/modules/confirm-delete.js`.
|
||||
- Reusable table/list component: `resources/views/components/table-list.php`.
|
||||
- Shared order status panel: `resources/views/components/order-status-panel.php`.
|
||||
- Template helpers are injected by `src/Core/View/Template.php`.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
56
.paul/codebase/impact_map.md
Normal file
56
.paul/codebase/impact_map.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Impact Map
|
||||
|
||||
Last refresh: 2026-05-18.
|
||||
|
||||
## Orders
|
||||
|
||||
- Controllers/repositories: `src/Modules/Orders/OrdersController.php`, `src/Modules/Orders/OrdersRepository.php`, `src/Modules/Orders/OrderImportRepository.php`, `src/Modules/Orders/OrderNotesService.php`.
|
||||
- Views: `resources/views/orders/show.php`, `resources/views/orders/list.php`, `resources/views/orders/partials/`.
|
||||
- Frontend modules: `public/assets/js/modules/inline-status-change.js`, `public/assets/js/modules/order-notes.js`.
|
||||
- Data: `orders`, `order_items`, `order_addresses`, `order_payments`, `order_notes`, `order_activity_log`.
|
||||
- Verification: affected PHPUnit service/repository tests plus manual list/detail UI smoke.
|
||||
|
||||
## Shipments
|
||||
|
||||
- Controllers/services: `src/Modules/Shipments/ShipmentController.php`, `src/Modules/Shipments/*ShipmentService.php`, `src/Modules/Shipments/*TrackingService.php`.
|
||||
- Registries/interfaces: `src/Modules/Shipments/ShipmentProviderRegistry.php`, `src/Modules/Shipments/ShipmentProviderInterface.php`, `src/Modules/Shipments/ShipmentTrackingRegistry.php`.
|
||||
- Views: `resources/views/shipments/prepare.php`, carrier settings views under `resources/views/settings/`.
|
||||
- Data: `shipment_packages`, `shipment_presets`, `delivery_statuses`, `delivery_status_mappings`, carrier mapping tables.
|
||||
- Verification: provider-specific tests and manual create/delete/tracking UI smoke.
|
||||
|
||||
## Integrations and Settings
|
||||
|
||||
- Surface: `src/Modules/Settings/*Integration*`, `src/Modules/Settings/*ApiClient.php`, `src/Modules/Settings/*SyncService.php`, `src/Modules/Settings/*MappingRepository.php`.
|
||||
- Routes/wiring: `routes/web.php`.
|
||||
- Views: `resources/views/settings/*.php`.
|
||||
- Data: `integrations`, provider settings tables, mapping tables, sync-state tables.
|
||||
- Verification: target service tests, settings form CSRF/session smoke, external API dry-run where available.
|
||||
|
||||
## Accounting
|
||||
|
||||
- Controllers/services/repositories: `src/Modules/Accounting/*`, `src/Modules/Settings/Fakturownia*`, `src/Modules/Settings/InvoiceConfig*`, `src/Modules/Settings/ReceiptConfig*`.
|
||||
- Views: `resources/views/accounting/*.php`, `resources/views/receipts/*.php`, accounting settings views.
|
||||
- Data: `receipt_configs`, `receipts`, `receipt_number_counters`, `invoice_configs`, `invoices`, `invoice_number_counters`.
|
||||
- Verification: receipt/invoice PHPUnit tests, PDF/export smoke, invoice idempotency checks.
|
||||
|
||||
## Cron and Automation
|
||||
|
||||
- Cron: `bin/cron.php`, `src/Modules/Cron/*`, `routes/web.php`.
|
||||
- Automation: `src/Modules/Automation/*`, `resources/views/automation/*.php`.
|
||||
- Data: `cron_jobs`, `cron_schedules`, `automation_rules`, `automation_conditions`, `automation_actions`, `automation_execution_logs`, `automation_email_once_deliveries`.
|
||||
- Verification: target handler/service test plus manual cron dry-run in local/dev.
|
||||
|
||||
## Frontend Assets
|
||||
|
||||
- Sources: `resources/scss/`, `resources/modules/jquery-alerts/`.
|
||||
- Compiled CSS/JS: `public/assets/css/`, `public/assets/js/modules/`.
|
||||
- Verification: `npm run build:assets` and manual affected-view check.
|
||||
|
||||
## Polish UI Copy / Translations
|
||||
|
||||
- Translation source: `resources/lang/pl.php`, loaded through `src/Core/I18n/Translator.php` and exposed to views as `$t()` by `src/Core/View/Template.php`.
|
||||
- View surfaces: `resources/views/orders/`, `resources/views/settings/`, `resources/views/accounting/`, `resources/views/automation/`, `resources/views/layouts/`, and shared components.
|
||||
- Frontend copy: `public/assets/js/modules/*.js`, especially alert/confirm/status messages.
|
||||
- Backend operator messages: controllers/services under `src/Modules/*` that write flash messages or JSON errors shown in UI.
|
||||
- Verification: PHP lint for touched files, residual search for mojibake/common ASCII Polish forms, `git diff --check`, and manual smoke of main UI pages.
|
||||
- Apply result 2026-05-18: normalized Polish copy across the translation file, legacy views, public JS modules and selected module messages; no DB schema, route, API payload or template-placeholder contract changes.
|
||||
@@ -1,42 +0,0 @@
|
||||
# orderPRO — Codebase Map
|
||||
|
||||
**Generated:** 2026-04-28 | **Version:** 3.2.0 | **Milestone:** v3.2 zamknięty (Phase 108 COMPLETE)
|
||||
|
||||
## What Is This Project
|
||||
|
||||
orderPRO is a **multi-channel order management system** for Polish e-commerce. It aggregates orders from Allegro (OAuth2) and shopPRO platforms, manages shipments via Apaczka and InPost APIs, generates PDF receipts/invoices, sends automated emails, and exposes a REST API for a Windows print client.
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
| Document | Contents |
|
||||
|----------|----------|
|
||||
| [stack.md](stack.md) | PHP 8.4, custom framework, PDO, SCSS, PHPUnit, Composer deps |
|
||||
| [architecture.md](architecture.md) | MVC + Repository + Service layers, modules, routing, data flows |
|
||||
| [conventions.md](conventions.md) | Naming, code patterns, security, UI rules (from CLAUDE.md) |
|
||||
| [testing.md](testing.md) | PHPUnit 11.5, test patterns, coverage areas |
|
||||
| [integrations.md](integrations.md) | Allegro, shopPRO, Apaczka, InPost, Email, Print queue |
|
||||
| [concerns.md](concerns.md) | Tech debt, SonarQube issues, known bugs, performance |
|
||||
| [db_schema.md](db_schema.md) | Pełny schemat bazy danych — 55 tabel, typy kolumn, klucze, indeksy |
|
||||
| [tech_changelog.md](tech_changelog.md) | Chronologiczny log zmian technicznych — co i dlaczego, per-phase |
|
||||
|
||||
## Key Directories
|
||||
|
||||
```
|
||||
src/Core/ Custom framework (router, PDO, session, logger, CSRF)
|
||||
src/Modules/ 13 feature modules (Orders, Shipments, Accounting, Email, …)
|
||||
routes/web.php All ~80 routes in one file
|
||||
resources/views/ PHP templates organized by module
|
||||
resources/scss/ SCSS sources → public/assets/css/
|
||||
database/migrations/ 84 SQL migration files (timestamped)
|
||||
tests/Unit/ PHPUnit tests for services
|
||||
.paul/codebase/ Mapa kodu (architecture, db_schema, tech_changelog, ...)
|
||||
.paul/changelog/ Dziennik zmian per dzień (klient-friendly)
|
||||
```
|
||||
|
||||
## Current State
|
||||
|
||||
- **Active phase:** — (Phase 108 COMPLETE, v3.2 zamknięty)
|
||||
- **Last completed:** Phase 108 — Delivery Status Management (2026-04-27)
|
||||
- **Total migrations:** 84+ (latest: delivery_status tables from Phase 108)
|
||||
- **God classes to watch:** `OrdersRepository` (1221 LOC), `OrdersController` (1187 LOC), `AutomationService` (834 LOC)
|
||||
- **BREAKING CHANGE (Phase 108):** Automation rules z kluczami group delivery status (`registered`, `courier_pickup`, `dropped_at_point`, `unclaimed`, `picked_up`) wymagają ręcznego odtworzenia po migracji do DB
|
||||
@@ -1,116 +1,41 @@
|
||||
# External Integrations
|
||||
# Integrations
|
||||
|
||||
## Allegro (Polish e-commerce — OAuth2)
|
||||
Last refresh: 2026-05-18.
|
||||
|
||||
**Auth:** OAuth2 Authorization Code Grant
|
||||
**Scopes:** `orders:read/write`, `sale:offers:read`, `shipments:read/write`
|
||||
**Token storage:** Encrypted in `allegro_integration_settings`
|
||||
**Token refresh:** `AllegroTokenManager` — auto-refreshes before expiry
|
||||
**User-Agent:** Required from 01.07.2026 (env: `ALLEGRO_USER_AGENT_URL`)
|
||||
## Marketplace and Shop APIs
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/Modules/Settings/AllegroApiClient.php` | REST calls: `getCheckoutForm()`, `listCheckoutForms()`, `getCheckoutFormShipments()` |
|
||||
| `src/Modules/Settings/AllegroOAuthClient.php` | OAuth2 flow, token exchange |
|
||||
| `src/Modules/Settings/AllegroTokenManager.php` | Token refresh & storage |
|
||||
| `src/Modules/Settings/AllegroOrderImportService.php` | Transform & insert Allegro orders |
|
||||
| `src/Modules/Settings/AllegroOrdersSyncService.php` | Continuous order sync |
|
||||
| `src/Modules/Settings/AllegroStatusSyncService.php` | Push status changes to Allegro |
|
||||
| `src/Modules/Settings/AllegroStatusDiscoveryService.php` | Fetch available Allegro statuses |
|
||||
| `src/Modules/Shipments/AllegroShipmentService.php` | Create shipments via Allegro |
|
||||
| `src/Modules/Shipments/AllegroTrackingService.php` | Track delivery status |
|
||||
- Allegro OAuth/API: `src/Modules/Settings/AllegroOAuthClient.php`, `src/Modules/Settings/AllegroApiClient.php`, `src/Modules/Settings/AllegroTokenManager.php`.
|
||||
- Allegro orders/status: `src/Modules/Settings/AllegroOrderImportService.php`, `src/Modules/Settings/AllegroStatusSyncService.php`, `src/Modules/Settings/AllegroOrdersSyncService.php`.
|
||||
- Allegro shipments/tracking: `src/Modules/Shipments/AllegroShipmentService.php`, `src/Modules/Shipments/AllegroTrackingService.php`.
|
||||
- shopPRO API: `src/Modules/Settings/ShopproApiClient.php`, `src/Modules/Settings/ShopproOrdersSyncService.php`, `src/Modules/Settings/ShopproStatusSyncService.php`, `src/Modules/Settings/ShopproPaymentStatusSyncService.php`.
|
||||
- Erli API: `src/Modules/Settings/ErliApiClient.php`, `src/Modules/Settings/ErliOrdersSyncService.php`, `src/Modules/Settings/ErliStatusSyncService.php`, `src/Modules/Settings/ErliExternalShipmentService.php`.
|
||||
|
||||
## shopPRO (Polish e-commerce — API Key)
|
||||
## Carriers
|
||||
|
||||
**Auth:** API Key + Base URL in integration config
|
||||
**Pagination:** page/per_page, max 100 items
|
||||
**Date filter:** `updated_from` parameter
|
||||
- Apaczka config/API: `src/Modules/Settings/ApaczkaIntegrationRepository.php`, `src/Modules/Settings/ApaczkaApiClient.php`.
|
||||
- Apaczka shipment/tracking: `src/Modules/Shipments/ApaczkaShipmentService.php`, `src/Modules/Shipments/ApaczkaTrackingService.php`.
|
||||
- InPost config/shipment/tracking: `src/Modules/Settings/InpostIntegrationRepository.php`, `src/Modules/Shipments/InpostShipmentService.php`, `src/Modules/Shipments/InpostTrackingService.php`.
|
||||
- Polkurier config/API: `src/Modules/Settings/PolkurierIntegrationRepository.php`, `src/Modules/Settings/PolkurierApiClient.php`.
|
||||
- Polkurier shipment/tracking: `src/Modules/Shipments/PolkurierShipmentService.php`, `src/Modules/Shipments/PolkurierTrackingService.php`.
|
||||
- Shared shipment surface: `src/Modules/Shipments/ShipmentProviderInterface.php`, `src/Modules/Shipments/ShipmentProviderRegistry.php`, `src/Modules/Shipments/ShipmentTrackingRegistry.php`.
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/Modules/Settings/ShopproApiClient.php` | `fetchOrders()`, status/payment sync |
|
||||
| `src/Modules/Settings/ShopproOrdersSyncService.php` | Order import/sync |
|
||||
| `src/Modules/Settings/ShopproPaymentStatusSyncService.php` | Payment sync |
|
||||
| `src/Modules/Settings/ShopproStatusSyncService.php` | Status mapping |
|
||||
| `src/Modules/Settings/ShopproOrderMapper.php` | Order transformation (867 LOC) |
|
||||
## Accounting, Email, SMS, Print
|
||||
|
||||
## Apaczka (Polish parcel aggregator)
|
||||
- Fakturownia: `src/Modules/Settings/FakturowniaApiClient.php`, `src/Modules/Settings/FakturowniaIntegrationRepository.php`.
|
||||
- HostedSMS: `src/Modules/Settings/HostedSmsApiClient.php`, `src/Modules/Settings/HostedSmsIntegrationRepository.php`.
|
||||
- SMSPLANET: `src/Modules/Settings/SmsplanetApiClient.php`, `src/Modules/Settings/SmsplanetIntegrationRepository.php`, `src/Modules/Sms/SmsplanetWebhookController.php`.
|
||||
- SMTP: `src/Modules/Email/EmailSendingService.php`, `src/Modules/Settings/SmtpSecurityContextFactory.php`.
|
||||
- MF whitelist lookup: `src/Core/Http/MfWhitelistApiClient.php`.
|
||||
- Print API: `src/Modules/Printing/PrintApiController.php`, `src/Modules/Printing/ApiKeyMiddleware.php`, `clients/windows/OrderPROPrint/`.
|
||||
|
||||
**Auth:** App ID + App Secret
|
||||
**Base URL:** `https://www.apaczka.pl/api/v2`
|
||||
**Custom exception:** `src/Core/Exceptions/ApaczkaApiException.php`
|
||||
## Cron and External Scheduling
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/Modules/Settings/ApaczkaApiClient.php` | `getServiceStructure()`, `sendOrder()`, `getOrderDetails()`, `getWaybill()` |
|
||||
| `src/Modules/Shipments/ApaczkaShipmentService.php` | Implements `ShipmentProviderInterface` |
|
||||
| `src/Modules/Shipments/ApaczkaTrackingService.php` | Implements `ShipmentTrackingInterface` |
|
||||
- CLI cron entry: `bin/cron.php`.
|
||||
- Web cron setting surface: `config/app.php`, `routes/web.php`.
|
||||
- Handler wiring: `src/Modules/Cron/CronHandlerFactory.php`, `src/Modules/Cron/CronRunner.php`.
|
||||
- Scheduled integration handlers include `src/Modules/Cron/ShopproOrdersImportHandler.php`, `src/Modules/Cron/AllegroOrdersImportHandler.php`, `src/Modules/Cron/ErliOrdersImportHandler.php`, `src/Modules/Cron/ShipmentTrackingHandler.php`.
|
||||
|
||||
## InPost (Parcel lockers + courier)
|
||||
## CDN and Browser Integrations
|
||||
|
||||
**Auth:** Organization token
|
||||
**Production:** `https://api-shipx-pl.easypack24.net/v1`
|
||||
**Sandbox:** `https://sandbox-api-shipx-pl.easypack24.net/v1`
|
||||
**Services:** Paczkomat Standard, Kurier Standard, Kurier Express
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/Modules/Shipments/InpostShipmentService.php` | Create shipments |
|
||||
| `src/Modules/Shipments/InpostTrackingService.php` | Track delivery status |
|
||||
|
||||
## Provider Abstraction
|
||||
|
||||
Shipment providers implement a common interface:
|
||||
- `ShipmentProviderInterface` — `createShipment()`, `downloadLabel()`
|
||||
- `ShipmentTrackingInterface` — `getDeliveryStatus()`
|
||||
- `ShipmentProviderRegistry` — selects correct provider by type
|
||||
- `ShipmentTrackingRegistry` — selects correct tracker
|
||||
|
||||
## Email (SMTP via PHPMailer)
|
||||
|
||||
**Library:** PHPMailer 7.0
|
||||
**Config:** Multiple mailboxes from DB (`EmailMailboxRepository`)
|
||||
**Features:** HTML + attachments, template variable resolution, logging
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/Modules/Email/EmailSendingService.php` | Compose & send via configured mailbox |
|
||||
| `src/Modules/Email/VariableResolver.php` | Replace `{{var}}` in templates with order data |
|
||||
| `src/Modules/Email/AttachmentGenerator.php` | Generate PDF attachments via Dompdf |
|
||||
| `src/Modules/Email/EmailMailboxRepository.php` | SMTP credentials & config |
|
||||
| `src/Modules/Email/EmailTemplateRepository.php` | Email template storage |
|
||||
|
||||
## Print Queue API (Windows client)
|
||||
|
||||
**Auth:** Bearer API key (header `Authorization: Bearer {key}`)
|
||||
**Purpose:** Windows desktop client retrieves print jobs (shipment labels)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/Modules/Printing/PrintApiController.php` | `POST /api/print/jobs`, status endpoints |
|
||||
| `src/Modules/Printing/ApiKeyMiddleware.php` | Validates API key against DB |
|
||||
| `src/Modules/Printing/PrintApiKeyRepository.php` | API key management |
|
||||
| `src/Modules/Printing/PrintJobRepository.php` | Job queue tracking |
|
||||
|
||||
## PDF & Excel (Libraries)
|
||||
|
||||
| Library | Version | Used For |
|
||||
|---------|---------|---------|
|
||||
| `dompdf/dompdf` | ^3.1 | Receipts, invoices, email attachments |
|
||||
| `phpoffice/phpspreadsheet` | ^5.5 | Accounting export to XLSX |
|
||||
|
||||
## SSL / HTTP
|
||||
|
||||
All external API calls use cURL with certificate validation.
|
||||
Resolver: `src/Core/Http/SslCertificateResolver.php`
|
||||
Config: `CURL_CA_BUNDLE_PATH` in `.env`
|
||||
|
||||
## Integration Config Storage
|
||||
|
||||
| Table | Contents |
|
||||
|-------|---------|
|
||||
| `integrations` | Base record (source type, enabled, API key encrypted) |
|
||||
| `allegro_integration_settings` | OAuth tokens (encrypted), Allegro-specific config |
|
||||
| `*_status_mappings` | Bidirectional status code translations |
|
||||
| `email_mailboxes` | SMTP connection settings |
|
||||
| `print_api_keys` | Print client API keys |
|
||||
- Google Fonts and Chart.js are loaded in `resources/views/layouts/app.php`.
|
||||
- Quill is loaded from CDN in `resources/views/settings/email-mailboxes.php` and `resources/views/settings/email-templates-form.php`.
|
||||
|
||||
57
.paul/codebase/quality_risks.md
Normal file
57
.paul/codebase/quality_risks.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Quality Risks
|
||||
|
||||
Last refresh: 2026-05-18.
|
||||
|
||||
## Radar Status
|
||||
|
||||
- `jscpd` ran through `npx` and produced `.paul/codebase/radar/jscpd/jscpd-report.json`.
|
||||
- `codebase-memory-mcp` is installed globally at version 0.6.1; a fresh Codex MCP process indexed the repo in `fast` mode with 8165 nodes and 13610 edges.
|
||||
- `ast-grep` is degraded: command unavailable and `npx --package @ast-grep/cli ast-grep` failed under the current Node/npm environment.
|
||||
- Structural fallback used `rg` and code/document review.
|
||||
|
||||
## Primary Risks
|
||||
|
||||
- God-class risk: large controllers/repositories mix request validation, orchestration, rendering state, SQL, and integration details, especially `src/Modules/Orders/OrdersController.php`, `src/Modules/Orders/OrdersRepository.php`, `src/Modules/Settings/ShopproIntegrationsController.php`.
|
||||
- Native prompt risk: remaining `window.confirm`, `confirm`, and `alert` fallbacks bypass `resources/modules/jquery-alerts`.
|
||||
- Inline frontend risk: many legacy `<script>`, inline handlers, and style fragments remain in views such as `resources/views/orders/show.php`, `resources/views/shipments/prepare.php`, and `resources/views/settings/email-mailboxes.php`.
|
||||
- Test gap risk: high-change controllers, routes, JS modules, cron wiring, and Windows print client lack direct automated tests.
|
||||
- Data/query risk: repositories use PDO directly; static SQL is normal, but any dynamic table/query construction should be reviewed before extension.
|
||||
|
||||
## Native Prompt Findings
|
||||
|
||||
- `public/assets/js/modules/confirm-delete.js` has `window.confirm` fallback.
|
||||
- `public/assets/js/modules/sms-template-picker.js` has `window.confirm` fallback.
|
||||
- `resources/views/shipments/prepare.php` has `confirm` fallback.
|
||||
- `resources/views/orders/show.php` has `confirm` fallback.
|
||||
- `resources/views/components/table-list.php` has `window.confirm` fallback.
|
||||
- `resources/views/accounting/invoice_form.php` has native `alert`.
|
||||
- `resources/views/settings/email-mailboxes.php` has `confirm` fallback.
|
||||
|
||||
## Hardcoded or External Surface
|
||||
|
||||
- CDN assets live in `resources/views/layouts/app.php`, `resources/views/settings/email-mailboxes.php`, and `resources/views/settings/email-templates-form.php`.
|
||||
- API endpoints/config handling is spread across `src/Modules/Settings/*ApiClient.php` and provider config repositories.
|
||||
- Runtime DB host must remain `DB_HOST`; do not use `DB_HOST_REMOTE` in application config.
|
||||
|
||||
## Recommended Follow-Up
|
||||
|
||||
- Plan a focused prompt-cleanup phase for the native `alert()` / `confirm()` leftovers.
|
||||
- Exclude `.playwright-mcp/`, `.scannerwork/`, `.vscode/ftp-kr.diff.*`, generated assets, and cache folders from future jscpd scans.
|
||||
- Use MCP tools through a fresh Codex session/process for structured codebase-memory graph output; the already-running session may not hot-reload new MCP servers.
|
||||
- Add direct tests around high-risk services before refactoring large controllers.
|
||||
|
||||
## Targeted Risk: Polish UI Copy Apply
|
||||
|
||||
- Date: 2026-05-18.
|
||||
- `resources/lang/pl.php`, hardcoded view copy, JS alerts and selected backend operator messages were normalized to proper Polish diacritics.
|
||||
- Preserved technical contracts: routes, form names, CSS classes, JS selectors, status/provider codes, API payload keys and template placeholders such as `{{zamowienie.numer}}` and `{{przesylka.numer}}`.
|
||||
- Residual search shows remaining ASCII Polish-like strings are intentional technical identifiers/provider URLs or template keys.
|
||||
- Verification gap: `sonar-scanner` is unavailable in PATH, so Sonar was not run for this APPLY.
|
||||
|
||||
## Targeted Risk: Polish UI Copy Plan
|
||||
|
||||
- Date: 2026-05-18.
|
||||
- `resources/lang/pl.php` contains many ASCII-transliterated user-facing values (`Zamowienia`, `Przesylki`, `Platnosci`, `Zrodlo`, etc.) and should be corrected as the primary source of truth.
|
||||
- Hardcoded visible copy also exists in views, JS modules and selected controllers/services. Correcting it is safe only when the string is clearly user-facing.
|
||||
- Main risk is over-translation: routes, array keys, status codes, provider codes, payload fields, CSS classes and JS selectors must remain unchanged.
|
||||
- Encoding must remain valid UTF-8; residual scans should check for mojibake markers such as `Ă`, `Ĺ`, `—`, and `â†`.
|
||||
6581
.paul/codebase/radar/ast-grep-full.txt
Normal file
6581
.paul/codebase/radar/ast-grep-full.txt
Normal file
File diff suppressed because it is too large
Load Diff
26
.paul/codebase/radar/codebase-memory-full.txt
Normal file
26
.paul/codebase/radar/codebase-memory-full.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
# codebase-memory-mcp full scan
|
||||
Timestamp: 2026-05-18T22:55:00+02:00
|
||||
Status: installed, MCP-tested, and indexed through a fresh Codex process.
|
||||
Version: codebase-memory-mcp 0.6.1
|
||||
Binary: C:\Users\jacek\AppData\Roaming\npm\codebase-memory-mcp.ps1
|
||||
Codex MCP command: C:/Users/jacek/AppData/Roaming/npm/node_modules/codebase-memory-mcp/bin/codebase-memory-mcp.exe
|
||||
|
||||
Commands:
|
||||
- codebase-memory-mcp --version
|
||||
- codebase-memory-mcp --help
|
||||
- codebase-memory-mcp --% cli index_repository {"repo_path":"C:\visual studio code\projekty\orderPRO"}
|
||||
- codebase-memory-mcp --% cli list_projects {}
|
||||
- codebase-memory-mcp --% cli index_status {"repo_path":"C:\visual studio code\projekty\orderPRO"}
|
||||
- codebase-memory-mcp --% cli get_architecture {"repo_path":"C:\visual studio code\projekty\orderPRO"}
|
||||
- codex exec used MCP tools: list_projects, index_repository, list_projects, get_architecture
|
||||
|
||||
Result:
|
||||
- CLI is now available in PATH and reports version 0.6.1.
|
||||
- Codex config lists `codebase-memory-mcp` as enabled stdio MCP server.
|
||||
- Current already-running Codex session does not hot-reload the new MCP server, but a fresh `codex exec` process loaded it correctly.
|
||||
- Initial MCP index completed in `fast` mode.
|
||||
- Project: `C-visual studio code-projekty-orderPRO`.
|
||||
- Path: `C:/visual studio code/projekty/orderPRO`.
|
||||
- Index: 8165 nodes, 13610 edges.
|
||||
- Architecture summary from MCP: 1775 `Method`, 225 `Class`, 160 `Route`, 698 `File`, 698 `Module`; main edge types include `DEFINES`, `CALLS`, `DEFINES_METHOD`, `TESTS`, `THROWS`, `USAGE`.
|
||||
- Direct PowerShell CLI JSON invocation still does not pass `repo_path` reliably; use MCP tools through a fresh Codex session/process for graph operations.
|
||||
@@ -0,0 +1,15 @@
|
||||
Mode: post-apply
|
||||
Timestamp: 2026-05-18 23:40 Europe/Warsaw
|
||||
Scope: .paul/plans/20260518-2305-polskie-tlumaczenia/PLAN.md resources/lang/pl.php resources/views public/assets/js/modules src/Modules DOCS .paul/codebase
|
||||
|
||||
Tool:
|
||||
- codebase-memory-mcp --version: codebase-memory-mcp 0.6.1
|
||||
- MCP detect_changes project: C-visual studio code-projekty-orderPRO
|
||||
|
||||
Result:
|
||||
- changed_count: 110
|
||||
- impacted_symbols: []
|
||||
|
||||
Interpretation:
|
||||
- The graph did not report impacted callable symbols for this copy-only change.
|
||||
- Main impact remains UI/documentation surface: translations, legacy views, public JS modules and selected operator-facing module messages.
|
||||
17021
.paul/codebase/radar/jscpd-i18n-apply-preflight/jscpd-report.json
Normal file
17021
.paul/codebase/radar/jscpd-i18n-apply-preflight/jscpd-report.json
Normal file
File diff suppressed because it is too large
Load Diff
17021
.paul/codebase/radar/jscpd-i18n-plan/jscpd-report.json
Normal file
17021
.paul/codebase/radar/jscpd-i18n-plan/jscpd-report.json
Normal file
File diff suppressed because it is too large
Load Diff
17057
.paul/codebase/radar/jscpd-i18n-post-apply/jscpd-report.json
Normal file
17057
.paul/codebase/radar/jscpd-i18n-post-apply/jscpd-report.json
Normal file
File diff suppressed because it is too large
Load Diff
28845
.paul/codebase/radar/jscpd/jscpd-report.json
Normal file
28845
.paul/codebase/radar/jscpd/jscpd-report.json
Normal file
File diff suppressed because one or more lines are too long
@@ -1,71 +1,56 @@
|
||||
# Technology Stack
|
||||
# Stack
|
||||
|
||||
Last refresh: 2026-05-18.
|
||||
|
||||
## Runtime
|
||||
|
||||
| Layer | Technology | Version | Notes |
|
||||
|-------|-----------|---------|-------|
|
||||
| PHP | PHP | ^8.4 | `declare(strict_types=1)` in all files |
|
||||
| Web server | Apache | XAMPP (local) | `public/.htaccess` handles routing |
|
||||
| Database | MySQL | InnoDB | utf8mb4_unicode_ci |
|
||||
| Node.js | npm | dev only | Sass build tool only, no runtime JS bundler |
|
||||
- Backend: PHP `^8.4`, custom lightweight MVC-style application.
|
||||
- Database: MySQL/InnoDB through PDO; no ORM.
|
||||
- Frontend: native PHP views plus plain JS modules; no JS bundler.
|
||||
- Assets: Sass from `resources/scss/` and `resources/modules/jquery-alerts/`, compiled into `public/assets/`.
|
||||
- Windows helper client: C# WinForms tray app in `clients/windows/OrderPROPrint/`.
|
||||
|
||||
## PHP Dependencies (`composer.json`)
|
||||
## Manifests
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `dompdf/dompdf` | ^3.1 | PDF generation (receipts, labels) |
|
||||
| `phpoffice/phpspreadsheet` | ^5.5 | Excel/XLSX export (accounting) |
|
||||
| `phpmailer/phpmailer` | ^7.0 | SMTP email sending |
|
||||
| `phpunit/phpunit` | ^11.5 (dev) | Unit testing |
|
||||
| `dg/bypass-finals` | ^1.9 (dev) | Mock final classes in tests |
|
||||
- PHP manifest: `composer.json`.
|
||||
- PHP lockfile: no `composer.lock` found, so dependency versions are not pinned in-repo.
|
||||
- Node manifests: `package.json`, `package-lock.json`.
|
||||
- PHPUnit config: `phpunit.xml`.
|
||||
- Sonar config: `sonar-project.properties`.
|
||||
|
||||
## Framework
|
||||
## Core Framework Paths
|
||||
|
||||
**Custom lightweight framework** — no Laravel/Symfony.
|
||||
- Application bootstrap: `public/index.php`, `index.php`, `bootstrap/app.php`.
|
||||
- Application shell: `src/Core/Application.php`.
|
||||
- Routing: `src/Core/Routing/Router.php`, `routes/web.php`.
|
||||
- HTTP primitives: `src/Core/Http/Request.php`, `src/Core/Http/Response.php`.
|
||||
- Views: `src/Core/View/Template.php`, `resources/views/`.
|
||||
- Database: `src/Core/Database/ConnectionFactory.php`, `src/Core/Database/Migrator.php`.
|
||||
- Security/session: `src/Core/Security/Csrf.php`, `src/Core/Support/Session.php`, `src/Modules/Auth/`.
|
||||
- Environment/config: `.env.example`, `src/Core/Support/Env.php`, `config/app.php`, `config/database.php`.
|
||||
|
||||
| Component | File |
|
||||
|-----------|------|
|
||||
| Application bootstrap | `src/Core/Application.php` |
|
||||
| Router | `src/Core/Routing/Router.php` |
|
||||
| Request / Response | `src/Core/Http/Request.php`, `Response.php` |
|
||||
| Template engine | `src/Core/View/Template.php` (PHP-native, `$e()` + `$t()` helpers) |
|
||||
| Session | `src/Core/Support/Session.php` |
|
||||
| Logger | `src/Core/Support/Logger.php` → `storage/logs/app.log` |
|
||||
| CSRF | `src/Core/Security/Csrf.php` |
|
||||
| i18n | `src/Core/I18n/Translator.php` (Polish primary: `resources/lang/pl/`) |
|
||||
| DB connection | `src/Core/Database/ConnectionFactory.php` (PDO, no ORM, no medoo) |
|
||||
| Migrator | `src/Core/Database/Migrator.php` (custom SQL runner, `migrations` table) |
|
||||
| SSL resolver | `src/Core/Http/SslCertificateResolver.php` (env: `CURL_CA_BUNDLE_PATH`) |
|
||||
## Commands
|
||||
|
||||
## Frontend
|
||||
- Dev server: `composer serve`.
|
||||
- Migrations: `composer migrate` or `php bin/migrate.php`.
|
||||
- Cron: `composer cron` or `php bin/cron.php`.
|
||||
- PHP tests: `composer test` or `vendor/bin/phpunit -c phpunit.xml --testdox`.
|
||||
- Asset build: `npm run build:assets`.
|
||||
- CSS build only: `npm run build:css`.
|
||||
- Alert module build: `npm run build:modules`.
|
||||
- Sass watch: `npm run watch:css`.
|
||||
|
||||
- **No CSS framework** — custom SCSS design tokens
|
||||
- **jQuery** — used only for `jquery-alerts` module (`resources/modules/jquery-alerts/`)
|
||||
- **No JS bundler** — files served directly from `public/assets/js/`
|
||||
- **Build**: `npm run build:assets` (Sass → compressed CSS + JS copy)
|
||||
## Notable Dependencies
|
||||
|
||||
## Build Scripts
|
||||
- PDF/rendering: `dompdf/dompdf`.
|
||||
- Spreadsheet export: `phpoffice/phpspreadsheet`.
|
||||
- SMTP: `phpmailer/phpmailer`.
|
||||
- Tests: `phpunit/phpunit`, `dg/bypass-finals`.
|
||||
- Frontend build: `sass`.
|
||||
|
||||
```json
|
||||
"build:css" → sass --style=compressed resources/scss/app.scss → public/assets/css/app.css
|
||||
"build:modules"→ sass jquery-alerts.scss + copy jquery-alerts.js
|
||||
"build:assets" → build:css && build:modules
|
||||
"watch:css" → sass --watch (development)
|
||||
```
|
||||
## Configuration Notes
|
||||
|
||||
```json
|
||||
"serve" → php -S localhost:8000 -t public public/index.php
|
||||
"migrate" → php bin/migrate.php
|
||||
"cron" → php bin/cron.php
|
||||
"test" → vendor/bin/phpunit -c phpunit.xml --testdox
|
||||
```
|
||||
|
||||
## Environment Variables (`.env.example`)
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `DB_HOST`, `DB_PORT`, `DB_DATABASE` | MySQL connection |
|
||||
| `DB_HOST_REMOTE` | Agent-only manual DB ops (NOT used by app runtime) |
|
||||
| `CURL_CA_BUNDLE_PATH` | SSL cert path (XAMPP: `C:/xampp/php/extras/ssl/cacert.pem`) |
|
||||
| `ALLEGRO_USER_AGENT_URL` | Required by Allegro REST API from 01.07.2026 |
|
||||
| `CRON_RUN_ON_WEB`, `CRON_WEB_LIMIT`, `CRON_PUBLIC_TOKEN` | Cron configuration |
|
||||
- Runtime database host is `DB_HOST` through `config/database.php`.
|
||||
- `DB_HOST_REMOTE` is agent-only/manual migration infrastructure and must not be wired into runtime.
|
||||
- TLS/CA behavior is centralized around `CURL_CA_BUNDLE_PATH` and `src/Core/Http/SslCertificateResolver.php`.
|
||||
- SMTP self-signed override is dev-only via `SMTP_ALLOW_SELF_SIGNED_DEV`.
|
||||
|
||||
@@ -1,626 +0,0 @@
|
||||
# Technical Changelog
|
||||
|
||||
## 2026-05-17 - Phase 139 Plan 02: Sonar Critical/Major Cleanup
|
||||
|
||||
**Co zrobiono:**
|
||||
- Finalny Sonar: 495 OPEN BLOCKER/CRITICAL/MAJOR po 139-02 (spadek z 605 po 139-01).
|
||||
- Dodano typowane wyjatki w selected Settings/Automation clusters: Fakturownia, Polkurier, Erli mapping, automation duplicate i email template duplicate.
|
||||
- Targetowane widoki/layouty renderuja alert component przez `$component()`; `messageHtml` pozostaje tylko dla zaufanego gotowego HTML.
|
||||
- `SmsTemplateController` i `UsersController` maja stale tras/flashy oraz male helpery walidacyjne, bez zmiany routingu i UX.
|
||||
|
||||
**Dlaczego:**
|
||||
- Najwiekszy bezpieczny zysk po 139-01 byl w `php:S4833` i `php:S112`; plan nie mial ruszac schematu DB ani god-class splitow.
|
||||
|
||||
**BREAKING / migracja:**
|
||||
- Brak migracji DB i brak breaking changes.
|
||||
|
||||
## 2026-05-17 - Phase 139 Plan 01: Sonar Critical/Major Cleanup
|
||||
|
||||
**Co zrobiono:**
|
||||
- Swiezy Sonar baseline: 648 OPEN BLOCKER/CRITICAL/MAJOR przed cleanupem; finalnie 605 po zmianach.
|
||||
- Delivery status cluster wyczyszczony do 0 issue w targetowanych plikach: domenowy `DeliveryStatusException`, guard helpers w repozytorium, tabelaryczne mapowania/URL-e w `DeliveryStatus`, kontrolery bez duplikowanych redirectow i widoki bez bezposrednich include.
|
||||
- `OrdersStatisticsRepository` ma uproszczone cache kolumn i generowanie SQL kwot/daty/IN/ROUND; usunieto potwierdzone issue z nadmiarowymi returnami, zlozonoscia i duplikatami literalow w targetowanym zakresie.
|
||||
- Pozostawiono `php:S1448` w `OrdersStatisticsRepository` jako nastepny refactor-slice, bo wymaga rozdzielenia klasy.
|
||||
|
||||
**Dlaczego:**
|
||||
- Phase 139 ma pracowac na aktualnych wynikach SonarQube. Pierwsza fala wybiera bezpieczne refaktory bez zmiany zachowania biznesowego.
|
||||
|
||||
**BREAKING / migracja:**
|
||||
- Brak migracji DB i brak zmian breaking.
|
||||
|
||||
## 2026-05-17 - Phase 138 Plan 01: Security and Legacy Hardening
|
||||
|
||||
**Co zrobiono:**
|
||||
- `EmailMailboxController::testConnection()` uzywa strict TLS verification dla implicit SSL i STARTTLS.
|
||||
- `SMTP_ALLOW_SELF_SIGNED_DEV` pozwala na self-signed/unverified certyfikaty tylko lokalnie/dev/testing.
|
||||
- `TemplateVariableCatalog` centralizuje zmienne e-mail/SMS i blokuje zapis szablonow z nieznanymi placeholderami.
|
||||
- `Session` dostal helpery `get/set/has/forget/pull`; raw `$_SESSION` przeniesiono do tej warstwy.
|
||||
- `Template` dostal `$component()` helper, a wskazane widoki przestaly uzywac hard `require` dla komponentow/partiali.
|
||||
|
||||
**Dlaczego:**
|
||||
- Phase 134 potwierdzil aktywne ryzyka security/legacy: weak SMTP TLS, niespojna polityka zmiennych, raw session access i view include debt.
|
||||
|
||||
**BREAKING / migracja:**
|
||||
- Brak migracji DB. Nowe/edytowane szablony z nieznanymi zmiennymi sa odrzucane.
|
||||
|
||||
## 2026-05-17 - Phase 137 Plan 01: Delivery Status Backlog Verification
|
||||
|
||||
**Co zrobiono:**
|
||||
- Zweryfikowano runtime code dla `DELIVERY-STATUS-MGMT`: `SHIPMENT_STATUS_OPTIONS` i `SHIPMENT_STATUS_OPTION_MAP` nie istnieja juz w source poza historyczna dokumentacja.
|
||||
- Potwierdzono, ze `AutomationController` uzywa `DeliveryStatus::getAllOptions()` / `getAllStatuses()` dla dropdownow i walidacji.
|
||||
- Potwierdzono, ze `AutomationService` porownuje `shipment_status` bezposrednio po znormalizowanych kluczach DB.
|
||||
- Lokalny MySQL byl niedostepny, wiec wykonano manualny read-only check przez `DB_HOST_REMOTE` bez zmiany runtime config.
|
||||
- Read-only DB check: `delivery_statuses=11`, `shipment_status` conditions=3, `update_shipment_status` actions=0, stare klucze=0, invalid keys=0.
|
||||
|
||||
**Dlaczego:**
|
||||
- Phase 134 oznaczyl `DELIVERY-STATUS-MGMT` jako wdrozone w kodzie, ale wymagajace operator verification po breaking change z Phase 108. Phase 137 potwierdzila, ze w aktualnych danych nie ma reguly wymagajacej recznej migracji.
|
||||
|
||||
**BREAKING / migracja:**
|
||||
- Brak zmian runtime i brak migracji. Phase 137 jest verification-only.
|
||||
|
||||
## 2026-05-17 - Phase 136 Plan 01: Fakturownia Invoice Idempotency
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano migracje `20260517_000118_add_invoice_external_idempotency_state.sql` z polami `invoices.external_status`, `external_oid`, `external_attempted_at`, `external_error_message` oraz unikalnym indeksem `(config_id, external_oid)`.
|
||||
- `InvoiceService::issueDelegated()` uzywa stabilnego Fakturownia `oid` z `orders.internal_order_number` (fallback `orderpro-{order_id}` tylko gdy brak numeru wewnetrznego).
|
||||
- Przed zewnetrznym POST system sprawdza Fakturownie przez `GET /invoices.json?oid=...`; znaleziony dokument jest automatycznie podpiety do lokalnego wiersza.
|
||||
- Delegowane wystawienie tworzy lokalny wiersz `pending_external` przed POST i finalizuje go po odpowiedzi Fakturowni.
|
||||
- Po timeoutcie lub bledzie polaczenia system ponownie sprawdza `oid`; jesli faktura istnieje, podpina ja i zwraca sukces, a jesli nie istnieje, oznacza wiersz jako `failed_retryable`.
|
||||
- `FakturowniaApiClient` dostal `findInvoiceByOid()` i wspolna normalizacje odpowiedzi faktury.
|
||||
- Dodano `FakturowniaInvoiceIdempotencyTest` dla lookup-first retry, sukcesu POST, auto-attach po timeoutcie i retryable failure.
|
||||
|
||||
**Dlaczego:**
|
||||
- `INVOICE-IDEMP-115` grozil druga faktura w Fakturowni, gdy pierwszy POST utworzyl dokument, ale odpowiedz nie dotarla do orderPRO. Fakturownia dokumentuje lookup po `oid`, a nie dokumentuje `Idempotency-Key`, wiec retry-safe flow opiera sie na stabilnym `oid` i lokalnym stanie.
|
||||
|
||||
**BREAKING / migracja:**
|
||||
- Wymagana migracja `20260517_000118_add_invoice_external_idempotency_state.sql`.
|
||||
- Brak zmian breaking dla faktur lokalnych; `invoice_number_counters` pozostaje bez zmian.
|
||||
|
||||
## 2026-05-16 - Phase 135 Plan 01: Accounting Net Correctness
|
||||
|
||||
**Co zrobiono:**
|
||||
- `ReceiptService::buildItemsSnapshot()` zwraca teraz `total_net` obok `total_gross`, liczac netto per pozycja po realnej stawce VAT z `tax_rate`/`vat`.
|
||||
- `ReceiptService::issue()` zapisuje nowe `receipts.total_net` z obliczonej sumy netto zamiast kopiowac kwote brutto.
|
||||
- Koszt wysylki w paragonach i statystykach pozostaje traktowany jako 23% VAT.
|
||||
- `OrdersStatisticsRepository::netAmountSql()` preferuje `orders.total_without_tax`, potem `orders.total_net`, a przy braku net zrodla liczy fallback z `order_items` (`original_price_without_tax` albo brutto/VAT/ilosc).
|
||||
- Dodano test `ReceiptServiceNetCalculationTest` oraz rozszerzono `OrdersStatisticsRepositoryTest` o source-net precedence i mieszane stawki VAT.
|
||||
|
||||
**Dlaczego:**
|
||||
- Phase 134 potwierdzil aktywne bugi `RECEIPT-NET-FIX` i `STAT-NET`: paragony zapisywaly netto jako kopie brutto, a statystyki zakladaly 23% VAT dla wszystkich zamowien bez source-level net.
|
||||
|
||||
**BREAKING / migracja:**
|
||||
- Brak migracji i brak breaking changes. Operator wybral brak backfillu historycznych paragonow; zmiana dotyczy nowych paragonow i runtime statystyk.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-16 - Phase 129 Plan 01: Erli Status Mapping + Sync
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano mapowania statusow Erli pull/push, `ErliStatusSyncService`, `ErliStatusSyncHandler`, endpoint `PATCH /orders/{id}/status` w kliencie API oraz UI mapowan w `/settings/integrations/erli`.
|
||||
- Import Erli odkrywa surowe statusy z inboxa i uzywa `erli_order_status_pull_mappings`; push obejmuje tylko reczne zmiany statusu z `order_status_history.change_source='manual'`.
|
||||
|
||||
**Dlaczego:**
|
||||
- Phase 128 miala domyslne statusy; Phase 129 daje operatorowi kontrolowane mapowanie i bezpieczny push bez petli automatyzacji.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-14 - Phase 130 Plan 01: polkurier delivery status mappings UI
|
||||
|
||||
**Co zrobiono:**
|
||||
- `src/Modules/Shipments/DeliveryStatus.php` — nowe stałe `POLKURIER_MAP` i `POLKURIER_DESCRIPTIONS` z 7 oficjalnymi kodami ORDER_STATUS z dokumentacji polkurier API v1.11 (`O`→`created`, `P`→`confirmed`, `A`→`cancelled`, `WP`→`in_transit`, `D`→`delivered`, `Z`→`returned`, `W`→`problem`). Wartości identyczne z migracją Phase 128 (`20260514_000115_seed_polkurier_delivery_status_mappings.sql`).
|
||||
- `src/Modules/Shipments/DeliveryStatus.php` — rejestracja `'polkurier' => self::POLKURIER_MAP` w `PROVIDER_MAPS` (po `'allegro_edge'`), analogicznie w `PROVIDER_DESCRIPTIONS`, oraz w match expressions `normalize()`/`description()`. `getDefaultMappings('polkurier')` zwraca 7 wpisów.
|
||||
- `src/Modules/Settings/DeliveryStatusesController.php` + `DeliveryStatusMappingController.php` — stałe `PROVIDERS` rozszerzone z 3 do 4 wpisów: `'polkurier' => 'polkurier'` (lowercase, spójne z Phase 127).
|
||||
- `src/Modules/Shipments/DeliveryStatusMappingRepository.php` — `countAllUnmappedForBadge()`: lista providerów rozszerzona z `['inpost', 'apaczka', 'allegro_wza']` do `['inpost', 'apaczka', 'allegro_wza', 'polkurier']`. Badge "niezmapowane statusy" w menu Ustawień reaguje teraz na nieznane raw statusy polkuriera.
|
||||
- View `_delivery-status-mappings-content.php` automatycznie iteruje po `$providersList` z controllera — żadnych zmian w widoku nie trzeba.
|
||||
|
||||
**Dlaczego:**
|
||||
- Phase 128 zaseed-owała DB override (`delivery_status_mappings` 7 wpisów) ale UI mapowania pozostał hardcoded na 3 providerów. Operator nie miał jak zmapować/podejrzeć statusów polkuriera w panelu.
|
||||
- Defaultowe mapowania hardcoded w kodzie (nie tylko z DB) — spójność z InPost/Apaczka/Allegro (wszyscy mają hardcoded fallback). UI działa od razu, niezależnie czy operator uruchomił migrację Phase 128.
|
||||
- Pattern `provider addition`: 5 punktów edycji w 4 plikach (1 const definition + 2 PROVIDER_* + 2 match arms + 2× PROVIDERS controller + 1 badge providers list) — checklist do reuse dla następnych przewoźników.
|
||||
|
||||
**Side-effects:**
|
||||
- Migracja `20260514_000115_seed_polkurier_delivery_status_mappings.sql` (Phase 128) staje się no-op po wdrożeniu Phase 130 — DB override == hardcoded default → render `is_custom=true` ale ta sama wartość. Migracja może być uruchomiona lub nie.
|
||||
|
||||
**Files modified:**
|
||||
- `src/Modules/Shipments/DeliveryStatus.php`
|
||||
- `src/Modules/Settings/DeliveryStatusesController.php`
|
||||
- `src/Modules/Settings/DeliveryStatusMappingController.php`
|
||||
- `src/Modules/Shipments/DeliveryStatusMappingRepository.php`
|
||||
- `.paul/codebase/tech_changelog.md` (this entry)
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-14 - Phase 129 Plan 01: Order User Notes module
|
||||
|
||||
**Co zrobiono:**
|
||||
- Migracja `database/migrations/20260514_000116_extend_order_notes_user_authored.sql` — `order_notes` rozszerzona o `user_id INT UNSIGNED NULL` (FK → `users(id)` ON DELETE SET NULL), `author_name VARCHAR(190) NULL`, oraz indeks `idx_order_notes_type_order (note_type, order_id)`. Wszystkie ADD owinięte w `INFORMATION_SCHEMA` guard z DDL no-op fallback (`ALTER TABLE COMMENT`) — pattern Phase 115/125.
|
||||
- `src/Modules/Orders/OrderNotesService.php` — nowy serwis CRUD nad `order_notes` z `note_type='user'`. Metody: `listUserNotes`, `listImportedNotes`, `countUserNotes`, `findById`, `create`, `update`, `delete`. Autoryzacja przez `WHERE user_id = :user_id` w UPDATE/DELETE — rowCount=0 ⇒ rzut `RuntimeException(code=403)`. Walidacja `body`: trim, niepuste, ≤ 2000 znakow.
|
||||
- `src/Modules/Orders/OrdersRepository.php` — dodany `userNotesCountSubquerySql($orderAlias)` (subquery `COUNT(*) FROM order_notes WHERE note_type='user'`) używany w `paginateSql()` jako kolumna `user_notes_count`. `loadOrderNotes()` zawężony do `note_type <> 'user'` (importowane ze źródła). `transformOrderRow()` ekspozuje `user_notes_count`.
|
||||
- `src/Modules/Orders/OrdersController.php` — nowa opcjonalna zależność `OrderNotesService` w konstruktorze (na końcu, nullable, BC-safe). 3 metody: `storeNote`, `updateNote`, `deleteNote` (każda CSRF + sesja + try/catch `RuntimeException`/`InvalidArgumentException`; rejestruje `order_activity_log event_type='note'` przez `OrdersRepository::recordActivity`). `toTableRow()` renderuje `<a class="order-notes-badge" href="/orders/{id}#notes">[N]</a>` obok numeru zamówienia gdy `user_notes_count > 0`. `show()` pobiera `userNotes` + `currentUserId` i przekazuje do widoku.
|
||||
- `routes/web.php` — 3 nowe route'y `POST /orders/{id}/notes`, `POST /orders/{id}/notes/{noteId}/update`, `POST /orders/{id}/notes/{noteId}/delete`. `OrderNotesService` instancjonowany przed `new OrdersController(...)` i przekazany ostatnim argumentem.
|
||||
- `resources/views/orders/show.php` — sekcja "Wiadomości i załączniki" przebudowana na 3 bloki: (1) `<div class="order-user-notes" id="notes">` z listą notatek (data · autor) + akcjami edit/delete dla autora + inline formularz dodawania, (2) ukryty `order-note-edit-form` per notatka rozwijany przez JS, (3) opcjonalny blok "Wiadomości ze źródła" gdy `$notesList !== []` (importowane, bez akcji).
|
||||
- `resources/lang/pl.php` — 10 nowych kluczy `orders.details.notes_user_*` / `notes_imported_title` (UI labels w PL).
|
||||
- `resources/scss/modules/_order-notes.scss` (nowy) + `@use` w `app.scss` — `.order-notes-badge` (niebieskoszary `#eef2ff/#4338ca`), `.order-user-notes`, `.order-event--user` (lewa krawędź `#6366f1`), `.order-imported-notes` (opacity 0.75), `.btn-link`, `.order-note-form`, `.order-note-edit-form`. CSS przebudowany via `npm run build:css`.
|
||||
- `public/assets/js/modules/order-notes.js` (nowy) + `<script>` w `layouts/app.php` — wanilijowy JS: klik "Edytuj" toggle'uje `js-order-note-body` ↔ `js-order-note-edit-form`, klik "Anuluj" wraca, submit formularza DELETE przechwycony i potwierdzany przez `OrderProAlerts.confirm({title, message, danger:true, onConfirm})` (options-object API z decyzji Phase 114). Idempotent guard `window.__orderNotesInit` + `dataset.bound`.
|
||||
- `.paul/codebase/db_schema.md` — sekcja `order_notes` rozszerzona o pełne kolumny + notatkę Phase 129-01 (note_type='user' vs imported).
|
||||
|
||||
**Dlaczego:**
|
||||
- Operator potrzebował miejsca na własne adnotacje per zamówienie (uzgodnienia z klientem, ustalenia wewnętrzne) niezależne od notatek importowanych z shopPRO/Allegro. Bez badge'a na liście trzeba by wchodzić w każde zamówienie żeby sprawdzić czy ma notatki.
|
||||
- Reuse istniejącej tabeli `order_notes` (Plan clarification #1) zamiast nowej tabeli — mniej obiektów DB, jeden punkt zarządzania, semantyka rozróżniona przez `note_type`. UNIQUE `(order_id, source_note_id)` nadal działa bo MySQL traktuje wiele NULL jako unique.
|
||||
- Brak admin override (Plan clarification #3 z dopiskiem): aplikacja nie ma systemu ról (`grep -rn "is_admin|role=" src/Modules/Auth` zwrócił 0 trafień). Autoryzacja przez `note.user_id = session.user_id` — operator który dodał notatkę edytuje/usuwa, inni widzą ale nie modyfikują. Pełen admin-override odłożony do osobnej fazy po wprowadzeniu ról.
|
||||
- Indeks `idx_order_notes_type_order (note_type, order_id)` zapewnia że subquery `user_notes_count` w paginacji `/orders/list` nie degraduje performance przy rosnącej liczbie notatek (Phase 106 pattern dla `customer_returned_count`).
|
||||
|
||||
**BREAKING:** brak — wszystkie zmiany BC. `loadOrderNotes()` teraz zwraca tylko `note_type <> 'user'`, ale nikt poza `findDetails()` jej nie używa, a sekcja widoku zachowuje wstecznie kompatybilne `$notesList` z importowanych notatek (osobny blok pod nową sekcją "Notatki").
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-14 - Phase 128 Plan 01: polkurier ShipmentService + Tracking + UI prepare
|
||||
|
||||
**Co zrobiono:**
|
||||
- `src/Modules/Settings/PolkurierApiClient.php` — pelen kontrakt API: `createShipment` (`apimetod=create_order`), `getLabel` (`get_label`), `getStatus` (`get_status`), `cancelOrder` (`cancel_order`), `getAvailableCarriers` (`available_carriers`), `getInpostParcelMachines` (`inpost_parcel_machines`), `getCourierPoints` (`get_courier_point`). Wspolny prywatny `call($apimetod, $data, $login, $token): mixed` parsuje envelope `{status, response}`; sukces -> zwraca `response`, blad -> rzuca `RuntimeException` z trescia z `response` (string albo zserializowany JSON dla tablic). Kontrakt zweryfikowany na oficjalnej dokumentacji PDF v1.11 (marzec 2026) — pobrana z `https://www.polkurier.pl/files/download/api_documentation_pdf`, zachowana w `.paul/phases/128-polkurier-shipment-service/polkurier-api-docs.txt`.
|
||||
- `src/Modules/Shipments/PolkurierShipmentService.php` — `final class implements ShipmentProviderInterface` (`code()='polkurier'`). Pelen flow `createShipment($orderId, $formData)`: walidacja credentials/sender, `normalizeShipmentType()` mapuje `package_type` (PACKAGE/BOX/...) na zbior polkuriera `[box,envelope,palette,small_parcel,parcel_size_20]` (lowercase wymagane przez API — odkryte podczas live testu), `splitStreetAndNumber()` rozdziela ulice regexem na `street`/`housenumber`/`flatnumber`, `buildRecipient` z payload `order_addresses` + override z formularza, `buildPickup` z domyslnym `nextBusinessDay()` + 10:00-16:00, `COD` z bank account z `company_settings`. Po sukcesie API: `extractOrderNumber` (priorytet `number` z SDK Order entity), `extractTrackingNumber` (priorytet `waybills[0].number` z OrderWaybill entity), synchroniczna proba `downloadLabel`. Diagnostyka: gdy `orderno=''`, zapisuje fragment surowej odpowiedzi do `shipment_packages.error_message`.
|
||||
- `src/Modules/Shipments/PolkurierTrackingService.php` — `final class implements ShipmentTrackingInterface`. `getDeliveryStatus($package)` woła `get_status`, parsuje `status_code`, mapuje przez `DeliveryStatus::normalizeWithOverrides('polkurier', ...)` z `delivery_status_mappings`. Graceful: null przy braku credentials/wyjatku API/braku `status_code`.
|
||||
- `src/Modules/Shipments/DeliveryStatus.php` — fallback URL sledzenia dla `provider='polkurier'`: `https://polkurier.pl/sledz-paczke/<tracking>`. Carrier_id routing (DPD/UPS/GLS/InPost/Pocztex) dziala automatycznie przez istniejacy `matchCarrierByName()` (carrier_id ustawiany na servicecode z polkuriera, np. "INPOST" lub "DPD" — substring match).
|
||||
- `routes/web.php` — `new PolkurierShipmentService(...)` w `ShipmentProviderRegistry`. Import `App\Modules\Settings\PolkurierApiClient` + `App\Modules\Shipments\PolkurierShipmentService`.
|
||||
- `src/Modules/Cron/CronHandlerFactory.php` — `new PolkurierTrackingService(new PolkurierApiClient(), new PolkurierIntegrationRepository($this->db, $this->integrationSecret), new DeliveryStatusMappingRepository($this->db))` w `ShipmentTrackingRegistry` w handlerze `shipment_tracking_sync`.
|
||||
- `src/Modules/Shipments/ShipmentController.php` — `prepare()` fetchuje `polkurierServices` przez registry i przekazuje do widoku. `create()` rozszerzony o pola `service_code`/`pickup_date`/`pickup_time_from`/`pickup_time_to` przekazywane do `createShipment()`.
|
||||
- `resources/views/shipments/prepare.php` — opcja "polkurier" w `#shipment-carrier-select`, panel `#shipment-polkurier-panel` z `<select id="shipment-polkurier-select">` (lista uslug z `available_carriers`), hidden `name="service_code"`, JS toggle (`showPanel`, `syncPolkurierFields`). Brak dedykowanego selektora punktu odbioru — operator wpisuje `receiver_point_id` w istniejacym text inpucie w sekcji Adres odbiorcy (np. `POP-RZE54`).
|
||||
- Nowa migracja `database/migrations/20260514_000115_seed_polkurier_delivery_status_mappings.sql` — 7 wpisow `provider='polkurier'`: `O`→`created`, `P`→`confirmed`, `A`→`cancelled`, `WP`→`in_transit`, `D`→`delivered`, `Z`→`returned`, `W`→`problem`. Idempotentne (`ON DUPLICATE KEY UPDATE`). Kody z oficjalnej tabeli ORDER_STATUS PDF v1.11.
|
||||
- `.paul/codebase/architecture.md` + `db_schema.md` — opisy fazy 128.
|
||||
|
||||
**Dlaczego:**
|
||||
- Phase 127 dostarczyl fundament (settings + test_auth_api). Bez ShipmentService polkurier byl tylko `wystawiony w hubie integracji ale niedzialajacy`. Operator chcial realnie nadawac paczki przez polkurier obok Apaczki — szczegolnie dla DPD/UPS/GLS/InPost gdzie polkurier oferuje lepsze ceny.
|
||||
- Pelny zakres (ShipmentService + TrackingService + UI prepare + delivery_status_mappings) w jednej fazie zgodnie z decyzja operatora z planu (clarifications, `delegation: off`, `autonomous: false` z checkpointem live testu na #114/#115).
|
||||
|
||||
**Live test iteracje (zarejestrowane podczas APPLY):**
|
||||
1. Pierwszy submit polkurier → "Blad tworzenia przesylki: Nie podano uslugi Apaczka." Przyczyna: `ReferenceError` na zmiennej `polkurierPointIdInput` (pozostalej po usunieciu duplikatu selektora punktu) w `clearHiddenFields()` → handler `carrierSelect.change` crashowal przed `showPanel()`, `provider_code` zostawal na PHP-renderowanej wartosci `apaczka`. Fix: usuniecie martwej referencji.
|
||||
2. Drugi submit → "Blad tworzenia przesylki: polkurier create_order: Typ paczki musi przyjmowac jeden z parametrow ze zbioru [box, envelope, palette, small_parcel, parcel_size_20]". Przyczyna: wysylanie `BOX` uppercase. Fix: `normalizeShipmentType()` z lowercase + aliasami.
|
||||
3. Trzeci submit → utworzona w polkurier, ale w orderPRO `status=pending` (brak orderno w parsing). Przyczyna: shape odpowiedzi `create_order` zwraca `Order` entity z polem `number` (nie `orderno`). Fix: `extractOrderNumber` z priorytetem `number` + fallback list + obsluga wrappera `{order:{...}}`. Etykieta poprawnie parsowana z pola `file` (response GetLabel.php).
|
||||
4. Czwarty test → etykieta A4 zamiast A6. Iteracja w bogus parametry (`format`/`label_size`/`paper_size`) wyslane do `get_label` — bez efektu, bo API ignoruje. Pobranie oficjalnej dokumentacji PDF potwierdzilo: `get_label` przyjmuje WYLACZNIE `orderno`. Rozmiar A4 vs A6 sterowany jest w panelu klienta polkurier.pl (Ustawienia konta → Preferencje etykiet), NIE przez API. Operator zmienil w panelu — etykieta A6 OK.
|
||||
|
||||
**Deviation vs PLAN:**
|
||||
- Plan deklarowal `delegation: off` — wykonane inline. Tasks 1-3 napisane przez orchestrator, checkpoint i Task 5/6 inline. Bez sub-agentow ze wzgledu na potrzebe szybkich iteracji po live test feedback (4 iteracje przed sukcesem) — spawn agentow zdublowalby koszt research z PDF.
|
||||
- Plan deklarowal AJAX endpoint `/shipments/polkurier/points` + UI selector punktow paczkomatowych (Task 3 action). USUNIETY po feedback operatora — istnieje juz pole "Punkt odbioru" w sekcji Adres odbiorcy, operator wpisuje ID recznie (np. `POP-RZE54`). Usuniety: `PolkurierShipmentService::lookupPickupPoints()`, `ShipmentController::polkurierPoints()`, route, JS handler.
|
||||
- AC-3 (TrackingService cron) dostarczony, ale niezweryfikowany na zywej bazie podczas APPLY (operator anulowal paczki w panelu polkurier po teście — cron tracking nie mial co pingowac). Dziala defensywnie (graceful null przy bledach), pierwszy realny passthrough nastapi przy nastepnej zywej paczce.
|
||||
- Plan zakladal seedowanie mapowan po zaobserwowaniu realnych statusow w live tescie. Seed wykonany bazujac na oficjalnej tabeli ORDER_STATUS z PDF v1.11 (kody O/P/A/WP/D/Z/W) zamiast obserwacji — bezpieczniejsze i wyczerpujace.
|
||||
- AC-1 wzmiankowal getAvailableCarriers/getParcelMachines/getPostOffices. `getInpostParcelMachines` i `getCourierPoints` zaimplementowane jako stuby na przyszle rozszerzenie UI, ale nie uzywane przez aktualny UI (operator wpisuje punkt recznie). `getPostOffices` POMINIETY — brak dedykowanej metody w SDK (jest tylko `inpost_parcel_machines` per courier, `pocztex_post_offices`, `kurier48_post_offices` — zlozenie tego w UI panel paczkomatow odlozone na kolejna faze).
|
||||
|
||||
**Follow-up:**
|
||||
- Operator musi uruchomic migracje gdy XAMPP MySQL online: `php bin/migrate.php` (utworzy 7 wpisow `provider='polkurier'`).
|
||||
- Po pierwszej zywej paczce w `in_transit` — weryfikacja crona `shipment_tracking_sync` (1x ping API polkuriera, zapis `delivery_status` + `delivery_status_raw` w `shipment_packages`).
|
||||
- Kolejne fazy v3.7: paczkomaty UI panel (`InpostParcelMachines`/`PocztexPostOffices`/`Kurier48PostOffices`), presety przesylek z `provider_code='polkurier'`, `OrderValuationV2` (wycena przed nadaniem).
|
||||
|
||||
## 2026-05-14 - Phase 127 Plan 01: polkurier Integration Foundation
|
||||
|
||||
**Co zrobiono:**
|
||||
- Nowa migracja `database/migrations/20260514_000114_create_polkurier_integration_settings.sql` — tabela `polkurier_integration_settings` (fixed `id=1`, FK do `integrations` CASCADE, kolumny: `login VARCHAR(190)`, `api_token_encrypted TEXT`, `default_label_format VARCHAR(8) DEFAULT 'PDF'`) + idempotentny seed rekordu `integrations.type='polkurier'`, `base_url='https://api.polkurier.pl/'`.
|
||||
- `src/Modules/Settings/PolkurierIntegrationRepository.php` — single-instance repository (mirror `HostedSmsIntegrationRepository`): `getSettings()` zwraca `has_api_token: bool` zamiast plaintext, `saveSettings()` szyfruje Token API przez `IntegrationSecretCipher`, `getCredentials()` gatuje na `is_active=1`, `getIntegrationId()` jako single source of truth.
|
||||
- `src/Modules/Settings/PolkurierApiClient.php` — POST do `https://api.polkurier.pl/` z JSON body `{authorization:{login,token}, apimetod, data}`. Endpoint test = `apimetod="test_auth_api"`. cURL z `SslCertificateResolver::resolve()`, PHP 8.5 compatible (brak `curl_close()`). Stuby createShipment/getLabel/getStatus/cancelOrder rzucaja RuntimeException — do implementacji w kolejnych fazach.
|
||||
- `src/Modules/Settings/PolkurierIntegrationController.php` — endpointy `GET /settings/integrations/polkurier`, `POST .../save`, `POST .../test` (CSRF `_token`). `test` zapisuje wynik przez `IntegrationsRepository::updateTestResult()`.
|
||||
- `resources/views/settings/polkurier.php` — formularz konfiguracji + przycisk realnego testu polaczenia. Wszystkie alerty przez komponent `resources/views/components/alert.php` (Phase 120 contract).
|
||||
- `src/Modules/Settings/IntegrationsHubController.php` — dodany parametr `PolkurierIntegrationRepository $polkurier` i metoda `buildPolkurierRow()`; wiersz polkurier wstawiony zaraz po Apaczka.
|
||||
- `routes/web.php` — DI wiring `PolkurierIntegrationRepository` + `PolkurierIntegrationController`, rozszerzony ctor `IntegrationsHubController`, 3 nowe routy `/settings/integrations/polkurier{,/save,/test}`.
|
||||
- `resources/lang/pl.php` — sekcja `settings.polkurier.*` (title/description/fields/hints/token/status/actions/flash) + `settings.integrations_hub.providers.polkurier`.
|
||||
- `.paul/codebase/db_schema.md` + `architecture.md` — opisy fazy 127.
|
||||
|
||||
**Dlaczego:**
|
||||
- Operator dostaje drugiego brokera kurierskiego rownolegle z Apaczka (decyzja w `.paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md`, clarifications).
|
||||
- Single-instance bo polkurier to jedno konto operatora (mirror Apaczka/InPost/HostedSMS/SMSPLANET).
|
||||
- Faza zamyka tylko warstwe ustawien + realny test (`apimetod=test_auth_api`); tworzenie przesylek, etykiety, tracking i mapowania metod dostawy beda w kolejnych fazach — analogicznie do tego jak Phase 116/117 zamknely tylko fundament HostedSMS/SMSPLANET.
|
||||
|
||||
**Deviation vs PLAN:**
|
||||
- AC-1 wymagal kolumny `environment ENUM('production','sandbox')`. polkurier nie ma srodowiska sandbox (jeden produkcyjny endpoint `https://api.polkurier.pl/`), wiec kolumna `environment` zostala POMINIETA jako YAGNI.
|
||||
- AC-1/AC-2 wymagaly tylko `api_token_encrypted`. polkurier API wymaga `login + token` razem w `authorization` (zweryfikowane w oficjalnym SDK https://github.com/Polkurier/polkurier-sdk — pliki `Auth.php`/`Request.php`/`Config.php`), wiec dodana kolumna `login VARCHAR(190)` z walidacja serwerowa.
|
||||
- Plan deklarowal `delegation: auto` (sub-agents). Zadania wykonane inline z powodu swiezo zgromadzonego research o API polkuriera (Config/Auth/Request/Methods z SDK); spawn agentow powtorzylby ten research. Decyzja chroni kontekst i czas. Boundaries i acceptance criteria niezmienione.
|
||||
|
||||
**BREAKING:** brak.
|
||||
|
||||
## 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
|
||||
|
||||
**Co zrobiono:**
|
||||
- Bugfix #1089: shopPRO order z `firm_nip` (bez kluczy z 5-elementowej listy `shouldRequestInvoice`) nie ustawial `invoice_requested=1` przy imporcie. Mapper wykrywal poprawnie (`is_invoice` przez heurystyke NIP), ale `ShopproOrdersSyncService::shouldRequestInvoice` mial wezsza liste kluczy -> UI w zakladce Platnosci wyswietlal odznaczony checkbox.
|
||||
- `ShopproOrderMapper::mapOrderAggregate()` zwraca teraz top-level klucz `invoice_detected` (wynik `resolveInvoiceRequested($payload)`). Klucz `is_invoice` usuniety z tablicy `order` (nie odpowiada juz zadnej kolumnie DB).
|
||||
- `ShopproOrdersSyncService::importOne()` propaguje `!empty($aggregate['invoice_detected'])` do `setInvoiceRequested(true)` zamiast wlasnej heurystyki. Stara metoda `shouldRequestInvoice` usunieta (zastapiona heurystyka mappera — zero duplikacji).
|
||||
- `AllegroOrderImportService::shouldRequestInvoice($payload)` (nowa prywatna metoda) — rozszerza detekcje o `invoice.naturalPerson === false`, `invoice.address.taxId`, `invoice.companyName`/`invoice.address.company.name`. Wczesniej tylko `invoice.required` -> analogiczna luka jak shopPRO dla klientow Allegro z NIP bez `required=true`.
|
||||
- Migracja `20260513_000113_drop_orders_is_invoice_and_backfill_invoice_requested.sql`:
|
||||
- Idempotentny guard przez `information_schema.COLUMNS` + prepared statements (DDL no-op gdy kolumna juz nie istnieje, pattern z Key Decision 2026-05-10).
|
||||
- Backfill: `UPDATE orders SET invoice_requested=1 WHERE is_invoice=1 AND invoice_requested=0` (7 zamowien na produkcji, w tym #1089).
|
||||
- DROP COLUMN `orders.is_invoice` (legacy z Phase 115, dryft wzgledem `invoice_requested`).
|
||||
- `OrderImportRepository::insertOrder()` SQL — usuniety `is_invoice` z kolumn INSERT i `:is_invoice` z VALUES. `orderParams()` — usunieta linia mapowania. Docstring `updateOrderDelta()` (Phase 112) — usunieta wzmianka.
|
||||
- `OrdersRepository` — usuniety `o.is_invoice` z SELECT (`paginate` query) i `transformOrderRow()` hydrate (klucz `is_invoice` nie wystepuje juz w zwracanych row'ach).
|
||||
|
||||
**Dlaczego:**
|
||||
- Bug #1089: zamowienie shopPRO z fakturowymi danymi firmowymi mialo `is_invoice=1` (mapper) ale `invoice_requested=0` (sync service). UI pokazywal odznaczony checkbox -> przycisk "Wystaw fakture" niedostepny -> operator musial recznie klikac toggle.
|
||||
- Dryft: Phase 115 zostawila dwie kolumny dla tej samej semantyki (`is_invoice` legacy + `invoice_requested` nowy). Dwie sciezki detekcji (mapper vs. sync service) mialy rozna szerokosc heurystyki -> systematyczny rozjazd dla shopPRO orders z `firm_name`/`firm_nip`.
|
||||
- Fix architekturalny: jedno zrodlo prawdy (`invoice_requested`), jedna heurystyka per zrodlo (mapper dla shopPRO, prywatna metoda dla Allegro).
|
||||
|
||||
**BREAKING:**
|
||||
- Kolumna `orders.is_invoice` przestaje istniec po migracji `20260513_000113_*`. Wewnetrzny kontrakt — nie wystepuje w API ani odpowiedziach JSON. Jezeli ktokolwiek (skrypt operatora, raport custom) czytal `is_invoice` z DB -> przelaczyc na `invoice_requested`.
|
||||
- `ShopproOrderMapper::mapOrderAggregate()` zwraca teraz dodatkowy top-level klucz `invoice_detected` (boolean). Klucz `is_invoice` znika z podtablicy `order` (nie odpowiada juz kolumnie DB).
|
||||
|
||||
## 2026-05-12 - Phase 124 Plan 01: SMS Templates
|
||||
|
||||
**Co zrobiono:**
|
||||
- Nowa tabela `sms_templates(id, name, body, is_active, created_at, updated_at)` + indeks `(is_active, name)` — migracja `20260512_000112_create_sms_templates.sql` (DDL).
|
||||
- `Sms\SmsTemplateRepository` z minimalnym CRUD (`listAll/listActive/findById/save/delete/toggleStatus`); walidacja name+body w `save()`.
|
||||
- `Sms\SmsVariableResolver` (wydzielony z `Email\VariableResolver`) — wspolna logika `buildVariableMap` + `resolve` dla Email i SMS (placeholdery `{{zamowienie.*|kupujacy.*|adres.*|firma.*|przesylka.*}}`).
|
||||
- `Email\VariableResolver` zrefaktorowany na fasade — konstruktor opcjonalnie przyjmuje SmsVariableResolver; metody publiczne deleguja. `EmailSendingService` bez zmian.
|
||||
- `Settings\SmsTemplateController` + widoki `settings/sms-templates.php` (lista) + `settings/sms-templates-form.php` (CRUD form z paleta zmiennych po prawej, licznikiem znakow, walidacja maxlength 918).
|
||||
- 7 nowych rout: `GET/POST /settings/sms-templates`, `/create`, `/edit`, `/save`, `/delete`, `/toggle`, `/variables`.
|
||||
- Dropdown "Wybierz szablon" w zakladce SMS na `/orders/{id}` (renderowany tylko gdy istnieja aktywne szablony) -> JS module `sms-template-picker.js` fetchuje `GET /orders/{id}/sms/template?template_id=N` i wkleja rozwiniete body do textarea. Przy niepustej textarea pyta przez `OrderProAlerts.confirm({...})` (options-object API).
|
||||
- `OrdersController::smsTemplate()` — nowy endpoint JSON; rozszerzony konstruktor o `?SmsTemplateRepository`, `?SmsVariableResolver`, `?CompanySettingsRepository` (default null = BC).
|
||||
- Sidebar Ustawien rozszerzony o link "Szablony SMS" (active state na `currentSettings === 'sms-templates'`).
|
||||
- Nowy SCSS partial `modules/_sms-templates.scss` (paleta zmiennych, licznik znakow). Import w `app.scss`.
|
||||
- Tlumaczenia `orders.details.sms.template_picker(_placeholder)` w `resources/lang/pl.php`.
|
||||
|
||||
**Dlaczego:**
|
||||
- Operator wysyla powtarzalne SMS-y (numer sledzenia, przypomnienie o platnosci, prosba o opinie). Szablony eliminuja recznie wpisywanie tekstu i tracking number, redukujac wysylke do dropdown + ewentualnej korekty.
|
||||
- Wspolny VariableResolver bo dokladnie te same placeholdery sa potrzebne w Email i SMS (DRY); zachowanie kontraktu `Email\VariableResolver` jako fasady = zero ryzyka regresji w EmailSendingService.
|
||||
- Stopka SMSPLANET pozostaje doklejana wylacznie przez `SmsConversationService::buildFinalOutboundBody()` (Phase 122) — nie duplikujemy jej w szablonach, walidacja 918 znakow obowiazuje na finalnej tresci.
|
||||
|
||||
**Migracja:**
|
||||
- `php bin/migrate.php` po wlaczeniu MySQL — utworzy `sms_templates`.
|
||||
- Operator po wdrozeniu tworzy szablony manualnie z `/settings/sms-templates`.
|
||||
|
||||
**BREAKING:** brak. `OrdersController` ctor: nowe params optional. `Email\VariableResolver` ctor: nowy opcjonalny drugi argument (default null = self-construct SmsVariableResolver).
|
||||
|
||||
## 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
|
||||
|
||||
**Co zrobiono:**
|
||||
- Poprawiono inbound SMSPLANET: dopasowanie po telefonie uzywa `order_addresses.phone`, a nie nieistniejacego w produkcji `orders.buyer_phone`.
|
||||
- Dodano GET dla `/webhooks/smsplanet/inbound` obok POST, dekodowanie formatu 2WAY `message=<JSON>`, odpowiedz plain `OK` po sukcesie i odporniejsze scalanie JSON body z parametrami requestu.
|
||||
|
||||
**Dlaczego:**
|
||||
- Publiczny POST webhooka zwracal 422 przez blad SQL `Unknown column 'o.buyer_phone'`, wiec odpowiedzi SMS nie byly zapisywane.
|
||||
|
||||
## 2026-05-12 - Phase 122 Plan 01: SMSPLANET Default SMS Footer
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano migracje `20260512_000111_smsplanet_default_footer.sql` z kolumna `smsplanet_integration_settings.default_footer`.
|
||||
- Rozszerzono konfiguracje SMSPLANET o opcjonalna stopke SMS z limitem 300 znakow.
|
||||
- Testowa wysylka oraz SMS z zamowienia dopinaja stopke przez pusta linie, waliduja finalna tresc w limicie 918 znakow i zapisuja finalne body w historii rozmowy.
|
||||
|
||||
**Dlaczego:**
|
||||
- Operator ma utrzymywac jeden wspolny podpis firmy bez recznego kopiowania go do kazdej wiadomosci SMS.
|
||||
|
||||
**Weryfikacja:**
|
||||
- Do uzupelnienia po APPLY.
|
||||
|
||||
## 2026-05-12 - Phase 121 Plan 01: SMSPLANET Conversation + Notifications
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano migracje `20260512_000110_smsplanet_conversation_notifications.sql` z tabelami `sms_messages`, `notifications` oraz polami `sender_mode` i `sender_phone`.
|
||||
- Dodano backend `Sms` i `Notifications`, publiczny webhook SMSPLANET, zakladke SMS w zamowieniu, centrum powiadomien, topbar badge i polling JS.
|
||||
- Usunieto tymczasowy override testowego nadawcy SMSPLANET; API uzywa wybranego trybu nadawcy.
|
||||
- Poprawiono migracje po pierwszej probie na bazie: rzeczywiste `orders.id` ma typ `BIGINT UNSIGNED`, wiec `sms_messages.order_id` i `notifications.related_order_id` tez musza miec `BIGINT UNSIGNED`.
|
||||
|
||||
**Dlaczego:**
|
||||
- Operator ma prowadzic dwukierunkowa rozmowe SMSPLANET w szczegolach zamowienia i widziec nowe odpowiedzi klientow globalnie.
|
||||
|
||||
**Weryfikacja:**
|
||||
- `php -l` PASS dla nowych/zmienionych PHP.
|
||||
- `npm run build:css` PASS.
|
||||
- Migracja PASS przez techniczne polaczenie `DB_HOST_REMOTE`; manualne smoke testy UI/webhook nadal wymagaja sesji w aplikacji.
|
||||
- `sonar-scanner` niedostepny w PATH.
|
||||
|
||||
## 2026-05-12 - Phase 120 Plan 01: Alert Component Unification
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano komponent `resources/views/components/alert.php` (params: `$type` info|success|warning|danger, `$message`/`$messageHtml`, `$dismissible`, `$role`) renderujacy `.alert` z inline SVG ikona, body i opcjonalnym przyciskiem dismiss.
|
||||
- SCSS `_ui-components.scss`: `.alert` zmieniony na flex z `.alert__icon`/`.alert__body`/`.alert__dismiss`; dodany brakujacy wariant `.alert--info` (#eff6ff/#bfdbfe/#1e3a8a); dodany wrapper `.alerts-stack`.
|
||||
- Vanilla JS `public/assets/js/modules/alert-dismiss.js` z idempotent guardem i delegated handlerem na `[data-alert-dismiss]`.
|
||||
- `Flash` rozszerzony o `push(string $type, string $message): void` i `all(): array` z BC dla `set/get` — `all()` konsumuje typowana kolejke + skanuje legacy `_flash` z heurystyka klucza (error/danger/fail → danger, warning → warning, success/.save/.created/.deleted/.toggled → success, reszta → info).
|
||||
- Layouty `app.php`, `auth.php`, `public.php` na poczatku content area iteruja `Flash::all()` przez komponent w `.alerts-stack` i ladja `alert-dismiss.js`.
|
||||
- Migracja 36 widokow (34 z planu + `orders/show.php` + `shipments/prepare.php`): inline `<div class="alert alert--TYPE">` zastapione `include components/alert.php`; `.flash--error`/`.flash--success` w starych widokach takze przeniesione na komponent.
|
||||
|
||||
**Dlaczego:**
|
||||
- Po teste polaczenia Fakturowni alert `OK (HTTP 200)` mial klase `alert--info`, ale `.alert--info` nie istnial — efekt: czarny tekst na bialym tle bez ikony. Zglosenie operatora po Phase 118.
|
||||
- Wiele wzorcow alertow w 36 widokach roznilo sie szczegolami (mt-12/role/struktura), brak komponentu wielokrotnego uzytku. Po Phase 120 jeden plik (`components/alert.php`) jest jedynym renderem markupu alert + centralny renderer flash w 3 layoutach przyjmuje przyszle `Flash::push()` bez koniecznosci powtarzania kodu w widokach.
|
||||
|
||||
**Boundaries (zachowane):**
|
||||
- Kontrolery NIE zmieniane — `Flash::set/get` BC; wzorce `Flash::set('module.key', '...')` + view local `$flashXxx` dzialaja jak dotychczas (widok renderuje przez komponent).
|
||||
- Modul `resources/modules/jquery-alerts` (dialogi `OrderProAlerts`) niezmieniany — osobny system.
|
||||
- `email-mailboxes.php` JS-generowane alerty AJAX testu SMTP — pozostawione bez zmian (uzywaja `.alert` SCSS, brak markupu komponentu — out of scope).
|
||||
- `.flash--*` SCSS nie usuniety (deferred cleanup) — widoki juz go nie uzywaja.
|
||||
- Build SCSS → CSS poza zakresem; `public/assets/css/app.css` musi byc zregenerowany lokalnie po wdrozeniu.
|
||||
|
||||
## 2026-05-12 - Phase 119 Plan 01: Re-import total_paid Protection
|
||||
|
||||
**Co zrobiono:**
|
||||
- `OrderImportRepository::updateOrderDelta()` przebudowane na dynamiczny SQL builder: `total_paid` i `is_canceled_by_buyer` są dołączane do UPDATE warunkowo. Gdy `payment_status` w bazie == `payment_status` z payloadu źródła, `total_paid` NIE jest aktualizowany. `is_canceled_by_buyer` jest pomijany w tej samej sytuacji, chyba że źródło flaguje anulowanie (`$cancelledBySource=true`) — wtedy zawsze wpisywane.
|
||||
- `upsertOrderAggregate()` wylicza `$paymentStatusUnchanged` przed wywołaniem delty i propaguje wraz z `$cancelledBySource`.
|
||||
- Test PHPUnit `tests/Unit/OrderImportRepositoryTest.php` z 3 scenariuszami (preserve / transition / cancel propagation). Uruchomienie odroczone — `vendor/` nieobecne w środowisku, składnia PHP zweryfikowana przez `php -l`.
|
||||
|
||||
**Dlaczego:**
|
||||
- Incydent zamówienia #976: operator usunął 2 pozycje Girlanda i zwrócił klientowi 28,00 PLN, obniżając `total_paid` ze 119,00 na 91,00. Audyt re-importu (Phase 112-01 delta-only) wykazał, że istniejące `updateOrderDelta` nadpisywało `total_paid` z payloadu źródła przy każdym wywołaniu, jeśli identical-payload guard nie zadziałał. Ręczne korekty kwoty były ulotne.
|
||||
|
||||
**Boundaries (zachowane):**
|
||||
- `paymentTransition` (Phase 111) i `statusOverwriteAllowed` (Phase 62) działają bez zmian.
|
||||
- Cancel propagation (`$cancelledBySource` override) z Phase 112-01 nadal wymusza `status_code='anulowane'` i — od Phase 119 — `is_canceled_by_buyer=1` nawet gdy `payment_status` stabilne.
|
||||
- Identical-payload no-op guard (Phase 112-01) nadal pierwsza linia obrony — Phase 119 dotyczy tylko ścieżki gdy guard nie zadziałał (payload się różni, ale `payment_status` stabilne).
|
||||
|
||||
## 2026-05-12 - Phase 118 Plan 01: Fakturownia Single Instance
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano migracje `20260512_000109_fakturownia_single_instance.sql`, ktora wybiera aktywna instancje Fakturowni, przepina delegowane `invoice_configs.integration_id` na jeden globalny rekord i usuwa nadmiarowe konta Fakturowni po przepieciu zaleznosci.
|
||||
- Przebudowano `FakturowniaIntegrationRepository` na jedna globalna konfiguracje (`getSettings`, `saveSettings`, `getIntegrationId`, `getCredentials`) z kompatybilnym `findAll()` zwracajacym jeden element.
|
||||
- Uproszczono `FakturowniaIntegrationController` i widok `/settings/integrations/fakturownia` do pojedynczego formularza konfiguracji i testu polaczenia.
|
||||
- Hub integracji pokazuje Fakturownie jako jedna instancje, bez licznika kont.
|
||||
- Zapis delegowanej konfiguracji faktury ustawia `invoice_configs.integration_id` na globalny rekord Fakturowni; UI konfiguracji faktury nie pokazuje juz selecta kont.
|
||||
|
||||
**BREAKING / migracja:**
|
||||
- Po migracji nie ma juz wielu kont Fakturowni. Jesli baza miala wiele rekordow `integrations.type='fakturownia'`, zachowany zostaje aktywny rekord (fallback: uzywany przez konfiguracje faktur, potem najnizsze id), a pozostale sa usuwane.
|
||||
|
||||
## 2026-05-12 - Phase 117 Plan 01: SMSPLANET Integration Settings + Test SMS
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano migracje `20260512_000108_create_smsplanet_integration_settings.sql` z pojedyncza konfiguracja `smsplanet_integration_settings` i bazowym wpisem `integrations` typu `smsplanet`.
|
||||
- Dodano `SmsplanetIntegrationRepository` z obsluga metod autoryzacji `token` oraz `key_password` i szyfrowaniem sekretow przez `IntegrationSecretCipher`.
|
||||
- Dodano `SmsplanetApiClient` dla SMSPLANET (`POST https://api2.smsplanet.pl/sms`) z obsluga Bearer token oraz `key` + `password`.
|
||||
- Dodano `SmsplanetIntegrationController` i trasy `/settings/integrations/smsplanet`, `/save`, `/test`.
|
||||
- Dodano widok `resources/views/settings/smsplanet.php` z konfiguracja i realna wysylka testowego SMS z edytowalna trescia oraz panelem ostatniego testu (`OK`, HTTP, `messageId`).
|
||||
- Dodano SMSPLANET do hubu integracji `/settings/integrations`.
|
||||
- Poprawiono import `IntegrationSecretCipher`, aby rzucal istniejacy `App\Core\Exceptions\IntegrationConfigException`.
|
||||
|
||||
**Dlaczego:**
|
||||
- Operator potrzebuje drugiej bramki SMS analogicznej do HostedSMS, ale bez uruchamiania jeszcze automatyzacji lub historii wysylek.
|
||||
- SMSPLANET wspiera dwa warianty autoryzacji, wiec konfiguracja przechowuje wszystkie sekrety w formie szyfrowanej i waliduje wymagania zalezne od wyboru operatora.
|
||||
- Test uzywa rzeczywistej wysylki, bo celem tej fazy jest potwierdzenie realnej sciezki API.
|
||||
|
||||
## 2026-05-12 - Phase 116 Plan 01: HostedSMS Integration Settings + Test SMS
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano migracje `20260512_000107_create_hostedsms_integration_settings.sql` z pojedyncza konfiguracja `hostedsms_integration_settings` i bazowym wpisem `integrations` typu `hostedsms`.
|
||||
- Dodano `HostedSmsIntegrationRepository` z szyfrowaniem hasla przez `IntegrationSecretCipher`.
|
||||
- Dodano `HostedSmsApiClient` dla HostedSMS SimpleAPI (`POST https://api.hostedsms.pl/SimpleApi`).
|
||||
- Dodano `HostedSmsIntegrationController` i trasy `/settings/integrations/hostedsms`, `/save`, `/test`.
|
||||
- Dodano widok `resources/views/settings/hostedsms.php` z konfiguracja i realna wysylka testowego SMS z edytowalna trescia oraz czytelnym panelem ostatniego testu (`OK`, HTTP, MessageId).
|
||||
- Dodano HostedSMS do hubu integracji `/settings/integrations`.
|
||||
|
||||
**Dlaczego:**
|
||||
- Operator potrzebuje najpierw zapisac dane HostedSMS i sprawdzic realna wysylke SMS, zanim integracja zostanie wykorzystana w automatyzacjach lub komunikacji z klientami.
|
||||
- Test uzywa rzeczywistej wysylki, bo SimpleAPI nie udostepnia osobnego endpointu ping/test.
|
||||
- Haslo nie jest ujawniane po zapisie; UI pokazuje tylko status zapisanego sekretu.
|
||||
|
||||
## 2026-05-10 - Phase 115 Plan 01: Wystawianie faktury z zamowienia
|
||||
|
||||
**Co zrobiono:**
|
||||
- **GUS lookup (Task 3b):** `FakturowniaApiClient::lookupClientByNip()` — GET `/clients/gus.json?nip=...&api_token=...` z walidacja NIP (10 cyfr) i parsowaniem `{name, tax_no, street, post_code, city, country}`. `InvoiceController::gusLookup()` jako AJAX endpoint `GET /api/fakturownia/gus-lookup?nip=...&config_id=...` — resolwer konta Fakturownia (preferuje `invoice_configs.integration_id` gdy delegated, fallback na pierwsze aktywne konto type='fakturownia'). `invoice_form.php` ma przycisk "Pobierz z GUS" obok pola NIP — vanilla JS fetch + auto-fill `buyer_company_name/street/postal_code/city`. Czyni "wystaw fakture" jednoklikowym po wpisaniu NIP. `InvoiceController` ctor rozszerzony o 2 deps (`FakturowniaIntegrationRepository`, `FakturowniaApiClient`).
|
||||
- `InvoiceRepository` (nowy) — CRUD dla `invoices`: `findByOrderId`, `findById`, `insertLocal`, `insertDelegated`, `nextLocalNumber` (atomowy INSERT ON DUPLICATE KEY UPDATE na `invoice_number_counters`), `paginate` z filtrami search/config/mode/date.
|
||||
- `InvoiceService` (nowy) — orchestrator wystawiania:
|
||||
- `issue()` rozgalezia na `issueLocal()` (numer lokalny + Dompdf-ready snapshot) lub `issueDelegated()` (POST do Fakturowni PRZED INSERT lokalnym; na sukcesie zapis `external_invoice_id`/`external_pdf_url`/`invoice_number` z odpowiedzi).
|
||||
- Static `extractBuyerTaxNumber()` parsuje NIP z roznych sciezek payload_json (Allegro `invoice.address.taxId`, shopPRO `buyer.tax_number` i pokrewne).
|
||||
- Snapshot pattern (snapshoty seller/buyer/items z Phase 8 wzorca) + obliczanie netto/brutto z VAT per pozycja.
|
||||
- `buildFakturowniaPayload()` mapuje na format `https://app.fakturownia.pl/api`.
|
||||
- `InvoiceIssueException` (nowy) — typed exception dla bledow biznesowych.
|
||||
- `FakturowniaApiClient` rozszerzony — nowe `createInvoice()` (POST `/invoices.json` z body `{api_token, invoice}`, parsuje response na `id/number/view_url/pdf_url/raw`) i `buildPdfUrl()` (helper bez fetcha, do redirect 302). Stary stub `downloadPdf()` zastapiony.
|
||||
- `InvoiceController` (nowy) — endpointy dla zamowienia (`create`, `store`, `show`, `pdf`) i lista `issuedList`. PDF: lokalna -> Dompdf inline (mirror `ReceiptController::pdf`); delegowana -> redirect 302 na `external_pdf_url`. Walidacja `invoice_requested=1` przed otwarciem formularza.
|
||||
- `OrdersController::toggleInvoiceRequested` (nowy) — AJAX endpoint POST `/orders/{id}/invoice-requested/toggle` z CSRF + `recordActivity('invoice_requested_changed')`.
|
||||
- `OrdersRepository::setInvoiceRequested(int, bool)` (nowy) — UPDATE `orders.invoice_requested`.
|
||||
- `OrdersController::show()` rozszerzone — przekazuje `invoices` + `invoiceConfigs` do widoku przez 2 nowe optional ctor params (`InvoiceRepository`, `InvoiceConfigRepository`).
|
||||
- `AllegroOrderImportService::importSingleOrder` — przy `wasCreated=true` i `payload.invoice.required` -> ustawia `orders.invoice_requested=1` (delta-only re-import nie nadpisuje manualnej flagi).
|
||||
- `ShopproOrdersSyncService::shouldRequestInvoice()` (nowy) — flexible parser sprawdzajacy `wants_invoice`, `invoice_required`, `invoice.required`, `buyer.wants_invoice`, `buyer.invoice` w raw payload. Dziala tylko przy pierwszym imporcie.
|
||||
- 4 nowe widoki:
|
||||
- `resources/views/accounting/invoice_form.php` — formularz wystawiania (config select z label "Lokalnie/Fakturownia: {prefix}", NIP prefilled z payload, manualne nadpisanie buyer fields, podglad pozycji + sprzedawca; confirm dialog dla powtornego wystawienia uzywa `OrderProAlerts.confirm` z options-object API).
|
||||
- `resources/views/accounting/invoice_preview.php` — HTML preview faktury z badgem trybu (Lokalnie/Fakturownia: {prefix}) + dual-button PDF.
|
||||
- `resources/views/accounting/invoice_pdf.php` — Dompdf template "FAKTURA VAT" z parties section, items table (netto/brutto/VAT per pozycja), termin platnosci, konto bankowe.
|
||||
- `resources/views/accounting/invoices_issued_list.php` — lista wystawionych faktur z filtrami (search/config/mode/date_from/date_to) + paginacja + badge Tryb (Lokalnie / Fakturownia: {prefix}).
|
||||
- `resources/views/orders/show.php` — header dostal `data-invoice-button-wrap` z przyciskiem "Wystaw fakture" (visible tylko gdy `invoice_requested=1`); pod headerem checkbox `data-invoice-requested-toggle` (auto-bound przez `invoice-requested-toggle.js`); pod tabem "Documents" nowa sekcja "Faktury" z badgem Tryb i akcjami Podglad/PDF.
|
||||
- `resources/views/settings/accounting.php` — link "Faktury wystawione" w karcie Faktury.
|
||||
- `public/assets/js/modules/invoice-requested-toggle.js` (nowy) — vanilla JS, AJAX POST z `_token` przy `change`, rollback checkbox state przy bledzie, optimistic show/hide `data-invoice-button-wrap`. Idempotent guard `data-bound`.
|
||||
- `resources/views/layouts/app.php` — rejestracja `invoice-requested-toggle.js` z cache-busting.
|
||||
- 6 nowych routes: `POST /orders/{id}/invoice-requested/toggle`, `GET /orders/{id}/invoice/create`, `POST /orders/{id}/invoice/store`, `GET /orders/{id}/invoice/{invoiceId}`, `GET /orders/{id}/invoice/{invoiceId}/pdf`, `GET /settings/accounting/invoices/issued`. Wszystkie pod `AuthMiddleware`.
|
||||
- DI wiring w `routes/web.php`: `InvoiceRepository`, `InvoiceService`, `InvoiceController`. `OrdersController` instantiation rozszerzona o 2 params (`$invoiceRepository`, `$invoiceConfigRepository`).
|
||||
- Docs: `architecture.md` (nowa sekcja "Phase 115"), `tech_changelog.md`, `todo.md` (INVOICE-IDEMP-115).
|
||||
|
||||
**Dlaczego:**
|
||||
- Plan zatwierdzony jako pelny vertical slice — Phase 113 (DB) + 114 (configs CRUD) byly fundamentem; bez wystawiania z zamowienia funkcja byla bezuzyteczna operacjonalnie.
|
||||
- Kolejnosc "POST do Fakturowni najpierw, INSERT lokalny po sukcesie" (per user decision) gwarantuje ze nie ma orphan rows w `invoices` gdy API padnie. Trade-off: brak idempotencji przy double-POST -> notatka INVOICE-IDEMP-115.
|
||||
- Auto-set `invoice_requested` z importu pozwala operatorowi nie myslec o flagi dla typowych przypadkow (Allegro `invoice.required=true`, shopPRO klienci wpisujacy NIP). Manualny toggle dla edge cases / korekty.
|
||||
- Dual PDF flow (Dompdf lokalny / redirect 302 do Fakturowni) — operator dostaje "natywny" PDF z Fakturowni dla delegowanych (ten sam co ksiegowy widzi w Fakturowni), brak ryzyka rozjazdu wygladu/numeracji.
|
||||
- `is_delegated=0` config zachowany jako pelny tryb lokalny — dla scenariuszy gdzie operator nie chce/nie ma konta Fakturowni.
|
||||
|
||||
**BREAKING:** Brak. `OrdersController` ctor dostal 2 NEW optional params (default null) — backwards compatible.
|
||||
|
||||
**Szczegolne uwagi:**
|
||||
- `invoice-config-form.js` (Phase 114) i `invoice-requested-toggle.js` (Phase 115) — dwa rozne moduly, oba w `layouts/app.php`. Nazwa "invoice-*" ale rozne kontekstowo (jeden formularz config, drugi zamowienie).
|
||||
- `OrderProAlerts.confirm` w `invoice_form.php` uzywa options-object API zgodnie z memory (Phase 114 fix).
|
||||
- Migracje no-op nie potrzebne — Phase 113 dostarczyla `orders.invoice_requested`, `invoice_*` tabele i `fakturownia_integration_settings`.
|
||||
- Skill audit: brak required skills dla tego planu.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-10 - Phase 114 Plan 01: Accounting Configs Refactor
|
||||
|
||||
**Co zrobiono:**
|
||||
- Migracja `20260511_000107_seed_default_invoice_config.sql` - idempotentny seed `Domyslny VAT` config (format `FV/%N/%M/%Y`, monthly, lokalna numeracja, payment_to_days=7) przez `NOT EXISTS` guard.
|
||||
- `InvoiceConfigRepository` - pelne CRUD `invoice_configs` z walidacja serwerowa wszystkich pol + krytyczna regula delegacji: `is_delegated=1` wymaga `integration_id` z `integrations.type='fakturownia'`. `delete()` pre-checkuje `invoices` zeby zwrocic PL komunikat zamiast SQLSTATE.
|
||||
- `InvoiceConfigController` - index/edit/save/toggle/delete dla `/settings/accounting/invoices`. Flash `accounting.invoices.save/.error`. Edycja na osobnej podstronie.
|
||||
- `ReceiptConfigController` refactor - rozdzielenie `index()` na `hub()` + `list()` + nowa `edit()`. Save/toggle/delete redirectuja na `/settings/accounting/receipts` (nie hub).
|
||||
- 4 nowe widoki:
|
||||
- `accounting.php` (REFAKTOR) - hub-rozdroze z 2 kartami Paragony/Faktury (usunieta lista i formularz inline).
|
||||
- `accounting-receipts.php` - lista paragonow w stylu spojnym z fakturami (`table.table + badge`).
|
||||
- `accounting-receipt-edit.php` - formularz paragonu na osobnej podstronie.
|
||||
- `accounting-invoices.php` - lista faktur (7 kolumn z dodatkami Tryb, Konto Fakturowni).
|
||||
- `accounting-invoice-edit.php` - formularz faktury z conditional `integration_id` select.
|
||||
- `invoice-config-form.js` - vanilla JS toggle dla `is_delegated` checkbox -> show/hide `integration_id` select wrapper + dynamiczny `required`.
|
||||
- `layouts/app.php` - rejestracja nowego modulu JS `invoice-config-form.js` (z cache-busting `?ver=mtime`).
|
||||
- Routy: 12 nowych endpointow ksiegowosci (6 receipts + 6 invoices) + 3 legacy aliasy starych `/settings/accounting/save|toggle|delete`.
|
||||
- Docs: `db_schema.md` (notka o seed), `architecture.md` (nowa sekcja "Phase 114"), tech_changelog.
|
||||
|
||||
**Dlaczego:**
|
||||
- Phase 113 dostarczyl tabele `invoice_configs` - bez UI/CRUD nie da sie operacjonalnie wystawiac faktur. Phase 115 (wystawianie faktury z zamowienia) wymaga gotowych invoice_configs.
|
||||
- Edycja paragonu pod tabela na `/settings/accounting` (stary uklad) miala 2 problemy: dlugi scroll przy wielu configach + brak miejsca na rosnacy formularz faktury (z conditional fields). Refaktor na osobne podstrony rozwiazuje oba.
|
||||
- Seed `Domyslny VAT` - operator od razu po deployment moze wystawiac faktury bez recznego skonfigurowania (zerowy onboarding).
|
||||
- Legacy aliasy `/settings/accounting/save|toggle|delete` - istniejace formularze w cache'u przegladarki / bookmarki / zewnetrzne narzedzia (jesli sa) dalej dzialaja bez 404.
|
||||
- JS toggle - operator nie zaznaczy delegacji bez wybrania konta (UX + serwerowa walidacja jako backup).
|
||||
|
||||
**BREAKING:**
|
||||
- Brak. Stare endpointy zachowane jako aliasy. Stary widok `/settings/accounting` to teraz hub z linkami zamiast listy - operator wie ze trzeba kliknac "Zarzadzaj paragonami".
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-10 - Phase 113 Plan 01: Fakturownia Integration Foundation
|
||||
|
||||
**Co zrobiono:**
|
||||
- Migracje SQL:
|
||||
- `20260510_000104_create_invoices_tables.sql` - cztery nowe tabele: `invoice_configs`, `invoices`, `invoice_number_counters`, `fakturownia_integration_settings` (multi-account, `integration_id UNIQUE FK` -> `integrations`).
|
||||
- `20260510_000105_add_invoice_requested_to_orders.sql` - `orders.invoice_requested TINYINT(1) NOT NULL DEFAULT 0` + index `idx_orders_invoice_requested`.
|
||||
- `20260510_000106_seed_fakturownia_integration_type.sql` - no-op placeholder dokumentujacy uznanie `integrations.type='fakturownia'` jako oficjalnie wspieranego.
|
||||
- `FakturowniaIntegrationRepository` - CRUD kont Fakturowni z resolved encryption (`integrations.api_key_encrypted` jako zrodlo prawdy, `settings.api_token_encrypted` jako cache). `findAll/findByIntegrationId/save/delete/getDecryptedToken`.
|
||||
- `FakturowniaApiClient::testConnection()` - GET `https://{prefix}.fakturownia.pl/account.json?api_token=...` z cURL + `SslCertificateResolver`. `createInvoice`/`downloadPdf` jako STUB-y rzucajace `RuntimeException` (do implementacji w kolejnym planie).
|
||||
- `IntegrationsRepository::updateTestResult()` - nowa publiczna metoda do zapisu `last_test_status / last_test_http_code / last_test_message / last_test_at`. Wykorzystywana przez `FakturowniaIntegrationController::test()`.
|
||||
- `FakturowniaIntegrationController` - lista (`/settings/integrations/fakturownia`), edycja (`/edit`, `/new`), save, test, delete. CSRF via `_token`, flash `fakturownia.save/.test/.error`.
|
||||
- Widoki `resources/views/settings/fakturownia.php` (lista z badge'ami) i `resources/views/settings/fakturownia-edit.php` (form: name, account_prefix, api_token, department_id, default_kind, default_payment_to_days, is_active).
|
||||
- `IntegrationsHubController::buildFakturowniaRow()` - karta Fakturowni w hubie `/settings/integrations` z agregowanym statusem wszystkich kont.
|
||||
- Routy w `routes/web.php` (`get /index`, `get /new`, `get /edit`, `post /save`, `post /test`, `post /delete`) + DI wiring (`$fakturowniaIntegrationRepository`, `$fakturowniaApiClient`, `$fakturowniaIntegrationController`).
|
||||
- Dokumentacja: `db_schema.md` (sekcja "Invoices" + `fakturownia_integration_settings` + kolumna `orders.invoice_requested`, total tables 55 -> 59), `architecture.md` (sekcja "Phase 113").
|
||||
|
||||
**Dlaczego:**
|
||||
- v3.7 Invoices wprowadza wystawianie faktur dla klientow wymagajacych dokumentu z NIP (clarifications: `orders.invoice_requested` z importera + manual override). Bez fundamentu DB i konfiguracji konta Fakturowni zaden kolejny plan v3.7 (CRUD configs, wystawianie, lista) nie ma sensu.
|
||||
- Multi-account przez `integrations.type='fakturownia'` zachowuje spojnosc z Allegro/shopPRO (rozne instancje) i pozwala na rozne konta Fakturowni dla roznych marek/oddzialow.
|
||||
- `is_delegated` flag w `invoice_configs` umozliwia w przyszlym planie dwa tryby: lokalna numeracja+PDF dompdf (default) lub delegacja do Fakturowni (numer+PDF z API).
|
||||
- STUB-y `createInvoice/downloadPdf` celowo rzucaja exception zamiast "TODO" - kazda przedwczesna probaba uzycia rzuci jasny blad zamiast cichego no-op.
|
||||
- `IntegrationsRepository::updateTestResult()` jest reusable - przyszle integracje (np. kolejne API) beda mogly korzystac z tej samej metody zamiast inline UPDATE.
|
||||
|
||||
**BREAKING:**
|
||||
- Brak zmian breaking. `IntegrationsHubController` ma nowy parametr konstruktora (`FakturowniaIntegrationRepository`) - wszystkie miejsca wywolania zaktualizowane.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-07 - Phase 112 Plan 01: Re-import Data Protection
|
||||
|
||||
**Co zrobiono:**
|
||||
- `OrderImportRepository::upsertOrderAggregate` - rozdzielono sciezke `created` (pierwszy import) od `else` (re-import). `replaceAddresses`, `replaceItems`, `replaceNotes`, `replacePayments`, `replaceShipments`, `replaceStatusHistory` wywolywane sa teraz wylacznie przy pierwszym imporcie. Logika `paymentTransition` / `statusOverwriteAllowed` (Phase 111) i preservacja `status_code` (Phase 62) bez zmian.
|
||||
- `OrderImportRepository::updateOrderDelta()` - nowa prywatna metoda zastepujaca `updateOrder()`. Aktualizuje wylacznie `status_code`, `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`. Pozostale kolumny zamowienia nie sa nadpisywane przez re-import.
|
||||
- Propagacja anulowania: gdy `is_canceled_by_buyer=1` ze zrodla LUB zmapowany pull `status_code='anulowane'` (Phase 75/83), wymuszone jest `orders.status_code='anulowane'` niezaleznie od `statusOverwriteAllowed`.
|
||||
- Identical-payload guard: porownanie znormalizowanego `payload_json` (nowy vs aktualny w DB). Identyczny payload + brak `paymentTransition`/`statusOverwriteAllowed`/`cancelledBySource` -> commit transakcji bez wywolywania UPDATE; `fetched_at` i `updated_at` pozostaja niezmienione.
|
||||
- `OrderImportRepository::getCurrentOrderState()` - rozszerzenie `getCurrentStatusAndPaymentStatus()` o kolumne `payload_json` (jeden SELECT zamiast dwoch).
|
||||
- `OrderImportRepository::normalizePayloadJson()` - nowy helper deserializujacy/reserializujacy payload do porownywalnej formy (`JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES`).
|
||||
|
||||
**Dlaczego:**
|
||||
- Bug case #882: po re-imporcie zamowienia produkty w `/orders/{id}` wyswietlaly badge "Brak projektu" mimo wczesniej wygenerowanych projektow PSD. Przyczyna: `replaceItems()` wykonywal DELETE+INSERT na `order_items` przy kazdym re-imporcie, co (a) zerowalo flagi `project_generated`/`project_generated_at` (Phase 97) na default 0/NULL i (b) zmienialo `order_items.id`, co lamie referencje skryptu batch `tools/generowanie/_batch_run.sh` (`UPDATE ... WHERE id IN (...)`).
|
||||
- Phase 111 dodala emisje `payment.status_changed` przy re-imporcie z tranzycja platnosci, wiec re-import istniejacych zamowien stal sie regularny - problem #882 stal sie regresja systemowa, nie krawedziem.
|
||||
- Zamowienia (orderPRO jako narzedzie zarzadzania) sa edytowane w aplikacji, nie w zrodle (Allegro/shopPRO). Re-import ze zrodla powinien aktualizowac wylacznie stan platnosci, anulowanie i znaczniki synchronizacji - reszta jest stanem lokalnym.
|
||||
- Identical-payload guard eliminuje niepotrzebne write'y do binloga/replikacji oraz sztuczne odswiezanie `updated_at` przy cyklicznym imporcie tych samych zamowien.
|
||||
|
||||
**BREAKING:**
|
||||
- Brak zmian breaking dla istniejacego API/UI/automatyzacji. Wewnetrznie usunieto `updateOrder()` i `getCurrentStatusAndPaymentStatus()` (oba private) - referencje zewnetrzne nie istnialy.
|
||||
- Backfill zamowien z resetowanymi flagami `project_generated` (np. #882) wykonywany recznie przez operatora poza zakresem planu.
|
||||
|
||||
## 2026-05-05 - Phase 111 Plan 01: Payment Transition Event
|
||||
|
||||
**Co zrobiono:**
|
||||
- `OrderImportRepository::upsertOrderAggregate` - rozszerzona detekcja `payment_transition`. Teraz porownuje poprzedni `payment_status` z nowym (warunek `0/1 -> 2`) zamiast polegac wylacznie na `status_code='nieoplacone'`. Logika preservacji status_code z Phase 62 (`statusOverwriteAllowed`) zostala wydzielona jako osobna decyzja.
|
||||
- `OrderImportRepository::getCurrentStatusAndPaymentStatus()` - nowa metoda pomocnicza zastepujaca `getCurrentStatus()`, zwraca i status_code, i payment_status w jednym SELECT.
|
||||
- `AllegroOrderImportService::importSingleOrder` - dodaje emit `payment.status_changed` gdy `payment_transition && !$wasCreated`.
|
||||
- `ShopproOrdersSyncService::importOneOrder` - analogiczny emit `payment.status_changed`.
|
||||
- `bin/backfill_payment_transition_111.php` - jednorazowy CLI dla zamowien z `payment_status=2 && status_code='nieoplacone'` (allegro + shoppro), idempotentny, wzorzec z Phase 98.
|
||||
|
||||
**Dlaczego:**
|
||||
- Zamowienie #864 (Allegro) zaimportowane 10s po zlozeniu, gdy Allegro jeszcze nie potwierdzilo platnosci. Re-import 2 minuty pozniej zaktualizowal payment_status na 2, ale `order.imported` jest gated przez `$wasCreated` (Phase 98), wiec automatyzacja "Zmien status na w realizacji (allegro)" nigdy nie odpalila.
|
||||
- Allegro nie mial odpowiednika `ShopproPaymentStatusSyncService`, wiec tranzycja platnosci znikala cicho. ShopPRO mial analogiczna luke w `ShopproOrdersSyncService` (flaga `payment_transition` byla wykrywana, ale nie emitowala eventu).
|
||||
- Regula automatyzacji #7 (`payment.status_changed` -> `update_order_status` na `w_realizacji`) nie ma warunku integration_id, wiec po wyemitowaniu eventu obejmie zarowno Allegro jak i shopPRO.
|
||||
- Idempotencja zalatwiona przez logike repo: po pierwszej tranzycji DB ma `payment_status=2`, kolejny re-import widzi old=2/new=2 i `payment_transition=false`. Brak duplikatow eventow.
|
||||
|
||||
## 2026-04-28 - Phase 110 Plan 01: Statistics Summary
|
||||
|
||||
**Co zrobiono:**
|
||||
- `/statistics/summary` - nowy widok podsumowania w menu `Statystyki -> Podsumowanie`.
|
||||
- `OrdersStatisticsController::summary()` - buduje miesieczny view-model dla wykresow liczby i wartosci zamowien.
|
||||
- `OrdersStatisticsRepository::aggregateByMonth()` - agreguje istniejace zamowienia po miesiacu i kanale/integracji.
|
||||
- `public/assets/js/modules/statistics-summary-charts.js` - renderer dwoch interaktywnych wykresow liniowych oparty o Chart.js 4.4.8 CDN.
|
||||
- `resources/views/statistics/summary.php` - filtry zgodne z raportem dziennym, dwa wykresy obok siebie na desktopie oraz dwie tabele fallback pod nimi.
|
||||
- Domyslny poczatek historii ustawiony na `2026-04-01` (`04-2026`) mimo starszych danych.
|
||||
|
||||
**Dlaczego:**
|
||||
- Operator potrzebuje szybkiego trendu miesiecznego przed przejsciem do szczegolowych dziennych statystyk.
|
||||
- Wykresy uzywaja obecnych tabel `orders`, `integrations`, `order_status_groups` i `order_statuses`, wiec migracja DB nie jest potrzebna.
|
||||
- Seria `Razem` jest liczona z tych samych danych co serie integracji, co ulatwia sprawdzenie sum miesiecznych.
|
||||
|
||||
## 2026-04-28 - Phase 109 Plan 01: Checkbox Multiselect Filters
|
||||
|
||||
**Co zrobiono:**
|
||||
- `public/assets/js/modules/checkbox-multiselect.js` - nowy vanilla JS enhancer dla natywnych `<select multiple data-checkbox-multiselect>`.
|
||||
- `resources/views/layouts/app.php` - globalne podpiecie modulu z cache busting przez `filemtime()`.
|
||||
- `resources/views/statistics/orders.php` - filtry `channels[]` i `status_groups[]` oznaczone do progresywnego ulepszenia bez zmiany nazw pol formularza.
|
||||
- `resources/scss/app.scss` - kompaktowe style dropdownu z checkboxami i opcja "Wszystkie".
|
||||
|
||||
**Dlaczego:**
|
||||
- Natywne selecty multiple byly malo czytelne i zajmowaly za duzo miejsca w filtrach statystyk.
|
||||
- Zachowanie oryginalnego selecta w DOM utrzymuje obecny kontrakt GET i fallback bez JavaScript.
|
||||
- Brak zmian w schemacie DB i logice agregacji statystyk.
|
||||
|
||||
> Chronologiczny log zmian technicznych — co i dlaczego.
|
||||
|
||||
## 2026-04-27 — Phase 108 Plan 02: Automation Dropdowns z DB
|
||||
|
||||
**Co zrobiono:**
|
||||
- `AutomationController` — usunięto stałą `SHIPMENT_STATUS_OPTIONS` (8 grupowych kluczy)
|
||||
- Dropdown statusów w warunku `shipment_status` i akcji `update_shipment_status` ładuje statusy z DB przez `DeliveryStatus::getAllOptions()`
|
||||
- Walidacja w `parseConditionValue()` i `parseActionConfig()` używa `DeliveryStatus::getAllStatuses()`
|
||||
- `AutomationService` — usunięto stałą `SHIPMENT_STATUS_OPTION_MAP`; ewaluacja `evaluateShipmentStatusCondition()` porównuje klucze bezpośrednio
|
||||
- `resolveStatusFromActionKey()` — bezpośredni klucz statusu z DB jako target (zamiast pierwszego z grupy)
|
||||
|
||||
**Dlaczego:**
|
||||
- Zamknięcie integracji z Plan 01 — operator dodaje status w `/settings/delivery-statuses` i jest on od razu dostępny w dropdownach automatyzacji bez deploymentu
|
||||
- Eliminacja kolizji semantycznej: stary klucz grupowy `picked_up` mapował na `delivered` (paczka odebrana przez klienta), nowy klucz DB `picked_up` to "Odebrana przez kuriera" (od nadawcy)
|
||||
- BREAKING: stare reguły z grupowymi kluczami (`registered`, `courier_pickup`, `dropped_at_point`, `unclaimed`, `picked_up_return`, oraz `picked_up`/`ready_for_pickup`/`cancelled` w starym znaczeniu) nie matchują — wymagają ręcznego odtworzenia z nowymi kluczami DB
|
||||
|
||||
## 2026-04-27 — Phase 108 Plan 01: Delivery Status Management
|
||||
|
||||
**Co zrobiono:**
|
||||
- Tabela `delivery_statuses` z seedem 11 statusów (migracja `20260427_000103`)
|
||||
- `DeliveryStatusRepository` — CRUD + per-request cache
|
||||
- `DeliveryStatus.php` — dynamiczne ładowanie statusów z DB (`setRepository()`)
|
||||
- Panel `/settings/delivery-statuses` z CRUD (zakładka "Statusy") i mapowaniem (zakładka "Mapowanie dostawy")
|
||||
- Sidebar: "Statusy" → "Statusy zamówień", nowe "Statusy przesyłek" z badge niezmapowanych
|
||||
- Badge przesyłek: inline CSS custom property `--status-color` dla niestandardowych statusów
|
||||
|
||||
**Dlaczego:**
|
||||
- Dodanie nowego statusu wymagało zmiany kodu + deploymentu; teraz z UI
|
||||
- Operator może definiować własne statusy znormalizowane bez ingerencji w kod
|
||||
@@ -1,114 +1,41 @@
|
||||
# Testing
|
||||
|
||||
## Setup
|
||||
Last refresh: 2026-05-18.
|
||||
|
||||
- **Framework:** PHPUnit 11.5
|
||||
- **Config:** `phpunit.xml`
|
||||
- **Bootstrap:** `tests/bootstrap.php` (PSR-4 autoloader for `Tests\` namespace)
|
||||
- **Run:** `composer test` → `vendor/bin/phpunit -c phpunit.xml --testdox`
|
||||
- **Helper:** `dg/bypass-finals` — allows mocking `final` classes
|
||||
## Framework
|
||||
|
||||
## PHPUnit Configuration
|
||||
- Test framework: PHPUnit 11.5 through `phpunit.xml`.
|
||||
- Bootstrap: `tests/bootstrap.php`.
|
||||
- Test namespace: `Tests\\`.
|
||||
- Test location: `tests/Unit/`.
|
||||
- `DG\\BypassFinals::enable()` is enabled in `tests/bootstrap.php` for mocking final classes.
|
||||
- PHPUnit fails on warnings and risky tests.
|
||||
|
||||
```xml
|
||||
<phpunit bootstrap="tests/bootstrap.php"
|
||||
cacheDirectory="storage/cache/phpunit"
|
||||
colors="true"
|
||||
executionOrder="depends,defects"
|
||||
failOnWarning="true"
|
||||
failOnRisky="true">
|
||||
```
|
||||
## Commands
|
||||
|
||||
## Test Files (`tests/Unit/`)
|
||||
- Full PHP suite: `composer test`.
|
||||
- Direct PHPUnit: `vendor/bin/phpunit -c phpunit.xml --testdox`.
|
||||
- Frontend assets: `npm run build:assets`.
|
||||
- `npm test` is a placeholder that exits with an error.
|
||||
|
||||
| File | Subject | Coverage |
|
||||
|------|---------|---------|
|
||||
| `AllegroOrderImportServiceTest.php` | `AllegroOrderImportService` | Import, retry on 401, empty ID guard |
|
||||
| `AllegroShipmentServiceTest.php` | `AllegroShipmentService` | Shipment creation |
|
||||
| `AllegroStatusSyncServiceTest.php` | `AllegroStatusSyncService` | Status sync |
|
||||
| `AllegroTokenManagerTest.php` | `AllegroTokenManager` | Token refresh |
|
||||
| `ApaczkaShipmentServiceTest.php` | `ApaczkaShipmentService` | Apaczka API calls |
|
||||
| `AutomationServiceTest.php` | `AutomationService` | Email-once guard, condition evaluation |
|
||||
| `DeliveryStatusTest.php` | Delivery status mapping | Status translation |
|
||||
## Existing Coverage Shape
|
||||
|
||||
**No repository, controller, or view tests exist** — only service-layer unit tests.
|
||||
- Services/mappers/repositories: `tests/Unit/ErliOrdersSyncServiceTest.php`, `tests/Unit/ErliOrderMapperTest.php`, `tests/Unit/FakturowniaInvoiceIdempotencyTest.php`, `tests/Unit/OrdersStatisticsRepositoryTest.php`.
|
||||
- Shipping/integration services: `tests/Unit/AllegroShipmentServiceTest.php`, `tests/Unit/ApaczkaShipmentServiceTest.php`, `tests/Unit/PolkurierShipmentServiceTest.php`.
|
||||
- Security/config: `tests/Unit/SmtpSecurityContextFactoryTest.php`, `tests/Unit/AllegroTokenManagerTest.php`.
|
||||
- View-rendering style check: `tests/Unit/ShipmentPreparePolkurierMappingTest.php`.
|
||||
|
||||
## Test Patterns
|
||||
## Gaps
|
||||
|
||||
### Mock Setup
|
||||
- No dedicated browser/e2e suite found.
|
||||
- No frontend JS test suite found for `public/assets/js/modules/*.js`.
|
||||
- No controller HTTP integration test suite found for `routes/web.php` and controllers.
|
||||
- No migration integration test suite found for `database/migrations/`.
|
||||
- Windows print client under `clients/windows/OrderPROPrint/` has no obvious automated tests.
|
||||
|
||||
```php
|
||||
final class AllegroOrderImportServiceTest extends TestCase {
|
||||
private AllegroIntegrationRepository&MockObject $integrationRepository;
|
||||
private AllegroApiClient&MockObject $apiClient;
|
||||
## Practical Verification Guidance
|
||||
|
||||
protected function setUp(): void {
|
||||
$this->apiClient = $this->createMock(AllegroApiClient::class);
|
||||
$this->service = new AllegroOrderImportService(
|
||||
$this->integrationRepository, $this->tokenManager, $this->apiClient, ...
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Behavior Verification
|
||||
|
||||
```php
|
||||
$this->emailOnceRepository
|
||||
->expects($this->exactly(2))
|
||||
->method('wasSent')
|
||||
->willReturnOnConsecutiveCalls(false, true);
|
||||
|
||||
$this->emailService
|
||||
->expects($this->once())
|
||||
->method('send');
|
||||
```
|
||||
|
||||
### Exception Testing
|
||||
|
||||
```php
|
||||
$this->expectException(AllegroApiException::class);
|
||||
$this->expectExceptionMessage('Podaj ID zamowienia');
|
||||
$this->service->importSingleOrder('');
|
||||
```
|
||||
|
||||
### Retry Logic Testing
|
||||
|
||||
```php
|
||||
$callCount = 0;
|
||||
$this->apiClient->method('getCheckoutForm')
|
||||
->willReturnCallback(function () use (&$callCount): array {
|
||||
if (++$callCount === 1) {
|
||||
throw new RuntimeException('ALLEGRO_HTTP_401');
|
||||
}
|
||||
return $payload;
|
||||
});
|
||||
|
||||
$this->service->importSingleOrder($id);
|
||||
$this->assertSame(2, $callCount); // retry happened
|
||||
```
|
||||
|
||||
### Test Data Builders
|
||||
|
||||
```php
|
||||
private function buildMinimalPayload(string $id): array {
|
||||
return [
|
||||
'id' => $id,
|
||||
'status' => 'READY_FOR_PROCESSING',
|
||||
'payment' => ['id' => 'pay-1', 'type' => 'allegro', ...],
|
||||
// all required nested fields
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## What Is Not Tested
|
||||
|
||||
- Controllers (no HTTP integration tests)
|
||||
- Repositories (no DB integration tests — no test database configured)
|
||||
- Views (no rendering tests)
|
||||
- Cron handlers
|
||||
- Migration scripts
|
||||
|
||||
## Manual UAT
|
||||
|
||||
Phase summaries note manual UAT steps after feature implementation (e.g., Phase 104 — Apaczka weekend delivery tested via UI). No documented UAT scripts or Postman collections.
|
||||
- For narrow service/repository changes, run the nearest `tests/Unit/*Test.php` first.
|
||||
- For shared behavior, integration wiring, or risky changes, run `composer test`.
|
||||
- For UI/SCSS changes, run `npm run build:assets` and manually verify the affected view.
|
||||
- For cron/integration work, test the target service/repository and smoke the relevant handler in `src/Modules/Cron/`.
|
||||
|
||||
@@ -1,98 +1,3 @@
|
||||
# TODO — odlozone zadania techniczne
|
||||
|
||||
> 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)
|
||||
|
||||
### Status audytu Phase 134 (2026-05-16)
|
||||
- **Active** - `ReceiptService::issue()` nadal zapisuje `total_net` jako kopie brutto, a `buildItemsSnapshot()` nie zwraca netto. Dowody: `.paul/phases/134-backlog-reality-check/BACKLOG-AUDIT.md`.
|
||||
|
||||
### Status Phase 135 (2026-05-16)
|
||||
- **Resolved for new receipts** - `ReceiptService::buildItemsSnapshot()` zwraca `total_net`, a `ReceiptService::issue()` zapisuje realne netto dla nowych paragonow. Historyczne paragony nie sa backfillowane decyzja operatora z planu 135-01.
|
||||
|
||||
### 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)
|
||||
|
||||
### Status audytu Phase 134 (2026-05-16)
|
||||
- **Active / needs operator/API decision** - brak lokalnego `pending_external`, idempotency key i lookupu po referencji przed ponownym POST. W Phase 136 najpierw potwierdzic mozliwosci API Fakturowni. Dowody: `.paul/phases/134-backlog-reality-check/BACKLOG-AUDIT.md`.
|
||||
|
||||
### Status Phase 136 (2026-05-17)
|
||||
- **Resolved** - delegowane faktury Fakturownia uzywaja stabilnego `oid = orders.internal_order_number`, lokalnego stanu `pending_external`/`failed_retryable`, lookupu `GET /invoices.json?oid=...` przed ponownym POST oraz auto-podpiecia znalezionej faktury po timeoutcie. Fakturownia nie dokumentuje `Idempotency-Key`, wiec deduplikacja opiera sie na `oid`.
|
||||
|
||||
### Kontekst
|
||||
- Phase 115-01 — wystawianie faktury z zamowienia (delegacja Fakturownia).
|
||||
- Flow: `InvoiceService::issueDelegated()` -> POST do Fakturowni -> on success INSERT do `invoices`.
|
||||
- Edge case: faktura zostala utworzona w Fakturowni, ale odpowiedz nie dotarla (timeout, network). Operator widzi blad, klika "Wystaw fakture" ponownie -> drugi POST -> Fakturownia tworzy DRUGA fakture.
|
||||
|
||||
### Zadania
|
||||
1. Dorzucic idempotency-key (np. UUID per attempt zachowany w sesji albo w `invoices` ze statusem `pending_external`).
|
||||
2. Sprawdzic czy Fakturownia API wspiera nag/lowek `Idempotency-Key` lub deduplikacje po referencji.
|
||||
3. Alternatywa: po bledzie API, przed kolejnym POST, query Fakturowni `GET /invoices.json?q=<order_reference>` zeby sprawdzic czy faktura juz istnieje.
|
||||
|
||||
### Status
|
||||
- Zamkniete w Phase 136 dla nowych delegowanych wystawien. Historyczne duplikaty, jesli istnieja, nie sa backfillowane ani automatycznie usuwane.
|
||||
|
||||
---
|
||||
|
||||
## STAT-NET — netto zamowien w statystykach (data: 2026-04-19)
|
||||
|
||||
### Status audytu Phase 134 (2026-05-16)
|
||||
- **Active** - `OrdersStatisticsRepository::netAmountSql()` nadal ma fallback `gross / 1.23`; mapper potrafi przyjac czesc pol netto/VAT, ale statystyki nie licza netto po pozycjach. Dowody: `.paul/phases/134-backlog-reality-check/BACKLOG-AUDIT.md`.
|
||||
|
||||
### Status Phase 135 (2026-05-16)
|
||||
- **Resolved for runtime statistics** - `OrdersStatisticsRepository::netAmountSql()` preferuje source-level net, a przy braku net liczy fallback z `order_items` po realnym VAT i dolicza dostawe jako 23% VAT. Stale `gross / 1.23` zostaje tylko jako ostatni fallback dla legacy zamowien bez pozycji.
|
||||
|
||||
### Kontekst
|
||||
- Statystyki `/statistics/orders` pokazuja `Netto` per dzien/kanal.
|
||||
- shopPRO nie wysyla kwoty netto ani na poziomie zamowienia (`orders.total_without_tax`), ani produktow (`order_items.original_price_without_tax` — rowniez puste).
|
||||
- Allegro: `orders.total_without_tax` rowniez moze byc puste.
|
||||
- Obecnie dziala fallback: netto = `ROUND(total_with_tax / 1.23, 2)` gdy kolumna netto jest pusta/zerowa. Zaklada 23% VAT dla wszystkich.
|
||||
|
||||
### Zadania
|
||||
1. **Ustalic zrodlo prawdy dla netto**:
|
||||
- Sprawdzic, czy API shopPRO udostepnia `price_netto` lub `total_netto` (payload zawiera tylko `price_brutto` + `vat`).
|
||||
- Jesli TAK → rozszerzyc mapping importu (`src/Modules/ShopPro/...`) i backfill migracja dla historycznych rekordow.
|
||||
- Jesli NIE → liczyc netto deterministycznie z `order_items.original_price_with_tax` i `order_items.tax_rate` (wtedy nie zakladamy sztywno 23%).
|
||||
2. **Backfill historycznych zamowien** po wdrozeniu zrodla netto (migracja SQL + idempotentny skrypt).
|
||||
3. **Zastapic fallback /1.23** w `OrdersStatisticsRepository::netAmountSql()`:
|
||||
- Preferuj `orders.total_without_tax`.
|
||||
- Jesli brak — `SUM(order_items.original_price_with_tax / (1 + order_items.tax_rate / 100) * order_items.quantity)`.
|
||||
- Stala 1.23 tylko jako ostateczny fallback przy braku item-levelu.
|
||||
|
||||
### Linki w kodzie
|
||||
- `src/Modules/Statistics/OrdersStatisticsRepository.php` - metoda `netAmountSql()` (komentarz `TODO(STAT-NET)`).
|
||||
|
||||
## DELIVERY-STATUS-MGMT — zarzadzanie statusami znormalizowanymi z panelu (data: 2026-04-26)
|
||||
|
||||
### Status audytu Phase 134 (2026-05-16)
|
||||
- **Implemented; operator verification remains** - core DB-driven statusy, CRUD i dropdowny automatyzacji sa wdrozone po Phase 108. Phase 137 powinien zweryfikowac tylko stare reguly automatyzacji/operator migration i zamknac nieaktualny wpis. Dowody: `.paul/phases/134-backlog-reality-check/BACKLOG-AUDIT.md`.
|
||||
|
||||
### Status Phase 137 (2026-05-17)
|
||||
- **Closed / verified** - runtime code nie zawiera juz `SHIPMENT_STATUS_OPTIONS` ani `SHIPMENT_STATUS_OPTION_MAP`; `AutomationController` i `AutomationService` uzywaja `DeliveryStatus::getAllStatuses()` / `getAllOptions()`. Read-only check przez `DB_HOST_REMOTE` wykazal 11 statusow w `delivery_statuses`, 3 warunki `shipment_status`, 0 akcji `update_shipment_status`, 0 starych kluczy (`registered`, `courier_pickup`, `dropped_at_point`, `unclaimed`, `picked_up_return`) i 0 kluczy spoza `delivery_statuses`. Dowody: `.paul/phases/137-delivery-status-backlog-verification/137-01-SUMMARY.md`.
|
||||
|
||||
### Kontekst
|
||||
- Aktualnie statusy znormalizowane (`created`, `confirmed`, `picked_up`, `in_transit`, itd.) sa stalymi w kodzie (`DeliveryStatus.php`).
|
||||
- Dodanie nowego statusu wymaga zmiany kodu + deploymentu.
|
||||
- Panel ustawien pozwala tylko mapowac surowe statusy kurierow na istniejace statusy znormalizowane - nie mozna dodac nowego znormalizowanego statusu z UI.
|
||||
|
||||
### Zadania
|
||||
1. Wyniesc liste statusow znormalizowanych do tabeli DB (np. `delivery_statuses`) z kolumnami: `key`, `label_pl`, `color`, `sort_order`, `is_terminal`, `is_system`.
|
||||
2. Statusy systemowe (`delivered`, `returned`, `cancelled`) oznaczac flaga `is_system = true` - nieedytowalne z UI (maja specjalne znaczenie w kodzie).
|
||||
3. Panel `/settings/delivery-statuses` - CRUD dla statusow niebedacych systemowymi (dodaj, zmien etykiete/kolor, usun jesli nieuzywany).
|
||||
4. `DeliveryStatus::ALL_STATUSES`, `LABEL_PL` i badge CSS zastapic dynamicznym ladowaniem z DB (cache per-request).
|
||||
5. Automatyzacje i mapowania kurierow - dropdown statusow znormalizowanych pobierany z DB zamiast hardcoded.
|
||||
|
||||
### Uwagi
|
||||
- `TERMINAL_STATUSES` musi zostac zachowane jako lista systemowych statusow koncowych - te nie powinny byc usuwalne.
|
||||
- Przy usuwaniu statusu znormalizowanego - blokada jesli uzywany w `delivery_status_mappings` lub `shipment_packages.delivery_status`.
|
||||
> Lista nieformalnych zadan do zrobienia pozniej.
|
||||
113
.paul/codebase/tooling_status.md
Normal file
113
.paul/codebase/tooling_status.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Tooling Status
|
||||
|
||||
Last refresh: 2026-05-19 Europe/Warsaw.
|
||||
|
||||
## Current Policy
|
||||
|
||||
- Quality Radar is lightweight by default.
|
||||
- `codebase-memory-mcp` remains enabled for automatic plan/apply/unify impact scans.
|
||||
- `jscpd` is disabled by policy for automatic runs; use it only on explicit duplicate-detection requests or if `quality_radar.tools.jscpd: true` is set in `.paul/config.md`.
|
||||
- `ast-grep` is disabled by policy for automatic runs; use it only on explicit structural-pattern requests or if `quality_radar.tools.ast_grep: true` is set in `.paul/config.md`.
|
||||
- Existing `jscpd` and `ast-grep` raw reports under `.paul/codebase/radar/` are historical artifacts and should not be loaded into context unless directly needed.
|
||||
- Generated `.paul/codebase/*.md` map is reduced to: `stack.md`, `architecture.md`, `conventions.md`, `testing.md`, `integrations.md`, `db_schema.md`, `impact_map.md`, `quality_risks.md`, and `tooling_status.md`.
|
||||
- `.paul/codebase/todo.md` is manual/user-owned and must not be generated or overwritten by PAUL workflows.
|
||||
- Retired generated docs: `index.md`, `structure.md`, `concerns.md`, `domain_duplicates.md`, and `tech_changelog.md`.
|
||||
- PAUL-generated Markdown documents should be written in Polish by default; paths, commands, config keys, identifiers, logs, and quoted source text stay unchanged.
|
||||
|
||||
## Scan
|
||||
|
||||
- Mode: policy update / previous full scan baseline.
|
||||
- Scope: Quality Radar tooling configuration.
|
||||
- Status: lightweight automatic radar enabled through `codebase-memory-mcp`; `jscpd` and `ast-grep` manual/on-demand by policy.
|
||||
|
||||
## Tools
|
||||
|
||||
- `codebase-memory-mcp --version`: ok, `codebase-memory-mcp 0.6.1`.
|
||||
- `codex mcp list`: `codebase-memory-mcp` enabled with stdio transport.
|
||||
- Fresh `codex exec` process used MCP tools successfully: `list_projects`, `index_repository`, `list_projects`, `get_architecture`.
|
||||
- Initial MCP index: project `C-visual studio code-projekty-orderPRO`, 8165 nodes, 13610 edges.
|
||||
- `codebase-memory-mcp --% cli index_repository {"repo_path":"C:\visual studio code\projekty\orderPRO"}`: exited 0; emitted only init logs in this shell mode.
|
||||
- `codebase-memory-mcp --% cli index_status {"repo_path":"C:\visual studio code\projekty\orderPRO"}`: exited 0; emitted only init logs in this shell mode.
|
||||
- `codebase-memory-mcp --% cli get_architecture {"repo_path":"C:\visual studio code\projekty\orderPRO"}`: exited 0; emitted only init logs in this shell mode.
|
||||
- `jscpd --version`: failed, command not found globally.
|
||||
- `npx --yes jscpd ...`: succeeded and wrote `.paul/codebase/radar/jscpd/jscpd-report.json`.
|
||||
- `ast-grep --version`: ok after global install, `ast-grep 0.42.2`.
|
||||
- `sg --version`: ok after global install, `ast-grep 0.42.2`.
|
||||
- `npx --yes @ast-grep/cli ...`: failed historically, npm could not determine executable.
|
||||
- `npx --yes --package @ast-grep/cli ast-grep ...`: failed historically with module resolution error under Node v24.15.0.
|
||||
- Fix applied: installed `@ast-grep/cli` globally and repaired npm wrapper scripts to call native `ast-grep.exe` / `sg.exe`.
|
||||
- Fallback: `rg` scans and explorer analysis remain available.
|
||||
|
||||
## Raw Outputs
|
||||
|
||||
- Codebase memory install/MCP/index note: `.paul/codebase/radar/codebase-memory-full.txt`.
|
||||
- jscpd JSON report: `.paul/codebase/radar/jscpd/jscpd-report.json`.
|
||||
- ast-grep refreshed output: `.paul/codebase/radar/ast-grep-full.txt`.
|
||||
|
||||
## Commands Attempted
|
||||
|
||||
- `codebase-memory-mcp --version`
|
||||
- `codebase-memory-mcp --help`
|
||||
- `codex mcp list`
|
||||
- `codex mcp get codebase-memory-mcp`
|
||||
- Fresh `codex exec` prompt that called MCP tools: `list_projects`, `index_repository`, `get_architecture`
|
||||
- `codebase-memory-mcp --% cli index_repository {"repo_path":"C:\visual studio code\projekty\orderPRO"}`
|
||||
- `codebase-memory-mcp --% cli index_status {"repo_path":"C:\visual studio code\projekty\orderPRO"}`
|
||||
- `codebase-memory-mcp --% cli get_architecture {"repo_path":"C:\visual studio code\projekty\orderPRO"}`
|
||||
- `jscpd --version`
|
||||
- `ast-grep --version`
|
||||
- `sg --version`
|
||||
- `npx --yes jscpd . --reporters json,console --output .paul/codebase/radar/jscpd --threshold 100 --ignore "**/.git/**,**/node_modules/**,**/vendor/**,**/storage/**,**/.paul/**"`
|
||||
- `npx --yes @ast-grep/cli -p ...`
|
||||
- `npx --yes --package @ast-grep/cli ast-grep -p ...`
|
||||
|
||||
## Next Action
|
||||
|
||||
- Treat automatic radar as usable through `codebase-memory-mcp`.
|
||||
- Do not run `jscpd` or `ast-grep` during routine PLAN/APPLY/UNIFY unless explicitly requested or enabled in `.paul/config.md`.
|
||||
- `ast-grep` was previously working globally, while `jscpd` relied on `npx`; keep those details only as historical setup notes.
|
||||
- If `npm install -g @ast-grep/cli` is rerun, verify the generated Windows wrappers still call the native `.exe` binaries.
|
||||
- Use MCP tools through a fresh Codex session/process for structured codebase-memory output.
|
||||
- Future jscpd runs should also ignore `.playwright-mcp/**`, `.scannerwork/**`, `.vscode/ftp-kr.diff.*`, generated assets, and cache folders.
|
||||
|
||||
## Targeted Plan Scan: Polish UI Copy
|
||||
|
||||
- Timestamp: 2026-05-18 23:05 Europe/Warsaw.
|
||||
- Mode: plan.
|
||||
- Scope: Polish translations/UI copy in `resources/lang`, `resources/views`, `public/assets/js/modules`, and `src/Modules`.
|
||||
- Status: degraded, because `codebase-memory-mcp` and `npx jscpd` worked, while `ast-grep`/`sg` remain unavailable.
|
||||
- Commands:
|
||||
- `codebase-memory-mcp --version` -> ok, 0.6.1.
|
||||
- MCP `list_projects`, `search_graph`, `get_code_snippet`, `search_code` -> ok.
|
||||
- `jscpd --version` -> unavailable globally.
|
||||
- `npx --yes jscpd resources/lang resources/views public/assets/js/modules src/Modules --reporters json,console --output .paul/codebase/radar/jscpd-i18n-plan --threshold 100 ...` -> ok.
|
||||
- `ast-grep --version`, `sg --version` -> unavailable.
|
||||
- Raw output: `.paul/codebase/radar/jscpd-i18n-plan/jscpd-report.json`.
|
||||
|
||||
## Targeted Apply Scan: Polish UI Copy
|
||||
|
||||
- Timestamp: 2026-05-18 23:25 Europe/Warsaw.
|
||||
- Mode: apply.
|
||||
- Scope: Polish translations/UI copy in `resources/lang`, `resources/views`, `public/assets/js/modules`, and `src/Modules`.
|
||||
- Status: degraded, because `codebase-memory-mcp` and `npx jscpd` worked, while `ast-grep`/`sg` and `sonar-scanner` are unavailable in PATH.
|
||||
- Commands:
|
||||
- MCP `detect_changes` from `HEAD` for the affected scope -> ok, 111 changed files, no impacted graph symbols reported.
|
||||
- `npx --yes jscpd resources/lang resources/views public/assets/js/modules src/Modules --reporters json,console --output .paul/codebase/radar/jscpd-i18n-post-apply --threshold 100 ...` -> ok.
|
||||
- `sonar-scanner --version` -> failed, command not found.
|
||||
- Raw output: `.paul/codebase/radar/jscpd-i18n-post-apply/jscpd-report.json`.
|
||||
|
||||
## Targeted UNIFY Scan: Polish UI Copy
|
||||
|
||||
- Timestamp: 2026-05-18 23:40 Europe/Warsaw.
|
||||
- Mode: post-apply / unify.
|
||||
- Scope: plan path plus modified UI copy, docs and radar files.
|
||||
- Status: degraded, because `codebase-memory-mcp` and `npx jscpd` worked, while global `jscpd`, `ast-grep`, `sg` and `sonar-scanner` are unavailable in PATH.
|
||||
- Commands:
|
||||
- `codebase-memory-mcp --version` -> ok, 0.6.1.
|
||||
- MCP `detect_changes` from `HEAD` for the affected scope -> ok, 110 changed files, no impacted graph symbols reported.
|
||||
- `jscpd --version` -> failed, command not found globally.
|
||||
- `ast-grep --version`, `sg --version` -> failed, command not found.
|
||||
- `npx --yes jscpd resources/lang resources/views public/assets/js/modules src/Modules --reporters json,console --output .paul/codebase/radar/jscpd-i18n-post-apply --threshold 100 ...` -> ok.
|
||||
- Raw outputs:
|
||||
- `.paul/codebase/radar/codebase-memory-post-apply-polish-ui-copy.txt`
|
||||
- `.paul/codebase/radar/jscpd-i18n-post-apply/jscpd-report.json`
|
||||
47
.paul/config.md
Normal file
47
.paul/config.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Project Config
|
||||
|
||||
**Project:** orderPRO
|
||||
**Updated:** 2026-05-19
|
||||
|
||||
## Project Settings
|
||||
|
||||
```yaml
|
||||
project:
|
||||
name: orderPRO
|
||||
```
|
||||
|
||||
## Integrations
|
||||
|
||||
### Quality Radar
|
||||
|
||||
```yaml
|
||||
quality_radar:
|
||||
enabled: true
|
||||
auto_install: true
|
||||
tools:
|
||||
codebase_memory_mcp: true
|
||||
jscpd: false
|
||||
ast_grep: false
|
||||
```
|
||||
|
||||
### SonarQube
|
||||
|
||||
```yaml
|
||||
sonarqube:
|
||||
enabled: false
|
||||
mode: manual-on-demand
|
||||
project_key: orderPRO
|
||||
```
|
||||
|
||||
## Preferences
|
||||
|
||||
```yaml
|
||||
preferences:
|
||||
auto_commit: false
|
||||
verbose_output: false
|
||||
state_autocompress: true
|
||||
state_autocompress_max_lines: 500
|
||||
```
|
||||
|
||||
---
|
||||
*Config updated: 2026-05-19*
|
||||
@@ -1,76 +0,0 @@
|
||||
# PAUL Handoff
|
||||
|
||||
**Date:** 2026-03-13
|
||||
**Status:** paused
|
||||
|
||||
---
|
||||
|
||||
## READ THIS FIRST
|
||||
|
||||
You have no prior context. This document tells you everything.
|
||||
|
||||
**Project:** orderPRO — aplikacja do zarządzania zamówieniami z wielu kanałów sprzedaży (Allegro, Erli, własne sklepy). Generowanie etykiet kurierskich.
|
||||
**Core value:** Sprzedawca obsługuje wszystkie kanały i nadaje przesyłki bez przełączania platform.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**Version:** v0.1.0 (In Progress)
|
||||
**Phase:** 6 of TBD — 06-sonarqube-quality
|
||||
**Plan:** 06-01 ✓ DONE, 06-02 ✓ DONE, 06-03 ✓ DONE, 06-04/05/06 awaiting
|
||||
|
||||
**Loop Position:**
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ✓ ✓ [loop closed — ready for next plan: 06-06]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Was Done (this session)
|
||||
|
||||
- **APPLY + UNIFY 06-02** — S1142: redukcja return statements (commit 028c46c)
|
||||
- AllegroIntegrationController: save 6→3, saveImportSettings 5→3, oauthCallback 4→3
|
||||
- ShopproIntegrationsController: save 9→3, saveStatusMappings 4→3, syncStatuses 4→3
|
||||
- Nowe wzorce: `validateXxxInput(): ?string` i `validateXxxAccess(): ?Response`
|
||||
- Phase 6 progress: 3/6 plans complete (50%)
|
||||
|
||||
---
|
||||
|
||||
## What's In Progress
|
||||
|
||||
Nic — pętla 06-02 zamknięta, codebase stabilny.
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
**Immediate:** `/paul:apply .paul/phases/06-sonarqube-quality/06-06-PLAN.md`
|
||||
|
||||
**Kolejność pozostałych planów:** 06-06 → 06-04 → 06-05
|
||||
- 06-05 (god classes) zależy od 06-04 i ma `checkpoint:human-verify` (nie autonomous)
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `.paul/STATE.md` | Live project state |
|
||||
| `.paul/ROADMAP.md` | Phase overview |
|
||||
| `.paul/phases/06-sonarqube-quality/06-06-PLAN.md` | Następny plan |
|
||||
| `.paul/phases/06-sonarqube-quality/06-02-SUMMARY.md` | Ostatni UNIFY — context |
|
||||
| `src/Modules/Settings/AllegroIntegrationController.php` | Zrefaktoryzowany w 06-02 |
|
||||
| `src/Modules/Settings/ShopproIntegrationsController.php` | Zrefaktoryzowany w 06-02 |
|
||||
|
||||
---
|
||||
|
||||
## Resume Instructions
|
||||
|
||||
1. Przeczytaj `.paul/STATE.md` — potwierdź pozycję w loop
|
||||
2. Uruchom `/paul:apply .paul/phases/06-sonarqube-quality/06-06-PLAN.md`
|
||||
|
||||
---
|
||||
|
||||
*Handoff created: 2026-03-13*
|
||||
@@ -1,74 +0,0 @@
|
||||
# PAUL Handoff
|
||||
|
||||
**Date:** 2026-03-13
|
||||
**Status:** paused — session complete, plans ready for execution
|
||||
|
||||
---
|
||||
|
||||
## READ THIS FIRST
|
||||
|
||||
You have no prior context. This document tells you everything.
|
||||
|
||||
**Project:** orderPRO — aplikacja do zarządzania zamówieniami z wielu źródeł sprzedaży (Allegro, Erli, shopPRO) z generowaniem etykiet przewozowych.
|
||||
**Core value:** Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów sprzedaży i nadawać przesyłki bez przełączania się między platformami.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**Milestone:** v0.2 Pre-Expansion Fixes
|
||||
**Phase:** 7 — pre-expansion-fixes
|
||||
**Plan:** 07-01..07-05 CREATED, żaden nie wykonany
|
||||
|
||||
**Loop Position:**
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ○ ○ [Plan gotowy — czeka na APPLY]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Was Done (ta sesja)
|
||||
|
||||
- UNIFY 06-05: god classes split — ShopproOrdersSyncService 39→9 metod, AllegroIntegrationController 35→25 metod
|
||||
- Faza 06 zamknięta: 6/6 planów, SonarQube quality baseline
|
||||
- /paul:complete-milestone: v0.1 Initial Release zamknięty, git tag v0.1.0
|
||||
- CONCERNS.md skategoryzowany — "przed rozbudową" vs "odroczić"
|
||||
- Faza 07 zaplanowana: 5 planów (07-01..07-05) gotowe do APPLY
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
**Immediate:** `/paul:apply .paul/phases/07-pre-expansion-fixes/07-01-PLAN.md`
|
||||
|
||||
Kolejność:
|
||||
1. 07-01 — autonomiczny (Performance: N+1, static cache, DB indexes)
|
||||
2. 07-02 — autonomiczny (SSL verify, cron→DB, migration 000014b)
|
||||
3. 07-03 — ma checkpoint:human-verify (UX: disable orderpro_to_allegro, UI items 14-17)
|
||||
4. 07-04 — autonomiczny (Tests: AllegroTokenManager + import)
|
||||
5. 07-05 — ma checkpoint:decision (InPost ShipmentProviderInterface)
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `.paul/STATE.md` | Live project state |
|
||||
| `.paul/phases/07-pre-expansion-fixes/07-01-PLAN.md` | Performance fixes |
|
||||
| `.paul/phases/07-pre-expansion-fixes/07-02-PLAN.md` | SSL + cron + migration |
|
||||
| `.paul/phases/07-pre-expansion-fixes/07-03-PLAN.md` | UX fixes (checkpoint) |
|
||||
| `.paul/phases/07-pre-expansion-fixes/07-04-PLAN.md` | Unit tests |
|
||||
| `.paul/phases/07-pre-expansion-fixes/07-05-PLAN.md` | InPost provider (checkpoint:decision) |
|
||||
|
||||
---
|
||||
|
||||
## Resume Instructions
|
||||
|
||||
1. `/paul:resume` — odczyta STATE.md i pokaże aktualny stan
|
||||
2. Zatwierdź: `/paul:apply .paul/phases/07-pre-expansion-fixes/07-01-PLAN.md`
|
||||
|
||||
---
|
||||
|
||||
*Handoff created: 2026-03-13*
|
||||
@@ -1,91 +0,0 @@
|
||||
# PAUL Handoff
|
||||
|
||||
**Date:** 2026-03-16
|
||||
**Status:** paused — checkpoint human-verify in progress
|
||||
|
||||
---
|
||||
|
||||
## READ THIS FIRST
|
||||
|
||||
You have no prior context. This document tells you everything.
|
||||
|
||||
**Project:** orderPRO — aplikacja do zarządzania zamówieniami z wielu platform
|
||||
**Core value:** Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów sprzedaży i nadawać przesyłki bez przełączania się między platformami.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**Milestone:** v0.4 Moduł E-mail
|
||||
**Phase:** 14 of 3 (in milestone) — Szablony wiadomości e-mail
|
||||
**Plan:** 14-01 — APPLY in progress (checkpoint human-verify)
|
||||
|
||||
**Loop Position:**
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ◐ ○ [APPLY in progress — Task 1+2 done, Task 3 checkpoint awaiting approval]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Was Done
|
||||
|
||||
- Task 1: Created `EmailTemplateRepository` + `EmailTemplateController` + routes (6 endpoints)
|
||||
- Task 2: Created view `email-templates.php` with Quill.js CDN editor, variable panel, preview modal, AJAX toggle
|
||||
- Added sidebar link "Szablony e-mail" in `layouts/app.php`
|
||||
- Compiled SCSS (modal-overlay, email-tpl-editor styles)
|
||||
- Fixed 2 bugs discovered during deploy:
|
||||
- `AuthService` namespace: `App\Core\Auth\AuthService` → `App\Modules\Auth\AuthService`
|
||||
- `Flash` namespace: `App\Core\Session\Flash` → `App\Core\Support\Flash`
|
||||
|
||||
---
|
||||
|
||||
## What's In Progress
|
||||
|
||||
- **Task 3 checkpoint:human-verify** — user was testing the deployed page when session paused
|
||||
- User reported 2 namespace errors which were fixed, needs to re-test
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
**Immediate:** User re-tests `/settings/email-templates` after namespace fixes. If approved → APPLY complete.
|
||||
|
||||
**After that:** Run `sonar-scanner` (required skill), then `/paul:unify .paul/phases/14-email-templates/14-01-PLAN.md`
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `.paul/STATE.md` | Live project state |
|
||||
| `.paul/ROADMAP.md` | Phase overview |
|
||||
| `.paul/phases/14-email-templates/14-01-PLAN.md` | Current plan |
|
||||
| `src/Modules/Settings/EmailTemplateController.php` | Controller (CRUD + preview + variables) |
|
||||
| `src/Modules/Settings/EmailTemplateRepository.php` | Repository (DB operations) |
|
||||
| `resources/views/settings/email-templates.php` | View (list + form + Quill.js + variable panel) |
|
||||
| `routes/web.php` | Routes (6 new endpoints) |
|
||||
| `resources/views/layouts/app.php` | Sidebar link added |
|
||||
| `resources/scss/app.scss` | Styles (modal-overlay, email-tpl-editor) |
|
||||
|
||||
---
|
||||
|
||||
## Namespace Fixes Applied
|
||||
|
||||
These were wrong in the initial controller and have been corrected:
|
||||
- `use App\Core\Auth\AuthService` → `use App\Modules\Auth\AuthService`
|
||||
- `use App\Core\Session\Flash` → `use App\Core\Support\Flash`
|
||||
|
||||
---
|
||||
|
||||
## Resume Instructions
|
||||
|
||||
1. Read `.paul/STATE.md` for latest position
|
||||
2. Check loop position — APPLY in progress, Task 3 checkpoint
|
||||
3. Run `/paul:resume` or ask user to re-test `/settings/email-templates`
|
||||
4. On approval → finalize APPLY → sonar-scanner → `/paul:unify`
|
||||
|
||||
---
|
||||
|
||||
*Handoff created: 2026-03-16*
|
||||
@@ -1,111 +0,0 @@
|
||||
# PAUL Handoff
|
||||
|
||||
**Date:** 2026-03-22
|
||||
**Status:** paused — sesja w toku, checkpoint human-verify
|
||||
|
||||
---
|
||||
|
||||
## READ THIS FIRST
|
||||
|
||||
You have no prior context. This document tells you everything.
|
||||
|
||||
**Project:** orderPRO — aplikacja do zarządzania zamówieniami wielokanałowymi
|
||||
**Core value:** Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów sprzedaży i nadawać przesyłki bez przełączania się między platformami.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**Milestone:** v0.7 Zdalne drukowanie etykiet
|
||||
**Phase:** 19 of 20 — UI Integration
|
||||
**Plan:** 19-01 — APPLY w toku (checkpoint human-verify)
|
||||
|
||||
**Loop Position:**
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ◐ ○ [APPLY in progress — checkpoint verification]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Was Done
|
||||
|
||||
- Przycisk "Drukuj" w widoku przesyłki (prepare.php) i szczegółów zamówienia (show.php)
|
||||
- Bulk endpoint POST /api/print/jobs/bulk (obsługuje package_ids i order_ids)
|
||||
- Zbiorcze drukowanie z listy zamówień (checkboxy + "Drukuj etykiety" header action)
|
||||
- Kolejka wydruku w Ustawienia > Drukowanie (lista zleceń z filtrami statusu + retry)
|
||||
- Ochrona przed duplikatami (findPendingByPackageId)
|
||||
- Auto-download etykiety przez ensureLabel() z ShipmentProviderRegistry
|
||||
- Stan "W kolejce" (czerwony, disabled) od razu gdy pending job istnieje
|
||||
- Sprawdzanie istnienia pliku etykiety na dysku (show.php + prepare.php) — ukrywa "Pobierz"/"Drukuj" gdy plik nie istnieje
|
||||
- Redirect po utworzeniu przesyłki → szczegóły zamówienia z tabem "Przesyłki"
|
||||
- Zapamiętywanie aktywnego taba w localStorage
|
||||
- Apaczka zapisuje nazwę usługi w carrier_id (np. "Orlen Paczka")
|
||||
- Kolumna "Przewoznik" pokazuje "Apaczka → Orlen Paczka"
|
||||
- Usunięto "(WZA)" z tytułu sekcji
|
||||
- Sekcja "Utworzone przesylki" przeniesiona pod formularz nowej przesyłki
|
||||
- Naprawiony bug use statement w ApaczkaApiClient.php (brak backslashy)
|
||||
- Wyłączono hook PreToolUse context-mode w settings.json
|
||||
|
||||
---
|
||||
|
||||
## What's In Progress
|
||||
|
||||
- Checkpoint human-verify — użytkownik testuje UI na produkcji (orderpro.projectpro.pl)
|
||||
- Ostatni feedback: nazwa usługi w kolumnie Przewoznik działa dla nowych przesyłek (stare mają puste carrier_id)
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
**Immediate:** Kontynuacja checkpoint human-verify — użytkownik potwierdza lub zgłasza kolejne uwagi
|
||||
|
||||
**After that:**
|
||||
1. Po "approved" → finalize APPLY
|
||||
2. /paul:unify dla 19-01
|
||||
3. sonar-scanner przed UNIFY (wymagane przez SPECIAL-FLOWS.md)
|
||||
|
||||
---
|
||||
|
||||
## Dodatkowe zmiany (poza planem 19-01)
|
||||
|
||||
- show.php: zapamiętywanie taba w localStorage
|
||||
- show.php: sprawdzanie file_exists dla label_path
|
||||
- show.php: przycisk "Drukuj" + JS handler
|
||||
- show.php: kolumna Przewoznik z provider → carrier_id
|
||||
- ShipmentController: redirect po sukcesie → /orders/{id} z flash
|
||||
- ApaczkaShipmentService: carrier_id = service name
|
||||
- ApaczkaApiClient: fix use statement
|
||||
- OrdersController: storagePath + printJobRepo injection
|
||||
- settings.json: wyłączony PreToolUse hook context-mode
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `.paul/STATE.md` | Live project state |
|
||||
| `.paul/ROADMAP.md` | Phase overview |
|
||||
| `.paul/phases/19-ui-integration/19-01-PLAN.md` | Current plan |
|
||||
| `src/Modules/Printing/PrintApiController.php` | REST API + ensureLabel + bulkCreateJobs |
|
||||
| `src/Modules/Printing/PrintJobRepository.php` | DB operations + pendingPackageIds |
|
||||
| `src/Modules/Settings/PrintSettingsController.php` | Kolejka wydruku w ustawieniach |
|
||||
| `resources/views/orders/show.php` | Przycisk Drukuj + tab persistence |
|
||||
| `resources/views/shipments/prepare.php` | Przycisk Drukuj + sekcja przesyłek |
|
||||
| `resources/views/orders/list.php` | Bulk print action |
|
||||
| `resources/views/settings/printing.php` | Kolejka wydruku UI |
|
||||
| `routes/web.php` | DI + nowe route'y |
|
||||
|
||||
---
|
||||
|
||||
## Resume Instructions
|
||||
|
||||
1. Read `.paul/STATE.md` for latest position
|
||||
2. Check loop position — APPLY in progress
|
||||
3. Run `/paul:resume` or `/paul:progress`
|
||||
4. Kontynuuj checkpoint human-verify z użytkownikiem
|
||||
|
||||
---
|
||||
|
||||
*Handoff created: 2026-03-22*
|
||||
@@ -1,125 +0,0 @@
|
||||
# PAUL Handoff
|
||||
|
||||
**Date:** 2026-03-23
|
||||
**Status:** paused — mid-APPLY Phase 28
|
||||
|
||||
---
|
||||
|
||||
## READ THIS FIRST
|
||||
|
||||
You have no prior context. This document tells you everything.
|
||||
|
||||
**Project:** orderPRO — wielokanałowe zarządzanie zamówieniami i przesyłkami
|
||||
**Core value:** Sprzedawca obsługuje zamówienia ze wszystkich kanałów i nadaje przesyłki bez przełączania się między platformami.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**Milestone:** v1.2 Śledzenie przesyłek
|
||||
**Phase:** [2] of [2] — Shipment Tracking UI + Settings
|
||||
**Plan:** 28-01 — APPLY in progress (Task 2 checkpoint pending, Task 3 not started)
|
||||
|
||||
**Loop Position:**
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ◐ ○ [APPLY mid-execution, checkpoint pending]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Was Done This Session
|
||||
|
||||
### Phase 27 — Shipment Tracking Backend (COMPLETE ✓)
|
||||
- Migracja DB: 3 kolumny (delivery_status, delivery_status_raw, delivery_status_updated_at) + indeks
|
||||
- DeliveryStatus class z mapowaniami statusów (30+ InPost, 11 Apaczka, 7 Allegro)
|
||||
- ShipmentTrackingInterface + 3 implementacje (InpostTrackingService, ApaczkaTrackingService, AllegroTrackingService)
|
||||
- ShipmentTrackingRegistry + ShipmentTrackingHandler cron (15 min interwał)
|
||||
- CronHandlerFactory rozszerzony o shipment_tracking_sync
|
||||
- Commit: `228c0e9`
|
||||
|
||||
### Phase 28 — Shipment Tracking UI (IN PROGRESS)
|
||||
- Task 1 DONE: SCSS badge'e statusów + DeliveryStatus::trackingUrl() z carrier detection
|
||||
- Task 2 PARTIALLY DONE: Badge'e w show.php i prepare.php, link śledzenia, boks Płatność i wysyłka
|
||||
- Task 3 NOT STARTED: Ustawienia interwału crona
|
||||
|
||||
### Dodatkowe poprawki poza planem:
|
||||
- **Fix: Przycisk Pobierz etykietę w show.php** — zmieniony z linku do prepare na formularz POST z bezpośrednim downloadem PDF
|
||||
- **Fix: delivery_status "delivered" → "confirmed"** — migracja błędnie ustawiała label_ready jako doręczona; naprawiono w DB (3 rows) i w pliku migracji
|
||||
- **Fix: carrier_id dla Apaczka** — uzupełniono z tabeli carrier_delivery_method_mappings (13 rows); dodano fallback w ApaczkaShipmentService
|
||||
- **Fix: Orlen Paczka URL** — poprawiony na `www.orlenpaczka.pl/sledz-paczke/?numer=`
|
||||
- **Fix: Google fallback** — gdy carrier nieznany, link śledzenia kieruje do Google search
|
||||
- **Nowa metoda: ShipmentPackageRepository::resolveCarrierName()** — lookup carrier z carrier_delivery_method_mappings
|
||||
|
||||
---
|
||||
|
||||
## What's In Progress
|
||||
|
||||
- **Checkpoint Task 2** — user testuje UI badge'ów i linków śledzenia, jeszcze nie zatwierdził "approved"
|
||||
- **Task 3** — ustawienia interwału trackingu w cronie — nie rozpoczęty
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
**Immediate:** Uzyskać checkpoint approval dla Task 2 (UI badge'y), potem wykonać Task 3 (cron interval settings)
|
||||
|
||||
**After that:**
|
||||
1. Sonar scan + UNIFY Phase 28
|
||||
2. PLAN Phase 29 — UI zarządzania mapowaniem statusów (user request)
|
||||
3. Dodać fazę 29 do ROADMAP
|
||||
|
||||
---
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale |
|
||||
|----------|-----------|
|
||||
| Google search jako fallback tracking URL | Gdy carrier_id nieznany — uniwersalne, zawsze działa |
|
||||
| carrier_delivery_method_mappings jako źródło carrier_id | API Apaczki nie zwraca usług; tabela mapowań jest konfigurowana przez usera |
|
||||
| Usunięto pattern matching po numerze śledzenia | Zawodne — 13-cyfrowy numer może być DPD, Orlen lub inny |
|
||||
| Przycisk Pobierz w show.php zmieniony na POST form | Pre-existing bug — link do prepare zamiast bezpośredniego downloadu |
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `.paul/STATE.md` | Live project state |
|
||||
| `.paul/ROADMAP.md` | Phase overview |
|
||||
| `.paul/phases/28-shipment-tracking-ui/28-01-PLAN.md` | Current plan |
|
||||
| `.paul/phases/27-shipment-tracking-backend/27-01-SUMMARY.md` | Phase 27 summary |
|
||||
| `src/Modules/Shipments/DeliveryStatus.php` | Status mapping + trackingUrl() |
|
||||
| `resources/views/orders/show.php` | Badge'e + link śledzenia w zamówieniu |
|
||||
| `resources/views/shipments/prepare.php` | Badge'e + link śledzenia w przygotowaniu |
|
||||
| `DOCS/SHIPMENT_TRACKING_STATUSES.md` | Dokumentacja statusów API |
|
||||
|
||||
---
|
||||
|
||||
## Modified Files (uncommitted)
|
||||
|
||||
- `src/Modules/Shipments/DeliveryStatus.php` — trackingUrl() z carrier detection + Google fallback
|
||||
- `src/Modules/Shipments/ShipmentPackageRepository.php` — resolveCarrierName()
|
||||
- `src/Modules/Shipments/ApaczkaShipmentService.php` — fallback carrier_id z mappings
|
||||
- `resources/views/orders/show.php` — kolumna Status dostawy, badge, link, fix Pobierz
|
||||
- `resources/views/shipments/prepare.php` — kolumna Status dostawy, badge, link
|
||||
- `resources/scss/modules/_delivery-status.scss` — style badge'ów
|
||||
- `resources/scss/app.scss` — @use delivery-status
|
||||
- `public/assets/css/app.css` — rebuilt
|
||||
- `database/migrations/20260323_000060_*` — fix initial status values
|
||||
|
||||
---
|
||||
|
||||
## Resume Instructions
|
||||
|
||||
1. Read `.paul/STATE.md` for latest position
|
||||
2. Check this handoff file
|
||||
3. Run `/paul:resume` or continue APPLY manually:
|
||||
- Get checkpoint approval for Task 2 (badge'e UI)
|
||||
- Execute Task 3 (cron interval settings in settings/cron.php)
|
||||
- Then sonar + `/paul:unify`
|
||||
|
||||
---
|
||||
|
||||
*Handoff created: 2026-03-23*
|
||||
253
.paul/plans/20260518-2305-polskie-tlumaczenia/PLAN.md
Normal file
253
.paul/plans/20260518-2305-polskie-tlumaczenia/PLAN.md
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
plan_id: 20260518-2305-polskie-tlumaczenia
|
||||
title: Poprawne polskie tlumaczenia UI
|
||||
storage: plan-first
|
||||
legacy_phase: null
|
||||
created: 2026-05-18T23:05:52+02:00
|
||||
status: unified
|
||||
type: execute
|
||||
autonomous: true
|
||||
delegation: auto
|
||||
files_modified:
|
||||
- resources/lang/pl.php
|
||||
- resources/views/
|
||||
- public/assets/js/modules/
|
||||
- src/Modules/
|
||||
- DOCS/ARCHITECTURE.md
|
||||
- DOCS/TECH_CHANGELOG.md
|
||||
- .paul/plans/20260518-2305-polskie-tlumaczenia/SUMMARY.md
|
||||
quality_radar: degraded
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Wdrozyc poprawne polskie znaki w tekstach interfejsu orderPRO, np. `Zamowienia` -> `Zamówienia`, bez zmiany logiki biznesowej.
|
||||
|
||||
## Purpose
|
||||
Interfejs ma wygladac profesjonalnie i naturalnie po polsku. Obecnie duza czesc etykiet, komunikatow i opisow jest zapisana bez polskich znakow albo w niejednolitym kodowaniu.
|
||||
|
||||
## Output
|
||||
- Skorygowany glowny slownik `resources/lang/pl.php`.
|
||||
- Skorygowane najbardziej widoczne hardcoded teksty w widokach, modulach JS i komunikatach kontrolerow.
|
||||
- Zaktualizowane `DOCS/ARCHITECTURE.md` i `DOCS/TECH_CHANGELOG.md`.
|
||||
- Raport/summary w `.paul/plans/20260518-2305-polskie-tlumaczenia/SUMMARY.md` po APPLY.
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Docs
|
||||
@AGENTS.md
|
||||
@DOCS/ARCHITECTURE.md
|
||||
@DOCS/DB_SCHEMA.md
|
||||
@DOCS/TECH_CHANGELOG.md
|
||||
@.paul/PROJECT.md
|
||||
@.paul/STATE.md
|
||||
@.paul/codebase/architecture.md
|
||||
@.paul/codebase/impact_map.md
|
||||
@.paul/codebase/quality_risks.md
|
||||
@.paul/codebase/tooling_status.md
|
||||
|
||||
## Source Files
|
||||
@resources/lang/pl.php
|
||||
@src/Core/I18n/Translator.php
|
||||
@src/Core/View/Template.php
|
||||
@resources/views/layouts/app.php
|
||||
@resources/views/auth/login.php
|
||||
@resources/views/orders/list.php
|
||||
@resources/views/orders/show.php
|
||||
@resources/views/orders/partials/preview-content.php
|
||||
@resources/views/orders/partials/email-send-modal.php
|
||||
@resources/views/shipments/prepare.php
|
||||
@resources/views/settings/*.php
|
||||
@resources/views/accounting/*.php
|
||||
@resources/views/automation/*.php
|
||||
@public/assets/js/modules/*.js
|
||||
@src/Modules/*/*Controller.php
|
||||
@src/Modules/*/*Service.php
|
||||
</context>
|
||||
|
||||
<clarifications>
|
||||
- No clarifications needed.
|
||||
- Zakres przyjety: poprawiamy teksty widoczne dla operatora w UI, flashach i komunikatach JS/PHP.
|
||||
- Poza zakresem sa wartosci techniczne, klucze statusow, nazwy marek/providera (`orderPRO`, `shopPRO`, `SMSPLANET`, `Polkurier`, API keys), raw payloady i dane importowane z marketplace.
|
||||
</clarifications>
|
||||
|
||||
<impact_scan>
|
||||
## Quality Radar
|
||||
|
||||
**Status:** degraded
|
||||
**Tools:** codebase-memory-mcp ok, jscpd ok przez `npx`, ast-grep degraded/unavailable
|
||||
|
||||
## Commands / Sources
|
||||
|
||||
- MCP: `list_projects`, `search_graph` dla `Translator`, `get_code_snippet` dla `src/Core/I18n/Translator.php`, `search_code` targeted.
|
||||
- Fallback `rg`: wyszukiwanie tekstow bez polskich znakow oraz uzyc `$t()`/`Translator`.
|
||||
- `npx --yes jscpd resources/lang resources/views public/assets/js/modules src/Modules --reporters json,console --output .paul/codebase/radar/jscpd-i18n-plan --threshold 100 ...`
|
||||
|
||||
## Affected Areas
|
||||
|
||||
- Translation layer: `resources/lang/pl.php`, `src/Core/I18n/Translator.php`, `src/Core/View/Template.php`.
|
||||
- Views: `resources/views/orders/`, `resources/views/settings/`, `resources/views/accounting/`, `resources/views/automation/`, `resources/views/layouts/`.
|
||||
- Frontend modules: `public/assets/js/modules/confirm-delete.js`, `order-notes.js`, `inline-status-change.js`, `invoice-requested-toggle.js`, `automation-form.js`, `sms-template-picker.js`, `email`/preview related modules where present.
|
||||
- Backend operator messages: selected controllers/services under `src/Modules/*` where hardcoded Polish UI/flash/error strings are returned to the operator.
|
||||
- Docs only: `DOCS/ARCHITECTURE.md`, `DOCS/TECH_CHANGELOG.md`; no DB schema change expected.
|
||||
|
||||
## Duplicate / Hardcoded Risks
|
||||
|
||||
- `resources/lang/pl.php` already centralizes many labels, but hardcoded UI text remains in views/JS/controllers. Handle by correcting visible hardcoded strings where small and low-risk; defer broad i18n refactor.
|
||||
- jscpd targeted scan found existing view/controller duplication, especially settings/accounting forms and integration pages. Do not refactor duplicated markup in this plan; only adjust display text.
|
||||
- Native prompt risks from `.paul/codebase/quality_risks.md` remain relevant but are not part of this translation-only work unless a touched string is inside an existing prompt.
|
||||
|
||||
## Encoding Risks
|
||||
|
||||
- Some files already contain proper UTF-8 Polish characters, while many strings are ASCII-transliterated. Preserve UTF-8 encoding and avoid introducing mojibake.
|
||||
- Do not apply blind global replacements to source code identifiers, array keys, routes, CSS classes, status codes, provider codes or external API payload keys.
|
||||
|
||||
## Explicit Deferrals
|
||||
|
||||
- Full i18n architecture cleanup and migration of every hardcoded string to `resources/lang/pl.php` is deferred; this plan focuses on correcting Polish copy without broad structural churn.
|
||||
- Database content cleanup is deferred. Existing DB rows, imported order data, template records and user-created values are not modified.
|
||||
- Native `alert()`/`confirm()` cleanup is deferred except for text corrections in already-touched files.
|
||||
</impact_scan>
|
||||
|
||||
<skills>
|
||||
- Superseded after UNIFY: orderPRO now treats SonarQube / `sonar-scanner` as manual on-demand only, not as an automatic APPLY/UNIFY requirement.
|
||||
</skills>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Navigation And Core Labels Use Polish Characters
|
||||
```gherkin
|
||||
Given the operator opens the authenticated layout
|
||||
When they view the sidebar, topbar and main order/statistics/settings pages
|
||||
Then common labels such as "Zamówienia", "Użytkownicy", "Księgowość", "Przesyłki", "Płatności" and "Źródło" display with Polish diacritics
|
||||
```
|
||||
|
||||
## AC-2: Translation File Is UTF-8 And Semantically Correct
|
||||
```gherkin
|
||||
Given resources/lang/pl.php is loaded by Translator
|
||||
When PHP parses the file
|
||||
Then it returns a valid array and corrected Polish strings without mojibake sequences like "zamĂ" or ASCII-only words like "Zamowienia" in user-facing values
|
||||
```
|
||||
|
||||
## AC-3: Visible Hardcoded UI Copy Is Corrected
|
||||
```gherkin
|
||||
Given the operator uses orders, shipments, settings, accounting, automation and notification surfaces
|
||||
When visible hardcoded PHP/JS messages are rendered
|
||||
Then corrected Polish copy is used without changing routes, keys, form names, CSS classes or JS behavior
|
||||
```
|
||||
|
||||
## AC-4: External And Technical Contracts Are Preserved
|
||||
```gherkin
|
||||
Given provider/API code, status codes, route paths and database keys exist
|
||||
When translations are corrected
|
||||
Then technical identifiers such as source keys, provider codes, route names, config keys and payload field names remain unchanged
|
||||
```
|
||||
|
||||
## AC-5: Documentation And Verification Are Complete
|
||||
```gherkin
|
||||
Given the implementation is finished
|
||||
When verification runs
|
||||
Then PHP lint passes for touched PHP files, relevant asset/build checks are attempted, docs are updated, and any unavailable tools are recorded
|
||||
```
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Build a targeted translation audit list</name>
|
||||
<files>resources/lang/pl.php, resources/views/**/*.php, public/assets/js/modules/*.js, src/Modules/**/*.php</files>
|
||||
<action>
|
||||
Use targeted searches for common ASCII Polish words (`Zamow`, `Przesyl`, `platnos`, `zrodlo`, `uzytk`, `usun`, `blad`, `wysyl`, `ksiegow`, `dostep`, etc.) and mojibake markers (`Ă`, `Ĺ`, `—`, `â†`) in user-facing strings. Classify matches into: safe UI copy, technical identifiers to preserve, external/provider text to preserve, and uncertain cases.
|
||||
</action>
|
||||
<verify>Review the match list before editing; confirm no routes/keys/classes/status codes are queued for mutation.</verify>
|
||||
<done>Supports AC-2, AC-3 and AC-4.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Correct the central Polish translation file</name>
|
||||
<files>resources/lang/pl.php</files>
|
||||
<action>
|
||||
Replace ASCII-transliterated and mojibake user-facing values with proper UTF-8 Polish text. Keep array keys, placeholder tokens (`:name`, `:count`) and product/provider names unchanged. Prefer natural Polish copy, not mechanical character replacement, especially for messages and descriptions.
|
||||
</action>
|
||||
<verify>C:\xampp\php\php.exe -l resources/lang/pl.php; run a PHP one-liner requiring the file and counting non-array/parse failures if useful.</verify>
|
||||
<done>AC-1 and AC-2 pass for dictionary-backed UI.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Correct visible hardcoded PHP view text</name>
|
||||
<files>resources/views/**/*.php</files>
|
||||
<action>
|
||||
Correct user-facing text embedded directly in views, especially orders, shipments, settings, accounting, automation, layouts and partials. Do not move large blocks to the translator unless the surrounding file already uses `$t()` and the change is small. Preserve `e()` escaping, CSRF fields, form names, data attributes and component contracts.
|
||||
</action>
|
||||
<verify>C:\xampp\php\php.exe -l for every touched PHP view file; targeted `rg` check for remaining high-signal ASCII forms in touched files.</verify>
|
||||
<done>AC-1, AC-3 and AC-4 pass for PHP-rendered UI.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 4: Correct visible JS and backend operator messages</name>
|
||||
<files>public/assets/js/modules/*.js, src/Modules/**/*.php</files>
|
||||
<action>
|
||||
Correct user-facing JS alert/confirm/status text and backend flash/API messages returned to UI. Avoid changing exception class names, log/event type identifiers, API payload keys, database codes or source/status mapping keys.
|
||||
</action>
|
||||
<verify>C:\xampp\php\php.exe -l for touched PHP classes; if asset scripts are changed and package scripts exist, run the project asset build/check command. Otherwise run targeted `rg` and manual code review.</verify>
|
||||
<done>AC-3 and AC-4 pass for JS/backend messages.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 5: Update technical documentation</name>
|
||||
<files>DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md, .paul/plans/20260518-2305-polskie-tlumaczenia/SUMMARY.md</files>
|
||||
<action>
|
||||
Document that the Polish UI copy was normalized to UTF-8 diacritics, note the affected layers, and state that no DB/schema/API contract changed. Do not modify `DOCS/DB_SCHEMA.md` except to explicitly skip it in the summary because there is no schema change.
|
||||
</action>
|
||||
<verify>Docs mention changed translation/UI behavior and no migration requirement.</verify>
|
||||
<done>AC-5 passes.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 6: Final verification and residual scan</name>
|
||||
<files>resources/lang/pl.php, touched PHP/JS/docs files</files>
|
||||
<action>
|
||||
Run lint on touched PHP files, targeted residual `rg` for common unaccented Polish words and mojibake markers, and `git diff --check`. Manually inspect the most visible pages if local app is available: `/orders/list`, `/orders/{id}`, `/settings/integrations`, `/settings/accounting`, `/settings/email-mailboxes`, `/statistics/orders`.
|
||||
</action>
|
||||
<verify>C:\xampp\php\php.exe -l touched PHP files; `git diff --check`; residual `rg`; SonarQube only on explicit operator request.</verify>
|
||||
<done>All AC pass or gaps are explicitly documented in SUMMARY.md.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
## Do Not Change
|
||||
- No database migrations or data backfills.
|
||||
- No changes to route paths, array keys, config keys, env names, status codes, provider codes, CSS class names, JS selectors, API payload fields or marketplace raw data.
|
||||
- Do not rewrite product/order/customer data stored in DB.
|
||||
- Do not introduce `DB_HOST_REMOTE` into runtime config.
|
||||
- Do not add native `alert()`/`confirm()` usage.
|
||||
|
||||
## Scope Limits
|
||||
- This is a copy/translation normalization plan, not a broad i18n refactor.
|
||||
- Brand and product names stay as written: `orderPRO`, `shopPRO`, `SMSPLANET`, `HostedSMS`, `Fakturownia`, `Polkurier`, `InPost`, `Apaczka`, `Erli`, `Allegro`.
|
||||
- Technical English labels may remain when they are intentional (`API Key`, `Base URL`, `OAuth`, `HTTP`, `JSON`, `SKU`, `EAN`).
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
- [ ] `C:\xampp\php\php.exe -l resources/lang/pl.php`
|
||||
- [ ] `C:\xampp\php\php.exe -l` for every touched PHP file
|
||||
- [ ] Targeted residual `rg` for mojibake markers and common ASCII Polish words in touched user-facing files
|
||||
- [ ] `git diff --check`
|
||||
- [ ] Asset build/check attempted if JS/SCSS build inputs require it
|
||||
- [ ] Manual smoke of key UI pages if local app/browser session is available
|
||||
- [ ] SonarQube skipped unless explicitly requested by operator
|
||||
- [ ] Quality Radar relevant risks handled or deferred
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- [ ] All AC pass.
|
||||
- [ ] The most visible UI surfaces show proper Polish diacritics.
|
||||
- [ ] No technical identifiers/contracts were accidentally translated.
|
||||
- [ ] Verification complete with gaps documented.
|
||||
- [ ] `DOCS/ARCHITECTURE.md` and `DOCS/TECH_CHANGELOG.md` updated; `DOCS/DB_SCHEMA.md` unchanged because no schema change.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
SUMMARY.md path: `.paul/plans/20260518-2305-polskie-tlumaczenia/SUMMARY.md`
|
||||
</output>
|
||||
95
.paul/plans/20260518-2305-polskie-tlumaczenia/SUMMARY.md
Normal file
95
.paul/plans/20260518-2305-polskie-tlumaczenia/SUMMARY.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
plan_id: 20260518-2305-polskie-tlumaczenia
|
||||
title: Poprawne polskie tlumaczenia UI
|
||||
completed: 2026-05-18T23:40:00+02:00
|
||||
storage: plan-first
|
||||
quality_radar: degraded
|
||||
---
|
||||
|
||||
# Summary: Poprawne polskie tlumaczenia UI
|
||||
|
||||
## Objective
|
||||
|
||||
Normalize visible Polish UI copy in orderPRO to proper UTF-8 Polish diacritics, without changing business logic, database schema, routes, form contracts, API payloads or technical identifiers.
|
||||
|
||||
## What Was Built
|
||||
|
||||
| Area | Result |
|
||||
|------|--------|
|
||||
| Central translations | `resources/lang/pl.php` now uses natural Polish UI labels and messages with diacritics. |
|
||||
| PHP views | Visible hardcoded copy in orders, shipments, settings, accounting, automation, receipt and shared component views was corrected. |
|
||||
| JavaScript messages | Existing module alerts/confirms/status texts were corrected without adding native prompt usage. |
|
||||
| Backend operator messages | Selected flash/API/error messages returned to the UI were corrected in module controllers/services. |
|
||||
| Template variables | Human labels/descriptions were localized while preserving ASCII placeholder keys such as `{{zamowienie.numer}}` and `{{przesylka.numer}}`. |
|
||||
| Documentation | `DOCS/ARCHITECTURE.md`, `DOCS/TECH_CHANGELOG.md` and PAUL radar/status artifacts were updated. |
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `resources/lang/pl.php` - corrected central Polish translation values.
|
||||
- `resources/views/**` - corrected visible legacy hardcoded UI copy.
|
||||
- `public/assets/js/modules/*.js` - corrected visible browser messages in existing modules.
|
||||
- `src/Modules/**/*.php` - corrected selected operator-facing flash/API/error text.
|
||||
- `src/Modules/Settings/TemplateVariableCatalog.php` - localized human labels/descriptions while preserving variable keys.
|
||||
- `DOCS/ARCHITECTURE.md` - documented Polish UI copy conventions and contract boundaries.
|
||||
- `DOCS/TECH_CHANGELOG.md` - recorded the technical change and no-schema-change scope.
|
||||
- `.paul/codebase/*` and `.paul/codebase/radar/jscpd-i18n-post-apply/` - recorded post-apply radar results.
|
||||
- `.paul/STATE.md` - updated loop state.
|
||||
- `.paul/plans/20260518-2305-polskie-tlumaczenia/*` - stored audit, residual scan and summary artifacts.
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Evidence |
|
||||
|-----------|--------|----------|
|
||||
| AC-1: Navigation and core labels use Polish characters | Pass | `resources/lang/pl.php` and visible views were normalized; residual scan no longer reports uncorrected high-signal UI labels such as `Zamowienia`, `Przesylki`, `Platnosci`, `Pokaz`. |
|
||||
| AC-2: Translation file is UTF-8 and semantically correct | Pass | `C:\xampp\php\php.exe -l resources\lang\pl.php` passed; PHP require check returned a valid array with 16 top-level keys; mojibake scan found no runtime/docs matches in changed scope. |
|
||||
| AC-3: Visible hardcoded UI copy is corrected | Pass | Orders, shipments, settings, accounting, automation, receipt, component and JS/backend user-facing strings were corrected; remaining scan hits are technical identifiers or external/provider URLs. |
|
||||
| AC-4: External and technical contracts are preserved | Pass | Placeholder guard found no `{{...}}` keys with Polish characters; `zamowienie.*`, `przesylka.*`, provider status slugs, route/form/API keys and URLs were intentionally left ASCII. |
|
||||
| AC-5: Documentation and verification are complete | Partial | Docs and PAUL artifacts were updated; PHP lint, residual scans, jscpd and `git diff --check` passed. Manual browser smoke remains a gap due local app/browser session availability. SonarQube was removed from the automatic PAUL workflow after this plan and is now manual on-demand only. |
|
||||
|
||||
## Verification Results
|
||||
|
||||
| Check | Result | Notes |
|
||||
|-------|--------|-------|
|
||||
| `C:\xampp\php\php.exe -l resources\lang\pl.php` | Pass | No syntax errors. |
|
||||
| `C:\xampp\php\php.exe -l` for changed PHP files | Pass | 91 changed PHP files linted successfully. |
|
||||
| PHP require check for `resources/lang/pl.php` | Pass | File returns an array with 16 top-level keys. |
|
||||
| Residual ASCII Polish scan | Pass | Remaining matches classified as template keys, provider status slugs or tracking URLs. |
|
||||
| Placeholder guard | Pass | No `{{...}}` placeholder with Polish characters found. |
|
||||
| Mojibake guard | Pass | No mojibake markers found in changed runtime/docs scope. |
|
||||
| `git diff --check` | Pass | Passed; Git printed only line-ending warnings. |
|
||||
| `npx --yes jscpd ...` | Pass | 226 files analyzed, 397 clones, 4754 duplicated lines; report written to `.paul/codebase/radar/jscpd-i18n-post-apply/jscpd-report.json`. |
|
||||
| `sonar-scanner --version` | Historical / no longer required | It failed during APPLY because `sonar-scanner` was not available in PATH. SonarQube is now manual on-demand only and is not required for future PAUL PLAN/APPLY/UNIFY loops. |
|
||||
| Asset build/check | Skipped | No SCSS/source build inputs were changed; public JS modules were edited directly as existing runtime assets. |
|
||||
| Manual browser smoke | Skipped/Gapped | Local app/browser session was not started during APPLY/UNIFY. |
|
||||
|
||||
## Quality Radar Results
|
||||
|
||||
**Status:** degraded
|
||||
|
||||
- New risks: none from the translation normalization itself.
|
||||
- Resolved risks: high-signal Polish copy/encoding risk for targeted UI surfaces is reduced; residual scan artifacts document preserved technical identifiers.
|
||||
- Deferred risks: existing jscpd duplication in settings/accounting/integration views remains baseline debt; native prompt cleanup remains separate future work; SonarQube is manual on-demand only.
|
||||
- Raw outputs:
|
||||
- `.paul/codebase/radar/jscpd-i18n-post-apply/jscpd-report.json`
|
||||
- `.paul/plans/20260518-2305-polskie-tlumaczenia/translation-audit.txt`
|
||||
- `.paul/plans/20260518-2305-polskie-tlumaczenia/hardcoded-ui-files.txt`
|
||||
- `.paul/plans/20260518-2305-polskie-tlumaczenia/residual-ui-scan.txt`
|
||||
|
||||
## Deviations
|
||||
|
||||
- The plan asked to attempt relevant asset/build checks if JS/SCSS build inputs required it; no SCSS/source build inputs changed, so no asset build was run.
|
||||
- Manual browser smoke was planned "if local app/browser session is available"; it was not run in this session.
|
||||
- `sonar-scanner` was attempted during APPLY under the old policy, but SonarQube has since been removed from automatic PAUL verification and moved to manual on-demand use.
|
||||
- The plan-first work did not modify `DOCS/DB_SCHEMA.md` because there was no schema change.
|
||||
|
||||
## Key Decisions / Patterns
|
||||
|
||||
- Correct visible copy directly where legacy views/modules already hardcode text; avoid a broad i18n refactor in this translation-only plan.
|
||||
- Preserve ASCII technical contracts for placeholder keys, provider slugs, route paths, form names, CSS classes, JS selectors, config keys and API payload fields.
|
||||
- Treat marketplace/imported/user-created data as out of scope; this change only touches source-controlled UI copy.
|
||||
|
||||
## Follow-up
|
||||
|
||||
- Run manual smoke on key pages: `/orders/list`, order detail, `/settings/integrations`, `/settings/accounting`, `/settings/email-mailboxes`, shipment prepare and statistics pages.
|
||||
- Run SonarQube only on explicit operator request.
|
||||
- Plan a separate cleanup for native prompt fallbacks and repeated settings/accounting/integration layout patterns if still valuable.
|
||||
@@ -0,0 +1,87 @@
|
||||
src\Modules\Automation\OrderStatusAgedService.php
|
||||
src\Modules\Email\EmailSendingService.php
|
||||
src\Modules\Accounting\ReceiptService.php
|
||||
public\assets\js\modules\order-notes.js
|
||||
src\Modules\Automation\AutomationController.php
|
||||
src\Modules\Automation\AutomationService.php
|
||||
src\Modules\Settings\AllegroIntegrationController.php
|
||||
src\Modules\Accounting\ReceiptController.php
|
||||
src\Modules\Settings\AllegroApiClient.php
|
||||
src\Modules\Settings\AllegroOAuthClient.php
|
||||
public\assets\js\modules\invoice-requested-toggle.js
|
||||
src\Modules\Accounting\InvoiceService.php
|
||||
src\Modules\Settings\AllegroOrderImportService.php
|
||||
public\assets\js\modules\inline-status-change.js
|
||||
public\assets\js\modules\automation-form.js
|
||||
public\assets\js\modules\confirm-delete.js
|
||||
src\Modules\Printing\PrintApiController.php
|
||||
resources\views\shipments\prepare.php
|
||||
src\Modules\Accounting\InvoiceController.php
|
||||
src\Modules\Shipments\AllegroShipmentService.php
|
||||
src\Modules\Settings\AllegroTokenManager.php
|
||||
src\Modules\Shipments\ApaczkaShipmentService.php
|
||||
src\Modules\Settings\AllegroStatusSyncService.php
|
||||
src\Modules\Sms\SmsVariableResolver.php
|
||||
src\Modules\Settings\ApaczkaApiClient.php
|
||||
resources\views\orders\show.php
|
||||
resources\views\orders\receipt-create.php
|
||||
src\Modules\Shipments\DeliveryStatus.php
|
||||
src\Modules\Shipments\InpostShipmentService.php
|
||||
src\Modules\Orders\OrderNotesService.php
|
||||
src\Modules\Orders\OrdersController.php
|
||||
src\Modules\Shipments\PolkurierShipmentService.php
|
||||
src\Modules\Settings\DeliveryStatusesController.php
|
||||
src\Modules\Shipments\PolkurierTrackingService.php
|
||||
resources\views\orders\partials\preview-content.php
|
||||
resources\views\accounting\invoices_issued_list.php
|
||||
src\Modules\Orders\OrdersRepository.php
|
||||
resources\views\accounting\invoice_form.php
|
||||
resources\views\components\table-list.php
|
||||
src\Modules\Shipments\ShipmentController.php
|
||||
resources\views\accounting\invoice_pdf.php
|
||||
src\Modules\Settings\EmailTemplateController.php
|
||||
resources\views\orders\partials\email-send-modal.php
|
||||
src\Modules\Shipments\ShipmentPackageRepository.php
|
||||
resources\views\accounting\invoice_preview.php
|
||||
src\Modules\Settings\EmailMailboxController.php
|
||||
src\Modules\Settings\ErliApiClient.php
|
||||
resources\views\settings\accounting-receipt-edit.php
|
||||
src\Modules\Settings\ErliExternalShipmentService.php
|
||||
resources\views\settings\accounting-invoice-edit.php
|
||||
resources\views\settings\accounting-invoices.php
|
||||
resources\views\settings\allegro.php
|
||||
resources\views\settings\accounting-receipts.php
|
||||
resources\views\settings\email-mailboxes.php
|
||||
resources\views\settings\accounting.php
|
||||
resources\views\automation\index.php
|
||||
resources\views\settings\delivery-statuses.php
|
||||
resources\views\automation\form.php
|
||||
resources\views\settings\email-templates.php
|
||||
src\Modules\Settings\ErliOrderMapper.php
|
||||
resources\views\settings\email-templates-form.php
|
||||
resources\views\settings\fakturownia-edit.php
|
||||
resources\views\settings\fakturownia.php
|
||||
src\Modules\Settings\ErliOrdersSyncService.php
|
||||
resources\views\receipts\print.php
|
||||
resources\views\receipts\show.php
|
||||
src\Modules\Settings\ErliStatusSyncService.php
|
||||
src\Modules\Settings\FakturowniaApiClient.php
|
||||
resources\views\settings\printing.php
|
||||
src\Modules\Settings\FakturowniaIntegrationController.php
|
||||
src\Modules\Settings\FakturowniaIntegrationRepository.php
|
||||
resources\views\settings\sms-templates-form.php
|
||||
src\Modules\Settings\HostedSmsApiClient.php
|
||||
resources\views\settings\sms-templates.php
|
||||
src\Modules\Settings\InvoiceConfigController.php
|
||||
src\Modules\Settings\InvoiceConfigRepository.php
|
||||
src\Modules\Settings\PolkurierApiClient.php
|
||||
src\Modules\Settings\PrintSettingsController.php
|
||||
src\Modules\Settings\ReceiptConfigController.php
|
||||
src\Modules\Settings\ShopproApiClient.php
|
||||
src\Modules\Settings\ShopproIntegrationsRepository.php
|
||||
src\Modules\Settings\ShopproOrdersSyncService.php
|
||||
src\Modules\Settings\ShopproPaymentStatusSyncService.php
|
||||
src\Modules\Settings\SmsplanetApiClient.php
|
||||
src\Modules\Settings\ShopproStatusSyncService.php
|
||||
src\Modules\Settings\TemplateVariableCatalog.php
|
||||
src\Modules\Settings\SmsTemplateController.php
|
||||
@@ -0,0 +1,32 @@
|
||||
# Residual UI Copy Scan
|
||||
|
||||
Timestamp: 2026-05-18 23:25 Europe/Warsaw
|
||||
Scope: resources/lang/pl.php, resources/views, public/assets/js/modules, src/Modules
|
||||
|
||||
## Command
|
||||
|
||||
rg -n "Wysyl|wysyl|Przesyl|przesyl|Zamow|zamow|Platn|platn|notatke|cofnac|ladowania|podgladu|odebrala|przesylke|przesylek|statusówi|inform¹c|Pokaz" resources/lang/pl.php resources/views public/assets/js/modules src/Modules
|
||||
|
||||
## Classification
|
||||
|
||||
Remaining matches are intentional technical identifiers/placeholders or provider URLs/status codes:
|
||||
|
||||
- SMS/e-mail template variable keys: `zamowienie.*`, `przesylka.*`, `kupujacy.*`; these are public template contract keys and must stay ASCII.
|
||||
- Delivery provider status codes and URL slugs: e.g. InPost/DHL tracking URLs and provider status slugs in `DeliveryStatus.php`; these are external/provider-facing identifiers and must stay ASCII.
|
||||
- UI copy matches from the scan were corrected during APPLY: statusowi, synchronizacji, Poka¿, podgl¹du, cofn¹æ, wysy³ka/wysy³anie.
|
||||
|
||||
## Placeholder Guard
|
||||
|
||||
Command:
|
||||
|
||||
rg -n "\{\{[^}\r\n]*[¹æê³ñ󜟿¥ÆÊ£ÑÓŒ<C393>¯][^}\r\n]*\}\}" resources/views resources/lang src public/assets/js/modules
|
||||
|
||||
Result: no matches. Template placeholders still use ASCII keys.
|
||||
|
||||
## Mojibake Guard
|
||||
|
||||
Command:
|
||||
|
||||
rg -n "Ä|A|A|Â|Ã|â€|Å|¼" resources/lang/pl.php resources/views public/assets/js/modules src/Modules DOCS
|
||||
|
||||
Result: no runtime/documentation matches in changed product/docs scope.
|
||||
1072
.paul/plans/20260518-2305-polskie-tlumaczenia/translation-audit.txt
Normal file
1072
.paul/plans/20260518-2305-polskie-tlumaczenia/translation-audit.txt
Normal file
File diff suppressed because it is too large
Load Diff
454
.vscode/ftp-kr.sync.cache.json
vendored
454
.vscode/ftp-kr.sync.cache.json
vendored
@@ -3,7 +3,7 @@
|
||||
"public_html": {
|
||||
"AGENTS.md": {
|
||||
"type": "-",
|
||||
"size": 3719,
|
||||
"size": 3729,
|
||||
"lmtime": 1772652932723,
|
||||
"modified": true
|
||||
},
|
||||
@@ -1017,27 +1017,27 @@
|
||||
"DOCS": {
|
||||
"ARCHITECTURE.md": {
|
||||
"type": "-",
|
||||
"size": 34823,
|
||||
"lmtime": 1779040812347,
|
||||
"size": 38673,
|
||||
"lmtime": 1779128071678,
|
||||
"modified": false
|
||||
},
|
||||
"DB_SCHEMA.md": {
|
||||
"type": "-",
|
||||
"size": 48789,
|
||||
"lmtime": 1779040840321,
|
||||
"size": 50221,
|
||||
"lmtime": 1779128071679,
|
||||
"modified": false
|
||||
},
|
||||
"TECH_CHANGELOG.md": {
|
||||
"type": "-",
|
||||
"size": 29892,
|
||||
"lmtime": 1779040825129,
|
||||
"size": 36923,
|
||||
"lmtime": 1779128071680,
|
||||
"modified": false
|
||||
},
|
||||
"todo.md": {
|
||||
"type": "-",
|
||||
"size": 878,
|
||||
"lmtime": 1779040800859,
|
||||
"modified": false
|
||||
"size": 1119,
|
||||
"lmtime": 1779052968103,
|
||||
"modified": true
|
||||
}
|
||||
},
|
||||
".env": {
|
||||
@@ -1048,7 +1048,7 @@
|
||||
},
|
||||
".env.example": {
|
||||
"type": "-",
|
||||
"size": 1026,
|
||||
"size": 1029,
|
||||
"lmtime": 1775673316997,
|
||||
"modified": true
|
||||
},
|
||||
@@ -2398,8 +2398,8 @@
|
||||
"css": {
|
||||
"app.css": {
|
||||
"type": "-",
|
||||
"size": 68999,
|
||||
"lmtime": 1778885937938,
|
||||
"size": 70439,
|
||||
"lmtime": 1779128071682,
|
||||
"modified": false
|
||||
},
|
||||
"app.css.map": {
|
||||
@@ -2531,8 +2531,8 @@
|
||||
"lang": {
|
||||
"pl.php": {
|
||||
"type": "-",
|
||||
"size": 84241,
|
||||
"lmtime": 1778886050212,
|
||||
"size": 84519,
|
||||
"lmtime": 1779128071684,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
@@ -2579,8 +2579,8 @@
|
||||
},
|
||||
"app.scss": {
|
||||
"type": "-",
|
||||
"size": 57489,
|
||||
"lmtime": 1778885937942,
|
||||
"size": 58639,
|
||||
"lmtime": 1779128071685,
|
||||
"modified": false
|
||||
},
|
||||
"components": {
|
||||
@@ -2696,14 +2696,14 @@
|
||||
"accounting": {
|
||||
"index.php": {
|
||||
"type": "-",
|
||||
"size": 3547,
|
||||
"size": 3532,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"invoice_form.php": {
|
||||
"type": "-",
|
||||
"size": 13645,
|
||||
"lmtime": 1778617477767,
|
||||
"size": 13609,
|
||||
"lmtime": 1779052663465,
|
||||
"modified": false
|
||||
},
|
||||
"invoice_pdf.php": {
|
||||
@@ -2728,22 +2728,22 @@
|
||||
"auth": {
|
||||
"login.php": {
|
||||
"type": "-",
|
||||
"size": 1784,
|
||||
"lmtime": 1775815268579,
|
||||
"size": 1835,
|
||||
"lmtime": 1779052631802,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"automation": {
|
||||
"form.php": {
|
||||
"type": "-",
|
||||
"size": 18891,
|
||||
"lmtime": 1777132029418,
|
||||
"size": 18966,
|
||||
"lmtime": 1779052631816,
|
||||
"modified": false
|
||||
},
|
||||
"index.php": {
|
||||
"type": "-",
|
||||
"size": 15396,
|
||||
"lmtime": 1775590768288,
|
||||
"size": 15857,
|
||||
"lmtime": 1779052631813,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
@@ -2772,20 +2772,20 @@
|
||||
"layouts": {
|
||||
"app.php": {
|
||||
"type": "-",
|
||||
"size": 17928,
|
||||
"lmtime": 1778940460710,
|
||||
"size": 18389,
|
||||
"lmtime": 1779128071687,
|
||||
"modified": false
|
||||
},
|
||||
"auth.php": {
|
||||
"type": "-",
|
||||
"size": 1549,
|
||||
"lmtime": 1772220107387,
|
||||
"size": 1630,
|
||||
"lmtime": 1779052663470,
|
||||
"modified": true
|
||||
},
|
||||
"public.php": {
|
||||
"type": "-",
|
||||
"size": 1670,
|
||||
"lmtime": 1775673889830,
|
||||
"size": 1751,
|
||||
"lmtime": 1779052663471,
|
||||
"modified": true
|
||||
}
|
||||
},
|
||||
@@ -2812,9 +2812,9 @@
|
||||
},
|
||||
"list.php": {
|
||||
"type": "-",
|
||||
"size": 4620,
|
||||
"lmtime": 1775816812032,
|
||||
"modified": true
|
||||
"size": 4393,
|
||||
"lmtime": 1779128071688,
|
||||
"modified": false
|
||||
},
|
||||
"partials": {
|
||||
"email-send-modal.php": {
|
||||
@@ -2838,15 +2838,15 @@
|
||||
},
|
||||
"receipt-create.php": {
|
||||
"type": "-",
|
||||
"size": 7380,
|
||||
"lmtime": 1775462801767,
|
||||
"size": 7351,
|
||||
"lmtime": 1779052663466,
|
||||
"modified": true
|
||||
},
|
||||
"show.php": {
|
||||
"type": "-",
|
||||
"size": 79678,
|
||||
"lmtime": 1778885937946,
|
||||
"modified": false
|
||||
"size": 79630,
|
||||
"lmtime": 1779052631838,
|
||||
"modified": true
|
||||
}
|
||||
},
|
||||
"products": {
|
||||
@@ -2898,75 +2898,75 @@
|
||||
"settings": {
|
||||
"accounting-invoice-edit.php": {
|
||||
"type": "-",
|
||||
"size": 7002,
|
||||
"lmtime": 1778444374477,
|
||||
"modified": true
|
||||
"size": 6990,
|
||||
"lmtime": 1779052631849,
|
||||
"modified": false
|
||||
},
|
||||
"accounting-invoices.php": {
|
||||
"type": "-",
|
||||
"size": 4273,
|
||||
"lmtime": 1778444348951,
|
||||
"modified": true
|
||||
"size": 4261,
|
||||
"lmtime": 1779052631846,
|
||||
"modified": false
|
||||
},
|
||||
"accounting.php": {
|
||||
"type": "-",
|
||||
"size": 1826,
|
||||
"lmtime": 1778446442249,
|
||||
"modified": true
|
||||
"size": 1814,
|
||||
"lmtime": 1779052631864,
|
||||
"modified": false
|
||||
},
|
||||
"accounting-receipt-edit.php": {
|
||||
"type": "-",
|
||||
"size": 4861,
|
||||
"lmtime": 1778444530228,
|
||||
"modified": true
|
||||
"size": 4849,
|
||||
"lmtime": 1779052631854,
|
||||
"modified": false
|
||||
},
|
||||
"accounting-receipts.php": {
|
||||
"type": "-",
|
||||
"size": 3471,
|
||||
"lmtime": 1778444511447,
|
||||
"modified": true
|
||||
"size": 3459,
|
||||
"lmtime": 1779052631859,
|
||||
"modified": false
|
||||
},
|
||||
"allegro.php": {
|
||||
"type": "-",
|
||||
"size": 42315,
|
||||
"lmtime": 1775589647792,
|
||||
"modified": true
|
||||
"size": 42291,
|
||||
"lmtime": 1779052631889,
|
||||
"modified": false
|
||||
},
|
||||
"apaczka.php": {
|
||||
"type": "-",
|
||||
"size": 2912,
|
||||
"lmtime": 1773001309374,
|
||||
"modified": true
|
||||
"size": 2900,
|
||||
"lmtime": 1779052631894,
|
||||
"modified": false
|
||||
},
|
||||
"company.php": {
|
||||
"type": "-",
|
||||
"size": 7745,
|
||||
"lmtime": 1773009480295,
|
||||
"modified": true
|
||||
"size": 7733,
|
||||
"lmtime": 1779052631899,
|
||||
"modified": false
|
||||
},
|
||||
"cron.php": {
|
||||
"type": "-",
|
||||
"size": 8831,
|
||||
"lmtime": 1774302948459,
|
||||
"modified": true
|
||||
"size": 8819,
|
||||
"lmtime": 1779052631905,
|
||||
"modified": false
|
||||
},
|
||||
"database.php": {
|
||||
"type": "-",
|
||||
"size": 3409,
|
||||
"lmtime": 1772491513567,
|
||||
"modified": true
|
||||
"size": 3385,
|
||||
"lmtime": 1779052631909,
|
||||
"modified": false
|
||||
},
|
||||
"delivery-statuses.php": {
|
||||
"type": "-",
|
||||
"size": 4644,
|
||||
"size": 4655,
|
||||
"lmtime": 1779039854324,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"delivery-status-form.php": {
|
||||
"type": "-",
|
||||
"size": 3189,
|
||||
"lmtime": 1777320073160,
|
||||
"modified": true
|
||||
"size": 3183,
|
||||
"lmtime": 1779052631915,
|
||||
"modified": false
|
||||
},
|
||||
"_delivery-status-mappings-content.php": {
|
||||
"type": "-",
|
||||
@@ -2976,33 +2976,33 @@
|
||||
},
|
||||
"delivery-status-mappings.php": {
|
||||
"type": "-",
|
||||
"size": 124,
|
||||
"size": 126,
|
||||
"lmtime": 1779039857211,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"email-mailboxes.php": {
|
||||
"type": "-",
|
||||
"size": 19326,
|
||||
"lmtime": 1774728209847,
|
||||
"modified": true
|
||||
"size": 19314,
|
||||
"lmtime": 1779052631919,
|
||||
"modified": false
|
||||
},
|
||||
"email-templates-form.php": {
|
||||
"type": "-",
|
||||
"size": 9678,
|
||||
"lmtime": 1775318330251,
|
||||
"modified": true
|
||||
"size": 9666,
|
||||
"lmtime": 1779052631926,
|
||||
"modified": false
|
||||
},
|
||||
"email-templates.php": {
|
||||
"type": "-",
|
||||
"size": 5682,
|
||||
"lmtime": 1775318475603,
|
||||
"modified": true
|
||||
"size": 5670,
|
||||
"lmtime": 1779052631931,
|
||||
"modified": false
|
||||
},
|
||||
"erli.php": {
|
||||
"type": "-",
|
||||
"size": 25931,
|
||||
"lmtime": 1778885104601,
|
||||
"modified": false
|
||||
"size": 26427,
|
||||
"lmtime": 1779052663469,
|
||||
"modified": true
|
||||
},
|
||||
"fakturownia-edit.php": {
|
||||
"type": "-",
|
||||
@@ -3012,9 +3012,9 @@
|
||||
},
|
||||
"fakturownia.php": {
|
||||
"type": "-",
|
||||
"size": 5539,
|
||||
"lmtime": 1778443573844,
|
||||
"modified": true
|
||||
"size": 5521,
|
||||
"lmtime": 1779052631943,
|
||||
"modified": false
|
||||
},
|
||||
"gs1.php": {
|
||||
"type": "-",
|
||||
@@ -3024,21 +3024,21 @@
|
||||
},
|
||||
"hostedsms.php": {
|
||||
"type": "-",
|
||||
"size": 6322,
|
||||
"lmtime": 0,
|
||||
"size": 6304,
|
||||
"lmtime": 1779052631947,
|
||||
"modified": false
|
||||
},
|
||||
"inpost.php": {
|
||||
"type": "-",
|
||||
"size": 7360,
|
||||
"lmtime": 1772750606535,
|
||||
"modified": true
|
||||
"size": 7348,
|
||||
"lmtime": 1779052631952,
|
||||
"modified": false
|
||||
},
|
||||
"integrations.php": {
|
||||
"type": "-",
|
||||
"size": 2667,
|
||||
"lmtime": 1772986683757,
|
||||
"modified": true
|
||||
"size": 3056,
|
||||
"lmtime": 1779128071689,
|
||||
"modified": false
|
||||
},
|
||||
"order-statuses.php": {
|
||||
"type": "-",
|
||||
@@ -3048,15 +3048,15 @@
|
||||
},
|
||||
"polkurier.php": {
|
||||
"type": "-",
|
||||
"size": 5370,
|
||||
"lmtime": 1778885937947,
|
||||
"modified": false
|
||||
"size": 5377,
|
||||
"lmtime": 1779052663470,
|
||||
"modified": true
|
||||
},
|
||||
"printing.php": {
|
||||
"type": "-",
|
||||
"size": 11190,
|
||||
"lmtime": 1774475356232,
|
||||
"modified": true
|
||||
"size": 11151,
|
||||
"lmtime": 1779052631967,
|
||||
"modified": false
|
||||
},
|
||||
"products.php": {
|
||||
"type": "-",
|
||||
@@ -3066,54 +3066,54 @@
|
||||
},
|
||||
"project-mappings.php": {
|
||||
"type": "-",
|
||||
"size": 9752,
|
||||
"lmtime": 1776018345590,
|
||||
"modified": true
|
||||
"size": 9740,
|
||||
"lmtime": 1779052631973,
|
||||
"modified": false
|
||||
},
|
||||
"shoppro.php": {
|
||||
"type": "-",
|
||||
"size": 48192,
|
||||
"lmtime": 1773003933110,
|
||||
"modified": true
|
||||
"size": 52281,
|
||||
"lmtime": 1779128071690,
|
||||
"modified": false
|
||||
},
|
||||
"smsplanet.php": {
|
||||
"type": "-",
|
||||
"size": 10493,
|
||||
"lmtime": 0,
|
||||
"size": 10475,
|
||||
"lmtime": 1779052632006,
|
||||
"modified": false
|
||||
},
|
||||
"sms-templates-form.php": {
|
||||
"type": "-",
|
||||
"size": 5184,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
"size": 5172,
|
||||
"lmtime": 1779052631996,
|
||||
"modified": true
|
||||
},
|
||||
"sms-templates.php": {
|
||||
"type": "-",
|
||||
"size": 4937,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
"size": 4925,
|
||||
"lmtime": 1779052632001,
|
||||
"modified": true
|
||||
},
|
||||
"statuses.php": {
|
||||
"type": "-",
|
||||
"size": 18314,
|
||||
"lmtime": 1772493011660,
|
||||
"modified": true
|
||||
"size": 18296,
|
||||
"lmtime": 1779052632010,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"shipments": {
|
||||
"prepare.php": {
|
||||
"type": "-",
|
||||
"size": 59177,
|
||||
"lmtime": 1778886050211,
|
||||
"size": 59427,
|
||||
"lmtime": 1779128071692,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"index.php": {
|
||||
"type": "-",
|
||||
"size": 1600,
|
||||
"lmtime": 1772491504461,
|
||||
"size": 1697,
|
||||
"lmtime": 1779052631805,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
@@ -3124,14 +3124,28 @@
|
||||
"lmtime": 1775674197653,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"statistics": {
|
||||
"orders.php": {
|
||||
"type": "-",
|
||||
"size": 8320,
|
||||
"lmtime": 1779052631831,
|
||||
"modified": false
|
||||
},
|
||||
"summary.php": {
|
||||
"type": "-",
|
||||
"size": 8567,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routes": {
|
||||
"web.php": {
|
||||
"type": "-",
|
||||
"size": 47936,
|
||||
"lmtime": 1778886050207,
|
||||
"size": 48135,
|
||||
"lmtime": 1779128071693,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
@@ -3422,8 +3436,8 @@
|
||||
},
|
||||
"AutomationRepository.php": {
|
||||
"type": "-",
|
||||
"size": 11262,
|
||||
"lmtime": 1778940460709,
|
||||
"size": 11267,
|
||||
"lmtime": 1779052561910,
|
||||
"modified": false
|
||||
},
|
||||
"AutomationService.php": {
|
||||
@@ -3437,6 +3451,12 @@
|
||||
"size": 5005,
|
||||
"lmtime": 1775948049470,
|
||||
"modified": false
|
||||
},
|
||||
"AutomationRuleException.php": {
|
||||
"type": "-",
|
||||
"size": 155,
|
||||
"lmtime": 1779052561904,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"Cron": {
|
||||
@@ -3636,14 +3656,20 @@
|
||||
},
|
||||
"OrdersController.php": {
|
||||
"type": "-",
|
||||
"size": 65754,
|
||||
"lmtime": 1778940460708,
|
||||
"size": 65729,
|
||||
"lmtime": 1779128071695,
|
||||
"modified": false
|
||||
},
|
||||
"OrderSourceRegistry.php": {
|
||||
"type": "-",
|
||||
"size": 1319,
|
||||
"lmtime": 1778940460704,
|
||||
"modified": true
|
||||
},
|
||||
"OrdersRepository.php": {
|
||||
"type": "-",
|
||||
"size": 49778,
|
||||
"lmtime": 1778940460706,
|
||||
"size": 49731,
|
||||
"lmtime": 1779128071696,
|
||||
"modified": false
|
||||
},
|
||||
"OrderStatusSyncService.php": {
|
||||
@@ -3651,12 +3677,6 @@
|
||||
"size": 17295,
|
||||
"lmtime": 1772489130897,
|
||||
"modified": false
|
||||
},
|
||||
"OrderSourceRegistry.php": {
|
||||
"type": "-",
|
||||
"size": 1262,
|
||||
"lmtime": 1778940460704,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"Printing": {
|
||||
@@ -3782,7 +3802,7 @@
|
||||
},
|
||||
"AllegroIntegrationController.php": {
|
||||
"type": "-",
|
||||
"size": 27140,
|
||||
"size": 27152,
|
||||
"lmtime": 1775589568656,
|
||||
"modified": true
|
||||
},
|
||||
@@ -3878,9 +3898,9 @@
|
||||
},
|
||||
"CarrierDeliveryMethodMappingRepository.php": {
|
||||
"type": "-",
|
||||
"size": 9034,
|
||||
"size": 9081,
|
||||
"lmtime": 1778884980256,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"CompanySettingsController.php": {
|
||||
"type": "-",
|
||||
@@ -3902,19 +3922,19 @@
|
||||
},
|
||||
"DeliveryStatusesController.php": {
|
||||
"type": "-",
|
||||
"size": 11209,
|
||||
"size": 11277,
|
||||
"lmtime": 1779040416085,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"DeliveryStatusMappingController.php": {
|
||||
"type": "-",
|
||||
"size": 10106,
|
||||
"size": 10179,
|
||||
"lmtime": 1779039843270,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"EmailMailboxController.php": {
|
||||
"type": "-",
|
||||
"size": 12752,
|
||||
"size": 12827,
|
||||
"lmtime": 1774725281395,
|
||||
"modified": true
|
||||
},
|
||||
@@ -3926,86 +3946,104 @@
|
||||
},
|
||||
"EmailTemplateController.php": {
|
||||
"type": "-",
|
||||
"size": 9273,
|
||||
"size": 9309,
|
||||
"lmtime": 1778941087323,
|
||||
"modified": true
|
||||
},
|
||||
"EmailTemplateException.php": {
|
||||
"type": "-",
|
||||
"size": 162,
|
||||
"lmtime": 1779052561905,
|
||||
"modified": true
|
||||
},
|
||||
"EmailTemplateRepository.php": {
|
||||
"type": "-",
|
||||
"size": 4583,
|
||||
"lmtime": 1775246458049,
|
||||
"modified": false
|
||||
"size": 4588,
|
||||
"lmtime": 1779052561910,
|
||||
"modified": true
|
||||
},
|
||||
"ErliApiClient.php": {
|
||||
"type": "-",
|
||||
"size": 14681,
|
||||
"size": 15065,
|
||||
"lmtime": 1778885466388,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ErliDeliveryMappingController.php": {
|
||||
"type": "-",
|
||||
"size": 7363,
|
||||
"size": 7557,
|
||||
"lmtime": 1778885057325,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ErliExternalShipmentService.php": {
|
||||
"type": "-",
|
||||
"size": 6386,
|
||||
"size": 6555,
|
||||
"lmtime": 1778885170413,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ErliIntegrationController.php": {
|
||||
"type": "-",
|
||||
"size": 15254,
|
||||
"size": 15611,
|
||||
"lmtime": 1778885023709,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ErliIntegrationRepository.php": {
|
||||
"type": "-",
|
||||
"size": 9274,
|
||||
"size": 9536,
|
||||
"lmtime": 1778881085068,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ErliOrderMapper.php": {
|
||||
"type": "-",
|
||||
"size": 19260,
|
||||
"lmtime": 1778882727736,
|
||||
"modified": false
|
||||
"size": 19733,
|
||||
"lmtime": 1779052561908,
|
||||
"modified": true
|
||||
},
|
||||
"ErliOrderMappingException.php": {
|
||||
"type": "-",
|
||||
"size": 165,
|
||||
"lmtime": 1779052561903,
|
||||
"modified": true
|
||||
},
|
||||
"ErliOrdersSyncService.php": {
|
||||
"type": "-",
|
||||
"size": 13118,
|
||||
"size": 13476,
|
||||
"lmtime": 1778939105137,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ErliOrderSyncStateRepository.php": {
|
||||
"type": "-",
|
||||
"size": 6123,
|
||||
"size": 6304,
|
||||
"lmtime": 1778882722443,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ErliPullStatusMappingRepository.php": {
|
||||
"type": "-",
|
||||
"size": 4993,
|
||||
"size": 5141,
|
||||
"lmtime": 1778882692008,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ErliStatusMappingRepository.php": {
|
||||
"type": "-",
|
||||
"size": 3849,
|
||||
"size": 3966,
|
||||
"lmtime": 1778882692007,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ErliStatusSyncService.php": {
|
||||
"type": "-",
|
||||
"size": 7860,
|
||||
"size": 8083,
|
||||
"lmtime": 1778882820718,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"FakturowniaApiClient.php": {
|
||||
"type": "-",
|
||||
"size": 12570,
|
||||
"lmtime": 1778448029992,
|
||||
"size": 12992,
|
||||
"lmtime": 1779052561906,
|
||||
"modified": true
|
||||
},
|
||||
"FakturowniaApiException.php": {
|
||||
"type": "-",
|
||||
"size": 163,
|
||||
"lmtime": 1779052561901,
|
||||
"modified": true
|
||||
},
|
||||
"FakturowniaIntegrationController.php": {
|
||||
@@ -4064,8 +4102,8 @@
|
||||
},
|
||||
"IntegrationsHubController.php": {
|
||||
"type": "-",
|
||||
"size": 14674,
|
||||
"lmtime": 1778886050206,
|
||||
"size": 15629,
|
||||
"lmtime": 1779128071698,
|
||||
"modified": false
|
||||
},
|
||||
"IntegrationsRepository.php": {
|
||||
@@ -4100,9 +4138,15 @@
|
||||
},
|
||||
"PolkurierApiClient.php": {
|
||||
"type": "-",
|
||||
"size": 12990,
|
||||
"lmtime": 1778885937958,
|
||||
"modified": false
|
||||
"size": 12992,
|
||||
"lmtime": 1779052561907,
|
||||
"modified": true
|
||||
},
|
||||
"PolkurierApiException.php": {
|
||||
"type": "-",
|
||||
"size": 161,
|
||||
"lmtime": 1779052561902,
|
||||
"modified": true
|
||||
},
|
||||
"PolkurierIntegrationController.php": {
|
||||
"type": "-",
|
||||
@@ -4172,9 +4216,9 @@
|
||||
},
|
||||
"ShopproIntegrationsController.php": {
|
||||
"type": "-",
|
||||
"size": 41512,
|
||||
"lmtime": 1773408010714,
|
||||
"modified": true
|
||||
"size": 43731,
|
||||
"lmtime": 1779128071699,
|
||||
"modified": false
|
||||
},
|
||||
"ShopproIntegrationsRepository.php": {
|
||||
"type": "-",
|
||||
@@ -4250,21 +4294,21 @@
|
||||
},
|
||||
"SmsTemplateController.php": {
|
||||
"type": "-",
|
||||
"size": 6282,
|
||||
"lmtime": 1778941087325,
|
||||
"size": 6610,
|
||||
"lmtime": 1779052603932,
|
||||
"modified": true
|
||||
},
|
||||
"SmtpSecurityContextFactory.php": {
|
||||
"type": "-",
|
||||
"size": 1159,
|
||||
"size": 1208,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"TemplateVariableCatalog.php": {
|
||||
"type": "-",
|
||||
"size": 3846,
|
||||
"size": 3976,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
"modified": true
|
||||
}
|
||||
},
|
||||
"Shipments": {
|
||||
@@ -4300,15 +4344,15 @@
|
||||
},
|
||||
"DeliveryStatus.php": {
|
||||
"type": "-",
|
||||
"size": 25566,
|
||||
"size": 25812,
|
||||
"lmtime": 1779040606945,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"DeliveryStatusRepository.php": {
|
||||
"type": "-",
|
||||
"size": 5416,
|
||||
"size": 5512,
|
||||
"lmtime": 1779039679909,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"InpostShipmentService.php": {
|
||||
"type": "-",
|
||||
@@ -4324,8 +4368,8 @@
|
||||
},
|
||||
"PolkurierShipmentService.php": {
|
||||
"type": "-",
|
||||
"size": 28548,
|
||||
"lmtime": 1778885937963,
|
||||
"size": 28976,
|
||||
"lmtime": 1779128071700,
|
||||
"modified": false
|
||||
},
|
||||
"PolkurierTrackingService.php": {
|
||||
@@ -4336,9 +4380,9 @@
|
||||
},
|
||||
"ShipmentController.php": {
|
||||
"type": "-",
|
||||
"size": 25600,
|
||||
"size": 25644,
|
||||
"lmtime": 1778938051366,
|
||||
"modified": false
|
||||
"modified": true
|
||||
},
|
||||
"ShipmentPackageRepository.php": {
|
||||
"type": "-",
|
||||
@@ -4392,8 +4436,8 @@
|
||||
},
|
||||
"UsersController.php": {
|
||||
"type": "-",
|
||||
"size": 5179,
|
||||
"lmtime": 1772491357326,
|
||||
"size": 5772,
|
||||
"lmtime": 1779052603935,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
@@ -6858,13 +6902,13 @@
|
||||
"email_photo_fetcher.cpython-312.pyc": {
|
||||
"type": "-",
|
||||
"size": 9439,
|
||||
"lmtime": 1778189100394,
|
||||
"lmtime": 1779128071704,
|
||||
"modified": false
|
||||
},
|
||||
"_pudelko_komunia_core.cpython-312.pyc": {
|
||||
"type": "-",
|
||||
"size": 5888,
|
||||
"lmtime": 1778885937966,
|
||||
"lmtime": 1779052069927,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -107,6 +107,12 @@ HTTP Request
|
||||
|
||||
## Frontend Enhancement Modules
|
||||
|
||||
### Polish UI Copy
|
||||
- `resources/lang/pl.php` is the primary Polish translation source loaded by `src/Core/I18n/Translator.php` and exposed to views as `$t()`.
|
||||
- Views and small browser modules may still contain legacy hardcoded UI copy, but visible operator-facing text is normalized to UTF-8 Polish diacritics.
|
||||
- Template placeholder keys remain ASCII technical contracts, e.g. `{{zamowienie.numer}}` and `{{przesylka.numer}}`; only their human labels/descriptions are localized.
|
||||
- This copy normalization does not change routes, form field names, status codes, API payload keys, database schema, or imported marketplace data.
|
||||
|
||||
### Orders List and Sidebar State
|
||||
- `/orders/list` renders the reusable order status panel and shared `components/table-list` directly; the previous descriptive intro card was removed so the operational table starts higher on the screen.
|
||||
- The order number cell shows the notes badge from `OrdersRepository::notesCountSubquerySql()`, counting every `order_notes` row for the order. Imported source notes (`note_type <> 'user'`) and operator notes (`note_type='user'`) both contribute to the same `[N]` badge, while order details still render those groups separately.
|
||||
|
||||
@@ -395,6 +395,17 @@
|
||||
- SMSPLANET wspiera dwa warianty autoryzacji, wiec konfiguracja przechowuje wszystkie sekrety w formie szyfrowanej i waliduje wymagania zalezne od wyboru operatora.
|
||||
- Test uzywa rzeczywistej wysylki, bo celem tej fazy jest potwierdzenie realnej sciezki API.
|
||||
|
||||
## 2026-05-18 - Polish UI Copy Normalization
|
||||
|
||||
**Co zrobiono:**
|
||||
- Znormalizowano polskie teksty interfejsu do UTF-8 z polskimi znakami w `resources/lang/pl.php`.
|
||||
- Poprawiono widoczne hardcoded komunikaty w wybranych widokach, modułach JS i komunikatach backendu.
|
||||
- Zachowano techniczne kontrakty placeholderów, tras, kluczy formularzy, kodów statusów i payloadów API.
|
||||
|
||||
**Dlaczego:**
|
||||
- Interfejs zawierał wiele tekstów bez polskich znaków, np. `Zamowienia`, `Przesylki`, `Platnosci`.
|
||||
- Zmiana jest warstwą copy/UI; nie dodaje migracji, tabel ani nowych konfiguracji środowiskowych.
|
||||
|
||||
## 2026-05-12 - Phase 116 Plan 01: HostedSMS Integration Settings + Test SMS
|
||||
|
||||
**Co zrobiono:**
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
html += '</select>';
|
||||
html += '<label class="checkbox-label">'
|
||||
+ '<input type="checkbox" name="' + namePrefix + '[send_once_per_order]" value="1"> '
|
||||
+ 'Wyslij tylko raz dla tego zamowienia'
|
||||
+ 'Wyślij tylko raz dla tego zamówienia'
|
||||
+ '</label>';
|
||||
|
||||
return html;
|
||||
@@ -145,7 +145,7 @@
|
||||
|
||||
function buildShipmentStatusActionConfig(namePrefix) {
|
||||
var html = '<select class="form-control" name="' + namePrefix + '[shipment_status_key]">'
|
||||
+ '<option value="">-- Wybierz docelowy status przesylki --</option>';
|
||||
+ '<option value="">-- Wybierz docelowy status przesyłki --</option>';
|
||||
|
||||
Object.keys(data.shipmentStatusOptions || {}).forEach(function(statusKey) {
|
||||
var config = data.shipmentStatusOptions[statusKey] || {};
|
||||
@@ -159,7 +159,7 @@
|
||||
|
||||
function buildOrderStatusActionConfig(namePrefix) {
|
||||
var html = '<select class="form-control" name="' + namePrefix + '[order_status_code]">'
|
||||
+ '<option value="">-- Wybierz docelowy status zamowienia --</option>';
|
||||
+ '<option value="">-- Wybierz docelowy status zamówienia --</option>';
|
||||
|
||||
(data.orderStatusOptions || []).forEach(function(statusOption) {
|
||||
html += '<option value="' + escapeHtml(statusOption.code || '') + '">'
|
||||
@@ -182,10 +182,10 @@
|
||||
row.innerHTML = '<div class="automation-row__fields">'
|
||||
+ '<select class="form-control automation-row__type" name="' + namePrefix + '[type]" onchange="window.AutomationForm.onConditionTypeChange(this)">'
|
||||
+ '<option value="integration" selected>Integracja (kanal sprzedazy)</option>'
|
||||
+ '<option value="shipment_status">Status przesylki</option>'
|
||||
+ '<option value="payment_status">Status platnosci</option>'
|
||||
+ '<option value="payment_method">Metoda platnosci</option>'
|
||||
+ '<option value="order_status">Status zamowienia</option>'
|
||||
+ '<option value="shipment_status">Status przesyłki</option>'
|
||||
+ '<option value="payment_status">Status płatności</option>'
|
||||
+ '<option value="payment_method">Metoda płatności</option>'
|
||||
+ '<option value="order_status">Status zamówienia</option>'
|
||||
+ '<option value="days_in_status">Liczba dni w statusie</option>'
|
||||
+ '</select>'
|
||||
+ '<div class="automation-row__config">'
|
||||
@@ -207,10 +207,10 @@
|
||||
|
||||
row.innerHTML = '<div class="automation-row__fields">'
|
||||
+ '<select class="form-control automation-row__type" name="' + namePrefix + '[type]" onchange="window.AutomationForm.onActionTypeChange(this)">'
|
||||
+ '<option value="send_email" selected>Wyslij e-mail</option>'
|
||||
+ '<option value="send_email" selected>Wyślij e-mail</option>'
|
||||
+ '<option value="issue_receipt">Wystaw paragon</option>'
|
||||
+ '<option value="update_shipment_status">Zmiana statusu przesylki</option>'
|
||||
+ '<option value="update_order_status">Zmiana statusu zamowienia</option>'
|
||||
+ '<option value="update_shipment_status">Zmiana statusu przesyłki</option>'
|
||||
+ '<option value="update_order_status">Zmiana statusu zamówienia</option>'
|
||||
+ '</select>'
|
||||
+ '<div class="automation-row__config">'
|
||||
+ buildEmailActionConfig(namePrefix)
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
var form = btn.closest('form');
|
||||
if (!form) return;
|
||||
|
||||
var title = form.getAttribute('data-confirm-title') || 'Usun pozycje';
|
||||
var message = form.getAttribute('data-confirm-message') || 'Czy na pewno chcesz usunac ten wpis?';
|
||||
var title = form.getAttribute('data-confirm-title') || 'Usuń pozycje';
|
||||
var message = form.getAttribute('data-confirm-message') || 'Czy na pewno chcesz usuńąć ten wpis?';
|
||||
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
var submitted = false;
|
||||
@@ -23,7 +23,7 @@
|
||||
title: title,
|
||||
message: message,
|
||||
danger: true,
|
||||
confirmLabel: 'Usun',
|
||||
confirmLabel: 'Usuń',
|
||||
onConfirm: doSubmit
|
||||
});
|
||||
if (result && typeof result.then === 'function') {
|
||||
|
||||
@@ -175,9 +175,9 @@
|
||||
if (!result.ok || !result.data.success) {
|
||||
wrap.innerHTML = prevHtml;
|
||||
wrap.setAttribute('data-current-status', prevStatus || '');
|
||||
var msg = (result.data && result.data.error) ? result.data.error : 'Nie udalo sie zmienic statusu';
|
||||
var msg = (result.data && result.data.error) ? result.data.error : 'Nie udało się zmienić statusu';
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.alert === 'function') {
|
||||
window.OrderProAlerts.alert({ title: 'Blad', message: msg });
|
||||
window.OrderProAlerts.alert({ title: 'Błąd', message: msg });
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -191,7 +191,7 @@
|
||||
wrap.innerHTML = prevHtml;
|
||||
wrap.setAttribute('data-current-status', prevStatus || '');
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.alert === 'function') {
|
||||
window.OrderProAlerts.alert({ title: 'Blad', message: 'Blad polaczenia z serwerem' });
|
||||
window.OrderProAlerts.alert({ title: 'Błąd', message: 'Błąd połączenia z serwerem' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
})
|
||||
.then(function (data) {
|
||||
if (!data || !data.success) {
|
||||
throw new Error(data && data.error ? data.error : 'Blad serwera');
|
||||
throw new Error(data && data.error ? data.error : 'Błąd serwera');
|
||||
}
|
||||
if (buttonWrap) {
|
||||
buttonWrap.style.display = newValue ? '' : 'none';
|
||||
@@ -43,7 +43,7 @@
|
||||
.catch(function (err) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.error === 'function') {
|
||||
window.OrderProAlerts.error('Nie udalo sie zmienic flagi faktury: ' + (err && err.message ? err.message : ''));
|
||||
window.OrderProAlerts.error('Nie udało się zmienić flagi faktury: ' + (err && err.message ? err.message : ''));
|
||||
} else {
|
||||
console.error('invoice-requested toggle failed', err);
|
||||
}
|
||||
|
||||
@@ -50,10 +50,10 @@
|
||||
event.preventDefault();
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Usunac notatke?',
|
||||
message: 'Tej operacji nie mozna cofnac.',
|
||||
title: 'Usunąć notatkę?',
|
||||
message: 'Tej operacji nie można cofnąć.',
|
||||
danger: true,
|
||||
confirmLabel: 'Usun',
|
||||
confirmLabel: 'Usuń',
|
||||
onConfirm: function () {
|
||||
form.dataset.bound = '2';
|
||||
form.submit();
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
applyBody(textarea, data.body);
|
||||
} else if (window.OrderProAlerts && window.OrderProAlerts.alert) {
|
||||
window.OrderProAlerts.alert({
|
||||
title: 'Nie udalo sie wczytac szablonu',
|
||||
message: (data && data.error) ? String(data.error) : 'Sprobuj ponownie.'
|
||||
title: 'Nie udało się wczytać szablonu',
|
||||
message: (data && data.error) ? String(data.error) : 'Spróbuj ponownie.'
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -66,8 +66,8 @@
|
||||
var triggered = false;
|
||||
var run = function () { if (!triggered) { triggered = true; doFetch(); } };
|
||||
var result = window.OrderProAlerts.confirm({
|
||||
title: 'Zamiana tresci',
|
||||
message: 'Tekst w polu wiadomosci zostanie nadpisany trescia szablonu. Kontynuowac?',
|
||||
title: 'Zamiana treśći',
|
||||
message: 'Tekst w polu wiadomości zostanie nadpisany treśćia szablonu. Kontynuowac?',
|
||||
confirmLabel: 'Wstaw szablon',
|
||||
danger: false,
|
||||
onConfirm: run,
|
||||
@@ -76,7 +76,7 @@
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(function (ok) { if (ok) run(); else picker.value = ''; });
|
||||
}
|
||||
} else if (window.confirm('Tekst w polu wiadomosci zostanie nadpisany. Kontynuowac?')) {
|
||||
} else if (window.confirm('Tekst w polu wiadomości zostanie nadpisany. Kontynuowac?')) {
|
||||
doFetch();
|
||||
} else {
|
||||
picker.value = '';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,10 +21,10 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
<section class="card">
|
||||
<div class="order-details-head">
|
||||
<div>
|
||||
<a href="/orders/<?= $e((string) $orderIdVal) ?>" class="order-back-link">← Powrot do zamowienia</a>
|
||||
<h2 class="section-title mt-12">Wystaw fakture</h2>
|
||||
<a href="/orders/<?= $e((string) $orderIdVal) ?>" class="order-back-link">← Powrót do zamówienia</a>
|
||||
<h2 class="section-title mt-12">Wystaw fakturę</h2>
|
||||
<div class="order-details-sub mt-4">
|
||||
Zamowienie <?= $e((string) ($orderRow['internal_order_number'] ?? ('#' . $orderIdVal))) ?>
|
||||
Zamówienie <?= $e((string) ($orderRow['internal_order_number'] ?? ('#' . $orderIdVal))) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,7 +37,7 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
<?php
|
||||
ob_start();
|
||||
?>
|
||||
<strong>Uwaga!</strong> Do tego zamowienia wystawiono juz <?= $e((string) count($existingInvoicesList)) ?> fakture/y:
|
||||
<strong>Uwaga!</strong> Do tego zamówienia wystawiono już <?= $e((string) count($existingInvoicesList)) ?> fakturę/y:
|
||||
<ul class="mt-4">
|
||||
<?php foreach ($existingInvoicesList as $ei): ?>
|
||||
<li>
|
||||
@@ -97,14 +97,14 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
</button>
|
||||
</div>
|
||||
<?php if ($autoNip !== ''): ?>
|
||||
<small class="muted">Auto-wykryty z payload zamowienia. Mozesz nadpisac lub kliknac "Pobierz z GUS".</small>
|
||||
<small class="muted">Auto-wykryty z payload zamówienia. Możesz nadpisać lub kliknąć "Pobierz z GUS".</small>
|
||||
<?php else: ?>
|
||||
<small class="muted">Wpisz NIP i kliknij "Pobierz z GUS" — dane firmy zostana wypelnione automatycznie.</small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="buyer_name">Imie i nazwisko</label>
|
||||
<label class="form-label" for="buyer_name">Imię i nazwisko</label>
|
||||
<input type="text" name="buyer_name" id="buyer_name" class="form-control"
|
||||
value="<?= $e($buyerNameDefault) ?>">
|
||||
</div>
|
||||
@@ -140,14 +140,14 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="section-title mt-16">Pozycje zamowienia</h3>
|
||||
<h3 class="section-title mt-16">Pozycje zamówienia</h3>
|
||||
<div class="table-wrap mt-8">
|
||||
<table class="table table--details">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Lp.</th>
|
||||
<th>Nazwa</th>
|
||||
<th>Ilosc</th>
|
||||
<th>Ilość</th>
|
||||
<th>Cena brutto</th>
|
||||
<th>VAT</th>
|
||||
<th>Suma brutto</th>
|
||||
@@ -182,7 +182,7 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $e((string) (count($itemsList) + 1)) ?></td>
|
||||
<td>Koszt wysylki</td>
|
||||
<td>Koszt wysyłki</td>
|
||||
<td>1</td>
|
||||
<td><?= $e(number_format($deliveryPrice, 2, '.', ' ')) ?></td>
|
||||
<td>23%</td>
|
||||
@@ -210,9 +210,9 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
|
||||
<div class="mt-16">
|
||||
<?php if ($hasExistingInvoices): ?>
|
||||
<button type="button" id="invoice-submit-btn" class="btn btn--primary">Wystaw fakture</button>
|
||||
<button type="button" id="invoice-submit-btn" class="btn btn--primary">Wystaw fakturę</button>
|
||||
<?php else: ?>
|
||||
<button type="submit" class="btn btn--primary">Wystaw fakture</button>
|
||||
<button type="submit" class="btn btn--primary">Wystaw fakturę</button>
|
||||
<?php endif; ?>
|
||||
<a href="/orders/<?= $e((string) $orderIdVal) ?>" class="btn btn--secondary ml-8">Anuluj</a>
|
||||
</div>
|
||||
@@ -249,7 +249,7 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
.then(function (resp) { return resp.json().then(function (j) { return { ok: resp.ok, data: j }; }); })
|
||||
.then(function (res) {
|
||||
if (!res.ok || !res.data || !res.data.success) {
|
||||
var msg = res.data && res.data.error ? res.data.error : 'Blad pobierania danych z GUS.';
|
||||
var msg = res.data && res.data.error ? res.data.error : 'Błąd pobierania danych z GUS.';
|
||||
throw new Error(msg);
|
||||
}
|
||||
var d = res.data.data || {};
|
||||
@@ -271,9 +271,9 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (window.OrderProAlerts && window.OrderProAlerts.error) {
|
||||
window.OrderProAlerts.error(err && err.message ? err.message : 'Blad GUS.');
|
||||
window.OrderProAlerts.error(err && err.message ? err.message : 'Błąd GUS.');
|
||||
} else {
|
||||
alert(err && err.message ? err.message : 'Blad GUS.');
|
||||
alert(err && err.message ? err.message : 'Błąd GUS.');
|
||||
}
|
||||
})
|
||||
.finally(function () {
|
||||
@@ -288,8 +288,8 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
<script>
|
||||
document.getElementById('invoice-submit-btn').addEventListener('click', function() {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Wystawic kolejna fakture?',
|
||||
message: 'Do tego zamowienia wystawiono juz fakture. Czy na pewno chcesz wystawic kolejna?',
|
||||
title: 'Wystawic kolejną fakturę?',
|
||||
message: 'Do tego zamówienia wystawiono już fakturę. Czy na pewno chcesz wystawić kolejną?',
|
||||
confirmLabel: 'Wystaw',
|
||||
danger: false,
|
||||
onConfirm: function() {
|
||||
|
||||
@@ -100,7 +100,7 @@ $totalVat = max(0.0, $totalGross - $totalNet);
|
||||
<tr>
|
||||
<th>Lp.</th>
|
||||
<th>Nazwa</th>
|
||||
<th class="text-right">Ilosc</th>
|
||||
<th class="text-right">Ilość</th>
|
||||
<th class="text-right">Cena netto</th>
|
||||
<th class="text-right">VAT</th>
|
||||
<th class="text-right">Cena brutto</th>
|
||||
@@ -138,7 +138,7 @@ $totalVat = max(0.0, $totalGross - $totalNet);
|
||||
|
||||
<?php if (($invoiceData['payment_due_date'] ?? null) !== null): ?>
|
||||
<div class="invoice-payment">
|
||||
<strong>Termin platnosci:</strong> <?= $e(substr((string) $invoiceData['payment_due_date'], 0, 10)) ?>
|
||||
<strong>Termin płatności:</strong> <?= $e(substr((string) $invoiceData['payment_due_date'], 0, 10)) ?>
|
||||
<?php if (trim((string) ($sellerData['bank_account'] ?? '')) !== ''): ?>
|
||||
| <strong>Konto:</strong> <?= $e((string) $sellerData['bank_account']) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -16,12 +16,12 @@ $externalPdfVal = trim((string) ($invoiceData['external_pdf_url'] ?? ''));
|
||||
<section class="card">
|
||||
<div class="order-details-head">
|
||||
<div>
|
||||
<a href="/orders/<?= $e((string) $orderIdVal) ?>" class="order-back-link">← Powrot do zamowienia</a>
|
||||
<a href="/orders/<?= $e((string) $orderIdVal) ?>" class="order-back-link">← Powrót do zamówienia</a>
|
||||
<h2 class="section-title mt-12">Faktura <?= $e($invoiceNumber) ?></h2>
|
||||
<div class="order-details-sub mt-4">
|
||||
<?php if ($isDelegatedFlag): ?>
|
||||
<span class="badge badge--success">Wystawione w Fakturowni<?php if (trim((string) ($accountPrefix ?? '')) !== ''): ?>: <?= $e((string) $accountPrefix) ?><?php endif; ?></span>
|
||||
<?php if ($externalIdVal !== ''): ?> <span class="muted">(id zewnetrzne: <?= $e($externalIdVal) ?>)</span><?php endif; ?>
|
||||
<?php if ($externalIdVal !== ''): ?> <span class="muted">(id zewnętrzne: <?= $e($externalIdVal) ?>)</span><?php endif; ?>
|
||||
<?php else: ?>
|
||||
<span class="badge badge--muted">Wystawione lokalnie</span>
|
||||
<?php endif; ?>
|
||||
@@ -33,7 +33,7 @@ $externalPdfVal = trim((string) ($invoiceData['external_pdf_url'] ?? ''));
|
||||
<?php else: ?>
|
||||
<a href="/orders/<?= $e((string) $orderIdVal) ?>/invoice/<?= $e((string) ($invoiceData['id'] ?? '')) ?>/pdf" class="btn btn--primary">Pobierz PDF</a>
|
||||
<?php endif; ?>
|
||||
<a href="/orders/<?= $e((string) $orderIdVal) ?>" class="btn btn--secondary">Powrot</a>
|
||||
<a href="/orders/<?= $e((string) $orderIdVal) ?>" class="btn btn--secondary">Powrót</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -80,7 +80,7 @@ $externalPdfVal = trim((string) ($invoiceData['external_pdf_url'] ?? ''));
|
||||
<tr>
|
||||
<th>Lp.</th>
|
||||
<th>Nazwa</th>
|
||||
<th>Ilosc</th>
|
||||
<th>Ilość</th>
|
||||
<th>Cena netto</th>
|
||||
<th>VAT</th>
|
||||
<th>Cena brutto</th>
|
||||
@@ -119,7 +119,7 @@ $externalPdfVal = trim((string) ($invoiceData['external_pdf_url'] ?? ''));
|
||||
<dt>Data wystawienia</dt><dd><?= $e(strlen($issueDateShow) >= 16 ? substr($issueDateShow, 0, 16) : $issueDateShow) ?></dd>
|
||||
<dt>Data sprzedazy</dt><dd><?= $e((string) ($invoiceData['sale_date'] ?? '-')) ?></dd>
|
||||
<?php if (($invoiceData['payment_due_date'] ?? null) !== null): ?>
|
||||
<dt>Termin platnosci</dt><dd><?= $e(substr((string) $invoiceData['payment_due_date'], 0, 10)) ?></dd>
|
||||
<dt>Termin płatności</dt><dd><?= $e(substr((string) $invoiceData['payment_due_date'], 0, 10)) ?></dd>
|
||||
<?php endif; ?>
|
||||
<dt>Konfiguracja</dt><dd><?= $e($configNameVal !== '' ? $configNameVal : '-') ?></dd>
|
||||
<dt>Typ</dt><dd><?= $e((string) ($invoiceData['kind'] ?? 'vat')) ?></dd>
|
||||
|
||||
@@ -11,16 +11,16 @@ $perPageNum = (int) ($perPage ?? 50);
|
||||
<section class="card">
|
||||
<div class="orders-head">
|
||||
<div>
|
||||
<a href="/settings/accounting" class="order-back-link">← Ksiegowosc</a>
|
||||
<a href="/settings/accounting" class="order-back-link">← Księgowość</a>
|
||||
<h2 class="section-title mt-12">Wystawione faktury</h2>
|
||||
<div class="muted mt-4">Lacznie: <?= $e((string) $totalCount) ?></div>
|
||||
<div class="muted mt-4">Łącznie: <?= $e((string) $totalCount) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" action="/settings/accounting/invoices/issued" class="mt-12">
|
||||
<div class="form-grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="search">Szukaj (numer, zamowienie)</label>
|
||||
<label class="form-label" for="search">Szukaj (numer, zamówienie)</label>
|
||||
<input type="text" name="search" id="search" class="form-control" value="<?= $e((string) ($filtersData['search'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -68,7 +68,7 @@ $perPageNum = (int) ($perPage ?? 50);
|
||||
<th>Brutto</th>
|
||||
<th>Tryb</th>
|
||||
<th>Konfiguracja</th>
|
||||
<th>Zamowienie</th>
|
||||
<th>Zamówienie</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -8,12 +8,12 @@ $actions = $rule !== null && is_array($rule['actions'] ?? null) ? $rule['actions
|
||||
|
||||
$eventLabels = [
|
||||
'receipt.created' => 'Utworzono paragon',
|
||||
'shipment.created' => 'Utworzenie przesylki',
|
||||
'shipment.status_changed' => 'Zmiana statusu przesylki',
|
||||
'payment.status_changed' => 'Zmiana statusu platnosci',
|
||||
'order.status_changed' => 'Zmiana statusu zamowienia',
|
||||
'shipment.created' => 'Utworzenie przesyłki',
|
||||
'shipment.status_changed' => 'Zmiana statusu przesyłki',
|
||||
'payment.status_changed' => 'Zmiana statusu płatności',
|
||||
'order.status_changed' => 'Zmiana statusu zamówienia',
|
||||
'order.status_aged' => 'Minelo X dni od zmiany statusu',
|
||||
'order.imported' => 'Pobranie zamowienia',
|
||||
'order.imported' => 'Pobranie zamówienia',
|
||||
];
|
||||
|
||||
$recipientLabels = [
|
||||
@@ -26,11 +26,11 @@ $receiptIssueDateModes = is_array($receiptIssueDateModes ?? null) ? $receiptIssu
|
||||
$receiptDuplicatePolicies = is_array($receiptDuplicatePolicies ?? null) ? $receiptDuplicatePolicies : [];
|
||||
$receiptIssueDateModeLabels = [
|
||||
'today' => 'Data dzisiejsza',
|
||||
'order_date' => 'Data zamowienia',
|
||||
'payment_date' => 'Data platnosci (fallback: dzisiaj)',
|
||||
'order_date' => 'Data zamówienia',
|
||||
'payment_date' => 'Data płatności (fallback: dzisiaj)',
|
||||
];
|
||||
$receiptDuplicatePolicyLabels = [
|
||||
'skip_if_exists' => 'Pomin jesli paragon juz istnieje',
|
||||
'skip_if_exists' => 'Pomin jesli paragon już istnieje',
|
||||
'allow_duplicates' => 'Wystawiaj kolejne paragony',
|
||||
];
|
||||
$shipmentStatusOptions = is_array($shipmentStatusOptions ?? null) ? $shipmentStatusOptions : [];
|
||||
@@ -53,7 +53,7 @@ $orderStatusOptions = is_array($orderStatusOptions ?? null) ? $orderStatusOption
|
||||
<div class="form-grid-2">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Nazwa zadania *</span>
|
||||
<input class="form-control" type="text" name="name" maxlength="128" required value="<?= $e((string) ($rule['name'] ?? '')) ?>" placeholder="np. Paragon Allegro - wyslij e-mail">
|
||||
<input class="form-control" type="text" name="name" maxlength="128" required value="<?= $e((string) ($rule['name'] ?? '')) ?>" placeholder="np. Paragon Allegro - wyślij e-mail">
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label">Zdarzenie *</span>
|
||||
@@ -82,10 +82,10 @@ $orderStatusOptions = is_array($orderStatusOptions ?? null) ? $orderStatusOption
|
||||
<div class="automation-row__fields">
|
||||
<select class="form-control automation-row__type" name="conditions[<?= $idx ?>][type]" onchange="window.AutomationForm.onConditionTypeChange(this)">
|
||||
<option value="integration"<?= ((string) ($cond['condition_type'] ?? '')) === 'integration' ? ' selected' : '' ?>>Integracja (kanal sprzedazy)</option>
|
||||
<option value="shipment_status"<?= ((string) ($cond['condition_type'] ?? '')) === 'shipment_status' ? ' selected' : '' ?>>Status przesylki</option>
|
||||
<option value="payment_status"<?= ((string) ($cond['condition_type'] ?? '')) === 'payment_status' ? ' selected' : '' ?>>Status platnosci</option>
|
||||
<option value="payment_method"<?= ((string) ($cond['condition_type'] ?? '')) === 'payment_method' ? ' selected' : '' ?>>Metoda platnosci</option>
|
||||
<option value="order_status"<?= ((string) ($cond['condition_type'] ?? '')) === 'order_status' ? ' selected' : '' ?>>Status zamowienia</option>
|
||||
<option value="shipment_status"<?= ((string) ($cond['condition_type'] ?? '')) === 'shipment_status' ? ' selected' : '' ?>>Status przesyłki</option>
|
||||
<option value="payment_status"<?= ((string) ($cond['condition_type'] ?? '')) === 'payment_status' ? ' selected' : '' ?>>Status płatności</option>
|
||||
<option value="payment_method"<?= ((string) ($cond['condition_type'] ?? '')) === 'payment_method' ? ' selected' : '' ?>>Metoda płatności</option>
|
||||
<option value="order_status"<?= ((string) ($cond['condition_type'] ?? '')) === 'order_status' ? ' selected' : '' ?>>Status zamówienia</option>
|
||||
<option value="days_in_status"<?= ((string) ($cond['condition_type'] ?? '')) === 'days_in_status' ? ' selected' : '' ?>>Liczba dni w statusie</option>
|
||||
</select>
|
||||
<div class="automation-row__config">
|
||||
@@ -165,10 +165,10 @@ $orderStatusOptions = is_array($orderStatusOptions ?? null) ? $orderStatusOption
|
||||
<div class="automation-row mt-8" data-index="<?= $idx ?>">
|
||||
<div class="automation-row__fields">
|
||||
<select class="form-control automation-row__type" name="actions[<?= $idx ?>][type]" onchange="window.AutomationForm.onActionTypeChange(this)">
|
||||
<option value="send_email"<?= ((string) ($act['action_type'] ?? '')) === 'send_email' ? ' selected' : '' ?>>Wyslij e-mail</option>
|
||||
<option value="send_email"<?= ((string) ($act['action_type'] ?? '')) === 'send_email' ? ' selected' : '' ?>>Wyślij e-mail</option>
|
||||
<option value="issue_receipt"<?= ((string) ($act['action_type'] ?? '')) === 'issue_receipt' ? ' selected' : '' ?>>Wystaw paragon</option>
|
||||
<option value="update_shipment_status"<?= ((string) ($act['action_type'] ?? '')) === 'update_shipment_status' ? ' selected' : '' ?>>Zmiana statusu przesylki</option>
|
||||
<option value="update_order_status"<?= ((string) ($act['action_type'] ?? '')) === 'update_order_status' ? ' selected' : '' ?>>Zmiana statusu zamowienia</option>
|
||||
<option value="update_shipment_status"<?= ((string) ($act['action_type'] ?? '')) === 'update_shipment_status' ? ' selected' : '' ?>>Zmiana statusu przesyłki</option>
|
||||
<option value="update_order_status"<?= ((string) ($act['action_type'] ?? '')) === 'update_order_status' ? ' selected' : '' ?>>Zmiana statusu zamówienia</option>
|
||||
</select>
|
||||
<div class="automation-row__config">
|
||||
<?php
|
||||
@@ -202,7 +202,7 @@ $orderStatusOptions = is_array($orderStatusOptions ?? null) ? $orderStatusOption
|
||||
</select>
|
||||
<?php elseif ($actionType === 'update_shipment_status'): ?>
|
||||
<select class="form-control" name="actions[<?= $idx ?>][shipment_status_key]">
|
||||
<option value="">-- Wybierz docelowy status przesylki --</option>
|
||||
<option value="">-- Wybierz docelowy status przesyłki --</option>
|
||||
<?php foreach ($shipmentStatusOptions as $statusKey => $statusConfig): ?>
|
||||
<?php $statusLabel = (string) ($statusConfig['label'] ?? $statusKey); ?>
|
||||
<option value="<?= $e((string) $statusKey) ?>"<?= ((string) ($actConfig['status_key'] ?? '')) === (string) $statusKey ? ' selected' : '' ?>>
|
||||
@@ -212,7 +212,7 @@ $orderStatusOptions = is_array($orderStatusOptions ?? null) ? $orderStatusOption
|
||||
</select>
|
||||
<?php elseif ($actionType === 'update_order_status'): ?>
|
||||
<select class="form-control" name="actions[<?= $idx ?>][order_status_code]">
|
||||
<option value="">-- Wybierz docelowy status zamowienia --</option>
|
||||
<option value="">-- Wybierz docelowy status zamówienia --</option>
|
||||
<?php foreach ($orderStatusOptions as $statusOption): ?>
|
||||
<?php
|
||||
$statusCode = (string) ($statusOption['code'] ?? '');
|
||||
@@ -237,7 +237,7 @@ $orderStatusOptions = is_array($orderStatusOptions ?? null) ? $orderStatusOption
|
||||
</select>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="actions[<?= $idx ?>][send_once_per_order]" value="1"<?= ((int) ($actConfig['send_once_per_order'] ?? 0)) === 1 ? ' checked' : '' ?>>
|
||||
Wyslij tylko raz dla tego zamowienia
|
||||
Wyślij tylko raz dla tego zamówienia
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -13,17 +13,17 @@ $historyTotal = max(0, (int) ($historyPagination['total'] ?? 0));
|
||||
|
||||
$eventLabels = [
|
||||
'receipt.created' => 'Utworzono paragon',
|
||||
'shipment.created' => 'Utworzenie przesylki',
|
||||
'shipment.status_changed' => 'Zmiana statusu przesylki',
|
||||
'payment.status_changed' => 'Zmiana statusu platnosci',
|
||||
'order.status_changed' => 'Zmiana statusu zamowienia',
|
||||
'shipment.created' => 'Utworzenie przesyłki',
|
||||
'shipment.status_changed' => 'Zmiana statusu przesyłki',
|
||||
'payment.status_changed' => 'Zmiana statusu płatności',
|
||||
'order.status_changed' => 'Zmiana statusu zamówienia',
|
||||
'order.status_aged' => 'Minelo X dni od zmiany statusu',
|
||||
'order.imported' => 'Pobranie zamowienia',
|
||||
'order.imported' => 'Pobranie zamówienia',
|
||||
];
|
||||
|
||||
$statusLabels = [
|
||||
'success' => 'Sukces',
|
||||
'failed' => 'Blad',
|
||||
'failed' => 'Błąd',
|
||||
];
|
||||
|
||||
$historyFiltersDefault = [
|
||||
@@ -114,7 +114,7 @@ $buildHistoryUrl = static function (array $overrides = []) use ($historyFiltersD
|
||||
<form action="/settings/automation/delete" method="post" class="automation-inline-form js-confirm-delete">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= (int) ($rule['id'] ?? 0) ?>">
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usun</button>
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usuń</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -168,7 +168,7 @@ $buildHistoryUrl = static function (array $overrides = []) use ($historyFiltersD
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">ID zamowienia</span>
|
||||
<span class="field-label">ID zamówienia</span>
|
||||
<input class="form-control" type="number" min="1" step="1" name="history_order_id" value="<?= (int) ($historyFilters['order_id'] ?? 0) > 0 ? $e((string) (int) ($historyFilters['order_id'] ?? 0)) : '' ?>">
|
||||
</label>
|
||||
|
||||
@@ -195,7 +195,7 @@ $buildHistoryUrl = static function (array $overrides = []) use ($historyFiltersD
|
||||
<th>Kiedy</th>
|
||||
<th>Zdarzenie</th>
|
||||
<th>Regula</th>
|
||||
<th>Zamowienie</th>
|
||||
<th>Zamówienie</th>
|
||||
<th>Status</th>
|
||||
<th>Wynik</th>
|
||||
</tr>
|
||||
@@ -226,7 +226,7 @@ $buildHistoryUrl = static function (array $overrides = []) use ($historyFiltersD
|
||||
<?php if ($entryStatus === 'success'): ?>
|
||||
<span class="badge badge--success">Sukces</span>
|
||||
<?php elseif ($entryStatus === 'failed'): ?>
|
||||
<span class="badge badge--danger">Blad</span>
|
||||
<span class="badge badge--danger">Błąd</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge--muted"><?= $e($entryStatus) ?></span>
|
||||
<?php endif; ?>
|
||||
@@ -290,7 +290,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
try {
|
||||
window.localStorage.setItem(tabStorageKey, target);
|
||||
} catch (error) {
|
||||
// Ignorujemy brak dostepu do localStorage.
|
||||
// Ignorujemy brak dostępu do localStorage.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,7 +317,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
activateTab(savedTab, false);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignorujemy brak dostepu do localStorage.
|
||||
// Ignorujemy brak dostępu do localStorage.
|
||||
}
|
||||
} else if (explicitTab === 'history') {
|
||||
activateTab('automation-tab-history', true);
|
||||
@@ -338,7 +338,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
window.OrderProAlerts.confirm(
|
||||
'Usuwanie zadania',
|
||||
'Czy na pewno chcesz usunac to zadanie automatyczne?',
|
||||
'Czy na pewno chcesz usuńąć to zadanie automatyczne?',
|
||||
function() { form.submit(); }
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -114,7 +114,7 @@ $buildUrl = static function (array $params = []) use ($basePath, $query): string
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
<div class="table-col-toggle-footer">
|
||||
<button type="button" class="btn btn--secondary js-col-toggle-reset">Pokaz wszystkie</button>
|
||||
<button type="button" class="btn btn--secondary js-col-toggle-reset">Pokaż wszystkie</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -316,7 +316,7 @@ $buildUrl = static function (array $params = []) use ($basePath, $query): string
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<input type="hidden" name="page" value="1">
|
||||
<span>Wyswietlaj</span>
|
||||
<span>Wyświetlaj</span>
|
||||
<select class="form-control js-per-page-select" name="per_page">
|
||||
<?php foreach ($perPageOptions as $opt): ?>
|
||||
<option value="<?= $e((string) $opt) ?>"<?= (int) $opt === $perPage ? ' selected' : '' ?>><?= $e((string) $opt) ?></option>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
body.innerHTML = html;
|
||||
})
|
||||
.catch(function () {
|
||||
body.innerHTML = '<div class="order-preview-loading">Nie udalo sie zaladowac podgladu.</div>';
|
||||
body.innerHTML = '<div class="order-preview-loading">Nie udało się załadować podglądu.</div>';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ $emailEnabled = $emailTemplatesList !== [] && $emailMailboxesList !== [] && $buy
|
||||
<div class="email-send-overlay" id="emailSendOverlay" style="display:none">
|
||||
<div class="email-send-modal">
|
||||
<div class="email-send-modal__header">
|
||||
<h3>Wyslij e-mail</h3>
|
||||
<h3>Wyślij e-mail</h3>
|
||||
<button type="button" class="email-send-modal__close" id="emailSendClose">×</button>
|
||||
</div>
|
||||
<div class="email-send-modal__body">
|
||||
@@ -64,7 +64,7 @@ $emailEnabled = $emailTemplatesList !== [] && $emailMailboxesList !== [] && $buy
|
||||
</div>
|
||||
<div class="email-send-modal__footer">
|
||||
<button type="button" class="btn btn--secondary" id="emailSendCancel">Anuluj</button>
|
||||
<button type="button" class="btn btn--primary" id="emailSendBtn" disabled>Wyslij</button>
|
||||
<button type="button" class="btn btn--primary" id="emailSendBtn" disabled>Wyślij</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,14 +128,14 @@ $emailEnabled = $emailTemplatesList !== [] && $emailMailboxesList !== [] && $buy
|
||||
previewBody.innerHTML = data.body_html || '';
|
||||
if (data.attachments && data.attachments.length > 0) {
|
||||
previewAttachments.style.display = 'block';
|
||||
previewAttachments.textContent = 'Zalaczniki: ' + data.attachments.join(', ');
|
||||
previewAttachments.textContent = 'Załączniki: ' + data.attachments.join(', ');
|
||||
} else {
|
||||
previewAttachments.style.display = 'none';
|
||||
}
|
||||
previewArea.style.display = 'block';
|
||||
})
|
||||
.catch(function () {
|
||||
previewBody.textContent = 'Blad ladowania podgladu';
|
||||
previewBody.textContent = 'Błąd ładowania podglądu';
|
||||
previewArea.style.display = 'block';
|
||||
})
|
||||
.finally(function () {
|
||||
@@ -149,7 +149,7 @@ $emailEnabled = $emailTemplatesList !== [] && $emailMailboxesList !== [] && $buy
|
||||
if (!tplId) return;
|
||||
|
||||
sendBtn.disabled = true;
|
||||
sendBtn.textContent = 'Wysylanie...';
|
||||
sendBtn.textContent = 'Wysyłanie...';
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('_token', csrfToken);
|
||||
@@ -163,23 +163,23 @@ $emailEnabled = $emailTemplatesList !== [] && $emailMailboxesList !== [] && $buy
|
||||
if (data.success) {
|
||||
closeModal();
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.success(data.message || 'E-mail wyslany');
|
||||
window.OrderProAlerts.success(data.message || 'E-mail wysłany');
|
||||
}
|
||||
setTimeout(function () { location.reload(); }, 1500);
|
||||
} else {
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.error(data.message || 'Blad wysylki');
|
||||
window.OrderProAlerts.error(data.message || 'Błąd wysyłki');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.error('Blad polaczenia z serwerem');
|
||||
window.OrderProAlerts.error('Błąd połączenia z serwerem');
|
||||
}
|
||||
})
|
||||
.finally(function () {
|
||||
sendBtn.disabled = false;
|
||||
sendBtn.textContent = 'Wyslij';
|
||||
sendBtn.textContent = 'Wyślij';
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -114,7 +114,7 @@ $copyBtn = static function (string $value) use ($e, $copyIcon, $checkIcon): stri
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Produkt</th>
|
||||
<th>Ilosc</th>
|
||||
<th>Ilość</th>
|
||||
<th>Cena</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -182,7 +182,7 @@ $copyBtn = static function (string $value) use ($e, $copyIcon, $checkIcon): stri
|
||||
$paymentType = strtoupper(trim((string) ($orderRow['external_payment_type_id'] ?? '')));
|
||||
if ($paymentType !== ''):
|
||||
?>
|
||||
<dt>Platnosc:</dt>
|
||||
<dt>Płatność:</dt>
|
||||
<dd><?= $e($paymentType) ?></dd>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -103,7 +103,7 @@ $hasExistingReceipts = $existingReceiptsList !== [];
|
||||
<?php if ($deliveryPriceVal > 0): ?>
|
||||
<tr>
|
||||
<td><?= $e((string) (count($itemsList) + 1)) ?></td>
|
||||
<td>Koszt wysylki</td>
|
||||
<td>Koszt wysyłki</td>
|
||||
<td><div>-</div><div class="muted">-</div></td>
|
||||
<td>1</td>
|
||||
<td><?= $e(number_format($deliveryPriceVal, 2, '.', ' ')) ?></td>
|
||||
|
||||
@@ -73,15 +73,15 @@ foreach ($addressesList as $address) {
|
||||
</div>
|
||||
<div class="order-details-actions">
|
||||
<button type="button" class="btn btn--secondary btn--disabled">Strefa klienta</button>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/prepare" class="btn btn--primary">Przygotuj przesylke</a>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/prepare" class="btn btn--primary">Przygotuj przesyłkę</a>
|
||||
<?php if ($receiptConfigsList !== []): ?>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/receipt/create" class="btn btn--secondary">Wystaw paragon</a>
|
||||
<?php endif; ?>
|
||||
<span data-invoice-button-wrap style="<?= $invoiceRequestedFlag ? '' : 'display:none;' ?>">
|
||||
<?php if ($invoiceConfigsList !== []): ?>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/invoice/create" class="btn btn--secondary">Wystaw fakture</a>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/invoice/create" class="btn btn--secondary">Wystaw fakturę</a>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn--secondary btn--disabled" title="Brak aktywnych konfiguracji faktur">Wystaw fakture</button>
|
||||
<button type="button" class="btn btn--secondary btn--disabled" title="Brak aktywnych konfiguracji faktur">Wystaw fakturę</button>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php
|
||||
@@ -92,11 +92,11 @@ foreach ($addressesList as $address) {
|
||||
$emailBtnEnabled = $emailTemplatesList !== [] && $emailMailboxesList !== [] && $emailBuyerAddr !== '';
|
||||
?>
|
||||
<?php if ($emailBtnEnabled): ?>
|
||||
<button type="button" class="btn btn--secondary" id="btn-send-email">Wyslij e-mail</button>
|
||||
<button type="button" class="btn btn--secondary" id="btn-send-email">Wyślij e-mail</button>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn--secondary btn--disabled" title="Skonfiguruj skrzynke i szablony w Ustawieniach">Wyslij e-mail</button>
|
||||
<button type="button" class="btn btn--secondary btn--disabled" title="Skonfiguruj skrzynke i szablony w Ustawieniach">Wyślij e-mail</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn--secondary" id="btn-header-payment">Platnosc</button>
|
||||
<button type="button" class="btn btn--secondary" id="btn-header-payment">Płatność</button>
|
||||
<button type="button" class="btn btn--secondary btn--disabled">Drukuj</button>
|
||||
<button type="button" class="btn btn--primary btn--disabled">Pakuj</button>
|
||||
<button type="button" class="btn btn--secondary btn--disabled">Edytuj</button>
|
||||
@@ -111,13 +111,13 @@ foreach ($addressesList as $address) {
|
||||
<?php $riskOrders = is_array($riskInfo['orders'] ?? null) ? $riskInfo['orders'] : []; ?>
|
||||
<?php if ($riskOrders !== []): ?>
|
||||
<details class="customer-risk-banner__list">
|
||||
<summary>Pokaz liste zamowien ze zwrotem (<?= count($riskOrders) ?>)</summary>
|
||||
<summary>Pokaż listę zamówień ze zwrotem (<?= count($riskOrders) ?>)</summary>
|
||||
<table class="customer-risk-banner__table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nr zamowienia</th>
|
||||
<th>Nr zamówienia</th>
|
||||
<th>Data</th>
|
||||
<th>Nr przesylki</th>
|
||||
<th>Nr przesyłki</th>
|
||||
<th>Przewoznik</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
@@ -276,7 +276,7 @@ foreach ($addressesList as $address) {
|
||||
<h3 class="section-title"><?= $e($t('orders.details.order_info')) ?></h3>
|
||||
<dl class="order-kv mt-12">
|
||||
<dt><?= $e($t('orders.details.fields.status')) ?></dt><dd><?= $e((string) ($statusLabel ?? '-')) ?></dd>
|
||||
<dt>Nr zamowienia</dt><dd><strong><?= $e((string) ($orderRow['internal_order_number'] ?? '-')) ?></strong></dd>
|
||||
<dt>Nr zamówienia</dt><dd><strong><?= $e((string) ($orderRow['internal_order_number'] ?? '-')) ?></strong></dd>
|
||||
<dt><?= $e($t('orders.details.fields.external_order_id')) ?></dt><dd><?= $e((string) ($orderRow['external_order_id'] ?? '-')) ?></dd>
|
||||
<dt><?= $e($t('orders.details.fields.ordered_at')) ?></dt><dd><?= $e((string) ($orderRow['ordered_at'] ?? '-')) ?></dd>
|
||||
<dt><?= $e($t('orders.details.fields.customer_login')) ?></dt><dd><?= $e((string) ($orderRow['customer_login'] ?? '-')) ?></dd>
|
||||
@@ -351,7 +351,7 @@ foreach ($addressesList as $address) {
|
||||
<dt><?= $e($t('orders.details.fields.payment_status')) ?></dt><dd><?= $e((string) ($orderRow['payment_status'] ?? '-')) ?></dd>
|
||||
<dt>Forma dostawy</dt><dd><?= $e($deliveryMethodValue !== '' ? $deliveryMethodValue : '-') ?></dd>
|
||||
<dt>Forma płatności</dt><dd><?= $e($paymentMethodValue !== '' ? $paymentMethodValue : '-') ?></dd>
|
||||
<dt>Typ platnosci</dt>
|
||||
<dt>Typ płatności</dt>
|
||||
<dd>
|
||||
<?php
|
||||
$paymentTypeRaw = strtoupper(trim((string) ($orderRow['external_payment_type_id'] ?? '')));
|
||||
@@ -361,7 +361,7 @@ foreach ($addressesList as $address) {
|
||||
'POBRANIE' => 'Za pobraniem',
|
||||
'ZA POBRANIEM' => 'Za pobraniem',
|
||||
'PŁATNOŚĆ PRZY ODBIORZE' => 'Za pobraniem',
|
||||
'ONLINE' => 'Platnosc online',
|
||||
'ONLINE' => 'Płatność online',
|
||||
'TRANSFER' => 'Przelew',
|
||||
];
|
||||
$paymentTypeLabel = $paymentTypeLabels[$paymentTypeRaw] ?? ($paymentTypeRaw !== '' ? $paymentTypeRaw : '-');
|
||||
@@ -618,7 +618,7 @@ foreach ($addressesList as $address) {
|
||||
<div class="order-tab-panel" data-order-tab-panel="shipments">
|
||||
<?php if ($packagesList !== []): ?>
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title">Wygenerowane przesylki</h3>
|
||||
<h3 class="section-title">Wygenerowane przesyłki</h3>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table table--details">
|
||||
<thead>
|
||||
@@ -656,7 +656,7 @@ foreach ($addressesList as $address) {
|
||||
<td><?= $e((string) ($pkg['id'] ?? '')) ?></td>
|
||||
<td>
|
||||
<?php if ($isManual): ?>
|
||||
<span class="order-tag is-neutral">Dodana recznie</span>
|
||||
<span class="order-tag is-neutral">Dodana ręcznie</span>
|
||||
<?php else: ?>
|
||||
<span class="order-tag <?= $pkgStatus === 'label_ready' || $pkgStatus === 'created' ? 'is-success' : ($pkgStatus === 'error' ? 'is-danger' : 'is-warn') ?>"
|
||||
data-pkg-status-tag="<?= $e((string) ($pkg['id'] ?? 0)) ?>">
|
||||
@@ -667,7 +667,7 @@ foreach ($addressesList as $address) {
|
||||
data-check-pkg-status="<?= $e((string) ($pkg['id'] ?? 0)) ?>"
|
||||
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
|
||||
data-auto-poll="1"
|
||||
style="font-size:0.7rem">Sprawdz status</button>
|
||||
style="font-size:0.7rem">Sprawdź status</button>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php if ($pkgError !== ''): ?>
|
||||
@@ -691,7 +691,7 @@ foreach ($addressesList as $address) {
|
||||
<td style="white-space:nowrap" data-pkg-tracking-cell="<?= $e((string) ($pkg['id'] ?? 0)) ?>">
|
||||
<?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?><?php
|
||||
$pkgTrackUrl = DeliveryStatus::trackingUrl($pkgProvider, $pkgTracking, $pkgCarrierId);
|
||||
if ($pkgTrackUrl !== null): ?> <a href="<?= $e($pkgTrackUrl) ?>" target="_blank" class="tracking-link" title="Sledz przesylke">🔗</a><?php endif; ?>
|
||||
if ($pkgTrackUrl !== null): ?> <a href="<?= $e($pkgTrackUrl) ?>" target="_blank" class="tracking-link" title="Sledz przesyłkę">🔗</a><?php endif; ?>
|
||||
</td>
|
||||
<td><?php if ($isManual): ?><?= $e($pkgCarrierId !== '' ? $pkgCarrierId : 'Reczna') ?><?php elseif ($pkgCarrierId !== ''): ?><?= $e($pkgProviderLabel) ?> → <?= $e($pkgCarrierId) ?><?php elseif ($pkgProviderLabel !== ''): ?><?= $e($pkgProviderLabel) ?><?php else: ?>-<?php endif; ?></td>
|
||||
<td data-pkg-label-cell="<?= $e((string) ($pkg['id'] ?? 0)) ?>">
|
||||
@@ -718,7 +718,7 @@ foreach ($addressesList as $address) {
|
||||
class="btn btn--sm btn--secondary btn-print-label"
|
||||
data-package-id="<?= $e((string) ($pkg['id'] ?? 0)) ?>"
|
||||
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
|
||||
title="Wyslij do drukarki">Drukuj</button>
|
||||
title="Wyślij do drukarki">Drukuj</button>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
@@ -728,7 +728,7 @@ foreach ($addressesList as $address) {
|
||||
<td>
|
||||
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/<?= $e((string) ($pkg['id'] ?? 0)) ?>/delete" class="form-delete-package" style="display:inline">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<button type="submit" class="btn btn--sm btn--danger btn-delete-package">Usun</button>
|
||||
<button type="submit" class="btn btn--sm btn--danger btn-delete-package">Usuń</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -741,7 +741,7 @@ foreach ($addressesList as $address) {
|
||||
|
||||
<?php if ($shipmentsList !== []): ?>
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title">Wysylki z Allegro</h3>
|
||||
<h3 class="section-title">Wysyłki z Allegro</h3>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table table--details">
|
||||
<thead>
|
||||
@@ -768,15 +768,15 @@ foreach ($addressesList as $address) {
|
||||
<?php if ($packagesList === [] && $shipmentsList === []): ?>
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title"><?= $e($t('orders.details.tabs.shipments')) ?></h3>
|
||||
<p class="muted mt-12">Brak przesylek dla tego zamowienia.</p>
|
||||
<p class="muted mt-12">Brak przesyłek dla tego zamówienia.</p>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title">Dodaj reczny numer przesylki</h3>
|
||||
<h3 class="section-title">Dodaj reczny numer przesyłki</h3>
|
||||
<form method="POST" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/manual" class="manual-tracking-form mt-12">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="text" name="tracking_number" placeholder="Nr przesylki" required class="form-control">
|
||||
<input type="text" name="tracking_number" placeholder="Nr przesyłki" required class="form-control">
|
||||
<input type="text" name="carrier_name" placeholder="Przewoznik (opcjonalnie)" class="form-control">
|
||||
<button type="submit" class="btn btn--primary">Dodaj</button>
|
||||
</form>
|
||||
@@ -794,9 +794,9 @@ foreach ($addressesList as $address) {
|
||||
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
|
||||
data-csrf-token="<?= $e((string) ($csrfToken ?? '')) ?>"
|
||||
<?= $invoiceRequestedFlag ? 'checked' : '' ?>>
|
||||
<span><strong>Klient prosi o fakture</strong></span>
|
||||
<span><strong>Klient prosi o fakturę</strong></span>
|
||||
</label>
|
||||
<span class="muted" style="font-size:12px;">Po zaznaczeniu pojawi sie przycisk "Wystaw fakture" w naglowku zamowienia.</span>
|
||||
<span class="muted" style="font-size:12px;">Po zaznaczeniu pojawi się przycisk "Wystaw fakturę" w nagłówku zamówienia.</span>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
@@ -1012,7 +1012,7 @@ foreach ($addressesList as $address) {
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($documentsList !== []): ?>
|
||||
<h4 class="section-title mt-12">Dokumenty zewnetrzne</h4>
|
||||
<h4 class="section-title mt-12">Dokumenty zewnętrzne</h4>
|
||||
<div class="table-wrap mt-8">
|
||||
<table class="table table--details">
|
||||
<thead>
|
||||
@@ -1136,12 +1136,12 @@ foreach ($addressesList as $address) {
|
||||
|
||||
var queryTab = '';
|
||||
try { queryTab = new URLSearchParams(window.location.search).get('tab') || ''; } catch (e) {}
|
||||
var forceTab = queryTab || <?= json_encode($flashSuccessMsg !== '' && strpos($flashSuccessMsg, 'Przesylka') !== false ? 'shipments' : '') ?>;
|
||||
var forceTab = queryTab || <?= json_encode($flashSuccessMsg !== '' && strpos($flashSuccessMsg, 'Przesyłka') !== false ? 'shipments' : '') ?>;
|
||||
var savedTab = null;
|
||||
try { savedTab = localStorage.getItem(storageKey); } catch (e) {}
|
||||
setActiveTab(forceTab || savedTab || 'details');
|
||||
|
||||
// Header "Platnosc" button — switch to payments tab and open form
|
||||
// Header "Płatność" button — switch to payments tab and open form
|
||||
var btnHeaderPayment = document.getElementById('btn-header-payment');
|
||||
if (btnHeaderPayment) {
|
||||
btnHeaderPayment.addEventListener('click', function () {
|
||||
@@ -1166,7 +1166,7 @@ foreach ($addressesList as $address) {
|
||||
btn.classList.remove('js-print-queue-pending');
|
||||
btn.classList.add('btn--secondary');
|
||||
btn.classList.add('btn-print-label');
|
||||
btn.setAttribute('title', 'Wyslij do drukarki');
|
||||
btn.setAttribute('title', 'Wyślij do drukarki');
|
||||
}
|
||||
|
||||
function stopPrintQueuePoll() {
|
||||
@@ -1212,7 +1212,7 @@ foreach ($addressesList as $address) {
|
||||
if (!packageId) return;
|
||||
btn.disabled = true;
|
||||
var originalText = btn.innerHTML;
|
||||
btn.innerHTML = 'Wysylam...';
|
||||
btn.innerHTML = 'Wysyłam...';
|
||||
var csrfInput = document.querySelector('input[name="_token"]');
|
||||
var csrf = csrfInput ? csrfInput.value : '<?= $e($csrfToken ?? '') ?>';
|
||||
|
||||
@@ -1232,25 +1232,25 @@ foreach ($addressesList as $address) {
|
||||
btn.classList.add('js-print-queue-pending');
|
||||
watchPrintQueueButton(btn);
|
||||
} else {
|
||||
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany blad';
|
||||
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany błąd';
|
||||
if (window.OrderProAlerts) { window.OrderProAlerts.show({ message: msg, type: 'error' }); }
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (window.OrderProAlerts) { window.OrderProAlerts.show({ message: 'Blad sieci.', type: 'error' }); }
|
||||
if (window.OrderProAlerts) { window.OrderProAlerts.show({ message: 'Błąd sieci.', type: 'error' }); }
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Przy zaladowaniu strony: uruchom polling dla przyciskow juz w kolejce
|
||||
// Przy zaladowaniu strony: uruchom polling dla przyciskow już w kolejce
|
||||
document.querySelectorAll('.js-print-queue-pending').forEach(function (btn) {
|
||||
watchPrintQueueButton(btn);
|
||||
});
|
||||
|
||||
// Auto-click najnowszej etykiety po utworzeniu przesylki (?printLast=1)
|
||||
// Auto-click najnowszej etykiety po utworzeniu przesyłki (?printLast=1)
|
||||
(function autoClickLastLabel() {
|
||||
if (!/[?&]printLast=1\b/.test(location.search)) return;
|
||||
var attempts = 0;
|
||||
@@ -1338,7 +1338,7 @@ foreach ($addressesList as $address) {
|
||||
+ '<button type="submit" class="btn btn--sm btn--secondary">Pobierz</button></form>';
|
||||
}
|
||||
html += '<button type="button" class="btn btn--sm btn--secondary btn-print-label"'
|
||||
+ ' data-package-id="' + pkgId + '" data-order-id="' + orderId + '" title="Wyslij do drukarki">Drukuj</button>';
|
||||
+ ' data-package-id="' + pkgId + '" data-order-id="' + orderId + '" title="Wyślij do drukarki">Drukuj</button>';
|
||||
html += '</span>';
|
||||
labelCell.innerHTML = html;
|
||||
}
|
||||
@@ -1362,7 +1362,7 @@ foreach ($addressesList as $address) {
|
||||
errEl.setAttribute('data-pkg-error', pkgId);
|
||||
tag.parentNode.appendChild(errEl);
|
||||
}
|
||||
if (errEl) errEl.textContent = errorMsg || 'Blad tworzenia przesylki';
|
||||
if (errEl) errEl.textContent = errorMsg || 'Błąd tworzenia przesyłki';
|
||||
}
|
||||
|
||||
function pollPackageStatus(pkgId, orderId, btn, attempt) {
|
||||
@@ -1382,7 +1382,7 @@ foreach ($addressesList as $address) {
|
||||
pollPackageStatus(pkgId, orderId, btn, attempt + 1);
|
||||
}, delay);
|
||||
} else if (btn) {
|
||||
btn.textContent = 'Sprawdz ponownie';
|
||||
btn.textContent = 'Sprawdź ponownie';
|
||||
btn.disabled = false;
|
||||
}
|
||||
} else if (data.status === 'error') {
|
||||
@@ -1391,7 +1391,7 @@ foreach ($addressesList as $address) {
|
||||
})
|
||||
.catch(function () {
|
||||
if (btn) {
|
||||
btn.textContent = 'Blad sieci';
|
||||
btn.textContent = 'Błąd sieci';
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
@@ -1494,11 +1494,11 @@ document.querySelectorAll('.form-delete-package').forEach(function(form) {
|
||||
e.preventDefault();
|
||||
if (window.OrderProAlerts && window.OrderProAlerts.confirm) {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Usuwanie przesylki',
|
||||
message: 'Czy na pewno chcesz usunac te przesylke?',
|
||||
title: 'Usuwanie przesyłki',
|
||||
message: 'Czy na pewno chcesz usuńąć te przesyłkę?',
|
||||
onConfirm: function() { form.submit(); }
|
||||
});
|
||||
} else if (confirm('Czy na pewno chcesz usunac te przesylke?')) {
|
||||
} else if (confirm('Czy na pewno chcesz usuńąć te przesyłkę?')) {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -78,9 +78,9 @@ $totalGross = (float) ($receiptData['total_gross'] ?? 0);
|
||||
<tr>
|
||||
<th>Lp.</th>
|
||||
<th>Nazwa</th>
|
||||
<th class="text-right">Ilosc</th>
|
||||
<th class="text-right">Ilość</th>
|
||||
<th class="text-right">Cena</th>
|
||||
<th class="text-right">Wartosc</th>
|
||||
<th class="text-right">Wartość</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -65,9 +65,9 @@ $totalGross = (float) ($receiptData['total_gross'] ?? 0);
|
||||
<tr>
|
||||
<th>Lp.</th>
|
||||
<th>Nazwa</th>
|
||||
<th>Ilosc</th>
|
||||
<th>Ilość</th>
|
||||
<th>Cena</th>
|
||||
<th>Wartosc</th>
|
||||
<th>Wartość</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -23,7 +23,7 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<p class="muted" style="margin-bottom:8px"><a href="/settings/accounting">Ksiegowosc</a> » <a href="/settings/accounting/invoices">Faktury</a></p>
|
||||
<p class="muted" style="margin-bottom:8px"><a href="/settings/accounting">Księgowość</a> » <a href="/settings/accounting/invoices">Faktury</a></p>
|
||||
<h2 class="section-title"><?= $isEdit ? 'Edycja konfiguracji faktury' : 'Nowa konfiguracja faktury' ?></h2>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
@@ -49,7 +49,7 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<label class="form-field">
|
||||
<span class="field-label">Format numeru *</span>
|
||||
<input class="form-control" type="text" name="number_format" maxlength="64" required placeholder="FV/%N/%M/%Y" value="<?= $e($numberFormat) ?>">
|
||||
<small class="field-hint"><code>%N</code> = numer, <code>%M</code> = miesiac (01-12), <code>%Y</code> = rok (4 cyfry)</small>
|
||||
<small class="field-hint"><code>%N</code> = numer, <code>%M</code> = miesiąc (01-12), <code>%Y</code> = rok (4 cyfry)</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -65,30 +65,30 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<span class="field-label">Data sprzedazy</span>
|
||||
<select class="form-control" name="sale_date_source">
|
||||
<option value="issue_date"<?= $saleDateSource === 'issue_date' ? ' selected' : '' ?>>Data wystawienia</option>
|
||||
<option value="order_date"<?= $saleDateSource === 'order_date' ? ' selected' : '' ?>>Data zamowienia</option>
|
||||
<option value="payment_date"<?= $saleDateSource === 'payment_date' ? ' selected' : '' ?>>Data platnosci</option>
|
||||
<option value="order_date"<?= $saleDateSource === 'order_date' ? ' selected' : '' ?>>Data zamówienia</option>
|
||||
<option value="payment_date"<?= $saleDateSource === 'payment_date' ? ' selected' : '' ?>>Data płatności</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label">Numer referencyjny zamowienia</span>
|
||||
<span class="field-label">Numer referencyjny zamówienia</span>
|
||||
<select class="form-control" name="order_reference">
|
||||
<option value="none"<?= $orderReference === 'none' ? ' selected' : '' ?>>Brak</option>
|
||||
<option value="orderpro"<?= $orderReference === 'orderpro' ? ' selected' : '' ?>>orderPRO</option>
|
||||
<option value="integration"<?= $orderReference === 'integration' ? ' selected' : '' ?>>Zewnetrzny</option>
|
||||
<option value="integration"<?= $orderReference === 'integration' ? ' selected' : '' ?>>Zewnętrzny</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-grid-3 mt-0">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Termin platnosci (dni)</span>
|
||||
<span class="field-label">Termin płatności (dni)</span>
|
||||
<input class="form-control" type="number" name="payment_to_days" min="0" max="365" value="<?= $paymentToDays ?>">
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label">Typ dokumentu</span>
|
||||
<select class="form-control" name="default_kind">
|
||||
<option value="vat"<?= $defaultKind === 'vat' ? ' selected' : '' ?>>Faktura VAT</option>
|
||||
<option value="proforma"<?= $defaultKind === 'proforma' ? ' selected' : '' ?>>Proforma</option>
|
||||
<option value="proformą"<?= $defaultKind === 'proformą' ? ' selected' : '' ?>>Proformą</option>
|
||||
<option value="invoice_other"<?= $defaultKind === 'invoice_other' ? ' selected' : '' ?>>Inna</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
@@ -6,9 +6,9 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<p class="muted" style="margin-bottom:8px"><a href="/settings/accounting">← Ksiegowosc</a></p>
|
||||
<p class="muted" style="margin-bottom:8px"><a href="/settings/accounting">← Księgowość</a></p>
|
||||
<h2 class="section-title">Konfiguracje faktur</h2>
|
||||
<p class="muted mt-12">Zarzadzaj konfiguracjami wystawiania faktur. Mozesz dodac wiele konfiguracji (np. dla roznych dzialalnosci) i opcjonalnie delegowac wystawianie do Fakturowni.</p>
|
||||
<p class="muted mt-12">Zarzadzaj konfiguracjami wystawiania faktur. Możesz dodać wiele konfiguracji (np. dla roznych dzialalnosci) i opcjonalnie delegowac wystawianie do Fakturowni.</p>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="mt-12"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $error, 'dismissible' => true]); ?></div>
|
||||
@@ -84,7 +84,7 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<form action="/settings/accounting/invoices/delete" method="post" style="display:inline" class="js-confirm-delete">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= $cid ?>">
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usun</button>
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usuń</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -17,7 +17,7 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<p class="muted" style="margin-bottom:8px"><a href="/settings/accounting">Ksiegowosc</a> » <a href="/settings/accounting/receipts">Paragony</a></p>
|
||||
<p class="muted" style="margin-bottom:8px"><a href="/settings/accounting">Księgowość</a> » <a href="/settings/accounting/receipts">Paragony</a></p>
|
||||
<h2 class="section-title"><?= $isEdit ? 'Edycja konfiguracji paragonu' : 'Nowa konfiguracja paragonu' ?></h2>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
@@ -43,7 +43,7 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<label class="form-field">
|
||||
<span class="field-label">Format numeru *</span>
|
||||
<input class="form-control" type="text" name="number_format" maxlength="64" required placeholder="PAR/%N/%M/%Y" value="<?= $e($numberFormat) ?>">
|
||||
<small class="field-hint"><code>%N</code> = numer, <code>%M</code> = miesiac, <code>%Y</code> = rok</small>
|
||||
<small class="field-hint"><code>%N</code> = numer, <code>%M</code> = miesiąc, <code>%Y</code> = rok</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -59,16 +59,16 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<span class="field-label">Data sprzedazy</span>
|
||||
<select class="form-control" name="sale_date_source">
|
||||
<option value="issue_date"<?= $saleDateSource === 'issue_date' ? ' selected' : '' ?>>Data wystawienia</option>
|
||||
<option value="order_date"<?= $saleDateSource === 'order_date' ? ' selected' : '' ?>>Data zamowienia</option>
|
||||
<option value="payment_date"<?= $saleDateSource === 'payment_date' ? ' selected' : '' ?>>Data platnosci</option>
|
||||
<option value="order_date"<?= $saleDateSource === 'order_date' ? ' selected' : '' ?>>Data zamówienia</option>
|
||||
<option value="payment_date"<?= $saleDateSource === 'payment_date' ? ' selected' : '' ?>>Data płatności</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label">Numer referencyjny zamowienia</span>
|
||||
<span class="field-label">Numer referencyjny zamówienia</span>
|
||||
<select class="form-control" name="order_reference">
|
||||
<option value="none"<?= $orderReference === 'none' ? ' selected' : '' ?>>Brak</option>
|
||||
<option value="orderpro"<?= $orderReference === 'orderpro' ? ' selected' : '' ?>>orderPRO</option>
|
||||
<option value="integration"<?= $orderReference === 'integration' ? ' selected' : '' ?>>Zewnetrzny</option>
|
||||
<option value="integration"<?= $orderReference === 'integration' ? ' selected' : '' ?>>Zewnętrzny</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
@@ -76,7 +76,7 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<div class="form-grid-2 mt-0">
|
||||
<label class="form-field" style="display:flex;align-items:center;gap:6px;flex-direction:row">
|
||||
<input type="checkbox" name="is_named" value="1"<?= $isNamed ? ' checked' : '' ?>>
|
||||
<span class="field-label" style="margin:0">Paragon imienny (z danymi kupujacego)</span>
|
||||
<span class="field-label" style="margin:0">Paragon imienny (z danymi kupującego)</span>
|
||||
</label>
|
||||
<label class="form-field" style="display:flex;align-items:center;gap:6px;flex-direction:row">
|
||||
<input type="checkbox" name="is_active" value="1"<?= $isActive ? ' checked' : '' ?>>
|
||||
|
||||
@@ -6,9 +6,9 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<p class="muted" style="margin-bottom:8px"><a href="/settings/accounting">← Ksiegowosc</a></p>
|
||||
<h2 class="section-title">Konfiguracje paragonow</h2>
|
||||
<p class="muted mt-12">Zarzadzaj konfiguracjami wystawiania paragonow.</p>
|
||||
<p class="muted" style="margin-bottom:8px"><a href="/settings/accounting">← Księgowość</a></p>
|
||||
<h2 class="section-title">Konfiguracje paragonów</h2>
|
||||
<p class="muted mt-12">Zarzadzaj konfiguracjami wystawiania paragonów.</p>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="mt-12"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $error, 'dismissible' => true]); ?></div>
|
||||
@@ -26,7 +26,7 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
</div>
|
||||
|
||||
<?php if ($configs === []): ?>
|
||||
<p class="muted mt-12">Brak konfiguracji paragonow. Dodaj pierwsza powyzej.</p>
|
||||
<p class="muted mt-12">Brak konfiguracji paragonów. Dodaj pierwsza powyzej.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
@@ -67,7 +67,7 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<form action="/settings/accounting/receipts/delete" method="post" style="display:inline" class="js-confirm-delete">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= $cid ?>">
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usun</button>
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usuń</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -4,8 +4,8 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="section-title">Ksiegowosc</h2>
|
||||
<p class="muted mt-12">Wybierz typ dokumentu ktorego konfiguracje chcesz zarzadzac.</p>
|
||||
<h2 class="section-title">Księgowość</h2>
|
||||
<p class="muted mt-12">Wybierz typ dokumentu którego konfiguracje chcesz zarzadzac.</p>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="mt-12"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $error, 'dismissible' => true]); ?></div>
|
||||
@@ -19,14 +19,14 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<div class="form-grid-2">
|
||||
<div style="border:1px solid var(--border-color, #e5e7eb);border-radius:8px;padding:16px">
|
||||
<h3 class="section-title" style="margin-top:0">Paragony</h3>
|
||||
<p class="muted mt-12">Konfiguracje wystawiania paragonow: format numeracji, sposob numerowania, oznaczenie zamowienia.</p>
|
||||
<p class="muted mt-12">Konfiguracje wystawiania paragonów: format numeracji, sposób numerowania, oznaczenie zamówienia.</p>
|
||||
<div class="form-actions mt-16">
|
||||
<a class="btn btn--primary" href="/settings/accounting/receipts">Zarzadzaj paragonami</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="border:1px solid var(--border-color, #e5e7eb);border-radius:8px;padding:16px">
|
||||
<h3 class="section-title" style="margin-top:0">Faktury</h3>
|
||||
<p class="muted mt-12">Konfiguracje wystawiania faktur: numeracja lokalna lub delegacja do Fakturowni, termin platnosci, typ dokumentu.</p>
|
||||
<p class="muted mt-12">Konfiguracje wystawiania faktur: numeracja lokalna lub delegacja do Fakturowni, termin płatności, typ dokumentu.</p>
|
||||
<div class="form-actions mt-16">
|
||||
<a class="btn btn--primary" href="/settings/accounting/invoices">Zarzadzaj fakturami</a>
|
||||
<a class="btn btn--secondary ml-8" href="/settings/accounting/invoices/issued">Faktury wystawione</a>
|
||||
|
||||
@@ -443,7 +443,7 @@ foreach ($pullStatusMappings as $pm) {
|
||||
|
||||
<div class="dm-apaczka-panel" style="<?= $currentCarrier !== 'apaczka' ? 'display:none' : '' ?>">
|
||||
<?php if ($dmApaczkaServices === []): ?>
|
||||
<div class="muted">Brak uslug Apaczka (sprawdz konfiguracje App ID/App Secret).</div>
|
||||
<div class="muted">Brak usług Apaczka (sprawdz konfiguracje App ID/App Secret).</div>
|
||||
<?php else: ?>
|
||||
<select class="form-control dm-apaczka-select">
|
||||
<option value="">-- <?= $e($t('settings.allegro.delivery.fields.no_mapping')) ?> --</option>
|
||||
@@ -467,7 +467,7 @@ foreach ($pullStatusMappings as $pm) {
|
||||
<?php // InPost simple select ?>
|
||||
<div class="dm-inpost-panel" style="<?= $currentCarrier !== 'inpost' ? 'display:none' : '' ?>">
|
||||
<?php if ($dmInpostServices === []): ?>
|
||||
<div class="muted">Brak uslug InPost (sprawdz polaczenie z Allegro).</div>
|
||||
<div class="muted">Brak usług InPost (sprawdz połączenie z Allegro).</div>
|
||||
<?php else: ?>
|
||||
<select class="form-control dm-inpost-select">
|
||||
<option value="">-- <?= $e($t('settings.allegro.delivery.fields.no_mapping')) ?> --</option>
|
||||
|
||||
@@ -31,7 +31,7 @@ $action = $isEdit
|
||||
<span class="field-label">Klucz *</span>
|
||||
<?php if ($isEdit): ?>
|
||||
<input class="form-control" type="text" value="<?= $e($rowKey) ?>" disabled>
|
||||
<small class="muted">Klucz nie może być zmieniany po utworzeniu.</small>
|
||||
<small class="muted">Klucz nie może być zmieńiany po utworzeniu.</small>
|
||||
<?php else: ?>
|
||||
<input class="form-control" type="text" name="key" maxlength="50" required
|
||||
pattern="[a-z][a-z0-9_]{0,49}"
|
||||
|
||||
@@ -14,7 +14,7 @@ $isEdit = $em !== null;
|
||||
|
||||
<section class="card">
|
||||
<h2 class="section-title">Skrzynki pocztowe</h2>
|
||||
<p class="muted mt-12">Konfiguracja skrzynek SMTP do wysylki wiadomosci e-mail.</p>
|
||||
<p class="muted mt-12">Konfiguracja skrzynek SMTP do wysyłki wiadomości e-mail.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="mt-12"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $errorMessage, 'dismissible' => true]); ?></div>
|
||||
@@ -28,7 +28,7 @@ $isEdit = $em !== null;
|
||||
<h3 class="section-title">Lista skrzynek</h3>
|
||||
|
||||
<?php if (count($mailboxes) === 0): ?>
|
||||
<p class="muted mt-12">Brak skrzynek pocztowych. Dodaj pierwsza skrzynke ponizej.</p>
|
||||
<p class="muted mt-12">Brak skrzynek pocztowych. Dodaj pierwsza skrzynke poniżej.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
@@ -73,7 +73,7 @@ $isEdit = $em !== null;
|
||||
<form action="/settings/email-mailboxes/delete" method="post" style="display:inline" class="js-confirm-delete">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= (int) ($mb['id'] ?? 0) ?>">
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usun</button>
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usuń</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -96,7 +96,7 @@ $isEdit = $em !== null;
|
||||
<div class="form-grid-2">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Nazwa *</span>
|
||||
<input class="form-control" type="text" name="name" maxlength="100" required value="<?= $e((string) ($em['name'] ?? '')) ?>" placeholder="np. Glowna skrzynka">
|
||||
<input class="form-control" type="text" name="name" maxlength="100" required value="<?= $e((string) ($em['name'] ?? '')) ?>" placeholder="np. Główna skrzynka">
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label">E-mail nadawcy *</span>
|
||||
@@ -135,14 +135,14 @@ $isEdit = $em !== null;
|
||||
|
||||
<div class="form-grid-2 mt-0">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Uzytkownik SMTP *</span>
|
||||
<span class="field-label">Użytkownik SMTP *</span>
|
||||
<input class="form-control" type="text" name="smtp_username" maxlength="255" required value="<?= $e((string) ($em['smtp_username'] ?? '')) ?>">
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label">Haslo SMTP <?= $isEdit ? '' : '*' ?></span>
|
||||
<span class="field-label">Hasło SMTP <?= $isEdit ? '' : '*' ?></span>
|
||||
<input class="form-control" type="password" name="smtp_password" maxlength="255" <?= $isEdit ? '' : 'required' ?> placeholder="<?= $isEdit ? '(bez zmian)' : '' ?>">
|
||||
<?php if ($isEdit): ?>
|
||||
<small class="field-hint">Pozostaw puste, aby zachowac aktualne haslo</small>
|
||||
<small class="field-hint">Pozostaw puste, aby zachowac aktualne hasło</small>
|
||||
<?php endif; ?>
|
||||
</label>
|
||||
</div>
|
||||
@@ -158,11 +158,11 @@ $isEdit = $em !== null;
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h4 class="section-title mt-16">Szablon wiadomosci</h4>
|
||||
<p class="muted mt-4" style="font-size:12px">Opcjonalnie. Naglowek i stopka beda dolaczane do kazdego e-maila wysylanego z tej skrzynki.</p>
|
||||
<h4 class="section-title mt-16">Szablon wiadomości</h4>
|
||||
<p class="muted mt-4" style="font-size:12px">Opcjonalnie. Nagłówek i stopka beda dolaczane do kazdego e-maila wysyłanego z tej skrzynki.</p>
|
||||
|
||||
<div class="form-field mt-12">
|
||||
<span class="field-label">Naglowek (header)</span>
|
||||
<span class="field-label">Nagłówek (header)</span>
|
||||
<div id="js-header-editor" style="min-height:80px"></div>
|
||||
<textarea id="js-header-source" class="html-source-area"></textarea>
|
||||
<div class="html-source-toggle"><button type="button" class="js-toggle-html" data-editor="header"></> HTML</button> <button type="button" class="js-preview-html" data-editor="header">Podglad</button></div>
|
||||
@@ -179,7 +179,7 @@ $isEdit = $em !== null;
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $isEdit ? 'Zapisz zmiany' : 'Dodaj skrzynke' ?></button>
|
||||
<button type="button" class="btn btn--secondary" id="js-test-connection">Testuj polaczenie</button>
|
||||
<button type="button" class="btn btn--secondary" id="js-test-connection">Testuj połączenie</button>
|
||||
<?php if ($isEdit): ?>
|
||||
<a href="/settings/email-mailboxes" class="btn btn--secondary">Anuluj</a>
|
||||
<?php endif; ?>
|
||||
@@ -218,13 +218,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
var headerEditor = new Quill('#js-header-editor', {
|
||||
theme: 'snow',
|
||||
modules: { toolbar: quillToolbar },
|
||||
placeholder: 'Naglowek wiadomosci (np. logo, nazwa firmy)...'
|
||||
placeholder: 'Nagłówek wiadomości (np. logo, nazwa firmy)...'
|
||||
});
|
||||
|
||||
var footerEditor = new Quill('#js-footer-editor', {
|
||||
theme: 'snow',
|
||||
modules: { toolbar: quillToolbar },
|
||||
placeholder: 'Stopka wiadomosci (np. dane kontaktowe, adres)...'
|
||||
placeholder: 'Stopka wiadomości (np. dane kontaktowe, adres)...'
|
||||
});
|
||||
|
||||
// --- HTML source toggle ---
|
||||
@@ -312,11 +312,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.js-preview-html').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var key = this.getAttribute('data-editor');
|
||||
var label = key === 'header' ? 'Naglowek (header)' : 'Stopka (footer)';
|
||||
var label = key === 'header' ? 'Nagłówek (header)' : 'Stopka (footer)';
|
||||
var html = getEditorHtml(key);
|
||||
if (!html) {
|
||||
if (window.OrderProAlerts && window.OrderProAlerts.warning) {
|
||||
window.OrderProAlerts.warning('Podglad', 'Brak tresci do wyswietlenia.');
|
||||
window.OrderProAlerts.warning('Podglad', 'Brak treśći do wyświetlenia.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -365,11 +365,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (window.OrderProAlerts && window.OrderProAlerts.confirm) {
|
||||
window.OrderProAlerts.confirm(
|
||||
'Usuwanie skrzynki',
|
||||
'Czy na pewno chcesz usunac te skrzynke pocztowa?',
|
||||
'Czy na pewno chcesz usuńąć te skrzynke pocztowa?',
|
||||
function() { form.submit(); }
|
||||
);
|
||||
} else {
|
||||
if (confirm('Czy na pewno chcesz usunac te skrzynke pocztowa?')) {
|
||||
if (confirm('Czy na pewno chcesz usuńąć te skrzynke pocztowa?')) {
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
@@ -399,16 +399,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
} else {
|
||||
resultDiv.className = 'mt-12 alert alert--danger';
|
||||
}
|
||||
resultDiv.textContent = data.message || 'Brak odpowiedzi';
|
||||
resultDiv.textContent = data.message || 'Brak odpowiedźi';
|
||||
})
|
||||
.catch(function(err) {
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'mt-12 alert alert--danger';
|
||||
resultDiv.textContent = 'Blad polaczenia: ' + err.message;
|
||||
resultDiv.textContent = 'Błąd połączenia: ' + err.message;
|
||||
})
|
||||
.finally(function() {
|
||||
testBtn.disabled = false;
|
||||
testBtn.textContent = 'Testuj polaczenie';
|
||||
testBtn.textContent = 'Testuj połączenie';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
|
||||
<section class="card">
|
||||
<h2 class="section-title"><?= $isEdit ? 'Edytuj szablon e-mail' : 'Dodaj szablon e-mail' ?></h2>
|
||||
<p class="muted mt-12">Skonfiguruj temat, tresc i zmienne, ktore beda podstawiane podczas wysylki.</p>
|
||||
<p class="muted mt-12">Skonfiguruj temat, treść i zmienne, które beda podstawiane podczas wysyłki.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="mt-12"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $errorMessage, 'dismissible' => true]); ?></div>
|
||||
@@ -29,7 +29,7 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
<div class="form-grid-2">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Nazwa szablonu *</span>
|
||||
<input class="form-control" type="text" name="name" maxlength="200" required value="<?= $e((string) ($template['name'] ?? '')) ?>" placeholder="np. Potwierdzenie zamowienia">
|
||||
<input class="form-control" type="text" name="name" maxlength="200" required value="<?= $e((string) ($template['name'] ?? '')) ?>" placeholder="np. Potwierdzenie zamówienia">
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label">Skrzynka nadawcza</span>
|
||||
@@ -47,8 +47,8 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
|
||||
<div class="form-grid-2 mt-0">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Temat wiadomosci *</span>
|
||||
<input class="form-control" type="text" name="subject" maxlength="500" required value="<?= $e((string) ($template['subject'] ?? '')) ?>" placeholder="np. Potwierdzenie zamowienia {{zamowienie.numer}}">
|
||||
<span class="field-label">Temat wiadomości *</span>
|
||||
<input class="form-control" type="text" name="subject" maxlength="500" required value="<?= $e((string) ($template['subject'] ?? '')) ?>" placeholder="np. Potwierdzenie zamówienia {{zamowienie.numer}}">
|
||||
</label>
|
||||
<div class="form-field" style="display:flex;align-items:flex-end;gap:8px">
|
||||
<label style="display:flex;align-items:center;gap:6px;flex-direction:row">
|
||||
@@ -60,7 +60,7 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
|
||||
<div class="form-grid-2 mt-0">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Zalacznik nr 1</span>
|
||||
<span class="field-label">Załącznik nr 1</span>
|
||||
<select class="form-control" name="attachment_1">
|
||||
<option value="">- brak -</option>
|
||||
<?php foreach ($attachmentTypes as $attachmentKey => $attachmentLabel): ?>
|
||||
@@ -72,12 +72,12 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
</div>
|
||||
|
||||
<div class="mt-12">
|
||||
<span class="field-label">Tresc wiadomosci *</span>
|
||||
<p class="muted mt-4">Dostepne sa zmienne przesylki: <code>{{przesylka.numer}}</code> oraz <code>{{przesylka.link_sledzenia}}</code>.</p>
|
||||
<span class="field-label">Treść wiadomości *</span>
|
||||
<p class="muted mt-4">Dostępne są zmienne przesyłki: <code>{{przesylka.numer}}</code> oraz <code>{{przesylka.link_sledzenia}}</code>.</p>
|
||||
<div class="email-tpl-editor-wrap mt-4">
|
||||
<div class="email-tpl-toolbar">
|
||||
<div class="email-tpl-var-dropdown">
|
||||
<button type="button" class="btn btn--sm btn--secondary" id="js-var-toggle">Wstaw zmienna</button>
|
||||
<button type="button" class="btn btn--sm btn--secondary" id="js-var-toggle">Wstaw zmienną</button>
|
||||
<div class="email-tpl-var-panel" id="js-var-panel" style="display:none">
|
||||
<?php foreach ($variableGroups as $groupKey => $group): ?>
|
||||
<div class="email-var-group">
|
||||
@@ -99,7 +99,7 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $isEdit ? 'Zapisz zmiany' : 'Dodaj szablon' ?></button>
|
||||
<a href="/settings/email-templates" class="btn btn--secondary">Powrot do listy</a>
|
||||
<a href="/settings/email-templates" class="btn btn--secondary">Powrót do listy</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -137,7 +137,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
['clean']
|
||||
]
|
||||
},
|
||||
placeholder: 'Wpisz tresc wiadomosci...'
|
||||
placeholder: 'Wpisz treść wiadomości...'
|
||||
});
|
||||
|
||||
var existingHtml = document.getElementById('js-body-html').value;
|
||||
|
||||
@@ -8,7 +8,7 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
<h2 class="section-title">Szablony e-mail</h2>
|
||||
<a href="/settings/email-templates/create" class="btn btn--primary btn--sm">Dodaj szablon</a>
|
||||
</div>
|
||||
<p class="muted mt-12">Szablony wiadomosci e-mail z edytorem i systemem zmiennych.</p>
|
||||
<p class="muted mt-12">Szablony wiadomości e-mail z edytorem i systemem zmiennych.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="mt-12"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $errorMessage, 'dismissible' => true]); ?></div>
|
||||
@@ -31,7 +31,7 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
<th>Nazwa</th>
|
||||
<th>Temat</th>
|
||||
<th>Skrzynka</th>
|
||||
<th>Zalacznik</th>
|
||||
<th>Załącznik</th>
|
||||
<th>Status</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
@@ -66,7 +66,7 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
<form action="/settings/email-templates/delete" method="post" style="display:inline" class="js-confirm-delete">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= $templateId ?>">
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usun</button>
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usuń</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -117,7 +117,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (window.OrderProAlerts && window.OrderProAlerts.confirm) {
|
||||
window.OrderProAlerts.confirm(
|
||||
'Usuwanie szablonu',
|
||||
'Czy na pewno chcesz usunac ten szablon e-mail?',
|
||||
'Czy na pewno chcesz usuńąć ten szablon e-mail?',
|
||||
function() { delForm.submit(); }
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
?>
|
||||
<section class="card">
|
||||
<h2 class="section-title">Integracja Fakturownia</h2>
|
||||
<p class="muted mt-12">Fakturownia ma jedna globalna konfiguracje. Wroc do strony konfiguracji.</p>
|
||||
<p class="muted mt-12">Fakturownia ma jedna globalna konfiguracje. Wróć do strony konfiguracji.</p>
|
||||
<div class="form-actions mt-16">
|
||||
<a class="btn btn--primary" href="/settings/integrations/fakturownia">Otworz konfiguracje</a>
|
||||
</div>
|
||||
|
||||
@@ -53,7 +53,7 @@ $flashError = trim((string) ($flashError ?? ''));
|
||||
<label class="form-field">
|
||||
<span class="field-label">Token API <?= $hasToken ? '' : '*' ?></span>
|
||||
<input class="form-control" type="password" name="api_token" autocomplete="new-password" placeholder="<?= $hasToken ? '********' : '' ?>" <?= $hasToken ? '' : 'required' ?>>
|
||||
<span class="muted"><?= $hasToken ? 'Token jest zapisany. Wpisz nowy, aby go nadpisac.' : 'Token API z Fakturowni (Ustawienia > Konta uzytkownikow > API).' ?></span>
|
||||
<span class="muted"><?= $hasToken ? 'Token jest zapisany. Wpisz nowy, aby go nadpisać.' : 'Token API z Fakturowni (Ustawienia > Konta użytkowników > API).' ?></span>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
@@ -63,16 +63,16 @@ $flashError = trim((string) ($flashError ?? ''));
|
||||
|
||||
<div class="form-grid-2 mt-0">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Domyslny typ dokumentu</span>
|
||||
<span class="field-label">Domyślny typ dokumentu</span>
|
||||
<select class="form-control" name="default_kind">
|
||||
<option value="vat" <?= $defaultKind === 'vat' ? 'selected' : '' ?>>Faktura VAT</option>
|
||||
<option value="proforma" <?= $defaultKind === 'proforma' ? 'selected' : '' ?>>Proforma</option>
|
||||
<option value="proformą" <?= $defaultKind === 'proformą' ? 'selected' : '' ?>>Proformą</option>
|
||||
<option value="invoice_other" <?= $defaultKind === 'invoice_other' ? 'selected' : '' ?>>Inna</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">Domyslny termin platnosci (dni)</span>
|
||||
<span class="field-label">Domyślny termin płatności (dni)</span>
|
||||
<input class="form-control" type="number" name="default_payment_to_days" min="0" max="120" value="<?= $defaultPaymentDays ?>">
|
||||
</label>
|
||||
</div>
|
||||
@@ -92,13 +92,13 @@ $flashError = trim((string) ($flashError ?? ''));
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title">Test polaczenia</h3>
|
||||
<h3 class="section-title">Test połączenia</h3>
|
||||
<p class="muted mt-12">Wykonuje GET <code><?= $e('https://' . ($prefix !== '' ? $prefix : '{prefix}') . '.fakturownia.pl/account.json') ?></code> z zapisanym tokenem.</p>
|
||||
|
||||
<form action="/settings/integrations/fakturownia/test" method="post" class="mt-12">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= $integrationId ?>">
|
||||
<button type="submit" class="btn btn--secondary">Testuj polaczenie</button>
|
||||
<button type="submit" class="btn btn--secondary">Testuj połączenie</button>
|
||||
</form>
|
||||
|
||||
<?php if ($lastTestAt !== ''): ?>
|
||||
|
||||
@@ -23,7 +23,7 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
<strong>Nowy klucz API:</strong>
|
||||
<code id="new-api-key" style="display:inline-block;padding:4px 8px;background:#f5f5f5;border:1px solid #ddd;border-radius:3px;font-size:13px;word-break:break-all;user-select:all"><?= $e($newKey) ?></code>
|
||||
<button type="button" class="btn btn--secondary btn--sm ml-8" onclick="navigator.clipboard.writeText(document.getElementById('new-api-key').textContent)">Kopiuj</button>
|
||||
<br><small class="muted">Ten klucz nie bedzie ponownie wyswietlony. Skopiuj go teraz.</small>
|
||||
<br><small class="muted">Ten klucz nie bedzie ponownie wyświetlony. Skopiuj go teraz.</small>
|
||||
<?php
|
||||
$newKeyAlertHtml = ob_get_clean();
|
||||
?>
|
||||
@@ -35,7 +35,7 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
<h3 class="section-title">Klucze API</h3>
|
||||
|
||||
<?php if (count($keysList) === 0): ?>
|
||||
<p class="muted mt-12">Brak kluczy API. Utworz pierwszy klucz ponizej.</p>
|
||||
<p class="muted mt-12">Brak kluczy API. Utworz pierwszy klucz poniżej.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
@@ -66,7 +66,7 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
<td>
|
||||
<form method="post" action="/settings/printing/keys/<?= $e((string) ($key['id'] ?? '')) ?>/delete" style="display:inline" class="js-delete-api-key-form">
|
||||
<input type="hidden" name="_token" value="<?= $e((string) ($csrfToken ?? '')) ?>">
|
||||
<button type="button" class="btn btn--danger btn--sm js-delete-api-key-btn">Usun</button>
|
||||
<button type="button" class="btn btn--danger btn--sm js-delete-api-key-btn">Usuń</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -107,7 +107,7 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
<th>Zamowienie</th>
|
||||
<th>Zamówienie</th>
|
||||
<th>Tracking</th>
|
||||
<th>Status</th>
|
||||
<th>Wydrukowano</th>
|
||||
@@ -133,7 +133,7 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
$badgeLabel = 'Wydrukowano';
|
||||
} elseif ($jobStatus === 'failed') {
|
||||
$badgeClass = 'print-status-badge--failed';
|
||||
$badgeLabel = 'Blad';
|
||||
$badgeLabel = 'Błąd';
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
@@ -163,7 +163,7 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
type="button"
|
||||
class="btn btn--sm btn--danger js-delete-print-job"
|
||||
data-job-id="<?= $e((string) $jobId) ?>"
|
||||
>Usun</button>
|
||||
>Usuń</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
@@ -184,8 +184,8 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
if (window.OrderProAlerts && window.OrderProAlerts.confirm) {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Usuwanie wpisu z kolejki',
|
||||
message: 'Czy na pewno chcesz usunac ten wpis kolejki wydruku?',
|
||||
confirmLabel: 'Usun',
|
||||
message: 'Czy na pewno chcesz usuńąć ten wpis kolejki wydruku?',
|
||||
confirmLabel: 'Usuń',
|
||||
cancelLabel: 'Anuluj',
|
||||
danger: true
|
||||
}).then(function (confirmed) {
|
||||
@@ -206,8 +206,8 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
if (window.OrderProAlerts && window.OrderProAlerts.confirm) {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Usuwanie klucza API',
|
||||
message: 'Czy na pewno chcesz usunac ten klucz API?',
|
||||
confirmLabel: 'Usun',
|
||||
message: 'Czy na pewno chcesz usuńąć ten klucz API?',
|
||||
confirmLabel: 'Usuń',
|
||||
cancelLabel: 'Anuluj',
|
||||
danger: true
|
||||
}).then(function (confirmed) {
|
||||
@@ -228,7 +228,7 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
if (!packageId || !csrf) return;
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wysylam...';
|
||||
btn.textContent = 'Wysyłam...';
|
||||
|
||||
fetch('/api/print/jobs', {
|
||||
method: 'POST',
|
||||
@@ -240,7 +240,7 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
if (res.status === 201) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany blad';
|
||||
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany błąd';
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.show({ message: msg, type: 'error' });
|
||||
}
|
||||
@@ -250,7 +250,7 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
})
|
||||
.catch(function () {
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.show({ message: 'Blad sieci.', type: 'error' });
|
||||
window.OrderProAlerts.show({ message: 'Błąd sieci.', type: 'error' });
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Ponow';
|
||||
|
||||
@@ -546,7 +546,7 @@ foreach ($dmMappings as $dm) {
|
||||
|
||||
<div class="dm-apaczka-panel dm-searchable-select" data-provider="apaczka" data-current-id="<?= $e($currentCarrier === 'apaczka' ? $currentMethodId : '') ?>" data-current-name="<?= $e($currentCarrier === 'apaczka' ? $currentServiceName : '') ?>" style="<?= $currentCarrier !== 'apaczka' ? 'display:none' : '' ?>">
|
||||
<?php if ($dmApaczkaServices === []): ?>
|
||||
<div class="muted">Brak uslug Apaczka (sprawdz konfiguracje App ID/App Secret).</div>
|
||||
<div class="muted">Brak usług Apaczka (sprawdz konfiguracje App ID/App Secret).</div>
|
||||
<?php else: ?>
|
||||
<input type="text" class="form-control dm-search-input" placeholder="<?= $e($t('settings.integrations.delivery.fields.search_placeholder')) ?>" value="<?= $e($currentCarrier === 'apaczka' ? $currentServiceName : '') ?>" autocomplete="off">
|
||||
<div class="searchable-select__dropdown dm-dropdown">
|
||||
|
||||
@@ -6,7 +6,7 @@ $variableGroups = is_array($variableGroups ?? null) ? $variableGroups : [];
|
||||
|
||||
<section class="card">
|
||||
<h2 class="section-title"><?= $isEdit ? 'Edytuj szablon SMS' : 'Dodaj szablon SMS' ?></h2>
|
||||
<p class="muted mt-12">Wpisz tresc wiadomosci ze zmiennymi typu <code>{{zamowienie.numer}}</code>. Stopka SMSPLANET jest doklejana automatycznie przy wysylce, nie dopisuj jej w szablonie.</p>
|
||||
<p class="muted mt-12">Wpisz treść wiadomości ze zmiennymi typu <code>{{zamowienie.numer}}</code>. Stopka SMSPLANET jest doklejana automatycznie przy wysyłce, nie dopisuj jej w szablonie.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="mt-12"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $errorMessage, 'dismissible' => true]); ?></div>
|
||||
@@ -38,19 +38,19 @@ $variableGroups = is_array($variableGroups ?? null) ? $variableGroups : [];
|
||||
|
||||
<div class="mt-12">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Tresc wiadomosci *</span>
|
||||
<textarea class="form-control" name="body" id="js-sms-body" rows="6" maxlength="918" required placeholder="np. Czesc {{kupujacy.imie_nazwisko}}, Twoja przesylka {{przesylka.numer}} jest w drodze."><?= $e((string) ($template['body'] ?? '')) ?></textarea>
|
||||
<span class="field-label">Treść wiadomości *</span>
|
||||
<textarea class="form-control" name="body" id="js-sms-body" rows="6" maxlength="918" required placeholder="np. Cześć {{kupujacy.imie_nazwisko}}, Twoja przesyłka {{przesylka.numer}} jest w drodze."><?= $e((string) ($template['body'] ?? '')) ?></textarea>
|
||||
<div class="sms-template-counter muted mt-4">
|
||||
<span id="js-sms-body-count">0</span> / 918 znakow
|
||||
<span class="sms-template-counter-warning" id="js-sms-body-warn" hidden>(pamietaj o stopce dodawanej przez SMSPLANET)</span>
|
||||
<span id="js-sms-body-count">0</span> / 918 znaków
|
||||
<span class="sms-template-counter-warning" id="js-sms-body-warn" hidden>(pamiętaj o stopce dodawanej przez SMSPLANET)</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="sms-var-panel mt-12">
|
||||
<div class="sms-var-panel__head">
|
||||
<span class="field-label sms-var-panel__title">Dostepne zmienne</span>
|
||||
<span class="muted sms-var-panel__hint">Kliknij chip, aby wstawic w pozycji kursora.</span>
|
||||
<span class="field-label sms-var-panel__title">Dostępne zmienne</span>
|
||||
<span class="muted sms-var-panel__hint">Kliknij chip, aby wstawić w pozycji kursora.</span>
|
||||
</div>
|
||||
<?php foreach ($variableGroups as $groupKey => $group): ?>
|
||||
<div class="sms-var-group" data-group="<?= $e($groupKey) ?>">
|
||||
@@ -69,7 +69,7 @@ $variableGroups = is_array($variableGroups ?? null) ? $variableGroups : [];
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $isEdit ? 'Zapisz zmiany' : 'Dodaj szablon' ?></button>
|
||||
<a href="/settings/sms-templates" class="btn btn--secondary">Powrot do listy</a>
|
||||
<a href="/settings/sms-templates" class="btn btn--secondary">Powrót do listy</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -7,7 +7,7 @@ $templates = is_array($templates ?? null) ? $templates : [];
|
||||
<h2 class="section-title">Szablony SMS</h2>
|
||||
<a href="/settings/sms-templates/create" class="btn btn--primary btn--sm">Dodaj szablon</a>
|
||||
</div>
|
||||
<p class="muted mt-12">Szybkie szablony wiadomosci SMS do wstawiania z zakladki SMS w szczegolach zamowienia. Stopka SMSPLANET jest doklejana automatycznie.</p>
|
||||
<p class="muted mt-12">Szybkie szablony wiadomości SMS do wstawiania z zakładki SMS w szczegółach zamówienia. Stopka SMSPLANET jest doklejana automatycznie.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="mt-12"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $errorMessage, 'dismissible' => true]); ?></div>
|
||||
@@ -28,7 +28,7 @@ $templates = is_array($templates ?? null) ? $templates : [];
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nazwa</th>
|
||||
<th>Tresc</th>
|
||||
<th>Treść</th>
|
||||
<th>Status</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
@@ -59,10 +59,10 @@ $templates = is_array($templates ?? null) ? $templates : [];
|
||||
data-active="<?= (int) ($tpl['is_active'] ?? 0) ?>">
|
||||
<?= ((int) ($tpl['is_active'] ?? 0)) === 1 ? 'Dezaktywuj' : 'Aktywuj' ?>
|
||||
</button>
|
||||
<form action="/settings/sms-templates/delete" method="post" class="inline-form js-confirm-delete" data-confirm-title="Usuwanie szablonu" data-confirm-message="Czy na pewno chcesz usunac ten szablon SMS?">
|
||||
<form action="/settings/sms-templates/delete" method="post" class="inline-form js-confirm-delete" data-confirm-title="Usuwanie szablonu" data-confirm-message="Czy na pewno chcesz usuńąć ten szablon SMS?">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= $templateId ?>">
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usun</button>
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usuń</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -60,8 +60,8 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<section class="card">
|
||||
<div class="order-details-head">
|
||||
<div>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>" class="order-back-link">← Powrot do zamowienia</a>
|
||||
<h2 class="section-title mt-12">Przygotuj przesylke #<?= $e((string) ($orderId ?? 0)) ?></h2>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>" class="order-back-link">← Powrót do zamówienia</a>
|
||||
<h2 class="section-title mt-12">Przygotuj przesyłkę #<?= $e((string) ($orderId ?? 0)) ?></h2>
|
||||
<div class="order-details-sub mt-4">
|
||||
<span><?= $e(ucfirst((string) ($orderRow['source'] ?? ''))) ?> <?= $e((string) ($orderRow['external_order_id'] ?? '')) ?></span>
|
||||
</div>
|
||||
@@ -103,7 +103,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
|
||||
<div class="shipment-grid mt-16">
|
||||
<section class="card">
|
||||
<h3 class="section-title">Przesylka</h3>
|
||||
<h3 class="section-title">Przesyłka</h3>
|
||||
|
||||
<?php if ($servicesError !== ''): ?>
|
||||
<div class="mt-12"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $servicesError, 'dismissible' => true]); ?></div>
|
||||
@@ -119,7 +119,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<option value="polkurier"<?= $preselectedCarrier === 'polkurier' ? ' selected' : '' ?>>polkurier</option>
|
||||
</select>
|
||||
<?php if ($deliveryMethodName !== ''): ?>
|
||||
<div class="muted mt-4" style="font-size:12px">Metoda z zamowienia: <strong><?= $e($deliveryMethodName) ?></strong><?php if ($mappedServiceName !== ''): ?> → <?= $e($mappedCarrierLabel) ?>: <?= $e($mappedServiceName) ?><?php endif; ?></div>
|
||||
<div class="muted mt-4" style="font-size:12px">Metoda z zamówienia: <strong><?= $e($deliveryMethodName) ?></strong><?php if ($mappedServiceName !== ''): ?> → <?= $e($mappedCarrierLabel) ?>: <?= $e($mappedServiceName) ?><?php endif; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($deliveryMappingDiagnostic !== ''): ?>
|
||||
<div class="mt-8"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $deliveryMappingDiagnostic, 'dismissible' => true]); ?></div>
|
||||
@@ -127,7 +127,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
</div>
|
||||
|
||||
<div class="form-field mt-12">
|
||||
<span class="field-label">Usluga dostawy</span>
|
||||
<span class="field-label">Usługa dostawy</span>
|
||||
<input type="hidden" name="delivery_method_id" id="shipment-delivery-service" value="" required>
|
||||
|
||||
<div id="shipment-allegro-panel" style="<?= $preselectedCarrier !== 'allegro' ? 'display:none' : '' ?>">
|
||||
@@ -135,7 +135,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<div class="mt-4"><?php $component('components/alert', ['type' => 'danger', 'message' => (string) $servicesError, 'dismissible' => true]); ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="searchable-select" id="shipment-service-wrapper" data-match-id="<?= $e($deliveryMethodId) ?>">
|
||||
<input type="text" class="form-control" id="shipment-service-search" placeholder="Szukaj uslugi dostawy Allegro..." autocomplete="off">
|
||||
<input type="text" class="form-control" id="shipment-service-search" placeholder="Szukaj usługi dostawy Allegro..." autocomplete="off">
|
||||
<div class="searchable-select__dropdown" id="shipment-service-dropdown">
|
||||
<?php foreach ($services as $svc): ?>
|
||||
<?php
|
||||
@@ -160,10 +160,10 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
|
||||
<div id="shipment-inpost-panel" style="<?= $preselectedCarrier !== 'inpost' ? 'display:none' : '' ?>">
|
||||
<?php if ($inpostSvcList === []): ?>
|
||||
<div class="muted">Brak uslug InPost (sprawdz konfiguracje InPost).</div>
|
||||
<div class="muted">Brak usług InPost (sprawdz konfiguracje InPost).</div>
|
||||
<?php else: ?>
|
||||
<select class="form-control" id="shipment-inpost-select">
|
||||
<option value="">-- Wybierz usluge InPost --</option>
|
||||
<option value="">-- Wybierz usługe InPost --</option>
|
||||
<?php foreach ($inpostSvcList as $inSvc): ?>
|
||||
<?php
|
||||
$inSvcId = is_array($inSvc['id'] ?? null) ? $inSvc['id'] : [];
|
||||
@@ -187,10 +187,10 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
|
||||
<div id="shipment-apaczka-panel" style="<?= $preselectedCarrier !== 'apaczka' ? 'display:none' : '' ?>">
|
||||
<?php if ($apaczkaSvcList === []): ?>
|
||||
<div class="muted">Brak uslug Apaczka (sprawdz konfiguracje App ID/App Secret).</div>
|
||||
<div class="muted">Brak usług Apaczka (sprawdz konfiguracje App ID/App Secret).</div>
|
||||
<?php else: ?>
|
||||
<select class="form-control" id="shipment-apaczka-select">
|
||||
<option value="">-- Wybierz usluge Apaczka --</option>
|
||||
<option value="">-- Wybierz usługe Apaczka --</option>
|
||||
<?php foreach ($apaczkaSvcList as $aSvc): ?>
|
||||
<?php
|
||||
if (!is_array($aSvc)) {
|
||||
@@ -221,16 +221,16 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<input type="checkbox" name="weekend_delivery" id="shipment-apaczka-weekend" value="1">
|
||||
Dostawa w weekend (sobota)
|
||||
</label>
|
||||
<div class="muted" style="font-size:12px">Dostepne dla paczkomatow InPost. Etykiety mozna generowac od czwartku 20:00 do piatku 18:00.</div>
|
||||
<div class="muted" style="font-size:12px">Dostępne dla paczkomatow InPost. Etykiety można generowac od czwartku 20:00 do piatku 18:00.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="shipment-polkurier-panel" style="<?= $preselectedCarrier !== 'polkurier' ? 'display:none' : '' ?>">
|
||||
<?php if ($polkurierSvcList === []): ?>
|
||||
<div class="muted">Brak uslug polkurier (sprawdz konfiguracje w Ustawienia → Integracje → polkurier).</div>
|
||||
<div class="muted">Brak usług polkurier (sprawdz konfiguracje w Ustawienia → Integracje → polkurier).</div>
|
||||
<?php else: ?>
|
||||
<select class="form-control" id="shipment-polkurier-select">
|
||||
<option value="">-- Wybierz usluge polkurier --</option>
|
||||
<option value="">-- Wybierz usługe polkurier --</option>
|
||||
<?php foreach ($polkurierSvcList as $pSvc): ?>
|
||||
<?php
|
||||
if (!is_array($pSvc)) {
|
||||
@@ -251,7 +251,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="muted mt-4" style="font-size:12px">Dla uslug paczkomatowych wpisz ID punktu w pole "Punkt odbioru" w sekcji Adres odbiorcy ponizej (np. <code>POP-RZE54</code>).</div>
|
||||
<div class="muted mt-4" style="font-size:12px">Dla usług paczkomatowych wpisz ID punktu w pole "Punkt odbioru" w sekcji Adres odbiorcy poniżej (np. <code>POP-RZE54</code>).</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -279,7 +279,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
|
||||
<div class="form-grid-4">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Dlugosc (cm)</span>
|
||||
<span class="field-label">Długość (cm)</span>
|
||||
<input class="form-control" type="number" name="length_cm" step="0.1" min="0.1" value="<?= $e((string) ($comp['default_package_length_cm'] ?? '25')) ?>">
|
||||
</label>
|
||||
<label class="form-field">
|
||||
@@ -329,7 +329,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<h3 class="section-title">Adres odbiorcy</h3>
|
||||
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label">Imie i nazwisko</span>
|
||||
<span class="field-label">Imię i nazwisko</span>
|
||||
<input class="form-control" type="text" name="receiver_name" maxlength="200" value="<?= $e((string) ($receiver['name'] ?? '')) ?>" required>
|
||||
</label>
|
||||
|
||||
@@ -384,11 +384,11 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
</div>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title">Pozycje zamowienia (<?= $e((string) count($itemsList)) ?>)</h3>
|
||||
<h3 class="section-title">Pozycje zamówienia (<?= $e((string) count($itemsList)) ?>)</h3>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table table--details">
|
||||
<thead>
|
||||
<tr><th>Lp.</th><th>Nazwa</th><th>Ilosc</th><th>Cena</th></tr>
|
||||
<tr><th>Lp.</th><th>Nazwa</th><th>Ilość</th><th>Cena</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($itemsList as $idx => $item): ?>
|
||||
@@ -404,7 +404,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary">Utworz przesylke</button>
|
||||
<button type="submit" class="btn btn--primary">Utworz przesyłkę</button>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>" class="btn btn--secondary">Anuluj</a>
|
||||
</div>
|
||||
</section>
|
||||
@@ -412,7 +412,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
|
||||
<?php if ($packages !== []): ?>
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title">Utworzone przesylki</h3>
|
||||
<h3 class="section-title">Utworzone przesyłki</h3>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table table--details">
|
||||
<thead>
|
||||
@@ -466,7 +466,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<td style="white-space:nowrap">
|
||||
<?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?><?php
|
||||
$pkgTrackUrl = DeliveryStatus::trackingUrl($pkgProvider, $pkgTracking, trim((string) ($pkg['carrier_id'] ?? '')));
|
||||
if ($pkgTrackUrl !== null): ?> <a href="<?= $e($pkgTrackUrl) ?>" target="_blank" class="tracking-link" title="Sledz przesylke">🔗</a><?php endif; ?>
|
||||
if ($pkgTrackUrl !== null): ?> <a href="<?= $e($pkgTrackUrl) ?>" target="_blank" class="tracking-link" title="Sledz przesyłkę">🔗</a><?php endif; ?>
|
||||
</td>
|
||||
<td><?= $e($pkgCarrier !== '' ? $pkgCarrier : '-') ?></td>
|
||||
<td>
|
||||
@@ -495,7 +495,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
class="btn btn--sm btn--secondary btn-print-label"
|
||||
data-package-id="<?= $e((string) $pkgId) ?>"
|
||||
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
|
||||
title="Wyslij do drukarki">Drukuj</button>
|
||||
title="Wyślij do drukarki">Drukuj</button>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
@@ -510,7 +510,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
data-check-status="<?= $e((string) $pkgId) ?>"
|
||||
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
|
||||
data-package-status="<?= $e($pkgStatus) ?>"
|
||||
data-auto-check="1">Sprawdz status</button>
|
||||
data-auto-check="1">Sprawdź status</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -851,26 +851,26 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
setTimeout(function () {
|
||||
checkPackageStatus(pkgId, oId, btn, attempt + 1);
|
||||
}, delayCreated);
|
||||
if (btn) btn.textContent = 'Generuje etykiete... (' + (attempt + 1) + ')';
|
||||
if (btn) btn.textContent = 'Generuje etykietę... (' + (attempt + 1) + ')';
|
||||
} else if (btn) {
|
||||
btn.textContent = 'W toku... Odswiez status';
|
||||
btn.textContent = 'W toku... Odśwież status';
|
||||
btn.disabled = false;
|
||||
}
|
||||
} else if (data.status === 'error') {
|
||||
if (btn) {
|
||||
btn.textContent = 'Blad: ' + (data.error || '');
|
||||
btn.textContent = 'Błąd: ' + (data.error || '');
|
||||
btn.disabled = false;
|
||||
}
|
||||
} else {
|
||||
if (btn) {
|
||||
btn.textContent = 'W toku... Sprobuj ponownie';
|
||||
btn.textContent = 'W toku... Spróbuj ponownie';
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (btn) {
|
||||
btn.textContent = 'Blad sieci';
|
||||
btn.textContent = 'Błąd sieci';
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
@@ -901,7 +901,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
btn.classList.remove('js-print-queue-pending');
|
||||
btn.classList.add('btn--secondary');
|
||||
btn.classList.add('btn-print-label');
|
||||
btn.setAttribute('title', 'Wyslij do drukarki');
|
||||
btn.setAttribute('title', 'Wyślij do drukarki');
|
||||
}
|
||||
|
||||
function stopPrintQueuePoll() {
|
||||
@@ -946,7 +946,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
if (!packageId) return;
|
||||
btn.disabled = true;
|
||||
var originalText = btn.innerHTML;
|
||||
btn.innerHTML = 'Wysylam...';
|
||||
btn.innerHTML = 'Wysyłam...';
|
||||
|
||||
fetch('/api/print/jobs', {
|
||||
method: 'POST',
|
||||
@@ -965,7 +965,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
btn.classList.add('js-print-queue-pending');
|
||||
watchPrintQueueButton(btn);
|
||||
} else {
|
||||
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany blad';
|
||||
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany błąd';
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.show({ message: msg, type: 'error' });
|
||||
}
|
||||
@@ -975,7 +975,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
})
|
||||
.catch(function () {
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.show({ message: 'Blad sieci — sprobuj ponownie.', type: 'error' });
|
||||
window.OrderProAlerts.show({ message: 'Błąd sieci — spróbuj ponownie.', type: 'error' });
|
||||
}
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
@@ -983,7 +983,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
});
|
||||
});
|
||||
|
||||
// Przy zaladowaniu strony: uruchom polling dla przyciskow juz w kolejce
|
||||
// Przy zaladowaniu strony: uruchom polling dla przyciskow już w kolejce
|
||||
document.querySelectorAll('.js-print-queue-pending').forEach(function (btn) {
|
||||
watchPrintQueueButton(btn);
|
||||
});
|
||||
@@ -1137,7 +1137,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
}
|
||||
|
||||
// --- Apply preset (autofill form) ---
|
||||
// Preset nadpisuje wylacznie rozmiary paczki i wage. Forma dostawy (carrier,
|
||||
// Preset nadpisuje wyłącznie rozmiary paczki i wage. Forma dostawy (carrier,
|
||||
// serwis, punkt nadania, format etykiety) pozostaje bez zmian.
|
||||
function applyPreset(preset) {
|
||||
setFieldValue('package_type', preset.package_type || 'PACKAGE');
|
||||
@@ -1342,10 +1342,10 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
function deletePreset(preset) {
|
||||
if (window.OrderProAlerts && window.OrderProAlerts.confirm) {
|
||||
window.OrderProAlerts.confirm({
|
||||
message: 'Usun\u0105\u0107 przycisk "' + preset.name + '"?',
|
||||
message: 'Usuń\u0105\u0107 przycisk "' + preset.name + '"?',
|
||||
onConfirm: function () { executeDelete(preset.id); }
|
||||
});
|
||||
} else if (confirm('Usun\u0105\u0107 przycisk "' + preset.name + '"?')) {
|
||||
} else if (confirm('Usuń\u0105\u0107 przycisk "' + preset.name + '"?')) {
|
||||
executeDelete(preset.id);
|
||||
}
|
||||
}
|
||||
|
||||
2
scripts/codex-export.cmd
Normal file
2
scripts/codex-export.cmd
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0codex-export.ps1" %*
|
||||
83
scripts/codex-export.ps1
Normal file
83
scripts/codex-export.ps1
Normal file
@@ -0,0 +1,83 @@
|
||||
param(
|
||||
[string]$OutputDir = "D:\notatnik-ai\codex",
|
||||
[string]$SourceCodexHome = (Join-Path $env:USERPROFILE ".codex")
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function New-CleanDirectory {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
|
||||
if (Test-Path -LiteralPath $Path) {
|
||||
Remove-Item -LiteralPath $Path -Recurse -Force
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||
}
|
||||
|
||||
function Copy-DirectoryContents {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Source,
|
||||
[Parameter(Mandatory = $true)][string]$Destination
|
||||
)
|
||||
|
||||
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
|
||||
Get-ChildItem -LiteralPath $Source -Force | ForEach-Object {
|
||||
Copy-Item -LiteralPath $_.FullName -Destination $Destination -Recurse -Force
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $SourceCodexHome)) {
|
||||
throw "Codex home not found: $SourceCodexHome"
|
||||
}
|
||||
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$archiveBaseName = "codex-backup-$timestamp"
|
||||
$stagingRoot = Join-Path $env:TEMP $archiveBaseName
|
||||
$payloadDir = Join-Path $stagingRoot "payload"
|
||||
$toolsDir = Join-Path $stagingRoot "tools"
|
||||
$sourceScriptsDir = $PSScriptRoot
|
||||
|
||||
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
||||
New-CleanDirectory -Path $stagingRoot
|
||||
New-Item -ItemType Directory -Path $payloadDir -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $toolsDir -Force | Out-Null
|
||||
|
||||
Copy-DirectoryContents -Source $SourceCodexHome -Destination (Join-Path $payloadDir ".codex")
|
||||
|
||||
$profileCodexFiles = Get-ChildItem -LiteralPath $env:USERPROFILE -Force -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -like ".codex*" -and $_.FullName -ne $SourceCodexHome }
|
||||
|
||||
foreach ($file in $profileCodexFiles) {
|
||||
Copy-Item -LiteralPath $file.FullName -Destination (Join-Path $payloadDir $file.Name) -Force
|
||||
}
|
||||
|
||||
Copy-Item -LiteralPath (Join-Path $sourceScriptsDir "codex-import.ps1") -Destination (Join-Path $toolsDir "codex-import.ps1") -Force
|
||||
Copy-Item -LiteralPath (Join-Path $sourceScriptsDir "codex-import.cmd") -Destination (Join-Path $toolsDir "codex-import.cmd") -Force
|
||||
|
||||
$files = Get-ChildItem -LiteralPath $payloadDir -Force -Recurse -File
|
||||
$manifest = [ordered]@{
|
||||
kind = "codex-local-backup"
|
||||
createdAt = (Get-Date).ToString("o")
|
||||
sourceCodexHome = $SourceCodexHome
|
||||
sourceUserProfile = $env:USERPROFILE
|
||||
payloadRoot = "payload"
|
||||
tools = @(
|
||||
"tools/codex-import.ps1",
|
||||
"tools/codex-import.cmd"
|
||||
)
|
||||
fileCount = @($files).Count
|
||||
totalBytes = [int64](($files | Measure-Object -Property Length -Sum).Sum)
|
||||
}
|
||||
|
||||
$manifest | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $stagingRoot "manifest.json") -Encoding UTF8
|
||||
|
||||
$zipPath = Join-Path $OutputDir "$archiveBaseName.zip"
|
||||
if (Test-Path -LiteralPath $zipPath) {
|
||||
Remove-Item -LiteralPath $zipPath -Force
|
||||
}
|
||||
|
||||
Compress-Archive -Path (Join-Path $stagingRoot "*") -DestinationPath $zipPath -Force
|
||||
Remove-Item -LiteralPath $stagingRoot -Recurse -Force
|
||||
|
||||
Write-Output $zipPath
|
||||
2
scripts/codex-import.cmd
Normal file
2
scripts/codex-import.cmd
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0codex-import.ps1" %*
|
||||
62
scripts/codex-import.ps1
Normal file
62
scripts/codex-import.ps1
Normal file
@@ -0,0 +1,62 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ArchivePath,
|
||||
[string]$TargetCodexHome = (Join-Path $env:USERPROFILE ".codex"),
|
||||
[switch]$NoBackup
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function New-CleanDirectory {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
|
||||
if (Test-Path -LiteralPath $Path) {
|
||||
Remove-Item -LiteralPath $Path -Recurse -Force
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ArchivePath)) {
|
||||
throw "Archive not found: $ArchivePath"
|
||||
}
|
||||
|
||||
$extractRoot = Join-Path $env:TEMP ("codex-restore-" + (Get-Date -Format "yyyyMMdd-HHmmss"))
|
||||
New-CleanDirectory -Path $extractRoot
|
||||
|
||||
try {
|
||||
Expand-Archive -LiteralPath $ArchivePath -DestinationPath $extractRoot -Force
|
||||
|
||||
$manifestPath = Join-Path $extractRoot "manifest.json"
|
||||
$payloadCodexHome = Join-Path $extractRoot "payload\.codex"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $manifestPath)) {
|
||||
throw "Invalid archive: manifest.json missing"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $payloadCodexHome)) {
|
||||
throw "Invalid archive: payload/.codex missing"
|
||||
}
|
||||
|
||||
if ((Test-Path -LiteralPath $TargetCodexHome) -and -not $NoBackup) {
|
||||
$backupPath = "$TargetCodexHome.before-import-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
|
||||
Move-Item -LiteralPath $TargetCodexHome -Destination $backupPath
|
||||
Write-Output "Existing Codex config moved to: $backupPath"
|
||||
} elseif (Test-Path -LiteralPath $TargetCodexHome) {
|
||||
Remove-Item -LiteralPath $TargetCodexHome -Recurse -Force
|
||||
}
|
||||
|
||||
Copy-Item -LiteralPath $payloadCodexHome -Destination $TargetCodexHome -Recurse -Force
|
||||
|
||||
$payloadRoot = Join-Path $extractRoot "payload"
|
||||
Get-ChildItem -LiteralPath $payloadRoot -Force -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -like ".codex*" } |
|
||||
ForEach-Object {
|
||||
Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $env:USERPROFILE $_.Name) -Force
|
||||
}
|
||||
|
||||
Write-Output "Codex config restored to: $TargetCodexHome"
|
||||
} finally {
|
||||
if (Test-Path -LiteralPath $extractRoot) {
|
||||
Remove-Item -LiteralPath $extractRoot -Recurse -Force
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ final class InvoiceController
|
||||
|
||||
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
|
||||
if ((int) ($order['invoice_requested'] ?? 0) !== 1) {
|
||||
Flash::set('order.error', 'Faktura nie zostala zazadana dla tego zamowienia.');
|
||||
Flash::set('order.error', 'Faktura nie zostala zazadana dla tego zamówienia.');
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ final class InvoiceController
|
||||
static fn (array $c): bool => (int) ($c['is_active'] ?? 0) === 1
|
||||
));
|
||||
if ($configs === []) {
|
||||
Flash::set('order.error', 'Brak aktywnych konfiguracji faktur. Skonfiguruj w Ustawienia > Ksiegowosc > Faktury.');
|
||||
Flash::set('order.error', 'Brak aktywnych konfiguracji faktur. Skonfiguruj w Ustawienia > Księgowość > Faktury.');
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ final class InvoiceController
|
||||
$existingInvoices = $this->invoices->findByOrderId($orderId);
|
||||
|
||||
$html = $this->template->render('accounting/invoice_form', [
|
||||
'title' => 'Wystaw fakture',
|
||||
'title' => 'Wystaw fakturę',
|
||||
'activeMenu' => 'orders',
|
||||
'activeOrders' => 'list',
|
||||
'user' => $this->auth->user(),
|
||||
@@ -123,7 +123,7 @@ final class InvoiceController
|
||||
$orderId = max(0, (int) $request->input('id', 0));
|
||||
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
Flash::set('order.error', 'Nieprawidlowy token CSRF.');
|
||||
Flash::set('order.error', 'Nieprawidłowy token CSRF.');
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ final class InvoiceController
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'invoice_issued',
|
||||
($result['mode'] === 'delegated' ? 'Wystawiono fakture (Fakturownia): ' : 'Wystawiono fakture: ') . $result['invoice_number'],
|
||||
($result['mode'] === 'delegated' ? 'Wystawiono fakturę (Fakturownia): ' : 'Wystawiono fakturę: ') . $result['invoice_number'],
|
||||
[
|
||||
'invoice_number' => $result['invoice_number'],
|
||||
'config_id' => $configId,
|
||||
@@ -170,7 +170,7 @@ final class InvoiceController
|
||||
} catch (InvoiceIssueException $e) {
|
||||
Flash::set('invoice.error', $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
Flash::set('invoice.error', 'Blad wystawiania faktury: ' . $e->getMessage());
|
||||
Flash::set('invoice.error', 'Błąd wystawiania faktury: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect('/orders/' . $orderId . '/invoice/create');
|
||||
|
||||
@@ -46,12 +46,12 @@ final class InvoiceService
|
||||
|
||||
$config = $this->invoiceConfigs->findById($configId);
|
||||
if ($config === null || (int) ($config['is_active'] ?? 0) !== 1) {
|
||||
throw new InvoiceIssueException('Nieprawidlowa lub nieaktywna konfiguracja faktury.');
|
||||
throw new InvoiceIssueException('Nieprawidłowa lub nieaktywna konfiguracja faktury.');
|
||||
}
|
||||
|
||||
$details = $this->orders->findDetails($orderId);
|
||||
if ($details === null) {
|
||||
throw new InvoiceIssueException('Zamowienie nie istnieje.');
|
||||
throw new InvoiceIssueException('Zamówienie nie istnieje.');
|
||||
}
|
||||
|
||||
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
|
||||
@@ -266,7 +266,7 @@ final class InvoiceService
|
||||
|
||||
$message = 'Fakturownia: ' . $e->getMessage();
|
||||
$this->invoices->markDelegatedExternalFailed($invoiceId, $message);
|
||||
throw new InvoiceIssueException($message . ' Sprobuj ponownie - orderPRO uzyje tego samego oid i najpierw sprawdzi Fakturownie.');
|
||||
throw new InvoiceIssueException($message . ' Spróbuj ponownie - orderPRO użyje tego samego oid i najpierw sprawdźi Fakturownie.');
|
||||
}
|
||||
|
||||
return $this->finalizeDelegatedInvoice($invoiceId, $response);
|
||||
@@ -539,7 +539,7 @@ final class InvoiceService
|
||||
$totalGross += $deliveryPrice;
|
||||
$totalNet += $deliveryNet;
|
||||
$itemsSnapshot[] = [
|
||||
'name' => 'Koszt wysylki',
|
||||
'name' => 'Koszt wysyłki',
|
||||
'quantity' => 1.0,
|
||||
'price_gross' => $deliveryPrice,
|
||||
'price_net' => $deliveryNet,
|
||||
@@ -583,12 +583,12 @@ final class InvoiceService
|
||||
$saleDay = substr($saleDate, 0, 10);
|
||||
$dueDay = substr($paymentDueDate, 0, 10);
|
||||
|
||||
// UWAGA: seller_* pola CELOWO pominiete. Konta Fakturowni z podwyzszonym
|
||||
// UWAGA: seller_* pola CELOWO pominięte. Konta Fakturowni z podwyzszonym
|
||||
// poziomem zabezpieczen interpretuja roznice w seller_name/tax_no/bank
|
||||
// jako proba "utworzenia nowego dzialu" i odrzucaja request HTTP 422
|
||||
// ("Poziom zabezpieczenia przed zmiana konta bankowego nie pozwala na
|
||||
// utworzenie dzialu"). Fakturownia uzywa wtedy danych sprzedawcy
|
||||
// zarejestrowanych na koncie (uzytkownik IS sprzedawca w Fakturowni).
|
||||
// zarejestrowanych na koncie (użytkownik IS sprzedawca w Fakturowni).
|
||||
// Lokalny snapshot `seller_data_json` w tabeli `invoices` zachowuje
|
||||
// dane orderPRO dla audytu — niezalezne od tego co poszlo do Fakturowni.
|
||||
$invoice = [
|
||||
@@ -623,10 +623,10 @@ final class InvoiceService
|
||||
|
||||
$descriptionReference = $orderReference !== '' ? $orderReference : $externalOid;
|
||||
if ($descriptionReference !== '') {
|
||||
$invoice['additional_info_desc'] = 'Zamowienie: ' . $descriptionReference;
|
||||
$invoice['additional_info_desc'] = 'Zamówienie: ' . $descriptionReference;
|
||||
}
|
||||
|
||||
// department_id celowo pominiete — konta Fakturowni z podwyzszonym
|
||||
// department_id celowo pominięte — konta Fakturowni z podwyzszonym
|
||||
// poziomem zabezpieczen odrzucaja ten parametr przez API (HTTP 422
|
||||
// "Poziom zabezpieczenia ... nie pozwala na utworzenie dzialu").
|
||||
// Fakturownia uzywa wtedy domyslnego dzialu konta.
|
||||
@@ -712,7 +712,7 @@ final class InvoiceService
|
||||
$externalPdfUrl = trim((string) ($remote['pdf_url'] ?? $remote['view_url'] ?? ''));
|
||||
|
||||
if ($externalId === '' || $externalNumber === '') {
|
||||
throw new InvoiceIssueException('Fakturownia zwrocila niekompletna odpowiedz (brak id/number).');
|
||||
throw new InvoiceIssueException('Fakturownia zwróciła niekompletną odpowiedź (brak id/number).');
|
||||
}
|
||||
|
||||
$this->invoices->finalizeDelegatedExternal($invoiceId, [
|
||||
|
||||
@@ -77,7 +77,7 @@ final class ReceiptController
|
||||
$orderId = max(0, (int) $request->input('id', 0));
|
||||
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
Flash::set('order.error', 'Nieprawidlowy token CSRF');
|
||||
Flash::set('order.error', 'Nieprawidłowy token CSRF');
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ final class ReceiptController
|
||||
} catch (ReceiptIssueException $e) {
|
||||
Flash::set('order.error', $e->getMessage());
|
||||
} catch (Throwable) {
|
||||
Flash::set('order.error', 'Blad wystawiania paragonu');
|
||||
Flash::set('order.error', 'Błąd wystawiania paragonu');
|
||||
}
|
||||
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
|
||||
@@ -36,12 +36,12 @@ final class ReceiptService
|
||||
|
||||
$config = $this->receiptConfigs->findById($configId);
|
||||
if ($config === null || (int) ($config['is_active'] ?? 0) !== 1) {
|
||||
throw new ReceiptIssueException('Nieprawidlowa lub nieaktywna konfiguracja paragonu');
|
||||
throw new ReceiptIssueException('Nieprawidłowa lub nieaktywna konfiguracja paragonu');
|
||||
}
|
||||
|
||||
$details = $this->orders->findDetails($orderId);
|
||||
if ($details === null) {
|
||||
throw new ReceiptIssueException('Zamowienie nie istnieje');
|
||||
throw new ReceiptIssueException('Zamówienie nie istnieje');
|
||||
}
|
||||
|
||||
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
|
||||
@@ -287,7 +287,7 @@ final class ReceiptService
|
||||
$totalGross += $deliveryPrice;
|
||||
$totalNet += $deliveryNet;
|
||||
$itemsSnapshot[] = [
|
||||
'name' => 'Koszt wysylki',
|
||||
'name' => 'Koszt wysyłki',
|
||||
'quantity' => 1.0,
|
||||
'price' => $deliveryPrice,
|
||||
'total' => round($deliveryPrice, 2),
|
||||
|
||||
@@ -29,9 +29,9 @@ final class AutomationController
|
||||
private const ALLOWED_RECEIPT_ISSUE_DATE_MODES = ['today', 'order_date', 'payment_date'];
|
||||
private const ALLOWED_RECEIPT_DUPLICATE_POLICIES = ['skip_if_exists', 'allow_duplicates'];
|
||||
private const PAYMENT_METHOD_OPTIONS = [
|
||||
'cod' => 'Platnosc przy odbiorze (COD)',
|
||||
'cod' => 'Płatność przy odbiorze (COD)',
|
||||
'transfer' => 'Przelew bankowy',
|
||||
'online' => 'Karta / platnosc online',
|
||||
'online' => 'Karta / płatność online',
|
||||
'other' => 'Inna',
|
||||
];
|
||||
public function __construct(
|
||||
@@ -120,7 +120,7 @@ final class AutomationController
|
||||
);
|
||||
Flash::set('settings.automation.success', 'Zadanie automatyczne zostalo utworzone');
|
||||
} catch (Throwable) {
|
||||
return $this->renderForm($this->buildRuleFromRequest($request), 'Blad zapisu zadania automatycznego');
|
||||
return $this->renderForm($this->buildRuleFromRequest($request), 'Błąd zapisu zadania automatycznego');
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/automation');
|
||||
@@ -135,7 +135,7 @@ final class AutomationController
|
||||
|
||||
$id = (int) $request->input('id', '0');
|
||||
if ($id <= 0) {
|
||||
Flash::set('settings.automation.error', 'Nieprawidlowy identyfikator');
|
||||
Flash::set('settings.automation.error', 'Nieprawidłowy identyfikator');
|
||||
return Response::redirect('/settings/automation');
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ final class AutomationController
|
||||
);
|
||||
Flash::set('settings.automation.success', 'Zadanie automatyczne zostalo zaktualizowane');
|
||||
} catch (Throwable) {
|
||||
return $this->renderForm($this->buildRuleFromRequest($request, $id), 'Blad aktualizacji zadania automatycznego');
|
||||
return $this->renderForm($this->buildRuleFromRequest($request, $id), 'Błąd aktualizacji zadania automatycznego');
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/automation');
|
||||
@@ -168,15 +168,15 @@ final class AutomationController
|
||||
|
||||
$id = (int) $request->input('id', '0');
|
||||
if ($id <= 0) {
|
||||
Flash::set('settings.automation.error', 'Nieprawidlowy identyfikator');
|
||||
Flash::set('settings.automation.error', 'Nieprawidłowy identyfikator');
|
||||
return Response::redirect('/settings/automation');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->repository->delete($id);
|
||||
Flash::set('settings.automation.success', 'Zadanie automatyczne zostalo usuniete');
|
||||
Flash::set('settings.automation.success', 'Zadanie automatyczne zostalo usuńięte');
|
||||
} catch (Throwable) {
|
||||
Flash::set('settings.automation.error', 'Blad usuwania zadania automatycznego');
|
||||
Flash::set('settings.automation.error', 'Błąd usuwania zadania automatycznego');
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/automation');
|
||||
@@ -191,7 +191,7 @@ final class AutomationController
|
||||
|
||||
$id = (int) $request->input('id', '0');
|
||||
if ($id <= 0) {
|
||||
Flash::set('settings.automation.error', 'Nieprawidlowy identyfikator');
|
||||
Flash::set('settings.automation.error', 'Nieprawidłowy identyfikator');
|
||||
return Response::redirect('/settings/automation');
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ final class AutomationController
|
||||
$this->repository->duplicate($id);
|
||||
Flash::set('settings.automation.success', 'Zadanie zostalo zduplikowane');
|
||||
} catch (Throwable) {
|
||||
Flash::set('settings.automation.error', 'Blad duplikowania zadania');
|
||||
Flash::set('settings.automation.error', 'Błąd duplikowania zadania');
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/automation');
|
||||
@@ -214,15 +214,15 @@ final class AutomationController
|
||||
|
||||
$id = (int) $request->input('id', '0');
|
||||
if ($id <= 0) {
|
||||
Flash::set('settings.automation.error', 'Nieprawidlowy identyfikator');
|
||||
Flash::set('settings.automation.error', 'Nieprawidłowy identyfikator');
|
||||
return Response::redirect('/settings/automation');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->repository->toggleActive($id);
|
||||
Flash::set('settings.automation.success', 'Status zadania zostal zmieniony');
|
||||
Flash::set('settings.automation.success', 'Status zadania zostal zmieńiony');
|
||||
} catch (Throwable) {
|
||||
Flash::set('settings.automation.error', 'Blad zmiany statusu');
|
||||
Flash::set('settings.automation.error', 'Błąd zmiany statusu');
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/automation');
|
||||
@@ -316,7 +316,7 @@ final class AutomationController
|
||||
private function validateCsrf(Request $request): ?Response
|
||||
{
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
Flash::set('settings.automation.error', 'Nieprawidlowy token CSRF');
|
||||
Flash::set('settings.automation.error', 'Nieprawidłowy token CSRF');
|
||||
return Response::redirect('/settings/automation');
|
||||
}
|
||||
|
||||
@@ -327,12 +327,12 @@ final class AutomationController
|
||||
{
|
||||
$name = trim((string) $request->input('name', ''));
|
||||
if ($name === '' || mb_strlen($name) > 128) {
|
||||
return 'Nazwa jest wymagana (maks. 128 znakow)';
|
||||
return 'Nazwa jest wymagana (maks. 128 znaków)';
|
||||
}
|
||||
|
||||
$eventType = (string) $request->input('event_type', '');
|
||||
if (!in_array($eventType, self::ALLOWED_EVENTS, true)) {
|
||||
return 'Nieprawidlowy typ zdarzenia';
|
||||
return 'Nieprawidłowy typ zdarzenia';
|
||||
}
|
||||
|
||||
$conditions = $this->extractConditions($request);
|
||||
|
||||
@@ -87,7 +87,7 @@ final class AutomationService
|
||||
$exception->getMessage(),
|
||||
$context
|
||||
);
|
||||
// Blad jednej reguly nie blokuje kolejnych
|
||||
// Błąd jednej reguly nie blokuje kolejnych
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,7 +428,7 @@ final class AutomationService
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'automation_receipt_skipped',
|
||||
$actorName . ' - pomieto, paragon juz istnieje dla zamowienia',
|
||||
$actorName . ' - pominięto, paragon już istnieje dla zamówienia',
|
||||
['duplicate_policy' => $duplicatePolicy, 'existing_count' => count($existingReceipts)],
|
||||
'system',
|
||||
$actorName
|
||||
@@ -477,7 +477,7 @@ final class AutomationService
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'automation_receipt_failed',
|
||||
$actorName . ' - blad wystawiania paragonu',
|
||||
$actorName . ' - błąd wystawiania paragonu',
|
||||
['error' => $exception->getMessage(), 'receipt_config_id' => $configId],
|
||||
'system',
|
||||
$actorName
|
||||
@@ -521,7 +521,7 @@ final class AutomationService
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'automation_shipment_status_updated',
|
||||
$actorName . ' - zaktualizowano status przesylki',
|
||||
$actorName . ' - zaktualizowano status przesyłki',
|
||||
[
|
||||
'package_id' => $packageId,
|
||||
'previous_status' => $previousStatus,
|
||||
@@ -572,7 +572,7 @@ final class AutomationService
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'automation_order_status_failed',
|
||||
$actorName . ' - nie udalo sie zmienic statusu zamowienia',
|
||||
$actorName . ' - nie udalo się zmienić statusu zamówienia',
|
||||
['target_status_code' => $statusCode],
|
||||
'system',
|
||||
$actorName
|
||||
@@ -764,7 +764,7 @@ final class AutomationService
|
||||
'executed_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
} catch (Throwable) {
|
||||
// Historia automatyzacji nie moze blokowac glownego flow.
|
||||
// Historia automatyzacji nie może blokowac głównego flow.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ final class OrderStatusAgedService
|
||||
try {
|
||||
$totalTriggered += $this->processRule($rule);
|
||||
} catch (Throwable) {
|
||||
// Blad jednej reguly nie blokuje kolejnych
|
||||
// Błąd jednej reguly nie blokuje kolejnych
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ final class OrderStatusAgedService
|
||||
]);
|
||||
$triggered++;
|
||||
} catch (Throwable) {
|
||||
// Blad jednego zamowienia nie blokuje kolejnych
|
||||
// Błąd jednego zamówienia nie blokuje kolejnych
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ final class AllegroTokenRefreshHandler
|
||||
{
|
||||
$credentials = $this->repository->getRefreshTokenCredentials();
|
||||
if ($credentials === null) {
|
||||
throw new RuntimeException('Brak kompletnych danych Allegro OAuth do odswiezenia tokenu.');
|
||||
throw new RuntimeException('Brak kompletnych danych Allegro OAuth do odświeżenia tokenu.');
|
||||
}
|
||||
|
||||
$token = $this->oauthClient->refreshAccessToken(
|
||||
|
||||
@@ -32,7 +32,7 @@ final class EmailSendingService
|
||||
{
|
||||
$details = $this->orders->findDetails($orderId);
|
||||
if ($details === null) {
|
||||
return ['success' => false, 'error' => 'Zamowienie nie znalezione', 'log_id' => 0];
|
||||
return ['success' => false, 'error' => 'Zamówienie nie znalezione', 'log_id' => 0];
|
||||
}
|
||||
|
||||
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
|
||||
@@ -103,7 +103,7 @@ final class EmailSendingService
|
||||
$templateName = (string) ($template['name'] ?? '');
|
||||
$activitySummary = $status === 'sent'
|
||||
? 'Wyslano e-mail "' . $resolvedSubject . '" do ' . $recipientEmail
|
||||
: 'Blad wysylki e-mail "' . $resolvedSubject . '" do ' . $recipientEmail . ': ' . ($errorMessage ?? '');
|
||||
: 'Błąd wysyłki e-mail "' . $resolvedSubject . '" do ' . $recipientEmail . ': ' . ($errorMessage ?? '');
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'email_' . $status,
|
||||
@@ -127,7 +127,7 @@ final class EmailSendingService
|
||||
{
|
||||
$details = $this->orders->findDetails($orderId);
|
||||
if ($details === null) {
|
||||
return ['subject' => '', 'body_html' => '<p>Zamowienie nie znalezione</p>', 'attachments' => []];
|
||||
return ['subject' => '', 'body_html' => '<p>Zamówienie nie znalezione</p>', 'attachments' => []];
|
||||
}
|
||||
|
||||
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
|
||||
|
||||
@@ -9,7 +9,7 @@ use RuntimeException;
|
||||
|
||||
/**
|
||||
* Phase 129-01: CRUD notatek autorskich operatora (note_type='user').
|
||||
* Importowane notatki ze zrodla (shoppro/allegro/message) maja wlasne zarzadzanie
|
||||
* Importowane notatki ze źródła (shoppro/allegro/message) mają własne zarządzanie
|
||||
* w OrderImportRepository::replaceNotes() — ten serwis ich nie dotyka.
|
||||
*/
|
||||
final class OrderNotesService
|
||||
@@ -96,7 +96,7 @@ final class OrderNotesService
|
||||
{
|
||||
$body = $this->sanitizeBody($body);
|
||||
if ($orderId <= 0 || $userId <= 0) {
|
||||
throw new InvalidArgumentException('Nieprawidlowe parametry notatki.');
|
||||
throw new InvalidArgumentException('Nieprawidłowe parametry notatki.');
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -117,13 +117,13 @@ final class OrderNotesService
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException kod 403 gdy uzytkownik nie jest autorem notatki
|
||||
* @throws RuntimeException kod 403 gdy użytkownik nie jest autorem notatki
|
||||
*/
|
||||
public function update(int $noteId, int $userId, string $body): void
|
||||
{
|
||||
$body = $this->sanitizeBody($body);
|
||||
if ($noteId <= 0 || $userId <= 0) {
|
||||
throw new InvalidArgumentException('Nieprawidlowe parametry notatki.');
|
||||
throw new InvalidArgumentException('Nieprawidłowe parametry notatki.');
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -139,17 +139,17 @@ final class OrderNotesService
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
throw new RuntimeException('Brak uprawnien — tylko autor moze edytowac notatke.', 403);
|
||||
throw new RuntimeException('Brak uprawnien — tylko autor może edytowac notatkę.', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException kod 403 gdy uzytkownik nie jest autorem notatki
|
||||
* @throws RuntimeException kod 403 gdy użytkownik nie jest autorem notatki
|
||||
*/
|
||||
public function delete(int $noteId, int $userId): void
|
||||
{
|
||||
if ($noteId <= 0 || $userId <= 0) {
|
||||
throw new InvalidArgumentException('Nieprawidlowe parametry notatki.');
|
||||
throw new InvalidArgumentException('Nieprawidłowe parametry notatki.');
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -163,7 +163,7 @@ final class OrderNotesService
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
throw new RuntimeException('Brak uprawnien — tylko autor moze usunac notatke.', 403);
|
||||
throw new RuntimeException('Brak uprawnien — tylko autor może usuńąć notatkę.', 403);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,10 +171,10 @@ final class OrderNotesService
|
||||
{
|
||||
$body = trim($body);
|
||||
if ($body === '') {
|
||||
throw new InvalidArgumentException('Tresc notatki nie moze byc pusta.');
|
||||
throw new InvalidArgumentException('Treść notatki nie może byc pusta.');
|
||||
}
|
||||
if (function_exists('mb_strlen') ? mb_strlen($body) > self::BODY_MAX_LENGTH : strlen($body) > self::BODY_MAX_LENGTH) {
|
||||
throw new InvalidArgumentException('Tresc notatki przekracza ' . self::BODY_MAX_LENGTH . ' znakow.');
|
||||
throw new InvalidArgumentException('Treść notatki przekracza ' . self::BODY_MAX_LENGTH . ' znaków.');
|
||||
}
|
||||
|
||||
return $body;
|
||||
|
||||
@@ -320,7 +320,7 @@ final class OrdersController
|
||||
}
|
||||
|
||||
if ($orderId <= 0 || $this->smsConversation === null) {
|
||||
Flash::set('order.error', 'Modul SMS nie jest dostepny.');
|
||||
Flash::set('order.error', 'Modul SMS nie jest dostępny.');
|
||||
return Response::redirect($redirectTo);
|
||||
}
|
||||
|
||||
@@ -335,12 +335,12 @@ final class OrdersController
|
||||
);
|
||||
|
||||
if ($result['ok']) {
|
||||
Flash::set('order.success', 'SMS zostal wyslany.');
|
||||
Flash::set('order.success', 'SMS zostal wysłany.');
|
||||
} else {
|
||||
Flash::set('order.error', 'Nie udalo sie wyslac SMS: ' . $result['message']);
|
||||
Flash::set('order.error', 'Nie udało się wysłać SMS: ' . $result['message']);
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('order.error', 'Nie udalo sie wyslac SMS: ' . $exception->getMessage());
|
||||
Flash::set('order.error', 'Nie udało się wysłać SMS: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect($redirectTo);
|
||||
@@ -352,10 +352,10 @@ final class OrdersController
|
||||
$templateId = max(0, (int) $request->input('template_id', 0));
|
||||
|
||||
if ($orderId <= 0 || $templateId <= 0) {
|
||||
return Response::json(['ok' => false, 'error' => 'Nieprawidlowe parametry.'], 400);
|
||||
return Response::json(['ok' => false, 'error' => 'Nieprawidłowe parametry.'], 400);
|
||||
}
|
||||
if ($this->smsTemplates === null || $this->smsVariableResolver === null) {
|
||||
return Response::json(['ok' => false, 'error' => 'Modul szablonow SMS nie jest dostepny.'], 500);
|
||||
return Response::json(['ok' => false, 'error' => 'Modul szablonow SMS nie jest dostępny.'], 500);
|
||||
}
|
||||
|
||||
$template = $this->smsTemplates->findById($templateId);
|
||||
@@ -365,7 +365,7 @@ final class OrdersController
|
||||
|
||||
$details = $this->orders->findDetails($orderId);
|
||||
if ($details === null) {
|
||||
return Response::json(['ok' => false, 'error' => 'Zamowienie nie znalezione.'], 404);
|
||||
return Response::json(['ok' => false, 'error' => 'Zamówienie nie znalezione.'], 404);
|
||||
}
|
||||
|
||||
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
|
||||
@@ -385,7 +385,7 @@ final class OrdersController
|
||||
}
|
||||
|
||||
/**
|
||||
* Sklada informacje o historii zwrotow klienta biezacego zamowienia.
|
||||
* Składa informacje o historii zwrotów klienta bieżącego zamówienia.
|
||||
*
|
||||
* @param array<string, mixed> $order
|
||||
* @return array{count:int, orders:array<int, array<string, mixed>>, email:string, phone:string, name:string, text:string}
|
||||
@@ -458,13 +458,13 @@ final class OrdersController
|
||||
} elseif ($hasPhone) {
|
||||
$subject = 'Osoba o numerze telefonu ' . $phone;
|
||||
} elseif ($hasName) {
|
||||
$subject = 'Osoba o imieniu i nazwisku ' . $name;
|
||||
$subject = 'Osoba o imięniu i nazwisku ' . $name;
|
||||
} else {
|
||||
$subject = 'Ten klient';
|
||||
}
|
||||
|
||||
$noun = $count === 1 ? 'przesylke' : 'przesylek';
|
||||
return $subject . ' nie odebrala ' . $count . ' ' . $noun . '.';
|
||||
$noun = $count === 1 ? 'przesyłkę' : 'przesyłek';
|
||||
return $subject . ' nie odebrała ' . $count . ' ' . $noun . '.';
|
||||
}
|
||||
|
||||
public function updateDetails(Request $request): Response
|
||||
@@ -622,7 +622,7 @@ final class OrdersController
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'invoice_requested_changed',
|
||||
'Klient prosi o fakture: ' . ($value ? 'tak' : 'nie'),
|
||||
'Klient prosi o fakturę: ' . ($value ? 'tak' : 'nie'),
|
||||
['invoice_requested' => $value ? 1 : 0],
|
||||
'user',
|
||||
$actorName !== '' ? $actorName : null
|
||||
@@ -641,7 +641,7 @@ final class OrdersController
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
if ($orderId <= 0 || $this->orderNotes === null) {
|
||||
Flash::set('order.error', 'Modul notatek nie jest dostepny.');
|
||||
Flash::set('order.error', 'Modul notatek nie jest dostępny.');
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
@@ -658,7 +658,7 @@ final class OrdersController
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'note',
|
||||
'Dodano notatke',
|
||||
'Dodano notatkę',
|
||||
null,
|
||||
'user',
|
||||
$authorName !== '' ? $authorName : null
|
||||
@@ -667,7 +667,7 @@ final class OrdersController
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
Flash::set('order.error', $exception->getMessage());
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('order.error', 'Nie udalo sie dodac notatki: ' . $exception->getMessage());
|
||||
Flash::set('order.error', 'Nie udało się dodać notatki: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect($redirectTo);
|
||||
@@ -684,7 +684,7 @@ final class OrdersController
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
if ($orderId <= 0 || $noteId <= 0 || $this->orderNotes === null) {
|
||||
Flash::set('order.error', 'Nieprawidlowe parametry.');
|
||||
Flash::set('order.error', 'Nieprawidłowe parametry.');
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
@@ -701,7 +701,7 @@ final class OrdersController
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'note',
|
||||
'Zaktualizowano notatke',
|
||||
'Zaktualizowano notatkę',
|
||||
['note_id' => $noteId],
|
||||
'user',
|
||||
$authorName !== '' ? $authorName : null
|
||||
@@ -712,7 +712,7 @@ final class OrdersController
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
Flash::set('order.error', $exception->getMessage());
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('order.error', 'Nie udalo sie zaktualizowac notatki: ' . $exception->getMessage());
|
||||
Flash::set('order.error', 'Nie udało się zaktualizować notatki: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect($redirectTo);
|
||||
@@ -729,7 +729,7 @@ final class OrdersController
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
if ($orderId <= 0 || $noteId <= 0 || $this->orderNotes === null) {
|
||||
Flash::set('order.error', 'Nieprawidlowe parametry.');
|
||||
Flash::set('order.error', 'Nieprawidłowe parametry.');
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
@@ -746,16 +746,16 @@ final class OrdersController
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'note',
|
||||
'Usunieto notatke',
|
||||
'Usuńięto notatkę',
|
||||
['note_id' => $noteId],
|
||||
'user',
|
||||
$authorName !== '' ? $authorName : null
|
||||
);
|
||||
Flash::set('order.success', 'Notatka usunieta.');
|
||||
Flash::set('order.success', 'Notatka usuńięta.');
|
||||
} catch (RuntimeException $exception) {
|
||||
Flash::set('order.error', $exception->getMessage());
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('order.error', 'Nie udalo sie usunac notatki: ' . $exception->getMessage());
|
||||
Flash::set('order.error', 'Nie udało się usuńąć notatki: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect($redirectTo);
|
||||
@@ -792,7 +792,7 @@ final class OrdersController
|
||||
$projectsTotal = max(0, (int) ($row['projects_total'] ?? 0));
|
||||
$returnedCount = max(0, (int) ($row['customer_returned_count'] ?? 0));
|
||||
$returnedBadge = $returnedCount >= 1
|
||||
? ' <span class="risk-return-badge" title="Klient nie odebral ' . $returnedCount . ' przesylek w historii">zwroty: ' . $returnedCount . '</span>'
|
||||
? ' <span class="risk-return-badge" title="Klient nie odebral ' . $returnedCount . ' przesyłek w historii">zwroty: ' . $returnedCount . '</span>'
|
||||
: '';
|
||||
|
||||
$notesCount = max(0, (int) ($row['notes_count'] ?? 0));
|
||||
@@ -1236,8 +1236,8 @@ final class OrdersController
|
||||
{
|
||||
return [
|
||||
'' => $this->translator->get('orders.filters.any'),
|
||||
'0' => 'nieoplacone',
|
||||
'1' => 'czesciowo oplacone',
|
||||
'0' => 'nieopłacone',
|
||||
'1' => 'częściowo opłacone',
|
||||
'2' => 'oplacone',
|
||||
'3' => 'zwrocone',
|
||||
];
|
||||
@@ -1290,12 +1290,12 @@ final class OrdersController
|
||||
{
|
||||
$orderId = max(0, (int) $request->input('id', 0));
|
||||
if ($orderId <= 0) {
|
||||
return Response::json(['success' => false, 'message' => 'Nieprawidlowe zamowienie'], 400);
|
||||
return Response::json(['success' => false, 'message' => 'Nieprawidłowe zamówienie'], 400);
|
||||
}
|
||||
|
||||
$csrfToken = (string) $request->input('_token', '');
|
||||
if (!Csrf::validate($csrfToken)) {
|
||||
return Response::json(['success' => false, 'message' => 'Sesja wygasla, odswiez strone'], 403);
|
||||
return Response::json(['success' => false, 'message' => 'Sesja wygasla, odśwież strone'], 403);
|
||||
}
|
||||
|
||||
if ($this->emailService === null) {
|
||||
@@ -1314,7 +1314,7 @@ final class OrdersController
|
||||
|
||||
return Response::json([
|
||||
'success' => $result['success'],
|
||||
'message' => $result['success'] ? 'E-mail wyslany pomyslnie' : ('Blad wysylki: ' . ($result['error'] ?? 'nieznany')),
|
||||
'message' => $result['success'] ? 'E-mail wysłany pomyslnie' : ('Błąd wysyłki: ' . ($result['error'] ?? 'nieznany')),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1461,7 +1461,7 @@ final class OrdersController
|
||||
$orderId = max(0, (int) $request->input('id', 0));
|
||||
$details = $this->orders->findDetails($orderId);
|
||||
if ($details === null) {
|
||||
return Response::html('<div class="order-preview-error">Zamowienie nie znalezione.</div>', 404);
|
||||
return Response::html('<div class="order-preview-error">Zamówienie nie znalezione.</div>', 404);
|
||||
}
|
||||
|
||||
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
|
||||
|
||||
@@ -599,8 +599,8 @@ final class OrdersRepository
|
||||
*/
|
||||
private function loadOrderNotes(int $orderId): array
|
||||
{
|
||||
// Phase 129-01: zwraca tylko notatki importowane ze zrodla (note_type != 'user').
|
||||
// Notatki autorskie operatora ladowane sa osobno przez OrderNotesService::listUserNotes().
|
||||
// Phase 129-01: zwraca tylko notatki importowane ze źródła (note_type != 'user').
|
||||
// Notatki autorskie operatora ladowane są osobno przez OrderNotesService::listUserNotes().
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM order_notes WHERE order_id = :order_id AND note_type <> "user" ORDER BY created_at_external DESC, id DESC');
|
||||
$stmt->execute(['order_id' => $orderId]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
@@ -682,10 +682,10 @@ final class OrdersRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Subquery zliczajaca zamowienia klienta biezacego wiersza, ktore w historii
|
||||
* mialy paczke z delivery_status='returned' (zwrot do nadawcy).
|
||||
* Subquery zliczająca zamówienia klienta bieżącego wiersza, które w historii
|
||||
* mialy paczkę z delivery_status='returned' (zwrot do nadawcy).
|
||||
* Matching po email LUB phone (tylko cyfry, min 6) LUB name — identyczne dopasowanie
|
||||
* po LOWER/TRIM. Wyklucza biezace zamowienie (self-exclusion).
|
||||
* po LOWER/TRIM. Wyklucza bieżące zamówienie (self-exclusion).
|
||||
*
|
||||
* Wymagania: MySQL 8.0+ (REGEXP_REPLACE).
|
||||
*
|
||||
@@ -714,7 +714,7 @@ final class OrdersRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Subquery liczby wszystkich notatek zamowienia, autorskich i importowanych.
|
||||
* Subquery liczby wszystkich notatek zamówienia, autorskich i importowanych.
|
||||
* Wspierane indeksem order_notes_order_idx (order_id).
|
||||
*/
|
||||
private function notesCountSubquerySql(string $orderAlias): string
|
||||
@@ -906,10 +906,10 @@ final class OrdersRepository
|
||||
|
||||
$summaryParts = [];
|
||||
if (isset($changed['delivery_method'])) {
|
||||
$summaryParts[] = 'forma dostawy';
|
||||
$summaryParts[] = 'formą dostawy';
|
||||
}
|
||||
if (isset($changed['payment_method']) || isset($changed['external_payment_type_id'])) {
|
||||
$summaryParts[] = 'forma płatności';
|
||||
$summaryParts[] = 'formą płatności';
|
||||
}
|
||||
$summary = 'Zmiana danych zamówienia: ' . implode(', ', $summaryParts);
|
||||
|
||||
|
||||
@@ -64,17 +64,17 @@ final class PrintApiController
|
||||
$this->lastLabelError = '';
|
||||
$labelPath = $this->ensureLabel($packageId, $package);
|
||||
if ($labelPath === '') {
|
||||
$msg = 'Etykieta niedostepna';
|
||||
$msg = 'Etykieta niedostępna';
|
||||
if ($this->lastLabelError !== '') {
|
||||
$msg .= ': ' . $this->lastLabelError;
|
||||
}
|
||||
$msg .= '. Kliknij najpierw "Pobierz" aby pobrac etykiete.';
|
||||
$msg .= '. Kliknij najpierw "Pobierz" aby pobrać etykietę.';
|
||||
return Response::json(['error' => $msg], 400);
|
||||
}
|
||||
|
||||
$existingPending = $this->printJobs->findPendingByPackageId($packageId);
|
||||
if ($existingPending !== null) {
|
||||
return Response::json(['error' => 'Zlecenie juz w kolejce'], 409);
|
||||
return Response::json(['error' => 'Zlecenie już w kolejce'], 409);
|
||||
}
|
||||
|
||||
$user = $this->auth->user();
|
||||
|
||||
@@ -15,7 +15,7 @@ final class AllegroApiClient
|
||||
{
|
||||
$safeId = rawurlencode(trim($checkoutFormId));
|
||||
if ($safeId === '') {
|
||||
throw new AllegroApiException('Brak ID zamowienia Allegro do pobrania.');
|
||||
throw new AllegroApiException('Brak ID zamówienia Allegro do pobrania.');
|
||||
}
|
||||
|
||||
$url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId;
|
||||
@@ -46,7 +46,7 @@ final class AllegroApiClient
|
||||
{
|
||||
$safeId = rawurlencode(trim($checkoutFormId));
|
||||
if ($safeId === '') {
|
||||
throw new AllegroApiException('Brak ID zamowienia Allegro do pobrania przesylek.');
|
||||
throw new AllegroApiException('Brak ID zamówienia Allegro do pobrania przesyłek.');
|
||||
}
|
||||
|
||||
$url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId . '/shipments';
|
||||
@@ -158,7 +158,7 @@ final class AllegroApiClient
|
||||
): array {
|
||||
$safeId = rawurlencode(trim($checkoutFormId));
|
||||
if ($safeId === '') {
|
||||
throw new AllegroApiException('Brak ID zamowienia Allegro do aktualizacji statusu.');
|
||||
throw new AllegroApiException('Brak ID zamówienia Allegro do aktualizacji statusu.');
|
||||
}
|
||||
|
||||
$normalizedStatus = strtoupper(trim($status));
|
||||
@@ -213,7 +213,7 @@ final class AllegroApiClient
|
||||
|
||||
$ch = curl_init($url);
|
||||
if ($ch === false) {
|
||||
throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
|
||||
throw new AllegroApiException('Nie udało się zainicjować połączenia z API Allegro.');
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, $this->withSslOptions([
|
||||
@@ -236,12 +236,12 @@ final class AllegroApiClient
|
||||
$ch = null;
|
||||
|
||||
if ($responseBody === false) {
|
||||
throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
|
||||
throw new AllegroApiException('Błąd połączenia z API Allegro: ' . $curlError);
|
||||
}
|
||||
|
||||
$json = json_decode((string) $responseBody, true);
|
||||
if (!is_array($json)) {
|
||||
throw new AllegroApiException('Nieprawidlowy JSON odpowiedzi API Allegro.');
|
||||
throw new AllegroApiException('Nieprawidłowy JSON odpowiedźi API Allegro.');
|
||||
}
|
||||
|
||||
if ($httpCode === 401) {
|
||||
@@ -261,7 +261,7 @@ final class AllegroApiClient
|
||||
$message = implode('; ', array_filter($parts));
|
||||
}
|
||||
if ($message === '') {
|
||||
$message = 'Blad API Allegro.';
|
||||
$message = 'Błąd API Allegro.';
|
||||
}
|
||||
throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
|
||||
}
|
||||
@@ -279,7 +279,7 @@ final class AllegroApiClient
|
||||
|
||||
$ch = curl_init($url);
|
||||
if ($ch === false) {
|
||||
throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
|
||||
throw new AllegroApiException('Nie udało się zainicjować połączenia z API Allegro.');
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, $this->withSslOptions([
|
||||
@@ -302,12 +302,12 @@ final class AllegroApiClient
|
||||
$ch = null;
|
||||
|
||||
if ($responseBody === false) {
|
||||
throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
|
||||
throw new AllegroApiException('Błąd połączenia z API Allegro: ' . $curlError);
|
||||
}
|
||||
|
||||
$json = json_decode((string) $responseBody, true);
|
||||
if (!is_array($json)) {
|
||||
throw new AllegroApiException('Nieprawidlowy JSON odpowiedzi API Allegro.');
|
||||
throw new AllegroApiException('Nieprawidłowy JSON odpowiedźi API Allegro.');
|
||||
}
|
||||
|
||||
if ($httpCode === 401) {
|
||||
@@ -327,7 +327,7 @@ final class AllegroApiClient
|
||||
$message = implode('; ', array_filter($parts));
|
||||
}
|
||||
if ($message === '') {
|
||||
$message = 'Blad API Allegro.';
|
||||
$message = 'Błąd API Allegro.';
|
||||
}
|
||||
throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
|
||||
}
|
||||
@@ -344,7 +344,7 @@ final class AllegroApiClient
|
||||
|
||||
$ch = curl_init($url);
|
||||
if ($ch === false) {
|
||||
throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
|
||||
throw new AllegroApiException('Nie udało się zainicjować połączenia z API Allegro.');
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, $this->withSslOptions([
|
||||
@@ -367,7 +367,7 @@ final class AllegroApiClient
|
||||
$ch = null;
|
||||
|
||||
if ($responseBody === false) {
|
||||
throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
|
||||
throw new AllegroApiException('Błąd połączenia z API Allegro: ' . $curlError);
|
||||
}
|
||||
|
||||
if ($httpCode === 401) {
|
||||
@@ -375,12 +375,12 @@ final class AllegroApiClient
|
||||
}
|
||||
|
||||
if ($httpCode === 204) {
|
||||
throw new AllegroApiException('Brak etykiety dla podanej przesylki.');
|
||||
throw new AllegroApiException('Brak etykiety dla podanej przesyłki.');
|
||||
}
|
||||
|
||||
if ($httpCode < 200 || $httpCode >= 300) {
|
||||
$json = json_decode((string) $responseBody, true);
|
||||
$message = is_array($json) ? trim((string) ($json['message'] ?? 'Blad API Allegro.')) : 'Blad API Allegro.';
|
||||
$message = is_array($json) ? trim((string) ($json['message'] ?? 'Błąd API Allegro.')) : 'Błąd API Allegro.';
|
||||
throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
|
||||
}
|
||||
|
||||
@@ -394,7 +394,7 @@ final class AllegroApiClient
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
if ($ch === false) {
|
||||
throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
|
||||
throw new AllegroApiException('Nie udało się zainicjować połączenia z API Allegro.');
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, $this->withSslOptions([
|
||||
@@ -415,12 +415,12 @@ final class AllegroApiClient
|
||||
$ch = null;
|
||||
|
||||
if ($responseBody === false) {
|
||||
throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
|
||||
throw new AllegroApiException('Błąd połączenia z API Allegro: ' . $curlError);
|
||||
}
|
||||
|
||||
$json = json_decode((string) $responseBody, true);
|
||||
if (!is_array($json)) {
|
||||
throw new AllegroApiException('Nieprawidlowy JSON odpowiedzi API Allegro.');
|
||||
throw new AllegroApiException('Nieprawidłowy JSON odpowiedźi API Allegro.');
|
||||
}
|
||||
|
||||
if ($httpCode === 401) {
|
||||
@@ -428,7 +428,7 @@ final class AllegroApiClient
|
||||
}
|
||||
|
||||
if ($httpCode < 200 || $httpCode >= 300) {
|
||||
$message = trim((string) ($json['message'] ?? 'Blad API Allegro.'));
|
||||
$message = trim((string) ($json['message'] ?? 'Błąd API Allegro.'));
|
||||
throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ final class AllegroOAuthClient
|
||||
$accessToken = trim((string) ($payload['access_token'] ?? ''));
|
||||
$refreshToken = trim((string) ($payload['refresh_token'] ?? ''));
|
||||
if ($accessToken === '' || $refreshToken === '') {
|
||||
throw new AllegroOAuthException('Allegro nie zwrocilo kompletu tokenow OAuth.');
|
||||
throw new AllegroOAuthException('Allegro nie zwróciło kompletu tokenow OAuth.');
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -97,7 +97,7 @@ final class AllegroOAuthClient
|
||||
|
||||
$accessToken = trim((string) ($payload['access_token'] ?? ''));
|
||||
if ($accessToken === '') {
|
||||
throw new AllegroOAuthException('Allegro nie zwrocilo access_token po odswiezeniu.');
|
||||
throw new AllegroOAuthException('Allegro nie zwróciło access_token po odświeżeniu.');
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -149,7 +149,7 @@ final class AllegroOAuthClient
|
||||
): array {
|
||||
$ch = curl_init($url);
|
||||
if ($ch === false) {
|
||||
throw new AllegroOAuthException('Nie udalo sie zainicjowac polaczenia OAuth z Allegro.');
|
||||
throw new AllegroOAuthException('Nie udało się zainicjować połączenia OAuth z Allegro.');
|
||||
}
|
||||
|
||||
$sslOpts = [
|
||||
@@ -179,17 +179,17 @@ final class AllegroOAuthClient
|
||||
$ch = null;
|
||||
|
||||
if ($responseBody === false) {
|
||||
throw new AllegroOAuthException('Blad polaczenia OAuth z Allegro: ' . $curlError);
|
||||
throw new AllegroOAuthException('Błąd połączenia OAuth z Allegro: ' . $curlError);
|
||||
}
|
||||
|
||||
$json = json_decode((string) $responseBody, true);
|
||||
if (!is_array($json)) {
|
||||
throw new AllegroOAuthException('Nieprawidlowy JSON odpowiedzi OAuth Allegro.');
|
||||
throw new AllegroOAuthException('Nieprawidłowy JSON odpowiedźi OAuth Allegro.');
|
||||
}
|
||||
|
||||
if ($httpCode < 200 || $httpCode >= 300) {
|
||||
$error = trim((string) ($json['error'] ?? 'oauth_error'));
|
||||
$description = trim((string) ($json['error_description'] ?? 'Brak szczegolow bledu OAuth.'));
|
||||
$description = trim((string) ($json['error_description'] ?? 'Brak szczegółów bledu OAuth.'));
|
||||
throw new AllegroOAuthException('OAuth Allegro [' . $error . ']: ' . $description);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ final class AllegroOrderImportService
|
||||
{
|
||||
private const IMPORT_TRIGGERS = [
|
||||
'manual_import' => 'Import reczny',
|
||||
'orders_sync' => 'Synchronizacja zamowien',
|
||||
'status_sync' => 'Synchronizacja statusow',
|
||||
'orders_sync' => 'Synchronizacja zamówień',
|
||||
'status_sync' => 'Synchronizacja statusów',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
@@ -39,7 +39,7 @@ final class AllegroOrderImportService
|
||||
{
|
||||
$orderId = trim($checkoutFormId);
|
||||
if ($orderId === '') {
|
||||
throw new AllegroApiException('Podaj ID zamowienia Allegro.');
|
||||
throw new AllegroApiException('Podaj ID zamówienia Allegro.');
|
||||
}
|
||||
$normalizedTrigger = $this->normalizeTrigger($trigger);
|
||||
$triggerLabel = self::IMPORT_TRIGGERS[$normalizedTrigger];
|
||||
@@ -74,8 +74,8 @@ final class AllegroOrderImportService
|
||||
if ($savedOrderId > 0) {
|
||||
$sourceUpdatedAt = trim((string) ($mapped['order']['source_updated_at'] ?? ''));
|
||||
$summary = $wasCreated
|
||||
? 'Zaimportowano zamowienie z Allegro'
|
||||
: 'Zaktualizowano zamowienie z Allegro (re-import)';
|
||||
? 'Zaimportowano zamówienie z Allegro'
|
||||
: 'Zaktualizowano zamówienie z Allegro (re-import)';
|
||||
$details = [
|
||||
'source' => IntegrationSources::ALLEGRO,
|
||||
'source_order_id' => trim($checkoutFormId),
|
||||
@@ -139,7 +139,7 @@ final class AllegroOrderImportService
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect "klient prosi o fakture" flag from Allegro checkout-form payload.
|
||||
* Detect "klient prosi o fakturę" flag from Allegro checkout-form payload.
|
||||
* Triggers on explicit `invoice.required`, business buyer (`naturalPerson=false`),
|
||||
* NIP in invoice address, or explicit company name.
|
||||
*
|
||||
@@ -186,7 +186,7 @@ final class AllegroOrderImportService
|
||||
{
|
||||
$checkoutFormId = trim((string) ($payload['id'] ?? ''));
|
||||
if ($checkoutFormId === '') {
|
||||
throw new AllegroApiException('Odpowiedz Allegro nie zawiera ID zamowienia.');
|
||||
throw new AllegroApiException('Odpowiedz Allegro nie zawiera ID zamówienia.');
|
||||
}
|
||||
|
||||
$status = trim((string) ($payload['status'] ?? ''));
|
||||
|
||||
@@ -115,7 +115,7 @@ final class AllegroStatusSyncService
|
||||
'pushed' => 0,
|
||||
'skipped' => 0,
|
||||
'failed' => 0,
|
||||
'message' => 'Brak mapowan statusow orderPRO -> Allegro.',
|
||||
'message' => 'Brak mapowań statusów orderPRO -> Allegro.',
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
@@ -131,7 +131,7 @@ final class AllegroStatusSyncService
|
||||
'pushed' => 0,
|
||||
'skipped' => 0,
|
||||
'failed' => 0,
|
||||
'message' => 'Brak zamowien do synchronizacji statusow.',
|
||||
'message' => 'Brak zamówień do synchronizacji statusów.',
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
@@ -304,7 +304,7 @@ final class AllegroStatusSyncService
|
||||
$statement = $this->pdo->prepare('UPDATE orders SET last_status_checked_at = NOW() WHERE id = ?');
|
||||
$statement->execute([$orderId]);
|
||||
} catch (Throwable) {
|
||||
// Blad zapisu znacznika nie powinien przerywac petli synchronizacji.
|
||||
// Błąd zapisu znacznika nie powinien przerywac petli synchronizacji.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ final class AllegroTokenManager
|
||||
{
|
||||
$oauth = $this->repository->getTokenCredentials();
|
||||
if ($oauth === null) {
|
||||
throw new AllegroOAuthException('Brak polaczenia OAuth Allegro. Polacz konto w Ustawieniach.');
|
||||
throw new AllegroOAuthException('Brak połączenia OAuth Allegro. Połącz konto w Ustawieniach.');
|
||||
}
|
||||
|
||||
$env = (string) ($oauth['environment'] ?? 'sandbox');
|
||||
@@ -89,7 +89,7 @@ final class AllegroTokenManager
|
||||
$updated = $this->repository->getTokenCredentials();
|
||||
$newToken = trim((string) ($updated['access_token'] ?? ''));
|
||||
if ($newToken === '') {
|
||||
throw new AllegroOAuthException('Nie udalo sie odswiezyc tokenu Allegro.');
|
||||
throw new AllegroOAuthException('Nie udało się odświeżyć tokenu Allegro.');
|
||||
}
|
||||
|
||||
return [$newToken, (string) ($updated['environment'] ?? 'sandbox')];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user