Пример #1
0
class SubscriptionTable(BaseTable):
    """
    Houses storing information about a customer account.
    """
    __tablename__ = 'braintree_subscription'

    braintree_sub_id = db.Column(db.String, nullable=False)
    plan_id = db.Column(db.String, nullable=False, default='Basic0x01')
    date_started = db.Column(db.DateTime, nullable=False)
    date_ended = db.Column(db.DateTime, nullable=True)

    user_id = db.Column(
        db.String(36),
        db.ForeignKey('users.public_id'),
        nullable=False,
    )

    braintree_customer_id = db.Column(
        db.String(36),
        db.ForeignKey('braintree_customer.public_id'),
        nullable=False,
    )

    # House keeping stuff.
    is_deleted = db.Column(db.BOOLEAN, nullable=False, default=False)

    def __init__(self,
                 bt_customer_id,
                 user_id,
                 sub_id,
                 *,
                 plan_id='Basic0x01'):
        super().__init__()

        self.braintree_customer_id = bt_customer_id
        self.braintree_sub_id = sub_id
        self.user_id = user_id
        self.plan_id = plan_id
        self.date_started = datetime.utcnow()

    def __repr__(self):
        return 'User: {0} -- PlanID: {1} -- CustomerID: {2}'.format(
            self.user_id,
            self.plan_id,
            self.braintree_customer_id,
        )
class ScheduleTable(BaseTable):
    """
    Houses the schedules of users.
    """
    __tablename__ = 'schedules'

    # Foreign keys.
    user_id = db.Column(
        db.String(36),
        db.ForeignKey('users.public_id'),
        nullable=False
    )

    # Actual schedule stuff.
    utc_duration = db.Column(TSRANGE, nullable=False)
    local_duration = db.Column(TSTZRANGE, nullable=False)

    day_number = db.Column(db.SmallInteger, nullable=False)
    month_number = db.Column(db.SmallInteger, nullable=False)

    # tz stuff
    local_tz = db.Column(db.String, nullable=False)

    ExcludeConstraint(('utc_duration', '&&'))
    ExcludeConstraint(('local_duration', '&&'))

    @property
    def local_tz_open(self): return self.local_duration.lower

    @property
    def local_tz_end(self): return self.local_duration.upper

    @property
    def utc_open(self): return self.utc_duration.lower

    @property
    def utc_end(self): return self.utc_duration.upper

    def __init__(self, open_date, end_date, user_id, local_tz):
        super().__init__()
        self.utc_duration = DateTimeRange(open_date, end_date)
        self.local_duration = DateTimeTZRange(
            self._localize(self.utc_duration.lower, local_tz),
            self._localize(self.utc_duration.upper, local_tz),
        )

        self.day_number = open_date.day
        self.month_number = open_date.month
        self.local_tz = local_tz

        self.user_id = str(user_id)

    def __repr__(self):
        return 'Open: {0} -> End: {1} -- For User: {2}'.format(
            self.utc_open,
            self.utc_end,
            self.user_id
        )
Пример #3
0
class PaymentTable(BaseTable):
    """
    Houses storing information pertinent to the master merchant.
    """
    __tablename__ = 'payments'

    submerchant_id = db.Column(
        db.Integer,
        db.ForeignKey('braintree_sub_merchant.id')
    )

    event_id = db.Column(
        db.Integer,
        db.ForeignKey('events.id')
    )

    base_amount = db.Column(db.DECIMAL, nullable=False)
    service_fee = db.Column(db.DECIMAL, nullable=False)
    total_price = db.Column(db.DECIMAL, nullable=False)

    def __init__(self, amount, service_fee, submerchant, event):
        super().__init__()

        self.base_amount = amount
        self.service_fee = service_fee
        self.total_price = event.total_price

        self.submerchant_id = submerchant.public_id
        self.event_id = event.public_id

    def __repr__(self):
        return 'Transaction {0} for submerchant {1} and event {2}'.format(
            self.public_id,
            self.submerchant_id,
            self.event_id
        )
