ADMIN — SYSTEM ADMINISTRATION
The control center of the entire ERP. User identities, permissions, authentication, printer hardware, print queue monitoring, AI assistant configuration, and activity auditing — everything that keeps the system secure, configured, and observable.
Admin is the gatekeeper and the control room. Before any operator can scan a QR code, before any lab user can approve a test, before any engineer can design a cable — they must first exist as a user in this module with the right permissions. Admin defines who can access the system, what they can do once inside, how they authenticate, and which hardware the system can talk to. It manages a hybrid permission system that combines role-based access (super_admin, lab_user, operator) with ultra-granular page-and-button-level permissions stored as JSON. It controls the factory’s label printers, monitors the print queue in real-time, and provides a full configuration interface for the AI assistant. Every action taken by every user is logged to an audit trail. Without this module, nobody can log in, no labels can print, and no permissions can be enforced.
TABLE OF CONTENTS
1. WHAT ADMIN DOES
The Admin module answers six questions that the entire ERP system depends on:
| Question | Who Answers It | How |
|---|---|---|
| Who is allowed to use the system? | User Management | Create/edit/suspend/delete user accounts with role assignment |
| What can each user do? | Permission System | Hybrid role-based + granular page.button JSON permissions |
| How do users prove their identity? | Authentication | Username/password → JWT access + refresh tokens with session tracking |
| Which printers exist and where? | Printer Management | Network printer registry with IP, port, material assignments, connection testing |
| What’s happening in the print queue? | Print Job Monitor | Real-time dashboard of all print jobs across 4 printers with retry/cancel |
| How is the AI assistant configured? | AI Settings | Multi-provider API key management, model selection, audit logging |
1.1 The Three User Types
The system recognizes three fundamental user types, each with a different scope of access and a different physical context of use:
Super Admin
Full system access. Can manage users, configure printers, adjust AI settings, view activity logs, and access every module. This is the factory manager or IT administrator. Typically operates from a desktop in the office.
Lab User
Lab dashboard, test management, quality control, alert management, and test reports. Can also test printer connections and retry failed print jobs. This is the quality engineer working in the laboratory. Typically operates from a dedicated lab workstation.
Operator
Machine operation, production forms, material scanning, basic dashboard, and work order viewing. This is the factory floor worker. Typically operates from a kiosk phone mounted near the production line — which is why operator accounts receive 6-month JWT tokens instead of the standard 12-hour expiry.
1.2 The Hybrid Permission Model
The permission system operates at two layers simultaneously:
Layer 1 — Role-Based (Legacy): The user_type field on the user record. Checked by is_permitted_user(user, ["super_admin", "lab_user"]). This is the coarse filter: “are you at least a lab user?”
Layer 2 — Granular (Modern): The permissions JSON field on the user record. Checked by check_user_permission(user, page_id, button_id). This is the fine filter: “on the Materials List page, can you click the Delete button?”
Both layers coexist through a backward-compatible function called check_permission_smart. It first checks the legacy role-based system, then falls back to the granular JSON system. Super admins bypass both — they always pass.
1.3 Beyond User Management
Admin is not just about users. It is the only module that manages physical hardware (network printers on the factory floor), provides real-time operational monitoring (print queue dashboard), and controls the AI assistant that other users interact with. It is also the only module that writes to the user_activity_logs table, creating an audit trail of every administrative action taken in the system.
2. THE DATA FLOW
Admin’s data flows are fundamentally different from production modules like Hammadde or Teknik. Those modules have a linear pipeline (supplier → order → entry → inventory). Admin has a hub-and-spoke pattern: it sits at the center and every other module depends on it.
2.1 Authentication Flow
Every subsequent API call across the entire system carries the JWT access token in the Authorization: Bearer header. The token contains the user’s ID, username, user_type, and permissions — meaning every route in every module can verify identity and check permissions without an extra database query.
2.2 Permission Enforcement Flow
On the frontend, the same check runs proactively: routes are hidden from the menu, buttons are disabled or invisible, and entire pages redirect to the user’s allowed dashboard if they attempt direct URL access.
2.3 Print Job Flow
Print jobs are created by other modules (Material Entry prints QR labels after recording a delivery, Materials List reprints labels on demand) but the queue itself lives in Admin. The Print Job Monitor gives super admins a real-time view of all jobs across all four factory printers, with the ability to retry failed jobs or cancel queued ones.
2.4 AI Interaction Flow
Every AI interaction is comprehensively logged: the user’s query, detected language, AI model used, reasoning steps, tool calls, API responses, timing metrics, and security flags. This creates a complete forensic trail of what the AI did and why.
2.5 The Hub-and-Spoke Dependency
Every module in the system is a spoke; Admin is the hub. Hammadde needs Admin for user authentication on every API call, for printer access when printing QR labels, and for permission checks on every button click. Teknik needs Admin for operator records and user identity. Production, Lab, Orders — all of them call get_current_user_dependency on every request, which decodes the JWT token that Admin issued. If Admin is down, the entire system is locked out.
3. THE DATABASE LAYER
The Admin module owns 10 database tables, more than any other module. These tables fall into four groups: identity, sessions, hardware, and auditing.
3.1 Identity Tables
| Table | Purpose | Key Columns | Relationships |
|---|---|---|---|
users |
Core identity for every person in the system | id, username (unique), email (unique), hashed_password, user_type, status, is_active, is_superuser, permissions (JSON text), force_password_change, last_login, preferred_interface, device_info | 1—N user_sessions, 0..1 super_admins, 0..1 lab_users, 0..1 operators |
super_admins |
Extension data for super admin users | id, user_id (FK, unique), admin_level, permissions (JSON), access_logs (JSON) | N—1 users |
lab_users |
Extension data for laboratory users | id, user_id (FK, unique), lab_department, certifications (JSON), test_specializations (JSON), shift_schedule (JSON) | N—1 users |
operators |
Extension data for factory floor operators | id, user_id (FK, unique), assigned_machines (JSON), shift_schedule (JSON), skill_level, certifications (JSON) | N—1 users |
user_roles |
Reusable permission templates | id, name (unique), description, permissions (JSON text), is_system (prevents deletion) | Standalone (template store) |
The Inheritance Pattern: Rather than using single-table inheritance with nullable columns, the system uses a joined-table pattern. The users table holds common fields (login credentials, status, permissions). The extension tables (super_admins, lab_users, operators) hold type-specific fields via a unique FK to users.id. This keeps the core users table lean while allowing each user type to store specialized data (e.g., operators have assigned_machines, lab users have certifications).
3.2 Session Table
| Table | Purpose | Key Columns | Relationships |
|---|---|---|---|
user_sessions |
Active and historical login sessions | id, user_id (FK), session_token (unique, indexed), refresh_token (unique, indexed), login_time, logout_time, expires_at, ip_address (IPv6-ready, 45 chars), user_agent, device_type, status | N—1 users (cascade delete) |
Sessions are not just for token validation. They provide a complete login history: which device was used, from which IP, when they logged in, when they logged out (or if the session expired). The cascade delete on the users relationship means deleting a user automatically purges all their sessions.
3.3 Hardware Tables
| Table | Purpose | Key Columns | Relationships |
|---|---|---|---|
printers |
Network label printers on the factory floor | id, name, description, ip_address, port (default 9100), status (ONLINE/OFFLINE/ERROR/MAINTENANCE), is_active, assigned_materials (JSON array), location, last_checked | Standalone |
print_jobs |
Label print job queue and history | id, material_id (FK → raw_materials), qr_code, printer_id (1–4), copies, status (QUEUED/PRINTING/COMPLETED/FAILED/CANCELLED), retry_count, error_message, requested_by (FK → users), requested_at, started_at, completed_at, material_type, lot_number, supplier_name | N—1 raw_materials, N—1 users |
Denormalized for display: The print_jobs table stores material_type, lot_number, and supplier_name as denormalized copies from the material record. This means the Print Job Monitor can display job history without joining to raw_materials or suppliers — critical for a real-time monitoring dashboard that refreshes frequently.
3.4 Audit Tables
| Table | Purpose | Key Columns | Relationships |
|---|---|---|---|
user_activity_logs |
Audit trail of all admin actions | id, user_id (FK, indexed), username (denormalized), action, module, target_type, target_id, details (JSON), ip_address, user_agent, created_at (indexed) | N—1 users |
ai_audit_logs |
Comprehensive AI interaction forensics | id, user_id, username, user_type, session_id, ip_address, device_type, browser, os, original_query, query_language, ai_model, ai_reasoning, ai_confidence, selected_tool, tool_params, api_endpoint, api_response_status, success, error_type, total_duration_ms, ai_processing_ms, security_flags, was_blocked, block_reason, created_at | Standalone (47 columns) |
The AI audit table is the most wide table in the entire system — 47 columns. This is deliberate. Each AI interaction is a multi-step pipeline (query parsing → tool selection → API call → response generation) and every step produces data that may be needed for debugging or security review. The table has composite indexes on (user_id, created_at), (selected_tool, created_at), (success, created_at), and (ip_address, created_at) to support analytics queries without full table scans.
3.5 Entity-Relationship Summary
| Relationship | Type | Cascade |
|---|---|---|
| users → user_sessions | 1—N | Delete (user deletion purges sessions) |
| users → super_admins | 1—0..1 | None (extension table) |
| users → lab_users | 1—0..1 | None (extension table) |
| users → operators | 1—0..1 | None (extension table) |
| users → user_activity_logs | 1—N | None (logs survive user deletion) |
| users → print_jobs (requested_by) | 1—N | None |
| raw_materials → print_jobs (material_id) | 1—N | None (cross-module FK) |
4. THE BACKEND ARCHITECTURE
The Admin backend is organized into five route groups, one core service, one security module, and a permission utility layer. Unlike production modules that have a single route file, Admin spans multiple files because its concerns are fundamentally different from each other.
4.1 Route Groups
| Route Group | Prefix | Endpoints | Auth Required | Min. Role |
|---|---|---|---|---|
| Authentication | /api/auth | 5 | Mixed (login is public) | None / Any |
| User Management | /api/user-management | 9 | Yes | Super Admin (most), Any (operators list) |
| Printers | /api/printers | 5 | Yes | Super Admin (CRUD), Lab User (test) |
| Print Queue | /api/print-queue | 5 | Yes | Any (queue), Super Admin (manage) |
| AI | /api/ai | 12 | Yes | Any (chat), Super Admin (config) |
4.2 Authentication Endpoints (5)
| Method | Path | Purpose | Key Behavior |
|---|---|---|---|
| POST | /auth/login | User login | Accepts username/password + device_type + remember_me. Returns access_token, refresh_token, expires_in, user object. Creates session record. |
| POST | /auth/logout | End session | Sets session status to “terminated”, stamps logout_time. |
| POST | /auth/refresh | Renew access token | Validates refresh_token, generates new access_token. Does not rotate the refresh token. |
| GET | /auth/me | Current user info | Decodes JWT, returns full user object with parsed permissions. |
| GET | /auth/health | Health check | Returns status + database connectivity check. |
4.3 User Management Endpoints (9)
| Method | Path | Purpose | Key Behavior |
|---|---|---|---|
| GET | /user-management/users/list | List all users | Supports status_filter and user_type query params. Parses permissions from JSON string to object. |
| GET | /user-management/operators | List operators | Returns only active operators. Available to all authenticated users (needed by production modules). |
| POST | /user-management/users/create | Create user | Auto-generates password if not provided. Sets force_password_change. Logs activity. |
| PUT | /user-management/users/{id} | Update user | Partial update: username, email, full_name, user_type, status, is_active, permissions, password. |
| POST | /user-management/users/{id}/suspend | Toggle suspend | Flips between “active” and “suspended” status. Logs activity. |
| DELETE | /user-management/users/{id} | Delete user permanently | Hard delete. Prevents self-deletion (cannot delete your own account). |
| POST | /user-management/users/{id}/reset-password | Force password reset | Generates secure 12-char random password (letters + digits + symbols). Sets force_password_change=True. |
| GET | /user-management/roles/list | List role templates | Returns all UserRole records for use in permission template assignment. |
| GET | /user-management/activity-logs | Activity log viewer | Supports filtering by user_id, module, action. Default limit 200. |
4.4 Printer & Print Queue Endpoints (10)
| Method | Path | Purpose | Key Behavior |
|---|---|---|---|
| GET | /printers/list | List printers | Optional active_only filter. |
| POST | /printers/create | Add printer | Name, IP, port (default 9100), location, assigned_materials (JSON array). |
| PUT | /printers/update/{id} | Edit printer | All fields updatable. |
| POST | /printers/{id}/test | Test connection | Attempts network connection to printer IP:port. Updates status to ONLINE or OFFLINE. |
| DELETE | /printers/{id} | Remove printer | Checks for pending print jobs before allowing deletion. |
| POST | /print-queue/queue/{material_id} | Queue label print | Creates QUEUED job linked to material and requesting user. Printer selected by query param (1–4). |
| GET | /print-queue/jobs | Job history | Filterable by status, printer_id. Max 500 results. |
| POST | /print-queue/jobs/{id}/retry | Retry failed job | Resets status to QUEUED, increments retry_count. |
| DELETE | /print-queue/jobs/{id} | Cancel job | Only QUEUED jobs can be cancelled (not PRINTING). |
| GET | /print-queue/stats | Queue statistics | Returns counts: queued, printing, completed, failed. |
4.5 AI Endpoints (12)
| Method | Path | Purpose | Key Behavior |
|---|---|---|---|
| POST | /api/ai/chat | Send AI query | Main chat endpoint. Processes query, calls tools, returns response. |
| GET | /api/ai/status | Service status | Returns AI service health and configuration state. |
| GET | /api/ai/models | Available models | Lists all supported AI models across providers. |
| POST | /api/ai/reset | Reset AI agent | Clears conversation context and resets agent state. |
| GET | /api/ai/config | Get configuration | Returns current: enabled flag, model_name, max_tokens, temperature. |
| POST | /api/ai/config | Save configuration | Updates AI settings. Super admin only. |
| GET | /api/ai/stats | Usage statistics | Query counts, success rates, average response times. |
| GET | /api/ai/audit-logs | Audit log viewer | Paginated (limit + offset). Returns full interaction forensics. |
| GET | /api/ai/api-keys/status | API key status | Returns masked key status for each provider (e.g., “sk-***...abc”). |
| POST | /api/ai/api-keys/save | Store API key | Writes to .env file. Supports Gemini, OpenAI, Anthropic. Masks in response. |
| DELETE | /api/ai/api-keys/{provider} | Remove API key | Removes key from .env file. |
| POST | /api/ai/api-keys/test/{provider} | Test API key | Makes a lightweight API call to validate the key works. |
4.6 Core Services
AuthenticationService
The central authentication engine. Handles five operations:
- authenticate_user — Finds user by username or email, verifies password with bcrypt, updates
last_logintimestamp. - create_user_session — Generates JWT access and refresh tokens. Critical detail: operator accounts get 6-month token expiry (for kiosk phones that shouldn’t require re-login), while all other accounts get the standard 12-hour expiry.
- get_user_by_token — Decodes JWT, validates expiry, checks that user still exists and is active.
- logout_user — Terminates session record, stamps logout time.
- refresh_token — Validates refresh token, issues new access token without creating a new session.
SecurityManager
Low-level cryptographic operations: bcrypt password hashing and verification, JWT creation with configurable expiry, JWT decoding with signature verification, and token expiry validation. Uses HS256 algorithm with a configurable secret key.
Permission Utilities
Four functions that form the permission checking backbone used by every module in the system:
is_permitted_user(user, required_types)— Legacy role check. Super admin always passes.check_user_permission(user, page_id, button_id)— Granular JSON permission check. Parses the user’spermissionsfield, navigates topages[page_id].buttons[button_id].has_special_permission(user, permission)— Checks special flags likehard_deleteormanage_users.check_permission_smart(user, required_types, page_id, button_id)— The unified entry point. Tries role-based first, then granular. This is what most routes actually call.
4.7 FastAPI Dependencies
Admin defines four injectable dependencies that every module in the system imports:
| Dependency | What It Does | Used By |
|---|---|---|
get_current_user_dependency | Extracts and validates user from JWT token in Authorization header | Every authenticated endpoint in the system |
require_super_admin | Raises 403 unless user_type is “super_admin” | Admin CRUD operations, system configuration |
require_lab_user | Raises 403 unless user_type is “lab_user” or “super_admin” | Lab test routes, quality control |
require_operator | Raises 403 unless user_type is “operator” or above | Production routes, material scanning |
Admin exports its security layer as importable dependencies. This is the architectural mechanism that makes Admin the hub: other modules don’t implement their own authentication — they import get_current_user_dependency from Admin and use it as a FastAPI Depends() parameter. One source of truth, zero duplication.
5. THE FRONTEND
The Admin frontend consists of five distinct pages plus a cross-cutting permission layer that affects every page in the entire application.
5.1 Page Inventory
| Page | Route | Component | Access |
|---|---|---|---|
| Login | /user/login | src/pages/user/login/index.tsx | Public (unauthenticated) |
| User Management | /admin/user-management | src/pages/Admin/UserManagement/index.tsx | canAdmin |
| Printer Management | /admin/printer-management | src/pages/Admin/PrinterManagement/index.tsx | canAdmin |
| Print Job Monitor | /admin/print-monitor | src/pages/Admin/PrintJobMonitor/index.tsx | canAdmin |
| AI Settings | /admin/ai-settings | src/pages/Admin/AISettings/index.tsx | canAdmin |
All four admin pages sit under the /admin route prefix and share the canAdmin access guard, which restricts visibility to super admin users. The Login page is the only public page in the entire application.
5.2 The Permission Layer (Cross-Cutting)
Admin’s most impactful frontend contribution isn’t its own pages — it’s the permission infrastructure that every other page uses. This lives in three files:
access.ts
Defines dynamic access functions: canAdmin, canLab, canProduction, etc. These are referenced in route config as access guards. Also provides canAccessPage(pageId) and module-level checks that read from the user’s parsed permission JSON.
permissionManifest.ts
The static registry of every page and every button in the system. Defines page IDs (e.g., admin.kullanici_yonetimi), button IDs (e.g., create_user, delete_user), labels, descriptions, and critical flags. Used by User Management to render the permission editor.
buttonRegistry.ts
A dynamic counterpart to the static manifest. Components register their buttons at mount time into a global registry. This allows the User Management permission editor to discover buttons that exist in the running application, even if the manifest hasn’t been updated.
The Hybrid Discovery Pattern: The permission system uses both a static manifest (hardcoded list of all pages and buttons) and a dynamic registry (components self-register at runtime). The User Management permission editor reads from the manifest for the structured tree view but can also discover dynamically registered buttons. This means new buttons added to any page in the system become available for permission assignment without manually updating a central config file.
5.3 Login Page
The login page handles three scenarios:
- Standard login: Username/password form with “Remember me” checkbox. Stores JWT tokens and user data in
localStorage(solen_auth_token,solen_refresh_token,solen_current_user). - Kiosk auto-login: Detects the Fully Kiosk Browser via user agent string. When detected, automatically logs in as the operator account and redirects to
/production. No human interaction required. - Secret URL token: Accepts
?kiosk=SOLEN_FACTORY_2024_KIOSK_SECRETas a URL parameter to trigger auto-login from any browser. This is a factory-floor convenience for mounting new kiosk devices.
5.4 User Management Page
The most complex page in the Admin module. It provides a full CRUD interface for user accounts with a three-tab modal:
- Tab 1 — Temel Bilgiler (Basic Info): Username, email, full name, user type (dropdown with super_admin, lab_user, operator, teknik_user, bakim_user, and custom types), status, password (for new users), force_password_change toggle.
- Tab 2 — Yetkiler (Permissions): The granular permission editor. Renders the full permission manifest as a tree: each page is a switch (access on/off), and under each page, individual buttons are checkboxes. Includes four quick-fill templates: Full, Empty, Operator Default, Lab User Default.
- Tab 3 — Önizleme (Preview): Shows a read-only summary of what the user will be able to access after saving — which modules, which pages, which buttons.
Beyond the modal, the page provides: search across username/email/full_name, filter by user_type and status, password reset (generates and displays new password), suspend/activate toggle, delete with confirmation, and last login display with Turkey/Istanbul timezone conversion.
5.5 Printer Management Page
CRUD interface for factory label printers. Each printer has: name, description, IP address, port, location, status, and assigned materials (multi-select from material types: raw_copper, raw_tin, raw_plastic, etc.). Includes a “Test Connection” button that hits the backend’s /printers/{id}/test endpoint to verify the printer is reachable on the network.
5.6 Print Job Monitor Page
Real-time monitoring dashboard. Top section shows four statistics cards: Queued (blue), Printing (orange), Completed (green), Failed (red). Below is a ProTable showing all print jobs with columns: QR code, material type, lot number, supplier, printer, status, requested by, requested at, completed at, error message. Supports retry (requeues failed jobs) and cancel (removes queued jobs). All timestamps are Turkey/Istanbul timezone.
5.7 AI Settings Page
Four-tab configuration interface:
- Overview: AI status (active/inactive), today’s query count, total queries, average response time, success rate visualization, and a connection test button.
- API Keys: Save, test, and delete API keys for three providers (Gemini, OpenAI, Anthropic). Keys are masked in the UI and stored in the server’s .env file.
- Model Settings: Enable/disable AI, select model (cascader grouped by provider: Gemini 3 Flash/Pro, GPT-5.2/Mini, Claude Sonnet/Opus/Haiku), configure max tokens (1024–8192) and temperature (0.1–0.9).
- Audit Log: Full history of AI interactions. ProTable with expandable rows showing the original query and AI response. Filterable by user, tool, status, IP address. Includes response time metrics.
5.8 Common Frontend Patterns
| Pattern | Usage |
|---|---|
| ProTable (Ant Design Pro) | Every list page uses ProTable with built-in search, filtering, pagination, and toolbar customization. |
| ProConfigProvider with trTR locale | All tables and forms display Turkish text for column headers, toolbar buttons, and empty states. |
| PageContainer | Consistent page headers with breadcrumbs and title. |
| Modal forms | Create/Edit operations use centered modals with form validation. |
| Status Tags | Color-coded Ant Design Tags for status display (green=active, red=suspended, blue=queued, etc.). |
| Turkey/Istanbul timezone | All timestamp displays convert to Europe/Istanbul using toLocaleString(). |
| message.success / message.error | Ant Design notifications for operation feedback, all in Turkish. |
5.9 The API Layer
The frontend communicates with the backend through a centralized API utility (src/utils/api.ts) that dynamically resolves the backend URL based on the deployment context:
- HTTPS mode: Uses
/apiproxy (reverse proxy handles routing). - HTTP localhost:
http://localhost:8000/api(development). - HTTP local network:
http://{hostname}:8000/api(factory LAN access). - Custom override:
window.BACKEND_URLfor special deployments.
Every API call includes the Authorization: Bearer {token} header, retrieved from localStorage. The authentication service (src/services/realAuth.ts) provides login(), logout(), getCurrentUser(), and getToken() functions that all pages import.
6. THE SUBMODULES
The Admin module is organized into four submodules, ordered by operational flow: printers must be configured before print jobs can be monitored, AI settings stand alone, and user management governs access to everything.
Yazıcı Yönetimi (Printer Management)
Registry and configuration of network label printers on the factory floor. IP/port configuration, material type assignments, connection testing, and status monitoring.
Yazdırma İzleme (Print Job Monitor)
Real-time dashboard monitoring all label print jobs across four printers. Queue statistics, job history, retry for failures, and cancel for queued jobs.
AI Ayarları (AI Settings)
Configuration interface for the ERP’s AI assistant. Multi-provider API key management, model selection, parameter tuning, usage statistics, and comprehensive audit logging of every AI interaction.
Kullanıcı Yönetimi (User Management)
Full lifecycle management of user accounts: creation, editing, suspension, deletion, password reset, and the granular permission editor. Defines who exists in the system and what they can do.
6.1 Yazıcı Yönetimi (Printer Management)
6.1.1 Purpose and Business Context
Every raw material that enters the factory receives a printed A6 label with a QR code, weight, supplier, lot number, and entry date. These labels are the physical identity of the material — they’re scanned at every downstream station (lab testing, production, stock). The label printers are industrial network devices (TCP port 9100 / IPP port 631) placed in specific locations: the laboratory and three production lines. Printer Management is the registry where these physical devices are configured, tested, and assigned to material types.
Without this submodule, the system wouldn’t know where to send print jobs. It answers four questions: What printers exist? Where are they? Are they online? What material types can each printer handle?
6.1.2 Database Schema
Single table: printers (model file: 66 lines)
| Column | Type | Constraints | Purpose |
|---|---|---|---|
id | Integer PK | Auto-increment, indexed | Unique printer identifier |
name | String(100) | NOT NULL | Human-readable name (e.g. “Lab Yazıcısı”) |
description | Text | NULLABLE | Optional description |
ip_address | String(50) | NOT NULL | Network IP (e.g. 192.168.0.182) |
port | Integer | NOT NULL, default 9100 | Network port (9100 for RAW, 631 for IPP) |
status | String(20) | NOT NULL, default “online” | Current status: online / offline / error / maintenance |
is_active | Boolean | NOT NULL, default True | Soft-enable/disable toggle |
assigned_materials | Text | NULLABLE | JSON array of material type codes this printer handles (e.g. ["raw_copper","raw_tin"]) |
location | String(100) | NULLABLE | Physical location (e.g. “Laboratuvar”, “Üretim - Hat 1”) |
created_at | DateTime | NOT NULL, auto | Creation timestamp |
updated_at | DateTime | NOT NULL, auto + onupdate | Last modification timestamp |
last_checked | DateTime | NULLABLE | Last connection test timestamp |
PrinterStatus Enum
class PrinterStatus(str, Enum): ONLINE = "online" OFFLINE = "offline" ERROR = "error" MAINTENANCE = "maintenance"
Default Printers (Migration Seed Data)
| ID | Name | IP | Port | Location | Assigned Materials |
|---|---|---|---|---|---|
| 1 | Lab Yazıcısı | 192.168.0.182 | 9100 | Laboratuvar | A, B, C, D, E, F (all types) |
| 2 | Üretim Yazıcısı 1 | 192.168.0.183 | 9100 | Üretim - Hat 1 | [] (none) |
| 3 | Üretim Yazıcısı 2 | 192.168.0.184 | 9100 | Üretim - Hat 2 | [] (none) |
| 4 | Üretim Yazıcısı 3 | 192.168.0.185 | 9100 | Üretim - Hat 3 | [] (none) |
6.1.3 API Contract — 5 Endpoints
| Method | Path | Permission | What It Does |
|---|---|---|---|
GET | /api/printers/list | Any authenticated | List all printers. Optional ?active_only=true filter. Parses assigned_materials from JSON. |
POST | /api/printers/create | super_admin only | Create new printer. Required: name, ip_address. Default port=9100, status=online, is_active=true. Converts assigned_materials array to JSON string. |
PUT | /api/printers/update/{id} | super_admin only | Update printer. Partial update — only provided fields change. Stamps updated_at. |
POST | /api/printers/{id}/test | super_admin or lab_user | Test connection via CUPS lpstat -p. Updates status to online/offline and stamps last_checked. |
DELETE | /api/printers/{id} | super_admin only | Delete printer. Blocked if printer has pending jobs (QUEUED or PRINTING). Error: “Bu yazıcının {n} bekleyen işi var.” |
6.1.4 CRUD Flow
Create:
- Click “Yeni Yazıcı Ekle” button in toolbar
- Modal (600px) opens with 7 fields: name (required), description, location, IP address (required), port (default 9100), assigned materials (multi-select: A=Bakır, B=Kalay, C=Plastik, D=Katalizör, E=Boya, F=Antirodent), status
- On submit:
POST /api/printers/create - Backend creates record with
is_active=true,status=online, converts assigned_materials array to JSON string - On success: toast “Yazıcı ‘{name}’ eklendi” → modal closes → table reloads
- Note: New printer requires application restart for its background print worker to initialize
Read:
- Page loads →
GET /api/printers/list - ProTable (11 columns) renders: ID, name, description, IP (blue tag), port, location, status (color-coded tag), assigned materials (letter tags), active (check/cross icon), actions
- Client-side search filters across name, description, IP, location, status, port, and ID simultaneously
Update:
- Click edit icon in Actions column
- Same modal opens pre-filled with current values
- Modify any fields → click “Güncelle”
PUT /api/printers/update/{id}→ backend updates only provided fields, stampsupdated_at- On success: toast “Yazıcı ‘{name}’ güncellendi” → modal closes → table reloads
Delete:
- Click delete icon (danger button) in Actions column
- Confirmation modal: “{name} yazıcısını silmek istediğinize emin misiniz?”
- On confirm:
DELETE /api/printers/{id} - Backend checks for pending print jobs (QUEUED or PRINTING status)
- If pending jobs exist: deletion blocked with error “Bu yazıcının {n} bekleyen işi var. Önce işleri tamamlayın veya iptal edin.”
- If no pending jobs: printer deleted → toast → table reloads
Test Connection:
- Click test icon (ApiOutlined) in Actions column
POST /api/printers/{id}/test- Backend calls
PrintService.test_printer_connection()which executeslpstat -p {printer_name}via subprocess - Possible results: “connected” (idle), “busy”, “not_found”, “timeout”, “error”
- Backend updates
printer.status(ONLINE or OFFLINE) and stampslast_checked - On success: toast with printer name → table reloads (status column reflects new state)
- On failure: warning message → table reloads (status shows “Çevrimdışı” in red)
6.1.5 The Print Service — Label Generation Engine
File: app/services/print_service.py (576 lines). This is the core service that generates and prints A6 labels.
Label Specifications
| Property | Value |
|---|---|
| Size | A6 (827 × 1165 pixels) |
| DPI | 200 |
| QR Code Size | 300px, centered, error correction level H |
| Timezone | Europe/Istanbul (Turkey) |
| Print Protocol | CUPS/IPP via lp command with A6 media + fit-to-page |
| Timeout | 10 seconds per print command |
Label Content Layout
- Centered QR code (300px) with QR text below
- Lot number (if exists)
- Weight (kg)
- Material type / product name
- Date and time (Turkey timezone)
- “Created by” user name
- Notes (if exists)
Key Service Methods
| Method | What It Does |
|---|---|
generate_qr_code(data, size) | Creates QR code image with error correction H |
create_label_image(material_data) | Builds A6 label with QR, weight, supplier, lot, dates using PIL |
print_via_cups(image, printer_name, copies) | Saves to temp PNG → lp command with A6 media → cleans up |
print_label(material_data) | Generate + print single label (1 copy) |
print_label_with_quantity(material_data, copies) | Generate + print with multiple copies |
print_multiple_labels(materials) | Batch print: sequential processing with success/fail counts |
get_label_preview(material_data) | Returns base64-encoded label image (no printing) |
test_printer_connection(printer_name) | Runs lpstat -p subprocess, returns connected/busy/not_found/timeout/error |
get_available_printers() | Runs lpstat -p -d, parses available CUPS printers |
Font Fallback Chain
The service tries fonts in platform order: macOS (/System/Library/Fonts/) → Linux (/usr/share/fonts/) → Windows (C:/Windows/Fonts/) → PIL default. This makes label generation work across all development and production environments.
6.1.6 The Print Worker — Background Job Processing
File: app/services/print_worker.py (292 lines). Each active printer gets a dedicated background worker thread.
Architecture
Worker Lifecycle
- Init: Generates CUPS printer name from IP (e.g.
Printer_192_168_0_182), ensures printer is registered in CUPS vialpadmin, sets A6 as default media vialpoptions - Poll loop: Every 1 second, queries DB for next QUEUED job for this printer (ordered by
requested_at— FIFO) - Process job: Sets status to PRINTING → stamps
started_at→ fetches material + user data → callsprint_label_with_quantity() - On success: Status → COMPLETED, stamps
completed_at - On failure: If
retry_count < 3: increment, set back to QUEUED. Ifretry_count ≥ 3: status → FAILED
CUPS Auto-Registration
When a worker starts, if the printer isn’t registered in CUPS, the worker automatically adds it:
# Auto-register printer in CUPS
lpadmin -p Printer_192_168_0_182 -E -v ipp://192.168.0.182:631/ipp/print -m everywhere
lpoptions -p Printer_192_168_0_182 -o media=A6
6.1.7 Material Assignment System
Each printer can be assigned specific material types via the assigned_materials JSON array. The six material type codes map to the factory’s QR code letter system:
| Code | Letter | Material |
|---|---|---|
raw_copper | A | Bakır (Copper) |
raw_tin | B | Kalay (Tin) |
raw_plastic | C | Plastik (Plastic) |
raw_catalyst | D | Katalizör (Catalyst) |
raw_dye | E | Boya (Dye) |
raw_antirodent | F | Antirodent |
The Lab printer (ID 1) is assigned all six types since it handles all material entries. Production printers have empty assignments — they are used for production-stage labeling.
6.1.8 Frontend Architecture
Single file: src/pages/Admin/PrinterManagement/index.tsx (527 lines). Route: /admin/printer-management.
Component Structure
ProTable (11 columns)
ID (fixed left), name (fixed left), description, IP address (blue tag), port, location, status (color-coded tag: green=online, red=offline, orange=error, gray=maintenance), assigned materials (letter tags), active (check/cross icon), actions (fixed right).
Add/Edit Modal (600px)
7 form fields: name (required), description (textarea), location, IP address (required), port (number, default 9100), assigned materials (multi-select with material codes), status (dropdown: online/offline/maintenance/error).
Toolbar
Search input (client-side filtering across all columns) + “Yeni Yazıcı Ekle” primary button. Table options: density toggle, reload, column settings.
Action Buttons (per row)
Test connection (ApiOutlined), Edit (EditOutlined), Delete (DeleteOutlined, danger). Delete shows confirmation modal before proceeding.
Client-Side Search
The search input filters across all visible columns simultaneously: name, description, IP address, location, status (both English and Turkish terms), port, and ID. This is pure frontend filtering — the full dataset is always fetched.
No WebSocket Integration
Unlike other modules, Printer Management does not use WebSocket for real-time updates. Table refreshes happen via manual actionRef.current?.reload() after each mutation (create, update, delete, test).
6.1.9 Permission Model
Page ID: admin.yazici_yonetimi
| Permission ID | Label | Description | Critical |
|---|---|---|---|
access_page | Sayfaya Erişim | View the page | |
view_table | Tablo Görüntüle | See the printer table | |
create_printer | Yeni Yazıcı Ekle | Create a new printer | |
edit_printer | Yazıcı Düzenle | Update printer settings | |
test_connection | Bağlantı Test | Test printer connectivity | |
assign_materials | Malzeme Ata | Assign material types to printer | |
delete_printer | Yazıcı Sil | Delete a printer | ● |
Backend enforces role-based: only super_admin can create/update/delete, super_admin + lab_user can test. Frontend registers these 7 granular permissions in buttonRegistry for the permission editor to discover.
6.1.10 How Printer Management Connects to the Rest of the System
→ Print Queue (6.2)
When Material Entry queues a print job, it specifies a printer_id (1–4). The print queue routes validate this ID against the printers table. The background workers are initialized from the printers table at application startup.
→ Material Entry (Hammadde Girişi)
After a material is entered, the success modal offers a “Print Label” button. This calls POST /api/print-queue/queue/{material_id}?printer_id=X, sending the job to the queue for the selected printer.
→ Materials List (Hammaddeler Listesi)
The reprint action in Materials List also targets a specific printer by ID. The GET /api/materials/print/preview/{id} endpoint uses get_default_print_service() which loads printer config from the printers table (prefers “Lab” printer, falls back to first active).
→ Application Startup (main.py)
initialize_workers(db_url) reads all active printers from DB, creates one PrintWorker daemon thread per printer, auto-registers each in CUPS, and starts polling. Workers are the bridge between the digital queue and physical hardware.
Architecture insight: Printer Management is a hardware abstraction layer. The rest of the system never talks to printers directly — they queue jobs with a printer_id, and the worker threads handle the physical communication via CUPS/IPP. This decoupling means a printer can be replaced, re-IPed, or taken offline without any code changes in Material Entry or Materials List. The assigned_materials field is metadata for the UI (helping users pick the right printer) but is not enforced at the queue level — any printer can technically print any material’s label.
6.2 Yazdırma İzleme (Print Job Monitor)
6.2.1 Purpose and Business Context
Every material label print passes through a job queue. The job is created the moment a user clicks “Yazdır” in Material Entry or Materials List, and a background worker picks it up within one second. Yazdırma İzleme is the real-time dashboard that makes this invisible pipeline visible: how many jobs are waiting, which are printing right now, which succeeded, which failed, and why.
It serves two purposes: monitoring (are the printers keeping up? any failures?) and intervention (retry a failed job, cancel a queued one). Without this submodule, a failed print would silently vanish after 3 retries, and nobody would know a material is missing its label.
6.2.2 Database Schema
Single table: print_jobs (model file: 69 lines)
| Column | Type | Constraints | Purpose |
|---|---|---|---|
id | Integer PK | Auto-increment, indexed | Unique job identifier |
material_id | Integer FK → raw_materials | NOT NULL | Which material this label is for |
qr_code | String(50) | NOT NULL | QR code text. Includes “ - REDDEDILDI” suffix for rejected materials |
printer_id | Integer | NOT NULL, indexed | Target printer (1–4) |
copies | Integer | NOT NULL, default 1 | Number of label copies to print |
status | String(20) | NOT NULL, indexed, default “queued” | Job lifecycle state |
retry_count | Integer | NOT NULL, default 0 | How many times the worker retried (max 3) |
error_message | Text | NULLABLE | Error details if job failed |
requested_by | Integer FK → users | NOT NULL | User who triggered the print |
requested_at | DateTime | NOT NULL, auto | When the job was queued |
started_at | DateTime | NULLABLE | When the worker started printing |
completed_at | DateTime | NULLABLE | When the job completed or failed |
material_type | String(50) | NULLABLE, denormalized | Material type for fast display without join |
lot_number | String(100) | NULLABLE, denormalized | Lot number for fast display |
supplier_name | String(200) | NULLABLE, denormalized | Supplier name for fast display |
PrintJobStatus Enum (5 states)
class PrintJobStatus(str, Enum): QUEUED = "queued" # Waiting in queue PRINTING = "printing" # Worker is printing now COMPLETED = "completed" # Label printed successfully FAILED = "failed" # Failed after 3 retries CANCELLED = "cancelled" # Manually cancelled by admin
Job Lifecycle
Denormalization Strategy
Three columns (material_type, lot_number, supplier_name) are deliberately denormalized from raw_materials and suppliers. This allows the monitoring dashboard to display all job data without expensive joins across tables — critical for a page that lists up to 500 jobs.
6.2.3 API Contract — 5 Endpoints
| Method | Path | Permission | What It Does |
|---|---|---|---|
POST | /api/print-queue/queue/{material_id}?printer_id=X | super_admin, lab_user, operator | Queue a print job. Creates PrintJob with QUEUED status. Copies from material.quantity (default 1). Appends “ - REDDEDILDI” to QR if material is rejected. Denormalizes material_type, lot_number, supplier_name. |
GET | /api/print-queue/jobs | super_admin only | List jobs with filters: ?status=, ?printer_id=, ?limit= (default 100, max 500). Ordered by requested_at DESC. Calculates duration seconds if completed. |
POST | /api/print-queue/jobs/{id}/retry | super_admin or lab_user | Reset failed job: status → QUEUED, retry_count → 0, clears error_message, resets timestamps. Worker re-picks it. |
DELETE | /api/print-queue/jobs/{id} | super_admin only | Cancel a queued job. Blocked if status is PRINTING (“Devam eden iş iptal edilemez”). Sets status → CANCELLED, stamps completed_at. |
GET | /api/print-queue/stats | super_admin only | Returns job counts: { queued, printing, completed, failed, total }. |
6.2.4 CRUD Flow
Create (Queue a Print Job):
- In Materials List, click the print icon on a material row (disabled for palettes/reels)
- Printer selection modal opens showing: QR code, label count (
material.quantityor 1), and a dropdown of active printers fetched fromGET /api/printers/list?active_only=true - Select printer → click “Yazdır”
POST /api/print-queue/queue/{material_id}?printer_id=X- Backend validates material exists, gets supplier name from relationship, checks if material is rejected (appends “ - REDDEDILDI” to QR), denormalizes type/lot/supplier into the job row
- On success: toast “Yazdırma işi kuyruğa eklendi (#123)” → modal closes
- Background worker picks up the job within 1 second and starts printing
Read (Monitor Jobs):
- Navigate to /admin/print-monitor
- Page loads →
GET /api/print-queue/jobs?limit=200+GET /api/print-queue/stats - 4 stat cards render at the top: Bekleyen (gray), Yazdırılıyor (blue), Tamamlanan (green), Başarısız (red)
- ProTable (16 columns) renders all jobs with: ID, QR code (blue tag), material type (color-coded Turkish tag), lot, supplier, printer (filterable 1–4), copies (bold), status (color-coded tag), retry count (X/3 warning tag if > 0), error message (red with tooltip), user, requested/started/completed timestamps (Turkey timezone), duration (seconds), actions
- Client-side search filters across QR code, lot, supplier, user, status, material type, ID, and printer — including Turkish translations
Retry (Failed → Queued):
- Retry button (RedoOutlined) appears only on rows with status “failed”
- Click retry →
POST /api/print-queue/jobs/{id}/retry - Backend resets: status → QUEUED, retry_count → 0, clears error_message, nulls started_at and completed_at
- On success: toast “İş #{id} tekrar kuyruğa eklendi” → table + stats reload
- Worker re-picks the job on next poll cycle (within 1 second)
Cancel (Queued → Cancelled):
- Cancel button (CloseCircleOutlined, danger) appears only on rows with status “queued”
- Click cancel → confirmation modal: “İş #{id} iptal edilsin mi?” with “İptal Et” (danger) / “Vazgeç”
- On confirm:
DELETE /api/print-queue/jobs/{id} - Backend validates job is not PRINTING (blocks with “Devam eden iş iptal edilemez”), sets status → CANCELLED, stamps completed_at
- On success: toast “İş #{id} iptal edildi” → table + stats reload
6.2.5 The Background Worker — How Jobs Actually Get Printed
The connection between this monitoring page and physical printing is the PrintWorker (292 lines, described in 6.1.6). Here’s the exact data flow when a worker processes a job:
The material_data Dict (Built by Worker)
| Key | Source |
|---|---|
qr_code | job.qr_code (includes “REDDEDILDI” if rejected) |
material_type | job.material_type (denormalized) |
lot_number | job.lot_number or material.lot_number |
supplier_name | job.supplier_name (denormalized) |
weight | material.weight (live from DB) |
received_date | material.received_date (live from DB) |
entered_by | user.full_name or user.username (live lookup) |
notes | material.notes (live from DB) |
Note the hybrid approach: some fields come from the denormalized job row (fast, no join), while weight, date, user name, and notes are fetched live from DB (always current).
Retry Logic (Automatic)
| retry_count | Print Fails | Result |
|---|---|---|
| 0 | First attempt fails | retry_count → 1, status → QUEUED, error stored |
| 1 | Second attempt fails | retry_count → 2, status → QUEUED |
| 2 | Third attempt fails | retry_count → 3, status → QUEUED |
| 3 | Fourth attempt fails | status → FAILED, completed_at stamped |
After the automatic 3 retries exhaust, the job shows as “Başarısız” with a retry count of “3/3” in the dashboard. An admin or lab user can then manually retry it (resetting the counter to 0).
6.2.6 Frontend Architecture
Single file: src/pages/Admin/PrintJobMonitor/index.tsx (509 lines). Route: /admin/print-monitor.
Component Structure
4 Stat Cards (Row, gutter 16)
Bekleyen İşler (gray #8c8c8c), Yazdırılıyor (blue #1890ff), Tamamlanan (green #52c41a), Başarısız (red #ff4d4f). Font size 24px. Refreshed after every table load and after retry/cancel actions.
ProTable (16 columns)
Default page size 50, max 500. Scroll width 1800px. Columns: ID (sortable, fixed left), QR code (blue tag), material type (color-coded), lot, supplier, printer (filterable), copies (bold), status (filterable, color-coded), retry count, error (red tooltip), user, 3 timestamps (Turkey TZ), duration, actions (fixed right).
Toolbar
Search input (client-side filtering across all columns including Turkish translations) + table options (density toggle, reload, column settings). Title: “Yazdırma İşleri”.
Action Buttons (conditional)
Retry (RedoOutlined): only visible on “failed” rows. Cancel (CloseCircleOutlined, danger): only visible on “queued” rows. No actions on “printing”, “completed”, or “cancelled” rows.
Date Formatting
All timestamps are stored as UTC in the database. The frontend converts them using dayjs.utc(dateStr).tz('Europe/Istanbul') and displays in D.MM.YYYY HH:mm:ss format (e.g. “18.02.2026 14:30:45”).
Material Type Tags (Color Mapping)
| Type | Tag Color | Turkish Label |
|---|---|---|
raw_copper | orange | Bakır |
raw_tin | cyan | Kalay |
raw_plastic | purple | Plastik |
raw_catalyst | magenta | Katalizör |
raw_dye | volcano | Boya |
raw_antirodent | geekblue | Antirodent |
No WebSocket — Manual Refresh
Unlike other pages in the ERP, the Print Job Monitor does not use WebSocket for real-time updates. The table and stats refresh via actionRef.current?.reload() after mutations (retry, cancel) and on the table’s built-in reload button. There is no automatic polling.
6.2.7 Permission Model
Page ID: admin.yazdirma_izleme
| Permission ID | Label | Description |
|---|---|---|
access_page | Sayfaya Erişim | Yazdırma izleme sayfasını görüntüleme |
view_jobs | İşleri Görüntüle | Yazdırma işlerini görme |
view_stats | İstatistikler | İstatistik kartlarını görme |
retry_job | Tekrar Dene | Başarısız işi tekrar çalıştırma |
cancel_job | İşi İptal Et | Bekleyen işi iptal etme |
refresh_data | Yenile | Veriyi yenileme butonu |
Backend enforces: super_admin can do everything. lab_user can retry failed jobs. operator can only queue new jobs (via Material Entry) but cannot access the monitoring dashboard.
6.2.8 How Print Job Monitor Connects to the Rest of the System
← Hammadde Girişi (Source)
After entering a material, the success modal’s “Yazdır” button queues a print job via POST /print-queue/queue/{id}. This is the primary job creation path.
← Hammaddeler Listesi (Source)
The reprint button on any material row opens a printer selection modal (showing QR code + label count), then queues via the same endpoint. Disabled for palettes and reels.
→ Yazıcı Yönetimi (6.1)
Print workers are initialized from the printers table. The printer selection modal in Materials List fetches active printers from /api/printers/list. Printer ID in the job maps to a physical device configured in 6.1.
→ PrintWorker (Background)
One daemon thread per printer polls the print_jobs table every 1 second, processes QUEUED jobs in FIFO order, handles automatic retries (max 3), and updates job status. Described in detail in 6.1.6.
Architecture insight: The print queue is a non-blocking, fire-and-forget system. When a user clicks “Yazdır”, the API returns immediately with a job ID — the user doesn’t wait for the physical printer. The worker processes it asynchronously. This design means Material Entry never blocks on slow or offline printers. The monitoring dashboard exists because this asynchrony creates an observability gap: you need a place to see what happened after you fired and forgot. The denormalization of material data into the job row is a deliberate performance trade-off — the monitoring page loads fast even with 500 jobs because it never joins to raw_materials or suppliers.
6.3 AI Ayarları (AI Settings)
6.3.1 Purpose and Business Context
The ERP includes an AI assistant currently powered by Google Gemini via API integration (with OpenAI and Anthropic Claude fully integrated and ready). The assistant can answer questions about orders, materials, production, and inventory by calling internal APIs on behalf of users. AI Ayarları is the configuration and monitoring center for this assistant: API key management, model selection, parameter tuning, usage statistics, and a comprehensive audit trail of every AI interaction.
The current API-based integration is a fast-track demo layer — it provides immediate AI capabilities while the long-term vision develops in parallel. The roadmap is to build and deploy custom, locally-hosted models across the entire factory: production optimization, quality control, predictive maintenance, anomaly detection — essentially invisible AI agents that monitor and control everything constantly. For the language/search component specifically, a lightweight ~1B parameter model (such as Qwen or similar) will be fine-tuned for ERP-specific search query understanding, replacing the external API dependency entirely. The multi-provider architecture in this settings page was designed with this transition in mind — swapping from an external API to a local model endpoint is a configuration change, not a rewrite.
The AI agent itself lives in a separate service (solen_ai_service), external to the main backend. This submodule provides the control plane — the knobs and dashboards that let an administrator manage the AI without touching code or config files.
6.3.2 Database Schema — The 47-Column Audit Log
The AI system uses two tables for forensic-grade audit logging:
ai_audit_logs (47 columns)
Every single AI interaction is recorded with full context. The columns are organized into 8 groups:
| Group | Columns | What It Captures |
|---|---|---|
| Identity (10) | user_id, username, user_type, session_id, session_token, ip_address, user_agent, device_type, browser, os | Who asked, from where, on what device |
| Query (6) | original_query, query_language, query_length, query_type, query_category, detected_entities | What was asked, classified and parsed (e.g. {"order_id": 377}) |
| AI Processing (9) | ai_model, ai_model_version, ai_preprocessing, ai_reasoning, ai_confidence, selected_tool, tool_params, tool_params_sanitized, alternative_tools | How the AI decided what to do, which tool it picked, what alternatives it considered |
| API Call (6) | api_endpoint, api_method, api_request_headers, api_response_status, api_response_data, api_response_size_bytes | The internal API call the AI made on behalf of the user |
| Result (6) | success, error_type, error_message, error_stack, final_response, response_type | What the AI answered, whether it succeeded, error details if not |
| Performance (4) | total_duration_ms, ai_processing_ms, api_call_ms, response_generation_ms | Timing breakdown: AI thinking + API call + response generation |
| Security (4) | security_flags, rate_limit_status, was_blocked, block_reason | Injection detection, rate limiting, blocked queries |
| Meta (2) | created_at, metadata | Timestamp and extensible JSON metadata |
11 indexes optimize queries: single-column on user_id, username, session_id, ip_address, selected_tool, success, created_at; composite on (user_id, created_at), (selected_tool, created_at), (success, created_at), (ip_address, created_at).
ai_audit_log_summaries (12 columns)
Daily aggregated statistics: total/successful/failed/blocked queries, unique users and IPs, top tools, top categories, average response times, error distribution. One row per day. Not currently exposed via API — reserved for future analytics dashboards.
6.3.3 API Contract — 12 Endpoints
| Method | Path | What It Does |
|---|---|---|
POST | /api/ai/chat | Send a query to the AI agent. Checks if AI is enabled. Returns response, tools used, timing. Sanitizes errors for users. |
GET | /api/ai/status | Check AI readiness: API key configured, model name, agent status (ready/error/not_initialized). |
GET | /api/ai/models | List available models grouped by provider with tier, description, recommended flag, coming_soon flag. |
POST | /api/ai/reset | Destroy current agent singleton. Reinitializes on next request with latest config. |
GET | /api/ai/config | Get current config: enabled, model_name, max_tokens, temperature, key status. |
POST | /api/ai/config | Update config. Partial updates supported. Saves to ai_config.json. |
GET | /api/ai/stats | Usage statistics from audit table: total/successful/failed queries, avg response time, today’s count. |
GET | /api/ai/audit-logs | Paginated audit log entries. Params: ?limit=100&offset=0. Returns key fields for display. |
GET | /api/ai/api-keys/status | Status of all 3 provider keys: configured flag + masked preview (first 8 + “...” + last 4 chars). |
POST | /api/ai/api-keys/save | Save API key for a provider. Validates length (≥ 20), checks prefix (warning only). Writes to .env, updates runtime env, resets agent. |
DELETE | /api/ai/api-keys/{provider} | Remove API key from .env and runtime environment. |
POST | /api/ai/api-keys/test/{provider} | Test key validity: Gemini via client instantiation, OpenAI/Anthropic via models endpoint. |
6.3.4 CRUD Flow
Create / Update API Key:
- Navigate to “API Anahtarları” tab → 3 provider cards (Google Gemini, OpenAI, Anthropic Claude) each with logo, status tag, and password input
- Enter API key in the password field → click “Kaydet”
POST /api/ai/api-keys/savewith{ provider, api_key }- Backend validates: not empty, ≥ 20 chars, checks prefix (AIza for Gemini, sk- for OpenAI, sk-ant- for Anthropic — warning only, not blocking)
- Writes to
solen_ai_service/.envfile, updates runtimeos.environ, resets AI agent singleton - On success: toast “API anahtarı kaydedildi” + prefix warning if applicable → masked key shown on card → status refreshes
Read (Overview Dashboard):
- Navigate to /admin/ai-settings → “Genel Bakış” tab loads
- 4 stat cards: AI status (active/inactive), today’s queries, total queries, average response time
- Success rate card with circular progress chart + successful/failed counts
- Connection test card: shows status, API key status, current model. “Bağlantıyı Test Et” button calls
GET /api/ai/status
Update Model Settings:
- Navigate to “Model Ayarları” tab
- Form with 4 fields: AI enabled (switch), model (cascader: Provider → Model, with tier tags and recommended stars), max tokens (select: 1024/2048/4096/8192), temperature (select: 0.1/0.3/0.5/0.7/0.9)
- Click “Ayarları Kaydet”
POST /api/ai/config→ saves toai_config.jsonPOST /api/ai/reset→ destroys current agent, reinitializes with new config on next query- Toast: “Ayarlar kaydedildi - AI yeniden başlatıldı”
Delete API Key:
- Click “Sil” button on a provider card
- Confirmation modal
DELETE /api/ai/api-keys/{provider}- Backend removes key from
.envfile andos.environ - Toast: “API anahtarı silindi” → card reverts to unconfigured state
Test API Key:
- Click “Test Et” button on a configured provider card
POST /api/ai/api-keys/test/{provider}- Backend tests: Gemini via client instantiation, OpenAI via
GET https://api.openai.com/v1/models, Anthropic viaGET https://api.anthropic.com/v1/models - Success toast or error toast with details
6.3.5 AI Provider Configuration
| Provider | Env Variable | Key Prefix | Models | Status |
|---|---|---|---|---|
| Google Gemini | GEMINI_API_KEY | AIza | gemini-3-flash (recommended), gemini-3-pro, gemini-2.5-flash, gemini-2.5-pro | Active |
| OpenAI | OPENAI_API_KEY | sk- | gpt-5.2 (recommended), gpt-5-mini, o3, gpt-4.1 | Active |
| Anthropic Claude | ANTHROPIC_API_KEY | sk-ant- | claude-sonnet-4-5 (recommended), claude-opus-4-5, claude-haiku-4-5 | Active |
Key Storage Architecture
API keys are stored in a .env file inside the solen_ai_service directory (separate from the main backend). They are never stored in the database. On save, the key is also set in os.environ for immediate use without restart. Keys are never returned in API responses — only masked previews (first 8 + “...” + last 4 characters).
Configuration Storage
Model settings (enabled, model_name, max_tokens, temperature) are stored in ai_config.json alongside the .env file. Defaults: enabled=true, model=gemini-3-flash, max_tokens=4096, temperature=0.7.
6.3.6 The AI Agent — Singleton Architecture
The AI agent (SolenAIAgent) is a singleton that persists across requests. It’s initialized on the first AI query and reused until explicitly reset.
The agent is reset (destroyed and recreated) when: API keys are saved/deleted, config is updated via the settings page, or POST /api/ai/reset is called. This ensures the agent always uses the latest configuration.
Chat Request Flow
- User sends query via
POST /api/ai/chatwith{ query, conversation_history, user_type } - Backend checks
ai_config.json— ifenabled: false, returns “AI servisi şu an devre dışı” - Gets or creates agent singleton, extracts user info from JWT
- Calls
agent.process_query(query, history, user_type, ip, username, user_id) - Agent internally: preprocesses query, selects tool, calls internal API, generates response, writes audit log
- Backend sanitizes response (hides raw API errors), returns
{ success, response, tool_used, timing_ms }
6.3.7 Audit Logging — Forensic-Grade Tracking
Every AI interaction writes a row to ai_audit_logs with 47 columns of context. The audit log answers 7 questions about every query:
- Who? — user_id, username, user_type, session info, IP, device, browser, OS
- What? — original query, language, type (search/question/command), category (order/material/production), detected entities
- How? — AI model, reasoning, confidence score, selected tool, tool parameters, alternative tools considered
- Where? — API endpoint called, method, response status, response data, response size
- Result? — success/fail, error type and message, final response text, response type
- Performance? — total ms, AI processing ms, API call ms, response generation ms
- Security? — injection flags, rate limit status, blocked flag and reason
6.3.8 Frontend Architecture
Single file: src/pages/Admin/AISettings/index.tsx (1,177 lines). Route: /admin/ai-settings.
4-Tab Layout
Tab 1: Genel Bakış (Overview)
4 stat cards (AI status, today’s queries, total queries, avg response time). Success rate circular progress chart. Connection test card with status indicator, API key flag, and model name.
Tab 2: API Anahtarları (API Keys)
3 provider cards (Gemini, OpenAI, Claude) each with: provider logo, “Aktif” status tag, masked key display, password input, save/test/delete buttons, and link to provider’s API console. Plus AI service account status card.
Tab 3: Model Ayarları (Model Settings)
Form: enabled switch, model cascader (Provider → Model with icons, tier tags, recommended stars, disabled if no API key), max tokens select (1024–8192), temperature select (0.1–0.9). Save triggers config update + agent reset.
Tab 4: Kullanım Geçmişi (Audit Log)
ProTable with expandable rows. Columns: timestamp, username + user_type tag, tool used (blue tag), status (success/error), response time (color-coded: green < 500ms, orange 500–1000ms, red > 1000ms), IP. Expanded row shows: query, AI response, error message. Search, pagination (10/20/50), density controls.
6.3.9 Permission Model
AI Settings does not have granular button-level permissions in the manifest. Access is controlled at the module level via canAdmin — any user with access to the admin module can view AI Settings. Backend endpoints have no explicit permission checks beyond JWT authentication.
This is a deliberate design choice: AI configuration is considered an admin-wide capability, not a per-button permission. In practice, only super admins access the admin module.
6.3.10 How AI Settings Connects to the Rest of the System
→ AI Agent (External Service)
The settings page configures the external solen_ai_service which contains the SolenAIAgent. API keys, model selection, and parameters are stored in files that the agent reads. The agent is imported as a singleton into the backend.
→ AI Search (Query Preprocessing)
ai_search_service.py (256 lines) uses Gemini to preprocess search queries: Turkish character correction, query classification, entity detection. Falls back to rule-based processing if AI is unavailable.
→ All ERP Modules (via AI Chat)
The AI agent can call internal APIs on behalf of users — querying orders, materials, suppliers, production data. It’s a read-only assistant that consumes data from every module but never writes.
→ Audit & Compliance
The 47-column audit log creates a complete forensic trail of every AI interaction: who asked what, how the AI processed it, what API it called, what it answered, and how long it took. This supports both debugging and security auditing.
Architecture insight: The AI system follows a control plane / data plane separation. The control plane (this settings page) manages configuration, keys, and monitoring. The data plane (the external SolenAIAgent) handles actual query processing. This separation means the AI agent can be upgraded, swapped, or scaled independently of the ERP backend. The 47-column audit log is deliberately over-engineered — it captures far more context than needed for basic logging, because AI forensics requires understanding not just what the AI did, but why it chose that action and what alternatives it considered. The multi-provider architecture (all 3 providers — Gemini, OpenAI, and Claude — fully integrated with 30+ models available) means switching AI providers is a configuration change, not a code change.
6.4 Kullanıcı Yönetimi (User Management)
6.4.1 Purpose and Business Context
User Management is the identity and authorization backbone of the entire ERP. Every other module — Hammadde, Teknik, Sipariş, Üretim, Stok, Lab, Admin — depends on this submodule to answer two questions: “Who is this person?” (authentication) and “What are they allowed to do?” (authorization). It is the most complex submodule in the system because it implements a hybrid permission model: legacy role-based access layered with a granular page+button permission system that controls visibility down to individual UI buttons across 8 modules, 26+ pages, and 200+ actions.
The factory has three core user archetypes: Super Admin (full system control, desktop), Lab User (quality testing, desktop), and Operator (machine operation, mobile kiosk). Beyond these, the system supports unlimited custom user types created on-the-fly, each with hand-crafted permissions.
6.4.2 Database Schema — 6 Tables, Joined-Table Inheritance
users (17 columns) — The Core Identity Table
| Column | Type | Details |
|---|---|---|
id | Integer PK | Auto-increment, indexed |
email | String(320) | Unique, indexed, not null |
hashed_password | String(1024) | bcrypt hash, not null |
is_active | Boolean | Default True |
is_superuser | Boolean | Default False (FastAPI-Users legacy) |
is_verified | Boolean | Default False |
username | String(50) | Unique, indexed, not null |
user_type | String(50) | super_admin, lab_user, operator, or custom |
full_name | String(100) | Display name |
status | String(20) | active / inactive / suspended |
last_login | DateTime | Updated on each login |
created_at | DateTime | UTC timestamp |
updated_at | DateTime | Auto-updated on change |
preferred_interface | String(20) | mobile / desktop / auto |
device_info | JSON | Last known device details |
permissions | Text | JSON string — the granular permission object (pages + buttons) |
force_password_change | Boolean | Default False, set True on password reset |
Joined-Table Inheritance — Extension Tables
Each user type has a dedicated extension table linked via user_id FK → users.id (unique, one-to-one). These store type-specific metadata:
| Table | Extra Columns | Purpose |
|---|---|---|
super_admins | admin_level (system/module/user), permissions (JSON list), access_logs (JSON list) | Admin hierarchy, action audit trail |
lab_users | lab_department, certifications (JSON list), test_specializations (JSON list), shift_schedule (JSON dict) | Lab-specific qualifications and shifts |
operators | assigned_machines (JSON list), shift_schedule (JSON dict), skill_level (junior/senior/expert), certifications (JSON list) | Machine assignments and skill tracking |
SQLAlchemy backrefs provide navigation: user.super_admin_profile, user.lab_user_profile, user.operator_profile.
user_sessions (11 columns)
| Column | Type | Details |
|---|---|---|
id | Integer PK | Auto-increment |
user_id | Integer FK | References users.id |
session_token | String(255) | Unique, indexed — the JWT access token |
refresh_token | String(255) | Unique, indexed |
login_time | DateTime | When session started |
logout_time | DateTime | Null until logout |
expires_at | DateTime | Token expiry timestamp |
ip_address | String(45) | IPv6-ready |
user_agent | Text | Browser/device string |
device_type | String(20) | mobile / desktop / tablet |
status | String(20) | active / expired / terminated |
user_roles (7 columns)
Reusable permission templates (role definitions). Each role stores a full permission JSON that can be quickly applied to users.
| Column | Type | Details |
|---|---|---|
id | Integer PK | Auto-increment |
name | String(50) | Unique, e.g. “Lab Technician” |
description | Text | Human-readable role description |
permissions | Text | JSON string — full permission structure |
is_system | Boolean | System roles cannot be deleted |
created_at | DateTime | UTC |
updated_at | DateTime | Auto-updated |
user_activity_logs (11 columns)
Audit trail for every administrative action on users. username is denormalized for fast reads.
| Column | Type | Details |
|---|---|---|
user_id | Integer FK | Who performed the action |
username | String(50) | Denormalized |
action | String(50) | create_user, update_user, suspend_user, delete_user, reset_password |
module | String(50) | Always user_management |
target_type | String(50) | Always user |
target_id | Integer | ID of affected user |
details | Text | JSON string with extra context |
ip_address | String(50) | Client IP |
user_agent | Text | Browser string |
created_at | DateTime | When action occurred |
6.4.3 Authentication System — JWT Tokens and Sessions
Token Architecture
| Token Type | Expiry | Payload | Purpose |
|---|---|---|---|
| Access Token | 12 hours (default) | sub (user_id), username, user_type, permissions, type: "access" | Authenticate every API request via Authorization: Bearer header |
| Refresh Token | 7 days | sub (user_id), type: "refresh", jti (unique token ID) | Generate new access token without re-login |
| Operator Kiosk Token | 6 months | Same as access token | Factory floor kiosk phones — avoid frequent re-login |
The 6-month token is triggered by a hardcoded check: if user.username == 'operator'. This is deliberate — the shared operator account is used on wall-mounted phones that should stay logged in for months.
Password System
| Operation | Implementation |
|---|---|
| Hashing | passlib with bcrypt scheme. CryptContext(schemes=["bcrypt"], deprecated="auto") |
| Verification | pwd_context.verify(plain, hashed) — constant-time comparison |
| Auto-Generation | secrets.choice() over ascii_letters + digits + "!@#$%", 12 characters |
| Validation (Create) | Min 8 chars, must contain uppercase + lowercase + digit |
| Validation (Login) | Min 5 chars (allows simple operator passwords like op123) |
Login Flow End-to-End
- User submits username + password to
POST /auth/login - Backend queries user by username or email (case-insensitive)
- Checks
is_active == Trueandstatus != "suspended" - Verifies password with bcrypt
- Updates
last_logintimestamp - Builds JWT payload with
user_id,username,user_type,permissions - If operator account: sets 6-month expiry; otherwise 12-hour expiry
- Creates
UserSessionrow (session_token, refresh_token, device info, IP, expiry) - Returns
{ access_token, refresh_token, expires_in, user } - Frontend stores tokens and user in
localStorage(keys:solen_auth_token,solen_refresh_token,solen_current_user) - Operators redirect to
/production, others to/welcome
Kiosk Auto-Login
The Login page detects factory kiosk devices in two ways:
- User-Agent detection: checks for “FullyKiosk” in the browser user-agent string (Fully Kiosk Browser is the app running on factory phones)
- Secret URL token: checks for
?kiosk=SOLEN_FACTORY_2024_KIOSK_SECRETin the URL
If either matches, the page automatically logs in as operator / op123 and redirects to /production — no human interaction needed.
Token Refresh and Logout
- Refresh:
POST /auth/refreshwith refresh token → validates, generates new access token, does not create new session - Logout:
POST /auth/logoutwith access token → sets sessionstatus = "terminated"andlogout_time→ frontend clearslocalStorage
6.4.4 The Permission System — The Most Complex Part
The authorization system is a 5-layer architecture that controls access from route-level down to individual button visibility:
Layer 1: The Permission Manifest (permissionManifest.ts, 565 lines)
A static, centralized definition of every permissionable action in the entire ERP. Structured as Module → Page → Button hierarchy:
| Module | Pages | Total Buttons | Highlights |
|---|---|---|---|
| Dashboard | 1 (Main) | 3 | access_page, view_stats, view_charts |
| Hammadde | 3 (Giriş, Liste, Tedarikçi) | 30 | 12 for entry forms, 11 for list (incl. hard_delete), 7 for suppliers |
| Lab | 2 (Panel, Test) | 5 | Lab access, test management |
| Teknik | 6 (Panel, Kablo Tasarım, Makine, Standart, Kablo DB, Markalama) | 32 | 11 for Cable Playground alone |
| Sipariş | 3 (Siparişlerim, Oluştur, Müşteri) | 17 | Order CRUD, customer management |
| Üretim | 12 (Planlama, 6 machine pages, Aktarma, Paletleme, Sevkiyat, Liste, Geçmiş) | ~40 | Start/stop production per machine type, planning, shipping |
| Stok | 3 (Ürün, Hammadde, Projeksiyon) | 14 | View, export, filter, adjust stock |
| Admin | 3 (Yazdırma İzleme, Yazıcı, Kullanıcı) | 22 | 9 for User Management (incl. manage_permissions) |
Total: 8 modules, 26+ pages, 200+ buttons.
Each button has metadata:
interface ButtonPermission { id: string; // e.g. "hard_delete_material" label: string; // e.g. "Kalıcı Silme" description: string; // e.g. "Malzemeyi veritabanından tamamen siler" critical?: boolean; // true = highlighted in red in permission editor }
The manifest also exports helper functions: createEmptyPermissions() (all false), createFullPermissions() (all true), getAllPages(), getAllButtons().
Layer 2: Button Registry (buttonRegistry.ts, 130 lines)
A dynamic complement to the static manifest. Components can register their buttons at runtime via registerPageButtons(). The buildCompleteManifest() function merges static manifest pages with dynamic registry buttons. This allows new pages to self-register permissions without modifying the central manifest file.
Layer 3: Access Control — Route Guards (access.ts, 93 lines)
Runs on every route change. Parses the current user’s permissions JSON and returns boolean flags consumed by routes.ts:
return { canAdmin: canAccessModule('admin'), // guards /admin/* routes canLab: canAccessModule('lab'), // guards /lab/* routes canProduction: canAccessModule('production'), canHammadde: canAccessModule('hammadde'), canTeknik: canAccessModule('teknik'), canSiparis: canAccessModule('siparis'), canStok: canAccessModule('stok'), canDashboard: canAccessModule('dashboard'), // ... plus sub-page guards };
canAccessModule(prefix) returns true if any page under that module has access: true. Routes use these as guards: access: 'canAdmin' in route config.
Layer 4: Permission Utilities — Component-Level (permissions.ts, 145 lines)
Provides fine-grained checks for individual components:
canAccessModule(module)— is module enabled?canPerformAction(module, action)— is specific action allowed?hasSpecialPermission(permission)— e.g.hard_delete
Also exports React wrapper components:
<PermissionButton module="hammadde" action="delete">— shows/hides button based on permission<PermissionGuard module="admin" action="manage">— wraps any component with permission check, shows fallback if denied
Layer 5: Backend Enforcement (permission_checker.py, 200 lines)
The server-side mirror. Even if a user bypasses the frontend, the backend enforces permissions:
| Function | What It Checks |
|---|---|
check_user_permission(user, page_id, button_id) | Parses user’s permissions JSON, checks pages[page_id].access and pages[page_id].buttons[button_id] |
has_special_permission(user, permission) | Checks special_permissions[permission] |
is_permitted_user(user, required_types) | Legacy role-based check + granular fallback |
check_permission_smart(user, types, page, button) | Hybrid: tries legacy role check first, then granular, then graceful fallback. Backward-compatible. |
require_permission(page_id, button_id) | Returns a FastAPI dependency that raises 403 if permission denied |
Super admin bypass: Every permission function checks user.user_type == "super_admin" first and returns True immediately.
The Permission JSON Structure
Stored as a JSON string in the users.permissions column. This is the contract between frontend and backend:
{
"pages": {
"hammadde.hammadde_girisi": {
"access": true,
"buttons": {
"add_copper": true,
"add_tin": false,
"submit_form": true,
"print_qr": true
}
},
"production.planning": {
"access": false,
"buttons": {}
}
}
}
6.4.5 API Contract — 14 Endpoints (9 User Management + 5 Auth)
Authentication Endpoints (5)
| Method | Path | What It Does |
|---|---|---|
POST | /auth/login | Authenticate with username/password. Returns access + refresh tokens and user object. |
POST | /auth/logout | Terminate session. Sets session status to “terminated” and records logout time. |
POST | /auth/refresh | Exchange refresh token for new access token. Does not create new session. |
GET | /auth/me | Get current user info from token. Used by frontend to restore state on page refresh. |
GET | /auth/health | Auth service health check: database connection, version, uptime. |
User Management Endpoints (9)
| Method | Path | Permission | What It Does |
|---|---|---|---|
GET | /user-management/users/list | super_admin | List all users. Supports ?status_filter and ?user_type query params. Parses permissions JSON. |
GET | /user-management/operators | any authenticated | List active users (for operator dropdowns in other modules). Returns only id, username, full_name. |
POST | /user-management/users/create | super_admin | Create user. Validates username/email uniqueness. Auto-generates password if not provided. Stores permissions JSON. Logs activity. |
PUT | /user-management/users/{id} | super_admin | Update user fields (username, email, name, type, status, permissions, password). Logs activity. |
POST | /user-management/users/{id}/suspend | super_admin | Toggle between active and suspended. Logs activity. |
DELETE | /user-management/users/{id} | super_admin | Permanently delete user. Self-deletion blocked (400). Logs activity. |
POST | /user-management/users/{id}/reset-password | super_admin | Generate new 12-char password. Sets force_password_change = True. Returns password (one-time). Logs activity. |
GET | /user-management/roles/list | super_admin | List all role templates with parsed permissions. |
GET | /user-management/activity-logs | super_admin | Query activity logs. Supports ?user_id, ?module, ?action, ?limit filters. |
6.4.6 CRUD Flow
Create (New User):
- Click “Yeni Kullanıcı” button → modal opens with 3 tabs
- Tab 1 – Temel Bilgiler: fill username (required, alphanumeric + underscore), email (validated), full name, password (optional — auto-generated if empty), user type (select or create custom type inline), status, force password change checkbox
- Tab 2 – Yetkiler: the permission editor — 4 quick templates at top (“Tüm Yetkiler”, “Hiçbiri”, “Operatör Varsayılan”, “Lab User Varsayılan”), then scrollable module list. Each module shows pages, each page has: access switch, “Tümü”/“Hiçbiri” quick toggles, individual button checkboxes. Critical buttons highlighted in red. Enabling a button auto-enables page access. Disabling page access disables all its buttons.
- Tab 3 – Önizleme: read-only summary showing accessible modules → pages → enabled buttons with color-coded tags
- Click “Oluştur” →
POST /user-management/users/createwith{ username, email, full_name, user_type, status, permissions, force_password_change, return_password: true } - Backend validates uniqueness (username, email), hashes password with bcrypt, stores user + permissions JSON, logs activity
- If password was auto-generated: modal shows the generated password with a warning “Bu şifreyi not edin, tekrar gösterilmeyecektir” (Note this password, it won’t be shown again)
Read (User List):
- Navigate to
/admin/user-management→ ProTable loads viaGET /user-management/users/list - 7 columns: ID (fixed left), Kullanıcı (username + full_name), Email, Tip (colored tag), Durum (colored tag), Son Giriş (UTC → Istanbul), İşlemler (fixed right)
- Client-side search filters across username, email, full_name, user_type, and status simultaneously
- Pagination: 10/20/50/100 per page
- ProTable density and column settings controls
Update (Edit User):
- Click edit icon (EditOutlined) on user row → same 3-tab modal opens, pre-filled with user data
- Existing permissions loaded and parsed (handles string JSON → object conversion)
- Password field is hidden in edit mode (use Reset Password for that)
- Modify any field or permission → click “Güncelle”
PUT /user-management/users/{id}with changed fields + full permissions object- Backend validates, updates, logs activity
Delete (Permanent):
- Click delete icon (DeleteOutlined, danger) on user row
- Confirmation modal: “Bu kullanıcıyı silmek istediğinizden emin misiniz?”
DELETE /user-management/users/{id}- Backend blocks self-deletion (400 error). Hard deletes user record. Logs activity.
Suspend / Activate:
- Click stop icon (StopOutlined) on user row
POST /user-management/users/{id}/suspend- Toggles status between
activeandsuspended. Suspended users cannot log in.
Reset Password:
- Click key icon (KeyOutlined) on user row
POST /user-management/users/{id}/reset-password- Backend generates cryptographically secure 12-character password, hashes with bcrypt, sets
force_password_change = True - Modal shows new password once with warning. Admin must communicate it to user out-of-band.
6.4.7 User Types and Their Differences
| Super Admin | Lab User | Operator | Custom Types | |
|---|---|---|---|---|
| Interface | Desktop | Desktop | Mobile (kiosk) | Configurable |
| Dashboard Route | /welcome | /lab/dashboard | /production | Defaults to /welcome |
| Token Expiry | 12 hours | 12 hours | 6 months | 12 hours |
| Permission Bypass | Yes — all checks return True | No | No | No |
| Default Modules | All (all_access) | Lab, Hammadde | Production | None (manual) |
| Extension Table | super_admins | lab_users | operators | None |
| Can Manage Users | Yes | No | No | No |
| Auto-Login | No | No | Yes (kiosk) | No |
| Menu Access | ["all"] | ["lab", "welcome"] | ["production"] | Based on permissions |
Custom types (e.g. teknik_user, bakim_user, data) can be created directly in the User Management modal — an input field at the bottom of the user type dropdown allows typing a new type name. The user_type field is a free String(50), not an enum, enabling this flexibility.
6.4.8 Frontend Architecture
Single file: src/pages/Admin/UserManagement/index.tsx (960 lines). Route: /admin/user-management.
ProTable (7 columns)
| # | Column | Width | Details |
|---|---|---|---|
| 1 | ID | 60px, fixed left | Numeric |
| 2 | Kullanıcı | 140px | Username (bold) + full_name below |
| 3 | 200px | Ellipsis on overflow | |
| 4 | Tip | 110px | Colored tag: super_admin (red), lab_user (blue), operator (green), teknik_user (purple), bakim_user (orange) |
| 5 | Durum | 100px | active (success/green), suspended (warning/yellow), inactive (default/gray) |
| 6 | Son Giriş | 140px | dayjs.utc(date).tz('Europe/Istanbul') formatted |
| 7 | İşlemler | 160px, fixed right | 4 action buttons (edit, reset password, suspend, delete) |
The Permission Editor (Tab 2 of Modal)
The most complex UI component in the entire ERP. For each of the 8 modules:
- Module header: module name with icon
- Page cards: each page as a Card component with:
- Header:
Switchfor page access + page label + tag showing “X/Y” enabled buttons count - Quick actions: “Tümü” (enable all) / “Hiçbiri” (disable all) text buttons
- Button grid:
Row/Collayout ofCheckboxcomponents for each button - Critical buttons: highlighted with red text and distinct styling
- Header:
- Smart toggling: enabling any button auto-enables its page access. Disabling page access cascades to disable all buttons.
Quick Templates
| Template | Effect |
|---|---|
| “Tüm Yetkiler” | createFullPermissions() — every page and every button set to true |
| “Hiçbiri” | createEmptyPermissions() — everything false |
| “Operatör Varsayılan” | Production module enabled (except planning page, which is excluded) |
| “Lab User Varsayılan” | Lab and Hammadde modules enabled |
Preview Tab (Tab 3)
Read-only summary using Card components. Shows only accessible modules/pages. Each page lists its enabled buttons as Tags (critical ones in red). Provides a quick visual confirmation before saving.
6.4.9 Activity Logging — Admin Audit Trail
Every user management action is logged to user_activity_logs via the log_activity() helper function:
| Action | Logged Fields |
|---|---|
create_user | Who created, target user ID, username, IP, user agent |
update_user | Who updated, target user ID, changed fields in details |
suspend_user | Who suspended, target user ID, new status |
delete_user | Who deleted, target user ID |
reset_password | Who reset, target user ID (password never logged) |
Logs are queryable via GET /user-management/activity-logs with filters for user_id, module, action, and limit (default 200). Only super admins can access this endpoint.
6.4.10 Permission Model
User Management requires the highest privilege level in the system:
| Action | Required Role |
|---|---|
| View user list | super_admin only |
| Create user | super_admin only |
| Edit user | super_admin only |
| Manage permissions | super_admin only |
| Suspend / activate user | super_admin only |
| Reset password | super_admin only |
| Delete user | super_admin only (self-deletion blocked) |
| View activity logs | super_admin only |
| List operators (dropdown) | any authenticated user |
In the permission manifest, User Management has 9 registered buttons: access_page, view_users, create_user, edit_user, manage_permissions, suspend_user, reset_password, delete_user, view_activity_logs.
Backend enforces role-based access: every endpoint except /user-management/operators checks current_user.user_type != "super_admin" and returns 403 if it fails.
6.4.11 How User Management Connects to the Rest of the System
→ Every Module (Authentication)
Every API endpoint in the entire ERP depends on User Management’s authentication. The get_current_user_dependency() FastAPI dependency is imported and used across all route files. JWT tokens issued here are the only way to access any protected resource.
→ Every Module (Authorization)
The permissions JSON stored per user controls what every user sees and can do across all 8 modules. Route guards (access.ts), component guards (PermissionGuard), and backend checks (permission_checker.py) all read from this same permission structure.
→ Hammadde Girişi (Operator Dropdown)
The GET /user-management/operators endpoint feeds operator/user dropdowns in material entry forms and production pages. Any authenticated user can access this, enabling forms across modules to show “who entered this material.”
→ Production Pages (Kiosk Auto-Login)
The kiosk auto-login system (?kiosk=... token or Fully Kiosk detection) bypasses the login form entirely for factory floor devices, landing directly on /production with the shared operator account’s 6-month token.
Architecture insight: User Management is the gravitational center of the entire ERP. Every module depends on it, but it depends on no other module. The 5-layer permission architecture (manifest → registry → route guards → component guards → backend enforcement) ensures defense-in-depth: even if one layer is bypassed, others still enforce access control. The hybrid permission model (legacy role-based + granular page+button JSON) is a pragmatic engineering decision — the system evolved from simple role checks to granular permissions without requiring a full rewrite. The check_permission_smart() function embodies this: it tries the new system first, falls back to legacy, and gracefully degrades. The 200+ button manifest makes this the finest-grained permission system possible without going to individual record-level access control. The 6-month kiosk token is an unusual but practical choice for a factory environment where shared devices on the production floor need to “just work” without IT intervention every 12 hours.