Merged PR 1893: feat(remission): add remission processed hint component and update schemas

feat(remission): add remission processed hint component and update schemas

- Introduced RemissionProcessedHintComponent to display hints based on remission processing status.
- Updated fetch-remission-return-receipts schema to include parameters for completed returns.
- Refactored remission return receipt service to handle completed and incomplete returns separately.
- Adjusted remission list component to utilize the new hint component and updated data fetching logic.

Refs: #5240 #5136
This commit is contained in:
Lorenz Hilpert
2025-07-28 08:30:04 +00:00
committed by Nino Righi
parent 71e9a6da0e
commit 72bcacefb6
9 changed files with 132 additions and 79 deletions

View File

@@ -0,0 +1,14 @@
import z from 'zod';
export const FetchRemissionReturnReceiptsSchema = z.object({
returncompleted: z.boolean(),
start: z.coerce.date().optional(),
});
export type FetchRemissionReturnReceipts = z.infer<
typeof FetchRemissionReturnReceiptsSchema
>;
export type FetchRemissionReturnReceiptsParams = z.input<
typeof FetchRemissionReturnReceiptsSchema
>;

View File

@@ -1,9 +1,10 @@
export * from './fetch-query-settings.schema';
export * from './fetch-remission-return-receipt.schema';
export * from './fetch-stock-in-stock.schema';
export * from './query-token.schema';
export * from './create-return.schema';
export * from './create-receipt.schema';
export * from './assign-package.schema';
export * from './add-return-item.schema';
export * from './add-return-suggestion.schema';
export * from './assign-package.schema';
export * from './create-receipt.schema';
export * from './create-return.schema';
export * from './fetch-query-settings.schema';
export * from './fetch-remission-return-receipt.schema';
export * from './fetch-remission-return-receipts.schema';
export * from './fetch-stock-in-stock.schema';
export * from './query-token.schema';

View File

@@ -16,6 +16,8 @@ import {
CreateReturnSchema,
FetchRemissionReturnParams,
FetchRemissionReturnReceiptSchema,
FetchRemissionReturnReceiptsParams,
FetchRemissionReturnReceiptsSchema,
} from '../schemas';
import {
Receipt,
@@ -67,23 +69,27 @@ export class RemissionReturnReceiptService {
* .fetchCompletedRemissionReturnReceipts(controller.signal);
*/
async fetchRemissionReturnReceipts(
params: FetchRemissionReturnReceiptsParams,
abortSignal?: AbortSignal,
): Promise<Return[]> {
this.#logger.debug('Fetching completed remission return receipts');
const { start, returncompleted } =
FetchRemissionReturnReceiptsSchema.parse(params);
const assignedStock =
await this.#remissionStockService.fetchAssignedStock(abortSignal);
this.#logger.info('Fetching completed returns from API', () => ({
stockId: assignedStock.id,
startDate: subDays(new Date(), 7).toISOString(),
startDate: start?.toISOString(),
}));
let req$ = this.#returnService.ReturnQueryReturns({
stockId: assignedStock.id,
queryToken: {
input: { returncompleted: 'true' },
start: subDays(new Date(), 7).toISOString(),
filter: { returncompleted: returncompleted ? 'true' : 'false' },
start: start?.toISOString(),
eagerLoading: 3,
},
});
@@ -111,64 +117,6 @@ export class RemissionReturnReceiptService {
return returns;
}
/**
* Fetches all incomplete remission return receipts for the assigned stock.
* Returns receipts not yet marked as completed within the last 7 days.
*
* @async
* @param {AbortSignal} [abortSignal] - Optional signal to abort the request
* @returns {Promise<Return[]>} Array of incomplete return objects with receipts
* @throws {ResponseArgsError} When the API request fails
*
* @example
* const incompleteReturns = await service
* .fetchIncompletedRemissionReturnReceipts();
*/
// async fetchIncompletedRemissionReturnReceipts(
// abortSignal?: AbortSignal,
// ): Promise<Return[]> {
// this.#logger.debug('Fetching incomplete remission return receipts');
// const assignedStock =
// await this.#remissionStockService.fetchAssignedStock(abortSignal);
// this.#logger.info('Fetching incomplete returns from API', () => ({
// stockId: assignedStock.id,
// startDate: subDays(new Date(), 7).toISOString(),
// }));
// let req$ = this.#returnService.ReturnQueryReturns({
// stockId: assignedStock.id,
// queryToken: {
// input: { returncompleted: 'false' },
// start: subDays(new Date(), 7).toISOString(),
// eagerLoading: 3,
// },
// });
// if (abortSignal) {
// this.#logger.debug('Request configured with abort signal');
// req$ = req$.pipe(takeUntilAborted(abortSignal));
// }
// const res = await firstValueFrom(req$);
// if (res?.error) {
// this.#logger.error(
// 'Failed to fetch incomplete returns',
// new Error(res.message || 'Unknown error'),
// );
// throw new ResponseArgsError(res);
// }
// const returns = (res?.result as Return[]) || [];
// this.#logger.debug('Successfully fetched incomplete returns', () => ({
// returnCount: returns.length,
// }));
// return returns;
// }
/**
* Fetches a specific remission return receipt by receipt and return IDs.
* Validates parameters using FetchRemissionReturnReceiptSchema before making the request.

View File

@@ -1,3 +1,5 @@
<remi-remission-processed-hint></remi-remission-processed-hint>
@if (!remissionStarted()) {
<remi-feature-remission-start-card></remi-feature-remission-start-card>
} @else {

View File

@@ -44,6 +44,7 @@ import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-i
import { RemissionListType } from '@isa/remission/data-access';
import { RemissionReturnCardComponent } from './remission-return-card/remission-return-card.component';
import { logger } from '@isa/core/logging';
import { RemissionProcessedHintComponent } from './remission-processed-hint/remission-processed-hint.component';
function querySettingsFactory() {
return inject(ActivatedRoute).snapshot.data['querySettings'];
@@ -83,6 +84,7 @@ function querySettingsFactory() {
RemissionListItemComponent,
IconButtonComponent,
StatefulButtonComponent,
RemissionProcessedHintComponent,
],
host: {
'[class]':

View File

@@ -0,0 +1,10 @@
@if (showHint() && !isLoading()) {
<ng-icon name="isaOtherInfo" size="1.5rem"></ng-icon>
<span class="mt-[2px]">
Die Remissionsliste wurde seit {{ daysThreshold() }} Tagen nicht bearbeitet
</span>
} @else if (error()) {
<button (click)="retry()" class="text-isa-accent-red hover:underline">
Fehler beim Laden. Erneut versuchen
</button>
}

View File

@@ -0,0 +1,8 @@
:host {
@apply flex items-start gap-1 justify-start;
@apply text-isa-accent-red isa-text-body-2-bold;
&:empty {
@apply hidden;
}
}

View File

@@ -0,0 +1,63 @@
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
input,
resource,
} from '@angular/core';
import { isaOtherInfo } from '@isa/icons';
import { RemissionReturnReceiptService } from '@isa/remission/data-access';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { subDays } from 'date-fns';
@Component({
selector: 'remi-remission-processed-hint',
templateUrl: './remission-processed-hint.component.html',
styleUrls: ['./remission-processed-hint.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [NgIcon],
providers: [provideIcons({ isaOtherInfo })],
})
export class RemissionProcessedHintComponent {
readonly #remissionReturnReceiptService = inject(
RemissionReturnReceiptService,
);
/**
* Threshold in days where at least one remission has been processed.
* If no remission has been processed in the last 7 days,
* the hint will be displayed.
*/
readonly daysThreshold = input<number>(7);
/**
* Resource that fetches remission return receipts.
* Automatically loads when the component is initialized.
*/
readonly returnReceiptsResource = resource({
params: () => ({
returncompleted: false,
start: subDays(new Date(), this.daysThreshold()),
}),
loader: ({ params, abortSignal }) =>
this.#remissionReturnReceiptService.fetchRemissionReturnReceipts(
params,
abortSignal,
),
});
readonly showHint = computed(() => {
const receipts = this.returnReceiptsResource.value();
return receipts && receipts.length === 0;
});
readonly error = this.returnReceiptsResource.error;
readonly isLoading = this.returnReceiptsResource.isLoading;
readonly retry = () => {
this.returnReceiptsResource.reload();
};
}

