- Add new reward-order-confirmation feature library with components and store - Implement checkout completion orchestrator service for order finalization - Migrate checkout/oms/crm models to Zod schemas for better type safety - Add order creation facade and display order schemas - Update shopping cart facade with order completion flow - Add comprehensive tests for shopping cart facade - Update routing to include order confirmation page
25 KiB
@isa/oms/data-access
A comprehensive Order Management System (OMS) data access library for Angular applications providing return processing, receipt management, order creation, and print capabilities.
Overview
The OMS Data Access library provides a complete suite of services, stores, and utilities for managing the entire return lifecycle in a retail environment. It integrates with the generated OMS API client and provides intelligent state management, question-based return workflows, validation, and receipt printing capabilities. The library follows domain-driven design principles with clear separation between business logic, state management, and API integration.
Table of Contents
- Features
- Quick Start
- Core Concepts
- API Reference
- Usage Examples
- Return Process Workflow
- Product Categories and Questions
- State Management
- Error Handling
- Testing
- Architecture Notes
Features
- Return Search - Advanced receipt search with query builder and filter support
- Return Process Management - Question-based return workflow with branching logic
- Product Category System - Category-specific questions and validation rules
- Receipt Management - Full CRUD operations for receipts and receipt items
- Order Creation - Create orders from completed checkouts
- Print Integration - Automated return receipt printing via label printers
- NgRx Signals Stores - Reactive state management with persistence
- Session & IndexedDB Storage - Automatic state persistence across browser sessions
- Orphaned Entity Cleanup - Automatic cleanup when tabs are closed
- Zod Schema Validation - Runtime validation for all data structures
- AbortSignal Support - Request cancellation for all async operations
- Comprehensive Logging - Integration with @isa/core/logging for debugging
- Question Branching Logic - Dynamic question flow based on previous answers
Quick Start
1. Import and Inject Services
import { Component, inject } from '@angular/core';
import {
ReturnSearchService,
ReturnDetailsService,
ReturnProcessService
} from '@isa/oms/data-access';
@Component({
selector: 'app-return-management',
template: '...'
})
export class ReturnManagementComponent {
#returnSearchService = inject(ReturnSearchService);
#returnDetailsService = inject(ReturnDetailsService);
#returnProcessService = inject(ReturnProcessService);
}
2. Search for Receipts
async searchReceipts(): Promise<void> {
const results = await firstValueFrom(
this.#returnSearchService.search({
input: { qs: '12345' }, // Receipt number or search term
filter: { receipt_type: '1;128;1024' },
take: 20,
skip: 0
})
);
console.log(`Found ${results.hits} receipts`);
results.result.forEach(receipt => {
console.log(`Receipt #${receipt.receiptNumber}: ${receipt.date}`);
});
}
3. Fetch Receipt Details
async getReceiptDetails(receiptId: number): Promise<void> {
const receipt = await this.#returnDetailsService.fetchReturnDetails({
receiptId: receiptId
});
console.log(`Receipt: ${receipt.receiptNumber}`);
console.log(`Buyer: ${receipt.buyer.firstName} ${receipt.buyer.lastName}`);
console.log(`Items: ${receipt.items.length}`);
}
4. Process a Return
async processReturn(): Promise<void> {
// Start a return process
const returnProcess: ReturnProcess = {
id: 1,
processId: 100,
receiptId: 12345,
receiptItem: receiptItem,
receiptDate: '2025-10-20',
answers: {},
productCategory: ProductCategory.ElektronischeGeraete,
quantity: 1
};
// Get active questions
const questions = this.#returnProcessService.activeReturnProcessQuestions(returnProcess);
// Check eligibility
const eligibility = this.#returnProcessService.eligibleForReturn(returnProcess);
if (eligibility?.state === EligibleForReturnState.Eligible) {
console.log('Item is eligible for return');
}
}
Core Concepts
Return Process Lifecycle
The return process follows a structured workflow:
1. Search & Selection
└─> Search for receipts using ReturnSearchService
└─> Select receipt items to return
2. Return Process Initialization
└─> Create ReturnProcess entities via ReturnProcessStore
└─> Assign product categories to items
3. Question Workflow
└─> Display category-specific questions
└─> Collect answers with validation
└─> Follow branching logic based on answers
4. Eligibility Determination
└─> Validate all questions answered
└─> Check category-specific business rules
└─> Determine if item is eligible for return
5. Completion
└─> Generate return receipts via ReceiptService
└─> Print return receipts via PrintReceiptsService
└─> Update process with return receipt
Product Categories
The library supports seven product categories, each with specific return rules:
enum ProductCategory {
BookCalendar = 'book-calendar', // Books and calendars
TonDatentraeger = 'ton-datentraeger', // Audio and data media
SpielwarenPuzzle = 'spielwaren-puzzle', // Toys and puzzles
SonstigesNonbook = 'sonstiges-nonbook', // Other non-book items
ElektronischeGeraete = 'elektronische-geraete', // Electronic devices
Tolino = 'tolino', // Tolino e-readers
Unknown = 'unknown' // Uncategorized
}
Each category has:
- Specific questions - Defined in the CategoryQuestions registry
- Branching logic - Questions can have nextQuestion properties
- Validation rules - Category-specific eligibility checks
- Business rules - Special handling (e.g., sealed packages, electronic devices)
Question System Architecture
Questions follow a tree-like structure with branching:
interface ReturnProcessQuestion {
key: string; // Unique question identifier
type: ReturnProcessQuestionType; // Question type (Select, Checklist, Info, Group, Product)
question: string; // Question text to display
options?: Array<{ // Available answer options
value: string;
label: string;
nextQuestion?: ReturnProcessQuestion; // Branching to next question
}>;
questions?: ReturnProcessQuestion[]; // Sub-questions for Group type
}
Question Types:
Select- Single choice from optionsChecklist- Multiple choice with optional "other" textInfo- Informational display (auto-answered)Group- Container for multiple related questionsProduct- Product-specific question
State Management with NgRx Signals
The library provides three signal stores with different persistence strategies:
1. ReturnSearchStore (Session Storage)
export const ReturnSearchStore = signalStore(
{ providedIn: 'root' },
withStorage('oms-data-access.return-search-store', SessionStorageProvider),
withEntities<ReturnSearchEntity>(config),
// ...
);
- Purpose: Manage search results and pagination
- Persistence: SessionStorage (cleared when tab closes)
- Entity: ReturnSearchEntity with status, items, hits
- Features: Paginated search, status tracking (Idle, Pending, Success, Error)
2. ReturnProcessStore (IndexedDB)
export const ReturnProcessStore = signalStore(
{ providedIn: 'root' },
withStorage('return-process', IDBStorageProvider),
withEntities<ReturnProcess>(),
// ...
);
- Purpose: Manage active return processes
- Persistence: IndexedDB (survives tab close, persistent across sessions)
- Entity: ReturnProcess with answers, product category, receipt info
- Features: Answer management, process completion tracking
3. ReturnDetailsStore (No Persistence)
export const ReturnDetailsStore = signalStore(
withState(initialState),
withEntities(receiptConfig),
// ...
);
- Purpose: Manage receipt details and item selection
- Persistence: None (in-memory only)
- Features: Item selection, category/quantity management, canReturn checks
Validation with Zod
All input parameters and API payloads are validated using Zod schemas:
import { QueryTokenSchema, ReturnReceiptValuesSchema } from '@isa/oms/data-access';
// Automatic validation in services
const validatedToken = QueryTokenSchema.parse(queryToken);
// Safe parsing with error handling
const result = ReturnReceiptValuesSchema.safeParse(payload);
if (!result.success) {
console.error('Validation failed:', result.error);
}
Key Schemas:
QueryTokenSchema- Search query validationFetchReturnDetailsSchema- Receipt detail fetch paramsReturnReceiptValuesSchema- Return submission payloadReturnProcessChecklistAnswerSchema- Checklist answer validation
API Reference
Services
ReturnSearchService
Service for searching receipts in the OMS system.
querySettings(): Observable<QuerySettingsDTO>
Fetches query settings for receipt search configuration.
Returns: Observable containing query settings (available filters, sort options)
Example:
this.#returnSearchService.querySettings().subscribe(settings => {
console.log('Available filters:', settings.filters);
});
search(queryToken: QueryTokenInput): Observable<ListResponseArgs<ReceiptListItem>>
Executes a search for receipts based on query parameters.
Parameters:
queryToken: QueryTokenInput- Search parameters (validated with Zod)input.qs- Search query stringfilter- Filter criteria (e.g., receipt_type)take- Number of results to returnskip- Number of results to skip (pagination)
Returns: Observable containing list of receipt items with metadata
Throws:
ReturnParseQueryTokenError- If query token validation failsReturnSearchSearchError- If search fails due to API error
Example:
this.#returnSearchService.search({
input: { qs: 'smith@example.com' },
filter: { receipt_type: '1;128;1024' },
take: 20,
skip: 0
}).subscribe({
next: (results) => console.log(`Found ${results.hits} receipts`),
error: (error) => console.error('Search failed:', error)
});
ReturnDetailsService
Service for managing receipt details and return eligibility.
fetchReturnDetails(params: FetchReturnDetails, abortSignal?: AbortSignal): Promise<Receipt>
Fetches detailed receipt information including items, buyer, and shipping.
Parameters:
params.receiptId: number- Receipt ID to fetch (validated with Zod)abortSignal?: AbortSignal- Optional signal to cancel request
Returns: Promise resolving to complete Receipt with items
Throws: Error if receipt not found or API error
Example:
const receipt = await this.#returnDetailsService.fetchReturnDetails(
{ receiptId: 12345 },
abortController.signal
);
console.log(`Receipt ${receipt.receiptNumber} has ${receipt.items.length} items`);
fetchReceiptsByEmail(params: { email: string }, abortSignal?: AbortSignal): Promise<ReceiptListItem[]>
Fetches all receipts associated with an email address.
Parameters:
params.email: string- Email address to search forabortSignal?: AbortSignal- Optional signal to cancel request
Returns: Promise resolving to array of receipt list items
Example:
const receipts = await this.#returnDetailsService.fetchReceiptsByEmail({
email: 'customer@example.com'
});
receipts.forEach(r => console.log(`Receipt: ${r.receiptNumber}`));
canReturn(params: { receiptItemId: number, quantity: number, category: ProductCategory }, abortSignal?: AbortSignal): Promise<CanReturn>
Checks if a specific receipt item can be returned.
Parameters:
params.receiptItemId: number- ID of receipt item to checkparams.quantity: number- Quantity to returnparams.category: ProductCategory- Product categoryabortSignal?: AbortSignal- Optional signal to cancel request
Returns: Promise resolving to CanReturn result with eligibility info
Example:
const canReturn = await this.#returnDetailsService.canReturn({
receiptItemId: 456,
quantity: 1,
category: ProductCategory.ElektronischeGeraete
});
if (canReturn.result) {
console.log('Item can be returned');
} else {
console.log('Return not allowed:', canReturn.message);
}
availableCategories(): KeyValue<ProductCategory, string>[]
Gets all available product categories (excludes Unknown).
Returns: Array of key-value pairs representing categories
Example:
const categories = this.#returnDetailsService.availableCategories();
categories.forEach(cat => {
console.log(`${cat.key}: ${cat.value}`);
});
ReturnProcessService
Service for managing the return process workflow including questions and eligibility.
activeReturnProcessQuestions(process: ReturnProcess): ReturnProcessQuestion[] | undefined
Gets active questions based on answers provided so far.
Parameters:
process: ReturnProcess- The return process containing answers
Returns: Array of active questions, or undefined if no questions apply
Throws:
PropertyNullOrUndefinedError- If questions cannot be foundError- If cyclic question dependencies detected
Example:
const questions = this.#returnProcessService.activeReturnProcessQuestions(process);
questions?.forEach(q => {
console.log(`Question: ${q.question}`);
if (q.type === ReturnProcessQuestionType.Select) {
q.options?.forEach(opt => console.log(` - ${opt.label}`));
}
});
returnProcessQuestionsProgress(returnProcess: ReturnProcess): { answered: number; total: number } | undefined
Calculates question progress for the return process.
Parameters:
returnProcess: ReturnProcess- The return process to analyze
Returns: Object with answered and total question counts, or undefined
Example:
const progress = this.#returnProcessService.returnProcessQuestionsProgress(process);
if (progress) {
console.log(`Progress: ${progress.answered}/${progress.total}`);
const percentage = (progress.answered / progress.total) * 100;
console.log(`${percentage}% complete`);
}
eligibleForReturn(returnProcess: ReturnProcess): EligibleForReturn | undefined
Determines if the product is eligible for return based on answers.
Parameters:
returnProcess: ReturnProcess- The return process with complete answers
Returns: Eligibility status, or undefined if questions incomplete
Example:
const eligibility = this.#returnProcessService.eligibleForReturn(process);
switch (eligibility?.state) {
case EligibleForReturnState.Eligible:
console.log('Item is eligible for return');
break;
case EligibleForReturnState.NotEligible:
console.log('Item is not eligible:', eligibility.reason);
break;
case EligibleForReturnState.Unknown:
console.log('Cannot determine eligibility');
break;
}
getReturnInfo(process: ReturnProcess): ReturnInfo | undefined
Retrieves consolidated return information from answers.
Parameters:
process: ReturnProcess- The return process
Returns: Consolidated return information or undefined
Example:
const info = this.#returnProcessService.getReturnInfo(process);
if (info) {
console.log('Return reason:', info.reason);
console.log('Additional notes:', info.notes);
}
completeReturnProcess(processes: ReturnProcess[]): Promise<Receipt[]>
Completes the return process and generates return receipts.
Parameters:
processes: ReturnProcess[]- Array of return processes to complete
Returns: Promise resolving to generated return receipts
Throws:
PropertyNullOrUndefinedError- If processes is null/undefinedPropertyIsEmptyError- If processes array is emptyReturnProcessIsNotCompleteError- If any process has unanswered questions
Example:
try {
const receipts = await this.#returnProcessService.completeReturnProcess([
process1,
process2
]);
console.log(`Generated ${receipts.length} return receipts`);
receipts.forEach(r => console.log(`Return receipt: ${r.receiptNumber}`));
} catch (error) {
if (error instanceof ReturnProcessIsNotCompleteError) {
console.error('Please answer all questions before completing');
}
}
OrderCreationService
Service for creating orders from checkout.
createOrdersFromCheckout(checkoutId: number): Promise<Order[]>
Creates orders from a completed checkout.
Parameters:
checkoutId: number- The checkout ID to create orders from
Returns: Promise resolving to array of created orders
Throws:
Error- If checkoutId is invalidResponseArgsError- If order creation fails
Example:
const orders = await this.#orderCreationService.createOrdersFromCheckout(12345);
orders.forEach(order => {
console.log(`Order ${order.orderNumber} created`);
});
getLogistician(logisticianNumber = '2470', abortSignal?: AbortSignal): Promise<LogisticianDTO>
Retrieves logistician information.
Parameters:
logisticianNumber: string- Logistician number (defaults to '2470')abortSignal?: AbortSignal- Optional signal to cancel request
Returns: Promise resolving to logistician data
Throws:
ResponseArgsError- If request failsError- If logistician not found
Example:
const logistician = await this.#orderCreationService.getLogistician('2470');
console.log(`Logistician: ${logistician.name}`);
PrintReceiptsService
Service for printing return receipts.
printReturnReceipts({ returnReceiptIds }: { returnReceiptIds: number[] }): Promise<void>
Prints return receipts to a label printer.
Parameters:
returnReceiptIds: number[]- Array of return receipt IDs to print
Returns: Promise that resolves when printing is complete
Throws: Error if no receipt IDs provided
Example:
await this.#printReceiptsService.printReturnReceipts({
returnReceiptIds: [123, 124, 125]
});
console.log('Receipts sent to printer');
Facades
OrderCreationFacade
Simplified interface for order creation operations. Delegates to OrderCreationService.
Methods:
createOrdersFromCheckout(checkoutId: number): Promise<Order[]>getLogistician(logisticianNumber = '2470', abortSignal?: AbortSignal): Promise<LogisticianDTO>
Note: This is a pass-through facade. Consider injecting OrderCreationService directly unless future orchestration logic is planned.
Stores
For detailed store usage and examples, see the Usage Examples section.
ReturnSearchStore
Signal store for managing receipt search state with session persistence.
Key Methods:
getEntity(processId)- Get search entity by process IDsearch(params)- Execute search with rxMethodremoveAllEntitiesByProcessId(processId)- Clean up search results
ReturnProcessStore
Signal store for managing return process entities with IndexedDB persistence.
Key Methods:
startProcess(params)- Initialize return processsetAnswer(id, question, answer)- Set question answerremoveAnswer(id, question)- Remove question answerfinishProcess(returnReceipts)- Mark process completeremoveAllEntitiesByProcessId(...processIds)- Clean up processes
ReturnDetailsStore
Signal store for managing receipt details and item selection (in-memory).
Key Methods:
receiptResource(receiptId)- Load receiptcanReturnResource(receiptItem)- Check return eligibilityaddSelectedItems(itemIds)- Add items to selectionsetProductCategory(itemId, category)- Set item categorysetQuantity(itemId, quantity)- Set return quantity
Helper Functions
Return Process Helpers
Key helper functions exported from @isa/oms/data-access:
canReturnReceiptItem(item)- Check if item is returnablegetReceiptItemQuantity(item)- Get item quantitygetReceiptItemReturnedQuantity(item)- Get returned quantitygetReceiptItemProductCategory(item)- Get item categoryreceiptItemHasCategory(item, category)- Check category matchactiveReturnProcessQuestions(questions, answers)- Get active questionsallReturnProcessQuestionsAnswered(params)- Check completioncalculateLongestQuestionDepth(questions, answers)- Calculate question tree depth
RxJS Operators
takeUntilAborted(signal: AbortSignal)
RxJS operator that completes the source Observable when an AbortSignal is aborted.
Example:
import { takeUntilAborted } from '@isa/oms/data-access';
const abortController = new AbortController();
observable$.pipe(
takeUntilAborted(abortController.signal)
).subscribe({
next: (data) => console.log('Data:', data),
error: (err) => console.error('Aborted or failed')
});
// Cancel request
abortController.abort();
Error Classes
OmsError- Base error classReturnSearchSearchError- Search failureReturnParseQueryTokenError- Query validation failureCreateReturnProcessError- Process creation failureReturnProcessIsNotCompleteError- Incomplete process submission
For detailed error handling patterns, see the Error Handling section.
Usage Examples
Complete Return Workflow
See the comprehensive workflow example in the full documentation above for a step-by-step guide covering:
- Searching for receipts
- Loading receipt details
- Selecting items for return
- Starting the return process
- Answering questions
- Checking eligibility
- Completing the return
Search with Pagination
Example showing how to implement paginated receipt search with the ReturnSearchStore.
Dynamic Question Display
Example component demonstrating how to render questions dynamically based on type and handle answers.
Return Process Workflow
The return process follows this workflow:
Search Receipt → Load Details → Select Items → Start Process
→ Answer Questions → Check Eligibility → Complete Return → Print Receipt
Question Branching
Questions support branching logic through the nextQuestion property, allowing different question flows based on previous answers.
Product Categories and Questions
Supported Categories
- BookCalendar - Books and calendars (2 questions)
- TonDatentraeger - Audio/data media (3-5 questions)
- SpielwarenPuzzle - Toys and puzzles (2 questions)
- SonstigesNonbook - Other non-book items (2 questions)
- ElektronischeGeraete - Electronic devices (4-8 questions, complex branching)
- Tolino - E-readers (6-12 questions, most complex)
- Unknown - Uncategorized (0 questions, manual processing)
Each category has specific questions and validation rules defined in the CategoryQuestions registry.
State Management
Persistence Strategies
- ReturnSearchStore: SessionStorage (tab-scoped, cleared on close)
- ReturnProcessStore: IndexedDB (persistent across sessions)
- ReturnDetailsStore: In-memory (no persistence)
Orphaned Entity Cleanup
All stores automatically clean up entities when tabs are closed using Angular effects and the @isa/core/tabs service.
Error Handling
Best Practices
- Use Zod
safeParse()for input validation - Handle service errors with try/catch
- Use Observable error callbacks for streams
- Check store entity status for error states
- Integrate with
@isa/core/loggingfor debugging
Common Error Types
- Validation Errors: ZodError from schema validation
- API Errors: ResponseArgsError from API calls
- Business Logic Errors: Custom OMS error classes
- Process Errors: ReturnProcessIsNotCompleteError for incomplete submissions
Testing
The library uses Jest with Spectator for testing.
Running Tests
# Run tests
npx nx test oms-data-access --skip-nx-cache
# Run with coverage
npx nx test oms-data-access --code-coverage --skip-nx-cache
# Run in watch mode
npx nx test oms-data-access --watch
Architecture Notes
Layer Architecture
Features → Facades → Services → Stores → Helpers → Generated APIs
Design Patterns
- Service Layer Pattern - Business logic encapsulation
- State Management Pattern - NgRx Signals with persistence
- Question Registry Pattern - Category-specific question lookup
- Helper Function Pattern - Pure transformation functions
- Adapter Pattern - API DTO to domain model mapping
Dependencies
@angular/core- Angular framework@ngrx/signals- State management@generated/swagger/oms-api- OMS API client@isa/core/logging- Logging infrastructure@isa/core/tabs- Tab management@isa/core/storage- Storage providerszod- Schema validationrxjs- Reactive programming
Path Alias
Import from: @isa/oms/data-access
License
Internal ISA Frontend library - not for external distribution.