Renamed "Salary Deduction" to "Employee Benefit"

Journal, Purchase and Purchase Return vouchers done!!

Changed the column type of "date" columns from "datetime" to better fit the data.
This commit is contained in:
tanshu 2020-05-21 13:11:47 +05:30
parent a0f27fe364
commit 98edca5f60
35 changed files with 977 additions and 894 deletions

@ -1,54 +0,0 @@
"""test
Revision ID: 03ea3e9cb1e5
Revises: 5498fc4bf58d
Create Date: 2020-05-14 21:25:08.945280
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '03ea3e9cb1e5'
down_revision = '5498fc4bf58d'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("attendances") as batch_op:
batch_op.alter_column('date', type_=sa.Date(), nullable=False)
with op.batch_alter_table("employees") as batch_op:
batch_op.alter_column('Designation', new_column_name='designation', nullable=False)
batch_op.alter_column('Salary', new_column_name='salary', nullable=False)
batch_op.alter_column('ServicePoints', new_column_name='points', nullable=False)
batch_op.alter_column('JoiningDate', new_column_name='joining_date', type_=sa.Date(), nullable=False)
batch_op.alter_column('LeavingDate', new_column_name='leaving_date', type_=sa.Date(), nullable=True)
with op.batch_alter_table("settings") as batch_op:
batch_op.alter_column('SettingID', new_column_name='id')
batch_op.alter_column('Name', new_column_name='name')
batch_op.alter_column('Data', new_column_name='data')
op.create_unique_constraint(op.f('uq_settings_name'), 'settings', ['name'])
op.drop_constraint('uq_settings_Name', 'settings', type_='unique')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("attendances") as batch_op:
batch_op.alter_column('date', type_=sa.DateTime())
with op.batch_alter_table("employees") as batch_op:
batch_op.alter_column('designation', new_column_name='Designation', nullable=True)
batch_op.alter_column('salary', new_column_name='Salary', nullable=True)
batch_op.alter_column('points', new_column_name='ServicePoints', nullable=True)
batch_op.alter_column('joining_date', new_column_name='JoiningDate', type_=sa.DateTime(), nullable=True)
batch_op.alter_column('leaving_date', new_column_name='LeavingDate', type_=sa.DateTime(), nullable=True)
with op.batch_alter_table("settings") as batch_op:
batch_op.alter_column('id', new_column_name='SettingID')
batch_op.alter_column('name', new_column_name='Name')
batch_op.alter_column('data', new_column_name='Data')
op.create_unique_constraint('uq_settings_Name', 'settings', ['name'])
op.drop_constraint(op.f('uq_settings_name'), 'settings', type_='unique')
# ### end Alembic commands ###

@ -1,46 +0,0 @@
"""Rename Service Charge to Incentive
Revision ID: 5498fc4bf58d
Revises: eed0b382c287
Create Date: 2020-05-12 16:39:12.157447
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import table, column
# revision identifiers, used by Alembic.
revision = '5498fc4bf58d'
down_revision = 'eed0b382c287'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('service_charges_journal_id_fkey', 'service_charges', type_='foreignkey')
op.drop_constraint('service_charges_voucher_id_fkey', 'service_charges', type_='foreignkey')
op.rename_table('service_charges', 'incentives')
op.create_foreign_key('fk_incentives_journal_id_journals', 'incentives', 'journals', ['journal_id'], ['id'])
op.create_foreign_key('fk_incentives_voucher_id_vouchers', 'incentives', 'vouchers', ['voucher_id'], ['id'])
role = table('auth_roles', column('name', sa.String))
op.execute(role.update().where(role.c.name == op.inline_literal('Service Charge')).values({'name': op.inline_literal('Incentive')}))
account = table('accounts', column('name', sa.String))
op.execute(account.update().where(account.c.name == op.inline_literal('Service Charges')).values({'name': op.inline_literal('Incentives')}))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('fk_incentives_journal_id_journals', 'incentives', type_='foreignkey')
op.drop_constraint('fk_incentives_voucher_id_vouchers', 'incentives', type_='foreignkey')
op.rename_table('incentives', 'service_charges')
op.create_foreign_key('service_charges_journal_id_fkey', 'service_charges', 'journals', ['journal_id'], ['journals.id'])
op.create_foreign_key('service_charges_voucher_id_fkey', 'service_charges', 'vouchers', ['voucher_id'], ['vouchers.id'])
role = table('auth_roles', column('name', sa.String))
op.execute(role.update().where(role.c.name == op.inline_literal('Incentive')).values({'name': op.inline_literal('Service Charge')}))
account = table('accounts', column('name', sa.String))
op.execute(account.update().where(account.c.name == op.inline_literal('Incentives')).values({'name': op.inline_literal('Service Charges')}))
# ### end Alembic commands ###

@ -7,6 +7,7 @@ Create Date: 2020-05-10 19:52:58.163810
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import table, column
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
@ -51,7 +52,7 @@ def upgrade():
batch_op.alter_column('UserID', new_column_name='user_id')
batch_op.alter_column('AttendanceType', new_column_name='attendance_type')
batch_op.alter_column('CreationDate', new_column_name='creation_date')
batch_op.alter_column('Date', new_column_name='date')
batch_op.alter_column('Date', new_column_name='date', type_=sa.Date(), nullable=False)
batch_op.alter_column('EmployeeID', new_column_name='employee_id')
batch_op.alter_column('AttendanceID', new_column_name='id')
batch_op.alter_column('IsValid', new_column_name='is_valid')
@ -65,7 +66,7 @@ def upgrade():
with op.batch_alter_table("batches") as batch_op:
batch_op.alter_column('BatchID', new_column_name='id')
batch_op.alter_column('Discount', new_column_name='discount')
batch_op.alter_column('Name', new_column_name='name')
batch_op.alter_column('Name', new_column_name='name', type_=sa.Date(), nullable=False)
batch_op.alter_column('ProductID', new_column_name='product_id')
batch_op.alter_column('QuantityRemaining', new_column_name='quantity_remaining')
batch_op.alter_column('Rate', new_column_name='rate')
@ -118,7 +119,8 @@ def upgrade():
with op.batch_alter_table("recipes") as batch_op:
batch_op.alter_column('recipe_id', new_column_name='id')
with op.batch_alter_table("salary_deductions") as batch_op:
op.rename_table('salary_deductions', 'employee_benefit')
with op.batch_alter_table("employee_benefit") as batch_op:
batch_op.alter_column('SalaryDeductionID', new_column_name='id')
batch_op.alter_column('DaysWorked', new_column_name='days_worked')
batch_op.alter_column('EsiEmployee', new_column_name='esi_employee')
@ -131,6 +133,23 @@ def upgrade():
with op.batch_alter_table("vouchers") as batch_op:
batch_op.alter_column('VoucherID', new_column_name='id')
batch_op.alter_column('date', type_=sa.Date(), nullable=False)
batch_op.alter_column('reconcile_date', type_=sa.Date(), nullable=False)
op.rename_table('service_charges', 'incentives')
with op.batch_alter_table("employees") as batch_op:
batch_op.alter_column('Designation', new_column_name='designation', nullable=False)
batch_op.alter_column('Salary', new_column_name='salary', nullable=False)
batch_op.alter_column('ServicePoints', new_column_name='points', nullable=False)
batch_op.alter_column('JoiningDate', new_column_name='joining_date', type_=sa.Date(), nullable=False)
batch_op.alter_column('LeavingDate', new_column_name='leaving_date', type_=sa.Date(), nullable=True)
with op.batch_alter_table("settings") as batch_op:
batch_op.alter_column('SettingID', new_column_name='id')
batch_op.alter_column('Name', new_column_name='name')
batch_op.alter_column('Data', new_column_name='data')
op.create_unique_constraint(op.f('uq_accounts_name'), 'accounts', ['name'])
op.drop_constraint('accounts_name_key', 'accounts', type_='unique')
@ -162,9 +181,23 @@ def upgrade():
op.drop_constraint('products_Name_Units_key', 'products', type_='unique')
op.create_unique_constraint(op.f('uq_recipe_items_recipe_id'), 'recipe_items', ['recipe_id', 'product_id'])
op.drop_constraint('recipe_items_recipe_id_product_id_key', 'recipe_items', type_='unique')
op.create_unique_constraint(op.f('uq_settings_Name'), 'settings', ['Name'])
op.create_unique_constraint(op.f('uq_settings_name'), 'settings', ['name'])
op.drop_constraint('settings_Name_key', 'settings', type_='unique')
op.create_index(op.f('ix_vouchers_date'), 'vouchers', ['date'], unique=False)
op.create_foreign_key('fk_incentives_journal_id_journals', 'incentives', 'journals', ['journal_id'], ['id'])
op.create_foreign_key('fk_incentives_voucher_id_vouchers', 'incentives', 'vouchers', ['voucher_id'], ['id'])
op.drop_constraint('service_charges_journal_id_fkey', 'incentives', type_='foreignkey')
op.drop_constraint('service_charges_voucher_id_fkey', 'incentives', type_='foreignkey')
op.create_foreign_key('fk_employee_benefit_journal_id_journals', 'employee_benefit', 'journals', ['journal_id'], ['id'])
op.create_foreign_key('fk_employee_benefit_voucher_id_vouchers', 'employee_benefit', 'vouchers', ['voucher_id'], ['id'])
op.drop_constraint('entities_salarydeductions_JournalID_fkey', 'employee_benefit', type_='foreignkey')
op.drop_constraint('salary_deductions_VoucherID_fkey', 'employee_benefit', type_='foreignkey')
role = table('auth_roles', column('name', sa.String))
op.execute(role.update().where(role.c.name == op.inline_literal('Service Charge')).values({'name': op.inline_literal('Incentive')}))
op.execute(role.update().where(role.c.name == op.inline_literal('Salary Deduction')).values({'name': op.inline_literal('Employee Benefit')}))
account = table('accounts', column('name', sa.String))
op.execute(account.update().where(account.c.name == op.inline_literal('Service Charges')).values({'name': op.inline_literal('Incentives')}))
### end Alembic commands ###

@ -25,7 +25,7 @@ from brewman.models import (
Inventory,
Journal,
Product,
SalaryDeduction,
EmployeeBenefit,
Voucher,
VoucherType,
) # noqa

