feat(projects): per-module status (independent sound/vibration lifecycle)

Each ProjectModule now carries its own status (active|on_hold|completed)
so one half of a combined project can wrap up while the other keeps
running — e.g. mark Sound "completed" while Vibration stays "active",
without archiving the whole project.

- models.py: ProjectModule.status column (default 'active')
- migrate_add_module_status.py: idempotent ALTER (run on prod before deploy)
- projects.py: _get_module_statuses() helper, MODULE_STATUSES, and a
  PUT /{id}/modules/{type}/status endpoint; module_status now included in
  the project GET, header, and /list contexts so the UI can render it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015m9FuJvk65kJmmP3c9c6r1
This commit is contained in:
2026-06-22 20:24:27 +00:00
parent 5dc0aa4064
commit 092b72f63c
3 changed files with 102 additions and 0 deletions
+4
View File
@@ -225,6 +225,10 @@ class ProjectModule(Base):
project_id = Column(String, nullable=False, index=True) # FK to projects.id
module_type = Column(String, nullable=False) # sound_monitoring | vibration_monitoring | ...
enabled = Column(Boolean, default=True, nullable=False)
# Per-module lifecycle, independent of the parent project's status. Lets one
# part of a combined project wrap up (e.g. sound "completed") while another
# keeps running ("active"). Values: active | on_hold | completed.
status = Column(String, default="active", nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
__table_args__ = (UniqueConstraint("project_id", "module_type", name="uq_project_module"),)