Example #1
0
class DataUsage(db.Model):
    """Model class to represent data usage record

    Note:
        A daily usage record is created for a subscription each day
        it is active, beginning at midnight UTC timezone.

    """
    __tablename__ = "data_usages"

    id = db.Column(db.Integer, primary_key=True)
    mb_used = db.Column(db.Float, default=0.0)
    from_date = db.Column(db.TIMESTAMP(timezone=True))
    to_date = db.Column(db.TIMESTAMP(timezone=True))

    subscription_id = db.Column(
        db.Integer, db.ForeignKey("subscriptions.id"), nullable=False
    )
    subscription = db.relationship("Subscription", back_populates="data_usages")

    def __repr__(self):  # pragma: no cover
        return (
            f"<{self.__class__.__name__}: {self.id} ({self.subscription_id}) "
            f"{self.mb_used} MB {self.from_date} - {self.to_date}>"
        )
Example #2
0
class BillingCycle(db.Model):
    """Model class to represent billing cycle dates

    NOTE: this is probably temporary....
    """

    __tablename__ = "billing_cycles"

    id = db.Column(db.Integer, primary_key=True)
    start_date = db.Column(db.TIMESTAMP(timezone=True))
    end_date = db.Column(db.TIMESTAMP(timezone=True))

    def __repr__(self):  # pragma: no cover
        return (f"<{self.__class__.__name__}: {self.id}, "
                f"start_date: {self.start_date}, end_date: {self.end_date}>")

    @classmethod
    def get_current_cycle(cls, date=None):
        """Helper method to get current billing cycle of given date

        Args:
            date (date): date to get billing cycle for

        Returns:
            object: billing cycle object, if any

        """
        if not date:
            date = datetime.now()
        return cls.query.filter(cls.start_date <= date,
                                cls.end_date > date).first()
Example #3
0
class Subscription(db.Model):
    """Model class to represent ATT subscriptions"""

    __tablename__ = "subscriptions"

    id = db.Column(db.Integer, primary_key=True)
    phone_number = db.Column(db.String(10))
    status = db.Column(ENUM(SubscriptionStatus), default=SubscriptionStatus.new)
    activation_date = db.Column(db.TIMESTAMP(timezone=True), nullable=True)
    expiry_date = db.Column(db.TIMESTAMP(timezone=True), nullable=True)

    plan_id = db.Column(db.String(30), db.ForeignKey("plans.id"), nullable=False)
    plan = db.relationship("Plan", foreign_keys=[plan_id], lazy="select")

    def __repr__(self):  # pragma: no cover
        return (
            f"<{self.__class__.__name__}: {self.id} ({self.status}), "
            f"phone_number: {self.phone_number or '[no phone number]'}, ",
            f"plan: {self.plan_id}>"
        )

    @classmethod
    def get_subscriptions(cls, **kwargs):
        """Gets a list of Subscription objects using given kwargs

        Generates query filters from kwargs param using base class method

        Args:
            kwargs: key value pairs to apply as filters

        Returns:
            list: objects returned from query result

        """
        return cls.query.filter(**kwargs).all()
class Versions(db.Model):
    """Model to represent versions table for subscriptions"""
    __tablename__ = "versions"

    id = db.Column(db.Integer, primary_key=True)
    subscription_id = db.Column(db.Integer,
                                db.ForeignKey("subscriptions.id"),
                                nullable=False)
    plan_id = db.Column(db.Integer, db.ForeignKey("plans.id"), nullable=False)

    effective_date_start = db.Column(db.TIMESTAMP(timezone=True))
    effective_date_end = db.Column(db.TIMESTAMP(timezone=True))
    creation_date = db.Column(db.TIMESTAMP(timezone=True))

    subscription = db.relationship("Subscription",
                                   foreign_keys=[subscription_id],
                                   lazy="select",
                                   back_populates="versions")
    plan = db.relationship("Plan", foreign_keys=[plan_id], lazy="select")

    def __repr__(self):
        return (
            f"<{self.__class__.__name__}: {self.id}, "
            f"effective_date_start: {self.effective_date_start}, effective_date_end: {self.effective_date_end}>, "
            f"<subscription: {self.subscription_id}>, "
            f"<plan: {self.plan_id}>")
Example #5
0
class ATTPlanVersion(db.Model):
    """Model class to represent ATT plan version

    Custom versioning class to keep track of plans enabled ATT side
    """
    __tablename__ = "att_plan_versions"
    id = db.Column(db.Integer, primary_key=True)
    subscription_id = db.Column(
        db.Integer, db.ForeignKey("subscriptions.id"), nullable=False
    )
    subscription = db.relationship(
        "Subscription", back_populates="att_plan_versions", lazy="select"
    )
    plan_id = db.Column(
        db.String(30), db.ForeignKey("plans.id"), nullable=False
    )
    plan = db.relationship("Plan", foreign_keys=[plan_id], lazy="select")

    start_effective_date = db.Column(db.TIMESTAMP(timezone=True), nullable=False)
    end_effective_date = db.Column(db.TIMESTAMP(timezone=True), nullable=False)

    mb_available = db.Column(db.BigInteger)

    def __repr__(self):  # pragma: no cover
        return (
            f"<{self.__class__.__name__}: {self.subscription_id}, "
            f"{str(self.plan_id)} ({self.start_effective_date} - "
            f"{self.end_effective_date}) "
        )
