Addition of turnaround time tracking.
This commit is contained in:
@@ -1,3 +1,7 @@
|
|||||||
|
## 202412.02
|
||||||
|
|
||||||
|
- Addition of turnaround time tracking
|
||||||
|
|
||||||
## 202411.05
|
## 202411.05
|
||||||
|
|
||||||
- Can now calculate turnaround time including holidays.
|
- Can now calculate turnaround time including holidays.
|
||||||
|
|||||||
@@ -69,6 +69,6 @@ def update_log(mapper, connection, target):
|
|||||||
else:
|
else:
|
||||||
logger.info(f"No changes detected, not updating logs.")
|
logger.info(f"No changes detected, not updating logs.")
|
||||||
|
|
||||||
if ctx.database_schema == "sqlite":
|
# if ctx.database_schema == "sqlite":
|
||||||
event.listen(LogMixin, 'after_update', update_log, propagate=True)
|
event.listen(LogMixin, 'after_update', update_log, propagate=True)
|
||||||
event.listen(LogMixin, 'after_insert', update_log, propagate=True)
|
event.listen(LogMixin, 'after_insert', update_log, propagate=True)
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ class Control(BaseClass):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def make_parent_buttons(cls, parent: QWidget) -> None:
|
def make_parent_buttons(cls, parent: QWidget) -> None:
|
||||||
"""
|
"""
|
||||||
Super that will make buttons in a CustomFigure. Made to be overrided.
|
Super that will make buttons in a CustomFigure. Made to be overridden.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parent (QWidget): chart holding widget to add buttons to.
|
parent (QWidget): chart holding widget to add buttons to.
|
||||||
@@ -299,6 +299,10 @@ class Control(BaseClass):
|
|||||||
|
|
||||||
|
|
||||||
class PCRControl(Control):
|
class PCRControl(Control):
|
||||||
|
"""
|
||||||
|
Class made to hold info from Design & Analysis software.
|
||||||
|
"""
|
||||||
|
|
||||||
id = Column(INTEGER, ForeignKey('_control.id'), primary_key=True)
|
id = Column(INTEGER, ForeignKey('_control.id'), primary_key=True)
|
||||||
subtype = Column(String(16)) #: PC or NC
|
subtype = Column(String(16)) #: PC or NC
|
||||||
target = Column(String(16)) #: N1, N2, etc.
|
target = Column(String(16)) #: N1, N2, etc.
|
||||||
@@ -348,7 +352,7 @@ class PCRControl(Control):
|
|||||||
df = df[df.ct > 0.0]
|
df = df[df.ct > 0.0]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
df = df
|
df = df
|
||||||
fig = PCRFigure(df=df, modes=[])
|
fig = PCRFigure(df=df, modes=[], settings=chart_settings)
|
||||||
return report, fig
|
return report, fig
|
||||||
|
|
||||||
def to_pydantic(self):
|
def to_pydantic(self):
|
||||||
@@ -433,12 +437,12 @@ class IridaControl(Control):
|
|||||||
def convert_by_mode(self, control_sub_type: str, mode: Literal['kraken', 'matches', 'contains'],
|
def convert_by_mode(self, control_sub_type: str, mode: Literal['kraken', 'matches', 'contains'],
|
||||||
consolidate: bool = False) -> Generator[dict, None, None]:
|
consolidate: bool = False) -> Generator[dict, None, None]:
|
||||||
"""
|
"""
|
||||||
split this instance into analysis types for controls graphs
|
split this instance into analysis types ('kraken', 'matches', 'contains') for controls graphs
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
consolidate (bool): whether to merge all off-target genera. Defaults to False
|
consolidate (bool): whether to merge all off-target genera. Defaults to False
|
||||||
control_sub_type (str): control subtype, 'MCS-NOS', etc.
|
control_sub_type (str): control subtype, 'MCS-NOS', etc.
|
||||||
mode (str): analysis type, 'contains', etc.
|
mode (Literal['kraken', 'matches', 'contains']): analysis type, 'contains', etc.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[dict]: list of records
|
List[dict]: list of records
|
||||||
@@ -562,7 +566,7 @@ class IridaControl(Control):
|
|||||||
df, modes = cls.prep_df(ctx=ctx, df=df)
|
df, modes = cls.prep_df(ctx=ctx, df=df)
|
||||||
# logger.debug(f"prepped df: \n {df}")
|
# logger.debug(f"prepped df: \n {df}")
|
||||||
fig = IridaFigure(df=df, ytitle=title, modes=modes, parent=parent,
|
fig = IridaFigure(df=df, ytitle=title, modes=modes, parent=parent,
|
||||||
months=chart_settings['months'])
|
settings=chart_settings)
|
||||||
return report, fig
|
return report, fig
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -571,9 +575,8 @@ class IridaControl(Control):
|
|||||||
Convert list of control records to dataframe
|
Convert list of control records to dataframe
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
ctx (dict): settings passed from gui
|
|
||||||
input_df (list[dict]): list of dictionaries containing records
|
input_df (list[dict]): list of dictionaries containing records
|
||||||
sub_type (str | None, optional): sub_type of submission type. Defaults to None.
|
sub_mode (str | None, optional): sub_type of submission type. Defaults to None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
DataFrame: dataframe of controls
|
DataFrame: dataframe of controls
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ class KitType(BaseClass):
|
|||||||
else:
|
else:
|
||||||
return (item.reagent_role for item in relevant_associations)
|
return (item.reagent_role for item in relevant_associations)
|
||||||
|
|
||||||
# TODO: Move to BasicSubmission?
|
|
||||||
def construct_xl_map_for_use(self, submission_type: str | SubmissionType) -> Generator[(str, str), None, None]:
|
def construct_xl_map_for_use(self, submission_type: str | SubmissionType) -> Generator[(str, str), None, None]:
|
||||||
"""
|
"""
|
||||||
Creates map of locations in Excel workbook for a SubmissionType
|
Creates map of locations in Excel workbook for a SubmissionType
|
||||||
@@ -274,8 +273,7 @@ class KitType(BaseClass):
|
|||||||
for kk, vv in assoc.to_export_dict().items():
|
for kk, vv in assoc.to_export_dict().items():
|
||||||
v[kk] = vv
|
v[kk] = vv
|
||||||
base_dict['reagent roles'].append(v)
|
base_dict['reagent roles'].append(v)
|
||||||
# for k, v in submission_type.construct_equipment_map():
|
for k, v in submission_type.construct_field_map("equipment"):
|
||||||
for k, v in submission_type.contstruct_field_map("equipment"):
|
|
||||||
try:
|
try:
|
||||||
assoc = next(item for item in submission_type.submissiontype_equipmentrole_associations if
|
assoc = next(item for item in submission_type.submissiontype_equipmentrole_associations if
|
||||||
item.equipment_role.name == k)
|
item.equipment_role.name == k)
|
||||||
@@ -428,7 +426,7 @@ class Reagent(BaseClass, LogMixin):
|
|||||||
submission=sub)) #: Association proxy to SubmissionSampleAssociation.samples
|
submission=sub)) #: Association proxy to SubmissionSampleAssociation.samples
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.name is not None:
|
if self.name:
|
||||||
return f"<Reagent({self.name}-{self.lot})>"
|
return f"<Reagent({self.name}-{self.lot})>"
|
||||||
else:
|
else:
|
||||||
return f"<Reagent({self.role.name}-{self.lot})>"
|
return f"<Reagent({self.role.name}-{self.lot})>"
|
||||||
@@ -447,11 +445,12 @@ class Reagent(BaseClass, LogMixin):
|
|||||||
|
|
||||||
if extraction_kit is not None:
|
if extraction_kit is not None:
|
||||||
# NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType
|
# NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType
|
||||||
try:
|
reagent_role = next((item for item in set(self.role).intersection(extraction_kit.reagent_roles)), self.role[0])
|
||||||
reagent_role = list(set(self.role).intersection(extraction_kit.reagent_roles))[0]
|
# try:
|
||||||
# NOTE: Most will be able to fall back to first ReagentType in itself because most will only have 1.
|
# reagent_role = list(set(self.role).intersection(extraction_kit.reagent_roles))[0]
|
||||||
except:
|
# # NOTE: Most will be able to fall back to first ReagentType in itself because most will only have 1.
|
||||||
reagent_role = self.role[0]
|
# except:
|
||||||
|
# reagent_role = self.role[0]
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
reagent_role = self.role[0]
|
reagent_role = self.role[0]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from sqlite3 import OperationalError as SQLOperationalError, IntegrityError as S
|
|||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
from openpyxl.drawing.image import Image as OpenpyxlImage
|
from openpyxl.drawing.image import Image as OpenpyxlImage
|
||||||
from tools import row_map, setup_lookup, jinja_template_loading, rreplace, row_keys, check_key_or_attr, Result, Report, \
|
from tools import row_map, setup_lookup, jinja_template_loading, rreplace, row_keys, check_key_or_attr, Result, Report, \
|
||||||
report_result, create_holidays_for_year
|
report_result, create_holidays_for_year, ctx
|
||||||
from datetime import datetime, date, timedelta
|
from datetime import datetime, date, timedelta
|
||||||
from typing import List, Any, Tuple, Literal, Generator
|
from typing import List, Any, Tuple, Literal, Generator
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
@@ -127,7 +127,7 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
submission_type = self.submission_type or "Basic"
|
submission_type = self.submission_type or "Basic"
|
||||||
return f"<{submission_type}Submission({self.rsl_plate_num})>"
|
return f"<Submission({self.rsl_plate_num})>"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def jsons(cls) -> List[str]:
|
def jsons(cls) -> List[str]:
|
||||||
@@ -1380,17 +1380,22 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
writer = pyd.to_writer()
|
writer = pyd.to_writer()
|
||||||
writer.xl.save(filename=fname.with_suffix(".xlsx"))
|
writer.xl.save(filename=fname.with_suffix(".xlsx"))
|
||||||
|
|
||||||
def get_turnaround_time(self):
|
def get_turnaround_time(self) -> Tuple[int|None, bool|None]:
|
||||||
completed = self.completed_date or datetime.now()
|
try:
|
||||||
return self.calculate_turnaround(start_date=self.submitted_date.date(), end_date=completed.date())
|
completed = self.completed_date.date()
|
||||||
|
except AttributeError:
|
||||||
|
completed = None
|
||||||
|
return self.calculate_turnaround(start_date=self.submitted_date.date(), end_date=completed)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def calculate_turnaround(cls, start_date:date|None=None, end_date:date|None=None) -> int|None:
|
def calculate_turnaround(cls, start_date:date|None=None, end_date:date|None=None) -> Tuple[int|None, bool|None]:
|
||||||
|
if not end_date:
|
||||||
|
return None, None
|
||||||
try:
|
try:
|
||||||
delta = np.busday_count(start_date, end_date, holidays=create_holidays_for_year(start_date.year))
|
delta = np.busday_count(start_date, end_date, holidays=create_holidays_for_year(start_date.year)) + 1
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None, None
|
||||||
return delta + 1
|
return delta, delta <= ctx.TaT_threshold
|
||||||
|
|
||||||
|
|
||||||
# Below are the custom submission types
|
# Below are the custom submission types
|
||||||
|
|||||||
@@ -293,20 +293,24 @@ class ReagentParser(object):
|
|||||||
report = Report()
|
report = Report()
|
||||||
if isinstance(submission_type, dict):
|
if isinstance(submission_type, dict):
|
||||||
submission_type = submission_type['value']
|
submission_type = submission_type['value']
|
||||||
|
if isinstance(submission_type, str):
|
||||||
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
reagent_map = {k: v for k, v in self.kit_object.construct_xl_map_for_use(submission_type)}
|
reagent_map = {k: v for k, v in self.kit_object.construct_xl_map_for_use(submission_type)}
|
||||||
try:
|
try:
|
||||||
del reagent_map['info']
|
del reagent_map['info']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
# logger.debug(f"Reagent map: {pformat(reagent_map)}")
|
logger.debug(f"Reagent map: {pformat(reagent_map)}")
|
||||||
# NOTE: If reagent map is empty, maybe the wrong kit was given, check if there's only one kit for that submission type and use it if so.
|
# NOTE: If reagent map is empty, maybe the wrong kit was given, check if there's only one kit for that submission type and use it if so.
|
||||||
if not reagent_map.keys():
|
if not reagent_map:
|
||||||
temp_kit_object = self.submission_type_obj.get_default_kit()
|
temp_kit_object = self.submission_type_obj.get_default_kit()
|
||||||
|
logger.debug(f"Temp kit: {temp_kit_object}")
|
||||||
if temp_kit_object:
|
if temp_kit_object:
|
||||||
self.kit_object = temp_kit_object
|
self.kit_object = temp_kit_object
|
||||||
reagent_map = {k: v for k, v in self.kit_object.construct_xl_map_for_use(submission_type)}
|
# reagent_map = {k: v for k, v in self.kit_object.construct_xl_map_for_use(submission_type)}
|
||||||
logger.warning(f"Attempting to salvage {self.kit_object} with default kit map: {reagent_map}")
|
logger.warning(f"Attempting to salvage with default kit {self.kit_object} and submission_type: {self.submission_type_obj}")
|
||||||
if not reagent_map.keys():
|
return self.fetch_kit_info_map(submission_type=self.submission_type_obj)
|
||||||
|
else:
|
||||||
logger.error(f"Still no reagent map, displaying error.")
|
logger.error(f"Still no reagent map, displaying error.")
|
||||||
try:
|
try:
|
||||||
ext_kit_loc = self.submission_type_obj.info_map['extraction_kit']['read'][0]
|
ext_kit_loc = self.submission_type_obj.info_map['extraction_kit']['read'][0]
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
'''
|
'''
|
||||||
Contains functions for generating summary reports
|
Contains functions for generating summary reports
|
||||||
'''
|
'''
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
from pandas import DataFrame, ExcelWriter
|
from pandas import DataFrame, ExcelWriter
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -137,3 +139,35 @@ class ReportMaker(object):
|
|||||||
for cell in worksheet['D']:
|
for cell in worksheet['D']:
|
||||||
if cell.row > 1:
|
if cell.row > 1:
|
||||||
cell.style = 'Currency'
|
cell.style = 'Currency'
|
||||||
|
|
||||||
|
class TurnaroundMaker(object):
|
||||||
|
|
||||||
|
def __init__(self, start_date: date, end_date: date):
|
||||||
|
self.start_date = start_date
|
||||||
|
self.end_date = end_date
|
||||||
|
# NOTE: Set page size to zero to override limiting query size.
|
||||||
|
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date, page_size=0)
|
||||||
|
records = [self.build_record(sub) for sub in self.subs]
|
||||||
|
self.df = DataFrame.from_records(records)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build_record(cls, sub):
|
||||||
|
days, tat_ok = sub.get_turnaround_time()
|
||||||
|
return dict(name=sub.rsl_plate_num, days=days, submitted_date=sub.submitted_date,
|
||||||
|
completed_date=sub.completed_date, acceptable=tat_ok)
|
||||||
|
|
||||||
|
def write_report(self, filename: Path | str, obj: QWidget | None = None):
|
||||||
|
"""
|
||||||
|
Writes info to files.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (Path | str): Basename of output file
|
||||||
|
obj (QWidget | None, optional): Parent object. Defaults to None.
|
||||||
|
"""
|
||||||
|
if isinstance(filename, str):
|
||||||
|
filename = Path(filename)
|
||||||
|
filename = filename.absolute()
|
||||||
|
self.writer = ExcelWriter(filename.with_suffix(".xlsx"), engine='openpyxl')
|
||||||
|
self.df.to_excel(self.writer, sheet_name="Turnaround")
|
||||||
|
# logger.debug(f"Writing report to: {filename}")
|
||||||
|
self.writer.close()
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
'''
|
'''
|
||||||
Contains all operations for creating charts, graphs and visual effects.
|
Contains all operations for creating charts, graphs and visual effects.
|
||||||
'''
|
'''
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QWidget
|
from PyQt6.QtWidgets import QWidget
|
||||||
import plotly, logging
|
import plotly, logging
|
||||||
from plotly.graph_objects import Figure
|
from plotly.graph_objects import Figure
|
||||||
@@ -14,10 +16,15 @@ class CustomFigure(Figure):
|
|||||||
|
|
||||||
df = None
|
df = None
|
||||||
|
|
||||||
def __init__(self, df: pd.DataFrame, modes: list, ytitle: str | None = None, parent: QWidget | None = None,
|
def __init__(self, df: pd.DataFrame, settings: dict, modes: list, ytitle: str | None = None, parent: QWidget | None = None):
|
||||||
months: int = 6):
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
# self.settings = settings
|
||||||
|
try:
|
||||||
|
months = int(settings['months'])
|
||||||
|
except KeyError:
|
||||||
|
months = 6
|
||||||
self.df = df
|
self.df = df
|
||||||
|
self.update_xaxes(range=[settings['start_date'] - timedelta(days=1), settings['end_date']])
|
||||||
|
|
||||||
def save_figure(self, group_name: str = "plotly_output", parent: QWidget | None = None):
|
def save_figure(self, group_name: str = "plotly_output", parent: QWidget | None = None):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -16,19 +16,23 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
|
|
||||||
class IridaFigure(CustomFigure):
|
class IridaFigure(CustomFigure):
|
||||||
|
|
||||||
def __init__(self, df: pd.DataFrame, modes: list, ytitle: str | None = None, parent: QWidget | None = None,
|
def __init__(self, df: pd.DataFrame, modes: list, settings: dict, ytitle: str | None = None, parent: QWidget | None = None):
|
||||||
months: int = 6):
|
|
||||||
|
|
||||||
super().__init__(df=df, modes=modes)
|
super().__init__(df=df, modes=modes, settings=settings)
|
||||||
|
try:
|
||||||
self.construct_chart(df=df, modes=modes)
|
months = int(settings['months'])
|
||||||
|
except KeyError:
|
||||||
|
months = 6
|
||||||
|
self.construct_chart(df=df, modes=modes, start_date=settings['start_date'], end_date=settings['end_date'])
|
||||||
self.generic_figure_markers(modes=modes, ytitle=ytitle, months=months)
|
self.generic_figure_markers(modes=modes, ytitle=ytitle, months=months)
|
||||||
|
|
||||||
def construct_chart(self, df: pd.DataFrame, modes: list):
|
def construct_chart(self, df: pd.DataFrame, modes: list, start_date: date, end_date:date):
|
||||||
"""
|
"""
|
||||||
Creates a plotly chart for controls from a pandas dataframe
|
Creates a plotly chart for controls from a pandas dataframe
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
end_date ():
|
||||||
|
start_date ():
|
||||||
df (pd.DataFrame): input dataframe of controls
|
df (pd.DataFrame): input dataframe of controls
|
||||||
modes (list): analysis modes to construct charts for
|
modes (list): analysis modes to construct charts for
|
||||||
ytitle (str | None, optional): title on the y-axis. Defaults to None.
|
ytitle (str | None, optional): title on the y-axis. Defaults to None.
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
|
|
||||||
class PCRFigure(CustomFigure):
|
class PCRFigure(CustomFigure):
|
||||||
|
|
||||||
def __init__(self, df: pd.DataFrame, modes: list, ytitle: str | None = None, parent: QWidget | None = None,
|
def __init__(self, df: pd.DataFrame, modes: list, settings: dict, ytitle: str | None = None, parent: QWidget | None = None,
|
||||||
months: int = 6):
|
months: int = 6):
|
||||||
super().__init__(df=df, modes=modes)
|
super().__init__(df=df, modes=modes, settings=settings)
|
||||||
|
try:
|
||||||
|
months = int(settings['months'])
|
||||||
|
except KeyError:
|
||||||
|
months = 6
|
||||||
# logger.debug(f"DF: {self.df}")
|
# logger.debug(f"DF: {self.df}")
|
||||||
self.construct_chart(df=df)
|
self.construct_chart(df=df)
|
||||||
|
|
||||||
|
|||||||
@@ -11,4 +11,6 @@ from .controls_chart import *
|
|||||||
from .submission_details import *
|
from .submission_details import *
|
||||||
from .equipment_usage import *
|
from .equipment_usage import *
|
||||||
from .gel_checker import *
|
from .gel_checker import *
|
||||||
|
from .summary import Summary
|
||||||
|
from .turnaround import TurnaroundTime
|
||||||
from .app import App
|
from .app import App
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from .submission_widget import SubmissionFormContainer
|
|||||||
from .controls_chart import ControlsViewer
|
from .controls_chart import ControlsViewer
|
||||||
# from .sample_search import SampleSearchBox
|
# from .sample_search import SampleSearchBox
|
||||||
from .summary import Summary
|
from .summary import Summary
|
||||||
|
from .turnaround import TurnaroundTime
|
||||||
from .omni_search import SearchBox
|
from .omni_search import SearchBox
|
||||||
|
|
||||||
logger = logging.getLogger(f'submissions.{__name__}')
|
logger = logging.getLogger(f'submissions.{__name__}')
|
||||||
@@ -269,12 +270,14 @@ class AddSubForm(QWidget):
|
|||||||
self.tab2 = QWidget()
|
self.tab2 = QWidget()
|
||||||
self.tab3 = QWidget()
|
self.tab3 = QWidget()
|
||||||
self.tab4 = QWidget()
|
self.tab4 = QWidget()
|
||||||
|
self.tab5 = QWidget()
|
||||||
self.tabs.resize(300, 200)
|
self.tabs.resize(300, 200)
|
||||||
# NOTE: Add tabs
|
# NOTE: Add tabs
|
||||||
self.tabs.addTab(self.tab1, "Submissions")
|
self.tabs.addTab(self.tab1, "Submissions")
|
||||||
self.tabs.addTab(self.tab2, "Irida Controls")
|
self.tabs.addTab(self.tab2, "Irida Controls")
|
||||||
self.tabs.addTab(self.tab3, "PCR Controls")
|
self.tabs.addTab(self.tab3, "PCR Controls")
|
||||||
self.tabs.addTab(self.tab4, "Cost Report")
|
self.tabs.addTab(self.tab4, "Cost Report")
|
||||||
|
self.tabs.addTab(self.tab5, "Turnaround Times")
|
||||||
# NOTE: Create submission adder form
|
# NOTE: Create submission adder form
|
||||||
self.formwidget = SubmissionFormContainer(self)
|
self.formwidget = SubmissionFormContainer(self)
|
||||||
self.formlayout = QVBoxLayout(self)
|
self.formlayout = QVBoxLayout(self)
|
||||||
@@ -310,6 +313,10 @@ class AddSubForm(QWidget):
|
|||||||
self.tab4.layout = QVBoxLayout(self)
|
self.tab4.layout = QVBoxLayout(self)
|
||||||
self.tab4.layout.addWidget(summary_report)
|
self.tab4.layout.addWidget(summary_report)
|
||||||
self.tab4.setLayout(self.tab4.layout)
|
self.tab4.setLayout(self.tab4.layout)
|
||||||
|
turnaround = TurnaroundTime(self)
|
||||||
|
self.tab5.layout = QVBoxLayout(self)
|
||||||
|
self.tab5.layout.addWidget(turnaround)
|
||||||
|
self.tab5.setLayout(self.tab5.layout)
|
||||||
# NOTE: add tabs to main widget
|
# NOTE: add tabs to main widget
|
||||||
self.layout.addWidget(self.tabs)
|
self.layout.addWidget(self.tabs)
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
|||||||
@@ -172,11 +172,11 @@ class SubmissionDetails(QDialog):
|
|||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def sign_off(self, submission: str | BasicSubmission):
|
def sign_off(self, submission: str | BasicSubmission):
|
||||||
# logger.debug(f"Signing off on {submission} - ({getuser()})")
|
logger.debug(f"Signing off on {submission} - ({getuser()})")
|
||||||
if isinstance(submission, str):
|
if isinstance(submission, str):
|
||||||
submission = BasicSubmission.query(rsl_plate_num=submission)
|
submission = BasicSubmission.query(rsl_plate_num=submission)
|
||||||
submission.signed_by = getuser()
|
submission.signed_by = getuser()
|
||||||
submission.completed = datetime.now().date()
|
submission.completed_date = datetime.now().date()
|
||||||
submission.save()
|
submission.save()
|
||||||
self.submission_details(submission=self.rsl_plate_num)
|
self.submission_details(submission=self.rsl_plate_num)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from PyQt6.QtCore import QSignalBlocker
|
from PyQt6.QtCore import QSignalBlocker
|
||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
|
from .info_tab import InfoPane
|
||||||
from PyQt6.QtWidgets import QWidget, QGridLayout, QPushButton, QLabel
|
from PyQt6.QtWidgets import QWidget, QGridLayout, QPushButton, QLabel
|
||||||
from backend.db import Organization
|
from backend.db import Organization
|
||||||
from backend.excel import ReportMaker
|
from backend.excel import ReportMaker
|
||||||
@@ -11,38 +12,21 @@ import logging
|
|||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
class Summary(QWidget):
|
class Summary(InfoPane):
|
||||||
|
|
||||||
def __init__(self, parent: QWidget) -> None:
|
def __init__(self, parent: QWidget) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.app = self.parent().parent()
|
|
||||||
# logger.debug(f"\n\n{self.app}\n\n")
|
|
||||||
self.report = Report()
|
|
||||||
self.datepicker = StartEndDatePicker(default_start=-31)
|
|
||||||
self.webview = QWebEngineView()
|
|
||||||
self.datepicker.start_date.dateChanged.connect(self.get_report)
|
|
||||||
self.datepicker.end_date.dateChanged.connect(self.get_report)
|
|
||||||
self.layout = QGridLayout(self)
|
|
||||||
self.layout.addWidget(self.datepicker, 0, 0, 1, 2)
|
|
||||||
self.save_excel_button = QPushButton("Save Excel", parent=self)
|
|
||||||
self.save_excel_button.pressed.connect(self.save_excel)
|
|
||||||
self.save_pdf_button = QPushButton("Save PDF", parent=self)
|
|
||||||
self.save_pdf_button.pressed.connect(self.save_pdf)
|
|
||||||
self.org_select = CheckableComboBox()
|
self.org_select = CheckableComboBox()
|
||||||
self.org_select.setEditable(False)
|
self.org_select.setEditable(False)
|
||||||
self.org_select.addItem("Select", header=True)
|
self.org_select.addItem("Select", header=True)
|
||||||
for org in [org.name for org in Organization.query()]:
|
for org in [org.name for org in Organization.query()]:
|
||||||
self.org_select.addItem(org)
|
self.org_select.addItem(org)
|
||||||
self.org_select.model().itemChanged.connect(self.get_report)
|
self.org_select.model().itemChanged.connect(self.date_changed)
|
||||||
self.layout.addWidget(self.save_excel_button, 0, 2, 1, 1)
|
|
||||||
self.layout.addWidget(self.save_pdf_button, 0, 3, 1, 1)
|
|
||||||
self.layout.addWidget(self.webview, 2, 0, 1, 4)
|
|
||||||
self.layout.addWidget(QLabel("Client"), 1, 0, 1, 1)
|
self.layout.addWidget(QLabel("Client"), 1, 0, 1, 1)
|
||||||
self.layout.addWidget(self.org_select, 1, 1, 1, 3)
|
self.layout.addWidget(self.org_select, 1, 1, 1, 3)
|
||||||
self.setLayout(self.layout)
|
self.date_changed()
|
||||||
self.get_report()
|
|
||||||
|
|
||||||
def get_report(self):
|
def date_changed(self):
|
||||||
orgs = [self.org_select.itemText(i) for i in range(self.org_select.count()) if self.org_select.itemChecked(i)]
|
orgs = [self.org_select.itemText(i) for i in range(self.org_select.count()) if self.org_select.itemChecked(i)]
|
||||||
if self.datepicker.start_date.date() > self.datepicker.end_date.date():
|
if self.datepicker.start_date.date() > self.datepicker.end_date.date():
|
||||||
logger.warning("Start date after end date is not allowed!")
|
logger.warning("Start date after end date is not allowed!")
|
||||||
@@ -51,11 +35,12 @@ class Summary(QWidget):
|
|||||||
# Without triggering this function again
|
# Without triggering this function again
|
||||||
with QSignalBlocker(self.datepicker.start_date) as blocker:
|
with QSignalBlocker(self.datepicker.start_date) as blocker:
|
||||||
self.datepicker.start_date.setDate(lastmonth)
|
self.datepicker.start_date.setDate(lastmonth)
|
||||||
self.get_report()
|
self.date_changed()
|
||||||
return
|
return
|
||||||
# NOTE: convert to python useable date objects
|
# NOTE: convert to python useable date objects
|
||||||
self.start_date = self.datepicker.start_date.date().toPyDate()
|
# self.start_date = self.datepicker.start_date.date().toPyDate()
|
||||||
self.end_date = self.datepicker.end_date.date().toPyDate()
|
# self.end_date = self.datepicker.end_date.date().toPyDate()
|
||||||
|
super().date_changed()
|
||||||
logger.debug(f"Getting report from {self.start_date} to {self.end_date} using {orgs}")
|
logger.debug(f"Getting report from {self.start_date} to {self.end_date} using {orgs}")
|
||||||
self.report_obj = ReportMaker(start_date=self.start_date, end_date=self.end_date, organizations=orgs)
|
self.report_obj = ReportMaker(start_date=self.start_date, end_date=self.end_date, organizations=orgs)
|
||||||
self.webview.setHtml(self.report_obj.html)
|
self.webview.setHtml(self.report_obj.html)
|
||||||
@@ -66,12 +51,12 @@ class Summary(QWidget):
|
|||||||
self.save_pdf_button.setEnabled(False)
|
self.save_pdf_button.setEnabled(False)
|
||||||
self.save_excel_button.setEnabled(False)
|
self.save_excel_button.setEnabled(False)
|
||||||
|
|
||||||
def save_excel(self):
|
# def save_excel(self):
|
||||||
fname = select_save_file(self, default_name=f"Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}", extension="xlsx")
|
# fname = select_save_file(self, default_name=f"Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}", extension="xlsx")
|
||||||
self.report_obj.write_report(fname, obj=self)
|
# self.report_obj.write_report(fname, obj=self)
|
||||||
|
#
|
||||||
def save_pdf(self):
|
# def save_pdf(self):
|
||||||
fname = select_save_file(obj=self,
|
# fname = select_save_file(obj=self,
|
||||||
default_name=f"Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}",
|
# default_name=f"Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}",
|
||||||
extension="pdf")
|
# extension="pdf")
|
||||||
save_pdf(obj=self.webview, filename=fname)
|
# save_pdf(obj=self.webview, filename=fname)
|
||||||
|
|||||||
Reference in New Issue
Block a user