feat(checkout): add reward order confirmation feature with schema migrations

- 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
This commit is contained in:
Lorenz Hilpert
2025-10-21 14:28:52 +02:00
parent b96d889da5
commit 2b5da00249
259 changed files with 61347 additions and 2652 deletions

View File

@@ -1,7 +1,843 @@
# oms-data-access
# @isa/oms/data-access
This library was generated with [Nx](https://nx.dev).
A comprehensive Order Management System (OMS) data access library for Angular applications providing return processing, receipt management, order creation, and print capabilities.
## Running unit tests
## Overview
Run `nx test oms-data-access` to execute the unit tests.
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.

View File

@@ -6,6 +6,7 @@
*
* Core components:
* - Models: Type definitions for data structures
* - Facades: Simplified interfaces for order creation operations
* - Services: API integrations for data retrieval and manipulation
* - Stores: State management for OMS-related data
* - Schemas: Validation schemas for ensuring data integrity
@@ -17,6 +18,7 @@
export * from './lib/errors';
export * from './lib/questions';
export * from './lib/models';
export * from './lib/facades';
export * from './lib/helpers/return-process';
export * from './lib/schemas';
export * from './lib/services';

View File

@@ -0,0 +1 @@
export const OMS_DISPLAY_ORDERS_KEY = 'OMS-DATA-ACCESS.DISPLAY_ORDERS';

View File

@@ -0,0 +1 @@
export { OrderCreationFacade } from './order-creation.facade';

View File

@@ -0,0 +1,145 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { OrderCreationFacade } from './order-creation.facade';
import { OrderCreationService } from '../services';
import { LogisticianDTO } from '@generated/swagger/oms-api';
import { Order } from '../models';
describe('OrderCreationFacade', () => {
let spectator: SpectatorService<OrderCreationFacade>;
const createService = createServiceFactory({
service: OrderCreationFacade,
mocks: [OrderCreationService],
});
beforeEach(() => {
spectator = createService();
});
describe('createOrdersFromCheckout', () => {
it('should delegate to OrderCreationService.createOrdersFromCheckout', async () => {
// Arrange
const checkoutId = 123;
const mockOrders: Order[] = [
{ id: 1 } as Order,
{ id: 2 } as Order,
];
const orderCreationService = spectator.inject(OrderCreationService);
(orderCreationService.createOrdersFromCheckout as jest.Mock).mockResolvedValue(mockOrders);
// Act
const result = await spectator.service.createOrdersFromCheckout(checkoutId);
// Assert
expect(result).toEqual(mockOrders);
expect(orderCreationService.createOrdersFromCheckout).toHaveBeenCalledWith(
checkoutId,
);
expect(orderCreationService.createOrdersFromCheckout).toHaveBeenCalledTimes(1);
});
it('should propagate errors from OrderCreationService', async () => {
// Arrange
const checkoutId = 0;
const errorMessage = 'Invalid checkoutId: 0';
const orderCreationService = spectator.inject(OrderCreationService);
(orderCreationService.createOrdersFromCheckout as jest.Mock).mockRejectedValue(
new Error(errorMessage)
);
// Act & Assert
await expect(
spectator.service.createOrdersFromCheckout(checkoutId),
).rejects.toThrow(errorMessage);
expect(orderCreationService.createOrdersFromCheckout).toHaveBeenCalledWith(
checkoutId,
);
});
});
describe('getLogistician', () => {
it('should delegate to OrderCreationService.getLogistician with default logisticianNumber', async () => {
// Arrange
const mockLogistician: LogisticianDTO = {
logisticianNumber: '2470',
name: 'Default Logistician',
} as LogisticianDTO;
const orderCreationService = spectator.inject(OrderCreationService);
(orderCreationService.getLogistician as jest.Mock).mockResolvedValue(mockLogistician);
// Act
const result = await spectator.service.getLogistician();
// Assert
expect(result).toEqual(mockLogistician);
expect(orderCreationService.getLogistician).toHaveBeenCalledWith(
'2470',
undefined,
);
expect(orderCreationService.getLogistician).toHaveBeenCalledTimes(1);
});
it('should delegate to OrderCreationService.getLogistician with custom logisticianNumber', async () => {
// Arrange
const logisticianNumber = '1000';
const mockLogistician: LogisticianDTO = {
logisticianNumber,
name: 'Custom Logistician',
} as LogisticianDTO;
const orderCreationService = spectator.inject(OrderCreationService);
(orderCreationService.getLogistician as jest.Mock).mockResolvedValue(mockLogistician);
// Act
const result = await spectator.service.getLogistician(logisticianNumber);
// Assert
expect(result).toEqual(mockLogistician);
expect(orderCreationService.getLogistician).toHaveBeenCalledWith(
logisticianNumber,
undefined,
);
});
it('should delegate to OrderCreationService.getLogistician with abortSignal', async () => {
// Arrange
const logisticianNumber = '2470';
const abortSignal = new AbortController().signal;
const mockLogistician: LogisticianDTO = {
logisticianNumber,
name: 'Default Logistician',
} as LogisticianDTO;
const orderCreationService = spectator.inject(OrderCreationService);
(orderCreationService.getLogistician as jest.Mock).mockResolvedValue(mockLogistician);
// Act
const result = await spectator.service.getLogistician(
logisticianNumber,
abortSignal,
);
// Assert
expect(result).toEqual(mockLogistician);
expect(orderCreationService.getLogistician).toHaveBeenCalledWith(
logisticianNumber,
abortSignal,
);
});
it('should propagate errors from OrderCreationService', async () => {
// Arrange
const errorMessage = 'Logistician 2470 not found';
const orderCreationService = spectator.inject(OrderCreationService);
(orderCreationService.getLogistician as jest.Mock).mockRejectedValue(
new Error(errorMessage)
);
// Act & Assert
await expect(spectator.service.getLogistician()).rejects.toThrow(
errorMessage,
);
expect(orderCreationService.getLogistician).toHaveBeenCalledWith(
'2470',
undefined,
);
});
});
});

View File

@@ -0,0 +1,46 @@
import { inject, Injectable } from '@angular/core';
import { LogisticianDTO } from '@generated/swagger/oms-api';
import { Order } from '../models';
import { OrderCreationService } from '../services';
/**
* Facade for order creation operations.
*
* @remarks
* This facade provides a simplified interface for creating orders from checkout
* and retrieving logistician information. It delegates to OrderCreationService
* for all operations.
*/
@Injectable({ providedIn: 'root' })
export class OrderCreationFacade {
#orderCreationService = inject(OrderCreationService);
/**
* Creates orders from a checkout.
*
* @param checkoutId - The ID of the checkout to create orders from
* @returns Promise resolving to array of created orders
* @throws {Error} If checkoutId is invalid or order creation fails
*/
async createOrdersFromCheckout(checkoutId: number): Promise<Order[]> {
return this.#orderCreationService.createOrdersFromCheckout(checkoutId);
}
/**
* Retrieves logistician information.
*
* @param logisticianNumber - The logistician number to retrieve (defaults to '2470')
* @param abortSignal - Optional signal to abort the operation
* @returns Promise resolving to logistician data
* @throws {Error} If logistician is not found or request fails
*/
async getLogistician(
logisticianNumber = '2470',
abortSignal?: AbortSignal,
): Promise<LogisticianDTO> {
return this.#orderCreationService.getLogistician(
logisticianNumber,
abortSignal,
);
}
}

