Exemplo n.º 1
0
class UsersRoles(TimeTrackedModel):
    __tablename__ = 'users_roles'
    id = db.Column(db.Integer(), primary_key=True)
    user_id = db.Column(
        db.Integer(),
        db.ForeignKey('users.id', onupdate='cascade', ondelete='cascade'))
    role_id = db.Column(db.Integer(),
                        db.ForeignKey('roles.id', ondelete='cascade'))
Exemplo n.º 2
0
class Role(TimeTrackedModel):
    __tablename__ = 'roles'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(255), nullable=False,
                     unique=True)  # for @roles_accepted()

    def __str__(self):
        return self.name
Exemplo n.º 3
0
class Access(db.Model):
    __tablename__ = 'accesses'

    id = db.Column(db.Integer, primary_key=True)
    created_at = db.Column(db.DateTime(timezone=True),
                           server_default=func.now())

    user_id = db.Column(db.Integer(),
                        db.ForeignKey('users.id', onupdate="cascade"),
                        nullable=False)
    user = db.relationship('User', back_populates='accesses')

    account_id = db.Column(db.Integer(),
                           db.ForeignKey('accounts.id', onupdate="cascade"),
                           nullable=False)
    account = db.relationship('Account', back_populates='accesses')

    def __init__(self, user_id=None, account_id=None):
        self.user_id = user_id
        self.account_id = account_id
Exemplo n.º 4
0
class Transfer(TimeTrackedModel):
    """
    Represents a transfer of money TO or FROM a Bank Account.

    Transferring TO:
        - payment to vendors
        - counter_party becomes "origin"

    Transferring FROM:
        - refund from vendors
        - counter_party becomes "destination"
    """
    __tablename__ = 'transfers'

    id = db.Column(db.Integer, primary_key=True)

    bank_account_id = db.Column(db.Integer(), db.ForeignKey('bank_accounts.id', onupdate="cascade"))
    bank_account = db.relationship('BankAccount', back_populates='transfers')

    counter_party = db.Column(db.String(96))

    gross = db.Column(db.Float(), nullable=False)
    net = db.Column(db.Float())
    currency = db.Column(db.String(12))
    exchange_rate = db.Column(db.Float(), nullable=False)

    is_refund = db.Column(db.Boolean, default=False)

    notes = db.Column(db.Text())
    date = db.Column(db.Date(), nullable=False)

    @property
    def suggested_net(self):
        pcts = set()
        for vendor in self.bank_account.vendors:
            pcts.add(vendor.service_fee)

        if not pcts:
            return "Not Set"

        pcts = sorted(list(pcts))

        ret = Markup('<ul>')
        for pct in pcts:
            ret += Markup('<li>')
            amt = self.gross * (100-pct) / 100.0
            ret += '%.2f (%s%%)' % (amt, pct)
            ret += Markup('</li>')
        ret += Markup('</ul>')

        return ret
Exemplo n.º 5
0
class Permission(TimeTrackedModel):
    __tablename__ = 'permissions'

    __table_args__ = (db.UniqueConstraint('vendor_id',
                                          'user_id',
                                          name='permission_vendor_user'), )

    id = db.Column(db.Integer, primary_key=True)

    vendor_id = db.Column(db.Integer(),
                          db.ForeignKey('vendors.id', onupdate="cascade"),
                          nullable=False)
    vendor = db.relationship('Vendor', back_populates='permissions')

    user_id = db.Column(db.Integer(),
                        db.ForeignKey('users.id', onupdate="cascade"),
                        nullable=False)
    user = db.relationship('User',
                           foreign_keys=[user_id],
                           back_populates='permissions')

    created_by_id = db.Column(db.Integer(),
                              db.ForeignKey('users.id', onupdate="cascade"))
    created_by = db.relationship('User', foreign_keys=[created_by_id])

    notes = db.Column(db.Text())

    def __str__(self):
        return '%s - %s' % (self.vendor, self.user)

    @classmethod
    def check(self, vendor, user):
        """https://stackoverflow.com/questions/32938475/flask-sqlalchemy-check-if-row-exists-in-table
        """
        if vendor and user:
            return db.session.query(db.exists().where(
                and_(Permission.vendor_id == vendor.id,
                     Permission.user_id == user.id))).scalar()
