openapi: 3.1.0
info:
  title: order-mesh API
  version: "1.0.0"
  description: |
    AIや外部システムから order-mesh を操作するための REST API。
    認証は Bearer トークン (`Authorization: Bearer omsh_xxxx`)。
    トークンは社内画面 `/api_keys` から発行する。

servers:
  - url: "{baseUrl}/api/v1"
    description: API server
    variables:
      baseUrl:
        default: https://micross.minorisu.com

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: omsh

  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
            details: { type: object }

    Partner:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        kind:
          type: string
          enum: [customer, producer]
        email: { type: string, nullable: true }
        origin: { type: string, nullable: true }
        phone: { type: string, nullable: true }
        note: { type: string, nullable: true }

    PartnerInput:
      type: object
      required: [name, kind]
      properties:
        name: { type: string }
        kind:
          type: string
          enum: [customer, producer]
        email: { type: string }
        origin: { type: string }
        phone: { type: string }
        note: { type: string }

    Product:
      type: object
      properties:
        id: { type: integer }
        code: { type: string }
        name: { type: string }
        spec: { type: string, nullable: true }
        box_size: { type: string, nullable: true }
        lot_size: { type: integer, nullable: true }
        standard: { type: string, nullable: true }
        producer_partner_id: { type: integer }
        producer_name: { type: string }
        origin_snapshot: { type: string, nullable: true }
        note: { type: string, nullable: true }

    ProductInput:
      type: object
      required: [name, producer_partner_id]
      properties:
        name: { type: string }
        spec: { type: string }
        box_size: { type: string }
        lot_size: { type: integer }
        standard: { type: string }
        producer_partner_id: { type: integer }
        note: { type: string }

    Estimate:
      type: object
      properties:
        id: { type: integer }
        title: { type: string }
        start_date: { type: string, format: date }
        status:
          type: string
          enum: [draft, sent, ordered]
        note: { type: string, nullable: true }
        items:
          type: array
          items: { $ref: "#/components/schemas/EstimateItem" }

    EstimateInput:
      type: object
      required: [title, start_date]
      properties:
        title: { type: string }
        start_date: { type: string, format: date }
        status: { type: string, enum: [draft, sent, ordered] }
        note: { type: string }

    EstimateItem:
      type: object
      properties:
        id: { type: integer }
        product_id: { type: integer }
        product_code: { type: string }
        product_name: { type: string }
        spec: { type: string, nullable: true }
        unit_price: { type: integer }
        lot_size: { type: integer, nullable: true }
        position: { type: integer }
        note: { type: string, nullable: true }
        producer_partner_id: { type: integer, nullable: true }
        producer_name: { type: string, nullable: true }

    EstimateItemInput:
      type: object
      required: [product_id, unit_price]
      properties:
        product_id: { type: integer }
        unit_price: { type: integer }
        lot_size: { type: integer }
        position: { type: integer }
        note: { type: string }

    SupplyList:
      type: object
      properties:
        id: { type: integer }
        producer_partner_id: { type: integer }
        producer_name: { type: string }
        week_start_date: { type: string, format: date }
        status: { type: string, enum: [draft, submitted] }
        note: { type: string, nullable: true }
        total_quantity: { type: integer }
        match_rate: { type: integer }
        items:
          type: array
          items: { $ref: "#/components/schemas/SupplyListItem" }

    SupplyListInput:
      type: object
      required: [producer_partner_id, week_start_date]
      properties:
        producer_partner_id: { type: integer }
        week_start_date: { type: string, format: date }
        status: { type: string, enum: [draft, submitted] }
        note: { type: string }

    SupplyListItem:
      type: object
      properties:
        id: { type: integer }
        raw_name: { type: string, description: "農家からの原文 品名" }
        raw_spec: { type: string, nullable: true }
        raw_unit: { type: string, nullable: true, description: "例: 4kg箱" }
        available_qty: { type: integer }
        unit_price_hint: { type: integer, nullable: true }
        product_id: { type: integer, nullable: true, description: "紐付け済みの場合のみ" }
        product_code: { type: string, nullable: true }
        matched: { type: boolean }
        note: { type: string, nullable: true }

    SupplyListItemInput:
      type: object
      required: [raw_name, available_qty]
      properties:
        raw_name: { type: string }
        raw_spec: { type: string }
        raw_unit: { type: string }
        available_qty: { type: integer }
        unit_price_hint: { type: integer }
        product_id: { type: integer, description: "任意 — 商品マスタへの紐付けは後からでもOK" }
        note: { type: string }

    Order:
      type: object
      properties:
        id: { type: integer }
        status: { type: string }
        submitted: { type: boolean }
        submitted_at: { type: string, format: date-time, nullable: true }
        confirmed: { type: boolean }
        confirmed_at: { type: string, format: date-time, nullable: true }
        partner_id: { type: integer }
        partner_name: { type: string }
        estimate_id: { type: integer }
        estimate_title: { type: string }
        total_quantity: { type: integer }
        items:
          type: array
          items: { $ref: "#/components/schemas/OrderItem" }

    OrderItem:
      type: object
      properties:
        id: { type: integer }
        estimate_item_id: { type: integer }
        product_code: { type: string }
        product_name: { type: string }
        delivery_date: { type: string, format: date }
        quantity: { type: integer }
        unit_price: { type: integer }
        subtotal: { type: integer }

