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,
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():

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"]
def set_date(session, date_):
def set_date(date_, session):
session["date"] = date_
return session["date"]

View File

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

View File

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

View File

@ -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()
]

View File

@ -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):

View File

@ -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_:

View File

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

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 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):

View File

@ -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'}

View File

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

View File

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

View File

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

View File

@ -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<AttendanceItem>) {
Object.assign(this, init);

View File

@ -46,7 +46,7 @@ export class UnpostedDataSource extends DataSource<Unposted> {
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':