Attendance Done!!

Changed the datatype of dates in attendance and employee to date from datetime
this might bork things in other places
This commit is contained in:
tanshu 2020-05-14 21:49:22 +05:30
parent 0a79b1acbb
commit bd05e6bb17
17 changed files with 201 additions and 108 deletions

View File

@ -50,6 +50,7 @@ def run_migrations_offline():
target_metadata=target_metadata, target_metadata=target_metadata,
literal_binds=True, literal_binds=True,
dialect_opts={"paramstyle": "named"}, dialect_opts={"paramstyle": "named"},
compare_type=True
) )
with context.begin_transaction(): with context.begin_transaction():
@ -73,7 +74,8 @@ def run_migrations_online():
with connectable.connect() as connection: with connectable.connect() as connection:
context.configure( context.configure(
connection=connection, target_metadata=target_metadata connection=connection, target_metadata=target_metadata,
compare_type=True
) )
with context.begin_transaction(): with context.begin_transaction():

View File

@ -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 ###

View File

@ -7,7 +7,7 @@ def get_date(session) -> str:
return session["date"] return session["date"]
def set_date(session, date_): def set_date(date_, session):
session["date"] = date_ session["date"] = date_
return session["date"] return session["date"]

View File

@ -392,11 +392,11 @@ class Employee(AccountBase):
__mapper_args__ = {"polymorphic_identity": "employees"} __mapper_args__ = {"polymorphic_identity": "employees"}
id = Column("id", GUID(), ForeignKey(AccountBase.id), primary_key=True) id = Column("id", GUID(), ForeignKey(AccountBase.id), primary_key=True)
designation = Column("Designation", Unicode(255)) designation = Column("designation", Unicode(255), nullable=False)
salary = Column("Salary", Integer) salary = Column("salary", Integer, nullable=False)
points = Column("ServicePoints", Numeric(precision=5, scale=2)) points = Column("points", Numeric(precision=5, scale=2), nullable=False)
joining_date = Column("JoiningDate", DateTime) joining_date = Column("joining_date", Date, nullable=False)
leaving_date = Column("LeavingDate", DateTime) leaving_date = Column("leaving_date", Date, nullable=True)
attendances = relationship( attendances = relationship(
"Attendance", backref="employee", cascade=None, cascade_backrefs=False "Attendance", backref="employee", cascade=None, cascade_backrefs=False
@ -562,9 +562,9 @@ class AccountType:
class DbSetting(Base): class DbSetting(Base):
__tablename__ = "settings" __tablename__ = "settings"
id = Column("SettingID", GUID(), primary_key=True, default=uuid.uuid4) id = Column("id", GUID(), primary_key=True, default=uuid.uuid4)
name = Column("Name", Unicode(255), unique=True, nullable=False) name = Column("name", Unicode(255), unique=True, nullable=False)
data = Column("Data", PickleType) data = Column("data", PickleType)
def __init__(self, id=None, name=None, data=None): def __init__(self, id=None, name=None, data=None):
self.id = id self.id = id

View File

@ -9,7 +9,7 @@ from sqlalchemy import (
DateTime, DateTime,
Numeric, Numeric,
ForeignKey, ForeignKey,
UniqueConstraint, UniqueConstraint, Date,
) )
from sqlalchemy.dialects.postgresql import BYTEA from sqlalchemy.dialects.postgresql import BYTEA
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
@ -378,7 +378,7 @@ class Attendance(Base):
id = Column("id", GUID(), primary_key=True, default=uuid.uuid4) id = Column("id", GUID(), primary_key=True, default=uuid.uuid4)
employee_id = Column("employee_id", GUID(), ForeignKey("employees.id")) 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) attendance_type = Column("attendance_type", Integer)
amount = Column("amount", Numeric) amount = Column("amount", Numeric)
creation_date = Column("creation_date", DateTime(timezone=True)) creation_date = Column("creation_date", DateTime(timezone=True))

View File

@ -1,15 +1,17 @@
from typing import List
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from ..schemas.auth import UserToken from ..schemas.auth import UserToken
from ..core.security import get_current_active_user as get_user from ..core.security import get_current_active_user as get_user
import brewman.schemas.master as schemas
from brewman.models.master import AccountType from brewman.models.master import AccountType
router = APIRouter() router = APIRouter()
@router.get("") @router.get("", response_model=List[schemas.AccountType])
def account_type_list(user: UserToken = Depends(get_user)): def account_type_list(user: UserToken = Depends(get_user)):
return [ return [
{"id": item.id, "name": item.name} schemas.AccountType(id=item.id, name=item.name)
for item in AccountType.list() for item in AccountType.list()
] ]

