{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://groceryai.ai/schemas/regole.schema.json",
  "title": "Le regole del negozio (regole.json)",
  "description": "Il documento di PUT /v1/rules e della pagina «Regole» del portale (corsia DATA-C4, contratto del portale §3.18 e §3.18-bis). Ogni campo e' una regola che l'assistente applica gia': nessun campo libero, nessun testo per il cliente. min_order_amount, payment_methods e zones sono obbligatori; le regole commerciali sono facoltative (assenti = restano come sono). Gli importi sono stringhe decimali nella valuta del negozio.",
  "type": "object",
  "additionalProperties": false,
  "required": ["based_on_version", "min_order_amount", "payment_methods", "zones"],
  "properties": {
    "$schema": { "type": "string" },
    "based_on_version": { "type": "integer", "minimum": 0, "description": "La versione da cui sei partito (la `version` di GET /v1/rules; 0 se il negozio non ne ha)." },
    "min_order_amount": { "type": ["string", "null"], "pattern": "^\\d{1,5}([.,]\\d{1,2})?$", "description": "L'ordine minimo; null o \"0.00\" = nessun minimo." },
    "payment_methods": {
      "type": "array", "minItems": 1, "uniqueItems": true,
      "items": { "enum": ["cash", "card_pos", "prepaid_link", "bank_transfer"] }
    },
    "zones": {
      "type": "array", "maxItems": 50,
      "items": {
        "type": "object", "additionalProperties": false, "required": ["zone_slug"],
        "properties": {
          "zone_slug": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,62}$" },
          "delivery_cost": { "type": ["string", "null"], "pattern": "^\\d{1,5}([.,]\\d{1,2})?$" },
          "free_threshold": { "type": ["string", "null"], "pattern": "^\\d{1,5}([.,]\\d{1,2})?$", "description": "Mai sotto delivery_cost (SOGLIA_SOTTO_IL_COSTO)." }
        }
      }
    },
    "bank_transfer": {
      "description": "Le coordinate del bonifico: servono se payment_methods contiene bank_transfer (COORDINATE_BANCARIE_ASSENTI).",
      "oneOf": [
        { "type": "null" },
        {
          "type": "object", "additionalProperties": false, "required": ["holder", "iban"],
          "properties": {
            "holder": { "type": "string", "minLength": 1, "maxLength": 70 },
            "iban": { "type": "string", "description": "IBAN con la cifra di controllo ISO 13616 (IBAN_NON_VALIDO); spazi e minuscole ammessi." },
            "bic": { "type": ["string", "null"], "description": "8 o 11 caratteri (BIC_NON_VALIDO)." },
            "institution": { "type": ["string", "null"], "maxLength": 70 }
          }
        }
      ]
    },
    "prepaid_flow": { "enum": ["operator_link", "none"], "description": "Il pagamento anticipato: gli operatori mandano il link, o nessuno." },
    "cash_requires_own_delivery": { "type": "boolean", "description": "I contanti solo quando la spesa arriva coi mezzi del negozio (di serie vero)." },
    "discounts": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object", "additionalProperties": false,
          "properties": {
            "welcome_percent": { "type": "integer", "minimum": 0, "maximum": 50, "description": "Il cliente nuovo (nessun ordine)." },
            "comeback_percent": { "type": "integer", "minimum": 0, "maximum": 50, "description": "Il cliente che torna dopo comeback_after_days." },
            "comeback_after_days": { "type": ["integer", "null"], "minimum": 1, "maximum": 365, "description": "Obbligatorio se comeback_percent > 0." },
            "loyalty": {
              "type": "array", "maxItems": 5, "description": "I gradini di fedelta', dal piu' stretto al piu' largo (giorni crescenti: GRADINI_NON_IN_ORDINE).",
              "items": {
                "type": "object", "additionalProperties": false, "required": ["percent", "within_days"],
                "properties": { "percent": { "type": "integer", "minimum": 1, "maximum": 50 }, "within_days": { "type": "integer", "minimum": 1, "maximum": 365 } }
              }
            },
            "exclude_offers": { "type": "boolean", "description": "Lo sconto non vale sui prodotti in offerta (di serie vero)." }
          }
        }
      ]
    },
    "friend_codes": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object", "additionalProperties": false, "required": ["codes", "percent"],
          "properties": {
            "codes": { "type": "array", "minItems": 1, "maxItems": 20, "items": { "type": "string", "pattern": "^[\\p{L}\\p{N}]{3,40}$" }, "description": "Si scrivono in maiuscolo; il cliente li scrive come vuole." },
            "percent": { "type": "integer", "minimum": 1, "maximum": 50 }
          }
        }
      ]
    },
    "heavy_extras": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object", "additionalProperties": false, "required": ["rate_per_unit"],
          "description": "Almeno un termine o un peso minimo (EXTRA_SENZA_CRITERIO).",
          "properties": {
            "title_terms": {
              "type": "array", "maxItems": 30,
              "items": {
                "type": "object", "additionalProperties": false, "required": ["term"],
                "properties": { "term": { "type": "string", "minLength": 2, "maxLength": 40 }, "whole_word": { "type": "boolean" } }
              }
            },
            "min_weight_kg": { "type": ["number", "null"], "exclusiveMinimum": 0, "maximum": 100 },
            "match": { "enum": ["any", "all"] },
            "free_units": { "type": "integer", "minimum": 0, "maximum": 100 },
            "rate_per_unit": { "type": "string", "pattern": "^\\d{1,5}([.,]\\d{1,2})?$" }
          }
        }
      ]
    },
    "order_edit_window_hours": { "type": ["number", "null"], "minimum": 0.5, "maximum": 72, "multipleOf": 0.5, "description": "Fino a quante ore prima della consegna il cliente cambia l'ordine (di serie 2)." },
    "checkout_upsell": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object", "additionalProperties": false, "required": ["terms"],
          "properties": {
            "terms": { "type": "array", "minItems": 1, "maxItems": 20, "items": { "type": "string", "minLength": 2, "maxLength": 40 } },
            "max": { "type": "integer", "minimum": 1, "maximum": 5 }
          }
        }
      ]
    },
    "cart_reminder": { "type": "boolean", "description": "Il promemoria della spesa lasciata a meta'." },
    "slot_on_send": { "enum": ["set_first", "propose"], "description": "All'invio: la prima fascia la mette l'assistente, o la propone con la domanda." }
  }
}
