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