Fix: Products could be added without skus rendering them useless
Feature: Change of product in purchase / issue / Purchase return allowed.
This commit is contained in:
@ -3,7 +3,7 @@ from fastapi import HTTPException, status
|
|||||||
from .voucher import Voucher
|
from .voucher import Voucher
|
||||||
|
|
||||||
|
|
||||||
def check_journals_are_valid(voucher: Voucher):
|
def check_journals_are_valid(voucher: Voucher) -> None:
|
||||||
if len(voucher.journals) < 2:
|
if len(voucher.journals) < 2:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
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:
|
if len(voucher.inventories) < 1:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||||
|
|||||||
@ -22,7 +22,7 @@ from ..models.inventory import Inventory
|
|||||||
from ..models.journal import Journal
|
from ..models.journal import Journal
|
||||||
from ..models.product import Product
|
from ..models.product import Product
|
||||||
from ..models.stock_keeping_unit import StockKeepingUnit
|
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 import Voucher
|
||||||
from ..models.voucher_type import VoucherType
|
from ..models.voucher_type import VoucherType
|
||||||
from ..schemas.blank_voucher_info import BlankVoucherInfo
|
from ..schemas.blank_voucher_info import BlankVoucherInfo
|
||||||
@ -54,7 +54,7 @@ def save_route(
|
|||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
item, batch_consumed = save(data, user, db)
|
item, batch_consumed = save(data, user, db)
|
||||||
amount = save_inventories(item, data.inventories, batch_consumed, 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)
|
save_journals(item, data.source, data.destination, amount, db)
|
||||||
check_journals_are_valid(item)
|
check_journals_are_valid(item)
|
||||||
save_files(item.id, i, t, db)
|
save_files(item.id, i, t, db)
|
||||||
@ -187,7 +187,7 @@ def update_route(
|
|||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
item, batch_consumed = update_voucher(id_, data, user, db)
|
item, batch_consumed = update_voucher(id_, data, user, db)
|
||||||
amount = update_inventories(item, data.inventories, batch_consumed, 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)
|
update_journals(item, data.source, data.destination, amount)
|
||||||
check_journals_are_valid(item)
|
check_journals_are_valid(item)
|
||||||
update_files(item.id, data.files, i, t, db)
|
update_files(item.id, data.files, i, t, db)
|
||||||
@ -273,19 +273,14 @@ def update_inventories(
|
|||||||
batch_consumed: Optional[bool],
|
batch_consumed: Optional[bool],
|
||||||
db: Session,
|
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)
|
amount: Decimal = Decimal(0)
|
||||||
for it in range(len(voucher.inventories), 0, -1):
|
for it in range(len(voucher.inventories), 0, -1):
|
||||||
item = voucher.inventories[it - 1]
|
item = voucher.inventories[it - 1]
|
||||||
batch: Batch = db.execute(select(Batch).where(Batch.id == item.batch_id)).scalar_one()
|
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)
|
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:
|
if index is not None:
|
||||||
new_inventory = inventories.pop(index)
|
new_inventory = inventories.pop(index)
|
||||||
if batch_consumed and new_inventory.quantity > batch_quantity:
|
if batch_consumed and new_inventory.quantity > batch_quantity:
|
||||||
|
|||||||
@ -44,7 +44,11 @@ def save(
|
|||||||
)
|
)
|
||||||
item.code = db.execute(select(func.coalesce(func.max(Product.code), 0) + 1)).scalar_one()
|
item.code = db.execute(select(func.coalesce(func.max(Product.code), 0) + 1)).scalar_one()
|
||||||
db.add(item)
|
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:
|
for sku in data.skus:
|
||||||
db.add(
|
db.add(
|
||||||
StockKeepingUnit(
|
StockKeepingUnit(
|
||||||
@ -86,6 +90,11 @@ def update_route(
|
|||||||
item.is_active = data.is_active
|
item.is_active = data.is_active
|
||||||
item.is_purchased = data.is_purchased
|
item.is_purchased = data.is_purchased
|
||||||
item.is_sold = data.is_sold
|
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):
|
for i in range(len(item.skus), 0, -1):
|
||||||
sku = item.skus[i - 1]
|
sku = item.skus[i - 1]
|
||||||
index = next((idx for (idx, d) in enumerate(data.skus) if d.id_ == sku.id), None)
|
index = next((idx for (idx, d) in enumerate(data.skus) if d.id_ == sku.id), None)
|
||||||
|
|||||||
@ -23,7 +23,7 @@ from ..models.product import Product
|
|||||||
from ..models.rate_contract import RateContract
|
from ..models.rate_contract import RateContract
|
||||||
from ..models.rate_contract_item import RateContractItem
|
from ..models.rate_contract_item import RateContractItem
|
||||||
from ..models.stock_keeping_unit import StockKeepingUnit
|
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 import Voucher
|
||||||
from ..models.voucher_type import VoucherType
|
from ..models.voucher_type import VoucherType
|
||||||
from ..schemas.blank_voucher_info import BlankVoucherInfo
|
from ..schemas.blank_voucher_info import BlankVoucherInfo
|
||||||
@ -55,7 +55,7 @@ def save_route(
|
|||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
item: Voucher = save(data, user, db)
|
item: Voucher = save(data, user, db)
|
||||||
save_inventories(item, data.vendor.id_, data.inventories, 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)
|
save_journals(item, data.vendor, db)
|
||||||
check_journals_are_valid(item)
|
check_journals_are_valid(item)
|
||||||
save_files(item.id, i, t, db)
|
save_files(item.id, i, t, db)
|
||||||
@ -186,7 +186,7 @@ def update_route(
|
|||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
item: Voucher = update_voucher(id_, data, user, db)
|
item: Voucher = update_voucher(id_, data, user, db)
|
||||||
update_inventory(item, data.vendor.id_, data.inventories, 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)
|
update_journals(item, data.vendor, db)
|
||||||
check_journals_are_valid(item)
|
check_journals_are_valid(item)
|
||||||
update_files(item.id, data.files, i, t, db)
|
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):
|
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):
|
for it in range(len(voucher.inventories), 0, -1):
|
||||||
item = voucher.inventories[it - 1]
|
item = voucher.inventories[it - 1]
|
||||||
quantity_consumed = -1 * get_batch_quantity(item.batch_id, voucher.id, db)
|
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:
|
if index is not None:
|
||||||
new_inventory = inventories.pop(index)
|
new_inventory = inventories.pop(index)
|
||||||
sku: StockKeepingUnit = db.execute(
|
sku: StockKeepingUnit = db.execute(
|
||||||
|
|||||||
@ -20,7 +20,7 @@ from ..models.inventory import Inventory
|
|||||||
from ..models.journal import Journal
|
from ..models.journal import Journal
|
||||||
from ..models.product import Product
|
from ..models.product import Product
|
||||||
from ..models.stock_keeping_unit import StockKeepingUnit
|
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 import Voucher
|
||||||
from ..models.voucher_type import VoucherType
|
from ..models.voucher_type import VoucherType
|
||||||
from ..schemas.blank_voucher_info import BlankVoucherInfo
|
from ..schemas.blank_voucher_info import BlankVoucherInfo
|
||||||
@ -51,7 +51,7 @@ def save_route(
|
|||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
item: Voucher = save(data, user, db)
|
item: Voucher = save(data, user, db)
|
||||||
save_inventories(item, data.inventories, db)
|
save_inventories(item, data.inventories, db)
|
||||||
check_inventories_are_valid(item)
|
check_duplicate_batches(item)
|
||||||
save_journals(item, data.vendor, db)
|
save_journals(item, data.vendor, db)
|
||||||
check_journals_are_valid(item)
|
check_journals_are_valid(item)
|
||||||
save_files(item.id, i, t, db)
|
save_files(item.id, i, t, db)
|
||||||
@ -175,7 +175,7 @@ def update_route(
|
|||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
item: Voucher = update_voucher(id_, data, user, db)
|
item: Voucher = update_voucher(id_, data, user, db)
|
||||||
update_inventory(item, data.inventories, db)
|
update_inventory(item, data.inventories, db)
|
||||||
check_inventories_are_valid(item)
|
check_duplicate_batches(item)
|
||||||
update_journals(item, data.vendor, db)
|
update_journals(item, data.vendor, db)
|
||||||
check_journals_are_valid(item)
|
check_journals_are_valid(item)
|
||||||
update_files(item.id, data.files, i, t, db)
|
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):
|
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):
|
for it in range(len(voucher.inventories), 0, -1):
|
||||||
item = voucher.inventories[it - 1]
|
item = voucher.inventories[it - 1]
|
||||||
batch = db.execute(select(Batch).where(Batch.id == item.batch_id)).scalar_one()
|
batch = db.execute(select(Batch).where(Batch.id == item.batch_id)).scalar_one()
|
||||||
batch_quantity = get_batch_quantity(item.batch_id, voucher.id, db)
|
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:
|
if index is not None:
|
||||||
new_inventory = new_inventories.pop(index)
|
new_inventory = new_inventories.pop(index)
|
||||||
if new_inventory.quantity > batch_quantity:
|
if new_inventory.quantity > batch_quantity:
|
||||||
|
|||||||
@ -166,20 +166,13 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (this.batch === null || quantity <= 0) {
|
if (this.batch === null || quantity <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const oldFiltered = this.voucher.inventories.filter(
|
const old = this.voucher.inventories.find((x) => x.batch.id === (this.batch as Batch).id);
|
||||||
(x) => x.batch.sku.id === (this.batch as Batch).sku.id,
|
if (old !== undefined) {
|
||||||
);
|
if (isConsumption && old.quantity + quantity > this.batch.quantityRemaining) {
|
||||||
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) {
|
|
||||||
this.toaster.show('Danger', 'Quantity issued cannot be more than quantity available');
|
this.toaster.show('Danger', 'Quantity issued cannot be more than quantity available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(old as Inventory).quantity += quantity;
|
old.quantity += quantity;
|
||||||
} else {
|
} else {
|
||||||
if (isConsumption && quantity > this.batch.quantityRemaining) {
|
if (isConsumption && quantity > this.batch.quantityRemaining) {
|
||||||
this.toaster.show('Danger', 'Quantity issued cannot be more than quantity available');
|
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;
|
const j = result as Inventory;
|
||||||
if (
|
if (
|
||||||
j.batch.sku.id !== row.batch.sku.id &&
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -179,10 +179,8 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
if (this.batch === null || quantity <= 0 || this.batch.quantityRemaining < quantity) {
|
if (this.batch === null || quantity <= 0 || this.batch.quantityRemaining < quantity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const oldFiltered = this.voucher.inventories.filter(
|
const old = this.voucher.inventories.find((x) => x.batch.id === (this.batch as Batch).id);
|
||||||
(x) => x.batch.sku.id === (this.batch as Batch).sku.id,
|
if (old !== undefined) {
|
||||||
);
|
|
||||||
if (oldFiltered.length) {
|
|
||||||
this.toaster.show('Danger', 'Product already added');
|
this.toaster.show('Danger', 'Product already added');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user