Renamed service points to points consistently

Renamed Schemas to shorten them
Added the name validators for Account Base
Added joining_date/leaving_date validators for employees
Employees should be working now
This commit is contained in:
tanshu 2020-05-10 13:32:08 +05:30
parent 6765f0a93e
commit aae48faf91
8 changed files with 268 additions and 247 deletions

View File

@ -396,7 +396,7 @@ class Employee(AccountBase):
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))
salary = Column("Salary", Integer) salary = Column("Salary", Integer)
service_points = Column("ServicePoints", Numeric(precision=5, scale=2)) points = Column("ServicePoints", Numeric(precision=5, scale=2))
joining_date = Column("JoiningDate", DateTime) joining_date = Column("JoiningDate", DateTime)
leaving_date = Column("LeavingDate", DateTime) leaving_date = Column("LeavingDate", DateTime)
@ -416,13 +416,13 @@ class Employee(AccountBase):
cost_centre_id=None, cost_centre_id=None,
designation=None, designation=None,
salary=None, salary=None,
service_points=None, points=None,
joining_date=None, joining_date=None,
leaving_date=None, leaving_date=None,
): ):
self.designation = designation self.designation = designation
self.salary = salary self.salary = salary
self.service_points = service_points self.points = points
self.joining_date = joining_date self.joining_date = joining_date
self.leaving_date = leaving_date self.leaving_date = leaving_date
super().__init__( super().__init__(

View File

@ -28,7 +28,7 @@ def get_db():
@router.post("/", response_model=schemas.Account) @router.post("/", response_model=schemas.Account)
def save( def save(
data: schemas.AccountSaveUpdate, data: schemas.AccountIn,
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: User = Security(get_user, scopes=["accounts"]), user: User = Security(get_user, scopes=["accounts"]),
): ):
@ -60,7 +60,7 @@ def save(
@router.put("/{id_}", response_model=schemas.Account) @router.put("/{id_}", response_model=schemas.Account)
def update( def update(
id_: uuid.UUID, id_: uuid.UUID,
data: schemas.AccountSaveUpdate, data: schemas.AccountIn,
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: User = Security(get_user, scopes=["accounts"]), user: User = Security(get_user, scopes=["accounts"]),
): ):
@ -123,28 +123,19 @@ def show_blank(
@router.get("/list") @router.get("/list")
async def show_list(db: Session = Depends(get_db), user: User = Depends(get_user)): async def show_list(db: Session = Depends(get_db), user: User = Depends(get_user)):
list_ = ( return [
db.query(Account) {
.order_by(Account.type) "id": item.id,
.order_by(Account.name) "name": item.name,
.order_by(Account.code) "type": item.type_object.name,
.all() "isActive": item.is_active,
) "isReconcilable": item.is_reconcilable,
accounts = [] "isStarred": item.is_starred,
for item in list_: "costCentre": item.cost_centre.name,
accounts.append( "isFixture": item.is_fixture,
{ }
"id": item.id, for item in db.query(Account).order_by(Account.type).order_by(Account.name).order_by(Account.code).all()
"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") @router.get("/query")

View File

@ -2,19 +2,32 @@ import datetime
import uuid import uuid
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.orm import Session
from fastapi import Depends, Security
from brewman.models.master import Employee from ..models.master import Employee
from brewman.models.voucher import Attendance from ..models.voucher import Attendance
from brewman.routers.fingerprint import get_prints from ..routers.fingerprint import get_prints
from brewman.routers.services.session import session_current_date from ..routers.services.session import session_current_date
from ..db.session import SessionLocal
from ..core.security import User, get_current_active_user as get_user
from fastapi import APIRouter from fastapi import APIRouter
router = APIRouter() router = APIRouter()
@router.get("/") # "Attendance" # Dependency
def attendance_blank(request): def get_db():
try:
db = SessionLocal()
yield db
finally:
db.close()
@router.get("/")
def attendance_blank(db: Session = Depends(get_db), user: User = Security(get_user, scopes=["attendance"])):
return {"date": session_current_date(request), "body": []} return {"date": session_current_date(request), "body": []}

View File

@ -42,9 +42,9 @@ def attendance_record(start_date, finish_date, dbsession):
employee.designation, employee.designation,
employee.cost_centre.name, employee.cost_centre.name,
employee.salary, employee.salary,
employee.service_points, employee.points,
] ]
row_value = ["", "", "", "", employee.salary, employee.service_points] row_value = ["", "", "", "", employee.salary, employee.points]
for date in daterange(start_date, finish_date, inclusive=True): for date in daterange(start_date, finish_date, inclusive=True):
att = ( att = (
dbsession.query(Attendance) dbsession.query(Attendance)

View File

@ -23,7 +23,7 @@ def get_db():
@router.post("/", response_model=schemas.CostCentre) @router.post("/", response_model=schemas.CostCentre)
def save( def save(
data: schemas.CostCentreSaveUpdate, data: schemas.CostCentreIn,
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: User = Security(get_user, scopes=["cost-centres"]), user: User = Security(get_user, scopes=["cost-centres"]),
): ):
@ -43,7 +43,7 @@ def save(
@router.put("/{id_}", response_model=schemas.CostCentre) @router.put("/{id_}", response_model=schemas.CostCentre)
def update( def update(
id_: uuid.UUID, id_: uuid.UUID,
data: schemas.CostCentreSaveUpdate, data: schemas.CostCentreIn,
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: User = Security(get_user, scopes=["cost-centres"]), user: User = Security(get_user, scopes=["cost-centres"]),
): ):

View File

@ -1,213 +1,165 @@
import datetime import traceback
import uuid import uuid
from decimal import Decimal
from sqlalchemy import desc from sqlalchemy import desc
from sqlalchemy.orm import joinedload_all from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import joinedload_all, Session
from fastapi import APIRouter, HTTPException, status, Depends, Security
from ..core.security import User, get_current_active_user as get_user
from ..db.session import SessionLocal
from brewman.models.master import CostCentre, Employee, AccountBase, Account from brewman.models.master import CostCentre, Employee, AccountBase, Account
from brewman.models.validation_exception import ValidationError from brewman.models.validation_exception import ValidationError
from brewman.models.voucher import Voucher, Journal, VoucherType from brewman.models.voucher import Voucher, Journal, VoucherType
from brewman.routers import to_uuid import brewman.schemas.master as schemas
from fastapi import APIRouter
router = APIRouter() router = APIRouter()
@router.post("/new") # "Employees" # Dependency
def save(request): def get_db():
name = request.json_body.get("name", "").strip()
if name == "":
raise ValidationError("Name cannot be blank")
cost_centre_id = uuid.UUID(request.json_body["costCentre"]["id"])
designation = request.json_body.get("designation", "").strip()
try: try:
salary = int(request.json_body["salary"]) db = SessionLocal()
if salary < 0: yield db
raise ValidationError("Salary must be an integer >= 0") finally:
except (ValueError, KeyError): db.close()
raise ValidationError("Salary must be an integer >= 0")
try:
service_points = round(Decimal(request.json_body["points"]), 2)
if service_points < 0:
raise ValidationError("Points must be a decimal >= 0 and < 1000")
except (ValueError, KeyError):
raise ValidationError("Points must be a decimal >= 0 and < 1000")
@router.post("/", response_model=schemas.Employee)
def save(
data: schemas.EmployeeIn,
db: Session = Depends(get_db),
user: User = Security(get_user, scopes=["employees"]),
):
try: try:
joining_date = datetime.datetime.strptime( item = Employee(
request.json_body["joiningDate"], "%d-%b-%Y" name=data.name,
is_starred=data.is_starred,
is_active=data.is_active,
cost_centre_id=data.cost_centre.id_,
designation=data.designation,
salary=data.salary,
points=data.points,
joining_date=data.joining_date,
leaving_date=None if data.is_active else data.leaving_date,
).create(db)
db.commit()
return employee_info(item.id, 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(),
) )
except (ValueError, KeyError, TypeError):
raise ValidationError("Joining Date is not a valid date")
is_starred = request.json_body["isStarred"]
is_active = request.json_body["isActive"] @router.put("/{id_}", response_model=schemas.Employee)
def update(
id_: uuid.UUID,
data: schemas.EmployeeIn,
db: Session = Depends(get_db),
user: User = Security(get_user, scopes=["employees"]),
):
try: try:
if is_active: item: Employee = db.query(Employee).filter(Employee.id == id_).first()
leaving_date = None if item.is_fixture:
else: raise HTTPException(
leaving_date = datetime.datetime.strptime( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request.json_body["leavingDate"], "%d-%b-%Y" detail=f"{item.name} is a fixture and cannot be edited or deleted.",
) )
if leaving_date < joining_date: item.name = data.name
raise ValidationError("Leaving Date cannot be less than Joining Date") item.cost_centre_id = data.cost_centre.id_
except (ValueError, KeyError, TypeError): item.designation = data.designation
raise ValidationError("Leaving Date is not a valid date") item.salary = data.salary
item.points = data.points
item = Employee( item.joining_date = data.joining_date
0, item.is_starred = data.is_starred
name, item.is_active = data.is_active
is_starred, item.leaving_date = data.leaving_date
is_active, db.commit()
cost_centre_id, return employee_info(item.id, db)
designation, except SQLAlchemyError as e:
salary, db.rollback()
service_points, raise HTTPException(
joining_date, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e),
leaving_date, )
).create(request.dbsession) except Exception:
transaction.commit() db.rollback()
return employee_info(item.id, request.dbsession) raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=traceback.format_exc(),
@router.put("/{id}") # "Employees"
def update(request):
item = (
request.dbsession.query(Employee)
.filter(Employee.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.get("name", "").strip()
if item.name == "":
raise ValidationError("Name cannot be blank")
item.cost_centre_id = uuid.UUID(request.json_body["costCentre"]["id"]) @router.delete("/{id_}")
item.designation = request.json_body.get("designation", "").strip() def delete(
id_: uuid.UUID,
try: db: Session = Depends(get_db),
item.salary = int(request.json_body["salary"]) user: User = Security(get_user, scopes=["employees"]),
if item.salary < 0: ):
raise ValidationError("Salary must be an integer >= 0") employee: Employee = db.query(Employee).filter(Employee.id == id_).first()
except (ValueError, KeyError): can_delete, reason = employee.can_delete("Advanced Delete" in user.permissions)
raise ValidationError("Salary must be an integer >= 0")
try:
item.service_points = round(Decimal(request.json_body["points"]), 2)
if item.service_points < 0:
raise ValidationError("Points must be a decimal >= 0")
except (ValueError, KeyError):
raise ValidationError("Points must be a decimal >= 0")
try:
item.joining_date = datetime.datetime.strptime(
request.json_body["joiningDate"], "%d-%b-%Y"
)
except (ValueError, KeyError, TypeError):
raise ValidationError("Joining Date is not a valid date")
item.is_starred = request.json_body["isStarred"]
item.is_active = request.json_body["isActive"]
try:
if item.is_active:
item.leaving_date = None
else:
item.leaving_date = datetime.datetime.strptime(
request.json_body["leavingDate"], "%d-%b-%Y"
)
if item.leaving_date < item.joining_date:
raise ValidationError("Leaving Date cannot be less than Joining Date")
except (ValueError, KeyError, TypeError):
raise ValidationError("Leaving Date is not a valid date")
transaction.commit()
return employee_info(item.id, request.dbsession)
@router.delete("/{id}") # "Employees"
def delete(request):
employee = (
request.dbsession.query(Employee)
.filter(Employee.id == uuid.UUID(request.matchdict["id"]))
.first()
)
can_delete, reason = employee.can_delete(request.has_permission("Advanced Delete"))
if can_delete: if can_delete:
delete_with_data(employee, request.dbsession) delete_with_data(employee, db)
transaction.commit() db.commit()
return employee_info(None, request.dbsession) return employee_info(None, db)
else: else:
transaction.abort() db.rollback()
response = Response("Cannot delete account because {0}".format(reason)) raise HTTPException(
response.status_int = 500 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
return response detail=f"Cannot delete account because {reason}",
)
@router.get("/{id}") # "Employees" @router.get("/")
def show_id(request): def show_blank(
id_ = to_uuid(request.matchdict["id"]) db: Session = Depends(get_db), user: User = Security(get_user, scopes=["employees"])
if id_ is None: ):
raise ValidationError("Invalid Employee") return employee_info(None, db)
return employee_info(id_, request.dbsession)
@router.get("/new") # "Employees" @router.get("/list")
def show_blank(request): async def show_list(db: Session = Depends(get_db), user: User = Depends(get_user)):
return employee_info(None, request.dbsession) return [
{
"id": item.id,
@router.get("/") # "Authenticated" "code": item.code,
async def show_list(l: bool): "name": item.name,
list_ = ( "designation": item.designation,
request.dbsession.query(Employee) "salary": item.salary,
"points": item.points,
"isActive": item.is_active,
"costCentre": item.cost_centre.name,
# "url": request.route_url("employees_id", id=item.id),
"joiningDate": item.joining_date.strftime("%d-%b-%Y"),
"leavingDate": ""
if item.is_active
else item.leaving_date.strftime("%d-%b-%Y"),
}
for item in db.query(Employee)
.order_by(desc(Employee.is_active)) .order_by(desc(Employee.is_active))
.order_by(Account.cost_centre_id) .order_by(Account.cost_centre_id)
.order_by(Employee.designation) .order_by(Employee.designation)
.order_by(Employee.name) .order_by(Employee.name)
.all() .all()
) ]
accounts = []
for item in list_:
accounts.append(
{
"id": item.id,
"code": item.code,
"name": item.name,
"designation": item.designation,
"salary": item.salary,
"points": item.service_points,
"isActive": item.is_active,
"costCentre": item.cost_centre.name,
"url": request.route_url("employees_id", id=item.id),
"joiningDate": item.joining_date.strftime("%d-%b-%Y"),
"leavingDate": ""
if item.is_active
else item.leaving_date.strftime("%d-%b-%Y"),
}
)
return accounts
@router.get("/", ) # "Authenticated" @router.get("/query",) # "Authenticated"
async def show_term(q: str): async def show_term(
filter_ = request.GET.get("q", None) q: str,
filter_ = None if filter_ == "" else filter_ c: int = None,
count = request.GET.get("c", None) db: Session = Depends(get_db),
count = None if count is None or count == "" else int(count) current_user: User = Depends(get_user),
):
list_ = [] list_ = []
for index, item in enumerate( for index, item in enumerate(AccountBase.list(10, q, dbsession=db)):
AccountBase.list(10, filter_, dbsession=request.dbsession)
):
list_.append( list_.append(
{ {
"id": item.id, "id": item.id,
@ -219,12 +171,21 @@ async def show_term(q: str):
}, },
} }
) )
if count is not None and index == count - 1: if c is not None and index == c - 1:
break break
return list_ return list_
def employee_info(id_, dbsession): @router.get("/{id_}")
def show_id(
id_: uuid.UUID,
db: Session = Depends(get_db),
user: User = Security(get_user, scopes=["employees"]),
):
return employee_info(id_, db)
def employee_info(id_, db):
if id_ is None: if id_ is None:
employee = { employee = {
"code": "(Auto)", "code": "(Auto)",
@ -233,7 +194,7 @@ def employee_info(id_, dbsession):
"costCentre": CostCentre.overall(), "costCentre": CostCentre.overall(),
} }
else: else:
employee = dbsession.query(Employee).filter(Employee.id == id_).first() employee = db.query(Employee).filter(Employee.id == id_).first()
if employee is None: if employee is None:
raise ValidationError("Invalid Employee") raise ValidationError("Invalid Employee")
employee = { employee = {
@ -244,7 +205,7 @@ def employee_info(id_, dbsession):
"isStarred": employee.is_starred, "isStarred": employee.is_starred,
"designation": employee.designation, "designation": employee.designation,
"salary": employee.salary, "salary": employee.salary,
"points": employee.service_points, "points": employee.points,
"joiningDate": employee.joining_date.strftime("%d-%b-%Y"), "joiningDate": employee.joining_date.strftime("%d-%b-%Y"),
"leavingDate": None "leavingDate": None
if employee.is_active if employee.is_active
@ -253,16 +214,17 @@ def employee_info(id_, dbsession):
"id": employee.cost_centre_id, "id": employee.cost_centre_id,
"name": employee.cost_centre.name, "name": employee.cost_centre.name,
}, },
"isFixture": employee.is_fixture,
} }
return employee return employee
def delete_with_data(employee, dbsession): def delete_with_data(employee, db):
suspense_account = ( suspense_account = (
dbsession.query(Account).filter(Account.id == Account.suspense()).first() db.query(Account).filter(Account.id == Account.suspense()).first()
) )
query = ( query = (
dbsession.query(Voucher) db.query(Voucher)
.options(joinedload_all(Voucher.journals, Journal.account, innerjoin=True)) .options(joinedload_all(Voucher.journals, Journal.account, innerjoin=True))
.filter(Voucher.journals.any(Journal.account_id == employee.id)) .filter(Voucher.journals.any(Journal.account_id == employee.id))
.all() .all()
@ -278,35 +240,33 @@ def delete_with_data(employee, dbsession):
else: else:
others = True others = True
if not others: if not others:
dbsession.delete(voucher) db.delete(voucher)
else: else:
if sus_jnl is None: if sus_jnl is None:
acc_jnl.account = suspense_account acc_jnl.account = suspense_account
voucher.narration += "\nSuspense \u20B9 {0:,.2f} is {1}".format( voucher.narration += (
acc_jnl.amount, employee.name f"\nSuspense \u20B9 {acc_jnl.amount:,.2f} is {employee.name}"
) )
else: else:
amount = (sus_jnl.debit * sus_jnl.amount) + ( amount = (sus_jnl.debit * sus_jnl.amount) + (
acc_jnl.debit * acc_jnl.amount acc_jnl.debit * acc_jnl.amount
) )
if acc_jnl.salary_deduction is not None: if acc_jnl.salary_deduction is not None:
dbsession.delete(acc_jnl.salary_deduction) db.delete(acc_jnl.salary_deduction)
dbsession.delete(acc_jnl) db.delete(acc_jnl)
if amount == 0: if amount == 0:
dbsession.delete(sus_jnl) db.delete(sus_jnl)
else: else:
sus_jnl.amount = abs(amount) sus_jnl.amount = abs(amount)
sus_jnl.debit = -1 if amount < 0 else 1 sus_jnl.debit = -1 if amount < 0 else 1
voucher.narration += "\nDeleted \u20B9 {0:,.2f} of {1}".format( voucher.narration += f"\nDeleted \u20B9 {acc_jnl.amount * acc_jnl.debit:,.2f} of {employee.name}"
acc_jnl.amount * acc_jnl.debit, employee.name
)
if voucher.type in ( if voucher.type in (
VoucherType.by_name("Payment").id, VoucherType.by_name("Payment").id,
VoucherType.by_name("Receipt").id, VoucherType.by_name("Receipt").id,
): ):
voucher.type = VoucherType.by_name("Journal") voucher.type = VoucherType.by_name("Journal")
for fingerprint in employee.fingerprints: for fingerprint in employee.fingerprints:
dbsession.delete(fingerprint) db.delete(fingerprint)
for attendance in employee.attendances: for attendance in employee.attendances:
dbsession.delete(attendance) db.delete(attendance)
dbsession.delete(employee) db.delete(employee)

View File

@ -442,7 +442,7 @@ def service_charge_employees(date, dbsession):
"designation": employee.designation, "designation": employee.designation,
"department": employee.cost_centre.name, "department": employee.cost_centre.name,
"daysWorked": att, "daysWorked": att,
"points": employee.service_points, "points": employee.points,
} }
) )

View File

@ -1,8 +1,14 @@
import uuid import uuid
from datetime import date from typing import Optional
from datetime import date, datetime
from decimal import Decimal from decimal import Decimal
from pydantic import BaseModel, Field 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])
class Product(BaseModel): class Product(BaseModel):
@ -53,36 +59,87 @@ class ProductGroup(BaseModel):
is_fixture: bool is_fixture: bool
class CostCentreSaveUpdate(BaseModel): class CostCentreLink(BaseModel):
name: str id_: uuid.UUID
class Config:
fields = {'id_': 'id'}
class CostCentre(CostCentreSaveUpdate): class CostCentreIn(BaseModel):
id_: uuid.UUID = Field(alias="id") name: str = Field(..., min_length=1)
is_fixture: bool = Field(alias="isFixture")
class AccountSaveUpdate(BaseModel): class CostCentre(CostCentreIn):
name: str id_: uuid.UUID
type: int is_fixture: bool
class Config:
fields = {'id_': 'id'}
anystr_strip_whitespace = True
alias_generator = to_camel
class AccountBase(BaseModel):
name: str = Field(..., min_length=1)
is_starred: bool is_starred: bool
is_active: bool is_active: bool
cost_centre: CostCentreLink
class Config:
fields = {'id_': 'id'}
anystr_strip_whitespace = True
alias_generator = to_camel
class AccountIn(AccountBase):
type: int
is_reconcilable: bool is_reconcilable: bool
cost_centre: CostCentre
class Account(AccountSaveUpdate): class Account(AccountIn):
id_: uuid.UUID = Field(alias="id") id_: uuid.UUID
code: int code: int
is_fixture: bool is_fixture: bool
class Employee(Account): class EmployeeIn(AccountBase):
designation: str designation: str
salary: int salary: int = Field(ge=0)
service_points: Decimal points: Decimal = Field(ge=0, lt=1000, multiple_of=0.01)
joining_date: date joining_date: date
leaving_date: date leaving_date: Optional[date]
@validator("joining_date", pre=True)
def parse_joining_date(cls, value):
return datetime.strptime(
value,
"%d-%b-%Y"
).date()
@validator("leaving_date", pre=True)
def parse_leaving_date(cls, value):
if value is None or value == "":
return None
else:
return datetime.strptime(
value,
"%d-%b-%Y"
).date()
@validator('leaving_date')
def leaving_date_more_than_joining_date(cls, v, values, **kwargs):
if values['is_active']:
return None
if v < values['joining_date']:
raise ValueError('Leaving Date cannot be less than Joining Date')
return v
class Employee(EmployeeIn):
id_: uuid.UUID
code: int
is_fixture: bool
class DbSetting(BaseModel): class DbSetting(BaseModel):