# seeds/orders/domain.yaml
#
# Order Management System — ADL Domain Definition
# ADL version: 0.3.1
#
# Resources:  Customer, Product, Order, OrderItem, Discount
# Relations:  Order belongsTo Customer
#             Order hasMany OrderItem (cascade delete)
#             OrderItem belongsTo Order
#             OrderItem belongsTo Product
#             Customer hasMany Order
#
# ADL features exercised:
#   Phase 1      — CRUD generation, REST route mapping, RBAC permissions
#   Phase 6      — hasMany, belongsTo, ?include=
#   Phase 7      — filter, sort, paginate, search, count
#   Phase 8      — Rich types: decimal, money, email, phone, datetime, uuid
#   Phase 9      — $merge (Timestamp), $ref enums/objects
#   Phase 10     — UUID primary keys, composite primary key (OrderItem)
#   Phase 12     — Audit trail on Order
#   Phase 14     — Field-level permissions, owner checks
#
# New features (first exercised in POC-03):
#   component    — Shipping address inline on Order
#   repeater     — Line items stored inline on Order
#   computed     — Virtual (Customer.fullName), materialized (OrderItem.subtotal),
#                  aggregation (Order.orderTotal)
#   versioning   — Full version history on Order
#   export       — Bulk CSV export of Orders and Products
#   import       — Bulk product catalog import
#   caching      — TTL read cache on Product
#   custom ops   — POST /orders/:id/duplicate, POST /orders/:id/apply-discount
#   compositePK  — OrderItem keyed by (orderId, productId)

