Journal virtually complete using AngularJS.

Footer / update amount input on add / delete / update with remaining amount left.
This commit is contained in:
unknown 2012-08-23 13:09:01 +05:30
parent d1e654649b
commit d7789642c1
9 changed files with 255 additions and 96 deletions

View File

@ -0,0 +1,55 @@
var overlord_directive = angular.module('overlord.directive', []);
overlord_directive.directive('autocomplete', function () {
var directiveDefinitionObject = {
restrict:'E',
replace:true,
transclude:true,
template:'<input type="text" />',
link:function (scope, element, attrs) {
element.autocomplete({
source:function (request, response) {
$.ajax({
type:"POST",
url:attrs.url,
contentType:"application/json; charset=utf-8",
dataType:"json",
data:JSON.stringify({prefixText:request.term, count:20}),
success:function (data) {
response(data);
},
error:function (XMLHttpRequest, textStatus, errorThrown) {
showError(textStatus.responseText);
}
});
},
minLength:1,
autoFocus:true,
select:function (event, ui) {
scope[attrs.selname] = ui.item.label;
scope[attrs.selid] = ui.item.id;
scope.$apply();
}
});
}
};
return directiveDefinitionObject;
});
overlord_directive.directive('datepicker', function () {
var directiveDefinitionObject = {
restrict:'E',
replace:true,
transclude:true,
template:'<input type="text" />',
link:function (scope, element, attrs) {
element.datepicker({
dateFormat:'dd-M-yy',
onSelect:function (dateText, inst) {
scope[attrs.model][attrs.prop] = dateText;
scope.$apply();
}
});
}
};
return directiveDefinitionObject;
});

View File

@ -0,0 +1,19 @@
var overlord_filter = angular.module('overlord.filter', []);
overlord_filter.filter('debit', function () {
return function (input) {
if (input == -1) {
return 'Cr';
} else if (input == 1) {
return 'Dr';
} else {
return '';
}
;
};
});
overlord_filter.filter('posted', function () {
return function (input) {
return input == true ? 'Posted' : 'Post';
};
});

View File

@ -0,0 +1,41 @@
var overlord_service = angular.module('overlord.service', []);
overlord_service.factory('save_voucher', ['$http', function ($http) {
return function (scope) {
$http.post('', scope.voucher).
success(function (data, status) {
scope.voucher = data;
scope.toasts.push({Type:'Success', Message:data.Code});
}).
error(function (data, status) {
scope.toasts.push({Type:'Error', Message:data});
});
};
}]);
overlord_service.factory('delete_voucher', ['$http', function ($http) {
return function (scope) {
$http.post(delete_url, {}).
success(function (data, status) {
scope.voucher = {};
scope.toasts.push({Type:'Success', Message:data.Code});
}).
error(function (data, status) {
scope.toasts.push({Type:'Error', Message:data});
});
};
}]);
overlord_service.factory('post_voucher', ['$http', function ($http) {
return function (scope) {
$http.post(post_url, {}).
success(function (data, status) {
scope.voucher.Posted = true;
scope.voucher.Poster = data.name;
scope.toasts.push({Type:'Success', Message:data.Code});
}).
error(function (data, status) {
scope.toasts.push({Type:'Error', Message:data});
});
};
}]);

View File

