mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merge branch 'feature/5202-Praemie-Order-Confirmation-Feature' into feature/5202-Praemie
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3,4 +3,5 @@ export * from './lib/constants';
|
||||
export * from './lib/models';
|
||||
export * from './lib/resources';
|
||||
export * from './lib/helpers';
|
||||
export * from './lib/schemas';
|
||||
export * from './lib/services';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { CrmSearchService } from '../services/crm-search.service';
|
||||
import { FetchCustomerInput } from '../schemas';
|
||||
import { Customer } from '../models';
|
||||
import { FetchCustomerInput, Customer } from '../schemas';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CustomerFacade {
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
areAddresseesEqual,
|
||||
areBranchesEqual,
|
||||
deduplicateAddressees,
|
||||
deduplicateBranches,
|
||||
} from './deduplicate-addressees.helper';
|
||||
import {
|
||||
DisplayAddresseeDTO,
|
||||
DisplayBranchDTO,
|
||||
AddressDTO,
|
||||
} from '@generated/swagger/oms-api';
|
||||
|
||||
describe('areAddresseesEqual', () => {
|
||||
const sampleAddress: AddressDTO = {
|
||||
street: 'Teststraße',
|
||||
streetNumber: '123',
|
||||
city: 'Berlin',
|
||||
zipCode: '10115',
|
||||
country: 'DEU',
|
||||
};
|
||||
|
||||
const createAddressee = (
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
address?: AddressDTO,
|
||||
): DisplayAddresseeDTO => ({
|
||||
firstName,
|
||||
lastName,
|
||||
address,
|
||||
gender: 0, // NotSet
|
||||
});
|
||||
|
||||
it('should return true when both addressees are undefined', () => {
|
||||
// Act
|
||||
const result = areAddresseesEqual(undefined, undefined);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when only first addressee is undefined', () => {
|
||||
// Arrange
|
||||
const addressee = createAddressee('John', 'Doe', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(undefined, addressee);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when only second addressee is undefined', () => {
|
||||
// Arrange
|
||||
const addressee = createAddressee('John', 'Doe', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee, undefined);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when addressees have same name and address', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress });
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when addressees have different first names', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('Jane', 'Doe', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when addressees have different last names', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Smith', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when addressees have different addresses', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when both addressees have no address', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe');
|
||||
const addressee2 = createAddressee('John', 'Doe');
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when one has address and other does not', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe');
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle empty string names', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('', '', sampleAddress);
|
||||
const addressee2 = createAddressee('', '', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should treat undefined names as empty strings', () => {
|
||||
// Arrange
|
||||
const addressee1: DisplayAddresseeDTO = {
|
||||
gender: 0, // NotSet
|
||||
address: sampleAddress,
|
||||
};
|
||||
const addressee2: DisplayAddresseeDTO = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
gender: 0, // NotSet
|
||||
address: sampleAddress,
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deduplicateAddressees', () => {
|
||||
const sampleAddress: AddressDTO = {
|
||||
street: 'Teststraße',
|
||||
streetNumber: '123',
|
||||
city: 'Berlin',
|
||||
zipCode: '10115',
|
||||
country: 'DEU',
|
||||
};
|
||||
|
||||
const createAddressee = (
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
address?: AddressDTO,
|
||||
): DisplayAddresseeDTO => ({
|
||||
firstName,
|
||||
lastName,
|
||||
address,
|
||||
gender: 0, // NotSet
|
||||
});
|
||||
|
||||
it('should return empty array when input is empty', () => {
|
||||
// Arrange
|
||||
const addressees: DisplayAddresseeDTO[] = [];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should filter out undefined values', () => {
|
||||
// Arrange
|
||||
const addressee = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressees = [undefined, addressee, undefined];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(addressee);
|
||||
});
|
||||
|
||||
it('should return single item when array has one addressee', () => {
|
||||
// Arrange
|
||||
const addressee = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressees = [addressee];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(addressee);
|
||||
});
|
||||
|
||||
it('should return all items when no duplicates exist', () => {
|
||||
// Arrange
|
||||
const addressees = [
|
||||
createAddressee('John', 'Doe', sampleAddress),
|
||||
createAddressee('Jane', 'Smith', sampleAddress),
|
||||
createAddressee('Bob', 'Johnson', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
}),
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should remove duplicate addressees keeping first occurrence', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress });
|
||||
const addressees = [addressee1, addressee2];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toBe(addressee1); // First occurrence kept
|
||||
});
|
||||
|
||||
it('should handle multiple duplicates and keep only first', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress });
|
||||
const addressee3 = createAddressee('John', 'Doe', { ...sampleAddress });
|
||||
const addressees = [addressee1, addressee2, addressee3];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toBe(addressee1);
|
||||
});
|
||||
|
||||
it('should not consider different names but same address as duplicates', () => {
|
||||
// Arrange
|
||||
const addressees = [
|
||||
createAddressee('John', 'Doe', sampleAddress),
|
||||
createAddressee('Jane', 'Doe', sampleAddress),
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should not consider same names but different addresses as duplicates', () => {
|
||||
// Arrange
|
||||
const addressees = [
|
||||
createAddressee('John', 'Doe', sampleAddress),
|
||||
createAddressee('John', 'Doe', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
}),
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should handle complex scenario with mixed duplicates and unique items', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress }); // Duplicate of 1
|
||||
const addressee3 = createAddressee('Jane', 'Smith', sampleAddress);
|
||||
const addressee4 = createAddressee('Jane', 'Smith', { ...sampleAddress }); // Duplicate of 3
|
||||
const addressee5 = createAddressee('Bob', 'Johnson', sampleAddress);
|
||||
const addressees = [
|
||||
addressee1,
|
||||
addressee2,
|
||||
addressee3,
|
||||
addressee4,
|
||||
addressee5,
|
||||
undefined,
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0]).toBe(addressee1);
|
||||
expect(result[1]).toBe(addressee3);
|
||||
expect(result[2]).toBe(addressee5);
|
||||
});
|
||||
|
||||
it('should preserve order of first occurrences', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('Alice', 'Wonder', sampleAddress);
|
||||
const addressee2 = createAddressee('Bob', 'Builder', sampleAddress);
|
||||
const addressee3 = createAddressee('Alice', 'Wonder', { ...sampleAddress }); // Duplicate
|
||||
const addressees = [addressee1, addressee2, addressee3];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toBe(addressee1);
|
||||
expect(result[1]).toBe(addressee2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('areBranchesEqual', () => {
|
||||
const sampleAddress: AddressDTO = {
|
||||
street: 'Teststraße',
|
||||
streetNumber: '123',
|
||||
city: 'Berlin',
|
||||
zipCode: '10115',
|
||||
country: 'DEU',
|
||||
};
|
||||
|
||||
const createBranch = (
|
||||
name: string,
|
||||
address?: AddressDTO,
|
||||
): DisplayBranchDTO => ({
|
||||
name,
|
||||
address,
|
||||
});
|
||||
|
||||
it('should return true when both branches are undefined', () => {
|
||||
// Act
|
||||
const result = areBranchesEqual(undefined, undefined);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when only first branch is undefined', () => {
|
||||
// Arrange
|
||||
const branch = createBranch('Branch 1', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areBranchesEqual(undefined, branch);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when branches have same name and address', () => {
|
||||
// Arrange
|
||||
const branch1 = createBranch('Branch 1', sampleAddress);
|
||||
const branch2 = createBranch('Branch 1', { ...sampleAddress });
|
||||
|
||||
// Act
|
||||
const result = areBranchesEqual(branch1, branch2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when branches have different names', () => {
|
||||
// Arrange
|
||||
const branch1 = createBranch('Branch 1', sampleAddress);
|
||||
const branch2 = createBranch('Branch 2', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areBranchesEqual(branch1, branch2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when branches have different addresses', () => {
|
||||
// Arrange
|
||||
const branch1 = createBranch('Branch 1', sampleAddress);
|
||||
const branch2 = createBranch('Branch 1', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = areBranchesEqual(branch1, branch2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deduplicateBranches', () => {
|
||||
const sampleAddress: AddressDTO = {
|
||||
street: 'Teststraße',
|
||||
streetNumber: '123',
|
||||
city: 'Berlin',
|
||||
zipCode: '10115',
|
||||
country: 'DEU',
|
||||
};
|
||||
|
||||
const createBranch = (
|
||||
name: string,
|
||||
address?: AddressDTO,
|
||||
): DisplayBranchDTO => ({
|
||||
name,
|
||||
address,
|
||||
});
|
||||
|
||||
it('should return empty array when input is empty', () => {
|
||||
// Arrange
|
||||
const branches: DisplayBranchDTO[] = [];
|
||||
|
||||
// Act
|
||||
const result = deduplicateBranches(branches);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should filter out undefined values', () => {
|
||||
// Arrange
|
||||
const branch = createBranch('Branch 1', sampleAddress);
|
||||
const branches = [undefined, branch, undefined];
|
||||
|
||||
// Act
|
||||
const result = deduplicateBranches(branches);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(branch);
|
||||
});
|
||||
|
||||
it('should remove duplicate branches keeping first occurrence', () => {
|
||||
// Arrange
|
||||
const branch1 = createBranch('Branch 1', sampleAddress);
|
||||
const branch2 = createBranch('Branch 1', { ...sampleAddress });
|
||||
const branches = [branch1, branch2];
|
||||
|
||||
// Act
|
||||
const result = deduplicateBranches(branches);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toBe(branch1);
|
||||
});
|
||||
|
||||
it('should return all items when no duplicates exist', () => {
|
||||
// Arrange
|
||||
const branches = [
|
||||
createBranch('Branch 1', sampleAddress),
|
||||
createBranch('Branch 2', sampleAddress),
|
||||
createBranch('Branch 3', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
}),
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateBranches(branches);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,207 @@
|
||||
import {
|
||||
DisplayAddresseeDTO,
|
||||
DisplayBranchDTO,
|
||||
} from '@generated/swagger/oms-api';
|
||||
|
||||
/**
|
||||
* Internal model representing an entity with name and address for comparison.
|
||||
* This allows for type-safe comparison without casting.
|
||||
*/
|
||||
interface EntityWithNameAndAddress {
|
||||
readonly name: string;
|
||||
readonly address: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an addressee-like object to a comparable entity.
|
||||
*/
|
||||
function toComparableAddressee(
|
||||
addressee: Pick<DisplayAddresseeDTO, 'firstName' | 'lastName' | 'address'>,
|
||||
): EntityWithNameAndAddress {
|
||||
const firstName = addressee.firstName ?? '';
|
||||
const lastName = addressee.lastName ?? '';
|
||||
const name = `${firstName}|${lastName}`.trim();
|
||||
return {
|
||||
name,
|
||||
address: addressee.address ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a branch-like object to a comparable entity.
|
||||
*/
|
||||
function toComparableBranch(
|
||||
branch: Pick<DisplayBranchDTO, 'name' | 'address'>,
|
||||
): EntityWithNameAndAddress {
|
||||
return {
|
||||
name: branch.name ?? '',
|
||||
address: branch.address ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two entities for equality based on name and address.
|
||||
*/
|
||||
function areEntitiesEqual(
|
||||
entity1: EntityWithNameAndAddress,
|
||||
entity2: EntityWithNameAndAddress,
|
||||
): boolean {
|
||||
if (entity1.name !== entity2.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const address1Str = JSON.stringify(entity1.address);
|
||||
const address2Str = JSON.stringify(entity2.address);
|
||||
|
||||
return address1Str === address2Str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two DisplayAddresseeDTO objects for equality based on name and address.
|
||||
* Two addressees are considered equal if they have the same firstName, lastName, and address.
|
||||
*
|
||||
* @param addressee1 - First addressee to compare
|
||||
* @param addressee2 - Second addressee to compare
|
||||
* @returns true if addressees are equal, false otherwise
|
||||
*/
|
||||
export function areAddresseesEqual(
|
||||
addressee1: DisplayAddresseeDTO | undefined,
|
||||
addressee2: DisplayAddresseeDTO | undefined,
|
||||
): boolean {
|
||||
if (!addressee1 && !addressee2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!addressee1 || !addressee2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areEntitiesEqual(
|
||||
toComparableAddressee(addressee1),
|
||||
toComparableAddressee(addressee2),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two DisplayBranchDTO objects for equality based on name and address.
|
||||
* Two branches are considered equal if they have the same name and address.
|
||||
*
|
||||
* @param branch1 - First branch to compare
|
||||
* @param branch2 - Second branch to compare
|
||||
* @returns true if branches are equal, false otherwise
|
||||
*/
|
||||
export function areBranchesEqual(
|
||||
branch1: DisplayBranchDTO | undefined,
|
||||
branch2: DisplayBranchDTO | undefined,
|
||||
): boolean {
|
||||
if (!branch1 && !branch2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!branch1 || !branch2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areEntitiesEqual(
|
||||
toComparableBranch(branch1),
|
||||
toComparableBranch(branch2),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an object can be compared as an addressee.
|
||||
*/
|
||||
function hasAddresseeShape(
|
||||
obj: unknown,
|
||||
): obj is Pick<DisplayAddresseeDTO, 'firstName' | 'lastName' | 'address'> {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
('firstName' in obj || 'lastName' in obj || 'address' in obj)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an object can be compared as a branch.
|
||||
*/
|
||||
function hasBranchShape(
|
||||
obj: unknown,
|
||||
): obj is Pick<DisplayBranchDTO, 'name' | 'address'> {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
('name' in obj || 'address' in obj)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes duplicate addressees from an array, keeping only the first occurrence.
|
||||
* Two addressees are considered duplicates if they have the same firstName, lastName, and address.
|
||||
*
|
||||
* @param addressees - Array of addressees to deduplicate
|
||||
* @returns Deduplicated array with only first occurrence of each unique addressee
|
||||
*/
|
||||
export function deduplicateAddressees<
|
||||
T extends Pick<DisplayAddresseeDTO, 'firstName' | 'lastName' | 'address'>,
|
||||
>(addressees: readonly (T | undefined)[]): T[] {
|
||||
const result: T[] = [];
|
||||
const seen: T[] = [];
|
||||
|
||||
for (const addressee of addressees) {
|
||||
if (!addressee || !hasAddresseeShape(addressee)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isDuplicate = seen.some((seenAddressee) =>
|
||||
hasAddresseeShape(seenAddressee)
|
||||
? areEntitiesEqual(
|
||||
toComparableAddressee(seenAddressee),
|
||||
toComparableAddressee(addressee),
|
||||
)
|
||||
: false,
|
||||
);
|
||||
|
||||
if (!isDuplicate) {
|
||||
result.push(addressee);
|
||||
seen.push(addressee);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes duplicate branches from an array, keeping only the first occurrence.
|
||||
* Two branches are considered duplicates if they have the same name and address.
|
||||
*
|
||||
* @param branches - Array of branches to deduplicate
|
||||
* @returns Deduplicated array with only first occurrence of each unique branch
|
||||
*/
|
||||
export function deduplicateBranches<
|
||||
T extends Pick<DisplayBranchDTO, 'name' | 'address'>,
|
||||
>(branches: readonly (T | undefined)[]): T[] {
|
||||
const result: T[] = [];
|
||||
const seen: T[] = [];
|
||||
|
||||
for (const branch of branches) {
|
||||
if (!branch || !hasBranchShape(branch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isDuplicate = seen.some((seenBranch) =>
|
||||
hasBranchShape(seenBranch)
|
||||
? areEntitiesEqual(
|
||||
toComparableBranch(seenBranch),
|
||||
toComparableBranch(branch),
|
||||
)
|
||||
: false,
|
||||
);
|
||||
|
||||
if (!isDuplicate) {
|
||||
result.push(branch);
|
||||
seen.push(branch);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,2 +1,8 @@
|
||||
export {
|
||||
areAddresseesEqual,
|
||||
areBranchesEqual,
|
||||
deduplicateAddressees,
|
||||
deduplicateBranches,
|
||||
} from './deduplicate-addressees.helper';
|
||||
export * from './get-customer-name.component';
|
||||
export * from './get-primary-bonus-card.helper';
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { AssignedPayerDTO } from '@generated/swagger/crm-api';
|
||||
import { Payer } from './payer';
|
||||
import { EntityContainer } from '@isa/common/data-access';
|
||||
|
||||
export type AssignedPayer = AssignedPayerDTO & {
|
||||
payer: EntityContainer<Payer>;
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
import { CustomerDTO } from '@generated/swagger/crm-api';
|
||||
import { CustomerType } from './customer-type';
|
||||
import { AssignedPayer } from './assigned-payer';
|
||||
|
||||
export interface Customer extends CustomerDTO {
|
||||
customerType: CustomerType;
|
||||
payers: Array<AssignedPayer>;
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
export * from './assigned-payer';
|
||||
export * from './bonus-card-info.model';
|
||||
export * from './country';
|
||||
export * from './customer-type';
|
||||
export * from './customer.model';
|
||||
export * from './payer';
|
||||
export * from './shipping-address.model';
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { ShippingAddressDTO } from '@generated/swagger/crm-api';
|
||||
|
||||
export type ShippingAddress = ShippingAddressDTO;
|
||||
@@ -1,54 +1,57 @@
|
||||
import { effect, inject, Injectable, resource, signal } from '@angular/core';
|
||||
import { CrmTabMetadataService, ShippingAddressService } from '../services';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { ShippingAddress } from '../models';
|
||||
|
||||
@Injectable()
|
||||
export class CustomerShippingAddressResource {
|
||||
#shippingAddressService = inject(ShippingAddressService);
|
||||
|
||||
#params = signal<{
|
||||
shippingAddressId: number | undefined;
|
||||
}>({
|
||||
shippingAddressId: undefined,
|
||||
});
|
||||
|
||||
params(params: { shippingAddressId?: number }) {
|
||||
this.#params.update((p) => ({ ...p, ...params }));
|
||||
}
|
||||
|
||||
readonly resource = resource({
|
||||
params: () => this.#params(),
|
||||
loader: async ({ params, abortSignal }): Promise<ShippingAddress | undefined> => {
|
||||
if (!params.shippingAddressId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const res = await this.#shippingAddressService.fetchShippingAddress(
|
||||
{
|
||||
shippingAddressId: params.shippingAddressId,
|
||||
},
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
return res.result as ShippingAddress;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SelectedCustomerShippingAddressResource extends CustomerShippingAddressResource {
|
||||
#tabId = inject(TabService).activatedTabId;
|
||||
#customerMetadata = inject(CrmTabMetadataService);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
effect(() => {
|
||||
const tabId = this.#tabId();
|
||||
const shippingAddressId = tabId
|
||||
? this.#customerMetadata.selectedShippingAddressId(tabId)
|
||||
: undefined;
|
||||
this.params({ shippingAddressId });
|
||||
});
|
||||
}
|
||||
}
|
||||
import { effect, inject, Injectable, resource, signal } from '@angular/core';
|
||||
import { CrmTabMetadataService, ShippingAddressService } from '../services';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { ShippingAddress } from '../schemas';
|
||||
|
||||
@Injectable()
|
||||
export class CustomerShippingAddressResource {
|
||||
#shippingAddressService = inject(ShippingAddressService);
|
||||
|
||||
#params = signal<{
|
||||
shippingAddressId: number | undefined;
|
||||
}>({
|
||||
shippingAddressId: undefined,
|
||||
});
|
||||
|
||||
params(params: { shippingAddressId?: number }) {
|
||||
this.#params.update((p) => ({ ...p, ...params }));
|
||||
}
|
||||
|
||||
readonly resource = resource({
|
||||
params: () => this.#params(),
|
||||
loader: async ({
|
||||
params,
|
||||
abortSignal,
|
||||
}): Promise<ShippingAddress | undefined> => {
|
||||
if (!params.shippingAddressId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const res = await this.#shippingAddressService.fetchShippingAddress(
|
||||
{
|
||||
shippingAddressId: params.shippingAddressId,
|
||||
},
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
return res.result as ShippingAddress;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SelectedCustomerShippingAddressResource extends CustomerShippingAddressResource {
|
||||
#tabId = inject(TabService).activatedTabId;
|
||||
#customerMetadata = inject(CrmTabMetadataService);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
effect(() => {
|
||||
const tabId = this.#tabId();
|
||||
const shippingAddressId = tabId
|
||||
? this.#customerMetadata.selectedShippingAddressId(tabId)
|
||||
: undefined;
|
||||
this.params({ shippingAddressId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,66 @@
|
||||
import { effect, inject, Injectable, resource, signal } from '@angular/core';
|
||||
import { CrmTabMetadataService, ShippingAddressService } from '../services';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { ShippingAddress } from '../models';
|
||||
|
||||
@Injectable()
|
||||
export class CustomerShippingAddressesResource {
|
||||
#shippingAddressService = inject(ShippingAddressService);
|
||||
|
||||
#params = signal<{
|
||||
customerId: number | undefined;
|
||||
take?: number | null;
|
||||
skip?: number | null;
|
||||
}>({
|
||||
customerId: undefined,
|
||||
});
|
||||
|
||||
params(params: { customerId?: number; take?: number | null; skip?: number | null }) {
|
||||
this.#params.update((p) => ({ ...p, ...params }));
|
||||
}
|
||||
|
||||
readonly resource = resource({
|
||||
params: () => this.#params(),
|
||||
loader: async ({ params, abortSignal }): Promise<ShippingAddress[] | undefined> => {
|
||||
if (!params.customerId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const res = await this.#shippingAddressService.fetchCustomerShippingAddresses(
|
||||
{
|
||||
customerId: params.customerId,
|
||||
take: params.take,
|
||||
skip: params.skip,
|
||||
},
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
return res.result as ShippingAddress[];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SelectedCustomerShippingAddressesResource extends CustomerShippingAddressesResource {
|
||||
#tabId = inject(TabService).activatedTabId;
|
||||
#customerMetadata = inject(CrmTabMetadataService);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
effect(() => {
|
||||
const tabId = this.#tabId();
|
||||
const customerId = tabId
|
||||
? this.#customerMetadata.selectedCustomerId(tabId)
|
||||
: undefined;
|
||||
this.params({ customerId });
|
||||
});
|
||||
}
|
||||
}
|
||||
import { effect, inject, Injectable, resource, signal } from '@angular/core';
|
||||
import { CrmTabMetadataService, ShippingAddressService } from '../services';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { ShippingAddress } from '../schemas';
|
||||
|
||||
@Injectable()
|
||||
export class CustomerShippingAddressesResource {
|
||||
#shippingAddressService = inject(ShippingAddressService);
|
||||
|
||||
#params = signal<{
|
||||
customerId: number | undefined;
|
||||
take?: number | null;
|
||||
skip?: number | null;
|
||||
}>({
|
||||
customerId: undefined,
|
||||
});
|
||||
|
||||
params(params: {
|
||||
customerId?: number;
|
||||
take?: number | null;
|
||||
skip?: number | null;
|
||||
}) {
|
||||
this.#params.update((p) => ({ ...p, ...params }));
|
||||
}
|
||||
|
||||
readonly resource = resource({
|
||||
params: () => this.#params(),
|
||||
loader: async ({
|
||||
params,
|
||||
abortSignal,
|
||||
}): Promise<ShippingAddress[] | undefined> => {
|
||||
if (!params.customerId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const res =
|
||||
await this.#shippingAddressService.fetchCustomerShippingAddresses(
|
||||
{
|
||||
customerId: params.customerId,
|
||||
take: params.take,
|
||||
skip: params.skip,
|
||||
},
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
return res.result as ShippingAddress[];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SelectedCustomerShippingAddressesResource extends CustomerShippingAddressesResource {
|
||||
#tabId = inject(TabService).activatedTabId;
|
||||
#customerMetadata = inject(CrmTabMetadataService);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
effect(() => {
|
||||
const tabId = this.#tabId();
|
||||
const customerId = tabId
|
||||
? this.#customerMetadata.selectedCustomerId(tabId)
|
||||
: undefined;
|
||||
this.params({ customerId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { effect, inject, Injectable, resource, signal } from '@angular/core';
|
||||
import { CrmSearchService, CrmTabMetadataService } from '../services';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { Customer } from '../models';
|
||||
import { Customer } from '../schemas';
|
||||
|
||||
@Injectable()
|
||||
export class CustomerResource {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { EntityContainerSchema } from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
import { PayerSchema } from './payer.schema';
|
||||
|
||||
export const AssignedPayerSchema = z.object({
|
||||
assignedToCustomer: z.string().describe('Assigned to customer').optional(),
|
||||
isDefault: z.string().describe('Whether this is the default').optional(),
|
||||
payer: EntityContainerSchema(PayerSchema).describe('Payer information').optional(),
|
||||
});
|
||||
|
||||
export type AssignedPayer = z.infer<typeof AssignedPayerSchema>;
|
||||
19
libs/crm/data-access/src/lib/schemas/attribute.schema.ts
Normal file
19
libs/crm/data-access/src/lib/schemas/attribute.schema.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Schema for AttributeDTO
|
||||
* Represents attribute data with date fields as strings (ISO format)
|
||||
*
|
||||
* Note: The API returns start/stop as string (not Date objects).
|
||||
* If date parsing is needed, use z.coerce.date() or parse in application logic.
|
||||
*/
|
||||
export const AttributeSchema = z.object({
|
||||
dataType: z.number().describe('Data type'),
|
||||
formatValidator: z.string().describe('Format validator').optional(),
|
||||
group: z.string().describe('Group').optional(),
|
||||
key: z.string().describe('Key'),
|
||||
name: z.string().describe('Name').optional(),
|
||||
start: z.string().describe('Start').optional(),
|
||||
stop: z.string().describe('Stop').optional(),
|
||||
value: z.string().describe('Value').optional(),
|
||||
});
|
||||
22
libs/crm/data-access/src/lib/schemas/bonus-card.schema.ts
Normal file
22
libs/crm/data-access/src/lib/schemas/bonus-card.schema.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { EntitySchema } from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Schema for BonusCardDTO
|
||||
* Represents bonus card information with proper type matching
|
||||
*
|
||||
* Note: cardProvider is a number in the API, and dates are returned as strings
|
||||
*/
|
||||
export const BonusCardSchema = z
|
||||
.object({
|
||||
bonusValue: z.number().describe('Bonus value').optional(),
|
||||
cardNumber: z.string().describe('Card number').optional(),
|
||||
cardProvider: z.number().describe('Card provider').optional(),
|
||||
isLocked: z.boolean().describe('Whether locked').optional(),
|
||||
isPaymentEnabled: z.boolean().describe('Whether paymentEnabled').optional(),
|
||||
markedAsLost: z.string().describe('Marked as lost').optional(),
|
||||
suspensionComment: z.string().describe('Suspension comment').optional(),
|
||||
validFrom: z.string().describe('Validity start date').optional(),
|
||||
validThrough: z.string().describe('Valid through').optional(),
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
21
libs/crm/data-access/src/lib/schemas/branch.schema.ts
Normal file
21
libs/crm/data-access/src/lib/schemas/branch.schema.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
AddressSchema,
|
||||
EntityContainerSchema,
|
||||
EntitySchema,
|
||||
LabelSchema,
|
||||
} from '@isa/common/data-access';
|
||||
import { number, z } from 'zod';
|
||||
|
||||
export const BranchSchema = z
|
||||
.object({
|
||||
address: AddressSchema.describe('Address').optional(),
|
||||
banchNumber: z.string().describe('Banch number').optional(),
|
||||
branchType: z.number().describe('Branch type'),
|
||||
isOnline: z.boolean().describe('Whether online').optional(),
|
||||
key: z.string().describe('Key').optional(),
|
||||
label: EntityContainerSchema(LabelSchema).describe('Label').optional(),
|
||||
name: z.string().describe('Name').optional(),
|
||||
parent: z.number().describe('Parent').optional(),
|
||||
shortName: z.string().describe('Short name').optional(),
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
96
libs/crm/data-access/src/lib/schemas/customer.schema.ts
Normal file
96
libs/crm/data-access/src/lib/schemas/customer.schema.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
AddressSchema,
|
||||
CommunicationDetailsSchema,
|
||||
EntityContainerSchema,
|
||||
EntitySchema,
|
||||
GenderSchema,
|
||||
KeyValueOfStringAndStringSchema,
|
||||
LabelSchema,
|
||||
NotificationChannelSchema,
|
||||
OrganisationSchema,
|
||||
} from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
import { CustomerType } from '../models';
|
||||
import { AssignedPayerSchema } from './assigned-payer.schema';
|
||||
import { AttributeSchema } from './attribute.schema';
|
||||
import { BonusCardSchema } from './bonus-card.schema';
|
||||
import { BranchSchema } from './branch.schema';
|
||||
import { LinkedRecordSchema } from './linked-record.schema';
|
||||
import { ShippingAddressSchema } from './shipping-address.schema';
|
||||
import { UserSchema } from './user.schema';
|
||||
|
||||
export const CustomerSchema = z
|
||||
.object({
|
||||
address: AddressSchema.describe('Address').optional(),
|
||||
agentComment: z.string().describe('Agent comment').optional(),
|
||||
attributes: z
|
||||
.array(EntityContainerSchema(AttributeSchema))
|
||||
.describe('Attribute list')
|
||||
.optional(),
|
||||
bonusCard: EntityContainerSchema(BonusCardSchema)
|
||||
.describe('Bonus card information')
|
||||
.optional(),
|
||||
campaignCode: z.string().describe('Campaign code').optional(),
|
||||
clientChannel: z.number().describe('Client channel').optional(),
|
||||
communicationDetails: CommunicationDetailsSchema.describe(
|
||||
'Communication details',
|
||||
).optional(),
|
||||
createdInBranch: EntityContainerSchema(BranchSchema)
|
||||
.describe('Created in branch')
|
||||
.optional(),
|
||||
customerGroup: z.string().describe('Customer group').optional(),
|
||||
customerNumber: z.string().describe('Customer number').optional(),
|
||||
customerStatus: z.number().describe('Customer status').optional(),
|
||||
customerType: z.nativeEnum(CustomerType).describe('Customer type'),
|
||||
dateOfBirth: z.string().describe('Date of birth').optional(),
|
||||
deactivationComment: z.string().describe('Deactivation comment').optional(),
|
||||
features: z
|
||||
.array(KeyValueOfStringAndStringSchema)
|
||||
.describe('Features')
|
||||
.optional(),
|
||||
fetchOnDeliveryNote: z
|
||||
.boolean()
|
||||
.describe('Fetch on delivery note')
|
||||
.optional(),
|
||||
firstName: z.string().describe('First name').optional(),
|
||||
gender: GenderSchema.describe('Gender').optional(),
|
||||
hasOnlineAccount: z
|
||||
.boolean()
|
||||
.describe('Whether has onlineAccount')
|
||||
.optional(),
|
||||
isGuestAccount: z.boolean().describe('Whether guestAccount').optional(),
|
||||
label: EntityContainerSchema(LabelSchema).describe('Label').optional(),
|
||||
lastName: z.string().describe('Last name').optional(),
|
||||
linkedRecords: z
|
||||
.array(LinkedRecordSchema)
|
||||
.describe('List of linked records')
|
||||
.optional(),
|
||||
notificationChannels: NotificationChannelSchema.describe(
|
||||
'Notification channels',
|
||||
).optional(),
|
||||
orderCount: z.number().describe('Number of orders').optional(),
|
||||
organisation: OrganisationSchema.describe(
|
||||
'Organisation information',
|
||||
).optional(),
|
||||
payers: z.array(AssignedPayerSchema).describe('Payers').optional(),
|
||||
preferredPaymentType: z
|
||||
.number()
|
||||
.describe('PreferredPayment type')
|
||||
.optional(),
|
||||
shippingAddresses: z
|
||||
.array(EntityContainerSchema(ShippingAddressSchema))
|
||||
.describe('Shipping addresses')
|
||||
.optional(),
|
||||
statusChangeComment: z
|
||||
.string()
|
||||
.describe('Status change comment')
|
||||
.optional(),
|
||||
statusComment: z.string().describe('Status comment').optional(),
|
||||
title: z.string().describe('Title').optional(),
|
||||
user: EntityContainerSchema(UserSchema)
|
||||
.describe('User information')
|
||||
.optional(),
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
|
||||
export type Customer = z.infer<typeof CustomerSchema>;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const FetchCustomerCardsSchema = z.object({
|
||||
customerId: z.number().int(),
|
||||
customerId: z.number().int().describe('Unique customer identifier'),
|
||||
});
|
||||
|
||||
export type FetchCustomerCards = z.infer<typeof FetchCustomerCardsSchema>;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const FetchCustomerShippingAddressesSchema = z.object({
|
||||
customerId: z.number().int(),
|
||||
take: z.number().int().optional().nullable(),
|
||||
skip: z.number().int().optional().nullable(),
|
||||
customerId: z.number().int().describe('Unique customer identifier'),
|
||||
take: z.number().int().optional().describe('Number of items to return per page').nullable(),
|
||||
skip: z.number().int().optional().describe('Number of items to skip for pagination').nullable(),
|
||||
});
|
||||
|
||||
export type FetchCustomerShippingAddresses = z.infer<typeof FetchCustomerShippingAddressesSchema>;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const FetchCustomerSchema = z.object({
|
||||
customerId: z.number().int(),
|
||||
eagerLoading: z.number().optional(),
|
||||
customerId: z.number().int().describe('Unique customer identifier'),
|
||||
eagerLoading: z.number().describe('Eager loading').optional(),
|
||||
});
|
||||
|
||||
export type FetchCustomer = z.infer<typeof FetchCustomerSchema>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const FetchShippingAddressSchema = z.object({
|
||||
shippingAddressId: z.number().int(),
|
||||
shippingAddressId: z.number().int().describe('ShippingAddress identifier'),
|
||||
});
|
||||
|
||||
export type FetchShippingAddress = z.infer<typeof FetchShippingAddressSchema>;
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
export * from './fetch-customer-cards.schema';
|
||||
export * from './fetch-customer-shipping-addresses.schema';
|
||||
export * from './fetch-customer.schema';
|
||||
export * from './fetch-shipping-address.schema';
|
||||
export * from './assigned-payer.schema';
|
||||
export * from './attribute.schema';
|
||||
export * from './bonus-card.schema';
|
||||
export * from './branch.schema';
|
||||
export * from './customer.schema';
|
||||
export * from './fetch-customer-cards.schema';
|
||||
export * from './fetch-customer-shipping-addresses.schema';
|
||||
export * from './fetch-customer.schema';
|
||||
export * from './fetch-shipping-address.schema';
|
||||
export * from './linked-record.schema';
|
||||
export * from './notification-channel.schema';
|
||||
export * from './payer-status.schema';
|
||||
export * from './payer.schema';
|
||||
export * from './payment-settings.schema';
|
||||
export * from './shipping-address.schema';
|
||||
export * from './user.schema';
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const LinkedRecordSchema = z.object({
|
||||
isSource: z.boolean().describe('Whether source').optional(),
|
||||
number: z.string().describe('Number').optional(),
|
||||
pk: z.string().describe('Pk').optional(),
|
||||
repository: z.string().describe('Repository').optional(),
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @deprecated This schema has been moved to @isa/common/data-access.
|
||||
* Please update imports to use:
|
||||
* import { NotificationChannelSchema, NotificationChannel } from '@isa/common/data-access';
|
||||
*
|
||||
* This re-export will be removed in a future version.
|
||||
*/
|
||||
export {
|
||||
NotificationChannelSchema,
|
||||
NotificationChannel,
|
||||
} from '@isa/common/data-access';
|
||||
14
libs/crm/data-access/src/lib/schemas/payer-status.schema.ts
Normal file
14
libs/crm/data-access/src/lib/schemas/payer-status.schema.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const PayerStatus = {
|
||||
NotSet: 0,
|
||||
Blocked: 1,
|
||||
Check: 2,
|
||||
LowDegreeOfCreditworthiness: 4,
|
||||
Dunning1: 8,
|
||||
Dunning2: 16,
|
||||
} as const;
|
||||
|
||||
export const PayerStatusSchema = z.nativeEnum(PayerStatus).describe('Payer status');
|
||||
|
||||
export type PayerStatus = z.infer<typeof PayerStatusSchema>;
|
||||
38
libs/crm/data-access/src/lib/schemas/payer.schema.ts
Normal file
38
libs/crm/data-access/src/lib/schemas/payer.schema.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
AddressSchema,
|
||||
CommunicationDetailsSchema,
|
||||
EntityContainerSchema,
|
||||
EntitySchema,
|
||||
GenderSchema,
|
||||
LabelSchema,
|
||||
OrganisationSchema,
|
||||
PayerType,
|
||||
} from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
import { PayerStatusSchema } from './payer-status.schema';
|
||||
import { PaymentSettingsSchema } from './payment-settings.schema';
|
||||
|
||||
export const PayerSchema = z
|
||||
.object({
|
||||
address: AddressSchema.describe('Address').optional(),
|
||||
agentComment: z.string().describe('Agent comment').optional(),
|
||||
communicationDetails: CommunicationDetailsSchema.describe('Communication details').optional(),
|
||||
deactivationComment: z.string().describe('Deactivation comment').optional(),
|
||||
defaultPaymentPeriod: z.number().describe('Default payment period').optional(),
|
||||
firstName: z.string().describe('First name').optional(),
|
||||
gender: GenderSchema.describe('Gender').optional(),
|
||||
isGuestAccount: z.boolean().describe('Whether guestAccount').optional(),
|
||||
label: EntityContainerSchema(LabelSchema).describe('Label').optional(),
|
||||
lastName: z.string().describe('Last name').optional(),
|
||||
organisation: OrganisationSchema.describe('Organisation information').optional(),
|
||||
payerGroup: z.string().describe('Payer group').optional(),
|
||||
payerNumber: z.string().describe('Unique payer account number').optional(),
|
||||
payerStatus: PayerStatusSchema.describe('Current status of the payer account').optional(),
|
||||
payerType: z.nativeEnum(PayerType).describe('Payer type').optional(),
|
||||
paymentTypes: z.array(PaymentSettingsSchema).describe('Payment types').optional(),
|
||||
standardInvoiceText: z.string().describe('Standard invoice text').optional(),
|
||||
statusChangeComment: z.string().describe('Status change comment').optional(),
|
||||
statusComment: z.string().describe('Status comment').optional(),
|
||||
title: z.string().describe('Title').optional(),
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const PaymentSettingsSchema = z.object({
|
||||
allow: z.string().describe('Allow').optional(),
|
||||
channel: z.string().describe('Communication channel').optional(),
|
||||
denaiedReason: z.string().describe('Denaied reason').optional(),
|
||||
deny: z.string().describe('Deny').optional(),
|
||||
paymentType: z.number().int().positive().describe('Payment type').optional(),
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
AddressSchema,
|
||||
CommunicationDetailsSchema,
|
||||
EntitySchema,
|
||||
GenderSchema,
|
||||
OrganisationSchema,
|
||||
} from '@isa/common/data-access';
|
||||
import z from 'zod';
|
||||
|
||||
export const ShippingAddressSchema = z
|
||||
.object({
|
||||
address: AddressSchema.describe('Address').optional(),
|
||||
agentomment: z.string().describe('Agentomment').optional(),
|
||||
communicationDetails: CommunicationDetailsSchema.describe('Communication details').optional(),
|
||||
firstName: z.string().describe('First name').optional(),
|
||||
gender: GenderSchema.describe('Gender').optional(),
|
||||
lastName: z.string().describe('Last name').optional(),
|
||||
organisation: OrganisationSchema.describe('Organisation information').optional(),
|
||||
title: z.string().describe('Title').optional(),
|
||||
type: z.number().describe('Type').optional(),
|
||||
validated: z.string().describe('Validated').optional(),
|
||||
validationResult: z.number().describe('Validation result').optional(),
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
|
||||
export type ShippingAddress = z.infer<typeof ShippingAddressSchema>;
|
||||
13
libs/crm/data-access/src/lib/schemas/user.schema.ts
Normal file
13
libs/crm/data-access/src/lib/schemas/user.schema.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { EntitySchema, GenderSchema } from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const UserSchema = z
|
||||
.object({
|
||||
email: z.string().email().describe('Email address').optional(),
|
||||
firstName: z.string().describe('First name').optional(),
|
||||
gender: GenderSchema.describe('Gender').optional(),
|
||||
lastName: z.string().describe('Last name').optional(),
|
||||
name: z.string().describe('Name').optional(),
|
||||
title: z.string().describe('Title').optional(),
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
@@ -1,6 +1,7 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { CustomerService } from '@generated/swagger/crm-api';
|
||||
import {
|
||||
Customer,
|
||||
FetchCustomerCardsInput,
|
||||
FetchCustomerCardsSchema,
|
||||
FetchCustomerInput,
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
takeUntilAborted,
|
||||
} from '@isa/common/data-access';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { BonusCardInfo, Customer } from '../models';
|
||||
import { BonusCardInfo } from '../models';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
|
||||
@@ -1,79 +1,79 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { ShippingAddressService as GeneratedShippingAddressService } from '@generated/swagger/crm-api';
|
||||
import {
|
||||
FetchCustomerShippingAddressesInput,
|
||||
FetchCustomerShippingAddressesSchema,
|
||||
FetchShippingAddressInput,
|
||||
FetchShippingAddressSchema,
|
||||
} from '../schemas';
|
||||
import {
|
||||
catchResponseArgsErrorPipe,
|
||||
ListResponseArgs,
|
||||
ResponseArgs,
|
||||
takeUntilAborted,
|
||||
} from '@isa/common/data-access';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ShippingAddress } from '../models';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ShippingAddressService {
|
||||
#shippingAddressService = inject(GeneratedShippingAddressService);
|
||||
#logger = logger(() => ({
|
||||
service: 'ShippingAddressService',
|
||||
}));
|
||||
|
||||
async fetchCustomerShippingAddresses(
|
||||
params: FetchCustomerShippingAddressesInput,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ListResponseArgs<ShippingAddress>> {
|
||||
this.#logger.info('Fetching customer shipping addresses from API');
|
||||
const { customerId, take, skip } =
|
||||
FetchCustomerShippingAddressesSchema.parse(params);
|
||||
|
||||
let req$ = this.#shippingAddressService
|
||||
.ShippingAddressGetShippingAddresses({ customerId, take, skip })
|
||||
.pipe(catchResponseArgsErrorPipe());
|
||||
|
||||
if (abortSignal) {
|
||||
req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await firstValueFrom(req$);
|
||||
this.#logger.debug('Successfully fetched customer shipping addresses');
|
||||
return res as ListResponseArgs<ShippingAddress>;
|
||||
} catch (error) {
|
||||
this.#logger.error('Error fetching customer shipping addresses', error);
|
||||
return {
|
||||
result: [],
|
||||
totalCount: 0,
|
||||
} as unknown as ListResponseArgs<ShippingAddress>;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchShippingAddress(
|
||||
params: FetchShippingAddressInput,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ResponseArgs<ShippingAddress>> {
|
||||
this.#logger.info('Fetching shipping address from API');
|
||||
const { shippingAddressId } = FetchShippingAddressSchema.parse(params);
|
||||
|
||||
let req$ = this.#shippingAddressService
|
||||
.ShippingAddressGetShippingaddress(shippingAddressId)
|
||||
.pipe(catchResponseArgsErrorPipe());
|
||||
|
||||
if (abortSignal) {
|
||||
req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await firstValueFrom(req$);
|
||||
this.#logger.debug('Successfully fetched shipping address');
|
||||
return res as ResponseArgs<ShippingAddress>;
|
||||
} catch (error) {
|
||||
this.#logger.error('Error fetching shipping address', error);
|
||||
return undefined as unknown as ResponseArgs<ShippingAddress>;
|
||||
}
|
||||
}
|
||||
}
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { ShippingAddressService as GeneratedShippingAddressService } from '@generated/swagger/crm-api';
|
||||
import {
|
||||
FetchCustomerShippingAddressesInput,
|
||||
FetchCustomerShippingAddressesSchema,
|
||||
FetchShippingAddressInput,
|
||||
FetchShippingAddressSchema,
|
||||
ShippingAddress,
|
||||
} from '../schemas';
|
||||
import {
|
||||
catchResponseArgsErrorPipe,
|
||||
ListResponseArgs,
|
||||
ResponseArgs,
|
||||
takeUntilAborted,
|
||||
} from '@isa/common/data-access';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ShippingAddressService {
|
||||
#shippingAddressService = inject(GeneratedShippingAddressService);
|
||||
#logger = logger(() => ({
|
||||
service: 'ShippingAddressService',
|
||||
}));
|
||||
|
||||
async fetchCustomerShippingAddresses(
|
||||
params: FetchCustomerShippingAddressesInput,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ListResponseArgs<ShippingAddress>> {
|
||||
this.#logger.info('Fetching customer shipping addresses from API');
|
||||
const { customerId, take, skip } =
|
||||
FetchCustomerShippingAddressesSchema.parse(params);
|
||||
|
||||
let req$ = this.#shippingAddressService
|
||||
.ShippingAddressGetShippingAddresses({ customerId, take, skip })
|
||||
.pipe(catchResponseArgsErrorPipe());
|
||||
|
||||
if (abortSignal) {
|
||||
req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await firstValueFrom(req$);
|
||||
this.#logger.debug('Successfully fetched customer shipping addresses');
|
||||
return res as ListResponseArgs<ShippingAddress>;
|
||||
} catch (error) {
|
||||
this.#logger.error('Error fetching customer shipping addresses', error);
|
||||
return {
|
||||
result: [],
|
||||
totalCount: 0,
|
||||
} as unknown as ListResponseArgs<ShippingAddress>;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchShippingAddress(
|
||||
params: FetchShippingAddressInput,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ResponseArgs<ShippingAddress>> {
|
||||
this.#logger.info('Fetching shipping address from API');
|
||||
const { shippingAddressId } = FetchShippingAddressSchema.parse(params);
|
||||
|
||||
let req$ = this.#shippingAddressService
|
||||
.ShippingAddressGetShippingaddress(shippingAddressId)
|
||||
.pipe(catchResponseArgsErrorPipe());
|
||||
|
||||
if (abortSignal) {
|
||||
req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await firstValueFrom(req$);
|
||||
this.#logger.debug('Successfully fetched shipping address');
|
||||
return res as ResponseArgs<ShippingAddress>;
|
||||
} catch (error) {
|
||||
this.#logger.error('Error fetching shipping address', error);
|
||||
return undefined as unknown as ResponseArgs<ShippingAddress>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user