Implement sync_from_state methods for Area, Brand, Item, RoomFunction, and Room models; enhance entity management and foreign key resolution in settings
This commit is contained in:
parent
8a5c5db9e0
commit
7833c4828b
9 changed files with 359 additions and 186 deletions
|
@ -6,6 +6,7 @@ from sqlalchemy import Identity, Integer, Unicode
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
|
from ..temp import is_temp_id
|
||||||
|
|
||||||
class Area(db.Model):
|
class Area(db.Model):
|
||||||
__tablename__ = 'Areas'
|
__tablename__ = 'Areas'
|
||||||
|
@ -28,31 +29,54 @@ class Area(db.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sync_from_state(cls, submitted_items: list[dict]):
|
def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]:
|
||||||
"""
|
"""
|
||||||
Syncs the Area table with the provided list of dictionaries.
|
Syncs the Area table (aka 'sections') with the submitted list.
|
||||||
Adds new areas and removes areas that are not in the list.
|
Supports add, update, and delete.
|
||||||
|
Also returns a mapping of temp IDs to real IDs for resolution.
|
||||||
"""
|
"""
|
||||||
submitted = {
|
submitted_clean = []
|
||||||
str(item.get("name", "")).strip()
|
seen_ids = set()
|
||||||
for item in submitted_items
|
temp_id_map = {}
|
||||||
if isinstance(item, dict) and str(item.get("name", "")).strip()
|
|
||||||
}
|
for item in submitted_items:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
name = str(item.get("name", "")).strip()
|
||||||
|
if not name:
|
||||||
|
continue
|
||||||
|
submitted_clean.append({"id": item.get("id"), "name": name})
|
||||||
|
if isinstance(item.get("id"), int) and item["id"] >= 0:
|
||||||
|
seen_ids.add(item["id"])
|
||||||
|
|
||||||
existing_query = db.session.query(cls)
|
existing_query = db.session.query(cls)
|
||||||
existing = {area.name: area for area in existing_query.all()}
|
existing_by_id = {area.id: area for area in existing_query.all()}
|
||||||
|
existing_ids = set(existing_by_id.keys())
|
||||||
|
|
||||||
existing_names = set(existing.keys())
|
print(f"Existing area IDs: {existing_ids}")
|
||||||
|
print(f"Submitted area IDs: {seen_ids}")
|
||||||
|
|
||||||
print(f"Existing areas: {existing_names}")
|
for entry in submitted_clean:
|
||||||
print(f"Submitted areas: {submitted}")
|
submitted_id = entry.get("id")
|
||||||
print(f"Areas to add: {submitted - existing_names}")
|
submitted_name = entry["name"]
|
||||||
print(f"Areas to remove: {existing_names - submitted}")
|
|
||||||
|
if is_temp_id(submitted_id):
|
||||||
|
new_area = cls(name=submitted_name)
|
||||||
|
db.session.add(new_area)
|
||||||
|
db.session.flush() # Get the real ID
|
||||||
|
temp_id_map[submitted_id] = new_area.id
|
||||||
|
print(f"➕ Adding area: {submitted_name}")
|
||||||
|
elif submitted_id in existing_by_id:
|
||||||
|
area = existing_by_id[submitted_id]
|
||||||
|
if area.name != submitted_name:
|
||||||
|
print(f"✏️ Updating area {area.id}: '{area.name}' → '{submitted_name}'")
|
||||||
|
area.name = submitted_name
|
||||||
|
|
||||||
|
for existing_id in existing_ids - seen_ids:
|
||||||
|
area = existing_by_id[existing_id]
|
||||||
|
db.session.delete(area)
|
||||||
|
print(f"🗑️ Removing area: {area.name}")
|
||||||
|
|
||||||
|
return temp_id_map
|
||||||
|
|
||||||
for name in submitted - existing_names:
|
|
||||||
db.session.add(cls(name=name))
|
|
||||||
print(f"➕ Adding area: {name}")
|
|
||||||
|
|
||||||
for name in existing_names - submitted:
|
|
||||||
db.session.delete(existing[name])
|
|
||||||
print(f"🗑️ Removing area: {name}")
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from sqlalchemy import Identity, Integer, Unicode
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
|
from ..temp import is_temp_id
|
||||||
|
|
||||||
class Brand(db.Model):
|
class Brand(db.Model):
|
||||||
__tablename__ = 'Brands'
|
__tablename__ = 'Brands'
|
||||||
|
@ -28,31 +29,47 @@ class Brand(db.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sync_from_state(cls, submitted_items: list[dict]):
|
def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]:
|
||||||
"""
|
submitted_clean = []
|
||||||
Syncs the Brand table with the provided list of dictionaries.
|
seen_ids = set()
|
||||||
Adds new brands and removes brands that are not in the list.
|
temp_id_map = {}
|
||||||
"""
|
|
||||||
submitted = {
|
|
||||||
str(item.get("name", "")).strip()
|
|
||||||
for item in submitted_items
|
|
||||||
if isinstance(item, dict) and str(item.get("name", "")).strip()
|
|
||||||
}
|
|
||||||
|
|
||||||
existing_query = db.session.query(cls)
|
for item in submitted_items:
|
||||||
existing = {brand.name: brand for brand in existing_query.all()}
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
name = str(item.get("name", "")).strip()
|
||||||
|
if not name:
|
||||||
|
continue
|
||||||
|
submitted_clean.append({"id": item.get("id"), "name": name})
|
||||||
|
if isinstance(item.get("id"), int) and item["id"] >= 0:
|
||||||
|
seen_ids.add(item["id"])
|
||||||
|
|
||||||
existing_names = set(existing.keys())
|
existing_by_id = {b.id: b for b in db.session.query(cls).all()}
|
||||||
|
existing_ids = set(existing_by_id.keys())
|
||||||
|
|
||||||
print(f"Existing brands: {existing_names}")
|
print(f"Existing brand IDs: {existing_ids}")
|
||||||
print(f"Submitted brands: {submitted}")
|
print(f"Submitted brand IDs: {seen_ids}")
|
||||||
print(f"Brands to add: {submitted - existing_names}")
|
|
||||||
print(f"Brands to remove: {existing_names - submitted}")
|
|
||||||
|
|
||||||
for name in submitted - existing_names:
|
for entry in submitted_clean:
|
||||||
db.session.add(cls(name=name))
|
submitted_id = entry.get("id")
|
||||||
|
name = entry["name"]
|
||||||
|
|
||||||
|
if is_temp_id(submitted_id):
|
||||||
|
obj = cls(name=name)
|
||||||
|
db.session.add(obj)
|
||||||
|
db.session.flush()
|
||||||
|
temp_id_map[submitted_id] = obj.id
|
||||||
print(f"➕ Adding brand: {name}")
|
print(f"➕ Adding brand: {name}")
|
||||||
|
elif submitted_id in existing_by_id:
|
||||||
|
obj = existing_by_id[submitted_id]
|
||||||
|
if obj.name != name:
|
||||||
|
print(f"✏️ Updating brand {obj.id}: '{obj.name}' → '{name}'")
|
||||||
|
obj.name = name
|
||||||
|
|
||||||
|
for id_to_remove in existing_ids - seen_ids:
|
||||||
|
db.session.delete(existing_by_id[id_to_remove])
|
||||||
|
print(f"🗑️ Removing brand ID {id_to_remove}")
|
||||||
|
|
||||||
|
return temp_id_map
|
||||||
|
|
||||||
|
|
||||||
for name in existing_names - submitted:
|
|
||||||
db.session.delete(existing[name])
|
|
||||||
print(f"🗑️ Removing brand: {name}")
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from sqlalchemy import Identity, Integer, Unicode
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
|
from ..temp import is_temp_id
|
||||||
|
|
||||||
class Item(db.Model):
|
class Item(db.Model):
|
||||||
__tablename__ = 'Items'
|
__tablename__ = 'Items'
|
||||||
|
@ -31,31 +32,46 @@ class Item(db.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sync_from_state(cls, submitted_items: list[dict]):
|
def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]:
|
||||||
"""
|
submitted_clean = []
|
||||||
Syncs the Items table with the provided list of dictionaries.
|
seen_ids = set()
|
||||||
Adds new items and removes items that are not in the list.
|
temp_id_map = {}
|
||||||
"""
|
|
||||||
submitted = {
|
|
||||||
str(item.get("name", "")).strip()
|
|
||||||
for item in submitted_items
|
|
||||||
if isinstance(item, dict) and str(item.get("name", "")).strip()
|
|
||||||
}
|
|
||||||
|
|
||||||
existing_query = db.session.query(cls)
|
for item in submitted_items:
|
||||||
existing = {item.description: item for item in existing_query.all()}
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
description = str(item.get("name", "")).strip()
|
||||||
|
if not description:
|
||||||
|
continue
|
||||||
|
submitted_clean.append({"id": item.get("id"), "description": description})
|
||||||
|
if isinstance(item.get("id"), int) and item["id"] >= 0:
|
||||||
|
seen_ids.add(item["id"])
|
||||||
|
|
||||||
existing_descriptions = set(existing.keys())
|
existing_by_id = {t.id: t for t in db.session.query(cls).all()}
|
||||||
|
existing_ids = set(existing_by_id.keys())
|
||||||
|
|
||||||
print(f"Existing items: {existing_descriptions}")
|
print(f"Existing item IDs: {existing_ids}")
|
||||||
print(f"Submitted items: {submitted}")
|
print(f"Submitted item IDs: {seen_ids}")
|
||||||
print(f"items to add: {submitted - existing_descriptions}")
|
|
||||||
print(f"items to remove: {existing_descriptions - submitted}")
|
|
||||||
|
|
||||||
for description in submitted - existing_descriptions:
|
for entry in submitted_clean:
|
||||||
db.session.add(cls(description=description))
|
submitted_id = entry.get("id")
|
||||||
print(f"➕ Adding item: {description}")
|
description = entry["description"]
|
||||||
|
|
||||||
|
if is_temp_id(submitted_id):
|
||||||
|
obj = cls(description=description)
|
||||||
|
db.session.add(obj)
|
||||||
|
db.session.flush()
|
||||||
|
temp_id_map[submitted_id] = obj.id
|
||||||
|
print(f"➕ Adding type: {description}")
|
||||||
|
elif submitted_id in existing_by_id:
|
||||||
|
obj = existing_by_id[submitted_id]
|
||||||
|
if obj.description != description:
|
||||||
|
print(f"✏️ Updating type {obj.id}: '{obj.description}' → '{description}'")
|
||||||
|
obj.description = description
|
||||||
|
|
||||||
|
for id_to_remove in existing_ids - seen_ids:
|
||||||
|
db.session.delete(existing_by_id[id_to_remove])
|
||||||
|
print(f"🗑️ Removing type ID {id_to_remove}")
|
||||||
|
|
||||||
|
return temp_id_map
|
||||||
|
|
||||||
for description in existing_descriptions - submitted:
|
|
||||||
db.session.delete(existing[description])
|
|
||||||
print(f"🗑️ Removing item: {description}")
|
|
|
@ -6,6 +6,7 @@ from sqlalchemy import Identity, Integer, Unicode
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
|
from ..temp import is_temp_id
|
||||||
|
|
||||||
class RoomFunction(db.Model):
|
class RoomFunction(db.Model):
|
||||||
__tablename__ = 'Room Functions'
|
__tablename__ = 'Room Functions'
|
||||||
|
@ -28,31 +29,46 @@ class RoomFunction(db.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sync_from_state(cls, submitted_items: list[dict]):
|
def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]:
|
||||||
"""
|
submitted_clean = []
|
||||||
Syncs the functions table with the provided list of dictionaries.
|
seen_ids = set()
|
||||||
Adds new functions and removes functions that are not in the list.
|
temp_id_map = {}
|
||||||
"""
|
|
||||||
submitted = {
|
|
||||||
str(item.get("name", "")).strip()
|
|
||||||
for item in submitted_items
|
|
||||||
if isinstance(item, dict) and str(item.get("name", "")).strip()
|
|
||||||
}
|
|
||||||
|
|
||||||
existing_query = db.session.query(cls)
|
for item in submitted_items:
|
||||||
existing = {item.description: item for item in existing_query.all()}
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
description = str(item.get("name", "")).strip()
|
||||||
|
if not description:
|
||||||
|
continue
|
||||||
|
submitted_clean.append({"id": item.get("id"), "description": description})
|
||||||
|
if isinstance(item.get("id"), int) and item["id"] >= 0:
|
||||||
|
seen_ids.add(item["id"])
|
||||||
|
|
||||||
existing_descriptions = set(existing.keys())
|
existing_by_id = {f.id: f for f in db.session.query(cls).all()}
|
||||||
|
existing_ids = set(existing_by_id.keys())
|
||||||
|
|
||||||
print(f"Existing functions: {existing_descriptions}")
|
print(f"Existing function IDs: {existing_ids}")
|
||||||
print(f"Submitted functions: {submitted}")
|
print(f"Submitted function IDs: {seen_ids}")
|
||||||
print(f"Functions to add: {submitted - existing_descriptions}")
|
|
||||||
print(f"Functions to remove: {existing_descriptions - submitted}")
|
|
||||||
|
|
||||||
for description in submitted - existing_descriptions:
|
for entry in submitted_clean:
|
||||||
db.session.add(cls(description=description))
|
submitted_id = entry.get("id")
|
||||||
print(f"➕ Adding item: {description}")
|
description = entry["description"]
|
||||||
|
|
||||||
|
if is_temp_id(submitted_id):
|
||||||
|
obj = cls(description=description)
|
||||||
|
db.session.add(obj)
|
||||||
|
db.session.flush()
|
||||||
|
temp_id_map[submitted_id] = obj.id
|
||||||
|
print(f"➕ Adding function: {description}")
|
||||||
|
elif submitted_id in existing_by_id:
|
||||||
|
obj = existing_by_id[submitted_id]
|
||||||
|
if obj.description != description:
|
||||||
|
print(f"✏️ Updating function {obj.id}: '{obj.description}' → '{description}'")
|
||||||
|
obj.description = description
|
||||||
|
|
||||||
|
for id_to_remove in existing_ids - seen_ids:
|
||||||
|
db.session.delete(existing_by_id[id_to_remove])
|
||||||
|
print(f"🗑️ Removing function ID {id_to_remove}")
|
||||||
|
|
||||||
|
return temp_id_map
|
||||||
|
|
||||||
for description in existing_descriptions - submitted:
|
|
||||||
db.session.delete(existing[description])
|
|
||||||
print(f"🗑️ Removing item: {description}")
|
|
137
models/rooms.py
137
models/rooms.py
|
@ -46,59 +46,116 @@ class Room(db.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sync_from_state(cls, submitted_rooms: list[dict], section_map: dict, function_map: dict, section_fallbacks: list, function_fallbacks: list):
|
def sync_from_state(
|
||||||
|
cls,
|
||||||
|
submitted_rooms: list[dict],
|
||||||
|
section_map: dict,
|
||||||
|
function_map: dict,
|
||||||
|
section_fallbacks: list,
|
||||||
|
function_fallbacks: list
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Syncs the Rooms table with the submitted room list.
|
Syncs the Rooms table with the submitted room list.
|
||||||
Resolves foreign keys using section_map and function_map.
|
Resolves foreign keys using section_map and function_map.
|
||||||
|
Supports add, update, and delete.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def resolve_id(raw_id, fallback_list, id_map, label):
|
def resolve_fk(raw_id, fallback_list, id_map, label):
|
||||||
try:
|
if not raw_id:
|
||||||
resolved = int(raw_id)
|
return None
|
||||||
if resolved >= 0:
|
|
||||||
if resolved in id_map.values():
|
|
||||||
return resolved
|
|
||||||
raise ValueError(f"ID {resolved} not found in {label}.")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
index = abs(resolved + 1)
|
# 🛠 Handle SQLAlchemy model objects and dicts both
|
||||||
|
name_entry = next(
|
||||||
|
(getattr(item, "name", None) or item.get("name")
|
||||||
|
for item in fallback_list
|
||||||
|
if str(getattr(item, "id", None) or item.get("id")) == str(raw_id)),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
if name_entry is None:
|
||||||
|
raise ValueError(f"Unable to resolve {label} ID: {raw_id}")
|
||||||
|
|
||||||
|
resolved_id = id_map.get(name_entry)
|
||||||
|
if resolved_id is None:
|
||||||
|
raise ValueError(f"{label.capitalize()} '{name_entry}' not found in ID map.")
|
||||||
|
return resolved_id
|
||||||
|
|
||||||
|
submitted_clean = []
|
||||||
|
seen_ids = set()
|
||||||
|
|
||||||
|
for room in submitted_rooms:
|
||||||
|
if not isinstance(room, dict):
|
||||||
|
continue
|
||||||
|
name = str(room.get("name", "")).strip()
|
||||||
|
if not name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rid = room.get("id")
|
||||||
|
section_id = room.get("section_id")
|
||||||
|
function_id = room.get("function_id")
|
||||||
|
|
||||||
|
submitted_clean.append({
|
||||||
|
"id": rid,
|
||||||
|
"name": name,
|
||||||
|
"section_id": section_id,
|
||||||
|
"function_id": function_id
|
||||||
|
})
|
||||||
|
|
||||||
|
if rid and not str(rid).startswith("room-"):
|
||||||
try:
|
try:
|
||||||
entry = fallback_list[index]
|
seen_ids.add(int(rid))
|
||||||
key = entry.get("name") if isinstance(entry, dict) else str(entry).strip()
|
except ValueError:
|
||||||
final_id = id_map.get(key)
|
pass # It's an invalid non-temp string
|
||||||
if final_id is None:
|
|
||||||
raise ValueError(f"ID for {key} not found in {label}.")
|
|
||||||
return final_id
|
|
||||||
except Exception as e:
|
|
||||||
raise ValueError(f"Failed to resolve {label} ID: {e}") from e
|
|
||||||
|
|
||||||
existing_query = db.session.query(cls)
|
existing_query = db.session.query(cls)
|
||||||
existing = {room.name: room for room in existing_query.all()}
|
existing_by_id = {room.id: room for room in existing_query.all()}
|
||||||
|
existing_ids = set(existing_by_id.keys())
|
||||||
|
|
||||||
submitted = {
|
print(f"Existing room IDs: {existing_ids}")
|
||||||
str(room.get("name", "")).strip(): room
|
print(f"Submitted room IDs: {seen_ids}")
|
||||||
for room in submitted_rooms
|
|
||||||
if isinstance(room, dict) and str(room.get("name", "")).strip()
|
|
||||||
}
|
|
||||||
|
|
||||||
existing_names = set(existing.keys())
|
for entry in submitted_clean:
|
||||||
submitted_names = set(submitted.keys())
|
rid = entry.get("id")
|
||||||
|
name = entry["name"]
|
||||||
|
|
||||||
print(f"Existing rooms: {existing_names}")
|
resolved_section_id = resolve_fk(entry.get("section_id"), section_fallbacks, section_map, "section")
|
||||||
print(f"Submitted rooms: {submitted_names}")
|
resolved_function_id = resolve_fk(entry.get("function_id"), function_fallbacks, function_map, "function")
|
||||||
print(f"Rooms to add: {submitted_names - existing_names}")
|
|
||||||
print(f"Rooms to remove: {existing_names - submitted_names}")
|
|
||||||
|
|
||||||
for name in submitted_names - existing_names:
|
if not rid or str(rid).startswith("room-"):
|
||||||
room_data = submitted[name]
|
new_room = cls(name=name, area_id=resolved_section_id, function_id=resolved_function_id)
|
||||||
area_id = resolve_id(room_data.get("section_id", -1), section_fallbacks, section_map, "section") if room_data.get("section_id") is not None else None
|
|
||||||
function_id = resolve_id(room_data.get("function_id", -1), function_fallbacks, function_map, "function") if room_data.get("function_id") is not None else None
|
|
||||||
new_room = cls(name=name, area_id=area_id, function_id=function_id)
|
|
||||||
db.session.add(new_room)
|
db.session.add(new_room)
|
||||||
print(f"➕ Adding room: {new_room}")
|
print(f"➕ Adding room: {new_room}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
rid_int = int(rid)
|
||||||
|
except ValueError:
|
||||||
|
print(f"⚠️ Invalid room ID format: {rid}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
room = existing_by_id.get(rid_int)
|
||||||
|
if not room:
|
||||||
|
print(f"⚠️ No matching room in DB for ID: {rid_int}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
updated = False
|
||||||
|
if room.name != name:
|
||||||
|
print(f"✏️ Updating room name {room.id}: '{room.name}' → '{name}'")
|
||||||
|
room.name = name
|
||||||
|
updated = True
|
||||||
|
if room.area_id != resolved_section_id:
|
||||||
|
print(f"✏️ Updating room area {room.id}: {room.area_id} → {resolved_section_id}")
|
||||||
|
room.area_id = resolved_section_id
|
||||||
|
updated = True
|
||||||
|
if room.function_id != resolved_function_id:
|
||||||
|
print(f"✏️ Updating room function {room.id}: {room.function_id} → {resolved_function_id}")
|
||||||
|
room.function_id = resolved_function_id
|
||||||
|
updated = True
|
||||||
|
if not updated:
|
||||||
|
print(f"✅ No changes to room {room.id}")
|
||||||
|
|
||||||
|
for existing_id in existing_ids - seen_ids:
|
||||||
|
room = existing_by_id[existing_id]
|
||||||
|
db.session.delete(room)
|
||||||
|
print(f"🗑️ Removing room: {room.name}")
|
||||||
|
|
||||||
|
|
||||||
for name in existing_names - submitted_names:
|
|
||||||
room_to_remove = existing[name]
|
|
||||||
db.session.delete(room_to_remove)
|
|
||||||
print(f"🗑️ Removing room: {room_to_remove}")
|
|
||||||
|
|
30
routes.py
30
routes.py
|
@ -412,21 +412,30 @@ def settings():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with db.session.begin():
|
with db.session.begin():
|
||||||
Brand.sync_from_state(state.get("brands", []))
|
# Sync each table and grab temp ID maps
|
||||||
Item.sync_from_state(state.get("types", []))
|
brand_map = Brand.sync_from_state(state.get("brands", []))
|
||||||
Area.sync_from_state(state.get("sections", []))
|
type_map = Item.sync_from_state(state.get("types", []))
|
||||||
RoomFunction.sync_from_state(state.get("functions", []))
|
section_map = Area.sync_from_state(state.get("sections", []))
|
||||||
|
function_map = RoomFunction.sync_from_state(state.get("functions", []))
|
||||||
|
|
||||||
# Refresh maps after inserts
|
# Fix up room foreign keys based on real IDs
|
||||||
section_map = {a.name: a.id for a in db.session.query(Area).all()}
|
submitted_rooms = []
|
||||||
function_map = {f.description: f.id for f in db.session.query(RoomFunction).all()}
|
for room in state.get("rooms", []):
|
||||||
|
room = dict(room) # shallow copy
|
||||||
|
sid = room.get("section_id")
|
||||||
|
fid = room.get("function_id")
|
||||||
|
if sid in section_map:
|
||||||
|
room["section_id"] = section_map[sid]
|
||||||
|
if fid in function_map:
|
||||||
|
room["function_id"] = function_map[fid]
|
||||||
|
submitted_rooms.append(room)
|
||||||
|
|
||||||
Room.sync_from_state(
|
Room.sync_from_state(
|
||||||
submitted_rooms=state.get("rooms", []),
|
submitted_rooms=submitted_rooms,
|
||||||
section_map=section_map,
|
section_map=section_map,
|
||||||
function_map=function_map,
|
function_map=function_map,
|
||||||
section_fallbacks=state.get("sections", []),
|
section_fallbacks=[{"id": v, "name": k} for k, v in section_map.items()],
|
||||||
function_fallbacks=state.get("functions", [])
|
function_fallbacks=[{"id": v, "name": k} for k, v in function_map.items()]
|
||||||
)
|
)
|
||||||
|
|
||||||
print("✅ COMMIT executed.")
|
print("✅ COMMIT executed.")
|
||||||
|
@ -454,3 +463,4 @@ def settings():
|
||||||
functions=[f.serialize() for f in functions],
|
functions=[f.serialize() for f in functions],
|
||||||
rooms=[r.serialize() for r in rooms],
|
rooms=[r.serialize() for r in rooms],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
const ComboBoxWidget = (() => {
|
const ComboBoxWidget = (() => {
|
||||||
let tempIdCounter = -1;
|
let tempIdCounter = 1;
|
||||||
|
|
||||||
|
function createTempId(prefix = "temp") {
|
||||||
|
return `${prefix}-${tempIdCounter++}`;
|
||||||
|
}
|
||||||
|
|
||||||
function createOption(text, value = null) {
|
function createOption(text, value = null) {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.textContent = text;
|
option.textContent = text;
|
||||||
option.value = value ?? (tempIdCounter--);
|
option.value = value ?? createTempId();
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +148,7 @@ const ComboBoxWidget = (() => {
|
||||||
initComboBox,
|
initComboBox,
|
||||||
createOption,
|
createOption,
|
||||||
sortOptions,
|
sortOptions,
|
||||||
handleComboAdd
|
handleComboAdd,
|
||||||
|
createTempId
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
6
temp.py
Normal file
6
temp.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
def is_temp_id(val):
|
||||||
|
return (
|
||||||
|
val is None or
|
||||||
|
(isinstance(val, int) and val < 0) or
|
||||||
|
(isinstance(val, str) and val.startswith("temp-"))
|
||||||
|
)
|
|
@ -59,7 +59,19 @@ submit_button=True
|
||||||
{% set room_editor %}
|
{% set room_editor %}
|
||||||
const roomEditor = new bootstrap.Modal(document.getElementById('roomEditor'));
|
const roomEditor = new bootstrap.Modal(document.getElementById('roomEditor'));
|
||||||
const roomNameInput = document.getElementById('roomName');
|
const roomNameInput = document.getElementById('roomName');
|
||||||
roomNameInput.value = document.getElementById('room-input').value;
|
const input = document.getElementById('room-input');
|
||||||
|
const name = input.value.trim();
|
||||||
|
const existingOption = Array.from(document.getElementById('room-list').options)
|
||||||
|
.find(opt => opt.textContent.trim() === name);
|
||||||
|
|
||||||
|
roomNameInput.value = name;
|
||||||
|
document.getElementById('roomId').value = existingOption?.value ?? ''; // this will be the ID or temp ID
|
||||||
|
|
||||||
|
if (existingOption?.dataset.sectionId)
|
||||||
|
document.getElementById('roomSection').value = existingOption.dataset.sectionId;
|
||||||
|
|
||||||
|
if (existingOption?.dataset.functionId)
|
||||||
|
document.getElementById('roomFunction').value = existingOption.dataset.functionId;
|
||||||
|
|
||||||
roomEditor.show();
|
roomEditor.show();
|
||||||
|
|
||||||
|
@ -71,7 +83,8 @@ submit_button=True
|
||||||
options=rooms,
|
options=rooms,
|
||||||
label='Rooms',
|
label='Rooms',
|
||||||
placeholder='Add a new room',
|
placeholder='Add a new room',
|
||||||
onAdd=room_editor
|
onAdd=room_editor,
|
||||||
|
onEdit=room_editor
|
||||||
) }}
|
) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,6 +102,7 @@ submit_button=True
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<label for="roomName" class="form-label">Room Name</label>
|
<label for="roomName" class="form-label">Room Name</label>
|
||||||
<input type="text" class="form-input" id="roomName" placeholder="Enter room name">
|
<input type="text" class="form-input" id="roomName" placeholder="Enter room name">
|
||||||
|
<input type="hidden" id="roomId">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -134,16 +148,20 @@ submit_button=True
|
||||||
function buildFormState() {
|
function buildFormState() {
|
||||||
function extractOptions(id) {
|
function extractOptions(id) {
|
||||||
const select = document.getElementById(`${id}-list`);
|
const select = document.getElementById(`${id}-list`);
|
||||||
return Array.from(select.options).map(opt => ({ name: opt.textContent.trim(), id: parseInt(opt.value) || undefined }));
|
return Array.from(select.options).map(opt => ({
|
||||||
|
name: opt.textContent.trim(),
|
||||||
|
id: opt.value || undefined
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomOptions = Array.from(document.getElementById('room-list').options);
|
const roomOptions = Array.from(document.getElementById('room-list').options);
|
||||||
const rooms = roomOptions.map(opt => {
|
const rooms = roomOptions.map(opt => {
|
||||||
const data = opt.dataset;
|
const data = opt.dataset;
|
||||||
return {
|
return {
|
||||||
|
id: opt.value || undefined,
|
||||||
name: opt.textContent.trim(),
|
name: opt.textContent.trim(),
|
||||||
section_id: data.sectionId ? parseInt(data.sectionId) : null,
|
section_id: data.sectionId ?? null,
|
||||||
function_id: data.functionId ? parseInt(data.functionId) : null
|
function_id: data.functionId ?? null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -192,43 +210,47 @@ submit_button=True
|
||||||
const name = document.getElementById('roomName').value.trim();
|
const name = document.getElementById('roomName').value.trim();
|
||||||
const sectionVal = document.getElementById('roomSection').value;
|
const sectionVal = document.getElementById('roomSection').value;
|
||||||
const funcVal = document.getElementById('roomFunction').value;
|
const funcVal = document.getElementById('roomFunction').value;
|
||||||
|
let idRaw = document.getElementById('roomId').value;
|
||||||
const section = sectionVal !== "" ? parseInt(sectionVal) : null;
|
|
||||||
const func = funcVal !== "" ? parseInt(funcVal) : null;
|
|
||||||
|
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
alert('Please enter a room name.');
|
alert('Please enter a room name.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid duplicate visible names
|
|
||||||
const roomList = document.getElementById('room-list');
|
const roomList = document.getElementById('room-list');
|
||||||
const exists = Array.from(roomList.options).some(opt => opt.textContent.trim() === name);
|
let existingOption = Array.from(roomList.options).find(opt => opt.value === idRaw);
|
||||||
if (exists) {
|
|
||||||
alert(`Room "${name}" already exists.`);
|
// If it's a brand new ID, generate one (string-based!)
|
||||||
return;
|
if (!idRaw) {
|
||||||
|
idRaw = ComboBoxWidget.createTempId("room");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to select box visibly
|
if (!existingOption) {
|
||||||
const option = ComboBoxWidget.createOption(name);
|
existingOption = ComboBoxWidget.createOption(name, idRaw);
|
||||||
|
roomList.appendChild(existingOption);
|
||||||
if (section !== null) {
|
|
||||||
option.dataset.sectionId = section;
|
|
||||||
}
|
|
||||||
if (func !== null) {
|
|
||||||
option.dataset.functionId = func;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
roomList.appendChild(option);
|
existingOption.textContent = name;
|
||||||
|
existingOption.value = idRaw;
|
||||||
|
existingOption.dataset.sectionId = sectionVal;
|
||||||
|
existingOption.dataset.functionId = funcVal;
|
||||||
|
|
||||||
ComboBoxWidget.sortOptions(roomList);
|
ComboBoxWidget.sortOptions(roomList);
|
||||||
|
|
||||||
// Track in state object
|
// Update formState.rooms
|
||||||
formState.rooms.push({
|
const index = formState.rooms.findIndex(r => r.id === idRaw);
|
||||||
|
const payload = {
|
||||||
|
id: idRaw,
|
||||||
name,
|
name,
|
||||||
section_id: section,
|
section_id: sectionVal !== "" ? sectionVal : null,
|
||||||
function_id: func
|
function_id: funcVal !== "" ? funcVal : null
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
formState.rooms[index] = payload;
|
||||||
|
} else {
|
||||||
|
formState.rooms.push(payload);
|
||||||
|
}
|
||||||
|
|
||||||
bootstrap.Modal.getInstance(modal).hide();
|
bootstrap.Modal.getInstance(modal).hide();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue