Merged PR 1900: feat(remission): add getStockToRemit helper and improve stock calculation logic

feat(remission): add getStockToRemit helper and improve stock calculation logic

Add new getStockToRemit helper function that handles different remission list types
(Pflicht and Abteilung) for calculating stock to remit. Refactor existing logic
to use the centralized helper instead of duplicated calculation code.

Changes:
- Add getStockToRemit function to handle RemissionListType-specific logic
- Update calculateStockToRemit to use strict undefined check for predefinedReturnQuantity
- Refactor RemissionListItemComponent to use getStockToRemit helper
- Update RemissionListComponent to use getStockToRemit for consistent calculations
- Add comprehensive test coverage for both helper functions

This centralizes stock calculation logic and ensures consistent behavior
across all remission components.

Ref: #5252
This commit is contained in:
Nino Righi
2025-07-30 12:00:08 +00:00
committed by Andreas Schickinger
parent b39abe630d
commit 244984b6cf
4 changed files with 117 additions and 75 deletions

View File

@@ -1,39 +1,62 @@
import { calculateStockToRemit } from './calc-stock-to-remit.helper';
import {
calculateStockToRemit,
getStockToRemit,
} from './calc-stock-to-remit.helper';
import { RemissionListType } from '@isa/remission/data-access';
describe('calculateStockToRemit', () => {
it('should return predefinedReturnQuantity if set', () => {
const input = {
it('should return predefinedReturnQuantity when provided', () => {
const result = calculateStockToRemit({
availableStock: 10,
predefinedReturnQuantity: 5,
remainingQuantityInStock: 2,
};
const result = calculateStockToRemit(input);
});
expect(result).toBe(5);
});
it('should calculate as availableStock - remainingQuantityInStock if predefinedReturnQuantity is not set', () => {
const input = {
it('should calculate availableStock minus remainingQuantityInStock when no predefinedReturnQuantity', () => {
const result = calculateStockToRemit({
availableStock: 10,
remainingQuantityInStock: 3,
};
const result = calculateStockToRemit(input);
});
expect(result).toBe(7);
});
});
it('should return 0 if result is negative', () => {
const input = {
availableStock: 2,
remainingQuantityInStock: 5,
};
const result = calculateStockToRemit(input);
expect(result).toBe(0);
describe('getStockToRemit', () => {
it('should handle Pflicht remission list type with predefined return quantity', () => {
const remissionItem = {
remainingQuantityInStock: 2,
predefinedReturnQuantity: 5,
} as any;
const result = getStockToRemit({
remissionItem,
remissionListType: RemissionListType.Pflicht,
availableStock: 10,
});
expect(result).toBe(5);
});
it('should treat undefined remainingQuantityInStock as 0', () => {
const input = {
availableStock: 8,
};
const result = calculateStockToRemit(input);
it('should handle Abteilung remission list type with return suggestion', () => {
const remissionItem = {
remainingQuantityInStock: 1,
returnItem: {
data: {
predefinedReturnQuantity: 8,
},
},
} as any;
const result = getStockToRemit({
remissionItem,
remissionListType: RemissionListType.Abteilung,
availableStock: 10,
});
expect(result).toBe(8);
});
});

View File

@@ -1,12 +1,57 @@
import {
RemissionItem,
RemissionListType,
ReturnItem,
ReturnSuggestion,
} from '@isa/remission/data-access';
/**
* Quantity to remit (remission quantity).
* Calculates the stock to remit based on the remission item and available stock.
* Uses the getStockToRemit helper function.
* @param {Object} params - The parameters for the calculation
* @param {RemissionItem} params.remissionItem - The remission item to calculate stock for
* @param {RemissionListType} params.remissionListType - The type of the remission list
* @param {number} params.availableStock - The available stock for the item
* @return {number} The calculated stock to remit
*/
export const getStockToRemit = ({
remissionItem,
remissionListType,
availableStock,
}: {
remissionItem: RemissionItem;
remissionListType: RemissionListType;
availableStock: number;
}): number => {
const remainingQuantityInStock = remissionItem?.remainingQuantityInStock;
let predefinedReturnQuantity: number | undefined = 0;
if (remissionListType === RemissionListType.Pflicht) {
predefinedReturnQuantity =
(remissionItem as ReturnItem)?.predefinedReturnQuantity ?? 0;
}
if (remissionListType === RemissionListType.Abteilung) {
predefinedReturnQuantity = (remissionItem as ReturnSuggestion)?.returnItem
?.data?.predefinedReturnQuantity;
}
return calculateStockToRemit({
availableStock,
remainingQuantityInStock,
predefinedReturnQuantity,
});
};
/**
* Calculates the stock to remit based on available stock, predefined return quantity,
* and remaining quantity in stock.
*
* - If `predefinedReturnQuantity` is set (non-zero), returns that value.
* - Otherwise, calculates as `availableStock - remainingQuantityInStock`.
* - Returns 0 if the result is negative.
*
* @remarks
* This value is used as an input for `targetStock`.
* @param {Object} params - The parameters for the calculation.
* @param {number} params.availableStock - The total available stock.
* @param {number} [params.predefinedReturnQuantity] - The predefined return quantity, if any.
* @param {number} [params.remainingQuantityInStock] - The remaining quantity in stock, if any.
* @returns {number} - The calculated stock to remit.
*/
export const calculateStockToRemit = ({
availableStock,
@@ -17,7 +62,7 @@ export const calculateStockToRemit = ({
predefinedReturnQuantity?: number;
remainingQuantityInStock?: number;
}): number => {
if (!predefinedReturnQuantity) {
if (predefinedReturnQuantity === undefined) {
const stockToRemit = availableStock - (remainingQuantityInStock ?? 0);
return stockToRemit < 0 ? 0 : stockToRemit;
}

View File

@@ -8,9 +8,8 @@ import {
import { FormsModule, Validators } from '@angular/forms';
import {
calculateAvailableStock,
calculateStockToRemit,
calculateTargetStock,
RemissionListType,
getStockToRemit,
RemissionStore,
ReturnItem,
ReturnSuggestion,
@@ -122,44 +121,11 @@ export class RemissionListItemComponent {
);
/**
* Computes the predefined return quantity for the current item.
* - For Abteilung (suggestion), uses the item's return item data or calculates based on stock.
* - For Pflicht (item), uses the item's predefined quantity.
* - Returns 0 if not available.
*/
predefinedReturnQuantity = computed(() => {
const item = this.item();
// ReturnSuggestion
if (this.remissionListType() === RemissionListType.Abteilung) {
const predefinedReturnQuantity = (item as ReturnSuggestion)?.returnItem
?.data?.predefinedReturnQuantity;
return (
predefinedReturnQuantity ??
calculateStockToRemit({
availableStock: this.availableStock(),
remainingQuantityInStock: this.remainingQuantityInStock(),
}) ??
0
);
}
// ReturnItem
if (this.remissionListType() === RemissionListType.Pflicht) {
return (item as ReturnItem)?.predefinedReturnQuantity ?? 0;
}
return 0;
});
/**
* Computes whether the item has a predefined return quantity.
* Returns true if the predefined quantity is greater than 0.
* Computes whether to display action buttons based on stock to remit and remission status.
* @returns true if stock to remit is greater than 0 and remission has started
*/
displayActions = computed<boolean>(() => {
return (
this.predefinedReturnQuantity() > 0 && this.#store.remissionStarted()
);
return this.stockToRemit() > 0 && this.#store.remissionStarted();
});
/**
@@ -182,16 +148,14 @@ export class RemissionListItemComponent {
);
/**
* Computes the stock to remit based on available stock, predefined return quantity,
* and remaining quantity in stock.
*
* @returns The calculated stock to remit.
* Computes the stock to remit based on the remission item and available stock.
* Uses the getStockToRemit helper function.
*/
stockToRemit = computed(() =>
calculateStockToRemit({
getStockToRemit({
remissionItem: this.item(),
remissionListType: this.remissionListType(),
availableStock: this.availableStock(),
predefinedReturnQuantity: this.predefinedReturnQuantity(),
remainingQuantityInStock: this.remainingQuantityInStock(),
}),
);

View File

@@ -39,6 +39,7 @@ import {
RemissionItem,
calculateAvailableStock,
RemissionReturnReceiptService,
getStockToRemit,
} from '@isa/remission/data-access';
import { injectDialog } from '@isa/ui/dialog';
import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-item-to-remit-dialog';
@@ -366,6 +367,7 @@ export class RemissionListComponent {
});
});
// TODO: Improvement - In Separate Komponente zusammen mit Remi-Button Auslagern
async remitItems() {
if (this.remitItemsInProgress()) {
return;
@@ -373,6 +375,7 @@ export class RemissionListComponent {
this.remitItemsInProgress.set(true);
try {
const remissionListType = this.selectedRemissionListType();
const selected = this.#store.selectedItems();
const quantities = this.#store.selectedQuantity();
@@ -382,6 +385,13 @@ export class RemissionListComponent {
const remissionItemIdNumber = Number(remissionItemId);
const quantity = quantities[remissionItemIdNumber];
const inStock = this.getAvailableStockForItem(item);
const stockToRemit =
quantity ??
getStockToRemit({
remissionItem: item,
remissionListType,
availableStock: inStock,
});
if (returnId && receiptId) {
await this.#remissionReturnReceiptService.remitItem({
@@ -389,7 +399,7 @@ export class RemissionListComponent {
addItem: {
returnId,
receiptId,
quantity,
quantity: stockToRemit,
inStock,
},
type: this.selectedRemissionListType(),