diff --git a/summer/QtDesignerFiles/ProductGroupDetail.ui b/summer/QtDesignerFiles/ProductGroupDetail.ui new file mode 100644 index 0000000..32ebab9 --- /dev/null +++ b/summer/QtDesignerFiles/ProductGroupDetail.ui @@ -0,0 +1,135 @@ + + + ProductGroupDetails + + + Qt::WindowModal + + + + 0 + 0 + 397 + 231 + + + + Product Group Details + + + true + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Name + + + + + + + + + + Maximum Discount + + + + + + + + + + Header + + + + + + + + + + QLineEdit::Normal + + + + + + + Sort Order + + + + + + + + + + Is Active? + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + txtName + sbMaxDiscount + txtHeader + sbSortOrder + chkIsActive + buttonBox + + + + + buttonBox + accepted() + ProductGroupDetails + accept() + + + 227 + 331 + + + 157 + 274 + + + + + buttonBox + rejected() + ProductGroupDetails + reject() + + + 295 + 337 + + + 286 + 274 + + + + + diff --git a/summer/QtDesignerFiles/ProductGroupList.ui b/summer/QtDesignerFiles/ProductGroupList.ui new file mode 100644 index 0000000..464baa8 --- /dev/null +++ b/summer/QtDesignerFiles/ProductGroupList.ui @@ -0,0 +1,37 @@ + + + ProductGroupList + + + Qt::ApplicationModal + + + + 0 + 0 + 800 + 600 + + + + Product Groups + + + + + + + + + + Add Product Group + + + + + + + + + + diff --git a/summer/models/__init__.py b/summer/models/__init__.py index 28233f6..cf04005 100644 --- a/summer/models/__init__.py +++ b/summer/models/__init__.py @@ -32,7 +32,7 @@ def initialize_sql(engine): def schema_exists(engine): - from models.master import DbSetting + from summer.models.master import DbSetting with engine.connect() as connection: return engine.dialect.has_table(connection, DbSetting.__tablename__) diff --git a/summer/models/master.py b/summer/models/master.py index 7bfb23b..f93c278 100644 --- a/summer/models/master.py +++ b/summer/models/master.py @@ -12,12 +12,11 @@ class Product(Base): __tableagrs__ = (UniqueConstraint('name', 'units')) id = Column('product_id', GUID(), primary_key=True, default=uuid.uuid4) - code = Column('code', Integer, unique=True, nullable=False) name = Column('name', Unicode(255), nullable=False) units = Column('units', Unicode(255), nullable=False) product_group_id = Column('product_group_id', GUID(), ForeignKey('product_groups.product_group_id'), nullable=False) price = Column('price', Numeric, nullable=False) - happy_hour = Column('happy_hour', Boolean, nullable=False) + has_happy_hour = Column('has_happy_hour', Boolean, nullable=False) service_tax_id = Column('service_tax_id', GUID(), ForeignKey('taxes.tax_id'), nullable=False) vat_id = Column('vat_id', GUID(), ForeignKey('taxes.tax_id'), nullable=False) @@ -127,14 +126,10 @@ class ProductGroup(Base): self.is_fixture = is_fixture @classmethod - def list(cls): - return DBSession.query(cls).order_by(cls.name) - - @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() def can_delete(self, advanced_delete): if self.is_fixture: diff --git a/summer/templates/MainWindow.py b/summer/templates/MainWindow.py index 7070dda..4262603 100644 --- a/summer/templates/MainWindow.py +++ b/summer/templates/MainWindow.py @@ -15,6 +15,7 @@ class MainWindow(base, form): self.btnExit.clicked.connect(self.handle_exit) self.btnUsers.clicked.connect(router.show_users) self.btnRoles.clicked.connect(router.show_roles) + self.btnProductGroups.clicked.connect(router.show_product_groups) def handle_exit(self): self.close() diff --git a/summer/templates/ProductGroupDetail.py b/summer/templates/ProductGroupDetail.py new file mode 100644 index 0000000..714554e --- /dev/null +++ b/summer/templates/ProductGroupDetail.py @@ -0,0 +1,33 @@ +__author__ = 'tanshu' +from PyQt5 import uic, QtCore + + +file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/ProductGroupDetail.ui' +base, form = uic.loadUiType(file) + + +class ProductGroupDetail(base, form): + def __init__(self, router, product_group, parent=None): + super(ProductGroupDetail, self).__init__() + self.setupUi(self) + self.router = router + self.product_group = product_group + self.txtName.setText(product_group['Name']) + self.sbMaxDiscount.setMaximum(100) + self.sbMaxDiscount.setMinimum(0) + self.sbMaxDiscount.setValue(product_group['MaxDiscount'] * 100) + self.txtHeader.setText(product_group['Header']) + self.chkIsActive.setCheckState(QtCore.Qt.Checked if product_group['IsActive'] else QtCore.Qt.Unchecked) + self.sbSortOrder.setMinimum(0) + self.sbSortOrder.setValue(product_group['SortOrder']) + self.accepted.connect(self.save_product_group) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) + + def save_product_group(self): + self.product_group['Name'] = self.txtName.text() + self.product_group['MaxDiscount'] = self.sbMaxDiscount.value() / 100 + self.product_group['Header'] = self.txtHeader.text() + self.product_group['IsActive'] = self.chkIsActive.isChecked() + self.product_group['SortOrder'] = self.sbSortOrder.value() + + self.router.save_product_group(self.product_group) diff --git a/summer/templates/ProductGroupList.py b/summer/templates/ProductGroupList.py new file mode 100644 index 0000000..8f9c242 --- /dev/null +++ b/summer/templates/ProductGroupList.py @@ -0,0 +1,79 @@ +__author__ = 'tanshu' +from PyQt5 import uic, QtCore, QtWidgets + + +file = '/home/tanshu/Programming/summer/summer/QtDesignerFiles/ProductGroupList.ui' +base, form = uic.loadUiType(file) + + +class ProductGroupList(base, form): + def __init__(self, router, product_groups, parent=None): + super(ProductGroupList, self).__init__() + self.setupUi(self) + 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.btnAddNew.clicked.connect(self.add_new_product_group) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) + self.tblProductGroups.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + + def update_data(self, data): + self.product_groups = data + self.model = ProductGroupsTableModel(self.product_groups) + self.tblProductGroups.setModel(self.model) + + def show_product_group(self, index): + product_group = self.model.product_groups[index.row()] + self.router.show_product_group(product_group['ProductGroupID']) + + def add_new_product_group(self): + self.router.show_product_group(None) + + +class ProductGroupsTableModel(QtCore.QAbstractTableModel): + def __init__(self, product_groups=None, parent=None): + QtCore.QAbstractTableModel.__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 columnCount(self, parent): + return 6 + + def data(self, index, role): + if role == QtCore.Qt.DisplayRole: + item = self.product_groups[index.row()] + column = index.column() + if column == 0: + return item['Name'] + elif column == 1: + return '{0:,.2f}%'.format(item['MaxDiscount'] * 100) + elif column == 2: + return item['Header'] + elif column == 3: + return item['IsActive'] + elif column == 4: + return item['IsFixture'] + elif column == 5: + 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 'Maximum Discount' + elif section == 2: + return 'Header' + elif section == 3: + return 'Is Active?' + elif section == 4: + return 'Is Fixture?' + elif section == 5: + return 'Sort Order' + else: + return section + 1 diff --git a/summer/templates/__init__.py b/summer/templates/__init__.py new file mode 100644 index 0000000..87c77ab --- /dev/null +++ b/summer/templates/__init__.py @@ -0,0 +1,7 @@ +__author__ = 'tanshu' +from summer.templates.RoleDetail import RoleDetail +from summer.templates.RoleList import RoleList +from summer.templates.UserDetail import UserDetail +from summer.templates.UserList import UserList +from summer.templates.ProductGroupList import ProductGroupList +from summer.templates.ProductGroupDetail import ProductGroupDetail diff --git a/summer/views/__init__.py b/summer/views/__init__.py index 34edfc7..bfa2d90 100644 --- a/summer/views/__init__.py +++ b/summer/views/__init__.py @@ -1,9 +1,8 @@ -from summer.templates.RoleDetail import RoleDetail -from summer.templates.RoleList import RoleList -from summer.templates.UserDetail import UserDetail -from summer.templates.UserList import UserList +from summer.templates import RoleDetail, RoleList, UserDetail, UserList, ProductGroupList, ProductGroupDetail 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 __author__ = 'tanshu' @@ -14,6 +13,8 @@ class Router(): self.user_detail_form = None self.role_list_form = None self.role_detail_form = None + self.product_group_list_form = None + self.product_group_detail_form = None def show_users(self): users = user_list() @@ -71,3 +72,32 @@ class Router(): save_role(role) else: update_role(role) + + + def show_product_groups(self): + product_groups = product_group_list() + if self.product_group_list_form is None: + self.product_group_list_form = ProductGroupList(self, product_groups) + self.product_group_list_form.destroyed.connect(self.product_group_list_form_destroyed) + self.product_group_list_form.show() + + def product_group_list_form_destroyed(self): + self.product_group_list_form = None + + def show_product_group(self, id=None): + product_group = product_group_info(id) + if self.product_group_detail_form is None: + self.product_group_detail_form = ProductGroupDetail(self, product_group) + self.product_group_detail_form.destroyed.connect(self.product_group_detail_form_destroyed) + self.product_group_detail_form.show() + + def product_group_detail_form_destroyed(self): + self.product_group_detail_form = None + if self.product_group_list_form is not None: + self.product_group_list_form.update_data(product_group_list()) + + def save_product_group(self, product_group): + if 'ProductGroupID' not in product_group: + save_product_group(product_group) + else: + update_product_group(product_group) diff --git a/summer/views/product_group.py b/summer/views/product_group.py new file mode 100644 index 0000000..f8785ba --- /dev/null +++ b/summer/views/product_group.py @@ -0,0 +1,81 @@ +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 ProductGroup + + +def save_product_group(json): + name = json['Name'].strip() + try: + max_discount = Decimal(json['MaxDiscount']) + if max_discount < 0 or max_discount > 1: + raise ValidationError("Fraction must be a decimal >= 0 <= 1") + except (ValueError, InvalidOperation): + raise ValidationError("Fraction must be a decimal >= 0 <= 1") + header = json['Header'].strip() + 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") + is_active = json['IsActive'] + item = ProductGroup(name, max_discount, header, sort_order, is_active) + with session_scope() as DBSession: + DBSession.add(item) + return product_group_info(item.id) + + +def update_product_group(json): + with session_scope() as DBSession: + item = ProductGroup.by_id(json['ProductGroupID'], 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: + max_discount = Decimal(json['MaxDiscount']) + if max_discount < 0 or max_discount > 1: + raise ValidationError("Fraction must be a decimal >= 0 <= 1") + except (ValueError, InvalidOperation): + raise ValidationError("Fraction must be a decimal >= 0 <= 1") + item.max_discount = max_discount + item.header = json['Header'].strip() + 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.sort_order = sort_order + item.is_active = json['IsActive'] + return product_group_info(item.id) + + +def show_list(): + with session_scope() as DBSession: + list = DBSession.query(ProductGroup).order_by(ProductGroup.sort_order).order_by(ProductGroup.name) + product_groups = [] + for item in list: + product_groups.append( + {'ProductGroupID': item.id, 'Name': item.name, 'MaxDiscount': item.max_discount, 'Header': item.header, + 'IsActive': item.is_active, 'IsFixture': item.is_fixture, 'SortOrder': item.sort_order}) + return product_groups + + +def product_group_info(id): + with session_scope() as DBSession: + if id is None: + item = {'Name': '', 'MaxDiscount': 1, 'Header': '', 'IsActive': True, 'IsFixture': False, 'SortOrder': 0} + else: + product_group = ProductGroup.by_id(id, session=DBSession) + return {'ProductGroupID': product_group.id, 'Name': product_group.name, + 'MaxDiscount': product_group.max_discount, 'Header': product_group.header, + 'IsActive': product_group.is_active, 'IsFixture': product_group.is_fixture, + 'SortOrder': product_group.sort_order} + return item + diff --git a/summer/views/role.py b/summer/views/role.py index 78f3f6c..b3c3ea9 100644 --- a/summer/views/role.py +++ b/summer/views/role.py @@ -6,7 +6,7 @@ from summer.models.auth import Role, Permission def save_role(json): - role = Role(json['Name']) + role = Role(json['Name'].strip()) with session_scope() as DBSession: DBSession.add(role) add_permissions(role, json['Permissions'], DBSession) @@ -16,7 +16,7 @@ def save_role(json): def update_role(json): with session_scope() as DBSession: role = Role.by_id(json['RoleID'], session=DBSession) - role.name = json['Name'] + role.name = json['Name'].strip() add_permissions(role, json['Permissions'], session=DBSession) return role_info(role.id) diff --git a/summer/views/user.py b/summer/views/user.py index 7e236ce..0634da1 100644 --- a/summer/views/user.py +++ b/summer/views/user.py @@ -4,7 +4,7 @@ from summer.models.validation_exception import ValidationError def save_user(json): - user = User(json['Name'], json['Password'], json['LockedOut']) + user = User(json['Name'].strip(), json['Password'], json['LockedOut']) with session_scope() as DBSession: DBSession.add(user) add_roles(user, json['Roles'], session=DBSession) @@ -15,10 +15,10 @@ def update_user(json): with session_scope() as DBSession: user = User.by_id(json['UserID'], session=DBSession) if user is None: - raise ValidationError('User name / id not found') + raise ValidationError('User not found') has_permission = True if has_permission: - user.name = json['Name'] + user.name = json['Name'].strip() user.locked_out = json['LockedOut'] add_roles(user, json['Roles'], session=DBSession)