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:
1 parent
2201a90278
commit
4e85d97e47
28 files changed
+613
-121
No files matched your search
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;*/
|
||||
/*}*/
|
||||
@@ -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)">
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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)">
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}]);
|
||||
@@ -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;
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="modal-body">
|
||||
<img ng-click="cancel()" ng-src="{{imageUrl}}">
|
||||
</div>
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user