Files
Lorenz Hilpert 1784e08ce6 chore: update project configurations to skip CI for specific libraries
Added "skip:ci" tag to multiple project configurations to prevent CI runs
for certain libraries. This change affects the following libraries:
crm-feature-customer-card-transactions, crm-feature-customer-loyalty-cards,
oms-data-access, oms-feature-return-details, oms-feature-return-process,
oms-feature-return-summary, remission-data-access, remission-feature-remission-list,
remission-feature-remission-return-receipt-details, remission-feature-remission-return-receipt-list,
remission-shared-remission-start-dialog, remission-shared-return-receipt-actions,
shared-address, shared-delivery, ui-carousel, and ui-dialog.

Also updated CI command in package.json to exclude tests with the "skip:ci" tag.
2025-11-20 17:24:35 +01:00
..
2025-08-14 16:47:48 +02:00

@isa/oms/feature/return-summary

A comprehensive Angular feature library for displaying and confirming return process summaries in the Order Management System (OMS). This library provides a review interface where users can inspect all items being returned, verify return details, and complete the return process with receipt printing.

Overview

The Return Summary feature library is a critical component in the OMS return workflow. It displays all items selected for return in a given process, shows their return conditions and reasons, and provides a confirmation interface to complete the return and generate return receipts. The library integrates tightly with the return process state management and handles the final step before items are officially returned to inventory.

Table of Contents

Features

  • Return Process Summary - Displays all items in a return process with complete details
  • Product Information Display - Shows product names, images, and identification
  • Return Details Visualization - Presents item condition, return reason, and customer comments
  • Eligibility Validation - Verifies each item is eligible for return before processing
  • Process Completion - Completes return process and generates return receipts
  • Receipt Printing - Automatically triggers receipt printing after successful return
  • Navigation Integration - Seamless routing to review screens after completion
  • Loading States - Visual feedback during asynchronous operations
  • Error Handling - Comprehensive error logging and user feedback
  • E2E Testing Support - Full data-* attribute coverage for automated testing
  • Responsive Design - Tailwind CSS-based layout with grid system
  • OnPush Change Detection - Optimized performance with reactive signals

Quick Start

1. Import Routes

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

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

2. Navigate to Summary

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

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

  async goToSummary(tabId: number): Promise<void> {
    await this.#router.navigate(['/return', tabId, 'summary']);
  }
}

3. Process Completion Flow

The component automatically handles:

  • Loading return process items from the store
  • Displaying return details for each item
  • Completing the return when user confirms
  • Navigating to review screen after success

Core Concepts

Return Process Workflow

The return summary is the penultimate step in the return workflow:

  1. Return Search - Find receipt and items to return
  2. Return Details Collection - Gather item condition, reason, comments
  3. Return Summary (This Library) - Review and confirm all details
  4. Return Review - Display completed return receipts

Return Process Data Structure

Each return process contains comprehensive information:

interface ReturnProcess {
  id: number;                           // Unique return process ID
  processId: number;                    // Parent process ID (groups multiple returns)
  receiptId: number;                    // Original receipt ID
  receiptItem: ReceiptItem;             // Item being returned with product details
  receiptDate: string | undefined;      // Original purchase date
  answers: Record<string, unknown>;     // Return questionnaire answers
  productCategory: string;              // Product category (affects questions)
  quantity: number;                     // Quantity being returned
  returnReceipt?: Receipt;              // Generated return receipt (set after completion)
}

Eligibility States

Items are validated for return eligibility:

enum EligibleForReturnState {
  Eligible = 'eligible',              // Can be returned
  NotEligible = 'ineligible',         // Cannot be returned
  Conditional = 'conditional'         // May be returned with conditions
}

interface EligibleForReturn {
  state: EligibleForReturnState;
  reason?: string;                     // Explanation if not eligible
}

Return Information Display