@ -16,7 +16,9 @@ from .routers import (
product_group,
recipe,
login,
journal
journal,
purchase,
purchase_return
)
from .routers.auth import client, user, role
from .routers.reports import (
@ -105,6 +107,8 @@ app.include_router(batch.router, prefix="/api/batch", tags=["vouchers"])
app.include_router(journal.router, prefix="/api/journal", tags=["vouchers"])
app.include_router(journal.router, prefix="/api/payment", tags=["vouchers"])
app.include_router(journal.router, prefix="/api/receipt", tags=["vouchers"])
app.include_router(purchase.router, prefix="/api/purchase", tags=["vouchers"])
app.include_router(purchase_return.router, prefix="/api/purchase-return", tags=["vouchers"])
def init():

@ -26,7 +26,7 @@ from .voucher import (
Inventory,
Journal,
Product,
SalaryDeduction,
EmployeeBenefit,
Voucher,
VoucherType,
)

@ -126,29 +126,6 @@ def purchase_return_update(voucher):
)
def inventory_valid(voucher):
if voucher.type in [2, 6, 3]:
if not len(voucher.inventories):
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Not enough inventories",
)
is_distinct_inventory(voucher.inventories)
def is_distinct_inventory(inventories):
found = set()
for item in inventories:
item_hash = hash(item.product_id)
if item_hash in found:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Duplicate inventories",
)
else:
found.add(item_hash)
def batch_valid(voucher):
pass

@ -9,7 +9,8 @@ from sqlalchemy import (
DateTime,
Numeric,
ForeignKey,
UniqueConstraint, Date,
UniqueConstraint,
Date,
)
from sqlalchemy.dialects.postgresql import BYTEA
from sqlalchemy.ext.hybrid import hybrid_property
@ -39,7 +40,7 @@ class VoucherType:
VoucherType(9, "Verification"),
VoucherType(10, "Opening Balance"),
VoucherType(11, "Closing Balance"),
VoucherType(12, "Salary Deduction"),
VoucherType(12, "Employee Benefit"),
VoucherType(13, "Incentive"),
]
return list
@ -63,10 +64,10 @@ class Voucher(Base):
__tablename__ = "vouchers"
id = Column("id", GUID(), primary_key=True, default=uuid.uuid4)
date = Column("date", DateTime, nullable=False, index=True)
date = Column("date", Date, nullable=False, index=True)
narration = Column("narration", Unicode(1000), nullable=False)
is_reconciled = Column("is_reconciled", Boolean, nullable=False)
reconcile_date = Column("reconcile_date", DateTime, nullable=False)
reconcile_date = Column("reconcile_date", Date, nullable=False)
is_starred = Column("is_starred", Boolean, nullable=False)
creation_date = Column("creation_date", DateTime(timezone=True), nullable=False)
last_edit_date = Column("last_edit_date", DateTime(timezone=True), nullable=False)
@ -86,14 +87,15 @@ class Voucher(Base):
cascade="delete, delete-orphan",
cascade_backrefs=False,
)
inventories = relationship(
"Inventory",
back_populates="voucher",
cascade="delete, delete-orphan",
cascade_backrefs=False,
)
salary_deductions = relationship(
"SalaryDeduction",
employee_benefits = relationship(
"EmployeeBenefit",
backref="voucher",
cascade="delete, delete-orphan",
cascade_backrefs=False,
@ -120,10 +122,6 @@ class Voucher(Base):
type = property(_get_type, _set_type)
type = synonym("_type", descriptor=type)
@property
def __name__(self):
return self.name
def __init__(
self,
date=None,
@ -134,25 +132,19 @@ class Voucher(Base):
posted=False,
creation_date=None,
last_edit_date=None,
type=None,
type_=None,
user_id=None,
poster_id=None,
):
self.date = date
self.is_reconciled = is_reconciled
self.reconcile_date = (
reconcile_date if reconcile_date and is_reconciled else date
)
self.reconcile_date = reconcile_date or date
self.is_starred = is_starred if is_starred is not None else False
self.narration = narration
self.posted = posted
self.creation_date = (
datetime.utcnow() if creation_date is None else creation_date
)
self.last_edit_date = (
datetime.utcnow() if last_edit_date is None else last_edit_date
)
self.type = type
self.creation_date = creation_date or datetime.utcnow()
self.last_edit_date = last_edit_date or datetime.utcnow()
self.type = type_
self.user_id = user_id
self.poster_id = poster_id
@ -199,8 +191,8 @@ class Journal(Base):
self.cost_centre_id = cost_centre_id
class SalaryDeduction(Base):
__tablename__ = "salary_deductions"
class EmployeeBenefit(Base):
__tablename__ = "employee_benefit"
id = Column("id", GUID(), primary_key=True, default=uuid.uuid4)
voucher_id = Column("voucher_id", GUID(), ForeignKey("vouchers.id"), nullable=False)
journal_id = Column("journal_id", GUID(), ForeignKey("journals.id"), nullable=False)
@ -213,14 +205,14 @@ class SalaryDeduction(Base):
journal = relationship(
Journal,
backref=backref("salary_deduction", uselist=False),
backref=backref("employee_benefit", uselist=False),
cascade=None,
cascade_backrefs=False,
)
def __init__(
self,
id=None,
id_=None,
voucher_id=None,
journal_id=None,
journal=None,
@ -231,7 +223,7 @@ class SalaryDeduction(Base):
esi_er=None,
pf_er=None,
):
self.id = id
self.id = id_
self.voucher_id = voucher_id
self.journal_id = journal_id
self.gross_salary = gross_salary
@ -304,17 +296,20 @@ class Inventory(Base):
tax=None,
discount=None,
batch=None,
product=None
):
self.id = id_
self.voucher_id = voucher_id
if product is None:
self.product_id = product_id
if batch is None:
self.batch_id = batch_id
self.quantity = quantity
self.rate = rate
self.tax = tax
self.discount = discount
if batch_id is None and batch is not None:
self.batch = batch
self.product = product
@hybrid_property
def amount(self):
@ -325,7 +320,7 @@ class Batch(Base):
__tablename__ = "batches"
id = Column("id", GUID(), primary_key=True, default=uuid.uuid4)
name = Column("name", DateTime)
name = Column("name", Date, nullable=False)
product_id = Column("product_id", GUID(), ForeignKey("products.id"), nullable=False)
quantity_remaining = Column("quantity_remaining", Numeric)
rate = Column("rate", Numeric)
@ -344,6 +339,7 @@ class Batch(Base):
rate=None,
tax=None,
discount=None,
product=None
):
self.name = name
self.product_id = product_id
@ -351,6 +347,9 @@ class Batch(Base):
self.rate = rate
self.tax = tax
self.discount = discount
if product is None:
self.product_id = product_id
self.product = product
def amount(self):
return (

@ -251,8 +251,8 @@ def delete_with_data(employee, db):
amount = (sus_jnl.debit * sus_jnl.amount) + (
acc_jnl.debit * acc_jnl.amount
)
if acc_jnl.salary_deduction is not None:
db.delete(acc_jnl.salary_deduction)
if acc_jnl.employee_benefit is not None:
db.delete(acc_jnl.employee_benefit)
db.delete(acc_jnl)
if amount == 0:
db.delete(sus_jnl)

@ -0,0 +1,108 @@
import traceback
import uuid
from typing import List
from fastapi import APIRouter, HTTPException, status, Depends, Security, UploadFile, File, Request
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from .voucher import issue_create_voucher, incentive_create_voucher, \
employee_benefit_create_voucher, voucher_info, check_voucher_lock_info, check_voucher_edit_allowed
from ..core.session import set_date
from ..schemas.auth import UserToken
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.voucher import Voucher
import brewman.schemas.voucher as schemas
router = APIRouter()
# Dependency
def get_db() -> Session:
try:
db = SessionLocal()
yield db
finally:
db.close()
@router.post("", response_model=schemas.Voucher)
def save_route(
request: Request,
data: schemas.Voucher,
db: Session = Depends(get_db),
files: List[UploadFile] = File(...),
user: UserToken = Security(get_user, scopes=["journal"]),
):
try:
item: Voucher = save(data, files, user, db)
db.commit()
set_date(request.session, data.date_)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
return voucher_info(item, db)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
except Exception:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=traceback.format_exc(),
)
def save(data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher:
check_voucher_lock_info(None, data.date_, db)
if data.type_ in ["Issue"]:
voucher = issue_create_voucher(data, files, user, db)
elif data.type_ in ["Employee Benefit"]:
voucher = employee_benefit_create_voucher(data, files, user, db)
elif data.type_ in ["Incentive"]:
voucher = incentive_create_voucher(data, files, user, db)
return voucher
@router.get("/{id_}")
def update_route(
id_: uuid.UUID,
request: Request,
data: schemas.Voucher,
db: Session = Depends(get_db),
files: List[UploadFile] = File(...),
user: UserToken = Security(get_user, scopes=["journal"]),
):
try:
item: Voucher = update(id_, data, files, user, db)
db.commit()
set_date(request.session, data.date_)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
return voucher_info(item, db)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
except Exception:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=traceback.format_exc(),
)
def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher:
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
check_voucher_lock_info(item.date, data.date_, db)
check_voucher_edit_allowed(item, user)
if data.type_ in ["Issue"]:
voucher = issue_update_voucher(item, data, files, user, db)
elif data.type_ in ["Employee Benefit"]:
voucher = employee_benefit_update_voucher(item, data, files, user, db)
elif data.type_ in ["Incentive"]:
voucher = incentive_update_voucher(item, data, files, user, db)
return voucher

