diff --git a/alembic/env.py b/alembic/env.py index 7d4a5ce4..81608ff8 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -50,6 +50,7 @@ def run_migrations_offline(): target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"}, + compare_type=True ) with context.begin_transaction(): @@ -73,7 +74,8 @@ def run_migrations_online(): with connectable.connect() as connection: context.configure( - connection=connection, target_metadata=target_metadata + connection=connection, target_metadata=target_metadata, + compare_type=True ) with context.begin_transaction(): diff --git a/alembic/versions/03ea3e9cb1e5_rename_further.py b/alembic/versions/03ea3e9cb1e5_rename_further.py new file mode 100644 index 00000000..6a972c1d --- /dev/null +++ b/alembic/versions/03ea3e9cb1e5_rename_further.py @@ -0,0 +1,54 @@ +"""test + +Revision ID: 03ea3e9cb1e5 +Revises: 5498fc4bf58d +Create Date: 2020-05-14 21:25:08.945280 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '03ea3e9cb1e5' +down_revision = '5498fc4bf58d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("attendances") as batch_op: + batch_op.alter_column('date', type_=sa.Date(), nullable=False) + with op.batch_alter_table("employees") as batch_op: + batch_op.alter_column('Designation', new_column_name='designation', nullable=False) + batch_op.alter_column('Salary', new_column_name='salary', nullable=False) + batch_op.alter_column('ServicePoints', new_column_name='points', nullable=False) + batch_op.alter_column('JoiningDate', new_column_name='joining_date', type_=sa.Date(), nullable=False) + batch_op.alter_column('LeavingDate', new_column_name='leaving_date', type_=sa.Date(), nullable=True) + with op.batch_alter_table("settings") as batch_op: + batch_op.alter_column('SettingID', new_column_name='id') + batch_op.alter_column('Name', new_column_name='name') + batch_op.alter_column('Data', new_column_name='data') + op.create_unique_constraint(op.f('uq_settings_name'), 'settings', ['name']) + op.drop_constraint('uq_settings_Name', 'settings', type_='unique') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("attendances") as batch_op: + batch_op.alter_column('date', type_=sa.DateTime()) + with op.batch_alter_table("employees") as batch_op: + batch_op.alter_column('designation', new_column_name='Designation', nullable=True) + batch_op.alter_column('salary', new_column_name='Salary', nullable=True) + batch_op.alter_column('points', new_column_name='ServicePoints', nullable=True) + batch_op.alter_column('joining_date', new_column_name='JoiningDate', type_=sa.DateTime(), nullable=True) + batch_op.alter_column('leaving_date', new_column_name='LeavingDate', type_=sa.DateTime(), nullable=True) + with op.batch_alter_table("settings") as batch_op: + batch_op.alter_column('id', new_column_name='SettingID') + batch_op.alter_column('name', new_column_name='Name') + batch_op.alter_column('data', new_column_name='Data') + op.create_unique_constraint('uq_settings_Name', 'settings', ['name']) + op.drop_constraint(op.f('uq_settings_name'), 'settings', type_='unique') + # ### end Alembic commands ### diff --git a/brewman/core/session.py b/brewman/core/session.py index f6c91dd9..201987f3 100644 --- a/brewman/core/session.py +++ b/brewman/core/session.py @@ -7,7 +7,7 @@ def get_date(session) -> str: return session["date"] -def set_date(session, date_): +def set_date(date_, session): session["date"] = date_ return session["date"] diff --git a/brewman/models/master.py b/brewman/models/master.py index 399f6716..d1fc1baa 100644 --- a/brewman/models/master.py +++ b/brewman/models/master.py @@ -392,11 +392,11 @@ class Employee(AccountBase): __mapper_args__ = {"polymorphic_identity": "employees"} id = Column("id", GUID(), ForeignKey(AccountBase.id), primary_key=True) - designation = Column("Designation", Unicode(255)) - salary = Column("Salary", Integer) - points = Column("ServicePoints", Numeric(precision=5, scale=2)) - joining_date = Column("JoiningDate", DateTime) - leaving_date = Column("LeavingDate", DateTime) + designation = Column("designation", Unicode(255), nullable=False) + salary = Column("salary", Integer, nullable=False) + points = Column("points", Numeric(precision=5, scale=2), nullable=False) + joining_date = Column("joining_date", Date, nullable=False) + leaving_date = Column("leaving_date", Date, nullable=True) attendances = relationship( "Attendance", backref="employee", cascade=None, cascade_backrefs=False @@ -562,9 +562,9 @@ class AccountType: class DbSetting(Base): __tablename__ = "settings" - id = Column("SettingID", GUID(), primary_key=True, default=uuid.uuid4) - name = Column("Name", Unicode(255), unique=True, nullable=False) - data = Column("Data", PickleType) + id = Column("id", GUID(), primary_key=True, default=uuid.uuid4) + name = Column("name", Unicode(255), unique=True, nullable=False) + data = Column("data", PickleType) def __init__(self, id=None, name=None, data=None): self.id = id diff --git a/brewman/models/voucher.py b/brewman/models/voucher.py index 0666ed7f..562662c4 100644 --- a/brewman/models/voucher.py +++ b/brewman/models/voucher.py @@ -9,7 +9,7 @@ from sqlalchemy import ( DateTime, Numeric, ForeignKey, - UniqueConstraint, + UniqueConstraint, Date, ) from sqlalchemy.dialects.postgresql import BYTEA from sqlalchemy.ext.hybrid import hybrid_property @@ -378,7 +378,7 @@ class Attendance(Base): id = Column("id", GUID(), primary_key=True, default=uuid.uuid4) employee_id = Column("employee_id", GUID(), ForeignKey("employees.id")) - date = Column("date", DateTime) + date = Column("date", Date, nullable=False) attendance_type = Column("attendance_type", Integer) amount = Column("amount", Numeric) creation_date = Column("creation_date", DateTime(timezone=True)) diff --git a/brewman/routers/account_types.py b/brewman/routers/account_types.py index 24d69a9f..a065e823 100644 --- a/brewman/routers/account_types.py +++ b/brewman/routers/account_types.py @@ -1,15 +1,17 @@ +from typing import List from fastapi import APIRouter, Depends from ..schemas.auth import UserToken from ..core.security import get_current_active_user as get_user +import brewman.schemas.master as schemas from brewman.models.master import AccountType router = APIRouter() -@router.get("") +@router.get("", response_model=List[schemas.AccountType]) def account_type_list(user: UserToken = Depends(get_user)): return [ - {"id": item.id, "name": item.name} + schemas.AccountType(id=item.id, name=item.name) for item in AccountType.list() ] diff --git a/brewman/routers/attendance.py b/brewman/routers/attendance.py index 7d4aa4cd..45276127 100644 --- a/brewman/routers/attendance.py +++ b/brewman/routers/attendance.py @@ -1,8 +1,9 @@ -import uuid -from datetime import datetime, date, timedelta +import traceback +from datetime import datetime, date, timedelta, time from fastapi import APIRouter, HTTPException, status, Depends, Security, Request from sqlalchemy import or_ +from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..schemas.auth import UserToken from ..core.security import get_current_active_user as get_user @@ -10,7 +11,7 @@ from ..db.session import SessionLocal from ..models.master import Employee from ..models.voucher import Attendance from ..routers.fingerprint import get_prints -from ..core.session import get_date +from ..core.session import get_date, set_date import brewman.schemas.voucher as schemas router = APIRouter() @@ -26,26 +27,37 @@ def get_db() -> Session: @router.get("") -def attendance_blank(request: Request, user: UserToken = Security(get_user, scopes=["attendance"])): +def attendance_blank( + request: Request, user: UserToken = Security(get_user, scopes=["attendance"]) +): return {"date": get_date(request.session), "body": []} @router.get("/{date_}") def attendance_date( date_: str, + request: Request, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["attendance"]), ): - return attendance_date_report(date_, db) + set_date(date_, request.session) + return { + "date": date_, + "body": attendance_date_report(datetime.strptime(date_, "%d-%b-%Y"), db), + } -def attendance_date_report(date_: str, db): - report = {"date": date_, "body": []} - date_ = datetime.strptime(date_, "%d-%b-%Y").date() +def attendance_date_report(date_: date, db: Session): + body = [] employees = ( db.query(Employee) .filter(Employee.joining_date <= date_) - .filter(or_(Employee.is_active, Employee.leaving_date >= date_)) + .filter( + or_( + Employee.is_active, + Employee.leaving_date >= date_, + ) + ) .order_by(Employee.cost_centre_id) .order_by(Employee.designation) .order_by(Employee.name) @@ -55,50 +67,60 @@ def attendance_date_report(date_: str, db): att = ( db.query(Attendance) .filter(Attendance.employee_id == item.id) - .filter(Attendance.date == date) + .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, db) - 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, - } + prints, hours_worked, full_day = get_prints(item.id, date_, db) + body.append( + schemas.AttendanceItem( + id=item.id, + code=item.code, + name=item.name, + designation=item.designation, + department=item.cost_centre.name, + attendanceType=schemas.AttendanceType(id=att), + prints=prints, + hoursWorked=hours_worked, + fullDay=full_day, + ) ) - return report + return body -@router.post("/{date_}") # "Attendance" +@router.post("/{date_}") def save( date_: str, data: schemas.AttendanceIn, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["attendance"]), ): - date_object = datetime.strptime(date_, "%d-%b-%Y").date() - - for item in data.body: - attendance_type = item.attendance_type.id_ - if attendance_type != 0: - attendance = Attendance( - employee_id=item.employee_id, - date=date_object, - attendance_type=attendance_type, - user_id=user.id_, - ) - attendance.create(db) - db.commit() - return attendance_date_report(date_, db) + try: + att_date = datetime.strptime(date_, "%d-%b-%Y").date() + for item in data.body: + if item.attendance_type.id_ != 0: + attendance = Attendance( + employee_id=item.id_, + date=att_date, + attendance_type=item.attendance_type.id_, + user_id=user.id_, + ) + attendance.create(db) + db.commit() + return {"date": date_, "body": attendance_date_report(att_date, db)} + 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(), + ) def date_range(start: date, stop: date, step=timedelta(days=1), inclusive=False): diff --git a/brewman/routers/attendance_types.py b/brewman/routers/attendance_types.py index 183d74bf..d55adfc8 100644 --- a/brewman/routers/attendance_types.py +++ b/brewman/routers/attendance_types.py @@ -1,12 +1,13 @@ -from brewman.models.master import AttendanceType, Employee - -from fastapi import APIRouter +from fastapi import APIRouter, Depends +from ..schemas.auth import UserToken +from ..core.security import get_current_active_user as get_user +from brewman.models.master import AttendanceType router = APIRouter() -@router.get("/") # "Authenticated" -def show_list(request): +@router.get("") +async def show_list(user: UserToken = Depends(get_user)): list_ = AttendanceType.list() attendance_types = [] for item in list_: diff --git a/brewman/routers/fingerprint.py b/brewman/routers/fingerprint.py index e22a557d..f9c4496d 100644 --- a/brewman/routers/fingerprint.py +++ b/brewman/routers/fingerprint.py @@ -1,11 +1,12 @@ import csv -import datetime +from datetime import datetime, date, timedelta, time import uuid from io import StringIO from sqlalchemy import bindparam, select, exists, and_ from sqlalchemy.dialects.postgresql import insert as pg_insert # from zope.sqlalchemy import mark_changed +from sqlalchemy.orm import Session from brewman.models.master import Employee from brewman.models.voucher import Fingerprint @@ -117,14 +118,12 @@ def fp(file_data, employees): # return Fingerprint.__table__.insert().from_select([Fingerprint.id, Fingerprint.employee_id, Fingerprint.date], sel) -def get_prints(employee_id, date, dbsession): - start_fp = date + datetime.timedelta(hours=7) - finish_fp = date + datetime.timedelta(hours=7, days=1) +def get_prints(employee_id: uuid.UUID, date_: date, db: Session): prints = ( - dbsession.query(Fingerprint) + db.query(Fingerprint) .filter(Fingerprint.employee_id == employee_id) - .filter(Fingerprint.date >= start_fp) - .filter(Fingerprint.date < finish_fp) + .filter(Fingerprint.date >= datetime.combine(date_, time(hour=7))) + .filter(Fingerprint.date < datetime.combine(date_ + timedelta(days=1), time(hour=7))) .order_by(Fingerprint.date) .all() ) @@ -132,31 +131,31 @@ def get_prints(employee_id, date, dbsession): last = None for i in range(len(prints), 0, -1): item = prints[i - 1].date - if last is not None and last - item < datetime.timedelta(minutes=10): + if last is not None and last - item < timedelta(minutes=10): prints.remove(prints[i - 1]) else: last = item if len(prints) == 0: - hours = "", "" + hours_worked, full_day = "", None elif len(prints) == 2: - hours = prints[1].date - prints[0].date - hours = working_hours(hours) + time_worked = prints[1].date - prints[0].date + hours_worked, full_day = working_hours(time_worked) elif len(prints) == 4: - hours = (prints[1].date - prints[0].date) + (prints[3].date - prints[2].date) - hours = working_hours(hours) + time_worked = (prints[1].date - prints[0].date) + (prints[3].date - prints[2].date) + hours_worked, full_day = working_hours(time_worked) else: - hours = "Error", "Error" + hours_worked, full_day = "Error", False return ( ", ".join([x.date.strftime("%H:%M") for x in prints]) + " ", - hours[0], - hours[1], + hours_worked, + full_day, ) -def working_hours(delta): +def working_hours(delta: timedelta): minutes = (delta.seconds // 60) % 60 minutes = int(5 * round(float(minutes) / 5)) hours = delta.seconds // 3600 - worked = str(hours).zfill(2) + ":" + str(minutes).zfill(2) - return worked, delta.seconds >= 60 * 60 * 9 # 9hrs + hours_worked = str(hours).zfill(2) + ":" + str(minutes).zfill(2) + return hours_worked, delta.seconds >= 60 * 60 * 9 # 9hrs diff --git a/brewman/schemas/__init__.py b/brewman/schemas/__init__.py index e69de29b..8bb04775 100644 --- a/brewman/schemas/__init__.py +++ b/brewman/schemas/__init__.py @@ -0,0 +1,5 @@ +def to_camel(string: str) -> str: + first, *others = string.split("_") + return "".join([first] + [word.capitalize() for word in others]) + + diff --git a/brewman/schemas/auth.py b/brewman/schemas/auth.py index f7b2030f..7f08c165 100644 --- a/brewman/schemas/auth.py +++ b/brewman/schemas/auth.py @@ -3,10 +3,7 @@ from typing import List, Optional from datetime import datetime from pydantic import BaseModel - -def to_camel(string: str) -> str: - first, *others = string.split("_") - return "".join([first] + [word.capitalize() for word in others]) +from brewman.schemas import to_camel class ClientIn(BaseModel): diff --git a/brewman/schemas/master.py b/brewman/schemas/master.py index 2c72f9b2..31d72094 100644 --- a/brewman/schemas/master.py +++ b/brewman/schemas/master.py @@ -5,10 +5,7 @@ from decimal import Decimal from pydantic import BaseModel, Field, validator - -def to_camel(string: str) -> str: - first, *others = string.split('_') - return ''.join([first] + [word.capitalize() for word in others]) +from brewman.schemas import to_camel class AccountLink(BaseModel): @@ -55,7 +52,6 @@ class ProductIn(BaseModel): fraction_units: str product_yield: Decimal = Field(ge=0, le=1, multiple_of=0.00001, default=1) product_group: ProductGroupLink = Field(...) - account_id: AccountLink = Field(...) price: Decimal = Field(ge=0, multiple_of=0.01, default=0) sale_price: Decimal = Field(ge=0, multiple_of=0.01, default=0) is_active: bool @@ -71,6 +67,7 @@ class ProductIn(BaseModel): class Product(ProductIn): id_: uuid.UUID code: int + account: AccountLink = Field(...) is_fixture: bool @@ -185,3 +182,13 @@ class DbSetting(BaseModel): id_: uuid.UUID name: str data: bytes + + +class AccountType(BaseModel): + id_: int + name: str + + class Config: + fields = {'id_': 'id'} + + diff --git a/brewman/schemas/reports.py b/brewman/schemas/reports.py index cfb06f1d..d94ca01f 100644 --- a/brewman/schemas/reports.py +++ b/brewman/schemas/reports.py @@ -4,14 +4,10 @@ from typing import List, Optional from datetime import datetime, date from pydantic import BaseModel, Field, validator +from brewman.schemas import to_camel from brewman.schemas.master import AccountLink, ProductLink -def to_camel(string: str) -> str: - first, *others = string.split("_") - return "".join([first] + [word.capitalize() for word in others]) - - class LedgerItem(BaseModel): id_: Optional[uuid.UUID] date_: date diff --git a/brewman/schemas/voucher.py b/brewman/schemas/voucher.py index eaba7f98..77e63ebf 100644 --- a/brewman/schemas/voucher.py +++ b/brewman/schemas/voucher.py @@ -1,8 +1,10 @@ import uuid from datetime import datetime, date from decimal import Decimal -from typing import List -from pydantic import BaseModel +from typing import List, Optional +from pydantic import BaseModel, validator + +from brewman.schemas import to_camel class Voucher(BaseModel): @@ -72,20 +74,26 @@ class Batch(BaseModel): class AttendanceType(BaseModel): id_: int - name: str - value: Decimal + name: Optional[str] + value: Optional[Decimal] + + class Config: + alias_generator = to_camel class AttendanceItem(BaseModel): - id: uuid.UUID - employee_id: uuid.UUID - date: date + id_: uuid.UUID + code: int + name: str + designation: str + department: str attendance_type: AttendanceType - amount: Decimal - creation_date: datetime - user_id: uuid.UUID - is_valid: bool + prints: str + hours_worked: str + full_day: Optional[bool] + class Config: + alias_generator = to_camel class AttendanceIn(BaseModel): body: List[AttendanceItem] diff --git a/overlord/src/app/attendance/attendance.component.html b/overlord/src/app/attendance/attendance.component.html index e8c249d7..f040c503 100644 --- a/overlord/src/app/attendance/attendance.component.html +++ b/overlord/src/app/attendance/attendance.component.html @@ -58,9 +58,9 @@ {{row.prints}} new_releases - - {{row.hours}} + + {{row.hoursWorked}} diff --git a/overlord/src/app/attendance/attendance.ts b/overlord/src/app/attendance/attendance.ts index 9b71ac45..3fb57b7e 100644 --- a/overlord/src/app/attendance/attendance.ts +++ b/overlord/src/app/attendance/attendance.ts @@ -17,8 +17,8 @@ export class AttendanceItem { department: string; attendanceType: AttendanceType; prints: string; - hours: string; - worked: string; + hoursWorked: string; + fullDay?: boolean; public constructor(init?: Partial) { Object.assign(this, init); diff --git a/overlord/src/app/unposted/unposted-datasource.ts b/overlord/src/app/unposted/unposted-datasource.ts index b89e105f..1afa893d 100644 --- a/overlord/src/app/unposted/unposted-datasource.ts +++ b/overlord/src/app/unposted/unposted-datasource.ts @@ -46,7 +46,7 @@ export class UnpostedDataSource extends DataSource { case 'date': return compare(a.date, b.date, isAsc); case 'type': - return compare(a.voucherType, b.voucherType, isAsc); + return compare(a.type, b.type, isAsc); case 'debitAmount': return compare(+a.debitAmount, +b.debitAmount, isAsc); case 'creditAmount':