Return details are collected from the return process answers:

  • Item Condition - Physical state of the item (e.g., "Unopened", "Used")
  • Return Reason - Why the customer is returning (e.g., "Defective", "Wrong item")
  • Return Details - Specific damage or issues (e.g., "Case damaged", "Missing accessories")
  • Other Product - If wrong item was delivered, shows the received item's EAN
  • Comment - Free-text customer explanation

API Reference

ReturnSummaryComponent

Main component for displaying the return summary and handling process completion.

Component Metadata

@Component({
  selector: 'oms-feature-return-summary',
  templateUrl: './return-summary.component.html',
  styleUrls: ['./return-summary.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [ReturnSummaryItemComponent, ButtonComponent, NgIcon],
  providers: [provideIcons({ isaActionChevronLeft })]
})

Properties

processId: Signal<number>

The active tab/process ID extracted from the route.

const processId = this.processId();  // e.g., 42
returnProcesses: Signal<ReturnProcess[]>

Computed signal containing all return processes for the current processId.

const items = this.returnProcesses();  // Array of ReturnProcess objects
console.log(`${items.length} items to return`);
returnItemsAndPrintReciptStatus: WritableSignal<undefined | 'pending' | 'success' | 'error'>

Tracks the current status of the return and print operation.

States:

  • undefined - Initial state, no operation started
  • 'pending' - Operation in progress
  • 'success' - Operation completed successfully
  • 'error' - Operation failed
const status = this.returnItemsAndPrintReciptStatus();
if (status === 'pending') {
  console.log('Processing return...');
}
returnItemsAndPrintReciptStatusPending: Signal<boolean>

Computed signal that returns true when the operation is pending.

const isProcessing = this.returnItemsAndPrintReciptStatusPending();
// Used to disable buttons during processing
location: Location

Angular Location service for navigation (back button functionality).

this.location.back();  // Navigate to previous page

Methods

async returnItemsAndPrintRecipt(): Promise<void>

Completes the return process, generates return receipts, and navigates to the review screen.

Workflow:

  1. Checks if operation is already in progress (prevents double-submit)
  2. Sets status to 'pending'
  3. Calls ReturnProcessService.completeReturnProcess() with all return processes
  4. Updates status based on success/failure
  5. Stores completed receipts in ReturnProcessStore
  6. Navigates to '../review' route on success
  7. Logs errors if operation fails

Example:

await component.returnItemsAndPrintRecipt();
// After successful completion:
// - Return receipts are created
// - State is updated with completed receipts
// - User is navigated to review screen

Error Handling:

  • Logs warnings if called while already pending
  • Logs errors with full context on failure
  • Sets status to 'error' on exception
  • Status remains visible to user (success button hidden, error stays pending)

ReturnSummaryItemComponent

Displays a single return process item with product details and return information.

Component Metadata

@Component({
  selector: 'oms-feature-return-summary-item',
  templateUrl: './return-summary-item.component.html',
  styleUrls: ['./return-summary-item.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [ReturnProductInfoComponent, NgIcon, IconButtonComponent],
  providers: [provideIcons({ isaActionChevronRight, isaActionEdit, isaActionClose })],
  host: {
    'data-what': 'list-item',
    'data-which': 'return-process-item',
    '[attr.data-receipt-id]': 'returnProcess()?.receiptId',
    '[attr.data-return-item-id]': 'returnProcess()?.returnItem?.id'
  }
})

Inputs

returnProcess: InputSignal<ReturnProcess> (required)

The return process object containing all information about the item being returned.

<oms-feature-return-summary-item
  [returnProcess]="returnProcessItem"
></oms-feature-return-summary-item>
returnItemsAndPrintReciptPending: InputSignal<boolean> (optional, default: false)

Whether the parent completion operation is in progress. Used to disable edit button during processing.

<oms-feature-return-summary-item
  [returnProcess]="item"
  [returnItemsAndPrintReciptPending]="isPending()"
></oms-feature-return-summary-item>

Properties

eligibleForReturn: Signal<EligibleForReturn | undefined>

Computes the return eligibility for the current return process.

const eligibility = this.eligibleForReturn();
if (eligibility?.state === EligibleForReturnState.NotEligible) {
  console.log(`Cannot return: ${eligibility.reason}`);
}
returnInfos: Signal<string[]>

Computes an array of human-readable return information strings.

Includes:

  • Item condition (except for Tolino products)
  • Return reason (except for Tolino products)
  • Mapped return details (damage, missing items, etc.)
  • Incorrect product delivered (shows EAN)
  • Customer comments

Special Handling:

  • Tolino products skip condition/reason display
  • Duplicates are automatically removed
  • Empty values are filtered out
const infos = this.returnInfos();
// Example output:
// [
//   "Opened and used",
//   "Defective",
//   "Display damaged: Yes",
//   "Missing accessories: Power cable",
//   "Screen has scratches"
// ]
location: Location

Angular Location service for back navigation (edit button).

this.location.back();  // Returns to return details screen

Usage Examples

Basic Implementation

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

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

Programmatic Navigation

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

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

  async proceedToSummary(): Promise<void> {
    // Navigate to summary for tab ID 42
    await this.#router.navigate(['/return', 42, 'summary']);
  }
}

Handling Return Completion

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

@Component({
  selector: 'app-return-completion-handler',
  template: '...'
})
export class ReturnCompletionHandlerComponent implements OnInit {
  #returnStore = inject(ReturnProcessStore);
  #router = inject(Router);
  #route = inject(ActivatedRoute);

  ngOnInit(): void {
    // After completion, user is redirected to '../review'
    // which resolves to '/return/:tabId/review'

    // Access completed receipts from the store
    const completedReceipts = this.#returnStore.entities()
      .filter(p => p.returnReceipt !== undefined)
      .map(p => p.returnReceipt);

    console.log(`${completedReceipts.length} returns completed`);
  }
}

Custom Navigation Flow

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

@Component({
  selector: 'app-custom-return-flow',
  template: `
    <oms-feature-return-summary></oms-feature-return-summary>
    <button (click)="cancelReturn()">Cancel</button>
  `
})
export class CustomReturnFlowComponent {
  #router = inject(Router);
  #route = inject(ActivatedRoute);
  #location = inject(Location);

  cancelReturn(): void {
    // Option 1: Navigate back
    this.#location.back();

    // Option 2: Navigate to specific route
    this.#router.navigate(['/return/search']);

    // Option 3: Navigate relative to current route
    this.#router.navigate(['../../search'], {
      relativeTo: this.#route
    });
  }
}

