Compare commits

...

6 Commits

Author SHA1 Message Date
Lorenz Hilpert
a608e4d02e chore: migrate packages to latest versions using Nx migrations
- Update Nx from 21.3.2 to 21.5.2
- Update Angular packages from 20.1.x to 20.2.4
- Update NgRx from 20.0.0 to 20.0.1
- Update development dependencies (@swc/core, @types/node, etc.)
- Resolve dependency conflicts with canvas and jsdom versions
- Apply Nx migrations for tsConfig options and Angular CLI updates
- Fix TypeScript error in customer-order-search.store.ts for tuple destructuring
- Update package overrides for compatibility with updated dependencies

All tests passing and build successful after migration.
2025-09-17 13:49:11 +02:00
Lorenz Hilpert
afc6351509 chore: disable markdown format on save in VSCode settings
Added formatOnSave: false for markdown files to prevent automatic formatting that may conflict with document structure preferences.
2025-09-17 11:50:03 +02:00
Lorenz Hilpert
c5d057e3a7 Merged PR 1950: 5343-Filter-NumberRange
Related work items: #5343
2025-09-16 09:54:29 +00:00
Nino Righi
707802ce0d Merged PR 1944: feat(checkout-reward): #5258
- feat(loyalty): add loyalty program feature with list and navigation
- fix(isa-app-side-menu): Update customer expand to signals
- feat(catalogue-data-access): add searchLoyaltyItems method with comprehensive test coverage
- feat(project-structure): migrate loyalty system to reward-based architecture
- feat(checkout-reward): add query settings resolver and catalog resource
- feat(swagger-cat-search-api): Swagger Update
- feat(checkout-reward): update API call and prepare filter integration

Refs: #5258
2025-09-12 10:44:42 +00:00
Lorenz Hilpert
e00de7598d feat(crm): add crm-data-access library with initial component and tests
- Introduced the crm-data-access library with a basic component.
- Added necessary configuration files, including ESLint and TypeScript settings.
- Implemented unit tests for the component to ensure functionality.

Refs: #5254
2025-09-11 20:13:56 +02:00
Lorenz Hilpert
516b7748c2 chore: update .gitignore and package-lock.json to include new files 2025-09-09 11:19:04 +02:00
174 changed files with 14536 additions and 8988 deletions

185
.github/prompts/plan.prompt.md vendored Normal file
View File

@@ -0,0 +1,185 @@
---
mode: agent
tools: [ 'edit', 'search', 'usages', 'vscodeAPI', 'problems', 'changes', 'fetch', 'githubRepo', 'Nx Mcp Server', 'context7' ]
description: Plan Mode - Research and create a detailed implementation plan before making any changes.
---
# Plan Mode
You are now operating in **Plan Mode** - a research and planning phase that ensures thorough analysis before implementation. Plan mode is **ALWAYS ACTIVE** when using this prompt. You must follow these strict guidelines for every request:
## Phase 1: Research & Analysis (MANDATORY)
### ALLOWED Operations:
- ✅ Read files using Read, Glob, Grep tools
- ✅ Search documentation and codebases
- ✅ Analyze existing patterns and structures
- ✅ Use WebFetch for documentation research
- ✅ List and explore project structure
- ✅ Use Nx/Angular/Context7 MCP tools for workspace analysis
- ✅ Review dependencies and configurations
### FORBIDDEN Operations:
-**NEVER** create, edit, or modify any files
-**NEVER** run commands that change system state
-**NEVER** make commits or push changes
-**NEVER** install packages or modify configurations
-**NEVER** run build/test commands during planning
## Phase 2: Plan Presentation (REQUIRED FORMAT)
After thorough research, present your plan using this exact structure:
```markdown
## 📋 Implementation Plan
### 🎯 Objective
[Clear statement of what will be accomplished]
### 🔍 Research Summary
- **Current State**: [What exists now]
- **Requirements**: [What needs to be built/changed]
- **Constraints**: [Limitations and considerations]
### 📁 Files to be Modified/Created
1. **File**: `path/to/file.ts`
- **Action**: Create/Modify/Delete
- **Purpose**: [Why this file needs changes]
- **Key Changes**: [Specific modifications planned]
2. **File**: `path/to/another-file.ts`
- **Action**: Create/Modify/Delete
- **Purpose**: [Why this file needs changes]
- **Key Changes**: [Specific modifications planned]
### 🏗️ Implementation Steps
1. **Step 1**: [Detailed description]
- Files affected: `file1.ts`, `file2.ts`
- Rationale: [Why this step is necessary]
2. **Step 2**: [Detailed description]
- Files affected: `file3.ts`
- Rationale: [Why this step is necessary]
3. **Step N**: [Continue numbering...]
### ⚠️ Risks & Considerations
- **Risk 1**: [Potential issue and mitigation]
- **Risk 2**: [Potential issue and mitigation]
### 🧪 Testing Strategy
- [How the changes will be tested]
- [Specific test files or approaches]
### 📚 Architecture Decisions
- **Pattern Used**: [Which architectural pattern will be followed]
- **Libraries/Dependencies**: [What will be used and why]
- **Integration Points**: [How this fits with existing code]
### ✅ Success Criteria
- [ ] Criterion 1
- [ ] Criterion 2
- [ ] All tests pass
- [ ] No lint errors
```
## Phase 3: Await Approval
After presenting the plan:
1. **STOP** all implementation activities
2. **WAIT** for explicit user approval
3. **DO NOT** proceed with any file changes
4. **RESPOND** to questions or plan modifications
5. **EXIT PLAN MODE** only when user explicitly says "execute", "implement", "go ahead", "approved", or similar approval language
## Phase 4: Implementation (After Exiting Plan Mode)
Once the user explicitly approves and you exit plan mode:
1. **PLAN MODE IS NOW DISABLED** - you can proceed with normal implementation
2. Use TodoWrite to create implementation todos
3. Follow the plan step-by-step
4. Update todos as you progress
5. Run tests and lint checks as specified
6. Provide progress updates
## Key Behavioral Rules
### Research Thoroughly
- Spend significant time understanding the codebase
- Look for existing patterns to follow
- Identify all dependencies and integration points
- Consider edge cases and error scenarios
### Be Comprehensive
- Plans should be detailed enough for another developer to implement
- Include all necessary file changes
- Consider testing, documentation, and deployment
- Address potential conflicts or breaking changes
### Show Your Work
- Explain reasoning behind architectural decisions
- Reference existing code patterns when applicable
- Cite documentation or best practices
- Provide alternatives when multiple approaches exist
### Safety First
- Never make changes during planning phase
- Always wait for explicit approval
- Flag potentially risky changes
- Suggest incremental implementation when complex
## Example Interactions
### Good Plan Mode Behavior:
```
User: "Add a dark mode toggle to the settings page"
Assistant: I'll research the current theming system and create a comprehensive plan for implementing dark mode.
[Extensive research using Read, Grep, Glob tools]
## 📋 Implementation Plan
[Follows complete format above]
Ready to proceed? Please approve this plan before I begin implementation.
```
### What NOT to do:
```
User: "Add a dark mode toggle"
Assistant: I'll add that right away!
[Immediately starts editing files - WRONG!]
```
## Integration with Existing Copilot Instructions
This plan mode respects all existing project patterns:
- Follows Angular + Nx workspace conventions
- Uses existing import path aliases
- Respects testing strategy (Jest/Vitest)
- Follows NgRx Signals patterns
- Adheres to logging and configuration patterns
- Maintains library conventions and file naming
Remember: **RESEARCH FIRST, PLAN THOROUGHLY, WAIT FOR APPROVAL, THEN IMPLEMENT**

1
.gitignore vendored
View File

@@ -75,3 +75,4 @@ vitest.config.*.timestamp*
.memory.json
nx.instructions.md
CLAUDE.md

192
.vscode/settings.json vendored
View File

@@ -1,92 +1,100 @@
{
"editor.accessibilitySupport": "off",
"typescript.tsdk": "node_modules/typescript/lib",
"exportall.config.exclude": [".test.", ".spec.", ".stories."],
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.validate": [
"json"
],
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[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",
"/libs/common/data-access/src/lib/models",
"/libs/common/data-access/src/lib/error",
"/libs/oms/data-access/src/lib/errors/return-process"
],
"github.copilot.chat.commitMessageGeneration.instructions": [
{
"file": ".github/commit-instructions.md"
}
],
"github.copilot.chat.codeGeneration.instructions": [
{
"file": ".vscode/llms/angular.txt"
},
{
"file": "docs/tech-stack.md"
},
{
"file": "docs/guidelines/code-style.md"
},
{
"file": "docs/guidelines/project-structure.md"
},
{
"file": "docs/guidelines/state-management.md"
},
{
"file": "docs/guidelines/testing.md"
}
],
"github.copilot.chat.testGeneration.instructions": [
{
"file": ".github/testing-instructions.md"
},
{
"file": "docs/tech-stack.md"
},
{
"file": "docs/guidelines/code-style.md"
},
{
"file": "docs/guidelines/testing.md"
}
],
"github.copilot.chat.reviewSelection.instructions": [
{
"file": ".github/copilot-instructions.md"
},
{
"file": ".github/review-instructions.md"
},
{
"file": "docs/tech-stack.md"
},
{
"file": "docs/guidelines/code-style.md"
},
{
"file": "docs/guidelines/project-structure.md"
},
{
"file": "docs/guidelines/state-management.md"
},
{
"file": "docs/guidelines/testing.md"
}
],
"nxConsole.generateAiAgentRules": true,
"chat.mcp.enabled": true,
"chat.mcp.discovery.enabled": true
}
{
"editor.accessibilitySupport": "off",
"typescript.tsdk": "node_modules/typescript/lib",
"exportall.config.exclude": [".test.", ".spec.", ".stories."],
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.validate": [
"json"
],
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.formatOnSave": false
},
"exportall.config.folderListener": [
"/libs/oms/data-access/src/lib/models",
"/libs/oms/data-access/src/lib/schemas",
"/libs/catalogue/data-access/src/lib/models",
"/libs/common/data-access/src/lib/models",
"/libs/common/data-access/src/lib/error",
"/libs/oms/data-access/src/lib/errors/return-process"
],
"github.copilot.chat.commitMessageGeneration.instructions": [
{
"file": ".github/commit-instructions.md"
}
],
"github.copilot.chat.codeGeneration.instructions": [
{
"file": ".vscode/llms/angular.txt"
},
{
"file": "docs/tech-stack.md"
},
{
"file": "docs/guidelines/code-style.md"
},
{
"file": "docs/guidelines/project-structure.md"
},
{
"file": "docs/guidelines/state-management.md"
},
{
"file": "docs/guidelines/testing.md"
}
],
"github.copilot.chat.testGeneration.instructions": [
{
"file": ".github/testing-instructions.md"
},
{
"file": "docs/tech-stack.md"
},
{
"file": "docs/guidelines/code-style.md"
},
{
"file": "docs/guidelines/testing.md"
}
],
"github.copilot.chat.reviewSelection.instructions": [
{
"file": ".github/copilot-instructions.md"
},
{
"file": ".github/review-instructions.md"
},
{
"file": "docs/tech-stack.md"
},
{
"file": "docs/guidelines/code-style.md"
},
{
"file": "docs/guidelines/project-structure.md"
},
{
"file": "docs/guidelines/state-management.md"
},
{
"file": "docs/guidelines/testing.md"
}
],
"nxConsole.generateAiAgentRules": true,
"chat.mcp.discovery.enabled": {
"claude-desktop": true,
"windsurf": true,
"cursor-global": true,
"cursor-workspace": true
},
"chat.mcp.access": "all"
}

