Back to Modules

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.

February 2026 • Solen Kablo • Living Document

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.

4
SUBMODULES
~36
API ENDPOINTS
10
DATABASE TABLES
3500+
LINES OF CODE

TABLE OF CONTENTS

1. What Admin Does 2. The Data Flow 3. The Database Layer 4. The Backend Architecture 5. The Frontend 6. The Submodules 6.1 Yazıcı Yönetimi 6.2 Yazdırma İzleme 6.3 AI Ayarları 6.4 Kullanıcı Yönetimi 7. Conclusion

1. WHAT ADMIN DOES

The Admin module answers six questions that the entire ERP system depends on:

QuestionWho Answers ItHow
Who is allowed to use the system?User ManagementCreate/edit/suspend/delete user accounts with role assignment
What can each user do?Permission SystemHybrid role-based + granular page.button JSON permissions
How do users prove their identity?AuthenticationUsername/password → JWT access + refresh tokens with session tracking
Which printers exist and where?Printer ManagementNetwork printer registry with IP, port, material assignments, connection testing
What’s happening in the print queue?Print Job MonitorReal-time dashboard of all print jobs across 4 printers with retry/cancel
How is the AI assistant configured?AI SettingsMulti-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

User opens app
POST /auth/login
bcrypt verify
JWT tokens issued
Session recorded

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

API request arrives
JWT decoded
check_permission_smart()
Role check OR page.button check
Allow / 403 Forbidden

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

Material Entry / Materials List
POST /print-queue/queue/{id}
Job → QUEUED
Printer processes
COMPLETED / FAILED

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

User asks AI question
POST /api/ai/chat
AI processes query
Response returned
Audit log written

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

TablePurposeKey ColumnsRelationships
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

TablePurposeKey ColumnsRelationships
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

TablePurposeKey ColumnsRelationships
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

TablePurposeKey ColumnsRelationships
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

