Inventory Module Technical Documentation

1. Overview

The Inventory Module is a comprehensive solution for managing retail inventory audits across multiple locations. Built using React Native and Expo, it provides a guided workflow for field auditors to conduct systematic inventory counts, track stock levels, and submit detailed reports to stakeholders.

1.1 Core Capabilities

  • Multi-location Inventory Management: Support for conducting audits across multiple retail locations with site-specific configurations
  • [Schedule](../Schedule/Schedule Listing/ScheduleListingOverview.md)-based Auditing: Frequency and specific-day based scheduling with timezone-aware calculations
  • Real-time Stock Tracking: Category-based SKU counting with movement tracking (in/out)
  • Multimedia Attachments: Support for photos and videos with compression and upload management
  • Digital Signatures: Dual signature capture (selfie and standard) for audit verification
  • Automated Reporting: Email-based report distribution with customizable recipient lists
  • Offline Support: Draft saving and sync capabilities for unreliable network conditions

1.2 Technical Stack

  • Frontend: React Native + Expo
  • State Management: Jotai (atoms) + React Query
  • Form Management: React Hook Form
  • Navigation: Expo Router (file-based routing)
  • UI Components: Tamagui + Custom components
  • Testing: Jest + React Native Testing Library

2. Architecture

The inventory module follows Clean Architecture principles with clear separation of concerns across three main layers:

graph TB
    subgraph "Presentation Layer"
        UI[UI Components]
        Controllers[Controllers]
        Screens[Screen Components]
    end
    
    subgraph "Business Logic Layer"
        UseCases[Use Cases]
        Services[Services]
        Domain[Domain Entities]
    end
    
    subgraph "Data Layer"
        Repositories[Repositories]
        API[API Clients]
        Cache[Cache/Storage]
    end
    
    UI --> Controllers
    Controllers --> UseCases
    UseCases --> Services
    Services --> Repositories
    Repositories --> API
    Repositories --> Cache
    
    Controllers --> Domain
    Services --> Domain
    UseCases --> Domain

2.1 Layer Responsibilities

  1. Presentation Layer

    • UI rendering and user interactions
    • Form validation and input handling
    • Navigation and routing logic
    • State presentation
  2. Business Logic Layer

    • Business rules and validations
    • Data transformations
    • Workflow orchestration
    • Domain model definitions
  3. Data Layer

    • API communication
    • Data persistence
    • Caching strategies
    • External service integration

3. Navigation Flow

The inventory audit process follows a linear, guided workflow designed to ensure data completeness and accuracy:

graph LR
    Home[Home Screen] --> Schedules[Schedules List]
    Schedules --> Audit[Audit Form]
    Audit --> Review[Review & Sign]
    Review --> Payoff[Success Screen]
    Payoff --> Home
    
    Review -.->|Cancel| Audit
    Audit -.->|Back| Schedules

3.1 Flow Characteristics

  • Progressive Disclosure: Information and actions revealed as needed
  • Data Validation: Each step validates before progression
  • State Preservation: Form data persisted across navigation
  • Completion Tracking: Progress indicators throughout

4. Screens and Routes

4.1 Route Structure

ScreenRoute PathFile LocationPurpose
Home/inventoryapps/expo/app/inventory/schedules.tsxEntry point showing daily locations
Schedules/inventory/[schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md)packages/app/features/inventory/schedules/screen.tsxAvailable schedule selection
Audit/inventory/auditpackages/app/features/inventory/audit/audit.tsxMain counting interface
Review/inventory/reviewpackages/app/features/inventory/review/review.tsxPre-submission validation
Payoff/inventory/payoffpackages/app/features/inventory/payoff/screen.tsxCompletion confirmation

4.2 Navigation Parameters

// Schedule to Audit
interface ScheduleToAuditParams {
  scheduleId: string;
  locationId: string;
  scheduleName: string;
}
 
// Audit to Review
interface AuditToReviewParams {
  draftReportId: string;
  scheduleId: string;
}
 
// Review to Payoff
interface ReviewToPayoffParams {
  reportId: string;
  duration: number;
  totalSKUs: number;
}

5. Home Screen

The Home screen serves as the primary entry point for field auditors to view their daily inventory assignments.

Component Structure

graph TD
    HomeScreen[Home Screen]
    HomeScreen --> Header[Date Header]
    HomeScreen --> SearchBar[Search Input]
    HomeScreen --> LocationList[Location Card List]
    
    LocationList --> LocationCard[Location Card]
    LocationCard --> LocationInfo[Site Details]
    LocationCard --> ScheduleInfo[Schedule Status]

Key Features

  1. Daily Schedule Display

    • Shows current date with day indicator
    • Filters locations requiring inventory today
    • Timezone-aware scheduling
  2. Search Functionality

    • Real-time filtering of locations
    • Search by site name or address
    • Debounced input for performance
  3. Location Cards

    • Site information display
    • Schedule status indicators
    • Quick access to audit workflow

Controller Logic

The inventoryHomeController manages:

  • Fetching daily inventory locations
  • Search query processing
  • Navigation to schedules
  • Cache invalidation
// Key controller methods
interface InventoryHomeController {
  locations: Location[];
  isLoading: boolean;
  searchQuery: string;
  setSearchQuery: (query: string) => void;
  navigateToSchedules: (locationId: string) => void;
  refetch: () => void;
}

Implementation Details

The home screen leverages:

  • React Query for data fetching and caching
  • Jotai atoms for global state management
  • Debounced search for performance optimization
  • Pull-to-refresh for data synchronization

6. Schedules Screen

The Schedules screen displays available inventory schedules for a selected location, with sophisticated status calculation and timezone-aware scheduling logic.

Component Architecture

graph TD
    SchedulesScreen[Schedules Screen]
    SchedulesScreen --> OfflineCheck{Internet Connected?}
    OfflineCheck -->|No| OfflinePage[Offline Page]
    OfflineCheck -->|Yes| MainContent[Main Content]
    
    MainContent --> Header[Schedule List Header]
    MainContent --> SearchBar[Search Input]
    MainContent --> ScheduleList[Schedule List]
    
    ScheduleList --> ScheduleItem[Schedule List Item]
    ScheduleItem --> StatusIndicator[Status Badge]
    ScheduleItem --> ProgressBar[Progress Indicator]
    ScheduleItem --> ActionButton[Action Button]

Schedule Status System

The module implements a sophisticated status calculation system with four primary states:

