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
27 files changed
+573 -81

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('api_issues_grid', '/api/Issues/Services/{date}')
config.add_route('salary_deduction_id', '/SalaryDeduction/{id}') config.add_route('salary_deduction_id', '/SalaryDeduction/{id}')
config.add_route('salary_deduction', '/SalaryDeduction') 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_id', '/api/Voucher/{id}')
config.add_route('api_voucher', '/api/Voucher') config.add_route('api_voucher', '/api/Voucher')
+28
View File
@@ -1,6 +1,7 @@
from datetime import datetime from datetime import datetime
import uuid import uuid
from sqlalchemy.dialects.postgresql import BYTEA
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import Column, Integer, Boolean, Unicode, DateTime, Numeric, ForeignKey, UniqueConstraint from sqlalchemy import Column, Integer, Boolean, Unicode, DateTime, Numeric, ForeignKey, UniqueConstraint
from sqlalchemy.orm import relationship, synonym, backref from sqlalchemy.orm import relationship, synonym, backref
@@ -334,3 +335,30 @@ class Fingerprint(Base):
else: else:
return old 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 { .thumbnails.small {
margin-left: -50px; margin-left: -50px;
} }
@@ -60,7 +59,6 @@
color: #333333; color: #333333;
} }
.widget-box { .widget-box {
background: none repeat scroll 0 0 #F9F9F9; background: none repeat scroll 0 0 #F9F9F9;
border-top: 1px solid #CDCDCD; border-top: 1px solid #CDCDCD;
@@ -71,6 +69,7 @@
margin-bottom: 16px; margin-bottom: 16px;
position: relative; position: relative;
} }
/* widgets from unicorn theme from http://wbpreview.com/previews/WB0F35928/widgets.html */ /* 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 { .widget-title, .modal-header, .table th, div.dataTables_wrapper .ui-widget-header, .ui-dialog .ui-dialog-titlebar {
background-color: #efefef; background-color: #efefef;
@@ -79,9 +78,11 @@
border-bottom: 1px solid #CDCDCD; border-bottom: 1px solid #CDCDCD;
height: 36px; height: 36px;
} }
.widget-title .nav-tabs { .widget-title .nav-tabs {
border-bottom: 0 none; border-bottom: 0 none;
} }
.widget-title .nav-tabs li a { .widget-title .nav-tabs li a {
border-bottom: medium none !important; border-bottom: medium none !important;
border-left: 1px solid #DDDDDD; border-left: 1px solid #DDDDDD;
@@ -95,25 +96,30 @@
font-weight: bold; font-weight: bold;
text-shadow: 0 1px 0 #FFFFFF; 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; border-left: medium none !important;
} }
.widget-title .nav-tabs li a:hover { .widget-title .nav-tabs li a:hover {
background-color: transparent !important; background-color: transparent !important;
border-color: #D6D6D6; border-color: #D6D6D6;
border-width: 0 1px; border-width: 0 1px;
color: #666666; color: #666666;
} }
.widget-title .nav-tabs li.active a { .widget-title .nav-tabs li.active a {
background-color: #F9F9F9 !important; background-color: #F9F9F9 !important;
color: #444444; color: #444444;
} }
.widget-title span.icon { .widget-title span.icon {
border-right: 1px solid #cdcdcd; border-right: 1px solid #cdcdcd;
padding: 9px 10px 7px 11px; padding: 9px 10px 7px 11px;
float: left; float: left;
opacity: .7; opacity: .7;
} }
.widget-title h5 { .widget-title h5 {
color: #666666; color: #666666;
text-shadow: 0 1px 0 #ffffff; text-shadow: 0 1px 0 #ffffff;
@@ -124,23 +130,28 @@
line-height: 12px; line-height: 12px;
margin: 0; margin: 0;
} }
.widget-title .buttons { .widget-title .buttons {
float: right; float: right;
margin: 8px 10px 0 0; margin: 8px 10px 0 0;
} }
.widget-title .label { .widget-title .label {
padding: 3px 5px 2px; padding: 3px 5px 2px;
float: right; float: right;
margin: 9px 15px 0 0; 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 { .widget-content {
padding: 12px 15px; padding: 12px 15px;
border-bottom: 1px solid #cdcdcd; border-bottom: 1px solid #cdcdcd;
} }
.widget-box.widget-plain .widget-content { .widget-box.widget-plain .widget-content {
padding: 12px 0 0; padding: 12px 0 0;
} }
.widget-box.collapsible .collapse.in .widget-content { .widget-box.collapsible .collapse.in .widget-content {
border-bottom: 1px solid #CDCDCD; border-bottom: 1px solid #CDCDCD;
} }
@@ -148,9 +159,11 @@
.nopadding { .nopadding {
padding: 0 !important; padding: 0 !important;
} }
.nopadding .table { .nopadding .table {
margin-bottom: 0; margin-bottom: 0;
} }
.nopadding .table-bordered { .nopadding .table-bordered {
border: 0; border: 0;
} }
@@ -159,4 +172,3 @@
color: #666666; color: #666666;
font-size: 11px; font-size: 11px;
} }
+36 -2
View File
@@ -2,46 +2,80 @@
.no-wrap { .no-wrap {
white-space: nowrap white-space: nowrap
} }
.hide
{ .hide {
display: none; display: none;
} }
.table tbody tr.Present td:not(.no-bg) { .table tbody tr.Present td:not(.no-bg) {
background-color: #228B22; background-color: #228B22;
} }
.table tbody tr.Off td:not(.no-bg) { .table tbody tr.Off td:not(.no-bg) {
background-color: #87CEFA; background-color: #87CEFA;
} }
.table tbody tr.Leave td:not(.no-bg) { .table tbody tr.Leave td:not(.no-bg) {
background-color: #CD5C5C; background-color: #CD5C5C;
} }
.table tbody tr.Absent td:not(.no-bg) { .table tbody tr.Absent td:not(.no-bg) {
background-color: #CD0000; background-color: #CD0000;
} }
.table tbody tr.HalfDay td:not(.no-bg) { .table tbody tr.HalfDay td:not(.no-bg) {
background-color: #98FB98; background-color: #98FB98;
} }
.table tbody tr.Double td:not(.no-bg) { .table tbody tr.Double td:not(.no-bg) {
background-color: #006400; background-color: #006400;
} }
.table tbody tr.PaidLeaveAvailed td:not(.no-bg) { .table tbody tr.PaidLeaveAvailed td:not(.no-bg) {
background-color: #EEEE00; background-color: #EEEE00;
} }
.table tbody tr.CasualLeaveAvailed td:not(.no-bg) { .table tbody tr.CasualLeaveAvailed td:not(.no-bg) {
background-color: #800080; background-color: #800080;
} }
.table tbody tr.Overtime td:not(.no-bg) { .table tbody tr.Overtime td:not(.no-bg) {
background-color: #006400; background-color: #006400;
} }
.table tbody tr.OffWorked td:not(.no-bg) { .table tbody tr.OffWorked td:not(.no-bg) {
background-color: #F5DEB3; background-color: #F5DEB3;
} }
.table tbody tr.COff td:not(.no-bg) { .table tbody tr.COff td:not(.no-bg) {
background-color: #F5DEB3; background-color: #F5DEB3;
} }
.table tbody tr.HalfPL td:not(.no-bg) { .table tbody tr.HalfPL td:not(.no-bg) {
background-color: #FFF68F; background-color: #FFF68F;
} }
.table tbody tr.HalfCL td:not(.no-bg) { .table tbody tr.HalfCL td:not(.no-bg) {
background-color: #FF00FF; 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> <textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div> </div>
</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="form-group">
<div class="col-md-offset-2 col-md-10"> <div class="col-md-offset-2 col-md-10">
<button class="btn btn-primary" tan-click="save()" ng-disabled="preventAlteration(voucher)"> <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> <textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div> </div>
</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="form-group">
<div class="col-md-offset-2 col-md-10"> <div class="col-md-offset-2 col-md-10">
<button class="btn btn-primary" tan-click="save()" ng-disabled="preventAlteration(voucher)"> <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> <textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div> </div>
</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"> <div class="form-actions">
<button class="btn btn-primary" tan-click="save()" <button class="btn btn-primary" tan-click="save()"
ng-disabled="preventAlteration(voucher)">{{voucher.VoucherID | save_button}} 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> <textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div> </div>
</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"> <div class="form-actions">
<button class="btn btn-primary" tan-click="save()" <button class="btn btn-primary" tan-click="save()"
ng-disabled="preventAlteration(voucher)">{{voucher.VoucherID | save_button}} 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> <textarea rows="3" id="txtNarration" class="form-control" ng-model="voucher.Narration"></textarea>
</div> </div>
</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="form-group">
<div class="col-md-offset-2 col-md-10"> <div class="col-md-offset-2 col-md-10">
<button class="btn btn-primary" tan-click="save()" ng-disabled="preventAlteration(voucher)"> <button class="btn btn-primary" tan-click="save()" ng-disabled="preventAlteration(voucher)">
+6 -3
View File
@@ -8,6 +8,10 @@ var AccountListCtrl = ['$scope', '$location', '$routeParams', 'Tokenizer', 'acco
$scope.filterAccounts(value); $scope.filterAccounts(value);
}, true); }, true);
$scope.$on('$destroy', function () {
Tokenizer.doFilter.cache = {};
});
$scope.searchInfo = { $scope.searchInfo = {
comparator: { comparator: {
'def': 'n', 'def': 'n',
@@ -35,13 +39,12 @@ var AccountListCtrl = ['$scope', '$location', '$routeParams', 'Tokenizer', 'acco
} }
$scope.$apply(function () { $scope.$apply(function () {
var matches = Tokenizer.parseFilterString(q, $scope.searchInfo); var matches = Tokenizer.parseFilterString(q, $scope.searchInfo);
$scope.accounts = Tokenizer.doFilter('/Accounts ' + q, $scope.info, matches); $scope.accounts = Tokenizer.doFilter(q, $scope.info, matches);
}); });
} }
}, 350); }, 350);
}] }];
;
AccountListCtrl.resolve = { AccountListCtrl.resolve = {
accounts: ['Account', function (Account) { accounts: ['Account', function (Account) {
return Account.query({}).$promise; 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] });
}
});
}
};
});
+136 -2
View File
@@ -9,7 +9,44 @@ overlord_service.factory('IssueGrid', ['$resource', function ($resource) {
overlord_service.factory('Voucher', ['$resource', function ($resource) { overlord_service.factory('Voucher', ['$resource', function ($resource) {
return $resource('/api/Voucher/:id', return $resource('/api/Voucher/:id',
{id: '@VoucherID'}, { {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 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}); $location.path('/Issue/' + date).search({Source: source, Destination: destination});
} }
function getOldInventory(productID, inventories) { function getOld(productID, inventories) {
for (var i = 0, l = inventories.length; i < l; i++) { return _.find(inventories, function (inventory) {
if (inventories[i].Product.ProductID === productID) { return inventory.Product.ProductID === productID;
return inventories[i]; });
}
}
} }
$scope.add = function () { $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), quantity = Number($scope.quantity),
consumption = (getDebitCredit($scope.voucher.Journals, -1) === '7b845f95-dfef-fa4a-897c-f0baf15284a3'); 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) { modalInstance.result.then(function (updated) {
if (updated.Product.ProductID !== $scope.selectedInventory.Batch.Product.ProductID) { 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') { if (typeof oldInventory !== 'undefined') {
delete $scope.selectedInventory; delete $scope.selectedInventory;
return false; return false;
+25 -4
View File
@@ -1,6 +1,6 @@
'use strict'; '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(); $('#txtAccount').focus();
$scope.voucher = voucher; $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 () { $scope.add = function () {
if (!$scope.account || !$scope.account.LedgerID || !$scope.amount || Number(!$scope.amount)) { if (!$scope.account || !$scope.account.LedgerID || !$scope.amount || Number(!$scope.amount)) {
return; return;
@@ -124,9 +147,7 @@ var JournalCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'voucher', 'Ac
}, function () { }, function () {
delete $scope.selectedJournal; delete $scope.selectedJournal;
} }
);
)
;
}; };
$scope.confirm = function () { $scope.confirm = function () {
+24 -1
View File
@@ -1,6 +1,6 @@
'use strict'; '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(); $('#txtAccount').focus();
$scope.voucher = voucher; $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 () { $scope.add = function () {
if (!$scope.account || !$scope.account.LedgerID || !$scope.amount || Number(!$scope.amount)) { if (!$scope.account || !$scope.account.LedgerID || !$scope.amount || Number(!$scope.amount)) {
return; return;
+6 -2
View File
@@ -9,6 +9,10 @@ var ProductListCtrl = ['$scope', '$location', '$routeParams', 'Tokenizer', 'prod
$scope.filterProducts(newValue); $scope.filterProducts(newValue);
}, true); }, true);
$scope.$on('$destroy', function () {
Tokenizer.doFilter.cache = {};
});
$scope.searchInfo = { $scope.searchInfo = {
comparator: { comparator: {
'def': 'n', 'def': 'n',
@@ -25,7 +29,7 @@ var ProductListCtrl = ['$scope', '$location', '$routeParams', 'Tokenizer', 'prod
't': 'ProductGroup', 't': 'ProductGroup',
'a': 'IsActive', 'a': 'IsActive',
'y': 'ProductYield', 'y': 'ProductYield',
'f':'Fraction', 'f': 'Fraction',
'fu': 'FractionUnits' 'fu': 'FractionUnits'
}, },
flags: { flags: {
@@ -44,7 +48,7 @@ var ProductListCtrl = ['$scope', '$location', '$routeParams', 'Tokenizer', 'prod
$scope.$apply(function () { $scope.$apply(function () {
var matches = Tokenizer.parseFilterString(q, $scope.searchInfo); var matches = Tokenizer.parseFilterString(q, $scope.searchInfo);
$scope.show_extended = 'e' in matches.f; $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'; '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.voucher = voucher;
$scope.rate = 0; $scope.rate = 0;
function getOldInventory(productID, inventories) { function getOld(productID, inventories) {
for (var i = 0, l = inventories.length; i < l; i++) { return _.find(inventories, function (inventory) {
if (inventories[i].Product.ProductID === productID) { return inventory.Product.ProductID === productID;
return inventories[i]; });
}
}
} }
$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 () { $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); quantity = Number($scope.quantity);
if (!$scope.batch || !$scope.batch.Product.ProductID || !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) { modalInstance.result.then(function (updated) {
if (updated.Product.ProductID !== $scope.selectedInventory.Batch.Product.ProductID) { 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') { if (typeof oldInventory !== 'undefined') {
delete $scope.selectedInventory; delete $scope.selectedInventory;
return false; return false;
@@ -158,7 +179,7 @@ var PurchaseReturnCtrl = ['$scope', '$location', 'dateFilter', '$modal', 'vouche
}; };
$scope.accounts = function ($viewValue) { $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) { $scope.batches = function ($viewValue) {
var params = {term: $viewValue, count: 20}; var params = {term: $viewValue, count: 20};
+30 -12
View File
@@ -1,20 +1,38 @@
'use strict'; '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; $scope.voucher = voucher;
function getOldInventory(productID, inventories) { function getOld(productID, inventories) {
if (angular.isUndefined(productID)) { return _.find(inventories, function (inventory) {
return; return inventory.Product.ProductID === productID;
} });
for (var i = 0, l = inventories.length; i < l; i++) {
if (inventories[i].Product.ProductID === productID) {
return inventories[i];
}
}
} }
$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 () { $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), quantity = Number($scope.quantity),
tax = ($scope.tax) ? Number($scope.tax) : 0, tax = ($scope.tax) ? Number($scope.tax) : 0,
discount = ($scope.discount) ? Number($scope.discount) : 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) { modalInstance.result.then(function (updated) {
if (updated.Product.ProductID !== $scope.selectedInventory.Product.ProductID) { 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') { if (typeof oldInventory !== 'undefined') {
delete $scope.selectedInventory; delete $scope.selectedInventory;
return false; return false;
+24 -1
View File
@@ -1,6 +1,6 @@
'use strict'; '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(); $('#txtAccount').focus();
$scope.voucher = voucher; $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 () { $scope.add = function () {
if (!$scope.account || !$scope.account.LedgerID || !$scope.amount || Number(!$scope.amount)) { if (!$scope.account || !$scope.account.LedgerID || !$scope.amount || Number(!$scope.amount)) {
return; 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 decimal import Decimal
from datetime import date, datetime, timedelta, time from datetime import date, datetime, timedelta, time
from io import BytesIO
import uuid import uuid
import re import re
@@ -9,6 +10,7 @@ from pyramid.response import FileResponse, Response
from pyramid.view import view_config from pyramid.view import view_config
from brewman.models.master import DbSetting from brewman.models.master import DbSetting
from brewman.models.voucher import DbImage
@view_config(route_name='home') @view_config(route_name='home')
@@ -36,6 +38,18 @@ def favicon(request):
return FileResponse(icon, request=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(): def get_lock_info():
data = DbSetting.by_name('Lock Info') data = DbSetting.by_name('Lock Info')
if data is None: if data is None:
+25 -10
View File
@@ -1,21 +1,24 @@
import uuid import uuid
import pkg_resources import pkg_resources
from pyramid.response import Response, FileResponse from pyramid.response import Response, FileResponse
from pyramid.security import authenticated_userid from pyramid.security import authenticated_userid
from pyramid.view import view_config from pyramid.view import view_config
from sqlalchemy import func from sqlalchemy import func
import transaction import transaction
from brewman import groupfinder from brewman import groupfinder
from brewman.models import DBSession from brewman.models import DBSession
from brewman.models.auth import User 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.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 brewman.views import get_lock_info
from .issue import issue_create_voucher, issue_update_voucher from .issue import issue_create_voucher, issue_update_voucher
from .journal import journal_update_voucher, journal_create_voucher from .journal import journal_update_voucher, journal_create_voucher
from .purchase import purchase_create_voucher, purchase_update_voucher from .purchase import purchase_create_voucher, purchase_update_voucher
__author__ = 'tanshu' __author__ = 'tanshu'
@@ -53,7 +56,7 @@ def voucher_post(request):
voucher.posted = True voucher.posted = True
voucher.poster_id = user.id voucher.poster_id = user.id
transaction.commit() 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): def check_delete_permissions(request, voucher):
@@ -87,10 +90,11 @@ def check_delete_permissions(request, voucher):
@TryCatchFunction @TryCatchFunction
def delete(request): def delete(request):
voucher = Voucher.by_id(uuid.UUID(request.matchdict['id'])) voucher = Voucher.by_id(uuid.UUID(request.matchdict['id']))
images = DbImage.by_resource_id(voucher.id, 'voucher')
permission = check_delete_permissions(request, voucher) permission = check_delete_permissions(request, voucher)
if permission is not None: if permission is not None:
return permission return permission
json_voucher = voucher_info(voucher) json_voucher = voucher_info(voucher, request)
batches_to_delete = [] batches_to_delete = []
if voucher.type == VoucherType.by_name('Issue').id: if voucher.type == VoucherType.by_name('Issue').id:
for item in voucher.journals: for item in voucher.journals:
@@ -125,6 +129,8 @@ def delete(request):
for b in batches_to_delete: for b in batches_to_delete:
DBSession.delete(b) DBSession.delete(b)
DBSession.delete(voucher) DBSession.delete(voucher)
for image in images:
DBSession.delete(image)
transaction.commit() transaction.commit()
return blank_voucher(info=json_voucher) return blank_voucher(info=json_voucher)
@@ -133,10 +139,10 @@ def delete(request):
@TryCatchFunction @TryCatchFunction
def get_old(request): def get_old(request):
id = request.matchdict.get('id', None) 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, json_voucher = {'VoucherID': voucher.id,
'Date': voucher.date.strftime('%d-%b-%Y'), 'Date': voucher.date.strftime('%d-%b-%Y'),
'Type': VoucherType.by_id(voucher.type).name, 'Type': VoucherType.by_id(voucher.type).name,
@@ -145,6 +151,7 @@ def voucher_info(voucher):
'Journals': [], 'Journals': [],
'Inventories': [], 'Inventories': [],
'SalaryDeductions': [], 'SalaryDeductions': [],
'Files': [],
'CreationDate': voucher.creation_date.strftime('%d-%b-%Y %H:%M'), 'CreationDate': voucher.creation_date.strftime('%d-%b-%Y %H:%M'),
'LastEditDate': voucher.last_edit_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}, 'User': {'UserID': voucher.user.id, 'Name': voucher.user.name},
@@ -170,7 +177,8 @@ def voucher_info(voucher):
'Name': item.journal.ledger.costcenter.name}}}}) 'Name': item.journal.ledger.costcenter.name}}}})
for item in voucher.inventories: for item in voucher.inventories:
text = "{0} ({1}) {2:.2f}@{3:.2f} from {4}".format(item.product.name, item.product.units, 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( json_voucher['Inventories'].append(
{'InventoryID': item.id, 'Quantity': item.quantity, 'Rate': item.rate, {'InventoryID': item.id, 'Quantity': item.quantity, 'Rate': item.rate,
'Tax': item.tax, 'Discount': item.discount, 'Amount': item.amount, '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, 'QuantityRemaining': item.batch.quantity_remaining, 'Tax': item.batch.tax,
'Discount': item.batch.discount, 'Rate': item.batch.rate, 'Discount': item.batch.discount, 'Rate': item.batch.rate,
'Product': {'ProductID': item.batch.product.id, 'Name': item.batch.product.full_name}}}) '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 return json_voucher
@@ -189,7 +202,8 @@ def blank_voucher(info):
type = info['Type'] type = info['Type']
if 'Date' not in info: if 'Date' not in info:
raise ValidationError('Date cannot be null') 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': if type == 'Journal':
pass pass
elif type == 'Payment': 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: if info is not None and 'Account' in info and info['Account'] is not None:
ledger = LedgerBase.by_id(info['Account']) ledger = LedgerBase.by_id(info['Account'])
if ledger is not None: if ledger is not None:
ledger = {'LedgerID': ledger.id, 'Name':ledger.name} ledger = {'LedgerID': ledger.id, 'Name': ledger.name}
else: else:
ledger = LedgerBase.cash_in_hand() ledger = LedgerBase.cash_in_hand()
json_voucher['Journals'].append({'Ledger': ledger, 'Amount': 0, 'Debit': -1}) 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: if info is not None and 'Account' in info and info['Account'] is not None:
ledger = LedgerBase.by_id(info['Account']) ledger = LedgerBase.by_id(info['Account'])
if ledger is not None: if ledger is not None:
ledger = {'LedgerID': ledger.id, 'Name':ledger.name} ledger = {'LedgerID': ledger.id, 'Name': ledger.name}
else: else:
ledger = LedgerBase.cash_in_hand() ledger = LedgerBase.cash_in_hand()
json_voucher['Journals'].append({'Ledger': ledger, 'Amount': 0, 'Debit': 1}) json_voucher['Journals'].append({'Ledger': ledger, 'Amount': 0, 'Debit': 1})
@@ -245,6 +259,7 @@ def blank_voucher(info):
json_voucher['SalaryDeductions'] = [] json_voucher['SalaryDeductions'] = []
else: else:
raise ValidationError("Voucher of type \"{0}\" does not exist.".format(type)) raise ValidationError("Voucher of type \"{0}\" does not exist.".format(type))
json_voucher['Files'] = []
return json_voucher return json_voucher
+14 -3
View File
@@ -1,14 +1,17 @@
import datetime import datetime
from decimal import Decimal from decimal import Decimal
import uuid import uuid
from brewman.models import DBSession from brewman.models import DBSession
from brewman.models.master import LedgerBase from brewman.models.master import LedgerBase
from brewman.models.operations import journals_valid 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' __author__ = 'tanshu'
def journal_create_voucher(json, user):
def journal_create_voucher(json, files, user):
dt = datetime.datetime.strptime(json['Date'], '%d-%b-%Y') 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'])) voucher = Voucher(date=dt, narration=json['Narration'], user_id=user.id, type=VoucherType.by_name(json['Type']))
DBSession.add(voucher) DBSession.add(voucher)
@@ -21,10 +24,12 @@ def journal_create_voucher(json, user):
voucher.journals.append(journal) voucher.journals.append(journal)
DBSession.add(journal) DBSession.add(journal)
journals_valid(voucher) journals_valid(voucher)
for key, value in files.items():
DBSession.add(DbImage(voucher.id, 'voucher', value['f'], value['t']))
return voucher 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.date = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
voucher.narration = json['Narration'] voucher.narration = json['Narration']
voucher.user_id = user.id voucher.user_id = user.id
@@ -55,6 +60,12 @@ def journal_update_voucher(voucher, json, user):
DBSession.add(journal) DBSession.add(journal)
voucher.journals.append(journal) voucher.journals.append(journal)
journals_valid(voucher) 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 return voucher
+15 -4
View File
@@ -1,17 +1,20 @@
import datetime import datetime
from decimal import Decimal from decimal import Decimal
import uuid import uuid
from sqlalchemy import func from sqlalchemy import func
from brewman.models import DBSession from brewman.models import DBSession
from brewman.models.master import Product, LedgerBase from brewman.models.master import Product, LedgerBase
from brewman.models.operations import journals_valid, inventory_valid from brewman.models.operations import journals_valid, inventory_valid
from brewman.models.validation_exception import ValidationError 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' __author__ = 'tanshu'
def purchase_create_voucher(json, user): def purchase_create_voucher(json, files, user):
dt = datetime.datetime.strptime(json['Date'], '%d-%b-%Y') 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'])) voucher = Voucher(date=dt, narration=json['Narration'], user_id=user.id, type=VoucherType.by_name(json['Type']))
DBSession.add(voucher) DBSession.add(voucher)
@@ -23,6 +26,8 @@ def purchase_create_voucher(json, user):
DBSession.add(item) DBSession.add(item)
journals_valid(voucher) journals_valid(voucher)
inventory_valid(voucher) inventory_valid(voucher)
for key, value in files.items():
DBSession.add(DbImage(voucher.id, 'voucher', value['f'], value['t']))
return voucher return voucher
@@ -65,7 +70,7 @@ def purchase_create_journals(inventories, ledgerID):
return list(journals.values()) 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.date = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
voucher.narration = json['Narration'] voucher.narration = json['Narration']
voucher.user_id = user.id voucher.user_id = user.id
@@ -76,6 +81,12 @@ def purchase_update_voucher(voucher, json, user):
purchase_update_journals(voucher, json['Journals']) purchase_update_journals(voucher, json['Journals'])
journals_valid(voucher) journals_valid(voucher)
inventory_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 return voucher
@@ -116,7 +127,7 @@ def purchase_update_inventory(voucher, newInventories):
item.batch.tax = tax item.batch.tax = tax
product.price = rate product.price = rate
newInventories.remove(i) 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 break
if not found: if not found:
uses = DBSession.query(func.count(Inventory.id)) \ uses = DBSession.query(func.count(Inventory.id)) \
@@ -5,12 +5,12 @@ from brewman.models import DBSession
from brewman.models.master import Product, LedgerBase from brewman.models.master import Product, LedgerBase
from brewman.models.operations import inventory_valid, journals_valid from brewman.models.operations import inventory_valid, journals_valid
from brewman.models.validation_exception import ValidationError 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' __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') 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'])) voucher = Voucher(date=dt, narration=json['Narration'], user_id=user.id, type=VoucherType.by_name(json['Type']))
DBSession.add(voucher) DBSession.add(voucher)
@@ -22,6 +22,8 @@ def purchase_return_create_voucher(json, user):
DBSession.add(item) DBSession.add(item)
journals_valid(voucher) journals_valid(voucher)
inventory_valid(voucher) inventory_valid(voucher)
for key, value in files.items():
DBSession.add(DbImage(voucher.id, 'voucher', value['f'], value['t']))
return voucher return voucher
@@ -62,7 +64,7 @@ def purchase_return_create_journals(inventories, ledgerID):
return list(journals.values()) 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.date = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
voucher.narration = json['Narration'] voucher.narration = json['Narration']
voucher.user_id = user.id voucher.user_id = user.id
@@ -73,6 +75,12 @@ def purchase_return_update_voucher(voucher, json, user):
purchase_return_update_journals(voucher, json['Journals']) purchase_return_update_journals(voucher, json['Journals'])
journals_valid(voucher) journals_valid(voucher)
inventory_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 return voucher
+25 -6
View File
@@ -1,8 +1,11 @@
import datetime
import uuid import uuid
from json import loads
import datetime
from pyramid.security import authenticated_userid from pyramid.security import authenticated_userid
from pyramid.view import view_defaults, view_config from pyramid.view import view_defaults, view_config
import transaction import transaction
from brewman.models.auth import User from brewman.models.auth import User
from brewman.models.validation_exception import TryCatchFunction, ValidationError from brewman.models.validation_exception import TryCatchFunction, ValidationError
from brewman.models.voucher import Voucher 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 .purchase_return import purchase_return_create_voucher
from .salary_deduction import salary_deduction_create_voucher from .salary_deduction import salary_deduction_create_voucher
__author__ = 'tanshu' __author__ = 'tanshu'
@view_defaults(request_method='POST', route_name='api_voucher', renderer='json') @view_defaults(request_method='POST', route_name='api_voucher', renderer='json')
class save_voucher(object): class save_voucher(object):
def __init__(self, request): 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.request = request
self.user = User.by_id(uuid.UUID(authenticated_userid(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.start, self.finish = get_lock_info()
self.voucher_date = datetime.datetime.strptime(self.json['Date'], '%d-%b-%Y') 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'))) raise ValidationError("Vouchers after {0} have been locked.".format(self.finish.strftime('%d-%b-%Y')))
if self.json['Type'] in ['Journal', 'Payment', 'Receipt']: 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']: 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']: 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']: elif self.json['Type'] in ['Issue']:
voucher = issue_create_voucher(self.json, self.user) voucher = issue_create_voucher(self.json, self.user)
elif self.json['Type'] in ['Salary Deduction']: elif self.json['Type'] in ['Salary Deduction']:
voucher = salary_deduction_create_voucher(self.json, self.user) voucher = salary_deduction_create_voucher(self.json, self.user)
transaction.commit() transaction.commit()
session_current_date_set(self.request, self.json['Date']) 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 import datetime
from json import loads
import uuid import uuid
from pyramid.response import Response from pyramid.response import Response
from pyramid.security import authenticated_userid from pyramid.security import authenticated_userid
@@ -20,10 +21,25 @@ __author__ = 'tanshu'
@view_defaults(request_method='POST', route_name='api_voucher_id', renderer='json') @view_defaults(request_method='POST', route_name='api_voucher_id', renderer='json')
class update_voucher(object): class update_voucher(object):
def __init__(self, request): 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.request = request
self.user = User.by_id(uuid.UUID(authenticated_userid(request))) self.user = User.by_id(uuid.UUID(authenticated_userid(request)))
self.voucher = Voucher.by_id(uuid.UUID(request.matchdict.get('id', None))) 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.start, self.finish = get_lock_info()
self.voucher_date = datetime.datetime.strptime(self.json['Date'], '%d-%b-%Y') 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: if self.error is not None:
return self.error return self.error
if self.json['Type'] in ['Journal', 'Payment', 'Receipt']: 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']: 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']: 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']: elif self.json['Type'] in ['Issue']:
voucher = issue_update_voucher(self.voucher, self.json, self.user) voucher = issue_update_voucher(self.voucher, self.json, self.user)
elif self.json['Type'] in ['Salary Deduction']: elif self.json['Type'] in ['Salary Deduction']:
voucher = salary_deduction_update_voucher(self.voucher, self.json, self.user) voucher = salary_deduction_update_voucher(self.voucher, self.json, self.user)
transaction.commit() transaction.commit()
session_current_date_set(self.request, self.json['Date']) 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)