Compare commits

..

12 Commits

Author SHA1 Message Date
Lorenz Hilpert
58b0fde3f8 Merge branch 'develop' into feature/4736-retoure 2024-10-18 14:43:29 +02:00
Lorenz Hilpert
22b0c57fb0 Styling Produktinfo in Retoure Details angepasst 2024-05-17 20:34:50 +02:00
Lorenz Hilpert
e59ddaf6a6 Anpassung Label und Felder in Retoure Details 2024-05-15 17:09:24 +02:00
Lorenz Hilpert
a74b4ed791 Details Page um mehrere Felder erweitert
Select Logik erweitert
Templates für Date felder angelegt
2024-05-14 19:05:29 +02:00
Lorenz Hilpert
3017f341af Mengenauswahl Input Styling angepasst 2024-05-14 11:54:48 +02:00
Lorenz Hilpert
c75a6d87f9 Improved Quantity Selector 2024-05-14 11:46:33 +02:00
Lorenz Hilpert
b85b69f078 Artikelmenge wurde nicht korrekt angezeigt 2024-05-13 18:11:35 +02:00
Lorenz Hilpert
93c050f09a Aktuallisierung des Breadcrumbs wenn sie der Suchparameter ändert 2024-05-13 17:28:29 +02:00
Lorenz Hilpert
23b2a05d47 Loader für die Details-Page
 Loader für die Items in der List-Page
 Breadcrumb in der List und Details Page eingebunden
 Nachladen von Items wenn Ende des ScrollContainers erreicht wurde
 Mengenauswahl eingebunden
2024-05-13 17:05:20 +02:00
Lorenz Hilpert
bd746f9915 Retoure Styling und data-table komponente 2024-05-10 18:56:52 +02:00
Lorenz Hilpert
91f70b0503 Removed race to catch fetch result 2024-05-03 15:39:45 +02:00
Lorenz Hilpert
3237dff1bc Suche und Anzeige von Retouren 2024-05-03 14:42:10 +02:00
4294 changed files with 60589 additions and 513247 deletions

View File

@@ -2,3 +2,6 @@ last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 iOS major versions
safari > 11
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

View File

@@ -7,7 +7,6 @@ indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = crlf
[*.md]
max_line_length = off

View File

@@ -1,86 +0,0 @@
# Commit Message Instructions (Conventional Commits)
Commit messages should follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/). This provides a standardized format for commit messages, making it easier to understand changes, automate changelog generation, and trigger build/publish processes.
## Format
The commit message structure is as follows:
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
---
### Components
1. **Type**: Indicates the kind of change introduced by the commit. Must be one of the allowed types (see below).
2. **Scope (Optional)**: A noun describing the section of the codebase affected by the change (e.g., `auth`, `ui`, `build`). Enclosed in parentheses.
3. **Description**: A concise summary of the change in the imperative, present tense (e.g., "add login feature", not "added login feature" or "adds login feature"). Starts with a lowercase letter and should not end with a period. Max 72 characters recommended for the entire header line (`<type>[optional scope]: <description>`).
4. **Body (Optional)**: A more detailed explanation of the changes. Use the imperative, present tense. Explain the _what_ and _why_ vs. _how_. Separate from the description by a blank line. Wrap lines at 72 characters.
5. **Footer(s) (Optional)**: Contains additional metadata. Common footers include:
- `BREAKING CHANGE:` followed by a description of the breaking change. A `!` can also be appended to the type/scope (`feat!:`) to indicate a breaking change.
- Issue references (e.g., `Refs: #123`, `Closes: #456`). Separate from the body by a blank line.
---
### Allowed Types
- **feat**: A new feature for the user.
- **fix**: A bug fix for the user.
- **build**: Changes that affect the build system or external dependencies (e.g., gulp, broccoli, npm).
- **chore**: Other changes that don't modify src or test files (e.g., updating dependencies, build tasks).
- **ci**: Changes to CI configuration files and scripts (e.g., Travis, Circle, BrowserStack, SauceLabs).
- **docs**: Documentation only changes.
- **perf**: A code change that improves performance.
- **refactor**: A code change that neither fixes a bug nor adds a feature.
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc).
- **test**: Adding missing tests or correcting existing tests.
---
### Examples
**Commit with description only:**
```
fix: correct minor typos in code
```
**Commit with scope:**
```
feat(lang): add polish language
```
**Commit with body and breaking change footer:**
```
refactor: drop support for Node 6
The new implementation relies on async/await and other features
introduced in Node 8+.
BREAKING CHANGE: refactor to use JavaScript features not available in Node 6.
```
**Commit with scope, body, and issue footer:**
```
docs(readme): improve installation instructions
Provide clearer steps for setting up the development environment.
Add links to prerequisite tools.
Closes: #12
```
**Commit with `!` for breaking change:**
```
feat(api)!: send an email to the customer when a product is shipped
```

View File

@@ -1,23 +0,0 @@
# Mentor Instructions
## Introduction
You are Mentor, an AI assistant focused on ensuring code quality, strict adherence to best practices, and development efficiency. **Your core function is to enforce the coding standards and guidelines established in this workspace.** Your goal is to help me produce professional, maintainable, and high-performing code.
**Always get the latest official documentation for Angular, Nx, or any related technology before implementing or when answering questions or providing feedback. Use Context7:**
## Tone and Personality
Maintain a professional, objective, and direct tone consistently:
- **Guideline Enforcement & Error Correction:** When code deviates from guidelines or contains errors, provide precise, technical feedback. Clearly state the issue, cite the relevant guideline or principle, and explain the required correction for optimal, maintainable results.
- **Technical Consultation:** In discussions about architecture, best practices, or complex coding inquiries, remain formal and analytical. Provide clear, well-reasoned explanations and recommendations grounded in industry standards and the project's specific guidelines.
## Behavioral Guidelines
- **Actionable Feedback:** Prioritize constructive, actionable feedback aimed at improving code quality, maintainability, and adherence to standards. Avoid rewriting code; focus on explaining the necessary changes and their rationale based on guidelines.
- **Strict Guideline Adherence:** Base _all_ feedback, suggestions, and explanations rigorously on the guidelines documented within this workspace. Cite specific rules and principles consistently.
- **Demand Clarity:** If a query or code snippet lacks sufficient detail for a thorough, professional analysis, request clarification.
- **Professional Framing:** Frame all feedback objectively, focusing on the technical aspects and the importance of meeting project standards for long-term success.
- **Context-Specific Expertise:** Provide specific, context-aware advice tailored to the code or problem, always within the framework of our established guidelines.
- **Enforce Standards:** Actively enforce project preferences for Type safety, Clean Code principles, and thorough documentation, as mandated by the workspace guidelines.

View File

@@ -1,182 +0,0 @@
# Code Review Instructions
## Summary
When conducting a code review, follow these steps to ensure a thorough and constructive process.
**Ensure that all review guidelines are followed. If any guideline is not adhered to, make it explicitly clear which guideline needs to be followed.**
## Review Process
1. 🎯 **Key Issues**
Identify critical issues in the code such as bugs, security vulnerabilities, or violations of the project's coding standards.
_Include specific links to files and line numbers (e.g., file.js#L10) where applicable._
2. 💡 **Suggestions for Improvement**
Highlight areas where the code can be enhanced in terms of readability, performance, maintainability, or adherence to best practices.
_Clarify what constitutes a "Critical" versus a "Minor" issue to avoid ambiguity._
3.**Code Examples**
Provide specific, concise code snippets that illustrate your suggestions.
_Include both a "Before" (problematic code) and an "After" (improved version) example where beneficial._
4. 📚 **Relevant Documentation Links**
Attach links to useful resources or official documentation to support the suggested changes.
_For example, link to ESLint, Jest, or Angular Style Guide pages when relevant._
## Tone and Feedback
- Be constructive and supportive.
Frame suggestions as opportunities for growth rather than criticism.
- Use the following emojis to categorize your feedback:
- 🚨 **Critical issues**
-**Minor Issues**
- ⚠️ **Warnings**
- 💡 **Suggestions**
-**Good practices**
## Additional Informations
- Missing tests and JSDocs are minor issues
- Missing unit test are minor issues
- Missing End-to-End (E2E) Testing Attributes (`data-what`, `data-which`) are warnings
### Review Template
````markdown
# Code Review
## Summary
A brief overview of the codes overall quality, highlighting key strengths and areas needing attention. This sets the stage for the detailed feedback below.
---
## 🚨 Critical Issues
High-priority issues that must be addressed immediately due to their potential to severely impact functionality, performance, or security.
### 1. High Priority: [Issue Title]
#### 🚨 Issue
Describe the issue clearly, including links to specific files and lines (e.g., file.js#L10). Explain why its critical—highlight crashes, security risks, or significant performance issues.
#### 💡 Suggestions for Improvement
Provide specific steps or alternative approaches to resolve the issue.
#### ✨ Code Example
**Current**: [file](file.js#L10) Problematic code with path to the file and line of the code
```typescript
// Code...
```
**Improvement**: Improved version
```typescript
// Code...
```
#### 📚 Relevant Documentation
Include URLs for further research (e.g., [Jest Documentation](https://jestjs.io/docs/getting-started)).
---
## ❗ Minor Issues
Issues that can improve code quality, maintainability, or adherence to best practices when resolved.
### 1. Medium Priority: [Issue Title]
#### ❗ Issue
Describe the issue clearly, including file and line references (e.g., file.js#L10). Explain the impact on the project.
#### 💡 Suggestions for Improvement
Offer concrete steps or alternative approaches to mitigate the issue.
#### ✨ Code Example
**Current**: [file](file.js#L10) Problematic code with path to the file and line of the code
```typescript
// Code...
```
**Improvement**: Improved version
```typescript
// Code...
```
#### 📚 Relevant Documentation
Provide links to further resources.
---
## ⚠️ Warnings
Low-priority issues or suggestions that could help prevent future problems or improve the code quality over time.
### 1. Low Priority: [Issue Title]
#### ⚠️ Issue
Describe the issue clearly with references (e.g., file.js#L10). Explain the potential impact if left unaddressed.
#### 💡 Suggestions for Improvement
Provide suggestions or alternative implementations to mitigate the issue.
#### ✨ Code Example
**Current**: [file](file.js#L10) Problematic code with path to the file and line of the code
```typescript
// Code...
```
**Improvement**: Improved version
```typescript
// Code...
```
#### 📚 Relevant Documentation
Include relevant resources for more information.
---
## 🛑 Bad Practices
Highlight up to five bad aspects of the code to reinforce improvements and encourage good practices. Use different funny emoji at the beginning of each bad practice.
- Emoji **Bad Practice 1**:
Describe a specific weakness (e.g., clear code structure) with an example reference (e.g., file.js#L20). Explain why its bad.
- Emoji **Bad Practice 2**:
Outline another negative feature (e.g., effective error handling) with a snippet reference.
---
## ✅ Good Practices
Highlight up to five positive aspects of the code to reinforce well-implemented patterns and encourage good practices. Use different funny emoji at the beginning of each good practice.
- Emoji **Good Practice 1**:
Describe a specific strength (e.g., clear code structure) with an example reference (e.g., file.js#L20). Explain why its commendable.
- Emoji **Good Practice 2**:
Outline another positive feature (e.g., effective error handling) with a snippet reference.
---
## 📓 Additional Notes
- **General Feedback**: Optional thoughts regarding the overall quality or potential areas for future improvement.
- **Next Steps**: Outline follow-up actions or further examination areas as needed.
````

View File

@@ -1,73 +0,0 @@
# Testing Instructions
## Framework and Tools
- **Vitest** is the recommended testing framework.
[Vitest Documentation (latest)](https://context7.com/vitest-dev/vitest/llms.txt?topic=getting+started)
- **Jest** and **Spectator** are **deprecated**.
Do not use them for new tests. Existing tests should be migrated to Vitest where possible.
## Guidelines
1. **Error Case Testing**: Ensure all edge cases and error scenarios are thoroughly tested.
2. **Arrange-Act-Assert Pattern**: Follow the Arrange-Act-Assert pattern for structuring your tests:
- **Arrange**: Set up the testing environment and initialize required variables.
- **Act**: Execute the functionality being tested.
- **Assert**: Verify the expected outcomes.
## Best Practices
- Write clear and descriptive test names.
- Ensure tests are isolated and do not depend on each other.
- Mock external dependencies to avoid side effects.
- Aim for high code coverage without compromising test quality.
## Example Test Structure
```typescript
// Example using Vitest (Jest and Spectator are deprecated)
import { describe, it, expect, beforeEach } from 'vitest';
import { render } from '@testing-library/angular';
import { MyComponent } from './my-component.component';
describe('MyComponent', () => {
let component: MyComponent;
beforeEach(async () => {
const { fixture } = await render(MyComponent);
component = fixture.componentInstance;
});
it('should display the correct title', async () => {
// Arrange
const expectedTitle = 'Hello World';
// Act
component.title = expectedTitle;
// If using Angular, trigger change detection:
// fixture.detectChanges();
// Assert
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toBe(expectedTitle);
});
it('should handle error cases gracefully', () => {
// Arrange
const invalidInput = null;
// Act
component.input = invalidInput;
// Assert
expect(() => component.processInput()).toThrowError('Invalid input');
});
});
```
## Additional Resources
- [Vitest Documentation (latest)](https://context7.com/vitest-dev/vitest/llms.txt?topic=getting+started)
- [Vitest Official Guide](https://vitest.dev/guide/)
- [Testing Library for Angular](https://testing-library.com/docs/angular-testing-library/intro/)
- **Jest** and **Spectator** documentation are deprecated

127
.gitignore vendored
View File

@@ -1,77 +1,50 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
.matomo
junit.xml
# compiled output
/dist
/tmp
/out-tsc
/
# dependencies
/node_modules
# profiling files
chrome-profiler-events.json
speed-measure-plugin.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage
/testresults
/libpeerconnection.log
npm-debug.log
yarn-error.log
yarn.lock
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
libs/swagger/src/lib/*
*storybook.log
.nx/cache
.nx/workspace-data
.angular
.claude
storybook-static
.cursor\rules\nx-rules.mdc
.github\instructions\nx.instructions.md
.cursor/rules/nx-rules.mdc
.github/instructions/nx.instructions.md
vite.config.*.timestamp*
vitest.config.*.timestamp*
.mcp.json
.memory.json
nx.instructions.md
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
# profiling files
chrome-profiler-events.json
speed-measure-plugin.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
.prettierrc
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage
/testresults
/libpeerconnection.log
npm-debug.log
yarn-error.log
yarn.lock
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
libs/swagger/src/lib/*

View File

@@ -1 +1 @@
npx lint-staged
npm run pretty-quick

View File

@@ -1,7 +0,0 @@
{
"*.ts": "npx eslint --fix --config eslint.config.js",
"*.tsx": "npx eslint --fix --config eslint.config.js",
"*.js": "npx eslint --fix --config eslint.config.js",
"*.jsx": "npx eslint --fix --config eslint.config.js",
"*.html": "npx eslint --fix --config eslint.config.js"
}

View File

@@ -1,8 +1,10 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/workspace-data
/node_modules
.angular
.vscode
/helmvalues
/apps/swagger
/ng-swagger-gen
*.json
*.yml

View File

@@ -1,37 +0,0 @@
{
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"tabWidth": 2,
"bracketSpacing": true,
"printWidth": 80,
"endOfLine": "auto",
"arrowParens": "always",
"quoteProps": "consistent",
"overrides": [
{
"files": "*.html",
"options": {
"parser": "html"
}
},
{
"files": "*.component.html",
"options": {
"parser": "angular"
}
},
{
"files": "*.scss",
"options": {
"singleQuote": false
}
},
{
"files": "*.json",
"options": {
"printWidth": 80
}
}
]
}

5
.prettierrc.json Normal file
View File

@@ -0,0 +1,5 @@
{
"singleQuote": true,
"printWidth": 140
}

View File

@@ -1,11 +1,7 @@
{
"recommendations": [
"johnpapa.angular2",
"esbenp.prettier-vscode",
"angular.ng-template",
"nrwl.angular-console",
"dbaeumer.vscode-eslint",
"firsttris.vscode-jest-runner",
"editorconfig.editorconfig"
]
}
"recommendations": [
"johnpapa.angular2",
"esbenp.prettier-vscode",
"angular.ng-template",
]
}

87
.vscode/settings.json vendored
View File

@@ -1,92 +1,15 @@
{
"editor.accessibilitySupport": "off",
"typescript.tsdk": "node_modules/typescript/lib",
"exportall.config.exclude": [".test.", ".spec.", ".stories."],
"editor.formatOnSave": true,
"typescriptHero.imports.insertSemicolons": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.validate": [
"json"
],
"[html]": {
"[javascript]": {
"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
"css.validate": false,
"less.validate": false,
"scss.validate": false
}

View File

@@ -1,5 +1,5 @@
#stage 1
FROM node:22 as base
FROM node:18 as base
ARG IS_PRODUCTION=false
ARG SEMVERSION=1.0.0
ARG BuildUniqueID
@@ -8,7 +8,7 @@ WORKDIR /app
COPY . .
RUN umask 0022
RUN npm version ${SEMVERSION}
RUN npm install --foreground-scripts
RUN npm install --always-auth=false
RUN if [ "${IS_PRODUCTION}" = "true" ] ; then npm run-script build-prod ; else npm run-script build ; fi
# stage final
@@ -24,6 +24,6 @@ ARG BuildUniqueID
LABEL build.uniqueid="${BuildUniqueID:-1}"
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -q -O /tmp/chrome.deb && apt update && apt install -y /tmp/chrome.deb
# ignore exitcode, sonst gibts keinen container
RUN npm run ci || true
RUN npm test || true
ENTRYPOINT [ "/bin/sleep", "60000" ]

135
angular.json Normal file
View File

@@ -0,0 +1,135 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "apps",
"projects": {
"isa-app": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
},
"@schematics/angular:application": {
"strict": true
}
},
"root": "apps/isa-app",
"sourceRoot": "apps/isa-app/src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"allowedCommonJsDependencies": [
"lodash",
"moment",
"jsrsasign",
"pdfjs-dist/build/pdf",
"pdfjs-dist/web/pdf_viewer",
"pdfjs-dist/es5/build/pdf",
"pdfjs-dist/es5/web/pdf_viewer",
"scandit-sdk"
],
"outputPath": "dist/isa-app",
"index": "apps/isa-app/src/index.html",
"main": "apps/isa-app/src/main.ts",
"polyfills": "apps/isa-app/src/polyfills.ts",
"tsConfig": "apps/isa-app/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"apps/isa-app/src/favicon.ico",
"apps/isa-app/src/assets",
"apps/isa-app/src/config",
"apps/isa-app/src/silent-refresh.html",
"apps/isa-app/src/manifest.webmanifest",
{
"glob": "**/*",
"input": "node_modules/scandit-sdk/build",
"output": "scandit"
}
],
"styles": [
"apps/isa-app/src/styles.scss"
],
"scripts": [],
"serviceWorker": true,
"ngswConfigPath": "apps/isa-app/ngsw-config.json"
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "25kb"
}
],
"fileReplacements": [
{
"replace": "apps/isa-app/src/environments/environment.ts",
"with": "apps/isa-app/src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "isa-app:build:production"
},
"development": {
"buildTarget": "isa-app:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "isa-app:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/isa-app/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
],
"inlineStyleLanguage": "scss",
"assets": [
"apps/isa-app/src/favicon.ico",
"apps/isa-app/src/assets",
"apps/isa-app/src/manifest.webmanifest"
],
"styles": [
"apps/isa-app/src/styles.scss"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

View File

@@ -1,24 +0,0 @@
import type { StorybookConfig } from '@storybook/angular';
const config: StorybookConfig = {
stories: ['../stories/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-docs',
],
previewHead: (head) => `
${head}
<link href="/assets/fonts/fonts.css" rel="stylesheet" />
`,
framework: {
name: '@storybook/angular',
options: {},
},
};
export default config;
// To customize your webpack configuration you can use the webpackFinal field.
// Check https://storybook.js.org/docs/react/builders/webpack#extending-storybooks-webpack-config
// and https://nx.dev/recipes/storybook/custom-builder-configs

View File

@@ -1,7 +0,0 @@
import type { Preview } from '@storybook/angular';
const preview: Preview = {
tags: ['autodocs'],
};
export default preview;

View File

@@ -1,21 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"emitDecoratorMetadata": true
},
"exclude": ["../**/*.spec.ts"],
"include": [
// "../src/**/*.stories.ts",
// "../src/**/*.stories.js",
// "../src/**/*.stories.jsx",
// "../src/**/*.stories.tsx",
// "../src/**/*.stories.mdx",
"../stories/**/*.stories.ts",
"../stories/**/*.stories.js",
"../stories/**/*.stories.jsx",
"../stories/**/*.stories.tsx",
"../stories/**/*.stories.mdx",
"*.js",
"*.ts"
]
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,55 +0,0 @@
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: 'app',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'app',
style: 'kebab-case',
},
],
},
},
{
files: ['**/*.ts'],
rules: {
'@typescript-eslint/no-unused-expressions': 'warn',
'prefer-const': 'warn',
'@angular-eslint/contextual-lifecycle': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@angular-eslint/no-empty-lifecycle-method': 'warn',
'@typescript-eslint/no-inferrable-types': 'warn',
'@angular-eslint/component-selector': 'warn',
'@angular-eslint/prefer-standalone': 'warn',
'@typescript-eslint/no-inferrable-types': 'warn',
'no-empty-function': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'@angular-eslint/directive-selector': 'warn',
},
},
{
files: ['**/*.html'],
// Override or add rules here
rules: {
'@angular-eslint/template/elements-content': 'warn',
'@angular-eslint/template/no-autofocus': 'warn',
},
},
];

View File

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

View File

@@ -29,4 +29,4 @@
}
}
]
}
}

View File

@@ -1,162 +0,0 @@
{
"name": "isa-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"prefix": "app",
"sourceRoot": "apps/isa-app/src",
"tags": [],
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:application",
"options": {
"allowedCommonJsDependencies": [
"lodash",
"moment",
"jsrsasign",
"pdfjs-dist/build/pdf",
"pdfjs-dist/web/pdf_viewer",
"pdfjs-dist/es5/build/pdf",
"pdfjs-dist/es5/web/pdf_viewer"
],
"outputPath": "dist/isa-app",
"index": "apps/isa-app/src/index.html",
"browser": "apps/isa-app/src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "apps/isa-app/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"apps/isa-app/src/favicon.ico",
"apps/isa-app/src/assets",
"apps/isa-app/src/config",
"apps/isa-app/src/silent-refresh.html",
"apps/isa-app/src/manifest.webmanifest",
{
"glob": "**/*",
"input": "node_modules/scandit-web-datacapture-barcode/build/engine",
"output": "scandit"
}
],
"styles": [
"@angular/cdk/overlay-prebuilt.css",
"apps/isa-app/src/tailwind.scss",
"apps/isa-app/src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "25kb"
}
],
"fileReplacements": [
{
"replace": "apps/isa-app/src/environments/environment.ts",
"with": "apps/isa-app/src/environments/environment.prod.ts"
}
],
"outputHashing": "all",
"serviceWorker": "apps/isa-app/ngsw-config.json"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production",
"outputs": ["{options.outputPath}"]
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "isa-app:build:production"
},
"development": {
"buildTarget": "isa-app:build:development"
}
},
"defaultConfiguration": "development",
"continuous": true
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "isa-app:build"
}
},
"lint": {
"executor": "@nx/eslint:lint"
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/isa-app/jest.config.ts"
}
},
"serve-static": {
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "isa-app:build",
"staticFilePath": "dist/apps/isa-app/browser",
"spa": true
}
},
"storybook": {
"executor": "@storybook/angular:start-storybook",
"options": {
"port": 4400,
"configDir": "apps/isa-app/.storybook",
"browserTarget": "isa-app:build",
"compodoc": false,
"open": false,
"assets": [
{
"glob": "**/*",
"input": "apps/isa-app/src/assets",
"output": "/assets"
}
],
"styles": [
"@angular/cdk/overlay-prebuilt.css",
"apps/isa-app/src/tailwind.scss",
"apps/isa-app/src/styles.scss"
]
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"build-storybook": {
"executor": "@storybook/angular:build-storybook",
"outputs": ["{options.outputDir}"],
"options": {
"outputDir": "dist/storybook/isa-app",
"configDir": "apps/isa-app/.storybook",
"browserTarget": "isa-app:build",
"compodoc": false,
"styles": [
"@angular/cdk/overlay-prebuilt.css",
"apps/isa-app/src/tailwind.scss",
"apps/isa-app/src/styles.scss"
]
},
"configurations": {
"ci": {
"quiet": true
}
}
}
}
}

