diff --git a/barker/barker/__version__.py b/barker/barker/__version__.py index 8c0d5d5..528787c 100644 --- a/barker/barker/__version__.py +++ b/barker/barker/__version__.py @@ -1 +1 @@ -__version__ = "2.0.0" +__version__ = "3.0.0" diff --git a/barker/barker/models/voucher.py b/barker/barker/models/voucher.py index 0f718b0..62a5502 100644 --- a/barker/barker/models/voucher.py +++ b/barker/barker/models/voucher.py @@ -252,7 +252,7 @@ class Settlement(Base): nullable=False, index=True, ) - settled = Column("settled", ForeignKey("settle_options.id"), nullable=False) + settled = Column("settled", Integer, ForeignKey("settle_options.id"), nullable=False) amount = Column("amount", Numeric, nullable=False) settle_option = relationship("SettleOption") diff --git a/barker/barker/printing/cashier_report.py b/barker/barker/printing/cashier_report.py new file mode 100644 index 0000000..dacbf53 --- /dev/null +++ b/barker/barker/printing/cashier_report.py @@ -0,0 +1,42 @@ +import asyncio + +from datetime import datetime + +from arq import ArqRedis, create_pool +from barker.schemas.cashier_report import CashierReport +from sqlalchemy.orm import Session + +from ..core.arq import settings as redis_settings +from ..models import Printer, SectionPrinter + + +def print_cashier_report(report: CashierReport, db: Session): + data = design_cashier_report(report) + printer = ( + db.query(Printer) + .join(SectionPrinter.printer) + # .filter(SectionPrinter.section_id == voucher.food_table.section_id) TODO: Use device's section_id + .filter(SectionPrinter.menu_category_id == None) # noqa: E711 + .first() + ) + + redis: ArqRedis = asyncio.run(create_pool(redis_settings)) + asyncio.run(redis.enqueue_job("sent_to_printer", data, printer.address, printer.cut_code)) + + +def design_cashier_report(report: CashierReport): + s = f"{report.cashier.name} Checkout By {report.user.name} @ {datetime.now():%d-%b-%Y %H:%M}".center(42) + s += "\n\r" + "-" * 42 + s += "\n\r" f"{report.start_date:%d-%b-%Y} To {report.finish_date:%d-%b-%Y}".center(42) + s += "\n\r" + "-" * 42 + for item in report.amounts: + s += f"\n\r{item.name} : {item.amount: >26.2f}" + s += "\n\r" + "=" * 42 + s += f"\n\rActive Cashiers : {report.cashiers}" + for so, it in report.info.items(): + s += f"\n\r" f"--- {so} ".ljust(42, "-") + for i in it: + s += f"\n\r{i.date} {i.bill_id} {i.customer}" + s += f"\n\rAmount: {i.amount:9.2f}" + s += "\n\r" + "-" * 42 + return s diff --git a/barker/barker/printing/discount_report.py b/barker/barker/printing/discount_report.py new file mode 100644 index 0000000..204d0cc --- /dev/null +++ b/barker/barker/printing/discount_report.py @@ -0,0 +1,32 @@ +import asyncio + +from arq import ArqRedis, create_pool +from sqlalchemy.orm import Session + +from ..core.arq import settings as redis_settings +from ..models import Printer, SectionPrinter +from ..schemas.discount_report import DiscountReport + + +def print_discount_report(report: DiscountReport, db: Session): + data = design_discount_report(report) + printer = ( + db.query(Printer) + .join(SectionPrinter.printer) + # .filter(SectionPrinter.section_id == voucher.food_table.section_id) TODO: Use device's section_id + .filter(SectionPrinter.menu_category_id == None) # noqa: E711 + .first() + ) + + redis: ArqRedis = asyncio.run(create_pool(redis_settings)) + asyncio.run(redis.enqueue_job("sent_to_printer", data, printer.address, printer.cut_code)) + + +def design_discount_report(report: DiscountReport): + s = f"{report.start_date:%d-%b-%Y} To {report.finish_date:%d-%b-%Y}".center(42) + s += "\n\r" + "-" * 42 + s += f"\n\r{'Name': <29.29} {'Amount': >12.12}" + for item in report.amounts: + s += f"\n\r{item.name: <29.29} {item.amount: >12.2f}" + s += "\n\r" + "=" * 42 + return s diff --git a/barker/barker/printing/sale_report.py b/barker/barker/printing/sale_report.py new file mode 100644 index 0000000..33d665c --- /dev/null +++ b/barker/barker/printing/sale_report.py @@ -0,0 +1,35 @@ +import asyncio + +from datetime import datetime + +from arq import ArqRedis, create_pool +from sqlalchemy.orm import Session + +from ..core.arq import settings as redis_settings +from ..models import Printer, SectionPrinter +from ..schemas.sale_report import SaleReport + + +def print_sale_report(report: SaleReport, db: Session): + data = design_sale_report(report) + printer = ( + db.query(Printer) + .join(SectionPrinter.printer) + # .filter(SectionPrinter.section_id == voucher.food_table.section_id) TODO: Use device's section_id + .filter(SectionPrinter.menu_category_id == None) # noqa: E711 + .first() + ) + + redis: ArqRedis = asyncio.run(create_pool(redis_settings)) + asyncio.run(redis.enqueue_job("sent_to_printer", data, printer.address, printer.cut_code)) + + +def design_sale_report(report: SaleReport): + s = f"{report.user.name} @ {datetime.now():%d-%b-%Y %H:%M}".center(42) + s += "\n\r" + "-" * 42 + s += "\n\r" f"{report.start_date:%d-%b-%Y} To {report.finish_date:%d-%b-%Y}".center(42) + s += "\n\r" + "-" * 42 + for item in report.amounts: + s += f"\n\r{item.name: <29.22} {item.amount: >12.2f}" + s += "\n\r" + "=" * 42 + return s diff --git a/barker/barker/routers/reports/bill_settlement_report.py b/barker/barker/routers/reports/bill_settlement_report.py index 0799fe9..de5a43f 100644 --- a/barker/barker/routers/reports/bill_settlement_report.py +++ b/barker/barker/routers/reports/bill_settlement_report.py @@ -1,4 +1,5 @@ from datetime import date, datetime, timedelta +from typing import List from fastapi import APIRouter, Depends, HTTPException, Security, status from sqlalchemy.orm import Session @@ -8,6 +9,7 @@ from ...core.security import get_current_active_user as get_user from ...db.session import SessionLocal from ...models import Reprint, Settlement, SettleOption, Voucher, VoucherType from ...schemas.auth import UserToken +from ...schemas.bill_settlement_report import BillSettlement, BillSettlementItem router = APIRouter() @@ -22,13 +24,13 @@ def get_db() -> Session: db.close() -@router.get("") +@router.get("", response_model=BillSettlement) def bill_details( s: str = None, f: str = None, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["bill-settlement-report"]), -): +) -> BillSettlement: start_date = date.today() if s is None else datetime.strptime(s, "%d-%b-%Y").date() finish_date = date.today() if f is None else datetime.strptime(f, "%d-%b-%Y").date() if (date.today() - start_date).days > 5 and "accounts-audit" not in user.permissions: @@ -37,14 +39,14 @@ def bill_details( detail="Accounts Audit", ) - return { - "startDate": start_date.strftime("%d-%b-%Y"), - "finishDate": finish_date.strftime("%d-%b-%Y"), - "amounts": settlements(start_date, finish_date, db) + reprints(start_date, finish_date, db), - } + return BillSettlement( + startDate=start_date, + finishDate=finish_date, + amounts=settlements(start_date, finish_date, db) + reprints(start_date, finish_date, db), + ) -def settlements(s: date, f: date, db: Session): +def settlements(s: date, f: date, db: Session) -> List[BillSettlementItem]: start_date = s + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES) finish_date = f + timedelta(days=1, minutes=settings.NEW_DAY_OFFSET_MINUTES) vouchers = ( @@ -69,28 +71,30 @@ def settlements(s: date, f: date, db: Session): else: details = so.settle_option.name report.append( - { - "date": item.date.strftime("%d-%b-%Y %H:%M:%S"), - "billId": item.full_bill_id, - "settlement": details, - "amount": round(so.amount, 2), - } + BillSettlementItem( + date=item.date, + billId=item.full_bill_id, + settlement=details, + amount=round(so.amount, 2), + ) ) + for i in report: + print(i.json()) return report -def reprints(s: date, f: date, db: Session): +def reprints(s: date, f: date, db: Session) -> List[BillSettlementItem]: start_date = s + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES) finish_date = f + timedelta(days=1, minutes=settings.NEW_DAY_OFFSET_MINUTES) return [ - { - "date": item.date.strftime("%d-%b-%Y %H:%M:%S"), - "billId": item.voucher.full_bill_id, - "settlement": f"Reprinted by {item.user.name}", - "Amount": round( + BillSettlementItem( + date=item.date, + billId=item.voucher.full_bill_id, + settlement=f"Reprinted by {item.user.name}", + amount=round( next(s.amount for s in item.voucher.settlements if s.settled == SettleOption.AMOUNT()) * -1, 2, ), - } + ) for item in db.query(Reprint).filter(Reprint.date >= start_date, Reprint.date <= finish_date).all() ] diff --git a/barker/barker/routers/reports/cashier_report.py b/barker/barker/routers/reports/cashier_report.py index 8ba9042..136ee5c 100644 --- a/barker/barker/routers/reports/cashier_report.py +++ b/barker/barker/routers/reports/cashier_report.py @@ -1,6 +1,7 @@ import uuid from datetime import date, datetime, timedelta +from typing import Dict, List from fastapi import APIRouter, Depends, HTTPException, Security, status from sqlalchemy import distinct @@ -10,7 +11,14 @@ from ...core.config import settings from ...core.security import get_current_active_user as get_user from ...db.session import SessionLocal from ...models import Settlement, User, Voucher -from ...schemas.auth import UserToken +from ...printing.cashier_report import print_cashier_report +from ...schemas.auth import UserLink, UserToken +from ...schemas.cashier_report import ( + CashierReport, + InfoItem, + NameAmount, + SettleOptionSchema, +) router = APIRouter() @@ -39,7 +47,10 @@ def active_cashiers( status_code=status.HTTP_403_FORBIDDEN, detail="Accounts Audit", ) + return get_active_cashiers(start_date, finish_date, db) + +def get_active_cashiers(start_date: date, finish_date: date, db: Session) -> List[UserLink]: users = ( db.query(User) .filter( @@ -53,18 +64,17 @@ def active_cashiers( .order_by(User.name) .all() ) - - return [{"id": u.id, "name": u.name} for u in users] + return [UserLink(id=u.id, name=u.name) for u in users] -@router.get("/{id_}") +@router.get("/{id_}", response_model=CashierReport) def show_id( id_: uuid.UUID, s: str = None, f: str = None, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["cashier-report"]), -): +) -> CashierReport: start_date = date.today() if s is None else datetime.strptime(s, "%d-%b-%Y").date() finish_date = date.today() if f is None else datetime.strptime(f, "%d-%b-%Y").date() if (date.today() - start_date).days > 5 and "accounts-audit" not in user.permissions: @@ -72,7 +82,12 @@ def show_id( status_code=status.HTTP_403_FORBIDDEN, detail="Accounts Audit", ) + user = UserLink(id=user.id_, name=user.name) + return get_id(id_, start_date, finish_date, user, db) + +def get_id(id_: uuid.UUID, start_date: date, finish_date: date, user: UserLink, db: Session) -> CashierReport: + cashier: str = db.query(User.name).filter(User.id == id_).scalar() vouchers = ( db.query(Voucher) .options(joinedload(Voucher.settlements, innerjoin=True).joinedload(Settlement.settle_option, innerjoin=True)) @@ -86,38 +101,61 @@ def show_id( .all() ) - info = {} + info: Dict[SettleOptionSchema, List[InfoItem]] = {} amounts = {} for item in vouchers: - 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] = [] + for so in (so for so in item.settlements if so.settle_option.show_in_choices or so.settle_option.is_print): + if so.settle_option.name not in info: + info[so.settle_option.name] = [] amounts[so.settle_option.name] = 0 - info[so.settled].append( - { - "date": item.date.strftime("%d-%b-%Y %H:%M:%S"), - "billId": item.full_bill_id, - "customer": item.customer.name if item.customer is not None else "", - "amount": so.amount, - } - ) amounts[so.settle_option.name] += so.amount - return { - "startDate": start_date.strftime("%d-%b-%Y"), - "finishDate": finish_date.strftime("%d-%b-%Y"), - "user": {"id": id_}, - "amounts": [{"name": key, "amount": value} for key, value in amounts.items()], - "info": info, - } + if so.settle_option.is_print: + info[so.settle_option.name].append( + InfoItem( + date=item.date, + billId=item.full_bill_id, + customer=item.customer.name if item.customer is not None else "", + amount=so.amount, + ) + ) + return CashierReport( + startDate=start_date, + finishDate=finish_date, + cashier=UserLink(id=id_, name=cashier), + cashiers=", ".join(x.name for x in get_active_cashiers(start_date, finish_date, db)), + amounts=[NameAmount(name=key, amount=value) for key, value in amounts.items()], + info=info, + user=user, + ) -@router.get("") +@router.get("/print/{id_}", response_model=bool) +def print_report( + id_: uuid.UUID, + s: str = None, + f: str = None, + db: Session = Depends(get_db), + user: UserToken = Security(get_user, scopes=["cashier-report"]), +) -> bool: + start_date = date.today() if s is None else datetime.strptime(s, "%d-%b-%Y").date() + finish_date = date.today() if f is None else datetime.strptime(f, "%d-%b-%Y").date() + if (date.today() - start_date).days > 5 and "accounts-audit" not in user.permissions: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Accounts Audit", + ) + report = get_id(id_, start_date, finish_date, user, db) + print_cashier_report(report, db) + return True + + +@router.get("", response_model=CashierReport) def show_blank( s: str = None, f: str = None, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["cashier-report"]), -): +) -> CashierReport: start_date = date.today() if s is None else datetime.strptime(s, "%d-%b-%Y").date() finish_date = date.today() if f is None else datetime.strptime(f, "%d-%b-%Y").date() if (date.today() - start_date).days > 5 and "accounts-audit" not in user.permissions: @@ -126,10 +164,12 @@ def show_blank( detail="Accounts Audit", ) - return { - "startDate": start_date.strftime("%d-%b-%Y"), - "finishDate": finish_date.strftime("%d-%b-%Y"), - "user": {"id": ""}, - "amounts": [], - "info": [], - } + return CashierReport( + startDate=start_date, + finishDate=finish_date, + cashier=UserLink(id=None), + cashiers="", + amounts=[], + info=[], + user=UserLink(id=user.id_), + ) diff --git a/barker/barker/routers/reports/discount_report.py b/barker/barker/routers/reports/discount_report.py index 1188145..c5215ab 100644 --- a/barker/barker/routers/reports/discount_report.py +++ b/barker/barker/routers/reports/discount_report.py @@ -1,4 +1,5 @@ from datetime import date, datetime, timedelta +from typing import List from fastapi import APIRouter, Depends, HTTPException, Security, status from sqlalchemy import func @@ -8,7 +9,9 @@ from ...core.config import settings from ...core.security import get_current_active_user as get_user from ...db.session import SessionLocal from ...models import Inventory, Kot, Product, SaleCategory, Voucher, VoucherType +from ...printing.discount_report import print_discount_report from ...schemas.auth import UserToken +from ...schemas.discount_report import DiscountReport, DiscountReportItem router = APIRouter() @@ -23,13 +26,13 @@ def get_db() -> Session: db.close() -@router.get("") -def discount_report_view( +@router.get("", response_model=DiscountReport) +def discount_report( s: str = None, f: str = None, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["discount-report"]), -): +) -> DiscountReport: start_date = date.today() if s is None else datetime.strptime(s, "%d-%b-%Y").date() finish_date = date.today() if f is None else datetime.strptime(f, "%d-%b-%Y").date() if (date.today() - start_date).days > 5 and "accounts-audit" not in user.permissions: @@ -37,15 +40,14 @@ def discount_report_view( status_code=status.HTTP_403_FORBIDDEN, detail="Accounts Audit", ) - - return { - "startDate": start_date.strftime("%d-%b-%Y"), - "finishDate": finish_date.strftime("%d-%b-%Y"), - "amounts": discount_report(start_date, finish_date, db), - } + return DiscountReport( + startDate=start_date, + finishDate=finish_date, + amounts=get_discount_report(start_date, finish_date, db), + ) -def discount_report(s: date, f: date, db: Session): +def get_discount_report(s: date, f: date, db: Session) -> List[DiscountReportItem]: start_date = s + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES) finish_date = f + timedelta(days=1, minutes=settings.NEW_DAY_OFFSET_MINUTES) @@ -67,4 +69,27 @@ def discount_report(s: date, f: date, db: Session): .all() ) - return [{"name": pg, "amount": amt} for pg, amt in list_] + return [DiscountReportItem(name=pg, amount=amt) for pg, amt in list_] + + +@router.get("/print", response_model=bool) +def print_report( + s: str = None, + f: str = None, + db: Session = Depends(get_db), + user: UserToken = Security(get_user, scopes=["discount-report"]), +) -> bool: + start_date = date.today() if s is None else datetime.strptime(s, "%d-%b-%Y").date() + finish_date = date.today() if f is None else datetime.strptime(f, "%d-%b-%Y").date() + if (date.today() - start_date).days > 5 and "accounts-audit" not in user.permissions: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Accounts Audit", + ) + report = DiscountReport( + startDate=start_date, + finishDate=finish_date, + amounts=get_discount_report(start_date, finish_date, db), + ) + print_discount_report(report, db) + return True diff --git a/barker/barker/routers/reports/sale_report.py b/barker/barker/routers/reports/sale_report.py index 7f395d6..cf7d6e1 100644 --- a/barker/barker/routers/reports/sale_report.py +++ b/barker/barker/routers/reports/sale_report.py @@ -1,4 +1,5 @@ from datetime import date, datetime, timedelta +from typing import List from fastapi import APIRouter, Depends, HTTPException, Security, status from sqlalchemy import func @@ -17,7 +18,9 @@ from ...models import ( Voucher, VoucherType, ) -from ...schemas.auth import UserToken +from ...printing.sale_report import print_sale_report +from ...schemas.auth import UserLink, UserToken +from ...schemas.sale_report import SaleReport, SaleReportItem from .tax_report import get_tax @@ -33,13 +36,13 @@ def get_db() -> Session: db.close() -@router.get("") +@router.get("", response_model=SaleReport) def get_sale_analysis( s: str = None, f: str = None, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["sale-report"]), -): +) -> SaleReport: start_date = date.today() if s is None else datetime.strptime(s, "%d-%b-%Y").date() finish_date = date.today() if f is None else datetime.strptime(f, "%d-%b-%Y").date() if (date.today() - start_date).days > 5 and "accounts-audit" not in user.permissions: @@ -48,20 +51,21 @@ def get_sale_analysis( detail="Accounts Audit", ) - return { - "startDate": start_date.strftime("%d-%b-%Y"), - "finishDate": finish_date.strftime("%d-%b-%Y"), - "amounts": ( + return SaleReport( + startDate=start_date, + finishDate=finish_date, + amounts=( get_sale(start_date, finish_date, db) - + [{"name": "--", "amount": 0}] + + [SaleReportItem(name="--", amount=0)] + get_settlements(start_date, finish_date, db) - + [{"name": "--", "amount": 0}] - + get_tax(start_date, finish_date, db) + + [SaleReportItem(name="--", amount=0)] + + [SaleReportItem(name=i.name, amount=i.amount) for i in get_tax(start_date, finish_date, db)] ), - } + user=UserLink(id=user.id_, name=user.name), + ) -def get_sale(s: date, f: date, db: Session): +def get_sale(s: date, f: date, db: Session) -> List[SaleReportItem]: start_date = s + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES) finish_date = f + timedelta(days=1, minutes=settings.NEW_DAY_OFFSET_MINUTES) @@ -84,11 +88,11 @@ def get_sale(s: date, f: date, db: Session): info = [] for gt, am in list_: total += am - info.append({"name": gt, "amount": am}) - return info + [{"name": "Total Settled", "amount": total}] + info.append(SaleReportItem(name=gt, amount=am)) + return info + [SaleReportItem(name="Total Settled", amount=total)] -def get_settlements(s: date, f: date, db: Session): +def get_settlements(s: date, f: date, db: Session) -> List[SaleReportItem]: start_date = s + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES) finish_date = f + timedelta(days=1, minutes=settings.NEW_DAY_OFFSET_MINUTES) @@ -105,5 +109,35 @@ def get_settlements(s: date, f: date, db: Session): info = [] for gt, am in list_: total += am - info.append({"name": gt, "amount": am}) - return info + [{"name": "Total", "amount": total}] + info.append(SaleReportItem(name=gt, amount=am)) + return info + [SaleReportItem(name="Total", amount=total)] + + +@router.get("/print", response_model=bool) +def print_report( + s: str = None, + f: str = None, + db: Session = Depends(get_db), + user: UserToken = Security(get_user, scopes=["discount-report"]), +) -> bool: + start_date = date.today() if s is None else datetime.strptime(s, "%d-%b-%Y").date() + finish_date = date.today() if f is None else datetime.strptime(f, "%d-%b-%Y").date() + if (date.today() - start_date).days > 5 and "accounts-audit" not in user.permissions: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Accounts Audit", + ) + report = SaleReport( + startDate=start_date, + finishDate=finish_date, + amounts=( + get_sale(start_date, finish_date, db) + + [SaleReportItem(name="--", amount=0)] + + get_settlements(start_date, finish_date, db) + + [SaleReportItem(name="--", amount=0)] + + [SaleReportItem(name=i.name, amount=i.amount) for i in get_tax(start_date, finish_date, db)] + ), + user=UserLink(id=user.id_, name=user.name), + ) + print_sale_report(report, db) + return True diff --git a/barker/barker/routers/reports/tax_report.py b/barker/barker/routers/reports/tax_report.py index c47c657..654d028 100644 --- a/barker/barker/routers/reports/tax_report.py +++ b/barker/barker/routers/reports/tax_report.py @@ -1,4 +1,5 @@ from datetime import date, datetime, timedelta +from typing import List from fastapi import APIRouter, Depends, HTTPException, Security, status from sqlalchemy import func @@ -9,6 +10,7 @@ from ...core.security import get_current_active_user as get_user from ...db.session import SessionLocal from ...models import Inventory, Kot, Tax, Voucher, VoucherType from ...schemas.auth import UserToken +from ...schemas.tax_report import TaxReport, TaxReportItem router = APIRouter() @@ -23,13 +25,13 @@ def get_db() -> Session: db.close() -@router.get("") +@router.get("", response_model=TaxReport) def get_tax_report( s: str = None, f: str = None, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["tax-report"]), -): +) -> TaxReport: start_date = date.today() if s is None else datetime.strptime(s, "%d-%b-%Y").date() finish_date = date.today() if f is None else datetime.strptime(f, "%d-%b-%Y").date() if (date.today() - start_date).days > 5 and "accounts-audit" not in user.permissions: @@ -38,14 +40,14 @@ def get_tax_report( detail="Accounts Audit", ) - return { - "startDate": start_date.strftime("%d-%b-%Y"), - "finishDate": finish_date.strftime("%d-%b-%Y"), - "amounts": get_tax(start_date, finish_date, db), - } + return TaxReport( + startDate=start_date.strftime("%d-%b-%Y"), + finishDate=finish_date.strftime("%d-%b-%Y"), + amounts=get_tax(start_date, finish_date, db), + ) -def get_tax(s: date, f: date, db: Session): +def get_tax(s: date, f: date, db: Session) -> List[TaxReportItem]: start_date = s + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES) finish_date = f + timedelta(days=1, minutes=settings.NEW_DAY_OFFSET_MINUTES) @@ -68,12 +70,4 @@ def get_tax(s: date, f: date, db: Session): .order_by(Tax.name, Inventory.tax_rate) .all() ) - return [ - { - "name": "{0} - {1:.2%}".format(i[0], i[1]), - "taxRate": i[1], - "saleAmount": i[2], - "amount": i[3], - } - for i in amounts - ] + return [TaxReportItem(name=f"{i[0]} - {i[1]:.2%}", taxRate=i[1], saleAmount=i[2], amount=i[3]) for i in amounts] diff --git a/barker/barker/schemas/auth.py b/barker/barker/schemas/auth.py index 78fadae..99ec093 100644 --- a/barker/barker/schemas/auth.py +++ b/barker/barker/schemas/auth.py @@ -89,6 +89,14 @@ class User(UserIn): id_: uuid.UUID +class UserLink(BaseModel): + id_: Optional[uuid.UUID] + name: Optional[str] + + class Config: + alias_generator = to_camel + + class UserList(BaseModel): id_: uuid.UUID name: str diff --git a/barker/barker/schemas/bill_settlement_report.py b/barker/barker/schemas/bill_settlement_report.py new file mode 100644 index 0000000..ff4c6f7 --- /dev/null +++ b/barker/barker/schemas/bill_settlement_report.py @@ -0,0 +1,50 @@ +from datetime import date, datetime +from decimal import Decimal +from typing import List + +from pydantic import BaseModel, validator + +from . import to_camel + + +class BillSettlementItem(BaseModel): + date: datetime + bill_id: str + settlement: str + amount: Decimal + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + json_encoders = { + datetime: lambda v: v.strftime("%d-%b-%Y %H:%M"), + } + + @validator("date", pre=True) + def parse_date(cls, value): + if isinstance(value, datetime): + return value + return datetime.strptime(value, "%d-%b-%Y %H:%M") + + +class BillSettlement(BaseModel): + start_date: date + finish_date: date + amounts: List[BillSettlementItem] + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + json_encoders = {datetime: lambda v: v.strftime("%d-%b-%Y %H:%M"), date: lambda v: v.strftime("%d-%b-%Y")} + + @validator("start_date", pre=True) + def parse_start_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() + + @validator("finish_date", pre=True) + def parse_finish_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() diff --git a/barker/barker/schemas/cashier_report.py b/barker/barker/schemas/cashier_report.py new file mode 100644 index 0000000..a7e2ae8 --- /dev/null +++ b/barker/barker/schemas/cashier_report.py @@ -0,0 +1,76 @@ +from datetime import date, datetime +from decimal import Decimal +from typing import Dict, List + +from pydantic import BaseModel, validator + +from . import to_camel +from .auth import UserLink + + +class NameAmount(BaseModel): + name: str + amount: Decimal + + class Config: + anystr_strip_whitespace = True + + +class SettleOptionSchema(BaseModel): + def __hash__(self): + return hash(self.id) + + def __eq__(self, other): + return self.id == other.id + + id: int + name: str + + class Config: + fields = {"id": "id"} + anystr_strip_whitespace = True + + +class InfoItem(BaseModel): + date: datetime + bill_id: str + customer: str + amount: Decimal + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + + @validator("date", pre=True) + def parse_date(cls, value): + if isinstance(value, datetime): + return value + return datetime.strptime(value, "%d-%b-%Y %H:%M") + + +class CashierReport(BaseModel): + start_date: date + finish_date: date + cashier: UserLink + cashiers: str + user: UserLink + amounts: List[NameAmount] + info: Dict[str, List[InfoItem]] + + class Config: + fields = {"id_": "id"} + anystr_strip_whitespace = True + alias_generator = to_camel + json_encoders = {datetime: lambda v: v.strftime("%d-%b-%Y %H:%M"), date: lambda v: v.strftime("%d-%b-%Y")} + + @validator("start_date", pre=True) + def parse_start_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() + + @validator("finish_date", pre=True) + def parse_finish_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() diff --git a/barker/barker/schemas/discount_report.py b/barker/barker/schemas/discount_report.py new file mode 100644 index 0000000..9617fcd --- /dev/null +++ b/barker/barker/schemas/discount_report.py @@ -0,0 +1,39 @@ +from datetime import date, datetime +from decimal import Decimal +from typing import List + +from pydantic import BaseModel, validator + +from . import to_camel + + +class DiscountReportItem(BaseModel): + name: str + amount: Decimal + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + + +class DiscountReport(BaseModel): + start_date: date + finish_date: date + amounts: List[DiscountReportItem] + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + json_encoders = {datetime: lambda v: v.strftime("%d-%b-%Y %H:%M"), date: lambda v: v.strftime("%d-%b-%Y")} + + @validator("start_date", pre=True) + def parse_start_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() + + @validator("finish_date", pre=True) + def parse_finish_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() diff --git a/barker/barker/schemas/sale_report.py b/barker/barker/schemas/sale_report.py new file mode 100644 index 0000000..0602328 --- /dev/null +++ b/barker/barker/schemas/sale_report.py @@ -0,0 +1,41 @@ +from datetime import date, datetime +from decimal import Decimal +from typing import List + +from pydantic import BaseModel, validator + +from . import to_camel +from .auth import UserLink + + +class SaleReportItem(BaseModel): + name: str + amount: Decimal + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + + +class SaleReport(BaseModel): + start_date: date + finish_date: date + amounts: List[SaleReportItem] + user: UserLink + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + json_encoders = {datetime: lambda v: v.strftime("%d-%b-%Y %H:%M"), date: lambda v: v.strftime("%d-%b-%Y")} + + @validator("start_date", pre=True) + def parse_start_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() + + @validator("finish_date", pre=True) + def parse_finish_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() diff --git a/barker/barker/schemas/tax_report.py b/barker/barker/schemas/tax_report.py new file mode 100644 index 0000000..4ae805d --- /dev/null +++ b/barker/barker/schemas/tax_report.py @@ -0,0 +1,41 @@ +from datetime import date, datetime +from decimal import Decimal +from typing import List + +from pydantic import BaseModel, validator + +from . import to_camel + + +class TaxReportItem(BaseModel): + name: str + tax_rate: Decimal + sale_amount: Decimal + amount: Decimal + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + + +class TaxReport(BaseModel): + start_date: date + finish_date: date + amounts: List[TaxReportItem] + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + json_encoders = {datetime: lambda v: v.strftime("%d-%b-%Y %H:%M"), date: lambda v: v.strftime("%d-%b-%Y")} + + @validator("start_date", pre=True) + def parse_start_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() + + @validator("finish_date", pre=True) + def parse_finish_date(cls, value): + if isinstance(value, date): + return value + return datetime.strptime(value, "%d-%b-%Y").date() diff --git a/barker/pyproject.toml b/barker/pyproject.toml index 76efbc4..91a27fd 100644 --- a/barker/pyproject.toml +++ b/barker/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "barker" -version = "2.0.0" +version = "3.0.0" description = "Point of Sale for a restaurant" authors = ["tanshu "] diff --git a/bookie/src/app/cashier-report/cashier-report.component.css b/bookie/src/app/cashier-report/cashier-report.component.css index a9626b3..747e946 100644 --- a/bookie/src/app/cashier-report/cashier-report.component.css +++ b/bookie/src/app/cashier-report/cashier-report.component.css @@ -2,3 +2,7 @@ display: flex; justify-content: flex-end; } + +.spacer { + flex: 1 1 auto; +} diff --git a/bookie/src/app/cashier-report/cashier-report.component.html b/bookie/src/app/cashier-report/cashier-report.component.html index ed60a5b..8d093c5 100644 --- a/bookie/src/app/cashier-report/cashier-report.component.html +++ b/bookie/src/app/cashier-report/cashier-report.component.html @@ -1,9 +1,13 @@ Cashier Report - +
diff --git a/bookie/src/app/cashier-report/cashier-report.component.ts b/bookie/src/app/cashier-report/cashier-report.component.ts index 2d58841..e95910c 100644 --- a/bookie/src/app/cashier-report/cashier-report.component.ts +++ b/bookie/src/app/cashier-report/cashier-report.component.ts @@ -3,11 +3,13 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; +import { ToasterService } from '../core/toaster.service'; import { User } from '../core/user'; import { ToCsvService } from '../shared/to-csv.service'; import { CashierReport } from './cashier-report'; import { CashierReportDataSource } from './cashier-report-datasource'; +import { CashierReportService } from './cashier-report.service'; @Component({ selector: 'app-cashier-report', @@ -28,6 +30,8 @@ export class CashierReportComponent implements OnInit { private router: Router, private fb: FormBuilder, private toCsv: ToCsvService, + private toaster: ToasterService, + private ser: CashierReportService, ) { this.createForm(); } @@ -37,7 +41,7 @@ export class CashierReportComponent implements OnInit { this.activeCashiers = data.cashiers; this.info = data.info; this.form.setValue({ - cashier: this.info.user.id, + cashier: this.info.cashier.id, startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); @@ -48,8 +52,8 @@ export class CashierReportComponent implements OnInit { show() { const info = this.getInfo(); const url = ['cashier-report']; - if (info.user.id) { - url.push(info.user.id); + if (info.cashier.id) { + url.push(info.cashier.id); } this.router.navigate(url, { queryParams: { @@ -59,6 +63,17 @@ export class CashierReportComponent implements OnInit { }); } + print() { + this.ser.print(this.info.cashier.id, this.info.startDate, this.info.finishDate).subscribe( + () => { + this.toaster.show('', 'Successfully Printed'); + }, + (error) => { + this.toaster.show('Error', error.error); + }, + ); + } + createForm() { this.form = this.fb.group({ startDate: '', @@ -71,7 +86,8 @@ export class CashierReportComponent implements OnInit { const formModel = this.form.value; return { - user: new User({ id: formModel.cashier }), + cashier: new User({ id: formModel.cashier }), + cashiers: this.info.cashiers, startDate: moment(formModel.startDate).format('DD-MMM-YYYY'), finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'), }; diff --git a/bookie/src/app/cashier-report/cashier-report.service.ts b/bookie/src/app/cashier-report/cashier-report.service.ts index ff1da7f..3de203d 100644 --- a/bookie/src/app/cashier-report/cashier-report.service.ts +++ b/bookie/src/app/cashier-report/cashier-report.service.ts @@ -46,4 +46,20 @@ export class CashierReportService { .pipe(catchError(this.log.handleError(serviceName, 'activeCashiers'))); } + + print(id: string, startDate: string, finishDate): Observable { + const printUrl = `${url}/print/${id}`; + const options = { params: new HttpParams() }; + if (startDate !== null) { + options.params = options.params.set('s', startDate); + } + if (finishDate !== null) { + options.params = options.params.set('f', finishDate); + } + return >( + this.http + .get(printUrl, options) + .pipe(catchError(this.log.handleError(serviceName, 'print'))) + ); + } } diff --git a/bookie/src/app/cashier-report/cashier-report.ts b/bookie/src/app/cashier-report/cashier-report.ts index 0e7f813..f8f32b7 100644 --- a/bookie/src/app/cashier-report/cashier-report.ts +++ b/bookie/src/app/cashier-report/cashier-report.ts @@ -6,7 +6,8 @@ import { CashierReportPrintItem } from './cashier-report-print-item'; export class CashierReport { startDate: string; finishDate: string; - user: User; + cashier: User; + cashiers: string; amounts?: CashierReportDisplayItem[]; info?: CashierReportPrintItem[]; } diff --git a/bookie/src/app/discount-report/discount-report.component.css b/bookie/src/app/discount-report/discount-report.component.css index a9626b3..747e946 100644 --- a/bookie/src/app/discount-report/discount-report.component.css +++ b/bookie/src/app/discount-report/discount-report.component.css @@ -2,3 +2,7 @@ display: flex; justify-content: flex-end; } + +.spacer { + flex: 1 1 auto; +} diff --git a/bookie/src/app/discount-report/discount-report.component.html b/bookie/src/app/discount-report/discount-report.component.html index 569610d..115fcfe 100644 --- a/bookie/src/app/discount-report/discount-report.component.html +++ b/bookie/src/app/discount-report/discount-report.component.html @@ -1,9 +1,13 @@ Discount Report + + diff --git a/bookie/src/app/discount-report/discount-report.component.ts b/bookie/src/app/discount-report/discount-report.component.ts index 25a2728..e2eeb56 100644 --- a/bookie/src/app/discount-report/discount-report.component.ts +++ b/bookie/src/app/discount-report/discount-report.component.ts @@ -3,10 +3,12 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; +import { ToasterService } from '../core/toaster.service'; import { ToCsvService } from '../shared/to-csv.service'; import { DiscountReport } from './discount-report'; import { DiscountReportDataSource } from './discount-report-datasource'; +import { DiscountReportService } from './discount-report.service'; @Component({ selector: 'app-discount-report', @@ -26,6 +28,8 @@ export class DiscountReportComponent implements OnInit { private router: Router, private fb: FormBuilder, private toCsv: ToCsvService, + private toaster: ToasterService, + private ser: DiscountReportService, ) { this.createForm(); } @@ -67,6 +71,17 @@ export class DiscountReportComponent implements OnInit { }; } + print() { + this.ser.print(this.info.startDate, this.info.finishDate).subscribe( + () => { + this.toaster.show('', 'Successfully Printed'); + }, + (error) => { + this.toaster.show('Error', error.error); + }, + ); + } + exportCsv() { const headers = { Name: 'name', diff --git a/bookie/src/app/discount-report/discount-report.service.ts b/bookie/src/app/discount-report/discount-report.service.ts index 5826484..4d8b10b 100644 --- a/bookie/src/app/discount-report/discount-report.service.ts +++ b/bookie/src/app/discount-report/discount-report.service.ts @@ -30,4 +30,20 @@ export class DiscountReportService { .pipe(catchError(this.log.handleError(serviceName, 'get'))) ); } + + print(startDate: string, finishDate): Observable { + const printUrl = `${url}/print`; + const options = { params: new HttpParams() }; + if (startDate !== null) { + options.params = options.params.set('s', startDate); + } + if (finishDate !== null) { + options.params = options.params.set('f', finishDate); + } + return >( + this.http + .get(printUrl, options) + .pipe(catchError(this.log.handleError(serviceName, 'print'))) + ); + } } diff --git a/bookie/src/app/sale-report/sale-report.component.css b/bookie/src/app/sale-report/sale-report.component.css index a9626b3..747e946 100644 --- a/bookie/src/app/sale-report/sale-report.component.css +++ b/bookie/src/app/sale-report/sale-report.component.css @@ -2,3 +2,7 @@ display: flex; justify-content: flex-end; } + +.spacer { + flex: 1 1 auto; +} diff --git a/bookie/src/app/sale-report/sale-report.component.html b/bookie/src/app/sale-report/sale-report.component.html index b720f15..65363c8 100644 --- a/bookie/src/app/sale-report/sale-report.component.html +++ b/bookie/src/app/sale-report/sale-report.component.html @@ -1,9 +1,13 @@ Sale Report + + diff --git a/bookie/src/app/sale-report/sale-report.component.ts b/bookie/src/app/sale-report/sale-report.component.ts index 25310a4..56af2c7 100644 --- a/bookie/src/app/sale-report/sale-report.component.ts +++ b/bookie/src/app/sale-report/sale-report.component.ts @@ -3,10 +3,12 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; +import { ToasterService } from '../core/toaster.service'; import { ToCsvService } from '../shared/to-csv.service'; import { SaleReport } from './sale-report'; import { SaleReportDatasource } from './sale-report-datasource'; +import { SaleReportService } from './sale-report.service'; @Component({ selector: 'app-sale-report', @@ -26,6 +28,8 @@ export class SaleReportComponent implements OnInit { private router: Router, private fb: FormBuilder, private toCsv: ToCsvService, + private toaster: ToasterService, + private ser: SaleReportService, ) { this.createForm(); } @@ -67,6 +71,17 @@ export class SaleReportComponent implements OnInit { }; } + print() { + this.ser.print(this.info.startDate, this.info.finishDate).subscribe( + () => { + this.toaster.show('', 'Successfully Printed'); + }, + (error) => { + this.toaster.show('Error', error.error); + }, + ); + } + exportCsv() { const headers = { Name: 'name', diff --git a/bookie/src/app/sale-report/sale-report.service.ts b/bookie/src/app/sale-report/sale-report.service.ts index affc89a..3689b36 100644 --- a/bookie/src/app/sale-report/sale-report.service.ts +++ b/bookie/src/app/sale-report/sale-report.service.ts @@ -30,4 +30,20 @@ export class SaleReportService { .pipe(catchError(this.log.handleError(serviceName, 'get'))) ); } + + print(startDate: string, finishDate): Observable { + const printUrl = `${url}/print`; + const options = { params: new HttpParams() }; + if (startDate !== null) { + options.params = options.params.set('s', startDate); + } + if (finishDate !== null) { + options.params = options.params.set('f', finishDate); + } + return >( + this.http + .get(printUrl, options) + .pipe(catchError(this.log.handleError(serviceName, 'print'))) + ); + } } diff --git a/bookie/src/environments/environment.prod.ts b/bookie/src/environments/environment.prod.ts index f59a58c..2f0a804 100644 --- a/bookie/src/environments/environment.prod.ts +++ b/bookie/src/environments/environment.prod.ts @@ -1,5 +1,5 @@ export const environment = { production: true, ACCESS_TOKEN_REFRESH_MINUTES: 10, // refresh token 10 minutes before expiry - version: "2.0.0", + version: '3.0.0', }; diff --git a/bookie/src/environments/environment.ts b/bookie/src/environments/environment.ts index c2002c7..e9d9f7e 100644 --- a/bookie/src/environments/environment.ts +++ b/bookie/src/environments/environment.ts @@ -5,7 +5,7 @@ export const environment = { production: false, ACCESS_TOKEN_REFRESH_MINUTES: 10, // refresh token 10 minutes before expiry - version: "2.0.0", + version: '3.0.0', }; /*