# Solution Design Document

## Validation Checklist

- [x] All required sections are complete
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] All context sources are listed with relevance ratings
- [x] Project commands are discovered from actual project files
- [x] Constraints → Strategy → Design → Implementation path is logical
- [x] Architecture pattern is clearly stated with rationale
- [x] Every component in diagram has directory mapping
- [x] Every interface has specification
- [x] Error handling covers all error types
- [x] Quality requirements are specific and measurable
- [x] Every quality requirement has test coverage
- [x] **All architecture decisions confirmed by user**
- [x] Component names consistent across diagrams
- [x] A developer could implement from this design

---

## Constraints

CON-1 **Framework & Platform**: Flutter 3.38.3, Dart 3.10.1, iOS 13+, Android 8+
CON-2 **State Management**: Riverpod 3.x with `AsyncNotifier` pattern (project convention per CLAUDE.md)
CON-3 **Data Models**: Freezed 3.x for models, Equatable for domain entities
CON-4 **API Protocol**: All endpoints use POST with form-encoded data, hybrid auth (JWT or API key)
CON-5 **Existing Infrastructure**: Must integrate with existing backstock repository, providers, screens
CON-6 **Barcode Scanning**: Camera permission required; must support 1D/2D barcodes
CON-7 **Permissions**: Delete action restricted to Manager+ access level (existing permission system)
CON-8 **No Offline Support**: Network connectivity required for all CRUD operations

## Implementation Context

### Required Context Sources

```yaml
# Internal documentation
- doc: docs/backend-api-updates.md
  relevance: CRITICAL
  why: "Complete API specification for all 10 bin CRUD endpoints"

- doc: docs/specs/006-backstock-bin-crud-actions/product-requirements.md
  relevance: CRITICAL
  why: "User stories, acceptance criteria, and business rules"

- doc: CLAUDE.md
  relevance: HIGH
  why: "Project conventions, patterns, and architecture guidance"

# Existing backstock implementation
- file: lib/data/models/backstock/backstock_models.dart
  relevance: CRITICAL
  sections: [BinDetailModel, BinCategoryModel, BinLocationModel, BinActionModel]
  why: "Existing Freezed models to extend for CRUD operations"

- file: lib/data/repositories/backstock_repository_impl.dart
  relevance: CRITICAL
  why: "Repository pattern to follow; existing bin methods to extend"

- file: lib/domain/entities/backstock/backstock_entities.dart
  relevance: HIGH
  why: "Domain entity patterns with Equatable"

- file: lib/data/models/mappers/backstock_mapper.dart
  relevance: HIGH
  why: "Model-to-entity mapping patterns"

- file: lib/presentation/providers/backstock_provider.dart
  relevance: HIGH
  why: "State management patterns for backstock"

- file: lib/presentation/screens/backstock/bin_detail_screen.dart
  relevance: HIGH
  why: "Existing bin detail UI to extend with CRUD actions"

- file: lib/presentation/screens/backstock/bin_search_screen.dart
  relevance: MEDIUM
  why: "Bin search UI patterns; will add create FAB"

# Form patterns reference
- file: lib/presentation/screens/tasks/task_create_screen.dart
  relevance: MEDIUM
  why: "Form creation patterns with validation"

- file: lib/presentation/screens/backstock/event_form_screen.dart
  relevance: MEDIUM
  why: "Backstock-specific form patterns"
```

### Implementation Boundaries

- **Must Preserve**: Existing bin search, bin detail view, backstock navigation
- **Can Modify**:
  - `backstock_models.dart` - Add new request/response models
  - `backstock_repository_impl.dart` - Add new repository methods
  - `backstock_provider.dart` - Add new providers for CRUD state
  - `bin_detail_screen.dart` - Add action button and edit menu
  - `bin_search_screen.dart` - Add create FAB and barcode scanner
  - `app_router.dart` - Add new routes for create/edit screens
- **Must Not Touch**: Core network layer, auth interceptors, other modules

### External Interfaces

#### System Context Diagram

```mermaid
graph TB
    MobileApp[BuyerKiosk Mobile App]

    User[Store Associate] --> MobileApp
    Manager[Shift Manager] --> MobileApp

    MobileApp --> BackendAPI[BuyerKiosk API]
    MobileApp --> Camera[Device Camera]

    BackendAPI --> MySQL[(MySQL Database)]
```

#### Interface Specifications

```yaml
# Inbound Interfaces (user interactions)
inbound:
  - name: "Bin CRUD Forms"
    type: UI Forms
    format: Flutter widgets
    authentication: JWT/API Key (already authenticated)
    data_flow: "User inputs → validation → API calls"

  - name: "Barcode Scanner"
    type: Device Camera
    format: 1D/2D barcode
    authentication: Camera permission
    data_flow: "Scanned barcode → lookup API → bin detail or create flow"

# Outbound Interfaces (API calls)
outbound:
  - name: "Backstock CRUD API"
    type: HTTPS POST
    format: Form-encoded
    authentication: JWT Bearer token or API key
    endpoints:
      - POST /api/mobile/backstock/:typeNum/bins/create
      - POST /api/mobile/backstock/:typeNum/bins/:binId/update
      - POST /api/mobile/backstock/:typeNum/bins/:binId/delete
      - POST /api/mobile/backstock/:typeNum/bins/:binId/hide
      - POST /api/mobile/backstock/:typeNum/bins/:binId/activate
      - POST /api/mobile/backstock/:typeNum/bins/:binId/action
      - POST /api/mobile/backstock/:typeNum/bins/:binId/generate-name
      - POST /api/mobile/backstock/:typeNum/bins/lookup
      - POST /api/mobile/backstock/:typeNum/bins/hidden
      - POST /api/mobile/backstock/:typeNum/action-types
    criticality: HIGH
```

