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
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")
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 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
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 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
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)
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()
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")
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 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))
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
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
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
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
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})")
def format_end_date(self) -> str: return Date(self.end_date.data ).format_week if self.end_date.data else "Invalid date"
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
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)
def formatted_last_date(self) -> str: return Date(self.last_date).format_date
def formatted_contract(self) -> Tuple[str, str]: return Date(self.start_date).format_date, Date( self.end_date).format_date
def contract(self) -> Contract: return self.Contract( Date(self.start_date).date, Date(self.end_date).date)
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