Skip to content
tsh-stock-realtime logo

TSH Stock Real-Time Synchronization

tsh-stock-realtime

Real-time stock synchronization for TSH Clients Console. Use when: (1) Debugging stock sync issues (webhooks not firing, stock not updating) (2) Understanding webhook event handling (3) Implementing stock-affecting features (4) Troubleshooting stock discrepancies between Zoho and app (5) Adding n...

mattnigh/skills_collection0installs24stars

SKILL.md

Full skill instructions

TSH Stock Real-Time Synchronization

Architecture Overview

┌─────────────────────────────────────────────────────────────────────┐
│                   TSH STOCK SYNC ARCHITECTURE                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  LAYER 1: WEBHOOKS (Instant, < 5 seconds)                          │
│  ├── Zoho transaction occurs                                        │
│  ├── POST /​api/​webhooks/​zoho                                        │
│  ├── quickSyncStock(itemIds)                                        │
│  └── Update Redis + Revalidate ISR                                  │
│                                                                     │
│  LAYER 2: PERIODIC SYNC (Every 15 minutes)                         │
│  ├── Vercel Cron job                                                │
│  ├── POST /​api/​sync/​stock                                           │
│  ├── syncStockFromBooks()                                           │
│  └── Full cache refresh                                             │
│                                                                     │
│  LAYER 3: ON-DEMAND SYNC (Manual trigger)                          │
│  ├── Admin triggers via API                                         │
│  ├── GET /​api/​sync/​stock?action=sync&force=true                     │
│  └── Immediate full refresh                                         │
│                                                                     │
│  LAYER 4: HEALTH MONITORING (Continuous)                           │
│  ├── GET /​api/​sync/​stock?action=status                              │
│  ├── Check: itemCount > 400                                         │
│  ├── Check: cache age < 4 hours                                     │
│  └── Alert if unhealthy                                             │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Webhook Event Reference

Stock-Affecting Transactions

TransactionStock EffectWebhook EventHandler
Invoice CreatedDecreasesinvoice.createdrevalidateProducts()
Invoice UpdatedMay changeinvoice.updatedrevalidateProducts()
Bill CreatedIncreasesbill.createdrevalidateProducts()
Bill UpdatedMay changebill.updatedrevalidateProducts()
Sales Order CreatedCommits stocksalesorder.createdrevalidateProducts()
Sales Order UpdatedMay releasesalesorder.updatedrevalidateProducts()
Credit Note CreatedMay increasecreditnote.createdrevalidateProducts()
Inventory AdjustmentChangesinventoryadjustment.createdrevalidateProducts()
Sales Return ReceivedIncreasessalesreturnreceive.createdrevalidateProducts()
Package ShippedPhysical decreasepackage.shippedrevalidateProducts()
Item UpdatedMay changeitem.updatedrevalidateProducts()

Webhook Handler Location

// src/​app/​api/​webhooks/​zoho/​route.ts

export async function POST(request: Request) {
  const payload = await request.json();
  const { event_type, resource_name, resource_id } = payload;

  // Extract affected item IDs from payload
  const itemIds = extractItemIds(payload);

  switch (resource_name) {
    case "invoice":
    case "bill":
    case "salesorder":
    case "creditnote":
    case "inventoryadjustment":
    case "salesreturnreceive":
    case "package":
      // Sync stock for affected items
      await quickSyncStock(itemIds);
      await revalidateTag('products');
      break;

    case "item":
      // Direct item update
      await quickSyncStock([resource_id]);
      await revalidateTag('products');
      break;
  }
}

Quick Sync Function

// src/​lib/​zoho/​stock-cache.ts