### Project Commands

```bash
# Environment Setup
Install Dependencies: flutter pub get
Generate Code: dart run build_runner build --delete-conflicting-outputs

# Development
Run App: flutter run
Run on Device: flutter run -d iphone

# Testing Commands
Unit Tests: flutter test
Test with Coverage: flutter test --coverage

# Code Quality
Analyze: flutter analyze
Format: dart format lib test

# Build
Build APK: flutter build apk --debug
Build iOS: flutter build ios --debug --no-codesign
```

## Solution Strategy

- **Architecture Pattern**: Clean Architecture with Riverpod (existing pattern)
  - Data layer: Freezed models + repository implementation
  - Domain layer: Equatable entities + repository interfaces
  - Presentation layer: AsyncNotifier providers + ConsumerWidget screens

- **Integration Approach**: Extend existing backstock infrastructure
  - Add new models to `backstock_models.dart`
  - Add new repository methods to `backstock_repository_impl.dart`
  - Add new providers to `backstock_provider.dart`
  - Add new screens to `lib/presentation/screens/backstock/`

- **Justification**: Maintains consistency with existing codebase patterns; minimizes risk by leveraging proven infrastructure

- **Key Decisions**:
  - Use `AsyncNotifier` pattern for all providers (project convention per CLAUDE.md)
  - Reuse existing category/location models for pickers
  - Implement barcode scanning with `mobile_scanner` package
  - Use bottom sheets for action picker (consistent with app UX)
  - Categories sorted by `binCount` (descending) for quick access to most-used
  - Cache invalidation after CRUD operations to ensure fresh data

## Building Block View

### Components

```mermaid
graph TB
    subgraph Presentation["Presentation Layer"]
        BinCreateScreen[BinCreateScreen]
        BinEditScreen[BinEditScreen]
        BinActionSheet[BinActionSheet]
        BarcodeScanner[BarcodeScannerSheet]
        HiddenBinsSheet[HiddenBinsSheet]
        CategoryPicker[CategoryPickerSheet]
        LocationPicker[LocationPickerSheet]
    end

    subgraph Providers["State Management"]
        BinCrudProvider[BinCrudNotifier]
        BinActionProvider[BinActionNotifier]
        HiddenBinsProvider[HiddenBinsNotifier]
        ActionTypesProvider[ActionTypesProvider]
    end

    subgraph Domain["Domain Layer"]
        BackstockRepo[BackstockRepository]
        BinDetail[BinDetail Entity]
        BinAction[BinAction Entity]
    end

    subgraph Data["Data Layer"]
        BackstockRepoImpl[BackstockRepositoryImpl]
        BinCreateModel[BinCreateRequestModel]
        BinUpdateModel[BinUpdateRequestModel]
        BinActionModel[BinActionRequestModel]
        LookupResultModel[BinLookupResultModel]
    end

    BinCreateScreen --> BinCrudProvider
    BinEditScreen --> BinCrudProvider
    BinActionSheet --> BinActionProvider
    BarcodeScanner --> BackstockRepo
    HiddenBinsSheet --> HiddenBinsProvider

    BinCrudProvider --> BackstockRepo
    BinActionProvider --> BackstockRepo
    HiddenBinsProvider --> BackstockRepo

    BackstockRepo --> BackstockRepoImpl
    BackstockRepoImpl --> BinCreateModel
    BackstockRepoImpl --> BinUpdateModel
    BackstockRepoImpl --> BinActionModel
```

### Generate Name Feature Flow

The "Generate Name" feature creates a descriptive name based on the bin's category (e.g., "Summer Boots #1500").

**UI Flow:**
1. User taps "Generate Name" button on create/edit screen
2. Button only enabled when main category is selected
3. System calls generate-name endpoint
4. Generated name populates the name field
5. User can edit the generated name before saving

**Provider Method:**
```dart
Future<String?> generateBinName({
  required String typeNum,
  required int binId,  // For existing bins during edit
}) async {
  state = state.copyWith(isGenerating: true);
  try {
    final name = await ref.read(backstockRepositoryProvider)
        .generateBinName(typeNum: typeNum, binId: binId);
    state = state.copyWith(isGenerating: false);
    return name;
  } catch (e) {
    state = state.copyWith(isGenerating: false, error: e.toString());
    return null;
  }
}
```

**Create Screen Integration:**
- "Generate Name" button appears next to name input field
- Button disabled until main category is selected
- For new bins (no binId), use a temporary create-then-generate or pre-generate flow
- Generated name is a suggestion; user can modify before saving

### Hidden Bins Feature Flow

**Toggle in Search Screen:**
- Filter chip or toggle switch labeled "Show Hidden" in search filters
- When enabled, search results include hidden bins
- Hidden bins display with:
  - Muted/greyed text color
  - "Hidden" badge/chip on the card
  - Opacity reduced to 0.6

**Provider State:**
```dart
class BinSearchState extends Equatable {
  final List<BinSummary> bins;
  final bool showHidden;  // Toggle state
  final bool isLoading;
  // ...
}
```

**Reactivation Flow:**
1. User opens hidden bin detail screen
2. "Reactivate" button visible (replaces "Hide" option)
3. User taps "Reactivate"
4. System calls activate endpoint
5. On success: bin.isHidden becomes false
6. Search results update to show bin as active
7. If "Show Hidden" was off, bin appears in normal results

**Hidden Bins List (Alternative View):**
- Accessible via menu option "View Hidden Bins"
- Calls `/bins/hidden` endpoint
- Displays list of all hidden bins for the store
- Each item has "Reactivate" quick action

