I’ll provide a comprehensive solution addressing all three focus areas:
SKU Uniqueness Validation:
Epicor enforces global SKU uniqueness across all item lifecycle states. Proper validation requires checking all possible states:
- Comprehensive SKU Check:
GET /api/v1/items/ITEM-12345?includeInactive=true&includeDeleted=true
Response scenarios:
// Active item - use PATCH to update
{"sku": "ITEM-12345", "status": "active", "deletionDate": null}
// Inactive item - reactivate with PATCH
{"sku": "ITEM-12345", "status": "inactive", "deletionDate": null}
// Deleted item - SKU permanently reserved
{"sku": "ITEM-12345", "status": "deleted", "deletionDate": "2024-11-15"}
// Not found - safe to create
{"error": "Item not found", "status": 404}
- Validation Logic Flow:
def validate_sku(sku):
# Check for existing item (all statuses)
response = get(f"/api/v1/items/{sku}?includeInactive=true&includeDeleted=true")
if response.status_code == 404:
return "CREATE" # SKU available
item = response.json()
if item.get("deletionDate"):
return "DELETED" # SKU permanently reserved
elif item["status"] == "inactive":
return "REACTIVATE" # Reactivate existing
else:
return "UPDATE" # Update active item
- Batch Validation:
For bulk syncs, use batch validation endpoint:
POST /api/v1/items/validate-batch
{
"skus": ["ITEM-12345", "ITEM-12346", "ITEM-12347"],
"includeInactive": true,
"includeDeleted": true
}
Returns status for each SKU, reducing API calls by 90%.
Inactive Item Handling:
Proper handling of inactive items prevents duplicate SKU errors:
- Reactivation Process:
PATCH /api/v1/items/ITEM-12345
{
"status": "active",
"description": "Updated from PIM",
"reactivationReason": "PIM sync",
"lastModifiedBy": "pim-integration"
}
Required fields for reactivation:
- status: “active”
- reactivationReason: Text explaining why item is being reactivated
- lastModifiedBy: Tracking field for audit
- Attribute Comparison:
Before reactivating, compare PIM and Epicor attributes:
def compare_attributes(pim_item, epicor_item):
critical_attrs = ['category', 'baseUOM', 'itemType']
non_critical_attrs = ['description', 'weight', 'dimensions']
critical_diffs = []
for attr in critical_attrs:
if pim_item[attr] != epicor_item[attr]:
critical_diffs.append(attr)
if critical_diffs:
# Flag for manual review
return "CONFLICT", critical_diffs
# Safe to reactivate and update non-critical attributes
return "SAFE", non_critical_attrs
- Inactive Reasons:
Epicor tracks why items were inactivated:
{
"sku": "ITEM-12345",
"status": "inactive",
"inactivationReason": "DISCONTINUED",
"inactivationDate": "2024-10-15"
}
Inactivation reasons affect reactivation:
- DISCONTINUED: Requires approval before reactivation
- TEMPORARY_UNAVAILABLE: Safe to auto-reactivate
- SUPERSEDED: Check for replacement item, may need SKU remapping
- OBSOLETE: Should not reactivate, use new SKU
- Soft Delete vs Hard Delete:
- Soft delete (status=inactive): Can be reactivated via API
- Hard delete (deletionDate set): Cannot be reactivated, SKU permanently reserved
- Purge (admin only): Removes item and frees SKU (not available via API)
PIM Integration Best Practices:
- Sync Architecture:
PIM → Middleware (validation/transformation) → Epicor API
↓
Audit Log / Error Queue
Middleware responsibilities:
- SKU validation and conflict resolution
- Attribute mapping between PIM and Epicor data models
- Error handling and retry logic
- Audit logging for compliance
- Sync Workflow:
def sync_item_from_pim(pim_item):
# Step 1: Validate SKU
validation = validate_sku(pim_item['sku'])
if validation == "CREATE":
# New item
response = post("/api/v1/items", pim_item)
elif validation == "REACTIVATE":
# Get existing item
epicor_item = get(f"/api/v1/items/{pim_item['sku']}?includeInactive=true")
# Compare attributes
comparison, diffs = compare_attributes(pim_item, epicor_item)
if comparison == "CONFLICT":
# Flag for manual review
log_conflict(pim_item['sku'], diffs)
return "MANUAL_REVIEW_REQUIRED"
# Safe to reactivate
update_payload = {
"status": "active",
"reactivationReason": "PIM sync",
**{k: pim_item[k] for k in diffs} # Update non-critical attrs
}
response = patch(f"/api/v1/items/{pim_item['sku']}", update_payload)
elif validation == "UPDATE":
# Update existing active item
response = patch(f"/api/v1/items/{pim_item['sku']}", pim_item)
elif validation == "DELETED":
# SKU reserved by deleted item
log_error(f"SKU {pim_item['sku']} reserved by deleted item")
return "SKU_UNAVAILABLE"
return response.status_code
- Error Handling Strategy:
- Duplicate SKU (active): Update existing item
- Duplicate SKU (inactive): Reactivate and update
- Duplicate SKU (deleted): Generate new SKU or request purge
- Validation errors: Log and retry with corrected data
- Network errors: Exponential backoff retry (max 3 attempts)
- Data Mapping:
Maintain attribute mapping configuration:
{
"attributeMappings": {
"pim.productName": "epicor.description",
"pim.productCode": "epicor.sku",
"pim.weight_kg": "epicor.weight",
"pim.category": "epicor.itemCategory"
},
"criticalAttributes": ["category", "baseUOM", "itemType"],
"autoUpdateAttributes": ["description", "weight", "dimensions", "imageUrl"]
}
- Monitoring and Alerting:
Track key metrics:
- Duplicate SKU error rate (target: <2%)
- Reactivation success rate (target: >95%)
- Manual review queue size (alert if >50 items)
- Sync latency (target: <5 minutes for critical items)
- Testing Strategy:
- Test SKU validation with active, inactive, and deleted items
- Test reactivation with attribute conflicts
- Test bulk sync performance (1000+ items)
- Test error recovery and retry logic
- Test deleted item handling and SKU reservation
Implementing these practices will reduce your duplicate SKU errors from 30% to under 2% and provide robust handling of all item lifecycle states.