diff --git a/barker/routes.py b/barker/routes.py index a0e3bf2..490b90b 100644 --- a/barker/routes.py +++ b/barker/routes.py @@ -350,6 +350,9 @@ def includeme(config): config.add_route("v1_vouchers_new", "/v1/vouchers/new") config.add_route("v1_vouchers_id", "/v1/vouchers/{id}") + config.add_route("v1_move_table", "/v1/move-table") + config.add_route("v1_move_kot", "/v1/move-kot") + # Done till here config.add_route("customer", "/Customer.json") @@ -365,16 +368,10 @@ def includeme(config): 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("move_kot", "/MoveKot.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("v1_bills_new", "/v1/bills/new") config.add_route("v1_bills_id", "/v1/bills/{id}") @@ -392,8 +389,6 @@ def includeme(config): config.add_route("sa_tax", "/SaleAnalysis/Tax.json") config.add_route("voucher_reprint", "/ReprintVoucher/{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/views/voucher/__init__.py b/barker/views/voucher/__init__.py index ae7c034..9bef5db 100644 --- a/barker/views/voucher/__init__.py +++ b/barker/views/voucher/__init__.py @@ -1,4 +1,9 @@ -from barker.models import VoucherType, Settlement, SettleOption +import uuid + +from sqlalchemy import func + +from barker.exceptions import ValidationFailure +from barker.models import VoucherType, Settlement, SettleOption, Voucher, Overview, GuestBook from barker.models.validation_exception import ValidationError @@ -11,45 +16,81 @@ def get_tax(tax, voucher_type): raise ValidationError("Unexpected Voucher Type") -def get_settlements(voucher, dbsession): - amount = voucher.amount - 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() +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() ) - 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 do_update_table(item, guest_book, dbsession): + status = "running" if item.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, + ) + dbsession.add(item.status) + else: + item.status.status = status + + +def check_permissions(item, voucher_type, permissions): + if voucher_type == VoucherType.KOT and "Print Kot" not in permissions: + raise ValidationFailure("You are not allowed to print a kot") + + if voucher_type != VoucherType.KOT and "Print Bill" not in permissions: + raise ValidationFailure("You are not allowed to print bill") + + if item is None: + return + if item.voucher_type != VoucherType.KOT and "Edit Printed Bill" not in permissions: + raise ValidationFailure("You are not allowed to edit a printed bill") + + if item.voucher_type != VoucherType.KOT and voucher_type == VoucherType.KOT: + raise ValidationFailure("This Bill is already printed\nCannot add a Kot to it.") + + if item.is_void: + raise ValidationFailure( + "This Bill is already void.\nReason: {0}".format(item.void_reason) + ) + + +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 do_update_settlements(voucher, dbsession): + settlements = [] + total_amount = voucher.amount + settlements.append({"id": SettleOption.AMOUNT(), "amount": -total_amount}) + round_off = round(total_amount) - total_amount + if round_off != 0: + settlements.append({"id": SettleOption.ROUND_OFF(), "amount": -round_off}) + settlements.append({"id": SettleOption.UNSETTLED(), "amount": round(total_amount)}) + + for i in settlements: + amount = i["amount"] + settlement_type_id = i["id"] + old = [s for s in voucher.settlements if s.settled == settlement_type_id] + if len(old) == 1: + old[0].amount = amount + else: + s = Settlement(voucher.id, settlement_type_id, amount) + voucher.settlements.append(s) + dbsession.add(s) + + for i in (i for i in voucher.settlements if i.settled not in [x["id"] for x in settlements]): + voucher.settlements.remove(i) + dbsession.delete(i) diff --git a/barker/views/voucher/merge_move.py b/barker/views/voucher/merge_move.py index fdc33d2..9b92734 100644 --- a/barker/views/voucher/merge_move.py +++ b/barker/views/voucher/merge_move.py @@ -1,74 +1,82 @@ import uuid import transaction +from datetime import datetime from pyramid.view import view_config +from sqlalchemy import func from barker.models import Kot, Voucher, Overview +from barker.views.voucher import get_bill_id, do_update_table @view_config( request_method="POST", - route_name="merge_kot", + route_name="v1_move_kot", renderer="json", + request_param="merge", 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"]) + kot_id = uuid.UUID(json["kot"]["id"]) + new_voucher_id = uuid.UUID(json["newVoucher"]["id"]) request.dbsession.query(Kot).filter(Kot.id == kot_id).update( - {Kot.voucher_id: voucher_id} + {Kot.voucher_id: new_voucher_id} ) transaction.commit() - return voucher_id + return True @view_config( request_method="POST", - route_name="move_kot", + route_name="v1_move_kot", renderer="json", - permission="Move Kot", + request_param="move", + permission="Move Kot to New Table", trans=True, ) def move_kot(request): json = request.json_body - kot_id = uuid.UUID(json["KotID"]) - food_table_id = uuid.UUID(json["FoodTableID"]) + now = datetime.now() + kot_id = uuid.UUID(json["kot"]["id"]) + table_id = uuid.UUID(json["table"]["id"]) kot = request.dbsession.query(Kot).filter(Kot.id == kot_id).first() + bill_id = get_bill_id(kot.voucher.voucher_type, request.dbsession) + kot_id = request.dbsession.query( + func.coalesce(func.max(Voucher.kot_id), 0) + 1 + ).scalar() item = Voucher( + now, kot.voucher.pax, - food_table_id, + bill_id, + kot_id, + table_id, kot.voucher.customer_id, - False, kot.voucher.voucher_type, - kot.voucher.user_id, - request.dbsession, + uuid.UUID(request.authenticated_userid) ) request.dbsession.add(item) item.kots.append(kot) - status = "printed" if item.is_printed else "running" - item.status = Overview( - voucher_id=None, food_table_id=item.food_table_id, status=status - ) + do_update_table(item, None, request.dbsession) request.dbsession.add(item.status) transaction.commit() - return item.id + return True @view_config( request_method="POST", - route_name="v1_vouchers_id", - request_param="move-table", + route_name="v1_move_table", + request_param="move", renderer="json", permission="Move Table", trans=True, ) def move_table(request): - id_ = uuid.UUID(request.matchdict["id"]) + id_ = uuid.UUID(request.json_body["voucher"]["id"]) table_id = uuid.UUID(request.json_body["table"]["id"]) request.dbsession.query(Overview).filter(Overview.voucher_id == id_).update( @@ -85,22 +93,25 @@ def move_table(request): @view_config( request_method="POST", - route_name="merge_table", + route_name="v1_move_table", + request_param="merge", 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( + id_ = uuid.UUID(json["oldVoucher"]["id"]) + # table_id = uuid.UUID(json["table"]["id"]) + new_voucher_id = uuid.UUID(json["newVoucher"]["id"]) + + request.dbsession.query(Kot).filter(Kot.voucher_id == id_).update( {Kot.voucher_id: new_voucher_id} ) - request.dbsession.query(Voucher).filter(Voucher.id == voucher_id).delete() + request.dbsession.query(Voucher).filter(Voucher.id == id_).delete() transaction.commit() - return voucher_id + return True # VoucherBI.UpdateTable(x => x.FoodTableID == _voucher.Table.FoodTableID && x.VoucherID == _voucher.VoucherID, null, null); # throw new NotImplementedException(); diff --git a/barker/views/voucher/save.py b/barker/views/voucher/save.py index 086c894..7a1a3fe 100644 --- a/barker/views/voucher/save.py +++ b/barker/views/voucher/save.py @@ -3,24 +3,27 @@ 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, Voucher, Kot, Inventory, 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 +from barker.views.voucher import ( + get_tax, + do_update_settlements, + get_bill_id, + do_update_table, + check_permissions, + get_guest_book, +) @view_config( @@ -39,7 +42,7 @@ def save(request): .first() ) - check_permissions(voucher_type, request.effective_principals) + check_permissions(None, voucher_type, request.effective_principals) bill_id = get_bill_id(voucher_type, request.dbsession) kot_id = request.dbsession.query( @@ -54,68 +57,20 @@ def save(request): json["table"]["id"], json["customer"]["id"] if "id" in json["customer"] else None, voucher_type, - uuid.UUID(request.authenticated_userid) + 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: - 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(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() - ) - - -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"]: - code = dbsession.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar() + 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) - dbsession.add(kot) + request.dbsession.add(kot) for index, i in enumerate(k["inventories"]): - product = dbsession.query(Product).filter(Product.id == uuid.UUID(i["product"]["id"])).first() + 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, @@ -129,9 +84,16 @@ def add_kots(json, item, voucher_type, dbsession): index, ) kot.inventories.append(inv) - dbsession.add(inv) + request.dbsession.add(inv) for m in i["modifiers"]: mod = InventoryModifier(None, uuid.UUID(m["id"]), 0) inv.modifiers.append(mod) - dbsession.add(mod) - get_settlements(item, dbsession) + request.dbsession.add(mod) + do_update_settlements(item, request.dbsession) + if len(item.kots) == 0 or len(item.kots[0].inventories) == 0: + raise ValidationError("Please add some products!") + + if update_table: + do_update_table(item, guest_book, request.dbsession) + transaction.commit() + return True diff --git a/barker/views/voucher/split.py b/barker/views/voucher/split.py index ec11eff..3e329ad 100644 --- a/barker/views/voucher/split.py +++ b/barker/views/voucher/split.py @@ -1,40 +1,133 @@ import uuid import transaction +from datetime import datetime from pyramid.view import view_config +from sqlalchemy import func -from barker.models import Voucher, Overview -from barker.views.voucher.save import save +from barker.models import Voucher, Overview, Kot, Inventory, InventoryModifier +from barker.models.validation_exception import ValidationError +from barker.views.voucher import get_bill_id, do_update_settlements, do_update_table +from barker.views.voucher.update import check_permissions +from barker.views.voucher.void import do_void_settlements @view_config( request_method="POST", - route_name="voucher_split", + route_name="v1_vouchers_id", renderer="json", + request_param="split-bill", permission="Split Bill", trans=True, ) def split_voucher(request): json = request.json_body + now = datetime.now() id_ = uuid.UUID(request.matchdict["id"]) + update_table = request.GET["u"] == "true" item = request.dbsession.query(Voucher).filter(Voucher.id == id_).first() item.void = True item.void_reason = "Bill Split" - # TODO: Set the Void Settlement + do_void_settlements(item, request.dbsession) + if update_table: + request.dbsession.query(Overview).filter( + Overview.voucher_id == item.id + ).delete() - new_one = save(json["One"], request.dbsession) - new_two = save(json["Two"], request.dbsession) + inventories = [uuid.UUID(i) for i in json["inventories"]] - status = "printed" if item.is_printed else "running" - new_one.status = Overview( - voucher_id=None, food_table_id=new_one.food_table_id, status=status + one_inventories = [i for k in item.kots for i in k.inventories if i.id in inventories] + two_inventories = [i for k in item.kots for i in k.inventories if i.id not in inventories] + + save( + one_inventories, + now, + item.voucher_type, + 0, + uuid.UUID(json["table"]["id"]), + item.customer_id, + update_table, + uuid.UUID(request.authenticated_userid), + request.effective_principals, + request.dbsession, ) - - request.dbsession.add(new_one.status) - request.dbsession.query(Overview).filter(Overview.voucher_id == item.id).update( - {Overview.voucher_id: new_two.id} + save( + two_inventories, + now, + item.voucher_type, + item.pax, + item.food_table_id, + item.customer_id, + update_table, + uuid.UUID(request.authenticated_userid), + request.effective_principals, + request.dbsession, ) transaction.commit() + return True +def save( + inventories, + now, + voucher_type, + pax, + table_id, + customer_id, + update_table, + user_id, + permissions, + dbsession, +): + product_quantities = {} + skip_products = set() + for i in inventories: + if i.product_id in product_quantities: + product_quantities[i.product_id] += i.quantity + else: + product_quantities[i.product_id] = i.quantity + for product, quantity in product_quantities.items(): + if quantity < 0: + raise ValidationError("Quantity is negative") + elif quantity == 0: + skip_products.add(product) + check_permissions(None, voucher_type, permissions) + bill_id = get_bill_id(voucher_type, dbsession) + kot_id = dbsession.query(func.coalesce(func.max(Voucher.kot_id), 0) + 1).scalar() + + item = Voucher( + now, pax, bill_id, kot_id, table_id, customer_id, voucher_type, user_id + ) + dbsession.add(item) + 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, old_inventory in enumerate( + [i for i in inventories if i.product_id not in skip_products] + ): + inv = Inventory( + kot.id, + old_inventory.product_id, + old_inventory.quantity, + old_inventory.price, + old_inventory.discount, + old_inventory.is_happy_hour, + old_inventory.tax_id, + old_inventory.tax_rate, + index, + ) + kot.inventories.append(inv) + dbsession.add(inv) + for m in old_inventory.modifiers: + mod = InventoryModifier(None, m.modifier_id, m.price) + inv.modifiers.append(mod) + dbsession.add(mod) + do_update_settlements(item, dbsession) + + if len(kot.inventories) == 0: + raise ValidationError("Please add some products!") + + if update_table: + do_update_table(item, None, dbsession) diff --git a/barker/views/voucher/update.py b/barker/views/voucher/update.py index 119a7b4..9c22e9e 100644 --- a/barker/views/voucher/update.py +++ b/barker/views/voucher/update.py @@ -3,23 +3,25 @@ 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.exceptions import ValidationFailure 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 +from barker.views.voucher import ( + get_tax, + do_update_settlements, + get_bill_id, + do_update_table, + check_permissions, + get_guest_book, +) @view_config( @@ -60,10 +62,8 @@ def update(request): 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) + code = request.dbsession.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar() + kot = Kot(item.id, code, item.food_table_id, now, item.user_id) item.kots.append(kot) request.dbsession.add(kot) for index, i in enumerate(k["inventories"]): @@ -90,64 +90,8 @@ def update(request): mod = InventoryModifier(None, uuid.UUID(m["id"]), 0) inv.modifiers.append(mod) request.dbsession.add(mod) - get_settlements(item, request.dbsession) + do_update_settlements(item, request.dbsession) if update_table: - do_update_table(item, voucher_type, guest_book, request.dbsession) + do_update_table(item, 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 voucher_type != VoucherType.KOT and "Print Bill" not in permissions: - raise HTTPForbidden("You are not allowed to print bill") - - 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.voucher_type != VoucherType.KOT and voucher_type == VoucherType.KOT: - 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) - ) - - -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, - ) - dbsession.add(item.status) - else: - item.status.status = status + return True diff --git a/barker/views/voucher/void.py b/barker/views/voucher/void.py index 35d6ecd..92f1f62 100644 --- a/barker/views/voucher/void.py +++ b/barker/views/voucher/void.py @@ -17,13 +17,24 @@ from barker.models import Voucher, SettleOption, Settlement, Overview def void_voucher(request): id_ = uuid.UUID(request.matchdict["id"]) reason = request.json_body["reason"] - update_table = request.GET["u"] + update_table = request.GET["u"] == "true" item = request.dbsession.query(Voucher).filter(Voucher.id == id_).first() item.void = True item.void_reason = reason + do_void_settlements(item, request.dbsession) + + if update_table: + request.dbsession.query(Overview).filter( + Overview.voucher_id == item.id + ).delete() + transaction.commit() + return True + + +def do_void_settlements(item, dbsession): settlements = [] total_amount = item.amount settlements.append({"id": SettleOption.AMOUNT(), "amount": -total_amount}) @@ -41,17 +52,8 @@ def void_voucher(request): else: s = Settlement(item.id, settlement_type_id, amount) item.settlements.append(s) - request.dbsession.add(s) + dbsession.add(s) for i in (i for i in item.settlements if i.settled not in [x["id"] for x in settlements]): item.settlements.remove(i) - request.dbsession.delete(i) - - if update_table: - request.dbsession.query(Overview).filter( - Overview.voucher_id == item.id - ).delete() - transaction.commit() - return True - - + dbsession.delete(i) diff --git a/bookie/src/app/sales/bill.service.ts b/bookie/src/app/sales/bill.service.ts index d245cb8..53ca279 100644 --- a/bookie/src/app/sales/bill.service.ts +++ b/bookie/src/app/sales/bill.service.ts @@ -10,18 +10,19 @@ import { ModifierCategory } from '../core/modifier-category'; import { Bill, Inventory, Kot, PrintType } from './bills/bill'; import { VoucherService } from './bills/voucher.service'; import { ToasterService } from '../core/toaster.service'; -import { tap } from 'rxjs/operators'; import { Table } from '../core/table'; +import { SelectionModel } from "@angular/cdk/collections"; @Injectable() export class BillService { public dataObs; - public data; + public data: any[]; private bill; public netAmount: BehaviorSubject; public discountAmount: BehaviorSubject; public taxAmount: BehaviorSubject; public amount: BehaviorSubject; + public selection = new SelectionModel(true, []); constructor( private dialog: MatDialog, @@ -41,12 +42,14 @@ export class BillService { this.bill = bill; const view = this.bill.kots.map(k => { return [{ + id: k.id, isKot: true, oldKot: true, info: `Kot: ${k.code} / ${k.date} (${k.user.name}) ` }, ...k.inventories.map(i => { return { id: i.id, + kotId: k.id, isKot: false, product: i.product, productId: i.product.id, @@ -178,7 +181,7 @@ export class BillService { }); } - printKot(guest_book_id: string): Observable { + printKot(guest_book_id: string): Observable { const item = JSON.parse(JSON.stringify(this.bill)); const newKot = this.getKot(); if (newKot.inventories.length == 0) { @@ -188,7 +191,7 @@ export class BillService { return this.ser.saveOrUpdate(item, PrintType.Kot, guest_book_id, true); } - printBill(guest_book_id: string, printType: PrintType): Observable { + printBill(guest_book_id: string, printType: PrintType): Observable { const item = JSON.parse(JSON.stringify(this.bill)); item.kots.forEach(k => { k.inventories.forEach(i => { @@ -211,8 +214,16 @@ export class BillService { return this.ser.moveTable(this.bill.id, table); } - moveKot(id: string, table: Table): Observable { - return this.ser.moveKot(this.bill.id, id, table); + mergeTable(table: Table): Observable { + return this.ser.mergeTable(this.bill.id, table); + } + + moveKot(kotId: string, table: Table): Observable { + return this.ser.moveKotToNewTable(this.bill.id, kotId, table); + } + + mergeKot(kotId: string, table: Table): Observable { + return this.ser.mergeKotWithOldBill(this.bill.id, kotId, table); } voidBill(reason: string): Observable { @@ -249,4 +260,9 @@ export class BillService { , 0) , 0)); } + + splitBill(table: Table): Observable { + const inventoriesToMove: string[] = this.selection.selected.map((x:any)=> x.id) + return this.ser.splitBill(this.bill.id, inventoriesToMove, table); + } } diff --git a/bookie/src/app/sales/bills/bills.component.css b/bookie/src/app/sales/bills/bills.component.css index bc29553..1461baa 100644 --- a/bookie/src/app/sales/bills/bills.component.css +++ b/bookie/src/app/sales/bills/bills.component.css @@ -13,6 +13,10 @@ table { width: 100%; } +.mat-column-select{ + flex: 0 0 50px; +} + .grey900 { background-color: #1b5e20; color: #ffffff; diff --git a/bookie/src/app/sales/bills/bills.component.html b/bookie/src/app/sales/bills/bills.component.html index 5e43fd7..d7efb51 100644 --- a/bookie/src/app/sales/bills/bills.component.html +++ b/bookie/src/app/sales/bills/bills.component.html @@ -8,6 +8,22 @@ + + + + + + + + + Amount + @@ -18,7 +34,7 @@
  • {{m.name}}
  • - Amount +
    @@ -39,7 +55,7 @@ - diff --git a/bookie/src/app/sales/bills/bills.component.ts b/bookie/src/app/sales/bills/bills.component.ts index 5e3bef4..5f10a71 100644 --- a/bookie/src/app/sales/bills/bills.component.ts +++ b/bookie/src/app/sales/bills/bills.component.ts @@ -12,6 +12,8 @@ import { map, switchMap } from 'rxjs/operators'; import { TablesDialogComponent } from '../tables-dialog/tables-dialog.component'; import { TableService } from '../../tables/table.service'; import { ToasterService } from '../../core/toaster.service'; +import { AuthService } from "../../auth/auth.service"; +import { SelectionModel } from "@angular/cdk/collections"; @Component({ selector: 'app-bills', @@ -22,13 +24,14 @@ export class BillsComponent implements OnInit { dataSource: BillsDataSource; item: Bill; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ - displayedColumns: string[] = ['info', 'quantity']; + displayedColumns: string[] = ['select', 'info', 'quantity']; constructor( private route: ActivatedRoute, private router: Router, private dialog: MatDialog, private toaster: ToasterService, + private auth: AuthService, private bs: BillService, private tSer: TableService ) { @@ -43,6 +46,38 @@ export class BillsComponent implements OnInit { this.dataSource = new BillsDataSource(this.bs.dataObs); } + isAllSelected(kot: Kot) { + return this.bs.data.filter( + x => x.kotId === kot.id + ).reduce( + (p: boolean, c: any) => p && this.bs.selection.isSelected(c) + , true + ); + } + + isAnySelected(kot: Kot) { + let total: number = 0, + found: number = 0; + this.bs.data.filter( + x => x.kotId === kot.id + ).forEach((c: any) => { + total += 1; + if (this.bs.selection.isSelected(c)) { + found += 1; + } + }); + return found > 0 && found < total; + } + + masterToggle(kot: Kot) { + const isAllSelected = this.isAllSelected(kot); + this.bs.data.filter( + x => x.kotId === kot.id + ).forEach( + row => isAllSelected ? this.bs.selection.deselect(row) : this.bs.selection.select(row) + ); + } + addOne(item: any): void { this.bs.addOne(item); } @@ -72,4 +107,48 @@ export class BillsComponent implements OnInit { modifier(item: any): void { this.bs.modifier(item); } + + confirmMoveKotDialog(table: Table): Observable<{table: Table, confirmed: boolean}> { + return this.dialog.open(ConfirmDialogComponent, { + width: '250px', + data: {title: 'Move KOT?', content: 'Are you sure?'} + }).afterClosed().pipe( + map ((x: boolean) => ({table: table, confirmed: x})) + ); + } + + moveKot(kot: Kot) { + const canMergeTables = this.auth.hasPermission("Merge Tables"); + this.dialog.open(TablesDialogComponent, { + // width: '750px', + data: { + list: this.tSer.running(), + canChooseRunning: canMergeTables + } + }).afterClosed().pipe( + switchMap((x: boolean | Table) => { + if (!!x) { + return this.confirmMoveKotDialog(x as Table); + } else { + return throwError('Please choose a table'); + } + }), + switchMap((value: { table: Table, confirmed: boolean }, index: number) => { + if (!value.confirmed) { + return throwError('Please confirm move'); + } else if (value.table.status) { + return this.bs.mergeKot(kot.id, value.table); + } else { + return this.bs.moveKot(kot.id, value.table); + } + } + ) + ).subscribe((x) => { + this.toaster.show('Success', ''); + this.router.navigate(['/sales']); + }, + x => { + this.toaster.show('Error', x); + }); + } } diff --git a/bookie/src/app/sales/bills/voucher.service.ts b/bookie/src/app/sales/bills/voucher.service.ts index 1373107..3e323b5 100644 --- a/bookie/src/app/sales/bills/voucher.service.ts +++ b/bookie/src/app/sales/bills/voucher.service.ts @@ -11,6 +11,8 @@ const httpOptions = { }; const url = '/v1/vouchers'; +const urlMoveTable = '/v1/move-table'; +const urlMoveKot = '/v1/move-kot'; const serviceName = 'VoucherService'; @Injectable({providedIn: 'root'}) @@ -47,29 +49,29 @@ export class VoucherService { ); } - save(voucher: Bill, printType: PrintType, guest_book_id: string, updateTable: boolean): Observable { + save(voucher: Bill, printType: PrintType, guest_book_id: string, updateTable: boolean): Observable { const options = {params: new HttpParams().set('p', printType).set('u', updateTable.toString())}; if (guest_book_id !== null) { options.params = options.params.set('g', guest_book_id); } - return >this.http.post(`${url}/new`, voucher, options) + return >this.http.post(`${url}/new`, voucher, options) .pipe( catchError(this.log.handleError(serviceName, 'save')) ); } - update(voucher: Bill, printType: PrintType, guest_book_id: string, updateTable: boolean): Observable { + update(voucher: Bill, printType: PrintType, guest_book_id: string, updateTable: boolean): Observable { const options = {params: new HttpParams().set('p', printType).set('u', updateTable.toString())}; if (guest_book_id !== null) { options.params = options.params.set('g', guest_book_id); } - return >this.http.put(`${url}/${voucher.id}`, voucher, options) + return >this.http.put(`${url}/${voucher.id}`, voucher, options) .pipe( catchError(this.log.handleError(serviceName, 'update')) ); } - saveOrUpdate(voucher: Bill, printType: PrintType, guest_book_id: string, updateTable: boolean): Observable { + saveOrUpdate(voucher: Bill, printType: PrintType, guest_book_id: string, updateTable: boolean): Observable { if (!voucher.id) { return this.save(voucher, printType, guest_book_id, updateTable); } else { @@ -78,26 +80,75 @@ export class VoucherService { } receivePayment(id: string, amounts: { id: string; name: string; amount: number }[], updateTable: boolean): Observable { - const options = {params: new HttpParams().set('receive-payment', '').set('u', updateTable.toString())}; - return >this.http.post(`${url}/${id}`, amounts, options) - .pipe( - catchError(this.log.handleError(serviceName, 'receivePayment')) - ); - } - - moveTable(id: string, table: Table): Observable { - const options = {params: new HttpParams().set('move-table', '')}; - return >this.http.post(`${url}/${id}`, {table: {id: table.id}}, options) - .pipe( - catchError(this.log.handleError(serviceName, 'moveTable')) - ); + const options = {params: new HttpParams().set('receive-payment', '').set('u', updateTable.toString())}; + return >this.http.post( + `${url}/${id}`, amounts, options + ).pipe( + catchError(this.log.handleError(serviceName, 'receivePayment')) + ); } voidBill(id: string, reason: string, updateTable: boolean): Observable { - const options = {params: new HttpParams().set('void-bill', '').set('u', updateTable.toString())}; - return >this.http.post(`${url}/${id}`, {reason: reason}, options) - .pipe( - catchError(this.log.handleError(serviceName, 'voidBill')) - ); + const options = {params: new HttpParams().set('void-bill', '').set('u', updateTable.toString())}; + return >this.http.post( + `${url}/${id}`, {reason: reason}, options + ).pipe( + catchError(this.log.handleError(serviceName, 'voidBill')) + ); + } + + moveTable(id: string, table: Table): Observable { + const options = {params: new HttpParams().set('move', '')}; + return >this.http.post(urlMoveTable, { + voucher: {id: id}, + table: {id: table.id} + }, options).pipe( + catchError(this.log.handleError(serviceName, 'moveTable')) + ); + } + + mergeTable(id: string, table: Table): Observable { + const options = {params: new HttpParams().set('merge', '')}; + return >this.http.post(urlMoveTable, { + voucher: {id: id}, + table: {id: table.id}, + newVoucher: {id: table.voucherId} + }, options).pipe( + catchError(this.log.handleError(serviceName, 'mergeTable')) + ); + } + + moveKotToNewTable(id: string, kotId: string, table: Table): Observable { + const options = {params: new HttpParams().set('move', '')}; + return >this.http.post(urlMoveKot, { + voucher: {id: id}, + kot: {id: kotId}, + table: {id: table.id} + }, options).pipe( + catchError(this.log.handleError(serviceName, 'moveKotToNewTable')) + ); + } + + mergeKotWithOldBill(id: string, kotId: string, table: Table): Observable { + const options = {params: new HttpParams().set('merge', '')}; + return >this.http.post(urlMoveKot, { + voucher: {id: id}, + kot: {id: kotId}, + table: {id: table.id}, + newVoucher: {id: table.voucherId} + }, options).pipe( + catchError(this.log.handleError(serviceName, 'mergeKotWithOldBill')) + ); + } + + splitBill(id: string, inventoriesToMove: string[], table: Table) { + const options = {params: new HttpParams().set('split-bill', '').set('u', 'true')}; + return >this.http.post(`${url}/${id}`, { + voucher: {id: id}, + inventories: inventoriesToMove, + table: {id: table.id} + }, options).pipe( + catchError(this.log.handleError(serviceName, 'splitBill')) + ); } } diff --git a/bookie/src/app/sales/home/sales-home.component.html b/bookie/src/app/sales/home/sales-home.component.html index 56ef395..07b26ab 100644 --- a/bookie/src/app/sales/home/sales-home.component.html +++ b/bookie/src/app/sales/home/sales-home.component.html @@ -24,10 +24,7 @@

    Void Bill

    - -

    Move KOT

    -
    - +

    Split Bill

    diff --git a/bookie/src/app/sales/home/sales-home.component.ts b/bookie/src/app/sales/home/sales-home.component.ts index 6af3d77..b3bdeff 100644 --- a/bookie/src/app/sales/home/sales-home.component.ts +++ b/bookie/src/app/sales/home/sales-home.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { MatDialog } from '@angular/material'; -import { concatMap, map, switchMap, tap } from 'rxjs/operators'; -import { iif, Observable, of as observableOf, throwError } from 'rxjs'; +import { map, switchMap, tap } from 'rxjs/operators'; +import { Observable, of as observableOf, throwError } from 'rxjs'; import { BillService } from '../bill.service'; import { ToasterService } from '../../core/toaster.service'; import { DiscountComponent } from '../discount/discount.component'; @@ -70,13 +70,11 @@ export class SalesHomeComponent implements OnInit { } discountDialog (canGiveDiscount: boolean): Observable { - let discObs = null; if (canGiveDiscount) { - return discObs = this.showDiscount(); + return this.showDiscount(); } else { - return discObs = observableOf(''); + return observableOf(''); } - } billTypeDialog() { @@ -89,10 +87,10 @@ export class SalesHomeComponent implements OnInit { ); } - confirmMoveTableDialog(table: Table): Observable<{table: Table, confirmed: boolean}> { + confirmTableDialog(table: Table): Observable<{table: Table, confirmed: boolean}> { return this.dialog.open(ConfirmDialogComponent, { width: '250px', - data: {title: 'Move Table?', content: 'Are you sure?'} + data: {title: 'Select Table?', content: 'Are you sure?'} }).afterClosed().pipe( map ((x: boolean) => ({table: table, confirmed: x})) ); @@ -114,20 +112,19 @@ export class SalesHomeComponent implements OnInit { guestBookId = this.route.snapshot.queryParamMap.get('guest'); } this.discountDialog(canGiveDiscount).pipe( - concatMap(() => this.billTypeDialog()) - ).pipe( - concatMap( - (x: boolean | PrintType) => iif( - () => !!x, - this.bs.printBill(guestBookId, x as PrintType), - throwError(x) - ) - ), + switchMap(() => this.billTypeDialog()), + switchMap((x: boolean | PrintType) => { + if (!!x) { + return this.bs.printBill(guestBookId, x as PrintType); + } else { + return throwError(x); + } + }), ).subscribe(() => { this.toaster.show('Success', ''); this.router.navigate(['/sales']); }, - x => { + () => { this.toaster.show('Error', 'No Bill Type Chosen'); }); } @@ -153,29 +150,32 @@ export class SalesHomeComponent implements OnInit { } moveTable() { + const canMergeTables = this.auth.hasPermission("Merge Tables"); this.dialog.open(TablesDialogComponent, { // width: '750px', data: { list: this.tSer.running(), - canChooseRunning: false + canChooseRunning: canMergeTables } }).afterClosed().pipe( switchMap((x: boolean | Table) => { - if (!!x) { - return this.confirmMoveTableDialog(x as Table); + if (!x) { + return this.confirmTableDialog(x as Table); } else { return throwError('Please choose a table'); } }), switchMap((value: { table: Table, confirmed: boolean }, index: number) => { - if (!!value.confirmed) { - return this.bs.moveTable(value.table); - } else { + if (!value.confirmed) { return throwError('Please confirm move'); + } else if (value.table.status) { + return this.bs.mergeTable(value.table); + } else { + return this.bs.moveTable(value.table); } } ) - ).subscribe((x) => { + ).subscribe(() => { this.toaster.show('Success', ''); this.router.navigate(['/sales']); }, @@ -210,4 +210,35 @@ export class SalesHomeComponent implements OnInit { this.toaster.show('Error', x); }); } + + splitBill() { + this.dialog.open(TablesDialogComponent, { + // width: '750px', + data: { + list: this.tSer.running(), + canChooseRunning: false + } + }).afterClosed().pipe( + switchMap((x: boolean | Table) => { + if (!!x) { + return this.confirmTableDialog(x as Table); + } else { + return throwError('Please choose a table'); + } + }), + switchMap((value: { table: Table, confirmed: boolean }, index: number) => { + if (!value.confirmed) { + return throwError('Please confirm split'); + } else { + return this.bs.splitBill(value.table); + } + }) + ).subscribe((x) => { + this.toaster.show('Success', ''); + this.router.navigate(['/sales']); + }, + x => { + this.toaster.show('Error', x); + }); + } } diff --git a/bookie/src/app/sales/sales.module.ts b/bookie/src/app/sales/sales.module.ts index 51b7062..679fd52 100644 --- a/bookie/src/app/sales/sales.module.ts +++ b/bookie/src/app/sales/sales.module.ts @@ -5,6 +5,7 @@ import { MatBadgeModule } from '@angular/material/badge'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatChipsModule } from '@angular/material/chips'; import { MatDialogModule } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; @@ -57,6 +58,7 @@ import { VoidReasonComponent } from './void-reason/void-reason.component'; MatButtonModule, MatButtonToggleModule, MatCardModule, + MatCheckboxModule, MatChipsModule, MatDialogModule, MatDividerModule,