feat(validation): complete validation editor and master data modules

This commit is contained in:
Schubert Ferenc 2026-07-10 22:42:03 +02:00
parent 2b5c765e41
commit f73a24df13
73 changed files with 10194 additions and 0 deletions

View file

@ -0,0 +1,21 @@
from app.models.contact import Contact
from app.models.customer import Customer
from app.models.device import Device
from app.models.document import Document
from app.models.equipment import Equipment
from app.models.location import Location
from app.models.program import Program
from app.models.user import User
from app.models.validation import Validation
__all__ = [
"Contact",
"Customer",
"Device",
"Document",
"Equipment",
"Location",
"Program",
"User",
"Validation",
]

View file

@ -0,0 +1,19 @@
from __future__ import annotations
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base, TimestampMixin, UUIDMixin
class Contact(Base, UUIDMixin, TimestampMixin):
__tablename__ = "contacts"
customer_id: Mapped[str] = mapped_column(ForeignKey("customers.id", ondelete="CASCADE"), index=True)
full_name: Mapped[str] = mapped_column(String(160))
function: Mapped[str | None] = mapped_column(String(120))
email: Mapped[str | None] = mapped_column(String(255))
phone: Mapped[str | None] = mapped_column(String(80))
customer: Mapped["Customer"] = relationship(back_populates="contacts")

View file

@ -0,0 +1,32 @@
from __future__ import annotations
import enum
from sqlalchemy import Enum, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base, TimestampMixin, UUIDMixin
class CustomerType(str, enum.Enum):
practice = "practice"
clinic = "clinic"
class Customer(Base, UUIDMixin, TimestampMixin):
__tablename__ = "customers"
customer_type: Mapped[CustomerType] = mapped_column(Enum(CustomerType))
name: Mapped[str] = mapped_column(String(220), index=True)
street: Mapped[str | None] = mapped_column(String(220))
postal_code: Mapped[str | None] = mapped_column(String(20))
city: Mapped[str | None] = mapped_column(String(120))
phone: Mapped[str | None] = mapped_column(String(80))
email: Mapped[str | None] = mapped_column(String(255))
hygiene_officer: Mapped[str | None] = mapped_column(String(160))
quality_manager: Mapped[str | None] = mapped_column(String(160))
notes: Mapped[str | None] = mapped_column(Text)
contacts: Mapped[list["Contact"]] = relationship(back_populates="customer", cascade="all, delete-orphan")
locations: Mapped[list["Location"]] = relationship(back_populates="customer", cascade="all, delete-orphan")

View file

@ -0,0 +1,29 @@
from __future__ import annotations
from datetime import date
from sqlalchemy import Date, ForeignKey, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base, TimestampMixin, UUIDMixin
class Device(Base, UUIDMixin, TimestampMixin):
__tablename__ = "devices"
customer_id: Mapped[str] = mapped_column(ForeignKey("customers.id"), index=True)
location_id: Mapped[str | None] = mapped_column(ForeignKey("locations.id"), index=True)
manufacturer: Mapped[str] = mapped_column(String(140))
model: Mapped[str] = mapped_column(String(140))
device_type: Mapped[str | None] = mapped_column(String(120))
serial_number: Mapped[str] = mapped_column(String(140), unique=True, index=True)
year_built: Mapped[int | None] = mapped_column(Integer)
commissioned_on: Mapped[date | None] = mapped_column(Date)
chamber_volume_liters: Mapped[int | None] = mapped_column(Integer)
steam_generation: Mapped[str | None] = mapped_column(String(220))
water_treatment: Mapped[str | None] = mapped_column(String(220))
documentation: Mapped[str | None] = mapped_column(Text)
supplier: Mapped[str | None] = mapped_column(String(180))
location: Mapped["Location | None"] = relationship(back_populates="devices")

View file

@ -0,0 +1,26 @@
from __future__ import annotations
import enum
from sqlalchemy import Enum, String
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base, TimestampMixin, UUIDMixin
class DocumentOwnerType(str, enum.Enum):
customer = "customer"
device = "device"
validation = "validation"
equipment = "equipment"
class Document(Base, UUIDMixin, TimestampMixin):
__tablename__ = "documents"
owner_type: Mapped[DocumentOwnerType] = mapped_column(Enum(DocumentOwnerType), index=True)
owner_id: Mapped[str] = mapped_column(String(80), index=True)
filename: Mapped[str] = mapped_column(String(255))
content_type: Mapped[str] = mapped_column(String(120))
storage_path: Mapped[str] = mapped_column(String(500))

View file

@ -0,0 +1,35 @@
from __future__ import annotations
import enum
from datetime import date
from sqlalchemy import Date, Enum, String
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base, TimestampMixin, UUIDMixin
class EquipmentKind(str, enum.Enum):
temperature_logger = "temperature_logger"
pressure_logger = "pressure_logger"
sensor = "sensor"
class EquipmentStatus(str, enum.Enum):
green = "green"
yellow = "yellow"
red = "red"
class Equipment(Base, UUIDMixin, TimestampMixin):
__tablename__ = "equipment"
kind: Mapped[EquipmentKind] = mapped_column(Enum(EquipmentKind))
manufacturer: Mapped[str | None] = mapped_column(String(140))
model: Mapped[str | None] = mapped_column(String(140))
serial_number: Mapped[str] = mapped_column(String(140), unique=True, index=True)
calibrated_on: Mapped[date | None] = mapped_column(Date)
calibration_due_on: Mapped[date | None] = mapped_column(Date)
certificate_document_id: Mapped[str | None] = mapped_column(String(80))
status: Mapped[EquipmentStatus] = mapped_column(Enum(EquipmentStatus), default=EquipmentStatus.green)

