예제 #1
0
class User(db.Model, BaseMixin, AuditableMixin, PersonMixin):
    """Basic user model
    """

    __tablename__ = "users"

    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(80), nullable=False)
    phone = db.Column(db.String(80))
    password = db.Column(db.String(255), nullable=False)
    role_code = db.Column(db.String(50))
    company_id = db.Column(db.String(50))

    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        self.password = pwd_context.hash(self.password)
        self.get_uuid()

    def __repr__(self):
        return "<User %s>" % self.username

    @property
    def role(self):
        """Get the role."""
        role = Role.query.filter_by(uuid=self.role_code).first()
        if role is None:
            return None
        return role.name

    @property
    def category(self):
        """Get the role category."""
        role = Role.query.filter_by(uuid=self.role_code).first()
        if role is None:
            return None
        return role.category

    @property
    def company(self):
        try:
            if self.company_id == "system":
                return "System"
            else:
                sql = (""" name
                FROM account_holders
                where uuid = '""" + self.company_id + """'
                """)
                data = query(sql)
                return data[0]["name"]
        except Exception:
            return ""
예제 #2
0
class Vehicle(db.Model, BaseMixin, AuditableMixin):

    registration_no = db.Column(db.String(50))
    chassis_no = db.Column(db.String(50))
    model_id = db.Column(db.String(50), db.ForeignKey("vehicle_model.uuid"))
    model_no = db.Column(db.String(50))
    engine_no = db.Column(db.String(50))
    vehicle_type = db.Column(db.String(50))
    customer_id = db.Column(db.String(50), db.ForeignKey("customer.uuid"))
    customer = db.relationship("Customer", back_populates="vehicles")
    vehicle_model = db.relationship("VehicleModel")

    def __init__(self, **kwargs):
        super(Vehicle, self).__init__(**kwargs)
        self.get_uuid()

    def __repr__(self):
        return "<Vehicle %s>" % self.uuid
예제 #3
0
class ItemLog(db.Model, BaseMixin, AuditableMixin):
    """Inventory log model to track usage

    If a purchase is made, debit is vendor id and credit is item id
    if a sale is mafe, debit is item id and credit is entity_id
    """

    item_id = db.Column(db.String(50), db.ForeignKey("item.uuid"))
    reference = db.Column(db.String(50))  # job id or vendor id
    category = db.Column(db.String(50))  # sale or purchase
    credit = db.Column(db.String(50))
    debit = db.Column(db.String(50))
    quantity = db.Column(db.Integer)
    unit_cost = db.Column(db.String(50))
    pay_type = db.Column(db.String(50))
    on_credit = db.Column(db.Boolean, default=False)
    credit_status = db.Column(db.String(50), default='NONE')
    amount = db.Column(db.Numeric(20, 2), CheckConstraint("amount > 0.0"))
    entity_id = db.Column(db.String(50), db.ForeignKey("entity.uuid"))
    accounting_date = db.Column(db.Date, default=db.func.now())
    accounting_period = db.Column(db.String(50), default=db.func.now())

    entity = db.relationship("Entity")
    item = db.relationship("Item")

    def __init__(self, **kwargs):
        super(ItemLog, self).__init__(**kwargs)
        self.accounting_period = datetime.now().strftime("%Y-%m")
        self.get_uuid()

    def __repr__(self):
        return "<ItemLog %s>" % self.item_id

    @property
    def debit_account(self):
        sql = (
            """ name FROM item_accounts where item_accounts.uuid ='"""
            + str(self.debit)
            + """'
            """
        )

        data = query(sql)
        return data if data is None else data[0]["name"]

    @property
    def credit_account(self):

        sql = (
            """ name FROM item_accounts where item_accounts.uuid ='"""
            + str(self.credit)
            + """'"""
        )

        data = query(sql)
        return data if data is None else data[0]["name"]

    def is_valid(self):
        """validate the object"""

        if self.category not in ('sale', 'purchase'):
            return False, {"msg": "The category {0} doesn't exist".format(self.tran_type)}, 422
        if not Item.get(uuid=self.credit) and not Item.get(uuid=self.debit):
            return False, {"msg": "The supplied item id does not exist"}, 422
        if ItemLog.get(reference=self.reference):
            return False, {"msg": "The supplied reference already exists"}, 409
        if ItemLog.get(reference=self.cheque_number):
            return False, {"msg": "This transaction is already reversed"}, 409
        if self.category == "reversal" and not ItemLog.get(
                reference=self.cheque_number
        ):
            return False, {"msg": "You can only reverse an existing transaction"}, 422

        # check balance
        item = Item.get(uuid=self.debit)
        bal_after = int(item.quantity) - int(self.quantity)
        app.logger.info(item.quantity, self.quantity)

        if Item.get(uuid=self.item_id).name != 'labour' and self.category == 'sale' and float(bal_after) < 0.0:
            return False, {
                "msg": "Insufficient quantity on the {0} account {1}".format(item.name, commas(item.quantity))}, 409

        if self.tran_type == "reversal":
            orig = ItemLog.get(reference=self.cheque_number)
            self.debit = orig.credit
            self.credit = orig.debit
            self.amount = orig.amount
            self.entity_id = orig.entity_id

        return True, self, 200

    @staticmethod
    def init_jobitem(job_item):
        item_log = ItemLog(
            item_id=job_item.item_id,
            debit=job_item.item_id,
            credit=job_item.entity_id,
            reference=job_item.job_id,
            category='sale',
            quantity=job_item.quantity,
            amount=job_item.cost,
            unit_cost=job_item.unit_cost,
            entity_id=job_item.entity_id,
        )
        valid, reason, status = item_log.is_valid()
        if not valid:
            raise Exception(reason.get('msg'), status)
        return item_log

    def transact(self):
        """
        :rtype: object
        If a customer is invoiced, value is debited off their account onto
        the entity account, when they make a payment
        1. Value is debited off the `escrow` onto the customer account
        2. Value is also moved off the entity account onto the pay type account

        In the future, the payment type account ought to be created per entity
        """
        valid, reason, status = self.is_valid()
        if not valid:
            raise Exception(reason.get('msg'), status)

        db.session.commit()
