{
  "name": "Margin Validator (Propeller Example)",
  "nodes": [
    {
      "parameters": {
        "content": "# Margin Validator\n\nA webhook-triggered workflow that validates quote margins against a 20% threshold before approval.\n\n## How It Works\n\n1. **Trigger**: Receives a POST request from the backoffice agent containing quote data\n2. **Calculate Margin**: Computes margin percentage from quote items:\n   - Sums cost prices across all items (including child items)\n   - Calculates revenue: `subTotalGross - discountGross`\n   - Margin: `(revenue - totalCost) / revenue × 100`\n3. **Validate**: Compares margin against the 20% threshold\n4. **Respond**: Returns approval status to the agent\n\n## Response\n\n≥ 20%: ✅ Quote approved and ready to share\n< 20%: ⚠️ Warning with options: request Sales Manager approval or increase margin",
        "height": 512,
        "width": 720
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        -304
      ],
      "typeVersion": 1,
      "id": "e075633e-8db3-444e-b8b4-e3ac8faa157f",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "657315f5-994b-4e43-b499-47896cf43875",
        "authentication": "basicAuth",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        128,
        240
      ],
      "id": "34704689-f69f-4634-83c5-643724c4ea9d",
      "name": "Webhook (POSTfrom Propeller)",
      "webhookId": "657315f5-994b-4e43-b499-47896cf43875",
      "credentials": {
        "httpBasicAuth": {
          "id": "",
          "name": "Create your own Basic Auth credential"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        576,
        240
      ],
      "id": "7ffe1902-0374-4076-9b39-346f6a0c6293",
      "name": "Respond to Propeller"
    },
    {
      "parameters": {
        "jsCode": "// Margin validator - checks if quote margin meets the 20% threshold\nconst inputData = items[0].json;\nconst quoteData = inputData.body?.requestMetadata?.payload || {};\n\n// Extract conversationId from incoming payload\nconst conversationId = inputData.body?.conversationId\n  || inputData.body?.requestMetadata?.conversationId\n  || $execution.id;\n\n// Extract quote information\nconst quoteId = quoteData.orderId || 'Unknown';\nconst customerName = quoteData.company?.name || 'Customer';\nconst quoteItems = quoteData.items || [];\n\n// Extract totals\nconst subTotalGross = quoteData.total?.subTotalGross || 0;\nconst discountGross = quoteData.total?.discountGross || 0;\nconst totalRevenue = subTotalGross - discountGross;\n\n// Calculate margin from cost prices (most reliable method)\n// Sums cost × quantity for all main items and their child items\nlet totalCost = 0;\n\nquoteItems.forEach(item => {\n  const quantity = item.quantity || 1;\n  const costPrice = item.costPrice || 0;\n  totalCost += costPrice * quantity;\n\n  if (item.childItems && item.childItems.length > 0) {\n    item.childItems.forEach(childItem => {\n      const childQuantity = childItem.quantity || 1;\n      const childCostPrice = childItem.costPrice || 0;\n      totalCost += childCostPrice * childQuantity;\n    });\n  }\n});\n\nconst marginAmount = totalRevenue - totalCost;\nconst marginPercentage = totalRevenue > 0 ? (marginAmount / totalRevenue) * 100 : 0;\n\n// Margin threshold\nconst MARGIN_THRESHOLD = 20;\n\n// Generate response based on margin\nlet responseText = '';\n\nif (marginPercentage < MARGIN_THRESHOLD) {\n  responseText = `⚠️ Margin Below 20%\\n\\n` +\n    `Current margin: ${marginPercentage.toFixed(2)}% (€${marginAmount.toFixed(2)})\\n\\n` +\n    `Do you want to send this quote for approval to the Sales Manager, or increase your margin above 20%?`;\n} else {\n  responseText = `✅ Approved\\n\\n` +\n    `Quote #${quoteId} for ${customerName} is approved and ready to share.\\n\\n` +\n    `Margin: ${marginPercentage.toFixed(2)}% (€${marginAmount.toFixed(2)})`;\n}\n\n// Return response in required format\nconst response = {\n  conversationId: conversationId,\n  responseMessage: responseText,\n  responseMetadata: {\n    processed: true,\n    marginPercentage: marginPercentage.toFixed(2),\n    marginAmount: marginAmount.toFixed(2),\n    totalRevenue: totalRevenue.toFixed(2),\n    totalCost: totalCost.toFixed(2),\n    threshold: MARGIN_THRESHOLD,\n    timestamp: new Date().toISOString()\n  }\n};\n\nreturn [{ json: response }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        352,
        240
      ],
      "id": "33a8335a-997d-4367-8f6a-3346b5d74158",
      "name": "Analyze Margin"
    }
  ],
  "pinData": {},
  "connections": {
    "Webhook (POSTfrom Propeller)": {
      "main": [
        [
          {
            "node": "Analyze Margin",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Margin": {
      "main": [
        [
          {
            "node": "Respond to Propeller",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}