Beispiel #1
0
 def __init__(self, *args, **kwargs) -> None:
     super().__init__(*args, **kwargs)
     self.error_message: str = str()
     hotels = Hotel.objects.filter_by(city=current_user.city).get()
     hotels.sort(key=lambda hotel: hotel.name)
     self.custom_hotels.choices.extend([(hotel.name, hotel.name)
                                        for hotel in hotels
                                        if hotel.name != current_user.hotel
                                        ])
     hotel: Hotel = next(
         (hotel for hotel in hotels if hotel.name == current_user.hotel),
         None)
     if not hotel:
         self.error_message = "Error in retrieving the hotel information"
         return
     if current_user.role == Config.HOTEL:
         date, _ = Usage.get_data_entry_date(hotel)
         if not date:
             self.error_message = "Error in retrieving last update date"
             return
         if date <= Date.previous_lock_in() and Date.today(
         ) <= hotel.contract.end_date:
             self.error_message = "There are pending data entry for previous lock in period"
             return
     self.primaries = hotel.primary_hotels if hotel else list()
     self.secondaries = hotel.secondary_hotels if hotel else list()
     self.usage_data: List[Usage] = list()
     self.hotel_counts: List[Tuple[Hotel, int]] = list()
     self.hotel_trends: List[Tuple[str, int, float]] = list()
     self.file_path: str = str()
Beispiel #2
0
 def validate_end_date(self, end_date: DateField):
     if not self.start_date.data or not self.end_date.data:
         self.raise_date_error(str())
     if end_date.data > Date.previous_lock_in():
         self.raise_date_error(
             f"To Date cannot be greater than previous lock in date "
             f"({Date(Date.previous_lock_in()).format_date})")
     if self.start_date.data > end_date.data:
         self.raise_date_error("From Date cannot be greater than To Date")
     days = (end_date.data - self.start_date.data).days + 1
     if self.day.data == self.ALL_DAY and days > self.MAX_ALL_DAYS:
         self.raise_date_error(
             f"For All Days query, the date range cannot be greater than {self.MAX_ALL_DAYS} days"
         )
     if self.day.data == self.WEEKDAY and days > self.MAX_WEEKDAYS:
         self.raise_date_error(
             f"For Weekdays query, the date range cannot be greater than {self.MAX_WEEKDAYS} days"
         )
     if self.day.data == self.WEEKEND and days > self.MAX_WEEKENDS:
         self.raise_date_error(
             f"For Weekends query, the date range cannot be greater than {self.MAX_WEEKENDS} days"
         )
     if self.day.data not in (self.ALL_DAY, self.WEEKDAY, self.WEEKEND
                              ) and days > self.MAX_SPECIFIC_DAYS:
         self.raise_date_error(
             f"For specific day query, the date range cannot be greater than "
             f"{self.MAX_SPECIFIC_DAYS} days")