### Directory Map

```
lib/
├── core/
│   └── constants/
│       └── api_constants.dart           # MODIFY: Add new endpoint constants
├── data/
│   ├── models/
│   │   └── backstock/
│   │       └── backstock_models.dart    # MODIFY: Add request/response models
│   ├── mappers/
│   │   └── backstock_mapper.dart        # MODIFY: Add new entity mappers
│   └── repositories/
│       └── backstock_repository_impl.dart # MODIFY: Add CRUD methods
├── domain/
│   ├── entities/
│   │   └── backstock/
│   │       └── backstock_entities.dart  # MODIFY: Add any new entities
│   └── repositories/
│       └── backstock_repository.dart    # MODIFY: Add repository interface methods
├── presentation/
│   ├── providers/
│   │   └── backstock_provider.dart      # MODIFY: Add CRUD state providers
│   ├── screens/
│   │   └── backstock/
│   │       ├── bin_create_screen.dart   # NEW: Bin creation form
│   │       ├── bin_edit_screen.dart     # NEW: Bin editing form
│   │       ├── bin_detail_screen.dart   # MODIFY: Add actions menu
│   │       └── bin_search_screen.dart   # MODIFY: Add FAB and scanner
│   └── widgets/
│       └── backstock/
│           ├── bin_action_sheet.dart    # NEW: Action picker sheet
│           ├── category_picker_sheet.dart # NEW: Category selection
│           ├── location_picker_sheet.dart # NEW: Location selection
│           ├── hidden_bins_sheet.dart   # NEW: Hidden bins list
│           └── barcode_scanner_sheet.dart # NEW: Barcode scanner
└── router/
    └── app_router.dart                  # MODIFY: Add new routes
```

### Interface Specifications

#### New API Endpoint Constants

```dart
// lib/core/constants/api_constants.dart - additions

static String backstockBinCreateEndpoint(String typeNum) =>
    'mobile/backstock/$typeNum/bins/create';

static String backstockBinUpdateEndpoint(String typeNum, int binId) =>
    'mobile/backstock/$typeNum/bins/$binId/update';

static String backstockBinDeleteEndpoint(String typeNum, int binId) =>
    'mobile/backstock/$typeNum/bins/$binId/delete';

static String backstockBinHideEndpoint(String typeNum, int binId) =>
    'mobile/backstock/$typeNum/bins/$binId/hide';

static String backstockBinActivateEndpoint(String typeNum, int binId) =>
    'mobile/backstock/$typeNum/bins/$binId/activate';

static String backstockBinActionEndpoint(String typeNum, int binId) =>
    'mobile/backstock/$typeNum/bins/$binId/action';

static String backstockBinLookupEndpoint(String typeNum) =>
    'mobile/backstock/$typeNum/bins/lookup';

static String backstockHiddenBinsEndpoint(String typeNum) =>
    'mobile/backstock/$typeNum/bins/hidden';

static String backstockActionTypesEndpoint(String typeNum) =>
    'mobile/backstock/$typeNum/action-types';

static String backstockGenerateNameEndpoint(String typeNum, int binId) =>
    'mobile/backstock/$typeNum/bins/$binId/generate-name';
```

#### Canonical Action Types Reference

**CRITICAL**: The action picker must use this canonical mapping. Action type 4 ("Created Bin") is auto-recorded on bin creation and must NOT appear in the user-selectable action list.

| ID | Name | Description | Requires | UI Behavior |
|----|------|-------------|----------|-------------|
| 0 | Removed Everything | All items removed from bin | - | Clears all categories; show confirmation |
| 1 | Removed Some Items | Some items removed | `categoryId` | Show category picker before confirm |
| 2 | Restock from Floor | Items returned from sales floor | - | Simple confirm |
| 3 | Moved Bin | Bin moved to different location | `toLocationId` | Show location picker before confirm |
| 4 | Created Bin | New bin created | - | **Auto-recorded only - DO NOT show in picker** |
| 5 | Pulled for Replenishment | Bin pulled for floor replenishment | - | Simple confirm |

**User-selectable actions**: IDs 0, 1, 2, 3, 5 (filter out ID 4 in action picker)

#### Endpoint Payload Contracts

**Create Bin** - `POST /bins/create`
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | ✓ | Bin name (alphanumeric, 1-50 chars) |
| `mainCategory` | string | ✓ | Category ID from categories endpoint |
| `location` | int | ✓ | Location ID from locations endpoint |
| `dateCreated` | string | - | YYYY-MM-DD format, defaults to today |
| `subCategories` | string[] | - | Up to 3 sub-category IDs |

**Update Bin** - `POST /bins/:binId/update`
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | - | New bin name |
| `mainCategory` | string | - | New main category ID |
| `location` | int | - | New location ID |
| `dateCreated` | string | - | New date (for age reset) |
| `subCategories` | string[] | - | New sub-categories (replaces existing) |
| `notes` | string | - | Bin notes |
| `resetAgeDate` | bool | - | Reset age to today if true |

**Perform Action** - `POST /bins/:binId/action`
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `actionId` | int | ✓ | Action type ID (0-5, excluding 4) |
| `categoryId` | string | For ID=1 | Category affected by removal |
| `toLocationId` | int | For ID=3 | Destination location for move |
| `resetAgeDate` | bool | - | Reset age to today if true |

**Lookup Bin** - `POST /bins/lookup`
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | ✓ | Exact bin name to find |

**Error Responses**
| Code | Meaning | Handling |
|------|---------|----------|
| 400 | Validation error (duplicate name, missing field) | Show inline form error |
| 403 | Permission denied | Show access denied message |
| 404 | Bin not found | Show "Bin not found" error |
| 500 | Server error | Show retry dialog |

