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 from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import table, column
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic. # 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('UserID', new_column_name='user_id')
batch_op.alter_column('AttendanceType', new_column_name='attendance_type') batch_op.alter_column('AttendanceType', new_column_name='attendance_type')
batch_op.alter_column('CreationDate', new_column_name='creation_date') 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('EmployeeID', new_column_name='employee_id')
batch_op.alter_column('AttendanceID', new_column_name='id') batch_op.alter_column('AttendanceID', new_column_name='id')
batch_op.alter_column('IsValid', new_column_name='is_valid') 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: with op.batch_alter_table("batches") as batch_op:
batch_op.alter_column('BatchID', new_column_name='id') batch_op.alter_column('BatchID', new_column_name='id')
batch_op.alter_column('Discount', new_column_name='discount') 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('ProductID', new_column_name='product_id')
batch_op.alter_column('QuantityRemaining', new_column_name='quantity_remaining') batch_op.alter_column('QuantityRemaining', new_column_name='quantity_remaining')
batch_op.alter_column('Rate', new_column_name='rate') batch_op.alter_column('Rate', new_column_name='rate')
@ -118,7 +119,8 @@ def upgrade():
with op.batch_alter_table("recipes") as batch_op: with op.batch_alter_table("recipes") as batch_op:
batch_op.alter_column('recipe_id', new_column_name='id') 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('SalaryDeductionID', new_column_name='id')
batch_op.alter_column('DaysWorked', new_column_name='days_worked') batch_op.alter_column('DaysWorked', new_column_name='days_worked')
batch_op.alter_column('EsiEmployee', new_column_name='esi_employee') 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: with op.batch_alter_table("vouchers") as batch_op:
batch_op.alter_column('VoucherID', new_column_name='id') 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.create_unique_constraint(op.f('uq_accounts_name'), 'accounts', ['name'])
op.drop_constraint('accounts_name_key', 'accounts', type_='unique') 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.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.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.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.drop_constraint('settings_Name_key', 'settings', type_='unique')
op.create_index(op.f('ix_vouchers_date'), 'vouchers', ['date'], unique=False) 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 ### ### end Alembic commands ###

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