View File

@ -1,8 +1,9 @@
import uuid import traceback
from datetime import datetime, date, timedelta from datetime import datetime, date, timedelta, time
from fastapi import APIRouter, HTTPException, status, Depends, Security, Request from fastapi import APIRouter, HTTPException, status, Depends, Security, Request
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from ..schemas.auth import UserToken from ..schemas.auth import UserToken
from ..core.security import get_current_active_user as get_user 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.master import Employee
from ..models.voucher import Attendance from ..models.voucher import Attendance
from ..routers.fingerprint import get_prints 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 import brewman.schemas.voucher as schemas
router = APIRouter() router = APIRouter()
@ -26,26 +27,37 @@ def get_db() -> Session:
@router.get("") @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": []} return {"date": get_date(request.session), "body": []}
@router.get("/{date_}") @router.get("/{date_}")
def attendance_date( def attendance_date(
date_: str, date_: str,
request: Request,
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["attendance"]), 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): def attendance_date_report(date_: date, db: Session):
report = {"date": date_, "body": []} body = []
date_ = datetime.strptime(date_, "%d-%b-%Y").date()
employees = ( employees = (
db.query(Employee) db.query(Employee)
.filter(Employee.joining_date <= date_) .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.cost_centre_id)
.order_by(Employee.designation) .order_by(Employee.designation)
.order_by(Employee.name) .order_by(Employee.name)
@ -55,50 +67,60 @@ def attendance_date_report(date_: str, db):
att = ( att = (
db.query(Attendance) db.query(Attendance)
.filter(Attendance.employee_id == item.id) .filter(Attendance.employee_id == item.id)
.filter(Attendance.date == date) .filter(Attendance.date == date_)
.filter(Attendance.is_valid == True) .filter(Attendance.is_valid == True)
.first() .first()
) )
att = 0 if att is None else att.attendance_type att = 0 if att is None else att.attendance_type
prints, hours, worked = get_prints(item.id, date, db) prints, hours_worked, full_day = get_prints(item.id, date_, db)
report["body"].append( body.append(
{ schemas.AttendanceItem(
"id": item.id, id=item.id,
"code": item.code, code=item.code,
"name": item.name, name=item.name,
"designation": item.designation, designation=item.designation,
"department": item.cost_centre.name, department=item.cost_centre.name,
"attendanceType": {"id": att}, attendanceType=schemas.AttendanceType(id=att),
"prints": prints, prints=prints,
"hours": hours, hoursWorked=hours_worked,
"worked": worked, fullDay=full_day,
} )
) )
return report return body
@router.post("/{date_}") # "Attendance" @router.post("/{date_}")
def save( def save(
date_: str, date_: str,
data: schemas.AttendanceIn, data: schemas.AttendanceIn,
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["attendance"]), user: UserToken = Security(get_user, scopes=["attendance"]),
): ):
date_object = datetime.strptime(date_, "%d-%b-%Y").date() try:
att_date = datetime.strptime(date_, "%d-%b-%Y").date()
for item in data.body: for item in data.body:
attendance_type = item.attendance_type.id_ if item.attendance_type.id_ != 0:
if attendance_type != 0: attendance = Attendance(
attendance = Attendance( employee_id=item.id_,
employee_id=item.employee_id, date=att_date,
date=date_object, attendance_type=item.attendance_type.id_,
attendance_type=attendance_type, user_id=user.id_,
user_id=user.id_, )
) attendance.create(db)
attendance.create(db) db.commit()
db.commit() return {"date": date_, "body": attendance_date_report(att_date, db)}
return attendance_date_report(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): def date_range(start: date, stop: date, step=timedelta(days=1), inclusive=False):

View File

@ -1,12 +1,13 @@
from brewman.models.master import AttendanceType, Employee from fastapi import APIRouter, Depends
from ..schemas.auth import UserToken
from fastapi import APIRouter from ..core.security import get_current_active_user as get_user
from brewman.models.master import AttendanceType
router = APIRouter() router = APIRouter()
@router.get("/") # "Authenticated" @router.get("")
def show_list(request): async def show_list(user: UserToken = Depends(get_user)):
list_ = AttendanceType.list() list_ = AttendanceType.list()
attendance_types = [] attendance_types = []
for item in list_: for item in list_:

View File

