diff --git a/.github/instructions/nx.instructions.md b/.github/instructions/nx.instructions.md index 260ba097d..47ad164db 100644 --- a/.github/instructions/nx.instructions.md +++ b/.github/instructions/nx.instructions.md @@ -1,40 +1,41 @@ ---- -applyTo: '**' ---- - -// This file is automatically generated by Nx Console - -You are in an nx workspace using Nx 21.3.2 and npm as the package manager. - -You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user: - -# General Guidelines -- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture -- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration -- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors -- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool - -# Generation Guidelines -If the user wants to generate something, use the following flow: - -- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable -- get the available generators using the 'nx_generators' tool -- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them -- get generator details using the 'nx_generator_schema' tool -- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure -- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic -- open the generator UI using the 'nx_open_generate_ui' tool -- wait for the user to finish the generator -- read the generator log file using the 'nx_read_generator_log' tool -- use the information provided in the log file to answer the user's question or continue with what they were doing - -# Running Tasks Guidelines -If the user wants help with tasks or commands (which include keywords like "test", "build", "lint", or other similar actions), use the following flow: -- Use the 'nx_current_running_tasks_details' tool to get the list of tasks (this can include tasks that were completed, stopped or failed). -- If there are any tasks, ask the user if they would like help with a specific task then use the 'nx_current_running_task_output' tool to get the terminal output for that task/command -- Use the terminal output from 'nx_current_running_task_output' to see what's wrong and help the user fix their problem. Use the appropriate tools if necessary -- If the user would like to rerun the task or command, always use `nx run ` to rerun in the terminal. This will ensure that the task will run in the nx context and will be run the same way it originally executed -- If the task was marked as "continuous" do not offer to rerun the task. This task is already running and the user can see the output in the terminal. You can use 'nx_current_running_task_output' to get the output of the task to verify the output. - - - +--- +applyTo: '**' +--- + +// This file is automatically generated by Nx Console + +You are in an nx workspace using Nx 21.3.2 and npm as the package manager. + +You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user: + +# General Guidelines + +- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture +- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration +- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors +- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool + +# Generation Guidelines + +If the user wants to generate something, use the following flow: + +- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable +- get the available generators using the 'nx_generators' tool +- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them +- get generator details using the 'nx_generator_schema' tool +- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure +- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic +- open the generator UI using the 'nx_open_generate_ui' tool +- wait for the user to finish the generator +- read the generator log file using the 'nx_read_generator_log' tool +- use the information provided in the log file to answer the user's question or continue with what they were doing + +# Running Tasks Guidelines + +If the user wants help with tasks or commands (which include keywords like "test", "build", "lint", or other similar actions), use the following flow: + +- Use the 'nx_current_running_tasks_details' tool to get the list of tasks (this can include tasks that were completed, stopped or failed). +- If there are any tasks, ask the user if they would like help with a specific task then use the 'nx_current_running_task_output' tool to get the terminal output for that task/command +- Use the terminal output from 'nx_current_running_task_output' to see what's wrong and help the user fix their problem. Use the appropriate tools if necessary +- If the user would like to rerun the task or command, always use `nx run ` to rerun in the terminal. This will ensure that the task will run in the nx context and will be run the same way it originally executed +- If the task was marked as "continuous" do not offer to rerun the task. This task is already running and the user can see the output in the terminal. You can use 'nx_current_running_task_output' to get the output of the task to verify the output. diff --git a/apps/isa-app/src/app/providers/isa.error-handler.ts b/apps/isa-app/src/app/providers/isa.error-handler.ts index cd5d67acd..5f6210520 100644 --- a/apps/isa-app/src/app/providers/isa.error-handler.ts +++ b/apps/isa-app/src/app/providers/isa.error-handler.ts @@ -1,11 +1,16 @@ -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, @@ -17,7 +22,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; } @@ -26,11 +31,14 @@ 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(); @@ -39,7 +47,11 @@ export class IsaErrorHandler implements ErrorHandler { return; } - this._isaLogProvider.log(LogLevel.ERROR, 'Client Error', error); + try { + this._isaLogProvider.log(LogLevel.ERROR, "Client Error", error); + } catch (logError) { + console.error("Error logging to IsaLogProvider:", logError); + } // this._modal.open({ // content: UiErrorModalComponent, diff --git a/apps/isa-app/src/app/providers/isa.log-provider.ts b/apps/isa-app/src/app/providers/isa.log-provider.ts index cbe728730..668eebc1c 100644 --- a/apps/isa-app/src/app/providers/isa.log-provider.ts +++ b/apps/isa-app/src/app/providers/isa.log-provider.ts @@ -1,28 +1,36 @@ -import { Injectable, Injector } from '@angular/core'; -import { LogLevel, LogProvider } from '@core/logger'; -import { UserStateService } from '@generated/swagger/isa-api'; -import { environment } from '../../environments/environment'; +import { Injectable } from "@angular/core"; +import { LogLevel, LogProvider } from "@core/logger"; +import { UserStateService } from "@generated/swagger/isa-api"; +import { environment } from "../../environments/environment"; -@Injectable({ providedIn: 'root' }) +@Injectable({ providedIn: "root" }) export class IsaLogProvider implements LogProvider { static InfoService: UserStateService | undefined; - 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(() => {}); + 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); } } } diff --git a/apps/isa-app/src/core/logger/log.provider.ts b/apps/isa-app/src/core/logger/log.provider.ts index e66f74464..303535616 100644 --- a/apps/isa-app/src/core/logger/log.provider.ts +++ b/apps/isa-app/src/core/logger/log.provider.ts @@ -1,5 +1,4 @@ -import { Injectable } from '@angular/core'; -import { LogLevel } from './log-level'; +import { LogLevel } from "./log-level"; export interface LogProvider { log(logLevel: LogLevel, message: string, ...optionalParams: any[]): void; diff --git a/libs/catalogue/data-access/src/lib/index.ts b/libs/catalogue/data-access/src/lib/index.ts new file mode 100644 index 000000000..4ef7a2f7f --- /dev/null +++ b/libs/catalogue/data-access/src/lib/index.ts @@ -0,0 +1,3 @@ +export * from './models'; +export * from './schemas'; +export * from './services'; diff --git a/libs/common/data-access/src/lib/index.ts b/libs/common/data-access/src/lib/index.ts index c3d24fa2c..b223bac86 100644 --- a/libs/common/data-access/src/lib/index.ts +++ b/libs/common/data-access/src/lib/index.ts @@ -1,2 +1,4 @@ -export * from './errors'; -export * from './models'; +export * from './errors'; +export * from './helpers'; +export * from './models'; +export * from './operators'; diff --git a/libs/oms/data-access/src/lib/index.ts b/libs/oms/data-access/src/lib/index.ts new file mode 100644 index 000000000..022fa20ff --- /dev/null +++ b/libs/oms/data-access/src/lib/index.ts @@ -0,0 +1,8 @@ +export * from './errors'; +export * from './guards'; +export * from './models'; +export * from './operators'; +export * from './questions'; +export * from './schemas'; +export * from './services'; +export * from './stores'; diff --git a/libs/oms/data-access/src/lib/services/return-details.service.ts b/libs/oms/data-access/src/lib/services/return-details.service.ts index 161566ccf..cb658a5cc 100644 --- a/libs/oms/data-access/src/lib/services/return-details.service.ts +++ b/libs/oms/data-access/src/lib/services/return-details.service.ts @@ -116,7 +116,7 @@ export class ReturnDetailsService { * Validates that the email parameter is a properly formatted email address. */ static FetchReceiptsEmailParamsSchema = z.object({ - email: z.string().email(), + email: z.string(), }); /** diff --git a/libs/oms/feature/return-details/src/lib/return-details.component.html b/libs/oms/feature/return-details/src/lib/return-details.component.html index d03394bed..36c7b37db 100644 --- a/libs/oms/feature/return-details/src/lib/return-details.component.html +++ b/libs/oms/feature/return-details/src/lib/return-details.component.html @@ -22,7 +22,7 @@ > @if (customerReceiptsResource.isLoading()) { - } @else { + } @else if (!customerReceiptsResource.error()) { @for (receipt of customerReceiptsResource.value(); track receipt.id) { @if (r.id !== receipt.id) { ({ - component: 'ReturnDetailsComponent', - itemId: this.receiptId(), - processId: this.processId(), - params: this.params(), - })); - #store = inject(ReturnDetailsStore); - #returnDetailsService = inject(ReturnDetailsService); - #returnProcessStore = inject(ReturnProcessStore); - - private processId = injectActivatedTabId(); - - private _router = inject(Router); - - private _activatedRoute = inject(ActivatedRoute); - - location = inject(Location); - - params = toSignal(this._activatedRoute.params); - - receiptId = computed(() => { - const params = this.params(); - if (params) { - return z.coerce.number().parse(params['receiptId']); - } - throw new Error('No receiptId found in route params'); - }); - - receiptResource = this.#store.receiptResource(this.receiptId); - - customerReceiptsResource = resource({ - params: this.receiptResource.value, - loader: async ({ params, abortSignal }) => { - const email = params.buyer?.communicationDetails?.email; - if (!email) { - return []; - } - return await this.#returnDetailsService.fetchReceiptsByEmail( - { email }, - abortSignal, - ); - }, - }); - - canStartProcess = computed(() => { - return ( - this.#store.selectedItemIds().length > 0 && this.processId() !== undefined - ); - }); - - startProcess() { - if (!this.canStartProcess()) { - this.#logger.warn( - 'Cannot start process: No items selected or no process ID', - ); - return; - } - - const processId = this.processId(); - const selectedItems = this.#store.selectedItems(); - const selectedQuantites = this.#store.selectedQuantityMap(); - const selectedProductCategories = this.#store.itemCategoryMap(); - - this.#logger.info('Starting return process', () => ({ - processId: processId, - selectedItems: selectedItems.map((item) => item.id), - })); - - if (!selectedItems.length || !processId) { - return; - } - - const itemsGrouptByReceiptId = groupBy( - selectedItems, - (item) => item.receipt?.id, - ); - const receipts = this.#store.receiptsEntityMap(); - - const returns = Object.entries(itemsGrouptByReceiptId).map( - ([receiptId, items]) => ({ - receipt: receipts[Number(receiptId)], - items: items.map((item) => { - const receiptItem = item; - return { - receiptItem, - quantity: selectedQuantites[receiptItem.id], - category: selectedProductCategories[receiptItem.id], - }; - }), - }), - ); - - this.#logger.info('Starting return process with returns', () => ({ - processId, - returns, - })); - - this.#returnProcessStore.startProcess({ - processId, - returns, - }); - - this._router.navigate(['../../', 'process'], { - relativeTo: this._activatedRoute, - }); - } -} +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + resource, +} from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { z } from 'zod'; + +import { NgIconComponent, provideIcons } from '@ng-icons/core'; +import { isaActionChevronLeft } from '@isa/icons'; +import { ButtonComponent } from '@isa/ui/buttons'; +import { injectActivatedTabId } from '@isa/core/tabs'; +import { Location } from '@angular/common'; +import { ExpandableDirectives } from '@isa/ui/expandable'; +import { ProgressBarComponent } from '@isa/ui/progress-bar'; +import { + ReturnDetailsService, + ReturnProcessStore, + ReturnDetailsStore, +} from '@isa/oms/data-access'; +import { ReturnDetailsStaticComponent } from './return-details-static/return-details-static.component'; +import { ReturnDetailsLazyComponent } from './return-details-lazy/return-details-lazy.component'; +import { logger } from '@isa/core/logging'; +import { groupBy } from 'lodash'; + +@Component({ + selector: 'oms-feature-return-details', + templateUrl: './return-details.component.html', + styleUrls: ['./return-details.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + ReturnDetailsStaticComponent, + ReturnDetailsLazyComponent, + NgIconComponent, + ButtonComponent, + ExpandableDirectives, + ProgressBarComponent, + ], + providers: [provideIcons({ isaActionChevronLeft }), ReturnDetailsStore], +}) +export class ReturnDetailsComponent { + #logger = logger(() => ({ + component: 'ReturnDetailsComponent', + itemId: this.receiptId(), + processId: this.processId(), + params: this.params(), + })); + #store = inject(ReturnDetailsStore); + #returnDetailsService = inject(ReturnDetailsService); + #returnProcessStore = inject(ReturnProcessStore); + + private processId = injectActivatedTabId(); + + private _router = inject(Router); + + private _activatedRoute = inject(ActivatedRoute); + + location = inject(Location); + + params = toSignal(this._activatedRoute.params); + + receiptId = computed(() => { + const params = this.params(); + if (params) { + return z.coerce.number().parse(params['receiptId']); + } + throw new Error('No receiptId found in route params'); + }); + + receiptResource = this.#store.receiptResource(this.receiptId); + + customerReceiptsResource = resource({ + params: this.receiptResource.value, + loader: async ({ params, abortSignal }) => { + const email = params.buyer?.communicationDetails?.email; + if (!email) { + return []; + } + + try { + return await this.#returnDetailsService.fetchReceiptsByEmail( + { email }, + abortSignal, + ); + } catch (error) { + this.#logger.error('Failed to fetch customer receipts', error); + return []; + } + }, + }); + + canStartProcess = computed(() => { + return ( + this.#store.selectedItemIds().length > 0 && this.processId() !== undefined + ); + }); + + startProcess() { + if (!this.canStartProcess()) { + this.#logger.warn( + 'Cannot start process: No items selected or no process ID', + ); + return; + } + + const processId = this.processId(); + const selectedItems = this.#store.selectedItems(); + const selectedQuantites = this.#store.selectedQuantityMap(); + const selectedProductCategories = this.#store.itemCategoryMap(); + + this.#logger.info('Starting return process', () => ({ + processId: processId, + selectedItems: selectedItems.map((item) => item.id), + })); + + if (!selectedItems.length || !processId) { + return; + } + + const itemsGrouptByReceiptId = groupBy( + selectedItems, + (item) => item.receipt?.id, + ); + const receipts = this.#store.receiptsEntityMap(); + + const returns = Object.entries(itemsGrouptByReceiptId).map( + ([receiptId, items]) => ({ + receipt: receipts[Number(receiptId)], + items: items.map((item) => { + const receiptItem = item; + return { + receiptItem, + quantity: selectedQuantites[receiptItem.id], + category: selectedProductCategories[receiptItem.id], + }; + }), + }), + ); + + this.#logger.info('Starting return process with returns', () => ({ + processId, + returns, + })); + + this.#returnProcessStore.startProcess({ + processId, + returns, + }); + + this._router.navigate(['../../', 'process'], { + relativeTo: this._activatedRoute, + }); + } +}