Skip to content

Commit

Permalink
Feature/cxspa 3823 read threshold from quote (#17589)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristophHi authored Jun 30, 2023
1 parent 82a7bce commit 0765b28
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 99 deletions.
4 changes: 2 additions & 2 deletions feature-libs/quote/assets/translations/en/quote.i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ export const quote = {
},
httpHandlers: {
threshold: {
underTresholdError:
'Requested quote does not meet the threshold of minimum {{minValue}}$.',
underThresholdError:
'Total price of requested quote does not meet the minimum threshold',
},
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
<ng-container *ngIf="quoteDetailsState$ | async as quoteDetailsState">
<ng-container *ngIf="quoteDetails$ | async as quoteDetails">
<section>
<button
#element
type="button"
*ngFor="let action of quoteDetailsState.data?.allowedActions"
*ngFor="let action of quoteDetails.allowedActions"
class="btn"
[ngClass]="action.isPrimary ? 'btn-primary' : 'btn-secondary'"
[disabled]="
mustDisableAction(
action.type,
quoteDetailsState.data?.totalPrice?.value
)
"
(click)="
onClick(action.type, quoteDetailsState.data?.code || 'TODO CHHI')
"
[disabled]="mustDisableAction(action.type, quoteDetails)"
(click)="onClick(action.type, quoteDetails.code)"
>
{{ 'quote.actions.' + action.type | cxTranslate }}
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import {
GlobalMessageService,
I18nTestingModule,
Price,
QueryState,
TranslationService,
} from '@spartacus/core';
Expand All @@ -23,6 +24,8 @@ import { createEmptyQuote } from '../../core/testing/quote-test-utils';

const mockCartId = '1234';
const mockCode = '3333';
const threshold = 20;
const totalPrice: Price = { value: threshold + 1 };

const mockQuote: Quote = {
...createEmptyQuote(),
Expand All @@ -33,6 +36,8 @@ const mockQuote: Quote = {
state: QuoteState.BUYER_DRAFT,
cartId: mockCartId,
code: mockCode,
threshold: threshold,
totalPrice: totalPrice,
};
const mockQuoteDetailsState: QueryState<Quote> = {
loading: false,
Expand Down Expand Up @@ -123,8 +128,8 @@ describe('QuoteActionsByRoleComponent', () => {
});

it('should read quote details state', (done) => {
component.quoteDetailsState$.pipe(take(1)).subscribe((state) => {
expect(state).toEqual(mockQuoteDetailsState);
component.quoteDetails$.pipe(take(1)).subscribe((state) => {
expect(state).toEqual(mockQuoteDetailsState.data);
done();
});
});
Expand Down Expand Up @@ -217,9 +222,9 @@ describe('QuoteActionsByRoleComponent', () => {
const allowedActionsSubmit = [
{ type: QuoteActionType.SUBMIT, isPrimary: true },
];
const quoteFailingThreshold = {
const quoteFailingThreshold: Quote = {
...mockQuote,
totalPrice: { value: -1 },
totalPrice: { value: threshold - 1 },
allowedActions: allowedActionsSubmit,
};
const queryStateSubmittableQuote: QueryState<Quote> = {
Expand All @@ -246,6 +251,15 @@ describe('QuoteActionsByRoleComponent', () => {
expect(actionButtons[0].nativeElement.disabled).toBe(false);
});

it('should let submit button enabled if threshold is not specified', () => {
mockQuote.threshold = undefined;
mockQuoteDetailsState$.next(queryStateSubmittableQuote);
fixture.detectChanges();
const actionButtons = fixture.debugElement.queryAll(By.css('.btn'));
expect(actionButtons).toBeDefined();
expect(actionButtons[0].nativeElement.disabled).toBe(false);
});

it('should disable submit button if threshold is not met and raise message', () => {
spyOn(globalMessageService, 'add').and.callThrough();

Expand All @@ -264,6 +278,23 @@ describe('QuoteActionsByRoleComponent', () => {
expect(globalMessageService.add).toHaveBeenCalled();
});

it('should disable submit button if total price value is not provided', () => {
quoteFailingThreshold.totalPrice.value = undefined;

const queryStateSubmittableQuoteFailingThreshold: QueryState<Quote> = {
...queryStateSubmittableQuote,
data: {
...quoteFailingThreshold,
},
};
mockQuoteDetailsState$.next(queryStateSubmittableQuoteFailingThreshold);
fixture.detectChanges();

const actionButtons = fixture.debugElement.queryAll(By.css('.btn'));
expect(actionButtons).toBeDefined();
expect(actionButtons[0].nativeElement.disabled).toBe(true);
});

it('should not touch buttons other than submit', () => {
mockQuoteDetailsState$.next(queryStateCancellableQuote);
fixture.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,23 @@ import {
ViewChild,
ViewContainerRef,
} from '@angular/core';
import {
GlobalMessageService,
GlobalMessageType,
Config,
} from '@spartacus/core';
import { QuoteFacade, QuoteActionType } from '@spartacus/quote/root';
import { GlobalMessageService, GlobalMessageType } from '@spartacus/core';
import { QuoteFacade, QuoteActionType, Quote } from '@spartacus/quote/root';
import { LAUNCH_CALLER, LaunchDialogService } from '@spartacus/storefront';
import { Subscription } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { filter, map, take, tap } from 'rxjs/operators';

@Component({
selector: 'cx-quote-actions-by-role',
templateUrl: './quote-actions-by-role.component.html',
})
export class QuoteActionsByRoleComponent implements OnInit, OnDestroy {
quoteDetailsState$ = this.quoteFacade
.getQuoteDetails()
.pipe(filter((state) => !state.loading));
quoteDetails$: Observable<Quote> = this.quoteFacade.getQuoteDetails().pipe(
filter((state) => !state.loading),
filter((state) => state.data !== undefined),
map((state) => state.data),
map((quote) => quote as Quote)
);

@ViewChild('element') element: ElementRef;

Expand All @@ -41,46 +40,36 @@ export class QuoteActionsByRoleComponent implements OnInit, OnDestroy {
protected quoteFacade: QuoteFacade,
protected launchDialogService: LaunchDialogService,
protected viewContainerRef: ViewContainerRef,
protected globalMessageService: GlobalMessageService,
protected config: Config
protected globalMessageService: GlobalMessageService
) {}

ngOnInit(): void {
//submit button present and threshold not reached: Display message
this.quoteDetailsState$
.pipe(
map((state) => state.data),
take(1)
)
.subscribe((quote) => {
const total = quote?.totalPrice;
const mustDisableAction = quote?.allowedActions.find((action) =>
this.mustDisableAction(action.type, total?.value)
);
if (mustDisableAction) {
this.globalMessageService.add(
{
key: 'quote.requestDialog.form.minRequestInitiationNote',
params: {
minValue: this.config.quote?.tresholds?.requestInitiation,
},
this.quoteDetails$.pipe(take(1)).subscribe((quote) => {
const mustDisableAction = quote.allowedActions.find((action) =>
this.mustDisableAction(action.type, quote)
);
if (mustDisableAction) {
this.globalMessageService.add(
{
key: 'quote.requestDialog.form.minRequestInitiationNote',
params: {
minValue: quote.threshold,
},
GlobalMessageType.MSG_TYPE_WARNING
);
}
});
},
GlobalMessageType.MSG_TYPE_WARNING
);
}
});
}

mustDisableAction(type: string, totalPrice?: number): boolean {
return (
type === QuoteActionType.SUBMIT && !this.isThresholdReached(totalPrice)
);
mustDisableAction(type: string, quote: Quote): boolean {
return type === QuoteActionType.SUBMIT && !this.isThresholdReached(quote);
}

protected isThresholdReached(totalPrice?: number): boolean {
const requestThreshold =
this.config.quote?.tresholds?.requestInitiation || 0;
return (totalPrice || 0) >= requestThreshold;
protected isThresholdReached(quote: Quote): boolean {
const requestThreshold = quote.threshold || 0;
return (quote.totalPrice.value || 0) >= requestThreshold;
}

onClick(quoteActionType: QuoteActionType, code: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import { tap } from 'rxjs/operators';
export class QuoteRequestDialogComponent {
iconTypes = ICON_TYPE;
requestInProgress$ = new BehaviorSubject<boolean>(false);
minRequestInitiationValue = this.config.quote?.tresholds?.requestInitiation;
// TODO CHHI: Delete when decision has been taken about quote request dialog
minRequestInitiationValue = 0; // TODO CHHI: delete the entire component, obsolete // this.config.quote?.tresholds?.requestInitiation;

focusConfig: FocusConfig = {
trap: true,
Expand Down
19 changes: 10 additions & 9 deletions feature-libs/quote/core/config/quote-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import { Injectable } from '@angular/core';
import { QuoteActionType, QuoteActionsByState } from '@spartacus/quote/root';
import { Config } from '@spartacus/core';

export interface QuoteTresholdsConfig {
//TODO CHHI probably not needed in SPA, later remove
/** Value above which seller approval is required */
sellerAutoApproval: number;
//TODO CHHI read this value via OCC
/** Minimal value required to submit a quote */
requestInitiation: number;
}
//TODO CHHI: Delete when decision has been taken about quote request dialog
// export interface QuoteTresholdsConfig {
//
// /** Value above which seller approval is required */
// sellerAutoApproval: number;
//
// /** Minimal value required to submit a quote */
// requestInitiation: number;
// }

export interface QuoteActionsConfig {
/** Actions that should be presented in template as primary */
Expand All @@ -33,7 +34,7 @@ export abstract class QuoteConfig {
* Commerce quotes config
*/
quote?: {
tresholds?: QuoteTresholdsConfig;
//tresholds?: QuoteTresholdsConfig;
actions?: QuoteActionsConfig;
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@ import {
GlobalMessageService,
HttpResponseStatus,
GlobalMessageType,
Config,
} from '@spartacus/core';
import { QuoteConfig } from '../config/quote-config';
import { QuoteBadRequestHandler } from './quote-bad-request.handler';

const MockRequest = {} as HttpRequest<any>;
const MockCQConfig: QuoteConfig = {
quote: {
tresholds: {
requestInitiation: 10000,
sellerAutoApproval: 1,
},
},
};
//TODO CHHI: Delete when decision has been taken about quote request dialog
// const MockCQConfig: QuoteConfig = {
// quote: {
// tresholds: {
// requestInitiation: 10000,
// sellerAutoApproval: 1,
// },
// },
// };

const MockQuoteUnderThresholdResponse = {
error: {
Expand Down Expand Up @@ -48,10 +47,6 @@ describe('QuoteBadRequestHandler', () => {
provide: GlobalMessageService,
useClass: MockGlobalMessageService,
},
{
provide: Config,
useValue: MockCQConfig,
},
],
});
service = TestBed.inject(QuoteBadRequestHandler);
Expand All @@ -72,10 +67,7 @@ describe('QuoteBadRequestHandler', () => {

expect(globalMessageService.add).toHaveBeenCalledWith(
{
key: 'quote.httpHandlers.threshold.underTresholdError',
params: {
minValue: MockCQConfig.quote?.tresholds?.requestInitiation,
},
key: 'quote.httpHandlers.threshold.underThresholdError',
},
GlobalMessageType.MSG_TYPE_ERROR
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import { HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import {
Config,
GlobalMessageService,
GlobalMessageType,
HttpErrorHandler,
Expand All @@ -20,10 +19,7 @@ import {
providedIn: 'root',
})
export class QuoteBadRequestHandler extends HttpErrorHandler {
constructor(
protected globalMessageService: GlobalMessageService,
private config: Config
) {
constructor(protected globalMessageService: GlobalMessageService) {
super(globalMessageService);
}
responseStatus = HttpResponseStatus.BAD_REQUEST;
Expand All @@ -48,10 +44,7 @@ export class QuoteBadRequestHandler extends HttpErrorHandler {
if (result) {
this.globalMessageService.add(
{
key: 'quote.httpHandlers.threshold.underTresholdError',
params: {
minValue: this.config.quote?.tresholds?.requestInitiation,
},
key: 'quote.httpHandlers.threshold.underThresholdError',
},
GlobalMessageType.MSG_TYPE_ERROR
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import { QuoteConfig } from '@spartacus/quote/core';
providers: [
provideConfig(<QuoteConfig>{
quote: {
tresholds: {
sellerAutoApproval: 75000,
requestInitiation: 25000,
},
//TODO CHHI: Delete when decision has been taken about quote request dialog
// tresholds: {
// sellerAutoApproval: 75000,
// requestInitiation: 25000,
// },
},
}),
provideConfig({
Expand Down

0 comments on commit 0765b28

Please sign in to comment.