This commit is contained in:
2026-05-31 20:19:44 +12:00
parent 2f2466ecac
commit 84792c0947
59 changed files with 5412 additions and 898 deletions
+5 -1
View File
@@ -3,9 +3,10 @@ from app.models.assumption import FreightCostRule, PackagingCostRule, ProcessCos
from app.models.client_access import ClientAccessAuditEvent, ClientAccount, ClientFeatureAccess, ClientUser, ClientUserModulePermission
from app.models.mix_calculator import MixCalculatorSession, MixCalculatorSessionLine
from app.models.mix import Mix, MixIngredient
from app.models.product import Product
from app.models.product import Product, ProductIngredient
from app.models.raw_material import RawMaterial, RawMaterialPriceVersion
from app.models.scenario import CostingResult, Scenario
from app.models.throughput import ProductionThroughput, ThroughputProduct
__all__ = [
"ClientAccount",
@@ -23,6 +24,9 @@ __all__ = [
"Permission",
"ProcessCostRule",
"Product",
"ProductIngredient",
"ProductionThroughput",
"ThroughputProduct",
"RawMaterial",
"RawMaterialPriceVersion",
"Role",
+23 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, Text
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.session import Base
@@ -29,6 +29,28 @@ class Product(Base):
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
mix: Mapped["Mix"] = relationship(back_populates="products")
ingredients: Mapped[list["ProductIngredient"]] = relationship(
back_populates="product",
cascade="all, delete-orphan",
order_by="ProductIngredient.sort_order",
)
class ProductIngredient(Base):
__tablename__ = "product_ingredients"
__table_args__ = (UniqueConstraint("product_id", "raw_material_id", name="uq_product_ingredient"),)
id: Mapped[int] = mapped_column(primary_key=True)
tenant_id: Mapped[str] = mapped_column(String(64), default="default", index=True)
product_id: Mapped[int] = mapped_column(ForeignKey("products.id"), index=True)
raw_material_id: Mapped[int] = mapped_column(ForeignKey("raw_materials.id"), index=True)
quantity_kg: Mapped[float] = mapped_column(Float)
sort_order: Mapped[int] = mapped_column(Integer, default=0)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
product: Mapped[Product] = relationship(back_populates="ingredients")
raw_material: Mapped["RawMaterial"] = relationship()
from app.models.mix import Mix # noqa: E402
from app.models.raw_material import RawMaterial # noqa: E402
+70
View File
@@ -0,0 +1,70 @@
from __future__ import annotations
from datetime import date, datetime
from sqlalchemy import Boolean, Date, DateTime, Float, ForeignKey, Index, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.session import Base
class ThroughputProduct(Base):
__tablename__ = "throughput_products"
__table_args__ = (
Index("ix_throughput_products_tenant_item", "tenant_id", "item_id", unique=True),
Index("ix_throughput_products_tenant_name", "tenant_id", "name"),
)
id: Mapped[int] = mapped_column(primary_key=True)
tenant_id: Mapped[str] = mapped_column(String(64), default="default", index=True)
item_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
name: Mapped[str] = mapped_column(String(255))
default_bag_size: Mapped[float | None] = mapped_column(Float, nullable=True)
is_bulka_default: Mapped[bool] = mapped_column(Boolean, default=False)
active: Mapped[bool] = mapped_column(Boolean, default=True)
is_stock_item: Mapped[bool] = mapped_column(Boolean, default=True)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
entries: Mapped[list["ProductionThroughput"]] = relationship(back_populates="product")
class ProductionThroughput(Base):
__tablename__ = "production_throughput_entries"
__table_args__ = (
Index("ix_throughput_entries_tenant_date", "tenant_id", "production_date"),
)
id: Mapped[int] = mapped_column(primary_key=True)
tenant_id: Mapped[str] = mapped_column(String(64), default="default", index=True)
production_date: Mapped[date] = mapped_column(Date)
product_id: Mapped[int | None] = mapped_column(ForeignKey("throughput_products.id"), nullable=True, index=True)
product_name_snapshot: Mapped[str] = mapped_column(String(255))
bag_size: Mapped[float | None] = mapped_column(Float, nullable=True)
scales_checked: Mapped[bool] = mapped_column(Boolean, default=True)
label_correct: Mapped[bool] = mapped_column(Boolean, default=True)
bag_sealed: Mapped[bool] = mapped_column(Boolean, default=True)
pallet_good_condition: Mapped[bool] = mapped_column(Boolean, default=True)
sample_box_no: Mapped[str | None] = mapped_column(String(64), nullable=True)
test_weight_1: Mapped[float | None] = mapped_column(Float, nullable=True)
test_weight_2: Mapped[float | None] = mapped_column(Float, nullable=True)
test_weight_3: Mapped[float | None] = mapped_column(Float, nullable=True)
test_weight_4: Mapped[float | None] = mapped_column(Float, nullable=True)
test_weight_5: Mapped[float | None] = mapped_column(Float, nullable=True)
quantity: Mapped[float] = mapped_column(Float, default=0.0)
quantity_type: Mapped[str] = mapped_column(String(8), default="bags")
calculated_kg: Mapped[float] = mapped_column(Float, default=0.0)
staff_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by: Mapped[str | None] = mapped_column(String(255), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
product: Mapped[ThroughputProduct | None] = relationship(back_populates="entries")