{
  "feature": "self-order-kiosk",
  "version": "1.0.0",
  "description": "Customer self-ordering system supporting kiosk terminals and mobile QR-code ordering, with menu browsing, cart management, payment processing, and real-time order status updates.\n",
  "category": "ui",
  "tags": [
    "self-service",
    "kiosk",
    "qr-code",
    "mobile-ordering",
    "restaurant"
  ],
  "actors": [
    {
      "id": "customer",
      "name": "Customer",
      "type": "human",
      "description": "Browses menu, places order, and pays via kiosk or mobile device"
    },
    {
      "id": "kitchen",
      "name": "Kitchen Staff",
      "type": "human",
      "description": "Receives and prepares ordered items"
    },
    {
      "id": "pos_operator",
      "name": "POS Operator",
      "type": "human",
      "description": "Manages configuration, monitors orders, marks items ready"
    },
    {
      "id": "system",
      "name": "Order System",
      "type": "system",
      "description": "Validates orders, processes payments, sends notifications"
    }
  ],
  "fields": [
    {
      "name": "ordering_mode",
      "type": "select",
      "label": "Ordering Mode",
      "required": true,
      "options": [
        {
          "value": "mobile",
          "label": "Mobile"
        },
        {
          "value": "kiosk",
          "label": "Kiosk"
        },
        {
          "value": "consultation",
          "label": "Consultation"
        }
      ]
    },
    {
      "name": "service_mode",
      "type": "select",
      "label": "Service Mode",
      "required": true,
      "options": [
        {
          "value": "counter",
          "label": "Counter"
        },
        {
          "value": "table",
          "label": "Table"
        }
      ]
    },
    {
      "name": "pay_after",
      "type": "select",
      "label": "Payment Timing",
      "required": true,
      "options": [
        {
          "value": "each",
          "label": "Each"
        },
        {
          "value": "meal",
          "label": "Meal"
        }
      ]
    },
    {
      "name": "table_identifier",
      "type": "text",
      "label": "Table Identifier",
      "required": false
    },
    {
      "name": "order_source",
      "type": "select",
      "label": "Order Source",
      "required": true,
      "options": [
        {
          "value": "mobile",
          "label": "Mobile"
        },
        {
          "value": "kiosk",
          "label": "Kiosk"
        }
      ]
    },
    {
      "name": "customer_name",
      "type": "text",
      "label": "Customer Name",
      "required": false
    },
    {
      "name": "customer_email",
      "type": "email",
      "label": "Customer Email",
      "required": false
    },
    {
      "name": "customer_phone",
      "type": "phone",
      "label": "Customer Phone",
      "required": false
    },
    {
      "name": "selected_language",
      "type": "select",
      "label": "Display Language",
      "required": false
    },
    {
      "name": "cart_items",
      "type": "json",
      "label": "Cart Items",
      "required": true
    },
    {
      "name": "cart_total",
      "type": "number",
      "label": "Cart Total",
      "required": true
    },
    {
      "name": "customer_note",
      "type": "text",
      "label": "Special Instructions",
      "required": false
    },
    {
      "name": "preset_type",
      "type": "select",
      "label": "Service Preset",
      "required": false,
      "options": [
        {
          "value": "dine_in",
          "label": "Dine In"
        },
        {
          "value": "takeaway",
          "label": "Takeaway"
        },
        {
          "value": "delivery",
          "label": "Delivery"
        }
      ]
    },
    {
      "name": "access_token",
      "type": "token",
      "label": "Access Token",
      "required": true
    },
    {
      "name": "order_access_token",
      "type": "token",
      "label": "Order Access Token",
      "required": true
    }
  ],
  "states": {
    "field": "order_status",
    "values": [
      {
        "name": "browsing",
        "description": "Customer is viewing the menu",
        "initial": true
      },
      {
        "name": "cart_review",
        "description": "Customer is reviewing items in cart"
      },
      {
        "name": "payment_pending",
        "description": "Customer has submitted order and payment is being processed"
      },
      {
        "name": "confirmed",
        "description": "Order confirmed and sent to kitchen"
      },
      {
        "name": "preparing",
        "description": "Kitchen is preparing the order"
      },
      {
        "name": "ready",
        "description": "Order is ready for pickup or delivery to table",
        "terminal": true
      },
      {
        "name": "cancelled",
        "description": "Customer cancelled the order before payment",
        "terminal": true
      }
    ],
    "transitions": [
      {
        "from": "browsing",
        "to": "cart_review",
        "actor": "customer",
        "description": "Customer adds first item to cart"
      },
      {
        "from": "cart_review",
        "to": "browsing",
        "actor": "customer",
        "description": "Customer continues browsing to add more items"
      },
      {
        "from": "cart_review",
        "to": "payment_pending",
        "actor": "customer",
        "description": "Customer submits order for payment"
      },
      {
        "from": "payment_pending",
        "to": "confirmed",
        "actor": "system",
        "description": "Payment processed successfully"
      },
      {
        "from": "confirmed",
        "to": "preparing",
        "actor": "kitchen",
        "description": "Kitchen acknowledges the order"
      },
      {
        "from": "preparing",
        "to": "ready",
        "actor": "kitchen",
        "description": "Order preparation complete"
      },
      {
        "from": "cart_review",
        "to": "cancelled",
        "actor": "customer",
        "description": "Customer abandons the order",
        "condition": "Order must still be in draft (unpaid) state"
      }
    ]
  },
  "rules": {
    "kiosk_no_cash": {
      "description": "Kiosk mode only allows card and digital payment methods — no cash"
    },
    "mobile_table_requires_identifier": {
      "description": "Mobile ordering with table service requires a valid table identifier from QR code"
    },
    "pay_each_for_counter": {
      "description": "Counter service mode forces immediate payment per order"
    },
    "pay_meal_requires_table": {
      "description": "Pay-after-meal option only available with table service mode"
    },
    "category_operating_hours": {
      "description": "Product categories can have operating hour restrictions (e.g., lunch menu 11:00-14:00). Products outside their category's hours are hidden.\n"
    },
    "overnight_hours_support": {
      "description": "Operating hours where start > end (e.g., 22:00-02:00) are treated as overnight and span across midnight.\n"
    },
    "product_must_be_self_order_enabled": {
      "description": "Only products explicitly marked as available for self-ordering are shown"
    },
    "server_side_price_recomputation": {
      "description": "All prices are recomputed server-side on order submission — frontend prices are display-only and cannot be manipulated.\n"
    },
    "active_session_required": {
      "description": "Self-ordering is only available when a POS session is active"
    },
    "constant_time_token_check": {
      "description": "Access tokens are compared using constant-time comparison to prevent timing attacks"
    },
    "kiosk_inactivity_timeout": {
      "description": "Kiosk resets to landing page after 30 seconds of inactivity on confirmation screen"
    }
  },
  "outcomes": {
    "mobile_order_placed": {
      "priority": 1,
      "given": [
        "customer scans table QR code containing table identifier and access token",
        "an active POS session exists",
        "customer selects products from the available menu"
      ],
      "then": [
        {
          "action": "create_record",
          "type": "order",
          "target": "order",
          "description": "Order created in draft state linked to the table"
        },
        {
          "action": "emit_event",
          "event": "self_order.order.submitted",
          "payload": [
            "order_id",
            "table_id",
            "order_source",
            "cart_items"
          ]
        },
        {
          "action": "notify",
          "channel": "pos_terminal",
          "description": "POS operator notified of new order"
        }
      ],
      "result": "Order appears on POS terminal and is sent to kitchen",
      "error": "SELF_ORDER_ORDER_NOT_FOUND"
    },
    "kiosk_order_with_payment": {
      "priority": 2,
      "given": [
        "customer uses kiosk terminal to select products",
        "customer initiates payment via card or digital method",
        "payment is processed successfully"
      ],
      "then": [
        {
          "action": "transition_state",
          "field": "order_status",
          "from": "payment_pending",
          "to": "confirmed"
        },
        {
          "action": "emit_event",
          "event": "self_order.payment.completed",
          "payload": [
            "order_id",
            "payment_method",
            "amount"
          ]
        },
        {
          "action": "notify",
          "channel": "kitchen",
          "description": "Kitchen receives the confirmed order"
        }
      ],
      "result": "Order confirmed, receipt printed, kiosk resets after timeout"
    },
    "item_unavailable_during_browse": {
      "priority": 3,
      "given": [
        "customer has items in cart",
        "one or more items become unavailable (out of stock or outside operating hours)"
      ],
      "then": [
        {
          "action": "notify",
          "channel": "customer_device",
          "description": "Show dialog listing removed items"
        },
        {
          "action": "set_field",
          "target": "cart_items",
          "description": "Unavailable items removed from cart"
        }
      ],
      "result": "Customer sees which items were removed and can adjust their order"
    },
    "order_cancelled_by_customer": {
      "priority": 4,
      "given": [
        "customer decides to cancel before payment",
        "order is still in draft state"
      ],
      "then": [
        {
          "action": "transition_state",
          "field": "order_status",
          "from": "cart_review",
          "to": "cancelled"
        },
        {
          "action": "emit_event",
          "event": "self_order.order.cancelled",
          "payload": [
            "order_id",
            "order_source"
          ]
        }
      ],
      "result": "Order removed, customer returns to landing page"
    },
    "session_closed_during_ordering": {
      "priority": 1,
      "error": "SELF_ORDER_SESSION_CLOSED",
      "given": [
        "POS operator closes the session while customers are ordering"
      ],
      "then": [
        {
          "action": "notify",
          "channel": "customer_device",
          "description": "Display closed message, disable ordering"
        }
      ],
      "result": "Customer cannot submit orders until a new session opens"
    },
    "invalid_access_token": {
      "priority": 1,
      "error": "SELF_ORDER_UNAUTHORIZED",
      "given": [
        "request contains an invalid or expired access token"
      ],
      "then": [
        {
          "action": "notify",
          "channel": "customer_device",
          "description": "Generic unauthorized error displayed"
        }
      ],
      "result": "Request rejected, no order data exposed"
    },
    "payment_failed": {
      "priority": 2,
      "error": "SELF_ORDER_PAYMENT_FAILED",
      "given": [
        "customer initiates payment on kiosk",
        "payment provider returns failure"
      ],
      "then": [
        {
          "action": "notify",
          "channel": "customer_device",
          "description": "Payment error shown with retry option"
        }
      ],
      "result": "Customer can retry payment or cancel the order"
    }
  },
  "errors": [
    {
      "code": "SELF_ORDER_SESSION_CLOSED",
      "message": "We're currently closed. Please try again later.",
      "status": 500
    },
    {
      "code": "SELF_ORDER_UNAUTHORIZED",
      "message": "Unauthorized access. Please scan the QR code again.",
      "status": 401
    },
    {
      "code": "SELF_ORDER_PAYMENT_FAILED",
      "message": "Payment could not be processed. Please try again or use a different method.",
      "status": 400
    },
    {
      "code": "SELF_ORDER_TABLE_NOT_FOUND",
      "message": "Table not found. Please scan the QR code at your table.",
      "status": 404
    },
    {
      "code": "SELF_ORDER_ORDER_NOT_FOUND",
      "message": "Your order does not exist or has been removed.",
      "status": 404
    },
    {
      "code": "SELF_ORDER_PRODUCT_UNAVAILABLE",
      "message": "One or more selected products are no longer available.",
      "status": 400
    }
  ],
  "events": [
    {
      "name": "self_order.order.submitted",
      "description": "Customer submitted an order from mobile or kiosk",
      "payload": [
        "order_id",
        "table_id",
        "order_source",
        "cart_items"
      ]
    },
    {
      "name": "self_order.payment.completed",
      "description": "Payment successfully processed for a self-order",
      "payload": [
        "order_id",
        "payment_method",
        "amount"
      ]
    },
    {
      "name": "self_order.order.cancelled",
      "description": "Customer cancelled their self-order",
      "payload": [
        "order_id",
        "order_source"
      ]
    },
    {
      "name": "self_order.order.ready",
      "description": "Kitchen marks the order as ready for pickup/delivery",
      "payload": [
        "order_id",
        "table_id"
      ]
    },
    {
      "name": "self_order.product.unavailable",
      "description": "A product became unavailable while customers are browsing",
      "payload": [
        "product_id",
        "reason"
      ]
    },
    {
      "name": "self_order.session.status_changed",
      "description": "POS session status changed (opened/closed) affecting self-ordering availability",
      "payload": [
        "session_id",
        "new_status"
      ]
    }
  ],
  "related": [
    {
      "feature": "pos-core",
      "type": "required",
      "reason": "Self-orders create POS orders within an active session"
    },
    {
      "feature": "loyalty-coupons",
      "type": "optional",
      "reason": "Customers can apply loyalty rewards during self-ordering"
    }
  ],
  "agi": {
    "goals": [
      {
        "id": "reliable_self_order_kiosk",
        "description": "Customer self-ordering system supporting kiosk terminals and mobile QR-code ordering, with menu browsing, cart management, payment processing, and real-time order status updates.\n",
        "success_metrics": [
          {
            "metric": "success_rate",
            "target": ">= 99%",
            "measurement": "Successful operations divided by total attempts"
          },
          {
            "metric": "error_rate",
            "target": "< 1%",
            "measurement": "Failed operations divided by total attempts"
          }
        ],
        "constraints": [
          {
            "type": "security",
            "description": "Sensitive fields must be encrypted at rest and never logged in plaintext",
            "negotiable": false
          }
        ]
      }
    ],
    "autonomy": {
      "level": "semi_autonomous",
      "human_checkpoints": [
        "before modifying sensitive data fields",
        "before transitioning to a terminal state"
      ],
      "escalation_triggers": [
        "error_rate > 5"
      ]
    },
    "safety": {
      "action_permissions": [
        {
          "action": "mobile_order_placed",
          "permission": "autonomous"
        },
        {
          "action": "kiosk_order_with_payment",
          "permission": "autonomous"
        },
        {
          "action": "item_unavailable_during_browse",
          "permission": "autonomous"
        },
        {
          "action": "order_cancelled_by_customer",
          "permission": "supervised"
        },
        {
          "action": "session_closed_during_ordering",
          "permission": "autonomous"
        },
        {
          "action": "invalid_access_token",
          "permission": "autonomous"
        },
        {
          "action": "payment_failed",
          "permission": "autonomous"
        }
      ]
    },
    "tradeoffs": [
      {
        "prefer": "accessibility",
        "over": "aesthetics",
        "reason": "UI must be usable by all users including those with disabilities"
      }
    ],
    "verification": {
      "invariants": [
        "sensitive fields are never logged in plaintext",
        "all data access is authenticated and authorized",
        "error messages never expose internal system details",
        "state transitions follow the defined state machine — no illegal transitions"
      ]
    },
    "coordination": {
      "protocol": "orchestrated",
      "consumes": [
        {
          "capability": "pos_core",
          "from": "pos-core",
          "fallback": "degrade"
        }
      ]
    }
  },
  "extensions": {
    "source": {
      "repo": "https://github.com/odoo/odoo.git",
      "project": "Odoo",
      "tech_stack": "Python + JavaScript/OWL",
      "files_traced": 30,
      "entry_points": [
        "addons/pos_self_order/controllers/orders.py",
        "addons/pos_self_order/models/pos_config.py",
        "addons/pos_self_order/models/pos_order.py"
      ]
    }
  }
}