325 lines
25 KiB
Markdown
325 lines
25 KiB
Markdown
# 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` | All 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** | 60+ | Integration controllers, OAuth clients, API clients, mappers | Allegro/shopPRO/Erli/Apaczka/InPost config, status mappings |
|
|
| **Sms** | 3 | `SmsMessageRepository`, `SmsConversationService`, `SmsplanetWebhookController` | SMSPLANET outbound order SMS, inbound webhook parsing, order matching |
|
|
| **Notifications** | 3 | `NotificationRepository`, `NotificationController`, `NotificationApiController` | Global notification history, unread polling API, mark-read actions |
|
|
| **Cron** | 12 | `CronRepository`, `CronHandlerFactory`, handler classes | Scheduled imports, syncs, token refresh |
|
|
| **Printing** | 4 | `PrintApiController`, `PrintJobRepository`, `ApiKeyMiddleware` | REST API for Windows print client |
|
|
| **Statistics** | 3 | `OrdersStatisticsController`, `OrdersStatisticsRepository`, `statistics-summary-charts.js` | Daily order statistics and monthly summary charts |
|
|
| **Info** | 1 | `InfoController` | Health check |
|
|
|
|
## Frontend Enhancement Modules
|
|
|
|
### Checkbox Multiselect (`public/assets/js/modules/checkbox-multiselect.js`)
|
|
- Loaded globally from `resources/views/layouts/app.php`.
|
|
- Enhances native `<select multiple data-checkbox-multiselect>` controls after `DOMContentLoaded`.
|
|
- Keeps the original select in the form, synchronizes option `selected` state, and preserves native GET/POST names such as `channels[]` and `status_groups[]`.
|
|
- Used by `/statistics/orders` and `/statistics/summary` filters to display a compact trigger, checkbox dropdown, "Wszystkie" bulk toggle, and selected count.
|
|
- Progressive enhancement: if JavaScript fails, the native multi-select remains visible.
|
|
|
|
### Statistics Summary Charts (`public/assets/js/modules/statistics-summary-charts.js`)
|
|
- Loaded globally from `resources/views/layouts/app.php` after Chart.js 4.4.8 CDN; activates only when `#js-statistics-summary-data` exists.
|
|
- Reads JSON produced by `OrdersStatisticsController::summary()` and renders two interactive Chart.js line charts on `/statistics/summary`.
|
|
- Chart 1 displays monthly order counts per selected integration plus a `Razem` line.
|
|
- Chart 2 displays monthly gross order values per selected integration plus a `Razem` line.
|
|
- The PHP view keeps table fallbacks under both charts, so the data remains visible if JavaScript fails.
|
|
|
|
### Notifications (`public/assets/js/modules/notifications.js`)
|
|
- Loaded globally from `resources/views/layouts/app.php`; activates only when the topbar notification button exists.
|
|
- Polls `/api/notifications/unread` every 30 seconds and updates the unread badge.
|
|
- Requests browser Notification API permission only after user interaction with the notification button.
|
|
- Shows native browser notifications for newly seen unread items when permission is granted; click navigates to `target_url`.
|
|
|
|
## Key Data Flows
|
|
|
|
### Order Lifecycle
|
|
1. **Import** — Cron handler → API client → `OrderImportService` → `OrdersRepository::insertOrder()` → `AutomationService::executeForNewOrder()`
|
|
2. **Status update** — `OrdersController::updateStatus()` → `OrdersRepository::updateStatus()` → automation check
|
|
3. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` / `ErliStatusSyncService` → marketplace API
|
|
|
|
### Statistics Summary
|
|
1. **Request** — `/statistics/summary` → `OrdersStatisticsController::summary()`
|
|
2. **Filters** — controller reuses statistics filter semantics: date range, `channels[]`, `status_groups[]`, default status groups excluding cancelled; default history starts at `2026-04-01`.
|
|
3. **Aggregation** — `OrdersStatisticsRepository::aggregateByMonth()` groups existing `orders` rows by `YYYY-MM` and channel key, using the same effective date/channel/status/gross amount SQL helpers as the daily report.
|
|
4. **View model** — controller builds per-integration series and total series for order count and gross value charts.
|
|
5. **Render** — `resources/views/statistics/summary.php` renders filters, chart JSON, two canvas targets, and table fallbacks.
|
|
|
|
### Shipment Flow
|
|
|
|
Phase 130 adds an Erli-specific post-label step: for `orders.source='erli'`, `ShipmentController` calls `ErliExternalShipmentService::syncPackage()` after a local provider has a tracking number. The service posts `vendor/status/trackingNumber/orderId` to Erli `POST /shipping/external` and stores the response in `shipment_packages.payload_json.erli_external_parcel`; failures are activity-log warnings and do not block local labels.
|
|
1. **Create** — `ShipmentController::create()` → `ShipmentProviderRegistry` → carrier `ShipmentService::createShipment()` → `ShipmentPackageRepository::insert()`
|
|
2. **Track** — Cron `ShipmentTrackingHandler` → `ShipmentTrackingRegistry` → carrier tracking API → `ShipmentPackageRepository::updateDeliveryStatus()`
|
|
|
|
### Receipt / Invoice
|
|
1. **Generate** — `ReceiptController::store()` → `ReceiptService::generateReceipt()` → `ReceiptRepository::insert()` + Dompdf PDF
|
|
2. **Email** — `EmailSendingService::send()` → `VariableResolver::resolve()` → `AttachmentGenerator::generatePdf()` → PHPMailer SMTP
|
|
|
|
### SMSPLANET Conversation
|
|
1. **Settings** — `/settings/integrations/smsplanet` stores auth, text sender, `sender_mode`, optional 2WAY `sender_phone`, and optional global `default_footer`.
|
|
2. **Outbound from order** — `/orders/{id}/sms/send` → `OrdersController::sendSms()` → `SmsConversationService::sendFromOrder()` appends `default_footer` when configured, validates the final body against 918 characters, sends through `SmsplanetApiClient::sendSms()`, and stores the final sent body in `sms_messages`.
|
|
3. **Inbound webhook** — public `/webhooks/smsplanet/inbound` accepts SMSPLANET 2WAY `POST application/x-www-form-urlencoded` with `message=<JSON>`, plus fallback POST/GET payloads → `SmsplanetWebhookController::inbound()` → `SmsConversationService::receiveSmsplanetWebhook()`; successful 2WAY receipt returns plain `OK`.
|
|
4. **Order matching** — inbound sender phone is normalized and matched to the latest order by `order_addresses.phone`.
|
|
5. **Notification** — inbound SMS creates `notifications.type='sms_inbound'` with a target URL to the order SMS tab when an order was matched.
|
|
|
|
### Automation Rules
|
|
1. **Setup** — `AutomationController` → `AutomationRepository::insertRule()`
|
|
2. **Trigger** — `AutomationService::executeForOrder()` → evaluates trigger (`order_status_changed`, `order_status_aged`) → runs action (send email, update status)
|
|
3. **Log** — `AutomationExecutionLogRepository` tracks every run
|
|
|
|
### Cron Jobs
|
|
|
|
| Handler | Task |
|
|
|---------|------|
|
|
| `AllegroOrdersImportHandler` | Fetch new Allegro orders |
|
|
| `AllegroStatusSyncHandler` | Push status changes to Allegro |
|
|
| `AllegroTokenRefreshHandler` | OAuth token refresh (24h expiry) |
|
|
| `ShopproOrdersImportHandler` | Fetch new shopPRO orders |
|
|
| `ErliOrdersImportHandler` | Fetch unread Erli inbox order events |
|
|
| `ErliStatusSyncHandler` | Pull Erli status events via inbox or push manual local status changes to Erli |
|
|
| `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 |
|
|
|
|
### Erli Integration Foundation
|
|
|
|
1. **Settings** - `/settings/integrations/erli` renders tabbed integration/status/delivery/settings panels and stores one global Erli API key encrypted via `IntegrationSecretCipher`, an optional account label, active flag, and last connection-test result.
|
|
2. **Connection test** - `ErliIntegrationController::test()` loads active credentials, calls `ErliApiClient::testConnection()`, performs a real authenticated `GET https://erli.pl/svc/shop-api/inbox`, and stores the result in `integrations.last_test_*`.
|
|
3. **Hub** - `IntegrationsHubController::buildErliRow()` adds Erli to `/settings/integrations` with configured/missing secret status, active status, last test timestamp, and configure URL.
|
|
4. **Order import** - Phase 128 adds `/settings/integrations/erli/import` and cron `erli_orders_import`. Both call `ErliOrdersSyncService`, which fetches unread `/inbox` messages, maps supported order events through `ErliOrderMapper`, persists via `OrderImportRepository::upsertOrderAggregate()`, emits existing automation events, and acknowledges `POST /inbox/mark-read` only after a zero-failure batch.
|
|
5. **Status mapping/sync** - Phase 129 adds pull/push status mapping tables, status controls in Erli settings, and cron `erli_status_sync`. Pull reuses inbox import; push sends manual orderPRO status changes to `PATCH /orders/{id}/status`.
|
|
6. **Delivery mapping and labels** - Phase 130 adds `ErliDeliveryMappingController` and a Delivery tab. It maps imported Erli delivery method labels to local shipment providers (`inpost`/`apaczka`) and stores Erli `source_vendor_code` for external parcel registration. Label files are still produced by local providers, not by Erli carrier-contract parcels.
|
|
7. **External shipment sync** - Phase 130 extends `ErliApiClient` with shipping dictionary calls and `createExternalParcel()` (`POST /shipping/external`). `ErliExternalShipmentService` registers a local package in Erli only after `shipment_packages.tracking_number` exists; failures are activity-log warnings and do not block local labels.
|
|
8. **Deferred** - Carrier tracking automation and broader delivery-status hooks are planned for v3.8 Phase 131.
|
|
|
|
## Dependency Injection
|
|
|
|
Manual constructor injection in `routes/web.php` — no DI container library. Example:
|
|
|
|
```php
|
|
$ordersController = new OrdersController(
|
|
$template, $translator, $auth,
|
|
$app->orders(), $shipmentPackageRepository,
|
|
$receiptRepository, $receiptConfigRepository, ...
|
|
);
|
|
```
|
|
|
|
All production classes are `final` — prevents accidental inheritance.
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
bootstrap/ app.php (service wiring, config loading)
|
|
bin/ migrate.php, cron.php (CLI entry points)
|
|
config/ app.php, database.php
|
|
database/
|
|
migrations/ 84 SQL files (YYYYMMDD_NNNNNN_description.sql)
|
|
drafts/ WIP migrations
|
|
public/
|
|
index.php HTTP entry point
|
|
.htaccess Apache rewrite rules
|
|
assets/css/ Compiled CSS (app.css, login.css, modules/)
|
|
assets/js/ jquery-alerts.js, global-search.js, automation-form.js
|
|
resources/
|
|
views/ PHP templates by module + components/ layouts/
|
|
scss/ SCSS sources (app.scss, login.scss, modules/_*.scss)
|
|
modules/ jquery-alerts JS+SCSS source
|
|
lang/pl/ Polish translations
|
|
routes/
|
|
web.php All routes (581 lines)
|
|
src/
|
|
Core/ Framework (25 files)
|
|
Modules/ 13 feature modules (~200+ PHP files)
|
|
storage/
|
|
logs/ app.log
|
|
sessions/ PHP session files
|
|
cache/ PHPUnit cache, etc.
|
|
tests/
|
|
Unit/ PHPUnit tests (7+ service test files)
|
|
bootstrap.php PSR-4 autoloader for tests
|
|
```
|
|
|
|
## Phase 127 - Erli Integration Foundation
|
|
|
|
### ErliIntegrationRepository (`src/Modules/Settings/ErliIntegrationRepository.php`)
|
|
- Zarzadza pojedynczym rekordem `erli_integration_settings` (`id=1`) i bazowym wpisem `integrations.type='erli'`.
|
|
- Szyfruje klucz API przez `IntegrationSecretCipher`; formularz widzi tylko flage `has_api_key`.
|
|
- `getCredentials()` zwraca aktywna konfiguracje z `base_url='https://erli.pl/svc/shop-api'` i odszyfrowanym Bearer API key.
|
|
- Pusty input `api_key` podczas edycji zachowuje zapisany sekret.
|
|
|
|
### ErliApiClient (`src/Modules/Settings/ErliApiClient.php`)
|
|
- `testConnection()` wykonuje realny `GET /inbox` do Erli z naglowkiem `Authorization: Bearer ...`.
|
|
- Phase 128: `fetchInbox()` pobiera do 500 nieprzeczytanych wiadomosci; `markInboxRead()` potwierdza `POST /inbox/mark-read` z `lastMessageId` dopiero po udanym batchu.
|
|
- Phase 129: `updateOrderStatus()` wysyla `PATCH /orders/{id}/status` z body `{"status": "..."}` dla recznych zmian statusu orderPRO mapowanych na status Erli.
|
|
- Phase 130: `getShippingMethods()`, `getDeliveryMethods()`, `getDeliveryVendors()`, `getPriceLists()`, `getPriceListsDetails()` i `createExternalParcel()` obsluguja natywne shipping API Erli bez wymuszania nadawania przez umowe Erli.
|
|
- Wysyla `Accept: application/json` i `User-Agent: orderPRO/1.0 (erli-integration)`.
|
|
- Traktuje HTTP 2xx jako sukces; 401/403 jako blad autoryzacji, 429 jako limit zapytan, pozostale bledy jako czytelny komunikat z odpowiedzi.
|
|
- Uzywa `SslCertificateResolver` i nie wywoluje `curl_close()` (PHP 8.5 compatible).
|
|
|
|
### ErliIntegrationController (`src/Modules/Settings/ErliIntegrationController.php`)
|
|
- Endpointy: `GET /settings/integrations/erli`, `POST /settings/integrations/erli/save`, `POST /settings/integrations/erli/test`, `POST /settings/integrations/erli/import`, `POST /settings/integrations/erli/statuses/save-pull`, `POST /settings/integrations/erli/statuses/save-push`, `POST /settings/integrations/erli/delivery/save`.
|
|
- `save` zapisuje label, aktywnosc, sekret, ustawienia importu (`orders_fetch_enabled`, `orders_fetch_start_date`, interwal crona) oraz kierunek/interwal `erli_status_sync`; `test` wykonuje realny test API i zapisuje wynik przez `IntegrationsRepository::updateTestResult()`.
|
|
- `importNow()` uruchamia reczny import Erli z pominieciem flagi cron enable, ale nadal wymaga aktywnych credentials.
|
|
|
|
### ErliDeliveryMappingController (`src/Modules/Settings/ErliDeliveryMappingController.php`)
|
|
- Buduje dane zakladki Dostawy: metody z `orders.external_carrier_id` dla `source='erli'`, aktualne `carrier_delivery_method_mappings`, slowniki vendorow/metod z Erli oraz lokalne uslugi InPost/Apaczka.
|
|
- `saveDeliveryMappings()` zapisuje mapowanie globalne `source_system='erli'`, `source_integration_id=0` z lokalnym providerem oraz `source_vendor_code` wymaganym przez Erli `POST /shipping/external`.
|
|
|
|
### ErliExternalShipmentService (`src/Modules/Settings/ErliExternalShipmentService.php`)
|
|
- `syncPackage(int $packageId)` sprawdza, czy paczka nalezy do zamowienia Erli i ma tracking number.
|
|
- Pobiera vendor Erli z mapowania dostawy albo proboje go wywnioskowac z lokalnego providera/carrier id.
|
|
- Wysyla `POST /shipping/external` z `orderId`, `vendor`, `status='sent'`, `trackingNumber`; sukces zapisuje w `shipment_packages.payload_json.erli_external_parcel`, blad trafia do activity log jako niekrytyczny.
|
|
|
|
### ErliOrdersSyncService / ErliOrderMapper (`src/Modules/Settings/`)
|
|
- `ErliOrdersSyncService::sync()` jest wspolnym entrypointem dla crona i importu recznego. Zwraca liczniki `processed`, `imported_created`, `imported_updated`, `failed`, `skipped`, `acknowledged`.
|
|
- Obsluguje tylko zdarzenia order inbox (`orderCreated`, `orderStatusChanged`, `orderSellerStatusChanged`); wiadomosci produktowe sa pomijane do przyszlych faz.
|
|
- `ErliOrderMapper` mapuje statusy przez `ErliPullStatusMappingRepository` gdy istnieje konfiguracja; w przeciwnym razie zachowuje fallbacki `pending -> nieoplacone`, `purchased -> nowe`, `cancelled/returned -> anulowane`.
|
|
- `ErliOrdersSyncService` odkrywa surowe statusy Erli z inboxa i dopisuje je do `erli_order_status_pull_mappings`, zeby operator mogl je zmapowac w UI.
|
|
- Nowe zamowienia z invoice/company/tax id ustawiają `orders.invoice_requested=1`; re-import korzysta z istniejacego delta-only kontraktu `OrderImportRepository`.
|
|
- Automatyzacje: `order.imported` dla nowych zamowien i `payment.status_changed` przy tranzycji platnosci na re-imporcie.
|
|
|
|
### ErliOrdersImportHandler (`src/Modules/Cron/ErliOrdersImportHandler.php`)
|
|
- Handler crona `erli_orders_import`, domyslnie seedowany jako disabled. Operator wlacza go z ustawien Erli.
|
|
|
|
### ErliStatusSyncService / ErliStatusSyncHandler (`src/Modules/Settings/`, `src/Modules/Cron/`)
|
|
- Kierunek `erli_to_orderpro` wywoluje `ErliOrdersSyncService::sync()` z `ignore_orders_fetch_enabled=true`, czyli statusy przychodzace z Erli przechodza tym samym bezpiecznym `/inbox` + ACK flow co import.
|
|
- Kierunek `orderpro_to_erli` wybiera tylko zamowienia `source='erli'` z reczna zmiana statusu (`order_status_history.change_source='manual'`) po `integration_order_sync_state.last_status_pushed_at`.
|
|
- Push korzysta z `erli_order_status_mappings` i `ErliApiClient::updateOrderStatus()`. Brak mapowania powoduje `skipped`; blad API powoduje `failed` i nie przesuwa kursora poza ostatni udany timestamp.
|
|
- `erli_status_sync` jest seedowany jako disabled; zapis ustawien Erli aktualizuje interwal, kierunek i wlaczenie harmonogramu zgodnie z aktywnoscia integracji.
|
|
|
|
### IntegrationsHubController
|
|
- Dodaje wiersz Erli do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.
|
|
|
|
### Scope Boundary
|
|
- Phase 127 nie dodaje importu zamowien, mapowania/synchronizacji statusow, etykiet ani trackingu Erli. Te obszary sa odlozone do Phases 128-131.
|
|
|
|
## Phase 116 - HostedSMS Integration Settings
|
|
|
|
### HostedSmsIntegrationRepository (`src/Modules/Settings/HostedSmsIntegrationRepository.php`)
|
|
- Zarzadza pojedynczym rekordem `hostedsms_integration_settings` (`id=1`) i bazowym wpisem `integrations` typu `hostedsms`.
|
|
- Szyfruje haslo przez `IntegrationSecretCipher`; formularz widzi tylko flage `has_password`.
|
|
- Udostepnia `getCredentials()` dla kontrolera testowej wysylki SMS.
|
|
|
|
### HostedSmsApiClient (`src/Modules/Settings/HostedSmsApiClient.php`)
|
|
- Wykonuje `POST https://api.hostedsms.pl/SimpleApi` jako `application/x-www-form-urlencoded`.
|
|
- Wysyla `UserEmail`, `Password`, `Sender`, `Phone`, `Message` oraz opcjonalnie `ConvertMessageToGSM7`.
|
|
- Traktuje `MessageId` jako sukces, a `ErrorMessage` jako blad biznesowy nawet przy HTTP 200.
|
|
|
|
### HostedSmsIntegrationController (`src/Modules/Settings/HostedSmsIntegrationController.php`)
|
|
- Endpointy: `GET /settings/integrations/hostedsms`, `POST /settings/integrations/hostedsms/save`, `POST /settings/integrations/hostedsms/test`.
|
|
- `test` realnie wysyla SMS z edytowalna trescia i zapisuje wynik w `integrations.last_test_*`.
|
|
|
|
### IntegrationsHubController
|
|
- Dodaje wiersz HostedSMS do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.
|
|
|
|
## Phase 117 - SMSPLANET Integration Settings
|
|
|
|
### SmsplanetIntegrationRepository (`src/Modules/Settings/SmsplanetIntegrationRepository.php`)
|
|
- Zarzadza pojedynczym rekordem `smsplanet_integration_settings` (`id=1`) i bazowym wpisem `integrations` typu `smsplanet`.
|
|
- Obsluguje dwie metody autoryzacji: Bearer token oraz `key` + `password`.
|
|
- Szyfruje token, klucz API i haslo przez `IntegrationSecretCipher`; formularz widzi tylko flagi `has_api_token`, `has_api_key` i `has_api_password`.
|
|
- Udostepnia `getCredentials()` tylko dla kompletnej i aktywnej konfiguracji testowej wysylki SMS, razem z opcjonalna `default_footer`.
|
|
|
|
### SmsplanetApiClient (`src/Modules/Settings/SmsplanetApiClient.php`)
|
|
- Wykonuje `POST https://api2.smsplanet.pl/sms` jako `application/x-www-form-urlencoded`.
|
|
- Dla Bearer token wysyla naglowek `Authorization: Bearer ...`; dla `key_password` wysyla parametry `key` i `password`.
|
|
- Wysyla `from`, `to`, `msg` oraz opcjonalnie `clear_polish` i `transactional`; test nie ustawia `test=1`, wiec wysyla realny SMS.
|
|
- Traktuje `messageId` jako sukces, a `errorMsg`/`errorCode` jako blad biznesowy.
|
|
|
|
### SmsplanetIntegrationController (`src/Modules/Settings/SmsplanetIntegrationController.php`)
|
|
- Endpointy: `GET /settings/integrations/smsplanet`, `POST /settings/integrations/smsplanet/save`, `POST /settings/integrations/smsplanet/test`.
|
|
- `test` realnie wysyla SMS z edytowalna trescia i zapisuje wynik w `integrations.last_test_*`.
|
|
- Testowa wysylka dopisuje `default_footer` przed wywolaniem SMSPLANET i waliduje finalna tresc w limicie 918 znakow.
|
|
|
|
### IntegrationsHubController
|
|
- Dodaje wiersz SMSPLANET do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.
|
|
|
|
## Phase 118 - Fakturownia Single Instance
|
|
|
|
### FakturowniaIntegrationRepository (`src/Modules/Settings/FakturowniaIntegrationRepository.php`)
|
|
- Zarzadza pojedynczym globalnym rekordem `fakturownia_integration_settings` (`id=1`) i jednym bazowym wpisem `integrations.type='fakturownia'`.
|
|
- `getSettings()` zwraca dane formularza, flagi `has_api_token`, aktywnosc i wynik ostatniego testu.
|
|
- `saveSettings()` aktualizuje globalna konfiguracje; pusty `api_token` zachowuje zapisany sekret.
|
|
- `findAll()` zostaje jako kompatybilny wrapper zwracajacy liste z jednym elementem dla starszych wywolan.
|
|
- `getIntegrationId()` jest zrodlem prawdy dla `invoice_configs.integration_id` przy delegacji faktur.
|
|
|
|
### FakturowniaIntegrationController
|
|
- Endpointy aktywne: `GET /settings/integrations/fakturownia`, `POST /settings/integrations/fakturownia/save`, `POST /settings/integrations/fakturownia/test`.
|
|
- Legacy `/new` i `/edit` przekierowuja na globalna konfiguracje; delete z UI nie jest oferowany.
|
|
- Widok `resources/views/settings/fakturownia.php` pokazuje jeden formularz konfiguracji oraz panel testu polaczenia.
|
|
|
|
### InvoiceConfigRepository + InvoiceConfigController
|
|
- Przy `is_delegated=1` zapis konfiguracji ignoruje wieloinstancyjny wybor konta i ustawia `integration_id` na globalny Fakturownia id.
|
|
- Kolumna `invoice_configs.integration_id` zostaje dla kompatybilnosci z `InvoiceService` i historia wystawionych faktur.
|
|
- Widok konfiguracji faktury pokazuje status globalnej Fakturowni zamiast selecta kont.
|
|
|
|
### Migration 20260512_000109
|
|
- Wybiera aktywna instancje Fakturowni jako zachowana; fallback: najczesciej uzywana w `invoice_configs`, potem najnizsze id.
|
|
- Przepina delegowane `invoice_configs.integration_id` na zachowana instancje i zeruje `integration_id` dla lokalnych konfiguracji.
|
|
- Usuwa nadmiarowe rekordy `fakturownia_integration_settings` i `integrations.type='fakturownia'` po przepieciu zaleznosci.
|
|
|
|
## 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
|