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>
30 KiB
30 KiB
Technical Changelog
2026-05-12 - Phase 120 Plan 01: Alert Component Unification
Co zrobiono:
- Dodano komponent
resources/views/components/alert.php(params:$typeinfo|success|warning|danger,$message/$messageHtml,$dismissible,$role) renderujacy.alertz inline SVG ikona, body i opcjonalnym przyciskiem dismiss. - SCSS
_ui-components.scss:.alertzmieniony 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.jsz idempotent guardem i delegated handlerem na[data-alert-dismiss]. Flashrozszerzony opush(string $type, string $message): voidiall(): arrayz BC dlaset/get—all()konsumuje typowana kolejke + skanuje legacy_flashz heurystyka klucza (error/danger/fail → danger, warning → warning, success/.save/.created/.deleted/.toggled → success, reszta → info).- Layouty
app.php,auth.php,public.phpna poczatku content area iterujaFlash::all()przez komponent w.alerts-stacki ladjaalert-dismiss.js. - Migracja 36 widokow (34 z planu +
orders/show.php+shipments/prepare.php): inline<div class="alert alert--TYPE">zastapioneinclude components/alert.php;.flash--error/.flash--successw starych widokach takze przeniesione na komponent.
Dlaczego:
- Po teste polaczenia Fakturowni alert
OK (HTTP 200)mial klasealert--info, ale.alert--infonie 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 przyszleFlash::push()bez koniecznosci powtarzania kodu w widokach.
Boundaries (zachowane):
- Kontrolery NIE zmieniane —
Flash::set/getBC; wzorceFlash::set('module.key', '...')+ view local$flashXxxdzialaja jak dotychczas (widok renderuje przez komponent). - Modul
resources/modules/jquery-alerts(dialogiOrderProAlerts) niezmieniany — osobny system. email-mailboxes.phpJS-generowane alerty AJAX testu SMTP — pozostawione bez zmian (uzywaja.alertSCSS, 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.cssmusi 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_paidiis_canceled_by_buyersą dołączane do UPDATE warunkowo. Gdypayment_statusw bazie ==payment_statusz payloadu źródła,total_paidNIE jest aktualizowany.is_canceled_by_buyerjest pomijany w tej samej sytuacji, chyba że źródło flaguje anulowanie ($cancelledBySource=true) — wtedy zawsze wpisywane.upsertOrderAggregate()wylicza$paymentStatusUnchangedprzed wywołaniem delty i propaguje wraz z$cancelledBySource.- Test PHPUnit
tests/Unit/OrderImportRepositoryTest.phpz 3 scenariuszami (preserve / transition / cancel propagation). Uruchomienie odroczone —vendor/nieobecne w środowisku, składnia PHP zweryfikowana przezphp -l.
Dlaczego:
- Incydent zamówienia #976: operator usunął 2 pozycje Girlanda i zwrócił klientowi 28,00 PLN, obniżając
total_paidze 119,00 na 91,00. Audyt re-importu (Phase 112-01 delta-only) wykazał, że istniejąceupdateOrderDeltanadpisywałototal_paidz 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) istatusOverwriteAllowed(Phase 62) działają bez zmian.- Cancel propagation (
$cancelledBySourceoverride) z Phase 112-01 nadal wymuszastatus_code='anulowane'i — od Phase 119 —is_canceled_by_buyer=1nawet gdypayment_statusstabilne. - 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_statusstabilne).
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 delegowaneinvoice_configs.integration_idna jeden globalny rekord i usuwa nadmiarowe konta Fakturowni po przepieciu zaleznosci. - Przebudowano
FakturowniaIntegrationRepositoryna jedna globalna konfiguracje (getSettings,saveSettings,getIntegrationId,getCredentials) z kompatybilnymfindAll()zwracajacym jeden element. - Uproszczono
FakturowniaIntegrationControlleri widok/settings/integrations/fakturowniado pojedynczego formularza konfiguracji i testu polaczenia. - Hub integracji pokazuje Fakturownie jako jedna instancje, bez licznika kont.
- Zapis delegowanej konfiguracji faktury ustawia
invoice_configs.integration_idna 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.sqlz pojedyncza konfiguracjasmsplanet_integration_settingsi bazowym wpisemintegrationstypusmsplanet. - Dodano
SmsplanetIntegrationRepositoryz obsluga metod autoryzacjitokenorazkey_passwordi szyfrowaniem sekretow przezIntegrationSecretCipher. - Dodano
SmsplanetApiClientdla SMSPLANET (POST https://api2.smsplanet.pl/sms) z obsluga Bearer token orazkey+password. - Dodano
SmsplanetIntegrationControlleri trasy/settings/integrations/smsplanet,/save,/test. - Dodano widok
resources/views/settings/smsplanet.phpz 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 istniejacyApp\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.sqlz pojedyncza konfiguracjahostedsms_integration_settingsi bazowym wpisemintegrationstypuhostedsms. - Dodano
HostedSmsIntegrationRepositoryz szyfrowaniem hasla przezIntegrationSecretCipher. - Dodano
HostedSmsApiClientdla HostedSMS SimpleAPI (POST https://api.hostedsms.pl/SimpleApi). - Dodano
HostedSmsIntegrationControlleri trasy/settings/integrations/hostedsms,/save,/test. - Dodano widok
resources/views/settings/hostedsms.phpz 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 endpointGET /api/fakturownia/gus-lookup?nip=...&config_id=...— resolwer konta Fakturownia (preferujeinvoice_configs.integration_idgdy delegated, fallback na pierwsze aktywne konto type='fakturownia').invoice_form.phpma przycisk "Pobierz z GUS" obok pola NIP — vanilla JS fetch + auto-fillbuyer_company_name/street/postal_code/city. Czyni "wystaw fakture" jednoklikowym po wpisaniu NIP.InvoiceControllerctor rozszerzony o 2 deps (FakturowniaIntegrationRepository,FakturowniaApiClient). InvoiceRepository(nowy) — CRUD dlainvoices:findByOrderId,findById,insertLocal,insertDelegated,nextLocalNumber(atomowy INSERT ON DUPLICATE KEY UPDATE nainvoice_number_counters),paginatez filtrami search/config/mode/date.InvoiceService(nowy) — orchestrator wystawiania:issue()rozgalezia naissueLocal()(numer lokalny + Dompdf-ready snapshot) lubissueDelegated()(POST do Fakturowni PRZED INSERT lokalnym; na sukcesie zapisexternal_invoice_id/external_pdf_url/invoice_numberz odpowiedzi).- Static
extractBuyerTaxNumber()parsuje NIP z roznych sciezek payload_json (Allegroinvoice.address.taxId, shopPRObuyer.tax_numberi pokrewne). - Snapshot pattern (snapshoty seller/buyer/items z Phase 8 wzorca) + obliczanie netto/brutto z VAT per pozycja.
buildFakturowniaPayload()mapuje na formathttps://app.fakturownia.pl/api.
InvoiceIssueException(nowy) — typed exception dla bledow biznesowych.FakturowniaApiClientrozszerzony — nowecreateInvoice()(POST/invoices.jsonz body{api_token, invoice}, parsuje response naid/number/view_url/pdf_url/raw) ibuildPdfUrl()(helper bez fetcha, do redirect 302). Stary stubdownloadPdf()zastapiony.InvoiceController(nowy) — endpointy dla zamowienia (create,store,show,pdf) i listaissuedList. PDF: lokalna -> Dompdf inline (mirrorReceiptController::pdf); delegowana -> redirect 302 naexternal_pdf_url. Walidacjainvoice_requested=1przed otwarciem formularza.OrdersController::toggleInvoiceRequested(nowy) — AJAX endpoint POST/orders/{id}/invoice-requested/togglez CSRF +recordActivity('invoice_requested_changed').OrdersRepository::setInvoiceRequested(int, bool)(nowy) — UPDATEorders.invoice_requested.OrdersController::show()rozszerzone — przekazujeinvoices+invoiceConfigsdo widoku przez 2 nowe optional ctor params (InvoiceRepository,InvoiceConfigRepository).AllegroOrderImportService::importSingleOrder— przywasCreated=trueipayload.invoice.required-> ustawiaorders.invoice_requested=1(delta-only re-import nie nadpisuje manualnej flagi).ShopproOrdersSyncService::shouldRequestInvoice()(nowy) — flexible parser sprawdzajacywants_invoice,invoice_required,invoice.required,buyer.wants_invoice,buyer.invoicew 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 uzywaOrderProAlerts.confirmz 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 dostaldata-invoice-button-wrapz przyciskiem "Wystaw fakture" (visible tylko gdyinvoice_requested=1); pod headerem checkboxdata-invoice-requested-toggle(auto-bound przezinvoice-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_tokenprzychange, rollback checkbox state przy bledzie, optimistic show/hidedata-invoice-button-wrap. Idempotent guarddata-bound.resources/views/layouts/app.php— rejestracjainvoice-requested-toggle.jsz 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 podAuthMiddleware. - DI wiring w
routes/web.php:InvoiceRepository,InvoiceService,InvoiceController.OrdersControllerinstantiation 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
invoicesgdy API padnie. Trade-off: brak idempotencji przy double-POST -> notatka INVOICE-IDEMP-115. - Auto-set
invoice_requestedz importu pozwala operatorowi nie myslec o flagi dla typowych przypadkow (Allegroinvoice.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=0config 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) iinvoice-requested-toggle.js(Phase 115) — dwa rozne moduly, oba wlayouts/app.php. Nazwa "invoice-*" ale rozne kontekstowo (jeden formularz config, drugi zamowienie).OrderProAlerts.confirmwinvoice_form.phpuzywa options-object API zgodnie z memory (Phase 114 fix).- Migracje no-op nie potrzebne — Phase 113 dostarczyla
orders.invoice_requested,invoice_*tabele ifakturownia_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 seedDomyslny VATconfig (formatFV/%N/%M/%Y, monthly, lokalna numeracja, payment_to_days=7) przezNOT EXISTSguard. InvoiceConfigRepository- pelne CRUDinvoice_configsz walidacja serwerowa wszystkich pol + krytyczna regula delegacji:is_delegated=1wymagaintegration_idzintegrations.type='fakturownia'.delete()pre-checkujeinvoiceszeby zwrocic PL komunikat zamiast SQLSTATE.InvoiceConfigController- index/edit/save/toggle/delete dla/settings/accounting/invoices. Flashaccounting.invoices.save/.error. Edycja na osobnej podstronie.ReceiptConfigControllerrefactor - rozdzielenieindex()nahub()+list()+ nowaedit(). 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 conditionalintegration_idselect.
invoice-config-form.js- vanilla JS toggle dlais_delegatedcheckbox -> show/hideintegration_idselect wrapper + dynamicznyrequired.layouts/app.php- rejestracja nowego modulu JSinvoice-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/accountingto 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+ indexidx_orders_invoice_requested.20260510_000106_seed_fakturownia_integration_type.sql- no-op placeholder dokumentujacy uznanieintegrations.type='fakturownia'jako oficjalnie wspieranego.
FakturowniaIntegrationRepository- CRUD kont Fakturowni z resolved encryption (integrations.api_key_encryptedjako zrodlo prawdy,settings.api_token_encryptedjako cache).findAll/findByIntegrationId/save/delete/getDecryptedToken.FakturowniaApiClient::testConnection()- GEThttps://{prefix}.fakturownia.pl/account.json?api_token=...z cURL +SslCertificateResolver.createInvoice/downloadPdfjako STUB-y rzucajaceRuntimeException(do implementacji w kolejnym planie).IntegrationsRepository::updateTestResult()- nowa publiczna metoda do zapisulast_test_status / last_test_http_code / last_test_message / last_test_at. Wykorzystywana przezFakturowniaIntegrationController::test().FakturowniaIntegrationController- lista (/settings/integrations/fakturownia), edycja (/edit,/new), save, test, delete. CSRF via_token, flashfakturownia.save/.test/.error.- Widoki
resources/views/settings/fakturownia.php(lista z badge'ami) iresources/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/integrationsz 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+ kolumnaorders.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_requestedz 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_delegatedflag winvoice_configsumozliwia w przyszlym planie dwa tryby: lokalna numeracja+PDF dompdf (default) lub delegacja do Fakturowni (numer+PDF z API).- STUB-y
createInvoice/downloadPdfcelowo 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.
IntegrationsHubControllerma nowy parametr konstruktora (FakturowniaIntegrationRepository) - wszystkie miejsca wywolania zaktualizowane.
2026-05-07 - Phase 112 Plan 01: Re-import Data Protection
Co zrobiono:
OrderImportRepository::upsertOrderAggregate- rozdzielono sciezkecreated(pierwszy import) odelse(re-import).replaceAddresses,replaceItems,replaceNotes,replacePayments,replaceShipments,replaceStatusHistorywywolywane sa teraz wylacznie przy pierwszym imporcie. LogikapaymentTransition/statusOverwriteAllowed(Phase 111) i preservacjastatus_code(Phase 62) bez zmian.OrderImportRepository::updateOrderDelta()- nowa prywatna metoda zastepujacaupdateOrder(). Aktualizuje wylaczniestatus_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=1ze zrodla LUB zmapowany pullstatus_code='anulowane'(Phase 75/83), wymuszone jestorders.status_code='anulowane'niezaleznie odstatusOverwriteAllowed. - Identical-payload guard: porownanie znormalizowanego
payload_json(nowy vs aktualny w DB). Identyczny payload + brakpaymentTransition/statusOverwriteAllowed/cancelledBySource-> commit transakcji bez wywolywania UPDATE;fetched_atiupdated_atpozostaja niezmienione. OrderImportRepository::getCurrentOrderState()- rozszerzeniegetCurrentStatusAndPaymentStatus()o kolumnepayload_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 naorder_itemsprzy kazdym re-imporcie, co (a) zerowalo flagiproject_generated/project_generated_at(Phase 97) na default 0/NULL i (b) zmienialoorder_items.id, co lamie referencje skryptu batchtools/generowanie/_batch_run.sh(UPDATE ... WHERE id IN (...)). - Phase 111 dodala emisje
payment.status_changedprzy 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_atprzy cyklicznym imporcie tych samych zamowien.
BREAKING:
- Brak zmian breaking dla istniejacego API/UI/automatyzacji. Wewnetrznie usunieto
updateOrder()igetCurrentStatusAndPaymentStatus()(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 detekcjapayment_transition. Teraz porownuje poprzednipayment_statusz nowym (warunek0/1 -> 2) zamiast polegac wylacznie nastatus_code='nieoplacone'. Logika preservacji status_code z Phase 62 (statusOverwriteAllowed) zostala wydzielona jako osobna decyzja.OrderImportRepository::getCurrentStatusAndPaymentStatus()- nowa metoda pomocnicza zastepujacagetCurrentStatus(), zwraca i status_code, i payment_status w jednym SELECT.AllegroOrderImportService::importSingleOrder- dodaje emitpayment.status_changedgdypayment_transition && !$wasCreated.ShopproOrdersSyncService::importOneOrder- analogiczny emitpayment.status_changed.bin/backfill_payment_transition_111.php- jednorazowy CLI dla zamowien zpayment_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.importedjest 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 wShopproOrdersSyncService(flagapayment_transitionbyla wykrywana, ale nie emitowala eventu). - Regula automatyzacji #7 (
payment.status_changed->update_order_statusnaw_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 ipayment_transition=false. Brak duplikatow eventow.
2026-04-28 - Phase 110 Plan 01: Statistics Summary
Co zrobiono:
/statistics/summary- nowy widok podsumowania w menuStatystyki -> 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_groupsiorder_statuses, wiec migracja DB nie jest potrzebna. - Seria
Razemjest 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 przezfilemtime().resources/views/statistics/orders.php- filtrychannels[]istatus_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_statusi akcjiupdate_shipment_statusładuje statusy z DB przezDeliveryStatus::getAllOptions() - Walidacja w
parseConditionValue()iparseActionConfig()używaDeliveryStatus::getAllStatuses() AutomationService— usunięto stałąSHIPMENT_STATUS_OPTION_MAP; ewaluacjaevaluateShipmentStatusCondition()porównuje klucze bezpośrednioresolveStatusFromActionKey()— 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-statusesi jest on od razu dostępny w dropdownach automatyzacji bez deploymentu - Eliminacja kolizji semantycznej: stary klucz grupowy
picked_upmapował nadelivered(paczka odebrana przez klienta), nowy klucz DBpicked_upto "Odebrana przez kuriera" (od nadawcy) - BREAKING: stare reguły z grupowymi kluczami (
registered,courier_pickup,dropped_at_point,unclaimed,picked_up_return, orazpicked_up/ready_for_pickup/cancelledw 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_statusesz seedem 11 statusów (migracja20260427_000103) DeliveryStatusRepository— CRUD + per-request cacheDeliveryStatus.php— dynamiczne ładowanie statusów z DB (setRepository())- Panel
/settings/delivery-statusesz 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-colordla 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