feat: initial commit
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
from app.models.user import User
|
||||
from app.models.template import Template
|
||||
from app.models.cad_file import CadFile
|
||||
from app.models.order import Order
|
||||
from app.models.order_item import OrderItem
|
||||
from app.models.audit_log import AuditLog
|
||||
from app.models.pricing_tier import PricingTier
|
||||
from app.models.product import Product
|
||||
from app.models.output_type import OutputType
|
||||
from app.models.order_line import OrderLine
|
||||
from app.models.render_template import RenderTemplate
|
||||
from app.models.material import Material
|
||||
from app.models.material_alias import MaterialAlias
|
||||
from app.models.render_position import ProductRenderPosition
|
||||
|
||||
__all__ = [
|
||||
"User", "Template", "CadFile", "Order", "OrderItem", "AuditLog",
|
||||
"PricingTier", "Product", "OutputType", "OrderLine",
|
||||
"RenderTemplate", "Material", "MaterialAlias", "ProductRenderPosition",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, Boolean, DateTime, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
__tablename__ = "audit_log"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
action: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
entity_type: Mapped[str] = mapped_column(String(100), nullable=True)
|
||||
entity_id: Mapped[str] = mapped_column(String(255), nullable=True)
|
||||
details: Mapped[dict] = mapped_column(JSONB, nullable=True)
|
||||
timestamp: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
# Notification center columns
|
||||
target_user_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True,
|
||||
)
|
||||
read_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
notification: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
|
||||
user: Mapped["User"] = relationship("User", back_populates="audit_logs", foreign_keys=[user_id])
|
||||
target_user: Mapped["User"] = relationship("User", foreign_keys=[target_user_id])
|
||||
@@ -0,0 +1,37 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, Enum as SAEnum, BigInteger
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from app.database import Base
|
||||
import enum
|
||||
|
||||
|
||||
class ProcessingStatus(str, enum.Enum):
|
||||
pending = "pending"
|
||||
processing = "processing"
|
||||
completed = "completed"
|
||||
failed = "failed"
|
||||
|
||||
|
||||
class CadFile(Base):
|
||||
__tablename__ = "cad_files"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
original_name: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
stored_path: Mapped[str] = mapped_column(String(1000), nullable=False)
|
||||
file_hash: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True)
|
||||
file_size: Mapped[int] = mapped_column(BigInteger, nullable=True)
|
||||
parsed_objects: Mapped[dict] = mapped_column(JSONB, nullable=True)
|
||||
thumbnail_path: Mapped[str] = mapped_column(String(1000), nullable=True)
|
||||
gltf_path: Mapped[str] = mapped_column(String(1000), nullable=True)
|
||||
processing_status: Mapped[ProcessingStatus] = mapped_column(
|
||||
SAEnum(ProcessingStatus), default=ProcessingStatus.pending, nullable=False
|
||||
)
|
||||
error_message: Mapped[str] = mapped_column(String(2000), nullable=True)
|
||||
render_log: Mapped[dict] = mapped_column(JSONB, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
order_items: Mapped[list["OrderItem"]] = relationship("OrderItem", back_populates="cad_file")
|
||||
products: Mapped[list["Product"]] = relationship("Product", back_populates="cad_file")
|
||||
@@ -0,0 +1,24 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, Text, ForeignKey, Integer
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Material(Base):
|
||||
__tablename__ = "materials"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name: Mapped[str] = mapped_column(String(200), nullable=False, unique=True)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=True)
|
||||
source: Mapped[str] = mapped_column(String(20), nullable=False, default="manual")
|
||||
schaeffler_code: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
creator: Mapped["User"] = relationship("User", foreign_keys=[created_by], lazy="select") # type: ignore[name-defined]
|
||||
aliases = relationship("MaterialAlias", back_populates="material", cascade="all, delete-orphan")
|
||||
@@ -0,0 +1,19 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class MaterialAlias(Base):
|
||||
__tablename__ = "material_aliases"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
material_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("materials.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
alias: Mapped[str] = mapped_column(String(300), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
material = relationship("Material", back_populates="aliases")
|
||||
@@ -0,0 +1,42 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import String, DateTime, Enum as SAEnum, ForeignKey, Text, Integer, Numeric
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from app.database import Base
|
||||
import enum
|
||||
|
||||
|
||||
class OrderStatus(str, enum.Enum):
|
||||
draft = "draft"
|
||||
submitted = "submitted"
|
||||
processing = "processing"
|
||||
completed = "completed"
|
||||
rejected = "rejected"
|
||||
|
||||
|
||||
class Order(Base):
|
||||
__tablename__ = "orders"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
order_number: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
|
||||
template_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("templates.id"), nullable=True)
|
||||
status: Mapped[OrderStatus] = mapped_column(SAEnum(OrderStatus), default=OrderStatus.draft, nullable=False)
|
||||
created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
source_excel: Mapped[str] = mapped_column(String(1000), nullable=True)
|
||||
notes: Mapped[str] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
submitted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
processing_started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
rejected_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
estimated_price: Mapped[Decimal | None] = mapped_column(Numeric(12, 2), nullable=True)
|
||||
|
||||
template: Mapped["Template"] = relationship("Template", back_populates="orders")
|
||||
created_by_user: Mapped["User"] = relationship("User", back_populates="orders", foreign_keys=[created_by])
|
||||
items: Mapped[list["OrderItem"]] = relationship("OrderItem", back_populates="order", cascade="all, delete-orphan")
|
||||
lines: Mapped[list["OrderLine"]] = relationship(
|
||||
"OrderLine", back_populates="order", cascade="all, delete-orphan"
|
||||
)
|
||||
@@ -0,0 +1,71 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, Enum as SAEnum, ForeignKey, Integer, Boolean, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from app.database import Base
|
||||
import enum
|
||||
|
||||
|
||||
class ItemStatus(str, enum.Enum):
|
||||
pending = "pending"
|
||||
approved = "approved"
|
||||
rejected = "rejected"
|
||||
|
||||
|
||||
class AIValidationStatus(str, enum.Enum):
|
||||
not_started = "not_started"
|
||||
pending = "pending"
|
||||
completed = "completed"
|
||||
failed = "failed"
|
||||
|
||||
|
||||
class OrderItem(Base):
|
||||
__tablename__ = "order_items"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
order_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("orders.id"), nullable=False)
|
||||
row_index: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
# 11 Standard fields (columns 0-10, skip col 5)
|
||||
ebene1: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
ebene2: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
baureihe: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
pim_id: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
produkt_baureihe: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
# col 5 is skipped (separator)
|
||||
gewaehltes_produkt: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
name_cad_modell: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
gewuenschte_bildnummer: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
lagertyp: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
medias_rendering: Mapped[bool] = mapped_column(Boolean, nullable=True)
|
||||
|
||||
# Component pairs (cols 11+): [{part_name, material, component_type, column_index}]
|
||||
components: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
|
||||
|
||||
# CAD linkage
|
||||
cad_file_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("cad_files.id"), nullable=True)
|
||||
thumbnail_path: Mapped[str] = mapped_column(String(1000), nullable=True)
|
||||
# Material assignments per CAD part: [{part_name, material}]
|
||||
cad_part_materials: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
|
||||
|
||||
# AI validation
|
||||
ai_validation_status: Mapped[AIValidationStatus] = mapped_column(
|
||||
SAEnum(AIValidationStatus), default=AIValidationStatus.not_started, nullable=False
|
||||
)
|
||||
ai_validation_result: Mapped[dict] = mapped_column(JSONB, nullable=True)
|
||||
|
||||
item_status: Mapped[ItemStatus] = mapped_column(SAEnum(ItemStatus), default=ItemStatus.pending, nullable=False)
|
||||
notes: Mapped[str] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
order: Mapped["Order"] = relationship("Order", back_populates="items")
|
||||
cad_file: Mapped["CadFile"] = relationship("CadFile", back_populates="order_items")
|
||||
|
||||
@property
|
||||
def cad_parsed_objects(self) -> list[str] | None:
|
||||
"""Part names extracted from the linked STEP file, for Pydantic serialization."""
|
||||
if self.cad_file and self.cad_file.parsed_objects:
|
||||
return self.cad_file.parsed_objects.get("objects") or []
|
||||
return None
|
||||
@@ -0,0 +1,52 @@
|
||||
import uuid
|
||||
import enum
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import String, DateTime, Text, ForeignKey, Numeric
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class OrderLine(Base):
|
||||
__tablename__ = "order_lines"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
order_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("orders.id", ondelete="CASCADE"), nullable=False, index=True
|
||||
)
|
||||
product_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("products.id"), nullable=False, index=True
|
||||
)
|
||||
output_type_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("output_types.id"), nullable=True
|
||||
)
|
||||
gewuenschte_bildnummer: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
item_status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending")
|
||||
render_status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending")
|
||||
result_path: Mapped[str | None] = mapped_column(String(1000), nullable=True)
|
||||
render_log: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||||
ai_validation_status: Mapped[str] = mapped_column(String(20), nullable=False, default="not_started")
|
||||
ai_validation_result: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||||
flamenco_job_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
render_backend_used: Mapped[str | None] = mapped_column(String(20), nullable=True)
|
||||
render_started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
render_completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
unit_price: Mapped[Decimal | None] = mapped_column(Numeric(10, 2), nullable=True)
|
||||
render_position_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("product_render_positions.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
)
|
||||
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
|
||||
)
|
||||
|
||||
order: Mapped["Order"] = relationship("Order", back_populates="lines")
|
||||
product: Mapped["Product"] = relationship("Product", back_populates="order_lines")
|
||||
output_type: Mapped["OutputType | None"] = relationship("OutputType", back_populates="order_lines")
|
||||
render_position: Mapped["ProductRenderPosition | None"] = relationship(
|
||||
"ProductRenderPosition", back_populates="order_lines"
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, Boolean, Text, Integer, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
VALID_RENDER_BACKENDS = {"celery", "flamenco", "auto"}
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class OutputType(Base):
|
||||
__tablename__ = "output_types"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name: Mapped[str] = mapped_column(String(200), unique=True, nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
renderer: Mapped[str] = mapped_column(String(50), nullable=False, default="threejs")
|
||||
render_settings: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
||||
output_format: Mapped[str] = mapped_column(String(20), nullable=False, default="png")
|
||||
sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
compatible_categories: Mapped[list] = mapped_column(JSONB, default=list, server_default="[]")
|
||||
render_backend: Mapped[str] = mapped_column(String(20), nullable=False, default="auto", server_default="auto")
|
||||
is_animation: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
|
||||
transparent_bg: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
|
||||
cycles_device: Mapped[str | None] = mapped_column(String(10), nullable=True, default=None)
|
||||
pricing_tier_id: Mapped[int | None] = mapped_column(
|
||||
Integer, ForeignKey("pricing_tiers.id", ondelete="SET NULL"), nullable=True, index=True
|
||||
)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
|
||||
)
|
||||
|
||||
order_lines: Mapped[list["OrderLine"]] = relationship("OrderLine", back_populates="output_type")
|
||||
pricing_tier: Mapped["PricingTier | None"] = relationship("PricingTier", back_populates="output_types")
|
||||
@@ -0,0 +1,25 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import String, Boolean, DateTime, Text, Numeric, Integer, UniqueConstraint, Index
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class PricingTier(Base):
|
||||
__tablename__ = "pricing_tiers"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
category_key: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
quality_level: Mapped[str] = mapped_column(String(50), nullable=False, default="Normal")
|
||||
price_per_item: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
output_types: Mapped[list["OutputType"]] = relationship("OutputType", back_populates="pricing_tier")
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("category_key", "quality_level", name="uq_pricing_tier"),
|
||||
Index("ix_pricing_tiers_category_key", "category_key"),
|
||||
)
|
||||
@@ -0,0 +1,66 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, Boolean, Text, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Product(Base):
|
||||
__tablename__ = "products"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
pim_id: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
name: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
category_key: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
|
||||
ebene1: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
ebene2: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
baureihe: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
produkt_baureihe: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
lagertyp: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
name_cad_modell: Mapped[str | None] = mapped_column(String(500), nullable=True, index=True)
|
||||
gewuenschte_bildnummer: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
medias_rendering: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
||||
components: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
|
||||
cad_part_materials: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
|
||||
cad_file_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("cad_files.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
arbeitspaket: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
source_excel: Mapped[str | None] = mapped_column(String(1000), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
|
||||
)
|
||||
|
||||
cad_file: Mapped["CadFile | None"] = relationship("CadFile", back_populates="products")
|
||||
order_lines: Mapped[list["OrderLine"]] = relationship(
|
||||
"OrderLine", back_populates="product", cascade="all, delete-orphan"
|
||||
)
|
||||
render_positions: Mapped[list["ProductRenderPosition"]] = relationship(
|
||||
"ProductRenderPosition", back_populates="product",
|
||||
cascade="all, delete-orphan", order_by="ProductRenderPosition.sort_order"
|
||||
)
|
||||
|
||||
@property
|
||||
def thumbnail_url(self) -> str | None:
|
||||
if self.cad_file and self.cad_file.thumbnail_path:
|
||||
from pathlib import Path
|
||||
return f"/thumbnails/{Path(self.cad_file.thumbnail_path).name}"
|
||||
return None
|
||||
|
||||
@property
|
||||
def processing_status(self) -> str | None:
|
||||
if self.cad_file:
|
||||
return self.cad_file.processing_status.value if hasattr(
|
||||
self.cad_file.processing_status, 'value'
|
||||
) else str(self.cad_file.processing_status)
|
||||
return None
|
||||
|
||||
@property
|
||||
def cad_parsed_objects(self) -> list[str] | None:
|
||||
if self.cad_file and self.cad_file.parsed_objects:
|
||||
return self.cad_file.parsed_objects.get("objects") or []
|
||||
return None
|
||||
@@ -0,0 +1,28 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, Boolean, Integer, Float, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class ProductRenderPosition(Base):
|
||||
__tablename__ = "product_render_positions"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
product_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("products.id", ondelete="CASCADE"), nullable=False, index=True
|
||||
)
|
||||
name: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
rotation_x: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
||||
rotation_y: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
||||
rotation_z: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
||||
is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
|
||||
)
|
||||
|
||||
product: Mapped["Product"] = relationship("Product", back_populates="render_positions")
|
||||
order_lines: Mapped[list["OrderLine"]] = relationship("OrderLine", back_populates="render_position")
|
||||
@@ -0,0 +1,30 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, Boolean, Text, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class RenderTemplate(Base):
|
||||
__tablename__ = "render_templates"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name: Mapped[str] = mapped_column(String(300), nullable=False)
|
||||
category_key: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
output_type_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("output_types.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
blend_file_path: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
original_filename: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
target_collection: Mapped[str] = mapped_column(String(200), nullable=False, default="Product", server_default="Product")
|
||||
material_replace_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
|
||||
lighting_only: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
|
||||
shadow_catcher_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
|
||||
camera_orbit: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default="true")
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default="true")
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default="now()")
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default="now()", onupdate=datetime.utcnow)
|
||||
|
||||
output_type = relationship("OutputType", lazy="joined")
|
||||
@@ -0,0 +1,11 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, String, Text, DateTime
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class SystemSetting(Base):
|
||||
__tablename__ = "system_settings"
|
||||
|
||||
key = Column(String(100), primary_key=True)
|
||||
value = Column(Text, nullable=True)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
@@ -0,0 +1,24 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, Boolean, DateTime, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Template(Base):
|
||||
__tablename__ = "templates"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
category_key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True)
|
||||
# JSONB config for each of the 11 standard columns: {col_index: {label, required, optional}}
|
||||
standard_fields: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
||||
# JSONB schema for expected component pairs
|
||||
component_schema: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=True)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
orders: Mapped[list["Order"]] = relationship("Order", back_populates="template")
|
||||
@@ -0,0 +1,29 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, Boolean, DateTime, Enum as SAEnum
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from app.database import Base
|
||||
import enum
|
||||
|
||||
|
||||
class UserRole(str, enum.Enum):
|
||||
admin = "admin"
|
||||
project_manager = "project_manager"
|
||||
client = "client"
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True)
|
||||
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
full_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
role: Mapped[UserRole] = mapped_column(SAEnum(UserRole), default=UserRole.client, nullable=False)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
orders: Mapped[list["Order"]] = relationship("Order", back_populates="created_by_user", foreign_keys="Order.created_by")
|
||||
audit_logs: Mapped[list["AuditLog"]] = relationship("AuditLog", back_populates="user", foreign_keys="AuditLog.user_id")
|
||||
Reference in New Issue
Block a user