2026-08-24 17:56:13 +02:00
|
|
|
"""Modèles C4 : contrats photocopieurs et stock de consommables partagé."""
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
|
|
|
|
from ...extensions import db
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PhotocopierContract(db.Model):
|
|
|
|
|
__tablename__ = "photocopier_contracts"
|
|
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
|
supplier_id = db.Column(db.Integer, db.ForeignKey("companies.id"), nullable=False, index=True)
|
|
|
|
|
name = db.Column(db.String(200), nullable=False)
|
|
|
|
|
reference = db.Column(db.String(120), nullable=True)
|
|
|
|
|
start_date = db.Column(db.Date, nullable=False)
|
|
|
|
|
end_date = db.Column(db.Date, nullable=False)
|
|
|
|
|
quota_start_date = db.Column(db.Date, nullable=True)
|
|
|
|
|
anniversary_month = db.Column(db.Integer, nullable=False)
|
|
|
|
|
anniversary_day = db.Column(db.Integer, nullable=False)
|
|
|
|
|
status = db.Column(db.String(20), nullable=False, default="ACTIVE", index=True)
|
|
|
|
|
notes = db.Column(db.Text, nullable=True)
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
|
|
|
|
|
closed_at = db.Column(db.Date, nullable=True)
|
|
|
|
|
|
|
|
|
|
supplier = db.relationship("Company")
|
|
|
|
|
periods = db.relationship("PhotocopierContractPeriod", back_populates="contract", cascade="all, delete-orphan", order_by="PhotocopierContractPeriod.start_date")
|
|
|
|
|
assignments = db.relationship("ContractEquipmentAssignment", back_populates="contract", cascade="all, delete-orphan")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PhotocopierContractPeriod(db.Model):
|
|
|
|
|
__tablename__ = "photocopier_contract_periods"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
db.UniqueConstraint("contract_id", "start_date", name="uq_c4_contract_period_start"),
|
|
|
|
|
db.CheckConstraint("end_date >= start_date", name="ck_c4_period_dates"),
|
|
|
|
|
db.CheckConstraint("(quota_bw IS NULL OR quota_bw >= 0) AND (quota_color IS NULL OR quota_color >= 0)", name="ck_c4_period_quotas"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
|
contract_id = db.Column(db.Integer, db.ForeignKey("photocopier_contracts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
start_date = db.Column(db.Date, nullable=False, index=True)
|
|
|
|
|
end_date = db.Column(db.Date, nullable=False, index=True)
|
|
|
|
|
quota_bw = db.Column(db.Integer, nullable=True)
|
|
|
|
|
quota_color = db.Column(db.Integer, nullable=True)
|
|
|
|
|
bw_included_unit_price = db.Column(db.Numeric(12, 6), nullable=True)
|
|
|
|
|
bw_overage_unit_price = db.Column(db.Numeric(12, 6), nullable=True)
|
|
|
|
|
color_included_unit_price = db.Column(db.Numeric(12, 6), nullable=True)
|
|
|
|
|
color_overage_unit_price = db.Column(db.Numeric(12, 6), nullable=True)
|
|
|
|
|
status = db.Column(db.String(20), nullable=False, default="OPEN")
|
|
|
|
|
|
|
|
|
|
contract = db.relationship("PhotocopierContract", back_populates="periods")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContractEquipmentAssignment(db.Model):
|
|
|
|
|
__tablename__ = "contract_equipment_assignments"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
db.UniqueConstraint("contract_id", "equipment_id", "entry_date", name="uq_c4_contract_equipment_entry"),
|
|
|
|
|
db.CheckConstraint("exit_date IS NULL OR exit_date >= entry_date", name="ck_c4_assignment_dates"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
|
contract_id = db.Column(db.Integer, db.ForeignKey("photocopier_contracts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
equipment_id = db.Column(db.Integer, db.ForeignKey("equipments.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
entry_date = db.Column(db.Date, nullable=False)
|
|
|
|
|
exit_date = db.Column(db.Date, nullable=True)
|
|
|
|
|
replacement_reason = db.Column(db.String(40), nullable=True)
|
|
|
|
|
replacement_comment = db.Column(db.Text, nullable=True)
|
|
|
|
|
|
|
|
|
|
contract = db.relationship("PhotocopierContract", back_populates="assignments")
|
|
|
|
|
equipment = db.relationship("Equipment")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConsumableOrder(db.Model):
|
|
|
|
|
__tablename__ = "consumable_orders"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
db.CheckConstraint("quantity_ordered > 0", name="ck_c4_order_quantity"),
|
|
|
|
|
db.CheckConstraint("quantity_received >= 0 AND quantity_received <= quantity_ordered", name="ck_c4_order_received"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
|
consumable_id = db.Column(db.Integer, db.ForeignKey("consumables.id", ondelete="RESTRICT"), nullable=False, index=True)
|
|
|
|
|
supplier_id = db.Column(db.Integer, db.ForeignKey("companies.id"), nullable=True, index=True)
|
|
|
|
|
quantity_ordered = db.Column(db.Integer, nullable=False)
|
|
|
|
|
quantity_received = db.Column(db.Integer, nullable=False, default=0)
|
|
|
|
|
status = db.Column(db.String(30), nullable=False, default="TO_ORDER", index=True)
|
|
|
|
|
ordered_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
received_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
comment = db.Column(db.Text, nullable=True)
|
|
|
|
|
created_by_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
|
2026-08-24 20:17:17 +02:00
|
|
|
cancelled_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
cancelled_by_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
|
|
|
|
|
cancellation_reason = db.Column(db.Text, nullable=True)
|
2026-08-24 17:56:13 +02:00
|
|
|
|
|
|
|
|
consumable = db.relationship("Consumable")
|
|
|
|
|
supplier = db.relationship("Company")
|
2026-08-24 20:17:17 +02:00
|
|
|
created_by = db.relationship("User", foreign_keys=[created_by_id])
|
|
|
|
|
cancelled_by = db.relationship("User", foreign_keys=[cancelled_by_id])
|
2026-08-24 17:56:13 +02:00
|
|
|
receipts = db.relationship("ConsumableReceipt", back_populates="order", cascade="all, delete-orphan")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def remaining_quantity(self):
|
|
|
|
|
return max((self.quantity_ordered or 0) - (self.quantity_received or 0), 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConsumableReceipt(db.Model):
|
|
|
|
|
__tablename__ = "consumable_receipts"
|
|
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
|
order_id = db.Column(db.Integer, db.ForeignKey("consumable_orders.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
quantity = db.Column(db.Integer, nullable=False)
|
|
|
|
|
received_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
|
|
|
|
|
received_by_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
|
|
|
|
|
comment = db.Column(db.Text, nullable=True)
|
|
|
|
|
|
|
|
|
|
order = db.relationship("ConsumableOrder", back_populates="receipts")
|
|
|
|
|
received_by = db.relationship("User")
|