From 350edf71268669dadb492fe751289fa2d1d7c24f Mon Sep 17 00:00:00 2001 From: tanshu Date: Mon, 6 Sep 2021 20:36:36 +0530 Subject: [PATCH] Removed timezone information from columns. Time will be stored in UTC coordinates. Moved to Sqlalchemy 1.4 model and SessionFuture. Upgraded to Angular 12 Upgraded the python dependencies --- .../alembic/versions/226cadcec9ac_timezone.py | 74 +++ brewman/brewman/core/security.py | 15 +- brewman/brewman/db/base.py | 52 +- brewman/brewman/db/session.py | 4 +- brewman/brewman/main.py | 4 +- brewman/brewman/models/account.py | 6 +- brewman/brewman/models/account_base.py | 36 +- brewman/brewman/models/attendance.py | 29 +- brewman/brewman/models/batch.py | 24 +- brewman/brewman/models/client.py | 2 +- brewman/brewman/models/cost_centre.py | 5 +- brewman/brewman/models/db_image.py | 5 +- brewman/brewman/models/employee.py | 10 +- brewman/brewman/models/emplyee_benefit.py | 5 +- brewman/brewman/models/fingerprint.py | 3 +- brewman/brewman/models/incentive.py | 5 +- brewman/brewman/models/inventory.py | 4 +- brewman/brewman/models/journal.py | 3 +- brewman/brewman/models/login_history.py | 5 +- brewman/brewman/models/permission.py | 2 +- brewman/brewman/models/product.py | 34 +- brewman/brewman/models/product_group.py | 5 +- brewman/brewman/models/recipe.py | 12 +- brewman/brewman/models/recipe_item.py | 3 +- brewman/brewman/models/role.py | 4 + brewman/brewman/models/user.py | 8 +- brewman/brewman/models/validations.py | 3 +- brewman/brewman/models/voucher.py | 4 +- brewman/brewman/routers/__init__.py | 3 +- brewman/brewman/routers/account.py | 162 +++---- brewman/brewman/routers/attendance.py | 88 ++-- brewman/brewman/routers/attendance_report.py | 64 ++- brewman/brewman/routers/auth/__init__.py | 0 brewman/brewman/routers/auth/client.py | 115 ----- brewman/brewman/routers/auth/user.py | 218 --------- brewman/brewman/routers/batch.py | 60 +-- brewman/brewman/routers/client.py | 98 ++++ brewman/brewman/routers/cost_centre.py | 99 ++-- brewman/brewman/routers/credit_salary.py | 75 ++- brewman/brewman/routers/db_image.py | 31 +- brewman/brewman/routers/db_integrity.py | 48 +- brewman/brewman/routers/employee.py | 187 ++++---- .../brewman/routers/employee_attendance.py | 110 ++--- brewman/brewman/routers/employee_benefit.py | 92 ++-- brewman/brewman/routers/fingerprint.py | 78 ++- brewman/brewman/routers/incentive.py | 124 ++--- brewman/brewman/routers/issue.py | 86 ++-- brewman/brewman/routers/issue_grid.py | 43 +- brewman/brewman/routers/journal.py | 77 ++- brewman/brewman/routers/lock_information.py | 55 +-- brewman/brewman/routers/login.py | 124 +++-- brewman/brewman/routers/maintenance.py | 46 +- brewman/brewman/routers/product.py | 206 ++++---- brewman/brewman/routers/product_group.py | 78 ++- brewman/brewman/routers/purchase.py | 102 ++-- brewman/brewman/routers/purchase_return.py | 82 ++-- brewman/brewman/routers/rebase.py | 149 +++--- brewman/brewman/routers/recipe.py | 445 +++++++++--------- .../brewman/routers/reports/balance_sheet.py | 37 +- brewman/brewman/routers/reports/cash_flow.py | 122 +++-- .../brewman/routers/reports/closing_stock.py | 55 +-- brewman/brewman/routers/reports/daybook.py | 42 +- brewman/brewman/routers/reports/ledger.py | 78 ++- .../routers/reports/net_transactions.py | 49 +- .../brewman/routers/reports/product_ledger.py | 92 ++-- .../brewman/routers/reports/profit_loss.py | 70 ++- .../routers/reports/purchase_entries.py | 40 +- brewman/brewman/routers/reports/purchases.py | 50 +- .../routers/reports/raw_material_cost.py | 92 ++-- brewman/brewman/routers/reports/reconcile.py | 127 +++-- .../brewman/routers/reports/stock_movement.py | 77 ++- .../brewman/routers/reports/trial_balance.py | 38 +- brewman/brewman/routers/reports/unposted.py | 36 +- brewman/brewman/routers/reset_stock.py | 92 ++-- brewman/brewman/routers/{auth => }/role.py | 122 ++--- brewman/brewman/routers/user.py | 206 ++++++++ brewman/brewman/routers/voucher.py | 185 ++++---- brewman/brewman/schemas/account.py | 5 +- brewman/brewman/schemas/balance_sheet.py | 3 +- brewman/brewman/schemas/cash_flow.py | 3 +- brewman/brewman/schemas/client.py | 3 +- brewman/brewman/schemas/closing_stock.py | 3 +- brewman/brewman/schemas/cost_centre.py | 3 +- brewman/brewman/schemas/daybook.py | 3 +- brewman/brewman/schemas/employee.py | 7 +- brewman/brewman/schemas/input.py | 15 +- brewman/brewman/schemas/ledger.py | 5 +- brewman/brewman/schemas/login_history.py | 3 +- brewman/brewman/schemas/net_transactions.py | 3 +- brewman/brewman/schemas/product.py | 5 +- brewman/brewman/schemas/product_group.py | 3 +- brewman/brewman/schemas/product_ledger.py | 5 +- brewman/brewman/schemas/profit_loss.py | 3 +- brewman/brewman/schemas/purchase_entries.py | 3 +- brewman/brewman/schemas/purchases.py | 3 +- brewman/brewman/schemas/raw_material_cost.py | 3 +- brewman/brewman/schemas/recipe.py | 68 ++- brewman/brewman/schemas/role.py | 3 +- brewman/brewman/schemas/settings.py | 3 +- brewman/brewman/schemas/stock_movement.py | 3 +- brewman/brewman/schemas/trial_balance.py | 3 +- brewman/brewman/schemas/unposted.py | 3 +- brewman/brewman/schemas/user.py | 5 +- brewman/brewman/schemas/voucher.py | 11 +- brewman/brewman/scripts/initializedb.py | 65 --- brewman/pyproject.toml | 32 +- deploy.sh | 8 +- docker/app/Dockerfile | 4 +- lint.sh | 2 +- overlord/.browserslistrc | 1 - overlord/.eslintrc.json | 8 +- overlord/.gitignore | 6 + overlord/angular.json | 69 +-- overlord/karma.conf.js | 22 +- overlord/package.json | 100 ++-- .../employee-benefits.component.ts | 6 +- .../src/app/incentive/incentive.component.ts | 8 +- overlord/src/app/issue/issue.component.ts | 6 +- overlord/src/app/journal/journal.component.ts | 6 +- overlord/src/app/payment/payment.component.ts | 6 +- overlord/src/app/product/product.service.ts | 4 +- .../purchase-return.component.ts | 6 +- .../src/app/purchase/purchase.component.ts | 6 +- overlord/src/app/receipt/receipt.component.ts | 6 +- .../src/app/unposted/unposted-datasource.ts | 7 +- overlord/src/environments/environment.ts | 2 +- overlord/src/polyfills.ts | 62 +-- overlord/src/test.ts | 2 +- overlord/tsconfig.json | 2 +- 129 files changed, 2720 insertions(+), 3027 deletions(-) create mode 100644 brewman/alembic/versions/226cadcec9ac_timezone.py delete mode 100644 brewman/brewman/routers/auth/__init__.py delete mode 100644 brewman/brewman/routers/auth/client.py delete mode 100644 brewman/brewman/routers/auth/user.py create mode 100644 brewman/brewman/routers/client.py rename brewman/brewman/routers/{auth => }/role.py (51%) create mode 100644 brewman/brewman/routers/user.py diff --git a/brewman/alembic/versions/226cadcec9ac_timezone.py b/brewman/alembic/versions/226cadcec9ac_timezone.py new file mode 100644 index 00000000..dd5d264b --- /dev/null +++ b/brewman/alembic/versions/226cadcec9ac_timezone.py @@ -0,0 +1,74 @@ +"""timezone + +Revision ID: 226cadcec9ac +Revises: 002f51d87565 +Create Date: 2021-04-19 09:58:54.556818 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '226cadcec9ac' +down_revision = '002f51d87565' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('attendances', 'creation_date', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + existing_nullable=True) + op.alter_column('auth_clients', 'creation_date', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + existing_nullable=False) + op.alter_column('auth_login_history', 'date', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + existing_nullable=False) + op.alter_column('images', 'creation_date', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + existing_nullable=False) + op.alter_column('vouchers', 'creation_date', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + existing_nullable=False) + op.alter_column('vouchers', 'last_edit_date', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + existing_nullable=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('vouchers', 'last_edit_date', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=False) + op.alter_column('vouchers', 'creation_date', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=False) + op.alter_column('images', 'creation_date', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=False) + op.alter_column('auth_login_history', 'date', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=False) + op.alter_column('auth_clients', 'creation_date', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=False) + op.alter_column('attendances', 'creation_date', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=True) + # ### end Alembic commands ### diff --git a/brewman/brewman/core/security.py b/brewman/brewman/core/security.py index 47947ae7..0fb8579d 100644 --- a/brewman/brewman/core/security.py +++ b/brewman/brewman/core/security.py @@ -9,10 +9,10 @@ from jose import jwt from jose.exceptions import ExpiredSignatureError from jwt import PyJWTError from pydantic import BaseModel, ValidationError +from sqlalchemy import select from sqlalchemy.orm import Session from ..core.config import settings -from ..db.session import SessionLocal from ..models.client import Client from ..models.user import User as UserModel @@ -39,15 +39,6 @@ def f7(seq): return [x for x in seq if not (x in seen or seen_add(x))] -# Dependency -def get_db(): - try: - db = SessionLocal() - yield db - finally: - db.close() - - def create_access_token(*, data: dict, expires_delta: timedelta = None): to_encode = data.copy() if expires_delta: @@ -75,7 +66,9 @@ def authenticate_user(username: str, password: str, db: Session) -> Optional[Use def client_allowed(user: UserModel, client_id: int, otp: Optional[int] = None, db: Session = None) -> (bool, Client): - client = db.query(Client).filter(Client.code == client_id).first() if client_id else None + client: Client = ( + db.execute(select(Client).where(Client.code == client_id)).scalar_one_or_none() if client_id else None + ) if client is None: client = Client.create(db) allowed = "clients" in set([p.name.replace(" ", "-").lower() for r in user.roles for p in r.permissions]) diff --git a/brewman/brewman/db/base.py b/brewman/brewman/db/base.py index a701546a..ee658bdf 100644 --- a/brewman/brewman/db/base.py +++ b/brewman/brewman/db/base.py @@ -1,28 +1,28 @@ # Import all the models, so that Base has them before being # imported by Alembic -from brewman.db.base_class import Base # noqa: F401 -from brewman.models.account import Account # noqa: F401 -from brewman.models.account_base import AccountBase # noqa: F401 -from brewman.models.attendance import Attendance # noqa: F401 -from brewman.models.batch import Batch # noqa: F401 -from brewman.models.client import Client # noqa: F401 -from brewman.models.cost_centre import CostCentre # noqa: F401 -from brewman.models.db_image import DbImage # noqa: F401 -from brewman.models.db_setting import DbSetting # noqa: F401 -from brewman.models.employee import Employee # noqa: F401 -from brewman.models.emplyee_benefit import EmployeeBenefit # noqa: F401 -from brewman.models.fingerprint import Fingerprint # noqa: F401 -from brewman.models.incentive import Incentive # noqa: F401 -from brewman.models.inventory import Inventory # noqa: F401 -from brewman.models.journal import Journal # noqa: F401 -from brewman.models.login_history import LoginHistory # noqa: F401 -from brewman.models.permission import Permission # noqa: F401 -from brewman.models.product import Product # noqa: F401 -from brewman.models.product_group import ProductGroup # noqa: F401 -from brewman.models.recipe import Recipe # noqa: F401 -from brewman.models.recipe_item import RecipeItem # noqa: F401 -from brewman.models.role import Role # noqa: F401 -from brewman.models.role_permission import role_permission # noqa: F401 -from brewman.models.user import User # noqa: F401 -from brewman.models.user_role import user_role # noqa: F401 -from brewman.models.voucher import Voucher # noqa: F401 +from .db.base_class import Base # noqa: F401 +from .models.account import Account # noqa: F401 +from .models.account_base import AccountBase # 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.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.emplyee_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.permission import Permission # noqa: F401 +from .models.product import Product # noqa: F401 +from .models.product_group import ProductGroup # noqa: F401 +from .models.recipe import Recipe # noqa: F401 +from .models.recipe_item import RecipeItem # noqa: F401 +from .models.role import Role # noqa: F401 +from .models.role_permission import role_permission # noqa: F401 +from .models.user import User # noqa: F401 +from .models.user_role import user_role # noqa: F401 +from .models.voucher import Voucher # noqa: F401 diff --git a/brewman/brewman/db/session.py b/brewman/brewman/db/session.py index 3329a6a1..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) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +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/main.py b/brewman/brewman/main.py index 39f561f3..b77a4ead 100644 --- a/brewman/brewman/main.py +++ b/brewman/brewman/main.py @@ -13,6 +13,7 @@ from .routers import ( attendance_report, attendance_types, batch, + client, cost_centre, credit_salary, db_image, @@ -35,9 +36,10 @@ from .routers import ( rebase, recipe, reset_stock, + role, + user, voucher, ) -from .routers.auth import client, role, user from .routers.reports import ( balance_sheet, cash_flow, diff --git a/brewman/brewman/models/account.py b/brewman/brewman/models/account.py index 2e64e46f..becb3fab 100644 --- a/brewman/brewman/models/account.py +++ b/brewman/brewman/models/account.py @@ -1,9 +1,13 @@ -from brewman.models.account_base import AccountBase +from sqlalchemy.orm import relationship + +from .account_base import AccountBase class Account(AccountBase): __mapper_args__ = {"polymorphic_identity": ""} + products = relationship("Product", primaryjoin="Account.id==Product.account_id", back_populates="account") + def can_delete(self, advanced_delete): if len(self.products) > 0: return False, "Account has products" diff --git a/brewman/brewman/models/account_base.py b/brewman/brewman/models/account_base.py index 35967c98..9df3eadf 100644 --- a/brewman/brewman/models/account_base.py +++ b/brewman/brewman/models/account_base.py @@ -1,11 +1,12 @@ import uuid -from brewman.models.account_type import AccountType -from brewman.models.meta import Base -from sqlalchemy import Boolean, Column, ForeignKey, Integer, Unicode, func +from sqlalchemy import Boolean, Column, ForeignKey, Integer, Unicode, func, select from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Session, relationship +from .account_type import AccountType +from .meta import Base + class AccountBase(Base): __tablename__ = "accounts" @@ -29,6 +30,7 @@ class AccountBase(Base): __mapper_args__ = {"polymorphic_on": account_type} journals = relationship("Journal", back_populates="account") + cost_centre = relationship("CostCentre", back_populates="accounts") @property def __name__(self): @@ -61,28 +63,29 @@ class AccountBase(Base): self.is_fixture = is_fixture @classmethod - def query(cls, q, type_, reconcilable=None, active=None, db=None): - query_ = db.query(cls) + def query(cls, q: str, type_: int, reconcilable: bool = None, active: bool = None, db: Session = None): + query_ = select(cls) if type_ is not None: if not isinstance(type_, int): type_ = int(type_) - query_ = query_.filter(cls.type == type_) + query_ = query_.where(cls.type == type_) if reconcilable is not None: - query_ = query_.filter(cls.is_reconcilable == reconcilable) + query_ = query_.where(cls.is_reconcilable == reconcilable) if active is not None: - query_ = query_.filter(cls.is_active == active) + query_ = query_.where(cls.is_active == active) if q is not None: for item in q.split(): - query_ = query_.filter(cls.name.ilike(f"%{item}%")) - return query_.order_by(cls.name) + query_ = query_.where(cls.name.ilike(f"%{item}%")) + return db.execute(query_.order_by(cls.name)).scalars().all() def create(self, db: Session): - code = db.query(func.max(AccountBase.code)).filter(AccountBase.type == self.type).one()[0] - self.code = 1 if code is None else code + 1 + self.code = db.execute( + select(func.coalesce(func.max(AccountBase.code), 0) + 1).where(AccountBase.type == self.type) + ).scalar_one() db.add(self) return self - def can_delete(self, advanced_delete): + def can_delete(self, advanced_delete: bool): if self.is_fixture: return False, f"{self.name} is a fixture and cannot be edited or deleted." if self.is_active: @@ -92,9 +95,10 @@ class AccountBase(Base): return True, "" @classmethod - def get_code(cls, type_, db: Session): - code = db.query(func.max(AccountBase.code)).filter(AccountBase.type == type_).one()[0] - return 1 if code is None else code + 1 + def get_code(cls, type_: int, db: Session): + return db.execute( + select(func.coalesce(func.max(AccountBase.code), 0) + 1).where(AccountBase.type == type_) + ).scalar_one() @classmethod def all_purchases(cls): diff --git a/brewman/brewman/models/attendance.py b/brewman/brewman/models/attendance.py index 2181c568..6f52248d 100644 --- a/brewman/brewman/models/attendance.py +++ b/brewman/brewman/models/attendance.py @@ -2,11 +2,21 @@ import uuid from datetime import datetime -from brewman.models.meta import Base -from sqlalchemy import Boolean, Column, Date, DateTime, ForeignKey, Integer, Numeric +from sqlalchemy import ( + Boolean, + Column, + Date, + DateTime, + ForeignKey, + Integer, + Numeric, + select, +) from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Session, relationship +from .meta import Base + class Attendance(Base): __tablename__ = "attendances" @@ -16,7 +26,7 @@ class Attendance(Base): date = Column("date", Date, nullable=False) attendance_type = Column("attendance_type", Integer) amount = Column("amount", Numeric(precision=5, scale=2)) - creation_date = Column("creation_date", DateTime(timezone=True)) + creation_date = Column("creation_date", DateTime()) user_id = Column("user_id", UUID(as_uuid=True), ForeignKey("auth_users.id")) is_valid = Column("is_valid", Boolean) @@ -44,11 +54,14 @@ class Attendance(Base): def create(self, db: Session): old = ( - db.query(Attendance) - .filter(Attendance.date == self.date) - .filter(Attendance.employee_id == self.employee_id) - .filter(Attendance.is_valid == True) # noqa: E712 - .first() + db.execute( + select(Attendance) + .filter(Attendance.date == self.date) + .filter(Attendance.employee_id == self.employee_id) + .filter(Attendance.is_valid == True) # noqa: E712 + ) + .scalars() + .one_or_none() ) if old is None or old.attendance_type != self.attendance_type: if old is not None: diff --git a/brewman/brewman/models/batch.py b/brewman/brewman/models/batch.py index 8d5bd8e1..a30214a1 100644 --- a/brewman/brewman/models/batch.py +++ b/brewman/brewman/models/batch.py @@ -1,11 +1,14 @@ import uuid -from brewman.models.meta import Base -from brewman.models.product import Product -from sqlalchemy import Column, Date, ForeignKey, Numeric +from datetime import date + +from sqlalchemy import Column, Date, ForeignKey, Numeric, select from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Session, relationship +from .meta import Base +from .product import Product + class Batch(Base): __tablename__ = "batches" @@ -19,6 +22,7 @@ class Batch(Base): discount = Column("discount", Numeric(precision=15, scale=5), nullable=False) inventories = relationship("Inventory", backref="batch", cascade=None, cascade_backrefs=False) + product = relationship("Product", back_populates="batches") def __init__( self, @@ -45,16 +49,16 @@ class Batch(Base): return self.quantity_remaining * self.rate * (1 + self.tax) * (1 - self.discount) @classmethod - def list(cls, q, include_nil, date, db: Session): - query = db.query(cls).join(cls.product) + def list(cls, q: str, include_nil: bool, date_: date, db: Session): + query = select(cls).join(cls.product) if not include_nil: - query = query.filter(cls.quantity_remaining > 0) - if date is not None: - query = query.filter(cls.name <= date) + query = query.where(cls.quantity_remaining > 0) + if date_ is not None: + query = query.where(cls.name <= date_) if q is not None: for item in q.split(): - query = query.filter(Product.name.ilike(f"%{item}%")) - return query.order_by(Product.name).order_by(cls.name).all() + query = query.where(Product.name.ilike(f"%{item}%")) + return db.execute(query.order_by(Product.name).order_by(cls.name)).scalars().all() @classmethod def suspense(cls): diff --git a/brewman/brewman/models/client.py b/brewman/brewman/models/client.py index 5a80deb3..85a320b8 100644 --- a/brewman/brewman/models/client.py +++ b/brewman/brewman/models/client.py @@ -23,7 +23,7 @@ class Client(Base): name = Column("name", Unicode(255), unique=True, nullable=False) enabled = Column("enabled", Boolean, nullable=False) otp = Column("otp", Integer) - creation_date = Column("creation_date", DateTime(timezone=True), nullable=False) + creation_date = Column("creation_date", DateTime(), nullable=False) login_history: List[LoginHistory] = relationship("LoginHistory", order_by=desc(LoginHistory.date), backref="client") diff --git a/brewman/brewman/models/cost_centre.py b/brewman/brewman/models/cost_centre.py index 7f3ebf1f..17069e90 100644 --- a/brewman/brewman/models/cost_centre.py +++ b/brewman/brewman/models/cost_centre.py @@ -1,10 +1,11 @@ import uuid -from brewman.models.meta import Base from sqlalchemy import Boolean, Column, Unicode from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship +from .meta import Base + class CostCentre(Base): __tablename__ = "cost_centres" @@ -13,7 +14,7 @@ class CostCentre(Base): name = Column("name", Unicode(255), unique=True) is_fixture = Column("is_fixture", Boolean, nullable=False) - accounts = relationship("AccountBase", backref="cost_centre") + accounts = relationship("AccountBase", back_populates="cost_centre") journals = relationship("Journal", back_populates="cost_centre") @property diff --git a/brewman/brewman/models/db_image.py b/brewman/brewman/models/db_image.py index b43c6cb0..64dae05f 100644 --- a/brewman/brewman/models/db_image.py +++ b/brewman/brewman/models/db_image.py @@ -2,10 +2,11 @@ import uuid from datetime import datetime -from brewman.models.meta import Base from sqlalchemy import Column, DateTime, Unicode from sqlalchemy.dialects.postgresql import BYTEA, UUID +from .meta import Base + class DbImage(Base): __tablename__ = "images" @@ -15,7 +16,7 @@ class DbImage(Base): resource_type = Column("resource_type", Unicode(255), nullable=False) image = Column("image", BYTEA, nullable=False) thumbnail = Column("thumbnail", BYTEA, nullable=False) - creation_date = Column("creation_date", DateTime(timezone=True), nullable=False) + creation_date = Column("creation_date", DateTime(), nullable=False) def __init__( self, diff --git a/brewman/brewman/models/employee.py b/brewman/brewman/models/employee.py index 5cdc821a..4ba7ce3b 100644 --- a/brewman/brewman/models/employee.py +++ b/brewman/brewman/models/employee.py @@ -1,8 +1,9 @@ -from brewman.models.account_base import AccountBase -from sqlalchemy import Column, Date, ForeignKey, Integer, Numeric, Unicode, func +from sqlalchemy import Column, Date, ForeignKey, Integer, Numeric, Unicode, func, select from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Session, relationship +from .account_base import AccountBase + class Employee(AccountBase): __tablename__ = "employees" @@ -47,8 +48,9 @@ class Employee(AccountBase): ) def create(self, db: Session): - code = db.query(func.max(AccountBase.code)).filter(AccountBase.type == self.type).one()[0] - self.code = 1 if code is None else code + 1 + self.code = db.execute( + select(func.coalesce(func.max(AccountBase.code), 0) + 1).filter(AccountBase.type == self.type) + ).scalar_one() self.name += f" ({str(self.code)})" db.add(self) return self diff --git a/brewman/brewman/models/emplyee_benefit.py b/brewman/brewman/models/emplyee_benefit.py index d05dcc0d..25e85bed 100644 --- a/brewman/brewman/models/emplyee_benefit.py +++ b/brewman/brewman/models/emplyee_benefit.py @@ -1,11 +1,12 @@ import uuid -from brewman.models.journal import Journal -from brewman.models.meta import Base from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import backref, relationship +from .journal import Journal +from .meta import Base + class EmployeeBenefit(Base): __tablename__ = "employee_benefit" diff --git a/brewman/brewman/models/fingerprint.py b/brewman/brewman/models/fingerprint.py index b9f0e020..07977a98 100644 --- a/brewman/brewman/models/fingerprint.py +++ b/brewman/brewman/models/fingerprint.py @@ -1,9 +1,10 @@ import uuid -from brewman.models.meta import Base from sqlalchemy import Column, DateTime, ForeignKey from sqlalchemy.dialects.postgresql import UUID +from .meta import Base + class Fingerprint(Base): __tablename__ = "fingerprints" diff --git a/brewman/brewman/models/incentive.py b/brewman/brewman/models/incentive.py index 03869fd7..573e49ff 100644 --- a/brewman/brewman/models/incentive.py +++ b/brewman/brewman/models/incentive.py @@ -1,11 +1,12 @@ import uuid -from brewman.models.journal import Journal -from brewman.models.meta import Base from sqlalchemy import Column, ForeignKey, Numeric from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import backref, relationship +from .journal import Journal +from .meta import Base + class Incentive(Base): __tablename__ = "incentives" diff --git a/brewman/brewman/models/inventory.py b/brewman/brewman/models/inventory.py index 97ed2d15..79df620c 100644 --- a/brewman/brewman/models/inventory.py +++ b/brewman/brewman/models/inventory.py @@ -1,11 +1,12 @@ import uuid -from brewman.models.meta import Base from sqlalchemy import Column, ForeignKey, Numeric, UniqueConstraint from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import relationship +from .meta import Base + class Inventory(Base): __tablename__ = "inventories" @@ -26,6 +27,7 @@ class Inventory(Base): discount = Column("discount", Numeric(precision=15, scale=5), nullable=False) voucher = relationship("Voucher", back_populates="inventories") + product = relationship("Product", back_populates="inventories") def __init__( self, diff --git a/brewman/brewman/models/journal.py b/brewman/brewman/models/journal.py index 7a90e9b8..d2e35074 100644 --- a/brewman/brewman/models/journal.py +++ b/brewman/brewman/models/journal.py @@ -1,11 +1,12 @@ import uuid -from brewman.models.meta import Base from sqlalchemy import Column, ForeignKey, Integer, Numeric from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import relationship +from .meta import Base + class Journal(Base): __tablename__ = "journals" diff --git a/brewman/brewman/models/login_history.py b/brewman/brewman/models/login_history.py index 5c391ef8..f890d99d 100644 --- a/brewman/brewman/models/login_history.py +++ b/brewman/brewman/models/login_history.py @@ -6,6 +6,7 @@ from datetime import datetime from sqlalchemy import Column, DateTime, UniqueConstraint from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship from sqlalchemy.schema import ForeignKey from .meta import Base @@ -22,7 +23,9 @@ class LoginHistory(Base): ForeignKey("auth_clients.client_id"), nullable=False, ) - date = Column("date", DateTime(timezone=True), nullable=False) + date = Column("date", DateTime(), nullable=False) + + user = relationship("User", back_populates="login_history") def __init__(self, user_id=None, client_id=None, date=None, id_=None): self.user_id = user_id diff --git a/brewman/brewman/models/permission.py b/brewman/brewman/models/permission.py index 5b8d78db..d42062b3 100644 --- a/brewman/brewman/models/permission.py +++ b/brewman/brewman/models/permission.py @@ -19,7 +19,7 @@ class Permission(Base): id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name = Column("name", Unicode(255), unique=True) - roles: List[Role] = relationship("Role", secondary=role_permission, backref="permissions") + roles: List[Role] = relationship("Role", secondary=role_permission, back_populates="permissions") def __init__(self, name=None, id_=None): self.name = name diff --git a/brewman/brewman/models/product.py b/brewman/brewman/models/product.py index 9eb918dd..1d4f8066 100644 --- a/brewman/brewman/models/product.py +++ b/brewman/brewman/models/product.py @@ -1,6 +1,5 @@ import uuid -from brewman.models.meta import Base from sqlalchemy import ( Boolean, Column, @@ -9,11 +8,16 @@ from sqlalchemy import ( Numeric, Unicode, UniqueConstraint, + case, func, + select, ) from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import Session, relationship +from .meta import Base + class Product(Base): __tablename__ = "products" @@ -40,10 +44,11 @@ class Product(Base): is_purchased = Column("is_purchased", Boolean, nullable=False) is_sold = Column("is_sold", Boolean, nullable=False) - batches = relationship("Batch", backref="product") - inventories = relationship("Inventory", backref="product") - recipes = relationship("Recipe", backref="product") - account = relationship("Account", primaryjoin="Account.id==Product.account_id", backref="products") + product_group = relationship("ProductGroup", back_populates="products") + batches = relationship("Batch", back_populates="product") + inventories = relationship("Inventory", back_populates="product") + recipes = relationship("Recipe", back_populates="product") + account = relationship("Account", primaryjoin="Account.id==Product.account_id", back_populates="products") def __init__( self, @@ -79,17 +84,20 @@ class Product(Base): self.id = id_ self.is_fixture = is_fixture - @property + @hybrid_property def full_name(self): - return "{0} ({1})".format(self.name, self.units) + return f"{self.name} ({self.units})" if self.units else self.name + + @full_name.expression + def full_name(cls): + return cls.name + case([(cls.units != "", " (" + cls.units + ")")], else_="") def create(self, db: Session): - code = db.query(func.max(Product.code)).one()[0] - self.code = 1 if code is None else code + 1 + self.code = db.execute(select(func.coalesce(func.max(Product.code), 0) + 1)).scalar_one() db.add(self) return self - def can_delete(self, advanced_delete): + def can_delete(self, advanced_delete: bool): if self.is_fixture: return False, f"{self.name} is a fixture and cannot be edited or deleted." if self.is_active: @@ -99,8 +107,8 @@ class Product(Base): return True, "" @classmethod - def query(cls, q, is_purchased=None, active=None, db=None): - query_ = db.query(cls) + def query(cls, q, is_purchased: bool = None, active: bool = None, db: Session = None): + query_ = select(cls) if active is not None: query_ = query_.filter(cls.is_active == active) if is_purchased is not None: @@ -109,7 +117,7 @@ class Product(Base): for item in q.split(): if item.strip() != "": query_ = query_.filter(cls.name.ilike(f"%{item}%")) - return query_ + return db.execute(query_).scalars().all() @classmethod def suspense(cls): diff --git a/brewman/brewman/models/product_group.py b/brewman/brewman/models/product_group.py index 5b4080d0..a983d414 100644 --- a/brewman/brewman/models/product_group.py +++ b/brewman/brewman/models/product_group.py @@ -1,10 +1,11 @@ import uuid -from brewman.models.meta import Base from sqlalchemy import Boolean, Column, Unicode from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship +from .meta import Base + class ProductGroup(Base): __tablename__ = "product_groups" @@ -13,7 +14,7 @@ class ProductGroup(Base): name = Column("name", Unicode(255), unique=True) is_fixture = Column("is_fixture", Boolean, nullable=False) - products = relationship("Product", backref="product_group") + products = relationship("Product", back_populates="product_group") def __init__(self, name=None, id_=None, is_fixture=False): self.name = name diff --git a/brewman/brewman/models/recipe.py b/brewman/brewman/models/recipe.py index f12fbd77..43643f77 100644 --- a/brewman/brewman/models/recipe.py +++ b/brewman/brewman/models/recipe.py @@ -2,11 +2,12 @@ import uuid from datetime import date -from brewman.models.meta import Base from sqlalchemy import Column, Date, ForeignKey, Numeric, Unicode from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship +from .meta import Base + class Recipe(Base): __tablename__ = "recipes" @@ -19,13 +20,14 @@ class Recipe(Base): sale_price = Column("sale_price", Numeric(precision=15, scale=2), nullable=False) notes = Column("notes", Unicode(255)) - valid_from = Column("valid_from", Date, nullable=False) - valid_to = Column("valid_to", Date, nullable=False) + valid_from = Column("valid_from", Date, nullable=True) + valid_till = Column("valid_till", Date, nullable=True) effective_from = Column("effective_from", Date, nullable=False) effective_to = Column("effective_to", Date) recipe_items = relationship("RecipeItem", backref="recipe") + product = relationship("Product", back_populates="recipes") def __init__( self, @@ -34,7 +36,7 @@ class Recipe(Base): cost_price=None, sale_price=None, valid_from=None, - valid_to=None, + valid_till=None, notes=None, effective_from=None, id_=None, @@ -44,7 +46,7 @@ class Recipe(Base): self.cost_price = cost_price self.sale_price = sale_price self.valid_from = valid_from - self.valid_to = valid_to + self.valid_till = valid_till self.notes = "" if notes is None else notes self.effective_from = date.today() if effective_from is None else effective_from self.effective_to = None diff --git a/brewman/brewman/models/recipe_item.py b/brewman/brewman/models/recipe_item.py index de270808..f4c64ddf 100644 --- a/brewman/brewman/models/recipe_item.py +++ b/brewman/brewman/models/recipe_item.py @@ -1,10 +1,11 @@ import uuid -from brewman.models.meta import Base from sqlalchemy import Column, ForeignKey, Integer, UniqueConstraint from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship +from .meta import Base + class RecipeItem(Base): __tablename__ = "recipe_items" diff --git a/brewman/brewman/models/role.py b/brewman/brewman/models/role.py index 2f99e7b0..fd5dc4c8 100644 --- a/brewman/brewman/models/role.py +++ b/brewman/brewman/models/role.py @@ -4,8 +4,10 @@ import uuid from sqlalchemy import Column, Unicode from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship from .meta import Base +from .role_permission import role_permission class Role(Base): @@ -14,6 +16,8 @@ class Role(Base): id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name = Column("name", Unicode(255), unique=True) + permissions = relationship("Permission", secondary=role_permission, back_populates="roles") + def __init__(self, name=None, id_=None): self.name = name self.id = id_ diff --git a/brewman/brewman/models/user.py b/brewman/brewman/models/user.py index 1cb5ae02..04154880 100644 --- a/brewman/brewman/models/user.py +++ b/brewman/brewman/models/user.py @@ -5,7 +5,7 @@ import uuid from hashlib import md5 from typing import List, Optional -from sqlalchemy import Boolean, Column, Unicode, desc +from sqlalchemy import Boolean, Column, Unicode, desc, select from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Session, relationship, synonym @@ -28,7 +28,9 @@ class User(Base): locked_out = Column("disabled", Boolean) roles: List[Role] = relationship("Role", secondary=user_role) - login_history: List[LoginHistory] = relationship("LoginHistory", order_by=desc(LoginHistory.date), backref="user") + login_history: List[LoginHistory] = relationship( + "LoginHistory", order_by=desc(LoginHistory.date), back_populates="user" + ) def _get_password(self): return self._password @@ -53,7 +55,7 @@ class User(Base): def auth(cls, name: str, password: str, db: Session) -> Optional[User]: if password is None: return None - user = db.query(User).filter(User.name.ilike(name)).first() + user = db.execute(select(cls).where(cls.name.ilike(name))).scalars().one_or_none() if not user: return None if user.password != encrypt(password) or user.locked_out: diff --git a/brewman/brewman/models/validations.py b/brewman/brewman/models/validations.py index 936699c6..af2d288a 100644 --- a/brewman/brewman/models/validations.py +++ b/brewman/brewman/models/validations.py @@ -3,9 +3,10 @@ from typing import Union import brewman.schemas.input as schema_in import brewman.schemas.voucher as schema -from brewman.models.voucher import Voucher from fastapi import HTTPException, status +from .voucher import Voucher + def check_journals_are_valid(voucher: Voucher): if len(voucher.journals) < 2: diff --git a/brewman/brewman/models/voucher.py b/brewman/brewman/models/voucher.py index ad7a4182..e4f86479 100644 --- a/brewman/brewman/models/voucher.py +++ b/brewman/brewman/models/voucher.py @@ -18,8 +18,8 @@ class Voucher(Base): is_reconciled = Column("is_reconciled", Boolean, nullable=False) reconcile_date = Column("reconcile_date", Date, nullable=False) is_starred = Column("is_starred", Boolean, nullable=False) - creation_date = Column("creation_date", DateTime(timezone=True), nullable=False) - last_edit_date = Column("last_edit_date", DateTime(timezone=True), nullable=False) + creation_date = Column("creation_date", DateTime(), nullable=False) + last_edit_date = Column("last_edit_date", DateTime(), nullable=False) _type = Column("voucher_type", Integer, nullable=False) user_id = Column("user_id", UUID(as_uuid=True), ForeignKey("auth_users.id"), nullable=False) posted = Column("is_posted", Boolean, nullable=False) diff --git a/brewman/brewman/routers/__init__.py b/brewman/brewman/routers/__init__.py index a793c724..fe6b0433 100644 --- a/brewman/brewman/routers/__init__.py +++ b/brewman/brewman/routers/__init__.py @@ -5,6 +5,7 @@ from datetime import date, timedelta from decimal import Decimal from typing import Optional +from sqlalchemy import select from sqlalchemy.orm import Session from ..models.db_setting import DbSetting @@ -13,7 +14,7 @@ from ..models.db_setting import DbSetting def get_lock_info(db: Session) -> (Optional[date], Optional[date]): start: Optional[date] finish: Optional[date] - data = db.query(DbSetting).filter(DbSetting.name == "Lock Info").first() + data = db.execute(select(DbSetting).where(DbSetting.name == "Lock Info")).scalars().one_or_none() if data is None: return None, None diff --git a/brewman/brewman/routers/account.py b/brewman/brewman/routers/account.py index 7ced4d54..652bfce1 100644 --- a/brewman/brewman/routers/account.py +++ b/brewman/brewman/routers/account.py @@ -7,12 +7,12 @@ from typing import List import brewman.schemas.account as schemas from fastapi import APIRouter, Depends, HTTPException, Security, status -from sqlalchemy import func +from sqlalchemy import func, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session, joinedload from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account import Account from ..models.account_base import AccountBase from ..models.account_type import AccountType @@ -26,112 +26,96 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=schemas.Account) def save( data: schemas.AccountIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["accounts"]), ) -> schemas.Account: try: - item = Account( - name=data.name, - type_=data.type, - is_starred=data.is_starred, - is_active=data.is_active, - is_reconcilable=data.is_reconcilable, - cost_centre_id=data.cost_centre.id_, - ).create(db) - db.commit() - return account_info(item) + with SessionFuture() as db: + item = Account( + name=data.name, + type_=data.type, + is_starred=data.is_starred, + is_active=data.is_active, + is_reconcilable=data.is_reconcilable, + cost_centre_id=data.cost_centre.id_, + ).create(db) + db.commit() + return account_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.put("/{id_}", response_model=schemas.Account) -def update( +def update_route( id_: uuid.UUID, data: schemas.AccountIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["accounts"]), ) -> schemas.Account: try: - item: Account = db.query(Account).filter(Account.id == id_).first() - if item.is_fixture: - raise HTTPException( - status_code=status.HTTP_423_LOCKED, - detail=f"{item.name} is a fixture and cannot be edited or deleted.", - ) - if not item.type == data.type: - item.code = Account.get_code(data.type, db) - item.type = data.type - item.name = data.name - item.is_active = data.is_active - item.is_reconcilable = data.is_reconcilable - item.is_starred = data.is_starred - item.cost_centre_id = data.cost_centre.id_ - db.commit() - return account_info(item) + with SessionFuture() as db: + item: Account = db.execute(select(Account).where(Account.id == id_)).scalar_one() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + if not item.type == data.type: + item.code = Account.get_code(data.type, db) + item.type = data.type + item.name = data.name + item.is_active = data.is_active + item.is_reconcilable = data.is_reconcilable + item.is_starred = data.is_starred + item.cost_centre_id = data.cost_centre.id_ + db.commit() + return account_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.delete("/{id_}", response_model=schemas.AccountBlank) -def delete( +def delete_route( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["accounts"]), ) -> schemas.AccountBlank: - account: Account = db.query(Account).filter(Account.id == id_).first() - can_delete, reason = account.can_delete("advanced-delete" in user.permissions) - if can_delete: - delete_with_data(account, db) - db.commit() - return account_blank() - else: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Cannot delete account because {reason}", - ) + with SessionFuture() as db: + account: Account = db.execute(select(Account).where(Account.id == id_)).scalar_one() + can_delete, reason = account.can_delete("advanced-delete" in user.permissions) + if can_delete: + delete_with_data(account, db) + db.commit() + return account_blank() + else: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Cannot delete account because {reason}", + ) @router.get("", response_model=schemas.AccountBlank) def show_blank( - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["accounts"]), ) -> schemas.AccountBlank: return account_blank() @router.get("/list", response_model=List[schemas.Account]) -async def show_list(db: Session = Depends(get_db), user: UserToken = Depends(get_user)) -> List[schemas.Account]: - return [ - account_info(item) - for item in db.query(Account).order_by(Account.type).order_by(Account.name).order_by(Account.code).all() - ] +async def show_list(user: UserToken = Depends(get_user)) -> List[schemas.Account]: + with SessionFuture() as db: + return [ + account_info(item) + for item in db.execute(select(Account).order_by(Account.type).order_by(Account.name).order_by(Account.code)) + .scalars() + .all() + ] @router.get("/query") @@ -141,15 +125,15 @@ async def show_term( r: bool = None, a: bool = None, c: int = None, - db: Session = Depends(get_db), current_user: UserToken = Depends(get_user), ): count = c list_ = [] - for index, item in enumerate(AccountBase.query(q, t, r, a, db)): - list_.append({"id": item.id, "name": item.name}) - if count is not None and index == count - 1: - break + with SessionFuture() as db: + for index, item in enumerate(AccountBase.query(q, t, r, a, db)): + list_.append({"id": item.id, "name": item.name}) + if count is not None and index == count - 1: + break return list_ @@ -157,34 +141,35 @@ async def show_term( async def show_balance( id_: uuid.UUID, d: str = None, - db: Session = Depends(get_db), user: UserToken = Depends(get_user), ): date = None if d is None or d == "" else datetime.strptime(d, "%d-%b-%Y") - return {"date": balance(id_, date, db), "total": balance(id_, None, db)} + with SessionFuture() as db: + return {"date": balance(id_, date, db), "total": balance(id_, None, db)} @router.get("/{id_}", response_model=schemas.Account) def show_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["accounts"]), ) -> schemas.Account: - item: Account = db.query(Account).filter(Account.id == id_).first() - return account_info(item) + with SessionFuture() as db: + item: Account = db.execute(select(Account).where(Account.id == id_)).scalar_one() + return account_info(item) def balance(id_: uuid.UUID, date, db: Session) -> Decimal: - account = db.query(AccountBase).filter(AccountBase.id == id_).first() + account = db.execute(select(AccountBase).where(AccountBase.id == id_)).scalar_one() if not account.type_object.balance_sheet: return 0 - bal = db.query(func.sum(Journal.amount * Journal.debit)).join(Journal.voucher) + bal = select(func.sum(Journal.amount * Journal.debit)).join(Journal.voucher) if date is not None: - bal = bal.filter(Voucher.date <= date) + bal = bal.where(Voucher.date <= date) - bal = bal.filter(Voucher.type != VoucherType.by_name("Issue").id).filter(Journal.account_id == id_).scalar() - return 0 if bal is None else bal + bal = bal.where(Voucher.type != VoucherType.by_name("Issue").id).where(Journal.account_id == id_) + result = db.execute(bal).scalar() + return 0 if result is None else result def account_info(item: Account) -> schemas.Account: @@ -217,11 +202,14 @@ def account_blank() -> schemas.AccountBlank: def delete_with_data(account: Account, db: Session) -> None: - suspense_account = db.query(Account).filter(Account.id == Account.suspense()).first() + suspense_account = db.execute(select(Account).where(Account.id == Account.suspense())).scalar_one() query: List[Voucher] = ( - db.query(Voucher) - .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) - .filter(Voucher.journals.any(Journal.account_id == account.id)) + db.execute( + select(Voucher) + .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) + .where(Voucher.journals.any(Journal.account_id == account.id)) + ) + .scalars() .all() ) diff --git a/brewman/brewman/routers/attendance.py b/brewman/brewman/routers/attendance.py index 2f47ac75..71e1bb78 100644 --- a/brewman/brewman/routers/attendance.py +++ b/brewman/brewman/routers/attendance.py @@ -2,14 +2,14 @@ from datetime import date, datetime, timedelta import brewman.schemas.voucher as schemas -from fastapi import APIRouter, Depends, HTTPException, Request, Security, status -from sqlalchemy import or_ +from fastapi import APIRouter, HTTPException, Request, Security, status +from sqlalchemy import or_, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_date, set_date -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.attendance import Attendance from ..models.employee import Employee from ..routers.fingerprint import get_prints @@ -19,15 +19,6 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.Attendance) def attendance_blank(request: Request, user: UserToken = Security(get_user, scopes=["attendance"])): return {"date": get_date(request.session), "body": []} @@ -37,40 +28,47 @@ def attendance_blank(request: Request, user: UserToken = Security(get_user, scop def attendance_date( date_: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["attendance"]), ): set_date(date_, request.session) - return { - "date": date_, - "body": attendance_date_report(datetime.strptime(date_, "%d-%b-%Y"), db), - } + with SessionFuture() as db: + return { + "date": date_, + "body": attendance_date_report(datetime.strptime(date_, "%d-%b-%Y"), db), + } def attendance_date_report(date_: date, db: Session): body = [] employees = ( - db.query(Employee) - .filter(Employee.joining_date <= date_) - .filter( - or_( - Employee.is_active, - Employee.leaving_date >= date_, + db.execute( + select(Employee) + .where( + Employee.joining_date <= date_, + or_( + Employee.is_active, + Employee.leaving_date >= date_, + ), ) + .order_by(Employee.cost_centre_id, Employee.designation, Employee.name) ) - .order_by(Employee.cost_centre_id) - .order_by(Employee.designation) - .order_by(Employee.name) + .scalars() .all() ) + for item in employees: att = ( - db.query(Attendance) - .filter(Attendance.employee_id == item.id) - .filter(Attendance.date == date_) - .filter(Attendance.is_valid == True) # noqa: E712 - .first() + db.execute( + select(Attendance).where( + Attendance.employee_id == item.id, + Attendance.date == date_, + Attendance.is_valid == True, # noqa: E712 + ) + ) + .scalars() + .one_or_none() ) + att = 0 if att is None else att.attendance_type prints, hours_worked, full_day = get_prints(item.id, date_, db) @@ -94,31 +92,27 @@ def attendance_date_report(date_: date, db: Session): def save( date_: str, data: schemas.Attendance, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["attendance"]), ): try: att_date = datetime.strptime(date_, "%d-%b-%Y").date() - for item in data.body: - if item.attendance_type.id_ != 0: - attendance = Attendance( - employee_id=item.id_, - date=att_date, - attendance_type=item.attendance_type.id_, - user_id=user.id_, - ) - attendance.create(db) - db.commit() - return {"date": date_, "body": attendance_date_report(att_date, db)} + with SessionFuture() as db: + for item in data.body: + if item.attendance_type.id_ != 0: + attendance = Attendance( + employee_id=item.id_, + date=att_date, + attendance_type=item.attendance_type.id_, + user_id=user.id_, + ) + attendance.create(db) + db.commit() + return {"date": date_, "body": attendance_date_report(att_date, db)} except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def date_range(start: date, stop: date, step=timedelta(days=1), inclusive=False): diff --git a/brewman/brewman/routers/attendance_report.py b/brewman/brewman/routers/attendance_report.py index 70f6cac4..8f570469 100644 --- a/brewman/brewman/routers/attendance_report.py +++ b/brewman/brewman/routers/attendance_report.py @@ -3,12 +3,12 @@ import io from datetime import date, datetime -from fastapi import APIRouter, Depends +from fastapi import APIRouter from fastapi.responses import StreamingResponse -from sqlalchemy import or_ +from sqlalchemy import or_, select from sqlalchemy.orm import Session -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.attendance import Attendance from ..models.attendance_type import AttendanceType from ..models.employee import Employee @@ -18,33 +18,24 @@ from .attendance import date_range router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("") def get_report( s: str = None, f: str = None, - db: Session = Depends(get_db), # user: UserToken = Security(get_user, scopes=["attendance"]) ## removed as jwt headers are a pain in the ass ): try: - output = io.StringIO() - attendance_record( - datetime.strptime(s, "%d-%b-%Y"), - datetime.strptime(f, "%d-%b-%Y"), - output, - db, - ) - headers = {"Content-Disposition": "attachment; filename = attendance-record.csv"} - output.seek(0) - return StreamingResponse(output, media_type="text/csv", headers=headers) + with SessionFuture() as db: + output = io.StringIO() + attendance_record( + datetime.strptime(s, "%d-%b-%Y"), + datetime.strptime(f, "%d-%b-%Y"), + output, + db, + ) + headers = {"Content-Disposition": "attachment; filename = attendance-record.csv"} + output.seek(0) + return StreamingResponse(output, media_type="text/csv", headers=headers) finally: pass # output.close() @@ -57,14 +48,15 @@ def attendance_record(start_date: date, finish_date: date, output, db: Session): not_set = AttendanceType.by_id(0) employees = ( - db.query(Employee) - .filter(Employee.joining_date <= finish_date) - .filter(or_(Employee.is_active, Employee.leaving_date >= start_date)) - .order_by(Employee.cost_centre_id) - .order_by(Employee.designation) - .order_by(Employee.name) + db.execute( + select(Employee) + .where(Employee.joining_date <= finish_date, or_(Employee.is_active, Employee.leaving_date >= start_date)) + .order_by(Employee.cost_centre_id, Employee.designation, Employee.name) + ) + .scalars() .all() ) + writer = csv.writer(output) writer.writerow(header) for employee in employees: @@ -79,11 +71,15 @@ def attendance_record(start_date: date, finish_date: date, output, db: Session): row_value = ["", "", "", "", employee.salary, employee.points] for date_ in date_range(start_date, finish_date, inclusive=True): att = ( - db.query(Attendance) - .filter(Attendance.employee_id == employee.id) - .filter(Attendance.date == date_) - .filter(Attendance.is_valid == True) # noqa: E712 - .first() + db.execute( + select(Attendance).where( + Attendance.employee_id == employee.id, + Attendance.date == date_, + Attendance.is_valid == True, # noqa: E712 + ) + ) + .scalars() + .one_or_none() ) att = not_set if att is None else AttendanceType.by_id(att.attendance_type) row_display.append(att.name) diff --git a/brewman/brewman/routers/auth/__init__.py b/brewman/brewman/routers/auth/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/brewman/brewman/routers/auth/client.py b/brewman/brewman/routers/auth/client.py deleted file mode 100644 index 00db0d50..00000000 --- a/brewman/brewman/routers/auth/client.py +++ /dev/null @@ -1,115 +0,0 @@ -import uuid - -from typing import List - -import brewman.schemas.client as schemas - -from fastapi import APIRouter, Depends, HTTPException, Security, status -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.orm import Session - -from ...core.security import get_current_active_user as get_user -from ...db.session import SessionLocal -from ...models.client import Client -from ...models.login_history import LoginHistory -from ...schemas.user import UserToken - - -router = APIRouter() - - -# Dependency -def get_db(): - try: - db = SessionLocal() - yield db - finally: - db.close() - - -@router.put("/{id_}") -def update( - id_: uuid.UUID, - data: schemas.ClientIn, - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["clients"]), -): - try: - item: Client = db.query(Client).filter(Client.id == id_).first() - item.enabled = data.enabled - if item.enabled: - item.otp = None - item.name = data.name - db.commit() - return {} - except SQLAlchemyError as e: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), - ) - except Exception: - db.rollback() - raise - - -@router.delete("/{id_}") -def delete( - id_: uuid.UUID, - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["clients"]), -): - try: - item: Client = db.query(Client).filter(Client.id == id_).first() - db.execute(LoginHistory.__table__.delete(LoginHistory.client_id == item.id)) - db.delete(item) - db.commit() - return {} - except SQLAlchemyError as e: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), - ) - except Exception: - db.rollback() - raise - - -@router.get("/list", response_model=List[schemas.ClientList]) -async def show_list( - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["clients"]), -) -> List[schemas.ClientList]: - list_ = db.query(Client).order_by(Client.name).all() - clients = [] - for item in list_: - clients.append( - schemas.ClientList( - id=item.id, - code=item.code, - name=item.name, - enabled=item.enabled, - otp=item.otp, - creationDate=item.creation_date, - lastUser=item.login_history[0].user.name if len(item.login_history) else "None", - lastDate=item.login_history[0].date if len(item.login_history) else None, - ) - ) - return clients - - -@router.get("/{id_}") -def show_id( - id_: uuid.UUID, - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["clients"]), -): - item: Client = db.query(Client).filter(Client.id == id_).first() - return { - "id": item.id, - "code": item.code, - "name": item.name, - "enabled": item.enabled, - "otp": item.otp, - } diff --git a/brewman/brewman/routers/auth/user.py b/brewman/brewman/routers/auth/user.py deleted file mode 100644 index 83284d8a..00000000 --- a/brewman/brewman/routers/auth/user.py +++ /dev/null @@ -1,218 +0,0 @@ -import uuid - -from typing import List - -import brewman.schemas.user as schemas - -from fastapi import APIRouter, Depends, HTTPException, Security, status -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.orm import Session - -from ...core.security import get_current_active_user as get_user -from ...db.session import SessionLocal -from ...models.role import Role -from ...models.user import User -from ...schemas.user import UserToken - - -router = APIRouter() - - -# Dependency -def get_db(): - try: - db = SessionLocal() - yield db - finally: - db.close() - - -@router.post("", response_model=schemas.User) -def save( - data: schemas.UserIn, - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["users"]), -) -> schemas.User: - try: - item = User(name=data.name, password=data.password, locked_out=data.locked_out) - db.add(item) - add_roles(item, data.roles, db) - db.commit() - return user_info(item, db, user) - except SQLAlchemyError as e: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), - ) - except Exception: - db.rollback() - raise - - -@router.get("/me", response_model=schemas.User) -def show_me( - db: Session = Depends(get_db), - user: UserToken = Depends(get_user), -) -> schemas.User: - item = db.query(User).filter(User.id == user.id_).first() - return user_info(item, db, user) - - -@router.put("/me", response_model=schemas.User) -def update_me( - data: schemas.UserIn, - db: Session = Depends(get_db), - user: UserToken = Depends(get_user), -) -> schemas.User: - try: - item: User = db.query(User).filter(User.id == user.id_).first() - if "users" in user.permissions: - item.name = data.name - item.locked_out = data.locked_out - add_roles(item, data.roles, db) - if data.password and item.password != data.password: - item.password = data.password - db.commit() - return user_info(item, db, user) - except SQLAlchemyError as e: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), - ) - except Exception: - db.rollback() - raise - - -@router.put("/{id_}", response_model=schemas.User) -def update( - id_: uuid.UUID, - data: schemas.UserIn, - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["users"]), -) -> schemas.User: - try: - item: User = db.query(User).filter(User.id == id_).first() - item.name = data.name - if data.password and item.password != data.password: - item.password = data.password - item.locked_out = data.locked_out - add_roles(item, data.roles, db) - db.commit() - return user_info(item, db, user) - except SQLAlchemyError as e: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), - ) - except Exception: - db.rollback() - raise - - -def add_roles(user: User, roles: List[schemas.RoleItem], db: Session) -> None: - for role in roles: - ug = [g for g in user.roles if g.id == role.id_] - ug = None if len(ug) == 0 else ug[0] - if role.enabled and ug is None: - user.roles.append(db.query(Role).filter(Role.id == role.id_).one()) - elif not role.enabled and ug: - user.roles.remove(ug) - - -@router.delete("/{id_}") -def delete( - id_: uuid.UUID, - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["users"]), -): - try: - item: User = db.query(User).filter(User.id == id_).first() - if item is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="User not found", - ) - else: - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="User deletion not implemented", - ) - except Exception: - db.rollback() - raise - - -@router.get("") -def show_blank( - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["users"]), -): - return user_blank(db) - - -@router.get("/list", response_model=List[schemas.UserList]) -async def show_list( - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["users"]), -) -> List[schemas.UserList]: - return [ - schemas.UserList( - id=item.id, - name=item.name, - lockedOut=item.locked_out, - roles=[p.name for p in sorted(item.roles, key=lambda p: p.name)], - lastDevice=item.login_history[0].client.name if len(item.login_history) else "None", - lastDate=item.login_history[0].date if len(item.login_history) else None, - ) - for item in db.query(User).order_by(User.name).all() - ] - - -@router.get("/active") -async def show_active(db: Session = Depends(get_db), user: UserToken = Depends(get_user)): - return [ - {"name": item.name} - for item in db.query(User).filter(User.locked_out == False).order_by(User.name) # noqa: E712 - ] - - -@router.get("/{id_}", response_model=schemas.User) -def show_id( - id_: uuid.UUID, - db: Session = Depends(get_db), - user: UserToken = Security(get_user, scopes=["users"]), -) -> schemas.User: - item = db.query(User).filter(User.id == id_).first() - return user_info(item, db, user) - - -def user_info(item: User, db: Session, user: UserToken) -> schemas.User: - return schemas.User( - id=item.id, - name=item.name, - password="", - lockedOut=item.locked_out, - roles=[ - schemas.RoleItem( - id=r.id, - name=r.name, - enabled=True if r in item.roles else False, - ) - for r in db.query(Role).order_by(Role.name).all() - ] - if "advanced-delete" in user.permissions - else [], - ) - - -def user_blank(db: Session) -> schemas.UserBlank: - return schemas.UserBlank( - name="", - password="", - lockedOut=False, - roles=[schemas.RoleItem(id=r.id, name=r.name, enabled=False) for r in db.query(Role).order_by(Role.name).all()], - ) diff --git a/brewman/brewman/routers/batch.py b/brewman/brewman/routers/batch.py index 76b7b525..eebda48e 100644 --- a/brewman/brewman/routers/batch.py +++ b/brewman/brewman/routers/batch.py @@ -1,10 +1,9 @@ import datetime from fastapi import APIRouter, Depends -from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.batch import Batch from ..schemas.user import UserToken @@ -12,45 +11,36 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("") def batch_term( q: str, c: int = None, d: str = None, - db: Session = Depends(get_db), current_user: UserToken = Depends(get_user), ): date = None if not d else datetime.datetime.strptime(d, "%d-%b-%Y") list_ = [] - for index, item in enumerate(Batch.list(q, include_nil=False, date=date, db=db)): - text = ( - f"{item.product.name} ({item.product.units}) {item.quantity_remaining:.2f}@" - f"{item.rate:.2f} from {item.name.strftime('%d-%b-%Y')}" - ) - list_.append( - { - "id": item.id, - "name": text, - "quantityRemaining": round(item.quantity_remaining, 2), - "rate": round(item.rate, 2), - "tax": round(item.tax, 5), - "discount": round(item.discount, 5), - "product": { - "id": item.product.id, - "name": item.product.name, - "units": item.product.units, - }, - } - ) - if c is not None and index == c - 1: - break - return list_ + with SessionFuture() as db: + for index, item in enumerate(Batch.list(q, include_nil=False, date=date, db=db)): + text = ( + f"{item.product.name} ({item.product.units}) {item.quantity_remaining:.2f}@" + f"{item.rate:.2f} from {item.name.strftime('%d-%b-%Y')}" + ) + list_.append( + { + "id": item.id, + "name": text, + "quantityRemaining": round(item.quantity_remaining, 2), + "rate": round(item.rate, 2), + "tax": round(item.tax, 5), + "discount": round(item.discount, 5), + "product": { + "id": item.product.id, + "name": item.product.name, + "units": item.product.units, + }, + } + ) + if c is not None and index == c - 1: + break + return list_ diff --git a/brewman/brewman/routers/client.py b/brewman/brewman/routers/client.py new file mode 100644 index 00000000..b2650efe --- /dev/null +++ b/brewman/brewman/routers/client.py @@ -0,0 +1,98 @@ +import uuid + +from typing import List + +import brewman.schemas.client as schemas + +from fastapi import APIRouter, HTTPException, Security, status +from sqlalchemy import delete, select +from sqlalchemy.exc import SQLAlchemyError + +from ..core.security import get_current_active_user as get_user +from ..db.session import SessionFuture +from ..models.client import Client +from ..models.login_history import LoginHistory +from ..schemas.user import UserToken + + +router = APIRouter() + + +@router.put("/{id_}") +def update_route( + id_: uuid.UUID, + data: schemas.ClientIn, + user: UserToken = Security(get_user, scopes=["clients"]), +): + try: + with SessionFuture() as db: + item: Client = db.execute(select(Client).where(Client.id == id_)).scalar_one() + item.enabled = data.enabled + if item.enabled: + item.otp = None + item.name = data.name + db.commit() + return {} + except SQLAlchemyError as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +@router.delete("/{id_}") +def delete_route( + id_: uuid.UUID, + user: UserToken = Security(get_user, scopes=["clients"]), +): + try: + with SessionFuture() as db: + item: Client = db.execute(select(Client).where(Client.id == id_)).scalar_one() + db.execute(delete(LoginHistory).where(LoginHistory.client_id == item.id)) + db.delete(item) + db.commit() + return {} + except SQLAlchemyError as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +@router.get("/list", response_model=List[schemas.ClientList]) +async def show_list( + user: UserToken = Security(get_user, scopes=["clients"]), +) -> List[schemas.ClientList]: + with SessionFuture() as db: + list_ = db.execute(select(Client).order_by(Client.name)).scalars().all() + clients = [] + for item in list_: + clients.append( + schemas.ClientList( + id=item.id, + code=item.code, + name=item.name, + enabled=item.enabled, + otp=item.otp, + creationDate=item.creation_date, + lastUser=item.login_history[0].user.name if len(item.login_history) else "None", + lastDate=item.login_history[0].date if len(item.login_history) else None, + ) + ) + return clients + + +@router.get("/{id_}") +def show_id( + id_: uuid.UUID, + user: UserToken = Security(get_user, scopes=["clients"]), +): + with SessionFuture() as db: + item: Client = db.execute(select(Client).where(Client.id == id_)).scalar_one() + return { + "id": item.id, + "code": item.code, + "name": item.name, + "enabled": item.enabled, + "otp": item.otp, + } diff --git a/brewman/brewman/routers/cost_centre.py b/brewman/brewman/routers/cost_centre.py index 54a5b33a..dfbfc69d 100644 --- a/brewman/brewman/routers/cost_centre.py +++ b/brewman/brewman/routers/cost_centre.py @@ -5,126 +5,111 @@ from typing import List import brewman.schemas.cost_centre as schemas from fastapi import APIRouter, Depends, HTTPException, Security, status +from sqlalchemy import delete, select, update from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture +from ..models.account_base import AccountBase from ..models.cost_centre import CostCentre +from ..models.journal import Journal from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db(): - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=schemas.CostCentre) def save( data: schemas.CostCentreIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["cost-centres"]), ) -> schemas.CostCentre: try: - item = CostCentre(name=data.name) - db.add(item) - db.commit() - return cost_centre_info(item) + with SessionFuture() as db: + item = CostCentre(name=data.name) + db.add(item) + db.commit() + return cost_centre_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.put("/{id_}", response_model=schemas.CostCentre) -def update( +def update_route( id_: uuid.UUID, data: schemas.CostCentreIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["cost-centres"]), ) -> schemas.CostCentre: try: - item = db.query(CostCentre).filter(CostCentre.id == id_).first() - if item.is_fixture: - raise HTTPException( - status_code=status.HTTP_423_LOCKED, - detail=f"{item.name} is a fixture and cannot be edited or deleted.", - ) - item.name = data.name - db.commit() - return cost_centre_info(item) + with SessionFuture() as db: + item = db.execute(select(CostCentre).where(CostCentre.id == id_)).scalar_one() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + item.name = data.name + db.commit() + return cost_centre_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.delete("/{id_}", response_model=schemas.CostCentreBlank) -def delete( +def delete_route( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["cost-centres"]), ): - try: - item: CostCentre = db.query(CostCentre).filter(CostCentre.id == id_).first() - - if item is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Cost Centre not found", - ) + with SessionFuture() as db: + item: CostCentre = db.execute(select(CostCentre).where(CostCentre.id == id_)).scalar_one() if item.is_fixture: raise HTTPException( status_code=status.HTTP_423_LOCKED, detail=f"{item.name} is a fixture and cannot be edited or deleted.", ) - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Cost Centre deletion not implemented", + db.execute( + update(AccountBase) + .where(AccountBase.cost_centre_id == item.id) + .values(cost_centre_id=CostCentre.cost_centre_overall()) ) - except Exception: - db.rollback() - raise + db.execute( + update(Journal) + .where(Journal.cost_centre_id == item.id) + .values(cost_centre_id=CostCentre.cost_centre_overall()) + ) + db.execute(delete(CostCentre).where(CostCentre.id == item.id)) + db.commit() @router.get("", response_model=schemas.CostCentreBlank) def show_blank( - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["cost-centres"]), ) -> schemas.CostCentreBlank: return cost_centre_blank() @router.get("/list", response_model=List[schemas.CostCentre]) -async def show_list(db: Session = Depends(get_db), user: UserToken = Depends(get_user)) -> List[schemas.CostCentre]: - return [cost_centre_info(item) for item in db.query(CostCentre).order_by(CostCentre.name).all()] +async def show_list(user: UserToken = Depends(get_user)) -> List[schemas.CostCentre]: + with SessionFuture() as db: + return [ + cost_centre_info(item) for item in db.execute(select(CostCentre).order_by(CostCentre.name)).scalars().all() + ] @router.get("/{id_}", response_model=schemas.CostCentre) def show_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["cost-centres"]), ) -> schemas.CostCentre: - item = db.query(CostCentre).filter(CostCentre.id == id_).first() - return cost_centre_info(item) + with SessionFuture() as db: + item = db.execute(select(CostCentre).where(CostCentre.id == id_)).scalar_one() + return cost_centre_info(item) def cost_centre_info(item: CostCentre) -> schemas.CostCentre: diff --git a/brewman/brewman/routers/credit_salary.py b/brewman/brewman/routers/credit_salary.py index 72cbf377..5fe4efa6 100644 --- a/brewman/brewman/routers/credit_salary.py +++ b/brewman/brewman/routers/credit_salary.py @@ -1,13 +1,13 @@ from calendar import monthrange from datetime import date, datetime -from fastapi import APIRouter, Body, Depends, HTTPException, Security, status -from sqlalchemy import or_ +from fastapi import APIRouter, Body, HTTPException, Security, status +from sqlalchemy import or_, select from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_first_day, get_last_day -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account import Account from ..models.attendance import Attendance from ..models.attendance_type import AttendanceType @@ -21,39 +21,29 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("") def credit_salary( month: str = Body(..., embed=True), - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["attendance"]), ): month = datetime.strptime(month, "%d-%b-%Y").date() - start_date = get_first_day(month) finish_date = get_last_day(month) - voucher = Voucher( - date=finish_date, - narration="Auto Generated Salary Entry", - user_id=user.id_, - type_=VoucherType.by_name("Journal"), - posted=True, - poster_id=user.id_, - ) - db.add(voucher) - for item in salary_journals(start_date, finish_date, db): - voucher.journals.append(item) - db.add(item) - db.commit() - return {"message": "Salary Entry created"} + with SessionFuture() as db: + voucher = Voucher( + date=finish_date, + narration="Auto Generated Salary Entry", + user_id=user.id_, + type_=VoucherType.by_name("Journal"), + posted=True, + poster_id=user.id_, + ) + db.add(voucher) + for item in salary_journals(start_date, finish_date, db): + voucher.journals.append(item) + db.add(item) + db.commit() + return {"message": "Salary Entry created"} def salary_journals(start_date: date, finish_date: date, db: Session): @@ -61,21 +51,26 @@ def salary_journals(start_date: date, finish_date: date, db: Session): amount = 0 journals = [] employees = ( - db.query(Employee) - .filter(Employee.joining_date <= finish_date) - .filter(or_(Employee.is_active, Employee.leaving_date >= start_date)) - .order_by(Employee.cost_centre_id) - .order_by(Employee.designation) - .order_by(Employee.name) + db.execute( + select(Employee) + .where(Employee.joining_date <= finish_date, or_(Employee.is_active, Employee.leaving_date >= start_date)) + .order_by(Employee.cost_centre_id, Employee.designation, Employee.name) + ) + .scalars() .all() ) + for employee in employees: att = ( - db.query(Attendance) - .filter(Attendance.employee_id == employee.id) - .filter(Attendance.date >= start_date) - .filter(Attendance.date <= finish_date) - .filter(Attendance.is_valid == True) # noqa: E712 + db.execute( + select(Attendance).where( + Attendance.employee_id == employee.id, + Attendance.date >= start_date, + Attendance.date <= finish_date, + Attendance.is_valid == True, # noqa: E712 + ) + ) + .scalars() .all() ) att = sum(map(lambda x: AttendanceType.by_id(x.attendance_type).value, att)) @@ -90,7 +85,7 @@ def salary_journals(start_date: date, finish_date: date, db: Session): cost_centre_id=employee.cost_centre_id, ) ) - salary = db.query(Account).filter(Account.id == Account.salary_id()).first() + salary = db.execute(select(Account).where(Account.id == Account.salary_id())).scalar_one() if amount == 0: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, diff --git a/brewman/brewman/routers/db_image.py b/brewman/brewman/routers/db_image.py index 8105db01..eb16b21c 100644 --- a/brewman/brewman/routers/db_image.py +++ b/brewman/brewman/routers/db_image.py @@ -5,34 +5,27 @@ from typing import List import brewman.schemas.voucher as output -from fastapi import APIRouter, Depends +from fastapi import APIRouter from fastapi.responses import StreamingResponse +from sqlalchemy import select from sqlalchemy.orm import Session -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.db_image import DbImage router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("/{id_}/{type_}") -def db_image(id_: uuid.UUID, type_: str, db: Session = Depends(get_db)): - item = db.query(DbImage).filter(DbImage.id == id_).first() - if type_ == "thumbnail": - item = BytesIO(item.thumbnail) - else: - item = BytesIO(item.image) - return StreamingResponse(item, media_type="image/jpeg") +def db_image(id_: uuid.UUID, type_: str): + with SessionFuture() as db: + item = db.execute(select(DbImage).where(DbImage.id == id_)).scalar_one() + if type_ == "thumbnail": + item = BytesIO(item.thumbnail) + else: + item = BytesIO(item.image) + return StreamingResponse(item, media_type="image/jpeg") def save_files(voucher_id: uuid.UUID, i: List[bytes], t: List[bytes], db: Session): @@ -52,7 +45,7 @@ def update_files( i = i or [] t = t or [] old = [f.id_ for f in data if f.id_] - images = db.query(DbImage).filter(DbImage.resource_id == voucher_id).all() + images = db.execute(select(DbImage).where(DbImage.resource_id == voucher_id)).scalars().all() for image in [i for i in images if i.id not in old]: db.delete(image) for index, value in enumerate(i): diff --git a/brewman/brewman/routers/db_integrity.py b/brewman/brewman/routers/db_integrity.py index e8005eb0..09854e8b 100644 --- a/brewman/brewman/routers/db_integrity.py +++ b/brewman/brewman/routers/db_integrity.py @@ -1,9 +1,9 @@ -from fastapi import APIRouter, Depends, Security -from sqlalchemy import desc, distinct, func, over +from fastapi import APIRouter, Security +from sqlalchemy import delete, desc, distinct, func, over, select from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.attendance import Attendance from ..schemas.user import UserToken @@ -11,56 +11,48 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("") -def post_check_db(db: Session = Depends(get_db), user: UserToken = Security(get_user)): +def post_check_db(user: UserToken = Security(get_user)): info = {} - duplicate_attendances = get_duplicate_attendances(db) - if duplicate_attendances > 0: - fix_duplicate_attendances(db) - info["attendanceCount"] = duplicate_attendances - db.commit() - return info + with SessionFuture() as db: + duplicate_attendances = get_duplicate_attendances(db) + if duplicate_attendances > 0: + fix_duplicate_attendances(db) + info["attendanceCount"] = duplicate_attendances + db.commit() + return info def get_duplicate_attendances(db: Session) -> int: sub_query = ( - db.query( + select( over( distinct(func.first_value(Attendance.id)), partition_by=[Attendance.employee_id, Attendance.date], ) ) - .filter(Attendance.is_valid == True) # noqa: E712 + .where(Attendance.is_valid == True) # noqa: E712 .subquery() ) - query = ( - db.query(func.count(Attendance.id)) - .filter(~Attendance.id.in_(sub_query)) - .filter(Attendance.is_valid == True) # noqa: E712 + query = db.execute( + select(func.count(Attendance.id)).where( + ~Attendance.id.in_(sub_query), Attendance.is_valid == True # noqa: E712 + ) ) return query.scalar() def fix_duplicate_attendances(db: Session) -> None: sub = ( - db.query( + select( over( distinct(func.first_value(Attendance.id)), partition_by=[Attendance.employee_id, Attendance.date], order_by=desc(Attendance.creation_date), ) ) - .filter(Attendance.is_valid == True) # noqa: E712 + .where(Attendance.is_valid == True) # noqa: E712 .subquery() ) - db.query(Attendance).filter(~Attendance.id.in_(sub)).filter(Attendance.is_valid == True).delete(False) # noqa: E712 + db.execute(delete(Attendance).where(~Attendance.id.in_(sub), Attendance.is_valid == True)) # noqa: E712 diff --git a/brewman/brewman/routers/employee.py b/brewman/brewman/routers/employee.py index 3687705b..50aaf960 100644 --- a/brewman/brewman/routers/employee.py +++ b/brewman/brewman/routers/employee.py @@ -6,12 +6,12 @@ from typing import List import brewman.schemas.employee as schemas from fastapi import APIRouter, Depends, HTTPException, Security, status -from sqlalchemy import desc +from sqlalchemy import desc, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session, joinedload from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account import Account from ..models.cost_centre import CostCentre from ..models.employee import Employee @@ -24,156 +24,142 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db(): - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=schemas.Employee) def save( data: schemas.EmployeeIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["employees"]), ) -> schemas.Employee: try: - item = Employee( - name=data.name, - is_starred=data.is_starred, - is_active=data.is_active, - cost_centre_id=data.cost_centre.id_, - designation=data.designation, - salary=data.salary, - points=data.points, - joining_date=data.joining_date, - leaving_date=None if data.is_active else data.leaving_date, - ).create(db) - db.commit() - return employee_info(item) + with SessionFuture() as db: + item = Employee( + name=data.name, + is_starred=data.is_starred, + is_active=data.is_active, + cost_centre_id=data.cost_centre.id_, + designation=data.designation, + salary=data.salary, + points=data.points, + joining_date=data.joining_date, + leaving_date=None if data.is_active else data.leaving_date, + ).create(db) + db.commit() + return employee_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.put("/{id_}", response_model=schemas.Employee) -def update( +def update_route( id_: uuid.UUID, data: schemas.EmployeeIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["employees"]), ) -> schemas.Employee: try: - item: Employee = db.query(Employee).filter(Employee.id == id_).first() - if item.is_fixture: - raise HTTPException( - status_code=status.HTTP_423_LOCKED, - detail=f"{item.name} is a fixture and cannot be edited or deleted.", - ) - item.name = data.name - item.cost_centre_id = data.cost_centre.id_ - item.designation = data.designation - item.salary = data.salary - item.points = data.points - item.joining_date = data.joining_date - item.is_starred = data.is_starred - item.is_active = data.is_active - item.leaving_date = data.leaving_date - db.commit() - return employee_info(item) + with SessionFuture() as db: + item: Employee = db.execute(select(Employee).where(Employee.id == id_)).scalar_one() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + item.name = data.name + item.cost_centre_id = data.cost_centre.id_ + item.designation = data.designation + item.salary = data.salary + item.points = data.points + item.joining_date = data.joining_date + item.is_starred = data.is_starred + item.is_active = data.is_active + item.leaving_date = data.leaving_date + db.commit() + return employee_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.delete("/{id_}", response_model=schemas.EmployeeBlank) -def delete( +def delete_route( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["employees"]), ) -> schemas.EmployeeBlank: - employee: Employee = db.query(Employee).filter(Employee.id == id_).first() - can_delete, reason = employee.can_delete("advanced-delete" in user.permissions) - if can_delete: - delete_with_data(employee, db) - db.commit() - return employee_blank() - else: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Cannot delete account because {reason}", - ) + with SessionFuture() as db: + employee: Employee = db.execute(select(Employee).where(Employee.id == id_)).scalar_one() + can_delete, reason = employee.can_delete("advanced-delete" in user.permissions) + if can_delete: + delete_with_data(employee, db) + db.commit() + return employee_blank() + else: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Cannot delete account because {reason}", + ) @router.get("", response_model=schemas.EmployeeBlank) def show_blank( - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["employees"]), ) -> schemas.EmployeeBlank: return employee_blank() @router.get("/list", response_model=List[schemas.Employee]) -async def show_list(db: Session = Depends(get_db), user: UserToken = Depends(get_user)): - return [ - employee_info(item) - for item in db.query(Employee) - .order_by(desc(Employee.is_active)) - .order_by(Account.cost_centre_id) - .order_by(Employee.designation) - .order_by(Employee.name) - .all() - ] +async def show_list(user: UserToken = Depends(get_user)): + with SessionFuture() as db: + return [ + employee_info(item) + for item in db.execute( + select(Employee) + .order_by(desc(Employee.is_active)) + .order_by(Account.cost_centre_id) + .order_by(Employee.designation) + .order_by(Employee.name) + ) + .scalars() + .all() + ] @router.get("/query") async def show_term( q: str, c: int = None, - db: Session = Depends(get_db), current_user: UserToken = Depends(get_user), ): list_ = [] - for index, item in enumerate(Employee.query(q=q, type_=10, db=db)): - list_.append( - { - "id": item.id, - "name": item.name, - "designation": item.designation, - "costCentre": { - "id": item.cost_centre.id, - "name": item.cost_centre.name, - }, - } - ) - if c is not None and index == c - 1: - break + with SessionFuture() as db: + for index, item in enumerate(Employee.query(q=q, type_=10, db=db)): + list_.append( + { + "id": item.id, + "name": item.name, + "designation": item.designation, + "costCentre": { + "id": item.cost_centre.id, + "name": item.cost_centre.name, + }, + } + ) + if c is not None and index == c - 1: + break return list_ @router.get("/{id_}", response_model=schemas.Employee) def show_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["employees"]), ) -> schemas.Employee: - item: Employee = db.query(Employee).filter(Employee.id == id_).first() - return employee_info(item) + with SessionFuture() as db: + item: Employee = db.execute(select(Employee).where(Employee.id == id_)).scalar_one() + return employee_info(item) def employee_info(employee: Employee) -> schemas.Employee: @@ -212,11 +198,14 @@ def employee_blank() -> schemas.EmployeeBlank: def delete_with_data(employee: Employee, db: Session): - suspense_account = db.query(Account).filter(Account.id == Account.suspense()).first() + suspense_account = db.execute(select(Account).where(Account.id == Account.suspense())).scalar_one() query = ( - db.query(Voucher) - .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) - .filter(Voucher.journals.any(Journal.account_id == employee.id)) + db.execute( + select(Voucher) + .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) + .where(Voucher.journals.any(Journal.account_id == employee.id)) + ) + .scalars() .all() ) diff --git a/brewman/brewman/routers/employee_attendance.py b/brewman/brewman/routers/employee_attendance.py index 443ce7f4..fb68060d 100644 --- a/brewman/brewman/routers/employee_attendance.py +++ b/brewman/brewman/routers/employee_attendance.py @@ -4,12 +4,13 @@ from datetime import date, datetime import brewman.schemas.voucher as schemas -from fastapi import APIRouter, Depends, HTTPException, Request, Security, status +from fastapi import APIRouter, Request, Security +from sqlalchemy import select from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_finish_date, get_start_date -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.attendance import Attendance from ..models.employee import Employee from ..routers.fingerprint import get_prints @@ -20,15 +21,6 @@ from .attendance import date_range router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.EmployeeAttendance) def show_blank( request: Request, @@ -48,42 +40,42 @@ def employee_attendance_report( request: Request, s: str = None, f: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["attendance"]), ): - employee: Employee = db.query(Employee).filter(Employee.id == id_).first() - if employee is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Employee not found", + with SessionFuture() as db: + employee: Employee = db.execute(select(Employee).where(Employee.id == id_)).scalar_one() + start_date = s if s is not None else get_start_date(request.session) + finish_date = f if f is not None else get_finish_date(request.session) + info = { + "startDate": start_date, + "finishDate": finish_date, + "employee": {"id": employee.id, "name": employee.name}, + } + start_date = datetime.strptime(start_date, "%d-%b-%Y").date() + finish_date = datetime.strptime(finish_date, "%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 ) - start_date = s if s is not None else get_start_date(request.session) - finish_date = f if f is not None else get_finish_date(request.session) - info = { - "startDate": start_date, - "finishDate": finish_date, - "employee": {"id": employee.id, "name": employee.name}, - } - start_date = datetime.strptime(start_date, "%d-%b-%Y").date() - finish_date = datetime.strptime(finish_date, "%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 - ) - info["body"] = employee_attendance(employee, start_date, finish_date, db) - return info + info["body"] = employee_attendance(employee, start_date, finish_date, db) + return info def employee_attendance(employee: Employee, start_date: date, finish_date: date, db: Session): list_ = [] for item in date_range(start_date, finish_date, inclusive=True): att = ( - db.query(Attendance) - .filter(Attendance.employee_id == employee.id) - .filter(Attendance.date == item) - .filter(Attendance.is_valid == True) # noqa: E712 - .first() + db.execute( + select(Attendance).where( + Attendance.employee_id == employee.id, + Attendance.date == item, + Attendance.is_valid == True, # noqa: E712 + ) + ) + .scalars() + .one_or_none() ) + att = 0 if att is None else att.attendance_type prints, hours_worked, full_day = get_prints(employee.id, item, db) list_.append( @@ -102,30 +94,30 @@ def employee_attendance(employee: Employee, start_date: date, finish_date: date, def save_employee_attendance( id_: uuid.UUID, data: schemas.EmployeeAttendance, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["attendance"]), ): start_date = None finish_date = None - employee: Employee = db.query(Employee).filter(Employee.id == id_).first() - for item in data.body: - if start_date is None: - start_date = item.date_ - finish_date = item.date_ + with SessionFuture() as db: + employee: Employee = db.execute(select(Employee).where(Employee.id == id_)).scalar_one() + for item in data.body: + if start_date is None: + start_date = item.date_ + finish_date = item.date_ - attendance_type = item.attendance_type.id_ - if attendance_type != 0: - attendance = Attendance( - employee_id=employee.id, - date=item.date_, - attendance_type=attendance_type, - user_id=user.id_, - ) - attendance.create(db) - db.commit() - return { - "startDate": start_date.strftime("%d-%b-%Y"), - "finishDate": finish_date.strftime("%d-%b-%Y"), - "employee": {"id": employee.id, "name": employee.name}, - "body": employee_attendance(employee, start_date, finish_date, db), - } + attendance_type = item.attendance_type.id_ + if attendance_type != 0: + attendance = Attendance( + employee_id=employee.id, + date=item.date_, + attendance_type=attendance_type, + user_id=user.id_, + ) + attendance.create(db) + db.commit() + return { + "startDate": start_date.strftime("%d-%b-%Y"), + "finishDate": finish_date.strftime("%d-%b-%Y"), + "employee": {"id": employee.id, "name": employee.name}, + "body": employee_attendance(employee, start_date, finish_date, db), + } diff --git a/brewman/brewman/routers/employee_benefit.py b/brewman/brewman/routers/employee_benefit.py index 894ee133..7dcd6dc1 100644 --- a/brewman/brewman/routers/employee_benefit.py +++ b/brewman/brewman/routers/employee_benefit.py @@ -8,12 +8,13 @@ import brewman.schemas.input as schema_in import brewman.schemas.voucher as output from fastapi import APIRouter, Depends, File, HTTPException, Request, Security, status +from sqlalchemy import select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_date, get_last_day, set_date -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account_base import AccountBase from ..models.employee import Employee from ..models.emplyee_benefit import EmployeeBenefit @@ -34,20 +35,10 @@ from .voucher import ( router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=output.Voucher) def save_route( request: Request, data: schema_in.EmployeeBenefitIn = Depends(schema_in.EmployeeBenefitIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["employee-benefit"]), @@ -55,24 +46,21 @@ def save_route( try: dt = get_last_day(data.date_) days_in_month = dt.day - item: Voucher = save(data, dt, user, db) - exp, total = save_employee_benefits(item, data.employee_benefits, days_in_month, db) - save_journals(item, exp, total, db) - check_journals_are_valid(item) - save_files(item.id, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - info = voucher_info(item, db) - return info + with SessionFuture() as db: + item: Voucher = save(data, dt, user, db) + exp, total = save_employee_benefits(item, data.employee_benefits, days_in_month, db) + save_journals(item, exp, total, db) + check_journals_are_valid(item) + save_files(item.id, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + info = voucher_info(item, db) + return info except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def save(data: schema_in.EmployeeBenefitIn, date_: date, user: UserToken, db: Session) -> Voucher: @@ -96,7 +84,7 @@ def save_employee_benefits( ): total_exp, total_total = 0, 0 for item in employee_benefits: - account = db.query(Employee).filter(Employee.id == item.employee.id_).first() + account = db.execute(select(Employee).where(Employee.id == item.employee.id_)).scalar_one() gross_salary = item.gross_salary days_worked = item.days_worked esi_ee, esi_er, esi_both = esi_contribution(gross_salary, days_worked, days_in_month) @@ -126,7 +114,7 @@ def save_employee_benefits( def save_journals(voucher: Voucher, exp: int, total: int, db: Session): - account = db.query(AccountBase).filter(AccountBase.id == AccountBase.esi_pf_expense()).first() + account = db.execute(select(AccountBase).where(AccountBase.id == AccountBase.esi_pf_expense())).scalar_one() journal = Journal( amount=exp, debit=1, @@ -135,7 +123,7 @@ def save_journals(voucher: Voucher, exp: int, total: int, db: Session): ) db.add(journal) voucher.journals.append(journal) - account = db.query(AccountBase).filter(AccountBase.id == AccountBase.esi_pf_payable()).first() + account = db.execute(select(AccountBase).where(AccountBase.id == AccountBase.esi_pf_payable())).scalar_one() journal = Journal( amount=total, debit=-1, @@ -151,7 +139,6 @@ def update_route( id_: uuid.UUID, request: Request, data: schema_in.EmployeeBenefitIn = Depends(schema_in.EmployeeBenefitIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["employee-benefit"]), @@ -159,32 +146,29 @@ def update_route( try: dt = get_last_day(data.date_) days_in_month = dt.day - item: Voucher = update(id_, data, user, db) - if dt != item.date: - raise HTTPException( - status_code=status.HTTP_423_LOCKED, - detail="Date Cannot be changed for Employee Benefit voucher!", - ) - exp, total = update_employee_benefits(item, data.employee_benefits, days_in_month, db) - update_journals(item, exp, total) - check_journals_are_valid(item) - update_files(item.id, data.files, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = update_voucher(id_, data, user, db) + if dt != item.date: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail="Date Cannot be changed for Employee Benefit voucher!", + ) + exp, total = update_employee_benefits(item, data.employee_benefits, days_in_month, db) + update_journals(item, exp, total) + check_journals_are_valid(item) + update_files(item.id, data.files, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise -def update(id_: uuid.UUID, data: schema_in.EmployeeBenefitIn, user: UserToken, db: Session) -> Voucher: - voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() +def update_voucher(id_: uuid.UUID, data: schema_in.EmployeeBenefitIn, user: UserToken, db: Session) -> Voucher: + voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() check_voucher_lock_info(voucher.date, data.date_, db) check_voucher_edit_allowed(voucher, user) voucher.is_starred = data.is_starred @@ -229,31 +213,27 @@ def update_journals(voucher: Voucher, exp: int, total: int): @router.get("/{id_}", response_model=output.Voucher) def get_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["employee-benefit"]), ): try: - item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.get("", response_model=output.Voucher) def show_blank( request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["employee-benefit"]), ): additional_info = {"date": get_date(request.session), "type": "Employee Benefit"} - return blank_voucher(additional_info, db) + with SessionFuture() as db: + return blank_voucher(additional_info, db) def esi_contribution(gross_salary, days_worked, days_in_month): diff --git a/brewman/brewman/routers/fingerprint.py b/brewman/brewman/routers/fingerprint.py index 459f3dfa..4054211e 100644 --- a/brewman/brewman/routers/fingerprint.py +++ b/brewman/brewman/routers/fingerprint.py @@ -5,13 +5,13 @@ from datetime import date, datetime, time, timedelta from io import StringIO from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status -from sqlalchemy import bindparam +from sqlalchemy import bindparam, 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_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.employee import Employee from ..models.fingerprint import Fingerprint from ..routers import get_lock_info @@ -21,54 +21,41 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("") def upload_prints( - db: Session = Depends(get_db), fingerprints: UploadFile = File(None), user: UserToken = Depends(get_user), ): try: - start, finish = get_lock_info(db) - employees = {} - for id_, code in db.query(Employee.id, Employee.code).all(): - employees[code] = id_ - file_data = read_file(fingerprints) - prints = [d for d in fp(file_data, employees) if start <= d["date"].date() <= finish] - paged_data = [prints[i : i + 100] for i in range(0, len(prints), 100)] - for i, page in enumerate(paged_data): - print(f"Processing page {i} of {len(paged_data)}") - db.execute( - pg_insert(Fingerprint) - .values( - { - "id": bindparam("id"), - "employee_id": bindparam("employee_id"), - "date": bindparam("date"), - } + with SessionFuture() as db: + start, finish = get_lock_info(db) + employees = {} + for id_, code in db.execute(select(Employee.id, Employee.code)).all(): + employees[code] = id_ + file_data = read_file(fingerprints) + prints = [d for d in fp(file_data, employees) if start <= d["date"].date() <= finish] + paged_data = [prints[i : i + 100] for i in range(0, len(prints), 100)] + for i, page in enumerate(paged_data): + print(f"Processing page {i} of {len(paged_data)}") + db.execute( + pg_insert(Fingerprint) + .values( + { + "id": bindparam("id"), + "employee_id": bindparam("employee_id"), + "date": bindparam("date"), + } + ) + .on_conflict_do_nothing(), + page, ) - .on_conflict_do_nothing(), - page, - ) - db.commit() - return {} + db.commit() + return {} except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def read_file(input_file: UploadFile): @@ -127,11 +114,16 @@ def fp(file_data, employees): def get_prints(employee_id: uuid.UUID, date_: date, db: Session): prints = ( - db.query(Fingerprint) - .filter(Fingerprint.employee_id == employee_id) - .filter(Fingerprint.date >= datetime.combine(date_, time(hour=7))) - .filter(Fingerprint.date < datetime.combine(date_ + timedelta(days=1), time(hour=7))) - .order_by(Fingerprint.date) + db.execute( + select(Fingerprint) + .where( + Fingerprint.employee_id == employee_id, + Fingerprint.date >= datetime.combine(date_, time(hour=7)), + Fingerprint.date < datetime.combine(date_ + timedelta(days=1), time(hour=7)), + ) + .order_by(Fingerprint.date) + ) + .scalars() .all() ) diff --git a/brewman/brewman/routers/incentive.py b/brewman/brewman/routers/incentive.py index a8bff6ce..7c22d53f 100644 --- a/brewman/brewman/routers/incentive.py +++ b/brewman/brewman/routers/incentive.py @@ -8,13 +8,13 @@ import brewman.schemas.input as schema_in import brewman.schemas.voucher as output from fastapi import APIRouter, Depends, HTTPException, Request, Security, status -from sqlalchemy import and_, func, or_ +from sqlalchemy import and_, func, or_, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_date, get_first_day, set_date -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account import Account from ..models.attendance import Attendance from ..models.attendance_type import AttendanceType @@ -36,43 +36,30 @@ from .voucher import ( router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=output.Voucher) def save_route( request: Request, data: schema_in.IncentiveIn = Depends(schema_in.IncentiveIn.load_form), - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["incentive"]), ): try: - item: Voucher = save(data, user, db) - employees = get_employees(get_first_day(data.date_), data.date_, data.incentives, None, db) - amount = balance(data.date_, None, db) * Decimal(0.9) # 10% for Deb Dip - total_points = sum(e.points * e.days_worked for e in employees) - point_value = round(amount / total_points, 2) - save_incentives(item, employees, point_value, db) - check_journals_are_valid(item) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - info = voucher_info(item, db) - return info + with SessionFuture() as db: + item: Voucher = save(data, user, db) + employees = get_employees(get_first_day(data.date_), data.date_, data.incentives, None, db) + amount = balance(data.date_, None, db) * Decimal(0.9) # 10% for Deb Dip + total_points = sum(e.points * e.days_worked for e in employees) + point_value = round(amount / total_points, 2) + save_incentives(item, employees, point_value, db) + check_journals_are_valid(item) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + info = voucher_info(item, db) + return info except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def save(data: schema_in.IncentiveIn, user: UserToken, db: Session) -> Voucher: @@ -110,7 +97,7 @@ def save_incentives( db.add(inc) total_amount += item_amount - sc = db.query(Account).filter(Account.id == Account.incentive_id()).first() + sc = db.execute(select(Account).where(Account.id == Account.incentive_id())).scalar_one() journal = Journal(amount=total_amount, debit=1, account_id=sc.id, cost_centre_id=sc.cost_centre_id) voucher.journals.append(journal) db.add(journal) @@ -121,33 +108,29 @@ def update_route( id_: uuid.UUID, request: Request, data: schema_in.IncentiveIn = Depends(schema_in.IncentiveIn.load_form), - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["incentive"]), ): try: - item: Voucher = update(id_, data, user, db) - employees = get_employees(get_first_day(data.date_), data.date_, data.incentives, item.journals, db) - amount = balance(data.date_, item.id, db) * Decimal(0.9) # 10% for Deb Dip - total_points = sum(e.points * e.days_worked for e in employees) - point_value = round(amount / total_points, 2) - update_incentives(item, employees, point_value, db) - check_journals_are_valid(item) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = update_voucher(id_, data, user, db) + employees = get_employees(get_first_day(data.date_), data.date_, data.incentives, item.journals, db) + amount = balance(data.date_, item.id, db) * Decimal(0.9) # 10% for Deb Dip + total_points = sum(e.points * e.days_worked for e in employees) + point_value = round(amount / total_points, 2) + update_incentives(item, employees, point_value, db) + check_journals_are_valid(item) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise -def update(id_: uuid.UUID, data: schema_in.IncentiveIn, user: UserToken, db: Session) -> Voucher: - voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() +def update_voucher(id_: uuid.UUID, data: schema_in.IncentiveIn, user: UserToken, db: Session) -> Voucher: + voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() check_voucher_lock_info(voucher.date, data.date_, db) check_voucher_edit_allowed(voucher, user) voucher.is_starred = data.is_starred @@ -180,32 +163,28 @@ def update_incentives( @router.get("/{id_}", response_model=output.Voucher) def get_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["incentive"]), ): try: - item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.get("", response_model=output.Voucher) def show_blank( request: Request, d: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["incentive"]), ): additional_info = {"date": d or get_date(request.session), "type": "Incentive"} - return blank_voucher(additional_info, db) + with SessionFuture() as db: + return blank_voucher(additional_info, db) def get_employees( @@ -216,13 +195,10 @@ def get_employees( db: Session, ) -> List[schema_in.IncentiveEmployee]: details = [] - employees = ( - db.query(Employee) - .filter(Employee.joining_date <= finish_date) - .filter(or_(Employee.is_active, Employee.leaving_date >= start_date)) - .order_by(Employee.cost_centre_id) - .order_by(Employee.designation) - .order_by(Employee.name) + employees = db.execute( + select(Employee) + .where(Employee.joining_date <= finish_date, or_(Employee.is_active, Employee.leaving_date >= start_date)) + .order_by(Employee.cost_centre_id, Employee.designation, Employee.name) .all() ) @@ -230,11 +206,15 @@ def get_employees( for employee in employees: att = ( - db.query(Attendance) - .filter(Attendance.employee_id == employee.id) - .filter(Attendance.date >= start_date) - .filter(Attendance.date <= finish_date) - .filter(Attendance.is_valid == True) # noqa: E712 + db.execute( + select(Attendance).where( + Attendance.employee_id == employee.id, + Attendance.date >= start_date, + Attendance.date <= finish_date, + Attendance.is_valid == True, # noqa: E712 + ) + ) + .scalars() .all() ) att = 0.5 * round(sum(map(lambda x: AttendanceType.by_id(x.attendance_type).value, att)) / 0.5) @@ -251,10 +231,10 @@ def get_employees( def balance(date_: date, voucher_id: Optional[uuid.UUID], db: Session): - amount = ( - db.query(func.sum(Journal.amount * Journal.debit)) + amount = db.execute( + select(func.sum(Journal.amount * Journal.debit)) .join(Journal.voucher) - .filter( + .where( Journal.account_id == Account.incentive_id(), Voucher.type != VoucherType.by_name("Issue").id, or_( @@ -267,9 +247,9 @@ def balance(date_: date, voucher_id: Optional[uuid.UUID], db: Session): ) ) if voucher_id is not None: - amount = amount.filter(Voucher.id != voucher_id) - amount = amount.scalar() - return 0 if amount is None else amount * -1 + amount = amount.where(Voucher.id != voucher_id) + result: Decimal = amount.scalar() + return 0 if result is None else result * -1 def check_if_employees_changed( diff --git a/brewman/brewman/routers/issue.py b/brewman/brewman/routers/issue.py index 8f7800ca..8cd7eda7 100644 --- a/brewman/brewman/routers/issue.py +++ b/brewman/brewman/routers/issue.py @@ -8,12 +8,13 @@ import brewman.schemas.input as schema_in import brewman.schemas.voucher as output from fastapi import APIRouter, Depends, File, HTTPException, Request, Security, status +from sqlalchemy import select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_date, set_date -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account_base import AccountBase from ..models.batch import Batch from ..models.cost_centre import CostCentre @@ -35,44 +36,31 @@ from .voucher import ( router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=output.Voucher) def save_route( request: Request, data: schema_in.IssueIn = Depends(schema_in.IssueIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["issue"]), ): try: - item, batch_consumed = save(data, user, db) - amount = save_inventories(item, data.inventories, batch_consumed, db) - check_inventories_are_valid(item) - save_journals(item, data.source, data.destination, amount, db) - check_journals_are_valid(item) - save_files(item.id, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - info = voucher_info(item, db) - return info + with SessionFuture() as db: + item, batch_consumed = save(data, user, db) + amount = save_inventories(item, data.inventories, batch_consumed, db) + check_inventories_are_valid(item) + save_journals(item, data.source, data.destination, amount, db) + check_journals_are_valid(item) + save_files(item.id, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + info = voucher_info(item, db) + return info except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def save(data: schema_in.IssueIn, user: UserToken, db: Session) -> (Voucher, Optional[bool]): @@ -107,7 +95,7 @@ def save_inventories( ) -> Decimal: amount: Decimal = Decimal(0) for item in inventories: - batch = db.query(Batch).filter(Batch.id == item.batch.id_).first() + batch = db.execute(select(Batch).where(Batch.id == item.batch.id_)).scalar_one() if batch_consumed and item.quantity > batch.quantity_remaining: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -170,35 +158,31 @@ def update_route( id_: uuid.UUID, request: Request, data: schema_in.IssueIn = Depends(schema_in.IssueIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["issue"]), ): try: - item, batch_consumed = update(id_, data, user, db) - amount = update_inventories(item, data.inventories, batch_consumed, db) - check_inventories_are_valid(item) - update_journals(item, data.source, data.destination, amount) - check_journals_are_valid(item) - update_files(item.id, data.files, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - # item: Voucher = db.query(Voucher).filter(Voucher.id == item.id).first() - return voucher_info(item, db) + with SessionFuture() as db: + item, batch_consumed = update_voucher(id_, data, user, db) + amount = update_inventories(item, data.inventories, batch_consumed, db) + check_inventories_are_valid(item) + update_journals(item, data.source, data.destination, amount) + check_journals_are_valid(item) + update_files(item.id, data.files, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + # item: Voucher = db.execute(select(Voucher).where(Voucher.id == item.id)).scalar_one() + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise -def update(id_: uuid.UUID, data: schema_in.IssueIn, user: UserToken, db: Session) -> (Voucher, Optional[bool]): - voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() +def update_voucher(id_: uuid.UUID, data: schema_in.IssueIn, user: UserToken, db: Session) -> (Voucher, Optional[bool]): + voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() check_voucher_lock_info(voucher.date, data.date_, db) check_voucher_edit_allowed(voucher, user) voucher.date = data.date_ @@ -249,7 +233,7 @@ def update_inventories( for j in range(len(inventories), 0, -1): i = inventories[j - 1] if item.id == i.id_: - batch = db.query(Batch).filter(Batch.id == i.batch.id_).first() + batch = db.execute(select(Batch).where(Batch.id == i.batch.id_)).scalar_one() found = True if item.batch_id != batch.id: raise HTTPException( @@ -321,21 +305,17 @@ def update_journals( @router.get("/{id_}", response_model=output.Voucher) def get_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["issue"]), ): try: - item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.get("", response_model=output.Voucher) @@ -344,7 +324,6 @@ def show_blank( date: str = None, source: str = None, destination: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["issue"]), ): date_ = date or get_date(request.session) @@ -353,4 +332,5 @@ def show_blank( additional_info["source"] = source if destination: additional_info["destination"] = destination - return blank_voucher(additional_info, db) + with SessionFuture() as db: + return blank_voucher(additional_info, db) diff --git a/brewman/brewman/routers/issue_grid.py b/brewman/brewman/routers/issue_grid.py index 6fb56ac6..fa4b1d23 100644 --- a/brewman/brewman/routers/issue_grid.py +++ b/brewman/brewman/routers/issue_grid.py @@ -1,12 +1,11 @@ from datetime import datetime -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Security +from sqlalchemy import select from sqlalchemy.orm import Session -from sqlalchemy.orm.util import aliased from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal -from ..models.journal import Journal +from ..db.session import SessionFuture from ..models.voucher import Voucher from ..models.voucher_type import VoucherType from ..schemas.user import UserToken @@ -15,42 +14,32 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("/{date}") def grid_date( date: str, - request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["issue"]), ): date = datetime.strptime(date, "%d-%b-%Y") - return get_grid(date, db) + with SessionFuture() as db: + return get_grid(date, db) -def get_grid(date, db): +def get_grid(date, db: Session): list_ = [] - - source_journal = aliased(Journal) - destination_journal = aliased(Journal) query = ( - db.query(Voucher, source_journal.amount) - .join(source_journal, Voucher.journals) - .join(destination_journal, Voucher.journals) - .filter(Voucher.date == date) - .filter(Voucher.type == VoucherType.by_name("Issue").id) - .order_by(Voucher.creation_date) + db.execute( + select(Voucher) + .join(Voucher.journals) + .where(Voucher.date == date, Voucher.type == VoucherType.by_name("Issue").id) + .order_by(Voucher.creation_date) + ) + .unique() + .scalars() .all() ) - for voucher, amount in query: + for voucher in query: + amount = voucher.journals[0].amount list_.append( { "id": str(voucher.id), diff --git a/brewman/brewman/routers/journal.py b/brewman/brewman/routers/journal.py index 1a8db331..5760cdf0 100644 --- a/brewman/brewman/routers/journal.py +++ b/brewman/brewman/routers/journal.py @@ -7,12 +7,13 @@ import brewman.schemas.input as schema_in import brewman.schemas.voucher as output from fastapi import APIRouter, Depends, File, HTTPException, Request, Security, status +from sqlalchemy import select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_date, set_date -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account_base import AccountBase from ..models.journal import Journal from ..models.validations import check_journals_are_valid @@ -31,42 +32,29 @@ from .voucher import ( router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=output.Voucher) def save_route( request: Request, data: schema_in.JournalIn = Depends(schema_in.JournalIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["journal"]), ): try: - item: Voucher = save(data, user, db) - db.flush() - check_journals_are_valid(item) - save_files(item.id, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - info = voucher_info(item, db) - return info + with SessionFuture() as db: + item: Voucher = save(data, user, db) + db.flush() + check_journals_are_valid(item) + save_files(item.id, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + info = voucher_info(item, db) + return info except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def save(data: schema_in.JournalIn, user: UserToken, db: Session) -> Voucher: @@ -80,7 +68,7 @@ def save(data: schema_in.JournalIn, user: UserToken, db: Session) -> Voucher: ) db.add(voucher) for item in data.journals: - account: AccountBase = db.query(AccountBase).filter(AccountBase.id == item.account.id_).first() + account: AccountBase = db.execute(select(AccountBase).where(AccountBase.id == item.account.id_)).scalar_one() journal = Journal( amount=round(item.amount, 2), debit=item.debit, @@ -97,18 +85,18 @@ def update_route( id_: uuid.UUID, request: Request, data: schema_in.JournalIn = Depends(schema_in.JournalIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["journal"]), ): try: - item: Voucher = update(id_, data, user, db) - check_journals_are_valid(item) - update_files(item.id, data.files, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = update_voucher(id_, data, user, db) + check_journals_are_valid(item) + update_files(item.id, data.files, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + return voucher_info(item, db) except SQLAlchemyError as e: db.rollback() raise HTTPException( @@ -120,8 +108,8 @@ def update_route( raise -def update(id_: uuid.UUID, data: schema_in.JournalIn, user: UserToken, db: Session) -> Voucher: - voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() +def update_voucher(id_: uuid.UUID, data: schema_in.JournalIn, user: UserToken, db: Session) -> Voucher: + voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() check_voucher_lock_info(voucher.date, data.date_, db) check_voucher_edit_allowed(voucher, user) voucher.date = data.date_ @@ -137,7 +125,7 @@ def update(id_: uuid.UUID, data: schema_in.JournalIn, user: UserToken, db: Sessi for j in range(len(data.journals), 0, -1): new_item = data.journals[j - 1] if new_item.id_ is not None and item.id == new_item.id_: - account = db.query(AccountBase).filter(AccountBase.id == new_item.account.id_).first() + account = db.execute(select(AccountBase).where(AccountBase.id == new_item.account.id_)).scalar_one() found = True item.debit = new_item.debit item.amount = round(new_item.amount, 2) @@ -148,7 +136,7 @@ def update(id_: uuid.UUID, data: schema_in.JournalIn, user: UserToken, db: Sessi if not found: voucher.journals.remove(item) for new_item in data.journals: - account = db.query(AccountBase).filter(AccountBase.id == new_item.account.id_).first() + account = db.execute(select(AccountBase).where(AccountBase.id == new_item.account.id_)).scalar_one() journal = Journal( amount=new_item.amount, debit=round(new_item.debit, 2), @@ -163,33 +151,23 @@ def update(id_: uuid.UUID, data: schema_in.JournalIn, user: UserToken, db: Sessi @router.get("/{id_}", response_model=output.Voucher) def get_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["journal"]), ): try: - item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - if item is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Voucher not found", - ) - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.get("", response_model=output.Voucher) def show_blank( request: Request, a: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["journal"]), ): if request.scope.get("path") == "/api/payment": @@ -202,4 +180,5 @@ def show_blank( additional_info = {"date": get_date(request.session), "type": type_} if a: additional_info["account"] = a - return blank_voucher(additional_info, db) + with SessionFuture() as db: + return blank_voucher(additional_info, db) diff --git a/brewman/brewman/routers/lock_information.py b/brewman/brewman/routers/lock_information.py index c29cd7cc..620794e4 100644 --- a/brewman/brewman/routers/lock_information.py +++ b/brewman/brewman/routers/lock_information.py @@ -1,8 +1,8 @@ -from fastapi import APIRouter, Depends, Security -from sqlalchemy.orm import Session +from fastapi import APIRouter, Security +from sqlalchemy import select from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.db_setting import DbSetting from ..schemas.settings import LockInformation from ..schemas.user import UserToken @@ -11,19 +11,9 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=LockInformation) def post( data_in: LockInformation, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["lock-date"]), ): data = { @@ -44,38 +34,39 @@ def post( else: data["Finish"]["Date"] = data_in.newer_date - lock_date = db.query(DbSetting).filter(DbSetting.name == "Lock Info").first() - if lock_date is not None: - lock_date.data = data - else: - lock_date = DbSetting(name="Lock Info", data=data) - db.add(lock_date) - db.commit() + with SessionFuture() as db: + lock_date = db.execute(select(DbSetting).where(DbSetting.name == "Lock Info")).scalar_one() + if lock_date is not None: + lock_date.data = data + else: + lock_date = DbSetting(name="Lock Info", data=data) + db.add(lock_date) + db.commit() return get_info(data) @router.delete("") -def delete( - db: Session = Depends(get_db), +def delete_route( user: UserToken = Security(get_user, scopes=["lock-date"]), ): - lock_date = db.query(DbSetting).filter(DbSetting.name == "Lock Info").first() - if lock_date is not None and lock_date.data is not None: - lock_date.data = None - db.commit() + with SessionFuture() as db: + lock_date = db.execute(select(DbSetting).where(DbSetting.name == "Lock Info")).scalar_one() + if lock_date is not None and lock_date.data is not None: + lock_date.data = None + db.commit() return get_info({}) @router.get("") def get( - db: Session = Depends(get_db), user: UserToken = Security(get_user), ): - data = db.query(DbSetting).filter(DbSetting.name == "Lock Info").first() - if data is None: - return get_info({}) - else: - return get_info(data.data) + with SessionFuture() as db: + data = db.execute(select(DbSetting).where(DbSetting.name == "Lock Info")).scalars().one_or_none() + if data is None: + return get_info({}) + else: + return get_info(data.data) def get_info(data): diff --git a/brewman/brewman/routers/login.py b/brewman/brewman/routers/login.py index 1bf1fbf7..7147a476 100644 --- a/brewman/brewman/routers/login.py +++ b/brewman/brewman/routers/login.py @@ -12,8 +12,7 @@ from fastapi import ( ) from fastapi.responses import JSONResponse from fastapi.security import OAuth2PasswordRequestForm -from sqlalchemy import and_, or_ -from sqlalchemy.orm import Session +from sqlalchemy import delete, or_, select from .. import __version__ from ..core.config import settings @@ -24,7 +23,7 @@ from ..core.security import ( create_access_token, get_current_active_user, ) -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.client import Client from ..models.login_history import LoginHistory from ..schemas.user import UserToken @@ -33,79 +32,74 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db(): - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("/token", response_model=Token) async def login_for_access_token( response: Response, form_data: OAuth2PasswordRequestForm = Depends(), client_id: int = Cookie(None), otp: int = Form(None), - db: Session = Depends(get_db), ): - user = authenticate_user(form_data.username, form_data.password, db) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - allowed, client = client_allowed(user, client_id, otp, db) - db.flush() - if allowed: - history = LoginHistory(user.id, client.id) - db.add(history) + with SessionFuture() as db: + user = authenticate_user(form_data.username, form_data.password, db) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + allowed, client = client_allowed(user, client_id, otp, db) db.flush() - db.execute( - LoginHistory.__table__.delete( - or_( - LoginHistory.date < datetime.utcnow() - timedelta(days=30), - LoginHistory.client_id.in_( - db.query(Client.id) - .filter( - Client.creation_date < datetime.utcnow() - timedelta(days=3), - Client.enabled == False, # noqa: E712 - ) - .subquery() - ), + if allowed: + history = LoginHistory(user.id, client.id) + db.add(history) + db.flush() + db.execute( + delete(LoginHistory) + .where( + or_( + LoginHistory.date < datetime.utcnow() - timedelta(days=30), + LoginHistory.client_id.in_( + select(Client.id) + .where( + Client.creation_date < datetime.utcnow() - timedelta(days=3), + Client.enabled == False, # noqa: E712 + ) + .subquery() + ), + ) + ) + .execution_options(synchronize_session=False) + ) + db.execute( + delete(Client).where( + Client.creation_date < datetime.utcnow() - timedelta(days=3), Client.enabled == False # noqa: E712 ) ) - ) - db.execute( - Client.__table__.delete( - and_(Client.creation_date < datetime.utcnow() - timedelta(days=3), Client.enabled == False) # noqa: E712 + db.commit() + response.set_cookie(key="client_id", value=str(client.code), max_age=10 * 365 * 24 * 60 * 60) + if not allowed: + not_allowed_response = JSONResponse( + status_code=status.HTTP_401_UNAUTHORIZED, + headers={"WWW-Authenticate": "Bearer"}, + content={"detail": "Client is not registered"}, + ) + not_allowed_response.set_cookie(key="client_id", value=str(client.code), max_age=10 * 365 * 24 * 60 * 60) + return not_allowed_response + access_token_expires = timedelta(minutes=settings.JWT_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={ + "sub": user.name, + "scopes": ["authenticated"] + + list( + set([p.name.replace(" ", "-").lower() for r in user.roles for p in r.permissions]) + ), # noqa: W503 + "userId": str(user.id), + "lockedOut": user.locked_out, + "ver": __version__.__version__, + }, + expires_delta=access_token_expires, ) - ) - db.commit() - response.set_cookie(key="client_id", value=str(client.code), max_age=10 * 365 * 24 * 60 * 60) - if not allowed: - not_allowed_response = JSONResponse( - status_code=status.HTTP_401_UNAUTHORIZED, - headers={"WWW-Authenticate": "Bearer"}, - content={"detail": "Client is not registered"}, - ) - not_allowed_response.set_cookie(key="client_id", value=str(client.code), max_age=10 * 365 * 24 * 60 * 60) - return not_allowed_response - access_token_expires = timedelta(minutes=settings.JWT_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={ - "sub": user.name, - "scopes": ["authenticated"] - + list(set([p.name.replace(" ", "-").lower() for r in user.roles for p in r.permissions])), # noqa: W503 - "userId": str(user.id), - "lockedOut": user.locked_out, - "ver": __version__.__version__, - }, - expires_delta=access_token_expires, - ) - return {"access_token": access_token, "token_type": "bearer"} + return {"access_token": access_token, "token_type": "bearer"} @router.post("/refresh", response_model=Token) diff --git a/brewman/brewman/routers/maintenance.py b/brewman/brewman/routers/maintenance.py index 1647c1d0..4736bce9 100644 --- a/brewman/brewman/routers/maintenance.py +++ b/brewman/brewman/routers/maintenance.py @@ -1,8 +1,9 @@ -from fastapi import APIRouter, Depends, Security +from fastapi import APIRouter, Security +from sqlalchemy import select from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.db_setting import DbSetting from ..models.user import User from ..schemas.settings import Maintenance @@ -12,45 +13,36 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=Maintenance) def get_maintenance( - db: Session = Depends(get_db), user: UserToken = Security(get_user), ): - data = db.query(DbSetting).filter(DbSetting.name == "Maintenance").first() - return info(data, db) + with SessionFuture() as db: + data = db.execute(select(DbSetting).where(DbSetting.name == "Maintenance")).scalars().one_or_none() + return info(data, db) @router.post("", response_model=Maintenance) def set_maintenance( data: Maintenance, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["maintenance"]), ): - maintenance = db.query(DbSetting).filter(DbSetting.name == "Maintenance").first() - if data.enabled is False and maintenance is not None: - db.delete(maintenance) - maintenance = None - elif data.enabled is True and maintenance is None: - maintenance = DbSetting(name="Maintenance", data=user.id_) - db.add(maintenance) - elif data.enabled is True and maintenance.data != user.id_: - maintenance.data = user.id_ - db.commit() - return info(maintenance, db) + with SessionFuture() as db: + maintenance = db.execute(select(DbSetting).where(DbSetting.name == "Maintenance")).scalars().one_or_none() + if data.enabled is False and maintenance is not None: + db.delete(maintenance) + maintenance = None + elif data.enabled is True and maintenance is None: + maintenance = DbSetting(name="Maintenance", data=user.id_) + db.add(maintenance) + elif data.enabled is True and maintenance.data != user.id_: + maintenance.data = user.id_ + db.commit() + return info(maintenance, db) def info(data, db: Session): if data is None: return {"enabled": False, "user": ""} - user = db.query(User).filter(User.id == data.data).one() + user = db.execute(select(User).where(User.id == data.data)).scalar_one() return {"enabled": True, "user": user.name} diff --git a/brewman/brewman/routers/product.py b/brewman/brewman/routers/product.py index ed4e5016..3efe473a 100644 --- a/brewman/brewman/routers/product.py +++ b/brewman/brewman/routers/product.py @@ -5,12 +5,12 @@ from typing import List import brewman.schemas.product as schemas from fastapi import APIRouter, Depends, HTTPException, Security, status -from sqlalchemy import desc +from sqlalchemy import desc, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session, joinedload from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account import Account from ..models.batch import Batch from ..models.inventory import Inventory @@ -23,127 +23,112 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db(): - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=schemas.Product) def save( data: schemas.ProductIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["products"]), ) -> schemas.Product: try: - item = Product( - name=data.name, - units=data.units, - fraction=round(data.fraction, 5), - fraction_units=data.fraction_units, - product_yield=round(data.product_yield, 5), - product_group_id=data.product_group.id_, - account_id=Account.all_purchases(), - price=round(data.price, 2), - sale_price=round(data.sale_price, 2), - is_active=data.is_active, - is_purchased=data.is_purchased, - is_sold=data.is_sold, - ).create(db) - db.commit() - return product_info(item) + with SessionFuture() as db: + item = Product( + name=data.name, + units=data.units, + fraction=round(data.fraction, 5), + fraction_units=data.fraction_units, + product_yield=round(data.product_yield, 5), + product_group_id=data.product_group.id_, + account_id=Account.all_purchases(), + price=round(data.price, 2), + sale_price=round(data.sale_price, 2), + is_active=data.is_active, + is_purchased=data.is_purchased, + is_sold=data.is_sold, + ).create(db) + db.commit() + return product_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.put("/{id_}", response_model=schemas.Product) -def update( +def update_route( id_: uuid.UUID, data: schemas.ProductIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["products"]), ) -> schemas.Product: try: - item: Product = db.query(Product).filter(Product.id == id_).first() - if item.is_fixture: - raise HTTPException( - status_code=status.HTTP_423_LOCKED, - detail=f"{item.name} is a fixture and cannot be edited or deleted.", - ) - item.name = data.name - item.units = data.units - item.fraction = round(data.fraction, 5) - item.fraction_units = data.fraction_units - item.product_yield = round(data.product_yield, 5) - item.product_group_id = data.product_group.id_ - item.account_id = Account.all_purchases() - item.price = round(data.price, 2) - item.sale_price = round(data.sale_price, 2) - item.is_active = data.is_active - item.is_purchased = data.is_purchased - item.is_sold = data.is_sold - db.commit() - return product_info(item) + with SessionFuture() as db: + item: Product = db.execute(select(Product).where(Product.id == id_)).scalar_one() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + item.name = data.name + item.units = data.units + item.fraction = round(data.fraction, 5) + item.fraction_units = data.fraction_units + item.product_yield = round(data.product_yield, 5) + item.product_group_id = data.product_group.id_ + item.account_id = Account.all_purchases() + item.price = round(data.price, 2) + item.sale_price = round(data.sale_price, 2) + item.is_active = data.is_active + item.is_purchased = data.is_purchased + item.is_sold = data.is_sold + db.commit() + return product_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.delete("/{id_}", response_model=schemas.ProductBlank) -def delete( +def delete_route( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["products"]), ) -> schemas.ProductBlank: - item: Product = db.query(Product).filter(Product.id == id_).first() - can_delete, reason = item.can_delete("advanced-delete" in user.permissions) + with SessionFuture() as db: + item: Product = db.execute(select(Product).where(Product.id == id_)).scalar_one() + can_delete, reason = item.can_delete("advanced-delete" in user.permissions) - if can_delete: - delete_with_data(item, db) - db.commit() - return product_blank() - else: - db.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Cannot delete product because {reason}", - ) + if can_delete: + delete_with_data(item, db) + db.commit() + return product_blank() + else: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Cannot delete product because {reason}", + ) @router.get("", response_model=schemas.ProductBlank) def show_blank( - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["products"]), ) -> schemas.ProductBlank: return product_blank() @router.get("/list", response_model=List[schemas.Product]) -def show_list(db: Session = Depends(get_db), user: UserToken = Depends(get_user)) -> List[schemas.Product]: - return [ - product_info(item) - for item in db.query(Product) - .order_by(desc(Product.is_active)) - .order_by(Product.product_group_id) - .order_by(Product.name) - .all() - ] +def show_list(user: UserToken = Depends(get_user)) -> List[schemas.Product]: + with SessionFuture() as db: + return [ + product_info(item) + for item in db.execute( + select(Product) + .order_by(desc(Product.is_active)) + .order_by(Product.product_group_id) + .order_by(Product.name) + ) + .scalars() + .all() + ] @router.get("/query") @@ -153,41 +138,41 @@ async def show_term( c: int = None, p: bool = None, e: bool = False, - db: Session = Depends(get_db), current_user: UserToken = Depends(get_user), ): count = c extended = e list_ = [] - for index, item in enumerate(Product.query(q, p, a, db)): - list_.append( - { - "id": item.id, - "name": item.full_name, - "price": item.price, - "units": item.units, - "fraction": item.fraction, - "fractionUnits": item.fraction_units, - "productYield": item.product_yield, - "isSold": item.is_sold, - "salePrice": item.sale_price, - } - if extended - else {"id": item.id, "name": item.full_name, "price": item.price} - ) - if count is not None and index == count - 1: - break + with SessionFuture() as db: + for index, item in enumerate(Product.query(q, p, a, db)): + list_.append( + { + "id": item.id, + "name": item.name, + "price": item.price, + "units": item.units, + "fraction": item.fraction, + "fractionUnits": item.fraction_units, + "productYield": item.product_yield, + "isSold": item.is_sold, + "salePrice": item.sale_price, + } + if extended + else {"id": item.id, "name": item.full_name, "price": item.price} + ) + if count is not None and index == count - 1: + break return sorted(list_, key=lambda k: k["name"]) @router.get("/{id_}", response_model=schemas.Product) def show_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["products"]), ) -> schemas.Product: - item: Product = db.query(Product).filter(Product.id == id_).first() - return product_info(item) + with SessionFuture() as db: + item: Product = db.execute(select(Product).where(Product.id == id_)).scalar_one() + return product_info(item) def product_info(product: Product) -> schemas.Product: @@ -227,12 +212,15 @@ def product_blank() -> schemas.ProductBlank: def delete_with_data(product: Product, db: Session) -> None: - suspense_product = db.query(Product).filter(Product.id == Product.suspense()).first() - suspense_batch = db.query(Batch).filter(Batch.id == Batch.suspense()).first() + suspense_product = db.execute(select(Product).where(Product.id == Product.suspense())).scalar_one() + suspense_batch = db.execute(select(Batch).where(Batch.id == Batch.suspense())).scalar_one() query = ( - db.query(Voucher) - .options(joinedload(Voucher.inventories, innerjoin=True).joinedload(Inventory.product, innerjoin=True)) - .filter(Voucher.inventories.any(Inventory.product_id == product.id)) + db.execute( + select(Voucher) + .options(joinedload(Voucher.inventories, innerjoin=True).joinedload(Inventory.product, innerjoin=True)) + .where(Voucher.inventories.any(Inventory.product_id == product.id)) + ) + .scalars() .all() ) diff --git a/brewman/brewman/routers/product_group.py b/brewman/brewman/routers/product_group.py index 077bda26..daac417c 100644 --- a/brewman/brewman/routers/product_group.py +++ b/brewman/brewman/routers/product_group.py @@ -5,11 +5,11 @@ from typing import List import brewman.schemas.product_group as schemas from fastapi import APIRouter, Depends, HTTPException, Security, status +from sqlalchemy import select from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.product_group import ProductGroup from ..schemas.user import UserToken @@ -17,73 +17,55 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db(): - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=schemas.ProductGroup) def save( data: schemas.ProductGroupIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["product-groups"]), ) -> schemas.ProductGroup: try: - item = ProductGroup(name=data.name) - db.add(item) - db.commit() - return product_group_info(item) + with SessionFuture() as db: + item = ProductGroup(name=data.name) + db.add(item) + db.commit() + return product_group_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.put("/{id_}", response_model=schemas.ProductGroup) -def update( +def update_route( id_: uuid.UUID, data: schemas.ProductGroupIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["product-groups"]), ) -> schemas.ProductGroup: try: - item = db.query(ProductGroup).filter(ProductGroup.id == id_).first() - if item.is_fixture: - raise HTTPException( - status_code=status.HTTP_423_LOCKED, - detail=f"{item.name} is a fixture and cannot be edited or deleted.", - ) - item.name = data.name - db.commit() - return product_group_info(item) + with SessionFuture() as db: + item = db.execute(select(ProductGroup).where(ProductGroup.id == id_)).scalar_one() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + item.name = data.name + db.commit() + return product_group_info(item) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.delete("/{id_}", response_model=schemas.ProductGroupBlank) -def delete( +def delete_route( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["product-groups"]), ) -> schemas.ProductGroupBlank: - try: - item = db.query(ProductGroup).filter(ProductGroup.id == id_).first() + with SessionFuture() as db: + item = db.execute(select(ProductGroup).where(ProductGroup.id == id_)).scalar_one() if item is None: raise HTTPException( @@ -100,32 +82,32 @@ def delete( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Product Group deletion not implemented", ) - except Exception: - db.rollback() - raise @router.get("", response_model=schemas.ProductGroupBlank) def show_blank( - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["product-groups"]), ) -> schemas.ProductGroupBlank: return product_group_blank() @router.get("/list", response_model=List[schemas.ProductGroup]) -async def show_list(db: Session = Depends(get_db), user: UserToken = Depends(get_user)) -> List[schemas.ProductGroup]: - return [product_group_info(item) for item in db.query(ProductGroup).order_by(ProductGroup.name).all()] +async def show_list(user: UserToken = Depends(get_user)) -> List[schemas.ProductGroup]: + with SessionFuture() as db: + return [ + product_group_info(item) + for item in db.execute(select(ProductGroup).order_by(ProductGroup.name)).scalars().all() + ] @router.get("/{id_}", response_model=schemas.ProductGroup) def show_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["product-groups"]), ) -> schemas.ProductGroup: - item = db.query(ProductGroup).filter(ProductGroup.id == id_).first() - return product_group_info(item) + with SessionFuture() as db: + item = db.execute(select(ProductGroup).where(ProductGroup.id == id_)).scalar_one() + return product_group_info(item) def product_group_info(item: ProductGroup) -> schemas.ProductGroup: diff --git a/brewman/brewman/routers/purchase.py b/brewman/brewman/routers/purchase.py index 5103410f..29a87a6b 100644 --- a/brewman/brewman/routers/purchase.py +++ b/brewman/brewman/routers/purchase.py @@ -8,13 +8,13 @@ import brewman.schemas.input as schema_in import brewman.schemas.voucher as output from fastapi import APIRouter, Depends, File, HTTPException, Request, Security, status -from sqlalchemy import func +from sqlalchemy import func, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_date, set_date -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account_base import AccountBase from ..models.batch import Batch from ..models.inventory import Inventory @@ -36,44 +36,31 @@ from .voucher import ( router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=output.Voucher) def save_route( request: Request, data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["purchase"]), ): try: - item: Voucher = save(data, user, db) - save_inventories(item, data.inventories, db) - check_inventories_are_valid(item) - save_journals(item, data.vendor, db) - check_journals_are_valid(item) - save_files(item.id, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - info = voucher_info(item, db) - return info + with SessionFuture() as db: + item: Voucher = save(data, user, db) + save_inventories(item, data.inventories, db) + check_inventories_are_valid(item) + save_journals(item, data.vendor, db) + check_journals_are_valid(item) + save_files(item.id, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + info = voucher_info(item, db) + return info except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def save(data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: @@ -91,7 +78,7 @@ def save(data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: def save_inventories(voucher: Voucher, inventories: List[schema_in.Inventory], db: Session): for item in inventories: - product: Product = db.query(Product).filter(Product.id == item.product.id_).first() + product: Product = db.execute(select(Product).where(Product.id == item.product.id_)).scalar_one() batch = Batch( name=voucher.date, product=product, @@ -116,7 +103,7 @@ def save_inventories(voucher: Voucher, inventories: List[schema_in.Inventory], d def save_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session): - vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() + vendor = db.execute(select(AccountBase).where(AccountBase.id == ven.id_)).scalar_one() journals = {} amount = 0 for item in voucher.inventories: @@ -147,34 +134,30 @@ def update_route( id_: uuid.UUID, request: Request, data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["purchase"]), ): try: - item: Voucher = update(id_, data, user, db) - update_inventory(item, data.inventories, db) - check_inventories_are_valid(item) - update_journals(item, data.vendor, db) - check_journals_are_valid(item) - update_files(item.id, data.files, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = update_voucher(id_, data, user, db) + update_inventory(item, data.inventories, db) + check_inventories_are_valid(item) + update_journals(item, data.vendor, db) + check_journals_are_valid(item) + update_files(item.id, data.files, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise -def update(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: - voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() +def update_voucher(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: + voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() check_voucher_lock_info(voucher.date, data.date_, db) check_voucher_edit_allowed(voucher, user) voucher.date = data.date_ @@ -193,7 +176,7 @@ def update_inventory(voucher: Voucher, new_inventories: List[schema_in.Inventory for j in range(len(new_inventories), 0, -1): new_inventory = new_inventories[j - 1] if new_inventory.id_ == item.id: - product = db.query(Product).filter(Product.id == new_inventory.product.id_).first() + product = db.execute(select(Product).where(Product.id == new_inventory.product.id_)).scalar_one() found = True if item.product_id != new_inventory.product.id_: raise HTTPException( @@ -225,12 +208,9 @@ def update_inventory(voucher: Voucher, new_inventories: List[schema_in.Inventory # TODO: Update all references of the batch with the new rates break if not found: - has_been_issued = ( - db.query(func.count(Inventory.id)) - .filter(Inventory.batch_id == item.batch.id) - .filter(Inventory.id != item.id) - .scalar() - ) + has_been_issued = db.execute( + select(func.count(Inventory.id)).where(Inventory.batch_id == item.batch.id, Inventory.id != item.id) + ).scalar() if has_been_issued > 0: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -241,7 +221,7 @@ def update_inventory(voucher: Voucher, new_inventories: List[schema_in.Inventory db.delete(item) voucher.inventories.remove(item) for new_inventory in new_inventories: - product = db.query(Product).filter(Product.id == new_inventory.product.id_).first() + product = db.execute(select(Product).where(Product.id == new_inventory.product.id_)).scalar_one() batch = Batch( name=voucher.date, product_id=product.id, @@ -268,11 +248,11 @@ def update_inventory(voucher: Voucher, new_inventories: List[schema_in.Inventory def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db): - vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() + vendor = db.execute(select(AccountBase).where(AccountBase.id == ven.id_)).scalar_one() journals = {} amount = 0 for item in voucher.inventories: - product = db.query(Product).filter(Product.id == item.product_id).first() + product = db.execute(select(Product).where(Product.id == item.product_id)).scalar_one() account = product.account amount += item.amount if account.id in journals: @@ -309,28 +289,24 @@ def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db): @router.get("/{id_}", response_model=output.Voucher) def get_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["purchase"]), ): try: - item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.get("", response_model=output.Voucher) def show_blank( request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["purchase"]), ): additional_info = {"date": get_date(request.session), "type": "Purchase"} - return blank_voucher(additional_info, db) + with SessionFuture() as db: + return blank_voucher(additional_info, db) diff --git a/brewman/brewman/routers/purchase_return.py b/brewman/brewman/routers/purchase_return.py index 766d3598..b796e5d8 100644 --- a/brewman/brewman/routers/purchase_return.py +++ b/brewman/brewman/routers/purchase_return.py @@ -8,12 +8,13 @@ import brewman.schemas.input as schema_in import brewman.schemas.voucher as output from fastapi import APIRouter, Depends, File, HTTPException, Request, Security, status +from sqlalchemy import select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_date, set_date -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account_base import AccountBase from ..models.batch import Batch from ..models.inventory import Inventory @@ -34,44 +35,31 @@ from .voucher import ( router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=output.Voucher) def save_route( request: Request, data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["purchase-return"]), ): try: - item: Voucher = save(data, user, db) - save_inventories(item, data.inventories, db) - check_inventories_are_valid(item) - save_journals(item, data.vendor, db) - check_journals_are_valid(item) - save_files(item.id, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - info = voucher_info(item, db) - return info + with SessionFuture() as db: + item: Voucher = save(data, user, db) + save_inventories(item, data.inventories, db) + check_inventories_are_valid(item) + save_journals(item, data.vendor, db) + check_journals_are_valid(item) + save_files(item.id, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + info = voucher_info(item, db) + return info except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def save(data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: @@ -89,7 +77,7 @@ def save(data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: def save_inventories(voucher: Voucher, inventories: List[schema_in.Inventory], db: Session): for item in inventories: - batch = db.query(Batch).filter(Batch.id == item.batch.id_).first() + batch = db.execute(select(Batch).where(Batch.id == item.batch.id_)).scalar_one() if item.quantity > batch.quantity_remaining: raise HTTPException( @@ -117,7 +105,7 @@ def save_inventories(voucher: Voucher, inventories: List[schema_in.Inventory], d def save_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session): - vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() + vendor = db.execute(select(AccountBase).where(AccountBase.id == ven.id_)).scalar_one() journals = {} amount = 0 for item in voucher.inventories: @@ -148,21 +136,21 @@ def update_route( id_: uuid.UUID, request: Request, data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), - db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["purchase-return"]), ): try: - item: Voucher = update(id_, data, user, db) - update_inventory(item, data.inventories, db) - check_inventories_are_valid(item) - update_journals(item, data.vendor, db) - check_journals_are_valid(item) - update_files(item.id, data.files, i, t, db) - db.commit() - set_date(data.date_.strftime("%d-%b-%Y"), request.session) - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = update_voucher(id_, data, user, db) + update_inventory(item, data.inventories, db) + check_inventories_are_valid(item) + update_journals(item, data.vendor, db) + check_journals_are_valid(item) + update_files(item.id, data.files, i, t, db) + db.commit() + set_date(data.date_.strftime("%d-%b-%Y"), request.session) + return voucher_info(item, db) except SQLAlchemyError as e: db.rollback() raise HTTPException( @@ -174,8 +162,8 @@ def update_route( raise -def update(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: - voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() +def update_voucher(id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: + voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() check_voucher_lock_info(voucher.date, data.date_, db) check_voucher_edit_allowed(voucher, user) voucher.date = data.date_ @@ -224,7 +212,7 @@ def update_inventory(voucher: Voucher, new_inventories: List[schema_in.Inventory def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db): - vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() + vendor = db.execute(select(AccountBase).where(AccountBase.id == ven.id_)).scalar_one() journals = {} amount = 0 for item in voucher.inventories: @@ -264,28 +252,24 @@ def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db): @router.get("/{id_}", response_model=output.Voucher) def get_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["purchase-return"]), ): try: - item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - return voucher_info(item, db) + with SessionFuture() as db: + item: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() + return voucher_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.get("", response_model=output.Voucher) def show_blank( request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["purchase-return"]), ): additional_info = {"date": get_date(request.session), "type": "Purchase Return"} - return blank_voucher(additional_info, db) + with SessionFuture() as db: + return blank_voucher(additional_info, db) diff --git a/brewman/brewman/routers/rebase.py b/brewman/brewman/routers/rebase.py index c2f4c7be..87397766 100644 --- a/brewman/brewman/routers/rebase.py +++ b/brewman/brewman/routers/rebase.py @@ -4,12 +4,12 @@ from datetime import date, datetime, timedelta from decimal import Decimal from typing import List -from fastapi import APIRouter, Depends, Security -from sqlalchemy import and_, distinct, func -from sqlalchemy.orm import Session, aliased, joinedload +from fastapi import APIRouter, Security +from sqlalchemy import and_, delete, distinct, func, select +from sqlalchemy.orm import Session, aliased from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account import Account from ..models.account_base import AccountBase from ..models.attendance import Attendance @@ -30,52 +30,48 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("/{date_}") def rebase( date_: str, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["rebase"]), ): - # request.dbsession.execute('SET statement_timeout TO 300000;') # 5 minutes - date_ = datetime.strptime(date_, "%d-%b-%Y") - voucher_l = opening_accounts(date_, user.id_, db) - voucher_b = opening_batches(date_, user.id_, db) - starred_vouchers = save_starred(date_, db) - db.flush() - delete_data(date_, starred_vouchers, db) - db.add(voucher_l) - for j in voucher_l.journals: - db.add(j) - db.flush() - db.add(voucher_b) - for j in voucher_b.journals: - db.add(j) - for i in voucher_b.inventories: - db.add(i) - db.flush() - cleanup_lint(date_, db) - # request.dbsession.execute('RESET statement_timeout;') - db.commit() - return {} + with SessionFuture() as db: + date_ = datetime.strptime(date_, "%d-%b-%Y") + voucher_l = opening_accounts(date_, user.id_, db) + voucher_b = opening_batches(date_, user.id_, db) + starred_vouchers = save_starred(date_, db) + db.flush() + delete_data(date_, starred_vouchers, db) + db.add(voucher_l) + for j in voucher_l.journals: + db.add(j) + db.flush() + db.add(voucher_b) + for j in voucher_b.journals: + db.add(j) + for i in voucher_b.inventories: + db.add(i) + db.flush() + cleanup_lint(date_, db) + # request.dbsession.execute('RESET statement_timeout;') + db.commit() + return {} def save_starred(date_: date, db: Session): - accounts = [i.id for i in db.query(AccountBase.id).filter(AccountBase.is_starred == True).all()] # noqa: E712 + accounts = [ + i.id for i in db.execute(select(AccountBase.id).where(AccountBase.is_starred == True)).all() # noqa: E712 + ] vouchers = [] query = ( - db.query(Voucher) - .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) - .filter(Voucher.date < date_) - .filter(Voucher.journals.any(Journal.account_id.in_(accounts))) + db.execute( + select(Voucher) + .join(Voucher.journals) + .join(Journal.account) + .where(Voucher.date < date_, Voucher.journals.any(Journal.account_id.in_(accounts))) + ) + .unique() + .scalars() .all() ) @@ -112,18 +108,19 @@ def save_starred(date_: date, db: Session): def opening_accounts(date_: date, user_id: uuid.UUID, db: Session): running_total = 0 sum_func = func.sum(Journal.signed_amount) - query = ( - db.query(AccountBase, sum_func) + query = db.execute( + select(AccountBase, sum_func) .join(Journal, Voucher.journals) .join(AccountBase, Journal.account) - .filter(AccountBase.is_starred == False) # noqa: E712 - .filter(AccountBase.id != AccountBase.suspense()) - .filter(Voucher.date < date_) - .filter(Voucher.type != VoucherType.by_name("Issue").id) + .where( + AccountBase.is_starred == False, # noqa: E712 + AccountBase.id != AccountBase.suspense(), + Voucher.date < date_, + Voucher.type != VoucherType.by_name("Issue").id, + ) .having(sum_func != 0) .group_by(AccountBase) - .all() - ) + ).all() dt = date_ - timedelta(days=1) voucher = Voucher( @@ -158,17 +155,15 @@ def opening_accounts(date_: date, user_id: uuid.UUID, db: Session): def opening_batches(date_: date, user_id: uuid.UUID, db: Session): total = 0 sum_func = func.sum(Journal.debit * Inventory.quantity) - query = ( - db.query(Batch, sum_func) + query = db.execute( + select(Batch, sum_func) .join(Journal, Voucher.journals) .join(Inventory, Voucher.inventories) .join(Batch, Inventory.batch) - .filter(Voucher.date < date_) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + .where(Voucher.date < date_, Journal.cost_centre_id == CostCentre.cost_centre_purchase()) .having(sum_func != 0) .group_by(Batch) - .all() - ) + ).all() dt = date_ - timedelta(days=1) voucher = Voucher( @@ -213,14 +208,14 @@ def opening_batches(date_: date, user_id: uuid.UUID, db: Session): def delete_data(date_: date, vouchers: List[Voucher], db: Session): sub_voucher = aliased(Voucher) - sub_query = db.query(sub_voucher.id).filter(sub_voucher.date < date_).subquery() + sub_query = select(sub_voucher.id).where(sub_voucher.date < date_).subquery() - db.execute(Inventory.__table__.delete(Inventory.voucher_id.in_(sub_query))) - db.execute(EmployeeBenefit.__table__.delete(EmployeeBenefit.voucher_id.in_(sub_query))) - db.execute(Incentive.__table__.delete(Incentive.voucher_id.in_(sub_query))) - db.execute(Journal.__table__.delete(and_(Journal.voucher_id.in_(sub_query), ~Journal.voucher_id.in_(vouchers)))) + db.execute(delete(Inventory).where(Inventory.voucher_id.in_(sub_query))) + db.execute(delete(EmployeeBenefit).where(EmployeeBenefit.voucher_id.in_(sub_query))) + db.execute(delete(Incentive).where(Incentive.voucher_id.in_(sub_query))) + db.execute(delete(Journal).where(and_(Journal.voucher_id.in_(sub_query), ~Journal.voucher_id.in_(vouchers)))) db.execute( - DbImage.__table__.delete( + delete(DbImage).where( and_( DbImage.resource_type == "voucher", DbImage.resource_id.in_(sub_query), @@ -228,25 +223,27 @@ def delete_data(date_: date, vouchers: List[Voucher], db: Session): ) ) ) - db.execute(Voucher.__table__.delete(and_(Voucher.date < date_, ~Voucher.id.in_(vouchers)))) + db.execute(delete(Voucher).where(Voucher.date < date_, ~Voucher.id.in_(vouchers))) def cleanup_lint(date_: date, db: Session): # Insert executes on the end so keep list of batches and journals - db.execute(Batch.__table__.delete(~Batch.id.in_(db.query(distinct(Inventory.batch_id)).subquery()))) - db.execute(Fingerprint.__table__.delete(Fingerprint.date < date_)) - db.execute(Attendance.__table__.delete(Attendance.date < date_)) + db.execute(delete(Batch).where(~Batch.id.in_(select(distinct(Inventory.batch_id)).subquery()))) + db.execute(delete(Fingerprint).where(Fingerprint.date < date_)) + db.execute(delete(Attendance).where(Attendance.date < date_)) db.execute( - Employee.__table__.delete( + delete(Employee).where( and_( - ~Employee.id.in_(db.query(distinct(Journal.account_id)).subquery()), - ~Employee.id.in_(db.query(distinct(Fingerprint.employee_id)).subquery()), - ~Employee.id.in_(db.query(distinct(Attendance.employee_id)).subquery()), + ~Employee.id.in_(select(distinct(Journal.account_id)).subquery()), + ~Employee.id.in_(select(distinct(Fingerprint.employee_id)).subquery()), + ~Employee.id.in_(select(distinct(Attendance.employee_id)).subquery()), Employee.id.in_( - db.query(AccountBase.id) - .filter(AccountBase.is_fixture == False) # noqa: E712 - .filter(AccountBase.is_active == False) - .filter(AccountBase.is_starred == False) + select(AccountBase.id) + .where( + AccountBase.is_fixture == False, # noqa: E712 + AccountBase.is_active == False, # noqa: E712 + AccountBase.is_starred == False, # noqa: E712 + ) .subquery() ), Employee.leaving_date < date_, @@ -254,17 +251,17 @@ def cleanup_lint(date_: date, db: Session): ) ) db.execute( - AccountBase.__table__.delete( + delete(AccountBase).where( and_( - ~AccountBase.id.in_(db.query(Employee.id).subquery()), + ~AccountBase.id.in_(select(Employee.id).subquery()), AccountBase.account_type == Employee.__mapper_args__["polymorphic_identity"], ) ) ) db.execute( - Account.__table__.delete( + delete(Account).where( and_( - ~Account.id.in_(db.query(distinct(Journal.account_id)).subquery()), + ~Account.id.in_(select(distinct(Journal.account_id)).subquery()), Account.is_fixture == False, # noqa: E712 Account.is_starred == False, Account.account_type == Account.__mapper_args__["polymorphic_identity"], diff --git a/brewman/brewman/routers/recipe.py b/brewman/brewman/routers/recipe.py index 16b54ed9..d530670a 100644 --- a/brewman/brewman/routers/recipe.py +++ b/brewman/brewman/routers/recipe.py @@ -1,158 +1,158 @@ -import datetime -import time import uuid -from decimal import Decimal, InvalidOperation +from datetime import date, timedelta +from typing import List -from brewman.models.voucher import Voucher -from fastapi import APIRouter -from sqlalchemy import desc, func, or_ +import brewman.schemas.recipe as schemas +from fastapi import APIRouter, Depends, HTTPException, Request, Security, status +from sqlalchemy import desc, func, or_, select +from sqlalchemy.orm import Session + +from ..core.security import get_current_active_user as get_user from ..core.session import get_finish_date, get_start_date, set_period +from ..db.session import SessionFuture from ..models.cost_centre import CostCentre from ..models.inventory import Inventory from ..models.journal import Journal from ..models.product import Product from ..models.recipe import Recipe from ..models.recipe_item import RecipeItem +from ..models.voucher import Voucher from ..models.voucher_type import VoucherType +from ..schemas.user import UserToken router = APIRouter() # @router.post("/new") # "Recipes" -@router.post("/{id}") # "Recipes" -def save(request): - json = request.json_body - recipe_product = ( - request.dbsession.query(Product).filter(Product.id == uuid.UUID(json["Product"]["ProductID"])).first() - ) +@router.post("", response_model=schemas.Recipe) +def save( + data: schemas.RecipeIn, + request: Request, + user: UserToken = Security(get_user), +) -> schemas.Recipe: + with SessionFuture() as db: + recipe_product: Product = db.execute(select(Product).where(Product.id == data.product.id_)).scalar_one() - try: - valid_from = datetime.date(*(time.strptime(request.json_body["ValidFrom"], "%d-%b-%Y")[0:3])) - except (ValueError, KeyError, TypeError): - raise ValidationError("Valid From is not a valid date") - try: - valid_to = datetime.date(*(time.strptime(request.json_body["ValidTo"], "%d-%b-%Y")[0:3])) - except (ValueError, KeyError, TypeError): - raise ValidationError("Valid To is not a valid date") + if data.valid_till < data.valid_from: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Valid Till cannot be less than valid from", + ) + sale_price = 0 + recipe_cost = 0 + if len(data.items) == 0: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Recipe has no ingredients", + ) - if valid_to < valid_from: - raise ValidationError("Valid To cannot be less than valid from") - try: - recipe_quantity = Decimal(json.get("Quantity", 0)) - if recipe_quantity < 0: - raise ValidationError("Quantity must be a decimal >= 0") - except (ValueError, InvalidOperation): - raise ValidationError("Quantity must be a decimal >= 0") + recipe = Recipe( + product_id=data.product.id_, + quantity=round(data.quantity, 2), + sale_price=round(data.sale_price, 2), + valid_from=data.valid_from, + valid_till=data.valid_till, + ) + for item in data.items: + product: Product = db.execute(select(Product).where(Product.id == item.product.id_)).scalar_one() + quantity = round(data.quantity, 2) + if product.is_purchased: + ingredient_cost = get_purchased_product_cost(product.id, data.valid_from, data.valid_till, db) + else: + ingredient_cost = get_sub_product_cost(product.id, data.valid_from, data.valid_till, db) + cost_per_unit = ingredient_cost / (product.fraction * product.product_yield) + recipe_cost += cost_per_unit * quantity + recipe.recipe_items.append(RecipeItem(None, product.id, quantity, ingredient_cost)) - sale_price = 0 - if recipe_product.is_sold: - try: - sale_price = Decimal(json.get("SalePrice", 0)) - if sale_price < 0: - raise ValidationError("Sale Price must be a decimal >= 0") - except (ValueError, InvalidOperation): - raise ValidationError("Sale Price must be a decimal >= 0") + recipe.cost_price = round(recipe_cost / recipe.quantity, 2) + if recipe_product.is_sold: + recipe_product.sale_price = sale_price - recipe_cost = 0 - if len(json["RecipeItems"]) == 0: - raise ValidationError("Recipe has no ingredients") - recipe = Recipe( - product_id=recipe_product.id, - quantity=recipe_quantity, - sale_price=sale_price, - valid_from=valid_from, - valid_to=valid_to, - ) - for item in json["RecipeItems"]: - product = request.dbsession.query(Product).filter(Product.id == uuid.UUID(item["Product"]["ProductID"])).first() - quantity = round(Decimal(item["Quantity"]), 2) - if product.is_purchased: - ingredient_cost = get_purchased_product_cost(product.id, valid_from, valid_to, request.dbsession) - else: - ingredient_cost = get_sub_product_cost(product.id, valid_from, valid_to, request.dbsession) - cost_per_unit = ingredient_cost / (product.fraction * product.product_yield) - recipe_cost += cost_per_unit * quantity - recipe.recipe_items.append(RecipeItem(None, product.id, quantity, ingredient_cost)) - - recipe.cost_price = round(recipe_cost / recipe_quantity, 2) - if recipe_product.is_sold: - recipe_product.sale_price = sale_price - - save_recipe(recipe, request.dbsession) - transaction.commit() - set_period(valid_from, valid_to, request) - return recipe_info(recipe.id, request) + save_recipe(recipe, db) + db.commit() + set_period(data.valid_from, data.valid_till, request.session) + return recipe_info(recipe) -def save_recipe(recipe, dbsession): - product = dbsession.query(Product).filter(Product.id == recipe.product_id).first() +def save_recipe(recipe: Recipe, db: Session): + product: Product = db.execute(select(Product).where(Product.id == recipe.product_id)).scalar_one() product.price = recipe.cost_price update_old_rows( recipe.product_id, recipe.valid_from, - recipe.valid_to, + recipe.valid_till, recipe.effective_from, - dbsession, + db, ) - dbsession.add(recipe) + db.add(recipe) for item in recipe.recipe_items: item.recipe_id = recipe.id - dbsession.add(item) + db.add(item) -def get_purchased_product_cost(product_id, start_date, finish_date, dbsession): +def get_purchased_product_cost(product_id: uuid.UUID, start_date: date, finish_date: date, db: Session): quantity_sum = func.sum(Journal.debit * Inventory.quantity).label("quantity") amount_sum = func.sum( Journal.debit * Inventory.quantity * Inventory.rate * (1 + Inventory.tax) * (1 - Inventory.discount) ).label("amount") - costing = ( - dbsession.query(quantity_sum, amount_sum) + costing = db.execute( + select(quantity_sum, amount_sum) .join(Product.inventories) .join(Inventory.voucher) .join(Voucher.journals) - .filter(Inventory.product_id == product_id) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type == VoucherType.by_name("Issue").id) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + .where( + Inventory.product_id == product_id, + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.type == VoucherType.by_name("Issue").id, + Journal.cost_centre_id == CostCentre.cost_centre_purchase(), + ) .group_by(Product) - .first() - ) + ).one_or_none() if costing is None: - product = dbsession.query(Product).filter(Product.id == product_id).first() + product = db.execute(select(Product).where(Product.id == product_id)).scalar_one() return product.price else: quantity, amount = costing return amount / quantity -def get_sub_product_cost(product_id, start_date, finish_date, dbsession): - product = dbsession.query(Product).filter(Product.id == product_id).first() +def get_sub_product_cost(product_id: uuid.UUID, start_date: date, finish_date: date, db: Session): + product: Product = db.execute(select(Product).where(Product.id == product_id)).scalar_one() old_recipe = ( - dbsession.query(Recipe) - .filter(Recipe.product_id == product_id) - .filter(Recipe.effective_to == None) - .filter(Recipe.valid_from == start_date) - .filter(Recipe.valid_to == finish_date) - .first() + db.execute( + select(Recipe).where( + Recipe.product_id == product_id, + Recipe.effective_to == None, + Recipe.valid_from == start_date, + Recipe.valid_till == finish_date, + ) + ) + .scalars() + .one_or_none() ) if old_recipe is not None: return old_recipe.cost_price old_recipe = ( - dbsession.query(Recipe) - .filter(Recipe.product_id == product_id) - .filter(Recipe.effective_to == None) - .order_by(desc(Recipe.valid_to)) - .first() + db.execute( + select(Recipe) + .where(Recipe.product_id == product_id, Recipe.effective_to == None) + .order_by(desc(Recipe.valid_till)) + ) + .scalars() + .one_or_none() ) if old_recipe is None: - raise ValidationError("Ingredient XXX has no recipe, cannot determine cost") + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Ingredient XXX has no recipe, cannot determine cost", + ) recipe_cost = 0 recipe = Recipe( @@ -160,31 +160,36 @@ def get_sub_product_cost(product_id, start_date, finish_date, dbsession): quantity=old_recipe.quantity, sale_price=product.sale_price, valid_from=start_date, - valid_to=finish_date, + valid_till=finish_date, ) for item in old_recipe.recipe_items: if item.product.is_purchased: - ingredient_cost = get_purchased_product_cost(item.product_id, start_date, finish_date, dbsession) + ingredient_cost = get_purchased_product_cost(item.product_id, start_date, finish_date, db) else: - ingredient_cost = get_sub_product_cost(item.product_id, start_date, finish_date, dbsession) + ingredient_cost = get_sub_product_cost(item.product_id, start_date, finish_date, db) cost_per_unit = ingredient_cost / (item.product.fraction * item.product.product_yield) recipe_cost += cost_per_unit * item.quantity recipe.recipe_items.append(RecipeItem(None, item.product_id, item.quantity, ingredient_cost)) recipe.cost_price = round(recipe_cost / old_recipe.quantity, 2) - save_recipe(recipe, dbsession) + save_recipe(recipe, db) return recipe_cost -def update_old_rows(product_id, valid_from, valid_to, effective_date, dbsession): +def update_old_rows(product_id, valid_from: date, valid_till: date, effective_date: date, db: Session): old = ( - dbsession.query(Recipe) - .filter(Recipe.product_id == product_id) - .filter(Recipe.valid_from < valid_to) - .filter(Recipe.valid_to > valid_from) - .filter(or_(Recipe.effective_to == None, Recipe.effective_to >= effective_date)) - .order_by(Recipe.effective_from) + db.execute( + select(Recipe) + .where( + Recipe.product_id == product_id, + Recipe.valid_from < valid_till, + Recipe.valid_till > valid_from, + or_(Recipe.effective_to == None, Recipe.effective_to >= effective_date), + ) + .order_by(Recipe.effective_from) + ) + .scalars() .all() ) @@ -198,20 +203,20 @@ def update_old_rows(product_id, valid_from, valid_to, effective_date, dbsession) cost_price=item.cost_price, sale_price=item.sale_price, valid_from=item.valid_from, - valid_to=valid_from - datetime.timedelta(days=1), + valid_till=valid_from - timedelta(days=1), effective_from=effective_date, ) for ri in item.recipe_items: recipe.recipe_items.append(RecipeItem(None, ri.product_id, ri.quantity, ri.price)) new_recipes.append(recipe) - if item.valid_to > valid_to: + if item.valid_till > valid_till: recipe = Recipe( product_id=item.product_id, quantity=item.quantity, cost_price=item.cost_price, sale_price=item.sale_price, - valid_from=valid_to + datetime.timedelta(days=1), - valid_to=item.valid_to, + valid_from=valid_till + timedelta(days=1), + valid_till=item.valid_till, effective_from=effective_date, ) for ri in item.recipe_items: @@ -220,117 +225,133 @@ def update_old_rows(product_id, valid_from, valid_to, effective_date, dbsession) if item.effective_from == effective_date and item.effective_to is None: for ri in item.recipe_items: - dbsession.delete(ri) - dbsession.delete(item) + db.delete(ri) + db.delete(item) else: item.effective_to = effective_date for recipe in new_recipes: - dbsession.add(recipe) + db.add(recipe) for item in recipe.recipe_items: item.recipe_id = recipe.id - dbsession.add(item) + db.add(item) -@router.delete("/{id}") # "Recipes" -def delete(request): - recipe = request.dbsession.query(Recipe).filter(Recipe.id == uuid.UUID(request.matchdict["id"])).first() - if len(recipe.product.recipes) > 1: - request.dbsession.delete(recipe) - else: - request.dbsession.delete(recipe) - request.dbsession.delete(recipe.product) - transaction.commit() - return recipe_info(None, request) +@router.delete("/{id_}", response_model=schemas.RecipeBlank) +def delete_route( + id_: uuid.UUID, + user: UserToken = Security(get_user, scopes=["recipes"]), +) -> schemas.RecipeBlank: + with SessionFuture() as db: + recipe: Recipe = db.execute(select(Recipe).where(Recipe.id == id_)).scalar_one() + if len(recipe.product.recipes) > 1: + db.delete(recipe) + else: + db.delete(recipe) + db.delete(recipe.product) + db.commit() + return recipe_blank() -@router.get("/{id}") # "Recipes" -def show_id(request): - return recipe_info(uuid.UUID(request.matchdict["id"]), request) +@router.get("", response_model=schemas.RecipeBlank) +def show_blank( + user: UserToken = Security(get_user, scopes=["recipes"]), +) -> schemas.RecipeBlank: + return recipe_blank() -@router.get("/new") # "Recipes" -def show_blank(request): - return recipe_info(None, request) - - -@router.get("/") # "Authenticated" -async def show_list(l: bool): - list_ = ( - request.dbsession.query(Recipe) - .join(Recipe.product) - .filter(Recipe.effective_to == None) - .order_by(Recipe.product_id) - .order_by(desc(Recipe.valid_to)) - .all() - ) - recipes = [] - product_id = None - for item in list_: - if item.product_id != product_id: - recipe = { - "ProductID": item.product_id, - "Name": item.product.name, - "ProductGroup": item.product.product_group.name, - "Prices": [], - } - recipes.append(recipe) - product_id = item.product_id - costing = 0 - if item.product.is_sold and item.sale_price != 0 and item.cost_price != 0: - costing = item.cost_price / item.sale_price - recipe["Prices"].append( - { - "ValidFrom": item.valid_from.strftime("%d-%b-%Y"), - "ValidTo": item.valid_to.strftime("%d-%b-%Y"), - "CostPrice": item.cost_price, - "SalePrice": item.sale_price, - "Costing": costing, - "Url": request.route_url("recipe_id", id=item.id), - } - ) - return recipes - - -def recipe_info(id_, request): - if id_ is None: - info = { - "Quantity": 1, - "ValidFrom": get_start_date(request), - "ValidTo": get_finish_date(request), - "RecipeItems": [], - } - else: - recipe = request.dbsession.query(Recipe).filter(Recipe.id == id_).one() - info = { - "RecipeID": recipe.id, - "Product": { - "ProductID": recipe.product_id, - "Name": recipe.product.name, - "Units": recipe.product.units, - "SalePrice": recipe.product.sale_price, - "IsSold": recipe.product.is_sold, - }, - "Quantity": recipe.quantity, - "SalePrice": recipe.sale_price, - "CostPrice": recipe.cost_price, - "ValidFrom": recipe.valid_from.strftime("%d-%b-%Y"), - "ValidTo": recipe.valid_to.strftime("%d-%b-%Y"), - "RecipeItems": [], - } - for item in recipe.recipe_items: - info["RecipeItems"].append( - { - "Product": { - "ProductID": item.product.id, - "Name": item.product.name, - "Units": item.product.units, - "FractionUnits": item.product.fraction_units, - "Fraction": item.product.fraction, - "ProductYield": item.product.product_yield, - }, - "Quantity": item.quantity, - "Price": item.price, - } +@router.get("/list", response_model=List[schemas.Recipe]) +async def show_list( + user: UserToken = Depends(get_user), +) -> List[schemas.Recipe]: + with SessionFuture() as db: + list_ = ( + db.execute( + select(Recipe) + .join(Recipe.product) + .where(Recipe.effective_to == None) + .order_by(Recipe.product_id) + .order_by(desc(Recipe.valid_till)) ) - return info + .scalars() + .all() + ) + return [ + schemas.Recipe( + id=item.id, + product=schemas.ProductLink(id=item.product.id, name=item.product.name), + quantity=item.quantity, + costPrice=item.cost_price, + salePrice=item.sale_price, + notes="", + validFrom=item.valid_from, + validTill=item.valid_till, + effectiveFrom=item.valid_from, + effectiveTill=item.valid_till, + items=[], + ) + for item in list_ + ] + + +@router.get("/{id_}", response_model=schemas.Recipe) +def show_id( + id_: uuid.UUID, + user: UserToken = Security(get_user, scopes=["recipes"]), +) -> schemas.Recipe: + with SessionFuture() as db: + item: Recipe = db.execute(select(Recipe).where(Recipe.id == id_)).scalar_one() + return recipe_info(item) + + +def recipe_info(recipe: Recipe) -> schemas.Recipe: + return schemas.Recipe( + id=recipe.id, + product=schemas.ProductLink( + id=recipe.product_id, + name=recipe.product.name, + units=recipe.product.units, + salePrice=recipe.product.sale_price, + isSold=recipe.product.is_sold, + ), + quantity=recipe.quantity, + salePrice=recipe.sale_price, + costPrice=recipe.cost_price, + validFrom=recipe.valid_from, + validTill=recipe.valid_till, + notes="", + items=[ + schemas.RecipeItem( + id=item.id, + product=schemas.ProductLink( + id=item.product.id, + name=item.product.name, + units=item.product.units, + fractionUnits=item.product.fraction_units, + fraction=item.product.fraction, + productYield=item.product.product_yield, + ), + quantity=item.quantity, + price=item.price, + ) + for item in recipe.recipe_items + ], + ) + + +def recipe_blank() -> schemas.RecipeBlank: + # if id_ is None: + # info = { + # "Quantity": 1, + # "ValidFrom": get_start_date(request), + # "ValidTo": get_finish_date(request), + # "RecipeItems": [], + # } + # else: + return schemas.RecipeBlank( + quantity=0, + costPrice=0, + salePrice=0, + items=[], + notes="", + ) diff --git a/brewman/brewman/routers/reports/balance_sheet.py b/brewman/brewman/routers/reports/balance_sheet.py index d8f749cd..38b5c642 100644 --- a/brewman/brewman/routers/reports/balance_sheet.py +++ b/brewman/brewman/routers/reports/balance_sheet.py @@ -3,13 +3,13 @@ from typing import List import brewman.schemas.balance_sheet as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security from sqlalchemy.orm import Session -from sqlalchemy.sql.expression import desc, func +from sqlalchemy.sql.expression import desc, func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.account_base import AccountBase from ...models.account_type import AccountType from ...models.journal import Journal @@ -23,15 +23,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.BalanceSheet) def report_blank( request: Request, @@ -44,12 +35,12 @@ def report_blank( def report_data( date_: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["balance-sheet"]), ) -> schemas.BalanceSheet: - body, footer = build_balance_sheet(datetime.strptime(date_, "%d-%b-%Y"), db) - set_period(get_start_date(request.session), date_, request.session) - return schemas.BalanceSheet(date=date_, body=body, footer=footer) + with SessionFuture() as db: + body, footer = build_balance_sheet(datetime.strptime(date_, "%d-%b-%Y"), db) + set_period(get_start_date(request.session), date_, request.session) + return schemas.BalanceSheet(date=date_, body=body, footer=footer) def build_balance_sheet(date_: date, db: Session) -> (List[schemas.BalanceSheetItem], schemas.BalanceSheetItem): @@ -86,18 +77,14 @@ def build_balance_sheet(date_: date, db: Session) -> (List[schemas.BalanceSheetI } amount_sum = func.sum(Journal.amount * Journal.debit) - query = ( - db.query(AccountBase, amount_sum) + query = db.execute( + select(AccountBase, amount_sum) .join(Journal.voucher) .join(Journal.account) - .filter(Voucher.date <= date_) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(AccountBase.type.in_(type_list)) + .where(Voucher.date <= date_, Voucher.type != VoucherType.by_name("Issue").id, AccountBase.type.in_(type_list)) .group_by(AccountBase) - .order_by(AccountBase.type) - .order_by(desc(func.abs(amount_sum))) - .all() - ) + .order_by(AccountBase.type, desc(func.abs(amount_sum))) + ).all() counter = 0 for account, amount in query: diff --git a/brewman/brewman/routers/reports/cash_flow.py b/brewman/brewman/routers/reports/cash_flow.py index 557514b1..c429c9ec 100644 --- a/brewman/brewman/routers/reports/cash_flow.py +++ b/brewman/brewman/routers/reports/cash_flow.py @@ -2,14 +2,13 @@ import datetime import brewman.schemas.cash_flow as schemas -from fastapi import APIRouter, Depends, Request, Security -from sqlalchemy.orm import Session +from fastapi import APIRouter, Request, Security from sqlalchemy.orm.util import aliased -from sqlalchemy.sql.expression import desc, func +from sqlalchemy.sql.expression import desc, func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.account_base import AccountBase from ...models.account_type import AccountType from ...models.journal import Journal @@ -21,15 +20,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.CashFlow) def report_blank( request: Request, @@ -48,17 +38,17 @@ def report_data( request: Request, s: str = None, f: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["cash-flow"]), ): - body, footer = build_report(s, f, db) - set_period(s, f, request.session) - return { - "startDate": s, - "finishDate": f, - "body": body, - "footer": footer, - } + with SessionFuture() as db: + body, footer = build_report(s, f, db) + set_period(s, f, request.session) + return { + "startDate": s, + "finishDate": f, + "body": body, + "footer": footer, + } @router.get("/{id_}") @@ -67,17 +57,17 @@ def report_id( request: Request, s: str = None, f: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["cash-flow"]), ): - details, footer = build_report_id(id_, s, f, db) - set_period(s, f, request.session) - return { - "startDate": s, - "finishDate": f, - "body": {"details": details}, - "footer": footer, - } + with SessionFuture() as db: + details, footer = build_report_id(id_, s, f, db) + set_period(s, f, request.session) + return { + "startDate": s, + "finishDate": f, + "body": {"details": details}, + "footer": footer, + } def build_report(start_date, finish_date, db): @@ -86,25 +76,25 @@ def build_report(start_date, finish_date, db): sub_account = aliased(AccountBase) sub_query = ( - db.query(sub_voucher.id) + select(sub_voucher.id) .join(sub_journal, sub_voucher.journals) .join(sub_account, sub_journal.account) - .filter(sub_account.type == AccountType.by_name("Cash").id) - .filter(sub_voucher.date >= datetime.datetime.strptime(start_date, "%d-%b-%Y")) - .filter(sub_voucher.date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y")) + .where( + sub_account.type == AccountType.by_name("Cash").id, + sub_voucher.date >= datetime.datetime.strptime(start_date, "%d-%b-%Y"), + sub_voucher.date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y"), + ) .subquery() ) - query = ( - db.query(AccountBase.type, func.sum(Journal.signed_amount)) + query = db.execute( + select(AccountBase.type, func.sum(Journal.signed_amount)) .join(Journal, Voucher.journals) .join(AccountBase, Journal.account) - .filter(Voucher.id.in_(sub_query)) - .filter(AccountBase.type != AccountType.by_name("Cash").id) + .where(Voucher.id.in_(sub_query), AccountBase.type != AccountType.by_name("Cash").id) .group_by(AccountBase.type) .order_by(func.sum(Journal.signed_amount)) - .all() - ) + ).all() total_amount = 0 cf = {"operating": [], "investing": [], "financing": []} @@ -119,25 +109,27 @@ def build_report(start_date, finish_date, db): } ) - opening = ( - db.query(func.sum(Journal.amount * Journal.debit)) + opening = db.execute( + select(func.sum(Journal.amount * Journal.debit)) .join(Journal.voucher) .join(Journal.account) - .filter(Voucher.date < start_date) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(AccountBase.type == AccountType.by_name("Cash").id) - .scalar() - ) + .where( + Voucher.date < start_date, + Voucher.type != VoucherType.by_name("Issue").id, + AccountBase.type == AccountType.by_name("Cash").id, + ) + ).scalar() - closing = ( - db.query(func.sum(Journal.amount * Journal.debit)) + closing = db.execute( + select(func.sum(Journal.amount * Journal.debit)) .join(Journal.voucher) .join(Journal.account) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(AccountBase.type == AccountType.by_name("Cash").id) - .scalar() - ) + .where( + Voucher.date <= finish_date, + Voucher.type != VoucherType.by_name("Issue").id, + AccountBase.type == AccountType.by_name("Cash").id, + ) + ).scalar() return ( cf, @@ -162,25 +154,25 @@ def build_report_id(account_type, start_date, finish_date, db): sub_account = aliased(AccountBase) sub_query = ( - db.query(sub_voucher.id) + select(sub_voucher.id) .join(sub_journal, sub_voucher.journals) .join(sub_account, sub_journal.account) - .filter(sub_account.type == AccountType.by_name("Cash").id) - .filter(sub_voucher.date >= datetime.datetime.strptime(start_date, "%d-%b-%Y")) - .filter(sub_voucher.date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y")) + .where( + sub_account.type == AccountType.by_name("Cash").id, + sub_voucher.date >= datetime.datetime.strptime(start_date, "%d-%b-%Y"), + sub_voucher.date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y"), + ) .subquery() ) - query = ( - db.query(AccountBase, func.sum(Journal.signed_amount)) + query = db.execute( + select(AccountBase, func.sum(Journal.signed_amount)) .join(Journal, Voucher.journals) .join(AccountBase, Journal.account) - .filter(Voucher.id.in_(sub_query)) - .filter(AccountBase.type == account_type) + .where(Voucher.id.in_(sub_query), AccountBase.type == account_type) .group_by(AccountBase) .order_by(desc(func.sum(Journal.amount))) - .all() - ) + ).all() total_amount = 0 for account, amount in query: diff --git a/brewman/brewman/routers/reports/closing_stock.py b/brewman/brewman/routers/reports/closing_stock.py index 2e9246ee..773e021d 100644 --- a/brewman/brewman/routers/reports/closing_stock.py +++ b/brewman/brewman/routers/reports/closing_stock.py @@ -4,13 +4,13 @@ from typing import List import brewman.schemas.closing_stock as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security from sqlalchemy.orm import Session -from sqlalchemy.sql.expression import func +from sqlalchemy.sql.expression import func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.cost_centre import CostCentre from ...models.inventory import Inventory from ...models.journal import Journal @@ -22,15 +22,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.ClosingStock) def report_blank( request: Request, @@ -43,30 +34,28 @@ def report_blank( def report_data( date_: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["closing-stock"]), ) -> schemas.ClosingStock: set_period(get_start_date(request.session), date_, request.session) - return schemas.ClosingStock( - date=date_, - body=build_report(datetime.strptime(date_, "%d-%b-%Y").date(), db), - ) + with SessionFuture() as db: + return schemas.ClosingStock( + date=date_, + body=build_report(datetime.strptime(date_, "%d-%b-%Y").date(), db), + ) def build_report(date_: date, db: Session) -> List[schemas.ClosingStockItem]: amount_sum = func.sum(Journal.debit * Inventory.quantity * Inventory.rate * (1 + Inventory.tax)).label("amount") quantity_sum = func.sum(Journal.debit * Inventory.quantity).label("quantity") - query = ( - db.query(Product, quantity_sum, amount_sum) + query = db.execute( + select(Product, quantity_sum, amount_sum) .join(Product.inventories) .join(Inventory.voucher) .join(Voucher.journals) - .filter(Voucher.date <= date_) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + .where(Voucher.date <= date_, Journal.cost_centre_id == CostCentre.cost_centre_purchase()) .group_by(Product) .order_by(amount_sum.desc()) - .all() - ) + ).all() body = [] for product, quantity, amount in query: @@ -83,26 +72,22 @@ def build_report(date_: date, db: Session) -> List[schemas.ClosingStockItem]: def get_opening_stock(date_: date, db: Session) -> Decimal: - opening_stock = ( - db.query(func.sum(Inventory.quantity * Inventory.rate * (1 + Inventory.tax) * Journal.debit)) + opening_stock = db.execute( + select(func.sum(Inventory.quantity * Inventory.rate * (1 + Inventory.tax) * Journal.debit)) .join(Journal.voucher) .join(Journal.account) .join(Voucher.inventories) - .filter(Voucher.date < date_) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) - .scalar() - ) + .where(Voucher.date < date_, Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + ).scalar() return 0 if opening_stock is None else opening_stock def get_closing_stock(date_, db: Session) -> Decimal: - closing_stock = ( - db.query(func.sum(Inventory.quantity * Inventory.rate * (1 + Inventory.tax) * Journal.debit)) + closing_stock = db.execute( + select(func.sum(Inventory.quantity * Inventory.rate * (1 + Inventory.tax) * Journal.debit)) .join(Journal.voucher) .join(Journal.account) .join(Voucher.inventories) - .filter(Voucher.date <= date_) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) - .scalar() - ) + .where(Voucher.date <= date_, Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + ).scalar() return 0 if closing_stock is None else closing_stock diff --git a/brewman/brewman/routers/reports/daybook.py b/brewman/brewman/routers/reports/daybook.py index a019e349..597136eb 100644 --- a/brewman/brewman/routers/reports/daybook.py +++ b/brewman/brewman/routers/reports/daybook.py @@ -3,12 +3,13 @@ from typing import List import brewman.schemas.daybook as schemas -from fastapi import APIRouter, Depends, Request, Security -from sqlalchemy.orm import Session, joinedload +from fastapi import APIRouter, Request, Security +from sqlalchemy import select +from sqlalchemy.orm import Session from ...core.security import get_current_active_user as get_user from ...core.session import get_finish_date, get_start_date, set_period -from ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.journal import Journal from ...models.voucher import Voucher from ...models.voucher_type import VoucherType @@ -18,15 +19,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.Daybook) def report_blank( request: Request, @@ -44,27 +36,31 @@ def report_data( start: str, finish: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["daybook"]), ) -> schemas.Daybook: start_date = datetime.strptime(start, "%d-%b-%Y") finish_date = datetime.strptime(finish, "%d-%b-%Y") - body = build_report(start_date, finish_date, db) - set_period(start, finish, request.session) - return schemas.Daybook(startDate=start_date, finishDate=finish_date, body=body) + with SessionFuture() as db: + body = build_report(start_date, finish_date, db) + set_period(start, finish, request.session) + return schemas.Daybook(startDate=start_date, finishDate=finish_date, body=body) def build_report(start_date: date, finish_date: date, db: Session) -> List[schemas.DaybookItem]: body = [] query = ( - db.query(Voucher) - .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .order_by(Voucher.date) - .order_by(Voucher.last_edit_date) + db.execute( + select(Voucher) + .join(Voucher.journals) + .join(Journal.account) + .where( + Voucher.date >= start_date, Voucher.date <= finish_date, Voucher.type != VoucherType.by_name("Issue").id + ) + .order_by(Voucher.date, Voucher.last_edit_date) + ) + .unique() + .scalars() .all() ) diff --git a/brewman/brewman/routers/reports/ledger.py b/brewman/brewman/routers/reports/ledger.py index 0134f904..02a5eb53 100644 --- a/brewman/brewman/routers/reports/ledger.py +++ b/brewman/brewman/routers/reports/ledger.py @@ -5,13 +5,13 @@ from typing import List import brewman.schemas.ledger as schemas -from fastapi import APIRouter, Depends, Request, Security -from sqlalchemy.orm import Session, joinedload -from sqlalchemy.sql.expression import func +from fastapi import APIRouter, Request, Security +from sqlalchemy.orm import Session +from sqlalchemy.sql.expression import func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.account_base import AccountBase from ...models.journal import Journal from ...models.voucher import Voucher @@ -22,15 +22,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.Ledger) def show_blank( request: Request, @@ -50,20 +41,20 @@ def show_data( request: Request, s: str = None, f: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["ledger"]), ) -> schemas.Ledger: - account = db.query(AccountBase).filter(AccountBase.id == id_).first() - start_date = s if s is not None else get_start_date(request.session) - finish_date = f if f is not None else get_finish_date(request.session) - body = build_report(account.id, start_date, finish_date, db) - set_period(start_date, finish_date, request.session) - return schemas.Ledger( - startDate=start_date, - finishDate=finish_date, - account=schemas.AccountLink(id=account.id, name=account.name), - body=body, - ) + with SessionFuture() as db: + account = db.execute(select(AccountBase).where(AccountBase.id == id_)).scalar_one() + start_date = s if s is not None else get_start_date(request.session) + finish_date = f if f is not None else get_finish_date(request.session) + body = build_report(account.id, start_date, finish_date, db) + set_period(start_date, finish_date, request.session) + return schemas.Ledger( + startDate=start_date, + finishDate=finish_date, + account=schemas.AccountLink(id=account.id, name=account.name), + body=body, + ) def build_report(account_id: uuid.UUID, start_date: str, finish_date: str, db: Session) -> List[schemas.LedgerItem]: @@ -72,14 +63,20 @@ def build_report(account_id: uuid.UUID, start_date: str, finish_date: str, db: S body.append(opening) query = ( - db.query(Voucher) - .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) - .filter(Voucher.journals.any(Journal.account_id == account_id)) - .filter(Voucher.date >= datetime.datetime.strptime(start_date, "%d-%b-%Y")) - .filter(Voucher.date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y")) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .order_by(Voucher.date) - .order_by(Voucher.last_edit_date) + db.execute( + select(Voucher) + .join(Voucher.journals) + .join(Journal.account) + .where( + Voucher.journals.any(Journal.account_id == account_id), + Voucher.date >= datetime.datetime.strptime(start_date, "%d-%b-%Y"), + Voucher.date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y"), + Voucher.type != VoucherType.by_name("Issue").id, + ) + .order_by(Voucher.date, Voucher.last_edit_date) + ) + .unique() + .scalars() .all() ) @@ -122,14 +119,15 @@ def build_report(account_id: uuid.UUID, start_date: str, finish_date: str, db: S def opening_balance(account_id: uuid.UUID, start_date: str, db: Session) -> schemas.LedgerItem: - opening = ( - db.query(func.sum(Journal.amount * Journal.debit)) + opening = db.execute( + select(func.sum(Journal.amount * Journal.debit)) .join(Journal.voucher) - .filter(Voucher.date < datetime.datetime.strptime(start_date, "%d-%b-%Y")) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(Journal.account_id == account_id) - .scalar() - ) + .where( + Voucher.date < datetime.datetime.strptime(start_date, "%d-%b-%Y"), + Voucher.type != VoucherType.by_name("Issue").id, + Journal.account_id == account_id, + ) + ).scalar() opening = 0 if opening is None else opening if opening < 0: credit = opening * -1 diff --git a/brewman/brewman/routers/reports/net_transactions.py b/brewman/brewman/routers/reports/net_transactions.py index a0c4ae0a..97e79e2f 100644 --- a/brewman/brewman/routers/reports/net_transactions.py +++ b/brewman/brewman/routers/reports/net_transactions.py @@ -3,13 +3,13 @@ from typing import List import brewman.schemas.net_transactions as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security from sqlalchemy.orm import Session -from sqlalchemy.sql.expression import desc, func +from sqlalchemy.sql.expression import desc, func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.account_base import AccountBase from ...models.journal import Journal from ...models.voucher import Voucher @@ -20,15 +20,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.NetTransactions) def show_blank( request: Request, @@ -46,35 +37,31 @@ def show_data( start: str, finish: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["net-transactions"]), ) -> schemas.NetTransactions: set_period(start, finish, request.session) - return schemas.NetTransactions( - startDate=start, - finishDate=finish, - body=build_report( - datetime.strptime(start, "%d-%b-%Y"), - datetime.strptime(finish, "%d-%b-%Y"), - db, - ), - ) + with SessionFuture() as db: + return schemas.NetTransactions( + startDate=start, + finishDate=finish, + body=build_report( + datetime.strptime(start, "%d-%b-%Y"), + datetime.strptime(finish, "%d-%b-%Y"), + db, + ), + ) def build_report(start_date: date, finish_date: date, db: Session) -> List[schemas.NetTransactionsItem]: amount_sum = func.sum(Journal.amount * Journal.debit).label("amount") - query = ( - db.query(AccountBase, amount_sum) + query = db.execute( + select(AccountBase, amount_sum) .join(Journal.voucher) .join(Journal.account) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type != VoucherType.by_name("Issue").id) + .where(Voucher.date >= start_date, Voucher.date <= finish_date, Voucher.type != VoucherType.by_name("Issue").id) .group_by(AccountBase) - .order_by(AccountBase.type) - .order_by(desc(func.abs(amount_sum))) - .all() - ) + .order_by(AccountBase.type, desc(func.abs(amount_sum))) + ).all() body = [] for account, amount in query: diff --git a/brewman/brewman/routers/reports/product_ledger.py b/brewman/brewman/routers/reports/product_ledger.py index 732db778..02115e0e 100644 --- a/brewman/brewman/routers/reports/product_ledger.py +++ b/brewman/brewman/routers/reports/product_ledger.py @@ -6,13 +6,13 @@ from typing import List import brewman.schemas.product_ledger as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security from sqlalchemy.orm import Session, joinedload -from sqlalchemy.sql.expression import func +from sqlalchemy.sql.expression import func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.cost_centre import CostCentre from ...models.inventory import Inventory from ...models.journal import Journal @@ -25,15 +25,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.ProductLedger) def show_blank( request: Request, @@ -53,25 +44,25 @@ def show_data( request: Request, s: str = None, f: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["product-ledger"]), ) -> schemas.ProductLedger: - product = db.query(Product).filter(Product.id == id_).first() - start_date = s if s is not None else get_start_date(request.session) - finish_date = f if f is not None else get_finish_date(request.session) - body = build_report( - product.id, - datetime.strptime(start_date, "%d-%b-%Y"), - datetime.strptime(finish_date, "%d-%b-%Y"), - db, - ) - set_period(start_date, finish_date, request.session) - return schemas.ProductLedger( - startDate=start_date, - finishDate=finish_date, - product=schemas.ProductLink(id=product.id, name=product.name), - body=body, - ) + with SessionFuture() as db: + product = db.execute(select(Product).where(Product.id == id_)).scalar_one() + start_date = s if s is not None else get_start_date(request.session) + finish_date = f if f is not None else get_finish_date(request.session) + body = build_report( + product.id, + datetime.strptime(start_date, "%d-%b-%Y"), + datetime.strptime(finish_date, "%d-%b-%Y"), + db, + ) + set_period(start_date, finish_date, request.session) + return schemas.ProductLedger( + startDate=start_date, + finishDate=finish_date, + product=schemas.ProductLink(id=product.id, name=product.name), + body=body, + ) def build_report( @@ -81,22 +72,22 @@ def build_report( running_total_q, running_total_a, opening = opening_balance(product_id, start_date, db) body.append(opening) - query = ( - db.query(Voucher, Inventory, Journal) + query = db.execute( + select(Voucher, Inventory, Journal) .options( joinedload(Journal.account, innerjoin=True), joinedload(Journal.cost_centre, innerjoin=True), ) - .filter(Voucher.id == Inventory.voucher_id) - .filter(Voucher.id == Journal.voucher_id) - .filter(Inventory.product_id == product_id) - .filter(Journal.cost_centre_id != CostCentre.cost_centre_purchase()) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .order_by(Voucher.date) - .order_by(Voucher.last_edit_date) - .all() - ) + .where( + Voucher.id == Inventory.voucher_id, + Voucher.id == Journal.voucher_id, + Inventory.product_id == product_id, + Journal.cost_centre_id != CostCentre.cost_centre_purchase(), + Voucher.date >= start_date, + Voucher.date <= finish_date, + ) + .order_by(Voucher.date, Voucher.last_edit_date) + ).all() for row in query: journal_debit = row.Journal.debit * -1 @@ -141,20 +132,21 @@ def build_report( def opening_balance( product_id: uuid.UUID, start_date: date, db: Session ) -> (Decimal, Decimal, schemas.ProductLedgerItem): - quantity, amount = ( - db.query( + quantity, amount = db.execute( + select( func.sum(Inventory.quantity * Journal.debit), func.sum(Inventory.amount * Journal.debit), ) .join(Inventory.voucher) .join(Voucher.journals) - .filter(Voucher.id == Inventory.voucher_id) - .filter(Voucher.id == Journal.voucher_id) - .filter(Inventory.product_id == product_id) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) - .filter(Voucher.date < start_date) - .one() - ) + .where( + Voucher.id == Inventory.voucher_id, + Voucher.id == Journal.voucher_id, + Inventory.product_id == product_id, + Journal.cost_centre_id == CostCentre.cost_centre_purchase(), + Voucher.date < start_date, + ) + ).one() if quantity and quantity > 0: debit_quantity = quantity diff --git a/brewman/brewman/routers/reports/profit_loss.py b/brewman/brewman/routers/reports/profit_loss.py index c1d355f8..70a64113 100644 --- a/brewman/brewman/routers/reports/profit_loss.py +++ b/brewman/brewman/routers/reports/profit_loss.py @@ -4,13 +4,13 @@ from typing import List import brewman.schemas.profit_loss as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security from sqlalchemy.orm import Session -from sqlalchemy.sql.expression import desc, func +from sqlalchemy.sql.expression import desc, func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.account_base import AccountBase from ...models.account_type import AccountType from ...models.journal import Journal @@ -23,15 +23,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.ProfitLoss) def report_blank( request: Request, @@ -50,17 +41,19 @@ def report_data( start: str, finish: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["profit-&-loss"]), ) -> schemas.ProfitLoss: - body, footer = build_profit_loss(datetime.strptime(start, "%d-%b-%Y"), datetime.strptime(finish, "%d-%b-%Y"), db) - set_period(start, finish, request.session) - return schemas.ProfitLoss( - startDate=start, - finishDate=finish, - body=body, - footer=footer, - ) + with SessionFuture() as db: + body, footer = build_profit_loss( + datetime.strptime(start, "%d-%b-%Y"), datetime.strptime(finish, "%d-%b-%Y"), db + ) + set_period(start, finish, request.session) + return schemas.ProfitLoss( + startDate=start, + finishDate=finish, + body=body, + footer=footer, + ) def build_profit_loss( @@ -71,19 +64,19 @@ def build_profit_loss( groups = {} amount_sum = func.sum(Journal.amount * Journal.debit) - query = ( - db.query(AccountBase, amount_sum) + query = db.execute( + select(AccountBase, amount_sum) .join(Journal.voucher) .join(Journal.account) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(AccountBase.type.in_(profit_type_list)) + .where( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.type != VoucherType.by_name("Issue").id, + AccountBase.type.in_(profit_type_list), + ) .group_by(AccountBase) - .order_by(AccountBase.type) - .order_by(desc(func.abs(amount_sum))) - .all() - ) + .order_by(AccountBase.type, desc(func.abs(amount_sum))) + ).all() # Get opening / closing stock opening_stock = get_opening_stock(start_date, db) @@ -141,13 +134,14 @@ def build_profit_loss( def get_accumulated_profit(finish_date: date, db: Session) -> Decimal: type_list = [i.id for i in AccountType.list() if i.balance_sheet is False] - accumulated_profit = ( - db.query(func.sum(Journal.amount * Journal.debit)) + accumulated_profit = db.execute( + select(func.sum(Journal.amount * Journal.debit)) .join(Journal.voucher) .join(Journal.account) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(AccountBase.type.in_(type_list)) - .scalar() - ) + .where( + Voucher.date <= finish_date, + Voucher.type != VoucherType.by_name("Issue").id, + AccountBase.type.in_(type_list), + ) + ).scalar() return 0 if accumulated_profit is None else accumulated_profit diff --git a/brewman/brewman/routers/reports/purchase_entries.py b/brewman/brewman/routers/reports/purchase_entries.py index 150fe6cc..40956e7f 100644 --- a/brewman/brewman/routers/reports/purchase_entries.py +++ b/brewman/brewman/routers/reports/purchase_entries.py @@ -3,12 +3,13 @@ from typing import List import brewman.schemas.purchase_entries as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security +from sqlalchemy import select from sqlalchemy.orm import Session from ...core.security import get_current_active_user as get_user from ...core.session import get_finish_date, get_start_date, set_period -from ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.voucher import Voucher from ...models.voucher_type import VoucherType from ...schemas.user import UserToken @@ -17,15 +18,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.PurchaseEntries) def report_blank( request: Request, @@ -43,24 +35,32 @@ def report_data( start: str, finish: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["purchase-entries"]), ) -> schemas.PurchaseEntries: start_date = datetime.strptime(start, "%d-%b-%Y") finish_date = datetime.strptime(finish, "%d-%b-%Y") - body = build_report(start_date, finish_date, db) - set_period(start, finish, request.session) - return schemas.PurchaseEntries(startDate=start, finishDate=finish, body=body) + with SessionFuture() as db: + body = build_report(start_date, finish_date, db) + set_period(start, finish, request.session) + return schemas.PurchaseEntries(startDate=start, finishDate=finish, body=body) def build_report(start_date: date, finish_date: date, db: Session) -> List[schemas.PurchaseEntriesItem]: body = [] query: List[Voucher] = ( - db.query(Voucher) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type == VoucherType.by_name("Purchase").id) - .order_by(Voucher.date) + db.execute( + select(Voucher) + .join(Voucher.journals) + .join(Voucher.inventories) + .where( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.type == VoucherType.by_name("Purchase").id, + ) + .order_by(Voucher.date) + ) + .unique() + .scalars() .all() ) diff --git a/brewman/brewman/routers/reports/purchases.py b/brewman/brewman/routers/reports/purchases.py index 773f23cf..633f1823 100644 --- a/brewman/brewman/routers/reports/purchases.py +++ b/brewman/brewman/routers/reports/purchases.py @@ -4,13 +4,13 @@ from typing import List import brewman.schemas.purchases as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security from sqlalchemy.orm import Session -from sqlalchemy.sql.expression import desc, func +from sqlalchemy.sql.expression import desc, func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.cost_centre import CostCentre from ...models.inventory import Inventory from ...models.journal import Journal @@ -23,15 +23,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.Purchases) def report_blank( request: Request, @@ -50,17 +41,17 @@ def report_data( start: str, finish: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["purchases"]), ) -> schemas.Purchases: - body, footer = build_report(start, finish, db) - set_period(start, finish, request.session) - return schemas.Purchases( - startDate=start, - finishDate=finish, - body=body, - footer=footer, - ) + with SessionFuture() as db: + body, footer = build_report(start, finish, db) + set_period(start, finish, request.session) + return schemas.Purchases( + startDate=start, + finishDate=finish, + body=body, + footer=footer, + ) def build_report( @@ -69,19 +60,20 @@ def build_report( body = [] quantity_sum = func.sum(Journal.debit * Inventory.quantity).label("quantity") amount_sum = func.sum(Journal.debit * Inventory.quantity * Inventory.rate * (1 + Inventory.tax)).label("amount") - query = ( - db.query(Product, quantity_sum, amount_sum) + query = db.execute( + select(Product, quantity_sum, amount_sum) .join(Product.inventories) .join(Inventory.voucher) .join(Voucher.journals) - .filter(Voucher.date >= datetime.datetime.strptime(start_date, "%d-%b-%Y")) - .filter(Voucher.date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y")) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + .where( + Voucher.date >= datetime.datetime.strptime(start_date, "%d-%b-%Y"), + Voucher.date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y"), + Voucher.type != VoucherType.by_name("Issue").id, + Journal.cost_centre_id == CostCentre.cost_centre_purchase(), + ) .group_by(Product) .order_by(desc(amount_sum)) - .all() - ) + ).all() total_amount = 0 for product, quantity, amount in query: diff --git a/brewman/brewman/routers/reports/raw_material_cost.py b/brewman/brewman/routers/reports/raw_material_cost.py index 72f196ed..f82611e0 100644 --- a/brewman/brewman/routers/reports/raw_material_cost.py +++ b/brewman/brewman/routers/reports/raw_material_cost.py @@ -5,13 +5,13 @@ from typing import List import brewman.schemas.raw_material_cost as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security from sqlalchemy.orm import Session -from sqlalchemy.sql.expression import case, func +from sqlalchemy.sql.expression import case, func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.account_base import AccountBase from ...models.cost_centre import CostCentre from ...models.inventory import Inventory @@ -25,15 +25,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.RawMaterialCost) def report_blank( request: Request, @@ -52,17 +43,17 @@ def report_data( request: Request, s: str = None, f: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["raw-material-cost"]), ) -> schemas.RawMaterialCost: - body, footer = build_report(datetime.strptime(s, "%d-%b-%Y"), datetime.strptime(f, "%d-%b-%Y"), db) - set_period(s, f, request.session) - return schemas.RawMaterialCost( - startDate=s, - finishDate=f, - body=body, - footer=footer, - ) + with SessionFuture() as db: + body, footer = build_report(datetime.strptime(s, "%d-%b-%Y"), datetime.strptime(f, "%d-%b-%Y"), db) + set_period(s, f, request.session) + return schemas.RawMaterialCost( + startDate=s, + finishDate=f, + body=body, + footer=footer, + ) @router.get("/{id_}", response_model=schemas.RawMaterialCost) @@ -71,17 +62,17 @@ def report_id( request: Request, s: str = None, f: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["raw-material-cost"]), ) -> schemas.RawMaterialCost: - body = build_report_id(id_, datetime.strptime(s, "%d-%b-%Y"), datetime.strptime(f, "%d-%b-%Y"), db) - set_period(s, f, request.session) - return schemas.RawMaterialCost( - id=id_, - startDate=s, - finishDate=f, - body=body, - ) + with SessionFuture() as db: + body = build_report_id(id_, datetime.strptime(s, "%d-%b-%Y"), datetime.strptime(f, "%d-%b-%Y"), db) + set_period(s, f, request.session) + return schemas.RawMaterialCost( + id=id_, + startDate=s, + finishDate=f, + body=body, + ) def build_report( @@ -91,19 +82,20 @@ def build_report( sum_issue = func.sum(case([(AccountBase.type == 2, Journal.signed_amount)], else_=0)).label("issue") sum_sale = func.sum(case([(AccountBase.type == 3, Journal.signed_amount * -1)], else_=0)).label("sale") - query = ( - db.query(CostCentre, sum_issue, sum_sale) + query = db.execute( + select(CostCentre, sum_issue, sum_sale) .join(CostCentre.journals) .join(Journal.voucher) .join(Journal.account) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .filter(Journal.cost_centre_id != CostCentre.cost_centre_purchase()) - .filter(AccountBase.type.in_([2, 3])) + .where( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Journal.cost_centre_id != CostCentre.cost_centre_purchase(), + AccountBase.type.in_([2, 3]), + ) .group_by(CostCentre) .order_by(sum_sale.desc()) - .all() - ) + ).all() issues = 0 sales = 0 @@ -132,23 +124,21 @@ def build_report_id( sum_net = func.sum(Inventory.rate * Inventory.quantity * Journal.debit).label("net") sum_gross = func.sum(Inventory.amount * Journal.debit).label("gross") - query = ( - db.query(Product, sum_quantity, sum_net, sum_gross) + query = db.execute( + select(Product, sum_quantity, sum_net, sum_gross) .join(Product.inventories) .join(Inventory.voucher) .join(Voucher.journals) .join(Product.product_group) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type == 3) - .filter(Journal.cost_centre_id == cost_centre_id) - .group_by(Product) - .group_by(Journal.debit) - .group_by(ProductGroup.name) - .order_by(ProductGroup.name) - .order_by(sum_net.desc()) - .all() - ) + .where( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.type == 3, + Journal.cost_centre_id == cost_centre_id, + ) + .group_by(Product, Journal.debit, ProductGroup.name) + .order_by(ProductGroup.name, sum_net.desc()) + ).all() groups = {} counter = 0 diff --git a/brewman/brewman/routers/reports/reconcile.py b/brewman/brewman/routers/reports/reconcile.py index e014859f..244e36f5 100644 --- a/brewman/brewman/routers/reports/reconcile.py +++ b/brewman/brewman/routers/reports/reconcile.py @@ -3,11 +3,11 @@ import uuid from fastapi import APIRouter, Depends, Request, Security from sqlalchemy.orm import Session, joinedload -from sqlalchemy.sql.expression import and_, func, or_ +from sqlalchemy.sql.expression import and_, func, or_, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.account_base import AccountBase from ...models.journal import Journal from ...models.voucher import Voucher @@ -18,15 +18,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("") def show_blank( request: Request, @@ -46,45 +37,42 @@ def show_data( request: Request, s: str = None, f: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["reconcile"]), ): - account = db.query(AccountBase).filter(AccountBase.id == id_).first() - start_date = s if s is not None else get_start_date(request.session) - finish_date = f if f is not None else get_finish_date(request.session) - body = build_report(account.id, start_date, finish_date, db) - set_period(start_date, finish_date, request.session) - return { - "startDate": start_date, - "finishDate": finish_date, - "account": {"id": account.id, "name": account.name}, - "body": body, - } + with SessionFuture() as db: + account = db.execute(select(AccountBase).where(AccountBase.id == id_)).scalar_one() + start_date = s if s is not None else get_start_date(request.session) + finish_date = f if f is not None else get_finish_date(request.session) + body = build_report(account.id, start_date, finish_date, db) + set_period(start_date, finish_date, request.session) + return { + "startDate": start_date, + "finishDate": finish_date, + "account": {"id": account.id, "name": account.name}, + "body": body, + } def build_report(account_id, start_date, finish_date, db): opening = opening_balance(account_id, start_date, db) body = [opening] - query = ( - db.query(Voucher) + query = db.execute( + select(Voucher) .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) - .filter(Voucher.journals.any(Journal.account_id == account_id)) - .filter( + .where( + Voucher.journals.any(Journal.account_id == account_id), or_( Voucher.is_reconciled == False, and_( Voucher.reconcile_date >= datetime.datetime.strptime(start_date, "%d-%b-%Y"), Voucher.reconcile_date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y"), ), - ) + ), + Voucher.type != VoucherType.by_name("Issue").id, ) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .order_by(Voucher.is_reconciled) - .order_by(Voucher.reconcile_date) - .order_by(Voucher.last_edit_date) - .all() - ) + .order_by(Voucher.is_reconciled, Voucher.reconcile_date, Voucher.last_edit_date) + ).all() for voucher in query: debit = 0 @@ -120,16 +108,17 @@ def build_report(account_id, start_date, finish_date, db): return body -def opening_balance(account_id, start_date, db): - opening = ( - db.query(func.sum(Journal.amount * Journal.debit)) +def opening_balance(account_id: uuid.UUID, start_date, db: Session): + opening = db.execute( + select(func.sum(Journal.amount * Journal.debit)) .join(Journal.voucher) - .filter(Voucher.reconcile_date < datetime.datetime.strptime(start_date, "%d-%b-%Y")) - .filter(Voucher.is_reconciled == True) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(Journal.account_id == account_id) - .scalar() - ) + .where( + Voucher.reconcile_date < datetime.datetime.strptime(start_date, "%d-%b-%Y"), + Voucher.is_reconciled == True, + Voucher.type != VoucherType.by_name("Issue").id, + Journal.account_id == account_id, + ) + ).scalar() opening = 0 if opening is None else opening if opening < 0: credit = opening * -1 @@ -157,31 +146,31 @@ def save( request: Request, s: str = None, f: str = None, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["reconcile"]), ): - account = db.query(AccountBase).filter(AccountBase.id == id_).first() - start_date = s if s is not None else get_start_date(request.session) - finish_date = f if f is not None else get_finish_date(request.session) - raise Exception("not fixed") - # for item in request.json_body["body"]: - # if "id" not in item or item["id"] is None: - # continue - # - # voucher = ( - # request.dbsession.query(Voucher) - # .filter(Voucher.id == uuid.UUID(item["id"])) - # .first() - # ) - # is_reconciled = item["isReconciled"] - # reconcile_date = datetime.datetime.strptime(item["reconcileDate"], "%d-%b-%Y") - # voucher.is_reconciled = is_reconciled - # voucher.reconcile_date = reconcile_date - # transaction.commit() - # body = build_report(account.id, start_date, finish_date, request) - # return { - # "startDate": start_date, - # "finishDate": finish_date, - # "account": {"id": account.id, "name": account.name}, - # "body": body - # } + with SessionFuture() as db: + account = db.execute(select(AccountBase).where(AccountBase.id == id_)).scalar_one() + start_date = s if s is not None else get_start_date(request.session) + finish_date = f if f is not None else get_finish_date(request.session) + raise Exception("not fixed") + # for item in request.json_body["body"]: + # if "id" not in item or item["id"] is None: + # continue + # + # voucher = ( + # request.dbsession.query(Voucher) + # .where(Voucher.id == uuid.UUID(item["id"])) + # .first() + # ) + # is_reconciled = item["isReconciled"] + # reconcile_date = datetime.datetime.strptime(item["reconcileDate"], "%d-%b-%Y") + # voucher.is_reconciled = is_reconciled + # voucher.reconcile_date = reconcile_date + # transaction.commit() + # body = build_report(account.id, start_date, finish_date, request) + # return { + # "startDate": start_date, + # "finishDate": finish_date, + # "account": {"id": account.id, "name": account.name}, + # "body": body + # } diff --git a/brewman/brewman/routers/reports/stock_movement.py b/brewman/brewman/routers/reports/stock_movement.py index 5cc50de9..7564ee49 100644 --- a/brewman/brewman/routers/reports/stock_movement.py +++ b/brewman/brewman/routers/reports/stock_movement.py @@ -4,13 +4,13 @@ from typing import List import brewman.schemas.stock_movement as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security from sqlalchemy.orm import Session -from sqlalchemy.sql.expression import func +from sqlalchemy.sql.expression import func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.cost_centre import CostCentre from ...models.inventory import Inventory from ...models.journal import Journal @@ -23,25 +23,16 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.StockMovement) def report_blank( request: Request, user: UserToken = Security(get_user, scopes=["stock-movement"]), ) -> schemas.StockMovement: - return { - "startDate": get_start_date(request.session), - "finishDate": get_finish_date(request.session), - "body": [], - } + return schemas.StockMovement( + startDate=get_start_date(request.session), + finishDate=get_finish_date(request.session), + body=[], + ) @router.get("/{start}/{finish}", response_model=schemas.StockMovement) @@ -49,27 +40,25 @@ def report_data( start: str, finish: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["stock-movement"]), ) -> schemas.StockMovement: - body = build_stock_movement(datetime.strptime(start, "%d-%b-%Y"), datetime.strptime(finish, "%d-%b-%Y"), db) - set_period(start, finish, request.session) - return {"startDate": start, "finishDate": finish, "body": body} + with SessionFuture() as db: + body = build_stock_movement(datetime.strptime(start, "%d-%b-%Y"), datetime.strptime(finish, "%d-%b-%Y"), db) + set_period(start, finish, request.session) + return schemas.StockMovement(startDate=start, finishDate=finish, body=body) def build_stock_movement(start_date: date, finish_date: date, db: Session) -> List[schemas.StockMovementItem]: dict_ = {} quantity_sum = func.sum(Journal.debit * Inventory.quantity).label("quantity") - openings = ( - db.query(Product, quantity_sum) + openings = db.execute( + select(Product, quantity_sum) .join(Product.inventories) .join(Inventory.voucher) .join(Voucher.journals) - .filter(Voucher.date < start_date) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + .where(Voucher.date < start_date, Journal.cost_centre_id == CostCentre.cost_centre_purchase()) .group_by(Product) - .all() - ) + ).all() for product, quantity in openings: dict_[product.id] = schemas.StockMovementItem( id=product.id, @@ -81,18 +70,19 @@ def build_stock_movement(start_date: date, finish_date: date, db: Session) -> Li closing=0, url=["/", "product-ledger", str(product.id)], ) - purchases = ( - db.query(Product, quantity_sum) + purchases = db.execute( + select(Product, quantity_sum) .join(Product.inventories) .join(Inventory.voucher) .join(Voucher.journals) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + .where( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.type != VoucherType.by_name("Issue").id, + Journal.cost_centre_id == CostCentre.cost_centre_purchase(), + ) .group_by(Product) - .all() - ) + ).all() for product, quantity in purchases: if product.id in dict_: dict_[product.id].purchase = Decimal(round(quantity, 2)) @@ -107,18 +97,19 @@ def build_stock_movement(start_date: date, finish_date: date, db: Session) -> Li closing=0, url=["/", "product-ledger", str(product.id)], ) - issues = ( - db.query(Product, quantity_sum) + issues = db.execute( + select(Product, quantity_sum) .join(Product.inventories) .join(Inventory.voucher) .join(Voucher.journals) - .filter(Voucher.date >= start_date) - .filter(Voucher.date <= finish_date) - .filter(Voucher.type == VoucherType.by_name("Issue").id) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + .where( + Voucher.date >= start_date, + Voucher.date <= finish_date, + Voucher.type == VoucherType.by_name("Issue").id, + Journal.cost_centre_id == CostCentre.cost_centre_purchase(), + ) .group_by(Product) - .all() - ) + ).all() for product, quantity in issues: if product.id in dict_: dict_[product.id].issue = Decimal(round(quantity * -1, 2)) diff --git a/brewman/brewman/routers/reports/trial_balance.py b/brewman/brewman/routers/reports/trial_balance.py index cd75f208..e987383d 100644 --- a/brewman/brewman/routers/reports/trial_balance.py +++ b/brewman/brewman/routers/reports/trial_balance.py @@ -3,13 +3,13 @@ from typing import List import brewman.schemas.trial_balance as schemas -from fastapi import APIRouter, Depends, Request, Security +from fastapi import APIRouter, Request, Security from sqlalchemy.orm import Session -from sqlalchemy.sql.expression import func +from sqlalchemy.sql.expression import func, 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 ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.account_base import AccountBase from ...models.journal import Journal from ...models.voucher import Voucher @@ -20,15 +20,6 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=schemas.TrialBalance) def report_blank( request: Request, @@ -41,29 +32,26 @@ def report_blank( def report_data( date_: str, request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["trial-balance"]), ) -> schemas.TrialBalance: set_period(get_start_date(request.session), date_, request.session) - return schemas.TrialBalance( - date=date_, - body=build_report(datetime.strptime(date_, "%d-%b-%Y"), db), - ) + with SessionFuture() as db: + return schemas.TrialBalance( + date=date_, + body=build_report(datetime.strptime(date_, "%d-%b-%Y"), db), + ) def build_report(date_: date, db: Session) -> List[schemas.TrialBalanceItem]: amount_sum = func.sum(Journal.amount * Journal.debit).label("amount") - query = ( - db.query(AccountBase, amount_sum) + query = db.execute( + select(AccountBase, amount_sum) .join(Journal.voucher) .join(Journal.account) - .filter(Voucher.date <= date_) - .filter(Voucher.type != VoucherType.by_name("Issue").id) + .where(Voucher.date <= date_, Voucher.type != VoucherType.by_name("Issue").id) .group_by(AccountBase) - .order_by(AccountBase.type) - .order_by(func.abs(amount_sum).desc()) - .all() - ) + .order_by(AccountBase.type, func.abs(amount_sum).desc()) + ).all() body = [] for account, amount in query: diff --git a/brewman/brewman/routers/reports/unposted.py b/brewman/brewman/routers/reports/unposted.py index 24e18997..80b5bcc0 100644 --- a/brewman/brewman/routers/reports/unposted.py +++ b/brewman/brewman/routers/reports/unposted.py @@ -2,11 +2,12 @@ from typing import List import brewman.schemas.unposted as schemas -from fastapi import APIRouter, Depends, Request, Security -from sqlalchemy.orm import Session, joinedload +from fastapi import APIRouter, Security +from sqlalchemy import select +from sqlalchemy.orm import Session from ...core.security import get_current_active_user as get_user -from ...db.session import SessionLocal +from ...db.session import SessionFuture from ...models.journal import Journal from ...models.voucher import Voucher from ...models.voucher_type import VoucherType @@ -16,34 +17,27 @@ from ...schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.get("", response_model=List[schemas.Unposted]) def report_data( - request: Request, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["post-vouchers"]), ) -> List[schemas.Unposted]: - return build_report(db) + with SessionFuture() as db: + return build_report(db) def build_report(db: Session) -> List[schemas.Unposted]: body = [] query = ( - db.query(Voucher) - .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) - .filter(Voucher.posted == False) # noqa: E712 - .filter(Voucher.type != VoucherType.by_name("Issue").id) - .order_by(Voucher.date) - .order_by(Voucher.last_edit_date) + db.execute( + select(Voucher) + .join(Voucher.journals) + .join(Journal.account) + .where(Voucher.posted == False, Voucher.type != VoucherType.by_name("Issue").id) # noqa: E712 + .order_by(Voucher.date, Voucher.last_edit_date) + ) + .unique() + .scalars() .all() ) diff --git a/brewman/brewman/routers/reset_stock.py b/brewman/brewman/routers/reset_stock.py index 016705bf..85cbeb81 100644 --- a/brewman/brewman/routers/reset_stock.py +++ b/brewman/brewman/routers/reset_stock.py @@ -1,11 +1,13 @@ import uuid -from fastapi import APIRouter, Depends, HTTPException, Security, status -from sqlalchemy import func +from decimal import Decimal + +from fastapi import APIRouter, HTTPException, Security, status +from sqlalchemy import func, select from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account_base import AccountBase from ..models.batch import Batch from ..models.cost_centre import CostCentre @@ -21,65 +23,62 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("/{id_}") -def rebase( +def reset_stock( id_: uuid.UUID, item: ResetStock, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["reset-stock"]), ): - product: Product = db.query(Product).filter(Product.id == id_).first() + with SessionFuture() as db: + product: Product = db.execute(select(Product).where(Product.id == id_)).scalar_one() - if item.reset_date > item.stock_date: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Reset cannot be after the stock date", - ) + if item.reset_date > item.stock_date: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Reset cannot be after the stock date", + ) - change = round(item.quantity, 2) - get_closing_stock(product, item.stock_date, db=db) - if change == 0: - return {"No Change Needed"} - final = get_closing_stock(product, db=db) - if final + change < 0: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Current Quantity will get negative. Cannot proceed", - ) + change = round(item.quantity, 2) - get_closing_stock(product, item.stock_date, db=db) + if change == 0: + return {"No Change Needed"} + final = get_closing_stock(product, db=db) + if final + change < 0: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Current Quantity will get negative. Cannot proceed", + ) - batch = get_last_batch(product, db) - set_batches(batch, final + change, db) + batch = get_last_batch(product, db) + set_batches(batch, final + change, db) - create_voucher(batch, change, item.reset_date, user.id_, db) - db.commit() - return {} + create_voucher(batch, change, item.reset_date, user.id_, db) + db.commit() + return {} def get_closing_stock(product, finish_date=None, db=None): query = ( - db.query(func.sum(Inventory.quantity * Journal.debit)) + select(func.sum(Inventory.quantity * Journal.debit)) .join(Voucher) - .filter(Voucher.id == Inventory.voucher_id) - .filter(Voucher.id == Journal.voucher_id) - .filter(Inventory.product_id == product.id) - .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) + .where( + Voucher.id == Inventory.voucher_id, + Voucher.id == Journal.voucher_id, + Inventory.product_id == product.id, + Journal.cost_centre_id == CostCentre.cost_centre_purchase(), + ) ) if finish_date is not None: - query = query.filter(Voucher.date <= finish_date) - query = query.one() - return 0 if query is None else query[0] + query = query.where(Voucher.date <= finish_date) + result = db.execute(query).scalar() + return 0 if result is None else result def get_last_batch(product, db): - batch = db.query(Batch).filter(Batch.product_id == product.id).order_by(Batch.name.desc()).first() + batch = ( + db.execute(select(Batch).where(Batch.product_id == product.id).order_by(Batch.name.desc())) + .scalars() + .one_or_none() + ) if batch is None: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -88,12 +87,13 @@ def get_last_batch(product, db): return batch -def set_batches(batch, quantity, db): +def set_batches(batch: Batch, quantity: Decimal, db: Session): batch.quantity_remaining = quantity - batches = db.query(Batch).filter(Batch.id != batch.id).filter(Batch.product_id == batch.product_id) + batches = ( + db.execute(select(Batch).where(Batch.id != batch.id, Batch.product_id == batch.product_id)).scalars().all() + ) for item in batches: item.quantity_remaining = 0 - pass def create_voucher(batch, quantity, date_, user_id, db): diff --git a/brewman/brewman/routers/auth/role.py b/brewman/brewman/routers/role.py similarity index 51% rename from brewman/brewman/routers/auth/role.py rename to brewman/brewman/routers/role.py index 3147ef38..86182e3e 100644 --- a/brewman/brewman/routers/auth/role.py +++ b/brewman/brewman/routers/role.py @@ -4,74 +4,61 @@ from typing import List import brewman.schemas.role as schemas -from fastapi import APIRouter, Depends, HTTPException, Security, status +from fastapi import APIRouter, HTTPException, Security, status +from sqlalchemy import select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session +from sqlalchemy.sql.functions import count -from ...core.security import get_current_active_user as get_user -from ...db.session import SessionLocal -from ...models.permission import Permission -from ...models.role import Role -from ...schemas.user import UserToken +from ..core.security import get_current_active_user as get_user +from ..db.session import SessionFuture +from ..models.permission import Permission +from ..models.role import Role +from ..models.role_permission import role_permission +from ..models.user_role import user_role +from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db(): - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("", response_model=schemas.Role) def save( data: schemas.RoleIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["users"]), ) -> schemas.Role: try: - item = Role(data.name) - db.add(item) - add_permissions(item, data.permissions, db) - db.commit() - return role_info(item, db) + with SessionFuture() as db: + item = Role(data.name) + db.add(item) + add_permissions(item, data.permissions, db) + db.commit() + return role_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise @router.put("/{id_}", response_model=schemas.Role) -def update( +def update_route( id_: uuid.UUID, data: schemas.RoleIn, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["users"]), ) -> schemas.Role: try: - item: Role = db.query(Role).filter(Role.id == id_).first() - item.name = data.name - add_permissions(item, data.permissions, db) - db.commit() - return role_info(item, db) + with SessionFuture() as db: + item: Role = db.execute(select(Role).where(Role.id == id_)).scalar_one() + item.name = data.name + add_permissions(item, data.permissions, db) + db.commit() + return role_info(item, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def add_permissions(role: Role, permissions: List[schemas.PermissionItem], db: Session) -> None: @@ -79,70 +66,65 @@ def add_permissions(role: Role, permissions: List[schemas.PermissionItem], db: S gp = [p for p in role.permissions if p.id == permission.id_] gp = None if len(gp) == 0 else gp[0] if permission.enabled and gp is None: - role.permissions.append(db.query(Permission).filter(Permission.id == permission.id_).one()) + role.permissions.append(db.execute(select(Permission).where(Permission.id == permission.id_)).scalar_one()) elif not permission.enabled and gp: role.permissions.remove(gp) @router.delete("/{id_}", response_model=schemas.RoleBlank) -def delete( +def delete_route( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["users"]), ) -> schemas.RoleBlank: - try: - item: Role = db.query(Role).filter(Role.id == id_).first() - if item is None: + with SessionFuture() as db: + if db.execute(select(count(user_role.c.id)).where(user_role.c.role_id == id_)).scalar_one() > 0: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Role not found", + detail="This Role has been assigned to users and cannot be deleted.", ) - else: + if db.execute(select(count(role_permission.c.id)).where(role_permission.c.role_id == id_)).scalar_one() > 0: raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Role deletion not implemented", + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="This Role has permissions and cannot be deleted.", ) - except Exception: - db.rollback() - raise + # item: Role = db.execute(select(Role).where(Role.id == id_)).scalar_one() + item: Role = db.execute(select(Role).where(Role.id == id_)).scalar_one() + db.delete(item) + db.commit() + return role_blank(db) @router.get("", response_model=schemas.RoleBlank) def show_blank( - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["users"]), ) -> schemas.RoleBlank: - return role_blank(db) + with SessionFuture() as db: + return role_blank(db) @router.get("/list", response_model=List[schemas.RoleList]) async def show_list( - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["users"]), ) -> List[schemas.RoleList]: - return [ - schemas.RoleList( - id=item.id, - name=item.name, - permissions=[p.name for p in sorted(item.permissions, key=lambda p: p.name)], - ) - for item in db.query(Role).order_by(Role.name).all() - ] + with SessionFuture() as db: + return [ + schemas.RoleList( + id=item.id, + name=item.name, + permissions=[p.name for p in sorted(item.permissions, key=lambda p: p.name)], + ) + for item in db.execute(select(Role).order_by(Role.name)).scalars().all() + ] @router.get("/{id_}", response_model=schemas.Role) def show_id( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["users"]), ) -> schemas.Role: - item: Role = db.query(Role).filter(Role.id == id_).first() - if item is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Role not found", - ) - return role_info(item, db) + with SessionFuture() as db: + item: Role = db.execute(select(Role).where(Role.id == id_)).scalar_one() + return role_info(item, db) def role_info(item: Role, db: Session) -> schemas.Role: @@ -155,7 +137,7 @@ def role_info(item: Role, db: Session) -> schemas.Role: name=p.name, enabled=True if p in item.permissions else False, ) - for p in db.query(Permission).order_by(Permission.name).all() + for p in db.execute(select(Permission).order_by(Permission.name)).scalars().all() ], ) @@ -165,6 +147,6 @@ def role_blank(db: Session) -> schemas.RoleBlank: name="", permissions=[ schemas.PermissionItem(id=p.id, name=p.name, enabled=False) - for p in db.query(Permission).order_by(Permission.name).all() + 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 new file mode 100644 index 00000000..98f0f5f8 --- /dev/null +++ b/brewman/brewman/routers/user.py @@ -0,0 +1,206 @@ +import uuid + +from typing import List + +import brewman.schemas.user as schemas + +from fastapi import APIRouter, Depends, HTTPException, Security, status +from sqlalchemy import delete, select, update +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import Session + +from ..core.security import get_current_active_user as get_user +from ..db.session import SessionFuture +from ..models.attendance import Attendance +from ..models.login_history import LoginHistory +from ..models.role import Role +from ..models.user import User +from ..models.user_role import user_role +from ..models.voucher import Voucher +from ..schemas.user import UserToken + + +router = APIRouter() + + +@router.post("", response_model=schemas.User) +def save( + data: schemas.UserIn, + user: UserToken = Security(get_user, scopes=["users"]), +) -> schemas.User: + try: + with SessionFuture() as db: + item = User(name=data.name, password=data.password, locked_out=data.locked_out) + db.add(item) + add_roles(item, data.roles, db) + db.commit() + return user_info(item, db, user) + except SQLAlchemyError as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +@router.get("/me", response_model=schemas.User) +def show_me( + user: UserToken = Depends(get_user), +) -> schemas.User: + with SessionFuture() as db: + item = db.execute(select(User).where(User.id == user.id_)).scalar_one() + return user_info(item, db, user) + + +@router.put("/me", response_model=schemas.User) +def update_me( + data: schemas.UserIn, + user: UserToken = Depends(get_user), +) -> schemas.User: + try: + with SessionFuture() as db: + item: User = db.execute(select(User).where(User.id == user.id_)).scalar_one() + if "users" in user.permissions: + item.name = data.name + item.locked_out = data.locked_out + add_roles(item, data.roles, db) + if data.password and item.password != data.password: + item.password = data.password + db.commit() + return user_info(item, db, user) + except SQLAlchemyError as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +@router.put("/{id_}", response_model=schemas.User) +def update_route( + id_: uuid.UUID, + data: schemas.UserIn, + user: UserToken = Security(get_user, scopes=["users"]), +) -> schemas.User: + try: + with SessionFuture() as db: + item: User = db.execute(select(User).where(User.id == id_)).scalar_one() + item.name = data.name + if data.password and item.password != data.password: + item.password = data.password + item.locked_out = data.locked_out + add_roles(item, data.roles, db) + db.commit() + return user_info(item, db, user) + except SQLAlchemyError as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +def add_roles(user: User, roles: List[schemas.RoleItem], db: Session) -> None: + for role in roles: + ug = [g for g in user.roles if g.id == role.id_] + ug = None if len(ug) == 0 else ug[0] + if role.enabled and ug is None: + user.roles.append(db.execute(select(Role).where(Role.id == role.id_)).one()) + elif not role.enabled and ug: + user.roles.remove(ug) + + +@router.delete("/{id_}") +def delete_route( + id_: uuid.UUID, + user: UserToken = Security(get_user, scopes=["users"]), +): + if id_ == user.id_: + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="You cannot delete yourself", + ) + with SessionFuture() as db: + item: User = db.execute(select(User).where(User.id == id_)).scalar_one() + admin: User = db.execute(select(User).where(User.name.ilike("admin"))).scalar_one() + db.execute(update(Voucher).where(Voucher.user_id == item.id).values(user_id=admin.id)) + db.execute(update(Attendance).where(Attendance.user_id == item.id).values(user_id=admin.id)) + db.execute(delete(LoginHistory).where(LoginHistory.user_id == item.id)) + db.execute(delete(user_role).where(user_role.c.user_id == item.id)) + db.execute(delete(User).where(User.id == item.id)) + db.commit() + + +@router.get("", response_model=schemas.UserBlank) +def show_blank( + user: UserToken = Security(get_user, scopes=["users"]), +) -> schemas.UserBlank: + with SessionFuture() as db: + return user_blank(db) + + +@router.get("/list", response_model=List[schemas.UserList]) +async def show_list( + user: UserToken = Security(get_user, scopes=["users"]), +) -> List[schemas.UserList]: + with SessionFuture() as db: + return [ + schemas.UserList( + id=item.id, + name=item.name, + lockedOut=item.locked_out, + roles=[p.name for p in sorted(item.roles, key=lambda p: p.name)], + lastDevice=item.login_history[0].client.name if len(item.login_history) else "None", + lastDate=item.login_history[0].date if len(item.login_history) else None, + ) + for item in db.execute(select(User).order_by(User.name)).scalars().all() + ] + + +@router.get("/active") +async def show_active(user: UserToken = Depends(get_user)): + with SessionFuture() as db: + return [ + {"name": name} + for name in db.execute(select(User.name).where(User.locked_out == False).order_by(User.name)) # noqa: E712 + .scalars() + .all() + ] + + +@router.get("/{id_}", response_model=schemas.User) +def show_id( + id_: uuid.UUID, + user: UserToken = Security(get_user, scopes=["users"]), +) -> schemas.User: + with SessionFuture() as db: + item = db.execute(select(User).where(User.id == id_)).scalar_one() + return user_info(item, db, user) + + +def user_info(item: User, db: Session, user: UserToken) -> schemas.User: + return schemas.User( + id=item.id, + name=item.name, + password="", + lockedOut=item.locked_out, + roles=[ + schemas.RoleItem( + id=r.id, + name=r.name, + enabled=True if r in item.roles else False, + ) + for r in db.execute(select(Role).order_by(Role.name)).scalars().all() + ] + if "advanced-delete" in user.permissions + else [], + ) + + +def user_blank(db: Session) -> schemas.UserBlank: + return schemas.UserBlank( + name="", + password="", + lockedOut=False, + roles=[ + schemas.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 c173d639..cdc270c6 100644 --- a/brewman/brewman/routers/voucher.py +++ b/brewman/brewman/routers/voucher.py @@ -6,14 +6,14 @@ from typing import Optional import brewman.schemas.voucher as output -from fastapi import APIRouter, Depends, HTTPException, Security, status -from sqlalchemy import and_, func, or_ +from fastapi import APIRouter, HTTPException, Security, status +from sqlalchemy import and_, func, or_, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_first_day -from ..db.session import SessionLocal +from ..db.session import SessionFuture from ..models.account import Account from ..models.account_base import AccountBase from ..models.attendance import Attendance @@ -32,37 +32,24 @@ from ..schemas.user import UserToken router = APIRouter() -# Dependency -def get_db() -> Session: - try: - db = SessionLocal() - yield db - finally: - db.close() - - @router.post("/post-voucher/{id_}", response_model=output.Voucher) def post_voucher( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["post-vouchers"]), ): try: - voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - check_voucher_lock_info(voucher.date, voucher.date, db) - voucher.posted = True - voucher.poster_id = user.id_ - db.commit() - return voucher_info(voucher, db) + with SessionFuture() as db: + voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() + check_voucher_lock_info(voucher.date, voucher.date, db) + voucher.posted = True + voucher.poster_id = user.id_ + db.commit() + return voucher_info(voucher, db) except SQLAlchemyError as e: - db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) - except Exception: - db.rollback() - raise def check_delete_permissions(voucher: Voucher, user: UserToken): @@ -89,66 +76,63 @@ def check_delete_permissions(voucher: Voucher, user: UserToken): @router.delete("/delete/{id_}") def delete_voucher( id_: uuid.UUID, - db: Session = Depends(get_db), user: UserToken = Security(get_user), ): - voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() - images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all() - check_delete_permissions(voucher, user) - check_voucher_lock_info(None, voucher.date, db) - json_voucher = voucher_info(voucher, db) - batches_to_delete = [] - if voucher.type == VoucherType.by_name("Issue").id: - for item in voucher.journals: - if item.debit == 1: - destination = item.cost_centre_id + with SessionFuture() as db: + voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() + images = db.execute(select(DbImage).where(DbImage.resource_id == voucher.id)).scalars().all() + check_delete_permissions(voucher, user) + check_voucher_lock_info(None, voucher.date, db) + json_voucher = voucher_info(voucher, db) + batches_to_delete = [] + if voucher.type == VoucherType.by_name("Issue").id: + for item in voucher.journals: + if item.debit == 1: + destination = item.cost_centre_id + else: + source = item.cost_centre_id + if source == CostCentre.cost_centre_purchase(): + batch_consumed = True + elif destination == CostCentre.cost_centre_purchase(): + batch_consumed = False else: - source = item.cost_centre_id - if source == CostCentre.cost_centre_purchase(): - batch_consumed = True - elif destination == CostCentre.cost_centre_purchase(): - batch_consumed = False - else: - batch_consumed = None + batch_consumed = None - if batch_consumed is None: - pass - elif batch_consumed: + if batch_consumed is None: + pass + elif batch_consumed: + for item in voucher.inventories: + item.batch.quantity_remaining += item.quantity + else: + for item in voucher.inventories: + if item.batch.quantity_remaining < item.quantity: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"Only {item.batch.quantity_remaining} of {item.product.name} remaining.\n" + f"So it cannot be deleted", + ) + item.batch.quantity_remaining -= item.quantity + elif voucher.type == VoucherType.by_name("Purchase").id: for item in voucher.inventories: - item.batch.quantity_remaining += item.quantity - else: - for item in voucher.inventories: - if item.batch.quantity_remaining < item.quantity: + uses = db.execute( + select(func.count(Inventory.id)).where(Inventory.batch_id == item.batch.id, Inventory.id != item.id) + ).scalar() + if uses > 0: raise HTTPException( status_code=status.HTTP_423_LOCKED, - detail=f"Only {item.batch.quantity_remaining} of {item.product.name} remaining.\n" - f"So it cannot be deleted", + detail=f"{item.product.name} has been issued and cannot be deleted", ) - item.batch.quantity_remaining -= item.quantity - elif voucher.type == VoucherType.by_name("Purchase").id: - for item in voucher.inventories: - uses = ( - db.query(func.count(Inventory.id)) - .filter(Inventory.batch_id == item.batch.id) - .filter(Inventory.id != item.id) - .scalar() - ) - if uses > 0: - raise HTTPException( - status_code=status.HTTP_423_LOCKED, - detail=f"{item.product.name} has been issued and cannot be deleted", - ) - batches_to_delete.append(item.batch) - elif voucher.type == VoucherType.by_name("Purchase Return").id: - for item in voucher.inventories: - item.batch.quantity_remaining += item.quantity - for b in batches_to_delete: - db.delete(b) - db.delete(voucher) - for image in images: - db.delete(image) - db.commit() - return blank_voucher(info=json_voucher, db=db) + batches_to_delete.append(item.batch) + elif voucher.type == VoucherType.by_name("Purchase Return").id: + for item in voucher.inventories: + item.batch.quantity_remaining += item.quantity + for b in batches_to_delete: + db.delete(b) + db.delete(voucher) + for image in images: + db.delete(image) + db.commit() + return blank_voucher(info=json_voucher, db=db) def voucher_info(voucher, db): @@ -215,7 +199,7 @@ def voucher_info(voucher, db): } ) for item in voucher.incentives: - employee = db.query(Employee).filter(Employee.id == item.journal.account_id).first() + employee = db.execute(select(Employee).where(Employee.id == item.journal.account_id)).scalar_one() json_voucher["incentives"].append( { "employeeId": item.journal.account_id, @@ -261,7 +245,7 @@ def voucher_info(voucher, db): }, } ) - images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all() + images = db.execute(select(DbImage).where(DbImage.resource_id == voucher.id)).scalars().all() for image in images: json_voucher["files"].append( { @@ -300,7 +284,11 @@ def blank_voucher(info, db): elif type_ == "Payment": account = None if info and "account" in info and info["account"]: - account = db.query(AccountBase).filter(AccountBase.id == uuid.UUID(info["account"])).first() + account = ( + db.execute(select(AccountBase).where(AccountBase.id == uuid.UUID(info["account"]))) + .scalars() + .one_or_none() + ) if account is not None: account = {"id": account.id, "name": account.name} else: @@ -309,7 +297,11 @@ def blank_voucher(info, db): elif type_ == "Receipt": account = None if info and "account" in info and info["account"]: - account = db.query(AccountBase).filter(AccountBase.id == uuid.UUID(info["account"])).first() + account = ( + db.execute(select(AccountBase).where(AccountBase.id == uuid.UUID(info["account"]))) + .scalars() + .one_or_none() + ) if account is not None: account = {"id": account.id, "name": account.name} else: @@ -348,21 +340,25 @@ def incentive_employees(date_, db: Session): finish_date = date_ details = [] employees = ( - db.query(Employee) - .filter(Employee.joining_date <= finish_date) - .filter(or_(Employee.is_active, Employee.leaving_date >= start_date)) - .order_by(Employee.cost_centre_id) - .order_by(Employee.designation) - .order_by(Employee.name) + db.execute( + select(Employee) + .where(Employee.joining_date <= finish_date, or_(Employee.is_active, Employee.leaving_date >= start_date)) + .order_by(Employee.cost_centre_id, Employee.designation, Employee.name) + ) + .scalars() .all() ) for employee in employees: att = ( - db.query(Attendance) - .filter(Attendance.employee_id == employee.id) - .filter(Attendance.date >= start_date) - .filter(Attendance.date <= finish_date) - .filter(Attendance.is_valid == True) # noqa: E712 + db.execute( + select(Attendance).where( + Attendance.employee_id == employee.id, + Attendance.date >= start_date, + Attendance.date <= finish_date, + Attendance.is_valid == True, # noqa: E712 + ) + ) + .scalars() .all() ) att = sum(map(lambda x: AttendanceType.by_id(x.attendance_type).value, att)) @@ -377,10 +373,10 @@ def incentive_employees(date_, db: Session): } ) - amount = ( - db.query(func.sum(Journal.amount * Journal.debit)) + amount = db.execute( + select(func.sum(Journal.amount * Journal.debit)) .join(Journal.voucher) - .filter( + .where( Journal.account_id == Account.incentive_id(), Voucher.type != VoucherType.by_name("Issue").id, or_( @@ -391,8 +387,7 @@ def incentive_employees(date_, db: Session): ), ), ) - .scalar() - ) + ).scalar() amount = 0 if amount is None else amount * Decimal(0.9) * -1 return details, amount diff --git a/brewman/brewman/schemas/account.py b/brewman/brewman/schemas/account.py index c0165108..019993d0 100644 --- a/brewman/brewman/schemas/account.py +++ b/brewman/brewman/schemas/account.py @@ -2,10 +2,11 @@ import uuid from typing import Optional -from brewman.schemas import to_camel -from brewman.schemas.cost_centre import CostCentreLink from pydantic import BaseModel, Field +from . import to_camel +from .cost_centre import CostCentreLink + class AccountLink(BaseModel): id_: uuid.UUID = Field(...) diff --git a/brewman/brewman/schemas/balance_sheet.py b/brewman/brewman/schemas/balance_sheet.py index a59e4992..8fe07aa6 100644 --- a/brewman/brewman/schemas/balance_sheet.py +++ b/brewman/brewman/schemas/balance_sheet.py @@ -2,10 +2,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class BalanceSheetItem(BaseModel): name: Optional[str] diff --git a/brewman/brewman/schemas/cash_flow.py b/brewman/brewman/schemas/cash_flow.py index 719942cd..5fefc794 100644 --- a/brewman/brewman/schemas/cash_flow.py +++ b/brewman/brewman/schemas/cash_flow.py @@ -2,10 +2,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel from pydantic import Field, validator from pydantic.main import BaseModel +from . import to_camel + class CashFlowItem(BaseModel): name: str diff --git a/brewman/brewman/schemas/client.py b/brewman/brewman/schemas/client.py index a88840af..99560d23 100644 --- a/brewman/brewman/schemas/client.py +++ b/brewman/brewman/schemas/client.py @@ -3,9 +3,10 @@ import uuid from datetime import datetime from typing import Optional -from brewman.schemas import to_camel from pydantic.main import BaseModel +from . import to_camel + class ClientIn(BaseModel): name: str diff --git a/brewman/brewman/schemas/closing_stock.py b/brewman/brewman/schemas/closing_stock.py index 4f81b433..ec5e847e 100644 --- a/brewman/brewman/schemas/closing_stock.py +++ b/brewman/brewman/schemas/closing_stock.py @@ -2,10 +2,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class ClosingStockItem(BaseModel): product: str diff --git a/brewman/brewman/schemas/cost_centre.py b/brewman/brewman/schemas/cost_centre.py index a6cb6c5b..96b74d4d 100644 --- a/brewman/brewman/schemas/cost_centre.py +++ b/brewman/brewman/schemas/cost_centre.py @@ -2,9 +2,10 @@ import uuid from typing import Optional -from brewman.schemas import to_camel from pydantic import BaseModel, Field +from . import to_camel + class CostCentreIn(BaseModel): name: str = Field(..., min_length=1) diff --git a/brewman/brewman/schemas/daybook.py b/brewman/brewman/schemas/daybook.py index c21054ba..0cb82a18 100644 --- a/brewman/brewman/schemas/daybook.py +++ b/brewman/brewman/schemas/daybook.py @@ -4,10 +4,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class DaybookItem(BaseModel): id_: Optional[uuid.UUID] diff --git a/brewman/brewman/schemas/employee.py b/brewman/brewman/schemas/employee.py index 82574317..343b41ea 100644 --- a/brewman/brewman/schemas/employee.py +++ b/brewman/brewman/schemas/employee.py @@ -4,11 +4,12 @@ from datetime import date, datetime from decimal import Decimal from typing import Optional -from brewman.schemas import to_camel -from brewman.schemas.account import AccountBase -from brewman.schemas.cost_centre import CostCentreLink from pydantic import BaseModel, Field, validator +from . import to_camel +from .account import AccountBase +from .cost_centre import CostCentreLink + class EmployeeIn(AccountBase): designation: str diff --git a/brewman/brewman/schemas/input.py b/brewman/brewman/schemas/input.py index fc9eaeef..c46fbff8 100644 --- a/brewman/brewman/schemas/input.py +++ b/brewman/brewman/schemas/input.py @@ -5,19 +5,14 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel -from brewman.schemas.account import AccountLink -from brewman.schemas.cost_centre import CostCentreLink -from brewman.schemas.voucher import ( - EmployeeBenefit, - Incentive, - Inventory, - Journal, - VoucherIn, -) from fastapi import Form from pydantic import BaseModel, Field, validator +from . import to_camel +from .account import AccountLink +from .cost_centre import CostCentreLink +from .voucher import EmployeeBenefit, Incentive, Inventory, Journal, VoucherIn + class JournalIn(VoucherIn): journals: List[Journal] diff --git a/brewman/brewman/schemas/ledger.py b/brewman/brewman/schemas/ledger.py index d449c4ca..5bad631f 100644 --- a/brewman/brewman/schemas/ledger.py +++ b/brewman/brewman/schemas/ledger.py @@ -4,11 +4,12 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel -from brewman.schemas.account import AccountLink from pydantic import validator from pydantic.main import BaseModel +from . import to_camel +from .account import AccountLink + class LedgerItem(BaseModel): id_: Optional[uuid.UUID] diff --git a/brewman/brewman/schemas/login_history.py b/brewman/brewman/schemas/login_history.py index 1cc0435b..f629849b 100644 --- a/brewman/brewman/schemas/login_history.py +++ b/brewman/brewman/schemas/login_history.py @@ -2,9 +2,10 @@ import uuid from datetime import datetime -from brewman.schemas import to_camel from pydantic import BaseModel +from . import to_camel + class LoginHistory(BaseModel): id_: uuid.UUID diff --git a/brewman/brewman/schemas/net_transactions.py b/brewman/brewman/schemas/net_transactions.py index 20523594..20497da2 100644 --- a/brewman/brewman/schemas/net_transactions.py +++ b/brewman/brewman/schemas/net_transactions.py @@ -2,10 +2,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class NetTransactionsItem(BaseModel): type_: str diff --git a/brewman/brewman/schemas/product.py b/brewman/brewman/schemas/product.py index 090e3b8d..f16dbe64 100644 --- a/brewman/brewman/schemas/product.py +++ b/brewman/brewman/schemas/product.py @@ -3,10 +3,11 @@ import uuid from decimal import Decimal from typing import Optional -from brewman.schemas import to_camel -from brewman.schemas.product_group import ProductGroupLink from pydantic import BaseModel, Field +from . import to_camel +from .product_group import ProductGroupLink + class ProductLink(BaseModel): id_: uuid.UUID = Field(...) diff --git a/brewman/brewman/schemas/product_group.py b/brewman/brewman/schemas/product_group.py index c7af927e..56181a1e 100644 --- a/brewman/brewman/schemas/product_group.py +++ b/brewman/brewman/schemas/product_group.py @@ -2,9 +2,10 @@ import uuid from typing import Optional -from brewman.schemas import to_camel from pydantic import BaseModel, Field +from . import to_camel + class ProductGroupIn(BaseModel): name: str = Field(..., min_length=1) diff --git a/brewman/brewman/schemas/product_ledger.py b/brewman/brewman/schemas/product_ledger.py index 5cbfb023..95020bd0 100644 --- a/brewman/brewman/schemas/product_ledger.py +++ b/brewman/brewman/schemas/product_ledger.py @@ -4,11 +4,12 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel -from brewman.schemas.product import ProductLink from pydantic import validator from pydantic.main import BaseModel +from . import to_camel +from .product import ProductLink + class ProductLedgerItem(BaseModel): id_: Optional[uuid.UUID] diff --git a/brewman/brewman/schemas/profit_loss.py b/brewman/brewman/schemas/profit_loss.py index 392b02fd..dd2e7868 100644 --- a/brewman/brewman/schemas/profit_loss.py +++ b/brewman/brewman/schemas/profit_loss.py @@ -2,10 +2,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class ProfitLossItem(BaseModel): group: Optional[str] diff --git a/brewman/brewman/schemas/purchase_entries.py b/brewman/brewman/schemas/purchase_entries.py index f3c7e5c7..59960a6d 100644 --- a/brewman/brewman/schemas/purchase_entries.py +++ b/brewman/brewman/schemas/purchase_entries.py @@ -2,10 +2,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class PurchaseEntriesItem(BaseModel): date_: date diff --git a/brewman/brewman/schemas/purchases.py b/brewman/brewman/schemas/purchases.py index 376e20d5..1e200f0f 100644 --- a/brewman/brewman/schemas/purchases.py +++ b/brewman/brewman/schemas/purchases.py @@ -2,10 +2,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class PurchasesItem(BaseModel): name: str diff --git a/brewman/brewman/schemas/raw_material_cost.py b/brewman/brewman/schemas/raw_material_cost.py index 19b11ceb..6da49095 100644 --- a/brewman/brewman/schemas/raw_material_cost.py +++ b/brewman/brewman/schemas/raw_material_cost.py @@ -4,10 +4,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class RawMaterialCostItem(BaseModel): name: Optional[str] diff --git a/brewman/brewman/schemas/recipe.py b/brewman/brewman/schemas/recipe.py index d510df4a..bf3b5a41 100644 --- a/brewman/brewman/schemas/recipe.py +++ b/brewman/brewman/schemas/recipe.py @@ -2,29 +2,71 @@ import uuid from datetime import date from decimal import Decimal +from typing import List, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Field + +from . import to_camel -class Recipe(BaseModel): - id_: uuid.UUID - product_id: uuid.UUID +class ProductLink(BaseModel): + id_: uuid.UUID = Field(...) + name: Optional[str] + units: Optional[str] + sale_price: Optional[Decimal] + is_sold: Optional[bool] + fraction_units: Optional[str] + fraction: Optional[Decimal] + product_yield: Optional[Decimal] + + class Config: + alias_generator = to_camel + + +class RecipeItem(BaseModel): + id_: Optional[uuid.UUID] + product: ProductLink + quantity: int + price: int + + class Config: + fields = {"id_": "id"} + + +class RecipeIn(BaseModel): + product: ProductLink quantity: Decimal cost_price: Decimal sale_price: Decimal notes: str - valid_from: date - valid_till: date + valid_from: Optional[date] + valid_till: Optional[date] - effective_from: date - effective_till: date + effective_from: Optional[date] + effective_till: Optional[date] + + items: List[RecipeItem] + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + json_encoders = {date: lambda v: v.strftime("%d-%b-%Y") if v is not None else None} -class RecipeItem(BaseModel): +class Recipe(RecipeIn): id_: uuid.UUID - recipe_id: uuid.UUID - product_id: uuid.UUID - quantity: int - price: int + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + json_encoders = {date: lambda v: v.strftime("%d-%b-%Y")} + + +class RecipeBlank(RecipeIn): + product: Optional[ProductLink] + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel diff --git a/brewman/brewman/schemas/role.py b/brewman/brewman/schemas/role.py index c3c3e5db..93c90cf4 100644 --- a/brewman/brewman/schemas/role.py +++ b/brewman/brewman/schemas/role.py @@ -2,10 +2,11 @@ import uuid from typing import List -from brewman.schemas.permission import PermissionItem from pydantic import Field from pydantic.main import BaseModel +from .permission import PermissionItem + class RoleIn(BaseModel): name: str = Field(..., min_length=1) diff --git a/brewman/brewman/schemas/settings.py b/brewman/brewman/schemas/settings.py index 9f07af53..6df8857a 100644 --- a/brewman/brewman/schemas/settings.py +++ b/brewman/brewman/schemas/settings.py @@ -2,9 +2,10 @@ from datetime import date, datetime from decimal import Decimal from typing import Optional -from brewman.schemas import to_camel from pydantic import BaseModel, validator +from . import to_camel + class LockInformation(BaseModel): lock_older: bool diff --git a/brewman/brewman/schemas/stock_movement.py b/brewman/brewman/schemas/stock_movement.py index eb8ebec1..859bc0ed 100644 --- a/brewman/brewman/schemas/stock_movement.py +++ b/brewman/brewman/schemas/stock_movement.py @@ -4,10 +4,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class StockMovementItem(BaseModel): id_: uuid.UUID diff --git a/brewman/brewman/schemas/trial_balance.py b/brewman/brewman/schemas/trial_balance.py index da49ce1a..a6c6ec0d 100644 --- a/brewman/brewman/schemas/trial_balance.py +++ b/brewman/brewman/schemas/trial_balance.py @@ -2,10 +2,11 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel from pydantic import validator from pydantic.main import BaseModel +from . import to_camel + class TrialBalanceItem(BaseModel): type_: Optional[str] diff --git a/brewman/brewman/schemas/unposted.py b/brewman/brewman/schemas/unposted.py index 75a5c27e..9a39931c 100644 --- a/brewman/brewman/schemas/unposted.py +++ b/brewman/brewman/schemas/unposted.py @@ -4,9 +4,10 @@ from datetime import date, datetime from decimal import Decimal from typing import List -from brewman.schemas import to_camel from pydantic import BaseModel, validator +from . import to_camel + class Unposted(BaseModel): id_: uuid.UUID diff --git a/brewman/brewman/schemas/user.py b/brewman/brewman/schemas/user.py index b150caa0..b63652e8 100644 --- a/brewman/brewman/schemas/user.py +++ b/brewman/brewman/schemas/user.py @@ -3,11 +3,12 @@ import uuid from datetime import datetime from typing import List, Optional -from brewman.schemas import to_camel -from brewman.schemas.role import RoleItem from pydantic import Field from pydantic.main import BaseModel +from . import to_camel +from .role import RoleItem + class UserIn(BaseModel): name: str = Field(..., min_length=1) diff --git a/brewman/brewman/schemas/voucher.py b/brewman/brewman/schemas/voucher.py index 4250c8cc..891d97ec 100644 --- a/brewman/brewman/schemas/voucher.py +++ b/brewman/brewman/schemas/voucher.py @@ -5,14 +5,15 @@ from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from brewman.schemas import to_camel -from brewman.schemas.account import AccountLink -from brewman.schemas.cost_centre import CostCentreLink -from brewman.schemas.employee import EmployeeLink -from brewman.schemas.product import ProductLink from fastapi import Form from pydantic import BaseModel, Field, validator +from . import to_camel +from .account import AccountLink +from .cost_centre import CostCentreLink +from .employee import EmployeeLink +from .product import ProductLink + class ImageUpload(BaseModel): id_: Optional[uuid.UUID] diff --git a/brewman/brewman/scripts/initializedb.py b/brewman/brewman/scripts/initializedb.py index d5415053..721bc0f9 100644 --- a/brewman/brewman/scripts/initializedb.py +++ b/brewman/brewman/scripts/initializedb.py @@ -1,68 +1,3 @@ -# import os -# import sys -# import uuid -# -# from brewman.models.auth import ( -# Client, -# Role, -# LoginHistory, -# Permission, -# User, -# role_permission, -# user_role, -# ) -# from brewman.models.master import ( -# Account, -# AccountBase, -# CostCentre, -# DbSetting, -# Employee, -# Product, -# ProductGroup, -# Recipe, -# RecipeItem, -# ) -# from brewman.models.voucher import ( -# Attendance, -# Batch, -# DbImage, -# EmployeeBenefit, -# Fingerprint, -# Incentive, -# Inventory, -# Journal, -# Product, -# Voucher, -# ) -# from pyramid.paster import get_appsettings, setup_logging -# from pyramid.scripts.common import parse_vars -# -# from ..models import get_engine, get_session_factory, get_tm_session -# from ..models.meta import Base -# -# -# def usage(argv): -# cmd = os.path.basename(argv[0]) -# print("usage: %s [var=value]\n" '(example: "%s development.ini")' % (cmd, cmd)) -# sys.exit(1) -# -# -# def main(argv=sys.argv): -# if len(argv) < 2: -# usage(argv) -# config_uri = argv[1] -# options = parse_vars(argv[2:]) -# setup_logging(config_uri) -# settings = get_appsettings(config_uri, options=options) -# -# engine = get_engine(settings) -# Base.metadata.create_all(engine) -# -# session_factory = get_session_factory(engine) -# -# with transaction.manager: -# dbsession = get_tm_session(session_factory, transaction.manager) -# # user = User("Admin", "123456", False, uuid.UUID("8de98592-76d9-c74d-bb3f-d6184d388b5a")) # dbsession.add(user) # diff --git a/brewman/pyproject.toml b/brewman/pyproject.toml index 10b1cc24..b04cff23 100644 --- a/brewman/pyproject.toml +++ b/brewman/pyproject.toml @@ -6,25 +6,25 @@ authors = ["tanshu "] [tool.poetry.dependencies] python = "^3.8" -uvicorn = {extras = ["standard"], version = "^0.13.4"} -fastapi = "^0.63.0" -python-jose = {extras = ["cryptography"], version = "^3.2.0"} -passlib = {extras = ["bcrypt"], version = "^1.7.3"} -psycopg2-binary = "^2.8.6" -SQLAlchemy = "^1.4.4" +uvicorn = {extras = ["standard"], version = "^0.15.0"} +fastapi = "^0.68.1" +python-jose = {extras = ["cryptography"], version = "^3.3.0"} +passlib = {extras = ["bcrypt"], version = "^1.7.4"} +psycopg2-binary = "^2.9.1" +SQLAlchemy = "^1.4.23" python-multipart = "^0.0.5" -PyJWT = "^1.7.1" -alembic = "^1.5.8" -itsdangerous = "^1.1.0" -python-dotenv = "^0.14.0" -pydantic = {extras = ["dotenv"], version = "^1.8.1"} -starlette = "^0.13.6" +PyJWT = "^2.1.0" +alembic = "^1.7.1" +itsdangerous = "^2.0.1" +python-dotenv = "^0.19.0" +pydantic = {extras = ["dotenv"], version = "^1.8.2"} +starlette = "^0.14.2" [tool.poetry.dev-dependencies] -flake8 = "^3.9.0" -black = "^20.8b1" -isort = {extras = ["toml"], version = "^5.8.0"} -pre-commit = "^2.11.1" +flake8 = "^3.9.2" +black = "^21.8b0" +isort = {extras = ["toml"], version = "^5.9.3"} +pre-commit = "^2.15.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/deploy.sh b/deploy.sh index cabf658e..999ee4b1 100755 --- a/deploy.sh +++ b/deploy.sh @@ -20,7 +20,7 @@ cd "$parent_path/docker/app" || exit docker build --tag brewman:latest . cd "$parent_path/docker" || exit docker save brewman:latest | bzip2 | pv | ssh tanshu@knox.tanshu.com 'bunzip2 | sudo docker load' -ansible-playbook playbook-exp.yml -ansible-playbook playbook-hops.yml -ansible-playbook playbook-acc.yml -ansible-playbook playbook-mhl.yml +ansible-playbook --limit knox playbook-exp.yml +ansible-playbook --limit knox playbook-hops.yml +ansible-playbook --limit knox playbook-acc.yml +ansible-playbook --limit knox playbook-mhl.yml diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile index d0fcef8b..999c72f0 100644 --- a/docker/app/Dockerfile +++ b/docker/app/Dockerfile @@ -2,7 +2,7 @@ FROM node:latest AS builder ADD https://git.tanshu.com/api/v1/repos/tanshu/brewman/tags /tags.json RUN git clone --single-branch --depth 1 --branch latest https://git.tanshu.com/tanshu/brewman.git /app WORKDIR /app/overlord -RUN npm install --unsafe-perm --legacy-peer-deps && /app/overlord/node_modules/.bin/ng build --prod +RUN npm install --unsafe-perm && /app/overlord/node_modules/.bin/ng build FROM python:latest @@ -12,7 +12,7 @@ COPY --from=builder /app/frontend /app/frontend WORKDIR /app # Install Poetry -RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python && \ +RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | POETRY_HOME=/opt/poetry python && \ cd /usr/local/bin && \ ln -s /opt/poetry/bin/poetry && \ poetry config virtualenvs.create false diff --git a/lint.sh b/lint.sh index 6c1bb9a2..7b0dc82a 100755 --- a/lint.sh +++ b/lint.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd -P ) cd "$parent_path/overlord" || exit -npx prettier --write src/ +npx prettier --write src/app npx ng lint --fix cd "$parent_path/brewman" || exit isort brewman diff --git a/overlord/.browserslistrc b/overlord/.browserslistrc index 0ccadaf3..427441dc 100644 --- a/overlord/.browserslistrc +++ b/overlord/.browserslistrc @@ -14,5 +14,4 @@ last 2 Edge major versions last 2 Safari major versions last 2 iOS major versions Firefox ESR -not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. diff --git a/overlord/.eslintrc.json b/overlord/.eslintrc.json index 354a261b..3144b51f 100644 --- a/overlord/.eslintrc.json +++ b/overlord/.eslintrc.json @@ -49,12 +49,12 @@ "import/order": [ "error", { - "alphabetize": {"order": "asc", "caseInsensitive": true}, + "alphabetize": { + "order": "asc", + "caseInsensitive": true + }, "newlines-between": "always" } - ], - "@typescript-eslint/no-explicit-any": [ - "error" ] } }, diff --git a/overlord/.gitignore b/overlord/.gitignore index ee5c9d83..de51f68a 100644 --- a/overlord/.gitignore +++ b/overlord/.gitignore @@ -4,10 +4,15 @@ /dist /tmp /out-tsc +# Only exists if Bazel was run +/bazel-out # dependencies /node_modules +# profiling files +chrome-profiler-events*.json + # IDEs and editors /.idea .project @@ -23,6 +28,7 @@ !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +.history/* # misc /.sass-cache diff --git a/overlord/angular.json b/overlord/angular.json index 86416c15..68219de0 100644 --- a/overlord/angular.json +++ b/overlord/angular.json @@ -22,7 +22,6 @@ "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", - "aot": true, "assets": [ "src/favicon.ico", "src/assets" @@ -35,6 +34,18 @@ }, "configurations": { "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], "fileReplacements": [ { "replace": "src/environments/environment.ts", @@ -47,33 +58,34 @@ "namedChunks": false, "extractLicenses": true, "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" - } - ] + "buildOptimizer": true + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true } - } + }, + "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "overlord:build:production" + }, + "development": { + "browserTarget": "overlord:build:development" + } + }, "options": { "browserTarget": "overlord:build", "proxyConfig": "proxy.conf.json" }, - "configurations": { - "production": { - "browserTarget": "overlord:build:production" - } - } + "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", @@ -107,21 +119,12 @@ "src/**/*.html" ] } - }, - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "overlord:serve" - }, - "configurations": { - "production": { - "devServerTarget": "overlord:serve:production" - } - } } } } }, - "defaultProject": "overlord" + "defaultProject": "overlord", + "cli": { + "defaultCollection": "@angular-eslint/schematics" + } } diff --git a/overlord/karma.conf.js b/overlord/karma.conf.js index e0147997..1d4956c2 100644 --- a/overlord/karma.conf.js +++ b/overlord/karma.conf.js @@ -9,16 +9,28 @@ module.exports = function (config) { require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), + require('karma-coverage'), require('@angular-devkit/build-angular/plugins/karma') ], client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + }, clearContext: false // leave Jasmine Spec Runner output visible in browser }, - coverageIstanbulReporter: { - dir: require('path').join(__dirname, './coverage/overlord'), - reports: ['html', 'lcovonly', 'text-summary'], - fixWebpackSourcePaths: true + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, './coverage/ele'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' } + ] }, reporters: ['progress', 'kjhtml'], port: 9876, diff --git a/overlord/package.json b/overlord/package.json index 205c3f17..e6feed4b 100644 --- a/overlord/package.json +++ b/overlord/package.json @@ -14,63 +14,61 @@ }, "private": true, "dependencies": { - "@angular/animations": "^11.1.0", - "@angular/cdk": "^11.1.0", - "@angular/common": "^11.1.0", - "@angular/compiler": "^11.1.0", - "@angular/core": "^11.1.0", - "@angular/flex-layout": "^11.0.0-beta.33", - "@angular/forms": "^11.1.0", - "@angular/material": "^11.1.0", - "@angular/material-moment-adapter": "^11.1.0", - "@angular/platform-browser": "^11.1.0", - "@angular/platform-browser-dynamic": "^11.1.0", - "@angular/router": "^11.1.0", - "@ngx-loading-bar/core": "^5.1.1", - "@ngx-loading-bar/http-client": "^5.1.1", - "@ngx-loading-bar/router": "^5.1.1", - "@types/mousetrap": "1.6.3", - "angular2-hotkeys": "^2.2.0", - "mathjs": "^9.0.0", + "@angular/animations": "^12.2.4", + "@angular/cdk": "^12.2.4", + "@angular/common": "^12.2.4", + "@angular/compiler": "^12.2.4", + "@angular/core": "^12.2.4", + "@angular/flex-layout": "^12.0.0-beta.34", + "@angular/forms": "^12.2.4", + "@angular/material": "^12.2.4", + "@angular/material-moment-adapter": "^12.2.4", + "@angular/platform-browser": "^12.2.4", + "@angular/platform-browser-dynamic": "^12.2.4", + "@angular/router": "^12.2.4", + "@ngx-loading-bar/core": "^5.1.2", + "@ngx-loading-bar/http-client": "^5.1.2", + "@ngx-loading-bar/router": "^5.1.2", + "@types/mousetrap": "1.6.8", + "angular2-hotkeys": "^2.4.0", + "mathjs": "^9.4.4", "moment": "^2.29.1", - "rxjs": "^6.6.3", + "rxjs": "^6.6.7", "tslib": "^2.1.0", - "zone.js": "^0.11.3" + "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~0.1101.1", - "@angular-eslint/builder": "^1.1.0", - "@angular-eslint/eslint-plugin": "^1.1.0", - "@angular-eslint/eslint-plugin-template": "^1.1.0", - "@angular-eslint/schematics": "^1.1.0", - "@angular-eslint/template-parser": "^1.1.0", - "@angular/cli": "^11.1.1", - "@angular/compiler-cli": "^11.1.0", - "@angular/language-service": "^11.1.0", - "@types/jasmine": "~3.6.3", - "@types/jasminewd2": "^2.0.3", - "@types/mathjs": "^6.0.11", - "@types/node": "^14.14.22", - "@typescript-eslint/eslint-plugin": "^4.14.0", - "@typescript-eslint/parser": "^4.14.0", - "eslint": "^7.18.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jsdoc": "^31.2.2", - "eslint-plugin-prefer-arrow": "1.2.2", - "husky": "^4.3.8", - "jasmine-core": "~3.6.0", - "jasmine-spec-reporter": "6.0.0", - "karma": "^6.0.1", + "@angular-devkit/build-angular": "^12.2.4", + "@angular-eslint/builder": "^12.3.1", + "@angular-eslint/eslint-plugin": "^12.3.1", + "@angular-eslint/eslint-plugin-template": "^12.3.1", + "@angular-eslint/schematics": "^12.3.1", + "@angular-eslint/template-parser": "^12.3.1", + "@angular/cli": "^12.2.4", + "@angular/compiler-cli": "^12.2.4", + "@angular/language-service": "^12.2.4", + "@types/jasmine": "~3.7.4", + "@types/mathjs": "^9.4.2", + "@types/node": "^16.7.10", + "@typescript-eslint/eslint-plugin": "4.30.0", + "@typescript-eslint/parser": "4.30.0", + "eslint": "^7.32.0", + "eslint-plugin-import": "^2.24.2", + "eslint-plugin-jsdoc": "^36.0.8", + "eslint-plugin-prefer-arrow": "1.2.3", + "husky": "^7.0.2", + "jasmine-core": "~3.8.0", + "jasmine-spec-reporter": "7.0.0", + "karma": "^6.3.2", "karma-chrome-launcher": "~3.1.0", - "karma-coverage-istanbul-reporter": "~3.0.2", - "karma-jasmine": "~4.0.0", - "karma-jasmine-html-reporter": "^1.5.0", - "lint-staged": "^10.5.3", - "prettier": "^2.2.1", - "protractor": "~7.0.0", - "standard-version": "^9.1.0", + "karma-coverage": "~2.0.3", + "karma-jasmine": "~4.0.1", + "karma-jasmine-html-reporter": "^1.6.0", + "lint-staged": "^11.1.2", + "prettier": "^2.3.2", + "standard-version": "^9.3.1", "ts-node": "^9.1.1", - "typescript": "~4.1.5" + "typescript": "~4.2.4" }, "husky": { "hooks": { diff --git a/overlord/src/app/employee-benefits/employee-benefits.component.ts b/overlord/src/app/employee-benefits/employee-benefits.component.ts index f9ad6997..e0f55f6f 100644 --- a/overlord/src/app/employee-benefits/employee-benefits.component.ts +++ b/overlord/src/app/employee-benefits/employee-benefits.component.ts @@ -67,9 +67,9 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { }), }); // Setup Employee Autocomplete - this.employees = ((this.form.get('addRow') as FormControl).get( - 'employee', - ) as FormControl).valueChanges.pipe( + this.employees = ( + (this.form.get('addRow') as FormControl).get('employee') as FormControl + ).valueChanges.pipe( startWith(null), map((x) => (x !== null && x.length >= 1 ? x : null)), debounceTime(150), diff --git a/overlord/src/app/incentive/incentive.component.ts b/overlord/src/app/incentive/incentive.component.ts index d30194c5..4517d9b3 100644 --- a/overlord/src/app/incentive/incentive.component.ts +++ b/overlord/src/app/incentive/incentive.component.ts @@ -106,9 +106,11 @@ export class IncentiveComponent implements OnInit { } change(row: Incentive, i: number) { - row.points = +(((this.form.get('incentives') as FormArray).get(`${i}`) as FormGroup).get( - 'points', - ) as FormControl).value; + row.points = +( + ((this.form.get('incentives') as FormArray).get(`${i}`) as FormGroup).get( + 'points', + ) as FormControl + ).value; ((this.form.get('incentives') as FormArray).get(`${i}`) as FormGroup).setValue({ points: `${row.points}`, }); diff --git a/overlord/src/app/issue/issue.component.ts b/overlord/src/app/issue/issue.component.ts index 8e4f21a3..e3383d3d 100644 --- a/overlord/src/app/issue/issue.component.ts +++ b/overlord/src/app/issue/issue.component.ts @@ -74,9 +74,9 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { narration: '', }); // Listen to Batch Autocomplete Change - this.batches = ((this.form.get('addRow') as FormControl).get( - 'batch', - ) as FormControl).valueChanges.pipe( + this.batches = ( + (this.form.get('addRow') as FormControl).get('batch') as FormControl + ).valueChanges.pipe( startWith('null'), debounceTime(150), distinctUntilChanged(), diff --git a/overlord/src/app/journal/journal.component.ts b/overlord/src/app/journal/journal.component.ts index 4a80dee3..4b56d46f 100644 --- a/overlord/src/app/journal/journal.component.ts +++ b/overlord/src/app/journal/journal.component.ts @@ -71,9 +71,9 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { }); this.accBal = null; // Setup Account Autocomplete - this.accounts = ((this.form.get('addRow') as FormControl).get( - 'account', - ) as FormControl).valueChanges.pipe( + this.accounts = ( + (this.form.get('addRow') as FormControl).get('account') as FormControl + ).valueChanges.pipe( startWith(null), map((x) => (x !== null && x.length >= 1 ? x : null)), debounceTime(150), diff --git a/overlord/src/app/payment/payment.component.ts b/overlord/src/app/payment/payment.component.ts index f9e4ffc9..547ed800 100644 --- a/overlord/src/app/payment/payment.component.ts +++ b/overlord/src/app/payment/payment.component.ts @@ -74,9 +74,9 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { }); this.accBal = null; // Listen to Account Autocomplete Change - this.accounts = ((this.form.get('addRow') as FormControl).get( - 'account', - ) as FormControl).valueChanges.pipe( + this.accounts = ( + (this.form.get('addRow') as FormControl).get('account') as FormControl + ).valueChanges.pipe( startWith(null), map((x) => (x !== null && x.length >= 1 ? x : null)), debounceTime(150), diff --git a/overlord/src/app/product/product.service.ts b/overlord/src/app/product/product.service.ts index 10f7ea84..c0d4da11 100644 --- a/overlord/src/app/product/product.service.ts +++ b/overlord/src/app/product/product.service.ts @@ -51,8 +51,8 @@ export class ProductService { .pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable; } - autocomplete(query: string): Observable { - const options = { params: new HttpParams().set('q', query) }; + autocomplete(query: string, extended: boolean = false): Observable { + const options = { params: new HttpParams().set('q', query).set('e', extended.toString()) }; return this.http .get(`${url}/query`, options) .pipe(catchError(this.log.handleError(serviceName, 'autocomplete'))) as Observable; diff --git a/overlord/src/app/purchase-return/purchase-return.component.ts b/overlord/src/app/purchase-return/purchase-return.component.ts index 569a5dc1..f4e58444 100644 --- a/overlord/src/app/purchase-return/purchase-return.component.ts +++ b/overlord/src/app/purchase-return/purchase-return.component.ts @@ -83,9 +83,9 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy switchMap((x) => (x === null ? observableOf([]) : this.accountSer.autocomplete(x))), ); // Listen to Batch Autocomplete Change - this.batches = ((this.form.get('addRow') as FormControl).get( - 'batch', - ) as FormControl).valueChanges.pipe( + this.batches = ( + (this.form.get('addRow') as FormControl).get('batch') as FormControl + ).valueChanges.pipe( startWith(''), debounceTime(150), distinctUntilChanged(), diff --git a/overlord/src/app/purchase/purchase.component.ts b/overlord/src/app/purchase/purchase.component.ts index 314f2275..76a266cb 100644 --- a/overlord/src/app/purchase/purchase.component.ts +++ b/overlord/src/app/purchase/purchase.component.ts @@ -87,9 +87,9 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { switchMap((x) => (x === null ? observableOf([]) : this.accountSer.autocomplete(x))), ); // Listen to Product Autocomplete Change - this.products = ((this.form.get('addRow') as FormControl).get( - 'product', - ) as FormControl).valueChanges.pipe( + this.products = ( + (this.form.get('addRow') as FormControl).get('product') as FormControl + ).valueChanges.pipe( startWith(null), map((x) => (x !== null && x.length >= 1 ? x : null)), debounceTime(150), diff --git a/overlord/src/app/receipt/receipt.component.ts b/overlord/src/app/receipt/receipt.component.ts index f840c881..b4090078 100644 --- a/overlord/src/app/receipt/receipt.component.ts +++ b/overlord/src/app/receipt/receipt.component.ts @@ -73,9 +73,9 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { }); this.accBal = null; // Listen to Account Autocomplete Change - this.accounts = ((this.form.get('addRow') as FormControl).get( - 'account', - ) as FormControl).valueChanges.pipe( + this.accounts = ( + (this.form.get('addRow') as FormControl).get('account') as FormControl + ).valueChanges.pipe( startWith(null), map((x) => (x !== null && x.length >= 1 ? x : null)), debounceTime(150), diff --git a/overlord/src/app/unposted/unposted-datasource.ts b/overlord/src/app/unposted/unposted-datasource.ts index c8858ec7..215ab5f1 100644 --- a/overlord/src/app/unposted/unposted-datasource.ts +++ b/overlord/src/app/unposted/unposted-datasource.ts @@ -17,11 +17,8 @@ export class UnpostedDataSource extends DataSource { } connect(): Observable { - const dataMutations: ( - | Observable - | EventEmitter - | EventEmitter - )[] = [observableOf(this.data)]; + const dataMutations: (Observable | EventEmitter | EventEmitter)[] = + [observableOf(this.data)]; if (this.paginator) { dataMutations.push((this.paginator as MatPaginator).page); } diff --git a/overlord/src/environments/environment.ts b/overlord/src/environments/environment.ts index e0e58a02..547c3bcb 100644 --- a/overlord/src/environments/environment.ts +++ b/overlord/src/environments/environment.ts @@ -16,4 +16,4 @@ export const environment = { * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/overlord/src/polyfills.ts b/overlord/src/polyfills.ts index 5ead8153..373f538a 100644 --- a/overlord/src/polyfills.ts +++ b/overlord/src/polyfills.ts @@ -11,64 +11,54 @@ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * - * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** * BROWSER POLYFILLS */ -/** IE9, IE10 and IE11 requires all of the following polyfills. **/ -// import 'core-js/es6/symbol'; -// import 'core-js/es6/object'; -// import 'core-js/es6/function'; -// import 'core-js/es6/parse-int'; -// import 'core-js/es6/parse-float'; -// import 'core-js/es6/number'; -// import 'core-js/es6/math'; -// import 'core-js/es6/string'; -// import 'core-js/es6/date'; -// import 'core-js/es6/array'; -// import 'core-js/es6/regexp'; -// import 'core-js/es6/map'; -// import 'core-js/es6/weak-map'; -// import 'core-js/es6/set'; - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +/** + * IE11 requires the following for NgClass support on SVG elements + */ // import 'classlist.js'; // Run `npm install --save classlist.js`. -/** IE10 and IE11 requires the following for the Reflect API. */ -// import 'core-js/es6/reflect'; - -/** Evergreen browsers require these. **/ -// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. - /** * Web Animations `@angular/platform-browser/animations` * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). - **/ + */ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * */ -// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame -// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick -// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - -/* - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - */ -// (window as any).__Zone_enable_cross_context_check = true; - /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ -import 'zone.js/dist/zone'; // Included with Angular CLI. +import 'zone.js'; // Included with Angular CLI. + /*************************************************************************************************** * APPLICATION IMPORTS diff --git a/overlord/src/test.ts b/overlord/src/test.ts index e103ada1..a0e80c3a 100644 --- a/overlord/src/test.ts +++ b/overlord/src/test.ts @@ -1,6 +1,6 @@ // This file is required by karma.conf.js and loads recursively all the .spec and framework files -import 'zone.js/dist/zone-testing'; +import 'zone.js/testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, diff --git a/overlord/tsconfig.json b/overlord/tsconfig.json index 3240f7ae..c3ebc8f9 100644 --- a/overlord/tsconfig.json +++ b/overlord/tsconfig.json @@ -14,7 +14,7 @@ "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, - "target": "es2015", + "target": "es2017", "module": "es2020", "lib": [ "es2018",