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.
This commit is contained in:
Tanshu 2013-09-30 01:09:02 +05:30
parent 9c965727df
commit 64c1820867
9 changed files with 161 additions and 42 deletions

@ -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()

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

@ -83,6 +83,7 @@
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button class="btn btn-primary" ng-click="save()">Save</button>
<button class="btn btn-danger" ng-show="employee.LedgerID" ng-click="confirm()">Delete</button>
</div>
</div>
</form>

@ -14,10 +14,10 @@
</div>
</div>
<div class="col-md-2">
<button class="btn btn-primary" ng-click="setLockDate()">Set</button>
<button class="btn btn-block btn-primary" ng-click="setLockDate()">Set</button>
</div>
<div class="col-md-2">
<button class="btn btn-danger" ng-click="clearLockDate()">Clear</button>
<button class="btn btn-block btn-danger" ng-click="clearLockDate()">Clear</button>
</div>
</div>
</form>

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

@ -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();
}];

@ -197,5 +197,5 @@ var JournalModalCtrl = ['$scope', '$modalInstance', '$q', 'edit', 'Account', fun
deferred.resolve(result);
});
return deferred.promise;
}
}]
};
}];

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

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