feat: add data access and feature libraries for return process, including models, schemas, and routes

This commit is contained in:
Lorenz Hilpert
2025-03-20 21:25:20 +01:00
parent fbd5414e47
commit db7da0699e
91 changed files with 1477 additions and 67 deletions

View File

@@ -15,5 +15,10 @@
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"exportall.config.folderListener": [
"/libs/oms/data-access/src/lib/models",
"/libs/oms/data-access/src/lib/schemas",
"/libs/catalogue/data-access/src/lib/models"
]
}

View File

@@ -0,0 +1,7 @@
# catalogue-data-access
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test catalogue-data-access` to execute the unit tests.

View File

@@ -0,0 +1,34 @@
import nx from '@nx/eslint-plugin';
import baseConfig from '../../../eslint.config.mjs';
export default [
...baseConfig,
...nx.configs['flat/angular'],
...nx.configs['flat/angular-template'],
{
files: ['**/*.ts'],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'lib',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'lib',
style: 'kebab-case',
},
],
},
},
{
files: ['**/*.html'],
// Override or add rules here
rules: {},
},
];

View File

@@ -0,0 +1,21 @@
export default {
displayName: 'catalogue-data-access',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/libs/catalogue/data-access',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

View File

@@ -0,0 +1,20 @@
{
"name": "catalogue-data-access",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/catalogue/data-access/src",
"prefix": "lib",
"projectType": "library",
"tags": [],
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/catalogue/data-access/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}

View File

@@ -0,0 +1,2 @@
export * from './lib/catalouge-search.service';
export * from './lib/models';

View File

@@ -0,0 +1,21 @@
import { inject, Injectable } from '@angular/core';
import { SearchService } from '@generated/swagger/cat-search-api';
import { map, Observable } from 'rxjs';
import { Item } from './models';
@Injectable({ providedIn: 'root' })
export class CatalougeSearchService {
#searchService = inject(SearchService);
searchByEans(...ean: string[]): Observable<Item[]> {
return this.#searchService.SearchByEAN(ean).pipe(
map((res) => {
if (res.error) {
throw new Error(res.message);
}
return res.result as Item[];
}),
);
}
}

View File

@@ -0,0 +1,2 @@
export * from './catalouge-search.service';
export * from './models';

View File

@@ -0,0 +1,2 @@
export * from './item';
export * from './product';

View File

@@ -0,0 +1,7 @@
import { ItemDTO } from '@generated/swagger/cat-search-api';
import { Product } from './product';
export interface Item extends ItemDTO {
id: number;
product: Product;
}

View File

@@ -0,0 +1,7 @@
import { ProductDTO } from '@generated/swagger/cat-search-api';
export interface Product extends ProductDTO {
ean: string;
format: string;
formatDetails: string;
}

View File

@@ -0,0 +1,6 @@
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
setupZoneTestEnv({
errorOnUnknownElements: true,
errorOnUnknownProperties: true,
});

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es2022",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": [
"src/**/*.spec.ts",
"src/test-setup.ts",
"jest.config.ts",
"src/**/*.test.ts"
],
"include": ["src/**/*.ts"]
}

View File

@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"target": "es2016",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@@ -1,3 +1,4 @@
export * from './lib/response-args.schema';
export * from './lib/entity-cotnainer';
export * from './lib/entity-container.schema';
export * from './lib/result';

View File

@@ -1,6 +1,6 @@
import { z } from 'zod';
const EntityContainerSchema = z.object({
const _EntityContainerSchema = z.object({
id: z.number(),
uId: z.string().optional(),
displayLabel: z.string().optional(),
@@ -8,8 +8,8 @@ const EntityContainerSchema = z.object({
selected: z.boolean().optional(),
});
export function EntityContainer<T>(data: z.ZodType<T>) {
return EntityContainerSchema.extend({
export function EntityContainerSchema<T>(data: z.ZodType<T>) {
return _EntityContainerSchema.extend({
data: data.optional(),
});
}

View File

@@ -0,0 +1,7 @@
export interface EntityContainer<T> {
id: number;
displayName?: string;
data?: T;
enabled?: boolean;
selected?: boolean;
}

View File

@@ -1,5 +1,6 @@
export * from './lib/idb.storage-provider';
export * from './lib/local.storage-provider';
export * from './lib/session.storage-provider';
export * from './lib/signal-store-feature';
export * from './lib/storage-provider';
export * from './lib/storage';

View File

@@ -0,0 +1,26 @@
import { Type } from '@angular/core';
import { getState, patchState, signalStoreFeature, withHooks, withMethods } from '@ngrx/signals';
import { StorageProvider } from './storage-provider';
import { injectStorage } from './storage';
export function withStorage(storageKey: string, storageProvider: Type<StorageProvider>) {
return signalStoreFeature(
withMethods((store, storage = injectStorage(storageProvider)) => ({
storeState: () => {
const state = getState(store);
storage.set(storageKey, state);
},
restoreState: async () => {
const data = await storage.get(storageKey);
if (data) {
patchState(store, data);
}
},
})),
withHooks((store) => ({
onInit() {
store.restoreState();
},
})),
);
}

View File

@@ -18,7 +18,7 @@ export class Storage {
return this.storageProvider.set(key, value);
}
async get<T>(token: string | object, schema?: z.ZodType<T>): Promise<T | undefined> {
async get<T>(token: string | object, schema?: z.ZodType<T>): Promise<T | unknown> {
let key: string;
if (typeof token === 'string') {
@@ -31,13 +31,13 @@ export class Storage {
if (schema) {
return await schema.parse(data);
}
return undefined;
return data;
}
}
const storageMap = new WeakMap<Type<StorageProvider>, Storage>();
export function storage(storageProvider: Type<StorageProvider>): Storage {
export function injectStorage(storageProvider: Type<StorageProvider>): Storage {
if (storageMap.has(storageProvider)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return storageMap.get(storageProvider)!;

View File

@@ -2,7 +2,7 @@
<ui-item-row-data-row>
<ui-item-row-data-label>Email:</ui-item-row-data-label>
<ui-item-row-data-value>
<span class="">{{ communicationDetails().email }}</span>
<span>{{ communicationDetails().email }}</span>
</ui-item-row-data-value>
</ui-item-row-data-row>
<ui-item-row-data-row>

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
import { Buyer, Receipt } from '@feature/return/services';
import { Buyer, Receipt } from '@isa/oms/data-access';
import { ReceiptTypeTranslationPipe } from '@isa/oms/utils/translation';
import { ItemRowDataImports } from '@isa/ui/item-rows';

View File

@@ -1,6 +1,6 @@
import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
import { Receipt } from '@feature/return/services';
import { Receipt } from '@isa/oms/data-access';
import { ReceiptTypeTranslationPipe } from '@isa/oms/utils/translation';
import { ItemRowDataImports } from '@isa/ui/item-rows';
@@ -22,8 +22,6 @@ export class ReturnDetailsOrderGroupDataComponent {
shippingName = computed(() => {
const receipt = this.receipt();
return [receipt.shipping?.person?.firstName, receipt.shipping?.person?.lastName]
.filter(Boolean)
.join(' ');
return [receipt.shipping?.firstName, receipt.shipping?.lastName].filter(Boolean).join(' ');
});
}

View File

@@ -24,7 +24,7 @@
</div>
<div class="text-isa-neutral-900 flex flex-col gap-2" uiItemRowProdcutInfo>
<div class="flex flex-row gap-2 items-center justify-start">
<ng-icon [name]="formatIcons()"></ng-icon>
<ng-icon [name]="item().product.format | lowercase"></ng-icon>
<span class="isa-text-body-2-bold truncate">
{{ item().product.formatDetail }}
</span>

View File

@@ -1,21 +1,13 @@
import { CurrencyPipe, DatePipe } from '@angular/common';
import { CurrencyPipe, DatePipe, LowerCasePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, input, model } from '@angular/core';
import { ReceiptItem } from '@feature/return/services';
import {
isaArtikelTaschenbuch,
isaArtikelCd,
isaArtikelEbook,
isaArtikelKartoniert,
} from '@isa/icons';
import { ProductFormatIconGroup } from '@isa/icons';
import { ReceiptItem } from '@isa/oms/data-access';
import { ProductImageDirective } from '@isa/shared/product-image';
import { CheckboxComponent } from '@isa/ui/input-controls';
import { ItemRowComponent } from '@isa/ui/item-rows';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
const FORMAT_ICON_MAP: Record<string, string> = {
HC: 'isaArtikelTaschenbuch',
};
@Component({
selector: 'lib-return-details-order-group-item',
templateUrl: './return-details-order-group-item.component.html',
@@ -29,17 +21,12 @@ const FORMAT_ICON_MAP: Record<string, string> = {
NgIconComponent,
DatePipe,
CurrencyPipe,
LowerCasePipe,
],
providers: [
provideIcons({ isaArtikelTaschenbuch, isaArtikelCd, isaArtikelEbook, isaArtikelKartoniert }),
],
providers: [provideIcons(ProductFormatIconGroup)],
})
export class ReturnDetailsOrderGroupItemComponent {
item = input.required<ReceiptItem>();
selected = model(false);
formatIcons = computed(() => {
return FORMAT_ICON_MAP[this.item().product.format] || 'isaArtikelTaschenbuch';
});
}

View File

@@ -6,6 +6,7 @@
color="brand"
size="large"
[disabled]="selectedItems().length === 0"
(click)="startProcess()"
>
Rückgabe starten
</button>

View File

@@ -7,7 +7,7 @@ import {
signal,
untracked,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import {
ReturnDetailsDataComponent,
@@ -17,10 +17,12 @@ import {
ReturnDetailsOrderGroupItemComponent,
} from '@feature/return/containers';
import { z } from 'zod';
import { ReturnDetailsStore } from '@feature/return/services';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaActionPlus, isaActionMinus } from '@isa/icons';
import { ButtonComponent, TextButtonComponent } from '@isa/ui/buttons';
import { ReceiptItem, ReturnDetailsStore, ReturnProcessStore } from '@isa/oms/data-access';
import { injectActivatedProcessId } from '@isa/core/process';
@Component({
selector: 'lib-return-details-page',
@@ -41,7 +43,13 @@ import { ButtonComponent, TextButtonComponent } from '@isa/ui/buttons';
providers: [provideIcons({ isaActionPlus, isaActionMinus })],
})
export class DetailsPageComponent {
private _params = toSignal(inject(ActivatedRoute).params);
private processId = injectActivatedProcessId();
private _router = inject(Router);
private _activatedRoute = inject(ActivatedRoute);
params = toSignal(this._activatedRoute.params);
// DUMMY Implementierung
selectedItems = signal<number[]>([]);
@@ -50,8 +58,10 @@ export class DetailsPageComponent {
#returnDetailsStore = inject(ReturnDetailsStore);
#returnProcessStore = inject(ReturnProcessStore);
itemId = computed<number>(() => {
const params = this._params();
const params = this.params();
if (params) {
return z.coerce.number().parse(params['receiptId']);
}
@@ -88,11 +98,44 @@ export class DetailsPageComponent {
if (selected) {
const receiptResult = this.receiptResult();
if (receiptResult) {
const allIds = receiptResult.data?.items.map((item) => item.id) || [];
const allIds = receiptResult.data?.items?.map((item) => item.id) || [];
this.selectedItems.set(allIds);
}
} else {
this.selectedItems.set([]);
}
}
startProcess() {
const processId = this.processId();
const selectedItems = this.selectedItems();
if (!selectedItems.length || !processId) {
return;
}
const data = this.receiptResult().data;
if (!data) {
return;
}
const items = selectedItems.reduce((acc, id) => {
const item = data.items.find((item) => item.id === id);
if (item?.data) {
acc.push(item.data);
}
return acc;
}, [] as ReceiptItem[]);
this.#returnProcessStore.startProcess({
processId,
items: items.map((item) => ({
product: item.product,
quantity: item.quantity,
receiptId: data.id,
receiptItemId: item.id,
})),
});
this._router.navigate(['../../', 'process'], { relativeTo: this._activatedRoute });
}
}

View File

@@ -23,4 +23,8 @@ export const routes: Routes = [
},
],
},
{
path: 'process',
loadChildren: () => import('@isa/oms/feature/return-process').then((feat) => feat.routes),
},
];

View File

@@ -1,5 +1,3 @@
export * from './lib/return-search.service';
export * from './lib/return-search.store';
export * from './lib/return-details.service';
export * from './lib/return-details.store';
export * from './lib/schemas';

View File

@@ -1,4 +1,4 @@
import { EntityContainer } from '@isa/common/result';
import { EntityContainerSchema } from '@isa/common/result';
import { z } from 'zod';
import { BranchSchema } from './branch.schema';
import { OrderItemTypeSchema } from './order-item-type.schema';
@@ -9,7 +9,7 @@ import { PriceSchema } from './price.schema';
export const ReceiptItemSchema = z.object({
id: z.number(),
lineNumber: z.number(),
orderBranch: EntityContainer(BranchSchema),
orderBranch: EntityContainerSchema(BranchSchema),
orderItemType: OrderItemTypeSchema,
paymentStatus: PaymentStatusSchema,
product: ProductSchema,

View File

@@ -1,6 +1,6 @@
import { z } from 'zod';
import { BuyerSchema } from './buyer.schema';
import { EntityContainer } from '@isa/common/result';
import { EntityContainerSchema } from '@isa/common/result';
import { ReceiptItemSchema } from './receipt-item.schema';
import { ReceiptTypeSchema } from './receipt-type.schema';
import { OrderSchema } from './order.schema';
@@ -11,10 +11,10 @@ export const ReceiptSchema = z.object({
id: z.number(),
receiptType: ReceiptTypeSchema,
buyer: BuyerSchema,
items: EntityContainer(ReceiptItemSchema).array(),
items: EntityContainerSchema(ReceiptItemSchema).array(),
printedDate: z.coerce.date(),
receiptNumber: z.string(),
order: EntityContainer(OrderSchema),
order: EntityContainerSchema(OrderSchema),
billing: Payer2Schema,
shipping: ShippingAddress2Schema,
});

View File

@@ -1 +1,2 @@
export * from './lib/icons'
export * from './lib/icons';
export * from './lib/icon-groups';

View File

@@ -0,0 +1,16 @@
import {
isaArtikelCd,
isaArtikelEbook,
isaArtikelKartoniert,
isaArtikelDigital,
isaArtikelGame,
isaArtikelTaschenbuch,
isaArtikelSonstige,
isaArtikelTolino,
isaNavigationArtikelsuche,
} from './icons';
export const ProductFormatIconGroup = {
tb: isaArtikelTaschenbuch,
hc: isaArtikelKartoniert,
};

View File

@@ -116,17 +116,39 @@ export const isaFiliale =
export const isaFilialeLocation =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <path d="M12 13C13.6569 13 15 11.6569 15 10C15 8.34315 13.6569 7 12 7C10.3431 7 9 8.34315 9 10C9 11.6569 10.3431 13 12 13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 22C16 18 20 14.4183 20 10C20 5.58172 16.4183 2 12 2C7.58172 2 4 5.58172 4 10C4 14.4183 8 18 12 22Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
export const isaArtikelKartoniert =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <path fill-rule="evenodd" clip-rule="evenodd" d="M7.24787 2.5C7.25947 2.5 7.2711 2.5 7.28276 2.5L7.60047 2.5C8.34332 2.49999 8.95985 2.49998 9.46281 2.54033C9.98596 2.5823 10.4728 2.67271 10.9325 2.90269C11.3354 3.10427 11.6965 3.37467 12.0002 3.6995C12.3038 3.37467 12.665 3.10427 13.0679 2.90269C13.5275 2.67271 14.0144 2.5823 14.5375 2.54033C15.0405 2.49998 15.657 2.49999 16.3999 2.5L16.7525 2.5C17.111 2.49998 17.437 2.49996 17.7088 2.52177C18.0006 2.54518 18.3162 2.59847 18.6274 2.75419C19.0751 2.9782 19.4425 3.3374 19.6739 3.78334C19.8357 4.09512 19.891 4.41165 19.9152 4.70214C19.9376 4.97133 19.9376 5.29341 19.9375 5.64431L19.9375 15.0162C19.9375 15.3671 19.9375 15.6892 19.9151 15.9584C19.8909 16.2489 19.8356 16.5654 19.6739 16.8772C19.4425 17.3231 19.0751 17.6823 18.6273 17.9063C18.3161 18.062 18.0006 18.1153 17.7088 18.1387C17.4369 18.1605 17.111 18.1605 16.7524 18.1605L15.7637 18.1605C14.8337 18.1605 14.5735 18.1704 14.3521 18.2364C14.127 18.3035 13.9187 18.4132 13.7388 18.5585C13.5634 18.7 13.4135 18.9026 12.8969 19.6636L12.8274 19.7659C12.6413 20.04 12.3315 20.2042 12.0001 20.2042C11.6687 20.2042 11.3589 20.04 11.1728 19.7659L11.1033 19.6636C10.5867 18.9026 10.4368 18.7 10.2615 18.5585C10.0815 18.4132 9.87318 18.3035 9.64811 18.2364C9.42671 18.1704 9.1665 18.1605 8.23647 18.1605L7.24783 18.1605C6.88925 18.1605 6.56329 18.1605 6.29144 18.1387C5.99966 18.1153 5.68411 18.062 5.37287 17.9063C4.92515 17.6823 4.55774 17.3231 4.32635 16.8772C4.16457 16.5654 4.10926 16.2489 4.08509 15.9584C4.0627 15.6892 4.06272 15.3671 4.06275 15.0162L4.06281 5.67991C4.06281 5.67991 4.06281 5.67991 4.06281 5.67991C4.06281 5.66801 4.06281 5.65612 4.06281 5.64428C4.06279 5.29339 4.06276 4.97133 4.08516 4.70215C4.10933 4.41166 4.16464 4.09512 4.32642 3.78334C4.55781 3.33739 4.92522 2.9782 5.37293 2.75419C5.68418 2.59847 5.99972 2.54518 6.2915 2.52177C6.56335 2.49996 6.8893 2.49998 7.24787 2.5ZM6.26449 4.54428C6.26445 4.54427 6.26506 4.54398 6.26646 4.54347L6.26449 4.54428ZM6.26646 4.54347C6.27658 4.53983 6.32436 4.52556 6.45145 4.51536C6.63354 4.50075 6.87803 4.5 7.28276 4.5H7.56026C8.35352 4.5 8.88941 4.50075 9.30286 4.53392C9.70517 4.5662 9.90362 4.62429 10.0376 4.69131C10.3731 4.85916 10.6424 5.12525 10.8101 5.44839C10.8752 5.57377 10.9333 5.76196 10.9658 6.15304C10.9994 6.55634 11.0002 7.07998 11.0002 7.85982L11.0001 16.6511C10.7538 16.5122 10.492 16.401 10.2197 16.3198C9.68245 16.1596 9.10813 16.16 8.36282 16.1604C8.32125 16.1605 8.27913 16.1605 8.23647 16.1605H7.2827C6.87797 16.1605 6.63348 16.1598 6.45139 16.1451C6.3243 16.1349 6.2767 16.1207 6.26659 16.1171C6.19397 16.0805 6.13794 16.0243 6.10332 15.9593C6.0995 15.9476 6.08735 15.9024 6.07821 15.7925C6.06355 15.6164 6.06275 15.3789 6.06275 14.9806L6.06281 5.67992C6.06281 5.2816 6.06362 5.04409 6.07827 4.86798C6.08742 4.75806 6.09956 4.71289 6.10339 4.7012C6.13801 4.63617 6.19384 4.58011 6.26646 4.54347ZM13.0001 16.6511C13.2464 16.5122 13.5082 16.401 13.7805 16.3198C14.3178 16.1596 14.8921 16.16 15.6374 16.1604C15.679 16.1605 15.7211 16.1605 15.7637 16.1605H16.7175C17.1222 16.1605 17.3667 16.1598 17.5488 16.1451C17.6759 16.1349 17.7235 16.1207 17.7336 16.1171C17.8062 16.0805 17.8623 16.0243 17.8969 15.9593C17.9007 15.9476 17.9129 15.9024 17.922 15.7925C17.9367 15.6164 17.9375 15.3789 17.9375 14.9806L17.9375 5.67991C17.9375 5.28159 17.9367 5.04409 17.9221 4.86798C17.9129 4.75807 17.9008 4.7129 17.8969 4.7012C17.8623 4.63617 17.8063 4.58004 17.7337 4.5434C17.7236 4.53976 17.676 4.52556 17.5489 4.51536C17.3668 4.50075 17.1223 4.5 16.7176 4.5H16.4401C15.6468 4.5 15.1109 4.50075 14.6975 4.53392C14.2952 4.5662 14.0967 4.62429 13.9628 4.69131C13.6273 4.85916 13.3579 5.12525 13.1902 5.44839C13.1252 5.57377 13.0671 5.76196 13.0345 6.15304C13.001 6.55634 13.0002 7.07998 13.0002 7.85983L13.0001 16.6511ZM17.7358 16.1162C17.7358 16.1162 17.735 16.1166 17.7336 16.1171L17.7358 16.1162Z" fill="currentColor"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M4.375 6.81248C3.82272 6.81248 3.375 7.2602 3.375 7.81249V17.8125C3.375 18.3648 3.82272 18.8125 4.375 18.8125H11.0594C11.2261 18.6929 11.4289 18.625 11.6424 18.625H12.3659C12.5793 18.625 12.7821 18.6929 12.9488 18.8125H19.625C20.1773 18.8125 20.625 18.3648 20.625 17.8125V7.81248C20.625 7.2602 20.1773 6.81248 19.625 6.81248H18.1797V4.81248H19.625C21.2819 4.81248 22.625 6.15563 22.625 7.81248V17.8125C22.625 19.4693 21.2819 20.8125 19.625 20.8125H13.4002C13.1792 20.8125 13 20.9917 13 21.2127V21.625C13 21.722 12.983 21.8151 12.9518 21.9014C12.822 22.3031 12.445 22.5938 12 22.5938C11.5549 22.5938 11.1778 22.303 11.0481 21.9011C11.017 21.8149 11 21.7219 11 21.625V21.5972C11 21.596 11 21.5949 11 21.5938V21.2015C10.9988 20.9865 10.8242 20.8125 10.6089 20.8125H4.375C2.71815 20.8125 1.375 19.4694 1.375 17.8125V7.81249C1.375 6.15563 2.71814 4.81248 4.375 4.81248H5.71094V6.81248H4.375Z" fill="currentColor"/></svg>';
export const isaArtikelKartoniert = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.24787 2.5C7.25947 2.5 7.2711 2.5 7.28276 2.5L7.60047 2.5C8.34332 2.49999 8.95985 2.49998 9.46281 2.54033C9.98596 2.5823 10.4728 2.67271 10.9325 2.90269C11.3354 3.10427 11.6965 3.37467 12.0002 3.6995C12.3038 3.37467 12.665 3.10427 13.0679 2.90269C13.5275 2.67271 14.0144 2.5823 14.5375 2.54033C15.0405 2.49998 15.657 2.49999 16.3999 2.5L16.7525 2.5C17.111 2.49998 17.437 2.49996 17.7088 2.52177C18.0006 2.54518 18.3162 2.59847 18.6274 2.75419C19.0751 2.9782 19.4425 3.3374 19.6739 3.78334C19.8357 4.09512 19.891 4.41165 19.9152 4.70214C19.9376 4.97133 19.9376 5.29341 19.9375 5.64431L19.9375 15.0162C19.9375 15.3671 19.9375 15.6892 19.9151 15.9584C19.8909 16.2489 19.8356 16.5654 19.6739 16.8772C19.4425 17.3231 19.0751 17.6823 18.6273 17.9063C18.3161 18.062 18.0006 18.1153 17.7088 18.1387C17.4369 18.1605 17.111 18.1605 16.7524 18.1605L15.7637 18.1605C14.8337 18.1605 14.5735 18.1704 14.3521 18.2364C14.127 18.3035 13.9187 18.4132 13.7388 18.5585C13.5634 18.7 13.4135 18.9026 12.8969 19.6636L12.8274 19.7659C12.6413 20.04 12.3315 20.2042 12.0001 20.2042C11.6687 20.2042 11.3589 20.04 11.1728 19.7659L11.1033 19.6636C10.5867 18.9026 10.4368 18.7 10.2615 18.5585C10.0815 18.4132 9.87318 18.3035 9.64811 18.2364C9.42671 18.1704 9.1665 18.1605 8.23647 18.1605L7.24783 18.1605C6.88925 18.1605 6.56329 18.1605 6.29144 18.1387C5.99966 18.1153 5.68411 18.062 5.37287 17.9063C4.92515 17.6823 4.55774 17.3231 4.32635 16.8772C4.16457 16.5654 4.10926 16.2489 4.08509 15.9584C4.0627 15.6892 4.06272 15.3671 4.06275 15.0162L4.06281 5.67991C4.06281 5.67991 4.06281 5.67991 4.06281 5.67991C4.06281 5.66801 4.06281 5.65612 4.06281 5.64428C4.06279 5.29339 4.06276 4.97133 4.08516 4.70215C4.10933 4.41166 4.16464 4.09512 4.32642 3.78334C4.55781 3.33739 4.92522 2.9782 5.37293 2.75419C5.68418 2.59847 5.99972 2.54518 6.2915 2.52177C6.56335 2.49996 6.8893 2.49998 7.24787 2.5ZM6.26449 4.54428C6.26445 4.54427 6.26506 4.54398 6.26646 4.54347L6.26449 4.54428ZM6.26646 4.54347C6.27658 4.53983 6.32436 4.52556 6.45145 4.51536C6.63354 4.50075 6.87803 4.5 7.28276 4.5H7.56026C8.35352 4.5 8.88941 4.50075 9.30286 4.53392C9.70517 4.5662 9.90362 4.62429 10.0376 4.69131C10.3731 4.85916 10.6424 5.12525 10.8101 5.44839C10.8752 5.57377 10.9333 5.76196 10.9658 6.15304C10.9994 6.55634 11.0002 7.07998 11.0002 7.85982L11.0001 16.6511C10.7538 16.5122 10.492 16.401 10.2197 16.3198C9.68245 16.1596 9.10813 16.16 8.36282 16.1604C8.32125 16.1605 8.27913 16.1605 8.23647 16.1605H7.2827C6.87797 16.1605 6.63348 16.1598 6.45139 16.1451C6.3243 16.1349 6.2767 16.1207 6.26659 16.1171C6.19397 16.0805 6.13794 16.0243 6.10332 15.9593C6.0995 15.9476 6.08735 15.9024 6.07821 15.7925C6.06355 15.6164 6.06275 15.3789 6.06275 14.9806L6.06281 5.67992C6.06281 5.2816 6.06362 5.04409 6.07827 4.86798C6.08742 4.75806 6.09956 4.71289 6.10339 4.7012C6.13801 4.63617 6.19384 4.58011 6.26646 4.54347ZM13.0001 16.6511C13.2464 16.5122 13.5082 16.401 13.7805 16.3198C14.3178 16.1596 14.8921 16.16 15.6374 16.1604C15.679 16.1605 15.7211 16.1605 15.7637 16.1605H16.7175C17.1222 16.1605 17.3667 16.1598 17.5488 16.1451C17.6759 16.1349 17.7235 16.1207 17.7336 16.1171C17.8062 16.0805 17.8623 16.0243 17.8969 15.9593C17.9007 15.9476 17.9129 15.9024 17.922 15.7925C17.9367 15.6164 17.9375 15.3789 17.9375 14.9806L17.9375 5.67991C17.9375 5.28159 17.9367 5.04409 17.9221 4.86798C17.9129 4.75807 17.9008 4.7129 17.8969 4.7012C17.8623 4.63617 17.8063 4.58004 17.7337 4.5434C17.7236 4.53976 17.676 4.52556 17.5489 4.51536C17.3668 4.50075 17.1223 4.5 16.7176 4.5H16.4401C15.6468 4.5 15.1109 4.50075 14.6975 4.53392C14.2952 4.5662 14.0967 4.62429 13.9628 4.69131C13.6273 4.85916 13.3579 5.12525 13.1902 5.44839C13.1252 5.57377 13.0671 5.76196 13.0345 6.15304C13.001 6.55634 13.0002 7.07998 13.0002 7.85983L13.0001 16.6511ZM17.7358 16.1162C17.7358 16.1162 17.735 16.1166 17.7336 16.1171L17.7358 16.1162Z" fill="#212529"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.375 6.81248C3.82272 6.81248 3.375 7.2602 3.375 7.81249V17.8125C3.375 18.3648 3.82272 18.8125 4.375 18.8125H11.0594C11.2261 18.6929 11.4289 18.625 11.6424 18.625H12.3659C12.5793 18.625 12.7821 18.6929 12.9488 18.8125H19.625C20.1773 18.8125 20.625 18.3648 20.625 17.8125V7.81248C20.625 7.2602 20.1773 6.81248 19.625 6.81248H18.1797V4.81248H19.625C21.2819 4.81248 22.625 6.15563 22.625 7.81248V17.8125C22.625 19.4693 21.2819 20.8125 19.625 20.8125H13.4002C13.1792 20.8125 13 20.9917 13 21.2127V21.625C13 21.722 12.983 21.8151 12.9518 21.9014C12.822 22.3031 12.445 22.5938 12 22.5938C11.5549 22.5938 11.1778 22.303 11.0481 21.9011C11.017 21.8149 11 21.7219 11 21.625V21.5972C11 21.596 11 21.5949 11 21.5938V21.2015C10.9988 20.9865 10.8242 20.8125 10.6089 20.8125H4.375C2.71815 20.8125 1.375 19.4694 1.375 17.8125V7.81249C1.375 6.15563 2.71814 4.81248 4.375 4.81248H5.71094V6.81248H4.375Z" fill="#212529"/>
</svg>`;
export const isaArtikelEbook =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <path d="M15 2V3.4C15 3.96005 15 4.24008 14.891 4.45399C14.7951 4.64215 14.6422 4.79513 14.454 4.89101C14.2401 5 13.9601 5 13.4 5H10.6C10.0399 5 9.75992 5 9.54601 4.89101C9.35785 4.79513 9.20487 4.64215 9.10899 4.45399C9 4.24008 9 3.96005 9 3.4V2M7.2 22H16.8C17.9201 22 18.4802 22 18.908 21.782C19.2843 21.5903 19.5903 21.2843 19.782 20.908C20 20.4802 20 19.9201 20 18.8V5.2C20 4.07989 20 3.51984 19.782 3.09202C19.5903 2.71569 19.2843 2.40973 18.908 2.21799C18.4802 2 17.9201 2 16.8 2H7.2C6.0799 2 5.51984 2 5.09202 2.21799C4.71569 2.40973 4.40973 2.71569 4.21799 3.09202C4 3.51984 4 4.0799 4 5.2V18.8C4 19.9201 4 20.4802 4.21799 20.908C4.40973 21.2843 4.71569 21.5903 5.09202 21.782C5.51984 22 6.07989 22 7.2 22Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
export const isaArtikelTaschenbuch = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.0322 3.01143C17.7488 2.98828 17.3766 2.9875 16.8 2.9875H8.8C7.94342 2.9875 7.36113 2.98828 6.91104 3.02506C6.47262 3.06088 6.24842 3.1258 6.09202 3.20549C5.7157 3.39724 5.40973 3.7032 5.21799 4.07952C5.1383 4.23592 5.07337 4.46013 5.03755 4.89854C5.00078 5.34863 5 5.93093 5 6.78751V14.5351C5.58835 14.1948 6.27143 14 7 14H19V5.1875C19 4.61095 18.9992 4.23868 18.9761 3.95534C18.9539 3.68368 18.9162 3.58296 18.891 3.53351C18.7951 3.34535 18.6422 3.19237 18.454 3.0965C18.4045 3.07131 18.3038 3.03363 18.0322 3.01143ZM21 5.14894C21 4.6218 21 4.16704 20.9694 3.79248C20.9371 3.39713 20.8658 3.00391 20.673 2.62553C20.3854 2.06105 19.9265 1.60211 19.362 1.31449C18.9836 1.12169 18.5904 1.05038 18.195 1.01807C17.8205 0.987471 17.3657 0.987486 16.8385 0.987504L8.7587 0.987504C7.95373 0.987492 7.28937 0.987481 6.74817 1.0317C6.18608 1.07762 5.66937 1.17619 5.18404 1.42348C4.43139 1.80697 3.81947 2.41889 3.43597 3.17154C3.18868 3.65688 3.09012 4.17358 3.04419 4.73568C2.99998 5.27687 2.99999 5.94124 3 6.74621V16.2413C2.99999 16.7796 2.99998 17.255 3.0132 17.6727C3.00446 17.7806 3 17.8898 3 18C3 18.1085 3.01727 18.2129 3.04922 18.3107C3.09721 18.8501 3.19735 19.3476 3.43597 19.816C3.81947 20.5686 4.43139 21.1805 5.18404 21.564C5.66937 21.8113 6.18608 21.9099 6.74817 21.9558C7.28936 22 7.95372 22 8.75868 22H16.8386C17.3657 22 17.8205 22 18.195 21.9694C18.5904 21.9371 18.9836 21.8658 19.362 21.673C19.9265 21.3854 20.3854 20.9265 20.673 20.362C20.8658 19.9836 20.9371 19.5904 20.9694 19.195C20.9967 18.8613 20.9997 18.4638 21 18.0083C21 18.0055 21 18.0028 21 18V17.9062C21 17.8838 21 17.8613 21 17.8386V5.14894ZM5.01679 17.7397C5.02196 17.865 5.02872 17.9808 5.03755 18.089C5.07337 18.5274 5.1383 18.7516 5.21799 18.908C5.40974 19.2843 5.7157 19.5903 6.09202 19.782C6.24842 19.8617 6.47262 19.9266 6.91104 19.9625C7.36113 19.9992 7.94342 20 8.8 20H16.8C17.3766 20 17.7488 19.9992 18.0322 19.9761C18.3038 19.9539 18.4045 19.9162 18.454 19.891C18.6422 19.7951 18.7951 19.6422 18.891 19.454C18.9162 19.4046 18.9539 19.3038 18.9761 19.0322C18.9992 18.7488 19 18.3766 19 17.8V16H7C5.98366 16 5.14439 16.7581 5.01679 17.7397Z" fill="#212529"/>
<rect x="9" y="14.125" width="2" height="9.25" rx="1" fill="#212529"/>
</svg>`;
export const isaArtikelTaschenbuch =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <path fill-rule="evenodd" clip-rule="evenodd" d="M18.0322 3.01143C17.7488 2.98828 17.3766 2.9875 16.8 2.9875H8.8C7.94342 2.9875 7.36113 2.98828 6.91104 3.02506C6.47262 3.06088 6.24842 3.1258 6.09202 3.20549C5.7157 3.39724 5.40973 3.7032 5.21799 4.07952C5.1383 4.23592 5.07337 4.46013 5.03755 4.89854C5.00078 5.34863 5 5.93093 5 6.78751V14.5351C5.58835 14.1948 6.27143 14 7 14H19V5.1875C19 4.61095 18.9992 4.23868 18.9761 3.95534C18.9539 3.68368 18.9162 3.58296 18.891 3.53351C18.7951 3.34535 18.6422 3.19237 18.454 3.0965C18.4045 3.07131 18.3038 3.03363 18.0322 3.01143ZM21 5.14894C21 4.6218 21 4.16704 20.9694 3.79248C20.9371 3.39713 20.8658 3.00391 20.673 2.62553C20.3854 2.06105 19.9265 1.60211 19.362 1.31449C18.9836 1.12169 18.5904 1.05038 18.195 1.01807C17.8205 0.987471 17.3657 0.987486 16.8385 0.987504L8.7587 0.987504C7.95373 0.987492 7.28937 0.987481 6.74817 1.0317C6.18608 1.07762 5.66937 1.17619 5.18404 1.42348C4.43139 1.80697 3.81947 2.41889 3.43597 3.17154C3.18868 3.65688 3.09012 4.17358 3.04419 4.73568C2.99998 5.27687 2.99999 5.94124 3 6.74621V16.2413C2.99999 16.7796 2.99998 17.255 3.0132 17.6727C3.00446 17.7806 3 17.8898 3 18C3 18.1085 3.01727 18.2129 3.04922 18.3107C3.09721 18.8501 3.19735 19.3476 3.43597 19.816C3.81947 20.5686 4.43139 21.1805 5.18404 21.564C5.66937 21.8113 6.18608 21.9099 6.74817 21.9558C7.28936 22 7.95372 22 8.75868 22H16.8386C17.3657 22 17.8205 22 18.195 21.9694C18.5904 21.9371 18.9836 21.8658 19.362 21.673C19.9265 21.3854 20.3854 20.9265 20.673 20.362C20.8658 19.9836 20.9371 19.5904 20.9694 19.195C20.9967 18.8613 20.9997 18.4638 21 18.0083C21 18.0055 21 18.0028 21 18V17.9062C21 17.8838 21 17.8613 21 17.8386V5.14894ZM5.01679 17.7397C5.02196 17.865 5.02872 17.9808 5.03755 18.089C5.07337 18.5274 5.1383 18.7516 5.21799 18.908C5.40974 19.2843 5.7157 19.5903 6.09202 19.782C6.24842 19.8617 6.47262 19.9266 6.91104 19.9625C7.36113 19.9992 7.94342 20 8.8 20H16.8C17.3766 20 17.7488 19.9992 18.0322 19.9761C18.3038 19.9539 18.4045 19.9162 18.454 19.891C18.6422 19.7951 18.7951 19.6422 18.891 19.454C18.9162 19.4046 18.9539 19.3038 18.9761 19.0322C18.9992 18.7488 19 18.3766 19 17.8V16H7C5.98366 16 5.14439 16.7581 5.01679 17.7397Z" fill="currentColor"/> <rect x="9" y="14.125" width="2" height="9.25" rx="1" fill="currentColor"/></svg>';
export const isaArtikelCd = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M21 18V12C21 7.02945 16.9706 3.00002 12 3.00002C7.02944 3.00002 3 7.02945 3 12V18M5.5 21C4.11929 21 3 19.8807 3 18.5V16.5C3 15.1193 4.11929 14 5.5 14C6.88071 14 8 15.1193 8 16.5V18.5C8 19.8807 6.88071 21 5.5 21ZM18.5 21C17.1193 21 16 19.8807 16 18.5V16.5C16 15.1193 17.1193 14 18.5 14C19.8807 14 21 15.1193 21 16.5V18.5C21 19.8807 19.8807 21 18.5 21Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
export const isaArtikelCd =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <path d="M21 18V12C21 7.02945 16.9706 3.00002 12 3.00002C7.02944 3.00002 3 7.02945 3 12V18M5.5 21C4.11929 21 3 19.8807 3 18.5V16.5C3 15.1193 4.11929 14 5.5 14C6.88071 14 8 15.1193 8 16.5V18.5C8 19.8807 6.88071 21 5.5 21ZM18.5 21C17.1193 21 16 19.8807 16 18.5V16.5C16 15.1193 17.1193 14 18.5 14C19.8807 14 21 15.1193 21 16.5V18.5C21 19.8807 19.8807 21 18.5 21Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
export const isaArtikelEbook = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M15 2V3.4C15 3.96005 15 4.24008 14.891 4.45399C14.7951 4.64215 14.6422 4.79513 14.454 4.89101C14.2401 5 13.9601 5 13.4 5H10.6C10.0399 5 9.75992 5 9.54601 4.89101C9.35785 4.79513 9.20487 4.64215 9.10899 4.45399C9 4.24008 9 3.96005 9 3.4V2M7.2 22H16.8C17.9201 22 18.4802 22 18.908 21.782C19.2843 21.5903 19.5903 21.2843 19.782 20.908C20 20.4802 20 19.9201 20 18.8V5.2C20 4.07989 20 3.51984 19.782 3.09202C19.5903 2.71569 19.2843 2.40973 18.908 2.21799C18.4802 2 17.9201 2 16.8 2H7.2C6.0799 2 5.51984 2 5.09202 2.21799C4.71569 2.40973 4.40973 2.71569 4.21799 3.09202C4 3.51984 4 4.0799 4 5.2V18.8C4 19.9201 4 20.4802 4.21799 20.908C4.40973 21.2843 4.71569 21.5903 5.09202 21.782C5.51984 22 6.07989 22 7.2 22Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
export const isaArtikelGame = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 2L15.6 5.6C18 -0.7 24.7 6 18.4 8.4L22 12L18.4 15.6C16 9.3 9.3 16 15.6 18.4L12 22L8.4 18.4C6 24.7 -0.7 18 5.6 15.6L2 12L5.6 8.4C8 14.7 14.7 8 8.4 5.6L12 2Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
export const isaArtikelDigital = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 7V12M8 8.99951C7.37209 9.83526 7 10.8742 7 12C7 14.7614 9.23858 17 12 17C14.7614 17 17 14.7614 17 12C17 10.8742 16.6279 9.83526 16 8.99951M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
export const isaArtikelTolino = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 17.5H12.01M7.2 22H16.8C17.9201 22 18.4802 22 18.908 21.782C19.2843 21.5903 19.5903 21.2843 19.782 20.908C20 20.4802 20 19.9201 20 18.8V5.2C20 4.07989 20 3.51984 19.782 3.09202C19.5903 2.71569 19.2843 2.40973 18.908 2.21799C18.4802 2 17.9201 2 16.8 2H7.2C6.0799 2 5.51984 2 5.09202 2.21799C4.71569 2.40973 4.40973 2.71569 4.21799 3.09202C4 3.51984 4 4.0799 4 5.2V18.8C4 19.9201 4 20.4802 4.21799 20.908C4.40973 21.2843 4.71569 21.5903 5.09202 21.782C5.51984 22 6.07989 22 7.2 22ZM12.5 17.5C12.5 17.7761 12.2761 18 12 18C11.7239 18 11.5 17.7761 11.5 17.5C11.5 17.2239 11.7239 17 12 17C12.2761 17 12.5 17.2239 12.5 17.5Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
export const isaArtikelSonstige = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M21 10H3M16 2V6M8 2V6M7.8 22H16.2C17.8802 22 18.7202 22 19.362 21.673C19.9265 21.3854 20.3854 20.9265 20.673 20.362C21 19.7202 21 18.8802 21 17.2V8.8C21 7.11984 21 6.27976 20.673 5.63803C20.3854 5.07354 19.9265 4.6146 19.362 4.32698C18.7202 4 17.8802 4 16.2 4H7.8C6.11984 4 5.27976 4 4.63803 4.32698C4.07354 4.6146 3.6146 5.07354 3.32698 5.63803C3 6.27976 3 7.11984 3 8.8V17.2C3 18.8802 3 19.7202 3.32698 20.362C3.6146 20.9265 4.07354 21.3854 4.63803 21.673C5.27976 22 6.11984 22 7.8 22Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
export const isaDeliveryWarenausgabe =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <path fill-rule="evenodd" clip-rule="evenodd" d="M11.597 1.18496C11.8628 1.13029 12.137 1.13029 12.4028 1.18496C12.7101 1.24816 12.9847 1.40199 13.203 1.52428C13.2234 1.53567 13.2432 1.54678 13.2626 1.55753L12.7844 2.41815L13.2626 1.55753L20.6626 5.66864C20.683 5.68 20.7041 5.69161 20.7257 5.70351C20.9568 5.83082 21.2474 5.99089 21.4706 6.23306L21.1561 6.52287L21.4706 6.23307C21.6636 6.44245 21.8096 6.69061 21.8989 6.96096C22.0022 7.27366 22.001 7.60545 22.0001 7.86932C22 7.89398 21.9999 7.91804 21.9999 7.94145L21.9999 12.5C21.9999 13.0523 21.5522 13.5 20.9999 13.5C20.4476 13.5 19.9999 13.0523 19.9999 12.5L19.9999 8.69947L17.0027 10.3645C16.9919 10.3708 16.981 10.3769 16.9699 10.3828L12.9999 12.5884L12.9999 20.3005L13.5142 20.0147C13.997 19.7465 14.6058 19.9205 14.874 20.4032C15.1423 20.886 14.9683 21.4948 14.4855 21.763L13.2626 22.4425C13.2432 22.4532 13.2234 22.4643 13.203 22.4757C12.9847 22.598 12.7101 22.7518 12.4028 22.815C12.137 22.8697 11.8628 22.8697 11.597 22.815C11.2897 22.7518 11.0151 22.598 10.7967 22.4757C10.7764 22.4643 10.7565 22.4532 10.7372 22.4425L3.33721 18.3314C3.31676 18.32 3.29568 18.3084 3.27408 18.2965C3.04295 18.1692 2.75232 18.0091 2.52915 17.7669C2.3362 17.5576 2.19018 17.3094 2.10085 17.039C1.99753 16.7263 1.99874 16.3946 1.9997 16.1307C1.99979 16.106 1.99988 16.082 1.99988 16.0586V7.94145C1.99988 7.91804 1.99979 7.89398 1.9997 7.86932C1.99874 7.60545 1.99753 7.27367 2.10085 6.96096C2.19017 6.69061 2.3362 6.44245 2.52915 6.23307C2.75232 5.99089 3.04294 5.83082 3.27406 5.70352C3.29567 5.69162 3.31675 5.68001 3.33721 5.66864L3.33721 5.66864L6.99892 3.63435C7.0085 3.62882 7.01816 3.62346 7.0279 3.61826L10.7372 1.55753C10.7565 1.54678 10.7764 1.53567 10.7967 1.52428C11.0151 1.40199 11.2897 1.24816 11.597 1.18496ZM7.49988 5.64396L5.05902 7L11.9998 10.856L14.4407 9.49998L7.49988 5.64396ZM16.4998 8.35602L9.55901 4.5L11.7085 3.30584C11.86 3.22165 11.9374 3.17911 11.9951 3.15128C11.9968 3.15048 11.9984 3.14972 11.9999 3.14899C12.0014 3.14971 12.003 3.15048 12.0047 3.15128C12.0624 3.17911 12.1397 3.22165 12.2913 3.30584L18.9407 6.99998L16.4998 8.35602ZM10.9999 12.5884L3.99988 8.6995V16.0586C3.99988 16.2415 4.0003 16.3352 4.00438 16.403C4.0045 16.405 4.00462 16.4069 4.00474 16.4087C4.00628 16.4097 4.0079 16.4108 4.00959 16.4118C4.06688 16.4483 4.14855 16.4942 4.30849 16.583L3.82285 17.4572L4.3085 16.583L10.9999 20.3005L10.9999 12.5884Z" fill="currentColor"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M22.7997 17.7121C23.0668 18.0678 23.0668 18.5572 22.7997 18.9129L20.6411 21.7879C20.3095 22.2296 19.6827 22.3188 19.241 21.9872C18.7993 21.6556 18.7101 21.0287 19.0417 20.5871L19.9987 19.3125L17.042 19.3125C16.4897 19.3125 16.042 18.8648 16.042 18.3125C16.042 17.7602 16.4897 17.3125 17.042 17.3125L19.9987 17.3125L19.0417 16.0379C18.7101 15.5963 18.7993 14.9694 19.241 14.6378C19.6827 14.3062 20.3095 14.3954 20.6411 14.8371L22.7997 17.7121Z" fill="currentColor"/></svg>';

View File

@@ -0,0 +1,7 @@
# oms-data-access
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test oms-data-access` to execute the unit tests.

