Exemple #1
0
class Postage(DB.Model):
    """Model for representing postage for one or more tickets."""
    __tablename__ = 'postage'
    object_id = DB.Column(DB.Integer, primary_key=True)

    paid = DB.Column(DB.Boolean(), default=False, nullable=False)
    postage_type = DB.Column(DB.Unicode(50), nullable=False)
    price = DB.Column(DB.Integer(), nullable=False)
    address = DB.Column(DB.Unicode(200), nullable=True)
    cancelled = DB.Column(DB.Boolean(), default=False, nullable=False)
    posted = DB.Column(DB.Boolean(), default=False, nullable=False)

    tickets = DB.relationship('Ticket',
                              secondary=POSTAGE_TICKET_LINK,
                              backref=DB.backref('postage',
                                                 lazy=False,
                                                 uselist=False),
                              lazy='dynamic')

    owner_id = DB.Column(DB.Integer,
                         DB.ForeignKey('user.object_id'),
                         nullable=True)
    owner = DB.relationship('User',
                            backref=DB.backref('postage_entries',
                                               lazy='dynamic'))

    def __init__(self, owner, postage_option, tickets, address=None):
        self.owner = owner
        self.postage_type = postage_option.name
        self.price = postage_option.price
        self.address = address
        self.tickets = tickets

        if self.price == 0:
            self.paid = True
class AdminFee(DB.Model):
    """Model for representing an administration fee."""
    __tablename__ = 'admin_fee'

    object_id = DB.Column(DB.Integer, primary_key=True)

    amount = DB.Column(DB.Integer(), nullable=False)
    reason = DB.Column(DB.UnicodeText(), nullable=False)
    paid = DB.Column(DB.Boolean(), nullable=False, default=False)

    charged_to_id = DB.Column(DB.Integer,
                              DB.ForeignKey('user.object_id'),
                              nullable=False)
    charged_to = DB.relationship('User',
                                 backref=DB.backref('admin_fees_charged',
                                                    lazy='dynamic'),
                                 foreign_keys=[charged_to_id])

    charged_by_id = DB.Column(DB.Integer,
                              DB.ForeignKey('user.object_id'),
                              nullable=False)
    charged_by = DB.relationship('User',
                                 backref=DB.backref('admin_fees_created',
                                                    lazy='dynamic'),
                                 foreign_keys=[charged_by_id])

    def __init__(self, amount, reason, charged_to, charged_by):
        self.amount = amount
        self.reason = reason
        self.charged_to = charged_to
        self.charged_by = charged_by

    def __repr__(self):
        return '<AdminFee {0}: £{1}>'.format(self.object_id,
                                             self.amount_pounds)

    @property
    def amount_pounds(self):
        """Get the fee amount as a string of pounds and pence."""
        amount = '{0:03d}'.format(self.amount)
        return amount[:-2] + '.' + amount[-2:]

    def mark_as_paid(self):
        """Email the creator of this fee when it has been paid."""
        self.paid = True

        if "Ticket Upgrade:" in self.reason:
            possible_tickets = [int(x) for x in self.reason[16:].split(',')]
            for ticket in self.charged_to.active_tickets:
                if ticket.object_id in possible_tickets:
                    ticket.add_note('Upgrade')
        else:
            APP.email_manager.send_template(self.charged_by.email,
                                            'Administration fee paid.',
                                            'admin_fee_paid.email',
                                            fee=self)
class PurchaseGroup(DB.Model):
    """Model for a purchase group to allow pooling allowances."""
    __tablename__ = 'purchase_group'
    object_id = DB.Column(DB.Integer, primary_key=True)

    code = DB.Column(DB.Unicode(10), unique=True, nullable=False)

    leader_id = DB.Column(DB.Integer,
                          DB.ForeignKey('user.object_id'),
                          nullable=False)
    leader = DB.relationship('User', foreign_keys=[leader_id])

    members = DB.relationship('User',
                              secondary=GROUP_MEMBER_LINK,
                              backref=DB.backref('purchase_group',
                                                 lazy=False,
                                                 uselist=False),
                              lazy='dynamic')

    disbanded = DB.Column(DB.Boolean(), default=False, nullable=False)
    purchased = DB.Column(DB.Boolean(), default=False, nullable=False)

    def __init__(self, leader):
        self.leader = leader
        self.members = [leader]

        self.code = util.generate_key(10)

    def __repr__(self):
        return '<PurchaseGroup({0}): {1} tickets, £{2}>'.format(
            self.object_id, self.total_requested, self.total_value_pounds)

    @property
    def total_value(self):
        """Get the total value of tickets requested by this group in pence."""
        return sum(request.value for request in self.requests)

    @property
    def total_value_pounds(self):
        """Get the total value of this group in pounds and pence."""
        value = '{0:03d}'.format(self.total_value)

        return value[:-2] + '.' + value[-2:]

    @property
    def total_requested(self):
        """Get the total number of tickets requested by this group."""
        return sum(request.number_requested for request in self.requests)

    @property
    def total_guest_tickets_requested(self):
        """Get the total number of guest tickets requested by this group."""
        return sum(request.number_requested for request in self.requests
                   if request.ticket_type.counts_towards_guest_limit)

    @staticmethod
    def get_by_code(code):
        """Get a purchase group object by its code."""
        group = PurchaseGroup.query.filter(PurchaseGroup.code == code).first()

        if not group:
            return None

        return group
