From 770fd0c6dde8dc5f9452b975ebcc63748b876230 Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Wed, 18 Feb 2026 10:08:27 +0000 Subject: [PATCH] mypy and ruff changes. --- brewman/brewman/core/config.py | 21 +-- brewman/brewman/core/security.py | 4 +- brewman/brewman/db/base.py | 124 +++++++++----- brewman/brewman/db/session.py | 2 +- brewman/brewman/models/account_base.py | 8 +- brewman/brewman/models/account_type.py | 4 +- brewman/brewman/models/attendance.py | 4 +- brewman/brewman/models/attendance_type.py | 6 +- brewman/brewman/models/cost_centre.py | 6 +- brewman/brewman/models/employee.py | 6 +- brewman/brewman/models/employee_benefit.py | 4 +- brewman/brewman/models/fingerprint.py | 2 +- brewman/brewman/models/incentive.py | 4 +- brewman/brewman/models/inventory.py | 6 +- brewman/brewman/models/journal.py | 12 +- brewman/brewman/models/login_history.py | 4 +- brewman/brewman/models/product.py | 4 +- brewman/brewman/models/product_group.py | 2 +- brewman/brewman/models/product_version.py | 12 +- brewman/brewman/models/rate_contract.py | 6 +- brewman/brewman/models/rate_contract_item.py | 4 +- brewman/brewman/models/role.py | 2 +- brewman/brewman/models/sku_version.py | 2 +- brewman/brewman/models/tag.py | 2 +- brewman/brewman/models/voucher.py | 20 +-- brewman/brewman/routers/__init__.py | 46 +++++- brewman/brewman/routers/account.py | 12 +- brewman/brewman/routers/attendance.py | 3 +- brewman/brewman/routers/batch.py | 3 +- .../brewman/routers/calculate_nutrition.py | 114 +++++++++---- brewman/brewman/routers/employee.py | 5 +- .../brewman/routers/employee_attendance.py | 12 +- brewman/brewman/routers/issue.py | 8 +- brewman/brewman/routers/journal.py | 2 +- brewman/brewman/routers/product.py | 6 +- brewman/brewman/routers/purchase.py | 5 +- brewman/brewman/routers/purchase_return.py | 5 +- brewman/brewman/routers/rate_contract.py | 5 +- brewman/brewman/routers/recipe.py | 41 +++-- brewman/brewman/routers/recipe_template.py | 6 +- brewman/brewman/routers/reports/cash_flow.py | 1 - .../brewman/routers/reports/closing_stock.py | 15 +- brewman/brewman/routers/reports/ledger.py | 3 +- .../routers/reports/mozimo_daily_register.py | 3 +- .../reports/mozimo_product_register.py | 26 +-- .../routers/reports/mozimo_sale_upload.py | 78 ++++----- .../brewman/routers/reports/product_ledger.py | 3 +- brewman/brewman/routers/reports/reconcile.py | 5 +- brewman/brewman/routers/role.py | 7 +- brewman/brewman/routers/user.py | 7 +- brewman/brewman/routers/voucher.py | 83 ++++++---- brewman/brewman/schemas/employee.py | 2 +- brewman/brewman/schemas/input.py | 2 +- .../schemas/mozimo_product_register.py | 12 +- brewman/brewman/schemas/raw_material_cost.py | 4 +- brewman/poetry.lock | 14 +- brewman/pyproject.toml | 152 +++++++++--------- 57 files changed, 563 insertions(+), 398 deletions(-) diff --git a/brewman/brewman/core/config.py b/brewman/brewman/core/config.py index 733dd362..e9434551 100644 --- a/brewman/brewman/core/config.py +++ b/brewman/brewman/core/config.py @@ -1,7 +1,7 @@ import secrets from dotenv import load_dotenv -from pydantic import PostgresDsn, field_validator, model_validator +from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict @@ -18,11 +18,7 @@ class Settings(BaseSettings): PORT: int = 80 DEBUG: bool = False LOG_LEVEL: str = "NOTSET" - POSTGRES_SERVER: str = "" - POSTGRES_USER: str = "postgres" - POSTGRES_PASSWORD: str = "" - POSTGRES_DB: str = "" - SQLALCHEMY_DATABASE_URI: str | None = None + SQLALCHEMY_DATABASE_URI: str = "" @field_validator("PRIVATE_KEY", mode="before") def convert_private_key_newlines(cls, v: str | None) -> str | None: @@ -36,19 +32,6 @@ class Settings(BaseSettings): return v.replace("\\n", "\n") return v - @model_validator(mode="after") - def assemble_db_connection(self) -> "Settings": - if isinstance(self.SQLALCHEMY_DATABASE_URI, str): - return self - self.SQLALCHEMY_DATABASE_URI = PostgresDsn.build( - scheme="postgresql+psycopg", - username=self.POSTGRES_USER, - password=self.POSTGRES_PASSWORD, - host=self.POSTGRES_SERVER, - path=f"/{self.POSTGRES_DB or ''}", - ) - return self - ALEMBIC_LOG_LEVEL: str = "INFO" ALEMBIC_SQLALCHEMY_LOG_LEVEL: str = "WARN" model_config = SettingsConfigDict(case_sensitive=True, env_file=".env") diff --git a/brewman/brewman/core/security.py b/brewman/brewman/core/security.py index f2417bf6..f850eb1a 100644 --- a/brewman/brewman/core/security.py +++ b/brewman/brewman/core/security.py @@ -86,11 +86,11 @@ async def get_current_user( raise credentials_exception token_scopes = payload.get("scopes", []) token_data = TokenData(scopes=token_scopes, username=username) - except (PyJWTError, ValidationError): + except PyJWTError, ValidationError: raise credentials_exception user = get_user( username=token_data.username, - id_=payload.get("userId", None), + id_=payload.get("userId", ""), locked_out=payload.get("lockedOut", True), scopes=token_scopes, ) diff --git a/brewman/brewman/db/base.py b/brewman/brewman/db/base.py index 5963a9e6..cebcac17 100644 --- a/brewman/brewman/db/base.py +++ b/brewman/brewman/db/base.py @@ -1,42 +1,86 @@ # Import all the models, so that Base has them before being # imported by Alembic -from ..models.account import Account # noqa: F401 -from ..models.account_base import AccountBase # noqa: F401 -from ..models.account_type import AccountType # noqa: F401 -from ..models.attendance import Attendance # noqa: F401 -from ..models.batch import Batch # noqa: F401 -from ..models.client import Client # noqa: F401 -from ..models.closing_stock import ClosingStock # noqa: F401 -from ..models.cost_centre import CostCentre # noqa: F401 -from ..models.db_image import DbImage # noqa: F401 -from ..models.db_setting import DbSetting # noqa: F401 -from ..models.employee import Employee # noqa: F401 -from ..models.employee_benefit import EmployeeBenefit # noqa: F401 -from ..models.fingerprint import Fingerprint # noqa: F401 -from ..models.incentive import Incentive # noqa: F401 -from ..models.inventory import Inventory # noqa: F401 -from ..models.journal import Journal # noqa: F401 -from ..models.login_history import LoginHistory # noqa: F401 -from ..models.mozimo_stock_register import MozimoStockRegister # noqa: F401 -from ..models.period import Period # noqa: F401 -from ..models.permission import Permission # noqa: F401 -from ..models.price import Price # noqa: F401 -from ..models.product import Product # noqa: F401 -from ..models.product_group import ProductGroup # noqa: F401 -from ..models.product_version import ProductVersion # noqa: F401 -from ..models.rate_contract import RateContract # noqa: F401 -from ..models.rate_contract_item import RateContractItem # noqa: F401 -from ..models.recipe import Recipe # noqa: F401 -from ..models.recipe_item import RecipeItem # noqa: F401 -from ..models.recipe_tag import RecipeTag # noqa: F401 -from ..models.recipe_template import RecipeTemplate # noqa: F401 -from ..models.role import Role # noqa: F401 -from ..models.role_permission import RolePermission # noqa: F401 -from ..models.stock_keeping_unit import StockKeepingUnit # noqa: F401 -from ..models.tag import Tag # noqa: F401 -from ..models.user import User # noqa: F401 -from ..models.user_role import UserRole # noqa: F401 -from ..models.voucher import Voucher # noqa: F401 -from ..models.voucher_tag import VoucherTag # noqa: F401 -from ..models.voucher_type import VoucherType # noqa: F401 -from .base_class import reg # noqa: F401 +from ..models.account import Account +from ..models.account_base import AccountBase +from ..models.account_type import AccountType +from ..models.attendance import Attendance +from ..models.batch import Batch +from ..models.client import Client +from ..models.closing_stock import ClosingStock +from ..models.cost_centre import CostCentre +from ..models.db_image import DbImage +from ..models.db_setting import DbSetting +from ..models.employee import Employee +from ..models.employee_benefit import EmployeeBenefit +from ..models.fingerprint import Fingerprint +from ..models.incentive import Incentive +from ..models.inventory import Inventory +from ..models.journal import Journal +from ..models.login_history import LoginHistory +from ..models.mozimo_stock_register import MozimoStockRegister +from ..models.period import Period +from ..models.permission import Permission +from ..models.price import Price +from ..models.product import Product +from ..models.product_group import ProductGroup +from ..models.product_version import ProductVersion +from ..models.rate_contract import RateContract +from ..models.rate_contract_item import RateContractItem +from ..models.recipe import Recipe +from ..models.recipe_item import RecipeItem +from ..models.recipe_tag import RecipeTag +from ..models.recipe_template import RecipeTemplate +from ..models.role import Role +from ..models.role_permission import RolePermission +from ..models.stock_keeping_unit import StockKeepingUnit +from ..models.tag import Tag +from ..models.user import User +from ..models.user_role import UserRole +from ..models.voucher import Voucher +from ..models.voucher_tag import VoucherTag +from ..models.voucher_type import VoucherType +from .base_class import reg + + +__all__ = [ + "Account", + "AccountBase", + "AccountType", + "Attendance", + "Batch", + "Client", + "ClosingStock", + "CostCentre", + "DbImage", + "DbSetting", + "Employee", + "EmployeeBenefit", + "Fingerprint", + "Incentive", + "Inventory", + "Journal", + "LoginHistory", + "MozimoStockRegister", + "Permission", + "Period", + "Price", + "Product", + "ProductGroup", + "ProductVersion", + "RateContract", + "RateContractItem", + "Recipe", + "RecipeItem", + "RecipeTag", + "RecipeTemplate", + "reg", + "Role", + "RolePermission", + "StockKeepingUnit", + "Tag", + "User", + "UserRole", + "Voucher", + "VoucherTag", + "VoucherType", +] diff --git a/brewman/brewman/db/session.py b/brewman/brewman/db/session.py index c584b81d..330a2a74 100644 --- a/brewman/brewman/db/session.py +++ b/brewman/brewman/db/session.py @@ -8,5 +8,5 @@ from ..core.config import settings logging.basicConfig() logging.getLogger("sqlalchemy.engine").setLevel(settings.LOG_LEVEL) -engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True, future=True) # type: ignore[arg-type] +engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True, future=True) SessionFuture = sessionmaker(autoflush=False, bind=engine, future=True) diff --git a/brewman/brewman/models/account_base.py b/brewman/brewman/models/account_base.py index b37b180c..0ee92f5d 100644 --- a/brewman/brewman/models/account_base.py +++ b/brewman/brewman/models/account_base.py @@ -42,11 +42,11 @@ class AccountBase: type_: Mapped[AccountType] = relationship("AccountType", back_populates="accounts") - journals: Mapped[list["Journal"]] = relationship("Journal", back_populates="account") - cost_centre: Mapped["CostCentre"] = relationship("CostCentre", back_populates="accounts") - products: Mapped[list["ProductVersion"]] = relationship("ProductVersion", back_populates="account") + journals: Mapped[list[Journal]] = relationship("Journal", back_populates="account") + cost_centre: Mapped[CostCentre] = relationship("CostCentre", back_populates="accounts") + products: Mapped[list[ProductVersion]] = relationship("ProductVersion", back_populates="account") - rate_contracts: Mapped[list["RateContract"]] = relationship("RateContract", back_populates="vendor") + rate_contracts: Mapped[list[RateContract]] = relationship("RateContract", back_populates="vendor") def __init__( self, diff --git a/brewman/brewman/models/account_type.py b/brewman/brewman/models/account_type.py index ad1c5490..6396e5d1 100644 --- a/brewman/brewman/models/account_type.py +++ b/brewman/brewman/models/account_type.py @@ -9,7 +9,7 @@ from ..db.base_class import reg if TYPE_CHECKING: # if the target of the relationship is in another module # that cannot normally be imported at runtime - from .account import AccountBase + from .account_base import AccountBase @reg.mapped_as_dataclass(unsafe_hash=True) @@ -24,4 +24,4 @@ class AccountType: order: Mapped[int] = mapped_column(Integer, nullable=False) show_in_list: Mapped[bool] = mapped_column(Boolean, nullable=False) - accounts: Mapped[list["AccountBase"]] = relationship("AccountBase", back_populates="type_") + accounts: Mapped[list[AccountBase]] = relationship("AccountBase", back_populates="type_") diff --git a/brewman/brewman/models/attendance.py b/brewman/brewman/models/attendance.py index 5bf4bfd8..306978e7 100644 --- a/brewman/brewman/models/attendance.py +++ b/brewman/brewman/models/attendance.py @@ -51,8 +51,8 @@ class Attendance: postgresql_where=(is_valid == True), # noqa: E712 ), ) - user: Mapped["User"] = relationship("User") - employee: Mapped["Employee"] = relationship("Employee", back_populates="attendances") + user: Mapped[User] = relationship("User") + employee: Mapped[Employee] = relationship("Employee", back_populates="attendances") def __init__( self, diff --git a/brewman/brewman/models/attendance_type.py b/brewman/brewman/models/attendance_type.py index a1281e3d..9e266194 100644 --- a/brewman/brewman/models/attendance_type.py +++ b/brewman/brewman/models/attendance_type.py @@ -12,7 +12,7 @@ class AttendanceType: self.value = value @classmethod - def list(cls) -> list["AttendanceType"]: + def list(cls) -> list[AttendanceType]: return [ AttendanceType(0, "Not Set", Decimal(0)), AttendanceType(1, "Present", Decimal(1)), @@ -29,9 +29,9 @@ class AttendanceType: ] @classmethod - def by_name(cls, name: str) -> "AttendanceType": + def by_name(cls, name: str) -> AttendanceType: return next(i for i in cls.list() if i.name == name) @classmethod - def by_id(cls, id_: int) -> "AttendanceType": + def by_id(cls, id_: int) -> AttendanceType: return next(i for i in cls.list() if i.id == id_) diff --git a/brewman/brewman/models/cost_centre.py b/brewman/brewman/models/cost_centre.py index c085d6c2..38e6c23d 100644 --- a/brewman/brewman/models/cost_centre.py +++ b/brewman/brewman/models/cost_centre.py @@ -12,7 +12,7 @@ from ..schemas.cost_centre import CostCentreLink if TYPE_CHECKING: # if the target of the relationship is in another module # that cannot normally be imported at runtime - from .account import AccountBase + from .account_base import AccountBase from .journal import Journal @@ -26,8 +26,8 @@ class CostCentre: name: Mapped[str] = mapped_column(Unicode, unique=True, nullable=False) is_fixture: Mapped[bool] = mapped_column(Boolean, nullable=False) - accounts: Mapped[list["AccountBase"]] = relationship("AccountBase", back_populates="cost_centre") - journals: Mapped[list["Journal"]] = relationship("Journal", back_populates="cost_centre") + accounts: Mapped[list[AccountBase]] = relationship("AccountBase", back_populates="cost_centre") + journals: Mapped[list[Journal]] = relationship("Journal", back_populates="cost_centre") @property def __name__(self) -> str: diff --git a/brewman/brewman/models/employee.py b/brewman/brewman/models/employee.py index 2947b7b2..8d21a6b4 100644 --- a/brewman/brewman/models/employee.py +++ b/brewman/brewman/models/employee.py @@ -28,8 +28,8 @@ class Employee(AccountBase): joining_date: Mapped[date] = mapped_column(Date, nullable=False) leaving_date: Mapped[date | None] = mapped_column(Date, nullable=True) - attendances: Mapped[list["Attendance"]] = relationship("Attendance", back_populates="employee") - fingerprints: Mapped[list["Fingerprint"]] = relationship("Fingerprint", back_populates="employee") + attendances: Mapped[list[Attendance]] = relationship("Attendance", back_populates="employee") + fingerprints: Mapped[list[Fingerprint]] = relationship("Fingerprint", back_populates="employee") def __init__( self, @@ -59,7 +59,7 @@ class Employee(AccountBase): cost_centre_id=cost_centre_id, ) - def create(self, db: Session) -> "Employee": + def create(self, db: Session) -> Employee: self.code = db.execute( select(func.coalesce(func.max(AccountBase.code), 0) + 1).filter(AccountBase.type_id == self.type_id) ).scalar_one() diff --git a/brewman/brewman/models/employee_benefit.py b/brewman/brewman/models/employee_benefit.py index a8dfe09b..1da23f20 100644 --- a/brewman/brewman/models/employee_benefit.py +++ b/brewman/brewman/models/employee_benefit.py @@ -27,7 +27,7 @@ class EmployeeBenefit: esi_er: Mapped[int] = mapped_column("esi_employer", Integer, nullable=False) pf_er: Mapped[int] = mapped_column("pf_employer", Integer, nullable=False) - journal: Mapped["Journal"] = relationship("Journal", back_populates="employee_benefit") + journal: Mapped[Journal] = relationship("Journal", back_populates="employee_benefit") def __init__( self, @@ -37,7 +37,7 @@ class EmployeeBenefit: pf_ee: int, esi_er: int, pf_er: int, - journal: "Journal", + journal: Journal, voucher_id: uuid.UUID | None = None, id_: uuid.UUID | None = None, ) -> None: diff --git a/brewman/brewman/models/fingerprint.py b/brewman/brewman/models/fingerprint.py index 147fa6a5..8485aea6 100644 --- a/brewman/brewman/models/fingerprint.py +++ b/brewman/brewman/models/fingerprint.py @@ -24,7 +24,7 @@ class Fingerprint: employee_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("employees.id"), nullable=False) date_: Mapped[datetime] = mapped_column("date", DateTime, nullable=False) - employee: Mapped["Employee"] = relationship("Employee", back_populates="fingerprints") + employee: Mapped[Employee] = relationship("Employee", back_populates="fingerprints") def __init__(self, employee_id: uuid.UUID, date_: datetime, id_: uuid.UUID | None = None) -> None: self.employee_id = employee_id diff --git a/brewman/brewman/models/incentive.py b/brewman/brewman/models/incentive.py index 94e82fed..46a201f1 100644 --- a/brewman/brewman/models/incentive.py +++ b/brewman/brewman/models/incentive.py @@ -24,11 +24,11 @@ class Incentive: days_worked: Mapped[Decimal] = mapped_column(Numeric(precision=5, scale=1), nullable=False) points: Mapped[Decimal] = mapped_column(Numeric(precision=5, scale=2), nullable=False) - journal: Mapped["Journal"] = relationship("Journal", back_populates="incentive") + journal: Mapped[Journal] = relationship("Journal", back_populates="incentive") def __init__( self, - journal: "Journal", + journal: Journal, days_worked: Decimal, points: Decimal, voucher_id: uuid.UUID | None = None, diff --git a/brewman/brewman/models/inventory.py b/brewman/brewman/models/inventory.py index 1b76e009..bc747a56 100644 --- a/brewman/brewman/models/inventory.py +++ b/brewman/brewman/models/inventory.py @@ -42,8 +42,8 @@ class Inventory: tax: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) discount: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) - voucher: Mapped["Voucher"] = relationship("Voucher", back_populates="inventories") - batch: Mapped["Batch"] = relationship("Batch", back_populates="inventories") + voucher: Mapped[Voucher] = relationship("Voucher", back_populates="inventories") + batch: Mapped[Batch] = relationship("Batch", back_populates="inventories") def __init__( self, @@ -51,7 +51,7 @@ class Inventory: rate: Decimal, tax: Decimal, discount: Decimal, - batch: "Batch", + batch: Batch, voucher_id: uuid.UUID | None = None, id_: uuid.UUID | None = None, ) -> None: diff --git a/brewman/brewman/models/journal.py b/brewman/brewman/models/journal.py index e1b39df8..31ff15e8 100644 --- a/brewman/brewman/models/journal.py +++ b/brewman/brewman/models/journal.py @@ -11,7 +11,7 @@ from ..db.base_class import reg if TYPE_CHECKING: - from .account import AccountBase + from .account_base import AccountBase from .cost_centre import CostCentre from .employee_benefit import EmployeeBenefit from .incentive import Incentive @@ -40,11 +40,11 @@ class Journal: nullable=False, ) - voucher: Mapped["Voucher"] = relationship("Voucher", back_populates="journals") - account: Mapped["AccountBase"] = relationship("AccountBase", back_populates="journals") - cost_centre: Mapped["CostCentre"] = relationship("CostCentre", back_populates="journals") - employee_benefit: Mapped["EmployeeBenefit"] = relationship("EmployeeBenefit", back_populates="journal") - incentive: Mapped["Incentive"] = relationship("Incentive", back_populates="journal") + voucher: Mapped[Voucher] = relationship("Voucher", back_populates="journals") + account: Mapped[AccountBase] = relationship("AccountBase", back_populates="journals") + cost_centre: Mapped[CostCentre] = relationship("CostCentre", back_populates="journals") + employee_benefit: Mapped[EmployeeBenefit] = relationship("EmployeeBenefit", back_populates="journal") + incentive: Mapped[Incentive] = relationship("Incentive", back_populates="journal") @hybrid_property def signed_amount(self) -> Decimal: diff --git a/brewman/brewman/models/login_history.py b/brewman/brewman/models/login_history.py index d18495e3..42433bf8 100644 --- a/brewman/brewman/models/login_history.py +++ b/brewman/brewman/models/login_history.py @@ -30,8 +30,8 @@ class LoginHistory: ) date: Mapped[datetime] = mapped_column(DateTime(), nullable=False) - user: Mapped["User"] = relationship("User", back_populates="login_history") - client: Mapped["Client"] = relationship("Client", back_populates="login_history") + user: Mapped[User] = relationship("User", back_populates="login_history") + client: Mapped[Client] = relationship("Client", back_populates="login_history") def __init__( self, diff --git a/brewman/brewman/models/product.py b/brewman/brewman/models/product.py index 27348e2a..69e0b6a6 100644 --- a/brewman/brewman/models/product.py +++ b/brewman/brewman/models/product.py @@ -20,8 +20,8 @@ class Product: Uuid, primary_key=True, insert_default=uuid.uuid4, server_default=text("gen_random_uuid()") ) is_fixture: Mapped[bool] = mapped_column(Boolean, nullable=False) - versions: Mapped[list["ProductVersion"]] = relationship(back_populates="product") - skus: Mapped[list["StockKeepingUnit"]] = relationship("StockKeepingUnit", back_populates="product") + versions: Mapped[list[ProductVersion]] = relationship(back_populates="product") + skus: Mapped[list[StockKeepingUnit]] = relationship("StockKeepingUnit", back_populates="product") def __init__(self, id_: uuid.UUID | None = None, is_fixture: bool | None = None) -> None: if id_ is not None: diff --git a/brewman/brewman/models/product_group.py b/brewman/brewman/models/product_group.py index 116c1771..2cf3e9e6 100644 --- a/brewman/brewman/models/product_group.py +++ b/brewman/brewman/models/product_group.py @@ -24,7 +24,7 @@ class ProductGroup: ice_cream: Mapped[bool] = mapped_column(Boolean, nullable=False) is_fixture: Mapped[bool] = mapped_column(Boolean, nullable=False) - products: Mapped[list["ProductVersion"]] = relationship("ProductVersion", back_populates="product_group") + products: Mapped[list[ProductVersion]] = relationship("ProductVersion", back_populates="product_group") def __init__( self, diff --git a/brewman/brewman/models/product_version.py b/brewman/brewman/models/product_version.py index 003251c1..d0d60a7c 100644 --- a/brewman/brewman/models/product_version.py +++ b/brewman/brewman/models/product_version.py @@ -39,9 +39,9 @@ class ProductVersion: is_purchased: Mapped[bool] = mapped_column(Boolean, nullable=False) is_sold: Mapped[bool] = mapped_column(Boolean, nullable=False) - product: Mapped["Product"] = relationship("Product", back_populates="versions") - product_group: Mapped["ProductGroup"] = relationship("ProductGroup", back_populates="products") - account: Mapped["Account"] = relationship("Account", back_populates="products") + product: Mapped[Product] = relationship("Product", back_populates="versions") + product_group: Mapped[ProductGroup] = relationship("ProductGroup", back_populates="products") + account: Mapped[Account] = relationship("Account", back_populates="products") allergen: Mapped[str] = mapped_column(Text, nullable=False) @@ -66,15 +66,15 @@ class ProductVersion: postgresql.ExcludeConstraint( (name, "="), (func.daterange(valid_from, valid_till, text("'[]'")), "&&"), - ), # type: ignore[no-untyped-call] + ), postgresql.ExcludeConstraint( (handle, "="), (func.daterange(valid_from, valid_till, text("'[]'")), "&&"), - ), # type: ignore[no-untyped-call] + ), postgresql.ExcludeConstraint( (product_id, "="), (func.daterange(valid_from, valid_till, text("'[]'")), "&&"), - ), # type: ignore[no-untyped-call] + ), ) def __init__( diff --git a/brewman/brewman/models/rate_contract.py b/brewman/brewman/models/rate_contract.py index 7a88ead6..7598b6a1 100644 --- a/brewman/brewman/models/rate_contract.py +++ b/brewman/brewman/models/rate_contract.py @@ -32,9 +32,9 @@ class RateContract: creation_date: Mapped[datetime] = mapped_column(DateTime(), nullable=False) last_edit_date: Mapped[datetime] = mapped_column(DateTime(), nullable=False) - user: Mapped["User"] = relationship("User") - vendor: Mapped["AccountBase"] = relationship("AccountBase", back_populates="rate_contracts") - items: Mapped[list["RateContractItem"]] = relationship("RateContractItem", back_populates="rate_contract") + user: Mapped[User] = relationship("User") + vendor: Mapped[AccountBase] = relationship("AccountBase", back_populates="rate_contracts") + items: Mapped[list[RateContractItem]] = relationship("RateContractItem", back_populates="rate_contract") def __init__( self, diff --git a/brewman/brewman/models/rate_contract_item.py b/brewman/brewman/models/rate_contract_item.py index 9d22ba8b..fbfed309 100644 --- a/brewman/brewman/models/rate_contract_item.py +++ b/brewman/brewman/models/rate_contract_item.py @@ -31,8 +31,8 @@ class RateContractItem: sku_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("stock_keeping_units.id"), nullable=False) price: Mapped[Decimal] = mapped_column("cost_price", Numeric(precision=15, scale=2), nullable=False) - rate_contract: Mapped["RateContract"] = relationship("RateContract", back_populates="items") - sku: Mapped["StockKeepingUnit"] = relationship("StockKeepingUnit") + rate_contract: Mapped[RateContract] = relationship("RateContract", back_populates="items") + sku: Mapped[StockKeepingUnit] = relationship("StockKeepingUnit") def __init__( self, diff --git a/brewman/brewman/models/role.py b/brewman/brewman/models/role.py index e5673728..2f439426 100644 --- a/brewman/brewman/models/role.py +++ b/brewman/brewman/models/role.py @@ -22,7 +22,7 @@ class Role: ) name: Mapped[str] = mapped_column(Unicode, unique=True) - permissions: Mapped[list["Permission"]] = relationship( + permissions: Mapped[list[Permission]] = relationship( "Permission", secondary=RolePermission.__table__, # type: ignore[attr-defined] back_populates="roles", diff --git a/brewman/brewman/models/sku_version.py b/brewman/brewman/models/sku_version.py index 6e67061c..26c56071 100644 --- a/brewman/brewman/models/sku_version.py +++ b/brewman/brewman/models/sku_version.py @@ -46,7 +46,7 @@ class SkuVersion: postgresql.ExcludeConstraint( (sku_id, "="), (func.daterange(valid_from, valid_till, text("'[]'")), "&&"), - ), # type: ignore[no-untyped-call] + ), ) def __init__( diff --git a/brewman/brewman/models/tag.py b/brewman/brewman/models/tag.py index ac8df5f9..6504c7af 100644 --- a/brewman/brewman/models/tag.py +++ b/brewman/brewman/models/tag.py @@ -22,7 +22,7 @@ class Tag: ) name: Mapped[str] = mapped_column(Unicode, unique=True, nullable=False) - recipes: Mapped[list["Recipe"]] = relationship( + recipes: Mapped[list[Recipe]] = relationship( "Recipe", secondary=RecipeTag.__table__, # type: ignore[attr-defined] order_by="Recipe.date_", diff --git a/brewman/brewman/models/voucher.py b/brewman/brewman/models/voucher.py index ee1ad557..511f1d56 100644 --- a/brewman/brewman/models/voucher.py +++ b/brewman/brewman/models/voucher.py @@ -1,7 +1,7 @@ import uuid from datetime import UTC, date, datetime -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from sqlalchemy import Boolean, Date, DateTime, Enum, ForeignKey, Unicode, Uuid, text from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -39,23 +39,19 @@ class Voucher: posted: Mapped[bool] = mapped_column("is_posted", Boolean, nullable=False) poster_id: Mapped[uuid.UUID | None] = mapped_column(Uuid, ForeignKey("users.id"), nullable=True) - user: Mapped["User"] = relationship("User", foreign_keys=[user_id], back_populates="vouchers") - poster: Mapped[Optional["User"]] = relationship("User", foreign_keys=[poster_id]) + user: Mapped[User] = relationship("User", foreign_keys=[user_id], back_populates="vouchers") + poster: Mapped[User | None] = relationship("User", foreign_keys=[poster_id]) - journals: Mapped[list["Journal"]] = relationship( - "Journal", cascade="delete, delete-orphan", back_populates="voucher" - ) - inventories: Mapped[list["Inventory"]] = relationship( + journals: Mapped[list[Journal]] = relationship("Journal", cascade="delete, delete-orphan", back_populates="voucher") + inventories: Mapped[list[Inventory]] = relationship( "Inventory", cascade="delete, delete-orphan", back_populates="voucher" ) - employee_benefits: Mapped[list["EmployeeBenefit"]] = relationship( + employee_benefits: Mapped[list[EmployeeBenefit]] = relationship( "EmployeeBenefit", cascade="delete, delete-orphan", backref="voucher" ) - incentives: Mapped[list["Incentive"]] = relationship( - "Incentive", cascade="delete, delete-orphan", backref="voucher" - ) + incentives: Mapped[list[Incentive]] = relationship("Incentive", cascade="delete, delete-orphan", backref="voucher") - tags: Mapped[list["Tag"]] = relationship( + tags: Mapped[list[Tag]] = relationship( "Tag", secondary=VoucherTag.__table__, # type: ignore[attr-defined] order_by="Tag.name", diff --git a/brewman/brewman/routers/__init__.py b/brewman/brewman/routers/__init__.py index 03841d11..a6e14b0c 100644 --- a/brewman/brewman/routers/__init__.py +++ b/brewman/brewman/routers/__init__.py @@ -1,14 +1,18 @@ from collections.abc import Sequence from datetime import UTC, date, datetime, timedelta +from typing import Any from fastapi import Request -from sqlalchemy import or_, select +from sqlalchemy import ColumnElement, Label, and_, or_, select from sqlalchemy.orm import Session -from brewman.core.session import get_date - +from ..core.session import get_date from ..models.db_setting import DbSetting +from ..models.product import Product +from ..models.product_version import ProductVersion from ..models.setting_type import SettingType +from ..models.sku_version import SkuVersion +from ..models.stock_keeping_unit import StockKeepingUnit from ..models.voucher_type import VoucherType from ..schemas.settings import ( AccountTypesSelected, @@ -93,3 +97,39 @@ def session_or_query_date(request: Request, d: str | None = None) -> date: return datetime.strptime(d, "%d-%b-%Y").date() session_date_str = get_date(request.session) return datetime.strptime(session_date_str, "%d-%b-%Y").date() + + +def _pv_active(date_: date | Label[Any]) -> ColumnElement[bool]: + return and_( + or_(ProductVersion.valid_from == None, ProductVersion.valid_from <= date_), # noqa: E711 + or_(ProductVersion.valid_till == None, ProductVersion.valid_till >= date_), # noqa: E711 + ) + + +def _sv_active(date_: date | Label[Any]) -> ColumnElement[bool]: + return and_( + or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= date_), # noqa: E711 + or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= date_), # noqa: E711 + ) + + +def _pv_onclause(date_: date | Label[Any]) -> ColumnElement[bool]: + return and_( + ProductVersion.product_id == Product.id, + or_( + ProductVersion.valid_from == None, # noqa: E711 + ProductVersion.valid_from <= date_, + ), + or_( + ProductVersion.valid_till == None, # noqa: E711 + ProductVersion.valid_till >= date_, + ), + ) + + +def _sv_onclause(date_: date | Label[Any]) -> ColumnElement[bool]: + return and_( + SkuVersion.sku_id == StockKeepingUnit.id, + or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= date_), # noqa: E711 + or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= date_), # noqa: E711 + ) diff --git a/brewman/brewman/routers/account.py b/brewman/brewman/routers/account.py index cde9d6ad..f967eb6c 100644 --- a/brewman/brewman/routers/account.py +++ b/brewman/brewman/routers/account.py @@ -18,7 +18,9 @@ from ..models.journal import Journal from ..models.voucher import Voucher from ..models.voucher_type import VoucherType from ..schemas import account as schemas +from ..schemas.account import AccountLink from ..schemas.balance import AccountBalance +from ..schemas.cost_centre import CostCentreLink from ..schemas.user import UserToken @@ -121,7 +123,7 @@ async def show_list(user: UserToken = Depends(get_user)) -> list[schemas.Account ] -@router.get("/query", response_model=list[schemas.AccountLink]) +@router.get("/query", response_model=list[AccountLink]) async def show_term( q: str, t: int | None = None, # AccountType @@ -129,8 +131,8 @@ async def show_term( a: bool | None = None, # Active c: int | None = None, # Count current_user: UserToken = Depends(get_user), -) -> list[schemas.AccountLink]: - list_: list[schemas.AccountLink] = [] +) -> list[AccountLink]: + list_: list[AccountLink] = [] with SessionFuture() as db: query_ = select(AccountBase) if t is not None: @@ -148,7 +150,7 @@ async def show_term( data: list[AccountBase] = list(db.execute(query_).scalars().all()) for item in data: - list_.append(schemas.AccountLink(id_=item.id, name=item.name)) + list_.append(AccountLink(id_=item.id, name=item.name)) return list_ @@ -197,7 +199,7 @@ def account_info(item: Account) -> schemas.Account: is_reconcilable=item.is_reconcilable, is_starred=item.is_starred, is_fixture=item.is_fixture, - cost_centre=schemas.CostCentreLink( + cost_centre=CostCentreLink( id_=item.cost_centre_id, name=item.cost_centre.name, ), diff --git a/brewman/brewman/routers/attendance.py b/brewman/brewman/routers/attendance.py index 4dd4edea..62db38c0 100644 --- a/brewman/brewman/routers/attendance.py +++ b/brewman/brewman/routers/attendance.py @@ -13,6 +13,7 @@ from ..models.attendance import Attendance from ..models.employee import Employee from ..routers.fingerprint import get_prints from ..schemas import attendance as schemas +from ..schemas.attendance_type import AttendanceType as AttendanceTypeSchema from ..schemas.user import UserToken @@ -81,7 +82,7 @@ def attendance_date_report(date_: date, db: Session) -> list[schemas.AttendanceI name=item.name, designation=item.designation, department=item.cost_centre.name, - attendance_type=schemas.AttendanceType(id_=at_type), + attendance_type=AttendanceTypeSchema(id_=at_type), prints=prints, hours_worked=hours_worked, full_day=full_day, diff --git a/brewman/brewman/routers/batch.py b/brewman/brewman/routers/batch.py index 54a59339..f19eea60 100644 --- a/brewman/brewman/routers/batch.py +++ b/brewman/brewman/routers/batch.py @@ -12,6 +12,7 @@ from ..models.product_version import ProductVersion from ..models.sku_version import SkuVersion from ..models.stock_keeping_unit import StockKeepingUnit from ..schemas import batch as schemas +from ..schemas.product import ProductLink from ..schemas.user import UserToken @@ -81,7 +82,7 @@ def batch_term( rate=round(item.rate, 2), tax=round(item.tax, 5), discount=round(item.discount, 5), - sku=schemas.ProductLink( + sku=ProductLink( id_=item.sku.id, name=f"{product_name} ({product_units})", ), diff --git a/brewman/brewman/routers/calculate_nutrition.py b/brewman/brewman/routers/calculate_nutrition.py index 31c2a592..534d3935 100644 --- a/brewman/brewman/routers/calculate_nutrition.py +++ b/brewman/brewman/routers/calculate_nutrition.py @@ -1,5 +1,7 @@ import uuid +from datetime import date + from fastapi import HTTPException, status from sqlalchemy import distinct, or_, select, update from sqlalchemy.exc import SQLAlchemyError @@ -7,13 +9,16 @@ from sqlalchemy.orm import Session from ..models.product import Product from ..models.product_group import ProductGroup +from ..models.product_version import ProductVersion from ..models.recipe import Recipe from ..models.recipe_item import RecipeItem +from ..models.sku_version import SkuVersion from ..models.stock_keeping_unit import StockKeepingUnit from ..schemas.nutritional_information import NutritionalInformation +from . import _pv_active, _pv_onclause, _sv_onclause -def calculate_nutrition(db: Session) -> None: +def calculate_nutrition(date_: date, db: Session) -> None: try: # Get all recipes that have nutritional values products = set( @@ -21,7 +26,8 @@ def calculate_nutrition(db: Session) -> None: select(distinct(StockKeepingUnit.product_id)) .join(StockKeepingUnit.recipes) .join(StockKeepingUnit.product) - .join(Product.product_group) + .join(ProductVersion, onclause=_pv_onclause(date_)) + .join(ProductVersion.product_group) .where(or_(ProductGroup.nutritional == True, ProductGroup.ice_cream == True)) # noqa: E712 ) .scalars() @@ -29,7 +35,7 @@ def calculate_nutrition(db: Session) -> None: ) db.flush() while len(products) > 0: - calculate(products, db) + calculate(date_, products, db) except SQLAlchemyError as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -37,29 +43,55 @@ def calculate_nutrition(db: Session) -> None: ) -def calculate(products: set[uuid.UUID], db: Session) -> None: +def calculate(date_: date, products: set[uuid.UUID], db: Session) -> None: sq = select(RecipeItem.recipe_id).where(RecipeItem.product_id.in_(products)) recipes = ( db.execute( - select(Recipe).join(Recipe.sku).where(StockKeepingUnit.product_id.in_(products), Recipe.id.notin_(sq)) + select(Recipe) + .join(Recipe.sku) + .join(SkuVersion, onclause=_sv_onclause(date_)) + .join(StockKeepingUnit.product) + .join(ProductVersion, onclause=_pv_onclause(date_)) + .where(StockKeepingUnit.product_id.in_(products), Recipe.id.notin_(sq)) ) .scalars() .all() ) for recipe in recipes: - protein = sum(i.quantity * i.product.protein for i in recipe.items) / recipe.sku.fraction - carbohydrate = sum(i.quantity * i.product.carbohydrate for i in recipe.items) / recipe.sku.fraction - total_sugar = sum(i.quantity * i.product.total_sugar for i in recipe.items) / recipe.sku.fraction - added_sugar = sum(i.quantity * i.product.added_sugar for i in recipe.items) / recipe.sku.fraction - total_fat = sum(i.quantity * i.product.total_fat for i in recipe.items) / recipe.sku.fraction - saturated_fat = sum(i.quantity * i.product.saturated_fat for i in recipe.items) / recipe.sku.fraction - trans_fat = sum(i.quantity * i.product.trans_fat for i in recipe.items) / recipe.sku.fraction - cholestrol = sum(i.quantity * i.product.cholestrol for i in recipe.items) / recipe.sku.fraction - sodium = sum(i.quantity * i.product.sodium for i in recipe.items) / recipe.sku.fraction - msnf = sum(i.quantity * i.product.msnf for i in recipe.items) / recipe.sku.fraction - other_solids = sum(i.quantity * i.product.other_solids for i in recipe.items) / recipe.sku.fraction - total_solids = sum(i.quantity * i.product.total_solids for i in recipe.items) / recipe.sku.fraction - water = sum(i.quantity * i.product.water for i in recipe.items) / recipe.sku.fraction + protein = ( + sum(i.quantity * i.product.versions[0].protein for i in recipe.items) / recipe.sku.versions[0].fraction + ) + carbohydrate = ( + sum(i.quantity * i.product.versions[0].carbohydrate for i in recipe.items) / recipe.sku.versions[0].fraction + ) + total_sugar = ( + sum(i.quantity * i.product.versions[0].total_sugar for i in recipe.items) / recipe.sku.versions[0].fraction + ) + added_sugar = ( + sum(i.quantity * i.product.versions[0].added_sugar for i in recipe.items) / recipe.sku.versions[0].fraction + ) + total_fat = ( + sum(i.quantity * i.product.versions[0].total_fat for i in recipe.items) / recipe.sku.versions[0].fraction + ) + saturated_fat = ( + sum(i.quantity * i.product.versions[0].saturated_fat for i in recipe.items) + / recipe.sku.versions[0].fraction + ) + trans_fat = ( + sum(i.quantity * i.product.versions[0].trans_fat for i in recipe.items) / recipe.sku.versions[0].fraction + ) + cholestrol = ( + sum(i.quantity * i.product.versions[0].cholestrol for i in recipe.items) / recipe.sku.versions[0].fraction + ) + sodium = sum(i.quantity * i.product.versions[0].sodium for i in recipe.items) / recipe.sku.versions[0].fraction + msnf = sum(i.quantity * i.product.versions[0].msnf for i in recipe.items) / recipe.sku.versions[0].fraction + other_solids = ( + sum(i.quantity * i.product.versions[0].other_solids for i in recipe.items) / recipe.sku.versions[0].fraction + ) + total_solids = ( + sum(i.quantity * i.product.versions[0].total_solids for i in recipe.items) / recipe.sku.versions[0].fraction + ) + water = sum(i.quantity * i.product.versions[0].water for i in recipe.items) / recipe.sku.versions[0].fraction db.execute( update(Product) @@ -85,7 +117,7 @@ def calculate(products: set[uuid.UUID], db: Session) -> None: db.flush() -def report_nutrition(db: Session) -> list[NutritionalInformation]: +def report_nutrition(date_: date, db: Session) -> list[NutritionalInformation]: try: # Get all recipes that have nutritional values products = set( @@ -93,13 +125,14 @@ def report_nutrition(db: Session) -> list[NutritionalInformation]: select(distinct(StockKeepingUnit.product_id)) .join(StockKeepingUnit.recipes) .join(StockKeepingUnit.product) - .join(Product.product_group) + .join(ProductVersion, onclause=_pv_onclause(date_)) + .join(ProductVersion.product_group) ) .scalars() .all() ) final, semi, ingredients = nut_final(db), nut_semi(db), nut_ingredients(db) - return report(products, final, semi, ingredients, db) + return report(date_, products, final, semi, ingredients, db) except SQLAlchemyError as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -108,12 +141,21 @@ def report_nutrition(db: Session) -> list[NutritionalInformation]: def report( - products: set[uuid.UUID], final: set[uuid.UUID], semi: set[uuid.UUID], ingredients: set[uuid.UUID], db: Session + date_: date, + products: set[uuid.UUID], + final: set[uuid.UUID], + semi: set[uuid.UUID], + ingredients: set[uuid.UUID], + db: Session, ) -> list[NutritionalInformation]: _list: list[NutritionalInformation] = [] ingredient_dict: dict[str, set[str]] = {} allergen_dict: dict[str, set[str]] = {} - ings = db.execute(select(Product).where(Product.id.in_(ingredients))).scalars().all() + ings = ( + db.execute(select(ProductVersion).where(ProductVersion.product_id.in_(ingredients), _pv_active(date_))) + .scalars() + .all() + ) for ingredient in ings: if ingredient.id in products: raise ValueError("Ingredient cannot be in a recipe") @@ -145,17 +187,25 @@ def report( ) while len(semi) > 0: semi_products = ( - db.execute(select(Recipe).join(Recipe.sku).where(StockKeepingUnit.product_id.in_(semi))).scalars().all() + db.execute( + select(Recipe) + .join(Recipe.sku) + .join(StockKeepingUnit.product) + .join(ProductVersion, onclause=_pv_onclause(date_)) + .where(StockKeepingUnit.product_id.in_(semi)) + ) + .scalars() + .all() ) for semi_recipe in semi_products: - semi_product = semi_recipe.sku.product + semi_product = semi_recipe.sku.product.versions[0] products.remove(semi_product.id) semi.remove(semi_product.id) ingredient_dict[semi_product.name] = set.union( - *[ingredient_dict[i.product.name] for i in semi_recipe.items] + *[ingredient_dict[i.product.versions[0].name] for i in semi_recipe.items] ) allergen_dict[semi_product.name] = set.union( - *[allergen_dict[i.product.name] for i in semi_recipe.items] + *[allergen_dict[i.product.versions[0].name] for i in semi_recipe.items] ) - set([""]) _list.append( @@ -183,13 +233,15 @@ def report( ) recipes = db.execute(select(Recipe).join(Recipe.sku).where(StockKeepingUnit.product_id.in_(final))).scalars().all() for recipe in recipes: - recipe_product = recipe.sku.product + recipe_product = recipe.sku.product.versions[0] products.remove(recipe_product.id) final.remove(recipe_product.id) - ingredient_dict[recipe_product.name] = set.union(*[ingredient_dict[i.product.name] for i in recipe.items]) - allergen_dict[recipe_product.name] = set.union(*[allergen_dict[i.product.name] for i in recipe.items]) - set( - [""] + ingredient_dict[recipe_product.name] = set.union( + *[ingredient_dict[i.product.versions[0].name] for i in recipe.items] ) + allergen_dict[recipe_product.name] = set.union( + *[allergen_dict[i.product.versions[0].name] for i in recipe.items] + ) - set([""]) _list.append( NutritionalInformation( name=recipe_product.name, diff --git a/brewman/brewman/routers/employee.py b/brewman/brewman/routers/employee.py index f3f5e689..4a33828f 100644 --- a/brewman/brewman/routers/employee.py +++ b/brewman/brewman/routers/employee.py @@ -18,6 +18,7 @@ from ..models.journal import Journal from ..models.voucher import Voucher from ..models.voucher_type import VoucherType from ..schemas import employee as schemas +from ..schemas.cost_centre import CostCentreLink from ..schemas.user import UserToken @@ -151,7 +152,7 @@ async def show_term( id_=item.id, name=item.name, designation=item.designation, - cost_centre=schemas.CostCentreLink( + cost_centre=CostCentreLink( id_=item.cost_centre.id, name=item.cost_centre.name, ), @@ -182,7 +183,7 @@ def employee_info(employee: Employee) -> schemas.Employee: points=employee.points, joining_date=employee.joining_date, leaving_date=None if employee.is_active else employee.leaving_date, - cost_centre=schemas.CostCentreLink( + cost_centre=CostCentreLink( id_=employee.cost_centre_id, name=employee.cost_centre.name, ), diff --git a/brewman/brewman/routers/employee_attendance.py b/brewman/brewman/routers/employee_attendance.py index c397175c..ebef0e4a 100644 --- a/brewman/brewman/routers/employee_attendance.py +++ b/brewman/brewman/routers/employee_attendance.py @@ -13,6 +13,8 @@ from ..models.attendance import Attendance from ..models.employee import Employee from ..routers.fingerprint import get_prints from ..schemas import employee_attendance as schemas +from ..schemas.account import AccountLink +from ..schemas.attendance_type import AttendanceType as AttendanceTypeSchema from ..schemas.user import UserToken from .attendance import date_range @@ -47,12 +49,14 @@ def employee_attendance_report( finish_date = datetime.strptime(f or get_finish_date(request.session), "%d-%b-%Y").date() start_date = employee.joining_date if employee.joining_date > start_date else start_date finish_date = ( - employee.leaving_date if not employee.is_active and employee.leaving_date < finish_date else finish_date # type: ignore[assignment] + employee.leaving_date + if not employee.is_active and employee.leaving_date and employee.leaving_date < finish_date + else finish_date ) info = schemas.EmployeeAttendance( start_date=start_date, finish_date=finish_date, - employee=schemas.AccountLink(id_=employee.id, name=employee.name), + employee=AccountLink(id_=employee.id, name=employee.name), body=employee_attendance(employee, start_date, finish_date, db), ) return info @@ -80,7 +84,7 @@ def employee_attendance( list_.append( schemas.EmployeeAttendanceItem( date_=item.strftime("%d-%b-%Y"), # type: ignore - attendance_type=schemas.AttendanceType(id_=at_type), + attendance_type=AttendanceTypeSchema(id_=at_type), prints=prints, hours_worked=hours_worked, full_day=full_day, @@ -113,6 +117,6 @@ def save_employee_attendance( return schemas.EmployeeAttendance( start_date=start_date, finish_date=finish_date, - employee=schemas.AccountLink(id_=employee.id, name=employee.name), + employee=AccountLink(id_=employee.id, name=employee.name), body=employee_attendance(employee, start_date, finish_date, db), ) diff --git a/brewman/brewman/routers/issue.py b/brewman/brewman/routers/issue.py index a076ff67..1627a5ac 100644 --- a/brewman/brewman/routers/issue.py +++ b/brewman/brewman/routers/issue.py @@ -196,8 +196,8 @@ def save_inventories( def save_journals( voucher: Voucher, - source: schema_in.CostCentreLink, - destination: schema_in.CostCentreLink, + source: CostCentreLink, + destination: CostCentreLink, amount: Decimal, db: Session, ) -> None: @@ -418,8 +418,8 @@ def update_inventories( def update_journals( voucher: Voucher, - source: schema_in.CostCentreLink, - destination: schema_in.CostCentreLink, + source: CostCentreLink, + destination: CostCentreLink, amount: Decimal, ) -> None: for i in range(len(voucher.journals), 0, -1): diff --git a/brewman/brewman/routers/journal.py b/brewman/brewman/routers/journal.py index cdcf9e97..6216aa12 100644 --- a/brewman/brewman/routers/journal.py +++ b/brewman/brewman/routers/journal.py @@ -202,7 +202,7 @@ def show_blank( else: type_ = VoucherType.JOURNAL - additional_info = BlankVoucherInfo(date_=date_, type_=type_) # type: ignore[arg-type] + additional_info = BlankVoucherInfo(date_=date_, type_=type_) if a: additional_info.account = AccountLink(id_=a) with SessionFuture() as db: diff --git a/brewman/brewman/routers/product.py b/brewman/brewman/routers/product.py index da702341..c5447bae 100644 --- a/brewman/brewman/routers/product.py +++ b/brewman/brewman/routers/product.py @@ -20,7 +20,9 @@ from ..models.rate_contract_item import RateContractItem from ..models.sku_version import SkuVersion from ..models.stock_keeping_unit import StockKeepingUnit from ..schemas import product as schemas +from ..schemas.product_group import ProductGroupLink from ..schemas.product_sku import ProductSku +from ..schemas.stock_keeping_unit import StockKeepingUnit as SkuSchema from ..schemas.user import UserToken from . import effective_date @@ -575,7 +577,7 @@ def product_info(version: ProductVersion, skus: Sequence[SkuVersion]) -> schemas description=version.description, fraction_units=version.fraction_units, skus=[ - schemas.StockKeepingUnit( + SkuSchema( id_=sku.id, units=sku.units, fraction=sku.fraction, @@ -589,7 +591,7 @@ def product_info(version: ProductVersion, skus: Sequence[SkuVersion]) -> schemas is_purchased=version.is_purchased, is_sold=version.is_sold, is_active=True, - product_group=schemas.ProductGroupLink(id_=version.product_group.id, name=version.product_group.name), + product_group=ProductGroupLink(id_=version.product_group.id, name=version.product_group.name), allergen=version.allergen, protein=version.protein, carbohydrate=version.carbohydrate, diff --git a/brewman/brewman/routers/purchase.py b/brewman/brewman/routers/purchase.py index 6dfaacab..dcc17f9e 100644 --- a/brewman/brewman/routers/purchase.py +++ b/brewman/brewman/routers/purchase.py @@ -26,6 +26,7 @@ from ..models.voucher import Voucher from ..models.voucher_type import VoucherType from ..schemas import input as schema_in from ..schemas import voucher as output +from ..schemas.account import AccountLink from ..schemas.blank_voucher_info import BlankVoucherInfo from ..schemas.inventory import Inventory as InventorySchema from ..schemas.user import UserToken @@ -170,7 +171,7 @@ def save_inventories(voucher: Voucher, vendor_id: uuid.UUID, inventories: list[I db.add(inventory) -def save_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session) -> None: +def save_journals(voucher: Voucher, ven: AccountLink, db: Session) -> None: vendor = db.execute(select(AccountBase).where(AccountBase.id == ven.id_)).scalar_one() journals: dict[uuid.UUID, Journal] = {} amount = Decimal(0) @@ -381,7 +382,7 @@ def update_inventory(voucher: Voucher, vendor_id: uuid.UUID, inventories: list[I save_inventories(voucher, vendor_id, inventories, db) -def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session) -> None: +def update_journals(voucher: Voucher, ven: AccountLink, db: Session) -> None: vendor = db.execute(select(AccountBase).where(AccountBase.id == ven.id_)).scalar_one() journals: dict[uuid.UUID, Journal] = {} amount = Decimal(0) diff --git a/brewman/brewman/routers/purchase_return.py b/brewman/brewman/routers/purchase_return.py index c79a0b0e..724c74db 100644 --- a/brewman/brewman/routers/purchase_return.py +++ b/brewman/brewman/routers/purchase_return.py @@ -24,6 +24,7 @@ from ..models.voucher import Voucher from ..models.voucher_type import VoucherType from ..schemas import input as schema_in from ..schemas import voucher as output +from ..schemas.account import AccountLink from ..schemas.blank_voucher_info import BlankVoucherInfo from ..schemas.inventory import Inventory as InventorySchema from ..schemas.user import UserToken @@ -178,7 +179,7 @@ def save_inventories(voucher: Voucher, inventories: list[InventorySchema], db: S db.add(item) -def save_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session) -> None: +def save_journals(voucher: Voucher, ven: AccountLink, db: Session) -> None: vendor = db.execute(select(AccountBase).where(AccountBase.id == ven.id_)).scalar_one() journals: dict[uuid.UUID, Journal] = {} amount = Decimal(0) @@ -368,7 +369,7 @@ def update_inventory(voucher: Voucher, new_inventories: list[InventorySchema], d save_inventories(voucher, new_inventories, db) -def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session) -> None: +def update_journals(voucher: Voucher, ven: AccountLink, db: Session) -> None: vendor = db.execute(select(AccountBase).where(AccountBase.id == ven.id_)).scalar_one() journals: dict[uuid.UUID, Journal] = {} amount = Decimal(0) diff --git a/brewman/brewman/routers/rate_contract.py b/brewman/brewman/routers/rate_contract.py index c5dab028..1c6acdfc 100644 --- a/brewman/brewman/routers/rate_contract.py +++ b/brewman/brewman/routers/rate_contract.py @@ -22,10 +22,11 @@ from ..models.rate_contract import RateContract from ..models.rate_contract_item import RateContractItem from ..models.sku_version import SkuVersion from ..models.stock_keeping_unit import StockKeepingUnit +from ..schemas.account import AccountLink from ..schemas.product import ProductLink -from ..schemas.rate_contract import AccountLink, RateContractBlank, RateContractIn from ..schemas.rate_contract import RateContract as RateContractSchema -from ..schemas.rate_contract import RateContractItem as RateContractItemSchema +from ..schemas.rate_contract import RateContractBlank, RateContractIn +from ..schemas.rate_contract_item import RateContractItem as RateContractItemSchema from ..schemas.user import UserToken from ..schemas.user_link import UserLink diff --git a/brewman/brewman/routers/recipe.py b/brewman/brewman/routers/recipe.py index e689a6b7..682b982a 100644 --- a/brewman/brewman/routers/recipe.py +++ b/brewman/brewman/routers/recipe.py @@ -1,5 +1,5 @@ -import subprocess -import tempfile +# import subprocess +# import tempfile import uuid from collections import defaultdict @@ -30,6 +30,7 @@ from ..routers.calculate_prices import calculate_prices from ..schemas import recipe as schemas from ..schemas import recipe_item as rischemas from ..schemas.nutritional_information import NutritionalInformation +from ..schemas.product import ProductLink from ..schemas.user import UserToken @@ -263,7 +264,7 @@ async def show_list( return [ schemas.Recipe( id_=item.id, - sku=schemas.ProductLink(id_=item.sku.id, name=item.sku.product.versions[0].name), + sku=ProductLink(id_=item.sku.id, name=item.sku.product.versions[0].name), date_=item.date_, source=item.source, instructions=item.instructions, @@ -407,19 +408,22 @@ def get_report( def excel(prices: list[tuple[str, str, Decimal]], recipes: list[Recipe]) -> BytesIO: wb = Workbook() - wb.active.title = "Rate List" - wb.active.cell(row=1, column=1, value="Name") - wb.active.cell(row=1, column=2, value="Units") - wb.active.cell(row=1, column=3, value="Rate") + ws = wb.active + assert ws is not None + + ws.title = "Rate List" + ws.cell(row=1, column=1, value="Name") + ws.cell(row=1, column=2, value="Units") + ws.cell(row=1, column=3, value="Rate") for i, p in enumerate(prices, start=2): - wb.active.cell(row=i, column=1, value=p[0]) - wb.active.cell(row=i, column=2, value=p[1]) - wb.active.cell(row=i, column=3, value=p[2]) + ws.cell(row=i, column=1, value=p[0]) + ws.cell(row=i, column=2, value=p[1]) + ws.cell(row=i, column=3, value=p[2]) pgs = set([x.sku.product.versions[0].product_group.name for x in recipes]) for pg in pgs: wb.create_sheet(pg) - rows = defaultdict(lambda: 1) + rows: defaultdict[str, int] = defaultdict(lambda: 1) register_styles(wb) for recipe in recipes: ws = wb[recipe.sku.product.versions[0].product_group.name] @@ -457,12 +461,13 @@ def excel(prices: list[tuple[str, str, Decimal]], recipes: list[Recipe]) -> Byte def get_nutrition( p: uuid.UUID | None = None, ) -> StreamingResponse: + date_ = date.today() with SessionFuture() as db: - calculate_nutrition(db) + calculate_nutrition(date_, db) db.commit() list_: list[NutritionalInformation] = [] with SessionFuture() as db: - list_ = report_nutrition(db) + list_ = report_nutrition(date_, db) e = nut(sorted(list_, key=lambda r: r.name)) e.seek(0) @@ -472,12 +477,14 @@ def get_nutrition( def nut(products: list[NutritionalInformation]) -> BytesIO: wb = Workbook() - wb.active.title = "Ingredients" + ws = wb.active + assert ws is not None + ws.title = "Ingredients" pgs = set([x.product_group for x in products]) pgs.remove("Ingredients") for pg in pgs: wb.create_sheet(pg) - rows = defaultdict(lambda: 1) + rows: defaultdict[str, int] = defaultdict(lambda: 1) register_styles(wb) for item in products: ws = wb[item.product_group] @@ -564,7 +571,7 @@ def show_id( def recipe_info(recipe: Recipe) -> schemas.Recipe: return schemas.Recipe( id_=recipe.id, - sku=schemas.ProductLink( + sku=ProductLink( id_=recipe.sku_id, name=f"{recipe.sku.product.versions[0].name} ({recipe.sku.versions[0].units})", ), @@ -578,7 +585,7 @@ def recipe_info(recipe: Recipe) -> schemas.Recipe: items=[ rischemas.RecipeItem( id_=item.id, - product=schemas.ProductLink( + product=ProductLink( id_=item.product.id, name=item.product.versions[0].name, fraction_units=item.product.versions[0].fraction_units, diff --git a/brewman/brewman/routers/recipe_template.py b/brewman/brewman/routers/recipe_template.py index 092b8bfa..3e5931fd 100644 --- a/brewman/brewman/routers/recipe_template.py +++ b/brewman/brewman/routers/recipe_template.py @@ -49,7 +49,7 @@ def update_route( if data.selected and not item.selected: db.execute(update(RecipeTemplate).where(RecipeTemplate.id != id_).values(selected=False)) item.name = data.name - item.text = data.text + item.text_ = data.text item.selected = data.selected item.date_ = date_ db.commit() @@ -92,7 +92,7 @@ async def show_list( with SessionFuture() as db: list_ = db.execute(select(RecipeTemplate).order_by(RecipeTemplate.name)).scalars().all() return [ - schemas.RecipeTemplate(id_=i.id, name=i.name, date_=i.date_, text=i.text, selected=i.selected) + schemas.RecipeTemplate(id_=i.id, name=i.name, date_=i.date_, text=i.text_, selected=i.selected) for i in list_ ] @@ -105,5 +105,5 @@ def show_id( with SessionFuture() as db: item = db.execute(select(RecipeTemplate).where(RecipeTemplate.id == id_)).scalar_one() return schemas.RecipeTemplate( - id_=item.id, name=item.name, date_=item.date_, text=item.text, selected=item.selected + id_=item.id, name=item.name, date_=item.date_, text=item.text_, selected=item.selected ) diff --git a/brewman/brewman/routers/reports/cash_flow.py b/brewman/brewman/routers/reports/cash_flow.py index 70fd1a85..fafc2cf3 100644 --- a/brewman/brewman/routers/reports/cash_flow.py +++ b/brewman/brewman/routers/reports/cash_flow.py @@ -2,7 +2,6 @@ from datetime import date, datetime from decimal import Decimal from fastapi import APIRouter, Request, Security -from sqlalchemy import not_ from sqlalchemy.orm import Session from sqlalchemy.sql.expression import desc, func, select diff --git a/brewman/brewman/routers/reports/closing_stock.py b/brewman/brewman/routers/reports/closing_stock.py index 5b670cb0..a6385499 100644 --- a/brewman/brewman/routers/reports/closing_stock.py +++ b/brewman/brewman/routers/reports/closing_stock.py @@ -27,7 +27,10 @@ from ...models.validations import check_journals_are_valid from ...models.voucher import Voucher from ...models.voucher_type import VoucherType from ...schemas import closing_stock as schemas +from ...schemas.cost_centre import CostCentreLink +from ...schemas.product import ProductLink from ...schemas.user import UserToken +from ...schemas.user_link import UserLink from .. import get_lock_info @@ -42,7 +45,7 @@ def report_blank( return schemas.ClosingStock( date_=get_finish_date(request.session), # type: ignore posted=False, - cost_centre=schemas.CostCentreLink(id_=CostCentre.cost_centre_purchase()), + cost_centre=CostCentreLink(id_=CostCentre.cost_centre_purchase()), items=[], ) @@ -125,15 +128,15 @@ def full_report(date_: date, cost_centre_id: uuid.UUID, db: Session) -> schemas. ) return schemas.ClosingStock( date_=date_, - cost_centre=schemas.CostCentreLink(id_=cost_centre_id), + cost_centre=CostCentreLink(id_=cost_centre_id), items=build_report(date_, cost_centre_id, db), creation_date=voucher.creation_date if voucher is not None else None, last_edit_date=voucher.last_edit_date if voucher is not None else None, - user=schemas.UserLink(id_=voucher.user.id, name=voucher.user.name) if voucher is not None else None, + user=UserLink(id_=voucher.user.id, name=voucher.user.name) if voucher is not None else None, posted=voucher.posted if voucher is not None else False, poster=None if voucher is None or voucher.poster is None - else schemas.UserLink(id_=voucher.poster.id, name=voucher.poster.name), + else UserLink(id_=voucher.poster.id, name=voucher.poster.name), ) @@ -220,11 +223,11 @@ def build_report(date_: date, cost_centre_id: uuid.UUID, db: Session) -> list[sc if quantity != 0 and amount != 0: id_ = next((p.id for p in physical_list if p.sku_id == sku_version.sku_id), None) physical = next((p.quantity for p in physical_list if p.sku_id == sku_version.sku_id), quantity) - cc = next((schemas.CostCentreLink(id_=c) for (c, s) in ccs if s == sku_version.sku_id), None) + cc = next((CostCentreLink(id_=c) for (c, s) in ccs if s == sku_version.sku_id), None) body.append( schemas.ClosingStockItem( id_=id_, - product=schemas.ProductLink( + product=ProductLink( id_=sku_version.sku_id, name=f"{sku_version.sku.product.versions[0].name} ({sku_version.units})" ), group=sku_version.sku.product.versions[0].product_group.name, diff --git a/brewman/brewman/routers/reports/ledger.py b/brewman/brewman/routers/reports/ledger.py index 4f7bbf38..734a8d2e 100644 --- a/brewman/brewman/routers/reports/ledger.py +++ b/brewman/brewman/routers/reports/ledger.py @@ -16,6 +16,7 @@ from ...models.journal import Journal from ...models.voucher import Voucher from ...models.voucher_type import VoucherType from ...schemas import ledger as schemas +from ...schemas.account import AccountLink from ...schemas.user import UserToken @@ -52,7 +53,7 @@ def show_data( return schemas.Ledger( start_date=start_date, finish_date=finish_date, - account=schemas.AccountLink(id_=account.id, name=account.name), + account=AccountLink(id_=account.id, name=account.name), body=body, ) diff --git a/brewman/brewman/routers/reports/mozimo_daily_register.py b/brewman/brewman/routers/reports/mozimo_daily_register.py index 1ef64377..2eff8feb 100644 --- a/brewman/brewman/routers/reports/mozimo_daily_register.py +++ b/brewman/brewman/routers/reports/mozimo_daily_register.py @@ -18,6 +18,7 @@ from ...models.product_version import ProductVersion from ...models.sku_version import SkuVersion from ...models.stock_keeping_unit import StockKeepingUnit from ...schemas import mozimo_daily_register as schemas +from ...schemas.product import ProductLink from ...schemas.user import UserToken @@ -101,7 +102,7 @@ def build_report(date_: date, db: Session) -> list[schemas.MozimoDailyRegisterIt body.append( schemas.MozimoDailyRegisterItem( id_=None if item is None else item.id_, - product=schemas.ProductLink(id_=sku_id, name=f"{name} ({units})"), + product=ProductLink(id_=sku_id, name=f"{name} ({units})"), opening=ob, received=Decimal(0) if item is None else item.received, sale=Decimal(0) if item is None else item.sale, diff --git a/brewman/brewman/routers/reports/mozimo_product_register.py b/brewman/brewman/routers/reports/mozimo_product_register.py index 2dbe203b..f0dec488 100644 --- a/brewman/brewman/routers/reports/mozimo_product_register.py +++ b/brewman/brewman/routers/reports/mozimo_product_register.py @@ -3,19 +3,21 @@ import uuid from datetime import UTC, date, datetime from decimal import Decimal -from fastapi import APIRouter, HTTPException, Request, Security, status +from fastapi import APIRouter, Depends, HTTPException, Request, Security, status from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from sqlalchemy.sql.expression import select from ...core.security import get_current_active_user as get_user -from ...core.session import get_finish_date, get_start_date, set_period +from ...core.session import set_period from ...db.session import SessionFuture from ...models.mozimo_stock_register import MozimoStockRegister from ...models.stock_keeping_unit import StockKeepingUnit from ...schemas import mozimo_product_register as schemas +from ...schemas.product import ProductLink from ...schemas.user import UserToken from ..attendance import date_range +from . import report_finish_date, report_start_date from .mozimo_daily_register import opening_balance @@ -24,12 +26,13 @@ router = APIRouter() @router.get("", response_model=schemas.MozimoProductRegister) def show_blank( - request: Request, + start_date: date = Depends(report_start_date), + finish_date: date = Depends(report_finish_date), user: UserToken = Security(get_user, scopes=["product-ledger"]), ) -> schemas.MozimoProductRegister: return schemas.MozimoProductRegister( - start_date=get_start_date(request.session), - finish_date=get_finish_date(request.session), + start_date=start_date, + finish_date=finish_date, product=None, body=[], ) @@ -39,20 +42,18 @@ def show_blank( def show_data( id_: uuid.UUID, request: Request, - s: str | None = None, - f: str | None = None, + start_date: date = Depends(report_start_date), + finish_date: date = Depends(report_finish_date), user: UserToken = Security(get_user, scopes=["product-ledger"]), ) -> schemas.MozimoProductRegister: with SessionFuture() as db: sku = db.execute(select(StockKeepingUnit).where(StockKeepingUnit.id == id_)).scalar_one() - start_date = datetime.strptime(s or get_start_date(request.session), "%d-%b-%Y").date() - finish_date = datetime.strptime(f or get_finish_date(request.session), "%d-%b-%Y").date() body = build_report(sku.id, start_date, finish_date, db) set_period(start_date, finish_date, request.session) return schemas.MozimoProductRegister( start_date=start_date, finish_date=finish_date, - product=schemas.ProductLink(id_=sku.id, name=f"{sku.product.versions[-1].name} ({sku.versions[-1].units})"), + product=ProductLink(id_=sku.id, name=f"{sku.product.versions[-1].name} ({sku.versions[-1].units})"), body=body, ) @@ -104,6 +105,11 @@ def save_route( user: UserToken = Security(get_user, scopes=["product-ledger"]), ) -> schemas.MozimoProductRegister: try: + if not data.product or not data.product.id_: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Product cannot be null", + ) if any(i for i in data.body if i.received < 0): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, diff --git a/brewman/brewman/routers/reports/mozimo_sale_upload.py b/brewman/brewman/routers/reports/mozimo_sale_upload.py index e6386cb0..8f23fe5f 100644 --- a/brewman/brewman/routers/reports/mozimo_sale_upload.py +++ b/brewman/brewman/routers/reports/mozimo_sale_upload.py @@ -1,46 +1,46 @@ -import csv -import io -import uuid +# import csv +# import io +# import uuid -from datetime import date, datetime, time, timedelta -from io import StringIO +# from datetime import date, datetime, time, timedelta +# from io import StringIO -from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status -from sqlalchemy import select -from sqlalchemy.dialects.postgresql import insert as pg_insert -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.orm import Session +# from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status +# from sqlalchemy import select +# from sqlalchemy.dialects.postgresql import insert as pg_insert +# from sqlalchemy.exc import SQLAlchemyError +# from sqlalchemy.orm import Session -from ...core.security import get_user -from ...db.session import SessionFuture -from ...models.employee import Employee -from ...models.fingerprint import Fingerprint -from ...schemas import fingerprint as schemas -from ...schemas.user import UserToken +# from ...core.security import get_user +# from ...db.session import SessionFuture +# from ...models.employee import Employee +# from ...models.fingerprint import Fingerprint +# from ...schemas import fingerprint as schemas +# from ...schemas.user import UserToken -router = APIRouter() +# router = APIRouter() -def fp(file_data: StringIO, employees: dict[int, uuid.UUID]) -> list[schemas.Fingerprint]: - fingerprints: list[schemas.Fingerprint] = [] - reader = csv.reader(file_data, delimiter="\t") - header = next(reader) - employee_column = 2 - date_column = len(header) - 1 - date_format = "%Y-%m-%d %H:%M:%S" - for row in reader: - try: - employee_code = int(row[employee_column]) # EnNo - date_ = datetime.strptime(row[date_column].replace("/", "-"), date_format) - if employee_code in employees: - fingerprints.append( - schemas.Fingerprint( - id_=uuid.uuid4(), - employee_id=employees[employee_code], - date_=date_, - ) - ) - except ValueError: - continue - return fingerprints +# def fp(file_data: StringIO, employees: dict[int, uuid.UUID]) -> list[schemas.Fingerprint]: +# fingerprints: list[schemas.Fingerprint] = [] +# reader = csv.reader(file_data, delimiter="\t") +# header = next(reader) +# employee_column = 2 +# date_column = len(header) - 1 +# date_format = "%Y-%m-%d %H:%M:%S" +# for row in reader: +# try: +# employee_code = int(row[employee_column]) # EnNo +# date_ = datetime.strptime(row[date_column].replace("/", "-"), date_format) +# if employee_code in employees: +# fingerprints.append( +# schemas.Fingerprint( +# id_=uuid.uuid4(), +# employee_id=employees[employee_code], +# date_=date_, +# ) +# ) +# except ValueError: +# continue +# return fingerprints diff --git a/brewman/brewman/routers/reports/product_ledger.py b/brewman/brewman/routers/reports/product_ledger.py index 799364ff..f20706c3 100644 --- a/brewman/brewman/routers/reports/product_ledger.py +++ b/brewman/brewman/routers/reports/product_ledger.py @@ -21,6 +21,7 @@ from ...models.stock_keeping_unit import StockKeepingUnit from ...models.voucher import Voucher from ...models.voucher_type import VoucherType from ...schemas import product_ledger as schemas +from ...schemas.product import ProductLink from ...schemas.user import UserToken @@ -62,7 +63,7 @@ def show_data( return schemas.ProductLedger( start_date=start_date, finish_date=finish_date, - product=schemas.ProductLink(id_=product.id, name=product.versions[-1].name), + product=ProductLink(id_=product.id, name=product.versions[-1].name), body=body, ) diff --git a/brewman/brewman/routers/reports/reconcile.py b/brewman/brewman/routers/reports/reconcile.py index 4740dd34..4f206cf7 100644 --- a/brewman/brewman/routers/reports/reconcile.py +++ b/brewman/brewman/routers/reports/reconcile.py @@ -16,6 +16,7 @@ from ...models.journal import Journal from ...models.voucher import Voucher from ...models.voucher_type import VoucherType from ...schemas import reconcile as schemas +from ...schemas.account import AccountLink from ...schemas.user import UserToken @@ -52,7 +53,7 @@ def show_data( return schemas.Reconcile( start_date=start_date, finish_date=finish_date, - account=schemas.AccountLink(id_=account.id, name=account.name), + account=AccountLink(id_=account.id, name=account.name), body=body, ) @@ -176,6 +177,6 @@ def save( return schemas.Reconcile( start_date=start_date, finish_date=finish_date, - account=schemas.AccountLink(id_=account.id, name=account.name), + account=AccountLink(id_=account.id, name=account.name), body=body, ) diff --git a/brewman/brewman/routers/role.py b/brewman/brewman/routers/role.py index 9e23fe50..68748d00 100644 --- a/brewman/brewman/routers/role.py +++ b/brewman/brewman/routers/role.py @@ -15,6 +15,7 @@ from ..models.role import Role from ..models.role_permission import RolePermission from ..models.user_role import UserRole from ..schemas import role as schemas +from ..schemas.permission import PermissionItem from ..schemas.user import UserToken @@ -60,7 +61,7 @@ def update_route( ) -def add_permissions(role: Role, permissions: list[schemas.PermissionItem], db: Session) -> None: +def add_permissions(role: Role, permissions: list[PermissionItem], db: Session) -> None: for permission in permissions: gp = next((p for p in role.permissions if p.id == permission.id_), None) if permission.enabled and gp is None: @@ -131,7 +132,7 @@ def role_info(item: Role, db: Session) -> schemas.Role: id_=item.id, name=item.name, permissions=[ - schemas.PermissionItem( + PermissionItem( id_=p.id, name=p.name, enabled=p in item.permissions, @@ -145,7 +146,7 @@ def role_blank(db: Session) -> schemas.RoleBlank: return schemas.RoleBlank( name="", permissions=[ - schemas.PermissionItem(id_=p.id, name=p.name, enabled=False) + PermissionItem(id_=p.id, name=p.name, enabled=False) for p in db.execute(select(Permission).order_by(Permission.name)).scalars().all() ], ) diff --git a/brewman/brewman/routers/user.py b/brewman/brewman/routers/user.py index ec227f26..a2eeab61 100644 --- a/brewman/brewman/routers/user.py +++ b/brewman/brewman/routers/user.py @@ -16,6 +16,7 @@ from ..models.user import User from ..models.user_role import UserRole from ..models.voucher import Voucher from ..schemas import user as schemas +from ..schemas.role import RoleItem from ..schemas.user import UserToken @@ -96,7 +97,7 @@ def update_route( ) -def add_roles(user: User, roles: list[schemas.RoleItem], db: Session) -> None: +def add_roles(user: User, roles: list[RoleItem], db: Session) -> None: for role in roles: ug = next((g for g in user.roles if g.id == role.id_), None) if role.enabled and ug is None: @@ -181,7 +182,7 @@ def user_info(item: User, db: Session, user: UserToken) -> schemas.User: password="", locked_out=item.locked_out, roles=[ - schemas.RoleItem( + RoleItem( id_=r.id, name=r.name, enabled=r in item.roles, @@ -199,7 +200,7 @@ def user_blank(db: Session) -> schemas.UserBlank: password="", locked_out=False, roles=[ - schemas.RoleItem(id_=r.id, name=r.name, enabled=False) + RoleItem(id_=r.id, name=r.name, enabled=False) for r in db.execute(select(Role).order_by(Role.name)).scalars().all() ], ) diff --git a/brewman/brewman/routers/voucher.py b/brewman/brewman/routers/voucher.py index 8b9948e0..ad3f2b58 100644 --- a/brewman/brewman/routers/voucher.py +++ b/brewman/brewman/routers/voucher.py @@ -3,6 +3,7 @@ import uuid from collections.abc import Sequence from datetime import date from decimal import Decimal +from typing import cast from fastapi import APIRouter, HTTPException, Security, status from sqlalchemy import and_, distinct, func, or_, select @@ -27,8 +28,20 @@ from ..models.voucher import Voucher from ..models.voucher_type import VoucherType from ..routers import get_lock_info from ..schemas import voucher as output +from ..schemas.account import AccountLink +from ..schemas.batch import Batch as BatchSchema from ..schemas.blank_voucher_info import BlankVoucherInfo +from ..schemas.cost_centre import CostCentreLink +from ..schemas.employee import EmployeeLink +from ..schemas.employee_benefit import EmployeeBenefit as EmployeeBenefitSchema +from ..schemas.image_upload import ImageUpload +from ..schemas.incentive import Incentive as IncentiveSchema +from ..schemas.inventory import Inventory as InventorySchema +from ..schemas.journal import Journal as JournalSchema +from ..schemas.product import ProductLink +from ..schemas.tag import Tag as TagSchema from ..schemas.user import UserToken +from ..schemas.user_link import UserLink router = APIRouter() @@ -227,50 +240,52 @@ def voucher_info(voucher: Voucher, db: Session) -> output.Voucher: files=[], creation_date=voucher.creation_date, last_edit_date=voucher.last_edit_date, - user=output.UserLink(id_=voucher.user.id, name=voucher.user.name), - poster=voucher.poster.name if voucher.posted else "", - tags=[output.Tag(id_=t.id, name=t.name) for t in voucher.tags], + user=UserLink(id_=voucher.user.id, name=voucher.user.name), + poster=voucher.poster.name if voucher.poster and voucher.posted else "", + tags=[TagSchema(id_=t.id, name=t.name) for t in voucher.tags], ) if voucher.reconcile_date is not None: json_voucher.reconcile_date = voucher.reconcile_date if voucher.voucher_type == VoucherType.PURCHASE: item = [j for j in voucher.journals if j.debit == -1][0] - json_voucher.vendor = output.AccountLink(id_=item.account.id, name=item.account.name) + json_voucher.vendor = AccountLink(id_=item.account.id, name=item.account.name) elif voucher.voucher_type == VoucherType.ISSUE: item = [j for j in voucher.journals if j.debit == -1][0] - json_voucher.source = output.CostCentreLink(id_=item.cost_centre_id, name="") + json_voucher.source = CostCentreLink(id_=item.cost_centre_id, name="") item = [j for j in voucher.journals if j.debit == 1][0] - json_voucher.destination = output.CostCentreLink(id_=item.cost_centre_id, name="") + json_voucher.destination = CostCentreLink(id_=item.cost_centre_id, name="") if voucher.voucher_type == VoucherType.PURCHASE_RETURN: item = [j for j in voucher.journals if j.debit == 1][0] - json_voucher.vendor = output.AccountLink(id_=item.account.id, name=item.account.name) + json_voucher.vendor = AccountLink(id_=item.account.id, name=item.account.name) else: for journal in voucher.journals: json_voucher.journals.append( - output.Journal( + JournalSchema( id_=journal.id, debit=journal.debit, amount=journal.amount, - account=output.AccountLink(id_=journal.account.id, name=journal.account.name), - cost_centre=output.CostCentreLink(id_=journal.cost_centre_id), + account=AccountLink(id_=journal.account.id, name=journal.account.name), + cost_centre=CostCentreLink(id_=journal.cost_centre_id), ) ) for benefit in voucher.employee_benefits: + acc = benefit.journal.account + b_employee = cast(Employee, acc) json_voucher.employee_benefits.append( - output.EmployeeBenefit( + EmployeeBenefitSchema( gross_salary=benefit.gross_salary, days_worked=benefit.days_worked, esi_employee=benefit.esi_ee, pf_employee=benefit.pf_ee, esi_employer=benefit.esi_er, pf_employer=benefit.pf_er, - employee=output.EmployeeLink( - id_=benefit.journal.account.id, - name=benefit.journal.account.name, - designation=benefit.journal.account.designation, - cost_centre=output.CostCentreLink( - id_=benefit.journal.account.cost_centre.id, - name=benefit.journal.account.cost_centre.name, + employee=EmployeeLink( + id_=b_employee.id, + name=b_employee.name, + designation=b_employee.designation, + cost_centre=CostCentreLink( + id_=b_employee.cost_centre.id, + name=b_employee.cost_centre.name, ), ), ) @@ -280,7 +295,7 @@ def voucher_info(voucher: Voucher, db: Session) -> output.Voucher: select(Employee).where(Employee.id == incentive.journal.account_id) ).scalar_one() json_voucher.incentives.append( - output.Incentive( + IncentiveSchema( employee_id=incentive.journal.account_id, name=incentive.journal.account.name, designation=employee.designation, @@ -328,21 +343,21 @@ def voucher_info(voucher: Voucher, db: Session) -> output.Voucher: f"from {inventory.batch.name.strftime('%d-%b-%Y')}" ) json_voucher.inventories.append( - output.Inventory( + InventorySchema( id_=inventory.id, quantity=inventory.quantity, rate=inventory.rate, tax=inventory.tax, discount=inventory.discount, amount=inventory.amount, - batch=output.Batch( + batch=BatchSchema( id_=inventory.batch.id, name=text, quantity_remaining=inventory.batch.quantity_remaining, tax=inventory.batch.tax, discount=inventory.batch.discount, rate=inventory.batch.rate, - sku=output.ProductLink( + sku=ProductLink( id_=inventory.batch.sku.id, name=f"{product_name} ({product_unit})" if product_unit else product_name, ), @@ -352,7 +367,7 @@ def voucher_info(voucher: Voucher, db: Session) -> output.Voucher: images = db.execute(select(DbImage).where(DbImage.resource_id == voucher.id)).scalars().all() for image in images: json_voucher.files.append( - output.ImageUpload( + ImageUpload( id_=image.id, resized=f"/db-image/{image.id}/resized", thumbnail=f"/db-image/{image.id}/thumbnail", @@ -382,30 +397,30 @@ def blank_voucher(info: BlankVoucherInfo, db: Session) -> output.Voucher: if info.account is not None: account = db.execute(select(AccountBase).where(AccountBase.id == info.account.id_)).scalars().one_or_none() if account is not None: - j_account = output.AccountLink(id_=account.id, name=account.name) + j_account = AccountLink(id_=account.id, name=account.name) else: j_account = AccountBase.cash_in_hand() - json_voucher.journals.append(output.Journal(account=j_account, amount=Decimal(0), debit=-1)) + json_voucher.journals.append(JournalSchema(account=j_account, amount=Decimal(0), debit=-1)) elif info.type_ == VoucherType.RECEIPT: account = None if info.account is not None: account = db.execute(select(AccountBase).where(AccountBase.id == info.account.id_)).scalars().one_or_none() if account is not None: - j_account = output.AccountLink(id_=account.id, name=account.name) + j_account = AccountLink(id_=account.id, name=account.name) else: j_account = AccountBase.cash_in_hand() - json_voucher.journals.append(output.Journal(account=j_account, amount=Decimal(0), debit=1)) + json_voucher.journals.append(JournalSchema(account=j_account, amount=Decimal(0), debit=1)) elif info.type_ == VoucherType.PURCHASE or info.type_ == VoucherType.PURCHASE_RETURN: json_voucher.vendor = AccountBase.local_purchase() elif info.type_ == VoucherType.ISSUE: if info.source is not None: - json_voucher.source = output.CostCentreLink(id_=info.source.id_) + json_voucher.source = CostCentreLink(id_=info.source.id_) else: - json_voucher.source = output.CostCentreLink(id_=CostCentre.cost_centre_purchase()) + json_voucher.source = CostCentreLink(id_=CostCentre.cost_centre_purchase()) if info.destination is not None: - json_voucher.destination = output.CostCentreLink(id_=info.destination.id_) + json_voucher.destination = CostCentreLink(id_=info.destination.id_) else: - json_voucher.destination = output.CostCentreLink(id_=CostCentre.cost_centre_kitchen()) + json_voucher.destination = CostCentreLink(id_=CostCentre.cost_centre_kitchen()) elif info.type_ == VoucherType.EMPLOYEE_BENEFIT: pass elif info.type_ == VoucherType.INCENTIVE: @@ -418,10 +433,10 @@ def blank_voucher(info: BlankVoucherInfo, db: Session) -> output.Voucher: return json_voucher -def incentive_employees(date_: date, db: Session) -> tuple[list[output.Incentive], Decimal]: +def incentive_employees(date_: date, db: Session) -> tuple[list[IncentiveSchema], Decimal]: start_date = get_first_day(date_) finish_date = date_ - details: list[output.Incentive] = [] + details: list[IncentiveSchema] = [] employees = ( db.execute( select(Employee) @@ -449,7 +464,7 @@ def incentive_employees(date_: date, db: Session) -> tuple[list[output.Incentive ) days_worked = sum(AttendanceType.by_id(x.attendance_type).value for x in att) or Decimal(0) details.append( - output.Incentive( + IncentiveSchema( employee_id=employee.id, name=employee.name, designation=employee.designation, diff --git a/brewman/brewman/schemas/employee.py b/brewman/brewman/schemas/employee.py index c8882467..cfda1d29 100644 --- a/brewman/brewman/schemas/employee.py +++ b/brewman/brewman/schemas/employee.py @@ -50,7 +50,7 @@ class EmployeeIn(AccountBase): return None if value is None else value.strftime("%d-%b-%Y") @model_validator(mode="after") - def leaving_date_more_than_joining_date(self) -> "EmployeeIn": + def leaving_date_more_than_joining_date(self) -> EmployeeIn: if self.is_active: self.leaving_date = None if (not self.is_active) and (self.leaving_date is None): diff --git a/brewman/brewman/schemas/input.py b/brewman/brewman/schemas/input.py index 19b56ae6..bc582239 100644 --- a/brewman/brewman/schemas/input.py +++ b/brewman/brewman/schemas/input.py @@ -58,7 +58,7 @@ class IssueIn(VoucherIn): model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) @model_validator(mode="after") - def source_destination_unique(self) -> "IssueIn": + def source_destination_unique(self) -> IssueIn: if self.source.id_ == self.destination.id_: raise ValueError("Source and destination cannot be the same") return self diff --git a/brewman/brewman/schemas/mozimo_product_register.py b/brewman/brewman/schemas/mozimo_product_register.py index 06873456..2dcd5116 100644 --- a/brewman/brewman/schemas/mozimo_product_register.py +++ b/brewman/brewman/schemas/mozimo_product_register.py @@ -52,17 +52,15 @@ class MozimoProductRegisterItem(BaseModel): class MozimoProductRegister(BaseModel): - start_date: date | str - finish_date: date | str + start_date: date + finish_date: date product: ProductLink | None = None body: list[MozimoProductRegisterItem] model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) @field_validator("start_date", mode="before") @classmethod - def parse_start_date(cls, value: date | str | None) -> date | None: - if value is None: - return None + def parse_start_date(cls, value: date | str) -> date: if isinstance(value, date): return value return datetime.strptime(value, "%d-%b-%Y").date() @@ -73,9 +71,7 @@ class MozimoProductRegister(BaseModel): @field_validator("finish_date", mode="before") @classmethod - def parse_finish_date(cls, value: date | str | None) -> date | None: - if value is None: - return None + def parse_finish_date(cls, value: date | str) -> date: if isinstance(value, date): return value return datetime.strptime(value, "%d-%b-%Y").date() diff --git a/brewman/brewman/schemas/raw_material_cost.py b/brewman/brewman/schemas/raw_material_cost.py index b7cc117b..366cddb1 100644 --- a/brewman/brewman/schemas/raw_material_cost.py +++ b/brewman/brewman/schemas/raw_material_cost.py @@ -22,8 +22,8 @@ class RawMaterialCostItem(BaseModel): group: str | None = None quantity: Daf | None = None - net: Daf | None = None - gross: Daf | None = None + net: Daf + gross: Daf order: int heading: bool | None = None diff --git a/brewman/poetry.lock b/brewman/poetry.lock index cd200bce..4c04ba8a 100644 --- a/brewman/poetry.lock +++ b/brewman/poetry.lock @@ -2764,6 +2764,18 @@ click = ">=8.2.1" rich = ">=12.3.0" shellingham = ">=1.3.0" +[[package]] +name = "types-openpyxl" +version = "3.1.5.20250919" +description = "Typing stubs for openpyxl" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_openpyxl-3.1.5.20250919-py3-none-any.whl", hash = "sha256:bd06f18b12fd5e1c9f0b666ee6151d8140216afa7496f7ebb9fe9d33a1a3ce99"}, + {file = "types_openpyxl-3.1.5.20250919.tar.gz", hash = "sha256:232b5906773eebace1509b8994cdadda043f692cfdba9bfbb86ca921d54d32d7"}, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -3224,4 +3236,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = "^3.14" -content-hash = "b35f77ad333cb52dcc445eefedfdc0287542fd1350dbd89182b66a30947aa33b" +content-hash = "74d69cc011dffe44bfff58f35d3b4a3910f2297433c59a1997bd70c86b41dfb2" diff --git a/brewman/pyproject.toml b/brewman/pyproject.toml index 63c76f96..417a188d 100644 --- a/brewman/pyproject.toml +++ b/brewman/pyproject.toml @@ -1,3 +1,34 @@ +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.mypy] +# --strict + +# Good safety defaults +warn_return_any = true +warn_unused_configs = true +warn_redundant_casts = true +warn_unused_ignores = true +no_implicit_optional = true +strict_equality = true +# Practical ergonomics +pretty = true +show_error_codes = true +show_column_numbers = true +error_summary = true +# These keep signal high in real-world FastAPI projects +disallow_any_generics = true +check_untyped_defs = true +# Start here; tighten later +disallow_untyped_calls = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_defs = true +disallow_untyped_decorators = true +plugins = ["sqlalchemy.ext.mypy.plugin", "pydantic.mypy"] +# --strict end + [tool.poetry] name = "brewman" version = "14.0.0" @@ -5,49 +36,51 @@ description = "Accounting plus inventory management for a restaurant." authors = ["tanshu "] [tool.poetry.dependencies] -python = "^3.14" -uvicorn = {extras = ["standard"], version = "^0.41.0"} -fastapi = {extras = ["all"], version = "^0.129.0"} -passlib = {extras = ["bcrypt"], version = "^1.7.4"} -psycopg = {extras = ["binary", "pool"], version = "^3.3.2"} -SQLAlchemy = "^2.0.46" -python-multipart = "^0.0.22" PyJWT = "^2.11.0" +SQLAlchemy = "^2.0.46" alembic = "^1.18.4" -itsdangerous = "^2.2.0" -python-dotenv = "^1.2.1" -pydantic = {extras = ["dotenv"], version = "^2.12.5"} -pandas = "^3.0.1" arq = "^0.27.0" -openpyxl = "^3.1.5" cryptography = "^46.0.5" +fastapi = { extras = ["all"], version = "^0.129.0" } gunicorn = "^25.1.0" +itsdangerous = "^2.2.0" +openpyxl = "^3.1.5" +pandas = "^3.0.1" +passlib = { extras = ["bcrypt"], version = "^1.7.4" } +psycopg = { extras = ["binary", "pool"], version = "^3.3.2" } +pydantic = { extras = ["dotenv"], version = "^2.12.5" } +python = "^3.14" +python-dotenv = "^1.2.1" +python-multipart = "^0.0.22" +uvicorn = { extras = ["standard"], version = "^0.41.0" } [tool.poetry.group.dev.dependencies] -pre-commit = "^4.5.1" -mypy = "^1.19.1" -ruff = "^0.15.1" bandit = "^1.9.3" +mypy = "^1.19.1" +pre-commit = "^4.5.1" +ruff = "^0.15.1" # safety = "^3.7.0" +types-openpyxl = "^3.1.5.20250919" -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +[tool.pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true [tool.ruff] line-length = 120 # Assume Python 3.14. target-version = "py314" exclude = [ - ".eggs", - ".git", - ".hg", - ".mypy_cache", - ".tox", - ".venv", - "_build", - "buck-out", - "build", + ".eggs", + ".git", + ".hg", + ".mypy_cache", + ".tox", + ".venv", + "_build", + "buck-out", + "build", ] [tool.ruff.lint] @@ -56,24 +89,22 @@ exclude = [ # McCabe complexity (`C901`) by default. # select = ["E4", "E7", "E9", "F"] select = [ - # pycodestyle - "E", - # Pyflakes - "F", - # pyupgrade - "UP", - # flake8-bugbear - # "B", - # flake8-simplify - "SIM", - # isort - "I", + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + # "B", + # flake8-simplify + "SIM", + # isort + "I", ] ignore = ["E203", "E501"] - # Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] - # Avoid trying to fix flake8-bugbear (`B`) violations. unfixable = ["B", "F401"] @@ -86,48 +117,9 @@ lines-between-types = 1 [tool.ruff.format] # Like Black, use double quotes for strings. quote-style = "double" - # Like Black, indent with spaces, rather than tabs. indent-style = "space" - # Like Black, respect magic trailing commas. skip-magic-trailing-comma = false - # Like Black, automatically detect the appropriate line ending. line-ending = "auto" - -[tool.mypy] -# --strict - -# Good safety defaults -warn_return_any = true -warn_unused_configs = true -warn_redundant_casts = true -warn_unused_ignores = true -no_implicit_optional = true -strict_equality = true - -# Practical ergonomics -pretty = true -show_error_codes = true -show_column_numbers = true -error_summary = true - -# These keep signal high in real-world FastAPI projects -disallow_any_generics = true -check_untyped_defs = true - -# Start here; tighten later -disallow_untyped_calls = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_defs = true -disallow_untyped_decorators = true - -plugins = ["sqlalchemy.ext.mypy.plugin", "pydantic.mypy"] -# --strict end - -[tool.pydantic-mypy] -init_forbid_extra = true -init_typed = true -warn_required_dynamic_aliases = true