mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
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:
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const FetchStockInStockSchema = z.object({
|
||||
stockId: z.number().optional(),
|
||||
itemIds: z.array(z.number()),
|
||||
stockId: z.number().describe('Stock identifier').optional(),
|
||||
itemIds: z.array(z.number()).describe('Item ids'),
|
||||
});
|
||||
|
||||
export type FetchStockInStock = z.infer<typeof FetchStockInStockSchema>;
|
||||
|
||||
@@ -1,7 +1,299 @@
|
||||
# remi-remission-list
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test remi-remission-list` to execute the unit tests.
|
||||
# @isa/remission/feature/remission-list
|
||||
|
||||
Feature module providing the main remission list view with filtering, searching, item selection, and remitting capabilities for department ("Abteilung") and mandatory ("Pflicht") return workflows.
|
||||
|
||||
## Overview
|
||||
|
||||
The Remission List Feature library implements the core remission workflow interface where users search, select, and remit items. It supports two remission types: Abteilung (department-based suggestions) and Pflicht (mandatory returns). The component integrates filtering, stock information, product groups, and provides sophisticated user interactions including automatic item selection (#5338), empty search handling, and batch remitting operations.
|
||||
|
||||
## Features
|
||||
|
||||
- **Dual Remission Types** - Abteilung (suggestions) and Pflicht (mandatory) lists
|
||||
- **Advanced Filtering** - FilterService integration with query parameter sync
|
||||
- **Product Search** - Real-time search with scanner support
|
||||
- **Stock Information** - Batched stock fetching for displayed items
|
||||
- **Product Groups** - Dynamic product group resolution
|
||||
- **Item Selection** - Multi-select with quantity tracking
|
||||
- **Batch Remitting** - Add multiple items to return receipt
|
||||
- **Auto-Selection** - Single item auto-select (#5338)
|
||||
- **Empty Search Handling** - Dialog to add unlisted items
|
||||
- **Resource-Based Data** - Efficient data fetching with resource pattern
|
||||
- **State Management** - RemissionStore integration
|
||||
- **Error Handling** - Comprehensive error dialogs
|
||||
- **Scroll Position** - Automatic scroll restoration
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { RemissionListComponent } from '@isa/remission/feature/remission-list';
|
||||
import { provideRemissionListRoutes } from '@isa/remission/feature/remission-list';
|
||||
|
||||
// In routes configuration
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: 'remission',
|
||||
loadChildren: () => import('@isa/remission/feature/remission-list').then(m => m.routes)
|
||||
}
|
||||
];
|
||||
|
||||
// Routes include:
|
||||
// - remission (default Pflicht list)
|
||||
// - remission/abteilung (department list)
|
||||
```
|
||||
|
||||
## Component API
|
||||
|
||||
### RemissionListComponent
|
||||
|
||||
Main list view component for remission workflows.
|
||||
|
||||
**Selector**: `remi-feature-remission-list`
|
||||
|
||||
**Route Data Requirements**:
|
||||
- `querySettings`: Filter query settings from resolver
|
||||
|
||||
**Key Signals**:
|
||||
```typescript
|
||||
selectedRemissionListType = injectRemissionListType(); // 'Abteilung' | 'Pflicht'
|
||||
remissionStarted = computed(() => store.remissionStarted());
|
||||
items = computed(() => remissionResource.value()?.result || []);
|
||||
stockInfoMap = computed(() => new Map(stockData.map(i => [i.itemId, i])));
|
||||
hasSelectedItems = computed(() => Object.keys(store.selectedItems()).length > 0);
|
||||
```
|
||||
|
||||
**Key Methods**:
|
||||
- `search(trigger: SearchTrigger): void` - Trigger filtered search
|
||||
- `remitItems(options?): Promise<void>` - Remit selected items to receipt
|
||||
- `reloadListAndReturnData(): void` - Refresh list and receipt data
|
||||
- `getStockForItem(item): StockInfo | undefined` - Get stock for item
|
||||
- `getProductGroupValueForItem(item): string | undefined` - Get product group name
|
||||
|
||||
## Resources
|
||||
|
||||
### RemissionListResource
|
||||
|
||||
Fetches remission items based on type and filters.
|
||||
|
||||
**Parameters**:
|
||||
- `remissionListType`: 'Abteilung' | 'Pflicht'
|
||||
- `queryToken`: Filter query
|
||||
- `searchTrigger`: Trigger type
|
||||
|
||||
### RemissionInStockResource
|
||||
|
||||
Batches stock information for visible items.
|
||||
|
||||
**Parameters**:
|
||||
- `itemIds`: Array of catalog product numbers
|
||||
|
||||
### RemissionProductGroupResource
|
||||
|
||||
Fetches product group key-value mappings.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Remission List Integration
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
import { RemissionListComponent } from '@isa/remission/feature/remission-list';
|
||||
|
||||
@Component({
|
||||
selector: 'app-remission-page',
|
||||
imports: [RemissionListComponent],
|
||||
template: `
|
||||
<remi-feature-remission-list></remi-feature-remission-list>
|
||||
`
|
||||
})
|
||||
export class RemissionPageComponent {}
|
||||
```
|
||||
|
||||
### Remitting Items Programmatically
|
||||
|
||||
```typescript
|
||||
import { RemissionListComponent } from '@isa/remission/feature/remission-list';
|
||||
|
||||
@Component({
|
||||
selector: 'app-custom-remission',
|
||||
viewChild: RemissionListComponent
|
||||
})
|
||||
export class CustomRemissionComponent {
|
||||
remissionList = viewChild.required(RemissionListComponent);
|
||||
|
||||
async remitSelectedItems() {
|
||||
await this.remissionList().remitItems();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Search Handling
|
||||
|
||||
```typescript
|
||||
import { RemissionListComponent } from '@isa/remission/feature/remission-list';
|
||||
|
||||
@Component({})
|
||||
export class EnhancedRemissionListComponent extends RemissionListComponent {
|
||||
override async search(trigger: SearchTrigger) {
|
||||
console.log('Search triggered:', trigger);
|
||||
super.search(trigger);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Workflow Details
|
||||
|
||||
### Search Flow
|
||||
|
||||
1. User enters search term or scans barcode
|
||||
2. FilterService commits query
|
||||
3. RemissionListResource fetches filtered items
|
||||
4. InStockResource fetches stock for results
|
||||
5. ProductGroupResource resolves group names
|
||||
6. Items displayed with stock/group information
|
||||
|
||||
### Empty Search Handling
|
||||
|
||||
If search returns no results:
|
||||
1. Check if search was user-initiated (#5338)
|
||||
2. Open SearchItemToRemitDialog
|
||||
3. User searches catalog and selects item
|
||||
4. If remission started, add item and remit automatically
|
||||
5. If Abteilung list, navigate to default Pflicht list
|
||||
6. Reload list data
|
||||
|
||||
### Auto-Selection (#5338)
|
||||
|
||||
When search returns single item and remission started:
|
||||
- Automatically select the item
|
||||
- Pre-fill with calculated stock to remit
|
||||
- User can adjust quantity before remitting
|
||||
|
||||
### Remitting Flow
|
||||
|
||||
1. User selects items from list
|
||||
2. For each item:
|
||||
- Calculate quantity to remit
|
||||
- Get available stock
|
||||
- Call `remitItem` API with returnId/receiptId
|
||||
3. Handle errors with dialog
|
||||
4. Clear selections
|
||||
5. Reload list and receipt data
|
||||
6. Show success/error state
|
||||
|
||||
### Error Handling
|
||||
|
||||
**RemissionResponseArgsErrorMessage.AlreadyCompleted**:
|
||||
- Clear RemissionStore state
|
||||
- Show error dialog
|
||||
- Reload data
|
||||
|
||||
**Other Errors**:
|
||||
- Log error with context
|
||||
- Show error dialog with message
|
||||
- Set button to error state
|
||||
- Reload data
|
||||
|
||||
## Components
|
||||
|
||||
### RemissionListItemComponent
|
||||
|
||||
Individual list item with selection controls.
|
||||
|
||||
**Features**:
|
||||
- Product information display
|
||||
- Stock information
|
||||
- Quantity input
|
||||
- Selection checkbox
|
||||
- Delete action
|
||||
|
||||
### RemissionListSelectComponent
|
||||
|
||||
Type selector (Abteilung/Pflicht).
|
||||
|
||||
### RemissionStartCardComponent
|
||||
|
||||
Card for starting new remission.
|
||||
|
||||
### RemissionReturnCardComponent
|
||||
|
||||
Card showing current return receipt.
|
||||
|
||||
### RemissionListEmptyStateComponent
|
||||
|
||||
Empty state when no items found.
|
||||
|
||||
## Routing
|
||||
|
||||
```typescript
|
||||
import { routes } from '@isa/remission/feature/remission-list';
|
||||
|
||||
// Default route: Pflicht list
|
||||
{
|
||||
path: '',
|
||||
component: RemissionListComponent,
|
||||
resolve: {
|
||||
querySettings: querySettingsResolverFn
|
||||
}
|
||||
}
|
||||
|
||||
// Abteilung route
|
||||
{
|
||||
path: 'abteilung',
|
||||
component: RemissionListComponent,
|
||||
resolve: {
|
||||
querySettings: querySettingsDepartmentResolverFn
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
**RemissionStore Integration**:
|
||||
```typescript
|
||||
// Selection
|
||||
store.selectRemissionItem(itemId, item);
|
||||
store.clearSelectedItems();
|
||||
|
||||
// Quantity
|
||||
store.selectedQuantity()[itemId];
|
||||
|
||||
// Current remission
|
||||
store.remissionStarted();
|
||||
store.returnId();
|
||||
store.receiptId();
|
||||
|
||||
// State clearing
|
||||
store.clearState();
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
npx nx test remission-feature-remission-list --skip-nx-cache
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@angular/core` - Angular framework
|
||||
- `@angular/router` - Routing
|
||||
- `@isa/remission/data-access` - RemissionStore, services, types
|
||||
- `@isa/remission/shared/product` - Product components
|
||||
- `@isa/remission/shared/search-item-to-remit-dialog` - Search dialog
|
||||
- `@isa/shared/filter` - Filtering infrastructure
|
||||
- `@isa/ui/buttons` - Button components
|
||||
- `@isa/ui/dialog` - Dialogs
|
||||
- `@isa/utils/scroll-position` - Scroll restoration
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
**Resource Pattern**: Uses Angular resource pattern for efficient data fetching with automatic dependency tracking.
|
||||
|
||||
**Filter Integration**: Deep FilterService integration with query parameter synchronization for shareable URLs.
|
||||
|
||||
**Computed Signals**: Extensive use of computed signals for derived state.
|
||||
|
||||
**Effects**: React to search results with automatic UI interactions (dialogs, auto-select).
|
||||
|
||||
## License
|
||||
|
||||
Internal ISA Frontend library - not for external distribution.
|
||||
|
||||
@@ -1,7 +1,145 @@
|
||||
# remission-feature-remission-return-receipt-details
|
||||
# @isa/remission/feature/remission-return-receipt-details
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
Feature component for displaying detailed view of a return receipt ("Warenbegleitschein") with items, actions, and completion workflows.
|
||||
|
||||
## Running unit tests
|
||||
## Overview
|
||||
|
||||
Run `nx test remission-feature-remission-return-receipt-details` to execute the unit tests.
|
||||
The Remission Return Receipt Details feature provides a comprehensive view of a single return receipt including all items, receipt metadata, package information, and action buttons for managing the receipt. It integrates with return-receipt-actions components for deletion, continuation, and completion workflows.
|
||||
|
||||
## Features
|
||||
|
||||
- **Receipt Details Display** - Receipt number, creation date, status
|
||||
- **Item List** - All items in the receipt with product information
|
||||
- **Package Information** - Assigned package numbers
|
||||
- **Action Buttons** - Delete, continue, complete actions
|
||||
- **Empty State** - User-friendly empty receipt view
|
||||
- **Loading States** - Skeleton loaders during data fetch
|
||||
- **Resource-Based Data** - Efficient return data fetching
|
||||
- **Navigation** - Back button to return to previous view
|
||||
- **Eager Loading** - Preload 3 levels of related data
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { RemissionReturnReceiptDetailsComponent } from '@isa/remission/feature/remission-return-receipt-details';
|
||||
|
||||
// In routes
|
||||
{
|
||||
path: 'receipt/:returnId/:receiptId',
|
||||
component: RemissionReturnReceiptDetailsComponent
|
||||
}
|
||||
|
||||
// Usage
|
||||
<a [routerLink]="['/receipt', returnId, receiptId]">View Receipt</a>
|
||||
```
|
||||
|
||||
## Component API
|
||||
|
||||
### RemissionReturnReceiptDetailsComponent
|
||||
|
||||
Standalone component for receipt details view.
|
||||
|
||||
**Selector**: `remi-remission-return-receipt-details`
|
||||
|
||||
**Inputs** (from route params):
|
||||
- `returnId`: number (required, coerced from string)
|
||||
- `receiptId`: number (required, coerced from string)
|
||||
|
||||
**Computed Signals**:
|
||||
```typescript
|
||||
returnLoading = computed(() => returnResource.isLoading());
|
||||
returnData = computed(() => returnResource.value());
|
||||
receiptNumber = computed(() => getReceiptNumberFromReturn(returnData()));
|
||||
receiptItems = computed(() => getReceiptItemsFromReturn(returnData()));
|
||||
canRemoveItems = computed(() => !returnData().completed);
|
||||
hasAssignedPackage = computed(() => getPackageNumbersFromReturn(returnData()) !== '');
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### ReturnResource
|
||||
|
||||
Fetches return with receipt data.
|
||||
|
||||
**Parameters**:
|
||||
- `returnId`: number
|
||||
- `eagerLoading`: number (levels to preload, default 3)
|
||||
|
||||
**Returns**: `Return` with nested receipts and items
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
import { RemissionReturnReceiptDetailsComponent } from '@isa/remission/feature/remission-return-receipt-details';
|
||||
|
||||
@Component({
|
||||
selector: 'app-receipt-page',
|
||||
imports: [RemissionReturnReceiptDetailsComponent],
|
||||
template: `
|
||||
<remi-remission-return-receipt-details
|
||||
[returnId]="returnId()"
|
||||
[receiptId]="receiptId()"
|
||||
></remi-remission-return-receipt-details>
|
||||
`
|
||||
})
|
||||
export class ReceiptPageComponent {
|
||||
returnId = input.required<number>();
|
||||
receiptId = input.required<number>();
|
||||
}
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### RemissionReturnReceiptDetailsCardComponent
|
||||
|
||||
Card displaying receipt metadata.
|
||||
|
||||
### RemissionReturnReceiptDetailsItemComponent
|
||||
|
||||
Individual receipt item display.
|
||||
|
||||
### RemissionReturnReceiptActionsComponent
|
||||
|
||||
Action buttons (delete, continue) from shared library.
|
||||
|
||||
### RemissionReturnReceiptCompleteComponent
|
||||
|
||||
Complete button from shared library.
|
||||
|
||||
## Helper Functions
|
||||
|
||||
```typescript
|
||||
// From @isa/remission/data-access
|
||||
getReceiptNumberFromReturn(return: Return): string
|
||||
getReceiptItemsFromReturn(return: Return): ReceiptItem[]
|
||||
getPackageNumbersFromReturn(return: Return): string
|
||||
```
|
||||
|
||||
## Empty State
|
||||
|
||||
**When Shown**: No return data or empty receipt
|
||||
|
||||
**Content**:
|
||||
- Title: "Kein Warenbegleitschein vorhanden"
|
||||
- Description: "Es wurde kein Warenbegleitschein gefunden."
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
npx nx test remission-return-receipt-details --skip-nx-cache
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@angular/core` - Angular framework
|
||||
- `@angular/common` - Location service
|
||||
- `@isa/remission/data-access` - Return types, helper functions
|
||||
- `@isa/remission/shared/return-receipt-actions` - Action components
|
||||
- `@isa/ui/buttons` - Button components
|
||||
- `@isa/ui/empty-state` - Empty state component
|
||||
- `@isa/icons` - Icons
|
||||
|
||||
## License
|
||||
|
||||
Internal ISA Frontend library - not for external distribution.
|
||||
|
||||
@@ -1,7 +1,183 @@
|
||||
# remission-feature-remission-return-receipt-list
|
||||
# @isa/remission/feature/remission-return-receipt-list
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
Feature component providing a comprehensive list view of all return receipts with filtering, sorting, and action capabilities.
|
||||
|
||||
## Running unit tests
|
||||
## Overview
|
||||
|
||||
Run `nx test remission-feature-remission-return-receipt-list` to execute the unit tests.
|
||||
The Remission Return Receipt List feature displays all return receipts (Warenbegleitscheine) in a filterable, sortable list view. It separates completed and incomplete receipts, provides date-based sorting, and integrates with return-receipt-actions for management operations. The component uses resource-based data fetching for optimal performance.
|
||||
|
||||
## Features
|
||||
|
||||
- **Completed/Incomplete Separation** - Separate resource fetching
|
||||
- **Date Sorting** - Sort by created or completed date
|
||||
- **Filtering** - FilterService integration with query params
|
||||
- **Action Integration** - Delete, continue, complete buttons
|
||||
- **Resource Pattern** - Efficient parallel data fetching
|
||||
- **Reload Capability** - Refresh list after actions
|
||||
- **Responsive Layout** - Card-based list design
|
||||
- **Empty States** - User-friendly empty list views
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { RemissionReturnReceiptListComponent } from '@isa/remission/feature/remission-return-receipt-list';
|
||||
|
||||
// In routes
|
||||
{
|
||||
path: 'receipts',
|
||||
component: RemissionReturnReceiptListComponent
|
||||
}
|
||||
```
|
||||
|
||||
## Component API
|
||||
|
||||
### RemissionReturnReceiptListComponent
|
||||
|
||||
Standalone list component for return receipts.
|
||||
|
||||
**Selector**: `remi-remission-return-receipt-list`
|
||||
|
||||
**Computed Signals**:
|
||||
```typescript
|
||||
orderDateBy = computed(() => filterService.orderBy().find(o => o.selected));
|
||||
completedRemissionReturnsResourceValue = computed(() => completedResource.value() || []);
|
||||
incompletedRemissionReturnsResourceValue = computed(() => incompletedResource.value() || []);
|
||||
returns = computed(() => {
|
||||
// Combines and sorts completed + incomplete
|
||||
// Flattens receipts into [Return, Receipt] tuples
|
||||
});
|
||||
```
|
||||
|
||||
**Methods**:
|
||||
- `reloadList(): void` - Reload both completed and incomplete resources
|
||||
|
||||
## Resources
|
||||
|
||||
### CompletedRemissionReturnsResource
|
||||
|
||||
Fetches completed return receipts.
|
||||
|
||||
**API**: `/api/remission/returns?completed=true`
|
||||
|
||||
### IncompletedRemissionReturnsResource
|
||||
|
||||
Fetches incomplete return receipts.
|
||||
|
||||
**API**: `/api/remission/returns?completed=false`
|
||||
|
||||
## Sorting
|
||||
|
||||
**Sort Fields**:
|
||||
- `created` - Receipt creation date
|
||||
- `completed` - Receipt completion date
|
||||
|
||||
**Sort Direction**:
|
||||
- `asc` - Ascending (oldest first)
|
||||
- `desc` - Descending (newest first)
|
||||
|
||||
**Implementation**:
|
||||
```typescript
|
||||
const compareFn = (a: string, b: string) => {
|
||||
return (orderBy.dir === 'desc' ? compareDesc : compareAsc)(a, b);
|
||||
};
|
||||
|
||||
completed = orderByKey(completed, 'created', compareFn);
|
||||
incompleted = orderByKey(incompleted, 'created', compareFn);
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
import { RemissionReturnReceiptListComponent } from '@isa/remission/feature/remission-return-receipt-list';
|
||||
|
||||
@Component({
|
||||
selector: 'app-receipts-page',
|
||||
imports: [RemissionReturnReceiptListComponent],
|
||||
template: `
|
||||
<div class="page-container">
|
||||
<h1>Return Receipts</h1>
|
||||
<remi-remission-return-receipt-list></remi-remission-return-receipt-list>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class ReceiptsPageComponent {}
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### ReturnReceiptListItemComponent
|
||||
|
||||
Individual receipt list item.
|
||||
|
||||
**Features**:
|
||||
- Receipt metadata display
|
||||
- Status indicator (completed/incomplete)
|
||||
- Item count
|
||||
- Action buttons
|
||||
|
||||
### ReturnReceiptListCardComponent
|
||||
|
||||
Card wrapper for list items.
|
||||
|
||||
## Query Settings
|
||||
|
||||
**Default Configuration**:
|
||||
```typescript
|
||||
{
|
||||
orderBy: [
|
||||
{ by: 'created', label: 'Erstellungsdatum', dir: 'desc', selected: true },
|
||||
{ by: 'completed', label: 'Abschlussdatum', dir: 'desc', selected: false }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Data Structure
|
||||
|
||||
**Return Receipt Tuple**:
|
||||
```typescript
|
||||
type ReturnReceiptTuple = [Return, Receipt];
|
||||
|
||||
// Return contains:
|
||||
{
|
||||
id: number;
|
||||
returnGroup: string;
|
||||
completed: boolean;
|
||||
receipts: Array<{ id: number; data: Receipt }>;
|
||||
}
|
||||
|
||||
// Receipt contains:
|
||||
{
|
||||
number: string;
|
||||
created: string;
|
||||
completed?: string;
|
||||
items: ReceiptItem[];
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
npx nx test remission-return-receipt-list --skip-nx-cache
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@angular/core` - Angular framework
|
||||
- `@angular/router` - Routing
|
||||
- `@isa/remission/data-access` - Return types
|
||||
- `@isa/shared/filter` - FilterService
|
||||
- `@isa/ui/buttons` - Button components
|
||||
- `date-fns` - Date comparison utilities
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
**Parallel Resource Fetching**: Completed and incomplete receipts are fetched in parallel for optimal performance.
|
||||
|
||||
**Tuple Flattening**: Returns with multiple receipts are flattened into [Return, Receipt] tuples for easier iteration.
|
||||
|
||||
**Filter Integration**: Uses FilterService with query parameter synchronization for shareable filtered views.
|
||||
|
||||
## License
|
||||
|
||||
Internal ISA Frontend library - not for external distribution.
|
||||
|
||||
@@ -1,7 +1,237 @@
|
||||
# remission-shared-product
|
||||
# @isa/remission/shared/product
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
A collection of Angular standalone components for displaying product information in remission workflows, including product details, stock information, and shelf metadata.
|
||||
|
||||
## Running unit tests
|
||||
## Overview
|
||||
|
||||
Run `nx test remission-shared-product` to execute the unit tests.
|
||||
The Remission Shared Product library provides three specialized presentation components designed for remission list views and return receipt workflows. These components display comprehensive product information including images, pricing, stock levels, shelf locations, and product group classifications. All components follow OnPush change detection for optimal performance and use Angular signals for reactive data management.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Component API](#component-api)
|
||||
- [Usage Examples](#usage-examples)
|
||||
- [Component Details](#component-details)
|
||||
- [Styling and Layout](#styling-and-layout)
|
||||
- [Testing](#testing)
|
||||
- [Architecture Notes](#architecture-notes)
|
||||
- [Dependencies](#dependencies)
|
||||
|
||||
## Features
|
||||
|
||||
- **Product Information Display** - Comprehensive product presentation with image, contributors, name, price, format, and EAN
|
||||
- **Stock Information Tracking** - Real-time stock display with current, remit, target, and ZOB quantities
|
||||
- **Shelf Metadata Presentation** - Department, shelf label, product group, assortment, and return reason display
|
||||
- **Flexible Orientation** - Horizontal or vertical layout options for product info component
|
||||
- **Loading States** - Skeleton loader support for stock information during fetch operations
|
||||
- **Responsive Design** - Tailwind CSS with ISA design system integration
|
||||
- **E2E Testing Attributes** - Comprehensive data-* attributes for automated testing
|
||||
- **Type-Safe Inputs** - Angular signal-based inputs with proper type definitions
|
||||
- **OnPush Change Detection** - Optimized performance with OnPush strategy
|
||||
- **Standalone Components** - All components are standalone with explicit imports
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Import Components
|
||||
|
||||
```typescript
|
||||
import {
|
||||
ProductInfoComponent,
|
||||
ProductStockInfoComponent,
|
||||
ProductShelfMetaInfoComponent
|
||||
} from '@isa/remission/shared/product';
|
||||
|
||||
@Component({
|
||||
selector: 'app-remission-item',
|
||||
imports: [
|
||||
ProductInfoComponent,
|
||||
ProductStockInfoComponent,
|
||||
ProductShelfMetaInfoComponent
|
||||
],
|
||||
template: '...'
|
||||
})
|
||||
export class RemissionItemComponent {
|
||||
// Component logic
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Display Product Information
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
<remi-product-info
|
||||
[item]="remissionItem()"
|
||||
[orientation]="'horizontal'"
|
||||
></remi-product-info>
|
||||
`
|
||||
})
|
||||
export class MyComponent {
|
||||
remissionItem = signal<ProductInfoItem>({
|
||||
product: {
|
||||
ean: '9781234567890',
|
||||
name: 'Product Name',
|
||||
contributors: 'Author Name',
|
||||
format: 'Hardcover',
|
||||
formatDetail: '256 pages'
|
||||
},
|
||||
retailPrice: {
|
||||
value: { value: 29.99, currency: 'EUR', currencySymbol: '€' },
|
||||
vat: { value: 4.78, inPercent: 19, label: '19%', vatType: 1 }
|
||||
},
|
||||
tag: 'Prio 1'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Display Stock Information
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
<remi-product-stock-info
|
||||
[availableStock]="45"
|
||||
[stockToRemit]="10"
|
||||
[targetStock]="35"
|
||||
[zob]="20"
|
||||
[stockFetching]="loading()"
|
||||
></remi-product-stock-info>
|
||||
`
|
||||
})
|
||||
export class MyComponent {
|
||||
loading = signal(false);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Display Shelf Metadata
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
<remi-product-shelf-meta-info
|
||||
[department]="'Reise'"
|
||||
[shelfLabel]="'Europa'"
|
||||
[productGroupKey]="'RG001'"
|
||||
[productGroupValue]="'Reiseführer'"
|
||||
[assortment]="'Basissortiment|BPrämienartikel|n'"
|
||||
[returnReason]="'Überbestand'"
|
||||
></remi-product-shelf-meta-info>
|
||||
`
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
## Component API
|
||||
|
||||
### ProductInfoComponent
|
||||
|
||||
Display component for comprehensive product information with image, pricing, and metadata.
|
||||
|
||||
#### Selector
|
||||
|
||||
```html
|
||||
<remi-product-info></remi-product-info>
|
||||
```
|
||||
|
||||
#### Inputs
|
||||
|
||||
| Input | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `item` | `ProductInfoItem` | Yes | - | Product data including product details, price, and tag |
|
||||
| `orientation` | `'horizontal' \| 'vertical'` | No | `'horizontal'` | Layout orientation for product display |
|
||||
| `innerGridClass` | `string` | No | `'grid-cols-[minmax(20rem,1fr),auto]'` | Custom grid CSS classes for inner layout |
|
||||
|
||||
#### ProductInfoItem Type
|
||||
|
||||
```typescript
|
||||
type ProductInfoItem = Pick<RemissionItem, 'product' | 'retailPrice' | 'tag'>;
|
||||
|
||||
// Full structure:
|
||||
{
|
||||
product: {
|
||||
ean: string;
|
||||
name: string;
|
||||
contributors: string;
|
||||
format: string;
|
||||
formatDetail?: string;
|
||||
};
|
||||
retailPrice?: {
|
||||
value: { value: number; currency: string; currencySymbol: string };
|
||||
vat: { value: number; inPercent: number; label: string; vatType: number };
|
||||
};
|
||||
tag?: 'Prio 1' | 'Prio 2' | 'Pflicht';
|
||||
}
|
||||
```
|
||||
|
||||
### ProductStockInfoComponent
|
||||
|
||||
Display component for product stock information including current, remit, target, and ZOB quantities.
|
||||
|
||||
#### Selector
|
||||
|
||||
```html
|
||||
<remi-product-stock-info></remi-product-stock-info>
|
||||
```
|
||||
|
||||
#### Inputs
|
||||
|
||||
| Input | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `stockFetching` | `boolean` | No | `false` | Loading state indicator for skeleton loader |
|
||||
| `availableStock` | `number` | No | `0` | Current available stock after removals |
|
||||
| `stockToRemit` | `number` | No | `0` | Remission quantity |
|
||||
| `targetStock` | `number` | No | `0` | Remaining stock after predefined return |
|
||||
| `zob` | `number` | No | `0` | Min Stock Category Management |
|
||||
|
||||
### ProductShelfMetaInfoComponent
|
||||
|
||||
Display component for product shelf metadata including department, location, product group, assortment, and return reason.
|
||||
|
||||
#### Selector
|
||||
|
||||
```html
|
||||
<remi-product-shelf-meta-info></remi-product-shelf-meta-info>
|
||||
```
|
||||
|
||||
#### Inputs
|
||||
|
||||
| Input | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `department` | `string` | No | `''` | Department name |
|
||||
| `shelfLabel` | `string` | No | `''` | Shelf label |
|
||||
| `productGroupKey` | `string` | No | `''` | Product group key |
|
||||
| `productGroupValue` | `string` | No | `''` | Product group value |
|
||||
| `assortment` | `string` | No | `''` | Assortment string (pipe-delimited) |
|
||||
| `returnReason` | `string` | No | `''` | Return reason |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
See the [full documentation](#quick-start) above for detailed usage examples.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
npx nx test remission-shared-product --skip-nx-cache
|
||||
|
||||
# Run tests with coverage
|
||||
npx nx test remission-shared-product --code-coverage --skip-nx-cache
|
||||
```
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
All components are pure presentation components following OnPush change detection strategy. They use Angular signals for reactive state management and include comprehensive E2E testing attributes.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@angular/core` - Angular framework
|
||||
- `@isa/remission/data-access` - RemissionItem types
|
||||
- `@isa/shared/product-image` - Product image directive
|
||||
- `@isa/ui/label` - Label component
|
||||
- `@isa/ui/skeleton-loader` - Loading skeleton
|
||||
- `@isa/icons` - Icon library
|
||||
|
||||
## License
|
||||
|
||||
Internal ISA Frontend library - not for external distribution.
|
||||
|
||||
@@ -1,7 +1,205 @@
|
||||
# remission-start-dialog
|
||||
# @isa/remission/shared/remission-start-dialog
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
Angular dialog component for initiating remission processes with two-step workflow: creating return receipts and assigning package numbers.
|
||||
|
||||
## Running unit tests
|
||||
## Overview
|
||||
|
||||
Run `nx test remission-start-dialog` to execute the unit tests.
|
||||
The Remission Start Dialog library provides a critical workflow component for starting new remission processes ("Warenbegleitschein" - return receipt workflow). It implements a two-step process: first creating or inputting a return receipt number, then assigning a package number ("Wannennummer"). The component integrates with RemissionStore for state management and supports both automatic and manual input modes.
|
||||
|
||||
## Features
|
||||
|
||||
- **Two-Step Workflow** - Receipt creation → Package assignment
|
||||
- **Receipt Number Options** - Auto-generate or manual input
|
||||
- **Package Number Assignment** - Scan or manual entry (#5289)
|
||||
- **Validation** - Server-side validation with error handling
|
||||
- **Loading States** - Separate loading states for each step
|
||||
- **Error Recovery** - Display validation errors inline
|
||||
- **RemissionStore Integration** - Automatic state synchronization
|
||||
- **Service Wrapper** - RemissionStartService for simplified usage
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { RemissionStartService } from '@isa/remission/shared/remission-start-dialog';
|
||||
|
||||
@Component({})
|
||||
export class MyComponent {
|
||||
#remissionStartService = inject(RemissionStartService);
|
||||
|
||||
async startNewRemission(returnGroup: string) {
|
||||
await this.#remissionStartService.startRemission(returnGroup);
|
||||
// RemissionStore is automatically updated
|
||||
// User is ready to add items
|
||||
}
|
||||
|
||||
// #5289 - Package assignment only
|
||||
async assignPackageOnly(returnId: number, receiptId: number) {
|
||||
const result = await this.#remissionStartService.assignPackage({
|
||||
returnId,
|
||||
receiptId
|
||||
});
|
||||
if (result) {
|
||||
console.log('Package assigned:', result);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Component API
|
||||
|
||||
### RemissionStartDialogComponent
|
||||
|
||||
Two-step dialog for creating return receipts and assigning packages.
|
||||
|
||||
**Selector**: `remi-remission-start-dialog`
|
||||
|
||||
**Dialog Data**:
|
||||
```typescript
|
||||
{
|
||||
returnGroup: string | undefined;
|
||||
assignPackage?: {
|
||||
returnId: number;
|
||||
receiptId: number;
|
||||
} | undefined; // #5289 - Package assignment only mode
|
||||
}
|
||||
```
|
||||
|
||||
**Dialog Result**:
|
||||
```typescript
|
||||
{
|
||||
returnId: number;
|
||||
receiptId: number;
|
||||
} | undefined
|
||||
```
|
||||
|
||||
### RemissionStartService
|
||||
|
||||
Injectable service for simplified dialog usage.
|
||||
|
||||
**Methods**:
|
||||
- `startRemission(returnGroup: string | undefined): Promise<void>`
|
||||
- `assignPackage({ returnId, receiptId }): Promise<RemissionStartDialogResult>`
|
||||
|
||||
## Two-Step Workflow
|
||||
|
||||
### Step 1: Create Return Receipt
|
||||
|
||||
**Options**:
|
||||
1. **Generate** - Auto-generate receipt number
|
||||
2. **Input** - Manual receipt number entry
|
||||
|
||||
**Component**: `CreateReturnReceiptComponent`
|
||||
|
||||
**API Call**: `RemissionReturnReceiptService.createRemission()`
|
||||
|
||||
**Validation**: Server-side validation via `invalidProperties` error response
|
||||
|
||||
### Step 2: Assign Package Number
|
||||
|
||||
**Input**: Scan or manually enter package number ("Wannennummer")
|
||||
|
||||
**Component**: `AssignPackageNumberComponent`
|
||||
|
||||
**API Call**: `RemissionReturnReceiptService.assignPackage()`
|
||||
|
||||
**Requirement**: #5289 - Package must be assigned before completing remission
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Start Remission from Card
|
||||
|
||||
```typescript
|
||||
import { RemissionStartService } from '@isa/remission/shared/remission-start-dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-remission-start-card',
|
||||
template: `
|
||||
<button (click)="startRemission()" uiButton>
|
||||
Neue Remission starten
|
||||
</button>
|
||||
`
|
||||
})
|
||||
export class RemissionStartCardComponent {
|
||||
#remissionStartService = inject(RemissionStartService);
|
||||
#router = inject(Router);
|
||||
#tabId = injectTabId();
|
||||
|
||||
returnGroup = input<string | undefined>(undefined);
|
||||
|
||||
async startRemission() {
|
||||
await this.#remissionStartService.startRemission(this.returnGroup());
|
||||
await this.#router.navigate(['/', this.#tabId(), 'remission']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Assign Package Before Completion
|
||||
|
||||
```typescript
|
||||
import { RemissionStartService } from '@isa/remission/shared/remission-start-dialog';
|
||||
|
||||
@Component({})
|
||||
export class CompleteButtonComponent {
|
||||
#remissionStartService = inject(RemissionStartService);
|
||||
|
||||
async completeRemission() {
|
||||
// #5289 - Ensure package is assigned
|
||||
if (!this.hasAssignedPackage()) {
|
||||
const result = await this.#remissionStartService.assignPackage({
|
||||
returnId: this.returnId(),
|
||||
receiptId: this.receiptId()
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return; // User canceled
|
||||
}
|
||||
}
|
||||
|
||||
// Proceed with completion
|
||||
await this.completeReturnReceipt();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Component Details
|
||||
|
||||
### Return Receipt Result Types
|
||||
|
||||
```typescript
|
||||
enum ReturnReceiptResultType {
|
||||
Close = 'close', // User closed dialog
|
||||
Generate = 'generate', // Auto-generate number
|
||||
Input = 'input' // Manual input
|
||||
}
|
||||
|
||||
type ReturnReceiptResult =
|
||||
| { type: ReturnReceiptResultType.Close }
|
||||
| { type: ReturnReceiptResultType.Generate }
|
||||
| { type: ReturnReceiptResultType.Input; value: string | undefined | null };
|
||||
```
|
||||
|
||||
### Request Status Tracking
|
||||
|
||||
```typescript
|
||||
type RequestStatus = {
|
||||
loading: boolean;
|
||||
invalidProperties?: Record<string, string>; // Server validation errors
|
||||
};
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
npx nx test remission-remission-start-dialog --skip-nx-cache
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@angular/core` - Angular framework
|
||||
- `@isa/remission/data-access` - RemissionStore, RemissionReturnReceiptService
|
||||
- `@isa/ui/dialog` - Dialog infrastructure
|
||||
- `@isa/icons` - Icons (isaActionScanner)
|
||||
|
||||
## License
|
||||
|
||||
Internal ISA Frontend library - not for external distribution.
|
||||
|
||||
@@ -1,7 +1,501 @@
|
||||
# remission-return-receipt-actions
|
||||
# @isa/remission/shared/return-receipt-actions
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
Angular standalone components for managing return receipt actions including deletion, continuation, and completion workflows in the remission process.
|
||||
|
||||
## Running unit tests
|
||||
## Overview
|
||||
|
||||
Run `nx test remission-return-receipt-actions` to execute the unit tests.
|
||||
The Remission Shared Return Receipt Actions library provides two specialized action components for managing return receipts in remission workflows. These components handle critical operations like deleting return receipts, continuing remission processes, and completing remission packages ("Wanne"). They integrate with the RemissionStore and RemissionReturnReceiptService to provide state management and API interactions.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Component API](#component-api)
|
||||
- [Usage Examples](#usage-examples)
|
||||
- [Component Details](#component-details)
|
||||
- [Business Logic](#business-logic)
|
||||
- [Testing](#testing)
|
||||
- [Dependencies](#dependencies)
|
||||
|
||||
## Features
|
||||
|
||||
- **Return Receipt Deletion** - Delete return receipts with validation and state cleanup
|
||||
- **Remission Continuation** - Continue existing or start new remission workflows
|
||||
- **Remission Completion** - Complete remission packages with automatic package assignment
|
||||
- **State Management** - Integration with RemissionStore for current remission tracking
|
||||
- **Confirmation Dialogs** - User confirmation for destructive and workflow-changing actions
|
||||
- **Navigation** - Automatic routing to remission list after operations
|
||||
- **Loading States** - Pending state indicators during async operations
|
||||
- **Error Handling** - Comprehensive error logging and recovery
|
||||
- **E2E Testing Attributes** - Complete data-* attributes for automated testing
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Import Components
|
||||
|
||||
```typescript
|
||||
import {
|
||||
RemissionReturnReceiptActionsComponent,
|
||||
RemissionReturnReceiptCompleteComponent
|
||||
} from '@isa/remission/shared/return-receipt-actions';
|
||||
|
||||
@Component({
|
||||
selector: 'app-return-receipt-card',
|
||||
imports: [
|
||||
RemissionReturnReceiptActionsComponent,
|
||||
RemissionReturnReceiptCompleteComponent
|
||||
],
|
||||
template: '...'
|
||||
})
|
||||
export class ReturnReceiptCardComponent {}
|
||||
```
|
||||
|
||||
### 2. Use Return Receipt Actions
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
<lib-remission-return-receipt-actions
|
||||
[remissionReturn]="returnData()"
|
||||
[displayDeleteAction]="true"
|
||||
(reloadData)="onReloadList()"
|
||||
></lib-remission-return-receipt-actions>
|
||||
`
|
||||
})
|
||||
export class MyComponent {
|
||||
returnData = signal<Return>({ id: 123, receipts: [...] });
|
||||
|
||||
onReloadList() {
|
||||
// Refresh the return receipt list
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use Complete Button
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
<lib-remission-return-receipt-complete
|
||||
[returnId]="123"
|
||||
[receiptId]="456"
|
||||
[itemsLength]="5"
|
||||
[hasAssignedPackage]="true"
|
||||
(reloadData)="onReloadList()"
|
||||
></lib-remission-return-receipt-complete>
|
||||
`
|
||||
})
|
||||
export class MyComponent {
|
||||
onReloadList() {
|
||||
// Refresh the data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Component API
|
||||
|
||||
### RemissionReturnReceiptActionsComponent
|
||||
|
||||
Action buttons for deleting and continuing return receipts.
|
||||
|
||||
#### Selector
|
||||
|
||||
```html
|
||||
<lib-remission-return-receipt-actions></lib-remission-return-receipt-actions>
|
||||
```
|
||||
|
||||
#### Inputs
|
||||
|
||||
| Input | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `remissionReturn` | `Return` | Yes | - | Return data containing receipts and metadata |
|
||||
| `displayDeleteAction` | `boolean` | No | `true` | Whether to show the delete button |
|
||||
|
||||
#### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `reloadData` | `void` | Emitted when the list needs to be reloaded |
|
||||
|
||||
#### Key Methods
|
||||
|
||||
**`onDelete(): Promise<void>`**
|
||||
- Deletes all receipts associated with the return
|
||||
- Clears RemissionStore state if deleting current remission
|
||||
- Emits `reloadData` event after successful deletion
|
||||
- Handles errors with comprehensive logging
|
||||
|
||||
**`onContinueRemission(): Promise<void>`**
|
||||
- Starts a new remission if not already started
|
||||
- Shows confirmation dialog if different remission is in progress
|
||||
- Navigates to remission list after starting
|
||||
- Validates receipt availability before continuing
|
||||
|
||||
### RemissionReturnReceiptCompleteComponent
|
||||
|
||||
Fixed-position button for completing a remission package ("Wanne").
|
||||
|
||||
#### Selector
|
||||
|
||||
```html
|
||||
<lib-remission-return-receipt-complete></lib-remission-return-receipt-complete>
|
||||
```
|
||||
|
||||
#### Inputs
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `returnId` | `number` | Yes | Return ID (coerced from string) |
|
||||
| `receiptId` | `number` | Yes | Receipt ID (coerced from string) |
|
||||
| `itemsLength` | `number` | Yes | Number of items in the receipt |
|
||||
| `hasAssignedPackage` | `boolean` | Yes | Whether package number is assigned |
|
||||
|
||||
#### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `reloadData` | `void` | Emitted when data needs to be reloaded |
|
||||
|
||||
#### Key Methods
|
||||
|
||||
**`completeRemission(): Promise<void>`**
|
||||
- Ensures package is assigned (#5289 requirement)
|
||||
- Completes the return receipt and return
|
||||
- Shows "Wanne abgeschlossen" dialog with options
|
||||
- Option 1: Complete return group (Beenden)
|
||||
- Option 2: Start new receipt in same group (Neue Wanne)
|
||||
- Emits `reloadData` event after completion
|
||||
|
||||
**`completeSingleReturnReceipt(): Promise<Return>`**
|
||||
- Completes the return receipt via API
|
||||
- Clears RemissionStore state
|
||||
- Returns the completed return data
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Complete Return Receipt Workflow
|
||||
|
||||
```typescript
|
||||
import {
|
||||
RemissionReturnReceiptActionsComponent,
|
||||
RemissionReturnReceiptCompleteComponent
|
||||
} from '@isa/remission/shared/return-receipt-actions';
|
||||
|
||||
@Component({
|
||||
selector: 'app-remission-receipt-details',
|
||||
imports: [
|
||||
RemissionReturnReceiptActionsComponent,
|
||||
RemissionReturnReceiptCompleteComponent
|
||||
],
|
||||
template: `
|
||||
<div class="receipt-details">
|
||||
<!-- Receipt content -->
|
||||
<div class="receipt-items">
|
||||
<!-- Item list -->
|
||||
</div>
|
||||
|
||||
<!-- Action buttons at top -->
|
||||
<lib-remission-return-receipt-actions
|
||||
[remissionReturn]="returnData()"
|
||||
[displayDeleteAction]="!isCompleted()"
|
||||
(reloadData)="handleReload()"
|
||||
></lib-remission-return-receipt-actions>
|
||||
|
||||
<!-- Complete button (fixed position) -->
|
||||
@if (!isCompleted()) {
|
||||
<lib-remission-return-receipt-complete
|
||||
[returnId]="returnId()"
|
||||
[receiptId]="receiptId()"
|
||||
[itemsLength]="receiptItems().length"
|
||||
[hasAssignedPackage]="hasPackage()"
|
||||
(reloadData)="handleReload()"
|
||||
></lib-remission-return-receipt-complete>
|
||||
}
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class RemissionReceiptDetailsComponent {
|
||||
returnData = signal<Return | undefined>(undefined);
|
||||
returnId = signal(0);
|
||||
receiptId = signal(0);
|
||||
receiptItems = signal<ReceiptItem[]>([]);
|
||||
hasPackage = signal(false);
|
||||
|
||||
isCompleted = computed(() => {
|
||||
return this.returnData()?.completed ?? false;
|
||||
});
|
||||
|
||||
handleReload() {
|
||||
// Reload receipt data from API
|
||||
this.fetchReceiptData();
|
||||
}
|
||||
|
||||
async fetchReceiptData() {
|
||||
// Fetch logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Return Receipt List Actions
|
||||
|
||||
```typescript
|
||||
import { RemissionReturnReceiptActionsComponent } from '@isa/remission/shared/return-receipt-actions';
|
||||
|
||||
@Component({
|
||||
selector: 'app-receipt-list-item',
|
||||
imports: [RemissionReturnReceiptActionsComponent],
|
||||
template: `
|
||||
<div class="receipt-card">
|
||||
<div class="receipt-header">
|
||||
<span>Receipt #{{ return().receipts[0].number }}</span>
|
||||
<span>{{ return().receipts[0].created | date }}</span>
|
||||
</div>
|
||||
|
||||
<div class="receipt-body">
|
||||
<div>Items: {{ itemCount() }}</div>
|
||||
<div>Status: {{ status() }}</div>
|
||||
</div>
|
||||
|
||||
<div class="receipt-actions">
|
||||
<lib-remission-return-receipt-actions
|
||||
[remissionReturn]="return()"
|
||||
[displayDeleteAction]="canDelete()"
|
||||
(reloadData)="reloadList.emit()"
|
||||
></lib-remission-return-receipt-actions>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class ReceiptListItemComponent {
|
||||
return = input.required<Return>();
|
||||
reloadList = output<void>();
|
||||
|
||||
itemCount = computed(() => {
|
||||
return this.return().receipts[0]?.data?.items?.length ?? 0;
|
||||
});
|
||||
|
||||
status = computed(() => {
|
||||
return this.return().completed ? 'Completed' : 'In Progress';
|
||||
});
|
||||
|
||||
canDelete = computed(() => {
|
||||
return !this.return().completed && this.itemCount() === 0;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Component Details
|
||||
|
||||
### RemissionReturnReceiptActionsComponent
|
||||
|
||||
**Button Layout:**
|
||||
- **Delete Button** (Secondary, optional):
|
||||
- Text: "Löschen"
|
||||
- Disabled when `itemQuantity > 0`
|
||||
- Calls `onDelete()` method
|
||||
- Only shown if `displayDeleteAction` is true
|
||||
|
||||
- **Continue Button** (Primary):
|
||||
- Text: "Befüllen"
|
||||
- Always enabled
|
||||
- Calls `onContinueRemission()` method
|
||||
|
||||
**Computed Properties:**
|
||||
```typescript
|
||||
returnId = computed(() => this.remissionReturn().id);
|
||||
receiptIds = computed(() => this.remissionReturn().receipts?.map(r => r.id) || []);
|
||||
firstReceiptId = computed(() => this.receiptIds()[0]);
|
||||
itemQuantity = computed(() => getReceiptItemQuantityFromReturn(this.remissionReturn()));
|
||||
isCurrentRemission = computed(() => this.#store.isCurrentRemission({ returnId, receiptId }));
|
||||
```
|
||||
|
||||
**Delete Flow:**
|
||||
1. Iterate through all receipt IDs
|
||||
2. For each receipt, check if it's the current remission
|
||||
3. If current, clear RemissionStore state
|
||||
4. Call `cancelReturnReceipt` API for each receipt
|
||||
5. Call `cancelReturn` API for the return
|
||||
6. Emit `reloadData` event
|
||||
7. Log errors if any occur
|
||||
|
||||
**Continue Flow:**
|
||||
1. Check if remission is already started
|
||||
2. If not started:
|
||||
- Start remission with first receipt ID
|
||||
- Navigate to remission list
|
||||
3. If different remission in progress:
|
||||
- Show confirmation dialog
|
||||
- Options: Continue current OR start new
|
||||
- If starting new, clear store and start with first receipt
|
||||
4. Navigate to remission list
|
||||
|
||||
### RemissionReturnReceiptCompleteComponent
|
||||
|
||||
**Button Appearance:**
|
||||
- Fixed position (bottom-right corner)
|
||||
- Large size
|
||||
- Brand color
|
||||
- Text: "Wanne abschließen"
|
||||
- Shows pending state during completion
|
||||
|
||||
**Completion Flow:**
|
||||
1. **Package Assignment Check** (#5289):
|
||||
- If no package assigned, open package assignment dialog
|
||||
- If dialog canceled, abort completion
|
||||
|
||||
2. **Complete Receipt**:
|
||||
- Call `completeSingleReturnReceipt()`
|
||||
- Clears RemissionStore state
|
||||
|
||||
3. **Return Group Handling**:
|
||||
- If return has `returnGroup`:
|
||||
- Show "Wanne abgeschlossen" dialog
|
||||
- Option 1 (Beenden): Complete return group via API
|
||||
- Option 2 (Neue Wanne): Start new remission in same group
|
||||
|
||||
4. **Emit Reload Event**: Notify parent to refresh data
|
||||
|
||||
**Dialog Content:**
|
||||
```
|
||||
Title: "Wanne abgeschlossen"
|
||||
Message: "Legen Sie abschließend den 'Blank Beizettel' in die abgeschlossene Wanne.
|
||||
Der Warenbegleitschein wird nach Abschluss der Remission versendet.
|
||||
Zum Öffnen eines neuen Warenbegleitscheins setzen Sie die Remission fort."
|
||||
|
||||
Buttons:
|
||||
- Neue Wanne (Close button)
|
||||
- Beenden (Confirm button)
|
||||
```
|
||||
|
||||
## Business Logic
|
||||
|
||||
### Package Assignment Requirement (#5289)
|
||||
|
||||
Before completing a remission, a package number must be assigned. If not already assigned:
|
||||
1. Open package assignment dialog
|
||||
2. User scans or enters package number
|
||||
3. Package is assigned to the receipt
|
||||
4. Completion proceeds
|
||||
5. If user cancels, completion is aborted
|
||||
|
||||
### Return Group Workflow
|
||||
|
||||
**Return Groups** represent physical containers ("Wannen") for grouped returns:
|
||||
- Multiple receipts can belong to the same return group
|
||||
- Completing a receipt offers two options:
|
||||
1. **Complete Entire Group**: Finalizes all receipts in the group
|
||||
2. **Start New Receipt**: Creates a new receipt in the same group
|
||||
|
||||
### State Management Integration
|
||||
|
||||
**RemissionStore Interactions:**
|
||||
```typescript
|
||||
// Check if current remission
|
||||
isCurrentRemission({ returnId, receiptId })
|
||||
|
||||
// Clear state when deleting or completing
|
||||
clearState()
|
||||
|
||||
// Start remission when continuing
|
||||
startRemission({ returnId, receiptId })
|
||||
|
||||
// Reload return data
|
||||
reloadReturn()
|
||||
```
|
||||
|
||||
### Confirmation Dialogs
|
||||
|
||||
**Delete Confirmation**: Not shown (direct deletion)
|
||||
|
||||
**Continue with Different Remission**:
|
||||
```
|
||||
Title: "Bereits geöffneter Warenbegleitschein"
|
||||
Message: "Möchten Sie wirklich einen neuen öffnen, oder möchten Sie zuerst den aktuellen abschließen?"
|
||||
Close: "Aktuellen bearbeiten"
|
||||
Confirm: "Neuen öffnen"
|
||||
```
|
||||
|
||||
**Completion Confirmation**:
|
||||
```
|
||||
Title: "Wanne abgeschlossen"
|
||||
Message: "Legen Sie abschließend den 'Blank Beizettel' in die abgeschlossene Wanne..."
|
||||
Close: "Neue Wanne"
|
||||
Confirm: "Beenden"
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
npx nx test remission-return-receipt-actions --skip-nx-cache
|
||||
|
||||
# Run with coverage
|
||||
npx nx test remission-return-receipt-actions --code-coverage --skip-nx-cache
|
||||
```
|
||||
|
||||
### Test Examples
|
||||
|
||||
```typescript
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { RemissionReturnReceiptActionsComponent } from './remission-return-receipt-actions.component';
|
||||
|
||||
describe('RemissionReturnReceiptActionsComponent', () => {
|
||||
let component: RemissionReturnReceiptActionsComponent;
|
||||
let fixture: ComponentFixture<RemissionReturnReceiptActionsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RemissionReturnReceiptActionsComponent]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RemissionReturnReceiptActionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should disable delete button when items exist', () => {
|
||||
fixture.componentRef.setInput('remissionReturn', {
|
||||
id: 123,
|
||||
receipts: [{ id: 456, data: { items: [{ id: 1 }] } }]
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const deleteBtn = fixture.nativeElement.querySelector('[data-what="return-receipt-delete-button"]');
|
||||
expect(deleteBtn?.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should emit reloadData after successful deletion', async () => {
|
||||
const reloadSpy = vi.fn();
|
||||
component.reloadData.subscribe(reloadSpy);
|
||||
|
||||
await component.onDelete();
|
||||
|
||||
expect(reloadSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@angular/core` - Angular framework
|
||||
- `@angular/router` - Navigation
|
||||
- `@isa/remission/data-access` - RemissionStore, RemissionReturnReceiptService, Return types
|
||||
- `@isa/remission/shared/remission-start-dialog` - RemissionStartService
|
||||
- `@isa/ui/buttons` - Button components
|
||||
- `@isa/ui/dialog` - Confirmation dialog
|
||||
- `@isa/core/tabs` - Tab ID injection
|
||||
- `@isa/core/logging` - Logger service
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
**State Synchronization**: Both components integrate tightly with RemissionStore to ensure UI state matches backend state.
|
||||
|
||||
**Navigation Integration**: Uses Angular Router and tab ID for context-aware navigation within multi-tab environments.
|
||||
|
||||
**Error Handling**: Comprehensive error logging with contextual information for debugging.
|
||||
|
||||
**User Confirmations**: Critical actions (changing remission, completing groups) require explicit user confirmation.
|
||||
|
||||
## License
|
||||
|
||||
Internal ISA Frontend library - not for external distribution.
|
||||
|
||||
@@ -1,7 +1,143 @@
|
||||
# remission-shared-search-item-to-remit-dialog
|
||||
# @isa/remission/shared/search-item-to-remit-dialog
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
Angular dialog component for searching and adding items to remission lists that are not on the mandatory return list (Pflichtremission).
|
||||
|
||||
## Running unit tests
|
||||
## Overview
|
||||
|
||||
Run `nx test remission-shared-search-item-to-remit-dialog` to execute the unit tests.
|
||||
The Search Item to Remit Dialog library provides a specialized dialog workflow for adding items to remission processes that aren't pre-populated in the mandatory remission list. Users can search for products by EAN or name, view stock information, and specify quantity and reason for returns. This enables flexible remission workflows beyond pre-defined lists.
|
||||
|
||||
## Features
|
||||
|
||||
- **Product Search** - Search by EAN or product name with real-time results
|
||||
- **Stock Information** - Display current in-stock quantities
|
||||
- **Quantity & Reason Selection** - Specify remit quantity and return reason
|
||||
- **Validation** - Ensure quantities don't exceed available stock
|
||||
- **ReturnItem Results** - Returns only ReturnItem types (#5273, #4768)
|
||||
- **Responsive Layout** - Adapts to desktop/mobile breakpoints
|
||||
- **InStock Resource** - Batched stock fetching for search results
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-item-to-remit-dialog';
|
||||
|
||||
@Component({
|
||||
imports: [SearchItemToRemitDialogComponent]
|
||||
})
|
||||
export class MyComponent {
|
||||
dialog = injectDialog(SearchItemToRemitDialogComponent);
|
||||
|
||||
async openSearch(searchTerm: string) {
|
||||
const dialogRef = this.dialog({
|
||||
data: { searchTerm },
|
||||
width: '48rem'
|
||||
});
|
||||
|
||||
const result = await firstValueFrom(dialogRef.closed);
|
||||
if (result) {
|
||||
// result is ReturnItem[]
|
||||
console.log('Selected items:', result);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Component API
|
||||
|
||||
### SearchItemToRemitDialogComponent
|
||||
|
||||
Main dialog container that manages the search workflow.
|
||||
|
||||
**Selector**: `remi-search-item-to-remit-dialog`
|
||||
|
||||
**Dialog Data**:
|
||||
```typescript
|
||||
{
|
||||
searchTerm: string | Signal<string>
|
||||
}
|
||||
```
|
||||
|
||||
**Dialog Result**: `ReturnItem[] | undefined`
|
||||
|
||||
### SearchItemToRemitListComponent
|
||||
|
||||
Displays list of search results with stock information.
|
||||
|
||||
**Features**:
|
||||
- Catalog item search via CatSearchService
|
||||
- In-stock quantity display
|
||||
- Click handlers for item selection
|
||||
|
||||
### SelectRemiQuantityAndReasonDialogComponent
|
||||
|
||||
Nested dialog for specifying quantity and reason.
|
||||
|
||||
**Inputs**:
|
||||
- `item`: Item (Product to remit)
|
||||
- `inStock`: number (Available quantity)
|
||||
|
||||
**Result**: `ReturnItem[]`
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-item-to-remit-dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-remission-list',
|
||||
imports: [SearchItemToRemitDialogComponent]
|
||||
})
|
||||
export class RemissionListComponent {
|
||||
#dialog = injectDialog(SearchItemToRemitDialogComponent);
|
||||
#store = inject(RemissionStore);
|
||||
|
||||
async addItemNotOnList(searchTerm: string) {
|
||||
const dialogRef = this.#dialog({
|
||||
data: { searchTerm: signal(searchTerm) },
|
||||
width: '48rem'
|
||||
});
|
||||
|
||||
const returnItems = await firstValueFrom(dialogRef.closed);
|
||||
|
||||
if (returnItems && this.#store.remissionStarted()) {
|
||||
for (const item of returnItems) {
|
||||
this.#store.selectRemissionItem(item.id, item);
|
||||
}
|
||||
await this.remitItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
**Search Flow**:
|
||||
1. User enters search term (EAN or product name)
|
||||
2. SearchItemToRemitListComponent searches catalog
|
||||
3. InStockResource fetches stock for results
|
||||
4. User clicks item → SelectRemiQuantityAndReasonDialog opens
|
||||
5. User specifies quantity and reason
|
||||
6. Dialog closes with ReturnItem array
|
||||
|
||||
**Stock Resource**: Uses BatchingResource pattern for efficient stock fetching across multiple items.
|
||||
|
||||
**Type Safety**: #5273, #4768 - Only ReturnItem types allowed to prevent incorrect data flow.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
npx nx test remission-search-item-to-remit-dialog --skip-nx-cache
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@angular/core` - Angular framework
|
||||
- `@isa/remission/data-access` - ReturnItem types
|
||||
- `@isa/catalogue/data-access` - Item search
|
||||
- `@isa/ui/dialog` - Dialog infrastructure
|
||||
- `@isa/ui/layout` - Breakpoint service
|
||||
- `@isa/remission/shared/product` - ProductInfoComponent
|
||||
|
||||
## License
|
||||
|
||||
Internal ISA Frontend library - not for external distribution.
|
||||
|
||||
Reference in New Issue
Block a user