예제 #1
0
 def __init__(self, hotel_id: str, date: str, timing: str, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.error_message: str = str()
     self.redirect: bool = False
     self.upload_errors: list = list()
     self.upload_data: list = list()
     self.hotel: Hotel = Hotel.get_by_id(hotel_id)
     if not self.hotel:
         self._error_redirect("Error in retrieving hotel details")
         return
     self.date = Date(date).date
     self.timing = timing
     if not self.date or self.timing not in Config.TIMINGS:
         self._error_redirect("Error in retrieving event details")
         return
     try:
         self._validate_date(self.date, self.timing)
     except ValidationError as error:
         self._error_redirect(str(error))
         return
     self.format_week: str = Date(self.date).format_week
     self.ballrooms.choices.extend([(room, room) for room in self.hotel.ballrooms])
     self.usage: Optional[Usage] = None
     query = Usage.objects.filter_by(city=self.hotel.city, hotel=self.hotel.name)
     self.usages = query.filter_by(date=Date(self.date).db_date, timing=timing).get()
     self.sort_usages()
     if request.method == "GET" or self.form_type.data != self.GOTO_DATE:
         self.goto_date.data = self.date
         self.goto_timing.data = self.timing
예제 #2
0
def mumbai_no_event():
    start_date = "2020-01-01"
    end_date = "2020-03-31"
    city = "Mumbai"
    query = Usage.objects.filter_by(city=city).filter("date", ">=", start_date)
    usage_data = query.filter("date", "<=", end_date).get()
    usage_data.sort(key=lambda usage: (usage.hotel, usage.date))
    hotels = Hotel.objects.get()
    no_events = list()
    start_date = Date(start_date).date
    days = (Date(end_date).date - start_date).days + 1
    periods = [(day, timing) for day in range(days)
               for timing in Config.TIMINGS]
    for day, timing in periods:
        date = start_date + dt.timedelta(days=day)
        date = Date(date).db_date
        date_usages = [
            usage for usage in usage_data
            if usage.date == date and usage.timing == timing
        ]
        for hotel in hotels:
            if any(usage.hotel == hotel.name for usage in date_usages):
                continue
            usage = Usage()
            usage.hotel = hotel.name
            usage.city = city
            usage.set_date(Date(date).date)
            usage.timing = timing
            usage.no_event = True
            no_events.append(usage.doc_to_dict())
    for usage in no_events:
        print(usage)
    # Usage.create_from_list_of_dict(no_events)
    print(f"{len(no_events)} no events created")
예제 #3
0
 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()
예제 #4
0
 def remove_last_entry(self) -> None:
     if self.last_timing == Config.EVENING:
         self.last_timing = Config.MORNING
     else:
         last_date = Date(self.last_date).date
         if not last_date:
             return
         last_date -= dt.timedelta(days=1)
         self.last_timing = Config.EVENING
         self.last_date = Date(last_date).db_date
     return
예제 #5
0
 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()
예제 #6
0
 def set_last_entry(self, input_date: Union[str, dt.date],
                    timing: str) -> bool:
     date = Date(input_date).date
     if not date:
         return False
     last_date = Date(self.last_date).date
     if last_date and last_date > Date.today():
         last_date = None
     if not last_date or date > last_date:
         self.last_date = Date(date).db_date
         self.last_timing = timing
         return True
     elif date == last_date and self.last_timing == Config.MORNING and timing == Config.EVENING:
         self.last_timing = timing
         return True
     return False
예제 #7
0
 def link_next(self) -> str:
     if self.timing == Config.EVENING:
         date = self.date + dt.timedelta(days=1)
         timing = Config.MORNING
     else:
         date = self.date
         timing = Config.EVENING
     return url_for("usage_manage", hotel_id=self.hotel.id, date=Date(date).db_date, timing=timing)
예제 #8
0
 def __init__(self):
     self.hotels: List[Tuple[str, List[List[str]]]] = list()
     self.header: List[Tuple[int, str, str, str]] = self.generate_header()
     self.hotel: str = current_user.hotel
     self.today: str = Date().format_week
     self.status: Tuple[str, str] = self.STATUS_ERROR
     self.display_status: str = "list-group-item-danger"
     self.generate_hotel_status()
예제 #9
0
 def _validate_date(self, date: dt.date, timing: str):
     if not date:
         raise ValidationError(str())
     start_date = Date(self.hotel.start_date).date
     if date < start_date:
         raise ValidationError(f"Date is before the contract start date ({Date(start_date).format_date})")
     data_entry_date, data_entry_timing = Usage.get_data_entry_date(self.hotel)
     if not data_entry_date:
         raise ValidationError(data_entry_timing)
     if date > data_entry_date:
         raise ValidationError(f"Date is beyond the last data entry date ({Date(data_entry_date).format_date})")
     if date == data_entry_date and data_entry_timing == Config.MORNING and timing == Config.EVENING:
         raise ValidationError(f"Cannot goto Evening till the data entry of Morning is completed")
예제 #10
0
 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
예제 #11
0
def data_entry() -> Response:
    hotel = Hotel.objects.filter_by(city=current_user.city,
                                    name=current_user.hotel).first()
    date, timing = Usage.get_data_entry_date(hotel)
    if not date:
        flash(timing)
        return redirect(url_for("view_dashboard"))
    if not timing:
        flash("All Done - Here are your last evening events")
        timing = Config.EVENING
    return redirect(
        url_for("usage_manage",
                hotel_id=hotel.id,
                date=Date(date).db_date,
                timing=timing))
예제 #12
0
 def update(self):
     if self.form_type.data == self.GOTO_DATE:
         self.redirect = True
         return
     elif self.form_type.data == self.UPLOAD:
         self.hotel.set_last_entry(self.upload_data[-1].date, self.upload_data[-1].timing)
         usages = Usage.create_from_list_of_dict([usage.doc_to_dict() for usage in self.upload_data])
         self.hotel.save()
         if not self.usages:
             self.usages = [u for u in usages if Date(self.date).db_date == u.date and self.timing == u.timing]
     elif self.form_type.data == self.CREATE:
         self.update_default_fields()
         self.update_from_form()
         self.usage.create()
         self.usages.append(self.usage)
         no_event = next((usage for usage in self.usages if usage.no_event), None)
         if no_event:
             self.usages.remove(no_event)
             no_event.delete()
     elif self.form_type.data == self.UPDATE:
         self.update_from_form()
         self.usage.save()
     elif self.form_type.data == self.DELETE:
         self.usages.remove(self.usage)
         self.usage.delete()
         last_date = Date(self.hotel.last_date).date
         if self.date == last_date and not self.usages:
             self.hotel.remove_last_entry()
             self.hotel.save()
     elif self.form_type.data == self.NO_EVENT:
         self.update_default_fields()
         self.usage.no_event = True
         self.usage.create()
         self.usages.append(self.usage)
     self.sort_usages()
     return
예제 #13
0
 def generate_hotel_status(self):
     hotels = Hotel.objects.filter_by(city=current_user.city).get()
     self.hotels = [(hotel.name, list()) for hotel in hotels]
     for index, hotel in enumerate(hotels):
         last_date = Date(hotel.last_date).date
         if last_date and (last_date < hotel.contract[0]
                           or last_date > Date.next_lock_in()):
             last_date = None
         for day in range(self.PAST_DAYS, -1, -1):
             date = Date.next_lock_in() - dt.timedelta(days=day)
             if not last_date:
                 self.add_status(
                     index, day, self.NOT_DONE
                     if hotel.is_contract_valid(date) else self.NA)
                 continue
             if date < last_date:
                 self.add_status(
                     index, day, self.DONE
                     if hotel.is_contract_valid(date) else self.NA)
             elif date > last_date:
                 self.add_status(
                     index, day, self.NOT_DONE
                     if hotel.is_contract_valid(date) else self.NA)
             else:
                 self.add_status(
                     index, day, self.DONE if hotel.last_timing
                     == Config.EVENING else self.PARTIAL)
         if current_user.hotel != hotel.name:
             continue
         end_date = min(Date.yesterday(), hotel.contract[1])
         last_date = last_date or hotel.contract[0]
         if hotel.contract[0] > Date.today():
             self.status = self.STATUS_NO_CONTRACT
             continue
         if last_date >= end_date:
             self.status = self.STATUS_ALL_DONE if last_date > end_date or hotel.last_timing == Config.EVENING \
                 else self.STATUS_PARTIAL
             continue
         days = (end_date - last_date).days
         self.status = (f"{days} days entry remaining",
                        "list-group-item-danger")
     self.hotels.sort(key=itemgetter(0))
     hotel = next(
         (hotel for hotel in self.hotels if hotel[0] == current_user.hotel))
     if hotel:
         self.hotels.remove(hotel)
         self.hotels.insert(0, hotel)
     return
예제 #14
0
 def get_data_entry_date(cls, hotel: Hotel) -> Tuple[Optional[dt.date], str]:
     start_date, end_date = hotel.contract
     today = Date.next_lock_in()
     data_entry_date = Date(hotel.last_date).date
     if end_date < start_date:
         return None, "Invalid Contract"
     if today < start_date:
         return None, "Cannot enter events for future contract"
     if not data_entry_date or data_entry_date < start_date:
         return start_date, Config.MORNING
     if today > end_date and data_entry_date > end_date:
         return end_date, str()
     if hotel.last_timing == Config.EVENING and (data_entry_date == end_date or data_entry_date == today):
         return data_entry_date, str()
     if hotel.last_timing == Config.MORNING and data_entry_date <= today:
         return data_entry_date, Config.EVENING
     if hotel.last_timing == Config.EVENING and data_entry_date < today:
         return data_entry_date + dt.timedelta(days=1), Config.MORNING
     return start_date, Config.MORNING
예제 #15
0
 def set_contract(self, start_date: dt.date, end_date: dt.date) -> None:
     self.start_date = Date(start_date).db_date
     self.end_date = Date(end_date).db_date
예제 #16
0
 def _check_start_period_of_uploaded_file(self):
     first_date, first_timing = Date(self.upload_data[0].date).date, self.upload_data[0].timing
     next_date, next_timing = Usage.get_data_entry_date(self.hotel)
     if (first_date, first_timing) != (next_date, next_timing):
         raise ValidationError(f"Start period ({Date(first_date).format_date} - {first_timing}) does not match with "
                               f"the next data entry period ({Date(next_date).format_date} - {next_timing})")
예제 #17
0
 def format_end_date(self) -> str:
     return Date(self.end_date.data
                 ).format_week if self.end_date.data else "Invalid date"
예제 #18
0
 def validate_filename(self, filename: FileField):
     if self.form_type.data != self.UPLOAD:
         return
     file: FileStorage = filename.data
     if not secure_filename(file.filename):
         raise ValidationError("No file selected for upload")
     file_path = File(current_user.id, "csv").local_path
     file.save(file_path)
     with open(file_path) as csv_file:
         csv_reader = csv.DictReader(csv_file)
         columns = set(csv_reader.fieldnames)
         csv_reader = [row for row in csv_reader]
     if columns != {HDR.DATE, HDR.TIMING, HDR.NO_EVENT, HDR.CLIENT, HDR.MEAL, HDR.TYPE, HDR.BALLROOM, HDR.EVENT}:
         raise ValidationError("Invalid column names in the csv file")
     for row in csv_reader:
         usage = Usage()
         usage.hotel = self.hotel.name
         usage.city = self.hotel.city
         date = Date.from_dd_mmm_yyyy(row[HDR.DATE]).date
         if not date:
             self.add_errors(row, HDR.DATE)
             continue
         usage.set_date(date)
         if row[HDR.TIMING] not in Config.TIMINGS:
             self.add_errors(row, HDR.TIMING)
             continue
         usage.timing = row[HDR.TIMING]
         if row[HDR.NO_EVENT] == HDR.NO_EVENT:
             usage.no_event = True
             self.upload_data.append(usage)
             continue
         if not row[HDR.CLIENT]:
             self.add_errors(row, HDR.CLIENT)
             continue
         usage.client = row[HDR.CLIENT]
         if usage.timing == Config.MORNING:
             if row[HDR.MEAL] not in Config.MORNING_MEALS:
                 self.add_errors(row, HDR.MEAL)
                 continue
             usage.meals = [Config.BREAKFAST, Config.LUNCH] if row[HDR.MEAL] == Config.BREAKFAST_LUNCH \
                 else [row[HDR.MEAL]]
         else:
             if row[HDR.MEAL] not in Config.EVENING_MEALS:
                 self.add_errors(row, HDR.MEAL)
                 continue
             usage.meals = [Config.HI_TEA, Config.DINNER] if row[HDR.MEAL] == Config.HI_TEA_DINNER \
                 else [row[HDR.MEAL]]
         if row[HDR.TYPE] not in Config.EVENTS:
             self.add_errors(row, HDR.TYPE)
             continue
         usage.event_type = row[HDR.TYPE]
         ballrooms = [room.strip() for room in row[HDR.BALLROOM].split(",")]
         if any(room not in self.hotel.ballrooms for room in ballrooms):
             self.add_errors(row, HDR.BALLROOM)
             continue
         usage.ballrooms = ballrooms
         self.hotel.set_ballroom_used(ballrooms)
         usage.event_description = row[HDR.EVENT]
         self.upload_data.append(usage)
     if self.upload_errors:
         raise ValidationError("Field specific errors. Fields with red highlight have errors.")
     if not self.upload_data:
         raise ValidationError("There are no events in the csv file")
     self.upload_data.sort(key=lambda usage_item: usage_item.timing, reverse=True)
     self.upload_data.sort(key=lambda usage_item: usage_item.date)
     if current_user.role != Config.ADMIN:
         self._check_start_period_of_uploaded_file()
     last_date, lock_in, end_date = Date(self.upload_data[-1].date).date, Date.next_lock_in(), self.hotel.contract[1]
     if last_date > lock_in:
         raise ValidationError(f"End period ({Date(last_date).format_date}) cannot be greater than the "
                               f"next lock in period ({Date(lock_in).format_date})")
     if last_date > end_date:
         raise ValidationError(f"End period ({Date(last_date).format_date}) cannot be greater than the "
                               f"contract end date ({Date(end_date).format_date})")
     previous_date = None
     for date, date_usages in itertools.groupby(self.upload_data, key=lambda usage_item: usage_item.date):
         date = Date(date).date
         date_usages = list(date_usages)
         if not any(usage.timing == Config.MORNING for usage in date_usages):
             raise ValidationError(f"Date {Date(date).format_date} does not have {Config.MORNING} events")
         if not any(usage.timing == Config.EVENING for usage in date_usages):
             raise ValidationError(f"Date {Date(date).format_date} does not have {Config.EVENING} events")
         for timing, timing_usages in itertools.groupby(date_usages, key=lambda usage_item: usage_item.timing):
             seen = set()
             for usage in timing_usages:
                 if usage.client in seen:
                     raise ValidationError(f"Duplicate client name {usage.client} for the period "
                                           f"{Date(date).format_date} - {timing}")
                 seen.add(usage.client)
         if previous_date and (date - previous_date).days != 1:
             raise ValidationError(f"There are missing events between {Date(previous_date).format_date} and "
                                   f"{Date(date).format_date}")
         previous_date = date
     return
예제 #19
0
 def link_goto(self) -> str:
     return url_for("usage_manage", hotel_id=self.hotel.id, date=Date(self.goto_date.data).db_date,
                    timing=self.goto_timing.data)
예제 #20
0
 def formatted_last_date(self) -> str:
     return Date(self.last_date).format_date
예제 #21
0
 def formatted_contract(self) -> Tuple[str, str]:
     return Date(self.start_date).format_date, Date(
         self.end_date).format_date
예제 #22
0
 def contract(self) -> Contract:
     return self.Contract(
         Date(self.start_date).date,
         Date(self.end_date).date)
예제 #23
0
 def set_date(self, date: dt.date) -> bool:
     self.date = Date(date).db_date
     self.day = date.strftime("%A")
     self.weekday = self.day not in ("Saturday", "Sunday")
     self.month = date.strftime("%Y-%m")
     return True