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)
designation = Column("Designation", Unicode(255))
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)
leaving_date = Column("LeavingDate", DateTime)
@ -416,13 +416,13 @@ class Employee(AccountBase):
cost_centre_id=None,
designation=None,
salary=None,
service_points=None,
points=None,
joining_date=None,
leaving_date=None,
):
self.designation = designation
self.salary = salary
self.service_points = service_points
self.points = points
self.joining_date = joining_date
self.leaving_date = leaving_date
super().__init__(

View File

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

View File

@ -2,19 +2,32 @@ import datetime
import uuid
from sqlalchemy import or_
from sqlalchemy.orm import Session
from fastapi import Depends, Security
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 ..models.master import Employee
from ..models.voucher import Attendance
from ..routers.fingerprint import get_prints
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
router = APIRouter()
@router.get("/") # "Attendance"
def attendance_blank(request):
# Dependency
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": []}

View File

@ -42,9 +42,9 @@ def attendance_record(start_date, finish_date, dbsession):
employee.designation,
employee.cost_centre.name,
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):
att = (
dbsession.query(Attendance)

View File

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

View File

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

View File

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

View File

@ -1,8 +1,14 @@
import uuid
from datetime import date
from typing import Optional
from datetime import date, datetime
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):
@ -53,36 +59,87 @@ class ProductGroup(BaseModel):
is_fixture: bool
class CostCentreSaveUpdate(BaseModel):
name: str
class CostCentreLink(BaseModel):
id_: uuid.UUID
class Config:
fields = {'id_': 'id'}
class CostCentre(CostCentreSaveUpdate):
id_: uuid.UUID = Field(alias="id")
is_fixture: bool = Field(alias="isFixture")
class CostCentreIn(BaseModel):
name: str = Field(..., min_length=1)
class AccountSaveUpdate(BaseModel):
name: str
type: int
class CostCentre(CostCentreIn):
id_: uuid.UUID
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_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
cost_centre: CostCentre
class Account(AccountSaveUpdate):
id_: uuid.UUID = Field(alias="id")
class Account(AccountIn):
id_: uuid.UUID
code: int
is_fixture: bool
class Employee(Account):
class EmployeeIn(AccountBase):
designation: str
salary: int
service_points: Decimal
salary: int = Field(ge=0)
points: Decimal = Field(ge=0, lt=1000, multiple_of=0.01)
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):