@ -16,7 +16,9 @@ from .routers import (
product_group, product_group,
recipe, recipe,
login, login,
journal journal,
purchase,
purchase_return
) )
from .routers.auth import client, user, role from .routers.auth import client, user, role
from .routers.reports import ( 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/journal", tags=["vouchers"])
app.include_router(journal.router, prefix="/api/payment", 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(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(): def init():

@ -26,7 +26,7 @@ from .voucher import (
Inventory, Inventory,
Journal, Journal,
Product, Product,
SalaryDeduction, EmployeeBenefit,
Voucher, Voucher,
VoucherType, 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): def batch_valid(voucher):
pass pass

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

@ -251,8 +251,8 @@ def delete_with_data(employee, db):
amount = (sus_jnl.debit * sus_jnl.amount) + ( amount = (sus_jnl.debit * sus_jnl.amount) + (
acc_jnl.debit * acc_jnl.amount acc_jnl.debit * acc_jnl.amount
) )
if acc_jnl.salary_deduction is not None: if acc_jnl.employee_benefit is not None:
db.delete(acc_jnl.salary_deduction) db.delete(acc_jnl.employee_benefit)
db.delete(acc_jnl) db.delete(acc_jnl)
if amount == 0: if amount == 0:
db.delete(sus_jnl) 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 ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal from ..db.session import SessionLocal
from ..models.voucher import Voucher, VoucherType, Journal 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() router = APIRouter()
@ -27,10 +28,10 @@ def get_db() -> Session:
db.close() db.close()
@router.post("", response_model=schemas.Voucher) @router.post("", response_model=output.Voucher)
def save_route( def save_route(
request: Request, 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), db: Session = Depends(get_db),
i: List[UploadFile] = File(None), i: List[UploadFile] = File(None),
t: List[UploadFile] = File(None), t: List[UploadFile] = File(None),
@ -39,7 +40,8 @@ def save_route(
try: try:
i = i or [] i = i or []
t = t 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() db.commit()
set_date(data.date_.strftime("%d-%b-%Y"), request.session) set_date(data.date_.strftime("%d-%b-%Y"), request.session)
info = voucher_info(item, db) 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) check_voucher_lock_info(None, data.date_, db)
voucher = Voucher( voucher = Voucher(
date=data.date_, date=data.date_,
narration=data.narration, narration=data.narration,
is_starred=data.is_starred, is_starred=data.is_starred,
user_id=user.id_, user_id=user.id_,
type=VoucherType.by_name(data.type_), type_=VoucherType.by_name(data.type_),
) )
db.add(voucher) db.add(voucher)
for item in data.journals: 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) voucher.journals.append(journal)
db.add(journal) db.add(journal)
# for key, value in files.items():
# db.add(DbImage(voucher.id, "voucher", value["f"], value["t"]))
return voucher 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( def update_route(
id_: uuid.UUID, id_: uuid.UUID,
request: Request, request: Request,
data: schemas.Voucher, data: schema_in.JournalIn = Depends(schema_in.JournalIn.load_form),
db: Session = Depends(get_db), db: Session = Depends(get_db),
i: List[UploadFile] = File(None), i: List[UploadFile] = File(None),
t: List[UploadFile] = File(None), t: List[UploadFile] = File(None),
@ -97,10 +103,10 @@ def update_route(
try: try:
i = i or [] i = i or []
t = t 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() db.commit()
set_date(data.date_, request.session) set_date(data.date_.strftime("%d-%b-%Y"), request.session)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
return voucher_info(item, db) return voucher_info(item, db)
except SQLAlchemyError as e: except SQLAlchemyError as e:
db.rollback() db.rollback()
@ -116,30 +122,7 @@ def update_route(
) )
@router.get("/{id_}") def update(id_: uuid.UUID, data: schema_in.JournalIn, user: UserToken, db: Session) -> 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(),
)
def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher:
voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
check_voucher_lock_info(voucher.date, data.date_, db) check_voucher_lock_info(voucher.date, data.date_, db)
check_voucher_edit_allowed(voucher, user) 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) db.add(journal)
voucher.journals.append(journal) voucher.journals.append(journal)
return voucher
def update_files(data: schema_in.VoucherIn, files: List[UploadFile], db: Session):
pass
# old_files = [ # old_files = [
# uuid.UUID(f["id"]) for f in json["files"] if "id" in f and f["id"] is not None # 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]: # 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(): # 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("") @router.get("")

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

@ -1,21 +1,22 @@
import traceback import traceback
import uuid import uuid
from decimal import Decimal
from typing import List from typing import List
from datetime import datetime
from fastapi import APIRouter, HTTPException, status, Depends, Security, UploadFile, File, Request from fastapi import APIRouter, HTTPException, status, Depends, Security, UploadFile, File, Request
from sqlalchemy import func
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from .voucher import journal_create_voucher, purchase_create_voucher, issue_create_voucher, incentive_create_voucher, \ from .voucher import voucher_info, check_voucher_lock_info, check_voucher_edit_allowed, blank_voucher
salary_deduction_create_voucher, voucher_info, check_voucher_lock_info, check_voucher_edit_allowed from ..core.session import set_date, get_date
from .voucher.purchase_return import purchase_return_create_voucher
from ..core.session import set_date
from ..models import Product, AccountBase from ..models import Product, AccountBase
from ..schemas.auth import UserToken from ..schemas.auth import UserToken
from ..core.security import get_current_active_user as get_user from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal from ..db.session import SessionLocal
from ..models.voucher import Voucher, VoucherType, Batch, Inventory, Journal 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() router = APIRouter()
@ -29,10 +30,10 @@ def get_db() -> Session:
db.close() db.close()
@router.post("", response_model=schemas.Voucher) @router.post("", response_model=output.Voucher)
def save_route( def save_route(
request: Request, 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), db: Session = Depends(get_db),
i: List[UploadFile] = File(None), i: List[UploadFile] = File(None),
t: List[UploadFile] = File(None), t: List[UploadFile] = File(None),
@ -41,7 +42,10 @@ def save_route(
try: try:
i = i or [] i = i or []
t = t 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() db.commit()
set_date(data.date_.strftime("%d-%b-%Y"), request.session) set_date(data.date_.strftime("%d-%b-%Y"), request.session)
info = voucher_info(item, db) 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) check_voucher_lock_info(None, data.date_, db)
voucher = Voucher( voucher = Voucher(
date=data.date_, date=data.date_,
narration=data.narration, narration=data.narration,
is_starred=data.is_starred, is_starred=data.is_starred,
user_id=user.id_, user_id=user.id_,
type=VoucherType.by_name(data.type_), type_=VoucherType.by_name(data.type_),
) )
db.add(voucher) 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 return voucher
def purchase_create_journals(account_id: uuid.UUID, db: Session): def save_inventories(voucher: Voucher, inventories: List[schema_in.Inventory], db: Session):
amount = 0
for item in inventories: for item in inventories:
product = db.query(Product).filter(Product.id == item.product_id).first() product: Product = db.query(Product).filter(Product.id == item.product.id_).first()
account = product.account batch = Batch(name=voucher.date, product=product, quantity_remaining=item.quantity, rate=item.rate, tax=item.tax, discount=item.discount)
item_amount = round(item.amount, 2) db.add(batch)
amount += item_amount 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: if account.id in journals:
journals[account.id].amount += item_amount journals[account.id].amount += round(item.amount, 2)
else: 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, debit=-1,
cost_centre_id=other_account.cost_centre_id, cost_centre_id=vendor.cost_centre_id,
account_id=other_account.id, account_id=vendor.id,
amount=amount, 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( def update_route(
id_: uuid.UUID, id_: uuid.UUID,
request: Request, request: Request,
data: schemas.Voucher, data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form),
db: Session = Depends(get_db), db: Session = Depends(get_db),
files: List[UploadFile] = File(...), i: List[UploadFile] = File(None),
user: UserToken = Security(get_user, scopes=["journal"]), t: List[UploadFile] = File(None),
user: UserToken = Security(get_user, scopes=["purchase"]),
): ):
try: 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() db.commit()
set_date(request.session, data.date_) set_date(data.date_.strftime("%d-%b-%Y"), request.session)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
return voucher_info(item, db) return voucher_info(item, db)
except SQLAlchemyError as e: except SQLAlchemyError as e:
db.rollback() 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: def update(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher:
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
check_voucher_lock_info(item.date, data.date_, db) check_voucher_lock_info(voucher.date, data.date_, db)
check_voucher_edit_allowed(item, user) check_voucher_edit_allowed(voucher, user)
if data.type_ in ["Purchase"]: voucher.date = data.date_
voucher = purchase_update_voucher(item, data, files, user, db) voucher.is_starred = data.is_starred
elif data.type_ in ["Purchase Return"]: voucher.narration = data.narration
voucher = purchase_return_update_voucher(item, data, files, user, db) voucher.user_id = user.id_
elif data.type_ in ["Issue"]: voucher.posted = False
voucher = issue_update_voucher(item, data, files, user, db) voucher.last_edit_date = datetime.utcnow()
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)
return voucher 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 traceback
import uuid import uuid
from decimal import Decimal
from typing import List from typing import List
from datetime import datetime
from fastapi import APIRouter, HTTPException, status, Depends, Security, UploadFile, File, Request from fastapi import APIRouter, HTTPException, status, Depends, Security, UploadFile, File, Request
from sqlalchemy import func
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from .voucher import journal_create_voucher, purchase_create_voucher, issue_create_voucher, incentive_create_voucher, \ from .voucher import voucher_info, check_voucher_lock_info, check_voucher_edit_allowed, blank_voucher
salary_deduction_create_voucher, voucher_info, check_voucher_lock_info, check_voucher_edit_allowed from ..core.session import set_date, get_date
from .voucher.purchase_return import purchase_return_create_voucher from ..models import Product, AccountBase
from ..core.session import set_date
from ..schemas.auth import UserToken from ..schemas.auth import UserToken
from ..core.security import get_current_active_user as get_user from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal from ..db.session import SessionLocal
from ..models.voucher import Voucher 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() router = APIRouter()
@ -28,20 +30,26 @@ def get_db() -> Session:
db.close() db.close()
@router.post("", response_model=schemas.Voucher) @router.post("", response_model=output.Voucher)
def save_route( def save_route(
request: Request, request: Request,
data: schemas.Voucher, data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form),
db: Session = Depends(get_db), db: Session = Depends(get_db),
files: List[UploadFile] = File(...), i: List[UploadFile] = File(None),
user: UserToken = Security(get_user, scopes=["journal"]), t: List[UploadFile] = File(None),
user: UserToken = Security(get_user, scopes=["purchase-return"]),
): ):
try: 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() db.commit()
set_date(request.session, data.date_) set_date(data.date_.strftime("%d-%b-%Y"), request.session)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first() info = voucher_info(item, db)
return voucher_info(item, db) return info
except SQLAlchemyError as e: except SQLAlchemyError as e:
db.rollback() db.rollback()
raise HTTPException( 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) check_voucher_lock_info(None, data.date_, db)
if data.type_ in ["Purchase"]: voucher = Voucher(
voucher = purchase_create_voucher(data, files, user, db) date=data.date_,
elif data.type_ in ["Purchase Return"]: narration=data.narration,
voucher = purchase_return_create_voucher(data, files, user, db) is_starred=data.is_starred,
elif data.type_ in ["Issue"]: user_id=user.id_,
voucher = issue_create_voucher(data, files, user, db) type_=VoucherType.by_name(data.type_),
elif data.type_ in ["Salary Deduction"]: )
voucher = salary_deduction_create_voucher(data, files, user, db) db.add(voucher)
elif data.type_ in ["Incentive"]:
voucher = incentive_create_voucher(data, files, user, db)
return 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( def update_route(
id_: uuid.UUID, id_: uuid.UUID,
request: Request, request: Request,
data: schemas.Voucher, data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form),
db: Session = Depends(get_db), db: Session = Depends(get_db),
files: List[UploadFile] = File(...), i: List[UploadFile] = File(None),
user: UserToken = Security(get_user, scopes=["journal"]), t: List[UploadFile] = File(None),
user: UserToken = Security(get_user, scopes=["purchase-return"]),
): ):
try: 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() db.commit()
set_date(request.session, data.date_) set_date(data.date_.strftime("%d-%b-%Y"), request.session)
# item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first()
return voucher_info(item, db) return voucher_info(item, db)
except SQLAlchemyError as e: except SQLAlchemyError as e:
db.rollback() 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: def update(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher:
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
check_voucher_lock_info(item.date, data.date_, db) check_voucher_lock_info(voucher.date, data.date_, db)
check_voucher_edit_allowed(item, user) check_voucher_edit_allowed(voucher, user)
if data.type_ in ["Purchase"]: voucher.date = data.date_
voucher = purchase_update_voucher(item, data, files, user, db) voucher.is_starred = data.is_starred
elif data.type_ in ["Purchase Return"]: voucher.narration = data.narration
voucher = purchase_return_update_voucher(item, data, files, user, db) voucher.user_id = user.id_
elif data.type_ in ["Issue"]: voucher.posted = False
voucher = issue_update_voucher(item, data, files, user, db) voucher.last_edit_date = datetime.utcnow()
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)
return voucher 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 brewman.routers import get_lock_info
from .issue import issue_create_voucher, issue_update_voucher 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 .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 ...core.session import get_first_day
from fastapi import APIRouter, Depends, Security, Request, HTTPException, status from fastapi import APIRouter, Depends, Security, Request, HTTPException, status
from ...schemas.auth import UserToken from ...schemas.auth import UserToken
@ -36,7 +34,6 @@ from ...schemas.auth import UserToken
router = APIRouter() router = APIRouter()
@router.post("/api/voucher/{id}") # , request_param="p" "Post Vouchers" @router.post("/api/voucher/{id}") # , request_param="p" "Post Vouchers"
def voucher_post(request): def voucher_post(request):
user = ( user = (
@ -195,6 +192,10 @@ def voucher_info(voucher, db):
json_voucher["reconcileDate"] = voucher.reconcile_date.strftime( json_voucher["reconcileDate"] = voucher.reconcile_date.strftime(
"%d-%b-%Y" "%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: for item in voucher.journals:
json_voucher["journals"].append( json_voucher["journals"].append(
{ {
@ -205,7 +206,7 @@ def voucher_info(voucher, db):
"costCentre": {"id": item.cost_centre_id}, "costCentre": {"id": item.cost_centre_id},
} }
) )
for item in voucher.salary_deductions: for item in voucher.employee_benefits:
json_voucher["employeeBenefits"].append( json_voucher["employeeBenefits"].append(
{ {
"grossSalary": item.gross_salary, "grossSalary": item.gross_salary,
@ -251,13 +252,7 @@ def voucher_info(voucher, db):
if x.account_id == Account.incentive_id() if x.account_id == Account.incentive_id()
][0] ][0]
for item in voucher.inventories: for item in voucher.inventories:
text = "{0} ({1}) {2:.2f}@{3:.2f} from {4}".format( 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')}"
item.product.name,
item.product.units,
item.batch.quantity_remaining,
item.batch.rate,
item.batch.name.strftime("%d-%b-%Y"),
)
json_voucher["inventories"].append( json_voucher["inventories"].append(
{ {
"id": item.id, "id": item.id,
@ -294,12 +289,12 @@ def voucher_info(voucher, db):
return json_voucher return json_voucher
def blank_voucher(info, dbsession): def blank_voucher(info, db):
if "type" not in info: if "type" not in info:
raise ValidationError("Voucher Type is null") raise ValueError("Voucher Type is null")
type_ = info["type"] type_ = info["type"]
if "date" not in info: if "date" not in info:
raise ValidationError("Date cannot be null") raise ValueError("Date cannot be null")
json_voucher = { json_voucher = {
"type": type_, "type": type_,
"date": info["date"], "date": info["date"],
@ -313,9 +308,9 @@ def blank_voucher(info, dbsession):
pass pass
elif type_ == "Payment": elif type_ == "Payment":
account = None 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 = ( account = (
dbsession.query(AccountBase) db.query(AccountBase)
.filter(AccountBase.id == uuid.UUID(info["account"])) .filter(AccountBase.id == uuid.UUID(info["account"]))
.first() .first()
) )
@ -326,9 +321,9 @@ def blank_voucher(info, dbsession):
json_voucher["journals"].append({"account": account, "amount": 0, "debit": -1}) json_voucher["journals"].append({"account": account, "amount": 0, "debit": -1})
elif type_ == "Receipt": elif type_ == "Receipt":
account = None 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 = ( account = (
dbsession.query(AccountBase) db.query(AccountBase)
.filter(AccountBase.id == uuid.UUID(info["account"])) .filter(AccountBase.id == uuid.UUID(info["account"]))
.first() .first()
) )
@ -338,9 +333,8 @@ def blank_voucher(info, dbsession):
account = AccountBase.cash_in_hand() account = AccountBase.cash_in_hand()
json_voucher["journals"].append({"account": account, "amount": 0, "debit": 1}) json_voucher["journals"].append({"account": account, "amount": 0, "debit": 1})
elif type_ == "Purchase": elif type_ == "Purchase":
json_voucher["journals"].append( json_voucher["vendor"] = AccountBase.local_purchase()
{"account": AccountBase.local_purchase(), "amount": 0, "debit": -1}
)
elif type_ == "Purchase Return": elif type_ == "Purchase Return":
json_voucher["journals"].append( json_voucher["journals"].append(
{"account": AccountBase.local_purchase(), "amount": 0, "debit": 1} {"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"] = [] json_voucher["employeeBenefits"] = []
elif type_ == "Incentive": elif type_ == "Incentive":
json_voucher["incentives"], json_voucher[ json_voucher["incentives"], json_voucher[

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

@ -16,15 +16,11 @@ class EmptyVoucher(object):
account = self.request.GET.get("a", None) account = self.request.GET.get("a", None)
return self.get_blank({"account": account}) 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" @router.get("/api/voucher", request_param="t=Purchase Return") # "Purchase Return"
def purchase_return(self): def purchase_return(self):
return self.get_blank() 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): def purchase_return(self):
return self.get_blank() return self.get_blank()

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

@ -36,7 +36,7 @@ from brewman.models.voucher import (
Inventory, Inventory,
Journal, Journal,
Product, Product,
SalaryDeduction, EmployeeBenefit,
Voucher, Voucher,
Incentive, Incentive,
DbImage, DbImage,
@ -125,7 +125,7 @@ def main(argv=sys.argv):
uuid.UUID("a8328891-7ce2-a943-8c29-2eabc1ffeea3"), uuid.UUID("a8328891-7ce2-a943-8c29-2eabc1ffeea3"),
), ),
Role("Clients", uuid.UUID("cfad44f0-f2a9-7045-89d7-9019cf0f371a")), 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("Messages", uuid.UUID("f586d128-b6d9-4090-a913-78fcbdb68e59")),
Role("Lock Date", uuid.UUID("d52de0be-9388-4b0b-a359-7e122ab6e53a")), Role("Lock Date", uuid.UUID("d52de0be-9388-4b0b-a359-7e122ab6e53a")),
Role("Net Transactions", uuid.UUID("2c40f7cf-67fc-4efa-a670-8d16a2e7884d")), Role("Net Transactions", uuid.UUID("2c40f7cf-67fc-4efa-a670-8d16a2e7884d")),

@ -3,7 +3,7 @@
<mat-menu #voucherMenu="matMenu"> <mat-menu #voucherMenu="matMenu">
<a mat-menu-item routerLink="/journal">Journal</a> <a mat-menu-item routerLink="/journal">Journal</a>
<a mat-menu-item routerLink="/purchase">Purchase</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="/payment">Payment</a>
<a mat-menu-item routerLink="/receipt">Receipt</a> <a mat-menu-item routerLink="/receipt">Receipt</a>
<a mat-menu-item routerLink="/issue">Issue</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(); 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(); const fd = new FormData();
voucher.files.filter(x => !x.id).forEach((file) => { voucher.files.filter(x => !x.id).forEach((file) => {
fd.append('i' , this.dataURLtoBlob(file.resized)); fd.append('i' , this.dataURLtoBlob(file.resized));
@ -73,10 +80,23 @@ export class VoucherService {
}); });
voucher.files = voucher.files.filter(x => x.id); voucher.files = voucher.files.filter(x => x.id);
fd.append('data', JSON.stringify(voucher)); 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>(`${url}/${endpoint}`, fd)
return <Observable<Voucher>>this.http.post<Voucher>(saveUrl, fd)
.pipe( .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; posted: boolean;
narration: string; narration: string;
incentive?: number; incentive?: number;
vendor?: Account;
journals: Journal[]; journals: Journal[];
inventories: Inventory[]; inventories: Inventory[];
employeeBenefits: EmployeeBenefit[]; employeeBenefits: EmployeeBenefit[];

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

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

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

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

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

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

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

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