예제 #4
0
class BaseMixin:
    """Allow a model to track its creation and update times"""

    id = db.Column(db.Integer, primary_key=True)
    uuid = db.Column(db.String(50), unique=True)
    active = db.Column(db.Boolean, default=True)
    modified_by = db.Column(db.Integer)
    created_by = db.Column(db.Integer)
    date_created = db.Column(db.DateTime(timezone=True),
                             default=datetime.datetime.utcnow)
    date_modified = db.Column(db.DateTime(timezone=True),
                              default=datetime.datetime.utcnow,
                              onupdate=datetime.datetime.utcnow)

    def __init__(self, **kwargs):
        self.created_by = get_jwt_identity()

    # @declared_attr
    # def user_id(cls):
    #     return db.Column(
    #         db.String(50), db.ForeignKey("users.id"), nullable=False
    #     )

    # @declared_attr
    # def user(cls):
    #     return db.relationship('User')

    @classmethod
    def get(cls, **kwargs):
        """Get a specific object from the database."""
        return cls.query.filter_by(**kwargs).first()

    def save(self):
        """Save an object in the database."""
        try:
            # db.session.session.expire_on_commit = False
            db.session.add(self)
            db.session.commit()
            return self
        except Exception as e:
            db.session.rollback()
            return {
                "message": "Ensure the object you're saving is valid.",
                "exception": str(e),
            }

    def serialize(self):
        """Convert sqlalchemy object to python dictionary."""
        return {
            column.name: getattr(self, column.name)
            for column in self.__table__.columns
        }

    def get_uuid(self):
        if not self.uuid:
            self.uuid = uuid.uuid4().hex

    def creator(self):
        try:
            sql = (""" first_name + ' ' + last_name as username
            FROM users
            where users.id = """ + str(self.created_by) + """
            """)
            data = query(sql)
            return data if data is None else data[0]["username"]
        except Exception:
            return ""

    def created_on(self):
        if self.date_created is not None:
            return self.date_created.strftime("%Y-%m-%d %I:%M:%S %p")

    def log(self, label):
        try:
            self.save()
            self.__generate_code__(label)
            db.session.commit()
        except Exception as e:
            db.session.rollback()
            return {
                "message": "Ensure the object you're saving is valid.",
                "exception": str(e),
            }

    def update(self):
        try:
            db.session.commit()
        except Exception as e:
            db.session.rollback()
            return {
                "message": "Ensure the object you're saving is valid.",
                "exception": str(e),
            }

    def __generate_code__(self, label):
        """Generate the resource code."""
        model_id = str(self.id)

        switcher = {1: "000" + model_id, 2: "00" + model_id, 3: "0" + model_id}

        result = switcher.get(len(model_id), model_id)
        result.upper()

        min_char = 2
        max_char = 2
        allchar = string.ascii_letters
        salt = "".join(
            choice(allchar) for x in range(randint(min_char, max_char)))

        self.uuid = label + salt.upper() + result
        return self
