feat(validation): add editing versioning revalidation and aligned reports
This commit is contained in:
parent
f73a24df13
commit
302e542fda
28 changed files with 2691 additions and 406 deletions
|
|
@ -0,0 +1,225 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.base import Base
|
||||
from app.models.customer import Customer, CustomerType
|
||||
from app.models.contact import Contact
|
||||
from app.models.device import Device
|
||||
from app.models.location import Location
|
||||
from app.models.validation import Validation, ValidationStatus
|
||||
from app.services.validation_workflow import ValidationWorkflowService
|
||||
from app.schemas.domain import ValidationCreate
|
||||
from app.modules.orion.service import OrionReportService
|
||||
|
||||
|
||||
def session() -> Session:
|
||||
engine = create_engine("sqlite+pysqlite:///:memory:")
|
||||
Base.metadata.create_all(engine)
|
||||
return Session(engine)
|
||||
|
||||
|
||||
def seed(session: Session):
|
||||
customer = Customer(customer_type=CustomerType.practice, name="Praxis Test")
|
||||
session.add(customer)
|
||||
session.flush()
|
||||
location = Location(customer_id=customer.id, name="OP")
|
||||
session.add(location)
|
||||
session.flush()
|
||||
device = Device(
|
||||
customer_id=customer.id,
|
||||
location_id=location.id,
|
||||
manufacturer="MELAG",
|
||||
model="Vacuklav",
|
||||
serial_number="SN-1",
|
||||
)
|
||||
session.add(device)
|
||||
session.flush()
|
||||
return customer, location, device
|
||||
|
||||
|
||||
def seed_contact(session: Session, customer: Customer) -> Contact:
|
||||
contact = Contact(customer_id=customer.id, full_name="Kontakt")
|
||||
session.add(contact)
|
||||
session.flush()
|
||||
return contact
|
||||
|
||||
|
||||
def valid_validation(customer: Customer, location: Location, device: Device) -> Validation:
|
||||
return Validation(
|
||||
report_number="VAL-TEST-1",
|
||||
validation_type="Erstvalidierung",
|
||||
performed_on=date.today(),
|
||||
customer_id=customer.id,
|
||||
location_id=location.id,
|
||||
device_id=device.id,
|
||||
examiner_name="Pruefer",
|
||||
status=ValidationStatus.draft.value,
|
||||
)
|
||||
|
||||
|
||||
def test_required_fields_are_reported():
|
||||
db = session()
|
||||
validation = Validation(status=ValidationStatus.draft.value)
|
||||
|
||||
review = ValidationWorkflowService(db).review(validation)
|
||||
|
||||
assert {item["field"] for item in review["errors"]} >= {
|
||||
"report_number",
|
||||
"validation_type",
|
||||
"performed_on",
|
||||
"customer_id",
|
||||
"location_id",
|
||||
"device_id",
|
||||
"examiner_name",
|
||||
}
|
||||
|
||||
|
||||
def test_status_changes_to_ready_when_no_errors():
|
||||
db = session()
|
||||
customer, location, device = seed(db)
|
||||
validation = valid_validation(customer, location, device)
|
||||
db.add(validation)
|
||||
db.flush()
|
||||
|
||||
review = ValidationWorkflowService(db).mark_ready_for_review(validation)
|
||||
|
||||
assert review["errors"] == []
|
||||
assert validation.status == ValidationStatus.ready_for_review.value
|
||||
|
||||
|
||||
def test_search_finds_report_number():
|
||||
db = session()
|
||||
customer, location, device = seed(db)
|
||||
db.add(valid_validation(customer, location, device))
|
||||
db.flush()
|
||||
|
||||
result = ValidationWorkflowService(db).query_validations(
|
||||
search="VAL-TEST",
|
||||
page=1,
|
||||
page_size=10,
|
||||
sort_by="updated_at",
|
||||
sort_order="desc",
|
||||
filters={},
|
||||
)
|
||||
|
||||
assert result["total"] == 1
|
||||
|
||||
|
||||
def test_csv_import_preview_detects_duplicate_and_missing_fields():
|
||||
db = session()
|
||||
customer, location, device = seed(db)
|
||||
db.add(valid_validation(customer, location, device))
|
||||
db.flush()
|
||||
csv_content = (
|
||||
"report_number,validation_type,test_date,customer_reference,location_reference,"
|
||||
"device_serial_number,examiner,status,result,notes\n"
|
||||
"VAL-TEST-1,Erstvalidierung,2026-07-11,Praxis Test,OP,SN-1,Pruefer,ENTWURF,offen,\n"
|
||||
"VAL-NEW,,,,,,,\n"
|
||||
).encode()
|
||||
|
||||
preview = ValidationWorkflowService(db).preview_csv(csv_content)
|
||||
|
||||
assert preview["duplicates"] == 1
|
||||
assert preview["invalid_rows"] == 1
|
||||
|
||||
|
||||
def test_validation_without_contact_id_can_be_saved():
|
||||
db = session()
|
||||
customer, location, device = seed(db)
|
||||
validation = valid_validation(customer, location, device)
|
||||
validation.contact_id = None
|
||||
db.add(validation)
|
||||
db.flush()
|
||||
|
||||
assert validation.contact_id is None
|
||||
|
||||
|
||||
def test_validation_with_valid_contact_id_can_be_saved():
|
||||
db = session()
|
||||
customer, location, device = seed(db)
|
||||
contact = seed_contact(db, customer)
|
||||
validation = valid_validation(customer, location, device)
|
||||
validation.contact_id = contact.id
|
||||
db.add(validation)
|
||||
db.flush()
|
||||
|
||||
assert validation.contact_id == contact.id
|
||||
|
||||
|
||||
def test_empty_contact_id_string_is_normalized_to_none():
|
||||
payload = ValidationCreate(
|
||||
report_number="VAL-EMPTY",
|
||||
validation_type="Erstvalidierung",
|
||||
performed_on=date.today(),
|
||||
customer_id=None,
|
||||
location_id="",
|
||||
contact_id="",
|
||||
device_id=None,
|
||||
examiner_name="Pruefer",
|
||||
)
|
||||
|
||||
assert payload.contact_id is None
|
||||
assert payload.location_id is None
|
||||
|
||||
|
||||
def test_invalid_uuid_returns_validation_error():
|
||||
try:
|
||||
ValidationCreate(
|
||||
report_number="VAL-BAD",
|
||||
validation_type="Erstvalidierung",
|
||||
performed_on=date.today(),
|
||||
customer_id="not-a-uuid",
|
||||
device_id=None,
|
||||
examiner_name="Pruefer",
|
||||
)
|
||||
except Exception as exc:
|
||||
assert "uuid" in str(exc).lower()
|
||||
else:
|
||||
raise AssertionError("Invalid UUID was accepted")
|
||||
|
||||
|
||||
def test_revalidation_date_uses_calendar_months():
|
||||
db = session()
|
||||
customer, location, device = seed(db)
|
||||
validation = valid_validation(customer, location, device)
|
||||
validation.performed_on = date(2026, 1, 31)
|
||||
validation.revalidation_interval_months = 1
|
||||
|
||||
ValidationWorkflowService(db).apply_revalidation_date(validation)
|
||||
|
||||
assert validation.next_validation_on == date(2026, 2, 28)
|
||||
|
||||
|
||||
def test_new_version_keeps_structured_json_and_previous_reference():
|
||||
db = session()
|
||||
customer, location, device = seed(db)
|
||||
validation = valid_validation(customer, location, device)
|
||||
validation.status = ValidationStatus.approved.value
|
||||
validation.documentation_checklist = [{"text": "A", "value": "yes"}]
|
||||
db.add(validation)
|
||||
db.flush()
|
||||
|
||||
clone = ValidationWorkflowService(db).create_new_version(validation)
|
||||
|
||||
assert clone.previous_validation_id == validation.id
|
||||
assert clone.version == 2
|
||||
assert clone.documentation_checklist == validation.documentation_checklist
|
||||
|
||||
|
||||
def test_orion_renders_reference_main_chapters(tmp_path):
|
||||
db = session()
|
||||
customer, location, device = seed(db)
|
||||
validation = valid_validation(customer, location, device)
|
||||
db.add(validation)
|
||||
db.flush()
|
||||
|
||||
html = OrionReportService(db, tmp_path).render_html(validation.id)
|
||||
|
||||
assert "1. Funktionsqualifikation" in html
|
||||
assert "2.2 Pruefmittel" in html
|
||||
assert "4. Ergebnisse der Validierung" in html
|
||||
assert "9. Kalibrierzertifikate" in html
|
||||
Loading…
Add table
Add a link
Reference in a new issue