Skip to content

Inventory - Module Specification

1. Overview

1.1 Purpose

Central management system for puppy inventory, breed information, and breed collections. Provides the data backbone for all puppy-related operations including catalog display, pricing management, status tracking, and legacy system synchronization.

1.2 Scope

In Scope: - Puppy CRUD operations and lifecycle management - Breed and breed collection management - Pricing management with history tracking - Status workflow with role-based visibility - Legacy POS system synchronization via webhook - Bulk operations for inventory management

Out of Scope: - Store and brand settings (→ Admin Settings module) - Lead management and follow-up (→ CRM module) - Purchase transactions (→ Ecommerce module) - Media file storage (→ Media module)


2. Dependencies

2.1 Upstream (What This Module Needs)

Module/Service What We Need Interface
Media Image/video metadata and R2 URLs Server Actions
Admin Settings Store configuration, brand settings Server Actions
Rich Text Editor Lexical editor for descriptions React Component

2.2 Downstream (What Needs This Module)

Module/Service What They Need Interface
Customer Website Puppy data, breed info, availability Server Actions
CRM Puppy references for leads Server Actions
Analytics Inventory events, pricing changes Event emission
Social Media Puppy data for content pipeline Server Actions

3. Data Ownership

3.1 Entities This Module Owns

Entity Description Key Fields
Puppy Individual puppy record id, name, breed, gender, price, wholesalePrice, status, storeId, supplierId
Breed Breed information id, name, slug, description, hybrid
BreedCollection Grouping of breeds id, name, slug, description
Supplier Breeder/source of puppies id, name, contactName, phone, email, location
PriceHistory Historical price changes id, puppyId, price, changedAt, changedBy
StatusHistory Historical status changes id, puppyId, status, changedAt, changedBy, reason

3.2 Entities This Module Uses (Read-Only)

Entity Owner How We Use It
Media Media Attach images/videos to puppies and breeds
Store Admin Settings Associate puppies with store locations
User Auth Track who made changes (changedBy)

3.3 Data Schema

import type { LexicalEditorState } from '@/modules/rich-text-editor';
import type { Media } from '@/modules/media';
import type { Store } from '@/modules/admin-settings';
import type { User } from '@/modules/auth';

// Puppy statuses with role-based visibility
type PuppyStatus =
  | 'arrived'      // Sales, Kennel Staff, Admin
  | 'available'    // Sales, Kennel Staff, Admin
  | 'sold'         // Admin only
  | 'unavailable'  // Kennel Staff, Admin
  | 'expired'      // Admin only
  | 'donated';     // Admin only

interface Puppy {
  id: string;
  slug: string;                 // URL-friendly identifier
  name: string;
  breedId: Breed['id'];
  supplierId: Supplier['id'];
  gender: 'male' | 'female';
  dateOfBirth: Date;
  color: string;
  wholesalePrice: number;       // Cost from supplier (Admin only)
  price: number;                // Retail price
  salePrice?: number;           // Discounted price (if on sale)
  status: PuppyStatus;
  storeId: Store['id'];
  mediaIds: Media['id'][];
  description?: LexicalEditorState;
  microchipNumber?: string;
  registrationNumber?: string;
  legacyId?: string;            // For POS sync
  arrivedAt: Date;
  soldAt?: Date;
  metadata?: Record<string, unknown>;
  createdAt: Date;
  updatedAt: Date;
}

interface Breed {
  id: string;
  name: string;
  slug: string;
  description?: LexicalEditorState;
  hybrid: boolean;              // true for designer breeds (e.g., Goldendoodle)
  mediaIds: Media['id'][];
  createdAt: Date;
  updatedAt: Date;
}

interface BreedCollection {
  id: string;
  name: string;
  slug: string;
  description?: LexicalEditorState;
  breedIds: Breed['id'][];      // Many-to-many relationship
  displayOrder: number;
  createdAt: Date;
  updatedAt: Date;
}

// Note: Breeds can belong to multiple BreedCollections
// e.g., "Goldendoodle" can be in both "Hypoallergenic" and "Designer Breeds"