class CustomerTable(BaseTable):
    """
    Houses storing information about a customer account.
    """
    __tablename__ = 'braintree_customer'

    braintree_customer_id = db.Column(db.String, nullable=False, unique=True)
    credit_card_token = db.Column(db.String, nullable=False, unique=True)
    first_name = db.Column(db.String(64), nullable=False)
    last_name = db.Column(db.String(64), nullable=False)

    user_id = db.Column(
        db.String(36),
        db.ForeignKey('users.public_id'),
        nullable=False,
    )

    # House keeping stuff.
    is_default = db.Column(db.BOOLEAN, nullable=False, default=False)
    is_deleted = db.Column(db.BOOLEAN, nullable=False, default=False)

    def __init__(self,
                 bt_customer_id,
                 cc_token,
                 first_name,
                 last_name,
                 user_id,
                 *,
                 is_default=False):
        super().__init__()

        self.braintree_customer_id = bt_customer_id
        self.credit_card_token = cc_token
        self.first_name = first_name
        self.last_name = last_name
        self.user_id = user_id
        self.is_default = is_default

    def __repr__(self):
        return 'User: {0} -- Default: {1} -- CustomerID: {2}'.format(
            self.user_id,
            self.is_default,
            self.braintree_customer_id,
        )
Пример #5
0
class SubmerchantTable(BaseTable):
    """
    Houses storing information relevant for submerchants.
    """
    __tablename__ = 'braintree_sub_merchant'

    master_merchant_id = db.Column(
        db.Integer, db.ForeignKey('braintree_master_merchant.id'))

    user_id = db.Column(
        db.String(36),
        db.ForeignKey('users.public_id'),
        nullable=False,
    )

    # House keeping stuff.
    is_deleted = db.Column(db.BOOLEAN, nullable=False, default=False)
    is_approved = db.Column(db.BOOLEAN, nullable=False, default=False)
    is_rejected = db.Column(db.Boolean, nullable=False, default=False)

    # Actual shit being put into braintree upon submerchant account creation.
    braintree_account_id = db.Column(db.String(24), nullable=False)

    service_fee_percent = db.Column(db.DECIMAL, nullable=False, default=.025)

    # Individual
    first_name = db.Column(db.String(64), nullable=False)
    last_name = db.Column(db.String(64), nullable=False)
    email = db.Column(db.String(64), nullable=False)
    date_of_birth = db.Column(db.DateTime, nullable=False)
    address_street_address = db.Column(db.String(128), nullable=False)
    address_locality = db.Column(db.String(32), nullable=False)
    address_region = db.Column(db.String(32), nullable=False)
    address_zip = db.Column(db.String(12), nullable=False)

    # Business stuff. Only required if user is registering as a business
    register_as_business = db.Column(db.Boolean, nullable=False, default=False)
    legal_name = db.Column(db.String(64))
    # NOTE: We do NOT store the tax_id.
    dba_name = db.Column(db.String(64))
    bus_address_street_address = db.Column(db.String(128))
    bus_address_locality = db.Column(db.String(32))
    bus_address_region = db.Column(db.String(32))
    bus_address_zip = db.Column(db.String(12))

    def __init__(self,
                 user_id,
                 account_id,
                 first_name,
                 last_name,
                 email,
                 date_of_birth,
                 address_street_address,
                 address_locality,
                 address_region,
                 address_zip,
                 register_as_business=False,
                 legal_name=None,
                 dba_name=None,
                 bus_address_street_address=None,
                 bus_address_locality=None,
                 bus_address_region=None,
                 bus_address_zip=None):
        super().__init__()

        self.user_id = user_id

        self.braintree_account_id = account_id
        self.first_name = first_name
        self.last_name = last_name
        self.email = email
        self.date_of_birth = date_of_birth
        self.address_street_address = address_street_address
        self.address_locality = address_locality
        self.address_region = address_region
        self.address_zip = address_zip

        if register_as_business:
            self.register_as_business = register_as_business
            self.legal_name = legal_name
            self.dba_name = dba_name
            self.bus_address_street_address = bus_address_street_address
            self.bus_address_locality = bus_address_locality
            self.bus_address_region = bus_address_region
            self.bus_address_zip = bus_address_zip

    def __repr__(self):
        return 'Account ID: {0} -- User Public ID: {1}'.format(
            self.braintree_account_id, self.user_id)