@ -1,11 +1,12 @@
import csv import csv
import datetime from datetime import datetime, date, timedelta, time
import uuid import uuid
from io import StringIO from io import StringIO
from sqlalchemy import bindparam, select, exists, and_ from sqlalchemy import bindparam, select, exists, and_
from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlalchemy.dialects.postgresql import insert as pg_insert
# from zope.sqlalchemy import mark_changed # from zope.sqlalchemy import mark_changed
from sqlalchemy.orm import Session
from brewman.models.master import Employee from brewman.models.master import Employee
from brewman.models.voucher import Fingerprint 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) # return Fingerprint.__table__.insert().from_select([Fingerprint.id, Fingerprint.employee_id, Fingerprint.date], sel)
def get_prints(employee_id, date, dbsession): def get_prints(employee_id: uuid.UUID, date_: date, db: Session):
start_fp = date + datetime.timedelta(hours=7)
finish_fp = date + datetime.timedelta(hours=7, days=1)
prints = ( prints = (
dbsession.query(Fingerprint) db.query(Fingerprint)
.filter(Fingerprint.employee_id == employee_id) .filter(Fingerprint.employee_id == employee_id)
.filter(Fingerprint.date >= start_fp) .filter(Fingerprint.date >= datetime.combine(date_, time(hour=7)))
.filter(Fingerprint.date < finish_fp) .filter(Fingerprint.date < datetime.combine(date_ + timedelta(days=1), time(hour=7)))
.order_by(Fingerprint.date) .order_by(Fingerprint.date)
.all() .all()
) )
@ -132,31 +131,31 @@ def get_prints(employee_id, date, dbsession):
last = None last = None
for i in range(len(prints), 0, -1): for i in range(len(prints), 0, -1):
item = prints[i - 1].date 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]) prints.remove(prints[i - 1])
else: else:
last = item last = item
if len(prints) == 0: if len(prints) == 0:
hours = "", "" hours_worked, full_day = "", None
elif len(prints) == 2: elif len(prints) == 2:
hours = prints[1].date - prints[0].date time_worked = prints[1].date - prints[0].date
hours = working_hours(hours) hours_worked, full_day = working_hours(time_worked)
elif len(prints) == 4: elif len(prints) == 4:
hours = (prints[1].date - prints[0].date) + (prints[3].date - prints[2].date) time_worked = (prints[1].date - prints[0].date) + (prints[3].date - prints[2].date)
hours = working_hours(hours) hours_worked, full_day = working_hours(time_worked)
else: else:
hours = "Error", "Error" hours_worked, full_day = "Error", False
return ( return (
", ".join([x.date.strftime("%H:%M") for x in prints]) + " ", ", ".join([x.date.strftime("%H:%M") for x in prints]) + " ",
hours[0], hours_worked,
hours[1], full_day,
) )
def working_hours(delta): def working_hours(delta: timedelta):
minutes = (delta.seconds // 60) % 60 minutes = (delta.seconds // 60) % 60
minutes = int(5 * round(float(minutes) / 5)) minutes = int(5 * round(float(minutes) / 5))
hours = delta.seconds // 3600 hours = delta.seconds // 3600
worked = str(hours).zfill(2) + ":" + str(minutes).zfill(2) hours_worked = str(hours).zfill(2) + ":" + str(minutes).zfill(2)
return worked, delta.seconds >= 60 * 60 * 9 # 9hrs return hours_worked, delta.seconds >= 60 * 60 * 9 # 9hrs

View File

@ -0,0 +1,5 @@
def to_camel(string: str) -> str:
first, *others = string.split("_")
return "".join([first] + [word.capitalize() for word in others])

View File

@ -3,10 +3,7 @@ from typing import List, Optional
from datetime import datetime from datetime import datetime
from pydantic import BaseModel from pydantic import BaseModel
from brewman.schemas import to_camel
def to_camel(string: str) -> str:
first, *others = string.split("_")
return "".join([first] + [word.capitalize() for word in others])
class ClientIn(BaseModel): class ClientIn(BaseModel):

View File

@ -5,10 +5,7 @@ from decimal import Decimal
from pydantic import BaseModel, Field, validator from pydantic import BaseModel, Field, validator
from brewman.schemas import to_camel
def to_camel(string: str) -> str:
first, *others = string.split('_')
return ''.join([first] + [word.capitalize() for word in others])
class AccountLink(BaseModel): class AccountLink(BaseModel):
@ -55,7 +52,6 @@ class ProductIn(BaseModel):
fraction_units: str fraction_units: str
product_yield: Decimal = Field(ge=0, le=1, multiple_of=0.00001, default=1) product_yield: Decimal = Field(ge=0, le=1, multiple_of=0.00001, default=1)
product_group: ProductGroupLink = Field(...) product_group: ProductGroupLink = Field(...)
account_id: AccountLink = Field(...)
price: Decimal = Field(ge=0, multiple_of=0.01, default=0) price: Decimal = Field(ge=0, multiple_of=0.01, default=0)
sale_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 is_active: bool
@ -71,6 +67,7 @@ class ProductIn(BaseModel):
class Product(ProductIn): class Product(ProductIn):
id_: uuid.UUID id_: uuid.UUID
code: int code: int
account: AccountLink = Field(...)
is_fixture: bool is_fixture: bool
@ -185,3 +182,13 @@ class DbSetting(BaseModel):
id_: uuid.UUID id_: uuid.UUID
name: str name: str
data: bytes data: bytes
class AccountType(BaseModel):
id_: int
name: str
class Config:
fields = {'id_': 'id'}

View File

@ -4,14 +4,10 @@ from typing import List, Optional
from datetime import datetime, date from datetime import datetime, date
from pydantic import BaseModel, Field, validator from pydantic import BaseModel, Field, validator
from brewman.schemas import to_camel
from brewman.schemas.master import AccountLink, ProductLink 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): class LedgerItem(BaseModel):
id_: Optional[uuid.UUID] id_: Optional[uuid.UUID]
date_: date date_: date

