Add locationtypes + location tree (ADR-001)

- New locationtypes lookup (LocationType model) seeded with section, cell,
  subcell, operation, meetingroom, lab, office, storage, hallway,
  networkcloset, building.
- locations gains locationtypeid + parentlocationid (self-FK) for the site
  location tree. Migration 7c03; cli reference-data seeds the types.
- GET /api/locations/types; locations CRUD accepts type + parent; list/detail
  return locationtypename + parentlocationname.
- Locations settings page: Type column + Type/Parent selectors in the modal.

Realizes the operation-as-Location model (operations are locations with
locationtypeid='operation').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-06-26 12:09:05 -04:00
parent a58feebe7a
commit 6fb8adc256
7 changed files with 192 additions and 10 deletions

View File

@@ -1,9 +1,25 @@
"""Location model."""
"""Location + LocationType models."""
from shopdb.extensions import db
from .base import BaseModel
class LocationType(BaseModel):
"""Location classification (ADR-001 shared reference data).
Seeded values: section, cell, subcell, operation, meetingroom, lab,
office, storage, hallway, networkcloset, building. Sites can extend.
"""
__tablename__ = 'locationtypes'
locationtypeid = db.Column(db.Integer, primary_key=True)
locationtype = db.Column(db.String(50), unique=True, nullable=False)
description = db.Column(db.Text)
def __repr__(self):
return f"<LocationType {self.locationtype}>"
class Location(BaseModel):
"""Physical location model."""
__tablename__ = 'locations'
@@ -15,6 +31,19 @@ class Location(BaseModel):
room = db.Column(db.String(50))
description = db.Column(db.Text)
# Classification + tree (ADR-001)
locationtypeid = db.Column(
db.Integer,
db.ForeignKey('locationtypes.locationtypeid'),
nullable=True
)
parentlocationid = db.Column(
db.Integer,
db.ForeignKey('locations.locationid'),
nullable=True,
comment='Parent location for the site location tree'
)
# Map configuration
mapimage = db.Column(db.String(500), comment='Path to floor map image')
mapwidth = db.Column(db.Integer)
@@ -26,5 +55,15 @@ class Location(BaseModel):
mapx = db.Column(db.Integer, comment='Default X coordinate for assets at this location')
mapy = db.Column(db.Integer, comment='Default Y coordinate for assets at this location')
# Relationships
locationtype = db.relationship('LocationType')
parent = db.relationship('Location', remote_side=[locationid])
def to_dict(self):
data = super().to_dict()
data['locationtypename'] = self.locationtype.locationtype if self.locationtype else None
data['parentlocationname'] = self.parent.locationname if self.parent else None
return data
def __repr__(self):
return f"<Location {self.locationname}>"