security:
  - BearerAuth: []

paths:
  /partners:
    get:
      summary: List partners
      parameters:
        - name: kind
          in: query
          required: false
          schema: { type: string, enum: [customer, producer] }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  partners:
                    type: array
                    items: { $ref: "#/components/schemas/Partner" }
    post:
      summary: Create partner
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                partner: { $ref: "#/components/schemas/PartnerInput" }
      responses:
        "201":
          description: Created
          content: { application/json: { schema: { type: object, properties: { partner: { $ref: "#/components/schemas/Partner" } } } } }

  /partners/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    get:
      summary: Get partner
      responses:
        "200":
          description: OK
          content: { application/json: { schema: { type: object, properties: { partner: { $ref: "#/components/schemas/Partner" } } } } }
    patch:
      summary: Update partner
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                partner: { $ref: "#/components/schemas/PartnerInput" }
      responses:
        "200": { description: OK }
    delete:
      summary: Delete partner
      responses:
        "204": { description: No Content }

  /products:
    get:
      summary: List products
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  products:
                    type: array
                    items: { $ref: "#/components/schemas/Product" }
    post:
      summary: Create product
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                product: { $ref: "#/components/schemas/ProductInput" }
      responses:
        "201": { description: Created }

  /products/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    get: { summary: Get product, responses: { "200": { description: OK } } }
    patch: { summary: Update product, responses: { "200": { description: OK } } }
    delete: { summary: Delete product, responses: { "204": { description: No Content } } }

  /estimates:
    get:
      summary: List estimates
      responses:
        "200": { description: OK }
    post:
      summary: Create estimate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                estimate: { $ref: "#/components/schemas/EstimateInput" }
      responses:
        "201": { description: Created }

  /estimates/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    get: { summary: Get estimate (with items), responses: { "200": { description: OK } } }
    patch: { summary: Update estimate, responses: { "200": { description: OK } } }
    delete: { summary: Delete estimate, responses: { "204": { description: No Content } } }

  /estimates/{estimate_id}/items:
    parameters:
      - { name: estimate_id, in: path, required: true, schema: { type: integer } }
    get: { summary: List items, responses: { "200": { description: OK } } }
    post:
      summary: Add item
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                item: { $ref: "#/components/schemas/EstimateItemInput" }
      responses:
        "201": { description: Created }

  /estimates/{estimate_id}/items/{id}:
    parameters:
      - { name: estimate_id, in: path, required: true, schema: { type: integer } }
      - { name: id, in: path, required: true, schema: { type: integer } }
    patch: { summary: Update item, responses: { "200": { description: OK } } }
    delete: { summary: Delete item, responses: { "204": { description: No Content } } }

  /orders:
    get:
      summary: List orders
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [submitted, confirmed] }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  orders:
                    type: array
                    items: { $ref: "#/components/schemas/Order" }

  /orders/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    get: { summary: Get order (with items), responses: { "200": { description: OK } } }

  /orders/{id}/confirm:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    post:
      summary: Confirm a submitted order
      responses:
        "200": { description: OK }
        "422": { description: Order not in submitted state }

  /orders/{id}/unconfirm:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    post:
      summary: Revert a confirmed order back to submitted
      responses:
        "200": { description: OK }

  /supply_lists:
    get:
      summary: List supply lists (仕入可能リスト)
      parameters:
        - name: producer_partner_id
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  supply_lists:
                    type: array
                    items: { $ref: "#/components/schemas/SupplyList" }
    post:
      summary: Create supply list
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                supply_list: { $ref: "#/components/schemas/SupplyListInput" }
      responses:
        "201": { description: Created }

  /supply_lists/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    get: { summary: Get supply list (with items), responses: { "200": { description: OK } } }
    patch: { summary: Update supply list, responses: { "200": { description: OK } } }
    delete: { summary: Delete supply list, responses: { "204": { description: No Content } } }

  /supply_lists/{supply_list_id}/items:
    parameters:
      - { name: supply_list_id, in: path, required: true, schema: { type: integer } }
    get: { summary: List items, responses: { "200": { description: OK } } }
    post:
      summary: Add item (raw_name 必須、product_id は任意)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                item: { $ref: "#/components/schemas/SupplyListItemInput" }
      responses:
        "201": { description: Created }

  /supply_lists/{supply_list_id}/items/{id}:
    parameters:
      - { name: supply_list_id, in: path, required: true, schema: { type: integer } }
      - { name: id, in: path, required: true, schema: { type: integer } }
    patch: { summary: Update item, responses: { "200": { description: OK } } }
    delete: { summary: Delete item, responses: { "204": { description: No Content } } }

  /supply_lists/{supply_list_id}/items/{id}/match:
    parameters:
      - { name: supply_list_id, in: path, required: true, schema: { type: integer } }
      - { name: id, in: path, required: true, schema: { type: integer } }
    patch:
      summary: Match item to a Product
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                product_id: { type: integer }
      responses:
        "200": { description: OK }

  /supply_lists/{supply_list_id}/items/{id}/unmatch:
    parameters:
      - { name: supply_list_id, in: path, required: true, schema: { type: integer } }
      - { name: id, in: path, required: true, schema: { type: integer } }
    patch:
      summary: Remove the match (sets product_id to null)
      responses:
        "200": { description: OK }

  /supply_bundles:
    get:
      summary: List supply bundles
      responses:
        "200": { description: OK }
    post:
      summary: Create a supply bundle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [supply_bundle]
              properties:
                supply_bundle:
                  type: object
                  required: [name, box_qty]
                  properties:
                    name:    { type: string }
                    box_qty: { type: integer, minimum: 1 }
                    note:    { type: string }
                member_product_ids:
                  type: array
                  items: { type: integer }
      responses:
        "201": { description: Created }

  /supply_bundles/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    get:    { summary: Get supply bundle, responses: { "200": { description: OK } } }
    patch:  { summary: Update supply bundle, responses: { "200": { description: OK } } }
    delete: { summary: Delete supply bundle, responses: { "204": { description: No Content }, "422": { description: In use } } }

  /supply_bundles/{id}/add_member:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    post:
      summary: Add a member product to the bundle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [product_id]
              properties:
                product_id: { type: integer }
                position:   { type: integer }
      responses:
        "200": { description: OK }

  /supply_bundles/{id}/remove_member:
    parameters:
      - { name: id, in: path, required: true, schema: { type: integer } }
    delete:
      summary: Remove a member product from the bundle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [product_id]
              properties:
                product_id: { type: integer }
      responses:
        "200": { description: OK }
