Journal virtually complete using AngularJS.
Footer / update amount input on add / delete / update with remaining amount left.
This commit is contained in:
parent
d1e654649b
commit
d7789642c1
55
brewman/brewman/static/scripts/angular_directive.js
vendored
Normal file
55
brewman/brewman/static/scripts/angular_directive.js
vendored
Normal 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;
|
||||
});
|
19
brewman/brewman/static/scripts/angular_filter.js
vendored
Normal file
19
brewman/brewman/static/scripts/angular_filter.js
vendored
Normal 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';
|
||||
};
|
||||
});
|
41
brewman/brewman/static/scripts/angular_service.js
vendored
Normal file
41
brewman/brewman/static/scripts/angular_service.js
vendored
Normal 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});
|
||||
});
|
||||
};
|
||||
}]);
|
@ -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;
|
||||
// }
|
||||
// };
|
||||
//});
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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>
|
@ -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):
|
||||
|
@ -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'])
|
||||
|
Loading…
Reference in New Issue
Block a user