Exemple #4
0
class Ticket(DB.Model):
    """Model for tickets."""
    __tablename__ = 'ticket'
    object_id = DB.Column(DB.Integer, primary_key=True)

    ticket_type = DB.Column(DB.Unicode(50), nullable=False)

    paid = DB.Column(DB.Boolean(), default=False, nullable=False)
    entered = DB.Column(DB.Boolean(), default=False, nullable=False)
    cancelled = DB.Column(DB.Boolean(), default=False, nullable=False)

    price_ = DB.Column(DB.Integer(), nullable=False)
    note = DB.Column(DB.UnicodeText(), nullable=True)
    expires = DB.Column(DB.DateTime(), nullable=True)
    barcode = DB.Column(DB.Unicode(20), unique=True, nullable=True)
    claim_code = DB.Column(
        DB.Unicode(17),  # 3 groups of 5 digits separated by dashes
        nullable=True)
    claims_made = DB.Column(DB.Integer, nullable=False, default=0)
    owner_id = DB.Column(DB.Integer,
                         DB.ForeignKey('user.object_id'),
                         nullable=False)
    owner = DB.relationship('User',
                            backref=DB.backref('tickets',
                                               lazy='dynamic',
                                               order_by=b'Ticket.cancelled'),
                            foreign_keys=[owner_id])

    holder_id = DB.Column(DB.Integer,
                          DB.ForeignKey('user.object_id'),
                          nullable=True)
    holder = DB.relationship('User',
                             backref=DB.backref('held_ticket', uselist=False),
                             foreign_keys=[holder_id])
    holder_name = DB.Column(DB.String(60), nullable=True, default="Unassigned")

    def __init__(self, owner, ticket_type, price):
        self.owner = owner
        self.ticket_type = ticket_type
        self.price = price

        self.expires = (datetime.datetime.utcnow() +
                        APP.config['TICKET_EXPIRY_TIME'])

        self.claim_code = '-'.join(
            util.generate_key(5, string.digits)
            for _ in xrange(3)).decode('utf-8')

    def __repr__(self):
        return '<Ticket {0} owned by {1} ({2})>'.format(
            self.object_id, self.owner.full_name, self.owner.object_id)

    def can_be_cancelled(self):
        if datetime.datetime.utcnow() < datetime.datetime(
                2018, 5, 11) and not self.cancelled and not self.paid:
            return True
        return False

    def can_be_resold(self):
        return False

    def can_be_claimed(self):
        if self.status == 'Awaiting ticket holder.':
            return True
        return False

    def can_be_reclaimed(self):
        # if self.paid and datetime.datetime.utcnow()<datetime.datetime(2018,5,11) and not self.cancelled and self.holder_id==self.owner_id:
        #     return True
        return False

    def has_holder(self):
        if not self.holder_id == None:
            return True
        return False

    def can_be_paid_for(self):
        if self.paid == 0:
            return True
        return False

    def can_be_collected(self):
        if self.paid:
            return True
        return False

    def is_assigned(self):
        if self.holder_name == 'Unassigned':
            return False
        return True

    @staticmethod
    def get_by_claim_code(code):
        return Ticket.query.filter_by(claim_code=code).first()

    @property
    def price_pounds(self):
        """Get the price of this ticket as a string of pounds and pence."""
        price = '{0:03d}'.format(self.price)
        return price[:-2] + '.' + price[-2:]

    @property
    def transaction(self):
        """Get the transaction this ticket was paid for in."""
        for transaction_item in self.transaction_items:
            if transaction_item.transaction.paid:
                return transaction_item.transaction

        return None

    @property
    def payment_method(self):
        """Get the payment method for this ticket."""
        transaction = self.transaction

        if transaction:
            return transaction.payment_method
        else:
            return 'Unknown Payment Method'

    @property
    def price(self):
        """Get the price of the ticket."""
        return self.price_

    @property
    def status(self):
        """Get the status of this ticket."""
        if self.cancelled:
            return 'Cancelled.'
        elif not self.paid:
            return 'Awaiting payment. Expires {0}.'.format(
                self.expires.strftime('%H:%M %d/%m/%Y'))
        elif self.entered:
            return 'Used for entry.'
        elif self.collected and self.holder:
            return 'Ticket Sent to {0}.'.format(self.holder.full_name)
        elif self.collected:
            return 'Collected as {0}.'.format(self.barcode)
        # elif self.holder is None:
        #     return 'Awaiting ticket holder.'
        elif self.holder_name == 'Unassigned':
            return 'Awaiting ticket holder.'
        elif APP.config['REQUIRE_USER_PHOTO']:
            if not self.holder.photo.verified:
                return 'Awaiting verification of holder photo.'
        else:
            # return 'Held by {0}.'.format(self.holder.full_name)
            return 'Assigned to {0}.'.format(self.holder_name)

    @price.setter
    def price(self, value):
        """Set the price of the ticket."""
        self.price_ = max(value, 0)

        if self.price_ == 0:
            self.mark_as_paid()

    @hybrid.hybrid_property
    def collected(self):
        """Has this ticket been assigned a barcode."""
        return self.barcode != None  # pylint: disable=singleton-comparison

    def mark_as_paid(self):
        """Mark the ticket as paid, and clear any expiry."""
        self.paid = True
        self.expires = None

    def add_note(self, note):
        """Add a note to the ticket."""
        if not note.endswith('\n'):
            note = note + '\n'

        if self.note is None:
            self.note = note
        else:
            self.note = self.note + note

    @staticmethod
    def count():
        """How many tickets have been sold."""
        # TODO
        return Ticket.query.filter(Ticket.cancelled == False).count()  # pylint: disable=singleton-comparison

    @staticmethod
    def write_csv_header(csv_writer):
        """Write the header of a CSV export file."""
        csv_writer.writerow([
            'Ticket ID',
            'Ticket Type',
            'Paid',
            'Collected',
            'Entered',
            'Cancelled',
            'Price (Pounds)',
            'Holder\'s Name',
            'Notes',
            'Expires',
            'Barcode',
            'Owner\' User ID',
            'Owner\'s Name',
        ])

    def write_csv_row(self, csv_writer):
        """Write this object as a row in a CSV export file."""
        csv_writer.writerow([
            self.object_id,
            self.ticket_type,
            'Yes' if self.paid else 'No',
            'Yes' if self.collected else 'No',
            'Yes' if self.entered else 'No',
            'Yes' if self.cancelled else 'No',
            self.price_pounds,
            self.holder.full_name.encode('utf-8')
            if self.holder is not None else 'N/A',
            self.note,
            self.expires.strftime('%Y-%m-%d %H:%M:%S')
            if self.expires is not None else 'N/A',
            self.barcode if self.barcode is not None else 'N/A',
            self.owner_id,
            self.owner.full_name.encode('utf-8'),
        ])
