Table of Contents
- 1 The Business Situation
- 2 The Existing Process
- 3 What the New System Needed to Do
- 4 Implementation Approaches Considered
- 4.1 Improving the existing spreadsheet
- 4.2 Using Google Workspace with n8n
- 4.3 Purchasing dedicated warehouse software
- 4.4 Building a custom application
- 5 The Selected Solution
- 6 System Architecture and Data Flow
- 7 Data Structure
- 7.1 Item_Master
- 7.2 Count_Tasks
- 7.3 Supporting records
- 8 Workflow Statuses and Ownership
- 9 Step-by-Step Implementation
- 9.1 Step 1: Prepare the Accounts and Permissions
- 9.2 Step 2: Build the Intake
- 9.3 Step 3: Create the System of Record
- 9.4 Step 4: Connect the Tools
- 9.5 Step 5: Build the Core Automation
- 9.6 Step 6: Add Approvals, Reminders, and Escalations
- 9.7 Step 7: Add Documents and File Management
- 9.8 Step 8: Add Reporting and Operational Views
- 9.9 Step 9: Add Security and Governance Controls
- 9.10 Step 10: Deploy and Test
- 10 Code and Configuration
- 10.1 Configuration values
- 10.2 Shopify inventory snapshot request
- 10.3 Weekly cycle-count scheduler
- 10.4 Shopify event normalization
- 10.5 Count validation Code node
- 10.6 Control formulas
- 10.7 Deployment and troubleshooting
- 11 Failure Handling and Operational Reliability
- 12 A Complete Example
- 13 Implementation Cost
- 14 Estimated Time and Cost Savings
- 15 Adding AI to the Automation
- 15.1 The Recommended AI Enhancement
- 15.2 Benefits of the AI Enhancement
- 15.3 What Remains Rule-Based or Human-Controlled
- 15.4 Estimating the Additional Value of AI
- 16 Testing Checklist
- 17 Ongoing Maintenance
- 18 When to Move to Dedicated Software
- 19 Implementation Checklist
The Business Situation
Larkspur Storage Goods is a fictional 42-person online retailer selling home-organization products from one warehouse. The business carries approximately 1,800 active Shopify variants and processes about 3,000 orders per month, averaging 1.8 line items per order.
The supply chain team includes an operations manager, an inventory control lead, four warehouse associates, and a quality analyst. A customer-service lead handles order exceptions that may affect delivery commitments.
Shopify is the commercial inventory system. Google Workspace is used for spreadsheets, forms, email, and shared files. Slack is the primary internal notification channel.
Inventory counting had previously depended on two irregular full-warehouse counts each year. Staff also performed occasional spot checks when a picker found an empty bin or a customer reported an incorrect shipment. Results were recorded in separate spreadsheets, Slack messages, or handwritten notes.
The business wanted to replace this reactive process with approximately 90 scheduled cycle counts per week. Items would be selected according to value, transaction volume, count history, open exceptions, and operational risk. Any difference between the system quantity and physical quantity would require an independent recount, a reason, approval, and a recorded inventory correction.
Note: This case study is provided as a representative example of the types of AI integration and digital transformation solutions Intelligex designs and delivers. Actual engagements are tailored to each client’s goals, constraints, existing systems, timeline, and available resources, so the approach, tools, and outcomes may vary.
The Existing Process
The original counting process followed a loose sequence:
- The inventory control lead exported a Shopify inventory report when a full count was planned.
- Warehouse associates counted every accessible bin, usually while normal picking continued.
- Counts were typed into a spreadsheet after the physical work was complete.
- The inventory control lead compared the spreadsheet with the earlier Shopify export.
- Large differences were sometimes recounted, but there was no consistent threshold.
- Potential causes were discussed in Slack or email.
- An authorized employee adjusted Shopify inventory.
- The count spreadsheet was filed in a shared folder without structured follow-up.
| Current problem | Practical business effect |
|---|---|
| System quantities were exported hours before counting. | Orders and fulfilments during the count created timing differences that looked like inventory errors. |
| Counts were not assigned by value or risk. | Low-impact products could consume the same attention as high-value or frequently miscounted items. |
| First counts and recounts were stored together. | A second counter could see the first result, weakening the independence of the recount. |
| Variance reasons were optional free text. | Recurring issues such as mis-binned stock or unprocessed returns could not be measured consistently. |
| Shopify events were disconnected from count records. | Operations could not easily distinguish a physical variance from an order, cancellation, or fulfilment timing issue. |
| Approvals took place in chat. | The final spreadsheet did not reliably show who approved an adjustment or what evidence was reviewed. |
| There was no defined task owner. | Overdue recounts and unresolved investigations depended on the inventory lead remembering to follow up. |
| Historical files used inconsistent formats. | Quality staff could not report recurring variances by SKU, bin, reason, counter, or product class. |
| The process depended heavily on one employee. | Counting slowed when the inventory lead was absent and operating knowledge was difficult to transfer. |
The main business requirement was not simply to count more often. Larkspur needed controlled evidence showing what was counted, which Shopify quantity was used, whether stock moved during the count, who performed the recount, why the difference occurred, and who authorized the correction.
What the New System Needed to Do
| Area | Requirement |
|---|---|
| Scheduling | Select approximately 90 item-location records each week using ABC class, movement, time since last count, prior variance, and open exceptions. |
| Intake | Allow warehouse staff to start a count and submit a quantity without seeing the Shopify system quantity. |
| Identifiers | Assign a deterministic task ID and retain Shopify product, variant, inventory item, location, order, and fulfilment identifiers. |
| Validation | Reject unknown task IDs, negative counts, decimals where only whole units are allowed, inactive users, and invalid workflow transitions. |
| Recounts | Require a second count by a different employee whenever the first physical count differs from the system snapshot. |
| Movement control | Flag Shopify activity occurring between count start and count submission. |
| Investigation | Require a controlled reason code, supporting notes, and optional evidence for confirmed variances. |
| Approval | Route approval according to unit value, absolute quantity, and recurrence thresholds. |
| Inventory correction | Keep the final Shopify adjustment under human control and record who confirmed it. |
| Notifications | Notify warehouse operations, quality, or customer service only when an action or exception requires attention. |
| Reporting | Show volume, completion time, overdue tasks, variance value, recurring SKUs, reasons, and automation failures. |
| Permissions | Prevent counters from viewing system quantities, costs, approval fields, and other employees’ first-count results. |
| Reliability | Prevent duplicate event processing, retry temporary failures, and route unresolved failures to a manual-review queue. |
| Audit evidence | Retain timestamps, submitter identities, approval decisions, Shopify external IDs, automation execution references, and evidence links. |
| Manual override | Allow the operations manager to suspend, reassign, reopen, or close a task with a documented reason. |
Normal validation, formulas, thresholds, permissions, and routing rules were deliberately separated from AI. None of these controls required a probabilistic model.
Implementation Approaches Considered
| Approach | Connected tools | Effort | Customization | Main limitation |
|---|---|---|---|---|
| Improve the existing spreadsheet | Shopify exports and Google Sheets | Low | Low | Still depends on manual exports, follow-ups, and reconciliation. |
| Workspace workflow with n8n | Shopify, Google Forms, Google Sheets, Google Drive, n8n, Slack | Moderate | High | Requires disciplined sheet design, archiving, and workflow maintenance. |
| Dedicated warehouse inventory application | Shopify and a specialized inventory platform | Moderate to high | Varies by product | May add broader warehouse features and cost beyond the immediate requirement. |
| Custom database and web application | Shopify API, database, application, identity provider, Slack | High | Very high | Requires application hosting, support, security engineering, and a larger implementation budget. |
Improving the existing spreadsheet
A single standardized spreadsheet would improve consistency, but it would not create blind counts, reliable reminders, event synchronization, approval evidence, or duplicate-event protection. It was suitable only as an interim control.
Using Google Workspace with n8n
This approach retained tools employees already understood while adding scheduled processing, Shopify integration, Slack routing, validation, retries, and operational logs. The expected volume was within a carefully managed Google Sheets design, provided high-volume event rows were archived regularly.
Purchasing dedicated warehouse software
A specialized warehouse management or inventory-counting platform could provide barcode scanning, mobile workflows, stronger concurrency controls, and more advanced inventory functions. Larkspur did not yet require a full warehouse management replacement, and the implementation would need a broader process change.
Building a custom application
A custom application would provide the strongest control over user experience and data integrity. It was not selected because 90 weekly count tasks and one warehouse did not justify the added hosting, software support, and security responsibilities.
The Selected Solution
Larkspur selected a Google Workspace workflow coordinated by n8n. Shopify remained the commercial inventory source, Google Sheets became the cycle-count system of record, Google Forms provided controlled intake, Google Drive stored evidence, and Slack delivered action notifications.
| Tool | Responsibility |
|---|---|
| Shopify | Provides products, variants, inventory items, locations, inventory snapshots, orders, and fulfilment events. |
| Google Forms | Captures count starts, first counts, recounts, investigations, approvals, and inventory-adjustment confirmations. |
| Google Sheets | Stores item records, count tasks, normalized events, submissions, users, reason codes, logs, and reports. |
| n8n | Schedules work, validates submissions, synchronizes systems, applies workflow rules, retries failures, and updates records. |
| Google Drive | Stores the workbook and optional photographs or supporting variance evidence. |
| Slack | Notifies warehouse, quality, customer-service, and approval owners without becoming the system of record. |
| Optional AI service | Suggests a structured variance summary and likely investigation category after the core workflow is stable. |
The selected design removed manual Shopify exports, task-list preparation, repeated status follow-ups, and spreadsheet consolidation. It did not automate the final inventory adjustment. An authorized employee still reviewed evidence, approved the correction, updated Shopify, and confirmed completion.
Slack was used for attention management rather than approvals. Approval decisions were submitted through a controlled form so the decision, approver identity, timestamp, and comments were written to the audit record.
System Architecture and Data Flow
- Intake: Restricted Google Forms for count activity, investigation, approval, and adjustment confirmation.
- System of record: A protected Google Sheets workbook.
- Automation layer: n8n workflows using Shopify, Google Sheets, Google Drive, and Slack credentials.
- Document storage: Restricted Google Drive folders created for variance evidence.
- Notifications: Slack channels and direct messages mapped to responsible users.
- Reporting: Google Sheets formulas, filtered views, pivot tables, and charts.
- AI layer: An optional structured-output analysis step for variance investigation support.
- Synchronize the item master. A nightly n8n schedule reads active Shopify variants and inventory quantities for the warehouse location. It validates SKU and location mappings, then updates the corresponding Item_Master row. Unknown or duplicate SKUs are written to the exception queue instead of being silently merged.
- Receive commercial events. Shopify order and fulfilment events trigger n8n. The workflow extracts only required operational fields, creates a deterministic event key, and checks whether that key has already been processed.
- Normalize event lines. n8n converts each order or fulfilment line into a consistent movement record containing the Shopify resource ID, SKU, quantity, location, event time, and status. The returned row identifier is stored in the automation log.
- Flag event exceptions. Missing SKUs, unknown locations, failed fulfilment states, cancellations requiring review, or unexpected activity during an open count are routed to operations or customer service. Normal events update movement statistics without producing Slack noise.
- Generate weekly count tasks. Each Monday morning, n8n reads Item_Master, excludes inactive products and items with open tasks, calculates a risk score, and selects up to 90 item-location records.
- Assign ownership. The SKU zone determines the first counter. n8n appends a Count_Tasks row, updates Item_Master to show an open task, and posts the assignment to the warehouse channel.
- Start the blind count. The assigned employee submits a Start Count action through Google Forms. n8n confirms authorization, obtains a current Shopify on-hand quantity, records the count-window timestamp, and changes the task to Counting. The quantity is not shown to the counter.
- Capture the first count. The employee submits a non-negative whole-unit quantity. n8n checks for Shopify movement during the count window and compares the result with the hidden system snapshot.
- Route the recount. A matching first count closes the task. A difference creates an Awaiting Recount status and assigns another authorized warehouse employee. The recount form does not display the first count or Shopify quantity.
- Investigate confirmed variance. A matching recount confirms the variance. n8n requests a reason code, notes, corrective action, and optional evidence. Disagreement between the first count and recount goes to Manual Review.
- Approve and adjust. n8n selects the approval route according to variance value, units, and recurrence. After approval, an authorized employee adjusts Shopify manually and submits an adjustment confirmation.
- Close and report. n8n closes the task, updates the item’s last-count date and variance history, clears its open-task flag, and refreshes operational reporting. Any failed step records an error, increments the retry count, and enters a recovery queue.
Data Structure
The workbook uses separate tabs for source data, workflow records, configuration, intake responses, logs, and reporting. Automation-owned tabs and columns are protected from ordinary users.
Item_Master
| Field | Type | Required | Source | Purpose |
|---|---|---|---|---|
| Item_Key | Text | Yes | n8n | Unique combination of Shopify inventory item and location. |
| SKU | Text | Yes | Shopify | Operational item identifier. Duplicate active SKUs are rejected. |
| Shopify_Product_GID | Text | Yes | Shopify | Shopify global product identifier. |
| Shopify_Variant_GID | Text | Yes | Shopify | Shopify global variant identifier. |
| Inventory_Item_GID | Text | Yes | Shopify | Identifier used for inventory queries. |
| Location_GID | Text | Yes | Shopify | Warehouse location identifier. |
| Product_Title | Text | Yes | Shopify | Readable product and variant description. |
| On_Hand_Qty | Integer | Yes | Shopify | Latest physical-stock quantity reported by Shopify. |
| Available_Qty | Integer | No | Shopify | Latest quantity available for sale or allocation. |
| Standard_Unit_Cost | Decimal | Yes | Finance-maintained sheet | Used to estimate variance value. Must be zero or greater. |
| ABC_Class | Enum | Yes | Operations | Allowed values A, B, or C. |
| Units_Ordered_30d | Integer | Yes | Movement ledger | Recent demand input to the scheduling score. |
| Units_Fulfilled_30d | Integer | Yes | Movement ledger | Recent warehouse movement input to the scheduling score. |
| Variance_Count_90d | Integer | Yes | Count_Tasks | Number of prior confirmed variances. |
| Open_Exception_Count | Integer | Yes | Event_Exceptions | Unresolved Shopify or inventory exceptions. |
| Last_Count_Date | Date | No | n8n | Most recent successfully closed count. |
| Open_Task | Boolean | Yes | n8n | Prevents overlapping count assignments. |
| Primary_Counter_Email | Yes | Users configuration | Default assignment based on warehouse zone. | |
| Active | Boolean | Yes | Shopify and operations | Controls scheduling eligibility. |
| Last_Shopify_Sync | Timestamp | Yes | n8n | Supports stale-data monitoring. |
Count_Tasks
| Field | Type | Required | Purpose and validation |
|---|---|---|---|
| Task_ID | Text | Yes | Deterministic unique task identifier. |
| Item_Key | Text | Yes | References one Item_Master row. |
| SKU | Text | Yes | Copied from Item_Master for reporting. |
| Scheduled_Date | Date | Yes | Date selected by the scheduling workflow. |
| Due_Date | Date | Yes | Normally two working days after assignment. |
| Priority | Enum | Yes | Allowed values Critical, High, Normal, or Low. |
| Risk_Score | Decimal | Yes | Calculated scheduling score retained for audit. |
| Owner_Email | Yes | Current action owner. | |
| Status | Enum | Yes | Controlled workflow stage. |
| System_Qty_At_Assignment | Integer | Yes | Planning snapshot, hidden from counters. |
| System_Qty_At_Count_Start | Integer | Conditional | Authoritative comparison snapshot recorded when the count starts. |
| Count_Window_Started_At | Timestamp | Conditional | Beginning of movement-detection window. |
| First_Count | Integer | Conditional | Must be a whole number of zero or greater. |
| First_Counter_Email | Conditional | Collected from the authenticated form submitter. | |
| Variance_1 | Integer | Conditional | First count minus system quantity. |
| Recount_Qty | Integer | Conditional | Must be entered by a different authorized counter. |
| Final_Count | Integer | Conditional | Recount quantity when a recount is required. |
| Final_Variance | Integer | Conditional | Final count minus system quantity. |
| Variance_Value | Decimal | Conditional | Final variance multiplied by standard unit cost. |
| Movement_During_Count | Boolean | Yes | Forces manual review when Shopify activity overlaps the count window. |
| Reason_Code | Enum | Conditional | Required for a confirmed non-zero variance. |
| Reason_Notes | Text | Conditional | Required investigation detail with a configured minimum length. |
| Evidence_Link | URL | No | Restricted Google Drive link. |
| Approval_Status | Enum | Yes | Not Required, Pending, Approved, Returned, or Rejected. |
| Approval_Route | Enum | No | Inventory Lead, Operations Manager, or Operations and Quality. |
| Approved_By | No | Validated against the active Users tab. | |
| Approved_At | Timestamp | No | Submission timestamp from the approval workflow. |
| Adjustment_Confirmation_ID | Text | No | Identifier proving that an authorized user confirmed the Shopify adjustment. |
| Automation_Status | Enum | Yes | Pending, Processing, Completed, Retry, or Failed. |
| Last_Automation_Run | Timestamp | No | Most recent processing attempt. |
| Retry_Count | Integer | Yes | Starts at zero and increments after recoverable failures. |
| Error_Message | Text | No | Sanitized operational error without credentials or sensitive payloads. |
| Closed_At | Timestamp | No | Completion timestamp used for processing-time reporting. |
Supporting records
- Movement_Ledger
- One normalized row per relevant Shopify event line. Event_Key is the unique identifier used for idempotency.
- Event_Exceptions
- Contains unknown SKUs, unrecognized locations, failed fulfilments, overlapping count movements, and other events requiring human review.
- Users
- Maps employee email, role, warehouse zone, Slack user ID, active status, approval limit, and delegate.
- Reason_Codes
- Contains RECEIVING_ERROR, PICK_SHORT, MISBINNED, DAMAGE, RETURN_NOT_PROCESSED, BUNDLE_MAPPING, SKU_MAPPING, SYSTEM_TIMING, COUNT_ERROR, and OTHER.
- Automation_Log
- Stores workflow name, execution reference, record ID, start time, result, retry count, and sanitized error.
- Failed_Events
- Acts as the dead-letter queue for events that cannot be completed after automated retries.
Item_Master has a one-to-many relationship with Count_Tasks and Movement_Ledger. Count_Tasks has one-to-many relationships with raw form submissions and approval events. Google Sheets does not enforce relational constraints, so n8n validates references before every write.
Workflow Statuses and Ownership
| Status | Owner | Entry condition | Exit condition | Reminder and escalation |
|---|---|---|---|---|
| Assigned | First counter | Weekly task created. | Authorized employee starts the count. | Reminder after one working day; escalate to inventory lead when overdue. |
| Counting | First counter | Shopify snapshot and start time recorded. | Valid first count submitted. | Alert after two hours because a long count window increases timing risk. |
| Awaiting Recount | Second counter | First count differs from system quantity. | Independent recount submitted. | Reminder after one working day; escalate after two. |
| Awaiting Reason | Inventory lead | Recount confirms a non-zero variance. | Complete investigation submitted. | Reminder after one working day. |
| Manual Review | Inventory lead | Counts disagree, movement occurred during counting, or validation is inconclusive. | Lead documents resolution, orders another count, or cancels the task. | Immediate Slack notification; escalate after one working day. |
| Awaiting Approval | Configured approver | Investigation passes validation. | Approved, returned, or rejected. | Reminder after 24 hours; delegate or escalate after 48 hours. |
| Quality Review | Quality analyst | High-value or recurring issue requires quality acknowledgment. | Quality review submitted. | Reminder after 24 hours. |
| Approved for Adjustment | Inventory lead | All required approvals are complete. | Shopify adjustment is performed and confirmed. | Reminder after four business hours. |
| Returned for Information | Inventory lead | Approver requests more evidence. | Investigation is resubmitted. | Reminder after one working day. |
| Rejected | Operations manager | Approver rejects the proposed adjustment. | Manager closes or reopens the investigation. | Included in daily exception review. |
| Closed | System owner | No variance exists or an approved adjustment is confirmed. | Terminal unless an authorized manager reopens it. | No reminders. |
| Automation Failed | System owner | A required integration remains unsuccessful after retries. | Failure is repaired and record replayed. | Immediate operations alert and daily reconciliation. |
A task can move backward when an approver returns it, a manager requests another count, or reconciliation finds missing evidence. Only the operations manager can cancel or reopen a closed task, and the override reason is mandatory.
Step-by-Step Implementation
Step 1: Prepare the Accounts and Permissions
- Create a Shopify custom app or approved integration credential with read access to the products, variants, inventory, locations, orders, and fulfilment records required by the implementation. The exact access-scope names depend on the Shopify API version and app configuration.
- Store the Shopify shop domain and Admin API access token in an n8n credential. Do not place the token in a Code node, Google Sheet, workflow name, or Slack message.
- Use an n8n deployment that supports scheduled workflows, webhooks, execution history, encrypted credentials, Google Sheets, Google Drive, Shopify or HTTP requests, and Slack.
- Create a Google Workspace automation identity such as
warehouse-automation@YOUR_DOMAIN. Grant it editor access only to the cycle-count workbook and evidence root folder. - Create a Slack application or n8n Slack OAuth connection that can post to the approved warehouse, quality, customer-service, and automation-alert channels. It does not require broad access to private conversations.
- Create separate production and test workbooks. Test forms must write only to the test workbook.
- Create test users representing a counter, second counter, inventory lead, operations manager, quality analyst, and customer-service lead.
- Restrict forms to authenticated company accounts where supported. Collect the submitter email automatically.
- Protect system quantities, costs, formulas, approval data, event records, user mappings, and logs. Warehouse counters use forms rather than direct access to Count_Tasks.
- Record a backup approver and Slack user ID for each operational role.
Larkspur assigned Marcus Lee, the inventory control lead, as primary system owner. Operations manager Elena Ruiz was the approval owner. Quality analyst Priya Shah owned recurring-variance reporting, and customer-service lead Sam Carter received customer-impacting event exceptions.
Step 2: Build the Intake
Four restricted Google Forms were created. Their response tabs were connected to the central workbook, but response tabs were not treated as authoritative workflow records until n8n validated each submission.
Count Capture form
| Field | Type and allowed values | Validation |
|---|---|---|
| Submitter email | Automatically collected email | Must match an active counter in Users. |
| Task ID | Required short text | Uppercase pattern beginning with CC and matching an open task. |
| Submission type | Start Count, First Count, or Recount | Must be valid for the current task status. |
| Counted quantity | Required for First Count and Recount | Whole number from zero to a configured upper bound. |
| Bin condition | Normal, Empty, Damaged, Mixed SKU, Label Problem, or Other | Required for count submissions. |
| Operational note | Optional paragraph | Required when Bin condition is Other. |
Form sections show the quantity fields only for First Count or Recount. The form never displays Shopify quantity or the previous count. Its confirmation message states that the submission is pending validation and does not guarantee task completion.
Variance Investigation form
This form collects Task ID, controlled reason code, investigation notes, corrective action, evidence upload or evidence link, and whether a customer order may be affected. Notes require enough detail to describe what was checked rather than simply stating that stock was wrong.
Variance Approval form
This form collects Task ID, decision, comments, and authenticated approver email. Allowed decisions are Approve, Return for Information, and Reject. n8n verifies that the submitter is the assigned approver or an active delegate.
Adjustment Confirmation form
This form collects Task ID, confirmation timestamp, the employee who updated Shopify, resulting Shopify on-hand quantity, adjustment reason used, and optional evidence link. The workflow verifies that the task has an Approved for Adjustment status before accepting the confirmation.
Duplicate form submissions are prevented through a Submission_Key made from the form identifier and response-row number. The workflow also checks whether the same task, stage, and submitter have already produced an accepted submission. Incomplete or invalid submissions remain in the response sheet with a Processed_Status of Rejected and a visible validation message.
Step 3: Create the System of Record
- Create one production workbook containing Item_Master, Count_Tasks, Movement_Ledger, Event_Exceptions, Users, Reason_Codes, Automation_Log, Failed_Events, Config, Dashboard, and the form response tabs.
- Freeze the header row, apply consistent date formats, and avoid merged cells in machine-read tabs.
- Use the exact header names expected by n8n. Renaming a machine-read header is treated as a controlled change.
- Create an Item_Key from Shopify inventory item ID and location ID. Do not rely only on SKU because duplicate or blank SKUs must be handled as exceptions.
- Use deterministic Task_ID values containing the schedule date and a hash of Item_Key.
- Protect formula columns and fields written by n8n.
- Create filtered views for open assignments, recounts, investigations, approvals, adjustment confirmations, overdue tasks, and failures.
- Set data validation lists for ABC_Class, Status, Priority, Reason_Code, Approval_Status, role, and active flags.
- Set defaults for Open_Task, Retry_Count, Automation_Status, and Approval_Status.
- Do not manually sort only part of a machine-read range. Sort through filtered views or named reporting tabs.
Google Sheets does not provide database-style unique constraints. Deterministic keys, pre-write lookups, serialized writer workflows where available, and daily duplicate reconciliation provide the necessary controls at this volume.
Step 4: Connect the Tools
| Source | Destination | Trigger | Important mapping | Failure behavior |
|---|---|---|---|---|
| Shopify inventory | Item_Master | Nightly schedule | Variant GID, inventory item GID, SKU, title, location, on-hand, available, active status | Retry temporary API errors; route missing SKU and duplicate mapping to Event_Exceptions. |
| Shopify orders | Movement_Ledger | Order event | Webhook ID, order ID, order number, line ID, SKU, quantity, event time, status | Use Event_Key lookup before append; failed records enter Failed_Events. |
| Shopify fulfilments | Movement_Ledger | Fulfilment event | Fulfilment ID, order ID, location ID, SKU, quantity, event time, status | Unknown location or failure-like status creates an operations exception. |
| Count form response | Count_Tasks | Scheduled poll for unprocessed rows | Task ID, type, count, condition, submitter, timestamp | Invalid response is marked Rejected without changing the task. |
| Count_Tasks | Slack | Status or owner change | Task ID, SKU, location, due date, required action, restricted record link | Notification failure is logged and retried without rolling back the task update. |
| Investigation form | Count_Tasks and Drive | New unprocessed response | Reason, notes, corrective action, evidence link, customer-impact flag | Missing required evidence for a configured high-risk case returns the submission. |
| Approval form | Count_Tasks | New unprocessed response | Decision, approver, comments, submitted timestamp | Unauthorized decisions are rejected and reported to the system owner. |
| Adjustment form | Count_Tasks and Item_Master | New unprocessed response | Confirmed quantity, employee, timestamp, confirmation ID | Quantity mismatch creates Manual Review instead of closing the task. |
The Shopify connection uses an Admin API access token stored in n8n. The Google connection uses OAuth or a controlled service identity. Slack uses a restricted OAuth credential. Returned Shopify GIDs, form response identifiers, Google Drive file IDs, and n8n execution references are retained in the system of record.
Interface labels can vary by n8n and application version. The implementation should preserve the trigger, data mapping, validation, destination action, and failure route even when a connector uses different labels.
Step 5: Build the Core Automation
Automation 1: Nightly Shopify item synchronization
- Trigger: n8n Schedule Trigger each night after warehouse activity slows.
- Conditions: Process the configured Shopify location and only records returned by the active API query.
- Actions: Page through variants, normalize identifiers, locate Item_Key in Item_Master, update existing rows, append new rows, and flag missing or duplicate SKUs.
- Fields updated: Shopify identifiers, title, quantities, active status, and Last_Shopify_Sync.
- Notification: Post only when duplicate SKUs, missing mappings, stale synchronization, or API failures require attention.
- Exception: Do not overwrite manually maintained unit cost, ABC class, zone, or counter assignment.
Automation 2: Shopify order and fulfilment event processing
- Trigger: Shopify event trigger for configured order and fulfilment topics.
- Conditions: Event_Key must not already exist.
- Actions: Normalize event lines, validate SKU and location, append Movement_Ledger rows, update rolling movement inputs, and compare event time with open count windows.
- Fields updated: Event status, movement quantity, Shopify external IDs, exception type, owner team, and processing timestamp.
- Notification: Operations receives inventory or location exceptions. Customer service receives only exceptions that may affect an order.
- Exception: A duplicate event is logged as Ignored Duplicate and causes no second movement update.
Automation 3: Weekly task generation
- Trigger: Monday schedule at 06:00 in the warehouse time zone.
- Conditions: Item is active, has a valid SKU and location, has no open task, and is due or has a qualifying risk exception.
- Actions: Calculate risk, select up to 90 records, create deterministic Task_ID values, append tasks, set Open_Task to Yes, and assign the first counter.
- Fields updated: Scheduled date, due date, risk score, priority, owner, status, system quantity at assignment, and automation status.
- Notification: Post a summary to the warehouse channel and direct notifications to assigned counters when configured.
- Exception: If the task ID already exists, skip creation and log the duplicate attempt.
Automation 4: Count submission processing
- Trigger: n8n polls unprocessed Count Capture responses every two minutes.
- Conditions: Submitter is active, task exists, stage is valid, and quantity passes validation.
- Actions: Start the count, capture a Shopify snapshot, record the first count, assign an independent recount, or route the task to Manual Review.
- Fields updated: Count timestamps, counters, quantities, variances, movement flag, owner, status, and submission processing result.
- Notification: Send the next action to the appropriate counter or inventory lead.
- Exception: Reject a recount from the original counter and leave the task awaiting an independent recount.
Automation 5: Investigation and approval
- Trigger: New unprocessed investigation or approval response.
- Conditions: The task is at the expected stage and the submitter has the required role.
- Actions: Validate reason and evidence, determine the approval route, record decisions, and move the task forward or backward.
- Fields updated: Reason, notes, evidence, approval route, approval status, approver, timestamps, and comments.
- Notification: Notify the next approver, delegate, inventory lead, or quality analyst.
- Exception: Unauthorized or duplicate approval submissions are retained as rejected evidence but do not alter the task.
Automation 6: Closure and reconciliation
- Trigger: Accepted adjustment confirmation or zero-variance completion.
- Conditions: All required approvals and fields are present.
- Actions: Close the task, update Item_Master, clear Open_Task, calculate processing time, and write an audit log entry.
- Fields updated: Final quantity, last-count date, variance history, confirmation ID, closed timestamp, and automation status.
- Notification: Send a completion message only for material or escalated cases.
- Exception: If the confirmed Shopify quantity does not match the expected final quantity, route the task to Manual Review.
Step 6: Add Approvals, Reminders, and Escalations
The representative approval thresholds are operational assumptions and should be replaced with values approved by finance and warehouse management.
- A zero final variance requires no approval and closes automatically.
- A confirmed variance below an absolute value of $25 and below five units routes to the inventory lead.
- A variance of $25 or more, five units or more, or two prior variances within 90 days routes to the operations manager.
- A variance of $250 or more, or three related occurrences within 90 days, requires operations approval and quality acknowledgment in parallel.
- The final Shopify inventory correction remains blocked until every required approval is complete.
The reminder workflow runs each business morning. It reads open tasks, compares the current time with Due_Date and the last reminder timestamp, and posts only when the configured interval has elapsed.
After 24 hours, an approver receives a reminder. After 48 hours, n8n checks Users for an active delegate. If a delegate exists, the task is reassigned and both users are notified. If no delegate is available, the operations manager receives an escalation.
A Return for Information decision changes the task to Returned for Information and restores ownership to the inventory lead. Reject changes the task to Rejected and requires the operations manager to close it without adjustment or reopen the investigation. Every decision retains the form response ID, approver email, timestamp, and comments.
Step 7: Add Documents and File Management
Routine counts do not require documents. Evidence is requested for material, recurring, damaged, mislabeled, or mixed-SKU variances.
- Create a restricted Drive root named
Warehouse Cycle Count Evidence. - Create year and month folders beneath the root.
- When a task first needs evidence, use n8n to create a Task_ID folder.
- Name files using
Task_ID_SKU_EvidenceType_YYYYMMDD. - Store only Drive file IDs and restricted links in Count_Tasks.
- Do not enable public or organization-wide links.
- Keep the original uploaded file and use Drive version history for replacements where practical.
- When an upload fails, retain the investigation as incomplete and notify the submitter rather than losing the rest of the response.
- Reject file types or sizes that conflict with the organization’s Google Workspace policy.
- Archive evidence according to the company’s inventory and financial record-retention policy.
If Google Forms file upload is unavailable under the organization’s policy, the form accepts a link to a file already stored in the restricted evidence folder. n8n verifies that the link refers to the approved Drive location before accepting it.
Step 8: Add Reporting and Operational Views
| View | Filter | Owner |
|---|---|---|
| New assignments | Status equals Assigned and Scheduled_Date is current week | Warehouse lead |
| Work awaiting action | Status is not Closed and Owner_Email is populated | Operations |
| Overdue tasks | Due_Date is before today and Status is not Closed | Inventory lead |
| Incomplete investigations | Status equals Awaiting Reason or Returned for Information | Inventory lead |
| Manual-review queue | Status equals Manual Review | Inventory lead |
| Rejected items | Status equals Rejected | Operations manager |
| Upcoming deadlines | Due_Date is within the next two working days | Warehouse team |
| Recently completed | Closed_At is within the last seven days | Operations and quality |
| Automation failures | Automation_Status equals Retry or Failed | System owner |
| Recurring variance | Variance_Count_90d is two or greater | Quality analyst |
The Dashboard reads only normalized tabs. It reports weekly tasks scheduled, count completion rate, median processing time, overdue count, confirmed variance units, signed and absolute variance value, top recurring SKUs, reasons by warehouse zone, Shopify event exceptions, and failed automation runs.
Rolling 30-day movement and 90-day variance measures refresh nightly. Pivot tables group by week, SKU, ABC class, reason, and owner. The quality analyst reviews trends weekly, while the inventory lead owns daily operational views.
Alerts are generated from record-level rules, not chart appearance. For example, the workflow alerts when failed automation records exceed zero, overdue tasks exceed ten, or the same SKU records three confirmed variances within 90 days.
Step 9: Add Security and Governance Controls
- Use least-privilege Shopify, Google, Slack, and n8n credentials.
- Restrict counters to forms and warehouse notifications. Do not grant them access to costs or hidden comparison quantities.
- Limit Count_Tasks editing to the automation identity, inventory lead, operations manager, and designated backup.
- Store all credentials in n8n’s encrypted credential store or an approved secret manager.
- Never write Shopify tokens, OAuth tokens, request signatures, or complete customer payloads to Sheets or Slack.
- Store only the customer-impact flag and Shopify order identifier needed for exception handling. Do not copy customer addresses into the count tracker.
- Review workbook sharing, Drive links, Slack application permissions, and n8n users quarterly.
- Remove former employee access from Google Workspace, Slack, Shopify, n8n, and Users promptly.
- Retain n8n execution logs according to operational need while avoiding unnecessary full-payload retention.
- Export or back up the workbook on an approved schedule and test restoration.
- Document every manual override with user, timestamp, previous status, new status, and reason.
- Do not send personal customer information, credentials, or unrestricted evidence files to an AI service.
- Require human approval for inventory adjustments, high-value write-offs, customer communications, and policy exceptions.
Step 10: Deploy and Test
- Build workflows against the test workbook and test forms.
- Use sanitized Shopify webhook samples or a Shopify test environment where available.
- Create test items covering A, B, and C classes, zero stock, duplicate SKUs, missing costs, inactive products, and open exceptions.
- Run technical tests for every workflow branch and failure route.
- Run user acceptance testing with one inventory lead, two counters, one approver, one quality user, and one customer-service user.
- Pilot 20 tasks for one zone before increasing to 90 weekly tasks.
- Compare each pilot task with Shopify and the physical bin before permitting inventory adjustments.
- Document form use, recount independence, reason selection, approval responsibilities, and failure recovery.
- Freeze configuration changes during the first production week except for urgent corrections.
- Activate workflows in dependency order: master sync, events, forms, task scheduling, reminders, approvals, closure, and reporting.
- Monitor every production execution during the pilot and review failed runs daily afterward.
- Keep the prior count workbook available as read-only rollback evidence. If the new workflow is suspended, stop task creation but preserve submitted records.
- Publish a launch message containing process ownership, support contacts, form links, and escalation rules.
Code and Configuration
The implementation uses native n8n triggers and application nodes where possible. Code nodes are used for deterministic normalization, scoring, and validation that would otherwise require many fragile expression nodes.
Configuration values
| Value | Purpose |
|---|---|
| YOUR_SHOP | Shopify shop subdomain. |
| YOUR_SUPPORTED_API_VERSION | A currently supported Shopify Admin API version selected during implementation. |
| YOUR_ADMIN_API_ACCESS_TOKEN | Stored only in an n8n credential. |
| YOUR_LOCATION_GID | Shopify warehouse location identifier. |
| YOUR_SPREADSHEET_ID | Production cycle-count workbook. |
| YOUR_TEST_SPREADSHEET_ID | Test workbook. |
| YOUR_FOLDER_ID | Restricted evidence root folder. |
| YOUR_SLACK_CHANNEL_ID | Approved warehouse or alert channel. |
Shopify inventory snapshot request
When the installed n8n Shopify node does not expose all required inventory quantities, use an HTTP Request node against this endpoint pattern:
POST https://YOUR_SHOP.myshopify.com/admin/api/YOUR_SUPPORTED_API_VERSION/graphql.json
Content-Type: application/json
X-Shopify-Access-Token: stored in the n8n Shopify credential
The GraphQL query requests variants and the warehouse inventory level:
query InventorySnapshot($cursor: String, $locationId: ID!) {
productVariants(first: 100, after: $cursor) {
edges {
cursor
node {
id
sku
displayName
product {
id
title
}
inventoryItem {
id
tracked
inventoryLevel(locationId: $locationId) {
id
quantities(names: ["available", "on_hand"]) {
name
quantity
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
Send variables as JSON:
{
"cursor": null,
"locationId": "YOUR_LOCATION_GID"
}
After each response, read pageInfo.hasNextPage. If it is true, set cursor to pageInfo.endCursor and request the next page. Stop only when hasNextPage is false.
Validate GraphQL errors before processing data. Monitor the response cost information when returned, pace requests according to available capacity, and retry temporary rate-limit or server errors after 5, 15, and 45 seconds. Authentication failures should not be retried repeatedly because they normally require credential repair.
Weekly cycle-count scheduler
Place the following JavaScript in an n8n Code node configured to run once for all Item_Master rows. The preceding Google Sheets node must return the full active item range using headers matching the script.
const CONFIG = {
MAX_TASKS: 90,
TASK_DUE_DAYS: 2,
CLASS_INTERVAL_DAYS: {
A: 30,
B: 90,
C: 180,
},
CLASS_POINTS: {
A: 40,
B: 25,
C: 10,
},
};
const now = new Date();
const scheduledDate = now.toISOString().slice(0, 10);
function toNumber(value, fallback = 0) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : fallback;
}
function toBoolean(value) {
return ["yes", "true", "1", "y"].includes(
String(value ?? "").trim().toLowerCase()
);
}
function parseDate(value) {
if (!value) return null;
const parsed = new Date(value);
return Number.isNaN(parsed.getTime()) ? null : parsed;
}
function daysSince(date) {
if (!date) return 999;
return Math.max(
0,
Math.floor((now.getTime() - date.getTime()) / 86400000)
);
}
function addDays(date, days) {
const result = new Date(date);
result.setUTCDate(result.getUTCDate() + days);
return result.toISOString().slice(0, 10);
}
function hashString(value) {
let hash = 2166136261;
for (let index = 0; index < value.length; index += 1) {
hash ^= value.charCodeAt(index);
hash = Math.imul(hash, 16777619);
}
return (hash >>> 0).toString(16).padStart(8, "0");
}
function cleanText(value) {
return String(value ?? "").trim();
}
const candidates = [];
for (const input of $input.all()) {
const row = input.json;
const itemKey = cleanText(row.Item_Key);
const sku = cleanText(row.SKU);
const locationGid = cleanText(row.Location_GID);
if (!itemKey || !sku || !locationGid) continue;
if (!toBoolean(row.Active)) continue;
if (toBoolean(row.Open_Task)) continue;
const abcClass = ["A", "B", "C"].includes(
cleanText(row.ABC_Class).toUpperCase()
)
? cleanText(row.ABC_Class).toUpperCase()
: "C";
const lastCountDate = parseDate(row.Last_Count_Date);
const elapsedDays = daysSince(lastCountDate);
const intervalDays = CONFIG.CLASS_INTERVAL_DAYS[abcClass];
const isDue = elapsedDays >= intervalDays;
const ordered30d = Math.max(0, toNumber(row.Units_Ordered_30d));
const fulfilled30d = Math.max(0, toNumber(row.Units_Fulfilled_30d));
const movementUnits = ordered30d + fulfilled30d;
const varianceCount90d = Math.max(
0,
toNumber(row.Variance_Count_90d)
);
const openExceptions = Math.max(
0,
toNumber(row.Open_Exception_Count)
);
const previousVarianceValue = Math.abs(
toNumber(row.Last_Absolute_Variance_Value)
);
const agePoints = Math.min(
25,
Math.round((elapsedDays / intervalDays) * 25)
);
const movementPoints = Math.min(
15,
Math.round(Math.log10(movementUnits + 1) * 6)
);
const variancePoints = Math.min(15, varianceCount90d * 5);
const exceptionPoints = Math.min(10, openExceptions * 5);
const riskScore =
CONFIG.CLASS_POINTS[abcClass] +
agePoints +
movementPoints +
variancePoints +
exceptionPoints;
const riskOverride =
openExceptions > 0 || previousVarianceValue >= 25;
if (!isDue && !riskOverride) continue;
let priority = "Normal";
if (riskScore >= 80 || openExceptions >= 2) {
priority = "Critical";
} else if (riskScore >= 60) {
priority = "High";
} else if (riskScore < 30) {
priority = "Low";
}
candidates.push({
row,
itemKey,
sku,
locationGid,
abcClass,
elapsedDays,
riskScore,
priority,
});
}
candidates.sort((left, right) => {
if (right.riskScore !== left.riskScore) {
return right.riskScore - left.riskScore;
}
if (right.elapsedDays !== left.elapsedDays) {
return right.elapsedDays - left.elapsedDays;
}
return left.sku.localeCompare(right.sku);
});
return candidates.slice(0, CONFIG.MAX_TASKS).map((candidate) => {
const row = candidate.row;
const taskHash = hashString(
`${candidate.itemKey}|${scheduledDate}`
);
return {
json: {
Task_ID: `CC-${scheduledDate.replaceAll("-", "")}-${taskHash}`,
Item_Key: candidate.itemKey,
SKU: candidate.sku,
Location_GID: candidate.locationGid,
Scheduled_Date: scheduledDate,
Due_Date: addDays(now, CONFIG.TASK_DUE_DAYS),
Priority: candidate.priority,
Risk_Score: candidate.riskScore,
ABC_Class: candidate.abcClass,
Owner_Email: cleanText(row.Primary_Counter_Email),
Status: "Assigned",
System_Qty_At_Assignment: Math.trunc(
toNumber(row.On_Hand_Qty)
),
Standard_Unit_Cost: toNumber(row.Standard_Unit_Cost),
Approval_Status: "Not Required",
Automation_Status: "Created",
Retry_Count: 0,
Created_At: now.toISOString(),
},
};
});
The next Google Sheets node performs a Task_ID lookup before appending. If no task exists, append the returned row and update Item_Master Open_Task to Yes. If the task exists, log an idempotent skip.
Test the script with at least one due item, one non-due item, one item with an open task, one missing SKU, and one risk override. The expected output is zero to 90 task objects. An empty output is valid when no items qualify.
Shopify event normalization
Use one workflow branch per event topic. Immediately after the trigger, add an Edit Fields node that sets Event_Type to one of orders_create, orders_updated, orders_cancelled, fulfillments_create, or fulfillments_update. Preserve the webhook identifier when available.
const source = $input.first().json;
const body = source.body ?? source;
const eventType = String(
source.Event_Type ?? source.event_type ?? ""
)
.trim()
.toLowerCase()
.replaceAll("/", "_");
const allowedTypes = new Set([
"orders_create",
"orders_updated",
"orders_cancelled",
"fulfillments_create",
"fulfillments_update",
]);
if (!allowedTypes.has(eventType)) {
throw new Error(`Unsupported Event_Type: ${eventType}`);
}
function text(value) {
return String(value ?? "").trim();
}
function hashString(value) {
let hash = 2166136261;
for (let index = 0; index < value.length; index += 1) {
hash ^= value.charCodeAt(index);
hash = Math.imul(hash, 16777619);
}
return (hash >>> 0).toString(16).padStart(8, "0");
}
function headerValue(headers, name) {
if (!headers || typeof headers !== "object") return "";
const target = name.toLowerCase();
const key = Object.keys(headers).find(
(candidate) => candidate.toLowerCase() === target
);
return key ? text(headers[key]) : "";
}
const headers = source.headers ?? {};
const resourceId = text(
body.admin_graphql_api_id ?? body.id ?? body.order_id
);
if (!resourceId) {
throw new Error("Shopify event has no resource identifier.");
}
const eventTime = text(
body.updated_at ?? body.created_at ?? new Date().toISOString()
);
const webhookId =
text(source.Webhook_ID) ||
headerValue(headers, "x-shopify-webhook-id");
const baseEventId =
webhookId ||
hashString(
`${eventType}|${resourceId}|${eventTime}|${text(body.status)}`
);
const rawStatus = text(
body.status ??
body.fulfillment_status ??
body.financial_status ??
"unknown"
);
const orderId = text(
body.order_id ??
body.order?.id ??
(eventType.startsWith("orders_") ? body.id : "")
);
const fulfilmentId = eventType.startsWith("fulfillments_")
? text(body.id)
: "";
const locationId = text(body.location_id ?? body.location?.id);
const orderNumber = text(body.name ?? body.order?.name);
const lineItems =
Array.isArray(body.line_items) && body.line_items.length > 0
? body.line_items
: [{}];
const failureLikeStatus = /(fail|error)/i.test(rawStatus);
const cancellationEvent = eventType === "orders_cancelled";
const output = [];
for (let index = 0; index < lineItems.length; index += 1) {
const line = lineItems[index];
const lineId = text(
line.admin_graphql_api_id ??
line.id ??
line.variant_id ??
`line-${index + 1}`
);
const sku = text(line.sku);
const quantityValue = Number(line.quantity ?? 0);
const quantity = Number.isFinite(quantityValue)
? Math.max(0, Math.trunc(quantityValue))
: 0;
let exceptionType = "";
if (!sku) {
exceptionType = "MISSING_SKU";
} else if (
eventType.startsWith("fulfillments_") &&
!locationId
) {
exceptionType = "MISSING_LOCATION";
} else if (failureLikeStatus) {
exceptionType = "FULFILMENT_STATUS_REVIEW";
}
let movementType = "STATUS_ONLY";
if (eventType === "orders_create") {
movementType = "ORDERED";
} else if (eventType === "fulfillments_create") {
movementType = "FULFILLED";
}
output.push({
json: {
Event_Key: `${baseEventId}:${hashString(lineId)}`,
Webhook_ID: webhookId,
Event_Type: eventType,
Event_Time: eventTime,
Shopify_Resource_ID: resourceId,
Shopify_Order_ID: orderId,
Shopify_Order_Number: orderNumber,
Shopify_Fulfilment_ID: fulfilmentId,
Shopify_Line_ID: lineId,
Shopify_Location_ID: locationId,
SKU: sku,
Quantity: quantity,
Movement_Type: movementType,
Raw_Status: rawStatus,
Exception_Type: exceptionType,
Operations_Flag: Boolean(exceptionType),
Customer_Service_Flag:
cancellationEvent || failureLikeStatus,
Processing_Status: "Normalized",
Processed_At: new Date().toISOString(),
},
});
}
return output;
After this Code node, look up Event_Key in Movement_Ledger. Append only when it does not exist. Then look up SKU and location in Item_Master. Set UNKNOWN_SKU or UNKNOWN_LOCATION when no valid match exists.
The workflow stores normalized fields rather than the full Shopify payload. Order-create and fulfilment-create events contribute to movement statistics. Update events revise status and support exception detection but do not add the entire quantity again.
Count validation Code node
The form-processing workflow looks up the task and merges the task fields with the form response. Place this code after the merge. It validates First Count and Recount submissions without changing external systems directly.
function clean(value) {
return String(value ?? "").trim();
}
function parseWholeCount(value) {
const parsed = Number(value);
if (
!Number.isFinite(parsed) ||
!Number.isInteger(parsed) ||
parsed < 0
) {
return null;
}
return parsed;
}
function invalid(row, message) {
return {
json: {
Task_ID: clean(row.Task_ID),
Submission_Key: clean(row.Submission_Key),
Validation_Result: "Rejected",
Validation_Message: message,
Task_Update_Allowed: false,
Processed_At: new Date().toISOString(),
},
};
}
const results = [];
for (const input of $input.all()) {
const row = input.json;
const taskId = clean(row.Task_ID);
const stage = clean(row.Submission_Type);
const status = clean(row.Task_Status ?? row.Status);
const submitter = clean(row.Submitter_Email).toLowerCase();
const owner = clean(row.Owner_Email).toLowerCase();
const firstCounter = clean(
row.First_Counter_Email
).toLowerCase();
const submittedCount = parseWholeCount(row.Submitted_Count);
const systemCount = parseWholeCount(
row.System_Qty_At_Count_Start
);
const firstCount = parseWholeCount(row.First_Count);
const movementDuringCount = [
"yes",
"true",
"1",
].includes(
clean(row.Movement_During_Count).toLowerCase()
);
if (!taskId) {
results.push(invalid(row, "Task ID is required."));
continue;
}
if (!submitter) {
results.push(
invalid(row, "Authenticated submitter email is required.")
);
continue;
}
if (submittedCount === null) {
results.push(
invalid(row, "Count must be a non-negative whole number.")
);
continue;
}
if (systemCount === null) {
results.push(
invalid(row, "The count-start system snapshot is missing.")
);
continue;
}
if (stage === "First Count") {
if (status !== "Counting") {
results.push(
invalid(
row,
`First Count is not valid while status is ${status}.`
)
);
continue;
}
if (owner && owner !== submitter) {
results.push(
invalid(row, "Submitter is not the assigned first counter.")
);
continue;
}
const variance = submittedCount - systemCount;
let nextStatus = "Closed";
if (movementDuringCount) {
nextStatus = "Manual Review";
} else if (variance !== 0) {
nextStatus = "Awaiting Recount";
}
results.push({
json: {
Task_ID: taskId,
Submission_Key: clean(row.Submission_Key),
Validation_Result: "Accepted",
Validation_Message: "",
Task_Update_Allowed: true,
First_Count: submittedCount,
First_Counter_Email: submitter,
First_Count_At: clean(row.Submitted_At),
Variance_1: variance,
Next_Status: nextStatus,
Requires_Recount:
variance !== 0 && !movementDuringCount,
Processed_At: new Date().toISOString(),
},
});
continue;
}
if (stage === "Recount") {
if (status !== "Awaiting Recount") {
results.push(
invalid(
row,
`Recount is not valid while status is ${status}.`
)
);
continue;
}
if (firstCount === null) {
results.push(
invalid(row, "The accepted first count is missing.")
);
continue;
}
if (submitter === firstCounter) {
results.push(
invalid(
row,
"Recount must be performed by a different employee."
)
);
continue;
}
const finalVariance = submittedCount - systemCount;
const countsAgree = submittedCount === firstCount;
let nextStatus = "Awaiting Reason";
if (movementDuringCount || !countsAgree) {
nextStatus = "Manual Review";
}
results.push({
json: {
Task_ID: taskId,
Submission_Key: clean(row.Submission_Key),
Validation_Result: "Accepted",
Validation_Message: "",
Task_Update_Allowed: true,
Recount_Qty: submittedCount,
Recount_Counter_Email: submitter,
Recount_At: clean(row.Submitted_At),
Final_Count: submittedCount,
Final_Variance: finalVariance,
Counts_Agree: countsAgree,
Next_Status: nextStatus,
Processed_At: new Date().toISOString(),
},
});
continue;
}
results.push(
invalid(row, `Unsupported submission type: ${stage}`)
);
}
return results;
Route records with Task_Update_Allowed equal to true to the Google Sheets update node. Route rejected records to a response-status update and optional Slack message. A temporary Sheets failure must not mark the form response as processed.
Control formulas
Assuming the documented Count_Tasks column order, these row formulas provide visible reconciliation checks. n8n also calculates canonical values so the workflow does not depend solely on formulas.
Q2: =IF(OR(L2="",N2=""),"",N2-L2)
U2: =IF(R2<>"",R2,N2)
V2: =IF(OR(L2="",U2=""),"",U2-L2)
X2: =IF(V2="","",V2*W2)
Copy formulas when each task is appended and protect the formula cells. Test using positive, negative, zero, and blank values. Locale-specific separators may need adjustment.
Deployment and troubleshooting
- Paste each script into the identified n8n Code node rather than a Google Apps Script editor.
- No external JavaScript dependencies are required.
- Replace configuration placeholders in credentials and workflow settings, not inside event payloads.
- Run each node with pinned test data before activating the workflow.
- Inspect n8n execution data, Automation_Log, and Failed_Events when a run fails.
- A missing header normally indicates that a Google Sheet column was renamed or omitted.
- A Shopify 401 or 403 response normally indicates an invalid credential or insufficient permission.
- A Shopify 429 or temporary server response should follow the configured retry schedule.
- A Sheets lookup returning multiple rows indicates duplicate keys and must enter Manual Review.
- Activate workflows only after test credentials and spreadsheet IDs have been replaced with production values.
Failure Handling and Operational Reliability
| Failure | Automated response | Manual recovery | Owner |
|---|---|---|---|
| Missing SKU | Create UNKNOWN_SKU exception and stop item update. | Correct Shopify SKU or approve a mapping, then replay. | Inventory lead |
| Duplicate webhook | Detect Event_Key and record Ignored Duplicate. | No action unless duplicate reconciliation finds conflicting data. | System owner |
| Duplicate task | Skip append when Task_ID already exists. | Review Open_Task and close the obsolete record if required. | Inventory lead |
| Invalid count | Reject submission without changing task status. | Counter submits a corrected whole-unit count. | Assigned counter |
| Movement during count | Set Movement_During_Count and route to Manual Review. | Review movements and perform another controlled count. | Inventory lead |
| Partial workflow completion | Leave response unprocessed, record completed action IDs, and retry idempotently. | Replay from the failed node after checking destination state. | System owner |
| Shopify API failure | Retry temporary failures after 5, 15, and 45 seconds. | Repair credential or API query and replay Failed_Events. | System owner |
| Expired Google credential | Stop writes and send an automation alert. | Reconnect OAuth, test access, and replay pending records. | Google Workspace administrator |
| Unavailable approver | Check delegate after the escalation threshold. | Operations manager assigns a temporary approver. | Operations manager |
| Failed folder creation | Keep task awaiting evidence and retry. | Create the folder manually and enter its Drive ID. | System owner |
| Failed file upload | Retain investigation as incomplete. | Upload to the approved folder and resubmit the evidence link. | Investigator |
| Invalid email | Reject assignment or notification mapping. | Correct Users and rerun assignment. | Inventory lead |
| Slack notification failure | Retry separately without rolling back the business record. | Review the queue and contact the owner through an approved fallback. | System owner |
| Rate limit | Pause according to response guidance and retry with backoff. | Reduce concurrency or reschedule non-urgent synchronization. | System owner |
| Timeout | Retry using the same idempotency key. | Verify destination state before manual replay. | System owner |
| Repeated unrecoverable failure | Move record to Failed_Events and notify operations. | Correct the root cause and use the controlled replay workflow. | System owner |
The replay workflow accepts a Failed_Event_ID, verifies the current destination state, and re-enters processing using the original Event_Key or Submission_Key. It never creates a new identity for the same business event.
A nightly reconciliation compares unprocessed form rows, open tasks, Item_Master Open_Task flags, failed event counts, and stale Shopify synchronization timestamps. Differences enter the manual-review queue. This catches cases where an external write succeeded but the n8n execution ended before its final status update.
A Complete Example
On July 20, 2026, the scheduler evaluated SKU BIN-14-WHT at the warehouse location. It was an A-class item, had 184 units of recent movement, and had one prior variance. Its risk score placed it within the weekly limit.
- n8n generated Task ID
CC-20260720-7c91a24fand appended it to Count_Tasks. - The task was assigned to warehouse associate Jordan Kim with a due date of July 22.
- Slack posted the Task ID, SKU, bin, due date, and Count Capture form link. The message did not include the Shopify quantity.
- Jordan submitted Start Count. n8n queried the current Shopify inventory level and recorded 84 units as System_Qty_At_Count_Start.
- Jordan counted 79 units and submitted First Count. n8n found no fulfilment event for that SKU and location during the count window.
- The calculated variance was 79 minus 84, or negative 5 units. At a standard cost of $8.50, the signed variance value was negative $42.50.
- The task moved to Awaiting Recount and was assigned to a different associate, Kai Morgan.
- Kai submitted a recount of 79 units. Because the counts agreed and the final variance remained non-zero, the task moved to Awaiting Reason.
- Marcus inspected the bin and nearby locations. He found five units placed in an adjacent bin with a similar label. He selected MISBINNED, entered the investigation notes, attached a restricted photograph, and recorded relabeling as the corrective action.
- Because the absolute variance value exceeded the representative $25 threshold, n8n routed approval to Elena Ruiz.
- Elena approved the correction through the approval form. The response ID, approver email, timestamp, and comments were written to Count_Tasks.
- The task changed to Approved for Adjustment. Marcus corrected the Shopify quantity to 79 and submitted the Adjustment Confirmation form.
- n8n verified the workflow status and confirmation, closed the task, cleared Item_Master Open_Task, and updated Last_Count_Date and variance history.
- The evidence remained linked to the task. The MISBINNED reason contributed to the quality dashboard’s recurring-cause report.
If a fulfilment event had occurred between count start and first-count submission, the workflow would have set Movement_During_Count to Yes and routed the task to Manual Review instead of treating negative five as a confirmed physical variance.
Implementation Cost
All amounts below are representative planning assumptions, not vendor quotes or verified client results. Existing Shopify, Google Workspace, and Slack subscriptions may already be business operating costs, but they still require suitable features and administration.
| Category | Assumption | Estimated amount |
|---|---|---|
| Internal discovery and data preparation | 22 hours at $40 per hour | $880 |
| Internal user testing and training | 18 hours at $40 per hour | $720 |
| Professional workflow implementation | 80 hours at $110 per hour | $8,800 |
| Total representative implementation | External implementation plus internal participation | $10,400 |
The professional implementation assumption includes process design, workbook construction, form configuration, n8n workflows, Shopify integration, Slack routing, testing support, technical documentation, and launch assistance.
| Category | Assumption | Estimated amount |
|---|---|---|
| n8n capacity allowance | Planning allowance for the required executions and features | $40 |
| Existing Shopify, Workspace, and Slack | No incremental license assumed for the representative calculation | $0 incremental |
| Core Shopify API access | No separate usage fee assumed for the configured app access | $0 incremental |
| Internal maintenance | Four hours per month at a loaded rate of $32 | $128 in staff capacity |
| Optional AI usage | Controlled monthly usage allowance | $8 |
An internal-only implementation could reduce external spending but would still require substantial staff time for architecture, integration, security, testing, documentation, and maintenance. The cost should not be treated as zero simply because some applications are already licensed.
Estimated Time and Cost Savings
The calculation uses the following representative assumptions:
- 360 cycle-count tasks per month.
- 12 minutes of current administrative handling per task.
- 4 minutes of routine handling after core automation.
- 12 percent of tasks require variance exception work.
- 10 additional manual minutes for each exception.
- 4 monthly hours for monitoring and maintenance.
- $32 loaded hourly labour cost.
- $40 recurring monthly automation cost.
- $10,400 one-time implementation cost.
Current monthly labour hours: Monthly volume × current minutes per record ÷ 60
360 × 12 ÷ 60 = 72 hours
New monthly labour hours: Monthly volume × new minutes per record ÷ 60, plus exception handling and maintenance
Routine handling: 360 × 4 ÷ 60 = 24 hours
Exception handling: 360 × 12% × 10 ÷ 60 = 7.2 hours
Maintenance: 4 hours
Total new monthly labour: 24 + 7.2 + 4 = 35.2 hours
Monthly hours recovered: Current monthly labour hours minus new monthly labour hours
72 − 35.2 = 36.8 hours
Estimated monthly labour value: Monthly hours recovered × loaded hourly labour cost
36.8 × $32 = $1,177.60
Net estimated monthly value: Monthly labour value minus recurring tool costs
$1,177.60 − $40 = $1,137.60
Estimated payback period: One-time implementation cost ÷ net estimated monthly value
$10,400 ÷ $1,137.60 = approximately 9.1 months
| Measure | Result |
|---|---|
| Current labour | 72 hours |
| New labour including exceptions and maintenance | 35.2 hours |
| Hours recovered | 36.8 hours |
| Gross labour value | $1,177.60 |
| Recurring automation allowance | $40 |
| Net estimated monthly value | $1,137.60 |
| Estimated payback | 9.1 months |
Recovered time does not automatically reduce payroll. It can represent additional counting capacity, quicker investigations, reduced overtime, fewer administrative follow-ups, and lower dependence on one employee.
Non-financial benefits include clearer ownership, more independent recounts, consistent variance reasons, better audit evidence, fewer incomplete records, earlier identification of recurring problems, and more targeted communication with customer service.
Readers should replace volume, handling time, exception rate, review time, maintenance time, labour cost, subscription cost, and implementation cost with their own measured figures.
Adding AI to the Automation
AI is introduced only after the deterministic cycle-count workflow is stable. Scheduling thresholds, count validation, exact quantity comparison, duplicate keys, permissions, approval limits, and inventory adjustments remain rule-based.
Potential AI applications include summarizing investigation notes, suggesting a reason category, comparing recent variance descriptions, detecting semantically similar recurring issues, and identifying missing investigation questions.
Normal automation already provides scheduling, forms, validation, recount routing, approval evidence, reminders, Shopify synchronization, Slack notifications, and reporting. AI adds value only when staff must interpret unstructured notes and event context.
The Recommended AI Enhancement
The recommended enhancement is an investigation assistant that reviews sanitized variance history and suggests a structured summary, likely category, recurrence pattern, and follow-up questions. It does not approve inventory changes or overwrite the investigator’s selected reason.
- Trigger: A task enters Awaiting Reason or high-risk Quality Review.
- AI input: SKU, anonymous location code, final variance, unit cost, prior reason codes, sanitized notes, movement timestamps, and normalized event exceptions.
- System instruction: Analyze only supplied evidence, distinguish facts from suggestions, and never approve an adjustment.
- Expected output: Valid JSON matching the configured schema.
- Validation: Parse JSON, enforce allowed categories, verify confidence range, and limit string lengths.
- Record update: Write to separate AI_Suggestion fields without changing human-entered reason or status.
- Human review: Inventory lead accepts, edits, or rejects the suggestion.
- Low confidence: Confidence below 0.75 creates follow-up questions but no category recommendation.
- Prohibited data: Customer names, addresses, email addresses, payment data, credentials, and unrestricted evidence files.
- Failure behavior: Continue with the normal investigation form when the AI service is unavailable.
Use this reusable system instruction:
You are an inventory variance analysis assistant.
Analyze only the evidence supplied in the user message. Do not invent events, counts, causes, or corrective actions. Distinguish observed facts from suggested explanations.
You may suggest an investigation category and follow-up questions. You must not approve an inventory adjustment, make an accounting decision, contact a customer, or change a workflow status.
Return only JSON that matches the supplied schema. If evidence is insufficient, use category OTHER, lower the confidence score, and explain what information is missing.
Use this user prompt template:
Review this warehouse cycle-count variance.
Current task:
{{ JSON.stringify($json.current_task) }}
Normalized Shopify events during the review window:
{{ JSON.stringify($json.shopify_events) }}
Prior sanitized variances for the same SKU:
{{ JSON.stringify($json.prior_variances) }}
Allowed categories:
RECEIVING_ERROR, PICK_SHORT, MISBINNED, DAMAGE,
RETURN_NOT_PROCESSED, BUNDLE_MAPPING, SKU_MAPPING,
SYSTEM_TIMING, COUNT_ERROR, OTHER
Identify:
1. A concise factual summary.
2. The most likely category, if supported.
3. Evidence supporting the suggestion.
4. Missing information.
5. Follow-up questions.
6. Whether the pattern appears recurring.
7. A confidence score from 0 to 1.
Do not approve or reject an inventory adjustment.
The required structured output is:
{
"summary": "Five units were missing from the assigned bin after two matching counts.",
"suggested_category": "MISBINNED",
"supporting_evidence": [
"First count and recount both recorded 79 units.",
"The system snapshot was 84 units.",
"A prior variance used the same reason category."
],
"missing_information": [
"Adjacent bins have not yet been checked."
],
"follow_up_questions": [
"Were nearby bins and receiving staging areas inspected?"
],
"recurring_pattern": true,
"confidence": 0.82,
"human_review_required": true
}
In n8n, aggregate the sanitized context, send it to the approved AI model node with structured output enabled, parse the response, and validate every field. If parsing fails, write AI_Status as Failed Validation and continue with the ordinary investigation workflow.
Log task ID, model identifier, request time, token or usage measure when available, validation result, confidence, and reviewer disposition. Do not log full customer or credential data.
Benefits of the AI Enhancement
- Reduces the time required to read several prior variance notes.
- Produces more consistent summaries for quality reporting.
- Suggests semantically similar recurring issues even when staff used different wording.
- Identifies missing investigation steps before approval.
- Supports prioritization by presenting evidence and uncertainty together.
- Improves search and reporting over historical free-text notes.
These are AI-specific benefits. They do not replace the larger benefits already delivered by risk-based scheduling, form validation, recount rules, workflow ownership, event synchronization, approval routing, and reporting.
What Remains Rule-Based or Human-Controlled
- Count comparison: Exact arithmetic remains deterministic.
- Duplicate prevention: Event and task keys remain rule-based.
- Recount requirement: Any configured variance follows an explicit rule.
- Approval route: Value, quantity, and recurrence thresholds remain deterministic.
- Final reason selection: The inventory lead reviews evidence and chooses the recorded cause.
- Inventory adjustment: An authorized employee makes the final Shopify change.
- Accounting treatment: Finance decides whether a variance requires a financial entry.
- Customer communication: Customer service reviews the order context and controls outbound communication.
- Safety or disciplinary decisions: Managers handle these under company policy rather than AI output.
- Risk acceptance: The operations manager decides whether unresolved evidence is sufficient.
These controls remain human-controlled because they can affect inventory valuation, financial records, employees, customers, and audit evidence.
Estimating the Additional Value of AI
The representative core workflow produces approximately 43 variance exceptions per month. Without AI, assume each exception requires 10 minutes of investigation preparation and review. With AI, assume the same human remains responsible but saves three minutes reviewing prior notes and preparing a summary.
Additional time recovered: 43 × 3 ÷ 60 = 2.15 hours per month.
Additional labour capacity value: 2.15 × $32 = $68.80 per month.
Less representative AI usage allowance: $68.80 − $8 = $60.80 net monthly capacity value.
| Process | Estimated monthly labour | Human involvement |
|---|---|---|
| Original manual process | 72 hours | Full manual preparation, comparison, follow-up, and reporting. |
| Core automation | 35.2 hours | Physical counts, investigations, approvals, corrections, and exception review. |
| Core automation with AI | Approximately 33.05 hours | Same decisions, with faster review of unstructured history. |
For planning, Larkspur assumes that 15 percent of AI suggestions may require material editing and 3 percent may fail validation or service delivery. These are pilot assumptions, not guaranteed performance. Actual correction and failure rates must be measured through sampled human review.
Testing Checklist
Use sample or sanitized data before processing real warehouse or customer information.
| Test | Expected result |
|---|---|
| Normal assignment and zero count variance | Task closes and Item_Master updates. |
| Missing required field | Form or n8n rejects the submission without changing the task. |
| Negative or decimal count | Submission is rejected with a clear validation message. |
| Duplicate form submission | Submission_Key prevents a second task update. |
| Duplicate Shopify event | Event_Key is logged as duplicate and movement is not counted twice. |
| Unknown SKU | Event enters the operations exception queue. |
| Failed authentication | Workflow stops, records the error, and alerts the system owner. |
| Expired credential | Credential is repaired before controlled replay. |
| Temporary API failure | Workflow retries using the same idempotency key. |
| Unavailable approver | Reminder and delegate rules execute. |
| Approval rejection | Task enters Rejected and no adjustment confirmation is accepted. |
| Return for information | Ownership returns to the inventory lead. |
| Reassignment | New owner receives the task and audit history records the change. |
| Overdue item | Dashboard and reminder workflow identify it. |
| Reminder timing | No duplicate reminder is sent within the configured interval. |
| Escalation timing | Delegate or manager receives the escalation after the threshold. |
| Same employee attempts recount | Recount is rejected and task remains Awaiting Recount. |
| First and recount values disagree | Task enters Manual Review. |
| Shopify movement during count | Movement flag is set and variance is not automatically confirmed. |
| Failed file upload | Investigation remains incomplete and can be recovered. |
| Failed folder creation | Workflow retries and reports the missing folder. |
| Failed Slack notification | Business record remains valid and notification retries separately. |
| Unauthorized approver | Decision is rejected and logged. |
| Malformed AI output | AI validation fails and normal human investigation continues. |
| Inaccurate AI suggestion | Reviewer rejects or edits the suggestion without changing the source evidence. |
| AI service unavailable | Core workflow continues without AI. |
| Successful approved adjustment | Confirmation closes the task and updates the item history. |
| Reporting accuracy | Dashboard totals reconcile with source task and event rows. |
| Audit record | Task retains submitters, timestamps, decisions, external IDs, and evidence. |
| Retry behavior | Temporary errors retry without duplicate destination records. |
Ongoing Maintenance
The inventory control lead owns the business process. A technical operations employee is the backup automation owner. The quality analyst owns reason-code and recurrence reporting.
| Frequency | Maintenance activity |
|---|---|
| Daily | Review failed runs, stale count windows, overdue tasks, unprocessed form responses, and failed notifications. |
| Weekly | Reconcile task totals, review recurring variances, sample approval evidence, and confirm Shopify synchronization freshness. |
| Monthly | Archive old Movement_Ledger rows, review execution volume and cost, test a controlled failure, and update operational documentation. |
| Quarterly | Review permissions, Slack application access, Google sharing, n8n users, delegates, reason codes, approval thresholds, and credential rotation requirements. |
| Semi-annually | Test workbook restoration, replay procedures, integration credentials, and rollback documentation. |
| After a platform change | Retest Shopify fields, API version, n8n nodes, Google headers, Slack destinations, and all critical workflow branches. |
| When an employee leaves | Deactivate the user, remove application access, update ownership, and assign outstanding tasks. |
| AI pilot weekly | Sample outputs, measure correction rate, inspect prohibited-data controls, and review usage cost. |
Archiving is particularly important because Shopify event lines can grow faster than cycle-count records. Completed movement rows can be exported to a controlled archive while the workbook retains the rolling period needed for operations and reporting.
When to Move to Dedicated Software
The implementation remains appropriate while one warehouse can operate within the workbook’s performance, permission, and concurrency limits. It should be reassessed when several of these conditions appear:
- Transaction or movement volume causes slow Google Sheets reads and writes.
- Multiple warehouses require local workflows, separate time zones, or complex transfer counting.
- Several counters need simultaneous mobile scanning and offline operation.
- Bin-level inventory, lot tracking, serial numbers, expiry dates, or regulated traceability become mandatory.
- Formal audit requirements demand database-enforced relationships and immutable logs.
- Approval permissions become too detailed for form and spreadsheet controls.
- Exception rates or custom workflow branches create excessive n8n maintenance.
- Customer-facing or supplier-facing portals are required.
- Real-time inventory reservation and warehouse execution must be coordinated in one platform.
- Operations requires vendor-supported service levels and application support.
- Security risk increases because too many users or integrations require workbook access.
- Advanced forecasting, slotting, replenishment, labor management, or warehouse analytics become core requirements.
Potential next steps include a warehouse management system, inventory management platform, no-code operational database, or a custom application backed by PostgreSQL or another managed database. Migration is not automatic. The existing workflow can remain useful until measured limitations justify the change.
Implementation Checklist
- Confirm cycle-count volume, ABC rules, risk factors, and approval thresholds.
- Confirm Shopify, Google Workspace, n8n, Google Drive, and Slack as the selected tools.
- Create production and test accounts, workbooks, forms, folders, and credentials.
- Define administrators, counters, approvers, delegates, quality users, and customer-service owners.
- Build Item_Master, Count_Tasks, Movement_Ledger, exception, user, configuration, and log structures.
- Configure blind count, investigation, approval, and adjustment-confirmation intake.
- Implement deterministic Item_Key, Task_ID, Event_Key, and Submission_Key values.
- Connect Shopify products, inventory, orders, and fulfilments to n8n.
- Map all source and destination fields.
- Build nightly synchronization and weekly scheduling workflows.
- Build count-start, first-count, recount, investigation, approval, and closure workflows.
- Configure approval thresholds, sequential decisions, parallel quality review, and delegates.
- Configure reminders, overdue rules, and escalation timing.
- Configure Slack channels, user mappings, and notification suppression for normal events.
- Create restricted Drive evidence folders and retention rules.
- Create operational views, pivots, charts, reconciliation measures, and failure reports.
- Apply least-privilege permissions and credential controls.
- Install and test the n8n Code nodes and Shopify request configuration.
- Test normal, duplicate, invalid, rejected, unavailable, failed, and recovered scenarios.
- Pilot one warehouse zone before full activation.
- Record representative implementation and recurring cost assumptions.
- Replace savings assumptions with measured internal figures.
- Add the optional AI enhancement only after the deterministic workflow is reliable.
- Assign primary and backup maintenance owners.
- Document the volume, security, workflow, and reporting criteria that would justify dedicated software.
Get a FREE
Proof of Concept
& Consultation
No Cost, No Commitment!