View File

@@ -1,6 +1,6 @@
import { canReturnReceiptItem } from './can-return-receipt-item.helper';
import { ReceiptItem } from '../../models/receipt-item';
import { Product } from '../../models/product';
import { Product } from '../../schemas/product.schema';
import { Quantity } from '../../models/quantity';
describe('canReturnReceiptItem', () => {

View File

@@ -1,6 +1,6 @@
import { getReceiptItemAction } from './get-receipt-item-action.helper';
import { ReceiptItem } from '../../models/receipt-item';
import { Product } from '../../models/product';
import { Product } from '../../schemas/product.schema';
import { Quantity } from '../../models/quantity';
describe('getReceiptItemAction', () => {

View File

@@ -1,6 +1,6 @@
import { getReceiptItemProductCategory } from './get-receipt-item-product-category.helper';
import { ReceiptItem } from '../../models/receipt-item';
import { Product } from '../../models/product';
import { Product } from '../../schemas/product.schema';
import { Quantity } from '../../models/quantity';
import { ProductCategory } from '../../questions/constants';

View File

@@ -1,12 +1,12 @@
import { isEmpty } from 'lodash';
import {
Product,
ReturnInfo,
ReturnProcessAnswers,
ReturnProcessQuestion,
ReturnProcessQuestionKey,
ReturnProcessQuestionType,
} from '../../models';
import { Product } from '../../schemas/product.schema';
import {
activeReturnProcessQuestions,
internalActiveReturnProcessQuestions,

View File

@@ -1,6 +1,6 @@
import { receiptItemHasCategory } from './receipt-item-has-category.helper';
import { ReceiptItem } from '../../models/receipt-item';
import { Product } from '../../models/product';
import { Product } from '../../schemas/product.schema';
import { Quantity } from '../../models/quantity';
import { ProductCategory } from '../../questions/constants';

View File

@@ -5,7 +5,6 @@ export * from './eligible-for-return';
export * from './gender';
export * from './logistician';
export * from './order';
export * from './product';
export * from './quantity';
export * from './receipt-item-list-item';
export * from './receipt-item-task-list-item';
@@ -21,5 +20,4 @@ export * from './return-process-question';
export * from './return-process-status';
export * from './return-process';
export * from './shipping-address-2';
export * from './shipping-type';
export * from './task-action-type';

View File

@@ -1,12 +0,0 @@
import { ProductDTO } from '@generated/swagger/oms-api';
export interface Product extends ProductDTO {
name: string;
contributors: string;
catalogProductNumber: string;
ean: string;
format: string;
formatDetail: string;
volume: string;
manufacturer: string;
}

View File

@@ -1,5 +1,5 @@
import { ReceiptItemListItemDTO } from '@generated/swagger/oms-api';
import { Product } from './product';
import { Product } from '../schemas/product.schema';
import { Quantity } from './quantity';
export interface ReceiptItemListItem extends ReceiptItemListItemDTO {

View File

@@ -1,5 +1,5 @@
import { ReceiptItemDTO } from '@generated/swagger/oms-api';
import { Product } from './product';
import { Product } from '../schemas/product.schema';
import { Quantity } from './quantity';
export interface ReceiptItem extends ReceiptItemDTO {

View File

@@ -1,4 +1,4 @@
import { Product } from './product';
import { Product } from '../schemas/product.schema';
import { ReturnProcessQuestionKey } from './return-process-question-key';
/**

View File

@@ -1,11 +0,0 @@
export enum ShippingType {
NotSet = 0,
PostCard = 1,
Letter = 2,
LargeLetter = 4,
BookRate = 8,
MerchandiseShipment = 16,
Parcel = 32,
Palette = 64,
MerchandiseShipmentSmall = 128,
}

View File

@@ -0,0 +1,23 @@
import {
AddressSchema,
CommunicationDetailsSchema,
ExternalReferenceSchema,
GenderSchema,
OrganisationSchema,
} from '@isa/common/data-access';
import { z } from 'zod';
export const DisplayAddresseeSchema = z.object({
number: z.string().describe('Number').optional(),
locale: z.string().describe('Locale').optional(),
gender: GenderSchema.describe('Gender'),
title: z.string().describe('Title').optional(),
firstName: z.string().describe('First name').optional(),
lastName: z.string().describe('Last name').optional(),
organisation: OrganisationSchema.describe('Organisation information').optional(),
address: AddressSchema.describe('Address').optional(),
communicationDetails: CommunicationDetailsSchema.describe('Communication details').optional(),
externalReference: ExternalReferenceSchema.describe('External system reference').optional(),
});
export type DisplayAddressee = z.infer<typeof DisplayAddresseeSchema>;

View File

@@ -0,0 +1,19 @@
import {
AddressSchema,
CommunicationDetailsSchema,
EntitySchema,
} from '@isa/common/data-access';
import { z } from 'zod';
export const DisplayBranchSchema = z
.object({
name: z.string().describe('Name').optional(),
key: z.string().describe('Key').optional(),
branchNumber: z.string().describe('Branch number').optional(),
address: AddressSchema.describe('Address').optional(),
communicationDetails: CommunicationDetailsSchema.describe('Communication details').optional(),
web: z.string().describe('Web').optional(),
})
.extend(EntitySchema.shape);
export type DisplayBranch = z.infer<typeof DisplayBranchSchema>;

View File

@@ -0,0 +1,12 @@
import { EntitySchema } from '@isa/common/data-access';
import { z } from 'zod';
export const DisplayLogisticianSchema = z
.object({
name: z.string().describe('Name').optional(),
logisticianNumber: z.string().describe('Logistician number').optional(),
gln: z.string().describe('Gln').optional(),
})
.extend(EntitySchema.shape);
export type DisplayLogistician = z.infer<typeof DisplayLogisticianSchema>;

View File

@@ -0,0 +1,26 @@
import { EntitySchema, QuantityUnitTypeSchema } from '@isa/common/data-access';
import { z } from 'zod';
import { PriceSchema } from './price.schema';
import { ProductSchema } from './product.schema';
import { PromotionSchema } from './promotion.schema';
// Forward declaration for circular reference
export const DisplayOrderItemSchema: z.ZodType<any> = z
.object({
buyerComment: z.string().describe('Buyer comment').optional(),
description: z.string().describe('Description text').optional(),
features: z.record(z.string().describe('Features'), z.string()).optional(),
order: z.lazy(() => z.any()).describe('Order').optional(), // Circular reference to DisplayOrder
orderDate: z.string().describe('Order date').optional(),
orderItemNumber: z.string().describe('OrderItem number').optional(),
price: PriceSchema.describe('Price information').optional(),
product: ProductSchema.describe('Product').optional(),
promotion: PromotionSchema.describe('Promotion information').optional(),
quantity: z.number().describe('Quantity').optional(),
quantityUnit: z.string().describe('Quantity unit').optional(),
quantityUnitType: QuantityUnitTypeSchema.describe('QuantityUnit type'),
subsetItems: z.array(z.lazy(() => z.any())).describe('Subset items').optional(), // Circular reference to DisplayOrderItemSubset
})
.extend(EntitySchema.shape);
export type DisplayOrderItem = z.infer<typeof DisplayOrderItemSchema>;

View File

@@ -0,0 +1,21 @@
import { EntitySchema, PaymentTypeSchema } from '@isa/common/data-access';
import { z } from 'zod';
export const DisplayOrderPaymentSchema = z
.object({
paymentActionRequired: z.boolean().describe('Payment action required'),
paymentType: PaymentTypeSchema.describe('Payment type'),
paymentNumber: z.string().describe('Payment number').optional(),
paymentComment: z.string().describe('Payment comment').optional(),
total: z.number().describe('Total amount'),
subtotal: z.number().describe('Subtotal').optional(),
shipping: z.number().describe('Shipping').optional(),
tax: z.number().describe('Tax amount').optional(),
currency: z.string().describe('Currency code').optional(),
dateOfPayment: z.string().describe('Date of payment').optional(),
completed: z.string().describe('Completed').optional(),
cancelled: z.string().describe('Cancelled').optional(),
})
.extend(EntitySchema.shape);
export type DisplayOrderPayment = z.infer<typeof DisplayOrderPaymentSchema>;

View File

@@ -0,0 +1,50 @@
import {
BuyerTypeSchema,
EntitySchema,
KeyValueOfStringAndStringSchema,
NotificationChannelSchema,
} from '@isa/common/data-access';
import { z } from 'zod';
import { DisplayAddresseeSchema } from './display-addressee.schema';
import { DisplayBranchSchema } from './display-branch.schema';
import { DisplayLogisticianSchema } from './display-logistician.schema';
import { DisplayOrderItemSchema } from './display-order-item.schema';
import { DisplayOrderPaymentSchema } from './display-order-payment.schema';
import { EnvironmentChannelSchema } from './environment-channel.schema';
import { LinkedRecordSchema } from './linked-record.schema';
import { OrderTypeSchema } from './order-type.schema';
import { TermsOfDeliverySchema } from './terms-of-delivery.schema';
export const DisplayOrderSchema = z
.object({
actions: z.array(KeyValueOfStringAndStringSchema).describe('Actions').optional(),
buyer: DisplayAddresseeSchema.describe('Buyer information').optional(),
buyerComment: z.string().describe('Buyer comment').optional(),
buyerIsGuestAccount: z.boolean().describe('Buyer is guest account').optional(),
buyerNumber: z.string().describe('Unique buyer identifier number').optional(),
buyerType: BuyerTypeSchema.describe('Buyer type').optional(),
clientChannel: EnvironmentChannelSchema.describe('Client channel').optional(),
completedDate: z.string().describe('Completed date').optional(),
features: z.record(z.string().describe('Features'), z.string()).optional(),
items: z.array(DisplayOrderItemSchema).describe('List of items').optional(),
itemsCount: z.number().describe('Number of itemss').optional(),
linkedRecords: z.array(LinkedRecordSchema).describe('List of linked records').optional(),
logistician: DisplayLogisticianSchema.describe('Logistician information').optional(),
notificationChannels: NotificationChannelSchema.describe('Notification channels').optional(),
orderBranch: DisplayBranchSchema.describe('Order branch').optional(),
orderDate: z.string().describe('Order date').optional(),
orderNumber: z.string().describe('Order number').optional(),
orderType: OrderTypeSchema.describe('Order type'),
orderValue: z.number().describe('Order value').optional(),
orderValueCurrency: z.string().describe('Order value currency').optional(),
payer: DisplayAddresseeSchema.describe('Payer information').optional(),
payerIsGuestAccount: z.boolean().describe('Payer is guest account').optional(),
payerNumber: z.string().describe('Unique payer account number').optional(),
payment: DisplayOrderPaymentSchema.describe('Payment').optional(),
shippingAddress: DisplayAddresseeSchema.describe('Shipping address information').optional(),
targetBranch: DisplayBranchSchema.describe('Target branch').optional(),
termsOfDelivery: TermsOfDeliverySchema.describe('Terms of delivery').optional(),
})
.extend(EntitySchema.shape);
export type DisplayOrder = z.infer<typeof DisplayOrderSchema>;

View File

@@ -0,0 +1,15 @@
import { z } from 'zod';
export const EnvironmentChannel = {
NotSet: 0,
System: 1,
Branch: 2,
CallCenter: 4,
WWW: 8,
Mobile: 16,
BackOffice: 32,
} as const;
export const EnvironmentChannelSchema = z.nativeEnum(EnvironmentChannel).describe('Environment channel');
export type EnvironmentChannel = z.infer<typeof EnvironmentChannelSchema>;

View File

@@ -1,4 +1,19 @@
export * from './display-addressee.schema';
export * from './display-branch.schema';
export * from './display-logistician.schema';
export * from './display-order-item.schema';
export * from './display-order-payment.schema';
export * from './display-order.schema';
export * from './environment-channel.schema';
export * from './fetch-return-details.schema';
export * from './linked-record.schema';
export * from './order-type.schema';
export * from './price.schema';
export * from './product.schema';
export * from './promotion.schema';
export * from './query-token.schema';
export * from './return-process-question-answer.schema';
export * from './return-receipt-values.schema';
export * from './shipping-type.schema';
export * from './terms-of-delivery.schema';
export * from './type-of-delivery.schema';

View File

@@ -0,0 +1,10 @@
import { z } from 'zod';
export const LinkedRecordSchema = z.object({
repository: z.string().describe('Repository').optional(),
pk: z.string().describe('Pk').optional(),
number: z.string().describe('Number').optional(),
isSource: z.boolean().describe('Whether source').optional(),
});
export type LinkedRecord = z.infer<typeof LinkedRecordSchema>;

View File

@@ -0,0 +1,14 @@
import { z } from 'zod';
export const OrderType = {
NotSet: 0,
Branch: 1,
Mail: 2,
Download: 4,
BranchAndDownload: 5, // Branch | Download
MailAndDownload: 6, // Mail | Download
} as const;
export const OrderTypeSchema = z.nativeEnum(OrderType).describe('Order type');
export type OrderType = z.infer<typeof OrderTypeSchema>;

View File

@@ -0,0 +1,15 @@
import {
PriceValueSchema,
TouchBaseSchema,
VatValueSchema,
} from '@isa/common/data-access';
import { z } from 'zod';
export const PriceSchema = z
.object({
value: PriceValueSchema.describe('Value').optional(),
vat: VatValueSchema.describe('Value Added Tax').optional(),
})
.extend(TouchBaseSchema.shape);
export type Price = z.infer<typeof PriceSchema>;

View File

@@ -0,0 +1,27 @@
import { TouchBaseSchema } from '@isa/common/data-access';
import { z } from 'zod';
export const ProductSchema = z
.object({
name: z.string().describe('Name').optional(),
additionalName: z.string().describe('Additional name').optional(),
ean: z.string().describe('European Article Number barcode').optional(),
supplierProductNumber: z.string().describe('SupplierProduct number').optional(),
catalogProductNumber: z.string().describe('CatalogProduct number').optional(),
contributors: z.string().describe('Contributors').optional(),
manufacturer: z.string().describe('Manufacturer information').optional(),
publicationDate: z.string().describe('Publication date').optional(),
productGroup: z.string().describe('Product group').optional(),
productGroupDetails: z.string().describe('Product group details').optional(),
edition: z.string().describe('Edition').optional(),
format: z.string().describe('Format').optional(),
formatDetail: z.string().describe('Format detail').optional(),
locale: z.string().describe('Locale').optional(),
serial: z.string().describe('Serial').optional(),
size: z.any().describe('Size').optional(), // TODO: Create SizeOfString schema
volume: z.string().describe('Volume').optional(),
weight: z.any().describe('Weight').optional(), // TODO: Create WeightOfAvoirdupois schema
})
.extend(TouchBaseSchema.shape);
export type Product = z.infer<typeof ProductSchema>;

View File

@@ -0,0 +1,13 @@
import { TouchBaseSchema } from '@isa/common/data-access';
import { z } from 'zod';
export const PromotionSchema = z
.object({
value: z.number().describe('Value').optional(),
type: z.string().describe('Type').optional(),
code: z.string().describe('Code value').optional(),
label: z.string().describe('Label').optional(),
})
.extend(TouchBaseSchema.shape);
export type Promotion = z.infer<typeof PromotionSchema>;

View File

@@ -1,19 +1,19 @@
import { z } from 'zod';
import { Product } from '../models/product';
import { Product } from './product.schema';
const ReceiptItemSchema = z.object({
id: z.number(),
id: z.number().describe('Unique identifier'),
});
export const ReturnReceiptValuesSchema = z.object({
quantity: z.number(),
category: z.string().optional(),
comment: z.string().optional(),
itemCondition: z.string().optional(),
returnDetails: z.string().optional(),
returnReason: z.string().optional(),
receiptItem: ReceiptItemSchema,
otherProduct: z.custom<Product>().optional(),
quantity: z.number().describe('Quantity'),
category: z.string().describe('Category information').optional(),
comment: z.string().describe('Comment text').optional(),
itemCondition: z.string().describe('Item condition').optional(),
returnDetails: z.string().describe('Return details').optional(),
returnReason: z.string().describe('Return reason').optional(),
receiptItem: ReceiptItemSchema.describe('Receipt item'),
otherProduct: z.custom<Product>().describe('Other product').optional(),
});
export type ReturnReceiptValues = z.infer<typeof ReturnReceiptValuesSchema>;

View File

@@ -0,0 +1,17 @@
import { z } from 'zod';
export const ShippingType = {
NotSet: 0,
Parcel: 1,
Letter: 2,
Pallet: 4,
Freight: 8,
Digital: 16,
InStore: 32,
ClickAndCollect: 64,
Dropship: 128,
} as const;
export const ShippingTypeSchema = z.nativeEnum(ShippingType).describe('Shipping type');
export type ShippingType = z.infer<typeof ShippingTypeSchema>;

View File

@@ -0,0 +1,18 @@
import { PriceValueSchema, TouchBaseSchema } from '@isa/common/data-access';
import { z } from 'zod';
import { ShippingTypeSchema } from './shipping-type.schema';
import { TypeOfDeliverySchema } from './type-of-delivery.schema';
export const TermsOfDeliverySchema = z
.object({
isPartialShipping: z.boolean().describe('Whether partialShipping').optional(),
partialShippingCharge: z.number().describe('Partial shipping charge').optional(),
postage: PriceValueSchema.describe('Postage').optional(),
typeOfDelivery: TypeOfDeliverySchema.describe('Type of delivery').optional(),
shippingType: ShippingTypeSchema.describe('Shipping type').optional(),
shippingDeadlineStart: z.string().describe('Shipping deadline start').optional(),
shippingDeadlineEnd: z.string().describe('Shipping deadline end').optional(),
})
.extend(TouchBaseSchema.shape);
export type TermsOfDelivery = z.infer<typeof TermsOfDeliverySchema>;

View File

@@ -0,0 +1,16 @@
import { z } from 'zod';
export const TypeOfDelivery = {
NotSet: 0,
Standard: 1,
Express: 2,
SameDay: 4,
Pickup: 8,
Economy: 16,
Overnight: 32,
International: 64,
} as const;
export const TypeOfDeliverySchema = z.nativeEnum(TypeOfDelivery).describe('Type of delivery');
export type TypeOfDelivery = z.infer<typeof TypeOfDeliverySchema>;

View File

@@ -1,9 +1,10 @@
export * from './logistician.service';
export * from './order-creation.service';
export * from './return-can-return.service';
export * from './return-details.service';
export * from './print-receipts.service';
export * from './return-process.service';
export * from './return-search.service';
export * from './return-task-list.service';
export * from './print-tolino-return-receipt.service';
export * from './logistician.service';
export * from './oms-metadata.service';
export * from './order-creation.service';
export * from './print-receipts.service';
export * from './print-tolino-return-receipt.service';
export * from './return-can-return.service';
export * from './return-details.service';
export * from './return-process.service';
export * from './return-search.service';
export * from './return-task-list.service';

View File

@@ -0,0 +1,32 @@
import { inject, Injectable } from '@angular/core';
import { TabService, getMetadataHelper } from '@isa/core/tabs';
import { DisplayOrder, DisplayOrderSchema } from '../schemas';
import { OMS_DISPLAY_ORDERS_KEY } from '../constants';
import z from 'zod';
@Injectable({ providedIn: 'root' })
export class OmsMetadataService {
#tabService = inject(TabService);
getDisplayOrders(tabId: number): DisplayOrder[] | undefined {
return getMetadataHelper(
tabId,
OMS_DISPLAY_ORDERS_KEY,
z.array(DisplayOrderSchema).optional(),
this.#tabService.entityMap(),
);
}
addDisplayOrders(tabId: number, orders: DisplayOrder[]) {
const existingOrders = this.getDisplayOrders(tabId) || [];
this.#tabService.patchTabMetadata(tabId, {
[OMS_DISPLAY_ORDERS_KEY]: [...existingOrders, ...orders],
});
}
clearDisplayOrders(tabId: number) {
this.#tabService.patchTabMetadata(tabId, {
[OMS_DISPLAY_ORDERS_KEY]: undefined,
});
}
}

View File

@@ -5,7 +5,8 @@ import { TabService } from '@isa/core/tabs';
import { patchState } from '@ngrx/signals';
import { setAllEntities, setEntity } from '@ngrx/signals/entities';
import { unprotected } from '@ngrx/signals/testing';
import { Product, ReturnProcess } from '../models';
import { Product } from '../schemas';
import { ReturnProcess } from '../models';
import { CreateReturnProcessError } from '../errors/return-process';
import { ProductCategory } from '../questions';

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff