Skip to content

Commit

Permalink
feat: extend order list filter by buyer selection (#1697)
Browse files Browse the repository at this point in the history
* requires ICM 12.1.0 with new permission APP_B2B_MANAGE_ORDERS
  • Loading branch information
SGrueber authored Aug 19, 2024
1 parent fb5a0d6 commit 28d9846
Show file tree
Hide file tree
Showing 28 changed files with 340 additions and 71 deletions.
3 changes: 3 additions & 0 deletions docs/guides/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ For the migration of customer projects it needs to be checked whether a `customD
Additionally it needs to be checked if the `textarea-description` wrapper is configured anywhere else then the default assignment to the `ish-textarea-field`.
If so these wrapper configurations need to be replaced with `maxlength-description`.

B2B users with the permission `APP_B2B_MANAGE_ORDERS` (only available for admin users in ICM 12.1.0 and higher) see now the orders of all users of the company on the My Account order history page.
They can filter the orders by buyer in order to see only e.g. the own orders again.

## From 5.0 to 5.1

The OrderListComponent is strictly presentational, components using it have to supply the data.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<ng-container *ngIf="buyers$ | async as buyers">
<select [formControl]="control" class="form-control" data-testing-id="buyers-select-box">
<option value="all">
{{ 'account.order_history.filter.all_buyers' | translate }}
</option>
<ng-container *ngIf="buyers.length">
<ng-container *ngFor="let buyer of buyers">
<option [value]="buyer.businessPartnerNo">{{ buyer.firstName }} {{ buyer.lastName }}</option>
</ng-container>
</ng-container>
</select>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { of } from 'rxjs';
import { instance, mock, when } from 'ts-mockito';

import { OrganizationManagementFacade } from '../../facades/organization-management.facade';

import { BuyersSelectComponent } from './buyers-select.component';

describe('Buyers Select Component', () => {
let component: BuyersSelectComponent;
let fixture: ComponentFixture<BuyersSelectComponent>;
let element: HTMLElement;

beforeEach(async () => {
const organizationManagementFacade = mock(OrganizationManagementFacade);

when(organizationManagementFacade.users$).thenReturn(of([]));
await TestBed.configureTestingModule({
imports: [ReactiveFormsModule, TranslateModule.forRoot()],
declarations: [BuyersSelectComponent],
providers: [{ provide: OrganizationManagementFacade, useFactory: () => instance(organizationManagementFacade) }],
}).compileComponents();
});

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

component.control = new FormControl('test');
});

it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => fixture.detectChanges()).not.toThrow();
});

it('should display the buyers select box after creation', () => {
fixture.detectChanges();

expect(element.querySelector('[data-testing-id=buyers-select-box]')).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core';
import { FormControl } from '@angular/forms';

import { GenerateLazyComponent } from 'ish-core/utils/module-loader/generate-lazy-component.decorator';

import { OrganizationManagementFacade } from '../../facades/organization-management.facade';

@Component({
selector: 'ish-buyers-select',
templateUrl: './buyers-select.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
@GenerateLazyComponent()
export class BuyersSelectComponent implements OnInit {
@Input() control: FormControl;

private organizationManagementFacade = inject(OrganizationManagementFacade);

buyers$ = this.organizationManagementFacade.users$;

ngOnInit() {
this.organizationManagementFacade.fetchUsers();
}
}
1 change: 1 addition & 0 deletions projects/organization-management/src/app/exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export { OrganizationManagementBreadcrumbService } from '../services/organizatio

export { UserBudget } from '../models/user-budget/user-budget.model';
export { LazyBudgetWidgetComponent } from './lazy-budget-widget/lazy-budget-widget.component';
export { LazyBuyersSelectComponent } from './lazy-buyers-select/lazy-buyers-select.component';
export { LazyCostCenterWidgetComponent } from './lazy-cost-center-widget/lazy-cost-center-widget.component';
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { NgModule } from '@angular/core';

import { LazyBudgetWidgetComponent } from './lazy-budget-widget/lazy-budget-widget.component';
import { LazyBuyersSelectComponent } from './lazy-buyers-select/lazy-buyers-select.component';
import { LazyCostCenterWidgetComponent } from './lazy-cost-center-widget/lazy-cost-center-widget.component';

@NgModule({
imports: [],
declarations: [LazyBudgetWidgetComponent, LazyCostCenterWidgetComponent],
exports: [LazyBudgetWidgetComponent, LazyCostCenterWidgetComponent],
declarations: [LazyBudgetWidgetComponent, LazyBuyersSelectComponent, LazyCostCenterWidgetComponent],
exports: [LazyBudgetWidgetComponent, LazyBuyersSelectComponent, LazyCostCenterWidgetComponent],
})
export class OrganizationManagementExportsModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ export class OrganizationManagementFacade {
return this.store.pipe(select(getCurrentUserBudget));
}

fetchUsers() {
this.store.dispatch(loadUsers());
}

addUser(user: B2bUser) {
this.store.dispatch(
addUser({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ describe('B2b User Mapper', () => {
const userData = {
firstName: 'Patricia',
lastName: 'Miller',
businessPartnerNo: 'pmiller',
email: 'test@test.intershop.de',
preferredInvoiceToAddress: BasketMockData.getAddress(),
preferredShipToAddress: { urn: 'urn:1234' } as Address,
preferredPaymentInstrument: { id: '1234' } as PaymentInstrument,
Expand All @@ -23,9 +25,9 @@ describe('B2b User Mapper', () => {
{
"active": true,
"birthday": undefined,
"businessPartnerNo": undefined,
"businessPartnerNo": "pmiller",
"department": undefined,
"email": undefined,
"email": "test@test.intershop.de",
"fax": undefined,
"firstName": "Patricia",
"lastName": "Miller",
Expand Down Expand Up @@ -55,6 +57,7 @@ describe('B2b User Mapper', () => {
},
{ name: 'firstName', value: 'Patricia' },
{ name: 'lastName', value: 'Miller' },
{ name: 'businessPartnerNo', value: 'pmiller' },
{ name: 'active', value: true },
{ name: 'budgetPeriod', type: 'String', value: 'monthly' },
{ name: 'orderSpentLimit', type: 'MoneyRO', value: { currency: 'USD', value: 500 } },
Expand All @@ -70,6 +73,7 @@ describe('B2b User Mapper', () => {
[
{
"active": true,
"businessPartnerNo": "pmiller",
"firstName": "Patricia",
"lastName": "Miller",
"login": "pmiller@test.intershop.de",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class B2bUserMapper {
login: e.login,
firstName: AttributeHelper.getAttributeValueByAttributeName(e.attributes, 'firstName'),
lastName: AttributeHelper.getAttributeValueByAttributeName(e.attributes, 'lastName'),
businessPartnerNo: AttributeHelper.getAttributeValueByAttributeName(e.attributes, 'businessPartnerNo'),
roleIDs: AttributeHelper.getAttributeValueByAttributeName(e.attributes, 'roleIDs'),
active: AttributeHelper.getAttributeValueByAttributeName(e.attributes, 'active'),
userBudget: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { SharedModule } from 'ish-shared/shared.module';

import { BudgetWidgetComponent } from './components/budget-widget/budget-widget.component';
import { BuyersSelectComponent } from './components/buyers-select/buyers-select.component';
import { CostCenterBudgetComponent } from './components/cost-center-budget/cost-center-budget.component';
import { CostCenterBuyerEditDialogComponent } from './components/cost-center-buyer-edit-dialog/cost-center-buyer-edit-dialog.component';
import { CostCenterFormComponent } from './components/cost-center-form/cost-center-form.component';
Expand All @@ -15,6 +16,7 @@ import { UserRolesSelectionComponent } from './components/user-roles-selection/u
import { OrganizationManagementStoreModule } from './store/organization-management-store.module';

const exportedComponents = [
BuyersSelectComponent,
CostCenterBudgetComponent,
CostCenterBuyerEditDialogComponent,
CostCenterFormComponent,
Expand Down
6 changes: 5 additions & 1 deletion src/app/core/facades/account.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
loadAddresses,
updateCustomerAddress,
} from 'ish-core/store/customer/addresses';
import { getUserRoles } from 'ish-core/store/customer/authorization';
import { getUserPermissions, getUserRoles } from 'ish-core/store/customer/authorization';
import {
firstGDPRDataRequest,
getDataRequestError,
Expand Down Expand Up @@ -96,6 +96,10 @@ export class AccountFacade {
userLoading$ = this.store.pipe(select(getUserLoading));
isLoggedIn$ = this.store.pipe(select(getUserAuthorized));
roles$ = this.store.pipe(select(getUserRoles));
isOrderManager$ = this.store.pipe(
select(getUserPermissions),
map(permissions => permissions?.includes('APP_B2B_MANAGE_ORDERS'))
);

loginUser(credentials: Credentials) {
this.store.dispatch(loginUser({ credentials }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export interface OrderListQuery {
lineItem_product?: string[];
lineItem_customerProductID?: string[];
lineItem_partialOrderNo?: string[];
buyer?: string;
}
4 changes: 3 additions & 1 deletion src/app/core/services/order/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ export class OrderService {
* @returns A list of the user's orders
*/
getOrders(query: OrderListQuery): Observable<Order[]> {
let params = orderListQueryToHttpParams(query);
const q = query?.buyer === 'all' ? { ...query, buyer: undefined as string, allBuyers: 'true' } : query;

let params = orderListQueryToHttpParams(q);
// for 7.10 compliance - ToDo: will be removed in PWA 6.0
params = params.set('page[limit]', query.limit);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<form [formGroup]="form" (ngSubmit)="submitForm()">
<form *ngIf="fields$ | async as fields" [formGroup]="form" (ngSubmit)="submitForm()">
<div>
<formly-form [form]="form" [fields]="[fields[0]]" />
<formly-form [form]="form" [fields]="[fields[0]]" [model]="model$ | async" />
</div>
<div [ngbCollapse]="formIsCollapsed">
<formly-form [form]="form" [fields]="fields.slice(1)" />
<formly-form [form]="form" [fields]="fields.slice(1)" [model]="model$ | async" />
</div>
<div class="row mb-4">
<div class="col-md-4 d-flex align-items-center">
Expand All @@ -20,7 +20,6 @@
{{ 'account.order_history.filter.clear' | translate }}
</button>
<button type="submit" class="btn btn-primary mr-0">
<fa-icon [icon]="['fas', 'search']" />
{{ 'account.order_history.filter.apply' | translate }}
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
import { FormlyForm } from '@ngx-formly/core';
import { TranslateModule } from '@ngx-translate/core';
import { MockComponent } from 'ng-mocks';
import { of } from 'rxjs';
import { instance, mock, when } from 'ts-mockito';

import { AccountFacade } from 'ish-core/facades/account.facade';

import { AccountOrderFiltersComponent } from './account-order-filters.component';

describe('Account Order Filters Component', () => {
let component: AccountOrderFiltersComponent;
let fixture: ComponentFixture<AccountOrderFiltersComponent>;
let element: HTMLElement;
let accountFacade: AccountFacade;

beforeEach(async () => {
accountFacade = mock(AccountFacade);
when(accountFacade.isOrderManager$).thenReturn(of(true));
when(accountFacade.isLoggedIn$).thenReturn(of(true));
await TestBed.configureTestingModule({
imports: [
MockComponent(FormlyForm),
Expand All @@ -23,7 +30,8 @@ describe('Account Order Filters Component', () => {
RouterTestingModule,
TranslateModule.forRoot(),
],
declarations: [AccountOrderFiltersComponent, MockComponent(FaIconComponent)],
declarations: [AccountOrderFiltersComponent],
providers: [{ provide: AccountFacade, useFactory: () => instance(accountFacade) }],
}).compileComponents();
});

Expand Down
Loading

0 comments on commit 28d9846

Please sign in to comment.