Error Handling

import { Component, inject, effect } from '@angular/core';
import { ReturnSummaryComponent } from '@isa/oms/feature/return-summary';
import { ToastService } from '@isa/ui/notifications';

@Component({
  selector: 'app-return-with-error-handling',
  template: `
    <oms-feature-return-summary></oms-feature-return-summary>
  `
})
export class ReturnWithErrorHandlingComponent {
  #toastService = inject(ToastService);
  returnSummary = inject(ReturnSummaryComponent);

  constructor() {
    // Monitor return status and show notifications
    effect(() => {
      const status = this.returnSummary.returnItemsAndPrintReciptStatus();

      if (status === 'success') {
        this.#toastService.success('Return completed successfully');
      } else if (status === 'error') {
        this.#toastService.error('Failed to complete return. Please try again.');
      }
    });
  }
}

Accessing Return Process Data

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

@Component({
  selector: 'app-return-summary-analytics',
  template: '...'
})
export class ReturnSummaryAnalyticsComponent {
  #returnStore = inject(ReturnProcessStore);
  processId = injectTabId();

  // Get all returns for current process
  currentReturns = computed(() => {
    const pid = this.processId();
    return this.#returnStore.entities()
      .filter(p => p.processId === pid);
  });

  // Calculate total items being returned
  totalItems = computed(() => {
    return this.currentReturns()
      .reduce((sum, p) => sum + (p.quantity || 1), 0);
  });