interface Supplier {
  id: string;
  name: string;                 // Internal/display name
  contactName: string;
  phone: string;
  email: string;
  location?: string;            // General location (city, state)

  // USDA compliance fields
  usdaNumber: string;           // USDA license number
  usdaName: string;             // USDA registered name
  usdaAddress: string;          // USDA registered address

  notes?: LexicalEditorState;
  isActive: boolean;
  createdAt: Date;
  updatedAt: Date;
}

interface PriceHistory {
  id: string;
  puppyId: Puppy['id'];
  previousPrice: number;
  newPrice: number;
  changedAt: Date;
  changedBy: User['id'];
  reason?: string;
}

interface StatusHistory {
  id: string;
  puppyId: Puppy['id'];
  previousStatus: PuppyStatus;
  newStatus: PuppyStatus;
  changedAt: Date;
  changedBy: User['id'];
  reason?: string;
}

4. Service Level Objectives

Objective Target Measurement
API Latency (p95) < 200ms Application monitoring
Availability 99.9% Health checks
Data Consistency 100% Audit logs
Sync Latency < 5 minutes Webhook monitoring

5. Interface Contract

5.1 Internal Service Interface

// Include patterns for optional relationship hydration
interface PuppyInclude {
  breed?: boolean;
  supplier?: boolean;
  store?: boolean;
  media?: boolean;
}

interface BreedInclude {
  collections?: boolean;
  media?: boolean;
}

interface BreedCollectionInclude {
  breeds?: boolean;
}

interface InventoryService {
  // Puppy operations
  getPuppy(id: string, include?: PuppyInclude): Promise<Puppy>;
  getPuppyBySlug(slug: string, include?: PuppyInclude): Promise<Puppy>;
  listPuppies(filters: PuppyFilters, pagination: Pagination, include?: PuppyInclude): Promise<PaginatedResult<Puppy>>;
  createPuppy(data: CreatePuppyDTO): Promise<Puppy>;
  updatePuppy(id: string, data: UpdatePuppyDTO): Promise<Puppy>;
  updatePuppyStatus(id: string, status: PuppyStatus, reason?: string): Promise<Puppy>;
  updatePuppyPrice(id: string, price: number, reason?: string): Promise<Puppy>;

  // Breed operations
  getBreed(id: string, include?: BreedInclude): Promise<Breed>;
  getBreedBySlug(slug: string, include?: BreedInclude): Promise<Breed>;
  listBreeds(filters?: BreedFilters, include?: BreedInclude): Promise<Breed[]>;
  createBreed(data: CreateBreedDTO): Promise<Breed>;
  updateBreed(id: string, data: UpdateBreedDTO): Promise<Breed>;

  // Breed Collection operations
  getBreedCollection(id: string, include?: BreedCollectionInclude): Promise<BreedCollection>;
  getBreedCollectionBySlug(slug: string, include?: BreedCollectionInclude): Promise<BreedCollection>;
  listBreedCollections(include?: BreedCollectionInclude): Promise<BreedCollection[]>;
  createBreedCollection(data: CreateBreedCollectionDTO): Promise<BreedCollection>;
  updateBreedCollection(id: string, data: UpdateBreedCollectionDTO): Promise<BreedCollection>;

  // Supplier operations
  getSupplier(id: string): Promise<Supplier>;
  listSuppliers(filters?: SupplierFilters): Promise<Supplier[]>;
  createSupplier(data: CreateSupplierDTO): Promise<Supplier>;
  updateSupplier(id: string, data: UpdateSupplierDTO): Promise<Supplier>;

  // History queries
  getPriceHistory(puppyId: string): Promise<PriceHistory[]>;
  getStatusHistory(puppyId: string): Promise<StatusHistory[]>;

  // Bulk operations
  bulkUpdateStatus(puppyIds: string[], status: PuppyStatus, reason?: string): Promise<void>;
  bulkUpdatePrice(puppyIds: string[], priceAdjustment: PriceAdjustment): Promise<void>;
}

// When includes are specified, relationships are hydrated on the returned object
// Example: getPuppy(id, { breed: true, media: true })
// Returns Puppy with puppy.breed as Breed object and puppy.media as Media[]
// Unincluded relationships remain as IDs (breedId, supplierId, etc.)