#### New Data Models

```dart
// lib/data/models/backstock/backstock_models.dart - additions

/// Request model for creating a bin
@freezed
abstract class BinCreateRequestModel with _$BinCreateRequestModel {
  const factory BinCreateRequestModel({
    required String name,
    required String mainCategory,  // Category ID
    required int location,         // Location ID
    String? dateCreated,           // YYYY-MM-DD format
    List<String>? subCategories,   // Sub-category IDs
  }) = _BinCreateRequestModel;

  factory BinCreateRequestModel.fromJson(Map<String, dynamic> json) =>
      _$BinCreateRequestModelFromJson(json);
}

/// Request model for updating a bin
@freezed
abstract class BinUpdateRequestModel with _$BinUpdateRequestModel {
  const factory BinUpdateRequestModel({
    String? name,
    String? mainCategory,
    int? location,
    String? dateCreated,
    List<String>? subCategories,
    String? notes,
    int? itemCount,
    double? estimatedValue,
    bool? resetAgeDate,
  }) = _BinUpdateRequestModel;

  factory BinUpdateRequestModel.fromJson(Map<String, dynamic> json) =>
      _$BinUpdateRequestModelFromJson(json);
}

/// Request model for performing an action on a bin
@freezed
abstract class BinActionRequestModel with _$BinActionRequestModel {
  const factory BinActionRequestModel({
    required int actionId,
    String? categoryId,     // Required for actionId=1 (Removed Some Items)
    int? toLocationId,      // Required for actionId=3 (Move)
    bool? resetAgeDate,
  }) = _BinActionRequestModel;

  factory BinActionRequestModel.fromJson(Map<String, dynamic> json) =>
      _$BinActionRequestModelFromJson(json);
}

/// Response model for bin lookup
@freezed
abstract class BinLookupResultModel with _$BinLookupResultModel {
  const factory BinLookupResultModel({
    required bool found,
    int? id,
    String? uuid,
    String? name,
    String? mainCategory,
    int? location,
    int? age,
    String? ageDate,
    bool? isHidden,
    bool? hasBeenUsed,
  }) = _BinLookupResultModel;

  factory BinLookupResultModel.fromJson(Map<String, dynamic> json) =>
      _$BinLookupResultModelFromJson(json);
}

/// Action type reference model
@freezed
abstract class ActionTypeModel with _$ActionTypeModel {
  const factory ActionTypeModel({
    required int id,
    required String name,
    required String description,
  }) = _ActionTypeModel;

  factory ActionTypeModel.fromJson(Map<String, dynamic> json) =>
      _$ActionTypeModelFromJson(json);
}
```

#### Repository Interface Additions

```dart
// lib/domain/repositories/backstock_repository.dart - additions

/// Create a new bin
Future<BinDetail> createBin({
  required String typeNum,
  required String name,
  required String mainCategory,
  required int location,
  String? dateCreated,
  List<String>? subCategories,
});

/// Update bin (comprehensive save)
Future<BinDetail> updateBin({
  required String typeNum,
  required int binId,
  String? name,
  String? mainCategory,
  int? location,
  String? dateCreated,
  List<String>? subCategories,
  String? notes,
  int? itemCount,
  double? estimatedValue,
  bool? resetAgeDate,
});

/// Delete bin (soft delete)
Future<void> deleteBin({
  required String typeNum,
  required int binId,
});

/// Hide bin (make inactive)
Future<void> hideBin({
  required String typeNum,
  required int binId,
});

/// Activate bin (unhide)
Future<BinDetail> activateBin({
  required String typeNum,
  required int binId,
});

/// Perform action on bin
Future<BinAction> performBinAction({
  required String typeNum,
  required int binId,
  required int actionId,
  String? categoryId,
  int? toLocationId,
  bool? resetAgeDate,
});

/// Lookup bin by name (barcode scanning)
Future<BinLookupResult> lookupBin({
  required String typeNum,
  required String name,
});

/// Get hidden bins
Future<HiddenBinsResult> getHiddenBins({
  required String typeNum,
});

/// Get action types reference
Future<List<ActionType>> getActionTypes({
  required String typeNum,
});
```

### Integration Points

```yaml
# Internal integration
- from: BinDetailScreen
  to: BinActionSheet
    - protocol: Widget callback
    - data_flow: "User taps action button → shows action picker → performs action"

- from: BinSearchScreen
  to: BarcodeScannerSheet
    - protocol: Widget callback
    - data_flow: "User taps scan → camera opens → barcode decoded → lookup API"

- from: BinSearchScreen
  to: BinCreateScreen
    - protocol: GoRouter navigation
    - data_flow: "User taps FAB → navigates to create form"

# External integration
- from: BackstockRepositoryImpl
  to: BuyerKiosk API
    - protocol: HTTPS POST (form-encoded)
    - endpoints: 10 new bin CRUD endpoints
    - data_flow: "CRUD operations with form data"
```

## Runtime View

### Primary Flow: Create New Bin

1. User taps FAB on bin search screen
2. System navigates to BinCreateScreen
3. User enters bin name, selects category and location
4. User optionally selects sub-categories
5. User taps "Create" button
6. System validates input and calls create API
7. On success, system navigates to new bin detail
8. Bin search list is refreshed

