From 98edca5f609e9cbd2bbaf68b4153449e76d8795d Mon Sep 17 00:00:00 2001
From: tanshu <tanshu@gmail.com>
Date: Thu, 21 May 2020 13:11:47 +0530
Subject: [PATCH] Renamed "Salary Deduction" to "Employee Benefit"

Journal, Purchase and Purchase Return vouchers done!!

Changed the column type of "date" columns from "datetime" to better fit the data.
---
 .../versions/03ea3e9cb1e5_rename_further.py   |  54 ---
 ...f58d_rename_service_charge_to_incentive.py |  46 ---
 .../eed0b382c287_lowercase_fastapi.py         |  41 ++-
 brewman/db/base.py                            |   2 +-
 brewman/main.py                               |   6 +-
 brewman/models/__init__.py                    |   2 +-
 brewman/models/operations.py                  |  23 --
 brewman/models/voucher.py                     |  61 ++--
 brewman/routers/employee.py                   |   4 +-
 .../{deduction.py => employee_benefit.py}     |   0
 brewman/routers/issue.py                      | 108 ++++++
 brewman/routers/journal.py                    |  90 ++---
 brewman/routers/management/rebase.py          |   4 +-
 brewman/routers/purchase.py                   | 313 +++++++++++++-----
 brewman/routers/purchase_return.py            | 273 ++++++++++++---
 brewman/routers/voucher/__init__.py           | 130 ++++----
 ...alary_deduction.py => employee_benefit.py} |  24 +-
 brewman/routers/voucher/emptyvoucher.py       |   6 +-
 brewman/routers/voucher/issue.py              |   1 -
 brewman/routers/voucher/purchase.py           | 192 -----------
 brewman/routers/voucher/purchase_return.py    | 218 ------------
 brewman/schemas/input.py                      |  95 ++++++
 brewman/schemas/voucher.py                    |  75 +++--
 brewman/scripts/initializedb.py               |   4 +-
 .../app/core/nav-bar/nav-bar.component.html   |   2 +-
 overlord/src/app/core/voucher.service.ts      |  28 +-
 overlord/src/app/core/voucher.ts              |   1 +
 .../employee-benefits.component.ts            |   2 +-
 .../src/app/incentive/incentive.component.ts  |   2 +-
 overlord/src/app/issue/issue.component.ts     |   2 +-
 overlord/src/app/journal/journal.component.ts |   2 +-
 overlord/src/app/payment/payment.component.ts |   2 +-
 .../purchase-return.component.ts              |  30 +-
 .../src/app/purchase/purchase.component.ts    |  26 +-
 overlord/src/app/receipt/receipt.component.ts |   2 +-
 35 files changed, 977 insertions(+), 894 deletions(-)
 delete mode 100644 alembic/versions/03ea3e9cb1e5_rename_further.py
 delete mode 100644 alembic/versions/5498fc4bf58d_rename_service_charge_to_incentive.py
 rename brewman/routers/{deduction.py => employee_benefit.py} (100%)
 rename brewman/routers/voucher/{salary_deduction.py => employee_benefit.py} (88%)
 delete mode 100644 brewman/routers/voucher/purchase.py
 delete mode 100644 brewman/routers/voucher/purchase_return.py
 create mode 100644 brewman/schemas/input.py

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);