@ -1,73 +1,6 @@
var myApp = angular.module('myApp', ['journalFilters']);
myApp.directive('autocomplete', function () {
var directiveDefinitionObject = {
restrict:'E',
replace:true,
transclude:true,
template:'<input type="text" />',
link:function (scope, element, attrs) {
element.autocomplete({
source:function (request, response) {
$.ajax({
type:"POST",
url:attrs.url,
contentType:"application/json; charset=utf-8",
dataType:"json",
data:JSON.stringify({prefixText:request.term, count:20}),
success:function (data) {
response(data);
},
error:function (XMLHttpRequest, textStatus, errorThrown) {
alert("Due to unexpected errors we were unable to load data\n\r" + textStatus);
}
});
},
minLength:1,
autoFocus:true,
select:function (event, ui) {
scope[attrs.selname] = ui.item.label;
scope[attrs.selid] = ui.item.id;
scope.$apply();
}
});
}
};
return directiveDefinitionObject;
});
var overlord = angular.module('overlord', ['overlord.directive', 'overlord.filter', 'overlord.service']);
myApp.directive('datepicker', function () {
var directiveDefinitionObject = {
restrict:'E',
replace:true,
transclude:true,
template:'<input type="text" />',
link:function (scope, element, attrs) {
element.datepicker({
dateFormat:'dd-M-yy',
onSelect:function (dateText, inst) {
scope[attrs.model][attrs.prop] = dateText;
scope.$apply();
}
});
}
};
return directiveDefinitionObject;
});
angular.module('journalFilters', []).filter('debit', function() {
return function(input) {
if (input == -1) {
return 'Cr';
} else if (input == 1) {
return 'Dr';
} else {
return '';
};
};
});
function AutoComplete(matchFieldName, lookupURL) {
$(matchFieldName).autocomplete({
source:function (request, response) {
@ -82,11 +15,62 @@ function AutoComplete(matchFieldName, lookupURL) {
response(data);
},
error:function (XMLHttpRequest, textStatus, errorThrown) {
alert("Due to unexpected errors we were unable to load data\n\r" + textStatus);
showError(textStatus.responseText);
}
});
},
minLength:1,
autoFocus:true
autoFocus:true
});
}
//http://jsfiddle.net/tadchristiansen/gt92r/
//<ng-confirm title="My Confirm Box" action-text="Do you want to do this?" action-button-text="Yes Sir" action-function="doIt()" cancel-button-text="No Way" cancel-function="cancel()">
// <a>Example Confirm</a>
//</ng-confirm>
//function PopupCtrl($scope, PopupService)
//{
// $scope.cancel = function () {
// PopupService.close();
// };
//
// $scope.doIt = function () {
// alert("you did it");
// PopupService.close();
// };
//
// $scope.okay = function () {
// alert("you clicked okay");
// PopupService.close();
// };
//}
//directivesModule.directive('ngConfirm', function(PopupService) {
// return {
// restrict: 'E',
// link: function postLink(scope, element, attrs) {
// // Could have custom or boostrap modal options here
// var popupOptions = {};
// element.bind("click", function()
// {
// PopupService.confirm(attrs["title"], attrs["actionText"],
// attrs["actionButtonText"], attrs["actionFunction"],
// attrs["cancelButtonText"], attrs["cancelFunction"],
// scope, popupOptions);
// });
// }
// };
//
//});
//overlord.factory('auth', function ($rootScope) {
// $rootScope.auth = {
// isUserInRole:function (role) {
// return $rootScope.auth.perms.indexOf(role) !== -1;
// }
// };
//});

View File

