Merged PR 1996: fix(crm): consolidate customer feature selection logic

fix(crm): consolidate customer feature selection logic

Introduce centralized `getEnabledCustomerFeature` helper to standardize
feature selection across components. Replaces inconsistent filtering
approaches with unified logic that prioritizes 'd-account' and
'd-no-account' features.

Changes:
- Add `getEnabledCustomerFeature` helper with unit tests
- Add `CustomerFeatureKey` and `CustomerFeatureGroup` enums
- Update customer-order-details-header component
- Update pickup-shelf-details-header component
- Update customer-result-list components
- Update order-details-main-view component

Ref: #5432
This commit is contained in:
Nino Righi
2025-11-03 10:00:02 +00:00
committed by Lorenz Hilpert
parent 7a04b828c3
commit f175b5d2af
14 changed files with 1019 additions and 164 deletions

View File

@@ -0,0 +1,297 @@
import { KeyValueOfStringAndString } from '@isa/common/data-access';
import { getEnabledCustomerFeature } from './get-enabled-customer-feature.helper';
import { CustomerFeatureKey, CustomerFeatureGroup } from '../schemas';
describe('getEnabledCustomerFeature', () => {
it('should return undefined when no features are provided', () => {
// Arrange
const customerFeatures: KeyValueOfStringAndString[] = [];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBeUndefined();
});
it('should return undefined when no features are enabled', () => {
// Arrange
const customerFeatures: KeyValueOfStringAndString[] = [
{
key: CustomerFeatureKey.B2B,
value: 'test',
enabled: false,
description: 'test description',
group: CustomerFeatureGroup.DCustomerType,
},
{
key: CustomerFeatureKey.Staff,
value: 'test',
enabled: false,
description: 'test description',
group: CustomerFeatureGroup.DCustomerType,
},
];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBeUndefined();
});
it('should return the only enabled feature', () => {
// Arrange
const enabledFeature: KeyValueOfStringAndString = {
key: CustomerFeatureKey.B2B,
value: 'test-value',
enabled: true,
description: 'B2B feature',
group: CustomerFeatureGroup.DCustomerType,
};
const customerFeatures: KeyValueOfStringAndString[] = [
enabledFeature,
{
key: CustomerFeatureKey.Staff,
value: 'test',
enabled: false,
description: 'Staff feature',
group: CustomerFeatureGroup.DCustomerType,
},
];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBe(enabledFeature);
});
it('should prefer d-account when multiple features are enabled', () => {
// Arrange
const dAccountFeature: KeyValueOfStringAndString = {
key: CustomerFeatureKey.DAccount,
value: 'account-value',
enabled: true,
description: 'D-Account feature',
group: CustomerFeatureGroup.DCustomerType,
};
const customerFeatures: KeyValueOfStringAndString[] = [
{
key: CustomerFeatureKey.B2B,
value: 'b2b-value',
enabled: true,
description: 'B2B feature',
group: CustomerFeatureGroup.DCustomerType,
},
dAccountFeature,
{
key: CustomerFeatureKey.Staff,
value: 'staff-value',
enabled: true,
description: 'Staff feature',
group: CustomerFeatureGroup.DCustomerType,
},
];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBe(dAccountFeature);
});
it('should prefer d-no-account when multiple features are enabled and d-account is not present', () => {
// Arrange
const dNoAccountFeature: KeyValueOfStringAndString = {
key: CustomerFeatureKey.DNoAccount,
value: 'no-account-value',
enabled: true,
description: 'D-No-Account feature',
group: CustomerFeatureGroup.DCustomerType,
};
const customerFeatures: KeyValueOfStringAndString[] = [
{
key: CustomerFeatureKey.B2B,
value: 'b2b-value',
enabled: true,
description: 'B2B feature',
group: CustomerFeatureGroup.DCustomerType,
},
dNoAccountFeature,
{
key: CustomerFeatureKey.Webshop,
value: 'webshop-value',
enabled: true,
description: 'Webshop feature',
group: CustomerFeatureGroup.DCustomerType,
},
];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBe(dNoAccountFeature);
});
it('should return first enabled feature when multiple are enabled but neither d-account nor d-no-account are present', () => {
// Arrange
const firstEnabledFeature: KeyValueOfStringAndString = {
key: CustomerFeatureKey.B2B,
value: 'b2b-value',
enabled: true,
description: 'B2B feature',
group: CustomerFeatureGroup.DCustomerType,
};
const customerFeatures: KeyValueOfStringAndString[] = [
firstEnabledFeature,
{
key: CustomerFeatureKey.Staff,
value: 'staff-value',
enabled: true,
description: 'Staff feature',
group: CustomerFeatureGroup.DCustomerType,
},
{
key: CustomerFeatureKey.Webshop,
value: 'webshop-value',
enabled: true,
description: 'Webshop feature',
group: CustomerFeatureGroup.DCustomerType,
},
];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBe(firstEnabledFeature);
});
it('should handle null or undefined customerFeatures gracefully', () => {
// Act & Assert
expect(getEnabledCustomerFeature(null as any)).toBeUndefined();
expect(getEnabledCustomerFeature(undefined as any)).toBeUndefined();
});
it('should return undefined when enabled features have no description', () => {
// Arrange
const customerFeatures: KeyValueOfStringAndString[] = [
{
key: CustomerFeatureKey.B2B,
value: 'test-value',
enabled: true,
description: undefined,
group: CustomerFeatureGroup.DCustomerType,
},
{
key: CustomerFeatureKey.Staff,
value: 'test-value',
enabled: true,
description: '',
group: CustomerFeatureGroup.DCustomerType,
},
];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBeUndefined();
});
it('should only consider features with both enabled flag and description', () => {
// Arrange
const validFeature: KeyValueOfStringAndString = {
key: CustomerFeatureKey.B2B,
value: 'test-value',
enabled: true,
description: 'Valid B2B feature',
group: CustomerFeatureGroup.DCustomerType,
};
const customerFeatures: KeyValueOfStringAndString[] = [
{
key: CustomerFeatureKey.Staff,
value: 'test-value',
enabled: true,
description: undefined,
group: CustomerFeatureGroup.DCustomerType,
},
validFeature,
{
key: CustomerFeatureKey.Webshop,
value: 'test-value',
enabled: true,
description: '',
group: CustomerFeatureGroup.DCustomerType,
},
];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBe(validFeature);
});
it('should return undefined when enabled features have wrong group', () => {
// Arrange
const customerFeatures: KeyValueOfStringAndString[] = [
{
key: CustomerFeatureKey.B2B,
value: 'test-value',
enabled: true,
description: 'B2B feature',
group: CustomerFeatureGroup.CustomerType,
},
{
key: CustomerFeatureKey.Staff,
value: 'test-value',
enabled: true,
description: 'Staff feature',
group: 'wrong-group',
},
];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBeUndefined();
});
it('should only consider features with d-customertype group', () => {
// Arrange
const validFeature: KeyValueOfStringAndString = {
key: CustomerFeatureKey.DAccount,
value: 'test-value',
enabled: true,
description: 'Valid D-Account feature',
group: CustomerFeatureGroup.DCustomerType,
};
const customerFeatures: KeyValueOfStringAndString[] = [
{
key: CustomerFeatureKey.Staff,
value: 'test-value',
enabled: true,
description: 'Staff feature',
group: CustomerFeatureGroup.CustomerType,
},
validFeature,
{
key: CustomerFeatureKey.Webshop,
value: 'test-value',
enabled: true,
description: 'Webshop feature',
group: 'other-group',
},
];
// Act
const result = getEnabledCustomerFeature(customerFeatures);
// Assert
expect(result).toBe(validFeature);
});
});

View File

@@ -0,0 +1,53 @@
import { KeyValueOfStringAndString } from '@isa/common/data-access';
import { CustomerFeatureKey, CustomerFeatureGroup } from '../schemas';
/**
* Retrieves the first enabled customer feature that meets all validation criteria.
*
* A feature is considered valid if it satisfies ALL of the following conditions:
* - `enabled` property must be `true`
* - `description` property must exist and be non-empty
* - `group` property must be 'd-customertype'
*
* When multiple valid features are found, the function prioritizes features in the following order:
* 1. Features with key 'd-account' (highest priority)
* 2. Features with key 'd-no-account' (second priority)
* 3. The first valid feature in the array (fallback)
*
* @param customerFeatures - Array of customer feature objects to filter and evaluate
* @returns The first enabled feature matching all criteria, or `undefined` if none are found
*
* @example
* ```typescript
* const features = [
* { key: 'b2b', enabled: true, description: 'B2B', group: 'd-customertype' },
* { key: 'd-account', enabled: true, description: 'Account', group: 'd-customertype' }
* ];
* const result = getEnabledCustomerFeature(features);
* // Returns the 'd-account' feature (prioritized over 'b2b')
* ```
*/
export const getEnabledCustomerFeature = (
customerFeatures: KeyValueOfStringAndString[],
): KeyValueOfStringAndString | undefined => {
const enabledFeatures = customerFeatures?.filter(
(f) =>
f?.enabled &&
!!f?.description &&
f?.group === CustomerFeatureGroup.DCustomerType,
);
// If multiple features are enabled, prefer 'd-account' or 'd-no-account'
if (enabledFeatures?.length > 1) {
const preferredFeature = enabledFeatures.find(
(f) =>
f.key === CustomerFeatureKey.DAccount ||
f.key === CustomerFeatureKey.DNoAccount,
);
// If a preferred feature is found, return it; otherwise fall back to the first enabled feature
return preferredFeature ?? enabledFeatures[0];
}
// Return the first enabled feature if only one is enabled
return enabledFeatures?.[0];
};