Exemple #5
0
class Battels(DB.Model):
    """Model for battels charges for current students."""
    __tablename__ = 'battels'
    object_id = DB.Column(DB.Integer, primary_key=True)

    battels_id = DB.Column(DB.Unicode(10), unique=True, nullable=True)
    email = DB.Column(DB.Unicode(120), unique=True, nullable=True)
    title = DB.Column(DB.Unicode(10), nullable=True)
    surname = DB.Column(DB.Unicode(60), nullable=True)
    forenames = DB.Column(DB.Unicode(60), nullable=True)
    michaelmas_charge = DB.Column(DB.Integer(), default=0, nullable=False)
    hilary_charge = DB.Column(DB.Integer(), default=0, nullable=False)
    manual = DB.Column(DB.Boolean(), default=False, nullable=False)

    def __init__(self,
                 battelsid=None,
                 email=None,
                 title=None,
                 surname=None,
                 forenames=None,
                 manual=False):
        self.battelsid = battelsid
        self.email = email
        self.title = title
        self.surname = surname
        self.forenames = forenames
        self.manual = manual

    def __repr__(self):
        return '<Battels {0}: {1}>'.format(self.object_id, self.battelsid)

    @property
    def michaelmas_charge_pounds(self):
        """Get the amount charged in Michaelmas as a pounds/pence string."""
        michaelmas_charge = '{0:03d}'.format(self.michaelmas_charge)
        return michaelmas_charge[:-2] + '.' + michaelmas_charge[-2:]

    @property
    def hilary_charge_pounds(self):
        """Get the amount charged in Hilary as a pounds/pence string."""
        hilary_charge = '{0:03d}'.format(self.hilary_charge)
        return hilary_charge[:-2] + '.' + hilary_charge[-2:]

    def charge(self, amount, term):
        """Apply a charge to this battels account."""
        if term == 'MTHT':
            # Integer division fails for negative numbers (i.e. refunds), as the
            # number is rounded the wrong way. Instead, we do floating point
            # division, and truncate.
            half = int(amount / 2.0)

            self.michaelmas_charge += half
            self.hilary_charge += amount - half
        elif term == 'MT':
            self.michaelmas_charge += amount
        elif term == 'HT':
            self.hilary_charge += amount
        else:
            raise ValueError(
                'Term "{0}" cannot be charged to battels'.format(term))

    def refund(self, amount, term):
        """Refund a ticket and mark it as cancelled."""
        if APP.config['CURRENT_TERM'] == 'MT':
            if term == 'MTHT':
                half = amount // 2

                self.michaelmas_charge -= half
                self.hilary_charge -= amount - half
            elif term == 'MT':
                self.michaelmas_charge -= amount
            elif term == 'HT':
                self.hilary_charge -= amount
        elif APP.config['CURRENT_TERM'] == 'HT':
            self.hilary_charge -= amount
        else:
            raise ValueError(
                'Can\'t refund battels tickets in the current term')
