openapi: 3.0.3
info:
  title: ACMEsuite — All APIs
  version: 0.2.0
  description: >
    HR system of **ACME analog Inc.** and system of record for employees
    (title/role/hierarchy), vacation/absence planning, deputies & assistants,
    sick days, compensation as well as **powers of attorney** and **approval
    limits**.


    This data feeds the approval/e-approval logic (which person may sign off on
    which amount, who deputizes during an absence) and consumers (who is
    available/on vacation/sick on day N). It evolves the existing `org` domain
    model (Person · OrgUnit · PowerOfAttorney · Absence · Role). The read-only
    `/api/org` browsing (people/units/hierarchy) continues to exist alongside it
    and is the source of people for consumers.


    Roles (ACMEbase): `WATCH` reads, `WORK` writes operationally, `ADMIN`
    maintains master data. Per operation under `x-required-role`; hierarchy
    ADMIN > WORK > WATCH.
  contact:
    name: ACME analog Inc · ACMEsuite
  license:
    name: Apache-2.0
    url: 'https://www.apache.org/licenses/LICENSE-2.0'
servers:
  - url: 'https://demo.acmesoftware.de/api/hr'
    description: ACMEsuite API (TLS at the edge)
security:
  - bearerAuth: []
tags:
  - name: Employees
    description: 'Employees (hired) — title, role, hierarchy, deputy/assistant.'
  - name: Applicants
    description: Applicants — candidates before being hired (applicant=true).
  - name: Compensation
    description: Compensation (salary/hourly wage) and personnel costs.
  - name: Roles
    description: Role catalog and role assignments.
  - name: Absences
    description: Vacation planning and sick days (absences) with deputy.
  - name: PowersOfAttorney
    description: Powers of attorney (Prokura/Handlungsvollmacht) per legal entity.
  - name: ApprovalLimits
    description: Approval limits — who may sign off up to which amount.
  - name: Customers
    description: Customers and resellers (sales partners).
  - name: Products
    description: Product catalog (analog goods from the ACME factory).
  - name: PriceLists
    description: Price lists (list/reseller) with tiered prices + price resolution.
  - name: Quotes
    description: 'Quotes — draft through acceptance, conversion into an order.'
  - name: Orders
    description: >-
      Orders incl. e-approval (submission, approval/rejection, production,
      fulfillment).
  - name: Contacts
    description: People at a customer (company). A contact may be linked to one customer.
  - name: Pipeline
    description: >
      Sales pipeline overlay — a stage projection over leads/quotes/orders for
      the CRM board, table and funnel. Read-mostly; stage is settable for
      drag-drop / inline edit.
  - name: Mail
    description: >
      Mail threads (correspondence) attached to a customer/contact. UI overlay —
      a real mail-sync backend is future work (currently mocked).
  - name: Forms
    description: >
      Web forms / newsletter signups. Layout and trigger actions are configured
      in Admin; `POST /forms/{id}/submit` is the public submission tool
      (validate → store → trigger).
  - name: Suppliers
    description: Suppliers.
  - name: Materials
    description: Raw materials and energy.
  - name: SupplyContracts
    description: 'Supply contracts — tiered prices + lead times, price resolution.'
  - name: SupplyOrders
    description: >-
      Procurements incl. approval (submission, approval/rejection, goods
      receipt).
  - name: Stock
    description: 'Material stock — read, consumption (production), reset.'
  - name: BOM
    description: Products and their bills of materials.
  - name: Planning
    description: Production capacity and material demand projection.
  - name: Feasibility
    description: Feasibility of a contract against capacity and material availability.
  - name: Shopfloor
    description: >
      Execution layer — production orders (planning board), the weekly shift
      plan and the live machine monitor (OEE). Backs the ACMEbuild "Fertigung"
      views.