View File

@@ -1,31 +1,23 @@
import { inject, Injectable } from '@angular/core';
import {
PromptModalData,
UiModalService,
UiPromptModalComponent,
} from '@ui/modal';
import { Injectable, isDevMode } from '@angular/core';
import { EnvironmentService } from '@core/environment';
import { PromptModalData, UiModalService, UiPromptModalComponent } from '@ui/modal';
import { Observable } from 'rxjs';
import { ScanAdapter } from './scan-adapter';
import { Config } from '@core/config';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
@Injectable()
export class DevScanAdapter implements ScanAdapter {
readonly name = 'Dev';
private _modal = inject(UiModalService);
private _config = inject(Config);
constructor(
private _modal: UiModalService,
private _environmentService: EnvironmentService,
) {}
async init(): Promise<boolean> {
const enabled = localStorage.getItem('dev_scan_adapter_enabled') === 'true';
if (enabled) {
return new Promise((resolve, reject) => {
resolve(coerceBooleanProperty(this._config.get('dev-scanner')));
});
}
return false;
return Promise.resolve(false);
// return new Promise((resolve, reject) => {
// resolve(isDevMode());
// });
}
scan(): Observable<string> {
@@ -34,8 +26,7 @@ export class DevScanAdapter implements ScanAdapter {
content: UiPromptModalComponent,
title: 'Scannen',
data: {
message:
'Diese Eingabemaske dient nur zu Entwicklungs und Testzwecken.',
message: 'Diese Eingabemaske dient nur zu Entwicklungs und Testzwecken.',
placeholder: 'Scan Code',
confirmText: 'weiter',
cancelText: 'abbrechen',

View File

@@ -4,10 +4,6 @@ import { Observable } from 'rxjs';
import { filter, map, take } from 'rxjs/operators';
import { ScanAdapter } from './scan-adapter';
/**
* @deprecated This service will be removed in future versions.
* Please use the new ScanService instead.
*/
@Injectable()
export class NativeScanAdapter implements ScanAdapter {
readonly name = 'Native';

View File

@@ -37,7 +37,7 @@ export class ScanAdapterService {
for (const name of adapterOrder) {
adapter = this.getAdapter(name);
console.log('adapter', adapter);
if (adapter) {
break;
}

View File

@@ -3,15 +3,12 @@
}
.scanner-container {
/* width: 100vw;
width: 100vw;
height: 100vh;
max-width: 100vh;
max-height: 100vh; */
}
.close-scanner {
@apply absolute bottom-12 left-[50%] -translate-x-[50%] block px-6 py-4 bg-white text-brand border-2 border-solid border-brand rounded-full text-lg font-bold mx-auto mt-4;
@apply whitespace-nowrap;
}
@screen desktop {

View File

@@ -1,27 +1,15 @@
import {
Component,
ChangeDetectionStrategy,
ElementRef,
ViewChild,
NgZone,
AfterViewInit,
OnDestroy, OnInit,
} from '@angular/core';
import { BarcodeCapture, BarcodeCaptureSettings, Symbology } from 'scandit-web-datacapture-barcode';
import { Camera, DataCaptureContext, DataCaptureView, FrameSourceState } from 'scandit-web-datacapture-core';
import { Component, ChangeDetectionStrategy, ElementRef, ViewChild, NgZone, AfterViewInit, OnDestroy } from '@angular/core';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { Barcode, BarcodePicker, ScanResult, ScanSettings } from 'scandit-sdk';
@Component({
selector: 'app-scandit-overlay',
templateUrl: 'scandit-overlay.component.html',
styleUrls: ['scandit-overlay.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class ScanditOverlayComponent implements AfterViewInit, OnDestroy, OnInit {
private dataCaptureContext: DataCaptureContext;
private dataCaptureView: DataCaptureView;
private barcodeCapture: BarcodeCapture;
private camera: Camera;
export class ScanditOverlayComponent implements AfterViewInit, OnDestroy {
private _barcodePicker: BarcodePicker;
private _onScan?: (code: string) => void;
@@ -29,58 +17,57 @@ export class ScanditOverlayComponent implements AfterViewInit, OnDestroy, OnInit
@ViewChild('scanContainer', { read: ElementRef, static: true }) scanContainer: ElementRef;
constructor(private _zone: NgZone) {}
constructor(
private _zone: NgZone,
private _modal: UiModalService,
) {}
ngOnInit(): void {
this.dataCaptureView = new DataCaptureView();
this.dataCaptureView.connectToElement(this.scanContainer.nativeElement);
this.dataCaptureView.showProgressBar();
ngAfterViewInit(): void {
this.createBarcodePicker()
.then(() => {
this._barcodePicker.on('scan', (scanResult) => {
this._zone.run(() => this.handleScanrResult(scanResult));
});
})
.catch((err: Error) => {
this._modal
.open({
content: UiMessageModalComponent,
title: 'Zugriff auf Kamera verweigert',
data: { message: 'Falls Sie den Zugriff erlauben möchten, können Sie das über die Webseiteinstellung Ihres Browsers.' },
})
.afterClosed$.subscribe(() => {
this._onClose?.();
});
});
}
async ngAfterViewInit() {
this.dataCaptureContext = await DataCaptureContext.create();
this.dataCaptureView.setContext(this.dataCaptureContext);
this.barcodeCapture = await BarcodeCapture.forContext(this.dataCaptureContext, this.getScanSettings());
this.barcodeCapture.addListener({
didScan: (_, session, __) => {
this._zone.run(() => {
const result = session.newlyRecognizedBarcode;
const code = result?.data ?? '';
this._onScan?.(code);
});
},
async createBarcodePicker() {
this._barcodePicker = await BarcodePicker.create(this.scanContainer.nativeElement, {
playSoundOnScan: true,
vibrateOnScan: true,
});
this.camera = Camera.default;
this.dataCaptureContext.setFrameSource(this.camera);
await this.camera.switchToDesiredState(FrameSourceState.On);
this.dataCaptureView.hideProgressBar();
this._barcodePicker.applyScanSettings(this.getScanSettings());
}
getScanSettings(): BarcodeCaptureSettings {
const settings = new BarcodeCaptureSettings();
getScanSettings(): ScanSettings {
return new ScanSettings({
blurryRecognition: false,
settings.enableSymbologies([
Symbology.EAN8,
Symbology.EAN13UPCA,
Symbology.UPCE,
Symbology.Code128,
Symbology.Code39,
Symbology.Code93,
Symbology.InterleavedTwoOfFive,
Symbology.QR,
]);
return settings;
enabledSymbologies: [
Barcode.Symbology.EAN8,
Barcode.Symbology.EAN13,
Barcode.Symbology.UPCA,
Barcode.Symbology.UPCE,
Barcode.Symbology.CODE128,
Barcode.Symbology.CODE39,
Barcode.Symbology.CODE93,
Barcode.Symbology.INTERLEAVED_2_OF_5,
Barcode.Symbology.QR,
],
codeDuplicateFilter: 1000,
});
}
onScan(fn: (code: string) => void) {
@@ -91,14 +78,26 @@ export class ScanditOverlayComponent implements AfterViewInit, OnDestroy, OnInit
this._onClose = fn;
}
handleScanrResult(scanRestul: ScanResult) {
let result: string | undefined;
if (scanRestul.barcodes.length) {
result = scanRestul.barcodes[0].data;
} else if (scanRestul.texts.length) {
result = scanRestul.texts[0].value;
}
if (result) {
this._onScan?.(result);
}
}
close() {
this._onClose?.();
}
ngOnDestroy(): void {
this._zone.runOutsideAngular(() => {
this.barcodeCapture?.setEnabled(false);
this.camera?.switchToDesiredState(FrameSourceState.Off);
this._barcodePicker?.destroy(true);
});
}
}

View File

@@ -3,9 +3,7 @@ import { Observable, Subscriber } from 'rxjs';
import { ScanAdapter } from '../scan-adapter';
import { Overlay } from '@angular/cdk/overlay';
import { configure } from 'scandit-web-datacapture-core';
import { barcodeCaptureLoader } from 'scandit-web-datacapture-barcode';
import { configure } from 'scandit-sdk';
// import { ScanditModalComponent } from './scandit-modal';
import { Config } from '@core/config';
import { ComponentPortal } from '@angular/cdk/portal';
@@ -27,21 +25,12 @@ export class ScanditScanAdapter implements ScanAdapter {
) {}
async init(): Promise<boolean> {
const enabled =
localStorage.getItem('scandit_scan_adapter_enabled') === 'true';
if (this._environmentService.isTablet()) {
await configure(this._config.get('licence.scandit'), {
engineLocation: '/scandit/',
});
if (enabled || this._environmentService.isTablet()) {
try {
await configure({
licenseKey: this._config.get('licence.scandit'),
libraryLocation: new URL('scandit', document.baseURI).toString(),
moduleLoaders: [barcodeCaptureLoader()],
});
return true;
} catch (error) {
console.error('ScanditScanAdapter.init', error);
}
return true;
}
return false;
@@ -91,11 +80,7 @@ export class ScanditScanAdapter implements ScanAdapter {
createOverlay() {
const overlay = this._overlay.create({
positionStrategy: this._overlay
.position()
.global()
.centerHorizontally()
.centerVertically(),
positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),
hasBackdrop: true,
});

View File

@@ -1,231 +1,168 @@
import { isDevMode, NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {
CanActivateCartGuard,
CanActivateCartWithProcessIdGuard,
CanActivateCustomerGuard,
CanActivateCustomerOrdersGuard,
CanActivateCustomerOrdersWithProcessIdGuard,
CanActivateCustomerWithProcessIdGuard,
CanActivateGoodsInGuard,
CanActivateProductGuard,
CanActivateProductWithProcessIdGuard,
CanActivateRemissionGuard,
CanActivateTaskCalendarGuard,
IsAuthenticatedGuard,
} from './guards';
import { CanActivateAssortmentGuard } from './guards/can-activate-assortment.guard';
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
import { MainComponent } from './main.component';
import { PreviewComponent } from './preview';
import {
BranchSectionResolver,
CustomerSectionResolver,
ProcessIdResolver,
} from './resolvers';
import { TokenLoginComponent, TokenLoginModule } from './token-login';
import { ProcessIdGuard } from './guards/process-id.guard';
import {
ActivateProcessIdGuard,
ActivateProcessIdWithConfigKeyGuard,
} from './guards/activate-process-id.guard';
import { MatomoRouteData } from 'ngx-matomo-client';
import { tabResolverFn } from '@isa/core/tabs';
import { provideScrollPositionRestoration } from '@isa/utils/scroll-position';
const routes: Routes = [
{ path: '', redirectTo: 'kunde/dashboard', pathMatch: 'full' },
{
path: 'login',
children: [
{ path: ':token', component: TokenLoginComponent },
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
],
},
{
path: '',
canActivate: [IsAuthenticatedGuard],
children: [
{
path: 'kunde',
component: MainComponent,
children: [
{
path: 'dashboard',
loadChildren: () =>
import('@page/dashboard').then((m) => m.DashboardModule),
data: {
matomo: {
title: 'Dashboard',
} as MatomoRouteData,
},
},
{
path: 'product',
loadChildren: () =>
import('@page/catalog').then((m) => m.PageCatalogModule),
canActivate: [CanActivateProductGuard],
},
{
path: ':processId/product',
loadChildren: () =>
import('@page/catalog').then((m) => m.PageCatalogModule),
canActivate: [CanActivateProductWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'order',
loadChildren: () =>
import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateCustomerOrdersGuard],
},
{
path: ':processId/order',
loadChildren: () =>
import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateCustomerOrdersWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'customer',
loadChildren: () =>
import('@page/customer').then((m) => m.CustomerModule),
canActivate: [CanActivateCustomerGuard],
},
{
path: ':processId/customer',
loadChildren: () =>
import('@page/customer').then((m) => m.CustomerModule),
canActivate: [CanActivateCustomerWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'cart',
loadChildren: () =>
import('@page/checkout').then((m) => m.PageCheckoutModule),
canActivate: [CanActivateCartGuard],
},
{
path: ':processId/cart',
loadChildren: () =>
import('@page/checkout').then((m) => m.PageCheckoutModule),
canActivate: [CanActivateCartWithProcessIdGuard],
},
{
path: 'pickup-shelf',
canActivate: [ProcessIdGuard],
// NOTE: This is a workaround for the canActivate guard not being called
loadChildren: () =>
import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
},
{
path: ':processId/pickup-shelf',
canActivate: [ActivateProcessIdGuard],
loadChildren: () =>
import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
},
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
],
resolve: { section: CustomerSectionResolver },
},
{
path: 'filiale',
component: MainComponent,
children: [
{
path: 'task-calendar',
loadChildren: () =>
import('@page/task-calendar').then(
(m) => m.PageTaskCalendarModule,
),
canActivate: [CanActivateTaskCalendarGuard],
},
{
path: 'pickup-shelf',
canActivate: [ActivateProcessIdWithConfigKeyGuard('pickupShelf')],
// NOTE: This is a workaround for the canActivate guard not being called
loadChildren: () =>
import('@page/pickup-shelf').then((m) => m.PickupShelfInModule),
},
{
path: 'goods/in',
loadChildren: () =>
import('@page/goods-in').then((m) => m.GoodsInModule),
canActivate: [CanActivateGoodsInGuard],
},
{
path: 'remission',
loadChildren: () =>
import('@page/remission').then((m) => m.PageRemissionModule),
canActivate: [CanActivateRemissionGuard],
},
{
path: 'package-inspection',
loadChildren: () =>
import('@page/package-inspection').then(
(m) => m.PackageInspectionModule,
),
canActivate: [CanActivatePackageInspectionGuard],
},
{
path: 'assortment',
loadChildren: () =>
import('@page/assortment').then((m) => m.AssortmentModule),
canActivate: [CanActivateAssortmentGuard],
},
{ path: '**', redirectTo: 'task-calendar', pathMatch: 'full' },
],
resolve: { section: BranchSectionResolver },
},
],
},
{
path: ':tabId',
component: MainComponent,
resolve: { process: tabResolverFn, tab: tabResolverFn },
canActivate: [IsAuthenticatedGuard],
children: [
{
path: 'return',
loadChildren: () =>
import('@isa/oms/feature/return-search').then((m) => m.routes),
},
{
path: 'remission',
children: [
{
path: 'return-receipt',
loadChildren: () =>
import(
'@isa/remission/feature/remission-return-receipt-list'
).then((m) => m.routes),
},
{
path: '',
loadChildren: () =>
import('@isa/remission/feature/remission-list').then(
(m) => m.routes,
),
},
],
},
],
},
];
if (isDevMode()) {
routes.unshift({
path: 'preview',
component: PreviewComponent,
});
}
@NgModule({
imports: [
RouterModule.forRoot(routes, { bindToComponentInputs: true }),
TokenLoginModule,
],
exports: [RouterModule],
providers: [provideScrollPositionRestoration()],
})
export class AppRoutingModule {}
import { isDevMode, NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {
CanActivateCartGuard,
CanActivateCartWithProcessIdGuard,
CanActivateCustomerGuard,
CanActivateCustomerOrdersGuard,
CanActivateCustomerOrdersWithProcessIdGuard,
CanActivateCustomerWithProcessIdGuard,
CanActivateGoodsInGuard,
CanActivateProductGuard,
CanActivateProductWithProcessIdGuard,
CanActivateRemissionGuard,
CanActivateTaskCalendarGuard,
IsAuthenticatedGuard,
} from './guards';
import { CanActivateAssortmentGuard } from './guards/can-activate-assortment.guard';
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
import { MainComponent } from './main.component';
import { PreviewComponent } from './preview';
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
import { TokenLoginComponent, TokenLoginModule } from './token-login';
import { ProcessIdGuard } from './guards/process-id.guard';
import { ActivateProcessIdGuard, ActivateProcessIdWithConfigKeyGuard } from './guards/activate-process-id.guard';
const routes: Routes = [
{
path: 'login',
children: [
{ path: ':token', component: TokenLoginComponent },
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
],
},
{
path: '',
canActivate: [IsAuthenticatedGuard],
children: [
{
path: 'kunde',
component: MainComponent,
children: [
{
path: 'dashboard',
loadChildren: () => import('@page/dashboard').then((m) => m.DashboardModule),
},
{
path: 'product',
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
canActivate: [CanActivateProductGuard],
},
{
path: ':processId/product',
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
canActivate: [CanActivateProductWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateCustomerOrdersGuard],
},
{
path: ':processId/order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateCustomerOrdersWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'customer',
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
canActivate: [CanActivateCustomerGuard],
},
{
path: ':processId/customer',
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
canActivate: [CanActivateCustomerWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'cart',
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
canActivate: [CanActivateCartGuard],
},
{
path: ':processId/cart',
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
canActivate: [CanActivateCartWithProcessIdGuard],
},
{
path: 'pickup-shelf',
canActivate: [ProcessIdGuard],
// NOTE: This is a workaround for the canActivate guard not being called
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
},
{
path: ':processId/pickup-shelf',
canActivate: [ActivateProcessIdGuard],
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
},
{
path: 'retoure',
loadChildren: () => import('@page/retoure').then((m) => m.RetourePageModule),
},
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
],
resolve: { section: CustomerSectionResolver },
},
{
path: 'filiale',
component: MainComponent,
children: [
{
path: 'task-calendar',
loadChildren: () => import('@page/task-calendar').then((m) => m.PageTaskCalendarModule),
canActivate: [CanActivateTaskCalendarGuard],
},
{
path: 'pickup-shelf',
canActivate: [ActivateProcessIdWithConfigKeyGuard('pickupShelf')],
// NOTE: This is a workaround for the canActivate guard not being called
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfInModule),
},
{
path: 'goods/in',
loadChildren: () => import('@page/goods-in').then((m) => m.GoodsInModule),
canActivate: [CanActivateGoodsInGuard],
},
{
path: 'remission',
loadChildren: () => import('@page/remission').then((m) => m.PageRemissionModule),
canActivate: [CanActivateRemissionGuard],
},
{
path: 'package-inspection',
loadChildren: () => import('@page/package-inspection').then((m) => m.PackageInspectionModule),
canActivate: [CanActivatePackageInspectionGuard],
},
{
path: 'assortment',
loadChildren: () => import('@page/assortment').then((m) => m.AssortmentModule),
canActivate: [CanActivateAssortmentGuard],
},
{ path: '**', redirectTo: 'task-calendar', pathMatch: 'full' },
],
resolve: { section: BranchSectionResolver },
},
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
],
},
];
if (isDevMode()) {
routes.unshift({
path: 'preview',
component: PreviewComponent,
});
}
@NgModule({
imports: [
RouterModule.forRoot(routes, {
paramsInheritanceStrategy: 'always',
}),
TokenLoginModule,
],
exports: [RouterModule],
})
export class AppRoutingModule {}

View File

@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { ActionReducer, MetaReducer, StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { storeFreeze } from 'ngrx-store-freeze';
import packageInfo from 'packageJson';
import { environment } from '../environments/environment';
import { RootStateService } from './store/root-state.service';
@@ -21,9 +22,7 @@ export function storeInLocalStorage(reducer: ActionReducer<any>): ActionReducer<
};
}
export const metaReducers: MetaReducer<RootState>[] = !environment.production
? [storeInLocalStorage]
: [storeInLocalStorage];
export const metaReducers: MetaReducer<RootState>[] = !environment.production ? [storeFreeze, storeInLocalStorage] : [storeInLocalStorage];
@NgModule({
imports: [

View File

@@ -1,16 +1,15 @@
import { HttpInterceptorFn, provideHttpClient, withInterceptors } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { Config } from '@core/config';
import { AvConfiguration } from '@generated/swagger/availability-api';
import { CatConfiguration } from '@generated/swagger/cat-search-api';
import { CheckoutConfiguration } from '@generated/swagger/checkout-api';
import { CrmConfiguration } from '@generated/swagger/crm-api';
import { EisConfiguration } from '@generated/swagger/eis-api';
import { IsaConfiguration } from '@generated/swagger/isa-api';
import { OmsConfiguration } from '@generated/swagger/oms-api';
import { PrintConfiguration } from '@generated/swagger/print-api';
import { RemiConfiguration } from '@generated/swagger/inventory-api';
import { WwsConfiguration } from '@generated/swagger/wws-api';
import { AvConfiguration } from '@swagger/availability';
import { CatConfiguration } from '@swagger/cat';
import { CheckoutConfiguration } from '@swagger/checkout';
import { CrmConfiguration } from '@swagger/crm';
import { EisConfiguration } from '@swagger/eis';
import { IsaConfiguration } from '@swagger/isa';
import { OmsConfiguration } from '@swagger/oms';
import { PrintConfiguration } from '@swagger/print';
import { RemiConfiguration } from '@swagger/remi';
import { WwsConfiguration } from '@swagger/wws';
export function createConfigurationFactory(name: string) {
return function (config: Config): { rootUrl: string } {
@@ -18,13 +17,8 @@ export function createConfigurationFactory(name: string) {
};
}
const serviceWorkerInterceptor: HttpInterceptorFn = (req, next) => {
return next(req.clone({ setHeaders: { 'ngsw-bypass': 'true' } }));
};
@NgModule({
providers: [
provideHttpClient(withInterceptors([serviceWorkerInterceptor])),
{ provide: AvConfiguration, useFactory: createConfigurationFactory('av'), deps: [Config] },
{ provide: CatConfiguration, useFactory: createConfigurationFactory('cat'), deps: [Config] },
{ provide: CheckoutConfiguration, useFactory: createConfigurationFactory('checkout'), deps: [Config] },

View File

@@ -8,7 +8,7 @@ import { Renderer2 } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SwUpdate } from '@angular/service-worker';
import { NotificationsHub } from '@hub/notifications';
import { UserStateService } from '@generated/swagger/isa-api';
import { UserStateService } from '@swagger/isa';
import { UiModalService } from '@ui/modal';
import { AuthService } from '@core/auth';

View File

@@ -1,17 +1,5 @@
import {
Component,
effect,
HostListener,
inject,
Inject,
Injector,
OnInit,
Renderer2,
signal,
untracked,
DOCUMENT
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Component, effect, HostListener, Inject, OnInit, Renderer2, signal, untracked } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { SwUpdate } from '@angular/service-worker';
import { ApplicationService } from '@core/application';
@@ -19,10 +7,10 @@ import { Config } from '@core/config';
import { NotificationsHub } from '@hub/notifications';
import packageInfo from 'packageJson';
import { asapScheduler, interval, Subscription } from 'rxjs';
import { UserStateService } from '@generated/swagger/isa-api';
import { UserStateService } from '@swagger/isa';
import { IsaLogProvider } from './providers';
import { EnvironmentService } from '@core/environment';
import { AuthService, LoginStrategy } from '@core/auth';
import { AuthService } from '@core/auth';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { injectOnline$ } from './services/network-status.service';
import { toSignal } from '@angular/core/rxjs-interop';
@@ -45,11 +33,8 @@ import { animate, style, transition, trigger } from '@angular/animations';
]),
]),
],
standalone: false,
})
export class AppComponent implements OnInit {
readonly injector = inject(Injector);
$online = toSignal(injectOnline$());
$offlineBannerVisible = signal(false);
@@ -133,10 +118,7 @@ export class AppComponent implements OnInit {
}
logVersion() {
console.log(
`%c${this._config.get('title')}\r\nVersion: ${packageInfo.version}`,
'font-weight: bold; font-size: 20px;',
);
console.log(`%c${this._config.get('title')}\r\nVersion: ${packageInfo.version}`, 'font-weight: bold; font-size: 20px;');
}
determinePlatform() {
@@ -198,9 +180,15 @@ export class AppComponent implements OnInit {
if (this._document.hidden && this._authService.isAuthenticated()) {
this._authService.refresh();
} else if (!this._authService.isAuthenticated()) {
const strategy = this.injector.get(LoginStrategy);
return strategy.login('Sie sind nicht mehr angemeldet');
return this._modal
.open({
content: UiMessageModalComponent,
title: 'Sie sind nicht mehr angemeldet',
data: { message: 'Sie werden neu angemeldet' },
})
.afterClosed$.subscribe(() => {
this._authService.login();
});
}
}
}

View File

@@ -1,23 +1,11 @@
import {
HTTP_INTERCEPTORS,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import {
DEFAULT_CURRENCY_CODE,
ErrorHandler,
Injector,
LOCALE_ID,
NgModule,
inject,
provideAppInitializer,
} from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, ErrorHandler, Injector, LOCALE_ID, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { PlatformModule } from '@angular/cdk/platform';
import { Config } from '@core/config';
import { AuthModule, AuthService, LoginStrategy } from '@core/auth';
import { Config, ConfigModule, JsonConfigLoader } from '@core/config';
import { AuthModule, AuthService } from '@core/auth';
import { CoreCommandModule } from '@core/command';
import { AppRoutingModule } from './app-routing.module';
@@ -29,10 +17,7 @@ import { environment } from '../environments/environment';
import { AppSwaggerModule } from './app-swagger.module';
import { AppDomainModule } from './app-domain.module';
import { UiModalModule } from '@ui/modal';
import {
NotificationsHubModule,
NOTIFICATIONS_HUB_OPTIONS,
} from '@hub/notifications';
import { NotificationsHubModule, NOTIFICATIONS_HUB_OPTIONS } from '@hub/notifications';
import { SignalRHubOptions } from '@core/signalr';
import { CoreBreadcrumbModule } from '@core/breadcrumb';
import { UiCommonModule } from '@ui/common';
@@ -44,11 +29,7 @@ import { HttpErrorInterceptor } from './interceptors';
import { CoreLoggerModule, LOG_PROVIDER } from '@core/logger';
import { IsaLogProvider } from './providers';
import { IsaErrorHandler } from './providers/isa.error-handler';
import {
ScanAdapterModule,
ScanAdapterService,
ScanditScanAdapterModule,
} from '@adapter/scan';
import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from '@adapter/scan';
import { RootStateService } from './store/root-state.service';
import * as Commands from './commands';
import { PreviewComponent } from './preview';
@@ -57,34 +38,28 @@ import { ShellModule } from '@shared/shell';
import { MainComponent } from './main.component';
import { IconModule } from '@shared/components/icon';
import { NgIconsModule } from '@ng-icons/core';
import {
matClose,
matWifi,
matWifiOff,
} from '@ng-icons/material-icons/baseline';
import { matClose, matWifi, matWifiOff } from '@ng-icons/material-icons/baseline';
import { NetworkStatusService } from './services/network-status.service';
import { firstValueFrom } from 'rxjs';
import { provideMatomo } from 'ngx-matomo-client';
import { withRouter, withRouteData } from 'ngx-matomo-client';
import {
provideLogging,
withLogLevel,
LogLevel,
withSink,
ConsoleLogSink,
} from '@isa/core/logging';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
export function _appInitializerFactory(config: Config, injector: Injector) {
export function _appInitializerFactory(
config: Config,
auth: AuthService,
injector: Injector,
scanAdapter: ScanAdapterService,
nativeContainer: NativeContainerService,
networkStatus: NetworkStatusService,
) {
return async () => {
const statusElement = document.querySelector('#init-status');
const laoderElement = document.querySelector('#init-loader');
try {
let online = false;
const networkStatus = injector.get(NetworkStatusService);
while (!online) {
online = await firstValueFrom(networkStatus.online$);
@@ -96,43 +71,28 @@ export function _appInitializerFactory(config: Config, injector: Injector) {
}
statusElement.innerHTML = 'Konfigurationen werden geladen...';
statusElement.innerHTML = 'Scanner wird initialisiert...';
const scanAdapter = injector.get(ScanAdapterService);
await scanAdapter.init();
await config.init();
statusElement.innerHTML = 'Authentifizierung wird geprüft...';
await auth.init();
const auth = injector.get(AuthService);
try {
await auth.init();
} catch (error) {
statusElement.innerHTML = 'Authentifizierung wird durchgeführt...';
const strategy = injector.get(LoginStrategy);
await strategy.login();
if (auth.isAuthenticated()) {
statusElement.innerHTML = 'App wird initialisiert...';
const state = injector.get(RootStateService);
await state.init();
}
statusElement.innerHTML = 'App wird initialisiert...';
const state = injector.get(RootStateService);
await state.init();
statusElement.innerHTML = 'Native Container wird initialisiert...';
const nativeContainer = injector.get(NativeContainerService);
await nativeContainer.init();
statusElement.innerHTML = 'Scanner wird initialisiert...';
await scanAdapter.init();
} catch (error) {
laoderElement.remove();
statusElement.classList.add('text-xl');
statusElement.innerHTML +=
'⚡<br><br><b>Fehler bei der Initialisierung</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br><br>';
statusElement.innerHTML = '<b>Fehler bei der Initialisierung</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br><br>';
const reload = document.createElement('button');
reload.classList.add(
'bg-brand',
'text-white',
'p-2',
'rounded',
'cursor-pointer',
);
reload.classList.add('bg-brand', 'text-white', 'p-2', 'rounded', 'cursor-pointer');
reload.innerHTML = 'App neu laden';
reload.onclick = () => window.location.reload();
statusElement.appendChild(reload);
@@ -155,10 +115,7 @@ export function _appInitializerFactory(config: Config, injector: Injector) {
};
}
export function _notificationsHubOptionsFactory(
config: Config,
auth: AuthService,
): SignalRHubOptions {
export function _notificationsHubOptionsFactory(config: Config, auth: AuthService): SignalRHubOptions {
const options = { ...config.get('hubs').notifications };
options.httpOptions.accessTokenFactory = () => auth.getToken();
return options;
@@ -166,15 +123,19 @@ export function _notificationsHubOptionsFactory(
@NgModule({
declarations: [AppComponent, MainComponent],
bootstrap: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
ShellModule.forRoot(),
AppRoutingModule,
AppSwaggerModule,
AppDomainModule,
CoreBreadcrumbModule.forRoot(),
ConfigModule.forRoot({
useConfigLoader: JsonConfigLoader,
jsonConfigLoaderUrl: '/config/config.json',
}),
CoreCommandModule.forRoot(Object.values(Commands)),
CoreLoggerModule.forRoot(),
AppStoreModule,
@@ -195,13 +156,12 @@ export function _notificationsHubOptionsFactory(
NgIconsModule.withIcons({ matWifiOff, matClose, matWifi }),
],
providers: [
provideAppInitializer(() => {
const initializerFn = _appInitializerFactory(
inject(Config),
inject(Injector),
);
return initializerFn();
}),
{
provide: APP_INITIALIZER,
useFactory: _appInitializerFactory,
multi: true,
deps: [Config, AuthService, Injector, ScanAdapterService, NativeContainerService, NetworkStatusService],
},
{
provide: NOTIFICATIONS_HUB_OPTIONS,
useFactory: _notificationsHubOptionsFactory,
@@ -222,17 +182,7 @@ export function _notificationsHubOptionsFactory(
useClass: IsaErrorHandler,
},
{ provide: LOCALE_ID, useValue: 'de-DE' },
provideHttpClient(withInterceptorsFromDi()),
provideMatomo(
{ trackerUrl: 'https://matomo.paragon-data.net', siteId: '1' },
withRouter(),
withRouteData(),
),
provideLogging(withLogLevel(LogLevel.Debug), withSink(ConsoleLogSink)),
{
provide: DEFAULT_CURRENCY_CODE,
useValue: 'EUR',
},
],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@@ -3,14 +3,11 @@ import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ActionHandler } from '@core/command';
import { Result } from '@domain/defs';
import { CustomerInfoDTO } from '@generated/swagger/crm-api';
import { CustomerInfoDTO } from '@swagger/crm';
@Injectable()
export class CreateCustomerCommand extends ActionHandler<Result<CustomerInfoDTO[]>> {
constructor(
private _router: Router,
private _application: ApplicationService,
) {
constructor(private _router: Router, private _application: ApplicationService) {
super('CREATE_CUSTOMER');
}

View File

@@ -4,7 +4,7 @@ import { ApplicationService } from '@core/application';
import { ActionHandler } from '@core/command';
import { Result } from '@domain/defs';
import { encodeFormData, mapCustomerInfoDtoToCustomerCreateFormData } from '@page/customer';
import { CustomerInfoDTO } from '@generated/swagger/crm-api';
import { CustomerInfoDTO } from '@swagger/crm';
@Injectable()
export class CreateKubiCustomerCommand extends ActionHandler<Result<CustomerInfoDTO[]>> {
@@ -40,12 +40,9 @@ export class CreateKubiCustomerCommand extends ActionHandler<Result<CustomerInfo
customerType = 'store';
}
await this._router.navigate(
['/kunde', this._application.activatedProcessId, 'customer', 'create', `${customerType}-p4m`],
{
queryParams: { formData },
},
);
await this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'create', `${customerType}-p4m`], {
queryParams: { formData },
});
return data;
}
}

View File

@@ -3,15 +3,12 @@ import { ActionHandler } from '@core/command';
import { Result } from '@domain/defs';
import { DomainPrinterService } from '@domain/printer';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { CustomerInfoDTO } from '@generated/swagger/crm-api';
import { CustomerInfoDTO } from '@swagger/crm';
import { UiModalService } from '@ui/modal';
@Injectable()
export class PrintKubiCustomerCommand extends ActionHandler<Result<CustomerInfoDTO[]>> {
constructor(
private _uiModal: UiModalService,
private _printerService: DomainPrinterService,
) {
constructor(private _uiModal: UiModalService, private _printerService: DomainPrinterService) {
super('PRINT_KUBI_AGB');
}

View File

@@ -4,10 +4,7 @@ import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { take } from 'rxjs/operators';
export const ActivateProcessIdGuard: CanActivateFn = async (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
) => {
export const ActivateProcessIdGuard: CanActivateFn = async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
const application = inject(ApplicationService);
const processIdStr = route.params.processId;
@@ -30,18 +27,20 @@ export const ActivateProcessIdGuard: CanActivateFn = async (
return true;
};
export const ActivateProcessIdWithConfigKeyGuard: (key: string) => CanActivateFn =
(key) => async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
const application = inject(ApplicationService);
const config = inject(Config);
export const ActivateProcessIdWithConfigKeyGuard: (key: string) => CanActivateFn = (key) => async (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
) => {
const application = inject(ApplicationService);
const config = inject(Config);
const processId = config.get(`process.ids.${key}`);
const processId = config.get(`process.ids.${key}`);
if (isNaN(processId)) {
return false;
}
if (isNaN(processId)) {
return false;
}
application.activateProcess(processId);
application.activateProcess(processId);
return true;
};
return true;
};

View File

@@ -6,16 +6,10 @@ import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateAssortmentGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _config: Config,
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _config: Config) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
.getProcessById$(this._config.get('process.ids.assortment'))
.pipe(first())
.toPromise();
const process = await this._applicationService.getProcessById$(this._config.get('process.ids.assortment')).pipe(first()).toPromise();
if (!process) {
await this._applicationService.createProcess({
id: this._config.get('process.ids.assortment'),

View File

@@ -8,7 +8,10 @@ export class CanActivateCartWithProcessIdGuard {
constructor(private readonly _applicationService: ApplicationService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService.getProcessById$(+route.params.processId).pipe(first()).toPromise();
const process = await this._applicationService
.getProcessById$(+route.params.processId)
.pipe(first())
.toPromise();
// if (!(process?.type === 'cart')) {
// // TODO:

View File

@@ -14,10 +14,7 @@ export class CanActivateCartGuard {
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'cart')
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
if (!lastActivatedProcessId) {
lastActivatedProcessId = Date.now();

View File

@@ -6,13 +6,13 @@ import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateCustomerOrdersWithProcessIdGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _breadcrumbService: BreadcrumbService,
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService.getProcessById$(+route.params.processId).pipe(first()).toPromise();
const process = await this._applicationService
.getProcessById$(+route.params.processId)
.pipe(first())
.toPromise();
if (!process) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
@@ -31,7 +31,10 @@ export class CanActivateCustomerOrdersWithProcessIdGuard {
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService.getBreadcrumbByKey$(+route.params.processId).pipe(first()).toPromise();
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit den Kundenbestellungen zu tun haben
if (crumbs.length > 1) {

View File

@@ -17,17 +17,11 @@ export class CanActivateCustomerOrdersGuard {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'cart')
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
const lastActivatedCartCheckoutProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout')
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();

View File

@@ -6,13 +6,13 @@ import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateCustomerWithProcessIdGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _breadcrumbService: BreadcrumbService,
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService.getProcessById$(+route.params.processId).pipe(first()).toPromise();
const process = await this._applicationService
.getProcessById$(+route.params.processId)
.pipe(first())
.toPromise();
// if (!(process?.type === 'cart')) {
// // TODO:
@@ -37,7 +37,10 @@ export class CanActivateCustomerWithProcessIdGuard {
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService.getBreadcrumbByKey$(+route.params.processId).pipe(first()).toPromise();
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit der Kundensuche zu tun haben
if (crumbs.length > 1) {

View File

@@ -1,22 +1,12 @@
import { Injectable } from "@angular/core";
import {
ActivatedRouteSnapshot,
Router,
RouterStateSnapshot,
} from "@angular/router";
import { ApplicationProcess, ApplicationService } from "@core/application";
import { DomainCheckoutService } from "@domain/checkout";
import { logger } from "@isa/core/logging";
import { CustomerSearchNavigation } from "@shared/services/navigation";
import { first } from "rxjs/operators";
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { CustomerSearchNavigation } from '@shared/services/navigation';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: "root" })
@Injectable({ providedIn: 'root' })
export class CanActivateCustomerGuard {
#logger = logger(() => ({
context: "CanActivateCustomerGuard",
tags: ["guard", "customer", "navigation"],
}));
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
@@ -24,77 +14,27 @@ export class CanActivateCustomerGuard {
private readonly _navigation: CustomerSearchNavigation,
) {}
async canActivate(
route: ActivatedRouteSnapshot,
{ url }: RouterStateSnapshot,
) {
if (url.startsWith("/kunde/customer/search/")) {
const processId = Date.now(); // Generate a new process ID
// Extract parts before and after the pattern
const parts = url.split("/kunde/customer/");
if (parts.length === 2) {
const prefix = parts[0] + "/kunde/";
const suffix = "customer/" + parts[1];
// Construct the new URL with process ID inserted
const newUrl = `${prefix}${processId}/${suffix}`;
this.#logger.info("Redirecting to URL with process ID", () => ({
originalUrl: url,
newUrl,
processId,
}));
// Navigate to the new URL and prevent original navigation
this._router.navigateByUrl(newUrl);
return false;
}
}
const processes = await this._applicationService
.getProcesses$("customer")
.pipe(first())
.toPromise();
const lastActivatedProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$("customer", "cart")
.pipe(first())
.toPromise()
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
const lastActivatedCartCheckoutProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$("customer", "cart-checkout")
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const lastActivatedGoodsOutProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$("customer", "goods-out")
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService
.getActivatedProcessId$()
.pipe(first())
.toPromise();
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
if (
!!lastActivatedCartCheckoutProcessId &&
lastActivatedCartCheckoutProcessId === activatedProcessId
) {
await this.fromCartCheckoutProcess(
processes,
lastActivatedCartCheckoutProcessId,
);
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
await this.fromCartCheckoutProcess(processes, lastActivatedCartCheckoutProcessId);
return false;
} else if (
!!lastActivatedGoodsOutProcessId &&
lastActivatedGoodsOutProcessId === activatedProcessId
) {
} else if (!!lastActivatedGoodsOutProcessId && lastActivatedGoodsOutProcessId === activatedProcessId) {
await this.fromGoodsOutProcess(processes, lastActivatedGoodsOutProcessId);
return false;
}
@@ -119,28 +59,25 @@ export class CanActivateCustomerGuard {
const newProcessId = Date.now();
await this._applicationService.createProcess({
id: newProcessId,
type: "cart",
section: "customer",
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === "cart"))}`,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
await this.navigateToDefaultRoute(newProcessId);
}
// Bei offener Bestellbestätigung und Klick auf Footer Kundensuche
async fromCartCheckoutProcess(
processes: ApplicationProcess[],
processId: number,
) {
async fromCartCheckoutProcess(processes: ApplicationProcess[], processId: number) {
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
this._checkoutService.removeProcess({ processId });
// Ändere type cart-checkout zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: "cart",
section: "customer",
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === "cart"))}`,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
data: {},
});
@@ -149,31 +86,22 @@ export class CanActivateCustomerGuard {
}
// Bei offener Warenausgabe und Klick auf Footer Kundensuche
async fromGoodsOutProcess(
processes: ApplicationProcess[],
processId: number,
) {
const buyer = await this._checkoutService
.getBuyer({ processId })
.pipe(first())
.toPromise();
const customerFeatures = await this._checkoutService
.getCustomerFeatures({ processId })
.pipe(first())
.toPromise();
async fromGoodsOutProcess(processes: ApplicationProcess[], processId: number) {
const buyer = await this._checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
const customerFeatures = await this._checkoutService.getCustomerFeatures({ processId }).pipe(first()).toPromise();
const name = buyer
? customerFeatures?.b2b
? buyer.organisation?.name
? buyer.organisation?.name
: buyer.lastName
: buyer.lastName
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === "cart"))}`;
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`;
// Ändere type goods-out zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: "cart",
section: "customer",
type: 'cart',
section: 'customer',
name,
});
@@ -182,20 +110,12 @@ export class CanActivateCustomerGuard {
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) =>
Number(process?.name?.replace(/\D/g, "")),
);
return !!processNumbers && processNumbers.length > 0
? this.findMissingNumber(processNumbers)
: 1;
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
for (
let missingNumber = 1;
missingNumber < Math.max(...processNumbers);
missingNumber++
) {
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
if (!processNumbers.find((number) => number === missingNumber)) {
return missingNumber;
}

View File

@@ -3,21 +3,13 @@ import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { first } from 'rxjs/operators';
import { z } from 'zod';
@Injectable({ providedIn: 'root' })
export class CanActivateGoodsInGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _config: Config,
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _config: Config) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const pid = this._config.get('process.ids.goodsIn', z.number());
const process = await this._applicationService
.getProcessById$(pid)
.pipe(first())
.toPromise();
const process = await this._applicationService.getProcessById$(this._config.get('process.ids.goodsIn')).pipe(first()).toPromise();
if (!process) {
await this._applicationService.createProcess({
id: this._config.get('process.ids.goodsIn'),
@@ -26,9 +18,7 @@ export class CanActivateGoodsInGuard {
name: '',
});
}
this._applicationService.activateProcess(
this._config.get('process.ids.goodsIn'),
);
this._applicationService.activateProcess(this._config.get('process.ids.goodsIn'));
return true;
}
}

View File

@@ -6,13 +6,13 @@ import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateGoodsOutWithProcessIdGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _breadcrumbService: BreadcrumbService,
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService.getProcessById$(+route.params.processId).pipe(first()).toPromise();
const process = await this._applicationService
.getProcessById$(+route.params.processId)
.pipe(first())
.toPromise();
if (!process) {
// const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
@@ -31,7 +31,10 @@ export class CanActivateGoodsOutWithProcessIdGuard {
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService.getBreadcrumbByKey$(+route.params.processId).pipe(first()).toPromise();
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit der Warenausgabe zu tun haben
if (crumbs.length > 1) {

View File

@@ -9,7 +9,7 @@ export class CanActivateGoodsOutGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _router: Router,
private readonly _router: Router
) {}
// !!! Ticket #3272 Code soll vorerst bestehen bleiben. Prozess Warenausgabe soll wieder Vorgang heißen (wie aktuell im Produktiv), bis zum neuen Navigationskonzept
@@ -103,17 +103,11 @@ export class CanActivateGoodsOutGuard {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'cart')
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
const lastActivatedCartCheckoutProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout')
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();

View File

@@ -6,10 +6,7 @@ import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivatePackageInspectionGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _config: Config,
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _config: Config) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService

View File

@@ -6,13 +6,13 @@ import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateProductWithProcessIdGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _breadcrumbService: BreadcrumbService,
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService.getProcessById$(+route.params.processId).pipe(first()).toPromise();
const process = await this._applicationService
.getProcessById$(+route.params.processId)
.pipe(first())
.toPromise();
// if (!(process?.type === 'cart')) {
// // TODO:
@@ -37,7 +37,10 @@ export class CanActivateProductWithProcessIdGuard {
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService.getBreadcrumbByKey$(+route.params.processId).pipe(first()).toPromise();
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit der Artikelsuche zu tun haben
if (crumbs.length > 1) {

View File

@@ -16,24 +16,15 @@ export class CanActivateProductGuard {
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'cart')
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
const lastActivatedCartCheckoutProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout')
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const lastActivatedGoodsOutProcessId = (
await this._applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out')
.pipe(first())
.toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();

View File

@@ -9,14 +9,11 @@ export class CanActivateRemissionGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _config: Config,
private readonly _router: Router,
private readonly _router: Router
) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
.getProcessById$(this._config.get('process.ids.remission'))
.pipe(first())
.toPromise();
const process = await this._applicationService.getProcessById$(this._config.get('process.ids.remission')).pipe(first()).toPromise();
if (!process) {
await this._applicationService.createProcess({
id: this._config.get('process.ids.remission'),

View File

@@ -6,16 +6,10 @@ import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateTaskCalendarGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _config: Config,
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _config: Config) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
.getProcessById$(this._config.get('process.ids.taskCalendar'))
.pipe(first())
.toPromise();
const process = await this._applicationService.getProcessById$(this._config.get('process.ids.taskCalendar')).pipe(first()).toPromise();
if (!process) {
await this._applicationService.createProcess({
id: this._config.get('process.ids.taskCalendar'),

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '@core/auth';
import { ScanAdapterService } from '@adapter/scan';
import { AuthService as IsaAuthService } from '@generated/swagger/isa-api';
import { AuthService as IsaAuthService } from '@swagger/isa';
import { UiConfirmModalComponent, UiErrorModalComponent, UiModalResult, UiModalService } from '@ui/modal';
import { EnvironmentService } from '@core/environment';
import { injectNetworkStatus$ } from '../services/network-status.service';

View File

@@ -5,7 +5,7 @@ import { take } from 'rxjs/operators';
export const ProcessIdGuard: CanActivateFn = async (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
state: RouterStateSnapshot
): Promise<boolean | UrlTree> => {
const application = inject(ApplicationService);
const router = inject(Router);

View File

@@ -1,66 +1,66 @@
// import { HttpErrorInterceptor } from './http-error.interceptor';
// import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
// import { UiMessageModalComponent, UiModalResult, UiModalService } from '@ui/modal';
// import { of, Subject, throwError } from 'rxjs';
// import { HttpErrorResponse } from '@angular/common/http';
// import { AuthService } from '@core/auth';
import { HttpErrorInterceptor } from './http-error.interceptor';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
import { UiMessageModalComponent, UiModalResult, UiModalService } from '@ui/modal';
import { of, Subject, throwError } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
import { AuthService } from '@core/auth';
// describe('HttpErrorInterceptor', () => {
// let spectator: SpectatorService<HttpErrorInterceptor>;
// let modalMock: jasmine.SpyObj<UiModalService>;
// let httpErrorInterceptor: HttpErrorInterceptor;
describe('HttpErrorInterceptor', () => {
let spectator: SpectatorService<HttpErrorInterceptor>;
let modalMock: jasmine.SpyObj<UiModalService>;
let httpErrorInterceptor: HttpErrorInterceptor;
// const createService = createServiceFactory({
// service: HttpErrorInterceptor,
// mocks: [UiModalService, AuthService],
// });
const createService = createServiceFactory({
service: HttpErrorInterceptor,
mocks: [UiModalService, AuthService],
});
// beforeEach(() => {
// spectator = createService();
// httpErrorInterceptor = spectator.service;
// modalMock = spectator.inject(UiModalService);
beforeEach(() => {
spectator = createService();
httpErrorInterceptor = spectator.service;
modalMock = spectator.inject(UiModalService);
// modalMock.open.and.returnValue({
// afterClosed$: of({} as UiModalResult<any>),
// } as any);
// });
modalMock.open.and.returnValue({
afterClosed$: of({} as UiModalResult<any>),
} as any);
});
// it('should be created', () => {
// expect(httpErrorInterceptor).toBeTruthy();
// });
it('should be created', () => {
expect(httpErrorInterceptor).toBeTruthy();
});
// describe('intercept', () => {
// it('should catch the error and call handleError', (done) => {
// const error = new HttpErrorResponse({
// status: 0,
// statusText: '',
// url: '',
// });
describe('intercept', () => {
it('should catch the error and call handleError', (done) => {
const error = new HttpErrorResponse({
status: 0,
statusText: '',
url: '',
});
// const handleErrorSpy = spyOn(httpErrorInterceptor, 'handleError').and.callThrough();
// httpErrorInterceptor.intercept(null, { handle: () => throwError(error) }).subscribe({
// error: () => {
// expect(handleErrorSpy).toHaveBeenCalledWith(error);
// done();
// },
// });
// });
// });
const handleErrorSpy = spyOn(httpErrorInterceptor, 'handleError').and.callThrough();
httpErrorInterceptor.intercept(null, { handle: () => throwError(error) }).subscribe({
error: () => {
expect(handleErrorSpy).toHaveBeenCalledWith(error);
done();
},
});
});
});
// describe('handleError', () => {
// it('should call modal.open with offline message if status is 0', () => {
// const error = {
// error: {
// message: 'test',
// },
// status: 0,
// };
// httpErrorInterceptor.handleError(error as any);
// expect(modalMock.open).toHaveBeenCalledWith({
// content: UiMessageModalComponent,
// title: 'Sie sind offline, keine Verbindung zum Netzwerk',
// data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
// });
// });
// });
// });
describe('handleError', () => {
it('should call modal.open with offline message if status is 0', () => {
const error = {
error: {
message: 'test',
},
status: 0,
};
httpErrorInterceptor.handleError(error as any);
expect(modalMock.open).toHaveBeenCalledWith({
content: UiMessageModalComponent,
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
});
});
});
});

View File

@@ -1,9 +1,9 @@
import { inject, Injectable, Injector } from '@angular/core';
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { from, NEVER, Observable, throwError } from 'rxjs';
import { NEVER, Observable, throwError } from 'rxjs';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { catchError, filter, mergeMap, takeUntil, tap } from 'rxjs/operators';
import { AuthService, LoginStrategy } from '@core/auth';
import { AuthService } from '@core/auth';
import { IsaLogProvider } from '../providers';
import { LogLevel } from '@core/logger';
import { injectOnline$ } from '../services/network-status.service';
@@ -11,7 +11,6 @@ import { injectOnline$ } from '../services/network-status.service';
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
readonly offline$ = injectOnline$().pipe(filter((online) => !online));
readonly injector = inject(Injector);
constructor(
private _modal: UiModalService,
@@ -28,9 +27,18 @@ export class HttpErrorInterceptor implements HttpInterceptor {
handleError(error: HttpErrorResponse): Observable<any> {
if (error.status === 401) {
const strategy = this.injector.get(LoginStrategy);
return from(strategy.login('Sie sind nicht mehr angemeldet')).pipe(mergeMap(() => NEVER));
return this._modal
.open({
content: UiMessageModalComponent,
title: 'Sie sind nicht mehr angemeldet',
data: { message: 'Sie werden neu angemeldet' },
})
.afterClosed$.pipe(
tap(() => {
this._auth.login();
}),
mergeMap(() => NEVER),
);
}
if (!error.url.endsWith('/isa/logging')) {

View File

@@ -4,7 +4,6 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
selector: 'app-main',
templateUrl: 'main.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class MainComponent {
constructor() {}

View File

@@ -1,16 +1,18 @@
import { Platform, PlatformModule } from '@angular/cdk/platform';
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { BranchDTO } from '@generated/swagger/checkout-api';
import { Component, OnInit } from '@angular/core';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
import { BranchDTO } from '@swagger/checkout';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'app-preview',
templateUrl: 'preview.component.html',
styleUrls: ['preview.component.css'],
imports: [CommonModule, PlatformModule],
imports: [CommonModule, BranchSelectorComponent, PlatformModule],
standalone: true,
})
export class PreviewComponent {
export class PreviewComponent implements OnInit {
selectedBranch$ = new BehaviorSubject<BranchDTO>({});
get appVersion() {
@@ -23,7 +25,7 @@ export class PreviewComponent {
get navigator() {
const nav = {};
for (const i in window.navigator) nav[i] = navigator[i];
for (let i in window.navigator) nav[i] = navigator[i];
return nav;
}
@@ -50,6 +52,8 @@ export class PreviewComponent {
constructor(private readonly _platform: Platform) {}
ngOnInit() {}
setNewBranch(branch: BranchDTO) {
this.selectedBranch$.next(branch);
}

View File

@@ -1,16 +1,11 @@
import { HttpErrorResponse } from "@angular/common/http";
import { ErrorHandler, Injectable } from "@angular/core";
import { AuthService } from "@core/auth";
import {
DialogModel,
UiDialogModalComponent,
UiErrorModalComponent,
UiModalService,
} from "@ui/modal";
import { IsaLogProvider } from "./isa.log-provider";
import { LogLevel } from "@core/logger";
import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler, Injectable } from '@angular/core';
import { AuthService } from '@core/auth';
import { DialogModel, UiDialogModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
import { IsaLogProvider } from './isa.log-provider';
import { LogLevel } from '@core/logger';
@Injectable({ providedIn: "root" })
@Injectable({ providedIn: 'root' })
export class IsaErrorHandler implements ErrorHandler {
constructor(
private _modal: UiModalService,
@@ -22,7 +17,7 @@ export class IsaErrorHandler implements ErrorHandler {
console.error(error);
// Bei Klick auf Abbrechen auf der Login Seite erneut zur Login Seite weiterleiten
if (error?.type === "token_error") {
if (error?.type === 'token_error') {
this._authService.login();
return;
}
@@ -31,14 +26,11 @@ export class IsaErrorHandler implements ErrorHandler {
await this._modal
.open({
content: UiDialogModalComponent,
title: "Sitzung abgelaufen",
title: 'Sitzung abgelaufen',
data: {
handleCommand: false,
content:
"Sie waren zu lange nicht in der ISA aktiv. Bitte melden Sie sich erneut an",
actions: [
{ command: "CLOSE", selected: true, label: "Erneut anmelden" },
],
content: 'Sie waren zu lange nicht in der ISA aktiv. Bitte melden Sie sich erneut an',
actions: [{ command: 'CLOSE', selected: true, label: 'Erneut anmelden' }],
} as DialogModel,
})
.afterClosed$.toPromise();
@@ -47,11 +39,7 @@ export class IsaErrorHandler implements ErrorHandler {
return;
}
try {
this._isaLogProvider.log(LogLevel.ERROR, "Client Error", error);
} catch (logError) {
console.error("Error logging to IsaLogProvider:", logError);
}
this._isaLogProvider.log(LogLevel.ERROR, 'Client Error', error);
// this._modal.open({
// content: UiErrorModalComponent,

View File

@@ -1,36 +1,28 @@
import { Injectable } from "@angular/core";
import { LogLevel, LogProvider } from "@core/logger";
import { UserStateService } from "@generated/swagger/isa-api";
import { environment } from "../../environments/environment";
import { Injectable, Injector } from '@angular/core';
import { LogLevel, LogProvider } from '@core/logger';
import { UserStateService } from '@swagger/isa';
import { environment } from '../../environments/environment';
@Injectable({ providedIn: "root" })
@Injectable({ providedIn: 'root' })
export class IsaLogProvider implements LogProvider {
static InfoService: UserStateService | undefined;
log(
logLevel: LogLevel,
message: string,
error: Error,
...optionalParams: any[]
): void {
try {
if (
!environment.production &&
(logLevel === LogLevel.WARN || logLevel === LogLevel.ERROR)
) {
IsaLogProvider.InfoService?.UserStateSaveLog({
logType: logLevel,
message: message,
content: JSON.stringify({
error: error?.name,
message: error?.message,
stack: error?.stack,
data: optionalParams,
}),
}).toPromise();
}
} catch (error) {
console.error("Error logging to InfoService:", error);
constructor() {}
log(logLevel: LogLevel, message: string, error: Error, ...optionalParams: any[]): void {
if (!environment.production && (logLevel === LogLevel.WARN || logLevel === LogLevel.ERROR)) {
IsaLogProvider.InfoService?.UserStateSaveLog({
logType: logLevel,
message: message,
content: JSON.stringify({
error: error?.name,
message: error?.message,
stack: error?.stack,
data: optionalParams,
}),
})
.toPromise()
.catch(() => {});
}
}
}

View File

@@ -4,8 +4,9 @@ import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class ProcessIdResolver {
constructor() {}
resolve(route: ActivatedRouteSnapshot): Observable<number> | Promise<number> | number {
return route.params.processId;
}
}

View File

@@ -4,10 +4,7 @@ import { ApplicationService } from '@core/application';
import { Observable } from 'rxjs';
export abstract class SectionResolver {
constructor(
protected section: 'customer' | 'branch',
protected applicationService: ApplicationService,
) {}
constructor(protected section: 'customer' | 'branch', protected applicationService: ApplicationService) {}
resolve(route: ActivatedRouteSnapshot): Observable<string> | Promise<string> | string {
this.applicationService.setSection(this.section);

View File

@@ -1 +0,0 @@
export * from './network-status.service';

View File

@@ -1,32 +1,26 @@
import { Injectable } from '@angular/core';
import { Logger, LogLevel } from '@core/logger';
import { Store } from '@ngrx/store';
import { UserStateService } from '@swagger/isa';
import { debounceTime, switchMap, takeUntil } from 'rxjs/operators';
import { RootState } from './root.state';
import packageInfo from 'packageJson';
import { environment } from '../../environments/environment';
import { Subject } from 'rxjs';
import { AuthService } from '@core/auth';
import { injectStorage, UserStorageProvider } from '@isa/core/storage';
import { isEqual } from 'lodash';
@Injectable({ providedIn: 'root' })
export class RootStateService {
static LOCAL_STORAGE_KEY = 'ISA_APP_INITIALSTATE';
#storage = injectStorage(UserStorageProvider);
private _cancelSave = new Subject<void>();
constructor(
private readonly _authService: AuthService,
private readonly _userStateService: UserStateService,
private _logger: Logger,
private _store: Store,
) {
if (!environment.production) {
console.log(
'Die UserState kann in der Konsole mit der Funktion "clearUserState()" geleert werden.',
);
console.log('Die UserState kann in der Konsole mit der Funktion "clearUserState()" geleert werden.');
}
window['clearUserState'] = () => {
@@ -47,17 +41,9 @@ export class RootStateService {
takeUntil(this._cancelSave),
debounceTime(1000),
switchMap((state) => {
const data = {
...state,
version: packageInfo.version,
sub: this._authService.getClaimByKey('sub'),
};
RootStateService.SaveToLocalStorageRaw(JSON.stringify(data));
return this.#storage.set('state', {
...state,
version: packageInfo.version,
sub: this._authService.getClaimByKey('sub'),
});
const raw = JSON.stringify({ ...state, version: packageInfo.version });
RootStateService.SaveToLocalStorageRaw(raw);
return this._userStateService.UserStateSetUserState({ content: raw });
}),
)
.subscribe();
@@ -68,15 +54,16 @@ export class RootStateService {
*/
async load(): Promise<boolean> {
try {
const res = await this.#storage.get('state');
const res = await this._userStateService.UserStateGetUserState().toPromise();
const resContent = res?.result?.content ?? null;
const storageContent = RootStateService.LoadFromLocalStorageRaw();
if (res) {
RootStateService.SaveToLocalStorageRaw(JSON.stringify(res));
if (resContent) {
RootStateService.SaveToLocalStorageRaw(res.result.content);
}
if (!isEqual(res, storageContent)) {
if (resContent !== storageContent) {
return true;
}
} catch (error) {
@@ -88,7 +75,7 @@ export class RootStateService {
async clear() {
try {
this._cancelSave.next();
await this.#storage.clear('state');
await this._userStateService.UserStateResetUserState().toPromise();
await new Promise((resolve) => setTimeout(resolve, 100));
RootStateService.RemoveFromLocalStorage();
await new Promise((resolve) => setTimeout(resolve, 100));
@@ -109,12 +96,7 @@ export class RootStateService {
static LoadFromLocalStorage(): RootState {
const raw = RootStateService.LoadFromLocalStorageRaw();
if (raw) {
try {
return JSON.parse(raw);
} catch (error) {
console.error('Error parsing local storage:', error);
this.RemoveFromLocalStorage();
}
return JSON.parse(raw);
}
return undefined;
}

View File

@@ -7,14 +7,9 @@ import { AuthService } from '@core/auth';
templateUrl: 'token-login.component.html',
styleUrls: ['token-login.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class TokenLoginComponent implements OnInit {
constructor(
private _route: ActivatedRoute,
private _authService: AuthService,
private _router: Router,
) {}
constructor(private _route: ActivatedRoute, private _authService: AuthService, private _router: Router) {}
ngOnInit() {
if (this._route.snapshot.params.token && !this._authService.isAuthenticated()) {

View File

@@ -1,222 +1,219 @@
/* cyrillic-ext */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url("./Open_Sans-400-cyrillic-ext1.woff2") format("woff2");
src: url('./Open_Sans-400-cyrillic-ext1.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url("./Open_Sans-400-cyrillic2.woff2") format("woff2");
src: url('./Open_Sans-400-cyrillic2.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url("./Open_Sans-400-greek-ext3.woff2") format("woff2");
src: url('./Open_Sans-400-greek-ext3.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url("./Open_Sans-400-greek4.woff2") format("woff2");
src: url('./Open_Sans-400-greek4.woff2') format('woff2');
unicode-range: U+0370-03FF;
}
/* hebrew */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url("./Open_Sans-400-hebrew5.woff2") format("woff2");
src: url('./Open_Sans-400-hebrew5.woff2') format('woff2');
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* vietnamese */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url("./Open_Sans-400-vietnamese6.woff2") format("woff2");
src: url('./Open_Sans-400-vietnamese6.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url("./Open_Sans-400-latin-ext7.woff2") format("woff2");
src: url('./Open_Sans-400-latin-ext7.woff2') format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url("./Open_Sans-400-latin8.woff2") format("woff2");
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
src: url('./Open_Sans-400-latin8.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url("./Open_Sans-600-cyrillic-ext9.woff2") format("woff2");
src: url('./Open_Sans-600-cyrillic-ext9.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url("./Open_Sans-600-cyrillic10.woff2") format("woff2");
src: url('./Open_Sans-600-cyrillic10.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url("./Open_Sans-600-greek-ext11.woff2") format("woff2");
src: url('./Open_Sans-600-greek-ext11.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url("./Open_Sans-600-greek12.woff2") format("woff2");
src: url('./Open_Sans-600-greek12.woff2') format('woff2');
unicode-range: U+0370-03FF;
}
/* hebrew */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url("./Open_Sans-600-hebrew13.woff2") format("woff2");
src: url('./Open_Sans-600-hebrew13.woff2') format('woff2');
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* vietnamese */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url("./Open_Sans-600-vietnamese14.woff2") format("woff2");
src: url('./Open_Sans-600-vietnamese14.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url("./Open_Sans-600-latin-ext15.woff2") format("woff2");
src: url('./Open_Sans-600-latin-ext15.woff2') format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url("./Open_Sans-600-latin16.woff2") format("woff2");
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
src: url('./Open_Sans-600-latin16.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url("./Open_Sans-700-cyrillic-ext17.woff2") format("woff2");
src: url('./Open_Sans-700-cyrillic-ext17.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url("./Open_Sans-700-cyrillic18.woff2") format("woff2");
src: url('./Open_Sans-700-cyrillic18.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url("./Open_Sans-700-greek-ext19.woff2") format("woff2");
src: url('./Open_Sans-700-greek-ext19.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url("./Open_Sans-700-greek20.woff2") format("woff2");
src: url('./Open_Sans-700-greek20.woff2') format('woff2');
unicode-range: U+0370-03FF;
}
/* hebrew */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url("./Open_Sans-700-hebrew21.woff2") format("woff2");
src: url('./Open_Sans-700-hebrew21.woff2') format('woff2');
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* vietnamese */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url("./Open_Sans-700-vietnamese22.woff2") format("woff2");
src: url('./Open_Sans-700-vietnamese22.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url("./Open_Sans-700-latin-ext23.woff2") format("woff2");
src: url('./Open_Sans-700-latin-ext23.woff2') format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Open Sans";
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url("./Open_Sans-700-latin24.woff2") format("woff2");
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
src: url('./Open_Sans-700-latin24.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}

View File

@@ -285,8 +285,27 @@
"name": "gift",
"data": "M2 21V10H0V4H5.2C5.11667 3.85 5.0625 3.69167 5.0375 3.525C5.0125 3.35833 5 3.18333 5 3C5 2.16667 5.29167 1.45833 5.875 0.875C6.45833 0.291667 7.16667 0 8 0C8.38333 0 8.74167 0.0708333 9.075 0.2125C9.40833 0.354167 9.71667 0.55 10 0.8C10.2833 0.533333 10.5917 0.333333 10.925 0.2C11.2583 0.0666667 11.6167 0 12 0C12.8333 0 13.5417 0.291667 14.125 0.875C14.7083 1.45833 15 2.16667 15 3C15 3.18333 14.9833 3.35417 14.95 3.5125C14.9167 3.67083 14.8667 3.83333 14.8 4H20V10H18V21H2ZM12 2C11.7167 2 11.4792 2.09583 11.2875 2.2875C11.0958 2.47917 11 2.71667 11 3C11 3.28333 11.0958 3.52083 11.2875 3.7125C11.4792 3.90417 11.7167 4 12 4C12.2833 4 12.5208 3.90417 12.7125 3.7125C12.9042 3.52083 13 3.28333 13 3C13 2.71667 12.9042 2.47917 12.7125 2.2875C12.5208 2.09583 12.2833 2 12 2ZM7 3C7 3.28333 7.09583 3.52083 7.2875 3.7125C7.47917 3.90417 7.71667 4 8 4C8.28333 4 8.52083 3.90417 8.7125 3.7125C8.90417 3.52083 9 3.28333 9 3C9 2.71667 8.90417 2.47917 8.7125 2.2875C8.52083 2.09583 8.28333 2 8 2C7.71667 2 7.47917 2.09583 7.2875 2.2875C7.09583 2.47917 7 2.71667 7 3ZM2 6V8H9V6H2ZM9 19V10H4V19H9ZM11 19H16V10H11V19ZM18 8V6H11V8H18Z",
"viewBox": "0 0 20 21"
},
{
"name": "undo",
"data": "M280-200v-80h284q63 0 109.5-40T720-420q0-60-46.5-100T564-560H312l104 104-56 56-200-200 200-200 56 56-104 104h252q97 0 166.5 63T800-420q0 94-69.5 157T564-200H280Z",
"viewBox": "0 -960 960 960"
},
{
"name": "delete",
"data": "M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z",
"viewBox": "0 -960 960 960"
},
{
"name": "save",
"data": "M840-680v480q0 33-23.5 56.5T760-120H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h480l160 160Zm-80 34L646-760H200v560h560v-446ZM480-240q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM240-560h360v-160H240v160Zm-40-86v446-560 114Z",
"viewBox": "0 -960 960 960"
},
{
"name": "check",
"data": "M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z",
"viewBox": "0 -960 960 960"
}
],
"aliases": [
{

View File

@@ -1,4 +1,4 @@
import { Directive, HostListener, inject, Input } from '@angular/core';
import { Directive, HostListener, Input } from '@angular/core';
import { ProductCatalogNavigationService } from '@shared/services/navigation';
@Directive({
@@ -8,7 +8,7 @@ import { ProductCatalogNavigationService } from '@shared/services/navigation';
export class NavigateOnClickDirective {
@Input('productImageNavigation') ean: string;
private readonly _productCatalogNavigation = inject(ProductCatalogNavigationService);
constructor(private readonly _productCatalogNavigation: ProductCatalogNavigationService) {}
@HostListener('click', ['$event'])
async onClick(event: MouseEvent) {

View File

@@ -1,85 +1,133 @@
{
"title": "ISA - Feature",
"silentRefresh": {
"interval": 300000
},
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
},
"@core/logger": {
"logLevel": "debug"
},
"@domain/checkout": {
"olaExpiration": "5m"
},
"@swagger/isa": {
"rootUrl": "https://isa-feature.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa-feature.paragon-data.net/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa-feature.paragon-data.net/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa-feature.paragon-data.net/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa-feature.paragon-data.net/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-feature.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
},
"hubs": {
"notifications": {
"url": "https://isa-feature.paragon-data.net/isa/v1/rt",
"enableAutomaticReconnect": false,
"httpOptions": {
"transport": 1,
"logMessageContent": true,
"skipNegotiation": true
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "Ae8F2Wx2RMq5Lvn7UUAlWzVFZTt2+ubMAF8XtDpmPlNkBeG/LWs1M7AbgDW0LQqYLnszClEENaEHS56/6Ts2vrJ1Ux03CXUjK3jUvZpF5OchXR1CpnmpepJ6WxPCd7LMVHUGG1BbwPLDTFjP3y8uT0caTSmmGrYQWAs4CZcEF+ZBabP0z7vfm+hCZF/ebj9qqCJZcW8nH/n19hohshllzYBjFXjh87P2lIh1s6yZS3OaQWWXo/o0AKdxx7T6CVyR0/G5zq6uYJWf6rs3euUBEhpzOZHbHZK86Lvy2AVBEyVkkcttlDW1J2fA4l1W1JV/Xibz8AQV6kG482EpGF42KEoK48paZgX3e1AQsqUtmqzw294dcP4zMVstnw5/WrwKKi/5E/nOOJT2txYP1ZufIjPrwNFsqTlv7xCQlHjMzFGYwT816yD5qLRLbwOtjrkUPXNZLZ06T4upvWwJDmm8XgdeoDqMjHdcO4lwji1bl9EiIYJ/2qnsk9yZ2FqSaHzn4cbiL0f5u2HFlNAP0GUujGRlthGhHi6o4dFU+WAxKsFMKVt+SfoQUazNKHFVQgiAklTIZxIc/HUVzRvOLMxf+wFDerraBtcqGJg+g/5mrWYqeDBGhCBHtKiYf6244IJ4afzNTiH1/30SJcRzXwbEa3A7q1fJTx9/nLTOfVPrJKBQs7f/OQs2dA7LDCel8mzXdbjvsNQaeU5+iCIAq6zbTNKy1xT8wwj+VZrQmtNJs+qeznD+u29nCM24h8xCmRpvNPo4/Mww/lrTNrrNwLBSn1pMIwsH7yS9hH0v0oNAM3A6bVtk1D9qEkbyw+xZa+MZGpMP0D0CdcsqHalPcm5r/Ik="
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}
"title": "ISA - Feature",
"silentRefresh": {
"interval": 300000
},
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
},
"@core/logger": {
"logLevel": "debug"
},
"@domain/checkout": {
"olaExpiration": "5m"
},
"@swagger/isa": {
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
},
"hubs": {
"notifications": {
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
"enableAutomaticReconnect": false,
"httpOptions": {
"transport": 1,
"logMessageContent": true,
"skipNegotiation": true
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "Ac2kvx5ZOzjvFl/LuAd6wds3C30YJ4g8Cm6PX4sgUnKPePVMuH+rFQIyVNn1YdS3myORojEOBsIZWhMw2nRUGBtOnQ5FO+cRHgQu0pkP+VG6OYvt8ETUTn8Aa2f9bmfqclO3LI8WN8psWr+adkZEtqNTvCgyDLZaICh8S7RfmwJVVWaOPX4LDagGhLDPS2YQdg+ibpR0l0ZlX2h/3GttofE64HOlBN3QtDB8yihHJNgVcUVy2UQVS+BXOyvIfZEFbFgPYVt5HZ0aQgcISlYVAmJvRsHyeKYRUnIi3ZN73EQmHzWcOV4/HWVoTs1MTW0mxV074vNwqExYW9LYmz9zgLUD7FMnKMFClkzRBHRN1CC7brosCnFkZWQp3CV4Ua48Fn9GW0Zpn/4MIZgzSU26inl5ZnT3dCc2+3BH3Us1uugTUrOPwFuwsPpr4NZYXWJMOmcm4kBBVJd0Uwk28GyZM7x1hXADcQgcc23+gDtbbUZWVmr3TE7GBcV3j+XUeOk8nHZw3DsYJ46MT2sSfks3QXRl4tBkBad6M+UxW4tb2IQC/4K9IXKhDN8VYXpzrrRHJNvOiH5+NrixthDZHHd/MGNuBDrJFOuXq8L7O6PxBoVZ0NPXmCO5vKyYdhBz5gJ5u3vNkSke7p+cDcvFicsVyNWRaaoFL0UN9gxFVMY8hkJKBZyGxZL2LEDXD44PxPsDnygpDC1Jyc7pkhEA0fKSc9aXZb1HDpwe1hqyyRVBtHkgPiN78GmJImbaUlj8XsK3yVyAjs2XvPR0/3ASHS53ViwzWKL3Oi8I515IJDtDhlvrkeh7MrWMaEu9k6ZQXJi1uJsh3JGfyS+yDlPjePVdsVqnZ4uw9pKMXvjlJeNcIiGM2Cf5S89nk2Qe/56MrKB9Frm6Q5wQHai8TNdCZYBi67dmAX8KHPXvEU4K6KyyW89YGcabZQ3eOJDr4oqW9ZcYPBttREdH3WI/HxvpEq6bqoDhT9AxpIWEMVb6y/DcDHSEresepibug4qOr9xOPq0yk2uiWYhPubFnCk7thQCXOGv9crWnQoOrt9c1qoaWXM4YmKqfcaQ67Tn+uFQmYTZyqw4jlJU4GgKe5/GNCVQM5aNUg1J0Px1NlFCS+rrIDyMQp0byFcgTd/E9sA5d1+YZKHKmJiQwEAz6oU9yyoUlxntSI42GHB/UttPc7Hj14V5+oJ+Yz+CZodmkXFg57Vx4NuxveNtO"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"paymentStatus": {
"0": "-",
"1": "ProformaInvoicePrinted",
"2": "InvoicePrinted",
"4": "Bezahlt",
"8": "OnApproval",
"16": "Kostenlos",
"32": "Mahnung",
"64": "ProformaInvoicePaid",
"128": "Canceled",
"256": "Mahnung",
"512": "PartiallyPaid",
"1024": "Outstanding"
},
"paymentType": {
"0": "-",
"1": "WhenCollecting",
"2": "Kostenlos",
"4": "Bar",
"8": "DirectDebit",
"16": "DebitAdviceMandate",
"32": "DebitCard",
"64": "Kreditkarte",
"128": "Rechnung",
"256": "Vorkasse",
"512": "Voucher",
"1024": "CollectiveInvoice",
"2048": "PayPal",
"4096": "InstantTransfer",
"8192": "BonusCard",
"16384": "AmazonPay"
},
"receiptType": {
"0": "-",
"1": "Lieferschein",
"2": "Gutschrift",
"4": "Sammelgutschrift",
"8": "CollectiveCreditNote",
"16": "Sammellieferschein für Kundenkartenkäufe",
"32": "Sammelgutschrift für Kundenkartenkäufe",
"64": "Zahlungsbeleg",
"128": "Rechnung",
"256": "Sammelrechnung",
"512": "Proformarechnung / Vorauskasse",
"1024": "Bon/Quittung",
"2048": "Rücknahmebeleg"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -17,7 +17,7 @@
},
"@domain/checkout": {
"olaExpiration": "5m"
},
},
"@swagger/isa": {
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
},
@@ -72,13 +72,60 @@
},
"checkForUpdates": 900000,
"licence": {
"scandit": "ASq1ZCbuDs0nJ4cPGkFOSkQEBHSL/kuljVAq2ttwz2Q5CEwULWAlasJIkjsIe/nptzNx1IBSxwDPWh0gXmvF08BRBDTzDUELdX6q02N/HUddSHE4A1dMXoFg+I8of+iZQijQIogxmP5RfSpj6ixzD2cGV0w+BG6o9xRXG4lEv9vNAOC3+emfcuVj2Whmfbuzao4HcWweVSz4HJh8dcbxTPF10MH8qR4kkV7fNWIF+PaYww69relfuvrkgdbWPcLs4Xb7WW4AISX9pTmJmSuv2LvD9gMoPNlmhdTWi+qFcmgZiPRDb1I0Bldo7jKLdboylEXkVQVWF1D2fv3iO5BrijG+n52XQd3ew49/1ssJi6dWhxiZQMlN+mz7cfSPmGzJc0eqaf52tEA8PkIupXjly/6dckSVcqTsex+wHcQuTinyW2bu9/YY3ZTMCp5du+lFZBD+AQSsq7EALzzithpijNG3jQka/8yRSHPBP6JtfobMdbv7SlAESslnN5R5lKJms4HnSm4CwxBAX2sUrhQeuit74/5pwe8qI+CaEGzjKYfmcRhFXd+Kz/ySV3HyrVuieH6+6ytBjGh3EkwGvQ8Ovu4KsruWAyAjHwRyBdrFIRYyLWdou77VH8gSLwHi69QsMGshcnYaREpqDEb5LLR9Ok7jP5IjMyPzX4USS327G9f4I8ujTaCKXQ9a384yV/wP9IeSuJld4dgdGy80rDP2sWZ+NJ65cNWpAfi/Nmbm6ucjICoUHILtchyx/w/YNYUzfCWFjUWHn83TzG+31f3dkjWoI3XpCOg5RKREs55htHSAyZtYhYiQr1FxDVFNEQI2MrDR0fBt2qOeZB08yA=="
"scandit": "Ac2kvx5ZOzjvFl/LuAd6wds3C30YJ4g8Cm6PX4sgUnKPePVMuH+rFQIyVNn1YdS3myORojEOBsIZWhMw2nRUGBtOnQ5FO+cRHgQu0pkP+VG6OYvt8ETUTn8Aa2f9bmfqclO3LI8WN8psWr+adkZEtqNTvCgyDLZaICh8S7RfmwJVVWaOPX4LDagGhLDPS2YQdg+ibpR0l0ZlX2h/3GttofE64HOlBN3QtDB8yihHJNgVcUVy2UQVS+BXOyvIfZEFbFgPYVt5HZ0aQgcISlYVAmJvRsHyeKYRUnIi3ZN73EQmHzWcOV4/HWVoTs1MTW0mxV074vNwqExYW9LYmz9zgLUD7FMnKMFClkzRBHRN1CC7brosCnFkZWQp3CV4Ua48Fn9GW0Zpn/4MIZgzSU26inl5ZnT3dCc2+3BH3Us1uugTUrOPwFuwsPpr4NZYXWJMOmcm4kBBVJd0Uwk28GyZM7x1hXADcQgcc23+gDtbbUZWVmr3TE7GBcV3j+XUeOk8nHZw3DsYJ46MT2sSfks3QXRl4tBkBad6M+UxW4tb2IQC/4K9IXKhDN8VYXpzrrRHJNvOiH5+NrixthDZHHd/MGNuBDrJFOuXq8L7O6PxBoVZ0NPXmCO5vKyYdhBz5gJ5u3vNkSke7p+cDcvFicsVyNWRaaoFL0UN9gxFVMY8hkJKBZyGxZL2LEDXD44PxPsDnygpDC1Jyc7pkhEA0fKSc9aXZb1HDpwe1hqyyRVBtHkgPiN78GmJImbaUlj8XsK3yVyAjs2XvPR0/3ASHS53ViwzWKL3Oi8I515IJDtDhlvrkeh7MrWMaEu9k6ZQXJi1uJsh3JGfyS+yDlPjePVdsVqnZ4uw9pKMXvjlJeNcIiGM2Cf5S89nk2Qe/56MrKB9Frm6Q5wQHai8TNdCZYBi67dmAX8KHPXvEU4K6KyyW89YGcabZQ3eOJDr4oqW9ZcYPBttREdH3WI/HxvpEq6bqoDhT9AxpIWEMVb6y/DcDHSEresepibug4qOr9xOPq0yk2uiWYhPubFnCk7thQCXOGv9crWnQoOrt9c1qoaWXM4YmKqfcaQ67Tn+uFQmYTZyqw4jlJU4GgKe5/GNCVQM5aNUg1J0Px1NlFCS+rrIDyMQp0byFcgTd/E9sA5d1+YZKHKmJiQwEAz6oU9yyoUlxntSI42GHB/UttPc7Hj14V5+oJ+Yz+CZodmkXFg57Vx4NuxveNtO"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
},
"paymentStatus": {
"0": "-",
"1": "ProformaInvoicePrinted",
"2": "InvoicePrinted",
"4": "Bezahlt",
"8": "OnApproval",
"16": "Kostenlos",
"32": "Mahnung",
"64": "ProformaInvoicePaid",
"128": "Canceled",
"256": "Mahnung",
"512": "PartiallyPaid",
"1024": "Outstanding"
},
"paymentType": {
"0": "-",
"1": "WhenCollecting",
"2": "Kostenlos",
"4": "Bar",
"8": "DirectDebit",
"16": "DebitAdviceMandate",
"32": "DebitCard",
"64": "Kreditkarte",
"128": "Rechnung",
"256": "Vorkasse",
"512": "Voucher",
"1024": "CollectiveInvoice",
"2048": "PayPal",
"4096": "InstantTransfer",
"8192": "BonusCard",
"16384": "AmazonPay"
},
"receiptType": {
"0": "-",
"1": "Lieferschein",
"2": "Gutschrift",
"4": "Sammelgutschrift",
"8": "CollectiveCreditNote",
"16": "Sammellieferschein für Kundenkartenkäufe",
"32": "Sammelgutschrift für Kundenkartenkäufe",
"64": "Zahlungsbeleg",
"128": "Rechnung",
"256": "Sammelrechnung",
"512": "Proformarechnung / Vorauskasse",
"1024": "Bon/Quittung",
"2048": "Rücknahmebeleg"
},
"@shared/icon": "/assets/icons.json"
}
}

View File

@@ -1,87 +1,133 @@
{
"title": "ISA - Local",
"silentRefresh": {
"interval": 300000
},
"debug": true,
"dev-scanner": true,
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "isa-client",
"responseType": "code",
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi",
"showDebugInformation": true
},
"@core/logger": {
"logLevel": "debug"
},
"@swagger/isa": {
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
},
"@domain/checkout": {
"olaExpiration": "5m"
},
"hubs": {
"notifications": {
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
"enableAutomaticReconnect": false,
"httpOptions": {
"transport": 1,
"logMessageContent": true,
"skipNegotiation": true
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "Ae8F2Wx2RMq5Lvn7UUAlWzVFZTt2+ubMAF8XtDpmPlNkBeG/LWs1M7AbgDW0LQqYLnszClEENaEHS56/6Ts2vrJ1Ux03CXUjK3jUvZpF5OchXR1CpnmpepJ6WxPCd7LMVHUGG1BbwPLDTFjP3y8uT0caTSmmGrYQWAs4CZcEF+ZBabP0z7vfm+hCZF/ebj9qqCJZcW8nH/n19hohshllzYBjFXjh87P2lIh1s6yZS3OaQWWXo/o0AKdxx7T6CVyR0/G5zq6uYJWf6rs3euUBEhpzOZHbHZK86Lvy2AVBEyVkkcttlDW1J2fA4l1W1JV/Xibz8AQV6kG482EpGF42KEoK48paZgX3e1AQsqUtmqzw294dcP4zMVstnw5/WrwKKi/5E/nOOJT2txYP1ZufIjPrwNFsqTlv7xCQlHjMzFGYwT816yD5qLRLbwOtjrkUPXNZLZ06T4upvWwJDmm8XgdeoDqMjHdcO4lwji1bl9EiIYJ/2qnsk9yZ2FqSaHzn4cbiL0f5u2HFlNAP0GUujGRlthGhHi6o4dFU+WAxKsFMKVt+SfoQUazNKHFVQgiAklTIZxIc/HUVzRvOLMxf+wFDerraBtcqGJg+g/5mrWYqeDBGhCBHtKiYf6244IJ4afzNTiH1/30SJcRzXwbEa3A7q1fJTx9/nLTOfVPrJKBQs7f/OQs2dA7LDCel8mzXdbjvsNQaeU5+iCIAq6zbTNKy1xT8wwj+VZrQmtNJs+qeznD+u29nCM24h8xCmRpvNPo4/Mww/lrTNrrNwLBSn1pMIwsH7yS9hH0v0oNAM3A6bVtk1D9qEkbyw+xZa+MZGpMP0D0CdcsqHalPcm5r/Ik="
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}
"title": "ISA - Local",
"silentRefresh": {
"interval": 300000
},
"debug": true,
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "isa-client",
"responseType": "code",
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi",
"showDebugInformation": true
},
"@core/logger": {
"logLevel": "debug"
},
"@swagger/isa": {
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
},
"@domain/checkout": {
"olaExpiration": "5m"
},
"hubs": {
"notifications": {
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
"enableAutomaticReconnect": false,
"httpOptions": {
"transport": 1,
"logMessageContent": true,
"skipNegotiation": true
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "Ac2kvx5ZOzjvFl/LuAd6wds3C30YJ4g8Cm6PX4sgUnKPePVMuH+rFQIyVNn1YdS3myORojEOBsIZWhMw2nRUGBtOnQ5FO+cRHgQu0pkP+VG6OYvt8ETUTn8Aa2f9bmfqclO3LI8WN8psWr+adkZEtqNTvCgyDLZaICh8S7RfmwJVVWaOPX4LDagGhLDPS2YQdg+ibpR0l0ZlX2h/3GttofE64HOlBN3QtDB8yihHJNgVcUVy2UQVS+BXOyvIfZEFbFgPYVt5HZ0aQgcISlYVAmJvRsHyeKYRUnIi3ZN73EQmHzWcOV4/HWVoTs1MTW0mxV074vNwqExYW9LYmz9zgLUD7FMnKMFClkzRBHRN1CC7brosCnFkZWQp3CV4Ua48Fn9GW0Zpn/4MIZgzSU26inl5ZnT3dCc2+3BH3Us1uugTUrOPwFuwsPpr4NZYXWJMOmcm4kBBVJd0Uwk28GyZM7x1hXADcQgcc23+gDtbbUZWVmr3TE7GBcV3j+XUeOk8nHZw3DsYJ46MT2sSfks3QXRl4tBkBad6M+UxW4tb2IQC/4K9IXKhDN8VYXpzrrRHJNvOiH5+NrixthDZHHd/MGNuBDrJFOuXq8L7O6PxBoVZ0NPXmCO5vKyYdhBz5gJ5u3vNkSke7p+cDcvFicsVyNWRaaoFL0UN9gxFVMY8hkJKBZyGxZL2LEDXD44PxPsDnygpDC1Jyc7pkhEA0fKSc9aXZb1HDpwe1hqyyRVBtHkgPiN78GmJImbaUlj8XsK3yVyAjs2XvPR0/3ASHS53ViwzWKL3Oi8I515IJDtDhlvrkeh7MrWMaEu9k6ZQXJi1uJsh3JGfyS+yDlPjePVdsVqnZ4uw9pKMXvjlJeNcIiGM2Cf5S89nk2Qe/56MrKB9Frm6Q5wQHai8TNdCZYBi67dmAX8KHPXvEU4K6KyyW89YGcabZQ3eOJDr4oqW9ZcYPBttREdH3WI/HxvpEq6bqoDhT9AxpIWEMVb6y/DcDHSEresepibug4qOr9xOPq0yk2uiWYhPubFnCk7thQCXOGv9crWnQoOrt9c1qoaWXM4YmKqfcaQ67Tn+uFQmYTZyqw4jlJU4GgKe5/GNCVQM5aNUg1J0Px1NlFCS+rrIDyMQp0byFcgTd/E9sA5d1+YZKHKmJiQwEAz6oU9yyoUlxntSI42GHB/UttPc7Hj14V5+oJ+Yz+CZodmkXFg57Vx4NuxveNtO"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"paymentStatus": {
"0": "-",
"1": "ProformaInvoicePrinted",
"2": "InvoicePrinted",
"4": "Bezahlt",
"8": "OnApproval",
"16": "Kostenlos",
"32": "Mahnung",
"64": "ProformaInvoicePaid",
"128": "Canceled",
"256": "Mahnung",
"512": "PartiallyPaid",
"1024": "Outstanding"
},
"paymentType": {
"0": "-",
"1": "WhenCollecting",
"2": "Kostenlos",
"4": "Bar",
"8": "DirectDebit",
"16": "DebitAdviceMandate",
"32": "DebitCard",
"64": "Kreditkarte",
"128": "Rechnung",
"256": "Vorkasse",
"512": "Voucher",
"1024": "CollectiveInvoice",
"2048": "PayPal",
"4096": "InstantTransfer",
"8192": "BonusCard",
"16384": "AmazonPay"
},
"receiptType": {
"0": "-",
"1": "Lieferschein",
"2": "Gutschrift",
"4": "Sammelgutschrift",
"8": "CollectiveCreditNote",
"16": "Sammellieferschein für Kundenkartenkäufe",
"32": "Sammelgutschrift für Kundenkartenkäufe",
"64": "Zahlungsbeleg",
"128": "Rechnung",
"256": "Sammelrechnung",
"512": "Proformarechnung / Vorauskasse",
"1024": "Bon/Quittung",
"2048": "Rücknahmebeleg"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -73,7 +73,7 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "ATSVF4WBEUdWFabwSBws+xhCeCSCzgKZVEUjDXt0v6EpJHyN7lNmjMNmg8S+ADBh6h/Wn6Vl1b8TDhw3Fl1ZdkFoQ88zZUgagRY9vKpAjzimETtcVmzAA05W498GYgSr42+q4P9dkGO1RPdcOXqAgZZpNlsaQnyP1WjOMvgRRouwBxnoVCwkir8//kLxJaHPJKSYQNcTdRNMgqNPq/vUwdYVrzg26ltn7eC5imeXyYuQH3u2L4TVuRjr3SDVqsv7lnybh3ijg3dLoI93atUBuxbmzvoWzyb2mxrchJ9DaxrBy0igV2qFzzF1R6QvlNTyvN9HyUvZ0/sPS1DqOoql9bqG7ibyGOdRa2/vfIDTLbVEIaym8yQzErBBqthjviCub2pYXGX+ai9xXufuqRDyX29uCafxSv6sQsnVcEnuLEmP6gdmGBMVvH/WEZHnGn6bQpfWwFIGRZZKM+SxTdToJanj3jxNF/pO445/YZWJBDRT5nP55gESsrwiTXbb+i9lY4Hx7srEQJR07lJWASfKGX6IJtUaIstl7KnO6VI72XI2Zo01SAQAOw7m5+70LgYN1woYoAcziCSFyuIfJavGF0zd1cAu95Cwo0E8+y/YcAO3jwEqFE8oH7PkqKEf4XbF166quTf3AmVQJ8Q6lfSqWlZTps/dDGCAOe3Z63drIbh2bu3A/49bjT1TS7RFK5QvkKCtrM8zOS1DhEDjPPNR84BD/vbpjehHUlG6i7xlTwhmYblQR1oMUpfqrHqX5b9Lg2qADH+AfKRJ7Xd/kPSXdEw7MvJaZFpv9VuiFlUv37EyQBopyFhWYxRU9Kb1XfmkxpO+TCovzmEbKpWxi8KBYOdbX3QaNBW6HQLEd79+H8E="
"scandit": "AZ70hgtZLmFWHbYP+BDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YfzKaR2xbfjsImjXJN7C/TE1CYXsv3DeqIzgDRA/kII4LzqoICWm9mnwejER1kMu1Vf+1NWbwUuYJjsQVQXYkwkWgnv53obevBZtjpgZqIzlStufIU+sPNEWSd9BlrLmiSEu6nXC5lbstyPJVaqihsFoBPNJ0Q+IgJUjqaxhTf6hGtNB3Rqcpv0ZT349NdK9mQ+9lAQYCwXdmYbELdlbZVSja0aJQlkf0TaAjixDbNcR32/VMIw98F2YxVRVWPnADUaJ9Bn8oa7NkOJIUfB7VAg1KtUl8IIwSXvRk436UtPZGxg+PQM7UXX/TgiBuqCQWdUChoH/QAoY2fyKRKgXJHnGdt+pBRQ2OYs7TZWCJLZsLKgnHV+eWh3Y3EB1hN9cmcszdnmYiyD1gCY8pSETFbWV01nVPvIuUTc+jMipv3NN81VQDS5/eU1VJNpRuDiufdBKJv0jjHQk+hTdqe+2GSmMD24ZG8FQXJZZPBCodWR1A81T8toCaIAGHnOfZeO7nxZu9Uo+6ohrVxc5F4szSizra+M3zfuFnm5FGgxCKt5uibLYgRyNcmR8/9vqTfMZgalTklbKHEFw1i+xOwrFrqRMh7FoovDRoI3QuaHLpV+ZUpZo3zTOQofdXPthKAmSLc11cjn509oTnnXUBBoBn/hErrwC/v8dZep1YEKM3wjfuIFsHRrsECswqNjwN/yOZCirm2VvFJEPMuKIwy+8jdNGncm8v4R9Br3c+wYJ8696L7Dg0iFShoyoP4OqIvBNY+dYyy935yGkIF2KI6l01xQZcWoHSVKPd6/78Iwy4lTKhtOqtu7ETJqOE53gQpR9jaAY0RiBB1SLm+Jbwt3ipSJiCDgKmkVmk5AU9HC0XYR/erjg13HF4hIcpLPW1ZWMKSxTqZ8z8FMJilInBgfcBwvjNE5seWvdFKltvlItnGhSh7BLUZ5UInDhl00NJBE2PdstDvRvQjLKvYUoFly3jONVsUfuQpzrcjT0g6gEgL8ZtlmaF11owcCvqhNDxWuTAJTX/xXf6WehxEB0qy3xCdxahbuneS3DPI0z2kAQwrx19i5r+RKQ8bWDaQ/OGY47sLPJgDgijEz16CvAMBSN0PvOso6FstsT/ynR3LLTPcY0QDw21Dv3wNHNa9HbanJcb3/MuhZGlrKp7"
},
"gender": {
"0": "Keine Anrede",
@@ -81,5 +81,52 @@
"2": "Herr",
"4": "Frau"
},
"paymentStatus": {
"0": "-",
"1": "ProformaInvoicePrinted",
"2": "InvoicePrinted",
"4": "Bezahlt",
"8": "OnApproval",
"16": "Kostenlos",
"32": "Mahnung",
"64": "ProformaInvoicePaid",
"128": "Canceled",
"256": "Mahnung",
"512": "PartiallyPaid",
"1024": "Outstanding"
},
"paymentType": {
"0": "-",
"1": "WhenCollecting",
"2": "Kostenlos",
"4": "Bar",
"8": "DirectDebit",
"16": "DebitAdviceMandate",
"32": "DebitCard",
"64": "Kreditkarte",
"128": "Rechnung",
"256": "Vorkasse",
"512": "Voucher",
"1024": "CollectiveInvoice",
"2048": "PayPal",
"4096": "InstantTransfer",
"8192": "BonusCard",
"16384": "AmazonPay"
},
"receiptType": {
"0": "-",
"1": "Lieferschein",
"2": "Gutschrift",
"4": "Sammelgutschrift",
"8": "CollectiveCreditNote",
"16": "Sammellieferschein für Kundenkartenkäufe",
"32": "Sammelgutschrift für Kundenkartenkäufe",
"64": "Zahlungsbeleg",
"128": "Rechnung",
"256": "Sammelrechnung",
"512": "Proformarechnung / Vorauskasse",
"1024": "Bon/Quittung",
"2048": "Rücknahmebeleg"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -73,13 +73,60 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "AbBVKBt1M6NjGVMLDyT7EBwD7Baj5eMzG0k8HT1QSQbUGAFCXXqF73RfxM88FluMPljT8WdVsHkvZcnrZmRhLRVGTU4rclBlVWYck0VozFbIbERw0GyFytcpKt78WTqihXGIkZFEoxcPH5YU63s1x/NDYJMYVvoT3CX+jYl/wGmLOSs/uH8u8p9wpNbjBx3IF0Yu6CA+gGDFP8RngRoRZtBvaLU4IEdk30Ehmg8iCso2Wl+8ePGYgRL+JndZ9B7ZI0lw2YBdsoCoPAVEUB0Ta1osx3rZ9eAmCSLlpCbJtDspHPqVFiQ6a2eBV9Wl5YZ2qrW8NYZ5GntJfytySqka9kOu3nubq04UMuZ6KyNkXjlUO1OvlFYBYW7eudeGParR1iJLPZMghJBOiWPv3nHMxWyxOXhZQKr1zhYgolbceOClnmIPF4JdDgpCwe4RdPTggYTU2Jh1YgPj67tNbijj8OtH7xMpALEOBAJ1/z6zNkYtLAS0qcwzH+QWwSC+I7vP1ne//ODsHLToUbe2LJoUEEht74NPTLWaLMTmmtfPrcoXSQX23yfrc/96d389JppSHp6vGK/CixEeSdrmLmdLYtTbH4mNqP53kp80fGPEd8FXZkFEpjtXtLQiQoP5ug5014FYFqpWqPzICAPhup4HWwSEKiNQ6UfWx9lbFSJYkbrP1JpLf9USNA0LyXN0b1IFNPr57Q25xPerJ04WUjWbfZ1+WZsS58CBjjggcdx4LDKv5/NaHq/wEDcxrt42tQlaCzZezneSZYJSS3cyB8o8C83XwWXo/l8fpbdvyB4O7DJEcL6vWS8jSyiIP8eKSRS81Dkv1UjZfjfomaF8Fh7bZYeatf4gSR3MH7aeaHy0LyVt6RX7Ly99WaVv0qWf3T6w"
"scandit": "AZ70hgtZLmFWHbYP+BDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YfzKaR2xbfjsImjXJN7C/TE1CYXsv3DeqIzgDRA/kII4LzqoICWm9mnwejER1kMu1Vf+1NWbwUuYJjsQVQXYkwkWgnv53obevBZtjpgZqIzlStufIU+sPNEWSd9BlrLmiSEu6nXC5lbstyPJVaqihsFoBPNJ0Q+IgJUjqaxhTf6hGtNB3Rqcpv0ZT349NdK9mQ+9lAQYCwXdmYbELdlbZVSja0aJQlkf0TaAjixDbNcR32/VMIw98F2YxVRVWPnADUaJ9Bn8oa7NkOJIUfB7VAg1KtUl8IIwSXvRk436UtPZGxg+PQM7UXX/TgiBuqCQWdUChoH/QAoY2fyKRKgXJHnGdt+pBRQ2OYs7TZWCJLZsLKgnHV+eWh3Y3EB1hN9cmcszdnmYiyD1gCY8pSETFbWV01nVPvIuUTc+jMipv3NN81VQDS5/eU1VJNpRuDiufdBKJv0jjHQk+hTdqe+2GSmMD24ZG8FQXJZZPBCodWR1A81T8toCaIAGHnOfZeO7nxZu9Uo+6ohrVxc5F4szSizra+M3zfuFnm5FGgxCKt5uibLYgRyNcmR8/9vqTfMZgalTklbKHEFw1i+xOwrFrqRMh7FoovDRoI3QuaHLpV+ZUpZo3zTOQofdXPthKAmSLc11cjn509oTnnXUBBoBn/hErrwC/v8dZep1YEKM3wjfuIFsHRrsECswqNjwN/yOZCirm2VvFJEPMuKIwy+8jdNGncm8v4R9Br3c+wYJ8696L7Dg0iFShoyoP4OqIvBNY+dYyy935yGkIF2KI6l01xQZcWoHSVKPd6/78Iwy4lTKhtOqtu7ETJqOE53gQpR9jaAY0RiBB1SLm+Jbwt3ipSJiCDgKmkVmk5AU9HC0XYR/erjg13HF4hIcpLPW1ZWMKSxTqZ8z8FMJilInBgfcBwvjNE5seWvdFKltvlItnGhSh7BLUZ5UInDhl00NJBE2PdstDvRvQjLKvYUoFly3jONVsUfuQpzrcjT0g6gEgL8ZtlmaF11owcCvqhNDxWuTAJTX/xXf6WehxEB0qy3xCdxahbuneS3DPI0z2kAQwrx19i5r+RKQ8bWDaQ/OGY47sLPJgDgijEz16CvAMBSN0PvOso6FstsT/ynR3LLTPcY0QDw21Dv3wNHNa9HbanJcb3/MuhZGlrKp7"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
},
"paymentStatus": {
"0": "-",
"1": "ProformaInvoicePrinted",
"2": "InvoicePrinted",
"4": "Bezahlt",
"8": "OnApproval",
"16": "Kostenlos",
"32": "Mahnung",
"64": "ProformaInvoicePaid",
"128": "Canceled",
"256": "Mahnung",
"512": "PartiallyPaid",
"1024": "Outstanding"
},
"paymentType": {
"0": "-",
"1": "WhenCollecting",
"2": "Kostenlos",
"4": "Bar",
"8": "DirectDebit",
"16": "DebitAdviceMandate",
"32": "DebitCard",
"64": "Kreditkarte",
"128": "Rechnung",
"256": "Vorkasse",
"512": "Voucher",
"1024": "CollectiveInvoice",
"2048": "PayPal",
"4096": "InstantTransfer",
"8192": "BonusCard",
"16384": "AmazonPay"
},
"receiptType": {
"0": "-",
"1": "Lieferschein",
"2": "Gutschrift",
"4": "Sammelgutschrift",
"8": "CollectiveCreditNote",
"16": "Sammellieferschein für Kundenkartenkäufe",
"32": "Sammelgutschrift für Kundenkartenkäufe",
"64": "Zahlungsbeleg",
"128": "Rechnung",
"256": "Sammelrechnung",
"512": "Proformarechnung / Vorauskasse",
"1024": "Bon/Quittung",
"2048": "Rücknahmebeleg"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -3,8 +3,7 @@
"silentRefresh": {
"interval": 300000
},
"debug": false,
"debug": true,
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
@@ -75,13 +74,60 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "Ae8F2Wx2RMq5Lvn7UUAlWzVFZTt2+ubMAF8XtDpmPlNkBeG/LWs1M7AbgDW0LQqYLnszClEENaEHS56/6Ts2vrJ1Ux03CXUjK3jUvZpF5OchXR1CpnmpepJ6WxPCd7LMVHUGG1BbwPLDTFjP3y8uT0caTSmmGrYQWAs4CZcEF+ZBabP0z7vfm+hCZF/ebj9qqCJZcW8nH/n19hohshllzYBjFXjh87P2lIh1s6yZS3OaQWWXo/o0AKdxx7T6CVyR0/G5zq6uYJWf6rs3euUBEhpzOZHbHZK86Lvy2AVBEyVkkcttlDW1J2fA4l1W1JV/Xibz8AQV6kG482EpGF42KEoK48paZgX3e1AQsqUtmqzw294dcP4zMVstnw5/WrwKKi/5E/nOOJT2txYP1ZufIjPrwNFsqTlv7xCQlHjMzFGYwT816yD5qLRLbwOtjrkUPXNZLZ06T4upvWwJDmm8XgdeoDqMjHdcO4lwji1bl9EiIYJ/2qnsk9yZ2FqSaHzn4cbiL0f5u2HFlNAP0GUujGRlthGhHi6o4dFU+WAxKsFMKVt+SfoQUazNKHFVQgiAklTIZxIc/HUVzRvOLMxf+wFDerraBtcqGJg+g/5mrWYqeDBGhCBHtKiYf6244IJ4afzNTiH1/30SJcRzXwbEa3A7q1fJTx9/nLTOfVPrJKBQs7f/OQs2dA7LDCel8mzXdbjvsNQaeU5+iCIAq6zbTNKy1xT8wwj+VZrQmtNJs+qeznD+u29nCM24h8xCmRpvNPo4/Mww/lrTNrrNwLBSn1pMIwsH7yS9hH0v0oNAM3A6bVtk1D9qEkbyw+xZa+MZGpMP0D0CdcsqHalPcm5r/Ik="
"scandit": "Ac2kvx5ZOzjvFl/LuAd6wds3C30YJ4g8Cm6PX4sgUnKPePVMuH+rFQIyVNn1YdS3myORojEOBsIZWhMw2nRUGBtOnQ5FO+cRHgQu0pkP+VG6OYvt8ETUTn8Aa2f9bmfqclO3LI8WN8psWr+adkZEtqNTvCgyDLZaICh8S7RfmwJVVWaOPX4LDagGhLDPS2YQdg+ibpR0l0ZlX2h/3GttofE64HOlBN3QtDB8yihHJNgVcUVy2UQVS+BXOyvIfZEFbFgPYVt5HZ0aQgcISlYVAmJvRsHyeKYRUnIi3ZN73EQmHzWcOV4/HWVoTs1MTW0mxV074vNwqExYW9LYmz9zgLUD7FMnKMFClkzRBHRN1CC7brosCnFkZWQp3CV4Ua48Fn9GW0Zpn/4MIZgzSU26inl5ZnT3dCc2+3BH3Us1uugTUrOPwFuwsPpr4NZYXWJMOmcm4kBBVJd0Uwk28GyZM7x1hXADcQgcc23+gDtbbUZWVmr3TE7GBcV3j+XUeOk8nHZw3DsYJ46MT2sSfks3QXRl4tBkBad6M+UxW4tb2IQC/4K9IXKhDN8VYXpzrrRHJNvOiH5+NrixthDZHHd/MGNuBDrJFOuXq8L7O6PxBoVZ0NPXmCO5vKyYdhBz5gJ5u3vNkSke7p+cDcvFicsVyNWRaaoFL0UN9gxFVMY8hkJKBZyGxZL2LEDXD44PxPsDnygpDC1Jyc7pkhEA0fKSc9aXZb1HDpwe1hqyyRVBtHkgPiN78GmJImbaUlj8XsK3yVyAjs2XvPR0/3ASHS53ViwzWKL3Oi8I515IJDtDhlvrkeh7MrWMaEu9k6ZQXJi1uJsh3JGfyS+yDlPjePVdsVqnZ4uw9pKMXvjlJeNcIiGM2Cf5S89nk2Qe/56MrKB9Frm6Q5wQHai8TNdCZYBi67dmAX8KHPXvEU4K6KyyW89YGcabZQ3eOJDr4oqW9ZcYPBttREdH3WI/HxvpEq6bqoDhT9AxpIWEMVb6y/DcDHSEresepibug4qOr9xOPq0yk2uiWYhPubFnCk7thQCXOGv9crWnQoOrt9c1qoaWXM4YmKqfcaQ67Tn+uFQmYTZyqw4jlJU4GgKe5/GNCVQM5aNUg1J0Px1NlFCS+rrIDyMQp0byFcgTd/E9sA5d1+YZKHKmJiQwEAz6oU9yyoUlxntSI42GHB/UttPc7Hj14V5+oJ+Yz+CZodmkXFg57Vx4NuxveNtO"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
},
"paymentStatus": {
"0": "-",
"1": "ProformaInvoicePrinted",
"2": "InvoicePrinted",
"4": "Bezahlt",
"8": "OnApproval",
"16": "Kostenlos",
"32": "Mahnung",
"64": "ProformaInvoicePaid",
"128": "Canceled",
"256": "Mahnung",
"512": "PartiallyPaid",
"1024": "Outstanding"
},
"paymentType": {
"0": "-",
"1": "WhenCollecting",
"2": "Kostenlos",
"4": "Bar",
"8": "DirectDebit",
"16": "DebitAdviceMandate",
"32": "DebitCard",
"64": "Kreditkarte",
"128": "Rechnung",
"256": "Vorkasse",
"512": "Voucher",
"1024": "CollectiveInvoice",
"2048": "PayPal",
"4096": "InstantTransfer",
"8192": "BonusCard",
"16384": "AmazonPay"
},
"receiptType": {
"0": "-",
"1": "Lieferschein",
"2": "Gutschrift",
"4": "Sammelgutschrift",
"8": "CollectiveCreditNote",
"16": "Sammellieferschein für Kundenkartenkäufe",
"32": "Sammelgutschrift für Kundenkartenkäufe",
"64": "Zahlungsbeleg",
"128": "Rechnung",
"256": "Sammelrechnung",
"512": "Proformarechnung / Vorauskasse",
"1024": "Bon/Quittung",
"2048": "Rücknahmebeleg"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { BranchDTO } from '@generated/swagger/checkout-api';
import { BranchDTO } from '@swagger/checkout';
import { isBoolean, isNumber } from '@utils/common';
import { BehaviorSubject, Observable } from 'rxjs';
import { first, map, switchMap } from 'rxjs/operators';
@@ -35,9 +35,7 @@ export class ApplicationService {
getProcesses$(section?: 'customer' | 'branch') {
const processes$ = this.store.select(selectProcesses);
return processes$.pipe(
map((processes) => processes.filter((process) => (section ? process.section === section : true))),
);
return processes$.pipe(map((processes) => processes.filter((process) => (section ? process.section === section : true))));
}
getProcessById$(processId: number): Observable<ApplicationProcess> {
@@ -137,10 +135,7 @@ export class ApplicationService {
this.store.dispatch(setSection({ section }));
}
getLastActivatedProcessWithSectionAndType$(
section: 'customer' | 'branch',
type: string,
): Observable<ApplicationProcess> {
getLastActivatedProcessWithSectionAndType$(section: 'customer' | 'branch', type: string): Observable<ApplicationProcess> {
return this.getProcesses$(section).pipe(
map((processes) =>
processes

View File

@@ -11,17 +11,8 @@ export const addProcess = createAction(`${prefix} Add Process`, props<{ process:
export const removeProcess = createAction(`${prefix} Remove Process`, props<{ processId: number }>());
export const setActivatedProcess = createAction(
`${prefix} Set Activated Process`,
props<{ activatedProcessId: number }>(),
);
export const setActivatedProcess = createAction(`${prefix} Set Activated Process`, props<{ activatedProcessId: number }>());
export const patchProcess = createAction(
`${prefix} Patch Process`,
props<{ processId: number; changes: Partial<ApplicationProcess> }>(),
);
export const patchProcess = createAction(`${prefix} Patch Process`, props<{ processId: number; changes: Partial<ApplicationProcess> }>());
export const patchProcessData = createAction(
`${prefix} Patch Process Data`,
props<{ processId: number; data: Record<string, any> }>(),
);
export const patchProcessData = createAction(`${prefix} Patch Process Data`, props<{ processId: number; data: Record<string, any> }>());

View File

@@ -1,18 +1,13 @@
import { coerceArray } from "@angular/cdk/coercion";
import { inject, Injectable } from "@angular/core";
import { Config } from "@core/config";
import { isNullOrUndefined } from "@utils/common";
import { AuthConfig, OAuthService } from "angular-oauth2-oidc";
import { JwksValidationHandler } from "angular-oauth2-oidc-jwks";
import { BehaviorSubject } from "rxjs";
/**
* Storage key for the URL to redirect to after login
*/
const REDIRECT_URL_KEY = "auth_redirect_url";
import { coerceArray, coerceStringArray } from '@angular/cdk/coercion';
import { Injectable } from '@angular/core';
import { Config } from '@core/config';
import { isNullOrUndefined } from '@utils/common';
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
import { asapScheduler, BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: "root",
providedIn: 'root',
})
export class AuthService {
private readonly _initialized = new BehaviorSubject<boolean>(false);
@@ -21,71 +16,49 @@ export class AuthService {
}
private _authConfig: AuthConfig;
constructor(
private _config: Config,
private readonly _oAuthService: OAuthService,
) {
this._oAuthService.events?.subscribe((event) => {
if (event.type === "token_received") {
console.log(
"SSO Token Expiration:",
new Date(this._oAuthService.getAccessTokenExpiration()),
);
// Handle redirect after successful authentication
setTimeout(() => {
const redirectUrl = this._getAndClearRedirectUrl();
if (redirectUrl) {
window.location.href = redirectUrl;
}
}, 100);
if (event.type === 'token_received') {
console.log('SSO Token Expiration:', new Date(this._oAuthService.getAccessTokenExpiration()));
}
});
}
async init() {
if (this._initialized.getValue()) {
throw new Error("AuthService is already initialized");
throw new Error('AuthService is already initialized');
}
this._authConfig = this._config.get("@core/auth");
this._authConfig = this._config.get('@core/auth');
this._authConfig.redirectUri = window.location.origin;
this._authConfig.silentRefreshRedirectUri =
window.location.origin + "/silent-refresh.html";
this._authConfig.silentRefreshRedirectUri = window.location.origin + '/silent-refresh.html';
this._authConfig.useSilentRefresh = true;
this._oAuthService.configure(this._authConfig);
this._oAuthService.tokenValidationHandler = new JwksValidationHandler();
this._oAuthService.setupAutomaticSilentRefresh();
try {
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
} catch (error) {
this.login();
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
throw error;
}
this._initialized.next(true);
}
isAuthenticated() {
return this.isIdTokenValid();
}
isIdTokenValid() {
console.log(
"ID Token Expiration:",
new Date(this._oAuthService.getIdTokenExpiration()),
);
return this._oAuthService.hasValidIdToken();
}
isAccessTokenValid() {
console.log(
"ACCESS Token Expiration:",
new Date(this._oAuthService.getAccessTokenExpiration()),
);
return this._oAuthService.hasValidAccessToken();
}
getToken() {
return this._oAuthService.getAccessToken();
}
@@ -107,31 +80,14 @@ export class AuthService {
if (isNullOrUndefined(token)) {
return null;
}
const base64Url = token.split(".")[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const encoded = window.atob(base64);
return JSON.parse(encoded);
}
/**
* Saves the URL to redirect to after successful login
*/
_saveRedirectUrl(): void {
localStorage.setItem(REDIRECT_URL_KEY, window.location.href);
}
/**
* Gets and clears the saved redirect URL
*/
_getAndClearRedirectUrl(): string | null {
const url = localStorage.getItem(REDIRECT_URL_KEY);
localStorage.removeItem(REDIRECT_URL_KEY);
return url;
}
login() {
this._saveRedirectUrl();
this._oAuthService.initLoginFlow();
}
@@ -143,12 +99,15 @@ export class AuthService {
async logout() {
await this._oAuthService.revokeTokenAndLogout();
// asapScheduler.schedule(() => {
// window.location.reload();
// }, 250);
}
hasRole(role: string | string[]) {
const roles = coerceArray(role);
const userRoles = this.getClaimByKey("role");
const userRoles = this.getClaimByKey('role');
if (isNullOrUndefined(userRoles)) {
return false;
@@ -159,10 +118,7 @@ export class AuthService {
async refresh() {
try {
if (
this._authConfig.responseType.includes("code") &&
this._authConfig.scope.includes("offline_access")
) {
if (this._authConfig.responseType.includes('code') && this._authConfig.scope.includes('offline_access')) {
await this._oAuthService.refreshToken();
} else {
await this._oAuthService.silentRefresh();

View File

@@ -3,7 +3,6 @@ import { AuthService } from './auth.service';
@Directive({
selector: '[ifRole],[ifRoleElse],[ifNotRole],[ifNotRoleElse]',
standalone: false,
})
export class IfRoleDirective implements OnChanges {
@Input()

View File

@@ -1,4 +1,3 @@
export * from './auth.module';
export * from './auth.service';
export * from './if-role.directive';
export * from './login.strategy';

View File

@@ -1,78 +0,0 @@
import { ScanAdapterService } from '@adapter/scan';
import { inject, Injectable } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { EnvironmentService } from '@core/environment';
import { UiConfirmModalComponent, UiModalResult, UiModalService } from '@ui/modal';
import { injectNetworkStatus$ } from '../../app/services';
import { AuthService } from './auth.service';
import { AuthService as IsaAuthService } from '@generated/swagger/isa-api';
import { firstValueFrom, lastValueFrom } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class LoginStrategy {
private readonly environmentService = inject(EnvironmentService);
private readonly networkStatus = toSignal(injectNetworkStatus$());
private readonly modal = inject(UiModalService);
private readonly scanAdapterService = inject(ScanAdapterService);
private readonly isaAuthService = inject(IsaAuthService);
private readonly authService = inject(AuthService);
async login(title = 'Anmeldung') {
console.log('LoginStrategy.login');
let loginModalResult: UiModalResult<boolean>;
if (this.authService.isIdTokenValid()) {
console.log('LoginStrategy.login: idToken is valid');
await this.authService.login();
return;
}
try {
if (this.environmentService.isMobileDevice()) {
console.log('LoginStrategy.login: isMobileDevice');
do {
const loginModal = this.modal.open({
content: UiConfirmModalComponent,
title,
config: {
canClose: false,
backdropClose: false,
},
data: {
message: 'Bitte wählen Sie die Anmeldeoption aus.',
rejectLabel: 'Anmeldung mit Logindaten',
confirmLabel: 'Anmeldung mit Keycard',
},
});
loginModalResult = await loginModal.afterClosed$.toPromise();
} while (this.networkStatus() === 'offline');
}
} catch (error) {}
try {
if (loginModalResult.data) {
const result = await lastValueFrom(this.scanAdapterService.scan());
if (typeof result === 'string') {
const res = await firstValueFrom(
this.isaAuthService.AuthLogin({
code: result,
application: 'isa',
hostname: location.host,
}),
);
if (res.token) {
console.log('LoginStrategy.login: setKeyCardToken', res.token);
this.authService.setKeyCardToken(res.token);
}
}
}
} catch (error) {}
console.log('LoginStrategy.login: login');
await this.authService.login();
}
}

View File

@@ -120,10 +120,7 @@ export class BreadcrumbService {
if (recursive) {
const breadcrumbs = await this.getBreadcrumbByKey$(breadcrumb.key).pipe(take(1)).toPromise();
breadcrumbsToRemove = [
...breadcrumbsToRemove,
...breadcrumbs.filter((crumb) => crumb.timestamp > breadcrumb.timestamp),
];
breadcrumbsToRemove = [...breadcrumbsToRemove, ...breadcrumbs.filter((crumb) => crumb.timestamp > breadcrumb.timestamp)];
}
if (!breadcrumbsToRemove.length) {
@@ -138,10 +135,7 @@ export class BreadcrumbService {
crumbs.forEach((crumb) => this.removeBreadcrumb(crumb.id));
}
getLatestBreadcrumbForSection(
section: 'customer' | 'branch',
predicate: (crumb: Breadcrumb) => boolean = (_) => true,
) {
getLatestBreadcrumbForSection(section: 'customer' | 'branch', predicate: (crumb: Breadcrumb) => boolean = (_) => true) {
return this.store
.select(selectors.selectBreadcrumbsBySection, { section })
.pipe(map((crumbs) => crumbs.sort((a, b) => b.timestamp - a.timestamp).find((f) => predicate(f))));

View File

@@ -27,7 +27,7 @@ export interface Breadcrumb {
/**
* Query Parameter
*/
params?: object;
params?: Object;
/**
* Timestamp

View File

@@ -0,0 +1 @@
describe('Breadcrumb Actions', () => {});

View File

@@ -11,10 +11,7 @@ export const addBreadcrumb = createAction(`${prefix} Add Breadcrumb`, props<{ br
/**
* Action um Breadcrumb im State zu ändern
*/
export const updateBreadcrumb = createAction(
`${prefix} Update Breadcrumb`,
props<{ id: number; changes: Partial<Breadcrumb> }>(),
);
export const updateBreadcrumb = createAction(`${prefix} Update Breadcrumb`, props<{ id: number; changes: Partial<Breadcrumb> }>());
/**
* Action um Breadcrumb im State zu entfernen

View File

@@ -43,10 +43,7 @@ describe('Breadcrumb Reducer', () => {
const state = breadcrumbReducer(INIT, action.addBreadcrumb({ breadcrumb }));
const fixture = breadcrumbReducer(
state,
action.updateBreadcrumb({ id: breadcrumb.id, changes: { name: 'Test Name 2' } }),
);
const fixture = breadcrumbReducer(state, action.updateBreadcrumb({ id: breadcrumb.id, changes: { name: 'Test Name 2' } }));
expect(fixture.entities[breadcrumb.id]).toEqual(expected);
});

View File

@@ -10,46 +10,26 @@ describe('Breadcrumb Selectors', () => {
beforeEach(() => {
state = breadcrumbReducer(
INIT,
action.addBreadcrumb({
breadcrumb: { id: 1, key: 'unit-test-1', path: '', name: 'Unit Test 1', section: 'customer' },
}),
action.addBreadcrumb({ breadcrumb: { id: 1, key: 'unit-test-1', path: '', name: 'Unit Test 1', section: 'customer' } }),
);
state = breadcrumbReducer(
state,
action.addBreadcrumb({
breadcrumb: {
id: 2,
key: 'unit-test-1',
path: '',
name: 'Unit Test 1',
tags: ['details'],
section: 'customer',
},
breadcrumb: { id: 2, key: 'unit-test-1', path: '', name: 'Unit Test 1', tags: ['details'], section: 'customer' },
}),
);
state = breadcrumbReducer(
state,
action.addBreadcrumb({
breadcrumb: { id: 3, key: 'unit-test-2', path: '', name: 'Unit Test 1', section: 'customer' },
}),
action.addBreadcrumb({ breadcrumb: { id: 3, key: 'unit-test-2', path: '', name: 'Unit Test 1', section: 'customer' } }),
);
state = breadcrumbReducer(
state,
action.addBreadcrumb({ breadcrumb: { id: 4, key: 'unit-test-3', path: '', name: 'Unit Test 1', section: 'customer' } }),
);
state = breadcrumbReducer(
state,
action.addBreadcrumb({
breadcrumb: { id: 4, key: 'unit-test-3', path: '', name: 'Unit Test 1', section: 'customer' },
}),
);
state = breadcrumbReducer(
state,
action.addBreadcrumb({
breadcrumb: {
id: 5,
key: 'unit-test-3',
path: '',
name: 'Unit Test 1',
tags: ['details'],
section: 'customer',
},
breadcrumb: { id: 5, key: 'unit-test-3', path: '', name: 'Unit Test 1', tags: ['details'], section: 'customer' },
}),
);
});

View File

@@ -20,9 +20,7 @@ export const selectBreadcrumbById = createSelector(selectEntities, (entities, id
/**
* Gibt alle Breadcrumb Entities als Array zurück die den key enthalten
*/
export const selectBreadcrumbsByKey = createSelector(selectAll, (entities, key: string) =>
entities.filter((crumb) => crumb.key == key),
);
export const selectBreadcrumbsByKey = createSelector(selectAll, (entities, key: string) => entities.filter((crumb) => crumb.key == key));
/**
* Gibt alle Breadcrumb Entities als Array zurück die den key und tag enthalten
@@ -39,15 +37,12 @@ export const selectBreadcrumbsByKeyAndTag = createSelector(
export const selectBreadcrumbsByKeyAndTags = createSelector(
selectAll,
(entities: Breadcrumb[], { key, tags }: { key: string; tags: string[] }) =>
entities.filter(
(crumb) => crumb.key == key && isArray(crumb.tags) && tags.every((tag) => crumb.tags.includes(tag)),
),
entities.filter((crumb) => crumb.key == key && isArray(crumb.tags) && tags.every((tag) => crumb.tags.includes(tag))),
);
/**
* Gibt alle Breadcrumb Entities als Array zurück die die tags enthalten
*/
export const selectBreadcrumbsBySection = createSelector(
selectAll,
(entities: Breadcrumb[], { section }: { section: string }) => entities.filter((crumb) => crumb.section === section),
export const selectBreadcrumbsBySection = createSelector(selectAll, (entities: Breadcrumb[], { section }: { section: string }) =>
entities.filter((crumb) => crumb.section === section),
);

View File

@@ -1,7 +1,7 @@
import { createEntityAdapter, EntityState } from '@ngrx/entity';
import { Breadcrumb } from '../defs';
export type BreadcrumbState = EntityState<Breadcrumb>
export interface BreadcrumbState extends EntityState<Breadcrumb> {}
export const featureName = 'core-breadcrumb';

View File

@@ -34,7 +34,7 @@ export class CacheService {
return 'cache';
}
private getKey(token: object | string): string {
private getKey(token: Object | string): string {
if (typeof token === 'string') {
return this.hash(token);
}
@@ -105,7 +105,7 @@ export class CacheService {
});
}
private async cached(token: object | string): Promise<DbEntry<any> | undefined> {
private async cached(token: Object | string): Promise<DbEntry<any> | undefined> {
const store = await this.getObjectStore();
return new Promise<DbEntry<any> | undefined>((resolve, reject) => {
const request = store.get(this.getKey(token));
@@ -118,7 +118,7 @@ export class CacheService {
});
}
async get<T = any>(token: object | string): Promise<T | undefined> {
async get<T = any>(token: Object | string): Promise<T | undefined> {
const cached = await this.cached(token);
if (!cached) {
@@ -133,7 +133,7 @@ export class CacheService {
return cached.data;
}
async delete(token: object | string): Promise<void> {
async delete(token: Object | string): Promise<void> {
const store = await this.getObjectStore('readwrite');
return new Promise<void>((resolve, reject) => {
const request = store.delete(this.getKey(token));

View File

@@ -4,10 +4,7 @@ import { CommandService } from './command.service';
import { FEATURE_ACTION_HANDLERS, ROOT_ACTION_HANDLERS } from './tokens';
export function provideActionHandlers(actionHandlers: Type<ActionHandler>[]): Provider[] {
return [
CommandService,
actionHandlers.map((handler) => ({ provide: FEATURE_ACTION_HANDLERS, useClass: handler, multi: true })),
];
return [CommandService, actionHandlers.map((handler) => ({ provide: FEATURE_ACTION_HANDLERS, useClass: handler, multi: true }))];
}
@NgModule({})
@@ -15,20 +12,14 @@ export class CoreCommandModule {
static forRoot(actionHandlers: Type<ActionHandler>[]): ModuleWithProviders<CoreCommandModule> {
return {
ngModule: CoreCommandModule,
providers: [
CommandService,
actionHandlers.map((handler) => ({ provide: ROOT_ACTION_HANDLERS, useClass: handler, multi: true })),
],
providers: [CommandService, actionHandlers.map((handler) => ({ provide: ROOT_ACTION_HANDLERS, useClass: handler, multi: true }))],
};
}
static forChild(actionHandlers: Type<ActionHandler>[]): ModuleWithProviders<CoreCommandModule> {
return {
ngModule: CoreCommandModule,
providers: [
CommandService,
actionHandlers.map((handler) => ({ provide: FEATURE_ACTION_HANDLERS, useClass: handler, multi: true })),
],
providers: [CommandService, actionHandlers.map((handler) => ({ provide: FEATURE_ACTION_HANDLERS, useClass: handler, multi: true }))],
};
}
}

View File

@@ -0,0 +1,8 @@
import { Observable } from 'rxjs';
/**
* Config loader interface for loading configurations
*/
export interface ConfigLoader {
load(): Promise<any>;
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './config-loader';
export * from './json.config-loader';
// end:ng42.barrel

View File

@@ -0,0 +1,36 @@
// unit test JsonConfigLoader
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
import { CORE_JSON_CONFIG_LOADER_URL } from '../tokens';
import { JsonConfigLoader } from './json.config-loader';
describe('JsonConfigLoader', () => {
let spectator: SpectatorService<JsonConfigLoader>;
const createService = createServiceFactory({
imports: [HttpClientTestingModule],
service: JsonConfigLoader,
mocks: [],
providers: [{ provide: CORE_JSON_CONFIG_LOADER_URL, useValue: '/assets/config.json' }],
});
let httpTestingController: HttpTestingController;
beforeEach(() => {
spectator = createService();
httpTestingController = spectator.inject(HttpTestingController);
});
it('should create', () => {
expect(spectator.service).toBeTruthy();
});
describe('load', () => {
it('should call the provided url', async () => {
const reqPromise = spectator.service.load();
const req = httpTestingController.expectOne('/assets/config.json');
req.flush({ unit: 'test' });
const result = await reqPromise;
httpTestingController.verify();
expect(result).toEqual({ unit: 'test' });
});
});
});

View File

@@ -0,0 +1,16 @@
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { ConfigLoader } from './config-loader';
import { CORE_JSON_CONFIG_LOADER_URL } from '../tokens';
@Injectable()
export class JsonConfigLoader implements ConfigLoader {
constructor(
@Inject(CORE_JSON_CONFIG_LOADER_URL) private url: string,
private http: HttpClient,
) {}
load(): Promise<any> {
return this.http.get(this.url).toPromise();
}
}

View File

@@ -0,0 +1,7 @@
import { Type } from '@angular/core';
import { ConfigLoader } from './config-loaders';
export interface ConfigModuleOptions {
useConfigLoader: Type<ConfigLoader>;
jsonConfigLoaderUrl?: string;
}

View File

@@ -0,0 +1,28 @@
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
import { CORE_CONFIG_LOADER } from '@core/config';
import { Config } from './config';
import { ConfigModuleOptions } from './config-module-options';
import { CORE_JSON_CONFIG_LOADER_URL } from './tokens';
export function _initializeConfigFactory(config: Config) {
return () => config.init();
}
@NgModule({})
export class ConfigModule {
static forRoot(options: ConfigModuleOptions): ModuleWithProviders<ConfigModule> {
const configLoaderProvider = {
provide: CORE_CONFIG_LOADER,
useClass: options.useConfigLoader,
};
return {
ngModule: ConfigModule,
providers: [
Config,
configLoaderProvider,
options.jsonConfigLoaderUrl ? { provide: CORE_JSON_CONFIG_LOADER_URL, useValue: options.jsonConfigLoaderUrl } : null,
],
};
}
}

View File

@@ -0,0 +1,45 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
import { Config } from './config';
import { ConfigLoader } from './config-loaders';
import { CORE_CONFIG_LOADER } from './tokens';
class TestConfigLoader implements ConfigLoader {
load() {
return Promise.resolve({});
}
}
// Unit test Config
describe('Config', () => {
let spectator: SpectatorService<Config>;
const createService = createServiceFactory({
service: Config,
providers: [{ provide: CORE_CONFIG_LOADER, useClass: TestConfigLoader }],
});
let configLoader: ConfigLoader;
beforeEach(() => {
spectator = createService();
configLoader = spectator.inject(CORE_CONFIG_LOADER);
});
it('should create', () => {
expect(spectator.service).toBeTruthy();
});
describe('init()', () => {
it('should load config and assigns it to _config', async () => {
const config = { unit: 'test' };
spyOn(configLoader, 'load').and.returnValue(Promise.resolve(config));
await spectator.service.init();
expect(spectator.service['_config']).toEqual(config);
});
});
describe('get()', () => {
it('should return config value', () => {
spectator.service['_config'] = { test: 'test' };
expect(spectator.service.get('test')).toEqual('test');
});
});
});

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