brewman/brewman/brewman/models/master.py

537 lines
18 KiB
Python

import uuid
from datetime import date
from sqlalchemy import (
Boolean,
Column,
Date,
ForeignKey,
Integer,
Numeric,
PickleType,
Unicode,
UniqueConstraint,
func,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Session, relationship
from .meta import Base
class Product(Base):
__tablename__ = "products"
__table_args__ = (UniqueConstraint("name", "units"),)
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)
batches = relationship("Batch", backref="product")
inventories = relationship("Inventory", backref="product")
recipes = relationship("Recipe", backref="product")
account = relationship("Account", primaryjoin="Account.id==Product.account_id", backref="products")
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,
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.id = id_
self.is_fixture = is_fixture
@property
def full_name(self):
return "{0} ({1})".format(self.name, self.units)
def create(self, db: Session):
code = db.query(func.max(Product.code)).one()[0]
self.code = 1 if code is None else code + 1
db.add(self)
return self
def can_delete(self, advanced_delete):
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=None, active=None, db=None):
query_ = db.query(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 query_
@classmethod
def suspense(cls):
return uuid.UUID("aa79a643-9ddc-4790-ac7f-a41f9efb4c15")
class Recipe(Base):
__tablename__ = "recipes"
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
product_id = Column("product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False)
quantity = Column("quantity", Numeric(precision=15, scale=2), nullable=False)
cost_price = Column("cost_price", Numeric(precision=15, scale=2), nullable=False)
sale_price = Column("sale_price", Numeric(precision=15, scale=2), nullable=False)
notes = Column("notes", Unicode(255))
valid_from = Column("valid_from", Date, nullable=False)
valid_to = Column("valid_to", Date, nullable=False)
effective_from = Column("effective_from", Date, nullable=False)
effective_to = Column("effective_to", Date)
recipe_items = relationship("RecipeItem", backref="recipe")
def __init__(
self,
product_id=None,
quantity=None,
cost_price=None,
sale_price=None,
valid_from=None,
valid_to=None,
notes=None,
effective_from=None,
id_=None,
):
self.product_id = product_id
self.quantity = quantity
self.cost_price = cost_price
self.sale_price = sale_price
self.valid_from = valid_from
self.valid_to = valid_to
self.notes = "" if notes is None else notes
self.effective_from = date.today() if effective_from is None else effective_from
self.effective_to = None
self.id = id_
class RecipeItem(Base):
__tablename__ = "recipe_items"
__table_args__ = (UniqueConstraint("recipe_id", "product_id"),)
id = Column("recipe_item_id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
recipe_id = Column("recipe_id", UUID(as_uuid=True), ForeignKey("recipes.id"), nullable=False)
product_id = Column("product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False)
quantity = Column("quantity", Integer, nullable=False)
price = Column("price", Integer, nullable=False)
product = relationship("Product")
def __init__(self, recipe_id=None, product_id=None, quantity=None, price=None, id_=None):
self.recipe_id = recipe_id
self.product_id = product_id
self.quantity = quantity
self.price = price
self.id = id_
class ProductGroup(Base):
__tablename__ = "product_groups"
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column("name", Unicode(255), unique=True)
is_fixture = Column("is_fixture", Boolean, nullable=False)
products = relationship("Product", backref="product_group")
def __init__(self, name=None, id_=None, is_fixture=False):
self.name = name
self.id = id_
self.is_fixture = is_fixture
@classmethod
def menu_item(cls):
return uuid.UUID("dad46805-f577-4e5b-8073-9b788e0173fc")
@classmethod
def semi(cls):
return uuid.UUID("e6bf81b9-1e9b-499f-81d5-ab5662e9d9b1")
class CostCentre(Base):
__tablename__ = "cost_centres"
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column("name", Unicode(255), unique=True)
is_fixture = Column("is_fixture", Boolean, nullable=False)
accounts = relationship("AccountBase", backref="cost_centre")
journals = relationship("Journal", backref="cost_centre")
@property
def __name__(self):
return self.name
def __init__(self, name=None, id_=None, is_fixture=False):
self.name = name
self.id = id_
self.is_fixture = is_fixture
@classmethod
def cost_centre_purchase(cls):
return uuid.UUID("7b845f95-dfef-fa4a-897c-f0baf15284a3")
@classmethod
def cost_centre_kitchen(cls):
return uuid.UUID("b2d398ce-e3cc-c542-9feb-5d7783e899df")
@classmethod
def cost_centre_overall(cls):
return uuid.UUID("36f59436-522a-0746-ae94-e0f746bf6c0d")
@classmethod
def overall(cls):
return {
"id": uuid.UUID("36f59436-522a-0746-ae94-e0f746bf6c0d"),
"name": "Overall",
}
class AccountBase(Base):
__tablename__ = "accounts"
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
code = Column("code", Integer, nullable=False)
name = Column("name", Unicode(255), unique=True, nullable=False)
type = Column("type", Integer, nullable=False)
account_type = Column("account_type", Unicode(50), nullable=False)
is_starred = Column("is_starred", Boolean, nullable=False)
is_active = Column("is_active", Boolean, nullable=False)
is_reconcilable = Column("is_reconcilable", Boolean, nullable=False)
cost_centre_id = Column(
"cost_centre_id",
UUID(as_uuid=True),
ForeignKey("cost_centres.id"),
nullable=False,
)
is_fixture = Column("is_fixture", Boolean, nullable=False)
__mapper_args__ = {"polymorphic_on": account_type}
journals = relationship("Journal", back_populates="account")
@property
def __name__(self):
return self.name
@property
def type_object(self):
return AccountType.by_id(self.type)
def __init__(
self,
code=None,
name=None,
type_=None,
is_starred=None,
is_active=None,
is_reconcilable=False,
cost_centre_id=None,
id_=None,
is_fixture=False,
):
self.code = code
self.name = name
self.type = type_
self.is_starred = is_starred
self.is_active = is_active
self.is_reconcilable = is_reconcilable
self.cost_centre_id = cost_centre_id
self.id = id_
self.is_fixture = is_fixture
@classmethod
def query(cls, q, type_, reconcilable=None, active=None, db=None):
query_ = db.query(cls)
if type_ is not None:
if not isinstance(type_, int):
type_ = int(type_)
query_ = query_.filter(cls.type == type_)
if reconcilable is not None:
query_ = query_.filter(cls.is_reconcilable == reconcilable)
if active is not None:
query_ = query_.filter(cls.is_active == active)
if q is not None:
for item in q.split():
query_ = query_.filter(cls.name.ilike(f"%{item}%"))
return query_.order_by(cls.name)
def create(self, db: Session):
code = db.query(func.max(AccountBase.code)).filter(AccountBase.type == self.type).one()[0]
self.code = 1 if code is None else code + 1
db.add(self)
return self
def can_delete(self, advanced_delete):
if self.is_fixture:
return False, f"{self.name} is a fixture and cannot be edited or deleted."
if self.is_active:
return False, "Account is active"
if len(self.journals) > 0 and not advanced_delete:
return False, "Account has journal entries"
return True, ""
@classmethod
def get_code(cls, type_, db: Session):
code = db.query(func.max(AccountBase.code)).filter(AccountBase.type == type_).one()[0]
return 1 if code is None else code + 1
@classmethod
def all_purchases(cls):
return uuid.UUID("240dd899-c413-854c-a7eb-67a29d154490")
@classmethod
def cash_in_hand(cls):
return {"id": "ed2341bb-80b8-9649-90db-f9aaca183bb3", "name": "Cash in Hand"}
@classmethod
def local_purchase(cls):
return {"id": "d2b75912-505f-2548-9093-466dfff6a0f9", "name": "Local Purchase"}
@classmethod
def salary(cls):
return {"id": "5c2b54d0-c174-004d-a0d5-92cdaadcefa7", "name": "Staff Salary"}
@classmethod
def salary_id(cls):
return uuid.UUID("5c2b54d0-c174-004d-a0d5-92cdaadcefa7")
@classmethod
def incentive(cls):
return {"id": "b7eff754-e8ba-e047-ab06-9132c15c7640", "name": "Incentives"}
@classmethod
def incentive_id(cls):
return uuid.UUID("b7eff754-e8ba-e047-ab06-9132c15c7640")
@classmethod
def esi_pf_expense(cls):
return uuid.UUID("d2a1a286-e900-764b-a1a5-9f4b00dbb940")
@classmethod
def esi_pf_payable(cls):
return uuid.UUID("42277912-cc18-854b-b134-9f4b00dba419")
@classmethod
def suspense(cls):
return uuid.UUID("3854e317-6f3b-5142-ab26-9c44d4cddd08")
class Employee(AccountBase):
__tablename__ = "employees"
__mapper_args__ = {"polymorphic_identity": "employees"}
id = Column("id", UUID(as_uuid=True), ForeignKey(AccountBase.id), primary_key=True)
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)
fingerprints = relationship("Fingerprint", backref="employee", cascade=None, cascade_backrefs=False)
def __init__(
self,
code=None,
name=None,
is_starred=None,
is_active=None,
cost_centre_id=None,
designation=None,
salary=None,
points=None,
joining_date=None,
leaving_date=None,
):
self.designation = designation
self.salary = salary
self.points = points
self.joining_date = joining_date
self.leaving_date = leaving_date
super().__init__(
code=code,
name=name,
type_=10,
is_starred=is_starred,
is_active=is_active,
is_reconcilable=False,
cost_centre_id=cost_centre_id,
)
def create(self, db: Session):
code = db.query(func.max(AccountBase.code)).filter(AccountBase.type == self.type).one()[0]
self.code = 1 if code is None else code + 1
self.name += f" ({str(self.code)})"
db.add(self)
return self
def can_delete(self, advanced_delete):
return super(Employee, self).can_delete(advanced_delete)
class Account(AccountBase):
__mapper_args__ = {"polymorphic_identity": ""}
def can_delete(self, advanced_delete):
if len(self.products) > 0:
return False, "Account has products"
return super(Account, self).can_delete(advanced_delete)
class AttendanceType:
def __init__(self, id_, name, value=None):
self.id = id_
self.name = name
self.value = value
@classmethod
def list(cls):
return [
AttendanceType(0, "Not Set", 0),
AttendanceType(1, "Present", 1),
AttendanceType(2, "Off Day", 1),
AttendanceType(3, "On Leave", 0),
AttendanceType(4, "Absent", 0),
AttendanceType(5, "Half Day", 0.5),
AttendanceType(6, "Double Duty", 2),
AttendanceType(7, "Paid Leave Availed", 1),
AttendanceType(8, "Casual Leave Availed", 1),
AttendanceType(9, "Compensatory Off", 1),
AttendanceType(10, "Half Day + PL", 1),
AttendanceType(11, "Half Day + CL", 1),
]
@classmethod
def by_name(cls, name):
return next(i for i in cls.list() if i.name == name)
@classmethod
def by_id(cls, id_):
return next(i for i in cls.list() if i.id == id_)
class AccountType:
def __init__(
self,
id_,
name,
balance_sheet=None,
debit=None,
cash_flow_classification=None,
order=None,
show_in_list=None,
):
self.id = id_
self.name = name
self.balance_sheet = balance_sheet
self.debit = debit
self.cash_flow_classification = cash_flow_classification
# Cash flow Classifications are:
# Cash
# Operating
# Investing
# Financing
self.order = order
self.show_in_list = show_in_list
@classmethod
def list(cls):
return [
AccountType(1, "Cash", True, True, "Cash", 10000, True),
AccountType(2, "Purchase", False, True, "Operating", 20000, True),
AccountType(3, "Sale", False, False, "Operating", 10000, True),
AccountType(4, "Assets", True, True, "Investing", 20000, True),
AccountType(5, "Capital", True, False, "Financing", 70000, True),
AccountType(6, "Debtors", True, True, "Operating", 30000, True),
AccountType(7, "Expenses", False, True, "Operating", 40000, True),
AccountType(9, "Creditors", True, False, "Operating", 60000, True),
AccountType(10, "Salary", True, True, "Operating", 40000, False),
AccountType(11, "Liabilities", True, False, "Operating", 50000, True),
AccountType(12, "Revenue", False, False, "Operating", 30000, True),
AccountType(13, "Tax", True, False, "Operating", 80000, True),
]
# list.append(AccountType(8, 'Discount', False, False, True, 30, True))
# list.append(AccountType(14, 'Total', False, False, False, 900, False))
# list.append(AccountType(15, 'Net', False, False, False, 1000, False))
@classmethod
def by_name(cls, name):
return next(i for i in cls.list() if i.name == name)
@classmethod
def by_id(cls, id_):
return next(i for i in cls.list() if i.id == id_)
class DbSetting(Base):
__tablename__ = "settings"
id = Column("id", UUID(as_uuid=True), 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_
self.name = name
self.data = data