class Availability(Base): """ Here you can set each availability that any employee has. Each availability is tied to an employee and it is also tied to a location. So for example, you could add an availability of employee 3 at location 2, from 9:00-5:00 on Tuesday. """ location_id = db.Column(db.Integer, db.ForeignKey('location.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) day = db.Column(db.Integer) # Which day of the week start = db.Column(db.Time, nullable=False) # What time your availability starts length = db.Column(db.Integer) # How many minutes you are available for def __init__(self, location_id, user_id, day, hour, minute, length): self.location_id = location_id self.user_id = user_id self.day = day self.start = time(hour, minute, 0) self.length = length @property def end(self): """ Returns a new time using the starting time but increasing the minutes by the session length """ hours = self.length // 60 minutes = self.length % 60 return time(self.start.hour + hours, self.start.minute + minutes) def __repr__(self): days = ["", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] return '(%r) %r-%r' % (days[self.day], self.start.strftime("%H:%M %Z"), self.end.strftime("%H:%M %Z"))
class Mapping(db.Model): id = db.Column(db.Integer, primary_key=True) business_id = db.Column(db.Integer, nullable=False) location_id = db.Column(db.Integer, nullable=False) user_id = db.Column(db.Integer, nullable=False) def __init__(self, business_id, location_id, user_id): self.business_id = business_id self.location_id = location_id self.user_id = user_id
class Location(Base): # Needs to list all services offered here business_id = db.Column(db.Integer, db.ForeignKey('business.id'), nullable=False) # Availabilities that the location has. This is tied to an employee availabilities = db.relationship('Availability', backref='location') # The opening hours of the location hours = db.relationship('Hour', backref='location') name = db.Column(db.String(128)) # Your location information country = db.Column(db.String(128)) province = db.Column(db.String(128)) city = db.Column(db.String(128)) address = db.Column(db.String(128)) timezone = db.Column(db.String(128)) postalcode = db.Column(db.String(128)) email = db.Column(db.String(64)) phone = db.Column(db.String(64)) def __init__(self, business_id, name="Burnaby", email=None, phone=None): self.business_id = business_id self.name = name self.country = None self.province = None self.city = None self.address = None self.timezone = None self.postalcode = None self.mail = email self.phone = phone def get_hours_by_day(self, day): return [hour for hour in self.hours if hour.day == day] def get_user_ids(self): return [ mapping.id for mapping in Mapping.query.filter_by( business_id=self.business_id).filter_by( location_id=self.id).all() ] def get_employee_count(self): return len(self.get_user_ids()) def __repr__(self): return '<Location (%r)>' % self.id
class ContactEmail(Base): # Email to be sent on contact page sender_email = db.Column(db.String(128), nullable=False) content = db.Column(db.String(128), nullable=False) sent = db.Column(db.Boolean, nullable=False) def __init__(self, sender_email, content): self.sender_email = sender_email self.content = content self.sent = False def __repr__(self): return '<Message: %r (From: %r [Sent: %r])>' % ( self.content, self.sender_email, self.sent)
class VerifyEmail(Base): # Email sent to verify accounts sender_email = db.Column(db.String(128), nullable=False) content = db.Column(db.String(128), nullable=False) sent = db.Column(db.Boolean, nullable=False) def __init__(self, sender_email, content): self.sender_email = sender_email self.content = content self.sent = False def __repr__(self): return '<Message: %r (From: %r [Sent: %r])>' % ( self.content, self.sender_email, self.sent)
class AnalyticEvent(db.Model): __abstract__ = True id = db.Column(db.Integer, primary_key=True) server_time = db.Column(db.DateTime, default=db.func.current_timestamp()) user_id = db.Column(db.Integer) activity = db.Column(db.String(16)) validity = db.Column(db.Boolean) def __init__(self, user_id=-1, activity="unknown"): self.user_id = user_id self.activity = activity self.validity = self.validate @property def validate(self): raise NotImplementedError
class TransactionEvent(AnalyticEvent): currency = db.Column(db.String(16)) amount = db.Column(db.Integer) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.currency = "CAD" self.amount = 0 @property def validate(self): if not isinstance(self.user_id, int): return False if not isinstance(self.activity, str) and self.activity not in ['purchase', 'refund']: return False if not isinstance(self.currency, str) and self.currency not in ['CAD']: return False if not isinstance(self.amount, int): return False return True
class AuthenticationEvent(AnalyticEvent): session_length = db.Column(db.Integer) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.session_length = 0 @property def validate(self): if not isinstance(self.user_id, int): return False if not isinstance(self.activity, str) or self.activity not in ['login', 'logout']: return False return True
class Hour(Base): """ Here you can set the opening hours of any location. So you could set location 5 to be open from 9am-3pm on Monday and 1pm-6pm on Friday. Each interval of time would be its own object. """ location_id = db.Column(db.Integer, db.ForeignKey('location.id')) day = db.Column(db.Integer) # Which day of the week start = db.Column(db.Integer) # What hour your availability starts length = db.Column(db.Integer) # How many minutes you are available for closed = db.Column(db.Boolean) # If your store is closed that day def __init__(self, location_id, day, start_time, length, closed=False): self.location_id = location_id self.day = day self.start = start_time self.length = length self.closed = closed @property def end(self): return self.start + self.length def display(self, day, closed): days = [ "", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ] if not closed: return '%r %r:00-%r:00' % (days[day], self.start, self.end) else: return '%r: Closed' % (days[day]) def __repr__(self): return '%r:00-%r:00' % (self.start, self.end)
class Service(Base): name = db.Column(db.String(128), nullable=False) availability = db.Column(db.DateTime) cost = db.Column(db.Float(2)) length = db.Column(db.Integer) deposit = db.Column(db.Boolean) # If a deposit is needed to book this location_id = db.Column(db.Integer) # Which location this is available at locations = db.Column( db.Boolean ) # If this is true, it's available at every location at your business appointments = db.relationship('Appointment', backref='service') business_id = db.Column(db.Integer, db.ForeignKey('business.id'), nullable=False) # Needs to connect to all locations this service is offered at def __init__(self, name, business_id, cost, length): self.name = name self.business_id = business_id self.cost = cost self.length = length self.availability = datetime.now() self.deposit = False self.location_id = 1 self.locations = False def get_description(self): cost = format_as_currency(self.cost) return "%{} for %{} minutes - ${}".format(self.name, self.length, cost) def __repr__(self): cost = format_as_currency(self.cost) return '{} for ${} and a length of {} min'.format( self.name, cost, self.length)
class Appointment(Base): # The business for which the appointment is for business_id = db.Column(db.Integer, db.ForeignKey('business.id'), nullable=False) # The client for which the appointment is for client_id = db.Column(db.Integer, db.ForeignKey('client.id'), nullable=False) # The day and time of the appointment date = db.Column(db.DateTime, nullable=False) # The length of the appointment in minutes service_id = db.Column(db.Integer, db.ForeignKey('service.id')) # If the user has paid yet or not paid = db.Column(db.Boolean) # Name of practitioner, if chosen practitioner_id = db.Column(db.Integer, db.ForeignKey('user.id')) def __init__(self, business_id, client_id, service_id, date, practitioner_id=None): self.business_id = business_id self.client_id = client_id self.service_id = service_id self.date = date self.practitioner_id = practitioner_id self.paid = False def get_end_time(self): return (self.date + timedelta(minutes=self.service.length)).time() def __repr__(self): return '<Appointment (%r) is on %r/%r/%r at %r:%r>' % ( self.id, self.date.year, self.date.month, self.date.day, self.date.hour, self.date.minute)
class Client(Base): email = db.Column(db.String(128)) name = db.Column(db.String(128)) # First and Last Name phone = db.Column(db.String(128)) contact_method = db.Column(db.String(128)) consent_form = db.Column(db.Boolean) # Unused business_id = db.Column(db.Integer, db.ForeignKey('business.id'), nullable=False) # List of every appointment appointments = db.relationship('Appointment', backref='client') def __init__(self, email, business_id, name, phone="No phone number"): self.email = email self.business_id = business_id self.name = name self.phone = phone self.contact_method = None self.consent_form = False def __repr__(self): return '<Client %r (%r)>' % (self.email, self.id)
class Business(Base): # Name of the business name = db.Column(db.String(128), nullable=False) # Name of the business country = db.Column(db.String(64)) # Name of the business currency = db.Column(db.String(32)) # Name of the business province = db.Column(db.String(64)) # Name of the business city = db.Column(db.String(64)) # Name of the business address = db.Column(db.String(64)) # Name of the business verified = db.Column(db.Boolean) # Name of the business subscription_days_remaining = db.Column(db.Integer) # Referral code referral = db.Column(db.String(128), unique=True) # List of all admins ids of the business owner_ids = db.Column(db.String(128), nullable=False) # List of all non-admins ids of the business employee_ids = db.Column(db.String(128), nullable=False) # List of every appointment appointments = db.relationship('Appointment', backref='business') # List of every admin/employee user account registered to this business users = db.relationship('User', backref='business') # List of every service offered by this business services = db.relationship('Service', backref='business') # List of every client clients = db.relationship('Client', backref='business') # List of every location locations = db.relationship('Location', backref='business') def __init__(self, name, country, currency, province, city, address): self.name = name self.country = country self.currency = currency self.province = province self.city = city self.address = address self.owner_ids = "" self.employee_ids = "" self.set_referral_id() self.verified = False self.subscription_days_remaining = 30 self._subscription_end_date = None def get_subscription_end_date(self): if self.subscription_days_remaining == 0: return None return datetime.now().date() + timedelta( days=self.subscription_days_remaining) # Custom property setter def set_referral_id(self): self.referral = random.choice(["A", "B", "C", "D"]) + self.name + str( random.randint(1, 1000)) # I will make this create a unique and random referral string later using a function def check_referral(self, referral): return True def get_client_link(self): return request.url_root + "booking/appointment/" + str(self.id) def get_employee_link(self): return url_for('register_user', business_id=self.id, business_referral=self.referral) def get_employees(self): # Automatically sorts them so that admins are displayed first return [user for user in self.users if user.is_owner ] + [user for user in self.users if not user.is_owner] def get_clients(self): return [ client for client in self.clients if client.email != "*****@*****.**" ] def get_todays_appointments(self): """ Checks the date of each appointment (ignoring the time) and checks if it's today's date. Might bug out if the user is in a different timezone? @klondikemarlen please check if this is timezone compatible """ return [ appointment for appointment in self.appointments if appointment.date.date() == datetime.now().date() ] def get_appointments_by_day(self, year, month, day): """ You can choose a date and it returns all apointments for the business on that date This function is really messy """ def correct_size(thing): if len(str(thing)) >= 2: return str(thing) else: return "0" + str(thing) date_check = correct_size(year) + "-" + correct_size( month) + "-" + correct_size(day) """ I think it should probably look like this and be shorter, simpler. better_date = datetime.date(year, month, day) return [appointment for appointment in self.appointments if appointment.date.date() == better_date.date()] """ return [ appointment for appointment in self.appointments if str(appointment.date.date()) == date_check ] def __repr__(self): return '<Business %r (ID: %r)>' % (self.name, self.id)
class User(Base): # Identification Data: email & password name = db.Column(db.String(128), nullable=False) email = db.Column(db.String(128), nullable=False, unique=True) password_hash = db.Column(db.String(192), nullable=False) phone = db.Column(db.Integer) # Business associated with the user business_id = db.Column(db.Integer, db.ForeignKey('business.id'), nullable=False) # Appointments associated with the user appointments = db.relationship('Appointment', backref='user') # Availabilities that the employee has availabilities = db.relationship('Availability', backref='user') # Used for login_manager is_authenticated = db.Column( db.Boolean) # They have filled in all required fields is_active = db.Column( db.Boolean) # Account activated and not currently suspended is_anonymous = db.Column(db.Boolean) # If account is anonymous # Permissions is_owner = db.Column( db.Boolean ) # Determines if they are an 'owner' of the business. Grants full permissions is_manager = db.Column( db.Boolean) # Determines if they have full control over a location # Used for email verification is_verified = db.Column(db.Boolean) def __init__(self, name, email, password, business_id, is_owner): self.name = name self.email = email self.set_password_hash(password) self.phone = 0000000000 self.business_id = business_id self.is_owner = is_owner self.is_manager = False self.is_authenticated = True self.is_active = True self.is_anonymous = False self.is_verified = False self.location_id = 1 def __repr__(self): return '<User %r (%r)>' % (self.email, self.id) def get_location_ids(self): return [ mapping.location_id for mapping in Mapping.query.filter_by( business_id=self.business_id).filter_by(user_id=self.id).all() ] # Custom property reminder @property def password(self): raise AttributeError( 'Password is not a readable attribute. Only password_hash is stored.' ) # Custom property setter def set_password_hash(self, password): self.password_hash = generate_password_hash(password) # Password verifier. Can send in a plain password and it will compare it to the hashed password. def check_password(self, password): return check_password_hash(self.password_hash, password) def get_id(self): return self.id def generate_verification_link(self): return str(self.id) + "-" + self.name def get_verification_link(self): return request.url_root[:-1] + url_for( 'verification', user_id=self.id, verification_link=self.generate_verification_link()) def check_verification_link(self, link): if link == request.url_root[:-1] + (str(self.id) + "-" + self.name): return True return False def get_availability_by_day(self, day): return [ availability for availability in self.availabilities if availability.day == day ] @property def location(self): return Location.query.get(self.location_id) def sorted_availabilities(self, day=None): """ Returns availabilities sorted first by day, then by start time. You can pass in one specific day to check. """ if day is not None: availabilities = [ availability for availability in self.availabilities if availability.day == day ] else: availabilities = self.availabilities return sorted(availabilities, key=lambda x: (x.day, x.start)) @staticmethod def verify_time_value(hour, minute): """ This function lets you pass in hours greater than 23 and minutes 60 or above. """ new_hour = (hour % 24) + (minute // 60) new_minute = (minute % 60) return new_hour, new_minute def working_hours_by_day(self, day): """ This creates a list of all hours that a user is working on any given day. Currently this function isnt used anywhere, but it will be used to generate the visuals. Feel free to run this function and it'll tell you all hours that the user is busy. """ availabilities = self.sorted_availabilities(day) options = [] if not availabilities: return 0 for availability in availabilities: count = 0 while True: new = time(availability.start.hour + count, 0) options.append((new.hour, new.__str__())) count += 1 if new >= availability.end: break return options def available_hours_by_day(self, day, condition): """ This creates a list of hours that a user DOESN'T work in a day. That way you can run this function in JSON to have the menus update when you choose a day, and let you choose hours that you are available. """ if condition == "close": pass all_hours = [i for i in range(28)] if not self.availabilities: # Need to return this first or it will crash when it cant iterate through an empty list return [(i, str(i) + ":00") for i in range(23)] busy_hours = [i[0] for i in self.working_hours_by_day(day)] available_hours = [i for i in all_hours if i not in busy_hours] options = [] for i in available_hours: if condition == "open": hour, minute = self.verify_time_value(i, 0) else: hour, minute = self.verify_time_value(i + 1, 0) hour = time(hour, minute).hour options.append((hour, str(hour) + ":00")) return options