commit 1a1fa7881d74a9c0fc33ffb3e4743bf4484de58f Author: tanshu Date: Tue Mar 14 18:26:57 2017 +0530 The Backend for the new narsil Most of the things done. Need to implement a few reports and management. Changed the method of handing the Validation Exception diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f5ac37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.pyc +env +.project +.pydevproject +*/__pycache__/ +.idea/ +*.egg-info/ diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..35a34f3 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version diff --git a/DB/db.cmd b/DB/db.cmd new file mode 100644 index 0000000..c218ee9 --- /dev/null +++ b/DB/db.cmd @@ -0,0 +1,28 @@ +"\Program Files\PostgreSQL\9.5\bin\dropdb.exe" -U postgres petty +"\Program Files\PostgreSQL\9.5\bin\createdb.exe" -U postgres petty +..\env\Scripts\initdb.exe ..\development.ini +..\env\Scripts\fixtures.exe ..\development.ini +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < a-Roles.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < b-Permissions.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < c-RolePermissions.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < d-Users.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < e-UserRoles.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < f-Customers.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < g-FoodTables.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < h-Taxes.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < i-ProductGroups.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < j-Products.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < k-Vouchers.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < l-Voucher-Food-Table.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < m-Kots.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < n-Inventories.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < o-Modifiers.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < p-InventoryModifiers.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < q-Locations.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < r-MachineLocations.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < s-Printers.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < t-PrintLocations.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < u-ProductGroupModifiers.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < v-Reprints.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < w-Settings.sql +"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < x-VoucherSettlements.sql \ No newline at end of file diff --git a/DB/migrate.cmd b/DB/migrate.cmd new file mode 100644 index 0000000..0fc1844 --- /dev/null +++ b/DB/migrate.cmd @@ -0,0 +1,33 @@ +@echo off +call:copyQuery a-Roles "SELECT 'INSERT INTO roles(id, name) VALUES (''' + CAST(GroupID AS Nvarchar(36)) + ''', ''' + Name + ''');' FROM Test.dbo.Auth_Groups;" +call:copyQuery b-Permissions "SELECT 'INSERT INTO permissions(id, name) VALUES (''' + CAST(RoleID AS Nvarchar(36)) + ''', ''' + Name + ''');' FROM Test.dbo.Auth_Roles;" +call:copyQuery c-RolePermissions "SELECT 'INSERT INTO role_permissions(id, permission_id, role_id) VALUES (''' + CAST(RoleGroupID AS Nvarchar(36)) + ''', ''' + CAST(RoleID AS Nvarchar(36)) + ''', ''' + CAST(GroupID AS Nvarchar(36)) + ''');' FROM Test.dbo.Auth_RoleGroups;" +call:copyQuery d-Users "SELECT 'INSERT INTO users(id, name, msr_string, password, locked_out) VALUES (''' + CAST(UserID AS Nvarchar(36)) + ''', ''' + Name + ''', ' + COALESCE('''' + MsrString + '''','null') + ', ''' + Password + ''', ' + CASE WHEN LockedOut = 1 THEN 'true' ELSE 'false' END + ');' FROM Test.dbo.Auth_Users;" +call:copyQuery e-UserRoles "SELECT 'INSERT INTO user_roles(id, user_id, role_id) VALUES (''' + CAST(UserGroupID AS Nvarchar(36)) + ''', ''' + CAST(UserID AS Nvarchar(36)) + ''', ''' + CAST(GroupID AS Nvarchar(36)) + ''');' FROM Test.dbo.Auth_UserGroups;" +call:copyQuery f-Customers "SELECT 'INSERT INTO customers(id, name, address, is_important, phone) VALUES (''' + CAST(CustomerID AS Nvarchar(36)) + ''', ''' + REPLACE(Name, '''', '''''') + ''', ''' + COALESCE(Address, 'null') + ''', ' + CASE WHEN Important = 1 THEN 'true' ELSE 'false' END + ', ''' + COALESCE(Phone, 'null') + ''');' FROM Test.dbo.Customers;" +call:copyQuery g-FoodTables "SELECT 'INSERT INTO food_tables(id, name, is_active, location, sort_order) VALUES (''' + CAST(FoodTableID AS Nvarchar(36)) + ''', ''' + Name + ''', ' + CASE WHEN IsActive = 1 THEN 'true' ELSE 'false' END + ', ''' + COALESCE(Location, 'null') + ''', ' + CAST(SortOrder AS Nvarchar(36)) + ');' FROM Test.dbo.FoodTables;" +call:copyQuery h-Taxes "SELECT 'INSERT INTO taxes(id, name, rate) VALUES ('''+ CAST(TaxID AS Nvarchar(36)) + ''', ' + '''' + REPLACE(Name, '''', '''''') + '''' + ', ' + CAST(Rate AS Nvarchar(36)) + ');' FROM Test.dbo.Taxes;" +call:copyQuery i-ProductGroups "SELECT 'INSERT INTO product_groups(id, name, discount_limit, is_modifier_compulsory, is_active, sort_order, group_type) VALUES (''' + CAST(ProductGroupID AS Nvarchar(36)) + ''', ''' + REPLACE(Name, '''', '''''') + ''', ' + CAST(DiscountLimit AS nvarchar(36)) + ', ' + CASE WHEN IsModifierCompulsory = 1 THEN 'true' ELSE 'false' END + ', ' + CASE WHEN IsActive = 1 THEN 'true' ELSE 'false' END + ', ' + CAST(SortOrder AS Nvarchar(36)) + ', ''' + GroupType + ''');' FROM Test.dbo.ProductGroups;" +call:copyQuery j-Products "SELECT 'INSERT INTO products(id, name, units, product_group_id, vat_id, service_tax_id, service_charge, is_sc_taxable, price, has_happy_hour, is_active, is_not_available, sort_order, quantity) VALUES (''' + CAST(ProductID AS Nvarchar(36)) + ''', ' + '''' + REPLACE(Name, '''', '''''') + '''' + ', ' + '''' + Units + '''' + ', ' + '''' + CAST(ProductGroupID AS Nvarchar(36)) + '''' + ', ' + '''' + CAST(VatID AS Nvarchar(36)) + '''' + ', ' + '''' + CAST(ServiceTaxID AS Nvarchar(36)) + '''' + ', ' + CAST(ServiceCharge AS Nvarchar(36)) + ', ' + CASE WHEN IsScTaxable = 1 THEN 'true' ELSE 'false' END + ', ' + CAST(Price AS Nvarchar(36)) + ', ' + CASE WHEN HasHappyHour = 1 THEN 'true' ELSE 'false' END + ', ' + CASE WHEN IsActive = 1 THEN 'true' ELSE 'false' END + ', ' + CASE WHEN IsNotAvailable = 1 THEN 'true' ELSE 'false' END + ', ' + CAST(SortOrder AS Nvarchar(36)) + ', ' + CAST(Quantity AS Nvarchar(36)) + ');' FROM Test.dbo.Products;" +call:copyQuery k-Vouchers "SELECT 'INSERT INTO vouchers(id, date, pax, user_id, creation_date, last_edit_date, bill_id, food_table_id, customer_id, narration, is_void, void_reason, is_printed, voucher_type, kot_id) VALUES (''' + CAST(VoucherID AS Nvarchar(36)) + ''', ' + '''' + CONVERT(Nvarchar(36), Date, 126) + ''', ' + COALESCE(CAST(Pax AS Nvarchar(36)), 'null') + ', ' + '''' + CAST(UserID AS Nvarchar(36)) + ''', ' + '''' + CONVERT(Nvarchar(36), CreationDate, 126) + ''', ' + '''' + CONVERT(Nvarchar(36), LastEditDate, 126) + ''', ' + COALESCE(CAST(BillID AS Nvarchar(36)), 'null') + ', ' + '''' + CAST(TableID AS Nvarchar(36)) + ''', ' + '''' + CAST(CustomerID AS Nvarchar(36)) + ''', ' + COALESCE('''' + REPLACE(Narration, '''', '''''') + '''', 'null') + ', ' + CASE WHEN Void = 1 THEN 'true' ELSE 'false' END + ', ' + COALESCE('''' + REPLACE(VoidReason, '''', '''''') + '''', 'null') + ', ' + CASE WHEN Printed = 1 THEN 'true' ELSE 'false' END + ', ' + CAST(VoucherType AS Nvarchar(36)) + ', ' + CAST(KotID AS Nvarchar(36)) + ');' FROM Test.dbo.Vouchers;" +call:copyQuery l-Voucher-Food-Table "SELECT 'INSERT INTO voucher_food_table(id, voucher_id, food_table_id, status) VALUES (''' + CAST(newid() AS Nvarchar(36)) + ''', ' + '''' + CAST(VoucherID AS Nvarchar(36)) + ''', ' + '''' + CAST(FoodTableID AS Nvarchar(36)) + ''', ' + '''' + Status + ''');' FROM Test.dbo.FoodTables WHERE VoucherID IS NOT NULL;" +call:copyQuery m-Kots "SELECT 'INSERT INTO kots(id, voucher_id, code, food_table_id, date, user_id) VALUES (''' + CAST(KotID AS Nvarchar(36)) + ''', ' + '''' + CAST(VoucherID AS Nvarchar(36)) + '''' + ', ' + CAST(Code AS Nvarchar(36)) + ', ' + '''' + CAST(TableID AS Nvarchar(36)) + '''' + ', ' + '''' + CONVERT(Nvarchar(36), Date, 126) + ''', ' + '''' + CAST(UserID AS Nvarchar(36)) + '''' + ');' FROM Test.dbo.Kots;" +call:copyQuery n-Inventories "SELECT 'INSERT INTO inventories(id, kot_id, product_id, sort_order, quantity, price, is_happy_hour, service_charge, is_sc_taxable, service_tax_rate, vat_rate, service_tax_id, vat_id, discount) VALUES (''' + CAST(InventoryID AS Nvarchar(36)) + ''', ' + '''' + CAST(KotID AS Nvarchar(36)) + '''' + ', ' + '''' + CAST(ProductID AS Nvarchar(36)) + '''' + ', ' + CAST(SortOrder AS Nvarchar(36)) + ', ' + CAST(Quantity AS Nvarchar(36)) + ', ' + CAST(Price AS Nvarchar(36)) + ', ' + CASE WHEN IsHappyHour = 1 THEN 'true' ELSE 'false' END + ', ' + CAST(ServiceCharge AS Nvarchar(36)) + ', ' + CASE WHEN IsScTaxable = 1 THEN 'true' ELSE 'false' END + ', ' + CAST(ServiceTaxRate AS Nvarchar(36)) + ', ' + CAST(VatRate AS Nvarchar(36)) + ', ' + '''' + CAST(ServiceTaxID AS Nvarchar(36)) + '''' + ', ' + '''' + CAST(VatID AS Nvarchar(36)) + '''' + ', ' + CAST(Discount AS Nvarchar(36)) + ');' FROM Test.dbo.Inventories;" +call:copyQuery o-Modifiers "SELECT 'INSERT INTO modifiers(id, name, show_in_bill, price) VALUES (''' + CAST(ModifierID AS Nvarchar(36)) + ''', ' + '''' + REPLACE(Name, '''', '''''') + '''' + ', ' + CASE WHEN ShowInBill = 1 THEN 'true' ELSE 'false' END + ', ' + CAST(Price AS Nvarchar(36)) + ');' FROM Test.dbo.Modifiers;" +call:copyQuery p-InventoryModifiers "SELECT 'INSERT INTO inventory_modifiers(id, inventory_id, modifier_id, price) VALUES (''' + CAST(InventoryModifierID AS Nvarchar(36)) + ''', ' + '''' + CAST(InventoryID AS Nvarchar(36)) + '''' + ', ' + '''' + CAST(ModifierID AS Nvarchar(36)) + '''' + ', 0);' FROM Test.dbo.InventoryModifiers;" +call:copyQuery q-Locations "SELECT 'INSERT INTO locations(id, name) VALUES (''' + CAST(NewID() AS Nvarchar(36)) + ''', ''' + Location + ''');' FROM Test.dbo.PrintLocations GROUP BY Location;" +call:copyQuery r-MachineLocations "SELECT 'INSERT INTO machine_locations(id, machine_name, location_id) SELECT ''' + CAST(MachineLocationID AS Nvarchar(36)) + ''', ' + '''' + REPLACE(Machine, '''', '''''') + '''' + ', id from locations where name = ' + '''' + REPLACE(Location, '''', '''''') + '''' + ';' FROM Test.dbo.MachineLocations;" +call:copyQuery s-Printers "SELECT 'INSERT INTO printers(id, name, cut_code) VALUES (''' + CAST(NewID() AS Nvarchar(36)) + ''', ''' + Printer + ''', ''' + CutCode + ''');' FROM Test.dbo.PrintLocations GROUP BY Printer, CutCode;" +call:copyQuery t-PrintLocations "SELECT 'INSERT INTO print_locations(id, product_group_id, location_id, printer_id, copies) VALUES (''' + CAST(PrintLocationID AS Nvarchar(36)) + ''', ' + COALESCE('''' + CAST(ProductGroupID AS Nvarchar(36)) + '''','null') + ', ' + '(select id from locations where name = ''' + Location + ''')' + ', ' + '(select id from printers where name = ''' + Printer + ''')' + ', ' + CAST(Copies AS nvarchar(36)) + ');' FROM Test.dbo.PrintLocations;" +call:copyQuery u-ProductGroupModifiers "SELECT 'INSERT INTO product_group_modifiers(id, product_group_id, modifier_id, show_automatically) VALUES (''' + CAST(ProductGroupModifierID AS Nvarchar(36)) + ''', ' + COALESCE('''' + CAST(ProductGroupID AS Nvarchar(36)) + '''','null') + ', ' + COALESCE('''' + CAST(ModifierID AS Nvarchar(36)) + '''','null') + ', ' + CASE WHEN ShowAutomatically = 1 THEN 'true' ELSE 'false' END + ');' FROM Test.dbo.ProductGroupModifiers;" +call:copyQuery v-Reprints "SELECT 'INSERT INTO reprints(id, user_id, date, voucher_id) VALUES (''' + CAST(ReprintID AS Nvarchar(36)) + ''', ' + '''' + CAST(UserID AS Nvarchar(36)) + '''' + ', ' + '''' + CONVERT(Nvarchar(36), Date, 126) + '''' + ', ' + '''' + CAST(VoucherID AS Nvarchar(36)) + '''' + ');' FROM Test.dbo.Reprints;" +call:copyQuery w-Settings "SELECT 'INSERT INTO public.settings(id, name, data) VALUES (''' + CAST(SettingID AS Nvarchar(36)) + ''', ' + '''' + REPLACE(Name, '''', '''''') + '''' + ', ' + '''' + REPLACE(Details, '''', '''''') + '''' + ');' FROM Test.dbo.Settings;" + +call:copyQuery x-VoucherSettlements "SELECT 'INSERT INTO settlements(id, voucher_id, settled, amount) VALUES (''' + CAST(VoucherSettlementID AS Nvarchar(36)) + ''', ' + '''' + CAST(VoucherID AS Nvarchar(36)) + '''' + ', ' + CAST(Settled AS Nvarchar(36)) + ', ' + CAST(Amount AS Nvarchar(36)) + ');' FROM Test.dbo.VoucherSettlements;" +goto:eof + +:copyQuery - here starts my function identified by it's label +echo. +echo. Copying %~1 +bcp "%~2" queryout Data\%~1.sql -c -t',' -S sovereign -U sa -P 123456 +goto:eof \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1be8248 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include barker diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..c775662 --- /dev/null +++ b/README.txt @@ -0,0 +1,41 @@ +# Installation on Ubuntu 14.04 +## Prepare the system +1. Install system-wide pip +**Currently the ensurepip is broken in Python 3.4 / Ubuntu 14.04, therefore we need to install system-wide pip** +```bash +sudo apt-get install python3-pip +``` + +2. Install postgresql +```bash +sudo apt-get install postgresql postgresql-client pgadmin3 +``` +[Full Guide](https://help.ubuntu.com/community/PostgreSQL) + +3. Configure the server and set password +```bash +sudo -u postgres psql postgres +\password postgres +``` + +4. Install components to build psycopg2 +```bash +sudo apt-get install libpq-dev python3-dev +``` + +## Setup the virutal environment +1. Create the virtual environment +**Currently the ensurepip is broken in Python 3.4 / Ubuntu 14.04, therefore --without-pip is needed.** +```bash +pyvenv-3.4 env --without-pip --system-site-packages +``` + +2. Activate the virtual environment +```bash +source env/bin/activate +``` + +3. Install pyramid, sqlachemy, psycopg2 and other dependencies: +```bash +python3 -m pip install pyramid waitress sqlalchemy zope.sqlalchemy psycopg2 +``` diff --git a/barker/__init__.py b/barker/__init__.py new file mode 100644 index 0000000..1bf96f2 --- /dev/null +++ b/barker/__init__.py @@ -0,0 +1,36 @@ +import os + +from pyramid.config import Configurator +from pyramid.session import SignedCookieSessionFactory +from barker.renderers import json_renderer, CSVRenderer +from barker.transactional_view_deriver import transactional_view + + +def main(global_config, **settings): + """ This function returns a Pyramid WSGI application. + """ + if 'sqlalchemy.url' not in settings: + DB_NAME = os.environ['DB_NAME'] + DB_USER = os.environ['DB_USER'] + DB_PASS = os.environ['DB_PASS'] + DB_URI = 'postgresql://{0}:{1}@postgres:5432/{2}'.format( + DB_USER, DB_PASS, DB_NAME + ) + settings['sqlalchemy.url'] = DB_URI + + SECRET_KEY = os.environ.get('SECRET_KEY', settings.get('secret_key', '')) + session_factory = SignedCookieSessionFactory(SECRET_KEY) + + config = Configurator( + settings=settings, + session_factory=session_factory + ) + config.include('.security') + config.include('.models') + config.include('.routes') + config.add_view_deriver(transactional_view) + config.add_renderer(name='json', factory=json_renderer) + config.add_renderer(name='csv', factory=CSVRenderer) + config.scan() + + return config.make_wsgi_app() diff --git a/barker/exceptions.py b/barker/exceptions.py new file mode 100644 index 0000000..33063f2 --- /dev/null +++ b/barker/exceptions.py @@ -0,0 +1,6 @@ +class ValidationFailure(Exception): + def __init__(self, message): + self.message = message + + def __str__(self): + return self.message diff --git a/barker/models/__init__.py b/barker/models/__init__.py new file mode 100644 index 0000000..c3f3d26 --- /dev/null +++ b/barker/models/__init__.py @@ -0,0 +1,105 @@ +from sqlalchemy import engine_from_config +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import configure_mappers +import zope.sqlalchemy + +# import or define all models here to ensure they are attached to the +# Base.metadata prior to any initialization routines +from .voucher import ( + Inventory, + InventoryModifier, + Kot, + Reprint, + Settlement, + Voucher, + VoucherFoodTable, + VoucherType +) +from .master import ( + Customer, + DbSetting, + FoodTable, + Location, + MachineLocation, + Modifier, + Printer, + PrintLocation, + Product, + ProductGroup, + ProductGroupModifier, + SettleOption, + Tax +) +from .auth import ( + Client, + LoginHistory, + Permission, + Role, + role_permissions, + User, + user_roles +) +# run configure_mappers after defining all of the models to ensure +# all relationships can be setup +configure_mappers() + + +def get_engine(settings, prefix='sqlalchemy.'): + return engine_from_config(settings, prefix) + + +def get_session_factory(engine): + factory = sessionmaker(expire_on_commit=False) + factory.configure(bind=engine) + return factory + + +def get_tm_session(session_factory, transaction_manager): + """ + Get a ``sqlalchemy.orm.Session`` instance backed by a transaction. + + This function will hook the session to the transaction manager which + will take care of committing any changes. + + - When using pyramid_tm it will automatically be committed or aborted + depending on whether an exception is raised. + + - When using scripts you should wrap the session in a manager yourself. + For example:: + + import transaction + + engine = get_engine(settings) + session_factory = get_session_factory(engine) + with transaction.manager: + dbsession = get_tm_session(session_factory, transaction.manager) + + """ + dbsession = session_factory() + zope.sqlalchemy.register( + dbsession, transaction_manager=transaction_manager) + return dbsession + + +def includeme(config): + """ + Initialize the model for a Pyramid app. + + Activate this setup using ``config.include('.models')``. + + """ + settings = config.get_settings() + + # use pyramid_tm to hook the transaction lifecycle to the request + config.include('pyramid_tm') + + session_factory = get_session_factory(get_engine(settings)) + config.registry['dbsession_factory'] = session_factory + + # make request.dbsession available for use in Pyramid + config.add_request_method( + # r.tm is the transaction manager used by pyramid_tm + lambda r: get_tm_session(session_factory, r.tm), + 'dbsession', + reify=True + ) diff --git a/barker/models/auth.py b/barker/models/auth.py new file mode 100644 index 0000000..be2ebb7 --- /dev/null +++ b/barker/models/auth.py @@ -0,0 +1,161 @@ +import random +import string +import uuid +from hashlib import md5 +from datetime import datetime + +from sqlalchemy.schema import ForeignKey, Table +from sqlalchemy import Column, Boolean, Unicode, Integer, DateTime, UniqueConstraint +from sqlalchemy.orm import synonym, relationship + +from barker.models.guidtype import GUID +from .meta import Base + + +def encrypt(val): + return md5(val.encode('utf-8') + "v2".encode('utf-8')).hexdigest() + + +class Client(Base): + __tablename__ = 'clients' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + code = Column('code', Integer, unique=True, nullable=False) + name = Column('name', Unicode(255), unique=True, nullable=False) + enabled = Column('enabled', Boolean, nullable=False) + otp = Column('otp', Integer) + creation_date = Column('creation_date', DateTime(timezone=True), nullable=False) + + login_history = relationship('LoginHistory', backref='client') + + def __init__(self, code=None, name=None, enabled=False, otp=None, creation_date=None, id=None): + self.code = code + self.name = name + self.enabled = enabled + self.otp = otp + self.creation_date = datetime.utcnow() if creation_date is None else creation_date + self.id = id + + @classmethod + def by_code(cls, code, dbsession): + if code is None: + return None + if not isinstance(code, int): + code = int(code) + return dbsession.query(cls).filter(cls.code == code).first() + + @classmethod + def create(cls, dbsession): + client_code = random.randint(1000, 9999) + otp = random.randint(1000, 9999) + name = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(6)) + client = Client(client_code, name, False, otp) + dbsession.add(client) + return client + + +user_roles = Table( + 'user_roles', Base.metadata, + Column('id', GUID(), primary_key=True, default=uuid.uuid4), + Column('user_id', GUID(), ForeignKey('users.id')), + Column('role_id', GUID(), ForeignKey('roles.id')) +) + +role_permissions = Table( + 'role_permissions', Base.metadata, + Column('id', GUID(), primary_key=True, default=uuid.uuid4), + Column('permission_id', GUID(), ForeignKey('permissions.id')), + Column('role_id', GUID(), ForeignKey('roles.id')) +) + + +class User(Base): + __tablename__ = 'users' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), unique=True, nullable=False) + _password = Column('password', Unicode(60), nullable=False) + locked_out = Column('locked_out', Boolean, nullable=False) + msr_string = Column('msr_string', Unicode(255), unique=True) + + roles = relationship("Role", secondary=user_roles, order_by="Role.name") + login_history = relationship('LoginHistory', backref='user') + + def _get_password(self): + return self._password + + def _set_password(self, password): + self._password = encrypt(password) + + password = property(_get_password, _set_password) + password = synonym('_password', descriptor=password) + + @property + def __name__(self): + return self.name + + def __init__(self, name=None, password=None, locked_out=None, msr_string=None, id=None): + self.name = name + self.password = password + self.locked_out = locked_out + self.msr_string = msr_string + self.id = id + + @classmethod + def auth(cls, name, password, dbsession): + if password is None: + return False, None + user = dbsession.query(User).filter(User.name.ilike(name)).first() + if not user: + return False, None + if user.password != encrypt(password) or user.locked_out: + return False, None + else: + return True, user + + @classmethod + def auth_msr(cls, msr, dbsession): + user = dbsession.query(User).filter(User.msr_string == msr).first() + if not user: + return False, None + else: + return True, user + + +class LoginHistory(Base): + __tablename__ = 'login_history' + __table_args__ = (UniqueConstraint('user_id', 'client_id', 'date'),) + id = Column('login_history_id', GUID(), primary_key=True, default=uuid.uuid4) + user_id = Column('user_id', GUID(), ForeignKey('users.id'), nullable=False) + client_id = Column('client_id', GUID(), ForeignKey('clients.id'), nullable=False) + date = Column('date', DateTime(timezone=True), nullable=False) + + def __init__(self, user_id=None, client_id=None, date=None, id=None): + self.user_id = user_id + self.client_id = client_id + self.date = datetime.utcnow() if date is None else date + self.id = id + + +class Role(Base): + __tablename__ = 'roles' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), unique=True) + + def __init__(self, name=None, id=None): + self.name = name + self.id = id + + +class Permission(Base): + __tablename__ = 'permissions' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), unique=True) + + roles = relationship("Role", secondary=role_permissions, backref="permissions") + + def __init__(self, name=None, id=None): + self.name = name + self.id = id diff --git a/barker/models/guidtype.py b/barker/models/guidtype.py new file mode 100644 index 0000000..73aebbc --- /dev/null +++ b/barker/models/guidtype.py @@ -0,0 +1,57 @@ +import uuid +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.dialects.sqlite import BLOB +from sqlalchemy.types import TypeDecorator, CHAR, Binary + + +class GUID(TypeDecorator): + """Platform-independent GUID type. + + Uses Postgresql's UUID type, otherwise uses + CHAR(32), storing as stringified hex values. + + """ + impl = Binary + + # if dialect.value == 'postgresql': + # impl = CHAR + # elif dialect.value == 'mysql': + # impl = MSBinary + # elif dialect.valie == 'sqlite': + # impl = Binary + # else: + # impl = Binary + + def load_dialect_impl(self, dialect): + if dialect.name == 'postgresql': + return dialect.type_descriptor(UUID()) + elif dialect.name == 'sqlite': + return dialect.type_descriptor(BLOB()) + else: + return dialect.type_descriptor(CHAR(32)) + + def process_bind_param(self, value, dialect): + if value is None: + return None + elif dialect.name == 'postgresql': + return str(value) + elif not isinstance(value, uuid.UUID): + raise ValueError('value %s is not a valid uuid.UUID' % value) + else: + return value.bytes + # if not isinstance(value, uuid.UUID): + # return "%.32x" % uuid.UUID(value) + # else: + # # hexstring + # return "%.32x" % value + + def process_result_value(self, value, dialect=None): + if value is None: + return None + elif isinstance(value, bytes): + return uuid.UUID(bytes=value) + else: + return uuid.UUID(value) + + def is_mutable(self): + return False diff --git a/barker/models/master.py b/barker/models/master.py new file mode 100644 index 0000000..60275c3 --- /dev/null +++ b/barker/models/master.py @@ -0,0 +1,318 @@ +import uuid + +from sqlalchemy import ( + UniqueConstraint, + Column, + Unicode, + Numeric, + Boolean, + ForeignKey, + Integer, case, JSON) +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship +from .meta import Base +from barker.models.guidtype import GUID + + +class Customer(Base): + __tablename__ = 'customers' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), nullable=False, unique=True) + phone = Column('phone', Unicode(255), nullable=False) + address = Column('address', Unicode(255), nullable=False) + is_important = Column('is_important', Boolean, nullable=False) + + @property + def __name__(self): + return self.name + + def __init__(self, name=None, phone=None, address=None, is_important=None, id=None): + self.name = name + self.phone = phone + self.address = address + self.is_important = is_important + self.id = id + + @classmethod + def cash(cls): + return uuid.UUID('2c716f4b-0736-429a-ad51-610d7c47cb5e') + +class FoodTable(Base): + __tablename__ = 'food_tables' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), nullable=False, unique=True) + location = Column('location', Unicode(255), nullable=False) + + is_active = Column('is_active', Boolean, nullable=False) + sort_order = Column('sort_order', Numeric, nullable=False) + + @property + def __name__(self): + return self.name + + def __init__(self, name=None, phone=None, address=None, is_important=None, id=None): + self.name = name + self.phone = phone + self.address = address + self.is_important = is_important + self.id = id + + +class Tax(Base): + __tablename__ = 'taxes' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), nullable=False, unique=True) + rate = Column('rate', Numeric, nullable=False) + + def __init__(self, name=None, rate=None, id=None): + self.name = name + self.rate = rate + self.id = id + + +class ProductGroup(Base): + __tablename__ = 'product_groups' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), nullable=False, unique=True) + discount_limit = Column('discount_limit', Numeric, nullable=False) + is_modifier_compulsory = Column('is_modifier_compulsory', Boolean, nullable=False) + group_type = Column('group_type', Unicode(255), nullable=False) + + is_active = Column('is_active', Boolean, nullable=False) + sort_order = Column('sort_order', Numeric, nullable=False) + + def __init__(self, name, discount_limit, is_modifier_compulsory, group_type, is_active, sort_order, id=None): + self.name = name + self.discount_limit = discount_limit + self.is_modifier_compulsory = is_modifier_compulsory + self.group_type = group_type + self.is_active = is_active + self.sort_order = sort_order + self.id = id + + +class Product(Base): + __tablename__ = 'products' + __table_args__ = (UniqueConstraint('name', 'units'),) + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + 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.id'), nullable=False) + vat_id = Column('vat_id', GUID(), ForeignKey('taxes.id'), nullable=False) + service_tax_id = Column('service_tax_id', GUID(), ForeignKey('taxes.id'), nullable=False) + service_charge = Column('service_charge', Numeric, nullable=False) + is_sc_taxable = Column('is_sc_taxable', Boolean, nullable=False) + price = Column('price', Numeric, nullable=False) + has_happy_hour = Column('has_happy_hour', Boolean, nullable=False) + is_not_available = Column('is_not_available', Boolean, nullable=False) + quantity = Column('quantity', Numeric, nullable=False) + + is_active = Column('is_active', Boolean, nullable=False) + sort_order = Column('sort_order', Numeric, nullable=False) + + product_group = relationship('ProductGroup', backref='products') + vat = relationship('Tax', foreign_keys=vat_id) + service_tax = relationship('Tax', foreign_keys=service_tax_id) + + def __init__(self, name=None, units=None, product_group_id=None, vat_id=None, service_tax_id=None, + service_charge=None, is_sc_taxable=None, price=None, has_happy_hour=None, is_not_available=None, + quantity=None, is_active=None, sort_order=None, id=None): + self.name = name + self.units = units + self.product_group_id = product_group_id + self.vat_id = vat_id + self.service_tax_id = service_tax_id + self.service_charge = service_charge + self.is_sc_taxable = is_sc_taxable + self.price = price + self.has_happy_hour = has_happy_hour + self.is_not_available = is_not_available + self.quantity = quantity + self.is_active = is_active + self.sort_order = sort_order + self.id = id + + @hybrid_property + def full_name(self): + return "{0} ({1})".format(self.name, self.units) + + @full_name.expression + def full_name(cls): + return cls.name + case([(cls.units != '', ' (' + cls.units + ')')], else_='') + + def can_delete(self, advanced_delete): + if self.is_fixture: + return False, "{0} is a fixture and cannot be edited or deleted.".format(self.name) + if self.is_active: + return False, 'Product is active' + if len(self.inventories) > 0 and not advanced_delete: + return False, 'Product has entries' + return True, '' + + +class Modifier(Base): + __tablename__ = 'modifiers' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), nullable=False, unique=True) + show_in_bill = Column('show_in_bill', Boolean, nullable=False) + price = Column('price', Numeric, nullable=False) + + def __init__(self, name=None, show_in_bill=None, price=None, id=None): + self.id = id + self.name = name + self.show_in_bill = show_in_bill + self.price = price + + +class ProductGroupModifier(Base): + __tablename__ = 'product_group_modifiers' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + product_group_id = Column('product_group_id', GUID(), ForeignKey('product_groups.id')) + modifier_id = Column('modifier_id', GUID(), ForeignKey('modifiers.id'), nullable=False) + show_automatically = Column('show_automatically', Boolean, nullable=False) + + product_group = relationship('ProductGroup', backref='modifiers') + modifier = relationship('Modifier', backref='product_groups') + + def __init__(self, product_group_id=None, modifier_id=None, show_automatically=None, id=None): + self.id = id + self.product_group_id = product_group_id + self.modifier_id = modifier_id + self.show_automatically = show_automatically + + +class DbSetting(Base): + __tablename__ = 'settings' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), unique=True, nullable=False) + data = Column('data', JSON) + + def __init__(self, id=None, name=None, data=None): + self.id = id + self.name = name + self.data = data + + +class Location(Base): + __tablename__ = 'locations' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), unique=True, nullable=False) + + def __init__(self, id=None, name=None): + self.id = id + self.name = name + + +class MachineLocation(Base): + __tablename__ = 'machine_locations' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + machine_name = Column('machine_name', Unicode(255), unique=True, nullable=False) + location_id = Column('location_id', GUID(), ForeignKey('locations.id'), nullable=False) + + location = relationship('Location', backref='machines') + + def __init__(self, machine_name=None, location_id=None, id=None): + self.machine_name = machine_name + self.location_id = location_id + self.id = id + + +class Printer(Base): + __tablename__ = 'printers' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + name = Column('name', Unicode(255), unique=True, nullable=False) + cut_code = Column('cut_code', Unicode(255), nullable=False) + + def __init__(self, id=None, name=None, cut_code=None): + self.id = id + self.name = name + self.cut_code = cut_code + + +class PrintLocation(Base): + __tablename__ = 'print_locations' + __table_args__ = (UniqueConstraint('product_group_id', 'location_id'),) + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + product_group_id = Column('product_group_id', GUID(), ForeignKey('product_groups.id')) + location_id = Column('location_id', GUID(), ForeignKey('locations.id'), nullable=False) + printer_id = Column('printer_id', GUID(), ForeignKey('printers.id'), nullable=False) + copies = Column('copies', Numeric, nullable=False) + + product_group = relationship('ProductGroup', backref='print_locations') + location = relationship('Location', backref='print_locations') + printer = relationship('Printer', backref='print_locations') + + def __init__(self, product_group_id, location_id, printer_id, copies): + self.product_group_id = product_group_id + self.location_id = location_id + self.printer_id = printer_id + self.copies = copies + + +class SettleOption(Base): + __tablename__ = 'settle_options' + + id = Column('id', Integer, primary_key=True) + name = Column('name', Unicode(255), unique=True, nullable=False) + show_in_choices = Column('show_in_choices', Boolean, nullable=False) + group = Column('display_group', Integer, nullable=False) + print = Column('print', Boolean, nullable=False) + + def __init__(self, name, show_in_choices, group, print, id): + self.id = id + self.name = name + self.show_in_choices = show_in_choices + self.group = group + self.print = print + + @classmethod + def UNSETTLED(cls): + return 1 + + @classmethod + def CASH(cls): + return 2 + + @classmethod + def CREDIT_CARD(cls): + return 3 + + @classmethod + def NO_CHARGE(cls): + return 4 + + @classmethod + def BILL_TO_COMPANY(cls): + return 5 + + @classmethod + def TIP(cls): + return 6 + + @classmethod + def ROUND_OFF(cls): + return 7 + + @classmethod + def AMOUNT(cls): + return 8 + + @classmethod + def VOID(cls): + return 9 + + @classmethod + def STAFF(cls): + return 10 diff --git a/barker/models/meta.py b/barker/models/meta.py new file mode 100644 index 0000000..0682247 --- /dev/null +++ b/barker/models/meta.py @@ -0,0 +1,16 @@ +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.schema import MetaData + +# Recommended naming convention used by Alembic, as various different database +# providers will autogenerate vastly different names making migrations more +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html +NAMING_CONVENTION = { + "ix": 'ix_%(column_0_label)s', + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" +} + +metadata = MetaData(naming_convention=NAMING_CONVENTION) +Base = declarative_base(metadata=metadata) diff --git a/barker/models/tzinfoutc.py b/barker/models/tzinfoutc.py new file mode 100644 index 0000000..ae86317 --- /dev/null +++ b/barker/models/tzinfoutc.py @@ -0,0 +1,45 @@ +from datetime import timedelta, tzinfo, datetime + +ZERO = timedelta(0) +HOUR = timedelta(hours=1) + + +# A UTC class. + +class UTC(tzinfo): + """UTC""" + + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return ZERO + + +utc = UTC() + + +def get_age(old_date, now=None): + def is_naive(date): + return date.tzinfo is None or date.tzinfo.utcoffset(date) is None + + if now is None: + now = datetime.utcnow().replace(tzinfo=utc) + if is_naive(old_date) == is_naive(now): + pass + elif is_naive(old_date): + old_date = old_date.replace(tzinfo=utc) + else: + now = now.replace(tzinfo=utc) + + delta = now - old_date + if delta.days > 0: + return '{0} days'.format(delta.days) + if delta.seconds > 3600: + return '{0} hours'.format(delta.seconds // 3600) + if delta.seconds > 60: + return '{0} minutes'.format(delta.seconds // 60) + return '{0} seconds'.format(delta.seconds) diff --git a/barker/models/voucher.py b/barker/models/voucher.py new file mode 100644 index 0000000..6d51abc --- /dev/null +++ b/barker/models/voucher.py @@ -0,0 +1,272 @@ +from datetime import datetime +from enum import IntEnum +import uuid +from decimal import Decimal + +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy import Column, Integer, Boolean, Unicode, DateTime, Numeric, ForeignKey, UniqueConstraint, func, case +from sqlalchemy.orm import relationship, synonym, backref +from barker.models.guidtype import GUID +from .meta import Base + + +class VoucherType(IntEnum): + REGULAR_BILL = 1 + NO_CHARGE = 2 + TAKE_AWAY = 3 + STAFF = 4 + + +class VoucherFoodTable(Base): + __tablename__ = 'voucher_food_table' + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + voucher_id = Column('voucher_id', GUID(), ForeignKey('vouchers.id'), nullable=False, unique=True) + food_table_id = Column('food_table_id', GUID(), ForeignKey('food_tables.id'), nullable=False, unique=True) + status = Column('status', Unicode(255), nullable=False) + + food_table = relationship('FoodTable', backref=backref('status', uselist=False)) + voucher = relationship('Voucher', backref=backref('status', uselist=False)) + + def __init__(self, voucher_id, food_table_id, status): + self.voucher_id = voucher_id + self.food_table_id = food_table_id + self.status = status + + +class InventoryModifier(Base): + __tablename__ = 'inventory_modifiers' + __table_args__ = (UniqueConstraint('inventory_id', 'modifier_id'),) + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + inventory_id = Column('inventory_id', GUID(), ForeignKey('inventories.id'), nullable=False) + modifier_id = Column('modifier_id', GUID(), ForeignKey('modifiers.id'), nullable=False) + price = Column('price', Numeric, nullable=False) + + inventory = relationship('Inventory', backref='modifiers') + modifier = relationship('Modifier') + + def __init__(self, inventory_id, modifier_id, price): + self.inventory_id = inventory_id + self.modifier_id = modifier_id + self.price = price + + +class Voucher(Base): + __tablename__ = 'vouchers' + __table_args__ = (UniqueConstraint('bill_id', 'voucher_type'),) + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + date = Column('date', DateTime, nullable=False, index=True) + pax = Column('pax', Numeric, nullable=False) + bill_id = Column('bill_id', Numeric) + kot_id = Column('kot_id', Numeric, nullable=False, unique=True) + creation_date = Column('creation_date', DateTime(timezone=True), nullable=False) + last_edit_date = Column('last_edit_date', DateTime(timezone=True), nullable=False) + food_table_id = Column('food_table_id', GUID(), ForeignKey('food_tables.id'), nullable=False) + customer_id = Column('customer_id', GUID(), ForeignKey('customers.id'), nullable=False) + narration = Column('narration', Unicode(1000), nullable=False) + is_void = Column('is_void', Boolean, nullable=False) + void_reason = Column('void_reason', Unicode(255)) + is_printed = Column('is_printed', Boolean, nullable=False) + voucher_type = Column('voucher_type', Integer, nullable=False) + user_id = Column('user_id', GUID(), ForeignKey('users.id'), nullable=False) + + user = relationship('User', backref='vouchers') + food_table = relationship('FoodTable', backref='vouchers') + customer = relationship('Customer', backref='vouchers') + + kots = relationship('Kot', backref='voucher', cascade="delete, delete-orphan", cascade_backrefs=False) + settlements = relationship('Settlement', backref='voucher', cascade="delete, delete-orphan", cascade_backrefs=False) + reprints = relationship('Reprint', backref='voucher', cascade="delete, delete-orphan", cascade_backrefs=False) + + @property + def __name__(self): + return self.name + + def __init__(self, pax, food_table_id, customer_id, is_printed, voucher_type, user_id, dbsession): + now = datetime.now() + self.date = now + self.pax = pax + if is_printed: + type = [1, 3] if voucher_type in [1, 3] else [voucher_type] + self.bill_id = dbsession.query( + func.coalesce(func.max(Voucher.bill_id), 0) + 1 + ).filter( + Voucher.voucher_type.in_(type) + ).scalar() + self.kot_id = dbsession.query( + func.coalesce(func.max(Voucher.kot_id), 0) + 1 + ).scalar() + + self.creation_date = now + self.last_edit_date = now + self.food_table_id = food_table_id + self.customer_id = customer_id + self.narration = '' + self.is_void = False + self.void_reason = None + self.is_printed = is_printed + self.voucher_type = voucher_type + self.user_id = user_id + + @property + def full_bill_id(self): + if self.bill_id is None: + return 'K-' + str(self.kot_id) + if self.voucher_type == VoucherType.NO_CHARGE.value: + return "NC-" + str(self.bill_id) + if self.voucher_type == VoucherType.STAFF.value: + return "ST-" + str(self.bill_id) + if self.voucher_type in [VoucherType.TAKE_AWAY.value, VoucherType.REGULAR_BILL.value]: + return str(self.bill_id // 10000) + "-" + str(self.bill_id % 10000) + else: + raise Exception + + +class Kot(Base): + __tablename__ = 'kots' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + voucher_id = Column('voucher_id', GUID(), ForeignKey('vouchers.id'), nullable=False, index=True) + code = Column('code', Numeric, nullable=False, unique=True) + food_table_id = Column('food_table_id', GUID(), ForeignKey('food_tables.id'), nullable=False) + date = Column('date', DateTime, nullable=False, index=True) + user_id = Column('user_id', GUID(), ForeignKey('users.id'), nullable=False) + + user = relationship('User', backref='kots') + food_table = relationship('FoodTable', backref='kots') + + def __init__(self, voucher_id=None, food_table_id=None, date=None, user_id=None, id=None, dbsession=None): + self.id = id + self.voucher_id = voucher_id + self.code = dbsession.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar() + self.food_table_id = food_table_id + self.date = date + self.user_id = user_id + + +class Settlement(Base): + __tablename__ = 'settlements' + __table_args__ = (UniqueConstraint('voucher_id', 'settled'),) + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + voucher_id = Column('voucher_id', GUID(), ForeignKey('vouchers.id'), nullable=False, index=True) + settled = Column('settled', ForeignKey('settle_options.id'), nullable=False) + amount = Column('amount', Numeric, nullable=False) + + settle_option = relationship('SettleOption') + + def __init__(self, voucher_id=None, settled=None, amount=None, id=None): + self.id = id + self.voucher_id = voucher_id + self.settled = settled + self.amount = amount + + +class Reprint(Base): + __tablename__ = 'reprints' + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + date = Column('date', DateTime, nullable=False, index=True) + voucher_id = Column('voucher_id', GUID(), ForeignKey('vouchers.id'), nullable=False, index=True) + user_id = Column('user_id', GUID(), ForeignKey('users.id'), nullable=False) + + user = relationship('User', backref='reprints') + + def __init__(self, voucher_id=None, user_id=None, id=None): + self.id = id + self.date = datetime.now() + self.voucher_id = voucher_id + self.user_id = user_id + + +class Inventory(Base): + __tablename__ = 'inventories' + __table_args__ = (UniqueConstraint('kot_id', 'product_id', 'is_happy_hour', 'price'),) + + id = Column('id', GUID(), primary_key=True, default=uuid.uuid4) + kot_id = Column('kot_id', GUID(), ForeignKey('kots.id'), nullable=False, index=True) + product_id = Column('product_id', GUID(), ForeignKey('products.id'), nullable=False) + quantity = Column('quantity', Numeric) + price = Column('price', Numeric) + is_happy_hour = Column('is_happy_hour', Boolean, nullable=False) + service_charge = Column('service_charge', Numeric, nullable=False) + is_sc_taxable = Column('is_sc_taxable', Boolean, nullable=False) + service_tax_rate = Column('service_tax_rate', Numeric) + service_tax_id = Column('service_tax_id', GUID(), ForeignKey('taxes.id'), nullable=False) + vat_rate = Column('vat_rate', Numeric) + vat_id = Column('vat_id', GUID(), ForeignKey('taxes.id'), nullable=False) + discount = Column('discount', Numeric) + sort_order = Column('sort_order', Numeric, nullable=False) + + kot = relationship('Kot', backref='inventories') + vat = relationship('Tax', foreign_keys=vat_id) + service_tax = relationship('Tax', foreign_keys=service_tax_id) + product = relationship('Product', backref='inventories') + + def __init__(self, kot_id, product_id, quantity, price, discount, is_hh, sc, is_sc_taxable, st_id, st_rate, vat_id, + vat_rate, sort_order): + self.kot_id = kot_id + self.product_id = product_id + self.quantity = quantity + self.price = price + self.discount = discount + self.is_happy_hour = is_hh + self.service_charge = sc + self.is_sc_taxable = is_sc_taxable + self.service_tax_id = st_id + self.service_tax_rate = st_rate + self.vat_id = vat_id + self.vat_rate = vat_rate + self.sort_order = sort_order + + @hybrid_property + def effective_price(self): + return 0 if self.is_happy_hour == True else self.price + + @effective_price.expression + def effective_price(cls): + return case( + [(cls.is_happy_hour == True, 0)], + else_=cls.price + ) + + @hybrid_property + def net(self): + return self.effective_price * self.quantity * (1 - self.discount) + + @hybrid_property + def net_taxable(self): + return self.net * (1 + self.service_charge if self.is_sc_taxable else 0) + + @net_taxable.expression + def net_taxable(cls): + return cls.net * (1 + case( + [(cls.is_sc_taxable == True, cls.service_charge)], + else_=0 + )) + + @hybrid_property + def sc_amount(self): + return self.net * self.service_charge + + @hybrid_property + def st_amount(self): + return self.net_taxable * self.service_tax_rate + + @hybrid_property + def vat_amount(self): + return self.net_taxable * self.vat_rate + + @hybrid_property + def amount(self): + if self.is_sc_taxable: + return Decimal(self.net * (1 + self.service_charge) * (1 + self.service_tax_rate + self.vat_rate)) + return Decimal(self.net * (1 + self.service_charge + self.service_tax_rate + self.vat_rate)) + + @amount.expression + def amount(cls): + return case([ + (cls.is_sc_taxable == True, cls.net * (1 + cls.service_charge) * (1 + cls.service_tax_rate + cls.vat_rate)) + ], + else_=cls.net * (1 + cls.service_charge + cls.service_tax_rate + cls.vat_rate)) diff --git a/barker/renderers.py b/barker/renderers.py new file mode 100644 index 0000000..3491e54 --- /dev/null +++ b/barker/renderers.py @@ -0,0 +1,57 @@ +import csv +from decimal import Decimal +from io import StringIO +import uuid + +from pyramid.renderers import JSON + + + + +class CSVRenderer(object): + def __init__(self, info): + pass + + def __call__(self, value, system): + csv_data = StringIO() + writer = csv.writer(csv_data, delimiter=',', quoting=csv.QUOTE_MINIMAL) + + if 'header' in value: + writer.writerow(value['header']) + if 'rows' in value: + writer.writerows(value['rows']) + if 'footer' in value: + writer.writerows(value['footer']) + + request = system.get('request') + if request is not None: + response = request.response + ct = response.content_type + if ct == response.default_content_type: + response.content_type = 'text/csv' + filename = value['filename'] if 'filename' in value else 'report.csv' + response.content_disposition = 'attachment;filename="{0}"'.format(filename) + return csv_data.getvalue() + + +json_renderer = JSON() + + +class DecimalAsFloatHack(float): + def __init__(self, d): + self.d = d + + def __repr__(self): + return str(self.d) + + +def decimal_adaptor(obj, request): + return DecimalAsFloatHack(obj) + + +def uuid_adaptor(obj, request): + return str(obj) + + +json_renderer.add_adapter(Decimal, decimal_adaptor) +json_renderer.add_adapter(uuid.UUID, uuid_adaptor) diff --git a/barker/routes.py b/barker/routes.py new file mode 100644 index 0000000..6526e1e --- /dev/null +++ b/barker/routes.py @@ -0,0 +1,90 @@ +def includeme(config): + config.add_route('msr', '/Msr.json') + config.add_route('login', '/Login.json') + config.add_route('logout', '/Logout.json') + + config.add_route('active_cashiers', '/Cashiers.json') + config.add_route('checkout', '/Checkout/{id}.json') + # config.add_route('customer_id', '/Customer/{id}.json') + + config.add_route('beer_consumption', '/BeerConsumption.json') + config.add_route('bill_details', '/BillDetails.json') + + config.add_route('customer', '/Customer.json') + config.add_route('customer_list', '/Customers.json') + config.add_route('customer_id', '/Customer/{id}.json') + + config.add_route('discount_report', '/DiscountReport.json') + + config.add_route('food_table', '/FoodTable.json') + config.add_route('food_table_list', '/FoodTables.json') + config.add_route('food_table_id', '/FoodTable/{id}.json') + + config.add_route('location_list', '/Locations.json') + + config.add_route('machine_location', '/MachineLocation.json') + config.add_route('machine_location_list', '/MachineLocations.json') + config.add_route('machine_location_id', '/MachineLocation/{id}.json') + + config.add_route('merge_kot', '/MergeKot.json') + config.add_route('merge_table', '/MergeTable.json') + + config.add_route('modifier', '/Modifier.json') + config.add_route('modifier_list', '/Modifiers.json') + config.add_route('modifier_id', '/Modifier/{id}.json') + + config.add_route('move_kot', '/MoveKot.json') + config.add_route('move_table', '/MoveTable.json') + + config.add_route('permission_list', '/Permissions.json') + + config.add_route('print_location', '/PrintLocation.json') + config.add_route('print_location_list', '/PrintLocations.json') + config.add_route('print_location_id', '/PrintLocation/{id}.json') + + config.add_route('product', '/Product.json') + config.add_route('product_list', '/Products.json') + config.add_route('product_id', '/Product/{id}.json') + + config.add_route('product_group', '/ProductGroup.json') + config.add_route('product_group_list', '/ProductGroups.json') + config.add_route('product_group_id', '/ProductGroup/{id}.json') + + config.add_route('product_group_type_list', '/ProductGroupTypes.json') + + config.add_route('quantity_sold', '/QuantitySold.json') + + config.add_route('reprint', '/Reprint.json') + config.add_route('reprint_report', '/ReprintReport.json') + + config.add_route('role', '/Role.json') + config.add_route('role_list', '/Roles.json') + config.add_route('role_id', '/Role/{id}.json') + + config.add_route('setting', '/Setting.json') + config.add_route('setting_list', '/Settings.json') + config.add_route('setting_id', '/Setting/{id}.json') + + config.add_route('sa_sale', '/SaleAnalysis/Sale.json') + config.add_route('sa_sc', '/SaleAnalysis/ServiceCharge.json') + config.add_route('sa_settlements', '/SaleAnalysis/Settlements.json') + config.add_route('sa_st', '/SaleAnalysis/ServiceTax.json') + config.add_route('sa_vat', '/SaleAnalysis/Vat.json') + + config.add_route('tax', '/Tax.json') + config.add_route('tax_list', '/Taxes.json') + config.add_route('tax_id', '/Tax/{id}.json') + + config.add_route('user', '/User.json') + config.add_route('user_list', '/Users.json') + config.add_route('user_id', '/User/{id}.json') + + config.add_route('voucher', '/Voucher.json') + config.add_route('voucher_id', '/Voucher/{id}.json') + config.add_route('voucher_reprint', '/ReprintVoucher/{id}.json') + config.add_route('voucher_settle', '/Settle/{id}.json') + config.add_route('voucher_split', '/Split/{id}.json') + config.add_route('voucher_void', '/Void/{id}.json') + + config.add_route('api_lock_info', '/api/LockInfo') + config.add_route('api_maintenance', '/api/Maintenance') diff --git a/barker/scripts/__init__.py b/barker/scripts/__init__.py new file mode 100644 index 0000000..5bb534f --- /dev/null +++ b/barker/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/barker/scripts/fixtures.py b/barker/scripts/fixtures.py new file mode 100644 index 0000000..310c348 --- /dev/null +++ b/barker/scripts/fixtures.py @@ -0,0 +1,76 @@ +import os +import sys +import uuid + +import transaction +from pyramid.paster import ( + get_appsettings, + setup_logging, +) +from pyramid.scripts.common import parse_vars + +from barker.models import ( + Customer, + DbSetting, + get_engine, + get_session_factory, + get_tm_session, + SettleOption +) + + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s [var=value]\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + + +def main(argv=sys.argv): + if len(argv) < 2: + usage(argv) + config_uri = argv[1] + options = parse_vars(argv[2:]) + setup_logging(config_uri) + settings = get_appsettings(config_uri, options=options) + + engine = get_engine(settings) + session_factory = get_session_factory(engine) + + with transaction.manager: + dbsession = get_tm_session(session_factory, transaction.manager) + + options = [ + SettleOption('Unsettled', False, 1, True, 1), + SettleOption('Cash', True, 2, False, 2), + SettleOption('Credit Card', True, 2, True, 3), + SettleOption('No Charge', True, 3, True, 4), + SettleOption('Bill To Company', True, 2, True, 5), + SettleOption('Tip', True, 2, True, 6), + SettleOption('Round Off', False, 1, False, 7), + SettleOption('Amount', False, 1, False, 8), + SettleOption('Void', True, 1, True, 9), + SettleOption('Staff', True, 4, True, 10), + ] + for option in options: + dbsession.add(option) + dbsession.add(DbSetting(None, 'Service Charge', {'Enabled': False})) + dbsession.add(Customer("Cash", "", "", False, uuid.UUID('2c716f4b-0736-429a-ad51-610d7c47cb5e'))) + dbsession.add(DbSetting( + uuid.UUID('fb738ba2-a3c9-40ed-891c-b930e6454974'), + 'Header', + {'Text': """ Hops n Grains + The Microbrewery + SCO 358, Sector 9, Panchkula + A Unit of Peitho Foods Pvt. Ltd. + CIN: U15139CH2010PTC032202 +(Reg Add: Plot No. 907, Indl Area II, Chd) + TIN: 06592507323 + Service Tax: AAFCP5097GSD001 +"""} + )) + dbsession.add(DbSetting( + uuid.UUID('f7799871-d16e-4c4d-9b57-2299a5839acb'), + 'Footer', + {'Text': "Call: 0172-4026666, 8054923853, 8054923856"} + )) diff --git a/barker/scripts/initializedb.py b/barker/scripts/initializedb.py new file mode 100644 index 0000000..343e436 --- /dev/null +++ b/barker/scripts/initializedb.py @@ -0,0 +1,66 @@ +import os +import sys + +from pyramid.paster import ( + get_appsettings, + setup_logging, +) + +from pyramid.scripts.common import parse_vars + +from barker.models.meta import Base +from barker.models import ( + get_engine, + get_session_factory, + get_tm_session, +) + +from barker.models import ( + Client, + Customer, + DbSetting, + FoodTable, + Inventory, + InventoryModifier, + Kot, + Location, + LoginHistory, + MachineLocation, + Modifier, + Permission, + PrintLocation, + Printer, + Product, + ProductGroup, + ProductGroupModifier, + Reprint, + Role, + role_permissions, + SettleOption, + Settlement, + Tax, + User, + user_roles, + Voucher, + VoucherFoodTable, + VoucherType, +) + + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s [var=value]\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + + +def main(argv=sys.argv): + if len(argv) < 2: + usage(argv) + config_uri = argv[1] + options = parse_vars(argv[2:]) + setup_logging(config_uri) + settings = get_appsettings(config_uri, options=options) + + engine = get_engine(settings) + Base.metadata.create_all(engine) diff --git a/barker/security/__init__.py b/barker/security/__init__.py new file mode 100644 index 0000000..3deec8e --- /dev/null +++ b/barker/security/__init__.py @@ -0,0 +1,39 @@ +import uuid + +from pyramid.authentication import AuthTktAuthenticationPolicy +from barker.security.permission_authorization_policy import PermissionAuthorizationPolicy +from ..models.auth import User + + +def get_user(request): + user_id = request.unauthenticated_userid + if user_id is not None: + user = request.dbsession.query(User).get(user_id) + return user + + +def groupfinder(user_id, request): + if 'perms' in request.session: + perms = request.session['perms'] + else: + perms = [] + user = request.dbsession.query(User).filter(User.id == uuid.UUID(user_id)).one() + for role in user.roles: + for perm in role.permissions: + perms.append(perm.name) + perms = sorted(f7(perms)) + request.session['perms'] = perms + return perms + + +def f7(seq): + seen = set() + seen_add = seen.add + return [x for x in seq if x not in seen and not seen_add(x)] + + +def includeme(config): + authn_policy = AuthTktAuthenticationPolicy('barker', timeout=900, reissue_time=90, callback=groupfinder) + config.set_authentication_policy(authn_policy) + config.set_authorization_policy(PermissionAuthorizationPolicy()) + config.add_request_method(get_user, 'user', reify=True) diff --git a/barker/security/permission_authorization_policy.py b/barker/security/permission_authorization_policy.py new file mode 100644 index 0000000..126e57f --- /dev/null +++ b/barker/security/permission_authorization_policy.py @@ -0,0 +1,27 @@ +from zope.interface import implementer + +from pyramid.interfaces import IAuthorizationPolicy + + +@implementer(IAuthorizationPolicy) +class PermissionAuthorizationPolicy(object): + def permits(self, context, principals, permission): + if isinstance(permission, str): + return self.check(principals, permission) + for p in permission: + if self.check(principals,p): + return True + return False + + def check(self, principals, permission): + if permission == 'Authenticated': + permission = 'system.Authenticated' + if permission == 'Everyone': + permission = 'system.Everyone' + allowed = permission in principals + return allowed + + def principals_allowed_by_permission(self, context, permission): + allowed = set() + allowed.add(permission) + return allowed diff --git a/barker/subscribers.py b/barker/subscribers.py new file mode 100644 index 0000000..75b7843 --- /dev/null +++ b/barker/subscribers.py @@ -0,0 +1,10 @@ +from pyramid.events import subscriber, NewRequest +from pyramid.httpexceptions import HTTPServiceUnavailable +from barker.models.master import DbSetting + + +@subscriber(NewRequest) +def maintenance_mode(event): + maintenance = event.request.dbsession.query(DbSetting).filter(DbSetting.name == 'Maintenance').first() + if maintenance is not None and maintenance.data != event.request.authenticated_userid: + raise HTTPServiceUnavailable diff --git a/barker/transactional_view_deriver.py b/barker/transactional_view_deriver.py new file mode 100644 index 0000000..1ef4bbb --- /dev/null +++ b/barker/transactional_view_deriver.py @@ -0,0 +1,29 @@ +from pyramid.response import Response +from sqlalchemy.exc import OperationalError, IntegrityError, DBAPIError +import transaction + + +def transactional_view(view, info): + if info.options.get('trans'): + def wrapper_view(context, request): + try: + response = view(context, request) + except ( + ValueError, KeyError, AttributeError, TypeError, OperationalError, IntegrityError, DBAPIError + ) as ex: + transaction.abort() + response = Response() + response.status_int = 500 + response.content_type = 'application/json' + response.json = {'Error': "Failed validation: {0!s}".format(ex)} + return response + + return wrapper_view + return view + + +# def transactional_view(view, info): +# return view + + +transactional_view.options = ('trans',) diff --git a/barker/views/__init__.py b/barker/views/__init__.py new file mode 100644 index 0000000..2f2eb82 --- /dev/null +++ b/barker/views/__init__.py @@ -0,0 +1,48 @@ +from decimal import Decimal +import uuid +import re + +from barker.exceptions import ValidationFailure +from pyramid.response import Response +from pyramid.view import forbidden_view_config, exception_view_config + + +@forbidden_view_config(renderer='json') +def forbidden(request): + response = Response() + response.status_int = 403 if request.authenticated_userid is not None else 401 + response.content_type = 'application/json' + response.json = {'Error': 'Forbidden'} + return response + + +@exception_view_config(ValidationFailure) +def failed_validation(exc, request): + response = Response() + response.status_int = 500 + response.content_type = 'application/json' + response.json = {'Error': "Failed validation: {0!s}".format(exc.message)} + return response + + +def to_uuid(value): + p = re.compile('^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$') + return uuid.UUID(value) if p.match(value) else None + + +def to_decimal(value, default=0): + import re + _parser = re.compile(r""" # A numeric string consists of: + (?P[-+])? # an optional sign, followed by either... + ( + (?=\d|\.\d) # ...a number (with at least one digit) + (?P\d*) # having a (possibly empty) integer part + (\.(?P\d*))? # followed by an optional fractional part + | + (?Ps)? # ...an (optionally signaling) + NaN # NaN + (?P\d*) # with (possibly empty) diagnostic info. + ) + \Z + """, re.VERBOSE | re.IGNORECASE).match + return Decimal(value) if _parser(value.strip()) is not None else default diff --git a/barker/views/customer.py b/barker/views/customer.py new file mode 100644 index 0000000..a36bcd4 --- /dev/null +++ b/barker/views/customer.py @@ -0,0 +1,71 @@ +import uuid + +import transaction +from pyramid.view import view_config + +from barker.models import Customer + + +@view_config(request_method='PUT', route_name='customer', renderer='json', permission='Customers', trans=True) +def save(request): + json = request.json_body + item = Customer(json['Name'], json['Phone'], json['Address'], json['Important']) + request.dbsession.add(item) + transaction.commit() + item = request.dbsession.query(Customer).filter(Customer.id == item.id).first() + return customer_info(item) + + +@view_config(request_method='POST', route_name='customer_id', renderer='json', permission='Customers', trans=True) +def update(request): + json = request.json_body + item = request.dbsession.query(Customer).filter(Customer.id == uuid.UUID(request.matchdict['id'])).first() + item.name = json['Name'] + item.phone = json['Phone'] + item.address = json['Address'] + item.is_important = json['Important'] + transaction.commit() + item = request.dbsession.query(Customer).filter(Customer.id == item.id).first() + return customer_info(item) + + +@view_config(request_method='DELETE', route_name='customer_id', renderer='json', permission='Customers', trans=True) +def delete(request): + item = request.dbsession.query(Customer).filter(Customer.id == uuid.UUID(request.matchdict['id'])).first() + request.dbsession.delete(item) + transaction.commit() + return {} + + +@view_config(request_method='GET', route_name='customer_id', renderer='json', permission='Authenticated') +def show_id(request): + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(Customer).filter(Customer.id == id).first() + return customer_info(item) + + +@view_config(request_method='GET', route_name='customer_list', renderer='json', permission='Authenticated') +def show_list(request): + list = request.dbsession.query(Customer).order_by(Customer.name).all() + customers = [] + for item in list: + customers.append({ + 'CustomerID': item.id, + 'Name': item.name, + 'Address': item.address, + 'Important': item.is_important, + 'Phone': item.phone, + 'Remarks': '' + }) + return customers + + +def customer_info(item): + return { + 'CustomerID': item.id, + 'Name': item.name, + 'Address': item.address, + 'Important': item.is_important, + 'Phone': item.phone, + 'Remarks': '' + } diff --git a/barker/views/food_table.py b/barker/views/food_table.py new file mode 100644 index 0000000..61c89e3 --- /dev/null +++ b/barker/views/food_table.py @@ -0,0 +1,107 @@ +import re +import uuid + +import transaction +from pyramid.view import view_config + +from barker.models import FoodTable, VoucherFoodTable + + +@view_config(request_method='PUT', route_name='food_table', renderer='json', permission='Tables', trans=True) +def save(request): + json = request.json_body + item = FoodTable(json['Name'], json['Phone'], json['Address'], json['Important']) + request.dbsession.add(item) + transaction.commit() + item = request.dbsession.query(FoodTable).filter(FoodTable.id == item.id).first() + return food_table_info(item) + + +@view_config(request_method='POST', route_name='food_table_id', renderer='json', permission='Tables', trans=True) +def update(request): + json = request.json_body + item = request.dbsession.query(FoodTable).filter(FoodTable.id == uuid.UUID(request.matchdict['id'])).first() + item.name = json['Name'] + item.phone = json['Phone'] + item.address = json['Address'] + item.is_important = json['Important'] + transaction.commit() + item = request.dbsession.query(FoodTable).filter(FoodTable.id == item.id).first() + return food_table_info(item) + + +@view_config(request_method='DELETE', route_name='food_table_id', renderer='json', permission='Tables', trans=True) +def delete(request): + item = request.dbsession.query(FoodTable).filter(FoodTable.id == uuid.UUID(request.matchdict['id'])).first() + request.dbsession.delete(item) + transaction.commit() + return {} + + +@view_config(request_method='GET', route_name='food_table_id', renderer='json', permission='Authenticated') +def show_id(request): + id = request.matchdict['id'] + p = re.compile('^[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}$') + if p.match(id): + id = uuid.UUID(id) + item = request.dbsession.query(FoodTable).filter(FoodTable.id == id).first() + else: + item = request.dbsession.query(FoodTable).filter(FoodTable.name == id).first() + return food_table_info(item) + + +@view_config(request_method='GET', route_name='food_table', renderer='json', request_param='v', permission='Tables') +def show_blank(request): + voucher_id = request.GET['v'] + voucher = request.dbsession.query(VoucherFoodTable).filter(VoucherFoodTable.voucher_id == voucher_id).first() + if voucher is not None: + return food_table_info(voucher.food_table) + return {} + + +@view_config(request_method='GET', route_name='food_table_list', renderer='json', permission='Authenticated') +def show_list(request): + active = request.GET.get('a', None) + list = request.dbsession.query(FoodTable) + if active is not None: + list = list.filter(FoodTable.is_active == active) + list = list.order_by(FoodTable.sort_order).all() + + food_tables = [] + for item in list: + food_tables.append({ + 'FoodTableID': item.id, + 'Name': item.name, + 'IsActive': item.is_active, + 'Location': item.location, + 'Status': '' if item.status is None else item.status.status, + 'SortOrder': item.sort_order, + 'VoucherID': None if item.status is None else item.status.voucher_id + }) + return food_tables + + +@view_config(request_method='POST', route_name='food_table_list', renderer='json', permission='Tables') +def sort_order(request): + json = request.json_body + for index, item in enumerate(json): + request.dbsession.query( + FoodTable + ).filter( + FoodTable.id == uuid.UUID(item['FoodTableID']) + ).update( + {FoodTable.sort_order: index} + ) + return True + + +def food_table_info(item): + return { + 'FoodTableID': item.id, + 'Name': item.name, + 'IsActive': item.is_active, + 'Location': item.location, + 'Status': '' if item.status is None else item.status.status, + 'SortOrder': item.sort_order, + 'VoucherID': None if item.status is None else item.status.voucher_id + } diff --git a/barker/views/location.py b/barker/views/location.py new file mode 100644 index 0000000..0fb852d --- /dev/null +++ b/barker/views/location.py @@ -0,0 +1,12 @@ +from pyramid.view import view_config + +from barker.models import Location + + +@view_config(request_method='GET', route_name='location_list', renderer='json', permission='Everyone') +def show_list(request): + list = request.dbsession.query(Location).order_by(Location.name).all() + locations = [] + for item in list: + locations.append(item.name) + return locations diff --git a/barker/views/login.py b/barker/views/login.py new file mode 100644 index 0000000..eca2688 --- /dev/null +++ b/barker/views/login.py @@ -0,0 +1,80 @@ +import transaction + +from pyramid.httpexceptions import HTTPFound +from pyramid.response import Response +from pyramid.security import forget, remember +from pyramid.view import view_config + +from barker.models import User + + +@view_config(route_name='logout') +def logout(request): + request.session.invalidate() + headers = forget(request) + return HTTPFound(location=request.route_url('home'), headers=headers) + + +@view_config(request_method='POST', route_name='login', renderer='json', trans=True) +def login(request): + username = request.json_body.get('Name', None) + password = request.json_body.get('Password', None) + found, user = User.auth(username, password, request.dbsession) + + if found: + headers = remember(request, str(user.id)) + auth = { + 'UserID': str(user.id), + 'Name': user.name, + 'Password': '', + 'LockedOut': user.locked_out, + 'MsrString': user.msr_string, + 'Permissions': get_permissions(user) + } + request.response.headers = headers + request.response.content_type = 'application/json' + request.response.json = auth + return request.response + elif not found: + response = Response() + response.status_int = 401 + response.content_type = 'application/json' + response.json = {'Error': 'Login Failed'} + transaction.abort() + return response + + +@view_config(request_method='POST', route_name='msr', renderer='json', trans=True) +def msr(request): + msr = request.json_body.get('Msr', None) + found, user = User.auth_msr(msr, request.dbsession) + + if found: + headers = remember(request, str(user.id)) + auth = { + 'UserID': str(user.id), + 'Name': user.name, + 'Password': '', + 'LockedOut': user.locked_out, + 'MsrString': user.msr_string, + 'Permissions': get_permissions(user) + } + request.response.headers = headers + request.response.content_type = 'application/json' + request.response.json = auth + return request.response + elif not found: + response = Response() + response.status_int = 401 + response.content_type = 'application/json' + response.json = {'Error': 'Login Failed'} + transaction.abort() + return response + + +def get_permissions(user): + perms = [] + for role in user.roles: + for perm in role.permissions: + perms.append({'PermissionID': str(perm.id), 'Name': perm.name}) + return sorted({p['PermissionID']: p for p in perms}.values(), key=lambda i: i['Name']) diff --git a/barker/views/machine_location.py b/barker/views/machine_location.py new file mode 100644 index 0000000..928d6d0 --- /dev/null +++ b/barker/views/machine_location.py @@ -0,0 +1,78 @@ +import uuid + +import transaction +from pyramid.view import view_config + +from barker.models import Location, MachineLocation + + +@view_config(request_method='PUT', route_name='machine_location', renderer='json', permission='Everyone', trans=True) +def save(request): + json = request.json_body + item = MachineLocation(json['Machine']) + item.location = request.dbsession.query(Location).filter(Location.name == json['Location']).first() + request.dbsession.add(item) + transaction.commit() + item = request.dbsession.query(MachineLocation).filter(MachineLocation.id == item.id).first() + return machine_location_info(item) + + +@view_config(request_method='POST', route_name='machine_location_id', renderer='json', permission='Machines', + trans=True) +def update(request): + id = uuid.UUID(request.matchdict['id']) + json = request.json_body + item = request.dbsession.query(MachineLocation).filter(MachineLocation.id == id).first() + item.machine_name = json['Machine'] + item.location = request.dbsession.query(Location).filter(Location.name == json['Location']).first() + transaction.commit() + item = request.dbsession.query(MachineLocation).filter(MachineLocation.id == item.id).first() + return machine_location_info(item) + + +@view_config(request_method='DELETE', route_name='machine_location_id', renderer='json', permission='Machines', + trans=True) +def delete(request): + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(MachineLocation).filter(MachineLocation.id == id).first() + request.dbsession.delete(item) + transaction.commit() + return {} + + +@view_config(request_method='GET', route_name='machine_location_id', renderer='json', permission='Authenticated') +def show_id(request): + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(MachineLocation).filter(MachineLocation.id == id).first() + return machine_location_info(item) + + +@view_config(request_method='GET', route_name='machine_location', renderer='json', request_param='n', + permission='Everyone') +def show_name(request): + name = request.GET['n'] + item = request.dbsession.query(MachineLocation).filter(MachineLocation.machine_name == name).first() + return machine_location_info(item) + + +@view_config(request_method='GET', route_name='machine_location_list', renderer='json', permission='Authenticated') +def show_list(request): + list = request.dbsession.query(MachineLocation).order_by(MachineLocation.machine_name).all() + machine_locations = [] + for item in list: + machine_locations.append({ + 'MachineLocationID': item.id, + 'Machine': item.machine_name, + 'Location': item.location.name + }) + return machine_locations + + +def machine_location_info(item): + if item is None: + return {} + return { + 'MachineLocationID': item.id, + 'Machine': item.machine_name, + 'Location': item.location.name + } diff --git a/barker/views/merge_move.py b/barker/views/merge_move.py new file mode 100644 index 0000000..5fb1c9d --- /dev/null +++ b/barker/views/merge_move.py @@ -0,0 +1,92 @@ +import uuid + +import transaction +from pyramid.view import view_config + +from barker.models import Kot, Voucher, VoucherFoodTable + + +@view_config(request_method='POST', route_name='merge_kot', renderer='json', permission='Merge Kots', trans=True) +def merge_kot(request): + json = request.json_body + kot_id = uuid.UUID(json['KotID']) + voucher_id = uuid.UUID(json['NewVoucherID']) + + request.dbsession.query(Kot).filter(Kot.id == kot_id).update({Kot.voucher_id: voucher_id}) + transaction.commit() + return voucher_id + + +@view_config(request_method='POST', route_name='move_kot', renderer='json', permission='Move Kot', trans=True) +def move_kot(request): + json = request.json_body + kot_id = uuid.UUID(json['KotID']) + food_table_id = uuid.UUID(json['FoodTableID']) + + kot = request.dbsession.query(Kot).filter(Kot.id == kot_id).first() + + item = Voucher(kot.voucher.pax, food_table_id, kot.voucher.customer_id, False, kot.voucher.voucher_type, + kot.voucher.user_id, request.dbsession) + request.dbsession.add(item) + item.kots.append(kot) + status = "printed" if item.is_printed else "running" + item.status = VoucherFoodTable(voucher_id=None, food_table_id=item.food_table_id, status=status) + request.dbsession.add(item.status) + transaction.commit() + return item.id + + +@view_config(request_method='POST', route_name='move_table', renderer='json', permission='Move Table', trans=True) +def move_table(request): + json = request.json_body + voucher_id = uuid.UUID(json['VoucherID']) + food_table_id = uuid.UUID(json['FoodTableID']) + + request.dbsession.query( + VoucherFoodTable + ).filter( + VoucherFoodTable.voucher_id == voucher_id + ).update({VoucherFoodTable.food_table_id: food_table_id}) + + request.dbsession.query( + Voucher + ).filter( + Voucher.id == voucher_id + ).update({Voucher.food_table_id: food_table_id}) + + transaction.commit() + + +@view_config(request_method='POST', route_name='merge_table', renderer='json', permission='Merge Tables', trans=True) +def merge_table(request): + json = request.json_body + voucher_id = uuid.UUID(json['VoucherID']) + new_voucher_id = uuid.UUID(json['NewVoucherID']) + + request.dbsession.query(Kot).filter(Kot.voucher_id == voucher_id).update({Kot.voucher_id: new_voucher_id}) + request.dbsession.query(Voucher).filter(Voucher.id == voucher_id).delete() + transaction.commit() + return voucher_id + + # VoucherBI.UpdateTable(x => x.FoodTableID == _voucher.Table.FoodTableID && x.VoucherID == _voucher.VoucherID, null, null); + # throw new NotImplementedException(); + # var newTable = _session.QueryOver().Where(x => x.Name == tableName).SingleOrDefault(); + # var newVoucher = _session.Get(newTable.VoucherID); + # var oldVoucher = _session.Get(voucherID); + # for (var i = oldVoucher.Kots.Count - 1; i >= 0; i--) + # { + # var kot = oldVoucher.Kots[i]; + # oldVoucher.Kots.Remove(kot); + # kot.Voucher = newVoucher; + # newVoucher.Kots.Add(kot); + # _session.Update(kot); + # } + # _session.Update(newVoucher); + # + # + # foreach (var item in oldVoucher.Settlements) + # { + # _session.Delete(item); + # } + # _session.Delete(oldVoucher); + # return newVoucher.VoucherID; diff --git a/barker/views/modifier.py b/barker/views/modifier.py new file mode 100644 index 0000000..063d51b --- /dev/null +++ b/barker/views/modifier.py @@ -0,0 +1,117 @@ +import uuid + +import transaction + +from pyramid.view import view_config +from sqlalchemy import or_ + +from barker.models import Modifier, ProductGroupModifier + + +@view_config(request_method='PUT', route_name='modifier', renderer='json', permission='Modifiers', trans=True) +def save(request): + json = request.json_body + item = Modifier(json['Name'], json['ShowInBill'], json['Price']) + request.dbsession.add(item) + add_groups(item, json['ProductGroupModifiers'], request.dbsession) + transaction.commit() + item = request.dbsession.query(Modifier).filter(Modifier.id == item.id).first() + return modifier_info(item) + + +@view_config(request_method='POST', route_name='modifier_id', renderer='json', permission='Modifiers', trans=True) +def update(request): + json = request.json_body + item = request.dbsession.query(Modifier).filter(Modifier.id == uuid.UUID(request.matchdict['id'])).first() + item.name = json['Name'] + item.show_in_bill = json['ShowInBill'] + item.price = json['Price'] + add_groups(item, json['ProductGroupModifiers'], request.dbsession) + transaction.commit() + item = request.dbsession.query(Modifier).filter(Modifier.id == item.id).first() + return modifier_info(item) + + +@view_config(request_method='DELETE', route_name='modifier_id', renderer='json', permission='Modifiers', trans=True) +def delete(request): + item = request.dbsession.query(Modifier).filter(Modifier.id == uuid.UUID(request.matchdict['id'])).first() + request.dbsession.delete(item) + transaction.commit() + return {} + + +@view_config(request_method='GET', route_name='modifier_id', renderer='json', permission='Authenticated') +def show_id(request): + id = uuid.UUID(request.matchdict['id']) + modifier = request.dbsession.query(Modifier).filter(Modifier.id == id).first() + return modifier_info(modifier) + + +@view_config(request_method='GET', route_name='modifier_list', renderer='json', permission='Authenticated') +def show_list(request): + list = request.dbsession.query(Modifier).order_by(Modifier.name).all() + modifiers = [] + for item in list: + modifiers.append({ + 'ModifierID': item.id, + 'Name': item.name, + 'ShowInBill': item.show_in_bill, + 'Price': item.price + }) + return modifiers + + +@view_config(request_method='GET', route_name='modifier_list', renderer='json', request_param='pg', + permission='Authenticated') +def show_for_pg(request): + product_group_id = uuid.UUID(request.GET['pg']) + pgm_list = request.dbsession.query( + ProductGroupModifier + ).filter( + or_(ProductGroupModifier.product_group == None, ProductGroupModifier.product_group_id == product_group_id) + ).all() + + pg_modifiers = [] + for item in pgm_list: + pg_modifiers.append({ + 'ModifierID': item.modifier_id, + 'Name': item.modifier.name, + 'ShowInBill': item.modifier.show_in_bill, + 'Price': item.modifier.price + }) + return pg_modifiers + + +def modifier_info(modifier): + return { + 'ModifierID': modifier.id, + 'Name': modifier.name, + 'ShowInBill': modifier.show_in_bill, + 'Price': modifier.price, + 'ProductGroupModifiers': [ + { + 'ProductGroupModifierID': i.id, + 'ProductGroup': {'ProductGroupID': i.product_group_id} if i.product_group_id is not None else None, + 'ShowAutomatically': i.show_automatically + } for i in modifier.product_groups] + } + + +def add_groups(modifier, groups, dbsession): + all = [p for p in groups if p['ProductGroup'] is None] + if len(all) > 0: + old = [p for p in modifier.product_groups if p.product_group_id is None] + if len(old) != 1: + for item in modifier.product_groups: + dbsession.delete(item) + modifier.product_groups.clear() + modifier.product_groups.append(ProductGroupModifier(None, None, False)) + else: + new = {uuid.UUID(p['ProductGroup']['ProductGroupID']) for p in groups} + old = {p.product_group_id for p in modifier.product_groups} + for pg_id in new - old: + modifier.product_groups.append(ProductGroupModifier(pg_id, None, False)) + + gp = [p for p in modifier.product_groups if p.product_group_id in (old - new)] + for item in gp: + dbsession.delete(item) diff --git a/barker/views/print_location.py b/barker/views/print_location.py new file mode 100644 index 0000000..e42fe32 --- /dev/null +++ b/barker/views/print_location.py @@ -0,0 +1,46 @@ +import uuid + +from pyramid.view import view_config + +from barker.models import Location, PrintLocation + + +@view_config(request_method='GET', route_name='print_location_id', renderer='json', permission='Authenticated') +def show(request): + id = request.matchdict['id'] + item = request.dbsession.query(PrintLocation).join(PrintLocation.location).filter( + Location.name == id, + PrintLocation.product_group_id == None + ).first() + return print_location_info(item) + + +@view_config(request_method='GET', route_name='print_location_id', renderer='json', request_param='pg', + permission='Authenticated') +def show_pg(request): + id = request.matchdict['id'] + pg = uuid.UUID(request.GET['pg']) + item = request.dbsession.query(PrintLocation).join(PrintLocation.location).filter( + Location.name == id, + PrintLocation.product_group_id == pg + ).first() + if item is None: + item = request.dbsession.query( + PrintLocation + ).join(PrintLocation.location).filter( + Location.name == id, + PrintLocation.product_group_id == None + ).first() + + return print_location_info(item) + + +def print_location_info(item): + return { + 'PrintLocationID': item.id, + 'ProductGroup': None if item.product_group is None else {'ProductGroupID': item.product_group_id}, + 'Location': item.location.name, + 'Printer': item.printer.name, + 'Copies': item.copies, + 'CutCode': item.printer.cut_code + } diff --git a/barker/views/product.py b/barker/views/product.py new file mode 100644 index 0000000..a309057 --- /dev/null +++ b/barker/views/product.py @@ -0,0 +1,113 @@ +import uuid + +import transaction +from pyramid.response import Response +from pyramid.view import view_config + +from barker.models import Product + + +@view_config(request_method='PUT', route_name='product', renderer='json', permission='Products', trans=True) +def save(request): + json = request.json_body + item = Product(json['Name'], json['Units'], json['ProductGroupID'], json['VatID'], json['ServiceTaxID'], + json['ServiceCharge'], json['IsScTaxable'], json['Price'], json['HasHappyHour'], + json['IsNotAvailable'], json['Quantity'], json['IsActive'], json['SortOrder']) + request.dbsession.add(item) + transaction.commit() + item = request.dbsession.query(Product).filter(Product.id == item.id).first() + return product_info(item) + + +@view_config(request_method='POST', route_name='product_id', renderer='json', permission='Products', trans=True) +def update(request): + item = request.dbsession.query(Product).filter(Product.id == uuid.UUID(request.matchdict['id'])).first() + json = request.json_body + item.name = json['Name'] + item.units = json['Units'] + item.product_group_id = json['ProductGroupID'] + item.vat_id = json['VatID'] + item.service_tax_id = json['ServiceTaxID'] + item.service_charge = json['ServiceCharge'] + item.is_sc_taxable = json['IsScTaxable'] + item.price = json['Price'] + item.has_happy_hour = json['HasHappyHour'] + item.is_not_available = json['IsNotAvailable'] + item.quantity = json['Quantity'] + item.is_active = json['IsActive'] + item.sort_order = json['SortOrder'] + transaction.commit() + item = request.dbsession.query(Product).filter(Product.id == item.id).first() + return product_info(item) + + +@view_config(request_method='DELETE', route_name='product_id', renderer='json', permission='Products', trans=True) +def delete(request): + item = request.dbsession.query(Product).filter(Product.id == uuid.UUID(request.matchdict['id'])).first() + if item is None: + response = Response("Product not Found") + response.status_int = 500 + return response + else: + response = Response("Product deletion not implemented") + response.status_int = 500 + return response + + +@view_config(request_method='GET', route_name='product_id', renderer='json', permission='Authenticated') +def show_id(request): + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(Product).filter(Product.id == id).first() + return product_info(item) + + +@view_config(request_method='GET', route_name='product_list', renderer='json', permission='Authenticated') +def show_list(request): + active = request.GET.get('a', None) + list = request.dbsession.query(Product) + if active is not None: + list = list.filter(Product.is_active == active) + list = list.order_by(Product.product_group_id, Product.sort_order).order_by(Product.name).all() + products = [] + for item in list: + products.append(product_info(item)) + return products + + +@view_config(request_method='POST', route_name='product_list', renderer='json', permission='Products', trans=True) +def sort_order(request): + json = request.json_body + index, last_group = 0, None + for item in json: + product_id = uuid.UUID(item['ProductID']) + product_group_id = uuid.UUID(item['ProductGroupID']) + if last_group != product_group_id: + index = 0 + last_group = product_group_id + request.dbsession.query( + Product + ).filter( + Product.id == product_id + ).update( + {Product.sort_order: index} + ) + return True + + +def product_info(item): + return { + 'ProductID': item.id, + 'Name': item.name, + 'Units': item.units, + 'ProductGroup': {'ProductGroupID': item.product_group_id, 'Name': item.product_group.name}, + 'Vat': {'TaxID': item.vat_id, 'Name': item.vat.name, 'Rate': item.vat.rate}, + 'ServiceTax': {'TaxID': item.service_tax_id, 'Name': item.service_tax.name, 'Rate': item.service_tax.rate}, + 'ServiceCharge': item.service_charge, + 'IsScTaxable': item.is_sc_taxable, + 'Price': item.price, + 'HasHappyHour': item.has_happy_hour, + 'IsNotAvailable': item.is_not_available, + + 'IsActive': item.is_active, + 'SortOrder': item.sort_order + } diff --git a/barker/views/product_group.py b/barker/views/product_group.py new file mode 100644 index 0000000..cf0d7d6 --- /dev/null +++ b/barker/views/product_group.py @@ -0,0 +1,140 @@ +import uuid + +import transaction +from pyramid.response import Response +from pyramid.view import view_config + +from barker.exceptions import ValidationFailure +from barker.models import Product, ProductGroup + + +@view_config(request_method='PUT', route_name='product_group', renderer='json', permission='Products', trans=True) +def save(request): + json = request.json_body + item = ProductGroup(json['Name'], json['DiscountLimit'], json['IsModifierCompulsory'], json['GroupType'], + json['IsActive'], json['SortOrder']) + request.dbsession.add(item) + transaction.commit() + item = request.dbsession.query(ProductGroup).filter(ProductGroup.id == item.id).first() + return product_group_info(item) + + +@view_config(request_method='POST', route_name='product_group_id', renderer='json', permission='Products', trans=True) +def update(request): + item = request.dbsession.query(ProductGroup).filter(ProductGroup.id == uuid.UUID(request.matchdict['id'])).first() + json = request.json_body + item.name = json['Name'] + item.discount_limit = json['DiscountLimit'] + item.is_modifier_compulsory = json['IsModifierCompulsory'] + item.group_type = json['GroupType'] + item.is_active = json['IsActive'] + item.sort_order = json['SortOrder'] + transaction.commit() + item = request.dbsession.query(ProductGroup).filter(ProductGroup.id == item.id).first() + return product_group_info(item) + + +@view_config(request_method='DELETE', route_name='product_group_id', renderer='json', permission='Products', trans=True) +def delete(request): + item = request.dbsession.query(ProductGroup).filter(ProductGroup.id == uuid.UUID(request.matchdict['id'])).first() + if item is None: + response = Response("Product Group not Found") + response.status_int = 500 + return response + elif item.is_fixture: + transaction.abort() + raise ValidationFailure("{0} is a fixture and cannot be edited or deleted.".format(item.name)) + else: + response = Response("Product Group deletion not implemented") + response.status_int = 500 + return response + + +@view_config(request_method='GET', route_name='product_group_id', renderer='json', permission='Authenticated') +def show_id(request): + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(ProductGroup).filter(ProductGroup.id == id).first() + return product_group_info(item) + + +@view_config(request_method='GET', route_name='product_group_list', renderer='json', permission='Authenticated') +def show_list(request): + list = request.dbsession.query(ProductGroup).order_by(ProductGroup.sort_order).order_by(ProductGroup.name).all() + product_groups = [] + for item in list: + product_groups.append(product_group_info(item)) + return product_groups + + +@view_config(request_method='GET', route_name='product_group_list', renderer='json', request_param='s', + permission='Authenticated') +def sale_list(request): + list = request.dbsession.query( + ProductGroup + ).filter(ProductGroup.is_active == True).order_by(ProductGroup.sort_order).order_by(ProductGroup.name).all() + product_groups = [] + for item in list: + products = request.dbsession.query( + Product + ).filter( + Product.product_group_id == item.id + ).filter(Product.is_active == True).order_by(Product.sort_order).order_by(Product.name).all() + if len(products) > 0: + pg = product_group_info(item) + pg['Products'] = [] + for p in products: + pg['Products'].append({ + 'ProductID': p.id, + 'Name': p.name, + 'Units': p.units, + 'Vat': {'TaxID': p.vat.id, 'Name': p.vat.name, 'Rate': p.vat.rate}, + 'ServiceTax': {'TaxID': p.service_tax.id, 'Name': p.service_tax.name, 'Rate': p.service_tax.rate}, + 'ServiceCharge': p.service_charge, + 'IsScTaxable': p.is_sc_taxable, + 'Price': p.price, + 'HasHappyHour': p.has_happy_hour, + 'IsActive': p.is_active, + 'IsNotAvailable': p.is_not_available, + 'SortOrder': p.sort_order, + 'Quantity': p.quantity}) + product_groups.append(pg) + return product_groups + + +@view_config(request_method='POST', route_name='product_group_list', renderer='json', permission='Products', trans=True) +def sort_order(request): + json = request.json_body + for index, item in enumerate(json): + request.dbsession.query( + ProductGroup + ).filter( + ProductGroup.id == uuid.UUID(item['ProductGroupID']) + ).update( + {ProductGroup.sort_order: index} + ) + return True + + +@view_config(request_method='GET', route_name='product_group_type_list', renderer='json', permission='Authenticated') +def product_group_type_list(request): + list = request.dbsession.query( + ProductGroup.group_type + ).group_by( + ProductGroup.group_type + ).order_by( + ProductGroup.group_type + ).all() + return [item.group_type for item in list] + + +def product_group_info(item): + return { + 'ProductGroupID': item.id, + 'Name': item.name, + 'DiscountLimit': item.discount_limit, + 'IsModifierCompulsory': item.is_modifier_compulsory, + 'GroupType': item.group_type, + + 'IsActive': item.is_active, + 'SortOrder': item.sort_order + } diff --git a/barker/views/reports/__init__.py b/barker/views/reports/__init__.py new file mode 100644 index 0000000..5e84a52 --- /dev/null +++ b/barker/views/reports/__init__.py @@ -0,0 +1 @@ +__author__ = 'tanshu' diff --git a/barker/views/reports/beer_consumption.py b/barker/views/reports/beer_consumption.py new file mode 100644 index 0000000..f9bf588 --- /dev/null +++ b/barker/views/reports/beer_consumption.py @@ -0,0 +1,42 @@ +import datetime + +from pyramid.httpexceptions import HTTPForbidden +from pyramid.view import view_config +from sqlalchemy import func + +from barker.models import Inventory, Kot, Product, Settlement, SettleOption, Voucher + + +@view_config(request_method='GET', route_name='beer_consumption', renderer='json', permission='Beer Consumption', + request_param=('s', 'f')) +def beer_consumption(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + list = request.dbsession.query( + Voucher.date, Product.id, Product.name, func.sum(Inventory.quantity * Product.quantity) + ).join(Voucher.kots).join(Kot.inventories).join(Inventory.product).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.is_void == False, + Voucher.settlements.any(Settlement.settled.in_([ + SettleOption.CASH(), + SettleOption.CREDIT_CARD(), + SettleOption.BILL_TO_COMPANY(), + SettleOption.STAFF(), + SettleOption.NO_CHARGE() + ])) + ).group_by( + Voucher.date, Product.id, Product.name + ).order_by( + Voucher.date, Product.id, Product.name + ).all() + return [{ + 'Date': i[0].strftime('%d-%b-%Y %H:%M:%S'), + 'ProductID': str(i[1]), + 'Name': i[2], + 'Quantity': i[3] + } for i in list if i[3] != 0] diff --git a/barker/views/reports/bill_details.py b/barker/views/reports/bill_details.py new file mode 100644 index 0000000..36c2353 --- /dev/null +++ b/barker/views/reports/bill_details.py @@ -0,0 +1,53 @@ +import datetime + +from pyramid.httpexceptions import HTTPForbidden +from pyramid.view import view_config +from sqlalchemy.orm import joinedload + +from barker.models import Settlement, SettleOption, Voucher + + +@view_config(request_method='GET', route_name='bill_details', renderer='json', permission='Bill Details', + request_param=('s', 'f')) +def bill_details(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + # TODO: Should be left outer join incase some bill is missing the settlements + vouchers = request.dbsession.query( + Voucher + ).options( + joinedload(Voucher.settlements, innerjoin=True).joinedload(Settlement.settle_option, innerjoin=True), + joinedload(Voucher.customer, innerjoin=True) + ).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date + ).order_by( + Voucher.voucher_type + ).order_by( + Voucher.bill_id + ).all() + + report = [] + for item in vouchers: + if item.is_void: + report.append({ + 'Date': item.date.strftime('%d-%b-%Y %H:%M:%S'), + 'BillID': item.full_bill_id, + 'Settlement': "Void: {0}".format(item.void_reason), + 'Amount': round(next( + s.amount for s in item.settlements if s.settled == SettleOption.AMOUNT() + ) * -1, 2) + }) + else: + for so in (so for so in item.settlements if so.settle_option.show_in_choices): + report.append({ + 'Date': item.date.strftime('%d-%b-%Y %H:%M:%S'), + 'BillID': item.full_bill_id, + 'Settlement': so.settle_option.name, + 'Amount': round(so.amount, 2) + }) + return report diff --git a/barker/views/reports/checkout.py b/barker/views/reports/checkout.py new file mode 100644 index 0000000..03683eb --- /dev/null +++ b/barker/views/reports/checkout.py @@ -0,0 +1,78 @@ +import datetime +import uuid + +from barker.models import Voucher, User, Settlement +from barker.models.master import SettleOption +from pyramid.view import view_config +from sqlalchemy.orm import joinedload + +__author__ = 'tanshu' + + +@view_config(request_method='GET', route_name='active_cashiers', renderer='json', permission='Cashier Checkout', + request_param=('s', 'f')) +def active_cashiers(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + user_ids = request.dbsession.query(Voucher.user_id).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date + ).distinct() + + users = request.dbsession.query( + User + ).filter( + User.id.in_(user_ids) + ).order_by( + User.name + ).all() + + return [{'UserID': u.id, 'Name': u.name} for u in users] + + +@view_config(request_method='GET', route_name='checkout', renderer='json', permission='Cashier Checkout', + request_param=('s', 'f')) +def check_me_out(request): + id = uuid.UUID(request.matchdict['id']) + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + vouchers = request.dbsession.query( + Voucher + ).options( + joinedload(Voucher.settlements, innerjoin=True).joinedload(Settlement.settle_option, innerjoin=True), + joinedload(Voucher.customer, innerjoin=True) + ).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.user_id == id + ).order_by( + Voucher.voucher_type + ).order_by( + Voucher.bill_id + ).all() + + info = {} + for item in vouchers: + if item.is_void: + if SettleOption.VOID() not in info: + info[SettleOption.VOID()] = [] + amount = next(s.amount * -1 for s in item.settlements if s.settled == SettleOption.AMOUNT()) + info[SettleOption.VOID()].append({ + 'Date': item.date.strftime('%d-%b-%Y %H:%M:%S'), + 'BillID': item.full_bill_id, + 'Customer': item.customer.name, + 'Amount': amount + }) + else: + for so in (so for so in item.settlements if so.settle_option.show_in_choices): + if so.settled not in info: + info[so.settled] = [] + info[so.settled].append({ + 'Date': item.date.strftime('%d-%b-%Y %H:%M:%S'), + 'BillID': item.full_bill_id, + 'Customer': item.customer.name, + 'Amount': so.amount + }) + return info diff --git a/barker/views/reports/discount_report.py b/barker/views/reports/discount_report.py new file mode 100644 index 0000000..496dd61 --- /dev/null +++ b/barker/views/reports/discount_report.py @@ -0,0 +1,36 @@ +import datetime + +from pyramid.httpexceptions import HTTPForbidden +from pyramid.view import view_config +from sqlalchemy import func + +from barker.models import Inventory, Kot, Product, ProductGroup, Settlement, SettleOption, Voucher + + +@view_config(request_method='GET', route_name='discount_report', renderer='json', permission='Discount Report', + request_param=('s', 'f')) +def discount_report(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + amount = func.sum(Inventory.quantity * Inventory.effective_price * Inventory.discount).label('Amount') + list = request.dbsession.query( + ProductGroup.group_type, amount + ).join(Voucher.kots).join(Kot.inventories).join(Inventory.product).join(Product.product_group).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.is_void == False, + Inventory.discount != 0, + Voucher.settlements.any(~Settlement.settled.in_( + [SettleOption.CASH(), SettleOption.CREDIT_CARD(), SettleOption.BILL_TO_COMPANY(), + SettleOption.UNSETTLED()])) + ).group_by( + ProductGroup.group_type + ).order_by( + ProductGroup.group_type + ).all() + + return [{'GroupType': pg, 'Amount': amt} for pg, amt in list] diff --git a/barker/views/reports/quantity_sold.py b/barker/views/reports/quantity_sold.py new file mode 100644 index 0000000..3d98e1b --- /dev/null +++ b/barker/views/reports/quantity_sold.py @@ -0,0 +1,57 @@ +import datetime + +from pyramid.httpexceptions import HTTPForbidden +from pyramid.view import view_config +from sqlalchemy import func + +from barker.models import Inventory, Kot, Product, ProductGroup, Settlement, SettleOption, Voucher + + +@view_config(request_method='GET', route_name='quantity_sold', renderer='json', permission='Sales Detail', + request_param=('s', 'f')) +def quantity_sold(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + sold = q(start_date, finish_date, [ + SettleOption.CASH(), + SettleOption.CREDIT_CARD(), + SettleOption.BILL_TO_COMPANY(), + SettleOption.STAFF() + ], request.dbsession) + + nc = q(start_date, finish_date, [ + SettleOption.NO_CHARGE() + ], request.dbsession) + + output = [{ + 'ProductID': str(i[0]), + 'Name': i[1], + 'IsHappyHour': i[2], + 'Sale': i[3] + } for i in sold] + for id, name, hh, qty in nc: + i = next((x for x in output if x['ProductID'] == str(id) and x['IsHappyHour'] == hh), None) + if i is None: + output.append({'ProductID': str(id), 'Name': name, 'IsHappyHour': hh, 'NC': qty}) + else: + i['NC'] = qty + return output + + +def q(start_date, finish_date, options, dbsession): + return dbsession.query( + Product.id, Product.full_name, Inventory.is_happy_hour, func.sum(Inventory.quantity) + ).join(Voucher.kots).join(Kot.inventories).join(Inventory.product).join(Product.product_group).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.is_void == False, + Voucher.settlements.any(Settlement.settled.in_(options)) + ).group_by( + Product.id, Product.full_name, Inventory.is_happy_hour, ProductGroup.name, ProductGroup.group_type + ).order_by( + ProductGroup.group_type, ProductGroup.name, Product.full_name, Inventory.is_happy_hour + ).all() diff --git a/barker/views/reports/reprint_report.py b/barker/views/reports/reprint_report.py new file mode 100644 index 0000000..978f270 --- /dev/null +++ b/barker/views/reports/reprint_report.py @@ -0,0 +1,62 @@ +import datetime + +from pyramid.httpexceptions import HTTPForbidden +from pyramid.view import view_config +from sqlalchemy.orm import joinedload + +from barker.models import Reprint, Settlement, SettleOption, Voucher + + +@view_config(request_method='GET', route_name='reprint_report', renderer='json', + permission='Void or Reprinted Bill Report', request_param=('s', 'f')) +def reprint_report(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + # TODO: Should be left outer join in case some bill is missing the settlements + voids = [] + void_q = request.dbsession.query( + Voucher + ).options( + joinedload(Voucher.settlements, innerjoin=True).joinedload(Settlement.settle_option, innerjoin=True), + joinedload(Voucher.customer, innerjoin=True) + ).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.is_void == True + ).order_by( + Voucher.voucher_type + ).order_by( + Voucher.bill_id + ).all() + + for item in void_q: + voids.append({ + 'Date': item.date.strftime('%d-%b-%Y %H:%M:%S'), + 'BillID': item.full_bill_id, + 'Settlement': "Void: {0}".format(item.void_reason), + 'Amount': round(next( + s.amount for s in item.settlements if s.settled == SettleOption.AMOUNT() + ) * -1, 2) + }) + + reprints = [] + reprint_q = request.dbsession.query(Reprint).filter( + Reprint.date >= start_date, + Reprint.date <= finish_date + ).all() + + for item in reprint_q: + reprints.append({ + 'Date': item.date.strftime('%d-%b-%Y %H:%M:%S'), + 'BillID': item.voucher.full_bill_id, + 'Settlement': "Reprinted by {0}".format(item.user.name), + 'Amount': round(next( + s.amount for s in item.voucher.settlements if s.settled == SettleOption.AMOUNT() + ) * -1, 2) + }) + + return voids + reprints diff --git a/barker/views/reports/sale_analysis.py b/barker/views/reports/sale_analysis.py new file mode 100644 index 0000000..7f4892b --- /dev/null +++ b/barker/views/reports/sale_analysis.py @@ -0,0 +1,150 @@ +import datetime + +from pyramid.httpexceptions import HTTPForbidden +from pyramid.view import view_config +from sqlalchemy import func + +from barker.models import Inventory, Kot, Product, ProductGroup, Settlement, SettleOption, Tax, Voucher + + +@view_config(request_method='GET', route_name='sa_sale', renderer='json', permission='Sales Analysis', + request_param=('s', 'f')) +def get_sale(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + amount = func.sum(Inventory.quantity * Inventory.effective_price * (1 - Inventory.discount)).label('Amount') + list = request.dbsession.query( + ProductGroup.group_type, amount + ).join(Voucher.kots).join(Kot.inventories).join(Inventory.product).join(Product.product_group).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.is_void == False, + Voucher.settlements.any(~Settlement.settled.in_([1, 4, 7, 8, 9, 10])) + ).group_by( + ProductGroup.group_type + ).order_by( + ProductGroup.group_type + ).all() + total = 0 + info = [] + for gt, am in list: + total += am + info.append({'GroupType': gt, 'Amount': am}) + return info + [{'GroupType': 'Total Settled', 'Amount': total}] + + +@view_config(request_method='GET', route_name='sa_settlements', renderer='json', permission='Sales Analysis', + request_param=('s', 'f')) +def get_settlements(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + list = request.dbsession.query( + SettleOption.name, func.sum(Settlement.amount) + ).join(Voucher.settlements).join(Settlement.settle_option).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.is_void == False + ).group_by( + SettleOption.name + ).order_by( + SettleOption.name + ).all() + total = 0 + info = [] + for gt, am in list: + total += am + info.append({'GroupType': gt, 'Amount': am}) + return info + [{'GroupType': 'Total', 'Amount': total}] + + +@view_config(request_method='GET', route_name='sa_sc', renderer='json', permission='Sales Analysis', + request_param=('s', 'f')) +def get_sc(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + amount = request.dbsession.query( + func.coalesce(func.sum(Inventory.sc_amount), 0) + ).join(Voucher.kots).join(Kot.inventories).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.is_void == False, + Voucher.settlements.any(~Settlement.settled.in_([1, 4, 7, 8, 9, 10])) + ).scalar() + return {'GroupType': 'Service Charge', 'Amount': amount} + + +@view_config(request_method='GET', route_name='sa_st', renderer='json', permission=('Tax Analysis', 'Sales Analysis'), + request_param=('s', 'f')) +def get_st(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + amounts = request.dbsession.query( + Inventory.service_tax_rate, + func.coalesce(func.sum(Inventory.net_taxable), 0), + func.coalesce(func.sum(Inventory.st_amount), 0) + ).join(Voucher.kots).join(Kot.inventories).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.is_void == False, + Voucher.settlements.any(~Settlement.settled.in_([1, 4, 7, 8, 9, 10])) + ).group_by( + Inventory.service_tax_rate + ).order_by( + Inventory.service_tax_rate + ).all() + return [{ + 'Name': "Service Tax - {0:.2%}".format(i[0]), + 'TaxRate': i[0], + 'NetSale': i[1], + 'TaxAmount': i[2] + } for i in amounts] + + +@view_config(request_method='GET', route_name='sa_vat', renderer='json', permission=('Tax Analysis', 'Sales Analysis'), + request_param=('s', 'f')) +def get_vat(request): + start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M') + finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M') + + if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals: + raise HTTPForbidden("Accounts Audit") + + amounts = request.dbsession.query( + Tax.name, + Inventory.vat_rate, + func.coalesce(func.sum(Inventory.net_taxable), 0), + func.coalesce(func.sum(Inventory.vat_amount), 0) + ).join(Voucher.kots).join(Kot.inventories).join(Inventory.vat).filter( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.is_void == False, + Voucher.settlements.any(~Settlement.settled.in_([1, 4, 7, 8, 9, 10])) + ).group_by( + Tax.name, + Inventory.vat_rate + ).order_by( + Tax.name, + Inventory.vat_rate + ).all() + return [{ + 'Name': "{0} - {1:.2%}".format(i[0], i[1]), + 'TaxRate': i[1], + 'NetSale': i[2], + 'TaxAmount': i[3] + } for i in amounts] diff --git a/barker/views/reprint.py b/barker/views/reprint.py new file mode 100644 index 0000000..0b2ac63 --- /dev/null +++ b/barker/views/reprint.py @@ -0,0 +1,22 @@ +import transaction +from pyramid.view import view_config + +from barker.models import Reprint + + +@view_config(request_method='PUT', route_name='reprint', renderer='json', permission='Print Bill', trans=True) +def save(request): + json = request.json_body + item = Reprint(json['User']['UserID'], json['Voucher']['VoucherID']) + request.dbsession.add(item) + transaction.commit() + item = request.dbsession.query(Reprint).filter(Reprint.id == item.id).first() + return reprint_info(item) + + +def reprint_info(item): + return { + 'ReprintID': item.id, + 'User': {'UserID': item.user_id, 'Name': item.user.name}, + 'Voucher': {'VoucherID': item.voucher_id, 'BillID': item.bill_id}, + } diff --git a/barker/views/role.py b/barker/views/role.py new file mode 100644 index 0000000..7d2915c --- /dev/null +++ b/barker/views/role.py @@ -0,0 +1,93 @@ +import uuid + +import transaction +from pyramid.response import Response +from pyramid.view import view_config + +from barker.models import Permission, Role + + +@view_config(request_method='PUT', route_name='role', renderer='json', permission='Users', trans=True) +def save(request): + item = Role(request.json_body['name']) + request.dbsession.add(item) + add_permissions(item, request.json_body['Permissions'], request.dbsession) + transaction.commit() + item = request.dbsession.query(Role).filter(Role.id == item.id).first() + return role_info(item) + + +@view_config(request_method='POST', route_name='role_id', renderer='json', permission='Users', trans=True) +def update(request): + id = request.matchdict.get('id', None) + item = request.dbsession.query(Role).filter(Role.id == uuid.UUID(id)).one() + item.name = request.json_body['Name'] + add_permissions(item, request.json_body['Roles'], request.dbsession) + transaction.commit() + item = request.dbsession.query(Role).filter(Role.id == item.id).first() + return role_info(item) + + +def add_permissions(role, permissions, dbsession): + new = {uuid.UUID(p['PermissionID']) for p in permissions} + old = {p.id for p in role.permissions} + for perm_id in new - old: + role.permissions.append(dbsession.query(Permission).filter(Permission.id == perm_id).one()) + + gp = [p for p in role.permissions if p.id in (old - new)] + for item in gp: + role.permissions.remove(item) + + +@view_config(request_method='DELETE', route_name='role_id', renderer='json', permission='Users', trans=True) +def delete(request): + id = request.matchdict.get('id', None) + if id is None: + response = Response("Role is Null") + response.status_int = 500 + return response + else: + response = Response("Role deletion not implemented") + response.status_int = 500 + return response + + +@view_config(request_method='GET', route_name='role_id', renderer='json', permission='Authenticated') +def show_id(request): + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(Role).filter(Role.id == id).first() + return role_info(item) + + +@view_config(request_method='GET', route_name='role_list', renderer='json', permission='Authenticated') +def show_list(request): + list = request.dbsession.query(Role).order_by(Role.name).all() + + roles = [] + for item in list: + role = {'RoleID': item.id, 'Name': item.name, 'Permissions': []} + for permission in sorted(item.permissions, key=lambda p: p.name): + role['Permissions'].append({'PermissionID': permission.id, 'Name': permission.name}) + roles.append(role) + return roles + + +@view_config(request_method='GET', route_name='permission_list', renderer='json', permission='Authenticated') +def permission_list(request): + list = request.dbsession.query(Permission).order_by(Permission.name).all() + + permissions = [] + for item in list: + permission = {'PermissionID': item.id, 'Name': item.name} + permissions.append(permission) + return permissions + + +def role_info(item): + return { + 'RoleID': item.id, + 'Name': item.name, + 'Permissions': [{ + 'PermissionID': p.id, 'Name': p.name + } for p in sorted(item.permissions, key=lambda p: p.name)] + } diff --git a/barker/views/setting.py b/barker/views/setting.py new file mode 100644 index 0000000..51d491f --- /dev/null +++ b/barker/views/setting.py @@ -0,0 +1,10 @@ +from pyramid.view import view_config + +from barker.models import DbSetting + + +@view_config(request_method='GET', route_name='setting_id', renderer='json', permission='Authenticated') +def show_id(request): + name = request.matchdict['id'] + item = request.dbsession.query(DbSetting).filter(DbSetting.name == name).first() + return {'SettingID': item.id, 'Name': item.name, 'Details': item.data} diff --git a/barker/views/tax.py b/barker/views/tax.py new file mode 100644 index 0000000..2533904 --- /dev/null +++ b/barker/views/tax.py @@ -0,0 +1,63 @@ +import uuid + +import transaction +from pyramid.view import view_config + +from barker.models import Tax + + +@view_config(request_method='PUT', route_name='tax', renderer='json', permission='Taxes', trans=True) +def save(request): + json = request.json_body + item = Tax(json['Name'], json['Rate']) + request.dbsession.add(item) + transaction.commit() + item = request.dbsession.query(Tax).filter(Tax.id == item.id).first() + return tax_info(item) + + +@view_config(request_method='POST', route_name='tax_id', renderer='json', permission='Taxes', trans=True) +def update(request): + json = request.json_body + item = request.dbsession.query(Tax).filter(Tax.id == uuid.UUID(request.matchdict['id'])).first() + item.name = json['Name'] + item.rate = json['Rate'] + transaction.commit() + item = request.dbsession.query(Tax).filter(Tax.id == item.id).first() + return tax_info(item) + + +@view_config(request_method='DELETE', route_name='tax_id', renderer='json', permission='Taxes', trans=True) +def delete(request): + item = request.dbsession.query(Tax).filter(Tax.id == uuid.UUID(request.matchdict['id'])).first() + request.dbsession.delete(item) + transaction.commit() + return {} + + +@view_config(request_method='GET', route_name='tax_id', renderer='json', permission='Authenticated') +def show_id(request): + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(Tax).filter(Tax.id == id).first() + return tax_info(item) + + +@view_config(request_method='GET', route_name='tax_list', renderer='json', permission='Authenticated') +def show_list(request): + list = request.dbsession.query(Tax).order_by(Tax.name).all() + taxes = [] + for item in list: + taxes.append({ + 'TaxID': item.id, + 'Name': item.name, + 'Rate': item.rate, + }) + return taxes + + +def tax_info(item): + return { + 'TaxID': item.id, + 'Name': item.name, + 'Rate': item.rate + } diff --git a/barker/views/user.py b/barker/views/user.py new file mode 100644 index 0000000..bcab857 --- /dev/null +++ b/barker/views/user.py @@ -0,0 +1,140 @@ +import re +import uuid + +import transaction +from pyramid.response import Response +from pyramid.view import view_config + +from barker.exceptions import ValidationFailure +from barker.models import Role, User + + +@view_config(request_method='PUT', route_name='user', renderer='json', permission='Users', trans=True) +def save(request): + json = request.json_body + item = User(json['Name'], json['Password'], json['LockedOut'], json['MsrString']) + request.dbsession.add(item) + add_roles(item, json['Roles'], request.dbsession) + transaction.commit() + item = request.dbsession.query(User).filter(User.id == item.id).first() + return user_info(item) + + +@view_config(request_method='POST', route_name='user_id', renderer='json', permission='Users', trans=True) +def update(request): + json = request.json_body + id = request.matchdict['id'] + p = re.compile('^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$') + if p.match(id): + item = request.dbsession.query(User).filter(User.id == uuid.UUID(id)).one() + else: + item = request.dbsession.query(User).filter(User.name.ilike(id)).first() + if item is None: + raise ValidationFailure('User name / id not found') + if 'Users' not in request.effective_principals: + item.name = json['Name'] + item.locked_out = json['LockedOut'] + add_roles(item, json['Roles'], request.dbsession) + + if json['Password'] != '' and json['Password'] != item.password: + item.password = json['Password'] + transaction.commit() + item = request.dbsession.query(User).filter(User.id == item.id).first() + return user_info(item) + + +@view_config(request_method='DELETE', route_name='user_id', renderer='json', permission='Users', trans=True) +def delete(request): + id = request.matchdict['id'] + if id is None: + response = Response("User is Null") + response.status_int = 500 + return response + else: + response = Response("User deletion not implemented") + response.status_int = 500 + return response + + +@view_config(request_method='POST', route_name='user_id', renderer='json', request_param='p', + permission='Authenticated', trans=True) +def update_password(request): + json = request.json_body + name = request.matchdict['id'] + old_password = json['OldPassword'] + new_password = json['NewPassword'] + found, user = User.auth(name, old_password, request.dbsession) + if found: + user.password = new_password + transaction.commit() + return found + + +@view_config(request_method='POST', route_name='user_id', renderer='json', request_param='m', + permission='Authenticated', trans=True) +def update_msr(request): + json = request.json_body + name = request.matchdict['id'] + old_password = json['OldPassword'] + new_msr = json['NewMsr'] + found, user = User.auth(name, old_password, request.dbsession) + if found: + user.msr_string = new_msr + transaction.commit() + return found + + +@view_config(request_method='GET', route_name='user_id', renderer='json', permission='Authenticated') +def show_id(request): + id = request.matchdict['id'] + p = re.compile('^[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}$') + if p.match(id): + item = request.dbsession.query(User).filter(User.id == uuid.UUID(id)).one() + else: + item = request.dbsession.query(User).filter(User.name.ilike(id)).first() + if item is None: + raise ValidationFailure('User name / id not found') + return user_info(item) + + +@view_config(request_method='GET', route_name='user_list', renderer='json', permission='Users') +def show_list(request): + list = request.dbsession.query(User).order_by(User.name).all() + return [{ + 'UserID': item.id, + 'Name': item.name, + 'LockedOut': item.locked_out, + 'Roles': [{ + 'RoleID': r.id, + 'Name': r.name + } for r in item.roles] + } for item in list] + + +@view_config(request_method='GET', route_name='user', renderer='json', request_param='names', + permission='Authenticated') +def show_name(request): + list = request.dbsession.query(User).filter(User.locked_out == False).order_by(User.name).all() + return [{'Name': item.name} for item in list] + + +def user_info(item): + return { + 'UserID': item.id, + 'Name': item.name, + 'Password': '', + 'LockedOut': item.locked_out, + 'Roles': [{ + 'RoleID': r.id, + 'name': r.name + } for r in item.roles]} + + +def add_roles(user, roles, dbsession): + new = {uuid.UUID(r['RoleID']) for r in roles} + old = {r.id for r in user.roles} + for role_id in new - old: + user.roles.append(dbsession.query(Role).filter(Role.id == role_id).one()) + ur = [r for r in user.roles if r.id in (old - new)] + for item in ur: + user.roles.remove(item) diff --git a/barker/views/voucher.py b/barker/views/voucher.py new file mode 100644 index 0000000..415b48c --- /dev/null +++ b/barker/views/voucher.py @@ -0,0 +1,395 @@ +import re +import uuid +from datetime import datetime +from decimal import Decimal + +import transaction +from pyramid.httpexceptions import HTTPForbidden +from pyramid.view import view_config +from sqlalchemy import func + +from barker.exceptions import ValidationFailure +from barker.models import ( + Inventory, + InventoryModifier, + Kot, + Settlement, + SettleOption, + Voucher, + VoucherFoodTable, + VoucherType, +) + + +@view_config(request_method='PUT', route_name='voucher', renderer='json', trans=True) +def save(request): + update_table = request.GET['u'] + json = request.json_body + + if not json['Printed'] and 'Print Kot' not in request.effective_principals: + raise HTTPForbidden("You are not allowed to print a kot") + + if json['Printed'] and 'Print Bill' not in request.effective_principals: + raise HTTPForbidden("You are not allowed to print bill") + + item = save_voucher(json, request.dbsession) + if update_table: + status = "printed" if item.is_printed else "running" + item.status = VoucherFoodTable(voucher_id=None, food_table_id=item.food_table_id, status=status) + request.dbsession.add(item.status) + transaction.commit() + item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first() + return voucher_info(item) + + +@view_config(request_method='POST', route_name='voucher_id', renderer='json', trans=True) +def update(request): + now = datetime.now() + update_table = request.GET['u'] + json = request.json_body + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(Voucher).filter(Voucher.id == id).first() + + if not json['Printed'] and 'Print Kot' not in request.effective_principals: + raise HTTPForbidden("You are not allowed to print a kot") + + if json['Printed'] and 'Print Bill' not in request.effective_principals: + raise HTTPForbidden("You are not allowed to print bill") + + if item.is_printed and 'Edit Printed Bill' not in request.effective_principals: + raise HTTPForbidden("You are not allowed to edit a printed bill") + + if item.is_printed and not json['Printed']: + transaction.abort() + raise ValidationFailure("This Bill is already printed\nCannot add a Kot to it.") + + if item.is_void: + transaction.abort() + raise ValidationFailure("This Bill is already void.\nReason: {0}".format(item.void_reason)) + + if item.is_printed and not json['Printed']: + transaction.abort() + raise ValidationFailure("This Bill is already printed\nCannot add a Kot to it.") + + item.pax = json['Pax'] + item.food_table_id = json['Table']['FoodTableID'] + item.customer_id = json['Customer']['CustomerID'] + item.voucher_type = json['VoucherType'] + if not item.is_printed and json['Printed']: + item.date = now + type = [1, 3] if item.voucher_type in [1, 3] else [item.voucher_type] + item.bill_id = request.dbsession.query( + func.coalesce(func.max(Voucher.bill_id), 0) + 1 + ).filter( + Voucher.voucher_type.in_(type) + ).scalar() + + item.is_printed = item.is_printed or json['Printed'] + item.user_id = json['User']['UserID'] + item.last_edit_date = now + for k in item.kots: + for i in k.inventories: + i.service_charge, i.service_tax_rate, i.vat_rate = get_sc_st_vat( + i.service_charge, i.service_tax_rate, i.vat_rate, item.voucher_type + ) + i.discount = next( + Decimal(inv['Discount']) for ko in json['Kots'] for inv in ko['Inventories'] if + uuid.UUID(inv['InventoryID']) == i.id + ) + for k in (k for k in json['Kots'] if k['KotID'] == '00000000-0000-0000-0000-000000000000'): + kot = Kot(item.id, item.food_table_id, item.date, item.user_id, dbsession=request.dbsession) + item.kots.append(kot) + request.dbsession.add(kot) + for index, i in enumerate(k['Inventories']): + sc, st, vat = get_sc_st_vat(i['ServiceCharge'], i['ServiceTaxRate'], i['VatRate'], json['VoucherType']) + inv = Inventory(kot.id, uuid.UUID(i['ProductID']), i['Quantity'], i['Price'], i['Discount'], + i['IsHappyHour'], sc, i['IsScTaxable'], i['ServiceTaxID'], st, i['VatID'], vat, index) + kot.inventories.append(inv) + request.dbsession.add(inv) + for m in i['Modifiers']: + mod = InventoryModifier(None, uuid.UUID(m['ModifierID']), 0) + inv.modifiers.append(mod) + request.dbsession.add(mod) + get_settlements(item, request.dbsession) + if update_table: + vft = request.dbsession.query(VoucherFoodTable).filter(VoucherFoodTable.voucher_id == item.id).first() + status = "printed" if item.is_printed else "running" + if vft is None: + item.status = VoucherFoodTable(voucher_id=None, food_table_id=item.food_table_id, status=status) + request.dbsession.add(item.status) + else: + vft.status = status + vft.food_table_id = item.food_table_id + transaction.commit() + item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first() + return voucher_info(item) + + +@view_config(request_method='POST', route_name='voucher_settle', renderer='json', permission='Settle Bill', trans=False) +def settle_voucher(request): + update_table = request.GET['u'] + json = request.json_body + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(Voucher).filter(Voucher.id == id).first() + + allowed = [SettleOption.AMOUNT(), SettleOption.ROUND_OFF(), SettleOption.UNSETTLED()] + for i in (j for j in json if j['Amount'] != 0): + amount = i['Amount'] + so = i['Settled'] + so_settled = [s for s in item.settlements if s.settled == so] + if len(so_settled) == 1: + so_settled[0].amount = amount + else: + s = Settlement(item.id, so, amount) + item.settlements.append(s) + request.dbsession.add(s) + allowed.append(so) + for i in (s for s in item.settlements if s.settled not in allowed): + item.settlements.remove(i) + request.dbsession.delete(i) + unsettled = get_settlements(item, request.dbsession) + + if unsettled == 0 and update_table: + request.dbsession.query(VoucherFoodTable).filter(VoucherFoodTable.voucher_id == item.id).delete() + transaction.commit() + item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first() + return voucher_info(item) + + +@view_config(request_method='POST', route_name='voucher_reprint', renderer='json', permission='Edit Printed Bill', trans=True) #Permission error +def voucher_change(request): + json = request.json_body + id = uuid.UUID(request.matchdict['id']) + item = save_voucher(json, request.dbsession) + old = request.dbsession.query(Voucher).filter(Voucher.id == id).first() + old.void = True + old.void_reason = "Bill Discounted / Changed. New Bill ID is {0}".format(item.full_bill_id) + # TODO: Set the Void Settlement + + if old.status is None: + item.status = VoucherFoodTable(voucher_id=None, food_table_id=item.food_table_id, status="printed") + request.dbsession.add(item.status) + else: + request.dbsession.query( + VoucherFoodTable + ).filter( + VoucherFoodTable.voucher_id == old.id + ).update({VoucherFoodTable.voucher_id: item.id}) + transaction.commit() + item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first() + return voucher_info(item) + + +@view_config(request_method='POST', route_name='voucher_void', renderer='json', permission='Void Bill', trans=True) +def void_voucher(request): + update_table = request.GET['u'] + json = request.json_body + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(Voucher).filter(Voucher.id == id).first() + + item.void = True + item.void_reason = json['Reason'] + + amount = round(-1 * sum(i.amount for k in item.kots for i in k.inventories), 5) * -1 + s = Settlement(item.id, SettleOption.VOID(), amount) + item.settlements.append(s) + request.dbsession.add(s) + allowed = [SettleOption.AMOUNT(), SettleOption.ROUND_OFF(), SettleOption.UNSETTLED(), SettleOption.VOID()] + for i in (s for s in item.settlements if s.settled not in allowed): + item.settlements.remove(i) + request.dbsession.delete(i) + get_settlements(item, request.dbsession) + + if update_table: + request.dbsession.delete(item.status) + transaction.commit() + item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first() + return voucher_info(item) + + +@view_config(request_method='POST', route_name='voucher_split', renderer='json', permission='Split Bill', trans=True) +def split_voucher(request): + json = request.json_body + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(Voucher).filter(Voucher.id == id).first() + item.void = True + item.void_reason = 'Bill Split' + # TODO: Set the Void Settlement + + new_one = save_voucher(json['One'], request.dbsession) + new_two = save_voucher(json['Two'], request.dbsession) + + status = "printed" if item.is_printed else "running" + new_one.status = VoucherFoodTable(voucher_id=None, food_table_id=new_one.food_table_id, status=status) + + request.dbsession.add(new_one.status) + request.dbsession.query( + VoucherFoodTable + ).filter( + VoucherFoodTable.voucher_id == item.id + ).update({VoucherFoodTable.voucher_id: new_two.id}) + + transaction.commit() + + +@view_config(request_method='GET', route_name='voucher_id', renderer='json', permission='Authenticated') +def show_id(request): + id = uuid.UUID(request.matchdict['id']) + item = request.dbsession.query(Voucher).filter(Voucher.id == id).first() + return voucher_info(item) + + +@view_config(request_method='GET', route_name='voucher', request_param='b', renderer='json', permission='Authenticated') +def show(request): + bill_id = request.GET['b'] + item = request.dbsession.query(Voucher) + if re.compile('^\d{2,}-\d{4}$').match(bill_id): + item = item.filter( + Voucher.bill_id == int(bill_id.replace('-', '')), + Voucher.voucher_type.in_([1, 3]) + ) + elif re.compile('^NC-\d+$').match(bill_id): + item = item.filter( + Voucher.bill_id == int(bill_id.replace('NC-', '')), + Voucher.voucher_type == 2 + ) + elif re.compile('^ST-\d+$').match(bill_id): + item = item.filter( + Voucher.bill_id == int(bill_id.replace('ST-', '')), + Voucher.voucher_type == 4 + ) + item = item.first() + if item is None: + return {} + return voucher_info(item) + + +def voucher_info(item): + return { + 'VoucherID': item.id, + 'Date': item.date.strftime('%d-%b-%Y %H:%M:%S'), + 'Pax': item.pax, + 'User': {'UserID': item.user_id, 'Name': item.user.name}, + 'CreationDate': item.creation_date.strftime('%d-%b-%Y %H:%M:%S'), + 'LastEditDate': item.last_edit_date.strftime('%d-%b-%Y %H:%M:%S'), + 'BillID': item.bill_id, + 'Table': {'FoodTableID': item.food_table_id, 'Name': item.food_table.name}, + 'Customer': {'CustomerID': item.customer_id, 'Name': item.customer.name}, + 'Settlements': [], + 'Narration': item.narration, + 'Void': item.is_void, + 'VoidReason': item.void_reason, + 'Printed': item.is_printed, + 'VoucherType': item.voucher_type, + 'KotID': item.kot_id, + 'Kots': [ + { + 'KotID': k.id, 'Code': k.code, 'Date': k.date.strftime('%d-%b-%Y %H:%M:%S'), + 'User': {'UserID': k.user_id, 'Name': k.user.name}, + 'Inventories': [ + { + 'InventoryID': i.id, + 'SortOrder': i.sort_order, + 'Product': + { + 'ProductID': i.product_id, + 'Name': i.product.name, + 'Units': i.product.units, + 'ProductGroup': + { + 'ProductGroupID': i.product.product_group_id, + 'Name': i.product.product_group.name, + 'GroupType': i.product.product_group.group_type, + 'DiscountLimit': i.product.product_group.discount_limit + } + }, + 'Quantity': i.quantity, + 'Price': i.price, + 'IsHappyHour': i.is_happy_hour, + 'ServiceCharge': i.service_charge, + 'IsScTaxable': i.is_sc_taxable, + 'ServiceTaxRate': i.service_tax_rate, + 'VatRate': i.vat_rate, + 'ServiceTax': {'TaxID': i.service_tax_id, 'Name': i.service_tax.name}, + 'Vat': {'TaxID': i.vat_id, 'Name': i.vat.name}, + 'Discount': i.discount, + 'InventoryModifier': [ + { + 'Modifier': { + 'ModifierID': m.modifier.id, + 'Name': m.modifier.name, + 'ShowInBill': m.modifier.show_in_bill + }, + 'Price': m.price + } for m in i.modifiers] + } for i in k.inventories] + } for k in item.kots], + 'Reprints': [] + } + + +def get_sc_st_vat(sc, st, vat, voucher_type): + if voucher_type == VoucherType.STAFF.value: + return 0, 0, 0 + elif voucher_type == VoucherType.NO_CHARGE.value: + return 0, 0, 0 + else: # voucher_type in [REGULAR_BILL, TAKE_AWAY]: + return sc, st, vat + + +def get_settlements(voucher, dbsession): + amount = round(-1 * sum(sum(i.amount for i in k.inventories) for k in voucher.kots), 5) + so_amount = [s for s in voucher.settlements if s.settled == SettleOption.AMOUNT()] + if len(so_amount) == 1: + so_amount[0].amount = amount + else: + s = Settlement(voucher.id, SettleOption.AMOUNT(), amount) + voucher.settlements.append(s) + dbsession.add(s) + + round_off = round(amount) - amount + so_round_off = [s for s in voucher.settlements if s.settled == SettleOption.ROUND_OFF()] + if len(so_round_off) == 1 and round_off != 0: + so_round_off[0].amount = round_off + elif len(so_round_off) == 1 and round_off == 0: + voucher.settlements.remove(so_round_off[0]) + dbsession.delete(so_round_off[0]) + elif len(so_round_off) == 0 and round_off != 0: + s = Settlement(voucher.id, SettleOption.ROUND_OFF(), round_off) + voucher.settlements.append(s) + dbsession.add(s) + + unsettled = sum(s.amount for s in voucher.settlements if s.settled != SettleOption.UNSETTLED()) + so_unsettled = [s for s in voucher.settlements if s.settled == SettleOption.UNSETTLED()] + if len(so_unsettled) == 1 and unsettled != 0: + so_unsettled[0].amount = unsettled + elif len(so_unsettled) == 1 and unsettled == 0: + voucher.settlements.remove(so_unsettled[0]) + dbsession.delete(so_unsettled[0]) + elif len(so_unsettled) == 0 and unsettled != 0: + s = Settlement(voucher.id, SettleOption.UNSETTLED(), unsettled) + voucher.settlements.append(s) + dbsession.add(s) + return unsettled + + +def save_voucher(json, dbsession): + item = Voucher(json['Pax'], json['Table']['FoodTableID'], json['Customer']['CustomerID'], json['Printed'], + json['VoucherType'], json['User']['UserID'], dbsession) + dbsession.add(item) + for k in json['Kots']: + kot = Kot(item.id, item.food_table_id, item.date, item.user_id, dbsession=dbsession) + item.kots.append(kot) + dbsession.add(kot) + for index, i in enumerate(k['Inventories']): + sc, st, vat = get_sc_st_vat(i['ServiceCharge'], i['ServiceTaxRate'], i['VatRate'], json['VoucherType']) + inv = Inventory(kot.id, uuid.UUID(i['ProductID']), i['Quantity'], i['Price'], i['Discount'], + i['IsHappyHour'], sc, i['IsScTaxable'], i['ServiceTaxID'], st, i['VatID'], vat, index) + kot.inventories.append(inv) + dbsession.add(inv) + for m in i['Modifiers']: + mod = InventoryModifier(None, uuid.UUID(m['ModifierID']), 0) + inv.modifiers.append(mod) + dbsession.add(mod) + get_settlements(item, dbsession) + return item diff --git a/container.ini b/container.ini new file mode 100644 index 0000000..1e098b9 --- /dev/null +++ b/container.ini @@ -0,0 +1,50 @@ +[app:main] +use = egg:barker + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en +secret_key = secret + +[server:main] +use = egg:waitress#main +host = 0.0.0.0 +port = 80 + +# Begin logging configuration + +[loggers] +keys = root, barker, sqlalchemy.engine.base + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console + +[logger_barker] +level = WARNING +handlers = +qualname = barker + +[logger_sqlalchemy.engine.base] +level = DEBUG +handlers = +qualname = sqlalchemy.engine.base + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/development.ini b/development.ini new file mode 100644 index 0000000..7bbd806 --- /dev/null +++ b/development.ini @@ -0,0 +1,51 @@ +[app:main] +use = egg:barker + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en +# sqlalchemy.url = postgresql://hops:123456@localhost:8765/hops +sqlalchemy.url = postgresql://postgres:123456@localhost:5432/petty + +[server:main] +use = egg:waitress#main +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, barker, sqlalchemy.engine.base + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console + +[logger_barker] +level = WARNING +handlers = +qualname = barker + +[logger_sqlalchemy.engine.base] +level = DEBUG +handlers = +qualname = sqlalchemy.engine.base + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/production.ini b/production.ini new file mode 100644 index 0000000..e069d67 --- /dev/null +++ b/production.ini @@ -0,0 +1,44 @@ +[app:main] +use = egg:barker + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en + +[server:main] +use = egg:waitress#main +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, barker + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console + +[logger_barker] +level = WARN +handlers = +qualname = barker + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7540447 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +pyramid +waitress +transaction +zope.sqlalchemy +SQLAlchemy +psycopg2 + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..384a5d5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[nosetests] +match = ^test +nocapture = 1 +cover-package = barker +with-coverage = 1 +cover-erase = 1 + +[compile_catalog] +directory = barker/locale +domain = barker +statistics = true + +[extract_messages] +add_comments = TRANSLATORS: +output_file = barker/locale/barker.pot +width = 80 + +[init_catalog] +domain = barker +input_file = barker/locale/barker.pot +output_dir = barker/locale + +[update_catalog] +domain = barker +input_file = barker/locale/barker.pot +output_dir = barker/locale +previous = true diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e59b039 --- /dev/null +++ b/setup.py @@ -0,0 +1,50 @@ +import os + +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'README.txt')) as f: + README = f.read() +with open(os.path.join(here, 'CHANGES.txt')) as f: + CHANGES = f.read() + +requires = [ + 'pyramid', + 'waitress', + 'pyramid_tm', + 'transaction', + 'zope.sqlalchemy', + 'SQLAlchemy', + 'psycopg2', +] + +setup(name='barker', + version='4.0', + description='barker', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + "Programming Language :: Python", + "Framework :: Pylons", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + ], + author='Tanshu', + author_email='barker@tanshu.com', + url='http://tanshu.com', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + install_requires=requires, + tests_require=requires, + test_suite="barker", + entry_points={ + 'paste.app_factory': [ + 'main = barker:main', + ], + 'console_scripts': [ + 'initdb = barker.scripts.initializedb:main', + 'fixtures = barker.scripts.fixtures:main', + ], + } + )