inventory/inventory/routes/entry.py
2025-10-02 15:42:00 -05:00

241 lines
13 KiB
Python

from datetime import datetime
from flask import Blueprint, render_template, abort, request, jsonify, current_app
from sqlalchemy.inspection import inspect
from typing import Any, Dict, List, Tuple, Callable, Optional
import crudkit
from crudkit.ui.fragments import render_form
from crudkit.core import normalize_payload
bp_entry = Blueprint("entry", __name__)
def _apply_worklog_updates(worklog, updates, delete_ids):
note_cls = type(worklog).updates.property.mapper.class_
note_svc = crudkit.crud.get_service(note_cls)
sess = note_svc.session
existing = {u.id: u for u in worklog.updates if not getattr(u, "is_deleted", False)}
for item in updates:
uid = item.get("id")
content = (item.get("content") or "").strip()
if not content and not uid:
continue
if uid:
# per-note version log preserved, but commit deferred.
note_svc.update(uid, {"content": content}, actor="bulk_child_update", commit=False)
else:
note_svc.create({"work_log_id": worklog.id, "content": content}, actor="bulk_child_create", commit=False)
for uid in delete_ids:
if uid in existing:
note_svc.delete(uid, actor="bulk_child_delete", commit=False)
def init_entry_routes(app):
@bp_entry.get("/entry/<model>/<int:id>")
def entry(model: str, id: int):
cls = crudkit.crud.get_model(model)
if cls is None or model not in ["inventory", "worklog", "user"]:
abort(404)
fields = {}
fields_spec = []
layout = []
if model == "inventory":
fields["fields"] = ["label", "name", "serial", "barcode", "brand", "model",
"device_type", "owner", "location", "condition", "image", "notes"]
fields_spec = [
{"name": "label", "type": "display", "label": "", "row": "label",
"attrs": {"class": "display-6 mb-3"}, "wrap": {"class": "col"}},
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
"wrap": {"class": "col-auto text-end me-2"}, "attrs": {"data-model": model}},
{"name": "name", "row": "names", "label": "Name", "wrap": {"class": "col-3"},
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
{"name": "serial", "row": "names", "label": "Serial #", "wrap": {"class": "col"},
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
{"name": "barcode", "row": "names", "label": "Barcode #", "wrap": {"class": "col"},
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
{"name": "brand", "label_spec": "{name}", "row": "device", "wrap": {"class": "col"},
"attrs": {"class": "form-control"}, "label": "Brand", "label_attrs": {"class": "form-label"}},
{"name": "model", "row": "device", "wrap": {"class": "col"},
"attrs": {"class": "form-control"}, "label": "Model #", "label_attrs": {"class": "form-label"}},
{"name": "device_type", "label_spec": "{description}", "row": "device", "wrap": {"class": "col"},
"attrs": {"class": "form-control"}, "label": "Device Type", "label_attrs": {"class": "form-label"}},
{"name": "owner", "row": "status", "label": "Contact", "wrap": {"class": "col"},
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"},
"label_spec": "{label}"},
{"name": "location", "row": "status", "label": "Location", "wrap": {"class": "col"},
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"},
"label_spec": "{name} - {room_function.description}"},
{"name": "condition", "row": "status", "label": "Condition", "wrap": {"class": "col"},
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
{"name": "image", "label": "", "row": "image", "type": "template", "label_spec": "{filename}",
"template": "image_display.html", "attrs": {"class": "img-fluid img-thumbnail h-auto"},
"wrap": {"class": "h-100 w-100"}},
{"name": "notes", "type": "template", "label": "Notes", "row": "notes", "wrap": {"class": "col"},
"template": "inventory_note.html"},
]
layout = [
{"name": "label", "order": 5, "attrs": {"class": "row align-items-center"}},
{"name": "kitchen_sink", "order": 6, "attrs": {"class": "row"}},
{"name": "everything", "order": 10, "attrs": {"class": "col"}, "parent": "kitchen_sink"},
{"name": "names", "order": 20, "attrs": {"class": "row"}, "parent": "everything"},
{"name": "device", "order": 30, "attrs": {"class": "row mt-2"}, "parent": "everything"},
{"name": "status", "order": 40, "attrs": {"class": "row mt-2"}, "parent": "everything"},
{"name": "notes", "order": 45, "attrs": {"class": "row mt-2"}, "parent": "everything"},
{"name": "image", "order": 50, "attrs": {"class": "col-4"}, "parent": "kitchen_sink"},
]
elif model.lower() == 'user':
fields["fields"] = ["label", "first_name", "last_name", "title", "active", "staff", "location", "supervisor"]
fields_spec = [
{"name": "label", "row": "label", "label": "", "type": "display",
"attrs": {"class": "display-6 mb-3"}, "wrap": {"class": "col"}},
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
"wrap": {"class": "col-auto text-end me-2"}, "attrs": {"data-model": model}},
{"name": "last_name", "label": "Last Name", "label_attrs": {"class": "form-label"},
"attrs": {"placeholder": "Doe", "class": "form-control"}, "row": "name", "wrap": {"class": "col-3"}},
{"name": "first_name", "label": "First Name", "label_attrs": {"class": "form-label"},
"attrs": {"placeholder": "John", "class": "form-control"}, "row": "name", "wrap": {"class": "col-3"}},
{"name": "title", "label": "Title", "label_attrs": {"class": "form-label"},
"attrs": {"placeholder": "President of the Universe", "class": "form-control"},
"row": "name", "wrap": {"class": "col-3"}},
{"name": "supervisor", "label": "Supervisor", "label_attrs": {"class": "form-label"},
"label_spec": "{label}", "row": "details", "wrap": {"class": "col-3"},
"attrs": {"class": "form-control"}},
{"name": "location", "label": "Room", "label_attrs": {"class": "form-label"},
"label_spec": "{name} - {room_function.description}",
"row": "details", "wrap": {"class": "col-3"}, "attrs": {"class": "form-control"}},
{"name": "active", "label": "Active", "label_attrs": {"class": "form-check-label"},
"row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}},
{"name": "staff", "label": "Staff Member", "label_attrs": {"class": "form-check-label"},
"row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}},
]
layout = [
{"name": "label", "order": 0, "attrs": {"class": "row align-items-center"}},
{"name": "name", "order": 10, "attrs": {"class": "row"}},
{"name": "details", "order": 20, "attrs": {"class": "row mt-2"}},
{"name": "checkboxes", "order": 30, "parent": "details",
"attrs": {"class": "col d-flex flex-column justify-content-end"}},
]
elif model == "worklog":
fields["fields"] = ["id", "contact", "work_item", "start_time", "end_time", "complete"]
fields_spec = [
{"name": "id", "label": "", "type": "display", "label_spec": "Work Item #{id}",
"attrs": {"class": "display-6 mb-3"}, "row": "label", "wrap": {"class": "col"}},
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
"wrap": {"class": "col-auto text-end me-2"}, "attrs": {"data-model": model}},
{"name": "contact", "row": "ownership", "wrap": {"class": "col"}, "label": "Contact",
"label_spec": "{label}", "attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
{"name": "work_item", "row": "ownership", "wrap": {"class": "col"}, "label": "Work Item",
"label_spec": "{label}", "attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
{"name": "start_time", "type": "datetime", "attrs": {"class": "form-control"}, "row": "timestamps",
"wrap": {"class": "col"}, "label_attrs": {"class": "form-label"}, "label": "Start"},
{"name": "end_time", "type": "datetime", "attrs": {"class": "form-control"}, "row": "timestamps",
"wrap": {"class": "col"}, "label_attrs": {"class": "form-label"}, "label": "End"},
{"name": "complete", "label": "Complete", "label_attrs": {"class": "form-check-label"},
"attrs": {"class": "form-check-input"}, "row": "timestamps", "wrap": {"class": "col form-check"}},
{"name": "updates", "label": "Updates", "row": "updates", "label_attrs": {"class": "form-label"},
"type": "template", "template": "update_list.html"},
]
layout = [
{"name": "label", "order": 0, "attrs": {"class": "row align-items-center"}},
{"name": "ownership", "order": 10, "attrs": {"class": "row mb-2"}},
{"name": "timestamps", "order": 20, "attrs": {"class": "row d-flex align-items-center"}},
{"name": "updates", "order": 30, "attrs": {"class": "row"}},
]
svc = crudkit.crud.get_service(cls)
obj = svc.get(id, fields)
if obj is None:
abort(404)
# Use the scoped_session proxy so teardown .remove() cleans it up
ScopedSession = current_app.extensions["crudkit"]["Session"]
if model == "worklog":
updates_cls = type(obj).updates.property.mapper.class_
updates_q = (ScopedSession.query(updates_cls)
.filter(updates_cls.work_log_id == obj.id,
updates_cls.is_deleted == False)
.order_by(updates_cls.timestamp.asc()))
all_updates = updates_q.all()
for f in fields_spec:
if f.get("name") == "updates" and f.get("type") == "template":
ctx = dict(f.get("template_ctx") or {})
ctx["updates"] = all_updates
f["template_ctx"] = ctx
break
form = render_form(
cls,
obj.as_dict(),
session=ScopedSession, # ← fixed: pass scoped proxy, not svc.session
instance=obj,
fields_spec=fields_spec,
layout=layout,
submit_attrs={"class": "d-none", "disabled": True},
)
return render_template("entry.html", form=form)
@bp_entry.post("/entry/<model>/<int:id>")
def update_entry(model, id):
try:
if model not in ["inventory", "user", "worklog"]:
raise TypeError("Invalid model.")
cls = crudkit.crud.get_model(model)
payload = normalize_payload(request.get_json(), cls)
updates = payload.pop("updates", None) or []
delete_ids = payload.pop("delete_update_ids", None) or []
params = {}
if model == "inventory":
pass
elif model == "user":
params = {
"fields": [
"last_name",
"first_name",
"title",
"supervisor_id",
"location_id",
"active",
"staff",
]
}
elif model == "worklog":
params = {
"fields": [
"contact_id",
"work_item_id",
"start_time",
"end_time",
"complete",
"updates",
]
}
else:
raise TypeError("Invalid model.")
service = crudkit.crud.get_service(cls)
sess = service.session
obj = service.update(id, data=payload, actor="update_entry", commit=False)
if model == "worklog" and (updates or delete_ids):
_apply_worklog_updates(obj, updates, delete_ids)
sess.commit()
return {"status": "success", "payload": payload}
except Exception as e:
print(e)
return {"status": "failure", "error": str(e)}
app.register_blueprint(bp_entry)