From 64c18208672bad048660c8d584887719c449acda Mon Sep 17 00:00:00 2001 From: Tanshu Date: Mon, 30 Sep 2013 01:09:02 +0530 Subject: [PATCH] Feature: Deletion of active accounts and employees built. Feature: Many columns made non-nullable to better safeguard data. Fixed: Employee save/update did not work due to misplaced date check. --- brewman/brewman/models/master.py | 21 +++-- brewman/brewman/models/voucher.py | 22 +++--- .../static/partial/employee-detail.html | 1 + brewman/brewman/static/partial/settings.html | 4 +- brewman/brewman/static/scripts/account.js | 2 +- brewman/brewman/static/scripts/employee.js | 30 +++++++- brewman/brewman/static/scripts/journal.js | 4 +- brewman/brewman/views/account.py | 42 +++++++++- brewman/brewman/views/employee.py | 77 +++++++++++++++---- 9 files changed, 161 insertions(+), 42 deletions(-) diff --git a/brewman/brewman/models/master.py b/brewman/brewman/models/master.py index 31575fc6..1b61a8a9 100644 --- a/brewman/brewman/models/master.py +++ b/brewman/brewman/models/master.py @@ -174,7 +174,8 @@ class LedgerBase(Base): def type_object(self): return LedgerType.by_id(self.type) - def __init__(self, code=None, name=None, type=None, is_active=None, is_reconcilable=False, costcenter_id=None, is_fixture=False): + def __init__(self, code=None, name=None, type=None, is_active=None, is_reconcilable=False, costcenter_id=None, + is_fixture=False): self.code = code self.name = name self.type = type @@ -216,15 +217,13 @@ class LedgerBase(Base): DBSession.add(self) return self - def can_delete(self): + def can_delete(self, advanced_delete): if self.is_fixture: return False, "{0} is a fixture and cannot be edited or deleted.".format(self.name) if self.is_active: return False, 'Account is active' - if len(self.journals) > 0: + if len(self.journals) > 0 and not advanced_delete: return False, 'Account has journal entries' - if len(self.products) > 0: - return False, 'Account has products' return True, '' @classmethod @@ -257,6 +256,10 @@ class LedgerBase(Base): def esi_pf_payable(cls): return uuid.UUID('42277912-cc18-854b-b134-9f4b00dba419') + @classmethod + def suspense(cls): + return uuid.UUID('3854e317-6f3b-5142-ab26-9c44d4cddd08') + class Employee(LedgerBase): __tablename__ = 'entities_employees' @@ -292,6 +295,9 @@ class Employee(LedgerBase): DBSession.add(self) return self + def can_delete(self, advanced_delete): + return super(Employee, self).can_delete(advanced_delete) + @classmethod def list(cls): return DBSession.query(Employee).order_by(desc(Employee.is_active)).order_by(Ledger.costcenter_id).order_by( @@ -305,6 +311,11 @@ class Employee(LedgerBase): class Ledger(LedgerBase): __mapper_args__ = {'polymorphic_identity': ''} + def can_delete(self, advanced_delete): + if len(self.products) > 0: + return False, 'Account has products' + return super(Ledger, self).can_delete(advanced_delete) + @classmethod def list(cls): return DBSession.query(Ledger).order_by(Ledger.type).order_by(Ledger.name).order_by(Ledger.code).all() diff --git a/brewman/brewman/models/voucher.py b/brewman/brewman/models/voucher.py index bbafa4c2..bb8dd83c 100644 --- a/brewman/brewman/models/voucher.py +++ b/brewman/brewman/models/voucher.py @@ -60,7 +60,7 @@ class Voucher(Base): last_edit_date = Column('LastEditDate', DateTime(timezone=True)) _type = Column('VoucherType', Integer) - user_id = Column('UserID', GUID(), ForeignKey('auth_users.UserID')) + user_id = Column('UserID', GUID(), ForeignKey('auth_users.UserID'), nullable=False) poster_id = Column('PosterID', GUID(), ForeignKey('auth_users.UserID')) user = relationship('User', primaryjoin="User.id==Voucher.user_id", cascade=None) @@ -121,9 +121,9 @@ class Journal(Base): id = Column('JournalID', GUID(), primary_key=True, default=uuid.uuid4) debit = Column('Debit', Integer) amount = Column('Amount', Numeric) - voucher_id = Column('VoucherID', GUID(), ForeignKey('entities_vouchers.VoucherID')) - ledger_id = Column('LedgerID', GUID(), ForeignKey('entities_ledgers.LedgerID')) - cost_center_id = Column('CostCenterID', GUID(), ForeignKey('entities_costcenters.CostCenterID')) + voucher_id = Column('VoucherID', GUID(), ForeignKey('entities_vouchers.VoucherID'), nullable=False) + ledger_id = Column('LedgerID', GUID(), ForeignKey('entities_ledgers.LedgerID'), nullable=False) + cost_center_id = Column('CostCenterID', GUID(), ForeignKey('entities_costcenters.CostCenterID'), nullable=False) @hybrid_property def signed_amount(self): @@ -157,8 +157,8 @@ class Journal(Base): class SalaryDeduction(Base): __tablename__ = 'entities_salarydeductions' id = Column('SalaryDeductionID', GUID(), primary_key=True, default=uuid.uuid4) - voucher_id = Column('VoucherID', GUID(), ForeignKey('entities_vouchers.VoucherID')) - journal_id = Column('JournalID', GUID(), ForeignKey('entities_journals.JournalID')) + voucher_id = Column('VoucherID', GUID(), ForeignKey('entities_vouchers.VoucherID'), nullable=False) + journal_id = Column('JournalID', GUID(), ForeignKey('entities_journals.JournalID'), nullable=False) gross_salary = Column('GrossSalary', Integer) days_worked = Column('DaysWorked', Integer) esi_ee = Column('EsiEmployee', Integer) @@ -188,9 +188,9 @@ class Inventory(Base): __tablename__ = 'entities_inventories' # __tableagrs__ = (UniqueConstraint('VoucherID', 'ProductID')) id = Column('InventoryID', GUID(), primary_key=True, default=uuid.uuid4) - voucher_id = Column('VoucherID', GUID(), ForeignKey('entities_vouchers.VoucherID')) - product_id = Column('ProductID', GUID(), ForeignKey('entities_products.ProductID')) - batch_id = Column('BatchID', GUID(), ForeignKey('entities_batches.BatchID')) + voucher_id = Column('VoucherID', GUID(), ForeignKey('entities_vouchers.VoucherID'), nullable=False) + product_id = Column('ProductID', GUID(), ForeignKey('entities_products.ProductID'), nullable=False) + batch_id = Column('BatchID', GUID(), ForeignKey('entities_batches.BatchID'), nullable=False) quantity = Column('Quantity', Numeric) rate = Column('Rate', Numeric) tax = Column('Tax', Numeric) @@ -219,7 +219,7 @@ class Batch(Base): id = Column('BatchID', GUID(), primary_key=True, default=uuid.uuid4) name = Column('Name', DateTime) - product_id = Column('ProductID', GUID(), ForeignKey('entities_products.ProductID')) + product_id = Column('ProductID', GUID(), ForeignKey('entities_products.ProductID'), nullable=False) quantity_remaining = Column('QuantityRemaining', Numeric) rate = Column('Rate', Numeric) tax = Column('Tax', Numeric) @@ -297,7 +297,7 @@ class Attendance(Base): DBSession.add(self) -class Fingerprint(Base): +class Fingerprint(Base): __tablename__ = 'entities_fingerprints' id = Column('FingerprintID', GUID(), primary_key=True, default=uuid.uuid4) diff --git a/brewman/brewman/static/partial/employee-detail.html b/brewman/brewman/static/partial/employee-detail.html index 07889b6d..383f443f 100644 --- a/brewman/brewman/static/partial/employee-detail.html +++ b/brewman/brewman/static/partial/employee-detail.html @@ -83,6 +83,7 @@
+
diff --git a/brewman/brewman/static/partial/settings.html b/brewman/brewman/static/partial/settings.html index deff356d..474e957b 100644 --- a/brewman/brewman/static/partial/settings.html +++ b/brewman/brewman/static/partial/settings.html @@ -14,10 +14,10 @@
- +
- +
diff --git a/brewman/brewman/static/scripts/account.js b/brewman/brewman/static/scripts/account.js index 58e9514c..b027012d 100644 --- a/brewman/brewman/static/scripts/account.js +++ b/brewman/brewman/static/scripts/account.js @@ -16,7 +16,7 @@ AccountListCtrl.resolve = { }] }; -var AccountCtrl = ['$scope', '$location', 'account', 'account_types', 'cost_centers', function ($scope, $location, account, account_types, cost_centers) { +var AccountCtrl = ['$scope', '$location', '$modal', 'account', 'account_types', 'cost_centers', function ($scope, $location, $modal, account, account_types, cost_centers) { $scope.account = account; $scope.account_types = account_types; diff --git a/brewman/brewman/static/scripts/employee.js b/brewman/brewman/static/scripts/employee.js index efd72f90..104540a8 100644 --- a/brewman/brewman/static/scripts/employee.js +++ b/brewman/brewman/static/scripts/employee.js @@ -17,19 +17,19 @@ EmployeeListCtrl.resolve = { }] }; -var EmployeeCtrl = ['$scope', '$routeParams', '$location', 'dateFilter', 'employee', 'cost_centers', function ($scope, $routeParams, $location, dateFilter, employee, cost_centers) { +var EmployeeCtrl = ['$scope', '$routeParams', '$location', 'dateFilter', '$modal', 'employee', 'cost_centers', function ($scope, $routeParams, $location, dateFilter, $modal, employee, cost_centers) { $scope.employee = employee; $scope.cost_centers = cost_centers; $scope.save = function () { - $scope.employee.$save(function (u, putResponseHeaders) { if (angular.isDate($scope.employee.JoiningDate)) { - $scope.info.Date = dateFilter($scope.info.JoiningDate, 'dd-MMM-yyyy'); + $scope.employee.JoiningDate = dateFilter($scope.employee.JoiningDate, 'dd-MMM-yyyy'); } if (angular.isDate($scope.employee.LeavingDate)) { - $scope.info.Date = dateFilter($scope.info.LeavingDate, 'dd-MMM-yyyy'); + $scope.employee.LeavingDate = dateFilter($scope.employee.LeavingDate, 'dd-MMM-yyyy'); } + $scope.employee.$save(function (u, putResponseHeaders) { $scope.toasts.push({Type: 'Success', Message: u.Code}); $location.path('/Employees'); }, function (data, status) { @@ -45,6 +45,28 @@ var EmployeeCtrl = ['$scope', '$routeParams', '$location', 'dateFilter', 'employ $scope.toasts.push({Type: 'Danger', Message: data.data}); }); }; + + $scope.confirm = function () { + var modalInstance = $modal.open({ + backdrop: true, + templateUrl: '/template/modal/confirm.html', + controller: ['$scope', '$modalInstance', function ($scope, $modalInstance) { + $scope.title = "Delete Account"; + $scope.body = "Are you sure? This cannot be undone."; + $scope.isDelete = true; + $scope.ok = function () { + $modalInstance.close(); + }; + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + }] + }); + modalInstance.result.then(function () { + $scope.delete(); + }); + }; + $('#txtName').focus(); }]; diff --git a/brewman/brewman/static/scripts/journal.js b/brewman/brewman/static/scripts/journal.js index bf6b955f..5cb9f736 100644 --- a/brewman/brewman/static/scripts/journal.js +++ b/brewman/brewman/static/scripts/journal.js @@ -197,5 +197,5 @@ var JournalModalCtrl = ['$scope', '$modalInstance', '$q', 'edit', 'Account', fun deferred.resolve(result); }); return deferred.promise; - } -}] + }; +}]; diff --git a/brewman/brewman/views/account.py b/brewman/brewman/views/account.py index bfa5600b..5306f997 100644 --- a/brewman/brewman/views/account.py +++ b/brewman/brewman/views/account.py @@ -1,12 +1,16 @@ import uuid from pyramid.response import Response +from pyramid.security import authenticated_userid from pyramid.view import view_config +from sqlalchemy.orm import joinedload_all import transaction +from brewman import groupfinder from brewman.models import DBSession from brewman.models.master import CostCenter, Ledger, LedgerType, LedgerBase from brewman.models.validation_exception import ValidationError, TryCatchFunction +from brewman.models.voucher import Voucher, Journal, VoucherType @view_config(route_name='account_list', renderer='brewman:templates/angular_base.mako', permission='Accounts') @@ -49,9 +53,9 @@ def update(request): @view_config(request_method='DELETE', route_name='api_account_id', renderer='json', permission='Accounts') def delete(request): account = Ledger.by_id(uuid.UUID(request.matchdict['id'])) - can_delete, reason = account.can_delete() + can_delete, reason = account.can_delete('Advanced Delete' in groupfinder(authenticated_userid(request), request)) if can_delete: - DBSession.delete(account) + delete_with_data(account) transaction.commit() return account_info(None) else: @@ -125,3 +129,37 @@ def account_info(id): return account +def delete_with_data(account): + suspense_ledger = Ledger.by_id(Ledger.suspense()) + query = Voucher.query().options(joinedload_all(Voucher.journals, Journal.ledger, innerjoin=True)) \ + .filter(Voucher.journals.any(Journal.ledger_id == account.id)) \ + .all() + + for voucher in query: + others, sus_jnl, acc_jnl = False, None, None + for journal in voucher.journals: + if journal.ledger_id == account.id: + acc_jnl = journal + elif journal.ledger_id == Ledger.suspense(): + sus_jnl = journal + else: + others = True + if not others: + DBSession.delete(voucher) + else: + if sus_jnl is None: + acc_jnl.ledger = suspense_ledger + voucher.narration += '\nSuspense \u20B9 {0:,.2f} is {1}'.format(acc_jnl.amount, account.name) + else: + amount = (sus_jnl.debit * sus_jnl.amount) + (acc_jnl.debit * acc_jnl.amount) + DBSession.delete(acc_jnl) + if amount == 0: + DBSession.delete(sus_jnl) + else: + sus_jnl.amount = abs(amount) + sus_jnl.debit = -1 if amount < 0 else 1 + voucher.narration += '\nDeleted \u20B9 {0:,.2f} of {1}'.format(acc_jnl.amount * acc_jnl.debit, + account.name) + if voucher.type in (VoucherType.by_name('Payment').id, VoucherType.by_name('Receipt').id): + voucher.type = VoucherType.by_name('Journal') + DBSession.delete(account) diff --git a/brewman/brewman/views/employee.py b/brewman/brewman/views/employee.py index 0f478bed..d7f1b732 100644 --- a/brewman/brewman/views/employee.py +++ b/brewman/brewman/views/employee.py @@ -1,21 +1,26 @@ import datetime import uuid from pyramid.response import Response +from pyramid.security import authenticated_userid from pyramid.view import view_config +from sqlalchemy.orm import joinedload_all import transaction +from brewman import groupfinder from brewman.models import DBSession -from brewman.models.master import CostCenter, Employee, LedgerBase +from brewman.models.master import CostCenter, Employee, LedgerBase, Ledger from brewman.models.validation_exception import ValidationError, TryCatchFunction +from brewman.models.voucher import Voucher, Journal, VoucherType + @view_config(route_name='employee_list', renderer='brewman:templates/angular_base.mako', permission='Authenticated') @view_config(request_method='GET', route_name='employee_id', renderer='brewman:templates/angular_base.mako', - permission='Employees') + permission='Employees') @view_config(request_method='GET', route_name='employee', renderer='brewman:templates/angular_base.mako', - permission='Employees') + permission='Employees') @view_config(request_method='GET', route_name='employee_functions', renderer='brewman:templates/angular_base.mako', - permission='Employees') + permission='Employees') def html(request): return {} @@ -26,13 +31,13 @@ def save(request): is_active = request.json_body['IsActive'] joining_date = datetime.datetime.strptime(request.json_body['JoiningDate'], '%d-%b-%Y') leaving_date = None if is_active else datetime.datetime.strptime(request.json_body['LeavingDate'], - '%d-%b-%Y') + '%d-%b-%Y') item = Employee(0, request.json_body['Name'], is_active, - uuid.UUID(request.json_body['CostCenter']['CostCenterID']), - request.json_body['Designation'], int(request.json_body['Salary']), - int(request.json_body['ServicePoints']), - joining_date, leaving_date).create() + uuid.UUID(request.json_body['CostCenter']['CostCenterID']), + request.json_body['Designation'], int(request.json_body['Salary']), + int(request.json_body['ServicePoints']), + joining_date, leaving_date).create() transaction.commit() return employee_info(item.id) @@ -51,7 +56,7 @@ def update(request): item.service_points = int(request.json_body['ServicePoints']) item.joining_date = datetime.datetime.strptime(request.json_body['JoiningDate'], '%d-%b-%Y') item.leaving_date = None if item.is_active else datetime.datetime.strptime(request.json_body['LeavingDate'], - '%d-%b-%Y') + '%d-%b-%Y') transaction.commit() return employee_info(item.id) @@ -59,9 +64,9 @@ def update(request): @view_config(request_method='DELETE', route_name='api_employee_id', renderer='json', permission='Employees') def delete(request): employee = Employee.by_id(uuid.UUID(request.matchdict['id'])) - can_delete, reason = employee.can_delete() + can_delete, reason = employee.can_delete('Advanced Delete' in groupfinder(authenticated_userid(request), request)) if can_delete: - DBSession.delete(employee) + delete_with_data(employee) transaction.commit() return employee_info(None) else: @@ -82,7 +87,7 @@ def show_blank(request): @view_config(request_method='GET', route_name='api_employee', request_param='list', renderer='json', - permission='Authenticated') + permission='Authenticated') def show_list(request): list = Employee.list() ledgers = [] @@ -97,7 +102,7 @@ def show_list(request): @view_config(request_method='GET', route_name='api_employee', renderer='json', request_param='term', - permission='Authenticated') + permission='Authenticated') def show_term(request): filter = request.GET.get('term', None) filter = None if filter == '' else filter @@ -123,4 +128,46 @@ def employee_info(id): 'ServicePoints': employee.service_points, 'JoiningDate': employee.joining_date.strftime('%d-%b-%Y'), 'LeavingDate': None if employee.is_active else employee.leaving_date.strftime('%d-%b-%Y'), 'CostCenter': {'CostCenterID': employee.costcenter_id, 'Name': employee.costcenter.name}} - return employee \ No newline at end of file + return employee + + +def delete_with_data(employee): + suspense_ledger = Ledger.by_id(Ledger.suspense()) + query = Voucher.query().options(joinedload_all(Voucher.journals, Journal.ledger, innerjoin=True))\ + .filter(Voucher.journals.any(Journal.ledger_id == employee.id))\ + .all() + + for voucher in query: + others, sus_jnl, acc_jnl = False, None, None + for journal in voucher.journals: + if journal.ledger_id == employee.id: + acc_jnl = journal + elif journal.ledger_id == Ledger.suspense(): + sus_jnl = journal + else: + others = True + if not others: + DBSession.delete(voucher) + else: + if sus_jnl is None: + acc_jnl.ledger = suspense_ledger + voucher.narration += '\nSuspense \u20B9 {0:,.2f} is {1}'.format(acc_jnl.amount, employee.name) + else: + amount = (sus_jnl.debit * sus_jnl.amount) + (acc_jnl.debit * acc_jnl.amount) + if acc_jnl.salary_deduction is not None: + DBSession.delete(acc_jnl.salary_deduction) + DBSession.delete(acc_jnl) + if amount == 0: + DBSession.delete(sus_jnl) + else: + sus_jnl.amount = abs(amount) + sus_jnl.debit = -1 if amount < 0 else 1 + voucher.narration += '\nDeleted \u20B9 {0:,.2f} of {1}'.format(acc_jnl.amount * acc_jnl.debit, + employee.name) + if voucher.type in (VoucherType.by_name('Payment').id, VoucherType.by_name('Receipt').id): + voucher.type = VoucherType.by_name('Journal') + for fingerprint in employee.fingerprints: + DBSession.delete(fingerprint) + for attendance in employee.attendances: + DBSession.delete(attendance) + DBSession.delete(employee)