@ -13,7 +13,8 @@ from ..schemas.auth import UserToken
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.voucher import Voucher, VoucherType, Journal
import brewman.schemas.voucher as schemas
import brewman.schemas.voucher as output
import brewman.schemas.input as schema_in
router = APIRouter()
@ -27,10 +28,10 @@ def get_db() -> Session:
db.close()
@router.post("", response_model=schemas.Voucher)
@router.post("", response_model=output.Voucher)
def save_route(
request: Request,
data: schemas.Voucher = Depends(schemas.Voucher.load_form),
data: schema_in.JournalIn = Depends(schema_in.JournalIn.load_form),
db: Session = Depends(get_db),
i: List[UploadFile] = File(None),
t: List[UploadFile] = File(None),
@ -39,7 +40,8 @@ def save_route(
try:
i = i or []
t = t or []
item: Voucher = save(data, i + t, user, db)
item: Voucher = save(data, user, db)
save_files(i + t, db)
db.commit()
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
info = voucher_info(item, db)
@ -58,14 +60,14 @@ def save_route(
)
def save(data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher:
def save(data: schema_in.JournalIn, user: UserToken, db: Session) -> Voucher:
check_voucher_lock_info(None, data.date_, db)
voucher = Voucher(
date=data.date_,
narration=data.narration,
is_starred=data.is_starred,
user_id=user.id_,
type=VoucherType.by_name(data.type_),
type_=VoucherType.by_name(data.type_),
)
db.add(voucher)
for item in data.journals:
@ -79,16 +81,20 @@ def save(data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Se
)
voucher.journals.append(journal)
db.add(journal)
# for key, value in files.items():
# db.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
return voucher
@router.put("/{id_}")
def save_files(files: List[UploadFile], db: Session):
# for key, value in files.items():
# db.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
pass
@router.put("/{id_}", response_model=output.Voucher)
def update_route(
id_: uuid.UUID,
request: Request,
data: schemas.Voucher,
data: schema_in.JournalIn = Depends(schema_in.JournalIn.load_form),
db: Session = Depends(get_db),
i: List[UploadFile] = File(None),
t: List[UploadFile] = File(None),
@ -97,10 +103,10 @@ def update_route(
try:
i = i or []
t = t or []
item: Voucher = update(id_, data, i + t, user, db)
item: Voucher = update(id_, data, user, db)
update_files(data, i + t, db)
db.commit()
set_date(data.date_, request.session)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
return voucher_info(item, db)
except SQLAlchemyError as e:
db.rollback()
@ -116,30 +122,7 @@ def update_route(
)
@router.get("/{id_}")
def get_id(
id_: uuid.UUID,
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["journal"]),
):
try:
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
return voucher_info(item, db)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
except Exception:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=traceback.format_exc(),
)
def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher:
def update(id_: uuid.UUID, data: schema_in.JournalIn, user: UserToken, db: Session) -> Voucher:
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)
@ -177,15 +160,42 @@ def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user:
)
db.add(journal)
voucher.journals.append(journal)
return voucher
def update_files(data: schema_in.VoucherIn, files: List[UploadFile], db: Session):
pass
# old_files = [
# uuid.UUID(f["id"]) for f in json["files"] if "id" in f and f["id"] is not None
# ]
# images = dbsession.query(DbImage).filter(DbImage.resource_id == voucher.id).all()
# images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all()
# for image in [i for i in images if i.id not in old_files]:
# dbsession.delete(image)
# db.delete(image)
# for key, value in files.items():
# dbsession.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) return voucher
# db.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
@router.get("/{id_}", response_model=output.Voucher)
def get_id(
id_: uuid.UUID,
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["journal"]),
):
try:
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
return voucher_info(item, db)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
except Exception:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=traceback.format_exc(),
)
@router.get("")

@ -11,7 +11,7 @@ from brewman.models.voucher import (
VoucherType,
Batch,
Inventory,
SalaryDeduction,
EmployeeBenefit,
Fingerprint,
Attendance,
DbImage,
@ -203,7 +203,7 @@ def delete_data(date, vouchers, dbsession):
dbsession.execute(Inventory.__table__.delete(Inventory.voucher_id.in_(sub_query)))
dbsession.execute(
SalaryDeduction.__table__.delete(SalaryDeduction.voucher_id.in_(sub_query))
EmployeeBenefit.__table__.delete(EmployeeBenefit.voucher_id.in_(sub_query))
)
dbsession.execute(
Incentive.__table__.delete(Incentive.voucher_id.in_(sub_query))

@ -1,21 +1,22 @@
import traceback
import uuid
from decimal import Decimal
from typing import List
from datetime import datetime
from fastapi import APIRouter, HTTPException, status, Depends, Security, UploadFile, File, Request
from sqlalchemy import func
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from .voucher import journal_create_voucher, purchase_create_voucher, issue_create_voucher, incentive_create_voucher, \
salary_deduction_create_voucher, voucher_info, check_voucher_lock_info, check_voucher_edit_allowed
from .voucher.purchase_return import purchase_return_create_voucher
from ..core.session import set_date
from .voucher import voucher_info, check_voucher_lock_info, check_voucher_edit_allowed, blank_voucher
from ..core.session import set_date, get_date
from ..models import Product, AccountBase
from ..schemas.auth import UserToken
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.voucher import Voucher, VoucherType, Batch, Inventory, Journal
import brewman.schemas.voucher as schemas
import brewman.schemas.voucher as output
import brewman.schemas.input as schema_in
router = APIRouter()
@ -29,10 +30,10 @@ def get_db() -> Session:
db.close()
@router.post("", response_model=schemas.Voucher)
@router.post("", response_model=output.Voucher)
def save_route(
request: Request,
data: schemas.Voucher = Depends(schemas.Voucher.load_form),
data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form),
db: Session = Depends(get_db),
i: List[UploadFile] = File(None),
t: List[UploadFile] = File(None),
@ -41,7 +42,10 @@ def save_route(
try:
i = i or []
t = t or []
item: Voucher = save(data, i + t, user, db)
item: Voucher = save(data, user, db)
save_inventories(item, data.inventories, db)
save_journals(item, data.vendor, db)
save_files(i + t, db)
db.commit()
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
info = voucher_info(item, db)
@ -60,82 +64,83 @@ def save_route(
)
def save(data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher:
def save(data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher:
check_voucher_lock_info(None, data.date_, db)
voucher = Voucher(
date=data.date_,
narration=data.narration,
is_starred=data.is_starred,
user_id=user.id_,
type=VoucherType.by_name(data.type_),
type_=VoucherType.by_name(data.type_),
)
db.add(voucher)
journals = {}
for item in data.inventories:
product: Product = db.query(Product).filter(Product.id == item.product.id_).first()
batch = Batch(name=data.date_, product_id=product.id, quantity_remaining=item.quantity, rate=item.rate, tax=item.tax,discount=item.discount)
db.add(batch)
inventory = Inventory(id=item.id_, product_id=product.id, batch=batch, quantity=item.quantity, rate=item.rate,tax=item.tax, discount=item.discount)
product.price = item.rate
voucher.inventories.append(inventory)
db.add(inventory)
if product.account_id not in journals:
journals[product.account_id] = {"account_id":product.account_id, "cost_centre_id": product.account.cost_centre_id, "debit": 1, "amount": inventory.amount}
else:
journals[product.account_id]["amount"] += inventory.amount
supplier = db.query(AccountBase).filter(AccountBase.id == data.journals[0].account.id_).first()
journals[supplier.id] = {"account_id":supplier.id, "cost_centre_id": supplier.cost_centre_id, "debit":-1, "amount": sum(round(x["amount"], 2) for x in journals)}
for item in journals:
journal = Journal(
debit=item["debit"],
account_id=item["account_id"],
cost_centre_id=item["cost_centre_id"],
amount=round(item["amount"], 2),
)
voucher.journals.append(item)
db.add(item)
journals_valid(voucher)
inventory_valid(voucher)
for key, value in files.items():
dbsession.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
return voucher
def purchase_create_journals(account_id: uuid.UUID, db: Session):
amount = 0
def save_inventories(voucher: Voucher, inventories: List[schema_in.Inventory], db: Session):
for item in inventories:
product = db.query(Product).filter(Product.id == item.product_id).first()
account = product.account
item_amount = round(item.amount, 2)
amount += item_amount
product: Product = db.query(Product).filter(Product.id == item.product.id_).first()
batch = Batch(name=voucher.date, product=product, quantity_remaining=item.quantity, rate=item.rate, tax=item.tax, discount=item.discount)
db.add(batch)
inventory = Inventory(id_=item.id_, product=product, batch=batch, quantity=item.quantity, rate=item.rate, tax=item.tax, discount=item.discount)
product.price = item.rate
voucher.inventories.append(inventory)
db.add(inventory)
def save_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session):
vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first()
journals = {}
amount = 0
for item in voucher.inventories:
account = item.product.account
amount += round(item.amount, 2)
if account.id in journals:
journals[account.id].amount += item_amount
journals[account.id].amount += round(item.amount, 2)
else:
journals[other_account.id] = Journal(
journals[account.id] = Journal(
debit=1,
cost_centre_id=account.cost_centre_id,
account_id=account.id,
amount=round(item.amount, 2),
)
journals[vendor.id] = Journal(
debit=-1,
cost_centre_id=other_account.cost_centre_id,
account_id=other_account.id,
cost_centre_id=vendor.cost_centre_id,
account_id=vendor.id,
amount=amount,
)
return list(journals.values())
for item in journals.values():
voucher.journals.append(item)
db.add(item)
@router.get("/{id_}")
def save_files(files: List[UploadFile], db: Session):
# for key, value in files.items():
# db.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
pass
@router.put("/{id_}", response_model=output.Voucher)
def update_route(
id_: uuid.UUID,
request: Request,
data: schemas.Voucher,
data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form),
db: Session = Depends(get_db),
files: List[UploadFile] = File(...),
user: UserToken = Security(get_user, scopes=["journal"]),
i: List[UploadFile] = File(None),
t: List[UploadFile] = File(None),
user: UserToken = Security(get_user, scopes=["purchase"]),
):
try:
item: Voucher = update(id_, data, files, user, db)
i = i or []
t = t or []
item: Voucher = update(id_, data, user, db)
update_inventory(item, data.inventories, db)
update_journals(item, data.vendor, db)
# journals_valid(voucher)
update_files(data, i + t, db)
db.commit()
set_date(request.session, data.date_)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
return voucher_info(item, db)
except SQLAlchemyError as e:
db.rollback()
@ -151,18 +156,178 @@ def update_route(
)
def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher:
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
check_voucher_lock_info(item.date, data.date_, db)
check_voucher_edit_allowed(item, user)
if data.type_ in ["Purchase"]:
voucher = purchase_update_voucher(item, data, files, user, db)
elif data.type_ in ["Purchase Return"]:
voucher = purchase_return_update_voucher(item, data, files, user, db)
elif data.type_ in ["Issue"]:
voucher = issue_update_voucher(item, data, files, user, db)
elif data.type_ in ["Salary Deduction"]:
voucher = salary_deduction_update_voucher(item, data, files, user, db)
elif data.type_ in ["Incentive"]:
voucher = incentive_update_voucher(item, data, files, user, db)
def update(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher:
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()
return voucher
def update_inventory(voucher: Voucher, new_inventories: List[schema_in.Inventory], db: Session):
for it in range(len(voucher.inventories), 0, -1):
item = voucher.inventories[it - 1]
found = False
for j in range(len(new_inventories), 0, -1):
new_inventory = new_inventories[j - 1]
if new_inventory.id_:
product = db.query(Product).filter(Product.id == new_inventory.product.id_).first()
found = True
if item.product_id != new_inventory.product.id_:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Product cannot be changed")
old_quantity = round(Decimal(item.quantity), 2)
quantity_remaining = round(Decimal(item.batch.quantity_remaining), 2)
if new_inventory.quantity < (old_quantity - quantity_remaining):
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"{old_quantity - quantity_remaining} is the minimum as it has been issued",
)
item.batch.quantity_remaining -= old_quantity - new_inventory.quantity
item.quantity = new_inventory.quantity
if voucher.date != item.batch.name:
item.batch.name = voucher.date
if voucher.date < item.batch.name:
# TODO: check for issued products which might have been in a back date
pass
item.rate = new_inventory.rate
item.batch.rate = new_inventory.rate
item.discount = new_inventory.discount
item.batch.discount = new_inventory.discount
item.tax = new_inventory.tax
item.batch.tax = new_inventory.tax
product.price = new_inventory.rate
new_inventories.remove(new_inventory)
# TODO: Update all references of the batch with the new rates
break
if not found:
has_been_issued = (
db.query(func.count(Inventory.id))
.filter(Inventory.batch_id == item.batch.id)
.filter(Inventory.id != item.id)
.scalar()
)
if has_been_issued > 0:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"{item.product.name} has been issued, it cannot be deleted",
)
else:
db.delete(item.batch)
db.delete(item)
voucher.inventories.remove(item)
for new_inventory in new_inventories:
product = db.query(Product).filter(Product.id == new_inventory.product.id_).first()
batch = Batch(
name=voucher.date,
product_id=product.id,
quantity_remaining=new_inventory.quantity,
rate=new_inventory.rate,
tax=new_inventory.tax,
discount=new_inventory.discount,
)
inventory = Inventory(
id_=None,
product_id=product.id,
batch=batch,
quantity=new_inventory.quantity,
rate=new_inventory.rate,
tax=new_inventory.tax,
discount=new_inventory.discount,
)
inventory.voucher_id = voucher.id
db.add(batch)
inventory.batch_id = batch.id
product.price = new_inventory.rate
voucher.inventories.append(inventory)
db.add(inventory)
def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db):
vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first()
journals = {}
amount = 0
for item in voucher.inventories:
product = db.query(Product).filter(Product.id == item.product_id).first()
account = product.account
amount += item.amount
if account.id in journals:
journals[account.id].amount += item.amount
else:
journals[account.id] = Journal(
debit=1,
cost_centre_id=account.cost_centre_id,
account_id=account.id,
amount=item.amount,
)
journals[vendor.id] = Journal(
debit=-1,
cost_centre_id=vendor.cost_centre_id,
account_id=vendor.id,
amount=amount,
)
for i in range(len(voucher.journals), 0, -1):
item = voucher.journals[i - 1]
if item.account_id in journals:
item.debit = journals[item.account_id].debit
item.amount = round(journals[item.account_id].amount, 2)
item.cost_centre_id = journals[item.account_id].cost_centre_id
del journals[item.account_id]
else:
db.delete(item)
voucher.journals.remove(item)
for item in journals.values():
item.amount = round(item.amount, 2)
voucher.journals.append(item)
db.add(item)
def update_files(data: schema_in.PurchaseIn, files: List[UploadFile], db: Session):
pass
# old_files = [
# uuid.UUID(f["id"]) for f in json["files"] if "id" in f and f["id"] is not None
# ]
# images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all()
# for image in [i for i in images if i.id not in old_files]:
# db.delete(image)
# for key, value in files.items():
# db.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
@router.get("/{id_}", response_model=output.Voucher)
def get_id(
id_: uuid.UUID,
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["purchase"]),
):
try:
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
return voucher_info(item, db)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
except Exception:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=traceback.format_exc(),
)
@router.get("")
def show_blank(
request: Request,
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["purchase"]),
):
additional_info = {"date": get_date(request.session), "type": "Purchase"}
return blank_voucher(additional_info, db)

