diff --git a/routes.py b/routes.py index 2c3be9f..9d65ff5 100644 --- a/routes.py +++ b/routes.py @@ -408,32 +408,89 @@ def settings(): db.session.flush() mapper[clean] = new_obj.id + def sync_named_entities( + submitted_items: list, + existing_items: set, + model, + attr: str, + label: str + ): + submitted_names = { + str(item.get("name", "")).strip() + for item in submitted_items + if isinstance(item, dict) and str(item.get("name", "")).strip() + } + + print(f"🔍 {label} in DB: {existing_items}") + print(f"🆕 {label} submitted: {submitted_names}") + print(f"➖ {label} to delete: {existing_items - submitted_names}") + + for name in submitted_names - existing_items: + db.session.add(model(**{attr: name})) + for name in existing_items - submitted_names: + print(f"🗑️ Deleting {label}: {name}") + db.session.execute(delete(model).where(getattr(model, attr) == name)) + + def process_entities(entity_list, model, attr, key_name="name"): + """Upserts and deletes based on entity name field.""" + existing = {getattr(e, attr): e.id for e in db.session.query(model).all()} + submitted = { + str(e.get(key_name, "")).strip() + for e in entity_list if isinstance(e, dict) and e.get(key_name) + } + + print(f"🔍 Existing: {existing}") + print(f"🆕 Submitted: {submitted}") + print(f"➕ To add: {submitted - set(existing)}") + print(f"➖ To delete: {set(existing) - submitted}") + + for name in submitted - set(existing): + db.session.add(model(**{attr: name})) + + for name in set(existing) - submitted: + db.session.execute(delete(model).where(getattr(model, attr) == name)) + + return submitted # Might be useful for mapping fallback + + def handle_rooms(rooms, section_map, function_map, section_fallbacks, function_fallbacks): + existing_rooms = {r.name: r.id for r in db.session.query(Room).all()} + submitted_rooms = { + str(r.get("name", "")).strip(): r for r in rooms if r.get("name") + } + + print(f"🔍 Rooms in DB: {list(existing_rooms.keys())}") + print(f"🆕 Rooms submitted: {list(submitted_rooms.keys())}") + print(f"➖ To delete: {set(existing_rooms) - set(submitted_rooms)}") + + for name, data in submitted_rooms.items(): + if name not in existing_rooms: + section_id = resolve_id(data.get("section_id"), section_fallbacks, section_map, "section") if data.get("section_id") is not None else None + function_id = resolve_id(data.get("function_id"), function_fallbacks, function_map, "function") if data.get("function_id") is not None else None + db.session.add(Room(name=name, area_id=section_id, function_id=function_id)) + + for name in set(existing_rooms) - set(submitted_rooms): + db.session.execute(delete(Room).where(Room.name == name)) + def resolve_id(raw_id, fallback_list, id_map, label): try: - resolved_id = int(raw_id) - except (TypeError, ValueError): - raise ValueError(f"{label.title()} ID was not a valid integer: {raw_id}") + resolved = int(raw_id) + if resolved >= 0: + if resolved in id_map.values(): + return resolved + raise ValueError(f"{label.title()} ID {resolved} not found.") + except Exception: + pass # Continue to fallback logic - if resolved_id >= 0: - # Try to validate this ID by checking if it appears in the map values - if resolved_id in id_map.values(): - return resolved_id - else: - raise ValueError(f"{label.title()} ID {resolved_id} not found in known {label}s.") - - # It's a negative ID = created on frontend. Resolve from fallback list - index = abs(resolved_id + 1) - entry = None # Ensure entry is always defined + index = abs(resolved + 1) try: entry = fallback_list[index] - key = entry["name"] if isinstance(entry, dict) else str(entry).strip() - except (IndexError, KeyError, TypeError) as e: - raise ValueError(f"Invalid {label} index or format at index {index}: {entry if entry else 'UNKNOWN'}") from e - - final_id = id_map.get(key) - if not final_id: - raise ValueError(f"Unresolved {label}: {key}") - return final_id + key = entry.get("name") if isinstance(entry, dict) else str(entry).strip() + final_id = id_map.get(key) + if final_id is None: + raise ValueError(f"Unresolved {label}: {key}") + return final_id + except Exception as e: + raise ValueError(f"Failed resolving {label} ID {raw_id}: {e}") from e if request.method == 'POST': print("⚠️⚠️⚠️ POST /settings reached! ⚠️⚠️⚠️") @@ -449,93 +506,22 @@ def settings(): try: with db.session.begin(): - # === BRANDS === - existing_brands = {b.name for b in db.session.query(Brand).all()} - submitted_brands = { - str(b.get("name", "")).strip() - for b in state.get("brands", []) - if isinstance(b, dict) and str(b.get("name", "")).strip() - } - print(f"🔍 Brands in DB: {existing_brands}") - print(f"🆕 Brands submitted: {submitted_brands}") - print(f"➖ Brands to delete: {existing_brands - submitted_brands}") - for name in submitted_brands - existing_brands: - db.session.add(Brand(name=name)) - for name in existing_brands - submitted_brands: - print(f"🗑️ Deleting brand: {name}") - db.session.execute(delete(Brand).where(Brand.name == name)) + brand_names = process_entities(state.get("brands", []), Brand, "name") + type_names = process_entities(state.get("types", []), Item, "description", "name") + section_names = process_entities(state.get("sections", []), Area, "name") + func_names = process_entities(state.get("functions", []), RoomFunction, "description") - # === TYPES === - existing_types = {i.description for i in db.session.query(Item).all()} - submitted_types = { - str(t.get("name") or t.get("description") or "").strip() - for t in state.get("types", []) - if isinstance(t, dict) and str(t.get("name") or t.get("description") or "").strip() - } - print(f"🔍 Types in DB: {existing_types}") - print(f"🆕 Types submitted: {submitted_types}") - print(f"➖ Types to delete: {existing_types - submitted_types}") - for desc in submitted_types - existing_types: - db.session.add(Item(description=desc)) - for desc in existing_types - submitted_types: - print(f"🗑️ Deleting type: {desc}") - db.session.execute(delete(Item).where(Item.description == desc)) - - # === SECTIONS === - existing_sections = {a.name for a in db.session.query(Area).all()} - submitted_sections = { - str(s.get("name", "")).strip() - for s in state.get("sections", []) - if isinstance(s, dict) and str(s.get("name", "")).strip() - } - print(f"🔍 Sections in DB: {existing_sections}") - print(f"🆕 Sections submitted: {submitted_sections}") - print(f"➖ Sections to delete: {existing_sections - submitted_sections}") - for name in submitted_sections - existing_sections: - db.session.add(Area(name=name)) - for name in existing_sections - submitted_sections: - print(f"🗑️ Deleting section: {name}") - db.session.execute(delete(Area).where(Area.name == name)) - - # === FUNCTIONS === - existing_funcs = {f.description for f in db.session.query(RoomFunction).all()} - submitted_funcs = { - str(f.get("name", "")).strip() - for f in state.get("functions", []) - if isinstance(f, dict) and str(f.get("name", "")).strip() - } - print(f"🔍 Functions in DB: {existing_funcs}") - print(f"🆕 Functions submitted: {submitted_funcs}") - print(f"➖ Functions to delete: {existing_funcs - submitted_funcs}") - for desc in submitted_funcs - existing_funcs: - db.session.add(RoomFunction(description=desc)) - for desc in existing_funcs - submitted_funcs: - print(f"🗑️ Deleting function: {desc}") - db.session.execute(delete(RoomFunction).where(RoomFunction.description == desc)) - - # === REPOPULATE MAPS === + # Refresh maps after inserts section_map = {a.name: a.id for a in db.session.query(Area).all()} function_map = {f.description: f.id for f in db.session.query(RoomFunction).all()} - # === ROOMS === - existing_rooms = {r.name: r.id for r in db.session.query(Room).all()} - submitted_rooms = { - str(r.get("name", "")).strip(): r - for r in state.get("rooms", []) - if str(r.get("name", "")).strip() - } - print(f"🔍 Rooms in DB: {list(existing_rooms.keys())}") - print(f"🆕 Rooms submitted: {list(submitted_rooms.keys())}") - print(f"➖ Rooms to delete: {set(existing_rooms.keys()) - set(submitted_rooms.keys())}") - for name, room_data in submitted_rooms.items(): - if name not in existing_rooms: - section_id = resolve_id(room_data.get("section_id"), state["sections"], section_map, "section") if room_data.get("section_id") is not None else None - function_id = resolve_id(room_data.get("function_id"), state["functions"], function_map, "function") if room_data.get("function_id") is not None else None - db.session.add(Room(name=name, area_id=section_id, function_id=function_id)) - - for name in set(existing_rooms.keys()) - set(submitted_rooms.keys()): - print(f"🗑️ Deleting room: {name}") - db.session.execute(delete(Room).where(Room.name == name)) + handle_rooms( + rooms=state.get("rooms", []), + section_map=section_map, + function_map=function_map, + section_fallbacks=state.get("sections", []), + function_fallbacks=state.get("functions", []) + ) print("✅ COMMIT executed.") flash("Changes saved.", "success") @@ -562,4 +548,3 @@ def settings(): functions=[f.serialize() for f in functions], rooms=[r.serialize() for r in rooms], ) -