Merged PR 1844: feat(oms-data-access, oms-shared-task-list): add Tolino return receipt print support and improve task action typing

feat(oms-data-access, oms-shared-task-list): add Tolino return receipt print support and improve task action typing

- Add `PrintTolinoReturnReceiptService` to `oms-data-access` for printing Tolino return receipts via office printers, including direct integration with the OMS print API.
- Extend `TaskActionType` to include `receiptItemId` for more precise task identification and action handling.
- Update `return-task-list-item` and `return-task-list` components in `oms-shared-task-list` to support the new Tolino print action, including UI and logic for triggering the print dialog.
- Refactor print-related service and test code to use the new print API signature and improve type safety.
- Add and update unit tests to cover new print flows and ensure correct integration.

Ref: #5121
This commit is contained in:
Nino Righi
2025-06-03 22:17:29 +00:00
committed by Lorenz Hilpert
parent bcd3c800b1
commit 543de57190
18 changed files with 488 additions and 125 deletions

View File

@@ -244,14 +244,15 @@ export { ResponseArgsOfIEnumerableOfReceiptDTO } from './models/response-args-of
export { GenerateCollectiveReceiptsArgs } from './models/generate-collective-receipts-args';
export { ResponseArgsOfIEnumerableOfString } from './models/response-args-of-ienumerable-of-string';
export { DateRange } from './models/date-range';
export { ResponseArgsOfString } from './models/response-args-of-string';
export { ListResponseArgsOfReceiptItemTaskListItemDTO } from './models/list-response-args-of-receipt-item-task-list-item-dto';
export { ResponseArgsOfIEnumerableOfReceiptItemTaskListItemDTO } from './models/response-args-of-ienumerable-of-receipt-item-task-list-item-dto';
export { ListResponseArgsOfReceiptListItemDTO } from './models/list-response-args-of-receipt-list-item-dto';
export { ResponseArgsOfIEnumerableOfReceiptListItemDTO } from './models/response-args-of-ienumerable-of-receipt-list-item-dto';
export { ReceiptListItemDTO } from './models/receipt-list-item-dto';
export { ListResponseArgsOfReceiptItemListItemDTO } from './models/list-response-args-of-receipt-item-list-item-dto';
export { ResponseArgsOfIEnumerableOfReceiptItemListItemDTO } from './models/response-args-of-ienumerable-of-receipt-item-list-item-dto';
export { ReceiptItemListItemDTO } from './models/receipt-item-list-item-dto';
export { ListResponseArgsOfReceiptItemTaskListItemDTO } from './models/list-response-args-of-receipt-item-task-list-item-dto';
export { ResponseArgsOfIEnumerableOfReceiptItemTaskListItemDTO } from './models/response-args-of-ienumerable-of-receipt-item-task-list-item-dto';
export { ResponseArgsOfIEnumerableOfValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO } from './models/response-args-of-ienumerable-of-value-tuple-of-long-and-receipt-type-and-entity-dtocontainer-of-receipt-dto';
export { ValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO } from './models/value-tuple-of-long-and-receipt-type-and-entity-dtocontainer-of-receipt-dto';
export { ReceiptOrderItemSubsetReferenceValues } from './models/receipt-order-item-subset-reference-values';

View File

