Physical Inventory Count Guide
Overview
Physical Inventory Counts reconcile actual on-hand stock with system records by counting physical inventory and adjusting for discrepancies. This process is essential for maintaining inventory accuracy, identifying shrinkage, detecting errors, and ensuring financial reporting integrity.
The system automatically calculates variances, creates adjustment movements, and maintains an immutable audit trail for regulatory compliance.
Table of Contents
- Core Concepts
- When to Perform Counts
- Count Procedures
- Recording Physical Counts
- Variance Handling
- Count Validation
- Best Practices
- Business Rules
- API Endpoints
Core Concepts
What is a Physical Count?
A physical count compares actual counted quantity with system quantity and adjusts inventory to match reality:
System Record (Before Count):
Product: Laptop - Dell XPS 13
Warehouse: Main DC
Quantity on Hand: 100 units
Reserved: 10 units
Available: 90 units
Physical Count:
Counted Quantity: 98 units
System Record (After Count):
Quantity on Hand: 98 units
Reserved: 10 units
Available: 88 units
Variance: -2 units (shrinkage)Why Perform Physical Counts?
Regulatory Compliance:
- Required by GAAP, IFRS for financial reporting
- SOX compliance for inventory valuation
- Tax audit requirements
Operational Accuracy:
- Detect theft, damage, or loss (shrinkage)
- Identify data entry errors
- Prevent stockouts from inaccurate records
- Improve order fulfillment accuracy
Financial Integrity:
- Accurate cost of goods sold (COGS)
- Correct inventory asset valuation
- Reliable financial statements
Process Improvement:
- Identify systemic issues
- Improve warehouse procedures
- Reduce inventory variance over time
Physical Count Model
The system records:
{
"product_id": 42,
"warehouse_id": 1,
"counted_quantity": 98,
"system_quantity_before": 100,
"variance": -2,
"variance_percentage": -2.0,
"counted_by": 5,
"counted_at": "2025-01-16T10:00:00.000000Z",
"adjustment_movement_id": 892
}Key Fields:
- counted_quantity: Actual counted units
- system_quantity_before: System quantity before adjustment
- variance: Difference (counted - system)
- variance_percentage: Variance as percentage of system quantity
- adjustment_movement_id: Stock movement created to correct variance
Count Types
Full Physical Count (Annual):
- Count all products in all warehouses
- Usually performed at year-end
- Most time-consuming but most accurate
Cycle Count (Ongoing):
- Count subset of products on rotating schedule
- Example: 20% of products weekly
- Spreads workload throughout year
- Maintains continuous accuracy
Spot Count (Ad-hoc):
- Count specific products as needed
- Triggered by suspected discrepancy
- Quick response to issues
ABC Count (Priority-based):
- A items (high value): Count monthly
- B items (medium value): Count quarterly
- C items (low value): Count annually
- Focuses effort on high-impact products
When to Perform Counts
Recommended Schedule
Annual Full Count:
- End of fiscal year
- Before financial statement preparation
- All products, all warehouses
- Complete reconciliation
Quarterly Cycle Counts:
- 25% of products each quarter
- Focus on high-value items
- Maintain ongoing accuracy
- Detect issues early
Monthly Spot Counts:
- Products with recent discrepancies
- High-theft items
- Fast-moving products
- Critical stock items
Event-Triggered Counts:
- After major shipment received
- Before warehouse reorganization
- When variance suspected
- After inventory system migration
ABC Analysis Schedule
Categorize products by annual dollar volume:
A Items (70-80% of value, 10-20% of SKUs):
- Count: Monthly
- Examples: High-value electronics, jewelry
- Priority: Critical
B Items (15-25% of value, 30-40% of SKUs):
- Count: Quarterly
- Examples: Mid-range products
- Priority: Important
C Items (5-10% of value, 40-50% of SKUs):
- Count: Annually
- Examples: Low-cost consumables
- Priority: Standard
Count Procedures
Step-by-Step Process
1. Preparation Phase
Plan the Count:
- Schedule during low-activity period
- Assign count teams
- Prepare count sheets or mobile devices
- Freeze transactions (optional)
Organize Warehouse:
- Clean and organize storage areas
- Group similar products
- Label bin locations clearly
- Separate damaged/quarantined items
2. Counting Phase
Count Instructions:
- Count each product in designated area
- Don't reference system quantities (blind count)
- Use two-person teams for accuracy
- Record bin locations
- Note damaged or obsolete items
Count Sheet Example:
Product: Laptop - Dell XPS 13
SKU: DELL-XPS13-001
Warehouse: Main DC
Location: A-12-3
Counter 1: 98 units
Counter 2: 98 units
Match: ✅ Yes
Notes: All units in good condition3. Recording Phase
Enter Counts into System:
- Record counted quantities via API
- System calculates variances automatically
- Review large variances before accepting
- Create adjustment movements
4. Investigation Phase
Investigate Variances:
- Recount if variance > threshold (e.g., 5% or $500 value)
- Check recent transactions
- Review movement history
- Interview staff if significant discrepancy
5. Adjustment Phase
Accept Count:
- System adjusts inventory to match count
- Creates stock movement record
- Updates last_counted_at timestamp
- Generates variance report
Document Findings:
- Record reasons for variances
- Note process improvements needed
- Update procedures if systemic issues found
Recording Physical Counts
Record Physical Count
Submit counted quantity to reconcile inventory:
Endpoint: POST /api/v1/operations/inventory/physical-count
Request:
curl -X POST "https://api.crm.test/api/v1/operations/inventory/physical-count" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"product_id": 42,
"warehouse_id": 1,
"counted_quantity": 98
}'Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
product_id | integer | ✅ Yes | Product being counted |
warehouse_id | integer | No | Warehouse location (null = default) |
counted_quantity | integer | ✅ Yes | Actual counted units (≥ 0) |
Response (200 OK):
{
"message": "Physical count recorded successfully",
"data": {
"id": 123,
"product_id": 42,
"warehouse_id": 1,
"quantity_on_hand": 98,
"quantity_reserved": 10,
"quantity_available": 88,
"reorder_point": 20,
"reorder_quantity": 50,
"last_counted_at": "2025-01-16T10:00:00.000000Z",
"last_movement_at": "2025-01-16T10:00:00.000000Z",
"warehouse": {
"id": 1,
"name": "Main Distribution Center",
"code": "MDC",
"type": "distribution"
},
"product": {
"id": 42,
"name": "Laptop - Dell XPS 13",
"sku": "DELL-XPS13-001",
"type": "standard"
},
"created_at": "2024-12-01T00:00:00.000000Z",
"updated_at": "2025-01-16T10:00:00.000000Z"
}
}What Happens:
System retrieves current inventory:
- Current quantity_on_hand: 100 units
Calculates variance:
- Variance: 98 (counted) - 100 (system) = -2 units
Creates adjustment movement (if variance ≠ 0):
- Movement type:
physical_count - Quantity: -2
- Reason: "physical_count"
- Movement type:
Updates inventory:
- quantity_on_hand: 100 → 98
- last_counted_at: Updated to current timestamp
Dispatches domain event:
InventoryCountCompletedevent fired- Contains variance details for reporting
No Variance Example
If counted quantity matches system:
Request:
{
"product_id": 42,
"warehouse_id": 1,
"counted_quantity": 100
}System Behavior:
- Variance: 0 units
- No adjustment movement created (no change needed)
- last_counted_at: Updated to current timestamp
- inventory record: Not modified (already correct)
Response:
{
"message": "Physical count recorded successfully",
"data": {
"quantity_on_hand": 100,
"last_counted_at": "2025-01-16T10:00:00.000000Z"
}
}Multiple Products Count
Record counts for multiple products:
# Product 1
curl -X POST "https://api.crm.test/api/v1/operations/inventory/physical-count" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"product_id": 42,
"warehouse_id": 1,
"counted_quantity": 98
}'
# Product 2
curl -X POST "https://api.crm.test/api/v1/operations/inventory/physical-count" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"product_id": 43,
"warehouse_id": 1,
"counted_quantity": 145
}'
# Product 3
curl -X POST "https://api.crm.test/api/v1/operations/inventory/physical-count" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"product_id": 44,
"warehouse_id": 1,
"counted_quantity": 0
}'Variance Handling
Understanding Variances
Variance Formula:
Variance = Counted Quantity - System Quantity
Variance % = (Variance / System Quantity) × 100Examples:
Negative Variance (Shrinkage):
System: 100 units
Counted: 98 units
Variance: -2 units (-2%)
Reason: Theft, damage, data entry errorPositive Variance (Overage):
System: 100 units
Counted: 103 units
Variance: +3 units (+3%)
Reason: Data entry error, unreported receiptNo Variance (Accurate):
System: 100 units
Counted: 100 units
Variance: 0 units (0%)
Reason: System is accurateVariance Thresholds
Implement variance investigation rules:
Recount Threshold:
- Variance > 5% or $500 value
- Requires second count before accepting
Approval Threshold:
- Variance > 10% or $2,000 value
- Requires manager approval
Investigation Threshold:
- Variance > 20% or $5,000 value
- Requires formal investigation
Example:
Product: Laptop - Dell XPS 13
Cost: $1,200 per unit
Variance: -5 units
Value: $6,000 loss
Action: ✅ Trigger investigation (> $5,000)
Variance: -2 units
Value: $2,400 loss
Action: ✅ Require approval (> $2,000)
Variance: -1 unit
Value: $1,200 loss
Action: ✅ Recount (> $500)Common Variance Causes
Negative Variances (Shrinkage):
- Theft: Internal or external
- Damage: Unreported damage/disposal
- Data Entry: Incorrect receipts recorded
- Location: Product in wrong location (not counted)
- Shipping: Shipped but not recorded
Positive Variances (Overage):
- Data Entry: Incorrect shipment recording
- Returns: Unreported returns
- Location: Product counted in multiple locations
- Receipts: Received but not recorded
Viewing Variance Reports
Get product movement history:
curl -X GET "https://api.crm.test/api/v1/operations/stock-movements/product/42/history" \
-H "Authorization: Bearer {token}"Filter for physical count movements:
curl -X GET "https://api.crm.test/api/v1/operations/stock-movements?movement_type=physical_count" \
-H "Authorization: Bearer {token}"Get movement details:
curl -X GET "https://api.crm.test/api/v1/operations/stock-movements/892" \
-H "Authorization: Bearer {token}"Response (Physical Count Movement):
{
"data": {
"id": 892,
"product_id": 42,
"warehouse_id": 1,
"movement_type": {
"value": "physical_count",
"label": "Physical Count"
},
"is_inbound": false,
"is_outbound": true,
"quantity": -2,
"quantity_before": 100,
"quantity_after": 98,
"product": {
"id": 42,
"name": "Laptop - Dell XPS 13",
"sku": "DELL-XPS13-001"
},
"warehouse": {
"id": 1,
"name": "Main Distribution Center",
"code": "MDC"
},
"reason": "physical_count",
"notes": null,
"created_by": 5,
"creator": {
"id": 5,
"name": "Inventory Manager",
"email": "inventory@example.com"
},
"created_at": "2025-01-16T10:00:00.000000Z"
}
}Count Validation
Validation Rules
1. Product Validation
{
"product_id": 99999
}
// ❌ Error: Selected product does not existProduct must exist in the system.
2. Warehouse Validation
{
"warehouse_id": 99999
}
// ❌ Error: Selected warehouse does not existWarehouse must exist (if specified).
3. Counted Quantity Validation
{
"counted_quantity": -5
}
// ❌ Error: Counted quantity cannot be negative
{
"counted_quantity": "abc"
}
// ❌ Error: Counted quantity must be a whole numberCounted quantity must be non-negative integer (≥ 0).
4. Reserved Quantity Validation
{
"product_id": 42,
"warehouse_id": 1,
"counted_quantity": 5
}
// Current reserved quantity: 10 units
// ❌ Error: Counted quantity cannot be less than reserved quantity.
// Reserved: 10, Counted: 5Why?
Reserved stock represents commitments (pending orders). You cannot count fewer units than are already committed.
Example:
System Record:
- Quantity on Hand: 100 units
- Reserved: 10 units (for pending orders)
- Available: 90 units
Physical Count: 8 units ❌ Invalid
Reason: Cannot have 8 total units when 10 are already reserved
Physical Count: 12 units ✅ Valid
Result: 12 on hand, 10 reserved, 2 availableSolution:
- Release Reservations First:
# Cancel orders or release reservations
POST /inventory/release
{
"product_id": 42,
"warehouse_id": 1,
"quantity": 5
}
# Then record count
POST /inventory/physical-count
{
"product_id": 42,
"warehouse_id": 1,
"counted_quantity": 5
}- Count Higher Quantity:
- Recount to ensure accuracy
- Verify reserved quantities in system
- Adjust count if needed
Error Responses
Validation Error (422 Unprocessable Entity):
{
"message": "The given data was invalid.",
"errors": {
"product_id": [
"Selected product does not exist"
],
"counted_quantity": [
"Counted quantity cannot be negative"
]
}
}Reserved Quantity Error (500 Internal Server Error):
{
"message": "Counted quantity cannot be less than reserved quantity. Reserved: 10, Counted: 5"
}Best Practices
1. Blind Counting
Don't show system quantities to counters:
Good:
Count Sheet:
Product: Laptop - Dell XPS 13
SKU: DELL-XPS13-001
Location: A-12-3
Counted Quantity: ______ (blank)Bad:
Count Sheet:
Product: Laptop - Dell XPS 13
System Quantity: 100 units ❌ Don't show
Counted Quantity: ______ (biased toward 100)2. Two-Person Teams
Use two-person teams for accuracy:
Counter 1 counts: 98 units
Counter 2 counts: 98 units
Match: ✅ Accept count
Counter 1 counts: 98 units
Counter 2 counts: 102 units
Mismatch: ❌ Recount3. Schedule Strategically
Best Times:
- After business hours
- Weekends
- Slow periods
- Between receiving shipments
Avoid:
- Peak season
- During active receiving
- Major sale events
- Warehouse reorganization
4. Investigate Large Variances
Always investigate before accepting:
Variance: -20 units ($24,000 value)
Actions:
1. Recount immediately
2. Check recent transactions
3. Search warehouse thoroughly
4. Interview staff
5. Review security footage
6. Document findings5. Document Everything
Record detailed notes:
{
"product_id": 42,
"counted_quantity": 98,
"notes": "Counted by John Doe and Jane Smith. 2 units found damaged in receiving area, removed from inventory. Variance appears to be unreported damage."
}6. Regular Cycle Counts
Maintain ongoing accuracy:
Weekly Cycle:
Week 1: Products 1-100
Week 2: Products 101-200
Week 3: Products 201-300
Week 4: Products 301-400
Week 5: Products 401-500
Repeat cycle7. Root Cause Analysis
Track variance trends:
Product: Laptop - Dell XPS 13
January Count: -2 units (shrinkage)
February Count: -3 units (shrinkage)
March Count: -4 units (shrinkage)
Pattern: Consistent negative variance
Action: Investigate for theft or process issue8. Freeze Transactions (Optional)
For critical counts, pause transactions:
1. Announce count period
2. Stop receiving/shipping
3. Complete count
4. Record counts
5. Resume operationsNot always practical, but ensures accuracy.
9. Compare Before and After
Review last count date:
{
"last_counted_at": "2024-12-15T10:00:00.000000Z",
"days_since_last_count": 32,
"variance_trend": "improving"
}10. Train Staff
Ensure counters understand:
- Proper counting techniques
- How to handle damaged goods
- Importance of accuracy
- Variance investigation process
Business Rules
Rule 1: System Adjustment
System always adjusts to match counted quantity:
System Before: 100 units
Counted: 98 units
System After: 98 units ✅ Always
System is the "source of truth" after count.Rule 2: Immutable Movement Record
Physical count creates a permanent audit record that cannot be modified or deleted. The system enforces immutability to maintain regulatory compliance.
Restrictions:
- Cannot update count movement records
- Cannot delete count movement records
- All count records are permanent
Solution - Create New Count:
If you need to correct a count, perform a new physical count:
POST /inventory/physical-count{
"product_id": 42,
"warehouse_id": 1,
"counted_quantity": 97
}Rule 3: Reserved Quantity Constraint
Cannot count less than reserved:
Reserved: 10 units
Minimum Counted: 10 units ✅
Less than 10: ❌ InvalidRule 4: Last Counted Timestamp
Every count updates last_counted_at:
Before Count: last_counted_at = "2024-12-15"
After Count: last_counted_at = "2025-01-16" ✅
Even if variance = 0, timestamp updates.Rule 5: Event Dispatching
Every count triggers a system event that contains count details:
Event Data:
{
"event": "InventoryCountCompleted",
"inventory_id": 123,
"product_id": 42,
"warehouse_id": 1,
"system_quantity": 100,
"counted_quantity": 98,
"variance": -2,
"user_id": 5
}Event Uses:
- Send notifications for large variances
- Update analytics dashboards
- Trigger investigations
- Log to external systems
API Endpoints
Physical Count Operations
| Method | Endpoint | Description |
|---|---|---|
| POST | /inventory/physical-count | Record physical count and adjust inventory |
Related Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /inventory/show?product_id={id} | View current inventory levels |
| GET | /stock-movements | View count movements (filter by movement_type=physical_count) |
| GET | /stock-movements/product/{productId}/history | Product movement history |
Base URL: https://api.crm.test/api/v1/operations/
Related Guides
- Inventory Management - Multi-warehouse inventory tracking
- Stock Movement Tracking - Audit trail and movement history
- Warehouse Transfers - Inter-warehouse transfers
- Product Management - Product catalog
- Warehouse Management - Warehouse configuration
Next Steps
- Plan Counts - Develop a counting schedule (annual, cycle, spot)
- Prepare Procedures - Document counting standards
- Train Staff - Ensure counters understand process
- Record Counts - Submit counted quantities via API
- Investigate Variances - Review and document discrepancies
- Improve Processes - Address root causes of variances
- Monitor Trends - Track accuracy improvements over time