{
  "name": "09 · Price Tag Audit — shelf vs till mismatch + printable label",
  "nodes": [
    {
      "parameters": {
        "content": "# 09 · Price Tag / Shelf Label Audit\n\n**The problem (straight from the floor):** a price changes in the POS but the shelf tag doesn't. The customer sees one price, the till charges another. That is a refund, an argument at the counter, and in Kuwait a consumer-protection complaint.\n\n**What this does:**\n1. Staff scan or enter the shelf price on a form (phone, 2 taps)\n2. It compares against the live POS price\n3. Any mismatch generates a **ready-to-print label** with the correct price + a print job you send to the label printer\n4. Re-scan the same shelf to confirm it's fixed\n\n**Also catches:** tags missing entirely, and price changes not yet reflected on the shelf.\n\nBuilt by **Maui Tech Hub** · mauitechhub.com/workflows",
        "height": 520,
        "width": 400,
        "color": 7
      },
      "id": "f801e5cc-31c1-4e09-ae55-991f6eff627b",
      "name": "NOTE",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -420,
        -60
      ]
    },
    {
      "parameters": {
        "formTitle": "Shelf Price Audit",
        "formFields": {
          "values": [
            {
              "fieldLabel": "SKU / Barcode",
              "fieldType": "text",
              "requiredField": true
            },
            {
              "fieldLabel": "Shelf Price Shown",
              "fieldType": "number",
              "requiredField": true
            },
            {
              "fieldLabel": "Aisle / Section",
              "fieldType": "text"
            },
            {
              "fieldLabel": "Checked By",
              "fieldType": "text"
            },
            {
              "fieldLabel": "Is there a tag at all?",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Yes"
                  },
                  {
                    "option": "No tag"
                  }
                ]
              }
            },
            {
              "fieldLabel": "Photo of shelf",
              "fieldType": "file"
            }
          ]
        },
        "options": {}
      },
      "id": "be46ecae-a99b-49c6-a44c-4e00ff5b1c42",
      "name": "Scan Shelf Tag",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.2,
      "position": [
        0,
        100
      ],
      "notes": "Open this form on any staff phone",
      "webhookId": "8c647e3d-36e2-4021-afee-33f7185c8bfd"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "POSPrices",
          "mode": "name"
        },
        "options": {}
      },
      "id": "c995e851-d3fe-49d7-bfe5-2df88874f7b7",
      "name": "Read POS Prices",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 1,
      "position": [
        260,
        100
      ],
      "notes": "SKU, Item, POSPrice, LastChanged"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Does the shelf agree with the till? That is the whole question.\nconst f = $('Scan Shelf Tag').first().json;\nconst sku = String(f['SKU / Barcode'] ?? f.sku ?? '').trim();\nconst shown = Number(f['Shelf Price Shown'] ?? f.price ?? 0);\nconst noTag = String(f['Is there a tag at all?'] ?? '').toLowerCase().includes('no');\nconst pos = $input.all().map(i => i.json)\n  .find(p => String(p.SKU ?? p.sku ?? '').trim() === sku);\nif (!pos) return [{ json: { unknown_sku: true, sku, shown,\n  verdict: 'SKU not in POS', needs_action: true,\n  reason: 'This SKU is not in the price file — check the barcode is right, '\n        + 'or the item may be delisted.' } }];\nconst correct = Number(pos.POSPrice ?? pos.pos_price ?? pos.Price ?? 0);\nconst item = pos.Item ?? pos.item ?? sku;\nconst diff = +(shown - correct).toFixed(3);\nconst overcharge = diff > 0;      // shelf shows MORE than till -> customer sees high price\nconst mismatch = Math.abs(diff) >= 0.001 || noTag;\nlet verdict, severity, action;\nif (noTag)            { verdict = 'NO TAG ON SHELF'; severity = 'HIGH';\n                        action = 'Print and fit a tag immediately'; }\nelse if (!mismatch)   { verdict = 'OK'; severity = 'NONE';\n                        action = 'No action — shelf matches till'; }\nelse if (overcharge)  { verdict = 'SHELF HIGHER THAN TILL'; severity = 'HIGH';\n                        action = 'Fix today — customer sees the higher price at '\n                               + 'shelf. Refund/complaint risk.'; }\nelse                  { verdict = 'SHELF LOWER THAN TILL'; severity = 'MEDIUM';\n                        action = 'Fix today — you are under-charging or the POS '\n                               + 'change did not reach the shelf.'; }\nreturn [{ json: {\n  sku, item, shelf_price: shown, pos_price: correct,\n  difference: diff, direction: diff === 0 ? 'match' : (overcharge ? 'shelf higher' : 'shelf lower'),\n  verdict, severity, action, needs_action: mismatch,\n  section: f['Aisle / Section'] ?? '', checker: f['Checked By'] ?? '',\n  checked_at: new Date().toISOString(),\n} }];"
      },
      "id": "47a8faa7-7895-44d2-8b2e-929367e49bff",
      "name": "Compare to POS",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        520,
        100
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "fix",
              "leftValue": "={{ $json.needs_action }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "04fba0df-fa00-49ee-8b73-40eca5967b49",
      "name": "Needs Fixing?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        780,
        100
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Emit a label payload any thermal/label printer can consume (ZPL + human text).\nconst CURRENCY = 'KD';        // <- change to '$', '£', '€', 'AED' ...\nconst r = $input.first().json;\nconst price = Number(r.pos_price ?? 0).toFixed(3);\nconst esc = s => String(s ?? '').replace(/[^\\x20-\\x7E]/g, '');\n// ZPL for Zebra-class printers (most supermarket label printers)\nconst zpl = [\n  '^XA',\n  '^FO40,40^A0N,34,34^FD' + esc(r.item).slice(0,28) + '^FS',\n  '^FO40,90^A0N,30,30^FD' + esc(r.sku) + '^FS',\n  '^FO40,140^A0N,64,64^FD'+CURRENCY+' ' + price + '^FS',\n  '^FO40,220^A0N,22,22^FD' + new Date().toISOString().slice(0,10) + '^FS',\n  '^XZ',\n].join('\\n');\nreturn [{ json: {\n  item: r.item, sku: r.sku, correct_price: price,\n  label_text: `${r.item}\\n${r.sku}\\n${CURRENCY} ${price}`,\n  zpl, printer: 'default_label_printer',\n  reason: r.verdict,\n} }];"
      },
      "id": "7cd1ad79-36d8-47bc-93a3-cd7c1cafb948",
      "name": "Print Correct Label",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        40
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://YOUR_LABEL_PRINTER_IP:9100",
        "sendBody": true,
        "contentType": "raw",
        "body": "={{ $json.zpl }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "7822d8d8-51fa-45fc-ad48-2537b33ce66c",
      "name": "Send Label to Printer",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1300,
        40
      ],
      "notes": "Raw ZPL to port 9100. No printer? Use the Telegram node instead."
    },
    {
      "parameters": {
        "chatId": "YOUR_MANAGER_CHAT_ID",
        "text": "=*PRICE TAG ISSUE*\\n{{ $json.item }} ({{ $json.sku }})\\nShelf: {{ $json.shelf_price }} → Till: {{ $json.pos_price }}\\n{{ $json.reason }}\\n_Aisle: {{ $json.section }} · Checked by {{ $json.checker }}_",
        "additionalFields": {}
      },
      "id": "d4d93178-a237-4937-83ad-c46fd4921a0c",
      "name": "Alert Manager",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1300,
        220
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "PriceAudit",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {}
        },
        "options": {}
      },
      "id": "abaa404e-655d-4b0b-a502-b08f7162f3ac",
      "name": "Mark Shelf OK",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 1,
      "position": [
        1040,
        220
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "PriceAudit",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {}
        },
        "options": {}
      },
      "id": "ef8bb2dc-c670-46a2-b9cd-0601b706710c",
      "name": "Log Finding",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 1,
      "position": [
        1560,
        40
      ]
    }
  ],
  "connections": {
    "Scan Shelf Tag": {
      "main": [
        [
          {
            "node": "Read POS Prices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read POS Prices": {
      "main": [
        [
          {
            "node": "Compare to POS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare to POS": {
      "main": [
        [
          {
            "node": "Needs Fixing?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Fixing?": {
      "main": [
        [
          {
            "node": "Mark Shelf OK",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Print Correct Label",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Print Correct Label": {
      "main": [
        [
          {
            "node": "Send Label to Printer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Label to Printer": {
      "main": [
        [
          {
            "node": "Alert Manager",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alert Manager": {
      "main": [
        [
          {
            "node": "Log Finding",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "instanceId": "mauitechhub-retail-ops"
  }
}