148
CLAUDE.md Normal file
View File

@@ -0,0 +1,148 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is an Angular monorepo managed by Nx. The main application is `isa-app`, which appears to be an inventory and returns management system for retail/e-commerce.
## Architecture
### Monorepo Structure
- **apps/isa-app**: Main Angular application
- **libs/**: Reusable libraries organized by domain and type
- **core/**: Core utilities (config, logging, storage, tabs)
- **common/**: Shared utilities (data-access, decorators, print)
- **ui/**: UI component libraries (buttons, dialogs, inputs, etc.)
- **shared/**: Shared domain components (filter, scanner, product components)
- **oms/**: Order Management System features and utilities
- **remission/**: Remission/returns management features
- **catalogue/**: Product catalogue functionality
- **utils/**: General utilities (validation, scroll position, parsing)
- **icons/**: Icon library
- **generated/swagger/**: Auto-generated API client code from OpenAPI specs
### Key Architectural Patterns
- **Standalone Components**: Project uses Angular standalone components
- **Feature Libraries**: Domain features organized as separate libraries (e.g., `oms-feature-return-search`)
- **Data Access Layer**: Separate data-access libraries for each domain (e.g., `oms-data-access`, `remission-data-access`)
- **Shared UI Components**: Reusable UI components in `libs/ui/`
- **Generated API Clients**: Swagger/OpenAPI clients auto-generated in `generated/swagger/`
## Common Development Commands
### Build Commands
```bash
# Build the main application (development)
npx nx build isa-app --configuration=development
# Build for production
npx nx build isa-app --configuration=production
# Serve the application with SSL
npx nx serve isa-app --ssl
```
### Testing Commands
```bash
# Run tests for a specific library (always use --skip-cache)
npx nx run <project-name>:test --skip-cache
# Example: npx nx run remission-data-access:test --skip-cache
# Run tests for all libraries except the main app
npx nx run-many -t test --exclude isa-app --skip-cache
# Run a single test file
npx nx run <project-name>:test --testFile=<path-to-test-file> --skip-cache
# Run tests with coverage
npx nx run <project-name>:test --code-coverage --skip-cache
# Run tests in watch mode
npx nx run <project-name>:test --watch
```
### Linting Commands
```bash
# Lint a specific project
npx nx lint <project-name>
# Example: npx nx lint remission-data-access
# Run linting for all projects
npx nx run-many -t lint
```
### Other Useful Commands
```bash
# Generate Swagger API clients
npm run generate:swagger
# Start Storybook
npx nx run isa-app:storybook
# Format code with Prettier
npm run prettier
# List all projects in the workspace
npx nx list
# Show project dependencies graph
npx nx graph
# Run affected tests (based on git changes)
npx nx affected:test
```
## Testing Framework
### Current Setup
- **Jest**: Primary test runner for existing libraries
- **Vitest**: Being adopted for new libraries (migration in progress)
- **Testing Utilities**:
- **Angular Testing Utilities** (TestBed, ComponentFixture): Use for new tests
- **Spectator**: Legacy testing utility for existing tests
- **ng-mocks**: For advanced mocking scenarios
### Test File Requirements
- Test files must end with `.spec.ts`
- Use AAA pattern (Arrange-Act-Assert)
- Include E2E testing attributes (`data-what`, `data-which`) in HTML templates
- Mock external dependencies and child components
## State Management
- **NgRx**: Store, Effects, Entity, Component Store, Signals
- **RxJS**: For reactive programming patterns
## Styling
- **Tailwind CSS**: Primary styling framework with custom configuration
- **SCSS**: For component-specific styles
- **Custom Tailwind plugins**: For buttons, inputs, menus, typography
## API Integration
- **Generated Swagger Clients**: Auto-generated TypeScript clients from OpenAPI specs
- **Available APIs**: availability, cat-search, checkout, crm, eis, inventory, isa, oms, print, wws
## Build Configuration
- **Angular 20.1.2**: Latest Angular version
- **TypeScript 5.8.3**: For type safety
- **Node.js >= 22.0.0**: Required Node version
- **npm >= 10.0.0**: Required npm version
## Important Conventions
- **Component Prefix**: Each library has its own prefix (e.g., `remi` for remission, `oms` for OMS)
- **Standalone Components**: All new components should be standalone
- **Path Aliases**: Use TypeScript path aliases defined in `tsconfig.base.json` (e.g., `@isa/core/config`)
- **Project Names**: Can be found in each library's `project.json` file
## Development Workflow Tips
- Always use `npx nx run` pattern for executing tasks
- Include `--skip-cache` flag when running tests to ensure fresh results
- Use Nx's affected commands to optimize CI/CD pipelines
- Project graph visualization helps understand dependencies: `npx nx graph`
## Development Notes
- Use start target to start the application. Only one project can be started: isa-app
- Make sure to have a look at @docs/guidelines/testing.md before writing tests
- Make sure to add e2e attributes to the html. Those are important for my colleagues writen e2e tests
- Guide for the e2e testing attributes can be found in the testing.md
- When reviewing code follow the instructions @.github/review-instructions.md

View File

@@ -100,7 +100,8 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/isa-app/jest.config.ts"
"jestConfig": "apps/isa-app/jest.config.ts",
"tsConfig": "apps/isa-app/tsconfig.spec.json"
}
},
"serve-static": {

View File

@@ -185,6 +185,11 @@ const routes: Routes = [
resolve: { process: tabResolverFn, tab: tabResolverFn },
canActivate: [IsAuthenticatedGuard],
children: [
{
path: 'reward',
loadChildren: () =>
import('@isa/checkout/feature/reward-catalog').then((m) => m.routes),
},
{
path: 'return',
loadChildren: () =>

View File

@@ -244,9 +244,15 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
}),
// #4822 Quick Fix - HSC Bestellungen Suche wird nicht ausgelöst (Möglicherweise nicht die beste Lösung)
switchMap(
async ([options, results, filter, branch]): Promise<
[typeof options, typeof results, typeof filter, typeof branch, boolean]
async (params: [
{ clear?: boolean; siletReload?: boolean },
OrderItemListItemDTO[],
Filter,
BranchDTO
]): Promise<
[{ clear?: boolean; siletReload?: boolean }, OrderItemListItemDTO[], Filter, BranchDTO, boolean]
> => {
const [options, results, filter, branch] = params;
const queryToken = filter?.getQueryToken() ?? {};
const cached: boolean =
options?.siletReload &&

View File

@@ -16,14 +16,16 @@
</a>
<div class="side-menu-group-sub-item-wrapper">
@if (customerSearchRoute$ | async; as customerSearchRoute) {
@if (
customerSearchRoute() || customerCreateRoute() || customerRewardRoute()
) {
<a
class="side-menu-group-item"
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="customerSearchRoute.path"
[queryParams]="customerSearchRoute.queryParams"
[routerLink]="customerSearchRoute().path"
[queryParams]="customerSearchRoute().queryParams"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/kunde\/\d*\/customer"
sharedRegexRouterLinkActiveTest="^(\/kunde\/\d*\/customer|\/\d*\/reward)"
(isActiveChange)="customerActive($event); focusSearchBox()"
>
<span class="side-menu-group-item-icon">
@@ -32,11 +34,11 @@
<span class="side-menu-group-item-label">Kunden</span>
<button
class="side-menu-group-arrow"
[class.side-menu-item-rotate]="customerExpanded"
[class.side-menu-item-rotate]="customerExpanded()"
(click)="
$event.stopPropagation();
$event.preventDefault();
customerExpanded = !customerExpanded
toggleCustomerExpanded()
"
>
<shared-icon icon="keyboard-arrow-down"></shared-icon>
@@ -44,13 +46,16 @@
</a>
}
<div class="side-menu-group-sub-items" [class.hidden]="!customerExpanded">
@if (customerSearchRoute$ | async; as customerSearchRoute) {
<div
class="side-menu-group-sub-items"
[class.hidden]="!customerExpanded()"
>
@if (customerSearchRoute() || customerRewardRoute()) {
<a
class="side-menu-group-item"
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="customerSearchRoute.path"
[queryParams]="customerSearchRoute.queryParams"
[routerLink]="customerSearchRoute().path"
[queryParams]="customerSearchRoute().queryParams"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/kunde\/\d*\/customer\/(\(search|search)"
(isActiveChange)="focusSearchBox()"
@@ -59,12 +64,12 @@
<span class="side-menu-group-item-label">Suchen</span>
</a>
}
@if (customerCreateRoute$ | async; as customerCreateRoute) {
@if (customerCreateRoute() || customerRewardRoute()) {
<a
class="side-menu-group-item"
(click)="closeSideMenu()"
[routerLink]="customerCreateRoute.path"
[queryParams]="customerCreateRoute.queryParams"
[routerLink]="customerCreateRoute().path"
[queryParams]="customerCreateRoute().queryParams"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/kunde\/\d*\/customer\/(\(create|create)"
>
@@ -72,6 +77,19 @@
<span class="side-menu-group-item-label">Erfassen</span>
</a>
}
@if (customerRewardRoute()) {
<a
class="side-menu-group-item"
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="customerRewardRoute()"
(isActiveChange)="focusSearchBox()"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/\d*\/reward"
>
<span class="side-menu-group-item-icon"> </span>
<span class="side-menu-group-item-label">Prämienshop</span>
</a>
}
</div>
</div>
@@ -95,7 +113,7 @@
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="[
'/',
processService.activatedTab()?.id || processService.nextId(),
tabService.activatedTab()?.id || tabService.nextId(),
'return',
]"
(isActiveChange)="focusSearchBox()"
@@ -289,7 +307,7 @@
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="[
'/',
processService.activatedTab()?.id || processService.nextId(),
tabService.activatedTab()?.id || tabService.nextId(),
'remission',
]"
(isActiveChange)="focusSearchBox(); remissionExpanded.set($event)"
@@ -319,7 +337,7 @@
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="[
'/',
processService.activatedTab()?.id || processService.nextId(),
tabService.activatedTab()?.id || tabService.nextId(),
'remission',
]"
(isActiveChange)="focusSearchBox()"
@@ -334,7 +352,7 @@
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="[
'/',
processService.activatedTab()?.id || processService.nextId(),
tabService.activatedTab()?.id || tabService.nextId(),
'remission',
'return-receipt',
]"

View File

@@ -1,7 +1,7 @@
import {
Component,
ChangeDetectionStrategy,
Inject,
computed,
ChangeDetectorRef,
inject,
DOCUMENT,
@@ -29,6 +29,7 @@ import {
PickUpShelfOutNavigationService,
ProductCatalogNavigationService,
} from '@shared/services/navigation';
import { toSignal } from '@angular/core/rxjs-interop';
import { TabService } from '@isa/core/tabs';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
@@ -68,7 +69,7 @@ export class ShellSideMenuComponent {
#pickUpShelfInNavigation = inject(PickupShelfInNavigationService);
#cdr = inject(ChangeDetectorRef);
#document = inject(DOCUMENT);
processService = inject(TabService);
tabService = inject(TabService);
branchKey$ = this.#stockService.StockCurrentBranch().pipe(
retry(3),
@@ -109,18 +110,32 @@ export class ShellSideMenuComponent {
}),
);
customerSearchRoute$ = this.getLastActivatedCustomerProcessId$().pipe(
map((processId) => {
return this.#customerSearchNavigation.defaultRoute({ processId });
}),
customerSearchRoute = toSignal(
this.getLastActivatedCustomerProcessId$().pipe(
map((processId) => {
return this.#customerSearchNavigation.defaultRoute({ processId });
}),
),
);
customerCreateRoute$ = this.getLastActivatedCustomerProcessId$().pipe(
map((processId) => {
return this.#customerCreateNavigation.defaultRoute({ processId });
}),
customerCreateRoute = toSignal(
this.getLastActivatedCustomerProcessId$().pipe(
map((processId) => {
return this.#customerCreateNavigation.defaultRoute({ processId });
}),
),
);
customerRewardRoute = computed(() => {
const routeName = 'reward';
const tabId = this.tabService.activatedTab()?.id;
return this.#router.createUrlTree([
'/',
tabId || this.tabService.nextId(),
routeName,
]);
});
pickUpShelfOutRoutePath$ = this.getLastActivatedCustomerProcessId$().pipe(
map((processId) => {
if (processId) {
@@ -212,26 +227,25 @@ export class ShellSideMenuComponent {
}
shelfExpanded = false;
customerExpanded = false;
customerExpanded = signal(false);
remissionExpanded = signal(false);
customerActive(isActive: boolean) {
if (isActive) {
this.expandCustomer();
this.customerExpanded.set(true);
}
}
toggleCustomerExpanded() {
this.customerExpanded.set(!this.customerExpanded());
}
shelfActive(isActive: boolean) {
if (isActive) {
this.expandShelf();
}
}
expandCustomer() {
this.customerExpanded = true;
this.#cdr.markForCheck();
}
expandShelf() {
this.shelfExpanded = true;
this.#cdr.markForCheck();

View File

@@ -0,0 +1,59 @@
import {
argsToTemplate,
moduleMetadata,
type Meta,
type StoryObj,
} from '@storybook/angular';
import {
InlineInputComponent,
InputControlDirective,
} from '@isa/ui/input-controls';
const meta: Meta<InlineInputComponent> = {
component: InlineInputComponent,
title: 'ui/input-controls/InlineInput',
argTypes: {
size: { control: 'select', options: ['small', 'medium'] },
},
args: {
size: 'medium',
},
decorators: [
moduleMetadata({
imports: [InputControlDirective],
}),
],
render: (args) => ({
props: args,
template: `
<ui-inline-input ${argsToTemplate(args)}>
<input type="text" placeholder="Enter inline text" />
</ui-inline-input>
`,
}),
};
export default meta;
type Story = StoryObj<InlineInputComponent>;
export const Primary: Story = {
args: {
size: 'medium',
},
};
export const WithLabel: Story = {
args: {
size: 'medium',
},
render: (args) => ({
props: args,
template: `
<ui-inline-input ${argsToTemplate(args)}>
<label>Label</label>
<input type="text" placeholder="Enter inline text" />
</ui-inline-input>
`,
}),
};

View File

@@ -1,128 +1,136 @@
# Tech Stack Documentation
## Core Technologies
### Frontend Framework
- **[Angular](https://angular.dev/overview)** (Latest Version)
- Modern web framework for building scalable single-page applications
- Leverages TypeScript for enhanced type safety and developer experience
### State Management
- **[NgRx](https://ngrx.io/docs)**
- Redux-inspired state management for Angular applications
- Provides predictable state container and powerful dev tools
- Used for managing complex application state and side effects
### Styling
- **[Tailwind CSS](https://tailwindcss.com/)**
- Utility-first CSS framework
- Enables rapid UI development with pre-built classes
- Highly customizable through configuration
## Development Tools
### Language
- **[TypeScript](https://www.typescriptlang.org/docs/handbook/intro.html)**
- Strongly typed programming language
- Provides enhanced IDE support and compile-time error checking
- Used throughout the entire application
### Runtime
- **[Node.js](https://nodejs.org/docs/latest-v22.x/api/index.html)**
- JavaScript runtime environment
- Used for development server and build tools
- Required for running npm scripts and development tools
### Testing Framework
- **[Jest](https://jestjs.io/docs/getting-started)**
- Primary testing framework
- Used for unit and integration tests
- Provides snapshot testing capabilities
- **[Spectator](https://ngneat.github.io/spectator/)**
- Angular testing utility library
- Simplifies component testing
- Reduces boilerplate in test files
### UI Development
- **[Storybook](https://storybook.js.org/docs/get-started/frameworks/angular)**
- UI component development environment
- Enables isolated component development
- Serves as living documentation for components
### Utilities
- **[date-fns](https://date-fns.org/docs/Getting-Started)**
- Modern JavaScript date utility library
- Used for consistent date formatting and manipulation
- Tree-shakeable to optimize bundle size
- **[Lodash](https://lodash.com/)**
- Utility library for common JavaScript tasks
- **[UUID](https://www.npmjs.com/package/uuid)**
- Generates unique identifiers
- **[Zod](https://github.com/colinhacks/zod)**
- TypeScript-first schema validation library
## Additional Technical Areas
### Authentication & Authorization
- **[angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc)**
- Simplifies implementing OAuth2 and OIDC authentication in Angular.
- **[angular-oauth2-oidc-jwks](https://github.com/manfredsteyer/angular-oauth2-oidc)**
- Adds JWKS support for secure token management.
### Real-Time Communication
- **[@microsoft/signalr](https://www.npmjs.com/package/@microsoft/signalr)**
- Provides real-time communication between client and server components.
### Barcode Scanning
- **[Scandit Web Data Capture Barcode](https://www.scandit.com/documentation/web/)**
- Robust barcode scanning capabilities integrated into the application.
- **[Scandit Web Data Capture Core](https://www.scandit.com/documentation/web/)**
- Core library supporting the barcode scanning features.
### Tooling
- **[Nx](https://nx.dev/)**
- Powerful monorepo tool for Angular and other frontend applications.
- **[Husky](https://typicode.github.io/husky/#/)**
- Manages Git hooks for consistent developer workflows.
- **[ESLint](https://eslint.org/) & [Prettier](https://prettier.io/)**
- Linting and formatting tools to maintain consistent code quality.
- **[Storybook](https://storybook.js.org/)**
- Isolated component development and living documentation environment.
## Development Environment Setup
1. **Required Software**
- Node.js (Latest LTS version)
- npm (comes with Node.js)
- Git
2. **IDE Recommendations**
- Visual Studio Code with following extensions:
- Angular Language Service
- ESLint
- Prettier
- Tailwind CSS IntelliSense
3. **Getting Started**
```bash
npm install # Install dependencies
npm run start # Start development server
npm run test # Run tests
npm run storybook # Start Storybook
```
# Tech Stack Documentation
## Core Technologies
### Frontend Framework
- **[Angular](https://angular.dev/overview)** (Latest Version)
- Modern web framework for building scalable single-page applications
- Leverages TypeScript for enhanced type safety and developer experience
### State Management
- **[NgRx](https://ngrx.io/docs)**
- Redux-inspired state management for Angular applications
- Provides predictable state container and powerful dev tools
- Used for managing complex application state and side effects
### Styling
- **[Tailwind CSS](https://tailwindcss.com/)**
- Utility-first CSS framework
- Enables rapid UI development with pre-built classes
- Highly customizable through configuration
## Development Tools
### Language
- **[TypeScript](https://www.typescriptlang.org/docs/handbook/intro.html)**
- Strongly typed programming language
- Provides enhanced IDE support and compile-time error checking
- Used throughout the entire application
### Runtime
- **[Node.js](https://nodejs.org/docs/latest-v22.x/api/index.html)**
- JavaScript runtime environment
- Used for development server and build tools
- Required for running npm scripts and development tools
### Testing Framework
- **[Jest](https://jestjs.io/docs/getting-started)**
- Primary testing framework
- Used for unit and integration tests
- Provides snapshot testing capabilities
- **[Spectator](https://ngneat.github.io/spectator/)**
- Angular testing utility library
- Simplifies component testing
- Reduces boilerplate in test files
### UI Development
- **[Storybook](https://storybook.js.org/docs/get-started/frameworks/angular)**
- UI component development environment
- Enables isolated component development
- Serves as living documentation for components
### Utilities
- **[date-fns](https://date-fns.org/docs/Getting-Started)**
- Modern JavaScript date utility library
- Used for consistent date formatting and manipulation
- Tree-shakeable to optimize bundle size
- **[Lodash](https://lodash.com/)**
- Utility library for common JavaScript tasks
- **[UUID](https://www.npmjs.com/package/uuid)**
- Generates unique identifiers
- **[Zod](https://github.com/colinhacks/zod)**
- TypeScript-first schema validation library
## Additional Technical Areas
### Authentication & Authorization
- **[angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc)**
- Simplifies implementing OAuth2 and OIDC authentication in Angular.
- **[angular-oauth2-oidc-jwks](https://github.com/manfredsteyer/angular-oauth2-oidc)**
- Adds JWKS support for secure token management.
### Real-Time Communication
- **[@microsoft/signalr](https://www.npmjs.com/package/@microsoft/signalr)**
- Provides real-time communication between client and server components.
### Barcode Scanning
- **[Scandit Web Data Capture Barcode](https://www.scandit.com/documentation/web/)**
- Robust barcode scanning capabilities integrated into the application.
- **[Scandit Web Data Capture Core](https://www.scandit.com/documentation/web/)**
- Core library supporting the barcode scanning features.
### Tooling
- **[Nx](https://nx.dev/)**
- Powerful monorepo tool for Angular and other frontend applications.
- **[Husky](https://typicode.github.io/husky/#/)**
- Manages Git hooks for consistent developer workflows.
- **[ESLint](https://eslint.org/) & [Prettier](https://prettier.io/)**
- Linting and formatting tools to maintain consistent code quality.
- **[Storybook](https://storybook.js.org/)**
- Isolated component development and living documentation environment.
## Domain Libraries
### Customer Relationship Management (CRM)
- **`@isa/crm/data-access`**
- Handles data access logic for customer-related features.
- Contains services for fetching and managing customer data.
## Development Environment Setup
1. **Required Software**
- Node.js (Latest LTS version)
- npm (comes with Node.js)
- Git
2. **IDE Recommendations**
- Visual Studio Code with following extensions:
- Angular Language Service
- ESLint
- Prettier
- Tailwind CSS IntelliSense
3. **Getting Started**
```bash
npm install # Install dependencies
npm run start # Start development server
npm run test # Run tests
npm run storybook # Start Storybook
```

View File

@@ -31,8 +31,9 @@ const PARAMETER_CODEC = new ParameterCodec();
export class BaseService {
constructor(
protected config: CatConfiguration,
protected http: HttpClient,
) {}
protected http: HttpClient
) {
}
private _rootUrl: string = '';
@@ -56,7 +57,7 @@ export class BaseService {
*/
protected newParams(): HttpParams {
return new HttpParams({
encoder: PARAMETER_CODEC,
encoder: PARAMETER_CODEC
});
}
}

