{"openapi":"3.1.0","info":{"title":"Food Production API","description":"Ingredients, products and recipes, stock and production planning.","version":"0.0.1"},"servers":[{"url":"/"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Green zone","description":"Weeks-of-stock bounds and product overrides"},{"name":"Stock","description":"Manual stock and average weekly sales per product"},{"name":"Machines","description":"Facility machines. Enabled is the active flag; disabled names remain reserved. Writes require admin permission."}],"paths":{"/api/v1/settings/green-zone":{"get":{"tags":["Green zone"],"summary":"Read global green zone","description":"Defaults to 3–5 weeks. Products without overrides immediately inherit global changes.","operationId":"get","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GreenZone"}}}}}},"put":{"tags":["Green zone"],"summary":"Set global green zone","description":"Admin only. Both bounds are required, with 0 < minWeeks < maxWeeks <= 520 and at most two decimal places.","operationId":"set","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GreenZone"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GreenZone"}}}}}}},"/api/v1/products/{id}":{"get":{"tags":["product-controller"],"operationId":"get_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProductSummary"}}}}}},"put":{"tags":["product-controller"],"operationId":"update","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProductSummary"}}}}}},"delete":{"tags":["product-controller"],"operationId":"delete","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK"}}}},"/api/v1/products/{id}/weekly-sales":{"put":{"tags":["Stock"],"summary":"Set average weekly sales","description":"Admin or production manager. Refused for INTERMEDIATE products: their weekly usage is calculated recursively in grams. Does not create stock movements. Optional version uses the same stock-row version as stock writes.","operationId":"setSales","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SalesWrite"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StockResponse"}}}}}}},"/api/v1/products/{id}/stock":{"get":{"tags":["Stock"],"summary":"Read stock, sales and effective green zone","description":"An existing product without a stock row returns zeros and a null version.","operationId":"get_2","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StockResponse"}}}}}},"put":{"tags":["Stock"],"summary":"Set absolute stock","description":"Admin or production manager. Intermediate stock is in grams. Actual changes create a MANUAL movement; unchanged stock creates no movement. Optional version rejects stale writes with 409.","operationId":"setStock","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockWrite"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StockResponse"}}}}}}},"/api/v1/products/{id}/recipe":{"get":{"tags":["recipe-controller"],"operationId":"current","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}},"put":{"tags":["recipe-controller"],"operationId":"save","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}}},"/api/v1/products/{id}/recipe/steps/{stepId}":{"put":{"tags":["recipe-step-controller"],"summary":"Edit a current step","description":"Requires revision; forks if in use. A null machineId makes this a manual step.","operationId":"edit","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StepRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}},"delete":{"tags":["recipe-step-controller"],"summary":"Delete a step","description":"Requires revision; forks if in use, clears assignments and offsets, and renumbers steps.","operationId":"delete_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Revision"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}}},"/api/v1/products/{id}/recipe/steps/{stepId}/lines/{ingredientId}":{"put":{"tags":["recipe-step-controller"],"summary":"Assign a line by ingredient ID","description":"Requires revision; optional nonnegative offsetMinutes may exceed duration. Returns resulting version after any fork.","operationId":"assign","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"ingredientId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Assignment"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}},"delete":{"tags":["recipe-step-controller"],"summary":"Unassign a line","description":"Requires revision and matching current assignment; clears offset. Returns resulting version after any fork.","operationId":"unassign","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"ingredientId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Revision"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}}},"/api/v1/products/{id}/recipe/steps/{stepId}/lines/products/{productId}":{"put":{"tags":["recipe-step-controller"],"operationId":"assignProduct","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"productId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Assignment"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}},"delete":{"tags":["recipe-step-controller"],"operationId":"unassignProduct","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"productId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Revision"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}}},"/api/v1/products/{id}/recipe/steps/reorder":{"put":{"tags":["recipe-step-controller"],"summary":"Reorder steps","description":"Supply revision and the exact current step ID set in desired order. Returns resulting version; stale revision: 409.","operationId":"reorder","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reorder"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}}},"/api/v1/products/{id}/green-zone":{"put":{"tags":["Green zone"],"summary":"Set product green-zone override","description":"Admin only. Requires both bounds with the same validation as the global zone.","operationId":"override","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GreenZone"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GreenZone"}}}}}},"delete":{"tags":["Green zone"],"summary":"Clear product override","description":"Admin only. The product resumes inheriting the global zone.","operationId":"clear","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"204":{"description":"No Content"}}}},"/api/v1/machines/{id}":{"get":{"tags":["Machines"],"operationId":"get_3","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"400":{"description":"Invalid input or paging","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Insufficient permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Machine not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Stale version or duplicate name","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}},"put":{"tags":["Machines"],"description":"Edit descriptive fields, even when disabled. Does not change enabled. Requires current body version.","operationId":"edit_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditMachineRequest"}}},"required":true},"responses":{"400":{"description":"Invalid input or paging","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Insufficient permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Machine not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Stale version or duplicate name","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/api/v1/ingredients/{id}":{"get":{"tags":["ingredient-controller"],"operationId":"get_4","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"401":{"description":"Token required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"403":{"description":"Permission denied","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"404":{"description":"Ingredient not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"409":{"description":"Duplicate name or stale version","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}}}},"put":{"tags":["ingredient-controller"],"operationId":"update_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Update"}}},"required":true},"responses":{"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"401":{"description":"Token required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"403":{"description":"Permission denied","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"404":{"description":"Ingredient not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"409":{"description":"Duplicate name or stale version","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}}}}},"/api/v1/ingredients/{id}/labelling":{"put":{"tags":["ingredient-controller"],"operationId":"labelling","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabellingRequest"}}},"required":true},"responses":{"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"401":{"description":"Token required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"403":{"description":"Permission denied","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"404":{"description":"Ingredient not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"409":{"description":"Duplicate name or stale version","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"422":{"description":"FRIDA provenance requires the link endpoint","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}}}}},"/api/v1/products":{"get":{"tags":["product-controller"],"operationId":"list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseProductSummary"}}}}}},"post":{"tags":["product-controller"],"operationId":"create","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProductSummary"}}}}}}},"/api/v1/products/{id}/recipe/steps":{"get":{"tags":["recipe-step-controller"],"operationId":"current_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecipeStepResponse"}}}}}}},"post":{"tags":["recipe-step-controller"],"summary":"Create a step","description":"Requires current recipe revision; forks if in use. Returns resulting version and new step IDs. Disabled machines: 422.","operationId":"create_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StepRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}}},"/api/v1/production-tasks":{"get":{"tags":["Production tasks"],"summary":"List production tasks, filtered by status, product and planned date","operationId":"list_1","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["PLANNED","IN_PROGRESS","DONE","CANCELLED"]}},{"name":"productId","in":"query","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"plannedFrom","in":"query","required":false,"schema":{"type":"string","format":"date"}},{"name":"plannedTo","in":"query","required":false,"schema":{"type":"string","format":"date"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseProductionTaskResponse"}}}}}},"post":{"tags":["Production tasks"],"summary":"Create a production task for a product, pinned to its current recipe version","operationId":"create_2","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductionTaskRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProductionTaskResponse"}}}}}}},"/api/v1/production-tasks/{id}/status":{"post":{"tags":["Production tasks"],"summary":"Change a task's status","description":"Allowed: PLANNED to IN_PROGRESS, IN_PROGRESS to DONE, and PLANNED or IN_PROGRESS to CANCELLED; anything else is refused with 422. Moving to DONE adds the units made to the product's stock. A stale version gives 409.","operationId":"changeStatus","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeStatusRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProductionTaskResponse"}}}}}}},"/api/v1/machines":{"get":{"tags":["Machines"],"description":"All machines by default, ordered by name then id. Filter with enabled.","operationId":"list_2","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}},{"name":"enabled","in":"query","required":false,"schema":{"type":"boolean"}}],"responses":{"400":{"description":"Invalid input or paging","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Insufficient permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Machine not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Stale version or duplicate name","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}},"post":{"tags":["Machines"],"operationId":"create_3","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMachineRequest"}}},"required":true},"responses":{"400":{"description":"Invalid input or paging","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Insufficient permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Machine not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Stale version or duplicate name","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"201":{"description":"Created enabled machine","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MachineView"}}}}}}},"/api/v1/machines/{id}/enable":{"post":{"tags":["Machines"],"description":"Enable. Current version required, including repeats; a current-version repeat preserves version and audit fields.","operationId":"enable","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MachineVersionRequest"}}},"required":true},"responses":{"400":{"description":"Invalid input or paging","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Insufficient permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Machine not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Stale version or duplicate name","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/api/v1/machines/{id}/disable":{"post":{"tags":["Machines"],"description":"Disable without deleting. Current version required, including repeats; a current-version repeat preserves version and audit fields.","operationId":"disable","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MachineVersionRequest"}}},"required":true},"responses":{"400":{"description":"Invalid input or paging","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Insufficient permission","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Machine not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Stale version or duplicate name","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/api/v1/ingredients":{"get":{"tags":["ingredient-controller"],"operationId":"list_3","parameters":[{"name":"enabled","in":"query","required":false,"schema":{"type":"boolean"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseIngredientResponse"}}}},"401":{"description":"Token required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseIngredientResponse"}}}},"403":{"description":"Permission denied","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseIngredientResponse"}}}},"404":{"description":"Ingredient not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseIngredientResponse"}}}},"409":{"description":"Duplicate name or stale version","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseIngredientResponse"}}}}}},"post":{"tags":["ingredient-controller"],"operationId":"create_4","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Create"}}},"required":true},"responses":{"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"401":{"description":"Token required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"403":{"description":"Permission denied","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"404":{"description":"Ingredient not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"409":{"description":"Duplicate name or stale version","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"201":{"description":"Ingredient created; Location contains its URL","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}}}}},"/api/v1/ingredients/{id}/enable":{"post":{"tags":["ingredient-controller"],"operationId":"enable_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"401":{"description":"Token required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"403":{"description":"Permission denied","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"404":{"description":"Ingredient not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"409":{"description":"Duplicate name or stale version","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}}}}},"/api/v1/ingredients/{id}/disable":{"post":{"tags":["ingredient-controller"],"operationId":"disable_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"401":{"description":"Token required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"403":{"description":"Permission denied","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"404":{"description":"Ingredient not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"409":{"description":"Duplicate name or stale version","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}}}}},"/api/v1/ingredients/{id}/composition-food":{"post":{"tags":["composition-link-controller"],"operationId":"link","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkRequest"}}},"required":true},"responses":{"200":{"description":"Linked ingredient","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"401":{"description":"Token required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"403":{"description":"Admin required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"404":{"description":"Ingredient or food not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"409":{"description":"Stale ingredient version","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}},"422":{"description":"Food violates ingredient nutrition rules","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IngredientResponse"}}}}}}},"/api/v1/products/{productId}/planning":{"get":{"tags":["planning-controller"],"operationId":"view","parameters":[{"name":"productId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/View"}}}}}}},"/api/v1/products/{productId}/planning/simulate":{"get":{"tags":["planning-controller"],"operationId":"simulate","parameters":[{"name":"productId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"multiplier","in":"query","required":true,"schema":{"type":"number"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/Simulation"}}}}}}},"/api/v1/products/{id}/stock-movements":{"get":{"tags":["Stock"],"summary":"Read stock movement history","description":"Newest first, with descending id as tie-break. Page is zero-based; size defaults to 20 and is at most 100.","operationId":"history","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseMovementResponse"}}}}}}},"/api/v1/products/{id}/recipe/versions":{"get":{"tags":["recipe-controller"],"operationId":"history_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecipeVersionSummary"}}}}}}}},"/api/v1/products/{id}/recipe/versions/{versionNumber}":{"get":{"tags":["recipe-controller"],"operationId":"version","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"versionNumber","in":"path","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecipeVersionResponse"}}}}}}},"/api/v1/products/{id}/recipe/versions/{versionNumber}/steps":{"get":{"tags":["recipe-step-controller"],"operationId":"version_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"versionNumber","in":"path","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecipeStepResponse"}}}}}}}},"/api/v1/products/{id}/recipe/versions/{versionNumber}/history":{"get":{"tags":["history-controller"],"summary":"Read recipe version changes, including lines and production steps","operationId":"version_2","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"versionNumber","in":"path","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseHistoryEntry"}}}}}}},"/api/v1/products/{id}/label":{"get":{"tags":["label-controller"],"summary":"Calculate a product label","description":"Defaults to the current recipe version. Historical versions use current ingredient labelling data. Known contributions are returned when nutrition is incomplete; check complete and each nutrient's complete flag. Raw and rounded nutrition are per 100 g of finished product. Intermediates use their current recipe composition recursively and appear as weighted compound ingredients with bracketed sublists. Unit weight never gates labelling; a non-blocking warning reports differences above 2%.","operationId":"label","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"recipeVersion","in":"query","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"sources","in":"query","description":"Comma-separated data sources; omitted includes all. Empty tokens are invalid.","required":false,"schema":{"type":"string","enum":["DEMO","MANUAL","SUPPLIER","ISGEM","FRIDA"]}}],"responses":{"200":{"description":"Calculated label","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LabelResponse"}}}},"400":{"description":"Invalid or empty source filter","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LabelResponse"}}}},"401":{"description":"Token required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LabelResponse"}}}},"403":{"description":"Read permission required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LabelResponse"}}}},"404":{"description":"Product, recipe or version not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LabelResponse"}}}},"422":{"description":"Finished batch weight is not positive","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LabelResponse"}}}}}}},"/api/v1/products/{id}/history":{"get":{"tags":["history-controller"],"summary":"Read product changes (recipe versions have separate history)","operationId":"product","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseHistoryEntry"}}}}}}},"/api/v1/production-tasks/{id}":{"get":{"tags":["Production tasks"],"summary":"Get a production task","operationId":"get_5","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProductionTaskResponse"}}}}}}},"/api/v1/production-tasks/{id}/weighing-list":{"get":{"tags":["Production tasks"],"summary":"Get scaled weighing amounts for a task's pinned recipe version","description":"Readable by admin and production manager. Mixable lines form one container with totalGrams; mixedContainer is null when empty. Separate lines remain individual. Both lists are ordered by lineOrder then line ID. Batch grams are reconstructed HALF_UP to 3 decimals, then multiplied without rounding. Each line uses kg at 1000 g or more, otherwise g.","operationId":"get_6","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/WeighingListResponse"}}}}}}},"/api/v1/production-tasks/{id}/history":{"get":{"tags":["history-controller"],"summary":"Read task changes, including status transitions","operationId":"task","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseHistoryEntry"}}}}}}},"/api/v1/ingredients/{id}/history":{"get":{"tags":["history-controller"],"summary":"Read an ingredient's changes","operationId":"ingredient","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseHistoryEntry"}}}}}}},"/api/v1/history":{"get":{"tags":["history-controller"],"summary":"Read the global change history","description":"Requires admin or history-read. user matches username exactly; from is inclusive and to exclusive.","operationId":"feed","parameters":[{"name":"entityType","in":"query","required":false,"schema":{"type":"string","enum":["INGREDIENT","PRODUCT","RECIPE_VERSION","PRODUCTION_TASK"]}},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":255,"minLength":0}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PageResponseHistoryEntry"}}}}}}},"/api/v1/composition-foods":{"get":{"tags":["composition-controller"],"operationId":"list_4","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"source","in":"query","required":false,"schema":{"type":"string","enum":["DEMO","MANUAL","SUPPLIER","ISGEM","FRIDA"]}},{"name":"datasetVersion","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/FoodPage"}}}}}}},"/api/v1/composition-foods/{id}":{"get":{"tags":["composition-controller"],"operationId":"get_7","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Composition food","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CompositionFood"}}}},"404":{"description":"Food not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CompositionFood"}}}}}}}},"components":{"schemas":{"ProblemDetail":{"type":"object","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer","format":"int32"},"detail":{"type":"string"},"instance":{"type":"string","format":"uri"},"properties":{"type":"object","additionalProperties":{}}}},"GreenZone":{"type":"object","properties":{"minWeeks":{"type":"number","exclusiveMinimum":0,"maximum":520},"maxWeeks":{"type":"number","exclusiveMinimum":0,"maximum":520}},"required":["maxWeeks","minWeeks"]},"ProductRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"minLength":0},"enabled":{"type":"boolean"},"version":{"type":"integer","format":"int64"},"kind":{"type":"string","description":"Admin-only. Kind changes require zero stock and no unfinished tasks. FINAL to INTERMEDIATE also requires no product green-zone override; history retains its original units. INTERMEDIATE to FINAL refuses current recipe references.","enum":["FINAL","INTERMEDIATE"]},"unitWeightGrams":{"type":"number","exclusiveMinimum":0}},"required":["name"]},"ProductSummary":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"enabled":{"type":"boolean"},"version":{"type":"integer","format":"int64"},"currentRecipeVersionNumber":{"type":"integer","format":"int32"},"stockUnits":{"type":"number","description":"Current stock: units for FINAL, grams for INTERMEDIATE; zero when absent"},"avgWeeklySalesUnits":{"type":"number","description":"Average weekly sales in units for FINAL; calculated recursive weekly usage in grams for INTERMEDIATE"},"weeksOfStock":{"type":["number","null"],"description":"Stock divided by average weekly sales; null for zero or missing sales"},"effectiveGreenZone":{"$ref":"#/components/schemas/GreenZone","description":"Product override, otherwise the global green zone, in weeks"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"updatedBy":{"type":"string"},"kind":{"type":"string","enum":["FINAL","INTERMEDIATE"]},"unitWeightGrams":{"type":"number"},"stockGrams":{"type":"number"},"weeklyUsageGrams":{"type":"number"}}},"SalesWrite":{"type":"object","properties":{"avgWeeklySalesUnits":{"type":"number","minimum":0},"version":{"type":"integer","format":"int64","minimum":0}},"required":["avgWeeklySalesUnits"]},"StockResponse":{"type":"object","properties":{"productId":{"type":"integer","format":"int64"},"stockUnits":{"type":"number"},"avgWeeklySalesUnits":{"type":"number"},"source":{"type":"string","enum":["MANUAL","PRODUCTION_USE","PRODUCTION_TASK","SYNC"]},"version":{"type":"integer","format":"int64"},"effectiveGreenZone":{"$ref":"#/components/schemas/GreenZone"},"kind":{"type":"string","enum":["FINAL","INTERMEDIATE"]},"stockGrams":{"type":"number"},"weeklyUsageGrams":{"type":"number"}}},"StockWrite":{"type":"object","properties":{"stockUnits":{"type":"number","description":"Absolute stock: units for FINAL products, grams for INTERMEDIATE products.","minimum":0},"version":{"type":"integer","format":"int64","minimum":0}},"required":["stockUnits"]},"Line":{"type":"object","properties":{"ingredientId":{"type":"integer","format":"int64"},"amount":{"type":"number","deprecated":true,"description":"Grams per ONE base unit, regardless of ingredient displayUnit; formerly batch amounts in the display unit. Prefer amountGramsPerUnit.","exclusiveMinimum":0},"amountGramsPerUnit":{"type":"number","description":"Grams per ONE base output unit."},"weighSeparately":{"type":"boolean"},"productId":{"type":"integer","format":"int64"}}},"RecipeRequest":{"type":"object","properties":{"revision":{"type":"integer","format":"int64"},"batchYieldUnits":{"type":"number","deprecated":true,"description":"Standard batch size in base output units for FINAL recipes only; formerly batch yield. Prefer batchSizeUnits.","exclusiveMinimum":0},"batchSizeUnits":{"type":"number","description":"Standard batch size in base output units."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/Line"},"maxItems":500,"minItems":0},"cookingLossPercent":{"type":"number","exclusiveMaximum":100,"minimum":0},"batchSizeGrams":{"type":"number","description":"INTERMEDIATE recipes only: standard batch output in grams. Must divide by unitWeightGrams exactly to at most 3 decimal places in base units; otherwise 400. Do not also supply batchSizeUnits/batchYieldUnits.","exclusiveMinimum":0}},"required":["lines"]},"RecipeLineResponse":{"type":"object","properties":{"position":{"type":"integer","format":"int32"},"ingredientId":{"type":"integer","format":"int64"},"ingredientName":{"type":"string"},"amount":{"type":"number","deprecated":true,"description":"Grams per one base unit; formerly batch amounts in the ingredient display unit. Prefer amountGramsPerUnit."},"unit":{"type":"string"},"amountGrams":{"type":"number","description":"Grams for the standard production batch, rounded HALF_UP to three decimals. Preserves the historical field meaning."},"weighSeparately":{"type":"boolean"},"stepId":{"type":"integer","format":"int64"},"offsetMinutes":{"type":"number"},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/Warning"}},"productId":{"type":"integer","format":"int64"},"productName":{"type":"string"},"amountGramsPerUnit":{"type":"number"}}},"RecipeStepResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"position":{"type":"integer","format":"int32"},"name":{"type":"string"},"machineId":{"type":"integer","format":"int64"},"machineName":{"type":"string"},"instructions":{"type":"string"},"durationMinutes":{"type":"number"},"setpoint":{"type":"string"},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/Warning"}},"lines":{"type":"array","items":{"$ref":"#/components/schemas/RecipeLineResponse"}}}},"RecipeVersionResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"versionNumber":{"type":"integer","format":"int32"},"current":{"type":"boolean"},"inUse":{"type":"boolean"},"batchYieldUnits":{"type":"number","deprecated":true,"description":"Standard batch size in base output units; prefer batchSizeUnits. For intermediates batchSizeGrams is the gram output."},"revision":{"type":"integer","format":"int64"},"hasWarnings":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"updatedBy":{"type":"string"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/RecipeLineResponse"}},"steps":{"type":"array","items":{"$ref":"#/components/schemas/RecipeStepResponse"}},"cookingLossPercent":{"type":"number"},"batchSizeGrams":{"type":"number"},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/Warning"}},"batchSizeUnits":{"type":"number"}}},"Warning":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}},"StepRequest":{"type":"object","properties":{"revision":{"type":"integer","format":"int64","minimum":0},"name":{"type":"string","maxLength":200,"minLength":0},"machineId":{"type":"integer","format":"int64"},"instructions":{"type":"string","maxLength":4000,"minLength":0},"durationMinutes":{"type":"number","exclusiveMinimum":0},"setpoint":{"type":"string","maxLength":500,"minLength":0}},"required":["name","revision"]},"Assignment":{"type":"object","properties":{"revision":{"type":"integer","format":"int64","minimum":0},"offsetMinutes":{"type":"number","minimum":0}},"required":["revision"]},"Reorder":{"type":"object","properties":{"revision":{"type":"integer","format":"int64","minimum":0},"stepIds":{"type":"array","items":{"type":"integer","format":"int64"}}},"required":["revision","stepIds"]},"EditMachineRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"minLength":0},"type":{"type":"string","maxLength":100,"minLength":0},"capacity":{"type":"number","exclusiveMinimum":0},"capacityUnit":{"type":"string","maxLength":20,"minLength":0},"notes":{"type":"string","maxLength":1000,"minLength":0},"version":{"type":"integer","format":"int64"}},"required":["name","type","version"]},"Update":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"minLength":0},"displayUnit":{"type":"string","deprecated":true,"description":"Ignored; weights are always grams.","enum":["g","kg"]},"version":{"type":"integer","format":"int64","minimum":0}},"required":["name","version"]},"IngredientResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"displayUnit":{"type":"string","deprecated":true,"description":"Legacy display preference; ignored for weight calculations.","enum":["g","kg"]},"enabled":{"type":"boolean"},"nutrition":{"$ref":"#/components/schemas/Nutrition"},"labelName":{"type":"string"},"subIngredients":{"type":"string"},"allergens":{"type":"object","additionalProperties":{"type":"string","enum":["CONTAINS","MAY_CONTAIN"]}},"nutritionSource":{"type":"string","enum":["DEMO","MANUAL","SUPPLIER","ISGEM","FRIDA"]},"nutritionRef":{"type":"string"},"nutritionRecordedAt":{"type":"string","format":"date-time"},"allergenSource":{"type":"string","enum":["DEMO","MANUAL","SUPPLIER","ISGEM","FRIDA"]},"allergenRef":{"type":"string"},"allergenRecordedAt":{"type":"string","format":"date-time"},"compositionFoodId":{"type":"integer","format":"int64"},"version":{"type":"integer","format":"int64"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"updatedBy":{"type":"string"}}},"Nutrition":{"type":"object","properties":{"energyKj":{"type":"number","minimum":0},"energyKcal":{"type":"number","minimum":0},"fat":{"type":"number","maximum":100,"minimum":0},"saturates":{"type":"number","maximum":100,"minimum":0},"carbohydrate":{"type":"number","maximum":100,"minimum":0},"sugars":{"type":"number","maximum":100,"minimum":0},"protein":{"type":"number","maximum":100,"minimum":0},"salt":{"type":"number","maximum":100,"minimum":0},"fibre":{"type":"number","maximum":100,"minimum":0},"consistent":{"type":"boolean"}}},"LabellingRequest":{"type":"object","properties":{"version":{"type":"integer","format":"int64","minimum":0},"nutrition":{"$ref":"#/components/schemas/Nutrition"},"labelName":{"type":"string","maxLength":200,"minLength":0},"subIngredients":{"type":"string","maxLength":4000,"minLength":0},"contains":{"type":"array","items":{"type":"string","enum":["GLUTEN","CRUSTACEANS","EGGS","FISH","PEANUTS","SOYBEANS","MILK","TREE_NUTS","CELERY","MUSTARD","SESAME","SULPHITES","LUPIN","MOLLUSCS"]}},"mayContain":{"type":"array","items":{"type":"string","enum":["GLUTEN","CRUSTACEANS","EGGS","FISH","PEANUTS","SOYBEANS","MILK","TREE_NUTS","CELERY","MUSTARD","SESAME","SULPHITES","LUPIN","MOLLUSCS"]}},"nutritionSource":{"type":"string","enum":["DEMO","MANUAL","SUPPLIER","ISGEM","FRIDA"]},"nutritionRef":{"type":"string","maxLength":500,"minLength":0},"nutritionRecordedAt":{"type":"string","format":"date-time"},"allergenSource":{"type":"string","enum":["DEMO","MANUAL","SUPPLIER","ISGEM","FRIDA"]},"allergenRef":{"type":"string","maxLength":500,"minLength":0},"allergenRecordedAt":{"type":"string","format":"date-time"},"validLabelling":{"type":"boolean"}},"required":["contains","mayContain","version"]},"CreateProductionTaskRequest":{"type":"object","properties":{"productId":{"type":"integer","format":"int64"},"multiplier":{"type":"number","description":"1 to 4 in steps of 0.25","example":2.5},"plannedDate":{"type":"string","format":"date","description":"Defaults to today (UTC)"},"notes":{"type":"string","maxLength":1000,"minLength":0}},"required":["multiplier","productId"]},"ProductionTaskResponse":{"type":"object","description":"A production task, pinned to the recipe version that was current when it was created.","properties":{"id":{"type":"integer","format":"int64"},"productId":{"type":"integer","format":"int64"},"productName":{"type":"string"},"recipeVersionId":{"type":"integer","format":"int64"},"recipeVersionNumber":{"type":"integer","format":"int32"},"multiplier":{"type":"number","description":"Batch multiplier, 1 to 4 in steps of 0.25"},"plannedUnits":{"type":"number","description":"Planned output: units for a FINAL task, grams for an INTERMEDIATE task. Kind is snapshotted at creation; historical quantities are not converted."},"actualUnits":{"type":["number","null"],"description":"Actual output in the task snapshot unit: units for FINAL, grams for INTERMEDIATE; set when done"},"status":{"type":"string","enum":["PLANNED","IN_PROGRESS","DONE","CANCELLED"]},"plannedDate":{"type":"string","format":"date"},"startedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"notes":{"type":"string"},"version":{"type":"integer","format":"int64","description":"Optimistic-locking version; send it back when changing the status"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"updatedBy":{"type":"string"},"outputKind":{"type":"string","enum":["FINAL","INTERMEDIATE"]},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/Warning"}},"quantityUnit":{"type":"string"},"plannedGrams":{"type":"number"},"actualGrams":{"type":"number"}}},"ChangeStatusRequest":{"type":"object","properties":{"status":{"type":"string","description":"The status to move to","enum":["PLANNED","IN_PROGRESS","DONE","CANCELLED"]},"version":{"type":"integer","format":"int64","description":"The task's current version, as last read; a stale version gives 409"},"actualUnits":{"type":["number","null"],"description":"Units actually made. Only allowed when moving to DONE; defaults to the planned units","minimum":0},"actualGrams":{"type":"number","description":"Actual grams made by an INTERMEDIATE task, only on DONE. Refused for FINAL tasks; actualUnits is refused for INTERMEDIATE tasks.","minimum":0}},"required":["status","version"]},"CreateMachineRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"minLength":0},"type":{"type":"string","maxLength":100,"minLength":0},"capacity":{"type":"number","exclusiveMinimum":0},"capacityUnit":{"type":"string","maxLength":20,"minLength":0},"notes":{"type":"string","maxLength":1000,"minLength":0}},"required":["name","type"]},"MachineView":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"type":{"type":"string"},"capacity":{"type":"number"},"capacityUnit":{"type":"string"},"notes":{"type":"string"},"enabled":{"type":"boolean"},"version":{"type":"integer","format":"int64"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"updatedBy":{"type":"string"}}},"MachineVersionRequest":{"type":"object","properties":{"version":{"type":"integer","format":"int64"}},"required":["version"]},"Create":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"minLength":0},"displayUnit":{"type":"string","deprecated":true,"description":"Ignored; weights are always grams.","enum":["g","kg"]}},"required":["name"]},"LinkRequest":{"type":"object","properties":{"foodId":{"type":"integer","format":"int64","exclusiveMinimum":0},"version":{"type":"integer","format":"int64","minimum":0}},"required":["foodId","version"]},"PageResponseProductSummary":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProductSummary"}},"page":{"type":"integer","format":"int32"},"size":{"type":"integer","format":"int32"},"totalElements":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int32"}}},"Recommendation":{"type":"object","properties":{"multiplier":{"type":["number","null"],"description":"Multiplier from 1 to 4 in 0.25 steps closest to the effective green-zone midpoint; ties choose the smaller value. Null for zero sales, missing yield, or when 4x falls below the lower bound."},"fourTimesFallsShort":{"type":"boolean"},"oneTimesExceedsUpperBound":{"type":"boolean"}}},"View":{"type":"object","description":"FINAL quantities are output units; INTERMEDIATE stock, computed weekly usage, recipe yield and suggestions are grams and use the global green zone.","properties":{"productId":{"type":"integer","format":"int64"},"stock":{"type":"number"},"averageWeeklySales":{"type":"number"},"weeksOfStock":{"type":"number"},"effectiveGreenZone":{"$ref":"#/components/schemas/Zone"},"status":{"type":"string"},"recipeYield":{"type":"number"},"recommendation":{"$ref":"#/components/schemas/Recommendation"},"targetWeeks":{"type":"number","description":"Midpoint of the effective green zone in weeks."},"suggestedUnits":{"type":["number","null"],"description":"Recipe yield times the suggested multiplier; null without a suggestion."},"suggestedResultingStock":{"type":["number","null"],"description":"Current stock plus suggested units; null without a suggestion."},"suggestedResultingWeeks":{"type":["number","null"],"description":"Suggested resulting stock divided by weekly sales; recurring divisions rounded to 12 decimal places HALF_UP for display only. Null without a suggestion."}}},"Zone":{"type":"object","properties":{"minWeeks":{"type":"number"},"maxWeeks":{"type":"number"}}},"Simulation":{"type":"object","properties":{"productId":{"type":"integer","format":"int64"},"multiplier":{"type":"number"},"resultingStock":{"type":"number"},"resultingWeeksOfStock":{"type":"number"}}},"MovementResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"source":{"type":"string","enum":["MANUAL","PRODUCTION_USE","PRODUCTION_TASK","SYNC"]},"delta":{"type":"number"},"resultingStock":{"type":"number"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"quantityUnit":{"type":"string","enum":["UNITS","GRAMS"]}}},"PageResponseMovementResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/MovementResponse"}},"page":{"type":"integer","format":"int32"},"size":{"type":"integer","format":"int32"},"totalElements":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int32"}}},"RecipeVersionSummary":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"versionNumber":{"type":"integer","format":"int32"},"current":{"type":"boolean"},"inUse":{"type":"boolean"},"batchYieldUnits":{"type":"number"},"hasWarnings":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"updatedBy":{"type":"string"},"batchSizeGrams":{"type":"number"},"batchSizeUnits":{"type":"number"}}},"HistoryEntry":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"entityType":{"type":"string","enum":["INGREDIENT","PRODUCT","RECIPE_VERSION","PRODUCTION_TASK"]},"entityId":{"type":"integer","format":"int64"},"action":{"type":"string"},"changedBySubject":{"type":"string"},"changedByUsername":{"type":"string"},"changedAt":{"type":"string","format":"date-time"},"changes":{"type":"object","additionalProperties":true,"description":"Business field changes as before/after JSON objects; the absent side is null for create/delete."}}},"PageResponseHistoryEntry":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/HistoryEntry"}},"page":{"type":"integer","format":"int32"},"size":{"type":"integer","format":"int32"},"totalElements":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int32"}}},"Entry":{"type":"object","properties":{"ingredientId":{"type":"integer","format":"int64"},"rawWeightGrams":{"type":"number"},"segments":{"type":"array","items":{"$ref":"#/components/schemas/Segment"}},"contains":{"type":"array","items":{"type":"string","enum":["GLUTEN","CRUSTACEANS","EGGS","FISH","PEANUTS","SOYBEANS","MILK","TREE_NUTS","CELERY","MUSTARD","SESAME","SULPHITES","LUPIN","MOLLUSCS"]},"uniqueItems":true},"mayContain":{"type":"array","items":{"type":"string","enum":["GLUTEN","CRUSTACEANS","EGGS","FISH","PEANUTS","SOYBEANS","MILK","TREE_NUTS","CELERY","MUSTARD","SESAME","SULPHITES","LUPIN","MOLLUSCS"]},"uniqueItems":true},"missingNutrients":{"type":"array","items":{"type":"string","enum":["ENERGY_KJ","ENERGY_KCAL","FAT","SATURATES","CARBOHYDRATE","SUGARS","PROTEIN","SALT","FIBRE"]}},"productId":{"type":"integer","format":"int64"},"ingredients":{"type":"array","items":{"$ref":"#/components/schemas/Entry"}}}},"LabelResponse":{"type":"object","properties":{"recipeVersion":{"type":"integer","format":"int32"},"finishedBatchWeightGrams":{"type":"number"},"ingredients":{"type":"array","items":{"$ref":"#/components/schemas/Entry"}},"ingredientList":{"type":"array","items":{"$ref":"#/components/schemas/Segment"}},"contains":{"type":"array","items":{"type":"string","enum":["GLUTEN","CRUSTACEANS","EGGS","FISH","PEANUTS","SOYBEANS","MILK","TREE_NUTS","CELERY","MUSTARD","SESAME","SULPHITES","LUPIN","MOLLUSCS"]},"uniqueItems":true},"mayContain":{"type":"array","items":{"type":"string","enum":["GLUTEN","CRUSTACEANS","EGGS","FISH","PEANUTS","SOYBEANS","MILK","TREE_NUTS","CELERY","MUSTARD","SESAME","SULPHITES","LUPIN","MOLLUSCS"]},"uniqueItems":true},"nutritionPer100g":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/Value"}},"complete":{"type":"boolean"},"sourcesUsed":{"type":"array","items":{"type":"string","enum":["DEMO","MANUAL","SUPPLIER","ISGEM","FRIDA"]},"uniqueItems":true},"demoData":{"type":"boolean"},"attributions":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/Warning"}}}},"Rounded":{"type":"object","properties":{"value":{"type":"number"},"lessThan":{"type":"boolean"},"unit":{"type":"string"}}},"Segment":{"type":"object","properties":{"text":{"type":"string"},"emphasis":{"type":"boolean"}}},"Value":{"type":"object","properties":{"raw":{"type":"number"},"rounded":{"$ref":"#/components/schemas/Rounded"},"complete":{"type":"boolean"}}},"PageResponseProductionTaskResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProductionTaskResponse"}},"page":{"type":"integer","format":"int32"},"size":{"type":"integer","format":"int32"},"totalElements":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int32"}}},"Container":{"type":"object","properties":{"totalGrams":{"type":"number"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/Line"}}}},"WeighingListResponse":{"type":"object","properties":{"taskId":{"type":"integer","format":"int64"},"recipeVersionId":{"type":"integer","format":"int64"},"multiplier":{"type":"number"},"mixedContainer":{"$ref":"#/components/schemas/Container"},"separateLines":{"type":"array","items":{"$ref":"#/components/schemas/Line"}}}},"PageResponseIngredientResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/IngredientResponse"}},"page":{"type":"integer","format":"int32"},"size":{"type":"integer","format":"int32"},"totalElements":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int32"}}},"CompositionFood":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"source":{"type":"string","enum":["DEMO","MANUAL","SUPPLIER","ISGEM","FRIDA"]},"datasetVersion":{"type":"string"},"foodCode":{"type":"string"},"nameDa":{"type":"string"},"nameEn":{"type":"string"},"foodGroup":{"type":"string"},"energyKj":{"type":"number"},"energyKcal":{"type":"number"},"fat":{"type":"number"},"saturates":{"type":"number"},"carbohydrate":{"type":"number"},"sugars":{"type":"number"},"fibre":{"type":"number"},"protein":{"type":"number"},"sodiumMg":{"type":"number"},"importedAt":{"type":"string","format":"date-time"},"attributions":{"type":"array","items":{"type":"string"}}}},"FoodPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CompositionFood"}},"page":{"type":"integer","format":"int32"},"size":{"type":"integer","format":"int32"},"totalElements":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int32"},"attributions":{"type":"array","items":{"type":"string"}}}},"Revision":{"type":"object","properties":{"revision":{"type":"integer","format":"int64","minimum":0}},"required":["revision"]}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}}}