interface PuppyFilters {
  storeId?: string;
  breedId?: string;
  breedCollectionId?: string;
  status?: PuppyStatus | PuppyStatus[];
  gender?: 'male' | 'female';
  minPrice?: number;
  maxPrice?: number;
  onSale?: boolean;             // Filter puppies with salePrice set
  newArrivals?: boolean;        // Filter puppies with arrivedAt within 7 days
  search?: string;
}

interface PriceAdjustment {
  type: 'fixed' | 'percentage';
  value: number;                // Amount or percentage
  direction: 'increase' | 'decrease';
}

5.2 Remote Procedure Interface

Communication Pattern: Next.js Server Actions

Procedure Input Output Auth Description
getPuppy { id } Puppy Public Get puppy details
getPuppyBySlug { slug } Puppy Public Get puppy by URL slug
listPuppies PuppyFilters PaginatedResult Public List puppies with filters
getBreed { id } Breed Public Get breed details
getBreedBySlug { slug } Breed Public Get breed by URL slug
listBreeds BreedFilters Breed[] Public List all breeds
getBreedCollection { id } BreedCollection Public Get collection details
listBreedCollections - BreedCollection[] Public List all collections
createPuppy CreatePuppyDTO Puppy Admin Create new puppy
updatePuppy { id, data } Puppy Admin Update puppy details
updatePuppyStatus { id, status, reason } Puppy Staff+ Change puppy status
updatePuppyPrice { id, price, reason } Puppy Admin Change puppy price
syncFromLegacy LegacySyncPayload SyncResult Webhook Receive legacy POS data

5.3 Webhook Endpoint

Endpoint Method Auth Description
/api/webhooks/legacy-sync POST API Key Receive puppy data from legacy POS
// Legacy POS pushes full inventory snapshot as array of raw JSON values
interface LegacySyncPayload {
  timestamp: Date;
  puppies: LegacyPuppyRecord[];
}

interface LegacyPuppyRecord {
  breed: string;
  dob: string;                  // Date of birth
  sex: string;
  color: string;
  microchip: string;
  price: number;
  wholesale_price: number;      // Cost from breeder
  arrivalDate: string;
  storeName: string;
  status: string;
  sale_comment?: string;        // Used for sale pricing notes
  first_price?: number;         // Original price (for sale tracking)
  current_weight?: number;
  registration?: string;
  sire_breed?: string;
  sire_weight?: number;
  dam_breed?: string;
  dam_weight?: number;
  video?: string;               // Video URL

  // Breeder/supplier information
  breeder_name?: string;
  breeder_usda_number?: string;
  breeder_usda_name?: string;
  breeder_usda_address?: string;
}

interface SyncResult {
  success: boolean;
  processed: number;
  created: number;
  updated: number;
  skipped: number;
  errors?: string[];
}

6. Error Handling Strategy

Error Code Condition User Message
INVENTORY_PUPPY_NOT_FOUND Puppy doesn't exist "Puppy not found"
INVENTORY_BREED_NOT_FOUND Breed doesn't exist "Breed not found"
INVENTORY_COLLECTION_NOT_FOUND Collection doesn't exist "Collection not found"
INVENTORY_INVALID_STATUS Invalid status transition "Cannot change to this status"
INVENTORY_INVALID_PRICE Price validation failed "Price must be greater than zero"
INVENTORY_DUPLICATE_LEGACY_ID Legacy ID already exists "This puppy already exists in the system"
INVENTORY_UNAUTHORIZED Insufficient permissions "You don't have permission to perform this action"

Error Response Format

interface ErrorResponse {
  code: string;
  message: string;
  field?: string;
  details?: Record<string, unknown>;
}

7. Observability

7.1 Logging

Event Level Fields to Include
Puppy created INFO puppy_id, breed_id, store_id, user_id
Puppy status changed INFO puppy_id, old_status, new_status, user_id, reason
Puppy price changed INFO puppy_id, old_price, new_price, user_id, reason
Legacy sync received INFO legacy_id, action, sync_result
Legacy sync failed ERROR legacy_id, action, error_code, error_message
Bulk operation completed INFO operation, puppy_count, user_id