RelationshipTypeCascade
users → user_sessions1—NDelete (user deletion purges sessions)
users → super_admins1—0..1None (extension table)
users → lab_users1—0..1None (extension table)
users → operators1—0..1None (extension table)
users → user_activity_logs1—NNone (logs survive user deletion)
users → print_jobs (requested_by)1—NNone
raw_materials → print_jobs (material_id)1—NNone (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 GroupPrefixEndpointsAuth RequiredMin. Role
Authentication/api/auth5Mixed (login is public)None / Any
User Management/api/user-management9YesSuper Admin (most), Any (operators list)
Printers/api/printers5YesSuper Admin (CRUD), Lab User (test)
Print Queue/api/print-queue5YesAny (queue), Super Admin (manage)
AI/api/ai12YesAny (chat), Super Admin (config)

4.2 Authentication Endpoints (5)

MethodPathPurposeKey Behavior
POST/auth/loginUser loginAccepts username/password + device_type + remember_me. Returns access_token, refresh_token, expires_in, user object. Creates session record.
POST/auth/logoutEnd sessionSets session status to “terminated”, stamps logout_time.
POST/auth/refreshRenew access tokenValidates refresh_token, generates new access_token. Does not rotate the refresh token.
GET/auth/meCurrent user infoDecodes JWT, returns full user object with parsed permissions.
GET/auth/healthHealth checkReturns status + database connectivity check.

4.3 User Management Endpoints (9)

MethodPathPurposeKey Behavior
GET/user-management/users/listList all usersSupports status_filter and user_type query params. Parses permissions from JSON string to object.
GET/user-management/operatorsList operatorsReturns only active operators. Available to all authenticated users (needed by production modules).
POST/user-management/users/createCreate userAuto-generates password if not provided. Sets force_password_change. Logs activity.
PUT/user-management/users/{id}Update userPartial update: username, email, full_name, user_type, status, is_active, permissions, password.
POST/user-management/users/{id}/suspendToggle suspendFlips between “active” and “suspended” status. Logs activity.
DELETE/user-management/users/{id}Delete user permanentlyHard delete. Prevents self-deletion (cannot delete your own account).
POST/user-management/users/{id}/reset-passwordForce password resetGenerates secure 12-char random password (letters + digits + symbols). Sets force_password_change=True.
GET/user-management/roles/listList role templatesReturns all UserRole records for use in permission template assignment.
GET/user-management/activity-logsActivity log viewerSupports filtering by user_id, module, action. Default limit 200.

4.4 Printer & Print Queue Endpoints (10)

MethodPathPurposeKey Behavior
GET/printers/listList printersOptional active_only filter.
POST/printers/createAdd printerName, IP, port (default 9100), location, assigned_materials (JSON array).
PUT/printers/update/{id}Edit printerAll fields updatable.
POST/printers/{id}/testTest connectionAttempts network connection to printer IP:port. Updates status to ONLINE or OFFLINE.
DELETE/printers/{id}Remove printerChecks for pending print jobs before allowing deletion.
POST/print-queue/queue/{material_id}Queue label printCreates QUEUED job linked to material and requesting user. Printer selected by query param (1–4).
GET/print-queue/jobsJob historyFilterable by status, printer_id. Max 500 results.
POST/print-queue/jobs/{id}/retryRetry failed jobResets status to QUEUED, increments retry_count.
DELETE/print-queue/jobs/{id}Cancel jobOnly QUEUED jobs can be cancelled (not PRINTING).
GET/print-queue/statsQueue statisticsReturns counts: queued, printing, completed, failed.

4.5 AI Endpoints (12)

MethodPathPurposeKey Behavior
POST/api/ai/chatSend AI queryMain chat endpoint. Processes query, calls tools, returns response.
GET/api/ai/statusService statusReturns AI service health and configuration state.
GET/api/ai/modelsAvailable modelsLists all supported AI models across providers.
POST/api/ai/resetReset AI agentClears conversation context and resets agent state.
GET/api/ai/configGet configurationReturns current: enabled flag, model_name, max_tokens, temperature.
POST/api/ai/configSave configurationUpdates AI settings. Super admin only.
GET/api/ai/statsUsage statisticsQuery counts, success rates, average response times.
GET/api/ai/audit-logsAudit log viewerPaginated (limit + offset). Returns full interaction forensics.
GET/api/ai/api-keys/statusAPI key statusReturns masked key status for each provider (e.g., “sk-***...abc”).
POST/api/ai/api-keys/saveStore API keyWrites to .env file. Supports Gemini, OpenAI, Anthropic. Masks in response.
DELETE/api/ai/api-keys/{provider}Remove API keyRemoves key from .env file.
POST/api/ai/api-keys/test/{provider}Test API keyMakes a lightweight API call to validate the key works.

4.6 Core Services

AuthenticationService

The central authentication engine. Handles five operations:

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:

4.7 FastAPI Dependencies

Admin defines four injectable dependencies that every module in the system imports:

DependencyWhat It DoesUsed By
get_current_user_dependencyExtracts and validates user from JWT token in Authorization headerEvery authenticated endpoint in the system
require_super_adminRaises 403 unless user_type is “super_admin”Admin CRUD operations, system configuration
require_lab_userRaises 403 unless user_type is “lab_user” or “super_admin”Lab test routes, quality control
require_operatorRaises 403 unless user_type is “operator” or aboveProduction 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

PageRouteComponentAccess
Login/user/loginsrc/pages/user/login/index.tsxPublic (unauthenticated)
User Management/admin/user-managementsrc/pages/Admin/UserManagement/index.tsxcanAdmin
Printer Management/admin/printer-managementsrc/pages/Admin/PrinterManagement/index.tsxcanAdmin
Print Job Monitor/admin/print-monitorsrc/pages/Admin/PrintJobMonitor/index.tsxcanAdmin
AI Settings/admin/ai-settingssrc/pages/Admin/AISettings/index.tsxcanAdmin

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:

  1. 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).
  2. 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.
  3. Secret URL token: Accepts ?kiosk=SOLEN_FACTORY_2024_KIOSK_SECRET as 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:

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:

5.8 Common Frontend Patterns

PatternUsage
ProTable (Ant Design Pro)Every list page uses ProTable with built-in search, filtering, pagination, and toolbar customization.
ProConfigProvider with trTR localeAll tables and forms display Turkish text for column headers, toolbar buttons, and empty states.
PageContainerConsistent page headers with breadcrumbs and title.
Modal formsCreate/Edit operations use centered modals with form validation.
Status TagsColor-coded Ant Design Tags for status display (green=active, red=suspended, blue=queued, etc.).
Turkey/Istanbul timezoneAll timestamp displays convert to Europe/Istanbul using toLocaleString().
message.success / message.errorAnt 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:

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.

6.1

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.

5 endpoints printers table TCP port 9100 material assignments
6.2

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.

5 endpoints print_jobs table real-time stats retry mechanism
6.3

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.

12 endpoints ai_audit_logs (47 columns) 3 AI providers .env key storage
6.4

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.

9 endpoints users + 3 extension tables user_roles templates activity_logs hybrid permission model

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)