View File

@@ -6,3 +6,4 @@ export {
} from './deduplicate-addressees.helper';
export * from './get-customer-name.component';
export * from './get-primary-bonus-card.helper';
export * from './get-enabled-customer-feature.helper';

View File

@@ -0,0 +1,15 @@
import { z } from 'zod';
// Customer feature group literals
export const CustomerFeatureGroup = {
DCustomerType: 'd-customertype',
CustomerType: 'customertype',
} as const;
export const CustomerFeatureGroupSchema = z
.enum([CustomerFeatureGroup.DCustomerType, CustomerFeatureGroup.CustomerType])
.describe('Customer feature group');
export type CustomerFeatureGroupType = z.infer<
typeof CustomerFeatureGroupSchema
>;

View File

@@ -0,0 +1,30 @@
import { z } from 'zod';
// Customer feature key literals
export const CustomerFeatureKey = {
DAccount: 'd-account',
DNoAccount: 'd-no-account',
B2B: 'b2b',
Staff: 'staff',
BookshelfId: 'bookshelfId',
P4MAccountId: 'p4mAccountId',
P4MUser: 'p4mUser',
Webshop: 'webshop',
Guest: 'guest',
} as const;
export const CustomerFeatureKeySchema = z
.enum([
CustomerFeatureKey.DAccount,
CustomerFeatureKey.DNoAccount,
CustomerFeatureKey.B2B,
CustomerFeatureKey.Staff,
CustomerFeatureKey.BookshelfId,
CustomerFeatureKey.P4MAccountId,
CustomerFeatureKey.P4MUser,
CustomerFeatureKey.Webshop,
CustomerFeatureKey.Guest,
])
.describe('Customer feature key');
export type CustomerFeatureKeyType = z.infer<typeof CustomerFeatureKeySchema>;

View File

@@ -3,6 +3,8 @@ export * from './attribute.schema';
export * from './bonus-card.schema';
export * from './branch.schema';
export * from './customer.schema';
export * from './customer-feature-keys.schema';
export * from './customer-feature-groups.schema';
export * from './fetch-customer-cards.schema';
export * from './fetch-customer-shipping-addresses.schema';
export * from './fetch-customer.schema';