domain:
  name: orders
  version: "1.0.0"
  description: >
    Order management system demonstrating advanced ADL features: decimal/money
    types, component objects, repeaters, computed fields (virtual, materialized,
    aggregation), composite primary keys, versioning, bulk import/export,
    caching, and custom operations.

  api: {}

  # ─── Shared objects ────────────────────────────────────────────
  objects:
    Timestamp:
      createdAt: { type: datetime, autoNow: create }
      updatedAt: { type: datetime, autoNow: update }

    Address:
      street:     { type: string, required: true, maxLength: 255 }
      city:       { type: string, required: true, maxLength: 100 }
      state:      { type: string, maxLength: 100 }
      postalCode: { type: string, maxLength: 20 }
      country:    { type: country, default: "US" }

  # ─── Shared enums ──────────────────────────────────────────────
  enums:
    OrderStatus:
      values:
        pending:
          label: "Pending"
          description: "Order placed, awaiting confirmation"
          color: "#f59e0b"
        confirmed:
          label: "Confirmed"
          description: "Order confirmed, preparing for processing"
          color: "#3b82f6"
        processing:
          label: "Processing"
          description: "Order is being prepared and packed"
          color: "#8b5cf6"
        shipped:
          label: "Shipped"
          description: "Order has been shipped"
          color: "#10b981"
        delivered:
          label: "Delivered"
          description: "Order successfully delivered"
          color: "#22c55e"
        cancelled:
          label: "Cancelled"
          description: "Order was cancelled"
          color: "#ef4444"
        refunded:
          label: "Refunded"
          description: "Order was refunded"
          color: "#6b7280"

    DiscountType:
      values:
        percentage:
          label: "Percentage"
          description: "Discount as a percentage of the total"
        fixed:
          label: "Fixed"
          description: "Fixed amount discount"

  # ─── Resources ─────────────────────────────────────────────────
  resources:

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  Customer                                                    ║
    # ╚══════════════════════════════════════════════════════════════╝
    Customer:
      description: >
        Buyer profile. Demonstrates the virtual computed field feature:
        fullName is calculated on read from firstName + lastName and is
        never stored in the database.

      object:
        id:
          type: uuid
          primaryKey: true

        firstName:
          type: string
          required: true
          maxLength: 100

        lastName:
          type: string
          required: true
          maxLength: 100

        email:
          type: email
          required: true
          unique: true

        phone:
          type: phone

        $merge:
          - { $ref: '#/objects/Timestamp' }

      computed:
        fullName:
          type: string
          formula: "concat(firstName, ' ', lastName)"
          stored: false
          description: "Virtual field — calculated on read, not stored in DB"

      relationships:
        orders:
          type: hasMany
          target: Order
          foreignKey: customerId

      indexes:
        - fields: [email]
          description: "Unique email lookup"
        - fields: [lastName, firstName]
          description: "Sort customers by name"

      features:
        search: true

      permissions:
        list:   [customer, admin, staff]
        read:   [customer, admin, staff]
        create: [authenticated]
        update: [customer, admin, staff]
        delete: [admin]

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  Product                                                     ║
    # ╚══════════════════════════════════════════════════════════════╝
    Product:
      description: >
        Catalog item with pricing. Demonstrates caching (read-heavy
        reference data with TTL), bulk import, and export features.

      object:
        id:
          type: uuid
          primaryKey: true

        sku:
          type: string
          required: true
          unique: true
          maxLength: 50

        name:
          type: string
          required: true
          maxLength: 200

        description:
          type: text

        unitPrice:
          type: decimal
          precision: 19
          scale: 4
          required: true
          min: 0

        stockQty:
          type: integer
          default: 0

        isActive:
          type: boolean
          default: true

        $merge:
          - { $ref: '#/objects/Timestamp' }

      indexes:
        - fields: [sku]
          description: "Unique SKU lookup"
        - fields: [isActive]
          description: "Filter active/inactive products"
        - fields: [unitPrice]
          description: "Sort and range-query by price"

      features:
        search: true
        caching: true
        cacheTtlSeconds: 300
        import: true
        export: true

      permissions:
        list:   ["*"]
        read:   ["*"]
        create: [admin, staff]
        update: [admin, staff]
        delete: [admin]

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  Order                                                       ║
    # ╚══════════════════════════════════════════════════════════════╝
    Order:
      description: >
        The primary resource. Demonstrates component (shippingAddress),
        repeater (lineItems), computed aggregation (orderTotal), versioning,
        audit, export, and custom operations.

      object:
        id:
          type: uuid
          primaryKey: true

        customerId:
          type: uuid
          required: true
          immutable: true

        customerName:
          type: string
          maxLength: 200
          description: "Denormalized customer name for display"

        status:
          $ref: '#/enums/OrderStatus'
          default: pending

        notes:
          type: text

        # Component — stored inline as JSON column, no separate table
        shippingAddress:
          type: component
          object: { $ref: '#/objects/Address' }

        # Repeater — ordered array stored as JSON column
        lineItems:
          type: repeater
          sortable: false
          object:
            productId:   { type: uuid, required: true }
            productName: { type: string, required: true }
            quantity:    { type: integer, required: true, min: 1 }
            unitPrice:   { type: decimal, precision: 19, scale: 4, required: true }

        discountCode:
          type: string
          maxLength: 50

        discountAmount:
          type: decimal
          precision: 19
          scale: 4
          default: "0"

        $merge:
          - { $ref: '#/objects/Timestamp' }

      computed:
        orderTotal:
          type: decimal
          formula: "sum(orderItems.subtotal)"
          stored: true
          updateOn: [orderItems.create, orderItems.update, orderItems.delete]
          description: "Aggregation — sum of all order item subtotals"

      relationships:
        customer:
          type: belongsTo
          target: Customer
          foreignKey: customerId

        orderItems:
          type: hasMany
          target: OrderItem
          foreignKey: orderId
          onDelete: cascade

      indexes:
        - fields: [customerId]
          description: "List orders by customer"
        - fields: [status]
          description: "Filter orders by status"
        - fields: [createdAt]
          description: "Sort orders by date"
        - fields: [customerId, status]
          description: "Composite: customer orders by status"

      features:
        versioning: true
        audit: true
        export: true

      audit:
        track: [status, discountCode, discountAmount, notes, shippingAddress]
        exclude: [updatedAt]

      # validation:
      #   discountNotExceedTotal:
      #     rule: "discountAmount <= (orderTotal ?? 0)"
      #     message: "Discount cannot exceed order total"

      # ─── Custom operations ──────────────────────────────────────
      operations:
        duplicate:
          method: POST
          path: "/:id/duplicate"
          guards: [customer, admin, staff]
          description: >
            Creates a new Order copying all fields from the source order.
            New ID generated, status reset to pending, timestamps reset.

        applyDiscount:
          method: POST
          path: "/:id/apply-discount"
          guards: [customer, admin, staff]
          payload:
            code: { type: string, required: true }
          description: >
            Validates the discount code exists, is active, not expired,
            not exceeded max uses. Applies discountAmount to the order
            and increments discount.usedCount.

      permissions:
        list:   [customer, admin, staff]
        read:   [customer, admin, staff]
        create: [authenticated]
        update: [customer, admin, staff]
        delete: [admin]

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  OrderItem                                                   ║
    # ╚══════════════════════════════════════════════════════════════╝
    OrderItem:
      description: >
        A line item in an order. Demonstrates composite primary key
        (orderId + productId) and materialized computed field (subtotal
        is recalculated and stored whenever quantity or unitPrice changes).

      object:
        orderId:
          type: uuid
          compositePkOrder: 1
          immutable: true

        productId:
          type: uuid
          compositePkOrder: 2
          immutable: true

        quantity:
          type: integer
          required: true
          min: 1

        unitPrice:
          type: decimal
          precision: 19
          scale: 4
          required: true

        $merge:
          - { $ref: '#/objects/Timestamp' }

      compositePrimaryKey: [orderId, productId]

      computed:
        subtotal:
          type: decimal
          formula: "quantity * unitPrice"
          stored: true
          updateOn: [quantity, unitPrice]
          description: "Materialized — recalculated on write when quantity or unitPrice changes"

      relationships:
        order:
          type: belongsTo
          target: Order
          foreignKey: orderId
          onDelete: cascade

        product:
          type: belongsTo
          target: Product
          foreignKey: productId

      indexes:
        - fields: [orderId]
          description: "List all items for an order"
        - fields: [productId]
          description: "Find all orders containing a product"

      permissions:
        list:   [customer, admin, staff]
        read:   [customer, admin, staff]
        create: [customer, admin, staff]
        update: [customer, admin, staff]
        delete: [customer, admin, staff]

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  Discount                                                    ║
    # ╚══════════════════════════════════════════════════════════════╝
    Discount:
      description: >
        Coupon or discount code that can be applied to orders via the
        apply-discount custom operation.

      object:
        id:
          type: uuid
          primaryKey: true

        code:
          type: string
          required: true
          unique: true
          uppercase: true
          maxLength: 50

        type:
          $ref: '#/enums/DiscountType'
          required: true

        value:
          type: decimal
          precision: 19
          scale: 4
          required: true
          min: 0

        maxUses:
          type: integer
          description: "Null means unlimited uses"

        usedCount:
          type: integer
          default: 0
          immutable: true
          description: "Incremented by the apply-discount custom operation"

        expiresAt:
          type: datetime

        isActive:
          type: boolean
          default: true

        $merge:
          - { $ref: '#/objects/Timestamp' }

      indexes:
        - fields: [code]
          description: "Unique code lookup"
        - fields: [isActive]
          description: "Filter active/inactive discounts"

      permissions:
        list:   [admin, staff]
        read:   [admin, staff]
        create: [admin]
        update: [admin]
        delete: [admin]