@ -1,20 +1,22 @@
import traceback
import uuid
from decimal import Decimal
from typing import List
from datetime import datetime
from fastapi import APIRouter, HTTPException, status, Depends, Security, UploadFile, File, Request
from sqlalchemy import func
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from .voucher import journal_create_voucher, purchase_create_voucher, issue_create_voucher, incentive_create_voucher, \
salary_deduction_create_voucher, voucher_info, check_voucher_lock_info, check_voucher_edit_allowed
from .voucher.purchase_return import purchase_return_create_voucher
from ..core.session import set_date
from .voucher import voucher_info, check_voucher_lock_info, check_voucher_edit_allowed, blank_voucher
from ..core.session import set_date, get_date
from ..models import Product, AccountBase
from ..schemas.auth import UserToken
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.voucher import Voucher
import brewman.schemas.voucher as schemas
from ..models.voucher import Voucher, VoucherType, Batch, Inventory, Journal
import brewman.schemas.voucher as output
import brewman.schemas.input as schema_in
router = APIRouter()
@ -28,20 +30,26 @@ def get_db() -> Session:
db.close()
@router.post("", response_model=schemas.Voucher)
@router.post("", response_model=output.Voucher)
def save_route(
request: Request,
data: schemas.Voucher,
data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form),
db: Session = Depends(get_db),
files: List[UploadFile] = File(...),
user: UserToken = Security(get_user, scopes=["journal"]),
i: List[UploadFile] = File(None),
t: List[UploadFile] = File(None),
user: UserToken = Security(get_user, scopes=["purchase-return"]),
):
try:
item: Voucher = save(data, files, user, db)
i = i or []
t = t or []
item: Voucher = save(data, user, db)
save_inventories(item, data.inventories, db)
save_journals(item, data.vendor, db)
save_files(i + t, db)
db.commit()
set_date(request.session, data.date_)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
return voucher_info(item, db)
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
info = voucher_info(item, db)
return info
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
@ -56,35 +64,95 @@ def save_route(
)
def save(data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher:
def save(data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher:
check_voucher_lock_info(None, data.date_, db)
if data.type_ in ["Purchase"]:
voucher = purchase_create_voucher(data, files, user, db)
elif data.type_ in ["Purchase Return"]:
voucher = purchase_return_create_voucher(data, files, user, db)
elif data.type_ in ["Issue"]:
voucher = issue_create_voucher(data, files, user, db)
elif data.type_ in ["Salary Deduction"]:
voucher = salary_deduction_create_voucher(data, files, user, db)
elif data.type_ in ["Incentive"]:
voucher = incentive_create_voucher(data, files, user, db)
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)
return voucher
@router.get("/{id_}")
def save_inventories(voucher: Voucher, inventories: List[schema_in.Inventory], db: Session):
for item in inventories:
batch = db.query(Batch).filter(Batch.id == item.batch.id_).first()
if item.quantity > batch.quantity_remaining:
raise ValueError(f"Maximum quantity is {batch.quantity_remaining}.")
if batch.name > voucher.date:
raise ValueError(f"Return date cannot be before {batch.product.name.strftime('%d-%b-%Y')}")
batch.quantity_remaining -= item.quantity
item = Inventory(
product=batch.product,
quantity=item.quantity,
rate=batch.rate,
tax=batch.tax,
discount=batch.discount,
batch=batch,
)
voucher.inventories.append(item)
db.add(item)
def save_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session):
vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first()
journals = {}
amount = 0
for item in voucher.inventories:
account = item.product.account
amount += round(item.amount, 2)
if account.id in journals:
journals[account.id].amount += round(item.amount, 2)
else:
journals[account.id] = Journal(
debit=-1,
cost_centre_id=account.cost_centre_id,
account_id=account.id,
amount=round(item.amount, 2),
)
journals[vendor.id] = Journal(
debit=1,
cost_centre_id=vendor.cost_centre_id,
account_id=vendor.id,
amount=amount,
)
for item in journals.values():
voucher.journals.append(item)
db.add(item)
def save_files(files: List[UploadFile], db: Session):
# for key, value in files.items():
# db.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
pass
@router.put("/{id_}", response_model=output.Voucher)
def update_route(
id_: uuid.UUID,
request: Request,
data: schemas.Voucher,
data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form),
db: Session = Depends(get_db),
files: List[UploadFile] = File(...),
user: UserToken = Security(get_user, scopes=["journal"]),
i: List[UploadFile] = File(None),
t: List[UploadFile] = File(None),
user: UserToken = Security(get_user, scopes=["purchase-return"]),
):
try:
item: Voucher = update(id_, data, files, user, db)
i = i or []
t = t or []
item: Voucher = update(id_, data, user, db)
update_inventory(item, data.inventories, db)
update_journals(item, data.vendor, db)
# journals_valid(voucher)
update_files(data, i + t, db)
db.commit()
set_date(request.session, data.date_)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
return voucher_info(item, db)
except SQLAlchemyError as e:
db.rollback()
@ -100,18 +168,129 @@ def update_route(
)
def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher:
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
check_voucher_lock_info(item.date, data.date_, db)
check_voucher_edit_allowed(item, user)
if data.type_ in ["Purchase"]:
voucher = purchase_update_voucher(item, data, files, user, db)
elif data.type_ in ["Purchase Return"]:
voucher = purchase_return_update_voucher(item, data, files, user, db)
elif data.type_ in ["Issue"]:
voucher = issue_update_voucher(item, data, files, user, db)
elif data.type_ in ["Salary Deduction"]:
voucher = salary_deduction_update_voucher(item, data, files, user, db)
elif data.type_ in ["Incentive"]:
voucher = incentive_update_voucher(item, data, files, user, db)
def update(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher:
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()
return voucher
def update_inventory(voucher: Voucher, new_inventories: List[schema_in.Inventory], db: Session):
for it in range(len(voucher.inventories), 0, -1):
item = voucher.inventories[it - 1]
found = False
for j in range(len(new_inventories), 0, -1):
new_inventory = new_inventories[j - 1]
if new_inventory.id_:
found = True
if item.product_id != new_inventory.product.id_:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Product cannot be changed")
old_quantity = round(Decimal(item.quantity), 2)
quantity_remaining = round(Decimal(item.batch.quantity_remaining), 2)
if new_inventory.quantity - old_quantity > quantity_remaining:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"{old_quantity + quantity_remaining} is the maximum for {item.product.full_name}.",
)
if item.batch.name > voucher.date:
raise ValueError(f"Voucher cannot be before {item.product.name.strftime('%d-%b-%Y')}")
item.batch.quantity_remaining -= new_inventory.quantity - old_quantity
item.quantity = new_inventory.quantity
new_inventories.remove(new_inventory)
break
if not found:
item.batch.quantity_remaining += item.quantity
voucher.inventories.remove(item)
db.delete(item)
save_inventories(voucher, new_inventories, db)
def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db):
vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first()
journals = {}
amount = 0
for item in voucher.inventories:
account = item.product.account
amount += item.amount
if account.id in journals:
journals[account.id].amount += item.amount
else:
journals[account.id] = Journal(
debit=-1,
cost_centre_id=account.cost_centre_id,
account_id=account.id,
amount=item.amount,
)
journals[vendor.id] = Journal(
debit=1,
cost_centre_id=vendor.cost_centre_id,
account_id=vendor.id,
amount=amount,
)
for i in range(len(voucher.journals), 0, -1):
item = voucher.journals[i - 1]
if item.account_id in journals:
item.debit = journals[item.account_id].debit
item.amount = round(journals[item.account_id].amount, 2)
item.cost_centre_id = journals[item.account_id].cost_centre_id
del journals[item.account_id]
else:
db.delete(item)
voucher.journals.remove(item)
for item in journals.values():
item.amount = round(item.amount, 2)
voucher.journals.append(item)
db.add(item)
def update_files(data: schema_in.PurchaseIn, files: List[UploadFile], db: Session):
pass
# old_files = [
# uuid.UUID(f["id"]) for f in json["files"] if "id" in f and f["id"] is not None
# ]
# images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all()
# for image in [i for i in images if i.id not in old_files]:
# db.delete(image)
# for key, value in files.items():
# db.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
@router.get("/{id_}", response_model=output.Voucher)
def get_id(
id_: uuid.UUID,
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["purchase-return"]),
):
try:
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
return voucher_info(item, db)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
except Exception:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=traceback.format_exc(),
)
@router.get("")
def show_blank(
request: Request,
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["purchase-return"]),
):
additional_info = {"date": get_date(request.session), "type": "Purchase"}
return blank_voucher(additional_info, db)