ColumnTypeConstraintsPurpose
idInteger PKAuto-increment, indexedUnique printer identifier
nameString(100)NOT NULLHuman-readable name (e.g. “Lab Yazıcısı”)
descriptionTextNULLABLEOptional description
ip_addressString(50)NOT NULLNetwork IP (e.g. 192.168.0.182)
portIntegerNOT NULL, default 9100Network port (9100 for RAW, 631 for IPP)
statusString(20)NOT NULL, default “online”Current status: online / offline / error / maintenance
is_activeBooleanNOT NULL, default TrueSoft-enable/disable toggle
assigned_materialsTextNULLABLEJSON array of material type codes this printer handles (e.g. ["raw_copper","raw_tin"])
locationString(100)NULLABLEPhysical location (e.g. “Laboratuvar”, “Üretim - Hat 1”)
created_atDateTimeNOT NULL, autoCreation timestamp
updated_atDateTimeNOT NULL, auto + onupdateLast modification timestamp
last_checkedDateTimeNULLABLELast connection test timestamp

PrinterStatus Enum

class PrinterStatus(str, Enum):
    ONLINE      = "online"
    OFFLINE     = "offline"
    ERROR       = "error"
    MAINTENANCE = "maintenance"

Default Printers (Migration Seed Data)

IDNameIPPortLocationAssigned Materials
1Lab Yazıcısı192.168.0.1829100LaboratuvarA, B, C, D, E, F (all types)
2Üretim Yazıcısı 1192.168.0.1839100Üretim - Hat 1[] (none)
3Üretim Yazıcısı 2192.168.0.1849100Üretim - Hat 2[] (none)
4Üretim Yazıcısı 3192.168.0.1859100Üretim - Hat 3[] (none)

6.1.3 API Contract — 5 Endpoints

MethodPathPermissionWhat It Does
GET/api/printers/listAny authenticatedList all printers. Optional ?active_only=true filter. Parses assigned_materials from JSON.
POST/api/printers/createsuper_admin onlyCreate 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 onlyUpdate printer. Partial update — only provided fields change. Stamps updated_at.
POST/api/printers/{id}/testsuper_admin or lab_userTest connection via CUPS lpstat -p. Updates status to online/offline and stamps last_checked.
DELETE/api/printers/{id}super_admin onlyDelete 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:

  1. Click “Yeni Yazıcı Ekle” button in toolbar
  2. 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
  3. On submit: POST /api/printers/create
  4. Backend creates record with is_active=true, status=online, converts assigned_materials array to JSON string
  5. On success: toast “Yazıcı ‘{name}’ eklendi” → modal closes → table reloads
  6. Note: New printer requires application restart for its background print worker to initialize

Read:

  1. Page loads → GET /api/printers/list
  2. 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
  3. Client-side search filters across name, description, IP, location, status, port, and ID simultaneously

Update:

  1. Click edit icon in Actions column
  2. Same modal opens pre-filled with current values
  3. Modify any fields → click “Güncelle”
  4. PUT /api/printers/update/{id} → backend updates only provided fields, stamps updated_at
  5. On success: toast “Yazıcı ‘{name}’ güncellendi” → modal closes → table reloads

Delete:

  1. Click delete icon (danger button) in Actions column
  2. Confirmation modal: “{name} yazıcısını silmek istediğinize emin misiniz?”
  3. On confirm: DELETE /api/printers/{id}
  4. Backend checks for pending print jobs (QUEUED or PRINTING status)
  5. 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.”
  6. If no pending jobs: printer deleted → toast → table reloads

Test Connection:

  1. Click test icon (ApiOutlined) in Actions column
  2. POST /api/printers/{id}/test
  3. Backend calls PrintService.test_printer_connection() which executes lpstat -p {printer_name} via subprocess
  4. Possible results: “connected” (idle), “busy”, “not_found”, “timeout”, “error”
  5. Backend updates printer.status (ONLINE or OFFLINE) and stamps last_checked
  6. On success: toast with printer name → table reloads (status column reflects new state)
  7. 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

PropertyValue
SizeA6 (827 × 1165 pixels)
DPI200
QR Code Size300px, centered, error correction level H
TimezoneEurope/Istanbul (Turkey)
Print ProtocolCUPS/IPP via lp command with A6 media + fit-to-page
Timeout10 seconds per print command

Label Content Layout

  1. Centered QR code (300px) with QR text below
  2. Lot number (if exists)
  3. Weight (kg)
  4. Material type / product name
  5. Date and time (Turkey timezone)
  6. “Created by” user name
  7. Notes (if exists)

Key Service Methods

MethodWhat 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

App starts initialize_workers(db_url) Load active printers from DB Create PrintWorker per printer start_all_workers()

Worker Lifecycle

  1. Init: Generates CUPS printer name from IP (e.g. Printer_192_168_0_182), ensures printer is registered in CUPS via lpadmin, sets A6 as default media via lpoptions
  2. Poll loop: Every 1 second, queries DB for next QUEUED job for this printer (ordered by requested_at — FIFO)
  3. Process job: Sets status to PRINTING → stamps started_at → fetches material + user data → calls print_label_with_quantity()
  4. On success: Status → COMPLETED, stamps completed_at
  5. On failure: If retry_count < 3: increment, set back to QUEUED. If retry_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:

CodeLetterMaterial
raw_copperABakır (Copper)
raw_tinBKalay (Tin)
raw_plasticCPlastik (Plastic)
raw_catalystDKatalizör (Catalyst)
raw_dyeEBoya (Dye)
raw_antirodentFAntirodent

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 IDLabelDescriptionCritical
access_pageSayfaya ErişimView the page
view_tableTablo GörüntüleSee the printer table
create_printerYeni Yazıcı EkleCreate a new printer
edit_printerYazıcı DüzenleUpdate printer settings
test_connectionBağlantı TestTest printer connectivity
assign_materialsMalzeme AtaAssign material types to printer
delete_printerYazıcı SilDelete 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)

ColumnTypeConstraintsPurpose
idInteger PKAuto-increment, indexedUnique job identifier
material_idInteger FK → raw_materialsNOT NULLWhich material this label is for
qr_codeString(50)NOT NULLQR code text. Includes “ - REDDEDILDI” suffix for rejected materials
printer_idIntegerNOT NULL, indexedTarget printer (1–4)
copiesIntegerNOT NULL, default 1Number of label copies to print
statusString(20)NOT NULL, indexed, default “queued”Job lifecycle state
retry_countIntegerNOT NULL, default 0How many times the worker retried (max 3)
error_messageTextNULLABLEError details if job failed
requested_byInteger FK → usersNOT NULLUser who triggered the print
requested_atDateTimeNOT NULL, autoWhen the job was queued
started_atDateTimeNULLABLEWhen the worker started printing
completed_atDateTimeNULLABLEWhen the job completed or failed
material_typeString(50)NULLABLE, denormalizedMaterial type for fast display without join
lot_numberString(100)NULLABLE, denormalizedLot number for fast display
supplier_nameString(200)NULLABLE, denormalizedSupplier 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

QUEUED PRINTING COMPLETED
PRINTING (fail) QUEUED (retry < 3) PRINTING FAILED (retry ≥ 3)
QUEUED CANCELLED (manual)

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

MethodPathPermissionWhat It Does
POST/api/print-queue/queue/{material_id}?printer_id=Xsuper_admin, lab_user, operatorQueue 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/jobssuper_admin onlyList 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}/retrysuper_admin or lab_userReset failed job: status → QUEUED, retry_count → 0, clears error_message, resets timestamps. Worker re-picks it.
DELETE/api/print-queue/jobs/{id}super_admin onlyCancel a queued job. Blocked if status is PRINTING (“Devam eden iş iptal edilemez”). Sets status → CANCELLED, stamps completed_at.
GET/api/print-queue/statssuper_admin onlyReturns job counts: { queued, printing, completed, failed, total }.

6.2.4 CRUD Flow

Create (Queue a Print Job):

  1. In Materials List, click the print icon on a material row (disabled for palettes/reels)
  2. Printer selection modal opens showing: QR code, label count (material.quantity or 1), and a dropdown of active printers fetched from GET /api/printers/list?active_only=true
  3. Select printer → click “Yazdır”
  4. POST /api/print-queue/queue/{material_id}?printer_id=X
  5. 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
  6. On success: toast “Yazdırma işi kuyruğa eklendi (#123)” → modal closes
  7. Background worker picks up the job within 1 second and starts printing

Read (Monitor Jobs):

  1. Navigate to /admin/print-monitor
  2. Page loads → GET /api/print-queue/jobs?limit=200 + GET /api/print-queue/stats
  3. 4 stat cards render at the top: Bekleyen (gray), Yazdırılıyor (blue), Tamamlanan (green), Başarısız (red)
  4. 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
  5. Client-side search filters across QR code, lot, supplier, user, status, material type, ID, and printer — including Turkish translations

Retry (Failed → Queued):

  1. Retry button (RedoOutlined) appears only on rows with status “failed”
  2. Click retry → POST /api/print-queue/jobs/{id}/retry
  3. Backend resets: status → QUEUED, retry_count → 0, clears error_message, nulls started_at and completed_at
  4. On success: toast “İş #{id} tekrar kuyruğa eklendi” → table + stats reload
  5. Worker re-picks the job on next poll cycle (within 1 second)

Cancel (Queued → Cancelled):

  1. Cancel button (CloseCircleOutlined, danger) appears only on rows with status “queued”
  2. Click cancel → confirmation modal: “İş #{id} iptal edilsin mi?” with “İptal Et” (danger) / “Vazgeç”
  3. On confirm: DELETE /api/print-queue/jobs/{id}
  4. Backend validates job is not PRINTING (blocks with “Devam eden iş iptal edilemez”), sets status → CANCELLED, stamps completed_at
  5. 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:

Poll DB every 1s Find oldest QUEUED job for this printer Status → PRINTING
Fetch RawMaterial Fetch User (entered_by) Build material_data dict print_label_with_quantity()

The material_data Dict (Built by Worker)

KeySource
qr_codejob.qr_code (includes “REDDEDILDI” if rejected)
material_typejob.material_type (denormalized)
lot_numberjob.lot_number or material.lot_number
supplier_namejob.supplier_name (denormalized)
weightmaterial.weight (live from DB)
received_datematerial.received_date (live from DB)
entered_byuser.full_name or user.username (live lookup)
notesmaterial.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_countPrint FailsResult
0First attempt failsretry_count → 1, status → QUEUED, error stored
1Second attempt failsretry_count → 2, status → QUEUED
2Third attempt failsretry_count → 3, status → QUEUED
3Fourth attempt failsstatus → 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)

