From fe74ef44bf09ceb9fa4a2e04e616265593efd1f0 Mon Sep 17 00:00:00 2001 From: tanshu Date: Fri, 8 May 2020 10:22:25 +0530 Subject: [PATCH] Moving to FastAPI Config is not properly working with .env file as of now Cost Centre is mostly working, going to work on Accounts now --- .env | 9 + .gitignore | 1 + MANIFEST.in | 2 +- brewman/__init__.py | 35 -- brewman/__main__.py | 3 + brewman/config.py | 12 + .../{views/Management => core}/__init__.py | 0 brewman/core/config.py | 52 +++ brewman/core/security.py | 122 +++++++ brewman/{views/auth => db}/__init__.py | 0 brewman/db/base.py | 31 ++ brewman/db/base_class.py | 16 + brewman/db/init_db.py | 25 ++ brewman/db/session.py | 7 + brewman/main.py | 50 +++ brewman/models/__init__.py | 31 +- brewman/models/auth.py | 2 +- brewman/renderers.py | 55 ---- .../Management}/__init__.py | 0 .../Management/db_integrity.py | 10 +- .../{views => routers}/Management/rebase.py | 11 +- .../{views => routers}/Management/settings.py | 51 +-- .../{views => routers}/Management/stock.py | 11 +- brewman/{views => routers}/__init__.py | 28 +- brewman/routers/account.py | 243 ++++++++++++++ brewman/routers/account_types.py | 12 + brewman/routers/attendance.py | 101 ++++++ .../{views => routers}/attendance_report.py | 6 +- brewman/routers/attendance_types.py | 14 + .../services => routers/auth}/__init__.py | 0 brewman/{views => routers}/auth/client.py | 47 +-- brewman/{views => routers}/auth/group.py | 56 +--- brewman/{views => routers}/auth/user.py | 72 +--- brewman/routers/cost_centre.py | 148 +++++++++ brewman/{views => routers}/employee.py | 81 +---- brewman/routers/employee_attendance.py | 124 +++++++ brewman/{views => routers}/fingerprint.py | 18 +- brewman/routers/login.py | 54 +++ brewman/{views => routers}/product.py | 73 +---- brewman/{views => routers}/product_group.py | 64 +--- brewman/{views => routers}/recipe.py | 63 +--- brewman/routers/reports/__init__.py | 0 .../reports/balance_sheet.py | 36 +- .../{views => routers}/reports/cash_flow.py | 36 +- .../reports/closing_stock.py | 31 +- brewman/{views => routers}/reports/daybook.py | 33 +- brewman/{views => routers}/reports/ledger.py | 30 +- .../reports/net_transactions.py | 30 +- .../reports/product_ledger.py | 38 +-- .../{views => routers}/reports/profit_loss.py | 29 +- .../reports/purchase_entries.py | 36 +- .../{views => routers}/reports/purchases.py | 27 +- .../reports/raw_material_cost.py | 42 +-- .../{views => routers}/reports/reconcile.py | 43 +-- .../reports/stock_movement.py | 29 +- .../reports/trial_balance.py | 31 +- .../{views => routers}/reports/unposted.py | 23 +- brewman/routers/services/__init__.py | 0 brewman/{views => routers}/services/batch.py | 9 +- .../services/cost_centre.py | 5 +- .../{views => routers}/services/session.py | 25 -- .../services/voucher/__init__.py | 54 +-- .../services/voucher/credit_salary.py | 11 +- .../services/voucher/emptyvoucher.py | 22 +- .../services/voucher/issue.py | 0 .../services/voucher/journal.py | 0 .../services/voucher/purchase.py | 0 .../services/voucher/purchase_return.py | 0 .../services/voucher/salary_deduction.py | 2 +- .../services/voucher/savevoucher.py | 28 +- .../services/voucher/service_charge.py | 2 +- .../services/voucher/updatevoucher.py | 29 +- brewman/routes.py | 183 ----------- brewman/schemas/__init__.py | 0 brewman/schemas/auth.py | 36 ++ brewman/schemas/master.py | 88 +++++ brewman/schemas/voucher.py | 96 ++++++ brewman/security/__init__.py | 42 --- .../permission_authorization_policy.py | 19 -- brewman/tests.py | 18 - brewman/transactional_view_deriver.py | 33 -- brewman/views/account.py | 310 ------------------ brewman/views/attendance.py | 274 ---------------- brewman/views/cost_centre.py | 132 -------- brewman/views/services/login.py | 155 --------- development.ini | 51 --- production.ini | 44 --- requirements.txt | 15 +- setup.py | 20 +- 89 files changed, 1516 insertions(+), 2421 deletions(-) create mode 100644 .env create mode 100644 brewman/__main__.py create mode 100644 brewman/config.py rename brewman/{views/Management => core}/__init__.py (100%) create mode 100644 brewman/core/config.py create mode 100644 brewman/core/security.py rename brewman/{views/auth => db}/__init__.py (100%) create mode 100644 brewman/db/base.py create mode 100644 brewman/db/base_class.py create mode 100644 brewman/db/init_db.py create mode 100644 brewman/db/session.py create mode 100644 brewman/main.py delete mode 100644 brewman/renderers.py rename brewman/{views/reports => routers/Management}/__init__.py (100%) rename brewman/{views => routers}/Management/db_integrity.py (88%) rename brewman/{views => routers}/Management/rebase.py (97%) rename brewman/{views => routers}/Management/settings.py (76%) rename brewman/{views => routers}/Management/stock.py (95%) rename brewman/{views => routers}/__init__.py (73%) create mode 100644 brewman/routers/account.py create mode 100644 brewman/routers/account_types.py create mode 100644 brewman/routers/attendance.py rename brewman/{views => routers}/attendance_report.py (94%) create mode 100644 brewman/routers/attendance_types.py rename brewman/{views/services => routers/auth}/__init__.py (100%) rename brewman/{views => routers}/auth/client.py (66%) rename brewman/{views => routers}/auth/group.py (67%) rename brewman/{views => routers}/auth/user.py (76%) create mode 100644 brewman/routers/cost_centre.py rename brewman/{views => routers}/employee.py (85%) create mode 100644 brewman/routers/employee_attendance.py rename brewman/{views => routers}/fingerprint.py (94%) create mode 100644 brewman/routers/login.py rename brewman/{views => routers}/product.py (86%) rename brewman/{views => routers}/product_group.py (59%) rename brewman/{views => routers}/recipe.py (89%) create mode 100644 brewman/routers/reports/__init__.py rename brewman/{views => routers}/reports/balance_sheet.py (77%) rename brewman/{views => routers}/reports/cash_flow.py (85%) rename brewman/{views => routers}/reports/closing_stock.py (77%) rename brewman/{views => routers}/reports/daybook.py (74%) rename brewman/{views => routers}/reports/ledger.py (82%) rename brewman/{views => routers}/reports/net_transactions.py (70%) rename brewman/{views => routers}/reports/product_ledger.py (84%) rename brewman/{views => routers}/reports/profit_loss.py (82%) rename brewman/{views => routers}/reports/purchase_entries.py (68%) rename brewman/{views => routers}/reports/purchases.py (76%) rename brewman/{views => routers}/reports/raw_material_cost.py (82%) rename brewman/{views => routers}/reports/reconcile.py (84%) rename brewman/{views => routers}/reports/stock_movement.py (83%) rename brewman/{views => routers}/reports/trial_balance.py (62%) rename brewman/{views => routers}/reports/unposted.py (73%) create mode 100644 brewman/routers/services/__init__.py rename brewman/{views => routers}/services/batch.py (89%) rename brewman/{views => routers}/services/cost_centre.py (89%) rename brewman/{views => routers}/services/session.py (65%) rename brewman/{views => routers}/services/voucher/__init__.py (89%) rename brewman/{views => routers}/services/voucher/credit_salary.py (93%) rename brewman/{views => routers}/services/voucher/emptyvoucher.py (70%) rename brewman/{views => routers}/services/voucher/issue.py (100%) rename brewman/{views => routers}/services/voucher/journal.py (100%) rename brewman/{views => routers}/services/voucher/purchase.py (100%) rename brewman/{views => routers}/services/voucher/purchase_return.py (100%) rename brewman/{views => routers}/services/voucher/salary_deduction.py (98%) rename brewman/{views => routers}/services/voucher/savevoucher.py (81%) rename brewman/{views => routers}/services/voucher/service_charge.py (98%) rename brewman/{views => routers}/services/voucher/updatevoucher.py (83%) delete mode 100644 brewman/routes.py create mode 100644 brewman/schemas/__init__.py create mode 100644 brewman/schemas/auth.py create mode 100644 brewman/schemas/master.py create mode 100644 brewman/schemas/voucher.py delete mode 100644 brewman/security/__init__.py delete mode 100644 brewman/security/permission_authorization_policy.py delete mode 100644 brewman/tests.py delete mode 100644 brewman/transactional_view_deriver.py delete mode 100644 brewman/views/account.py delete mode 100644 brewman/views/attendance.py delete mode 100644 brewman/views/cost_centre.py delete mode 100644 brewman/views/services/login.py delete mode 100644 development.ini delete mode 100644 production.ini diff --git a/.env b/.env new file mode 100644 index 00000000..e6288ca8 --- /dev/null +++ b/.env @@ -0,0 +1,9 @@ +HOST=0.0.0.0 +PORT=9998 +LOG_LEVEL=info +DEBUG=true +DB_URL=postgresql://postgres:123456@localhost:5432/hops +MODULE_NAME=brewman.main +SERVER_NAME=localhost +SERVER_HOST=localhost +PROJECT_NAME=bifrost \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8c43f529..40edb316 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.pyc env +venv .project .pydevproject */__pycache__/ diff --git a/MANIFEST.in b/MANIFEST.in index 90ae255a..027113eb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include *.txt *.ini *.cfg *.rst +include *.txt *.ini *.cfg *.rst .env recursive-include brewman *.ico *.png *.css *.gif *.jpg *.txt *.js *.html *.map *.eot *.svg *.ttf *.woff diff --git a/brewman/__init__.py b/brewman/__init__.py index 82802754..f4b67eec 100644 --- a/brewman/__init__.py +++ b/brewman/__init__.py @@ -1,38 +1,3 @@ -import os -from pyramid.config import Configurator - -from pyramid.session import SignedCookieSessionFactory -from brewman.renderers import json_renderer, CSVRenderer -from brewman.transactional_view_deriver import transactional_view - - def main(global_config, **settings): - """ This function returns a Pyramid WSGI application. - """ - if 'sqlalchemy.url' not in settings: - DB_NAME = os.environ['DB_NAME'] - DB_USER = os.environ['DB_USER'] - DB_PASS = os.environ['DB_PASS'] - DB_URI = 'postgresql://{0}:{1}@postgres:5432/{2}'.format( - DB_USER, DB_PASS, DB_NAME - ) - settings['sqlalchemy.url']= DB_URI - SECRET_KEY = os.environ.get('SECRET_KEY', settings.get('secret_key', '')) session_factory = SignedCookieSessionFactory(SECRET_KEY) - - - config = Configurator( - settings=settings, - session_factory=session_factory - ) - config.include('.security') - config.include('.models') - config.include('.routes') - config.add_view_deriver(transactional_view) - config.add_renderer(name='json', factory=json_renderer) - config.add_renderer(name='csv', factory=CSVRenderer) - config.scan() - - return config.make_wsgi_app() - diff --git a/brewman/__main__.py b/brewman/__main__.py new file mode 100644 index 00000000..95baff47 --- /dev/null +++ b/brewman/__main__.py @@ -0,0 +1,3 @@ +from brewman.main import init + +init() diff --git a/brewman/config.py b/brewman/config.py new file mode 100644 index 00000000..b61c2ea2 --- /dev/null +++ b/brewman/config.py @@ -0,0 +1,12 @@ +from environs import Env + +env = Env() +env.read_env() # read .env file, if it exists + + +class Settings: + debug: bool = env("DEBUG") + host: str = env("HOST") + port: int = env.int("PORT") + db_url: str = env("DB_URL") + log_level: str = env("LOG_LEVEL") \ No newline at end of file diff --git a/brewman/views/Management/__init__.py b/brewman/core/__init__.py similarity index 100% rename from brewman/views/Management/__init__.py rename to brewman/core/__init__.py diff --git a/brewman/core/config.py b/brewman/core/config.py new file mode 100644 index 00000000..6523e987 --- /dev/null +++ b/brewman/core/config.py @@ -0,0 +1,52 @@ +import secrets +from typing import Any, Dict, List, Optional, Union + +from pydantic import AnyHttpUrl, BaseSettings, PostgresDsn, validator + + +class Settings(BaseSettings): + API_V1_STR: str = "/api/v1" + SECRET_KEY: str = secrets.token_urlsafe(32) + # 60 minutes * 24 hours * 8 days = 8 days + ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 + SERVER_NAME: str = "localhost" + SERVER_HOST: AnyHttpUrl = "http://localhost:9998" + # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins + # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \ + # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]' + BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = [] + + @validator("BACKEND_CORS_ORIGINS", pre=True) + def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]: + if isinstance(v, str) and not v.startswith("["): + return [i.strip() for i in v.split(",")] + elif isinstance(v, (list, str)): + return v + raise ValueError(v) + + PROJECT_NAME: str = "bifrost" + + POSTGRES_SERVER: str = "localhost" + POSTGRES_USER: str = "postgres" + POSTGRES_PASSWORD: str = "123456" + POSTGRES_DB: str = "hops" + SQLALCHEMY_DATABASE_URI: Optional[PostgresDsn] = None + + @validator("SQLALCHEMY_DATABASE_URI", pre=True) + def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any: + if isinstance(v, str): + return v + return PostgresDsn.build( + scheme="postgresql", + user=values.get("POSTGRES_USER"), + password=values.get("POSTGRES_PASSWORD"), + host=values.get("POSTGRES_SERVER"), + path=f"/{values.get('POSTGRES_DB') or ''}", + ) + + class Config: + case_sensitive = True + env_file = '.env' + + +settings = Settings() diff --git a/brewman/core/security.py b/brewman/core/security.py new file mode 100644 index 00000000..25c32aa8 --- /dev/null +++ b/brewman/core/security.py @@ -0,0 +1,122 @@ +import uuid +from datetime import datetime, timedelta +from typing import List +from jwt import PyJWTError + +from fastapi import Depends, HTTPException, status, Security +from fastapi.security import OAuth2PasswordBearer, SecurityScopes +from pydantic import BaseModel, ValidationError +from sqlalchemy.orm import Session +from jose import jwt + +from brewman.models.auth import User as UserModel +from ..db.session import SessionLocal + + +# to get a string like this run: +# openssl rand -hex 32 +SECRET_KEY = "8546a61262dab7c05ccf2e26abe30bc10966904df6dfd29259ea85dd0844a8e7" +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + + +oauth2_scheme = OAuth2PasswordBearer( + tokenUrl="/token", + scopes={} +) + + +class Token(BaseModel): + access_token: str + token_type: str + + +class TokenData(BaseModel): + username: str = None + scopes: List[str] = [] + + +class User(BaseModel): + id_: uuid.UUID + name: str + locked_out: bool = None + password: str + + +def f7(seq): + seen = set() + seen_add = seen.add + 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: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + + +def get_user(username: str, db: Session): + user = db.query(UserModel).filter(UserModel.name.ilike(username)).first() + if user: + return User(id_=user.id, name=user.name, locked_out=user.locked_out, password=user.password) + + +def authenticate_user(username: str, password: str, db: Session): + found, user = UserModel.auth(username, password, db) + if not found: + return False + return user + + +async def get_current_user(security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)): + if security_scopes.scopes: + authenticate_value = f'Bearer scope="{security_scopes.scope_str}"' + else: + authenticate_value = f"Bearer" + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": authenticate_value}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + token_scopes = payload.get("scopes", []) + token_data = TokenData(scopes=token_scopes, username=username) + except (PyJWTError, ValidationError): + raise credentials_exception + user = get_user(username=token_data.username, db=db) + if user is None: + raise credentials_exception + for scope in security_scopes.scopes: + if scope not in token_data.scopes: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not enough permissions", + headers={"WWW-Authenticate": authenticate_value}, + ) + return user + + +async def get_current_active_user(current_user: User = Security(get_current_user, scopes=["authenticated"])): + if current_user.locked_out: + raise HTTPException(status_code=400, detail="Inactive user") + return current_user + + diff --git a/brewman/views/auth/__init__.py b/brewman/db/__init__.py similarity index 100% rename from brewman/views/auth/__init__.py rename to brewman/db/__init__.py diff --git a/brewman/db/base.py b/brewman/db/base.py new file mode 100644 index 00000000..61f24b72 --- /dev/null +++ b/brewman/db/base.py @@ -0,0 +1,31 @@ +# Import all the models, so that Base has them before being +# imported by Alembic +from brewman.db.base_class import Base # noqa +from brewman.models import ( + Client, + user_group, + role_group, + User, + LoginHistory, + Group, + Role, + Product, + AttendanceType, + CostCentre, + Employee, + Account, + AccountBase, + AccountType, + ProductGroup, + Recipe, + RecipeItem, + Attendance, + Batch, + Fingerprint, + Inventory, + Journal, + Product, + SalaryDeduction, + Voucher, + VoucherType, +) # noqa diff --git a/brewman/db/base_class.py b/brewman/db/base_class.py new file mode 100644 index 00000000..98c6156e --- /dev/null +++ b/brewman/db/base_class.py @@ -0,0 +1,16 @@ +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.schema import MetaData + +# Recommended naming convention used by Alembic, as various different database +# providers will autogenerate vastly different names making migrations more +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html +NAMING_CONVENTION = { + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s", +} + +metadata = MetaData(naming_convention=NAMING_CONVENTION) +Base = declarative_base(metadata=metadata) diff --git a/brewman/db/init_db.py b/brewman/db/init_db.py new file mode 100644 index 00000000..4f52d64a --- /dev/null +++ b/brewman/db/init_db.py @@ -0,0 +1,25 @@ +from sqlalchemy.orm import Session + +from app import crud, schemas +from brewman.core.config import settings +from brewman.db import base # noqa: F401 + +# make sure all SQL Alchemy models are imported (app.db.base) before initializing DB +# otherwise, SQL Alchemy might fail to initialize relationships properly +# for more details: https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/28 + + +def init_db(db: Session) -> None: + # Tables should be created with Alembic migrations + # But if you don't want to use migrations, create + # the tables un-commenting the next line + # Base.metadata.create_all(bind=engine) + + user = crud.user.get_by_email(db, email=settings.FIRST_SUPERUSER) + if not user: + user_in = schemas.UserCreate( + email=settings.FIRST_SUPERUSER, + password=settings.FIRST_SUPERUSER_PASSWORD, + is_superuser=True, + ) + user = crud.user.create(db, obj_in=user_in) # noqa: F841 diff --git a/brewman/db/session.py b/brewman/db/session.py new file mode 100644 index 00000000..07e89e47 --- /dev/null +++ b/brewman/db/session.py @@ -0,0 +1,7 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from brewman.core.config import settings + +engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/brewman/main.py b/brewman/main.py new file mode 100644 index 00000000..6d2814bd --- /dev/null +++ b/brewman/main.py @@ -0,0 +1,50 @@ +import uvicorn +from fastapi import FastAPI + +from .routers import ( + account, + account_types, + attendance_types, + attendance, + employee_attendance, + attendance_report, + cost_centre, + employee, + fingerprint, + product, + product_group, + recipe, + login +) +from .routers.auth import client, user, group +from .db.base_class import Base +from .config import Settings as settings +from .db.session import SessionLocal, engine + +Base.metadata.create_all(bind=engine) + +app = FastAPI() + + +# app.include_router(brewman.routers, prefix="/api/db-image", tags=["db-image"]) +app.include_router(login.router, tags=["login"]) +app.include_router(account.router, prefix="/api/accounts", tags=["accounts"]) +app.include_router(account_types.router, prefix="/api/account-types", tags=["accounts"]) +app.include_router(attendance.router, prefix="/api/attendance", tags=["attendance"]) +app.include_router(attendance_types.router, prefix="/api/attendance-types", tags=["attendance"]) +app.include_router(employee_attendance.router, prefix="/api/employee-attendance", tags=["attendance"]) +app.include_router(employee_attendance.router, prefix="/api/employee-attendance", tags=["attendance"]) +app.include_router(attendance_report.router, prefix="/api/attendance-report", tags=["attendance"]) +app.include_router(cost_centre.router, prefix="/api/cost-centres", tags=["cost-centres"]) +app.include_router(employee.router, prefix="/api/employees", tags=["employees"]) +app.include_router(fingerprint.router, prefix="/api/fingerprint", tags=["employees"]) +app.include_router(product.router, prefix="/api/products", tags=["products"]) +app.include_router(product_group.router, prefix="/api/product-groups", tags=["products"]) +app.include_router(recipe.router, prefix="/api/recipes", tags=["products"]) + +app.include_router(client.router, prefix="/api/clients", tags=["users"]) +app.include_router(group.router, prefix="/api/groups", tags=["users"]) +app.include_router(user.router, prefix="/api/users", tags=["users"]) + +def init(): + uvicorn.run(app, host=settings.host, port=settings.port) diff --git a/brewman/models/__init__.py b/brewman/models/__init__.py index 15536c73..a288de16 100644 --- a/brewman/models/__init__.py +++ b/brewman/models/__init__.py @@ -1,20 +1,17 @@ from sqlalchemy import engine_from_config from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import configure_mappers -import zope.sqlalchemy # import or define all models here to ensure they are attached to the # Base.metadata prior to any initialization routines -from .voucher import ( - Attendance, - Batch, - Fingerprint, - Inventory, - Journal, - Product, - SalaryDeduction, - Voucher, - VoucherType, +from .auth import ( + Client, + user_group, + role_group, + User, + LoginHistory, + Group, + Role ) from .master import ( Product, @@ -28,7 +25,17 @@ from .master import ( Recipe, RecipeItem, ) -from .auth import Client, Group, Role, User, role_group, user_group, LoginHistory +from .voucher import ( + Attendance, + Batch, + Fingerprint, + Inventory, + Journal, + Product, + SalaryDeduction, + Voucher, + VoucherType, +) # run configure_mappers after defining all of the models to ensure # all relationships can be setup diff --git a/brewman/models/auth.py b/brewman/models/auth.py index 63f9f62a..f168db39 100644 --- a/brewman/models/auth.py +++ b/brewman/models/auth.py @@ -108,7 +108,7 @@ class User(Base): self.id = id @classmethod - def auth(cls, name, password, dbsession): + def auth(cls, name, password, dbsession) -> (bool, any): if password is None: return False, None user = dbsession.query(User).filter(User.name.ilike(name)).first() diff --git a/brewman/renderers.py b/brewman/renderers.py deleted file mode 100644 index 0b11e12f..00000000 --- a/brewman/renderers.py +++ /dev/null @@ -1,55 +0,0 @@ -import csv -from decimal import Decimal -from io import StringIO -import uuid - -from pyramid.renderers import JSON - - -class CSVRenderer(object): - def __init__(self, info): - pass - - def __call__(self, value, system): - csv_data = StringIO() - writer = csv.writer(csv_data, delimiter=",", quoting=csv.QUOTE_MINIMAL) - - if "header" in value: - writer.writerow(value["header"]) - if "rows" in value: - writer.writerows(value["rows"]) - if "footer" in value: - writer.writerows(value["footer"]) - - request = system.get("request") - if request is not None: - response = request.response - ct = response.content_type - if ct == response.default_content_type: - response.content_type = "text/csv" - filename = value["filename"] if "filename" in value else "report.csv" - response.content_disposition = 'attachment;filename="{0}"'.format(filename) - return csv_data.getvalue() - - -json_renderer = JSON() - - -class DecimalAsFloatHack(float): - def __init__(self, d): - self.d = d - - def __repr__(self): - return str(self.d) - - -def decimal_adaptor(obj, request): - return DecimalAsFloatHack(obj) - - -def uuid_adaptor(obj, request): - return str(obj) - - -json_renderer.add_adapter(Decimal, decimal_adaptor) -json_renderer.add_adapter(uuid.UUID, uuid_adaptor) diff --git a/brewman/views/reports/__init__.py b/brewman/routers/Management/__init__.py similarity index 100% rename from brewman/views/reports/__init__.py rename to brewman/routers/Management/__init__.py diff --git a/brewman/views/Management/db_integrity.py b/brewman/routers/Management/db_integrity.py similarity index 88% rename from brewman/views/Management/db_integrity.py rename to brewman/routers/Management/db_integrity.py index c3d2f3c3..bd158b36 100644 --- a/brewman/views/Management/db_integrity.py +++ b/brewman/routers/Management/db_integrity.py @@ -1,16 +1,8 @@ -from pyramid.view import view_config from sqlalchemy import func, distinct, over, desc -import transaction - from brewman.models.voucher import Attendance -@view_config( - request_method="POST", - route_name="api_db_integrity", - renderer="json", - permission="Authenticated", -) +@router.post("/api/db-integrity") # "Authenticated" def post_check_db(request): info = {} diff --git a/brewman/views/Management/rebase.py b/brewman/routers/Management/rebase.py similarity index 97% rename from brewman/views/Management/rebase.py rename to brewman/routers/Management/rebase.py index 6a535711..fbe1ea32 100644 --- a/brewman/views/Management/rebase.py +++ b/brewman/routers/Management/rebase.py @@ -1,9 +1,6 @@ import datetime import uuid from decimal import Decimal - -import transaction -from pyramid.view import view_config from sqlalchemy import func, and_, distinct from sqlalchemy.orm import aliased, joinedload_all @@ -22,13 +19,7 @@ from brewman.models.voucher import ( ) -@view_config( - request_method="POST", - route_name="api_rebase", - renderer="json", - permission="Rebase", - trans=True, -) +@router.post("/api/rebase/{date}") # "Rebase" def rebase(request): # request.dbsession.execute('SET statement_timeout TO 300000;') # 5 minutes date = request.matchdict.get("date", None) diff --git a/brewman/views/Management/settings.py b/brewman/routers/Management/settings.py similarity index 76% rename from brewman/views/Management/settings.py rename to brewman/routers/Management/settings.py index 8cd30beb..d4e61c11 100644 --- a/brewman/views/Management/settings.py +++ b/brewman/routers/Management/settings.py @@ -1,28 +1,9 @@ import datetime - -import pkg_resources -import transaction -from pyramid.response import FileResponse -from pyramid.view import view_config - from brewman.models.auth import User from brewman.models.master import DbSetting -@view_config(route_name="settings", permission="Authenticated") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="POST", - route_name="api_lock_information", - renderer="json", - permission="Lock Date", - trans=True, -) +@router.post("/api/lock-information") # "Lock Date" def set_lock_info(request): start_locked = request.json_body["lockOlder"] finish_locked = request.json_body["lockNewer"] @@ -59,13 +40,7 @@ def set_lock_info(request): return get_lock_info(request) -@view_config( - request_method="DELETE", - route_name="api_lock_information", - renderer="json", - permission="Lock Date", - trans=True, -) +@router.delete("/api/lock-information") # "Lock Date" def clear_lock_info(request): lock_date = ( request.dbsession.query(DbSetting).filter(DbSetting.name == "Lock Info").first() @@ -76,12 +51,7 @@ def clear_lock_info(request): return {} -@view_config( - request_method="GET", - route_name="api_lock_information", - renderer="json", - permission="Authenticated", -) +@router.get("/api/lock-information") # "Authenticated" def get_lock_info(request): data = ( request.dbsession.query(DbSetting).filter(DbSetting.name == "Lock Info").first() @@ -106,12 +76,7 @@ def get_lock_info(request): return info -@view_config( - request_method="GET", - route_name="api_maintenance", - renderer="json", - permission="Authenticated", -) +@router.get("/api/maintenance") # "Authenticated" def get_maintenance(request): data = ( request.dbsession.query(DbSetting) @@ -124,13 +89,7 @@ def get_maintenance(request): return {"enabled": True, "user": user.name} -@view_config( - request_method="POST", - route_name="api_maintenance", - renderer="json", - permission="Maintenance", - trans=True, -) +@router.post("/api/maintenance") # "Maintenance" def set_maintenance(request): status = request.json_body["enabled"] maintenance = ( diff --git a/brewman/views/Management/stock.py b/brewman/routers/Management/stock.py similarity index 95% rename from brewman/views/Management/stock.py rename to brewman/routers/Management/stock.py index 2ce693f1..4986ffac 100644 --- a/brewman/views/Management/stock.py +++ b/brewman/routers/Management/stock.py @@ -2,22 +2,13 @@ import datetime from decimal import Decimal import uuid -from pyramid.view import view_config from sqlalchemy import func -import transaction - from brewman.models.master import AccountBase, CostCentre, Product from brewman.models.validation_exception import ValidationError from brewman.models.voucher import Journal, Voucher, VoucherType, Batch, Inventory -@view_config( - request_method="POST", - route_name="api_reset_stock", - renderer="json", - permission="Reset Stock", - trans=True, -) +@router.post("/api/reset-stock/{id}") # "Reset Stock" def rebase(request): user_id = uuid.UUID(request.authenticated_userid) product = ( diff --git a/brewman/views/__init__.py b/brewman/routers/__init__.py similarity index 73% rename from brewman/views/__init__.py rename to brewman/routers/__init__.py index 0ec964c3..c415992d 100644 --- a/brewman/views/__init__.py +++ b/brewman/routers/__init__.py @@ -4,37 +4,15 @@ from datetime import date, datetime, timedelta, time from decimal import Decimal from io import BytesIO -import pkg_resources -from pyramid.httpexceptions import HTTPForbidden, HTTPFound -from pyramid.response import FileResponse, Response -from pyramid.view import view_config - from brewman.models.master import DbSetting from brewman.models.voucher import DbImage +from fastapi import APIRouter -@view_config(route_name="home") -@view_config(request_method="GET", route_name="login") -def home(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config(context=HTTPForbidden) -def forbidden_json(request): - if request.accept.quality("application/json") == 1: - response = Response("Forbidden") - response.status_int = 401 - return response - else: - ret = HTTPFound( - location=request.route_url("login", _query={"returnUrl": request.path_qs}) - ) - return ret - - -@view_config(route_name="db_image") +@router.get("/{id}/{type}") def db_image(request): item = ( request.dbsession.query(DbImage) diff --git a/brewman/routers/account.py b/brewman/routers/account.py new file mode 100644 index 00000000..ea91e0b2 --- /dev/null +++ b/brewman/routers/account.py @@ -0,0 +1,243 @@ +import uuid +from datetime import datetime + +from fastapi import APIRouter, Depends, Security +from pydantic import ValidationError +from sqlalchemy import func +from sqlalchemy.orm import joinedload_all, Session + +from ..core.security import User, get_current_active_user as get_user +from ..db.session import SessionLocal +from ..models.master import CostCentre, Account, AccountType, AccountBase +from ..models.validation_exception import ValidationError +from ..models.voucher import Voucher, Journal, VoucherType +from ..schemas.master import Account as AccountSchema +router = APIRouter() + + +# Dependency +def get_db(): + try: + db = SessionLocal() + yield db + finally: + db.close() + + +@router.post("/") +def save(data: AccountSchema, db: Session = Depends(get_db), user: User = Security(get_user, scopes=["accounts"])): + try: + item = Account( + code=0, + 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.id, db) + except: + db.rollback() + finally: + db.close() + + +@router.put("/{id_}") +def update(id_: uuid.UUID, db: Session = Depends(get_db), user: User = Security(get_user, scopes=["accounts"])): + item = ( + db.query(Account) + .filter(Account.id == id_) + .first() + ) + if item.is_fixture: + raise ValidationError( + "{0} is a fixture and cannot be edited or deleted.".format(item.name) + ) + new_type = int(request.json_body["type"]) + if not item.type == new_type: + item.code = Account.get_code(new_type, request.dbsession) + item.type = new_type + item.name = request.json_body["name"] + item.is_active = request.json_body["isActive"] + item.is_reconcilable = request.json_body["isReconcilable"] + item.is_starred = request.json_body["isStarred"] + item.cost_centre_id = uuid.UUID(request.json_body["costCentre"]["id"]) + transaction.commit() + return account_info(item.id, request.dbsession) + + +@router.delete("/{id}") # "Accounts" +def delete(id_: uuid.UUID, db: Session = Depends(get_db), user: User = Security(get_user, scopes=["accounts"])): + account = ( + db.query(Account) + .filter(Account.id == id_) + .first() + ) + can_delete, reason = account.can_delete(request.has_permission("Advanced Delete")) + if can_delete: + delete_with_data(account, db) + transaction.commit() + return account_info(None, db) + else: + transaction.abort() + response = Response("Cannot delete account because {0}".format(reason)) + response.status_int = 500 + return response + + +@router.get("/list") # "Authenticated" +async def show_list(db: Session = Depends(get_db), user: User = Depends(get_user)): + list_ = ( + db.query(Account) + .order_by(Account.type) + .order_by(Account.name) + .order_by(Account.code) + .all() + ) + accounts = [] + for item in list_: + accounts.append( + { + "id": item.id, + "name": item.name, + "type": item.type_object.name, + "isActive": item.is_active, + "isReconcilable": item.is_reconcilable, + "isStarred": item.is_starred, + "costCentre": item.cost_centre.name, + "isFixture": item.is_fixture, + } + ) + return accounts + + +@router.get("/query") # "Authenticated" +async def show_term(q: str, t: int = None, r: bool = None, a: bool = None, c: int = None, + db: Session = Depends(get_db), current_user: User = Depends(get_user)): + count = c + + list_ = [] + for index, item in enumerate(AccountBase.list(t, q, r, a, db)): + list_.append({"id": item.id, "name": item.name}) + if count is not None and index == count - 1: + break + return {"user": current_user.name, "list": list_} + + +@router.get("/{id_}/balance") # "Authenticated" +async def show_balance(id_: uuid.UUID, d: str = None, db: Session = Depends(get_db), user: User = Depends(get_user)): + date = ( + None + if d is None or d == "" + else datetime.datetime.strptime(d, "%d-%b-%Y") + ) + return {"date": balance(id_, date, db), "total": balance(id_, None, db)} + + +@router.get("/{id_}") # "Accounts" +def show_id(id_: uuid.UUID, db: Session = Depends(get_db), user: User = Security(get_user, scopes=["accounts"])): + return account_info(id_, db) + + +def balance(id_: uuid.UUID, date, dbsession: Session): + account = dbsession.query(AccountBase).filter(AccountBase.id == id_).first() + if not account.type_object.balance_sheet: + return 0 + + bal = dbsession.query(func.sum(Journal.amount * Journal.debit)).join( + Journal.voucher + ) + if date is not None: + bal = bal.filter(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 + + +@router.get("/") # "Accounts" +def show_blank(db: Session = Depends(get_db), user: User = Security(get_user, scopes=["accounts"])): + return account_info(None, db) + + +def account_info(id_, db): + if id_ is None: + account = { + "code": "(Auto)", + "type": AccountType.by_name("Creditors").id, + "isActive": True, + "isReconcilable": False, + "isStarred": False, + "costCentre": CostCentre.overall(), + } + else: + account = db.query(Account).filter(Account.id == id_).first() + account = { + "id": account.id, + "code": account.code, + "name": account.name, + "type": account.type, + "isActive": account.is_active, + "isReconcilable": account.is_reconcilable, + "isStarred": account.is_starred, + "isFixture": account.is_fixture, + "costCentre": { + "id": account.cost_centre_id, + "name": account.cost_centre.name, + }, + } + return account + + +def delete_with_data(account, db): + suspense_account = ( + db.query(Account).filter(Account.id == Account.suspense()).first() + ) + query = ( + db.query(Voucher) + .options(joinedload_all(Voucher.journals, Journal.account, innerjoin=True)) + .filter(Voucher.journals.any(Journal.account_id == account.id)) + .all() + ) + + for voucher in query: + others, sus_jnl, acc_jnl = False, None, None + for journal in voucher.journals: + if journal.account_id == account.id: + acc_jnl = journal + elif journal.account_id == Account.suspense(): + sus_jnl = journal + else: + others = True + if not others: + db.delete(voucher) + else: + if sus_jnl is None: + acc_jnl.account = suspense_account + voucher.narration += "\nSuspense \u20B9{0:,.2f} is {1}".format( + acc_jnl.amount, account.name + ) + else: + amount = (sus_jnl.debit * sus_jnl.amount) + ( + acc_jnl.debit * acc_jnl.amount + ) + db.delete(acc_jnl) + if amount == 0: + db.delete(sus_jnl) + else: + sus_jnl.amount = abs(amount) + sus_jnl.debit = -1 if amount < 0 else 1 + voucher.narration += "\nDeleted \u20B9{0:,.2f} of {1}".format( + acc_jnl.amount * acc_jnl.debit, account.name + ) + if voucher.type in ( + VoucherType.by_name("Payment").id, + VoucherType.by_name("Receipt").id, + ): + voucher.type = VoucherType.by_name("Journal") + db.delete(account) diff --git a/brewman/routers/account_types.py b/brewman/routers/account_types.py new file mode 100644 index 00000000..b188bbf2 --- /dev/null +++ b/brewman/routers/account_types.py @@ -0,0 +1,12 @@ +from brewman.models.master import AccountType +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/") # "Authenticated" +def account_type_list(request): + account_types = [] + for item in AccountType.list(): + account_types.append({"id": item.id, "name": item.name}) + return account_types diff --git a/brewman/routers/attendance.py b/brewman/routers/attendance.py new file mode 100644 index 00000000..43a811ca --- /dev/null +++ b/brewman/routers/attendance.py @@ -0,0 +1,101 @@ +import datetime +import uuid + +from sqlalchemy import or_ + +from brewman.models.master import Employee +from brewman.models.voucher import Attendance +from brewman.routers.fingerprint import get_prints +from brewman.routers.services.session import session_current_date + +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/") # "Attendance" +def attendance_blank(request): + return {"date": session_current_date(request), "body": []} + + +@router.get("/{date}") # "Attendance" +def attendance_date(request): + date = request.matchdict.get("date", None) + return attendance_date_report(date, request.dbsession) + + +def attendance_date_report(date, dbsession): + report = {"date": date, "body": []} + date = datetime.datetime.strptime(date, "%d-%b-%Y") + employees = ( + dbsession.query(Employee) + .filter(Employee.joining_date <= date) + .filter(or_(Employee.is_active, Employee.leaving_date >= date)) + .order_by(Employee.cost_centre_id) + .order_by(Employee.designation) + .order_by(Employee.name) + .all() + ) + for item in employees: + att = ( + dbsession.query(Attendance) + .filter(Attendance.employee_id == item.id) + .filter(Attendance.date == date) + .filter(Attendance.is_valid == True) + .first() + ) + att = 0 if att is None else att.attendance_type + + prints, hours, worked = get_prints(item.id, date, dbsession) + report["body"].append( + { + "id": item.id, + "code": item.code, + "name": item.name, + "designation": item.designation, + "department": item.cost_centre.name, + "attendanceType": {"id": att}, + "prints": prints, + "hours": hours, + "worked": worked, + } + ) + return report + + +@router.post("/{date}") # "Attendance" +def save(request): + user_id = uuid.UUID(request.authenticated_userid) + date = request.matchdict.get("date", None) + date_object = datetime.datetime.strptime(date, "%d-%b-%Y") + + for item in request.json_body["body"]: + employee_id = uuid.UUID(item["id"]) + attendance_type = item["attendanceType"]["id"] + if attendance_type != 0: + attendance = Attendance( + employee_id=employee_id, + date=date_object, + attendance_type=attendance_type, + user_id=user_id, + ) + attendance.create(request.dbsession) + transaction.commit() + return attendance_date_report(date, request.dbsession) + + +def daterange(start, stop, step=datetime.timedelta(days=1), inclusive=False): + # inclusive=False to behave like range by default + if step.days > 0: + while start < stop: + yield start + start = start + step + # not +=! don't modify object passed in if it's mutable + # since this function is not restricted to + # only types from datetime module + elif step.days < 0: + while start > stop: + yield start + start = start + step + if inclusive and start == stop: + yield start diff --git a/brewman/views/attendance_report.py b/brewman/routers/attendance_report.py similarity index 94% rename from brewman/views/attendance_report.py rename to brewman/routers/attendance_report.py index ed872449..b3cd4516 100644 --- a/brewman/views/attendance_report.py +++ b/brewman/routers/attendance_report.py @@ -1,13 +1,15 @@ import datetime -from pyramid.view import view_config from sqlalchemy import or_ from ..models.master import AttendanceType, Employee from ..models.voucher import Attendance from .attendance import daterange from .services.session import session_period_start, session_period_finish +from fastapi import APIRouter -@view_config(route_name="attendance_report", renderer="csv", permission="Attendance") +router = APIRouter() + +@router.get("/") # "Attendance", renderer="csv" def get_report(request): start_date = request.GET.get("StartDate", session_period_start(request)) finish_date = request.GET.get("FinishDate", session_period_finish(request)) diff --git a/brewman/routers/attendance_types.py b/brewman/routers/attendance_types.py new file mode 100644 index 00000000..183d74bf --- /dev/null +++ b/brewman/routers/attendance_types.py @@ -0,0 +1,14 @@ +from brewman.models.master import AttendanceType, Employee + +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/") # "Authenticated" +def show_list(request): + list_ = AttendanceType.list() + attendance_types = [] + for item in list_: + attendance_types.append({"id": item.id, "name": item.name, "value": item.value}) + return attendance_types diff --git a/brewman/views/services/__init__.py b/brewman/routers/auth/__init__.py similarity index 100% rename from brewman/views/services/__init__.py rename to brewman/routers/auth/__init__.py diff --git a/brewman/views/auth/client.py b/brewman/routers/auth/client.py similarity index 66% rename from brewman/views/auth/client.py rename to brewman/routers/auth/client.py index 7729868e..c098af0f 100644 --- a/brewman/views/auth/client.py +++ b/brewman/routers/auth/client.py @@ -1,29 +1,13 @@ import uuid - -import pkg_resources -from pyramid.response import Response, FileResponse -from pyramid.view import view_config from sqlalchemy import desc -import transaction - from brewman.models.auth import Client, LoginHistory +from fastapi import APIRouter -@view_config(request_method="GET", route_name="clients_list", permission="Clients") -@view_config(request_method="GET", route_name="clients_id", permission="Clients") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="PUT", - route_name="api_clients_id", - renderer="json", - permission="Clients", - trans=True, -) +@router.put("/{id}") # "Clients" def update(request): item = ( request.dbsession.query(Client) @@ -38,13 +22,7 @@ def update(request): return {} -@view_config( - request_method="DELETE", - route_name="api_clients_id", - renderer="json", - permission="Clients", - trans=True, -) +@router.delete("/{id}") # "Clients" def delete(request): id_ = request.matchdict.get("id", None) if id_ is None: @@ -61,14 +39,8 @@ def delete(request): return {} -@view_config( - request_method="GET", - route_name="api_clients_list", - request_param="l", - renderer="json", - permission="Clients", -) -def show_list(request): +@router.get("/") # "Clients" +async def show_list(l: bool): list_ = request.dbsession.query(Client).order_by(Client.name).all() clients = [] for item in list_: @@ -97,12 +69,7 @@ def show_list(request): return clients -@view_config( - request_method="GET", - route_name="api_clients_id", - renderer="json", - permission="Clients", -) +@router.get("/{id}") # "Clients" def show_id(request): item = ( request.dbsession.query(Client) diff --git a/brewman/views/auth/group.py b/brewman/routers/auth/group.py similarity index 67% rename from brewman/views/auth/group.py rename to brewman/routers/auth/group.py index af1b2bac..73e52679 100644 --- a/brewman/views/auth/group.py +++ b/brewman/routers/auth/group.py @@ -1,29 +1,12 @@ import uuid - -import pkg_resources -import transaction -from pyramid.response import Response, FileResponse -from pyramid.view import view_config - from brewman.models.auth import Group, Role +from fastapi import APIRouter -@view_config(request_method="GET", route_name="groups_new", permission="Users") -@view_config(request_method="GET", route_name="groups_id", permission="Users") -@view_config(route_name="groups_list", permission="Users") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="POST", - route_name="api_groups_new", - renderer="json", - permission="Users", - trans=True, -) +@router.post("/new") # "Users" def save(request): group = Group(request.json_body["name"]) request.dbsession.add(group) @@ -32,13 +15,7 @@ def save(request): return group_info(group.id, request.dbsession) -@view_config( - request_method="PUT", - route_name="api_groups_id", - renderer="json", - permission="Users", - trans=True, -) +@router.put("/{id}") # "Users" def update(request): id_ = request.matchdict.get("id", None) group = request.dbsession.query(Group).filter(Group.id == uuid.UUID(id_)).one() @@ -59,12 +36,7 @@ def add_permissions(group, permissions, dbsession): group.roles.remove(gp) -@view_config( - request_method="DELETE", - route_name="api_groups_id", - renderer="json", - permission="Users", -) +@router.delete("/{id}") # "Users" def delete(request): id_ = request.matchdict.get("id", None) if id_ is None: @@ -77,28 +49,18 @@ def delete(request): return response -@view_config( - request_method="GET", route_name="api_groups_id", renderer="json", permission="Users" -) +@router.get("/{id}") # "Users" def show_id(request): return group_info(uuid.UUID(request.matchdict.get("id", None)), request.dbsession) -@view_config( - request_method="GET", route_name="api_groups_new", renderer="json", permission="Users" -) +@router.get("/new") # "Users" def show_blank(request): return group_info(None, request.dbsession) -@view_config( - request_method="GET", - route_name="api_groups_list", - renderer="json", - request_param="l", - permission="Users", -) -def show_list(request): +@router.get("/") # "Users" +async def show_list(l: bool): list_ = request.dbsession.query(Group).order_by(Group.name).all() groups = [] diff --git a/brewman/views/auth/user.py b/brewman/routers/auth/user.py similarity index 76% rename from brewman/views/auth/user.py rename to brewman/routers/auth/user.py index 595a1ecf..61eb547e 100644 --- a/brewman/views/auth/user.py +++ b/brewman/routers/auth/user.py @@ -1,14 +1,12 @@ import re import uuid - -import pkg_resources -import transaction -from pyramid.response import Response, FileResponse -from pyramid.view import view_config - from brewman.models.auth import User, Group from brewman.models.validation_exception import ValidationError +from fastapi import APIRouter + +router = APIRouter() + class UserView(object): def __init__(self, request): @@ -22,21 +20,7 @@ class UserView(object): .one() ) - @view_config(request_method="GET", route_name="users_new", permission="Users") - @view_config(request_method="GET", route_name="users_id", permission="Authenticated") - @view_config(route_name="users_list", permission="Users") - def html(self): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=self.request) - - @view_config( - request_method="POST", - route_name="api_users_new", - renderer="json", - permission="Users", - trans=True, - ) + @router.post("/new") # "Users" def save(self): return self.save_user() @@ -51,13 +35,7 @@ class UserView(object): transaction.commit() return self.user_info(user.id) - @view_config( - request_method="PUT", - route_name="api_users_id", - renderer="json", - permission="Authenticated", - trans=True, - ) + @router.put("/{id}") # "Authenticated" def update(self): id_ = self.request.matchdict["id"] p = re.compile( @@ -91,12 +69,7 @@ class UserView(object): transaction.commit() return self.user_info(user.id) - @view_config( - request_method="DELETE", - route_name="api_users_id", - renderer="json", - permission="Users", - ) + @router.delete("/{id}") # "Users" def delete(self): id_ = self.request.matchdict.get("id", None) if id_ is None: @@ -108,12 +81,7 @@ class UserView(object): response.status_int = 500 return response - @view_config( - request_method="GET", - route_name="api_users_id", - renderer="json", - permission="Authenticated", - ) + @router.get("/{id}") # "Authenticated" def show_id(self): id_ = self.request.matchdict["id"] p = re.compile( @@ -123,20 +91,12 @@ class UserView(object): id_ = uuid.UUID(id_) return self.user_info(id_) - @view_config( - request_method="GET", route_name="api_users_new", renderer="json", permission="Users" - ) + @router.get("/new") # "Users" def show_blank(self): return self.user_info(None) - @view_config( - request_method="GET", - route_name="api_users_list", - renderer="json", - request_param="l", - permission="Users", - ) - def show_list(self): + @router.get("/") # "Users" + async def show_list(l: bool): list_ = self.request.dbsession.query(User).order_by(User.name).all() users = [] for item in list_: @@ -148,14 +108,8 @@ class UserView(object): users.append(user) return users - @view_config( - request_method="GET", - route_name="api_users_list", - renderer="json", - request_param="n", - permission="Authenticated", - ) - def show_name(self): + @router.get("/") # "Authenticated" + async def show_name(n: bool): list_ = ( self.request.dbsession.query(User) .filter(User.locked_out == False) diff --git a/brewman/routers/cost_centre.py b/brewman/routers/cost_centre.py new file mode 100644 index 00000000..85a05941 --- /dev/null +++ b/brewman/routers/cost_centre.py @@ -0,0 +1,148 @@ +import traceback +import uuid +from typing import List, Optional + +from sqlalchemy.exc import SQLAlchemyError + +import brewman.schemas.master as schemas + +from fastapi import APIRouter, HTTPException, status, Depends, Security +from sqlalchemy.orm import Session + +from ..core.security import User, get_current_active_user as get_user +from ..db.session import SessionLocal +from ..models.master import CostCentre + + +router = APIRouter() + + +# Dependency +def get_db(): + try: + db = SessionLocal() + yield db + finally: + db.close() + + +@router.post("/", response_model=schemas.CostCentre) +def save( + data: schemas.CostCentreSaveUpdate, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["cost-centres"]), +): + try: + item = CostCentre(data.name) + db.add(item) + db.commit() + return cost_centre_info(item, db) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), + ) + + +@router.put("/{id_}", response_model=schemas.CostCentre) +def update( + id_: uuid.UUID, + data: schemas.CostCentreSaveUpdate, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["cost-centres"]), +): + try: + item = db.query(CostCentre).filter(CostCentre.id == id_).first() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + item.name = data.name + db.commit() + return cost_centre_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 HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), + ) + + +@router.delete("/{id_}") +def delete( + id_: uuid.UUID, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["cost-centres"]), +): + try: + item = db.query(CostCentre).filter(CostCentre.id == id_).first() + + if item is None: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Cost Centre not found", + ) + elif item.is_fixture: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + else: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Cost Centre deletion not implemented", + ) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), + ) + + +@router.get("/") +def show_blank( + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["cost-centres"]), +): + return cost_centre_info(None, db) + + +@router.get("/list", response_model=List[schemas.CostCentre]) +async def show_list(db: Session = Depends(get_db), user: User = Depends(get_user)): + return [ + {"id": item.id, "name": item.name, "isFixture": item.is_fixture} + for item in db.query(CostCentre).order_by(CostCentre.name).all() + ] + + +@router.get("/{id_}", response_model=schemas.CostCentre) +def show_id( + id_: uuid.UUID, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["cost-centres"]), +): + item = db.query(CostCentre).filter(CostCentre.id == id_).first() + return cost_centre_info(item, db) + + +def cost_centre_info(item: Optional[CostCentre], db: Session): + if item is None: + return { + "name": "", + "isFixture": False, + } + else: + return { + "id": item.id, + "name": item.name, + "isFixture": item.is_fixture, + } diff --git a/brewman/views/employee.py b/brewman/routers/employee.py similarity index 85% rename from brewman/views/employee.py rename to brewman/routers/employee.py index 42c57a0d..a48d0cc0 100644 --- a/brewman/views/employee.py +++ b/brewman/routers/employee.py @@ -2,38 +2,20 @@ import datetime import uuid from decimal import Decimal -import pkg_resources -import transaction -from pyramid.response import Response, FileResponse -from pyramid.view import view_config from sqlalchemy import desc from sqlalchemy.orm import joinedload_all from brewman.models.master import CostCentre, Employee, AccountBase, Account from brewman.models.validation_exception import ValidationError from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views import to_uuid +from brewman.routers import to_uuid + +from fastapi import APIRouter + +router = APIRouter() -@view_config(request_method="GET", route_name="employees_new", permission="Employees") -@view_config(request_method="GET", route_name="employees_id", permission="Employees") -@view_config(route_name="employees_list", permission="Authenticated") -@view_config( - request_method="GET", route_name="employee_functions", permission="Employees" -) -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="POST", - route_name="api_employees_new", - renderer="json", - permission="Employees", - trans=True, -) +@router.post("/new") # "Employees" def save(request): name = request.json_body.get("name", "").strip() if name == "": @@ -93,13 +75,7 @@ def save(request): return employee_info(item.id, request.dbsession) -@view_config( - request_method="PUT", - route_name="api_employees_id", - renderer="json", - permission="Employees", - trans=True, -) +@router.put("/{id}") # "Employees" def update(request): item = ( request.dbsession.query(Employee) @@ -157,13 +133,7 @@ def update(request): return employee_info(item.id, request.dbsession) -@view_config( - request_method="DELETE", - route_name="api_employees_id", - renderer="json", - permission="Employees", - trans=True, -) +@router.delete("/{id}") # "Employees" def delete(request): employee = ( request.dbsession.query(Employee) @@ -182,13 +152,7 @@ def delete(request): return response -@view_config( - request_method="GET", - route_name="api_employees_id", - renderer="json", - permission="Employees", - trans=True, -) +@router.get("/{id}") # "Employees" def show_id(request): id_ = to_uuid(request.matchdict["id"]) if id_ is None: @@ -196,24 +160,13 @@ def show_id(request): return employee_info(id_, request.dbsession) -@view_config( - request_method="GET", - route_name="api_employees_new", - renderer="json", - permission="Employees", -) +@router.get("/new") # "Employees" def show_blank(request): return employee_info(None, request.dbsession) -@view_config( - request_method="GET", - route_name="api_employees_list", - request_param="l", - renderer="json", - permission="Authenticated", -) -def show_list(request): +@router.get("/") # "Authenticated" +async def show_list(l: bool): list_ = ( request.dbsession.query(Employee) .order_by(desc(Employee.is_active)) @@ -244,14 +197,8 @@ def show_list(request): return accounts -@view_config( - request_method="GET", - route_name="api_employees_list", - renderer="json", - request_param="q", - permission="Authenticated", -) -def show_term(request): +@router.get("/", ) # "Authenticated" +async def show_term(q: str): filter_ = request.GET.get("q", None) filter_ = None if filter_ == "" else filter_ count = request.GET.get("c", None) diff --git a/brewman/routers/employee_attendance.py b/brewman/routers/employee_attendance.py new file mode 100644 index 00000000..267be86c --- /dev/null +++ b/brewman/routers/employee_attendance.py @@ -0,0 +1,124 @@ +import datetime +import uuid + +from sqlalchemy import or_ + +from brewman.models.master import Employee +from brewman.models.validation_exception import ValidationError +from brewman.models.voucher import Attendance +from brewman.routers.fingerprint import get_prints +from brewman.routers.services.session import ( + session_period_start, + session_period_finish, + session_current_date, +) + +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/") # "Attendance" +def employee_attendance_blank(request): + return { + "startDate": session_period_start(request), + "finishDate": session_period_finish(request), + "employee": None, + "body": [], + } + + +@router.get("/{id}") # "Attendance" +def employee_attendance_report(request): + employee = ( + request.dbsession.query(Employee) + .filter(Employee.id == uuid.UUID(request.matchdict["id"])) + .first() + ) + if employee is None: + raise ValidationError("Employee id is wrong") + start_date = request.GET.get("s", session_period_start(request)) + finish_date = request.GET.get("f", session_period_finish(request)) + info = { + "startDate": start_date, + "finishDate": finish_date, + "employee": {"id": employee.id, "name": employee.name}, + } + start_date = datetime.datetime.strptime(start_date, "%d-%b-%Y") + finish_date = datetime.datetime.strptime(finish_date, "%d-%b-%Y") + 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, request.dbsession + ) + return info + + +def employee_attendance(employee, start_date, finish_date, dbsession): + if not isinstance(start_date, datetime.datetime): + start_date = datetime.datetime.strptime(start_date, "%d-%b-%Y") + if not isinstance(finish_date, datetime.datetime): + finish_date = datetime.datetime.strptime(finish_date, "%d-%b-%Y") + list_ = [] + for item in daterange(start_date, finish_date, inclusive=True): + att = ( + dbsession.query(Attendance) + .filter(Attendance.employee_id == employee.id) + .filter(Attendance.date == item) + .filter(Attendance.is_valid == True) + .first() + ) + att = 0 if att is None else att.attendance_type + prints, hours, worked = get_prints(employee.id, item, dbsession) + list_.append( + { + "date": item.strftime("%d-%b-%Y"), + "attendanceType": {"id": att}, + "prints": prints, + "hours": hours, + "worked": worked, + } + ) + return list_ + + +@router.post("/{id}") # "Attendance" +def save_employee_attendance(request): + start_date = None + finish_date = None + user_id = uuid.UUID(request.authenticated_userid) + employee = ( + request.dbsession.query(Employee) + .filter(Employee.id == uuid.UUID(request.matchdict["id"])) + .first() + ) + for item in request.json_body["body"]: + if start_date is None: + start_date = item["date"] + finish_date = item["date"] + + attendance_type = item["attendanceType"]["id"] + if attendance_type != 0: + date = datetime.datetime.strptime(item["date"], "%d-%b-%Y") + attendance = Attendance( + employee_id=employee.id, + date=date, + attendance_type=attendance_type, + user_id=user_id, + ) + attendance.create(request.dbsession) + transaction.commit() + return { + "startDate": start_date, + "finishDate": finish_date, + "employee": {"id": employee.id, "name": employee.name}, + "body": employee_attendance( + employee, start_date, finish_date, request.dbsession + ), + } diff --git a/brewman/views/fingerprint.py b/brewman/routers/fingerprint.py similarity index 94% rename from brewman/views/fingerprint.py rename to brewman/routers/fingerprint.py index e4d6157f..e22a557d 100644 --- a/brewman/views/fingerprint.py +++ b/brewman/routers/fingerprint.py @@ -3,24 +3,20 @@ import datetime import uuid from io import StringIO -import transaction -from pyramid.view import view_config from sqlalchemy import bindparam, select, exists, and_ from sqlalchemy.dialects.postgresql import insert as pg_insert -from zope.sqlalchemy import mark_changed +# from zope.sqlalchemy import mark_changed from brewman.models.master import Employee from brewman.models.voucher import Fingerprint -from brewman.views import get_lock_info +from brewman.routers import get_lock_info + +from fastapi import APIRouter + +router = APIRouter() -@view_config( - request_method="POST", - route_name="api_fingerprint", - renderer="json", - permission="Authenticated", - trans=True, -) +@router.post("/") # "Authenticated" def upload_prints(request): fingerprints_file = request.POST["uploadedFile"].file start, finish = get_lock_info(request.dbsession) diff --git a/brewman/routers/login.py b/brewman/routers/login.py new file mode 100644 index 00000000..33961ea5 --- /dev/null +++ b/brewman/routers/login.py @@ -0,0 +1,54 @@ +from datetime import timedelta + +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.orm import Session +from ..core.security import ( + Token, + authenticate_user, + ACCESS_TOKEN_EXPIRE_MINUTES, + create_access_token, +) +from ..db.session import SessionLocal + +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( + form_data: OAuth2PasswordRequestForm = Depends(), 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"}, + ) + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={ + "sub": user.name, + "scopes": ["authenticated"] + + list( + set( + [ + p.name.replace(" ", "-").lower() + for g in user.groups + for p in g.roles + ] + ) + ), + }, + expires_delta=access_token_expires, + ) + return {"access_token": access_token, "token_type": "bearer"} diff --git a/brewman/views/product.py b/brewman/routers/product.py similarity index 86% rename from brewman/views/product.py rename to brewman/routers/product.py index 104c598d..6da78537 100644 --- a/brewman/views/product.py +++ b/brewman/routers/product.py @@ -1,10 +1,6 @@ import uuid from decimal import Decimal, InvalidOperation -import pkg_resources -import transaction -from pyramid.response import Response, FileResponse -from pyramid.view import view_config from sqlalchemy import desc from sqlalchemy.orm import joinedload_all @@ -12,23 +8,12 @@ from brewman.models.master import Product, Account from brewman.models.validation_exception import ValidationError from brewman.models.voucher import Voucher, Batch, Inventory, VoucherType +from fastapi import APIRouter -@view_config(request_method="GET", route_name="products_new", permission="Products") -@view_config(request_method="GET", route_name="products_id", permission="Products") -@view_config(route_name="products_list", permission="Authenticated") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="POST", - route_name="api_products_new", - renderer="json", - permission="Products", - trans=True, -) +@router.post("/new") # "Products" def save(request): json = request.json_body @@ -92,13 +77,7 @@ def save(request): return product_info(item.id, request.dbsession) -@view_config( - request_method="PUT", - route_name="api_products_id", - renderer="json", - permission="Products", - trans=True, -) +@router.put("/{id}") # "Products" def update(request): item = ( request.dbsession.query(Product) @@ -148,13 +127,7 @@ def update(request): return product_info(item.id, request.dbsession) -@view_config( - request_method="DELETE", - route_name="api_products_id", - renderer="json", - permission="Products", - trans=True, -) +@router.delete("/{id}") # "Products" def delete(request): product = ( request.dbsession.query(Product) @@ -174,34 +147,18 @@ def delete(request): return response -@view_config( - request_method="GET", - route_name="api_products_id", - renderer="json", - permission="Products", -) +@router.get("/{id}") # "Products" def show_id(request): return product_info(uuid.UUID(request.matchdict.get("id", None)), request.dbsession) -@view_config( - request_method="GET", - route_name="api_products_new", - renderer="json", - permission="Products", -) +@router.get("/new") # "Products" def show_blank(request): return product_info(None, request.dbsession) -@view_config( - request_method="GET", - route_name="api_products_list", - request_param="l", - renderer="json", - permission="Authenticated", -) -def show_list(request): +@router.get("/", ) # "Authenticated" +def show_list(l: bool): list_ = ( request.dbsession.query(Product) .order_by(desc(Product.is_active)) @@ -232,16 +189,10 @@ def show_list(request): return products -@view_config( - request_method="GET", - route_name="api_products_list", - renderer="json", - request_param="t", - permission="Authenticated", -) -def show_term(request): +@router.get("/") # "Authenticated" +async def show_term(t: str): term = request.GET.get("t", None) - term = term.strip() if term is not None and term.strip() is not "" else None + term = term.strip() if term is not None and term.strip() != "" else None active = request.GET.get("a", None) active = active if active is not None else None count = request.GET.get("c", None) diff --git a/brewman/views/product_group.py b/brewman/routers/product_group.py similarity index 59% rename from brewman/views/product_group.py rename to brewman/routers/product_group.py index a23a6735..1af24157 100644 --- a/brewman/views/product_group.py +++ b/brewman/routers/product_group.py @@ -1,30 +1,13 @@ import uuid -import pkg_resources -from pyramid.response import Response, FileResponse - -from pyramid.view import view_config -import transaction from brewman.models.master import ProductGroup from brewman.models.validation_exception import ValidationError +from fastapi import APIRouter + +router = APIRouter() -@view_config(request_method="GET", route_name="product_groups_new", permission="Product Groups") -@view_config(request_method="GET", route_name="product_groups_id", permission="Product Groups") -@view_config(route_name="product_groups_list", permission="Authenticated") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="POST", - route_name="api_product_groups_new", - renderer="json", - permission="Product Groups", - trans=True, -) +@router.post("/new") # "Product Groups" def save(request): item = ProductGroup(request.json_body["name"]) request.dbsession.add(item) @@ -32,13 +15,7 @@ def save(request): return product_group_info(item.id, request.dbsession) -@view_config( - request_method="PUT", - route_name="api_product_groups_id", - renderer="json", - permission="Product Groups", - trans=True, -) +@router.put("/{id}") # "Product Groups" def update(request): item = ( request.dbsession.query(ProductGroup) @@ -54,12 +31,7 @@ def update(request): return product_group_info(item.id, request.dbsession) -@view_config( - request_method="DELETE", - route_name="api_product_groups_id", - renderer="json", - permission="Product Groups", -) +@router.delete("/{id}") # "Product Groups" def delete(request): item = ( request.dbsession.query(ProductGroup) @@ -81,36 +53,20 @@ def delete(request): return response -@view_config( - request_method="GET", - route_name="api_product_groups_id", - renderer="json", - permission="Product Groups", -) +@router.get("/{id}") # "Product Groups" def show_id(request): return product_group_info( uuid.UUID(request.matchdict.get("id", None)), request.dbsession ) -@view_config( - request_method="GET", - route_name="api_product_groups_new", - renderer="json", - permission="Product Groups", -) +@router.get("/new") # "Product Groups" def show_blank(request): return product_group_info(None, request.dbsession) -@view_config( - request_method="GET", - route_name="api_product_groups_list", - request_param="l", - renderer="json", - permission="Authenticated", -) -def show_list(request): +@router.get("/") # "Authenticated" +async def show_list(l: bool): list_ = request.dbsession.query(ProductGroup).order_by(ProductGroup.name).all() product_groups = [] for item in list_: diff --git a/brewman/views/recipe.py b/brewman/routers/recipe.py similarity index 89% rename from brewman/views/recipe.py rename to brewman/routers/recipe.py index 31c6e488..aff50d96 100644 --- a/brewman/views/recipe.py +++ b/brewman/routers/recipe.py @@ -3,45 +3,22 @@ import datetime import time from decimal import Decimal, InvalidOperation -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config - from sqlalchemy import desc, or_, func -import transaction - from brewman.models.master import Recipe, Product, RecipeItem, CostCentre from brewman.models.validation_exception import ValidationError from brewman.models.voucher import Voucher, Inventory, VoucherType, Journal -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_start, session_period_finish, session_period_set, ) +from fastapi import APIRouter + +router = APIRouter() -@view_config(request_method="GET", route_name="recipes_new", permission="Recipes") -@view_config(request_method="GET", route_name="recipes_id", permission="Recipes") -@view_config(route_name="recipes_list", permission="Recipes") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="POST", - route_name="api_recipes_new", - renderer="json", - permission="Recipes", -) -@view_config( - request_method="POST", - route_name="api_recipes_id", - renderer="json", - permission="Recipes", - trans=True, -) +# @router.post("/new") # "Recipes" +@router.post("/{id}") # "Recipes" def save(request): json = request.json_body recipe_product = ( @@ -283,12 +260,7 @@ def update_old_rows(product_id, valid_from, valid_to, effective_date, dbsession) dbsession.add(item) -@view_config( - request_method="DELETE", - route_name="api_recipes_id", - renderer="json", - permission="Recipes", -) +@router.delete("/{id}") # "Recipes" def delete(request): recipe = ( request.dbsession.query(Recipe) @@ -304,31 +276,18 @@ def delete(request): return recipe_info(None, request) -@view_config( - request_method="GET", - route_name="api_recipes_id", - renderer="json", - permission="Recipes", -) +@router.get("/{id}") # "Recipes" def show_id(request): return recipe_info(uuid.UUID(request.matchdict["id"]), request) -@view_config( - request_method="GET", route_name="api_recipes_new", renderer="json", permission="Recipes" -) +@router.get("/new") # "Recipes" def show_blank(request): return recipe_info(None, request) -@view_config( - request_method="GET", - route_name="api_recipes_list", - renderer="json", - request_param="list", - permission="Authenticated", -) -def show_list(request): +@router.get("/") # "Authenticated" +async def show_list(l: bool): list_ = ( request.dbsession.query(Recipe) .join(Recipe.product) diff --git a/brewman/routers/reports/__init__.py b/brewman/routers/reports/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/brewman/views/reports/balance_sheet.py b/brewman/routers/reports/balance_sheet.py similarity index 77% rename from brewman/views/reports/balance_sheet.py rename to brewman/routers/reports/balance_sheet.py index 9ee51f56..a536225e 100644 --- a/brewman/views/reports/balance_sheet.py +++ b/brewman/routers/reports/balance_sheet.py @@ -1,50 +1,28 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse from sqlalchemy.sql.expression import func, desc -from pyramid.view import view_config - from brewman.models.master import AccountType, AccountBase from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views.reports.closing_stock import get_closing_stock -from brewman.views.reports.profit_loss import get_accumulated_profit -from brewman.views.services.session import ( +from brewman.routers.reports.closing_stock import get_closing_stock +from brewman.routers.reports.profit_loss import get_accumulated_profit +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) +from fastapi import APIRouter -@view_config( - request_method="GET", route_name="balance_sheet", permission="Balance Sheet" -) -@view_config( - request_method="GET", route_name="balance_sheet_date", permission="Balance Sheet" -) -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="GET", - route_name="api_balance_sheet", - renderer="json", - permission="Balance Sheet", -) +@router.get("/api/balance-sheet") # "Balance Sheet" def report_blank(request): return {"date": session_period_finish(request), "body": [], "footer": []} -@view_config( - request_method="GET", - route_name="api_balance_sheet_date", - renderer="json", - permission="Balance Sheet", -) +@router.get("/api/balance-sheet/{date}") # "Balance Sheet" def report_data(request): date = request.matchdict.get("date", None) body, footer = build_balance_sheet(date, request.dbsession) diff --git a/brewman/views/reports/cash_flow.py b/brewman/routers/reports/cash_flow.py similarity index 85% rename from brewman/views/reports/cash_flow.py rename to brewman/routers/reports/cash_flow.py index 4d272be9..982ca211 100644 --- a/brewman/views/reports/cash_flow.py +++ b/brewman/routers/reports/cash_flow.py @@ -1,34 +1,22 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse from sqlalchemy.orm.util import aliased from sqlalchemy.sql.expression import func, desc -from pyramid.view import view_config from brewman.models.master import AccountBase, AccountType from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) +from fastapi import APIRouter -@view_config(request_method="GET", route_name="cash_flow", permission="Cash Flow") -@view_config(request_method="GET", route_name="cash_flow_id", permission="Cash Flow") -def get_html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="GET", - route_name="api_cash_flow", - renderer="json", - permission="Cash Flow", -) +@router.get("/api/cash-flow") # "Cash Flow" def report_blank(request): return { "startDate": session_period_start(request), @@ -38,13 +26,7 @@ def report_blank(request): } -@view_config( - request_method="GET", - route_name="api_cash_flow", - request_param=["s", "f"], - renderer="json", - permission="Cash Flow", -) +@router.get("/api/cash-flow", request_param=["s", "f"], permission="Cash Flow") def report_data(request): start_date = request.GET["s"] finish_date = request.GET["f"] @@ -58,13 +40,7 @@ def report_data(request): } -@view_config( - request_method="GET", - route_name="api_cash_flow_id", - request_param=["s", "f"], - renderer="json", - permission="Cash Flow", -) +@router.get("/api/cash-flow/{id}", request_param=["s", "f"], permission="Cash Flow") def get_cash_flow_id(request): id_ = request.matchdict["id"] start_date = request.GET["s"] diff --git a/brewman/views/reports/closing_stock.py b/brewman/routers/reports/closing_stock.py similarity index 77% rename from brewman/views/reports/closing_stock.py rename to brewman/routers/reports/closing_stock.py index 274b6968..154025d3 100644 --- a/brewman/views/reports/closing_stock.py +++ b/brewman/routers/reports/closing_stock.py @@ -1,47 +1,26 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.sql.expression import func from brewman.models.master import Product, CostCentre from brewman.models.voucher import Voucher, Journal, Inventory -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) +from fastapi import APIRouter -@view_config( - request_method="GET", route_name="closing_stock", permission="Closing Stock" -) -@view_config( - request_method="GET", route_name="closing_stock_date", permission="Closing Stock" -) -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="GET", - route_name="api_closing_stock", - renderer="json", - permission="Closing Stock", -) +@router.get("/api/closing-stock") # "Closing Stock" def report_blank(request): return {"date": session_period_finish(request), "body": []} -@view_config( - request_method="GET", - route_name="api_closing_stock_date", - renderer="json", - permission="Closing Stock", -) +@router.get("/api/closing-stock/{date}") # "Closing Stock" def report_data(request): date = request.matchdict.get("date", None) session_period_set(session_period_start(request), date, request) diff --git a/brewman/views/reports/daybook.py b/brewman/routers/reports/daybook.py similarity index 74% rename from brewman/views/reports/daybook.py rename to brewman/routers/reports/daybook.py index cee3a80f..5203e96c 100644 --- a/brewman/views/reports/daybook.py +++ b/brewman/routers/reports/daybook.py @@ -1,32 +1,21 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.orm import joinedload_all from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) -from brewman.views.services.voucher import get_edit_url +from brewman.routers.services.voucher import get_edit_url + +from fastapi import APIRouter + +router = APIRouter() -@view_config(request_method="GET", route_name="daybook", permission="Daybook") -def daybook_get(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="GET", - route_name="api_daybook", - renderer="json", - permission="Daybook", -) +@router.get("/api/daybook") # "Daybook" def report_blank(request): return { "startDate": session_period_start(request), @@ -35,13 +24,7 @@ def report_blank(request): } -@view_config( - request_method="GET", - route_name="api_daybook", - request_param=["s", "f"], - renderer="json", - permission="Daybook", -) +@router.get("/api/daybook", request_param=["s", "f"], permission="Daybook") def report_data(request): start_date = request.GET["s"] finish_date = request.GET["f"] diff --git a/brewman/views/reports/ledger.py b/brewman/routers/reports/ledger.py similarity index 82% rename from brewman/views/reports/ledger.py rename to brewman/routers/reports/ledger.py index bc78ba1a..88118896 100644 --- a/brewman/views/reports/ledger.py +++ b/brewman/routers/reports/ledger.py @@ -1,33 +1,24 @@ import datetime import uuid -import pkg_resources -from pyramid.response import FileResponse from sqlalchemy.orm import joinedload_all from sqlalchemy.sql.expression import func -from pyramid.view import view_config from brewman.models.master import AccountBase from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) -from brewman.views.services.voucher import get_edit_url +from brewman.routers.services.voucher import get_edit_url + +from fastapi import APIRouter + +router = APIRouter() -@view_config(request_method="GET", route_name="ledger_id", permission="Ledger") -@view_config(request_method="GET", route_name="ledger", permission="Ledger") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="GET", route_name="api_ledger", renderer="json", permission="Ledger" -) +@router.get("/api/ledger") # "Ledger" def show_blank(request): return { "startDate": session_period_start(request), @@ -37,12 +28,7 @@ def show_blank(request): } -@view_config( - request_method="GET", - route_name="api_ledger_id", - renderer="json", - permission="Ledger", -) +@router.get("/api/ledger/{id}") # "Ledger" def show_data(request): account = ( request.dbsession.query(AccountBase) diff --git a/brewman/views/reports/net_transactions.py b/brewman/routers/reports/net_transactions.py similarity index 70% rename from brewman/views/reports/net_transactions.py rename to brewman/routers/reports/net_transactions.py index 9ce71581..00b7ec6d 100644 --- a/brewman/views/reports/net_transactions.py +++ b/brewman/routers/reports/net_transactions.py @@ -1,35 +1,21 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse from sqlalchemy.sql.expression import func, desc -from pyramid.view import view_config - from brewman.models.master import AccountBase from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) +from fastapi import APIRouter -@view_config( - request_method="GET", route_name="net_transactions", permission="Net Transactions" -) -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="GET", - route_name="api_net_transactions", - renderer="json", - permission="Net Transactions", -) +@router.get("/api/net-transactions") # "Net Transactions" def show_blank(request): return { "startDate": session_period_start(request), @@ -38,13 +24,7 @@ def show_blank(request): } -@view_config( - request_method="GET", - route_name="api_net_transactions", - request_param=["s", "f"], - renderer="json", - permission="Net Transactions", -) +@router.get("/api/net-transactions", request_param=["s", "f"], permission="Net Transactions") def show_data(request): start_date = request.GET["s"] finish_date = request.GET["f"] diff --git a/brewman/views/reports/product_ledger.py b/brewman/routers/reports/product_ledger.py similarity index 84% rename from brewman/views/reports/product_ledger.py rename to brewman/routers/reports/product_ledger.py index 00fa506d..59d9d99a 100644 --- a/brewman/views/reports/product_ledger.py +++ b/brewman/routers/reports/product_ledger.py @@ -1,41 +1,24 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.orm import joinedload from sqlalchemy.sql.expression import func from brewman.models.master import Product, CostCentre from brewman.models.validation_exception import ValidationError from brewman.models.voucher import Voucher, Journal, VoucherType, Inventory -from brewman.views import to_uuid -from brewman.views.services.session import ( +from brewman.routers import to_uuid +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) -from brewman.views.services.voucher import get_edit_url +from brewman.routers.services.voucher import get_edit_url +from fastapi import APIRouter + +router = APIRouter() -@view_config( - request_method="GET", route_name="product_ledger_id", permission="Product Ledger" -) -@view_config( - request_method="GET", route_name="product_ledger", permission="Product Ledger" -) -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="GET", - route_name="api_product_ledger", - renderer="json", - permission="Product Ledger", -) +@router.get("/api/product-ledger") # "Product Ledger" def show_blank(request): return { "startDate": session_period_start(request), @@ -45,12 +28,7 @@ def show_blank(request): } -@view_config( - request_method="GET", - route_name="api_product_ledger_id", - renderer="json", - permission="Product Ledger", -) +@router.get("/api/product-ledger/{id}") # "Product Ledger" def show_data(request): id_ = to_uuid(request.matchdict["id"]) if id_ is None: diff --git a/brewman/views/reports/profit_loss.py b/brewman/routers/reports/profit_loss.py similarity index 82% rename from brewman/views/reports/profit_loss.py rename to brewman/routers/reports/profit_loss.py index 974c9528..0f7d4db1 100644 --- a/brewman/views/reports/profit_loss.py +++ b/brewman/routers/reports/profit_loss.py @@ -1,33 +1,22 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.sql.expression import func, desc from brewman.models.master import AccountType, AccountBase from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views.reports.closing_stock import get_opening_stock, get_closing_stock -from brewman.views.services.session import ( +from brewman.routers.reports.closing_stock import get_opening_stock, get_closing_stock +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) +from fastapi import APIRouter -@view_config(request_method="GET", route_name="profit_loss", permission="Profit & Loss") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="GET", - route_name="api_profit_loss", - renderer="json", - permission="Profit & Loss", -) +@router.get("/api/profit-loss", permission="Profit & Loss") def report_blank(request): return { "startDate": session_period_start(request), @@ -37,13 +26,7 @@ def report_blank(request): } -@view_config( - request_method="GET", - route_name="api_profit_loss", - request_param=["s", "f"], - renderer="json", - permission="Profit & Loss", -) +@router.get("/api/profit-loss", request_param=["s", "f"], permission="Profit & Loss") def report_data(request): start_date = request.GET["s"] finish_date = request.GET["f"] diff --git a/brewman/views/reports/purchase_entries.py b/brewman/routers/reports/purchase_entries.py similarity index 68% rename from brewman/views/reports/purchase_entries.py rename to brewman/routers/reports/purchase_entries.py index 91264c4b..ea38db49 100644 --- a/brewman/views/reports/purchase_entries.py +++ b/brewman/routers/reports/purchase_entries.py @@ -1,33 +1,19 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config - from brewman.models.voucher import Voucher, VoucherType -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) -from brewman.views.services.voucher import get_edit_url +from brewman.routers.services.voucher import get_edit_url + +from fastapi import APIRouter + +router = APIRouter() -@view_config( - request_method="GET", route_name="purchase_entries", permission="Purchase Entries" -) -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="GET", - route_name="api_purchase_entries", - renderer="json", - permission="Purchase Entries", -) +@router.get("/api/purchase-entries") # "Purchase Entries" def report_blank(request): return { "startDate": session_period_start(request), @@ -36,13 +22,7 @@ def report_blank(request): } -@view_config( - request_method="GET", - route_name="api_purchase_entries", - request_param=["s", "f"], - renderer="json", - permission="Purchase Entries", -) +@router.get("/api/purchase-entries", request_param=["s", "f"], permission="Purchase Entries") def report_data(request): start_date = request.GET["s"] finish_date = request.GET["f"] diff --git a/brewman/views/reports/purchases.py b/brewman/routers/reports/purchases.py similarity index 76% rename from brewman/views/reports/purchases.py rename to brewman/routers/reports/purchases.py index 3c5cbcc7..b48295c9 100644 --- a/brewman/views/reports/purchases.py +++ b/brewman/routers/reports/purchases.py @@ -1,32 +1,21 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.sql.expression import func, desc from brewman.models.master import CostCentre, Product from brewman.models.voucher import Voucher, Journal, Inventory, VoucherType -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) +from fastapi import APIRouter -@view_config(request_method="GET", route_name="purchases", permission="Purchases") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="GET", - route_name="api_purchases", - renderer="json", - permission="Purchases", -) +@router.get("/api/purchases") # "Purchases" def report_blank(request): return { "startDate": session_period_start(request), @@ -36,13 +25,7 @@ def report_blank(request): } -@view_config( - request_method="GET", - route_name="api_purchases", - request_param=["s", "f"], - renderer="json", - permission="Purchases", -) +@router.get("/api/purchases", request_param=["s", "f"], permission="Purchases") def report_data(request): start_date = request.GET["s"] finish_date = request.GET["f"] diff --git a/brewman/views/reports/raw_material_cost.py b/brewman/routers/reports/raw_material_cost.py similarity index 82% rename from brewman/views/reports/raw_material_cost.py rename to brewman/routers/reports/raw_material_cost.py index 3748fdab..ff883b41 100644 --- a/brewman/views/reports/raw_material_cost.py +++ b/brewman/routers/reports/raw_material_cost.py @@ -1,40 +1,22 @@ import datetime import uuid -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.sql.expression import func, case from brewman.models.master import AccountBase, CostCentre, Product, ProductGroup from brewman.models.voucher import Voucher, Journal, Inventory -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) +from fastapi import APIRouter -@view_config( - request_method="GET", route_name="raw_material_cost", permission="Raw Material Cost" -) -@view_config( - request_method="GET", - route_name="raw_material_cost_id", - permission="Raw Material Cost", -) -def get_html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="GET", - route_name="api_raw_material_cost", - renderer="json", - permission="Raw Material Cost", -) +@router.get("/api/raw-material-cost") # "Raw Material Cost" def report_blank(request): return { "startDate": session_period_start(request), @@ -44,13 +26,7 @@ def report_blank(request): } -@view_config( - request_method="GET", - route_name="api_raw_material_cost", - request_param=["s", "f"], - renderer="json", - permission="Raw Material Cost", -) +@router.get("/api/raw-material-cost", request_param=["s", "f"], permission="Raw Material Cost") def report_data(request): start_date = request.GET["s"] finish_date = request.GET["f"] @@ -64,13 +40,7 @@ def report_data(request): } -@view_config( - request_method="GET", - route_name="api_raw_material_cost_id", - request_param=["s", "f"], - renderer="json", - permission="Raw Material Cost", -) +@router.get("/api/raw-material-cost/{id}", request_param=["s", "f"], permission="Raw Material Cost") def report_id(request): id_ = request.matchdict["id"] start_date = request.GET["s"] diff --git a/brewman/views/reports/reconcile.py b/brewman/routers/reports/reconcile.py similarity index 84% rename from brewman/views/reports/reconcile.py rename to brewman/routers/reports/reconcile.py index 5e795c84..c00e3e7f 100644 --- a/brewman/views/reports/reconcile.py +++ b/brewman/routers/reports/reconcile.py @@ -1,37 +1,23 @@ import datetime import uuid - -import pkg_resources -import transaction -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.orm import joinedload_all from sqlalchemy.sql.expression import func, or_, and_ from brewman.models.master import AccountBase from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) -from brewman.views.services.voucher import get_edit_url +from brewman.routers.services.voucher import get_edit_url + +from fastapi import APIRouter + +router = APIRouter() -@view_config(request_method="GET", route_name="reconcile_id", permission="Reconcile") -@view_config(request_method="GET", route_name="reconcile", permission="Reconcile") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="GET", - route_name="api_reconcile", - renderer="json", - permission="Reconcile", -) +@router.get("/api/reconcile") # "Reconcile" def show_blank(request): return { "startDate": session_period_start(request), @@ -41,13 +27,7 @@ def show_blank(request): } -@view_config( - request_method="GET", - route_name="api_reconcile_id", - renderer="json", - permission="Reconcile", - trans=True, -) +@router.get("/api/reconcile/{id}") # "Reconcile" def show_data(request): account = ( request.dbsession.query(AccountBase) @@ -160,12 +140,7 @@ def opening_balance(account_id, start_date, dbsession): } -@view_config( - request_method="POST", - route_name="api_reconcile_id", - renderer="json", - permission="Reconcile", -) +@router.post("/api/reconcile/{id}") # "Reconcile" def save(request): account = ( request.dbsession.query(AccountBase) diff --git a/brewman/views/reports/stock_movement.py b/brewman/routers/reports/stock_movement.py similarity index 83% rename from brewman/views/reports/stock_movement.py rename to brewman/routers/reports/stock_movement.py index 16ba67c1..a2970f3a 100644 --- a/brewman/views/reports/stock_movement.py +++ b/brewman/routers/reports/stock_movement.py @@ -1,34 +1,21 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.sql.expression import func from brewman.models.master import Product, CostCentre from brewman.models.voucher import Voucher, Journal, VoucherType, Inventory -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) +from fastapi import APIRouter -@view_config( - request_method="GET", route_name="stock_movement", permission="Stock Movement" -) -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="GET", - route_name="api_stock_movement", - renderer="json", - permission="Stock Movement", -) +@router.get("/api/stock-movement") # "Stock Movement" def report_blank(request): return { "startDate": session_period_start(request), @@ -37,13 +24,7 @@ def report_blank(request): } -@view_config( - request_method="GET", - route_name="api_stock_movement", - request_param=["s", "f"], - renderer="json", - permission="Stock Movement", -) +@router.get("/api/stock-movement", request_param=["s", "f"], permission="Stock Movement") def report_data(request): start_date = request.GET["s"] finish_date = request.GET["f"] diff --git a/brewman/views/reports/trial_balance.py b/brewman/routers/reports/trial_balance.py similarity index 62% rename from brewman/views/reports/trial_balance.py rename to brewman/routers/reports/trial_balance.py index 6ca98519..6bef95df 100644 --- a/brewman/views/reports/trial_balance.py +++ b/brewman/routers/reports/trial_balance.py @@ -1,47 +1,26 @@ import datetime -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.sql.expression import func from brewman.models.master import AccountBase from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views.services.session import ( +from brewman.routers.services.session import ( session_period_set, session_period_start, session_period_finish, ) +from fastapi import APIRouter -@view_config( - request_method="GET", route_name="trial_balance", permission="Trial Balance" -) -@view_config( - request_method="GET", route_name="trial_balance_date", permission="Trial Balance" -) -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) +router = APIRouter() -@view_config( - request_method="GET", - route_name="api_trial_balance", - renderer="json", - permission="Trial Balance", -) +@router.get("/api/trial-balance") # "Trial Balance" def report_blank(request): return {"date": session_period_finish(request), "body": []} -@view_config( - request_method="GET", - route_name="api_trial_balance_date", - renderer="json", - permission="Trial Balance", -) +@router.get("/api/trial-balance/{date}") # "Trial Balance" def report_data(request): date = request.matchdict.get("date", None) session_period_set(session_period_start(request), date, request) diff --git a/brewman/views/reports/unposted.py b/brewman/routers/reports/unposted.py similarity index 73% rename from brewman/views/reports/unposted.py rename to brewman/routers/reports/unposted.py index 14983226..51ba3050 100644 --- a/brewman/views/reports/unposted.py +++ b/brewman/routers/reports/unposted.py @@ -1,25 +1,14 @@ -import pkg_resources -from pyramid.response import FileResponse -from pyramid.view import view_config from sqlalchemy.orm import joinedload_all from brewman.models.voucher import Voucher, Journal, VoucherType -from brewman.views.services.voucher import get_edit_url +from brewman.routers.services.voucher import get_edit_url + +from fastapi import APIRouter + +router = APIRouter() -@view_config(request_method="GET", route_name="unposted", permission="Post Vouchers") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="GET", - route_name="api_unposted", - renderer="json", - permission="Post Vouchers", -) +@router.get("/api/unposted") # "Post Vouchers" def report_data(request): return build_report(request) diff --git a/brewman/routers/services/__init__.py b/brewman/routers/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/brewman/views/services/batch.py b/brewman/routers/services/batch.py similarity index 89% rename from brewman/views/services/batch.py rename to brewman/routers/services/batch.py index b82460c0..89ac26b0 100644 --- a/brewman/views/services/batch.py +++ b/brewman/routers/services/batch.py @@ -1,16 +1,9 @@ import datetime -from pyramid.view import view_config - from brewman.models.voucher import Batch -@view_config( - request_method="GET", - route_name="api_batch", - renderer="json", - permission="Authenticated", -) +@router.get("/api/batch") # "Authenticated" def batch_term(request): filter_ = request.GET.get("t", None) filter_ = filter_ if filter_ is not None and filter_.strip() is not "" else None diff --git a/brewman/views/services/cost_centre.py b/brewman/routers/services/cost_centre.py similarity index 89% rename from brewman/views/services/cost_centre.py rename to brewman/routers/services/cost_centre.py index 226659d5..9815e2e9 100644 --- a/brewman/views/services/cost_centre.py +++ b/brewman/routers/services/cost_centre.py @@ -1,14 +1,11 @@ import datetime -from pyramid.view import view_config from sqlalchemy.orm.util import aliased from brewman.models.voucher import Voucher, Journal, VoucherType -@view_config( - route_name="api_issue_grid", renderer="json", permission="Issue", trans=True -) +@router.get("/api/issue-grid/{date}") # "Issue" def grid_date(request): date = datetime.datetime.strptime(request.matchdict["date"], "%d-%b-%Y") return get_grid(date, request.dbsession) diff --git a/brewman/views/services/session.py b/brewman/routers/services/session.py similarity index 65% rename from brewman/views/services/session.py rename to brewman/routers/services/session.py index bc6355db..15fa7649 100644 --- a/brewman/views/services/session.py +++ b/brewman/routers/services/session.py @@ -1,11 +1,5 @@ -import uuid from datetime import date, timedelta -from pyramid.view import view_config - -from brewman.models.auth import User -from brewman.security import f7 - def session_current_date(request): session = request.session @@ -44,25 +38,6 @@ def session_period_set(start, finish, request): ) -@view_config(route_name="api_auth", renderer="json") -def user_permission(request): - user_id = request.authenticated_userid - if user_id is None: - auth = {"isAuthenticated": False, "perms": {}} - elif "auth" in request.session: - auth = request.session["auth"] - else: - user = request.dbsession.query(User).filter(User.id == uuid.UUID(user_id)).one() - auth = { - "id": user.id, - "name": user.name, - "isAuthenticated": True, - "perms": sorted(f7([p.name for g in user.groups for p in g.roles])), - } - request.session["auth"] = auth - return auth - - def get_first_day(dt, d_years=0, d_months=0): # d_years, d_months are "deltas" to apply to dt y, m = dt.year + d_years, dt.month + d_months diff --git a/brewman/views/services/voucher/__init__.py b/brewman/routers/services/voucher/__init__.py similarity index 89% rename from brewman/views/services/voucher/__init__.py rename to brewman/routers/services/voucher/__init__.py index dab24cdd..6b9ce8f5 100644 --- a/brewman/views/services/voucher/__init__.py +++ b/brewman/routers/services/voucher/__init__.py @@ -1,11 +1,6 @@ import datetime import uuid from decimal import Decimal - -import pkg_resources -import transaction -from pyramid.response import Response, FileResponse -from pyramid.view import view_config from sqlalchemy import func, or_ from brewman.models.auth import User @@ -25,7 +20,7 @@ from brewman.models.voucher import ( Attendance, Journal, ) -from brewman.views import get_lock_info +from brewman.routers import get_lock_info from .issue import issue_create_voucher, issue_update_voucher from .journal import journal_update_voucher, journal_create_voucher from .purchase import purchase_create_voucher, purchase_update_voucher @@ -33,44 +28,7 @@ from .service_charge import service_charge_create_voucher, service_charge_update from ..session import get_first_day -@view_config(request_method="GET", route_name="journal_id", permission="Journal") -@view_config(request_method="GET", route_name="journal", permission="Journal") -@view_config(request_method="GET", route_name="payment_id", permission="Payment") -@view_config(request_method="GET", route_name="payment", permission="Payment") -@view_config(request_method="GET", route_name="receipt_id", permission="Receipt") -@view_config(request_method="GET", route_name="receipt", permission="Receipt") -@view_config(request_method="GET", route_name="purchase_id", permission="Purchase") -@view_config(request_method="GET", route_name="purchase", permission="Purchase") -@view_config( - request_method="GET", route_name="purchase_return_id", permission="Purchase Return" -) -@view_config( - request_method="GET", route_name="purchase_return", permission="Purchase Return" -) -@view_config(request_method="GET", route_name="issue_id", permission="Issue") -@view_config(request_method="GET", route_name="issue", permission="Issue") -@view_config( - request_method="GET", route_name="employee_benefits_id", permission="Issue" -) -@view_config(request_method="GET", route_name="employee_benefits", permission="Issue") -@view_config( - request_method="GET", route_name="incentive_id", permission="Service Charge" -) -@view_config(request_method="GET", route_name="incentive", permission="Service Charge") -def journal_get(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="POST", - route_name="api_voucher_id", - request_param="p", - renderer="json", - permission="Post Vouchers", - trans=True, -) +@router.post("/api/voucher/{id}", request_param="p") # "Post Vouchers" def voucher_post(request): user = ( request.dbsession.query(User) @@ -140,9 +98,7 @@ def check_delete_permissions(request, voucher): return response -@view_config( - request_method="DELETE", route_name="api_voucher_id", renderer="json", trans=True -) +@router.delete("/api/voucher/{id}") def delete(request): voucher = ( request.dbsession.query(Voucher) @@ -211,9 +167,7 @@ def delete(request): return blank_voucher(info=json_voucher, dbsession=request.dbsession) -@view_config( - request_method="GET", route_name="api_voucher_id", renderer="json", trans=True -) +@router.get("/api/voucher/{id}") def get_old(request): id_ = request.matchdict.get("id", None) voucher = ( diff --git a/brewman/views/services/voucher/credit_salary.py b/brewman/routers/services/voucher/credit_salary.py similarity index 93% rename from brewman/views/services/voucher/credit_salary.py rename to brewman/routers/services/voucher/credit_salary.py index 58cf9227..15a3a24c 100644 --- a/brewman/views/services/voucher/credit_salary.py +++ b/brewman/routers/services/voucher/credit_salary.py @@ -2,23 +2,14 @@ import calendar import datetime import uuid -from pyramid.view import view_config from sqlalchemy import or_ -import transaction - from ....models.auth import User from ....models.master import Employee, AttendanceType, Account from ....models.voucher import Voucher, VoucherType, Attendance, Journal from ..session import get_first_day, get_last_day -@view_config( - request_method="POST", - route_name="api_credit_salary", - renderer="json", - permission="Attendance", - trans=True, -) +@router.post("/api/credit-salary") # "Attendance" def credit_salary(request): user = ( request.dbsession.query(User) diff --git a/brewman/views/services/voucher/emptyvoucher.py b/brewman/routers/services/voucher/emptyvoucher.py similarity index 70% rename from brewman/views/services/voucher/emptyvoucher.py rename to brewman/routers/services/voucher/emptyvoucher.py index c5fd3e32..0f14a035 100644 --- a/brewman/views/services/voucher/emptyvoucher.py +++ b/brewman/routers/services/voucher/emptyvoucher.py @@ -1,40 +1,38 @@ -from pyramid.view import view_config, view_defaults -from brewman.views.services.session import session_current_date -from brewman.views.services.voucher import blank_voucher +from brewman.routers.services.session import session_current_date +from brewman.routers.services.voucher import blank_voucher -@view_defaults(request_method="GET", route_name="api_voucher", renderer="json") class EmptyVoucher(object): def __init__(self, request): self.request = request - @view_config(request_param="t=Journal", permission="Journal") + @router.get("/api/voucher", request_param="t=Journal") # "Journal" def journal(self): return self.get_blank() - @view_config(request_param="t=Payment", permission="Payment") + @router.get("/api/voucher", request_param="t=Payment") # "Payment" def payment(self): account = self.request.GET.get("a", None) return self.get_blank({"account": account}) - @view_config(request_param="t=Receipt", permission="Receipt") + @router.get("/api/voucher", request_param="t=Receipt") # "Receipt" def receipt(self): account = self.request.GET.get("a", None) return self.get_blank({"account": account}) - @view_config(request_param="t=Purchase", permission="Purchase") + @router.get("/api/voucher", request_param="t=Purchase") # "Purchase" def purchase(self): return self.get_blank() - @view_config(request_param="t=Purchase Return", permission="Purchase Return") + @router.get("/api/voucher", request_param="t=Purchase Return") # "Purchase Return" def purchase_return(self): return self.get_blank() - @view_config(request_param="t=Salary Deduction", permission="Purchase Return") + @router.get("/api/voucher", request_param="t=Salary Deduction") # "Purchase Return" def purchase_return(self): return self.get_blank() - @view_config(request_param="t=Issue", permission="Issue") + @router.get("/api/voucher", request_param="t=Issue") # "Issue" def issue(self): voucher_type = self.request.GET.get("t", None) date = self.request.GET.get("date", None) @@ -53,7 +51,7 @@ class EmptyVoucher(object): else: return self.get_blank() - @view_config(request_param="t=Service Charge", permission="Service Charge") + @router.get("/api/voucher", request_param="t=Service Charge") # "Service Charge" def service_charge(self): voucher_type = self.request.GET.get("t", None) date = self.request.GET.get("d", None) diff --git a/brewman/views/services/voucher/issue.py b/brewman/routers/services/voucher/issue.py similarity index 100% rename from brewman/views/services/voucher/issue.py rename to brewman/routers/services/voucher/issue.py diff --git a/brewman/views/services/voucher/journal.py b/brewman/routers/services/voucher/journal.py similarity index 100% rename from brewman/views/services/voucher/journal.py rename to brewman/routers/services/voucher/journal.py diff --git a/brewman/views/services/voucher/purchase.py b/brewman/routers/services/voucher/purchase.py similarity index 100% rename from brewman/views/services/voucher/purchase.py rename to brewman/routers/services/voucher/purchase.py diff --git a/brewman/views/services/voucher/purchase_return.py b/brewman/routers/services/voucher/purchase_return.py similarity index 100% rename from brewman/views/services/voucher/purchase_return.py rename to brewman/routers/services/voucher/purchase_return.py diff --git a/brewman/views/services/voucher/salary_deduction.py b/brewman/routers/services/voucher/salary_deduction.py similarity index 98% rename from brewman/views/services/voucher/salary_deduction.py rename to brewman/routers/services/voucher/salary_deduction.py index d43199b4..403da5d6 100644 --- a/brewman/views/services/voucher/salary_deduction.py +++ b/brewman/routers/services/voucher/salary_deduction.py @@ -5,7 +5,7 @@ from brewman.models.master import AccountBase, Employee from brewman.models.operations import journals_valid from brewman.models.validation_exception import ValidationError from brewman.models.voucher import Journal, Voucher, VoucherType, SalaryDeduction -from brewman.views.services.session import get_last_day +from brewman.routers.services.session import get_last_day def salary_deduction_create_voucher(json, user, dbsession): diff --git a/brewman/views/services/voucher/savevoucher.py b/brewman/routers/services/voucher/savevoucher.py similarity index 81% rename from brewman/views/services/voucher/savevoucher.py rename to brewman/routers/services/voucher/savevoucher.py index b87ac116..149d97e7 100644 --- a/brewman/views/services/voucher/savevoucher.py +++ b/brewman/routers/services/voucher/savevoucher.py @@ -1,15 +1,11 @@ import uuid from json import loads import datetime - -from pyramid.view import view_defaults, view_config -import transaction - from brewman.models.auth import User from brewman.models.validation_exception import ValidationError from brewman.models.voucher import Voucher -from brewman.views import get_lock_info -from brewman.views.services.session import session_current_date_set +from brewman.routers import get_lock_info +from brewman.routers.services.session import session_current_date_set from . import ( voucher_info, journal_create_voucher, @@ -21,9 +17,7 @@ from .purchase_return import purchase_return_create_voucher from .salary_deduction import salary_deduction_create_voucher -@view_defaults( - request_method="POST", route_name="api_voucher", renderer="json", trans=True -) +@router.post("/api/voucher") class SaveVoucher(object): def __init__(self, request): def parse_post(req): @@ -51,35 +45,35 @@ class SaveVoucher(object): self.start, self.finish = get_lock_info(request.dbsession) self.voucher_date = datetime.datetime.strptime(self.json["date"], "%d-%b-%Y") - @view_config(request_param="t=Journal", permission="Journal") + @router.get("/api/voucher", request_param="t=Journal") # "Journal" def journal(self): return self.save() - @view_config(request_param="t=Payment", permission="Payment") + @router.get("/api/voucher", request_param="t=Payment") # "Payment" def payment(self): return self.save() - @view_config(request_param="t=Receipt", permission="Receipt") + @router.get("/api/voucher", request_param="t=Receipt") # "Receipt" def receipt(self): return self.save() - @view_config(request_param="t=Purchase", permission="Purchase") + @router.get("/api/voucher", request_param="t=Purchase") # "Purchase" def purchase(self): return self.save() - @view_config(request_param="t=Purchase Return", permission="Purchase Return") + @router.get("/api/voucher", request_param="t=Purchase Return") # "Purchase Return" def purchase_return(self): return self.save() - @view_config(request_param="t=Issue", permission="Issue") + @router.get("/api/voucher", request_param="t=Issue") # "Issue" def issue(self): return self.save() - @view_config(request_param="t=Salary Deduction", permission="Salary Deduction") + @router.get("/api/voucher", request_param="t=Salary Deduction") # "Salary Deduction" def salary_deduction(self): return self.save() - @view_config(request_param="t=Service Charge", permission="Service Charge") + @router.get("/api/voucher", request_param="t=Service Charge") # "Service Charge" def salary_deduction(self): return self.save() diff --git a/brewman/views/services/voucher/service_charge.py b/brewman/routers/services/voucher/service_charge.py similarity index 98% rename from brewman/views/services/voucher/service_charge.py rename to brewman/routers/services/voucher/service_charge.py index 730cd07f..76eeb655 100644 --- a/brewman/views/services/voucher/service_charge.py +++ b/brewman/routers/services/voucher/service_charge.py @@ -13,7 +13,7 @@ from brewman.models.voucher import ( Attendance, ServiceCharge, ) -from brewman.views.services.session import get_first_day +from brewman.routers.services.session import get_first_day def service_charge_create_voucher(json, user, dbsession): diff --git a/brewman/views/services/voucher/updatevoucher.py b/brewman/routers/services/voucher/updatevoucher.py similarity index 83% rename from brewman/views/services/voucher/updatevoucher.py rename to brewman/routers/services/voucher/updatevoucher.py index 4a64b300..6711cdd6 100644 --- a/brewman/views/services/voucher/updatevoucher.py +++ b/brewman/routers/services/voucher/updatevoucher.py @@ -1,15 +1,13 @@ import datetime -import transaction import uuid from json import loads from pyramid.response import Response -from pyramid.view import view_defaults, view_config from brewman.models.auth import User from brewman.models.voucher import Voucher -from brewman.views import get_lock_info -from brewman.views.services.session import session_current_date_set +from brewman.routers import get_lock_info +from brewman.routers.services.session import session_current_date_set from . import ( voucher_info, issue_update_voucher, @@ -17,17 +15,14 @@ from . import ( journal_update_voucher, ) from . import service_charge_update_voucher -from brewman.views.services.voucher.purchase_return import ( +from brewman.routers.services.voucher.purchase_return import ( purchase_return_update_voucher, ) -from brewman.views.services.voucher.salary_deduction import ( +from brewman.routers.services.voucher.salary_deduction import ( salary_deduction_update_voucher, ) -@view_defaults( - request_method="POST", route_name="api_voucher_id", renderer="json", trans=True -) class UpdateVoucher(object): def __init__(self, request): def parse_post(request): @@ -95,35 +90,35 @@ class UpdateVoucher(object): else: self.error = None - @view_config(request_param="t=Journal", permission="Journal") + @router.get("/api/voucher/{id}", request_param="t=Journal") # "Journal" def journal(self): return self.update() - @view_config(request_param="t=Payment", permission="Payment") + @router.get("/api/voucher/{id}", request_param="t=Payment") # "Payment" def payment(self): return self.update() - @view_config(request_param="t=Receipt", permission="Receipt") + @router.get("/api/voucher/{id}", request_param="t=Receipt") # "Receipt" def receipt(self): return self.update() - @view_config(request_param="t=Purchase", permission="Purchase") + @router.get("/api/voucher/{id}", request_param="t=Purchase") # "Purchase" def purchase(self): return self.update() - @view_config(request_param="t=Purchase Return", permission="Purchase Return") + @router.get("/api/voucher/{id}", request_param="t=Purchase Return") # "Purchase Return" def purchase_return(self): return self.update() - @view_config(request_param="t=Issue", permission="Issue") + @router.get("/api/voucher/{id}", request_param="t=Issue") # "Issue" def issue(self): return self.update() - @view_config(request_param="t=Salary Deduction", permission="Salary Deduction") + @router.get("/api/voucher/{id}", request_param="t=Salary Deduction") # "Salary Deduction" def salary_deduction(self): return self.update() - @view_config(request_param="t=Service Charge", permission="Service Charge") + @router.get("/api/voucher/{id}", request_param="t=Service Charge") # "Service Charge" def service_charge(self): return self.update() diff --git a/brewman/routes.py b/brewman/routes.py deleted file mode 100644 index d6811dfd..00000000 --- a/brewman/routes.py +++ /dev/null @@ -1,183 +0,0 @@ -def includeme(config): - config.add_route("api_db_integrity", "/api/db-integrity") - config.add_route("api_login", "/api/login") - config.add_route("login", "/login") - config.add_route("logout", "/logout") - config.add_route("api_logout", "/api/logout") - - config.add_route("home", "/") - - config.add_route("api_account_type_list", "/api/account-types") - - config.add_route("cost_centres_new", "/cost-centres/new") - config.add_route("cost_centres_id", "/cost-centres/{id}") - config.add_route("cost_centres_list", "/cost-centres") - config.add_route("api_cost_centres_new", "/api/cost-centres/new") - config.add_route("api_cost_centres_id", "/api/cost-centres/{id}") - config.add_route("api_cost_centres_list", "/api/cost-centres") - - config.add_route("accounts_new", "/accounts/new") - config.add_route("accounts_id", "/accounts/{id}") - config.add_route("accounts_list", "/accounts") - config.add_route("api_accounts_new", "/api/accounts/new") - config.add_route("api_accounts_id", "/api/accounts/{id}") - config.add_route("api_accounts_list", "/api/accounts") - - config.add_route("employees_new", "/employees/new") - config.add_route("employees_id", "/employees/{id}") - config.add_route("employees_list", "/employees") - config.add_route("api_employees_new", "/api/employees/new") - config.add_route("api_employees_id", "/api/employees/{id}") - config.add_route("api_employees_list", "/api/employees") - - config.add_route("users_new", "/users/new") - config.add_route("users_id", "/users/{id}") - config.add_route("users_list", "/users") - config.add_route("api_users_new", "/api/users/new") - config.add_route("api_users_id", "/api/users/{id}") - config.add_route("api_users_list", "/api/users") - - config.add_route("groups_new", "/groups/new") - config.add_route("groups_id", "/groups/{id}") - config.add_route("groups_list", "/groups") - config.add_route("api_groups_new", "/api/groups/new") - config.add_route("api_groups_id", "/api/groups/{id}") - config.add_route("api_groups_list", "/api/groups") - - config.add_route("clients_id", "/clients/{id}") - config.add_route("clients_list", "/clients") - config.add_route("api_clients_id", "/api/clients/{id}") - config.add_route("api_clients_list", "/api/clients") - - config.add_route("products_new", "/products/new") - config.add_route("products_id", "/products/{id}") - config.add_route("products_list", "/products") - config.add_route("api_products_new", "/api/products/new") - config.add_route("api_products_id", "/api/products/{id}") - config.add_route("api_products_list", "/api/products") - - config.add_route("recipes_new", "/recipes/new") - config.add_route("recipes_id", "/recipes/{id}") - config.add_route("recipes_list", "/recipes") - config.add_route("api_recipes_new", "/api/recipes/new") - config.add_route("api_recipes_id", "/api/recipes/{id}") - config.add_route("api_recipes_list", "/api/recipes") - - config.add_route("product_groups_new", "/product-groups/new") - config.add_route("product_groups_id", "/product-groups/{id}") - config.add_route("product_groups_list", "/product-groups") - config.add_route("api_product_groups_new", "/api/product-groups/new") - config.add_route("api_product_groups_id", "/api/product-groups/{id}") - config.add_route("api_product_groups_list", "/api/product-groups") - - config.add_route("journal_id", "/journal/{id}") - config.add_route("journal", "/journal") - config.add_route("purchase_id", "/purchase/{id}") - config.add_route("purchase", "/purchase") - config.add_route("purchase_return_id", "/return/{id}") - config.add_route("purchase_return", "/return") - config.add_route("payment_id", "/payment/{id}") - config.add_route("payment", "/payment") - config.add_route("receipt_id", "/receipt/{id}") - config.add_route("receipt", "/receipt") - config.add_route("issue_id", "/issue/{id}") - config.add_route("issue", "/issue") - config.add_route("api_issue_grid", "/api/issue-grid/{date}") - config.add_route("employee_benefits_id", "/employee-benefits/{id}") - config.add_route("employee_benefits", "/employee-benefits") - config.add_route("incentive_id", "/incentive/{id}") - config.add_route("incentive", "/incentive") - config.add_route("db_image", "/api/db-image/{id}/{type}") - - config.add_route("api_voucher_id", "/api/voucher/{id}") - config.add_route("api_voucher", "/api/voucher") - - config.add_route("settings", "/settings") - config.add_route("api_lock_information", "/api/lock-information") - config.add_route("api_maintenance", "/api/maintenance") - - config.add_route("attendance_date", "/attendance/{date}") - config.add_route("attendance", "/attendance") - config.add_route("api_attendance_date", "/api/attendance/{date}") - config.add_route("api_attendance", "/api/attendance") - - config.add_route("api_attendance_types", "/api/attendance-types") - - config.add_route("employee_attendance_id", "/employee-attendance/{id}") - config.add_route("employee_attendance", "/employee-attendance") - config.add_route("api_employee_attendance_id", "/api/employee-attendance/{id}") - config.add_route("api_employee_attendance", "/api/employee-attendance") - - config.add_route("attendance_report", "/attendance-report") - config.add_route("api_credit_salary", "/api/credit-salary") - config.add_route("employee_functions", "/employee-functions") - config.add_route("api_fingerprint", "/api/fingerprint") - - config.add_route("ledger_id", "/ledger/{id}") - config.add_route("ledger", "/ledger") - config.add_route("api_ledger_id", "/api/ledger/{id}") - config.add_route("api_ledger", "/api/ledger") - - config.add_route("reconcile_id", "/reconcile/{id}") - config.add_route("reconcile", "/reconcile") - config.add_route("api_reconcile_id", "/api/reconcile/{id}") - config.add_route("api_reconcile", "/api/reconcile") - - config.add_route("product_ledger_id", "/product-ledger/{id}") - config.add_route("product_ledger", "/product-ledger") - config.add_route("api_product_ledger_id", "/api/product-ledger/{id}") - config.add_route("api_product_ledger", "/api/product-ledger") - - config.add_route("trial_balance_date", "/trial-balance/{date}") - config.add_route("trial_balance", "/trial-balance") - config.add_route("api_trial_balance_date", "/api/trial-balance/{date}") - config.add_route("api_trial_balance", "/api/trial-balance") - - config.add_route("net_transactions", "/net-transactions") - config.add_route("api_net_transactions", "/api/net-transactions") - - config.add_route("purchases", "/purchases") - config.add_route("api_purchases", "/api/purchases") - - config.add_route("closing_stock_date", "/closing-stock/{date}") - config.add_route("closing_stock", "/closing-stock") - config.add_route("api_closing_stock_date", "/api/closing-stock/{date}") - config.add_route("api_closing_stock", "/api/closing-stock") - - config.add_route("cash_flow_id", "/cash-flow/{id}") - config.add_route("cash_flow", "/cash-flow") - config.add_route("api_cash_flow_id", "/api/cash-flow/{id}") - config.add_route("api_cash_flow", "/api/cash-flow") - - config.add_route("raw_material_cost_id", "/raw-material-cost/{id}") - config.add_route("raw_material_cost", "/raw-material-cost") - config.add_route("api_raw_material_cost_id", "/api/raw-material-cost/{id}") - config.add_route("api_raw_material_cost", "/api/raw-material-cost") - - config.add_route("api_daybook", "/api/daybook") - config.add_route("daybook", "/daybook") - - config.add_route("unposted", "/unposted") - config.add_route("api_unposted", "/api/unposted") - - config.add_route("profit_loss", "/profit-loss") - config.add_route("api_profit_loss", "/api/profit-loss") - - config.add_route("stock_movement", "/stock-movement") - config.add_route("api_stock_movement", "/api/stock-movement") - - config.add_route("balance_sheet_date", "/balance-sheet/{date}") - config.add_route("balance_sheet", "/balance-sheet") - config.add_route("api_balance_sheet_date", "/api/balance-sheet/{date}") - config.add_route("api_balance_sheet", "/api/balance-sheet") - - config.add_route("purchase_entries", "/purchase-entries") - config.add_route("api_purchase_entries", "/api/purchase-entries") - - config.add_route("api_auth", "/api/auth") - config.add_route("api_rebase", "/api/rebase/{date}") - config.add_route("api_reset_stock", "/api/reset-stock/{id}") - - config.add_route("api_batch", "/api/batch") - - config.add_static_view("", "brewman:static") diff --git a/brewman/schemas/__init__.py b/brewman/schemas/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/brewman/schemas/auth.py b/brewman/schemas/auth.py new file mode 100644 index 00000000..a8189ab3 --- /dev/null +++ b/brewman/schemas/auth.py @@ -0,0 +1,36 @@ +import uuid +from datetime import datetime +from pydantic import BaseModel + + +class Client(BaseModel): + id_: uuid.UUID + code: int + name: str + enabled: bool + otp: int + creation_date: datetime + + +class User(BaseModel): + id_: uuid.UUID + name: str + password: str + locked_out: bool + + +class LoginHistory(BaseModel): + id_: uuid.UUID + user_id: uuid.UUID + client_id: uuid.UUID + date: datetime + + +class Group(BaseModel): + id_: uuid.UUID + name: str + + +class Role(BaseModel): + id_: uuid.UUID + name: str diff --git a/brewman/schemas/master.py b/brewman/schemas/master.py new file mode 100644 index 00000000..06198bf5 --- /dev/null +++ b/brewman/schemas/master.py @@ -0,0 +1,88 @@ +import uuid +from datetime import date +from decimal import Decimal + +from pydantic import BaseModel, Field + + +class Product(BaseModel): + id_: uuid.UUID + code: int + name: str + units: str + fraction: Decimal + fraction_units: str + product_yield: Decimal + product_group_id: uuid.UUID + account_id: uuid.UUID + price: Decimal + sale_price: Decimal + is_active: bool + is_fixture: bool + is_purchased: bool + is_sold: bool + + +class Recipe(BaseModel): + id_: uuid.UUID + product_id: uuid.UUID + + quantity: Decimal + cost_price: Decimal + sale_price: Decimal + notes: str + + valid_from: date + valid_to: date + + effective_from: date + effective_to: date + + +class RecipeItem(BaseModel): + id_: uuid.UUID + recipe_id: uuid.UUID + product_id: uuid.UUID + quantity: int + price: int + + +class ProductGroup(BaseModel): + id_: uuid.UUID + name: str + is_fixture: bool + + +class CostCentreSaveUpdate(BaseModel): + name: str + + +class CostCentre(CostCentreSaveUpdate): + id_: uuid.UUID = Field(alias="id") + is_fixture: bool = Field(alias="isFixture") + + +class Account(BaseModel): + id_: uuid.UUID + code: int + name: str + type: int + is_starred: bool + is_active: bool + is_reconcilable: bool + cost_centre: CostCentre + is_fixture: bool + + +class Employee(Account): + designation: str + salary: int + service_points: Decimal + joining_date: date + leaving_date: date + + +class DbSetting(BaseModel): + id_: uuid.UUID + name: str + data: bytes diff --git a/brewman/schemas/voucher.py b/brewman/schemas/voucher.py new file mode 100644 index 00000000..47ea001c --- /dev/null +++ b/brewman/schemas/voucher.py @@ -0,0 +1,96 @@ +import uuid +from datetime import datetime, date +from decimal import Decimal + +from pydantic import BaseModel + + +class Voucher(BaseModel): + id: uuid.UUID + date: date + narration: str + is_reconciled: bool + reconcile_date: datetime + is_starred: bool + creation_date: datetime + last_edit_date: datetime + _type: int + user_id: uuid.UUID + posted: bool + poster_id: uuid.UUID + + +class Journal(BaseModel): + id: uuid.UUID + debit: int + amount: Decimal + voucher_id: uuid.UUID + account_id: uuid.UUID + cost_centre_id: uuid.UUID + + +class SalaryDeduction(BaseModel): + id: uuid.UUID + voucher_id: uuid.UUID + journal_id: uuid.UUID + gross_salary: int + days_worked: int + esi_ee: int + pf_ee: int + esi_er: int + pf_er: int + + +class ServiceCharge(BaseModel): + id: uuid.UUID + voucher_id: uuid.UUID + journal_id: uuid.UUID + days_worked: int + points: Decimal + + +class Inventory(BaseModel): + id: uuid.UUID + voucher_id: uuid.UUID + product_id: uuid.UUID + batch_id: uuid.UUID + quantity: Decimal + rate: Decimal + tax: Decimal + discount: Decimal + + +class Batch(BaseModel): + id: uuid.UUID + name: datetime + product_id: uuid.UUID + quantity_remaining: Decimal + rate: Decimal + tax: Decimal + discount: Decimal + + +class Attendance(BaseModel): + id: uuid.UUID + employee_id: uuid.UUID + date: date + attendance_type: int + amount: Decimal + creation_date: datetime + user_id: uuid.UUID + is_valid: bool + + +class Fingerprint(BaseModel): + id: uuid.UUID + employee_id: uuid.UUID + date: date + + +class DbImage(BaseModel): + id: uuid.UUID + resource_id: uuid.UUID + resource_type: str + image: bytearray + thumbnail: bytearray + creation_date: datetime diff --git a/brewman/security/__init__.py b/brewman/security/__init__.py deleted file mode 100644 index e5b6b130..00000000 --- a/brewman/security/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -import uuid -from pyramid.authentication import AuthTktAuthenticationPolicy -from brewman.security.permission_authorization_policy import ( - PermissionAuthorizationPolicy, -) -from ..models.auth import User - - -def get_user(request): - user_id = request.unauthenticated_userid - if user_id is not None: - user = request.dbsession.query(User).get(user_id) - return user - - -def groupfinder(user_id, request): - if "perms" in request.session: - perms = request.session["perms"] - else: - perms = [] - user = request.dbsession.query(User).filter(User.id == uuid.UUID(user_id)).one() - for item in user.groups: - for perm in item.roles: - perms.append(perm.name) - perms = sorted(f7(perms)) - request.session["perms"] = perms - return perms - - -def f7(seq): - seen = set() - seen_add = seen.add - return [x for x in seq if x not in seen and not seen_add(x)] - - -def includeme(config): - authn_policy = AuthTktAuthenticationPolicy( - "brewman", timeout=900, reissue_time=90, callback=groupfinder - ) - config.set_authentication_policy(authn_policy) - config.set_authorization_policy(PermissionAuthorizationPolicy()) - config.add_request_method(get_user, "user", reify=True) diff --git a/brewman/security/permission_authorization_policy.py b/brewman/security/permission_authorization_policy.py deleted file mode 100644 index 0d98e658..00000000 --- a/brewman/security/permission_authorization_policy.py +++ /dev/null @@ -1,19 +0,0 @@ -from zope.interface import implementer - -from pyramid.interfaces import IAuthorizationPolicy - - -@implementer(IAuthorizationPolicy) -class PermissionAuthorizationPolicy(object): - def permits(self, context, principals, permission): - if permission == "Authenticated": - permission = "system.Authenticated" - if permission == "Everyone": - permission = "system.Everyone" - allowed = permission in principals - return allowed - - def principals_allowed_by_permission(self, context, permission): - allowed = set() - allowed.add(permission) - return allowed diff --git a/brewman/tests.py b/brewman/tests.py deleted file mode 100644 index de11ce63..00000000 --- a/brewman/tests.py +++ /dev/null @@ -1,18 +0,0 @@ -import unittest - -from pyramid import testing - - -class ViewTests(unittest.TestCase): - def setUp(self): - self.config = testing.setUp() - - def tearDown(self): - testing.tearDown() - - def test_my_view(self): - from .views import my_view - - request = testing.DummyRequest() - info = my_view(request) - self.assertEqual(info["project"], "brewman") diff --git a/brewman/transactional_view_deriver.py b/brewman/transactional_view_deriver.py deleted file mode 100644 index 18ff593d..00000000 --- a/brewman/transactional_view_deriver.py +++ /dev/null @@ -1,33 +0,0 @@ -from pyramid.response import Response -from sqlalchemy.exc import OperationalError, IntegrityError, DBAPIError -import transaction -from brewman.models.validation_exception import ValidationError - - -def transactional_view(view, info): - if info.options.get("trans"): - - def wrapper_view(context, request): - try: - response = view(context, request) - except ( - ValidationError, - ValueError, - KeyError, - AttributeError, - TypeError, - OperationalError, - IntegrityError, - DBAPIError, - ) as ex: - transaction.abort() - response = Response("Failed validation: {0}".format(str(ex))) - response.status_int = 500 - finally: - return response - - return wrapper_view - return view - - -transactional_view.options = ("trans",) diff --git a/brewman/views/account.py b/brewman/views/account.py deleted file mode 100644 index 972423ce..00000000 --- a/brewman/views/account.py +++ /dev/null @@ -1,310 +0,0 @@ -import datetime -import uuid - -import pkg_resources -import transaction -from pyramid.response import Response, FileResponse -from pyramid.view import view_config -from sqlalchemy import func -from sqlalchemy.orm import joinedload_all - -from brewman.models.master import CostCentre, Account, AccountType, AccountBase -from brewman.models.validation_exception import ValidationError -from brewman.models.voucher import Voucher, Journal, VoucherType - - -@view_config(request_method="GET", route_name="accounts_new", permission="Accounts") -@view_config(request_method="GET", route_name="accounts_id", permission="Accounts") -@view_config(route_name="accounts_list", permission="Accounts") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="POST", - route_name="api_accounts_new", - renderer="json", - permission="Accounts", - trans=True, -) -def save(request): - json = request.json_body - item = Account( - code=0, - name=json["name"], - type=int(json["type"]), - is_starred=json["isStarred"], - is_active=json["isActive"], - is_reconcilable=json["isReconcilable"], - cost_centre_id=uuid.UUID(json["costCentre"]["id"]), - ).create(request.dbsession) - transaction.commit() - return account_info(item.id, request.dbsession) - - -@view_config( - request_method="PUT", - route_name="api_accounts_id", - renderer="json", - permission="Accounts", - trans=True, -) -def update(request): - item = ( - request.dbsession.query(Account) - .filter(Account.id == uuid.UUID(request.matchdict["id"])) - .first() - ) - if item.is_fixture: - raise ValidationError( - "{0} is a fixture and cannot be edited or deleted.".format(item.name) - ) - new_type = int(request.json_body["type"]) - if not item.type == new_type: - item.code = Account.get_code(new_type, request.dbsession) - item.type = new_type - item.name = request.json_body["name"] - item.is_active = request.json_body["isActive"] - item.is_reconcilable = request.json_body["isReconcilable"] - item.is_starred = request.json_body["isStarred"] - item.cost_centre_id = uuid.UUID(request.json_body["costCentre"]["id"]) - transaction.commit() - return account_info(item.id, request.dbsession) - - -@view_config( - request_method="DELETE", - route_name="api_accounts_id", - renderer="json", - permission="Accounts", -) -def delete(request): - account = ( - request.dbsession.query(Account) - .filter(Account.id == uuid.UUID(request.matchdict["id"])) - .first() - ) - can_delete, reason = account.can_delete(request.has_permission("Advanced Delete")) - if can_delete: - delete_with_data(account, request.dbsession) - transaction.commit() - return account_info(None, request.dbsession) - else: - transaction.abort() - response = Response("Cannot delete account because {0}".format(reason)) - response.status_int = 500 - return response - - -@view_config( - request_method="GET", - route_name="api_accounts_id", - renderer="json", - request_param="b", - permission="Authenticated", -) -def show_balance(request): - date = request.GET.get("d", None) - date = ( - None - if date is None or date == "" - else datetime.datetime.strptime(date, "%d-%b-%Y") - ) - id_ = uuid.UUID(request.matchdict["id"]) - return { - "date": balance(id_, date, request.dbsession), - "total": balance(id_, None, request.dbsession), - } - - -def balance(id_, date, dbsession): - account = dbsession.query(AccountBase).filter(AccountBase.id == id_).first() - if not account.type_object.balance_sheet: - return 0 - - bal = dbsession.query(func.sum(Journal.amount * Journal.debit)).join( - Journal.voucher - ) - if date is not None: - bal = bal.filter(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 - - -@view_config( - request_method="GET", - route_name="api_accounts_id", - renderer="json", - permission="Accounts", -) -def show_id(request): - return account_info(uuid.UUID(request.matchdict["id"]), request.dbsession) - - -@view_config( - request_method="GET", - route_name="api_accounts_new", - renderer="json", - permission="Accounts", -) -def show_blank(request): - return account_info(None, request.dbsession) - - -@view_config( - request_method="GET", - route_name="api_accounts_list", - renderer="json", - request_param="l", - permission="Authenticated", -) -def show_list(request): - list_ = ( - request.dbsession.query(Account) - .order_by(Account.type) - .order_by(Account.name) - .order_by(Account.code) - .all() - ) - accounts = [] - for item in list_: - accounts.append( - { - "id": item.id, - "name": item.name, - "type": item.type_object.name, - "isActive": item.is_active, - "isReconcilable": item.is_reconcilable, - "isStarred": item.is_starred, - "costCentre": item.cost_centre.name, - "isFixture": item.is_fixture, - } - ) - return accounts - - -@view_config( - request_method="GET", - route_name="api_accounts_list", - renderer="json", - request_param="q", - permission="Authenticated", -) -def show_term(request): - type_ = request.GET.get("t", None) - type_ = int(type_) if type_ is not None else None - filter_ = request.GET.get("q", None) - filter_ = filter_ if filter_ is not None and filter_ is not "" else None - reconcilable = request.GET.get("r", None) - reconcilable = reconcilable if reconcilable is not None else None - active = request.GET.get("a", None) - active = active if active is not None else None - count = request.GET.get("c", None) - count = None if count is None or count == "" else int(count) - - list_ = [] - for index, item in enumerate( - AccountBase.list(type_, filter_, reconcilable, active, request.dbsession) - ): - list_.append({"id": item.id, "name": item.name}) - if count is not None and index == count - 1: - break - return list_ - - -@view_config( - request_method="GET", - route_name="api_account_type_list", - renderer="json", - permission="Authenticated", -) -def account_type_list(request): - account_types = [] - for item in AccountType.list(): - account_types.append({"id": item.id, "name": item.name}) - return account_types - - -def account_info(id_, dbsession): - if id_ is None: - account = { - "code": "(Auto)", - "type": AccountType.by_name("Creditors").id, - "isActive": True, - "isReconcilable": False, - "isStarred": False, - "costCentre": CostCentre.overall(), - } - else: - account = dbsession.query(Account).filter(Account.id == id_).first() - account = { - "id": account.id, - "code": account.code, - "name": account.name, - "type": account.type, - "isActive": account.is_active, - "isReconcilable": account.is_reconcilable, - "isStarred": account.is_starred, - "isFixture": account.is_fixture, - "costCentre": { - "id": account.cost_centre_id, - "name": account.cost_centre.name, - }, - } - return account - - -def delete_with_data(account, dbsession): - suspense_account = ( - dbsession.query(Account).filter(Account.id == Account.suspense()).first() - ) - query = ( - dbsession.query(Voucher) - .options(joinedload_all(Voucher.journals, Journal.account, innerjoin=True)) - .filter(Voucher.journals.any(Journal.account_id == account.id)) - .all() - ) - - for voucher in query: - others, sus_jnl, acc_jnl = False, None, None - for journal in voucher.journals: - if journal.account_id == account.id: - acc_jnl = journal - elif journal.account_id == Account.suspense(): - sus_jnl = journal - else: - others = True - if not others: - dbsession.delete(voucher) - else: - if sus_jnl is None: - acc_jnl.account = suspense_account - voucher.narration += "\nSuspense \u20B9{0:,.2f} is {1}".format( - acc_jnl.amount, account.name - ) - else: - amount = (sus_jnl.debit * sus_jnl.amount) + ( - acc_jnl.debit * acc_jnl.amount - ) - dbsession.delete(acc_jnl) - if amount == 0: - dbsession.delete(sus_jnl) - else: - sus_jnl.amount = abs(amount) - sus_jnl.debit = -1 if amount < 0 else 1 - voucher.narration += "\nDeleted \u20B9{0:,.2f} of {1}".format( - acc_jnl.amount * acc_jnl.debit, account.name - ) - if voucher.type in ( - VoucherType.by_name("Payment").id, - VoucherType.by_name("Receipt").id, - ): - voucher.type = VoucherType.by_name("Journal") - dbsession.delete(account) diff --git a/brewman/views/attendance.py b/brewman/views/attendance.py deleted file mode 100644 index c44f658b..00000000 --- a/brewman/views/attendance.py +++ /dev/null @@ -1,274 +0,0 @@ -import datetime -import uuid - -import pkg_resources -import transaction -from pyramid.response import FileResponse -from pyramid.view import view_config -from sqlalchemy import or_ - -from brewman.models.master import AttendanceType, Employee -from brewman.models.validation_exception import ValidationError -from brewman.models.voucher import Attendance -from brewman.views.fingerprint import get_prints -from brewman.views.services.session import ( - session_period_start, - session_period_finish, - session_current_date, -) - - -@view_config( - request_method="GET", route_name="attendance_date", permission="Attendance" -) -@view_config(request_method="GET", route_name="attendance", permission="Attendance") -@view_config( - request_method="GET", route_name="employee_attendance_id", permission="Attendance" -) -@view_config( - request_method="GET", route_name="employee_attendance", permission="Attendance" -) -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="GET", - route_name="api_attendance_types", - renderer="json", - permission="Authenticated", -) -def show_list(request): - list_ = AttendanceType.list() - attendance_types = [] - for item in list_: - attendance_types.append({"id": item.id, "name": item.name, "value": item.value}) - return attendance_types - - -@view_config( - request_method="GET", - route_name="api_attendance", - renderer="json", - permission="Attendance", -) -def attendance_blank(request): - return {"date": session_current_date(request), "body": []} - - -@view_config( - request_method="GET", - route_name="api_attendance_date", - renderer="json", - permission="Attendance", -) -def attendance_date(request): - date = request.matchdict.get("date", None) - return attendance_date_report(date, request.dbsession) - - -def attendance_date_report(date, dbsession): - report = {"date": date, "body": []} - date = datetime.datetime.strptime(date, "%d-%b-%Y") - employees = ( - dbsession.query(Employee) - .filter(Employee.joining_date <= date) - .filter(or_(Employee.is_active, Employee.leaving_date >= date)) - .order_by(Employee.cost_centre_id) - .order_by(Employee.designation) - .order_by(Employee.name) - .all() - ) - for item in employees: - att = ( - dbsession.query(Attendance) - .filter(Attendance.employee_id == item.id) - .filter(Attendance.date == date) - .filter(Attendance.is_valid == True) - .first() - ) - att = 0 if att is None else att.attendance_type - - prints, hours, worked = get_prints(item.id, date, dbsession) - report["body"].append( - { - "id": item.id, - "code": item.code, - "name": item.name, - "designation": item.designation, - "department": item.cost_centre.name, - "attendanceType": {"id": att}, - "prints": prints, - "hours": hours, - "worked": worked, - } - ) - return report - - -@view_config( - request_method="POST", - route_name="api_attendance_date", - renderer="json", - permission="Attendance", - trans=True, -) -def save(request): - user_id = uuid.UUID(request.authenticated_userid) - date = request.matchdict.get("date", None) - date_object = datetime.datetime.strptime(date, "%d-%b-%Y") - - for item in request.json_body["body"]: - employee_id = uuid.UUID(item["id"]) - attendance_type = item["attendanceType"]["id"] - if attendance_type != 0: - attendance = Attendance( - employee_id=employee_id, - date=date_object, - attendance_type=attendance_type, - user_id=user_id, - ) - attendance.create(request.dbsession) - transaction.commit() - return attendance_date_report(date, request.dbsession) - - -@view_config( - request_method="GET", - route_name="api_employee_attendance", - renderer="json", - permission="Attendance", -) -def employee_attendance_blank(request): - return { - "startDate": session_period_start(request), - "finishDate": session_period_finish(request), - "employee": None, - "body": [], - } - - -@view_config( - request_method="GET", - route_name="api_employee_attendance_id", - renderer="json", - permission="Attendance", -) -def employee_attendance_report(request): - employee = ( - request.dbsession.query(Employee) - .filter(Employee.id == uuid.UUID(request.matchdict["id"])) - .first() - ) - if employee is None: - raise ValidationError("Employee id is wrong") - start_date = request.GET.get("s", session_period_start(request)) - finish_date = request.GET.get("f", session_period_finish(request)) - info = { - "startDate": start_date, - "finishDate": finish_date, - "employee": {"id": employee.id, "name": employee.name}, - } - start_date = datetime.datetime.strptime(start_date, "%d-%b-%Y") - finish_date = datetime.datetime.strptime(finish_date, "%d-%b-%Y") - 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, request.dbsession - ) - return info - - -def employee_attendance(employee, start_date, finish_date, dbsession): - if not isinstance(start_date, datetime.datetime): - start_date = datetime.datetime.strptime(start_date, "%d-%b-%Y") - if not isinstance(finish_date, datetime.datetime): - finish_date = datetime.datetime.strptime(finish_date, "%d-%b-%Y") - list_ = [] - for item in daterange(start_date, finish_date, inclusive=True): - att = ( - dbsession.query(Attendance) - .filter(Attendance.employee_id == employee.id) - .filter(Attendance.date == item) - .filter(Attendance.is_valid == True) - .first() - ) - att = 0 if att is None else att.attendance_type - prints, hours, worked = get_prints(employee.id, item, dbsession) - list_.append( - { - "date": item.strftime("%d-%b-%Y"), - "attendanceType": {"id": att}, - "prints": prints, - "hours": hours, - "worked": worked, - } - ) - return list_ - - -@view_config( - request_method="POST", - route_name="api_employee_attendance_id", - renderer="json", - permission="Attendance", - trans=True, -) -def save_employee_attendance(request): - start_date = None - finish_date = None - user_id = uuid.UUID(request.authenticated_userid) - employee = ( - request.dbsession.query(Employee) - .filter(Employee.id == uuid.UUID(request.matchdict["id"])) - .first() - ) - for item in request.json_body["body"]: - if start_date is None: - start_date = item["date"] - finish_date = item["date"] - - attendance_type = item["attendanceType"]["id"] - if attendance_type != 0: - date = datetime.datetime.strptime(item["date"], "%d-%b-%Y") - attendance = Attendance( - employee_id=employee.id, - date=date, - attendance_type=attendance_type, - user_id=user_id, - ) - attendance.create(request.dbsession) - transaction.commit() - return { - "startDate": start_date, - "finishDate": finish_date, - "employee": {"id": employee.id, "name": employee.name}, - "body": employee_attendance( - employee, start_date, finish_date, request.dbsession - ), - } - - -def daterange(start, stop, step=datetime.timedelta(days=1), inclusive=False): - # inclusive=False to behave like range by default - if step.days > 0: - while start < stop: - yield start - start = start + step - # not +=! don't modify object passed in if it's mutable - # since this function is not restricted to - # only types from datetime module - elif step.days < 0: - while start > stop: - yield start - start = start + step - if inclusive and start == stop: - yield start diff --git a/brewman/views/cost_centre.py b/brewman/views/cost_centre.py deleted file mode 100644 index 0a8ad846..00000000 --- a/brewman/views/cost_centre.py +++ /dev/null @@ -1,132 +0,0 @@ -import uuid -import pkg_resources -from pyramid.response import Response, FileResponse - -from pyramid.view import view_config -import transaction - -from brewman.models.master import CostCentre -from brewman.models.validation_exception import ValidationError - - -@view_config(request_method="GET", route_name="cost_centres_new", permission="Cost Centres") -@view_config(request_method="GET", route_name="cost_centres_id", permission="Cost Centres") -@view_config(route_name="cost_centres_list", permission="Authenticated") -def html(request): - package, resource = "brewman:static/index.html".split(":", 1) - file = pkg_resources.resource_filename(package, resource) - return FileResponse(file, request=request) - - -@view_config( - request_method="POST", - route_name="api_cost_centres_new", - renderer="json", - permission="Cost Centres", - trans=True, -) -def save(request): - item = CostCentre(request.json_body["name"]) - request.dbsession.add(item) - transaction.commit() - return cost_centre_info(item.id, request.dbsession) - - -@view_config( - request_method="PUT", - route_name="api_cost_centres_id", - renderer="json", - permission="Cost Centres", - trans=True, -) -def update(request): - item = ( - request.dbsession.query(CostCentre) - .filter(CostCentre.id == uuid.UUID(request.matchdict["id"])) - .first() - ) - if item.is_fixture: - raise ValidationError( - "{0} is a fixture and cannot be edited or deleted.".format(item.name) - ) - item.name = request.json_body["name"] - transaction.commit() - return cost_centre_info(item.id, request.dbsession) - - -@view_config( - request_method="DELETE", - route_name="api_cost_centres_id", - renderer="json", - permission="Cost Centres", -) -def delete(request): - item = ( - request.dbsession.query(CostCentre) - .filter(CostCentre.id == uuid.UUID(request.matchdict["id"])) - .first() - ) - - if item is None: - response = Response("Cost Centre not found") - response.status_int = 500 - return response - elif item.is_fixture: - raise ValidationError( - "{0} is a fixture and cannot be edited or deleted.".format(item.name) - ) - else: - response = Response("Cost Centre deletion not implemented") - response.status_int = 500 - return response - - -@view_config( - request_method="GET", - route_name="api_cost_centres_id", - renderer="json", - permission="Cost Centres", -) -def show_id(request): - return cost_centre_info( - uuid.UUID(request.matchdict.get("id", None)), request.dbsession - ) - - -@view_config( - request_method="GET", - route_name="api_cost_centres_new", - renderer="json", - permission="Cost Centres", -) -def show_blank(request): - return cost_centre_info(None, request.dbsession) - - -@view_config( - request_method="GET", - route_name="api_cost_centres_list", - request_param="l", - renderer="json", - permission="Authenticated", -) -def show_list(request): - list_ = request.dbsession.query(CostCentre).order_by(CostCentre.name).all() - cost_centres = [] - for item in list_: - cost_centres.append( - {"id": item.id, "name": item.name, "isFixture": item.is_fixture} - ) - return cost_centres - - -def cost_centre_info(id_, dbsession): - if id_ is None: - return {} - else: - cost_centre = dbsession.query(CostCentre).filter(CostCentre.id == id_).first() - return { - "id": cost_centre.id, - "name": cost_centre.name, - "isFixture": cost_centre.is_fixture, - } diff --git a/brewman/views/services/login.py b/brewman/views/services/login.py deleted file mode 100644 index 376ea4c3..00000000 --- a/brewman/views/services/login.py +++ /dev/null @@ -1,155 +0,0 @@ -from datetime import datetime, timedelta - -import transaction -from pyramid.httpexceptions import HTTPFound -from pyramid.response import Response -from pyramid.security import remember, forget -from pyramid.view import view_config -from sqlalchemy import and_ - -from brewman.models.auth import User, Client, LoginHistory -from brewman.security import f7 - - -@view_config(route_name="logout") -def logout(request): - request.session.invalidate() - headers = forget(request) - return HTTPFound(location=request.route_url("home"), headers=headers) - - -@view_config(request_method="POST", route_name="api_logout", renderer="json") -def api_logout(request): - request.session.invalidate() - request.response.headers = forget(request) - return {"isAuthenticated": False, "perms": {}} - - -@view_config(request_method="POST", route_name="api_login", renderer="json", trans=True) -def login(request): - username = request.json_body.get("name", None) - password = request.json_body.get("password", None) - found, user = User.auth(username, password, request.dbsession) - - client = Client.by_code(request.cookies.get("ClientID", None), request.dbsession) - otp = request.json_body.get("otp", None) - client_name = request.json_body.get("clientName", None) - if user is not None: - allowed, client, response = check_client( - client, otp, client_name, user, request.dbsession - ) - - if found and allowed: - headers = remember(request, str(user.id)) - request.response.headers = headers - request.response.set_cookie( - "ClientID", value=str(client.code), max_age=365 * 24 * 60 * 60 - ) - record_login(user.id, client, request.dbsession) - transaction.commit() - - return { - "id": user.id, - "name": user.name, - "isAuthenticated": True, - "perms": sorted(f7([p.name for g in user.groups for p in g.roles])), - } - elif not found: - response = Response("Login failed") - response.status_int = 403 - transaction.commit() - return response - else: - transaction.commit() - return response - - -def check_client(client, otp, client_name, user, dbsession): - outside_login_allowed = False - for group in user.groups: - for perm in group.roles: - if perm.name == "Clients": - outside_login_allowed = True - - if ( - dbsession.query(Client).filter(Client.enabled == True).count() == 0 - and outside_login_allowed - ): - client = Client.create(dbsession) - client.otp = None - client.name = "Created on login by " + user.name - client.enabled = True - return True, client, None - - if client is None: - if outside_login_allowed: - client = Client.create(dbsession) - return True, client, None - else: - client = Client.create(dbsession) - response = Response("Unknown Client") - response.status_int = 403 - response.set_cookie( - "ClientID", value=str(client.code), max_age=10 * 365 * 24 * 60 * 60 - ) - return False, None, response - - if client.enabled or outside_login_allowed: - return True, client, None - - if client.otp is None: - response = Response("Client is Forbidden") - response.status_int = 403 - return False, None, response - - if otp is None: - response = Response("OTP not supplied") - response.status_int = 403 - return False, None, response - elif client.otp != int(otp): - response = Response("OTP is wrong") - response.status_int = 403 - return False, None, response - else: - client.otp = None - client.enabled = True - client.name = client_name - return True, client, None - - -def record_login(user_id, client, dbsession): - history = LoginHistory(user_id) - history.client = client - dbsession.add(history) - - recent_logins = ( - dbsession.query(LoginHistory.client_id.distinct()) - .filter(LoginHistory.date > datetime.utcnow() - timedelta(days=30)) - .subquery() - ) - - deletable_clients = ( - dbsession.query(Client.id) - .filter(Client.creation_date < datetime.utcnow() - timedelta(days=3)) - .filter(Client.enabled == False) - .subquery() - ) - - dbsession.execute( - LoginHistory.__table__.delete( - and_( - ~LoginHistory.client_id.in_(recent_logins), - LoginHistory.client_id.in_(deletable_clients), - ) - ) - ) - - dbsession.execute( - Client.__table__.delete( - and_( - Client.creation_date < datetime.utcnow() - timedelta(days=3), - Client.enabled == False, - ~Client.id.in_(recent_logins), - ) - ) - ) diff --git a/development.ini b/development.ini deleted file mode 100644 index 234382c5..00000000 --- a/development.ini +++ /dev/null @@ -1,51 +0,0 @@ -[app:main] -use = egg:brewman - -pyramid.reload_templates = false -pyramid.debug_authorization = false -pyramid.debug_notfound = false -pyramid.debug_routematch = false -pyramid.default_locale_name = en -# sqlalchemy.url = postgresql://hops:123456@localhost:7654/hops -sqlalchemy.url = postgresql://postgres:123456@localhost:5432/hops - -[server:main] -use = egg:waitress#main -host = 0.0.0.0 -port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, brewman, sqlalchemy.engine.base - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARNING -handlers = console - -[logger_brewman] -level = WARNING -handlers = -qualname = brewman - -[logger_sqlalchemy.engine.base] -level = DEBUG -handlers = -qualname = sqlalchemy.engine.base - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/production.ini b/production.ini deleted file mode 100644 index 173e9f12..00000000 --- a/production.ini +++ /dev/null @@ -1,44 +0,0 @@ -[app:main] -use = egg:brewman - -pyramid.reload_templates = false -pyramid.debug_authorization = false -pyramid.debug_notfound = false -pyramid.debug_routematch = false -pyramid.default_locale_name = en - -[server:main] -use = egg:waitress#main -host = 0.0.0.0 -port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, brewman - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console - -[logger_brewman] -level = WARN -handlers = -qualname = brewman - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/requirements.txt b/requirements.txt index 7ef9947b..e6b62afb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ -pyramid -waitress -transaction -zope.sqlalchemy -SQLAlchemy -psycopg2-binary - +uvicorn +fastapi +environs +python-jose[cryptography] +passlib[bcrypt] +psycopg2 +python-multipart +pyjwt \ No newline at end of file diff --git a/setup.py b/setup.py index e8b025c3..c9409248 100644 --- a/setup.py +++ b/setup.py @@ -3,20 +3,12 @@ import os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(here, 'README.txt')) as f: - README = f.read() -with open(os.path.join(here, 'CHANGES.txt')) as f: - CHANGES = f.read() - -requires = [ - 'pyramid', - 'waitress', - 'pyramid_tm', - 'transaction', - 'zope.sqlalchemy', - 'SQLAlchemy', - 'psycopg2-binary', -] +with open(os.path.join(here, 'README.txt'), "r") as r: + README = r.read() +with open(os.path.join(here, 'CHANGES.txt'), "r") as c: + CHANGES = c.read() +with open(os.path.join(here, 'requirements.txt'), "r") as r: + requires = r.read().splitlines() setup(name='brewman', version='6.0',