export async function quickSyncStock(
  itemIds: string[],
  reason: string
): Promise<void> {
  const WHOLESALE_LOCATION_NAME = 'Main WareHouse';

  for (const itemId of itemIds) {
    try {
      // Fetch item with locations from Zoho Books
      const item = await getItemWithLocations(itemId);

      // Extract warehouse-specific stock
      const location = item.locations?.find(
        loc => loc.location_name === WHOLESALE_LOCATION_NAME
      );
      const stock = location?.location_available_for_sale_stock || 0;

      // Update Redis cache
      await redis.set(`stock:${itemId}`, stock, { ex: STOCK_CACHE_TTL });

      console.log(`[quickSyncStock] ${itemId}: ${stock} (${reason})`);
    } catch (error) {
      console.error(`[quickSyncStock] Failed for ${itemId}:`, error);
    }
  }
}

Unified Stock Functions

ALWAYS Use These Functions

// For single item (product detail page)
import { getUnifiedStock } from '@/​lib/​zoho/​stock-cache';

const { stock, source } = await getUnifiedStock(itemId, {
  fetchOnMiss: true,      // Fetch from API if not in cache
  context: 'product-detail',
});

// source: 'cache' | 'api' | 'unavailable'
// For multiple items (shop list page)
import { getUnifiedStockBulk } from '@/​lib/​zoho/​stock-cache';

const stockMap = await getUnifiedStockBulk(itemIds, {
  context: 'shop-list',
});

// Returns: Map<itemId, stock>

Why Unified Functions?

PROBLEM SOLVED:
  - Shop list showed different stock than product detail page
  - Root cause: Different fallback sources when Redis cache missed
  - List fell back to Books API (total across ALL warehouses)
  - Detail fell back to Inventory API (warehouse-specific)

SOLUTION:
  - Unified functions use ONLY Redis cache as stock source
  - On cache miss, detail page fetches from API and caches result
  - Next list page load sees the cached value
  - NEVER fall back to Books item.available_stock

Stock Data Flow

┌──────────────────────────────────────────────────────────────────┐
│                     STOCK DATA FLOW                              │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ZOHO BOOKS/​INVENTORY                                            │
│  │                                                               │
│  ├── Transaction occurs (Invoice, Bill, etc.)                    │
│  │   │                                                           │
│  │   └──► Webhook fires to /​api/​webhooks/​zoho                    │
│  │        │                                                      │
│  │        ├── Extract item IDs from payload                      │
│  │        │                                                      │
│  │        └──► quickSyncStock(itemIds)                           │
│  │             │                                                 │
│  │             ├── Fetch item with locations from API            │
│  │             │                                                 │
│  │             ├── Extract Main WareHouse stock                  │
│  │             │   └── location_available_for_sale_stock         │
│  │             │                                                 │
│  │             └──► Update Redis cache                           │
│  │                  │                                            │
│  │                  └──► Revalidate Next.js ISR cache            │
│  │                                                               │
│  └── Periodic sync (every 15 min)                                │
│      │                                                           │
│      └──► Full cache refresh from Books API                      │
│                                                                  │
│  REDIS CACHE (Upstash)                                           │
│  │                                                               │
│  ├── Key: stock:{itemId}                                         │
│  ├── Value: number (warehouse-specific stock)                    │
│  ├── TTL: 4 hours (14400 seconds)                                │
│  │                                                               │
│  └──► Read by: getUnifiedStock(), getUnifiedStockBulk()          │
│                                                                  │
│  NEXT.JS APPLICATION                                             │
│  │                                                               │
│  ├── Shop List Page                                              │
│  │   └── getUnifiedStockBulk(itemIds) → Redis                    │
│  │                                                               │
│  └── Product Detail Page                                         │
│      └── getUnifiedStock(itemId) → Redis → API (if miss)         │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Cache Configuration

SettingValueNotes
Stock Cache TTL4 hours (14400s)Reduced from 24h for freshness
Redis ProviderUpstashhttps://fine-mole-41883.upstash.io
Key Patternstock:{itemId}One key per item
Freshness Check10 minutesFor periodic sync trigger
Sync FrequencyEvery 15 minutesVercel Cron

Debugging Stock Sync

Check Cache Status

# Check cache health
curl "https://www.tsh.sale/​api/​sync/​stock?action=status"

