diff --git a/Conversion/SqliteDB.txt b/Conversion/SqliteDB.txt index 77db7186..8bcdc0bf 100644 --- a/Conversion/SqliteDB.txt +++ b/Conversion/SqliteDB.txt @@ -1,5 +1,6 @@ UPDATE entities_vouchers SET date = date || ".000000", lasteditdate = lasteditdate || ".000000", creationdate = creationdate || ".000000"; UPDATE entities_attendances SET date = date || ".000000", creationdate = creationdate || ".000000"; +UPDATE entities_fingerprints SET date = date || ".000000"; ALTER TABLE "Entities_Ledgers" ADD COLUMN "ledger_type" nvarchar(50) NOT NULL DEFAULT ""; UPDATE "Entities_Ledgers" SET ledger_type = 'employees' where type = 10; diff --git a/brewman/brewman/__init__.py b/brewman/brewman/__init__.py index d3451be6..ab0b1468 100644 --- a/brewman/brewman/__init__.py +++ b/brewman/brewman/__init__.py @@ -89,6 +89,7 @@ def main(global_config, **settings): config.add_route('receipt', '/Receipt') config.add_route('issue_id', '/Issue/{id}') config.add_route('issue', '/Issue') + config.add_route('issues_grid', '/Issues/Services/{date}') config.add_route('day_book', '/Reports/DayBook') config.add_route('unposted', '/Reports/Unposted') @@ -119,9 +120,7 @@ def main(global_config, **settings): config.add_route('services_employee_list', '/Services/Employees') - config.add_route('services_ledger_id_list', '/Services/LedgersWithID') config.add_route('services_ledger_list', '/Services/Ledgers') - config.add_route('services_ledger_list_by_type', '/Services/LedgersByType') config.add_route('services_ledger_add_journal', '/Services/AddJournal') config.add_route('services_ledger_cash_in_hand', '/Services/Ledger/CashInHand') @@ -134,12 +133,10 @@ def main(global_config, **settings): config.add_route('services_batch_list', '/Services/Batches') config.add_route('services_batch_add_inventory', '/Services/AddBatch') - config.add_route('services_cost_center_list', '/Services/CostCenters') - config.add_route('services_cost_center_grid', '/Services/CostCenterGrid') - config.add_route('services_user_list', '/Services/Users') - config.add_route('services_voucher_get', '/Services/Voucher') + config.add_route('voucher', '/Voucher/{id}') + config.add_route('voucher_new', '/Voucher') config.add_route('post_voucher', '/Services/Post/{id}') config.add_route('delete_voucher', '/Services/Delete/{id}') diff --git a/brewman/brewman/models/master.py b/brewman/brewman/models/master.py index 565071e5..3a4d7fb9 100644 --- a/brewman/brewman/models/master.py +++ b/brewman/brewman/models/master.py @@ -184,16 +184,15 @@ class LedgerBase(Base): return DBSession.query(cls).all() @classmethod - def list(cls, name): + def list(cls, type, name): query = DBSession.query(cls) - for item in name.split(): - query = query.filter(cls.name.like('%' + item + '%')) + if type != None: + query = query.filter(cls.type == type) + if name != None: + for item in name.split(): + query = query.filter(cls.name.like('%' + item + '%')) return query.order_by(cls.name) - @classmethod - def list_by_type(cls, type): - return DBSession.query(cls).filter(cls.type == type).order_by(cls.name) - def create(self): code = DBSession.query(func.max(LedgerBase.code)).filter(LedgerBase.type == self.type).one()[0] if code is None: diff --git a/brewman/brewman/models/voucher.py b/brewman/brewman/models/voucher.py index 89d53571..e96e54cb 100644 --- a/brewman/brewman/models/voucher.py +++ b/brewman/brewman/models/voucher.py @@ -200,7 +200,6 @@ class Batch(Base): __tablename__ = 'entities_batches' id = Column('BatchID', GUID(), primary_key=True, default=uuid.uuid4) - code = Column('Code', Integer, unique=True) name = Column('Name', Unicode(255)) product_id = Column('ProductID', GUID(), ForeignKey('entities_products.ProductID')) quantity_remaining = Column('QuantityRemaining', Numeric) @@ -210,9 +209,8 @@ class Batch(Base): inventories = relationship('Inventory', backref='batch', cascade=None, cascade_backrefs=False) - def __init__(self, code=None, name=None, product_id=None, quantity_remaining=None, rate=None, tax=None, + def __init__(self, name=None, product_id=None, quantity_remaining=None, rate=None, tax=None, discount=None): - self.code = code self.name = name self.product_id = product_id self.quantity_remaining = quantity_remaining @@ -226,22 +224,12 @@ class Batch(Base): @classmethod def list(cls, name, include_nil): query = DBSession.query(Batch).join(Batch.product) - if include_nil != True: + if not include_nil: query = query.filter(cls.quantity_remaining > 0) for item in name.split(): query = query.filter(Product.name.like('%' + item + '%')) return query.order_by(Product.name).all() - @classmethod - def by_full_name(cls, name): - start = name.find("{") + 1 - finish = name.rfind("}") - if start < 0 or finish < 0 or finish <= start: - return None - print(name) - print(name[start:finish]) - code = int(name[start:finish]) - return DBSession.query(cls).filter(cls.code == code).first() @classmethod def by_id(cls, id): diff --git a/brewman/brewman/static/js/angular-resource-1.0.1.js b/brewman/brewman/static/js/angular-resource-1.0.1.js new file mode 100644 index 00000000..1b8f16e1 --- /dev/null +++ b/brewman/brewman/static/js/angular-resource-1.0.1.js @@ -0,0 +1,428 @@ +/** + * @license AngularJS v1.0.1 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) { +'use strict'; + +/** + * @ngdoc overview + * @name ngResource + * @description + */ + + /** + * @ngdoc object + * @name ngResource.$resource + * @requires $http + * + * @description + * A factory which creates a resource object that lets you interact with + * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. + * + * The returned resource object has action methods which provide high-level behaviors without + * the need to interact with the low level {@link ng.$http $http} service. + * + * @param {string} url A parameterized URL template with parameters prefixed by `:` as in + * `/user/:username`. + * + * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in + * `actions` methods. + * + * Each key value in the parameter object is first bound to url template if present and then any + * excess keys are appended to the url search query after the `?`. + * + * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in + * URL `/path/greet?salutation=Hello`. + * + * If the parameter value is prefixed with `@` then the value of that parameter is extracted from + * the data object (useful for non-GET operations). + * + * @param {Object.=} actions Hash with declaration of custom action that should extend the + * default set of resource actions. The declaration should be created in the following format: + * + * {action1: {method:?, params:?, isArray:?}, + * action2: {method:?, params:?, isArray:?}, + * ...} + * + * Where: + * + * - `action` – {string} – The name of action. This name becomes the name of the method on your + * resource object. + * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, + * and `JSONP` + * - `params` – {object=} – Optional set of pre-bound parameters for this action. + * - isArray – {boolean=} – If true then the returned object for this action is an array, see + * `returns` section. + * + * @returns {Object} A resource "class" object with methods for the default set of resource actions + * optionally extended with custom `actions`. The default set contains these actions: + * + * { 'get': {method:'GET'}, + * 'save': {method:'POST'}, + * 'query': {method:'GET', isArray:true}, + * 'remove': {method:'DELETE'}, + * 'delete': {method:'DELETE'} }; + * + * Calling these methods invoke an {@link ng.$http} with the specified http method, + * destination and parameters. When the data is returned from the server then the object is an + * instance of the resource class `save`, `remove` and `delete` actions are available on it as + * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read, + * update, delete) on server-side data like this: + *
+        var User = $resource('/user/:userId', {userId:'@id'});
+        var user = User.get({userId:123}, function() {
+          user.abc = true;
+          user.$save();
+        });
+     
+ * + * It is important to realize that invoking a $resource object method immediately returns an + * empty reference (object or array depending on `isArray`). Once the data is returned from the + * server the existing reference is populated with the actual data. This is a useful trick since + * usually the resource is assigned to a model which is then rendered by the view. Having an empty + * object results in no rendering, once the data arrives from the server then the object is + * populated with the data and the view automatically re-renders itself showing the new data. This + * means that in most case one never has to write a callback function for the action methods. + * + * The action methods on the class object or instance object can be invoked with the following + * parameters: + * + * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` + * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` + * - non-GET instance actions: `instance.$action([parameters], [success], [error])` + * + * + * @example + * + * # Credit card resource + * + *
+     // Define CreditCard class
+     var CreditCard = $resource('/user/:userId/card/:cardId',
+      {userId:123, cardId:'@id'}, {
+       charge: {method:'POST', params:{charge:true}}
+      });
+
+     // We can retrieve a collection from the server
+     var cards = CreditCard.query(function() {
+       // GET: /user/123/card
+       // server returns: [ {id:456, number:'1234', name:'Smith'} ];
+
+       var card = cards[0];
+       // each item is an instance of CreditCard
+       expect(card instanceof CreditCard).toEqual(true);
+       card.name = "J. Smith";
+       // non GET methods are mapped onto the instances
+       card.$save();
+       // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
+       // server returns: {id:456, number:'1234', name: 'J. Smith'};
+
+       // our custom method is mapped as well.
+       card.$charge({amount:9.99});
+       // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
+     });
+
+     // we can create an instance as well
+     var newCard = new CreditCard({number:'0123'});
+     newCard.name = "Mike Smith";
+     newCard.$save();
+     // POST: /user/123/card {number:'0123', name:'Mike Smith'}
+     // server returns: {id:789, number:'01234', name: 'Mike Smith'};
+     expect(newCard.id).toEqual(789);
+ * 
+ * + * The object returned from this function execution is a resource "class" which has "static" method + * for each action in the definition. + * + * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`. + * When the data is returned from the server then the object is an instance of the resource type and + * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD + * operations (create, read, update, delete) on server-side data. + +
+     var User = $resource('/user/:userId', {userId:'@id'});
+     var user = User.get({userId:123}, function() {
+       user.abc = true;
+       user.$save();
+     });
+   
+ * + * It's worth noting that the success callback for `get`, `query` and other method gets passed + * in the response that came from the server as well as $http header getter function, so one + * could rewrite the above example and get access to http headers as: + * +
+     var User = $resource('/user/:userId', {userId:'@id'});
+     User.get({userId:123}, function(u, getResponseHeaders){
+       u.abc = true;
+       u.$save(function(u, putResponseHeaders) {
+         //u => saved user object
+         //putResponseHeaders => $http header getter
+       });
+     });
+   
+ + * # Buzz client + + Let's look at what a buzz client created with the `$resource` service looks like: + + + + +
+ + +
+
+

+ + {{item.actor.name}} + Expand replies: {{item.links.replies[0].count}} +

+ {{item.object.content | html}} +
+ + {{reply.actor.name}}: {{reply.content | html}} +
+
+
+
+ + +
+ */ +angular.module('ngResource', ['ng']). + factory('$resource', ['$http', '$parse', function($http, $parse) { + var DEFAULT_ACTIONS = { + 'get': {method:'GET'}, + 'save': {method:'POST'}, + 'query': {method:'GET', isArray:true}, + 'remove': {method:'DELETE'}, + 'delete': {method:'DELETE'} + }; + var noop = angular.noop, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + isFunction = angular.isFunction, + getter = function(obj, path) { + return $parse(path)(obj); + }; + + /** + * We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path + * segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); + } + + + /** + * This method is intended for encoding *key* or *value* parts of query component. We need a custom + * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be + * encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace((pctEncodeSpaces ? null : /%20/g), '+'); + } + + function Route(template, defaults) { + this.template = template = template + '#'; + this.defaults = defaults || {}; + var urlParams = this.urlParams = {}; + forEach(template.split(/\W/), function(param){ + if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) { + urlParams[param] = true; + } + }); + this.template = template.replace(/\\:/g, ':'); + } + + Route.prototype = { + url: function(params) { + var self = this, + url = this.template, + encodedVal; + + params = params || {}; + forEach(this.urlParams, function(_, urlParam){ + encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || ""); + url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1"); + }); + url = url.replace(/\/?#$/, ''); + var query = []; + forEach(params, function(value, key){ + if (!self.urlParams[key]) { + query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); + } + }); + query.sort(); + url = url.replace(/\/*$/, ''); + return url + (query.length ? '?' + query.join('&') : ''); + } + }; + + + function ResourceFactory(url, paramDefaults, actions) { + var route = new Route(url); + + actions = extend({}, DEFAULT_ACTIONS, actions); + + function extractParams(data){ + var ids = {}; + forEach(paramDefaults || {}, function(value, key){ + ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; + }); + return ids; + } + + function Resource(value){ + copy(value || {}, this); + } + + forEach(actions, function(action, name) { + var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; + Resource[name] = function(a1, a2, a3, a4) { + var params = {}; + var data; + var success = noop; + var error = null; + switch(arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (hasBody) data = a1; + else params = a1; + break; + case 0: break; + default: + throw "Expected between 0-4 arguments [params, data, success, error], got " + + arguments.length + " arguments."; + } + + var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); + $http({ + method: action.method, + url: route.url(extend({}, extractParams(data), action.params || {}, params)), + data: data + }).then(function(response) { + var data = response.data; + + if (data) { + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + value.push(new Resource(item)); + }); + } else { + copy(data, value); + } + } + (success||noop)(value, response.headers); + }, error); + + return value; + }; + + + Resource.bind = function(additionalParamDefaults){ + return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + + Resource.prototype['$' + name] = function(a1, a2, a3) { + var params = extractParams(this), + success = noop, + error; + + switch(arguments.length) { + case 3: params = a1; success = a2; error = a3; break; + case 2: + case 1: + if (isFunction(a1)) { + success = a1; + error = a2; + } else { + params = a1; + success = a2 || noop; + } + case 0: break; + default: + throw "Expected between 1-3 arguments [params, success, error], got " + + arguments.length + " arguments."; + } + var data = hasBody ? this : undefined; + Resource[name].call(this, params, data, success, error); + }; + }); + return Resource; + } + + return ResourceFactory; + }]); + +})(window, window.angular); diff --git a/brewman/brewman/static/scripts/angular_directive.js b/brewman/brewman/static/scripts/angular_directive.js index c21fa118..7e4a165b 100644 --- a/brewman/brewman/static/scripts/angular_directive.js +++ b/brewman/brewman/static/scripts/angular_directive.js @@ -44,6 +44,46 @@ overlord_directive.directive('autocomplete', function () { return directiveDefinitionObject; }); +overlord_directive.directive('autobatch', function () { + var directiveDefinitionObject = { + restrict:'E', + replace:true, + transclude:true, + template:'', + scope:{ + selname:'=', + batch:'=' + }, + 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.selname = ui.item.label; + scope.batch = ui.item.batch; + scope.$apply(); + } + }); + } + }; + return directiveDefinitionObject; +}); + overlord_directive.directive('datepicker', function () { var directiveDefinitionObject = { restrict:'E', @@ -62,3 +102,25 @@ overlord_directive.directive('datepicker', function () { }; return directiveDefinitionObject; }); + +overlord_directive.directive('fadey', function () { + var directiveDefinitionObject = { + restrict:'A', + link:function (scope, element, attrs) { + var duration = 500; + element = jQuery(element); + element.hide(); + element.fadeIn(duration) + + scope.destroy = function(complete) { + element.fadeOut(duration, function() { + if (complete) { + complete.apply(scope); + } + }); + }; + + } + }; + return directiveDefinitionObject; +}); diff --git a/brewman/brewman/static/scripts/angular_filter.js b/brewman/brewman/static/scripts/angular_filter.js index aba5cfa2..35e96b18 100644 --- a/brewman/brewman/static/scripts/angular_filter.js +++ b/brewman/brewman/static/scripts/angular_filter.js @@ -21,9 +21,11 @@ overlord_filter.filter('posted', function () { overlord_filter.filter('journalDebit', function () { return function (input, debit) { var list = []; - for (var i = 0, l = input.length; i < l; i++) { - if (input[i].Debit === debit) { - list.push(input[i]); + if (typeof input !== 'undefined') { + for (var i = 0, l = input.length; i < l; i++) { + if (input[i].Debit === debit) { + list.push(input[i]); + } } } return list; diff --git a/brewman/brewman/static/scripts/angular_service.js b/brewman/brewman/static/scripts/angular_service.js index f3809012..3d84669c 100644 --- a/brewman/brewman/static/scripts/angular_service.js +++ b/brewman/brewman/static/scripts/angular_service.js @@ -1,4 +1,17 @@ -var overlord_service = angular.module('overlord.service', []); +var overlord_service = angular.module('overlord.service', ['ngResource']); + +overlord_service.factory('get_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('save_voucher', ['$http', function ($http) { return function (scope) { @@ -39,3 +52,31 @@ overlord_service.factory('post_voucher', ['$http', function ($http) { }); }; }]); + +overlord_service.factory('remove_toast', function () { + return function (toast) { + $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}); + }); + }; +}); + +// TODO: Replace hardcoded url with route_url +overlord_service.factory('issue_grid', ['$resource', function ($resource) { + return $resource('/Issues/Services/:date'); +}]); + +// TODO: Replace hardcoded url with route_url +overlord_service.factory('Voucher', ['$resource', function ($resource) { + return $resource('/Voucher/:id', + {id:'@VoucherID'}, { + post: {method:'POST', params:{post:true}} + }); +}]); + diff --git a/brewman/brewman/static/scripts/autocomplete.js b/brewman/brewman/static/scripts/autocomplete.js index f4c0bc61..0a386d50 100644 --- a/brewman/brewman/static/scripts/autocomplete.js +++ b/brewman/brewman/static/scripts/autocomplete.js @@ -1,6 +1,5 @@ var overlord = angular.module('overlord', ['overlord.directive', 'overlord.filter', 'overlord.service']); - function AutoComplete(matchFieldName, lookupURL) { $(matchFieldName).autocomplete({ source:function (request, response) { @@ -25,43 +24,23 @@ function AutoComplete(matchFieldName, lookupURL) { } -//http://jsfiddle.net/tadchristiansen/gt92r/ -// -// Example Confirm -// +function BaseCtrl($scope) { + $scope.auth = { + perms:perms, + isUserInRole:function (role) { + return $scope.auth.perms.indexOf(role) !== -1; + } + }; -//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); -// }); -// } -// }; -// -//}); \ No newline at end of file + $scope.toasts = []; + $scope.clearToast = function(item) { + var idx = $scope.toasts.indexOf(item); + if (idx !== -1) { + //injected into repeater scope by fadey directive + this.destroy(function() { + $scope.toasts.splice(idx, 1); + }); + } + }; +} diff --git a/brewman/brewman/static/scripts/issue.js b/brewman/brewman/static/scripts/issue.js index 4bbb9cfc..ac062ad3 100644 --- a/brewman/brewman/static/scripts/issue.js +++ b/brewman/brewman/static/scripts/issue.js @@ -1,219 +1,130 @@ -function SmallGridData() { - var myDate = $('#txtDate').val(); - var mySource = $('#ddlSourceCostCenter').val(); - var myDestination = $('#ddlDestinationCostCenter').val(); - var $table = $('#smallGridContent'); - $.ajax({ - type: "POST", - contentType: "application/json; charset=utf-8", - url: small_grid_url, - data: JSON.stringify({ date: myDate, source: mySource, destination: myDestination}), - dataType: "json", - success: function(response) { - var rowString = ''; - var rows = response; - if (null !== rows) { - $.each(rows, function(index, row) { - rowString += '' + row.Code + '' + row.Amount + ''; - }) +function IssueCtrl($scope, Voucher, issue_grid) { + $scope.voucher = new Voucher(voucher); + $scope.cost_centers = cost_centers; + + $scope.addInventory = function () { + for (var i = 0, l = this.voucher.Inventories.length; i < l; i++) { + if (this.voucher.Inventories[i].Product.ProductID === $scope.batch.Product.ProductID) { + break; } - $table.html(rowString); } + if (i >= l && Number($scope.quantity) <= $scope.batch.QuantityRemaining) { + var amount = Number($scope.quantity) * $scope.batch.Rate * (1 + $scope.batch.Tax) * (1 - $scope.batch.Discount); + this.voucher.Inventories.push( + { + Product:$scope.batch.Product, + Quantity:Number($scope.quantity), + Rate:Number($scope.batch.Rate), + Tax:Number($scope.batch.Tax), + Discount:Number($scope.batch.Discount), + Amount:amount, + Batch:$scope.batch + }); + delete $scope.batch; + delete $scope.name; + delete $scope.quantity; + $('#txtBatch').val(''); + } + $('#txtBatch').focus(); + }; + $scope.removeInventory = function (inventory) { + var index = this.voucher.Inventories.indexOf(inventory); + this.voucher.Inventories.splice(index, 1); + }; - }); -} - -function BuildRow(row) { - return '' + row.InventoryID + - '' + row.Product.ProductID + - '' + row.Product.Name + - '' + row.Quantity + - '' + row.Rate + - '' + row.Amount + - '' + ((row.Batch != null) ? row.Batch.BatchID : '') + - '' + ((row.Product.ProductID != '') ? '' : '') + ''; -} - -function GetFormData() { - var inventories = new Array(); - var journals = new Array(); - $("tr.Inventory").each(function() { - $this = $(this); - var inventoryID = $this.find("td.InventoryID").html(); - var productID = $this.find("td.ProductID").html(); - var quantity = $this.find("td.Quantity").html(); - var batchID = $this.find("td.BatchID").html(); - var inv = { 'InventoryID': inventoryID, 'Product': { 'ProductID': productID }, 'Quantity': quantity, 'Batch': { 'BatchID': batchID} }; - inventories.push(inv); - }); - var mySource = $('#ddlSourceCostCenter').val(); - var myDestination = $('#ddlDestinationCostCenter').val(); - journals.push({ 'CostCenter': { 'CostCenterID': mySource }, 'Debit': -1 }); - journals.push({ 'CostCenter': { 'CostCenterID': myDestination }, 'Debit': 1 }); - - var voucherID = $("#VoucherID").val(); - if (voucherID == "") { - voucherID = "00000000-0000-0000-0000-000000000000"; - } - var code = $('#txtCode').val(); - if (code == "") { - code = 0; - } - var date = $('#txtDate').val(); - var narration = $('#txtNarration').val(); - - return JSON.stringify({ voucher: { VoucherID: voucherID, Code: code, Date: date, Narration: narration, Inventories: inventories, Journals: journals} }); -} - -function AddRow(url, batchString, quantity) { - var $table = $('#tbodyMain') - $.ajax({ - type: "POST", - contentType: "application/json; charset=utf-8", - url: url, - data: JSON.stringify({ batchString: batchString, quantity: quantity }), - dataType: "json", - success: function(response) { - var row = response; - var exists = false; - $("tr.Inventory").each(function() { - $this = $(this) - var productID = $this.find("td.ProductID").html(); - if (productID == row.Product.ProductID) { - exists = true; - } - }); - if (exists == false) { - $table.append(BuildRow(row)); - Recalculate(); - $('#txtBatch').val(""); - $('#txtQuantity').val(""); - $("#ctl00_statusDiv").removeClass().addClass("hidden"); - $("#ctl00_statusDiv").html(""); - $('#txtBatch').focus(); + $scope.updateGrid = function () { + var voucher = $scope.voucher; + for (var i = 0, l = voucher.Journals.length; i < l; i++) { + if (voucher.Journals[i].Debit === -1) { + var creditJournal = voucher.Journals[i].CostCenter.CostCenterID; } else { - $("#ctl00_statusDiv").removeClass().addClass("error"); - $("#ctl00_statusDiv").html("Duplicate Product"); - } - }, - error: function(jqXHR, textStatus) { - $("#ctl00_statusDiv").removeClass().addClass("error"); - var msg = $.parseJSON(jqXHR.responseText).Message; - $("#ctl00_statusDiv").html(msg); - } - }); - return false; -} -function PopulateVoucher(voucher) { - $("#VoucherID").val(voucher.VoucherID); - $('#txtCode').val(voucher.Code); - $('#txtDate').val(voucher.Date); - $('#txtNarration').val(voucher.Narration); - - $.each(voucher.Journals, function(index, row) { - if (row.Debit = 1) { - $('#ddlDestinationCostCenter').val(row.CostCenter.CostCenterID); - } - else { - $('#ddlSourceCostCenter').val(row.CostCenter.CostCenterID); - } - }) - - var rowString = ''; - $.each(voucher.Inventories, function(index, row) { - rowString += BuildRow(row); - }) - var $table = $('#tbodyMain') - $table.html(rowString); - Recalculate(); - $("#btnUpdate").show(); - $("#btnSave").hide(); - -} -function ResetForm(voucher) { - $('#VoucherID').val(""); - $('#txtCode').val(""); - $('#txtNarration').val(""); - - $('#txtBatch').val(""); - $('#txtQuantity').val(""); - - SmallGridData(); - - var $table = $('#tbodyMain'); - $table.html(""); - Recalculate(); - $("#btnSave").show(); - $("#btnUpdate").hide(); - - - $('#txtBatch').focus(); - return false; -} - -function CheckForVoucher() { - var voucherID = getParameterByName('VoucherID'); - if (voucherID == "") { - return; - } - LoadVoucher(voucherID, 'VoucherIssue'); -} - -function DeleteRow(productID) { - $('#' + productID).remove(); - Recalculate(); - return false; -} - -function ShowQuantityBox(place) { - if (window.getSelection) - window.getSelection().removeAllRanges(); - else if (document.selection) - document.selection.empty(); - - var inputString = ''; - if (place.html().indexOf(' roles_url = '${request.route_url('is_user_in_roles')}'; @@ -75,11 +73,11 @@ -
+
-
- {{toast.Type}}! {{toast.Message}} +
+ {{toast.Type}}! {{toast.Message}}
diff --git a/brewman/brewman/templates/transaction/issue.mako b/brewman/brewman/templates/transaction/issue.mako index 6df3347b..6c1875cb 100644 --- a/brewman/brewman/templates/transaction/issue.mako +++ b/brewman/brewman/templates/transaction/issue.mako @@ -2,139 +2,152 @@ <%inherit file="../base.mako"/> <%block name="header"> ${h.ScriptLink(request, 'issue.js')} - ${h.ScriptLink(request, 'voucher.js')} + <%block name="content"> -

Ledger Edit / New

- - \ No newline at end of file diff --git a/brewman/brewman/templates/transaction/journal.mako b/brewman/brewman/templates/transaction/journal.mako index 3adc6cda..417fd01d 100644 --- a/brewman/brewman/templates/transaction/journal.mako +++ b/brewman/brewman/templates/transaction/journal.mako @@ -2,13 +2,9 @@ <%inherit file="../base.mako"/> <%block name="header"> - ${h.ScriptLink(request, 'journal.js')} ${h.ScriptLink(request, 'payment.js')} ${h.ScriptLink(request, 'purchase.js')} ${h.ScriptLink(request, 'receipt.js')}