View File

@@ -0,0 +1,34 @@
import nx from '@nx/eslint-plugin';
import baseConfig from '../../../eslint.config.mjs';
export default [
...baseConfig,
...nx.configs['flat/angular'],
...nx.configs['flat/angular-template'],
{
files: ['**/*.ts'],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'lib',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'lib',
style: 'kebab-case',
},
],
},
},
{
files: ['**/*.html'],
// Override or add rules here
rules: {},
},
];

View File

@@ -0,0 +1,21 @@
export default {
displayName: 'oms-data-access',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/libs/oms/data-access',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

View File

@@ -0,0 +1,20 @@
{
"name": "oms-data-access",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/oms/data-access/src",
"prefix": "lib",
"projectType": "library",
"tags": [],
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/oms/data-access/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}

View File

@@ -0,0 +1,6 @@
export * from './lib/models';
export * from './lib/schemas';
export * from './lib/return-details.service';
export * from './lib/return-details.store';
export * from './lib/return-process.service';
export * from './lib/return-process.store';

View File

@@ -0,0 +1,6 @@
export * from './models';
export * from './return-details.service';
export * from './return-details.store';
export * from './return-process.service';
export * from './return-process.store';
export * from './schemas';

View File

@@ -0,0 +1,5 @@
import { BuyerDTO } from '@generated/swagger/oms-api';
export interface Buyer extends BuyerDTO {
buyerNumber: string;
}

View File

@@ -0,0 +1,8 @@
export * from './buyer';
export * from './product';
export * from './quantity';
export * from './receipt-item';
export * from './receipt';
export * from './return-process-answers';
export * from './return-process-question';
export * from './return-process';

View File

@@ -0,0 +1,7 @@
import { ProductDTO } from '@generated/swagger/oms-api';
export interface Product extends ProductDTO {
ean: string;
format: string;
formatDetails: string;
}

View File

@@ -0,0 +1,5 @@
import { QuantityDTO } from '@generated/swagger/oms-api';
export interface Quantity extends QuantityDTO {
quantity: number;
}

View File

@@ -0,0 +1,9 @@
import { ReceiptItemDTO } from '@generated/swagger/oms-api';
import { Product } from './product';
import { Quantity } from './quantity';
export interface ReceiptItem extends ReceiptItemDTO {
id: number;
product: Product;
quantity: Quantity;
}

View File

@@ -0,0 +1,10 @@
import { ReceiptDTO } from '@generated/swagger/oms-api';
import { EntityContainer } from '@isa/common/result';
import { ReceiptItem } from './receipt-item';
import { Buyer } from './buyer';
export interface Receipt extends ReceiptDTO {
id: number;
items: EntityContainer<ReceiptItem>[];
buyer: Buyer;
}

View File

@@ -0,0 +1,3 @@
export interface ReturnProcessAnswers {
answerId: string;
}

View File

@@ -0,0 +1,31 @@
export const ReturnProcessQuestionType = {
Select: 'select',
Product: 'product',
} as const;
export type ReturnProcessQuestionType =
(typeof ReturnProcessQuestionType)[keyof typeof ReturnProcessQuestionType];
export interface ReturnProcessQuestionBase {
id: string;
description: string;
type: ReturnProcessQuestionType;
}
export interface ReturnProcessSelectQuestion extends ReturnProcessQuestionBase {
type: typeof ReturnProcessQuestionType.Select;
options: ReturnProcessQuestionSelectOptions[];
}
export interface ReturnProcessQuestionSelectOptions {
label: string;
value: string;
nextQuestionId?: string;
}
export interface ReturnProcessProductQuestion extends ReturnProcessQuestionBase {
type: typeof ReturnProcessQuestionType.Product;
nextQuestionId?: string;
}
export type ReturnProcessQuestion = ReturnProcessSelectQuestion | ReturnProcessProductQuestion;

View File

@@ -0,0 +1,7 @@
export interface ItemToReturn {
id: number;
processId: number;
receiptId: number;
receiptItemId: number;
quantity: number;
}

View File

@@ -1,21 +1,14 @@
import { inject, Injectable } from '@angular/core';
import {
FetchReturnDetails,
FetchReturnDetailsSchema,
} from './schemas/fetch-return-details.schema';
import { FetchReturnDetails, FetchReturnDetailsSchema } from './schemas';
import { map, Observable, throwError } from 'rxjs';
import { ReceiptService } from '@generated/swagger/oms-api';
import { safeParse } from '@isa/utils/z-safe-parse';
import {
ResponseArgsOfReceipt,
ResponseArgsOfReceiptSchema,
} from './schemas/response-args-of-receipt.schema';
import { Receipt } from './models';
@Injectable({ providedIn: 'root' })
export class ReturnDetailsService {
#receiptService = inject(ReceiptService);
fetchReturnDetails(params: FetchReturnDetails): Observable<ResponseArgsOfReceipt> {
fetchReturnDetails(params: FetchReturnDetails): Observable<Receipt> {
try {
const parsed = FetchReturnDetailsSchema.parse(params);
@@ -27,7 +20,7 @@ export class ReturnDetailsService {
throw new Error('Failed to fetch return details');
}
return safeParse(ResponseArgsOfReceiptSchema, res);
return res.result as Receipt;
}),
);
} catch (error) {

View File

@@ -1,12 +1,12 @@
import { patchState, signalStore, type, withMethods } from '@ngrx/signals';
import { addEntity, entityConfig, updateEntity, withEntities } from '@ngrx/signals/entities';
import { Result, ResultStatus } from '@isa/common/result';
import { Receipt } from './schemas';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { pipe, switchMap, tap } from 'rxjs';
import { inject } from '@angular/core';
import { ReturnDetailsService } from './return-details.service';
import { tapResponse } from '@ngrx/operators';
import { Receipt } from './models';
export type ReturnResult = Result<Receipt | undefined> & { id: number };
@@ -53,7 +53,7 @@ export const ReturnDetailsStore = signalStore(
returnDetailsService.fetchReturnDetails({ receiptId }).pipe(
tapResponse({
next(value) {
store._fetchSuccess(receiptId, value.result);
store._fetchSuccess(receiptId, value);
},
error(error) {
store._fetchError(receiptId, error);

View File

@@ -0,0 +1,46 @@
import { Injectable } from '@angular/core';
import { Product, ReturnProcessQuestion, ReturnProcessQuestionType } from './models';
import { Observable, of } from 'rxjs';
const bookAndCalendarQuestions: ReturnProcessQuestion[] = [
{
id: 'item_condition',
description: 'Wie ist der Artkelzustand?',
type: ReturnProcessQuestionType.Select,
options: [
{ label: 'Neuwertig/Originalverpackt', value: 'ovp', nextQuestionId: 'return_reason' },
{ label: 'Beschädigt/Fehldruck', value: 'damaged' },
],
},
{
id: 'return_reason',
description: 'Warum möchtest du den Artikel zurücksenden?',
type: ReturnProcessQuestionType.Select,
options: [
{ label: 'Gefällt nicht/Wiederruf', value: 'dislike' },
{ label: 'Fehllieferung', value: 'wrong_item', nextQuestionId: 'delivered_item' },
],
},
{
id: 'delivered_item',
description: 'Welcher Artikel wurde geliefert?',
type: ReturnProcessQuestionType.Product,
},
];
@Injectable({ providedIn: 'root' })
export class ReturnProcessService {
returnProcessQuestions(
product: Product,
): Observable<{ startWithQuestionId: string; questions: ReturnProcessQuestion[] }> {
if (product.format === 'HC' || product.format === 'TB') {
return of({ startWithQuestionId: 'item_condition', questions: bookAndCalendarQuestions });
}
return of({ startWithQuestionId: '', questions: [] });
}
eligibleForReturn(): Observable<boolean> {
throw new Error('Method not implemented.');
}
}

View File

@@ -0,0 +1,92 @@
import { patchState, signalStore, withComputed, withHooks, withMethods } from '@ngrx/signals';
import { withEntities, setAllEntities, updateEntity } from '@ngrx/signals/entities';
import { IDBStorageProvider, withStorage } from '@isa/core/storage';
import { computed, effect, inject } from '@angular/core';
import { ProcessService } from '@isa/core/process';
import { Product, Quantity } from './models';
export interface ReturnProcess {
id: number;
processId: number;
receiptId: number;
receiptItemId: number;
product: Product;
quantity: Quantity;
answers: Record<string, unknown>;
}
export type StartProcess = {
processId: number;
items: Pick<ReturnProcess, 'receiptId' | 'receiptItemId' | 'quantity' | 'product'>[];
};
export const ReturnProcessStore = signalStore(
{ providedIn: 'root' },
withStorage('return-process', IDBStorageProvider),
withEntities<ReturnProcess>(),
withComputed((store) => ({
nextId: computed(() => Math.max(0, ...store.ids().map(Number)) + 1),
})),
withMethods((store) => ({
removeAllEntitiesByProcessId: (...processIds: number[]) => {
const entitiesToRemove = store
.entities()
.filter((entity) => processIds.includes(entity.processId));
patchState(store, setAllEntities(entitiesToRemove));
store.storeState();
},
setAnswer: (id: number, question: string, answer: unknown) => {
const entity = store.entityMap()[id];
if (entity) {
const answers = { ...entity.answers, [question]: answer };
patchState(store, updateEntity({ id: entity.id, changes: { answers } }));
store.storeState();
}
},
removeAnswer: (id: number, question: string) => {
const entity = store.entityMap()[id];
if (entity) {
const answers = { ...entity.answers };
delete answers[question];
patchState(store, updateEntity({ id: entity.id, changes: { answers } }));
store.storeState();
}
},
})),
withMethods((store) => ({
startProcess: (params: StartProcess) => {
store.removeAllEntitiesByProcessId(params.processId);
const entities: ReturnProcess[] = [];
const nextId = store.nextId();
for (let i = 0; i < params.items.length; i++) {
const item = params.items[i];
entities.push({
id: nextId + i,
processId: params.processId,
receiptId: item.receiptId,
receiptItemId: item.receiptItemId,
quantity: item.quantity,
product: item.product,
answers: {},
});
}
patchState(store, setAllEntities(entities));
store.storeState();
},
})),
withHooks((store, processService = inject(ProcessService)) => ({
onInit() {
effect(() => {
const processIds = processService.ids();
const entities = store.entities().find((entity) => !processIds.includes(entity.processId));
if (entities) {
store.removeAllEntitiesByProcessId(entities.processId);
}
});
},
})),
);

View File

@@ -0,0 +1,7 @@
import { z } from 'zod';
export const FetchReturnDetailsSchema = z.object({
receiptId: z.number(),
});
export type FetchReturnDetails = z.infer<typeof FetchReturnDetailsSchema>;

View File

@@ -0,0 +1 @@
export * from './fetch-return-details';

View File

@@ -0,0 +1,6 @@
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
setupZoneTestEnv({
errorOnUnknownElements: true,
errorOnUnknownProperties: true,
});

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es2022",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": [
"src/**/*.spec.ts",
"src/test-setup.ts",
"jest.config.ts",
"src/**/*.test.ts"
],
"include": ["src/**/*.ts"]
}

View File

@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"target": "es2016",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@@ -0,0 +1,7 @@
# oms-feature-return-process
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test oms-feature-return-process` to execute the unit tests.

View File

@@ -0,0 +1,34 @@
import nx from '@nx/eslint-plugin';
import baseConfig from '../../../../eslint.config.mjs';
export default [
...baseConfig,
...nx.configs['flat/angular'],
...nx.configs['flat/angular-template'],
{
files: ['**/*.ts'],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'lib',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'lib',
style: 'kebab-case',
},
],
},
},
{
files: ['**/*.html'],
// Override or add rules here
rules: {},
},
];

View File

@@ -0,0 +1,21 @@
export default {
displayName: 'oms-feature-return-process',
preset: '../../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../../coverage/libs/oms/feature/return-process',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

View File

@@ -0,0 +1,20 @@
{
"name": "oms-feature-return-process",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/oms/feature/return-process/src",
"prefix": "lib",
"projectType": "library",
"tags": [],
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/oms/feature/return-process/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}

View File

@@ -0,0 +1 @@
export * from './lib/routes';

View File

@@ -0,0 +1,18 @@
@if (returnProcesses(); as processes) {
<div class="return-process-header">
<div>
<button uiButton color="tertiary" size="small">
<ng-icon name="isaActionChevronLeft"></ng-icon>
<span>zurück</span>
</button>
</div>
<h1 class="isa-text-subtitle-1-regular text-center">
Wählen Sie den Artikelzustand und den <br />
Rückgabegrund aus
</h1>
<div></div>
</div>
@for (process of processes; track process.id) {
<lib-return-process-item [returnProcessId]="process.id"></lib-return-process-item>
}
}

View File

@@ -0,0 +1,14 @@
:host {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1.5rem;
align-self: stretch;
}
.return-process-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
align-self: stretch;
}

View File

@@ -0,0 +1,31 @@
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
import { ButtonComponent } from '@isa/ui/buttons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaActionChevronLeft } from '@isa/icons';
import { ReturnProcess, ReturnProcessStore } from '@isa/oms/data-access';
import { injectActivatedProcessId } from '@isa/core/process';
import { ReturnProcessItemComponent } from './return-process-item/return-process-item.component';
@Component({
selector: 'lib-return-process-feature',
templateUrl: './return-process-feature.component.html',
styleUrls: ['./return-process-feature.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ButtonComponent, NgIconComponent, ReturnProcessItemComponent],
providers: [provideIcons({ isaActionChevronLeft })],
})
export class ReturnProcessFeatureComponent {
#returnProcessStore = inject(ReturnProcessStore);
processId = injectActivatedProcessId();
returnProcesses = computed<ReturnProcess[]>(() => {
const processId = this.processId();
if (!processId) {
throw new Error('No process id found');
}
return this.#returnProcessStore.entities().filter((process) => process.processId === processId);
});
}

View File

@@ -0,0 +1,28 @@
@if (returnProcess(); as process) {
<div class="return-process-item-header">
<h3 class="isa-text-subtitle-2-bold">Rückgabe informationen</h3>
</div>
<div class="return-process-item-body">
<img
sharedProductImage
[ean]="process.product.ean"
[alt]="process.product.name"
class="return-process-item-body__product-image w-14"
/>
<div class="return-process-item-body__product-info">
<div class="return-process-item-body__product-info-contibutors isa-text-body-2-bold">
{{ process.product.contributors }}
</div>
<div class="return-process-item-body__product-info-name isa-text-body-2-regular">
{{ process.product.name }}
</div>
<div class="return-process-item-body__product-info-format">
<ng-icon [name]="process.product.format | lowercase"></ng-icon>
<span class="truncate">{{ process.product.formatDetail }}</span>
</div>
</div>
<lib-return-process-questions
[returnProcessId]="returnProcessId()"
></lib-return-process-questions>
</div>
}

View File

@@ -0,0 +1,49 @@
:host {
display: flex;
padding: 1.5rem;
flex-direction: column;
align-items: flex-start;
gap: 1.5rem;
align-self: stretch;
border-radius: 1rem;
background: #fff;
@apply text-neutral-900;
}
.return-process-item-header {
display: flex;
padding-bottom: 1.5rem;
justify-content: space-between;
align-items: center;
align-self: stretch;
@apply border-b border-neutral-300;
}
.return-process-item-body {
display: flex;
align-items: flex-start;
gap: 1.5rem;
width: 100%;
}
.return-process-item-body__product-info {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
flex: 1 0 0;
}
.return-process-item-body__product-info-format {
display: flex;
align-items: center;
gap: 0.5rem;
align-self: stretch;
}
[lib-return-process-questions] {
flex-shrink: 0;
flex-grow: 1;
align-self: stretch;
}

View File

@@ -0,0 +1,26 @@
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
import { ProductImageDirective } from '@isa/shared/product-image';
import { ReturnProcessStore } from '@isa/oms/data-access';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { ProductFormatIconGroup } from '@isa/icons';
import { LowerCasePipe } from '@angular/common';
import { ReturnProcessQuestionsComponent } from '../return-process-questions/return-process-questions.component';
@Component({
selector: 'lib-return-process-item',
templateUrl: './return-process-item.component.html',
styleUrls: ['./return-process-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [ProductImageDirective, NgIconComponent, LowerCasePipe, ReturnProcessQuestionsComponent],
providers: [provideIcons(ProductFormatIconGroup)],
})
export class ReturnProcessItemComponent {
returnProcessStore = inject(ReturnProcessStore);
returnProcessId = input.required<number>();
returnProcess = computed(() => {
return this.returnProcessStore.entityMap()[this.returnProcessId()];
});
}

View File

@@ -0,0 +1,35 @@
<div class="flex items-start gap-6 flex-shrink-0 justify-between">
<div>
{{ question().description }}
</div>
<div class="flex flex-col gap-6">
<div class="text-right">
<ui-text-field>
<input
placeholder="EAN eingeben / scannen"
type="text"
[formControl]="control"
size="small"
/>
<button uiTextButton size="small" color="strong" (click)="check()">Prüfen</button>
</ui-text-field>
</div>
</div>
</div>
@if (product(); as p) {
<div class="mb-6 flex flex-col gap-4">
<div class="isa-text-body-2-bold">Gelieferter Artikel:</div>
<div class="flex flex-row gap-4">
<img class="w-[3.375rem]" sharedProductImage [ean]="p.ean" [alt]="p.name" />
<div class="flex flex-col gap-2">
<div class="isa-text-body-2-bold">{{ p.contributors }}</div>
<div class="text-isa-secondary-900 isa-text-body-2-regular">{{ p.name }}</div>
</div>
</div>
</div>
<div class="isa-text-caption-regular">
Falls es sich um den falschen Artikel handelt, geben sie die EAN erneut ein, oder Scannen Sie
das Produkt erneut.
</div>
}

View File

@@ -0,0 +1,3 @@
:host {
@apply flex flex-col gap-6 justify-between text-isa-neutral-900;
}

View File

@@ -0,0 +1,49 @@
import { ChangeDetectionStrategy, Component, inject, input, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { Product, ReturnProcessProductQuestion, ReturnProcessStore } from '@isa/oms/data-access';
import { TextButtonComponent } from '@isa/ui/buttons';
import { TextFieldComponent } from '@isa/ui/input-controls';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { filter, pipe, switchMap } from 'rxjs';
import { CatalougeSearchService } from '@isa/catalogue/data-access';
import { tapResponse } from '@ngrx/operators';
import { ProductImageDirective } from '@isa/shared/product-image';
@Component({
selector: 'lib-return-process-product-question',
templateUrl: './return-process-product-question.component.html',
styleUrls: ['./return-process-product-question.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule, TextFieldComponent, TextButtonComponent, ProductImageDirective],
})
export class ReturnProcessProductQuestionComponent {
// #returnProcessStore = inject(ReturnProcessStore);
#catalogueSearchService = inject(CatalougeSearchService);
processId = input.required<number>();
question = input.required<ReturnProcessProductQuestion>();
control = new FormControl<string | undefined>('9783551557438', [Validators.required]);
product = signal<Product | undefined>(undefined);
check = rxMethod<void>(
pipe(
filter(() => this.control.valid),
switchMap(() =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.#catalogueSearchService.searchByEans(this.control.value!).pipe(
tapResponse({
next: (value) => {
this.product.set(value[0].product);
},
error: (error) => {
console.error(error);
},
}),
),
),
),
);
}

View File

@@ -0,0 +1,21 @@
@for (question of renderQuestions(); track question.id; let last = $last) {
<div class="item-content" [class.with-border-bottom]="!last">
@switch (question.type) {
@case ('select') {
<lib-return-process-select-question
[processId]="returnProcessId()"
[question]="question"
></lib-return-process-select-question>
}
@case ('product') {
<lib-return-process-product-question
[processId]="returnProcessId()"
[question]="question"
></lib-return-process-product-question>
}
@default {
{{ question | json }}
}
}
</div>
}

View File

@@ -0,0 +1,12 @@
:host {
@apply flex flex-col items-stretch justify-between gap-6;
@apply text-isa-neutral-900;
}
.item-content {
padding-bottom: 1.5rem;
&.with-border-bottom {
@apply border-b border-solid border-isa-neutral-300;
}
}

View File

@@ -0,0 +1,79 @@
import { JsonPipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import {
ReturnProcess,
ReturnProcessQuestion,
ReturnProcessQuestionType,
ReturnProcessService,
ReturnProcessStore,
} from '@isa/oms/data-access';
import { filter, switchMap } from 'rxjs';
import { ReturnProcessSelectQuestionComponent } from './return-process-select-question/return-process-select-question.component';
import { ReturnProcessProductQuestionComponent } from './return-process-product-question/return-process-product-question.component';
@Component({
selector: 'lib-return-process-questions',
templateUrl: './return-process-questions.component.html',
styleUrls: ['./return-process-questions.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [JsonPipe, ReturnProcessSelectQuestionComponent, ReturnProcessProductQuestionComponent],
})
export class ReturnProcessQuestionsComponent {
#returnProcessStore = inject(ReturnProcessStore);
#returnProcessSerivce = inject(ReturnProcessService);
returnProcessId = input.required<number>();
returnProcess = computed<ReturnProcess | undefined>(() => {
const returnProcessId = this.returnProcessId();
return this.#returnProcessStore.entityMap()[returnProcessId];
});
questions = toSignal(
toObservable(this.returnProcess).pipe(
filter((returnProcess) => !!returnProcess),
switchMap((returnProcess) =>
this.#returnProcessSerivce.returnProcessQuestions(returnProcess.product),
),
),
);
renderQuestions = computed(() => {
const answers = this.returnProcess()?.answers;
if (!answers) {
return [];
}
const questions = this.questions();
if (!questions) {
return [];
}
let questionId: string | undefined = questions?.startWithQuestionId;
const result: ReturnProcessQuestion[] = [];
while (questionId) {
const question = questions?.questions.find((q) => q.id === questionId);
if (question) {
result.push(question);
if (question.type === ReturnProcessQuestionType.Select) {
const option = question.options.find((o) => o.value === answers[question.id]);
questionId = option?.nextQuestionId;
} else if (question.type === ReturnProcessQuestionType.Product) {
questionId = answers[question.id] ? question.nextQuestionId : undefined;
} else {
console.error('Unknown question type', question);
break;
}
} else {
break;
}
}
return result;
});
}

View File

@@ -0,0 +1,10 @@
<div>
{{ question().description }}
</div>
<ui-chips [formControl]="control">
@for (option of question().options; track option.value) {
<ui-chip-option [value]="option.value">
{{ option.label }}
</ui-chip-option>
}
</ui-chips>

View File

@@ -0,0 +1,7 @@
:host {
display: flex;
align-items: center;
gap: 1.5rem;
flex-shrink: 0;
@apply justify-between;
}

View File

@@ -0,0 +1,41 @@
import { ChangeDetectionStrategy, Component, effect, inject, input } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { ReturnProcessSelectQuestion, ReturnProcessStore } from '@isa/oms/data-access';
import { ChipsComponent, ChipOptionComponent } from '@isa/ui/input-controls';
@Component({
selector: 'lib-return-process-select-question',
templateUrl: './return-process-select-question.component.html',
styleUrls: ['./return-process-select-question.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [ChipsComponent, ChipOptionComponent, ReactiveFormsModule],
})
export class ReturnProcessSelectQuestionComponent {
#returnProcessStore = inject(ReturnProcessStore);
processId = input.required<number>();
question = input.required<ReturnProcessSelectQuestion>();
control = new FormControl<string | undefined>(undefined, [Validators.required]);
constructor() {
this.control.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
if (this.control.valid) {
this.#returnProcessStore.setAnswer(this.processId(), this.question().id, value);
} else {
this.#returnProcessStore.removeAnswer(this.processId(), this.question().id);
}
});
effect(() => {
const value =
this.#returnProcessStore.entityMap()[this.processId()]?.answers[this.question().id];
if (value !== this.control.value) {
this.control.setValue(value as string);
}
});
}
}

View File

@@ -0,0 +1,4 @@
import { Route } from '@angular/router';
import { ReturnProcessFeatureComponent } from './return-process-feature.component';
export const routes: Route[] = [{ path: '', component: ReturnProcessFeatureComponent }];

View File

@@ -0,0 +1,6 @@
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
setupZoneTestEnv({
errorOnUnknownElements: true,
errorOnUnknownProperties: true,
});

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es2022",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": [
"src/**/*.spec.ts",
"src/test-setup.ts",
"jest.config.ts",
"src/**/*.test.ts"
],
"include": ["src/**/*.ts"]
}

View File

@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"module": "commonjs",
"target": "es2016",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@@ -2,3 +2,5 @@ export * from './lib/checkbox/checkbox.component';
export * from './lib/dropdown/dropdown.component';
export * from './lib/dropdown/dropdown.types';
export * from './lib/text-field/text-field.component';
export * from './lib/chips/chips.component';
export * from './lib/chips/chip-option.component';

View File

@@ -0,0 +1,18 @@
.ui-chip-option {
display: inline-flex;
height: 2.5rem;
min-width: 10rem;
padding: 0.5rem 1.25rem;
justify-content: center;
align-items: center;
flex-shrink: 0;
border-radius: 6.25rem;
@apply border border-solid border-neutral-600;
@apply isa-text-body-2-bold text-isa-neutral-600 cursor-pointer;
}
.ui-chip-option__selected {
@apply bg-isa-neutral-200 text-isa-neutral-800;
}

View File

@@ -0,0 +1,37 @@
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
input,
ViewEncapsulation,
} from '@angular/core';
import { ChipsComponent } from './chips.component';
@Component({
selector: 'ui-chip-option',
template: `<ng-content></ng-content>`,
styleUrls: ['./chip-option.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
host: { '[class]': '["ui-chip-option", selectedClass()]', '(click)': 'toggle()' },
})
export class ChipOptionComponent<T> {
private host = inject(ChipsComponent, { host: true });
selected = computed(() => {
this.host.value();
return this.host['selectionModel'].isSelected(this.value());
});
selectedClass = computed(() => {
return this.selected() ? 'ui-chip-option__selected' : '';
});
value = input.required<T>();
toggle() {
this.host.select(this.value());
}
}

View File

@@ -0,0 +1,5 @@
.ui-chips {
display: flex;
align-items: center;
gap: 0.75rem;
}

View File

@@ -0,0 +1,81 @@
import {
ChangeDetectionStrategy,
Component,
computed,
model,
ViewEncapsulation,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { SelectionModel } from '@angular/cdk/collections';
@Component({
selector: 'ui-chips',
template: `<ng-content></ng-content>`,
styleUrls: ['./chips.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
'[class]': '["ui-chips", disabledClass()]',
},
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: ChipsComponent,
multi: true,
},
],
})
export class ChipsComponent<T> implements ControlValueAccessor {
value = model<T>();
disabled = model(false);
disabledClass = computed(() => {
return this.disabled() ? 'ui-chips__disabled' : '';
});
private selectionModel: SelectionModel<T>;
onChange?: (value: T) => void;
onTouched?: () => void;
constructor() {
this.selectionModel = new SelectionModel<T>(false);
}
writeValue(obj: unknown): void {
this.selectionModel?.select(obj as T);
this.value.set(obj as T);
}
registerOnChange(fn: () => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled.set(isDisabled);
}
select(value: T, options: { emitEvent: boolean } = { emitEvent: true }) {
this.selectionModel.select(value);
this.onTouched?.();
this.value.set(this.selectionModel.selected[0]);
if (options.emitEvent) {
this.onChange?.(this.selectionModel.selected[0]);
}
}
toggle(value: T, options: { emitEvent: boolean } = { emitEvent: true }) {
this.selectionModel.toggle(value);
this.onTouched?.();
this.value.set(this.selectionModel.selected[0]);
if (options.emitEvent) {
this.onChange?.(this.selectionModel.selected[0]);
}
}
}

View File

@@ -44,6 +44,7 @@
],
"@generated/swagger/wws-api": ["generated/swagger/wws-api/src/index.ts"],
"@hub/*": ["apps/isa-app/src/hub/*/index.ts"],
"@isa/catalogue/data-access": ["libs/catalogue/data-access/src/index.ts"],
"@isa/common/result": ["libs/common/result/src/index.ts"],
"@isa/core/config": ["libs/core/config/src/index.ts"],
"@isa/core/process": ["libs/core/process/src/index.ts"],
@@ -51,6 +52,10 @@
"@isa/core/scroll-position": ["libs/core/scroll-position/src/index.ts"],
"@isa/core/storage": ["libs/core/storage/src/index.ts"],
"@isa/icons": ["libs/icons/src/index.ts"],
"@isa/oms/data-access": ["libs/oms/data-access/src/index.ts"],
"@isa/oms/feature/return-process": [
"libs/oms/feature/return-process/src/index.ts"
],
"@isa/oms/utils/translation": ["libs/oms/utils/translation/src/index.ts"],
"@isa/shared/filter": ["libs/shared/filter/src/index.ts"],
"@isa/shared/product-image": ["libs/shared/product-image/src/index.ts"],