Feature: Images of vouchers can be uploaded with vouchers for verification.

Note: Massive breaking changes in how vouchers are uploaded.  Make sure that the client-side code is not cached.
Also, need to implement Database Integrity to check for orphan images.
This commit is contained in:
Amritanshu committed 2014-06-03 17:12:52 +05:30
1 parent 2201a90278
commit 4e85d97e47
28 files changed
+615 -123

No files matched your search

+40 -37
View File
@@ -1,47 +1,50 @@
'use strict';
var AccountListCtrl = ['$scope', '$location', '$routeParams', 'Tokenizer', 'accounts', function ($scope, $location, $routeParams, Tokenizer, accounts) {
$scope.search = $routeParams.q || '';
$scope.info = accounts;
$scope.search = $routeParams.q || '';
$scope.info = accounts;
$scope.$watch('search', function (value) {
$scope.filterAccounts(value);
}, true);
$scope.$watch('search', function (value) {
$scope.filterAccounts(value);
}, true);
$scope.searchInfo = {
comparator: {
'def': 'n',
'n': {'Col': 'Name', 'Comparator': 'text'},
't': {'Col': 'Type', 'Comparator': 'text'},
'a': {'Col': 'IsActive', 'Comparator': 'boolean'},
'r': {'Col': 'IsReconcilable', 'Comparator': 'boolean'},
'c': {'Col': 'CostCenter', 'Comparator': 'text'}
},
sorter: {
'n': 'Name',
't': 'Type',
'a': 'IsActive',
'r': 'IsReconcilable',
'c': 'CostCenter'
$scope.$on('$destroy', function () {
Tokenizer.doFilter.cache = {};
});
$scope.searchInfo = {
comparator: {
'def': 'n',
'n': {'Col': 'Name', 'Comparator': 'text'},
't': {'Col': 'Type', 'Comparator': 'text'},
'a': {'Col': 'IsActive', 'Comparator': 'boolean'},
'r': {'Col': 'IsReconcilable', 'Comparator': 'boolean'},
'c': {'Col': 'CostCenter', 'Comparator': 'text'}
},
sorter: {
'n': 'Name',
't': 'Type',
'a': 'IsActive',
'r': 'IsReconcilable',
'c': 'CostCenter'
}
};
$scope.filterAccounts = _.debounce(function (q) {
if (q !== $scope._search) {
$scope._search = q;
if (angular.isUndefined(q) || q === '') {
$location.path('/Accounts').search('q', null).replace();
} else {
$location.path('/Accounts').search({'q': q}).replace();
}
};
$scope.filterAccounts = _.debounce(function (q) {
if (q !== $scope._search) {
$scope._search = q;
if (angular.isUndefined(q) || q === '') {
$location.path('/Accounts').search('q', null).replace();
} else {
$location.path('/Accounts').search({'q': q}).replace();
}
$scope.$apply(function () {
var matches = Tokenizer.parseFilterString(q, $scope.searchInfo);
$scope.accounts = Tokenizer.doFilter('/Accounts ' + q, $scope.info, matches);
$scope.$apply(function () {
var matches = Tokenizer.parseFilterString(q, $scope.searchInfo);
$scope.accounts = Tokenizer.doFilter(q, $scope.info, matches);
});
}
}, 350);
}]
;
});
}
}, 350);
}];
AccountListCtrl.resolve = {
accounts: ['Account', function (Account) {
return Account.query({}).$promise;
+15
View File
@@ -366,3 +366,18 @@ overlord_directive.directive('chartLinePlusBar', ['$parse', function ($parse) {
};
}]);
overlord_directive.directive('fileUpload', function () {
return {
scope: true, //create a new scope
link: function (scope, el, attrs) {
el.bind('change', function (event) {
var files = event.target.files;
//iterate files since 'multiple' may be specified on the element
for (var i = 0; i < files.length; i++) {
//emit event upward
scope.$emit("fileSelected", { file: files[i] });
}
});
}
};
});
+137 -3
View File
@@ -9,7 +9,44 @@ overlord_service.factory('IssueGrid', ['$resource', function ($resource) {
overlord_service.factory('Voucher', ['$resource', function ($resource) {
return $resource('/api/Voucher/:id',
{id: '@VoucherID'}, {
post: {method: 'POST', params: {post: true}}
post: {method: 'POST', params: {post: true}},
save: {method: 'POST', transformRequest: function (orgData, headersGetter) {
var data = angular.copy(orgData);
function dataURLtoBlob(dataURL) {
var re = /^data:([\w/\-\.]+);\w+,(.*)$/,
m = dataURL.match(re),
mimeString = m[1],
byteString = atob(m[2]),
ab = new ArrayBuffer(byteString.length),
dw = new DataView(ab),
i;
for (i = 0; i < byteString.length; i++) {
dw.setUint8(i, byteString.charCodeAt(i));
}
return new Blob([ab], {type: mimeString});
}
var fd = new FormData(),
files = [],
i;
for (i = data.Files.length - 1; i >= 0; i--) {
var item = data.Files[i];
if (!item.ID) {
fd.append('f' + i, dataURLtoBlob(item.Resized));
fd.append('t' + i, dataURLtoBlob(item.Thumbnail));
data.Files.splice(i, 1);
}
}
fd.append('model', angular.toJson(data));
var headers = headersGetter();
angular.forEach(headers, function (value, key) {
delete headers[key];
});
headers['Content-Type'] = undefined;
return fd;
}}
});
}]);
@@ -304,5 +341,102 @@ overlord_service.factory('Tokenizer', ['$filter', function ($filter) {
return arrayCopy;
});
return {'parseFilterString': parseFilterString, 'doFilter': doFilter };
}]);
return {parseFilterString: parseFilterString, doFilter: doFilter };
}]);
overlord_service.factory("ReaderPromise", ["$q", function ($q) {
var onLoad = function (reader, deferred) {
return function () {
deferred.resolve(reader.result);
};
};
var onError = function (reader, deferred) {
return function () {
deferred.reject(reader.result);
};
};
var onProgress = function (reader, scope) {
return function (event) {
scope.$emit("fileProgress",
{
total: event.total,
loaded: event.loaded
});
};
};
var getReader = function (deferred, scope) {
var reader = new FileReader();
reader.onload = onLoad(reader, deferred);
reader.onerror = onError(reader, deferred, scope);
if (!angular.isUndefined(scope)) {
reader.onprogress = onProgress(reader, scope);
}
return reader;
};
var readAsDataURL = function (file, scope) {
var deferred = $q.defer();
var reader = getReader(deferred, scope);
reader.readAsDataURL(file);
return deferred.promise;
};
return {
readAsDataURL: readAsDataURL
};
}]);
overlord_service.factory("UploadedImageResizer", ["$q", "ReaderPromise", function ($q, ReaderPromise) {
function ResizeImage(file, MAX_WIDTH, MAX_HEIGHT) {
var image = file,
canvas = document.createElement('canvas'),
ctx = canvas.getContext("2d"),
img = document.createElement("img"),
ratio = 1;
img.src = image;
var width = img.width,
height = img.height;
if (width > MAX_WIDTH) {
ratio = MAX_WIDTH / width;
}
if (height > MAX_HEIGHT && MAX_HEIGHT / height < ratio) {
ratio = MAX_HEIGHT / height;
}
if (ratio === 1) {
return file;
}
width *= ratio;
height *= ratio;
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
return canvas.toDataURL("image/jpeg", 0.95);
}
var ProcessUpload = function (uploadedFile, oldFiles) {
var file = {File: uploadedFile.file},
exists = _.any(oldFiles, function (value) {
return angular.equals(value.File, file.File);
});
if (exists) {
return;
}
ReaderPromise.readAsDataURL(uploadedFile.file).then(function (result) {
file.Original = result;
file.Thumbnail = ResizeImage(result, 100, 150);
file.Resized = ResizeImage(result, 825, 1170);
});
oldFiles.push(file);
};
return ProcessUpload;
}]);
+6 -8
View File
@@ -12,16 +12,14 @@ var IssueCtrl = ['$scope', '$routeParams', '$location', 'dateFilter', 'voucher',
$location.path('/Issue/' + date).search({Source: source, Destination: destination});
}
function getOldInventory(productID, inventories) {
for (var i = 0, l = inventories.length; i < l; i++) {
if (inventories[i].Product.ProductID === productID) {
return inventories[i];
}
}
function getOld(productID, inventories) {
return _.find(inventories, function (inventory) {
return inventory.Product.ProductID === productID;
});
}
$scope.add = function () {
var oldInventory = getOldInventory($scope.batch.Product.ProductID, this.voucher.Inventories),
var oldInventory = getOld($scope.batch.Product.ProductID, this.voucher.Inventories),
quantity = Number($scope.quantity),
consumption = (getDebitCredit($scope.voucher.Journals, -1) === '7b845f95-dfef-fa4a-897c-f0baf15284a3');
@@ -189,7 +187,7 @@ var IssueCtrl = ['$scope', '$routeParams', '$location', 'dateFilter', 'voucher',
});
modalInstance.result.then(function (updated) {
if (updated.Product.ProductID !== $scope.selectedInventory.Batch.Product.ProductID) {
var oldInventory = getOldInventory(updated.Batch.Product.ProductID, $scope.voucher.Inventories);
var oldInventory = getOld(updated.Batch.Product.ProductID, $scope.voucher.Inventories);
if (typeof oldInventory !== 'undefined') {
delete $scope.selectedInventory;
return false;
+25 -4
View File
@@ -1,6 +1,6 @@
'use strict';
var JournalCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'voucher', 'Account', function ($scope, $location, dateFilter, $modal, voucher, Account) {
var JournalCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'UploadedImageResizer', 'voucher', 'Account', function ($scope, $location, dateFilter, $modal, UploadedImageResizer, voucher, Account) {
$('#txtAccount').focus();
$scope.voucher = voucher;
@@ -11,6 +11,29 @@ var JournalCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'voucher', 'Ac
});
}
$scope.$on("fileSelected", function (event, args) {
UploadedImageResizer(args, $scope.voucher.Files);
});
$scope.deleteFile = function (item) {
var index = $scope.voucher.Files.indexOf(item);
$scope.voucher.Files.splice(index, 1);
};
$scope.zoomImage = function (item) {
$modal.open({
templateUrl: '/template/modal/image.html',
controller: ['$scope', '$modalInstance', function ($scope, $modalInstance) {
$scope.imageUrl = item.Resized;
$scope.size = null;
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]
});
};
$scope.add = function () {
if (!$scope.account || !$scope.account.LedgerID || !$scope.amount || Number(!$scope.amount)) {
return;
@@ -124,9 +147,7 @@ var JournalCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'voucher', 'Ac
}, function () {
delete $scope.selectedJournal;
}
)
;
);
};
$scope.confirm = function () {
+24 -1
View File
@@ -1,6 +1,6 @@
'use strict';
var PaymentCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'voucher', 'ledgers', 'Account', function ($scope, $location, dateFilter, $modal, voucher, ledgers, Account) {
var PaymentCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'UploadedImageResizer', 'voucher', 'ledgers', 'Account', function ($scope, $location, dateFilter, $modal, UploadedImageResizer, voucher, ledgers, Account) {
$('#txtAccount').focus();
$scope.voucher = voucher;
@@ -11,6 +11,29 @@ var PaymentCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'voucher', 'le
});
}
$scope.$on("fileSelected", function (event, args) {
UploadedImageResizer(args, $scope.voucher.Files);
});
$scope.deleteFile = function (item) {
var index = $scope.voucher.Files.indexOf(item);
$scope.voucher.Files.splice(index, 1);
};
$scope.zoomImage = function (item) {
$modal.open({
templateUrl: '/template/modal/image.html',
controller: ['$scope', '$modalInstance', function ($scope, $modalInstance) {
$scope.imageUrl = item.Resized;
$scope.size = null;
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]
});
};
$scope.add = function () {
if (!$scope.account || !$scope.account.LedgerID || !$scope.amount || Number(!$scope.amount)) {
return;
+6 -2
View File
@@ -9,6 +9,10 @@ var ProductListCtrl = ['$scope', '$location', '$routeParams', 'Tokenizer', 'prod
$scope.filterProducts(newValue);
}, true);
$scope.$on('$destroy', function () {
Tokenizer.doFilter.cache = {};
});
$scope.searchInfo = {
comparator: {
'def': 'n',
@@ -25,7 +29,7 @@ var ProductListCtrl = ['$scope', '$location', '$routeParams', 'Tokenizer', 'prod
't': 'ProductGroup',
'a': 'IsActive',
'y': 'ProductYield',
'f':'Fraction',
'f': 'Fraction',
'fu': 'FractionUnits'
},
flags: {
@@ -44,7 +48,7 @@ var ProductListCtrl = ['$scope', '$location', '$routeParams', 'Tokenizer', 'prod
$scope.$apply(function () {
var matches = Tokenizer.parseFilterString(q, $scope.searchInfo);
$scope.show_extended = 'e' in matches.f;
$scope.products = Tokenizer.doFilter('/Products ' + q, $scope.info, matches);
$scope.products = Tokenizer.doFilter(q, $scope.info, matches);
});
}
+31 -10
View File
@@ -1,18 +1,39 @@
'use strict';
var PurchaseReturnCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'voucher', 'Account', 'Batch', function ($scope, $location, dateFilter, $modal, voucher, Account, Batch) {
var PurchaseReturnCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'UploadedImageResizer', 'voucher', 'Account', 'Batch', function ($scope, $location, dateFilter, $modal, UploadedImageResizer, voucher, Account, Batch) {
$scope.voucher = voucher;
$scope.rate = 0;
function getOldInventory(productID, inventories) {
for (var i = 0, l = inventories.length; i < l; i++) {
if (inventories[i].Product.ProductID === productID) {
return inventories[i];
}
}
function getOld(productID, inventories) {
return _.find(inventories, function (inventory) {
return inventory.Product.ProductID === productID;
});
}
$scope.$on("fileSelected", function (event, args) {
UploadedImageResizer(args, $scope.voucher.Files);
});
$scope.deleteFile = function (item) {
var index = $scope.voucher.Files.indexOf(item);
$scope.voucher.Files.splice(index, 1);
};
$scope.zoomImage = function (item) {
$modal.open({
templateUrl: '/template/modal/image.html',
controller: ['$scope', '$modalInstance', function ($scope, $modalInstance) {
$scope.imageUrl = item.Resized;
$scope.size = null;
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]
});
};
$scope.add = function () {
var oldInventory = getOldInventory($scope.batch.Product.ProductID, this.voucher.Inventories),
var oldInventory = getOld($scope.batch.Product.ProductID, this.voucher.Inventories),
quantity = Number($scope.quantity);
if (!$scope.batch || !$scope.batch.Product.ProductID || !quantity) {
@@ -123,7 +144,7 @@ var PurchaseReturnCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'vouche
});
modalInstance.result.then(function (updated) {
if (updated.Product.ProductID !== $scope.selectedInventory.Batch.Product.ProductID) {
var oldInventory = getOldInventory(updated.Batch.Product.ProductID, $scope.voucher.Inventories);
var oldInventory = getOld(updated.Batch.Product.ProductID, $scope.voucher.Inventories);
if (typeof oldInventory !== 'undefined') {
delete $scope.selectedInventory;
return false;
@@ -158,7 +179,7 @@ var PurchaseReturnCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'vouche
};
$scope.accounts = function ($viewValue) {
return Account.autocomplete({term: $viewValue, count: 20, a:true}).$promise;
return Account.autocomplete({term: $viewValue, count: 20, a: true}).$promise;
};
$scope.batches = function ($viewValue) {
var params = {term: $viewValue, count: 20};
+30 -12
View File
@@ -1,20 +1,38 @@
'use strict';
var PurchaseCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'voucher', 'Account', 'Product', function ($scope, $location, dateFilter, $modal, voucher, Account, Product) {
var PurchaseCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'UploadedImageResizer', 'voucher', 'Account', 'Product', function ($scope, $location, dateFilter, $modal, UploadedImageResizer, voucher, Account, Product) {
$scope.voucher = voucher;
function getOldInventory(productID, inventories) {
if (angular.isUndefined(productID)) {
return;
}
for (var i = 0, l = inventories.length; i < l; i++) {
if (inventories[i].Product.ProductID === productID) {
return inventories[i];
}
}
function getOld(productID, inventories) {
return _.find(inventories, function (inventory) {
return inventory.Product.ProductID === productID;
});
}
$scope.$on("fileSelected", function (event, args) {
UploadedImageResizer(args, $scope.voucher.Files);
});
$scope.deleteFile = function (item) {
var index = $scope.voucher.Files.indexOf(item);
$scope.voucher.Files.splice(index, 1);
};
$scope.zoomImage = function (item) {
$modal.open({
templateUrl: '/template/modal/image.html',
controller: ['$scope', '$modalInstance', function ($scope, $modalInstance) {
$scope.imageUrl = item.Resized;
$scope.size = null;
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]
});
};
$scope.add = function () {
var oldInventory = getOldInventory($scope.product.ProductID, this.voucher.Inventories),
var oldInventory = getOld($scope.product.ProductID, this.voucher.Inventories),
quantity = Number($scope.quantity),
tax = ($scope.tax) ? Number($scope.tax) : 0,
discount = ($scope.discount) ? Number($scope.discount) : 0,
@@ -128,7 +146,7 @@ var PurchaseCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'voucher', 'A
});
modalInstance.result.then(function (updated) {
if (updated.Product.ProductID !== $scope.selectedInventory.Product.ProductID) {
var oldInventory = getOldInventory(updated.Product.ProductID, $scope.voucher.Inventories);
var oldInventory = getOld(updated.Product.ProductID, $scope.voucher.Inventories);
if (typeof oldInventory !== 'undefined') {
delete $scope.selectedInventory;
return false;
+24 -1
View File
@@ -1,6 +1,6 @@
'use strict';
var ReceiptCtrl = ['$scope', '$routeParams', '$location', 'dateFilter', '$modal', 'voucher', 'ledgers', 'Voucher', 'Account', function ($scope, $routeParams, $location, dateFilter, $modal, voucher, ledgers, Voucher, Account) {
var ReceiptCtrl = ['$scope', '$routeParams', '$location', 'dateFilter', '$modal', 'UploadedImageResizer', 'voucher', 'ledgers', 'Voucher', 'Account', function ($scope, $routeParams, $location, dateFilter, $modal, UploadedImageResizer, voucher, ledgers, Voucher, Account) {
$('#txtAccount').focus();
$scope.voucher = voucher;
@@ -11,6 +11,29 @@ var ReceiptCtrl = ['$scope', '$routeParams', '$location', 'dateFilter', '$modal'
});
}
$scope.$on("fileSelected", function (event, args) {
UploadedImageResizer(args, $scope.voucher.Files);
});
$scope.deleteFile = function (item) {
var index = $scope.voucher.Files.indexOf(item);
$scope.voucher.Files.splice(index, 1);
};
$scope.zoomImage = function (item) {
$modal.open({
templateUrl: '/template/modal/image.html',
controller: ['$scope', '$modalInstance', function ($scope, $modalInstance) {
$scope.imageUrl = item.Resized;
$scope.size = null;
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]
});
};
$scope.add = function () {
if (!$scope.account || !$scope.account.LedgerID || !$scope.amount || Number(!$scope.amount)) {
return;