# Expected response:
{
  "status": "healthy",
  "itemCount": 450,
  "lastSync": "2025-12-11T10:30:00Z",
  "cacheAge": "2 hours"
}

Force Full Sync

# Trigger full sync
curl "https://www.tsh.sale/​api/​sync/​stock?action=sync&secret=tsh-stock-sync-2024&force=true"

# Expected response:
{
  "success": true,
  "itemsSynced": 450,
  "duration": "45s"
}

Revalidate Caches

# Revalidate all caches
curl "https://www.tsh.sale/​api/​revalidate?tag=all&secret=tsh-revalidate-2024"

# Revalidate products only
curl "https://www.tsh.sale/​api/​revalidate?tag=products&secret=tsh-revalidate-2024"

Check Specific Item Stock

# Debug specific item
curl "https://www.tsh.sale/​api/​debug/​stock?itemId=2646610000109854052"

# Expected response:
{
  "itemId": "2646610000109854052",
  "cachedStock": 15,
  "apiStock": 15,
  "source": "cache",
  "warehouse": "Main WareHouse"
}

Troubleshooting

Stock Shows 0 When It Shouldn't

CheckCommandFix
Cache empty??action=statusRun full sync
Wrong warehouse?Check location_nameMust be "Main WareHouse"
Stale cache?Check lastSyncForce sync if > 4h old
Webhook not firing?Check Zoho webhook logsReconfigure webhook

Stock Discrepancy Between List/​Detail

CauseSymptomsFix
Cache missDetail shows differentRun sync, check TTL
Wrong functionUsing deprecated fnUse getUnifiedStock()
Books fallbackList shows ALL warehouseNever use item.available_stock

Webhook Not Updating Stock

  1. Check Zoho Webhook Configuration

    • Go to Zoho Settings → Webhooks
    • Verify URL: https://www.tsh.sale/​api/​webhooks/​zoho
    • Verify events are enabled
  2. Check Vercel Logs

    vercel logs --follow | grep webhook
    
  3. Verify Webhook Payload

    • Add logging to webhook handler
    • Check if resource_name matches expected
  4. Test Webhook Manually

    curl -X POST https://www.tsh.sale/​api/​webhooks/​zoho \
      -H "Content-Type: application/​json" \
      -d '{"resource_name":"item","event_type":"updated","resource_id":"123"}'
    

Adding New Stock-Affecting Feature

When adding features that affect stock:

1. Identify Webhook Events

Example: Adding "Stock Transfer" feature
Events needed:
  - inventorytransfer.created
  - inventorytransfer.updated

2. Update Webhook Handler

// src/​app/​api/​webhooks/​zoho/​route.ts

case "inventorytransfer":
  const itemIds = extractItemIdsFromTransfer(payload);
  await quickSyncStock(itemIds, `stock transfer: ${eventType}`);
  await revalidateProducts(`stock transfer: ${eventType}`);
  break;

3. Test the Flow

# 1. Create test transaction in Zoho
# 2. Check webhook received
curl "https://www.tsh.sale/​api/​debug/​webhook-log"

# 3. Verify stock updated
curl "https://www.tsh.sale/​api/​debug/​stock?itemId=<ITEM_ID>"

Golden Rules

  1. Single Source of Truth: ALWAYS use getUnifiedStock() or getUnifiedStockBulk()

  2. Never Use Books Fallback: NEVER use item.available_stock (combines ALL warehouses)

  3. Warehouse Isolation: ONLY show stock from "Main WareHouse" (ID: 2646610000000077024)

  4. Cache Before Display: Ensure Redis cache is populated before displaying stock

  5. Graceful Degradation: Show "Check availability" if stock unavailable, NOT zero

  6. Sync Before Deploy: Run full stock sync before deploying stock-related changes

Related Files

FilePurpose
src/​lib/​zoho/​stock-cache.tsStock caching, unified functions
src/​lib/​zoho/​products.tsProduct fetching, stock extraction
src/​app/​api/​webhooks/​zoho/​route.tsWebhook handler
src/​app/​api/​sync/​stock/​route.tsSync endpoint
.claude/​STOCK_RULES.mdStock display rules
.claude/​skills/​tsh-stock/​SKILL.mdBasic stock skill

