Files
Lorenz Hilpert 1784e08ce6 chore: update project configurations to skip CI for specific libraries
Added "skip:ci" tag to multiple project configurations to prevent CI runs
for certain libraries. This change affects the following libraries:
crm-feature-customer-card-transactions, crm-feature-customer-loyalty-cards,
oms-data-access, oms-feature-return-details, oms-feature-return-process,
oms-feature-return-summary, remission-data-access, remission-feature-remission-list,
remission-feature-remission-return-receipt-details, remission-feature-remission-return-receipt-list,
remission-shared-remission-start-dialog, remission-shared-return-receipt-actions,
shared-address, shared-delivery, ui-carousel, and ui-dialog.

Also updated CI command in package.json to exclude tests with the "skip:ci" tag.
2025-11-20 17:24:35 +01:00
..

@isa/remission/shared/return-receipt-actions

Angular standalone components for managing return receipt actions including deletion, continuation, and completion workflows in the remission process.

Overview

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

  • 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

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

@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

@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

<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

<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

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

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:

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:

// 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

# 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

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.