View File

@@ -4,6 +4,7 @@
* Auocomplete-Ergebnis
*/
export interface AutocompleteDTO {
/**
* Anzeige / Bezeichner
*/

View File

@@ -5,6 +5,7 @@ import { CatalogType } from './catalog-type';
* Suchabfrage
*/
export interface AutocompleteTokenDTO {
/**
* Katalogbereich
*/
@@ -13,7 +14,7 @@ export interface AutocompleteTokenDTO {
/**
* Filter
*/
filter?: { [key: string]: string };
filter?: {[key: string]: string};
/**
* Eingabe

View File

@@ -7,6 +7,7 @@ import { AvailabilityType } from './availability-type';
* Verfügbarkeit
*/
export interface AvailabilityDTO {
/**
* Voraussichtliches Lieferdatum
*/

View File

@@ -1,2 +1,2 @@
/* tslint:disable */
export type AvailabilityType = 0 | 1 | 2 | 32 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384;
export type AvailabilityType = 0 | 1 | 2 | 32 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384;

View File

@@ -1,2 +1,2 @@
/* tslint:disable */
export type Avoirdupois = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096;
export type Avoirdupois = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096;

View File

@@ -3,4 +3,4 @@
/**
* Katalogbereich
*/
export type CatalogType = 0 | 1 | 2 | 4;
export type CatalogType = 0 | 1 | 2 | 4;

View File

@@ -1,2 +1,2 @@
/* tslint:disable */
export type CRUDA = 0 | 1 | 2 | 4 | 8 | 16;
export type CRUDA = 0 | 1 | 2 | 4 | 8 | 16;

View File

@@ -1,2 +1,2 @@
/* tslint:disable */
export type DialogContentType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128;
export type DialogContentType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128;

View File

@@ -1,2 +1,2 @@
/* tslint:disable */
export type DialogSettings = 0 | 1 | 2 | 4;
export type DialogSettings = 0 | 1 | 2 | 4;

View File

@@ -2,7 +2,7 @@
import { TouchedBase } from './touched-base';
import { CRUDA } from './cruda';
import { EntityStatus } from './entity-status';
export interface EntityDTO extends TouchedBase {
export interface EntityDTO extends TouchedBase{
changed?: string;
created?: string;
cruda?: CRUDA;

View File

@@ -1,2 +1,2 @@
/* tslint:disable */
export type EntityStatus = 0 | 1 | 2 | 4 | 8;
export type EntityStatus = 0 | 1 | 2 | 4 | 8;

View File

@@ -4,6 +4,7 @@
* Bild
*/
export interface ImageDTO {
/**
* Copyright
*/

View File

@@ -1,2 +1,2 @@
/* tslint:disable */
export type InputType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 3072 | 4096 | 8192 | 12288;
export type InputType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 3072 | 4096 | 8192 | 12288;

View File

@@ -11,7 +11,8 @@ import { StockInfoDTO } from './stock-info-dto';
import { Successor } from './successor';
import { TextDTO } from './text-dto';
import { ItemType } from './item-type';
export interface ItemDTO extends EntityDTO {
export interface ItemDTO extends EntityDTO{
/**
* Verfügbarkeit laut Katalog
*/
@@ -30,7 +31,7 @@ export interface ItemDTO extends EntityDTO {
/**
* Weitere Artikel-IDs
*/
ids?: { [key: string]: number };
ids?: {[key: string]: number};
/**
* Primary image / Id des Hauptbilds
@@ -45,7 +46,7 @@ export interface ItemDTO extends EntityDTO {
/**
* Markierungen (Lesezeichen) wie (BOD, Prämie)
*/
labels?: { [key: string]: string };
labels?: {[key: string]: string};
/**
* Produkt-Stammdaten

View File

@@ -3,4 +3,4 @@
/**
* Artikel-/Produkttyp
*/
export type ItemType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768 | 65536;
export type ItemType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768 | 65536;

View File

@@ -1,5 +1,6 @@
/* tslint:disable */
export interface LesepunkteRequest {
/**
* Artikel ID
*/

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { ResponseArgsOfIEnumerableOfAutocompleteDTO } from './response-args-of-ienumerable-of-autocomplete-dto';
export interface ListResponseArgsOfAutocompleteDTO extends ResponseArgsOfIEnumerableOfAutocompleteDTO {
export interface ListResponseArgsOfAutocompleteDTO extends ResponseArgsOfIEnumerableOfAutocompleteDTO{
completed?: boolean;
hits?: number;
skip?: number;

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { ResponseArgsOfIEnumerableOfItemDTO } from './response-args-of-ienumerable-of-item-dto';
export interface ListResponseArgsOfItemDTO extends ResponseArgsOfIEnumerableOfItemDTO {
export interface ListResponseArgsOfItemDTO extends ResponseArgsOfIEnumerableOfItemDTO{
completed?: boolean;
hits?: number;
skip?: number;

View File

@@ -2,7 +2,7 @@
import { TouchedBase } from './touched-base';
import { PriceValueDTO } from './price-value-dto';
import { VATValueDTO } from './vatvalue-dto';
export interface PriceDTO extends TouchedBase {
export interface PriceDTO extends TouchedBase{
value?: PriceValueDTO;
vat?: VATValueDTO;
}

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { TouchedBase } from './touched-base';
export interface PriceValueDTO extends TouchedBase {
export interface PriceValueDTO extends TouchedBase{
currency?: string;
currencySymbol?: string;
value?: number;

View File

@@ -1,7 +1,7 @@
/* tslint:disable */
export interface ProblemDetails {
detail?: string;
extensions: { [key: string]: any };
extensions: {[key: string]: any};
instance?: string;
status?: number;
title?: string;

View File

@@ -2,7 +2,7 @@
import { TouchedBase } from './touched-base';
import { SizeOfString } from './size-of-string';
import { WeightOfAvoirdupois } from './weight-of-avoirdupois';
export interface ProductDTO extends TouchedBase {
export interface ProductDTO extends TouchedBase{
additionalName?: string;
catalogProductNumber?: string;
contributors?: string;

View File

@@ -1,7 +1,8 @@
/* tslint:disable */
import { QueryTokenDTO2 } from './query-token-dto2';
import { CatalogType } from './catalog-type';
export interface QueryTokenDTO extends QueryTokenDTO2 {
export interface QueryTokenDTO extends QueryTokenDTO2{
/**
* Katalogbereich
*/

View File

@@ -1,13 +1,13 @@
/* tslint:disable */
import { OrderByDTO } from './order-by-dto';
export interface QueryTokenDTO2 {
filter?: { [key: string]: string };
filter?: {[key: string]: string};
friendlyName?: string;
fuzzy?: number;
hitsOnly?: boolean;
ids?: Array<number>;
input?: { [key: string]: string };
options?: { [key: string]: string };
input?: {[key: string]: string};
options?: {[key: string]: string};
orderBy?: Array<OrderByDTO>;
skip?: number;
take?: number;

View File

@@ -1,5 +1,5 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
export interface ResponseArgsOfIDictionaryOfLongAndNullableInteger extends ResponseArgs {
result?: { [key: string]: number };
export interface ResponseArgsOfIDictionaryOfLongAndNullableInteger extends ResponseArgs{
result?: {[key: string]: number};
}

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
import { AutocompleteDTO } from './autocomplete-dto';
export interface ResponseArgsOfIEnumerableOfAutocompleteDTO extends ResponseArgs {
export interface ResponseArgsOfIEnumerableOfAutocompleteDTO extends ResponseArgs{
result?: Array<AutocompleteDTO>;
}

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
import { InputGroupDTO } from './input-group-dto';
export interface ResponseArgsOfIEnumerableOfInputGroupDTO extends ResponseArgs {
export interface ResponseArgsOfIEnumerableOfInputGroupDTO extends ResponseArgs{
result?: Array<InputGroupDTO>;
}

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
import { ItemDTO } from './item-dto';
export interface ResponseArgsOfIEnumerableOfItemDTO extends ResponseArgs {
export interface ResponseArgsOfIEnumerableOfItemDTO extends ResponseArgs{
result?: Array<ItemDTO>;
}

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
import { OrderByDTO } from './order-by-dto';
export interface ResponseArgsOfIEnumerableOfOrderByDTO extends ResponseArgs {
export interface ResponseArgsOfIEnumerableOfOrderByDTO extends ResponseArgs{
result?: Array<OrderByDTO>;
}

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
import { QueryTokenDTO } from './query-token-dto';
export interface ResponseArgsOfIEnumerableOfQueryTokenDTO extends ResponseArgs {
export interface ResponseArgsOfIEnumerableOfQueryTokenDTO extends ResponseArgs{
result?: Array<QueryTokenDTO>;
}

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
import { ItemDTO } from './item-dto';
export interface ResponseArgsOfItemDTO extends ResponseArgs {
export interface ResponseArgsOfItemDTO extends ResponseArgs{
result?: ItemDTO;
}

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
import { UISettingsDTO } from './uisettings-dto';
export interface ResponseArgsOfUISettingsDTO extends ResponseArgs {
export interface ResponseArgsOfUISettingsDTO extends ResponseArgs{
result?: UISettingsDTO;
}

View File

@@ -3,7 +3,7 @@ import { DialogOfString } from './dialog-of-string';
export interface ResponseArgs {
dialog?: DialogOfString;
error: boolean;
invalidProperties?: { [key: string]: string };
invalidProperties?: {[key: string]: string};
message?: string;
requestId?: number;
}

View File

@@ -1,5 +1,6 @@
/* tslint:disable */
export interface ReviewDTO {
/**
* Autor
*/

View File

@@ -4,6 +4,7 @@
* Regalinfo
*/
export interface ShelfInfoDTO {
/**
* Sortiment
*/

View File

@@ -3,4 +3,5 @@
/**
* Shop
*/
export interface ShopDTO {}
export interface ShopDTO {
}

View File

@@ -4,6 +4,7 @@
* Eigenchaften
*/
export interface SpecDTO {
/**
* PK
*/

View File

@@ -5,6 +5,7 @@ import { StockStatus } from './stock-status';
* Bestandsinformation
*/
export interface StockInfoDTO {
/**
* Filiale PK
*/

View File

@@ -3,4 +3,4 @@
/**
* Dispositionsstatus
*/
export type StockStatus = 0 | 1 | 2 | 3 | 4;
export type StockStatus = 0 | 1 | 2 | 3 | 4;

View File

@@ -1,6 +1,7 @@
/* tslint:disable */
import { ProductDTO } from './product-dto';
export interface Successor extends ProductDTO {
export interface Successor extends ProductDTO{
/**
* PK
*/

View File

@@ -1,5 +1,6 @@
/* tslint:disable */
export interface TextDTO {
/**
* PK
*/

View File

@@ -1,2 +1,3 @@
/* tslint:disable */
export interface TouchedBase {}
export interface TouchedBase {
}

View File

@@ -1,5 +1,5 @@
/* tslint:disable */
export interface TranslationDTO {
target?: string;
values?: { [key: string]: string };
values?: {[key: string]: string};
}

View File

@@ -1,7 +1,8 @@
/* tslint:disable */
import { QuerySettingsDTO } from './query-settings-dto';
import { TranslationDTO } from './translation-dto';
export interface UISettingsDTO extends QuerySettingsDTO {
export interface UISettingsDTO extends QuerySettingsDTO{
/**
* Url Template für Detail-Bild
*/

View File

@@ -1,2 +1,2 @@
/* tslint:disable */
export type VATType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128;
export type VATType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128;

View File

@@ -1,7 +1,7 @@
/* tslint:disable */
import { TouchedBase } from './touched-base';
import { VATType } from './vattype';
export interface VATValueDTO extends TouchedBase {
export interface VATValueDTO extends TouchedBase{
inPercent?: number;
label?: string;
value?: number;

View File

@@ -16,7 +16,10 @@ class PromotionService extends __BaseService {
static readonly PromotionLesepunktePath = '/promotion/lesepunkte';
static readonly PromotionLesepunkte2Path = '/stock/{stockId}/promotion/lesepunkte';
constructor(config: __Configuration, http: HttpClient) {
constructor(
config: __Configuration,
http: HttpClient
) {
super(config, http);
}
@@ -24,24 +27,26 @@ class PromotionService extends __BaseService {
* Lesepunkte
* @param items Ids und Mengen
*/
PromotionLesepunkteResponse(
items: Array<LesepunkteRequest>,
): __Observable<__StrictHttpResponse<ResponseArgsOfIDictionaryOfLongAndNullableInteger>> {
PromotionLesepunkteResponse(items: Array<LesepunkteRequest>): __Observable<__StrictHttpResponse<ResponseArgsOfIDictionaryOfLongAndNullableInteger>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
__body = items;
let req = new HttpRequest<any>('POST', this.rootUrl + `/promotion/lesepunkte`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/promotion/lesepunkte`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfIDictionaryOfLongAndNullableInteger>;
}),
})
);
}
/**
@@ -49,7 +54,9 @@ class PromotionService extends __BaseService {
* @param items Ids und Mengen
*/
PromotionLesepunkte(items: Array<LesepunkteRequest>): __Observable<ResponseArgsOfIDictionaryOfLongAndNullableInteger> {
return this.PromotionLesepunkteResponse(items).pipe(__map((_r) => _r.body as ResponseArgsOfIDictionaryOfLongAndNullableInteger));
return this.PromotionLesepunkteResponse(items).pipe(
__map(_r => _r.body as ResponseArgsOfIDictionaryOfLongAndNullableInteger)
);
}
/**
@@ -60,9 +67,7 @@ class PromotionService extends __BaseService {
*
* - `items`: Ids und Mengen
*/
PromotionLesepunkte2Response(
params: PromotionService.PromotionLesepunkte2Params,
): __Observable<__StrictHttpResponse<ResponseArgsOfIDictionaryOfLongAndNullableInteger>> {
PromotionLesepunkte2Response(params: PromotionService.PromotionLesepunkte2Params): __Observable<__StrictHttpResponse<ResponseArgsOfIDictionaryOfLongAndNullableInteger>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
@@ -75,15 +80,14 @@ class PromotionService extends __BaseService {
{
headers: __headers,
params: __params,
responseType: 'json',
},
);
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfIDictionaryOfLongAndNullableInteger>;
}),
})
);
}
/**
@@ -94,18 +98,20 @@ class PromotionService extends __BaseService {
*
* - `items`: Ids und Mengen
*/
PromotionLesepunkte2(
params: PromotionService.PromotionLesepunkte2Params,
): __Observable<ResponseArgsOfIDictionaryOfLongAndNullableInteger> {
return this.PromotionLesepunkte2Response(params).pipe(__map((_r) => _r.body as ResponseArgsOfIDictionaryOfLongAndNullableInteger));
PromotionLesepunkte2(params: PromotionService.PromotionLesepunkte2Params): __Observable<ResponseArgsOfIDictionaryOfLongAndNullableInteger> {
return this.PromotionLesepunkte2Response(params).pipe(
__map(_r => _r.body as ResponseArgsOfIDictionaryOfLongAndNullableInteger)
);
}
}
module PromotionService {
/**
* Parameters for PromotionLesepunkte2
*/
export interface PromotionLesepunkte2Params {
/**
* Lager PK (optional)
*/
@@ -118,4 +124,4 @@ module PromotionService {
}
}
export { PromotionService };
export { PromotionService }

View File

@@ -37,12 +37,16 @@ class SearchService extends __BaseService {
static readonly SearchDetailByEANPath = '/s/ean/{ean}';
static readonly SearchDetailByEAN2Path = '/stock/{stockId}/ean/{ean}';
static readonly SearchSettingsPath = '/s/settings';
static readonly SearchLoyaltySettingsPath = '/s/loyalty/settings';
static readonly SearchSearchFilterPath = '/s/filter';
static readonly SearchSearchSortPath = '/s/sort';
static readonly SearchHistoryPath = '/s/history';
static readonly SearchGetRecommendationsPath = '/s/recommendations/{digId}';
constructor(config: __Configuration, http: HttpClient) {
constructor(
config: __Configuration,
http: HttpClient
) {
super(config, http);
}
@@ -55,17 +59,21 @@ class SearchService extends __BaseService {
let __headers = new HttpHeaders();
let __body: any = null;
__body = queryToken;
let req = new HttpRequest<any>('POST', this.rootUrl + `/s/top`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/s/top`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -73,7 +81,9 @@ class SearchService extends __BaseService {
* @param queryToken Suchkriterien
*/
SearchTop(queryToken: QueryTokenDTO): __Observable<ListResponseArgsOfItemDTO> {
return this.SearchTopResponse(queryToken).pipe(__map((_r) => _r.body as ListResponseArgsOfItemDTO));
return this.SearchTopResponse(queryToken).pipe(
__map(_r => _r.body as ListResponseArgsOfItemDTO)
);
}
/**
@@ -90,17 +100,21 @@ class SearchService extends __BaseService {
let __body: any = null;
__body = params.queryToken;
let req = new HttpRequest<any>('POST', this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s/top`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s/top`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -112,7 +126,9 @@ class SearchService extends __BaseService {
* - `queryToken`: Suchkriterien
*/
SearchTop2(params: SearchService.SearchTop2Params): __Observable<ListResponseArgsOfItemDTO> {
return this.SearchTop2Response(params).pipe(__map((_r) => _r.body as ListResponseArgsOfItemDTO));
return this.SearchTop2Response(params).pipe(
__map(_r => _r.body as ListResponseArgsOfItemDTO)
);
}
/**
@@ -124,17 +140,21 @@ class SearchService extends __BaseService {
let __headers = new HttpHeaders();
let __body: any = null;
__body = queryToken;
let req = new HttpRequest<any>('POST', this.rootUrl + `/s`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/s`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -142,7 +162,9 @@ class SearchService extends __BaseService {
* @param queryToken Suchkriterien
*/
SearchSearch(queryToken: QueryTokenDTO): __Observable<ListResponseArgsOfItemDTO> {
return this.SearchSearchResponse(queryToken).pipe(__map((_r) => _r.body as ListResponseArgsOfItemDTO));
return this.SearchSearchResponse(queryToken).pipe(
__map(_r => _r.body as ListResponseArgsOfItemDTO)
);
}
/**
@@ -159,17 +181,21 @@ class SearchService extends __BaseService {
let __body: any = null;
__body = params.queryToken;
let req = new HttpRequest<any>('POST', this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -181,7 +207,9 @@ class SearchService extends __BaseService {
* - `queryToken`: Suchkriterien
*/
SearchSearch2(params: SearchService.SearchSearch2Params): __Observable<ListResponseArgsOfItemDTO> {
return this.SearchSearch2Response(params).pipe(__map((_r) => _r.body as ListResponseArgsOfItemDTO));
return this.SearchSearch2Response(params).pipe(
__map(_r => _r.body as ListResponseArgsOfItemDTO)
);
}
/**
@@ -193,17 +221,21 @@ class SearchService extends __BaseService {
let __headers = new HttpHeaders();
let __body: any = null;
__body = queryToken;
let req = new HttpRequest<any>('POST', this.rootUrl + `/s/complete`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/s/complete`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfAutocompleteDTO>;
}),
})
);
}
/**
@@ -211,7 +243,9 @@ class SearchService extends __BaseService {
* @param queryToken Suchbegriff
*/
SearchAutocomplete(queryToken: AutocompleteTokenDTO): __Observable<ListResponseArgsOfAutocompleteDTO> {
return this.SearchAutocompleteResponse(queryToken).pipe(__map((_r) => _r.body as ListResponseArgsOfAutocompleteDTO));
return this.SearchAutocompleteResponse(queryToken).pipe(
__map(_r => _r.body as ListResponseArgsOfAutocompleteDTO)
);
}
/**
@@ -222,25 +256,27 @@ class SearchService extends __BaseService {
*
* - `queryToken`: Suchbegriff
*/
SearchAutocomplete2Response(
params: SearchService.SearchAutocomplete2Params,
): __Observable<__StrictHttpResponse<ListResponseArgsOfAutocompleteDTO>> {
SearchAutocomplete2Response(params: SearchService.SearchAutocomplete2Params): __Observable<__StrictHttpResponse<ListResponseArgsOfAutocompleteDTO>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
__body = params.queryToken;
let req = new HttpRequest<any>('POST', this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s/complete`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s/complete`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfAutocompleteDTO>;
}),
})
);
}
/**
@@ -252,7 +288,9 @@ class SearchService extends __BaseService {
* - `queryToken`: Suchbegriff
*/
SearchAutocomplete2(params: SearchService.SearchAutocomplete2Params): __Observable<ListResponseArgsOfAutocompleteDTO> {
return this.SearchAutocomplete2Response(params).pipe(__map((_r) => _r.body as ListResponseArgsOfAutocompleteDTO));
return this.SearchAutocomplete2Response(params).pipe(
__map(_r => _r.body as ListResponseArgsOfAutocompleteDTO)
);
}
/**
@@ -264,17 +302,21 @@ class SearchService extends __BaseService {
let __headers = new HttpHeaders();
let __body: any = null;
__body = ids;
let req = new HttpRequest<any>('POST', this.rootUrl + `/s/byid`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/s/byid`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -282,7 +324,9 @@ class SearchService extends __BaseService {
* @param ids PKs
*/
SearchById(ids: Array<number>): __Observable<ListResponseArgsOfItemDTO> {
return this.SearchByIdResponse(ids).pipe(__map((_r) => _r.body as ListResponseArgsOfItemDTO));
return this.SearchByIdResponse(ids).pipe(
__map(_r => _r.body as ListResponseArgsOfItemDTO)
);
}
/**
@@ -299,17 +343,21 @@ class SearchService extends __BaseService {
let __body: any = null;
__body = params.ids;
let req = new HttpRequest<any>('POST', this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s/byid`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s/byid`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -321,7 +369,9 @@ class SearchService extends __BaseService {
* - `ids`: PKs
*/
SearchById2(params: SearchService.SearchById2Params): __Observable<ListResponseArgsOfItemDTO> {
return this.SearchById2Response(params).pipe(__map((_r) => _r.body as ListResponseArgsOfItemDTO));
return this.SearchById2Response(params).pipe(
__map(_r => _r.body as ListResponseArgsOfItemDTO)
);
}
/**
@@ -333,17 +383,21 @@ class SearchService extends __BaseService {
let __headers = new HttpHeaders();
let __body: any = null;
__body = eans;
let req = new HttpRequest<any>('POST', this.rootUrl + `/s/byean`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/s/byean`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -351,7 +405,9 @@ class SearchService extends __BaseService {
* @param eans EANs
*/
SearchByEAN(eans: Array<string>): __Observable<ListResponseArgsOfItemDTO> {
return this.SearchByEANResponse(eans).pipe(__map((_r) => _r.body as ListResponseArgsOfItemDTO));
return this.SearchByEANResponse(eans).pipe(
__map(_r => _r.body as ListResponseArgsOfItemDTO)
);
}
/**
@@ -368,17 +424,21 @@ class SearchService extends __BaseService {
let __body: any = null;
__body = params.eans;
let req = new HttpRequest<any>('POST', this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s/byean`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/s/byean`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -390,7 +450,9 @@ class SearchService extends __BaseService {
* - `eans`: EANs
*/
SearchByEAN2(params: SearchService.SearchByEAN2Params): __Observable<ListResponseArgsOfItemDTO> {
return this.SearchByEAN2Response(params).pipe(__map((_r) => _r.body as ListResponseArgsOfItemDTO));
return this.SearchByEAN2Response(params).pipe(
__map(_r => _r.body as ListResponseArgsOfItemDTO)
);
}
/**
@@ -407,17 +469,21 @@ class SearchService extends __BaseService {
let __body: any = null;
__body = params.eans;
let req = new HttpRequest<any>('POST', this.rootUrl + `/branch/${encodeURIComponent(String(params.branchNumber))}/s/byean`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/branch/${encodeURIComponent(String(params.branchNumber))}/s/byean`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ListResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -429,7 +495,9 @@ class SearchService extends __BaseService {
* - `branchNumber`: Filiale-Nr (optional)
*/
SearchByEAN3(params: SearchService.SearchByEAN3Params): __Observable<ListResponseArgsOfItemDTO> {
return this.SearchByEAN3Response(params).pipe(__map((_r) => _r.body as ListResponseArgsOfItemDTO));
return this.SearchByEAN3Response(params).pipe(
__map(_r => _r.body as ListResponseArgsOfItemDTO)
);
}
/**
@@ -446,17 +514,21 @@ class SearchService extends __BaseService {
let __body: any = null;
if (params.doNotTrack != null) __params = __params.set('doNotTrack', params.doNotTrack.toString());
let req = new HttpRequest<any>('GET', this.rootUrl + `/s/${encodeURIComponent(String(params.id))}`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/s/${encodeURIComponent(String(params.id))}`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -468,7 +540,9 @@ class SearchService extends __BaseService {
* - `doNotTrack`:
*/
SearchDetail(params: SearchService.SearchDetailParams): __Observable<ResponseArgsOfItemDTO> {
return this.SearchDetailResponse(params).pipe(__map((_r) => _r.body as ResponseArgsOfItemDTO));
return this.SearchDetailResponse(params).pipe(
__map(_r => _r.body as ResponseArgsOfItemDTO)
);
}
/**
@@ -486,6 +560,7 @@ class SearchService extends __BaseService {
let __headers = new HttpHeaders();
let __body: any = null;
if (params.doNotTrack != null) __params = __params.set('doNotTrack', params.doNotTrack.toString());
let req = new HttpRequest<any>(
'GET',
@@ -494,15 +569,14 @@ class SearchService extends __BaseService {
{
headers: __headers,
params: __params,
responseType: 'json',
},
);
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -516,7 +590,9 @@ class SearchService extends __BaseService {
* - `doNotTrack`:
*/
SearchDetail2(params: SearchService.SearchDetail2Params): __Observable<ResponseArgsOfItemDTO> {
return this.SearchDetail2Response(params).pipe(__map((_r) => _r.body as ResponseArgsOfItemDTO));
return this.SearchDetail2Response(params).pipe(
__map(_r => _r.body as ResponseArgsOfItemDTO)
);
}
/**
@@ -528,17 +604,21 @@ class SearchService extends __BaseService {
let __headers = new HttpHeaders();
let __body: any = null;
let req = new HttpRequest<any>('GET', this.rootUrl + `/s/ean/${encodeURIComponent(String(ean))}`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/s/ean/${encodeURIComponent(String(ean))}`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -546,7 +626,9 @@ class SearchService extends __BaseService {
* @param ean EAN
*/
SearchDetailByEAN(ean: string): __Observable<ResponseArgsOfItemDTO> {
return this.SearchDetailByEANResponse(ean).pipe(__map((_r) => _r.body as ResponseArgsOfItemDTO));
return this.SearchDetailByEANResponse(ean).pipe(
__map(_r => _r.body as ResponseArgsOfItemDTO)
);
}
/**
@@ -562,6 +644,7 @@ class SearchService extends __BaseService {
let __headers = new HttpHeaders();
let __body: any = null;
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/stock/${encodeURIComponent(String(params.stockId))}/ean/${encodeURIComponent(String(params.ean))}`,
@@ -569,15 +652,14 @@ class SearchService extends __BaseService {
{
headers: __headers,
params: __params,
responseType: 'json',
},
);
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfItemDTO>;
}),
})
);
}
/**
@@ -589,7 +671,9 @@ class SearchService extends __BaseService {
* - `ean`: EAN
*/
SearchDetailByEAN2(params: SearchService.SearchDetailByEAN2Params): __Observable<ResponseArgsOfItemDTO> {
return this.SearchDetailByEAN2Response(params).pipe(__map((_r) => _r.body as ResponseArgsOfItemDTO));
return this.SearchDetailByEAN2Response(params).pipe(
__map(_r => _r.body as ResponseArgsOfItemDTO)
);
}
/**
@@ -599,24 +683,63 @@ class SearchService extends __BaseService {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
let req = new HttpRequest<any>('GET', this.rootUrl + `/s/settings`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/s/settings`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfUISettingsDTO>;
}),
})
);
}
/**
* Settings
*/
SearchSettings(): __Observable<ResponseArgsOfUISettingsDTO> {
return this.SearchSettingsResponse().pipe(__map((_r) => _r.body as ResponseArgsOfUISettingsDTO));
return this.SearchSettingsResponse().pipe(
__map(_r => _r.body as ResponseArgsOfUISettingsDTO)
);
}
/**
* Settings
*/
SearchLoyaltySettingsResponse(): __Observable<__StrictHttpResponse<ResponseArgsOfUISettingsDTO>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/s/loyalty/settings`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfUISettingsDTO>;
})
);
}
/**
* Settings
*/
SearchLoyaltySettings(): __Observable<ResponseArgsOfUISettingsDTO> {
return this.SearchLoyaltySettingsResponse().pipe(
__map(_r => _r.body as ResponseArgsOfUISettingsDTO)
);
}
/**
@@ -626,24 +749,30 @@ class SearchService extends __BaseService {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
let req = new HttpRequest<any>('GET', this.rootUrl + `/s/filter`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/s/filter`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfIEnumerableOfInputGroupDTO>;
}),
})
);
}
/**
* Filter
*/
SearchSearchFilter(): __Observable<ResponseArgsOfIEnumerableOfInputGroupDTO> {
return this.SearchSearchFilterResponse().pipe(__map((_r) => _r.body as ResponseArgsOfIEnumerableOfInputGroupDTO));
return this.SearchSearchFilterResponse().pipe(
__map(_r => _r.body as ResponseArgsOfIEnumerableOfInputGroupDTO)
);
}
/**
@@ -653,24 +782,30 @@ class SearchService extends __BaseService {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
let req = new HttpRequest<any>('GET', this.rootUrl + `/s/sort`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/s/sort`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfIEnumerableOfOrderByDTO>;
}),
})
);
}
/**
* Filter
*/
SearchSearchSort(): __Observable<ResponseArgsOfIEnumerableOfOrderByDTO> {
return this.SearchSearchSortResponse().pipe(__map((_r) => _r.body as ResponseArgsOfIEnumerableOfOrderByDTO));
return this.SearchSearchSortResponse().pipe(
__map(_r => _r.body as ResponseArgsOfIEnumerableOfOrderByDTO)
);
}
/**
@@ -682,17 +817,21 @@ class SearchService extends __BaseService {
let __headers = new HttpHeaders();
let __body: any = null;
if (take != null) __params = __params.set('take', take.toString());
let req = new HttpRequest<any>('GET', this.rootUrl + `/s/history`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/s/history`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfIEnumerableOfQueryTokenDTO>;
}),
})
);
}
/**
@@ -700,7 +839,9 @@ class SearchService extends __BaseService {
* @param take Take
*/
SearchHistory(take?: null | number): __Observable<ResponseArgsOfIEnumerableOfQueryTokenDTO> {
return this.SearchHistoryResponse(take).pipe(__map((_r) => _r.body as ResponseArgsOfIEnumerableOfQueryTokenDTO));
return this.SearchHistoryResponse(take).pipe(
__map(_r => _r.body as ResponseArgsOfIEnumerableOfQueryTokenDTO)
);
}
/**
@@ -713,25 +854,27 @@ class SearchService extends __BaseService {
*
* @return ResponseArgs of Recomendations
*/
SearchGetRecommendationsResponse(
params: SearchService.SearchGetRecommendationsParams,
): __Observable<__StrictHttpResponse<ResponseArgsOfIEnumerableOfItemDTO>> {
SearchGetRecommendationsResponse(params: SearchService.SearchGetRecommendationsParams): __Observable<__StrictHttpResponse<ResponseArgsOfIEnumerableOfItemDTO>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
if (params.sessionId != null) __params = __params.set('sessionId', params.sessionId.toString());
let req = new HttpRequest<any>('GET', this.rootUrl + `/s/recommendations/${encodeURIComponent(String(params.digId))}`, __body, {
headers: __headers,
params: __params,
responseType: 'json',
});
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/s/recommendations/${encodeURIComponent(String(params.digId))}`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter((_r) => _r instanceof HttpResponse),
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfIEnumerableOfItemDTO>;
}),
})
);
}
/**
@@ -745,15 +888,19 @@ class SearchService extends __BaseService {
* @return ResponseArgs of Recomendations
*/
SearchGetRecommendations(params: SearchService.SearchGetRecommendationsParams): __Observable<ResponseArgsOfIEnumerableOfItemDTO> {
return this.SearchGetRecommendationsResponse(params).pipe(__map((_r) => _r.body as ResponseArgsOfIEnumerableOfItemDTO));
return this.SearchGetRecommendationsResponse(params).pipe(
__map(_r => _r.body as ResponseArgsOfIEnumerableOfItemDTO)
);
}
}
module SearchService {
/**
* Parameters for SearchTop2
*/
export interface SearchTop2Params {
/**
* Lager PK (optional)
*/
@@ -769,6 +916,7 @@ module SearchService {
* Parameters for SearchSearch2
*/
export interface SearchSearch2Params {
/**
* Lager PK (optional)
*/
@@ -784,6 +932,7 @@ module SearchService {
* Parameters for SearchAutocomplete2
*/
export interface SearchAutocomplete2Params {
/**
* Lager PK (optional)
*/
@@ -799,6 +948,7 @@ module SearchService {
* Parameters for SearchById2
*/
export interface SearchById2Params {
/**
* Lager PK (optional)
*/
@@ -814,6 +964,7 @@ module SearchService {
* Parameters for SearchByEAN2
*/
export interface SearchByEAN2Params {
/**
* Lager PK (optional)
*/
@@ -829,6 +980,7 @@ module SearchService {
* Parameters for SearchByEAN3
*/
export interface SearchByEAN3Params {
/**
* EANs
*/
@@ -844,6 +996,7 @@ module SearchService {
* Parameters for SearchDetail
*/
export interface SearchDetailParams {
/**
* PK
*/
@@ -855,6 +1008,7 @@ module SearchService {
* Parameters for SearchDetail2
*/
export interface SearchDetail2Params {
/**
* Lager PK (optional)
*/
@@ -871,6 +1025,7 @@ module SearchService {
* Parameters for SearchDetailByEAN2
*/
export interface SearchDetailByEAN2Params {
/**
* Lager PK (optional)
*/
@@ -895,4 +1050,4 @@ module SearchService {
}
}
export { SearchService };
export { SearchService }

View File

@@ -6,4 +6,4 @@ import { HttpResponse } from '@angular/common/http';
*/
export type StrictHttpResponse<T> = HttpResponse<T> & {
readonly body: T;
};
}

View File

@@ -10,7 +10,8 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/catalogue/data-access/jest.config.ts"
"jestConfig": "libs/catalogue/data-access/jest.config.ts",
"tsConfig": "libs/catalogue/data-access/tsconfig.spec.json"
}
},
"lint": {

View File

@@ -1 +1,2 @@
export * from './catalouge-search.schemas';
export * from './query-token.schema';

View File

@@ -0,0 +1,31 @@
import { z } from 'zod';
/**
* Schema for defining order-by parameters in queries.
* Used for sorting search results.
*/
export const OrderBySchema = z.object({
by: z.string(), // Field name to sort by
label: z.string(), // Display label for the sort option
desc: z.boolean(), // Whether sorting is descending
selected: z.boolean(), // Whether this sort option is currently selected
});
/**
* Schema for validating and parsing query tokens.
* Used for search operations to ensure consistent query structure.
*/
export const QueryTokenSchema = z.object({
filter: z.record(z.any()).default({}), // Filter criteria as key-value pairs
input: z.record(z.string()).default({}).optional(),
orderBy: z.array(OrderBySchema).default([]).optional(), // Sorting parameters
skip: z.number().int().min(0).default(0),
take: z.number().int().min(1).max(100).default(20),
});
/**
* Type representing the structure of a query token.
* Generated from the Zod schema for type safety.
*/
export type QueryToken = z.infer<typeof QueryTokenSchema>;
export type QueryTokenInput = z.input<typeof QueryTokenSchema>;

View File

@@ -4,6 +4,7 @@ import { CatalougeSearchService } from './catalouge-search.service';
import { SearchService } from '@generated/swagger/cat-search-api';
import { Item } from '../models';
import { SearchByTermInput } from '../schemas/catalouge-search.schemas';
import { QueryTokenInput } from '../schemas';
describe('CatalougeSearchService', () => {
let service: CatalougeSearchService;
@@ -23,7 +24,9 @@ describe('CatalougeSearchService', () => {
});
service = TestBed.inject(CatalougeSearchService);
searchServiceSpy = TestBed.inject(SearchService) as jest.Mocked<SearchService>;
searchServiceSpy = TestBed.inject(
SearchService,
) as jest.Mocked<SearchService>;
});
it('should be created', () => {
@@ -34,8 +37,16 @@ describe('CatalougeSearchService', () => {
it('should return items when search is successful', (done) => {
// Arrange
const mockItems: Item[] = [
{ id: 1, product: { name: 'Item 1' }, catalogAvailability: { available: true } } as unknown as Item,
{ id: 2, product: { name: 'Item 2' }, catalogAvailability: { available: true } } as unknown as Item,
{
id: 1,
product: { name: 'Item 1' },
catalogAvailability: { available: true },
} as unknown as Item,
{
id: 2,
product: { name: 'Item 2' },
catalogAvailability: { available: true },
} as unknown as Item,
];
const mockResponse = {
error: false,
@@ -48,7 +59,10 @@ describe('CatalougeSearchService', () => {
next: (result) => {
// Assert
expect(result).toEqual(mockItems);
expect(searchServiceSpy.SearchByEAN).toHaveBeenCalledWith(['123456789', '987654321']);
expect(searchServiceSpy.SearchByEAN).toHaveBeenCalledWith([
'123456789',
'987654321',
]);
done();
},
error: done.fail,
@@ -77,7 +91,13 @@ describe('CatalougeSearchService', () => {
it('should handle single EAN', (done) => {
// Arrange
const mockItems: Item[] = [{ id: 1, product: { name: 'Item 1' }, catalogAvailability: { available: true } } as unknown as Item];
const mockItems: Item[] = [
{
id: 1,
product: { name: 'Item 1' },
catalogAvailability: { available: true },
} as unknown as Item,
];
const mockResponse = {
error: false,
result: mockItems,
@@ -89,7 +109,9 @@ describe('CatalougeSearchService', () => {
next: (result) => {
// Assert
expect(result).toEqual(mockItems);
expect(searchServiceSpy.SearchByEAN).toHaveBeenCalledWith(['123456789']);
expect(searchServiceSpy.SearchByEAN).toHaveBeenCalledWith([
'123456789',
]);
done();
},
error: done.fail,
@@ -121,7 +143,11 @@ describe('CatalougeSearchService', () => {
it('should return search results when successful', async () => {
// Arrange
const mockItems: Item[] = [
{ id: 1, product: { name: 'Test Item' }, catalogAvailability: { available: true } } as unknown as Item,
{
id: 1,
product: { name: 'Test Item' },
catalogAvailability: { available: true },
} as unknown as Item,
];
const mockResponse = {
error: false,
@@ -166,9 +192,9 @@ describe('CatalougeSearchService', () => {
const abortController = new AbortController();
// Act & Assert
await expect(service.searchByTerm(params, abortController.signal))
.rejects
.toThrow('Search failed');
await expect(
service.searchByTerm(params, abortController.signal),
).rejects.toThrow('Search failed');
});
it('should handle abort signal', async () => {
@@ -219,4 +245,167 @@ describe('CatalougeSearchService', () => {
});
});
});
});
describe('searchLoyaltyItems', () => {
it('should return loyalty items when search is successful', async () => {
// Arrange
const mockItems: Item[] = [
{
id: 1,
product: { name: 'Loyalty Item 1' },
catalogAvailability: { available: true },
} as unknown as Item,
{
id: 2,
product: { name: 'Loyalty Item 2' },
catalogAvailability: { available: true },
} as unknown as Item,
];
const mockResponse = {
error: false,
result: mockItems,
total: 2,
};
searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));
const params: QueryTokenInput = {
filter: { category: 'electronics' },
input: { brand: 'test-brand' },
orderBy: [{ by: 'name', label: 'Name', desc: false, selected: true }],
skip: 0,
take: 10,
};
const abortController = new AbortController();
// Act
const result = await service.searchLoyaltyItems(
params,
abortController.signal,
);
// Assert
expect(result).toEqual(mockResponse);
expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
filter: {
format: '!eb;!dl',
praemie: '1-',
category: 'electronics',
},
input: { brand: 'test-brand' },
orderBy: [{ by: 'name', label: 'Name', desc: false, selected: true }],
skip: 0,
take: 10,
});
});
it('should apply default loyalty filter when no custom filter provided', async () => {
// Arrange
const mockItems: Item[] = [
{
id: 1,
product: { name: 'Loyalty Item' },
catalogAvailability: { available: true },
} as unknown as Item,
];
const mockResponse = {
error: false,
result: mockItems,
total: 1,
};
searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));
const params: QueryTokenInput = {
input: { search: 'test' },
};
const abortController = new AbortController();
// Act
const result = await service.searchLoyaltyItems(
params,
abortController.signal,
);
// Assert
expect(result).toEqual(mockResponse);
expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
filter: {
format: '!eb;!dl',
praemie: '1-',
},
input: { search: 'test' },
orderBy: undefined,
skip: 0,
take: 20,
});
});
it('should merge custom filter with loyalty filter', async () => {
// Arrange
const mockResponse = {
error: false,
result: [],
total: 0,
};
searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));
const params: QueryTokenInput = {
filter: {
category: 'books',
format: 'custom-format', // This should override the default format
},
input: { title: 'test-book' },
skip: 5,
take: 15,
};
const abortController = new AbortController();
// Act
await service.searchLoyaltyItems(params, abortController.signal);
// Assert
expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
filter: {
format: 'custom-format', // Custom filter should override default
praemie: '1-',
category: 'books',
},
input: { title: 'test-book' },
orderBy: undefined,
skip: 5,
take: 15,
});
});
it('should handle minimal parameters with defaults', async () => {
// Arrange
const mockResponse = {
error: false,
result: [],
total: 0,
};
searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));
const params: QueryTokenInput = {};
const abortController = new AbortController();
// Act
const result = await service.searchLoyaltyItems(
params,
abortController.signal,
);
// Assert
expect(result).toEqual(mockResponse);
expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
filter: {
format: '!eb;!dl',
praemie: '1-',
},
input: undefined,
orderBy: undefined,
skip: 0,
take: 20,
});
});
});
});

View File

@@ -8,6 +8,7 @@ import {
SearchByTermSchema,
} from '../schemas/catalouge-search.schemas';
import { ListResponseArgs } from '@isa/common/data-access';
import { QueryTokenInput, QueryTokenSchema } from '../schemas';
@Injectable({ providedIn: 'root' })
export class CatalougeSearchService {
@@ -50,4 +51,33 @@ export class CatalougeSearchService {
return res as ListResponseArgs<Item>;
}
async searchLoyaltyItems(params: QueryTokenInput, abortSignal: AbortSignal) {
const { filter, input, orderBy, skip, take } =
QueryTokenSchema.parse(params);
const loyaltyFilter = {
format: '!eb;!dl',
praemie: '1-',
...filter,
};
const req$ = this.#searchService
.SearchSearch({
filter: loyaltyFilter,
input,
orderBy,
skip,
take,
})
.pipe(takeUntilAborted(abortSignal));
const res = await firstValueFrom(req$);
if (res.error) {
throw new Error(res.message);
}
return res as ListResponseArgs<Item>;
}
}

View File

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

View File

@@ -0,0 +1,34 @@
const nx = require('@nx/eslint-plugin');
const baseConfig = require('../../../eslint.config.js');
module.exports = [
...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,20 @@
{
"name": "checkout-data-access",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/checkout/data-access/src",
"prefix": "lib",
"projectType": "library",
"tags": [],
"targets": {
"test": {
"executor": "@nx/vite:test",
"outputs": ["{options.reportsDirectory}"],
"options": {
"reportsDirectory": "../../../coverage/libs/checkout/data-access"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}

View File

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

View File

@@ -0,0 +1 @@
export * from './query-settings';

View File

@@ -0,0 +1,3 @@
import { UISettingsDTO } from '@generated/swagger/cat-search-api';
export type QuerySettings = UISettingsDTO;

View File

@@ -0,0 +1 @@
export * from './reward-checkout.service';

View File

@@ -0,0 +1,28 @@
import { Injectable, inject } from '@angular/core';
import { logger } from '@isa/core/logging';
import { SearchService } from '@generated/swagger/cat-search-api';
import { firstValueFrom } from 'rxjs';
import { QuerySettings } from '../models';
@Injectable({ providedIn: 'root' })
export class RewardCheckoutService {
#searchService = inject(SearchService);
#logger = logger(() => ({ service: 'RewardCheckoutService' }));
async fetchQuerySettings(): Promise<QuerySettings> {
this.#logger.info('Fetching query settings from API');
const req$ = this.#searchService.SearchLoyaltySettings();
const res = await firstValueFrom(req$);
if (res.error || !res.result) {
const error = new Error(res.message || 'Failed to fetch Query Settings');
this.#logger.error('Failed to fetch query settings', error);
throw error;
}
this.#logger.debug('Successfully fetched query settings');
return res.result as QuerySettings;
}
}

View File

@@ -0,0 +1,13 @@
import '@angular/compiler';
import '@analogjs/vitest-angular/setup-zone';
import {
BrowserTestingModule,
platformBrowserTesting,
} from '@angular/platform-browser/testing';
import { getTestBed } from '@angular/core/testing';
getTestBed().initTestEnvironment(
BrowserTestingModule,
platformBrowserTesting(),
);

View File

@@ -0,0 +1,30 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"importHelpers": true,
"moduleResolution": "bundler",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"module": "preserve"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"typeCheckHostBindings": true,
"strictTemplates": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@@ -0,0 +1,27 @@
{
"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",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": ["src/**/*.ts"]
}

View File

@@ -0,0 +1,29 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
]
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"files": ["src/test-setup.ts"]
}

View File

@@ -0,0 +1,27 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import angular from '@analogjs/vite-plugin-angular';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/checkout/data-access',
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: ['src/test-setup.ts'],
reporters: ['default'],
coverage: {
reportsDirectory: '../../../coverage/libs/checkout/data-access',
provider: 'v8' as const,
},
},
}));

View File

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

View File

@@ -0,0 +1,34 @@
const nx = require('@nx/eslint-plugin');
const baseConfig = require('../../../../eslint.config.js');
module.exports = [
...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,20 @@
{
"name": "reward-catalog",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/checkout/feature/reward-catalog/src",
"prefix": "lib",
"projectType": "library",
"tags": [],
"targets": {
"test": {
"executor": "@nx/vite:test",
"outputs": ["{options.reportsDirectory}"],
"options": {
"reportsDirectory": "../../../../coverage/libs/checkout/feature/reward-catalog"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}

View File

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

View File

@@ -0,0 +1,9 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import {
QuerySettings,
RewardCheckoutService,
} from '@isa/checkout/data-access';
export const querySettingsResolverFn: ResolveFn<QuerySettings> = () =>
inject(RewardCheckoutService).fetchQuerySettings();

View File

@@ -0,0 +1 @@
export * from './reward-catalog.resource';

View File

@@ -0,0 +1,33 @@
import { inject, resource } from '@angular/core';
import {
CatalougeSearchService,
QueryTokenInput,
} from '@isa/catalogue/data-access';
import { ResponseArgsError } from '@isa/common/data-access';
import { SearchTrigger } from '@isa/shared/filter';
export const createRewardCatalogResource = (
params: () => {
queryToken: QueryTokenInput;
searchTrigger: SearchTrigger | 'reload' | 'initial';
},
) => {
const catalogSearchService = inject(CatalougeSearchService);
return resource({
params,
loader: async ({ abortSignal, params }) => {
const fetchCatalogResponse =
await catalogSearchService.searchLoyaltyItems(
params.queryToken,
abortSignal,
);
if (fetchCatalogResponse?.error) {
throw new ResponseArgsError(fetchCatalogResponse);
}
return fetchCatalogResponse;
},
});
};

View File

@@ -0,0 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'reward-catalog-item',
templateUrl: './reward-catalog-item.component.html',
styleUrl: './reward-catalog-item.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RewardCatalogItemComponent {}

View File

@@ -0,0 +1,70 @@
<reward-start-card></reward-start-card>
<!-- <filter-controls-panel (triggerSearch)="search($event)"></filter-controls-panel> -->
<span
class="text-isa-neutral-900 isa-text-body-2-regular self-start"
data-what="result-count"
>
<!-- {{ hits() }} Einträge -->
XXX Einträge
</span>
<div class="flex flex-col gap-4 w-full items-center justify-center mb-24">
<!-- @if (hits()) {
<!-- @for (item of items(); track item.id) {
@defer (on viewport) {
<reward-catalog-item
#listElement
[item]="item"
[stock]="getStockForItem(item)"
[stockFetching]="inStockFetching()"
[productGroupValue]="getProductGroupValueForItem(item)"
(inProgressChange)="onListItemActionInProgress($event)"
></reward-catalog-item>
} @placeholder {
<div class="h-[7.75rem] w-full flex items-center justify-center">
<ui-icon-button
[pending]="true"
[color]="'tertiary'"
data-what="load-spinner"
data-which="item-placeholder"
></ui-icon-button>
</div>
}
}
} @else {
<ui-empty-state
class="w-full justify-self-center"
title="Keine Suchergebnisse"
description="Bitte prüfen Sie die Schreibweise oder ändern Sie die Filtereinstellungen."
>
</ui-empty-state>
} -->
<ui-empty-state
class="w-full justify-self-center"
title="Keine Suchergebnisse"
description="Bitte prüfen Sie die Schreibweise oder ändern Sie die Filtereinstellungen."
>
</ui-empty-state>
</div>
<!--
<ui-stateful-button
class="fixed right-6 bottom-6"
(clicked)="remitItems()"
(action)="remitItems()"
[(state)]="remitItemsState"
defaultContent="Prämie auswählen"
defaultWidth="13rem"
[errorContent]="remitItemsError()"
errorWidth="32rem"
errorAction="Erneut versuchen"
successContent="Hinzugefügt"
successWidth="20rem"
size="large"
color="brand"
[pending]="remitItemsInProgress()"
[disabled]="!hasSelectedItems() || listItemActionInProgress()"
>
</ui-stateful-button>} -->

View File

@@ -0,0 +1,131 @@
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
signal,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import {
provideFilter,
withQuerySettingsFactory,
withQueryParamsSync,
FilterControlsPanelComponent,
SearchTrigger,
FilterService,
} from '@isa/shared/filter';
import { IconButtonComponent } from '@isa/ui/buttons';
import { injectRestoreScrollPosition } from '@isa/utils/scroll-position';
import { RewardStartCardComponent } from './reward-start-card/reward-start-card.component';
import { logger } from '@isa/core/logging';
import { createRewardCatalogResource } from './resources';
import { EmptyStateComponent } from '@isa/ui/empty-state';
import { RewardCatalogItemComponent } from './reward-catalog-item/reward-catalog-item.component';
/**
* Factory function to retrieve query settings from the activated route data.
* @returns The query settings from the activated route data.
*/
function querySettingsFactory() {
return inject(ActivatedRoute).snapshot.data['querySettings'];
}
@Component({
selector: 'reward-catalog',
templateUrl: './reward-catalog.component.html',
styleUrl: './reward-catalog.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
provideFilter(
withQuerySettingsFactory(querySettingsFactory),
withQueryParamsSync(),
),
],
imports: [
FilterControlsPanelComponent,
IconButtonComponent,
EmptyStateComponent,
RewardStartCardComponent,
RewardCatalogItemComponent,
],
host: {
'[class]':
'"w-full flex flex-col gap-4 mt-5 isa-desktop:mt-6 overflow-x-hidden"',
},
})
export class RewardCatalogComponent {
/**
* Signal to trigger searches in the reward catalog.
* Can be 'reload', 'initial', or a specific SearchTrigger value.
*/
searchTrigger = signal<SearchTrigger | 'reload' | 'initial'>('initial');
/**
* Logger instance for logging component events and errors.
* @private
*/
#logger = logger(() => ({
component: 'RewardCatalogComponent',
}));
/**
* FilterService instance for managing filter state and queries.
* @private
*/
// #filterService = inject(FilterService);
/**
* Restores scroll position when navigating back to this component.
*/
restoreScrollPosition = injectRestoreScrollPosition();
/**
* Resource for fetching and managing the reward catalog data.
* Uses the FilterService to get the current query token and reacts to search triggers.
* @private
* @returns The reward catalog resource.
*/
// rewardCatalogResource = createRewardCatalogResource(() => {
// return {
// queryToken: this.#filterService.query(),
// searchTrigger: this.searchTrigger(),
// };
// });
/**
* Computed signal for the current reward catalog response.
* @returns The latest reward catalog response or undefined.
*/
// listResponseValue = computed(() => this.rewardCatalogResource.value());
/**
* Computed signal indicating whether the reward catalog resource is currently fetching data.
* @returns True if fetching, false otherwise.
*/
// listFetching = computed(
// () => this.rewardCatalogResource.status() === 'loading',
// );
/**
* Computed signal for the reward catalog items to display.
* @returns Array of Item.
*/
// items = computed(() => {
// const value = this.listResponseValue();
// return value?.result ? value.result : [];
// });
/**
* Computed signal for the total number of hits in the reward catalog.
* @returns Number of hits, or 0 if unavailable.
*/
// hits = computed(() => {
// const value = this.listResponseValue();
// return value?.hits ? value.hits : 0;
// });
search(trigger: SearchTrigger): void {
// this.#filterService.commit();
this.searchTrigger.set(trigger);
}
}

View File

@@ -0,0 +1,11 @@
:host {
@apply w-full grid grid-cols-[1fr,auto] gap-6 rounded-2xl bg-isa-neutral-400 p-6 justify-between;
}
.reward-start-card__title-container {
@apply flex flex-col gap-4 text-isa-neutral-900;
}
.reward-start-card__select-cta {
@apply h-12 w-[13rem] justify-self-end;
}

View File

@@ -0,0 +1,20 @@
<div class="reward-start-card__title-container">
<h2 class="isa-text-subtitle-1-regular">Prämienshop</h2>
<p class="isa-text-body-1-regular">
Sie können die Prämien nach Punkten sortieren, filter oder direkt über die
Suche auswählen
</p>
</div>
<button
class="reward-start-card__select-cta"
data-which="select-customer"
data-what="select-customer"
uiButton
color="tertiary"
size="large"
(click)="selectCustomer()"
>
Kund*in auswählen
</button>

View File

@@ -0,0 +1,15 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ButtonComponent } from '@isa/ui/buttons';
@Component({
selector: 'reward-start-card',
templateUrl: './reward-start-card.component.html',
styleUrl: './reward-start-card.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ButtonComponent],
})
export class RewardStartCardComponent {
selectCustomer() {
console.log('Kunde auswählen');
}
}

View File

@@ -0,0 +1,19 @@
import { Routes } from '@angular/router';
import { RewardCatalogComponent } from './reward-catalog.component';
import { querySettingsResolverFn } from './resolvers/query-settings.resolver-fn';
export const routes: Routes = [
{
path: '',
component: RewardCatalogComponent,
resolve: { querySettings: querySettingsResolverFn },
data: {
scrollPositionRestoration: true,
},
},
{
path: '',
redirectTo: '',
pathMatch: 'full',
},
];

View File

@@ -0,0 +1,13 @@
import '@angular/compiler';
import '@analogjs/vitest-angular/setup-zone';
import {
BrowserTestingModule,
platformBrowserTesting,
} from '@angular/platform-browser/testing';
import { getTestBed } from '@angular/core/testing';
getTestBed().initTestEnvironment(
BrowserTestingModule,
platformBrowserTesting(),
);

View File

@@ -0,0 +1,30 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"importHelpers": true,
"moduleResolution": "bundler",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"module": "preserve"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"typeCheckHostBindings": true,
"strictTemplates": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@@ -0,0 +1,27 @@
{
"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",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": ["src/**/*.ts"]
}

View File

@@ -0,0 +1,29 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
]
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"files": ["src/test-setup.ts"]
}

Some files were not shown because too many files have changed in this diff Show More