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.
@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
- Quick Start
- Core Concepts
- API Reference
- Usage Examples
- Routing and Navigation
- Component Architecture
- State Management
- Testing
- Best Practices
- Dependencies
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:
- Return Search - Find receipt and items to return
- Return Details Collection - Gather item condition, reason, comments
- Return Summary (This Library) - Review and confirm all details
- 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:
- Checks if operation is already in progress (prevents double-submit)
- Sets status to 'pending'
- Calls
ReturnProcessService.completeReturnProcess()with all return processes - Updates status based on success/failure
- Stores completed receipts in
ReturnProcessStore - Navigates to '../review' route on success
- 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
- Example:
-
Review View (after completion):
/return/:tabId/summary/review- Example:
/return/42/summary/review - Loads
@isa/oms/feature/return-reviewlazily
- Example:
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 withinjectTabId()@isa/core/logging- Centralized logging service
OMS Domain Libraries
@isa/oms/data-access- Return process state management and servicesReturnProcessStore- NgRx Signals storeReturnProcessService- Business logic serviceReturnProcess- Return process modelEligibleForReturn,EligibleForReturnState- Eligibility typesProductCategory- Product categorizationreturnDetailsMapping- 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 withNgIconandprovideIcons()
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
-
Presentation + Container Pattern
ReturnSummaryComponent= Container (orchestrates flow)ReturnSummaryItemComponent= Presentation (displays data)
-
Signal-Based Reactivity
- All state management uses Angular signals
- Computed signals for derived state
- No manual subscription management needed
-
Service Facade Pattern
ReturnProcessServiceabstracts API complexity- Single method
completeReturnProcess()handles entire flow
-
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:
- Undo Functionality - Allow users to go back and modify before final confirmation
- Bulk Actions - Select/deselect items for return in the summary
- Print Preview - Show receipt preview before final confirmation
- Offline Support - Queue returns when offline, sync when online
- Analytics Integration - Track return patterns and reasons
- Accessibility Improvements - Screen reader announcements for status changes
- Animation - Smooth transitions between states
- Keyboard Shortcuts - Quick navigation and confirmation via keyboard
Known Considerations
- No Loading Skeleton - Component shows empty state until data loads
- No Retry Mechanism - Failed returns require manual retry (button click)
- Hard-Coded German Text - Internationalization not implemented
- Navigation Dependency - Assumes review route exists at '../review'
- Store Coupling - Tightly coupled to ReturnProcessStore structure
License
Internal ISA Frontend library - not for external distribution.