Merged PR 1888: fix: improve sorting of remission return receipts

fix: improve sorting of remission return receipts

- Refactor data fetching to use a single API call for all returns
- Apply sorting separately to completed and incomplete returns
- Fix template tracking to use index instead of potentially undefined ID
- Remove redundant API calls for incomplete returns

This ensures proper sorting of remission return receipts while maintaining
the separation between completed and incomplete items in the display order.

Ref: #5224
This commit is contained in:
Lorenz Hilpert
2025-07-18 06:39:44 +00:00
committed by Nino Righi
parent 442670bdd0
commit 598df7d5ed
3 changed files with 69 additions and 66 deletions

View File

@@ -50,7 +50,7 @@ export class RemissionReturnReceiptService {
* const completedReturns = await service * const completedReturns = await service
* .fetchCompletedRemissionReturnReceipts(controller.signal); * .fetchCompletedRemissionReturnReceipts(controller.signal);
*/ */
async fetchCompletedRemissionReturnReceipts( async fetchRemissionReturnReceipts(
abortSignal?: AbortSignal, abortSignal?: AbortSignal,
): Promise<Return[]> { ): Promise<Return[]> {
this.#logger.debug('Fetching completed remission return receipts'); this.#logger.debug('Fetching completed remission return receipts');
@@ -108,50 +108,50 @@ export class RemissionReturnReceiptService {
* const incompleteReturns = await service * const incompleteReturns = await service
* .fetchIncompletedRemissionReturnReceipts(); * .fetchIncompletedRemissionReturnReceipts();
*/ */
async fetchIncompletedRemissionReturnReceipts( // async fetchIncompletedRemissionReturnReceipts(
abortSignal?: AbortSignal, // abortSignal?: AbortSignal,
): Promise<Return[]> { // ): Promise<Return[]> {
this.#logger.debug('Fetching incomplete remission return receipts'); // this.#logger.debug('Fetching incomplete remission return receipts');
const assignedStock = // const assignedStock =
await this.#remissionStockService.fetchAssignedStock(abortSignal); // await this.#remissionStockService.fetchAssignedStock(abortSignal);
this.#logger.info('Fetching incomplete returns from API', () => ({ // this.#logger.info('Fetching incomplete returns from API', () => ({
stockId: assignedStock.id, // stockId: assignedStock.id,
startDate: subDays(new Date(), 7).toISOString(), // startDate: subDays(new Date(), 7).toISOString(),
})); // }));
let req$ = this.#returnService.ReturnQueryReturns({ // let req$ = this.#returnService.ReturnQueryReturns({
stockId: assignedStock.id, // stockId: assignedStock.id,
queryToken: { // queryToken: {
input: { returncompleted: 'false' }, // input: { returncompleted: 'false' },
start: subDays(new Date(), 7).toISOString(), // start: subDays(new Date(), 7).toISOString(),
eagerLoading: 3, // eagerLoading: 3,
}, // },
}); // });
if (abortSignal) { // if (abortSignal) {
this.#logger.debug('Request configured with abort signal'); // this.#logger.debug('Request configured with abort signal');
req$ = req$.pipe(takeUntilAborted(abortSignal)); // req$ = req$.pipe(takeUntilAborted(abortSignal));
} // }
const res = await firstValueFrom(req$); // const res = await firstValueFrom(req$);
if (res?.error) { // if (res?.error) {
this.#logger.error( // this.#logger.error(
'Failed to fetch incomplete returns', // 'Failed to fetch incomplete returns',
new Error(res.message || 'Unknown error'), // new Error(res.message || 'Unknown error'),
); // );
throw new ResponseArgsError(res); // throw new ResponseArgsError(res);
} // }
const returns = (res?.result as Return[]) || []; // const returns = (res?.result as Return[]) || [];
this.#logger.debug('Successfully fetched incomplete returns', () => ({ // this.#logger.debug('Successfully fetched incomplete returns', () => ({
returnCount: returns.length, // returnCount: returns.length,
})); // }));
return returns; // return returns;
} // }
/** /**
* Fetches a specific remission return receipt by receipt and return IDs. * Fetches a specific remission return receipt by receipt and return IDs.

View File

@@ -3,7 +3,7 @@
</div> </div>
<div class="grid grid-flow-rows grid-cols-1 gap-4"> <div class="grid grid-flow-rows grid-cols-1 gap-4">
@for (remissionReturn of returns(); track remissionReturn[1].id) { @for (remissionReturn of returns(); track $index) {
<a [routerLink]="[remissionReturn[0].id, remissionReturn[1].id]"> <a [routerLink]="[remissionReturn[0].id, remissionReturn[1].id]">
<remi-return-receipt-list-item <remi-return-receipt-list-item
[remissionReturn]="remissionReturn[0]" [remissionReturn]="remissionReturn[0]"

View File

@@ -64,19 +64,19 @@ export class RemissionReturnReceiptListComponent {
* Resource that fetches completed remission return receipts. * Resource that fetches completed remission return receipts.
* Automatically loads when the component is initialized. * Automatically loads when the component is initialized.
*/ */
completedRemissionReturnsResource = resource({ remissionReturnsResource = resource({
loader: () => loader: () =>
this.#remissionReturnReceiptService.fetchCompletedRemissionReturnReceipts(), this.#remissionReturnReceiptService.fetchRemissionReturnReceipts(),
}); });
/** /**
* Resource that fetches incomplete remission return receipts. * Resource that fetches incomplete remission return receipts.
* Automatically loads when the component is initialized. * Automatically loads when the component is initialized.
*/ */
incompletedRemissionReturnsResource = resource({ // incompletedRemissionReturnsResource = resource({
loader: () => // loader: () =>
this.#remissionReturnReceiptService.fetchIncompletedRemissionReturnReceipts(), // this.#remissionReturnReceiptService.fetchIncompletedRemissionReturnReceipts(),
}); // });
/** /**
* Computed signal that combines completed and incomplete returns. * Computed signal that combines completed and incomplete returns.
@@ -85,32 +85,35 @@ export class RemissionReturnReceiptListComponent {
* @returns {Array<[Return, Receipt]>} Array of tuples containing return and receipt pairs * @returns {Array<[Return, Receipt]>} Array of tuples containing return and receipt pairs
*/ */
returns = computed(() => { returns = computed(() => {
const completed = this.completedRemissionReturnsResource.value() || []; const returns = this.remissionReturnsResource.value() || [];
const incompleted = this.incompletedRemissionReturnsResource.value() || []; let completed = returns.filter((ret) => ret.completed);
let incompleted = returns.filter((ret) => !ret.completed);
const orderBy = this.orderDateBy(); const orderBy = this.orderDateBy();
const allReturnReceiptTuples = [...incompleted, ...completed].flatMap( if (orderBy) {
(ret) => const compareFn = (a: string | undefined, b: string | undefined) => {
ret.receipts if (a === undefined) return 1;
.filter((rec) => rec.data != null) if (b === undefined) return -1;
.map((rec) => [ret, rec.data] as [Return, Receipt]), return (orderBy.dir === 'desc' ? compareDesc : compareAsc)(a, b);
); };
if (!orderBy) { const orderByField = orderBy.by as 'created' | 'completed';
return allReturnReceiptTuples; completed = orderByKey(completed, orderByField, compareFn);
incompleted = orderByKey(incompleted, orderByField, compareFn);
} }
const orderByField = orderBy.by as 'created' | 'completed'; return [...incompleted, ...completed].flatMap((ret) =>
const compareFn = orderBy.dir === 'desc' ? compareDesc : compareAsc; ret.receipts
.filter((rec) => rec.data != null)
return allReturnReceiptTuples.sort((a, b) => { .map((rec) => [ret, rec.data] as [Return, Receipt]),
const dateA = a[1][orderByField]; );
const dateB = b[1][orderByField];
if (!dateA) return -1;
if (!dateB) return 1;
return compareFn(dateA, dateB);
});
}); });
} }
function orderByKey<T, K extends keyof T>(
items: T[],
by: K,
compareFn: T[K] extends infer U ? (a: U, b: U) => number : never,
): T[] {
return [...items].sort((a, b) => compareFn(a[by], b[by]) ?? 0);
}