TypeTag ColorTurkish Label
raw_copperorangeBakır
raw_tincyanKalay
raw_plasticpurplePlastik
raw_catalystmagentaKatalizör
raw_dyevolcanoBoya
raw_antirodentgeekblueAntirodent

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 IDLabelDescription
access_pageSayfaya ErişimYazdırma izleme sayfasını görüntüleme
view_jobsİşleri GörüntüleYazdırma işlerini görme
view_statsİstatistiklerİstatistik kartlarını görme
retry_jobTekrar DeneBaşarısız işi tekrar çalıştırma
cancel_jobİşi İptal EtBekleyen işi iptal etme
refresh_dataYenileVeriyi 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:

GroupColumnsWhat It Captures
Identity (10)user_id, username, user_type, session_id, session_token, ip_address, user_agent, device_type, browser, osWho asked, from where, on what device
Query (6)original_query, query_language, query_length, query_type, query_category, detected_entitiesWhat 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_toolsHow 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_bytesThe internal API call the AI made on behalf of the user
Result (6)success, error_type, error_message, error_stack, final_response, response_typeWhat the AI answered, whether it succeeded, error details if not
Performance (4)total_duration_ms, ai_processing_ms, api_call_ms, response_generation_msTiming breakdown: AI thinking + API call + response generation
Security (4)security_flags, rate_limit_status, was_blocked, block_reasonInjection detection, rate limiting, blocked queries
Meta (2)created_at, metadataTimestamp 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

MethodPathWhat It Does
POST/api/ai/chatSend a query to the AI agent. Checks if AI is enabled. Returns response, tools used, timing. Sanitizes errors for users.
GET/api/ai/statusCheck AI readiness: API key configured, model name, agent status (ready/error/not_initialized).
GET/api/ai/modelsList available models grouped by provider with tier, description, recommended flag, coming_soon flag.
POST/api/ai/resetDestroy current agent singleton. Reinitializes on next request with latest config.
GET/api/ai/configGet current config: enabled, model_name, max_tokens, temperature, key status.
POST/api/ai/configUpdate config. Partial updates supported. Saves to ai_config.json.
GET/api/ai/statsUsage statistics from audit table: total/successful/failed queries, avg response time, today’s count.
GET/api/ai/audit-logsPaginated audit log entries. Params: ?limit=100&offset=0. Returns key fields for display.
GET/api/ai/api-keys/statusStatus of all 3 provider keys: configured flag + masked preview (first 8 + “...” + last 4 chars).
POST/api/ai/api-keys/saveSave 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:

  1. Navigate to “API Anahtarları” tab → 3 provider cards (Google Gemini, OpenAI, Anthropic Claude) each with logo, status tag, and password input
  2. Enter API key in the password field → click “Kaydet”
  3. POST /api/ai/api-keys/save with { provider, api_key }
  4. Backend validates: not empty, ≥ 20 chars, checks prefix (AIza for Gemini, sk- for OpenAI, sk-ant- for Anthropic — warning only, not blocking)
  5. Writes to solen_ai_service/.env file, updates runtime os.environ, resets AI agent singleton
  6. On success: toast “API anahtarı kaydedildi” + prefix warning if applicable → masked key shown on card → status refreshes

Read (Overview Dashboard):

  1. Navigate to /admin/ai-settings → “Genel Bakış” tab loads
  2. 4 stat cards: AI status (active/inactive), today’s queries, total queries, average response time
  3. Success rate card with circular progress chart + successful/failed counts
  4. Connection test card: shows status, API key status, current model. “Bağlantıyı Test Et” button calls GET /api/ai/status