Пример #6
0
class AddressTable(BaseTable):
    """
    Houses the schedules of contact form submissions.
    """
    __tablename__ = 'addresses'

    first_name = db.Column(db.String, nullable=False)
    last_name = db.Column(db.String, nullable=False)

    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.id'),
                        nullable=False,
                        index=True)

    street_address = db.Column(db.String, nullable=False)
    extended_address = db.Column(db.String, nullable=True)
    # City
    locality = db.Column(db.String, nullable=False)
    # State
    region = db.Column(db.String, nullable=False)
    postal_code = db.Column(db.String, nullable=False)
    country_code_alpha2 = db.Column(db.String(2), nullable=False)

    is_default = db.Column(db.Boolean, nullable=False, default=False)
    is_deleted = db.Column(db.Boolean, nullable=False, default=False)

    # Indexes
    db.Index('idx_default_addresses', is_default)
    db.Index('idx_locality', locality)
    db.Index('idx_region', region)
    db.Index('idx_postal_code', postal_code)
    db.Index('idx_country_code', country_code_alpha2)

    def __init__(self,
                 user_id,
                 first_name,
                 last_name,
                 street_address,
                 locality,
                 region,
                 postal_code,
                 country_code_alpha2,
                 is_default=False,
                 extended_address=None):
        super().__init__()
        try:
            self.user_id = UserDAO().get(user_id).id
        except DAOException as e:
            logging.error(
                'Failed to get user by pub ID {0} w/ exc of {1}'.format(
                    user_id,
                    e,
                ))
            raise TableException('Failed to find requested user.')
        except AttributeError as e:
            logging.error('Requested user ({0}) does not exist when '
                          'creating new address.'.format(user_id, ))

            raise TableException(e)

        self.first_name = first_name
        self.last_name = last_name
        self.street_address = street_address
        self.locality = locality
        self.region = region
        self.postal_code = postal_code
        self.country_code_alpha2 = country_code_alpha2
        self.is_default = is_default

        if extended_address is not None and extended_address != '':
            self.extended_address = extended_address

    def __repr__(self):
        return """
            First name: {0} Last name: {1} Default: {2} Locality: {3}
            Region: {4} Country2: {5}
            """.format(
            self.first_name,
            self.last_name,
            self.is_default,
            self.locality,
            self.region,
            self.country_code_alpha2,
        )
