Files
orderPRO/.paul/codebase/tech_changelog.md
Jacek Pyziak 933dfcc67b feat(120): alert component unification
Phase 120 - Plan 01:
- Reusable PHP alert component (resources/views/components/alert.php)
  with inline SVG icon per type, optional dismiss button.
- Missing .alert--info SCSS variant added (#eff6ff/#bfdbfe/#1e3a8a) -
  fixes black-on-white alert after Fakturownia test connection.
- Flash::push(type, message) + Flash::all() with BC for set/get;
  legacy key heuristic (error/.save/warning -> typed entries).
- Central flash renderer in 3 layouts (app/auth/public) iterating
  Flash::all() through component (.alerts-stack wrap).
- Vanilla JS alert-dismiss.js with idempotent guard and delegated
  click handler on [data-alert-dismiss].
- 36 views migrated off inline <div class="alert alert--TYPE">;
  .flash--error/success removed from views (orders/show, shipments/prepare).
- SCSS rebuilt: public/assets/css/{app,login}.css.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:47:41 +02:00

30 KiB

Technical Changelog

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/getall() 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