Merged PR 2069: feature(oms-data-access, oms-return-task-list): Return can now handle Rewards

feature(oms-data-access, oms-return-task-list): Return can now handle Rewards

#5373
This commit is contained in:
Nino Righi
2025-12-05 10:10:18 +00:00
committed by Lorenz Hilpert
parent 7e7721b222
commit 3696fb5b2d
11 changed files with 186 additions and 14 deletions

View File

@@ -1,5 +1,6 @@
/* tslint:disable */ /* tslint:disable */
import { EntityDTOBaseOfDisplayOrderItemDTOAndIOrderItem } from './entity-dtobase-of-display-order-item-dtoand-iorder-item'; import { EntityDTOBaseOfDisplayOrderItemDTOAndIOrderItem } from './entity-dtobase-of-display-order-item-dtoand-iorder-item';
import { KeyValueDTOOfStringAndString } from './key-value-dtoof-string-and-string';
import { LoyaltyDTO } from './loyalty-dto'; import { LoyaltyDTO } from './loyalty-dto';
import { DisplayOrderDTO } from './display-order-dto'; import { DisplayOrderDTO } from './display-order-dto';
import { PriceDTO } from './price-dto'; import { PriceDTO } from './price-dto';
@@ -9,6 +10,11 @@ import { QuantityUnitType } from './quantity-unit-type';
import { DisplayOrderItemSubsetDTO } from './display-order-item-subset-dto'; import { DisplayOrderItemSubsetDTO } from './display-order-item-subset-dto';
export interface DisplayOrderItemDTO extends EntityDTOBaseOfDisplayOrderItemDTOAndIOrderItem{ export interface DisplayOrderItemDTO extends EntityDTOBaseOfDisplayOrderItemDTOAndIOrderItem{
/**
* Mögliche Aktionen
*/
actions?: Array<KeyValueDTOOfStringAndString>;
/** /**
* Bemerkung des Auftraggebers * Bemerkung des Auftraggebers
*/ */

View File

@@ -1,10 +1,16 @@
/* tslint:disable */ /* tslint:disable */
import { EntityDTOBaseOfDisplayOrderItemSubsetDTOAndIOrderItemStatus } from './entity-dtobase-of-display-order-item-subset-dtoand-iorder-item-status'; import { EntityDTOBaseOfDisplayOrderItemSubsetDTOAndIOrderItemStatus } from './entity-dtobase-of-display-order-item-subset-dtoand-iorder-item-status';
import { KeyValueDTOOfStringAndString } from './key-value-dtoof-string-and-string';
import { DateRangeDTO } from './date-range-dto'; import { DateRangeDTO } from './date-range-dto';
import { DisplayOrderItemDTO } from './display-order-item-dto'; import { DisplayOrderItemDTO } from './display-order-item-dto';
import { OrderItemProcessingStatusValue } from './order-item-processing-status-value'; import { OrderItemProcessingStatusValue } from './order-item-processing-status-value';
export interface DisplayOrderItemSubsetDTO extends EntityDTOBaseOfDisplayOrderItemSubsetDTOAndIOrderItemStatus{ export interface DisplayOrderItemSubsetDTO extends EntityDTOBaseOfDisplayOrderItemSubsetDTOAndIOrderItemStatus{
/**
* Mögliche Aktionen
*/
actions?: Array<KeyValueDTOOfStringAndString>;
/** /**
* Abholfachnummer * Abholfachnummer
*/ */
@@ -40,6 +46,11 @@ export interface DisplayOrderItemSubsetDTO extends EntityDTOBaseOfDisplayOrderIt
*/ */
estimatedShippingDate?: string; estimatedShippingDate?: string;
/**
* Zusätzliche Markierungen (z.B. Abo, ...)
*/
features?: {[key: string]: string};
/** /**
* Bestellposten * Bestellposten
*/ */

View File

@@ -118,6 +118,11 @@ export interface ReceiptDTO extends EntityDTOBaseOfReceiptDTOAndIReceipt{
*/ */
receiptNumber?: string; receiptNumber?: string;
/**
* Subtype of the receipt / Beleg-Unterart
*/
receiptSubType?: string;
/** /**
* Belegtext * Belegtext
*/ */

View File

@@ -10,6 +10,7 @@ export * from './get-receipt-item-quantity.helper';
export * from './get-return-info.helper'; export * from './get-return-info.helper';
export * from './get-return-process-questions.helper'; export * from './get-return-process-questions.helper';
export * from './get-tolino-questions.helper'; export * from './get-tolino-questions.helper';
export * from './is-task-type.helper';
export * from './receipt-item-has-category.helper'; export * from './receipt-item-has-category.helper';
export * from './return-details-mapping.helper'; export * from './return-details-mapping.helper';
export * from './return-receipt-values-mapping.helper'; export * from './return-receipt-values-mapping.helper';

View File

@@ -0,0 +1,88 @@
import { TaskActionTypes } from '../../models';
import { isTaskType } from './is-task-type.helper';
describe('isTaskType', () => {
describe('OK type matching', () => {
it('should return true when comparing OK with OK', () => {
expect(isTaskType('OK', TaskActionTypes.OK)).toBe(true);
});
it('should return true when comparing RETOURE_OK with OK', () => {
expect(isTaskType('RETOURE_OK', TaskActionTypes.OK)).toBe(true);
});
it('should return true when comparing OK with RETOURE_OK', () => {
expect(isTaskType('OK', TaskActionTypes.RETOURE_OK)).toBe(true);
});
it('should return true when comparing RETOURE_OK with RETOURE_OK', () => {
expect(isTaskType('RETOURE_OK', TaskActionTypes.RETOURE_OK)).toBe(true);
});
});
describe('NOK type matching', () => {
it('should return true when comparing NOK with NOK', () => {
expect(isTaskType('NOK', TaskActionTypes.NOK)).toBe(true);
});
it('should return true when comparing RETOURE_NOK with NOK', () => {
expect(isTaskType('RETOURE_NOK', TaskActionTypes.NOK)).toBe(true);
});
it('should return true when comparing NOK with RETOURE_NOK', () => {
expect(isTaskType('NOK', TaskActionTypes.RETOURE_NOK)).toBe(true);
});
it('should return true when comparing RETOURE_NOK with RETOURE_NOK', () => {
expect(isTaskType('RETOURE_NOK', TaskActionTypes.RETOURE_NOK)).toBe(true);
});
});
describe('UNKNOWN type matching', () => {
it('should return true when comparing UNKNOWN with UNKNOWN', () => {
expect(isTaskType('UNKNOWN', TaskActionTypes.UNKNOWN)).toBe(true);
});
it('should return true when comparing RETOURE_UNKNOWN with UNKNOWN', () => {
expect(isTaskType('RETOURE_UNKNOWN', TaskActionTypes.UNKNOWN)).toBe(true);
});
it('should return true when comparing UNKNOWN with RETOURE_UNKNOWN', () => {
expect(isTaskType('UNKNOWN', TaskActionTypes.RETOURE_UNKNOWN)).toBe(true);
});
it('should return true when comparing RETOURE_UNKNOWN with RETOURE_UNKNOWN', () => {
expect(
isTaskType('RETOURE_UNKNOWN', TaskActionTypes.RETOURE_UNKNOWN),
).toBe(true);
});
});
describe('non-matching types', () => {
it('should return false when comparing OK with NOK', () => {
expect(isTaskType('OK', TaskActionTypes.NOK)).toBe(false);
});
it('should return false when comparing RETOURE_OK with UNKNOWN', () => {
expect(isTaskType('RETOURE_OK', TaskActionTypes.UNKNOWN)).toBe(false);
});
it('should return false when comparing NOK with OK', () => {
expect(isTaskType('NOK', TaskActionTypes.OK)).toBe(false);
});
});
describe('falsy values', () => {
it('should return false when value is undefined', () => {
expect(isTaskType(undefined, TaskActionTypes.OK)).toBe(false);
});
it('should return false when value is null', () => {
expect(isTaskType(null, TaskActionTypes.OK)).toBe(false);
});
it('should return false when value is empty string', () => {
expect(isTaskType('', TaskActionTypes.OK)).toBe(false);
});
});
});

View File

@@ -0,0 +1,34 @@
import { TaskActionTypeType } from '../../models';
/**
* Checks if a task action type value matches a given type, including its RETOURE_ variant.
*
* This helper normalizes both values by stripping the 'RETOURE_' prefix before comparison,
* allowing flexible matching between base types and their return variants.
*
* @param value - The task action type value to check (can be a base type or RETOURE_ variant)
* @param type - The task action type to compare against (can be a base type or RETOURE_ variant)
* @returns `true` if the normalized values match, `false` otherwise
*
* @example
* // All of these return true:
* isTaskType('OK', TaskActionTypes.OK) // 'OK' === 'OK'
* isTaskType('RETOURE_OK', TaskActionTypes.OK) // 'OK' === 'OK'
* isTaskType('OK', TaskActionTypes.RETOURE_OK) // 'OK' === 'OK'
* isTaskType('RETOURE_OK', TaskActionTypes.RETOURE_OK) // 'OK' === 'OK'
*
* @example
* // Returns false:
* isTaskType('OK', TaskActionTypes.NOK) // 'OK' !== 'NOK'
* isTaskType('RETOURE_OK', TaskActionTypes.UNKNOWN) // 'OK' !== 'UNKNOWN'
* isTaskType(undefined, TaskActionTypes.OK) // value is falsy
*/
export const isTaskType = (
value: TaskActionTypeType | string | undefined | null,
type: TaskActionTypeType,
): boolean => {
if (!value) return false;
const normalizedValue = value.replace('RETOURE_', '');
const normalizedType = type.replace('RETOURE_', '');
return normalizedValue === normalizedType;
};

View File

@@ -2,8 +2,11 @@ import { KeyValueDTOOfStringAndString } from '@generated/swagger/oms-api';
export const TaskActionTypes = { export const TaskActionTypes = {
OK: 'OK', OK: 'OK',
RETOURE_OK: 'RETOURE_OK',
NOK: 'NOK', NOK: 'NOK',
RETOURE_NOK: 'RETOURE_NOK',
UNKNOWN: 'UNKNOWN', UNKNOWN: 'UNKNOWN',
RETOURE_UNKNOWN: 'RETOURE_UNKNOWN',
} as const; } as const;
export type TaskActionTypeType = export type TaskActionTypeType =
@@ -13,6 +16,6 @@ export interface TaskActionType {
type: TaskActionTypeType; type: TaskActionTypeType;
taskId: number; taskId: number;
receiptItemId?: number; receiptItemId?: number;
updateTo?: Exclude<TaskActionTypeType, 'UNKNOWN'>; updateTo?: Exclude<TaskActionTypeType, 'UNKNOWN' | 'RETOURE_UNKNOWN'>;
actions?: Array<KeyValueDTOOfStringAndString>; actions?: Array<KeyValueDTOOfStringAndString>;
} }

View File

@@ -1,6 +1,11 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { map, Observable, throwError } from 'rxjs'; import { map, Observable, throwError } from 'rxjs';
import { ReceiptItemTaskListItem, TaskActionTypeType } from '../models'; import {
ReceiptItemTaskListItem,
TaskActionTypes,
TaskActionTypeType,
} from '../models';
import { isTaskType } from '../helpers';
import { QueryTokenInput, QueryTokenSchema } from '../schemas'; import { QueryTokenInput, QueryTokenSchema } from '../schemas';
import { ZodError } from 'zod'; import { ZodError } from 'zod';
import { ReturnParseQueryTokenError } from '../errors'; import { ReturnParseQueryTokenError } from '../errors';
@@ -98,7 +103,7 @@ export class ReturnTaskListService {
* @throws Error when the update operation fails or returns an error * @throws Error when the update operation fails or returns an error
*/ */
updateTaskType(updateTask: { updateTaskType(updateTask: {
type: Exclude<TaskActionTypeType, 'UNKNOWN'>; type: Exclude<TaskActionTypeType, 'UNKNOWN' | 'RETOURE_UNKNOWN'>;
taskId: number; taskId: number;
}) { }) {
try { try {
@@ -127,18 +132,18 @@ export class ReturnTaskListService {
* @private * @private
*/ */
private _updateTaskRequestHelper(updateTask: { private _updateTaskRequestHelper(updateTask: {
type: Exclude<TaskActionTypeType, 'UNKNOWN'>; type: Exclude<TaskActionTypeType, 'UNKNOWN' | 'RETOURE_UNKNOWN'>;
taskId: number; taskId: number;
}): Observable<ResponseArgsOfReceiptItemTaskListItemDTO> { }): Observable<ResponseArgsOfReceiptItemTaskListItemDTO> {
if (!updateTask?.taskId) { if (!updateTask?.taskId) {
return throwError(() => new Error('Task ID missing')); return throwError(() => new Error('Task ID missing'));
} }
if (updateTask.type === 'OK') { if (isTaskType(updateTask.type, TaskActionTypes.OK)) {
return this.#receiptService.ReceiptSetReceiptItemTaskToOK( return this.#receiptService.ReceiptSetReceiptItemTaskToOK(
updateTask.taskId, updateTask.taskId,
); );
} else if (updateTask.type === 'NOK') { } else if (isTaskType(updateTask.type, TaskActionTypes.NOK)) {
return this.#receiptService.ReceiptSetReceiptItemTaskToNOK( return this.#receiptService.ReceiptSetReceiptItemTaskToNOK(
updateTask.taskId, updateTask.taskId,
); );

View File

@@ -8,7 +8,7 @@
@let taskItem = item(); @let taskItem = item();
@let taskActionType = type(); @let taskActionType = type();
@if (taskActionType === 'UNKNOWN') { @if (isTaskType(taskActionType, TaskActionTypes.UNKNOWN)) {
<div <div
data-what="task-list" data-what="task-list"
data-which="processing-comment" data-which="processing-comment"
@@ -51,14 +51,21 @@
</div> </div>
} }
@if (taskActionType === 'UNKNOWN' && !taskItem?.completed) { @if (
isTaskType(taskActionType, TaskActionTypes.UNKNOWN) && !taskItem?.completed
) {
<div class="task-unknown-actions"> <div class="task-unknown-actions">
<button <button
class="flex items-center" class="flex items-center"
type="button" type="button"
uiButton uiButton
color="secondary" color="secondary"
(click)="onActionClick({ type: taskActionType, updateTo: 'OK' })" (click)="
onActionClick({
type: taskActionType,
updateTo: TaskActionTypes.RETOURE_OK,
})
"
data-what="button" data-what="button"
data-which="resellable" data-which="resellable"
> >
@@ -69,7 +76,12 @@
type="button" type="button"
uiButton uiButton
color="secondary" color="secondary"
(click)="onActionClick({ type: taskActionType, updateTo: 'NOK' })" (click)="
onActionClick({
type: taskActionType,
updateTo: TaskActionTypes.RETOURE_NOK,
})
"
data-what="button" data-what="button"
data-which="damaged" data-which="damaged"
> >

View File

@@ -9,9 +9,11 @@ import {
} from '@angular/core'; } from '@angular/core';
import { isaActionCheck, isaActionPrinter } from '@isa/icons'; import { isaActionCheck, isaActionPrinter } from '@isa/icons';
import { import {
isTaskType,
Product, Product,
ReceiptItemTaskListItem, ReceiptItemTaskListItem,
TaskActionType, TaskActionType,
TaskActionTypes,
TaskActionTypeType, TaskActionTypeType,
} from '@isa/oms/data-access'; } from '@isa/oms/data-access';
import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info'; import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';
@@ -37,6 +39,8 @@ import { NgIconComponent, provideIcons } from '@ng-icons/core';
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
}) })
export class ReturnTaskListItemComponent { export class ReturnTaskListItemComponent {
readonly TaskActionTypes = TaskActionTypes;
readonly isTaskType = isTaskType;
appearance = input<'main' | 'review'>('main'); appearance = input<'main' | 'review'>('main');
item = input.required<ReceiptItemTaskListItem>(); item = input.required<ReceiptItemTaskListItem>();
action = output<TaskActionType>(); action = output<TaskActionType>();

View File

@@ -10,12 +10,13 @@ import {
} from '@angular/core'; } from '@angular/core';
import { ReturnTaskListItemComponent } from './return-task-list-item/return-task-list-item.component'; import { ReturnTaskListItemComponent } from './return-task-list-item/return-task-list-item.component';
import { import {
isTaskType,
PrintTolinoReturnReceiptService, PrintTolinoReturnReceiptService,
QueryTokenInput, QueryTokenInput,
ReceiptItemTaskListItem,
ReturnTaskListService, ReturnTaskListService,
ReturnTaskListStore, ReturnTaskListStore,
TaskActionType, TaskActionType,
TaskActionTypes,
TaskActionTypeType, TaskActionTypeType,
} from '@isa/oms/data-access'; } from '@isa/oms/data-access';
import { IconButtonComponent } from '@isa/ui/buttons'; import { IconButtonComponent } from '@isa/ui/buttons';
@@ -86,7 +87,9 @@ export class ReturnTaskListComponent {
const appearance = this.appearance(); const appearance = this.appearance();
if (processId) { if (processId) {
const filter: Record<string, unknown> = const filter: Record<string, unknown> =
appearance === 'review' ? { eob: true } : { completed: false }; appearance === 'review'
? { eob: true, tasktype: '!retoure_loyalty' }
: { completed: false, tasktype: '!retoure_loyalty' };
const queryToken: QueryTokenInput = { const queryToken: QueryTokenInput = {
filter, filter,
}; };
@@ -113,7 +116,7 @@ export class ReturnTaskListComponent {
); );
} }
if (action.type === 'UNKNOWN' && !!action.updateTo) { if (isTaskType(action.type, TaskActionTypes.UNKNOWN) && !!action.updateTo) {
return await this.updateTask({ return await this.updateTask({
taskId: action.taskId, taskId: action.taskId,
updateTo: action.updateTo, updateTo: action.updateTo,
@@ -149,7 +152,7 @@ export class ReturnTaskListComponent {
updateTo, updateTo,
}: { }: {
taskId: number; taskId: number;
updateTo: Exclude<TaskActionTypeType, 'UNKNOWN'>; updateTo: Exclude<TaskActionTypeType, 'UNKNOWN' | 'RETOURE_UNKNOWN'>;
}) { }) {
try { try {
const processId = this.processId(); const processId = this.processId();