예제 #5
0
class PersonMixin:
    first_name = db.Column(db.String(50))
    last_name = db.Column(db.String(50))
예제 #6
0
 def code(cls):
     return db.Column(db.String(50),
                      db.ForeignKey("tarriff.uuid"),
                      nullable=False)  # tarriff code
예제 #7
0
class Expenditure(db.Model, BaseMixin, AuditableMixin):
    """Expenditure model: expenses, purchases, payouts, salaries etc
    """
    reference = db.Column(db.String(50))
    amount = db.Column(db.String(50))
    phone = db.Column(db.String(80))
    pay_type = db.Column(db.String(50))
    on_credit = db.Column(db.Boolean, default=False)
    category = db.Column(db.String(50))
    credit_status = db.Column(db.String(50), default='NONE')
    narration = db.Column(db.String(4000))
    entity_id = db.Column(db.String(50),
                          db.ForeignKey("entity.uuid"),
                          nullable=False)
    vendor_id = db.Column(db.String(50),
                          db.ForeignKey("vendor.uuid"),
                          nullable=False)

    entity = db.relationship('Entity')
    vendor = db.relationship('Vendor')

    def __init__(self, **kwargs):
        super(Expenditure, self).__init__(**kwargs)
        if self.pay_type == 'credit':
            self.on_credit = True
            self.credit_status = 'PENDING'

        self.get_uuid()

    def __repr__(self):
        return "<Expenditure of {0} on {1}>".format(self.amount, self.item)

    @property
    def credit(self):
        entries = Entry.query.filter_by(cheque_number=self.reference).all()
        count = 0
        paid = 0
        for entry in entries:
            if 'credit' not in entry.reference:
                paid += entry.amount
                count += 1

        return {
            "paid": float(paid),
            "balance": float(self.amount) - float(paid),
            "payments": count
        }

    def clear_credit(self, amount_to_pay):
        """Check if expenses on credit are cleared"""
        if not self.on_credit:
            raise Exception('This is not a credit expense')

        paid = self.credit['paid']
        actual_bal = self.credit['balance']
        count = self.credit['payments']

        balance = float(self.amount) - (float(paid) + float(amount_to_pay))

        self.credit_status = 'PAID' if int(balance) == 0 else 'PARTIAL'
        app.logger.info(self.credit_status)

        if balance < 0.0:
            raise Exception(
                'Amount to pay should not be greater than the balance {2}'.
                format(commas(actual_bal)))
        else:
            entry = Entry.init_expenditure(self)
            entry.amount = amount_to_pay
            entry.reference = self.uuid + str(count)
            entry.transact()

    @staticmethod
    def init_lpo(lpo):
        exp = Expenditure(reference=lpo.uuid,
                          amount=lpo.amount,
                          phone='',
                          pay_type=lpo.pay_type,
                          category='purchase',
                          narration=lpo.narration,
                          entity_id=lpo.entity_id,
                          vendor_id=lpo.vendor_id)
        exp.create()

    def create(self):
        entry = Entry.init_expenditure(self)
        self.save()
        entry.transact()