Update Model Settings:

  1. Navigate to “Model Ayarları” tab
  2. 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)
  3. Click “Ayarları Kaydet”
  4. POST /api/ai/config → saves to ai_config.json
  5. POST /api/ai/reset → destroys current agent, reinitializes with new config on next query
  6. Toast: “Ayarlar kaydedildi - AI yeniden başlatıldı”

Delete API Key:

  1. Click “Sil” button on a provider card
  2. Confirmation modal
  3. DELETE /api/ai/api-keys/{provider}
  4. Backend removes key from .env file and os.environ
  5. Toast: “API anahtarı silindi” → card reverts to unconfigured state

Test API Key:

  1. Click “Test Et” button on a configured provider card
  2. POST /api/ai/api-keys/test/{provider}
  3. Backend tests: Gemini via client instantiation, OpenAI via GET https://api.openai.com/v1/models, Anthropic via GET https://api.anthropic.com/v1/models
  4. Success toast or error toast with details

6.3.5 AI Provider Configuration

ProviderEnv VariableKey PrefixModelsStatus
Google GeminiGEMINI_API_KEYAIzagemini-3-flash (recommended), gemini-3-pro, gemini-2.5-flash, gemini-2.5-proActive
OpenAIOPENAI_API_KEYsk-gpt-5.2 (recommended), gpt-5-mini, o3, gpt-4.1Active
Anthropic ClaudeANTHROPIC_API_KEYsk-ant-claude-sonnet-4-5 (recommended), claude-opus-4-5, claude-haiku-4-5Active

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.

First /ai/chat call Load ai_config.json Create SolenAIAgent(config) Singleton stored in _agent_instance

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

  1. User sends query via POST /api/ai/chat with { query, conversation_history, user_type }
  2. Backend checks ai_config.json — if enabled: false, returns “AI servisi şu an devre dışı”
  3. Gets or creates agent singleton, extracts user info from JWT
  4. Calls agent.process_query(query, history, user_type, ip, username, user_id)
  5. Agent internally: preprocesses query, selects tool, calls internal API, generates response, writes audit log
  6. 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:

  1. Who? — user_id, username, user_type, session info, IP, device, browser, OS
  2. What? — original query, language, type (search/question/command), category (order/material/production), detected entities
  3. How? — AI model, reasoning, confidence score, selected tool, tool parameters, alternative tools considered
  4. Where? — API endpoint called, method, response status, response data, response size
  5. Result? — success/fail, error type and message, final response text, response type
  6. Performance? — total ms, AI processing ms, API call ms, response generation ms
  7. 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

ColumnTypeDetails
idInteger PKAuto-increment, indexed
emailString(320)Unique, indexed, not null
hashed_passwordString(1024)bcrypt hash, not null
is_activeBooleanDefault True
is_superuserBooleanDefault False (FastAPI-Users legacy)
is_verifiedBooleanDefault False
usernameString(50)Unique, indexed, not null
user_typeString(50)super_admin, lab_user, operator, or custom
full_nameString(100)Display name
statusString(20)active / inactive / suspended
last_loginDateTimeUpdated on each login
created_atDateTimeUTC timestamp
updated_atDateTimeAuto-updated on change
preferred_interfaceString(20)mobile / desktop / auto
device_infoJSONLast known device details
permissionsTextJSON string — the granular permission object (pages + buttons)
force_password_changeBooleanDefault 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:

TableExtra ColumnsPurpose
super_adminsadmin_level (system/module/user), permissions (JSON list), access_logs (JSON list)Admin hierarchy, action audit trail
lab_userslab_department, certifications (JSON list), test_specializations (JSON list), shift_schedule (JSON dict)Lab-specific qualifications and shifts
operatorsassigned_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)