```mermaid
sequenceDiagram
    actor User
    participant SearchScreen as BinSearchScreen
    participant CreateScreen as BinCreateScreen
    participant Provider as BinCrudNotifier
    participant Repo as BackstockRepository
    participant API as Backend API

    User->>SearchScreen: Tap FAB
    SearchScreen->>CreateScreen: Navigate
    User->>CreateScreen: Fill form & tap Create
    CreateScreen->>Provider: createBin(data)
    Provider->>Repo: createBin(typeNum, ...)
    Repo->>API: POST /bins/create
    API-->>Repo: { success, bin }
    Repo-->>Provider: BinDetail
    Provider-->>CreateScreen: Success state
    CreateScreen->>SearchScreen: Navigate back
    CreateScreen->>SearchScreen: Refresh list
```

### Primary Flow: Perform Bin Action

1. User opens bin detail screen
2. User taps "Actions" button
3. System shows BinActionSheet with action types
4. User selects action (e.g., "Pulled for Replenishment")
5. If action requires input (category/location), secondary picker shown
6. User confirms action
7. System calls action API
8. Action appears in history timeline
9. Bin state updated if applicable (e.g., location changed for Move)

```mermaid
sequenceDiagram
    actor User
    participant DetailScreen as BinDetailScreen
    participant ActionSheet as BinActionSheet
    participant Provider as BinActionNotifier
    participant Repo as BackstockRepository
    participant API as Backend API

    User->>DetailScreen: Tap Actions
    DetailScreen->>ActionSheet: Show
    User->>ActionSheet: Select action type
    alt Action requires input
        ActionSheet->>User: Show secondary picker
        User->>ActionSheet: Select category/location
    end
    User->>ActionSheet: Confirm
    ActionSheet->>Provider: performAction(binId, actionId, ...)
    Provider->>Repo: performBinAction(...)
    Repo->>API: POST /bins/:id/action
    API-->>Repo: { success, action }
    Repo-->>Provider: BinAction
    Provider-->>ActionSheet: Success
    ActionSheet->>DetailScreen: Close & refresh
    DetailScreen->>DetailScreen: Update action history
```

### Secondary Flow: Barcode Scanning

1. User taps scan icon on bin search screen
2. System checks camera permission
   - If not granted → show permission request dialog
   - If permanently denied → show "Enable in Settings" prompt with button to open app settings
3. Camera opens with barcode scanner overlay
   - **Flash toggle button** in top-right corner (torch icon)
   - **Close button** in top-left corner
   - Scan zone indicator in center
   - "Point camera at bin barcode" helper text
4. User scans barcode (supports 1D and 2D formats)
5. On successful decode, scanner vibrates briefly and pauses
6. System calls lookup API with scanned name
7. If bin found → navigate to bin detail
8. If bin not found → show prompt with options:
   - "Create Bin '{scannedName}'" button → navigate to create screen with name pre-filled
   - "Scan Again" button → resume scanner
   - "Cancel" button → close scanner

**Scanner UI Controls:**
- Flash toggle: `Icons.flash_on` / `Icons.flash_off` with state management
- Close: `Icons.close` to dismiss sheet
- Fallback: "Enter name manually" link below scan zone for poor lighting conditions

```mermaid
sequenceDiagram
    actor User
    participant SearchScreen as BinSearchScreen
    participant Scanner as BarcodeScannerSheet
    participant Repo as BackstockRepository
    participant API as Backend API
    participant CreateScreen as BinCreateScreen

    User->>SearchScreen: Tap scan icon
    SearchScreen->>Scanner: Show camera
    User->>Scanner: Scan barcode
    Scanner->>Repo: lookupBin(name)
    Repo->>API: POST /bins/lookup
    API-->>Repo: { found, bin? }
    alt Bin found
        Repo-->>Scanner: BinLookupResult(found=true)
        Scanner->>SearchScreen: Navigate to bin detail
    else Bin not found
        Repo-->>Scanner: BinLookupResult(found=false)
        Scanner->>User: Show "Create new bin?" prompt
        User->>Scanner: Confirm create
        Scanner->>CreateScreen: Navigate with pre-filled name
    end
```

### Error Handling

- **Validation errors**: Show inline error messages on form fields
- **Duplicate bin name**: Show "Bin name already exists" inline error
- **Network errors**: Show retry dialog; do not lose entered form data
- **Permission denied**: Show access denied error (e.g., delete requires Manager+)
- **Bin not found**: Show "Bin not found" error (edge case during action)
- **Camera permission denied**: Show settings prompt to enable camera

## Deployment View

No change to existing deployment - this is a Flutter app feature addition.

- **Environment**: iOS and Android mobile devices
- **Configuration**: No new environment variables required
- **Dependencies**:
  - Add `mobile_scanner: ^5.1.1` for barcode scanning
- **Performance**:
  - Categories cached per session
  - Locations cached per session
  - Action types fetched once per session

## Cross-Cutting Concepts

### Pattern Documentation

```yaml
# Existing patterns used
- pattern: AsyncNotifier state management pattern
  relevance: CRITICAL
  why: "All providers follow AsyncNotifier pattern for consistency (per CLAUDE.md)"

- pattern: Repository pattern with Dio
  relevance: CRITICAL
  why: "All API calls go through repository implementation"

- pattern: Freezed models with JSON serialization
  relevance: CRITICAL
  why: "All data models use Freezed for immutability"

- pattern: Form validation with GlobalKey<FormState>
  relevance: HIGH
  why: "All forms use standard Flutter form validation"

- pattern: Bottom sheet pickers
  relevance: HIGH
  why: "Consistent UX for selection dialogs"
```

### System-Wide Patterns

- **Security**: Uses existing JWT/API key authentication via interceptor
- **Error Handling**: Consistent use of `ErrorDisplay.fromError()` widget
- **Performance**: Categories and locations cached after first fetch
- **Logging**: Analytics events for CRUD operations per PRD requirements