  // Group by product category
  byCategory = computed(() => {
    const returns = this.currentReturns();
    return returns.reduce((acc, p) => {
      const category = p.productCategory || 'Unknown';
      acc[category] = (acc[category] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);
  });
}

Routing and Navigation

Route Configuration

The library exports a routes constant that defines its routing structure:

// libs/oms/feature/return-summary/src/lib/routes.ts
export const routes: Routes = [
  {
    path: '',
    component: ReturnSummaryComponent
  },
  {
    path: 'review',
    loadChildren: () =>
      import('@isa/oms/feature/return-review').then(feat => feat.routes)
  }
];

Integration in Application

Parent Route Setup:

// In app routing or OMS feature routing
const omsRoutes: Routes = [
  {
    path: 'return/:tabId',
    children: [
      {
        path: 'search',
        loadChildren: () => import('@isa/oms/feature/return-search')
          .then(m => m.routes)
      },
      {
        path: 'details',
        loadChildren: () => import('@isa/oms/feature/return-details')
          .then(m => m.routes)
      },
      {
        path: 'summary',
        loadChildren: () => import('@isa/oms/feature/return-summary')
          .then(m => m.routes)
      }
    ]
  }
];

URL Structure

  • Summary View: /return/:tabId/summary

    • Example: /return/42/summary
  • Review View (after completion): /return/:tabId/summary/review

    • Example: /return/42/summary/review
    • Loads @isa/oms/feature/return-review lazily

Navigation Flow

┌─────────────────┐
│ Return Search   │
│ /return/:id     │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Return Details  │
│ /return/:id/... │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Return Summary  │◄────── You are here
│ /return/:id/    │        (This Library)
│      summary    │
└────────┬────────┘
         │ (Complete Return)
         ▼
┌─────────────────┐
│ Return Review   │
│ /return/:id/    │
│  summary/review │
└─────────────────┘

Back Navigation

Both components use Angular's Location service for back navigation:

import { Location } from '@angular/common';

@Component({ ... })
class ReturnSummaryComponent {
  location = inject(Location);

  goBack(): void {
    this.location.back();  // Returns to previous page in history
  }
}

Use Cases:

  • Back Button (ReturnSummaryComponent): Returns to return details collection screen
  • Edit Button (ReturnSummaryItemComponent): Returns to edit specific item details

Relative Navigation

The component uses relative navigation for the review route:

await this.#router.navigate(['../', 'review'], {
  relativeTo: this.#activatedRoute
});

// From: /return/42/summary
// To:   /return/42/summary/review

Component Architecture

Component Hierarchy

ReturnSummaryComponent
├── Back Button (navigates to previous page)
├── Heading ("Zusammenfassung überprüfen")
├── Return Items Container
│   └── ReturnSummaryItemComponent (for each return process)
│       ├── ReturnProductInfoComponent (product display)
│       ├── Return Information List (conditions, reasons, etc.)
│       └── Edit Button (navigates back to edit)
└── Complete Button ("Artikel zurückgeben und drucken")

Standalone Component Pattern

Both components use Angular's standalone component architecture:

@Component({
  selector: 'oms-feature-return-summary',
  imports: [
    ReturnSummaryItemComponent,    // Child component
    ButtonComponent,               // UI library
    NgIcon                         // Icon library
  ],
  providers: [
    provideIcons({ isaActionChevronLeft })  // Icon registration
  ]
})

Benefits:

  • No NgModule required
  • Explicit dependency management
  • Better tree-shaking
  • Easier testing

Change Detection Strategy

Both components use OnPush change detection for optimal performance:

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})

Implications:

  • Components only re-render when:
    • Input signals change
    • Computed signals update
    • Async events trigger (button clicks)
  • Better performance with large lists
  • Requires reactive patterns (signals, observables)

Styling Architecture

Host Styling:

// return-summary.component.scss
:host {
  @apply grid grid-flow-row gap-6 justify-stretch mt-6 relative;
}

Item Styling:

// return-summary-item.component.scss
:host {
  @apply grid grid-cols-[1fr,1fr,auto] p-6 text-isa-secondary-900 items-center;
}

Layout Structure:

  • Summary uses vertical grid layout (grid-flow-row)
  • Items use 3-column grid: [product info | return details | edit button]
  • Tailwind utility classes for spacing and alignment
  • ISA design system colors and typography

E2E Testing Attributes

Complete data attribute coverage for automated testing:

ReturnSummaryComponent:

<!-- Heading -->
<h1 data-what="heading" data-which="return-summary-title">

<!-- Container -->
<div data-what="container" data-which="return-items-list">

<!-- List Items -->
<oms-feature-return-summary-item
  data-what="list-item"
  data-which="return-process-item"
  [attr.data-item-id]="item.id"
  [attr.data-item-category]="item.productCategory">

<!-- Complete Button -->
<button data-what="button" data-which="return-and-print">

ReturnSummaryItemComponent:

<!-- Host attributes (dynamic) -->
<component
  data-what="list-item"
  data-which="return-process-item"
  [attr.data-receipt-id]="returnProcess()?.receiptId"
  [attr.data-return-item-id]="returnProcess()?.returnItem?.id">

<!-- Return info list -->
<ul data-what="list" data-which="return-infos">
  <li data-what="list-item"
      data-which="return-info"
      [attr.data-return-info]="returnInfo"
      [attr.data-info-index]="$index">

<!-- Edit button -->
<button data-what="button" data-which="edit-return-item">

State Management

ReturnProcessStore Integration

The component relies on NgRx Signals-based state management:

import { ReturnProcessStore } from '@isa/oms/data-access';

@Component({ ... })
class ReturnSummaryComponent {
  #returnProcessStore = inject(ReturnProcessStore);

  // Reactive query: filters entities by processId
  returnProcesses = computed<ReturnProcess[]>(() => {
    const processId = this.processId();
    return this.#returnProcessStore
      .entities()
      .filter(p => p.processId === processId);
  });
}

Store Methods Used

Reading State:

// Get all return process entities
this.#returnProcessStore.entities()  // Signal<ReturnProcess[]>

Writing State:

// Mark processes as finished with generated receipts
this.#returnProcessStore.finishProcess(returnReceipts: Receipt[])

Process Lifecycle

┌─────────────────────────────────────────────────────────┐
│ Store State: ReturnProcess[]                            │
├─────────────────────────────────────────────────────────┤
│ Initial State:                                          │
│   - Multiple ReturnProcess entities                     │
│   - Grouped by processId                                │
│   - Contains answers, receiptItem, productCategory      │
│   - returnReceipt is undefined                          │
└────────────────────┬────────────────────────────────────┘
                     │
                     │ User clicks "Complete Return"
                     ▼
┌─────────────────────────────────────────────────────────┐
│ ReturnProcessService.completeReturnProcess()            │
├─────────────────────────────────────────────────────────┤
│ - API call to OMS backend                               │
│ - Generates return receipts                             │
│ - Prints receipts                                       │
│ - Returns Receipt[] array                               │
└────────────────────┬────────────────────────────────────┘
                     │
                     │ Success: Receipt[] returned
                     ▼
┌─────────────────────────────────────────────────────────┐
│ Store.finishProcess(receipts)                           │
├─────────────────────────────────────────────────────────┤
│ Updates State:                                          │
│   - Matches receipts to ReturnProcess entities          │
│   - Sets returnReceipt property on each                 │
│   - Marks processes as completed                        │
└────────────────────┬────────────────────────────────────┘
                     │
                     │ Navigate to review
                     ▼
┌─────────────────────────────────────────────────────────┐
│ ReturnReview Screen                                     │
│ - Displays completed returnReceipts                     │
│ - Shows confirmation to user                            │
└─────────────────────────────────────────────────────────┘

Tab ID Management

Uses @isa/core/tabs for process ID extraction:

import { injectTabId } from '@isa/core/tabs';

@Component({ ... })
class ReturnSummaryComponent {
  // Automatically extracts tabId from route params
  processId = injectTabId();  // Signal<number>
}

Benefits:

  • Centralized tab/process ID management
  • Type-safe signal-based access
  • Automatic route parameter tracking
  • Consistent across OMS features

Testing

The library uses Jest with Spectator for unit testing.

Running Tests

# Run tests for this library
npx nx test oms-feature-return-summary --skip-nx-cache

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

# Run tests in watch mode
npx nx test oms-feature-return-summary --watch

Test Structure

The library includes comprehensive unit tests covering:

  • Component creation - Verifies component instantiation
  • State filtering - Tests processId-based filtering of return processes
  • Child component rendering - Validates ReturnSummaryItemComponent is rendered correctly
  • Input binding - Tests data flow to child components
  • E2E attributes - Validates all data-* attributes are properly set
  • User interactions - Tests button clicks and navigation
  • Return information - Tests computed return details display
  • Eligibility checking - Tests return eligibility validation
  • Navigation - Tests back button and edit functionality

Example Test: E2E Attributes

it('should have proper E2E testing attributes', () => {
  // Arrange
  const mockReturnProcesses: ReturnProcess[] = [
    { id: 1, processId: 1, productCategory: 'Electronics' } as ReturnProcess,
    { id: 2, processId: 1, productCategory: 'Books' } as ReturnProcess
  ];

  jest.spyOn(spectator.component, 'returnProcesses')
    .mockReturnValue(mockReturnProcesses);

  // Act
  spectator.detectChanges();

  // Assert
  const heading = spectator.query('[data-what="heading"]');
  expect(heading).toHaveAttribute('data-which', 'return-summary-title');

  const listItems = spectator.queryAll('oms-feature-return-summary-item');
  expect(listItems[0]).toHaveAttribute('data-item-id', '1');
  expect(listItems[0]).toHaveAttribute('data-item-category', 'Electronics');
  expect(listItems[1]).toHaveAttribute('data-item-id', '2');
  expect(listItems[1]).toHaveAttribute('data-item-category', 'Books');

  const button = spectator.query('[data-which="return-and-print"]');
  expect(button).toBeTruthy();
});

Example Test: Return Information Display

it('should provide correct return information array', () => {
  // Arrange
  const mockReturnInfo = {
    itemCondition: 'Used',
    returnReason: 'Defective',
    returnDetails: { [ReturnProcessQuestionKey.CaseDamaged]: 'Yes' },
    otherProduct: { ean: '1234567890123' } as Product,
    comment: 'Screen has scratches'
  };

  jest.spyOn(returnProcessService, 'getReturnInfo')
    .mockReturnValue(mockReturnInfo);

  spectator.setInput('returnProcess', mockReturnProcess);
  spectator.detectChanges();

  // Act
  const actualInfos = spectator.component.returnInfos();

  // Assert
  expect(actualInfos).toEqual([
    'Used',
    'Defective',
    'Gehäuse beschädigt: Yes',
    'Geliefert wurde: 1234567890123',
    'Screen has scratches'
  ]);
});

Mocking Strategy

ReturnSummaryComponent Tests:

const createComponent = createComponentFactory({
  component: ReturnSummaryComponent,
  imports: [MockComponent(ReturnSummaryItemComponent)],
  mocks: [ReturnProcessStore, ReturnProcessService, ActivatedRoute, Router]
});

ReturnSummaryItemComponent Tests:

const createComponent = createRoutingFactory({
  component: ReturnSummaryItemComponent,
  declarations: MockComponents(
    ReturnProductInfoComponent,
    NgIcon,
    IconButtonComponent
  ),
  providers: [
    MockProvider(ReturnProcessService, {
      getReturnInfo: jest.fn(),
      eligibleForReturn: jest.fn().mockReturnValue({ state: 'eligible' })
    }),
    MockProvider(Location, { back: jest.fn() })
  ]
});

Coverage Requirements

The tests ensure coverage of:

  • All public methods and properties
  • All computed signals and their logic
  • All user interaction handlers
  • All conditional template rendering
  • All E2E testing attributes
  • All error scenarios

Best Practices

1. Use Computed Signals for Derived State

// ✅ Good: Reactive computed signal
returnProcesses = computed<ReturnProcess[]>(() => {
  const processId = this.processId();
  return this.#returnProcessStore
    .entities()
    .filter(p => p.processId === processId);
});

// ❌ Bad: Manual filtering in template
// {{ store.entities() | filterByProcessId: processId() }}

2. Prevent Double-Submit with Status Checking

async returnItemsAndPrintRecipt() {
  // ✅ Good: Check if already processing
  if (this.returnItemsAndPrintReciptStatus() === 'pending') {
    this.#logger.warn('Return process already in progress');
    return;
  }

  try {
    this.returnItemsAndPrintReciptStatus.set('pending');
    // ... process return
  } catch (error) {
    this.returnItemsAndPrintReciptStatus.set('error');
  }
}

// ❌ Bad: No duplicate check, user can click multiple times

3. Comprehensive Error Logging

// ✅ Good: Contextual error logging
this.#logger.error(
  'Error completing return process',
  error as Error,
  () => ({
    function: 'returnItemsAndPrintRecipt',
    processCount: this.returnProcesses().length,
    processId: this.processId()
  })
);

// ❌ Bad: Generic console.error
console.error('Error:', error);

4. Use E2E Attributes Consistently

// ✅ Good: Complete E2E attribute coverage
<button
  data-what="button"
  data-which="return-and-print"
  [disabled]="returnItemsAndPrintReciptStatusPending()"
  (click)="returnItemsAndPrintRecipt()">

// ❌ Bad: Missing E2E attributes
<button
  [disabled]="isPending"
  (click)="submit()">

5. Disable UI During Async Operations

// ✅ Good: Disable button during pending state
<button
  uiButton
  [disabled]="returnItemsAndPrintReciptStatusPending()"
  [pending]="returnItemsAndPrintReciptStatusPending()"
  (click)="returnItemsAndPrintRecipt()">

// ❌ Bad: No disabled state, allows multiple clicks
<button (click)="returnItemsAndPrintRecipt()">

6. Clean Return Info Display

// ✅ Good: Remove duplicates and filter empty values
returnInfos = computed<string[]>(() => {
  const result: string[] = [];
  // ... collect info
  return Array.from(new Set(result));  // Remove duplicates
});

// ❌ Bad: Show duplicate info or empty entries
returnInfos = computed(() => {
  return [condition, reason, details];  // May have duplicates
});

7. Navigation After State Update

// ✅ Good: Update store before navigation
this.#returnProcessStore.finishProcess(returnReceipts);
await this.#router.navigate(['../', 'review'], {
  relativeTo: this.#activatedRoute
});

// ❌ Bad: Navigate before state update
await this.#router.navigate(['../', 'review']);
this.#returnProcessStore.finishProcess(returnReceipts);

8. Conditional Rendering with Signals

// ✅ Good: Use control flow syntax with signals
@if (returnItemsAndPrintReciptStatus() !== "success") {
  <button>Complete Return</button>
}

// ❌ Bad: Old *ngIf syntax (still works but less optimal)
<button *ngIf="status !== 'success'">Complete Return</button>

9. Product Category-Specific Logic

// ✅ Good: Handle Tolino products specially
if (returnProcess.productCategory !== ProductCategory.Tolino) {
  if (itemCondition) result.push(itemCondition);
  if (returnReason) result.push(returnReason);
}

// ❌ Bad: Show all fields for all categories
result.push(itemCondition, returnReason);

10. Relative Route Navigation

// ✅ Good: Relative navigation with ActivatedRoute
await this.#router.navigate(['../', 'review'], {
  relativeTo: this.#activatedRoute
});

// ❌ Bad: Hardcoded absolute paths
await this.#router.navigate(['/return/42/summary/review']);

Dependencies

Required Angular Libraries

  • @angular/core (^20.1.2) - Angular framework with signals support
  • @angular/common (^20.1.2) - Common Angular utilities (Location service)
  • @angular/router (^20.1.2) - Routing and navigation

ISA Internal Libraries

Core Libraries

  • @isa/core/tabs - Tab/process ID management with injectTabId()
  • @isa/core/logging - Centralized logging service

OMS Domain Libraries

  • @isa/oms/data-access - Return process state management and services
    • ReturnProcessStore - NgRx Signals store
    • ReturnProcessService - Business logic service
    • ReturnProcess - Return process model
    • EligibleForReturn, EligibleForReturnState - Eligibility types
    • ProductCategory - Product categorization
    • returnDetailsMapping - Return details formatter

Shared Libraries

  • @isa/oms/shared/product-info - Product information display component
  • @isa/ui/buttons - Button and icon button components
  • @isa/icons - Icon library (isaActionChevronLeft, isaActionChevronRight, isaActionEdit, isaActionClose)

Third-Party Libraries

  • @ng-icons/core - Icon component system with NgIcon and provideIcons()

Path Alias

Import from: @isa/oms/feature/return-summary

Peer Dependencies

This library expects the following to be available in the consuming application:

  • Angular 20.1.2 or higher
  • Tailwind CSS configured with ISA design tokens
  • NgRx Signals (@ngrx/signals)
  • Return process state properly initialized in ReturnProcessStore

Architecture Notes

Integration Points

Upstream Dependencies:

Return Details Collection
    ↓ (populates ReturnProcessStore)
Return Summary (This Library)
    ↓ (completes and stores receipts)
Return Review

State Flow:

ReturnProcessStore
    ↓ (entities filtered by processId)
ReturnSummaryComponent
    ↓ (passes individual processes)
ReturnSummaryItemComponent
    ↓ (displays product + return info)
User Confirmation
    ↓ (completion)
ReturnProcessService
    ↓ (API call + receipt generation)
Back to ReturnProcessStore
    ↓ (finishProcess updates)
Navigate to Review

Design Patterns

  1. Presentation + Container Pattern

    • ReturnSummaryComponent = Container (orchestrates flow)
    • ReturnSummaryItemComponent = Presentation (displays data)
  2. Signal-Based Reactivity

    • All state management uses Angular signals
    • Computed signals for derived state
    • No manual subscription management needed
  3. Service Facade Pattern

    • ReturnProcessService abstracts API complexity
    • Single method completeReturnProcess() handles entire flow
  4. Optimistic UI Updates

    • Status signal immediately set to 'pending'
    • UI disables to prevent further interaction
    • Rollback on error (status set to 'error')

Future Enhancements

Potential improvements identified:

  1. Undo Functionality - Allow users to go back and modify before final confirmation
  2. Bulk Actions - Select/deselect items for return in the summary
  3. Print Preview - Show receipt preview before final confirmation
  4. Offline Support - Queue returns when offline, sync when online
  5. Analytics Integration - Track return patterns and reasons
  6. Accessibility Improvements - Screen reader announcements for status changes
  7. Animation - Smooth transitions between states
  8. Keyboard Shortcuts - Quick navigation and confirmation via keyboard

Known Considerations

  1. No Loading Skeleton - Component shows empty state until data loads
  2. No Retry Mechanism - Failed returns require manual retry (button click)
  3. Hard-Coded German Text - Internationalization not implemented
  4. Navigation Dependency - Assumes review route exists at '../review'
  5. Store Coupling - Tightly coupled to ReturnProcessStore structure

License

Internal ISA Frontend library - not for external distribution.