ColumnTypeDetails
idInteger PKAuto-increment
user_idInteger FKReferences users.id
session_tokenString(255)Unique, indexed — the JWT access token
refresh_tokenString(255)Unique, indexed
login_timeDateTimeWhen session started
logout_timeDateTimeNull until logout
expires_atDateTimeToken expiry timestamp
ip_addressString(45)IPv6-ready
user_agentTextBrowser/device string
device_typeString(20)mobile / desktop / tablet
statusString(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.

ColumnTypeDetails
idInteger PKAuto-increment
nameString(50)Unique, e.g. “Lab Technician”
descriptionTextHuman-readable role description
permissionsTextJSON string — full permission structure
is_systemBooleanSystem roles cannot be deleted
created_atDateTimeUTC
updated_atDateTimeAuto-updated

user_activity_logs (11 columns)

Audit trail for every administrative action on users. username is denormalized for fast reads.

ColumnTypeDetails
user_idInteger FKWho performed the action
usernameString(50)Denormalized
actionString(50)create_user, update_user, suspend_user, delete_user, reset_password
moduleString(50)Always user_management
target_typeString(50)Always user
target_idIntegerID of affected user
detailsTextJSON string with extra context
ip_addressString(50)Client IP
user_agentTextBrowser string
created_atDateTimeWhen action occurred

6.4.3 Authentication System — JWT Tokens and Sessions

Token Architecture

Login (username + password) bcrypt verify Generate Access Token (HS256) Generate Refresh Token Create UserSession row
Token TypeExpiryPayloadPurpose
Access Token12 hours (default)sub (user_id), username, user_type, permissions, type: "access"Authenticate every API request via Authorization: Bearer header
Refresh Token7 dayssub (user_id), type: "refresh", jti (unique token ID)Generate new access token without re-login
Operator Kiosk Token6 monthsSame as access tokenFactory 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

OperationImplementation
Hashingpasslib with bcrypt scheme. CryptContext(schemes=["bcrypt"], deprecated="auto")
Verificationpwd_context.verify(plain, hashed) — constant-time comparison
Auto-Generationsecrets.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

  1. User submits username + password to POST /auth/login
  2. Backend queries user by username or email (case-insensitive)
  3. Checks is_active == True and status != "suspended"
  4. Verifies password with bcrypt
  5. Updates last_login timestamp
  6. Builds JWT payload with user_id, username, user_type, permissions
  7. If operator account: sets 6-month expiry; otherwise 12-hour expiry
  8. Creates UserSession row (session_token, refresh_token, device info, IP, expiry)
  9. Returns { access_token, refresh_token, expires_in, user }
  10. Frontend stores tokens and user in localStorage (keys: solen_auth_token, solen_refresh_token, solen_current_user)
  11. Operators redirect to /production, others to /welcome

Kiosk Auto-Login

The Login page detects factory kiosk devices in two ways:

  1. User-Agent detection: checks for “FullyKiosk” in the browser user-agent string (Fully Kiosk Browser is the app running on factory phones)
  2. Secret URL token: checks for ?kiosk=SOLEN_FACTORY_2024_KIOSK_SECRET in 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

  1. Refresh: POST /auth/refresh with refresh token → validates, generates new access token, does not create new session
  2. Logout: POST /auth/logout with access token → sets session status = "terminated" and logout_time → frontend clears localStorage

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: Permission Manifest (static definition) Layer 2: Button Registry (dynamic runtime) Layer 3: Access Control (route guards) Layer 4: Permission Utilities (component-level) Layer 5: Backend Enforcement (API-level)

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:

ModulePagesTotal ButtonsHighlights
Dashboard1 (Main)3access_page, view_stats, view_charts
Hammadde3 (Giriş, Liste, Tedarikçi)3012 for entry forms, 11 for list (incl. hard_delete), 7 for suppliers
Lab2 (Panel, Test)5Lab access, test management
Teknik6 (Panel, Kablo Tasarım, Makine, Standart, Kablo DB, Markalama)3211 for Cable Playground alone
Sipariş3 (Siparişlerim, Oluştur, Müşteri)17Order CRUD, customer management
Üretim12 (Planlama, 6 machine pages, Aktarma, Paletleme, Sevkiyat, Liste, Geçmiş)~40Start/stop production per machine type, planning, shipping
Stok3 (Ürün, Hammadde, Projeksiyon)14View, export, filter, adjust stock
Admin3 (Yazdırma İzleme, Yazıcı, Kullanıcı)229 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:

Also exports React wrapper components:

Layer 5: Backend Enforcement (permission_checker.py, 200 lines)

The server-side mirror. Even if a user bypasses the frontend, the backend enforces permissions:

FunctionWhat 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)

MethodPathWhat It Does
POST/auth/loginAuthenticate with username/password. Returns access + refresh tokens and user object.
POST/auth/logoutTerminate session. Sets session status to “terminated” and records logout time.
POST/auth/refreshExchange refresh token for new access token. Does not create new session.
GET/auth/meGet current user info from token. Used by frontend to restore state on page refresh.
GET/auth/healthAuth service health check: database connection, version, uptime.

User Management Endpoints (9)

MethodPathPermissionWhat It Does
GET/user-management/users/listsuper_adminList all users. Supports ?status_filter and ?user_type query params. Parses permissions JSON.
GET/user-management/operatorsany authenticatedList active users (for operator dropdowns in other modules). Returns only id, username, full_name.
POST/user-management/users/createsuper_adminCreate user. Validates username/email uniqueness. Auto-generates password if not provided. Stores permissions JSON. Logs activity.
PUT/user-management/users/{id}super_adminUpdate user fields (username, email, name, type, status, permissions, password). Logs activity.
POST/user-management/users/{id}/suspendsuper_adminToggle between active and suspended. Logs activity.
DELETE/user-management/users/{id}super_adminPermanently delete user. Self-deletion blocked (400). Logs activity.
POST/user-management/users/{id}/reset-passwordsuper_adminGenerate new 12-char password. Sets force_password_change = True. Returns password (one-time). Logs activity.
GET/user-management/roles/listsuper_adminList all role templates with parsed permissions.
GET/user-management/activity-logssuper_adminQuery activity logs. Supports ?user_id, ?module, ?action, ?limit filters.