class Subscription(db.Model):
    """Model class to represent ATT subscriptions"""

    __tablename__ = "subscriptions"

    id = db.Column(db.Integer, primary_key=True)
    phone_number = db.Column(db.String(10))
    status = db.Column(ENUM(SubscriptionStatus),
                       default=SubscriptionStatus.new)
    activation_date = db.Column(db.TIMESTAMP(timezone=True), nullable=True)
    expiry_date = db.Column(db.TIMESTAMP(timezone=True), nullable=True)

    plan_id = db.Column(db.String(30),
                        db.ForeignKey("plans.id"),
                        nullable=False)
    plan = db.relationship("Plan", foreign_keys=[plan_id], lazy="select")
    service_codes = db.relationship(
        "ServiceCode",
        secondary=subscriptions_service_codes,
        primaryjoin=
        "Subscription.id==subscriptions_service_codes.c.subscription_id",
        secondaryjoin=
        "ServiceCode.id==subscriptions_service_codes.c.service_code_id",
        back_populates="subscriptions",
        cascade="all,delete",
        lazy="subquery")

    data_usages = db.relationship(DataUsage, back_populates="subscription")

    def __repr__(self):  # pragma: no cover
        return (f"<{self.__class__.__name__}: {self.id} ({self.status}), "
                f"phone_number: {self.phone_number or '[no phone number]'}, ",
                f"plan: {self.plan_id}>")

    @classmethod
    def get_subscriptions(cls, **kwargs):
        """Gets a list of Subscription objects using given kwargs

        Generates query filters from kwargs param using base class method

        Args:
            kwargs: key value pairs to apply as filters

        Returns:
            list: objects returned from query result

        """
        return cls.query.filter(**kwargs).all()

    @property
    def service_code_names(self):
        """Helper property to return names of active service codes"""
        return [code.name for code in self.service_codes]
Example #7
0
class SubscriptionServiceChange(db.Model):
    """Model class to keep track of exact datetime when Subscription
    service codes were changed (added/removed).
    """
    __tablename__ = "subscription_service_changes"

    change_id = db.Column("id", db.Integer, primary_key=True)
    service_code_id = db.Column(db.Integer,
                                db.ForeignKey("service_codes.id"),
                                nullable=False)
    subscription_id = db.Column(db.Integer,
                                db.ForeignKey("subscriptions.id"),
                                nullable=False)
    change_date = db.Column(db.TIMESTAMP(timezone=True),
                            server_default=db.func.now())
    event_type = db.Column(db.Enum("added", "removed"), nullable=False)

    subscription = db.relationship(Subscription,
                                   foreign_keys=[subscription_id],
                                   lazy="select")
    service_code = db.relationship(ServiceCode,
                                   foreign_keys=[service_code_id],
                                   lazy="select")

    def __repr__(self):  # pragma: no cover
        return (
            f"<{self.__class__.__name__}: {self.change_id} "
            f"service: {self.service_code_id} was {self.event_type.value} at "
            f"{self.change_date}, subscription: {self.subscription_id}>")
class SubscriptionVersion(db.Model):
    """Model to represent versioning table for subscriptions"""

    __tablename__ = "subscriptions_versions"

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

    subscription_id = db.Column(db.Integer,
                                db.ForeignKey("subscriptions.id"),
                                nullable=False)
    subscription = db.relationship("Subscription",
                                   foreign_keys=[subscription_id],
                                   lazy="select",
                                   back_populates="versions")

    plan_id = db.Column(db.Integer, db.ForeignKey("plans.id"), nullable=False)
    plan = db.relationship("Plan", foreign_keys=[plan_id], lazy="select")

    date_start = db.Column(db.TIMESTAMP(timezone=True))
    date_end = db.Column(db.TIMESTAMP(timezone=True))
    date_created = db.Column(db.TIMESTAMP(timezone=True))
