diff --git a/brewman/brewman/models/validations.py b/brewman/brewman/models/validations.py index bb8b9bdc..a981b76f 100644 --- a/brewman/brewman/models/validations.py +++ b/brewman/brewman/models/validations.py @@ -3,7 +3,7 @@ from fastapi import HTTPException, status from .voucher import Voucher -def check_journals_are_valid(voucher: Voucher): +def check_journals_are_valid(voucher: Voucher) -> None: if len(voucher.journals) < 2: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -27,7 +27,21 @@ def check_journals_are_valid(voucher: Voucher): ) -def check_inventories_are_valid(voucher: Voucher): +def check_duplicate_batches(voucher: Voucher) -> None: + if len(voucher.inventories) < 1: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Not enough inventories", + ) + product_set = set(x.batch.id for x in voucher.inventories) + if len(voucher.inventories) != len(product_set): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Duplicate inventories are not allowed", + ) + + +def check_duplicate_skus(voucher: Voucher) -> None: if len(voucher.inventories) < 1: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, diff --git a/brewman/brewman/routers/issue.py b/brewman/brewman/routers/issue.py index b276b5b7..dc8f0245 100644 --- a/brewman/brewman/routers/issue.py +++ b/brewman/brewman/routers/issue.py @@ -22,7 +22,7 @@ from ..models.inventory import Inventory from ..models.journal import Journal from ..models.product import Product from ..models.stock_keeping_unit import StockKeepingUnit -from ..models.validations import check_inventories_are_valid, check_journals_are_valid +from ..models.validations import check_duplicate_batches, check_journals_are_valid from ..models.voucher import Voucher from ..models.voucher_type import VoucherType from ..schemas.blank_voucher_info import BlankVoucherInfo @@ -54,7 +54,7 @@ def save_route( with SessionFuture() as db: item, batch_consumed = save(data, user, db) amount = save_inventories(item, data.inventories, batch_consumed, db) - check_inventories_are_valid(item) + check_duplicate_batches(item) save_journals(item, data.source, data.destination, amount, db) check_journals_are_valid(item) save_files(item.id, i, t, db) @@ -187,7 +187,7 @@ def update_route( with SessionFuture() as db: item, batch_consumed = update_voucher(id_, data, user, db) amount = update_inventories(item, data.inventories, batch_consumed, db) - check_inventories_are_valid(item) + check_duplicate_batches(item) update_journals(item, data.source, data.destination, amount) check_journals_are_valid(item) update_files(item.id, data.files, i, t, db) @@ -273,19 +273,14 @@ def update_inventories( batch_consumed: Optional[bool], db: Session, ): - old_set = set([(i.id, i.batch_id) for i in voucher.inventories]) - new_set = set([(i.id_, i.batch.id_) for i in inventories if i.id_ is not None]) - if len(new_set - old_set): - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Product / Batch cannot be changed", - ) amount: Decimal = Decimal(0) for it in range(len(voucher.inventories), 0, -1): item = voucher.inventories[it - 1] batch: Batch = db.execute(select(Batch).where(Batch.id == item.batch_id)).scalar_one() batch_quantity = get_batch_quantity(item.batch_id, voucher.id, db) - index = next((idx for (idx, d) in enumerate(inventories) if d.id_ == item.id), None) + index = next( + (idx for (idx, d) in enumerate(inventories) if d.id_ == item.id and d.batch.id_ == item.batch.id), None + ) if index is not None: new_inventory = inventories.pop(index) if batch_consumed and new_inventory.quantity > batch_quantity: diff --git a/brewman/brewman/routers/product.py b/brewman/brewman/routers/product.py index 9eef1cf9..1e6c20dc 100644 --- a/brewman/brewman/routers/product.py +++ b/brewman/brewman/routers/product.py @@ -44,7 +44,11 @@ def save( ) item.code = db.execute(select(func.coalesce(func.max(Product.code), 0) + 1)).scalar_one() db.add(item) - + if not len(data.skus): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Not enough stock keeping units.", + ) for sku in data.skus: db.add( StockKeepingUnit( @@ -86,6 +90,11 @@ def update_route( item.is_active = data.is_active item.is_purchased = data.is_purchased item.is_sold = data.is_sold + if not len(data.skus): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Not enough stock keeping units.", + ) for i in range(len(item.skus), 0, -1): sku = item.skus[i - 1] index = next((idx for (idx, d) in enumerate(data.skus) if d.id_ == sku.id), None) diff --git a/brewman/brewman/routers/purchase.py b/brewman/brewman/routers/purchase.py index cc6d9be7..91e565e9 100644 --- a/brewman/brewman/routers/purchase.py +++ b/brewman/brewman/routers/purchase.py @@ -23,7 +23,7 @@ from ..models.product import Product from ..models.rate_contract import RateContract from ..models.rate_contract_item import RateContractItem from ..models.stock_keeping_unit import StockKeepingUnit -from ..models.validations import check_inventories_are_valid, check_journals_are_valid +from ..models.validations import check_duplicate_skus, check_journals_are_valid from ..models.voucher import Voucher from ..models.voucher_type import VoucherType from ..schemas.blank_voucher_info import BlankVoucherInfo @@ -55,7 +55,7 @@ def save_route( with SessionFuture() as db: item: Voucher = save(data, user, db) save_inventories(item, data.vendor.id_, data.inventories, db) - check_inventories_are_valid(item) + check_duplicate_skus(item) save_journals(item, data.vendor, db) check_journals_are_valid(item) save_files(item.id, i, t, db) @@ -186,7 +186,7 @@ def update_route( with SessionFuture() as db: item: Voucher = update_voucher(id_, data, user, db) update_inventory(item, data.vendor.id_, data.inventories, db) - check_inventories_are_valid(item) + check_duplicate_skus(item) update_journals(item, data.vendor, db) check_journals_are_valid(item) update_files(item.id, data.files, i, t, db) @@ -237,17 +237,13 @@ def update_voucher(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, def update_inventory(voucher: Voucher, vendor_id: uuid.UUID, inventories: List[InventorySchema], db: Session): - old_set = set([(i.id, i.batch.sku_id) for i in voucher.inventories]) - new_set = set([(i.id_, i.batch.sku.id_) for i in inventories if i.id_ is not None]) - if len(new_set - old_set): - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Product cannot be changed", - ) for it in range(len(voucher.inventories), 0, -1): item = voucher.inventories[it - 1] quantity_consumed = -1 * get_batch_quantity(item.batch_id, voucher.id, db) - index = next((idx for (idx, d) in enumerate(inventories) if d.id_ == item.id), None) + index = next( + (idx for (idx, d) in enumerate(inventories) if d.id_ == item.id and d.batch.sku.id_ == item.batch.sku_id), + None, + ) if index is not None: new_inventory = inventories.pop(index) sku: StockKeepingUnit = db.execute( diff --git a/brewman/brewman/routers/purchase_return.py b/brewman/brewman/routers/purchase_return.py index 6ed808b7..879c3e36 100644 --- a/brewman/brewman/routers/purchase_return.py +++ b/brewman/brewman/routers/purchase_return.py @@ -20,7 +20,7 @@ from ..models.inventory import Inventory from ..models.journal import Journal from ..models.product import Product from ..models.stock_keeping_unit import StockKeepingUnit -from ..models.validations import check_inventories_are_valid, check_journals_are_valid +from ..models.validations import check_duplicate_batches, check_journals_are_valid from ..models.voucher import Voucher from ..models.voucher_type import VoucherType from ..schemas.blank_voucher_info import BlankVoucherInfo @@ -51,7 +51,7 @@ def save_route( with SessionFuture() as db: item: Voucher = save(data, user, db) save_inventories(item, data.inventories, db) - check_inventories_are_valid(item) + check_duplicate_batches(item) save_journals(item, data.vendor, db) check_journals_are_valid(item) save_files(item.id, i, t, db) @@ -175,7 +175,7 @@ def update_route( with SessionFuture() as db: item: Voucher = update_voucher(id_, data, user, db) update_inventory(item, data.inventories, db) - check_inventories_are_valid(item) + check_duplicate_batches(item) update_journals(item, data.vendor, db) check_journals_are_valid(item) update_files(item.id, data.files, i, t, db) @@ -229,18 +229,13 @@ def update_voucher(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, def update_inventory(voucher: Voucher, new_inventories: List[InventorySchema], db: Session): - old_set = set([(i.id, i.batch.sku_id) for i in voucher.inventories]) - new_set = set([(i.id_, i.batch.sku.id_) for i in new_inventories if i.id_ is not None]) - if len(new_set - old_set): - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Product cannot be changed", - ) for it in range(len(voucher.inventories), 0, -1): item = voucher.inventories[it - 1] batch = db.execute(select(Batch).where(Batch.id == item.batch_id)).scalar_one() batch_quantity = get_batch_quantity(item.batch_id, voucher.id, db) - index = next((idx for (idx, d) in enumerate(new_inventories) if d.id_ == item.id), None) + index = next( + (idx for (idx, d) in enumerate(new_inventories) if d.id_ == item.id and d.batch.id_ == item.batch.id), None + ) if index is not None: new_inventory = new_inventories.pop(index) if new_inventory.quantity > batch_quantity: diff --git a/overlord/src/app/issue/issue.component.ts b/overlord/src/app/issue/issue.component.ts index a39845c2..612e9f45 100644 --- a/overlord/src/app/issue/issue.component.ts +++ b/overlord/src/app/issue/issue.component.ts @@ -166,20 +166,13 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { if (this.batch === null || quantity <= 0) { return; } - const oldFiltered = this.voucher.inventories.filter( - (x) => x.batch.sku.id === (this.batch as Batch).sku.id, - ); - const old = oldFiltered.length ? oldFiltered[0] : null; - if (oldFiltered.length) { - if (((old as Inventory).batch as Batch).id !== this.batch.id) { - this.toaster.show('Danger', 'Product with a different batch already added'); - return; - } - if (isConsumption && (old as Inventory).quantity + quantity > this.batch.quantityRemaining) { + const old = this.voucher.inventories.find((x) => x.batch.id === (this.batch as Batch).id); + if (old !== undefined) { + if (isConsumption && old.quantity + quantity > this.batch.quantityRemaining) { this.toaster.show('Danger', 'Quantity issued cannot be more than quantity available'); return; } - (old as Inventory).quantity += quantity; + old.quantity += quantity; } else { if (isConsumption && quantity > this.batch.quantityRemaining) { this.toaster.show('Danger', 'Quantity issued cannot be more than quantity available'); @@ -238,7 +231,7 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { const j = result as Inventory; if ( j.batch.sku.id !== row.batch.sku.id && - this.voucher.inventories.filter((x) => x.batch.sku.id === j.batch.sku.id).length + this.voucher.inventories.filter((x) => x.batch.id === j.batch.id).length ) { return; } diff --git a/overlord/src/app/purchase-return/purchase-return.component.ts b/overlord/src/app/purchase-return/purchase-return.component.ts index 40c14b36..f7cd4fe4 100644 --- a/overlord/src/app/purchase-return/purchase-return.component.ts +++ b/overlord/src/app/purchase-return/purchase-return.component.ts @@ -179,10 +179,8 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy if (this.batch === null || quantity <= 0 || this.batch.quantityRemaining < quantity) { return; } - const oldFiltered = this.voucher.inventories.filter( - (x) => x.batch.sku.id === (this.batch as Batch).sku.id, - ); - if (oldFiltered.length) { + const old = this.voucher.inventories.find((x) => x.batch.id === (this.batch as Batch).id); + if (old !== undefined) { this.toaster.show('Danger', 'Product already added'); return; }