예제 #8
0
class Job(db.Model, BaseMixin, AuditableMixin):
    """"
       job cards
    """

    employee_id = db.Column(db.String(50),
                            db.ForeignKey("employee.uuid"),
                            nullable=False)
    request_id = db.Column(db.String(50),
                           db.ForeignKey("service_request.uuid"),
                           nullable=False)
    entity_id = db.Column(db.String(50),
                          db.ForeignKey("entity.uuid"),
                          nullable=False)
    is_complete = db.Column(db.Boolean, default=False)
    completed_date = db.Column(db.DateTime(timezone=True),
                               default=datetime.datetime.utcnow)
    completed_by = db.Column(db.Integer, db.ForeignKey("users.id"))

    employee = db.relationship("Employee")
    request = db.relationship("ServiceRequest")
    entity = db.relationship("Entity")

    def __init__(self, **kwargs):
        super(Job, self).__init__(**kwargs)
        self.get_uuid()

    def __repr__(self):
        return "<Job %s>" % self.employee_id

    @property
    def time(self):
        if self.completed_date:
            import datetime
            from dateutil.relativedelta import relativedelta

            start = self.date_created
            ends = self.completed_date

            diff = relativedelta(ends, start)
            return {
                "years":
                diff.years,
                "months":
                diff.months,
                "days":
                diff.days,
                "hours":
                diff.hours,
                "minutes":
                diff.minutes,
                "word":
                "%d year %d month %d days %d hours %d minutes" %
                (diff.years, diff.months, diff.days, diff.hours, diff.minutes)
            }
        else:
            return None

    def complete(self):
        from autoshop.models.item import Item, ItemLog
        from autoshop.commons.util import random_tran_id

        job = Job.get(uuid=self.uuid)

        hours = job.time['hours']
        days_in_hours = job.time['days'] * 24

        time = hours + days_in_hours

        if time == 0 and job.time['minutes'] > 0:
            time = 1

        item = Item.get(code='labour')
        if item:
            log = ItemLog(item_id=item.uuid,
                          debit=item.uuid,
                          credit=item.entity_id,
                          reference=random_tran_id(),
                          category='sale',
                          quantity=time,
                          unit_cost=item.price,
                          amount=time * int(item.price),
                          entity_id=item.entity_id)

            job_item = JobItem(job_id=self.uuid,
                               item_id=item.uuid,
                               quantity=time,
                               unit_cost=item.price,
                               entity_id=item.entity_id)

            db.session.add(job_item)
            db.session.add(log)
            db.session.commit()
예제 #9
0
class Account(db.Model, BaseMixin, AuditableMixin):
    """An Account is a store of value for the entity,
    vendor and customer
    """

    __tablename__ = "accounts"

    send_sms = db.Column(db.Boolean, default=False)
    owner_id = db.Column(db.String(50), unique=True)
    acc_type = db.Column(db.String(50))
    group = db.Column(db.String(50))
    minimum_balance = db.Column(db.Numeric(20, 2))

    def __init__(self, **kwargs):
        super(Account, self).__init__(**kwargs)
        self.get_uuid()

    def __repr__(self):
        return "<Account %s>" % self.name

    @property
    def balance(self):
        """Get the account balance."""
        try:
            return query(" balance from account_balances where id=" +
                         str(self.id))[0]["balance"]
        except Exception:
            return 0

    @property
    def name(self):
        """
        Get the limits per category..
        this is the summation of all entries under a category
        """
        sql = (""" name FROM accounts INNER JOIN account_holders on
        account_holders.uuid=accounts.owner_id where accounts.id =
        """ + str(self.id) + """""")

        data = query(sql)
        return data if data is None else data[0]["name"]

    @property
    def wallets(self):
        """
        Get the limits per category..
        this is the summation of all entries under a category
        """
        sql = (
            """ accounts.id,account_ledgers.category,vehicle.registration_no,
        COALESCE(sum(account_ledgers.amount), 0.0) as balance FROM accounts
        LEFT OUTER JOIN account_ledgers ON accounts.id = account_ledgers.account_id
        INNER JOIN vehicle on vehicle.uuid=account_ledgers.category where
        acc_type='customer' and accounts.id = """ + str(self.id) + """
        GROUP BY accounts.id,account_ledgers.category,vehicle.registration_no """
        )

        wallets = query(sql)
        if not wallets:
            return []
        return wallets

    @property
    def no_of_wallets(self):
        """
        Get the limits per category..
        this is the summation of all entries under a category
        """
        sql = (""" accounts.id,account_ledgers.category,category.name,
        COALESCE(sum(account_ledgers.amount), 0.0) as balance FROM accounts
        LEFT OUTER JOIN account_ledgers ON accounts.id = account_ledgers.account_id
        INNER JOIN category on category.uuid=account_ledgers.category
        where accounts.id = """ + str(self.id) + """
        GROUP BY accounts.id,account_ledgers.category,category.name""")

        wallets = query(sql)
        if wallets is None:
            return 0
        return len(wallets)
예제 #10
0
class LocalPurchaseOrder(db.Model, BaseMixin, AuditableMixin, CreditMixin):
    """Basic LPO model
    """

    entity_id = db.Column(db.String(50),
                          db.ForeignKey("entity.uuid"),
                          nullable=False)
    vendor_id = db.Column(db.String(50),
                          db.ForeignKey("vendor.uuid"),
                          nullable=False)
    amount = db.Column(db.String(50), default='0')
    narration = db.Column(db.String(50))
    status = db.Column(db.String(50), default='PENDING')

    entity = db.relationship('Entity')
    vendor = db.relationship('Vendor')

    def __init__(self, **kwargs):
        super(LocalPurchaseOrder, self).__init__(**kwargs)
        if self.pay_type == 'credit':
            self.on_credit = True
            self.credit_status = 'PENDING'
        self.get_uuid()

    def __repr__(self):
        return "<LocalPurchaseOrder %s>" % self.uuid

    @property
    def items(self):
        return LpoItem.query.filter_by(order_id=self.uuid).count()

    def log_items(self):
        logs = []
        # entries = []
        items = LpoItem.query.filter_by(order_id=self.uuid).all()

        if not items:
            raise Exception("No items found in LPO. Please add some items")

        total = 0
        self.status = 'COMPLETED'

        for item in items:
            log = ItemLog(item_id=item.item_id,
                          debit=self.vendor_id,
                          credit=item.item_id,
                          reference=self.uuid,
                          category='purchase',
                          quantity=item.quantity,
                          unit_cost=item.unit_price,
                          amount=int(item.unit_price) * int(item.quantity),
                          entity_id=item.entity_id,
                          pay_type=self.pay_type)
            logs.append(log)
            total += int(log.amount)

        self.amount = total

        if not Expenditure.get(uuid=self.uuid):
            db.session.add_all(logs)
            db.session.commit()

            Expenditure.init_lpo(self)

    def clear_credit(self, amount_to_pay):
        """Check if expenses on credit are cleared"""
        if not self.on_credit:
            return

        paid = self.credit['paid']
        actual_bal = self.credit['balance']
        count = self.credit['payments']

        balance = float(self.amount) - (float(paid) + float(amount_to_pay))

        self.credit_status = 'PAID' if int(balance) == 0 else 'PARTIAL'

        if balance < 0.0:
            raise Exception(
                'Amount to pay should not be greater than the balance {2}'.
                format(commas(actual_bal)))
        else:
            exp = Expenditure.get(reference=self.uuid)
            exp.amount = amount_to_pay
            exp.pay_type = self.pay_type
            entry = Entry.init_expenditure(exp)
            entry.reference = self.uuid + str(count)
            entry.transact()
예제 #11
0
class Expense(db.Model, BaseMixin, AuditableMixin):
    """Expense model
    """

    item = db.Column(db.String(80))
    reference = db.Column(db.String(50))
    amount = db.Column(db.String(50))
    pay_type = db.Column(db.String(50))
    on_credit = db.Column(db.Boolean, default=False)
    credit_status = db.Column(db.String(50), default='NONE')
    narration = db.Column(db.String(4000))
    entity_id = db.Column(db.String(50))

    def __init__(self, **kwargs):
        super(Expense, self).__init__(**kwargs)
        if self.pay_type == 'credit':
            self.on_credit = True
            self.credit_status = 'PENDING'

        self.get_uuid()

    def __repr__(self):
        return "<Expense %s>" % self.name

    @property
    def credit(self):
        entries = Entry.query.filter_by(cheque_number=self.reference).all()
        count = 0
        paid = 0
        for entry in entries:
            if 'credit' not in entry.reference:
                paid += entry.amount
                count += 1

        return {
            "paid": float(paid),
            "balance": float(self.amount) - float(paid),
            "payments": count
        }

    def clear_credit(self, amount_to_pay):
        """Check if expenses on credit are cleared"""
        if not self.on_credit:
            raise Exception('This is not a credit expense')

        paid = self.credit['paid']
        actual_bal = self.credit['balance']
        count = self.credit['payments']

        balance = float(self.amount) - (float(paid) + float(amount_to_pay))

        self.credit_status = 'PAID' if int(balance) == 0 else 'PARTIAL'
        app.logger.info(self.credit_status)

        if balance < 0.0:
            raise Exception('Amount {0}, Actual {1}, Balance {2}'.format(
                commas(self.amount), commas(actual_bal), commas(balance)))
        else:
            entry = Entry.init_expense(self)
            entry.amount = amount_to_pay
            entry.reference = self.uuid + str(count)
            entry.transact()
