# Database Schema **Updated:** 2026-05-10 | **Total tables:** 59 | **Engine:** InnoDB | **Charset:** utf8mb4_unicode_ci --- ## Auth / Users **users** — System user accounts with authentication | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK, AUTO_INCREMENT | | `name` | VARCHAR(120) | NO | | | `email` | VARCHAR(190) | NO | UNIQUE | | `password_hash` | VARCHAR(255) | NO | | | `remember_token` | VARCHAR(255) | YES | SHA256 of cookie token | | `created_at` | DATETIME | NO | DEFAULT CURRENT_TIMESTAMP | --- ## Products **products** — Main product catalog | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `uuid` | CHAR(36) | NO | UNIQUE | | `type` | ENUM('simple','variant_parent') | NO | DEFAULT 'simple' | | `sku` | VARCHAR(128) | YES | UNIQUE | | `ean` | VARCHAR(32) | YES | | | `status` | TINYINT(1) | NO | DEFAULT 1 | | `promoted` | TINYINT(1) | NO | DEFAULT 0 | | `new_to_date` | DATE | YES | | | `additional_message` | TINYINT(1) | NO | DEFAULT 0 | | `additional_message_required` | TINYINT(1) | NO | DEFAULT 0 | | `additional_message_text` | TEXT | YES | | | `vat` | DECIMAL(5,2) | YES | | | `weight` | DECIMAL(10,3) | YES | | | `price_brutto` | DECIMAL(12,2) | NO | DEFAULT 0.00 | | `price_brutto_promo` | DECIMAL(12,2) | YES | | | `price_netto` | DECIMAL(12,2) | YES | | | `price_netto_promo` | DECIMAL(12,2) | YES | | | `quantity` | DECIMAL(12,3) | NO | DEFAULT 0.000 | | `producer_id` | INT UNSIGNED | YES | | | `producer_name` | VARCHAR(255) | YES | | | `product_unit_id` | INT UNSIGNED | YES | | | `custom_fields_json` | TEXT | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | ON UPDATE CURRENT_TIMESTAMP | | `deleted_at` | DATETIME | YES | Soft delete | Indexes: `products_status_idx`, `products_type_idx`, `products_updated_at_idx`, `products_ean_idx` **product_translations** — Localized product names/descriptions | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `product_id` | INT UNSIGNED | NO | FK → products(id) CASCADE | | `lang` | VARCHAR(8) | NO | | | `name` | VARCHAR(255) | NO | | | `short_description` | TEXT | YES | | | `description` | LONGTEXT | YES | | | `meta_title` | VARCHAR(255) | YES | | | `meta_description` | VARCHAR(255) | YES | | | `meta_keywords` | VARCHAR(255) | YES | | | `seo_link` | VARCHAR(255) | YES | | | `security_information` | MEDIUMTEXT | YES | GPSR data | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | UNIQUE: `(product_id, lang)` **product_images** — Product image storage references | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `product_id` | INT UNSIGNED | NO | FK → products(id) CASCADE | | `storage_path` | VARCHAR(255) | NO | | | `alt` | VARCHAR(255) | YES | | | `sort_order` | INT | NO | DEFAULT 0 | | `is_main` | TINYINT(1) | NO | DEFAULT 0 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **product_categories** — Product–category associations (m2m) | Column | Type | Notes | |--------|------|-------| | `product_id` | INT UNSIGNED | FK → products(id) CASCADE | | `category_id` | INT UNSIGNED | | | `created_at` | DATETIME | | PK: `(product_id, category_id)` **product_variants** — Variants for `variant_parent` products | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `product_id` | INT UNSIGNED | NO | FK → products(id) CASCADE | | `permutation_hash` | VARCHAR(191) | NO | | | `sku` | VARCHAR(128) | YES | UNIQUE | | `ean` | VARCHAR(32) | YES | | | `status` | TINYINT(1) | NO | DEFAULT 1 | | `stock_0_buy` | TINYINT(1) | NO | DEFAULT 0 | | `price_brutto` | DECIMAL(12,2) | YES | | | `price_netto` | DECIMAL(12,2) | YES | | | `weight` | DECIMAL(10,3) | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | UNIQUE: `(product_id, permutation_hash)` **product_variant_attributes** — Variant–attribute value mapping | Column | Type | Notes | |--------|------|-------| | `variant_id` | INT UNSIGNED | FK → product_variants(id) CASCADE | | `attribute_id` | INT UNSIGNED | FK → attributes(id) RESTRICT | | `value_id` | INT UNSIGNED | FK → attribute_values(id) RESTRICT | | `created_at` | DATETIME | | PK: `(variant_id, attribute_id)` **attributes** — Attribute definitions (e.g., size, color) | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `type` | TINYINT UNSIGNED | NO | DEFAULT 1 | | `status` | TINYINT(1) | NO | DEFAULT 1 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **attribute_translations** — Localized attribute names | Column | Type | Notes | |--------|------|-------| | `attribute_id` | INT UNSIGNED | FK → attributes(id) CASCADE | | `lang` | VARCHAR(8) | | | `name` | VARCHAR(255) | | | `created_at` | DATETIME | | | `updated_at` | DATETIME | | PK: `(attribute_id, lang)` **attribute_values** — Possible values per attribute | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `attribute_id` | INT UNSIGNED | NO | FK → attributes(id) CASCADE | | `status` | TINYINT(1) | NO | DEFAULT 1 | | `is_default` | TINYINT(1) | NO | DEFAULT 0 | | `impact_on_price` | DECIMAL(12,2) | YES | | | `sort_order` | INT | NO | DEFAULT 0 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **attribute_value_translations** — Localized value names | Column | Type | Notes | |--------|------|-------| | `value_id` | INT UNSIGNED | FK → attribute_values(id) CASCADE | | `lang` | VARCHAR(8) | | | `name` | VARCHAR(255) | | | `created_at` | DATETIME | | | `updated_at` | DATETIME | | PK: `(value_id, lang)` **product_change_log** — Audit trail for product modifications | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `product_id` | INT UNSIGNED | NO | FK → products(id) CASCADE | | `user_id` | INT UNSIGNED | YES | FK → users(id) SET NULL | | `change_type` | VARCHAR(64) | NO | | | `before_json` | JSON | YES | | | `after_json` | JSON | YES | | | `created_at` | DATETIME | NO | | **sales_channels** — Selling channel definitions | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `code` | VARCHAR(64) | NO | UNIQUE | | `name` | VARCHAR(128) | NO | | | `type` | VARCHAR(64) | NO | | | `status` | TINYINT(1) | NO | DEFAULT 1 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **product_channel_map** — Link products to external channel offers | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `product_id` | INT UNSIGNED | NO | FK → products(id) CASCADE | | `channel_id` | INT UNSIGNED | NO | FK → sales_channels(id) CASCADE | | `integration_id` | INT UNSIGNED | YES | FK → integrations(id) SET NULL | | `external_product_id` | VARCHAR(128) | YES | | | `external_variant_id` | VARCHAR(128) | YES | | | `sync_state` | VARCHAR(32) | NO | DEFAULT 'not_linked' | | `link_type` | VARCHAR(32) | NO | DEFAULT 'manual' | | `link_status` | VARCHAR(32) | NO | DEFAULT 'active' | | `confidence` | TINYINT UNSIGNED | YES | | | `linked_at` | DATETIME | YES | | | `unlinked_at` | DATETIME | YES | | | `sync_meta_json` | JSON | YES | | | `last_sync_at` | DATETIME | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | UNIQUE: `(product_id, channel_id, external_product_id, external_variant_id)` **channel_offers** — External channel offer snapshots | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `integration_id` | INT UNSIGNED | NO | FK → integrations(id) CASCADE | | `channel_id` | INT UNSIGNED | NO | FK → sales_channels(id) CASCADE | | `external_product_id` | VARCHAR(128) | NO | | | `external_variant_id` | VARCHAR(128) | YES | | | `external_offer_id` | VARCHAR(128) | YES | | | `name` | VARCHAR(255) | NO | | | `sku` | VARCHAR(128) | YES | | | `ean` | VARCHAR(32) | YES | | | `price_brutto` | DECIMAL(12,2) | YES | | | `quantity` | DECIMAL(12,3) | YES | | | `currency` | VARCHAR(8) | YES | | | `offer_status` | VARCHAR(32) | NO | DEFAULT 'active' | | `source_updated_at` | DATETIME | YES | | | `last_seen_at` | DATETIME | NO | | | `payload_json` | JSON | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | UNIQUE: `(integration_id, external_product_id, external_variant_id)` **product_link_events** — Audit log for product-channel linking changes **product_link_alerts** — Alerts for product-channel link issues (ENUM status: `active`, `resolved`) **product_integration_translations** — Integration-specific product description overrides --- ## Orders **orders** — Imported orders from sales channels | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `internal_order_number` | VARCHAR(11) | YES | UNIQUE, auto-assigned | | `integration_id` | INT UNSIGNED | NO | FK → integrations(id) CASCADE | | `external_order_id` | VARCHAR(64) | NO | | | `external_order_number` | VARCHAR(128) | YES | | | `status` | VARCHAR(64) | YES | Internal status code | | `currency` | CHAR(3) | YES | | | `total_gross` | DECIMAL(12,2) | YES | | | `total_net` | DECIMAL(12,2) | YES | | | `total_with_tax` | DECIMAL(12,2) | YES | | | `total_paid` | DECIMAL(12,2) | YES | | | `buyer_email` | VARCHAR(190) | YES | | | `buyer_name` | VARCHAR(190) | YES | | | `buyer_phone` | VARCHAR(64) | YES | | | `payment_method` | VARCHAR(128) | YES | | | `payment_status` | VARCHAR(64) | YES | | | `external_payment_type_id` | VARCHAR(128) | YES | | | `delivery_method` | VARCHAR(128) | YES | | | `delivery_price` | DECIMAL(12,2) | YES | | | `delivery_tracking_number` | VARCHAR(128) | YES | | | `invoice_requested` | TINYINT(1) | NO | DEFAULT 0 (Phase 113-01) | | `external_created_at` | DATETIME | YES | | | `external_updated_at` | DATETIME | YES | | | `last_status_checked_at` | DATETIME | YES | | | `payload_json` | JSON | YES | Full raw API payload | | `fetched_at` | DATETIME | NO | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | UNIQUE: `(integration_id, external_order_id)` > Note: Order notes are stored in the separate `order_notes` table (no `notes` column on `orders`). **order_items** — Line items within orders | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `order_id` | INT UNSIGNED | NO | FK → orders(id) CASCADE | | `external_item_id` | VARCHAR(64) | YES | | | `name` | VARCHAR(255) | NO | | | `sku` | VARCHAR(128) | YES | | | `ean` | VARCHAR(64) | YES | | | `quantity` | DECIMAL(12,3) | NO | DEFAULT 0 | | `price_gross` | DECIMAL(12,2) | YES | | | `price_net` | DECIMAL(12,2) | YES | | | `vat` | DECIMAL(6,2) | YES | | | `personalization` | TEXT | YES | Customer personalization data for design generation | | `project_generated` | TINYINT(1) | NO | DEFAULT 0 | | `project_generated_at` | DATETIME | YES | | | `payload_json` | JSON | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **order_activity_log** — Event log for order state changes | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | BIGINT UNSIGNED | NO | PK | | `order_id` | BIGINT UNSIGNED | NO | FK → orders(id) CASCADE | | `event_type` | VARCHAR(32) | NO | | | `summary` | VARCHAR(255) | NO | | | `details_json` | JSON | YES | | | `actor_type` | VARCHAR(16) | NO | DEFAULT 'system' | | `actor_name` | VARCHAR(128) | YES | | | `created_at` | DATETIME | NO | | **order_payments** — Payments linked to orders | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `order_id` | INT UNSIGNED | NO | FK → orders(id) CASCADE | | `source_payment_id` | VARCHAR(64) | YES | | | `external_payment_id` | VARCHAR(64) | YES | | | `payment_type_id` | VARCHAR(64) | NO | | | `payment_date` | DATETIME | YES | | | `amount` | DECIMAL(12,2) | YES | | | `currency` | CHAR(3) | YES | | | `comment` | VARCHAR(255) | YES | | | `payload_json` | JSON | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | UNIQUE: `(order_id, source_payment_id)` --- ## Order Statuses **order_status_groups** — Status group categories (e.g., "Nowe", "W realizacji") | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `name` | VARCHAR(120) | NO | | | `code` | VARCHAR(64) | NO | UNIQUE | | `color_hex` | CHAR(7) | NO | DEFAULT '#64748b' | | `sort_order` | INT | NO | DEFAULT 0 | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **order_statuses** — Individual statuses within groups | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `group_id` | INT UNSIGNED | NO | FK → order_status_groups(id) CASCADE | | `name` | VARCHAR(120) | NO | | | `code` | VARCHAR(64) | NO | UNIQUE | | `sort_order` | INT | NO | DEFAULT 0 | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **order_status_mappings** — Map external (e.g., shopPRO) statuses to internal ones | Column | Type | Notes | |--------|------|-------| | `id` | INT UNSIGNED | PK | | `integration_id` | INT UNSIGNED | FK → integrations(id) CASCADE | | `shoppro_status_code` | VARCHAR(64) | | | `shoppro_status_name` | VARCHAR(128) | | | `orderpro_status_code` | VARCHAR(64) | | | `created_at` | DATETIME | | | `updated_at` | DATETIME | | UNIQUE: `(integration_id, shoppro_status_code)` **integration_order_sync_state** — Track order fetch progress per integration **integration_order_status_sync_state** — Track status sync progress per integration and direction --- ## Shipments & Delivery **shipment_packages** — Prepared shipments with tracking | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | BIGINT UNSIGNED | NO | PK | | `order_id` | BIGINT UNSIGNED | NO | FK → orders(id) CASCADE | | `provider` | VARCHAR(32) | NO | DEFAULT 'allegro_wza' | | `delivery_method_id` | VARCHAR(128) | YES | | | `credentials_id` | VARCHAR(128) | YES | | | `command_id` | VARCHAR(64) | YES | | | `shipment_id` | VARCHAR(64) | YES | | | `tracking_number` | VARCHAR(128) | YES | | | `status` | VARCHAR(32) | NO | DEFAULT 'draft' | | `delivery_status` | VARCHAR(32) | NO | DEFAULT 'unknown' | | `delivery_status_raw` | VARCHAR(128) | YES | Provider's original status string | | `delivery_status_updated_at` | DATETIME | YES | | | `carrier_id` | VARCHAR(64) | YES | | | `package_type` | VARCHAR(16) | NO | DEFAULT 'PACKAGE' | | `weight_kg` | DECIMAL(8,3) | YES | | | `length_cm` | DECIMAL(8,1) | YES | | | `width_cm` | DECIMAL(8,1) | YES | | | `height_cm` | DECIMAL(8,1) | YES | | | `insurance_amount` | DECIMAL(12,2) | YES | | | `cod_amount` | DECIMAL(12,2) | YES | | | `label_format` | VARCHAR(8) | NO | DEFAULT 'PDF' | | `label_path` | VARCHAR(512) | YES | | | `receiver_point_id` | VARCHAR(64) | YES | Parcel locker ID | | `sender_point_id` | VARCHAR(64) | YES | | | `reference_number` | VARCHAR(128) | YES | | | `error_message` | TEXT | YES | | | `payload_json` | JSON | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | Indexes: `shipment_packages_order_idx`, `shipment_packages_status_idx`, `shipment_packages_tracking_idx`, `idx_delivery_status` **shipment_presets** — Predefined shipment configurations (saved templates) | Column | Type | Notes | |--------|------|-------| | `id` | BIGINT UNSIGNED | PK | | `name` | VARCHAR(100) | | | `color` | VARCHAR(7) | DEFAULT '#3b82f6' | | `carrier` | VARCHAR(32) | | | `provider_code` | VARCHAR(32) | | | `delivery_method_id` | VARCHAR(128) | | | `credentials_id` | VARCHAR(128) | DEFAULT '' | | `carrier_id` | VARCHAR(64) | DEFAULT '' | | `package_type` | VARCHAR(16) | DEFAULT 'PACKAGE' | | `length_cm` | DECIMAL(8,1) | DEFAULT 25.0 | | `width_cm` | DECIMAL(8,1) | DEFAULT 20.0 | | `height_cm` | DECIMAL(8,1) | DEFAULT 8.0 | | `weight_kg` | DECIMAL(8,3) | DEFAULT 1.000 | | `sender_point_id` | VARCHAR(64) | DEFAULT '' | | `label_format` | VARCHAR(8) | DEFAULT 'PDF' | | `sort_order` | INT UNSIGNED | DEFAULT 0 | | `created_at` | TIMESTAMP | | | `updated_at` | TIMESTAMP | | **delivery_statuses** — Normalized delivery status definitions (Phase 108) | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `key` | VARCHAR(50) | NO | UNIQUE | | `label_pl` | VARCHAR(100) | NO | Polish label | | `color` | VARCHAR(7) | NO | DEFAULT '#6c757d' | | `sort_order` | TINYINT UNSIGNED | NO | DEFAULT 0 | | `is_terminal` | TINYINT(1) | NO | DEFAULT 0 — marks final states | | `is_system` | TINYINT(1) | NO | DEFAULT 0 — system-managed | | `created_at` | DATETIME | NO | | **delivery_status_mappings** — Map provider-specific raw statuses to normalized keys (Phase 108) | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `provider` | VARCHAR(32) | NO | | | `raw_status` | VARCHAR(64) | NO | | | `normalized_status` | VARCHAR(32) | NO | FK ref → delivery_statuses.key | | `description` | VARCHAR(255) | NO | DEFAULT '' | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | UNIQUE: `(provider, raw_status)` --- ## Integrations **integrations** — Integration configurations for external services | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `type` | VARCHAR(32) | NO | e.g., 'allegro', 'shoppro', 'apaczka', 'inpost' | | `name` | VARCHAR(128) | NO | | | `base_url` | VARCHAR(255) | NO | | | `api_key_encrypted` | TEXT | YES | AES-encrypted | | `timeout_seconds` | SMALLINT UNSIGNED | NO | DEFAULT 10 | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `orders_fetch_enabled` | TINYINT(1) | NO | DEFAULT 0 | | `orders_fetch_start_date` | DATE | YES | | | `order_status_sync_direction` | VARCHAR(32) | NO | DEFAULT 'shoppro_to_orderpro' | | `last_test_status` | VARCHAR(16) | YES | | | `last_test_http_code` | SMALLINT UNSIGNED | YES | | | `last_test_message` | VARCHAR(255) | YES | | | `last_test_at` | DATETIME | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | UNIQUE: `(type, name)` **integration_test_logs** — API test results log **allegro_integration_settings** — Allegro OAuth tokens and API config | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `integration_id` | INT UNSIGNED | YES | UNIQUE, FK → integrations(id) CASCADE | | `environment` | VARCHAR(16) | NO | DEFAULT 'sandbox' | | `client_id` | VARCHAR(128) | YES | | | `client_secret_encrypted` | TEXT | YES | | | `redirect_uri` | VARCHAR(255) | YES | | | `orders_fetch_enabled` | TINYINT(1) | NO | DEFAULT 0 | | `orders_fetch_start_date` | DATE | YES | | | `access_token_encrypted` | MEDIUMTEXT | YES | AES-encrypted | | `refresh_token_encrypted` | MEDIUMTEXT | YES | AES-encrypted | | `token_type` | VARCHAR(32) | YES | | | `token_scope` | VARCHAR(255) | YES | | | `token_expires_at` | DATETIME | YES | | | `connected_at` | DATETIME | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **allegro_order_status_mappings** — Allegro status → internal status | Column | Type | Notes | |--------|------|-------| | `id` | INT UNSIGNED | PK | | `allegro_status_code` | VARCHAR(64) | UNIQUE | | `allegro_status_name` | VARCHAR(120) | | | `orderpro_status_code` | VARCHAR(64) | | | `created_at` | DATETIME | | | `updated_at` | DATETIME | | **allegro_delivery_method_mappings** — Map order delivery method strings to Allegro services | Column | Type | Notes | |--------|------|-------| | `id` | INT UNSIGNED | PK | | `order_delivery_method` | VARCHAR(200) | UNIQUE | | `allegro_delivery_method_id` | VARCHAR(128) | | | `allegro_credentials_id` | VARCHAR(128) | | | `allegro_carrier_id` | VARCHAR(128) | | | `allegro_service_name` | VARCHAR(255) | | | `created_at` | DATETIME | | | `updated_at` | DATETIME | | **apaczka_integration_settings** — Apaczka API credentials | Column | Type | Notes | |--------|------|-------| | `id` | TINYINT UNSIGNED | PK (fixed 1 row) | | `integration_id` | INT UNSIGNED | UNIQUE, FK → integrations(id) | | `api_key_encrypted` | TEXT | | | `created_at` | DATETIME | | | `updated_at` | DATETIME | | **inpost_integration_settings** — InPost ShipX settings | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | TINYINT UNSIGNED | NO | PK (fixed 1 row) | | `integration_id` | INT UNSIGNED | YES | UNIQUE, FK → integrations(id) | | `api_token_encrypted` | TEXT | YES | | | `organization_id` | VARCHAR(50) | YES | | | `environment` | ENUM('sandbox','production') | NO | DEFAULT 'sandbox' | | `default_dispatch_method` | ENUM('pop','parcel_locker','courier') | NO | DEFAULT 'pop' | | `default_dispatch_point` | VARCHAR(50) | YES | | | `default_insurance` | DECIMAL(10,2) | YES | | | `default_locker_size` | ENUM('small','medium','large') | NO | DEFAULT 'small' | | `default_courier_length` | SMALLINT UNSIGNED | YES | DEFAULT 20 | | `default_courier_width` | SMALLINT UNSIGNED | YES | DEFAULT 15 | | `default_courier_height` | SMALLINT UNSIGNED | YES | DEFAULT 8 | | `label_format` | ENUM('Pdf','Zpl','Epl') | NO | DEFAULT 'Pdf' | | `weekend_delivery` | TINYINT(1) | NO | DEFAULT 0 | | `auto_insurance_value` | TINYINT(1) | NO | DEFAULT 0 | | `multi_parcel` | TINYINT(1) | NO | DEFAULT 0 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **fakturownia_integration_settings** — Fakturownia account credentials (Phase 113-01; multi-account via integration_id) | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `integration_id` | INT UNSIGNED | NO | UNIQUE, FK → integrations(id) CASCADE | | `account_prefix` | VARCHAR(64) | NO | Subdomain: {prefix}.fakturownia.pl | | `api_token_encrypted` | TEXT | YES | AES-encrypted via `IntegrationSecretCipher` | | `department_id` | VARCHAR(64) | YES | Optional Fakturownia department | | `default_kind` | VARCHAR(32) | NO | DEFAULT 'vat' | | `default_payment_to_days` | TINYINT UNSIGNED | NO | DEFAULT 7 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | UNIQUE: `(integration_id)` — one settings row per Fakturownia integration. Multiple integrations of `type='fakturownia'` allowed. --- ## Accounting / Receipts **receipt_configs** — Receipt generation configurations | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `name` | VARCHAR(128) | NO | | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `number_format` | VARCHAR(64) | NO | DEFAULT 'PAR/%N/%M/%Y' | | `numbering_type` | ENUM('monthly','yearly') | NO | DEFAULT 'monthly' | | `is_named` | TINYINT(1) | NO | DEFAULT 0 | | `sale_date_source` | ENUM('order_date','payment_date','issue_date') | NO | DEFAULT 'issue_date' | | `order_reference` | ENUM('none','orderpro','integration') | NO | DEFAULT 'none' | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **receipts** — Generated receipts / invoices | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `order_id` | BIGINT UNSIGNED | NO | FK → orders(id) CASCADE | | `config_id` | INT UNSIGNED | NO | FK → receipt_configs(id) RESTRICT | | `receipt_number` | VARCHAR(64) | NO | UNIQUE | | `issue_date` | DATETIME | NO | | | `sale_date` | DATETIME | NO | | | `seller_data_json` | JSON | NO | Snapshot of company data at issue time | | `buyer_data_json` | JSON | YES | | | `items_json` | JSON | NO | | | `total_net` | DECIMAL(12,2) | NO | DEFAULT 0.00 | | `total_gross` | DECIMAL(12,2) | NO | DEFAULT 0.00 | | `order_reference_value` | VARCHAR(128) | YES | | | `created_by` | INT UNSIGNED | YES | | | `created_at` | DATETIME | NO | | **receipt_number_counters** — Sequential numbering per config/period | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `config_id` | INT UNSIGNED | NO | FK → receipt_configs(id) CASCADE | | `year` | SMALLINT UNSIGNED | NO | | | `month` | TINYINT UNSIGNED | YES | NULL for yearly numbering | | `last_number` | INT UNSIGNED | NO | DEFAULT 0 | UNIQUE: `(config_id, year, month)` --- ## Invoices (Phase 113-01) **invoice_configs** — Invoice generation configurations (analogous to `receipt_configs` plus delegation flag) | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `name` | VARCHAR(128) | NO | | | `integration_id` | INT UNSIGNED | YES | FK → integrations(id) SET NULL — points to Fakturownia account when delegated | | `is_delegated` | TINYINT(1) | NO | DEFAULT 0 — when 1, invoice creation calls Fakturownia API | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `number_format` | VARCHAR(64) | NO | DEFAULT 'FV/%N/%M/%Y' | | `numbering_type` | ENUM('monthly','yearly') | NO | DEFAULT 'monthly' | | `sale_date_source` | ENUM('order_date','payment_date','issue_date') | NO | DEFAULT 'issue_date' | | `order_reference` | ENUM('none','orderpro','integration') | NO | DEFAULT 'none' | | `payment_to_days` | TINYINT UNSIGNED | NO | DEFAULT 7 | | `default_kind` | VARCHAR(32) | NO | DEFAULT 'vat' (vat/proforma/etc) | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **invoices** — Generated invoices (snapshot pattern like `receipts`, plus external linkage when delegated) | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `order_id` | BIGINT UNSIGNED | NO | FK → orders(id) CASCADE | | `config_id` | INT UNSIGNED | NO | FK → invoice_configs(id) RESTRICT | | `invoice_number` | VARCHAR(64) | NO | UNIQUE | | `issue_date` | DATETIME | NO | | | `sale_date` | DATETIME | NO | | | `payment_due_date` | DATETIME | YES | | | `seller_data_json` | JSON | NO | Snapshot of company data at issue time | | `buyer_data_json` | JSON | YES | | | `items_json` | JSON | NO | | | `total_net` | DECIMAL(12,2) | NO | DEFAULT 0.00 | | `total_gross` | DECIMAL(12,2) | NO | DEFAULT 0.00 | | `order_reference_value` | VARCHAR(128) | YES | | | `external_invoice_id` | VARCHAR(128) | YES | Fakturownia invoice id when delegated | | `external_pdf_url` | VARCHAR(500) | YES | URL returned by Fakturownia | | `kind` | VARCHAR(32) | NO | DEFAULT 'vat' | | `created_by` | INT UNSIGNED | YES | | | `created_at` | DATETIME | NO | | Indexes: `invoices_number_unique`, `invoices_order_idx`, `invoices_config_date_idx (config_id, issue_date)`, `invoices_external_idx` **invoice_number_counters** — Sequential numbering per config/period (mirrors `receipt_number_counters`) | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `config_id` | INT UNSIGNED | NO | FK → invoice_configs(id) CASCADE | | `year` | SMALLINT UNSIGNED | NO | | | `month` | TINYINT UNSIGNED | YES | NULL for yearly numbering | | `last_number` | INT UNSIGNED | NO | DEFAULT 0 | UNIQUE: `(config_id, year, month)` --- ## Email **email_mailboxes** — SMTP mailbox configurations | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `name` | VARCHAR(100) | NO | | | `smtp_host` | VARCHAR(255) | NO | | | `smtp_port` | SMALLINT UNSIGNED | NO | DEFAULT 587 | | `smtp_encryption` | ENUM('tls','ssl','none') | NO | DEFAULT 'tls' | | `smtp_username` | VARCHAR(255) | NO | | | `smtp_password_encrypted` | TEXT | NO | AES-encrypted | | `sender_email` | VARCHAR(255) | NO | | | `sender_name` | VARCHAR(200) | YES | | | `html_layout` | LONGTEXT | YES | Wrapper HTML for all emails from this mailbox | | `is_default` | TINYINT(1) | NO | DEFAULT 0 | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **email_templates** — Email message templates with variable placeholders | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `name` | VARCHAR(200) | NO | | | `subject` | VARCHAR(500) | NO | | | `body_html` | TEXT | NO | Supports `{{variable}}` placeholders | | `mailbox_id` | INT UNSIGNED | YES | FK → email_mailboxes(id) SET NULL | | `attachment1` | LONGTEXT | YES | | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | **email_logs** — Sent email history | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | BIGINT UNSIGNED | NO | PK | | `template_id` | INT UNSIGNED | YES | FK → email_templates(id) SET NULL | | `mailbox_id` | INT UNSIGNED | YES | FK → email_mailboxes(id) SET NULL | | `order_id` | INT UNSIGNED | YES | | | `recipient_email` | VARCHAR(255) | NO | | | `recipient_name` | VARCHAR(200) | YES | | | `subject` | VARCHAR(500) | NO | | | `body_html` | TEXT | NO | | | `attachments_json` | JSON | YES | | | `status` | ENUM('sent','failed','pending') | NO | DEFAULT 'pending' | | `error_message` | TEXT | YES | | | `sent_at` | DATETIME | YES | | | `created_at` | DATETIME | NO | | --- ## Automation **automation_rules** — Business rules for order event automation | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `name` | VARCHAR(128) | NO | | | `event_type` | VARCHAR(64) | NO | e.g., 'order_status_changed', 'order_status_aged' | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | Index: `(event_type, is_active)` **automation_conditions** — Conditions for automation rules | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `rule_id` | INT UNSIGNED | NO | FK → automation_rules(id) CASCADE | | `condition_type` | VARCHAR(64) | NO | | | `condition_value` | JSON | NO | | | `sort_order` | SMALLINT UNSIGNED | NO | DEFAULT 0 | **automation_actions** — Actions executed when rules trigger | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `rule_id` | INT UNSIGNED | NO | FK → automation_rules(id) CASCADE | | `action_type` | VARCHAR(64) | NO | e.g., 'send_email', 'update_status', 'create_receipt' | | `action_config` | JSON | NO | | | `sort_order` | SMALLINT UNSIGNED | NO | DEFAULT 0 | **automation_execution_logs** — Audit log for rule executions | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | BIGINT UNSIGNED | NO | PK | | `event_type` | VARCHAR(64) | NO | | | `rule_id` | INT UNSIGNED | YES | FK → automation_rules(id) SET NULL | | `rule_name` | VARCHAR(128) | NO | Snapshot at execution time | | `order_id` | INT UNSIGNED | NO | FK → orders(id) CASCADE | | `execution_status` | VARCHAR(16) | NO | | | `result_message` | VARCHAR(500) | YES | | | `context_json` | JSON | YES | | | `executed_at` | DATETIME | NO | | | `created_at` | DATETIME | NO | | **automation_email_once_deliveries** — Idempotency guard: email sent-once per rule+action+order (Phase 107) | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | BIGINT UNSIGNED | NO | PK | | `rule_id` | INT UNSIGNED | NO | FK → automation_rules(id) CASCADE | | `action_id` | INT UNSIGNED | NO | FK → automation_actions(id) CASCADE | | `order_id` | BIGINT UNSIGNED | NO | FK → orders(id) CASCADE | | `created_at` | DATETIME | NO | | UNIQUE: `(rule_id, action_id, order_id)` --- ## Print Queue **print_api_keys** — API keys for remote print client authentication | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT | NO | PK | | `name` | VARCHAR(128) | NO | | | `key_hash` | VARCHAR(128) | NO | UNIQUE, SHA256 | | `key_prefix` | VARCHAR(8) | NO | Shown in UI for identification | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `last_used_at` | DATETIME | YES | | | `created_at` | DATETIME | NO | | **print_jobs** — Print queue for remote label printing | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `order_id` | BIGINT UNSIGNED | NO | | | `package_id` | BIGINT UNSIGNED | NO | | | `label_path` | VARCHAR(255) | NO | | | `status` | ENUM('pending','printing','completed','failed') | NO | DEFAULT 'pending' | | `created_by` | INT UNSIGNED | NO | | | `created_at` | DATETIME | NO | | | `completed_at` | DATETIME | YES | | --- ## Cron & Scheduling **cron_jobs** — Individual cron job queue entries | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `job_type` | VARCHAR(80) | NO | | | `status` | ENUM('pending','processing','completed','failed','cancelled') | NO | DEFAULT 'pending' | | `priority` | TINYINT UNSIGNED | NO | DEFAULT 100 | | `payload` | JSON | YES | | | `result` | JSON | YES | | | `attempts` | SMALLINT UNSIGNED | NO | DEFAULT 0 | | `max_attempts` | SMALLINT UNSIGNED | NO | DEFAULT 3 | | `last_error` | VARCHAR(500) | YES | | | `scheduled_at` | DATETIME | NO | | | `started_at` | DATETIME | YES | | | `completed_at` | DATETIME | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | Index: `(status, priority, scheduled_at)` **cron_schedules** — Recurring job definitions | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `job_type` | VARCHAR(80) | NO | UNIQUE | | `interval_seconds` | INT UNSIGNED | NO | | | `priority` | TINYINT UNSIGNED | NO | DEFAULT 100 | | `max_attempts` | SMALLINT UNSIGNED | NO | DEFAULT 3 | | `payload` | JSON | YES | | | `enabled` | TINYINT(1) | NO | DEFAULT 1 | | `last_run_at` | DATETIME | YES | | | `next_run_at` | DATETIME | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | --- ## Settings & Configuration **app_settings** — Global key-value configuration store | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `setting_key` | VARCHAR(120) | NO | UNIQUE | | `setting_value` | TEXT | YES | | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | Default keys: `cron_run_on_web`, `cron_web_limit`, `gs1_api_login`, `gs1_prefix`, `products_sku_format` **company_settings** — Single-record seller/company configuration (always id=1) | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | TINYINT UNSIGNED | NO | PK, always 1 | | `company_name` | VARCHAR(200) | YES | | | `person_name` | VARCHAR(200) | YES | | | `street` | VARCHAR(200) | YES | | | `city` | VARCHAR(128) | YES | | | `postal_code` | VARCHAR(16) | YES | | | `country_code` | CHAR(2) | NO | DEFAULT 'PL' | | `phone` | VARCHAR(64) | YES | | | `email` | VARCHAR(128) | YES | | | `tax_number` | VARCHAR(64) | YES | NIP | | `bank_account` | VARCHAR(64) | YES | | | `bank_owner_name` | VARCHAR(200) | YES | | | `contact_person_first_name` | VARCHAR(100) | YES | | | `contact_person_last_name` | VARCHAR(100) | YES | | | `contact_person_phone` | VARCHAR(64) | YES | | | `contact_person_email` | VARCHAR(128) | YES | | | `default_package_length_cm` | DECIMAL(8,1) | NO | DEFAULT 25.0 | | `default_package_width_cm` | DECIMAL(8,1) | NO | DEFAULT 20.0 | | `default_package_height_cm` | DECIMAL(8,1) | NO | DEFAULT 8.0 | | `default_package_weight_kg` | DECIMAL(8,3) | NO | DEFAULT 1.000 | | `default_label_format` | VARCHAR(8) | NO | DEFAULT 'PDF' | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | --- ## Design Generation **project_mappings** — Map product name patterns to graphic generation scripts | Column | Type | Nullable | Notes | |--------|------|----------|-------| | `id` | INT UNSIGNED | NO | PK | | `product_name_pattern` | VARCHAR(255) | NO | Pattern matched against order_items.name | | `script_name` | VARCHAR(255) | NO | Script filename in tools/generowanie/ | | `output_dir` | VARCHAR(500) | YES | | | `is_active` | TINYINT(1) | NO | DEFAULT 1 | | `created_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | | --- ## Schema Characteristics | Property | Value | |----------|-------| | Engine | InnoDB (all tables) | | Charset | utf8mb4_unicode_ci | | Encrypted columns | `*_encrypted` suffix — AES via `IntegrationSecretCipher` | | Soft deletes | `products.deleted_at` only | | Audit via JSON | `payload_json` snapshots in orders, shipments, receipts | | Migrations | `database/migrations/YYYYMMDD_NNNNNN_description.sql` | | Deferred indexes | `idx_order_addresses_order_type`, `idx_shipment_packages_order_delivery` — apply at >50k orders | ## Reporting Usage **Statistics Summary (`/statistics/summary`)** — no dedicated reporting tables. - Reads existing `orders` rows and groups by month using the same effective order date used by `/statistics/orders`. - Default summary history starts at April 2026 (`2026-04-01`), even if older rows exist. - Splits series by channel key: Allegro as one series and each shopPRO integration by `orders.integration_id`. - Uses `integrations.name` only for display labels when available. - Filters by selected status groups through `order_status_groups` and `order_statuses`. - Uses existing gross amount columns via `OrdersStatisticsRepository::grossAmountSql()`. - No schema migration was introduced for Phase 110.