Skip to content

Commit

Permalink
feat: recurring orders listing in my account (initial, service but no…
Browse files Browse the repository at this point in the history
… state management)
  • Loading branch information
shauke committed Aug 13, 2024
1 parent c950184 commit 17e950c
Show file tree
Hide file tree
Showing 20 changed files with 295 additions and 1 deletion.
9 changes: 8 additions & 1 deletion src/app/core/facades/account.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { PasswordReminderUpdate } from 'ish-core/models/password-reminder-update
import { PasswordReminder } from 'ish-core/models/password-reminder/password-reminder.model';
import { PaymentInstrument } from 'ish-core/models/payment-instrument/payment-instrument.model';
import { User } from 'ish-core/models/user/user.model';
import { RecurringOrdersService } from 'ish-core/services/recurring-orders/recurring-orders.service';
import { MessagesPayloadType } from 'ish-core/store/core/messages';
import { getServerConfigParameter } from 'ish-core/store/core/server-config';
import {
Expand Down Expand Up @@ -84,7 +85,7 @@ export class AccountFacade {
*/
private internalUserError$ = new Subject<HttpError>();

constructor(private store: Store) {
constructor(private store: Store, private recurringOrdersService: RecurringOrdersService) {
store.pipe(select(getUserError)).subscribe(this.internalUserError$);
}

Expand Down Expand Up @@ -191,6 +192,12 @@ export class AccountFacade {
ordersLoading$ = this.store.pipe(select(getOrdersLoading));
ordersError$ = this.store.pipe(select(getOrdersError));

// RECURRING ORDERS

recurringOrders$() {
return this.recurringOrdersService.getRecurringOrders();
}

// PAYMENT

private eligiblePaymentMethods$ = this.store.pipe(select(getUserPaymentMethods));
Expand Down
20 changes: 20 additions & 0 deletions src/app/core/models/recurring-order/recurring-order.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Link } from 'ish-core/models/link/link.model';
import { PriceData } from 'ish-core/models/price/price.interface';
import { UserData } from 'ish-core/models/user/user.interface';

export interface RecurringOrderData {
number: string;
creationDate: number;
startDate: number;
nextOrderDate: number;
totalNet: PriceData;
totalGross: PriceData;
interval: string;
repetitions: number;
active: boolean;
expired: boolean;
itemCount: number;
priceType: string;
buyer: UserData;
link: Link;
}
28 changes: 28 additions & 0 deletions src/app/core/models/recurring-order/recurring-order.mapper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { TestBed } from '@angular/core/testing';

import { RecurringOrderData } from './recurring-order.interface';
import { RecurringOrderMapper } from './recurring-order.mapper';

describe('Recurring Order Mapper', () => {
let recurringOrderMapper: RecurringOrderMapper;

beforeEach(() => {
recurringOrderMapper = TestBed.inject(RecurringOrderMapper);
});

describe('fromData', () => {
it('should throw when input is falsy', () => {
expect(() => recurringOrderMapper.fromData(undefined)).toThrow();

Check failure on line 15 in src/app/core/models/recurring-order/recurring-order.mapper.spec.ts

View workflow job for this annotation

GitHub Actions / Quality

Property 'fromData' does not exist on type 'RecurringOrderMapper'. Did you mean to access the static member 'RecurringOrderMapper.fromData' instead?
});

it('should map incoming data to model data', () => {
const data: RecurringOrderData = {
incomingField: 'test',

Check failure on line 20 in src/app/core/models/recurring-order/recurring-order.mapper.spec.ts

View workflow job for this annotation

GitHub Actions / Quality

Type '{ incomingField: string; otherField: boolean; }' is not assignable to type 'RecurringOrderData'.
otherField: false,
};
const mapped = recurringOrderMapper.fromData(data);

Check failure on line 23 in src/app/core/models/recurring-order/recurring-order.mapper.spec.ts

View workflow job for this annotation

GitHub Actions / Quality

Property 'fromData' does not exist on type 'RecurringOrderMapper'. Did you mean to access the static member 'RecurringOrderMapper.fromData' instead?
expect(mapped).toHaveProperty('id', 'test');
expect(mapped).not.toHaveProperty('otherField');
});
});
});
33 changes: 33 additions & 0 deletions src/app/core/models/recurring-order/recurring-order.mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';

import { PriceMapper } from 'ish-core/models/price/price.mapper';

import { RecurringOrderData } from './recurring-order.interface';
import { RecurringOrder } from './recurring-order.model';

@Injectable({ providedIn: 'root' })
export class RecurringOrderMapper {
static fromData(recurringOrderData: RecurringOrderData[]): RecurringOrder[] {
console.log('RECURRING ORDER DATA MAPPER', recurringOrderData);

Check warning on line 11 in src/app/core/models/recurring-order/recurring-order.mapper.ts

View workflow job for this annotation

GitHub Actions / GitBash

Unexpected console statement

Check warning on line 11 in src/app/core/models/recurring-order/recurring-order.mapper.ts

View workflow job for this annotation

GitHub Actions / Powershell

Unexpected console statement

Check warning on line 11 in src/app/core/models/recurring-order/recurring-order.mapper.ts

View workflow job for this annotation

GitHub Actions / CommandLine

Unexpected console statement
if (recurringOrderData.length) {
return recurringOrderData.map(data => ({
id: data.link.title,
number: data.number,
creationDate: data.creationDate,
startDate: data.startDate,
nextOrderDate: data.nextOrderDate,
interval: data.interval,
repetitions: data.repetitions,
active: data.active,
expired: data.expired,
itemCount: data.itemCount,
priceType: data.priceType,
totalNet: PriceMapper.fromData(data.totalNet),
totalGross: PriceMapper.fromData(data.totalGross),
buyer: data.buyer,
}));
} else {
return [];
}
}
}
19 changes: 19 additions & 0 deletions src/app/core/models/recurring-order/recurring-order.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Price } from 'ish-core/models/price/price.model';
import { User } from 'ish-core/models/user/user.model';

export interface RecurringOrder {
id: string;
number: string;
creationDate: number;
startDate: number;
nextOrderDate: number;
interval: string;
repetitions: number;
active: boolean;
expired: boolean;
itemCount: number;
priceType: string;
totalNet: Price;
totalGross: Price;
buyer: User;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TestBed } from '@angular/core/testing';
import { instance, mock } from 'ts-mockito';

import { ApiService } from 'ish-core/services/api/api.service';

import { RecurringOrdersService } from './recurring-orders.service';

describe('Recurring Orders Service', () => {
let apiServiceMock: ApiService;
let recurringOrdersService: RecurringOrdersService;

beforeEach(() => {
apiServiceMock = mock(ApiService);
TestBed.configureTestingModule({
providers: [{ provide: ApiService, useFactory: () => instance(apiServiceMock) }],
});
recurringOrdersService = TestBed.inject(RecurringOrdersService);
});

it('should be created', () => {
expect(recurringOrdersService).toBeTruthy();
});
});
32 changes: 32 additions & 0 deletions src/app/core/services/recurring-orders/recurring-orders.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { iif, map, switchMap, take } from 'rxjs';

import { RecurringOrderData } from 'ish-core/models/recurring-order/recurring-order.interface';
import { RecurringOrderMapper } from 'ish-core/models/recurring-order/recurring-order.mapper';
import { ApiService, unpackEnvelope } from 'ish-core/services/api/api.service';
import { getLoggedInCustomer } from 'ish-core/store/customer/user';
import { whenTruthy } from 'ish-core/utils/operators';
import { encodeResourceID } from 'ish-core/utils/url-resource-ids';

@Injectable({ providedIn: 'root' })
export class RecurringOrdersService {
constructor(private apiService: ApiService, private store: Store) {}

private currentCustomer$ = this.store.pipe(select(getLoggedInCustomer), whenTruthy(), take(1));

getRecurringOrders() {
return this.currentCustomer$.pipe(
switchMap(customer =>
iif(
() => customer.isBusinessCustomer,
this.apiService.b2bUserEndpoint().get(`recurringorders`),
this.apiService.get(`privatecustomers/${encodeResourceID(customer.customerNo)}/recurringorders`)
).pipe(
unpackEnvelope<RecurringOrderData>(),
map(data => RecurringOrderMapper.fromData(data))
)
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<h1>{{ 'account.recurring_orders.heading' | translate }}</h1>

<ng-container *ngIf="recurringOrders$ | async as recurringOrders">
<ish-recurring-order-list [recurringOrders]="recurringOrders" />
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AccountRecurringOrdersPageComponent } from './account-recurring-orders-page.component';

describe('Account Recurring Orders Page Component', () => {
let component: AccountRecurringOrdersPageComponent;
let fixture: ComponentFixture<AccountRecurringOrdersPageComponent>;
let element: HTMLElement;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AccountRecurringOrdersPageComponent],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(AccountRecurringOrdersPageComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
});

it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => fixture.detectChanges()).not.toThrow();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

import { AccountFacade } from 'ish-core/facades/account.facade';
import { RecurringOrder } from 'ish-core/models/recurring-order/recurring-order.model';

@Component({
selector: 'ish-account-recurring-orders-page',
templateUrl: './account-recurring-orders-page.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AccountRecurringOrdersPageComponent implements OnInit {
recurringOrders$: Observable<RecurringOrder[]>;

constructor(private accountFacade: AccountFacade) {}

ngOnInit() {
this.recurringOrders$ = this.accountFacade.recurringOrders$();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { SharedModule } from 'ish-shared/shared.module';

import { AccountRecurringOrdersPageComponent } from './account-recurring-orders-page.component';
import { RecurringOrderListComponent } from './recurring-order-list/recurring-order-list.component';

const accountRecurringOrdersPageRoutes: Routes = [{ path: '', component: AccountRecurringOrdersPageComponent }];

@NgModule({
imports: [RouterModule.forChild(accountRecurringOrdersPageRoutes), SharedModule],
declarations: [AccountRecurringOrdersPageComponent, RecurringOrderListComponent],
})
export class AccountRecurringOrdersPageModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<pre>{{ recurringOrders | json }}</pre>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { RecurringOrderListComponent } from './recurring-order-list.component';

describe('Recurring Order List Component', () => {
let component: RecurringOrderListComponent;
let fixture: ComponentFixture<RecurringOrderListComponent>;
let element: HTMLElement;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RecurringOrderListComponent],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(RecurringOrderListComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
});

it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => fixture.detectChanges()).not.toThrow();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

import { RecurringOrder } from 'ish-core/models/recurring-order/recurring-order.model';

@Component({
selector: 'ish-recurring-order-list',
templateUrl: './recurring-order-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RecurringOrderListComponent {
@Input() recurringOrders: RecurringOrder[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ export const navigationItems: NavigationItem[] = [
routerLink: '/account/orders',
notRole: ['APP_B2B_CXML_USER', 'APP_B2B_OCI_USER'],
},
{
id: 'recurring-orders',
localizationKey: 'account.recurring_orders.navigation.link',
routerLink: '/account/recurring-orders',
serverSetting: 'recurringOrder.enabled',
},
{
id: 'wishlists',
localizationKey: 'account.wishlists.link',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export const navigationItems: NavigationItem[] = [
routerLink: '/account/orders',
notRole: ['APP_B2B_CXML_USER', 'APP_B2B_OCI_USER'],
},
{
id: 'recurring-orders',
localizationKey: 'account.recurring_orders.navigation.link',
routerLink: '/account/recurring-orders',
serverSetting: 'recurringOrder.enabled',
},
{
id: 'requisitions',
localizationKey: 'account.requisitions.requisitions',
Expand Down
7 changes: 7 additions & 0 deletions src/app/pages/account/account-page.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ const accountPageRoutes: Routes = [
data: { permission: 'APP_B2B_PURCHASE' },
loadChildren: () => import('requisition-management').then(m => m.RequisitionManagementRoutingModule),
},
{
path: 'recurring-orders',
loadChildren: () =>
import('../account-recurring-orders/account-recurring-orders-page.module').then(
m => m.AccountRecurringOrdersPageModule
),
},
],
},
];
Expand Down
2 changes: 2 additions & 0 deletions src/assets/i18n/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@
"account.quotes.widget.responded.label": "Beantwortete Preisangebote",
"account.quotes.widget.submitted.label": "Gesendete Anfragen",
"account.quotes.widget.view_all_quotes.link": "Alle Preisangebote anzeigen",
"account.recurring_orders.heading": "Recurring orders",
"account.recurring_orders.navigation.link": "Recurring orders",
"account.register.address.headding": "Adresse",
"account.register.address.message": "Um bei der Bestellung Zeit zu sparen, geben Sie unten Ihre Hauptadressen für Rechnung und Lieferung an. Die Informationen werden gespeichert, sodass Sie sie bei zukünftigen Bestellungen nicht mehr jedes Mal erneut eingeben müssen.",
"account.register.company_information.heading": "Firmeninformationen",
Expand Down
2 changes: 2 additions & 0 deletions src/assets/i18n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@
"account.quotes.widget.responded.label": "Responded quotes",
"account.quotes.widget.submitted.label": "Sent requests",
"account.quotes.widget.view_all_quotes.link": "View all quotes",
"account.recurring_orders.heading": "Recurring orders",
"account.recurring_orders.navigation.link": "Recurring orders",
"account.register.address.headding": "Address",
"account.register.address.message": "To save time when placing an order, please provide your primary invoice or shipping address below. We will store this information so you won’t have to enter it again.",
"account.register.company_information.heading": "Company information",
Expand Down
2 changes: 2 additions & 0 deletions src/assets/i18n/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@
"account.quotes.widget.responded.label": "Devis répondus",
"account.quotes.widget.submitted.label": "Requêtes envoyées",
"account.quotes.widget.view_all_quotes.link": "Afficher tous les devis",
"account.recurring_orders.heading": "Recurring orders",
"account.recurring_orders.navigation.link": "Recurring orders",
"account.register.address.headding": "Adresse",
"account.register.address.message": "Pour gagner du temps lors de la commande, veuillez indiquer votre adresse de facturation ou de livraison ci-dessous. Nous conserverons ces informations afin que vous n’ayez pas à les saisir à nouveau.",
"account.register.company_information.heading": "Informations sur la société",
Expand Down

0 comments on commit 17e950c

Please sign in to comment.