예제 #12
0
class Transaction(db.Model, BaseMixin, AuditableMixin):
    tranid = db.Column(db.String(50))
    reference = db.Column(db.String(50))  # customer/vendor id
    vendor_id = db.Column(db.String(50))
    phone = db.Column(db.String(50))
    category = db.Column(db.String(50))
    tran_type = db.Column(db.String(50))
    pay_type = db.Column(db.String(50))
    amount = db.Column(db.String(50))
    narration = db.Column(db.String(200))
    status = db.Column(db.String(50), default="PENDING")
    status_id = db.Column(db.String(50))
    reason = db.Column(db.String(4000))
    entity_id = db.Column(db.String(50))
    is_synchronous = db.Column(db.Boolean, default=False)
    processed = db.Column(db.Boolean, default=False)
    reconciled = db.Column(db.Boolean, default=False)

    def __init__(self, **kwargs):
        super(Transaction, self).__init__(**kwargs)
        self.get_uuid()

    def __repr__(self):
        return "<Transaction %s>" % self.uuid
예제 #13
0
class Entry(db.Model):
    """Ledger model
    An entry is a record of movement of value between accounts
    """

    __tablename__ = "entries"

    id = db.Column(db.Integer, primary_key=True)
    reference = db.Column(db.String(80), nullable=False)
    amount = db.Column(db.Numeric(20, 2), CheckConstraint("amount > 0.0"))
    debit = db.Column(db.Integer, db.ForeignKey("accounts.id", ondelete="RESTRICT"))
    credit = db.Column(db.Integer, db.ForeignKey("accounts.id", ondelete="RESTRICT"))
    tran_type = db.Column(db.String(50))
    phone = db.Column(db.String(50))
    category = db.Column(db.String(50))
    pay_type = db.Column(db.String(50))
    description = db.Column(db.String(4000))
    cheque_number = db.Column(db.String(80))
    entity_id = db.Column(db.String(50))
    date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
    accounting_date = db.Column(db.Date, default=db.func.now())
    accounting_period = db.Column(db.String(50), default=db.func.now())

    def __init__(self, **kwargs):
        super(Entry, self).__init__(**kwargs)
        self.accounting_period = datetime.now().strftime("%Y-%m")

    def __repr__(self):
        return "<Entry %s>" % self.reference

    @property
    def entity(self):
        return Entity.get(uuid=self.entity_id).name

    @property
    def debit_account(self):
        """
        Get the limits per category..
        this is the summation of all entries under a category
        """
        sql = (
            """ name FROM accounts INNER JOIN account_holders on
            account_holders.uuid=accounts.owner_id where accounts.id = """
            + str(self.debit)
            + """
            """
        )

        data = query(sql)
        return data if data is None else data[0]["name"]

    @property
    def credit_account(self):
        """
        Get the limits per category..
        this is the summation of all entries under a category
        """
        sql = (
            """ name FROM accounts INNER JOIN account_holders on
            account_holders.uuid=accounts.owner_id where accounts.id =
            """
            + str(self.credit)
            + """ """
        )

        data = query(sql)
        return data if data is None else data[0]["name"]

    def save(self):
        """Save an object in the database."""
        try:
            db.session.add(self)
            db.session.commit()
            return self
        except Exception as e:
            db.session.rollback()
            return {
                "message": "Ensure the object you're saving is valid.",
                "exception": str(e),
            }

    def serialize(self):
        """Convert sqlalchemy object to python dictionary."""
        return {
            column.name: getattr(self, column.name) for column in self.__table__.columns
        }

    @classmethod
    def get(cls, **kwargs):
        """Get a specific object from the database."""
        return cls.query.filter_by(**kwargs).first()

    def is_valid(self):
        """validate the object"""
        
        if not TransactionType.get(uuid=self.tran_type):
            return False, {"msg": "The transaction type {0} doesn't exist".format(self.tran_type)}, 422
        if not Account.get(id=self.credit) and not Account.get(id=self.debit):
            return False, {"msg": "The supplied account id does not exist"}, 422
        if Entry.get(reference=self.reference):
            return False, {"msg": "The supplied reference already exists"}, 409
        if Entry.get(reference=self.cheque_number):
            return False, {"msg": "This transaction is already reversed"}, 409
        if self.tran_type == "reversal" and not Entry.get(
                reference=self.cheque_number
            ):
            return False, {"msg": "You can only reverse an existing transaction"}, 422
        
        # check balance
        account = Account.get(id=self.debit)
        bal_after = int(account.balance) - int(self.amount)
        app.logger.info(self.amount)

        if account.minimum_balance is not None and float(bal_after) < float(account.minimum_balance):
            return False, {"msg": "Insufficient balance on {0} account {1}".format(account.name,commas(account.balance))}, 409

        if self.tran_type == "reversal":
            orig = Entry.get(tranid=self.cheque_number)
            self.debit = orig.credit
            self.credit = orig.debit
            self.amount = orig.amount
            self.entity_id = orig.entity_id

        
        return True, self, 200

    @staticmethod
    def init_expense(expense):
        entry = Entry(
            reference=expense.uuid,
            amount=expense.amount,
            phone='',
            entity_id=expense.entity_id,
            description=expense.narration,
            tran_type='expense',
            cheque_number=expense.reference,
            category=expense.item,
            pay_type=expense.pay_type,
        )
                        
        if expense.pay_type == 'credit':
            entry.debit = CommissionAccount.get(code='credit').account.id
            entry.credit = CommissionAccount.get(code='expenses').account.id
            entry.reference = entry.reference + '-credit'
        elif expense.on_credit and expense.pay_type != 'credit' and expense.credit_status in ('PAID','PARTIAL'):
            entry.debit = Account.get(owner_id=expense.pay_type).id
            entry.credit = CommissionAccount.get(code='credit').account.id
        else:
            entry.debit = Account.get(owner_id=expense.pay_type).id
            entry.credit = CommissionAccount.get(code='expenses').account.id

        valid, reason, status = entry.is_valid()
        if valid:
            return entry
        else:
            raise Exception(reason, status)

    @staticmethod
    def init_item_log(item_log):
        entry = Entry(
            reference=item_log.uuid,
            amount=item_log.amount,
            phone='',
            entity_id=item_log.entity_id,
            description=item_log.item_id,
            tran_type=item_log.category,
            category=item_log.item_id,
            pay_type=item_log.pay_type,
        )
                        
        if item_log.pay_type == 'credit':
            entry.debit = CommissionAccount.get(code='credit').account.id
            entry.credit = Account.get(owner_id=item_log.debit).id
            entry.reference = entry.reference + '-credit'
        elif item_log.on_credit and item_log.pay_type != 'credit' and item_log.credit_status == 'PAID':
            entry.debit = Account.get(owner_id=item_log.pay_type).id
            entry.credit = CommissionAccount.get(code='credit').account.id
        else:
            entry.debit = Account.get(owner_id=item_log.pay_type).id
            entry.credit = Account.get(owner_id=item_log.debit).id

        valid, reason, status = entry.is_valid()
        if valid:
            return entry
        else:
            raise Exception(reason, status)
    
    @staticmethod
    def init_expenditure(expenditure):
        entry = Entry(
            reference=expenditure.uuid,
            amount=expenditure.amount,
            phone=expenditure.phone,
            entity_id=expenditure.entity_id,
            description=expenditure.narration,
            tran_type=expenditure.category,
            cheque_number=expenditure.reference,
            category=expenditure.vendor_id,
            pay_type=expenditure.pay_type,
        )
                        
        if expenditure.pay_type == 'credit':
            entry.debit = CommissionAccount.get(code='credit').account.id
            entry.credit = CommissionAccount.get(code=expenditure.category).account.id
            entry.reference = entry.reference + '-credit'
        elif expenditure.on_credit and expenditure.pay_type != 'credit' and expenditure.credit_status in ('PAID','PARTIAL'):
            entry.debit = Account.get(owner_id=expenditure.pay_type).id
            entry.credit = CommissionAccount.get(code='credit').account.id
        else:
            entry.debit = Account.get(owner_id=expenditure.pay_type).id
            entry.credit = CommissionAccount.get(code=expenditure.category).account.id

        return entry

    @staticmethod
    def init_transaction(transaction):
        entry = Entry(
            reference=transaction.uuid,
            amount=transaction.amount,
            phone=transaction.phone,
            entity_id=transaction.entity_id,
            description=transaction.narration,
            tran_type=transaction.tran_type,
            category=transaction.category,
            pay_type=transaction.pay_type,
        )
        
        cust_acct = Account.get(owner_id=transaction.reference)

        if transaction.tran_type == 'payment':
            entry.debit = CommissionAccount.get(code='escrow').account.id
            entry.credit = cust_acct.id
        elif transaction.tran_type == 'bill':
            entry.debit = cust_acct.id        
            entry.credit = Account.get(owner_id=transaction.entity_id).id
        else:
            raise Exception("Failed to determine transaction accounts")

        valid, reason, status = entry.is_valid()
        if valid:
            return entry
        else:
            raise Exception(reason, status)

    def get_entries(self):
        entries = [self]

        charge = Tarriff.get(
            tran_type=self.tran_type, payment_type=self.pay_type, entity_id="ALL"
        )

        if charge:
            charge_fee = get_charge_fee(charge.code, self.amount)

            splits = ChargeSplit.query.filter_by(code=charge.code).all()

            for split in splits:
                name = "charge-" + str(splits.index(split) + 1)
                fee = float(split.percentage / 100) * float(charge_fee)

                e = Entry(
                    reference=self.reference,
                    amount=fee,
                    debit=self.debit,
                    credit=Account.get(code=split.account_code).id,
                    description=charge.name,
                    tran_type="charge",
                    phone=self.phone,
                    pay_type=self.pay_type,
                    utility_code=name,
                    entity_id=self.entity_id,
                )
                entries.append(e)

        if self.tran_type == 'payment':
            payment_entry = Entry(
                reference=self.reference,
                amount=self.amount,
                description=self.description,
                tran_type=self.tran_type,
                phone=self.phone,
                pay_type=self.pay_type,
                entity_id=self.entity_id
            )
            payment_entry.debit = Account.get(owner_id=self.entity_id).id
            payment_entry.credit = Account.get(owner_id=self.pay_type).id
            entries.append(payment_entry)

        return entries

    def transact(self):
        """
        :rtype: object
        If a customer is invoiced, value is debited off their account onto
        the entity account, when they make a payment
        1. Value is debited off the `escrow` onto the customer account
        2. Value is also moved off the entity account onto the pay type account

        In the future, the payment type account ought to be created per entity
        """
        valid, reason, status = self.is_valid()
        if not valid:
            raise Exception(reason.get('msg'), status)

        entries = self.get_entries()

        for entr in entries:
            db.session.add(entr)

        db.session.commit()

        
    def sms(self, phone):
        try:
            from autoshop.commons.messaging import send_sms_async

            if self.tran_type == "payment":
                msg = (
                    "Hello "
                    + Account.get(id=self.credit).name
                    + ", your payment of UGX "
                    + "{:,}".format(float(self.amount))
                    + " has been received "
                )
            else:
                msg = (
                    "Hello "
                    + Account.get(id=self.debit).name
                    + ", please note that you have a bill of UGX "
                    + "{:,}".format(float(self.amount))
                    + ". "
                )

            send_sms_async(phone, msg)
        except Exception:
            pass