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:
@@ -4,7 +4,7 @@ from flask import Blueprint, request
|
||||
from flask_jwt_extended import jwt_required
|
||||
|
||||
from shopdb.extensions import db
|
||||
from shopdb.core.models import Location
|
||||
from shopdb.core.models import Location, LocationType
|
||||
from shopdb.utils.responses import (
|
||||
success_response,
|
||||
error_response,
|
||||
@@ -16,6 +16,19 @@ from shopdb.utils.pagination import get_pagination_params, paginate_query
|
||||
locations_bp = Blueprint('locations', __name__)
|
||||
|
||||
|
||||
@locations_bp.route('/types', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def list_location_types():
|
||||
"""List all location types."""
|
||||
types = LocationType.query.filter_by(isactive=True).order_by(
|
||||
LocationType.locationtype).all()
|
||||
return success_response([{
|
||||
'locationtypeid': t.locationtypeid,
|
||||
'locationtype': t.locationtype,
|
||||
'description': t.description,
|
||||
} for t in types])
|
||||
|
||||
|
||||
@locations_bp.route('', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def list_locations():
|
||||
@@ -81,6 +94,8 @@ def create_location():
|
||||
floor=data.get('floor'),
|
||||
room=data.get('room'),
|
||||
description=data.get('description'),
|
||||
locationtypeid=data.get('locationtypeid'),
|
||||
parentlocationid=data.get('parentlocationid'),
|
||||
mapimage=data.get('mapimage'),
|
||||
mapwidth=data.get('mapwidth'),
|
||||
mapheight=data.get('mapheight')
|
||||
@@ -117,7 +132,9 @@ def update_location(location_id: int):
|
||||
http_code=409
|
||||
)
|
||||
|
||||
for key in ['locationname', 'building', 'floor', 'room', 'description', 'mapimage', 'mapwidth', 'mapheight', 'isactive']:
|
||||
for key in ['locationname', 'building', 'floor', 'room', 'description',
|
||||
'locationtypeid', 'parentlocationid', 'mapimage', 'mapwidth',
|
||||
'mapheight', 'isactive']:
|
||||
if key in data:
|
||||
setattr(loc, key, data[key])
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from .machine import MachineType
|
||||
from .vendor import Vendor
|
||||
from .model import Model
|
||||
from .businessunit import BusinessUnit
|
||||
from .location import Location
|
||||
from .location import Location, LocationType
|
||||
from .operatingsystem import OperatingSystem
|
||||
from .relationship import AssetRelationship, RelationshipType
|
||||
from .communication import Communication, CommunicationType
|
||||
@@ -32,6 +32,7 @@ __all__ = [
|
||||
'Model',
|
||||
'BusinessUnit',
|
||||
'Location',
|
||||
'LocationType',
|
||||
'OperatingSystem',
|
||||
# Relationships
|
||||
'AssetRelationship',
|
||||
|
||||
@@ -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}>"
|
||||
|
||||
Reference in New Issue
Block a user