From ffb50945389cb1f76096ae1fa2976d0b9e65d496 Mon Sep 17 00:00:00 2001 From: anjana-bl Date: Fri, 7 Feb 2025 13:26:17 +0530 Subject: [PATCH 1/7] chore: code changes --- .../order/assets/translations/en/order.json | 3 ++- .../order-detail-actions.component.html | 15 +++++++++++++ feature-libs/order/root/model/order.model.ts | 1 + .../quote/assets/translations/en/quote.json | 4 +++- .../links/quote-links.component.html | 22 +++++++++++++++++++ .../components/links/quote-links.module.ts | 3 ++- feature-libs/quote/root/model/quote.model.ts | 1 + .../feature-toggles/config/feature-toggles.ts | 6 +++++ .../spartacus/spartacus-features.module.ts | 1 + 9 files changed, 53 insertions(+), 3 deletions(-) diff --git a/feature-libs/order/assets/translations/en/order.json b/feature-libs/order/assets/translations/en/order.json index 4674637a531..cb18ff1ac84 100644 --- a/feature-libs/order/assets/translations/en/order.json +++ b/feature-libs/order/assets/translations/en/order.json @@ -97,7 +97,8 @@ "reject": "No", "cancelSuccess": "Replenishment order #{{replenishmentOrderCode}} has been successfully cancelled" }, - "caption": "Order contents." + "caption": "Order contents.", + "viewQuote": "View Quote" }, "orderHistory": { "orderHistory": "Order history", diff --git a/feature-libs/order/components/order-details/order-detail-actions/order-detail-actions.component.html b/feature-libs/order/components/order-details/order-detail-actions/order-detail-actions.component.html index 0aa7185c009..e57079424ee 100644 --- a/feature-libs/order/components/order-details/order-detail-actions/order-detail-actions.component.html +++ b/feature-libs/order/components/order-details/order-detail-actions/order-detail-actions.component.html @@ -38,5 +38,20 @@ {{ 'orderDetails.cancellationAndReturn.returnAction' | cxTranslate }} + +
+ +
+
diff --git a/feature-libs/order/root/model/order.model.ts b/feature-libs/order/root/model/order.model.ts index 1d29273558c..2574a28b2bd 100644 --- a/feature-libs/order/root/model/order.model.ts +++ b/feature-libs/order/root/model/order.model.ts @@ -142,4 +142,5 @@ export interface Order { user?: Principal; returnable?: boolean; cancellable?: boolean; + quoteCode?: string; } diff --git a/feature-libs/quote/assets/translations/en/quote.json b/feature-libs/quote/assets/translations/en/quote.json index a027507c528..83dba6cdb17 100644 --- a/feature-libs/quote/assets/translations/en/quote.json +++ b/feature-libs/quote/assets/translations/en/quote.json @@ -176,10 +176,12 @@ "newCart": "New Cart", "quotes": "Quotes", "download": "Download Proposal", + "order": "Order {{code}}", "a11y": { "newCart": "Create new empty cart and navigate to it.", "quotes": "Navigate to quote search result list.", - "download": "Start downloading a quote proposal" + "download": "Start downloading a quote proposal", + "order": "Navigate to details page of order {{code}}" } }, "list": { diff --git a/feature-libs/quote/components/links/quote-links.component.html b/feature-libs/quote/components/links/quote-links.component.html index 0dbc8a77906..c60a64f60fe 100644 --- a/feature-libs/quote/components/links/quote-links.component.html +++ b/feature-libs/quote/components/links/quote-links.component.html @@ -40,6 +40,28 @@ {{ 'quote.links.download' | cxTranslate }} + +
  • + + {{ + 'quote.links.order' + | cxTranslate: { code: quoteDetails.orderCode } + }} +
  • +
    diff --git a/feature-libs/quote/components/links/quote-links.module.ts b/feature-libs/quote/components/links/quote-links.module.ts index 016f9920998..6baa196b54d 100644 --- a/feature-libs/quote/components/links/quote-links.module.ts +++ b/feature-libs/quote/components/links/quote-links.module.ts @@ -10,6 +10,7 @@ import { RouterModule } from '@angular/router'; import { AuthGuard, CmsConfig, + FeaturesConfigModule, I18nModule, provideDefaultConfig, UrlModule, @@ -17,7 +18,7 @@ import { import { QuoteLinksComponent } from './quote-links.component'; @NgModule({ - imports: [CommonModule, I18nModule, RouterModule, UrlModule], + imports: [CommonModule, I18nModule, RouterModule, UrlModule, FeaturesConfigModule], providers: [ provideDefaultConfig({ cmsComponents: { diff --git a/feature-libs/quote/root/model/quote.model.ts b/feature-libs/quote/root/model/quote.model.ts index e918242a4e0..bb3343cc35e 100644 --- a/feature-libs/quote/root/model/quote.model.ts +++ b/feature-libs/quote/root/model/quote.model.ts @@ -33,6 +33,7 @@ export interface OccQuote { totalPriceWithTax?: Price; updatedTime?: Date; version?: number; + orderCode?: string; } export type Quote = Omit & { diff --git a/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts b/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts index 9d0a6ac3724..b020b1f7b6a 100644 --- a/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts +++ b/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts @@ -15,6 +15,11 @@ export interface FeatureTogglesInterface { */ showDeliveryOptionsTranslation?: boolean; + /** + * In Order details page, it shows link to its Quote details page and vice-versa + */ + showOrderQuoteLink?: boolean; + /** * In 'ProductListItemComponent' and 'ProductGridItemComponent', it hides the 'Add to cart' button * when a product does not have a defined price or its purchasable field is set to false @@ -996,6 +1001,7 @@ export interface FeatureTogglesInterface { } export const defaultFeatureToggles: Required = { + showOrderQuoteLink: false, showDeliveryOptionsTranslation: false, formErrorsDescriptiveMessages: true, showSearchingCustomerByOrderInASM: true, diff --git a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts index 8cce537eaa5..0b22bab5b17 100644 --- a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts +++ b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts @@ -296,6 +296,7 @@ if (environment.cpq) { useExtractedBillingAddressComponent: false, showBillingAddressInDigitalPayments: false, showDownloadProposalButton: false, + showOrderQuoteLink: false, showPromotionsInPDP: false, searchBoxV2: false, recentSearches: true, From 4fa6bb75e9e4062cd022fa9b3d8ac210ab0abec1 Mon Sep 17 00:00:00 2001 From: anjana-bl Date: Wed, 12 Feb 2025 12:22:11 +0530 Subject: [PATCH 2/7] chore: adding assets --- feature-libs/quote/assets/translations/en/quote.json | 4 ++-- .../quote/components/links/quote-links.component.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/feature-libs/quote/assets/translations/en/quote.json b/feature-libs/quote/assets/translations/en/quote.json index 83dba6cdb17..147f1cd8565 100644 --- a/feature-libs/quote/assets/translations/en/quote.json +++ b/feature-libs/quote/assets/translations/en/quote.json @@ -176,12 +176,12 @@ "newCart": "New Cart", "quotes": "Quotes", "download": "Download Proposal", - "order": "Order {{code}}", + "order": "View Order", "a11y": { "newCart": "Create new empty cart and navigate to it.", "quotes": "Navigate to quote search result list.", "download": "Start downloading a quote proposal", - "order": "Navigate to details page of order {{code}}" + "order": "Navigate to order associated with this quote" } }, "list": { diff --git a/feature-libs/quote/components/links/quote-links.component.html b/feature-libs/quote/components/links/quote-links.component.html index c60a64f60fe..467c4a60138 100644 --- a/feature-libs/quote/components/links/quote-links.component.html +++ b/feature-libs/quote/components/links/quote-links.component.html @@ -45,7 +45,7 @@ - -
    - -
    -
    diff --git a/feature-libs/order/components/order-details/order-overview/order-overview.component.html b/feature-libs/order/components/order-details/order-overview/order-overview.component.html index 18b000975c0..925bb92bd04 100644 --- a/feature-libs/order/components/order-details/order-overview/order-overview.component.html +++ b/feature-libs/order/components/order-details/order-overview/order-overview.component.html @@ -153,6 +153,25 @@ [content]="getOrderStatusCardContent(order.statusDisplay) | async" > + + + + + + + { + return this.translation.translate('orderDetails.quoteCode').pipe( + filter(() => Boolean(quoteCode)), + map((textTitle) => ({ + title: textTitle, + text: [quoteCode], + })) + ); + } + getOrderCurrentDateCardContent(isoDate: string | null): Observable { return this.translation.translate('orderDetails.placedOn').pipe( filter(() => Boolean(isoDate)), diff --git a/feature-libs/order/root/model/order.model.ts b/feature-libs/order/root/model/order.model.ts index 2574a28b2bd..7020eb07271 100644 --- a/feature-libs/order/root/model/order.model.ts +++ b/feature-libs/order/root/model/order.model.ts @@ -142,5 +142,5 @@ export interface Order { user?: Principal; returnable?: boolean; cancellable?: boolean; - quoteCode?: string; + sapQuoteCode?: string; } diff --git a/feature-libs/quote/assets/translations/en/quote.json b/feature-libs/quote/assets/translations/en/quote.json index 147f1cd8565..ffc57ae9550 100644 --- a/feature-libs/quote/assets/translations/en/quote.json +++ b/feature-libs/quote/assets/translations/en/quote.json @@ -176,7 +176,7 @@ "newCart": "New Cart", "quotes": "Quotes", "download": "Download Proposal", - "order": "View Order", + "order": "Order Detail", "a11y": { "newCart": "Create new empty cart and navigate to it.", "quotes": "Navigate to quote search result list.", diff --git a/feature-libs/quote/components/links/quote-links.component.html b/feature-libs/quote/components/links/quote-links.component.html index 467c4a60138..94e2bafe4c5 100644 --- a/feature-libs/quote/components/links/quote-links.component.html +++ b/feature-libs/quote/components/links/quote-links.component.html @@ -41,7 +41,7 @@ -
  • +
  • diff --git a/feature-libs/quote/root/model/quote.model.ts b/feature-libs/quote/root/model/quote.model.ts index bb3343cc35e..1f1496f110c 100644 --- a/feature-libs/quote/root/model/quote.model.ts +++ b/feature-libs/quote/root/model/quote.model.ts @@ -33,7 +33,7 @@ export interface OccQuote { totalPriceWithTax?: Price; updatedTime?: Date; version?: number; - orderCode?: string; + sapOrderCode?: string; } export type Quote = Omit & { From b732594faf29b446791e8a486d9690e2313c53ab Mon Sep 17 00:00:00 2001 From: anjana-bl Date: Mon, 24 Feb 2025 17:35:48 +0530 Subject: [PATCH 4/7] feat: adding link for sapQuoteCode --- .../order-overview.component.html | 29 +++++++++++++++---- .../order-overview.component.ts | 1 - 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/feature-libs/order/components/order-details/order-overview/order-overview.component.html b/feature-libs/order/components/order-details/order-overview/order-overview.component.html index 925bb92bd04..d8b130b96e7 100644 --- a/feature-libs/order/components/order-details/order-overview/order-overview.component.html +++ b/feature-libs/order/components/order-details/order-overview/order-overview.component.html @@ -63,6 +63,25 @@ [content]="getOrderStatusCardContent(order.statusDisplay) | async" > + + + + + {{ order.sapQuoteCode }} + + + - diff --git a/feature-libs/order/components/order-details/order-overview/order-overview.component.ts b/feature-libs/order/components/order-details/order-overview/order-overview.component.ts index 06f3ed0fd6b..e38f429b592 100644 --- a/feature-libs/order/components/order-details/order-overview/order-overview.component.ts +++ b/feature-libs/order/components/order-details/order-overview/order-overview.component.ts @@ -123,7 +123,6 @@ export class OrderOverviewComponent { filter(() => Boolean(quoteCode)), map((textTitle) => ({ title: textTitle, - text: [quoteCode], })) ); } From 7f181a58b9f5fc81297c47be21e85fad5d0d7f0a Mon Sep 17 00:00:00 2001 From: anjana-bl Date: Wed, 26 Feb 2025 18:27:20 +0530 Subject: [PATCH 5/7] feat: changing field names --- .../order-overview.component.html | 66 +++++++++---------- .../order-overview.component.ts | 11 +--- .../occ/adapters/occ-order-history.adapter.ts | 26 +++++++- .../occ/config/default-occ-order-config.ts | 1 + .../occ/model/occ-order-endpoints.model.ts | 6 ++ .../links/quote-links.component.html | 10 +-- .../components/links/quote-links.module.ts | 8 ++- .../quote/occ/adapters/occ-quote.adapter.ts | 24 ++++++- .../occ/config/default-occ-quote-config.ts | 1 + .../occ/model/occ-quote-endpoints.model.ts | 5 ++ 10 files changed, 98 insertions(+), 60 deletions(-) diff --git a/feature-libs/order/components/order-details/order-overview/order-overview.component.html b/feature-libs/order/components/order-details/order-overview/order-overview.component.html index d8b130b96e7..be9ecc2ab6b 100644 --- a/feature-libs/order/components/order-details/order-overview/order-overview.component.html +++ b/feature-libs/order/components/order-details/order-overview/order-overview.component.html @@ -63,24 +63,12 @@ [content]="getOrderStatusCardContent(order.statusDisplay) | async" > - - - - - {{ order.sapQuoteCode }} - - + - - - - - {{ order.sapQuoteCode }} - - + + + + +
    +
    + {{ 'orderDetails.quoteCode' | cxTranslate }} +
    + + {{ quoteCode }} + +
    +
    +
    diff --git a/feature-libs/order/components/order-details/order-overview/order-overview.component.ts b/feature-libs/order/components/order-details/order-overview/order-overview.component.ts index e38f429b592..7614afe1415 100644 --- a/feature-libs/order/components/order-details/order-overview/order-overview.component.ts +++ b/feature-libs/order/components/order-details/order-overview/order-overview.component.ts @@ -11,6 +11,7 @@ import { CmsOrderDetailOverviewComponent, CostCenter, PaymentDetails, + RoutingService, TranslationService, } from '@spartacus/core'; import { Card, CmsComponentData } from '@spartacus/storefront'; @@ -30,6 +31,7 @@ export class OrderOverviewComponent { protected orderOverviewComponentService = inject( OrderOverviewComponentService ); + protected routingService = inject(RoutingService); readonly cartOutlets = CartOutlets; readonly orderOutlets = OrderOutlets; @@ -118,15 +120,6 @@ export class OrderOverviewComponent { ); } - getQuoteCardContent(quoteCode: string): Observable { - return this.translation.translate('orderDetails.quoteCode').pipe( - filter(() => Boolean(quoteCode)), - map((textTitle) => ({ - title: textTitle, - })) - ); - } - getOrderCurrentDateCardContent(isoDate: string | null): Observable { return this.translation.translate('orderDetails.placedOn').pipe( filter(() => Boolean(isoDate)), diff --git a/feature-libs/order/occ/adapters/occ-order-history.adapter.ts b/feature-libs/order/occ/adapters/occ-order-history.adapter.ts index c0604b8064b..0055ddaccdc 100644 --- a/feature-libs/order/occ/adapters/occ-order-history.adapter.ts +++ b/feature-libs/order/occ/adapters/occ-order-history.adapter.ts @@ -8,12 +8,15 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { ConverterService, + FeatureConfigService, InterceptorUtil, LoggerService, OCC_USER_ID_ANONYMOUS, OCC_USER_ID_CURRENT, Occ, OccEndpointsService, + OccFieldsService, + ScopedDataWithUrl, USE_CLIENT_TOKEN, normalizeHttpError, } from '@spartacus/core'; @@ -42,6 +45,8 @@ const CONTENT_TYPE_JSON_HEADER = { 'Content-Type': 'application/json' }; @Injectable() export class OccOrderHistoryAdapter implements OrderHistoryAdapter { protected logger = inject(LoggerService); + private occFieldsService = inject(OccFieldsService); + private featureConfigService = inject(FeatureConfigService); constructor( protected http: HttpClient, @@ -50,9 +55,24 @@ export class OccOrderHistoryAdapter implements OrderHistoryAdapter { ) {} public load(userId: string, orderCode: string): Observable { - const url = this.occEndpoints.buildUrl('orderDetail', { - urlParams: { userId, orderId: orderCode }, - }); + const url = this.featureConfigService.isEnabled('showOrderQuoteLink') + ? (() => { + const scopes = ['orderDetail', 'quoteCode']; + const scopedDataWithUrls: ScopedDataWithUrl[] = scopes.map( + (scope) => ({ + scopedData: { scope, userId, orderCode }, + url: this.occEndpoints.buildUrl(scope, { + urlParams: { userId, orderId: orderCode }, + }), + }) + ); + const mergedUrl = + this.occFieldsService.getOptimalUrlGroups(scopedDataWithUrls); + return Object.keys(mergedUrl)[0]; + })() + : this.occEndpoints.buildUrl('orderDetail', { + urlParams: { userId, orderId: orderCode }, + }); let headers = new HttpHeaders(); if (userId === OCC_USER_ID_ANONYMOUS) { diff --git a/feature-libs/order/occ/config/default-occ-order-config.ts b/feature-libs/order/occ/config/default-occ-order-config.ts index 0ba68dcc0e9..bec63e034bf 100644 --- a/feature-libs/order/occ/config/default-occ-order-config.ts +++ b/feature-libs/order/occ/config/default-occ-order-config.ts @@ -13,6 +13,7 @@ export const defaultOccOrderConfig: OccConfig = { /* eslint-disable max-len */ orderHistory: 'users/${userId}/orders', orderDetail: 'users/${userId}/orders/${orderId}?fields=FULL', + quoteCode: 'users/${userId}/orders/${orderId}?fields=sapQuoteCode', consignmentTracking: 'users/${userId}/orders/${orderCode}/consignments/${consignmentCode}/tracking', cancelOrder: 'users/${userId}/orders/${orderId}/cancellation', diff --git a/feature-libs/order/occ/model/occ-order-endpoints.model.ts b/feature-libs/order/occ/model/occ-order-endpoints.model.ts index 55ae018fc33..2a95abd9b23 100644 --- a/feature-libs/order/occ/model/occ-order-endpoints.model.ts +++ b/feature-libs/order/occ/model/occ-order-endpoints.model.ts @@ -19,6 +19,12 @@ export interface OrderOccEndpoints { * @member {string} */ orderDetail?: string | OccEndpoint; + /** + * Endpoint for Quote Code associated with the Order. The Quote Code is present if the order was placed from a quote. + * + * @member {string} + */ + quoteCode?: string | OccEndpoint; /** * Endpoint for consignment tracking * diff --git a/feature-libs/quote/components/links/quote-links.component.html b/feature-libs/quote/components/links/quote-links.component.html index 94e2bafe4c5..2506908b60d 100644 --- a/feature-libs/quote/components/links/quote-links.component.html +++ b/feature-libs/quote/components/links/quote-links.component.html @@ -43,10 +43,7 @@
  • - {{ - 'quote.links.order' - | cxTranslate - }}
  • diff --git a/feature-libs/quote/components/links/quote-links.module.ts b/feature-libs/quote/components/links/quote-links.module.ts index 6baa196b54d..75fce4a7977 100644 --- a/feature-libs/quote/components/links/quote-links.module.ts +++ b/feature-libs/quote/components/links/quote-links.module.ts @@ -18,7 +18,13 @@ import { import { QuoteLinksComponent } from './quote-links.component'; @NgModule({ - imports: [CommonModule, I18nModule, RouterModule, UrlModule, FeaturesConfigModule], + imports: [ + CommonModule, + I18nModule, + RouterModule, + UrlModule, + FeaturesConfigModule, + ], providers: [ provideDefaultConfig({ cmsComponents: { diff --git a/feature-libs/quote/occ/adapters/occ-quote.adapter.ts b/feature-libs/quote/occ/adapters/occ-quote.adapter.ts index 7de46ec52cf..52966bf06dd 100644 --- a/feature-libs/quote/occ/adapters/occ-quote.adapter.ts +++ b/feature-libs/quote/occ/adapters/occ-quote.adapter.ts @@ -8,10 +8,13 @@ import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { ConverterService, + FeatureConfigService, LoggerService, normalizeHttpError, OccEndpointsService, + OccFieldsService, PaginationModel, + ScopedDataWithUrl, } from '@spartacus/core'; import { QUOTE_ACTION_SERIALIZER, @@ -42,6 +45,8 @@ export class OccQuoteAdapter implements QuoteAdapter { protected occEndpointsService = inject(OccEndpointsService); protected converterService = inject(ConverterService); protected loggerService = inject(LoggerService); + private occFieldsService = inject(OccFieldsService); + private featureConfigService = inject(FeatureConfigService); getQuotes( userId: string, @@ -100,9 +105,22 @@ export class OccQuoteAdapter implements QuoteAdapter { } protected getQuoteEndpoint(userId: string, quoteCode: string): string { - return this.occEndpointsService.buildUrl('getQuote', { - urlParams: { userId, quoteCode }, - }); + if (this.featureConfigService.isEnabled('showOrderQuoteLink')) { + const scopes = ['getQuote', 'getOrderCode']; + const scopedDataWithUrls: ScopedDataWithUrl[] = scopes.map((scope) => ({ + scopedData: { scope, userId, quoteCode }, + url: this.occEndpointsService.buildUrl(scope, { + urlParams: { userId, quoteCode }, + }), + })); + const mergedUrl = + this.occFieldsService.getOptimalUrlGroups(scopedDataWithUrls); + return Object.keys(mergedUrl)[0]; + } else { + return this.occEndpointsService.buildUrl('getQuote', { + urlParams: { userId, quoteCode }, + }); + } } editQuote( diff --git a/feature-libs/quote/occ/config/default-occ-quote-config.ts b/feature-libs/quote/occ/config/default-occ-quote-config.ts index 03fe05e33c5..b56f41993d7 100644 --- a/feature-libs/quote/occ/config/default-occ-quote-config.ts +++ b/feature-libs/quote/occ/config/default-occ-quote-config.ts @@ -19,6 +19,7 @@ export const defaultOccQuoteConfig: OccConfig = { 'users/${userId}/quotes/${quoteCode}?fields=FULL,expirationTime' + PRICE_HEADER_FIELDS + ',entries(FULL)', + getOrderCode: 'users/${userId}/quotes/${quoteCode}?fields=sapOrderCode', editQuote: 'users/${userId}/quotes/${quoteCode}', performQuoteAction: 'users/${userId}/quotes/${quoteCode}/action', addComment: 'users/${userId}/quotes/${quoteCode}/comments', diff --git a/feature-libs/quote/occ/model/occ-quote-endpoints.model.ts b/feature-libs/quote/occ/model/occ-quote-endpoints.model.ts index 44d6f67769d..d3455f2ff4b 100644 --- a/feature-libs/quote/occ/model/occ-quote-endpoints.model.ts +++ b/feature-libs/quote/occ/model/occ-quote-endpoints.model.ts @@ -17,6 +17,11 @@ export interface QuoteOccEndpoints { */ createQuote?: string | OccEndpoint; + /** + * Get Order Code associated with the Quote. The Order Code is present if the quote was used to place an order. + */ + getOrderCode?: string | OccEndpoint; + /** * Get a specific quote. */ From 96d8b1e5d47b06c31a970cc1269feafa4e099dd5 Mon Sep 17 00:00:00 2001 From: anjana-bl Date: Mon, 3 Mar 2025 10:05:48 +0530 Subject: [PATCH 6/7] feat: adding unit test cases --- .../order/assets/translations/en/order.json | 3 +- .../order-overview.component.html | 6 +-- .../order-overview.component.spec.ts | 38 +++++++++++++- .../order-overview.component.ts | 2 - .../occ-order-history.adapter.spec.ts | 31 +++++++++++ .../links/quote-links.component.spec.ts | 33 +++++++++++- .../occ/adapters/occ-quote.adapter.spec.ts | 51 ++++++++++++++++++- 7 files changed, 154 insertions(+), 10 deletions(-) diff --git a/feature-libs/order/assets/translations/en/order.json b/feature-libs/order/assets/translations/en/order.json index 1728b0f4e4d..a9f21e05a7f 100644 --- a/feature-libs/order/assets/translations/en/order.json +++ b/feature-libs/order/assets/translations/en/order.json @@ -10,7 +10,8 @@ "placed": "Placed", "placedBy": "Placed By", "unit": "Unit", - "quoteCode": "Quote ID", + "quoteCode": "Quote ID {{id}}", + "quoteDetail": "Quote Detail", "costCenter": "Cost Center", "costCenterAndUnit": "Cost Center / Unit", "costCenterAndUnitValue": "{{costCenterName}} / {{unitName}}", diff --git a/feature-libs/order/components/order-details/order-overview/order-overview.component.html b/feature-libs/order/components/order-details/order-overview/order-overview.component.html index be9ecc2ab6b..1bb43b870ff 100644 --- a/feature-libs/order/components/order-details/order-overview/order-overview.component.html +++ b/feature-libs/order/components/order-details/order-overview/order-overview.component.html @@ -185,9 +185,9 @@ -
    +
    - {{ 'orderDetails.quoteCode' | cxTranslate }} + {{ 'orderDetails.quoteCode' | cxTranslate: { id: quoteCode } }}
    - {{ quoteCode }} + {{ 'orderDetails.quoteDetail' | cxTranslate }}
    diff --git a/feature-libs/order/components/order-details/order-overview/order-overview.component.spec.ts b/feature-libs/order/components/order-details/order-overview/order-overview.component.spec.ts index 25c5e13d7fb..4dabe70a5a9 100644 --- a/feature-libs/order/components/order-details/order-overview/order-overview.component.spec.ts +++ b/feature-libs/order/components/order-details/order-overview/order-overview.component.spec.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, Pipe, PipeTransform } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DeliveryMode } from '@spartacus/cart/base/root'; import { @@ -14,6 +14,7 @@ import { EMPTY, Observable, of } from 'rxjs'; import { OrderDetailsService } from '../order-details.service'; import { OrderOverviewComponent } from './order-overview.component'; import { OrderOverviewComponentService } from './order-overview-component.service'; +import { MockFeatureDirective } from 'projects/storefrontlib/shared/test/mock-feature-directive'; @Component({ selector: 'cx-card', @@ -25,6 +26,14 @@ class MockCardComponent { content: Card; } +@Pipe({ + name: 'cxUrl', + standalone: false, +}) +class MockUrlPipe implements PipeTransform { + transform() {} +} + const mockDeliveryAddress: Address = { firstName: 'John', lastName: 'Smith', @@ -154,7 +163,12 @@ describe('OrderOverviewComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [I18nTestingModule], - declarations: [OrderOverviewComponent, MockCardComponent], + declarations: [ + OrderOverviewComponent, + MockCardComponent, + MockUrlPipe, + MockFeatureDirective, + ], providers: [ { provide: TranslationService, useClass: MockTranslationService }, { @@ -552,4 +566,24 @@ describe('OrderOverviewComponent', () => { ); }); }); + + it('should render quote code in UI', () => { + component.order$ = of({ ...mockOrder, sapQuoteCode: '12345' }); + fixture.detectChanges(); + const quoteContainer = + fixture.nativeElement.querySelector('#quote-container'); + expect(quoteContainer).not.toBeNull(); + const quoteTemplate = quoteContainer.querySelector('.cx-card-title'); + expect(quoteTemplate.textContent).toContain('12345'); + const quoteLink = quoteContainer.querySelector('.cx-card-actions'); + expect(quoteLink.innerText).toEqual('orderDetails.quoteDetail'); + }); + + it('should not render quote code in UI', () => { + component.order$ = of({ ...mockOrder }); + fixture.detectChanges(); + const quoteContainer = + fixture.nativeElement.querySelector('#quote-container'); + expect(quoteContainer).toBeNull(); + }); }); diff --git a/feature-libs/order/components/order-details/order-overview/order-overview.component.ts b/feature-libs/order/components/order-details/order-overview/order-overview.component.ts index 7614afe1415..026d0ddec5e 100644 --- a/feature-libs/order/components/order-details/order-overview/order-overview.component.ts +++ b/feature-libs/order/components/order-details/order-overview/order-overview.component.ts @@ -11,7 +11,6 @@ import { CmsOrderDetailOverviewComponent, CostCenter, PaymentDetails, - RoutingService, TranslationService, } from '@spartacus/core'; import { Card, CmsComponentData } from '@spartacus/storefront'; @@ -31,7 +30,6 @@ export class OrderOverviewComponent { protected orderOverviewComponentService = inject( OrderOverviewComponentService ); - protected routingService = inject(RoutingService); readonly cartOutlets = CartOutlets; readonly orderOutlets = OrderOutlets; diff --git a/feature-libs/order/occ/adapters/occ-order-history.adapter.spec.ts b/feature-libs/order/occ/adapters/occ-order-history.adapter.spec.ts index f025793e341..f42527ac312 100644 --- a/feature-libs/order/occ/adapters/occ-order-history.adapter.spec.ts +++ b/feature-libs/order/occ/adapters/occ-order-history.adapter.spec.ts @@ -12,6 +12,7 @@ import { ConverterService, OccConfig, OccEndpointsService, + OccFieldsService, } from '@spartacus/core'; import { CancellationRequestEntryInputList, @@ -48,6 +49,7 @@ describe('OccOrderHistoryAdapter', () => { let httpMock: HttpTestingController; let converter: ConverterService; let occEnpointsService: OccEndpointsService; + let occFieldsService: OccFieldsService; beforeEach(() => { TestBed.configureTestingModule({ @@ -67,6 +69,7 @@ describe('OccOrderHistoryAdapter', () => { httpMock = TestBed.inject(HttpTestingController); converter = TestBed.inject(ConverterService); occEnpointsService = TestBed.inject(OccEndpointsService); + occFieldsService = TestBed.inject(OccFieldsService); spyOn(converter, 'pipeable').and.callThrough(); spyOn(converter, 'convert').and.callThrough(); spyOn(occEnpointsService, 'buildUrl').and.callThrough(); @@ -122,7 +125,31 @@ describe('OccOrderHistoryAdapter', () => { }); describe('getOrder', () => { + it('should fetch a single order without quote code', waitForAsync(() => { + spyOn( + (occOrderHistoryAdapter as any).featureConfigService, + 'isEnabled' + ).and.returnValue(false); + occOrderHistoryAdapter.load(userId, orderData.code).subscribe(); + httpMock.expectOne((req: HttpRequest) => { + return req.method === 'GET'; + }, `GET a single order`); + expect(occEnpointsService.buildUrl).toHaveBeenCalledWith('orderDetail', { + urlParams: { userId, orderId: orderData.code }, + }); + expect(occEnpointsService.buildUrl).not.toHaveBeenCalledWith( + 'quoteCode', + { + urlParams: { userId, orderId: orderData.code }, + } + ); + })); it('should fetch a single order', waitForAsync(() => { + spyOn(occFieldsService, 'getOptimalUrlGroups').and.callThrough(); + spyOn( + (occOrderHistoryAdapter as any).featureConfigService, + 'isEnabled' + ).and.returnValue(true); occOrderHistoryAdapter.load(userId, orderData.code).subscribe(); httpMock.expectOne((req: HttpRequest) => { return req.method === 'GET'; @@ -130,6 +157,10 @@ describe('OccOrderHistoryAdapter', () => { expect(occEnpointsService.buildUrl).toHaveBeenCalledWith('orderDetail', { urlParams: { userId, orderId: orderData.code }, }); + expect(occEnpointsService.buildUrl).toHaveBeenCalledWith('quoteCode', { + urlParams: { userId, orderId: orderData.code }, + }); + expect(occFieldsService.getOptimalUrlGroups).toHaveBeenCalled(); })); it('should use converter', () => { diff --git a/feature-libs/quote/components/links/quote-links.component.spec.ts b/feature-libs/quote/components/links/quote-links.component.spec.ts index bd7ab413cd1..9dc7f2774e1 100644 --- a/feature-libs/quote/components/links/quote-links.component.spec.ts +++ b/feature-libs/quote/components/links/quote-links.component.spec.ts @@ -32,6 +32,7 @@ import { createEmptyQuote } from '../../core/testing/quote-test-utils'; import { CommonQuoteTestUtilsService } from '../testing/common-quote-test-utils.service'; import { QuoteLinksComponent } from './quote-links.component'; import createSpy = jasmine.createSpy; +import { MockFeatureDirective } from 'projects/storefrontlib/shared/test/mock-feature-directive'; class MockCartUtilsService implements Partial { goToNewCart = createSpy(); @@ -57,6 +58,11 @@ const mockQuote: Quote = { totalPrice: totalPrice, }; +const mockWithOrderCode: Quote = { + ...mockQuote, + sapOrderCode: '12345', +}; + const mockQuoteAttachment = (): File => { const blob = new Blob([''], { type: 'application/pdf' }); return blob as File; @@ -114,7 +120,7 @@ describe('QuoteLinksComponent', () => { UrlTestingModule, RouterModule.forRoot(mockRoutes), ], - declarations: [QuoteLinksComponent], + declarations: [QuoteLinksComponent, MockFeatureDirective], providers: [ { provide: QuoteFacade, @@ -319,4 +325,29 @@ describe('QuoteLinksComponent', () => { }); }); }); + + describe('order details link', () => { + it('should not show order details link when order code is present', () => { + const anchorElements = + fixture.nativeElement.querySelectorAll('a.cx-action-link'); + const orderLink = Array.from(anchorElements).find( + (el: any) => el.innerText.trim() === 'quote.links.order' + ); + expect(orderLink).toBeUndefined(); + }); + it('should show order details link when order code is present', async () => { + mockQuoteDetails$.next(mockWithOrderCode); + fixture.detectChanges(); + const anchorElements = + fixture.nativeElement.querySelectorAll('a.cx-action-link'); + const orderLink = Array.from(anchorElements).find( + (el: any) => el.innerText.trim() === 'quote.links.order' + ); + expect(orderLink).not.toBeUndefined(); + expect((orderLink as HTMLAnchorElement).href).toContain( + 'cxRoute:orderDetails' + ); + expect((orderLink as HTMLAnchorElement).href).toContain('code:12345'); + }); + }); }); diff --git a/feature-libs/quote/occ/adapters/occ-quote.adapter.spec.ts b/feature-libs/quote/occ/adapters/occ-quote.adapter.spec.ts index f49ec2dc2e6..c86836eb990 100644 --- a/feature-libs/quote/occ/adapters/occ-quote.adapter.spec.ts +++ b/feature-libs/quote/occ/adapters/occ-quote.adapter.spec.ts @@ -8,7 +8,12 @@ import { provideHttpClientTesting, } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { ConverterService, OccConfig, OccEndpoints } from '@spartacus/core'; +import { + ConverterService, + OccConfig, + OccEndpoints, + OccEndpointsService, +} from '@spartacus/core'; import { QUOTE_ACTION_SERIALIZER, QUOTE_COMMENT_SERIALIZER, @@ -110,6 +115,7 @@ describe(`OccQuoteAdapter`, () => { let classUnderTest: OccQuoteAdapter; let httpTestingController: HttpTestingController; let converterService: ConverterService; + let occEnpointsService: OccEndpointsService; beforeEach(() => { TestBed.configureTestingModule({ @@ -124,10 +130,12 @@ describe(`OccQuoteAdapter`, () => { classUnderTest = TestBed.inject(OccQuoteAdapter); httpTestingController = TestBed.inject(HttpTestingController); converterService = TestBed.inject(ConverterService); + occEnpointsService = TestBed.inject(OccEndpointsService); spyOn(converterService, 'pipeable').and.callThrough(); spyOn(converterService, 'pipeableMany').and.callThrough(); spyOn(converterService, 'convert').and.callThrough(); + spyOn(occEnpointsService, 'buildUrl').and.callThrough(); }); afterEach(() => { @@ -182,7 +190,42 @@ describe(`OccQuoteAdapter`, () => { ); }); + it('getQuote should return quote details based on provided quoteCode without orderCode', (done) => { + spyOn( + (classUnderTest as any).featureConfigService, + 'isEnabled' + ).and.returnValue(false); + classUnderTest + .getQuote(userId, mockQuote.code) + .pipe(take(1)) + .subscribe((result) => { + expect(result).toEqual(mockQuote); + done(); + }); + + const mockReq = httpTestingController.expectOne((req) => + isQuoteReq(req, 'GET') + ); + expect(occEnpointsService.buildUrl).toHaveBeenCalledWith('getQuote', { + urlParams: { userId, quoteCode: mockQuote.code }, + }); + expect(occEnpointsService.buildUrl).not.toHaveBeenCalledWith( + 'getOrderCode', + { + urlParams: { userId, quoteCode: mockQuote.code }, + } + ); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + mockReq.flush(mockQuote); + expect(converterService.pipeable).toHaveBeenCalledWith(QUOTE_NORMALIZER); + }); + it('getQuote should return quote details based on provided quoteCode', (done) => { + spyOn( + (classUnderTest as any).featureConfigService, + 'isEnabled' + ).and.returnValue(true); classUnderTest .getQuote(userId, mockQuote.code) .pipe(take(1)) @@ -199,6 +242,12 @@ describe(`OccQuoteAdapter`, () => { expect(mockReq.request.responseType).toEqual('json'); mockReq.flush(mockQuote); expect(converterService.pipeable).toHaveBeenCalledWith(QUOTE_NORMALIZER); + expect(occEnpointsService.buildUrl).toHaveBeenCalledWith('getQuote', { + urlParams: { userId, quoteCode: mockQuote.code }, + }); + expect(occEnpointsService.buildUrl).toHaveBeenCalledWith('getOrderCode', { + urlParams: { userId, quoteCode: mockQuote.code }, + }); }); it('getQuote should call httpErrorHandler on error', (done) => { From 921571bbc564e99a731329f60bc7720d6d33fb4f Mon Sep 17 00:00:00 2001 From: anjana-bl Date: Mon, 3 Mar 2025 10:20:48 +0530 Subject: [PATCH 7/7] Update spartacus-features.module.ts --- .../src/app/spartacus/spartacus-features.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts index a3de2b899b7..e82bf522ece 100644 --- a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts +++ b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts @@ -449,4 +449,4 @@ if (environment.cpq) { }), ], }) -export class SpartacusFeaturesModule { } +export class SpartacusFeaturesModule {}