openapi: "3.1.0"
info:
  title: ListerLeo API
  version: "1.0.0"
  description: |
    Warehouse auction management platform API. Supports barcode scanning with AI-powered
    product identification, item cataloging into lots, warehouse put-away operations,
    pick list fulfillment, ZPL/ESC-POS label generation, CSV exports to auction platforms
    (HiBid, Proxibid, AuctionZip), and 9 real-time analytics reports.

    ## Authentication
    All endpoints (except login and registration) require a Bearer token obtained via
    `POST /auth/login` or `POST /auth/barcode-login`. Pass it as `Authorization: Bearer <token>`.

    ## Multi-Tenancy
    All data is automatically scoped to the authenticated user's organization. No explicit
    organization ID is needed in requests.

    ## Pagination
    The `GET /items` endpoint returns paginated results. All other list endpoints return
    full collections.
  contact:
    name: ListerLeo / Vendidit
    url: https://listerleo.com

servers:
  - url: https://listerleo.com/api
    description: Production
  - url: https://staging.listerleo.com/api
    description: Staging
  - url: http://localhost:8000/api
    description: Local Development

tags:
  - name: Auth
    description: Authentication, registration, and user info
  - name: Organization
    description: Organization context, settings, and reference data
  - name: Auctions
    description: Auction CRUD operations
  - name: Scanning
    description: Barcode scanning, product lookup, and AI enrichment
  - name: Photo Lookup
    description: Photo-based product identification
  - name: Items
    description: Auction item CRUD and image management
  - name: Put-Away
    description: Warehouse put-away operations (item/pallet placement)
  - name: Pallets
    description: Pallet management
  - name: Locations
    description: Warehouse location lookup
  - name: Labels
    description: ZPL and ESC/POS label generation
  - name: Dimensions
    description: AI dimension estimation
  - name: Exports
    description: CSV export generation and download
  - name: Pick Lists
    description: Order picking with concurrent picker support
  - name: Analytics
    description: Real-time dashboards and reports

security:
  - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: Sanctum Bearer token from POST /auth/login

  schemas:
    User:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        email: { type: string, format: email }
        organization:
          type: object
          nullable: true
          properties:
            id: { type: integer }
            name: { type: string }
            slug: { type: string }
        teams:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              name: { type: string }
        current_team:
          type: object
          nullable: true
          properties:
            id: { type: integer }
            name: { type: string }
        permissions: { type: array, items: { type: string } }
        roles: { type: array, items: { type: string } }

    AuthResponse:
      type: object
      properties:
        token: { type: string }
        user: { $ref: "#/components/schemas/User" }

    AuctionItem:
      type: object
      properties:
        id: { type: integer }
        lot_id: { type: integer, nullable: true }
        serial_number: { type: string }
        barcode: { type: string, nullable: true }
        title: { type: string, nullable: true }
        description: { type: string, nullable: true }
        condition: { type: string, nullable: true }
        condition_notes: { type: string, nullable: true }
        quantity: { type: integer, default: 1 }
        starting_bid: { type: number, nullable: true }
        reserve_price: { type: number, nullable: true }
        buy_now_price: { type: number, nullable: true }
        estimated_value: { type: number, nullable: true }
        cost: { type: number, nullable: true }
        length: { type: number, nullable: true }
        width: { type: number, nullable: true }
        height: { type: number, nullable: true }
        dimension_unit: { type: string, enum: [in, cm], default: in }
        weight: { type: number, nullable: true }
        weight_unit: { type: string, enum: [lb, kg], default: lb }
        status: { type: string, enum: [scanned, cataloged, put_away, picked, sold, archived] }
        vendor_manifest_id: { type: integer, nullable: true }
        location:
          type: object
          nullable: true
          properties:
            code: { type: string }
            path: { type: string, nullable: true }
        pallet:
          type: object
          nullable: true
          properties:
            number: { type: string }
        images:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              url: { type: string, format: uri }
              type: { type: string, enum: [photo, stock] }
              tag: { type: string }
        scanned_at: { type: string, format: date-time, nullable: true }
        scanned_by_name: { type: string, nullable: true }
        cataloged_at: { type: string, format: date-time, nullable: true }
        cataloged_by_name: { type: string, nullable: true }
        globalsku_data: { type: object, nullable: true }

    Auction:
      type: object
      properties:
        id: { type: integer }
        vendor_id: { type: integer, nullable: true }
        vendor_name: { type: string, nullable: true }
        name: { type: string }
        description: { type: string, nullable: true }
        auction_date: { type: string, format: date, nullable: true }
        auction_end_date: { type: string, format: date, nullable: true }
        external_id: { type: string, nullable: true }
        auction_url: { type: string, format: uri, nullable: true }
        buyers_premium_pct: { type: number, nullable: true }
        location: { type: string, nullable: true }
        timezone: { type: string, nullable: true }
        preview_start: { type: string, format: date-time, nullable: true }
        preview_end: { type: string, format: date-time, nullable: true }
        terms: { type: string, nullable: true }
        platform: { type: string, nullable: true }
        status: { type: string, enum: [draft, open, closed, exported, completed] }
        lot_count: { type: integer }
        item_count: { type: integer }
        created_at: { type: string, format: date-time }

    Lot:
      type: object
      properties:
        id: { type: integer }
        auction_id: { type: integer, nullable: true }
        auction_name: { type: string, nullable: true }
        vendor_id: { type: integer, nullable: true }
        vendor_name: { type: string, nullable: true }
        lot_number: { type: string }
        name: { type: string, nullable: true }
        description: { type: string, nullable: true }
        status: { type: string, enum: [draft, receiving, cataloging, staging, ready_for_auction, exported, sold, archived] }
        item_count: { type: integer }
        auction_date: { type: string, format: date, nullable: true }
        parent_lot_id: { type: integer, nullable: true }

    Vendor:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        code: { type: string, nullable: true }
        contact_name: { type: string, nullable: true }
        contact_email: { type: string, nullable: true }
        contact_phone: { type: string, nullable: true }
        is_active: { type: boolean }
        default_buyers_premium_pct: { type: number, nullable: true }
        default_timezone: { type: string, nullable: true }
        default_terms: { type: string, nullable: true }
        default_pickup_location: { type: string, nullable: true }

    Location:
      type: object
      properties:
        id: { type: integer }
        warehouse_name: { type: string, nullable: true }
        location_type: { type: string, enum: [zone, aisle, shelf, bin] }
        location_code: { type: string }
        name: { type: string, nullable: true }
        full_path: { type: string, nullable: true }
        barcode: { type: string, nullable: true }
        is_active: { type: boolean }

    Pallet:
      type: object
      properties:
        id: { type: integer }
        pallet_number: { type: string }
        barcode: { type: string }
        status: { type: string, enum: [empty, staging, full, shipped, archived] }
        item_count: { type: integer }
        location:
          type: object
          nullable: true
          properties:
            code: { type: string }
            path: { type: string, nullable: true }

    ConditionCode:
      type: object
      properties:
        id: { type: integer }
        code: { type: string }
        name: { type: string }
        description: { type: string, nullable: true }
        sort_order: { type: integer }

    PickList:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        source: { type: string, nullable: true }
        status: { type: string, enum: [pending, in_progress, completed, cancelled] }
        auction_id: { type: integer, nullable: true }
        auction_name: { type: string, nullable: true }
        lot:
          type: object
          nullable: true
          properties:
            id: { type: integer }
            lot_number: { type: string }
            name: { type: string, nullable: true }
        assigned_to:
          type: object
          nullable: true
          properties:
            id: { type: integer }
            name: { type: string }
        active_picker:
          type: object
          nullable: true
          properties:
            id: { type: integer }
            name: { type: string }
        last_picked_at: { type: string, format: date-time, nullable: true }
        items_count: { type: integer }
        progress: { type: number }
        created_at: { type: string, format: date-time }

    PickListItem:
      type: object
      properties:
        id: { type: integer }
        auction_item:
          type: object
          nullable: true
          properties:
            id: { type: integer }
            serial_number: { type: string }
            title: { type: string, nullable: true }
            barcode: { type: string, nullable: true }
        location:
          type: object
          nullable: true
          properties:
            code: { type: string }
            name: { type: string, nullable: true }
            warehouse: { type: string, nullable: true }
        pallet:
          type: object
          nullable: true
          properties:
            pallet_number: { type: string }
        expected_location: { type: string, nullable: true }
        sold_price: { type: number, nullable: true }
        buyer_info: { type: string, nullable: true }
        picked: { type: boolean }
        picked_by:
          type: object
          nullable: true
          properties:
            id: { type: integer }
            name: { type: string }
        picked_at: { type: string, format: date-time, nullable: true }
        claimed_by:
          type: object
          nullable: true
          properties:
            id: { type: integer }
            name: { type: string }
        notes: { type: string, nullable: true }

    Error:
      type: object
      properties:
        message: { type: string }

    ValidationError:
      type: object
      properties:
        message: { type: string }
        errors:
          type: object
          additionalProperties:
            type: array
            items: { type: string }

