diff --git a/epictrack-api/src/api/reports/resource_forecast_report.py b/epictrack-api/src/api/reports/resource_forecast_report.py index 3c2e085c1..c5a66d931 100644 --- a/epictrack-api/src/api/reports/resource_forecast_report.py +++ b/epictrack-api/src/api/reports/resource_forecast_report.py @@ -4,7 +4,7 @@ from datetime import datetime from functools import partial from io import BytesIO -from typing import IO, List, Set, Tuple +from typing import IO, List, Tuple from dateutil import rrule from reportlab.lib import colors @@ -15,17 +15,33 @@ from reportlab.platypus import NextPageTemplate, Paragraph, Table, TableStyle from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate from reportlab.platypus.frames import Frame -from sqlalchemy import and_, func, or_, select +from sqlalchemy import and_, func, or_ from sqlalchemy.dialects.postgresql import DATERANGE from sqlalchemy.orm import aliased from api.models import ( - EAAct, EAOTeam, Event, FederalInvolvement, PhaseCode, Project, Region, Staff, StaffWorkRole, SubType, Type, Work, - WorkPhase, WorkType, db) + EAAct, + EAOTeam, + Event, + FederalInvolvement, + PhaseCode, + Project, + Region, + Staff, + StaffWorkRole, + SubType, + Type, + Work, + WorkPhase, + WorkType, + db, +) from api.models.event_configuration import EventConfiguration from api.models.event_template import EventPositionEnum, EventTemplateVisibilityEnum from api.models.work import WorkStateEnum from api.models.work_type import WorkTypeEnum +from api.models.event_type import EventTypeEnum +from api.models.phase_code import PhaseVisibilityEnum from .report_factory import ReportFactory @@ -68,22 +84,6 @@ def __init__(self, filters): if self.filters and "exclude" in self.filters: self.excluded_items = self.filters["exclude"] self.report_title = "EAO Resource Forecast" - start_event_configurations = ( - db.session.query( - func.min(EventConfiguration.id).label("event_configuration_id"), - EventConfiguration.work_phase_id, - ) - .filter( - EventConfiguration.visibility - == EventTemplateVisibilityEnum.MANDATORY.value, - EventConfiguration.event_position == EventPositionEnum.START.value, - ) - .group_by(EventConfiguration.work_phase_id) - .all() - ) - self.start_event_configurations = [ - x.event_configuration_id for x in start_event_configurations - ] self.months = [] self.month_labels = [] self.report_cells = { @@ -137,11 +137,12 @@ def __init__(self, filters): } self.end_date = None - def _filter_work_events(self, work_id, events): - work_events = list(filter(lambda x: work_id == x.work_id, events)) - return work_events + def _filter_work_events(self, work_id: int, events: [Event]) -> [Event]: + """Filter the events based on given work id""" + return [event for event in events if event["work_id"] == work_id] - def _get_report_meta_data(self, report_date: datetime, available_width: float): + def _get_pdf_output_layout(self, report_date: datetime, available_width: float): + """Returns the pdf output layout""" section_headings = [] cell_headings = [] styles = [] @@ -183,15 +184,138 @@ def _get_report_meta_data(self, report_date: datetime, available_width: float): return headers, cell_keys, styles, cell_widths def _fetch_data(self, report_date: datetime): - """Fetches the relevant data for EA Resource Forecast Report""" - self._set_month_labels(report_date) - works = self._get_works(report_date) - work_ids = set((work.work_id for work in works)) - works = super()._format_data(works) - events = self._get_events(work_ids) - events = {y: self._filter_work_events(y, events) for y in work_ids} - results = self._prepare_fetch_results(works, events) - return results + """Find and return works that are started before end date and did not end before report date""" + env_region = aliased(Region) + nrs_region = aliased(Region) + responsible_epd = aliased(Staff) + work_lead = aliased(Staff) + less_than_end_date_query = self._get_less_than_end_date_query() + greater_than_report_date_query = self._get_greater_than_report_date_query(report_date) + + works = ( + Project.query.filter( + Project.is_project_closed.is_(False), + Project.is_deleted.is_(False), + Project.is_active.is_(True), + ) + .join( + Work, + and_( + Work.project_id == Project.id, + Work.work_state.in_( + [ + WorkStateEnum.IN_PROGRESS.value, + WorkStateEnum.SUSPENDED.value, + ] + ), + Work.is_active.is_(True), + Work.is_deleted.is_(False), + ), + ) + .join(WorkPhase, WorkPhase.id == Work.current_work_phase_id) + .join(PhaseCode, PhaseCode.id == WorkPhase.phase_id) + .join(WorkType, Work.work_type_id == WorkType.id) + .join(EAAct, Work.ea_act_id == EAAct.id) + .outerjoin(EAOTeam, Work.eao_team_id == EAOTeam.id) + .join( + FederalInvolvement, Work.federal_involvement_id == FederalInvolvement.id + ) + .join(SubType, Project.sub_type_id == SubType.id) + .join(Type, Project.type_id == Type.id) + .join(env_region, env_region.id == Project.region_id_env) + .join(nrs_region, nrs_region.id == Project.region_id_flnro) + .join( + less_than_end_date_query, Work.id == less_than_end_date_query.c.work_id + ) + .join( + greater_than_report_date_query, + Work.id == greater_than_report_date_query.c.work_id, + ) + .outerjoin(responsible_epd, responsible_epd.id == Work.responsible_epd_id) + .outerjoin(work_lead, work_lead.id == Work.work_lead_id) + .add_columns( + Work.title.label("work_title"), + Project.capital_investment.label("capital_investment"), + WorkType.name.label("ea_type"), + WorkType.report_title.label("ea_type_label"), + WorkType.sort_order.label("ea_type_sort_order"), + PhaseCode.name.label("project_phase"), + EAAct.name.label("ea_act"), + FederalInvolvement.name.label("iaac"), + SubType.short_name.label("sub_type"), + Type.short_name.label("type"), + EAOTeam.name.label("eao_team"), + env_region.name.label("env_region"), + nrs_region.name.label("nrs_region"), + Work.id.label("work_id"), + responsible_epd.full_name.label("responsible_epd"), + work_lead.full_name.label("work_lead"), + Work.id.label("work_id"), + func.concat(SubType.short_name, " (", Type.short_name, ")").label( + "sector(sub)" + ), + Project.fte_positions_operation.label("fte_positions_operation"), + Project.fte_positions_construction.label("fte_positions_construction"), + WorkType.id.label("work_type_id"), + ) + .all() + ) + return works + + def _get_less_than_end_date_query(self): + """Returns subquery which returns work_ids which matches the less than end_date condition""" + return ( + db.session.query(Event.work_id) + .join( + EventConfiguration, + and_( + Event.event_configuration_id == EventConfiguration.id, + EventConfiguration.event_position == EventPositionEnum.START.value, + ), + ) + .join( + WorkPhase, + and_( + WorkPhase.id == EventConfiguration.work_phase_id, + WorkPhase.is_active.is_(True), + WorkPhase.sort_order + == 1, # indicate the work phase is the first one + WorkPhase.is_deleted.is_(False), + ), + ) + .filter( + func.coalesce(Event.actual_date, Event.anticipated_date) + <= self.end_date + ) + .subquery() + ) + + def _get_greater_than_report_date_query(self, report_date): + """Returns work_ids matches event actual >= report date""" + end_work_phase_query = ( + db.session.query( + func.max(WorkPhase.id).label("end_phase_id"), + ) + .filter(WorkPhase.is_active.is_(True), WorkPhase.is_deleted.is_(False)) + .group_by(WorkPhase.work_id) + .subquery() + ) + return ( + db.session.query(Event.work_id) + .join( + EventConfiguration, + and_( + Event.event_configuration_id == EventConfiguration.id, + EventConfiguration.event_position == EventPositionEnum.END.value, + ), + ) + .join( + end_work_phase_query, + end_work_phase_query.c.end_phase_id == EventConfiguration.work_phase_id, + ) + .filter(or_(Event.actual_date.is_(None), Event.actual_date >= report_date)) + .subquery() + ) def _filter_data(self, data_items): """Filter the data based on applied filters""" @@ -242,12 +366,18 @@ def _format_data(self, data): def generate_report(self, report_date, return_type): """Generates a report and returns it""" - data = self._fetch_data(report_date) - data = self._format_data(data) + self._set_month_labels(report_date) + works = self._fetch_data(report_date) + work_ids = set((work.work_id for work in works)) + works = super()._format_data(works) + events = self._get_events(work_ids) + start_events = self._filter_start_events(events) + start_events = {y: self._filter_work_events(y, start_events) for y in work_ids} + work_data = self._update_month_labels(works, start_events) + data = self._format_data(work_data) if not data: return {}, None - valid_work_ids = self._get_valid_work_ids(report_date) - second_phases = self._fetch_second_phases(valid_work_ids) + second_phases = self._fetch_second_phases(events) data = self._sort_data(data, second_phases) if return_type == "json" and data: return data, None @@ -257,6 +387,57 @@ def generate_report(self, report_date, return_type): pdf_stream = self._generate_pdf(formatted_data, report_date) return pdf_stream.getvalue(), f"{self.report_title}_{report_date:%Y_%m_%d}.pdf" + def _update_month_labels(self, works, start_events): + """Update month labels in the work result""" + results = defaultdict(list) + for work_id, work_data in works.items(): + work = work_data[0] + for index, month in enumerate(self.months[1:]): + month_events = list( + filter( + lambda x: x["start_date"].date() <= month, + start_events[work_id], + ) + ) + if month_events: + month_events = sorted(month_events, key=lambda x: x["start_date"]) + latest_event = month_events[-1] + work.update( + { + self.month_labels[index]: latest_event["event_phase"], + f"{self.month_labels[index]}_color": latest_event[ + "phase_color" + ], + } + ) + else: + work.update( + { + self.month_labels[index]: "", + f"{self.month_labels[index]}_color": "#FFFFFF", + } + ) + work_data[0] = work + results[work_id] = work_data + return results + + def _filter_start_events(self, events: [Event]) -> [Event]: + """Filter the start events of each phase per work""" + start_events = [ + { + "work_id": event.work_id, + "start_date": event.actual_date + if event.actual_date + else event.anticipated_date, + "event_phase": event.event_configuration.work_phase.name, + "phase_color": event.event_configuration.work_phase.phase.color, + } + for event in events + if event.event_configuration.event_position.value + == EventPositionEnum.START.value + ] + return start_events + def _add_months( self, start: datetime, months: int, set_to_last: bool = True ) -> datetime: @@ -269,76 +450,6 @@ def _add_months( result = result.replace(day=monthrange(start.year + year_offset, month)[1]) return result - def _get_valid_work_ids(self, report_date: datetime) -> Set[int]: - """Find and return works that are started before end date and did not end before report date""" - end_work_phase_query = ( - db.session.query( - func.max(WorkPhase.id).label("end_phase_id"), - ) - .group_by(WorkPhase.work_id) - .subquery() - ) - works_started = ( - db.session.execute( - select(WorkPhase.work_id) - .join( - EventConfiguration, - and_( - EventConfiguration.work_phase_id == WorkPhase.id, - EventConfiguration.event_position == EventPositionEnum.START.value, - WorkPhase.sort_order == 1, - ), - ) - .join(Event, Event.event_configuration_id == EventConfiguration.id) - .where( - and_( - func.coalesce(Event.actual_date, Event.anticipated_date) <= self.end_date, - WorkPhase.sort_order == 1, - Work.work_state.in_( - [ - WorkStateEnum.IN_PROGRESS.value, - WorkStateEnum.SUSPENDED.value, - ] - ), - ) - ) - .order_by(WorkPhase.work_id, WorkPhase.phase_id.asc()) - .distinct(WorkPhase.work_id) - ) - .scalars() - .all() - ) - - works_not_finished = ( - db.session.execute( - select(WorkPhase.work_id) - .join( - EventConfiguration, - and_( - EventConfiguration.work_phase_id == WorkPhase.id, - EventConfiguration.event_position == EventPositionEnum.END.value, - ), - ) - .join(Event, Event.event_configuration_id == EventConfiguration.id) - .join( - end_work_phase_query, - WorkPhase.id == end_work_phase_query.c.end_phase_id, - ) - .where( - or_(Event.actual_date.is_(None), Event.actual_date >= report_date), - Work.work_state.in_( - [WorkStateEnum.IN_PROGRESS.value, WorkStateEnum.SUSPENDED.value] - ), - ) - .order_by(WorkPhase.work_id, WorkPhase.phase_id.desc()) - .distinct(WorkPhase.work_id) - ) - .scalars() - .all() - ) - valid_work_ids = set(works_not_finished) & set(works_started) - return valid_work_ids - def _set_month_labels(self, report_date: datetime) -> None: """Calculate and set month related attributes to the self""" report_start_date = report_date.date().replace(day=1) @@ -405,37 +516,59 @@ def _sort_data(self, data, second_phases) -> List: ) others = self._sort_data_by_work_type(data, WorkTypeEnum.OTHER.value) - sorted_data = assessments + exemption_orders + amendments + order_transfers + minister_designations - sorted_data += ceao_designations + project_notifications + extensions + substantial_start_decisions + sorted_data = ( + assessments + + exemption_orders + + amendments + + order_transfers + + minister_designations + ) + sorted_data += ( + ceao_designations + + project_notifications + + extensions + + substantial_start_decisions + ) sorted_data += order_suspensions + order_cancellations + others return sorted_data - def _fetch_second_phases(self, work_ids) -> List[WorkPhase]: + def _fetch_second_phases(self, events) -> List[WorkPhase]: """Fetch the second work phases for given work ids""" - second_work_phases = ( - db.session.query(WorkPhase) - .join(Event, Event.work_id == WorkPhase.work_id) - .join( - EventConfiguration, - EventConfiguration.id == Event.event_configuration_id, - ) - .filter( - WorkPhase.work_id.in_(work_ids), - WorkPhase.sort_order == 2, - EventConfiguration.event_position == EventPositionEnum.START.value, - ) - .add_columns( - Event.actual_date.label("actual_date"), - Event.anticipated_date.label("anticipated_date"), - ) - .all() - ) + # second_work_phases = ( + # db.session.query(WorkPhase) + # .join(Event, Event.work_id == WorkPhase.work_id) + # .join( + # EventConfiguration, + # EventConfiguration.id == Event.event_configuration_id, + # ) + # .filter( + # WorkPhase.work_id.in_(work_ids), + # WorkPhase.sort_order == 2, + # EventConfiguration.event_position == EventPositionEnum.START.value, + # ) + # .add_columns( + # Event.actual_date.label("actual_date"), + # Event.anticipated_date.label("anticipated_date"), + # ) + # .all() + # ) + second_work_phases = [ + { + "work_phase": event.event_configuration.work_phase, + "actual_date": event.actual_date, + "anticipated_date": event.anticipated_date, + } + for event in events + if event.event_configuration.work_phase.sort_order == 2 + and event.event_configuration.event_position.value + == EventPositionEnum.START.value + ] return second_work_phases def _find_work_second_phase(self, second_phases, work_id) -> WorkPhase: """Find the second work phase for given work id""" second_phase = next( - (x for x in second_phases if x.WorkPhase.work_id == work_id), None + (x for x in second_phases if x["work_phase"].work_id == work_id), None ) return second_phase @@ -446,13 +579,17 @@ def _sort_data_by_work_type(self, data, work_type_id, second_phases=None) -> Lis high_priority = [ x for x in temp_data - if self._find_work_second_phase(second_phases, x["work_id"]).actual_date + if self._find_work_second_phase(second_phases, x["work_id"])[ + "actual_date" + ] ] high_priority = sorted(high_priority, key=lambda k: k["work_title"]) rest = [ x for x in temp_data - if self._find_work_second_phase(second_phases, x["work_id"]).actual_date + if self._find_work_second_phase(second_phases, x["work_id"])[ + "actual_date" + ] is None ] rest = sorted(rest, key=lambda k: k["work_title"]) @@ -462,94 +599,44 @@ def _sort_data_by_work_type(self, data, work_type_id, second_phases=None) -> Lis sorted_data = sorted(sorted_data, key=lambda k: k["work_title"]) return sorted_data - def _get_works(self, report_date) -> List[Work]: - """Fetch relevant works for the given report date.""" - env_region = aliased(Region) - nrs_region = aliased(Region) - responsible_epd = aliased(Staff) - work_lead = aliased(Staff) - valid_work_ids = self._get_valid_work_ids(report_date) - works = ( - Project.query.filter( - Project.is_project_closed.is_(False), - Project.is_deleted.is_(False), - Project.is_active.is_(True), - ) - .join(Work, Work.project_id == Project.id) - .join(WorkPhase, WorkPhase.id == Work.current_work_phase_id) - .join(PhaseCode, PhaseCode.id == WorkPhase.phase_id) - .join(WorkType, Work.work_type_id == WorkType.id) - .join(EAAct, Work.ea_act_id == EAAct.id) - .outerjoin(EAOTeam, Work.eao_team_id == EAOTeam.id) - .join( - FederalInvolvement, Work.federal_involvement_id == FederalInvolvement.id - ) - .join(SubType, Project.sub_type_id == SubType.id) - .join(Type, Project.type_id == Type.id) - .join(env_region, env_region.id == Project.region_id_env) - .join(nrs_region, nrs_region.id == Project.region_id_flnro) - .outerjoin(responsible_epd, responsible_epd.id == Work.responsible_epd_id) - .outerjoin(work_lead, work_lead.id == Work.work_lead_id) - .filter( - Work.is_active.is_(True), - Work.is_deleted.is_(False), - Work.id.in_(valid_work_ids), - ) - .add_columns( - Work.title.label("work_title"), - Project.capital_investment.label("capital_investment"), - WorkType.name.label("ea_type"), - WorkType.report_title.label("ea_type_label"), - WorkType.sort_order.label("ea_type_sort_order"), - PhaseCode.name.label("project_phase"), - EAAct.name.label("ea_act"), - FederalInvolvement.name.label("iaac"), - SubType.short_name.label("sub_type"), - Type.short_name.label("type"), - EAOTeam.name.label("eao_team"), - env_region.name.label("env_region"), - nrs_region.name.label("nrs_region"), - Work.id.label("work_id"), - responsible_epd.full_name.label("responsible_epd"), - work_lead.full_name.label("work_lead"), - Work.id.label("work_id"), - func.concat(SubType.short_name, " (", Type.short_name, ")").label( - "sector(sub)" - ), - Project.fte_positions_operation.label("fte_positions_operation"), - Project.fte_positions_construction.label("fte_positions_construction"), - WorkType.id.label("work_type_id"), - ) - .all() - ) - return works - - def _get_events(self, work_ids) -> List[Event]: - """Fetch event information for given work ids""" - events = ( + def _get_events(self, work_ids: [int]) -> List[Event]: + """Returns the start event of each of the work phases for the works""" + return ( Event.query.filter( Event.work_id.in_(work_ids), - Event.event_configuration_id.in_(self.start_event_configurations), - func.coalesce(Event.actual_date, Event.anticipated_date) <= self.end_date, + # func.coalesce(Event.actual_date, Event.anticipated_date) + # <= self.end_date, ) .join( EventConfiguration, - Event.event_configuration_id == EventConfiguration.id, + and_( + Event.event_configuration_id == EventConfiguration.id, + EventConfiguration.visibility + == EventTemplateVisibilityEnum.MANDATORY.value, + EventConfiguration.is_active.is_(True), + EventConfiguration.is_deleted.is_(False), + ), ) - .join(WorkPhase, EventConfiguration.work_phase_id == WorkPhase.id) - .join(PhaseCode, WorkPhase.phase_id == PhaseCode.id) - .order_by(func.coalesce(Event.actual_date, Event.anticipated_date)) - .add_columns( - Event.work_id.label("work_id"), - func.coalesce(Event.actual_date, Event.anticipated_date).label( - "start_date" + .join( + WorkPhase, + and_( + EventConfiguration.work_phase_id == WorkPhase.id, + WorkPhase.is_active.is_(True), + WorkPhase.is_deleted.is_(False), ), - PhaseCode.name.label("event_phase"), - PhaseCode.color.label("phase_color"), ) + .join(PhaseCode, WorkPhase.phase_id == PhaseCode.id) + .order_by(func.coalesce(Event.actual_date, Event.anticipated_date)) + # .add_columns( + # Event.work_id.label("work_id"), + # func.coalesce(Event.actual_date, Event.anticipated_date).label( + # "start_date" + # ), + # PhaseCode.name.label("event_phase"), + # PhaseCode.color.label("phase_color"), + # ) .all() ) - return events def _prepare_fetch_results(self, works, events) -> dict: """Matches events with corresponding works and returns formatted data""" @@ -585,51 +672,33 @@ def _prepare_fetch_results(self, works, events) -> dict: def _get_referral_timing(self, work_id) -> Event: """Find the referral event for given work id""" - referral_timing_query = ( - db.session.query(PhaseCode) + referral_date = ( + db.session.query(func.coalesce(Event.actual_date, Event.anticipated_date)) .join( - WorkPhase, + EventConfiguration, and_( - PhaseCode.id == WorkPhase.phase_id, - WorkPhase.work_id == work_id, + Event.event_configuration_id == EventConfiguration.id, + EventConfiguration.event_type_id == EventTypeEnum.REFERRAL.value, + EventConfiguration.visibility + == EventTemplateVisibilityEnum.MANDATORY.value, ), ) - .join(WorkType, PhaseCode.work_type_id == WorkType.id) .join( - EventConfiguration, + WorkPhase, and_( EventConfiguration.work_phase_id == WorkPhase.id, - EventConfiguration.visibility == EventTemplateVisibilityEnum.MANDATORY, + WorkPhase.is_active.is_(True), + WorkPhase.is_deleted.is_(False), + WorkPhase.visibility == PhaseVisibilityEnum.REGULAR.value, + WorkPhase.is_completed.is_(False), + WorkPhase.work_id == work_id, ), ) - .join(Event, EventConfiguration.id == Event.event_configuration_id) - .filter( - and_( - WorkType.id == PhaseCode.work_type_id, - Event.work_id == work_id, - ) - ) - .add_columns(EventConfiguration.id.label("event_configuration_id")) - .group_by(PhaseCode.id, EventConfiguration.id) - .order_by(PhaseCode.id.desc()) - ) - - if referral_timing_query.count() > 1: - referral_timing_obj = referral_timing_query.offset(1).first() - else: - referral_timing_obj = referral_timing_query.first() - referral_timing = ( - Event.query.filter( - Event.event_configuration_id == referral_timing_obj.event_configuration_id - ) - .add_columns( - func.coalesce(Event.actual_date, Event.anticipated_date).label( - "event_start_date" - ) - ) - .first() + .order_by(WorkPhase.sort_order.desc()) + .limit(1) + .scalar() ) - return referral_timing + return referral_date def _get_work_team_members(self, work_id) -> Tuple[List[str], str]: """Fetch and return team members by work id""" @@ -684,7 +753,8 @@ def _get_table_data(self, data, column_count, cells): Paragraph( f"{ea_type_label.upper()}({len(projects)})", normal_style ) - ] + [""] * (column_count - 1) + ] + + [""] * (column_count - 1) ) normal_style.textColor = colors.black styles.append(("SPAN", (0, row_index), (-1, row_index))) @@ -728,12 +798,12 @@ def _get_table_data(self, data, column_count, cells): def _handle_months(self, work_data) -> dict: """Update the work data to include relevant month information.""" - referral_timing = self._get_referral_timing(work_data["work_id"]) - work_data["referral_timing"] = f"{referral_timing.event_start_date:%B %d, %Y}" + referral_date = self._get_referral_timing(work_data["work_id"]) + work_data["referral_timing"] = f"{referral_date:%B %d, %Y}" months = [] referral_month_index = len(self.month_labels) referral_month = next( - (x for x in self.months if referral_timing.event_start_date.date() <= x), + (x for x in self.months if referral_date.date() <= x), None, ) if referral_month: @@ -763,7 +833,9 @@ def _generate_pdf(self, data, report_date) -> IO: id="large_table", ) page_template = PageTemplate( - id="LaterPages", frames=[page_table_frame], onPage=self._on_every_page(report_date) + id="LaterPages", + frames=[page_table_frame], + onPage=self._on_every_page(report_date), ) doc.addPageTemplates(page_template) story = [NextPageTemplate(["*", "LaterPages"])] @@ -786,7 +858,9 @@ def _generate_pdf(self, data, report_date) -> IO: ("ALIGN", (0, 2), (-1, -1), "LEFT"), ("FONTNAME", (0, 2), (-1, -1), "Helvetica"), ("FONTNAME", (0, 0), (-1, 1), "Helvetica-Bold"), - ] + styles + table_styles + ] + + styles + + table_styles ) ) story.append(table) @@ -796,6 +870,7 @@ def _generate_pdf(self, data, report_date) -> IO: def _on_every_page(self, report_date: datetime): """Adds default information for each page.""" + def add_default_info(canvas, doc): """Adds default information to the page.""" normal_style, _ = self._get_styles() @@ -822,6 +897,7 @@ def add_default_info(canvas, doc): subheading.wrap(doc.width, inch * 0.5) subheading.drawOn(canvas, doc.leftMargin, doc.height + inch * 1) canvas.restoreState() + return add_default_info def _get_quarter_section_meta_data( diff --git a/epictrack-api/src/api/templates/event_templates/assessment/001_EAC_Assessment.xlsx b/epictrack-api/src/api/templates/event_templates/assessment/001_EAC_Assessment.xlsx index 6895a1c74..34fd0122a 100644 Binary files a/epictrack-api/src/api/templates/event_templates/assessment/001_EAC_Assessment.xlsx and b/epictrack-api/src/api/templates/event_templates/assessment/001_EAC_Assessment.xlsx differ diff --git a/epictrack-web/src/components/reports/resourceForecast/ResourceForecast.tsx b/epictrack-web/src/components/reports/resourceForecast/ResourceForecast.tsx index 1bffe4132..763731446 100644 --- a/epictrack-web/src/components/reports/resourceForecast/ResourceForecast.tsx +++ b/epictrack-web/src/components/reports/resourceForecast/ResourceForecast.tsx @@ -1,16 +1,13 @@ import React from "react"; import { - Alert, Autocomplete, Box, - Container, Grid, IconButton, TextField, Tooltip, } from "@mui/material"; import { - MaterialReactTable, MRT_ColumnDef, MRT_ColumnFiltersState, MRT_ToggleFullScreenButton, @@ -24,6 +21,7 @@ import { RESULT_STATUS, REPORT_TYPE, DISPLAY_DATE_FORMAT, + COMMON_ERROR_MESSAGE, } from "../../../constants/application-constant"; import ReportService from "../../../services/reportService"; import { dateUtils } from "../../../utils"; @@ -32,12 +30,14 @@ import ClearAllIcon from "@mui/icons-material/ClearAll"; import FileDownloadIcon from "@mui/icons-material/FileDownload"; import ReportHeader from "../shared/report-header/ReportHeader"; import { ETPageContainer } from "../../shared"; +import MasterTrackTable from "components/shared/MasterTrackTable"; +import { showNotification } from "components/shared/notificationProvider"; export default function ResourceForecast() { const [reportDate, setReportDate] = React.useState(""); const [showReportDateBanner, setShowReportDateBanner] = React.useState(false); - const [resultStatus, setResultStatus] = React.useState(); + const [isLoading, setIsLoading] = React.useState(false); const [rfData, setRFData] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState([]); @@ -138,6 +138,7 @@ export default function ResourceForecast() { flexWrap: "wrap", alignContent: "center", justifyContent: "center", + textWrap: "wrap", padding: "1rem", }} > @@ -312,7 +313,7 @@ export default function ResourceForecast() { ] ); const fetchReportData = React.useCallback(async () => { - setResultStatus(RESULT_STATUS.LOADING); + setIsLoading(true); try { const reportData = await ReportService.fetchReportData( REPORT_TYPE.RESOURCE_FORECAST, @@ -320,8 +321,8 @@ export default function ResourceForecast() { report_date: reportDate, } ); - setResultStatus(RESULT_STATUS.LOADED); - if (reportData.status === 200) { + setIsLoading(false); + if (reportData.status && reportData.status === 200) { const data = reportData.data as never[]; data.forEach((element) => { Object.keys(element).forEach( @@ -329,13 +330,14 @@ export default function ResourceForecast() { ); }); setRFData(data); - } - if (reportData.status === 204) { - setResultStatus(RESULT_STATUS.NO_RECORD); + } else { setRFData([]); } } catch (error) { - setResultStatus(RESULT_STATUS.ERROR); + showNotification(COMMON_ERROR_MESSAGE, { + type: "error", + }); + setRFData([]); } }, [reportDate]); const downloadPDFReport = React.useCallback(async () => { @@ -362,7 +364,9 @@ export default function ResourceForecast() { document.body.appendChild(link); link.click(); } catch (error) { - setResultStatus(RESULT_STATUS.ERROR); + showNotification(COMMON_ERROR_MESSAGE, { + type: "error", + }); } }, [reportDate, filters, fetchReportData]); return ( @@ -383,65 +387,55 @@ export default function ResourceForecast() { /> - {resultStatus !== RESULT_STATUS.ERROR && ( - ( - <> - - - {/* add your own custom print button or something */} - - { - setColumnFilters([]); - setColumnVisibility({}); - setGlobalFilter(undefined); - }} - > - - - - - exportToCsv(table)}> - - - - - - )} - data={rfData} - /> - )} - {resultStatus === RESULT_STATUS.ERROR && ( - - - Error occured during processing. Please try again after some time. - - - )} + ( + <> + + + {/* add your own custom print button or something */} + + { + setColumnFilters([]); + setColumnVisibility({}); + setGlobalFilter(undefined); + }} + > + + + + + exportToCsv(table)}> + + + + + + )} + data={rfData} + /> );