{
  "id": "AsWbqyBg1C3kfmmL",
  "meta": {
    "aiBuilderAssisted": true,
    "templateCredsSetupCompleted": true
  },
  "name": "SAP B1 Low Stock Alerts to WhatsApp and Email",
  "tags": [],
  "nodes": [
    {
      "id": "ca145c9b-6464-4c42-9989-2bee15eea304",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3360,
        944
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## SAP B1 Low Stock Alerts to WhatsApp and Email\n\n### How it works\n\nThis workflow runs every six hours to log in to SAP Business One, retrieve inventory data, and identify items below the configured stock threshold. It checks whether each low-stock item has already been alerted recently, then sends new alerts through WhatsApp and email when needed. After notifications are sent, it records the alert state and logs out of SAP B1; if there are no items or no new alerts, it exits through the logout path.\n\n### Setup steps\n\n- Configure the schedule trigger interval if six-hour checks are not appropriate.\n- Set the SAP Business One Service Layer base URL, company database, username, and password in the login, fetch, and logout HTTP request nodes.\n- Configure the Data Table used to store previous alert state, including keys such as ItemCode and last alert details.\n- Add WhatsApp Cloud API credentials, phone number ID, recipient number, and message template or text settings in the WhatsApp HTTP request node.\n- Configure SMTP or email provider credentials, sender, recipients, and subject/body fields in the email send node.\n\n### Customization\n\nAdjust the low-stock extraction logic and alert suppression window in the code nodes, change the message formatting in the Format Alert Text node, or add extra notification channels after the formatting step."
      },
      "typeVersion": 1
    },
    {
      "id": "d5112892-cafa-4aa0-b31b-4939ec66988b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3920,
        1168
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 304,
        "content": "## Schedule and SAP fetch\n\nStarts the workflow on a six-hour schedule, authenticates with SAP Business One, and requests the inventory data needed for stock evaluation."
      },
      "typeVersion": 1
    },
    {
      "id": "b007ac69-d666-479d-ab77-1e9fe7e7482c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4640,
        1136
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 320,
        "content": "## Extract stock candidates\n\nParses the SAP response into low-stock items and decides whether the workflow should continue to alert processing or skip directly to cleanup."
      },
      "typeVersion": 1
    },
    {
      "id": "fbacbc40-648c-4c76-929b-3055e5474451",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5120,
        1040
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 304,
        "content": "## Check alert history\n\nLooks up prior alerts for the detected items, computes whether a new alert is required, and branches based on that decision."
      },
      "typeVersion": 1
    },
    {
      "id": "399676c4-dba3-4411-92f2-7b18d3ad2799",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5840,
        944
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 512,
        "content": "## Format and send alerts\n\nBuilds WhatsApp and email alert content, sends both notifications in parallel, and merges the delivery results for downstream state tracking."
      },
      "typeVersion": 1
    },
    {
      "id": "00de2d17-a462-4dc8-8f9f-fb84a6a9bfa6",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6560,
        992
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 464,
        "content": "## Save state and logout\n\nStores the latest alert status after successful sends and closes the SAP B1 session. This final cleanup node is also reached by branches where no stock alert is necessary."
      },
      "typeVersion": 1
    },
    {
      "id": "79fd5183-be4f-4846-ae20-c9dd3f7807a5",
      "name": "Every 6 Hours Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        3968,
        1296
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "bca55eea-9296-48f5-9e27-d3746fd967ce",
      "name": "Post to SAP B1 Login",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4208,
        1296
      ],
      "parameters": {
        "url": "<__PLACEHOLDER_VALUE__https://your-sap-server:50000/b1s/v1/Login__>",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "CompanyDB",
              "value": "<__PLACEHOLDER_VALUE__SBODEMOGB__>"
            },
            {
              "name": "UserName",
              "value": "<__PLACEHOLDER_VALUE__manager__>"
            },
            {
              "name": "Password",
              "value": "<__PLACEHOLDER_VALUE__your-password__>"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "e697d77f-1146-428b-80ab-5e20140cedfb",
      "name": "Get Low Stock Items",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4448,
        1296
      ],
      "parameters": {
        "url": "<__PLACEHOLDER_VALUE__https://your-sap-server:50000/b1s/v1/Items__>",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "$select",
              "value": "ItemCode,ItemName,QuantityOnStock,MinInventoryQuantity,ItemsGroupCode"
            },
            {
              "name": "$filter",
              "value": "QuantityOnStock lt MinInventoryQuantity and Valid eq 'tYES' and Frozen eq 'tNO'"
            },
            {
              "name": "$orderby",
              "value": "QuantityOnStock asc"
            },
            {
              "name": "$top",
              "value": "50"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Cookie",
              "value": "=B1SESSION={{ $('Post to SAP B1 Login').item.json.SessionId }}; ROUTEID=.node1"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "26209c7c-9379-45d3-a9bb-1b2494f4490f",
      "name": "Extract Items with Low Stock",
      "type": "n8n-nodes-base.code",
      "position": [
        4688,
        1296
      ],
      "parameters": {
        "jsCode": "\nconst records = $input.first().json.value || [];\nreturn records.map(r => ({ json: r }));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "b016f0bd-c316-41aa-a772-12e5b5607055",
      "name": "Check for Low Stock Items",
      "type": "n8n-nodes-base.if",
      "position": [
        4928,
        1296
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-items",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.ItemCode }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1b9c380d-0999-4d33-aa9f-3cfae6b1e4cd",
      "name": "Lookup Previous Alerts",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        5168,
        1168
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "conditions": [
            {
              "keyName": "itemCode",
              "keyValue": "={{ $json.ItemCode }}"
            }
          ]
        },
        "matchType": "allConditions",
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "nTczqcL5oytUzP8v"
        }
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "0699da0b-45d1-406a-a3bc-a8e2a2c0b7fb",
      "name": "Determine New Alert Need",
      "type": "n8n-nodes-base.code",
      "position": [
        5408,
        1168
      ],
      "parameters": {
        "jsCode": "\nconst item = $('Extract Items with Low Stock').item.json;\nconst rows = $input.all();\nconst previousAlert = rows.length > 0 && rows[0].json && rows[0].json.lastAlertedQty !== undefined\n  ? rows[0].json\n  : null;\n\nconst shouldAlert = !previousAlert || item.QuantityOnStock < previousAlert.lastAlertedQty;\n\nreturn [{\n  json: {\n    ...item,\n    shouldAlert\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "2ee00cc4-c1e7-47f5-a4e1-1baf9d5edd01",
      "name": "Check Alert Necessity",
      "type": "n8n-nodes-base.if",
      "position": [
        5648,
        1168
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "should-alert-check",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.shouldAlert }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9dad976c-c70d-4145-9d5d-5ea522da4d86",
      "name": "Prepare Alert Message",
      "type": "n8n-nodes-base.set",
      "position": [
        5888,
        1168
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "whatsappText",
              "type": "string",
              "value": "={{ \"\u26a0\ufe0f *Low Stock Alert*\\n\\n\" +\"\ud83d\udce6 \" + $json.ItemName + \" (\" + $json.ItemCode + \")\\n\" +\"\ud83d\udcc9 Current Stock: \" + $json.QuantityOnStock + \"\\n\" +\"\ud83c\udfaf Reorder Level: \" + $json.MinInventoryQuantity + \"\\n\\n\" +(($json.QuantityOnStock === 0) ? \"\ud83d\udea8 OUT OF STOCK - reorder immediately\" : \"Stock is below the minimum threshold\") }}"
            },
            {
              "id": "2",
              "name": "emailSubject",
              "type": "string",
              "value": "={{ ($json.QuantityOnStock === 0 ? \"OUT OF STOCK: \" : \"Low Stock: \") + $json.ItemName + \" (\" + $json.ItemCode + \")\" }}"
            },
            {
              "id": "3",
              "name": "emailHtml",
              "type": "string",
              "value": "={{ \"<h2>Low Stock Alert</h2>\" +\"<p><strong>Item:</strong> \" + $json.ItemName + \" (\" + $json.ItemCode + \")</p>\" +\"<p><strong>Current Stock:</strong> \" + $json.QuantityOnStock + \"</p>\" +\"<p><strong>Reorder Level:</strong> \" + $json.MinInventoryQuantity + \"</p>\" +($json.QuantityOnStock === 0 ? \"<p style=\\\"color:red;font-weight:bold;\\\">OUT OF STOCK - reorder immediately</p>\" : \"<p>Stock is below the configured minimum threshold.</p>\") }}"
            },
            {
              "id": "4",
              "name": "ItemCode",
              "type": "string",
              "value": "={{ $json.ItemCode }}"
            },
            {
              "id": "5",
              "name": "QuantityOnStock",
              "type": "number",
              "value": "={{ $json.QuantityOnStock }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "bc455507-1462-451d-b677-5325adbce5b0",
      "name": "Post to SAP B1 Logout",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        6848,
        1296
      ],
      "parameters": {
        "url": "<__PLACEHOLDER_VALUE__https://your-sap-server:50000/b1s/v1/Logout__>",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Cookie",
              "value": "=B1SESSION={{ $('Post to SAP B1 Login').item.json.SessionId }}; ROUTEID=.node1"
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 4.3
    },
    {
      "id": "853d56bb-29e5-492e-a836-947057a581cb",
      "name": "Post WhatsApp Alert",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        6128,
        1072
      ],
      "parameters": {
        "url": "<__PLACEHOLDER_VALUE__https://graph.facebook.com/v19.0/YOUR_PHONE_NUMBER_ID/messages__>",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "jsonBody": "={{ { messaging_product: 'whatsapp', to: '966500000000', type: 'text', text: { body: $json.whatsappText } } }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.3
    },
    {
      "id": "f4d01b27-063b-4dc7-8d12-754e40636878",
      "name": "Merge Alert Outputs",
      "type": "n8n-nodes-base.merge",
      "position": [
        6368,
        1168
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "32bf150e-1ef3-4459-92c2-eb2335840553",
      "name": "Send Email Notification",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        6128,
        1280
      ],
      "parameters": {
        "html": "={{ $json.emailHtml }}",
        "options": {},
        "subject": "={{ $json.emailSubject }}",
        "toEmail": "<__PLACEHOLDER_VALUE__inventory-team@yourcompany.com__>",
        "fromEmail": "<__PLACEHOLDER_VALUE__alerts@yourcompany.com__>"
      },
      "typeVersion": 2.1
    },
    {
      "id": "72586c9a-b2d6-4151-93ca-6b47a7307362",
      "name": "Save Alert Status in Table",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        6608,
        1168
      ],
      "parameters": {
        "columns": {
          "value": {
            "mappingValues": [
              {
                "fieldId": "itemCode",
                "fieldValue": "={{ $('Prepare Alert Message').item.json.ItemCode }}"
              },
              {
                "fieldId": "lastAlertedQty",
                "fieldValue": "={{ $('Prepare Alert Message').item.json.QuantityOnStock }}"
              }
            ]
          },
          "mappingMode": "defineBelow"
        },
        "filters": {
          "conditions": [
            {
              "keyName": "itemCode",
              "keyValue": "={{ $('Prepare Alert Message').item.json.ItemCode }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "nTczqcL5oytUzP8v"
        }
      },
      "executeOnce": true,
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "versionId": "f8f71a83-4a8b-4946-875f-781921d8f8c4",
  "connections": {
    "Get Low Stock Items": {
      "main": [
        [
          {
            "node": "Extract Items with Low Stock",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Alert Outputs": {
      "main": [
        [
          {
            "node": "Save Alert Status in Table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post WhatsApp Alert": {
      "main": [
        [
          {
            "node": "Merge Alert Outputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post to SAP B1 Login": {
      "main": [
        [
          {
            "node": "Get Low Stock Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Alert Necessity": {
      "main": [
        [
          {
            "node": "Prepare Alert Message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post to SAP B1 Logout",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every 6 Hours Trigger": {
      "main": [
        [
          {
            "node": "Post to SAP B1 Login",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Alert Message": {
      "main": [
        [
          {
            "node": "Post WhatsApp Alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Email Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lookup Previous Alerts": {
      "main": [
        [
          {
            "node": "Determine New Alert Need",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email Notification": {
      "main": [
        [
          {
            "node": "Merge Alert Outputs",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Determine New Alert Need": {
      "main": [
        [
          {
            "node": "Check Alert Necessity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Low Stock Items": {
      "main": [
        [
          {
            "node": "Lookup Previous Alerts",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post to SAP B1 Logout",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Alert Status in Table": {
      "main": [
        [
          {
            "node": "Post to SAP B1 Logout",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Items with Low Stock": {
      "main": [
        [
          {
            "node": "Check for Low Stock Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}