paths:
  # ── Auth ──────────────────────────────────────
  /auth/login:
    post:
      tags: [Auth]
      summary: Login with email and password
      security: []
      operationId: login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string }
      responses:
        "200":
          description: Authenticated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/AuthResponse" }
        "422": { description: Invalid credentials, content: { application/json: { schema: { $ref: "#/components/schemas/ValidationError" } } } }

  /auth/register-organization:
    post:
      tags: [Auth]
      summary: Register a new organization with the first user
      security: []
      operationId: registerOrganization
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [organization_name, name, email, password, password_confirmation]
              properties:
                organization_name: { type: string }
                name: { type: string }
                email: { type: string, format: email }
                password: { type: string, minLength: 8 }
                password_confirmation: { type: string }
      responses:
        "201":
          description: Organization and user created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/AuthResponse" }
        "422": { description: Validation error, content: { application/json: { schema: { $ref: "#/components/schemas/ValidationError" } } } }

  /auth/register:
    post:
      tags: [Auth]
      summary: Register a new user under an existing organization
      security: []
      operationId: registerUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email, password, password_confirmation, organization_id]
              properties:
                name: { type: string }
                email: { type: string, format: email }
                password: { type: string, minLength: 8 }
                password_confirmation: { type: string }
                organization_id: { type: integer }
      responses:
        "201":
          description: User created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/AuthResponse" }

  /auth/barcode-login:
    post:
      tags: [Auth]
      summary: Login via barcode badge scan
      security: []
      operationId: barcodeLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [barcode]
              properties:
                barcode: { type: string }
      responses:
        "200":
          description: Authenticated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/AuthResponse" }

  /auth/user:
    get:
      tags: [Auth]
      summary: Get current authenticated user
      operationId: getCurrentUser
      responses:
        "200":
          description: Current user
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/User" }

  /auth/logout:
    post:
      tags: [Auth]
      summary: Revoke current access token
      operationId: logout
      responses:
        "200":
          description: Logged out
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string, example: Logged out }

  /auth/login-barcode:
    get:
      tags: [Auth]
      summary: Get the current user's login barcode value
      operationId: getLoginBarcode
      responses:
        "200":
          description: Login barcode
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      login_barcode: { type: string }

  # ── Organization Context ─────────────────────
  /org/settings:
    get:
      tags: [Organization]
      summary: Get resolved organization settings
      operationId: getOrgSettings
      parameters:
        - name: vendor_id
          in: query
          schema: { type: integer }
        - name: lot_id
          in: query
          schema: { type: integer }
        - name: auction_id
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: Settings cascade resolved for the given context

  /org/condition-codes:
    get:
      tags: [Organization]
      summary: List condition codes for the organization
      operationId: getConditionCodes
      responses:
        "200":
          description: Condition codes
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/ConditionCode" }

  /org/vendors:
    get:
      tags: [Organization]
      summary: List active vendors
      operationId: getVendors
      responses:
        "200":
          description: Vendors
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Vendor" }

  /org/lots:
    get:
      tags: [Organization]
      summary: List lots for context selection
      operationId: getLots
      parameters:
        - name: vendor_id
          in: query
          schema: { type: integer }
        - name: auction_id
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: Lots
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Lot" }

  /org/auctions:
    get:
      tags: [Organization]
      summary: List active auctions (draft and open)
      operationId: getOrgAuctions
      responses:
        "200":
          description: Auctions
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Auction" }

  /org/filter-options:
    get:
      tags: [Organization]
      summary: Get all filter dropdown options
      operationId: getFilterOptions
      responses:
        "200":
          description: Filter options including vendors, lots, locations, statuses

  /org/manifests:
    get:
      tags: [Organization]
      summary: List vendor manifests
      operationId: getManifests
      responses:
        "200":
          description: Vendor manifests

  # ── Auctions ─────────────────────────────────
  /auctions:
    get:
      tags: [Auctions]
      summary: List all auctions
      operationId: listAuctions
      responses:
        "200":
          description: Auctions list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Auction" }
    post:
      tags: [Auctions]
      summary: Create an auction
      operationId: createAuction
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                description: { type: string }
                vendor_id: { type: integer, nullable: true }
                auction_date: { type: string, format: date }
                auction_end_date: { type: string, format: date }
                platform: { type: string, enum: [hibid, proxibid, auctionzip, custom] }
                external_id: { type: string }
                auction_url: { type: string, format: uri }
                buyers_premium_pct: { type: number, minimum: 0, maximum: 100 }
                location: { type: string }
                timezone: { type: string }
                preview_start: { type: string, format: date-time }
                preview_end: { type: string, format: date-time }
                terms: { type: string }
                status: { type: string, enum: [draft, open] }
      responses:
        "201":
          description: Auction created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Auction" }

  /auctions/{auction}:
    get:
      tags: [Auctions]
      summary: Get auction details
      operationId: getAuction
      parameters:
        - name: auction
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Auction
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Auction" }
    put:
      tags: [Auctions]
      summary: Update an auction
      operationId: updateAuction
      parameters:
        - name: auction
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                description: { type: string }
                vendor_id: { type: integer, nullable: true }
                auction_date: { type: string, format: date }
                auction_end_date: { type: string, format: date }
                platform: { type: string }
                external_id: { type: string }
                auction_url: { type: string, format: uri }
                buyers_premium_pct: { type: number, minimum: 0, maximum: 100 }
                location: { type: string }
                timezone: { type: string }
                preview_start: { type: string, format: date-time }
                preview_end: { type: string, format: date-time }
                terms: { type: string }
                status: { type: string }
      responses:
        "200":
          description: Updated
    delete:
      tags: [Auctions]
      summary: Delete an auction
      operationId: deleteAuction
      parameters:
        - name: auction
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Deleted

  # ── Scanning ─────────────────────────────────
  /scan/lookup:
    post:
      tags: [Scanning]
      summary: Look up a barcode for product pre-fill data
      operationId: lookupBarcode
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [barcode]
              properties:
                barcode: { type: string }
                vendor_id: { type: integer }
                lot_id: { type: integer }
                manifest_id: { type: integer }
      responses:
        "200":
          description: Lookup result
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      found: { type: boolean }
                      source: { type: string, nullable: true }
                      needs_photo: { type: boolean }
                      data:
                        type: object
                        nullable: true
                        properties:
                          title: { type: string }
                          brand: { type: string }
                          description: { type: string }
                          estimated_value: { type: number }
                          images: { type: array, items: { type: string } }

  /scan:
    post:
      tags: [Scanning]
      summary: Scan a barcode and create an auction item
      operationId: scanItem
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                barcode: { type: string }
                lot_id: { type: integer, description: Required unless lot_per_item is true }
                vendor_id: { type: integer, description: Required when lot_per_item is true }
                auction_id: { type: integer }
                lot_per_item: { type: boolean, default: false }
                title: { type: string }
                description: { type: string }
                condition: { type: string }
                quantity: { type: integer }
                starting_bid: { type: number }
                estimated_value: { type: number }
                cost: { type: number }
                location_id: { type: integer, description: Auto put-away to this location }
      responses:
        "201":
          description: Item created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/AuctionItem" }

  /scan/enrich:
    post:
      tags: [Scanning]
      summary: Dispatch AI enrichment job for an item
      operationId: enrichItem
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [item_id]
              properties:
                item_id: { type: integer }
      responses:
        "200":
          description: Enrichment dispatched

  /scan/enrichment-status/{item}:
    get:
      tags: [Scanning]
      summary: Check enrichment status for an item
      operationId: getEnrichmentStatus
      parameters:
        - name: item
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Enrichment status
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      status: { type: string, enum: [pending, completed] }
                      data: { type: object, nullable: true }

  # ── Photo Lookup ─────────────────────────────
  /scan/photo-lookup:
    post:
      tags: [Photo Lookup]
      summary: Upload a photo for AI product identification
      operationId: submitPhotoLookup
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [image]
              properties:
                image: { type: string, format: binary }
                title: { type: string }
      responses:
        "200":
          description: Search submitted
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      search_id: { type: integer, nullable: true }
                      image_url: { type: string }

  /scan/photo-lookup/poll:
    get:
      tags: [Photo Lookup]
      summary: Poll for photo identification results
      operationId: pollPhotoResults
      parameters:
        - name: search_id
          in: query
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Poll result
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      status: { type: string, enum: [pending, completed, error] }
                      data:
                        type: object
                        nullable: true
                        properties:
                          title: { type: string }
                          brand: { type: string }
                          description: { type: string }
                          estimated_value: { type: number }

  /scan/photo-lookup/enrich:
    post:
      tags: [Photo Lookup]
      summary: Dispatch full enrichment from photo results
      operationId: dispatchPhotoEnrichment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [item_id, search_id]
              properties:
                item_id: { type: integer }
                search_id: { type: integer }
      responses:
        "200":
          description: Enrichment queued

  # ── Items ────────────────────────────────────
  /items:
    get:
      tags: [Items]
      summary: List items (paginated) with filters
      operationId: listItems
      parameters:
        - name: lot_id
          in: query
          schema: { type: integer }
        - name: status
          in: query
          schema: { type: string, enum: [scanned, cataloged, put_away, picked, sold] }
        - name: condition
          in: query
          schema: { type: string }
        - name: vendor_id
          in: query
          schema: { type: integer }
        - name: auction_id
          in: query
          schema: { type: integer }
        - name: pallet_id
          in: query
          schema: { type: integer }
        - name: search
          in: query
          schema: { type: string }
        - name: per_page
          in: query
          schema: { type: integer, default: 25 }
        - name: page
          in: query
          schema: { type: integer, default: 1 }
      responses:
        "200":
          description: Paginated items
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/AuctionItem" }
                  meta:
                    type: object
                    properties:
                      current_page: { type: integer }
                      last_page: { type: integer }
                      per_page: { type: integer }
                      total: { type: integer }

  /items/{item}:
    get:
      tags: [Items]
      summary: Get item details
      operationId: getItem
      parameters:
        - name: item
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Item details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/AuctionItem" }
    put:
      tags: [Items]
      summary: Update an item
      operationId: updateItem
      parameters:
        - name: item
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                title: { type: string }
                description: { type: string }
                condition: { type: string }
                condition_notes: { type: string }
                quantity: { type: integer }
                starting_bid: { type: number }
                reserve_price: { type: number }
                buy_now_price: { type: number }
                estimated_value: { type: number }
                cost: { type: number }
                status: { type: string }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/AuctionItem" }
    delete:
      tags: [Items]
      summary: Delete an item
      operationId: deleteItem
      parameters:
        - name: item
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Deleted

  /items/{item}/confirm:
    put:
      tags: [Items]
      summary: Confirm item with final details (marks as cataloged)
      operationId: confirmItem
      parameters:
        - name: item
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title, condition]
              properties:
                title: { type: string }
                condition: { type: string }
                description: { type: string }
                condition_notes: { type: string }
                quantity: { type: integer }
                starting_bid: { type: number }
                reserve_price: { type: number }
                buy_now_price: { type: number }
                estimated_value: { type: number }
                cost: { type: number }
                location_id: { type: integer }
                length: { type: number }
                width: { type: number }
                height: { type: number }
                dimension_unit: { type: string }
                weight: { type: number }
                weight_unit: { type: string }
      responses:
        "200":
          description: Item confirmed
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/AuctionItem" }

  /items/{item}/images:
    post:
      tags: [Items]
      summary: Upload images for an item
      operationId: uploadItemImages
      parameters:
        - name: item
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                images:
                  type: array
                  items: { type: string, format: binary }
                tag: { type: string, default: main }
      responses:
        "200":
          description: Images uploaded

  # ── Put-Away ─────────────────────────────────
  /put-away/item-to-location:
    post:
      tags: [Put-Away]
      summary: Place an item at a warehouse location
      operationId: itemToLocation
      description: Accepts serial number, barcode, or ID for item. Accepts location code, barcode, or ID for location. Auto-creates location if not found.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [item_identifier, location_identifier]
              properties:
                item_identifier: { type: string }
                location_identifier: { type: string }
      responses:
        "200":
          description: Item placed
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/AuctionItem" }
                  message: { type: string }

  /put-away/item-to-pallet:
    post:
      tags: [Put-Away]
      summary: Place an item onto a pallet
      operationId: itemToPallet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [item_identifier, pallet_identifier]
              properties:
                item_identifier: { type: string }
                pallet_identifier: { type: string }
      responses:
        "200":
          description: Item placed on pallet

  /put-away/pallet-to-location:
    post:
      tags: [Put-Away]
      summary: Move a pallet to a warehouse location
      operationId: palletToLocation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pallet_identifier, location_identifier]
              properties:
                pallet_identifier: { type: string }
                location_identifier: { type: string }
      responses:
        "200":
          description: Pallet placed

  /put-away/item-from-pallet/{item}:
    delete:
      tags: [Put-Away]
      summary: Remove an item from its pallet
      operationId: removeItemFromPallet
      parameters:
        - name: item
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Item removed from pallet

  # ── Pallets ──────────────────────────────────
  /pallets:
    post:
      tags: [Pallets]
      summary: Create a new pallet
      operationId: createPallet
      responses:
        "201":
          description: Pallet created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Pallet" }

  /pallets/{identifier}:
    get:
      tags: [Pallets]
      summary: Get pallet by number, barcode, or ID
      operationId: getPallet
      parameters:
        - name: identifier
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Pallet details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Pallet" }

  /pallets/{pallet}/items:
    get:
      tags: [Pallets]
      summary: List items on a pallet
      operationId: getPalletItems
      parameters:
        - name: pallet
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Pallet items

  # ── Locations ────────────────────────────────
  /locations:
    get:
      tags: [Locations]
      summary: List locations with optional filters
      operationId: listLocations
      parameters:
        - name: warehouse_id
          in: query
          schema: { type: integer }
        - name: type
          in: query
          schema: { type: string, enum: [zone, aisle, shelf, bin] }
        - name: search
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Locations
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Location" }

  /locations/{identifier}:
    get:
      tags: [Locations]
      summary: Get location by barcode, code, or ID
      operationId: getLocation
      parameters:
        - name: identifier
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Location
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Location" }

  # ── Labels ───────────────────────────────────
  /labels/serial:
    post:
      tags: [Labels]
      summary: Generate a serial/item label (ZPL or ESC/POS)
      operationId: generateSerialLabel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [auction_item_id]
              properties:
                auction_item_id: { type: integer }
                format: { type: string, enum: [zpl, escpos], default: zpl }
      responses:
        "200":
          description: Label data
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      label_data: { type: string }
                      format: { type: string }

  /labels/shelf:
    post:
      tags: [Labels]
      summary: Generate a shelf/location label
      operationId: generateShelfLabel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [location_id]
              properties:
                location_id: { type: integer }
                format: { type: string, enum: [zpl, escpos], default: zpl }
      responses:
        "200":
          description: Label data

  /labels/pallet:
    post:
      tags: [Labels]
      summary: Generate a pallet label
      operationId: generatePalletLabel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pallet_id]
              properties:
                pallet_id: { type: integer }
                format: { type: string, enum: [zpl, escpos], default: zpl }
      responses:
        "200":
          description: Label data

  /labels/login:
    post:
      tags: [Labels]
      summary: Generate a login barcode badge label
      operationId: generateLoginLabel
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                format: { type: string, enum: [zpl, escpos], default: zpl }
      responses:
        "200":
          description: Label data

  # ── Dimensions ───────────────────────────────
  /dimensions/estimate:
    post:
      tags: [Dimensions]
      summary: Estimate dimensions for a product
      operationId: estimateDimensions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title: { type: string }
                barcode: { type: string }
      responses:
        "200":
          description: Dimension estimates

  # ── Exports ──────────────────────────────────
  /exports:
    post:
      tags: [Exports]
      summary: Generate a CSV export
      operationId: createExport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [export_template_id]
              properties:
                export_template_id: { type: integer }
                lot_id: { type: integer }
                auction_id: { type: integer }
      responses:
        "201":
          description: Export created/queued

  /exports/{export}/download:
    get:
      tags: [Exports]
      summary: Download a completed export file
      operationId: downloadExport
      parameters:
        - name: export
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: CSV file download
          content:
            text/csv:
              schema:
                type: string
                format: binary

  # ── Pick Lists ───────────────────────────────
  /pick-lists:
    get:
      tags: [Pick Lists]
      summary: List pick lists
      operationId: listPickLists
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [pending, in_progress, completed] }
        - name: auction_id
          in: query
          schema: { type: integer }
        - name: assigned_to
          in: query
          schema: { type: string, description: "Use 'me' to filter to current user" }
        - name: exclude_completed
          in: query
          schema: { type: boolean }
      responses:
        "200":
          description: Pick lists
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PickList" }

  /pick-lists/{pickList}:
    get:
      tags: [Pick Lists]
      summary: Get pick list with all items and locations
      operationId: getPickList
      parameters:
        - name: pickList
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Pick list with items
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    allOf:
                      - $ref: "#/components/schemas/PickList"
                      - type: object
                        properties:
                          items:
                            type: array
                            items: { $ref: "#/components/schemas/PickListItem" }

  /pick-lists/{pickList}/claim:
    post:
      tags: [Pick Lists]
      summary: Claim unclaimed items for the current user
      operationId: claimPickItems
      description: Uses row-level locking to prevent concurrent pickers from claiming the same items. Items are sorted by warehouse location for an efficient picking route.
      parameters:
        - name: pickList
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                limit: { type: integer, minimum: 1, maximum: 100, description: Max items to claim }
      responses:
        "200":
          description: Claimed items
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      claimed_count: { type: integer }
                      pick_list_id: { type: integer }
                      pick_list_status: { type: string }
                      items:
                        type: array
                        items: { $ref: "#/components/schemas/PickListItem" }

  /pick-lists/{pickList}/pick:
    post:
      tags: [Pick Lists]
      summary: Mark an item as picked by scanning
      operationId: pickItem
      parameters:
        - name: pickList
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                serial_number: { type: string, description: Serial number or barcode }
                auction_item_id: { type: integer }
      responses:
        "200":
          description: Item picked
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
                  data:
                    type: object
                    properties:
                      pick_list_item_id: { type: integer }
                      auction_item:
                        type: object
                        properties:
                          id: { type: integer }
                          serial_number: { type: string }
                          title: { type: string }
                      picked_at: { type: string, format: date-time }
                      pick_list_progress: { type: number }
                      pick_list_status: { type: string }

  /pick-lists/{pickList}/release:
    post:
      tags: [Pick Lists]
      summary: Release claimed items back to the pool
      operationId: releasePickItems
      parameters:
        - name: pickList
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Items released
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      released_count: { type: integer }

  # ── Analytics ────────────────────────────────
  /analytics/dashboard:
    get:
      tags: [Analytics]
      summary: Overview dashboard with KPIs
      operationId: getDashboard
      parameters:
        - name: date_from
          in: query
          schema: { type: string, format: date }
        - name: date_to
          in: query
          schema: { type: string, format: date }
        - name: warehouse_id
          in: query
          schema: { type: integer }
        - name: team_id
          in: query
          schema: { type: integer }
        - name: auction_id
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: Dashboard data

  /analytics/receiving:
    get:
      tags: [Analytics]
      summary: Receiving analytics (scans by day, user, vendor, warehouse, hour)
      operationId: getReceivingAnalytics
      parameters: &analytics_params
        - name: date_from
          in: query
          schema: { type: string, format: date }
        - name: date_to
          in: query
          schema: { type: string, format: date }
        - name: warehouse_id
          in: query
          schema: { type: integer }
        - name: team_id
          in: query
          schema: { type: integer }
        - name: auction_id
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: Receiving analytics

  /analytics/put-away:
    get:
      tags: [Analytics]
      summary: Put-away analytics
      operationId: getPutAwayAnalytics
      parameters: *analytics_params
      responses:
        "200":
          description: Put-away analytics

  /analytics/picking:
    get:
      tags: [Analytics]
      summary: Picking analytics
      operationId: getPickingAnalytics
      parameters: *analytics_params
      responses:
        "200":
          description: Picking analytics

  /analytics/vendor-performance:
    get:
      tags: [Analytics]
      summary: Vendor performance (volume, sell-through, revenue by vendor)
      operationId: getVendorPerformance
      parameters: *analytics_params
      responses:
        "200":
          description: Vendor performance

  /analytics/inventory-aging:
    get:
      tags: [Analytics]
      summary: Inventory aging (by status, stale items, aging buckets)
      operationId: getInventoryAging
      parameters: *analytics_params
      responses:
        "200":
          description: Inventory aging

  /analytics/revenue:
    get:
      tags: [Analytics]
      summary: Revenue analytics (daily trends, by vendor, cost vs revenue)
      operationId: getRevenue
      parameters: *analytics_params
      responses:
        "200":
          description: Revenue analytics

  /analytics/throughput:
    get:
      tags: [Analytics]
      summary: Throughput analytics (pipeline velocity, timing)
      operationId: getThroughput
      parameters: *analytics_params
      responses:
        "200":
          description: Throughput analytics

  /analytics/user-productivity:
    get:
      tags: [Analytics]
      summary: User productivity (scans, catalogs, picks per user)
      operationId: getUserProductivity
      parameters: *analytics_params
      responses:
        "200":
          description: User productivity
