diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..9869ee0 --- /dev/null +++ b/README.txt @@ -0,0 +1,6 @@ +TODO: Authorization and Authentication +TODO: User View == Proper has_permission handling +TODO: Settings / Config module +INFO: product now has a 'has_happy_hour' check box, if it is there, then show twice in list once as happy hour +and one normal. in the sale also do the same. this will also make sure that the price is synced and if happy hour +is being properly given \ No newline at end of file diff --git a/summer/QtDesignerFiles/FlowLayout.py b/summer/QtDesignerFiles/FlowLayout.py new file mode 100644 index 0000000..51aba30 --- /dev/null +++ b/summer/QtDesignerFiles/FlowLayout.py @@ -0,0 +1,103 @@ +from PyQt5.QtCore import QPoint, QRect, QSize, Qt +from PyQt5.QtWidgets import QLayout, QSizePolicy, QStyle + +__author__ = 'tanshu' + + +class FlowLayout(QLayout): + def __init__(self, parent=None, margin=0, spacing=-1): + super(FlowLayout, self).__init__(parent) + + if parent is not None: + self.setContentsMargins(margin, margin, margin, margin) + + self.setSpacing(spacing) + + self.itemList = [] + + def __del__(self): + item = self.takeAt(0) + while item: + item = self.takeAt(0) + + def addItem(self, item): + self.itemList.append(item) + + def count(self): + return len(self.itemList) + + def itemAt(self, index): + if index >= 0 and index < len(self.itemList): + return self.itemList[index] + + return None + + def takeAt(self, index): + if index >= 0 and index < len(self.itemList): + return self.itemList.pop(index) + + return None + + def expandingDirections(self): + return Qt.Orientations(Qt.Orientation(0)) + + def hasHeightForWidth(self): + return True + + def heightForWidth(self, width): + height = self.doLayout(QRect(0, 0, width, 0), True) + return height + + def setGeometry(self, rect): + super(FlowLayout, self).setGeometry(rect) + self.doLayout(rect, False) + + def sizeHint(self): + return self.minimumSize() + + def minimumSize(self): + size = QSize() + + for item in self.itemList: + size = size.expandedTo(item.minimumSize()) + + margin, _, _, _ = self.getContentsMargins() + + size += QSize(2 * margin, 2 * margin) + return size + + def horizontalSpacing(self): + return self.smartSpacing(QStyle.PM_LayoutHorizontalSpacing) + + def verticalSpacing(self): + return self.smartSpacing(QStyle.PM_LayoutVerticalSpacing) + + def doLayout(self, rect, testOnly): + left, top, right, bottom = self.getContentsMargins() + effectiveRect = rect.adjusted(+left, +top, -right, -bottom) + x = effectiveRect.x() + y = effectiveRect.y() + lineHeight = 0 + + for item in self.itemList: + wid = item.widget() + if wid.isHidden(): + continue + spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, + Qt.Horizontal) + spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, + Qt.Vertical) + nextX = x + item.sizeHint().width() + spaceX + if nextX - spaceX > rect.right() and lineHeight > 0: + x = rect.x() + y = y + lineHeight + spaceY + nextX = x + item.sizeHint().width() + spaceX + lineHeight = 0 + + if not testOnly: + item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) + + x = nextX + lineHeight = max(lineHeight, item.sizeHint().height()) + + return y + lineHeight - rect.y() diff --git a/summer/QtDesignerFiles/RoleList.ui b/summer/QtDesignerFiles/ListForm.ui similarity index 80% rename from summer/QtDesignerFiles/RoleList.ui rename to summer/QtDesignerFiles/ListForm.ui index 4402c80..9dc3677 100644 --- a/summer/QtDesignerFiles/RoleList.ui +++ b/summer/QtDesignerFiles/ListForm.ui @@ -1,7 +1,7 @@ - RoleList - + ListForm + Qt::ApplicationModal @@ -14,17 +14,17 @@ - Roles + List Form - + - Add Role + Add Item diff --git a/summer/QtDesignerFiles/MainWindow.ui b/summer/QtDesignerFiles/MainWindow.ui index 53df6f8..8ae68fc 100644 --- a/summer/QtDesignerFiles/MainWindow.ui +++ b/summer/QtDesignerFiles/MainWindow.ui @@ -251,8 +251,8 @@ - 650 - 340 + 10 + 450 150 100 @@ -274,6 +274,19 @@ Management + + + + 650 + 340 + 150 + 100 + + + + Taxes + + @@ -297,6 +310,7 @@ btnDiscountReport btnChangePassword btnManagement + btnTaxes btnExit diff --git a/summer/QtDesignerFiles/ProductDetail.ui b/summer/QtDesignerFiles/ProductDetail.ui new file mode 100644 index 0000000..e4cecce --- /dev/null +++ b/summer/QtDesignerFiles/ProductDetail.ui @@ -0,0 +1,188 @@ + + + ProductDetails + + + Qt::WindowModal + + + + 0 + 0 + 397 + 417 + + + + Product Details + + + true + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Name + + + + + + + + + + Units + + + + + + + + + + Group + + + + + + + + + + Price + + + + + + + + + + Has Happy Hour? + + + + + + + Service Charge + + + + + + + + + + Is Service Charge Taxable? + + + + + + + Service Tax + + + + + + + + + + VAT + + + + + + + + + + Sort Order + + + + + + + + + + Is Active? + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + txtName + txtUnits + cmbProductGroup + dsbPrice + chkHasHappyHour + dsbServiceCharge + chkScTaxable + cmbServiceTax + cmbVat + sbSortOrder + chkIsActive + buttonBox + + + + + buttonBox + accepted() + ProductDetails + accept() + + + 227 + 331 + + + 157 + 274 + + + + + buttonBox + rejected() + ProductDetails + reject() + + + 295 + 337 + + + 286 + 274 + + + + + diff --git a/summer/QtDesignerFiles/ProductGroupList.ui b/summer/QtDesignerFiles/ProductGroupList.ui deleted file mode 100644 index 464baa8..0000000 --- a/summer/QtDesignerFiles/ProductGroupList.ui +++ /dev/null @@ -1,37 +0,0 @@ - - - ProductGroupList - - - Qt::ApplicationModal - - - - 0 - 0 - 800 - 600 - - - - Product Groups - - - - - - - - - - Add Product Group - - - - - - - - - - diff --git a/summer/QtDesignerFiles/SaleWindow.py b/summer/QtDesignerFiles/SaleWindow.py new file mode 100644 index 0000000..22f99e7 --- /dev/null +++ b/summer/QtDesignerFiles/SaleWindow.py @@ -0,0 +1,68 @@ +from PyQt5 import QtCore, QtWidgets + +from summer.QtDesignerFiles.FlowLayout import FlowLayout + + +class SaleWindowQt(object): + def setupUi(self, SaleWindow): + SaleWindow.setObjectName("SaleWindow") + SaleWindow.resize(1024, 768) + self.centralwidget = QtWidgets.QWidget(SaleWindow) + self.centralwidget.setObjectName("centralwidget") + + self.tableView = QtWidgets.QTableView(self.centralwidget) + self.tableView.setGeometry(QtCore.QRect(10, 10, 400, 550)) + self.tableView.setObjectName("tableView") + + self.abWidget = QtWidgets.QWidget(self.centralwidget) + self.abWidget.setGeometry(QtCore.QRect(10, 620, 1004, 140)) + self.abWidget.setObjectName("abWidget") + self.flActions = FlowLayout(self.abWidget) + self.flActions.setContentsMargins(0, 0, 0, 0) + self.flActions.setObjectName("flActions") + + self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget) + self.verticalLayoutWidget.setGeometry(QtCore.QRect(909, 10, 101, 641)) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.vlGroups = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.vlGroups.setContentsMargins(0, 0, 0, 0) + self.vlGroups.setObjectName("vlGroups") + self.pushButton_9 = QtWidgets.QPushButton(self.verticalLayoutWidget) + self.pushButton_9.setObjectName("pushButton_9") + self.vlGroups.addWidget(self.pushButton_9) + self.pushButton_8 = QtWidgets.QPushButton(self.verticalLayoutWidget) + self.pushButton_8.setObjectName("pushButton_8") + self.vlGroups.addWidget(self.pushButton_8) + + self.mbWidget = QtWidgets.QWidget(self.centralwidget) + self.mbWidget.setGeometry(QtCore.QRect(420, 10, 510, 550)) + self.mbWidget.setObjectName("mbWidget") + self.flMainButtons = FlowLayout(self.mbWidget) + self.flMainButtons.setContentsMargins(0, 0, 0, 0) + self.flMainButtons.setObjectName("flMainButtons") + + SaleWindow.setCentralWidget(self.centralwidget) + self.statusbar = QtWidgets.QStatusBar(SaleWindow) + self.statusbar.setObjectName("statusbar") + SaleWindow.setStatusBar(self.statusbar) + + self.retranslateUi(SaleWindow) + QtCore.QMetaObject.connectSlotsByName(SaleWindow) + + def retranslateUi(self, SaleWindow): + _translate = QtCore.QCoreApplication.translate + SaleWindow.setWindowTitle(_translate("SaleWindow", "MainWindow")) + self.pushButton_9.setText(_translate("SaleWindow", "PushButton")) + self.pushButton_8.setText(_translate("SaleWindow", "PushButton")) + + def add_action(self, button, title, action): + act = QtWidgets.QPushButton(self.horizontalLayoutWidget) + act.setObjectName(button) + act.clicked.connect(action) + act.setText(title) + self.flActions.addWidget(act) + self.actions[button] = act + + def remove_action(self, button): + if button in self.actions: + del self.actions[button] diff --git a/summer/QtDesignerFiles/TaxDetail.ui b/summer/QtDesignerFiles/TaxDetail.ui new file mode 100644 index 0000000..15207ec --- /dev/null +++ b/summer/QtDesignerFiles/TaxDetail.ui @@ -0,0 +1,98 @@ + + + TaxDetails + + + Qt::WindowModal + + + + 0 + 0 + 397 + 231 + + + + Tax Details + + + true + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Name + + + + + + + + + + Rate % + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + txtName + dsbRate + buttonBox + + + + + buttonBox + accepted() + TaxDetails + accept() + + + 227 + 331 + + + 157 + 274 + + + + + buttonBox + rejected() + TaxDetails + reject() + + + 295 + 337 + + + 286 + 274 + + + + + diff --git a/summer/QtDesignerFiles/UserList.ui b/summer/QtDesignerFiles/UserList.ui deleted file mode 100644 index c830e04..0000000 --- a/summer/QtDesignerFiles/UserList.ui +++ /dev/null @@ -1,37 +0,0 @@ - - - UserList - - - Qt::ApplicationModal - - - - 0 - 0 - 800 - 600 - - - - Users - - - - - - - - - - Add User - - - - - - - - - - diff --git a/summer/models/auth.py b/summer/models/auth.py index 4e51951..67971c0 100644 --- a/summer/models/auth.py +++ b/summer/models/auth.py @@ -7,8 +7,8 @@ from sqlalchemy.schema import ForeignKey, Table from sqlalchemy import Column, Boolean, Unicode from sqlalchemy.orm import synonym, relationship -from models.guidtype import GUID -from models import Base +from summer.models.guidtype import GUID +from summer.models import Base def encrypt(val): diff --git a/summer/models/master.py b/summer/models/master.py index f93c278..38ba8f2 100644 --- a/summer/models/master.py +++ b/summer/models/master.py @@ -1,10 +1,12 @@ __author__ = 'tanshu' import uuid + from sqlalchemy import UniqueConstraint, Column, Integer, Unicode, Numeric, Boolean, ForeignKey, func, PickleType from sqlalchemy.orm import relationship -from models import Base -from models.guidtype import GUID + +from summer.models import Base +from summer.models.guidtype import GUID class Product(Base): @@ -30,15 +32,14 @@ class Product(Base): service_tax = relationship('Tax', foreign_keys=service_tax_id, backref='products_service_tax') vat = relationship('Tax', foreign_keys=vat_id, backref='products_vat') - def __init__(self, code=None, name=None, units=None, product_group_id=None, price=None, happy_hour=None, + def __init__(self, name=None, units=None, product_group_id=None, price=None, has_happy_hour=None, service_tax_id=None, vat_id=None, service_charge=None, sc_taxable=None, is_active=None, sort_order=None, is_fixture=False): - self.code = code self.name = name self.units = units self.product_group_id = product_group_id self.price = price - self.happy_hour = happy_hour + self.has_happy_hour = has_happy_hour self.service_tax_id = service_tax_id self.vat_id = vat_id self.service_charge = service_charge @@ -52,8 +53,8 @@ class Product(Base): return "{0} ({1})".format(self.name, self.units) @classmethod - def list(cls, name, active, *, DBSession=None): - query = DBSession.query(cls) + def list(cls, name, active, *, session=None): + query = session.query(cls) if active is not None: query = query.filter(cls.is_active != active) for item in name.split(): @@ -61,28 +62,28 @@ class Product(Base): return query.order_by(cls.name) @classmethod - def by_name(cls, name, *, DBSession=None): - return DBSession.query(cls).filter(cls.name == name).first() + def by_name(cls, name, *, session=None): + return session.query(cls).filter(cls.name == name).first() @classmethod - def by_full_name(cls, full_name, *, DBSession=None): - return DBSession.query(cls).filter(cls.name + ' (' + cls.units + ')' == full_name).first() + def by_full_name(cls, full_name, *, session=None): + return session.query(cls).filter(cls.name + ' (' + cls.units + ')' == full_name).first() @classmethod - def by_id(cls, id, *, DBSession=None): - return DBSession.query(cls).filter(cls.id == id).first() + def by_id(cls, id, *, session=None): + return session.query(cls).filter(cls.id == id).first() @classmethod - def query(cls, *, DBSession=None): - return DBSession.query(cls) + def query(cls, *, session=None): + return session.query(cls) - def create(self, *, DBSession=None): - code = DBSession.query(func.max(Product.code)).one()[0] + def create(self, *, session=None): + code = session.query(func.max(Product.code)).one()[0] if code is None: self.code = 1 else: self.code = code + 1 - DBSession.add(self) + session.add(self) return self def can_delete(self, advanced_delete): @@ -126,7 +127,7 @@ class ProductGroup(Base): self.is_fixture = is_fixture @classmethod - def by_id(cls, id, * , session=None): + def by_id(cls, id, *, session=None): if not isinstance(id, uuid.UUID): id = uuid.UUID(id) return session.query(cls).filter(cls.id == id).first() @@ -134,8 +135,8 @@ class ProductGroup(Base): def can_delete(self, advanced_delete): if self.is_fixture: return False, ('Fixture', "{0} cannot be edited or deleted.".format(self.name)) - #if len(self.inventories) > 0 and not advanced_delete: - # return False, 'Product has entries' + # if len(self.inventories) > 0 and not advanced_delete: + # return False, 'Product has entries' return True, '' @@ -156,23 +157,23 @@ class Tax(Base): self.is_fixture = is_fixture @classmethod - def by_id(cls, id): + def by_id(cls, id, *, session=None): if not isinstance(id, uuid.UUID): id = uuid.UUID(id) - return DBSession.query(cls).filter(cls.id == id).first() + return session.query(cls).filter(cls.id == id).first() @classmethod - def by_name(cls, name): - return DBSession.query(cls).filter(cls.name == name).first() + def by_name(cls, name, *, session=None): + return session.query(cls).filter(cls.name == name).first() @classmethod - def list(cls): - return DBSession.query(cls).order_by(cls.name).all() + def list(cls, *, session=None): + return session.query(cls).order_by(cls.name).all() def can_delete(self, advanced_delete): if self.is_fixture: return False, ('Fixture', "{0} cannot be edited or deleted.".format(self.name)) - #if len(self.inventories) > 0 and not advanced_delete: + # if len(self.inventories) > 0 and not advanced_delete: # return False, 'Product has entries' return True, '' @@ -243,7 +244,7 @@ class FoodTable(Base): return DBSession.query(cls).order_by(cls.name).all() def can_delete(self, advanced_delete): - #if len(self.inventories) > 0 and not advanced_delete: + # if len(self.inventories) > 0 and not advanced_delete: # return False, 'Product has entries' return True, '' @@ -306,7 +307,7 @@ class Modifier(Base): return DBSession.query(cls).order_by(cls.name).all() def can_delete(self, advanced_delete): - #if len(self.inventories) > 0 and not advanced_delete: + # if len(self.inventories) > 0 and not advanced_delete: # return False, 'Product has entries' return True, '' diff --git a/summer/models/validation_exception.py b/summer/models/validation_exception.py index 14a6e82..13559ce 100644 --- a/summer/models/validation_exception.py +++ b/summer/models/validation_exception.py @@ -1,16 +1,11 @@ -import json - __author__ = 'tanshu' -from sqlalchemy.exc import OperationalError, IntegrityError - class ValidationError(Exception): - def __init__(self, header, message, Errors=None): - self.header = header + def __init__(self, message, Errors=None): self.message = message # Call the base class constructor with the parameters it needs - Exception.__init__(self, (header, message)) + Exception.__init__(self, message) # Now for your custom code... self.Errors = Errors @@ -19,22 +14,4 @@ class ValidationError(Exception): return self.message def json(self): - return (self.header, self.message) -# -# -# def TryCatchFunction(f): -# def _decorator(self, *args, **kwargs): -# try: -# return f(self, *args, **kwargs) -# except ValidationError as ex: -# transaction.abort() -# response = Response(json.dumps(ex.json())) -# response.status_int = 500 -# return response -# except (ValueError, KeyError, AttributeError, TypeError, OperationalError, IntegrityError) as ex: -# transaction.abort() -# response = Response(json.dumps(('Failed validation', str(ex)))) -# response.status_int = 500 -# return response -# -# return _decorator + return self.message diff --git a/summer/models/voucher.py b/summer/models/voucher.py index 7d45660..cc6d3d9 100644 --- a/summer/models/voucher.py +++ b/summer/models/voucher.py @@ -6,9 +6,9 @@ from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy import Column, Integer, DateTime, Numeric, ForeignKey, Boolean, UniqueConstraint from sqlalchemy.orm import relationship, synonym, backref -from models.master import Tax -from models.guidtype import GUID -from models import Base +from summer.models.master import Tax +from summer.models.guidtype import GUID +from summer.models import Base class VoucherType: diff --git a/summer/templates/MainWindow.py b/summer/templates/MainWindow.py index 4262603..ce41f4c 100644 --- a/summer/templates/MainWindow.py +++ b/summer/templates/MainWindow.py @@ -7,15 +7,17 @@ from PyQt5 import uic file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/MainWindow.ui' base, form = uic.loadUiType(file) - class MainWindow(base, form): def __init__(self, router, parent=None): super(MainWindow, self).__init__() self.setupUi(self) self.btnExit.clicked.connect(self.handle_exit) self.btnUsers.clicked.connect(router.show_users) + self.btnSales.clicked.connect(router.show_sales) self.btnRoles.clicked.connect(router.show_roles) self.btnProductGroups.clicked.connect(router.show_product_groups) + self.btnTaxes.clicked.connect(router.show_taxes) + self.btnProducts.clicked.connect(router.show_products) def handle_exit(self): self.close() diff --git a/summer/templates/ProductDetail.py b/summer/templates/ProductDetail.py new file mode 100644 index 0000000..b672875 --- /dev/null +++ b/summer/templates/ProductDetail.py @@ -0,0 +1,108 @@ +__author__ = 'tanshu' +from PyQt5 import uic, QtCore + + +file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/ProductDetail.ui' +base, form = uic.loadUiType(file) + + +class ProductDetail(base, form): + def __init__(self, router, product, product_groups, taxes, parent=None): + super(ProductDetail, self).__init__() + self.setupUi(self) + self.router = router + self.product = product + self.txtName.setText(product['Name']) + self.txtUnits.setText(product['Units']) + self.product_group_model = ProductGroupModel(product_groups) + self.cmbProductGroup.setModel(self.product_group_model) + pg_index = [i for (i, d) in enumerate(product_groups) if + d['ProductGroupID'] == product['ProductGroup']['ProductGroupID']] + if len(pg_index) != 0: + self.cmbProductGroup.setCurrentIndex(pg_index[0]) + + self.dsbPrice.setMinimum(0) + self.dsbPrice.setValue(product['Price']) + self.chkHasHappyHour.setCheckState(QtCore.Qt.Checked if product['HasHappyHour'] else QtCore.Qt.Unchecked) + + self.service_tax_model = TaxModel(taxes) + self.cmbServiceTax.setModel(self.service_tax_model) + st_index = [i for (i, d) in enumerate(taxes) if d['TaxID'] == product['ServiceTax']['TaxID']] + if len(st_index) != 0: + self.cmbServiceTax.setCurrentIndex(st_index[0]) + + self.vat_model = TaxModel(taxes) + self.cmbVat.setModel(self.vat_model) + vat_index = [i for (i, d) in enumerate(taxes) if d['TaxID'] == product['Vat']['TaxID']] + if len(vat_index) != 0: + self.cmbVat.setCurrentIndex(vat_index[0]) + + self.dsbServiceCharge.setMinimum(0) + self.dsbServiceCharge.setValue(product['ServiceCharge']) + self.chkScTaxable.setCheckState(QtCore.Qt.Checked if product['ScTaxable'] else QtCore.Qt.Unchecked) + self.chkIsActive.setCheckState(QtCore.Qt.Checked if product['IsActive'] else QtCore.Qt.Unchecked) + self.sbSortOrder.setMinimum(0) + self.sbSortOrder.setValue(product['SortOrder']) + self.accepted.connect(self.save_product) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) + + def save_product(self): + self.product['Name'] = self.txtName.text() + self.product['Units'] = self.txtUnits.text() + self.product['ProductGroup'] = self.cmbProductGroup.currentData() + self.product['Price'] = self.dsbPrice.value() + self.product['HasHappyHour'] = self.chkHasHappyHour.isChecked() + self.product['ServiceTax'] = self.cmbServiceTax.currentData() + self.product['Vat'] = self.cmbVat.currentData() + self.product['ServiceCharge'] = self.dsbServiceCharge.value() / 100 + self.product['ScTaxable'] = self.chkScTaxable.isChecked() + self.product['IsActive'] = self.chkIsActive.isChecked() + self.product['SortOrder'] = self.sbSortOrder.value() + + self.router.save_product(self.product) + + +class ProductGroupModel(QtCore.QAbstractListModel): + def __init__(self, product_groups=None, parent=None): + QtCore.QAbstractListModel.__init__(self, parent) + self.product_groups = product_groups if product_groups is not None else [] + + def rowCount(self, parent): + return len(self.product_groups) + + def data(self, index, role): + if role == QtCore.Qt.DisplayRole: + item = self.product_groups[index.row()] + return item['Name'] + if role == QtCore.Qt.UserRole: + return self.product_groups[index.row()] + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + return 'Product Groups' + else: + return section + 1 + + +class TaxModel(QtCore.QAbstractListModel): + def __init__(self, taxes=None, parent=None): + QtCore.QAbstractListModel.__init__(self, parent) + self.taxes = taxes if taxes is not None else [] + + def rowCount(self, parent): + return len(self.taxes) + + def data(self, index, role): + if role == QtCore.Qt.DisplayRole: + item = self.taxes[index.row()] + return item['Name'] + ' ' + '({0:,.2f}%)'.format(item['Rate'] * 100) + if role == QtCore.Qt.UserRole: + return self.taxes[index.row()] + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + return 'Taxes' + else: + return section + 1 \ No newline at end of file diff --git a/summer/templates/ProductGroupList.py b/summer/templates/ProductGroupList.py index 8f9c242..7562cdb 100644 --- a/summer/templates/ProductGroupList.py +++ b/summer/templates/ProductGroupList.py @@ -2,7 +2,7 @@ __author__ = 'tanshu' from PyQt5 import uic, QtCore, QtWidgets -file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/ProductGroupList.ui' +file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/ListForm.ui' base, form = uic.loadUiType(file) @@ -10,19 +10,26 @@ class ProductGroupList(base, form): def __init__(self, router, product_groups, parent=None): super(ProductGroupList, self).__init__() self.setupUi(self) + self.translateUi() self.router = router self.product_groups = product_groups self.model = ProductGroupsTableModel(product_groups) - self.tblProductGroups.setModel(self.model) - self.tblProductGroups.doubleClicked.connect(self.show_product_group) + self.tblItems.setModel(self.model) + self.tblItems.doubleClicked.connect(self.show_product_group) self.btnAddNew.clicked.connect(self.add_new_product_group) self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) - self.tblProductGroups.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tblItems.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + + def translateUi(self): + self.setObjectName("ProductGroupList") + _translate = QtCore.QCoreApplication.translate + self.setWindowTitle(_translate("ListForm", "Product Groups")) + self.btnAddNew.setText(_translate("ListForm", "Add Product Group")) def update_data(self, data): self.product_groups = data self.model = ProductGroupsTableModel(self.product_groups) - self.tblProductGroups.setModel(self.model) + self.tblItems.setModel(self.model) def show_product_group(self, index): product_group = self.model.product_groups[index.row()] diff --git a/summer/templates/ProductList.py b/summer/templates/ProductList.py new file mode 100644 index 0000000..cdd5281 --- /dev/null +++ b/summer/templates/ProductList.py @@ -0,0 +1,110 @@ +__author__ = 'tanshu' +from PyQt5 import uic, QtCore, QtWidgets + + +file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/ListForm.ui' +base, form = uic.loadUiType(file) + + +class ProductList(base, form): + def __init__(self, router, products, parent=None): + super(ProductList, self).__init__() + self.setupUi(self) + self.translateUi() + self.router = router + self.products = products + self.model = ProductsTableModel(products) + self.tblItems.setModel(self.model) + self.tblItems.doubleClicked.connect(self.show_product) + self.btnAddNew.clicked.connect(self.add_new_product) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) + self.tblItems.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + + def translateUi(self): + self.setObjectName("ProductList") + _translate = QtCore.QCoreApplication.translate + self.setWindowTitle(_translate("ListForm", "Products")) + self.btnAddNew.setText(_translate("ListForm", "Add Product")) + + def update_data(self, data): + self.products = data + self.model = ProductsTableModel(self.products) + self.tblItems.setModel(self.model) + + def show_product(self, index): + product = self.model.products[index.row()] + self.router.show_product(product['ProductID']) + + def add_new_product(self): + self.router.show_product(None) + + +class ProductsTableModel(QtCore.QAbstractTableModel): + def __init__(self, products=None, parent=None): + QtCore.QAbstractTableModel.__init__(self, parent) + self.products = products if products is not None else [] + + def rowCount(self, parent): + return len(self.products) + + def columnCount(self, parent): + return 12 + + def data(self, index, role): + if role == QtCore.Qt.DisplayRole: + item = self.products[index.row()] + column = index.column() + if column == 0: + return item['Name'] + elif column == 1: + return item['Units'] + elif column == 2: + return item['ProductGroup']['Name'] + elif column == 3: + return '{0:,.2f}'.format(item['Price']) + elif column == 4: + return item['HasHappyHour'] + elif column == 5: + return item['ServiceTax']['Name'] + ' ' + '({0:,.2f}%)'.format(item['ServiceTax']['Rate'] * 100) + elif column == 6: + return item['Vat']['Name'] + ' ' + '({0:,.2f}%)'.format(item['Vat']['Rate'] * 100) + elif column == 7: + return '{0:,.2f}%'.format(item['ServiceCharge'] * 100) + elif column == 8: + return item['ScTaxable'] + elif column == 9: + return item['IsActive'] + elif column == 10: + return item['IsFixture'] + elif column == 11: + return item['SortOrder'] + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + if section == 0: + return 'Name' + elif section == 1: + return 'Units' + elif section == 2: + return 'Product Group' + elif section == 3: + return 'Price' + elif section == 4: + return 'Has Happy Hour' + elif section == 5: + return 'Service Tax' + elif section == 6: + return 'Vat' + elif section == 7: + return 'Service Charge' + elif section == 8: + return 'Is Sc Taxable?' + elif section == 9: + return 'Is Active?' + elif section == 10: + return 'Is Fixture?' + elif section == 11: + return 'Sort Order' + else: + return section + 1 diff --git a/summer/templates/RoleList.py b/summer/templates/RoleList.py index e5fdbf9..1110923 100644 --- a/summer/templates/RoleList.py +++ b/summer/templates/RoleList.py @@ -2,7 +2,7 @@ __author__ = 'tanshu' from PyQt5 import uic, QtCore, QtWidgets -file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/RoleList.ui' +file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/ListForm.ui' base, form = uic.loadUiType(file) @@ -10,19 +10,26 @@ class RoleList(base, form): def __init__(self, router, roles, parent=None): super(RoleList, self).__init__() self.setupUi(self) + self.translateUi() self.router = router self.roles = roles self.model = RolesTableModel(roles) - self.tblRoles.setModel(self.model) - self.tblRoles.doubleClicked.connect(self.show_role) + self.tblItems.setModel(self.model) + self.tblItems.doubleClicked.connect(self.show_role) self.btnAddNew.clicked.connect(self.add_new_role) self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) - self.tblRoles.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tblItems.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + + def translateUi(self): + self.setObjectName("RoleList") + _translate = QtCore.QCoreApplication.translate + self.setWindowTitle(_translate("ListForm", "Roles")) + self.btnAddNew.setText(_translate("ListForm", "Add Role")) def update_data(self, data): self.roles = data self.model = RolesTableModel(self.roles) - self.tblRoles.setModel(self.model) + self.tblItems.setModel(self.model) def show_role(self, index): role = self.model.roles[index.row()] diff --git a/summer/templates/SaleWindow.py b/summer/templates/SaleWindow.py new file mode 100644 index 0000000..44d1e29 --- /dev/null +++ b/summer/templates/SaleWindow.py @@ -0,0 +1,83 @@ +from PyQt5 import QtCore, QtWidgets + +from summer.QtDesignerFiles.SaleWindow import SaleWindowQt + + +__author__ = 'tanshu' + + +class SaleWindow(SaleWindowQt, QtWidgets.QMainWindow): + def __init__(self, router, parent=None): + super(SaleWindow, self).__init__() + self.setupUi(self) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) + self.actionButtons = [] + self.mainButtons = [] + self.fillActionBar() + self.fillMainButtons() + + def addButton(self, widget, layout, name, text, connect=None): + button = QtWidgets.QPushButton(widget) + button.setObjectName(name) + button.setText(text) + button.setMinimumSize(75, 75) + button.setMaximumSize(75, 75) + layout.addWidget(button) + if connect is not None: + button.clicked.connect(connect(button)) + return button + + def addActionButton(self, name, text, connect=None): + button = self.addButton(self.abWidget, self.flActions, name, text, connect) + self.actionButtons.append(button) + + def fillActionBar(self): + buttons = [ + ('quantity', 'Quantity'), + ('delete', 'Delete'), + ('discount', 'Discount - F3'), + ('modifier', 'Modifier'), + ('waiter', 'Waiter - F5'), + ('print_kot', 'Print KOT - F12'), + ('print_bill', 'Print Bill - F11'), + ('clear', 'Clear - Esc'), + ('settle_bill', 'Settle Bill'), + ('rate', 'Rate'), + ('move_table', 'Move Table'), + ('void_bill', 'Void Bill'), + ('move_kot', 'Move KOT'), + ('split_bill', 'Split Bill') + ] + for name, text in buttons: + self.addActionButton(name, text, self.hide_button) + + + def addMainButton(self, name, text, connect=None): + button = self.addButton(self.mbWidget, self.flMainButtons, name, text, connect) + self.mainButtons.append(button) + + def hide_button(self, button): + def inner(): + button.hide() + + return inner + + def fillMainButtons(self): + buttons = [ + ('quantity', 'Quantity'), + ('delete', 'Delete'), + ('discount', 'Discount - F3'), + ('modifier', 'Modifier'), + ('waiter', 'Waiter - F5'), + ('print_kot', 'Print KOT - F12'), + ('print_bill', 'Print Bill - F11'), + ('clear', 'Clear - Esc'), + ('settle_bill', 'Settle Bill'), + ('rate', 'Rate'), + ('move_table', 'Move Table'), + ('void_bill', 'Void Bill'), + ('move_kot', 'Move KOT'), + ('split_bill', 'Split Bill') + ] + for name, text in buttons: + self.addMainButton(name, text, self.hide_button) diff --git a/summer/templates/TaxDetail.py b/summer/templates/TaxDetail.py new file mode 100644 index 0000000..c0cbcef --- /dev/null +++ b/summer/templates/TaxDetail.py @@ -0,0 +1,25 @@ +__author__ = 'tanshu' +from PyQt5 import uic, QtCore + + +file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/TaxDetail.ui' +base, form = uic.loadUiType(file) + + +class TaxDetail(base, form): + def __init__(self, router, tax, parent=None): + super(TaxDetail, self).__init__() + self.setupUi(self) + self.router = router + self.tax = tax + self.txtName.setText(tax['Name']) + self.dsbRate.setMinimum(0) + self.dsbRate.setValue(tax['Rate'] * 100) + self.accepted.connect(self.save_tax) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) + + def save_tax(self): + self.tax['Name'] = self.txtName.text() + self.tax['Rate'] = self.dsbRate.value() / 100 + + self.router.save_tax(self.tax) diff --git a/summer/templates/TaxList.py b/summer/templates/TaxList.py new file mode 100644 index 0000000..846d7b0 --- /dev/null +++ b/summer/templates/TaxList.py @@ -0,0 +1,74 @@ +__author__ = 'tanshu' +from PyQt5 import uic, QtCore, QtWidgets + + +file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/ListForm.ui' +base, form = uic.loadUiType(file) + + +class TaxList(base, form): + def __init__(self, router, taxes, parent=None): + super(TaxList, self).__init__() + self.setupUi(self) + self.translateUi() + self.router = router + self.taxes = taxes + self.model = TaxsTableModel(taxes) + self.tblItems.setModel(self.model) + self.tblItems.doubleClicked.connect(self.show_tax) + self.btnAddNew.clicked.connect(self.add_new_tax) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) + self.tblItems.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + + def translateUi(self): + self.setObjectName("TaxList") + _translate = QtCore.QCoreApplication.translate + self.setWindowTitle(_translate("ListForm", "Taxes")) + self.btnAddNew.setText(_translate("ListForm", "Add Tax")) + + def update_data(self, data): + self.taxes = data + self.model = TaxsTableModel(self.taxes) + self.tblItems.setModel(self.model) + + def show_tax(self, index): + tax = self.model.taxes[index.row()] + self.router.show_tax(tax['TaxID']) + + def add_new_tax(self): + self.router.show_tax(None) + + +class TaxsTableModel(QtCore.QAbstractTableModel): + def __init__(self, taxes=None, parent=None): + QtCore.QAbstractTableModel.__init__(self, parent) + self.taxes = taxes if taxes is not None else [] + + def rowCount(self, parent): + return len(self.taxes) + + def columnCount(self, parent): + return 3 + + def data(self, index, role): + if role == QtCore.Qt.DisplayRole: + item = self.taxes[index.row()] + column = index.column() + if column == 0: + return item['Name'] + elif column == 1: + return '{0:,.2f}%'.format(item['Rate'] * 100) + elif column == 2: + return item['IsFixture'] + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + if section == 0: + return 'Name' + elif section == 1: + return 'Rate' + elif section == 2: + return 'Is Fixture?' + else: + return section + 1 diff --git a/summer/templates/UserList.py b/summer/templates/UserList.py index f7fa123..8243068 100644 --- a/summer/templates/UserList.py +++ b/summer/templates/UserList.py @@ -2,7 +2,7 @@ __author__ = 'tanshu' from PyQt5 import uic, QtCore, QtWidgets -file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/UserList.ui' +file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/ListForm.ui' base, form = uic.loadUiType(file) @@ -10,19 +10,26 @@ class UserList(base, form): def __init__(self, router, users, parent=None): super(UserList, self).__init__() self.setupUi(self) + self.translateUi() self.router = router self.users = users self.model = UsersTableModel(users) - self.tblUsers.setModel(self.model) - self.tblUsers.doubleClicked.connect(self.show_user) + self.tblItems.setModel(self.model) + self.tblItems.doubleClicked.connect(self.show_user) self.btnAddNew.clicked.connect(self.add_new_user) self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) - self.tblUsers.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tblItems.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + + def translateUi(self): + self.setObjectName("UserList") + _translate = QtCore.QCoreApplication.translate + self.setWindowTitle(_translate("ListForm", "Users")) + self.btnAddNew.setText(_translate("ListForm", "Add User")) def update_data(self, data): self.users = data self.model = UsersTableModel(self.users) - self.tblUsers.setModel(self.model) + self.tblItems.setModel(self.model) def show_user(self, index): user = self.model.users[index.row()] diff --git a/summer/templates/__init__.py b/summer/templates/__init__.py index 87c77ab..9c739b6 100644 --- a/summer/templates/__init__.py +++ b/summer/templates/__init__.py @@ -5,3 +5,7 @@ from summer.templates.UserDetail import UserDetail from summer.templates.UserList import UserList from summer.templates.ProductGroupList import ProductGroupList from summer.templates.ProductGroupDetail import ProductGroupDetail +from summer.templates.TaxList import TaxList +from summer.templates.TaxDetail import TaxDetail +from summer.templates.ProductList import ProductList +from summer.templates.ProductDetail import ProductDetail \ No newline at end of file diff --git a/summer/views/__init__.py b/summer/views/__init__.py index bfa2d90..d38fbb1 100644 --- a/summer/views/__init__.py +++ b/summer/views/__init__.py @@ -1,8 +1,12 @@ -from summer.templates import RoleDetail, RoleList, UserDetail, UserList, ProductGroupList, ProductGroupDetail +from summer.templates import RoleDetail, RoleList, UserDetail, UserList, ProductGroupList, ProductGroupDetail, TaxList, \ + TaxDetail, ProductList, ProductDetail +from summer.templates.SaleWindow import SaleWindow from summer.views.user import show_list as user_list, user_info, save_user, update_user from summer.views.role import show_list as role_list, role_info, save_role, update_role from summer.views.product_group import show_list as product_group_list, product_group_info, save_product_group, \ update_product_group +from summer.views.tax import show_list as tax_list, tax_info, save_tax, update_tax +from summer.views.product import show_list as product_list, product_info, save_product, update_product __author__ = 'tanshu' @@ -15,6 +19,11 @@ class Router(): self.role_detail_form = None self.product_group_list_form = None self.product_group_detail_form = None + self.tax_list_form = None + self.tax_detail_form = None + self.product_list_form = None + self.product_detail_form = None + self.sales_form = None def show_users(self): users = user_list() @@ -101,3 +110,73 @@ class Router(): save_product_group(product_group) else: update_product_group(product_group) + + + def show_taxes(self): + taxes = tax_list() + if self.tax_list_form is None: + self.tax_list_form = TaxList(self, taxes) + self.tax_list_form.destroyed.connect(self.tax_list_form_destroyed) + self.tax_list_form.show() + + def tax_list_form_destroyed(self): + self.tax_list_form = None + + def show_tax(self, id=None): + tax = tax_info(id) + if self.tax_detail_form is None: + self.tax_detail_form = TaxDetail(self, tax) + self.tax_detail_form.destroyed.connect(self.tax_detail_form_destroyed) + self.tax_detail_form.show() + + def tax_detail_form_destroyed(self): + self.tax_detail_form = None + if self.tax_list_form is not None: + self.tax_list_form.update_data(tax_list()) + + def save_tax(self, tax): + if 'TaxID' not in tax: + save_tax(tax) + else: + update_tax(tax) + + + def show_products(self): + products = product_list() + if self.product_list_form is None: + self.product_list_form = ProductList(self, products) + self.product_list_form.destroyed.connect(self.product_list_form_destroyed) + self.product_list_form.show() + + def product_list_form_destroyed(self): + self.product_list_form = None + + def show_product(self, id=None): + pass + product = product_info(id) + product_group = product_group_list() + taxes = tax_list() + if self.product_detail_form is None: + self.product_detail_form = ProductDetail(self, product, product_group, taxes) + self.product_detail_form.destroyed.connect(self.product_detail_form_destroyed) + self.product_detail_form.show() + + def product_detail_form_destroyed(self): + self.product_detail_form = None + if self.product_list_form is not None: + self.product_list_form.update_data(product_list()) + + def save_product(self, product): + if 'ProductID' not in product: + save_product(product) + else: + update_product(product) + + def show_sales(self): + if self.sales_form is None: + self.sales_form = SaleWindow(self) + self.sales_form.destroyed.connect(self.sales_form_destroyed) + self.sales_form.show() + + def sales_form_destroyed(self): + self.sales_form = None diff --git a/summer/views/product.py b/summer/views/product.py new file mode 100644 index 0000000..64ca5fd --- /dev/null +++ b/summer/views/product.py @@ -0,0 +1,177 @@ +__author__ = 'tanshu' +from decimal import Decimal, InvalidOperation + +from summer.models import session_scope +from summer.models.validation_exception import ValidationError +from summer.models.master import Product + + +def save_product(json): + name = json['Name'].strip() + if name == '': + raise ValidationError('Name cannot be blank') + + units = json['Units'].strip() + product_group = json['ProductGroup'] + if product_group is None: + raise ValidationError('Please choose a product group') + product_group_id = product_group['ProductGroupID'] + + try: + price = Decimal(json['Price']) + if price < 0: + raise ValidationError("Price must be a decimal >= 0") + except (ValueError, InvalidOperation): + raise ValidationError("Price must be a decimal >= 0") + + has_happy_hour = json['HasHappyHour'] + + service_tax = json['ServiceTax'] + if service_tax is None: + raise ValidationError('Please choose a service tax') + service_tax_id = service_tax['TaxID'] + + vat = json['Vat'] + if vat is None: + raise ValidationError('Please choose vat') + vat_id = vat['TaxID'] + + try: + service_charge = Decimal(json['ServiceCharge']) + if service_charge < 0: + raise ValidationError("Service Charge must be a decimal >= 0") + except (ValueError, InvalidOperation): + raise ValidationError("Service Charge must be a decimal >= 0") + + sc_taxable = json['ScTaxable'] + is_active = json['IsActive'] + try: + sort_order = Decimal(json['SortOrder']) + if sort_order < 0: + raise ValidationError("Sort Order must be a decimal >= 0") + except (ValueError, InvalidOperation): + raise ValidationError("Sort Order must be a decimal >= 0") + + item = Product(name, units, product_group_id, price, has_happy_hour, service_tax_id, vat_id, service_charge, + sc_taxable, is_active, sort_order) + with session_scope() as DBSession: + DBSession.add(item) + return product_info(item.id) + + +def update_product(json): + with session_scope() as DBSession: + item = Product.by_id(json['ProductID'], session=DBSession) + if item.is_fixture: + raise ValidationError("{0} is a fixture and cannot be edited or deleted.".format(item.full_name)) + item.name = json['Name'].strip() + item.units = json['Units'].strip() + item.product_group_id = json['ProductGroup']['ProductGroupID'] + try: + item.price = Decimal(json['Price']) + if item.price < 0: + raise ValidationError("Price must be a decimal >= 0") + except (ValueError, InvalidOperation): + raise ValidationError("Price must be a decimal >= 0") + item.has_happy_hour = json['HasHappyHour'] + item.service_tax_id = json['ServiceTax']['TaxID'] + item.vat_id = json['Vat']['TaxID'] + try: + item.service_charge = Decimal(json['ServiceCharge']) + if item.service_charge <= 0: + raise ValidationError("Service Charge must be a decimal > 0") + except (ValueError, InvalidOperation): + raise ValidationError("Service Charge must be a decimal > 0") + item.sc_taxable = json['ScTaxable'] + item.is_active = json['IsActive'] + try: + item.sort_order = Decimal(json['SortOrder']) + if item.sort_order < 0: + raise ValidationError("Sort Order must be a decimal >= 0") + except (ValueError, InvalidOperation): + raise ValidationError("Sort Order must be a decimal >= 0") + return product_info(item.id) + + +def delete_product(id): + with session_scope() as DBSession: + item = Product.by_id(id, session=DBSession) + # TODO: Proper advanced delete checking + # 'Advanced Delete' + advance_delete = True + can_delete, reason = item.can_delete(advance_delete) + + if can_delete: + delete_with_data(item, DBSession) + return product_info(None) + else: + raise ValidationError("Cannot delete product because {0}".format(reason)) + + +def show_list(): + with session_scope() as DBSession: + list = DBSession.query(Product).order_by(Product.sort_order).order_by(Product.name).all() + products = [] + for item in list: + products.append({'Name': item.name, 'Units': item.units, 'ProductGroup': {'Name': item.product_group.name}, + 'Price': item.price, 'HasHappyHour': item.has_happy_hour, + 'ServiceTax': {'Name': item.service_tax.name, 'Rate': item.service_tax.rate}, + 'Vat': {'Name': item.vat.name, 'Rate': item.vat.rate}, + 'ServiceCharge': item.service_charge, 'ScTaxable': item.sc_taxable, + 'IsActive': item.is_active, 'IsFixture': item.is_fixture, 'SortOrder': item.sort_order}) + return products + + +def product_info(id): + with session_scope() as DBSession: + if id is None: + item = {'Name': '', 'Units': '', 'ProductGroup': {'ProductGroupID': None}, 'Price': 0, + 'HasHappyHour': False, 'ServiceTax': {'TaxID': None}, 'Vat': {'TaxID': None}, 'ServiceCharge': 0, + 'ScTaxable': True, 'IsActive': True, 'IsFixture': False, 'SortOrder': 0} + else: + product = Product.by_id(id, session=DBSession) + item = {'ProductID': product.id, 'Name': product.name, 'Units': product.units, + 'ProductGroup': {'ProductGroupID': product.product_group.id, 'Name': product.product_group.name}, + 'Price': product.price, 'HasHappyHour': product.has_happy_hour, + 'ServiceTax': {'TaxID': product.service_tax.id, 'Name': product.service_tax.name, + 'Rate': product.service_tax.rate}, + 'Vat': {'TaxID': product.vat.id, 'Name': product.vat.name, 'Rate': product.vat.rate}, + 'ServiceCharge': product.service_charge, 'ScTaxable': product.sc_taxable, + 'IsActive': product.is_active, 'IsFixture': product.is_fixture, 'SortOrder': product.sort_order} + return item + + +def delete_with_data(product, *, session=None): + pass + # suspense_product = Product.by_id(Product.suspense(), session=session) + # query = Voucher.query().options(joinedload_all(Voucher.inventories, Inventory.product, innerjoin=True)) \ + # .filter(Voucher.inventories.any(Inventory.product_id == product.id)) \ + # .all() + # + # for voucher in query: + # others, sus_inv, prod_inv = False, None, None + # for inventory in voucher.inventories: + # if inventory.product_id == product.id: + # prod_inv = inventory + # elif inventory.product_id == Product.suspense(): + # sus_inv = inventory + # else: + # others = True + # if not others and voucher.type == VoucherType.by_id('Issue'): + # DBSession.delete(voucher) + # else: + # if sus_inv is None: + # prod_inv.product = suspense_product + # prod_inv.quantity = prod_inv.amount + # prod_inv.rate = 1 + # prod_inv.tax = 0 + # prod_inv.discount = 0 + # prod_inv.batch = suspense_batch + # voucher.narration += '\nSuspense \u20B9{0:,.2f} is {1}'.format(prod_inv.amount, product.name) + # else: + # sus_inv.quantity += prod_inv.amount + # DBSession.delete(prod_inv) + # voucher.narration += '\nDeleted \u20B9{0:,.2f} of {1}'.format(prod_inv.amount, product.name) + # for batch in product.batches: + # DBSession.delete(batch) + # DBSession.delete(product) diff --git a/summer/views/tax.py b/summer/views/tax.py new file mode 100644 index 0000000..af9288d --- /dev/null +++ b/summer/views/tax.py @@ -0,0 +1,59 @@ +from decimal import Decimal, InvalidOperation + +from summer.models import session_scope +from summer.models.validation_exception import ValidationError + + +__author__ = 'tanshu' + +from summer.models.master import Tax + + +def save_tax(json): + name = json['Name'].strip() + try: + rate = Decimal(json['Rate']) + if rate < 0: + raise ValidationError("Rate must be a decimal >= 0") + except (ValueError, InvalidOperation): + raise ValidationError("Rate must be a decimal >= 0") + item = Tax(name, rate) + with session_scope() as DBSession: + DBSession.add(item) + return tax_info(item.id) + + +def update_tax(json): + with session_scope() as DBSession: + item = Tax.by_id(json['TaxID'], session=DBSession) + if item.is_fixture: + raise ValidationError("{0} is a fixture and cannot be edited or deleted.".format(item.name)) + item.name = json['Name'].strip() + try: + rate = Decimal(json['Rate']) + if rate < 0: + raise ValidationError("Rate must be a decimal >= 0") + except (ValueError, InvalidOperation): + raise ValidationError("Rate must be a decimal >= 0") + item.rate = rate + return tax_info(item.id) + + +def show_list(): + with session_scope() as DBSession: + list = DBSession.query(Tax).order_by(Tax.name) + taxes = [] + for item in list: + taxes.append({'TaxID': item.id, 'Name': item.name, 'Rate': item.rate, 'IsFixture': item.is_fixture}) + return taxes + + +def tax_info(id): + with session_scope() as DBSession: + if id is None: + item = {'Name': '', 'Rate': 0, 'IsFixture': False} + else: + tax = Tax.by_id(id, session=DBSession) + return {'TaxID': tax.id, 'Name': tax.name, 'Rate': tax.rate, 'IsFixture': tax.is_fixture} + return item +