### Analytics Tracking Implementation

Per PRD requirements, track these events using the existing analytics provider:

```dart
// Event: bin_created
ref.read(analyticsProvider).track('bin_created', {
  'binId': int,
  'categoryId': String,
  'locationId': int,
  'hasSubCategories': bool,
});

// Event: bin_updated
ref.read(analyticsProvider).track('bin_updated', {
  'binId': int,
  'fieldsChanged': List<String>,  // ['name', 'category', 'notes', etc.]
});

// Event: bin_action_performed
ref.read(analyticsProvider).track('bin_action_performed', {
  'binId': int,
  'actionId': int,
  'actionName': String,
  'categoryId': String?,  // For Removed Some Items
  'locationId': int?,     // For Moved Bin
});

// Event: bin_hidden
ref.read(analyticsProvider).track('bin_hidden', {
  'binId': int,
  'binAge': int,
});

// Event: bin_activated
ref.read(analyticsProvider).track('bin_activated', {
  'binId': int,
  'daysHidden': int,
});

// Event: bin_barcode_scanned
ref.read(analyticsProvider).track('bin_barcode_scanned', {
  'binName': String,
  'found': bool,
  'createFlow': bool,  // true if user proceeded to create
});

// Event: bin_name_generated
ref.read(analyticsProvider).track('bin_name_generated', {
  'binId': int?,  // null for new bins
  'categoryId': String,
});

// Event: bin_create_abandoned
ref.read(analyticsProvider).track('bin_create_abandoned', {
  'step': String,  // 'name', 'category', 'location', 'submit'
  'fieldsEntered': List<String>,
});
```

**Implementation Notes:**
- Track abandonment in `dispose()` if form not submitted
- Use existing analytics service/provider pattern from the codebase
- Sensitive data (bin names may contain product info) - confirm with privacy policy

### Implementation Patterns

#### State Management Pattern (AsyncNotifier)

```dart
// Bin CRUD operations using AsyncNotifier pattern (project convention)
class BinCrudNotifier extends AsyncNotifier<BinDetail?> {
  @override
  Future<BinDetail?> build() async => null;  // No initial data

  Future<BinDetail?> createBin({
    required String typeNum,
    required String name,
    required String mainCategory,
    required int location,
    String? dateCreated,
    List<String>? subCategories,
  }) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      final bin = await ref.read(backstockRepositoryProvider).createBin(
        typeNum: typeNum,
        name: name,
        mainCategory: mainCategory,
        location: location,
        dateCreated: dateCreated,
        subCategories: subCategories,
      );
      // Invalidate cached bin search to show new bin
      ref.invalidate(binSearchProvider(typeNum));
      return bin;
    });
    return state.valueOrNull;
  }

  Future<BinDetail?> updateBin({
    required String typeNum,
    required int binId,
    String? name,
    String? mainCategory,
    int? location,
    List<String>? subCategories,
    String? notes,
    bool? resetAgeDate,
  }) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      final bin = await ref.read(backstockRepositoryProvider).updateBin(
        typeNum: typeNum,
        binId: binId,
        name: name,
        mainCategory: mainCategory,
        location: location,
        subCategories: subCategories,
        notes: notes,
        resetAgeDate: resetAgeDate,
      );
      // Invalidate caches
      ref.invalidate(binSearchProvider(typeNum));
      ref.invalidate(binDetailProvider((typeNum: typeNum, binId: binId)));
      return bin;
    });
    return state.valueOrNull;
  }
}

final binCrudProvider = AsyncNotifierProvider<BinCrudNotifier, BinDetail?>(
  BinCrudNotifier.new,
);
```

#### Cache Invalidation Rules

After CRUD operations, invalidate related providers to ensure fresh data:

| Operation | Invalidate |
|-----------|------------|
| Create bin | `binSearchProvider(typeNum)` |
| Update bin | `binSearchProvider(typeNum)`, `binDetailProvider(binId)` |
| Delete bin | `binSearchProvider(typeNum)` |
| Hide bin | `binSearchProvider(typeNum)`, `binDetailProvider(binId)` |
| Activate bin | `binSearchProvider(typeNum)`, `hiddenBinsProvider(typeNum)`, `binDetailProvider(binId)` |
| Perform action | `binDetailProvider(binId)` (refreshes action history) |

#### Form Screen Pattern

```dart
class BinCreateScreen extends ConsumerStatefulWidget {
  final String typeNum;
  final String? prefilledName;  // From barcode scan

  const BinCreateScreen({
    super.key,
    required this.typeNum,
    this.prefilledName,
  });

  @override
  ConsumerState<BinCreateScreen> createState() => _BinCreateScreenState();
}

class _BinCreateScreenState extends ConsumerState<BinCreateScreen> {
  final _formKey = GlobalKey<FormState>();
  late TextEditingController _nameController;
  BackstockCategory? _selectedCategory;
  BackstockLocation? _selectedLocation;
  List<BackstockCategory> _selectedSubCategories = [];

  @override
  void initState() {
    super.initState();
    _nameController = TextEditingController(text: widget.prefilledName);
  }

  @override
  void dispose() {
    _nameController.dispose();
    super.dispose();
  }

  Future<void> _submit() async {
    if (!_formKey.currentState!.validate()) return;
    if (_selectedCategory == null || _selectedLocation == null) {
      // Show validation error
      return;
    }

    final bin = await ref.read(binCrudProvider.notifier).createBin(
      typeNum: widget.typeNum,
      name: _nameController.text.trim(),
      mainCategory: _selectedCategory!.id.toString(),
      location: _selectedLocation!.id,
      subCategories: _selectedSubCategories.map((c) => c.id.toString()).toList(),
    );

    if (bin != null && mounted) {
      // Track analytics event
      ref.read(analyticsProvider).track('bin_created', {
        'binId': bin.id,
        'categoryId': _selectedCategory!.id,
        'locationId': _selectedLocation!.id,
        'hasSubCategories': _selectedSubCategories.isNotEmpty,
      });

      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Bin created successfully')),
      );
      // Navigate to new bin detail
      context.go('/store/${widget.typeNum}/backstock/bins/${bin.id}');
    }
  }
}
```