@ -23,12 +23,10 @@ from brewman.models.voucher import (
)
from brewman.routers import get_lock_info
from .issue import issue_create_voucher, issue_update_voucher
from .purchase import purchase_create_voucher, purchase_update_voucher
from .incentive import incentive_create_voucher, incentive_update_voucher
from .salary_deduction import salary_deduction_create_voucher, salary_deduction_update_voucher
from .employee_benefit import employee_benefit_create_voucher, employee_benefit_update_voucher
from ...core.session import get_first_day
from fastapi import APIRouter, Depends, Security, Request, HTTPException, status
from ...schemas.auth import UserToken
@ -36,7 +34,6 @@ from ...schemas.auth import UserToken
router = APIRouter()
@router.post("/api/voucher/{id}") # , request_param="p" "Post Vouchers"
def voucher_post(request):
user = (
@ -195,6 +192,10 @@ def voucher_info(voucher, db):
json_voucher["reconcileDate"] = voucher.reconcile_date.strftime(
"%d-%b-%Y"
)
if voucher.type == 2: # "Purchase"
item = [j for j in voucher.journals if j.debit == -1][0]
json_voucher["vendor"] = {"id": item.account.id, "name": item.account.name}
else:
for item in voucher.journals:
json_voucher["journals"].append(
{
@ -205,7 +206,7 @@ def voucher_info(voucher, db):
"costCentre": {"id": item.cost_centre_id},
}
)
for item in voucher.salary_deductions:
for item in voucher.employee_benefits:
json_voucher["employeeBenefits"].append(
{
"grossSalary": item.gross_salary,
@ -251,13 +252,7 @@ def voucher_info(voucher, db):
if x.account_id == Account.incentive_id()
][0]
for item in voucher.inventories:
text = "{0} ({1}) {2:.2f}@{3:.2f} from {4}".format(
item.product.name,
item.product.units,
item.batch.quantity_remaining,
item.batch.rate,
item.batch.name.strftime("%d-%b-%Y"),
)
text = f"{item.product.name} ({item.product.units}) {item.batch.quantity_remaining:.2f}@{item.batch.rate:.2f} from {item.batch.name.strftime('%d-%b-%Y')}"
json_voucher["inventories"].append(
{
"id": item.id,
@ -294,12 +289,12 @@ def voucher_info(voucher, db):
return json_voucher
def blank_voucher(info, dbsession):
def blank_voucher(info, db):
if "type" not in info:
raise ValidationError("Voucher Type is null")
raise ValueError("Voucher Type is null")
type_ = info["type"]
if "date" not in info:
raise ValidationError("Date cannot be null")
raise ValueError("Date cannot be null")
json_voucher = {
"type": type_,
"date": info["date"],
@ -313,9 +308,9 @@ def blank_voucher(info, dbsession):
pass
elif type_ == "Payment":
account = None
if info is not None and "account" in info and info["account"] is not None:
if info and "account" in info and info["account"]:
account = (
dbsession.query(AccountBase)
db.query(AccountBase)
.filter(AccountBase.id == uuid.UUID(info["account"]))
.first()
)
@ -326,9 +321,9 @@ def blank_voucher(info, dbsession):
json_voucher["journals"].append({"account": account, "amount": 0, "debit": -1})
elif type_ == "Receipt":
account = None
if info is not None and "account" in info and info["account"] is not None:
if info and "account" in info and info["account"]:
account = (
dbsession.query(AccountBase)
db.query(AccountBase)
.filter(AccountBase.id == uuid.UUID(info["account"]))
.first()
)
@ -338,9 +333,8 @@ def blank_voucher(info, dbsession):
account = AccountBase.cash_in_hand()
json_voucher["journals"].append({"account": account, "amount": 0, "debit": 1})
elif type_ == "Purchase":
json_voucher["journals"].append(
{"account": AccountBase.local_purchase(), "amount": 0, "debit": -1}
)
json_voucher["vendor"] = AccountBase.local_purchase()
elif type_ == "Purchase Return":
json_voucher["journals"].append(
{"account": AccountBase.local_purchase(), "amount": 0, "debit": 1}
@ -392,7 +386,7 @@ def blank_voucher(info, dbsession):
}
)
elif type_ == "Salary Deduction":
elif type_ == "Employee Benefit":
json_voucher["employeeBenefits"] = []
elif type_ == "Incentive":
json_voucher["incentives"], json_voucher[

@ -4,17 +4,17 @@ import uuid
from brewman.core.session import get_last_day
from brewman.models.master import AccountBase, Employee
from brewman.models.voucher import Journal, Voucher, VoucherType, SalaryDeduction
from brewman.models.voucher import Journal, Voucher, VoucherType, EmployeeBenefit
def salary_deduction_create_voucher(json, user, dbsession):
def employee_benefit_create_voucher(json, user, dbsession):
dt = get_last_day(datetime.datetime.strptime(json["date"], "%d-%b-%Y"))
days_in_month = dt.day
voucher = Voucher(date=dt, user_id=user.id, type=VoucherType.by_id(12))
dbsession.add(voucher)
exp, total = 0, 0
for item in json["employeeBenefits"]:
item_exp, item_total = add_salary_deduction(
item_exp, item_total = add_employee_benefit(
item, days_in_month, voucher, dbsession
)
exp += item_exp
@ -50,10 +50,10 @@ def salary_deduction_create_voucher(json, user, dbsession):
return voucher
def salary_deduction_update_voucher(voucher, json, user, dbsession):
def employee_benefit_update_voucher(voucher, json, user, dbsession):
dt = get_last_day(datetime.datetime.strptime(json["date"], "%d-%b-%Y"))
if dt != voucher.date.date():
raise ValidationError("Date Cannot be changed for Salary Deduction voucher!")
raise ValidationError("Date Cannot be changed for Employee Benefit voucher!")
days_in_month = voucher.date.day
voucher.user_id = user.id
voucher.posted = False
@ -61,8 +61,8 @@ def salary_deduction_update_voucher(voucher, json, user, dbsession):
new_deductions = json["employeeBenefits"]
exp, total, journals = 0, 0, []
for i in range(len(voucher.salary_deductions), 0, -1):
item = voucher.salary_deductions[i - 1]
for i in range(len(voucher.employee_benefits), 0, -1):
item = voucher.employee_benefits[i - 1]
found = False
for j in range(len(new_deductions), 0, -1):
new_item = new_deductions[j - 1]
@ -77,10 +77,10 @@ def salary_deduction_update_voucher(voucher, json, user, dbsession):
new_deductions.remove(new_item)
break
if not found:
voucher.salary_deductions.remove(item)
voucher.employee_benefits.remove(item)
voucher.journals.remove(item.journal)
for new_item in new_deductions:
item_exp, item_total = add_salary_deduction(
item_exp, item_total = add_employee_benefit(
new_item, days_in_month, voucher, dbsession
)
exp += item_exp
@ -101,7 +101,7 @@ def salary_deduction_update_voucher(voucher, json, user, dbsession):
return voucher
def add_salary_deduction(item, days_in_month, voucher, dbsession):
def add_employee_benefit(item, days_in_month, voucher, dbsession):
account = (
dbsession.query(Employee)
.filter(Employee.id == uuid.UUID(item["journal"]["account"]["id"]))
@ -119,7 +119,7 @@ def add_salary_deduction(item, days_in_month, voucher, dbsession):
account_id=account.id,
cost_centre_id=account.cost_centre_id,
)
sd = SalaryDeduction(
sd = EmployeeBenefit(
journal=journal,
gross_salary=gross_salary,
days_worked=days_worked,
@ -129,7 +129,7 @@ def add_salary_deduction(item, days_in_month, voucher, dbsession):
pf_er=pf_er,
)
voucher.journals.append(journal)
voucher.salary_deductions.append(sd)
voucher.employee_benefits.append(sd)
dbsession.add(journal)
dbsession.add(sd)
return esi_er + pf_er, esi_both + pf_both

@ -16,15 +16,11 @@ class EmptyVoucher(object):
account = self.request.GET.get("a", None)
return self.get_blank({"account": account})
@router.get("/api/voucher", request_param="t=Purchase") # "Purchase"
def purchase(self):
return self.get_blank()
@router.get("/api/voucher", request_param="t=Purchase Return") # "Purchase Return"
def purchase_return(self):
return self.get_blank()
@router.get("/api/voucher", request_param="t=Salary Deduction") # "Purchase Return"
@router.get("/api/voucher", request_param="t=Employee Benefit") # "Purchase Return"
def purchase_return(self):
return self.get_blank()

@ -5,7 +5,6 @@ import uuid
from fastapi import HTTPException, status
from brewman.models.master import CostCentre, AccountBase
from brewman.models.operations import inventory_valid
from brewman.models.voucher import Voucher, VoucherType, Batch, Inventory, Journal

@ -1,192 +0,0 @@
import datetime
import uuid
from decimal import Decimal
from fastapi import HTTPException, status
from sqlalchemy import func
from brewman.models.master import Product, AccountBase
from brewman.models.operations import inventory_valid
from brewman.models.voucher import (
Voucher,
VoucherType,
Batch,
Inventory,
Journal,
DbImage,
)
def purchase_update_voucher(voucher, json, files, user, dbsession):
voucher.date = datetime.datetime.strptime(json["date"], "%d-%b-%Y")
voucher.is_starred = json["isStarred"]
voucher.narration = json["narration"].strip()
voucher.user_id = user.id
voucher.posted = False
voucher.last_edit_date = datetime.datetime.utcnow()
purchase_update_inventory(voucher, json["inventories"], dbsession)
purchase_update_journals(voucher, json["journals"], dbsession)
journals_valid(voucher)
inventory_valid(voucher)
old_files = [
uuid.UUID(f["id"]) for f in json["files"] if "id" in f and f["id"] is not None
]
images = dbsession.query(DbImage).filter(DbImage.resource_id == voucher.id).all()
for image in [i for i in images if i.id not in old_files]:
dbsession.delete(image)
for key, value in files.items():
dbsession.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
return voucher
def purchase_update_inventory(voucher, new_inventories, dbsession):
for it in range(len(voucher.inventories), 0, -1):
item = voucher.inventories[it - 1]
found = False
for j in range(len(new_inventories), 0, -1):
i = new_inventories[j - 1]
if "id" in i and i["id"] is not None and item.id == uuid.UUID(i["id"]):
product = (
dbsession.query(Product)
.filter(Product.id == uuid.UUID(i["product"]["id"]))
.first()
)
found = True
if item.product_id != product.id:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Product cannot be changed",
)
new_quantity = round(Decimal(i["quantity"]), 2)
old_quantity = round(Decimal(item.quantity), 2)
quantity_remaining = round(Decimal(item.batch.quantity_remaining), 2)
if new_quantity <= 0:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Quantity of {item.product.name} cannot be zero",
)
if old_quantity > new_quantity and quantity_remaining < (
old_quantity - new_quantity
):
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"{old_quantity - quantity_remaining} has been issued, minimum quantity is",
)
item.batch.quantity_remaining -= old_quantity - new_quantity
item.quantity = new_quantity
rate = round(Decimal(i["rate"]), 2)
discount = round(Decimal(i["discount"]), 5)
tax = round(Decimal(i["tax"]), 5)
if voucher.date != item.batch.name:
item.batch.name = voucher.date
if voucher.date < item.batch.name:
# TODO: check for issued products which might have been in a back date
pass
item.rate = rate
item.batch.rate = rate
item.discount = discount
item.batch.discount = discount
item.tax = tax
item.batch.tax = tax
product.price = rate
new_inventories.remove(i)
# TODO: Update all references of the batch with the new rates
break
if not found:
uses = (
dbsession.query(func.count(Inventory.id))
.filter(Inventory.batch_id == item.batch.id)
.filter(Inventory.id != item.id)
.scalar()
)
if uses > 0:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"{item.product.name} has been issued, it cannot be deleted",
)
else:
dbsession.delete(item.batch)
dbsession.delete(item)
voucher.inventories.remove(item)
for i in new_inventories:
product = (
dbsession.query(Product)
.filter(Product.id == uuid.UUID(i["product"]["id"]))
.first()
)
new_quantity = round(Decimal(i["quantity"]), 2)
rate = round(Decimal(i["rate"]), 2)
tax = round(Decimal(i["tax"]), 5)
discount = round(Decimal(i["discount"]), 5)
batch = Batch(
name=voucher.date,
product_id=product.id,
quantity_remaining=new_quantity,
rate=rate,
tax=tax,
discount=discount,
)
inventory = Inventory(
id=None,
product_id=product.id,
batch=batch,
quantity=new_quantity,
rate=rate,
tax=tax,
discount=discount,
)
inventory.voucher_id = voucher.id
dbsession.add(batch)
inventory.batch_id = batch.id
product.price = rate
voucher.inventories.append(inventory)
dbsession.add(inventory)
def purchase_update_journals(voucher, journals, dbsession):
other_account = [ff for ff in journals if ff["debit"] == -1]
other_account = (
dbsession.query(AccountBase)
.filter(AccountBase.id == uuid.UUID(other_account[0]["account"]["id"]))
.first()
)
journals = dict()
amount = 0
for item in voucher.inventories:
product = dbsession.query(Product).filter(Product.id == item.product_id).first()
account = product.account
amount += item.amount
if account.id in journals:
journals[account.id].amount += item.amount
else:
journals[account.id] = Journal(
debit=1,
cost_centre_id=account.cost_centre_id,
account_id=account.id,
amount=item.amount,
)
journals[other_account.id] = Journal(
debit=-1,
cost_centre_id=other_account.cost_centre_id,
account_id=other_account.id,
amount=amount,
)
for i in range(len(voucher.journals), 0, -1):
item = voucher.journals[i - 1]
if item.account_id in journals:
item.debit = journals[item.account_id].debit
item.amount = round(journals[item.account_id].amount, 2)
item.cost_centre_id = journals[item.account_id].cost_centre_id
del journals[item.account_id]
else:
dbsession.delete(item)
voucher.journals.remove(item)
for item in journals.values():
item.amount = round(item.amount, 2)
voucher.journals.append(item)
dbsession.add(item)

@ -1,218 +0,0 @@
import datetime
import uuid
from decimal import Decimal
from brewman.models.master import Product, AccountBase
from brewman.models.operations import inventory_valid, journals_valid
from brewman.models.validation_exception import ValidationError
from brewman.models.voucher import (
Voucher,
VoucherType,
Batch,
Inventory,
Journal,
DbImage,
)
def purchase_return_create_voucher(json, files, user, dbsession):
dt = datetime.datetime.strptime(json["date"], "%d-%b-%Y")
voucher = Voucher(
date=dt,
narration=json["narration"].strip(),
is_starred=json["isStarred"],
user_id=user.id,
type=VoucherType.by_name(json["type"]),
)
dbsession.add(voucher)
for item in json["inventories"]:
purchase_return_create_inventory(voucher, item, dbsession)
for item in purchase_return_create_journals(
voucher.inventories, json["journals"][0]["account"]["id"], dbsession
):
voucher.journals.append(item)
dbsession.add(item)
journals_valid(voucher)
inventory_valid(voucher)
for key, value in files.items():
dbsession.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
return voucher
def purchase_return_create_inventory(voucher, item, dbsession):
batch = (
dbsession.query(Batch)
.filter(Batch.id == uuid.UUID(item["batch"]["id"]))
.first()
)
inventory_id = (
uuid.UUID(item["id"]) if "id" in item and item["id"] is not None else None
)
quantity = Decimal(item["quantity"])
if quantity <= 0:
raise ValidationError(f"Quantity of {item.product.name} cannot be zero")
if quantity > batch.quantity_remaining:
raise ValidationError(f"Quantity available is {batch.quantity_remaining} only")
if batch.name > voucher.date:
raise ValidationError("Batch of {batch.product.name} was purchased after the issue date")
batch.quantity_remaining -= quantity
item = Inventory(
id=inventory_id,
product_id=batch.product.id,
quantity=quantity,
rate=batch.rate,
tax=batch.tax,
discount=batch.discount,
batch=batch,
)
voucher.inventories.append(item)
dbsession.add(item)
def purchase_return_create_journals(inventories, account_id, dbsession):
other_account = (
dbsession.query(AccountBase)
.filter(AccountBase.id == uuid.UUID(account_id))
.first()
)
journals = dict()
amount = 0
for item in inventories:
product = dbsession.query(Product).filter(Product.id == item.product_id).first()
account = product.account
amount += round(item.amount, 2)
if account.id in journals:
journals[account.id].amount += round(item.amount, 2)
else:
journals[account.id] = Journal(
debit=-1,
cost_centre_id=account.cost_centre_id,
account_id=account.id,
amount=round(item.amount, 2),
)
journals[other_account.id] = Journal(
debit=1,
cost_centre_id=other_account.cost_centre_id,
account_id=other_account.id,
amount=amount,
)
return list(journals.values())
def purchase_return_update_voucher(voucher, json, files, user, dbsession):
voucher.date = datetime.datetime.strptime(json["date"], "%d-%b-%Y")
voucher.is_starred = json["isStarred"]
voucher.narration = json["narration"].strip()
voucher.user_id = user.id
voucher.posted = False
voucher.last_edit_date = datetime.datetime.utcnow()
purchase_return_update_inventory(
voucher, json["inventories"], json["date"], dbsession
)
purchase_return_update_journals(voucher, json["journals"], dbsession)
journals_valid(voucher)
inventory_valid(voucher)
old_files = [
uuid.UUID(f["id"]) for f in json["files"] if "id" in f and f["id"] is not None
]
images = dbsession.query(DbImage).filter(DbImage.resource_id == voucher.id).all()
for image in [i for i in images if i.id not in old_files]:
dbsession.delete(image)
for key, value in files.items():
dbsession.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
return voucher
def purchase_return_update_inventory(voucher, new_inventories, date, dbsession):
for it in range(len(voucher.inventories), 0, -1):
item = voucher.inventories[it - 1]
found = False
for j in range(len(new_inventories), 0, -1):
i = new_inventories[j - 1]
if "id" in i and i["id"] is not None and item.id == uuid.UUID(i["id"]):
product = (
dbsession.query(Product)
.filter(Product.id == uuid.UUID(i["product"]["id"]))
.first()
)
found = True
if item.product_id != product.id:
raise ValidationError("Product cannot be changed")
quantity = round(Decimal(i["quantity"]), 2)
if quantity == 0:
raise ValidationError(
"Quantity of {0} cannot be zero".format(item.product.name)
)
if quantity - item.quantity > item.batch.quantity_remaining:
raise ValidationError(
"Maximum quantity available for {0} is {1}".format(
item.product.full_name,
item.quantity + item.batch.quantity_remaining,
)
)
if item.batch.name > voucher.date:
raise ValidationError(
"Batch of {0} was purchased after the issue date".format(
item.product.name
)
)
item.batch.quantity_remaining -= quantity - item.quantity
item.quantity = quantity
new_inventories.remove(i)
break
if not found:
item.batch.quantity_remaining += item.quantity
voucher.inventories.remove(item)
dbsession.delete(item)
for i in new_inventories:
purchase_return_create_inventory(voucher, i, dbsession)
def purchase_return_update_journals(voucher, journals, dbsession):
other_account = [ff for ff in journals if ff["debit"] == 1]
other_account = (
dbsession.query(AccountBase)
.filter(AccountBase.id == uuid.UUID(other_account[0]["account"]["id"]))
.first()
)
journals = dict()
amount = 0
for item in voucher.inventories:
product = dbsession.query(Product).filter(Product.id == item.product_id).first()
account = product.account
amount += round(item.amount, 2)
if account.id in journals:
journals[account.id].amount += round(item.amount, 2)
else:
journals[account.id] = Journal(
debit=-1,
cost_centre_id=account.cost_centre_id,
account_id=account.id,
amount=round(item.amount, 2),
)
journals[other_account.id] = Journal(
debit=1,
cost_centre_id=other_account.cost_centre_id,
account_id=other_account.id,
amount=amount,
)
for i in range(len(voucher.journals), 0, -1):
item = voucher.journals[i - 1]
if item.account_id in journals:
item.debit = journals[item.account_id].debit
item.amount = journals[item.account_id].amount
item.cost_centre_id = journals[item.account_id].cost_centre_id
del journals[item.account_id]
else:
dbsession.delete(item)
voucher.journals.remove(item)
for item in journals.values():
item.amount = item.amount
voucher.journals.append(item)
dbsession.add(item)

95
brewman/schemas/input.py Normal file

@ -0,0 +1,95 @@
import uuid
import json
from datetime import datetime, date
from decimal import Decimal
from typing import List, Optional
from fastapi import Form
from pydantic import validator
from sqlalchemy.orm import Session
from brewman.schemas import to_camel
from brewman.schemas.master import AccountLink, CostCentreLink, ProductLink
from brewman.schemas.voucher import VoucherIn, Journal, Inventory
class JournalIn(VoucherIn):
journals: List[Journal]
class Config:
anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {
date: lambda v: v.strftime("%d-%b-%Y"),
datetime: lambda v: v.strftime("%d-%b-%Y %H:%I")
}
@validator("date_", pre=True)
def parse_date(cls, value):
if isinstance(value, date):
return value
return datetime.strptime(value, "%d-%b-%Y").date()
@validator("journals")
def validate_signed_amount(cls, value: List[Journal]):
if sum(x.debit * x.amount for x in value) != 0:
raise ValueError("Journal amounts do no match")
return value
@validator("journals")
def is_distinct(cls, value: List[Journal]):
journal_set = set(hash(x.account.id_) ^ hash(None if x.cost_centre is None else x.cost_centre.id_) for x in value)
if len(value) != len(journal_set):
raise ValueError("Duplicate journals")
return value
@classmethod
def load_form(cls, data: str = Form(...)):
json_data = json.loads(data)
return cls.parse_obj(json_data)
class PurchaseIn(VoucherIn):
vendor: AccountLink
inventories: List[Inventory]
class Config:
anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {
date: lambda v: v.strftime("%d-%b-%Y"),
datetime: lambda v: v.strftime("%d-%b-%Y %H:%I")
}
@validator("date_", pre=True)
def parse_date(cls, value):
if isinstance(value, date):
return value
return datetime.strptime(value, "%d-%b-%Y").date()
@validator("inventories") # For Purchase, Issue and Return Vouchers
def validate_enough_inventories(cls, value: List[Inventory]):
if len(value) < 1:
raise ValueError("Not enough inventories")
return value
@validator("inventories") # For Purchase, Issue and Return Vouchers
def validate_inventories_unique(cls, value: List[Inventory]):
if len(set(x.product.id_ for x in value)) != len(value):
raise ValueError("Duplicate products")
return value
@classmethod
def load_form(cls, data: str = Form(...)):
json_data = json.loads(data)
return cls.parse_obj(json_data)
def validate_inventory(self, date_: date, old_inv: List[Inventory], db: Session):
for it in range(len(old_inv), 0, -1):
item = old_inv[it - 1]
found = False
for j in range(len(self.inventories), 0, -1):
i = self.inventories[j - 1]
pass

@ -2,7 +2,7 @@ import uuid
import json
from datetime import datetime, date
from decimal import Decimal
from typing import List, Optional
from typing import List, Optional, Any
from fastapi import Form
from pydantic import BaseModel, validator, Field
@ -11,6 +11,14 @@ from brewman.schemas import to_camel
from brewman.schemas.master import AccountLink, CostCentreLink, ProductLink
class UserLink(BaseModel):
id_: uuid.UUID
name: str
class Config:
alias_generator = to_camel
class Journal(BaseModel):
id_: Optional[uuid.UUID]
debit: int = Field(ge=-1, le=1, multiple_of=1)
@ -23,20 +31,34 @@ class Journal(BaseModel):
alias_generator = to_camel
class Inventory(BaseModel):
class Batch(BaseModel):
id_: uuid.UUID
name: str
product: ProductLink
batch_id: uuid.UUID
quantity: Decimal = Field(ge=0, multiple_of=0.01)
rate: Decimal = Field(ge=0, multiple_of=0.01)
tax: Decimal = Field(ge=0, multiple_of=0.00001)
discount: Decimal = Field(ge=0, multiple_of=0.00001)
quantity_remaining: Decimal
rate: Decimal
tax: Decimal
discount: Decimal
class Config:
alias_generator = to_camel
class SalaryDeduction(BaseModel):
class Inventory(BaseModel):
id_: Optional[uuid.UUID]
product: ProductLink
batch: Optional[Batch]
quantity: Decimal = Field(ge=0, multiple_of=0.01)
rate: Decimal = Field(ge=0, multiple_of=0.01)
tax: Decimal = Field(ge=0, multiple_of=0.00001, le=5)
discount: Decimal = Field(ge=0, multiple_of=0.00001, le=1)
amount: Optional[Decimal]
class Config:
alias_generator = to_camel
class EmployeeBenefit(BaseModel):
id: uuid.UUID
voucher_id: uuid.UUID
journal_id: uuid.UUID
@ -56,31 +78,31 @@ class Incentive(BaseModel):
points: Decimal
class Batch(BaseModel):
id: uuid.UUID
name: datetime
product_id: uuid.UUID
quantity_remaining: Decimal
rate: Decimal
tax: Decimal
discount: Decimal
class Voucher(BaseModel):
id_: Optional[uuid.UUID]
class VoucherIn(BaseModel):
date_: date
narration: str
is_starred: bool
type_: str
@classmethod
def load_form(cls, data: str = Form(...)):
json_data = json.loads(data)
return cls.parse_obj(json_data)
class Voucher(VoucherIn):
id_: Optional[uuid.UUID]
is_reconciled: Optional[bool]
reconcile_date: Optional[date]
is_starred: bool
creation_date: Optional[datetime]
last_edit_date: Optional[datetime]
type_: str
user_id: Optional[uuid.UUID]
user: UserLink
posted: Optional[bool]
poster_id: Optional[uuid.UUID]
journals: List[Journal]
inventories: List[Inventory]
vendor: Optional[AccountLink]
files: List[Any]
class Config:
anystr_strip_whitespace = True
@ -121,7 +143,7 @@ class Voucher(BaseModel):
@validator("journals")
def validate_enough_journals(cls, value: List[Journal]):
if len(value) < 2:
if 0 < len(value) < 2:
raise ValueError("Not enough journals")
return value
@ -138,11 +160,6 @@ class Voucher(BaseModel):
raise ValueError("Duplicate journals")
return value
@classmethod
def load_form(cls, data: str = Form(...)):
json_data = json.loads(data)
return cls.parse_obj(json_data)
class AttendanceType(BaseModel):
id_: int

@ -36,7 +36,7 @@ from brewman.models.voucher import (
Inventory,
Journal,
Product,
SalaryDeduction,
EmployeeBenefit,
Voucher,
Incentive,
DbImage,
@ -125,7 +125,7 @@ def main(argv=sys.argv):
uuid.UUID("a8328891-7ce2-a943-8c29-2eabc1ffeea3"),
),
Role("Clients", uuid.UUID("cfad44f0-f2a9-7045-89d7-9019cf0f371a")),
Role("Salary Deduction", uuid.UUID("92d70e80-1c32-384d-959e-abf84b804696")),
Role("Employee Benefit", uuid.UUID("92d70e80-1c32-384d-959e-abf84b804696")),
Role("Messages", uuid.UUID("f586d128-b6d9-4090-a913-78fcbdb68e59")),
Role("Lock Date", uuid.UUID("d52de0be-9388-4b0b-a359-7e122ab6e53a")),
Role("Net Transactions", uuid.UUID("2c40f7cf-67fc-4efa-a670-8d16a2e7884d")),

@ -3,7 +3,7 @@
<mat-menu #voucherMenu="matMenu">
<a mat-menu-item routerLink="/journal">Journal</a>
<a mat-menu-item routerLink="/purchase">Purchase</a>
<a mat-menu-item routerLink="/return">Purchase Return</a>
<a mat-menu-item routerLink="/purchase-return">Purchase Return</a>
<a mat-menu-item routerLink="/payment">Payment</a>
<a mat-menu-item routerLink="/receipt">Receipt</a>
<a mat-menu-item routerLink="/issue">Issue</a>

@ -63,9 +63,16 @@ export class VoucherService {
);
}
save(voucher: Voucher): Observable<Voucher> {
saveOrUpdate(voucher: Voucher): Observable<Voucher> {
const endpoint = voucher.type.replace(/ /g, '-').toLowerCase();
if (!voucher.id) {
return this.save(voucher, endpoint);
} else {
return this.update(voucher, endpoint);
}
}
save(voucher: Voucher, endpoint: string): Observable<Voucher> {
const fd = new FormData();
voucher.files.filter(x => !x.id).forEach((file) => {
fd.append('i' , this.dataURLtoBlob(file.resized));
@ -73,10 +80,23 @@ export class VoucherService {
});
voucher.files = voucher.files.filter(x => x.id);
fd.append('data', JSON.stringify(voucher));
const saveUrl: string = (voucher.id === undefined || voucher.id === null) ? `${url}/${endpoint}` : `${url}/${endpoint}/${voucher.id}`;
return <Observable<Voucher>>this.http.post<Voucher>(saveUrl, fd)
return <Observable<Voucher>>this.http.post<Voucher>(`${url}/${endpoint}`, fd)
.pipe(
catchError(this.log.handleError(serviceName, 'list'))
catchError(this.log.handleError(serviceName, 'save'))
);
}
update(voucher: Voucher, endpoint: string): Observable<Voucher> {
const fd = new FormData();
voucher.files.filter(x => !x.id).forEach((file) => {
fd.append('i' , this.dataURLtoBlob(file.resized));
fd.append('t' , this.dataURLtoBlob(file.thumbnail));
});
voucher.files = voucher.files.filter(x => x.id);
fd.append('data', JSON.stringify(voucher));
return <Observable<Voucher>>this.http.put<Voucher>(`${url}/${endpoint}/${voucher.id}`, fd)
.pipe(
catchError(this.log.handleError(serviceName, 'update'))
);
}

@ -10,6 +10,7 @@ export class Voucher {
posted: boolean;
narration: string;
incentive?: number;
vendor?: Account;
journals: Journal[];
inventories: Inventory[];
employeeBenefits: EmployeeBenefit[];

@ -191,7 +191,7 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit {
}
save() {
this.ser.save(this.getVoucher())
this.ser.saveOrUpdate(this.getVoucher())
.subscribe(
(result) => {
this.toaster.show('Success', '');

@ -141,7 +141,7 @@ export class IncentiveComponent implements OnInit {
}
save() {
this.ser.save(this.getVoucher())
this.ser.saveOrUpdate(this.getVoucher())
.subscribe(
(result) => {
this.toaster.show('Success', '');

@ -223,7 +223,7 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy {
}
save() {
this.ser.save(this.getVoucher())
this.ser.saveOrUpdate(this.getVoucher())
.subscribe(
(result) => {
this.loadVoucher(result);

@ -224,7 +224,7 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy {
}
save() {
this.ser.save(this.getVoucher())
this.ser.saveOrUpdate(this.getVoucher())
.subscribe(
(result) => {
this.loadVoucher(result);

@ -228,7 +228,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
}
save() {
this.ser.save(this.getVoucher())
this.ser.saveOrUpdate(this.getVoucher())
.subscribe(
(result) => {
this.loadVoucher(result);

@ -8,7 +8,7 @@ import {PurchaseReturnDataSource} from './purchase-return-datasource';
import {Account} from '../core/account';
import {VoucherService} from '../core/voucher.service';
import {AccountService} from '../core/account.service';
import {Batch, DbFile, Inventory, Journal, Voucher} from '../core/voucher';
import {Batch, DbFile, Inventory, Voucher} from '../core/voucher';
import * as moment from 'moment';
import {AuthService} from '../auth/auth.service';
import {ConfirmDialogComponent} from '../shared/confirm-dialog/confirm-dialog.component';
@ -17,7 +17,7 @@ import {debounceTime, distinctUntilChanged, map, startWith, switchMap} from 'rxj
import {PurchaseReturnDialogComponent} from './purchase-return-dialog.component';
import {ImageDialogComponent} from '../shared/image-dialog/image-dialog.component';
import {BatchService} from '../core/batch.service';
import {Hotkey, HotkeysService} from "angular2-hotkeys";
import {Hotkey, HotkeysService} from 'angular2-hotkeys';
@Component({
selector: 'app-purchase-return',
@ -31,9 +31,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy
public inventoryObservable = new BehaviorSubject<Inventory[]>([]);
dataSource: PurchaseReturnDataSource;
form: FormGroup;
purchaseReturnJournal: Journal;
voucher: Voucher;
account: Account;
batch: Batch;
accBal: any;
@ -71,13 +69,15 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy
return false; // Prevent bubbling
}, ['INPUT', 'SELECT', 'TEXTAREA']));
this.hotkeys.add(new Hotkey('ctrl+s', (event: KeyboardEvent): boolean => {
if (this.canSave())
if (this.canSave()) {
this.save();
}
return false; // Prevent bubbling
}, ['INPUT', 'SELECT', 'TEXTAREA']));
this.hotkeys.add(new Hotkey('ctrl+p', (event: KeyboardEvent): boolean => {
if (this.voucher.id && !this.voucher.posted && this.auth.user.perms.indexOf('Post Vouchers') !== -1)
if (this.voucher.id && !this.voucher.posted && this.auth.user.perms.indexOf('Post Vouchers') !== -1) {
this.post();
}
return false; // Prevent bubbling
}, ['INPUT', 'SELECT', 'TEXTAREA']));
}
@ -92,11 +92,10 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy
loadVoucher(voucher: Voucher) {
this.voucher = voucher;
this.purchaseReturnJournal = this.voucher.journals.filter(x => x.debit === 1)[0];
this.form.setValue({
date: moment(this.voucher.date, 'DD-MMM-YYYY').toDate(),
account: this.purchaseReturnJournal.account,
amount: this.purchaseReturnJournal.amount,
account: this.voucher.vendor,
amount: Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)),
addRow: {
batch: '',
quantity: ''
@ -152,8 +151,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy
updateView() {
this.inventoryObservable.next(this.voucher.inventories);
this.purchaseReturnJournal.amount = Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0));
this.form.get('amount').setValue(this.purchaseReturnJournal.amount);
this.form.get('amount').setValue(Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)));
}
editRow(row: Inventory) {
@ -217,12 +215,12 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy
}
save() {
this.ser.save(this.getVoucher())
this.ser.saveOrUpdate(this.getVoucher())
.subscribe(
(result) => {
this.loadVoucher(result);
this.toaster.show('Success', '');
this.router.navigate(['/return', result.id]);
this.router.navigate(['/purchase-return', result.id]);
},
(error) => {
this.toaster.show('Danger', error.error);
@ -233,7 +231,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy
getVoucher(): Voucher {
const formModel = this.form.value;
this.voucher.date = moment(formModel.date).format('DD-MMM-YYYY');
this.purchaseReturnJournal.account = formModel.account;
this.voucher.vendor = formModel.account;
this.voucher.narration = formModel.narration;
return this.voucher;
}
@ -243,7 +241,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy
.subscribe(
(result) => {
this.toaster.show('Success', '');
this.router.navigate(['/return'], {replaceUrl: true});
this.router.navigate(['/purchase-return'], {replaceUrl: true});
},
(error) => {
this.toaster.show('Danger', error.error);
@ -297,7 +295,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy
}
accountSelected(event: MatAutocompleteSelectedEvent): void {
this.account = event.option.value;
this.form.get('account').setValue(event.option.value);
}
displayBatchName(batch?: Batch): string | undefined {

@ -8,7 +8,7 @@ import {PurchaseDataSource} from './purchase-datasource';
import {Account} from '../core/account';
import {VoucherService} from '../core/voucher.service';
import {AccountService} from '../core/account.service';
import {DbFile, Inventory, Journal, Voucher} from '../core/voucher';
import {DbFile, Inventory, Voucher} from '../core/voucher';
import * as moment from 'moment';
import {AuthService} from '../auth/auth.service';
import {ConfirmDialogComponent} from '../shared/confirm-dialog/confirm-dialog.component';
@ -18,7 +18,7 @@ import {PurchaseDialogComponent} from './purchase-dialog.component';
import {ImageDialogComponent} from '../shared/image-dialog/image-dialog.component';
import {ProductService} from '../product/product.service';
import {Product} from '../core/product';
import {Hotkey, HotkeysService} from "angular2-hotkeys";
import {Hotkey, HotkeysService} from 'angular2-hotkeys';
@Component({
selector: 'app-purchase',
@ -32,9 +32,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy {
public inventoryObservable = new BehaviorSubject<Inventory[]>([]);
dataSource: PurchaseDataSource;
form: FormGroup;
purchaseJournal: Journal;
voucher: Voucher;
account: Account;
product: Product;
accBal: any;
@ -72,13 +70,15 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy {
return false; // Prevent bubbling
}, ['INPUT', 'SELECT', 'TEXTAREA']));
this.hotkeys.add(new Hotkey('ctrl+s', (event: KeyboardEvent): boolean => {
if (this.canSave())
if (this.canSave()) {
this.save();
}
return false; // Prevent bubbling
}, ['INPUT', 'SELECT', 'TEXTAREA']));
this.hotkeys.add(new Hotkey('ctrl+p', (event: KeyboardEvent): boolean => {
if (this.voucher.id && !this.voucher.posted && this.auth.user.perms.indexOf('Post Vouchers') !== -1)
if (this.voucher.id && !this.voucher.posted && this.auth.user.perms.indexOf('Post Vouchers') !== -1) {
this.post();
}
return false; // Prevent bubbling
}, ['INPUT', 'SELECT', 'TEXTAREA']));
}
@ -93,11 +93,10 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy {
loadVoucher(voucher) {
this.voucher = voucher;
this.purchaseJournal = this.voucher.journals.filter(x => x.debit === -1)[0];
this.form.setValue({
date: moment(this.voucher.date, 'DD-MMM-YYYY').toDate(),
account: this.purchaseJournal.account,
amount: this.purchaseJournal.amount,
account: this.voucher.vendor,
amount: Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)),
addRow: {
product: '',
quantity: '',
@ -161,8 +160,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy {
updateView() {
this.inventoryObservable.next(this.voucher.inventories);
this.purchaseJournal.amount = Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0));
this.form.get('amount').setValue(this.purchaseJournal.amount);
this.form.get('amount').setValue(Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)));
}
editRow(row: Inventory) {
@ -230,7 +228,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy {
}
save() {
this.ser.save(this.getVoucher())
this.ser.saveOrUpdate(this.getVoucher())
.subscribe(
(result) => {
this.loadVoucher(result);
@ -246,7 +244,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy {
getVoucher(): Voucher {
const formModel = this.form.value;
this.voucher.date = moment(formModel.date).format('DD-MMM-YYYY');
this.purchaseJournal.account = formModel.account;
this.voucher.vendor = formModel.account;
this.voucher.narration = formModel.narration;
return this.voucher;
}
@ -306,7 +304,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy {
}
accountSelected(event: MatAutocompleteSelectedEvent): void {
this.account = event.option.value;
this.form.get('account').setValue(event.option.value);
}
displayProductName(product?: Product): string | undefined {

@ -227,7 +227,7 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy {
}
save() {
this.ser.save(this.getVoucher())
this.ser.saveOrUpdate(this.getVoucher())
.subscribe(
(result) => {
this.loadVoucher(result);