View File

@ -1,8 +1,10 @@
import uuid import uuid
from datetime import datetime, date from datetime import datetime, date
from decimal import Decimal from decimal import Decimal
from typing import List from typing import List, Optional
from pydantic import BaseModel from pydantic import BaseModel, validator
from brewman.schemas import to_camel
class Voucher(BaseModel): class Voucher(BaseModel):
@ -72,20 +74,26 @@ class Batch(BaseModel):
class AttendanceType(BaseModel): class AttendanceType(BaseModel):
id_: int id_: int
name: str name: Optional[str]
value: Decimal value: Optional[Decimal]
class Config:
alias_generator = to_camel
class AttendanceItem(BaseModel): class AttendanceItem(BaseModel):
id: uuid.UUID id_: uuid.UUID
employee_id: uuid.UUID code: int
date: date name: str
designation: str
department: str
attendance_type: AttendanceType attendance_type: AttendanceType
amount: Decimal prints: str
creation_date: datetime hours_worked: str
user_id: uuid.UUID full_day: Optional[bool]
is_valid: bool
class Config:
alias_generator = to_camel
class AttendanceIn(BaseModel): class AttendanceIn(BaseModel):
body: List[AttendanceItem] body: List[AttendanceItem]

View File

@ -58,9 +58,9 @@
{{row.prints}} {{row.prints}}
<mat-icon *ngIf="!form.controls.attendances.controls[i].pristine">new_releases</mat-icon> <mat-icon *ngIf="!form.controls.attendances.controls[i].pristine">new_releases</mat-icon>
<mat-chip-list class="no-bg"> <mat-chip-list class="no-bg">
<mat-chip *ngIf="row.hours.length" class="no-bg" [selected]="row.worked !== 'Error'" <mat-chip *ngIf="row.hoursWorked.length" class="no-bg" [selected]="true"
[color]="row.worked === true ? 'primary' : 'warn'"> [color]="row.fullDay === true ? 'primary' : 'warn'">
{{row.hours}} {{row.hoursWorked}}
</mat-chip> </mat-chip>
</mat-chip-list> </mat-chip-list>
</mat-cell> </mat-cell>

View File

@ -17,8 +17,8 @@ export class AttendanceItem {
department: string; department: string;
attendanceType: AttendanceType; attendanceType: AttendanceType;
prints: string; prints: string;
hours: string; hoursWorked: string;
worked: string; fullDay?: boolean;
public constructor(init?: Partial<AttendanceItem>) { public constructor(init?: Partial<AttendanceItem>) {
Object.assign(this, init); Object.assign(this, init);

View File

@ -46,7 +46,7 @@ export class UnpostedDataSource extends DataSource<Unposted> {
case 'date': case 'date':
return compare(a.date, b.date, isAsc); return compare(a.date, b.date, isAsc);
case 'type': case 'type':
return compare(a.voucherType, b.voucherType, isAsc); return compare(a.type, b.type, isAsc);
case 'debitAmount': case 'debitAmount':
return compare(+a.debitAmount, +b.debitAmount, isAsc); return compare(+a.debitAmount, +b.debitAmount, isAsc);
case 'creditAmount': case 'creditAmount':