Пример #7
0
class EventTable(BaseTable):
    """
    Houses the schedules of users.
    """
    __tablename__ = 'events'

    # Foreign keys.
    scheduling_user_id = db.Column(db.String(36),
                                   db.ForeignKey('users.public_id'),
                                   nullable=False,
                                   index=True)

    scheduled_user_id = db.Column(db.String(36),
                                  db.ForeignKey('users.public_id'),
                                  nullable=False,
                                  index=True)

    utc_duration = db.Column(TSTZRANGE, nullable=False)
    scheduled_tz_duration = db.Column(TSTZRANGE, nullable=False)
    scheduling_tz_duration = db.Column(TSTZRANGE, nullable=False)

    day_number = db.Column(db.SmallInteger, nullable=False)
    month_number = db.Column(db.SmallInteger, nullable=False)

    duration = db.Column(db.Integer, nullable=False)

    total_price = db.Column(db.DECIMAL, nullable=False)
    service_fee = db.Column(db.DECIMAL, nullable=False)

    # TODO(ian): Mark this with a payment transaction ID.
    transaction_id = db.Column(db.Integer, nullable=True)

    notes = db.Column(db.String(512), nullable=True)

    ExcludeConstraint(('utc_duration', '&&'))
    ExcludeConstraint(('scheduled_tz_duration', '&&'))
    ExcludeConstraint(('scheduling_tz_duration', '&&'))

    @property
    def utc_start(self):
        return self.utc_duration.lower

    @property
    def utc_end(self):
        return self.utc_duration.upper

    @property
    def scheduled_tz_start(self):
        return self.scheduled_tz_duration.lower

    @property
    def scheduled_tz_end(self):
        return self.scheduled_tz_duration.upper

    @property
    def scheduling_tz_start(self):
        return self.scheduling_tz_duration.lower

    @property
    def scheduling_tz_end(self):
        return self.scheduling_tz_duration.upper

    def __init__(self,
                 start_time,
                 end_time,
                 scheduling,
                 scheduled,
                 notes=None):
        super().__init__()

        scheduling_user_info = db.session.query(User).filter_by(
            public_id=scheduling).first()

        scheduled_user_info = db.session.query(User).filter_by(
            public_id=scheduled).first()

        self.utc_duration = DateTimeTZRange(start_time, end_time)
        self._set_duration_for_user(scheduling_user_info, is_scheduling=True)
        self._set_duration_for_user(scheduled_user_info, is_scheduling=False)

        if scheduling_user_info is None or scheduled_user_info is None:
            raise ModelException('Invalid requested users.')

        self.scheduling_user_id = str(scheduling)
        self.scheduled_user_id = str(scheduled)

        self.day_number = start_time.day
        self.month_number = self.utc_duration.lower.month

        self.duration = ((end_time - start_time).seconds // 60)
        if self.duration != 60:
            if self.duration >= 60 and self.duration % 60 == 0:
                pass
            else:
                self.duration %= 60

        self.total_price = self.calculate_total_price(
            self.duration,
            scheduled_user_info,
        )

        submerchant_info = db.session.query(SubmerchantTable).filter_by(
            user_id=scheduled).first()

        self.service_fee = self.calculate_service_fee(submerchant_info)

        if notes is not None:
            self.notes = notes

    def __repr__(self):
        return 'Start: {0} - End: {1} - Duration (minutes): {2} - Price {3}'.\
            format(
                self.utc_start,
                self.utc_end,
                self.duration,
                self.total_price
            )

    def calculate_total_price(self, duration, scheduled_user):
        """
        Calculates the total price for the event.

        :param int duration:
        :param UserTable scheduled_user: The user which si being scheduled.
        :rtype: float
        :return: The total price for the duration.
        """
        if scheduled_user is None:
            raise TableException("Invalid user to schedule.")

        if scheduled_user.is_premium:
            if duration not in [5, 15, 30, 45, 60]:
                if duration >= 60 and duration % 60 == 0:
                    pass
                else:
                    raise TableException(
                        "Invalid event duration. Premium users can only accept"
                        " durations of 5, 15, 30, or 60 minutes.")
        else:
            if duration not in [60]:
                if duration >= 60 and duration % 60 == 0:
                    pass
                else:
                    raise TableException(
                        "Invalid event duration. Non-premium users can only "
                        "accept durations of 60 minutes.")

        if scheduled_user.is_premium:
            price_lookup = {
                5: scheduled_user.five_min_price,
                15: scheduled_user.fifteen_min_price,
                30: scheduled_user.thirty_min_price,
                65: scheduled_user.sixty_min_price,
            }

            return price_lookup[duration] + decimal.Decimal(price_lookup[
                duration]) * decimal.Decimal(0.026) + decimal.Decimal(0.2)
        else:
            if scheduled_user.sixty_min_price is None:
                logging.error('Attempted to create event for user {0}'
                              ', but failed because no sixty_min_price'.format(
                                  scheduled_user.public_id, ))
                raise TableException(
                    'Invalid user to be scheduled. User has no price set'
                    'for 60 minutes.')

            return scheduled_user.sixty_min_price + decimal.Decimal(
                scheduled_user.sixty_min_price) * decimal.Decimal(
                    0.026) + decimal.Decimal(0.2)

    def calculate_service_fee(self, submerchant):
        return self.total_price * submerchant.service_fee_percent

    def _set_duration_for_user(self, user_info, is_scheduling=True):
        localized_start = self._localize(self.utc_duration.lower,
                                         user_info.local_tz)

        localized_end = self._localize(self.utc_duration.upper,
                                       user_info.local_tz)

        if is_scheduling:
            self.scheduling_tz_duration = DateTimeTZRange(
                localized_start,
                localized_end,
            )
        else:
            self.scheduled_tz_duration = DateTimeTZRange(
                localized_start,
                localized_end,
            )