# @isa/oms/feature/return-review

A comprehensive Angular feature library for reviewing completed return processes in the Order Management System (OMS). Provides a confirmation interface for successful returns with task review capabilities and receipt printing functionality.

## Overview

The Return Review feature library implements the final step in the return process workflow, displaying a success confirmation and allowing users to review all tasks (completed and pending) associated with the return. It provides a read-only task list view with the ability to reprint return confirmation receipts, serving as both a completion confirmation and a reference point for return operations.

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [Component API Reference](#component-api-reference)
- [Usage Examples](#usage-examples)
- [Routing and Navigation](#routing-and-navigation)
- [Architecture Notes](#architecture-notes)
- [Dependencies](#dependencies)
- [Testing](#testing)
- [Best Practices](#best-practices)

## Features

- **Success Confirmation** - Clear visual confirmation of successful return completion
- **Receipt Reprinting** - One-click reprint of return confirmation receipts
- **Complete Task Review** - Display all tasks (completed and pending) in review mode
- **Navigation Protection** - Guard against leaving with uncompleted tasks
- **Tab-Based Isolation** - Process ID derived from tab context for multi-tab support
- **Integration with Task List** - Leverages shared task list component in review appearance
- **Print Service Integration** - Automatic batch printing of all return receipts for the process
- **Clean UI Design** - Minimal, focused interface emphasizing completion status
- **E2E Testing Support** - Comprehensive `data-what` and `data-which` attributes

## Quick Start

### 1. Import Routes

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

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

### 2. Navigate to Review

```typescript
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-return-process',
  template: '...',
})
export class ReturnProcessComponent {
  #router = inject(Router);

  completeReturn(): void {
    // After completing return process
    this.#router.navigate(['/return-review']);
  }
}
```

### 3. Basic Usage

The component is automatically loaded via routing and requires no configuration:

```typescript
// Route automatically loads the component
// Component uses tab context for process ID
// Task list automatically fetches and displays all tasks
```

## Core Concepts

### Return Review Workflow

The return review represents the final step in the return process:

1. **Return Process Completion** - User completes all required return process steps
2. **Navigation to Review** - System navigates to return review route
3. **Success Confirmation** - User sees "Die Rückgabe war erfolgreich!" message
4. **Task Review** - All tasks (completed and pending) displayed in table layout
5. **Receipt Printing** - Optional reprint of return confirmation receipts
6. **Task Completion** - User completes any remaining pending tasks
7. **Navigation Away** - Guard checks for uncompleted tasks before allowing exit

### Process ID and Tab Context

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

```typescript
processId = injectTabId();  // Returns Signal<number | undefined>
```

**Benefits:**
- Multiple return processes can run simultaneously in different tabs
- Each tab maintains independent state
- No manual process ID management required
- Process ID automatically propagates to child components

### Task List Integration

The review component delegates task display to `ReturnTaskListComponent`:

```html
<oms-shared-return-task-list
  [appearance]="'review'"
></oms-shared-return-task-list>
```

**Key Behaviors:**
- **Appearance Mode**: `'review'` shows all tasks (completed and pending)
- **Layout**: Table-based grid layout optimized for scanning
- **Read-Only**: Tasks can be marked as completed but not modified
- **Automatic Fetching**: Component fetches tasks on initialization

### Receipt Printing

The component provides receipt reprinting functionality:

```typescript
async printReceipt() {
  const processId = this.processId();
  if (processId) {
    // 1. Fetch all return processes for this process ID
    const receiptIds = this.#returnProcessStore
      .entities()
      .filter((p) => p.processId === processId && p.returnReceipt?.id)
      .map((p) => p.returnReceipt!.id);

    // 2. Print all receipts in batch
    await this.#printReceiptsService.printReturnReceipts({
      returnReceiptIds: receiptIds,
    });
  }
}
```

**Features:**
- Batch printing of all return receipts for the process
- Filters out processes without return receipt IDs
- Handles printing failures gracefully
- No user feedback required (print service handles UI)

### Navigation Guard

The library includes `UncompletedTasksGuard` to prevent accidental navigation away from pending tasks:

```typescript
canDeactivate(): boolean | Promise<boolean> {
  const hasUncompletedTasks = this.uncompletedTaskListItems().length > 0;

  if (!hasUncompletedTasks) {
    return true;  // Allow navigation
  }

  return this.openDialog();  // Confirm with user
}
```

**Dialog Behavior:**
- **Title**: "Aufgaben erledigen"
- **Message**: "Bitte schließen Sie die Aufgaben ab bevor Sie das die Rückgabe verlassen"
- **Close Button**: "Verlassen" (allows navigation)
- **Confirm Button**: "Zurück" (stays on page)
- **Logic**: Confirmation = stay, close/cancel = leave

## Component API Reference

### ReturnReviewComponent

Main component orchestrating the review interface.

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

**Inputs:** None (uses injected services and tab context)

**Outputs:** None (internal actions only)

**Template Structure:**
```html
<oms-feature-return-review-header
  (printReceipt)="printReceipt()"
></oms-feature-return-review-header>

<oms-shared-return-task-list
  [appearance]="'review'"
></oms-shared-return-task-list>
```

**Injected Services:**
- `PrintReceiptsService` - Receipt printing operations
- `ReturnProcessStore` - Access to return process entities

**Computed Properties:**
- `processId: Signal<number | undefined>` - Derived from tab context via `injectTabId()`

**Methods:**

##### `printReceipt(): Promise<void>`

Reprints all return confirmation receipts for the current process.

**Behavior:**
1. Retrieves current process ID from tab context
2. Filters return processes by process ID
3. Extracts return receipt IDs from processes
4. Calls print service with batch of receipt IDs

**Error Handling:**
- Print service handles errors internally
- No explicit error UI in component
- Failures logged by print service

**Example:**
```typescript
// Triggered by header print button
await component.printReceipt();
// All return receipts for process printed via print service
```

**Host Styling:**
```scss
:host {
  @apply flex flex-col w-full justify-start mt-6 p-6 bg-white rounded-2xl;
}
```

**Change Detection:** `OnPush` - Optimized change detection strategy

---

### ReturnReviewHeaderComponent

Header component displaying success message and print button.

**Selector:** `oms-feature-return-review-header`

**Inputs:** None

**Outputs:**

##### `printReceipt: EventEmitter<void>`

Emitted when user clicks the print receipt button.

**Payload:** None (void event)

**Template Structure:**
```html
<h2 class="isa-text-subtitle-1-regular">Die Rückgabe war erfolgreich!</h2>

<button
  data-what="button"
  data-which="print-receipt"
  class="self-start"
  (click)="printReceipt.emit()"
  uiInfoButton
>
  <span uiInfoButtonLabel>Rückgabe Bestätigung erneut drucken</span>
  <ng-icon name="isaActionPrinter" uiInfoButtonIcon></ng-icon>
</button>
```

**UI Components:**
- `InfoButtonComponent` - Styled button with icon and label
- `NgIconComponent` - Icon rendering with `isaActionPrinter` icon

**Host Styling:**
```scss
:host {
  @apply w-full
         flex
         flex-col
         gap-6
         desktop:gap-0
         desktop:flex-row
         desktop:justify-between
         desktop:items-center
         border-b
         border-solid
         border-isa-neutral-300
         pb-6;
}
```

**Responsive Behavior:**
- **Mobile/Tablet**: Vertical stack (flex-col) with 24px gap
- **Desktop**: Horizontal layout (flex-row) with space-between alignment

**Change Detection:** `OnPush`

**TODO Note:**
```typescript
// TODO: Kann direkt in der Komponente gehandled werden
printReceipt = output<void>();
```

This comment suggests future refactoring to handle printing directly in the header component instead of emitting to parent. This would simplify the component hierarchy.

---

### UncompletedTasksGuard

Navigation guard preventing users from leaving with uncompleted tasks.

**Injection:** `{ providedIn: 'root' }` - Singleton guard

**Implements:** `CanDeactivate<ReturnReviewComponent>`

**Injected Services:**
- `ReturnTaskListStore` - Task list state management
- `injectConfirmationDialog()` - Confirmation dialog service
- `injectTabId()` - Tab-based process ID

**Computed Properties:**

##### `uncompletedTaskListItems: Signal<ReceiptItemTaskListItem[]>`

Computes list of uncompleted tasks for current process.

**Logic:**
1. Retrieves process ID from tab context
2. Fetches task list entity from store by process ID
3. Filters tasks where `completed === false`
4. Returns empty array if no process ID or no tasks

**Example:**
```typescript
// Process ID = 123, tasks: [{ id: 1, completed: false }, { id: 2, completed: true }]
uncompletedTaskListItems() // Returns [{ id: 1, completed: false }]
```

**Methods:**

##### `canDeactivate(): boolean | Promise<boolean>`

Angular router guard method determining if navigation is allowed.

**Return Values:**
- `true` - Allow navigation (no uncompleted tasks)
- `Promise<boolean>` - User confirmation required (has uncompleted tasks)

**Logic:**
```typescript
const hasUncompletedTasks = this.uncompletedTaskListItems().length > 0;

if (!hasUncompletedTasks) {
  return true;  // No tasks pending - allow navigation
}

return this.openDialog();  // Confirm with user
```

##### `openDialog(): Promise<boolean>`

Opens confirmation dialog for user to decide on navigation.

**Dialog Configuration:**
```typescript
{
  title: 'Aufgaben erledigen',
  data: {
    message: 'Bitte schließen Sie die Aufgaben ab bevor Sie das die Rückgabe verlassen',
    closeText: 'Verlassen',
    confirmText: 'Zurück',
  },
}
```

**Return Logic:**
```typescript
const result = await firstValueFrom(confirmDialogRef.closed);
return !result?.confirmed;  // Invert: confirmed = stay, close = leave
```

**Dialog Result Mapping:**
- User clicks "Zurück" (confirm): `confirmed === true` → returns `false` (block navigation)
- User clicks "Verlassen" (close): `confirmed === false` → returns `true` (allow navigation)
- User dismisses dialog: `confirmed === undefined` → returns `true` (allow navigation)

## Usage Examples

### Basic Return Review Flow

```typescript
import { Component } from '@angular/core';
import { ReturnReviewComponent } from '@isa/oms/feature/return-review';

@Component({
  selector: 'app-returns-workflow',
  standalone: true,
  imports: [ReturnReviewComponent],
  template: `
    <oms-feature-return-review></oms-feature-return-review>
  `
})
export class ReturnsWorkflowComponent {}
```

### Routing Configuration

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

export const routes: Routes = [
  {
    path: 'returns',
    children: [
      {
        path: 'search',
        loadChildren: () =>
          import('@isa/oms/feature/return-search').then((m) => m.routes),
      },
      {
        path: 'details/:receiptId',
        loadChildren: () =>
          import('@isa/oms/feature/return-details').then((m) => m.routes),
      },
      {
        path: 'process',
        loadChildren: () =>
          import('@isa/oms/feature/return-process').then((m) => m.routes),
      },
      {
        path: 'review',
        loadChildren: () =>
          import('@isa/oms/feature/return-review').then((m) => m.routes),
      },
    ],
  },
];
```

### Programmatic Navigation to Review

```typescript
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-return-process-complete',
  template: '...',
})
export class ReturnProcessCompleteComponent {
  #router = inject(Router);

  finishReturn(): void {
    // Navigate to review after completing return process
    this.#router.navigate(['/returns/review']);
  }
}
```

### Custom Header with Print Button

```typescript
import { Component } from '@angular/core';
import { ReturnReviewHeaderComponent } from '@isa/oms/feature/return-review';

@Component({
  selector: 'app-custom-review-header',
  standalone: true,
  imports: [ReturnReviewHeaderComponent],
  template: `
    <div class="custom-header-container">
      <div class="branding">
        <img src="/assets/logo.svg" alt="Company Logo" />
      </div>

      <oms-feature-return-review-header
        (printReceipt)="handlePrint()"
      ></oms-feature-return-review-header>

      <div class="actions">
        <button (click)="closeTab()">Fenster schließen</button>
      </div>
    </div>
  `
})
export class CustomReviewHeaderComponent {
  handlePrint(): void {
    console.log('Printing receipts...');
  }

  closeTab(): void {
    window.close();
  }
}
```

### Accessing Store for Custom Logic

```typescript
import { Component, inject, computed } from '@angular/core';
import { ReturnProcessStore, ReturnTaskListStore } from '@isa/oms/data-access';
import { injectTabId } from '@isa/core/tabs';
import { ReturnReviewComponent } from '@isa/oms/feature/return-review';

@Component({
  selector: 'app-advanced-review',
  standalone: true,
  imports: [ReturnReviewComponent],
  template: `
    <div class="review-header">
      <h1>Rückgabe erfolgreich</h1>
      @if (hasPendingTasks()) {
        <p class="warning">
          Sie haben noch {{ pendingTaskCount() }} ausstehende Aufgaben
        </p>
      }
    </div>

    <oms-feature-return-review></oms-feature-return-review>
  `
})
export class AdvancedReviewComponent {
  #taskStore = inject(ReturnTaskListStore);
  processId = injectTabId();

  pendingTaskCount = computed(() => {
    const processId = this.processId();
    if (!processId) return 0;

    const entity = this.#taskStore.entityMap()[processId];
    return entity?.data?.filter(task => !task.completed).length ?? 0;
  });

  hasPendingTasks = computed(() => this.pendingTaskCount() > 0);
}
```

### Handling Navigation Guard Events

```typescript
import { Component, inject } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';

@Component({
  selector: 'app-review-with-navigation',
  template: `
    <div>
      <button (click)="navigateBack()">Zurück zur Suche</button>
      <oms-feature-return-review></oms-feature-return-review>
      <button (click)="startNewReturn()">Neue Rückgabe</button>
    </div>
  `
})
export class ReviewWithNavigationComponent {
  #router = inject(Router);
  #location = inject(Location);

  navigateBack(): void {
    // Will trigger UncompletedTasksGuard if tasks are pending
    this.#location.back();
  }

  startNewReturn(): void {
    // Will trigger UncompletedTasksGuard if tasks are pending
    this.#router.navigate(['/returns/search']);
  }
}
```

### Testing Guard Behavior

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

@Component({
  selector: 'app-guard-test',
  template: `
    <div>
      <p>Pending tasks: {{ pendingCount() }}</p>
      <button (click)="tryNavigate()">Try Navigate</button>
      <button (click)="completeTasks()">Complete All Tasks</button>
    </div>
  `
})
export class GuardTestComponent {
  #router = inject(Router);
  #store = inject(ReturnTaskListStore);
  processId = injectTabId();

  pendingCount = computed(() => {
    const processId = this.processId();
    if (!processId) return 0;

    const entity = this.#store.entityMap()[processId];
    return entity?.data?.filter(t => !t.completed).length ?? 0;
  });

  tryNavigate(): void {
    // If pending tasks exist, guard will show confirmation dialog
    this.#router.navigate(['/returns/search']);
  }

  completeTasks(): void {
    // Complete all tasks to allow navigation without prompt
    const processId = this.processId();
    if (!processId) return;

    const entity = this.#store.entityMap()[processId];
    const tasks = entity?.data ?? [];

    tasks.forEach(task => {
      if (!task.completed) {
        // Complete task via store update
        this.#store.updateTaskListItem({
          processId,
          taskListItem: { ...task, completed: true }
        });
      }
    });
  }
}
```

## Routing and Navigation

### Route Configuration

The library exports routes configured with the navigation guard:

```typescript
// libs/oms/feature/return-review/src/lib/routes.ts
import { Routes } from '@angular/router';
import { ReturnReviewComponent } from './return-review.component';
import { UncompletedTasksGuard } from './guards/uncompleted-tasks.guard';

export const routes: Routes = [
  {
    path: '',
    component: ReturnReviewComponent,
    canDeactivate: [UncompletedTasksGuard],
  },
];
```

**Key Features:**
- **Empty path**: Component loads at parent route path
- **Deactivation guard**: Prevents navigation with uncompleted tasks
- **Standalone component**: No module wrapper required

### Integration with OMS Workflow

The return review fits into the OMS workflow as follows:

```
Return Search → Return Details → Return Process → Return Review
   (search)        (select)         (process)        (confirm)
```

**Navigation Flow:**
1. User searches for receipt (`/returns/search`)
2. User selects items for return (`/returns/details/:receiptId`)
3. User processes return items (`/returns/process`)
4. System navigates to review (`/returns/review`)
5. User reviews tasks and optionally reprints receipt
6. User navigates away (with guard protection)

### Programmatic Navigation

```typescript
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-return-orchestrator',
  template: '...'
})
export class ReturnOrchestratorComponent {
  #router = inject(Router);

  completeReturnProcess(): void {
    // After completing return process steps
    this.#router.navigate(['/returns/review']);
  }

  exitReview(): void {
    // Navigate away from review
    // Guard will check for uncompleted tasks
    this.#router.navigate(['/returns/search']);
  }

  reopenProcess(): void {
    // Navigate back to process from review
    // Guard will check for uncompleted tasks
    this.#router.navigate(['/returns/process']);
  }
}
```

### Guard Behavior in Routing

The `UncompletedTasksGuard` integrates with Angular router lifecycle:

**Scenario 1: No Uncompleted Tasks**
```
User clicks back button
→ Guard checks uncompleted tasks
→ None found
→ Navigation proceeds immediately
→ User sees previous route
```

**Scenario 2: Uncompleted Tasks Exist**
```
User clicks back button
→ Guard checks uncompleted tasks
→ Tasks found
→ Confirmation dialog opens
→ User clicks "Zurück" (confirm)
→ Dialog closes
→ Navigation blocked
→ User remains on review page

OR

→ User clicks "Verlassen" (close)
→ Dialog closes
→ Navigation proceeds
→ User sees previous route
```

### Route Resolver Pattern (Future Enhancement)

Currently, the component relies on store state from previous routes. A future enhancement could use route resolvers:

```typescript
// Potential future enhancement
export const routes: Routes = [
  {
    path: '',
    component: ReturnReviewComponent,
    canDeactivate: [UncompletedTasksGuard],
    resolve: {
      tasks: ReturnTasksResolver,  // Ensures tasks are loaded
      receipts: ReturnReceiptsResolver  // Ensures receipts are available
    }
  },
];
```

This would guarantee data availability even if users navigate directly to the route.

## Architecture Notes

### Current Architecture

The library follows a component-based architecture with guard protection:

```
ReturnReviewComponent (orchestrator)
       ↓
├─→ ReturnReviewHeaderComponent (presentation)
│   └─→ InfoButtonComponent (UI)
│   └─→ NgIconComponent (UI)
├─→ ReturnTaskListComponent (shared feature)
│   └─→ [See @isa/oms/shared/task-list README]
├─→ PrintReceiptsService (printing)
├─→ ReturnProcessStore (state)
└─→ Tab Context (process isolation)

UncompletedTasksGuard (navigation protection)
       ↓
├─→ ReturnTaskListStore (task state)
├─→ ConfirmationDialog (UI)
└─→ Tab Context (process isolation)
```

### Design Patterns

#### 1. Container/Presenter Pattern

**Container:** `ReturnReviewComponent`
- Manages process ID via tab context
- Handles receipt printing logic
- Orchestrates child components
- Interacts with stores and services

**Presenter:** `ReturnReviewHeaderComponent`
- Pure presentation logic
- Emits events to parent
- No direct service dependencies (except icon provider)
- Receives all data via inputs (currently none)

**Benefits:**
- Clear separation of concerns
- Header component is highly reusable
- Easy to test in isolation
- Simple data flow

#### 2. Tab-Based Process Isolation

```typescript
// Each browser tab gets unique process context
processId = injectTabId();  // Signal<number | undefined>

// Guard also uses tab context
processId = injectTabId();  // Same signal, same value

// Store lookups use process ID
const entity = store.entityMap()[processId()];
```

**Benefits:**
- Multiple simultaneous return reviews in different tabs
- No cross-tab state interference
- Automatic cleanup when tab closes
- No manual process ID management

#### 3. Declarative Routing with Guards

```typescript
export const routes: Routes = [
  {
    path: '',
    component: ReturnReviewComponent,
    canDeactivate: [UncompletedTasksGuard],  // Declarative protection
  },
];
```

**Benefits:**
- Guard logic separate from component
- Reusable across routes
- Clear intent in route configuration
- Angular handles lifecycle automatically

#### 4. Delegation to Shared Components

```html
<oms-shared-return-task-list
  [appearance]="'review'"
></oms-shared-return-task-list>
```

**Benefits:**
- No duplication of task list logic
- Consistent task display across features
- Shared component handles complexity
- Review component stays focused

#### 5. Store-Based State Management

```typescript
// Component reads from store
const receiptIds = this.#returnProcessStore
  .entities()
  .filter((p) => p.processId === processId && p.returnReceipt?.id)
  .map((p) => p.returnReceipt!.id);
```

**Benefits:**
- Single source of truth
- Reactive updates
- Store managed elsewhere (return process feature)
- Component consumes, doesn't manage

### Known Architectural Considerations

#### 1. Header Component Output Pattern (Low Priority)

**Current State:**
```typescript
// ReturnReviewHeaderComponent
printReceipt = output<void>();

// ReturnReviewComponent
<oms-feature-return-review-header
  (printReceipt)="printReceipt()"
></oms-feature-return-review-header>
```

**TODO Comment:**
```typescript
// TODO: Kann direkt in der Komponente gehandled werden
```

**Issue:**
- Header emits event to parent
- Parent handles printing logic
- Unnecessary indirection for single-purpose button

**Proposed Refactoring:**
```typescript
// ReturnReviewHeaderComponent (future)
export class ReturnReviewHeaderComponent {
  #printReceiptsService = inject(PrintReceiptsService);
  #returnProcessStore = inject(ReturnProcessStore);
  processId = injectTabId();

  async printReceipt(): void {
    // Handle printing directly in header
    const processId = this.processId();
    if (processId) {
      const receiptIds = this.#returnProcessStore
        .entities()
        .filter((p) => p.processId === processId && p.returnReceipt?.id)
        .map((p) => p.returnReceipt!.id);

      await this.#printReceiptsService.printReturnReceipts({
        returnReceiptIds: receiptIds,
      });
    }
  }
}

// ReturnReviewComponent (future)
<oms-feature-return-review-header></oms-feature-return-review-header>
// No output binding needed
```

**Benefits:**
- Simpler component tree
- No unnecessary event propagation
- Header is self-contained
- Parent component simplified

**Impact:** Low - current pattern works, refactoring is optimization

#### 2. Guard State Duplication (Medium Priority)

**Current State:**
```typescript
// UncompletedTasksGuard
#returnTaskListStore = inject(ReturnTaskListStore);
processId = injectTabId();

uncompletedTaskListItems = computed(() => {
  const processId = this.processId();
  // ... filter logic
});

// Similar logic could exist in components
```

**Issue:**
- Guard recomputes uncompleted tasks
- Component might have similar logic
- Potential duplication of filtering logic
- Guard is stateful (uses computed signals)

**Proposed Enhancement:**
```typescript
// Create shared service
export class ReturnTaskValidator {
  #store = inject(ReturnTaskListStore);

  hasUncompletedTasks(processId: number): boolean {
    const entity = this.#store.entityMap()[processId];
    return entity?.data?.some(task => !task.completed) ?? false;
  }

  getUncompletedTasks(processId: number): ReceiptItemTaskListItem[] {
    const entity = this.#store.entityMap()[processId];
    return entity?.data?.filter(task => !task.completed) ?? [];
  }
}

// Guard uses service
export class UncompletedTasksGuard implements CanDeactivate<ReturnReviewComponent> {
  #validator = inject(ReturnTaskValidator);
  processId = injectTabId();

  canDeactivate(): boolean | Promise<boolean> {
    const processId = this.processId();
    if (!processId || !this.#validator.hasUncompletedTasks(processId)) {
      return true;
    }
    return this.openDialog();
  }
}
```

**Benefits:**
- Centralized task validation logic
- Guard becomes stateless function
- Reusable across components and guards
- Easier to test

**Impact:** Medium - improves maintainability and testability

#### 3. Print Failure User Feedback (Medium Priority)

**Current State:**
```typescript
async printReceipt() {
  // ...
  await this.#printReceiptsService.printReturnReceipts({
    returnReceiptIds: receiptIds,
  });
  // No error handling, no user feedback
}
```

**Issue:**
- Print service may fail silently
- User doesn't know if printing succeeded
- No retry mechanism
- No loading indicator

**Proposed Enhancement:**
```typescript
async printReceipt() {
  const processId = this.processId();
  if (!processId) return;

  const receiptIds = this.#returnProcessStore
    .entities()
    .filter((p) => p.processId === processId && p.returnReceipt?.id)
    .map((p) => p.returnReceipt!.id);

  try {
    // Show loading state
    this.isPrinting.set(true);

    await this.#printReceiptsService.printReturnReceipts({
      returnReceiptIds: receiptIds,
    });

    // Show success toast
    this.#toastService.success('Belege erfolgreich gedruckt');
  } catch (error) {
    // Show error toast with retry option
    this.#toastService.error('Drucken fehlgeschlagen', {
      action: {
        label: 'Erneut versuchen',
        handler: () => this.printReceipt()
      }
    });
  } finally {
    this.isPrinting.set(false);
  }
}
```

**Benefits:**
- Clear user feedback
- Error recovery mechanism
- Loading state prevents double-clicks
- Better UX

**Impact:** Medium - improves user experience and error handling

#### 4. Direct Store Entity Access (Low Priority)

**Current State:**
```typescript
const receiptIds = this.#returnProcessStore
  .entities()
  .filter((p) => p.processId === processId && p.returnReceipt?.id)
  .map((p) => p.returnReceipt!.id);
```

**Observation:**
- Component directly accesses store entities
- Filter logic embedded in component
- Non-null assertion (`!`) indicates assumed structure
- No type guard for returnReceipt existence

**Proposed Enhancement:**
```typescript
// Add selector to store or create service
export class ReturnProcessStore extends SignalStore {
  // ... existing store code

  getReceiptIdsForProcess(processId: number): number[] {
    return this.entities()
      .filter((p) =>
        p.processId === processId &&
        p.returnReceipt?.id !== undefined
      )
      .map((p) => p.returnReceipt.id);
  }
}

// Component uses selector
const receiptIds = this.#returnProcessStore.getReceiptIdsForProcess(processId);
```

**Benefits:**
- Encapsulates query logic in store
- Type-safe, no non-null assertions
- Reusable across components
- Easier to test and maintain

**Impact:** Low - current pattern works, enhancement improves encapsulation

### Performance Considerations

#### 1. Tab-Based Process Isolation
- O(1) store lookup via process ID
- No iteration over all processes
- Isolated state prevents memory leaks
- Efficient cleanup when tab closes

#### 2. Delegated Task Rendering
- Task list component handles virtualization
- Review component has minimal rendering logic
- Deferred rendering in task list reduces initial load
- OnPush change detection minimizes checks

#### 3. Guard Computation
```typescript
uncompletedTaskListItems = computed(() => {
  // Only recomputes when dependencies change
  // processId signal or store entity signal
});
```
- Computed signals cache results
- Only recalculates when process ID or tasks change
- Efficient for navigation checks

#### 4. Store Entity Access
```typescript
this.#returnProcessStore.entities()
```
- Direct array access (no observable subscription)
- Filters executed on-demand
- No memory held by subscriptions
- Component cleanup is simple

### Future Enhancements

Potential improvements identified:

1. **Loading States** - Add loading indicator during receipt printing
2. **Success Feedback** - Toast notification on successful print
3. **Error Handling** - User-visible error messages with retry option
4. **Print Preview** - Show preview before printing all receipts
5. **Selective Printing** - Allow printing individual receipts
6. **Task Summary** - Show completion statistics (e.g., "3 of 5 tasks completed")
7. **Export Functionality** - Export task list to PDF or CSV
8. **Time Tracking** - Display time taken for return process
9. **Header Refactoring** - Move print logic into header component
10. **Shared Validator** - Extract task validation to shared service
11. **Route Resolver** - Add resolver to ensure data availability
12. **Accessibility** - ARIA labels and keyboard navigation for guard dialog

## Dependencies

### Required Libraries

#### Angular Core
- `@angular/core` - Angular framework, signals, dependency injection
- `@angular/common` - Common Angular utilities (Location service)

#### OMS Domain

**Feature Libraries:**
- `@isa/oms/shared/task-list` - Task list component in review mode
  - `ReturnTaskListComponent` - Main task display component

**Data Access:**
- `@isa/oms/data-access` - Data services and stores
  - `PrintReceiptsService` - Receipt printing operations
  - `ReturnProcessStore` - Return process state management
  - `ReturnTaskListStore` - Task list state management
  - `ReceiptItemTaskListItem` - Task data model

#### UI Components
- `@isa/ui/buttons` - Button components
  - `InfoButtonComponent` - Info button with icon and label
- `@isa/ui/dialog` - Dialog components
  - `injectConfirmationDialog()` - Confirmation dialog service

#### Core Services
- `@isa/core/tabs` - Tab context management
  - `injectTabId()` - Process ID from tab context

#### Icons
- `@isa/icons` - ISA icon set
  - `isaActionPrinter` - Printer icon
- `@ng-icons/core` - Icon component infrastructure
  - `NgIconComponent` - Icon rendering component
  - `provideIcons()` - Icon provider function

#### State Management
- `@ngrx/signals` - Signal-based state management (indirect via stores)

#### Utilities
- `rxjs` - Reactive programming
  - `firstValueFrom` - Promise conversion for observables

### Development Dependencies

- `jest` - Testing framework
- `@nx/jest` - Nx Jest integration
- `jest-preset-angular` - Angular-specific Jest configuration
- `@angular/compiler-cli` - Angular compiler
- `typescript` - TypeScript compiler

### Path Alias

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

**Example:**
```typescript
import { routes } from '@isa/oms/feature/return-review';
```

## Testing

The library uses **Jest** with **Angular Testing Utilities** for testing.

### Running Tests

```bash
# Run tests for this library
npx nx test return-review --skip-nx-cache

# Run tests with coverage
npx nx test return-review --code-coverage --skip-nx-cache

# Run tests in watch mode
npx nx test return-review --watch

# Run specific test file
npx nx test return-review --testFile=src/lib/return-review.component.spec.ts --skip-nx-cache
```

### Test Structure (Recommended)

#### Testing ReturnReviewComponent

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { ReturnReviewComponent } from './return-review.component';
import { PrintReceiptsService, ReturnProcessStore } from '@isa/oms/data-access';
import { provideLocationMocks } from '@angular/common/testing';
import { signal } from '@angular/core';

describe('ReturnReviewComponent', () => {
  let component: ReturnReviewComponent;
  let fixture: ComponentFixture<ReturnReviewComponent>;
  let mockPrintService: jest.Mocked<PrintReceiptsService>;
  let mockStore: jest.Mocked<ReturnProcessStore>;

  beforeEach(async () => {
    mockPrintService = {
      printReturnReceipts: jest.fn(() => Promise.resolve())
    } as any;

    mockStore = {
      entities: jest.fn(() => [])
    } as any;

    await TestBed.configureTestingModule({
      imports: [ReturnReviewComponent],
      providers: [
        { provide: PrintReceiptsService, useValue: mockPrintService },
        { provide: ReturnProcessStore, useValue: mockStore },
        provideLocationMocks(),
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(ReturnReviewComponent);
    component = fixture.componentInstance;
  });

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

  it('should print receipts when printReceipt is called', async () => {
    // Arrange
    const mockProcessId = 123;
    component['processId'] = signal(mockProcessId);

    const mockProcesses = [
      { processId: 123, returnReceipt: { id: 1 } },
      { processId: 123, returnReceipt: { id: 2 } },
      { processId: 456, returnReceipt: { id: 3 } }, // Different process
    ];
    mockStore.entities.mockReturnValue(mockProcesses as any);

    // Act
    await component.printReceipt();

    // Assert
    expect(mockPrintService.printReturnReceipts).toHaveBeenCalledWith({
      returnReceiptIds: [1, 2]
    });
  });

  it('should filter out processes without return receipt ID', async () => {
    // Arrange
    const mockProcessId = 123;
    component['processId'] = signal(mockProcessId);

    const mockProcesses = [
      { processId: 123, returnReceipt: { id: 1 } },
      { processId: 123, returnReceipt: null }, // No receipt
      { processId: 123 }, // No returnReceipt property
    ];
    mockStore.entities.mockReturnValue(mockProcesses as any);

    // Act
    await component.printReceipt();

    // Assert
    expect(mockPrintService.printReturnReceipts).toHaveBeenCalledWith({
      returnReceiptIds: [1]
    });
  });

  it('should not print if process ID is undefined', async () => {
    // Arrange
    component['processId'] = signal(undefined);

    // Act
    await component.printReceipt();

    // Assert
    expect(mockPrintService.printReturnReceipts).not.toHaveBeenCalled();
  });
});
```

#### Testing ReturnReviewHeaderComponent

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach } from '@jest/globals';
import { ReturnReviewHeaderComponent } from './return-review-header.component';

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ReturnReviewHeaderComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(ReturnReviewHeaderComponent);
    component = fixture.componentInstance;
  });

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

  it('should display success message', () => {
    // Arrange & Act
    fixture.detectChanges();

    // Assert
    const heading = fixture.nativeElement.querySelector('h2');
    expect(heading?.textContent).toContain('Die Rückgabe war erfolgreich!');
  });

  it('should emit printReceipt event when button clicked', () => {
    // Arrange
    let emitted = false;
    component.printReceipt.subscribe(() => {
      emitted = true;
    });

    fixture.detectChanges();

    // Act
    const button = fixture.nativeElement.querySelector(
      '[data-what="button"][data-which="print-receipt"]'
    );
    button?.click();

    // Assert
    expect(emitted).toBe(true);
  });

  it('should have correct E2E testing attributes', () => {
    // Arrange & Act
    fixture.detectChanges();

    // Assert
    const button = fixture.nativeElement.querySelector(
      '[data-what="button"][data-which="print-receipt"]'
    );
    expect(button).toBeTruthy();
    expect(button.getAttribute('data-what')).toBe('button');
    expect(button.getAttribute('data-which')).toBe('print-receipt');
  });

  it('should display printer icon', () => {
    // Arrange & Act
    fixture.detectChanges();

    // Assert
    const icon = fixture.nativeElement.querySelector('ng-icon');
    expect(icon).toBeTruthy();
    expect(icon.getAttribute('name')).toBe('isaActionPrinter');
  });
});
```

#### Testing UncompletedTasksGuard

```typescript
import { TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { UncompletedTasksGuard } from './uncompleted-tasks.guard';
import { ReturnTaskListStore } from '@isa/oms/data-access';
import { signal } from '@angular/core';
import { of } from 'rxjs';

describe('UncompletedTasksGuard', () => {
  let guard: UncompletedTasksGuard;
  let mockStore: jest.Mocked<ReturnTaskListStore>;
  let mockDialog: jest.Mock;

  beforeEach(() => {
    mockStore = {
      entityMap: jest.fn(() => ({}))
    } as any;

    mockDialog = jest.fn();

    TestBed.configureTestingModule({
      providers: [
        UncompletedTasksGuard,
        { provide: ReturnTaskListStore, useValue: mockStore },
      ]
    });

    guard = TestBed.inject(UncompletedTasksGuard);
    guard['processId'] = signal(123);
    guard['#confirmationDialog'] = mockDialog as any;
  });

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

  it('should allow navigation when no uncompleted tasks', () => {
    // Arrange
    const mockTasks = [
      { id: 1, completed: true },
      { id: 2, completed: true }
    ];
    mockStore.entityMap.mockReturnValue({
      123: { id: 123, data: mockTasks, status: 'Success' }
    } as any);

    // Act
    const result = guard.canDeactivate();

    // Assert
    expect(result).toBe(true);
    expect(mockDialog).not.toHaveBeenCalled();
  });

  it('should block navigation and show dialog when uncompleted tasks exist', async () => {
    // Arrange
    const mockTasks = [
      { id: 1, completed: true },
      { id: 2, completed: false }  // Uncompleted task
    ];
    mockStore.entityMap.mockReturnValue({
      123: { id: 123, data: mockTasks, status: 'Success' }
    } as any);

    mockDialog.mockReturnValue({
      closed: of({ confirmed: true })  // User clicks "Zurück"
    });

    // Act
    const result = await guard.canDeactivate();

    // Assert
    expect(mockDialog).toHaveBeenCalled();
    expect(result).toBe(false);  // Block navigation
  });

  it('should allow navigation if user confirms leaving', async () => {
    // Arrange
    const mockTasks = [
      { id: 1, completed: false }
    ];
    mockStore.entityMap.mockReturnValue({
      123: { id: 123, data: mockTasks, status: 'Success' }
    } as any);

    mockDialog.mockReturnValue({
      closed: of({ confirmed: false })  // User clicks "Verlassen"
    });

    // Act
    const result = await guard.canDeactivate();

    // Assert
    expect(result).toBe(true);  // Allow navigation
  });

  it('should handle missing process ID', () => {
    // Arrange
    guard['processId'] = signal(undefined);

    // Act
    const result = guard.canDeactivate();

    // Assert
    expect(result).toBe(true);
  });

  it('should handle missing task data', () => {
    // Arrange
    mockStore.entityMap.mockReturnValue({
      123: { id: 123, data: undefined, status: 'Pending' }
    } as any);

    // Act
    const result = guard.canDeactivate();

    // Assert
    expect(result).toBe(true);
  });
});
```

### Test Coverage Goals

- **Component Logic**: 80%+ coverage
- **Guard Logic**: 90%+ coverage (critical for data integrity)
- **Template Rendering**: Test all conditional paths
- **User Interactions**: Test all button clicks and events
- **Store Integration**: Mock store methods and verify calls
- **E2E Attributes**: Verify all data-what/data-which attributes exist

### E2E Testing

The component includes E2E testing attributes for automated testing:

```typescript
// Example Playwright test
import { test, expect } from '@playwright/test';

test.describe('Return Review', () => {
  test('should display success message', async ({ page }) => {
    // Navigate to review page
    await page.goto('/returns/review');

    // Verify success message
    await expect(page.locator('h2')).toContainText('Die Rückgabe war erfolgreich!');
  });

  test('should print receipt on button click', async ({ page }) => {
    await page.goto('/returns/review');

    // Monitor for print dialog or download
    const [download] = await Promise.all([
      page.waitForEvent('download'),
      page.click('[data-what="button"][data-which="print-receipt"]')
    ]);

    expect(download).toBeTruthy();
  });

  test('should show task list in review mode', async ({ page }) => {
    await page.goto('/returns/review');

    // Verify task list component is present
    await expect(
      page.locator('oms-shared-return-task-list')
    ).toBeVisible();
  });

  test('should prompt when navigating with uncompleted tasks', async ({ page }) => {
    await page.goto('/returns/review');

    // Assume tasks exist and are not completed
    page.on('dialog', async (dialog) => {
      expect(dialog.message()).toContain('Aufgaben erledigen');
      await dialog.dismiss(); // Click "Verlassen"
    });

    // Try to navigate away
    await page.click('[data-which="back-button"]');

    // Should show dialog (handled by event listener)
  });
});
```

## Best Practices

### 1. Rely on Tab Context for Process ID

```typescript
// ✅ GOOD: Component automatically uses tab-based process ID
// No manual process ID management needed
<oms-feature-return-review></oms-feature-return-review>

// ❌ BAD: Trying to manually pass process ID
// Component handles this internally via injectTabId()
<oms-feature-return-review [processId]="123"></oms-feature-return-review>
```

### 2. Trust the Navigation Guard

```typescript
// ✅ GOOD: Guard automatically checks for uncompleted tasks
// No additional confirmation logic needed in components

// ❌ BAD: Duplicate confirmation logic
async navigateAway() {
  const hasTasks = this.checkUncompletedTasks(); // Guard already does this
  if (hasTasks) {
    const confirm = await this.showDialog();
    if (!confirm) return;
  }
  this.router.navigate(['/search']);
}
```

### 3. Let Task List Handle Display

```typescript
// ✅ GOOD: Use shared task list component
<oms-shared-return-task-list
  [appearance]="'review'"
></oms-shared-return-task-list>

// ❌ BAD: Duplicate task list implementation
<div *ngFor="let task of tasks()">
  <!-- Custom task rendering duplicates shared component -->
</div>
```

### 4. Use E2E Attributes Consistently

```typescript
// ✅ GOOD: Include data attributes for testing
<button
  (click)="handleAction()"
  data-what="button"
  data-which="custom-action"
>
  Custom Action
</button>

// ❌ BAD: No testing attributes
<button (click)="handleAction()">
  Custom Action
</button>
```

### 5. Handle Print Failures Gracefully

```typescript
// ✅ GOOD: Wrap print calls in try-catch
try {
  await this.printReceipt();
  // Consider adding success feedback
} catch (error) {
  console.error('Print failed', error);
  // Consider showing error message to user
}

// ❌ BAD: Assume printing always succeeds
await this.printReceipt(); // No error handling
```

### 6. Respect Component Hierarchy

```typescript
// ✅ GOOD: Use component as designed
<oms-feature-return-review-header
  (printReceipt)="handlePrint()"
></oms-feature-return-review-header>

// ❌ BAD: Bypass component and access services directly
// Breaks encapsulation
const header = this.headerComponent;
header.#printReceiptsService.printReturnReceipts(...);
```

### 7. Navigate After Process Completion

```typescript
// ✅ GOOD: Navigate to review as final step
async completeReturn() {
  await this.returnService.complete();
  this.router.navigate(['/returns/review']);
}

// ❌ BAD: Navigate before process completes
this.router.navigate(['/returns/review']); // May show incomplete state
await this.returnService.complete();
```

### 8. Style with CSS Classes, Not Inline Styles

```scss
// ✅ GOOD: Override via CSS classes
oms-feature-return-review-header {
  padding: 2rem;
  background-color: var(--custom-bg);
}

// ❌ BAD: Inline styles in template
<oms-feature-return-review-header
  style="padding: 2rem;"
></oms-feature-return-review-header>
```

### 9. Handle Missing Process ID

```typescript
// ✅ GOOD: Check for process ID before operations
async printReceipt() {
  const processId = this.processId();
  if (!processId) {
    console.warn('No process ID available');
    return;
  }
  // Continue with printing
}

// ❌ BAD: Assume process ID always exists
async printReceipt() {
  const processId = this.processId()!; // Non-null assertion
  // Will fail if processId is undefined
}
```

### 10. Test Guard Behavior

```typescript
// ✅ GOOD: Test all guard scenarios
it('should allow navigation without tasks', () => {
  expect(guard.canDeactivate()).toBe(true);
});

it('should block navigation with tasks', async () => {
  // Setup uncompleted tasks
  const result = await guard.canDeactivate();
  expect(result).toBe(false);
});

// ❌ BAD: Only test happy path
it('should work', () => {
  expect(guard.canDeactivate()).toBe(true);
});
```

### 11. Consider Accessibility

```typescript
// ✅ GOOD: Add ARIA labels and roles
<div role="status" aria-live="polite">
  <h2>Die Rückgabe war erfolgreich!</h2>
</div>

<button
  aria-label="Rückgabe Bestätigung erneut drucken"
  (click)="printReceipt()"
>
  <ng-icon name="isaActionPrinter"></ng-icon>
</button>

// ⚠️ CURRENT STATE: Limited accessibility
// Consider enhancement for screen reader support
```

### 12. Monitor Store State for Advanced Features

```typescript
// ✅ GOOD: Use computed signals for derived data
import { computed } from '@angular/core';

receiptCount = computed(() => {
  const processId = this.processId();
  if (!processId) return 0;

  return this.#returnProcessStore
    .entities()
    .filter(p => p.processId === processId && p.returnReceipt?.id)
    .length;
});

// ❌ BAD: Direct store manipulation
this.#returnProcessStore.entities()[0] = newData; // Breaks store contract
```

---

## License

Internal ISA Frontend library - not for external distribution.