View File

@@ -19,7 +19,7 @@ import {
FilterService,
} from '@isa/shared/filter';
import { RouterLink } from '@angular/router';
import { compareAsc, compareDesc } from 'date-fns';
import { compareAsc, compareDesc, subDays } from 'date-fns';
import { RETURN_RECEIPT_QUERY_SETTINGS } from './remission-return-receipt-list.query-settings';
/**
@@ -64,19 +64,25 @@ export class RemissionReturnReceiptListComponent {
* Resource that fetches completed remission return receipts.
* Automatically loads when the component is initialized.
*/
remissionReturnsResource = resource({
loader: () =>
this.#remissionReturnReceiptService.fetchRemissionReturnReceipts(),
completedRemissionReturnsResource = resource({
loader: ({ abortSignal }) =>
this.#remissionReturnReceiptService.fetchRemissionReturnReceipts(
{ returncompleted: true, start: subDays(new Date(), 7) },
abortSignal,
),
});
/**
* Resource that fetches incomplete remission return receipts.
* Automatically loads when the component is initialized.
*/
// incompletedRemissionReturnsResource = resource({
// loader: () =>
// this.#remissionReturnReceiptService.fetchIncompletedRemissionReturnReceipts(),
// });
incompletedRemissionReturnsResource = resource({
loader: ({ abortSignal }) =>
this.#remissionReturnReceiptService.fetchRemissionReturnReceipts(
{ returncompleted: false },
abortSignal,
),
});
/**
* Computed signal that combines completed and incomplete returns.
@@ -85,9 +91,8 @@ export class RemissionReturnReceiptListComponent {
* @returns {Array<[Return, Receipt]>} Array of tuples containing return and receipt pairs
*/
returns = computed(() => {
const returns = this.remissionReturnsResource.value() || [];
let completed = returns.filter((ret) => ret.completed);
let incompleted = returns.filter((ret) => !ret.completed);
let completed = this.completedRemissionReturnsResource.value() || [];
let incompleted = this.incompletedRemissionReturnsResource.value() || [];
const orderBy = this.orderDateBy();
if (orderBy) {