class CustomerModel(UserMixin, db.Model): __tablename__ = "customers" payment_gateway_id = db.Column(db.String(75), nullable=True) bookings = db.relationship(lambda: BookingModel, uselist=True, lazy="dynamic", passive_deletes=True) payments = db.relationship(lambda: PaymentModel, lazy="dynamic", uselist=True, passive_deletes=True, ) refunds = db.relationship(lambda: BookingRefundModel, lazy="dynamic", uselist=True, passive_deletes=True, ) role_id = db.Column(db.String(36), db.ForeignKey("roles.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, index=True, ) role = db.relationship("RoleModel", viewonly=True) activity_monitoring = db.relationship(lambda: CustomerActivityModel, lazy="dynamic", uselist=True, passive_deletes=True) def __init__(self, **kwargs): super(CustomerModel, self).__init__(**kwargs) self.password = CustomerModel.encrypt_password(kwargs["password"]) def initialize_refund_message(self, status: bool): """ Send email concerning refund claim """ from app.blueprints.customer.tasks import deliver_refund_mail deliver_refund_mail.delay(self.email, status) return None
class BookingContactModel(ResourceMixin, db.Model): __tablename__ = "booking_contacts" first_name = db.Column(db.String(25), nullable=True, index=False) last_name = db.Column(db.String(25), nullable=True, index=False) telephone = db.Column(db.String(15), nullable=False, index=True, ) email = db.Column(db.String(128), nullable=False, index=True, ) note = db.Column(db.String(250), nullable=True, index=False) event_address = db.Column(db.String(150), nullable=False, index=False) event_gps = db.Column(db.String(150), nullable=True, index=False) booking_id = db.Column(db.String(36), db.ForeignKey("bookings.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, index=True, ) booking = db.relationship("BookingModel", viewonly=True) def __init__(self, **kwargs): super(BookingContactModel, self).__init__(**kwargs) @classmethod def get_booking_contact_by_booking_id(cls, booking_id) -> 'BookingContactModel': """:return: BookingContactModel :param booking_id:str """ return cls.query.filter_by(booking_id=booking_id).first()
class ReviewModel(ResourceMixin, db.Model): __tablename__ = "reviews" def __init__(self, **kwargs): super(ReviewModel, self).__init__(**kwargs) rating = db.Column(db.Integer(), nullable=False, default=0, index=False) review_body = db.Column(db.String(125), nullable=True, index=False) booking_id = db.Column(db.String(36), db.ForeignKey("bookings.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, index=True) booking = db.relationship("BookingModel", viewonly=True)
class CustomerActivityModel(ResourceMixin, db.Model): __tablename__ = "customer_activity_monitoring" def __init__(self, **kwargs): super(CustomerActivityModel, self).__init__(**kwargs) sign_in_ip = db.Column(db.String(45), nullable=True, index=False, ) customer_id = db.Column(db.String(36), db.ForeignKey("customers.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, index=True, ) customer = db.relationship("CustomerModel", viewonly=True) @classmethod def update_activity_tracking(cls, customer_id: str, ip_address: str): """ Update client's activity monitoring """ monitor = cls(customer_id=customer_id, sign_in_ip=ip_address) return monitor.save()
class RoleModel(ResourceMixin, db.Model): __tablename__ = "roles" name = db.Column(db.String(10), index=True, unique=True, nullable=False) def __init__(self, **kwargs): super(RoleModel, self).__init__(**kwargs) @classmethod def find_by_identity(cls, identity): search_chain = ((cls.name == identity), (cls.id == identity)) return cls.query.filter(or_(*search_chain)).first()
class EmployeeModel(UserMixin, db.Model): __tablename__ = "employees" services = db.relationship(lambda: ServiceModel, secondary=lambda: employees_services, lazy="dynamic", backref=db.backref("employees", lazy="dynamic")) role_id = db.Column( db.String(36), db.ForeignKey("roles.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, index=True, ) role = db.relationship("RoleModel", viewonly=True) time_sheets = db.relationship(lambda: TimeSheetModel, lazy="dynamic", uselist=True, passive_deletes=True) def __init__(self, **kwargs): super(EmployeeModel, self).__init__(**kwargs) self.password = EmployeeModel.encrypt_password(kwargs["password"]) @classmethod def get_employee_detail(cls, employee_id: str) -> Optional["EmployeeModel"]: employee = cls.query.get(employee_id) if employee is None: return None return employee.services.all() @classmethod def get_employees(cls) -> List["EmployeeModel"]: return cls.query.all() @classmethod def get_employees_by_service_rendered( cls, service_id: str) -> List["EmployeeModel"]: return cls.query.join( cls.services).filter(ServiceModel.id == service_id).all() @classmethod def get_active_employees_by_service_rendered( cls, service_id: str) -> List["EmployeeModel"]: data = cls.query.join(cls.services).filter( ServiceModel.id == service_id, cls.is_active).all() return data
class ServiceMonitoringModel(ResourceMixin, db.Model): __tablename__ = "service_monitoring" def __init__(self, **kwargs): super(ServiceMonitoringModel, self).__init__(**kwargs) service_id = db.Column( db.String(36), db.ForeignKey("services.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, index=True, ) service = db.relationship("ServiceModel", viewonly=True) total_seconds = db.Column(db.Integer(), nullable=False, index=False, default=0) @classmethod def update_activity_tracking(cls, service_id: str, total_seconds: int): """ Update service's monitoring """ monitor = cls(service_id=service_id, total_seconds=total_seconds) return monitor.save() @classmethod def get_service_activities( cls, order_values: str, page: int, ): """Returns Paginated ServiceMonitoringModel""" paginated_monitor = cls.query.order_by(text(order_values)).paginate( page, 50, False) return paginated_monitor
class BookingRefundModel(ResourceMixin, db.Model): BOOKING_REFUND_REASON = OrderedDict([("duplicate", "Duplicate"), ("fraudulent", "Fraudulent"), ("requested_by_customer", "Requested by customer")]) BOOKING_REFUND_STATUS = OrderedDict([('failed', 'Failed'), ('canceled', 'Canceled'), ('succeeded', 'Succeeded')]) __tablename__ = "booking_refunds" refund_id = db.Column( db.String(75), nullable=False, index=True, ) amount = db.Column(db.Float(precision=2), nullable=False) currency = db.Column(db.String(3), nullable=False) payment_id = db.Column( db.String(75), db.ForeignKey("payments.stripe_id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, ) payment = db.relationship("PaymentModel") customer_id = db.Column( db.String(75), db.ForeignKey("customers.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, index=True, ) customer = db.relationship("CustomerModel", viewonly=True) reason = db.Column(db.Enum(*BOOKING_REFUND_REASON, native_enum=False), nullable=False) status = db.Column(db.Enum(*BOOKING_REFUND_STATUS, native_enum=False), nullable=False) receipt_number = db.Column(db.String(50), nullable=True) def __init__(self, **kwargs): super(BookingRefundModel, self).__init__(**kwargs) @classmethod def find_by_identity(cls, identity: str) -> Optional["BookingRefundModel"]: """:return: RefundModel :param identity:str """ search_chain = ((cls.payment_id == identity), (cls.id == identity)) return cls.query.filter(or_(*search_chain)).first() @classmethod def construct_refund_body(cls, reason: str, payment_id: str, amount: float, meta: Dict[str, Any]) -> Dict[str, Any]: """:param payment_id:str :param amount: float :param meta:Dict[str, Any] :param reason:str :return:Dict[str, Any] amount is in lowest denomination of currency. eg Dollar will be cents """ return { "charge": payment_id, "amount": amount, "meta": meta, "reason": reason }
class ResourceMixin(object): id = db.Column(db.String(36), primary_key=True, unique=True, nullable=False, default=id_generator) created_at = db.Column(db.TIMESTAMP(timezone=True), nullable=False, default=tz_aware_datetime) updated_at = db.Column(db.TIMESTAMP(timezone=True), nullable=False, default=tz_aware_datetime, onupdate=tz_aware_datetime) @classmethod def sort_by(cls, field: str, direction: str) -> Tuple[str, str]: """ Validates the sort field and direction """ if field not in cls.__table__.columns: field = "created_at" if direction not in ("asc", "desc"): direction = "asc" return field, direction @classmethod def get_bulk_ids(cls, scope, ids, omit_ids=None, query=""): if omit_ids is None: omit_ids = [] omit__map_ids = map(str, omit_ids) if scope == "all_search_results": ids = cls.query.with_entites(cls.id).filter(cls.search(query)) ids = [str(item[0] for item in ids)] if omit__map_ids: ids = [_id for _id in ids if _id not in omit__map_ids] return ids @classmethod def get_by_id(cls, _id): """ Get Model by ID """ return cls.query.get(_id) @classmethod def bulk_delete(cls, ids): delete_count = cls.query.filter( cls.id.in_(ids)).delete(synchronize_session=False) db.session.commit() return delete_count def save(self): db.session.add(self) db.session.commit() return None def delete(self): db.session.delete(self) return db.session.commit() @classmethod def for_update(cls, _id, kwargs): cls.query.filter(cls.id == _id).update({**kwargs}, synchronize_session='evaluate') return None def __str__(self): """ Create a human readable version of class instance :return self """ obj_id = hex(id(self)) columns = self.__table__.c.keys() values = ", ".join("%s=%r" % (n, getattr(self, n)) for n in columns) return '<%s %s(%s)>' % (obj_id, self.__class__.__name__, values)
class PaymentModel(ResourceMixin, db.Model): PAYMENT_STATUS = OrderedDict([("paid", "Paid"), ("unpaid", "Unpaid"), ("processing", "Processing")]) __tablename__ = "payments" stripe_id = db.Column( db.String(75), nullable=False, unique=True, index=True, ) currency = db.Column( db.String(3), nullable=False, index=False, ) amount_subtotal = db.Column(db.Float(precision=2), nullable=False) payment_method = db.Column(db.String(15), nullable=False) amount_total = db.Column(db.Float(precision=2), nullable=False) booking_id = db.Column( db.String(36), db.ForeignKey("bookings.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, index=True, ) coupon_code = db.Column(db.String(16), nullable=True) booking = db.relationship("BookingModel") customer_id = db.Column( db.String(36), db.ForeignKey("customers.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, index=True, ) customer = db.relationship("CustomerModel", viewonly=True) status = db.Column("status", db.Enum(*PAYMENT_STATUS, native_enum=False), nullable=False, index=False, default="processing") booking_refund = db.relationship( lambda: BookingRefundModel, lazy="dynamic", uselist=True, passive_deletes=True, ) def __init__(self, **kwargs): super(PaymentModel, self).__init__(**kwargs) @classmethod def paginate_payments(cls, page: int, order_values: str, search_query=None, customer_id: str = None, is_admin: bool = False): """Returns Paginated PaymentModel""" paginated_payments = None if search_query is None: if not is_admin: paginated_payments = cls.query.filter_by( customer_id=customer_id).order_by( text(order_values)).paginate(page, 50, False) else: paginated_payments = cls.query.order_by( text(order_values)).paginate(page, 50, False) else: if not is_admin: paginated_payments = cls.query.filter( cls.customer_id == customer_id, search_query).order_by( text(order_values)).paginate(page, 50, False) else: paginated_payments = cls.query.filter(search_query).order_by( text(order_values)).paginate(page, 50, False) return paginated_payments @classmethod def find_by_identity(cls, stripe_id: str, customer_id: str, is_admin: bool = False) -> "PaymentModel": if not is_admin: return cls.query.filter_by(client_id=customer_id, stripe_id=stripe_id).first() return cls.query.filter_by(stripe_id=stripe_id).first() @classmethod def get_payment(cls, stripe_id: str) -> "PaymentModel": return cls.query.filter_by(stripe_id=stripe_id).first() @classmethod def search(cls, search_query: str = None): """ Returns a search query based on search term. """ if not search_query: return None fmt_query = f"%{search_query}%" search_chain = (cls.status.ilike(fmt_query)) return or_(*search_chain)
class ServiceModel(ResourceMixin, db.Model): __tablename__ = "services" def __init__(self, **kwargs): super(ServiceModel, self).__init__(**kwargs) name = db.Column(db.String(75), unique=True, nullable=False, index=True) description = db.Column(db.String(125), nullable=False, index=False) price = db.Column(db.Float(precision=2), nullable=False, index=False) is_available = db.Column(db.Boolean, nullable=False, index=False, default=True) activity_monitoring = db.relationship(lambda: ServiceMonitoringModel, lazy="dynamic", uselist=True, passive_deletes=True) bookings = db.relationship("BookingModel", lazy="dynamic", uselist=True, passive_deletes=True) @classmethod def get_services(cls, include_outdated=False, is_admin=False): if is_admin: if include_outdated: return cls.query.all() return cls.query.filter_by(is_available=True).all() @classmethod def get_service( cls, service_name: str, service_id: str, ): query = ((cls.name == service_name), (cls.id == service_id)) return cls.query.filter(or_(*query)).first() @classmethod def get_service_by_identity(cls, identity: str): query = ((cls.name == identity), (cls.id == identity)) return cls.query.filter(or_(*query)).first() @classmethod def get_service_and_status(cls, identity: str): query = ((cls.name == identity), (cls.id == identity)) return cls.query.filter(or_(*query), cls.is_available).first() @classmethod def search(cls, search_query: str = None): """ Returns a search query based on search term. """ if not search_query: return None fmt_query = f"%{search_query}%" search_chain = [(cls.name.ilike(fmt_query))] return or_(*search_chain) @classmethod def paginate_services(cls, page: int, order_values: str, search_query=None, is_admin=False): """Returns Paginated BookingModel""" paginated_services = None if search_query is None: if not is_admin: paginated_services = cls.query.filter_by( is_available=True).order_by(text(order_values)).paginate( page, 50, False) else: paginated_services = cls.query.order_by( text(order_values)).paginate(page, 50, False) else: if not is_admin: paginated_services = cls.query.filter( cls.is_available, search_query).order_by( text(order_values)).paginate(page, 50, False) else: paginated_services = cls.query.filter(search_query).order_by( text(order_values)).paginate(page, 50, False) return paginated_services
from app.extensions.db_ext import db employees_services = db.Table( "employees_services", db.Model.metadata, db.Column("employee_id", db.String(), db.ForeignKey("employees.id"), primary_key=True), db.Column("service_id", db.String(), db.ForeignKey("services.id"), primary_key=True))
class UserMixin(ResourceMixin): GENDER = OrderedDict([("male", "Male"), ("female", "Female"), ("private", "Private")]) password = db.Column(db.String(128), nullable=False, index=False) gender = db.Column(db.Enum(*GENDER, native_enum=False), nullable=False, index=True, default="private") is_active = db.Column(db.Boolean, nullable=False, default=True) email = db.Column(db.String(128), nullable=False, index=True, unique=True) first_name = db.Column(db.String(45), nullable=False, index=False, ) last_name = db.Column(db.String(45), nullable=False, index=False) telephone = db.Column(db.String(15), nullable=False, index=True, unique=True) # ! Address country = db.Column(db.String(25), nullable=False, index=False) city = db.Column(db.String(50), nullable=False, index=False) street = db.Column(db.String(50), nullable=True, index=False, ) @classmethod def deserialize_token(cls, reset_token): """ Deserialize password reset token """ private_key = current_app.config["SECRET_KEY"] serializer = TimedJSONWebSignatureSerializer(private_key) try: payload = serializer.loads(reset_token) return cls.find_by_identity(payload["email"]) except Exception as e: return None def initialize_password_reset(self): """ generate token to reset password for a user """ reset_token = self.serialize_token() # TODO: Password reset email from app.blueprints.auth.tasks import deliver_password_reset_mail deliver_password_reset_mail.delay(self.email, reset_token) return None def serialize_token(self, expiration=3600): private_key = current_app.config["SECRET_KEY"] serializer = TimedJSONWebSignatureSerializer(private_key, expires_in=expiration) return serializer.dumps({"email": self.email}).decode("utf-8") def authenticate(self, plain_password, with_password=True): """ Check password """ if with_password: return check_password_hash(self.password, plain_password) return True @classmethod def search(cls, search_query): """ Returns a search query based on search term. """ if not search_query: return None fmt_query = f"%{search_query}%" search_chain = (cls.last_name.ilike(fmt_query), cls.first_name.ilike(fmt_query), cls.city.ilike(fmt_query), cls.country.ilike(fmt_query)) return or_(*search_chain) @classmethod def find_by_identity(cls, identity): search_chain = ((cls.email == identity), (cls.telephone == identity), (cls.id == identity)) return cls.query.filter(or_(*search_chain)).first() @classmethod def verify_for_signup(cls, email: str, telephone: str) -> bool: search_chain = ((cls.email == email), (cls.telephone == telephone)) user = cls.query.filter(or_(*search_chain)).first() return user is not None @classmethod def encrypt_password(cls, plain_password: str) -> str: """ Hash Password """ return generate_password_hash(plain_password) @classmethod def paginate_account(cls, page: int, order_values: str, search_query=None): """Returns Paginated UserModel""" paginated_account = None if search_query is None: paginated_account = cls.query.order_by(cls.role_id, text(order_values)).paginate( page, 50, False) else: paginated_account = cls.query.filter(search_query).order_by(cls.role_id, text(order_values)).paginate( page, 50, False) return paginated_account
class TimeSheetModel(ResourceMixin, db.Model): AVAILABILITY_STATUS = OrderedDict([('pending', 'Pending'), ('completed', 'Completed'), ('cancelled', 'Cancelled'), ('ongoing', 'Ongoing')]) __tablename__ = "time_sheets" def __init__(self, **kwargs): super(TimeSheetModel, self).__init__(**kwargs) booking_id = db.Column(db.String(36), db.ForeignKey("bookings.id", onupdate="CASCADE", ondelete="CASCADE"), index=True) booking = db.relationship("BookingModel", viewonly=True) service_id = db.Column(db.String(36), db.ForeignKey("services.id", onupdate="CASCADE", ondelete="CASCADE"), index=True) service = db.relationship("ServiceModel", viewonly=True) employee_id = db.Column(db.String(36), db.ForeignKey("employees.id", onupdate="CASCADE", ondelete="CASCADE"), index=True) employee = db.relationship("EmployeeModel", viewonly=True) status = db.Column(db.Enum(*AVAILABILITY_STATUS, native_enum=False), nullable=True, index=True, default="pending") start_date = db.Column(db.TIMESTAMP(timezone=True), nullable=False) end_date = db.Column(db.TIMESTAMP(timezone=True), nullable=False) @classmethod def get_schedules(cls, ) -> List["TimeSheetModel"]: return cls.query.all() @classmethod def get_staff_schedules_for_booking(cls, employee_id, start_date) -> List["TimeSheetModel"]: return cls.query.filter(cls.employee_id == employee_id, cls.start_date >= start_date, cls.status != "cancelled").all() @classmethod def populate_schedules(cls, schedules: List, booking_id: str, employee_id: str, service_id: str): instances = [ TimeSheetModel(booking_id=booking_id, employee_id=employee_id, start_date=schedule.get("start_date"), end_date=schedule.get("end_date"), service_id=service_id) for schedule in schedules ] db.session.add_all(instances) db.session.commit() return None @classmethod def update_populate_schedules(cls, booking_id: str, status: str): cls.query.filter(cls.booking_id == booking_id).update( {cls.status: status}, synchronize_session="evaluate") return None