diff --git a/alembic/versions/03ea3e9cb1e5_rename_further.py b/alembic/versions/03ea3e9cb1e5_rename_further.py deleted file mode 100644 index 6a972c1d..00000000 --- a/alembic/versions/03ea3e9cb1e5_rename_further.py +++ /dev/null @@ -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 ### diff --git a/alembic/versions/5498fc4bf58d_rename_service_charge_to_incentive.py b/alembic/versions/5498fc4bf58d_rename_service_charge_to_incentive.py deleted file mode 100644 index ea72e322..00000000 --- a/alembic/versions/5498fc4bf58d_rename_service_charge_to_incentive.py +++ /dev/null @@ -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 ### diff --git a/alembic/versions/eed0b382c287_lowercase_fastapi.py b/alembic/versions/eed0b382c287_lowercase_fastapi.py index f13f4636..9b81c5b9 100644 --- a/alembic/versions/eed0b382c287_lowercase_fastapi.py +++ b/alembic/versions/eed0b382c287_lowercase_fastapi.py @@ -7,6 +7,7 @@ Create Date: 2020-05-10 19:52:58.163810 """ from alembic import op import sqlalchemy as sa +from sqlalchemy import table, column from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. @@ -51,7 +52,7 @@ def upgrade(): batch_op.alter_column('UserID', new_column_name='user_id') batch_op.alter_column('AttendanceType', new_column_name='attendance_type') batch_op.alter_column('CreationDate', new_column_name='creation_date') - batch_op.alter_column('Date', new_column_name='date') + batch_op.alter_column('Date', new_column_name='date', type_=sa.Date(), nullable=False) batch_op.alter_column('EmployeeID', new_column_name='employee_id') batch_op.alter_column('AttendanceID', new_column_name='id') batch_op.alter_column('IsValid', new_column_name='is_valid') @@ -65,7 +66,7 @@ def upgrade(): with op.batch_alter_table("batches") as batch_op: batch_op.alter_column('BatchID', new_column_name='id') batch_op.alter_column('Discount', new_column_name='discount') - batch_op.alter_column('Name', new_column_name='name') + batch_op.alter_column('Name', new_column_name='name', type_=sa.Date(), nullable=False) batch_op.alter_column('ProductID', new_column_name='product_id') batch_op.alter_column('QuantityRemaining', new_column_name='quantity_remaining') batch_op.alter_column('Rate', new_column_name='rate') @@ -118,7 +119,8 @@ def upgrade(): with op.batch_alter_table("recipes") as batch_op: batch_op.alter_column('recipe_id', new_column_name='id') - with op.batch_alter_table("salary_deductions") as batch_op: + op.rename_table('salary_deductions', 'employee_benefit') + with op.batch_alter_table("employee_benefit") as batch_op: batch_op.alter_column('SalaryDeductionID', new_column_name='id') batch_op.alter_column('DaysWorked', new_column_name='days_worked') batch_op.alter_column('EsiEmployee', new_column_name='esi_employee') @@ -131,6 +133,23 @@ def upgrade(): with op.batch_alter_table("vouchers") as batch_op: batch_op.alter_column('VoucherID', new_column_name='id') + batch_op.alter_column('date', type_=sa.Date(), nullable=False) + batch_op.alter_column('reconcile_date', type_=sa.Date(), nullable=False) + + op.rename_table('service_charges', 'incentives') + + with op.batch_alter_table("employees") as batch_op: + batch_op.alter_column('Designation', new_column_name='designation', nullable=False) + batch_op.alter_column('Salary', new_column_name='salary', nullable=False) + batch_op.alter_column('ServicePoints', new_column_name='points', nullable=False) + batch_op.alter_column('JoiningDate', new_column_name='joining_date', type_=sa.Date(), nullable=False) + batch_op.alter_column('LeavingDate', new_column_name='leaving_date', type_=sa.Date(), nullable=True) + + with op.batch_alter_table("settings") as batch_op: + batch_op.alter_column('SettingID', new_column_name='id') + batch_op.alter_column('Name', new_column_name='name') + batch_op.alter_column('Data', new_column_name='data') + op.create_unique_constraint(op.f('uq_accounts_name'), 'accounts', ['name']) op.drop_constraint('accounts_name_key', 'accounts', type_='unique') @@ -162,9 +181,23 @@ def upgrade(): op.drop_constraint('products_Name_Units_key', 'products', type_='unique') op.create_unique_constraint(op.f('uq_recipe_items_recipe_id'), 'recipe_items', ['recipe_id', 'product_id']) op.drop_constraint('recipe_items_recipe_id_product_id_key', 'recipe_items', type_='unique') - op.create_unique_constraint(op.f('uq_settings_Name'), 'settings', ['Name']) + op.create_unique_constraint(op.f('uq_settings_name'), 'settings', ['name']) op.drop_constraint('settings_Name_key', 'settings', type_='unique') op.create_index(op.f('ix_vouchers_date'), 'vouchers', ['date'], unique=False) + op.create_foreign_key('fk_incentives_journal_id_journals', 'incentives', 'journals', ['journal_id'], ['id']) + op.create_foreign_key('fk_incentives_voucher_id_vouchers', 'incentives', 'vouchers', ['voucher_id'], ['id']) + op.drop_constraint('service_charges_journal_id_fkey', 'incentives', type_='foreignkey') + op.drop_constraint('service_charges_voucher_id_fkey', 'incentives', type_='foreignkey') + op.create_foreign_key('fk_employee_benefit_journal_id_journals', 'employee_benefit', 'journals', ['journal_id'], ['id']) + op.create_foreign_key('fk_employee_benefit_voucher_id_vouchers', 'employee_benefit', 'vouchers', ['voucher_id'], ['id']) + op.drop_constraint('entities_salarydeductions_JournalID_fkey', 'employee_benefit', type_='foreignkey') + op.drop_constraint('salary_deductions_VoucherID_fkey', 'employee_benefit', type_='foreignkey') + + role = table('auth_roles', column('name', sa.String)) + op.execute(role.update().where(role.c.name == op.inline_literal('Service Charge')).values({'name': op.inline_literal('Incentive')})) + op.execute(role.update().where(role.c.name == op.inline_literal('Salary Deduction')).values({'name': op.inline_literal('Employee Benefit')})) + account = table('accounts', column('name', sa.String)) + op.execute(account.update().where(account.c.name == op.inline_literal('Service Charges')).values({'name': op.inline_literal('Incentives')})) ### end Alembic commands ### diff --git a/brewman/db/base.py b/brewman/db/base.py index b9a39c3a..96390195 100644 --- a/brewman/db/base.py +++ b/brewman/db/base.py @@ -25,7 +25,7 @@ from brewman.models import ( Inventory, Journal, Product, - SalaryDeduction, + EmployeeBenefit, Voucher, VoucherType, ) # noqa diff --git a/brewman/main.py b/brewman/main.py index 9915fc0c..aa63214c 100644 --- a/brewman/main.py +++ b/brewman/main.py @@ -16,7 +16,9 @@ from .routers import ( product_group, recipe, login, - journal + journal, + purchase, + purchase_return ) from .routers.auth import client, user, role from .routers.reports import ( @@ -105,6 +107,8 @@ app.include_router(batch.router, prefix="/api/batch", tags=["vouchers"]) app.include_router(journal.router, prefix="/api/journal", tags=["vouchers"]) app.include_router(journal.router, prefix="/api/payment", tags=["vouchers"]) app.include_router(journal.router, prefix="/api/receipt", tags=["vouchers"]) +app.include_router(purchase.router, prefix="/api/purchase", tags=["vouchers"]) +app.include_router(purchase_return.router, prefix="/api/purchase-return", tags=["vouchers"]) def init(): diff --git a/brewman/models/__init__.py b/brewman/models/__init__.py index 47db3dc7..a2d6c410 100644 --- a/brewman/models/__init__.py +++ b/brewman/models/__init__.py @@ -26,7 +26,7 @@ from .voucher import ( Inventory, Journal, Product, - SalaryDeduction, + EmployeeBenefit, Voucher, VoucherType, ) diff --git a/brewman/models/operations.py b/brewman/models/operations.py index 821f98b3..e826b391 100644 --- a/brewman/models/operations.py +++ b/brewman/models/operations.py @@ -126,29 +126,6 @@ def purchase_return_update(voucher): ) -def inventory_valid(voucher): - if voucher.type in [2, 6, 3]: - if not len(voucher.inventories): - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Not enough inventories", - ) - is_distinct_inventory(voucher.inventories) - - -def is_distinct_inventory(inventories): - found = set() - for item in inventories: - item_hash = hash(item.product_id) - if item_hash in found: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Duplicate inventories", - ) - else: - found.add(item_hash) - - def batch_valid(voucher): pass diff --git a/brewman/models/voucher.py b/brewman/models/voucher.py index 562662c4..a137d518 100644 --- a/brewman/models/voucher.py +++ b/brewman/models/voucher.py @@ -9,7 +9,8 @@ from sqlalchemy import ( DateTime, Numeric, ForeignKey, - UniqueConstraint, Date, + UniqueConstraint, + Date, ) from sqlalchemy.dialects.postgresql import BYTEA from sqlalchemy.ext.hybrid import hybrid_property @@ -39,7 +40,7 @@ class VoucherType: VoucherType(9, "Verification"), VoucherType(10, "Opening Balance"), VoucherType(11, "Closing Balance"), - VoucherType(12, "Salary Deduction"), + VoucherType(12, "Employee Benefit"), VoucherType(13, "Incentive"), ] return list @@ -63,10 +64,10 @@ class Voucher(Base): __tablename__ = "vouchers" id = Column("id", GUID(), primary_key=True, default=uuid.uuid4) - date = Column("date", DateTime, nullable=False, index=True) + date = Column("date", Date, nullable=False, index=True) narration = Column("narration", Unicode(1000), nullable=False) is_reconciled = Column("is_reconciled", Boolean, nullable=False) - reconcile_date = Column("reconcile_date", DateTime, nullable=False) + reconcile_date = Column("reconcile_date", Date, nullable=False) is_starred = Column("is_starred", Boolean, nullable=False) creation_date = Column("creation_date", DateTime(timezone=True), nullable=False) last_edit_date = Column("last_edit_date", DateTime(timezone=True), nullable=False) @@ -86,14 +87,15 @@ class Voucher(Base): cascade="delete, delete-orphan", cascade_backrefs=False, ) + inventories = relationship( "Inventory", back_populates="voucher", cascade="delete, delete-orphan", cascade_backrefs=False, ) - salary_deductions = relationship( - "SalaryDeduction", + employee_benefits = relationship( + "EmployeeBenefit", backref="voucher", cascade="delete, delete-orphan", cascade_backrefs=False, @@ -120,10 +122,6 @@ class Voucher(Base): type = property(_get_type, _set_type) type = synonym("_type", descriptor=type) - @property - def __name__(self): - return self.name - def __init__( self, date=None, @@ -134,25 +132,19 @@ class Voucher(Base): posted=False, creation_date=None, last_edit_date=None, - type=None, + type_=None, user_id=None, poster_id=None, ): self.date = date self.is_reconciled = is_reconciled - self.reconcile_date = ( - reconcile_date if reconcile_date and is_reconciled else date - ) + self.reconcile_date = reconcile_date or date self.is_starred = is_starred if is_starred is not None else False self.narration = narration self.posted = posted - self.creation_date = ( - datetime.utcnow() if creation_date is None else creation_date - ) - self.last_edit_date = ( - datetime.utcnow() if last_edit_date is None else last_edit_date - ) - self.type = type + self.creation_date = creation_date or datetime.utcnow() + self.last_edit_date = last_edit_date or datetime.utcnow() + self.type = type_ self.user_id = user_id self.poster_id = poster_id @@ -199,8 +191,8 @@ class Journal(Base): self.cost_centre_id = cost_centre_id -class SalaryDeduction(Base): - __tablename__ = "salary_deductions" +class EmployeeBenefit(Base): + __tablename__ = "employee_benefit" id = Column("id", GUID(), primary_key=True, default=uuid.uuid4) voucher_id = Column("voucher_id", GUID(), ForeignKey("vouchers.id"), nullable=False) journal_id = Column("journal_id", GUID(), ForeignKey("journals.id"), nullable=False) @@ -213,14 +205,14 @@ class SalaryDeduction(Base): journal = relationship( Journal, - backref=backref("salary_deduction", uselist=False), + backref=backref("employee_benefit", uselist=False), cascade=None, cascade_backrefs=False, ) def __init__( self, - id=None, + id_=None, voucher_id=None, journal_id=None, journal=None, @@ -231,7 +223,7 @@ class SalaryDeduction(Base): esi_er=None, pf_er=None, ): - self.id = id + self.id = id_ self.voucher_id = voucher_id self.journal_id = journal_id self.gross_salary = gross_salary @@ -304,17 +296,20 @@ class Inventory(Base): tax=None, discount=None, batch=None, + product=None ): self.id = id_ self.voucher_id = voucher_id - self.product_id = product_id - self.batch_id = batch_id + if product is None: + self.product_id = product_id + if batch is None: + self.batch_id = batch_id self.quantity = quantity self.rate = rate self.tax = tax self.discount = discount - if batch_id is None and batch is not None: - self.batch = batch + self.batch = batch + self.product = product @hybrid_property def amount(self): @@ -325,7 +320,7 @@ class Batch(Base): __tablename__ = "batches" id = Column("id", GUID(), primary_key=True, default=uuid.uuid4) - name = Column("name", DateTime) + name = Column("name", Date, nullable=False) product_id = Column("product_id", GUID(), ForeignKey("products.id"), nullable=False) quantity_remaining = Column("quantity_remaining", Numeric) rate = Column("rate", Numeric) @@ -344,6 +339,7 @@ class Batch(Base): rate=None, tax=None, discount=None, + product=None ): self.name = name self.product_id = product_id @@ -351,6 +347,9 @@ class Batch(Base): self.rate = rate self.tax = tax self.discount = discount + if product is None: + self.product_id = product_id + self.product = product def amount(self): return ( diff --git a/brewman/routers/employee.py b/brewman/routers/employee.py index a255050e..d197a902 100644 --- a/brewman/routers/employee.py +++ b/brewman/routers/employee.py @@ -251,8 +251,8 @@ def delete_with_data(employee, db): amount = (sus_jnl.debit * sus_jnl.amount) + ( acc_jnl.debit * acc_jnl.amount ) - if acc_jnl.salary_deduction is not None: - db.delete(acc_jnl.salary_deduction) + if acc_jnl.employee_benefit is not None: + db.delete(acc_jnl.employee_benefit) db.delete(acc_jnl) if amount == 0: db.delete(sus_jnl) diff --git a/brewman/routers/deduction.py b/brewman/routers/employee_benefit.py similarity index 100% rename from brewman/routers/deduction.py rename to brewman/routers/employee_benefit.py diff --git a/brewman/routers/issue.py b/brewman/routers/issue.py index e69de29b..ad7c5efb 100644 --- a/brewman/routers/issue.py +++ b/brewman/routers/issue.py @@ -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 diff --git a/brewman/routers/journal.py b/brewman/routers/journal.py index 5f6cf8fb..a9cd8002 100644 --- a/brewman/routers/journal.py +++ b/brewman/routers/journal.py @@ -13,7 +13,8 @@ from ..schemas.auth import UserToken from ..core.security import get_current_active_user as get_user from ..db.session import SessionLocal from ..models.voucher import Voucher, VoucherType, Journal -import brewman.schemas.voucher as schemas +import brewman.schemas.voucher as output +import brewman.schemas.input as schema_in router = APIRouter() @@ -27,10 +28,10 @@ def get_db() -> Session: db.close() -@router.post("", response_model=schemas.Voucher) +@router.post("", response_model=output.Voucher) def save_route( request: Request, - data: schemas.Voucher = Depends(schemas.Voucher.load_form), + data: schema_in.JournalIn = Depends(schema_in.JournalIn.load_form), db: Session = Depends(get_db), i: List[UploadFile] = File(None), t: List[UploadFile] = File(None), @@ -39,7 +40,8 @@ def save_route( try: i = i or [] t = t or [] - item: Voucher = save(data, i + t, user, db) + item: Voucher = save(data, user, db) + save_files(i + t, db) db.commit() set_date(data.date_.strftime("%d-%b-%Y"), request.session) info = voucher_info(item, db) @@ -58,14 +60,14 @@ def save_route( ) -def save(data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher: +def save(data: schema_in.JournalIn, user: UserToken, db: Session) -> Voucher: check_voucher_lock_info(None, data.date_, db) voucher = Voucher( date=data.date_, narration=data.narration, is_starred=data.is_starred, user_id=user.id_, - type=VoucherType.by_name(data.type_), + type_=VoucherType.by_name(data.type_), ) db.add(voucher) for item in data.journals: @@ -79,16 +81,20 @@ def save(data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Se ) voucher.journals.append(journal) db.add(journal) - # for key, value in files.items(): - # db.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) return voucher -@router.put("/{id_}") +def save_files(files: List[UploadFile], db: Session): + # for key, value in files.items(): + # db.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) + pass + + +@router.put("/{id_}", response_model=output.Voucher) def update_route( id_: uuid.UUID, request: Request, - data: schemas.Voucher, + data: schema_in.JournalIn = Depends(schema_in.JournalIn.load_form), db: Session = Depends(get_db), i: List[UploadFile] = File(None), t: List[UploadFile] = File(None), @@ -97,10 +103,10 @@ def update_route( try: i = i or [] t = t or [] - item: Voucher = update(id_, data, i + t, user, db) + item: Voucher = update(id_, data, user, db) + update_files(data, i + t, db) db.commit() - set_date(data.date_, request.session) - # item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) return voucher_info(item, db) except SQLAlchemyError as e: db.rollback() @@ -116,30 +122,7 @@ def update_route( ) -@router.get("/{id_}") -def get_id( - id_: uuid.UUID, - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["journal"]), -): - try: - item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - return voucher_info(item, db) - except SQLAlchemyError as e: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), - ) - except Exception: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=traceback.format_exc(), - ) - - -def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher: +def update(id_: uuid.UUID, data: schema_in.JournalIn, user: UserToken, db: Session) -> Voucher: voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() check_voucher_lock_info(voucher.date, data.date_, db) check_voucher_edit_allowed(voucher, user) @@ -177,15 +160,42 @@ def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user: ) db.add(journal) voucher.journals.append(journal) + return voucher + +def update_files(data: schema_in.VoucherIn, files: List[UploadFile], db: Session): + pass # old_files = [ # uuid.UUID(f["id"]) for f in json["files"] if "id" in f and f["id"] is not None # ] - # images = dbsession.query(DbImage).filter(DbImage.resource_id == voucher.id).all() + # images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all() # for image in [i for i in images if i.id not in old_files]: - # dbsession.delete(image) + # db.delete(image) # for key, value in files.items(): - # dbsession.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) return voucher + # db.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) + + +@router.get("/{id_}", response_model=output.Voucher) +def get_id( + id_: uuid.UUID, + db: Session = Depends(get_db), + user: UserToken = Security(get_user, scopes=["journal"]), +): + try: + item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() + return voucher_info(item, db) + except SQLAlchemyError as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), + ) @router.get("") diff --git a/brewman/routers/management/rebase.py b/brewman/routers/management/rebase.py index d1af29ea..3bdee868 100644 --- a/brewman/routers/management/rebase.py +++ b/brewman/routers/management/rebase.py @@ -11,7 +11,7 @@ from brewman.models.voucher import ( VoucherType, Batch, Inventory, - SalaryDeduction, + EmployeeBenefit, Fingerprint, Attendance, DbImage, @@ -203,7 +203,7 @@ def delete_data(date, vouchers, dbsession): dbsession.execute(Inventory.__table__.delete(Inventory.voucher_id.in_(sub_query))) dbsession.execute( - SalaryDeduction.__table__.delete(SalaryDeduction.voucher_id.in_(sub_query)) + EmployeeBenefit.__table__.delete(EmployeeBenefit.voucher_id.in_(sub_query)) ) dbsession.execute( Incentive.__table__.delete(Incentive.voucher_id.in_(sub_query)) diff --git a/brewman/routers/purchase.py b/brewman/routers/purchase.py index 8d0bdf0c..026a33ff 100644 --- a/brewman/routers/purchase.py +++ b/brewman/routers/purchase.py @@ -1,21 +1,22 @@ import traceback import uuid +from decimal import Decimal from typing import List - +from datetime import datetime from fastapi import APIRouter, HTTPException, status, Depends, Security, UploadFile, File, Request +from sqlalchemy import func from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session -from .voucher import journal_create_voucher, purchase_create_voucher, issue_create_voucher, incentive_create_voucher, \ - salary_deduction_create_voucher, voucher_info, check_voucher_lock_info, check_voucher_edit_allowed -from .voucher.purchase_return import purchase_return_create_voucher -from ..core.session import set_date +from .voucher import voucher_info, check_voucher_lock_info, check_voucher_edit_allowed, blank_voucher +from ..core.session import set_date, get_date from ..models import Product, AccountBase from ..schemas.auth import UserToken from ..core.security import get_current_active_user as get_user from ..db.session import SessionLocal from ..models.voucher import Voucher, VoucherType, Batch, Inventory, Journal -import brewman.schemas.voucher as schemas +import brewman.schemas.voucher as output +import brewman.schemas.input as schema_in router = APIRouter() @@ -29,10 +30,10 @@ def get_db() -> Session: db.close() -@router.post("", response_model=schemas.Voucher) +@router.post("", response_model=output.Voucher) def save_route( request: Request, - data: schemas.Voucher = Depends(schemas.Voucher.load_form), + data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), db: Session = Depends(get_db), i: List[UploadFile] = File(None), t: List[UploadFile] = File(None), @@ -41,7 +42,10 @@ def save_route( try: i = i or [] t = t or [] - item: Voucher = save(data, i + t, user, db) + item: Voucher = save(data, user, db) + save_inventories(item, data.inventories, db) + save_journals(item, data.vendor, db) + save_files(i + t, db) db.commit() set_date(data.date_.strftime("%d-%b-%Y"), request.session) info = voucher_info(item, db) @@ -60,82 +64,83 @@ def save_route( ) -def save(data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher: +def save(data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: check_voucher_lock_info(None, data.date_, db) voucher = Voucher( date=data.date_, narration=data.narration, is_starred=data.is_starred, user_id=user.id_, - type=VoucherType.by_name(data.type_), + type_=VoucherType.by_name(data.type_), ) db.add(voucher) - journals = {} - for item in data.inventories: - product: Product = db.query(Product).filter(Product.id == item.product.id_).first() - batch = Batch(name=data.date_, product_id=product.id, quantity_remaining=item.quantity, rate=item.rate, tax=item.tax,discount=item.discount) - db.add(batch) - inventory = Inventory(id=item.id_, product_id=product.id, batch=batch, quantity=item.quantity, rate=item.rate,tax=item.tax, discount=item.discount) - product.price = item.rate - voucher.inventories.append(inventory) - db.add(inventory) - if product.account_id not in journals: - journals[product.account_id] = {"account_id":product.account_id, "cost_centre_id": product.account.cost_centre_id, "debit": 1, "amount": inventory.amount} - else: - journals[product.account_id]["amount"] += inventory.amount - supplier = db.query(AccountBase).filter(AccountBase.id == data.journals[0].account.id_).first() - - journals[supplier.id] = {"account_id":supplier.id, "cost_centre_id": supplier.cost_centre_id, "debit":-1, "amount": sum(round(x["amount"], 2) for x in journals)} - for item in journals: - journal = Journal( - debit=item["debit"], - account_id=item["account_id"], - cost_centre_id=item["cost_centre_id"], - amount=round(item["amount"], 2), - ) - - voucher.journals.append(item) - db.add(item) - - journals_valid(voucher) - inventory_valid(voucher) - for key, value in files.items(): - dbsession.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) return voucher -def purchase_create_journals(account_id: uuid.UUID, db: Session): - amount = 0 +def save_inventories(voucher: Voucher, inventories: List[schema_in.Inventory], db: Session): for item in inventories: - product = db.query(Product).filter(Product.id == item.product_id).first() - account = product.account - item_amount = round(item.amount, 2) - amount += item_amount + product: Product = db.query(Product).filter(Product.id == item.product.id_).first() + batch = Batch(name=voucher.date, product=product, quantity_remaining=item.quantity, rate=item.rate, tax=item.tax, discount=item.discount) + db.add(batch) + inventory = Inventory(id_=item.id_, product=product, batch=batch, quantity=item.quantity, rate=item.rate, tax=item.tax, discount=item.discount) + product.price = item.rate + voucher.inventories.append(inventory) + db.add(inventory) + + +def save_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session): + vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() + journals = {} + amount = 0 + for item in voucher.inventories: + account = item.product.account + amount += round(item.amount, 2) if account.id in journals: - journals[account.id].amount += item_amount + journals[account.id].amount += round(item.amount, 2) else: - journals[other_account.id] = Journal( + journals[account.id] = Journal( + debit=1, + cost_centre_id=account.cost_centre_id, + account_id=account.id, + amount=round(item.amount, 2), + ) + journals[vendor.id] = Journal( debit=-1, - cost_centre_id=other_account.cost_centre_id, - account_id=other_account.id, + cost_centre_id=vendor.cost_centre_id, + account_id=vendor.id, amount=amount, ) - return list(journals.values()) + for item in journals.values(): + voucher.journals.append(item) + db.add(item) -@router.get("/{id_}") + +def save_files(files: List[UploadFile], db: Session): + # for key, value in files.items(): + # db.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) + pass + + +@router.put("/{id_}", response_model=output.Voucher) def update_route( id_: uuid.UUID, request: Request, - data: schemas.Voucher, + data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), db: Session = Depends(get_db), - files: List[UploadFile] = File(...), - user: UserToken = Security(get_user, scopes=["journal"]), + i: List[UploadFile] = File(None), + t: List[UploadFile] = File(None), + user: UserToken = Security(get_user, scopes=["purchase"]), ): try: - item: Voucher = update(id_, data, files, user, db) + i = i or [] + t = t or [] + item: Voucher = update(id_, data, user, db) + update_inventory(item, data.inventories, db) + update_journals(item, data.vendor, db) + # journals_valid(voucher) + update_files(data, i + t, db) db.commit() - set_date(request.session, data.date_) - # item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) return voucher_info(item, db) except SQLAlchemyError as e: db.rollback() @@ -151,18 +156,178 @@ def update_route( ) -def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher: - item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - check_voucher_lock_info(item.date, data.date_, db) - check_voucher_edit_allowed(item, user) - if data.type_ in ["Purchase"]: - voucher = purchase_update_voucher(item, data, files, user, db) - elif data.type_ in ["Purchase Return"]: - voucher = purchase_return_update_voucher(item, data, files, user, db) - elif data.type_ in ["Issue"]: - voucher = issue_update_voucher(item, data, files, user, db) - elif data.type_ in ["Salary Deduction"]: - voucher = salary_deduction_update_voucher(item, data, files, user, db) - elif data.type_ in ["Incentive"]: - voucher = incentive_update_voucher(item, data, files, user, db) +def update(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: + voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() + check_voucher_lock_info(voucher.date, data.date_, db) + check_voucher_edit_allowed(voucher, user) + voucher.date = data.date_ + voucher.is_starred = data.is_starred + voucher.narration = data.narration + voucher.user_id = user.id_ + voucher.posted = False + voucher.last_edit_date = datetime.utcnow() return voucher + + +def update_inventory(voucher: Voucher, new_inventories: List[schema_in.Inventory], db: Session): + for it in range(len(voucher.inventories), 0, -1): + item = voucher.inventories[it - 1] + found = False + for j in range(len(new_inventories), 0, -1): + new_inventory = new_inventories[j - 1] + if new_inventory.id_: + product = db.query(Product).filter(Product.id == new_inventory.product.id_).first() + found = True + if item.product_id != new_inventory.product.id_: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Product cannot be changed") + old_quantity = round(Decimal(item.quantity), 2) + quantity_remaining = round(Decimal(item.batch.quantity_remaining), 2) + if new_inventory.quantity < (old_quantity - quantity_remaining): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=f"{old_quantity - quantity_remaining} is the minimum as it has been issued", + ) + item.batch.quantity_remaining -= old_quantity - new_inventory.quantity + item.quantity = new_inventory.quantity + if voucher.date != item.batch.name: + item.batch.name = voucher.date + if voucher.date < item.batch.name: + # TODO: check for issued products which might have been in a back date + pass + item.rate = new_inventory.rate + item.batch.rate = new_inventory.rate + item.discount = new_inventory.discount + item.batch.discount = new_inventory.discount + item.tax = new_inventory.tax + item.batch.tax = new_inventory.tax + product.price = new_inventory.rate + new_inventories.remove(new_inventory) + # TODO: Update all references of the batch with the new rates + break + if not found: + has_been_issued = ( + db.query(func.count(Inventory.id)) + .filter(Inventory.batch_id == item.batch.id) + .filter(Inventory.id != item.id) + .scalar() + ) + if has_been_issued > 0: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=f"{item.product.name} has been issued, it cannot be deleted", + ) + else: + db.delete(item.batch) + db.delete(item) + voucher.inventories.remove(item) + for new_inventory in new_inventories: + product = db.query(Product).filter(Product.id == new_inventory.product.id_).first() + batch = Batch( + name=voucher.date, + product_id=product.id, + quantity_remaining=new_inventory.quantity, + rate=new_inventory.rate, + tax=new_inventory.tax, + discount=new_inventory.discount, + ) + inventory = Inventory( + id_=None, + product_id=product.id, + batch=batch, + quantity=new_inventory.quantity, + rate=new_inventory.rate, + tax=new_inventory.tax, + discount=new_inventory.discount, + ) + inventory.voucher_id = voucher.id + db.add(batch) + inventory.batch_id = batch.id + product.price = new_inventory.rate + voucher.inventories.append(inventory) + db.add(inventory) + + +def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db): + vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() + journals = {} + amount = 0 + for item in voucher.inventories: + product = db.query(Product).filter(Product.id == item.product_id).first() + account = product.account + amount += item.amount + if account.id in journals: + journals[account.id].amount += item.amount + else: + journals[account.id] = Journal( + debit=1, + cost_centre_id=account.cost_centre_id, + account_id=account.id, + amount=item.amount, + ) + journals[vendor.id] = Journal( + debit=-1, + cost_centre_id=vendor.cost_centre_id, + account_id=vendor.id, + amount=amount, + ) + for i in range(len(voucher.journals), 0, -1): + item = voucher.journals[i - 1] + if item.account_id in journals: + item.debit = journals[item.account_id].debit + item.amount = round(journals[item.account_id].amount, 2) + item.cost_centre_id = journals[item.account_id].cost_centre_id + del journals[item.account_id] + else: + db.delete(item) + voucher.journals.remove(item) + for item in journals.values(): + item.amount = round(item.amount, 2) + voucher.journals.append(item) + db.add(item) + + +def update_files(data: schema_in.PurchaseIn, files: List[UploadFile], db: Session): + pass + # old_files = [ + # uuid.UUID(f["id"]) for f in json["files"] if "id" in f and f["id"] is not None + # ] + # images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all() + # for image in [i for i in images if i.id not in old_files]: + # db.delete(image) + # for key, value in files.items(): + # db.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) + + +@router.get("/{id_}", response_model=output.Voucher) +def get_id( + id_: uuid.UUID, + db: Session = Depends(get_db), + user: UserToken = Security(get_user, scopes=["purchase"]), +): + try: + item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() + return voucher_info(item, db) + except SQLAlchemyError as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), + ) + + +@router.get("") +def show_blank( + request: Request, + db: Session = Depends(get_db), + user: UserToken = Security(get_user, scopes=["purchase"]), +): + additional_info = {"date": get_date(request.session), "type": "Purchase"} + return blank_voucher(additional_info, db) diff --git a/brewman/routers/purchase_return.py b/brewman/routers/purchase_return.py index af0aaaf9..540a70fe 100644 --- a/brewman/routers/purchase_return.py +++ b/brewman/routers/purchase_return.py @@ -1,20 +1,22 @@ import traceback import uuid +from decimal import Decimal from typing import List - +from datetime import datetime from fastapi import APIRouter, HTTPException, status, Depends, Security, UploadFile, File, Request +from sqlalchemy import func from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session -from .voucher import journal_create_voucher, purchase_create_voucher, issue_create_voucher, incentive_create_voucher, \ - salary_deduction_create_voucher, voucher_info, check_voucher_lock_info, check_voucher_edit_allowed -from .voucher.purchase_return import purchase_return_create_voucher -from ..core.session import set_date +from .voucher import voucher_info, check_voucher_lock_info, check_voucher_edit_allowed, blank_voucher +from ..core.session import set_date, get_date +from ..models import Product, AccountBase from ..schemas.auth import UserToken from ..core.security import get_current_active_user as get_user from ..db.session import SessionLocal -from ..models.voucher import Voucher -import brewman.schemas.voucher as schemas +from ..models.voucher import Voucher, VoucherType, Batch, Inventory, Journal +import brewman.schemas.voucher as output +import brewman.schemas.input as schema_in router = APIRouter() @@ -28,20 +30,26 @@ def get_db() -> Session: db.close() -@router.post("", response_model=schemas.Voucher) +@router.post("", response_model=output.Voucher) def save_route( request: Request, - data: schemas.Voucher, + data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), db: Session = Depends(get_db), - files: List[UploadFile] = File(...), - user: UserToken = Security(get_user, scopes=["journal"]), + i: List[UploadFile] = File(None), + t: List[UploadFile] = File(None), + user: UserToken = Security(get_user, scopes=["purchase-return"]), ): try: - item: Voucher = save(data, files, user, db) + i = i or [] + t = t or [] + item: Voucher = save(data, user, db) + save_inventories(item, data.inventories, db) + save_journals(item, data.vendor, db) + save_files(i + t, db) db.commit() - set_date(request.session, data.date_) - # item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first() - return voucher_info(item, db) + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + info = voucher_info(item, db) + return info except SQLAlchemyError as e: db.rollback() raise HTTPException( @@ -56,35 +64,95 @@ def save_route( ) -def save(data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher: +def save(data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: check_voucher_lock_info(None, data.date_, db) - if data.type_ in ["Purchase"]: - voucher = purchase_create_voucher(data, files, user, db) - elif data.type_ in ["Purchase Return"]: - voucher = purchase_return_create_voucher(data, files, user, db) - elif data.type_ in ["Issue"]: - voucher = issue_create_voucher(data, files, user, db) - elif data.type_ in ["Salary Deduction"]: - voucher = salary_deduction_create_voucher(data, files, user, db) - elif data.type_ in ["Incentive"]: - voucher = incentive_create_voucher(data, files, user, db) + voucher = Voucher( + date=data.date_, + narration=data.narration, + is_starred=data.is_starred, + user_id=user.id_, + type_=VoucherType.by_name(data.type_), + ) + db.add(voucher) return voucher -@router.get("/{id_}") +def save_inventories(voucher: Voucher, inventories: List[schema_in.Inventory], db: Session): + for item in inventories: + batch = db.query(Batch).filter(Batch.id == item.batch.id_).first() + + if item.quantity > batch.quantity_remaining: + raise ValueError(f"Maximum quantity is {batch.quantity_remaining}.") + if batch.name > voucher.date: + raise ValueError(f"Return date cannot be before {batch.product.name.strftime('%d-%b-%Y')}") + + batch.quantity_remaining -= item.quantity + + item = Inventory( + product=batch.product, + quantity=item.quantity, + rate=batch.rate, + tax=batch.tax, + discount=batch.discount, + batch=batch, + ) + voucher.inventories.append(item) + db.add(item) + + +def save_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session): + vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() + journals = {} + amount = 0 + for item in voucher.inventories: + account = item.product.account + amount += round(item.amount, 2) + if account.id in journals: + journals[account.id].amount += round(item.amount, 2) + else: + journals[account.id] = Journal( + debit=-1, + cost_centre_id=account.cost_centre_id, + account_id=account.id, + amount=round(item.amount, 2), + ) + journals[vendor.id] = Journal( + debit=1, + cost_centre_id=vendor.cost_centre_id, + account_id=vendor.id, + amount=amount, + ) + for item in journals.values(): + voucher.journals.append(item) + db.add(item) + + +def save_files(files: List[UploadFile], db: Session): + # for key, value in files.items(): + # db.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) + pass + + +@router.put("/{id_}", response_model=output.Voucher) def update_route( id_: uuid.UUID, request: Request, - data: schemas.Voucher, + data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), db: Session = Depends(get_db), - files: List[UploadFile] = File(...), - user: UserToken = Security(get_user, scopes=["journal"]), + i: List[UploadFile] = File(None), + t: List[UploadFile] = File(None), + user: UserToken = Security(get_user, scopes=["purchase-return"]), ): try: - item: Voucher = update(id_, data, files, user, db) + i = i or [] + t = t or [] + item: Voucher = update(id_, data, user, db) + update_inventory(item, data.inventories, db) + update_journals(item, data.vendor, db) + # journals_valid(voucher) + update_files(data, i + t, db) db.commit() - set_date(request.session, data.date_) - # item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) return voucher_info(item, db) except SQLAlchemyError as e: db.rollback() @@ -100,18 +168,129 @@ def update_route( ) -def update(id_: uuid.UUID, data: schemas.Voucher, files: List[UploadFile], user: UserToken, db: Session) -> Voucher: - item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - check_voucher_lock_info(item.date, data.date_, db) - check_voucher_edit_allowed(item, user) - if data.type_ in ["Purchase"]: - voucher = purchase_update_voucher(item, data, files, user, db) - elif data.type_ in ["Purchase Return"]: - voucher = purchase_return_update_voucher(item, data, files, user, db) - elif data.type_ in ["Issue"]: - voucher = issue_update_voucher(item, data, files, user, db) - elif data.type_ in ["Salary Deduction"]: - voucher = salary_deduction_update_voucher(item, data, files, user, db) - elif data.type_ in ["Incentive"]: - voucher = incentive_update_voucher(item, data, files, user, db) +def update(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: + voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() + check_voucher_lock_info(voucher.date, data.date_, db) + check_voucher_edit_allowed(voucher, user) + voucher.date = data.date_ + voucher.is_starred = data.is_starred + voucher.narration = data.narration + voucher.user_id = user.id_ + voucher.posted = False + voucher.last_edit_date = datetime.utcnow() return voucher + + +def update_inventory(voucher: Voucher, new_inventories: List[schema_in.Inventory], db: Session): + for it in range(len(voucher.inventories), 0, -1): + item = voucher.inventories[it - 1] + found = False + for j in range(len(new_inventories), 0, -1): + new_inventory = new_inventories[j - 1] + if new_inventory.id_: + found = True + if item.product_id != new_inventory.product.id_: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Product cannot be changed") + old_quantity = round(Decimal(item.quantity), 2) + quantity_remaining = round(Decimal(item.batch.quantity_remaining), 2) + if new_inventory.quantity - old_quantity > quantity_remaining: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=f"{old_quantity + quantity_remaining} is the maximum for {item.product.full_name}.", + ) + if item.batch.name > voucher.date: + raise ValueError(f"Voucher cannot be before {item.product.name.strftime('%d-%b-%Y')}") + item.batch.quantity_remaining -= new_inventory.quantity - old_quantity + item.quantity = new_inventory.quantity + new_inventories.remove(new_inventory) + break + if not found: + item.batch.quantity_remaining += item.quantity + voucher.inventories.remove(item) + db.delete(item) + save_inventories(voucher, new_inventories, db) + + +def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db): + vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() + journals = {} + amount = 0 + for item in voucher.inventories: + account = item.product.account + amount += item.amount + if account.id in journals: + journals[account.id].amount += item.amount + else: + journals[account.id] = Journal( + debit=-1, + cost_centre_id=account.cost_centre_id, + account_id=account.id, + amount=item.amount, + ) + journals[vendor.id] = Journal( + debit=1, + cost_centre_id=vendor.cost_centre_id, + account_id=vendor.id, + amount=amount, + ) + for i in range(len(voucher.journals), 0, -1): + item = voucher.journals[i - 1] + if item.account_id in journals: + item.debit = journals[item.account_id].debit + item.amount = round(journals[item.account_id].amount, 2) + item.cost_centre_id = journals[item.account_id].cost_centre_id + del journals[item.account_id] + else: + db.delete(item) + voucher.journals.remove(item) + for item in journals.values(): + item.amount = round(item.amount, 2) + voucher.journals.append(item) + db.add(item) + + +def update_files(data: schema_in.PurchaseIn, files: List[UploadFile], db: Session): + pass + # old_files = [ + # uuid.UUID(f["id"]) for f in json["files"] if "id" in f and f["id"] is not None + # ] + # images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all() + # for image in [i for i in images if i.id not in old_files]: + # db.delete(image) + # for key, value in files.items(): + # db.add(DbImage(voucher.id, "voucher", value["f"], value["t"])) + + +@router.get("/{id_}", response_model=output.Voucher) +def get_id( + id_: uuid.UUID, + db: Session = Depends(get_db), + user: UserToken = Security(get_user, scopes=["purchase-return"]), +): + try: + item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() + return voucher_info(item, db) + except SQLAlchemyError as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), + ) + + +@router.get("") +def show_blank( + request: Request, + db: Session = Depends(get_db), + user: UserToken = Security(get_user, scopes=["purchase-return"]), +): + additional_info = {"date": get_date(request.session), "type": "Purchase"} + return blank_voucher(additional_info, db) diff --git a/brewman/routers/voucher/__init__.py b/brewman/routers/voucher/__init__.py index 0a6690e1..d37c56bf 100644 --- a/brewman/routers/voucher/__init__.py +++ b/brewman/routers/voucher/__init__.py @@ -23,12 +23,10 @@ from brewman.models.voucher import ( ) from brewman.routers import get_lock_info from .issue import issue_create_voucher, issue_update_voucher -from .purchase import purchase_create_voucher, purchase_update_voucher from .incentive import incentive_create_voucher, incentive_update_voucher -from .salary_deduction import salary_deduction_create_voucher, salary_deduction_update_voucher +from .employee_benefit import employee_benefit_create_voucher, employee_benefit_update_voucher from ...core.session import get_first_day - from fastapi import APIRouter, Depends, Security, Request, HTTPException, status from ...schemas.auth import UserToken @@ -36,18 +34,17 @@ from ...schemas.auth import UserToken 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): user = ( request.dbsession.query(User) - .filter(User.id == uuid.UUID(request.authenticated_userid)) - .one() + .filter(User.id == uuid.UUID(request.authenticated_userid)) + .one() ) voucher = ( request.dbsession.query(Voucher) - .filter(Voucher.id == uuid.UUID(request.matchdict["id"])) - .first() + .filter(Voucher.id == uuid.UUID(request.matchdict["id"])) + .first() ) start, finish = get_lock_info(request.dbsession) if start is not None and start > voucher.date: @@ -66,8 +63,8 @@ def voucher_post(request): def check_delete_permissions(request, voucher): user = ( request.dbsession.query(User) - .filter(User.id == uuid.UUID(request.authenticated_userid)) - .one() + .filter(User.id == uuid.UUID(request.authenticated_userid)) + .one() ) if voucher.posted and not request.has_permission("Edit Posted Vouchers"): @@ -75,7 +72,7 @@ def check_delete_permissions(request, voucher): response.status_int = 403 return response elif voucher.user_id != user.id and not request.has_permission( - "Edit Other User's Vouchers" + "Edit Other User's Vouchers" ): response = Response("You are not allowed to edit other user's vouchers") response.status_int = 403 @@ -99,8 +96,8 @@ def check_delete_permissions(request, voucher): def delete(request): voucher = ( request.dbsession.query(Voucher) - .filter(Voucher.id == uuid.UUID(request.matchdict["id"])) - .first() + .filter(Voucher.id == uuid.UUID(request.matchdict["id"])) + .first() ) images = ( request.dbsession.query(DbImage).filter(DbImage.resource_id == voucher.id).all() @@ -141,9 +138,9 @@ def delete(request): for item in voucher.inventories: uses = ( request.dbsession.query(func.count(Inventory.id)) - .filter(Inventory.batch_id == item.batch.id) - .filter(Inventory.id != item.id) - .scalar() + .filter(Inventory.batch_id == item.batch.id) + .filter(Inventory.id != item.id) + .scalar() ) if uses > 0: raise ValueError( @@ -195,17 +192,21 @@ def voucher_info(voucher, db): json_voucher["reconcileDate"] = voucher.reconcile_date.strftime( "%d-%b-%Y" ) - for item in voucher.journals: - json_voucher["journals"].append( - { - "id": item.id, - "debit": item.debit, - "amount": item.amount, - "account": {"id": item.account.id, "name": item.account.name}, - "costCentre": {"id": item.cost_centre_id}, - } - ) - for item in voucher.salary_deductions: + if voucher.type == 2: # "Purchase" + item = [j for j in voucher.journals if j.debit == -1][0] + json_voucher["vendor"] = {"id": item.account.id, "name": item.account.name} + else: + for item in voucher.journals: + json_voucher["journals"].append( + { + "id": item.id, + "debit": item.debit, + "amount": item.amount, + "account": {"id": item.account.id, "name": item.account.name}, + "costCentre": {"id": item.cost_centre_id}, + } + ) + for item in voucher.employee_benefits: json_voucher["employeeBenefits"].append( { "grossSalary": item.gross_salary, @@ -231,8 +232,8 @@ def voucher_info(voucher, db): for item in voucher.incentives: employee = ( db.query(Employee) - .filter(Employee.id == item.journal.account_id) - .first() + .filter(Employee.id == item.journal.account_id) + .first() ) json_voucher["incentives"].append( { @@ -251,13 +252,7 @@ def voucher_info(voucher, db): if x.account_id == Account.incentive_id() ][0] for item in voucher.inventories: - text = "{0} ({1}) {2:.2f}@{3:.2f} from {4}".format( - item.product.name, - item.product.units, - item.batch.quantity_remaining, - item.batch.rate, - item.batch.name.strftime("%d-%b-%Y"), - ) + text = f"{item.product.name} ({item.product.units}) {item.batch.quantity_remaining:.2f}@{item.batch.rate:.2f} from {item.batch.name.strftime('%d-%b-%Y')}" json_voucher["inventories"].append( { "id": item.id, @@ -294,12 +289,12 @@ def voucher_info(voucher, db): return json_voucher -def blank_voucher(info, dbsession): +def blank_voucher(info, db): if "type" not in info: - raise ValidationError("Voucher Type is null") + raise ValueError("Voucher Type is null") type_ = info["type"] if "date" not in info: - raise ValidationError("Date cannot be null") + raise ValueError("Date cannot be null") json_voucher = { "type": type_, "date": info["date"], @@ -313,11 +308,11 @@ def blank_voucher(info, dbsession): pass elif type_ == "Payment": account = None - if info is not None and "account" in info and info["account"] is not None: + if info and "account" in info and info["account"]: account = ( - dbsession.query(AccountBase) - .filter(AccountBase.id == uuid.UUID(info["account"])) - .first() + db.query(AccountBase) + .filter(AccountBase.id == uuid.UUID(info["account"])) + .first() ) if account is not None: account = {"id": account.id, "name": account.name} @@ -326,11 +321,11 @@ def blank_voucher(info, dbsession): json_voucher["journals"].append({"account": account, "amount": 0, "debit": -1}) elif type_ == "Receipt": account = None - if info is not None and "account" in info and info["account"] is not None: + if info and "account" in info and info["account"]: account = ( - dbsession.query(AccountBase) - .filter(AccountBase.id == uuid.UUID(info["account"])) - .first() + db.query(AccountBase) + .filter(AccountBase.id == uuid.UUID(info["account"])) + .first() ) if account is not None: account = {"id": account.id, "name": account.name} @@ -338,9 +333,8 @@ def blank_voucher(info, dbsession): account = AccountBase.cash_in_hand() json_voucher["journals"].append({"account": account, "amount": 0, "debit": 1}) elif type_ == "Purchase": - json_voucher["journals"].append( - {"account": AccountBase.local_purchase(), "amount": 0, "debit": -1} - ) + json_voucher["vendor"] = AccountBase.local_purchase() + elif type_ == "Purchase Return": json_voucher["journals"].append( {"account": AccountBase.local_purchase(), "amount": 0, "debit": 1} @@ -392,7 +386,7 @@ def blank_voucher(info, dbsession): } ) - elif type_ == "Salary Deduction": + elif type_ == "Employee Benefit": json_voucher["employeeBenefits"] = [] elif type_ == "Incentive": json_voucher["incentives"], json_voucher[ @@ -411,21 +405,21 @@ def incentive_employees(date, dbsession): details = [] employees = ( dbsession.query(Employee) - .filter(Employee.joining_date <= finish_date) - .filter(or_(Employee.is_active, Employee.leaving_date >= start_date)) - .order_by(Employee.cost_centre_id) - .order_by(Employee.designation) - .order_by(Employee.name) - .all() + .filter(Employee.joining_date <= finish_date) + .filter(or_(Employee.is_active, Employee.leaving_date >= start_date)) + .order_by(Employee.cost_centre_id) + .order_by(Employee.designation) + .order_by(Employee.name) + .all() ) for employee in employees: att = ( dbsession.query(Attendance) - .filter(Attendance.employee_id == employee.id) - .filter(Attendance.date >= start_date) - .filter(Attendance.date < finish_date) - .filter(Attendance.is_valid == True) - .all() + .filter(Attendance.employee_id == employee.id) + .filter(Attendance.date >= start_date) + .filter(Attendance.date < finish_date) + .filter(Attendance.is_valid == True) + .all() ) att = sum(map(lambda x: AttendanceType.by_id(x.attendance_type).value, att)) details.append( @@ -441,11 +435,11 @@ def incentive_employees(date, dbsession): amount = ( dbsession.query(func.sum(Journal.amount * Journal.debit)) - .join(Journal.voucher) - .filter(Voucher.date < finish_date) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(Journal.account_id == Account.incentive_id()) - .scalar() + .join(Journal.voucher) + .filter(Voucher.date < finish_date) + .filter(Voucher.type != VoucherType.by_name("Issue").id) + .filter(Journal.account_id == Account.incentive_id()) + .scalar() ) amount = 0 if amount is None else amount * Decimal(0.9) * -1 return details, amount diff --git a/brewman/routers/voucher/salary_deduction.py b/brewman/routers/voucher/employee_benefit.py similarity index 88% rename from brewman/routers/voucher/salary_deduction.py rename to brewman/routers/voucher/employee_benefit.py index 4e89bdf4..be6cca03 100644 --- a/brewman/routers/voucher/salary_deduction.py +++ b/brewman/routers/voucher/employee_benefit.py @@ -4,17 +4,17 @@ import uuid from brewman.core.session import get_last_day from brewman.models.master import AccountBase, Employee -from brewman.models.voucher import Journal, Voucher, VoucherType, SalaryDeduction +from brewman.models.voucher import Journal, Voucher, VoucherType, EmployeeBenefit -def salary_deduction_create_voucher(json, user, dbsession): +def employee_benefit_create_voucher(json, user, dbsession): dt = get_last_day(datetime.datetime.strptime(json["date"], "%d-%b-%Y")) days_in_month = dt.day voucher = Voucher(date=dt, user_id=user.id, type=VoucherType.by_id(12)) dbsession.add(voucher) exp, total = 0, 0 for item in json["employeeBenefits"]: - item_exp, item_total = add_salary_deduction( + item_exp, item_total = add_employee_benefit( item, days_in_month, voucher, dbsession ) exp += item_exp @@ -50,10 +50,10 @@ def salary_deduction_create_voucher(json, user, dbsession): return voucher -def salary_deduction_update_voucher(voucher, json, user, dbsession): +def employee_benefit_update_voucher(voucher, json, user, dbsession): dt = get_last_day(datetime.datetime.strptime(json["date"], "%d-%b-%Y")) if dt != voucher.date.date(): - raise ValidationError("Date Cannot be changed for Salary Deduction voucher!") + raise ValidationError("Date Cannot be changed for Employee Benefit voucher!") days_in_month = voucher.date.day voucher.user_id = user.id voucher.posted = False @@ -61,8 +61,8 @@ def salary_deduction_update_voucher(voucher, json, user, dbsession): new_deductions = json["employeeBenefits"] exp, total, journals = 0, 0, [] - for i in range(len(voucher.salary_deductions), 0, -1): - item = voucher.salary_deductions[i - 1] + for i in range(len(voucher.employee_benefits), 0, -1): + item = voucher.employee_benefits[i - 1] found = False for j in range(len(new_deductions), 0, -1): new_item = new_deductions[j - 1] @@ -77,10 +77,10 @@ def salary_deduction_update_voucher(voucher, json, user, dbsession): new_deductions.remove(new_item) break if not found: - voucher.salary_deductions.remove(item) + voucher.employee_benefits.remove(item) voucher.journals.remove(item.journal) for new_item in new_deductions: - item_exp, item_total = add_salary_deduction( + item_exp, item_total = add_employee_benefit( new_item, days_in_month, voucher, dbsession ) exp += item_exp @@ -101,7 +101,7 @@ def salary_deduction_update_voucher(voucher, json, user, dbsession): return voucher -def add_salary_deduction(item, days_in_month, voucher, dbsession): +def add_employee_benefit(item, days_in_month, voucher, dbsession): account = ( dbsession.query(Employee) .filter(Employee.id == uuid.UUID(item["journal"]["account"]["id"])) @@ -119,7 +119,7 @@ def add_salary_deduction(item, days_in_month, voucher, dbsession): account_id=account.id, cost_centre_id=account.cost_centre_id, ) - sd = SalaryDeduction( + sd = EmployeeBenefit( journal=journal, gross_salary=gross_salary, days_worked=days_worked, @@ -129,7 +129,7 @@ def add_salary_deduction(item, days_in_month, voucher, dbsession): pf_er=pf_er, ) voucher.journals.append(journal) - voucher.salary_deductions.append(sd) + voucher.employee_benefits.append(sd) dbsession.add(journal) dbsession.add(sd) return esi_er + pf_er, esi_both + pf_both diff --git a/brewman/routers/voucher/emptyvoucher.py b/brewman/routers/voucher/emptyvoucher.py index 1523b947..cbfe17cf 100644 --- a/brewman/routers/voucher/emptyvoucher.py +++ b/brewman/routers/voucher/emptyvoucher.py @@ -16,15 +16,11 @@ class EmptyVoucher(object): account = self.request.GET.get("a", None) return self.get_blank({"account": account}) - @router.get("/api/voucher", request_param="t=Purchase") # "Purchase" - def purchase(self): - return self.get_blank() - @router.get("/api/voucher", request_param="t=Purchase Return") # "Purchase Return" def purchase_return(self): return self.get_blank() - @router.get("/api/voucher", request_param="t=Salary Deduction") # "Purchase Return" + @router.get("/api/voucher", request_param="t=Employee Benefit") # "Purchase Return" def purchase_return(self): return self.get_blank() diff --git a/brewman/routers/voucher/issue.py b/brewman/routers/voucher/issue.py index b39af475..46135abe 100644 --- a/brewman/routers/voucher/issue.py +++ b/brewman/routers/voucher/issue.py @@ -5,7 +5,6 @@ import uuid from fastapi import HTTPException, status from brewman.models.master import CostCentre, AccountBase -from brewman.models.operations import inventory_valid from brewman.models.voucher import Voucher, VoucherType, Batch, Inventory, Journal diff --git a/brewman/routers/voucher/purchase.py b/brewman/routers/voucher/purchase.py deleted file mode 100644 index 7e3ffa16..00000000 --- a/brewman/routers/voucher/purchase.py +++ /dev/null @@ -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) diff --git a/brewman/routers/voucher/purchase_return.py b/brewman/routers/voucher/purchase_return.py deleted file mode 100644 index c3bdccdc..00000000 --- a/brewman/routers/voucher/purchase_return.py +++ /dev/null @@ -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) diff --git a/brewman/schemas/input.py b/brewman/schemas/input.py new file mode 100644 index 00000000..f2397309 --- /dev/null +++ b/brewman/schemas/input.py @@ -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 \ No newline at end of file diff --git a/brewman/schemas/voucher.py b/brewman/schemas/voucher.py index eeaf21da..a009c983 100644 --- a/brewman/schemas/voucher.py +++ b/brewman/schemas/voucher.py @@ -2,7 +2,7 @@ import uuid import json from datetime import datetime, date from decimal import Decimal -from typing import List, Optional +from typing import List, Optional, Any from fastapi import Form from pydantic import BaseModel, validator, Field @@ -11,6 +11,14 @@ from brewman.schemas import to_camel from brewman.schemas.master import AccountLink, CostCentreLink, ProductLink +class UserLink(BaseModel): + id_: uuid.UUID + name: str + + class Config: + alias_generator = to_camel + + class Journal(BaseModel): id_: Optional[uuid.UUID] debit: int = Field(ge=-1, le=1, multiple_of=1) @@ -23,20 +31,34 @@ class Journal(BaseModel): alias_generator = to_camel -class Inventory(BaseModel): +class Batch(BaseModel): id_: uuid.UUID + name: str product: ProductLink - batch_id: uuid.UUID - quantity: Decimal = Field(ge=0, multiple_of=0.01) - rate: Decimal = Field(ge=0, multiple_of=0.01) - tax: Decimal = Field(ge=0, multiple_of=0.00001) - discount: Decimal = Field(ge=0, multiple_of=0.00001) + quantity_remaining: Decimal + rate: Decimal + tax: Decimal + discount: Decimal class Config: alias_generator = to_camel -class SalaryDeduction(BaseModel): +class Inventory(BaseModel): + id_: Optional[uuid.UUID] + product: ProductLink + batch: Optional[Batch] + quantity: Decimal = Field(ge=0, multiple_of=0.01) + rate: Decimal = Field(ge=0, multiple_of=0.01) + tax: Decimal = Field(ge=0, multiple_of=0.00001, le=5) + discount: Decimal = Field(ge=0, multiple_of=0.00001, le=1) + amount: Optional[Decimal] + + class Config: + alias_generator = to_camel + + +class EmployeeBenefit(BaseModel): id: uuid.UUID voucher_id: uuid.UUID journal_id: uuid.UUID @@ -56,31 +78,31 @@ class Incentive(BaseModel): points: Decimal -class Batch(BaseModel): - id: uuid.UUID - name: datetime - product_id: uuid.UUID - quantity_remaining: Decimal - rate: Decimal - tax: Decimal - discount: Decimal - - -class Voucher(BaseModel): - id_: Optional[uuid.UUID] +class VoucherIn(BaseModel): date_: date narration: str + is_starred: bool + type_: str + + @classmethod + def load_form(cls, data: str = Form(...)): + json_data = json.loads(data) + return cls.parse_obj(json_data) + + +class Voucher(VoucherIn): + id_: Optional[uuid.UUID] is_reconciled: Optional[bool] reconcile_date: Optional[date] - is_starred: bool creation_date: Optional[datetime] last_edit_date: Optional[datetime] - type_: str - user_id: Optional[uuid.UUID] + user: UserLink posted: Optional[bool] poster_id: Optional[uuid.UUID] journals: List[Journal] inventories: List[Inventory] + vendor: Optional[AccountLink] + files: List[Any] class Config: anystr_strip_whitespace = True @@ -121,7 +143,7 @@ class Voucher(BaseModel): @validator("journals") def validate_enough_journals(cls, value: List[Journal]): - if len(value) < 2: + if 0 < len(value) < 2: raise ValueError("Not enough journals") return value @@ -138,11 +160,6 @@ class Voucher(BaseModel): raise ValueError("Duplicate journals") return value - @classmethod - def load_form(cls, data: str = Form(...)): - json_data = json.loads(data) - return cls.parse_obj(json_data) - class AttendanceType(BaseModel): id_: int diff --git a/brewman/scripts/initializedb.py b/brewman/scripts/initializedb.py index 7549b44c..33f7b033 100644 --- a/brewman/scripts/initializedb.py +++ b/brewman/scripts/initializedb.py @@ -36,7 +36,7 @@ from brewman.models.voucher import ( Inventory, Journal, Product, - SalaryDeduction, + EmployeeBenefit, Voucher, Incentive, DbImage, @@ -125,7 +125,7 @@ def main(argv=sys.argv): uuid.UUID("a8328891-7ce2-a943-8c29-2eabc1ffeea3"), ), Role("Clients", uuid.UUID("cfad44f0-f2a9-7045-89d7-9019cf0f371a")), - Role("Salary Deduction", uuid.UUID("92d70e80-1c32-384d-959e-abf84b804696")), + Role("Employee Benefit", uuid.UUID("92d70e80-1c32-384d-959e-abf84b804696")), Role("Messages", uuid.UUID("f586d128-b6d9-4090-a913-78fcbdb68e59")), Role("Lock Date", uuid.UUID("d52de0be-9388-4b0b-a359-7e122ab6e53a")), Role("Net Transactions", uuid.UUID("2c40f7cf-67fc-4efa-a670-8d16a2e7884d")), diff --git a/overlord/src/app/core/nav-bar/nav-bar.component.html b/overlord/src/app/core/nav-bar/nav-bar.component.html index c659fc1d..c16a03d3 100644 --- a/overlord/src/app/core/nav-bar/nav-bar.component.html +++ b/overlord/src/app/core/nav-bar/nav-bar.component.html @@ -3,7 +3,7 @@ <mat-menu #voucherMenu="matMenu"> <a mat-menu-item routerLink="/journal">Journal</a> <a mat-menu-item routerLink="/purchase">Purchase</a> - <a mat-menu-item routerLink="/return">Purchase Return</a> + <a mat-menu-item routerLink="/purchase-return">Purchase Return</a> <a mat-menu-item routerLink="/payment">Payment</a> <a mat-menu-item routerLink="/receipt">Receipt</a> <a mat-menu-item routerLink="/issue">Issue</a> diff --git a/overlord/src/app/core/voucher.service.ts b/overlord/src/app/core/voucher.service.ts index 403413db..0e3586f5 100644 --- a/overlord/src/app/core/voucher.service.ts +++ b/overlord/src/app/core/voucher.service.ts @@ -63,9 +63,16 @@ export class VoucherService { ); } - save(voucher: Voucher): Observable<Voucher> { + saveOrUpdate(voucher: Voucher): Observable<Voucher> { const endpoint = voucher.type.replace(/ /g, '-').toLowerCase(); + if (!voucher.id) { + return this.save(voucher, endpoint); + } else { + return this.update(voucher, endpoint); + } + } + save(voucher: Voucher, endpoint: string): Observable<Voucher> { const fd = new FormData(); voucher.files.filter(x => !x.id).forEach((file) => { fd.append('i' , this.dataURLtoBlob(file.resized)); @@ -73,10 +80,23 @@ export class VoucherService { }); voucher.files = voucher.files.filter(x => x.id); fd.append('data', JSON.stringify(voucher)); - const saveUrl: string = (voucher.id === undefined || voucher.id === null) ? `${url}/${endpoint}` : `${url}/${endpoint}/${voucher.id}`; - return <Observable<Voucher>>this.http.post<Voucher>(saveUrl, fd) + return <Observable<Voucher>>this.http.post<Voucher>(`${url}/${endpoint}`, fd) .pipe( - catchError(this.log.handleError(serviceName, 'list')) + catchError(this.log.handleError(serviceName, 'save')) + ); + } + + update(voucher: Voucher, endpoint: string): Observable<Voucher> { + const fd = new FormData(); + voucher.files.filter(x => !x.id).forEach((file) => { + fd.append('i' , this.dataURLtoBlob(file.resized)); + fd.append('t' , this.dataURLtoBlob(file.thumbnail)); + }); + voucher.files = voucher.files.filter(x => x.id); + fd.append('data', JSON.stringify(voucher)); + return <Observable<Voucher>>this.http.put<Voucher>(`${url}/${endpoint}/${voucher.id}`, fd) + .pipe( + catchError(this.log.handleError(serviceName, 'update')) ); } diff --git a/overlord/src/app/core/voucher.ts b/overlord/src/app/core/voucher.ts index bc308ebc..6f2d0180 100644 --- a/overlord/src/app/core/voucher.ts +++ b/overlord/src/app/core/voucher.ts @@ -10,6 +10,7 @@ export class Voucher { posted: boolean; narration: string; incentive?: number; + vendor?: Account; journals: Journal[]; inventories: Inventory[]; employeeBenefits: EmployeeBenefit[]; diff --git a/overlord/src/app/employee-benefits/employee-benefits.component.ts b/overlord/src/app/employee-benefits/employee-benefits.component.ts index e2f55e3d..446185c9 100644 --- a/overlord/src/app/employee-benefits/employee-benefits.component.ts +++ b/overlord/src/app/employee-benefits/employee-benefits.component.ts @@ -191,7 +191,7 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { } save() { - this.ser.save(this.getVoucher()) + this.ser.saveOrUpdate(this.getVoucher()) .subscribe( (result) => { this.toaster.show('Success', ''); diff --git a/overlord/src/app/incentive/incentive.component.ts b/overlord/src/app/incentive/incentive.component.ts index 11e6e763..cefa2573 100644 --- a/overlord/src/app/incentive/incentive.component.ts +++ b/overlord/src/app/incentive/incentive.component.ts @@ -141,7 +141,7 @@ export class IncentiveComponent implements OnInit { } save() { - this.ser.save(this.getVoucher()) + this.ser.saveOrUpdate(this.getVoucher()) .subscribe( (result) => { this.toaster.show('Success', ''); diff --git a/overlord/src/app/issue/issue.component.ts b/overlord/src/app/issue/issue.component.ts index 30839f65..543d4ac9 100644 --- a/overlord/src/app/issue/issue.component.ts +++ b/overlord/src/app/issue/issue.component.ts @@ -223,7 +223,7 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { } save() { - this.ser.save(this.getVoucher()) + this.ser.saveOrUpdate(this.getVoucher()) .subscribe( (result) => { this.loadVoucher(result); diff --git a/overlord/src/app/journal/journal.component.ts b/overlord/src/app/journal/journal.component.ts index b9efa24c..9cbc8e17 100644 --- a/overlord/src/app/journal/journal.component.ts +++ b/overlord/src/app/journal/journal.component.ts @@ -224,7 +224,7 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { } save() { - this.ser.save(this.getVoucher()) + this.ser.saveOrUpdate(this.getVoucher()) .subscribe( (result) => { this.loadVoucher(result); diff --git a/overlord/src/app/payment/payment.component.ts b/overlord/src/app/payment/payment.component.ts index d1538d10..d0491b14 100644 --- a/overlord/src/app/payment/payment.component.ts +++ b/overlord/src/app/payment/payment.component.ts @@ -228,7 +228,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { } save() { - this.ser.save(this.getVoucher()) + this.ser.saveOrUpdate(this.getVoucher()) .subscribe( (result) => { this.loadVoucher(result); diff --git a/overlord/src/app/purchase-return/purchase-return.component.ts b/overlord/src/app/purchase-return/purchase-return.component.ts index 8fee6dce..0c7c5c8d 100644 --- a/overlord/src/app/purchase-return/purchase-return.component.ts +++ b/overlord/src/app/purchase-return/purchase-return.component.ts @@ -8,7 +8,7 @@ import {PurchaseReturnDataSource} from './purchase-return-datasource'; import {Account} from '../core/account'; import {VoucherService} from '../core/voucher.service'; import {AccountService} from '../core/account.service'; -import {Batch, DbFile, Inventory, Journal, Voucher} from '../core/voucher'; +import {Batch, DbFile, Inventory, Voucher} from '../core/voucher'; import * as moment from 'moment'; import {AuthService} from '../auth/auth.service'; import {ConfirmDialogComponent} from '../shared/confirm-dialog/confirm-dialog.component'; @@ -17,7 +17,7 @@ import {debounceTime, distinctUntilChanged, map, startWith, switchMap} from 'rxj import {PurchaseReturnDialogComponent} from './purchase-return-dialog.component'; import {ImageDialogComponent} from '../shared/image-dialog/image-dialog.component'; import {BatchService} from '../core/batch.service'; -import {Hotkey, HotkeysService} from "angular2-hotkeys"; +import {Hotkey, HotkeysService} from 'angular2-hotkeys'; @Component({ selector: 'app-purchase-return', @@ -31,9 +31,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy public inventoryObservable = new BehaviorSubject<Inventory[]>([]); dataSource: PurchaseReturnDataSource; form: FormGroup; - purchaseReturnJournal: Journal; voucher: Voucher; - account: Account; batch: Batch; accBal: any; @@ -71,13 +69,15 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy return false; // Prevent bubbling }, ['INPUT', 'SELECT', 'TEXTAREA'])); this.hotkeys.add(new Hotkey('ctrl+s', (event: KeyboardEvent): boolean => { - if (this.canSave()) + if (this.canSave()) { this.save(); + } return false; // Prevent bubbling }, ['INPUT', 'SELECT', 'TEXTAREA'])); this.hotkeys.add(new Hotkey('ctrl+p', (event: KeyboardEvent): boolean => { - if (this.voucher.id && !this.voucher.posted && this.auth.user.perms.indexOf('Post Vouchers') !== -1) + if (this.voucher.id && !this.voucher.posted && this.auth.user.perms.indexOf('Post Vouchers') !== -1) { this.post(); + } return false; // Prevent bubbling }, ['INPUT', 'SELECT', 'TEXTAREA'])); } @@ -92,11 +92,10 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy loadVoucher(voucher: Voucher) { this.voucher = voucher; - this.purchaseReturnJournal = this.voucher.journals.filter(x => x.debit === 1)[0]; this.form.setValue({ date: moment(this.voucher.date, 'DD-MMM-YYYY').toDate(), - account: this.purchaseReturnJournal.account, - amount: this.purchaseReturnJournal.amount, + account: this.voucher.vendor, + amount: Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)), addRow: { batch: '', quantity: '' @@ -152,8 +151,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy updateView() { this.inventoryObservable.next(this.voucher.inventories); - this.purchaseReturnJournal.amount = Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)); - this.form.get('amount').setValue(this.purchaseReturnJournal.amount); + this.form.get('amount').setValue(Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0))); } editRow(row: Inventory) { @@ -217,12 +215,12 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy } save() { - this.ser.save(this.getVoucher()) + this.ser.saveOrUpdate(this.getVoucher()) .subscribe( (result) => { this.loadVoucher(result); this.toaster.show('Success', ''); - this.router.navigate(['/return', result.id]); + this.router.navigate(['/purchase-return', result.id]); }, (error) => { this.toaster.show('Danger', error.error); @@ -233,7 +231,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy getVoucher(): Voucher { const formModel = this.form.value; this.voucher.date = moment(formModel.date).format('DD-MMM-YYYY'); - this.purchaseReturnJournal.account = formModel.account; + this.voucher.vendor = formModel.account; this.voucher.narration = formModel.narration; return this.voucher; } @@ -243,7 +241,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy .subscribe( (result) => { this.toaster.show('Success', ''); - this.router.navigate(['/return'], {replaceUrl: true}); + this.router.navigate(['/purchase-return'], {replaceUrl: true}); }, (error) => { this.toaster.show('Danger', error.error); @@ -297,7 +295,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.account = event.option.value; + this.form.get('account').setValue(event.option.value); } displayBatchName(batch?: Batch): string | undefined { diff --git a/overlord/src/app/purchase/purchase.component.ts b/overlord/src/app/purchase/purchase.component.ts index c51b8333..910c351e 100644 --- a/overlord/src/app/purchase/purchase.component.ts +++ b/overlord/src/app/purchase/purchase.component.ts @@ -8,7 +8,7 @@ import {PurchaseDataSource} from './purchase-datasource'; import {Account} from '../core/account'; import {VoucherService} from '../core/voucher.service'; import {AccountService} from '../core/account.service'; -import {DbFile, Inventory, Journal, Voucher} from '../core/voucher'; +import {DbFile, Inventory, Voucher} from '../core/voucher'; import * as moment from 'moment'; import {AuthService} from '../auth/auth.service'; import {ConfirmDialogComponent} from '../shared/confirm-dialog/confirm-dialog.component'; @@ -18,7 +18,7 @@ import {PurchaseDialogComponent} from './purchase-dialog.component'; import {ImageDialogComponent} from '../shared/image-dialog/image-dialog.component'; import {ProductService} from '../product/product.service'; import {Product} from '../core/product'; -import {Hotkey, HotkeysService} from "angular2-hotkeys"; +import {Hotkey, HotkeysService} from 'angular2-hotkeys'; @Component({ selector: 'app-purchase', @@ -32,9 +32,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { public inventoryObservable = new BehaviorSubject<Inventory[]>([]); dataSource: PurchaseDataSource; form: FormGroup; - purchaseJournal: Journal; voucher: Voucher; - account: Account; product: Product; accBal: any; @@ -72,13 +70,15 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { return false; // Prevent bubbling }, ['INPUT', 'SELECT', 'TEXTAREA'])); this.hotkeys.add(new Hotkey('ctrl+s', (event: KeyboardEvent): boolean => { - if (this.canSave()) + if (this.canSave()) { this.save(); + } return false; // Prevent bubbling }, ['INPUT', 'SELECT', 'TEXTAREA'])); this.hotkeys.add(new Hotkey('ctrl+p', (event: KeyboardEvent): boolean => { - if (this.voucher.id && !this.voucher.posted && this.auth.user.perms.indexOf('Post Vouchers') !== -1) + if (this.voucher.id && !this.voucher.posted && this.auth.user.perms.indexOf('Post Vouchers') !== -1) { this.post(); + } return false; // Prevent bubbling }, ['INPUT', 'SELECT', 'TEXTAREA'])); } @@ -93,11 +93,10 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { loadVoucher(voucher) { this.voucher = voucher; - this.purchaseJournal = this.voucher.journals.filter(x => x.debit === -1)[0]; this.form.setValue({ date: moment(this.voucher.date, 'DD-MMM-YYYY').toDate(), - account: this.purchaseJournal.account, - amount: this.purchaseJournal.amount, + account: this.voucher.vendor, + amount: Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)), addRow: { product: '', quantity: '', @@ -161,8 +160,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { updateView() { this.inventoryObservable.next(this.voucher.inventories); - this.purchaseJournal.amount = Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)); - this.form.get('amount').setValue(this.purchaseJournal.amount); + this.form.get('amount').setValue(Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0))); } editRow(row: Inventory) { @@ -230,7 +228,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { } save() { - this.ser.save(this.getVoucher()) + this.ser.saveOrUpdate(this.getVoucher()) .subscribe( (result) => { this.loadVoucher(result); @@ -246,7 +244,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { getVoucher(): Voucher { const formModel = this.form.value; this.voucher.date = moment(formModel.date).format('DD-MMM-YYYY'); - this.purchaseJournal.account = formModel.account; + this.voucher.vendor = formModel.account; this.voucher.narration = formModel.narration; return this.voucher; } @@ -306,7 +304,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.account = event.option.value; + this.form.get('account').setValue(event.option.value); } displayProductName(product?: Product): string | undefined { diff --git a/overlord/src/app/receipt/receipt.component.ts b/overlord/src/app/receipt/receipt.component.ts index 647293f1..25374596 100644 --- a/overlord/src/app/receipt/receipt.component.ts +++ b/overlord/src/app/receipt/receipt.component.ts @@ -227,7 +227,7 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { } save() { - this.ser.save(this.getVoucher()) + this.ser.saveOrUpdate(this.getVoucher()) .subscribe( (result) => { this.loadVoucher(result);