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

+1
View File
@@ -85,6 +85,7 @@ def main(global_config, **settings):
config.add_route('api_issues_grid', '/api/Issues/Services/{date}')
config.add_route('salary_deduction_id', '/SalaryDeduction/{id}')
config.add_route('salary_deduction', '/SalaryDeduction')
config.add_route('db_image', '/api/DbImage/{id}/{type}')
config.add_route('api_voucher_id', '/api/Voucher/{id}')
config.add_route('api_voucher', '/api/Voucher')
+2 -2
View File
@@ -482,12 +482,12 @@ class LedgerType:
list.append(LedgerType(5, 'Capital', True, False, False, 70, True))
list.append(LedgerType(6, 'Debtors', True, True, True, 30, True))
list.append(LedgerType(7, 'Expenses', False, True, True, 40, True))
# list.append(LedgerType(8, 'Discount', False, False, True, 30, True))
# list.append(LedgerType(8, 'Discount', False, False, True, 30, True))
list.append(LedgerType(9, 'Creditors', True, False, True, 60, True))
list.append(LedgerType(10, 'Salary', True, True, True, 40, False))
list.append(LedgerType(11, 'Liabilities', True, False, True, 50, True))
list.append(LedgerType(12, 'Revenue', False, False, True, 30, True))
# list.append(LedgerType(13, 'Tax', True, False, True, 80, True))
# list.append(LedgerType(13, 'Tax', True, False, True, 80, True))
# list.append(LedgerType(14, 'Total', False, False, False, 900, False))
# list.append(LedgerType(15, 'Net', False, False, False, 1000, False))
return list
+28
View File
@@ -1,6 +1,7 @@
from datetime import datetime
import uuid
from sqlalchemy.dialects.postgresql import BYTEA
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import Column, Integer, Boolean, Unicode, DateTime, Numeric, ForeignKey, UniqueConstraint
from sqlalchemy.orm import relationship, synonym, backref
@@ -334,3 +335,30 @@ class Fingerprint(Base):
else:
return old
class DbImage(Base):
__tablename__ = 'images'
id = Column('id', GUID(), primary_key=True, default=uuid.uuid4)
resource_id = Column('resource_id', GUID(), nullable=False)
resource_type = Column('resource_type', Unicode(255), nullable=False)
image = Column('image', BYTEA, nullable=False)
thumbnail = Column('thumbnail', BYTEA, nullable=False)
creation_date = Column('creation_date', DateTime(timezone=True), nullable=False)
def __init__(self, resource_id=None, resource_type=None, image=None, thumbnail=None, creation_date=None, id=None):
self.resource_id = resource_id
self.resource_type = resource_type
self.image = image
self.thumbnail = thumbnail
self.creation_date = datetime.utcnow() if creation_date is None else creation_date
@classmethod
def by_id(cls, id):
if not isinstance(id, uuid.UUID):
id = uuid.UUID(id)
return DBSession.query(cls).filter(cls.id == id).first()
@classmethod
def by_resource_id(cls, id, type):
return DBSession.query(cls).filter(cls.resource_id == id, cls.resource_type == type).all()
+17 -5
View File
@@ -4,7 +4,6 @@
*
*/
.thumbnails.small {
margin-left: -50px;
}
@@ -60,7 +59,6 @@
color: #333333;
}
.widget-box {
background: none repeat scroll 0 0 #F9F9F9;
border-top: 1px solid #CDCDCD;
@@ -71,6 +69,7 @@
margin-bottom: 16px;
position: relative;
}
/* widgets from unicorn theme from http://wbpreview.com/previews/WB0F35928/widgets.html */
.widget-title, .modal-header, .table th, div.dataTables_wrapper .ui-widget-header, .ui-dialog .ui-dialog-titlebar {
background-color: #efefef;
@@ -79,9 +78,11 @@
border-bottom: 1px solid #CDCDCD;
height: 36px;
}
.widget-title .nav-tabs {
border-bottom: 0 none;
}
.widget-title .nav-tabs li a {
border-bottom: medium none !important;
border-left: 1px solid #DDDDDD;
@@ -95,25 +96,30 @@
font-weight: bold;
text-shadow: 0 1px 0 #FFFFFF;
}
.widget-title .nav-tabs li:first-child a{
.widget-title .nav-tabs li:first-child a {
border-left: medium none !important;
}
.widget-title .nav-tabs li a:hover {
background-color: transparent !important;
border-color: #D6D6D6;
border-width: 0 1px;
color: #666666;
}
.widget-title .nav-tabs li.active a {
background-color: #F9F9F9 !important;
color: #444444;
}
.widget-title span.icon {
border-right: 1px solid #cdcdcd;
padding: 9px 10px 7px 11px;
float: left;
opacity: .7;
}
.widget-title h5 {
color: #666666;
text-shadow: 0 1px 0 #ffffff;
@@ -124,23 +130,28 @@
line-height: 12px;
margin: 0;
}
.widget-title .buttons {
float: right;
margin: 8px 10px 0 0;
}
.widget-title .label {
padding: 3px 5px 2px;
float: right;
margin: 9px 15px 0 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.3) inset, 0 1px 0 #ffffff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) inset, 0 1px 0 #ffffff;
}
.widget-content {
padding: 12px 15px;
border-bottom: 1px solid #cdcdcd;
}
.widget-box.widget-plain .widget-content {
padding: 12px 0 0;
}
.widget-box.collapsible .collapse.in .widget-content {
border-bottom: 1px solid #CDCDCD;
}
@@ -148,9 +159,11 @@
.nopadding {
padding: 0 !important;
}
.nopadding .table {
margin-bottom: 0;
}
.nopadding .table-bordered {
border: 0;
}
@@ -159,4 +172,3 @@
color: #666666;
font-size: 11px;
}
+37 -3
View File
@@ -2,46 +2,80 @@
.no-wrap {
white-space: nowrap
}
.hide
{
.hide {
display: none;
}
.table tbody tr.Present td:not(.no-bg) {
background-color: #228B22;
}
.table tbody tr.Off td:not(.no-bg) {
background-color: #87CEFA;
}
.table tbody tr.Leave td:not(.no-bg) {
background-color: #CD5C5C;
}
.table tbody tr.Absent td:not(.no-bg) {
background-color: #CD0000;
}
.table tbody tr.HalfDay td:not(.no-bg) {
background-color: #98FB98;
}
.table tbody tr.Double td:not(.no-bg) {
background-color: #006400;
}
.table tbody tr.PaidLeaveAvailed td:not(.no-bg) {
background-color: #EEEE00;
}
.table tbody tr.CasualLeaveAvailed td:not(.no-bg) {
background-color: #800080;
}
.table tbody tr.Overtime td:not(.no-bg) {
background-color: #006400;
}
.table tbody tr.OffWorked td:not(.no-bg) {
background-color: #F5DEB3;
}
.table tbody tr.COff td:not(.no-bg) {
background-color: #F5DEB3;
}
.table tbody tr.HalfPL td:not(.no-bg) {
background-color: #FFF68F;
}
.table tbody tr.HalfCL td:not(.no-bg) {
background-color: #FF00FF;
}
}
.img-container {
position: relative;
display: inline-block;
cursor: crosshair;
}
.img-container > .overlay {
position: absolute;
top: 14px;
right: 14px;
display: none;
}
.img-container:hover .overlay {
display: block;
}
/*.img-container:hover ~ .img-container > .overlay:hover {*/
/*display: block;*/
/*}*/
+12
View File
@@ -79,6 +79,18 @@
<textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-md-10 col-md-offset-2">
<div class="img-container" ng-repeat="item in voucher.Files">
<img ng-click="zoomImage(item)" ng-src="{{item.Thumbnail}}" class="img-thumbnail">
<a href ng-click="deleteFile(item)" class="overlay"><i class="glyphicon glyphicon-trash"></i></a>
</div>
<label ng-href for="fileUp">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg==" class="img-thumbnail">
</label>
<input type="file" id="fileUp" file-upload multiple style="display: none;" accept="image/*">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button class="btn btn-primary" tan-click="save()" ng-disabled="preventAlteration(voucher)">
+12
View File
@@ -79,6 +79,18 @@
<textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-md-10 col-md-offset-2">
<div class="img-container" ng-repeat="item in voucher.Files">
<img ng-click="zoomImage(item)" ng-src="{{item.Thumbnail}}" class="img-thumbnail">
<a href ng-click="deleteFile(item)" class="overlay"><i class="glyphicon glyphicon-trash"></i></a>
</div>
<label ng-href for="fileUp">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg==" class="img-thumbnail">
</label>
<input type="file" id="fileUp" file-upload multiple style="display: none;" accept="image/*">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button class="btn btn-primary" tan-click="save()" ng-disabled="preventAlteration(voucher)">
@@ -85,6 +85,18 @@
<textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-md-10 col-md-offset-2">
<div class="img-container" ng-repeat="item in voucher.Files">
<img ng-click="zoomImage(item)" ng-src="{{item.Thumbnail}}" class="img-thumbnail">
<a href ng-click="deleteFile(item)" class="overlay"><i class="glyphicon glyphicon-trash"></i></a>
</div>
<label ng-href for="fileUp">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg==" class="img-thumbnail">
</label>
<input type="file" id="fileUp" file-upload multiple style="display: none;" accept="image/*">
</div>
</div>
<div class="form-actions">
<button class="btn btn-primary" tan-click="save()"
ng-disabled="preventAlteration(voucher)">{{voucher.VoucherID | save_button}}
+12
View File
@@ -91,6 +91,18 @@
<textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-md-10 col-md-offset-2">
<div class="img-container" ng-repeat="item in voucher.Files">
<img ng-click="zoomImage(item)" ng-src="{{item.Thumbnail}}" class="img-thumbnail">
<a href ng-click="deleteFile(item)" class="overlay"><i class="glyphicon glyphicon-trash"></i></a>
</div>
<label ng-href for="fileUp">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg==" class="img-thumbnail">
</label>
<input type="file" id="fileUp" file-upload multiple style="display: none;" accept="image/*">
</div>
</div>
<div class="form-actions">
<button class="btn btn-primary" tan-click="save()"
ng-disabled="preventAlteration(voucher)">{{voucher.VoucherID | save_button}}
+12
View File
@@ -79,6 +79,18 @@
<textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-md-10 col-md-offset-2">
<div class="img-container" ng-repeat="item in voucher.Files">
<img ng-click="zoomImage(item)" ng-src="{{item.Thumbnail}}" class="img-thumbnail">
<a href ng-click="deleteFile(item)" class="overlay"><i class="glyphicon glyphicon-trash"></i></a>
</div>
<label ng-href for="fileUp">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg==" class="img-thumbnail">
</label>
<input type="file" id="fileUp" file-upload multiple style="display: none;" accept="image/*">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button class="btn btn-primary" tan-click="save()" ng-disabled="preventAlteration(voucher)">
+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;
+3
View File
@@ -0,0 +1,3 @@
<div class="modal-body">
<img ng-click="cancel()" ng-src="{{imageUrl}}">
</div>
+14
View File
@@ -1,5 +1,6 @@
from decimal import Decimal
from datetime import date, datetime, timedelta, time
from io import BytesIO
import uuid
import re
@@ -9,6 +10,7 @@ from pyramid.response import FileResponse, Response
from pyramid.view import view_config
from brewman.models.master import DbSetting
from brewman.models.voucher import DbImage
@view_config(route_name='home')
@@ -36,6 +38,18 @@ def favicon(request):
return FileResponse(icon, request=request)
@view_config(route_name='db_image')
def db_image(request):
item = DbImage.by_id(uuid.UUID(request.matchdict['id']))
if request.matchdict['type'] == 'thumbnail':
item = BytesIO(item.thumbnail)
else:
item = BytesIO(item.image)
response = Response(content_type='image/jpeg')
response.app_iter = item
return response
def get_lock_info():
data = DbSetting.by_name('Lock Info')
if data is None:
+25 -10
View File
@@ -1,21 +1,24 @@
import uuid
import pkg_resources
from pyramid.response import Response, FileResponse
from pyramid.security import authenticated_userid
from pyramid.view import view_config
from sqlalchemy import func
import transaction
from brewman import groupfinder
from brewman.models import DBSession
from brewman.models.auth import User
from brewman.models.master import LedgerBase, CostCenter, DbSetting
from brewman.models.master import LedgerBase, CostCenter
from brewman.models.validation_exception import ValidationError, TryCatchFunction
from brewman.models.voucher import Voucher, VoucherType, Batch, Inventory
from brewman.models.voucher import Voucher, VoucherType, Inventory, DbImage
from brewman.views import get_lock_info
from .issue import issue_create_voucher, issue_update_voucher
from .journal import journal_update_voucher, journal_create_voucher
from .purchase import purchase_create_voucher, purchase_update_voucher
__author__ = 'tanshu'
@@ -53,7 +56,7 @@ def voucher_post(request):
voucher.posted = True
voucher.poster_id = user.id
transaction.commit()
return voucher_info(Voucher.by_id(voucher.id))
return voucher_info(Voucher.by_id(voucher.id), request)
def check_delete_permissions(request, voucher):
@@ -87,10 +90,11 @@ def check_delete_permissions(request, voucher):
@TryCatchFunction
def delete(request):
voucher = Voucher.by_id(uuid.UUID(request.matchdict['id']))
images = DbImage.by_resource_id(voucher.id, 'voucher')
permission = check_delete_permissions(request, voucher)
if permission is not None:
return permission
json_voucher = voucher_info(voucher)
json_voucher = voucher_info(voucher, request)
batches_to_delete = []
if voucher.type == VoucherType.by_name('Issue').id:
for item in voucher.journals:
@@ -125,6 +129,8 @@ def delete(request):
for b in batches_to_delete:
DBSession.delete(b)
DBSession.delete(voucher)
for image in images:
DBSession.delete(image)
transaction.commit()
return blank_voucher(info=json_voucher)
@@ -133,10 +139,10 @@ def delete(request):
@TryCatchFunction
def get_old(request):
id = request.matchdict.get('id', None)
return voucher_info(Voucher.by_id(uuid.UUID(id)))
return voucher_info(Voucher.by_id(uuid.UUID(id)), request)
def voucher_info(voucher):
def voucher_info(voucher, request):
json_voucher = {'VoucherID': voucher.id,
'Date': voucher.date.strftime('%d-%b-%Y'),
'Type': VoucherType.by_id(voucher.type).name,
@@ -145,6 +151,7 @@ def voucher_info(voucher):
'Journals': [],
'Inventories': [],
'SalaryDeductions': [],
'Files': [],
'CreationDate': voucher.creation_date.strftime('%d-%b-%Y %H:%M'),
'LastEditDate': voucher.last_edit_date.strftime('%d-%b-%Y %H:%M'),
'User': {'UserID': voucher.user.id, 'Name': voucher.user.name},
@@ -170,7 +177,8 @@ def voucher_info(voucher):
'Name': item.journal.ledger.costcenter.name}}}})
for item in voucher.inventories:
text = "{0} ({1}) {2:.2f}@{3:.2f} from {4}".format(item.product.name, item.product.units,
item.batch.quantity_remaining, item.batch.rate, item.batch.name.strftime('%d-%b-%Y'))
item.batch.quantity_remaining, item.batch.rate,
item.batch.name.strftime('%d-%b-%Y'))
json_voucher['Inventories'].append(
{'InventoryID': item.id, 'Quantity': item.quantity, 'Rate': item.rate,
'Tax': item.tax, 'Discount': item.discount, 'Amount': item.amount,
@@ -180,6 +188,11 @@ def voucher_info(voucher):
'QuantityRemaining': item.batch.quantity_remaining, 'Tax': item.batch.tax,
'Discount': item.batch.discount, 'Rate': item.batch.rate,
'Product': {'ProductID': item.batch.product.id, 'Name': item.batch.product.full_name}}})
images = DbImage.by_resource_id(voucher.id, 'voucher')
for image in images:
resized = request.route_url('db_image', id=image.id, type='resized')
thumbnail = request.route_url('db_image', id=image.id, type='thumbnail')
json_voucher['Files'].append({'ID': image.id, 'Resized': resized, 'Thumbnail': thumbnail})
return json_voucher
@@ -189,7 +202,8 @@ def blank_voucher(info):
type = info['Type']
if 'Date' not in info:
raise ValidationError('Date cannot be null')
json_voucher = {'Type': type, 'Date': info['Date'], 'Posted': False, 'Narration': "", 'Journals': [], 'Inventories': []}
json_voucher = {'Type': type, 'Date': info['Date'], 'Posted': False, 'Narration': "", 'Journals': [],
'Inventories': []}
if type == 'Journal':
pass
elif type == 'Payment':
@@ -197,7 +211,7 @@ def blank_voucher(info):
if info is not None and 'Account' in info and info['Account'] is not None:
ledger = LedgerBase.by_id(info['Account'])
if ledger is not None:
ledger = {'LedgerID': ledger.id, 'Name':ledger.name}
ledger = {'LedgerID': ledger.id, 'Name': ledger.name}
else:
ledger = LedgerBase.cash_in_hand()
json_voucher['Journals'].append({'Ledger': ledger, 'Amount': 0, 'Debit': -1})
@@ -206,7 +220,7 @@ def blank_voucher(info):
if info is not None and 'Account' in info and info['Account'] is not None:
ledger = LedgerBase.by_id(info['Account'])
if ledger is not None:
ledger = {'LedgerID': ledger.id, 'Name':ledger.name}
ledger = {'LedgerID': ledger.id, 'Name': ledger.name}
else:
ledger = LedgerBase.cash_in_hand()
json_voucher['Journals'].append({'Ledger': ledger, 'Amount': 0, 'Debit': 1})
@@ -245,6 +259,7 @@ def blank_voucher(info):
json_voucher['SalaryDeductions'] = []
else:
raise ValidationError("Voucher of type \"{0}\" does not exist.".format(type))
json_voucher['Files'] = []
return json_voucher
+16 -5
View File
@@ -1,14 +1,17 @@
import datetime
from decimal import Decimal
import uuid
from brewman.models import DBSession
from brewman.models.master import LedgerBase
from brewman.models.operations import journals_valid
from brewman.models.voucher import Journal, Voucher, VoucherType
from brewman.models.voucher import Journal, Voucher, VoucherType, DbImage
__author__ = 'tanshu'
def journal_create_voucher(json, user):
def journal_create_voucher(json, files, user):
dt = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
voucher = Voucher(date=dt, narration=json['Narration'], user_id=user.id, type=VoucherType.by_name(json['Type']))
DBSession.add(voucher)
@@ -17,14 +20,16 @@ def journal_create_voucher(json, user):
journal_id = uuid.UUID(item['JournalID']) if 'JournalID' in item else None
amount = round(Decimal(item['Amount']), 2)
journal = Journal(id=journal_id, amount=amount, debit=int(item['Debit']), ledger_id=ledger.id,
cost_center_id=ledger.costcenter_id)
cost_center_id=ledger.costcenter_id)
voucher.journals.append(journal)
DBSession.add(journal)
journals_valid(voucher)
for key, value in files.items():
DBSession.add(DbImage(voucher.id, 'voucher', value['f'], value['t']))
return voucher
def journal_update_voucher(voucher, json, user):
def journal_update_voucher(voucher, json, files, user):
voucher.date = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
voucher.narration = json['Narration']
voucher.user_id = user.id
@@ -51,10 +56,16 @@ def journal_update_voucher(voucher, json, user):
for j in newJournals:
ledger = LedgerBase.by_id(uuid.UUID(j['Ledger']['LedgerID']))
journal = Journal(id=None, amount=round(Decimal(j['Amount']), 2), debit=int(j['Debit']), ledger_id=ledger.id,
cost_center_id=ledger.costcenter_id)
cost_center_id=ledger.costcenter_id)
DBSession.add(journal)
voucher.journals.append(journal)
journals_valid(voucher)
old_files = [uuid.UUID(f['ID']) for f in json['Files'] if 'ID' in f]
for image in [i for i in DbImage.by_resource_id(voucher.id, 'voucher') if i.id not in old_files]:
DBSession.delete(image)
for key, value in files.items():
DBSession.add(DbImage(voucher.id, 'voucher', value['f'], value['t']))
return voucher
+17 -6
View File
@@ -1,17 +1,20 @@
import datetime
from decimal import Decimal
import uuid
from sqlalchemy import func
from brewman.models import DBSession
from brewman.models.master import Product, LedgerBase
from brewman.models.operations import journals_valid, inventory_valid
from brewman.models.validation_exception import ValidationError
from brewman.models.voucher import Voucher, VoucherType, Batch, Inventory, Journal
from brewman.models.voucher import Voucher, VoucherType, Batch, Inventory, Journal, DbImage
__author__ = 'tanshu'
def purchase_create_voucher(json, user):
def purchase_create_voucher(json, files, user):
dt = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
voucher = Voucher(date=dt, narration=json['Narration'], user_id=user.id, type=VoucherType.by_name(json['Type']))
DBSession.add(voucher)
@@ -23,6 +26,8 @@ def purchase_create_voucher(json, user):
DBSession.add(item)
journals_valid(voucher)
inventory_valid(voucher)
for key, value in files.items():
DBSession.add(DbImage(voucher.id, 'voucher', value['f'], value['t']))
return voucher
@@ -65,7 +70,7 @@ def purchase_create_journals(inventories, ledgerID):
return list(journals.values())
def purchase_update_voucher(voucher, json, user):
def purchase_update_voucher(voucher, json, files, user):
voucher.date = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
voucher.narration = json['Narration']
voucher.user_id = user.id
@@ -76,6 +81,12 @@ def purchase_update_voucher(voucher, json, user):
purchase_update_journals(voucher, json['Journals'])
journals_valid(voucher)
inventory_valid(voucher)
old_files = [uuid.UUID(f['ID']) for f in json['Files'] if 'ID' in f]
for image in [i for i in DbImage.by_resource_id(voucher.id, 'voucher') if i.id not in old_files]:
DBSession.delete(image)
for key, value in files.items():
DBSession.add(DbImage(voucher.id, 'voucher', value['f'], value['t']))
return voucher
@@ -106,7 +117,7 @@ def purchase_update_inventory(voucher, newInventories):
if voucher.date != item.batch.name:
item.batch.name = voucher.date
if voucher.date < item.batch.name:
# TODO: check for issued products which might have been in a back date
# TODO: check for issued products which might have been in a back date
pass
item.rate = rate
item.batch.rate = rate
@@ -116,7 +127,7 @@ def purchase_update_inventory(voucher, newInventories):
item.batch.tax = tax
product.price = rate
newInventories.remove(i)
#TODO: Update all references of the batch with the new rates
# TODO: Update all references of the batch with the new rates
break
if not found:
uses = DBSession.query(func.count(Inventory.id)) \
@@ -160,7 +171,7 @@ def purchase_update_journals(voucher, journals):
journals[ledger.id] = Journal(debit=1, cost_center_id=ledger.costcenter_id, ledger_id=ledger.id,
amount=item.amount)
journals[other_ledger.id] = Journal(debit=-1, cost_center_id=other_ledger.costcenter_id, ledger_id=other_ledger.id,
amount=amount)
amount=amount)
for i in range(len(voucher.journals), 0, -1):
item = voucher.journals[i - 1]
if item.ledger_id in journals:
@@ -5,12 +5,12 @@ from brewman.models import DBSession
from brewman.models.master import Product, LedgerBase
from brewman.models.operations import inventory_valid, journals_valid
from brewman.models.validation_exception import ValidationError
from brewman.models.voucher import Voucher, VoucherType, Batch, Inventory, Journal
from brewman.models.voucher import Voucher, VoucherType, Batch, Inventory, Journal, DbImage
__author__ = 'tanshu'
def purchase_return_create_voucher(json, user):
def purchase_return_create_voucher(json, files, user):
dt = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
voucher = Voucher(date=dt, narration=json['Narration'], user_id=user.id, type=VoucherType.by_name(json['Type']))
DBSession.add(voucher)
@@ -22,6 +22,8 @@ def purchase_return_create_voucher(json, user):
DBSession.add(item)
journals_valid(voucher)
inventory_valid(voucher)
for key, value in files.items():
DBSession.add(DbImage(voucher.id, 'voucher', value['f'], value['t']))
return voucher
@@ -62,7 +64,7 @@ def purchase_return_create_journals(inventories, ledgerID):
return list(journals.values())
def purchase_return_update_voucher(voucher, json, user):
def purchase_return_update_voucher(voucher, json, files, user):
voucher.date = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
voucher.narration = json['Narration']
voucher.user_id = user.id
@@ -73,6 +75,12 @@ def purchase_return_update_voucher(voucher, json, user):
purchase_return_update_journals(voucher, json['Journals'])
journals_valid(voucher)
inventory_valid(voucher)
old_files = [uuid.UUID(f['ID']) for f in json['Files'] if 'ID' in f]
for image in [i for i in DbImage.by_resource_id(voucher.id, 'voucher') if i.id not in old_files]:
DBSession.delete(image)
for key, value in files.items():
DBSession.add(DbImage(voucher.id, 'voucher', value['f'], value['t']))
return voucher
+25 -6
View File
@@ -1,8 +1,11 @@
import datetime
import uuid
from json import loads
import datetime
from pyramid.security import authenticated_userid
from pyramid.view import view_defaults, view_config
import transaction
from brewman.models.auth import User
from brewman.models.validation_exception import TryCatchFunction, ValidationError
from brewman.models.voucher import Voucher
@@ -12,15 +15,31 @@ from . import voucher_info, journal_create_voucher, purchase_create_voucher, iss
from .purchase_return import purchase_return_create_voucher
from .salary_deduction import salary_deduction_create_voucher
__author__ = 'tanshu'
@view_defaults(request_method='POST', route_name='api_voucher', renderer='json')
class save_voucher(object):
def __init__(self, request):
def parse_post(request):
keys = request.POST.keys()
files = {}
model = {}
for key in keys:
if key == 'model':
model = loads(request.POST[key], encoding=request.charset)
else:
index = key[1:]
if index in files:
files[index][key[:1]] = bytearray(request.POST[key].file.read())
else:
files[index] = {key[:1]: bytearray(request.POST[key].file.read())}
return model, files
self.request = request
self.user = User.by_id(uuid.UUID(authenticated_userid(request)))
self.json = request.json_body
self.json, self.files = parse_post(request)
self.start, self.finish = get_lock_info()
self.voucher_date = datetime.datetime.strptime(self.json['Date'], '%d-%b-%Y')
@@ -66,15 +85,15 @@ class save_voucher(object):
raise ValidationError("Vouchers after {0} have been locked.".format(self.finish.strftime('%d-%b-%Y')))
if self.json['Type'] in ['Journal', 'Payment', 'Receipt']:
voucher = journal_create_voucher(self.json, self.user)
voucher = journal_create_voucher(self.json, self.files, self.user)
elif self.json['Type'] in ['Purchase']:
voucher = purchase_create_voucher(self.json, self.user)
voucher = purchase_create_voucher(self.json, self.files, self.user)
elif self.json['Type'] in ['Purchase Return']:
voucher = purchase_return_create_voucher(self.json, self.user)
voucher = purchase_return_create_voucher(self.json, self.files, self.user)
elif self.json['Type'] in ['Issue']:
voucher = issue_create_voucher(self.json, self.user)
elif self.json['Type'] in ['Salary Deduction']:
voucher = salary_deduction_create_voucher(self.json, self.user)
transaction.commit()
session_current_date_set(self.request, self.json['Date'])
return voucher_info(Voucher.by_id(voucher.id))
return voucher_info(Voucher.by_id(voucher.id), self.request)
@@ -1,4 +1,5 @@
import datetime
from json import loads
import uuid
from pyramid.response import Response
from pyramid.security import authenticated_userid
@@ -20,10 +21,25 @@ __author__ = 'tanshu'
@view_defaults(request_method='POST', route_name='api_voucher_id', renderer='json')
class update_voucher(object):
def __init__(self, request):
def parse_post(request):
keys = request.POST.keys()
files = {}
model = {}
for key in keys:
if key == 'model':
model = loads(request.POST[key], encoding=request.charset)
else:
index = key[1:]
if index in files:
files[index][key[:1]] = bytearray(request.POST[key].file.read())
else:
files[index] = {key[:1]: bytearray(request.POST[key].file.read())}
return model, files
self.request = request
self.user = User.by_id(uuid.UUID(authenticated_userid(request)))
self.voucher = Voucher.by_id(uuid.UUID(request.matchdict.get('id', None)))
self.json = request.json_body
self.json, self.files = parse_post(request)
self.start, self.finish = get_lock_info()
self.voucher_date = datetime.datetime.strptime(self.json['Date'], '%d-%b-%Y')
@@ -87,15 +103,15 @@ class update_voucher(object):
if self.error is not None:
return self.error
if self.json['Type'] in ['Journal', 'Payment', 'Receipt']:
voucher = journal_update_voucher(self.voucher, self.json, self.user)
voucher = journal_update_voucher(self.voucher, self.json, self.files, self.user)
elif self.json['Type'] in ['Purchase']:
voucher = purchase_update_voucher(self.voucher, self.json, self.user)
voucher = purchase_update_voucher(self.voucher, self.json, self.files, self.user)
elif self.json['Type'] in ['Purchase Return']:
voucher = purchase_return_update_voucher(self.voucher, self.json, self.user)
voucher = purchase_return_update_voucher(self.voucher, self.json, self.files, self.user)
elif self.json['Type'] in ['Issue']:
voucher = issue_update_voucher(self.voucher, self.json, self.user)
elif self.json['Type'] in ['Salary Deduction']:
voucher = salary_deduction_update_voucher(self.voucher, self.json, self.user)
transaction.commit()
session_current_date_set(self.request, self.json['Date'])
return voucher_info(Voucher.by_id(voucher.id))
return voucher_info(Voucher.by_id(voucher.id), self.request)