Checklist for Stock Changes

Before modifying stock-related code:

  • Read .claude/​STOCK_RULES.md
  • Check Redis cache status
  • Understand which webhook events affect the change
  • Use unified stock functions
  • Test on staging first
  • Run full sync after deploy
  • Verify list/​detail stock consistency

More skills from mattnigh

spring-framework-patterns logo
mattnigh/skills_collection

spring-framework-patterns

Comprehensive Spring Framework and Spring Boot best practices including dependency injection patterns, bean lifecycle and scopes, REST API development, Spring Data JPA, service layer design, Spring Security, testing strategies, caching, AOP, async processing, error handling, and common anti-patte...

24 0
View

TypeScript厳格モードによる型安全性設計を専門とするスキル。 📖 参照書籍: - 『Effective TypeScript』(Dan Vanderkam): 型設計 📚 リソース参照: - `resources/Level1_basics.md`: レベル1の基礎ガイド - `resources/Level2_intermediate.md`: レベル2の実務ガイド - `resources/Level3_advanced.md`: レベル3の応用ガイド - `resources/Level4_expert.md`: レベル4の専門ガイド - `resources/di...

24 0
View
deploying-cloud-k8s logo
mattnigh/skills_collection

deploying-cloud-k8s

Deploys applications to cloud Kubernetes (AKS/GKE/DOKS) with CI/CD pipelines. Use when deploying to production, setting up GitHub Actions, troubleshooting deployments. Covers build-time vs runtime vars, architecture matching, and battle-tested debugging.

24 0
View
moai-alfred-agent-guide logo
mattnigh/skills_collection

moai-alfred-agent-guide

19-agent team structure, decision trees for agent selection, Haiku vs Sonnet model selection, and agent collaboration principles. Use when deciding which sub-agent to invoke, understanding team responsibilities, or learning multi-agent orchestration.

24 0
View
mermaid-diagram-generator logo
mattnigh/skills_collection

mermaid-diagram-generator

Converts architecture descriptions, module specs, or workflow docs into Mermaid diagrams. Use when visualizing brick module relationships, workflows (DDD, investigation), or system architecture. Supports: flowcharts, sequence diagrams, class diagrams, state machines, entity relationship diagrams,...

24 0
View
pre-flight-check logo
mattnigh/skills_collection

pre-flight-check

INVOKE FIRST before any code work. Validates git workflow (branch, issue, worklog) and checks approach. Use at START of every task and END before completing. Prevents skipped steps.

24 0
View
browser-dev-tools logo
mattnigh/skills_collection

browser-dev-tools

This skill should be used when working with browser-rendered artifacts (i.e. Bun, React/React Native) to proactively validate that development work on localhost is being built appropriately and to support debugging browser-rendered content. Use this skill after making frontend changes to verify t...

24 0
View
lead-dev logo
mattnigh/skills_collection

lead-dev

Lead Développeur - Coordination technique opérationnelle, code review, mentoring et livraison. Pair de web-dev-process au niveau OPÉRATIONS.

24 0
View
worker-monitor logo
mattnigh/skills_collection

worker-monitor

Monitor Docker workers, RQ queue health, and auto-scale workers (max 4). Use when: checking job progress, monitoring queue depth, scaling workers up/down, diagnosing slow processing, or waiting for jobs to complete. Referenced by data-quality skill during reprocessing.

24 0
View
quality-advisor logo
mattnigh/skills_collection

quality-advisor

Proactive quality guidance system that monitors artifact creation and provides real-time feedback on documentation quality

24 0
View
bump-version logo
mattnigh/skills_collection

bump-version

This skill should be used when the user wants to bump the version number in the workspace. It updates versions across all pyproject.toml files (root, tde, and tda packages) and the CHANGELOG.md to keep them synchronized.

24 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View