paths:
  /hr/employees:
    get:
      tags:
        - Employees
      summary: List employees
      description: Employees (hired). Applicants are not included — see `/applicants`.
      operationId: listEmployees
      x-required-role: WATCH
      parameters:
        - name: active
          in: query
          schema:
            type: boolean
          description: Active/inactive only.
        - name: unitId
          in: query
          schema:
            type: string
          description: Filter by primary org unit.
        - name: q
          in: query
          schema:
            type: string
          description: Free text (name/email/title).
      responses:
        '200':
          description: List of employees.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Employee'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Employees
      summary: Create employee
      description: 'Creates a hired employee directly (active=true, applicant=false).'
      operationId: createEmployee
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmployeeCreate'
      responses:
        '201':
          description: Created employee.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Employee'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/hr/employees/{id}':
    parameters:
      - $ref: '#/components/parameters/PersonId'
    get:
      tags:
        - Employees
      summary: Read employee
      operationId: getEmployee
      x-required-role: WATCH
      responses:
        '200':
          description: Employee.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Employee'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Employees
      summary: 'Maintain overlay (title, manager, deputy, assistant, active)'
      operationId: updateEmployee
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmployeeUpdate'
      responses:
        '200':
          description: Updated employee.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Employee'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/hr/employees/{id}/compensation':
    parameters:
      - $ref: '#/components/parameters/PersonId'
    patch:
      tags:
        - Compensation
      summary: Set compensation (salary/hourly wage)
      operationId: updateCompensation
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompensationUpdate'
      responses:
        '200':
          description: Updated employee.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Employee'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/hr/employees/{id}/roles':
    parameters:
      - $ref: '#/components/parameters/PersonId'
    get:
      tags:
        - Roles
      summary: Role assignments of the person
      operationId: listEmployeeRoles
      x-required-role: WATCH
      responses:
        '200':
          description: Role assignments.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RoleAssignment'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Roles
      summary: Assign role
      operationId: assignRole
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleAssignmentCreate'
      responses:
        '201':
          description: Created assignment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleAssignment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/hr/employees/{id}/roles/{assignmentId}':
    parameters:
      - $ref: '#/components/parameters/PersonId'
      - name: assignmentId
        in: path
        required: true
        schema:
          type: string
    delete:
      tags:
        - Roles
      summary: Remove role assignment
      operationId: removeRole
      x-required-role: WORK
      responses:
        '204':
          description: Removed.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/hr/employees/{id}/absences':
    parameters:
      - $ref: '#/components/parameters/PersonId'
    get:
      tags:
        - Absences
      summary: Absences of the person (vacation/sick)
      operationId: listEmployeeAbsences
      x-required-role: WATCH
      responses:
        '200':
          description: Absences.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Absence'
        '401':
          $ref: '#/components/responses/Unauthorized'
  '/hr/employees/{id}/powers-of-attorney':
    parameters:
      - $ref: '#/components/parameters/PersonId'
    get:
      tags:
        - PowersOfAttorney
      summary: Powers of attorney of the person
      operationId: listEmployeePowers
      x-required-role: WATCH
      responses:
        '200':
          description: Powers of attorney.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PowerOfAttorney'
        '401':
          $ref: '#/components/responses/Unauthorized'
  '/hr/employees/{id}/approval-limit':
    parameters:
      - $ref: '#/components/parameters/PersonId'
    get:
      tags:
        - ApprovalLimits
      summary: Effective approval limit of the person on a given date
      description: >
        Returns the approval limit effective on the given date (resolved from
        active powers of attorney or an explicitly maintained limit). Does NOT
        account for deputies — that is handled by e-approval resolution.
      operationId: getEmployeeApprovalLimit
      x-required-role: WATCH
      parameters:
        - $ref: '#/components/parameters/AsOf'
        - name: legalEntityId
          in: query
          schema:
            type: string
          description: Restrict to one legal entity.
      responses:
        '200':
          description: Effective approval limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApprovalLimit'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /hr/applicants:
    get:
      tags:
        - Applicants
      summary: List applicants
      operationId: listApplicants
      x-required-role: WATCH
      parameters:
        - name: unitId
          in: query
          schema:
            type: string
          description: Target org unit.
        - name: q
          in: query
          schema:
            type: string
          description: Free text (name/title).
      responses:
        '200':
          description: Applicants.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Applicant'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Applicants
      summary: Create applicant
      operationId: createApplicant
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApplicantWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Applicant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/hr/applicants/{id}':
    parameters:
      - $ref: '#/components/parameters/PersonId'
    get:
      tags:
        - Applicants
      summary: Read applicant
      operationId: getApplicant
      x-required-role: WATCH
      responses:
        '200':
          description: Applicant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Applicant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Applicants
      summary: Move applicant along the recruiting pipeline
      description: Sets the recruiting `stage` (Bewerber board drag & drop).
      operationId: updateApplicant
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApplicantStageUpdate'
      responses:
        '200':
          description: Updated applicant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Applicant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
    delete:
      tags:
        - Applicants
      summary: Reject/remove application
      operationId: rejectApplicant
      x-required-role: WORK
      responses:
        '204':
          description: Removed.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/hr/applicants/{id}/hire':
    parameters:
      - $ref: '#/components/parameters/PersonId'
    post:
      tags:
        - Applicants
      summary: Hire applicant
      description: >
        Starts hiring the applicant: opens a hiring folder for approval (HR
        officer → HR management → managing director). On the concluding approval
        the applicant becomes an employee (applicant=false, active=true); a
        rejection imposes a re-hire block.
      operationId: hireApplicant
      x-required-role: WORK
      parameters:
        - name: day
          in: query
          schema:
            type: integer
            default: 0
          description: Day of hiring.
      responses:
        '200':
          description: Hired employee.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Employee'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /hr/payroll:
    get:
      tags:
        - Compensation
      summary: Personnel cost overview (weekly)
      operationId: getPayroll
      x-required-role: WATCH
      responses:
        '200':
          description: Payroll summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayrollSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /hr/roles:
    get:
      tags:
        - Roles
      summary: Role catalog
      operationId: listRoles
      x-required-role: WATCH
      responses:
        '200':
          description: Roles.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Role'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /hr/absences:
    get:
      tags:
        - Absences
      summary: 'Search absences (vacation planning, sick days)'
      operationId: listAbsences
      x-required-role: WATCH
      parameters:
        - name: personId
          in: query
          schema:
            type: string
        - name: type
          in: query
          schema:
            $ref: '#/components/schemas/AbsenceType'
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/AbsenceStatus'
        - name: from
          in: query
          schema:
            type: string
            format: date
          description: Overlap from this day on.
        - name: until
          in: query
          schema:
            type: string
            format: date
          description: Overlap up to this day.
      responses:
        '200':
          description: Matches.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Absence'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Absences
      summary: Create absence (plan vacation / report sick)
      description: >
        Creates an absence. `VACATION` starts as `PLANNED` (approval follows),
        `SICK` is usually recorded directly as `APPROVED` (reported).
      operationId: createAbsence
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AbsenceCreate'
            examples:
              urlaub:
                summary: Plan vacation
                value:
                  personId: p-julia-jefa
                  type: VACATION
                  from: '2026-07-20'
                  until: '2026-08-02'
                  substituteId: p-dirk-direktor
                  note: Summer vacation
              krank:
                summary: Report sick day
                value:
                  personId: p-dirk-direktor
                  type: SICK
                  from: '2026-06-24'
                  until: '2026-06-26'
      responses:
        '201':
          description: Created absence.
          headers:
            Location:
              schema:
                type: string
              description: URI of the new resource.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Absence'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/hr/absences/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Absences
      summary: Read absence
      operationId: getAbsence
      x-required-role: WATCH
      responses:
        '200':
          description: Absence.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Absence'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Absences
      summary: 'Change absence (approve/reject/cancel, deputy, period)'
      operationId: updateAbsence
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AbsenceUpdate'
      responses:
        '200':
          description: Changed absence.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Absence'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
    delete:
      tags:
        - Absences
      summary: Delete absence
      operationId: deleteAbsence
      x-required-role: WORK
      responses:
        '204':
          description: Deleted.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /hr/powers-of-attorney:
    get:
      tags:
        - PowersOfAttorney
      summary: Search powers of attorney
      operationId: listPowers
      x-required-role: WATCH
      parameters:
        - name: holderId
          in: query
          schema:
            type: string
        - name: legalEntityId
          in: query
          schema:
            type: string
        - name: includeRevoked
          in: query
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Powers of attorney.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PowerOfAttorney'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - PowersOfAttorney
      summary: Grant power of attorney
      operationId: grantPower
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PowerOfAttorneyGrant'
      responses:
        '201':
          description: Granted power of attorney.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PowerOfAttorney'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/hr/powers-of-attorney/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - PowersOfAttorney
      summary: Read power of attorney
      operationId: getPower
      x-required-role: WATCH
      responses:
        '200':
          description: Power of attorney.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PowerOfAttorney'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/hr/powers-of-attorney/{id}/revocation':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    put:
      tags:
        - PowersOfAttorney
      summary: Revoke power of attorney
      operationId: revokePower
      x-required-role: WORK
      responses:
        '200':
          description: Revoked power of attorney.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PowerOfAttorney'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /hr/approval-limits:
    get:
      tags:
        - ApprovalLimits
      summary: List approval limits
      operationId: listApprovalLimits
      x-required-role: WATCH
      parameters:
        - name: personId
          in: query
          schema:
            type: string
        - $ref: '#/components/parameters/AsOf'
      responses:
        '200':
          description: Approval limits.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ApprovalLimit'
        '401':
          $ref: '#/components/responses/Unauthorized'
    put:
      tags:
        - ApprovalLimits
      summary: Set explicit approval limit
      description: >-
        Sets an explicitly maintained approval limit (overrides the one derived
        from powers of attorney).
      operationId: setApprovalLimit
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovalLimitSet'
      responses:
        '200':
          description: Set approval limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApprovalLimit'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/hr/legal-entities/{legalEntityId}/signatories':
    parameters:
      - name: legalEntityId
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - ApprovalLimits
      summary: Authorized signatories for an amount on a given date
      description: >-
        Who may sign off on the given amount for this legal entity on the given
        date (from active powers of attorney).
      operationId: listSignatories
      x-required-role: WATCH
      parameters:
        - name: amount
          in: query
          required: true
          schema:
            type: number
        - name: currency
          in: query
          schema:
            type: string
            default: EUR
            minLength: 3
            maxLength: 3
        - $ref: '#/components/parameters/AsOf'
      responses:
        '200':
          description: Authorized signatories.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PowerOfAttorney'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /crm/customers:
    get:
      tags:
        - Customers
      summary: List customers/resellers
      operationId: listCustomers
      x-required-role: WATCH
      parameters:
        - name: kind
          in: query
          schema:
            $ref: '#/components/schemas/CustomerKind'
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/CustomerStatus'
        - name: resellerId
          in: query
          schema:
            type: string
          description: Customers of a reseller.
        - name: q
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Matches.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Customers
      summary: Create customer/reseller
      operationId: createCustomer
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/customers/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Customers
      summary: Read customer
      operationId: getCustomer
      x-required-role: WATCH
      responses:
        '200':
          description: Customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Customers
      summary: Update customer
      operationId: updateCustomer
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerWrite'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /crm/products:
    get:
      tags:
        - Products
      summary: List products
      operationId: listProducts
      x-required-role: WATCH
      parameters:
        - name: category
          in: query
          schema:
            type: string
        - name: active
          in: query
          schema:
            type: boolean
        - name: q
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Products.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Products
      summary: Create product (master data)
      operationId: createProduct
      x-required-role: ADMIN
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/products/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Products
      summary: Read product
      operationId: getProduct
      x-required-role: WATCH
      responses:
        '200':
          description: Product.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Products
      summary: Update product (master data)
      operationId: updateProduct
      x-required-role: ADMIN
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductWrite'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /crm/price-lists:
    get:
      tags:
        - PriceLists
      summary: List price lists
      operationId: listPriceLists
      x-required-role: WATCH
      parameters:
        - name: kind
          in: query
          schema:
            $ref: '#/components/schemas/PriceListKind'
      responses:
        '200':
          description: Price lists.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PriceList'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - PriceLists
      summary: Create price list (master data)
      operationId: createPriceList
      x-required-role: ADMIN
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PriceListWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PriceList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/price-lists/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - PriceLists
      summary: Read price list
      operationId: getPriceList
      x-required-role: WATCH
      responses:
        '200':
          description: Price list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PriceList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /crm/price:
    get:
      tags:
        - PriceLists
      summary: Resolve effective unit price
      description: >-
        Returns the unit price applicable for customer + product + quantity
        (incl. tier/reseller).
      operationId: resolvePrice
      x-required-role: WATCH
      parameters:
        - name: customerId
          in: query
          required: true
          schema:
            type: string
        - name: productId
          in: query
          required: true
          schema:
            type: string
        - name: quantity
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
      responses:
        '200':
          description: Price.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolvedPrice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /crm/quotes:
    get:
      tags:
        - Quotes
      summary: List quotes
      operationId: listQuotes
      x-required-role: WATCH
      parameters:
        - name: customerId
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/QuoteStatus'
      responses:
        '200':
          description: Quotes.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Quote'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Quotes
      summary: Create quote
      description: Unit prices are resolved from the customer's price list if not provided.
      operationId: createQuote
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/quotes/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Quotes
      summary: Read quote
      operationId: getQuote
      x-required-role: WATCH
      responses:
        '200':
          description: Quote.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Quotes
      summary: Update quote (status/validity)
      operationId: updateQuote
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteUpdate'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/crm/quotes/{id}/order':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Quotes
      summary: Convert quote into order
      description: Marks the quote as accepted and creates an order from it.
      operationId: convertQuoteToOrder
      x-required-role: WORK
      responses:
        '201':
          description: Order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /crm/orders:
    get:
      tags:
        - Orders
      summary: List orders
      description: >-
        Consumers read the approved orders via `status=APPROVED` for the
        production cycle.
      operationId: listOrders
      x-required-role: WATCH
      parameters:
        - name: customerId
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/OrderStatus'
      responses:
        '200':
          description: Orders.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Orders
      summary: Create order
      operationId: createOrder
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
    delete:
      tags:
        - Orders
      summary: Delete all orders (data reset)
      description: >
        Destructive reset of the order stock — intended only for the
        data/economy reset, hence `ADMIN`. Not for normal operation.
      operationId: deleteAllOrders
      x-required-role: ADMIN
      responses:
        '204':
          description: Stock cleared.
        '401':
          $ref: '#/components/responses/Unauthorized'
  '/crm/orders/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Orders
      summary: Read order
      operationId: getOrder
      x-required-role: WATCH
      responses:
        '200':
          description: Order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/crm/orders/{id}/submission':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Orders
      summary: Submit order for approval (e-approval)
      description: >-
        Sets the status to PENDING_APPROVAL or — below the threshold — directly
        to APPROVED.
      operationId: submitOrder
      x-required-role: WORK
      responses:
        '200':
          description: Submitted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/orders/{id}/decision':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Orders
      summary: Approval decision (e-approval)
      operationId: decideOrder
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovalDecision'
      responses:
        '200':
          description: Decided.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/orders/{id}/production':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Orders
      summary: Record produced quantities (partial fulfillment)
      description: >
        Reports the quantities produced on a day per line; reduces
        `remainingQuantity`. The production cycle calls this after
        manufacturing. Once the order is thereby complete, it moves to
        FULFILLED.
      operationId: recordProduction
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductionReport'
      responses:
        '200':
          description: Updated order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/orders/{id}/fulfillment':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Orders
      summary: Mark order as fully fulfilled
      operationId: fulfillOrder
      x-required-role: WORK
      responses:
        '200':
          description: Fulfilled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/orders/{id}/cancellation':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Orders
      summary: Cancel order
      operationId: cancelOrder
      x-required-role: WORK
      responses:
        '200':
          description: Cancelled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /crm/pipeline:
    get:
      tags:
        - Pipeline
      summary: List pipeline deals
      description: >
        The sales pipeline as a list of `Deal`s, each projected onto one of five
        stages.


        **Status → stage derivation** (for deals backed by a quote/order): quote
        `DRAFT` → `NEU`; quote `SENT` → `QUALIFIZIERT`; quote `ACCEPTED` or
        order `CREATED` → `ANGEBOT`; order `PENDING_APPROVAL` → `VERHANDLUNG`;
        order `APPROVED` or `FULFILLED` → `GEWONNEN`. Lost deals (quote
        `REJECTED`/`EXPIRED`, order `REJECTED`/`CANCELLED`) are excluded by
        default. Bare leads (no quote yet) sit in the stage last set on them
        (default `NEU`).
      operationId: listPipeline
      x-required-role: WATCH
      parameters:
        - name: stage
          in: query
          schema:
            $ref: '#/components/schemas/PipelineStage'
        - name: owner
          in: query
          schema:
            type: string
          description: Filter by owner initials.
        - name: customerId
          in: query
          schema:
            type: string
          description: Deals of a customer.
        - name: contactId
          in: query
          schema:
            type: string
          description: Deals with a contact.
        - name: q
          in: query
          schema:
            type: string
          description: Free-text over company/contact.
      responses:
        '200':
          description: Deals.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Deal'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Pipeline
      summary: Create a pipeline deal (lead)
      description: >
        Adds a new deal to the pipeline. Without a `customerId`/backing quote it
        is a bare lead (enters `NEU` by default); the sales team later attaches
        a quote/order as it progresses.
      operationId: createDeal
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DealCreate'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Deal'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/pipeline/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Pipeline
      summary: Read a pipeline deal
      operationId: getDeal
      x-required-role: WATCH
      responses:
        '200':
          description: Deal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Deal'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Pipeline
      summary: Update a pipeline deal (stage / owner / value / contact)
      description: >
        Sets sales-overlay attributes. Moving `stage` reflects the board
        drag-drop / inline edit; where a deal is backed by a quote/order the
        server advances the underlying record along its legal transition,
        otherwise it records the stage on the overlay.
      operationId: updateDeal
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DealUpdate'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Deal'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /crm/contacts:
    get:
      tags:
        - Contacts
      summary: List contacts
      operationId: listContacts
      x-required-role: WATCH
      parameters:
        - name: customerId
          in: query
          schema:
            type: string
          description: Contacts of a customer.
        - name: q
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Contacts.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Contact'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Contacts
      summary: Create contact
      operationId: createContact
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/contacts/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Contacts
      summary: Read contact
      operationId: getContact
      x-required-role: WATCH
      responses:
        '200':
          description: Contact.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Contacts
      summary: Update contact
      operationId: updateContact
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactWrite'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /crm/threads:
    get:
      tags:
        - Mail
      summary: List mail threads
      description: Correspondence attached to a customer and/or contact. Filter by either.
      operationId: listThreads
      x-required-role: WATCH
      parameters:
        - name: customerId
          in: query
          schema:
            type: string
        - name: contactId
          in: query
          schema:
            type: string
        - name: q
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Threads (newest first).
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MailThread'
        '401':
          $ref: '#/components/responses/Unauthorized'
  '/crm/threads/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Mail
      summary: Read a mail thread (with messages)
      operationId: getThread
      x-required-role: WATCH
      responses:
        '200':
          description: Thread.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MailThread'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /crm/forms:
    get:
      tags:
        - Forms
      summary: List web forms
      description: >-
        Configured web forms / newsletter signups (definition maintained in
        Admin).
      operationId: listForms
      x-required-role: WATCH
      responses:
        '200':
          description: Forms.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WebForm'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Forms
      summary: Create a web form (Admin)
      operationId: createForm
      x-required-role: ADMIN
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebFormWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebForm'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/forms/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Forms
      summary: Read a web form (definition)
      operationId: getForm
      x-required-role: WATCH
      responses:
        '200':
          description: Form.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebForm'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Forms
      summary: 'Update a web form (Admin — fields, required flags, trigger actions)'
      operationId: updateForm
      x-required-role: ADMIN
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebFormWrite'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebForm'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/crm/forms/{id}/submit':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Forms
      summary: Submit a web form (public)
      description: >
        Public submission tool for the embedded web form / newsletter signup —
        **no auth** (overrides the global bearer security). Validates the
        submitted values against the form's required fields (`422` listing the
        offending fields on failure); on success stores the submission and runs
        the configured trigger actions (send email, create contact/opportunity,
        schedule appointment, newsletter subscribe/unsubscribe). Returns the
        stored submission incl. which actions were triggered.
      operationId: submitForm
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FormSubmitRequest'
      responses:
        '201':
          description: Accepted & stored.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FormSubmission'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/crm/forms/{id}/submissions':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Forms
      summary: List captured submissions of a form
      operationId: listFormSubmissions
      x-required-role: WORK
      responses:
        '200':
          description: Submissions (newest first).
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FormSubmission'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /supply/suppliers:
    get:
      tags:
        - Suppliers
      summary: List suppliers
      operationId: listSuppliers
      x-required-role: WATCH
      parameters:
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/SupplierStatus'
        - name: q
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Matches.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Supplier'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Suppliers
      summary: Create supplier
      operationId: createSupplier
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupplierWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Supplier'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/supply/suppliers/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Suppliers
      summary: Read supplier
      operationId: getSupplier
      x-required-role: WATCH
      responses:
        '200':
          description: Supplier.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Supplier'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Suppliers
      summary: Update supplier
      operationId: updateSupplier
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupplierWrite'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Supplier'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /supply/materials:
    get:
      tags:
        - Materials
      summary: List raw materials/energy
      operationId: listMaterials
      x-required-role: WATCH
      parameters:
        - name: kind
          in: query
          schema:
            $ref: '#/components/schemas/MaterialKind'
        - name: q
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Materials.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Material'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Materials
      summary: Create material (master data)
      operationId: createMaterial
      x-required-role: ADMIN
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MaterialWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Material'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/supply/materials/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Materials
      summary: Read material
      operationId: getMaterial
      x-required-role: WATCH
      responses:
        '200':
          description: Material.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Material'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
        - Materials
      summary: Update material (master data)
      operationId: updateMaterial
      x-required-role: ADMIN
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MaterialWrite'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Material'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/supply/materials/{id}/stock':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Stock
      summary: Read available stock of a material
      operationId: getStock
      x-required-role: WATCH
      responses:
        '200':
          description: Stock.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MaterialStock'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/supply/materials/{id}/stock/consumption':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Stock
      summary: Consume stock (production)
      description: >-
        Reduces available stock by the produced quantity. Called by the
        production cycle.
      operationId: consumeStock
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StockConsumption'
      responses:
        '200':
          description: New stock.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MaterialStock'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /supply/stock/reset:
    post:
      tags:
        - Stock
      summary: Reset all stock (data reset)
      description: >-
        Resets the warehouse stock — only for the data/economy reset, hence
        ADMIN. Not for normal operation.
      operationId: resetStock
      x-required-role: ADMIN
      responses:
        '204':
          description: Stock reset.
        '401':
          $ref: '#/components/responses/Unauthorized'
  /supply/supply-contracts:
    get:
      tags:
        - SupplyContracts
      summary: List supply contracts
      operationId: listSupplyContracts
      x-required-role: WATCH
      parameters:
        - name: supplierId
          in: query
          schema:
            type: string
        - name: materialId
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Contracts.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SupplyContract'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - SupplyContracts
      summary: Create supply contract
      operationId: createSupplyContract
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupplyContractWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupplyContract'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/supply/supply-contracts/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - SupplyContracts
      summary: Read supply contract
      operationId: getSupplyContract
      x-required-role: WATCH
      responses:
        '200':
          description: Contract.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupplyContract'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /supply/procurement-price:
    get:
      tags:
        - SupplyContracts
      summary: Resolve procurement price + lead time
      description: >-
        Returns the unit price applicable for supplier + material + quantity
        (best tier) and the lead time.
      operationId: resolveProcurementPrice
      x-required-role: WATCH
      parameters:
        - name: supplierId
          in: query
          required: true
          schema:
            type: string
        - name: materialId
          in: query
          required: true
          schema:
            type: string
        - name: quantity
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
      responses:
        '200':
          description: Price + lead time.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolvedSupplyPrice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /supply/supply-orders:
    get:
      tags:
        - SupplyOrders
      summary: List procurements
      operationId: listSupplyOrders
      x-required-role: WATCH
      parameters:
        - name: supplierId
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/SupplyOrderStatus'
      responses:
        '200':
          description: Procurements.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SupplyOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - SupplyOrders
      summary: Create procurement
      operationId: createSupplyOrder
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupplyOrderWrite'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupplyOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/supply/supply-orders/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - SupplyOrders
      summary: Read procurement
      operationId: getSupplyOrder
      x-required-role: WATCH
      responses:
        '200':
          description: Procurement.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupplyOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  '/supply/supply-orders/{id}/submission':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - SupplyOrders
      summary: Submit procurement for approval
      description: 'Above the threshold → PENDING_APPROVAL, otherwise APPROVED.'
      operationId: submitSupplyOrder
      x-required-role: WORK
      responses:
        '200':
          description: Submitted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupplyOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/supply/supply-orders/{id}/decision':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - SupplyOrders
      summary: Approval decision
      operationId: decideSupplyOrder
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovalDecision'
      responses:
        '200':
          description: Decided.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupplyOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/supply/supply-orders/{id}/receipt':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - SupplyOrders
      summary: Book goods receipt (delivered)
      description: Books the goods receipt and increases the material stock.
      operationId: receiveSupplyOrder
      x-required-role: WORK
      responses:
        '200':
          description: Received.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupplyOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/supply/supply-orders/{id}/cancellation':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - SupplyOrders
      summary: Cancel procurement
      operationId: cancelSupplyOrder
      x-required-role: WORK
      responses:
        '200':
          description: Cancelled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupplyOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /build/boms:
    get:
      tags:
        - BOM
      summary: All bills of materials
      operationId: listBoms
      x-required-role: WATCH
      responses:
        '200':
          description: Bills of materials.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProductBom'
        '401':
          $ref: '#/components/responses/Unauthorized'
  '/build/products/{id}/bom':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Product ID.
    get:
      tags:
        - BOM
      summary: Bill of materials of a product
      operationId: getBom
      x-required-role: WATCH
      responses:
        '200':
          description: Bill of materials.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductBom'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags:
        - BOM
      summary: Create/update bill of materials (master data)
      operationId: upsertBom
      x-required-role: ADMIN
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BomWrite'
      responses:
        '200':
          description: Saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductBom'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /build/capacity:
    get:
      tags:
        - Planning
      summary: Current production capacity
      description: >
        Planned daily capacity from factory/port workers (availability from
        ACMEhr) and the labor/port units per worker and day.
      operationId: getCapacity
      x-required-role: WATCH
      responses:
        '200':
          description: Capacity.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Capacity'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /build/supply-projection:
    get:
      tags:
        - Planning
      summary: Material demand projection
      description: >
        Aggregated material demand over the horizon, derived from the open
        production contracts (ACMEcrm) and the BOMs. Basis for procurement in
        ACMEsupply.
      operationId: getSupplyProjection
      x-required-role: WATCH
      parameters:
        - name: horizonDays
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 30
          description: Planning horizon in days.
      responses:
        '200':
          description: Demand per material.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupplyProjection'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /build/feasibility:
    post:
      tags:
        - Feasibility
      summary: Can a contract be fulfilled?
      description: >
        Checks a contract specification (products, quantities, delivery date)
        against capacity and material availability. Non-mutating. Returns
        `feasible` and — if not — the scarce resources (worker or material) with
        the shortfall.
      operationId: checkFeasibility
      x-required-role: WATCH
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FeasibilityRequest'
      responses:
        '200':
          description: Feasibility result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeasibilityResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /build/production-orders:
    get:
      tags:
        - Shopfloor
      summary: Production orders on the planning board
      description: >
        Open and recently closed production orders, each in one of the board
        stages GEPLANT · RUESTEN · IN_ARBEIT · PRUEFUNG · FERTIG. Filter by
        stage to fetch a single column.
      operationId: listProductionOrders
      x-required-role: WATCH
      parameters:
        - name: stage
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/OrderStage'
          description: Restrict to a single board stage.
      responses:
        '200':
          description: Production orders.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProductionOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Shopfloor
      summary: Create a production order
      description: >-
        Schedules a new order; it enters the board in `GEPLANT` unless a stage
        is given.
      operationId: createProductionOrder
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductionOrderCreate'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductionOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  '/build/production-orders/{id}':
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Production order ID.
    patch:
      tags:
        - Shopfloor
      summary: Advance / edit a production order
      description: >
        Moves an order between board stages (drag & drop) or reassigns its
        machine. Non-null fields are applied; others are left unchanged.
      operationId: updateProductionOrder
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductionOrderUpdate'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductionOrder'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /build/shift-plan:
    get:
      tags:
        - Shopfloor
      summary: Weekly shift plan
      description: >
        The current week's shift matrix — three shifts (early/late/night) × six
        working days (Mon–Sat), each cell FREE, FULL or PARTIAL. Basis for the
        daily labor capacity.
      operationId: getShiftPlan
      x-required-role: WATCH
      responses:
        '200':
          description: Shift plan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShiftPlan'
        '401':
          $ref: '#/components/responses/Unauthorized'
    put:
      tags:
        - Shopfloor
      summary: Replace the weekly shift plan
      operationId: putShiftPlan
      x-required-role: WORK
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ShiftPlanWrite'
      responses:
        '200':
          description: Saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShiftPlan'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /build/machines:
    get:
      tags:
        - Shopfloor
      summary: Machine monitor (digital twin)
      description: >
        Live status of the shop-floor machines with **OEE** and its availability
        / performance / quality components, plus the currently running order and
        its progress.
      operationId: listMachines
      x-required-role: WATCH
      responses:
        '200':
          description: Machines.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Machine'
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  schemas:
    CompensationType:
      type: string
      enum:
        - HOURLY
        - SALARIED
    WorkLocation:
      type: string
      description: Where an employee primarily works.
      enum:
        - ONSITE
        - REMOTE
        - HYBRID
    Employee:
      type: object
      required:
        - id
        - fullName
        - active
      properties:
        id:
          type: string
          maxLength: 32
          example: p-julia-jefa
        firstName:
          type: string
        lastName:
          type: string
        fullName:
          type: string
          example: Julia Jefa
        email:
          type: string
          format: email
          nullable: true
        jobTitle:
          type: string
          nullable: true
          example: Managing Director
        active:
          type: boolean
        applicant:
          type: boolean
          description: Applicant (not yet hired).
        primaryOrgUnitId:
          type: string
          nullable: true
        primaryOrgUnitName:
          type: string
          nullable: true
        managerId:
          type: string
          nullable: true
          description: Reporting line (manager).
        deputyIds:
          type: array
          items:
            type: string
          description: Named deputies.
        assistantIds:
          type: array
          items:
            type: string
        secondaryUnitIds:
          type: array
          items:
            type: string
          description: Matrix/secondary memberships.
        compType:
          $ref: '#/components/schemas/CompensationType'
        hourlyRate:
          type: number
          nullable: true
          description: Hourly rate when HOURLY.
        workLocation:
          $ref: '#/components/schemas/WorkLocation'
    EmployeeCreate:
      type: object
      required:
        - firstName
        - lastName
      properties:
        firstName:
          type: string
        lastName:
          type: string
        email:
          type: string
          format: email
        jobTitle:
          type: string
        primaryOrgUnitId:
          type: string
        managerId:
          type: string
        workLocation:
          $ref: '#/components/schemas/WorkLocation'
    EmployeeUpdate:
      description: Maintenance of the curated overlay. Only set fields are changed.
      type: object
      properties:
        jobTitle:
          type: string
        managerId:
          type: string
          nullable: true
        active:
          type: boolean
        deputyIds:
          type: array
          items:
            type: string
        assistantIds:
          type: array
          items:
            type: string
        workLocation:
          $ref: '#/components/schemas/WorkLocation'
    CompensationUpdate:
      type: object
      required:
        - compType
      properties:
        compType:
          $ref: '#/components/schemas/CompensationType'
        hourlyRate:
          type: number
          nullable: true
          description: Required when HOURLY.
    PayrollSummary:
      description: 'Weekly personnel costs, split by salary/hourly wage.'
      type: object
      required:
        - salariedCount
        - hourlyCount
        - weeklySalariedEur
        - weeklyHourlyEur
        - weeklyTotalEur
      properties:
        salariedCount:
          type: integer
        hourlyCount:
          type: integer
        weeklySalariedEur:
          type: number
        weeklyHourlyEur:
          type: number
        weeklyTotalEur:
          type: number
    ApplicantStage:
      type: string
      description: Recruiting pipeline stage of an applicant.
      enum:
        - NEW
        - SCREENING
        - INTERVIEW
        - OFFER
        - REJECTED
    Applicant:
      description: Applicant before being hired (applicant=true).
      type: object
      required:
        - id
        - fullName
      properties:
        id:
          type: string
          maxLength: 32
        firstName:
          type: string
        lastName:
          type: string
        fullName:
          type: string
        email:
          type: string
          format: email
          nullable: true
        jobTitle:
          type: string
          nullable: true
          description: Target role.
        targetOrgUnitId:
          type: string
          nullable: true
          description: Target org unit.
        targetOrgUnitName:
          type: string
          nullable: true
        appliedOn:
          type: string
          format: date
          nullable: true
        stage:
          $ref: '#/components/schemas/ApplicantStage'
        matchScore:
          type: integer
          minimum: 0
          maximum: 100
          nullable: true
          description: Fit score 0–100 from screening; null until scored.
    ApplicantWrite:
      type: object
      required:
        - firstName
        - lastName
      properties:
        firstName:
          type: string
        lastName:
          type: string
        email:
          type: string
          format: email
        jobTitle:
          type: string
        targetOrgUnitId:
          type: string
        stage:
          $ref: '#/components/schemas/ApplicantStage'
        matchScore:
          type: integer
          minimum: 0
          maximum: 100
          nullable: true
    ApplicantStageUpdate:
      type: object
      required:
        - stage
      properties:
        stage:
          $ref: '#/components/schemas/ApplicantStage'
    Role:
      type: object
      required:
        - id
        - title
        - kind
      properties:
        id:
          type: string
        title:
          type: string
          example: Head of Purchasing
        kind:
          $ref: '#/components/schemas/RoleKind'
        description:
          type: string
          nullable: true
    RoleKind:
      type: string
      enum:
        - BUSINESS
        - GOVERNANCE
        - SYSTEM
    RoleAssignment:
      type: object
      required:
        - id
        - personId
        - roleId
      properties:
        id:
          type: string
        personId:
          type: string
        personName:
          type: string
        roleId:
          type: string
        roleTitle:
          type: string
        orgUnitId:
          type: string
          nullable: true
        validFrom:
          type: string
          format: date
          nullable: true
        validUntil:
          type: string
          format: date
          nullable: true
    RoleAssignmentCreate:
      type: object
      required:
        - roleId
      properties:
        roleId:
          type: string
        orgUnitId:
          type: string
          nullable: true
        validFrom:
          type: string
          format: date
          nullable: true
        validUntil:
          type: string
          format: date
          nullable: true
    AbsenceType:
      type: string
      description: Type of absence.
      enum:
        - VACATION
        - SICK
        - CURE
        - TRAINING
        - OTHER
    AbsenceStatus:
      type: string
      enum:
        - PLANNED
        - APPROVED
        - REJECTED
        - CANCELLED
    Absence:
      type: object
      required:
        - id
        - personId
        - type
        - status
        - period
      properties:
        id:
          type: string
        personId:
          type: string
        personName:
          type: string
        type:
          $ref: '#/components/schemas/AbsenceType'
        status:
          $ref: '#/components/schemas/AbsenceStatus'
        period:
          $ref: '#/components/schemas/DateRange'
        substituteId:
          type: string
          nullable: true
          description: Deputy during the absence.
        substituteName:
          type: string
          nullable: true
        reasonKey:
          type: string
          nullable: true
          description: Business key/reason (e.g. urlaub-1).
        note:
          type: string
          nullable: true
        workingDays:
          type: integer
          nullable: true
          description: Effective working days (excluding weekend).
    AbsenceCreate:
      type: object
      required:
        - personId
        - type
        - from
        - until
      properties:
        personId:
          type: string
        type:
          $ref: '#/components/schemas/AbsenceType'
        from:
          type: string
          format: date
        until:
          type: string
          format: date
        substituteId:
          type: string
          nullable: true
        note:
          type: string
          nullable: true
    AbsenceUpdate:
      type: object
      properties:
        status:
          $ref: '#/components/schemas/AbsenceStatus'
        substituteId:
          type: string
          nullable: true
        from:
          type: string
          format: date
        until:
          type: string
          format: date
        note:
          type: string
          nullable: true
    PowerOfAttorneyType:
      type: string
      enum:
        - PROKURA
        - HANDLUNGSVOLLMACHT
        - GENERAL
        - SPECIAL
    SignatureRule:
      type: string
      description: 'SOLE = sole signing authority, JOINT = only jointly.'
      enum:
        - SOLE
        - JOINT
    PowerOfAttorney:
      type: object
      required:
        - id
        - holderId
        - legalEntityId
        - type
        - signatureRule
        - limit
        - revoked
      properties:
        id:
          type: string
        holderId:
          type: string
        holderName:
          type: string
        legalEntityId:
          type: string
        type:
          $ref: '#/components/schemas/PowerOfAttorneyType'
        signatureRule:
          $ref: '#/components/schemas/SignatureRule'
        limit:
          $ref: '#/components/schemas/Money'
        scope:
          type: string
          nullable: true
        validity:
          $ref: '#/components/schemas/DateRange'
        revoked:
          type: boolean
    PowerOfAttorneyGrant:
      type: object
      required:
        - holderId
        - legalEntityId
        - type
        - signatureRule
        - validFrom
      properties:
        holderId:
          type: string
        legalEntityId:
          type: string
        type:
          $ref: '#/components/schemas/PowerOfAttorneyType'
        signatureRule:
          $ref: '#/components/schemas/SignatureRule'
        limit:
          $ref: '#/components/schemas/Money'
        scope:
          type: string
          nullable: true
        validFrom:
          type: string
          format: date
        validUntil:
          type: string
          format: date
          nullable: true
    ApprovalLimit:
      description: Approval limit — up to which amount a person may sign off.
      type: object
      required:
        - personId
        - maxAmount
        - source
      properties:
        personId:
          type: string
        personName:
          type: string
        legalEntityId:
          type: string
          nullable: true
        maxAmount:
          $ref: '#/components/schemas/Money'
        signatureRule:
          $ref: '#/components/schemas/SignatureRule'
        source:
          type: string
          description: Origin of the limit.
          enum:
            - POWER_OF_ATTORNEY
            - EXPLICIT
        validity:
          $ref: '#/components/schemas/DateRange'
    ApprovalLimitSet:
      type: object
      required:
        - personId
        - maxAmount
      properties:
        personId:
          type: string
        legalEntityId:
          type: string
          nullable: true
        maxAmount:
          $ref: '#/components/schemas/Money'
        validFrom:
          type: string
          format: date
          nullable: true
        validUntil:
          type: string
          format: date
          nullable: true
    Problem:
      description: Error per RFC 7807.
      type: object
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
    DateRange:
      description: Validity period. `until=null` = open-ended.
      type: object
      properties:
        from:
          type: string
          format: date
        until:
          type: string
          format: date
          nullable: true
    Money:
      description: >-
        Monetary amount. `unlimited=true` → no amount limit (then
        amount/currency are empty).
      type: object
      properties:
        amount:
          type: number
          nullable: true
          example: 50000
        currency:
          type: string
          minLength: 3
          maxLength: 3
          nullable: true
          example: EUR
        unlimited:
          type: boolean
          default: false
    CustomerKind:
      type: string
      enum:
        - CUSTOMER
        - RESELLER
    CustomerStatus:
      type: string
      enum:
        - PROSPECT
        - ACTIVE
        - BLOCKED
    Customer:
      type: object
      required:
        - id
        - name
        - kind
        - status
      properties:
        id:
          type: string
        name:
          type: string
          example: Kontor Nord GmbH
        kind:
          $ref: '#/components/schemas/CustomerKind'
        status:
          $ref: '#/components/schemas/CustomerStatus'
        email:
          type: string
          format: email
          nullable: true
        country:
          type: string
          nullable: true
          example: DE
        parentResellerId:
          type: string
          nullable: true
          description: For end customers of a reseller.
        priceListId:
          type: string
          nullable: true
          description: Assigned price list.
    CustomerWrite:
      type: object
      required:
        - name
        - kind
      properties:
        name:
          type: string
        kind:
          $ref: '#/components/schemas/CustomerKind'
        status:
          $ref: '#/components/schemas/CustomerStatus'
        email:
          type: string
          format: email
        country:
          type: string
        parentResellerId:
          type: string
          nullable: true
        priceListId:
          type: string
          nullable: true
    Product:
      type: object
      required:
        - id
        - sku
        - name
        - active
      properties:
        id:
          type: string
        sku:
          type: string
          example: ACME-RADIO-01
        name:
          type: string
          example: Tube radio „Nostalgie“
        category:
          type: string
          nullable: true
        unit:
          type: string
          default: piece
        active:
          type: boolean
        listPrice:
          $ref: '#/components/schemas/Money'
    ProductWrite:
      type: object
      required:
        - sku
        - name
      properties:
        sku:
          type: string
        name:
          type: string
        category:
          type: string
        unit:
          type: string
        active:
          type: boolean
        listPrice:
          $ref: '#/components/schemas/Money'
    PriceListKind:
      type: string
      enum:
        - LIST
        - RESELLER
        - CUSTOM
    PriceListItem:
      type: object
      required:
        - productId
        - unitPrice
      properties:
        productId:
          type: string
        unitPrice:
          type: number
          example: 149
        minQuantity:
          type: integer
          default: 1
          description: The price applies from this quantity on (tier).
    PriceList:
      type: object
      required:
        - id
        - name
        - currency
        - kind
        - items
      properties:
        id:
          type: string
        name:
          type: string
        currency:
          type: string
          minLength: 3
          maxLength: 3
          example: EUR
        kind:
          $ref: '#/components/schemas/PriceListKind'
        validity:
          $ref: '#/components/schemas/DateRange'
        items:
          type: array
          items:
            $ref: '#/components/schemas/PriceListItem'
    PriceListWrite:
      type: object
      required:
        - name
        - currency
        - kind
      properties:
        name:
          type: string
        currency:
          type: string
          minLength: 3
          maxLength: 3
        kind:
          $ref: '#/components/schemas/PriceListKind'
        validFrom:
          type: string
          format: date
        validUntil:
          type: string
          format: date
          nullable: true
        items:
          type: array
          items:
            $ref: '#/components/schemas/PriceListItem'
    ResolvedPrice:
      type: object
      required:
        - productId
        - customerId
        - quantity
        - unitPrice
      properties:
        productId:
          type: string
        customerId:
          type: string
        quantity:
          type: integer
        unitPrice:
          type: number
        currency:
          type: string
        priceListId:
          type: string
          nullable: true
        source:
          type: string
          enum:
            - PRICE_LIST
            - LIST_PRICE
    QuoteStatus:
      type: string
      enum:
        - DRAFT
        - SENT
        - ACCEPTED
        - REJECTED
        - EXPIRED
    LineWrite:
      type: object
      required:
        - productId
        - quantity
      properties:
        productId:
          type: string
        quantity:
          type: integer
          minimum: 1
        unitPrice:
          type: number
          nullable: true
          description: Optional — otherwise resolved from the price list.
        discountPercent:
          type: number
          default: 0
    Line:
      type: object
      required:
        - productId
        - quantity
        - unitPrice
        - lineTotal
      properties:
        productId:
          type: string
        productName:
          type: string
        quantity:
          type: integer
        unitPrice:
          type: number
        discountPercent:
          type: number
        lineTotal:
          type: number
        remainingQuantity:
          type: integer
          description: Quantity still to be produced (order lines).
    Quote:
      type: object
      required:
        - id
        - customerId
        - status
        - currency
        - lines
        - netTotal
      properties:
        id:
          type: string
        customerId:
          type: string
        customerName:
          type: string
        status:
          $ref: '#/components/schemas/QuoteStatus'
        currency:
          type: string
        validUntil:
          type: string
          format: date
          nullable: true
        lines:
          type: array
          items:
            $ref: '#/components/schemas/Line'
        netTotal:
          type: number
        createdOn:
          type: string
          format: date
          nullable: true
    QuoteWrite:
      type: object
      required:
        - customerId
        - lines
      properties:
        customerId:
          type: string
        validUntil:
          type: string
          format: date
          nullable: true
        lines:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/LineWrite'
    QuoteUpdate:
      type: object
      properties:
        status:
          $ref: '#/components/schemas/QuoteStatus'
        validUntil:
          type: string
          format: date
          nullable: true
    OrderStatus:
      type: string
      enum:
        - CREATED
        - PENDING_APPROVAL
        - APPROVED
        - REJECTED
        - FULFILLED
        - CANCELLED
    Order:
      type: object
      required:
        - id
        - customerId
        - status
        - orderDate
        - lines
        - total
      properties:
        id:
          type: string
        customerId:
          type: string
        customerName:
          type: string
        customerCountry:
          type: string
          nullable: true
        quoteId:
          type: string
          nullable: true
        status:
          $ref: '#/components/schemas/OrderStatus'
        orderDate:
          type: string
          format: date
        lines:
          type: array
          items:
            $ref: '#/components/schemas/Line'
        total:
          $ref: '#/components/schemas/Money'
        approval:
          $ref: '#/components/schemas/Approval'
        shippingMode:
          type: string
          nullable: true
          description: 'Shipping mode (e.g. AIR = air freight, otherwise sea freight).'
        note:
          type: string
          nullable: true
    OrderWrite:
      type: object
      required:
        - customerId
        - lines
      properties:
        customerId:
          type: string
        quoteId:
          type: string
          nullable: true
        lines:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/LineWrite'
        shippingMode:
          type: string
          nullable: true
        note:
          type: string
          nullable: true
    ProductionReport:
      description: 'Per product, the share produced today (additionally fulfilled).'
      type: object
      required:
        - lines
      properties:
        lines:
          type: array
          minItems: 1
          items:
            type: object
            required:
              - productId
              - producedQuantity
            properties:
              productId:
                type: string
              producedQuantity:
                type: integer
                minimum: 0
    PipelineStage:
      description: >
        Ordered sales stage. `probability` is derived from it: NEU 15 ·
        QUALIFIZIERT 35 · ANGEBOT 60 · VERHANDLUNG 80 · GEWONNEN 100 (%).
      type: string
      enum:
        - NEU
        - QUALIFIZIERT
        - ANGEBOT
        - VERHANDLUNG
        - GEWONNEN
    DealSource:
      description: What the deal is currently backed by in the transactional model.
      type: string
      enum:
        - LEAD
        - QUOTE
        - ORDER
    Owner:
      description: Sales owner assigned to a deal (avatar initials + display name).
      type: object
      required:
        - initials
      properties:
        initials:
          type: string
          minLength: 1
          maxLength: 3
          example: JS
        name:
          type: string
          nullable: true
          example: Jana Schmidt
    Deal:
      description: >-
        A pipeline deal — a stage projection over a lead/quote/order (see `GET
        /pipeline`).
      type: object
      required:
        - id
        - source
        - company
        - stage
        - probability
        - value
      properties:
        id:
          type: string
          example: deal-1024
        source:
          $ref: '#/components/schemas/DealSource'
        quoteId:
          type: string
          nullable: true
        orderId:
          type: string
          nullable: true
        customerId:
          type: string
          nullable: true
        contactId:
          type: string
          nullable: true
          description: The contact this deal is with.
        company:
          type: string
          example: Nordwind Logistik
        contact:
          type: string
          nullable: true
          example: Michael Braun · CPO
        stage:
          $ref: '#/components/schemas/PipelineStage'
        probability:
          type: integer
          minimum: 0
          maximum: 100
          description: Derived from `stage`.
        value:
          $ref: '#/components/schemas/Money'
        owner:
          $ref: '#/components/schemas/Owner'
        lastActivity:
          type: string
          nullable: true
          description: Human label
          e.g. "Angebot gesendet · vor 18 Min".: null
        lastActivityAt:
          type: string
          format: date-time
          nullable: true
        ageDays:
          type: integer
          nullable: true
          description: Days since the deal entered its current stage.
    DealCreate:
      type: object
      required:
        - company
      properties:
        company:
          type: string
        contact:
          type: string
          nullable: true
        customerId:
          type: string
          nullable: true
        contactId:
          type: string
          nullable: true
        stage:
          $ref: '#/components/schemas/PipelineStage'
        value:
          $ref: '#/components/schemas/Money'
        ownerInitials:
          type: string
          nullable: true
    DealUpdate:
      description: Partial update of a deal's sales-overlay attributes.
      type: object
      properties:
        stage:
          $ref: '#/components/schemas/PipelineStage'
        company:
          type: string
        contact:
          type: string
          nullable: true
        contactId:
          type: string
          nullable: true
        value:
          $ref: '#/components/schemas/Money'
        ownerInitials:
          type: string
          nullable: true
    Contact:
      description: A person at a customer (company).
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          example: contact-1001
        customerId:
          type: string
          nullable: true
          description: The company this contact belongs to.
        name:
          type: string
          example: Sara Mena
        role:
          type: string
          nullable: true
          example: CTO
        email:
          type: string
          format: email
          nullable: true
        phone:
          type: string
          nullable: true
        primary:
          type: boolean
          default: false
          description: Primary contact of the customer.
        newsletter:
          type: boolean
          default: false
          description: Newsletter opt-in (set by form newsletter actions).
    ContactWrite:
      type: object
      required:
        - name
      properties:
        customerId:
          type: string
          nullable: true
        name:
          type: string
        role:
          type: string
          nullable: true
        email:
          type: string
          format: email
          nullable: true
        phone:
          type: string
          nullable: true
        primary:
          type: boolean
        newsletter:
          type: boolean
    MailDirection:
      type: string
      enum:
        - INBOUND
        - OUTBOUND
    MailMessage:
      type: object
      required:
        - id
        - direction
        - sentAt
      properties:
        id:
          type: string
        direction:
          $ref: '#/components/schemas/MailDirection'
        from:
          type: string
          nullable: true
        to:
          type: string
          nullable: true
        sentAt:
          type: string
          format: date-time
        snippet:
          type: string
          nullable: true
        body:
          type: string
          nullable: true
    MailThread:
      description: A correspondence thread attached to a customer and/or contact.
      type: object
      required:
        - id
        - subject
      properties:
        id:
          type: string
          example: thread-1001
        subject:
          type: string
          example: Angebot Rahmen XL
        customerId:
          type: string
          nullable: true
        contactId:
          type: string
          nullable: true
        participants:
          type: array
          items:
            type: string
        messageCount:
          type: integer
        lastMessageAt:
          type: string
          format: date-time
          nullable: true
        preview:
          type: string
          nullable: true
          description: Snippet of the latest message.
        messages:
          type: array
          items:
            $ref: '#/components/schemas/MailMessage'
    FormFieldType:
      type: string
      enum:
        - TEXT
        - EMAIL
        - PHONE
        - NUMBER
        - TEXTAREA
        - CHECKBOX
        - SELECT
        - DATE
    FormField:
      description: One field of a web form.
      type: object
      required:
        - key
        - label
        - type
      properties:
        key:
          type: string
          example: email
          description: Unique within the form; keys the submission values.
        label:
          type: string
          example: E-Mail
        type:
          $ref: '#/components/schemas/FormFieldType'
        required:
          type: boolean
          default: false
        placeholder:
          type: string
          nullable: true
        options:
          type: array
          items:
            type: string
          nullable: true
          description: Choices for SELECT.
    FormActionType:
      description: >
        What a valid submission triggers (configured in Admin): send an email,
        create a contact or opportunity (deal), schedule an appointment, or
        subscribe/unsubscribe the submitter from the newsletter.
      type: string
      enum:
        - SEND_EMAIL
        - CREATE_CONTACT
        - CREATE_OPPORTUNITY
        - SCHEDULE_APPOINTMENT
        - NEWSLETTER_SUBSCRIBE
        - NEWSLETTER_UNSUBSCRIBE
    FormAction:
      description: 'A trigger configured in Admin, run on a valid submission.'
      type: object
      required:
        - type
      properties:
        type:
          $ref: '#/components/schemas/FormActionType'
        config:
          type: object
          nullable: true
          additionalProperties: true
          description: >
            Action-specific settings, e.g. template + recipient for SEND_EMAIL,
            target stage/owner for CREATE_OPPORTUNITY, calendar/slot for
            SCHEDULE_APPOINTMENT, list id for NEWSLETTER_*.
    WebForm:
      description: >-
        A web form / newsletter signup. Layout and trigger actions are
        maintained in Admin.
      type: object
      required:
        - id
        - name
        - slug
        - fields
      properties:
        id:
          type: string
          example: form-lead
        name:
          type: string
          example: Kontaktformular
        slug:
          type: string
          example: kontakt
          description: Public identifier used to embed the form.
        description:
          type: string
          nullable: true
        enabled:
          type: boolean
          default: true
        fields:
          type: array
          items:
            $ref: '#/components/schemas/FormField'
        actions:
          type: array
          items:
            $ref: '#/components/schemas/FormAction'
          description: Triggered on a valid submission.
        createdOn:
          type: string
          format: date
          nullable: true
    WebFormWrite:
      type: object
      required:
        - name
        - slug
        - fields
      properties:
        name:
          type: string
        slug:
          type: string
        description:
          type: string
          nullable: true
        enabled:
          type: boolean
        fields:
          type: array
          items:
            $ref: '#/components/schemas/FormField'
        actions:
          type: array
          items:
            $ref: '#/components/schemas/FormAction'
    FormSubmitRequest:
      description: A public submission — field values keyed by `FormField.key`.
      type: object
      required:
        - values
      properties:
        values:
          type: object
          additionalProperties: true
          example:
            name: Sara Mena
            email: sara@example.com
            newsletter: true
    TriggeredAction:
      description: Outcome of one configured action after a submission.
      type: object
      required:
        - type
        - status
      properties:
        type:
          $ref: '#/components/schemas/FormActionType'
        status:
          type: string
          enum:
            - DONE
            - SKIPPED
            - FAILED
            - PENDING
        detail:
          type: string
          nullable: true
        ref:
          type: string
          nullable: true
          description: Id of a created contact/deal/appointment
          if any.: null
    FormSubmission:
      description: A stored form submission and the outcome of its trigger actions.
      type: object
      required:
        - id
        - formId
        - values
        - submittedAt
      properties:
        id:
          type: string
        formId:
          type: string
        values:
          type: object
          additionalProperties: true
        submittedAt:
          type: string
          format: date-time
        triggered:
          type: array
          items:
            $ref: '#/components/schemas/TriggeredAction'
    Approval:
      description: E-approval status of an order/procurement.
      type: object
      properties:
        required:
          type: boolean
        approverId:
          type: string
          nullable: true
          description: Authorized signatory (resolved from ACMEhr).
        decision:
          type: string
          nullable: true
          enum:
            - APPROVE
            - REJECT
        decidedOn:
          type: string
          format: date
          nullable: true
        comment:
          type: string
          nullable: true
    ApprovalDecision:
      description: Approval decision by an authorized signatory.
      type: object
      required:
        - approverId
        - decision
      properties:
        approverId:
          type: string
          description: Person (ACMEhr) making the decision.
        decision:
          type: string
          enum:
            - APPROVE
            - REJECT
        comment:
          type: string
          nullable: true
    SupplierStatus:
      type: string
      enum:
        - PROSPECT
        - ACTIVE
        - BLOCKED
    Supplier:
      type: object
      required:
        - id
        - name
        - status
      properties:
        id:
          type: string
        name:
          type: string
          example: Rohstoff Handels AG
        status:
          $ref: '#/components/schemas/SupplierStatus'
        email:
          type: string
          format: email
          nullable: true
        country:
          type: string
          nullable: true
          example: DE
    SupplierWrite:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        status:
          $ref: '#/components/schemas/SupplierStatus'
        email:
          type: string
          format: email
        country:
          type: string
    MaterialKind:
      type: string
      enum:
        - RAW_MATERIAL
        - ENERGY
    Material:
      type: object
      required:
        - id
        - code
        - name
        - kind
        - unit
      properties:
        id:
          type: string
        code:
          type: string
          example: STAHL
        name:
          type: string
          example: Stahlblech
        kind:
          $ref: '#/components/schemas/MaterialKind'
        unit:
          type: string
          example: kg
    MaterialWrite:
      type: object
      required:
        - code
        - name
        - kind
      properties:
        code:
          type: string
        name:
          type: string
        kind:
          $ref: '#/components/schemas/MaterialKind'
        unit:
          type: string
    MaterialStock:
      description: Available warehouse stock of a material.
      type: object
      required:
        - materialId
        - available
      properties:
        materialId:
          type: string
        available:
          type: number
          description: Available quantity in the material's unit.
    StockConsumption:
      type: object
      required:
        - quantity
      properties:
        quantity:
          type: number
          minimum: 0
          description: Quantity to consume.
    Tier:
      type: object
      required:
        - minQuantity
        - unitPrice
      properties:
        minQuantity:
          type: integer
          default: 1
          description: The price applies from this quantity on (tier).
        unitPrice:
          type: number
    SupplyContract:
      type: object
      required:
        - id
        - supplierId
        - materialId
        - currency
        - leadTimeDays
        - tiers
      properties:
        id:
          type: string
        supplierId:
          type: string
        supplierName:
          type: string
        materialId:
          type: string
        materialName:
          type: string
        currency:
          type: string
          minLength: 3
          maxLength: 3
          example: EUR
        leadTimeDays:
          type: integer
          description: Lead time in days.
        validity:
          $ref: '#/components/schemas/DateRange'
        tiers:
          type: array
          items:
            $ref: '#/components/schemas/Tier'
    SupplyContractWrite:
      type: object
      required:
        - supplierId
        - materialId
        - currency
        - leadTimeDays
        - tiers
      properties:
        supplierId:
          type: string
        materialId:
          type: string
        currency:
          type: string
          minLength: 3
          maxLength: 3
        leadTimeDays:
          type: integer
        validFrom:
          type: string
          format: date
        validUntil:
          type: string
          format: date
          nullable: true
        tiers:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/Tier'
    ResolvedSupplyPrice:
      type: object
      required:
        - supplierId
        - materialId
        - quantity
        - unitPrice
        - leadTimeDays
      properties:
        supplierId:
          type: string
        materialId:
          type: string
        quantity:
          type: integer
        unitPrice:
          type: number
        currency:
          type: string
        leadTimeDays:
          type: integer
        contractId:
          type: string
          nullable: true
    SupplyOrderStatus:
      type: string
      enum:
        - CREATED
        - PENDING_APPROVAL
        - APPROVED
        - REJECTED
        - RECEIVED
        - CANCELLED
    LineWrite1:
      type: object
      required:
        - materialId
        - quantity
      properties:
        materialId:
          type: string
        quantity:
          type: integer
          minimum: 1
        unitPrice:
          type: number
          nullable: true
          description: Optional — otherwise resolved from the supply contract.
    Line1:
      type: object
      required:
        - materialId
        - quantity
        - unitPrice
        - lineTotal
      properties:
        materialId:
          type: string
        materialName:
          type: string
        quantity:
          type: integer
        unitPrice:
          type: number
        lineTotal:
          type: number
    SupplyOrder:
      type: object
      required:
        - id
        - supplierId
        - status
        - orderDate
        - lines
        - total
      properties:
        id:
          type: string
        supplierId:
          type: string
        supplierName:
          type: string
        status:
          $ref: '#/components/schemas/SupplyOrderStatus'
        orderDate:
          type: string
          format: date
        expectedDeliveryDate:
          type: string
          format: date
          nullable: true
          description: orderDate + max. lead time.
        lines:
          type: array
          items:
            $ref: '#/components/schemas/Line1'
        total:
          $ref: '#/components/schemas/Money'
        approval:
          $ref: '#/components/schemas/Approval'
        note:
          type: string
          nullable: true
    SupplyOrderWrite:
      type: object
      required:
        - supplierId
        - lines
      properties:
        supplierId:
          type: string
        lines:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/LineWrite1'
        note:
          type: string
          nullable: true
    BomLine:
      type: object
      required:
        - materialId
        - quantity
      properties:
        materialId:
          type: string
          description: ACMEsupply material ID.
        quantity:
          type: number
          description: Quantity per product piece.
    ProductBom:
      type: object
      required:
        - productId
        - laborUnits
        - energyUnits
        - lines
      properties:
        productId:
          type: string
        laborUnits:
          type: number
          description: Labor units per piece.
        energyUnits:
          type: number
          description: Energy per piece (kWh).
        lines:
          type: array
          items:
            $ref: '#/components/schemas/BomLine'
    BomWrite:
      type: object
      required:
        - laborUnits
        - energyUnits
        - lines
      properties:
        laborUnits:
          type: number
        energyUnits:
          type: number
        lines:
          type: array
          items:
            $ref: '#/components/schemas/BomLine'
    Capacity:
      type: object
      required:
        - factoryWorkers
        - laborUnitsPerWorkerDay
        - factoryLaborUnitsPerDay
        - harborWorkers
        - harborUnitsPerWorkerDay
        - harborUnitsPerDay
      properties:
        factoryWorkers:
          type: integer
          description: Available factory workers (from ACMEhr).
        laborUnitsPerWorkerDay:
          type: number
          description: Labor units per worker and day.
        factoryLaborUnitsPerDay:
          type: number
          description: Total labor capacity/day (workers × units).
        harborWorkers:
          type: integer
          description: Available port workers (shipping).
        harborUnitsPerWorkerDay:
          type: integer
          description: Shipping units per port worker and day.
        harborUnitsPerDay:
          type: integer
          description: Total shipping capacity/day.
    SupplyProjection:
      type: object
      required:
        - horizonDays
        - materials
      properties:
        horizonDays:
          type: integer
        materials:
          type: array
          items:
            $ref: '#/components/schemas/SupplyProjectionItem'
    SupplyProjectionItem:
      type: object
      required:
        - materialId
        - requiredQuantity
      properties:
        materialId:
          type: string
        requiredQuantity:
          type: number
          description: Quantity required over the horizon.
    FeasibilityRequest:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/FeasibilityItem'
        dueDate:
          type: string
          format: date
          description: Desired delivery date (leave open = earliest possible).
      description: Contract specification whose feasibility is checked.
    FeasibilityItem:
      type: object
      required:
        - productId
        - quantity
      properties:
        productId:
          type: string
        quantity:
          type: integer
          minimum: 1
    FeasibilityResult:
      type: object
      required:
        - feasible
        - requiredLaborUnits
        - requiredMaterials
        - scarce
      properties:
        feasible:
          type: boolean
        earliestDate:
          type: string
          format: date
          description: Earliest possible delivery date at current capacity (if computable).
        requiredLaborUnits:
          type: number
          description: Total labor units required for the specification.
        requiredMaterials:
          type: array
          items:
            $ref: '#/components/schemas/MaterialRequirement'
        scarce:
          type: array
          description: Scarce resources; empty when feasible=true.
          items:
            $ref: '#/components/schemas/ScarceResource'
    MaterialRequirement:
      type: object
      required:
        - materialId
        - requiredQuantity
        - availableQuantity
      properties:
        materialId:
          type: string
        requiredQuantity:
          type: number
        availableQuantity:
          type: number
    ScarceResource:
      type: object
      required:
        - resource
        - shortfall
      properties:
        resource:
          type: string
          enum:
            - WORKER
            - MATERIAL
        reference:
          type: string
          description: Material ID for `MATERIAL`; for `WORKER` e.g. `labor` or `harbor`.
        shortfall:
          type: number
          description: Shortfall (units or material quantity).
    OrderStage:
      type: string
      description: Stage of a production order on the planning board.
      enum:
        - GEPLANT
        - RUESTEN
        - IN_ARBEIT
        - PRUEFUNG
        - FERTIG
    ProductionOrder:
      type: object
      required:
        - id
        - orderNo
        - quantity
        - stage
      properties:
        id:
          type: string
        orderNo:
          type: string
          description: Human order number
          e.g. `FA-1042`.: null
        productId:
          type: string
          description: 'Product being manufactured (see /products/{id}/bom).'
        productName:
          type: string
          description: Denormalised product name for display.
        quantity:
          type: integer
          minimum: 1
          description: Pieces to produce.
        machine:
          type: string
          nullable: true
          description: 'Assigned machine (e.g. CNC-03), if any.'
        ownerInitials:
          type: string
          nullable: true
          description: Responsible planner's initials.
        stage:
          $ref: '#/components/schemas/OrderStage'
        dueDate:
          type: string
          format: date
          nullable: true
    ProductionOrderCreate:
      type: object
      required:
        - productId
        - quantity
      properties:
        productId:
          type: string
        quantity:
          type: integer
          minimum: 1
        machine:
          type: string
          nullable: true
        ownerInitials:
          type: string
          nullable: true
        stage:
          $ref: '#/components/schemas/OrderStage'
        dueDate:
          type: string
          format: date
          nullable: true
    ProductionOrderUpdate:
      type: object
      description: Partial update; only the given fields change.
      properties:
        stage:
          $ref: '#/components/schemas/OrderStage'
        machine:
          type: string
          nullable: true
        ownerInitials:
          type: string
          nullable: true
    ShiftCell:
      type: string
      description: Staffing of one shift on one day.
      enum:
        - FREE
        - FULL
        - PARTIAL
    ShiftRow:
      type: object
      required:
        - shift
        - cells
      properties:
        shift:
          type: string
          enum:
            - EARLY
            - LATE
            - NIGHT
        label:
          type: string
          description: Display label
          e.g. `Frühschicht`.: null
        time:
          type: string
          description: Shift window
          e.g. `06–14`.: null
        cells:
          type: array
          minItems: 6
          maxItems: 6
          description: One cell per working day Mon–Sat.
          items:
            $ref: '#/components/schemas/ShiftCell'
    ShiftPlan:
      type: object
      required:
        - rows
      properties:
        week:
          type: string
          description: Calendar week label
          e.g. `KW 29`.: null
        rows:
          type: array
          items:
            $ref: '#/components/schemas/ShiftRow'
    ShiftPlanWrite:
      type: object
      required:
        - rows
      properties:
        rows:
          type: array
          items:
            $ref: '#/components/schemas/ShiftRow'
    MachineStatus:
      type: string
      description: Live machine state driving the tile color.
      enum:
        - RUNNING
        - SETUP
        - FAULT
        - MAINTENANCE
        - IDLE
    Machine:
      type: object
      required:
        - id
        - name
        - status
        - oee
        - availability
        - performance
        - quality
        - progress
      properties:
        id:
          type: string
        name:
          type: string
          description: Machine label
          e.g. `CNC-01`.: null
        status:
          $ref: '#/components/schemas/MachineStatus'
        oee:
          type: integer
          minimum: 0
          maximum: 100
          description: Overall Equipment Effectiveness (%).
        availability:
          type: integer
          minimum: 0
          maximum: 100
          description: OEE availability component (%).
        performance:
          type: integer
          minimum: 0
          maximum: 100
          description: OEE performance component (%).
        quality:
          type: integer
          minimum: 0
          maximum: 100
          description: OEE quality component (%).
        progress:
          type: integer
          minimum: 0
          maximum: 100
          description: Progress of the running order (%).
        currentOrder:
          type: string
          nullable: true
          description: 'Running order + product, or a note (e.g. maintenance until).'
  responses:
    Unauthorized:
      description: Missing/invalid authentication or insufficient role.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Unprocessable:
      description: Semantically invalid.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    NotFound:
      description: Resource not found.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  parameters:
    PersonId:
      name: id
      in: path
      required: true
      description: Natural person key.
      schema:
        type: string
        maxLength: 32
    AsOf:
      name: 'on'
      in: query
      description: Reference date (default = today / current day).
      schema:
        type: string
        format: date
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >
        JWT from ACMEbase auth. The bearer role (WATCH/WORK/ADMIN) must satisfy
        the role required per operation under `x-required-role` (hierarchy ADMIN
        > WORK > WATCH).
