# Architecture **Analysis Date:** 2026-03-12 --- ## 1. Directory Structure ``` orderPRO/ # Project root ├── bin/ # CLI scripts (cron, migrations, data tools) ├── bootstrap/ │ └── app.php # Application bootstrap: autoload, env, config, DI wiring ├── config/ │ ├── app.php # Application config (name, debug, locale, paths, cron, secrets) │ └── database.php # DB connection params ├── database/ │ ├── migrations/ # SQL migration files (47 files, named YYYYMMDD_NNNNNN_description.sql) │ └── drafts/ # Non-active schema drafts ├── DOCS/ │ ├── ARCHITECTURE.md # Existing architecture notes (Polish, incremental) │ ├── DB_SCHEMA.md # Table-level schema reference │ ├── TECH_CHANGELOG.md # Chronological technical changelog │ └── todo.md # Work backlog ├── public/ │ ├── index.php # Single HTTP entry point │ └── assets/ # Compiled CSS, JS, images (served directly) ├── resources/ │ ├── lang/ # Translation files (PL locale) │ ├── modules/ # Frontend JS/CSS modules (e.g. jquery-alerts) │ ├── scss/ # SCSS source → compiled to public/assets/css/ │ └── views/ # PHP template files (plain PHP, no engine) ├── routes/ │ └── web.php # All route definitions (returns closure) ├── src/ │ ├── Core/ # Framework kernel (router, DB, HTTP, security, i18n, views) │ └── Modules/ # Feature modules (Auth, Orders, Settings, Cron, Shipments, Users) ├── storage/ │ ├── cache/ # File cache │ ├── logs/ # app.log │ ├── sessions/ # PHP session files │ └── tmp/ # Temp files (e.g. downloaded labels) └── tests/ └── Unit/ # Unit tests (currently empty) ``` --- ## 2. Application Layers ### Layer 1 — Kernel (`src/Core/`) Framework infrastructure. No business logic. | Class | File | Responsibility | |---|---|---| | `Application` | `src/Core/Application.php` | Root service locator. Wires all dependencies, boots session, registers error handlers, runs cron on web. | | `Router` | `src/Core/Routing/Router.php` | Pattern-matching router for GET/POST. Supports `{param}` segments and middleware pipeline. | | `Request` | `src/Core/Http/Request.php` | Immutable wrapper around `$_GET`, `$_POST`, `$_FILES`, `$_SERVER`. | | `Response` | `src/Core/Http/Response.php` | Fluent response builder (`html()`, `json()`, `redirect()`). | | `Template` | `src/Core/View/Template.php` | Output-buffer PHP view renderer. Injects `$e()` (XSS escape) and `$t()` (translation) helpers. Supports layout wrapping. | | `ConnectionFactory` | `src/Core/Database/ConnectionFactory.php` | Creates PDO connection from config array. | | `Migrator` | `src/Core/Database/Migrator.php` | Applies pending `.sql` files from `database/migrations/`. Uses DB lock (`GET_LOCK`) to prevent concurrent runs. | | `Csrf` | `src/Core/Security/Csrf.php` | Session-based CSRF token generate/validate using `hash_equals`. | | `Session` | `src/Core/Support/Session.php` | Thin wrapper around PHP sessions. | | `Flash` | `src/Core/Support/Flash.php` | Single-request flash messages stored in `$_SESSION`. | | `Logger` | `src/Core/Support/Logger.php` | Appends `[LEVEL] message {context_json}` lines to `storage/logs/app.log`. | | `Env` | `src/Core/Support/Env.php` | Parses `.env` file; `get()`, `bool()` helpers. | | `Translator` | `src/Core/I18n/Translator.php` | Loads language files from `resources/lang/`; `get($key, $replace)`. | ### Layer 2 — Modules (`src/Modules/`) Feature slices. Each module owns its controllers, repositories and services. | Module | Path | Active Modules | |---|---|---| | Auth | `src/Modules/Auth/` | Login/logout, session-based auth | | Orders | `src/Modules/Orders/` | Order list/detail, status change, import | | Settings | `src/Modules/Settings/` | Statuses, integrations (Allegro/Apaczka/InPost/shopPRO), DB, cron, company | | Cron | `src/Modules/Cron/` | Job scheduling, execution, handlers per job type | | Shipments | `src/Modules/Shipments/` | Provider-agnostic shipment creation, label download | | Users | `src/Modules/Users/` | User management (list, create) | ### Layer 3 — Views (`resources/views/`) Plain PHP templates. No Blade, no Twig. | Path | Renders for | |---|---| | `layouts/app.php` | Main authenticated shell (sidebar nav) | | `layouts/auth.php` | Login page shell | | `orders/list.php` | Order list page | | `orders/show.php` | Order detail page | | `settings/allegro.php` | Allegro integration settings | | `settings/apaczka.php` | Apaczka integration settings | | `settings/inpost.php` | InPost integration settings | | `settings/shoppro.php` | shopPRO integration settings (multi-instance) | | `settings/statuses.php` | Status groups & statuses management | | `settings/cron.php` | Cron scheduler UI | | `settings/integrations.php` | Integrations hub | | `settings/database.php` | Migrations UI | | `settings/company.php` | Company/sender settings | | `shipments/prepare.php` | Shipment creation form | | `users/index.php` | Users list | | `auth/login.php` | Login form | | `components/table-list.php` | Reusable paginated table component | | `components/order-status-panel.php` | Reusable order status panel | --- ## 3. Bootstrap and Request Flow ### Bootstrap sequence ``` public/index.php └─ bootstrap/app.php ├─ autoload (vendor/autoload.php or spl_autoload from src/) ├─ Env::load('.env') ├─ load config/app.php + config/database.php ├─ new Application($basePath, $config) │ ├─ new Router │ ├─ new Translator │ ├─ new Template │ ├─ ConnectionFactory::make → PDO │ ├─ new UserRepository, OrdersRepository, OrderStatusRepository │ ├─ new Migrator │ └─ new AuthService, Logger └─ $app->boot() ├─ prepareDirectories (storage/*) ├─ configureSession ├─ registerErrorHandlers └─ require routes/web.php → $routes($app) (instantiates all controllers, repositories, services and registers routes) ``` ### Per-request flow ``` HTTP request → public/index.php → $app->run() ├─ Request::capture() (wraps $_GET/$_POST/$_FILES/$_SERVER) ├─ maybeRunCronOnWeb() (if cron_run_on_web=1, throttled, DB-locked) └─ Router::dispatch($request) ├─ find route by method + path (exact match first, then {param} patterns) ├─ attach URL params to $request via withAttributes() ├─ build middleware pipeline (array_reduce, reversed) │ └─ AuthMiddleware::handle() (most routes) │ └─ redirect /login if not authenticated └─ call controller method($request): Response ├─ reads input via $request->input() ├─ validates CSRF via Csrf::validate() ├─ calls repository/service methods └─ returns Response::html($template->render(...)) or Response::redirect(...) or Response::json(...) → Response::send() (sets headers, echoes body) ``` --- ## 4. Module Organization ### Auth (`src/Modules/Auth/`) | Class | Responsibility | |---|---| | `AuthController` | `GET /login`, `POST /login`, `POST /logout` | | `AuthService` | `check()`, `user()`, `attempt(email, password)`, `logout()` backed by session | | `AuthMiddleware` | `handle(Request, callable $next)` — redirects to `/login` if session unauthenticated | ### Orders (`src/Modules/Orders/`) | Class | Responsibility | |---|---| | `OrdersController` | `index` (list), `show` (detail), `updateStatus` — builds view data, delegates DB to repos | | `OrdersRepository` | `paginate()`, `findDetails()`, `statusCounts()`, `statusPanelConfig()`, `sourceOptions()`, `updateOrderStatus()`, `recordActivity()` | | `OrderImportRepository` | `upsertOrderAggregate()` — transactional upsert of the full order aggregate (order + addresses + items + payments + shipments + notes + status history) | ### Settings (`src/Modules/Settings/`) Large module covering all configuration: **Controllers:** - `SettingsController` — statuses, status groups, DB migrations - `AllegroIntegrationController` — Allegro OAuth2, import, status mappings, delivery mappings - `ApaczkaIntegrationController` — Apaczka API config - `InpostIntegrationController` — InPost ShipX config - `ShopproIntegrationsController` — shopPRO multi-instance management (statuses, delivery, sync) - `IntegrationsHubController` — overview hub listing all provider instances - `CronSettingsController` — cron schedule UI - `CompanySettingsController` — company/sender details **Repositories:** - `OrderStatusRepository` — CRUD + reorder for `order_status_groups` and `order_statuses` - `AllegroIntegrationRepository` — reads/writes `allegro_integration_settings`; encrypts secrets via `IntegrationSecretCipher` - `AllegroStatusMappingRepository` — `allegro_order_status_mappings` CRUD - `AllegroOrderSyncStateRepository` — cursor state for Allegro sync (`integration_order_sync_state`) - `ApaczkaIntegrationRepository` — reads/writes `apaczka_integration_settings` - `InpostIntegrationRepository` — reads/writes `inpost_integration_settings` - `IntegrationsRepository` — base `integrations` table queries (hub listing) - `ShopproIntegrationsRepository` — multi-instance CRUD for `integrations` where `type=shoppro` - `ShopproStatusMappingRepository` — `order_status_mappings` per shopPRO integration - `ShopproDeliveryMethodMappingRepository` — `shoppro_delivery_method_mappings` - `CarrierDeliveryMethodMappingRepository` — `carrier_delivery_method_mappings` (unified, cross-source) - `CompanySettingsRepository` — `company_settings` single-row config - `AllegroDeliveryMethodMappingRepository` — Allegro-specific delivery method mappings (legacy, superseded by carrier table) **API Clients (HTTP adapters):** - `AllegroOAuthClient` — builds OAuth2 authorize URL, exchanges code for tokens, refreshes tokens - `AllegroApiClient` — calls Allegro REST API (`/order/checkout-forms`, `/sale/product-offers`, `/delivery-services`, etc.) - `ApaczkaApiClient` — calls Apaczka REST API (services, create shipment, label, points) - `ShopproApiClient` — calls shopPRO REST API (orders, products, dictionaries, statuses) **Services:** - `AllegroOrderImportService` — maps single Allegro checkout-form to neutral order aggregate, calls `OrderImportRepository::upsertOrderAggregate()` - `AllegroOrdersSyncService` — paginates Allegro checkout-forms list and imports new/changed orders; maintains sync cursor - `AllegroStatusSyncService` — syncs statuses between Allegro and orderPRO based on configured direction - `AllegroStatusDiscoveryService` — fetches live status list from Allegro API and seeds `allegro_order_status_mappings` - `ShopproOrdersSyncService` — fetches shopPRO orders, maps to neutral aggregate, handles paczkomat/pickup points, media - `ShopproStatusSyncService` — orchestrates shopPRO→orderPRO status synchronisation - `ShopproPaymentStatusSyncService` — polls shopPRO `paid` flag and updates `orders.payment_status` - `IntegrationSecretCipher` — AES-256-CBC encryption with HMAC-SHA256 authentication (`v1:base64(iv+mac+cipher)`) ### Cron (`src/Modules/Cron/`) | Class | Responsibility | |---|---| | `CronRunner` | Dispatches due schedules to `cron_jobs` queue; processes pending jobs up to `$limit`; delegates to typed handlers | | `CronRepository` | `findDueSchedules()`, `claimNextPendingJob()`, `markJobCompleted()`, `markJobFailed()`, `enqueueJobFromSchedule()`, `getBoolSetting()`, `getIntSetting()` | | `AllegroTokenRefreshHandler` | `handle()` → refreshes Allegro OAuth token before expiry | | `AllegroOrdersImportHandler` | `handle()` → calls `AllegroOrdersSyncService::sync()` | | `AllegroStatusSyncHandler` | `handle()` → calls `AllegroStatusSyncService::sync()` | | `ShopproOrdersImportHandler` | `handle()` → calls `ShopproOrdersSyncService::syncAll()` | | `ShopproStatusSyncHandler` | `handle()` → calls `ShopproStatusSyncService::syncAll()` | | `ShopproPaymentStatusSyncHandler` | `handle()` → calls `ShopproPaymentStatusSyncService::syncAll()` | Cron is also triggerable via CLI (`bin/cron.php`) and optionally runs on each HTTP request when `cron_run_on_web=1` (throttled with session timer + DB advisory lock `orderpro_web_cron_lock`). ### Shipments (`src/Modules/Shipments/`) Provider-agnostic shipment layer built around `ShipmentProviderInterface`: ```php interface ShipmentProviderInterface { public function code(): string; public function getDeliveryServices(): array; public function createShipment(int $orderId, array $formData): array; public function checkCreationStatus(int $packageId): array; public function downloadLabel(int $packageId, string $storagePath): array; } ``` | Class | Provider Code | Responsibility | |---|---|---| | `AllegroShipmentService` | `allegro_wza` | Allegro WZA carrier integration | | `ApaczkaShipmentService` | `apaczka` | Apaczka carrier API: wycena, create, label, points fallback for pickup addresses | | `ShipmentProviderRegistry` | — | Map of `code → ShipmentProviderInterface`; resolved by `ShipmentController` | | `ShipmentController` | — | `prepare` (form), `create`, `checkStatus`, `label` endpoints | | `ShipmentPackageRepository` | — | CRUD for `shipment_packages` table | ### Users (`src/Modules/Users/`) | Class | Responsibility | |---|---| | `UsersController` | List users, create user | | `UserRepository` | Find by email, list, insert/update | --- ## 5. Core Domain Concepts ### Order Aggregate The order domain is provider-neutral. All external orders (Allegro, shopPRO) are normalised into the same tables: ``` orders ← main record (source, integration_id, statuses, totals, flags) ├─ order_addresses ← 1:N (type: customer | delivery | invoice) ├─ order_items ← 1:N (products, quantities, media_url) ├─ order_payments ← 1:N (payment method, amounts) ├─ order_shipments ← 1:N (tracking numbers, provider info) ├─ order_documents ← 1:N (invoices, etc.) ├─ order_notes ← 1:N (internal notes) ├─ order_status_history ← 1:N (status transitions with timestamps) └─ order_activity_log ← 1:N (universal event log: status_change, payment, shipment, import, note, ...) ``` Key `orders` columns: - `source` — origin system (`allegro`, `shoppro`) - `source_order_id` — external ID in source system - `integration_id` — FK to `integrations.id` - `internal_order_number` — `OPXXXXXXXXX` (unique internal identifier, e.g. `OP000000001`) - `external_status_id` / `external_status_code` — last known status from source - `payment_status` — payment state (`0`=unpaid, `2`=paid, etc.) - `ordered_at` / `source_created_at` / `source_updated_at` / `fetched_at` — date fallback chain ### Integration Registry (`integrations` table) Single base table for all integration instances. Specific settings are in satellite tables linked by `integration_id`: ``` integrations (type, name, is_active, api_key_encrypted, orders_fetch_enabled, ...) ├─ allegro_integration_settings (1:1, environment, OAuth tokens, token_expires_at) ├─ apaczka_integration_settings (1:1, app_id, app_secret_encrypted) └─ inpost_integration_settings (1:1, api_token, organization_id, defaults) integrations (type=shoppro, multi-instance) ├─ order_status_mappings (1:N, shoppro_status_code → orderpro_status_code) └─ shoppro_delivery_method_mappings (1:N, delivery method → carrier service) ``` ### Status Configuration ``` order_status_groups (name, code, color_hex, sort_order, is_active) └─ order_statuses (1:N, name, code, sort_order, is_active) ``` Status codes in `orders` (`external_status_code`) are mapped to human labels via join with `order_statuses`. Fallback group "Pozostale" catches unmapped codes. External status mappings per provider: - `allegro_order_status_mappings` — `allegro_status_code → orderpro_status_code` - `order_status_mappings` — generic per-integration mapping (`shoppro_status_code → orderpro_status_code`) ### Carrier / Delivery Method Mappings ``` carrier_delivery_method_mappings source_system + source_integration_id + order_delivery_method → provider + provider_service_id + provider_account_id ``` This is the unified resolution table used by `ShipmentController` to auto-select the carrier service when preparing a shipment. ### Cron Scheduling Tables ``` cron_schedules (job_type, interval_seconds, priority, is_active, next_run_at) → cron_jobs (job_type, payload, status, priority, scheduled_at, result_json, ...) ``` `CronRunner` reads due schedules, enqueues jobs, then claims and processes pending jobs up to the configured limit. --- ## 6. Dependency Injection Pattern The application does not use a DI container. All dependencies are wired manually in `routes/web.php` (for HTTP) and in `Application::maybeRunCronOnWeb()` (for web cron). This means: - Every controller receives its dependencies via constructor injection. - `Application` holds the core singletons (PDO, AuthService, OrdersRepository, etc.) and exposes them via typed getters (`$app->db()`, `$app->orders()`, `$app->auth()`, etc.). - `routes/web.php` is the composition root for the web context. --- ## 7. Security Architecture | Concern | Mechanism | |---|---| | Authentication | Session-based. `AuthService::check()` reads `$_SESSION`. `AuthMiddleware` guards all protected routes. | | CSRF | `Csrf::token()` / `Csrf::validate()` — single session token, validated on all POST handlers before any mutation. | | Secret storage | API keys and OAuth tokens encrypted at-rest using `IntegrationSecretCipher` (AES-256-CBC + HMAC-SHA256). Secret key comes from `INTEGRATIONS_SECRET` env var. | | XSS | Template helper `$e()` wraps `htmlspecialchars()` with `ENT_QUOTES`. | | SQL injection | All queries use PDO prepared statements (medoo-style named params). No raw string concatenation. | | DB locks | `GET_LOCK` used for migrations (`orderpro_migrations_lock`) and web cron (`orderpro_web_cron_lock`) to prevent concurrent execution. | --- ## 8. Database Design Summary **Migration system:** SQL files in `database/migrations/` named `YYYYMMDD_NNNNNN_description.sql`, sorted alphabetically and tracked in `migrations` table. Applied via `Migrator::runPending()` from UI or CLI (`bin/migrate.php`). **Core tables:** | Table | Purpose | |---|---| | `users` | Panel users (email, password_hash) | | `orders` | Master order record | | `order_addresses` | Typed addresses per order (customer/delivery/invoice) | | `order_items` | Order line items with media_url | | `order_payments` | Payment records per order | | `order_shipments` | Shipment/tracking records from source system | | `order_documents` | Document references (invoices etc.) | | `order_notes` | Internal notes | | `order_status_history` | Full status transition history | | `order_activity_log` | Universal event log (status_change, payment, shipment, import, note, document, message) | | `order_status_groups` | Configurable status groups (with color, sort) | | `order_statuses` | Statuses within groups | | `order_status_mappings` | External status → orderPRO status per integration (generic) | | `allegro_order_status_mappings` | Allegro-specific status mappings | | `integrations` | Base table for all integration instances (allegro, apaczka, inpost, shoppro) | | `allegro_integration_settings` | Allegro OAuth2 credentials and tokens (1:1 with integrations) | | `apaczka_integration_settings` | Apaczka app_id + secrets (1:1 with integrations) | | `inpost_integration_settings` | InPost ShipX config and defaults (1:1 with integrations) | | `integration_order_sync_state` | Import cursor per integration (last synced order, timestamps) | | `carrier_delivery_method_mappings` | Unified: order delivery method → shipment provider service | | `shoppro_delivery_method_mappings` | shopPRO-specific delivery mappings (legacy, superseded by carrier table) | | `shipment_packages` | Packages created via shipment providers (status, tracking, label path) | | `company_settings` | Single-row sender company config (address, contact_person, package defaults) | | `cron_schedules` | Named job schedules with intervals and priorities | | `cron_jobs` | Job queue: pending, running, completed, failed | | `app_settings` | Key-value settings store (cron_run_on_web, allegro_status_sync_direction, etc.) | | `migrations` | Applied migration filenames (Migrator tracking) | --- ## 9. CLI Scripts (`bin/`) | Script | Purpose | |---|---| | `bin/cron.php` | CLI cron runner; loads app and calls `CronRunner::run($limit)` | | `bin/migrate.php` | CLI migration runner; calls `Migrator::runPending()` | | `bin/deploy_and_seed_orders.php` | Applies order schema draft + seeds test data (`--count`, `--append`, `--profile=default\|realistic`) | | `bin/fix_status_codes.php` | Repairs status group/status codes (PL→ASCII transliteration, `--dry-run`) | | `bin/fill_order_item_images.php` | Backfills `order_items.media_url` from integration APIs | | `bin/randomize_order_statuses.php` | Dev utility to randomize order statuses | | `bin/fix_gs1_brand.php` | Fixes GS1 brand data | | `bin/test_gs1_api.php` | Tests GS1 API connectivity | --- ## 10. Frontend Architecture - **Styles:** SCSS source in `resources/scss/`, compiled to `public/assets/css/`. - **Scripts:** JS in `resources/modules/` (e.g. `jquery-alerts`), compiled to `public/assets/js/modules/`. - **Alerts/confirms:** `window.OrderProAlerts.confirm(...)` from `public/assets/js/modules/jquery-alerts.js`. Native `alert()`/`confirm()` are forbidden. - **Views:** Plain PHP; no JS framework. Inline JS kept minimal, page-level only in view files. - **Layout:** Single shell `resources/views/layouts/app.php` with sidebar nav. Sidebar `activeMenu` and `activeSettings` variables control highlighted state. --- *Full architecture analysis: 2026-03-12*