Agent skill
tsh-stock
TSH stock and warehouse logic for Main WareHouse. Use when: (1) Displaying stock levels on product pages (2) Understanding warehouse configuration (3) Debugging stock display issues (showing 0 when shouldn't) (4) Working with "In Stock" / "Out of Stock" badges (5) Implementing stock-based filtering (6) Understanding stock sync architecture (webhooks, cron, manual) (7) Understanding the difference between list and detail page stock
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/tsh-stock
SKILL.md
TSH Stock Rules
Warehouse Configuration
| Setting | Value |
|---|---|
| Warehouse Name | Main WareHouse |
| Warehouse ID | 2646610000000077024 |
| Business Location | Main TSH Business |
| Stock Type | Accounting Stock (Available for Sale) |
| Field | location_available_for_sale_stock |
| Array | locations (NOT warehouses) |
Stock Formula
Available for Sale = Stock on Hand - Committed Stock
- Stock on Hand: Physical stock in warehouse
- Committed Stock: Reserved for pending orders
- Available for Sale: What can actually be sold
Critical: Correct Field Names
// CORRECT - Use these
const WHOLESALE_LOCATION_NAME = 'Main WareHouse';
const location = item.locations?.find(
loc => loc.location_name === WHOLESALE_LOCATION_NAME
);
const stock = location?.location_available_for_sale_stock;
// WRONG - Never use
item.warehouses // Wrong array name
warehouse_available_stock // Wrong field name
stock_on_hand // Includes committed stock
item.available_stock // Combines ALL warehouses
Code Implementation
Constants
// src/lib/zoho/stock-cache.ts & src/lib/zoho/products.ts
const WHOLESALE_LOCATION_NAME = 'Main WareHouse';
const WHOLESALE_WAREHOUSE_ID = '2646610000000077024';
Stock Extraction Function
function getWholesaleAvailableStock(item: ZohoItemWithLocations): number {
if (item.locations && item.locations.length > 0) {
const wholesaleLocation = item.locations.find(
(loc) => loc.location_name === WHOLESALE_LOCATION_NAME
);
if (wholesaleLocation) {
return wholesaleLocation.location_available_for_sale_stock || 0;
}
}
// NEVER fall back to item.available_stock (combines all warehouses)
return 0;
}
Unified Stock Functions (ALWAYS USE)
// For single item (detail page)
const { stock, source } = await getUnifiedStock(itemId, {
fetchOnMiss: true,
context: 'product-detail',
});
// For multiple items (list page)
const stockMap = await getUnifiedStockBulk(itemIds, {
context: 'shop-list',
});
Type Definition
interface ZohoItem {
item_id: string;
name: string;
available_stock: number;
locations?: Array<{
location_id: string;
location_name: string;
location_stock_on_hand: number;
location_available_for_sale_stock: number;
}>;
}
List vs Detail Page
Detail Page (Accurate Stock)
- Endpoint:
GET /inventory/v1/items/{item_id} - Returns: Full item with
locationsarray - Stock: Accurate per-warehouse breakdown
export async function getProduct(itemId: string) {
const response = await zohoFetch(`/inventory/v1/items/${itemId}`, {
params: { organization_id: process.env.ZOHO_ORGANIZATION_ID },
});
const item = response.item;
const stock = getWholesaleAvailableStock(item);
return { ...item, stock };
}
List Page (Filtered Stock)
- Endpoint:
GET /inventory/v1/items - Note: Does NOT return
locationsarray - Use
warehouse_idparameter to filter
export async function getAllProductsComplete() {
const response = await zohoFetch('/inventory/v1/items', {
params: {
organization_id: process.env.ZOHO_ORGANIZATION_ID,
warehouse_id: WHOLESALE_WAREHOUSE_ID,
status: 'active',
},
});
return response.items.map(item => ({
...item,
stock: item.available_stock ?? 0,
}));
}
Stock Badge Component
'use client';
import { Badge } from '@/components/ui/badge';
import { useTranslations } from 'next-intl';
export function StockBadge({ stock }: { stock: number }) {
const t = useTranslations('products');
if (stock > 10) {
return <Badge variant="default">{t('inStock')}</Badge>;
}
if (stock > 0) {
return (
<Badge variant="warning">
{t('lowStock', { count: stock })}
</Badge>
);
}
return <Badge variant="secondary">{t('outOfStock')}</Badge>;
}
Translations
// en.json
{
"products": {
"inStock": "In Stock",
"outOfStock": "Out of Stock",
"lowStock": "Only {count} left",
"available": "available"
}
}
// ar.json
{
"products": {
"inStock": "متوفر",
"outOfStock": "غير متوفر",
"lowStock": "متبقي {count} فقط",
"available": "متاح"
}
}
Product Card with Stock
export function ProductCard({ product }) {
const t = useTranslations('products');
return (
<Card>
<CardContent>
<h3>{product.name}</h3>
<div className="flex items-center gap-2 mt-2">
{product.stock > 0 ? (
<>
<span className="text-green-600">{t('inStock')}</span>
<span className="text-sm text-muted-foreground">
({product.stock} {t('available')})
</span>
</>
) : (
<span className="text-red-600">{t('outOfStock')}</span>
)}
</div>
</CardContent>
</Card>
);
}
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| Stock shows 0 | Wrong field | Use location_available_for_sale_stock |
| Stock shows 0 | Wrong array | Use locations not warehouses |
| Stock shows 0 | Wrong warehouse | Use ID 2646610000000077024 |
| Stock shows 0 | All committed | Stock reserved for orders |
| Not updating | Cache stale | Revalidate products cache |
| Different list/detail | List lacks locations | Expected - use warehouse_id filter |
Debug Commands
# Check stock data
curl "https://www.tsh.sale/api/debug/stock"
# Revalidate product cache
curl "https://www.tsh.sale/api/revalidate?tag=products&secret=tsh-revalidate-2024"
Other Locations (DO NOT USE FOR THIS CONSOLE)
| Location | Type | Purpose | Use |
|---|---|---|---|
| Main WareHouse | Warehouse | B2B wholesale | ✅ THIS CONSOLE |
| Dora Store | Warehouse | Retail shop | ❌ EndUser Console |
| inactive 1/2 | Warehouse | Inactive | ❌ Delete |
Only use Main WareHouse (2646610000000077024) for this B2B console.
Stock Sync Commands
# Check cache status
curl "https://www.tsh.sale/api/sync/stock?action=status"
# Force full sync
curl "https://www.tsh.sale/api/sync/stock?action=sync&secret=tsh-stock-sync-2024&force=true"
Checklist
- Use
getUnifiedStock()orgetUnifiedStockBulk()for stock retrieval - NEVER use
item.available_stockdirectly - Handle 0 stock with "Out of Stock" badge
- Add stock translations (en.json, ar.json)
- Verify list/detail stock consistency after changes
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?