#### Edit Form - Save Button Logic

The save button on the edit form must be disabled until changes are made:

```dart
class _BinEditScreenState extends ConsumerState<BinEditScreen> {
  // Original values for dirty checking
  late String _originalName;
  late String _originalCategoryId;
  late int _originalLocationId;
  late List<String> _originalSubCategoryIds;
  late String? _originalNotes;

  // Current values
  late TextEditingController _nameController;
  BackstockCategory? _selectedCategory;
  BackstockLocation? _selectedLocation;
  List<BackstockCategory> _selectedSubCategories = [];
  late TextEditingController _notesController;

  @override
  void initState() {
    super.initState();
    // Store original values from bin
    _originalName = widget.bin.name;
    _originalCategoryId = widget.bin.mainCategory?.id ?? '';
    _originalLocationId = widget.bin.location?.id ?? 0;
    _originalSubCategoryIds = widget.bin.subCategories.map((c) => c.id).toList();
    _originalNotes = widget.bin.notes;

    // Initialize controllers
    _nameController = TextEditingController(text: _originalName);
    _notesController = TextEditingController(text: _originalNotes ?? '');
    _nameController.addListener(_onFormChanged);
    _notesController.addListener(_onFormChanged);
  }

  bool get _hasChanges {
    if (_nameController.text.trim() != _originalName) return true;
    if (_selectedCategory?.id != _originalCategoryId) return true;
    if (_selectedLocation?.id != _originalLocationId) return true;
    if (_notesController.text.trim() != (_originalNotes ?? '')) return true;
    final currentSubIds = _selectedSubCategories.map((c) => c.id).toList();
    if (!listEquals(currentSubIds, _originalSubCategoryIds)) return true;
    return false;
  }

  void _onFormChanged() => setState(() {});  // Rebuild to update save button state

  // In build():
  ElevatedButton(
    onPressed: _hasChanges ? _submit : null,  // Disabled when no changes
    child: Text('Save'),
  )
}
```

#### Action Sheet Pattern

```dart
class BinActionSheet extends ConsumerStatefulWidget {
  final String typeNum;
  final int binId;
  final List<ActionType> actionTypes;
  final List<BackstockCategory> categories;
  final List<BackstockLocation> locations;

  const BinActionSheet({...});

  static Future<BinAction?> show(BuildContext context, {...}) async {
    return showModalBottomSheet<BinAction>(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (context) => BinActionSheet(...),
    );
  }
}

class _BinActionSheetState extends ConsumerState<BinActionSheet> {
  ActionType? _selectedAction;
  BackstockCategory? _selectedCategory;
  BackstockLocation? _selectedLocation;

  bool get _canSubmit {
    if (_selectedAction == null) return false;
    // Validate required fields per action type
    if (_selectedAction!.id == 1 && _selectedCategory == null) return false;
    if (_selectedAction!.id == 3 && _selectedLocation == null) return false;
    return true;
  }

  Future<void> _performAction() async {
    final action = await ref.read(binActionProvider.notifier).performAction(
      typeNum: widget.typeNum,
      binId: widget.binId,
      actionId: _selectedAction!.id,
      categoryId: _selectedCategory?.id.toString(),
      toLocationId: _selectedLocation?.id,
    );

    if (action != null && mounted) {
      Navigator.pop(context, action);
    }
  }
}
```

## Architecture Decisions

- [x] ADR-1 **Use mobile_scanner package for barcode scanning**
  - Rationale: Well-maintained, supports both iOS and Android, handles 1D and 2D barcodes
  - Trade-offs: Adds ~2MB to app size
  - User confirmed: ✅ 2026-02-07

- [x] ADR-2 **Extend existing backstock models rather than creating new module**
  - Rationale: Maintains consistency, reduces code duplication, leverages existing mappers
  - Trade-offs: Models file grows larger
  - User confirmed: ✅ 2026-02-07

- [x] ADR-3 **Use bottom sheets for pickers (category, location, action)**
  - Rationale: Consistent with app UX patterns (workbook notes, task management)
  - Trade-offs: More code than simple dropdowns, but better mobile UX
  - User confirmed: ✅ 2026-02-07

- [x] ADR-4 **Cache categories, locations, and action types per session**
  - Rationale: These rarely change; reduces API calls and improves UX
  - Trade-offs: Stale data if backend changes during session
  - User confirmed: ✅ 2026-02-07

- [x] ADR-5 **Navigate to bin detail after successful create**
  - Rationale: User likely wants to verify/act on newly created bin
  - Trade-offs: Requires refresh of search list on return
  - User confirmed: ✅ 2026-02-07

## Quality Requirements

- **Performance**:
  - Form submission completes within 2 seconds
  - Barcode scan-to-result within 500ms
  - Category picker loads within 1 second

- **Usability**:
  - Forms preserve data on validation failure
  - Clear error messages for all failure cases
  - Confirmation dialogs for destructive actions (hide, delete)

- **Reliability**:
  - Network errors show retry option
  - Form data preserved on navigation back
  - Duplicate bin name detected before submission

## Risks and Technical Debt

### Known Technical Issues