View file

@ -0,0 +1,21 @@
from __future__ import annotations
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base, TimestampMixin, UUIDMixin
class Location(Base, UUIDMixin, TimestampMixin):
__tablename__ = "locations"
customer_id: Mapped[str] = mapped_column(ForeignKey("customers.id", ondelete="CASCADE"), index=True)
name: Mapped[str] = mapped_column(String(160))
street: Mapped[str | None] = mapped_column(String(220))
postal_code: Mapped[str | None] = mapped_column(String(20))
city: Mapped[str | None] = mapped_column(String(120))
room: Mapped[str | None] = mapped_column(String(120))
customer: Mapped["Customer"] = relationship(back_populates="locations")
devices: Mapped[list["Device"]] = relationship(back_populates="location")

View file

@ -0,0 +1,17 @@
from __future__ import annotations
from sqlalchemy import ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base, TimestampMixin, UUIDMixin
class Program(Base, UUIDMixin, TimestampMixin):
__tablename__ = "programs"
device_id: Mapped[str] = mapped_column(ForeignKey("devices.id"), index=True)
name: Mapped[str] = mapped_column(String(160))
temperature_celsius: Mapped[int | None] = mapped_column(Integer)
holding_time_minutes: Mapped[int | None] = mapped_column(Integer)
drying_time_minutes: Mapped[int | None] = mapped_column(Integer)

View file

@ -0,0 +1,25 @@
from __future__ import annotations
import enum
from sqlalchemy import Boolean, Enum, String
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base, TimestampMixin, UUIDMixin
class UserRole(str, enum.Enum):
admin = "admin"
employee = "employee"
auditor = "auditor"
class User(Base, UUIDMixin, TimestampMixin):
__tablename__ = "users"
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
full_name: Mapped[str] = mapped_column(String(160))
role: Mapped[UserRole] = mapped_column(Enum(UserRole), default=UserRole.employee)
password_hash: Mapped[str] = mapped_column(String(255))
is_active: Mapped[bool] = mapped_column(Boolean, default=True)

View file

@ -0,0 +1,49 @@
from __future__ import annotations
import enum
from datetime import date
from sqlalchemy import Date, Enum, ForeignKey, JSON, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base, TimestampMixin, UUIDMixin
class ValidationStatus(str, enum.Enum):
draft = "draft"
in_progress = "in_progress"
ready_for_report = "ready_for_report"
completed = "completed"
class Validation(Base, UUIDMixin, TimestampMixin):
__tablename__ = "validations"
report_number: Mapped[str] = mapped_column(String(80), unique=True, index=True)
customer_id: Mapped[str] = mapped_column(ForeignKey("customers.id"), index=True)
location_id: Mapped[str | None] = mapped_column(ForeignKey("locations.id"), index=True)
contact_id: Mapped[str | None] = mapped_column(ForeignKey("contacts.id"), index=True)
device_id: Mapped[str | None] = mapped_column(ForeignKey("devices.id"), index=True)
validation_type: Mapped[str] = mapped_column(String(120))
project: Mapped[str | None] = mapped_column(String(180))
test_location: Mapped[str | None] = mapped_column(String(180))
examiner_name: Mapped[str | None] = mapped_column(String(180))
participants: Mapped[str | None] = mapped_column(Text)
operator_name: Mapped[str | None] = mapped_column(String(180))
scheduled_on: Mapped[date | None] = mapped_column(Date)
performed_on: Mapped[date | None] = mapped_column(Date)
next_validation_on: Mapped[date | None] = mapped_column(Date)
examiner_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"))
status: Mapped[ValidationStatus] = mapped_column(Enum(ValidationStatus), default=ValidationStatus.draft)
result: Mapped[str | None] = mapped_column(String(120))
notes: Mapped[str | None] = mapped_column(Text)
equipment_ids: Mapped[list[str]] = mapped_column(JSON, default=list)
environment_conditions: Mapped[dict] = mapped_column(JSON, default=dict)
documentation_checklist: Mapped[list[dict]] = mapped_column(JSON, default=list)
performance_checklist: Mapped[list[dict]] = mapped_column(JSON, default=list)
programs: Mapped[list[dict]] = mapped_column(JSON, default=list)
loading_patterns: Mapped[list[dict]] = mapped_column(JSON, default=list)
measurement_data: Mapped[list[dict]] = mapped_column(JSON, default=list)
drying: Mapped[dict] = mapped_column(JSON, default=dict)
recommendations: Mapped[list[dict]] = mapped_column(JSON, default=list)
attachments: Mapped[list[dict]] = mapped_column(JSON, default=list)