From ec736cf294b898380f302f8a56dec2dd66f02957 Mon Sep 17 00:00:00 2001 From: Sebastian Frick Date: Thu, 19 Feb 2026 22:46:38 +0100 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20TUI-Screens=20f=C3=BCr=20Reze?= =?UTF-8?q?pt-Filter,=20Archivierung=20und=20Chargen-Einbuchung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Production: Rezeptliste mit Status-Filter (Draft/Active/Archived), Rezept archivieren für aktive Rezepte, list() gibt RecipeSummaryDTO zurück. Inventory: Charge einbuchen (AddBatch) mit neuem Stocks-Resource und Screens. --- frontend/apps/cli/src/App.tsx | 4 + .../components/inventory/AddBatchScreen.tsx | 150 ++++++++++++++ .../components/inventory/InventoryMenu.tsx | 1 + .../inventory/StockBatchEntryScreen.tsx | 51 +++++ .../production/RecipeDetailScreen.tsx | 33 +++- .../production/RecipeListScreen.tsx | 38 +++- frontend/apps/cli/src/hooks/useRecipes.ts | 11 +- frontend/apps/cli/src/hooks/useStocks.ts | 41 ++++ .../apps/cli/src/state/navigation-context.tsx | 2 + frontend/openapi.json | 2 +- frontend/packages/api-client/src/index.ts | 8 + .../api-client/src/resources/recipes.ts | 13 +- .../api-client/src/resources/stocks.ts | 28 +++ frontend/packages/types/src/generated/api.ts | 184 +++++++++++++++++- frontend/packages/types/src/inventory.ts | 2 + frontend/packages/types/src/production.ts | 1 + 16 files changed, 553 insertions(+), 16 deletions(-) create mode 100644 frontend/apps/cli/src/components/inventory/AddBatchScreen.tsx create mode 100644 frontend/apps/cli/src/components/inventory/StockBatchEntryScreen.tsx create mode 100644 frontend/apps/cli/src/hooks/useStocks.ts create mode 100644 frontend/packages/api-client/src/resources/stocks.ts diff --git a/frontend/apps/cli/src/App.tsx b/frontend/apps/cli/src/App.tsx index 227479c..cd88b59 100644 --- a/frontend/apps/cli/src/App.tsx +++ b/frontend/apps/cli/src/App.tsx @@ -36,6 +36,8 @@ import { InventoryMenu } from './components/inventory/InventoryMenu.js'; import { StorageLocationListScreen } from './components/inventory/StorageLocationListScreen.js'; import { StorageLocationCreateScreen } from './components/inventory/StorageLocationCreateScreen.js'; import { StorageLocationDetailScreen } from './components/inventory/StorageLocationDetailScreen.js'; +import { StockBatchEntryScreen } from './components/inventory/StockBatchEntryScreen.js'; +import { AddBatchScreen } from './components/inventory/AddBatchScreen.js'; // Produktion import { ProductionMenu } from './components/production/ProductionMenu.js'; import { RecipeListScreen } from './components/production/RecipeListScreen.js'; @@ -104,6 +106,8 @@ function ScreenRouter() { {current === 'storage-location-list' && } {current === 'storage-location-create' && } {current === 'storage-location-detail' && } + {current === 'stock-batch-entry' && } + {current === 'stock-add-batch' && } {/* Produktion */} {current === 'production-menu' && } {current === 'recipe-list' && } diff --git a/frontend/apps/cli/src/components/inventory/AddBatchScreen.tsx b/frontend/apps/cli/src/components/inventory/AddBatchScreen.tsx new file mode 100644 index 0000000..0197751 --- /dev/null +++ b/frontend/apps/cli/src/components/inventory/AddBatchScreen.tsx @@ -0,0 +1,150 @@ +import { useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import { useNavigation } from '../../state/navigation-context.js'; +import { FormInput } from '../shared/FormInput.js'; +import { LoadingSpinner } from '../shared/LoadingSpinner.js'; +import { ErrorDisplay } from '../shared/ErrorDisplay.js'; +import { SuccessDisplay } from '../shared/SuccessDisplay.js'; +import { useStocks } from '../../hooks/useStocks.js'; +import { BATCH_TYPE_LABELS } from '@effigenix/api-client'; +import type { BatchType } from '@effigenix/api-client'; + +type Field = 'batchId' | 'batchType' | 'quantityAmount' | 'quantityUnit' | 'expiryDate'; +const FIELDS: Field[] = ['batchId', 'batchType', 'quantityAmount', 'quantityUnit', 'expiryDate']; + +const FIELD_LABELS: Record = { + batchId: 'Chargen-Nr. *', + batchType: 'Chargentyp *', + quantityAmount: 'Menge *', + quantityUnit: 'Einheit *', + expiryDate: 'Ablaufdatum (YYYY-MM-DD) *', +}; + +const BATCH_TYPES: BatchType[] = ['PURCHASED', 'PRODUCED']; + +export function AddBatchScreen() { + const { params, navigate, back } = useNavigation(); + const stockId = params['stockId'] ?? ''; + + const { addBatch, loading, error, clearError } = useStocks(); + + const [values, setValues] = useState>({ + batchId: '', batchType: 'PURCHASED', quantityAmount: '', quantityUnit: '', expiryDate: '', + }); + const [activeField, setActiveField] = useState('batchId'); + const [fieldErrors, setFieldErrors] = useState>>({}); + const [success, setSuccess] = useState(null); + + const setField = (field: Field) => (value: string) => { + setValues((v) => ({ ...v, [field]: value })); + }; + + useInput((_input, key) => { + if (loading) return; + + if (activeField === 'batchType') { + if (key.leftArrow || key.rightArrow) { + const idx = BATCH_TYPES.indexOf(values.batchType as BatchType); + const next = key.rightArrow + ? BATCH_TYPES[(idx + 1) % BATCH_TYPES.length] + : BATCH_TYPES[(idx - 1 + BATCH_TYPES.length) % BATCH_TYPES.length]; + if (next) setValues((v) => ({ ...v, batchType: next })); + return; + } + } + + if (key.tab || key.downArrow) { + setActiveField((f) => { + const idx = FIELDS.indexOf(f); + return FIELDS[(idx + 1) % FIELDS.length] ?? f; + }); + } + if (key.upArrow) { + setActiveField((f) => { + const idx = FIELDS.indexOf(f); + return FIELDS[(idx - 1 + FIELDS.length) % FIELDS.length] ?? f; + }); + } + if (key.escape) back(); + }); + + const handleSubmit = async () => { + const errors: Partial> = {}; + if (!values.batchId.trim()) errors.batchId = 'Chargen-Nr. ist erforderlich.'; + if (!values.quantityAmount.trim()) errors.quantityAmount = 'Menge ist erforderlich.'; + if (values.quantityAmount.trim() && isNaN(Number(values.quantityAmount))) errors.quantityAmount = 'Muss eine Zahl sein.'; + if (!values.quantityUnit.trim()) errors.quantityUnit = 'Einheit ist erforderlich.'; + if (!values.expiryDate.trim()) errors.expiryDate = 'Ablaufdatum ist erforderlich.'; + if (values.expiryDate.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(values.expiryDate.trim())) { + errors.expiryDate = 'Format: YYYY-MM-DD'; + } + setFieldErrors(errors); + if (Object.keys(errors).length > 0) return; + + const batch = await addBatch(stockId, { + batchId: values.batchId.trim(), + batchType: values.batchType, + quantityAmount: values.quantityAmount.trim(), + quantityUnit: values.quantityUnit.trim(), + expiryDate: values.expiryDate.trim(), + }); + + if (batch) { + setSuccess(`Charge ${batch.batchId} erfolgreich eingebucht.`); + } + }; + + const handleFieldSubmit = (field: Field) => (_value: string) => { + const idx = FIELDS.indexOf(field); + if (idx < FIELDS.length - 1) { + setActiveField(FIELDS[idx + 1] ?? field); + } else { + void handleSubmit(); + } + }; + + if (!stockId) return ; + if (loading) return ; + + return ( + + Charge einbuchen + {error && } + {success && navigate('inventory-menu')} />} + + {!success && ( + + {FIELDS.map((field) => { + if (field === 'batchType') { + const typeName = BATCH_TYPE_LABELS[values.batchType as BatchType] ?? values.batchType; + return ( + + + {FIELD_LABELS[field]}: {activeField === field ? `← ${typeName} →` : typeName} + + + ); + } + return ( + + ); + })} + + )} + + + + Tab/↑↓ Feld wechseln · ←→ Typ wählen · Enter auf letztem Feld speichern · Escape Abbrechen + + + + ); +} diff --git a/frontend/apps/cli/src/components/inventory/InventoryMenu.tsx b/frontend/apps/cli/src/components/inventory/InventoryMenu.tsx index 33a4ce1..074002e 100644 --- a/frontend/apps/cli/src/components/inventory/InventoryMenu.tsx +++ b/frontend/apps/cli/src/components/inventory/InventoryMenu.tsx @@ -11,6 +11,7 @@ interface MenuItem { const MENU_ITEMS: MenuItem[] = [ { label: 'Lagerorte', screen: 'storage-location-list', description: 'Lagerorte verwalten (Kühlräume, Trockenlager, …)' }, + { label: 'Charge einbuchen', screen: 'stock-batch-entry', description: 'Neue Charge in einen Bestand einbuchen' }, ]; export function InventoryMenu() { diff --git a/frontend/apps/cli/src/components/inventory/StockBatchEntryScreen.tsx b/frontend/apps/cli/src/components/inventory/StockBatchEntryScreen.tsx new file mode 100644 index 0000000..eb3edba --- /dev/null +++ b/frontend/apps/cli/src/components/inventory/StockBatchEntryScreen.tsx @@ -0,0 +1,51 @@ +import { useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import { useNavigation } from '../../state/navigation-context.js'; +import { FormInput } from '../shared/FormInput.js'; + +export function StockBatchEntryScreen() { + const { navigate, back } = useNavigation(); + const [stockId, setStockId] = useState(''); + const [error, setError] = useState(null); + + const handleChange = (value: string) => { + setStockId(value); + if (error) setError(null); + }; + + useInput((_input, key) => { + if (key.escape) back(); + }); + + const handleSubmit = () => { + if (!stockId.trim()) { + setError('Bestand-ID ist erforderlich.'); + return; + } + navigate('stock-add-batch', { stockId: stockId.trim() }); + }; + + return ( + + Charge einbuchen + Geben Sie die Bestand-ID ein, um eine neue Charge einzubuchen. + + + + + + + + Enter Weiter · Escape Zurück + + + + ); +} diff --git a/frontend/apps/cli/src/components/production/RecipeDetailScreen.tsx b/frontend/apps/cli/src/components/production/RecipeDetailScreen.tsx index 06a16b4..b80b6e6 100644 --- a/frontend/apps/cli/src/components/production/RecipeDetailScreen.tsx +++ b/frontend/apps/cli/src/components/production/RecipeDetailScreen.tsx @@ -9,8 +9,8 @@ import { SuccessDisplay } from '../shared/SuccessDisplay.js'; import { ConfirmDialog } from '../shared/ConfirmDialog.js'; import { client } from '../../utils/api-client.js'; -type MenuAction = 'add-ingredient' | 'remove-ingredient' | 'add-step' | 'remove-step' | 'activate' | 'back'; -type Mode = 'menu' | 'select-step-to-remove' | 'confirm-remove' | 'select-ingredient-to-remove' | 'confirm-remove-ingredient' | 'confirm-activate'; +type MenuAction = 'add-ingredient' | 'remove-ingredient' | 'add-step' | 'remove-step' | 'activate' | 'archive' | 'back'; +type Mode = 'menu' | 'select-step-to-remove' | 'confirm-remove' | 'select-ingredient-to-remove' | 'confirm-remove-ingredient' | 'confirm-activate' | 'confirm-archive'; function errorMessage(err: unknown): string { return err instanceof Error ? err.message : 'Unbekannter Fehler'; @@ -33,6 +33,7 @@ export function RecipeDetailScreen() { const [ingredientToRemove, setIngredientToRemove] = useState(null); const isDraft = recipe?.status === 'DRAFT'; + const isActive = recipe?.status === 'ACTIVE'; const menuItems: { id: MenuAction; label: string }[] = [ ...(isDraft ? [ @@ -42,6 +43,9 @@ export function RecipeDetailScreen() { { id: 'remove-step' as const, label: '[Schritt entfernen]' }, { id: 'activate' as const, label: '[Rezept aktivieren]' }, ] : []), + ...(isActive ? [ + { id: 'archive' as const, label: '[Rezept archivieren]' }, + ] : []), { id: 'back' as const, label: '[Zurück]' }, ]; @@ -127,6 +131,9 @@ export function RecipeDetailScreen() { case 'activate': setMode('confirm-activate'); break; + case 'archive': + setMode('confirm-archive'); + break; case 'back': back(); break; @@ -179,6 +186,20 @@ export function RecipeDetailScreen() { setActionLoading(false); }, [recipe]); + const handleArchive = useCallback(async () => { + if (!recipe) return; + setMode('menu'); + setActionLoading(true); + try { + const updated = await client.recipes.archiveRecipe(recipe.id); + setRecipe(updated); + setSuccessMessage('Rezept wurde archiviert.'); + } catch (err: unknown) { + setError(errorMessage(err)); + } + setActionLoading(false); + }, [recipe]); + if (loading) return ; if (error && !recipe) return ; if (!recipe) return Rezept nicht gefunden.; @@ -307,6 +328,14 @@ export function RecipeDetailScreen() { /> )} + {mode === 'confirm-archive' && ( + void handleArchive()} + onCancel={() => setMode('menu')} + /> + )} + {mode === 'menu' && ( Aktionen: diff --git a/frontend/apps/cli/src/components/production/RecipeListScreen.tsx b/frontend/apps/cli/src/components/production/RecipeListScreen.tsx index dbcbefd..c0058b6 100644 --- a/frontend/apps/cli/src/components/production/RecipeListScreen.tsx +++ b/frontend/apps/cli/src/components/production/RecipeListScreen.tsx @@ -5,16 +5,30 @@ import { useRecipes } from '../../hooks/useRecipes.js'; import { LoadingSpinner } from '../shared/LoadingSpinner.js'; import { ErrorDisplay } from '../shared/ErrorDisplay.js'; import { RECIPE_TYPE_LABELS } from '@effigenix/api-client'; -import type { RecipeType } from '@effigenix/api-client'; +import type { RecipeType, RecipeStatus } from '@effigenix/api-client'; + +const STATUS_FILTERS: { key: string; label: string; value: RecipeStatus | undefined }[] = [ + { key: 'a', label: 'ALLE', value: undefined }, + { key: 'D', label: 'DRAFT', value: 'DRAFT' }, + { key: 'A', label: 'ACTIVE', value: 'ACTIVE' }, + { key: 'R', label: 'ARCHIVED', value: 'ARCHIVED' }, +]; + +const STATUS_COLORS: Record = { + DRAFT: 'yellow', + ACTIVE: 'green', + ARCHIVED: 'gray', +}; export function RecipeListScreen() { const { navigate, back } = useNavigation(); const { recipes, loading, error, fetchRecipes, clearError } = useRecipes(); const [selectedIndex, setSelectedIndex] = useState(0); + const [statusFilter, setStatusFilter] = useState(undefined); useEffect(() => { - void fetchRecipes(); - }, [fetchRecipes]); + void fetchRecipes(statusFilter); + }, [fetchRecipes, statusFilter]); useInput((input, key) => { if (loading) return; @@ -28,13 +42,26 @@ export function RecipeListScreen() { } if (input === 'n') navigate('recipe-create'); if (key.backspace || key.escape) back(); + + // Status-Filter + for (const filter of STATUS_FILTERS) { + if (input === filter.key) { + setStatusFilter(filter.value); + setSelectedIndex(0); + break; + } + } }); + const activeFilterLabel = STATUS_FILTERS.find((f) => f.value === statusFilter)?.label ?? 'ALLE'; + return ( Rezepte ({recipes.length}) + Filter: + {activeFilterLabel} {loading && } @@ -57,13 +84,14 @@ export function RecipeListScreen() { const isSelected = index === selectedIndex; const textColor = isSelected ? 'cyan' : 'white'; const typeName = RECIPE_TYPE_LABELS[recipe.type as RecipeType] ?? recipe.type; + const statusColor = STATUS_COLORS[recipe.status] ?? 'white'; return ( {isSelected ? '▶ ' : ' '} {recipe.name.substring(0, 26).padEnd(27)} {typeName.padEnd(18)} {String(recipe.version).padEnd(5)} - {recipe.status} + {recipe.status} ); })} @@ -72,7 +100,7 @@ export function RecipeListScreen() { - ↑↓ nav · Enter Details · [n] Neu · Backspace Zurück + ↑↓ nav · Enter Details · [n] Neu · [a] Alle [D] Draft [A] Active [R] Archived · Backspace Zurück diff --git a/frontend/apps/cli/src/hooks/useRecipes.ts b/frontend/apps/cli/src/hooks/useRecipes.ts index 4256e72..9f7b19b 100644 --- a/frontend/apps/cli/src/hooks/useRecipes.ts +++ b/frontend/apps/cli/src/hooks/useRecipes.ts @@ -1,9 +1,9 @@ import { useState, useCallback } from 'react'; -import type { RecipeDTO, CreateRecipeRequest } from '@effigenix/api-client'; +import type { RecipeSummaryDTO, CreateRecipeRequest, RecipeStatus } from '@effigenix/api-client'; import { client } from '../utils/api-client.js'; interface RecipesState { - recipes: RecipeDTO[]; + recipes: RecipeSummaryDTO[]; loading: boolean; error: string | null; } @@ -19,10 +19,10 @@ export function useRecipes() { error: null, }); - const fetchRecipes = useCallback(async () => { + const fetchRecipes = useCallback(async (status?: RecipeStatus) => { setState((s) => ({ ...s, loading: true, error: null })); try { - const recipes = await client.recipes.list(); + const recipes = await client.recipes.list(status); setState({ recipes, loading: false, error: null }); } catch (err) { setState((s) => ({ ...s, loading: false, error: errorMessage(err) })); @@ -33,7 +33,8 @@ export function useRecipes() { setState((s) => ({ ...s, loading: true, error: null })); try { const recipe = await client.recipes.create(request); - setState((s) => ({ recipes: [...s.recipes, recipe], loading: false, error: null })); + const recipes = await client.recipes.list(); + setState({ recipes, loading: false, error: null }); return recipe; } catch (err) { setState((s) => ({ ...s, loading: false, error: errorMessage(err) })); diff --git a/frontend/apps/cli/src/hooks/useStocks.ts b/frontend/apps/cli/src/hooks/useStocks.ts new file mode 100644 index 0000000..73d81b1 --- /dev/null +++ b/frontend/apps/cli/src/hooks/useStocks.ts @@ -0,0 +1,41 @@ +import { useState, useCallback } from 'react'; +import type { StockBatchDTO, AddStockBatchRequest } from '@effigenix/api-client'; +import { client } from '../utils/api-client.js'; + +interface StocksState { + loading: boolean; + error: string | null; +} + +function errorMessage(err: unknown): string { + return err instanceof Error ? err.message : 'Unbekannter Fehler'; +} + +export function useStocks() { + const [state, setState] = useState({ + loading: false, + error: null, + }); + + const addBatch = useCallback(async (stockId: string, request: AddStockBatchRequest): Promise => { + setState({ loading: true, error: null }); + try { + const batch = await client.stocks.addBatch(stockId, request); + setState({ loading: false, error: null }); + return batch; + } catch (err) { + setState({ loading: false, error: errorMessage(err) }); + return null; + } + }, []); + + const clearError = useCallback(() => { + setState((s) => ({ ...s, error: null })); + }, []); + + return { + ...state, + addBatch, + clearError, + }; +} diff --git a/frontend/apps/cli/src/state/navigation-context.tsx b/frontend/apps/cli/src/state/navigation-context.tsx index 8b39a88..be17508 100644 --- a/frontend/apps/cli/src/state/navigation-context.tsx +++ b/frontend/apps/cli/src/state/navigation-context.tsx @@ -33,6 +33,8 @@ export type Screen = | 'storage-location-list' | 'storage-location-create' | 'storage-location-detail' + | 'stock-batch-entry' + | 'stock-add-batch' // Produktion | 'production-menu' | 'recipe-list' diff --git a/frontend/openapi.json b/frontend/openapi.json index 1facdd2..ab43566 100644 --- a/frontend/openapi.json +++ b/frontend/openapi.json @@ -1 +1 @@ -{"openapi": "3.0.1", "info": {"title": "Effigenix Fleischerei ERP API", "description": "RESTful API for Effigenix Fleischerei ERP System.\n\n## Authentication\n\nAll endpoints (except /api/auth/login and /api/auth/refresh) require JWT authentication.\n\n1. Login via POST /api/auth/login with username and password\n2. Copy the returned access token\n3. Click \"Authorize\" button (top right)\n4. Enter: Bearer \n5. Click \"Authorize\"\n\n## User Management\n\n- **Authentication**: Login, logout, refresh token\n- **User Management**: Create, update, list users (ADMIN only)\n- **Role Management**: Assign roles, lock/unlock users (ADMIN only)\n- **Password Management**: Change password (requires current password)\n\n## Error Handling\n\nAll errors return a consistent error response format:\n\n```json\n{\n \"code\": \"USER_NOT_FOUND\",\n \"message\": \"User with ID 'user-123' not found\",\n \"status\": 404,\n \"timestamp\": \"2026-02-17T12:00:00\",\n \"path\": \"/api/users/user-123\",\n \"validationErrors\": null\n}\n```\n\n## Architecture\n\nBuilt with:\n- Domain-Driven Design (DDD)\n- Clean Architecture (Hexagonal Architecture)\n- Spring Boot 3.2\n- Java 21\n- PostgreSQL\n", "contact": {"name": "Effigenix Development Team", "url": "https://effigenix.com", "email": "dev@effigenix.com"}, "license": {"name": "Proprietary", "url": "https://effigenix.com/license"}, "version": "0.1.0"}, "servers": [{"url": "http://localhost:8080", "description": "Local Development Server"}, {"url": "https://api.effigenix.com", "description": "Production Server"}], "tags": [{"name": "Storage Locations", "description": "Storage location management endpoints"}, {"name": "Product Categories", "description": "Product category management endpoints"}, {"name": "User Management", "description": "User management endpoints (requires authentication)"}, {"name": "Articles", "description": "Article management endpoints"}, {"name": "Recipes", "description": "Recipe management endpoints"}, {"name": "Role Management", "description": "Role management endpoints (ADMIN only)"}, {"name": "Stocks", "description": "Stock management endpoints"}, {"name": "Customers", "description": "Customer management endpoints"}, {"name": "Suppliers", "description": "Supplier management endpoints"}, {"name": "Authentication", "description": "Authentication and session management endpoints"}], "paths": {"/api/users/{id}": {"get": {"tags": ["User Management"], "summary": "Get user by ID", "description": "Retrieve a single user by their ID.", "operationId": "getUserById", "parameters": [{"name": "id", "in": "path", "description": "User ID", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "User retrieved successfully", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "401": {"description": "Authentication required", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "404": {"description": "User not found", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}}, "security": [{"Bearer Authentication": []}]}, "put": {"tags": ["User Management"], "summary": "Update user", "description": "Update user details (email, branchId).", "operationId": "updateUser", "parameters": [{"name": "id", "in": "path", "description": "User ID", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateUserRequest"}}}, "required": true}, "responses": {"200": {"description": "User updated successfully", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "409": {"description": "Email already exists", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "404": {"description": "User not found", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/users/{id}/password": {"put": {"tags": ["User Management"], "summary": "Change password", "description": "Change user password. Requires current password for verification.", "operationId": "changePassword", "parameters": [{"name": "id", "in": "path", "description": "User ID", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChangePasswordRequest"}}}, "required": true}, "responses": {"400": {"description": "Invalid password"}, "401": {"description": "Invalid current password"}, "404": {"description": "User not found"}, "204": {"description": "Password changed successfully"}}, "security": [{"Bearer Authentication": []}]}}, "/api/suppliers/{id}": {"get": {"tags": ["Suppliers"], "operationId": "getSupplier", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/SupplierResponse"}}}}}, "security": [{"Bearer Authentication": []}]}, "put": {"tags": ["Suppliers"], "operationId": "updateSupplier", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateSupplierRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/SupplierResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/inventory/storage-locations/{id}": {"put": {"tags": ["Storage Locations"], "operationId": "updateStorageLocation", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateStorageLocationRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/StorageLocationResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/customers/{id}": {"get": {"tags": ["Customers"], "operationId": "getCustomer", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/CustomerResponse"}}}}}, "security": [{"Bearer Authentication": []}]}, "put": {"tags": ["Customers"], "operationId": "updateCustomer", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateCustomerRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/CustomerResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/customers/{id}/preferences": {"put": {"tags": ["Customers"], "operationId": "setPreferences", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SetPreferencesRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/CustomerResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/customers/{id}/frame-contract": {"put": {"tags": ["Customers"], "operationId": "setFrameContract", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SetFrameContractRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/CustomerResponse"}}}}}, "security": [{"Bearer Authentication": []}]}, "delete": {"tags": ["Customers"], "operationId": "removeFrameContract", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK"}}, "security": [{"Bearer Authentication": []}]}}, "/api/categories/{id}": {"put": {"tags": ["Product Categories"], "operationId": "updateCategory", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateProductCategoryRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ProductCategoryResponse"}}}}}, "security": [{"Bearer Authentication": []}]}, "delete": {"tags": ["Product Categories"], "operationId": "deleteCategory", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK"}}, "security": [{"Bearer Authentication": []}]}}, "/api/articles/{id}": {"get": {"tags": ["Articles"], "operationId": "getArticle", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ArticleResponse"}}}}}, "security": [{"Bearer Authentication": []}]}, "put": {"tags": ["Articles"], "operationId": "updateArticle", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateArticleRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ArticleResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/articles/{id}/sales-units/{suId}/price": {"put": {"tags": ["Articles"], "operationId": "updateSalesUnitPrice", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "suId", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateSalesUnitPriceRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ArticleResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/users": {"get": {"tags": ["User Management"], "summary": "List all users", "description": "Get a list of all users in the system.", "operationId": "listUsers", "responses": {"200": {"description": "Users retrieved successfully", "content": {"*/*": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/UserDTO"}}}}}, "401": {"description": "Authentication required", "content": {"*/*": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/UserDTO"}}}}}}, "security": [{"Bearer Authentication": []}]}, "post": {"tags": ["User Management"], "summary": "Create user (ADMIN only)", "description": "Create a new user account with specified roles.", "operationId": "createUser", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateUserRequest"}}}, "required": true}, "responses": {"400": {"description": "Validation error or invalid password", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "403": {"description": "Missing permission", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "409": {"description": "Username or email already exists", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "201": {"description": "User created successfully", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/users/{id}/unlock": {"post": {"tags": ["User Management"], "summary": "Unlock user (ADMIN only)", "description": "Unlock a user account (allows login).", "operationId": "unlockUser", "parameters": [{"name": "id", "in": "path", "description": "User ID", "required": true, "schema": {"type": "string"}}], "responses": {"403": {"description": "Missing permission", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "200": {"description": "User unlocked successfully", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "404": {"description": "User not found", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "409": {"description": "Invalid status transition", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/users/{id}/roles": {"post": {"tags": ["User Management"], "summary": "Assign role (ADMIN only)", "description": "Assign a role to a user.", "operationId": "assignRole", "parameters": [{"name": "id", "in": "path", "description": "User ID", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AssignRoleRequest"}}}, "required": true}, "responses": {"200": {"description": "Role assigned successfully", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "403": {"description": "Missing permission", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "404": {"description": "User or role not found", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/users/{id}/lock": {"post": {"tags": ["User Management"], "summary": "Lock user (ADMIN only)", "description": "Lock a user account (prevents login).", "operationId": "lockUser", "parameters": [{"name": "id", "in": "path", "description": "User ID", "required": true, "schema": {"type": "string"}}], "responses": {"403": {"description": "Missing permission", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "200": {"description": "User locked successfully", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "404": {"description": "User not found", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}, "409": {"description": "Invalid status transition", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/UserDTO"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/suppliers": {"get": {"tags": ["Suppliers"], "operationId": "listSuppliers", "parameters": [{"name": "status", "in": "query", "required": false, "schema": {"type": "string", "enum": ["ACTIVE", "INACTIVE"]}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/SupplierResponse"}}}}}}, "security": [{"Bearer Authentication": []}]}, "post": {"tags": ["Suppliers"], "operationId": "createSupplier", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateSupplierRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/SupplierResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/suppliers/{id}/rating": {"post": {"tags": ["Suppliers"], "operationId": "rateSupplier", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RateSupplierRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/SupplierResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/suppliers/{id}/deactivate": {"post": {"tags": ["Suppliers"], "operationId": "deactivate", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/SupplierResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/suppliers/{id}/certificates": {"post": {"tags": ["Suppliers"], "operationId": "addCertificate", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AddCertificateRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/SupplierResponse"}}}}}, "security": [{"Bearer Authentication": []}]}, "delete": {"tags": ["Suppliers"], "operationId": "removeCertificate", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RemoveCertificateRequest"}}}, "required": true}, "responses": {"200": {"description": "OK"}}, "security": [{"Bearer Authentication": []}]}}, "/api/suppliers/{id}/activate": {"post": {"tags": ["Suppliers"], "operationId": "activate", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/SupplierResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/recipes": {"post": {"tags": ["Recipes"], "operationId": "createRecipe", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateRecipeRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/RecipeResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/recipes/{id}/steps": {"post": {"tags": ["Recipes"], "operationId": "addProductionStep", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AddProductionStepRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/RecipeResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/recipes/{id}/ingredients": {"post": {"tags": ["Recipes"], "operationId": "addIngredient", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AddRecipeIngredientRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/RecipeResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/recipes/{id}/activate": {"post": {"tags": ["Recipes"], "operationId": "activateRecipe", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/RecipeResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/inventory/storage-locations": {"get": {"tags": ["Storage Locations"], "operationId": "listStorageLocations", "parameters": [{"name": "storageType", "in": "query", "required": false, "schema": {"type": "string"}}, {"name": "active", "in": "query", "required": false, "schema": {"type": "boolean"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/StorageLocationResponse"}}}}}}, "security": [{"Bearer Authentication": []}]}, "post": {"tags": ["Storage Locations"], "operationId": "createStorageLocation", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateStorageLocationRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/StorageLocationResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/customers": {"get": {"tags": ["Customers"], "operationId": "listCustomers", "parameters": [{"name": "type", "in": "query", "required": false, "schema": {"type": "string", "enum": ["B2C", "B2B"]}}, {"name": "status", "in": "query", "required": false, "schema": {"type": "string", "enum": ["ACTIVE", "INACTIVE"]}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/CustomerResponse"}}}}}}, "security": [{"Bearer Authentication": []}]}, "post": {"tags": ["Customers"], "operationId": "createCustomer", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateCustomerRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/CustomerResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/customers/{id}/delivery-addresses": {"post": {"tags": ["Customers"], "operationId": "addDeliveryAddress", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AddDeliveryAddressRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/CustomerResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/customers/{id}/deactivate": {"post": {"tags": ["Customers"], "operationId": "deactivate_1", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/CustomerResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/customers/{id}/activate": {"post": {"tags": ["Customers"], "operationId": "activate_1", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/CustomerResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/categories": {"get": {"tags": ["Product Categories"], "operationId": "listCategories", "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/ProductCategoryResponse"}}}}}}, "security": [{"Bearer Authentication": []}]}, "post": {"tags": ["Product Categories"], "operationId": "createCategory", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateProductCategoryRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ProductCategoryResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/auth/refresh": {"post": {"tags": ["Authentication"], "summary": "Refresh access token", "description": "Refresh an expired access token using a valid refresh token.", "operationId": "refresh", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RefreshTokenRequest"}}}, "required": true}, "responses": {"401": {"description": "Invalid or expired refresh token", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/LoginResponse"}}}}, "200": {"description": "Token refresh successful", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/LoginResponse"}}}}}}}, "/api/auth/logout": {"post": {"tags": ["Authentication"], "summary": "User logout", "description": "Invalidate current JWT token.", "operationId": "logout", "responses": {"401": {"description": "Invalid or missing authentication token"}, "204": {"description": "Logout successful"}}}}, "/api/auth/login": {"post": {"tags": ["Authentication"], "summary": "User login", "description": "Authenticate user with username and password. Returns JWT tokens.", "operationId": "login", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LoginRequest"}}}, "required": true}, "responses": {"429": {"description": "Too many login attempts", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/LoginResponse"}}}}, "401": {"description": "Invalid credentials, user locked, or user inactive", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/LoginResponse"}}}}, "200": {"description": "Login successful", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/LoginResponse"}}}}}}}, "/api/articles": {"get": {"tags": ["Articles"], "operationId": "listArticles", "parameters": [{"name": "categoryId", "in": "query", "required": false, "schema": {"type": "string"}}, {"name": "status", "in": "query", "required": false, "schema": {"type": "string", "enum": ["ACTIVE", "INACTIVE"]}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/ArticleResponse"}}}}}}, "security": [{"Bearer Authentication": []}]}, "post": {"tags": ["Articles"], "operationId": "createArticle", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateArticleRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ArticleResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/articles/{id}/suppliers": {"post": {"tags": ["Articles"], "operationId": "assignSupplier", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AssignSupplierRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ArticleResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/articles/{id}/sales-units": {"post": {"tags": ["Articles"], "operationId": "addSalesUnit", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AddSalesUnitRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ArticleResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/articles/{id}/deactivate": {"post": {"tags": ["Articles"], "operationId": "deactivate_2", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ArticleResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/articles/{id}/activate": {"post": {"tags": ["Articles"], "operationId": "activate_2", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/ArticleResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/inventory/storage-locations/{id}/deactivate": {"patch": {"tags": ["Storage Locations"], "operationId": "deactivateStorageLocation", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/StorageLocationResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/inventory/storage-locations/{id}/activate": {"patch": {"tags": ["Storage Locations"], "operationId": "activateStorageLocation", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/StorageLocationResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/roles": {"get": {"tags": ["Role Management"], "summary": "List all roles (ADMIN only)", "description": "Get a list of all available roles in the system. Requires USER_MANAGEMENT permission.", "operationId": "listRoles", "responses": {"200": {"description": "Roles retrieved successfully", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/RoleDTO"}}}}, "403": {"description": "Missing USER_MANAGEMENT permission", "content": {"*/*": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/RoleDTO"}}}}}, "401": {"description": "Authentication required", "content": {"*/*": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/RoleDTO"}}}}}}, "security": [{"Bearer Authentication": []}]}}, "/api/users/{id}/roles/{roleName}": {"delete": {"tags": ["User Management"], "summary": "Remove role (ADMIN only)", "description": "Remove a role from a user.", "operationId": "removeRole", "parameters": [{"name": "id", "in": "path", "description": "User ID", "required": true, "schema": {"type": "string"}}, {"name": "roleName", "in": "path", "description": "Role name", "required": true, "schema": {"type": "string", "enum": ["ADMIN", "PRODUCTION_MANAGER", "PRODUCTION_WORKER", "QUALITY_MANAGER", "QUALITY_INSPECTOR", "PROCUREMENT_MANAGER", "WAREHOUSE_WORKER", "SALES_MANAGER", "SALES_STAFF"]}}], "responses": {"403": {"description": "Missing permission"}, "404": {"description": "User or role not found"}, "204": {"description": "Role removed successfully"}}, "security": [{"Bearer Authentication": []}]}}, "/api/recipes/{id}/steps/{stepNumber}": {"delete": {"tags": ["Recipes"], "operationId": "removeProductionStep", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "stepNumber", "in": "path", "required": true, "schema": {"type": "integer", "format": "int32"}}], "responses": {"200": {"description": "OK"}}, "security": [{"Bearer Authentication": []}]}}, "/api/recipes/{id}/ingredients/{ingredientId}": {"delete": {"tags": ["Recipes"], "operationId": "removeIngredient", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "ingredientId", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK"}}, "security": [{"Bearer Authentication": []}]}}, "/api/customers/{id}/delivery-addresses/{label}": {"delete": {"tags": ["Customers"], "operationId": "removeDeliveryAddress", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "label", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK"}}, "security": [{"Bearer Authentication": []}]}}, "/api/articles/{id}/suppliers/{supplierId}": {"delete": {"tags": ["Articles"], "operationId": "removeSupplier", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "supplierId", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK"}}, "security": [{"Bearer Authentication": []}]}}, "/api/articles/{id}/sales-units/{suId}": {"delete": {"tags": ["Articles"], "operationId": "removeSalesUnit", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "suId", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "OK"}}, "security": [{"Bearer Authentication": []}]}}, "/api/inventory/stocks": {"post": {"tags": ["Stocks"], "operationId": "createStock", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateStockRequest"}}}, "required": true}, "responses": {"200": {"description": "OK", "content": {"*/*": {"schema": {"$ref": "#/components/schemas/StockResponse"}}}}}, "security": [{"Bearer Authentication": []}]}}}, "components": {"schemas": {"UpdateUserRequest": {"type": "object", "properties": {"email": {"type": "string", "description": "New email address", "example": "newemail@example.com"}, "branchId": {"type": "string", "description": "New branch ID", "example": "BRANCH-002"}}, "description": "Request to update user details"}, "RoleDTO": {"required": ["description", "id", "name", "permissions"], "type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string", "enum": ["ADMIN", "PRODUCTION_MANAGER", "PRODUCTION_WORKER", "QUALITY_MANAGER", "QUALITY_INSPECTOR", "PROCUREMENT_MANAGER", "WAREHOUSE_WORKER", "SALES_MANAGER", "SALES_STAFF"]}, "permissions": {"uniqueItems": true, "type": "array", "items": {"type": "string", "enum": ["RECIPE_READ", "RECIPE_WRITE", "RECIPE_DELETE", "BATCH_READ", "BATCH_WRITE", "BATCH_COMPLETE", "BATCH_DELETE", "PRODUCTION_ORDER_READ", "PRODUCTION_ORDER_WRITE", "PRODUCTION_ORDER_DELETE", "HACCP_READ", "HACCP_WRITE", "TEMPERATURE_LOG_READ", "TEMPERATURE_LOG_WRITE", "CLEANING_RECORD_READ", "CLEANING_RECORD_WRITE", "GOODS_INSPECTION_READ", "GOODS_INSPECTION_WRITE", "STOCK_READ", "STOCK_WRITE", "STOCK_MOVEMENT_READ", "STOCK_MOVEMENT_WRITE", "INVENTORY_COUNT_READ", "INVENTORY_COUNT_WRITE", "PURCHASE_ORDER_READ", "PURCHASE_ORDER_WRITE", "PURCHASE_ORDER_DELETE", "GOODS_RECEIPT_READ", "GOODS_RECEIPT_WRITE", "SUPPLIER_READ", "SUPPLIER_WRITE", "SUPPLIER_DELETE", "ORDER_READ", "ORDER_WRITE", "ORDER_DELETE", "INVOICE_READ", "INVOICE_WRITE", "INVOICE_DELETE", "CUSTOMER_READ", "CUSTOMER_WRITE", "CUSTOMER_DELETE", "LABEL_READ", "LABEL_WRITE", "LABEL_PRINT", "MASTERDATA_READ", "MASTERDATA_WRITE", "BRANCH_READ", "BRANCH_WRITE", "BRANCH_DELETE", "USER_READ", "USER_WRITE", "USER_DELETE", "USER_LOCK", "USER_UNLOCK", "ROLE_READ", "ROLE_WRITE", "ROLE_ASSIGN", "ROLE_REMOVE", "REPORT_READ", "REPORT_GENERATE", "NOTIFICATION_READ", "NOTIFICATION_SEND", "AUDIT_LOG_READ", "SYSTEM_SETTINGS_READ", "SYSTEM_SETTINGS_WRITE"]}}, "description": {"type": "string"}}}, "UserDTO": {"required": ["createdAt", "email", "id", "roles", "status", "username"], "type": "object", "properties": {"id": {"type": "string"}, "username": {"type": "string"}, "email": {"type": "string"}, "roles": {"uniqueItems": true, "type": "array", "items": {"$ref": "#/components/schemas/RoleDTO"}}, "branchId": {"type": "string"}, "status": {"type": "string", "enum": ["ACTIVE", "INACTIVE", "LOCKED"]}, "createdAt": {"type": "string", "format": "date-time"}, "lastLogin": {"type": "string", "format": "date-time"}}}, "ChangePasswordRequest": {"required": ["currentPassword", "newPassword"], "type": "object", "properties": {"currentPassword": {"type": "string", "description": "Current password", "example": "OldPass123"}, "newPassword": {"maxLength": 2147483647, "minLength": 8, "type": "string", "description": "New password (min 8 characters)", "example": "NewSecurePass456"}}, "description": "Request to change user password"}, "UpdateSupplierRequest": {"type": "object", "properties": {"name": {"type": "string"}, "phone": {"type": "string"}, "email": {"type": "string"}, "contactPerson": {"type": "string"}, "street": {"type": "string"}, "houseNumber": {"type": "string"}, "postalCode": {"type": "string"}, "city": {"type": "string"}, "country": {"type": "string"}, "paymentDueDays": {"type": "integer", "format": "int32"}, "paymentDescription": {"type": "string"}}}, "AddressResponse": {"required": ["city", "country", "houseNumber", "postalCode", "street"], "type": "object", "properties": {"street": {"type": "string"}, "houseNumber": {"type": "string"}, "postalCode": {"type": "string"}, "city": {"type": "string"}, "country": {"type": "string"}}, "nullable": true}, "ContactInfoResponse": {"required": ["contactPerson", "email", "phone"], "type": "object", "properties": {"phone": {"type": "string"}, "email": {"type": "string"}, "contactPerson": {"type": "string"}}}, "PaymentTermsResponse": {"required": ["paymentDescription", "paymentDueDays"], "type": "object", "properties": {"paymentDueDays": {"type": "integer", "format": "int32"}, "paymentDescription": {"type": "string"}}, "nullable": true}, "QualityCertificateResponse": {"required": ["certificateType", "issuer", "validFrom", "validUntil"], "type": "object", "properties": {"certificateType": {"type": "string"}, "issuer": {"type": "string"}, "validFrom": {"type": "string", "format": "date"}, "validUntil": {"type": "string", "format": "date"}}}, "SupplierRatingResponse": {"required": ["deliveryScore", "priceScore", "qualityScore"], "type": "object", "properties": {"qualityScore": {"type": "integer", "format": "int32"}, "deliveryScore": {"type": "integer", "format": "int32"}, "priceScore": {"type": "integer", "format": "int32"}}, "nullable": true}, "SupplierResponse": {"required": ["certificates", "contactInfo", "createdAt", "id", "name", "status", "updatedAt"], "type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "address": {"$ref": "#/components/schemas/AddressResponse"}, "contactInfo": {"$ref": "#/components/schemas/ContactInfoResponse"}, "paymentTerms": {"$ref": "#/components/schemas/PaymentTermsResponse"}, "certificates": {"type": "array", "items": {"$ref": "#/components/schemas/QualityCertificateResponse"}}, "rating": {"$ref": "#/components/schemas/SupplierRatingResponse"}, "status": {"type": "string"}, "createdAt": {"type": "string", "format": "date-time"}, "updatedAt": {"type": "string", "format": "date-time"}}}, "UpdateStorageLocationRequest": {"type": "object", "properties": {"name": {"type": "string"}, "minTemperature": {"type": "string"}, "maxTemperature": {"type": "string"}}}, "StorageLocationResponse": {"required": ["active", "id", "name", "storageType"], "type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "storageType": {"type": "string"}, "temperatureRange": {"$ref": "#/components/schemas/TemperatureRangeResponse"}, "active": {"type": "boolean"}}}, "TemperatureRangeResponse": {"required": ["maxTemperature", "minTemperature"], "type": "object", "properties": {"minTemperature": {"type": "number"}, "maxTemperature": {"type": "number"}}, "nullable": true}, "UpdateCustomerRequest": {"type": "object", "properties": {"name": {"type": "string"}, "street": {"type": "string"}, "houseNumber": {"type": "string"}, "postalCode": {"type": "string"}, "city": {"type": "string"}, "country": {"type": "string"}, "phone": {"type": "string"}, "email": {"type": "string"}, "contactPerson": {"type": "string"}, "paymentDueDays": {"type": "integer", "format": "int32"}, "paymentDescription": {"type": "string"}}}, "ContractLineItemResponse": {"required": ["agreedPrice", "agreedQuantity", "articleId", "unit"], "type": "object", "properties": {"articleId": {"type": "string"}, "agreedPrice": {"type": "number"}, "agreedQuantity": {"type": "number"}, "unit": {"type": "string"}}}, "CustomerResponse": {"required": ["billingAddress", "contactInfo", "createdAt", "deliveryAddresses", "id", "name", "preferences", "status", "type", "updatedAt"], "type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "type": {"type": "string"}, "billingAddress": {"$ref": "#/components/schemas/AddressResponse"}, "contactInfo": {"$ref": "#/components/schemas/ContactInfoResponse"}, "paymentTerms": {"$ref": "#/components/schemas/PaymentTermsResponse"}, "deliveryAddresses": {"type": "array", "items": {"$ref": "#/components/schemas/DeliveryAddressResponse"}}, "frameContract": {"$ref": "#/components/schemas/FrameContractResponse"}, "preferences": {"type": "array", "items": {"type": "string"}}, "status": {"type": "string"}, "createdAt": {"type": "string", "format": "date-time"}, "updatedAt": {"type": "string", "format": "date-time"}}}, "DeliveryAddressResponse": {"required": ["address", "contactPerson", "deliveryNotes", "label"], "type": "object", "properties": {"label": {"type": "string"}, "address": {"$ref": "#/components/schemas/AddressResponse"}, "contactPerson": {"type": "string"}, "deliveryNotes": {"type": "string"}}}, "FrameContractResponse": {"required": ["deliveryRhythm", "id", "lineItems", "validFrom", "validUntil"], "type": "object", "properties": {"id": {"type": "string"}, "validFrom": {"type": "string", "format": "date"}, "validUntil": {"type": "string", "format": "date"}, "deliveryRhythm": {"type": "string"}, "lineItems": {"type": "array", "items": {"$ref": "#/components/schemas/ContractLineItemResponse"}}}, "nullable": true}, "SetPreferencesRequest": {"required": ["preferences"], "type": "object", "properties": {"preferences": {"uniqueItems": true, "type": "array", "items": {"type": "string", "enum": ["BIO", "REGIONAL", "TIERWOHL", "HALAL", "KOSHER", "GLUTENFREI", "LAKTOSEFREI"]}}}}, "LineItem": {"required": ["agreedPrice", "articleId"], "type": "object", "properties": {"articleId": {"type": "string"}, "agreedPrice": {"type": "number"}, "agreedQuantity": {"type": "number"}, "unit": {"type": "string", "enum": ["PIECE_FIXED", "KG", "HUNDRED_GRAM", "PIECE_VARIABLE"]}}}, "SetFrameContractRequest": {"required": ["lineItems", "rhythm"], "type": "object", "properties": {"validFrom": {"type": "string", "format": "date"}, "validUntil": {"type": "string", "format": "date"}, "rhythm": {"type": "string", "enum": ["DAILY", "WEEKLY", "BIWEEKLY", "MONTHLY", "ON_DEMAND"]}, "lineItems": {"type": "array", "items": {"$ref": "#/components/schemas/LineItem"}}}}, "UpdateProductCategoryRequest": {"type": "object", "properties": {"name": {"type": "string"}, "description": {"type": "string"}}}, "ProductCategoryResponse": {"required": ["description", "id", "name"], "type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "description": {"type": "string"}}}, "UpdateArticleRequest": {"type": "object", "properties": {"name": {"type": "string"}, "categoryId": {"type": "string"}}}, "ArticleResponse": {"required": ["articleNumber", "categoryId", "createdAt", "id", "name", "salesUnits", "status", "supplierIds", "updatedAt"], "type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "articleNumber": {"type": "string"}, "categoryId": {"type": "string"}, "salesUnits": {"type": "array", "items": {"$ref": "#/components/schemas/SalesUnitResponse"}}, "status": {"type": "string"}, "supplierIds": {"type": "array", "items": {"type": "string"}}, "createdAt": {"type": "string", "format": "date-time"}, "updatedAt": {"type": "string", "format": "date-time"}}}, "SalesUnitResponse": {"required": ["id", "price", "priceModel", "unit"], "type": "object", "properties": {"id": {"type": "string"}, "unit": {"type": "string"}, "priceModel": {"type": "string"}, "price": {"type": "number"}}}, "UpdateSalesUnitPriceRequest": {"required": ["price"], "type": "object", "properties": {"price": {"type": "number"}}}, "CreateUserRequest": {"required": ["email", "password", "roleNames", "username"], "type": "object", "properties": {"username": {"maxLength": 50, "minLength": 3, "type": "string", "description": "Username (unique)", "example": "john.doe"}, "email": {"type": "string", "description": "Email address (unique)", "example": "john.doe@example.com"}, "password": {"maxLength": 2147483647, "minLength": 8, "type": "string", "description": "Password (min 8 characters)", "example": "SecurePass123"}, "roleNames": {"uniqueItems": true, "type": "array", "description": "Role names to assign", "example": ["USER", "MANAGER"], "items": {"type": "string", "description": "Role names to assign", "example": "[\"USER\",\"MANAGER\"]", "enum": ["ADMIN", "PRODUCTION_MANAGER", "PRODUCTION_WORKER", "QUALITY_MANAGER", "QUALITY_INSPECTOR", "PROCUREMENT_MANAGER", "WAREHOUSE_WORKER", "SALES_MANAGER", "SALES_STAFF"]}}, "branchId": {"type": "string", "description": "Branch ID (optional)", "example": "BRANCH-001"}}, "description": "Request to create a new user"}, "AssignRoleRequest": {"required": ["roleName"], "type": "object", "properties": {"roleName": {"type": "string", "description": "Role name to assign", "example": "MANAGER", "enum": ["ADMIN", "PRODUCTION_MANAGER", "PRODUCTION_WORKER", "QUALITY_MANAGER", "QUALITY_INSPECTOR", "PROCUREMENT_MANAGER", "WAREHOUSE_WORKER", "SALES_MANAGER", "SALES_STAFF"]}}, "description": "Request to assign a role to a user"}, "CreateSupplierRequest": {"required": ["name", "phone"], "type": "object", "properties": {"name": {"type": "string"}, "phone": {"type": "string"}, "email": {"type": "string"}, "contactPerson": {"type": "string"}, "street": {"type": "string"}, "houseNumber": {"type": "string"}, "postalCode": {"type": "string"}, "city": {"type": "string"}, "country": {"type": "string"}, "paymentDueDays": {"type": "integer", "format": "int32"}, "paymentDescription": {"type": "string"}}}, "RateSupplierRequest": {"type": "object", "properties": {"qualityScore": {"maximum": 5, "minimum": 1, "type": "integer", "format": "int32"}, "deliveryScore": {"maximum": 5, "minimum": 1, "type": "integer", "format": "int32"}, "priceScore": {"maximum": 5, "minimum": 1, "type": "integer", "format": "int32"}}}, "AddCertificateRequest": {"required": ["certificateType"], "type": "object", "properties": {"certificateType": {"type": "string"}, "issuer": {"type": "string"}, "validFrom": {"type": "string", "format": "date"}, "validUntil": {"type": "string", "format": "date"}}}, "CreateRecipeRequest": {"required": ["name", "outputQuantity", "outputUom", "type"], "type": "object", "properties": {"name": {"type": "string"}, "version": {"type": "integer", "format": "int32"}, "type": {"type": "string", "enum": ["RAW_MATERIAL", "INTERMEDIATE", "FINISHED_PRODUCT"]}, "description": {"type": "string"}, "yieldPercentage": {"type": "integer", "format": "int32"}, "shelfLifeDays": {"type": "integer", "format": "int32"}, "outputQuantity": {"type": "string"}, "outputUom": {"type": "string"}}}, "IngredientResponse": {"required": ["articleId", "id", "position", "quantity", "substitutable", "uom"], "type": "object", "properties": {"id": {"type": "string"}, "position": {"type": "integer", "format": "int32"}, "articleId": {"type": "string"}, "quantity": {"type": "string"}, "uom": {"type": "string"}, "subRecipeId": {"type": "string", "nullable": true}, "substitutable": {"type": "boolean"}}}, "ProductionStepResponse": {"required": ["description", "id", "stepNumber"], "type": "object", "properties": {"id": {"type": "string"}, "stepNumber": {"type": "integer", "format": "int32"}, "description": {"type": "string"}, "durationMinutes": {"type": "integer", "format": "int32", "nullable": true}, "temperatureCelsius": {"type": "integer", "format": "int32", "nullable": true}}}, "RecipeResponse": {"required": ["createdAt", "description", "id", "ingredients", "name", "outputQuantity", "outputUom", "productionSteps", "status", "type", "updatedAt", "version", "yieldPercentage"], "type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "version": {"type": "integer", "format": "int32"}, "type": {"type": "string"}, "description": {"type": "string"}, "yieldPercentage": {"type": "integer", "format": "int32"}, "shelfLifeDays": {"type": "integer", "format": "int32", "nullable": true}, "outputQuantity": {"type": "string"}, "outputUom": {"type": "string"}, "status": {"type": "string"}, "ingredients": {"type": "array", "items": {"$ref": "#/components/schemas/IngredientResponse"}}, "productionSteps": {"type": "array", "items": {"$ref": "#/components/schemas/ProductionStepResponse"}}, "createdAt": {"type": "string", "format": "date-time"}, "updatedAt": {"type": "string", "format": "date-time"}}}, "AddProductionStepRequest": {"required": ["description"], "type": "object", "properties": {"stepNumber": {"minimum": 1, "type": "integer", "format": "int32"}, "description": {"maxLength": 500, "minLength": 0, "type": "string"}, "durationMinutes": {"minimum": 1, "type": "integer", "format": "int32"}, "temperatureCelsius": {"maximum": 1000, "minimum": -273, "type": "integer", "format": "int32"}}}, "AddRecipeIngredientRequest": {"required": ["articleId", "quantity", "uom"], "type": "object", "properties": {"position": {"minimum": 1, "type": "integer", "format": "int32"}, "articleId": {"type": "string"}, "quantity": {"type": "string"}, "uom": {"type": "string"}, "subRecipeId": {"type": "string"}, "substitutable": {"type": "boolean"}}}, "CreateStorageLocationRequest": {"required": ["name", "storageType"], "type": "object", "properties": {"name": {"type": "string"}, "storageType": {"type": "string"}, "minTemperature": {"type": "string"}, "maxTemperature": {"type": "string"}}}, "CreateCustomerRequest": {"required": ["city", "country", "name", "phone", "postalCode", "street", "type"], "type": "object", "properties": {"name": {"type": "string"}, "type": {"type": "string", "enum": ["B2C", "B2B"]}, "street": {"type": "string"}, "houseNumber": {"type": "string"}, "postalCode": {"type": "string"}, "city": {"type": "string"}, "country": {"type": "string"}, "phone": {"type": "string"}, "email": {"type": "string"}, "contactPerson": {"type": "string"}, "paymentDueDays": {"type": "integer", "format": "int32"}, "paymentDescription": {"type": "string"}}}, "AddDeliveryAddressRequest": {"required": ["city", "country", "postalCode", "street"], "type": "object", "properties": {"label": {"type": "string"}, "street": {"type": "string"}, "houseNumber": {"type": "string"}, "postalCode": {"type": "string"}, "city": {"type": "string"}, "country": {"type": "string"}, "contactPerson": {"type": "string"}, "deliveryNotes": {"type": "string"}}}, "CreateProductCategoryRequest": {"required": ["name"], "type": "object", "properties": {"name": {"type": "string"}, "description": {"type": "string"}}}, "RefreshTokenRequest": {"required": ["refreshToken"], "type": "object", "properties": {"refreshToken": {"type": "string", "description": "Refresh token"}}, "description": "Refresh token request"}, "LoginResponse": {"required": ["accessToken", "expiresAt", "expiresIn", "refreshToken", "tokenType"], "type": "object", "properties": {"accessToken": {"type": "string", "description": "JWT access token", "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}, "tokenType": {"type": "string", "description": "Token type", "example": "Bearer"}, "expiresIn": {"type": "integer", "description": "Token expiration time in seconds", "format": "int64", "example": 3600}, "expiresAt": {"type": "string", "description": "Token expiration timestamp", "format": "date-time"}, "refreshToken": {"type": "string", "description": "Refresh token for obtaining new access token"}}, "description": "Login response with JWT tokens"}, "LoginRequest": {"required": ["password", "username"], "type": "object", "properties": {"username": {"type": "string", "description": "Username", "example": "admin"}, "password": {"type": "string", "description": "Password", "example": "admin123"}}, "description": "Login request with username and password"}, "CreateArticleRequest": {"required": ["articleNumber", "categoryId", "name", "price", "priceModel", "unit"], "type": "object", "properties": {"name": {"type": "string"}, "articleNumber": {"type": "string"}, "categoryId": {"type": "string"}, "unit": {"type": "string", "enum": ["PIECE_FIXED", "KG", "HUNDRED_GRAM", "PIECE_VARIABLE"]}, "priceModel": {"type": "string", "enum": ["FIXED", "WEIGHT_BASED"]}, "price": {"type": "number"}}}, "AssignSupplierRequest": {"required": ["supplierId"], "type": "object", "properties": {"supplierId": {"type": "string"}}}, "AddSalesUnitRequest": {"required": ["price", "priceModel", "unit"], "type": "object", "properties": {"unit": {"type": "string", "enum": ["PIECE_FIXED", "KG", "HUNDRED_GRAM", "PIECE_VARIABLE"]}, "priceModel": {"type": "string", "enum": ["FIXED", "WEIGHT_BASED"]}, "price": {"type": "number"}}}, "RemoveCertificateRequest": {"required": ["certificateType"], "type": "object", "properties": {"certificateType": {"type": "string"}, "issuer": {"type": "string"}, "validFrom": {"type": "string", "format": "date"}}}, "CreateStockRequest": {"required": ["articleId", "storageLocationId"], "type": "object", "properties": {"articleId": {"type": "string"}, "storageLocationId": {"type": "string"}, "minimumLevelAmount": {"type": "string"}, "minimumLevelUnit": {"type": "string"}, "minimumShelfLifeDays": {"type": "integer", "format": "int32"}}}, "StockResponse": {"required": ["articleId", "id", "storageLocationId"], "type": "object", "properties": {"id": {"type": "string"}, "articleId": {"type": "string"}, "storageLocationId": {"type": "string"}, "minimumLevel": {"$ref": "#/components/schemas/MinimumLevelResponse"}, "minimumShelfLifeDays": {"type": "integer", "format": "int32", "nullable": true}}}}, "securitySchemes": {"Bearer Authentication": {"type": "http", "description": "JWT authentication token obtained from POST /api/auth/login.\n\nFormat: Bearer \n\nExample:\nBearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n", "scheme": "bearer", "bearerFormat": "JWT"}}}} +{"openapi":"3.0.1","info":{"title":"Effigenix Fleischerei ERP API","description":"RESTful API for Effigenix Fleischerei ERP System.\n\n## Authentication\n\nAll endpoints (except /api/auth/login and /api/auth/refresh) require JWT authentication.\n\n1. Login via POST /api/auth/login with username and password\n2. Copy the returned access token\n3. Click \"Authorize\" button (top right)\n4. Enter: Bearer \n5. Click \"Authorize\"\n\n## User Management\n\n- **Authentication**: Login, logout, refresh token\n- **User Management**: Create, update, list users (ADMIN only)\n- **Role Management**: Assign roles, lock/unlock users (ADMIN only)\n- **Password Management**: Change password (requires current password)\n\n## Error Handling\n\nAll errors return a consistent error response format:\n\n```json\n{\n \"code\": \"USER_NOT_FOUND\",\n \"message\": \"User with ID 'user-123' not found\",\n \"status\": 404,\n \"timestamp\": \"2026-02-17T12:00:00\",\n \"path\": \"/api/users/user-123\",\n \"validationErrors\": null\n}\n```\n\n## Architecture\n\nBuilt with:\n- Domain-Driven Design (DDD)\n- Clean Architecture (Hexagonal Architecture)\n- Spring Boot 3.2\n- Java 21\n- PostgreSQL\n","contact":{"name":"Effigenix Development Team","url":"https://effigenix.com","email":"dev@effigenix.com"},"license":{"name":"Proprietary","url":"https://effigenix.com/license"},"version":"0.1.0"},"servers":[{"url":"http://localhost:8080","description":"Local Development Server"},{"url":"https://api.effigenix.com","description":"Production Server"}],"tags":[{"name":"Storage Locations","description":"Storage location management endpoints"},{"name":"Product Categories","description":"Product category management endpoints"},{"name":"User Management","description":"User management endpoints (requires authentication)"},{"name":"Articles","description":"Article management endpoints"},{"name":"Recipes","description":"Recipe management endpoints"},{"name":"Role Management","description":"Role management endpoints (ADMIN only)"},{"name":"Stocks","description":"Stock management endpoints"},{"name":"Customers","description":"Customer management endpoints"},{"name":"Suppliers","description":"Supplier management endpoints"},{"name":"Authentication","description":"Authentication and session management endpoints"}],"paths":{"/api/users/{id}":{"get":{"tags":["User Management"],"summary":"Get user by ID","description":"Retrieve a single user by their ID.","operationId":"getUserById","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User retrieved successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"404":{"description":"User not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}}},"security":[{"Bearer Authentication":[]}]},"put":{"tags":["User Management"],"summary":"Update user","description":"Update user details (email, branchId).","operationId":"updateUser","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRequest"}}},"required":true},"responses":{"409":{"description":"Email already exists","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"200":{"description":"User updated successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"404":{"description":"User not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/users/{id}/password":{"put":{"tags":["User Management"],"summary":"Change password","description":"Change user password. Requires current password for verification.","operationId":"changePassword","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"400":{"description":"Invalid password"},"401":{"description":"Invalid current password"},"404":{"description":"User not found"},"204":{"description":"Password changed successfully"}},"security":[{"Bearer Authentication":[]}]}},"/api/suppliers/{id}":{"get":{"tags":["Suppliers"],"operationId":"getSupplier","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}}},"security":[{"Bearer Authentication":[]}]},"put":{"tags":["Suppliers"],"operationId":"updateSupplier","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSupplierRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/inventory/storage-locations/{id}":{"put":{"tags":["Storage Locations"],"operationId":"updateStorageLocation","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStorageLocationRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StorageLocationResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/customers/{id}":{"get":{"tags":["Customers"],"operationId":"getCustomer","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CustomerResponse"}}}}},"security":[{"Bearer Authentication":[]}]},"put":{"tags":["Customers"],"operationId":"updateCustomer","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomerRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CustomerResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/customers/{id}/preferences":{"put":{"tags":["Customers"],"operationId":"setPreferences","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPreferencesRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CustomerResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/customers/{id}/frame-contract":{"put":{"tags":["Customers"],"operationId":"setFrameContract","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetFrameContractRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CustomerResponse"}}}}},"security":[{"Bearer Authentication":[]}]},"delete":{"tags":["Customers"],"operationId":"removeFrameContract","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"Bearer Authentication":[]}]}},"/api/categories/{id}":{"put":{"tags":["Product Categories"],"operationId":"updateCategory","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductCategoryRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProductCategoryResponse"}}}}},"security":[{"Bearer Authentication":[]}]},"delete":{"tags":["Product Categories"],"operationId":"deleteCategory","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"Bearer Authentication":[]}]}},"/api/articles/{id}":{"get":{"tags":["Articles"],"operationId":"getArticle","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ArticleResponse"}}}}},"security":[{"Bearer Authentication":[]}]},"put":{"tags":["Articles"],"operationId":"updateArticle","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateArticleRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ArticleResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/articles/{id}/sales-units/{suId}/price":{"put":{"tags":["Articles"],"operationId":"updateSalesUnitPrice","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"suId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSalesUnitPriceRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ArticleResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/users":{"get":{"tags":["User Management"],"summary":"List all users","description":"Get a list of all users in the system.","operationId":"listUsers","responses":{"200":{"description":"Users retrieved successfully","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserDTO"}}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserDTO"}}}}}},"security":[{"Bearer Authentication":[]}]},"post":{"tags":["User Management"],"summary":"Create user (ADMIN only)","description":"Create a new user account with specified roles.","operationId":"createUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}},"required":true},"responses":{"400":{"description":"Validation error or invalid password","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"403":{"description":"Missing permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"409":{"description":"Username or email already exists","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"201":{"description":"User created successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/users/{id}/unlock":{"post":{"tags":["User Management"],"summary":"Unlock user (ADMIN only)","description":"Unlock a user account (allows login).","operationId":"unlockUser","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"string"}}],"responses":{"403":{"description":"Missing permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"200":{"description":"User unlocked successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"404":{"description":"User not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"409":{"description":"Invalid status transition","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/users/{id}/roles":{"post":{"tags":["User Management"],"summary":"Assign role (ADMIN only)","description":"Assign a role to a user.","operationId":"assignRole","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignRoleRequest"}}},"required":true},"responses":{"403":{"description":"Missing permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"200":{"description":"Role assigned successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"404":{"description":"User or role not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/users/{id}/lock":{"post":{"tags":["User Management"],"summary":"Lock user (ADMIN only)","description":"Lock a user account (prevents login).","operationId":"lockUser","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User locked successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"403":{"description":"Missing permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"404":{"description":"User not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"409":{"description":"Invalid status transition","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/suppliers":{"get":{"tags":["Suppliers"],"operationId":"listSuppliers","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["ACTIVE","INACTIVE"]}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SupplierResponse"}}}}}},"security":[{"Bearer Authentication":[]}]},"post":{"tags":["Suppliers"],"operationId":"createSupplier","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupplierRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/suppliers/{id}/rating":{"post":{"tags":["Suppliers"],"operationId":"rateSupplier","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateSupplierRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/suppliers/{id}/deactivate":{"post":{"tags":["Suppliers"],"operationId":"deactivate","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/suppliers/{id}/certificates":{"post":{"tags":["Suppliers"],"operationId":"addCertificate","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddCertificateRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}}},"security":[{"Bearer Authentication":[]}]},"delete":{"tags":["Suppliers"],"operationId":"removeCertificate","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveCertificateRequest"}}},"required":true},"responses":{"200":{"description":"OK"}},"security":[{"Bearer Authentication":[]}]}},"/api/suppliers/{id}/activate":{"post":{"tags":["Suppliers"],"operationId":"activate","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/recipes":{"get":{"tags":["Recipes"],"operationId":"listRecipes","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecipeSummaryResponse"}}}}}},"security":[{"Bearer Authentication":[]}]},"post":{"tags":["Recipes"],"operationId":"createRecipe","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecipeRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/recipes/{id}/steps":{"post":{"tags":["Recipes"],"operationId":"addProductionStep","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddProductionStepRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/recipes/{id}/ingredients":{"post":{"tags":["Recipes"],"operationId":"addIngredient","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddRecipeIngredientRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/recipes/{id}/archive":{"post":{"tags":["Recipes"],"operationId":"archiveRecipe","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/recipes/{id}/activate":{"post":{"tags":["Recipes"],"operationId":"activateRecipe","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/inventory/storage-locations":{"get":{"tags":["Storage Locations"],"operationId":"listStorageLocations","parameters":[{"name":"storageType","in":"query","required":false,"schema":{"type":"string"}},{"name":"active","in":"query","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StorageLocationResponse"}}}}}},"security":[{"Bearer Authentication":[]}]},"post":{"tags":["Storage Locations"],"operationId":"createStorageLocation","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStorageLocationRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StorageLocationResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/inventory/stocks":{"post":{"tags":["Stocks"],"operationId":"createStock","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStockRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StockResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/inventory/stocks/{stockId}/batches":{"post":{"tags":["Stocks"],"operationId":"addBatch","parameters":[{"name":"stockId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddStockBatchRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StockBatchResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/customers":{"get":{"tags":["Customers"],"operationId":"listCustomers","parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["B2C","B2B"]}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["ACTIVE","INACTIVE"]}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CustomerResponse"}}}}}},"security":[{"Bearer Authentication":[]}]},"post":{"tags":["Customers"],"operationId":"createCustomer","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CustomerResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/customers/{id}/delivery-addresses":{"post":{"tags":["Customers"],"operationId":"addDeliveryAddress","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddDeliveryAddressRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CustomerResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/customers/{id}/deactivate":{"post":{"tags":["Customers"],"operationId":"deactivate_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CustomerResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/customers/{id}/activate":{"post":{"tags":["Customers"],"operationId":"activate_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CustomerResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/categories":{"get":{"tags":["Product Categories"],"operationId":"listCategories","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProductCategoryResponse"}}}}}},"security":[{"Bearer Authentication":[]}]},"post":{"tags":["Product Categories"],"operationId":"createCategory","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductCategoryRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProductCategoryResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Refresh access token","description":"Refresh an expired access token using a valid refresh token.","operationId":"refresh","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshTokenRequest"}}},"required":true},"responses":{"401":{"description":"Invalid or expired refresh token","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"200":{"description":"Token refresh successful","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}}}}},"/api/auth/logout":{"post":{"tags":["Authentication"],"summary":"User logout","description":"Invalidate current JWT token.","operationId":"logout","responses":{"401":{"description":"Invalid or missing authentication token"},"204":{"description":"Logout successful"}}}},"/api/auth/login":{"post":{"tags":["Authentication"],"summary":"User login","description":"Authenticate user with username and password. Returns JWT tokens.","operationId":"login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"429":{"description":"Too many login attempts","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"description":"Invalid credentials, user locked, or user inactive","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"200":{"description":"Login successful","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}}}}},"/api/articles":{"get":{"tags":["Articles"],"operationId":"listArticles","parameters":[{"name":"categoryId","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["ACTIVE","INACTIVE"]}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ArticleResponse"}}}}}},"security":[{"Bearer Authentication":[]}]},"post":{"tags":["Articles"],"operationId":"createArticle","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateArticleRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ArticleResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/articles/{id}/suppliers":{"post":{"tags":["Articles"],"operationId":"assignSupplier","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignSupplierRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ArticleResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/articles/{id}/sales-units":{"post":{"tags":["Articles"],"operationId":"addSalesUnit","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSalesUnitRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ArticleResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/articles/{id}/deactivate":{"post":{"tags":["Articles"],"operationId":"deactivate_2","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ArticleResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/articles/{id}/activate":{"post":{"tags":["Articles"],"operationId":"activate_2","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ArticleResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/inventory/storage-locations/{id}/deactivate":{"patch":{"tags":["Storage Locations"],"operationId":"deactivateStorageLocation","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StorageLocationResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/inventory/storage-locations/{id}/activate":{"patch":{"tags":["Storage Locations"],"operationId":"activateStorageLocation","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StorageLocationResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/roles":{"get":{"tags":["Role Management"],"summary":"List all roles (ADMIN only)","description":"Get a list of all available roles in the system. Requires USER_MANAGEMENT permission.","operationId":"listRoles","responses":{"200":{"description":"Roles retrieved successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RoleDTO"}}}},"403":{"description":"Missing USER_MANAGEMENT permission","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RoleDTO"}}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RoleDTO"}}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/recipes/{id}":{"get":{"tags":["Recipes"],"operationId":"getRecipe","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeResponse"}}}}},"security":[{"Bearer Authentication":[]}]}},"/api/users/{id}/roles/{roleName}":{"delete":{"tags":["User Management"],"summary":"Remove role (ADMIN only)","description":"Remove a role from a user.","operationId":"removeRole","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"string"}},{"name":"roleName","in":"path","description":"Role name","required":true,"schema":{"type":"string","enum":["ADMIN","PRODUCTION_MANAGER","PRODUCTION_WORKER","QUALITY_MANAGER","QUALITY_INSPECTOR","PROCUREMENT_MANAGER","WAREHOUSE_WORKER","SALES_MANAGER","SALES_STAFF"]}}],"responses":{"403":{"description":"Missing permission"},"404":{"description":"User or role not found"},"204":{"description":"Role removed successfully"}},"security":[{"Bearer Authentication":[]}]}},"/api/recipes/{id}/steps/{stepNumber}":{"delete":{"tags":["Recipes"],"operationId":"removeProductionStep","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"stepNumber","in":"path","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK"}},"security":[{"Bearer Authentication":[]}]}},"/api/recipes/{id}/ingredients/{ingredientId}":{"delete":{"tags":["Recipes"],"operationId":"removeIngredient","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"ingredientId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"Bearer Authentication":[]}]}},"/api/customers/{id}/delivery-addresses/{label}":{"delete":{"tags":["Customers"],"operationId":"removeDeliveryAddress","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"label","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"Bearer Authentication":[]}]}},"/api/articles/{id}/suppliers/{supplierId}":{"delete":{"tags":["Articles"],"operationId":"removeSupplier","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"supplierId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"Bearer Authentication":[]}]}},"/api/articles/{id}/sales-units/{suId}":{"delete":{"tags":["Articles"],"operationId":"removeSalesUnit","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"suId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"Bearer Authentication":[]}]}}},"components":{"schemas":{"UpdateUserRequest":{"type":"object","properties":{"email":{"type":"string","description":"New email address","example":"newemail@example.com"},"branchId":{"type":"string","description":"New branch ID","example":"BRANCH-002"}},"description":"Request to update user details"},"RoleDTO":{"required":["description","id","name","permissions"],"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","enum":["ADMIN","PRODUCTION_MANAGER","PRODUCTION_WORKER","QUALITY_MANAGER","QUALITY_INSPECTOR","PROCUREMENT_MANAGER","WAREHOUSE_WORKER","SALES_MANAGER","SALES_STAFF"]},"permissions":{"uniqueItems":true,"type":"array","items":{"type":"string","enum":["RECIPE_READ","RECIPE_WRITE","RECIPE_DELETE","BATCH_READ","BATCH_WRITE","BATCH_COMPLETE","BATCH_DELETE","PRODUCTION_ORDER_READ","PRODUCTION_ORDER_WRITE","PRODUCTION_ORDER_DELETE","HACCP_READ","HACCP_WRITE","TEMPERATURE_LOG_READ","TEMPERATURE_LOG_WRITE","CLEANING_RECORD_READ","CLEANING_RECORD_WRITE","GOODS_INSPECTION_READ","GOODS_INSPECTION_WRITE","STOCK_READ","STOCK_WRITE","STOCK_MOVEMENT_READ","STOCK_MOVEMENT_WRITE","INVENTORY_COUNT_READ","INVENTORY_COUNT_WRITE","PURCHASE_ORDER_READ","PURCHASE_ORDER_WRITE","PURCHASE_ORDER_DELETE","GOODS_RECEIPT_READ","GOODS_RECEIPT_WRITE","SUPPLIER_READ","SUPPLIER_WRITE","SUPPLIER_DELETE","ORDER_READ","ORDER_WRITE","ORDER_DELETE","INVOICE_READ","INVOICE_WRITE","INVOICE_DELETE","CUSTOMER_READ","CUSTOMER_WRITE","CUSTOMER_DELETE","LABEL_READ","LABEL_WRITE","LABEL_PRINT","MASTERDATA_READ","MASTERDATA_WRITE","BRANCH_READ","BRANCH_WRITE","BRANCH_DELETE","USER_READ","USER_WRITE","USER_DELETE","USER_LOCK","USER_UNLOCK","ROLE_READ","ROLE_WRITE","ROLE_ASSIGN","ROLE_REMOVE","REPORT_READ","REPORT_GENERATE","NOTIFICATION_READ","NOTIFICATION_SEND","AUDIT_LOG_READ","SYSTEM_SETTINGS_READ","SYSTEM_SETTINGS_WRITE"]}},"description":{"type":"string"}}},"UserDTO":{"required":["createdAt","email","id","roles","status","username"],"type":"object","properties":{"id":{"type":"string"},"username":{"type":"string"},"email":{"type":"string"},"roles":{"uniqueItems":true,"type":"array","items":{"$ref":"#/components/schemas/RoleDTO"}},"branchId":{"type":"string"},"status":{"type":"string","enum":["ACTIVE","INACTIVE","LOCKED"]},"createdAt":{"type":"string","format":"date-time"},"lastLogin":{"type":"string","format":"date-time"}}},"ChangePasswordRequest":{"required":["currentPassword","newPassword"],"type":"object","properties":{"currentPassword":{"type":"string","description":"Current password","example":"OldPass123"},"newPassword":{"maxLength":2147483647,"minLength":8,"type":"string","description":"New password (min 8 characters)","example":"NewSecurePass456"}},"description":"Request to change user password"},"UpdateSupplierRequest":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"contactPerson":{"type":"string"},"street":{"type":"string"},"houseNumber":{"type":"string"},"postalCode":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"paymentDueDays":{"type":"integer","format":"int32"},"paymentDescription":{"type":"string"}}},"AddressResponse":{"required":["city","country","houseNumber","postalCode","street"],"type":"object","properties":{"street":{"type":"string"},"houseNumber":{"type":"string"},"postalCode":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"}},"nullable":true},"ContactInfoResponse":{"required":["contactPerson","email","phone"],"type":"object","properties":{"phone":{"type":"string"},"email":{"type":"string"},"contactPerson":{"type":"string"}}},"PaymentTermsResponse":{"required":["paymentDescription","paymentDueDays"],"type":"object","properties":{"paymentDueDays":{"type":"integer","format":"int32"},"paymentDescription":{"type":"string"}},"nullable":true},"QualityCertificateResponse":{"required":["certificateType","issuer","validFrom","validUntil"],"type":"object","properties":{"certificateType":{"type":"string"},"issuer":{"type":"string"},"validFrom":{"type":"string","format":"date"},"validUntil":{"type":"string","format":"date"}}},"SupplierRatingResponse":{"required":["deliveryScore","priceScore","qualityScore"],"type":"object","properties":{"qualityScore":{"type":"integer","format":"int32"},"deliveryScore":{"type":"integer","format":"int32"},"priceScore":{"type":"integer","format":"int32"}},"nullable":true},"SupplierResponse":{"required":["certificates","contactInfo","createdAt","id","name","status","updatedAt"],"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"address":{"$ref":"#/components/schemas/AddressResponse"},"contactInfo":{"$ref":"#/components/schemas/ContactInfoResponse"},"paymentTerms":{"$ref":"#/components/schemas/PaymentTermsResponse"},"certificates":{"type":"array","items":{"$ref":"#/components/schemas/QualityCertificateResponse"}},"rating":{"$ref":"#/components/schemas/SupplierRatingResponse"},"status":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"UpdateStorageLocationRequest":{"type":"object","properties":{"name":{"type":"string"},"minTemperature":{"type":"string"},"maxTemperature":{"type":"string"}}},"StorageLocationResponse":{"required":["active","id","name","storageType"],"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"storageType":{"type":"string"},"temperatureRange":{"$ref":"#/components/schemas/TemperatureRangeResponse"},"active":{"type":"boolean"}}},"TemperatureRangeResponse":{"required":["maxTemperature","minTemperature"],"type":"object","properties":{"minTemperature":{"type":"number"},"maxTemperature":{"type":"number"}},"nullable":true},"UpdateCustomerRequest":{"type":"object","properties":{"name":{"type":"string"},"street":{"type":"string"},"houseNumber":{"type":"string"},"postalCode":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"contactPerson":{"type":"string"},"paymentDueDays":{"type":"integer","format":"int32"},"paymentDescription":{"type":"string"}}},"ContractLineItemResponse":{"required":["agreedPrice","agreedQuantity","articleId","unit"],"type":"object","properties":{"articleId":{"type":"string"},"agreedPrice":{"type":"number"},"agreedQuantity":{"type":"number"},"unit":{"type":"string"}}},"CustomerResponse":{"required":["billingAddress","contactInfo","createdAt","deliveryAddresses","id","name","preferences","status","type","updatedAt"],"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"billingAddress":{"$ref":"#/components/schemas/AddressResponse"},"contactInfo":{"$ref":"#/components/schemas/ContactInfoResponse"},"paymentTerms":{"$ref":"#/components/schemas/PaymentTermsResponse"},"deliveryAddresses":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryAddressResponse"}},"frameContract":{"$ref":"#/components/schemas/FrameContractResponse"},"preferences":{"type":"array","items":{"type":"string"}},"status":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"DeliveryAddressResponse":{"required":["address","contactPerson","deliveryNotes","label"],"type":"object","properties":{"label":{"type":"string"},"address":{"$ref":"#/components/schemas/AddressResponse"},"contactPerson":{"type":"string"},"deliveryNotes":{"type":"string"}}},"FrameContractResponse":{"required":["deliveryRhythm","id","lineItems","validFrom","validUntil"],"type":"object","properties":{"id":{"type":"string"},"validFrom":{"type":"string","format":"date"},"validUntil":{"type":"string","format":"date"},"deliveryRhythm":{"type":"string"},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/ContractLineItemResponse"}}},"nullable":true},"SetPreferencesRequest":{"required":["preferences"],"type":"object","properties":{"preferences":{"uniqueItems":true,"type":"array","items":{"type":"string","enum":["BIO","REGIONAL","TIERWOHL","HALAL","KOSHER","GLUTENFREI","LAKTOSEFREI"]}}}},"LineItem":{"required":["agreedPrice","articleId"],"type":"object","properties":{"articleId":{"type":"string"},"agreedPrice":{"type":"number"},"agreedQuantity":{"type":"number"},"unit":{"type":"string","enum":["PIECE_FIXED","KG","HUNDRED_GRAM","PIECE_VARIABLE"]}}},"SetFrameContractRequest":{"required":["lineItems","rhythm"],"type":"object","properties":{"validFrom":{"type":"string","format":"date"},"validUntil":{"type":"string","format":"date"},"rhythm":{"type":"string","enum":["DAILY","WEEKLY","BIWEEKLY","MONTHLY","ON_DEMAND"]},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItem"}}}},"UpdateProductCategoryRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"}}},"ProductCategoryResponse":{"required":["description","id","name"],"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"}}},"UpdateArticleRequest":{"type":"object","properties":{"name":{"type":"string"},"categoryId":{"type":"string"}}},"ArticleResponse":{"required":["articleNumber","categoryId","createdAt","id","name","salesUnits","status","supplierIds","updatedAt"],"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"articleNumber":{"type":"string"},"categoryId":{"type":"string"},"salesUnits":{"type":"array","items":{"$ref":"#/components/schemas/SalesUnitResponse"}},"status":{"type":"string"},"supplierIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SalesUnitResponse":{"required":["id","price","priceModel","unit"],"type":"object","properties":{"id":{"type":"string"},"unit":{"type":"string"},"priceModel":{"type":"string"},"price":{"type":"number"}}},"UpdateSalesUnitPriceRequest":{"required":["price"],"type":"object","properties":{"price":{"type":"number"}}},"CreateUserRequest":{"required":["email","password","roleNames","username"],"type":"object","properties":{"username":{"maxLength":50,"minLength":3,"type":"string","description":"Username (unique)","example":"john.doe"},"email":{"type":"string","description":"Email address (unique)","example":"john.doe@example.com"},"password":{"maxLength":2147483647,"minLength":8,"type":"string","description":"Password (min 8 characters)","example":"SecurePass123"},"roleNames":{"uniqueItems":true,"type":"array","description":"Role names to assign","example":["USER","MANAGER"],"items":{"type":"string","description":"Role names to assign","example":"[\"USER\",\"MANAGER\"]","enum":["ADMIN","PRODUCTION_MANAGER","PRODUCTION_WORKER","QUALITY_MANAGER","QUALITY_INSPECTOR","PROCUREMENT_MANAGER","WAREHOUSE_WORKER","SALES_MANAGER","SALES_STAFF"]}},"branchId":{"type":"string","description":"Branch ID (optional)","example":"BRANCH-001"}},"description":"Request to create a new user"},"AssignRoleRequest":{"required":["roleName"],"type":"object","properties":{"roleName":{"type":"string","description":"Role name to assign","example":"MANAGER","enum":["ADMIN","PRODUCTION_MANAGER","PRODUCTION_WORKER","QUALITY_MANAGER","QUALITY_INSPECTOR","PROCUREMENT_MANAGER","WAREHOUSE_WORKER","SALES_MANAGER","SALES_STAFF"]}},"description":"Request to assign a role to a user"},"CreateSupplierRequest":{"required":["name","phone"],"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"contactPerson":{"type":"string"},"street":{"type":"string"},"houseNumber":{"type":"string"},"postalCode":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"paymentDueDays":{"type":"integer","format":"int32"},"paymentDescription":{"type":"string"}}},"RateSupplierRequest":{"type":"object","properties":{"qualityScore":{"maximum":5,"minimum":1,"type":"integer","format":"int32"},"deliveryScore":{"maximum":5,"minimum":1,"type":"integer","format":"int32"},"priceScore":{"maximum":5,"minimum":1,"type":"integer","format":"int32"}}},"AddCertificateRequest":{"required":["certificateType"],"type":"object","properties":{"certificateType":{"type":"string"},"issuer":{"type":"string"},"validFrom":{"type":"string","format":"date"},"validUntil":{"type":"string","format":"date"}}},"CreateRecipeRequest":{"required":["name","outputQuantity","outputUom","type"],"type":"object","properties":{"name":{"type":"string"},"version":{"type":"integer","format":"int32"},"type":{"type":"string","enum":["RAW_MATERIAL","INTERMEDIATE","FINISHED_PRODUCT"]},"description":{"type":"string"},"yieldPercentage":{"type":"integer","format":"int32"},"shelfLifeDays":{"type":"integer","format":"int32"},"outputQuantity":{"type":"string"},"outputUom":{"type":"string"}}},"IngredientResponse":{"required":["articleId","id","position","quantity","substitutable","uom"],"type":"object","properties":{"id":{"type":"string"},"position":{"type":"integer","format":"int32"},"articleId":{"type":"string"},"quantity":{"type":"string"},"uom":{"type":"string"},"subRecipeId":{"type":"string","nullable":true},"substitutable":{"type":"boolean"}}},"ProductionStepResponse":{"required":["description","id","stepNumber"],"type":"object","properties":{"id":{"type":"string"},"stepNumber":{"type":"integer","format":"int32"},"description":{"type":"string"},"durationMinutes":{"type":"integer","format":"int32","nullable":true},"temperatureCelsius":{"type":"integer","format":"int32","nullable":true}}},"RecipeResponse":{"required":["createdAt","description","id","ingredients","name","outputQuantity","outputUom","productionSteps","status","type","updatedAt","version","yieldPercentage"],"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"version":{"type":"integer","format":"int32"},"type":{"type":"string"},"description":{"type":"string"},"yieldPercentage":{"type":"integer","format":"int32"},"shelfLifeDays":{"type":"integer","format":"int32","nullable":true},"outputQuantity":{"type":"string"},"outputUom":{"type":"string"},"status":{"type":"string"},"ingredients":{"type":"array","items":{"$ref":"#/components/schemas/IngredientResponse"}},"productionSteps":{"type":"array","items":{"$ref":"#/components/schemas/ProductionStepResponse"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"AddProductionStepRequest":{"required":["description"],"type":"object","properties":{"stepNumber":{"minimum":1,"type":"integer","format":"int32"},"description":{"maxLength":500,"minLength":0,"type":"string"},"durationMinutes":{"minimum":1,"type":"integer","format":"int32"},"temperatureCelsius":{"maximum":1000,"minimum":-273,"type":"integer","format":"int32"}}},"AddRecipeIngredientRequest":{"required":["articleId","quantity","uom"],"type":"object","properties":{"position":{"minimum":1,"type":"integer","format":"int32"},"articleId":{"type":"string"},"quantity":{"type":"string"},"uom":{"type":"string"},"subRecipeId":{"type":"string"},"substitutable":{"type":"boolean"}}},"CreateStorageLocationRequest":{"required":["name","storageType"],"type":"object","properties":{"name":{"type":"string"},"storageType":{"type":"string"},"minTemperature":{"type":"string"},"maxTemperature":{"type":"string"}}},"CreateStockRequest":{"required":["articleId","storageLocationId"],"type":"object","properties":{"articleId":{"type":"string"},"storageLocationId":{"type":"string"},"minimumLevelAmount":{"type":"string"},"minimumLevelUnit":{"type":"string"},"minimumShelfLifeDays":{"type":"integer","format":"int32"}}},"MinimumLevelResponse":{"required":["amount","unit"],"type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string"}},"nullable":true},"StockResponse":{"required":["articleId","id","storageLocationId"],"type":"object","properties":{"id":{"type":"string"},"articleId":{"type":"string"},"storageLocationId":{"type":"string"},"minimumLevel":{"$ref":"#/components/schemas/MinimumLevelResponse"},"minimumShelfLifeDays":{"type":"integer","format":"int32","nullable":true}}},"AddStockBatchRequest":{"required":["batchId","batchType","expiryDate","quantityAmount","quantityUnit"],"type":"object","properties":{"batchId":{"type":"string"},"batchType":{"type":"string"},"quantityAmount":{"type":"string"},"quantityUnit":{"type":"string"},"expiryDate":{"type":"string"}}},"StockBatchResponse":{"type":"object","properties":{"id":{"type":"string"},"batchId":{"type":"string"},"batchType":{"type":"string"},"quantityAmount":{"type":"number"},"quantityUnit":{"type":"string"},"expiryDate":{"type":"string","format":"date"},"status":{"type":"string"},"receivedAt":{"type":"string","format":"date-time"}}},"CreateCustomerRequest":{"required":["city","country","name","phone","postalCode","street","type"],"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["B2C","B2B"]},"street":{"type":"string"},"houseNumber":{"type":"string"},"postalCode":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"contactPerson":{"type":"string"},"paymentDueDays":{"type":"integer","format":"int32"},"paymentDescription":{"type":"string"}}},"AddDeliveryAddressRequest":{"required":["city","country","postalCode","street"],"type":"object","properties":{"label":{"type":"string"},"street":{"type":"string"},"houseNumber":{"type":"string"},"postalCode":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"contactPerson":{"type":"string"},"deliveryNotes":{"type":"string"}}},"CreateProductCategoryRequest":{"required":["name"],"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"}}},"RefreshTokenRequest":{"required":["refreshToken"],"type":"object","properties":{"refreshToken":{"type":"string","description":"Refresh token"}},"description":"Refresh token request"},"LoginResponse":{"required":["accessToken","expiresAt","expiresIn","refreshToken","tokenType"],"type":"object","properties":{"accessToken":{"type":"string","description":"JWT access token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"tokenType":{"type":"string","description":"Token type","example":"Bearer"},"expiresIn":{"type":"integer","description":"Token expiration time in seconds","format":"int64","example":3600},"expiresAt":{"type":"string","description":"Token expiration timestamp","format":"date-time"},"refreshToken":{"type":"string","description":"Refresh token for obtaining new access token"}},"description":"Login response with JWT tokens"},"LoginRequest":{"required":["password","username"],"type":"object","properties":{"username":{"type":"string","description":"Username","example":"admin"},"password":{"type":"string","description":"Password","example":"admin123"}},"description":"Login request with username and password"},"CreateArticleRequest":{"required":["articleNumber","categoryId","name","price","priceModel","unit"],"type":"object","properties":{"name":{"type":"string"},"articleNumber":{"type":"string"},"categoryId":{"type":"string"},"unit":{"type":"string","enum":["PIECE_FIXED","KG","HUNDRED_GRAM","PIECE_VARIABLE"]},"priceModel":{"type":"string","enum":["FIXED","WEIGHT_BASED"]},"price":{"type":"number"}}},"AssignSupplierRequest":{"required":["supplierId"],"type":"object","properties":{"supplierId":{"type":"string"}}},"AddSalesUnitRequest":{"required":["price","priceModel","unit"],"type":"object","properties":{"unit":{"type":"string","enum":["PIECE_FIXED","KG","HUNDRED_GRAM","PIECE_VARIABLE"]},"priceModel":{"type":"string","enum":["FIXED","WEIGHT_BASED"]},"price":{"type":"number"}}},"RecipeSummaryResponse":{"required":["createdAt","description","id","ingredientCount","name","outputQuantity","outputUom","status","stepCount","type","updatedAt","version","yieldPercentage"],"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"version":{"type":"integer","format":"int32"},"type":{"type":"string"},"description":{"type":"string"},"yieldPercentage":{"type":"integer","format":"int32"},"shelfLifeDays":{"type":"integer","format":"int32","nullable":true},"outputQuantity":{"type":"string"},"outputUom":{"type":"string"},"status":{"type":"string"},"ingredientCount":{"type":"integer","format":"int32"},"stepCount":{"type":"integer","format":"int32"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"RemoveCertificateRequest":{"required":["certificateType"],"type":"object","properties":{"certificateType":{"type":"string"},"issuer":{"type":"string"},"validFrom":{"type":"string","format":"date"}}}},"securitySchemes":{"Bearer Authentication":{"type":"http","description":"JWT authentication token obtained from POST /api/auth/login.\n\nFormat: Bearer \n\nExample:\nBearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n","scheme":"bearer","bearerFormat":"JWT"}}}} \ No newline at end of file diff --git a/frontend/packages/api-client/src/index.ts b/frontend/packages/api-client/src/index.ts index 060849a..ad2ac58 100644 --- a/frontend/packages/api-client/src/index.ts +++ b/frontend/packages/api-client/src/index.ts @@ -24,6 +24,7 @@ export { createArticlesResource } from './resources/articles.js'; export { createCustomersResource } from './resources/customers.js'; export { createStorageLocationsResource } from './resources/storage-locations.js'; export { createRecipesResource } from './resources/recipes.js'; +export { createStocksResource } from './resources/stocks.js'; export { ApiError, AuthenticationError, @@ -81,11 +82,14 @@ export type { CreateStorageLocationRequest, UpdateStorageLocationRequest, RecipeDTO, + RecipeSummaryDTO, IngredientDTO, ProductionStepDTO, CreateRecipeRequest, AddRecipeIngredientRequest, AddProductionStepRequest, + StockBatchDTO, + AddStockBatchRequest, } from '@effigenix/types'; // Resource types (runtime, stay in resource files) @@ -111,6 +115,8 @@ export type { export { STORAGE_TYPE_LABELS } from './resources/storage-locations.js'; export type { RecipesResource, RecipeType, RecipeStatus } from './resources/recipes.js'; export { RECIPE_TYPE_LABELS } from './resources/recipes.js'; +export type { StocksResource, BatchType } from './resources/stocks.js'; +export { BATCH_TYPE_LABELS } from './resources/stocks.js'; import { createApiClient } from './client.js'; import { createAuthResource } from './resources/auth.js'; @@ -122,6 +128,7 @@ import { createArticlesResource } from './resources/articles.js'; import { createCustomersResource } from './resources/customers.js'; import { createStorageLocationsResource } from './resources/storage-locations.js'; import { createRecipesResource } from './resources/recipes.js'; +import { createStocksResource } from './resources/stocks.js'; import type { TokenProvider } from './token-provider.js'; import type { ApiConfig } from '@effigenix/config'; @@ -145,6 +152,7 @@ export function createEffigenixClient( customers: createCustomersResource(axiosClient), storageLocations: createStorageLocationsResource(axiosClient), recipes: createRecipesResource(axiosClient), + stocks: createStocksResource(axiosClient), }; } diff --git a/frontend/packages/api-client/src/resources/recipes.ts b/frontend/packages/api-client/src/resources/recipes.ts index e5d2142..d971165 100644 --- a/frontend/packages/api-client/src/resources/recipes.ts +++ b/frontend/packages/api-client/src/resources/recipes.ts @@ -3,6 +3,7 @@ import type { AxiosInstance } from 'axios'; import type { RecipeDTO, + RecipeSummaryDTO, IngredientDTO, ProductionStepDTO, CreateRecipeRequest, @@ -21,6 +22,7 @@ export const RECIPE_TYPE_LABELS: Record = { export type { RecipeDTO, + RecipeSummaryDTO, IngredientDTO, ProductionStepDTO, CreateRecipeRequest, @@ -34,8 +36,10 @@ const BASE = '/api/recipes'; export function createRecipesResource(client: AxiosInstance) { return { - async list(): Promise { - const res = await client.get(BASE); + async list(status?: RecipeStatus): Promise { + const params: Record = {}; + if (status) params['status'] = status; + const res = await client.get(BASE, { params }); return res.data; }, @@ -71,6 +75,11 @@ export function createRecipesResource(client: AxiosInstance) { const res = await client.post(`${BASE}/${id}/activate`); return res.data; }, + + async archiveRecipe(id: string): Promise { + const res = await client.post(`${BASE}/${id}/archive`); + return res.data; + }, }; } diff --git a/frontend/packages/api-client/src/resources/stocks.ts b/frontend/packages/api-client/src/resources/stocks.ts new file mode 100644 index 0000000..4874d26 --- /dev/null +++ b/frontend/packages/api-client/src/resources/stocks.ts @@ -0,0 +1,28 @@ +/** Stocks resource – Inventory BC. */ + +import type { AxiosInstance } from 'axios'; +import type { StockBatchDTO, AddStockBatchRequest } from '@effigenix/types'; + +export type BatchType = 'PURCHASED' | 'PRODUCED'; + +export const BATCH_TYPE_LABELS: Record = { + PURCHASED: 'Eingekauft', + PRODUCED: 'Produziert', +}; + +export type { StockBatchDTO, AddStockBatchRequest }; + +// ── Resource factory ───────────────────────────────────────────────────────── + +const BASE = '/api/inventory/stocks'; + +export function createStocksResource(client: AxiosInstance) { + return { + async addBatch(stockId: string, request: AddStockBatchRequest): Promise { + const res = await client.post(`${BASE}/${stockId}/batches`, request); + return res.data; + }, + }; +} + +export type StocksResource = ReturnType; diff --git a/frontend/packages/types/src/generated/api.ts b/frontend/packages/types/src/generated/api.ts index 4e958f5..3fbc6c5 100644 --- a/frontend/packages/types/src/generated/api.ts +++ b/frontend/packages/types/src/generated/api.ts @@ -347,7 +347,7 @@ export interface paths { path?: never; cookie?: never; }; - get?: never; + get: operations["listRecipes"]; put?: never; post: operations["createRecipe"]; delete?: never; @@ -388,6 +388,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/recipes/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["archiveRecipe"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/recipes/{id}/activate": { parameters: { query?: never; @@ -436,6 +452,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/inventory/stocks/{stockId}/batches": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["addBatch"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/customers": { parameters: { query?: never; @@ -708,6 +740,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/recipes/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getRecipe"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/users/{id}/roles/{roleName}": { parameters: { query?: never; @@ -1209,6 +1257,25 @@ export interface components { /** Format: int32 */ minimumShelfLifeDays?: number | null; }; + AddStockBatchRequest: { + batchId: string; + batchType: string; + quantityAmount: string; + quantityUnit: string; + expiryDate: string; + }; + StockBatchResponse: { + id?: string; + batchId?: string; + batchType?: string; + quantityAmount?: number; + quantityUnit?: string; + /** Format: date */ + expiryDate?: string; + status?: string; + /** Format: date-time */ + receivedAt?: string; + }; CreateCustomerRequest: { name: string; /** @enum {string} */ @@ -1303,6 +1370,29 @@ export interface components { priceModel: "FIXED" | "WEIGHT_BASED"; price: number; }; + RecipeSummaryResponse: { + id: string; + name: string; + /** Format: int32 */ + version: number; + type: string; + description: string; + /** Format: int32 */ + yieldPercentage: number; + /** Format: int32 */ + shelfLifeDays?: number | null; + outputQuantity: string; + outputUom: string; + status: string; + /** Format: int32 */ + ingredientCount: number; + /** Format: int32 */ + stepCount: number; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + updatedAt: string; + }; RemoveCertificateRequest: { certificateType: string; issuer?: string; @@ -2156,6 +2246,28 @@ export interface operations { }; }; }; + listRecipes: { + parameters: { + query?: { + status?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["RecipeSummaryResponse"][]; + }; + }; + }; + }; createRecipe: { parameters: { query?: never; @@ -2232,6 +2344,28 @@ export interface operations { }; }; }; + archiveRecipe: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["RecipeResponse"]; + }; + }; + }; + }; activateRecipe: { parameters: { query?: never; @@ -2325,6 +2459,32 @@ export interface operations { }; }; }; + addBatch: { + parameters: { + query?: never; + header?: never; + path: { + stockId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AddStockBatchRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["StockBatchResponse"]; + }; + }; + }; + }; listCustomers: { parameters: { query?: { @@ -2811,6 +2971,28 @@ export interface operations { }; }; }; + getRecipe: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["RecipeResponse"]; + }; + }; + }; + }; removeRole: { parameters: { query?: never; diff --git a/frontend/packages/types/src/inventory.ts b/frontend/packages/types/src/inventory.ts index 5934e51..4be2059 100644 --- a/frontend/packages/types/src/inventory.ts +++ b/frontend/packages/types/src/inventory.ts @@ -8,7 +8,9 @@ import type { components } from './generated/api'; // Response DTOs export type StorageLocationDTO = components['schemas']['StorageLocationResponse']; export type TemperatureRangeDTO = components['schemas']['TemperatureRangeResponse']; +export type StockBatchDTO = components['schemas']['StockBatchResponse']; // Request types export type CreateStorageLocationRequest = components['schemas']['CreateStorageLocationRequest']; export type UpdateStorageLocationRequest = components['schemas']['UpdateStorageLocationRequest']; +export type AddStockBatchRequest = components['schemas']['AddStockBatchRequest']; diff --git a/frontend/packages/types/src/production.ts b/frontend/packages/types/src/production.ts index c4812ec..1ba14ff 100644 --- a/frontend/packages/types/src/production.ts +++ b/frontend/packages/types/src/production.ts @@ -7,6 +7,7 @@ import type { components } from './generated/api'; // Response DTOs export type RecipeDTO = components['schemas']['RecipeResponse']; +export type RecipeSummaryDTO = components['schemas']['RecipeSummaryResponse']; export type IngredientDTO = components['schemas']['IngredientResponse']; export type ProductionStepDTO = components['schemas']['ProductionStepResponse'];