# @isa/oms/feature/return-process

A comprehensive Angular feature library for managing product returns with dynamic question flows, validation, and backend integration. Part of the Order Management System (OMS) domain.

## Overview

The Return Process feature library provides a complete workflow for processing product returns in a retail environment. It handles complex question flows based on product categories (books, electronics, Tolino devices, etc.), validates user input in real-time, checks return eligibility through both frontend and backend validation, and guides users through the entire return journey from initial selection to final confirmation.

The library integrates with the OMS data-access layer for state management and business logic, leverages NgRx Signals for reactive state updates, and provides a type-safe, schema-validated approach to collecting return information.

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Core Components](#core-components)
- [Question System Architecture](#question-system-architecture)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [Routing and Navigation](#routing-and-navigation)
- [State Management](#state-management)
- [Validation and Eligibility](#validation-and-eligibility)
- [Testing](#testing)
- [Architecture Notes](#architecture-notes)
- [Dependencies](#dependencies)

## Features

- **Dynamic Question Flows** - Category-specific questions for books, electronics, Tolino devices, and more
- **Five Question Types** - Select (chips), Product (EAN search), Checklist (multi-select), Info (display), and Group (nested questions)
- **Real-time Validation** - Frontend and backend validation with immediate feedback
- **Progress Tracking** - Visual progress bar showing question completion status
- **Eligibility Checking** - Automatic determination of return eligibility based on answers
- **Barcode Scanning** - Integrated scanner support for product identification
- **Type-safe State** - Zod schema validation for all user inputs
- **Automatic Navigation** - Conditional routing based on process completion
- **Session Persistence** - Answers preserved across browser sessions via IndexedDB
- **Responsive Design** - Tailwind CSS with ISA design system integration

## Quick Start

### 1. Add Routes to Your Application

```typescript
import { Route } from '@angular/router';

export const routes: Route[] = [
  {
    path: 'return-process/:id',
    loadChildren: () =>
      import('@isa/oms/feature/return-process').then((m) => m.routes),
  },
];
```

### 2. Initialize a Return Process

```typescript
import { Component, inject } from '@angular/core';
import { ReturnProcessStore } from '@isa/oms/data-access';
import { Router } from '@angular/router';

@Component({
  selector: 'app-receipt-selection',
  template: '...'
})
export class ReceiptSelectionComponent {
  #returnProcessStore = inject(ReturnProcessStore);
  #router = inject(Router);

  async startReturn(receipt: Receipt, items: ReceiptItem[]): Promise<void> {
    const processId = Date.now(); // Unique process ID

    // Initialize the return process
    this.#returnProcessStore.startProcess({
      processId,
      returns: [
        {
          receipt,
          items: items.map(item => ({
            receiptItem: item,
            quantity: 1,
            category: 'Buch/Kalender' // Product category
          }))
        }
      ]
    });

    // Navigate to return process
    await this.#router.navigate(['/return-process', processId]);
  }
}
```

### 3. The Library Handles the Rest

Once initialized, the library automatically:
- Displays appropriate questions based on product category
- Validates answers using Zod schemas
- Checks eligibility with backend API
- Shows progress and conditional navigation
- Persists state across sessions

## Core Components

### ReturnProcessComponent

Main orchestrator component that manages the overall return process workflow.

**Selector:** `oms-feature-return-process`

**Key Responsibilities:**
- Displays all return process items for a given process ID
- Manages navigation (back button, continue to summary)
- Aggregates eligibility across all items
- Validates completion before allowing navigation

**Inputs:** None (uses route parameter via `injectTabId()`)

**Outputs:** None (navigation-based)

**Template Features:**
- Back navigation button
- Header with instructions
- List of return process items
- Conditional "Continue" button (only shows when all items are eligible)

**Example Usage:**
```typescript
// Automatically rendered via routing
// Route: /return-process/:id
```

### ReturnProcessItemComponent

Displays a single return process item with product info, questions, and eligibility status.

**Selector:** `oms-feature-return-process-item`

**Inputs:**
- `returnProcessId: number` (required) - The unique identifier for the return process

**Key Features:**
- Product information display
- Question renderer integration
- Progress bar visualization
- Eligibility status indicators (checkmark/error with message)
- Loading spinner during backend validation

**Eligibility States:**
- **Eligible** - Green checkmark with "Artikel bereit für Rückgabe"
- **Not Eligible** - Red X with specific error message
- **Unknown** - Loading spinner during validation

**Example:**
```html
<oms-feature-return-process-item
  [returnProcessId]="123">
</oms-feature-return-process-item>
```

### ReturnProcessQuestionsComponent

Container component that fetches and passes questions to the renderer.

**Selector:** `oms-feature-return-process-questions`

**Inputs:**
- `returnProcessId: number` (required) - The return process identifier

**Key Responsibilities:**
- Retrieves active questions from ReturnProcessService
- Filters questions based on product category
- Passes questions to renderer for display

**Example:**
```html
<oms-feature-return-process-questions
  [returnProcessId]="processId">
</oms-feature-return-process-questions>
```

### ReturnProcessQuestionsRendererComponent

Recursive renderer that displays questions dynamically based on their type.

**Selector:** `oms-feature-return-process-questions-renderer`

**Inputs:**
- `returnProcessId: number` (required) - The return process identifier
- `questions: ReturnProcessQuestion[]` (required) - Array of questions to render

**Supported Question Types:**
1. **Select** - Single-choice chip selection
2. **Product** - EAN input with scanner integration
3. **Checklist** - Multi-select with optional text field
4. **Info** - Display-only informational content
5. **Group** - Nested questions (recursive rendering)

**Example:**
```html
<oms-feature-return-process-questions-renderer
  [returnProcessId]="123"
  [questions]="activeQuestions">
</oms-feature-return-process-questions-renderer>
```

### Question Type Components

#### ReturnProcessSelectQuestionComponent

Single-choice question using chip UI components.

**Selector:** `oms-feature-return-process-select-question`

**Inputs:**
- `returnProcessId: number` (required)
- `question: ReturnProcessSelectQuestion` (required)

**Features:**
- Reactive form control with validation
- Automatic answer persistence to store
- Answer removal when invalid

**Example:**
```html
<oms-feature-return-process-select-question
  [returnProcessId]="123"
  [question]="selectQuestion">
</oms-feature-return-process-select-question>
```

#### ReturnProcessProductQuestionComponent

Product lookup question with EAN input and barcode scanning.

**Selector:** `oms-feature-return-process-product-question`

**Inputs:**
- `returnProcessId: number` (required)
- `question: ReturnProcessProductQuestion` (required)

**Features:**
- EAN validation with custom validator
- Barcode scanner integration
- Product search via CatalogueSearchService
- Product preview with image and details
- Error states (not found, invalid EAN)
- Auto-focus on input field

**States:**
- **Empty** - Input field with "Check" button
- **Fetching** - Loading spinner on button
- **Found** - Product display with clear button
- **Not Found** - Error message "Kein Artikel gefunden"
- **Invalid** - Error message "Die eingegebene EAN ist ungültig"

**Example:**
```html
<oms-feature-return-process-product-question
  [returnProcessId]="123"
  [question]="productQuestion">
</oms-feature-return-process-product-question>
```

#### ReturnProcessChecklistQuestionComponent

Multi-select checklist with optional "other" text field.

**Selector:** `oms-feature-return-process-checklist-question`

**Inputs:**
- `returnProcessId: number` (required)
- `question: ReturnProcessChecklistQuestion` (required)

**Features:**
- Multiple option selection via checkboxes
- Optional textarea for "other" input
- Linked signals for reactive updates
- Zod schema validation
- Deep equality checking with lodash

**Answer Structure:**
```typescript
{
  options?: string[],  // Selected checkbox values
  other?: string       // Optional text input
}
```

**Example:**
```html
<oms-feature-return-process-checklist-question
  [returnProcessId]="123"
  [question]="checklistQuestion">
</oms-feature-return-process-checklist-question>
```

#### ReturnProcessInfoQuestionComponent

Display-only informational question (no user input).

**Selector:** `oms-feature-return-process-info-question`

**Inputs:**
- `question: ReturnProcessInfoQuestion` (required)

**Features:**
- Displays description and content list
- No answer collection
- Always considered "answered"

**Example:**
```html
<oms-feature-return-process-info-question
  [question]="infoQuestion">
</oms-feature-return-process-info-question>
```

## Question System Architecture

### Product Categories

The question system supports six product categories, each with tailored question flows:

```typescript
const ProductCategory = {
  Unknown: 'unknown',
  BookCalendar: 'Buch/Kalender',
  TonDatentraeger: 'Ton-/Datenträger',
  SpielwarenPuzzle: 'Spielwaren/Puzzle',
  SonstigesNonbook: 'Sonstiges und Non-Book',
  ElektronischeGeraete: 'Andere Elektronische Geräte',
  Tolino: 'Tolino',
};
```

### Question Types

Five distinct question types handle different input scenarios:

```typescript
const ReturnProcessQuestionType = {
  Select: 'select',       // Single choice (chips)
  Product: 'product',     // EAN lookup
  Checklist: 'checklist', // Multi-select
  Info: 'info',           // Display only
  Group: 'group',         // Nested questions
};
```

### Question Structure

#### Select Question Example

```typescript
{
  key: 'item-condition',
  description: 'Wie ist der Artikelzustand?',
  type: 'select',
  options: [
    {
      label: 'Neuwertig/Originalverpackt',
      value: 'Originalverpackt',
      nextQuestion: 'return-reason',
      returnInfo: { itemCondition: 'Neuwertig/Originalverpackt' }
    },
    {
      label: 'Beschädigt/Fehldruck',
      value: 'Geöffnet/Defekt',
      returnInfo: { itemCondition: 'Beschädigt/Fehldruck' }
    }
  ]
}
```

#### Product Question Example

```typescript
{
  key: 'delivered-item',
  description: 'Welcher Artikel wurde geliefert?',
  type: 'product',
  nextQuestion: 'item-damaged' // Optional next question
}
```

#### Checklist Question Example

```typescript
{
  key: 'package-missing-items',
  description: 'Was fehlt?',
  type: 'checklist',
  options: [
    { label: 'Karton / Umverpackung', value: 'Karton / Umverpackung' },
    { label: 'Ladekabel', value: 'Ladekabel' },
    { label: 'Quickstart-Guide', value: 'Quickstart-Guide' }
  ],
  other: {
    label: 'Sonstiges'
  }
}
```

#### Group Question Example (Nested)

```typescript
{
  key: 'package-complete-group',
  type: 'group',
  questions: [
    {
      key: 'package-incomplete-info',
      description: 'Fehlende Teile',
      type: 'info',
      content: ['Item 1', 'Item 2']
    },
    {
      key: 'package-missing-items',
      description: 'Was fehlt?',
      type: 'checklist',
      options: [...]
    }
  ]
}
```

### Question Flow and Branching

Questions support conditional branching via the `nextQuestion` property:

```typescript
// Question 1: Item Condition
{
  key: 'item-condition',
  type: 'select',
  options: [
    {
      value: 'Originalverpackt',
      nextQuestion: 'return-reason', // Branch to return reason
    },
    {
      value: 'Geöffnet/Defekt',
      // No nextQuestion - ends flow
    }
  ]
}

// Question 2: Return Reason (shown only if OVP selected)
{
  key: 'return-reason',
  type: 'select',
  options: [
    {
      value: 'Gefällt nicht/Widerruf',
      // Ends flow
    },
    {
      value: 'Fehllieferung',
      nextQuestion: 'delivered-item', // Branch to product lookup
    }
  ]
}

// Question 3: Delivered Item (shown only if wrong item)
{
  key: 'delivered-item',
  type: 'product',
  description: 'Welcher Artikel wurde geliefert?'
}
```

### Active Question Determination

The `ReturnProcessService.activeReturnProcessQuestions()` method:

1. **Starts with all category questions** from `CategoryQuestions` registry
2. **Filters based on previous answers** using `nextQuestion` logic
3. **Validates answers** using Zod schemas
4. **Returns only questions that should be shown** based on user choices
5. **Detects cyclic dependencies** and throws errors

**Example Flow:**

```typescript
// User selects "Originalverpackt" for item-condition
answers = { 'item-condition': 'Originalverpackt' };

// Active questions: ['item-condition', 'return-reason']
// ('return-reason' shown because option has nextQuestion)

// User selects "Fehllieferung" for return-reason
answers = {
  'item-condition': 'Originalverpackt',
  'return-reason': 'Fehllieferung'
};

// Active questions: ['item-condition', 'return-reason', 'delivered-item']
// ('delivered-item' shown because 'Fehllieferung' option has nextQuestion)
```

### Category Question Registry

Questions are organized by category in the `CategoryQuestions` registry (from `@isa/oms/data-access`):

```typescript
const CategoryQuestions: Record<ProductCategory, ReturnProcessQuestion[]> = {
  'Buch/Kalender': bookCalendarQuestions,
  'Ton-/Datenträger': tonDatentraegerQuestions,
  'Spielwaren/Puzzle': nonbookQuestions,
  'Sonstiges und Non-Book': nonbookQuestions,
  'Andere Elektronische Geräte': elektronischeGeraeteQuestions,
  'Tolino': tolinoQuestions,
  'unknown': []
};
```

## API Reference

### ReturnProcessComponent

**Selector:** `oms-feature-return-process`

**Inputs:** None (uses route parameter)

**Computed Signals:**
- `processId: Signal<number | null>` - The current process ID from route
- `returnProcesses: Signal<ReturnProcess[]>` - Filtered processes for current ID
- `canContinueToSummary: Signal<boolean>` - True when all questions answered and eligible
- `canReturn: WritableSignal<boolean>` - Backend validation result for all processes

**Methods:**
None (template-driven navigation)

**Template Actions:**
- `location.back()` - Navigate to previous page

### ReturnProcessItemComponent

**Selector:** `oms-feature-return-process-item`

**Inputs:**
- `returnProcessId: number` (required) - Unique return process identifier

**Computed Signals:**
- `returnProcess: Signal<ReturnProcess | undefined>` - The current process entity
- `progress: Signal<{ answered: number; total: number } | undefined>` - Question progress
- `eligibleForReturn: Signal<EligibleForReturn | undefined>` - Frontend eligibility check
- `eligibleForReturnState: Signal<EligibleForReturnState>` - Combined frontend/backend state
- `eligibleForReturnMessage: Signal<string | undefined>` - Eligibility explanation message

**Writable Signals:**
- `canReturn: WritableSignal<CanReturn | undefined>` - Backend validation result

**Lifecycle:**
- **Constructor Effect** - Automatically validates eligibility on process changes

### ReturnProcessQuestionsComponent

**Selector:** `oms-feature-return-process-questions`

**Inputs:**
- `returnProcessId: number` (required) - Return process identifier

**Computed Signals:**
- `returnProcess: Signal<ReturnProcess | undefined>` - Current process
- `questions: Signal<ReturnProcessQuestion[]>` - Active questions for display

### ReturnProcessSelectQuestionComponent

**Selector:** `oms-feature-return-process-select-question`

**Inputs:**
- `returnProcessId: number` (required)
- `question: ReturnProcessSelectQuestion` (required)

**Form Control:**
- `control: FormControl<string | undefined>` - Reactive form control with required validation

**Lifecycle:**
- **Constructor** - Sets up value change subscription and answer synchronization

### ReturnProcessProductQuestionComponent

**Selector:** `oms-feature-return-process-product-question`

**Inputs:**
- `returnProcessId: number` (required)
- `question: ReturnProcessProductQuestion` (required)

**Form Control:**
- `control: FormControl<string>` - EAN input with validation (required + EAN format)

**Signals:**
- `product: WritableSignal<Product | undefined>` - Found product data
- `status: WritableSignal<{ fetching: boolean; hasResult?: boolean }>` - Search status

**Methods:**
- `check(): void` - Validates EAN and searches for product
- `resetProduct(): void` - Clears product and answer
- `onScan(value: string | null): void` - Handles barcode scanner input

**RxJS Methods:**
- `check: RxMethod<void>` - Observable-based product search with error handling

### ReturnProcessChecklistQuestionComponent

**Selector:** `oms-feature-return-process-checklist-question`

**Inputs:**
- `returnProcessId: number` (required)
- `question: ReturnProcessChecklistQuestion` (required)

**Computed Signals:**
- `currentAnswer: Signal<ReturnProcessChecklistAnswer>` - Current answer from store

**Linked Signals:**
- `options: LinkedSignal<string[] | undefined>` - Selected checkbox values
- `other: LinkedSignal<string | undefined>` - Optional text input

**Methods:**
- `updateAnswer(answer: ReturnProcessChecklistAnswer): void` - Persists answer to store

**Effects:**
- `valueChangesEffect` - Watches signal changes and updates store

### ReturnProcessInfoQuestionComponent

**Selector:** `oms-feature-return-process-info-question`

**Inputs:**
- `question: ReturnProcessInfoQuestion` (required)

**No Methods or Signals** (Display-only component)

## Usage Examples

### Complete Return Process Flow

```typescript
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { ReturnProcessStore, Receipt, ReceiptItem } from '@isa/oms/data-access';

@Component({
  selector: 'app-return-workflow',
  template: `
    <button (click)="startReturnProcess()">
      Start Return
    </button>
  `
})
export class ReturnWorkflowComponent {
  #returnProcessStore = inject(ReturnProcessStore);
  #router = inject(Router);

  async startReturnProcess(): Promise<void> {
    // Sample data
    const receipt: Receipt = {
      id: 12345,
      printedDate: '2024-01-15',
      // ... other receipt properties
    };

    const items: ReceiptItem[] = [
      {
        id: 1,
        product: {
          ean: '9783123456789',
          name: 'Sample Book',
          category: 'Buch/Kalender'
        },
        quantity: 1,
        // ... other item properties
      }
    ];

    // Generate unique process ID (typically tab ID or timestamp)
    const processId = Date.now();

    try {
      // Initialize return process in store
      this.#returnProcessStore.startProcess({
        processId,
        returns: [
          {
            receipt,
            items: items.map(item => ({
              receiptItem: item,
              quantity: 1,
              category: 'Buch/Kalender'
            }))
          }
        ]
      });

      // Navigate to return process page
      await this.#router.navigate(['/return-process', processId]);
    } catch (error) {
      console.error('Failed to start return process:', error);
    }
  }
}
```

### Accessing Return Process State

```typescript
import { Component, computed, inject } from '@angular/core';
import { ReturnProcessStore, ReturnProcessService } from '@isa/oms/data-access';

@Component({
  selector: 'app-process-monitor',
  template: `
    <div>
      <h3>Return Process Status</h3>
      <p>Total Processes: {{ processCount() }}</p>
      <p>Completed: {{ completedCount() }}</p>

      @for (process of processes(); track process.id) {
        <div>
          <p>Process {{ process.id }}</p>
          <p>Progress: {{ getProgress(process) }}</p>
          <p>Eligible: {{ isEligible(process) }}</p>
        </div>
      }
    </div>
  `
})
export class ProcessMonitorComponent {
  #returnProcessStore = inject(ReturnProcessStore);
  #returnProcessService = inject(ReturnProcessService);

  processes = this.#returnProcessStore.entities;

  processCount = computed(() => this.processes().length);

  completedCount = computed(() => {
    return this.processes().filter(p => {
      const progress = this.#returnProcessService
        .returnProcessQuestionsProgress(p);
      return progress?.answered === progress?.total;
    }).length;
  });

  getProgress(process: ReturnProcess): string {
    const progress = this.#returnProcessService
      .returnProcessQuestionsProgress(process);
    if (!progress) return 'N/A';
    return `${progress.answered}/${progress.total}`;
  }

  isEligible(process: ReturnProcess): boolean {
    const result = this.#returnProcessService.eligibleForReturn(process);
    return result?.state === 'eligible';
  }
}
```

### Programmatic Answer Setting

```typescript
import { Component, inject } from '@angular/core';
import { ReturnProcessStore } from '@isa/oms/data-access';

@Component({
  selector: 'app-auto-answer',
  template: '...'
})
export class AutoAnswerComponent {
  #returnProcessStore = inject(ReturnProcessStore);

  autoFillAnswers(processId: number): void {
    // Set select answer
    this.#returnProcessStore.setAnswer(
      processId,
      'item-condition',
      'Originalverpackt'
    );

    // Set checklist answer
    this.#returnProcessStore.setAnswer(
      processId,
      'package-missing-items',
      {
        options: ['Ladekabel', 'Quickstart-Guide'],
        other: 'Custom missing item'
      }
    );

    // Set product answer
    this.#returnProcessStore.setAnswer(
      processId,
      'delivered-item',
      {
        ean: '9783123456789',
        name: 'Sample Product',
        catalogProductNumber: '12345',
        // ... other product properties
      }
    );

    // Remove answer
    this.#returnProcessStore.removeAnswer(
      processId,
      'item-condition'
    );
  }
}
```

### Testing Question Eligibility

```typescript
import { Component, inject, signal } from '@angular/core';
import {
  ReturnProcessService,
  ReturnProcess,
  EligibleForReturnState
} from '@isa/oms/data-access';

@Component({
  selector: 'app-eligibility-check',
  template: `
    <div>
      <h3>Eligibility Status</h3>
      @if (eligibility(); as result) {
        @switch (result.state) {
          @case ('eligible') {
            <p class="text-green-600">✓ Eligible for return</p>
          }
          @case ('not-eligible') {
            <p class="text-red-600">✗ Not eligible: {{ result.reason }}</p>
          }
          @case ('unknown') {
            <p class="text-gray-600">⏳ Checking eligibility...</p>
          }
        }
      }
    </div>
  `
})
export class EligibilityCheckComponent {
  #returnProcessService = inject(ReturnProcessService);

  process = signal<ReturnProcess>({
    id: 1,
    processId: 100,
    receiptId: 12345,
    productCategory: 'Tolino',
    quantity: 1,
    receiptDate: '2024-01-15',
    receiptItem: { /* ... */ },
    answers: {
      'item-condition': 'Originalverpackt',
      'return-reason': 'Gefällt nicht/Widerruf',
      'display-damaged': 'Nein',
      'device-power': 'Ja'
    }
  });

  eligibility = computed(() => {
    return this.#returnProcessService.eligibleForReturn(this.process());
  });
}
```

## Routing and Navigation

### Route Configuration

The library exports a `routes` array that defines three routes:

```typescript
// libs/oms/feature/return-process/src/lib/routes.ts

export const routes: Route[] = [
  {
    path: '',
    component: ReturnProcessComponent
  },
  {
    path: 'summary',
    loadChildren: () =>
      import('@isa/oms/feature/return-summary').then((feat) => feat.routes)
  },
  {
    path: 'review',
    loadChildren: () =>
      import('@isa/oms/feature/return-review').then((feat) => feat.routes)
  }
];
```

### Integration in Main App

```typescript
// apps/isa-app/src/app/app-routing.module.ts

import { Route } from '@angular/router';

export const routes: Route[] = [
  {
    path: 'return-process/:id',
    loadChildren: () =>
      import('@isa/oms/feature/return-process').then((m) => m.routes)
  },
  // ... other routes
];
```

### Navigation Flow

1. **Start** - External component navigates to `/return-process/:id`
2. **Questions** - User answers questions on main route (`/return-process/:id`)
3. **Summary** - Click "Weiter" navigates to `/return-process/:id/summary`
4. **Review** - Additional route for final review at `/return-process/:id/review`

### Process ID Management

The library uses `injectTabId()` from `@isa/core/tabs` to retrieve the process ID:

```typescript
// In ReturnProcessComponent
processId = injectTabId(); // Returns Signal<number | null>
```

This integrates with the tab system to support multiple concurrent return processes.

### Navigation Guards

The "Weiter" (Continue) button is conditionally displayed:

```html
@if (canContinueToSummary() && canReturn()) {
  <div class="text-right">
    <a routerLink="summary" uiButton color="brand">
      Weiter
    </a>
  </div>
}
```

**Conditions for Navigation:**
1. All processes must exist
2. All questions must be answered
3. Frontend eligibility must be "eligible"
4. Backend validation must pass

## State Management

### ReturnProcessStore Integration

The library uses `ReturnProcessStore` from `@isa/oms/data-access` for state management:

**Store Features:**
- **Entity Management** - Stores `ReturnProcess` entities with normalized structure
- **Session Persistence** - Automatically persists to IndexedDB via `IDBStorageProvider`
- **Computed Next ID** - Generates unique IDs for new processes
- **Answer Management** - Methods for setting/removing answers
- **Orphan Cleanup** - Automatically removes entities when tabs close

**Key Methods:**

```typescript
// Start new return process
ReturnProcessStore.startProcess(params: StartProcess): void

// Set answer for a question
ReturnProcessStore.setAnswer<T>(id: number, question: string, answer: T): void

// Remove answer
ReturnProcessStore.removeAnswer(id: number, question: string): void

// Remove processes by ID
ReturnProcessStore.removeAllEntitiesByProcessId(...processIds: number[]): void

// Access entities
ReturnProcessStore.entities: Signal<ReturnProcess[]>
ReturnProcessStore.entityMap: Signal<Record<number, ReturnProcess>>
```

### Answer Structure

Answers are stored as a key-value map with question keys:

```typescript
interface ReturnProcess {
  id: number;
  processId: number;
  receiptId: number;
  receiptItem: ReceiptItem;
  receiptDate: string | undefined;
  productCategory: string;
  quantity: number;
  answers: Record<string, unknown>; // Question answers
  returnReceipt?: Receipt; // Set when process completed
}
```

**Example Answer Data:**

```typescript
{
  'item-condition': 'Originalverpackt',
  'return-reason': 'Fehllieferung',
  'delivered-item': {
    ean: '9783123456789',
    name: 'Sample Book',
    catalogProductNumber: '12345'
  },
  'package-missing-items': {
    options: ['Ladekabel'],
    other: 'Custom item'
  }
}
```

### State Synchronization

Components use Angular signals and effects for reactive state updates:

```typescript
// Example from ReturnProcessSelectQuestionComponent
constructor() {
  // Listen to form changes and update store
  this.control.valueChanges
    .pipe(takeUntilDestroyed())
    .subscribe((value) => {
      if (this.control.valid) {
        this.#returnProcessStore.setAnswer(
          this.returnProcessId(),
          this.question().key,
          value
        );
      } else {
        this.#returnProcessStore.removeAnswer(
          this.returnProcessId(),
          this.question().key
        );
      }
    });

  // Sync store changes back to form
  effect(() => {
    const value = this.#returnProcessStore
      .entityMap()[this.returnProcessId()]
      ?.answers[this.question().key];
    if (value !== this.control.value) {
      this.control.setValue(value as string);
    }
  });
}
```

### Session Persistence

All return process data is automatically persisted to IndexedDB:

- **Automatic Save** - Every `setAnswer()` and `removeAnswer()` triggers `saveToStorage()`
- **Restore on Load** - State automatically restored when user returns
- **Tab Cleanup** - Orphaned processes removed via `onInit` hook

**Storage Provider:**

```typescript
withStorage('return-process', IDBStorageProvider)
```

## Validation and Eligibility

### Frontend Validation (Schema-Based)

All answers are validated using Zod schemas (defined in `@isa/oms/data-access`):

```typescript
// Example schemas
const ReturnProcessQuestionSchema = {
  'item-condition': z.enum(['Originalverpackt', 'Geöffnet/Defekt']),
  'return-reason': z.enum(['Gefällt nicht/Widerruf', 'Fehllieferung']),
  'display-damaged': z.enum(['Ja', 'Nein']),
  'delivered-item': z.object({
    ean: z.string(),
    name: z.string(),
    catalogProductNumber: z.string().optional(),
    // ... more fields
  }).passthrough(),
  'package-missing-items': z.object({
    options: z.array(z.enum([...])).min(1).optional(),
    other: z.string().optional()
  })
};
```

### Form-Level Validation

Individual question components use reactive forms with validators:

```typescript
// Select Question - Required validator
control = new FormControl<string | undefined>(undefined, [
  Validators.required
]);

// Product Question - EAN validator
control = new FormControl<string>('', [
  Validators.required,
  eanValidator // Custom EAN format validator from @isa/utils/ean-validation
]);
```

### Eligibility Determination

Eligibility is checked at two levels:

#### 1. Frontend Eligibility (ReturnProcessService)

```typescript
eligibleForReturn(returnProcess: ReturnProcess): EligibleForReturn | undefined {
  // Returns undefined if questions not answered
  // Returns { state, reason } based on category-specific logic

  switch (returnProcess.productCategory) {
    case 'Andere Elektronische Geräte':
      return isElektronischeGeraeteEligibleForReturn(returnProcess, questions);
    case 'Ton-/Datenträger':
      return isTonDatentraegerEligibleForReturn(returnProcess, questions);
    case 'Tolino':
      return isTolinoEligibleForReturn(returnProcess, questions);
    default:
      return { state: 'eligible' };
  }
}
```

**Eligibility States:**

```typescript
const EligibleForReturnState = {
  NotEligible: 'not-eligible',
  Eligible: 'eligible',
  Unknown: 'unknown'
};
```

**Example Category Logic (Tolino):**

```typescript
// Not eligible if display damaged
if (answers['display-damaged'] === 'Ja') {
  return {
    state: 'not-eligible',
    reason: 'Display beschädigt - Rückgabe nicht möglich'
  };
}

// Not eligible if device doesn't power on
if (answers['device-power'] === 'Nein') {
  return {
    state: 'not-eligible',
    reason: 'Gerät schaltet nicht ein - Rückgabe nicht möglich'
  };
}

// Eligible if all checks pass
return { state: 'eligible' };
```

#### 2. Backend Validation (ReturnCanReturnService)

```typescript
// In ReturnProcessItemComponent
constructor() {
  effect(() => {
    const returnProcess = this.returnProcess();

    untracked(async () => {
      if (returnProcess) {
        this.canReturn.set(undefined);
        try {
          const canReturnResponse = await this.#returnCanReturnService
            .canReturn(returnProcess);
          this.canReturn.set(canReturnResponse);
        } catch (error) {
          this.#logger.error('Failed to validate return process', error);
          this.canReturn.set(undefined);
        }
      }
    });
  });
}
```

**Backend Response:**

```typescript
interface CanReturn {
  result: boolean;      // Can return or not
  message?: string;     // Explanation message
}
```

### Combined Eligibility Logic

The final eligibility state combines frontend and backend validation:

```typescript
eligibleForReturnState = computed(() => {
  const frontendEligible = this.eligibleForReturn()?.state;
  const backendCanReturn = this.canReturn()?.result;

  // Still validating
  if (backendCanReturn === undefined) {
    return 'unknown';
  }

  // Both must be true for eligibility
  if (backendCanReturn === true && frontendEligible === 'eligible') {
    return 'eligible';
  }

  return 'not-eligible';
});
```

### Progress Calculation

Question progress is calculated considering nested questions:

```typescript
returnProcessQuestionsProgress(returnProcess: ReturnProcess) {
  const questions = getReturnProcessQuestions(returnProcess);
  const activeQuestions = this.activeReturnProcessQuestions(returnProcess);

  // Count answered questions (including group sub-questions)
  const answered = activeQuestions.reduce((acc, q) => {
    if (q.type === 'group') {
      return acc + q.questions.filter(
        subQ => subQ.key in returnProcess.answers
      ).length;
    }
    return q.key in returnProcess.answers ? acc + 1 : acc;
  }, 0);

  // Calculate total possible questions (longest path)
  const total = calculateLongestQuestionDepth(questions, returnProcess.answers);

  return { answered, total };
}
```

## Testing

The library uses **Jest** for testing (migration to Vitest pending).

### Running Tests

```bash
# Run all tests for this library
npx nx test oms-feature-return-process --skip-nx-cache

# Run with coverage
npx nx test oms-feature-return-process --code-coverage --skip-nx-cache

# Run in watch mode
npx nx test oms-feature-return-process --watch
```

### Test Structure

Current test coverage includes:

- **Component Tests** - Basic component instantiation and template rendering
- **Integration Tests** - Component interactions with stores and services
- **E2E Attributes** - Verification of `data-what` and `data-which` attributes

### Example Test

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReturnProcessComponent } from './return-process.component';

describe('ReturnProcessComponent', () => {
  let component: ReturnProcessComponent;
  let fixture: ComponentFixture<ReturnProcessComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ReturnProcessComponent],
      providers: [
        // Mock providers
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(ReturnProcessComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should have E2E attribute on back button', () => {
    const backButton = fixture.nativeElement.querySelector('[data-what="back-navigation"]');
    expect(backButton).toBeTruthy();
  });
});
```

### E2E Testing Attributes

All interactive elements include E2E testing attributes:

```html
<!-- Back navigation -->
<button data-what="back-navigation" (click)="location.back()">
  zurück
</button>

<!-- Summary navigation -->
<a data-what="summary-navigation" routerLink="summary">
  Weiter
</a>

<!-- Loading spinner -->
<ui-icon-button
  data-what="load-spinner"
  data-which="can-return"
  [pending]="true">
</ui-icon-button>
```

### Testing Best Practices

1. **Mock Dependencies** - Use TestBed to provide mock services
2. **Verify E2E Attributes** - Always test presence of `data-what` and `data-which`
3. **Test User Flows** - Simulate complete question flows
4. **Validate State Changes** - Ensure store updates correctly
5. **Test Error States** - Verify error handling and display

## Architecture Notes

### Component Hierarchy

```
ReturnProcessComponent (Main orchestrator)
└── ReturnProcessItemComponent (Per-item wrapper)
    ├── ReturnProductInfoComponent (Product display)
    └── ReturnProcessQuestionsComponent (Question container)
        └── ReturnProcessQuestionsRendererComponent (Recursive renderer)
            ├── ReturnProcessSelectQuestionComponent
            ├── ReturnProcessProductQuestionComponent
            ├── ReturnProcessChecklistQuestionComponent
            ├── ReturnProcessInfoQuestionComponent
            └── ReturnProcessQuestionsRendererComponent (Recursive for groups)
```

### Data Flow

```
┌─────────────────────────────────────────────────────────────┐
│                     User Interaction                        │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│              Question Component (e.g., Select)              │
│  - FormControl validation                                   │
│  - User input collection                                    │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                  ReturnProcessStore                         │
│  - setAnswer(id, key, value)                               │
│  - Persist to IndexedDB                                     │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│               ReturnProcessService                          │
│  - activeReturnProcessQuestions() → Filter visible Qs      │
│  - eligibleForReturn() → Frontend validation               │
│  - returnProcessQuestionsProgress() → Progress calc        │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│             ReturnCanReturnService (Backend)                │
│  - canReturn() → Backend validation via API                │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                 Component Signals Update                    │
│  - eligibleForReturnState                                   │
│  - canContinueToSummary                                     │
│  - progress                                                 │
└─────────────────────────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                    UI Re-renders                            │
│  - Show/hide questions                                      │
│  - Update progress bar                                      │
│  - Enable/disable navigation                                │
└─────────────────────────────────────────────────────────────┘
```

### Design Patterns

1. **Presenter Pattern** - Components are thin presenters, business logic in services
2. **Reactive State** - Signals and computed values for automatic UI updates
3. **Recursive Rendering** - Question renderer supports nested question groups
4. **Strategy Pattern** - Different question types use different rendering strategies
5. **Command Pattern** - Store methods (`setAnswer`, `removeAnswer`) encapsulate actions
6. **Observer Pattern** - Effects watch signals and trigger side effects

### Key Architectural Decisions

#### 1. Two-Level Validation

**Decision:** Implement both frontend and backend validation

**Rationale:**
- Frontend validation provides immediate feedback
- Backend validation ensures business rules and data integrity
- Combined approach prevents invalid returns while maintaining UX

**Trade-off:** Additional complexity and API calls, but improved reliability

#### 2. Recursive Question Rendering

**Decision:** Use recursive component for nested question groups

**Rationale:**
- Tolino category requires nested questions (Group type)
- Recursive rendering handles arbitrary nesting depth
- Single renderer component reduces code duplication

**Trade-off:** Slightly more complex than flat rendering, but handles all cases

#### 3. Signal-Based State Management

**Decision:** Use Angular signals instead of RxJS observables for local state

**Rationale:**
- Simpler mental model for component state
- Automatic change detection without manual subscriptions
- Better performance with fine-grained reactivity

**Trade-off:** Mixing signals and observables can be confusing

#### 4. Session Persistence via IndexedDB

**Decision:** Persist all return process data to IndexedDB

**Rationale:**
- Preserve user progress across browser sessions
- Handle page refreshes gracefully
- Support multi-tab workflows

**Trade-off:** Storage size limitations, async complexity

### Known Limitations

1. **No Undo/Redo** - Once answered, questions can only be re-answered, not undone
2. **Linear Question Flow** - Questions follow a tree structure, no arbitrary jumps
3. **Single Product Category** - Each process item has one category, no mixing
4. **Backend Dependency** - Cannot complete process without backend validation
5. **No Draft Saving** - Process must be completed or abandoned, no "save for later"

### Future Enhancements

Potential improvements identified:

1. **Answer History** - Track answer changes for audit trail
2. **Conditional Validation** - More complex validation rules based on multiple answers
3. **Question Templates** - Reusable question patterns across categories
4. **Multi-step Progress** - Show step-by-step progress instead of single bar
5. **Answer Pre-fill** - Automatically suggest answers based on previous returns
6. **Offline Support** - Queue backend validation for later when offline
7. **Migration to Vitest** - Update testing framework to match newer libraries

## Dependencies

### Required Angular Libraries

- `@angular/core` ^20.1.2 - Angular framework
- `@angular/common` ^20.1.2 - Common Angular directives
- `@angular/forms` ^20.1.2 - Reactive forms
- `@angular/router` ^20.1.2 - Routing and navigation

### Required Internal Libraries

#### OMS Domain
- `@isa/oms/data-access` - Core business logic, stores, and services
- `@isa/oms/shared/product-info` - Product information display component

#### UI Components
- `@isa/ui/buttons` - Button and icon button components
- `@isa/ui/input-controls` - Form controls (chips, checkbox, textarea, text field)
- `@isa/ui/progress-bar` - Progress visualization

#### Shared Components
- `@isa/shared/scanner` - Barcode scanner integration
- `@isa/shared/product-image` - Product image directive
- `@isa/shared/product-router-link` - Product navigation directive

#### Core Utilities
- `@isa/core/tabs` - Tab management and process ID injection
- `@isa/core/logging` - Centralized logging service
- `@isa/icons` - Icon library

#### Catalogue
- `@isa/catalogue/data-access` - Product search service

#### Utilities
- `@isa/utils/ean-validation` - EAN format validation

### External Dependencies

- `@ngrx/signals` ^18.1.2 - Signal-based state management
- `@ngrx/operators` ^18.1.2 - RxJS operators for NgRx
- `@ng-icons/core` ^30.3.0 - Icon components
- `zod` ^3.24.1 - Schema validation
- `rxjs` ^7.8.1 - Reactive programming
- `lodash` ^4.17.21 - Utility functions (isEqual)

### Generated API Clients

- `@generated/swagger/oms-api` - OMS API client for backend communication

### Path Alias

Import from: `@isa/oms/feature/return-process`

## License

Internal ISA Frontend library - not for external distribution.
