diff --git a/src/app/pages/checkout-shipping/checkout-shipping/checkout-shipping.component.html b/src/app/pages/checkout-shipping/checkout-shipping/checkout-shipping.component.html
index 448c1befe3..12a6dde70b 100644
--- a/src/app/pages/checkout-shipping/checkout-shipping/checkout-shipping.component.html
+++ b/src/app/pages/checkout-shipping/checkout-shipping/checkout-shipping.component.html
@@ -7,59 +7,65 @@
diff --git a/src/app/shared/components/basket/basket-buyer/basket-buyer.component.spec.ts b/src/app/shared/components/basket/basket-buyer/basket-buyer.component.spec.ts
index 54f7724fc2..b257f47bd5 100644
--- a/src/app/shared/components/basket/basket-buyer/basket-buyer.component.spec.ts
+++ b/src/app/shared/components/basket/basket-buyer/basket-buyer.component.spec.ts
@@ -1,5 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { TranslateModule } from '@ngx-translate/core';
+import { MockComponent } from 'ng-mocks';
import { of } from 'rxjs';
import { instance, mock, when } from 'ts-mockito';
@@ -20,8 +23,8 @@ describe('Basket Buyer Component', () => {
accountFacade = mock(AccountFacade);
await TestBed.configureTestingModule({
- imports: [TranslateModule.forRoot()],
- declarations: [BasketBuyerComponent],
+ imports: [RouterTestingModule, TranslateModule.forRoot()],
+ declarations: [BasketBuyerComponent, MockComponent(FaIconComponent)],
providers: [{ provide: AccountFacade, useFactory: () => instance(accountFacade) }],
}).compileComponents();
});
@@ -50,4 +53,10 @@ describe('Basket Buyer Component', () => {
expect(element.querySelector('[data-testing-id="taxationID"]')).toBeTruthy();
expect(element.querySelector('[data-testing-id="taxationID"]').innerHTML).toContain('1234');
});
+
+ it('should display the order reference id of the customer', () => {
+ fixture.detectChanges();
+ expect(element.querySelector('[data-testing-id="orderReferenceID"]')).toBeTruthy();
+ expect(element.querySelector('[data-testing-id="orderReferenceID"]').innerHTML).toContain('111-222-333');
+ });
});
diff --git a/src/app/shared/components/basket/basket-buyer/basket-buyer.component.ts b/src/app/shared/components/basket/basket-buyer/basket-buyer.component.ts
index feaefa257f..931b9e4e99 100644
--- a/src/app/shared/components/basket/basket-buyer/basket-buyer.component.ts
+++ b/src/app/shared/components/basket/basket-buyer/basket-buyer.component.ts
@@ -16,11 +16,16 @@ import { whenTruthy } from 'ish-core/utils/operators';
})
export class BasketBuyerComponent implements OnInit, OnDestroy {
@Input() object: Basket | Order;
+ /**
+ * Router link for editing the order reference id. If a routerLink is given a link is displayed to route to an edit page.
+ */
+ @Input() editRouterLink?: string;
customer$: Observable
;
user$: Observable;
taxationID: string;
+ orderReferenceID: string;
userName: string;
private destroy$ = new Subject();
@@ -28,8 +33,10 @@ export class BasketBuyerComponent implements OnInit, OnDestroy {
constructor(private accountFacade: AccountFacade) {}
ngOnInit() {
+ this.taxationID = this.getAttributeValue('taxationID');
+ this.orderReferenceID = this.getAttributeValue('orderReferenceID');
+
// default values for anonymous users
- this.taxationID = this.object.attributes?.find(attr => attr.name === 'taxationID')?.value as string;
this.userName = `${this.object.invoiceToAddress?.firstName} ${this.object.invoiceToAddress?.lastName}`;
this.customer$ = this.accountFacade.customer$;
@@ -45,6 +52,10 @@ export class BasketBuyerComponent implements OnInit, OnDestroy {
});
}
+ private getAttributeValue(attributeName: string): string {
+ return this.object.attributes?.find(attr => attr.name === attributeName)?.value as string;
+ }
+
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
diff --git a/src/app/shared/components/basket/basket-order-reference/basket-order-reference.component.html b/src/app/shared/components/basket/basket-order-reference/basket-order-reference.component.html
new file mode 100644
index 0000000000..10013bf51b
--- /dev/null
+++ b/src/app/shared/components/basket/basket-order-reference/basket-order-reference.component.html
@@ -0,0 +1,19 @@
+
+
{{ 'checkout.orderReferenceId.title' | translate }}
+
+
+
diff --git a/src/app/shared/components/basket/basket-order-reference/basket-order-reference.component.spec.ts b/src/app/shared/components/basket/basket-order-reference/basket-order-reference.component.spec.ts
new file mode 100644
index 0000000000..8081d59984
--- /dev/null
+++ b/src/app/shared/components/basket/basket-order-reference/basket-order-reference.component.spec.ts
@@ -0,0 +1,84 @@
+import { SimpleChange } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormControl, FormGroup } from '@angular/forms';
+import { TranslateModule } from '@ngx-translate/core';
+import { MockComponent } from 'ng-mocks';
+import { anything, instance, mock, verify, when } from 'ts-mockito';
+
+import { CheckoutFacade } from 'ish-core/facades/checkout.facade';
+import { Basket } from 'ish-core/models/basket/basket.model';
+import { SuccessMessageComponent } from 'ish-shared/components/common/success-message/success-message.component';
+import { FormlyTestingModule } from 'ish-shared/formly/dev/testing/formly-testing.module';
+import { SpecialValidators } from 'ish-shared/forms/validators/special-validators';
+
+import { BasketOrderReferenceComponent } from './basket-order-reference.component';
+
+describe('Basket Order Reference Component', () => {
+ let component: BasketOrderReferenceComponent;
+ let fixture: ComponentFixture;
+ let element: HTMLElement;
+ let checkoutFacade: CheckoutFacade;
+
+ beforeEach(async () => {
+ checkoutFacade = mock(CheckoutFacade);
+
+ await TestBed.configureTestingModule({
+ imports: [FormlyTestingModule, TranslateModule.forRoot()],
+ declarations: [BasketOrderReferenceComponent, MockComponent(SuccessMessageComponent)],
+ providers: [{ provide: CheckoutFacade, useFactory: () => instance(checkoutFacade) }],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BasketOrderReferenceComponent);
+ component = fixture.componentInstance;
+ element = fixture.nativeElement;
+
+ when(checkoutFacade.setBasketCustomAttribute(anything())).thenReturn();
+ });
+
+ it('should be created', () => {
+ expect(component).toBeTruthy();
+ expect(element).toBeTruthy();
+ expect(() => fixture.detectChanges()).not.toThrow();
+ });
+
+ it('should display order reference id input fields on form', () => {
+ fixture.detectChanges();
+
+ expect(element.innerHTML).toContain('orderReferenceId');
+ });
+
+ it('should read the order reference id from the basket', () => {
+ component.basket = {
+ attributes: [{ name: 'orderReferenceID', value: '4711' }],
+ } as Basket;
+
+ fixture.detectChanges();
+ component.ngOnChanges({ basket: new SimpleChange(undefined, component.basket, false) });
+
+ expect(component.model.orderReferenceId).toBe('4711');
+ });
+
+ it('should emit form when a valid form is submitted', () => {
+ fixture.detectChanges();
+
+ component.form = new FormGroup({
+ orderReferenceId: new FormControl('xxx', SpecialValidators.noSpecialChars),
+ });
+ component.submitForm();
+
+ verify(checkoutFacade.setBasketCustomAttribute(anything())).once();
+ });
+
+ it('should not emit form when an invalid form is submitted', () => {
+ fixture.detectChanges();
+
+ component.form = new FormGroup({
+ orderReferenceId: new FormControl('%%%', SpecialValidators.noSpecialChars),
+ });
+ component.submitForm();
+
+ verify(checkoutFacade.setBasketCustomAttribute(anything())).never();
+ });
+});
diff --git a/src/app/shared/components/basket/basket-order-reference/basket-order-reference.component.ts b/src/app/shared/components/basket/basket-order-reference/basket-order-reference.component.ts
new file mode 100644
index 0000000000..1abb6b432f
--- /dev/null
+++ b/src/app/shared/components/basket/basket-order-reference/basket-order-reference.component.ts
@@ -0,0 +1,97 @@
+import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ Input,
+ OnChanges,
+ OnInit,
+ SimpleChange,
+ SimpleChanges,
+} from '@angular/core';
+import { FormGroup } from '@angular/forms';
+import { FormlyFieldConfig } from '@ngx-formly/core';
+
+import { CheckoutFacade } from 'ish-core/facades/checkout.facade';
+import { Basket } from 'ish-core/models/basket/basket.model';
+import { SpecialValidators } from 'ish-shared/forms/validators/special-validators';
+
+@Component({
+ selector: 'ish-basket-order-reference',
+ templateUrl: './basket-order-reference.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class BasketOrderReferenceComponent implements OnInit, OnChanges {
+ @Input() basket: Basket;
+
+ form = new FormGroup({});
+ model: { orderReferenceId: string } = { orderReferenceId: '' };
+ fields: FormlyFieldConfig[];
+
+ showSuccessMessage = false;
+
+ constructor(private checkoutFacade: CheckoutFacade, private cd: ChangeDetectorRef) {}
+
+ ngOnInit() {
+ this.fields = [
+ {
+ key: 'orderReferenceId',
+ type: 'ish-text-input-field',
+ templateOptions: {
+ postWrappers: ['description'],
+ label: 'checkout.orderReferenceId.label',
+ maxLength: 35,
+ customDescription: {
+ class: 'input-help',
+ key: 'checkout.orderReferenceId.note',
+ },
+ },
+ validators: {
+ validation: [SpecialValidators.noSpecialChars],
+ },
+ validation: {
+ messages: {
+ noSpecialChars: 'account.name.error.forbidden.chars',
+ },
+ },
+ },
+ ];
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (this.basket) {
+ this.successMessage(changes.basket);
+ this.model.orderReferenceId = this.getOrderReferenceId(this.basket);
+ }
+ }
+
+ private getOrderReferenceId(basket: Basket): string {
+ return basket?.attributes?.find(attr => attr.name === 'orderReferenceID')?.value as string;
+ }
+
+ private successMessage(basketChange: SimpleChange) {
+ if (
+ this.getOrderReferenceId(basketChange?.previousValue) !== this.getOrderReferenceId(basketChange?.currentValue) &&
+ !basketChange?.firstChange
+ ) {
+ this.showSuccessMessage = true;
+ setTimeout(() => {
+ this.showSuccessMessage = false;
+ this.cd.markForCheck();
+ }, 5000);
+ }
+ }
+
+ get disabled() {
+ return this.form.invalid || (!this.getOrderReferenceId(this.basket) && !this.form.get('orderReferenceId').value);
+ }
+
+ submitForm() {
+ if (this.disabled) {
+ return;
+ }
+ this.checkoutFacade.setBasketCustomAttribute({
+ name: 'orderReferenceID',
+ value: this.form.get('orderReferenceId').value,
+ });
+ }
+}
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index d0cc7c2717..befef18966 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -56,6 +56,7 @@ import { BasketBuyerComponent } from './components/basket/basket-buyer/basket-bu
import { BasketCostSummaryComponent } from './components/basket/basket-cost-summary/basket-cost-summary.component';
import { BasketInfoComponent } from './components/basket/basket-info/basket-info.component';
import { BasketItemsSummaryComponent } from './components/basket/basket-items-summary/basket-items-summary.component';
+import { BasketOrderReferenceComponent } from './components/basket/basket-order-reference/basket-order-reference.component';
import { BasketPromotionCodeComponent } from './components/basket/basket-promotion-code/basket-promotion-code.component';
import { BasketPromotionComponent } from './components/basket/basket-promotion/basket-promotion.component';
import { BasketValidationItemsComponent } from './components/basket/basket-validation-items/basket-validation-items.component';
@@ -216,6 +217,7 @@ const exportedComponents = [
BasketInfoComponent,
BasketInvoiceAddressWidgetComponent,
BasketItemsSummaryComponent,
+ BasketOrderReferenceComponent,
BasketPromotionCodeComponent,
BasketPromotionComponent,
BasketShippingAddressWidgetComponent,
diff --git a/src/assets/i18n/de_DE.json b/src/assets/i18n/de_DE.json
index acffdc52f7..a5cd98cd3d 100644
--- a/src/assets/i18n/de_DE.json
+++ b/src/assets/i18n/de_DE.json
@@ -611,6 +611,11 @@
"checkout.order.number.label": "Ihre Bestellnummer ist:",
"checkout.order.shipping.label": "Versand",
"checkout.order.total_cost.label": "Gesamtkosten",
+ "checkout.orderReferenceId.apply.button.label": "Übernehmen",
+ "checkout.orderReferenceId.label": "Bestellungs-ID",
+ "checkout.orderReferenceId.note": "Sie können eine ID für Ihre eigene Buchhaltung eingeben. Diese wird auf der Rechnung und dem Packzettel erscheinen.",
+ "checkout.orderReferenceId.success.message": "Ihre Bestellungs-ID wurde übernommen.",
+ "checkout.orderReferenceId.title": "Bestellungs-ID eingeben",
"checkout.order_details.heading": "Bestellübersicht",
"checkout.order_review.heading.text": "Prüfen Sie die Details Ihrer Bestellung und nehmen Sie, falls notwendig, noch Änderungen vor. Klicken Sie auf \"Bestellung absenden\", um den Bestellvorgang abzuschließen.",
"checkout.order_review.heading.title": "Ihre Bestelldaten prüfen",
@@ -693,6 +698,7 @@
"checkout.widget.billing-address.heading": "Rechnungsadresse",
"checkout.widget.buyer.TaxationID": "Steuernummer:",
"checkout.widget.buyer.heading": "Einkäufer",
+ "checkout.widget.buyer.orderReferenceId": "Bestellungs-ID",
"checkout.widget.payment_method.heading": "Zahlungsart",
"checkout.widget.promotion.discount": "Rabatt",
"checkout.widget.return_to_cart.link": "Zurück zum Warenkorb",
diff --git a/src/assets/i18n/en_US.json b/src/assets/i18n/en_US.json
index 059245a7fc..6b9e626aaf 100644
--- a/src/assets/i18n/en_US.json
+++ b/src/assets/i18n/en_US.json
@@ -613,6 +613,11 @@
"checkout.order.number.label": "Your order number is:",
"checkout.order.shipping.label": "Shipping",
"checkout.order.total_cost.label": "Total Cost",
+ "checkout.orderReferenceId.apply.button.label": "Apply",
+ "checkout.orderReferenceId.label": "Order Reference ID",
+ "checkout.orderReferenceId.note": "You can enter an ID for your own book keeping. It will appear on your invoice and packing slip.",
+ "checkout.orderReferenceId.success.message": "Your order reference ID has been applied.",
+ "checkout.orderReferenceId.title": "Enter an order reference ID",
"checkout.order_details.heading": "Order Summary",
"checkout.order_review.heading.text": "Review the details of your order below and make any changes if needed. Click \"Submit Order\" to complete your purchase.",
"checkout.order_review.heading.title": "Review Your Order Information",
@@ -695,6 +700,7 @@
"checkout.widget.billing-address.heading": "Invoice Address",
"checkout.widget.buyer.TaxationID": "Taxation ID:",
"checkout.widget.buyer.heading": "Buyer",
+ "checkout.widget.buyer.orderReferenceId": "Order Reference ID",
"checkout.widget.payment_method.heading": "Payment Method",
"checkout.widget.promotion.discount": "Discount",
"checkout.widget.return_to_cart.link": "Return to Cart",
diff --git a/src/assets/i18n/fr_FR.json b/src/assets/i18n/fr_FR.json
index 9adbe59248..2666ea144b 100644
--- a/src/assets/i18n/fr_FR.json
+++ b/src/assets/i18n/fr_FR.json
@@ -613,6 +613,11 @@
"checkout.order.number.label": "Votre numéro de commande est :",
"checkout.order.shipping.label": "Livraison",
"checkout.order.total_cost.label": "Coût total",
+ "checkout.orderReferenceId.apply.button.label": "Appliquer",
+ "checkout.orderReferenceId.label": "ID de référence de la commande",
+ "checkout.orderReferenceId.note": "Vous pouvez entrer un ID pour votre propre comptabilisation. Il apparaîtra sur votre facture et sur votre bordereau d’expédition.",
+ "checkout.orderReferenceId.success.message": "L'ID de référence de votre commande a été appliqué.",
+ "checkout.orderReferenceId.title": "Entrer un ID de référence de commande",
"checkout.order_details.heading": "Récapitulatif de la commande",
"checkout.order_review.heading.text": "Vérifiez les détails de votre commande ci-dessous et faites des modifications si nécessaire. Cliquez « Soumettre la commande » pour effectuer l’achat.",
"checkout.order_review.heading.title": "Vérifier vos informations de commande",
@@ -695,6 +700,7 @@
"checkout.widget.billing-address.heading": "Adresse de facturation",
"checkout.widget.buyer.TaxationID": "ID de taxation:",
"checkout.widget.buyer.heading": "Acheteur",
+ "checkout.widget.buyer.orderReferenceId": "ID de référence de la commande",
"checkout.widget.payment_method.heading": "Mode de paiement",
"checkout.widget.promotion.discount": "Réduction",
"checkout.widget.return_to_cart.link": "Retour au panier",