feat(navigation): add prominent validation quickstart action
This commit is contained in:
parent
0bbcaba211
commit
9c6b184e39
28614 changed files with 4356173 additions and 23 deletions
|
|
@ -0,0 +1,256 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy import and_, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.contact import Contact
|
||||
from app.models.customer import Customer
|
||||
from app.models.device import Device
|
||||
from app.models.equipment import Equipment, EquipmentKind
|
||||
from app.models.location import Location
|
||||
from app.models.user import User
|
||||
from app.models.validation import Validation, ValidationStatus
|
||||
from app.modules.orion.template_service import ReportTemplateService
|
||||
from app.schemas.domain import QuickStartCreateRequest, QuickStartCustomerData, QuickStartValidationSummary
|
||||
from app.services.validation_workflow import ValidationWorkflowService
|
||||
|
||||
|
||||
class QuickStartService:
|
||||
def __init__(self, session: Session) -> None:
|
||||
self.session = session
|
||||
|
||||
def customer_data(self, customer_id: str) -> QuickStartCustomerData:
|
||||
customer = self._customer(customer_id)
|
||||
locations = list(
|
||||
self.session.scalars(
|
||||
select(Location).where(Location.customer_id == customer.id).order_by(Location.name.asc())
|
||||
)
|
||||
)
|
||||
contacts = list(
|
||||
self.session.scalars(
|
||||
select(Contact).where(Contact.customer_id == customer.id).order_by(Contact.full_name.asc())
|
||||
)
|
||||
)
|
||||
devices = list(
|
||||
self.session.scalars(
|
||||
select(Device).where(Device.customer_id == customer.id).order_by(Device.serial_number.asc())
|
||||
)
|
||||
)
|
||||
validations = self._validation_summaries([device.id for device in devices])
|
||||
return QuickStartCustomerData(
|
||||
customer=customer,
|
||||
locations=locations,
|
||||
contacts=contacts,
|
||||
devices=devices,
|
||||
validations=validations,
|
||||
)
|
||||
|
||||
def device_last_validation(self, device_id: str) -> QuickStartValidationSummary | None:
|
||||
return self._last_validation(device_id)
|
||||
|
||||
def create_validation(self, payload: QuickStartCreateRequest, examiner: User) -> Validation:
|
||||
customer = self._customer(str(payload.customer_id))
|
||||
devices = list(
|
||||
self.session.scalars(
|
||||
select(Device).where(Device.customer_id == customer.id).order_by(Device.serial_number.asc())
|
||||
)
|
||||
)
|
||||
device = self._device(str(payload.device_id)) if payload.device_id else self._single_or_default(devices)
|
||||
if device is None:
|
||||
raise ValueError("Kein Gerät verfügbar.")
|
||||
locations = list(
|
||||
self.session.scalars(
|
||||
select(Location).where(Location.customer_id == customer.id).order_by(Location.name.asc())
|
||||
)
|
||||
)
|
||||
contacts = list(
|
||||
self.session.scalars(
|
||||
select(Contact).where(Contact.customer_id == customer.id).order_by(Contact.full_name.asc())
|
||||
)
|
||||
)
|
||||
location = self._location(str(payload.location_id)) if payload.location_id else self._single_or_default(locations)
|
||||
contact = self._contact(str(payload.contact_id)) if payload.contact_id else self._single_or_default(contacts)
|
||||
|
||||
base = self._base_validation(device.id, payload.validation_type)
|
||||
bundle = ReportTemplateService(self.session).ensure_default_template()
|
||||
validation = Validation(
|
||||
report_number=self._next_report_number(),
|
||||
customer_id=customer.id,
|
||||
location_id=location.id if location else (locations[0].id if locations else None),
|
||||
contact_id=contact.id if contact else None,
|
||||
device_id=device.id,
|
||||
validation_type=payload.validation_type,
|
||||
performed_on=date.today(),
|
||||
examiner_name=examiner.full_name,
|
||||
examiner_id=examiner.id,
|
||||
operator_name=customer.quality_manager or customer.hygiene_officer or examiner.full_name,
|
||||
status=ValidationStatus.draft.value,
|
||||
result="offen",
|
||||
revalidation_interval_months=base.revalidation_interval_months if base else 24,
|
||||
next_validation_manually_overridden=False,
|
||||
version=1 if base is None else base.version + 1,
|
||||
previous_validation_id=base.id if base else None,
|
||||
equipment_ids=list(base.equipment_ids) if base and base.equipment_ids else self._default_equipment_ids(),
|
||||
environment_conditions=deepcopy(base.environment_conditions) if base else {},
|
||||
documentation_checklist=deepcopy(base.documentation_checklist) if base else self._template_checklist(bundle, "documentation_checklist"),
|
||||
performance_checklist=deepcopy(base.performance_checklist) if base else self._template_checklist(bundle, "performance_checklist"),
|
||||
programs=deepcopy(base.programs) if base else self._default_programs(),
|
||||
loading_patterns=deepcopy(base.loading_patterns) if base else self._default_loading_patterns(),
|
||||
measurement_data=[],
|
||||
drying={},
|
||||
recommendations=deepcopy(base.recommendations) if base else [],
|
||||
attachments=[],
|
||||
)
|
||||
self.session.add(validation)
|
||||
self.session.flush()
|
||||
ValidationWorkflowService(self.session).apply_revalidation_date(validation)
|
||||
return validation
|
||||
|
||||
def _customer(self, customer_id: str) -> Customer:
|
||||
customer = self.session.get(Customer, customer_id)
|
||||
if customer is None:
|
||||
raise ValueError("Kunde nicht gefunden.")
|
||||
return customer
|
||||
|
||||
def _device(self, device_id: str) -> Device:
|
||||
device = self.session.get(Device, device_id)
|
||||
if device is None:
|
||||
raise ValueError("Gerät nicht gefunden.")
|
||||
return device
|
||||
|
||||
def _location(self, location_id: str) -> Location:
|
||||
location = self.session.get(Location, location_id)
|
||||
if location is None:
|
||||
raise ValueError("Standort nicht gefunden.")
|
||||
return location
|
||||
|
||||
def _contact(self, contact_id: str) -> Contact:
|
||||
contact = self.session.get(Contact, contact_id)
|
||||
if contact is None:
|
||||
raise ValueError("Ansprechpartner nicht gefunden.")
|
||||
return contact
|
||||
|
||||
def _single_or_default(self, items):
|
||||
if len(items) == 1:
|
||||
return items[0]
|
||||
return None
|
||||
|
||||
def _last_validation(self, device_id: str) -> QuickStartValidationSummary | None:
|
||||
validation = self.session.scalar(
|
||||
select(Validation)
|
||||
.where(
|
||||
and_(
|
||||
Validation.device_id == device_id,
|
||||
Validation.status.in_([ValidationStatus.approved.value, ValidationStatus.completed.value]),
|
||||
)
|
||||
)
|
||||
.order_by(Validation.performed_on.desc().nulls_last(), Validation.updated_at.desc())
|
||||
)
|
||||
if validation is None:
|
||||
return None
|
||||
return self._summary(validation)
|
||||
|
||||
def _validation_summaries(self, device_ids: list[str]) -> list[QuickStartValidationSummary]:
|
||||
if not device_ids:
|
||||
return []
|
||||
validations = list(
|
||||
self.session.scalars(
|
||||
select(Validation)
|
||||
.where(Validation.device_id.in_(device_ids))
|
||||
.order_by(Validation.performed_on.desc().nulls_last(), Validation.updated_at.desc())
|
||||
.limit(10)
|
||||
)
|
||||
)
|
||||
return [self._summary(item) for item in validations]
|
||||
|
||||
def _summary(self, validation: Validation) -> QuickStartValidationSummary:
|
||||
return QuickStartValidationSummary(
|
||||
id=validation.id,
|
||||
device_id=validation.device_id,
|
||||
report_number=validation.report_number,
|
||||
performed_on=validation.performed_on,
|
||||
result=validation.result,
|
||||
next_validation_on=validation.next_validation_on,
|
||||
status=validation.status,
|
||||
validation_type=validation.validation_type,
|
||||
equipment_ids=list(validation.equipment_ids or []),
|
||||
)
|
||||
|
||||
def _base_validation(self, device_id: str, validation_type: str) -> Validation | None:
|
||||
if validation_type not in {"Revalidierung", "Leistungsbeurteilung"}:
|
||||
return None
|
||||
return self.session.scalar(
|
||||
select(Validation)
|
||||
.where(
|
||||
and_(
|
||||
Validation.device_id == device_id,
|
||||
Validation.status.in_([ValidationStatus.approved.value, ValidationStatus.completed.value]),
|
||||
)
|
||||
)
|
||||
.order_by(Validation.performed_on.desc().nulls_last(), Validation.updated_at.desc())
|
||||
)
|
||||
|
||||
def _next_report_number(self) -> str:
|
||||
total = self.session.scalar(select(func.count()).select_from(Validation)) or 0
|
||||
return f"VAL-{total + 1:05d}"
|
||||
|
||||
def _default_equipment_ids(self) -> list[str]:
|
||||
equipment = list(
|
||||
self.session.scalars(
|
||||
select(Equipment).where(Equipment.kind == EquipmentKind.temperature_logger).order_by(Equipment.serial_number.asc())
|
||||
)
|
||||
)
|
||||
equipment.extend(
|
||||
list(
|
||||
self.session.scalars(
|
||||
select(Equipment).where(Equipment.kind == EquipmentKind.pressure_logger).order_by(Equipment.serial_number.asc())
|
||||
)
|
||||
)
|
||||
)
|
||||
selected: list[str] = []
|
||||
temp_count = 0
|
||||
pressure_count = 0
|
||||
for item in equipment:
|
||||
if item.kind == EquipmentKind.temperature_logger and temp_count < 5:
|
||||
selected.append(item.id)
|
||||
temp_count += 1
|
||||
elif item.kind == EquipmentKind.pressure_logger and pressure_count < 1:
|
||||
selected.append(item.id)
|
||||
pressure_count += 1
|
||||
return selected
|
||||
|
||||
def _template_checklist(self, bundle, key: str) -> list[dict]:
|
||||
checklist_key = {
|
||||
"documentation_checklist": "documentation_control",
|
||||
"performance_checklist": "sterilizer_description",
|
||||
}.get(key, key)
|
||||
template = next((item for item in bundle.checklists if item.checklist_key == checklist_key), None)
|
||||
if template is None:
|
||||
return []
|
||||
return [
|
||||
{
|
||||
"template_key": template.checklist_key,
|
||||
"template_title": template.title,
|
||||
"text": item,
|
||||
"value": "na",
|
||||
"comment": "",
|
||||
}
|
||||
for item in template.items
|
||||
]
|
||||
|
||||
def _default_programs(self) -> list[dict]:
|
||||
return [
|
||||
{"name": "Vakuumtest", "selected": True, "custom": False},
|
||||
{"name": "Bowie-Dick / Leerkammerprofil", "selected": True, "custom": False},
|
||||
{"name": "134 C hohl verpackt", "selected": True, "custom": False},
|
||||
]
|
||||
|
||||
def _default_loading_patterns(self) -> list[dict]:
|
||||
return [
|
||||
{"run": 1, "pattern": "Standardbeladung", "description": "", "images": []},
|
||||
{"run": 2, "pattern": "Standardbeladung", "description": "", "images": []},
|
||||
{"run": 3, "pattern": "Standardbeladung", "description": "", "images": []},
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue