# @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](#features)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [Return Process Workflow](#return-process-workflow)
- [Product Categories and Questions](#product-categories-and-questions)
- [State Management](#state-management)
- [Error Handling](#error-handling)
- [Testing](#testing)
- [Architecture Notes](#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

```typescript
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

```typescript
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

```typescript
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

```typescript
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:

```typescript
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:

```typescript
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 options
- `Checklist` - Multiple choice with optional "other" text
- `Info` - Informational display (auto-answered)
- `Group` - Container for multiple related questions
- `Product` - Product-specific question

### State Management with NgRx Signals

The library provides three signal stores with different persistence strategies:

#### 1. ReturnSearchStore (Session Storage)
```typescript
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)
```typescript
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)
```typescript
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:

```typescript
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 validation
- `FetchReturnDetailsSchema` - Receipt detail fetch params
- `ReturnReceiptValuesSchema` - Return submission payload
- `ReturnProcessChecklistAnswerSchema` - 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:**
```typescript
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 string
  - `filter` - Filter criteria (e.g., receipt_type)
  - `take` - Number of results to return
  - `skip` - Number of results to skip (pagination)

**Returns:** Observable containing list of receipt items with metadata

**Throws:**
- `ReturnParseQueryTokenError` - If query token validation fails
- `ReturnSearchSearchError` - If search fails due to API error

**Example:**
```typescript
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:**
```typescript
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 for
- `abortSignal?: AbortSignal` - Optional signal to cancel request

**Returns:** Promise resolving to array of receipt list items

**Example:**
```typescript
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 check
- `params.quantity: number` - Quantity to return
- `params.category: ProductCategory` - Product category
- `abortSignal?: AbortSignal` - Optional signal to cancel request

**Returns:** Promise resolving to CanReturn result with eligibility info

**Example:**
```typescript
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:**
```typescript
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 found
- `Error` - If cyclic question dependencies detected

**Example:**
```typescript
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:**
```typescript
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:**
```typescript
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:**
```typescript
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/undefined
- `PropertyIsEmptyError` - If processes array is empty
- `ReturnProcessIsNotCompleteError` - If any process has unanswered questions

**Example:**
```typescript
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 invalid
- `ResponseArgsError` - If order creation fails

**Example:**
```typescript
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 fails
- `Error` - If logistician not found

**Example:**
```typescript
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:**
```typescript
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](#usage-examples) section.

#### ReturnSearchStore

Signal store for managing receipt search state with session persistence.

**Key Methods:**
- `getEntity(processId)` - Get search entity by process ID
- `search(params)` - Execute search with rxMethod
- `removeAllEntitiesByProcessId(processId)` - Clean up search results

#### ReturnProcessStore

Signal store for managing return process entities with IndexedDB persistence.

**Key Methods:**
- `startProcess(params)` - Initialize return process
- `setAnswer(id, question, answer)` - Set question answer
- `removeAnswer(id, question)` - Remove question answer
- `finishProcess(returnReceipts)` - Mark process complete
- `removeAllEntitiesByProcessId(...processIds)` - Clean up processes

#### ReturnDetailsStore

Signal store for managing receipt details and item selection (in-memory).

**Key Methods:**
- `receiptResource(receiptId)` - Load receipt
- `canReturnResource(receiptItem)` - Check return eligibility
- `addSelectedItems(itemIds)` - Add items to selection
- `setProductCategory(itemId, category)` - Set item category
- `setQuantity(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 returnable
- `getReceiptItemQuantity(item)` - Get item quantity
- `getReceiptItemReturnedQuantity(item)` - Get returned quantity
- `getReceiptItemProductCategory(item)` - Get item category
- `receiptItemHasCategory(item, category)` - Check category match
- `activeReturnProcessQuestions(questions, answers)` - Get active questions
- `allReturnProcessQuestionsAnswered(params)` - Check completion
- `calculateLongestQuestionDepth(questions, answers)` - Calculate question tree depth

---

### RxJS Operators

#### `takeUntilAborted(signal: AbortSignal)`

RxJS operator that completes the source Observable when an AbortSignal is aborted.

**Example:**
```typescript
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 class
- `ReturnSearchSearchError` - Search failure
- `ReturnParseQueryTokenError` - Query validation failure
- `CreateReturnProcessError` - Process creation failure
- `ReturnProcessIsNotCompleteError` - Incomplete process submission

For detailed error handling patterns, see the [Error Handling](#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:
1. Searching for receipts
2. Loading receipt details
3. Selecting items for return
4. Starting the return process
5. Answering questions
6. Checking eligibility
7. 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

1. Use Zod `safeParse()` for input validation
2. Handle service errors with try/catch
3. Use Observable error callbacks for streams
4. Check store entity status for error states
5. Integrate with `@isa/core/logging` for 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

```bash
# 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 providers
- `zod` - Schema validation
- `rxjs` - Reactive programming

### Path Alias

Import from: `@isa/oms/data-access`

---

## License

Internal ISA Frontend library - not for external distribution.