StatusColorDescriptionAction
COMPLETEDGreen (#0BBB45)Schedule fully finished”Report Available”
IN_PROGRESSYellow (#FFBC2B)Has active draft”Continue >“
NOT_STARTEDGrey (#8F8F8F)Not begun yet”Tap to Start >“
OVERDUERed (#FF0000)Past deadline”Check-in Passed”

Status Calculation Logic

The getScheduleStatusEnum function implements complex business rules:

flowchart TD
    Start[Start] --> CheckType{Schedule Type?}
    
    CheckType -->|Specific Days| SpecificDays[Check Specific Days Logic]
    CheckType -->|Frequency Based| FrequencyBased[Check Frequency Logic]
    
    SpecificDays --> SD1{Any Date Completed?}
    SD1 -->|Yes| Completed[COMPLETED]
    SD1 -->|No| SD2{Today Scheduled?}
    
    SD2 -->|Yes| SD3{Has Draft?}
    SD3 -->|Yes| InProgress[IN_PROGRESS]
    SD3 -->|No| SD4{In Time Window?}
    
    SD4 -->|No| Overdue[OVERDUE]
    SD4 -->|Yes| NotStarted[NOT_STARTED]
    
    SD2 -->|No| SD5{Past Dates Exist?}
    SD5 -->|Yes| Overdue
    SD5 -->|No| NotStarted
    
    FrequencyBased --> FB1{Past End Date?}
    FB1 -->|Yes| Overdue
    FB1 -->|No| FB2{Past Period Incomplete?}
    
    FB2 -->|Yes| Overdue
    FB2 -->|No| FB3{Current Period Status}
    
    FB3 --> FB4{All Occurrences Done?}
    FB4 -->|Yes| Completed
    FB4 -->|No| FB5{Has Draft?}
    
    FB5 -->|Yes| InProgress
    FB5 -->|No| FB6{In Time Window?}
    
    FB6 -->|No| Overdue
    FB6 -->|Yes| NotStarted

Schedule Types

  1. Specific Days Schedules

    • Fixed dates for inventory counts
    • Each date tracked independently
    • Complete when any date finished
  2. Frequency-Based Schedules

    • Daily, weekly, monthly patterns
    • Flexible day selection
    • Occurrence tracking (e.g., 3 times per week)

Key Features

  1. Timezone-Aware Calculations

    • Schedule-specific timezone support
    • Fallback to site timezone
    • All calculations in local timezone
  2. Time Window Enforcement

    • Optional deadline configuration
    • Start/end time validation
    • Automatic overdue marking
  3. Draft Management

    • Resume interrupted audits
    • Sync pending saves
    • Progress percentage display
  4. Search and Filtering

    • Real-time schedule filtering
    • Search by schedule name
    • Status-based filtering

Controller Methods

interface InventorySchedulesController {
  // State
  schedules: InventorySchedule[];
  isLoading: boolean;
  searchQuery: string;
  
  // Actions
  setSearchQuery: (query: string) => void;
  createNewReport: (schedule: InventorySchedule) => Promise<void>;
  continueReport: (schedule: InventorySchedule) => Promise<void>;
  resetSchedule: (scheduleId: string) => Promise<void>;
  
  // Navigation
  navigateToAudit: (params: NavigationParams) => void;
}

Business Logic Implementation

The schedule service implements:

  1. Status Priority Hierarchy

    COMPLETED → OVERDUE → IN_PROGRESS → NOT_STARTED
    
  2. Occurrence Calculation

    • Weekly: Counts completed days in current week
    • Monthly: Tracks completion within month
    • Custom: Flexible period definitions
  3. Progress Tracking

    • Visual progress bars
    • Completion ratios (X/Y format)
    • Draft progress percentages

7. Audit Screen

The Audit screen is the core inventory counting interface where field auditors perform systematic stock counts and movement tracking.

7.1 Architecture Overview

graph TB
    subgraph "Presentation"
        AuditScreen[Audit Screen]
        FormComponents[Form Components]
        CategoryList[Category List]
        InputComponents[Input Components]
    end
    
    subgraph "Business Logic"
        AuditController[Audit Controller]
        AuditService[Audit Service]
        ValidationRules[Validation Rules]
        AutoSave[Auto-Save Manager]
    end
    
    subgraph "Data"
        FormState[React Hook Form]
        LocalStorage[MMKV Storage]
        API[Backend API]
        Atoms[Jotai State]
    end
    
    AuditScreen --> AuditController
    FormComponents --> FormState
    AuditController --> AuditService
    AuditController --> AutoSave
    AutoSave --> LocalStorage
    AuditService --> API
    AuditController --> Atoms

7.2 Audit Types

The system supports two fundamental audit methodologies:

1. Opname (Stock Count)

Direct inventory counting where auditors enter the observed stock quantity:

flowchart LR
    A[Observe Stock] --> B[Enter Count]
    B --> C[Validate Input]
    C --> D[Update Status]
    D --> E[Check Threshold]
    E -->|Below| F[Mark Low Stock]
    E -->|Above| G[Mark OK]

Characteristics:

  • Single input field per SKU
  • Direct stock quantity entry
  • Automatic status calculation
  • Low stock threshold validation

2. Movement (Stock Movement)

Tracks inventory changes through categorized movements:

flowchart TD
    Start[Initial Stock] --> InMovements[Stock In]
    Start --> OutMovements[Stock Out]
    
    InMovements --> PO[Purchase Order]
    InMovements --> TI[Transfer In]
    InMovements --> OI[Other In]
    
    OutMovements --> Sales[Sales]
    OutMovements --> TO[Transfer Out]
    OutMovements --> Waste[Expired/Waste]
    OutMovements --> OO[Other Out]
    
    PO --> Calculate[Calculate Final Stock]
    TI --> Calculate
    OI --> Calculate
    Sales --> Calculate
    TO --> Calculate
    Waste --> Calculate
    OO --> Calculate
    
    Calculate --> Final[Final = Initial + In - Out]

Movement Categories:

  • Inbound: Purchase Order, Transfer In, Other In
  • Outbound: Sales, Transfer Out, Expired/Waste, Other Out
  • Calculation: Final Stock = Before Audit + Total In - Total Out

7.3 Component Structure

7.3.1 Category List Component

Virtual list implementation for performance:

interface CategoryListProps {
  categories: Category[];
  skuDetails: SKUReportDetail[];
  control: Control<InventoryReportForm>;
  onRefresh: () => void;
  isRefreshing: boolean;
}
 
// Virtual list configuration
const ESTIMATED_ITEM_HEIGHT = {
  category: 48,
  sku: 120,
  expandedSku: 280
};

7.3.2 SKU Card Component

Expandable cards with status indicators:

interface SKUCardState {
  isExpanded: boolean;
  status: 'default' | 'ok' | 'low-stock';
  hasValue: boolean;
  validationError?: string;
}

7.4 Input Validation System

The module implements comprehensive validation:

graph TD
    Input[User Input] --> TypeCheck{Is Numeric?}
    TypeCheck -->|No| ShowError[Show Error]
    TypeCheck -->|Yes| RangeCheck{In Range?}
    
    RangeCheck -->|No| Clamp[Clamp Value]
    RangeCheck -->|Yes| BusinessRules{Business Rules?}
    
    BusinessRules -->|Fail| ShowWarning[Show Warning]
    BusinessRules -->|Pass| UpdateValue[Update Form]
    
    Clamp --> UpdateValue
    UpdateValue --> UpdateStatus[Update Status]

Validation Rules:

  • Numeric only inputs
  • Non-negative values
  • Maximum value: 1,000,000,000
  • Real-time validation feedback
  • Business rule compliance

7.5 Auto-Save Implementation

Sophisticated auto-save mechanism ensures data persistence:

// Auto-save triggers
enum SaveTrigger {
  APP_STATE_CHANGE = 'app_state_change',
  BACK_BUTTON = 'back_button',
  MANUAL_SAVE = 'manual_save',
  NAVIGATION = 'navigation'
}
 
// Save flow
const autoSaveFlow = {
  trigger: SaveTrigger,
  validate: () => boolean,
  serialize: () => string,
  persist: () => Promise<void>,
  sync: () => Promise<void>
};

Key Features:

  • Sub-50ms save performance
  • Offline-first approach
  • Conflict resolution
  • Background sync
  • Modal state awareness

7.6 Attachment Management

Media attachment support (permission-based):

graph LR
    AttachmentButton[Add Attachment] --> TypeSelect{Select Type}
    TypeSelect --> Photo[Take Photo]
    TypeSelect --> Video[Record Video]
    TypeSelect --> Note[Add Note]
    
    Photo --> Preview[Show Thumbnail]
    Video --> Preview
    Note --> TextDisplay[Show Text]
    
    Preview --> Delete[Delete Option]

Capabilities:

  • Photo capture/selection
  • Video recording
  • Text annotations
  • Thumbnail previews
  • Batch operations

7.7 Progress Tracking

Real-time progress calculation:

interface ProgressMetrics {
  totalSKUs: number;
  modifiedSKUs: number;
  completionPercentage: number;
  lowStockCount: number;
  pendingSaves: number;
}

7.8 Performance Optimizations

  1. Virtual List Rendering

    • Renders only visible items
    • Estimated heights for smooth scrolling
    • Lazy accordion expansion
  2. Memoization Strategy

    • Component-level memoization
    • Callback optimization
    • Selective re-renders
  3. State Management

    • Atomic state updates
    • Batch operations
    • Debounced searches
  4. Data Handling

    • Incremental saves
    • Compressed storage
    • Efficient serialization

8. Review Screen

The Review screen provides comprehensive pre-submission validation and signature collection for inventory audits.

8.1 Screen Architecture

graph TB
    ReviewScreen[Review Screen]
    ReviewScreen --> SiteDetails[Site Details Card]
    ReviewScreen --> LowStock[Low Stock Alert]
    ReviewScreen --> Signatures[Signature Section]
    ReviewScreen --> Email[Email Recipients]
    ReviewScreen --> Submit[Submit Button]
    
    Signatures --> StandardSig[Standard Signature]
    Signatures --> SelfieSig[Selfie Signature]
    
    Submit --> ActionSheet[Review Action Sheet]
    ActionSheet --> Validation[Pre-Validation]
    Validation --> Upload[Upload Progress]
    Upload --> API[Submit to API]
    API --> Success[Navigate to Payoff]

8.2 Core Components

8.2.1 Site Details Card

Displays audit context information:

  • Location name and address
  • Schedule name and type
  • Check-in timestamp
  • Duration calculation

8.2.2 Low Stock Alert Section

Highlights inventory concerns:

  • Count of low stock items
  • Expandable list view
  • SKU details with thresholds
  • Visual warning indicators

8.2.3 Signature Management

Standard Signature Flow:

sequenceDiagram
    participant User
    participant SignatureCard
    participant SignaturePad
    participant Storage
    
    User->>SignatureCard: Tap to Sign
    SignatureCard->>SignaturePad: Open Modal
    User->>SignaturePad: Draw Signature
    SignaturePad->>Storage: Save Image
    Storage->>SignatureCard: Return URI
    SignatureCard->>User: Display Preview

Selfie Signature Flow:

sequenceDiagram
    participant User
    participant SelfieCard
    participant Camera
    participant ImageProcessor
    participant Form
    
    User->>SelfieCard: Tap to Capture
    SelfieCard->>Camera: Open Front Camera
    User->>Camera: Take Selfie
    Camera->>ImageProcessor: Process Image
    ImageProcessor->>Form: Update State
    Form->>SelfieCard: Display Thumbnail

8.3 Email Recipient Management

Dynamic email list configuration:

interface EmailTarget {
  email: string;
  enabled: boolean;
  setByAdmin: boolean;
  status: 'unsent' | 'sent' | 'failed';
}
 
// Email validation pattern
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

Features:

  • Add/remove recipients
  • Email validation
  • Duplicate prevention
  • Admin-set recipients protection

8.4 Submission Validation Flow

flowchart TD
    Start[Start Submission] --> ConnCheck{Internet Connected?}
    ConnCheck -->|No| ShowOffline[Show Offline Error]
    ConnCheck -->|Yes| SigCheck{Signatures Valid?}
    
    SigCheck -->|No| ShowSigError[Show Signature Error]
    SigCheck -->|Yes| AttachCheck{Attachments Valid?}
    
    AttachCheck -->|No| ShowAttachModal[Show Error Modal]
    ShowAttachModal --> ForceSubmit{Force Submit?}
    ForceSubmit -->|No| Cancel[Cancel Submission]
    ForceSubmit -->|Yes| Upload
    
    AttachCheck -->|Yes| Upload[Upload Attachments]
    Upload --> Track[Track Progress]
    Track --> Submit[Submit Report]
    Submit -->|Success| Navigate[Go to Payoff]
    Submit -->|Error| ShowError[Show Error Toast]

8.5 Attachment Upload System

8.5.1 Upload Architecture

interface UploadStrategy {
  endpoint: '/v1.0/miscellaneous/upload-file/attachment';
  method: 'POST';
  payload: FormData;
  headers: {
    'Content-Type': 'multipart/form-data';
  };
}
 
interface UploadProgress {
  current: number;
  total: number;
  percentage: number;
  isUploading: boolean;
}

8.5.2 Upload Categories

  1. SKU Attachments

    • Photos (JPEG/PNG)
    • Videos (MP4/MOV)
    • Compressed for performance
  2. Signature Images

    • Standard signatures
    • Base64 to image conversion
    • PNG format
  3. Selfie Signatures

    • Front camera captures
    • Auto-orientation
    • Quality optimization

8.6 Error Handling Strategy

graph TD
    Error[Error Detected] --> Type{Error Type}
    
    Type -->|Missing File| MissingModal[Show Missing Files]
    Type -->|Upload Failed| RetryUpload[Retry Upload]
    Type -->|API Error| ShowToast[Show Error Toast]
    Type -->|Validation| ValidationModal[Show Validation Errors]
    
    MissingModal --> UserChoice{User Action}
    UserChoice -->|Force Submit| ContinueSubmit[Submit Anyway]
    UserChoice -->|Cancel| StopSubmit[Cancel Process]
    
    RetryUpload -->|Success| Continue[Continue Flow]
    RetryUpload -->|Fail| LogError[Log and Continue]

8.7 Progress Tracking

Real-time upload feedback:

interface ProgressModal {
  visible: boolean;
  current: number;
  total: number;
  message: string;
  
  // Auto-close after completion
  autoCloseDelay: 1500; // ms
}

8.8 State Management

Atomic state updates via Jotai:

// Core atoms
const uploadProgressAtom = atom<UploadProgress>({
  current: 0,
  total: 0,
  percentage: 0,
  isUploading: false
});
 
const attachmentErrorsAtom = atom<AttachmentError[]>([]);
const isSubmittingAtom = atom<boolean>(false);

8.9 Submission Payload

Final report structure:

interface SubmissionPayload {
  // Signatures
  signatures: Array<{
    name: string;
    role: string;
    attachmentID: string;
  }>;
  
  // Selfie signatures
  selfieSignatures: Array<{
    attachmentID: string;
  }>;
  
  // Email configuration
  emailTargets: EmailTarget[];
  
  // Inventory data
  skuDetails: Array<{
    skuID: string;
    finalStockOnHand: number;
    movements?: StockMovement[];
    attachments?: Attachment[];
  }>;
  
  // Timestamps
  checkInAt: Date;
  checkOutAt: Date;
  submittedAt: Date;
}

8.10 Performance Considerations

  1. Parallel Uploads

    • Concurrent attachment uploads
    • Batch processing
    • Progress aggregation
  2. Memory Management

    • Image compression
    • Cleanup after upload
    • Efficient FormData usage
  3. Error Recovery

    • Graceful degradation
    • Partial success handling
    • Retry mechanisms

9. Payoff Screen

The Payoff screen serves as the success confirmation and summary dashboard after successful inventory submission.

9.1 Screen Architecture

graph TB
    PayoffScreen[Payoff Screen]
    PayoffScreen --> Animation[Success Animation]
    PayoffScreen --> Message[Success Message]
    PayoffScreen --> Summary[Summary Card]
    PayoffScreen --> CTA[Got It Button]
    
    Summary --> DateInfo[Date/Time Info]
    Summary --> Metrics[Audit Metrics]
    Summary --> LowStock[Low Stock Alert]
    
    LowStock --> BottomSheet[Low Stock Details]
    CTA --> Navigate[Navigate Home]
    Navigate --> InvalidateCache[Invalidate Schedules]

9.2 Component Structure

9.2.1 Success Animation

  • Lottie animation for celebration
  • Visual feedback for completion
  • Engaging user experience

9.2.2 Summary Card

Comprehensive audit metrics display:

interface SummaryData {
  // Visit Information
  dateOfVisit: string;      // Formatted date
  timeOfVisit: string;      // Formatted time with timezone
  duration: string;         // "2h 30m" format
  
  // Audit Metrics
  totalSkusChecked: number;
  lowStockTotal: number;
  
  // Site Context
  siteName: string;
}

9.3 Low Stock Management

sequenceDiagram
    participant User
    participant PayoffScreen
    participant LowStockRow
    participant BottomSheet
    participant FlatList
    
    PayoffScreen->>LowStockRow: Render if items > 0
    User->>LowStockRow: Tap to view
    LowStockRow->>BottomSheet: Open modal
    BottomSheet->>FlatList: Render items
    FlatList->>User: Display SKU details

9.3.1 Low Stock Item Structure

interface LowStockSKU {
  name: string;
  productCode: string;
  unitOfMeasure: string;
  finalStockOnHand: number;
  lowStockThreshold: number;
}
 
// Display format
const displayText = `${finalStockOnHand} ${unitOfMeasure}`;

9.4 Data Flow Management

flowchart TD
    PrevScreen[Review Screen] --> SetData[Set Payoff Data]
    SetData --> Atom[payoffDataAtom]
    SetData --> URLParams[URL Query Params]
    
    PayoffScreen[Payoff Screen] --> CheckAtom{Atom Has Data?}
    CheckAtom -->|Yes| UseAtom[Use Atom Data]
    CheckAtom -->|No| CheckURL{URL Has Params?}
    
    CheckURL -->|Yes| UseURL[Parse URL Data]
    CheckURL -->|No| UseEmpty[Show Empty State]
    
    UseAtom --> RenderScreen[Render Summary]
    UseURL --> RenderScreen
    UseEmpty --> RenderScreen

9.5 Navigation Behavior

9.5.1 Exit Flow

const handleGotIt = () => {
  // 1. Navigate to home
  router.push('/home/inventory');
  
  // 2. Invalidate cache
  queryClient.invalidateQueries(['getSKUSchedules']);
  
  // 3. Reset state
  setIsSubmitting(false);
};

9.5.2 Back Button Handling

  • Android: Intercepts and redirects to inventory home
  • iOS: Natural navigation (no back button)
  • Prevents returning to submission flow

9.6 State Management

9.6.1 Primary State (Jotai Atom)

const payoffDataAtom = atom<FormattedReportData | null>(null);
 
interface FormattedReportData {
  siteName: string;
  checkInTime: Date;
  checkOutTime: Date;
  duration: string;
  totalSkusChecked: number;
  lowStockTotal: number;
  lowStockSkus: LowStockSKU[];
}

9.6.2 Fallback State (URL Parameters)

interface PayoffQueryParams {
  siteName?: string;
  checkInRaw?: string;
  checkOutRaw?: string;
  duration?: string;
  totalSkusChecked?: string;
  lowStockTotal?: string;
}

9.7 UI/UX Features

9.7.1 Visual Design

  • Background: Custom payoff gradient
  • Card: Elevated with shadow effect
  • Colors: Success green theme
  • Animation: Celebratory Lottie

9.7.2 Typography

const textStyles = {
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '$color.green900'
  },
  label: {
    fontSize: 14,
    color: '$color.gray700'
  },
  value: {
    fontSize: 16,
    fontWeight: '600',
    color: '$color.gray900'
  }
};

9.7.3 Accessibility

  • Fixed font scaling for consistency
  • High contrast text
  • Clear visual hierarchy
  • Screen reader support

9.8 Performance Optimizations

  1. List Rendering

    // Virtualized list for low stock items
    <FlatList
      data={lowStockSkus}
      keyExtractor={(item) => item.productCode}
      renderItem={renderItem}
      initialNumToRender={10}
      maxToRenderPerBatch={10}
      windowSize={10}
    />
  2. Component Memoization

    const LowStockItem = memo(({ item, index }) => {
      // Render logic
    });
  3. State Reset

    • Clears navigation params
    • Resets form state
    • Invalidates stale queries

9.9 Localization

All text content uses the localization system:

const LL = useI18n();
 
// Usage examples
LL.inventory.payoff.title()
LL.inventory.payoff.generatingReport()
LL.inventory.payoff.summary.dateOfVisit()
LL.inventory.payoff.gotIt()

9.10 Success Metrics Display

The screen prominently displays:

  1. Temporal Data: When the audit occurred
  2. Efficiency Metric: Time spent on audit
  3. Completeness Metric: Total SKUs checked
  4. Alert Metric: Low stock items requiring attention

This comprehensive summary provides immediate value to users while celebrating their completed work.

10. API Endpoints

The inventory module integrates with backend services through a well-defined RESTful API structure.

10.1 Endpoint Summary Table

EndpointMethodPurposeFile LocationRequest/Response
/v1.0/skus/[site](../Sites/SitesOverview.md)-skus/fetchSites/{userId}GETFetch sites with SKU schedulesinventory-home-repository.tsReturns site list with schedule info
/v1.0/skus/[schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md)/activeGETGet active schedules for siteinventory-schedules-repository.tsQuery: siteId, date, timezone
/v1.0/skus/[reports](../Reports/ReportsOverview.md)POSTCreate new SKU reportinventory-schedules-repository.tsBody: scheduleId, date, timezone
/v1.0/skus/[reports](../Reports/ReportsOverview.md)/{skuReportID}GETFetch specific SKU reportinventory-schedules-repository.tsReturns full report data
/v1.0/skus/[reports](../Reports/ReportsOverview.md)/{skuReportID}DELETEReset/delete SKU reportinventory-schedules-repository.tsRemoves draft report
/v1.0/skus/compactGETFetch SKUs in compact formatinventory-audit-repository.tsQuery: scheduleId
/v1.0/skus/[reports](../Reports/ReportsOverview.md)/{reportID}PATCHUpdate SKU report (save draft)inventory-audit-repository.tsBody: partial report data
/v1.0/skus/[reports](../Reports/ReportsOverview.md)/submit/{reportID}POSTSubmit completed reportinventory-review-repository.tsBody: signatures, emails, SKU data
/v1.0/miscellaneous/upload-file/attachmentPOSTUpload attachmentsupload-inventory-attachments.tsFormData: file, type, metadata

10.2 API Integration Flow

sequenceDiagram
    participant App
    participant API
    participant Backend
    
    App->>API: GET fetchSites/{userId}
    API->>Backend: Query user sites
    Backend-->>App: Return site list
    
    App->>API: GET schedules/active
    API->>Backend: Query active schedules
    Backend-->>App: Return schedules
    
    App->>API: POST reports
    API->>Backend: Create new report
    Backend-->>App: Return reportId
    
    App->>API: GET compact SKUs
    API->>Backend: Fetch SKU list
    Backend-->>App: Return SKUs
    
    App->>API: PATCH reports/{id}
    API->>Backend: Save draft
    Backend-->>App: Confirm save
    
    App->>API: POST upload attachment
    API->>Backend: Store file
    Backend-->>App: Return reference
    
    App->>API: POST submit/{id}
    API->>Backend: Process submission
    Backend-->>App: Confirm success

10.3 Error Handling

All API calls implement standardized error handling:

interface APIError {
  statusCode: number;
  message: string;
  details?: Record<string, any>;
}
 
// Common error scenarios
- 400: Bad Request (validation errors)
- 401: Unauthorized (auth token expired)
- 404: Not Found (resource doesn't exist)
- 409: Conflict (duplicate submission)
- 500: Server Error (backend issues)

11. Package Dependencies

11.1 Core Dependencies Table

PackageVersionPurposeUsage in Inventory Module
react^18.2.0Core React libraryComponent building, hooks, lifecycle
react-native0.72.xMobile frameworkPlatform-specific components
expo~49.0.0Development platformCamera, media, file system
@tamagui/core1.xUI frameworkCross-platform styling
jotai^2.xState managementGlobal state atoms
@tanstack/react-query^4.xServer stateAPI data fetching/caching
react-hook-form^7.xForm managementInventory form validation
dayjs^1.11.xDate utilitiesTimezone calculations
zod^3.xSchema validationType-safe validation
@nimbly-technologies/nimbly-commonworkspace:*Shared typesCommon interfaces/enums

11.2 UI Component Libraries

PackagePurposeKey Usage
@my/uiInternal component libraryButtons, cards, modals
@tamagui/lucide-iconsIcon setUI icons throughout
@expo/vector-iconsAdditional iconsSpecialized icons
lottie-react-nativeAnimationsSuccess animations

11.3 Specialized Libraries

PackagePurposeSpecific Use Case
expo-avMedia playbackVideo attachments preview
fuse.jsFuzzy searchSKU search functionality
react-native-safe-area-contextSafe area handlingProper screen boundaries
solitoNavigation utilitiesCross-platform routing

11.4 Development Dependencies

PackagePurpose
vitestUnit testing framework
@testing-library/reactComponent testing
@testing-library/react-hooksHook testing
typescriptType safety

12. State Management

The inventory module uses a hybrid state management approach optimized for different data types:

12.1 State Architecture

graph TB
    subgraph "Client State (Jotai)"
        FormState[Form Data]
        UIState[UI State]
        DraftState[Draft Reports]
        SearchState[Search Queries]
    end
    
    subgraph "Server State (React Query)"
        ScheduleData[Schedule Data]
        SKUData[SKU Lists]
        ReportData[Report Data]
        SubmissionState[Submission Status]
    end
    
    subgraph "Local Storage (MMKV)"
        PendingSaves[Pending Saves]
        DraftBackups[Draft Backups]
    end
    
    FormState --> PendingSaves
    ReportData --> DraftState
    ScheduleData --> UIState

12.2 Key Atoms (Jotai)

// Site list for home screen
export const siteListAtom = atom<GetSiteSKUScheduleListResponse[]>([]);
 
// Current draft report ID
export const draftReportIDAtom = atom<string | null>(null);
 
// Search query for SKU filtering
export const inventoryAuditSkuSearchAtom = atom<string>('');
 
// Review button disable state
export const isReviewDisabledAtom = atom<boolean>(false);
 
// Upload progress tracking
export const inventoryUploadProgressAtom = atom<UploadProgress>({
  current: 0,
  total: 0,
  percentage: 0,
  isUploading: false
});
 
// Attachment validation errors
export const inventoryAttachmentErrorListAtom = atom<AttachmentError[]>([]);
 
// Submission state
export const isSubmittingAtom = atom<boolean>(false);
 
// Payoff screen data
export const payoffDataAtom = atom<FormattedReportData | null>(null);

12.3 React Query Keys

// Query key patterns
const queryKeys = {
  siteSKUSchedules: ['getSiteSKUScheduleList', userId],
  schedules: ['getSKUSchedules', siteId, date, timezone],
  skuReport: ['getSKUReport', reportId],
  skuList: ['getSKUs', scheduleId]
};
 
// Mutation keys
const mutationKeys = {
  createReport: 'createSKUReport',
  updateReport: 'updateSKUReport',
  submitReport: 'submitSKUReport',
  uploadFile: 'uploadFile'
};

13. Data Flow

The inventory module implements a sophisticated data flow pattern that ensures data consistency and optimal performance.

Complete Data Flow Diagram

graph TB
    subgraph "User Journey"
        Start[User Opens App] --> Home[Home Screen]
        Home --> Schedules[Select Schedule]
        Schedules --> Audit[Count Inventory]
        Audit --> Review[Review & Sign]
        Review --> Submit[Submit Report]
        Submit --> Payoff[Success Screen]
        Payoff --> Home
    end
    
    subgraph "Data Operations"
        Home --> FetchSites[Fetch Sites API]
        Schedules --> FetchSchedules[Fetch Schedules API]
        Schedules --> CreateReport[Create Report API]
        Audit --> FetchSKUs[Fetch SKUs API]
        Audit --> SaveDraft[Save Draft Local/API]
        Review --> ValidateData[Validate All Data]
        Submit --> UploadFiles[Upload Attachments]
        Submit --> SubmitAPI[Submit Report API]
    end
    
    subgraph "State Updates"
        FetchSites --> UpdateSiteAtom[Update Site Atom]
        CreateReport --> UpdateDraftAtom[Update Draft Atom]
        SaveDraft --> UpdateFormState[Update Form State]
        UploadFiles --> UpdateProgress[Update Progress Atom]
        SubmitAPI --> UpdatePayoffAtom[Update Payoff Atom]
    end

Form Data Lifecycle

stateDiagram-v2
    [*] --> Empty: Initial State
    Empty --> Loading: Fetch SKU Data
    Loading --> Loaded: Data Ready
    Loaded --> Editing: User Input
    Editing --> Validating: Real-time Validation
    Validating --> Valid: Pass
    Validating --> Invalid: Fail
    Valid --> Saving: Auto-save Trigger
    Invalid --> Editing: Fix Errors
    Saving --> Saved: Local Storage
    Saved --> Syncing: Online Sync
    Syncing --> Synced: API Updated
    Synced --> Editing: Continue Edit
    Synced --> Reviewing: Navigate Review
    Reviewing --> Submitting: Submit Report
    Submitting --> Submitted: Success
    Submitted --> [*]

14. Key Components

Inventory Form Component

The central form component that manages all SKU data entry:

// Location: packages/app/features/inventory/audit/_components/inventory-form.tsx
export const InventoryForm = ({
  control,
  skuReportDetails,
  onRefresh,
  isRefreshing,
  categories
}: InventoryFormProps) => {
  // Virtual list optimization for large SKU lists
  const flatListRef = useRef<FlatList>(null);
  
  // Search functionality
  const searchQuery = useAtomValue(inventoryAuditSkuSearchAtom);
  
  // Filtered and sectioned data
  const { filteredSections, totalItems } = useMemo(() => {
    // Complex filtering logic
    return filterAndSectionSKUs(skuReportDetails, categories, searchQuery);
  }, [skuReportDetails, categories, searchQuery]);
  
  // Render optimizations
  const renderItem = useCallback(({ item, index }) => {
    // Memoized render logic
  }, [control]);
  
  return (
    <VirtualList
      ref={flatListRef}
      data={filteredSections}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      getItemLayout={getItemLayout}
      // Performance props
      removeClippedSubviews
      maxToRenderPerBatch={10}
      windowSize={10}
      initialNumToRender={5}
    />
  );
};

Schedule List Item Component

Displays schedule status with sophisticated business logic:

// Location: packages/app/features/inventory/schedules/_components/schedule-list-item.tsx
export const ScheduleListItem = ({ schedule, onPress }: Props) => {
  const { status, progress, actionText, isClickable } = useScheduleStatus(schedule);
  
  return (
    <TouchableOpacity
      disabled={!isClickable}
      onPress={() => onPress(schedule)}
      style={[styles.container, getStatusStyle(status)]}
    >
      <StatusBadge status={status} />
      <ScheduleInfo schedule={schedule} />
      <ProgressBar progress={progress} />
      <ActionButton text={actionText} status={status} />
    </TouchableOpacity>
  );
};
 
// Status calculation hook
const useScheduleStatus = (schedule: InventorySchedule) => {
  return useMemo(() => {
    const status = getScheduleStatusEnum(schedule);
    const progress = calculateProgress(schedule);
    const actionText = getActionText(status);
    const isClickable = status !== ScheduleStatus.COMPLETED;
    
    return { status, progress, actionText, isClickable };
  }, [schedule]);
};

Movement Input Sheet Component

Handles complex stock movement calculations:

// Location: packages/app/features/inventory/audit/_components/inventory-movement-input-sheet.tsx
export const InventoryMovementInputSheet = ({
  isOpen,
  onClose,
  skuDetail,
  control,
  index
}: Props) => {
  // Movement calculations
  const calculateFinalStock = useCallback((movements: StockMovements) => {
    const totalIn = movements.purchaseOrder + 
                   movements.transferIn + 
                   movements.otherIn;
    const totalOut = movements.sales + 
                    movements.transferOut + 
                    movements.expired + 
                    movements.otherOut;
    
    return skuDetail.beforeAuditStockOnHand + totalIn - totalOut;
  }, [skuDetail.beforeAuditStockOnHand]);
  
  // Apply to all functionality
  const handleApplyToAll = useCallback((category: MovementCategory, value: number) => {
    const updates = skuDetails.map((sku, idx) => ({
      index: idx,
      category,
      value
    }));
    
    batchUpdateMovements(updates);
  }, [skuDetails]);
  
  return (
    <Sheet
      modal
      open={isOpen}
      onOpenChange={onClose}
      snapPoints={[80]}
      dismissOnSnapToBottom
    >
      <Sheet.Frame>
        <MovementHeader sku={skuDetail} />
        <MovementInputs control={control} index={index} />
        <FinalStockDisplay 
          value={calculateFinalStock(watchedMovements)}
          threshold={skuDetail.lowStockThreshold}
        />
        <ApplyToAllSection onApply={handleApplyToAll} />
      </Sheet.Frame>
    </Sheet>
  );
};

15. Business Logic

15.1 Schedule Status Calculation Engine

The core business logic for determining schedule status:

// Location: packages/app/features/inventory/schedules/utils/schedule-status.tsx
 
export const getScheduleStatusEnum = (
  schedule: InventorySchedule,
  timezone?: string
): ScheduleStatus => {
  // Timezone resolution
  const effectiveTimezone = schedule.timezone || timezone || 'UTC';
  
  // Current time in schedule timezone
  const now = getCurrentTimeInTimezone(effectiveTimezone);
  const today = getDateInTimezone(effectiveTimezone);
  
  // Specific days logic
  if (hasSpecificDays(schedule)) {
    return calculateSpecificDaysStatus(schedule, today, now);
  }
  
  // Frequency-based logic
  return calculateFrequencyStatus(schedule, today, now);
};
 
const calculateSpecificDaysStatus = (
  schedule: InventorySchedule,
  today: string,
  currentTime: number
): ScheduleStatus => {
  const { scheduledDates } = schedule.specificDaysSchedule;
  
  // Check completion
  const hasCompleted = Object.values(scheduledDates).some(
    date => date.status === 'completed'
  );
  if (hasCompleted) return ScheduleStatus.COMPLETED;
  
  // Check today's status
  const todaySchedule = scheduledDates[today];
  if (todaySchedule) {
    if (todaySchedule.draftReportID) return ScheduleStatus.IN_PROGRESS;
    if (isInTimeWindow(schedule, currentTime)) return ScheduleStatus.NOT_STARTED;
    return ScheduleStatus.OVERDUE;
  }
  
  // Check past dates
  const hasPastDates = Object.keys(scheduledDates).some(
    date => date < today && scheduledDates[date].status !== 'completed'
  );
  
  return hasPastDates ? ScheduleStatus.OVERDUE : ScheduleStatus.NOT_STARTED;
};

Auto-Save Manager

Sophisticated auto-save implementation with conflict resolution:

// Location: packages/app/features/inventory/audit/_controllers/useAutoSave.ts
 
export const useAutoSave = (reportId: string) => {
  const formData = useWatch();
  const [isSaving, setIsSaving] = useState(false);
  const saveTimeoutRef = useRef<NodeJS.Timeout>();
  
  // Debounced save function
  const debouncedSave = useMemo(
    () => debounce(async (data: InventoryReportForm) => {
      if (isSaving || !shouldSave(data)) return;
      
      setIsSaving(true);
      try {
        // Save to local storage first
        await saveToMMKV(reportId, data);
        
        // Attempt API sync if online
        if (isOnline()) {
          await syncToAPI(reportId, data);
          clearLocalSave(reportId);
        }
      } catch (error) {
        console.error('Auto-save failed:', error);
      } finally {
        setIsSaving(false);
      }
    }, 3000),
    [reportId, isSaving]
  );
  
  // Trigger save on data changes
  useEffect(() => {
    debouncedSave(formData);
    
    return () => {
      debouncedSave.cancel();
    };
  }, [formData, debouncedSave]);
  
  // App state change handler
  useAppStateChange((state) => {
    if (state === 'background') {
      debouncedSave.flush();
    }
  });
  
  return { isSaving };
};

Stock Validation Engine

Complex validation rules for inventory counts:

// Location: packages/app/features/inventory/audit/_services/inventory-audit-service.ts
 
export class InventoryAuditService {
  static validateStockEntry(
    value: string | number,
    auditType: AuditType,
    skuDetail: SKUReportDetail
  ): ValidationResult {
    // Type validation
    if (!isNumeric(value)) {
      return {
        isValid: false,
        error: 'Value must be numeric',
        sanitizedValue: null
      };
    }
    
    const numericValue = Number(value);
    
    // Range validation
    if (numericValue < 0) {
      return {
        isValid: false,
        error: 'Value cannot be negative',
        sanitizedValue: 0
      };
    }
    
    if (numericValue > MAX_STOCK_VALUE) {
      return {
        isValid: false,
        error: `Value cannot exceed ${MAX_STOCK_VALUE}`,
        sanitizedValue: MAX_STOCK_VALUE
      };
    }
    
    // Business rule validation
    if (auditType === AuditType.MOVEMENT) {
      const movements = calculateTotalMovements(skuDetail);
      const finalStock = skuDetail.beforeAuditStockOnHand + movements.in - movements.out;
      
      if (finalStock < 0) {
        return {
          isValid: false,
          error: 'Final stock cannot be negative',
          sanitizedValue: numericValue
        };
      }
    }
    
    // Low stock warning
    const warning = numericValue < skuDetail.lowStockThreshold
      ? 'Stock is below threshold'
      : undefined;
    
    return {
      isValid: true,
      sanitizedValue: numericValue,
      warning
    };
  }
}

16. Error Handling

16.1 Comprehensive Error Management

graph TD
    Error[Error Occurs] --> Type{Error Type}
    
    Type -->|Network| NetworkHandler[Network Error Handler]
    Type -->|Validation| ValidationHandler[Validation Handler]
    Type -->|Storage| StorageHandler[Storage Handler]
    Type -->|Media| MediaHandler[Media Handler]
    Type -->|API| APIHandler[API Error Handler]
    
    NetworkHandler --> Retry{Retry Available?}
    Retry -->|Yes| RetryQueue[Add to Retry Queue]
    Retry -->|No| ShowOffline[Show Offline Message]
    
    ValidationHandler --> ShowInline[Show Inline Error]
    StorageHandler --> Fallback[Use Fallback Storage]
    MediaHandler --> SkipMedia[Skip Media Upload]
    APIHandler --> ParseError[Parse Error Response]
    
    ParseError --> UserMessage[Show User Message]
    RetryQueue --> BackgroundSync[Background Sync]

Error Recovery Strategies

// Network Error Recovery
export const withNetworkRetry = async <T>(
  operation: () => Promise<T>,
  options: RetryOptions = {}
): Promise<T> => {
  const { maxRetries = 3, backoff = 1000 } = options;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (!isNetworkError(error) || attempt === maxRetries - 1) {
        throw error;
      }
      
      await delay(backoff * Math.pow(2, attempt));
    }
  }
  
  throw new Error('Max retries exceeded');
};
 
// Storage Error Recovery
export const withStorageFallback = async (
  primary: () => Promise<void>,
  fallback: () => Promise<void>
): Promise<void> => {
  try {
    await primary();
  } catch (error) {
    console.warn('Primary storage failed, using fallback:', error);
    await fallback();
  }
};
 
// Validation Error Display
export const displayValidationError = (
  error: ValidationError,
  toast: ToastController
): void => {
  const message = error.field
    ? `${error.field}: ${error.message}`
    : error.message;
  
  toast.show({
    type: 'error',
    message,
    duration: 3000,
    position: 'top'
  });
};

17. Performance Optimizations

Rendering Optimizations

// Virtual List Implementation
export const OptimizedSKUList = memo(({ data, ...props }) => {
  // Memoized sections
  const sections = useMemo(() => 
    data.reduce((acc, item) => {
      const section = acc.find(s => s.categoryId === item.categoryId);
      if (section) {
        section.data.push(item);
      } else {
        acc.push({
          categoryId: item.categoryId,
          categoryName: item.categoryName,
          data: [item]
        });
      }
      return acc;
    }, []),
    [data]
  );
  
  // Optimized item layout
  const getItemLayout = useCallback((data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index
  }), []);
  
  // Render callbacks
  const renderItem = useCallback(({ item }) => (
    <SKUCard {...item} />
  ), []);
  
  const keyExtractor = useCallback(
    (item) => item.skuId,
    []
  );
  
  return (
    <SectionList
      sections={sections}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      getItemLayout={getItemLayout}
      // Performance settings
      removeClippedSubviews
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      windowSize={10}
      initialNumToRender={10}
      {...props}
    />
  );
});

Memory Management

// Image compression and cleanup
export const useImageOptimization = () => {
  const compressImage = useCallback(async (uri: string): Promise<string> => {
    const compressed = await ImageManipulator.manipulateAsync(
      uri,
      [{ resize: { width: 1024 } }],
      { compress: 0.7, format: ImageManipulator.SaveFormat.JPEG }
    );
    
    // Clean up original if different
    if (compressed.uri !== uri) {
      await FileSystem.deleteAsync(uri, { idempotent: true });
    }
    
    return compressed.uri;
  }, []);
  
  const cleanupImages = useCallback(async (uris: string[]) => {
    await Promise.all(
      uris.map(uri => 
        FileSystem.deleteAsync(uri, { idempotent: true })
          .catch(console.warn)
      )
    );
  }, []);
  
  // Cleanup on unmount
  useEffect(() => {
    return () => {
      // Cleanup temporary images
      cleanupImages(tempImageRefs.current);
    };
  }, []);
  
  return { compressImage, cleanupImages };
};

18. Testing Strategy

Test Coverage Overview

graph LR
    Tests[Test Suite] --> Unit[Unit Tests]
    Tests --> Integration[Integration Tests]
    Tests --> E2E[E2E Tests]
    
    Unit --> Components[Component Tests]
    Unit --> Hooks[Hook Tests]
    Unit --> Utils[Utility Tests]
    Unit --> Services[Service Tests]
    
    Integration --> API[API Integration]
    Integration --> State[State Management]
    Integration --> Navigation[Navigation Flow]
    
    E2E --> UserFlows[User Workflows]
    E2E --> ErrorScenarios[Error Scenarios]

Testing Examples

// Component Testing
describe('InventoryForm', () => {
  it('should render SKU list correctly', () => {
    const { getByTestId } = render(
      <InventoryForm
        control={mockControl}
        skuReportDetails={mockSKUs}
        categories={mockCategories}
      />
    );
    
    expect(getByTestId('sku-list')).toBeTruthy();
    expect(getByTestId('sku-item-0')).toHaveTextContent('Test SKU');
  });
  
  it('should handle search filtering', async () => {
    const { getByTestId, queryByTestId } = render(<InventoryForm {...props} />);
    
    const searchInput = getByTestId('search-input');
    await userEvent.type(searchInput, 'specific sku');
    
    await waitFor(() => {
      expect(queryByTestId('sku-item-filtered')).toBeTruthy();
      expect(queryByTestId('sku-item-other')).toBeFalsy();
    });
  });
});
 
// Service Testing
describe('InventoryAuditService', () => {
  describe('validateStockEntry', () => {
    it('should reject negative values', () => {
      const result = InventoryAuditService.validateStockEntry(
        -5,
        AuditType.OPNAME,
        mockSKU
      );
      
      expect(result.isValid).toBe(false);
      expect(result.error).toBe('Value cannot be negative');
    });
    
    it('should warn on low stock', () => {
      const lowStockSKU = { ...mockSKU, lowStockThreshold: 100 };
      const result = InventoryAuditService.validateStockEntry(
        50,
        AuditType.OPNAME,
        lowStockSKU
      );
      
      expect(result.isValid).toBe(true);
      expect(result.warning).toBe('Stock is below threshold');
    });
  });
});
 
// Integration Testing
describe('Inventory Audit Flow', () => {
  it('should complete full audit workflow', async () => {
    const { navigate } = renderApp();
    
    // Navigate to schedules
    await navigate('/inventory/schedules');
    
    // Select schedule
    const schedule = await screen.findByTestId('schedule-item-0');
    await userEvent.press(schedule);
    
    // Fill audit form
    const skuInput = await screen.findByTestId('sku-input-0');
    await userEvent.type(skuInput, '100');
    
    // Navigate to review
    const reviewBtn = await screen.findByTestId('review-button');
    await userEvent.press(reviewBtn);
    
    // Add signature
    const signatureBtn = await screen.findByTestId('signature-button');
    await userEvent.press(signatureBtn);
    
    // Submit
    const submitBtn = await screen.findByTestId('submit-button');
    await userEvent.press(submitBtn);
    
    // Verify success
    await waitFor(() => {
      expect(screen.getByText('Report Submitted!')).toBeTruthy();
    });
  });
});

19. Troubleshooting Guide

Common Issues and Solutions

1. Schedule Status Incorrect

Problem: Schedule shows wrong status (e.g., NOT_STARTED when should be OVERDUE)

Root Cause: Timezone mismatch between client and server

Solution:

// Ensure timezone is properly set
const effectiveTimezone = schedule.timezone || site.timezone || 'UTC';
 
// Always use timezone-aware functions
import { getDateInTimezone, getCurrentTimeInMinutesInTimezone } from './date-helpers';
 
const now = getCurrentTimeInMinutesInTimezone(effectiveTimezone);
const today = getDateInTimezone(effectiveTimezone);

2. Auto-Save Not Working

Problem: Form data not persisting when app backgrounded

Root Cause: Modal open state blocking save

Solution:

// Check modal state before save
const canSave = !isModalOpen && !isSubmitting && hasChanges;
 
if (canSave) {
  await saveFormData();
}

3. Large SKU Lists Lagging

Problem: Performance issues with 300+ SKUs

Root Cause: Rendering all items at once

Solution:

// Use virtual list with proper configuration
<FlatList
  data={skus}
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
  removeClippedSubviews
  maxToRenderPerBatch={10}
  windowSize={10}
  initialNumToRender={5}
/>

4. Attachment Upload Failures

Problem: Photos/videos failing to upload

Root Cause: File size or corrupted files

Solution:

// Compress before upload
const compressedUri = await ImageManipulator.manipulateAsync(
  uri,
  [{ resize: { width: 1024 } }],
  { compress: 0.7, format: 'jpeg' }
);
 
// Validate file exists
const fileInfo = await FileSystem.getInfoAsync(uri);
if (!fileInfo.exists) {
  throw new Error('File not found');
}

Debugging Techniques

Enable Debug Logging

// Add to environment config
export const DEBUG_INVENTORY = __DEV__ && true;
 
// Use in code
if (DEBUG_INVENTORY) {
  console.log('[Inventory](../Inventory/InventoryOverview.md)', 'Schedule status:', status);
  console.log('[Inventory](../Inventory/InventoryOverview.md)', 'Timezone:', effectiveTimezone);
  console.log('[Inventory](../Inventory/InventoryOverview.md)', 'Current time:', currentTime);
}

React Query DevTools

// In development, add DevTools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
 
if (__DEV__) {
  return (
    <>
      <App />
      <ReactQueryDevtools />
    </>
  );
}

Performance Profiling

// Use React DevTools Profiler
import { Profiler } from 'react';
 
<Profiler id="InventoryForm" onRender={onRenderCallback}>
  <InventoryForm {...props} />
</Profiler>
 
// Callback to measure performance
const onRenderCallback = (id, phase, actualDuration) => {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
};

Security Considerations

  1. Data Validation

    // Always validate user input
    const sanitizedValue = DOMPurify.sanitize(userInput);
     
    // Validate against schema
    const result = inventorySchema.safeParse(data);
    if (!result.success) {
      throw new ValidationError(result.error);
    }
  2. API Security

    • Use HTTPS for all requests
    • Include authentication tokens
    • Validate response data
    • Handle token expiration
  3. Local Storage Security

    • Encrypt sensitive data
    • Clear data on logout
    • Use secure storage APIs
    • Implement data expiration

Accessibility

  1. Screen Reader Support

    <TouchableOpacity
      accessible
      accessibilityRole="button"
      accessibilityLabel={`Schedule ${schedule.name}, Status: ${status}`}
      accessibilityHint="Double tap to open schedule"
    >
  2. Keyboard Navigation

    • Support Tab navigation
    • Provide keyboard shortcuts
    • Handle focus management
    • Show focus indicators
  3. Visual Accessibility

    • Use high contrast colors
    • Support dynamic text sizing
    • Provide alternative text
    • Avoid color-only indicators

20. Migration Guide

Upgrading from Legacy System

  1. Data Migration

    // Transform legacy data format
    const migrateLegacySchedule = (legacy: LegacySchedule): InventorySchedule => {
      return {
        scheduleID: legacy.id,
        scheduleType: mapLegacyType(legacy.type),
        timezone: legacy.tz || 'UTC',
        // ... map other fields
      };
    };
  2. API Compatibility

    • Use version headers
    • Handle deprecated endpoints
    • Implement response transformers
    • Maintain backwards compatibility
  3. State Migration

    • Clear old cache keys
    • Transform stored data
    • Update atom structures
    • Migrate user preferences

22. Conclusion

The Inventory Module represents a sophisticated, production-ready solution for retail inventory management. Built with modern React Native technologies and following clean architecture principles, it provides a robust foundation for inventory auditing workflows.

Key Achievements

  1. User Experience

    • Intuitive guided workflow
    • Real-time validation
    • Offline capability
    • Performance optimization
  2. Technical Excellence

    • Clean architecture
    • Comprehensive testing
    • Type safety
    • Scalable design
  3. Business Value

    • Reduced audit time
    • Improved accuracy
    • Real-time reporting
    • Multi-location support

Support and Resources

  • Documentation: This comprehensive guide
  • Source Code: GitHub Repository
  • [Issue Tracking](../Issue Tracker/IssueTrackerOverview.md): GitHub [Issues](../Issue Tracker/IssueTrackerOverview.md)
  • Team Contact: Development team via Slack