Beispiel #3
0
class QueryForm(FSForm):
    # Constants
    MAX_ALL_DAYS = 100
    MAX_WEEKDAYS = int(MAX_ALL_DAYS * 7 / 5)
    MAX_WEEKENDS = int(MAX_ALL_DAYS * 7 / 2)
    MAX_SPECIFIC_DAYS = int(MAX_ALL_DAYS * 7 / 1)
    DEFAULT_DATE = Date.previous_lock_in()
    PRIMARY_HOTEL = "Primary Comp Set"
    SECONDARY_HOTEL = "Secondary Comp Set"
    CUSTOM_HOTEL = "Custom Comp Set"
    HOTEL_CHOICES = (PRIMARY_HOTEL, SECONDARY_HOTEL, CUSTOM_HOTEL)
    ALL_TIMING = "All Timings"
    TIMING_CHOICES = tuple([ALL_TIMING] + list(Config.TIMINGS))
    ALL_MEAL = "All Meals"
    ALL_MEAL_CHOICES = tuple([ALL_MEAL] + list(Config.MEALS))
    MORNING_MEAL_CHOICES = tuple([ALL_MEAL] + list(Config.MORNING_MEALS))
    EVENING_MEAL_CHOICES = tuple([ALL_MEAL] + list(Config.EVENING_MEALS))
    ALL_DAY = "All Days"
    WEEKDAY = "Weekdays"
    WEEKEND = "Weekends"
    DAY_CHOICES = (ALL_DAY, WEEKDAY, WEEKEND)
    ALL_EVENT = "All Events"
    EVENT_CHOICES = tuple([ALL_EVENT] + list(Config.EVENTS))
    # Form type
    DOWNLOAD = "download"
    HOTEL_QUERY = "hotel_query"
    FILTER_QUERY = "filter_query"
    # Form elements
    hotel_select = RadioField("Comp Set Type",
                              choices=[(choice, choice)
                                       for choice in HOTEL_CHOICES],
                              default=PRIMARY_HOTEL)
    custom_hotels = SelectMultipleField("Select hotels", choices=list())
    start_date = DateField("From Date",
                           default=DEFAULT_DATE,
                           format="%d/%m/%Y")
    end_date = DateField("To Date", default=DEFAULT_DATE, format="%d/%m/%Y")
    timing = RadioField("Select timing",
                        choices=[(timing, timing)
                                 for timing in TIMING_CHOICES],
                        default=ALL_TIMING)
    all_meal = RadioField("Select meals",
                          choices=[(meal, meal) for meal in ALL_MEAL_CHOICES],
                          default=ALL_MEAL)
    morning_meal = RadioField("Select morning meals",
                              choices=[(meal, meal)
                                       for meal in MORNING_MEAL_CHOICES],
                              default=ALL_MEAL)
    evening_meal = RadioField("Select evening meals",
                              choices=[(meal, meal)
                                       for meal in EVENING_MEAL_CHOICES],
                              default=ALL_MEAL)
    day = RadioField("Select the day(s) of the week",
                     choices=[(day, day) for day in DAY_CHOICES],
                     default=ALL_DAY)
    event = RadioField("Select event type",
                       choices=[(event, event) for event in EVENT_CHOICES],
                       default=ALL_EVENT)
    form_type = HiddenField()
    submit = SubmitField("Query")

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.error_message: str = str()
        hotels = Hotel.objects.filter_by(city=current_user.city).get()
        hotels.sort(key=lambda hotel: hotel.name)
        self.custom_hotels.choices.extend([(hotel.name, hotel.name)
                                           for hotel in hotels
                                           if hotel.name != current_user.hotel
                                           ])
        hotel: Hotel = next(
            (hotel for hotel in hotels if hotel.name == current_user.hotel),
            None)
        if not hotel:
            self.error_message = "Error in retrieving the hotel information"
            return
        if current_user.role == Config.HOTEL:
            date, _ = Usage.get_data_entry_date(hotel)
            if not date:
                self.error_message = "Error in retrieving last update date"
                return
            if date <= Date.previous_lock_in() and Date.today(
            ) <= hotel.contract.end_date:
                self.error_message = "There are pending data entry for previous lock in period"
                return
        self.primaries = hotel.primary_hotels if hotel else list()
        self.secondaries = hotel.secondary_hotels if hotel else list()
        self.usage_data: List[Usage] = list()
        self.hotel_counts: List[Tuple[Hotel, int]] = list()
        self.hotel_trends: List[Tuple[str, int, float]] = list()
        self.file_path: str = str()

    def raise_date_error(self, message):
        self.start_date.data = self.end_date.data = self.DEFAULT_DATE
        raise ValidationError(message)

    def validate_end_date(self, end_date: DateField):
        if not self.start_date.data or not self.end_date.data:
            self.raise_date_error(str())
        if end_date.data > Date.previous_lock_in():
            self.raise_date_error(
                f"To Date cannot be greater than previous lock in date "
                f"({Date(Date.previous_lock_in()).format_date})")
        if self.start_date.data > end_date.data:
            self.raise_date_error("From Date cannot be greater than To Date")
        days = (end_date.data - self.start_date.data).days + 1
        if self.day.data == self.ALL_DAY and days > self.MAX_ALL_DAYS:
            self.raise_date_error(
                f"For All Days query, the date range cannot be greater than {self.MAX_ALL_DAYS} days"
            )
        if self.day.data == self.WEEKDAY and days > self.MAX_WEEKDAYS:
            self.raise_date_error(
                f"For Weekdays query, the date range cannot be greater than {self.MAX_WEEKDAYS} days"
            )
        if self.day.data == self.WEEKEND and days > self.MAX_WEEKENDS:
            self.raise_date_error(
                f"For Weekends query, the date range cannot be greater than {self.MAX_WEEKENDS} days"
            )
        if self.day.data not in (self.ALL_DAY, self.WEEKDAY, self.WEEKEND
                                 ) and days > self.MAX_SPECIFIC_DAYS:
            self.raise_date_error(
                f"For specific day query, the date range cannot be greater than "
                f"{self.MAX_SPECIFIC_DAYS} days")

    def get_filter_meals(self) -> List[str]:
        if self.timing.data == self.ALL_TIMING:
            return [self.all_meal.data
                    ] if self.all_meal.data != self.ALL_MEAL else list()
        elif self.timing.data == Config.MORNING:
            if self.morning_meal.data == Config.BREAKFAST_LUNCH:
                return [Config.BREAKFAST, Config.LUNCH]
            return [self.morning_meal.data
                    ] if self.morning_meal.data != self.ALL_MEAL else list()
        if self.evening_meal.data == Config.HI_TEA_DINNER:
            return [Config.HI_TEA, Config.DINNER]
        return [self.evening_meal.data
                ] if self.evening_meal.data != self.ALL_MEAL else list()

    def update_data(self):
        query = Usage.objects.filter_by(city=current_user.city, no_event=False)
        if self.day.data != self.ALL_DAY:
            query = query.filter_by(weekday=self.day.data == self.WEEKDAY)
        if self.timing.data != self.ALL_TIMING:
            query = query.filter_by(timing=self.timing.data)
        if self.event.data != self.ALL_EVENT:
            query = query.filter_by(event_type=self.event.data)
        if self.hotel_select.data == self.PRIMARY_HOTEL:
            hotels = self.primaries[:]
        elif self.hotel_select.data == self.SECONDARY_HOTEL:
            hotels = self.secondaries[:]
        else:
            hotels = self.custom_hotels.data[:] if self.custom_hotels.data else list(
            )
        hotels.append(current_user.hotel)
        query = query.filter("hotel", query.IN, hotels)
        query = query.filter("date", ">=", Date(self.start_date.data).db_date)
        query = query.filter("date", "<=", Date(self.end_date.data).db_date)
        self.usage_data = query.get()
        filter_meals = self.get_filter_meals()
        if filter_meals:
            self.usage_data = [
                usage for usage in self.usage_data
                if any(meal in usage.meals for meal in filter_meals)
            ]
        self.usage_data.sort(key=lambda usage: usage.timing, reverse=True)
        self.usage_data.sort(key=lambda usage: usage.date)
        self.determine_hotel_counts()
        self.determine_hotel_trends()
        if self.form_type.data == self.DOWNLOAD and len(self.usage_data) > 0:
            self.download()

    def download(self):
        sheet = File.create_sheet()
        data_rows = len(self.usage_data) + 4
        sheet.prepare(data_rows=data_rows)
        header = [
            "Hotel Name", "Date", "Timing", "Client", "Meal", "Event Type",
            "Ballroom", "Event Description"
        ]
        data = [[
            u.hotel, u.formatted_date, u.timing, u.client, u.formatted_meal,
            u.event_type, u.formatted_ballroom, u.event_description
        ] for u in self.usage_data]
        data.insert(0, header)
        range_name = f"Data!A1:H{data_rows}"
        sheet.update_range(range_name, data)
        row1 = self.selected_hotels
        row1.extend([str()] * (9 - len(row1)))
        row1.insert(0, self.hotel_select.data)
        row2 = ["From Date", "To Date", "Days", "Timing", "Meal", "Event"
                ] + [str()] * 4
        row3 = [
            Date(self.start_date.data).format_date,
            Date(self.end_date.data).format_date, self.day.data,
            self.timing.data, " and ".join(self.meals), self.event.data
        ] + [str()] * 4
        sheet.update_range(f"Report!A1:J3", [row1, row2, row3])
        hotel_counts: List[list] = [
            list(hotel_count) for hotel_count in self.hotel_counts
        ]
        hotel_counts.insert(0,
                            ["Hotel", f"Total Count={len(self.usage_data)}"])
        row_end = len(self.hotel_counts) + 5
        sheet.update_range(f"Report!H5:I{row_end}", hotel_counts)
        headers = [GridRange.from_range(f"Report!H6:H{row_end}").to_dict()]
        values = [GridRange.from_range(f"Report!I6:I{row_end}").to_dict()]
        anchor = GridCoordinate.from_cell(f"Report!A5").to_dict()
        pie = sheet.pie_spec(headers, values, anchor)
        hotel_trends: List[list] = [
            list(hotel_trend) for hotel_trend in self.hotel_trends
        ]
        hotel_trends.insert(0, ["Date", "My Prop", "Comp Set Avg"])
        row_end = len(self.hotel_trends) + 25
        sheet.update_range(f"Report!H25:J{row_end}", hotel_trends)
        headers = [GridRange.from_range(f"Report!H25:H{row_end}").to_dict()]
        values = [
            GridRange.from_range(f"Report!I25:I{row_end}").to_dict(),
            GridRange.from_range(f"Report!J25:J{row_end}").to_dict()
        ]
        anchor = GridCoordinate.from_cell(f"Report!A25").to_dict()
        trend = sheet.trend_spec(headers, values, anchor)
        sheet.update_chart(pie, trend)
        self.file_path = sheet.download_from_drive()
        sheet.delete_sheet()

    def determine_hotel_trends(self):
        self.usage_data.sort(key=lambda usage: usage.date)
        total_hotel_count = len(self.selected_hotels) + 1
        for date, usages in itertools.groupby(self.usage_data,
                                              lambda usage: usage.date):
            date = Date(date).format_date[:6]
            usages = list(usages)
            my_count = sum(1 for usage in usages
                           if usage.hotel == current_user.hotel)
            other_count = sum(1 for usage in usages
                              if usage.hotel != current_user.hotel)
            other_average = round(other_count / total_hotel_count, 1)
            self.hotel_trends.append((date, my_count, other_average))
        return

    def determine_hotel_counts(self):
        hotels = {usage.hotel for usage in self.usage_data}
        self.hotel_counts = [(hotel,
                              sum(1 for usage in self.usage_data
                                  if usage.hotel == hotel))
                             for hotel in hotels]
        self.hotel_counts.sort(key=itemgetter(1), reverse=True)
        if current_user.hotel in hotels:
            current_hotel = next(hotel for hotel in self.hotel_counts
                                 if hotel[0] == current_user.hotel)
            self.hotel_counts.remove(current_hotel)
        else:
            current_hotel = (current_user.hotel, 0)
        self.hotel_counts.insert(0, current_hotel)

    @property
    def format_start_date(self) -> str:
        return Date(self.start_date.data
                    ).format_week if self.start_date.data else "Invalid date"

    @property
    def format_end_date(self) -> str:
        return Date(self.end_date.data
                    ).format_week if self.end_date.data else "Invalid date"

    @property
    def selected_hotels(self) -> List[str]:
        if self.hotel_select.data == self.PRIMARY_HOTEL:
            hotels = self.primaries
        elif self.hotel_select.data == self.SECONDARY_HOTEL:
            hotels = self.secondaries
        else:
            hotels = self.custom_hotels.data
        return hotels if hotels else ["No Hotel"]

    @property
    def meals(self) -> List[str]:
        filter_meals = self.get_filter_meals()
        return filter_meals if filter_meals else [self.ALL_MEAL]
Beispiel #4
0
 def display_edit_delete(self) -> str:
     return "disabled" if current_user.role == Config.HOTEL and self.date < Date.previous_lock_in() else str()