Exemple #6
0
class Voucher(DB.Model):
    """Model for a discount voucher."""
    __tablename__ = 'voucher'
    object_id = DB.Column(DB.Integer, primary_key=True)

    code = DB.Column(DB.Unicode(30), nullable=False)
    expires = DB.Column(DB.DateTime(), nullable=True)
    discount_type = DB.Column(DB.Enum('Fixed Price', 'Fixed Discount',
                                      'Percentage Discount'),
                              nullable=False)
    discount_value = DB.Column(DB.Integer(), nullable=False)
    applies_to = DB.Column(DB.Enum('Ticket', 'Transaction'), nullable=False)
    single_use = DB.Column(DB.Boolean(), nullable=False)
    used = DB.Column(DB.Boolean(), default=False, nullable=True)

    used_by_id = DB.Column(DB.Integer,
                           DB.ForeignKey('user.object_id'),
                           nullable=True)
    used_by = DB.relationship('User',
                              backref=DB.backref('vouchers_used',
                                                 lazy='dynamic'))

    def __init__(self, code, expires, discount_type, discount_value,
                 applies_to, single_use):
        if discount_type not in [
                'Fixed Price', 'Fixed Discount', 'Percentage Discount'
        ]:
            raise ValueError(
                '{0} is not a valid discount type'.format(discount_type))

        if applies_to not in ['Ticket', 'Transaction']:
            raise ValueError(
                '{0} is not a valid application'.format(applies_to))

        self.code = code
        self.discount_type = discount_type
        self.discount_value = discount_value
        self.applies_to = applies_to
        self.single_use = single_use

        if isinstance(expires, datetime.timedelta):
            self.expires = datetime.datetime.utcnow() + expires
        else:
            self.expires = expires

    def __repr__(self):
        return '<Voucher: {0}/{1}>'.format(self.object_id, self.code)

    @staticmethod
    def get_by_code(code):
        """Get an Announcement object by a voucher code."""
        return Voucher.query.filter(Voucher.code == code).first()

    def apply(self, tickets, user):
        """Apply the voucher to a set of tickets.

        Checks if the voucher can be used, and applies its discount to the
        tickets.

        Args:
            tickets: (list(Ticket)) list of tickets to apply the voucher to
            user: (User) user who is using the voucher

        Returns:
            (bool, list(tickets), str/None) whether the voucher was applied, the
            mutated tickets, and an error message
        """
        if self.single_use and self.used:
            return (False, tickets, 'Voucher has already been used.')

        if (self.expires is not None
                and self.expires < datetime.datetime.utcnow()):
            return (False, tickets, 'Voucher has expired.')

        self.used = True
        if self.single_use:
            self.used_by = user

        if self.applies_to == 'Ticket':
            tickets[0] = self.apply_to_ticket(tickets[0])
            return (True, tickets, None)
        else:
            return (True, [self.apply_to_ticket(t) for t in tickets], None)

    def apply_to_ticket(self, ticket):
        """Apply the voucher to a single ticket.

        Recalculates the price of the ticket, and notes on the ticket that a
        voucher was used

        Args:
            ticket: (Ticket) the ticket to apply the voucher to

        Returns:
            (ticket) the mutated ticket
        """
        if self.discount_type == 'Fixed Price':
            ticket.price = self.discount_value
        elif self.discount_type == 'Fixed Discount':
            ticket.price = ticket.price - self.discount_value
        else:
            ticket.price = ticket.price * (100 - self.discount_value) / 100

        ticket.add_note('Used voucher {0}/{1}'.format(self.object_id,
                                                      self.code))

        return ticket