- Existing bin detail screen uses `dynamic` type for bin parameter
- No input validation for bin name format (API allows any string)

### Technical Debt

- Consider extracting bottom sheet picker pattern to shared component
- Consider adding form state persistence for crash recovery

### Implementation Gotchas

- Camera permission on iOS requires `NSCameraUsageDescription` in Info.plist (already present for other features)
- Category ID is string in API but int in some models - handle both
- Action type 4 ("Created Bin") is auto-recorded - do not show in action picker
- Form-encoded POST requires `Options(contentType: Headers.formUrlEncodedContentType)`

## Test Specifications

### Critical Test Scenarios

**Scenario 1: Create Bin Happy Path**
```gherkin
Given: User is on bin search screen
And: Categories and locations are loaded
When: User taps FAB, fills form with valid data, taps Create
Then: Bin is created via API
And: User is navigated to new bin detail screen
And: Search list is refreshed
```

**Scenario 2: Duplicate Bin Name**
```gherkin
Given: User is on bin create screen
When: User enters a name that already exists
And: User taps Create
Then: Inline error "Bin name already exists" is shown
And: Form remains open with data preserved
```

**Scenario 3: Perform Bin Action**
```gherkin
Given: User is on bin detail screen
When: User taps Actions button
And: User selects "Pulled for Replenishment"
And: User confirms action
Then: Action is recorded via API
And: Action appears in history timeline
```

**Scenario 4: Barcode Scan - Bin Found**
```gherkin
Given: User is on bin search screen
When: User taps scan icon
And: User scans barcode for existing bin "1500"
Then: API lookup returns found=true
And: User is navigated to bin detail for bin "1500"
```

**Scenario 5: Barcode Scan - Bin Not Found**
```gherkin
Given: User is on bin search screen
When: User taps scan icon
And: User scans barcode for non-existent bin "9999"
Then: API lookup returns found=false
And: User sees "Create bin 9999?" prompt
And: User taps Create
And: User is navigated to create screen with name pre-filled
```

**Scenario 6: Delete Requires Manager Permission**
```gherkin
Given: User is logged in with Employee access level
When: User views bin detail
Then: Delete option is not visible in menu
---
Given: User is logged in with Manager access level
When: User views bin detail
Then: Delete option is visible in menu
```

**Scenario 7: Hidden Bins Toggle and Reactivation**
```gherkin
Given: User is on bin search screen
And: There are hidden bins in the store
When: User enables "Show Hidden" toggle
Then: Hidden bins appear in search results
And: Hidden bins display with muted styling and "Hidden" badge
---
Given: User is viewing a hidden bin's detail screen
When: User taps "Reactivate" button
Then: Bin is activated via API
And: Bin appears in normal search results
And: "Hidden" badge is removed
```

**Scenario 8: Generate Descriptive Name**
```gherkin
Given: User is on bin create screen
When: User selects main category "Toddler / Boots"
And: User taps "Generate Name" button
Then: API returns generated name (e.g., "Toddler Boots #1500")
And: Name field is populated with generated name
And: User can edit the generated name
---
Given: User is on bin create screen
And: No main category is selected
Then: "Generate Name" button is disabled
```

**Scenario 9: Edit Form Save Button State**
```gherkin
Given: User is on bin edit screen
And: No changes have been made
Then: Save button is disabled
---
Given: User is on bin edit screen
When: User changes the bin name
Then: Save button becomes enabled
---
Given: User is on bin edit screen
When: User changes a field then reverts to original value
Then: Save button becomes disabled again
```

**Scenario 10: Barcode Scanner Flash Toggle**
```gherkin
Given: User has opened barcode scanner
And: Device supports flashlight
When: User taps flash toggle button
Then: Flashlight turns on
And: Flash icon changes to "flash_on" state
---
When: User taps flash toggle again
Then: Flashlight turns off
And: Flash icon changes to "flash_off" state
```

**Scenario 11: Bin State Changed During Action**
```gherkin
Given: User has opened action sheet for bin 1500
And: Another user hides bin 1500 from another device
When: User submits an action on bin 1500
Then: API returns 404 "Bin not found"
And: User sees error message "This bin is no longer available"
And: Action sheet closes
And: Bin detail screen refreshes
```

### Test Coverage Requirements

- **Business Logic**: All CRUD operations, action type validation, permission checks
- **User Interface**: Form validation, picker interactions, error states
- **Integration Points**: API request/response handling, navigation flows
- **Edge Cases**: Network errors, duplicate names, concurrent updates

---

## Glossary

### Domain Terms

| Term | Definition | Context |
|------|------------|---------|
| Bin | A container for storing backstock items with category and location | Core entity for CRUD operations |
| Action | A recorded activity on a bin (pull, store, move, etc.) | Recorded in action history |
| Category | Product classification from store's POS system | Used for bin organization |
| Location | Physical storage location (on-site or off-site) | Where bin is stored |
| Hidden Bin | A bin marked as inactive but not deleted | Can be reactivated |

### Technical Terms

| Term | Definition | Context |
|------|------------|---------|
| typeNum | Store identifier (e.g., "ou00", "pc01") | Required for all API calls |
| Freezed | Dart code generation for immutable classes | Used for data models |
| Equatable | Dart package for value equality | Used for domain entities |
| AsyncNotifier | Riverpod 3.x pattern for async state management | Used for all providers |

### API Terms

| Term | Definition | Context |
|------|------------|---------|
| mainCategory | Primary category ID for bin | String in API, parsed to int |
| subCategories | Additional category IDs (max 3) | Array of strings |
| actionId | Numeric action type (0-5) | See action types reference |
| resetAgeDate | Flag to reset bin age to today | Boolean in action request |
