Added: Rate Contract Module. To implement: Checking this during purchase.

This commit is contained in:
2021-09-11 15:43:18 +05:30
parent 20ce1a297e
commit dee053c115
37 changed files with 1391 additions and 216 deletions

View File

@ -5,10 +5,8 @@ Revises: ad8b2d208492
Create Date: 2021-09-11 05:32:56.683107
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.

View File

@ -0,0 +1,71 @@
"""rate contracts
Revision ID: 071e8f29d257
Revises: 0363f582ab28
Create Date: 2021-09-11 05:57:49.062468
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
from brewman.models.permission import Permission
from sqlalchemy.dialects import postgresql
revision = "071e8f29d257"
down_revision = "0363f582ab28"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.execute(Permission.__table__.insert().values(id="f97589c6-df9f-45d3-874d-4245a4d99ade", name="Rate Contracts"))
op.create_table(
"rate_contracts",
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("date", sa.Date(), nullable=False),
sa.Column("vendor_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("valid_from", sa.Date(), nullable=True),
sa.Column("valid_till", sa.Date(), nullable=True),
sa.Column("narration", sa.Unicode(length=1000), nullable=False),
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("creation_date", sa.DateTime(), nullable=False),
sa.Column("last_edit_date", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], name=op.f("fk_rate_contracts_user_id_users")),
sa.ForeignKeyConstraint(["vendor_id"], ["accounts.id"], name=op.f("fk_rate_contracts_vendor_id_accounts")),
sa.PrimaryKeyConstraint("id", name=op.f("pk_rate_contracts")),
)
op.create_index(op.f("ix_rate_contracts_date"), "rate_contracts", ["date"], unique=False)
op.create_table(
"rate_contract_items",
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("rate_contract_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("product_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("cost_price", sa.Numeric(precision=15, scale=2), nullable=False),
sa.ForeignKeyConstraint(
["product_id"], ["products.id"], name=op.f("fk_rate_contract_items_product_id_products")
),
sa.ForeignKeyConstraint(
["rate_contract_id"],
["rate_contracts.id"],
name=op.f("fk_rate_contract_items_rate_contract_id_rate_contracts"),
),
sa.PrimaryKeyConstraint("id", name=op.f("pk_rate_contract_items")),
sa.UniqueConstraint("rate_contract_id", "product_id", name=op.f("uq_rate_contract_items_rate_contract_id")),
)
op.create_index(
op.f("ix_rate_contract_items_rate_contract_id"), "rate_contract_items", ["rate_contract_id"], unique=False
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_rate_contract_items_rate_contract_id"), table_name="rate_contract_items")
op.drop_table("rate_contract_items")
op.drop_index(op.f("ix_rate_contracts_date"), table_name="rate_contracts")
op.drop_table("rate_contracts")
# ### end Alembic commands ###

View File

@ -1,7 +1,7 @@
"""Initial Commit
Revision ID: 0bf3d70ee7de
Revises:
Revises:
Create Date: 2020-05-10 19:02:57.301086
"""

View File

@ -273,7 +273,7 @@ def upgrade():
)
op.execute(permission.delete().where(permission.c.name.in_(["Payment", "Receipt"])))
### end Alembic commands ###
# ### end Alembic commands ###
def downgrade():

View File

@ -18,6 +18,8 @@ from ..models.login_history import LoginHistory # noqa: F401
from ..models.permission import Permission # noqa: F401
from ..models.product import Product # noqa: F401
from ..models.product_group import ProductGroup # noqa: F401
from ..models.rate_contract import RateContract # noqa: F401
from ..models.rate_contract_item import RateContractItem # noqa: F401
from ..models.recipe import Recipe # noqa: F401
from ..models.recipe_item import RecipeItem # noqa: F401
from ..models.role import Role # noqa: F401

View File

@ -33,6 +33,7 @@ from .routers import (
product_group,
purchase,
purchase_return,
rate_contract,
rebase,
recipe,
reset_stock,
@ -76,6 +77,7 @@ app.include_router(attendance_types.router, prefix="/api/attendance-types", tags
app.include_router(employee_attendance.router, prefix="/api/employee-attendance", tags=["attendance"])
app.include_router(employee_attendance.router, prefix="/api/employee-attendance", tags=["attendance"])
app.include_router(attendance_report.router, prefix="/attendance-report", tags=["attendance"])
app.include_router(rate_contract.router, prefix="/api/rate-contracts", tags=["products"])
app.include_router(cost_centre.router, prefix="/api/cost-centres", tags=["cost-centres"])
app.include_router(employee.router, prefix="/api/employees", tags=["employees"])

View File

@ -32,6 +32,8 @@ class AccountBase(Base):
journals = relationship("Journal", back_populates="account")
cost_centre = relationship("CostCentre", back_populates="accounts")
rate_contracts = relationship("RateContract", back_populates="vendor")
@property
def __name__(self):
return self.name

View File

@ -1,124 +1,50 @@
import uuid
from sqlalchemy import (
Boolean,
Column,
ForeignKey,
Integer,
Numeric,
Unicode,
UniqueConstraint,
case,
func,
select,
)
from datetime import datetime
from sqlalchemy import Column, Date, DateTime, ForeignKey, Unicode
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import Session, relationship
from sqlalchemy.orm import relationship
from .meta import Base
class Product(Base):
__tablename__ = "products"
__table_args__ = (UniqueConstraint("name", "units"),)
class RateContract(Base):
__tablename__ = "rate_contracts"
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
code = Column("code", Integer, unique=True)
name = Column("name", Unicode(255), nullable=False)
units = Column("units", Unicode(255), nullable=False)
fraction = Column("fraction", Numeric(precision=15, scale=5), nullable=False)
fraction_units = Column("fraction_units", Unicode(255), nullable=False)
product_yield = Column("product_yield", Numeric(precision=15, scale=5), nullable=False)
product_group_id = Column(
"product_group_id",
UUID(as_uuid=True),
ForeignKey("product_groups.id"),
nullable=False,
)
account_id = Column("account_id", UUID(as_uuid=True), ForeignKey("accounts.id"), nullable=False)
price = Column("cost_price", Numeric(precision=15, scale=2), nullable=False)
sale_price = Column("sale_price", Numeric(precision=15, scale=2), nullable=False)
is_active = Column("is_active", Boolean, nullable=False)
is_fixture = Column("is_fixture", Boolean, nullable=False)
is_purchased = Column("is_purchased", Boolean, nullable=False)
is_sold = Column("is_sold", Boolean, nullable=False)
date = Column("date", Date, nullable=False, index=True)
vendor_id = Column("vendor_id", UUID(as_uuid=True), ForeignKey("accounts.id"), nullable=False)
valid_from = Column("valid_from", Date(), nullable=True)
valid_till = Column("valid_till", Date(), nullable=True)
narration = Column("narration", Unicode(1000), nullable=False)
product_group = relationship("ProductGroup", back_populates="products")
batches = relationship("Batch", back_populates="product")
inventories = relationship("Inventory", back_populates="product")
recipes = relationship("Recipe", back_populates="product")
account = relationship("Account", primaryjoin="Account.id==Product.account_id", back_populates="products")
user_id = Column("user_id", UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
creation_date = Column("creation_date", DateTime(), nullable=False)
last_edit_date = Column("last_edit_date", DateTime(), nullable=False)
user = relationship("User")
vendor = relationship("AccountBase", back_populates="rate_contracts")
items = relationship("RateContractItem", back_populates="rate_contract")
def __init__(
self,
code=None,
name=None,
units=None,
fraction=None,
fraction_units=None,
product_yield=None,
product_group_id=None,
account_id=None,
price=None,
sale_price=None,
is_active=None,
is_purchased=None,
is_sold=None,
date=None,
vendor_id=None,
valid_from=None,
valid_till=None,
narration="",
user_id=None,
creation_date=None,
last_edit_date=None,
id_=None,
is_fixture=False,
):
self.code = code
self.name = name
self.units = units
self.fraction = fraction
self.fraction_units = fraction_units
self.product_yield = product_yield
self.product_group_id = product_group_id
self.account_id = account_id
self.price = price
self.sale_price = sale_price
self.is_active = is_active
self.is_purchased = is_purchased
self.is_sold = is_sold
self.date = date
self.vendor_id = vendor_id
self.valid_from = valid_from
self.valid_till = valid_till
self.narration = narration
self.user_id = user_id
self.creation_date = creation_date or datetime.utcnow()
self.last_edit_date = last_edit_date or datetime.utcnow()
self.id = id_
self.is_fixture = is_fixture
@hybrid_property
def full_name(self):
return f"{self.name} ({self.units})" if self.units else self.name
@full_name.expression
def full_name(cls):
return cls.name + case([(cls.units != "", " (" + cls.units + ")")], else_="")
def create(self, db: Session):
self.code = db.execute(select(func.coalesce(func.max(Product.code), 0) + 1)).scalar_one()
db.add(self)
return self
def can_delete(self, advanced_delete: bool):
if self.is_fixture:
return False, f"{self.name} is a fixture and cannot be edited or deleted."
if self.is_active:
return False, "Product is active"
if len(self.inventories) > 0 and not advanced_delete:
return False, "Product has entries"
return True, ""
@classmethod
def query(cls, q, is_purchased: bool = None, active: bool = None, db: Session = None):
query_ = select(cls)
if active is not None:
query_ = query_.filter(cls.is_active == active)
if is_purchased is not None:
query_ = query_.filter(cls.is_purchased == is_purchased)
if q is not None:
for item in q.split():
if item.strip() != "":
query_ = query_.filter(cls.name.ilike(f"%{item}%"))
return db.execute(query_).scalars().all()
@classmethod
def suspense(cls):
return uuid.UUID("aa79a643-9ddc-4790-ac7f-a41f9efb4c15")

View File

@ -1,119 +1,38 @@
import uuid
from sqlalchemy import (
Boolean,
Column,
ForeignKey,
Integer,
Numeric,
Unicode,
UniqueConstraint,
case,
func,
select, DateTime, Date,
)
from sqlalchemy import Column, ForeignKey, Numeric, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import Session, relationship
from sqlalchemy.orm import relationship
from .meta import Base
class RateContract(Base):
__tablename__ = "rate_contracts"
__table_args__ = (UniqueConstraint("name", "units"),)
class RateContractItem(Base):
__tablename__ = "rate_contract_items"
__table_args__ = (UniqueConstraint("rate_contract_id", "product_id"),)
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
rate_contract_id = Column(
"rate_contract_id",
UUID(as_uuid=True),
ForeignKey("rate_contracts.id"),
nullable=False,
index=True,
)
product_id = Column("product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False)
price = Column("cost_price", Numeric(precision=15, scale=2), nullable=False)
user = relationship("User", primaryjoin="User.id==Voucher.user_id", cascade=None)
creation_date = Column("creation_date", DateTime(), nullable=False)
last_edit_date = Column("last_edit_date", DateTime(), nullable=False)
vendor_id = Column("vendor_id", UUID(as_uuid=True), ForeignKey("accounts.id"), nullable=False)
# product_id = Column("product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False)
# price = Column("cost_price", Numeric(precision=15, scale=2), nullable=False)
valid_from = Column("valid_from", Date(), nullable=True)
valid_till = Column("valid_till", Date(), nullable=True)
user = relationship("User")
vendor = relationship("Account", back_populates="rate_contracts")
# batches = relationship("Batch", back_populates="product")
# inventories = relationship("Inventory", back_populates="product")
# recipes = relationship("Recipe", back_populates="product")
# account = relationship("Account", primaryjoin="Account.id==Product.account_id", back_populates="products")
rate_contract = relationship("RateContract", back_populates="items")
product = relationship("Product")
def __init__(
self,
code=None,
name=None,
units=None,
fraction=None,
fraction_units=None,
product_yield=None,
product_group_id=None,
account_id=None,
rate_contract_id=None,
product_id=None,
price=None,
sale_price=None,
is_active=None,
is_purchased=None,
is_sold=None,
id_=None,
is_fixture=False,
):
self.code = code
self.name = name
self.units = units
self.fraction = fraction
self.fraction_units = fraction_units
self.product_yield = product_yield
self.product_group_id = product_group_id
self.account_id = account_id
self.rate_contract_id = rate_contract_id
self.product_id = product_id
self.price = price
self.sale_price = sale_price
self.is_active = is_active
self.is_purchased = is_purchased
self.is_sold = is_sold
self.id = id_
self.is_fixture = is_fixture
@hybrid_property
def full_name(self):
return f"{self.name} ({self.units})" if self.units else self.name
@full_name.expression
def full_name(cls):
return cls.name + case([(cls.units != "", " (" + cls.units + ")")], else_="")
def create(self, db: Session):
self.code = db.execute(select(func.coalesce(func.max(Product.code), 0) + 1)).scalar_one()
db.add(self)
return self
def can_delete(self, advanced_delete: bool):
if self.is_fixture:
return False, f"{self.name} is a fixture and cannot be edited or deleted."
if self.is_active:
return False, "Product is active"
if len(self.inventories) > 0 and not advanced_delete:
return False, "Product has entries"
return True, ""
@classmethod
def query(cls, q, is_purchased: bool = None, active: bool = None, db: Session = None):
query_ = select(cls)
if active is not None:
query_ = query_.filter(cls.is_active == active)
if is_purchased is not None:
query_ = query_.filter(cls.is_purchased == is_purchased)
if q is not None:
for item in q.split():
if item.strip() != "":
query_ = query_.filter(cls.name.ilike(f"%{item}%"))
return db.execute(query_).scalars().all()
@classmethod
def suspense(cls):
return uuid.UUID("aa79a643-9ddc-4790-ac7f-a41f9efb4c15")

View File

@ -0,0 +1,187 @@
import uuid
from typing import List
from fastapi import APIRouter, HTTPException, Request, Security, status
from sqlalchemy import delete, select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from ..core.security import get_current_active_user as get_user
from ..core.session import (
get_date,
get_finish_date,
get_start_date,
set_date,
set_period,
)
from ..db.session import SessionFuture
from ..models.rate_contract import RateContract
from ..models.rate_contract_item import RateContractItem
from ..schemas.product import ProductLink
from ..schemas.rate_contract import AccountLink
from ..schemas.rate_contract import RateContract as RateContractSchema
from ..schemas.rate_contract import RateContractIn as RateContractInSchema
from ..schemas.rate_contract import RateContractItem as RateContractItemSchema
from ..schemas.user import UserToken
from ..schemas.user_link import UserLink
router = APIRouter()
@router.post("", response_model=RateContractSchema)
async def save(
data: RateContractInSchema,
request: Request,
user: UserToken = Security(get_user, scopes=["rate-contracts"]),
) -> RateContractSchema:
try:
with SessionFuture() as db:
item = RateContract(
date=data.date_,
vendor_id=data.vendor.id_,
valid_from=data.valid_from,
valid_till=data.valid_till,
narration=data.narration,
user_id=user.id_,
)
db.add(item)
add_items(item, data.items, db)
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
set_period(data.valid_from.strftime("%d-%b-%Y"), data.valid_till.strftime("%d-%b-%Y"), request.session)
db.commit()
return rate_contract_info(item)
except SQLAlchemyError as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
def add_items(rate_contract: RateContract, items: List[RateContractItemSchema], db: Session) -> None:
for item in items:
rci = RateContractItem(rate_contract_id=rate_contract.id, product_id=item.product.id_, price=item.price)
rate_contract.items.append(rci)
db.add(rci)
@router.put("/{id_}", response_model=RateContractSchema)
async def update_route(
id_: uuid.UUID,
data: RateContractInSchema,
request: Request,
user: UserToken = Security(get_user, scopes=["rate-contracts"]),
) -> RateContractSchema:
try:
with SessionFuture() as db:
item: RateContract = db.execute(select(RateContract).where(RateContract.id == id_)).scalar_one()
item.date = data.date_
item.vendor_id = data.vendor.id_
item.valid_from = data.valid_from
item.valid_till = data.valid_till
item.narration = data.narration
item.user_id = user.id_
update_items(item, data.items, db)
set_date(data.date_.strftime("%d-%b-%Y"), request.session)
set_period(data.valid_from.strftime("%d-%b-%Y"), data.valid_till.strftime("%d-%b-%Y"), request.session)
db.commit()
return rate_contract_info(item)
except SQLAlchemyError as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
def update_items(rate_contract: RateContract, items: List[RateContractItemSchema], db: Session):
for it in range(len(rate_contract.items), 0, -1):
item = rate_contract.items[it - 1]
for j in range(len(items), 0, -1):
new_item = items[j - 1]
if new_item.id_ == item.id:
item.product_id = new_item.product.id_
item.price = new_item.price
items.remove(new_item)
break
else:
db.delete(item)
rate_contract.items.remove(item)
for item in items:
rci = RateContractItem(rate_contract_id=rate_contract.id, product_id=item.product.id_, price=item.price)
rate_contract.items.append(rci)
db.add(rci)
@router.delete("/{id_}", response_model=RateContractInSchema)
async def delete_route(
id_: uuid.UUID,
request: Request,
user: UserToken = Security(get_user, scopes=["rate-contracts"]),
) -> RateContractInSchema:
with SessionFuture() as db:
db.execute(delete(RateContractItem).where(RateContractItem.rate_contract_id == id_))
db.execute(delete(RateContract).where(RateContract.id == id_))
db.commit()
return rate_contract_blank(request.session)
@router.get("", response_model=RateContractInSchema)
async def show_blank(
request: Request,
user: UserToken = Security(get_user, scopes=["rate-contracts"]),
) -> RateContractInSchema:
return rate_contract_blank(request.session)
@router.get("/list", response_model=List[RateContractSchema])
async def show_list(
user: UserToken = Security(get_user),
) -> List[RateContractSchema]:
with SessionFuture() as db:
return [
rate_contract_info(item)
for item in db.execute(select(RateContract).order_by(RateContract.date)).scalars().all()
]
@router.get("/{id_}", response_model=RateContractSchema)
async def show_id(
id_: uuid.UUID,
user: UserToken = Security(get_user, scopes=["rate-contracts"]),
) -> RateContractSchema:
with SessionFuture() as db:
item: RateContract = db.execute(select(RateContract).where(RateContract.id == id_)).scalar_one()
return rate_contract_info(item)
def rate_contract_info(item: RateContract) -> RateContractSchema:
return RateContractSchema(
id=item.id,
date=item.date,
vendor=AccountLink(id=item.vendor_id, name=item.vendor.name),
validFrom=item.valid_from,
validTill=item.valid_till,
narration=item.narration,
creationDate=item.creation_date,
lastEditDate=item.last_edit_date,
user=UserLink(id=item.user_id, name=item.user.name),
items=[
RateContractItemSchema(
id=i.id,
product=ProductLink(id=i.product_id, name=i.product.full_name),
price=i.price,
)
for i in item.items
],
)
def rate_contract_blank(session: dict) -> RateContractInSchema:
return RateContractInSchema(
date=get_date(session),
validFrom=get_start_date(session),
validTill=get_finish_date(session),
narration="",
items=[],
)

View File

@ -1,8 +1,7 @@
import uuid
from datetime import date, datetime
from datetime import datetime
from decimal import Decimal
from typing import List, Optional
import brewman.schemas.voucher as output
@ -16,7 +15,6 @@ from ..core.session import get_first_day
from ..db.session import SessionFuture
from ..models.account import Account
from ..models.account_base import AccountBase
from ..models.account_type import AccountType
from ..models.attendance import Attendance
from ..models.attendance_type import AttendanceType
from ..models.cost_centre import CostCentre

View File

@ -0,0 +1,91 @@
import uuid
from datetime import date, datetime
from typing import List, Optional
from pydantic import BaseModel, validator
from . import to_camel
from .account import AccountLink
from .rate_contract_item import RateContractItem
from .user_link import UserLink
class RateContractIn(BaseModel):
date_: date
vendor: Optional[AccountLink]
valid_from: date
valid_till: date
narration: str
items: List[RateContractItem]
class Config:
anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {
date: lambda v: v.strftime("%d-%b-%Y"),
datetime: lambda v: v.strftime("%d-%b-%Y %H:%I"),
}
@validator("date_", pre=True)
def parse_date(cls, value):
if isinstance(value, date):
return value
return datetime.strptime(value, "%d-%b-%Y").date()
@validator("valid_from", pre=True)
def parse_creation_date(cls, value):
if isinstance(value, date):
return value
return datetime.strptime(value, "%d-%b-%Y")
@validator("valid_till", pre=True)
def parse_last_edit_date(cls, value):
if isinstance(value, date):
return value
return datetime.strptime(value, "%d-%b-%Y")
class RateContract(RateContractIn):
id_: uuid.UUID
creation_date: Optional[datetime]
last_edit_date: Optional[datetime]
user: Optional[UserLink]
class Config:
anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {
date: lambda v: v.strftime("%d-%b-%Y"),
datetime: lambda v: v.strftime("%d-%b-%Y %H:%I"),
}
# @validator("date_", pre=True)
# def parse_date(cls, value):
# if isinstance(value, date):
# return value
# return datetime.strptime(value, "%d-%b-%Y").date()
#
# @validator("valid_from", pre=True)
# def parse_creation_date(cls, value):
# if isinstance(value, datetime):
# return value
# return datetime.strptime(value, "%d-%b-%Y %H:%M")
#
# @validator("valid_till", pre=True)
# def parse_last_edit_date(cls, value):
# if isinstance(value, datetime):
# return value
# return datetime.strptime(value, "%d-%b-%Y %H:%M")
@validator("creation_date", pre=True)
def parse_creation_date(cls, value):
if isinstance(value, datetime):
return value
return datetime.strptime(value, "%d-%b-%Y %H:%M")
@validator("last_edit_date", pre=True)
def parse_last_edit_date(cls, value):
if isinstance(value, datetime):
return value
return datetime.strptime(value, "%d-%b-%Y %H:%M")

View File

@ -0,0 +1,17 @@
import uuid
from decimal import Decimal
from typing import Optional
from brewman.schemas import to_camel
from brewman.schemas.product import ProductLink
from pydantic import BaseModel, Field
class RateContractItem(BaseModel):
id_: Optional[uuid.UUID]
product: ProductLink
price: Decimal = Field(ge=0, multiple_of=0.01)
class Config:
alias_generator = to_camel