2020-05-21 07:41:47 +00:00
|
|
|
import uuid
|
2020-10-07 15:18:43 +00:00
|
|
|
|
2020-05-21 19:45:25 +00:00
|
|
|
from datetime import datetime
|
|
|
|
from decimal import Decimal
|
|
|
|
from typing import List, Optional
|
2020-05-21 07:41:47 +00:00
|
|
|
|
2020-10-07 15:18:43 +00:00
|
|
|
import brewman.schemas.input as schema_in
|
|
|
|
import brewman.schemas.voucher as output
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, File, HTTPException, Request, Security, status
|
2020-05-21 07:41:47 +00:00
|
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
2020-10-07 15:18:43 +00:00
|
|
|
from ..core.security import get_current_active_user as get_user
|
|
|
|
from ..core.session import get_date, set_date
|
|
|
|
from ..db.session import SessionLocal
|
|
|
|
from ..models import AccountBase, CostCentre
|
|
|
|
from ..models.voucher import Batch, Inventory, Journal, Voucher, VoucherType
|
|
|
|
from ..schemas.auth import UserToken
|
2020-05-30 05:39:19 +00:00
|
|
|
from .db_image import save_files, update_files
|
2020-05-21 19:45:25 +00:00
|
|
|
from .voucher import (
|
|
|
|
blank_voucher,
|
2020-10-07 15:18:43 +00:00
|
|
|
check_voucher_edit_allowed,
|
|
|
|
check_voucher_lock_info,
|
|
|
|
voucher_info,
|
2020-05-21 19:45:25 +00:00
|
|
|
)
|
2020-10-07 15:18:43 +00:00
|
|
|
|
2020-05-21 07:41:47 +00:00
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
|
|
|
|
# Dependency
|
|
|
|
def get_db() -> Session:
|
|
|
|
try:
|
|
|
|
db = SessionLocal()
|
|
|
|
yield db
|
|
|
|
finally:
|
|
|
|
db.close()
|
|
|
|
|
|
|
|
|
2020-05-21 19:45:25 +00:00
|
|
|
@router.post("", response_model=output.Voucher)
|
2020-05-21 07:41:47 +00:00
|
|
|
def save_route(
|
|
|
|
request: Request,
|
2020-05-21 19:45:25 +00:00
|
|
|
data: schema_in.IssueIn = Depends(schema_in.IssueIn.load_form),
|
2020-05-21 07:41:47 +00:00
|
|
|
db: Session = Depends(get_db),
|
2020-05-30 05:39:19 +00:00
|
|
|
i: List[bytes] = File(None),
|
|
|
|
t: List[bytes] = File(None),
|
2020-05-21 19:45:25 +00:00
|
|
|
user: UserToken = Security(get_user, scopes=["issue"]),
|
2020-05-21 07:41:47 +00:00
|
|
|
):
|
|
|
|
try:
|
2020-05-21 19:45:25 +00:00
|
|
|
item, batch_consumed = save(data, user, db)
|
|
|
|
amount = save_inventories(item, data.inventories, batch_consumed, db)
|
|
|
|
save_journals(item, data.source, data.destination, amount, db)
|
2020-05-30 05:39:19 +00:00
|
|
|
save_files(item.id, i, t, db)
|
2020-05-21 07:41:47 +00:00
|
|
|
db.commit()
|
2020-05-21 19:45:25 +00:00
|
|
|
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
|
|
|
|
info = voucher_info(item, db)
|
|
|
|
return info
|
2020-05-21 07:41:47 +00:00
|
|
|
except SQLAlchemyError as e:
|
|
|
|
db.rollback()
|
|
|
|
raise HTTPException(
|
2020-10-07 15:18:43 +00:00
|
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
detail=str(e),
|
2020-05-21 07:41:47 +00:00
|
|
|
)
|
|
|
|
except Exception:
|
|
|
|
db.rollback()
|
2020-06-01 03:31:31 +00:00
|
|
|
raise
|
2020-05-21 07:41:47 +00:00
|
|
|
|
|
|
|
|
2020-10-07 16:59:24 +00:00
|
|
|
def save(
|
|
|
|
data: schema_in.IssueIn, user: UserToken, db: Session
|
|
|
|
) -> (Voucher, Optional[bool]):
|
2020-05-21 07:41:47 +00:00
|
|
|
check_voucher_lock_info(None, data.date_, db)
|
2020-05-21 19:45:25 +00:00
|
|
|
voucher = Voucher(
|
|
|
|
date=data.date_,
|
|
|
|
narration=data.narration,
|
|
|
|
is_starred=data.is_starred,
|
|
|
|
user_id=user.id_,
|
|
|
|
type_=VoucherType.by_name(data.type_),
|
|
|
|
)
|
|
|
|
db.add(voucher)
|
|
|
|
if data.source.id_ == data.destination.id_:
|
|
|
|
raise HTTPException(
|
2020-10-07 15:18:43 +00:00
|
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
|
|
detail="Source cannot be the same as destination",
|
2020-05-21 19:45:25 +00:00
|
|
|
)
|
|
|
|
if data.source.id_ == CostCentre.cost_centre_purchase():
|
|
|
|
batch_consumed = True
|
|
|
|
elif data.destination.id_ == CostCentre.cost_centre_purchase():
|
|
|
|
batch_consumed = False
|
|
|
|
else:
|
|
|
|
batch_consumed = None
|
|
|
|
return voucher, batch_consumed
|
|
|
|
|
|
|
|
|
|
|
|
def save_inventories(
|
2020-10-07 15:18:43 +00:00
|
|
|
voucher: Voucher,
|
|
|
|
inventories: List[schema_in.Inventory],
|
|
|
|
batch_consumed: Optional[bool],
|
|
|
|
db: Session,
|
2020-05-21 19:45:25 +00:00
|
|
|
) -> Decimal:
|
|
|
|
amount: Decimal = Decimal(0)
|
|
|
|
for item in inventories:
|
|
|
|
batch = db.query(Batch).filter(Batch.id == item.batch.id_).first()
|
|
|
|
if batch_consumed and item.quantity > batch.quantity_remaining:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
|
|
detail=f"Quantity available is {batch.quantity_remaining} only",
|
|
|
|
)
|
|
|
|
if batch.name > voucher.date:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
|
|
detail=f"Batch of {batch.product.name} was purchased after the issue date",
|
|
|
|
)
|
|
|
|
if batch_consumed is None:
|
|
|
|
pass
|
|
|
|
elif batch_consumed:
|
|
|
|
batch.quantity_remaining -= item.quantity
|
|
|
|
else:
|
|
|
|
batch.quantity_remaining += item.quantity
|
|
|
|
|
|
|
|
item = Inventory(
|
|
|
|
id_=item.id_,
|
|
|
|
product=batch.product,
|
|
|
|
quantity=item.quantity,
|
|
|
|
rate=batch.rate,
|
|
|
|
tax=batch.tax,
|
|
|
|
discount=batch.discount,
|
|
|
|
batch=batch,
|
|
|
|
)
|
|
|
|
voucher.inventories.append(item)
|
|
|
|
db.add(item)
|
|
|
|
amount += round(item.amount, 2)
|
|
|
|
return amount
|
|
|
|
|
|
|
|
|
|
|
|
def save_journals(
|
|
|
|
voucher: Voucher,
|
|
|
|
source: schema_in.CostCentreLink,
|
|
|
|
destination: schema_in.CostCentreLink,
|
|
|
|
amount: Decimal,
|
|
|
|
db: Session,
|
|
|
|
):
|
2020-10-07 15:18:43 +00:00
|
|
|
s = Journal(
|
|
|
|
debit=-1,
|
|
|
|
account_id=AccountBase.all_purchases(),
|
|
|
|
amount=amount,
|
|
|
|
cost_centre_id=source.id_,
|
|
|
|
)
|
|
|
|
d = Journal(
|
|
|
|
debit=1,
|
|
|
|
account_id=AccountBase.all_purchases(),
|
|
|
|
amount=amount,
|
|
|
|
cost_centre_id=destination.id_,
|
|
|
|
)
|
2020-05-21 19:45:25 +00:00
|
|
|
voucher.journals.append(s)
|
|
|
|
db.add(s)
|
|
|
|
voucher.journals.append(d)
|
|
|
|
db.add(d)
|
|
|
|
|
2020-05-21 07:41:47 +00:00
|
|
|
|
2020-05-21 19:45:25 +00:00
|
|
|
@router.put("/{id_}", response_model=output.Voucher)
|
2020-05-21 07:41:47 +00:00
|
|
|
def update_route(
|
|
|
|
id_: uuid.UUID,
|
|
|
|
request: Request,
|
2020-05-21 19:45:25 +00:00
|
|
|
data: schema_in.IssueIn = Depends(schema_in.IssueIn.load_form),
|
2020-05-21 07:41:47 +00:00
|
|
|
db: Session = Depends(get_db),
|
2020-05-30 05:39:19 +00:00
|
|
|
i: List[bytes] = File(None),
|
|
|
|
t: List[bytes] = File(None),
|
2020-05-21 19:45:25 +00:00
|
|
|
user: UserToken = Security(get_user, scopes=["issue"]),
|
2020-05-21 07:41:47 +00:00
|
|
|
):
|
|
|
|
try:
|
2020-05-21 19:45:25 +00:00
|
|
|
item, batch_consumed = update(id_, data, user, db)
|
|
|
|
amount = update_inventories(item, data.inventories, batch_consumed, db)
|
|
|
|
update_journals(item, data.source, data.destination, amount)
|
2020-05-30 05:39:19 +00:00
|
|
|
update_files(item.id, data.files, i, t, db)
|
2020-05-21 07:41:47 +00:00
|
|
|
db.commit()
|
2020-05-21 19:45:25 +00:00
|
|
|
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
|
2020-05-21 07:41:47 +00:00
|
|
|
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
|
|
|
|
return voucher_info(item, db)
|
|
|
|
except SQLAlchemyError as e:
|
2020-05-21 19:45:25 +00:00
|
|
|
db.rollback()
|
|
|
|
raise HTTPException(
|
2020-10-07 15:18:43 +00:00
|
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
detail=str(e),
|
2020-05-21 19:45:25 +00:00
|
|
|
)
|
|
|
|
except Exception:
|
2020-05-21 07:41:47 +00:00
|
|
|
db.rollback()
|
2020-06-01 03:31:31 +00:00
|
|
|
raise
|
2020-05-21 19:45:25 +00:00
|
|
|
|
|
|
|
|
2020-10-07 16:59:24 +00:00
|
|
|
def update(
|
|
|
|
id_: uuid.UUID, data: schema_in.IssueIn, user: UserToken, db: Session
|
|
|
|
) -> (Voucher, Optional[bool]):
|
2020-05-21 19:45:25 +00:00
|
|
|
voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
|
|
|
|
check_voucher_lock_info(voucher.date, data.date_, db)
|
|
|
|
check_voucher_edit_allowed(voucher, user)
|
|
|
|
voucher.date = data.date_
|
|
|
|
voucher.is_starred = data.is_starred
|
|
|
|
voucher.narration = data.narration
|
|
|
|
voucher.user_id = user.id_
|
|
|
|
voucher.posted = False
|
|
|
|
voucher.last_edit_date = datetime.utcnow()
|
|
|
|
|
|
|
|
for item in voucher.journals:
|
|
|
|
if item.debit == 1:
|
|
|
|
destination = item.cost_centre_id
|
|
|
|
else:
|
|
|
|
source = item.cost_centre_id
|
|
|
|
|
|
|
|
if source == CostCentre.cost_centre_purchase():
|
|
|
|
old_batch_consumed = True
|
|
|
|
elif destination == CostCentre.cost_centre_purchase():
|
|
|
|
old_batch_consumed = False
|
|
|
|
else:
|
|
|
|
old_batch_consumed = None
|
|
|
|
|
|
|
|
if data.source.id_ == CostCentre.cost_centre_purchase():
|
|
|
|
new_batch_consumed = True
|
|
|
|
elif data.destination.id_ == CostCentre.cost_centre_purchase():
|
|
|
|
new_batch_consumed = False
|
|
|
|
else:
|
|
|
|
new_batch_consumed = None
|
|
|
|
|
|
|
|
if new_batch_consumed != old_batch_consumed:
|
|
|
|
raise HTTPException(
|
2020-10-07 15:18:43 +00:00
|
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
|
|
detail="Purchase cost centre cannot be changed",
|
2020-05-21 19:45:25 +00:00
|
|
|
)
|
|
|
|
return voucher, new_batch_consumed
|
|
|
|
|
|
|
|
|
|
|
|
def update_inventories(
|
2020-10-07 15:18:43 +00:00
|
|
|
voucher: Voucher,
|
|
|
|
inventories: List[schema_in.Inventory],
|
|
|
|
batch_consumed: Optional[bool],
|
|
|
|
db: Session,
|
2020-05-21 19:45:25 +00:00
|
|
|
):
|
|
|
|
amount: Decimal = Decimal(0)
|
|
|
|
for it in range(len(voucher.inventories), 0, -1):
|
|
|
|
item = voucher.inventories[it - 1]
|
|
|
|
found = False
|
|
|
|
for j in range(len(inventories), 0, -1):
|
|
|
|
i = inventories[j - 1]
|
|
|
|
if item.id == i.id_:
|
|
|
|
batch = db.query(Batch).filter(Batch.id == i.batch.id_).first()
|
|
|
|
found = True
|
|
|
|
if item.batch_id != batch.id:
|
|
|
|
raise HTTPException(
|
2020-10-07 15:18:43 +00:00
|
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
|
|
detail="Product / Batch cannot be changed",
|
2020-05-21 19:45:25 +00:00
|
|
|
)
|
2020-10-07 16:59:24 +00:00
|
|
|
if (
|
|
|
|
batch_consumed
|
|
|
|
and i.quantity - item.quantity > item.batch.quantity_remaining
|
|
|
|
):
|
2020-05-21 19:45:25 +00:00
|
|
|
raise HTTPException(
|
|
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
|
|
detail=f"Maximum quantity available for {item.product.full_name} is {item.quantity + item.batch.quantity_remaining}",
|
|
|
|
)
|
|
|
|
if item.batch.name > voucher.date:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
|
|
detail=f"Batch of {item.product.name} was purchased after the issue date",
|
|
|
|
)
|
|
|
|
|
|
|
|
if batch_consumed is None:
|
|
|
|
pass
|
|
|
|
elif batch_consumed:
|
|
|
|
item.batch.quantity_remaining -= i.quantity - item.quantity
|
|
|
|
else:
|
|
|
|
item.batch.quantity_remaining += i.quantity - item.quantity
|
|
|
|
|
|
|
|
item.quantity = i.quantity
|
|
|
|
item.rate = batch.rate
|
|
|
|
item.tax = batch.tax
|
|
|
|
item.discount = batch.discount
|
|
|
|
amount += round(item.amount, 2)
|
|
|
|
|
|
|
|
inventories.remove(i)
|
|
|
|
break
|
|
|
|
if not found:
|
|
|
|
if batch_consumed is None:
|
|
|
|
pass
|
|
|
|
elif batch_consumed:
|
|
|
|
item.batch.quantity_remaining += item.quantity
|
|
|
|
else:
|
|
|
|
if item.batch.quantity_remaining < item.quantity:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
|
|
detail=f"Product {item.product.name} cannot be removed, minimum quantity is {item.batch.quantity_remaining}",
|
|
|
|
)
|
|
|
|
item.batch.quantity_remaining -= item.quantity
|
|
|
|
db.delete(item)
|
|
|
|
voucher.inventories.remove(item)
|
|
|
|
amount += save_inventories(voucher, inventories, batch_consumed, db)
|
|
|
|
return amount
|
|
|
|
|
|
|
|
|
|
|
|
def update_journals(
|
2020-10-07 15:18:43 +00:00
|
|
|
voucher: Voucher,
|
|
|
|
source: schema_in.CostCentreLink,
|
|
|
|
destination: schema_in.CostCentreLink,
|
|
|
|
amount: Decimal,
|
2020-05-21 19:45:25 +00:00
|
|
|
):
|
|
|
|
for i in range(len(voucher.journals), 0, -1):
|
|
|
|
item = voucher.journals[i - 1]
|
|
|
|
if item.debit == -1:
|
|
|
|
item.cost_centre_id = source.id_
|
|
|
|
item.amount = amount
|
|
|
|
else:
|
|
|
|
item.cost_centre_id = destination.id_
|
|
|
|
item.amount = amount
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{id_}", response_model=output.Voucher)
|
|
|
|
def get_id(
|
2020-10-07 15:18:43 +00:00
|
|
|
id_: uuid.UUID,
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
user: UserToken = Security(get_user, scopes=["issue"]),
|
2020-05-21 19:45:25 +00:00
|
|
|
):
|
|
|
|
try:
|
|
|
|
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
|
|
|
|
return voucher_info(item, db)
|
|
|
|
except SQLAlchemyError as e:
|
|
|
|
db.rollback()
|
|
|
|
raise HTTPException(
|
2020-10-07 15:18:43 +00:00
|
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
detail=str(e),
|
2020-05-21 07:41:47 +00:00
|
|
|
)
|
|
|
|
except Exception:
|
|
|
|
db.rollback()
|
2020-06-01 03:31:31 +00:00
|
|
|
raise
|
2020-05-21 07:41:47 +00:00
|
|
|
|
|
|
|
|
2020-05-22 04:40:45 +00:00
|
|
|
@router.get("", response_model=output.Voucher)
|
2020-05-21 19:45:25 +00:00
|
|
|
def show_blank(
|
|
|
|
request: Request,
|
|
|
|
date: str = None,
|
|
|
|
source: str = None,
|
|
|
|
destination: str = None,
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
user: UserToken = Security(get_user, scopes=["issue"]),
|
|
|
|
):
|
|
|
|
date_ = date or get_date(request.session)
|
|
|
|
additional_info = {"date": date_, "type": "Issue"}
|
|
|
|
if source:
|
|
|
|
additional_info["source"] = source
|
|
|
|
if destination:
|
|
|
|
additional_info["destination"] = destination
|
|
|
|
return blank_voucher(additional_info, db)
|