@@ -6,6 +6,7 @@ import { EntityDTOContainerOfBranchDTO } from './entity-dtocontainer-of-branch-d
import { KeyValueDTOOfStringAndString } from './key-value-dtoof-string-and-string';
import { EntityDTOContainerOfReceiptItemDTO } from './entity-dtocontainer-of-receipt-item-dto';
import { EntityDTOContainerOfLabelDTO } from './entity-dtocontainer-of-label-dto';
import { LinkedRecordDTO } from './linked-record-dto';
import { EntityDTOContainerOfOrderDTO } from './entity-dtocontainer-of-order-dto';
import { EntityDTOContainerOfPaymentDTO } from './entity-dtocontainer-of-payment-dto';
import { PaymentInfoDTO } from './payment-info-dto';
@@ -57,6 +58,11 @@ export interface ReceiptDTO extends EntityDTOBaseOfReceiptDTOAndIReceipt{
*/
label?: EntityDTOContainerOfLabelDTO;
/**
* Verknüpfte Datensätze (readonly)
*/
linkedRecords?: Array<LinkedRecordDTO>;
/**
* Bestellung
*/

View File

@@ -45,11 +45,26 @@ export interface ReceiptItemTaskListItemDTO {
*/
features?: {[key: string]: string};
/**
* Details
*/
handlingDetails?: string;
/**
* Reason / Grund
*/
handlingReason?: string;
/**
* Task ID
*/
id?: number;
/**
* Item condition / Artikelzustand
*/
itemCondition?: string;
/**
* Aufgabentyp
*/

View File

@@ -0,0 +1,9 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
export interface ResponseArgsOfString extends ResponseArgs{
/**
* Wert
*/
result?: string;
}

View File

@@ -16,10 +16,11 @@ import { ResponseArgsOfIEnumerableOfReceiptDTO } from '../models/response-args-o
import { GenerateCollectiveReceiptsArgs } from '../models/generate-collective-receipts-args';
import { ResponseArgsOfIEnumerableOfString } from '../models/response-args-of-ienumerable-of-string';
import { DateRange } from '../models/date-range';
import { ListResponseArgsOfReceiptListItemDTO } from '../models/list-response-args-of-receipt-list-item-dto';
import { QueryTokenDTO } from '../models/query-token-dto';
import { ListResponseArgsOfReceiptItemListItemDTO } from '../models/list-response-args-of-receipt-item-list-item-dto';
import { ResponseArgsOfString } from '../models/response-args-of-string';
import { ListResponseArgsOfReceiptItemTaskListItemDTO } from '../models/list-response-args-of-receipt-item-task-list-item-dto';
import { QueryTokenDTO } from '../models/query-token-dto';
import { ListResponseArgsOfReceiptListItemDTO } from '../models/list-response-args-of-receipt-list-item-dto';
import { ListResponseArgsOfReceiptItemListItemDTO } from '../models/list-response-args-of-receipt-item-list-item-dto';
import { ResponseArgsOfIEnumerableOfValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO } from '../models/response-args-of-ienumerable-of-value-tuple-of-long-and-receipt-type-and-entity-dtocontainer-of-receipt-dto';
import { ReceiptOrderItemSubsetReferenceValues } from '../models/receipt-order-item-subset-reference-values';
@Injectable({
@@ -33,6 +34,8 @@ class ReceiptService extends __BaseService {
static readonly ReceiptSetReceiptItemTaskToNOKPath = '/receipt/item/task/{taskId}/nok';
static readonly ReceiptGenerateCollectiveReceiptsPath = '/receipt/collectivereceipts';
static readonly ReceiptGenerateCollectiveReceiptsSimulationSummaryPath = '/receipt/collectivereceipts/simulationsummary';
static readonly ReceiptPostRuecknahmebelegPath = '/ruecknahmebeleg/{receiptId}';
static readonly ReceiptQueryReceiptItemTasksPath = '/receipt/item/task/s';
static readonly ReceiptQueryReceiptPath = '/receipt/s';
static readonly ReceiptQueryReceiptItemPath = '/receipt/item/s';
static readonly ReceiptCreateShippingNotePath = '/receipt/shippingnote/fromorder';
@@ -40,7 +43,6 @@ class ReceiptService extends __BaseService {
static readonly ReceiptCreateInvoicePath = '/receipt/invoice/fromorder';
static readonly ReceiptCreateInvoice2Path = '/receipt/invoice/fromitems';
static readonly ReceiptCreateReturnReceiptPath = '/receipt/return-receipt';
static readonly ReceiptQueryReceiptItemTasksPath = '/receipt/item/task/s';
static readonly ReceiptReceiptItemTaskCompletedPath = '/receipt/item/task/{taskId}/completed';
static readonly ReceiptGetReceiptsByOrderItemSubsetPath = '/order/orderitem/orderitemsubset/receipts';
@@ -307,6 +309,74 @@ class ReceiptService extends __BaseService {
);
}
/**
* @param receiptId undefined
*/
ReceiptPostRuecknahmebelegResponse(receiptId: number): __Observable<__StrictHttpResponse<ResponseArgsOfString>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/ruecknahmebeleg/${encodeURIComponent(String(receiptId))}`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfString>;
})
);
}
/**
* @param receiptId undefined
*/
ReceiptPostRuecknahmebeleg(receiptId: number): __Observable<ResponseArgsOfString> {
return this.ReceiptPostRuecknahmebelegResponse(receiptId).pipe(
__map(_r => _r.body as ResponseArgsOfString)
);
}
/**
* @param queryToken undefined
*/
ReceiptQueryReceiptItemTasksResponse(queryToken: QueryTokenDTO): __Observable<__StrictHttpResponse<ListResponseArgsOfReceiptItemTaskListItemDTO>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
__body = queryToken;
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/receipt/item/task/s`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfReceiptItemTaskListItemDTO>;
})
);
}
/**
* @param queryToken undefined
*/
ReceiptQueryReceiptItemTasks(queryToken: QueryTokenDTO): __Observable<ListResponseArgsOfReceiptItemTaskListItemDTO> {
return this.ReceiptQueryReceiptItemTasksResponse(queryToken).pipe(
__map(_r => _r.body as ListResponseArgsOfReceiptItemTaskListItemDTO)
);
}
/**
* Belege
* @param params The `ReceiptService.ReceiptQueryReceiptParams` containing the following parameters:
@@ -647,42 +717,6 @@ class ReceiptService extends __BaseService {
);
}
/**
* Suche nach Bestellpostenstatus-Aufgaben
* @param queryToken Suchkriterien
*/
ReceiptQueryReceiptItemTasksResponse(queryToken: QueryTokenDTO): __Observable<__StrictHttpResponse<ListResponseArgsOfReceiptItemTaskListItemDTO>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
__body = queryToken;
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/receipt/item/task/s`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfReceiptItemTaskListItemDTO>;
})
);
}
/**
* Suche nach Bestellpostenstatus-Aufgaben
* @param queryToken Suchkriterien
*/
ReceiptQueryReceiptItemTasks(queryToken: QueryTokenDTO): __Observable<ListResponseArgsOfReceiptItemTaskListItemDTO> {
return this.ReceiptQueryReceiptItemTasksResponse(queryToken).pipe(
__map(_r => _r.body as ListResponseArgsOfReceiptItemTaskListItemDTO)
);
}
/**
* Aufgabe auf erledigt setzen
* @param taskId undefined

View File

@@ -11,6 +11,7 @@ import { ResponseArgs } from '../models/response-args';
import { PrintRequestOfIEnumerableOfLong } from '../models/print-request-of-ienumerable-of-long';
import { PrintRequestOfIEnumerableOfDisplayOrderDTO } from '../models/print-request-of-ienumerable-of-display-order-dto';
import { PrintRequestOfIEnumerableOfPriceQRCodeDTO } from '../models/print-request-of-ienumerable-of-price-qrcode-dto';
import { PrintRequestOfLong } from '../models/print-request-of-long';
@Injectable({
providedIn: 'root',
})
@@ -25,6 +26,8 @@ class OMSPrintService extends __BaseService {
static readonly OMSPrintReturnReceiptPath = '/print/return-receipt';
static readonly OMSPrintKleinbetragsrechnungPath = '/print/kleinbetragsrechnung';
static readonly OMSPrintKleinbetragsrechnungPdfPath = '/print/kleinbetragsrechnung/{receiptId}/pdf';
static readonly OMSPrintTolinoRetourenscheinPath = '/print/tolino-retourenschein';
static readonly OMSPrintTolinoRetourenscheinPdfPath = '/print/tolino-retourenschein/{receipItemtId}/pdf';
constructor(
config: __Configuration,
@@ -392,6 +395,78 @@ class OMSPrintService extends __BaseService {
__map(_r => _r.body as Blob)
);
}
/**
* Tolino Retourenschein
* @param data Retourenbelegpostion PKs
*/
OMSPrintTolinoRetourenscheinResponse(data: PrintRequestOfLong): __Observable<__StrictHttpResponse<ResponseArgs>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
__body = data;
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/print/tolino-retourenschein`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgs>;
})
);
}
/**
* Tolino Retourenschein
* @param data Retourenbelegpostion PKs
*/
OMSPrintTolinoRetourenschein(data: PrintRequestOfLong): __Observable<ResponseArgs> {
return this.OMSPrintTolinoRetourenscheinResponse(data).pipe(
__map(_r => _r.body as ResponseArgs)
);
}
/**
* Tolino Retourenschein PDF
* @param receipItemtId Retourenbelegposition PK
*/
OMSPrintTolinoRetourenscheinPdfResponse(receipItemtId: number): __Observable<__StrictHttpResponse<Blob>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/print/tolino-retourenschein/${encodeURIComponent(String(receipItemtId))}/pdf`,
__body,
{
headers: __headers,
params: __params,
responseType: 'blob'
});
return this.http.request<any>(req).pipe(
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<Blob>;
})
);
}
/**
* Tolino Retourenschein PDF
* @param receipItemtId Retourenbelegposition PK
*/
OMSPrintTolinoRetourenscheinPdf(receipItemtId: number): __Observable<Blob> {
return this.OMSPrintTolinoRetourenscheinPdfResponse(receipItemtId).pipe(
__map(_r => _r.body as Blob)
);
}
}
module OMSPrintService {

View File

@@ -143,7 +143,10 @@ describe('PrintService', () => {
it('should fetch printers of the specified type', async () => {
// Act
await spectator.service.print(PrinterType.LABEL, mockPrint);
await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(printSpy).toHaveBeenCalledWith(PrinterType.LABEL);
@@ -151,7 +154,10 @@ describe('PrintService', () => {
it('should attempt direct printing on desktop when a printer is selected', async () => {
// Act
await spectator.service.print(PrinterType.LABEL, mockPrint);
await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(mockPrint).toHaveBeenCalledWith(mockPrinters[1]);
@@ -160,10 +166,10 @@ describe('PrintService', () => {
it('should return the selected printer after successful direct print', async () => {
// Act
const result = await spectator.service.print(
PrinterType.LABEL,
mockPrint,
);
const result = await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(result).toEqual({ printer: mockPrinters[1] });
@@ -175,7 +181,10 @@ describe('PrintService', () => {
mockPrint.mockRejectedValueOnce(printError);
// Act
await spectator.service.print(PrinterType.LABEL, mockPrint);
await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(mockDialogFn).toHaveBeenCalledWith({
@@ -192,7 +201,10 @@ describe('PrintService', () => {
platform.ANDROID = true;
// Act
await spectator.service.print(PrinterType.LABEL, mockPrint);
await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(mockPrint).not.toHaveBeenCalled();
@@ -204,7 +216,10 @@ describe('PrintService', () => {
platform.IOS = true;
// Act
await spectator.service.print(PrinterType.LABEL, mockPrint);
await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(mockPrint).not.toHaveBeenCalled();
@@ -221,7 +236,10 @@ describe('PrintService', () => {
printSpy.mockResolvedValueOnce(printersWithoutSelection);
// Act
await spectator.service.print(PrinterType.LABEL, mockPrint);
await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(mockPrint).not.toHaveBeenCalled();
@@ -233,7 +251,10 @@ describe('PrintService', () => {
platform.ANDROID = true; // Force dialog to show
// Act
await spectator.service.print(PrinterType.LABEL, mockPrint);
await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(mockDialogFn).toHaveBeenCalledWith({
@@ -252,10 +273,10 @@ describe('PrintService', () => {
mockDialogClosedObservable.closed = of({ printer: selectedPrinter });
// Act
const result = await spectator.service.print(
PrinterType.LABEL,
mockPrint,
);
const result = await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(result).toEqual({ printer: selectedPrinter });
@@ -267,10 +288,10 @@ describe('PrintService', () => {
mockDialogClosedObservable.closed = of(undefined);
// Act
const result = await spectator.service.print(
PrinterType.LABEL,
mockPrint,
);
const result = await spectator.service.print({
printerType: PrinterType.LABEL,
printFn: mockPrint,
});
// Assert
expect(result).toEqual({ printer: undefined });

View File

@@ -48,18 +48,27 @@ export class PrintService {
}
/**
* Initiates a print operation with platform-specific optimizations
* On desktop, attempts to print directly using the default printer if available
* Falls back to showing a printer selection dialog if needed or on mobile devices
* Initiates a print operation with platform-specific optimizations.
* On desktop, attempts to print directly using the default printer if available.
* Falls back to showing a printer selection dialog if needed or on mobile devices.
*
* @param printerType The type of printer to use (LABEL or OFFICE)
* @param printFn Function that performs the actual print operation with the selected printer
* @returns Object containing the selected printer or undefined if operation was cancelled
* @param printerType - The type of printer to use (LABEL or OFFICE).
* @param printFn - Function that performs the actual print operation with the selected printer.
* @param directPrint - (Optional) If true, forces direct print without showing the dialog.
* If false, always shows the print dialog.
* If undefined, uses direct print only on non-mobile platforms with a selected printer.
* @returns An object containing the selected printer if the operation was successful,
* or undefined if the operation was cancelled.
*/
async print(
printerType: PrinterType,
printFn: (printer: Printer) => Promise<any>,
): Promise<{ printer?: Printer }> {
async print({
printerType,
printFn,
directPrint,
}: {
printerType: PrinterType;
printFn: (printer: Printer) => Promise<unknown>;
directPrint?: boolean | undefined;
}): Promise<{ printer?: Printer }> {
// Get the list of printers based on the printer type
const printers = await this.printers(printerType);
@@ -69,10 +78,12 @@ export class PrintService {
// and we can try to print directly to the selected printer.
// If it fails, we show the print dialog with the error.
const directPrintAllowed =
selectedPrinter && !(this.#platform.ANDROID || this.#platform.IOS);
directPrint === undefined
? selectedPrinter && !(this.#platform.ANDROID || this.#platform.IOS)
: directPrint;
let error: unknown | undefined = undefined;
if (directPrintAllowed) {
if (directPrintAllowed && selectedPrinter) {
try {
await printFn(selectedPrinter);
return { printer: selectedPrinter };

View File

@@ -12,6 +12,7 @@ export type TaskActionTypeType =
export interface TaskActionType {
type: TaskActionTypeType;
taskId: number;
receiptItemId?: number;
updateTo?: Exclude<TaskActionTypeType, 'UNKNOWN'>;
actions?: Array<KeyValueDTOOfStringAndString>;
}

View File

@@ -4,3 +4,4 @@ export * from './print-receipts.service';
export * from './return-process.service';
export * from './return-search.service';
export * from './return-task-list.service';
export * from './print-tolino-return-receipt.service';

View File

@@ -35,17 +35,19 @@ describe('PrintReceiptsService', () => {
it('should call the print service with the correct parameters', async () => {
const mockReturnReceiptIds = [1, 2, 3];
mockPrintService.print.mockImplementation((printerType, callback) => {
expect(printerType).toBe(PrinterType.LABEL);
const mockPrinter: Printer = {
key: 'mockPrinterKey',
value: 'Mock Printer',
selected: true,
enabled: true,
description: 'Mock printer description',
};
return callback(mockPrinter);
});
mockPrintService.print.mockImplementation(
async ({ printerType, printFn }) => {
expect(printerType).toBe(PrinterType.LABEL);
const mockPrinter: Printer = {
key: 'mockPrinterKey',
value: 'Mock Printer',
selected: true,
enabled: true,
description: 'Mock printer description',
};
return printFn(mockPrinter).then(() => ({ printer: mockPrinter }));
},
);
mockOmsPrintService.OMSPrintReturnReceipt.mockReturnValue(
of({ error: false }),
@@ -56,8 +58,10 @@ describe('PrintReceiptsService', () => {
});
expect(mockPrintService.print).toHaveBeenCalledWith(
expect.anything(),
expect.any(Function),
expect.objectContaining({
printerType: PrinterType.LABEL,
printFn: expect.any(Function),
}),
);
expect(mockOmsPrintService.OMSPrintReturnReceipt).toHaveBeenCalledWith({
printer: expect.any(String),

View File

@@ -31,13 +31,16 @@ export class PrintReceiptsService {
throw new Error('No return receipt IDs provided');
}
return this.#printService.print(PrinterType.LABEL, (printer) => {
return firstValueFrom(
this.#omsPrintService.OMSPrintReturnReceipt({
printer: printer.key,
data: returnReceiptIds,
}),
);
return this.#printService.print({
printerType: PrinterType.LABEL,
printFn: (printer) => {
return firstValueFrom(
this.#omsPrintService.OMSPrintReturnReceipt({
printer: printer.key,
data: returnReceiptIds,
}),
);
},
});
}
}

View File

@@ -0,0 +1,77 @@
import { SpectatorService, createServiceFactory } from '@ngneat/spectator/jest';
import { PrintTolinoReturnReceiptService } from './print-tolino-return-receipt.service';
import { OMSPrintService } from '@generated/swagger/print-api';
import { PrintService, Printer, PrinterType } from '@isa/common/print';
import { of } from 'rxjs';
describe('PrintTolinoReturnReceiptService', () => {
let spectator: SpectatorService<PrintTolinoReturnReceiptService>;
const createService = createServiceFactory({
service: PrintTolinoReturnReceiptService,
mocks: [OMSPrintService, PrintService],
});
let mockPrintService: jest.Mocked<PrintService>;
let mockOmsPrintService: jest.Mocked<OMSPrintService>;
beforeEach(() => {
spectator = createService();
mockPrintService = spectator.inject(PrintService);
mockOmsPrintService = spectator.inject(OMSPrintService);
});
it('should be created', () => {
expect(spectator.service).toBeTruthy();
});
describe('printTolinoReturnReceipt', () => {
it('should throw an error if no return receipt ID is provided', async () => {
await expect(
spectator.service.printTolinoReturnReceipt({
returnReceiptId: undefined as unknown as number,
}),
).rejects.toThrow('No return receipt ID provided');
});
it('should call the print service with the correct parameters', async () => {
const mockReturnReceiptId = 42;
mockPrintService.print.mockImplementation(
async ({ printerType, printFn, directPrint }) => {
expect(printerType).toBe(PrinterType.OFFICE);
expect(directPrint).toBe(false);
const mockPrinter: Printer = {
key: 'mockPrinterKey',
value: 'Mock Printer',
selected: true,
enabled: true,
description: 'Mock printer description',
};
return printFn(mockPrinter).then(() => ({ printer: mockPrinter }));
},
);
mockOmsPrintService.OMSPrintTolinoRetourenschein.mockReturnValue(
of({ error: false }),
);
await spectator.service.printTolinoReturnReceipt({
returnReceiptId: mockReturnReceiptId,
});
expect(mockPrintService.print).toHaveBeenCalledWith(
expect.objectContaining({
printerType: PrinterType.OFFICE,
printFn: expect.any(Function),
}),
);
expect(
mockOmsPrintService.OMSPrintTolinoRetourenschein,
).toHaveBeenCalledWith({
printer: expect.any(String),
data: mockReturnReceiptId,
});
});
});
});

View File

@@ -0,0 +1,44 @@
import { inject, Injectable } from '@angular/core';
import { OMSPrintService } from '@generated/swagger/print-api';
import { PrinterType, PrintService } from '@isa/common/print';
import { firstValueFrom } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class PrintTolinoReturnReceiptService {
#omsPrintService = inject(OMSPrintService);
#printService = inject(PrintService);
/**
* Prints a Tolino return receipt using the office printer.
*
* @param params - The parameters for printing.
* @param params.returnReceiptId - The unique identifier of the return receipt to print.
* @returns A promise that resolves when the print job is completed.
* @throws {Error} If no return receipt ID is provided.
*
* @example
* await printTolinoReturnReceiptService.printTolinoReturnReceipt({ returnReceiptId: 123 });
*/
async printTolinoReturnReceipt({
returnReceiptId,
}: {
returnReceiptId: number;
}) {
if (!returnReceiptId) {
throw new Error('No return receipt ID provided');
}
return this.#printService.print({
printerType: PrinterType.OFFICE,
printFn: (printer) => {
return firstValueFrom(
this.#omsPrintService.OMSPrintTolinoRetourenschein({
printer: printer.key,
data: returnReceiptId,
}),
);
},
directPrint: false,
});
}
}

View File

@@ -1,4 +1,5 @@
<oms-shared-return-product-info
class="product-info"
[product]="product()"
data-what="component"
data-which="return-product-info"
@@ -6,19 +7,25 @@
@let taskItem = item();
@let taskActionType = type();
<div
class="h-full p-4 flex flex-col gap-4 rounded-lg bg-isa-secondary-100"
[class.unkown-type]="taskActionType === 'UNKNOWN'"
>
@if (taskActionType === 'UNKNOWN') {
<div
data-what="task-list"
data-which="processing-comment"
class="processing-comment"
class="processing-comment-unknown"
>
{{ processingComment() }}
</div>
@if (!taskItem?.completed) {
@if (taskActionType !== 'UNKNOWN') {
} @else {
<div class="task-item-infos">
<div
data-what="task-list"
data-which="processing-comment"
class="processing-comment"
>
{{ processingComment() }}
</div>
@if (!taskItem?.completed) {
<button
class="flex items-center gap-2 self-end"
type="button"
@@ -31,21 +38,21 @@
<ng-icon name="isaActionCheck" uiButtonIcon></ng-icon>
Als erledigt markieren
</button>
} @else {
<span
class="flex items-center gap-2 text-isa-accent-green isa-text-body-2-bold self-end"
data-what="info"
data-which="completed"
>
<ng-icon name="isaActionCheck" uiButtonIcon></ng-icon>
Abgeschlossen
</span>
}
} @else {
<span
class="flex items-center gap-2 text-isa-accent-green isa-text-body-2-bold self-end"
data-what="info"
data-which="completed"
>
<ng-icon name="isaActionCheck" uiButtonIcon></ng-icon>
Abgeschlossen
</span>
}
</div>
</div>
}
@if (taskActionType === 'UNKNOWN' && !taskItem?.completed) {
<div class="flex flex-row gap-3 h-full py-2">
<div class="task-unknown-actions">
<button
class="flex items-center"
type="button"
@@ -71,11 +78,11 @@
</div>
}
@if (taskItem?.actions && taskItem.actions.length > 0) {
@if (displayPrintTolino()) {
<button
data-what="button"
data-which="print-receipt"
class="self-start"
class="tolino-print-cta"
(click)="onActionClick({ type: taskActionType, actions: taskItem.actions })"
uiInfoButton
>

View File

@@ -3,21 +3,58 @@
}
.oms-shared-return-task-list-item__review {
@apply grid grid-cols-[1fr,1fr] desktop:grid-cols-[1fr,1fr,minmax(20rem,auto)] gap-6 py-6 text-isa-secondary-900 items-center border-b border-solid border-isa-neutral-300 last:pb-0 last:border-none;
@apply grid grid-cols-[1fr,1fr] desktop:grid-cols-[1fr,1fr,minmax(20rem,auto)] gap-x-6 py-6 text-isa-secondary-900 items-center border-b border-solid border-isa-neutral-300 last:pb-0 last:border-none;
@media screen and (max-width: 1024px) {
grid-template-areas:
'product infos'
'unknown-comment actions';
.product-info {
grid-area: product;
}
.task-item-infos {
grid-area: infos;
}
.tolino-print-cta,
.task-unknown-actions {
@apply mt-6 desktop:mt-0;
grid-area: actions;
}
.processing-comment-unknown {
@apply mt-6 desktop:mt-0;
grid-area: unknown-comment;
}
}
}
.oms-shared-return-task-list-item__main {
@apply bg-white rounded-2xl p-6 flex flex-col gap-6 w-[24.25rem];
.tolino-print-cta {
@apply self-end;
}
}
.task-item-infos {
@apply h-full p-4 flex flex-col gap-4 rounded-lg bg-isa-secondary-100;
}
.tolino-print-cta {
@apply w-fit self-end justify-self-end desktop:self-start;
}
.task-unknown-actions {
@apply flex flex-row gap-3 h-full py-2 items-center;
}
.processing-comment {
@apply isa-text-body-2-bold;
}
.unkown-type {
@apply rounded-none bg-isa-white;
.processing-comment {
@apply isa-text-body-1-regular;
}
.processing-comment-unknown {
@apply isa-text-body-1-regular;
}

View File

@@ -67,6 +67,13 @@ export class ReturnTaskListItemComponent {
return undefined;
});
displayPrintTolino = computed(() => {
const item = this.item();
return item?.actions?.some(
(keyValue) => keyValue.key === 'PRINT_TOLINO_RETURN_RECEIPT',
);
});
type: Signal<TaskActionTypeType> = computed(() => {
const item = this.item();
const mappedType: Omit<TaskActionType, 'taskId'> = {
@@ -77,8 +84,10 @@ export class ReturnTaskListItemComponent {
onActionClick(type: Omit<TaskActionType, 'taskId'>) {
const taskId = this.item().id;
const receiptItemId = this.item()?.receiptItem?.id;
const actionType: TaskActionType = {
taskId,
receiptItemId,
type: type.type,
updateTo: type?.updateTo,
actions: type?.actions,

View File

@@ -10,7 +10,9 @@ import {
} from '@angular/core';
import { ReturnTaskListItemComponent } from './return-task-list-item/return-task-list-item.component';
import {
PrintTolinoReturnReceiptService,
QueryTokenInput,
ReceiptItemTaskListItem,
ReturnTaskListService,
ReturnTaskListStore,
TaskActionType,
@@ -39,6 +41,7 @@ import { logger, provideLoggerContext } from '@isa/core/logging';
export class ReturnTaskListComponent {
appearance = input<'main' | 'review'>('main');
#returnTaskListService = inject(ReturnTaskListService);
#printTolinoReturnReceiptService = inject(PrintTolinoReturnReceiptService);
#returnTaskListStore = inject(ReturnTaskListStore);
#logger = logger();
@@ -99,10 +102,15 @@ export class ReturnTaskListComponent {
}
async handleAction(action: TaskActionType) {
if (action.actions && action.actions.length > 0) {
// TODO: Spezieller Print request für Tolino - DIN-A4 Retourenschein Drucken
console.log('Tolino Print action: ', action.actions);
return;
if (
action?.actions?.some(
(keyValue) => keyValue.key === 'PRINT_TOLINO_RETURN_RECEIPT',
) &&
action.receiptItemId
) {
return await this.#printTolinoReturnReceiptService.printTolinoReturnReceipt(
{ returnReceiptId: action.receiptItemId },
);
}
if (action.type === 'UNKNOWN' && !!action.updateTo) {