Files
orderPRO/.paul/codebase/architecture.md
Jacek Pyziak 782a291210 feat(112): re-import data protection — delta-only re-import + project_generated preservation
Phase 112 / Plan 112-01 complete (v3.6):
- OrderImportRepository::upsertOrderAggregate split into create vs re-import paths
- replaceAddresses/Items/Notes/Shipments/StatusHistory invoked only on first import
- new updateOrderDelta() narrows UPDATE to status_code (cond.), payment_status,
  total_paid, is_canceled_by_buyer, source_updated_at, payload_json, fetched_at
- source-side cancellation override (is_canceled_by_buyer=1 OR pull status_code='anulowane')
- identical-payload no-op guard via normalizePayloadJson()
- fixes case #882: order_items.id stable, project_generated (Phase 97) preserved
- Phase 111 payment.status_changed emit retained without regression

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:22:37 +02:00

11 KiB

Architecture

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 51+ Integration controllers, OAuth clients, API clients, mappers Allegro/shopPRO/Apaczka/InPost config, status mappings
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 → OrderImportServiceOrdersRepository::insertOrder()AutomationService::executeForNewOrder()
  2. Re-import (Phase 111 + 112)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/replaceStatusHistoryorder_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.
  3. Status updateOrdersController::updateStatus()OrdersRepository::updateStatus() → automation check
  4. Status sync — Cron → AllegroStatusSyncService / ShopproStatusSyncService → carrier API

Statistics Summary

  1. Request/statistics/summaryOrdersStatisticsController::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. AggregationOrdersStatisticsRepository::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. Renderresources/views/statistics/summary.php renders filters, chart JSON, two canvas targets, and table fallbacks.

Shipment Flow

  1. CreateShipmentController::create()ShipmentProviderRegistry → carrier ShipmentService::createShipment()ShipmentPackageRepository::insert()
  2. Track — Cron ShipmentTrackingHandlerShipmentTrackingRegistry → carrier tracking API → ShipmentPackageRepository::updateDeliveryStatus()

Receipt / Invoice

  1. GenerateReceiptController::store()ReceiptService::generateReceipt()ReceiptRepository::insert() + Dompdf PDF
  2. EmailEmailSendingService::send()VariableResolver::resolve()AttachmentGenerator::generatePdf() → PHPMailer SMTP

Automation Rules

  1. SetupAutomationControllerAutomationRepository::insertRule()
  2. TriggerAutomationService::executeForOrder() → evaluates trigger (order_status_changed, order_status_aged) → runs action (send email, update status)
  3. LogAutomationExecutionLogRepository tracks every run

Cron Jobs

Handler Task
AllegroOrdersImportHandler Fetch new Allegro orders
AllegroStatusSyncHandler Push status changes to Allegro
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:

$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