@ -1,15 +1,41 @@
function JournalCtrl($scope) {
// $scope.voucher = {
// code:'(Auto)',
// Journals:[],
// date:''
// };
function JournalCtrl($scope, save_voucher, delete_voucher, post_voucher) {
$scope.debit = 1;
$scope.addJournal = function () {
this.voucher.Journals.push({Debit:$scope.debit, Amount:$scope.amount, Ledger:{LedgerID:$scope.id, Name:$scope.name}});
for (var i = 0, l = this.voucher.Journals.length; i < l; i++) {
if (this.voucher.Journals[i].Ledger.LedgerID === $scope.id) {
var amount = (this.voucher.Journals[i].Debit * this.voucher.Journals[i].Amount) + (parseInt($scope.debit) * Number($scope.amount));
if (amount < 0) {
this.voucher.Journals[i].Debit = -1;
this.voucher.Journals[i].Amount = amount * -1;
} else {
this.voucher.Journals[i].Debit = 1;
this.voucher.Journals[i].Amount = amount;
}
break;
}
}
if (i >= l) {
this.voucher.Journals.push({Debit:$scope.debit, Amount:$scope.amount, Ledger:{LedgerID:$scope.id, Name:$scope.name}});
}
delete $scope.amount;
delete $scope.id;
delete $scope.name;
$('#txtLedger').focus();
};
$scope.removeJournal = function (journal) {
var index = this.voucher.Journals.indexOf(journal);
this.voucher.Journals.splice(index, 1);
};
$scope.save = function () {
save_voucher($scope);
};
$scope.delete = function () {
delete_voucher($scope);
};
$scope.post = function () {
post_voucher($scope);
};
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<html lang="en" ng-app="overlord">
<head>
<meta charset="utf-8">
<title>${title}</title>
@ -33,9 +33,12 @@
</script>
${h.ScriptLink(request, 'autocomplete.js')}
${h.ScriptLink(request, 'session.js')}
${h.ScriptLink(request, 'angular_directive.js')}
${h.ScriptLink(request, 'angular_filter.js')}
${h.ScriptLink(request, 'angular_service.js')}
${h.ScriptLink(request, 'session.js')}
<%block name="header" />
<%block name="header" />
<!-- Le fav and touch icons -->
<link rel="shortcut icon" href="${request.static_url('brewman:static/img/favicon.ico')}">
@ -76,7 +79,11 @@
<div class="container">
<div class="row">
<div id="status" class="span4 offset4"></div>
<div id="status" class="span4 offset4">
<div ng-repeat="toast in toasts" class="alert alert-{{toast.Type | lowercase}}">
<button class="close" data-dismiss="alert">×</button> <strong>{{toast.Type}}!</strong> {{toast.Message}}
</div>
</div>
</div>
<%block name="content"/>

View File

@ -2,19 +2,25 @@
<%inherit file="../base.mako"/>
<%block name="header">
<script type="text/javascript">
const add_journal_url = '${request.route_url('services_ledger_add_journal')}';
const post_url = '${request.route_url('post_voucher', id = id)}'
const delete_url = '${request.route_url('delete_voucher', id = id)}'
</script>
${h.ScriptLink(request, 'journal.js')}
<script type="text/javascript">
myApp.run(['$rootScope', function ($rootScope) {
overlord.run(['$rootScope', function ($rootScope) {
$rootScope.voucher = ${json_voucher};
$rootScope.saveUrl = '${request.route_url('post_voucher', id = id)}';
$rootScope.toasts = [];
$rootScope.auth = {
perms:${perms},
isUserInRole:function (role) {
return $rootScope.auth.perms.indexOf(role) !== -1;
}
};
}]);
</script>
<script type="text/javascript">
$(document).ready(function () {
// backboneRoles(['EditPosted', 'PostTransactions', 'Journal Delete', 'Journal Create', 'Journal Update']);
$('#txtLedger').focus();
});
</script>
@ -44,13 +50,14 @@
<option value="1">Dr</option>
<option value="-1">Cr</option>
</select>
<autocomplete id="txtLedger" url="${request.route_url('services_ledger_id_list')}" selname="name" selid="id"></autocomplete>
<autocomplete id="txtLedger" url="${request.route_url('services_ledger_id_list')}" selname="name"
selid="id" ng-model="name"></autocomplete>
<div class=" input-prepend">
<span class="add-on">₹</span>
<input type="text" id="txtAmount" class="span2" autocomplete="off"
placeholder="Amount" ng-model="amount"/>
</div>
<button ng:click="addJournal()">Add</button>
<button ng-click="addJournal()">Add</button>
</div>
</div>
<div class=" control-group">
@ -86,14 +93,28 @@
<div class="controls">
<textarea rows="2" cols="20" id="txtNarration"
class="non-search-box">{{voucher.Narration}}</textarea>
class="non-search-box" ng-model="voucher.Narration"></textarea>
</div>
</div>
<div class="form-actions">
${h.SubmitButton('btnSave', 'Save', 'btn btn-primary')}
${h.SubmitButton('btnUpdate', 'Update', 'btn-primary')}
${h.SubmitButton('btnPost', 'Post', 'btn btn-inverse')}
${h.SubmitButton('btnDelete', 'Delete', 'btn btn-danger')}
<button class="btn btn-primary" ng-click="save()" ng-hide="voucher.Code != '(Auto)'"
ng-disabled="!auth.isUserInRole('Journal Create')">Save
</button>
<button class="btn btn-primary" ng-click="save()" ng-hide="voucher.Code == '(Auto)'"
ng-disabled="!auth.isUserInRole('Journal Update') || (voucher.Posted && !auth.isUserInRole('EditPosted'))">
Update
</button>
<button class="btn btn-inverse" ng-click="post()" ng-hide="voucher.Code == '(Auto)'"
ng-disabled="voucher.Posted || !auth.isUserInRole('PostTransactions')">{{voucher.Posted | posted}}
</button>
<button class="btn btn-danger" ng-click="delete()" ng-hide="voucher.Code == '(Auto)'"
ng-disabled="!auth.isUserInRole('Journal Delete') || (voucher.Posted && !auth.isUserInRole('EditPosted'))">
Delete
</button>
</div>
<div class="row-fluid">
Created on {{voucher.CreationDate}} and Last Edited on {{voucher.LastEditDate}} by {{voucher.User}}. Posted
by {{voucher.Poster}}
</div>
</form>
</%block>

View File

@ -50,7 +50,7 @@ def voucher_post(request):
voucher.posted = True
voucher.poster_id = user.id
transaction.commit()
return {}
return {'id': str(user.id), 'name': user.name}
@view_config(request_method='POST', route_name='delete_voucher', renderer='json', xhr=True)
def voucher_delete(request):

View File

@ -24,6 +24,7 @@ from brewman.views.transactions import session_current_date
permission='Journal Create')
def journal_get(request):
id = request.matchdict.get('id', None)
perms = Literal(json.dumps(request.session['perms']))
if id is None:
json_voucher = Literal(
json.dumps({'Code': '(Auto)', 'Date': session_current_date(request), 'Posted': True, 'Narration': "",
@ -33,12 +34,17 @@ def journal_get(request):
json_voucher = Literal(json.dumps(voucher_info(voucher)))
return {'title': 'Hops n Grains - Journal Entry',
'id': id,
'json_voucher': json_voucher}
'json_voucher': json_voucher,
'perms': perms}
def voucher_info(voucher):
json_voucher = {'VoucherID': str(voucher.id), 'Date': voucher.date.strftime('%d-%b-%Y'), 'Posted': voucher.posted,
'Code': voucher.code, 'Narration': voucher.narration, 'Journals': []}
'Code': voucher.code, 'Narration': voucher.narration, 'Journals': [],
'CreationDate': voucher.creation_date.strftime('%d-%b-%Y %H:%M'),
'LastEditDate': voucher.last_edit_date.strftime('%d-%b-%Y %H:%M'), 'User': voucher.user.name,
'Poster': voucher.poster.name if voucher.posted else ''}
for item in voucher.journals:
json_voucher['Journals'].append({'JournalID': str(item.id), 'Debit': item.debit, 'Amount': str(item.amount),
'Ledger': {'LedgerID': str(item.ledger.id), 'Name': item.ledger.name}})
@ -95,7 +101,7 @@ def update_voucher(id, json, user):
found = False
for i in range(len(newJournals), 0, -1):
j = newJournals[i - 1]
if item.id == uuid.UUID(j['JournalID']):
if 'JournalID' in j and item.id == uuid.UUID(j['JournalID']):
ledger = LedgerBase.by_id(uuid.UUID(j['Ledger']['LedgerID']))
found = True
item.debit = int(j['Debit'])