7.2 Metrics

Metric Type Description
inventory_puppies_total Gauge Total puppies by status
inventory_sync_requests Counter Legacy sync webhook calls
inventory_sync_errors Counter Failed sync attempts
inventory_price_changes Counter Price modifications
inventory_status_changes Counter Status transitions

7.3 Tracing

  • Span name: inventory.{operation}
  • Required attributes: user_id, puppy_id, operation, store_id

8. Testing Strategy

Test Type Coverage Target Tools Focus Areas
Unit 80%+ Vitest Status transitions, price validation, filters
Integration Critical paths Vitest Database operations, sync webhook
E2E Happy paths Playwright CRUD workflows, bulk operations

9. Feature Inventory

Puppy Management

Feature ID Name Status Priority
FEATURE-030 Puppy CRUD Draft Must Have
FEATURE-031 Puppy Status Workflow Draft Must Have
FEATURE-032 Puppy Pricing Management Draft Must Have
FEATURE-033 Price History Tracking Draft Must Have
FEATURE-034 Status History Tracking Draft Must Have
FEATURE-035 Legacy POS Sync Webhook Draft Must Have
FEATURE-036 Bulk Status Update Draft Should Have
FEATURE-037 Bulk Price Adjustment Draft Should Have

Breed Management

Feature ID Name Status Priority
FEATURE-038 Breed CRUD Draft Must Have
FEATURE-039 Breed Collection CRUD Draft Must Have
FEATURE-040 Breed-Collection Many-to-Many Draft Must Have

Supplier Management

Feature ID Name Status Priority
FEATURE-041 Supplier CRUD Draft Must Have

10. Access Control

Role-Based Puppy Visibility

Role Visible Statuses
Sales arrived, available
Kennel Staff arrived, available, unavailable
Admin All statuses

Operation Permissions

Operation Sales Kennel Staff Admin
View puppies (filtered) Yes Yes Yes
View wholesale price No No Yes
View supplier info No No Yes
Update puppy details No No Yes
Change status No Limited* Yes
Change price No No Yes
Bulk operations No No Yes
Manage breeds No No Yes
Manage suppliers No No Yes

*Kennel Staff can change status between: arrived ↔ available ↔ unavailable


11. Decisions

D1: Store Ownership Moved to Admin Settings (2026-01-19)

Status: Accepted Context: Store and brand configuration is more administrative than inventory-related Decision: Move Store entity to Admin Settings module; Inventory references stores via storeId Consequences: Cleaner separation; Inventory focused on puppy data only

D2: Minimal Breed Schema (2026-01-19)

Status: Accepted Context: Competitor sites have extensive breed data, but we need to ship Phase 1 Decision: Start with minimal breed fields (name, slug, description, hybrid, mediaIds); expand later based on content needs Consequences: Faster initial development; may need migration for additional fields

D3: Many-to-Many Breed Collections (2026-01-19)

Status: Accepted Context: Breeds can logically belong to multiple groupings (e.g., "Hypoallergenic" and "Small Dogs") Decision: Implement many-to-many relationship between Breeds and BreedCollections Consequences: More flexible categorization; slightly more complex queries

D4: Lexical for Rich Text (2026-01-19)

Status: Accepted Context: Description fields need rich text formatting Decision: Use Lexical editor; store as JSON in description fields Consequences: Consistent editor experience; requires Rich Text Editor module

D5: Legacy Sync via Webhook (2026-01-19)

Status: Accepted Context: Need to sync puppy data from existing POS system during transition Decision: Expose webhook endpoint for legacy system to push updates Consequences: Legacy system must be modified to call webhook; one-way sync only


12. References


Change Log

Version Date Author Changes
1.0 2026-01-19 Claude Initial module spec - Draft
1.1 2026-01-20 Claude Added references to Media, Admin Settings, Rich Text Editor modules
1.2 2026-01-20 Claude Updated relationship types with explicit module imports (Media['id'], Store['id'], etc.)