# @isa/checkout/feature/reward-shopping-cart

A comprehensive reward shopping cart feature for Angular applications supporting loyalty points redemption workflow across retail operations.

## Overview

The Reward Shopping Cart feature library provides a complete user interface for managing loyalty point redemptions through the ISA rewards program. It integrates customer bonus card data, shopping cart management, delivery address selection, and order completion into a unified checkout flow specifically designed for reward items (Prämienausgabe). The library coordinates between CRM data, checkout services, and OMS order creation to enable seamless reward fulfillment.

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Component Architecture](#component-architecture)
- [Component API Reference](#component-api-reference)
- [Usage Examples](#usage-examples)
- [Routing and Navigation](#routing-and-navigation)
- [Shopping Cart Management](#shopping-cart-management)
- [Checkout Workflow](#checkout-workflow)
- [State Management](#state-management)
- [Architecture Notes](#architecture-notes)
- [Dependencies](#dependencies)
- [Testing](#testing)
- [Best Practices](#best-practices)

## Features

- **Loyalty points integration** - Display customer bonus card points and redemption costs
- **Reactive shopping cart** - Real-time cart updates with quantity controls and item removal
- **Availability checking** - Automatic stock validation with responsive quantity limits
- **Address management** - Display and edit billing/shipping addresses with CRM integration
- **Order completion** - Orchestrated checkout with cross-domain coordination (Checkout + OMS)
- **Responsive design** - Adaptive layouts for desktop (horizontal) and tablet (vertical)
- **Optimistic updates** - Immediate UI feedback with background resource reloading
- **Error handling** - Comprehensive logging and graceful error recovery
- **E2E testing attributes** - Full support for automated testing with data-* attributes
- **Deferred rendering** - Viewport-based lazy loading for optimal performance

## Quick Start

### 1. Import Routes

```typescript
// app-routing.module.ts
import { routes as rewardShoppingCartRoutes } from '@isa/checkout/feature/reward-shopping-cart';

const routes: Routes = [
  {
    path: 'reward/cart',
    loadChildren: () => rewardShoppingCartRoutes
  }
];
```

### 2. Navigate to Shopping Cart

```typescript
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-product-list',
  template: '...'
})
export class ProductListComponent {
  #router = inject(Router);

  async viewRewardCart(tabId: number): Promise<void> {
    await this.#router.navigate(['/', tabId, 'reward', 'cart']);
  }
}
```

### 3. Prerequisites

Before navigating to the reward shopping cart, ensure:

```typescript
// 1. Customer is selected (required for bonus points)
crmTabMetadataService.selectCustomer(tabId, customerId);

// 2. Reward shopping cart exists (created automatically when adding items)
checkoutMetadataService.getRewardShoppingCartId(tabId); // Should return valid ID

// 3. At least one item in the cart
// Items are added via RewardSelectionDialog or directly via ShoppingCartFacade
```

## Component Architecture

The library follows a hierarchical component structure with clear separation of concerns:

```
RewardShoppingCartComponent (Root)
├─ NavigateBackButtonComponent (Navigation)
├─ CheckoutCustomerRewardCardComponent (Customer/Points Display)
├─ BillingAndShippingAddressCardComponent (Address Management)
├─ RewardShoppingCartItemsComponent (Item List Container)
│  └─ RewardShoppingCartItemComponent (Individual Item)
│     ├─ ProductInfoRedemptionComponent (Product Display)
│     ├─ DestinationInfoComponent (Delivery Info)
│     ├─ RewardShoppingCartItemQuantityControlComponent (Quantity)
│     └─ RewardShoppingCartItemRemoveButtonComponent (Remove)
├─ CompleteOrderButtonComponent (Checkout Action)
└─ RewardSelectionTriggerComponent (Back to Selection)
```

### Component Responsibilities

**RewardShoppingCartComponent (Root)**
- Coordinates all child components
- Provides shared resources (SelectedRewardShoppingCartResource, SelectedCustomerResource)
- Manages overall layout and page structure

**CheckoutCustomerRewardCardComponent**
- Displays customer name and bonus card points
- Shows selected items count and total points required
- Calculates remaining points after redemption

**BillingAndShippingAddressCardComponent**
- Displays billing and shipping addresses from CRM customer data
- Provides edit button to navigate to customer details
- Uses NavigationStateService for preserved context

**RewardShoppingCartItemsComponent**
- Container for shopping cart items
- Filters out items without product data
- Implements deferred loading for performance

**RewardShoppingCartItemComponent**
- Displays individual cart item with product information
- Manages busy state for item operations
- Handles purchase option updates via modal
- Responsive layout switching (horizontal/vertical)

**RewardShoppingCartItemQuantityControlComponent**
- Manages item quantity with availability validation
- Automatically updates cart on quantity changes
- Handles price overrides for redemption items (sets to 0)
- Implements reactive availability checking

**RewardShoppingCartItemRemoveButtonComponent**
- Provides item removal functionality
- Manages busy state during removal
- Reloads cart resource after successful removal

**CompleteOrderButtonComponent**
- Orchestrates checkout completion
- Validates customer, payer, and shipping address
- Creates orders via CheckoutCompletionOrchestratorService
- Manages loading and disabled states

## Component API Reference

### RewardShoppingCartComponent

Root component for the reward shopping cart feature.

**Selector:** `checkout-reward-shopping-cart`

**Providers:**
- `SelectedRewardShoppingCartResource` - Manages reward cart data
- `SelectedCustomerResource` - Provides customer information
- `SelectedCustomerBonusCardsResource` - Provides bonus card data

**Usage:**
```typescript
// Loaded via routing - see Quick Start
```

**Template Structure:**
```html
<tabs-navigate-back-button></tabs-navigate-back-button>
<div><!-- Header with instructions --></div>
<checkout-customer-reward-card></checkout-customer-reward-card>
<checkout-billing-and-shipping-address-card></checkout-billing-and-shipping-address-card>
<checkout-reward-shopping-cart-items></checkout-reward-shopping-cart-items>
<checkout-complete-order-button></checkout-complete-order-button>
```

---

### CheckoutCustomerRewardCardComponent

Displays customer loyalty points and redemption summary.

**Selector:** `checkout-customer-reward-card`

**Computed Properties:**
- `customerName(): string` - Customer full name or "Kein Kunde ausgewählt"
- `bonusCardPoints(): number` - Total points on primary bonus card
- `selectedItems(): number` - Number of items in cart
- `totalPointsRequired(): number` - Sum of loyalty points for all items
- Remaining points displayed as: `bonusCardPoints() - totalPointsRequired()`

**Usage:**
```typescript
// Automatically rendered in RewardShoppingCartComponent
```

**Display Example:**
```
┌─────────────────────────────────────────────────┐
│ Max Mustermann              │ 1500 Lesepunkte   │
│ Prämien ausgewählt          │ 3                 │
│ Prämienkosten               │ 750 Lesepunkte    │
│ Verbleibende Lesepunkte     │ 750 Lesepunkte    │
└─────────────────────────────────────────────────┘
```

---

### BillingAndShippingAddressCardComponent

Displays and manages billing and shipping addresses.

**Selector:** `checkout-billing-and-shipping-address-card`

**Computed Properties:**
- `customer()` - Current customer from resource
- `payer()` - Same as customer (billing entity)
- `payerName(): string` - Formatted customer name
- `payerAddress()` - Customer billing address
- `shippingAddress()` - Same as customer (for display)
- `shippingName(): string` - Formatted shipping name
- `shippingAddressAddress()` - Customer shipping address

**Methods:**
- `async navigateToCustomer(): Promise<void>` - Navigates to customer details with preserved return context

**Usage:**
```html
<checkout-billing-and-shipping-address-card></checkout-billing-and-shipping-address-card>
```

**Navigation Behavior:**
```typescript
// Navigates to: /{tabId}/customer/search/{customerId}
// Preserves return URL: /{tabId}/reward/cart
// Context key: 'select-customer'
```

---

### RewardShoppingCartItemsComponent

Container component for rendering shopping cart items.

**Selector:** `checkout-reward-shopping-cart-items`

**Computed Properties:**
- `items(): ShoppingCartItem[]` - Filtered cart items with valid product data

**Filtering Logic:**
```typescript
items = computed(() => {
  const cart = this.#rewardShoppingCartResource.resource.value();
  return cart?.items
    ?.map(item => item.data!)
    .filter(item => item.product != null) ?? [];
});
```

**Usage:**
```html
<checkout-reward-shopping-cart-items></checkout-reward-shopping-cart-items>
```

**Deferred Rendering:**
```html
@for (item of items(); track item.id) {
  @defer (on viewport) {
    <checkout-reward-shopping-cart-item [item]="item"></checkout-reward-shopping-cart-item>
  } @placeholder {
    <div>Item</div>
  }
}
```

---

### RewardShoppingCartItemComponent

Individual shopping cart item display with quantity and removal controls.

**Selector:** `checkout-reward-shopping-cart-item`

**Inputs:**
- `item: WritableSignal<ShoppingCartItem>` - Required. Shopping cart item data

**Properties:**
- `isBusy: WritableSignal<boolean>` - Busy state for operations
- `isHorizontal: Signal<boolean>` - Responsive layout flag (desktop = horizontal)
- `itemId: Signal<number | undefined>` - Computed item ID
- `shoppingCartId: Signal<number | undefined>` - Computed cart ID

**Methods:**
- `async updatePurchaseOption(): Promise<void>` - Opens purchase options modal

**Usage:**
```html
<checkout-reward-shopping-cart-item
  [item]="cartItem"
></checkout-reward-shopping-cart-item>
```

**Responsive Behavior:**
```typescript
// Desktop (≥1440px): Horizontal layout with inline destination info
isHorizontal = breakpoint([Breakpoint.DesktopL, Breakpoint.DesktopXL]);

// Tablet/Mobile (<1440px): Vertical layout with stacked destination info
```

**Stock Warning:**
```html
<!-- Shown when maxQuantity < 2 -->
@if (quantityControl.maxQuantity() < 2) {
  <div class="text-isa-accent-red">
    Geringer Bestand - Artikel holen vor Abschluss
  </div>
}
```

---

### RewardShoppingCartItemQuantityControlComponent

Manages item quantity with automatic availability validation.

**Selector:** `checkout-reward-shopping-cart-item-quantity-control`

**Export As:** `checkoutRewardShoppingCartItemQuantityControl`

**Inputs:**
- `item: WritableSignal<ShoppingCartItem>` - Required. Shopping cart item

**Properties:**
- `isBusy: WritableSignal<boolean>` - Busy state during updates
- `quantity: LinkedSignal<number>` - Two-way bound quantity
- `maxQuantity: Signal<number>` - Computed max based on availability
- `orderType: Signal<OrderType>` - Computed from item features
- `availabilityResource: Resource<Availability>` - Reactive availability loader

**Max Quantity Logic:**
```typescript
maxQuantity = computed(() => {
  const orderType = this.orderType();

  // Shipping orders: virtually unlimited
  if (orderType === OrderType.Delivery ||
      orderType === OrderType.DigitalShipping ||
      orderType === OrderType.B2BShipping) {
    return 999;
  }

  // Pickup/InStore: limited by actual stock
  return this.availabilityResource.value()?.qty ?? 999;
});
```

**Automatic Updates:**
```typescript
// Updates shopping cart when quantity changes
updateQuantityEffect = effect(() => {
  const originalQuantity = item?.quantity ?? 0;
  const newQuantity = this.quantity();

  if (originalQuantity !== newQuantity && !isBusy && !isLoading && availability) {
    // Performs update with price override to 0 (redemption item)
    await this.performUpdate(newQuantity, availability);
  }
});
```

**Usage:**
```html
<checkout-reward-shopping-cart-item-quantity-control
  #quantityControl="checkoutRewardShoppingCartItemQuantityControl"
  [item]="cartItem"
></checkout-reward-shopping-cart-item-quantity-control>

<!-- Access maxQuantity from parent -->
@if (quantityControl.maxQuantity() < 2) {
  <div>Low stock warning</div>
}
```

**E2E Attributes:**
```html
<shared-quantity-control
  [attr.data-product-id]="catalogProductNumber()"
  [attr.data-item-id]="item().id"
></shared-quantity-control>
```

---

### RewardShoppingCartItemRemoveButtonComponent

Provides item removal functionality with optimistic updates.

**Selector:** `checkout-reward-shopping-cart-item-remove-button`

**Inputs:**
- `item: WritableSignal<ShoppingCartItem>` - Required. Shopping cart item
- `isBusy: ModelSignal<boolean>` - Required. Two-way bound busy state

**Properties:**
- `itemId: Signal<number | undefined>` - Computed item ID
- `shoppingCartId: Signal<number | undefined>` - Computed cart ID

**Methods:**
- `async remove(): Promise<void>` - Removes item from shopping cart

**Usage:**
```html
<checkout-reward-shopping-cart-item-remove-button
  [item]="cartItem"
  [(isBusy)]="isBusy"
></checkout-reward-shopping-cart-item-remove-button>
```

**E2E Attributes:**
```html
<ui-icon-button
  [attr.data-what]="'remove-shopping-cart-item-button'"
  [attr.data-which]="item().id"
  [attr.data-product-id]="item().product.catalogProductNumber"
></ui-icon-button>
```

**Removal Flow:**
```typescript
async remove() {
  // Guard checks
  if (this.isBusy() || !shoppingCartId || !itemId) return;

  this.isBusy.set(true);

  try {
    // Remove via facade
    await this.#shoppingCartFacade.removeItem({ shoppingCartId, shoppingCartItemId });

    // Reload cart resource
    this.#rewardShoppingCartResource.reload();
  } catch (error) {
    this.#logger.error('Error removing item', error);
  } finally {
    this.isBusy.set(false);
  }
}
```

---

### CompleteOrderButtonComponent

Orchestrates checkout completion and order creation.

**Selector:** `checkout-complete-order-button`

**Properties:**
- `isBusy: WritableSignal<boolean>` - Busy state during completion
- `isCompleted: WritableSignal<boolean>` - Completion status flag
- `isLoading: Signal<boolean>` - Combined loading state
- `isDisabled: Signal<boolean>` - Disabled state (busy OR completed OR loading)
- `shippingAddress: Signal<ShippingAddress | null>` - Selected shipping address from CRM
- `payer: Signal<Payer | null>` - Selected payer from CRM
- `shoppingCartId: Signal<number | null>` - Reward cart ID from metadata

**Methods:**
- `async complete(): Promise<void>` - Completes checkout and creates orders

**Providers:**
- `CheckoutCompletionOrchestratorService` - Component-level orchestrator instance

**Usage:**
```html
<checkout-complete-order-button
  class="fixed bottom-6 right-6"
></checkout-complete-order-button>
```

**Button States:**
```html
<button
  uiButton
  color="brand"
  size="large"
  [disabled]="isDisabled()"
  [pending]="isLoading()"
  (click)="complete()"
>
  Abschließen
</button>
```

**Completion Flow:**
```typescript
async complete() {
  // Validation
  if (isDisabled()) return;
  if (!shoppingCartId) throw new Error('Missing shopping cart ID');
  if (!customer) throw new Error('Missing customer data');

  // Complete via orchestrator
  const orders = await this.#orchestrator.completeCheckoutAndCreateOrders({
    shoppingCartId,
    crmCustomer: customer,
    crmShippingAddress: shippingAddress ?? undefined,
    crmPayer: payer ?? undefined,
    notificationChannels: customer.notificationChannels ?? 0,
  });

  // Mark as completed
  this.isCompleted.set(true);
}
```

## Usage Examples

### Basic Setup with Routing

```typescript
// app-routing.module.ts
import { Routes } from '@angular/router';
import { routes as rewardCartRoutes } from '@isa/checkout/feature/reward-shopping-cart';

export const routes: Routes = [
  {
    path: ':tabId/reward/cart',
    loadChildren: () => rewardCartRoutes,
  },
];
```

### Navigating to Reward Cart

```typescript
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { TabService } from '@isa/core/tabs';

@Component({
  selector: 'app-reward-selection',
  template: `
    <button (click)="viewCart()">
      Warenkorb anzeigen
    </button>
  `
})
export class RewardSelectionComponent {
  #router = inject(Router);
  #tabService = inject(TabService);

  async viewCart(): Promise<void> {
    const tabId = this.#tabService.activatedTabId();
    await this.#router.navigate(['/', tabId, 'reward', 'cart']);
  }
}
```

### Adding Items to Reward Cart

```typescript
import { Component, inject } from '@angular/core';
import { ShoppingCartFacade } from '@isa/checkout/data-access';
import { TabService } from '@isa/core/tabs';

@Component({
  selector: 'app-product-detail',
  template: '...'
})
export class ProductDetailComponent {
  #shoppingCartFacade = inject(ShoppingCartFacade);
  #tabService = inject(TabService);

  async addToRewardCart(
    productId: number,
    loyaltyPoints: number
  ): Promise<void> {
    const tabId = this.#tabService.activatedTabId();

    if (!tabId) {
      throw new Error('No active tab');
    }

    // Add item to reward cart (creates cart if needed)
    await this.#shoppingCartFacade.addItemsToShoppingCart({
      tabId,
      items: [{
        catalogProductNumber: productId,
        rewardCartQuantity: 1, // Quantity for reward cart
        cartQuantity: 0,       // Not added to regular cart
        itemFeatures: {
          orderType: 'Versand',
          // ... other features
        }
      }]
    });
  }
}
```

### Checking Reward Cart Status

```typescript
import { Component, inject, computed } from '@angular/core';
import { CheckoutMetadataService } from '@isa/checkout/data-access';
import { TabService } from '@isa/core/tabs';

@Component({
  selector: 'app-header',
  template: `
    @if (hasRewardCart()) {
      <button (click)="viewCart()">
        Reward Cart: {{ itemCount() }} items
      </button>
    }
  `
})
export class HeaderComponent {
  #checkoutMetadata = inject(CheckoutMetadataService);
  #tabService = inject(TabService);

  hasRewardCart = computed(() => {
    const tabId = this.#tabService.activatedTabId();
    return tabId
      ? !!this.#checkoutMetadata.getRewardShoppingCartId(tabId)
      : false;
  });

  // Note: To get item count, you'd need to fetch the full cart
  // via SelectedRewardShoppingCartResource
}
```

### Custom Item Rendering

```typescript
import { Component, input } from '@angular/core';
import { ShoppingCartItem } from '@isa/checkout/data-access';

@Component({
  selector: 'app-custom-cart-item',
  template: `
    <div class="cart-item">
      <h3>{{ item().product.name }}</h3>
      <p>Points: {{ item().loyalty.value }}</p>
      <p>Quantity: {{ item().quantity }}</p>

      <!-- Use standard quantity control -->
      <checkout-reward-shopping-cart-item-quantity-control
        [item]="item"
      ></checkout-reward-shopping-cart-item-quantity-control>

      <!-- Use standard remove button -->
      <checkout-reward-shopping-cart-item-remove-button
        [item]="item"
        [(isBusy)]="isBusy"
      ></checkout-reward-shopping-cart-item-remove-button>
    </div>
  `
})
export class CustomCartItemComponent {
  item = input.required<ShoppingCartItem>();
  isBusy = signal(false);
}
```

### Handling Completion Events

```typescript
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { CheckoutCompletionOrchestratorService } from '@isa/checkout/feature/reward-shopping-cart';

@Component({
  selector: 'app-custom-complete-button',
  template: `
    <button
      (click)="complete()"
      [disabled]="isCompleted()"
    >
      {{ isCompleted() ? 'Completed' : 'Complete Order' }}
    </button>
  `,
  providers: [CheckoutCompletionOrchestratorService]
})
export class CustomCompleteButtonComponent {
  #orchestrator = inject(CheckoutCompletionOrchestratorService);
  #router = inject(Router);

  isCompleted = signal(false);

  async complete(): Promise<void> {
    try {
      const orders = await this.#orchestrator.completeCheckoutAndCreateOrders({
        shoppingCartId: 123,
        crmCustomer: customer,
        crmShippingAddress: address,
        crmPayer: payer,
        notificationChannels: 1,
      });

      this.isCompleted.set(true);

      // Navigate to order confirmation
      await this.#router.navigate(['/reward/order-confirmation', orders[0].id]);
    } catch (error) {
      console.error('Completion failed', error);
    }
  }
}
```

## Routing and Navigation

### Route Definition

```typescript
// libs/checkout/feature/reward-shopping-cart/src/lib/routes.ts
import { Routes } from '@angular/router';
import { RewardShoppingCartComponent } from './reward-shopping-cart.component';

export const routes: Routes = [
  {
    path: '',
    component: RewardShoppingCartComponent,
  },
];
```

### Integration with App Routes

```typescript
// app-routing.module.ts
const routes: Routes = [
  {
    path: ':tabId/reward/cart',
    loadChildren: () => import('@isa/checkout/feature/reward-shopping-cart').then(m => m.routes),
  },
];
```

### Navigation Patterns

#### From Product List to Cart

```typescript
// Navigate to reward cart
await this.router.navigate(['/', tabId, 'reward', 'cart']);
```

#### Back Navigation

```typescript
// Uses NavigateBackButtonComponent (automatically handles tab history)
<tabs-navigate-back-button></tabs-navigate-back-button>
```

#### To Customer Details (Preserving Context)

```typescript
// BillingAndShippingAddressCardComponent does this automatically
async navigateToCustomer() {
  const returnUrl = `/${this.tabId()}/reward/cart`;

  await this.#navigationState.navigateWithPreservedContext(
    ['/', 'kunde', this.tabId(), 'customer', 'search', customerId],
    { returnUrl },
    'select-customer',
  );
}
```

#### After Order Completion

```typescript
// Typically navigates to order confirmation (implement in your app)
await this.router.navigate([
  '/',
  tabId,
  'reward',
  'order-confirmation',
  orderId
]);
```

## Shopping Cart Management

### Cart Creation

Reward shopping carts are created automatically when adding the first item:

```typescript
// ShoppingCartService.addItemsToShoppingCart()
const rewardCartId = await this.#getOrCreateRewardShoppingCartId(tabId);
```

### Cart Metadata

Cart IDs are stored per-tab in `CheckoutMetadataService`:

```typescript
// Get reward cart ID
const cartId = checkoutMetadataService.getRewardShoppingCartId(tabId);

// Set reward cart ID (usually done automatically)
checkoutMetadataService.setRewardShoppingCartId(tabId, cartId);
```

### Cart Resource Loading

`SelectedRewardShoppingCartResource` automatically loads cart data based on active tab:

```typescript
@Injectable()
export class SelectedRewardShoppingCartResource extends ShoppingCartResource {
  constructor() {
    super();

    // Automatically updates when tab changes
    effect(() => {
      const tabId = this.#tabId();
      const shoppingCartId = tabId
        ? this.#checkoutMetadata.getRewardShoppingCartId(tabId)
        : undefined;

      this.params({ shoppingCartId });
    });
  }
}
```

### Adding Items

```typescript
await shoppingCartFacade.addItemsToShoppingCart({
  tabId,
  items: [{
    catalogProductNumber: productId,
    rewardCartQuantity: 1,  // Adds to reward cart
    cartQuantity: 0,         // Does NOT add to regular cart
    itemFeatures: {
      orderType: 'Versand',
      // ... availability and pricing info
    }
  }]
});
```

### Updating Quantity

```typescript
// Done automatically by RewardShoppingCartItemQuantityControlComponent
await shoppingCartFacade.updateItem({
  shoppingCartId,
  shoppingCartItemId,
  values: {
    quantity: newQuantity,
    availability: updatedAvailability,
  }
});
```

### Removing Items

```typescript
// Done by RewardShoppingCartItemRemoveButtonComponent
await shoppingCartFacade.removeItem({
  shoppingCartId,
  shoppingCartItemId,
});
```

### Reloading Cart Data

```typescript
// After any mutation, reload the resource
this.#rewardShoppingCartResource.reload();
```

## Checkout Workflow

### Complete Workflow Steps

1. **Customer Selection** (prerequisite)
   - Customer must be selected via CRM tab metadata
   - Bonus card data loaded automatically

2. **Item Selection** (prerequisite)
   - Items added to reward cart via RewardSelectionDialog
   - Each item has loyalty points cost

3. **Cart Review** (this feature)
   - View all selected items
   - Adjust quantities
   - Remove items if needed
   - View loyalty points balance

4. **Address Verification**
   - Review billing/shipping addresses
   - Edit addresses via customer details if needed
   - Return to cart after editing

5. **Order Completion**
   - Click "Abschließen" button
   - Validates customer, payer, shipping address
   - Completes checkout via orchestrator
   - Creates orders in OMS
   - Navigates to confirmation (if implemented)

### CheckoutCompletionOrchestratorService

This service coordinates the final checkout steps:

```typescript
@Injectable()
export class CheckoutCompletionOrchestratorService {
  /**
   * Complete checkout with CRM data and create orders.
   *
   * @param params - CRM-sourced checkout parameters
   * @param abortSignal - Optional cancellation signal
   * @returns Created orders
   */
  async completeCheckoutAndCreateOrders(
    params: CompleteCrmOrderParams,
    abortSignal?: AbortSignal,
  ): Promise<Order[]> {
    // Step 1: Complete checkout with CRM data transformation
    const orders = await this.#shoppingCartFacade.completeWithCrmData(
      params,
      abortSignal,
    );

    return orders;
  }
}
```

### Orchestrator Responsibilities

1. **Data Transformation**
   - Converts CRM customer to checkout format
   - Adapts shipping address structure
   - Transforms payer information

2. **Checkout Completion**
   - Calls checkout API with transformed data
   - Validates all required fields
   - Handles API errors

3. **Order Creation**
   - Automatically creates orders in OMS
   - Returns order references

4. **Error Handling**
   - Logs all steps
   - Provides unified error handling
   - Rethrows errors for component handling

### Architectural Note: Feature vs Data-Access

The orchestrator lives in the **feature layer** (not data-access) because:

- **Cross-domain coordination**: Combines checkout + OMS + CRM
- **Business workflow**: Specific to reward checkout use case
- **Facade independence**: Prevents circular dependencies between domain facades
- **Component coupling**: Tightly coupled to UI workflow

This demonstrates proper architectural layering:
- **Feature layer**: Orchestrates cross-domain workflows
- **Data-access facades**: Focus on single-domain operations
- **Services**: Don't depend on other domain facades

## State Management

### Resource-Based State

The library uses Angular's `resource()` API for reactive data loading:

```typescript
readonly resource = resource({
  params: () => this.#params(),
  loader: ({ params, abortSignal }) =>
    params?.shoppingCartId
      ? this.#shoppingCartService.getShoppingCart(params.shoppingCartId, abortSignal)
      : Promise.resolve(null),
});
```

### Automatic Resource Updates

Resources automatically reload when parameters change:

```typescript
// Tab change triggers parameter update
effect(() => {
  const tabId = this.#tabId();
  const shoppingCartId = tabId
    ? this.#checkoutMetadata.getRewardShoppingCartId(tabId)
    : undefined;

  this.params({ shoppingCartId }); // Triggers resource reload
});
```

### Manual Resource Reloading

After mutations, manually reload resources:

```typescript
// After removing item
await this.#shoppingCartFacade.removeItem({ ... });
this.#rewardShoppingCartResource.reload();

// After updating quantity
await this.#shoppingCartFacade.updateItem({ ... });
this.#rewardShoppingCartResource.reload();
```

### Computed State

Derive UI state from resource values:

```typescript
selectedItems = computed(() => {
  const cart = this.#shoppingCartResource.value();
  return cart?.items?.length ?? 0;
});

totalPointsRequired = computed(() => {
  const cart = this.#shoppingCartResource.value();
  if (!cart?.items?.length) return 0;

  const loyalty = cart.items.map(i => i.data?.loyalty?.value ?? 0);
  return loyalty.reduce((a, b) => a + b, 0);
});
```

### Busy State Management

Components coordinate busy states for smooth UX:

```typescript
// Parent component
isBusy = signal(false);

// Child components share busy state
<checkout-reward-shopping-cart-item-quantity-control
  [item]="item"
  [isBusy]="isBusy"
></checkout-reward-shopping-cart-item-quantity-control>

<checkout-reward-shopping-cart-item-remove-button
  [item]="item"
  [(isBusy)]="isBusy"
></checkout-reward-shopping-cart-item-remove-button>
```

## Architecture Notes

### Current Architecture

The library follows a feature-layer architecture with clear separation:

```
Feature Layer (This Library)
       ↓
├─ RewardShoppingCartComponent (UI coordination)
├─ Child Components (Specialized UI)
├─ CheckoutCompletionOrchestratorService (Cross-domain workflow)
       ↓
Data Access Layer (Dependencies)
├─ @isa/checkout/data-access
│  ├─ SelectedRewardShoppingCartResource (Cart data)
│  ├─ ShoppingCartFacade (Cart operations)
│  └─ CheckoutMetadataService (Tab metadata)
├─ @isa/crm/data-access
│  ├─ SelectedCustomerResource (Customer data)
│  ├─ SelectedCustomerBonusCardsResource (Bonus cards)
│  └─ CrmTabMetadataService (CRM metadata)
├─ @isa/oms/data-access
│  └─ OrderCreationFacade (Order creation)
└─ @isa/availability/data-access
   └─ AvailabilityFacade (Stock checking)
```

### Design Patterns

#### 1. Resource Pattern

Uses Angular `resource()` for declarative data loading:

**Benefits:**
- Automatic loading/reloading
- Built-in abort signal support
- Loading state management
- Parameter reactivity

**Example:**
```typescript
availabilityResource = resource<Availability, GetSingleItemAvailabilityInputParams>({
  params: () => ({
    orderType: this.orderType(),
    item: {
      itemId: this.item().id,
      quantity: this.quantity(), // Reactive - triggers reload
    }
  }),
  loader: async ({ params, abortSignal }) => {
    return await this.#availabilityFacade.getAvailability(params, abortSignal);
  }
});
```

#### 2. Orchestrator Pattern

`CheckoutCompletionOrchestratorService` demonstrates proper orchestration:

**Responsibilities:**
- Coordinate cross-domain operations
- Provide unified error handling
- Manage complex workflows

**Why Not in Data-Access:**
- Prevents circular dependencies between facades
- Specific to feature workflow
- Tightly coupled to UI needs

#### 3. Provider Scoping

Component-level providers for proper lifecycle:

```typescript
@Component({
  selector: 'checkout-reward-shopping-cart',
  providers: [
    SelectedRewardShoppingCartResource,
    SelectedCustomerResource,
    SelectedCustomerBonusCardsResource,
  ],
})
export class RewardShoppingCartComponent {}
```

**Benefits:**
- Fresh resource instances per component
- Automatic cleanup on component destroy
- Isolated state per tab

#### 4. Reactive Breakpoints

Responsive layout using breakpoint service:

```typescript
import { breakpoint, Breakpoint } from '@isa/ui/layout';

isHorizontal = breakpoint([
  Breakpoint.DesktopL,
  Breakpoint.DesktopXL,
]);
```

**Benefits:**
- Server-side rendering compatible
- Reactive breakpoint changes
- Consistent with project standards

### Known Architectural Considerations

#### 1. Quantity Control Complexity (High Priority)

**Current State:**
- 320-line component with multiple responsibilities
- Reactive resource + effect for quantity changes
- Price transformation logic embedded

**TODO Comments Identified:**
```typescript
// TODO: [Complexity Reduction - Critical Priority]
// Replace reactive pattern with debounced manual fetch
// Effort: ~3 hours | Impact: High

// TODO: [Complexity Reduction - Critical Priority]
// Extract price transformation to RewardPriceTransformer service
// Effort: ~4 hours | Impact: High

// TODO: [Performance Optimization - Medium Priority]
// Remove redundant computed signals
// Effort: ~1-2 hours | Impact: Medium
```

**Proposed Refactoring:**
1. Replace effect + resource with debounced async method
2. Extract price transformation to service
3. Reduce signal overhead

#### 2. Missing Test Coverage (High Priority)

**Current State:**
- Zero test files for feature components
- TODO comments reference missing tests

**TODO Comments Identified:**
```typescript
// RewardShoppingCartItemComponent:
// TODO: [Next Sprint - Medium Priority] Create test file

// RewardShoppingCartItemQuantityControlComponent:
// TODO: [Next Sprint - High Priority] Create comprehensive test file
// Coverage target: >80%

// RewardShoppingCartItemRemoveButtonComponent:
// TODO: [Next Sprint - Medium Priority] Create test file
```

**Recommended Approach:**
- Use Vitest + Angular Testing Utilities (per project standards)
- Cover critical business logic first (quantity control, orchestrator)
- Add E2E attribute validation tests

#### 3. Customer Address Duplication (Low Priority)

**Current State:**
BillingAndShippingAddressCardComponent shows same address twice:

```typescript
payer = computed(() => this.customer());
shippingAddress = computed(() => this.customer());
```

**Consideration:**
- May be intentional (rewards always ship to customer)
- Consider hiding one section if addresses are identical
- Low priority unless UX feedback indicates confusion

### Performance Considerations

1. **Deferred Rendering**
   - Items load on viewport intersection
   - Reduces initial render time for large carts

2. **Reactive Availability**
   - Availability checks only on quantity change
   - AbortSignal cancels in-flight requests

3. **Resource Caching**
   - Resources don't reload unless params change
   - Manual reloads only after mutations

4. **Breakpoint Service**
   - Efficient media query matching
   - Avoids CSS-only solutions that SSR poorly

### Future Enhancements

Potential improvements identified:

1. **Navigation Enhancement**
   - Navigate to order confirmation after completion
   - Currently just marks as completed (no navigation)

2. **Error Presentation**
   - User-friendly error messages for completion failures
   - Currently only logs errors (no UI feedback)

3. **Loading States**
   - Skeleton loaders for better perceived performance
   - Currently shows placeholder text ("Item")

4. **Accessibility**
   - ARIA labels for screen readers
   - Keyboard navigation support

5. **Analytics Integration**
   - Track completion rate
   - Monitor cart abandonment

## Dependencies

### Required Libraries

**Angular Core:**
- `@angular/core` - Angular framework
- `@angular/router` - Routing support
- `@angular/forms` - FormsModule for ngModel

**ISA Libraries:**
- `@isa/checkout/data-access` - Shopping cart and checkout services
- `@isa/checkout/shared/product-info` - Product display components
- `@isa/checkout/shared/reward-selection-dialog` - Reward selection trigger
- `@isa/crm/data-access` - Customer and bonus card services
- `@isa/oms/data-access` - Order creation services
- `@isa/availability/data-access` - Availability checking
- `@isa/core/tabs` - Tab management
- `@isa/core/navigation` - Navigation state service
- `@isa/core/logging` - Logging service
- `@isa/ui/buttons` - Button components
- `@isa/ui/layout` - Breakpoint service
- `@isa/shared/address` - Address display component
- `@isa/shared/quantity-control` - Quantity control component
- `@isa/icons` - Icon library

**Third-Party:**
- `@ng-icons/core` - Icon rendering

### Path Alias

Import from: `@isa/checkout/feature/reward-shopping-cart`

### Peer Dependencies

Ensure these are available in your application:

- Node.js ≥22.0.0
- npm ≥10.0.0
- Angular 20.1.2
- TypeScript 5.8.3

## Testing

The library uses **Vitest** with **Angular Testing Utilities** for testing (per project standards).

### Running Tests

```bash
# Run tests for this library
npx nx test checkout-feature-reward-shopping-cart --skip-nx-cache

# Run tests with coverage
npx nx test checkout-feature-reward-shopping-cart --code-coverage --skip-nx-cache

# Run tests in watch mode
npx nx test checkout-feature-reward-shopping-cart --watch
```

### Current Test Coverage

**Status:** No test files currently exist (Feature in development)

**TODO Items:**
- RewardShoppingCartItemComponent tests (Medium Priority)
- RewardShoppingCartItemQuantityControlComponent tests (High Priority - >80% coverage target)
- RewardShoppingCartItemRemoveButtonComponent tests (Medium Priority)
- CheckoutCompletionOrchestratorService tests (High Priority)

### Testing Recommendations

#### Component Testing Pattern

```typescript
import { TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { signal } from '@angular/core';
import { RewardShoppingCartItemComponent } from './reward-shopping-cart-item.component';

describe('RewardShoppingCartItemComponent', () => {
  let component: RewardShoppingCartItemComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RewardShoppingCartItemComponent],
      providers: [
        // Mock providers
        { provide: SelectedRewardShoppingCartResource, useValue: mockResource },
        { provide: PurchaseOptionsModalService, useValue: mockModalService },
      ]
    });

    const fixture = TestBed.createComponent(RewardShoppingCartItemComponent);
    component = fixture.componentInstance;
    fixture.componentRef.setInput('item', mockItem);
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should open purchase options modal', async () => {
    await component.updatePurchaseOption();
    expect(mockModalService.open).toHaveBeenCalled();
  });
});
```

#### Service Testing Pattern

```typescript
import { TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { CheckoutCompletionOrchestratorService } from './checkout-completion-orchestrator.service';

describe('CheckoutCompletionOrchestratorService', () => {
  let service: CheckoutCompletionOrchestratorService;
  let mockShoppingCartFacade: any;

  beforeEach(() => {
    mockShoppingCartFacade = {
      completeWithCrmData: vi.fn().mockResolvedValue([{ id: 1 }])
    };

    TestBed.configureTestingModule({
      providers: [
        CheckoutCompletionOrchestratorService,
        { provide: ShoppingCartFacade, useValue: mockShoppingCartFacade },
      ]
    });

    service = TestBed.inject(CheckoutCompletionOrchestratorService);
  });

  it('should complete checkout and create orders', async () => {
    const orders = await service.completeCheckoutAndCreateOrders({
      shoppingCartId: 123,
      crmCustomer: mockCustomer,
    });

    expect(orders).toHaveLength(1);
    expect(mockShoppingCartFacade.completeWithCrmData).toHaveBeenCalled();
  });
});
```

#### E2E Attribute Testing

```typescript
it('should have E2E attributes on remove button', () => {
  const fixture = TestBed.createComponent(RewardShoppingCartItemRemoveButtonComponent);
  fixture.componentRef.setInput('item', mockItem);
  fixture.detectChanges();

  const button = fixture.nativeElement.querySelector('button');

  expect(button.getAttribute('data-what')).toBe('remove-shopping-cart-item-button');
  expect(button.getAttribute('data-which')).toBe(String(mockItem.id));
  expect(button.getAttribute('data-product-id')).toBe(mockItem.product.catalogProductNumber);
});
```

## Best Practices

### 1. Always Validate Prerequisites

Before navigating to the reward cart, ensure:

```typescript
// ✅ Good: Check prerequisites
async navigateToRewardCart() {
  const tabId = this.#tabService.activatedTabId();
  const customerId = this.#crmMetadata.selectedCustomerId(tabId);
  const cartId = this.#checkoutMetadata.getRewardShoppingCartId(tabId);

  if (!customerId) {
    // Show error: "Please select a customer first"
    return;
  }

  if (!cartId) {
    // Show error: "Reward cart is empty"
    return;
  }

  await this.router.navigate(['/', tabId, 'reward', 'cart']);
}

// ❌ Bad: Navigate without checking
async navigateToRewardCart() {
  await this.router.navigate(['/', tabId, 'reward', 'cart']);
  // Will show errors/empty states in cart page
}
```

### 2. Use Provided Resources

Always inject provided resources at the component level:

```typescript
// ✅ Good: Uses provided resource from RewardShoppingCartComponent
@Component({
  selector: 'app-custom-child',
  template: '...'
})
export class CustomChildComponent {
  #cartResource = inject(SelectedRewardShoppingCartResource);

  items = computed(() => this.#cartResource.resource.value()?.items ?? []);
}

// ❌ Bad: Creates new resource instance
@Component({
  selector: 'app-custom-child',
  providers: [SelectedRewardShoppingCartResource], // Don't do this
  template: '...'
})
export class CustomChildComponent {
  // Will create separate instance, lose shared state
}
```

### 3. Reload Resources After Mutations

Always reload resources after modifying cart data:

```typescript
// ✅ Good: Reloads resource after mutation
async removeItem() {
  await this.#shoppingCartFacade.removeItem({ ... });
  this.#rewardShoppingCartResource.reload(); // Refresh UI
}

// ❌ Bad: Forgets to reload
async removeItem() {
  await this.#shoppingCartFacade.removeItem({ ... });
  // UI won't update until next navigation
}
```

### 4. Handle Errors Gracefully

Provide user feedback for errors:

```typescript
// ✅ Good: Logs and shows user feedback
async complete() {
  try {
    const orders = await this.#orchestrator.completeCheckoutAndCreateOrders({ ... });
    this.#toastService.success('Order completed successfully');
    await this.router.navigate(['/confirmation', orders[0].id]);
  } catch (error) {
    this.#logger.error('Completion failed', error);
    this.#toastService.error('Failed to complete order. Please try again.');
  }
}

// ❌ Bad: Silent failure
async complete() {
  try {
    await this.#orchestrator.completeCheckoutAndCreateOrders({ ... });
  } catch (error) {
    // User has no idea what happened
  }
}
```

### 5. Use E2E Testing Attributes

Always include data-* attributes for QA testing:

```html
<!-- ✅ Good: Has E2E attributes -->
<button
  (click)="remove()"
  [attr.data-what]="'remove-shopping-cart-item-button'"
  [attr.data-which]="item().id"
  [attr.data-product-id]="item().product.catalogProductNumber"
>
  Remove
</button>

<!-- ❌ Bad: No E2E attributes -->
<button (click)="remove()">
  Remove
</button>
```

### 6. Coordinate Busy States

Share busy states between related components:

```typescript
// ✅ Good: Shared busy state prevents double-clicks
<checkout-reward-shopping-cart-item-quantity-control
  [item]="item"
></checkout-reward-shopping-cart-item-quantity-control>

<checkout-reward-shopping-cart-item-remove-button
  [item]="item"
  [(isBusy)]="isBusy"  // Shared with parent
></checkout-reward-shopping-cart-item-remove-button>

// Parent manages overall busy state
async updatePurchaseOption() {
  if (this.isBusy()) return; // Guards against concurrent operations
  this.isBusy.set(true);
  // ...
}
```

### 7. Use Breakpoint Service for Responsive Design

Prefer the breakpoint service over CSS-only solutions:

```typescript
// ✅ Good: SSR-compatible, reactive
import { breakpoint, Breakpoint } from '@isa/ui/layout';

isHorizontal = breakpoint([Breakpoint.DesktopL, Breakpoint.DesktopXL]);

// Template
@if (isHorizontal()) {
  <div class="horizontal-layout">...</div>
} @else {
  <div class="vertical-layout">...</div>
}

// ❌ Bad: CSS-only (SSR issues)
<div class="hidden isa-desktop:block">...</div>
<div class="block isa-desktop:hidden">...</div>
```

### 8. Leverage Deferred Rendering

Use @defer for large lists:

```html
<!-- ✅ Good: Defers rendering until viewport -->
@for (item of items(); track item.id) {
  @defer (on viewport) {
    <checkout-reward-shopping-cart-item [item]="item"></checkout-reward-shopping-cart-item>
  } @placeholder {
    <div>Loading item...</div>
  }
}

<!-- ❌ Bad: Renders all items immediately -->
@for (item of items(); track item.id) {
  <checkout-reward-shopping-cart-item [item]="item"></checkout-reward-shopping-cart-item>
}
```

### 9. Follow Angular Modern Patterns

Use Angular 20+ features:

```typescript
// ✅ Good: Modern Angular patterns
@Component({
  selector: 'app-example',
  changeDetection: ChangeDetectionStrategy.OnPush, // Always OnPush
  imports: [...],  // Standalone with explicit imports
})
export class ExampleComponent {
  #service = inject(Service);  // inject() over constructor

  value = input.required<string>();  // input() over @Input()
  output = output<string>();  // output() over @Output()

  computed = computed(() => this.value().toUpperCase());  // Signals
}

// ❌ Bad: Legacy patterns
@Component({
  selector: 'app-example',
  // No changeDetection specified (defaults to Default)
})
export class ExampleComponent {
  @Input() value!: string;  // Old decorator syntax
  @Output() output = new EventEmitter<string>();

  constructor(private service: Service) {}  // Constructor injection
}
```

### 10. Document Complex Logic

Add TODO comments for technical debt:

```typescript
// ✅ Good: Clear TODO with context
// TODO: [Next Sprint - High Priority] Create comprehensive test file
// - Test availability resource loading with quantity changes
// - Test price handling for redemption items (price override to 0)
// - Coverage target: >80% for this critical business logic component

// TODO: [Performance Optimization - Medium Priority] Replace structuredClone
// Current: Deep clone on every update (~1-2ms overhead)
// Target: Shallow spreads only when modifying
// Effort: ~1 hour | Impact: Medium | Risk: Very Low

// ❌ Bad: Vague TODO
// TODO: Fix this later
```

---

## License

Internal ISA Frontend library - not for external distribution.