Exemplo n.º 6
0
class Account(db.Model):
    __tablename__ = 'accounts'
    __versioned__ = {'exclude': ['VPSs']}

    id = db.Column(db.Integer, primary_key=True)

    status = db.Column(db.Enum(AccountStatus, name='account_status'),
                       nullable=False,
                       default=AccountStatus.UNINITIALIZED)
    adwords_id = db.Column(db.String(20), nullable=False, unique=True)
    nickname = db.Column(db.String(48))

    account_budget = db.Column(db.Float())
    account_budget_override = db.Column(db.Float())
    remaining_account_budget = db.Column(db.Float())
    remaining_account_budget_override = db.Column(db.Float())

    daily_budget = db.Column(db.Float())
    currency = db.Column(db.String(12))
    exchange_rate = db.Column(db.Float())
    is_unlimited = db.Column(db.Boolean(),
                             server_default=expression.false(),
                             default=False)

    login = db.Column(db.String(48))
    password = db.Column(db.String(48))

    batch = db.Column(db.String(48))
    country = db.Column(db.String(24))
    external_comment = db.Column(db.Text())
    internal_comment = db.Column(db.Text())
    auto_tag_on = db.Column(db.Boolean(), default=False)

    VPSs = db.relationship("Vps",
                           secondary=association_table,
                           back_populates="accounts")

    client_id = db.Column(db.Integer(),
                          db.ForeignKey('users.id', onupdate="cascade"))
    client = db.relationship('User', back_populates='accounts')

    vendor_id = db.Column(db.Integer(),
                          db.ForeignKey('vendors.id', onupdate="cascade"))
    vendor = db.relationship('Vendor', back_populates='accounts')

    accesses = db.relationship('Access', back_populates='account')

    # We still need this for sorting
    updated_at = db.Column(db.DateTime(timezone=True),
                           server_default=func.now(),
                           onupdate=func.now())

    last_visited_by_eve = db.Column(db.DateTime(timezone=True))

    def __str__(self):
        if self.adwords_id:
            return self.adwords_id
        return 'Empty Account'

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, self.id)

    def get_account_budget(self):
        if self.account_budget_override is not None:
            return self.account_budget_override
        return self.account_budget

    def get_remaining_account_budget(self):
        if self.remaining_account_budget_override is not None:
            return self.remaining_account_budget_override
        return self.remaining_account_budget

    @hybrid_property
    def days_left(self):
        """Returns the number of days left before the budget exhausts.

        Finance dept sorts by days_left asc to alert us so that they can alert us
        which accounts that are running out of money.    For accounts with budget=0
        or empty, we do not want to see them there so we will return a large
        number, i.e. 99.
        """
        if no_none(self.get_remaining_account_budget(), self.daily_budget) \
                and self.daily_budget:
            return math.floor(self.get_remaining_account_budget() /
                              self.daily_budget)
        else:
            return 99

    @days_left.expression
    def days_left(cls):
        return sqlalchemy.func.floor(cls.remaining_account_budget /
                                     (cls.daily_budget + 1))  # hack

    @property
    def percentage_spent(self):
        ab, rab = self.get_account_budget(), self.get_remaining_account_budget(
        )
        if no_none(ab, rab) and ab:
            return 100 * (ab - rab) / ab

    @property
    def spent(self):
        ab, rab = self.get_account_budget(), self.get_remaining_account_budget(
        )
        if no_none(ab, rab):
            return ab - rab

    @hybrid_property
    def spent_in_hkd(self):
        if no_none(self.spent, self.exchange_rate):
            return self.spent * self.exchange_rate
        return 0

    @spent_in_hkd.expression
    def spent_in_hkd(cls):
        return (cls.account_budget -
                cls.remaining_account_budget) * cls.exchange_rate

    @property
    def daily_budget_in_hkd(self):
        if no_none(self.daily_budget, self.exchange_rate):
            return self.daily_budget * self.exchange_rate

    @property
    def remaining_in_hkd(self):
        if no_none(self.get_remaining_account_budget(), self.exchange_rate):
            return self.get_remaining_account_budget() * self.exchange_rate

    @property
    @cache.memoize()
    def days_to_topup(self):
        """Rather than using inline model form, just reflect this
        """
        return self.vendor.days_to_topup

    @property
    @cache.memoize()
    def suspended_on(self):
        """Returns a dt for when suspension occurs. If account is not suspended return None.
        """
        for v in self.versions:
            if v.status == AccountStatus.SUSPENDED:
                return v.updated_at

    @property
    def clients_allowed(self):
        """Returns a string which shows which clients are allowed.

        Not memoizing here because this could be used by other widgets. So it makes sense to use
        cache_helper as we are sharing this between different classes.
        """
        key = 'clients_allowed-%s' % self.vendor_id
        if self.vendor:
            ret = cache.get(key)
            if not ret:
                pems = Permission.query.filter(
                    Permission.vendor_id == self.vendor_id).order_by(
                        Permission.user_id)
                if pems.count():
                    ret = ranges([p.user_id for p in pems])
                else:
                    ret = lazy_gettext("Vendor permissions not defined.")
                cache.set(key, ret)
            return ret
        return lazy_gettext("Vendor is empty.")

    @property
    @cache.memoize()
    def VPSs_jinja(self):
        """Show only AWS VPSs if there are multiple VPSs.
        """
        VPSs = self.VPSs
        has_aws_vps = False
        pos_aws_vps = 0

        for i, vps in enumerate(VPSs):
            if vps.provider and vps.provider.upper().startswith('AWS'):
                has_aws_vps = True
                pos_aws_vps = i
                break

        if has_aws_vps:
            return str(VPSs[pos_aws_vps])
        else:
            return ", ".join(sorted([str(v) for v in VPSs]))

    @property
    @cache.memoize()
    def created_at(self):
        """Running this operation even after memoization has caused the request to take longer than
        30 seconds. Hence, we will only execute this method on the relevant vendors.
        """
        VENDORS = [40, 41, 42]
        if self.vendor_id in VENDORS:
            return self.versions.first().transaction.issued_at.strftime(
                '%m/%d %H:%M')
        return ""