mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1977: #5390 Reward Checkout Action Card - Collect Request
#5390 Reward Checkout Action Card - Collect Request
This commit is contained in:
committed by
Lorenz Hilpert
parent
bcb412e48d
commit
7376846894
@@ -37,6 +37,7 @@ export { Gender } from './models/gender';
|
||||
export { DateRangeDTO } from './models/date-range-dto';
|
||||
export { PaymentType } from './models/payment-type';
|
||||
export { PaymentStatus } from './models/payment-status';
|
||||
export { LoyaltyDTO } from './models/loyalty-dto';
|
||||
export { QueryTokenDTO } from './models/query-token-dto';
|
||||
export { ListResponseArgsOfOrderItemListItemDTO } from './models/list-response-args-of-order-item-list-item-dto';
|
||||
export { ResponseArgsOfIEnumerableOfOrderItemListItemDTO } from './models/response-args-of-ienumerable-of-order-item-list-item-dto';
|
||||
@@ -130,7 +131,6 @@ export { Price } from './models/price';
|
||||
export { ShippingTarget } from './models/shipping-target';
|
||||
export { EntityDTOBaseOfShopItemDTOAndIShopItem } from './models/entity-dtobase-of-shop-item-dtoand-ishop-item';
|
||||
export { CampaignDTO } from './models/campaign-dto';
|
||||
export { LoyaltyDTO } from './models/loyalty-dto';
|
||||
export { EntityDTOBaseOfOrderItemDTOAndIOrderItem } from './models/entity-dtobase-of-order-item-dtoand-iorder-item';
|
||||
export { EntityDTOContainerOfSupplierDTO } from './models/entity-dtocontainer-of-supplier-dto';
|
||||
export { SupplierDTO } from './models/supplier-dto';
|
||||
@@ -207,6 +207,8 @@ export { EntityDTOContainerOfOrderItemSubsetTransitionDTO } from './models/entit
|
||||
export { OrderItemSubsetTransitionDTO } from './models/order-item-subset-transition-dto';
|
||||
export { EntityDTOBaseOfOrderItemSubsetTransitionDTOAndIOrderItemStatusTransition } from './models/entity-dtobase-of-order-item-subset-transition-dtoand-iorder-item-status-transition';
|
||||
export { EntityDTOBaseOfOrderItemSubsetTaskDTOAndIOrderItemStatusTask } from './models/entity-dtobase-of-order-item-subset-task-dtoand-iorder-item-status-task';
|
||||
export { LoyaltyCollectValues } from './models/loyalty-collect-values';
|
||||
export { LoyaltyCollectType } from './models/loyalty-collect-type';
|
||||
export { ResponseArgsOfBoolean } from './models/response-args-of-boolean';
|
||||
export { ResponseArgsOfOrderItemDTO } from './models/response-args-of-order-item-dto';
|
||||
export { ResponseArgsOfIEnumerableOfOrderItemDTO } from './models/response-args-of-ienumerable-of-order-item-dto';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* tslint:disable */
|
||||
import { EntityDTOBaseOfDisplayOrderItemDTOAndIOrderItem } from './entity-dtobase-of-display-order-item-dtoand-iorder-item';
|
||||
import { LoyaltyDTO } from './loyalty-dto';
|
||||
import { DisplayOrderDTO } from './display-order-dto';
|
||||
import { PriceDTO } from './price-dto';
|
||||
import { ProductDTO } from './product-dto';
|
||||
@@ -23,6 +24,11 @@ export interface DisplayOrderItemDTO extends EntityDTOBaseOfDisplayOrderItemDTOA
|
||||
*/
|
||||
features?: {[key: string]: string};
|
||||
|
||||
/**
|
||||
* Loylty
|
||||
*/
|
||||
loyalty?: LoyaltyDTO;
|
||||
|
||||
/**
|
||||
* Bestellung
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/* tslint:disable */
|
||||
export type LoyaltyCollectType = 0 | 1 | 2;
|
||||
@@ -0,0 +1,18 @@
|
||||
/* tslint:disable */
|
||||
import { LoyaltyCollectType } from './loyalty-collect-type';
|
||||
|
||||
/**
|
||||
* Loyalty collect values
|
||||
*/
|
||||
export interface LoyaltyCollectValues {
|
||||
|
||||
/**
|
||||
* Collect Type
|
||||
*/
|
||||
collectType: LoyaltyCollectType;
|
||||
|
||||
/**
|
||||
* Quantity (optional, default null)
|
||||
*/
|
||||
quantity?: number;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { EnvironmentChannel } from './environment-channel';
|
||||
import { CRUDA } from './cruda';
|
||||
import { DateRangeDTO } from './date-range-dto';
|
||||
import { Gender } from './gender';
|
||||
import { LoyaltyDTO } from './loyalty-dto';
|
||||
import { OrderType } from './order-type';
|
||||
import { PaymentStatus } from './payment-status';
|
||||
import { PaymentType } from './payment-type';
|
||||
@@ -102,6 +103,11 @@ export interface OrderItemListItemDTO {
|
||||
*/
|
||||
lastName?: string;
|
||||
|
||||
/**
|
||||
* Loylty
|
||||
*/
|
||||
loyalty?: LoyaltyDTO;
|
||||
|
||||
/**
|
||||
* Bestellfiliale
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
/* tslint:disable */
|
||||
import { EntityReferenceDTO } from './entity-reference-dto';
|
||||
import { KeyValueDTOOfStringAndString } from './key-value-dtoof-string-and-string';
|
||||
import { CRUDA } from './cruda';
|
||||
import { PriceDTO } from './price-dto';
|
||||
import { LoyaltyDTO } from './loyalty-dto';
|
||||
import { ProductDTO } from './product-dto';
|
||||
import { PromotionDTO } from './promotion-dto';
|
||||
import { QuantityDTO } from './quantity-dto';
|
||||
import { ReceiptListItemDTO } from './receipt-list-item-dto';
|
||||
export interface ReceiptItemListItemDTO extends EntityReferenceDTO{
|
||||
|
||||
/**
|
||||
* Mögliche Aktionen
|
||||
*/
|
||||
actions?: Array<KeyValueDTOOfStringAndString>;
|
||||
buyerComment?: string;
|
||||
|
||||
/**
|
||||
@@ -19,6 +26,11 @@ export interface ReceiptItemListItemDTO extends EntityReferenceDTO{
|
||||
*/
|
||||
discountedPrice?: PriceDTO;
|
||||
|
||||
/**
|
||||
* Zusätzliche Markierungen
|
||||
*/
|
||||
features?: {[key: string]: string};
|
||||
|
||||
/**
|
||||
* PK
|
||||
*/
|
||||
@@ -35,6 +47,11 @@ export interface ReceiptItemListItemDTO extends EntityReferenceDTO{
|
||||
*/
|
||||
lineNumber?: number;
|
||||
|
||||
/**
|
||||
* Loyalty
|
||||
*/
|
||||
loyalty?: LoyaltyDTO;
|
||||
|
||||
/**
|
||||
* Bestellnummer
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,8 @@ import { ResponseArgsOfQuerySettingsDTO } from '../models/response-args-of-query
|
||||
import { ResponseArgsOfIEnumerableOfAutocompleteDTO } from '../models/response-args-of-ienumerable-of-autocomplete-dto';
|
||||
import { AutocompleteTokenDTO } from '../models/autocomplete-token-dto';
|
||||
import { ListResponseArgsOfDBHOrderItemListItemDTO } from '../models/list-response-args-of-dbhorder-item-list-item-dto';
|
||||
import { ResponseArgsOfIEnumerableOfDBHOrderItemListItemDTO } from '../models/response-args-of-ienumerable-of-dbhorder-item-list-item-dto';
|
||||
import { LoyaltyCollectValues } from '../models/loyalty-collect-values';
|
||||
import { ListResponseArgsOfOrderItemListItemDTO } from '../models/list-response-args-of-order-item-list-item-dto';
|
||||
import { ResponseArgsOfIEnumerableOfOrderItemDTO } from '../models/response-args-of-ienumerable-of-order-item-dto';
|
||||
import { OrderItemDTO } from '../models/order-item-dto';
|
||||
@@ -54,6 +56,7 @@ class OrderService extends __BaseService {
|
||||
static readonly OrderKundenbestellungenSettingsPath = '/kundenbestellungen/s/settings';
|
||||
static readonly OrderKundenbestellungenAutocompletePath = '/kundenbestellungen/s/complete';
|
||||
static readonly OrderKundenbestellungenPath = '/kundenbestellungen/s';
|
||||
static readonly OrderLoyaltyCollectPath = '/order/{orderId}/orderitem/{orderItemId}/orderitemsubset/{orderItemSubsetId}/loyaltycollect';
|
||||
static readonly OrderQueryOrderItemPath = '/order/item/s';
|
||||
static readonly OrderQueryOrderItemAutocompletePath = '/order/item/s/complete';
|
||||
static readonly OrderGetOrderItemPath = '/order/orderitem/{orderItemId}';
|
||||
@@ -636,6 +639,63 @@ class OrderService extends __BaseService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ausgabe order Storno von Prämienbestellposten
|
||||
* Falls die Menge/Stückzahl kleiner der ursprünglichen Menge/Stückzahl ist, wird eine neue Bestellpostenteilmenge erzeugt.
|
||||
* @param params The `OrderService.OrderLoyaltyCollectParams` containing the following parameters:
|
||||
*
|
||||
* - `orderItemSubsetId`: PK Bestellpostenteilmenge
|
||||
*
|
||||
* - `orderItemId`: PK Bestellposten
|
||||
*
|
||||
* - `orderId`: PK Bestellung
|
||||
*
|
||||
* - `data`: Daten zur Änderung des Bearbeitungsstatus
|
||||
*/
|
||||
OrderLoyaltyCollectResponse(params: OrderService.OrderLoyaltyCollectParams): __Observable<__StrictHttpResponse<ResponseArgsOfIEnumerableOfDBHOrderItemListItemDTO>> {
|
||||
let __params = this.newParams();
|
||||
let __headers = new HttpHeaders();
|
||||
let __body: any = null;
|
||||
|
||||
|
||||
|
||||
__body = params.data;
|
||||
let req = new HttpRequest<any>(
|
||||
'POST',
|
||||
this.rootUrl + `/order/${encodeURIComponent(String(params.orderId))}/orderitem/${encodeURIComponent(String(params.orderItemId))}/orderitemsubset/${encodeURIComponent(String(params.orderItemSubsetId))}/loyaltycollect`,
|
||||
__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<ResponseArgsOfIEnumerableOfDBHOrderItemListItemDTO>;
|
||||
})
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Ausgabe order Storno von Prämienbestellposten
|
||||
* Falls die Menge/Stückzahl kleiner der ursprünglichen Menge/Stückzahl ist, wird eine neue Bestellpostenteilmenge erzeugt.
|
||||
* @param params The `OrderService.OrderLoyaltyCollectParams` containing the following parameters:
|
||||
*
|
||||
* - `orderItemSubsetId`: PK Bestellpostenteilmenge
|
||||
*
|
||||
* - `orderItemId`: PK Bestellposten
|
||||
*
|
||||
* - `orderId`: PK Bestellung
|
||||
*
|
||||
* - `data`: Daten zur Änderung des Bearbeitungsstatus
|
||||
*/
|
||||
OrderLoyaltyCollect(params: OrderService.OrderLoyaltyCollectParams): __Observable<ResponseArgsOfIEnumerableOfDBHOrderItemListItemDTO> {
|
||||
return this.OrderLoyaltyCollectResponse(params).pipe(
|
||||
__map(_r => _r.body as ResponseArgsOfIEnumerableOfDBHOrderItemListItemDTO)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Suche nach Bestellposten
|
||||
* @param queryToken Suchkriterien
|
||||
@@ -1671,6 +1731,32 @@ module OrderService {
|
||||
buyerNumber?: null | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for OrderLoyaltyCollect
|
||||
*/
|
||||
export interface OrderLoyaltyCollectParams {
|
||||
|
||||
/**
|
||||
* PK Bestellpostenteilmenge
|
||||
*/
|
||||
orderItemSubsetId: number;
|
||||
|
||||
/**
|
||||
* PK Bestellposten
|
||||
*/
|
||||
orderItemId: number;
|
||||
|
||||
/**
|
||||
* PK Bestellung
|
||||
*/
|
||||
orderId: number;
|
||||
|
||||
/**
|
||||
* Daten zur Änderung des Bearbeitungsstatus
|
||||
*/
|
||||
data: LoyaltyCollectValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for OrderUpdateOrderItem
|
||||
*/
|
||||
|
||||
@@ -236,6 +236,7 @@ class ReceiptService extends __BaseService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Aufgabe auf erledigt setzen
|
||||
* @param taskId undefined
|
||||
*/
|
||||
ReceiptReceiptItemTaskCompletedResponse(taskId: number): __Observable<__StrictHttpResponse<ResponseArgsOfReceiptItemTaskListItemDTO>> {
|
||||
@@ -261,6 +262,7 @@ class ReceiptService extends __BaseService {
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Aufgabe auf erledigt setzen
|
||||
* @param taskId undefined
|
||||
*/
|
||||
ReceiptReceiptItemTaskCompleted(taskId: number): __Observable<ResponseArgsOfReceiptItemTaskListItemDTO> {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.confirmation-list-item-done {
|
||||
@apply bg-transparent p-0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<div
|
||||
class="w-[24.5rem] h-full p-4 flex flex-col gap-4 rounded-lg bg-isa-secondary-100"
|
||||
[class.confirmation-list-item-done]="item().status !== 1"
|
||||
>
|
||||
@if (!isComplete()) {
|
||||
<div
|
||||
data-what="confirmation-message"
|
||||
data-which="confirmation-comment"
|
||||
class="isa-text-body-2-bold"
|
||||
>
|
||||
Bitte buchen Sie die Prämie aus dem Abholfach aus oder wählen Sie eine
|
||||
andere Aktion.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<ui-dropdown
|
||||
class="h-8 border-none pl-0 hover:bg-transparent"
|
||||
[value]="selectedAction()"
|
||||
(valueChange)="setDropdownAction($event)"
|
||||
>
|
||||
<ui-dropdown-option [value]="LoyaltyCollectType.Collect"
|
||||
>Prämie ausbuchen</ui-dropdown-option
|
||||
>
|
||||
<ui-dropdown-option [value]="LoyaltyCollectType.OutOfStock"
|
||||
>Nicht gefunden</ui-dropdown-option
|
||||
>
|
||||
<ui-dropdown-option [value]="LoyaltyCollectType.Cancel"
|
||||
>Stornieren</ui-dropdown-option
|
||||
>
|
||||
</ui-dropdown>
|
||||
|
||||
<button
|
||||
class="flex items-center gap-2 self-end"
|
||||
type="button"
|
||||
uiButton
|
||||
color="primary"
|
||||
size="small"
|
||||
(click)="onCollect()"
|
||||
data-what="button"
|
||||
data-which="complete"
|
||||
>
|
||||
<ng-icon name="isaActionCheck" uiButtonIcon></ng-icon>
|
||||
Abschließen
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<div
|
||||
data-what="done-message"
|
||||
data-which="done-comment"
|
||||
class="isa-text-body-2-bold"
|
||||
>
|
||||
Artikel wurde Storniert und Lesepunkte gut geschrieben.
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="flex items-center gap-2 self-end text-isa-accent-green isa-text-body-2-bold"
|
||||
>
|
||||
<ng-icon name="isaActionCheck"></ng-icon>
|
||||
Abgeschlossen
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,111 @@
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||
import { DisplayOrderItem } from '@isa/oms/data-access';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
input,
|
||||
inject,
|
||||
computed,
|
||||
signal,
|
||||
effect,
|
||||
} from '@angular/core';
|
||||
import { isaActionCheck } from '@isa/icons';
|
||||
import {
|
||||
DisplayOrderItem,
|
||||
OrderRewardCollectFacade,
|
||||
LoyaltyCollectType,
|
||||
OrderItemSubsetResource,
|
||||
getProcessingStatusCompleted,
|
||||
} from '@isa/oms/data-access';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { provideIcons } from '@ng-icons/core';
|
||||
import { OrderConfiramtionStore } from '../../../reward-order-confirmation.store';
|
||||
import {
|
||||
DropdownButtonComponent,
|
||||
DropdownOptionComponent,
|
||||
} from '@isa/ui/input-controls';
|
||||
|
||||
@Component({
|
||||
selector: 'checkout-confirmation-list-item-action-card',
|
||||
templateUrl: './confirmation-list-item-action-card.component.html',
|
||||
styleUrls: ['./confirmation-list-item-action-card.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [],
|
||||
imports: [
|
||||
NgIcon,
|
||||
ButtonComponent,
|
||||
DropdownButtonComponent,
|
||||
DropdownOptionComponent,
|
||||
],
|
||||
providers: [provideIcons({ isaActionCheck }), OrderItemSubsetResource],
|
||||
})
|
||||
export class ConfirmationListItemActionCardComponent {
|
||||
LoyaltyCollectType = LoyaltyCollectType;
|
||||
#orderRewardCollectFacade = inject(OrderRewardCollectFacade);
|
||||
#store = inject(OrderConfiramtionStore);
|
||||
#orderItemSubsetResource = inject(OrderItemSubsetResource);
|
||||
|
||||
item = input.required<DisplayOrderItem>();
|
||||
|
||||
orders = this.#store.orders;
|
||||
|
||||
getOrderIdBasedOnItem = computed(() => {
|
||||
const item = this.item();
|
||||
const orders = this.orders();
|
||||
if (!orders) {
|
||||
return undefined;
|
||||
}
|
||||
const order = orders.find((order) =>
|
||||
order.items?.some((orderItem) => orderItem.id === item.id),
|
||||
);
|
||||
return order?.id;
|
||||
});
|
||||
|
||||
orderItemSubsets = this.#orderItemSubsetResource.orderItemSubsets;
|
||||
selectedAction = signal<LoyaltyCollectType>(LoyaltyCollectType.Collect);
|
||||
isComplete = computed(() => {
|
||||
const subsets = this.orderItemSubsets();
|
||||
const statuses = subsets?.map((subset) => subset.processingStatus);
|
||||
return getProcessingStatusCompleted(statuses);
|
||||
});
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const item = this.item();
|
||||
const orderItemSubsetIds = item.subsetItems
|
||||
?.map((subset) => subset.id)
|
||||
?.filter((id): id is number => id !== undefined);
|
||||
|
||||
if (orderItemSubsetIds?.length) {
|
||||
this.#orderItemSubsetResource.loadOrderItemSubsets(orderItemSubsetIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setDropdownAction(value: LoyaltyCollectType) {
|
||||
this.selectedAction.set(value);
|
||||
}
|
||||
|
||||
async onCollect() {
|
||||
const item = this.item();
|
||||
const orderId = this.getOrderIdBasedOnItem();
|
||||
const orderItemId = item.id;
|
||||
const collectType = this.selectedAction();
|
||||
|
||||
if (orderId && orderItemId) {
|
||||
for (const subsetItem of item.subsetItems ?? []) {
|
||||
const orderItemSubsetId = subsetItem.id;
|
||||
const quantity = subsetItem.quantity;
|
||||
|
||||
if (orderItemSubsetId && !!quantity) {
|
||||
await this.#orderRewardCollectFacade.collect({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
collectType,
|
||||
quantity,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.#orderItemSubsetResource.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
:host {
|
||||
@apply flex w-full items-start gap-6;
|
||||
}
|
||||
:host {
|
||||
@apply flex w-full items-start gap-6;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import z from 'zod';
|
||||
|
||||
export const DateRangeSchema = z.object({
|
||||
start: z.string().optional(),
|
||||
end: z.string().optional(),
|
||||
});
|
||||
|
||||
export type DateRange = z.infer<typeof DateRangeSchema>;
|
||||
@@ -2,6 +2,7 @@ export * from './address.schema';
|
||||
export * from './addressee-with-reference.schema';
|
||||
export * from './buyer-type.schema';
|
||||
export * from './communication-details.schema';
|
||||
export * from './date-range.schema';
|
||||
export * from './entity-container.schema';
|
||||
export * from './entity-reference-container.schema';
|
||||
export * from './entity-reference.schema';
|
||||
|
||||
@@ -19,8 +19,9 @@ export * from './lib/errors';
|
||||
export * from './lib/questions';
|
||||
export * from './lib/models';
|
||||
export * from './lib/facades';
|
||||
export * from './lib/helpers/return-process';
|
||||
export * from './lib/helpers';
|
||||
export * from './lib/schemas';
|
||||
export * from './lib/services';
|
||||
export * from './lib/operators';
|
||||
export * from './lib/stores';
|
||||
export * from './lib/resources';
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { OrderCreationFacade } from './order-creation.facade';
|
||||
export { OrderRewardCollectFacade } from './order-reward-collect.facade';
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
|
||||
import { OrderRewardCollectFacade } from './order-reward-collect.facade';
|
||||
import { OrderRewardCollectService } from '../services';
|
||||
import { DBHOrderItemListItemDTO } from '@generated/swagger/oms-api';
|
||||
|
||||
describe('OrderRewardCollectFacade', () => {
|
||||
let spectator: SpectatorService<OrderRewardCollectFacade>;
|
||||
const createService = createServiceFactory({
|
||||
service: OrderRewardCollectFacade,
|
||||
mocks: [OrderRewardCollectService],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createService();
|
||||
});
|
||||
|
||||
describe('collect', () => {
|
||||
it('should delegate to OrderRewardCollectService.collect', async () => {
|
||||
// Arrange
|
||||
const mockParams = {
|
||||
orderId: 123,
|
||||
orderItemId: 456,
|
||||
orderItemSubsetId: 789,
|
||||
collectType: 1 as const, // Valid LoyaltyCollectType.Collect
|
||||
quantity: 2,
|
||||
};
|
||||
const mockResult: DBHOrderItemListItemDTO[] = [
|
||||
{ orderItemType: 1 } as DBHOrderItemListItemDTO,
|
||||
];
|
||||
const rewardItemService = spectator.inject(OrderRewardCollectService);
|
||||
(rewardItemService.collect as jest.Mock).mockResolvedValue(mockResult);
|
||||
|
||||
// Act
|
||||
const result = await spectator.service.collect(mockParams);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockResult);
|
||||
expect(rewardItemService.collect).toHaveBeenCalledWith({
|
||||
orderId: mockParams.orderId,
|
||||
orderItemId: mockParams.orderItemId,
|
||||
orderItemSubsetId: mockParams.orderItemSubsetId,
|
||||
collectType: mockParams.collectType,
|
||||
quantity: mockParams.quantity,
|
||||
});
|
||||
expect(rewardItemService.collect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should propagate errors from OrderRewardCollectService', async () => {
|
||||
// Arrange
|
||||
const mockParams = {
|
||||
orderId: 123,
|
||||
orderItemId: 456,
|
||||
orderItemSubsetId: 789,
|
||||
collectType: 1 as const, // Valid LoyaltyCollectType.Collect
|
||||
quantity: 2,
|
||||
};
|
||||
const errorMessage = 'Failed to collect reward item';
|
||||
const rewardItemService = spectator.inject(OrderRewardCollectService);
|
||||
(rewardItemService.collect as jest.Mock).mockRejectedValue(
|
||||
new Error(errorMessage),
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
await expect(spectator.service.collect(mockParams)).rejects.toThrow(
|
||||
errorMessage,
|
||||
);
|
||||
expect(rewardItemService.collect).toHaveBeenCalledWith({
|
||||
orderId: mockParams.orderId,
|
||||
orderItemId: mockParams.orderItemId,
|
||||
orderItemSubsetId: mockParams.orderItemSubsetId,
|
||||
collectType: mockParams.collectType,
|
||||
quantity: mockParams.quantity,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOrderItemSubset', () => {
|
||||
it('should delegate to OrderRewardCollectService.fetchOrderItemSubset', async () => {
|
||||
// Arrange
|
||||
const mockParams = {
|
||||
orderItemSubsetId: 789,
|
||||
};
|
||||
const mockResult = {
|
||||
id: 789,
|
||||
quantity: 2,
|
||||
name: 'Test Subset',
|
||||
};
|
||||
const rewardItemService = spectator.inject(OrderRewardCollectService);
|
||||
(rewardItemService.fetchOrderItemSubset as jest.Mock).mockResolvedValue(
|
||||
mockResult,
|
||||
);
|
||||
|
||||
// Act
|
||||
const result = await spectator.service.getOrderItemSubset(mockParams);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockResult);
|
||||
expect(rewardItemService.fetchOrderItemSubset).toHaveBeenCalledWith(
|
||||
mockParams,
|
||||
undefined,
|
||||
);
|
||||
expect(rewardItemService.fetchOrderItemSubset).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { OrderRewardCollectService } from '../services';
|
||||
import {
|
||||
FetchOrderItemSubsetSchemaInput,
|
||||
OrderLoyaltyCollectInput,
|
||||
} from '../schemas';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OrderRewardCollectFacade {
|
||||
#orderRewardCollectService = inject(OrderRewardCollectService);
|
||||
|
||||
async collect(params: OrderLoyaltyCollectInput) {
|
||||
return this.#orderRewardCollectService.collect(params);
|
||||
}
|
||||
|
||||
async getOrderItemSubset(
|
||||
params: FetchOrderItemSubsetSchemaInput,
|
||||
abortSignal?: AbortSignal,
|
||||
) {
|
||||
return this.#orderRewardCollectService.fetchOrderItemSubset(
|
||||
params,
|
||||
abortSignal,
|
||||
);
|
||||
}
|
||||
}
|
||||
2
libs/oms/data-access/src/lib/helpers/index.ts
Normal file
2
libs/oms/data-access/src/lib/helpers/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './return-process';
|
||||
export * from './reward';
|
||||
@@ -0,0 +1,97 @@
|
||||
import { OrderItemProcessingStatusValue } from '../../schemas';
|
||||
import { getProcessingStatusCompleted } from './get-processing-status-completed.helper';
|
||||
|
||||
describe('getProcessingStatusCompleted', () => {
|
||||
it('should return true when all statuses are different from Bestellt (16)', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.Versendet, // 64
|
||||
OrderItemProcessingStatusValue.Eingetroffen, // 128
|
||||
OrderItemProcessingStatusValue.Abgeholt, // 256
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when statuses include various completed states', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.Zugestellt, // 4194304
|
||||
OrderItemProcessingStatusValue.Abgeholt, // 256
|
||||
OrderItemProcessingStatusValue.Versendet, // 64
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when at least one status is Bestellt (16)', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.Versendet, // 64
|
||||
OrderItemProcessingStatusValue.Bestellt, // 16
|
||||
OrderItemProcessingStatusValue.Abgeholt, // 256
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when all statuses are Bestellt (16)', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.Bestellt, // 16
|
||||
OrderItemProcessingStatusValue.Bestellt, // 16
|
||||
OrderItemProcessingStatusValue.Bestellt, // 16
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when array is empty', () => {
|
||||
// Arrange
|
||||
const statuses: number[] = [];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when statuses is undefined', () => {
|
||||
// Arrange
|
||||
const statuses = undefined;
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true with single status different from Bestellt', () => {
|
||||
// Arrange
|
||||
const statuses = [OrderItemProcessingStatusValue.Abgeholt]; // 256
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { OrderItemProcessingStatusValue } from '../../schemas';
|
||||
|
||||
/**
|
||||
* Checks if all processing statuses are completed (not in "Bestellt" state).
|
||||
* Returns true if all statuses are different from "Bestellt" (16).
|
||||
* Returns false if any status is still "Bestellt" (16) or if the array is empty/undefined.
|
||||
*/
|
||||
export const getProcessingStatusCompleted = (
|
||||
statuses: number[] | undefined,
|
||||
): boolean => {
|
||||
if (!statuses || statuses.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return statuses.every(
|
||||
(status) => status !== OrderItemProcessingStatusValue.Bestellt,
|
||||
);
|
||||
};
|
||||
1
libs/oms/data-access/src/lib/helpers/reward/index.ts
Normal file
1
libs/oms/data-access/src/lib/helpers/reward/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './get-processing-status-completed.helper';
|
||||
@@ -6,3 +6,4 @@ export * from './questions';
|
||||
export * from './schemas';
|
||||
export * from './services';
|
||||
export * from './stores';
|
||||
export * from './resources';
|
||||
|
||||
1
libs/oms/data-access/src/lib/resources/index.ts
Normal file
1
libs/oms/data-access/src/lib/resources/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './order-item-subset.resource';
|
||||
@@ -0,0 +1,51 @@
|
||||
import { computed, inject, Injectable, resource, signal } from '@angular/core';
|
||||
import { DisplayOrderItemSubset } from '../schemas';
|
||||
import { OrderRewardCollectFacade } from '../facades';
|
||||
|
||||
@Injectable()
|
||||
export class OrderItemSubsetResource {
|
||||
#orderRewardCollectFacade = inject(OrderRewardCollectFacade);
|
||||
|
||||
#orderItemSubsetIds = signal<number[] | undefined>(undefined);
|
||||
|
||||
#orderItemSubsetsResource = resource({
|
||||
params: computed(() => ({
|
||||
orderItemSubsetIds: this.#orderItemSubsetIds(),
|
||||
})),
|
||||
loader: async ({
|
||||
params,
|
||||
abortSignal,
|
||||
}): Promise<DisplayOrderItemSubset[]> => {
|
||||
if (!params?.orderItemSubsetIds?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const results: DisplayOrderItemSubset[] = [];
|
||||
for (const id of params.orderItemSubsetIds) {
|
||||
const result = await this.#orderRewardCollectFacade.getOrderItemSubset(
|
||||
{ orderItemSubsetId: id },
|
||||
abortSignal,
|
||||
);
|
||||
if (result) {
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
defaultValue: [],
|
||||
});
|
||||
|
||||
readonly orderItemSubsets = this.#orderItemSubsetsResource.value.asReadonly();
|
||||
readonly loading = this.#orderItemSubsetsResource.isLoading;
|
||||
readonly error = computed(
|
||||
() => this.#orderItemSubsetsResource.error()?.message ?? null,
|
||||
);
|
||||
|
||||
loadOrderItemSubsets(orderItemSubsetIds: number[] | undefined) {
|
||||
this.#orderItemSubsetIds.set(orderItemSubsetIds);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.#orderItemSubsetsResource.reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { EntitySchema, DateRangeSchema } from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
import { OrderItemProcessingStatusValueSchema } from './order-item-processing-status-value.schema';
|
||||
|
||||
// Forward declaration for circular reference
|
||||
export const DisplayOrderItemSubsetSchema = z
|
||||
.object({
|
||||
compartmentCode: z.string().describe('Compartment code').optional(),
|
||||
compartmentInfo: z.string().describe('Compartment information').optional(),
|
||||
compartmentStart: z.string().describe('Compartment start').optional(),
|
||||
compartmentStop: z.string().describe('Compartment stop').optional(),
|
||||
description: z.string().describe('Description text').optional(),
|
||||
estimatedDelivery: DateRangeSchema.describe(
|
||||
'Estimated delivery date range',
|
||||
).optional(),
|
||||
estimatedShippingDate: z
|
||||
.string()
|
||||
.describe('Estimated shipping date')
|
||||
.optional(),
|
||||
orderItem: z
|
||||
.lazy(() => z.any())
|
||||
.describe('Order item')
|
||||
.optional(), // Circular reference to DisplayOrderItem
|
||||
orderItemSubsetNumber: z
|
||||
.string()
|
||||
.describe('Order item subset number')
|
||||
.optional(),
|
||||
preferredPickUpDate: z
|
||||
.string()
|
||||
.describe('Preferred pick up date')
|
||||
.optional(),
|
||||
processingStatus:
|
||||
OrderItemProcessingStatusValueSchema.describe('Processing status'),
|
||||
processingStatusDate: z
|
||||
.string()
|
||||
.describe('Processing status date')
|
||||
.optional(),
|
||||
quantity: z.number().describe('Quantity').optional(),
|
||||
specialComment: z.string().describe('Special comment').optional(),
|
||||
ssc: z.string().describe('SSC code').optional(),
|
||||
sscText: z.string().describe('SSC text').optional(),
|
||||
supplierLabel: z.string().describe('Supplier label').optional(),
|
||||
supplierName: z.string().describe('Supplier name').optional(),
|
||||
supplyChannel: z.string().describe('Supply channel').optional(),
|
||||
trackingNumber: z.string().describe('Tracking number').optional(),
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
|
||||
export type DisplayOrderItemSubset = z.infer<
|
||||
typeof DisplayOrderItemSubsetSchema
|
||||
>;
|
||||
@@ -1,32 +1,33 @@
|
||||
import { EntitySchema, QuantityUnitTypeSchema } from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
import { PriceSchema } from './price.schema';
|
||||
import { ProductSchema } from './product.schema';
|
||||
import { PromotionSchema } from './promotion.schema';
|
||||
|
||||
// Forward declaration for circular reference
|
||||
export const DisplayOrderItemSchema = z
|
||||
.object({
|
||||
buyerComment: z.string().describe('Buyer comment').optional(),
|
||||
description: z.string().describe('Description text').optional(),
|
||||
features: z.record(z.string().describe('Features'), z.string()).optional(),
|
||||
order: z
|
||||
.lazy(() => z.any())
|
||||
.describe('Order')
|
||||
.optional(), // Circular reference to DisplayOrder
|
||||
orderDate: z.string().describe('Order date').optional(),
|
||||
orderItemNumber: z.string().describe('OrderItem number').optional(),
|
||||
price: PriceSchema.describe('Price information').optional(),
|
||||
product: ProductSchema.describe('Product').optional(),
|
||||
promotion: PromotionSchema.describe('Promotion information').optional(),
|
||||
quantity: z.number().describe('Quantity').optional(),
|
||||
quantityUnit: z.string().describe('Quantity unit').optional(),
|
||||
quantityUnitType: QuantityUnitTypeSchema.describe('QuantityUnit type'),
|
||||
subsetItems: z
|
||||
.array(z.lazy(() => z.any()))
|
||||
.describe('Subset items')
|
||||
.optional(), // Circular reference to DisplayOrderItemSubset
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
|
||||
export type DisplayOrderItem = z.infer<typeof DisplayOrderItemSchema>;
|
||||
import { EntitySchema, QuantityUnitTypeSchema } from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
import { PriceSchema } from './price.schema';
|
||||
import { ProductSchema } from './product.schema';
|
||||
import { PromotionSchema } from './promotion.schema';
|
||||
import { DisplayOrderItemSubsetSchema } from './display-order-item-subset.schema';
|
||||
|
||||
// Forward declaration for circular reference
|
||||
export const DisplayOrderItemSchema = z
|
||||
.object({
|
||||
buyerComment: z.string().describe('Buyer comment').optional(),
|
||||
description: z.string().describe('Description text').optional(),
|
||||
features: z.record(z.string().describe('Features'), z.string()).optional(),
|
||||
order: z
|
||||
.lazy(() => z.any())
|
||||
.describe('Order')
|
||||
.optional(), // Circular reference to DisplayOrder
|
||||
orderDate: z.string().describe('Order date').optional(),
|
||||
orderItemNumber: z.string().describe('OrderItem number').optional(),
|
||||
price: PriceSchema.describe('Price information').optional(),
|
||||
product: ProductSchema.describe('Product').optional(),
|
||||
promotion: PromotionSchema.describe('Promotion information').optional(),
|
||||
quantity: z.number().describe('Quantity').optional(),
|
||||
quantityUnit: z.string().describe('Quantity unit').optional(),
|
||||
quantityUnitType: QuantityUnitTypeSchema.describe('QuantityUnit type'),
|
||||
subsetItems: z
|
||||
.array(DisplayOrderItemSubsetSchema)
|
||||
.describe('Subset items')
|
||||
.optional(),
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
|
||||
export type DisplayOrderItem = z.infer<typeof DisplayOrderItemSchema>;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import z from 'zod';
|
||||
|
||||
export const FetchOrderItemSubsetSchema = z.object({
|
||||
orderItemSubsetId: z.number(),
|
||||
});
|
||||
|
||||
export type FetchOrderItemSubsetSchemaInput = z.infer<
|
||||
typeof FetchOrderItemSubsetSchema
|
||||
>;
|
||||
@@ -17,3 +17,8 @@ export * from './return-receipt-values.schema';
|
||||
export * from './shipping-type.schema';
|
||||
export * from './terms-of-delivery.schema';
|
||||
export * from './type-of-delivery.schema';
|
||||
export * from './loyalty-collect-type.schema';
|
||||
export * from './order-loyalty-collect.schema';
|
||||
export * from './fetch-order-item-subset.schema';
|
||||
export * from './display-order-item-subset.schema';
|
||||
export * from './order-item-processing-status-value.schema';
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import z from 'zod';
|
||||
|
||||
export const LoyaltyCollectType = {
|
||||
Collect: 0,
|
||||
OutOfStock: 1,
|
||||
Cancel: 2,
|
||||
} as const;
|
||||
|
||||
const ALL_FLAGS = Object.values(LoyaltyCollectType).reduce<number>(
|
||||
(a, b) => a | b,
|
||||
0,
|
||||
);
|
||||
|
||||
export const LoyaltyCollectTypeSchema = z
|
||||
.nativeEnum(LoyaltyCollectType)
|
||||
.refine((val) => (val & ALL_FLAGS) === val, {
|
||||
message: 'Invalid loyalty collect type',
|
||||
})
|
||||
.describe('Loyalty collect type');
|
||||
|
||||
export type LoyaltyCollectType = z.infer<typeof LoyaltyCollectTypeSchema>;
|
||||
@@ -0,0 +1,51 @@
|
||||
import z from 'zod';
|
||||
|
||||
export const OrderItemProcessingStatusValue = {
|
||||
NotSet: 0,
|
||||
NeuAngelegt1: 1,
|
||||
NeuAngelegt2: 2,
|
||||
NeuÜbernommen: 4,
|
||||
Geparkt: 8,
|
||||
Bestellt: 16,
|
||||
VorbereitungVersand: 32,
|
||||
Versendet: 64,
|
||||
Eingetroffen: 128,
|
||||
Abgeholt: 256,
|
||||
StorniertKunde: 512,
|
||||
Storniert: 1024,
|
||||
StorniertLieferant: 2048,
|
||||
NichtLieferbar: 4096,
|
||||
Nachbestellt: 8192,
|
||||
Zurückgegeben: 16384,
|
||||
ZumDownloadVerfügbar: 32768,
|
||||
Downloaded: 65536,
|
||||
NichtAbgeholt: 131072,
|
||||
AnsLagerNichtAbgeholt: 262144,
|
||||
Angefragt: 524288,
|
||||
WeitergeleitetIntern: 1048576,
|
||||
Überfällig: 2097152,
|
||||
Zugestellt: 4194304,
|
||||
LieferantErmittelt: 8388608,
|
||||
DerzeitNichtLieferbar: 16777216,
|
||||
Reserviert: 33554432,
|
||||
Zusammengestellt: 67108864,
|
||||
Verpackt: 134217728,
|
||||
Lieferschein: 268435456,
|
||||
} as const;
|
||||
|
||||
export const OrderItemProcessingStatusValueSchema = z.number().refine(
|
||||
(val) => {
|
||||
// Validate that the value is a valid combination of flags
|
||||
const allFlags = Object.values(
|
||||
OrderItemProcessingStatusValue,
|
||||
).reduce<number>((acc, flag) => acc | flag, 0);
|
||||
return (val & allFlags) === val;
|
||||
},
|
||||
{
|
||||
message: 'Invalid order item processing status value',
|
||||
},
|
||||
);
|
||||
|
||||
export type OrderItemProcessingStatusValue = z.infer<
|
||||
typeof OrderItemProcessingStatusValueSchema
|
||||
>;
|
||||
@@ -0,0 +1,14 @@
|
||||
import z from 'zod';
|
||||
import { LoyaltyCollectTypeSchema } from './loyalty-collect-type.schema';
|
||||
|
||||
export const OrderLoyaltyCollectSchema = z.object({
|
||||
orderId: z.number(),
|
||||
orderItemId: z.number(),
|
||||
orderItemSubsetId: z.number(),
|
||||
collectType: LoyaltyCollectTypeSchema,
|
||||
quantity: z.number().int().nonnegative(),
|
||||
});
|
||||
|
||||
export type OrderLoyaltyCollectInput = z.infer<
|
||||
typeof OrderLoyaltyCollectSchema
|
||||
>;
|
||||
@@ -8,3 +8,4 @@ export * from './return-details.service';
|
||||
export * from './return-process.service';
|
||||
export * from './return-search.service';
|
||||
export * from './return-task-list.service';
|
||||
export * from './order-reward-collect.service';
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
|
||||
import { OrderRewardCollectService } from './order-reward-collect.service';
|
||||
import {
|
||||
OrderService,
|
||||
DBHOrderItemListItemDTO,
|
||||
} from '@generated/swagger/oms-api';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('OrderRewardCollectService', () => {
|
||||
let spectator: SpectatorService<OrderRewardCollectService>;
|
||||
const createService = createServiceFactory({
|
||||
service: OrderRewardCollectService,
|
||||
mocks: [OrderService],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createService();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(spectator.service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('collect', () => {
|
||||
it('should collect reward item successfully', async () => {
|
||||
// Arrange
|
||||
const mockParams = {
|
||||
orderId: 123,
|
||||
orderItemId: 456,
|
||||
orderItemSubsetId: 789,
|
||||
collectType: 1 as const, // Valid LoyaltyCollectType.Collect
|
||||
quantity: 2,
|
||||
};
|
||||
const mockResult: DBHOrderItemListItemDTO[] = [
|
||||
{ orderItemType: 1 } as DBHOrderItemListItemDTO,
|
||||
];
|
||||
const mockResponse = { result: mockResult, error: false };
|
||||
const orderService = spectator.inject(OrderService);
|
||||
orderService.OrderLoyaltyCollect.mockReturnValue(of(mockResponse));
|
||||
|
||||
// Act
|
||||
const result = await spectator.service.collect(mockParams);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockResult);
|
||||
expect(orderService.OrderLoyaltyCollect).toHaveBeenCalledWith({
|
||||
orderId: mockParams.orderId,
|
||||
orderItemId: mockParams.orderItemId,
|
||||
orderItemSubsetId: mockParams.orderItemSubsetId,
|
||||
data: {
|
||||
collectType: mockParams.collectType,
|
||||
quantity: mockParams.quantity,
|
||||
},
|
||||
});
|
||||
expect(orderService.OrderLoyaltyCollect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should throw error if API response contains error', async () => {
|
||||
// Arrange
|
||||
const mockParams = {
|
||||
orderId: 123,
|
||||
orderItemId: 456,
|
||||
orderItemSubsetId: 789,
|
||||
collectType: 1 as const, // Valid LoyaltyCollectType.Collect
|
||||
quantity: 2,
|
||||
};
|
||||
const mockResponse = { error: true, result: undefined };
|
||||
const orderService = spectator.inject(OrderService);
|
||||
orderService.OrderLoyaltyCollect.mockReturnValue(of(mockResponse));
|
||||
|
||||
// Act & Assert
|
||||
await expect(spectator.service.collect(mockParams)).rejects.toThrow();
|
||||
expect(orderService.OrderLoyaltyCollect).toHaveBeenCalledWith({
|
||||
orderId: mockParams.orderId,
|
||||
orderItemId: mockParams.orderItemId,
|
||||
orderItemSubsetId: mockParams.orderItemSubsetId,
|
||||
data: {
|
||||
collectType: mockParams.collectType,
|
||||
quantity: mockParams.quantity,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchOrderItemSubset', () => {
|
||||
it('should fetch order item subset successfully', async () => {
|
||||
// Arrange
|
||||
const mockParams = {
|
||||
orderItemSubsetId: 789,
|
||||
};
|
||||
const mockResult = {
|
||||
id: 789,
|
||||
quantity: 2,
|
||||
name: 'Test Subset',
|
||||
};
|
||||
const mockResponse = { result: mockResult, error: false };
|
||||
const orderService = spectator.inject(OrderService);
|
||||
orderService.OrderGetOrderItemSubset.mockReturnValue(of(mockResponse));
|
||||
|
||||
// Act
|
||||
const result = await spectator.service.fetchOrderItemSubset(mockParams);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockResult);
|
||||
expect(orderService.OrderGetOrderItemSubset).toHaveBeenCalledWith(
|
||||
mockParams.orderItemSubsetId,
|
||||
);
|
||||
expect(orderService.OrderGetOrderItemSubset).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { OrderService } from '@generated/swagger/oms-api';
|
||||
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import {
|
||||
DisplayOrderItemSubset,
|
||||
FetchOrderItemSubsetSchema,
|
||||
FetchOrderItemSubsetSchemaInput,
|
||||
OrderLoyaltyCollectInput,
|
||||
OrderLoyaltyCollectSchema,
|
||||
} from '../schemas';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OrderRewardCollectService {
|
||||
#logger = logger(() => ({ service: 'OrderRewardCollectService' }));
|
||||
#orderService = inject(OrderService);
|
||||
|
||||
async collect(params: OrderLoyaltyCollectInput) {
|
||||
try {
|
||||
params = OrderLoyaltyCollectSchema.parse(params);
|
||||
} catch (error) {
|
||||
this.#logger.error('Failed to parse schema', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const req$ = this.#orderService.OrderLoyaltyCollect({
|
||||
orderId: params.orderId,
|
||||
orderItemId: params.orderItemId,
|
||||
orderItemSubsetId: params.orderItemSubsetId,
|
||||
data: {
|
||||
collectType: params.collectType,
|
||||
quantity: params.quantity,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await firstValueFrom(req$);
|
||||
|
||||
if (res.error) {
|
||||
const error = new ResponseArgsError(res);
|
||||
this.#logger.error('Failed to collect reward item', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res.result;
|
||||
}
|
||||
|
||||
async fetchOrderItemSubset(
|
||||
params: FetchOrderItemSubsetSchemaInput,
|
||||
abortSignal?: AbortSignal,
|
||||
) {
|
||||
try {
|
||||
params = FetchOrderItemSubsetSchema.parse(params);
|
||||
} catch (error) {
|
||||
this.#logger.error('Failed to parse schema', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
let req$ = this.#orderService.OrderGetOrderItemSubset(
|
||||
params.orderItemSubsetId,
|
||||
);
|
||||
|
||||
if (abortSignal) {
|
||||
req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
const res = await firstValueFrom(req$);
|
||||
|
||||
if (res.error) {
|
||||
const error = new ResponseArgsError(res);
|
||||
this.#logger.error('Failed to fetch order item subset', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res.result as DisplayOrderItemSubset;
|
||||
}
|
||||
}
|
||||
567
package-lock.json
generated
567
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user