class Subscription(db.Model):
    """Model class to represent ATT subscriptions"""

    __tablename__ = "subscriptions"

    id = db.Column(db.Integer, primary_key=True)
    phone_number = db.Column(db.String(10))
    status = db.Column(ENUM(SubscriptionStatus),
                       default=SubscriptionStatus.new)
    activation_date = db.Column(db.TIMESTAMP(timezone=True), nullable=True)
    expiry_date = db.Column(db.TIMESTAMP(timezone=True), nullable=True)

    plan_id = db.Column(db.String(30),
                        db.ForeignKey("plans.id"),
                        nullable=False)
    plan = db.relationship("Plan", foreign_keys=[plan_id], lazy="select")
    service_codes = db.relationship(
        "ServiceCode",
        secondary=subscriptions_service_codes,
        primaryjoin=
        "Subscription.id==subscriptions_service_codes.c.subscription_id",
        secondaryjoin=
        "ServiceCode.id==subscriptions_service_codes.c.service_code_id",
        back_populates="subscriptions",
        cascade="all,delete",
        lazy="subquery")

    data_usages = db.relationship(DataUsage, back_populates="subscription")
    versions = db.relationship("SubscriptionVersion",
                               back_populates="subscription",
                               order_by="SubscriptionVersion.date_created")

    def __repr__(self):  # pragma: no cover
        return (f"<{self.__class__.__name__}: {self.id} ({self.status}), "
                f"phone_number: {self.phone_number or '[no phone number]'}, ",
                f"plan: {self.plan_id}>")

    @classmethod
    def get_subscriptions(cls, **kwargs):
        """Gets a list of Subscription objects using given kwargs

        Generates query filters from kwargs param using base class method

        Args:
            kwargs: key value pairs to apply as filters

        Returns:
            list: objects returned from query result

        """
        return cls.query.filter_by(**kwargs).all()

    @property
    def service_code_names(self):
        """Helper property to return names of active service codes"""
        return [code.name for code in self.service_codes]

    @classmethod
    def get_subscriptions_in_cycle(cls, billing_cycle, subscription_id=None):
        """The function filters subscriptions that have versions within given billing cycle. If subscription_id
            argument has been passed the function returns queryset that is filtered with this id.

        Args:
            billing_cycle (BillingCycle): object of BillingCycle
            subscription_id (int, optional): id of Subscription object, default is None

        Returns:
            sqlalchemy.orm.query.Query: returns queryset for subscription that have versions withing given
                billing cycle with joined versions array to every subscription (ordered by date_created).
        """
        query = cls.query

        if subscription_id is not None:
            query = query.filter_by(id=subscription_id)

        query = query \
            .outerjoin(
                SubscriptionVersion,
                and_(
                    Subscription.id == SubscriptionVersion.subscription_id,
                    SubscriptionVersion.date_start >= billing_cycle.start_date,
                    SubscriptionVersion.date_end <= billing_cycle.end_date,
                ),
            ) \
            .options(contains_eager(cls.versions))

        return query
Example #10
0
class DataUsage(db.Model):
    """Model class to represent data usage record

    Note:
        A daily usage record is created for a subscription each day
        it is active, beginning at midnight UTC timezone.

    """
    __tablename__ = "data_usages"

    id = db.Column(db.Integer, primary_key=True)
    mb_used = db.Column(db.Float, default=0.0)
    from_date = db.Column(db.TIMESTAMP(timezone=True))
    to_date = db.Column(db.TIMESTAMP(timezone=True))

    subscription_id = db.Column(db.Integer,
                                db.ForeignKey("subscriptions.id"),
                                nullable=False)
    subscription = db.relationship("Subscription",
                                   back_populates="data_usages")

    def __repr__(self):  # pragma: no cover
        return (
            f"<{self.__class__.__name__}: {self.id} ({self.subscription_id}) "
            f"{self.mb_used} MB {self.from_date} - {self.to_date}>")

    @classmethod
    def get_statistics_for_a_subscription(cls, sid, date=None):
        """Helper method to get data usage on billing cycle of given date

        Args:
            sid (int): subscription id to look up
            date (date): date to get billing cycle for

        Returns:
            dict: {
                over_limit,      true if data usage is over plan limit
                amount_used,     the amount of data used in megabytes
                amount_left      amount used over plan limit in megabytes
            }

        """
        cycle = BillingCycle.get_current_cycle(date)
        default_usage = cls.query \
            .filter(cls.subscription_id == sid) \
            .first()
        subscription = default_usage is not None and default_usage.subscription
        plan = default_usage is not None and default_usage.subscription.plan

        # get total amount used in current cycle
        query = []
        query.append(cls.subscription_id == sid)

        if cycle is not None:
            query.append(cls.from_date >= cycle.start_date)
            query.append(cls.to_date <= cycle.end_date)

        amount = cls.query \
            .with_entities(func.sum(cls.mb_used)) \
            .filter(*query) \
            .scalar()

        if plan and subscription and subscription.status != SubscriptionStatus.new:
            mb_available = plan.mb_available - amount
        else:
            mb_available = 0

        return {
            "over_limit": mb_available <= 0,
            "amount_used": amount,
            "amount_left": mb_available if mb_available > 0 else 0
        }