From c81b92c3364048eee0c69e0a127c6830b574e356 Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Thu, 8 Aug 2019 13:31:30 +0530 Subject: [PATCH] Fix: import script to fit the new structure of voucher table (is_printed field removed, voucher_type != KOT is now assumed to be printed) Fix: Take-away bill type is now removed Fix: Table overview now shows the right amounts Voucher Save and Update should now work Discounts now working (permissions are not checked) --- DB/migrate.cmd | 2 +- barker/models/voucher.py | 94 ++++---- barker/views/food_table.py | 4 +- barker/views/menu_category.py | 3 +- barker/views/sale_category.py | 21 +- barker/views/voucher/__init__.py | 30 +-- barker/views/voucher/change.py | 4 +- barker/views/voucher/save.py | 122 +++++++---- barker/views/voucher/show.py | 4 +- barker/views/voucher/split.py | 6 +- barker/views/voucher/update.py | 204 ++++++++++-------- .../sale-category/sale-category.service.ts | 8 + bookie/src/app/sales/bill.service.ts | 54 ++++- bookie/src/app/sales/bills/bill.ts | 3 +- .../app/sales/discount/discount-datasource.ts | 16 ++ .../app/sales/discount/discount.component.css | 4 + .../sales/discount/discount.component.html | 30 +++ .../sales/discount/discount.component.spec.ts | 25 +++ .../app/sales/discount/discount.component.ts | 52 +++++ .../app/sales/home/sales-home.component.html | 3 + .../app/sales/home/sales-home.component.ts | 68 ++++-- .../sales/modifiers/modifiers.component.ts | 2 +- .../sales/quantity/quantity.component.html | 9 +- bookie/src/app/sales/sales.module.ts | 15 +- 24 files changed, 534 insertions(+), 249 deletions(-) create mode 100644 bookie/src/app/sales/discount/discount-datasource.ts create mode 100644 bookie/src/app/sales/discount/discount.component.css create mode 100644 bookie/src/app/sales/discount/discount.component.html create mode 100644 bookie/src/app/sales/discount/discount.component.spec.ts create mode 100644 bookie/src/app/sales/discount/discount.component.ts diff --git a/DB/migrate.cmd b/DB/migrate.cmd index 43f3392..400235d 100644 --- a/DB/migrate.cmd +++ b/DB/migrate.cmd @@ -16,7 +16,7 @@ call:copyQuery m-Sections "SELECT 'INSERT INTO sections(id, name) VALUES (''' + call:copyQuery n-Printers "SELECT 'INSERT INTO printers(id, name, address, cut_code) VALUES (''' + CAST(NewID() AS Nvarchar(36)) + ''', ''' + Printer + ''', ''' + Printer + ''', ''' + CutCode + ''');' FROM Test.dbo.PrintLocations GROUP BY Printer, CutCode;" call:copyQuery o-SectionPrinters "SELECT 'INSERT INTO section_printers(id, menu_category_id, section_id, printer_id, copies) VALUES (''' + CAST(PrintLocationID AS Nvarchar(36)) + ''', ' + COALESCE('''' + CAST(ProductGroupID AS Nvarchar(36)) + '''','null') + ', ' + '(select id from sections where name = ''' + Location + ''')' + ', ' + '(select id from printers where name = ''' + Printer + ''')' + ', ' + CAST(Copies AS nvarchar(36)) + ');' FROM Test.dbo.PrintLocations;" -call:copyQuery p-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') + ', (SELECT id from users limit 1), ' + '''' + 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 p-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, voucher_type, kot_id) VALUES (''' + CAST(VoucherID AS Nvarchar(36)) + ''', ' + '''' + CONVERT(Nvarchar(36), Date, 126) + ''', ' + COALESCE(CAST(Pax AS Nvarchar(36)), 'null') + ', (SELECT id from users limit 1), ' + '''' + 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 = 0 THEN '0' ELSE CAST(VoucherType AS Nvarchar(36)) END + ', ' + CAST(KotID AS Nvarchar(36)) + ');' FROM Test.dbo.Vouchers;" call:copyQuery q-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) + ''', (SELECT id from users limit 1));' FROM Test.dbo.Kots;" call:copyQuery r-Inventories "SELECT 'INSERT INTO inventories(id, kot_id, product_id, sort_order, quantity, price, is_happy_hour, tax_rate, tax_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(VatRate AS Nvarchar(36)) + ', ' + '''' + CAST(VatID AS Nvarchar(36)) + '''' + ', ' + CAST(Discount AS Nvarchar(36)) + ');' FROM Test.dbo.Inventories;" call:copyQuery s-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;" diff --git a/barker/models/voucher.py b/barker/models/voucher.py index 61b65da..7815b13 100644 --- a/barker/models/voucher.py +++ b/barker/models/voucher.py @@ -1,5 +1,5 @@ from datetime import datetime -from enum import IntEnum +from enum import Enum import uuid from decimal import Decimal @@ -16,12 +16,12 @@ from sqlalchemy import ( func, case, ) -from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm import relationship, backref, synonym from barker.models.guidtype import GUID from .meta import Base -class VoucherType(IntEnum): +class VoucherType(Enum): KOT = 0 REGULAR_BILL = 1 NO_CHARGE = 2 @@ -49,9 +49,15 @@ class GuestBook(Base): class Overview(Base): __tablename__ = "overview" id = Column("id", GUID(), primary_key=True, default=uuid.uuid4) - voucher_id = Column("voucher_id", GUID(), ForeignKey("vouchers.id"), unique=True) + 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"), unique=True + "food_table_id", + GUID(), + ForeignKey("food_tables.id"), + nullable=False, + unique=True, ) guest_book_id = Column( "guest_book_id", GUID(), ForeignKey("guest_book.id"), unique=True @@ -62,11 +68,12 @@ class Overview(Base): food_table = relationship("FoodTable", backref=backref("status", uselist=False)) guest = relationship("GuestBook", backref=backref("status", uselist=False)) - def __init__(self, voucher_id, food_table_id, guest_book_id, status): + def __init__(self, voucher_id, food_table_id, guest_book_id, status, id_=None): self.voucher_id = voucher_id self.food_table_id = food_table_id self.guest_book_id = guest_book_id self.status = status + self.id = id_ class InventoryModifier(Base): @@ -109,8 +116,7 @@ class Voucher(Base): 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) + _voucher_type = Column("voucher_type", Integer, nullable=False) user_id = Column("user_id", GUID(), ForeignKey("users.id"), nullable=False) user = relationship("User", backref="vouchers") @@ -136,61 +142,57 @@ class Voucher(Base): cascade_backrefs=False, ) - @property - def __name__(self): - return self.name + def _get_voucher_type(self): + return VoucherType(self._voucher_type) + + def _set_voucher_type(self, voucher_type): + self._voucher_type = voucher_type.value + + voucher_type = property(_get_voucher_type, _set_voucher_type) + voucher_type = synonym("_voucher_type", descriptor=voucher_type) def __init__( self, + date, pax, + bill_id, + kot_id, food_table_id, customer_id, - is_printed, voucher_type, user_id, - dbsession, ): - now = datetime.now() - self.date = now + self.date = date 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.creation_date = date + self.last_edit_date = date + self.bill_id = bill_id + self.kot_id = kot_id 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: + if self.voucher_type == VoucherType.KOT: return "K-" + str(self.kot_id) - if self.voucher_type == VoucherType.NO_CHARGE.value: + if self.voucher_type == VoucherType.NO_CHARGE: return "NC-" + str(self.bill_id) - if self.voucher_type == VoucherType.STAFF.value: + if self.voucher_type == VoucherType.STAFF: return "ST-" + str(self.bill_id) - if self.voucher_type in [ - VoucherType.TAKE_AWAY.value, - VoucherType.REGULAR_BILL.value, - ]: + if self.voucher_type == VoucherType.REGULAR_BILL: return str(self.bill_id // 10000) + "-" + str(self.bill_id % 10000) else: raise Exception + @property + def amount(self): + return sum(i.amount for k in self.kots for i in k.inventories) + class Kot(Base): __tablename__ = "kots" @@ -212,15 +214,15 @@ class Kot(Base): def __init__( self, voucher_id=None, + code=None, food_table_id=None, date=None, user_id=None, - id=None, - dbsession=None, + id_=None, ): - self.id = id + self.id = id_ self.voucher_id = voucher_id - self.code = dbsession.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar() + self.code = code self.food_table_id = food_table_id self.date = date self.user_id = user_id @@ -310,7 +312,7 @@ class Inventory(Base): @hybrid_property def effective_price(self): - return 0 if self.is_happy_hour == True else self.price + return 0 if self.is_happy_hour else self.price @effective_price.expression def effective_price(cls): @@ -320,9 +322,17 @@ class Inventory(Base): def net(self): return self.effective_price * self.quantity * (1 - self.discount) + @net.expression + def net(cls): + return cls.effective_price * cls.quantity * (1 - cls.discount) + @hybrid_property def tax_amount(self): - return self.net_taxable * self.tax_rate + return self.net * self.tax_rate + + @tax_amount.expression + def tax_amount(cls): + return cls.net * cls.tax_rate @hybrid_property def amount(self): diff --git a/barker/views/food_table.py b/barker/views/food_table.py index 2f14091..ddfc6e9 100644 --- a/barker/views/food_table.py +++ b/barker/views/food_table.py @@ -104,7 +104,7 @@ def show_voucher(request): .first() ) if voucher is not None: - return food_table_info(voucher.food_table) + return food_table_info(voucher.food_table, request.dbsession) return {} @@ -143,7 +143,7 @@ def show_running(request): ft["voucherId"] = item.status.voucher_id ft["pax"] = item.status.voucher.pax ft["date"] = item.status.voucher.date.strftime("%d-%b-%Y %H:%M") - ft["amount"] = 12345 + ft["amount"] = item.status.voucher.amount if item.status.guest is not None: ft["guest"] = item.status.guest.customer.name food_tables.append(ft) diff --git a/barker/views/menu_category.py b/barker/views/menu_category.py index b3522cd..cb6387a 100644 --- a/barker/views/menu_category.py +++ b/barker/views/menu_category.py @@ -172,7 +172,8 @@ def sale_list(request): "isNotAvailable": p.is_not_available, "sortOrder": p.sort_order, "quantity": p.quantity, - } for p in products + } + for p in products ] menu_categories.append(pg) return menu_categories diff --git a/barker/views/sale_category.py b/barker/views/sale_category.py index 80acdcc..f39708f 100644 --- a/barker/views/sale_category.py +++ b/barker/views/sale_category.py @@ -109,10 +109,23 @@ def show_list(request): .order_by(SaleCategory.name) .all() ) - sale_categories = [] - for item in list_: - sale_categories.append(sale_category_info(item, request.dbsession)) - return sale_categories + return [sale_category_info(item, request.dbsession) for item in list_] + + +@view_config( + request_method="GET", + route_name="v1_sale_categories_list", + renderer="json", + request_param="d", + permission="Authenticated", +) +def show_list_for_discount(request): + list_ = ( + request.dbsession.query(SaleCategory) + .order_by(SaleCategory.name) + .all() + ) + return [{"id": item.id, "name": item.name, "discount": 0} for item in list_] def sale_category_info(item, dbsession): diff --git a/barker/views/voucher/__init__.py b/barker/views/voucher/__init__.py index 91da2e0..ac58aab 100644 --- a/barker/views/voucher/__init__.py +++ b/barker/views/voucher/__init__.py @@ -1,34 +1,14 @@ -# 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, -# Overview, -# VoucherType, -# ) from barker.models import VoucherType, Settlement, SettleOption +from barker.models.validation_exception import ValidationError def get_tax(tax, voucher_type): - if voucher_type == VoucherType.STAFF.value: + if voucher_type in [VoucherType.STAFF, VoucherType.NO_CHARGE]: return 0 - elif voucher_type == VoucherType.NO_CHARGE.value: - return 0 - else: # voucher_type in [REGULAR_BILL, TAKE_AWAY]: + elif voucher_type in [VoucherType.KOT, VoucherType.REGULAR_BILL]: return tax + else: + raise ValidationError("Unexpected Voucher Type") def get_settlements(voucher, dbsession): diff --git a/barker/views/voucher/change.py b/barker/views/voucher/change.py index ca395f4..70f8404 100644 --- a/barker/views/voucher/change.py +++ b/barker/views/voucher/change.py @@ -4,7 +4,7 @@ import transaction from pyramid.view import view_config from barker.models import Voucher, Overview -from barker.views.voucher.save import save_voucher +from barker.views.voucher.save import save from barker.views.voucher.show import voucher_info @@ -18,7 +18,7 @@ from barker.views.voucher.show import voucher_info def voucher_change(request): json = request.json_body id_ = uuid.UUID(request.matchdict["id"]) - item = save_voucher(json, request.dbsession) + item = save(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( diff --git a/barker/views/voucher/save.py b/barker/views/voucher/save.py index 16a6819..086c894 100644 --- a/barker/views/voucher/save.py +++ b/barker/views/voucher/save.py @@ -1,8 +1,11 @@ +import datetime import uuid +from decimal import Decimal import transaction from pyramid.httpexceptions import HTTPForbidden from pyramid.view import view_config +from sqlalchemy import func from barker.models import ( Overview, @@ -12,7 +15,10 @@ from barker.models import ( InventoryModifier, VoucherType, Product, + GuestBook, + FoodTable, ) +from barker.models.validation_exception import ValidationError from barker.views.voucher import get_tax, get_settlements from barker.views.voucher.show import voucher_info @@ -21,63 +27,102 @@ from barker.views.voucher.show import voucher_info request_method="POST", route_name="v1_vouchers_new", renderer="json", trans=True ) def save(request): + json = request.json_body + now = datetime.datetime.now() update_table = request.GET["u"] == "true" voucher_type = VoucherType[request.GET["p"]] - guest_book_id = request.GET.get("g", None) - if guest_book_id is not None: - guest_book_id = uuid.UUID(guest_book_id) - json = request.json_body + guest_book = get_guest_book(request.GET.get("g", None), request.dbsession) - if not json["isPrinted"] and "Print Kot" not in request.effective_principals: - raise HTTPForbidden("You are not allowed to print a kot") - - if json["isPrinted"] and "Print Bill" not in request.effective_principals: - raise HTTPForbidden("You are not allowed to print bill") - - item = save_voucher( - json, voucher_type, uuid.UUID(request.authenticated_userid), request.dbsession + table = ( + request.dbsession.query(FoodTable) + .filter(FoodTable.id == uuid.UUID(json["table"]["id"])) + .first() ) + + check_permissions(voucher_type, request.effective_principals) + + bill_id = get_bill_id(voucher_type, request.dbsession) + kot_id = request.dbsession.query( + func.coalesce(func.max(Voucher.kot_id), 0) + 1 + ).scalar() + + item = Voucher( + now, + guest_book.pax if guest_book is not None else table.seats, + bill_id, + kot_id, + json["table"]["id"], + json["customer"]["id"] if "id" in json["customer"] else None, + voucher_type, + uuid.UUID(request.authenticated_userid) + ) + request.dbsession.add(item) + add_kots(json, item, voucher_type, request.dbsession) + + if len(item.kots) == 0 or len(item.kots[0].inventories) == 0: + raise ValidationError("Please add some products!") + if update_table: - status = "printed" if item.is_printed else "running" - item.status = Overview( - voucher_id=None, - food_table_id=item.food_table_id, - guest_book_id=guest_book_id, - status=status, - ) - request.dbsession.add(item.status) + do_update_table(item, voucher_type, guest_book, request.dbsession) transaction.commit() item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first() return voucher_info(item) -def save_voucher(json, voucher_type, user_id, dbsession): - item = Voucher( - json["pax"], - json["table"]["id"], - json["customer"]["id"] if "id" in json["customer"] else None, - json["isPrinted"], - voucher_type, - user_id, - dbsession, +def check_permissions(voucher_type, permissions): + if voucher_type == VoucherType.KOT and "Print Kot" not in permissions: + raise HTTPForbidden("You are not allowed to print a kot") + + if voucher_type != VoucherType.KOT and "Print Bill" not in permissions: + raise HTTPForbidden("You are not allowed to print bill") + + +def get_guest_book(guest_book_id, dbsession): + if guest_book_id is None: + return guest_book_id + return ( + dbsession.query(GuestBook) + .filter(GuestBook.id == uuid.UUID(guest_book_id)) + .first() ) - dbsession.add(item) + + +def get_bill_id(voucher_type, dbsession): + if voucher_type == VoucherType.KOT: + return None + return ( + dbsession.query(func.coalesce(func.max(Voucher.bill_id), 0) + 1) + .filter(Voucher.voucher_type == voucher_type.value) + .scalar() + ) + + +def do_update_table(item, voucher_type, guest_book, dbsession): + status = "running" if voucher_type == VoucherType.KOT else "printed" + item.status = Overview( + voucher_id=None, + food_table_id=item.food_table_id, + guest_book_id=guest_book.id if guest_book is not None else None, + status=status, + ) + dbsession.add(item.status) + + +def add_kots(json, item, voucher_type, dbsession): for k in json["kots"]: - kot = Kot( - item.id, item.food_table_id, item.date, item.user_id, dbsession=dbsession - ) + code = dbsession.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar() + kot = Kot(item.id, code, item.food_table_id, item.date, item.user_id) item.kots.append(kot) dbsession.add(kot) for index, i in enumerate(k["inventories"]): - product_id = uuid.UUID(i["product"]["id"]) - product = dbsession.query(Product).filter(Product.id == product_id).first() + product = dbsession.query(Product).filter(Product.id == uuid.UUID(i["product"]["id"])).first() tax_rate = get_tax(product.sale_category.tax.rate, voucher_type) inv = Inventory( kot.id, - product_id, - i["quantity"], + product.id, + round(Decimal(i["quantity"]), 2), product.price, - i["discount"], + round(Decimal(i["discount"]), 5), i["isHappyHour"], product.sale_category.tax_id, tax_rate, @@ -90,4 +135,3 @@ def save_voucher(json, voucher_type, user_id, dbsession): inv.modifiers.append(mod) dbsession.add(mod) get_settlements(item, dbsession) - return item diff --git a/barker/views/voucher/show.py b/barker/views/voucher/show.py index 935b2dc..f8bbd7f 100644 --- a/barker/views/voucher/show.py +++ b/barker/views/voucher/show.py @@ -97,8 +97,7 @@ def voucher_info(item): "narration": item.narration, "void": item.is_void, "voidReason": item.void_reason, - "isPrinted": item.is_printed, - "voucherType": item.voucher_type, + "voucherType": item.voucher_type.value, "kotId": item.kot_id, "kots": [ { @@ -156,7 +155,6 @@ def voucher_blank(table, guest): "pax": table.seats if guest is None else guest.pax, "table": {"id": table.id, "name": table.name}, "customer": {"id": guest.customer_id, "name": guest.customer.name} if guest is not None else {}, - "isPrinted": False, "kots": [] } diff --git a/barker/views/voucher/split.py b/barker/views/voucher/split.py index d56ecb0..ec11eff 100644 --- a/barker/views/voucher/split.py +++ b/barker/views/voucher/split.py @@ -4,7 +4,7 @@ import transaction from pyramid.view import view_config from barker.models import Voucher, Overview -from barker.views.voucher.save import save_voucher +from barker.views.voucher.save import save @view_config( @@ -22,8 +22,8 @@ def split_voucher(request): 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) + new_one = save(json["One"], request.dbsession) + new_two = save(json["Two"], request.dbsession) status = "printed" if item.is_printed else "running" new_one.status = Overview( diff --git a/barker/views/voucher/update.py b/barker/views/voucher/update.py index 7cea6a1..119a7b4 100644 --- a/barker/views/voucher/update.py +++ b/barker/views/voucher/update.py @@ -8,7 +8,16 @@ from pyramid.view import view_config from sqlalchemy import func from barker.exceptions import ValidationFailure -from barker.models import Voucher, Kot, Inventory, InventoryModifier, Overview +from barker.models import ( + Voucher, + Kot, + Inventory, + InventoryModifier, + Overview, + VoucherType, + GuestBook, + Product, +) from barker.views.voucher import get_tax, get_settlements from barker.views.voucher.show import voucher_info @@ -17,22 +26,89 @@ from barker.views.voucher.show import voucher_info request_method="PUT", route_name="v1_vouchers_id", renderer="json", trans=True ) def update(request): - now = datetime.now() - update_table = request.GET["u"] == "true" json = request.json_body - id = uuid.UUID(request.matchdict["id"]) - item = request.dbsession.query(Voucher).filter(Voucher.id == id).first() + now = datetime.datetime.now() + update_table = request.GET["u"] == "true" + voucher_type = VoucherType[request.GET["p"]] + guest_book = get_guest_book(request.GET.get("g", None), request.dbsession) + item = ( + request.dbsession.query(Voucher) + .filter(Voucher.id == uuid.UUID(request.matchdict["id"])) + .first() + ) - if not json["Printed"] and "Print Kot" not in request.effective_principals: + check_permissions(item, voucher_type, request.effective_principals) + + if guest_book is not None: + item.pax = guest_book.pax + item.food_table_id = json["table"]["id"] + if "customer" in json and "id" in json["customer"]: + item.customer_id = json["customer"]["id"] + if item.voucher_type == VoucherType.KOT and voucher_type != VoucherType.KOT: + item.date = now + item.bill_id = get_bill_id(voucher_type, request.dbsession) + item.voucher_type = voucher_type + item.user_id = uuid.UUID(request.authenticated_userid) + item.last_edit_date = now + for k in item.kots: + for i in k.inventories: + i.tax_rate = get_tax(i.tax_rate, voucher_type) + i.discount = next( + round(Decimal(inv["discount"]), 5) + for ko in json["kots"] + for inv in ko["inventories"] + if uuid.UUID(inv["id"]) == i.id + ) + for k in (k for k in json["kots"] if "id" not in k and len(k["inventories"]) > 0): + code = request.dbsession.query( + func.coalesce(func.max(Kot.code), 0) + 1 + ).scalar() + kot = Kot(item.id, code, item.food_table_id, item.date, item.user_id) + item.kots.append(kot) + request.dbsession.add(kot) + for index, i in enumerate(k["inventories"]): + product = ( + request.dbsession.query(Product) + .filter(Product.id == uuid.UUID(i["product"]["id"])) + .first() + ) + tax_rate = get_tax(product.sale_category.tax.rate, voucher_type) + inv = Inventory( + kot.id, + product.id, + round(Decimal(i["quantity"]), 2), + product.price, + round(Decimal(i["discount"]), 5), + i["isHappyHour"], + product.sale_category.tax_id, + tax_rate, + index, + ) + kot.inventories.append(inv) + request.dbsession.add(inv) + for m in i["modifiers"]: + mod = InventoryModifier(None, uuid.UUID(m["id"]), 0) + inv.modifiers.append(mod) + request.dbsession.add(mod) + get_settlements(item, request.dbsession) + if update_table: + do_update_table(item, voucher_type, guest_book, request.dbsession) + transaction.commit() + item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first() + return voucher_info(item) + + +def check_permissions(item, voucher_type, permissions): + if voucher_type == VoucherType.KOT and "Print Kot" not in permissions: raise HTTPForbidden("You are not allowed to print a kot") - if json["Printed"] and "Print Bill" not in request.effective_principals: + if voucher_type != VoucherType.KOT and "Print Bill" not in permissions: raise HTTPForbidden("You are not allowed to print bill") - if item.is_printed and "Edit Printed Bill" not in request.effective_principals: + if item.voucher_type != VoucherType.KOT and "Edit Printed Bill" not in permissions: raise HTTPForbidden("You are not allowed to edit a printed bill") - if item.is_printed and not json["Printed"]: + if item.voucher_type != VoucherType.KOT and voucher_type == VoucherType.KOT: transaction.abort() raise ValidationFailure("This Bill is already printed\nCannot add a Kot to it.") @@ -42,84 +118,36 @@ def update(request): "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() +def get_guest_book(guest_book_id, dbsession): + if guest_book_id is None: + return guest_book_id + return ( + dbsession.query(GuestBook) + .filter(GuestBook.id == uuid.UUID(guest_book_id)) + .first() + ) + + +def get_bill_id(voucher_type, dbsession): + if voucher_type == VoucherType.KOT: + return None + return ( + dbsession.query(func.coalesce(func.max(Voucher.bill_id), 0) + 1) + .filter(Voucher.voucher_type == voucher_type.value) + .scalar() + ) + + +def do_update_table(item, voucher_type, guest_book, dbsession): + status = "running" if voucher_type == VoucherType.KOT else "printed" + if item.status is None: + item.status = Overview( + voucher_id=item.id, + food_table_id=item.food_table_id, + guest_book_id=guest_book.id if guest_book is not None else None, + status=status, ) - - 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.tax_rate = get_tax(i.tax_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"]): - tax_rate = get_tax(i["taxRate"], json["VoucherType"]) - inv = Inventory( - kot.id, - uuid.UUID(i["product"]["id"]), - i["quantity"], - i["price"], - i["discount"], - i["isHappyHour"], - i["tax"]["id"], - tax_rate, - 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(Overview) - .filter(Overview.voucher_id == item.id) - .first() - ) - status = "printed" if item.is_printed else "running" - if vft is None: - item.status = Overview( - 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) - - + dbsession.add(item.status) + else: + item.status.status = status diff --git a/bookie/src/app/sale-category/sale-category.service.ts b/bookie/src/app/sale-category/sale-category.service.ts index 47f43c1..c4a89f5 100644 --- a/bookie/src/app/sale-category/sale-category.service.ts +++ b/bookie/src/app/sale-category/sale-category.service.ts @@ -34,6 +34,14 @@ export class SaleCategoryService { ); } + listForDiscount(): Observable<{name: string, discount: number}[]> { + const options = {params: new HttpParams().set('d', '')}; + return >this.http.get<{name: string, discount: number}[]>(url, options) + .pipe( + catchError(this.log.handleError(serviceName, 'list')) + ); + } + save(saleCategory: SaleCategory): Observable { return >this.http.post(`${url}/new`, saleCategory, httpOptions) .pipe( diff --git a/bookie/src/app/sales/bill.service.ts b/bookie/src/app/sales/bill.service.ts index aed0c13..aa733f1 100644 --- a/bookie/src/app/sales/bill.service.ts +++ b/bookie/src/app/sales/bill.service.ts @@ -1,12 +1,15 @@ import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material'; import { BehaviorSubject } from 'rxjs'; +import { Observable } from 'rxjs/internal/Observable'; import { Product } from '../core/product'; import { ModifiersComponent } from './modifiers/modifiers.component'; import { ModifierCategoryService } from '../modifier-categories/modifier-category.service'; import { ModifierCategory } from '../core/modifier-category'; import { Bill, Inventory, Kot, PrintType } from './bills/bill'; -import {VoucherService} from './bills/voucher.service'; +import { VoucherService } from './bills/voucher.service'; +import { ToasterService } from "../core/toaster.service"; +import { tap } from "rxjs/operators"; @Injectable() export class BillService { @@ -16,6 +19,7 @@ export class BillService { constructor( private dialog: MatDialog, + private toaster: ToasterService, private ser: VoucherService, private modifierCategoryService: ModifierCategoryService ) { @@ -37,7 +41,8 @@ export class BillService { productId: i.product.id, isHappyHour: i.isHappyHour, isPrinted: true, - info: `${i.product.name} (${i.product.units}) @ ${i.price}`, + info: `${i.product.name} (${i.product.units}) @ ${i.price} - ${i.discount * 100}%`, + price: i.price, quantity: i.quantity, discount: i.discount, taxRate: i.taxRate, @@ -61,7 +66,7 @@ export class BillService { product: product, productId: product.id, isHappyHour: product.hasHappyHour, - info: `${product.name} (${product.units}) @ ${product.price}`, + info: `${product.name} (${product.units}) @ ${product.price} - ${0}%`, price: product.price, quantity: 1, discount: 0, @@ -128,8 +133,18 @@ export class BillService { this.showModifier(item); } - printKot(guest_book_id: string): void { - const nk = new Kot({ + discount(discounts:{id: string, name: string, discount: number}[]): void { + this.data.forEach(x=> { + if (!x.isKot) { + x.discount = discounts.find(d => d.id === x.product.saleCategory.id).discount / 100; + x.info = `${x.product.name} (${x.product.units}) @ ${x.price} - ${x.discount * 100}%`; + } + }); + this.dataObs.next(this.data); + } + + private getKot(): Kot { + return new Kot({ inventories: this.data.filter(x => !x.isKot && !x.isPrinted).map(y => new Inventory({ product: y.product, quantity: y.quantity, @@ -141,15 +156,32 @@ export class BillService { tax: y.tax })) }); - let item = JSON.parse(JSON.stringify(this.bill)) - item.kots.push(nk); - this.ser.saveOrUpdate(item, PrintType.Kot, guest_book_id, true).subscribe(x => - console.log(x) + } + + printKot(guest_book_id: string): Observable { + let item = JSON.parse(JSON.stringify(this.bill)); + let newKot = this.getKot(); + if (newKot.inventories.length == 0) { + this.toaster.show('Error', 'Cannot print a blank KOT\nPlease add some products!'); + } + item.kots.push(newKot); + return this.ser.saveOrUpdate(item, PrintType.Kot, guest_book_id, true).pipe( + tap(x => console.log(x)) ); } - printBill(): boolean { - return false; + printBill(guest_book_id: string): Observable { + let item = JSON.parse(JSON.stringify(this.bill)); + item.kots.forEach(k => { + k.inventories.forEach(i => { + i.discount = this.data.find(x => !x.isKot && x.id === i.id).discount; + }) + }); + console.log(item); + item.kots.push(this.getKot()); + return this.ser.saveOrUpdate(item, PrintType.Bill, guest_book_id, true).pipe( + tap(x => console.log(x)) + ); } } diff --git a/bookie/src/app/sales/bills/bill.ts b/bookie/src/app/sales/bills/bill.ts index 6ff25d7..706bc5b 100644 --- a/bookie/src/app/sales/bills/bill.ts +++ b/bookie/src/app/sales/bills/bill.ts @@ -46,7 +46,6 @@ export class Bill { settlements: any[]; void: boolean; voidReason: string; - printed: boolean; voucherType: string; serial: number; kots: Kot[]; @@ -59,7 +58,7 @@ export class Bill { export enum PrintType { Kot = 'KOT', - Bill = 'BILL', + Bill = 'REGULAR_BILL', NoCharge = 'NO_CHARGE', Staff = 'STAFF' } diff --git a/bookie/src/app/sales/discount/discount-datasource.ts b/bookie/src/app/sales/discount/discount-datasource.ts new file mode 100644 index 0000000..91f56ef --- /dev/null +++ b/bookie/src/app/sales/discount/discount-datasource.ts @@ -0,0 +1,16 @@ +import { DataSource } from '@angular/cdk/collections'; +import { Observable, of as observableOf } from 'rxjs'; + +export class DiscountDataSource extends DataSource<{name: string, discount: number}> { + + constructor(private data: {name: string, discount: number}[]) { + super(); + } + + connect(): Observable<{name: string, discount: number}[]> { + return observableOf(this.data); + } + + disconnect() { + } +} diff --git a/bookie/src/app/sales/discount/discount.component.css b/bookie/src/app/sales/discount/discount.component.css new file mode 100644 index 0000000..a9626b3 --- /dev/null +++ b/bookie/src/app/sales/discount/discount.component.css @@ -0,0 +1,4 @@ +.right { + display: flex; + justify-content: flex-end; +} diff --git a/bookie/src/app/sales/discount/discount.component.html b/bookie/src/app/sales/discount/discount.component.html new file mode 100644 index 0000000..40bcbc5 --- /dev/null +++ b/bookie/src/app/sales/discount/discount.component.html @@ -0,0 +1,30 @@ +

Discount

+ +
+ + + + + Name + {{row.name}} + + + + + Discount + + + + + + + + + + +
+
+ + + + diff --git a/bookie/src/app/sales/discount/discount.component.spec.ts b/bookie/src/app/sales/discount/discount.component.spec.ts new file mode 100644 index 0000000..18fc249 --- /dev/null +++ b/bookie/src/app/sales/discount/discount.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DiscountComponent } from './discount.component'; + +describe('DiscountComponent', () => { + let component: DiscountComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DiscountComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DiscountComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/bookie/src/app/sales/discount/discount.component.ts b/bookie/src/app/sales/discount/discount.component.ts new file mode 100644 index 0000000..a73236e --- /dev/null +++ b/bookie/src/app/sales/discount/discount.component.ts @@ -0,0 +1,52 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Observable } from 'rxjs'; +import { FormArray, FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { DiscountDataSource } from "./discount-datasource"; + +@Component({ + selector: 'app-modifiers', + templateUrl: './discount.component.html', + styleUrls: ['./discount.component.css'] +}) +export class DiscountComponent { + list: any[]; + form: FormGroup; + dataSource: DiscountDataSource; + + displayedColumns = ['name', 'discount']; + + constructor( + public dialogRef: MatDialogRef, + private fb: FormBuilder, + @Inject(MAT_DIALOG_DATA) public data: Observable + ) { + this.createForm(); + this.data.subscribe((list: any[]) => { + this.list = list; + this.form.setControl('discounts', this.fb.array( + this.list.map( + x => this.fb.group({ + name: [x.name], + discount: ["", [Validators.min(0), Validators.max(100)]] + }) + ) + )); + this.dataSource = new DiscountDataSource(list); + }); + } + + createForm() { + this.form = this.fb.group({ + discounts: '' + }); + } + + accept(): void { + const array = this.form.get('discounts') as FormArray; + this.list.forEach((item, index) => { + item.discount = Math.max(Math.min(array.controls[index].value.discount, 100), 0); + }); + this.dialogRef.close(this.list); + } +} diff --git a/bookie/src/app/sales/home/sales-home.component.html b/bookie/src/app/sales/home/sales-home.component.html index 26b22d7..f5efe98 100644 --- a/bookie/src/app/sales/home/sales-home.component.html +++ b/bookie/src/app/sales/home/sales-home.component.html @@ -3,6 +3,9 @@ queryParamsHandling="preserve">

Add Product

+ +

Discount

+

Print KOT

diff --git a/bookie/src/app/sales/home/sales-home.component.ts b/bookie/src/app/sales/home/sales-home.component.ts index 85d92ab..b2e70a1 100644 --- a/bookie/src/app/sales/home/sales-home.component.ts +++ b/bookie/src/app/sales/home/sales-home.component.ts @@ -1,8 +1,12 @@ import { Component, OnInit } from '@angular/core'; -import { Subject } from 'rxjs'; -import { AuthService } from '../../auth/auth.service'; +import { ActivatedRoute, Router } from "@angular/router"; +import { MatDialog } from "@angular/material"; +import { mergeMap, tap } from "rxjs/operators"; +import { Observable, of as observableOf } from "rxjs"; import { BillService } from '../bill.service'; -import { ActivatedRoute } from "@angular/router"; +import { ToasterService } from "../../core/toaster.service"; +import { DiscountComponent } from "../discount/discount.component"; +import { SaleCategoryService } from "../../sale-category/sale-category.service"; @Component({ selector: 'app-sales-home', @@ -10,19 +14,17 @@ import { ActivatedRoute } from "@angular/router"; styleUrls: ['./sales-home.component.css'] }) export class SalesHomeComponent implements OnInit { - public nameObject = new Subject(); - constructor(private route: ActivatedRoute, private auth: AuthService, private bs: BillService) { + constructor( + private route: ActivatedRoute, + private router: Router, + private dialog: MatDialog, + private toaster: ToasterService, + private mcSer: SaleCategoryService, + private bs: BillService) { } ngOnInit() { - this.auth.userObservable.subscribe((user) => { - if (user.isAuthenticated) { - this.nameObject.next(user.name); - } else { - this.nameObject.next(null); - } - }); } printKot() { @@ -30,10 +32,48 @@ export class SalesHomeComponent implements OnInit { if (this.route.snapshot.queryParamMap.has("guest")) { guestBookId = this.route.snapshot.queryParamMap.get("guest"); } - this.bs.printKot(guestBookId); + this.bs.printKot(guestBookId).subscribe(x => { + this.toaster.show('Success', ''); + this.router.navigate(['/sales']); + }); } + discount(): void { + this.showDiscount().subscribe(); + } + + showDiscount(): Observable { + const dialogRef = this.dialog.open(DiscountComponent, { + // width: '750px', + data: this.mcSer.listForDiscount() + }); + return dialogRef.afterClosed().pipe( + tap((result: boolean | { id: string, name: string, discount: number }[]) => { + if (!!result) { + this.bs.discount(result as { id: string, name: string, discount: number }[]); + } + }) + ) + } + + printBill() { - this.bs.printBill(); + const canGiveDiscount = true; + let guestBookId = null; + if (this.route.snapshot.queryParamMap.has("guest")) { + guestBookId = this.route.snapshot.queryParamMap.get("guest"); + } + let discObs = null + if (!canGiveDiscount) { + discObs = this.showDiscount(); + } else { + discObs = observableOf(""); + } + discObs.pipe( + mergeMap(x => this.bs.printBill(guestBookId)) + ).subscribe(x => { + this.toaster.show('Success', ''); + this.router.navigate(['/sales']); + }); } } diff --git a/bookie/src/app/sales/modifiers/modifiers.component.ts b/bookie/src/app/sales/modifiers/modifiers.component.ts index 5677d63..82ff727 100644 --- a/bookie/src/app/sales/modifiers/modifiers.component.ts +++ b/bookie/src/app/sales/modifiers/modifiers.component.ts @@ -2,7 +2,7 @@ import { Component, Inject } from '@angular/core'; import { ModifierCategory } from '../../core/modifier-category'; import { Modifier } from '../../core/modifier'; import { MAT_DIALOG_DATA } from '@angular/material'; -import {Observable} from 'rxjs'; +import { Observable } from 'rxjs'; @Component({ selector: 'app-modifiers', diff --git a/bookie/src/app/sales/quantity/quantity.component.html b/bookie/src/app/sales/quantity/quantity.component.html index a3526f8..f795d47 100644 --- a/bookie/src/app/sales/quantity/quantity.component.html +++ b/bookie/src/app/sales/quantity/quantity.component.html @@ -1,4 +1,4 @@ -
+
@@ -9,9 +9,8 @@
-
-
+ + -
- + diff --git a/bookie/src/app/sales/sales.module.ts b/bookie/src/app/sales/sales.module.ts index 6a349d3..91a1e14 100644 --- a/bookie/src/app/sales/sales.module.ts +++ b/bookie/src/app/sales/sales.module.ts @@ -25,19 +25,21 @@ import { BillsComponent } from './bills/bills.component'; import { SalesHomeComponent } from './home/sales-home.component'; import { BillService } from './bill.service'; import { QuantityComponent } from './quantity/quantity.component'; +import { DiscountComponent } from "./discount/discount.component"; @NgModule({ providers: [ BillService ], declarations: [ - RunningTablesComponent, - MenuCategoriesComponent, - ProductsComponent, - ModifiersComponent, BillsComponent, - SalesHomeComponent, - QuantityComponent + DiscountComponent, + MenuCategoriesComponent, + ModifiersComponent, + ProductsComponent, + QuantityComponent, + RunningTablesComponent, + SalesHomeComponent ], imports: [ CommonModule, @@ -59,6 +61,7 @@ import { QuantityComponent } from './quantity/quantity.component'; SalesRoutingModule ], entryComponents: [ + DiscountComponent, ModifiersComponent, QuantityComponent ]