6.4.6 CRUD Flow

Create (New User):

  1. Click “Yeni Kullanıcı” button → modal opens with 3 tabs
  2. 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
  3. 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.
  4. Tab 3 – Önizleme: read-only summary showing accessible modules → pages → enabled buttons with color-coded tags
  5. Click “Oluştur” → POST /user-management/users/create with { username, email, full_name, user_type, status, permissions, force_password_change, return_password: true }
  6. Backend validates uniqueness (username, email), hashes password with bcrypt, stores user + permissions JSON, logs activity
  7. 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):

  1. Navigate to /admin/user-management → ProTable loads via GET /user-management/users/list
  2. 7 columns: ID (fixed left), Kullanıcı (username + full_name), Email, Tip (colored tag), Durum (colored tag), Son Giriş (UTC → Istanbul), İşlemler (fixed right)
  3. Client-side search filters across username, email, full_name, user_type, and status simultaneously
  4. Pagination: 10/20/50/100 per page
  5. ProTable density and column settings controls

Update (Edit User):

  1. Click edit icon (EditOutlined) on user row → same 3-tab modal opens, pre-filled with user data
  2. Existing permissions loaded and parsed (handles string JSON → object conversion)
  3. Password field is hidden in edit mode (use Reset Password for that)
  4. Modify any field or permission → click “Güncelle”
  5. PUT /user-management/users/{id} with changed fields + full permissions object
  6. Backend validates, updates, logs activity

Delete (Permanent):

  1. Click delete icon (DeleteOutlined, danger) on user row
  2. Confirmation modal: “Bu kullanıcıyı silmek istediğinizden emin misiniz?”
  3. DELETE /user-management/users/{id}
  4. Backend blocks self-deletion (400 error). Hard deletes user record. Logs activity.

Suspend / Activate:

  1. Click stop icon (StopOutlined) on user row
  2. POST /user-management/users/{id}/suspend
  3. Toggles status between active and suspended. Suspended users cannot log in.

Reset Password:

  1. Click key icon (KeyOutlined) on user row
  2. POST /user-management/users/{id}/reset-password
  3. Backend generates cryptographically secure 12-character password, hashes with bcrypt, sets force_password_change = True
  4. 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 AdminLab UserOperatorCustom Types
InterfaceDesktopDesktopMobile (kiosk)Configurable
Dashboard Route/welcome/lab/dashboard/productionDefaults to /welcome
Token Expiry12 hours12 hours6 months12 hours
Permission BypassYes — all checks return TrueNoNoNo
Default ModulesAll (all_access)Lab, HammaddeProductionNone (manual)
Extension Tablesuper_adminslab_usersoperatorsNone
Can Manage UsersYesNoNoNo
Auto-LoginNoNoYes (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)

#ColumnWidthDetails
1ID60px, fixed leftNumeric
2Kullanıcı140pxUsername (bold) + full_name below
3Email200pxEllipsis on overflow
4Tip110pxColored tag: super_admin (red), lab_user (blue), operator (green), teknik_user (purple), bakim_user (orange)
5Durum100pxactive (success/green), suspended (warning/yellow), inactive (default/gray)
6Son Giriş140pxdayjs.utc(date).tz('Europe/Istanbul') formatted
7İşlemler160px, fixed right4 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:

  1. Module header: module name with icon
  2. Page cards: each page as a Card component with:
    • Header: Switch for 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/Col layout of Checkbox components for each button
    • Critical buttons: highlighted with red text and distinct styling
  3. Smart toggling: enabling any button auto-enables its page access. Disabling page access cascades to disable all buttons.

Quick Templates

TemplateEffect
“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:

ActionLogged Fields
create_userWho created, target user ID, username, IP, user agent
update_userWho updated, target user ID, changed fields in details
suspend_userWho suspended, target user ID, new status
delete_userWho deleted, target user ID
reset_passwordWho 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:

ActionRequired Role
View user listsuper_admin only
Create usersuper_admin only
Edit usersuper_admin only
Manage permissionssuper_admin only
Suspend / activate usersuper_admin only
Reset passwordsuper_admin only
Delete usersuper_admin only (self-deletion blocked)
View activity logssuper_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.