Build Seth's changes C1 to C11 (Gitea issues 1, 8, 18, 23, 24, 25, 27, 31, 32, 33, 36)
- C1 (issue 1): over target follows VISUAL ticket hours, not the timer. - C2 (issue 24): a Q.C. reject always needs a reason; accept needs an NCR number after a reject without one. - C3 (issue 25): an incomplete unit ITP blocks filing. - C4 (issue 27): prints open the print dialog on the page, no new tab. - C5 (issue 31): filing is blocked while VISUAL shows a work order open; the feed sets vclosed on VISUAL packets. - C6 (issue 8): the data book leaves out the history pages. - C7 (issue 18): the supervisor approves each received short; accept and filing wait for it. - C8 (issue 23): per-department checklists, edited by the department supervisor or Admin, frozen at START. - C9 (issue 32): Q.C. and Clerical may run the stamp check. - C10 (issue 33): the PM screen and reports are for PS-ProjectMgmt and Admin only. - C11 (issue 36): the Stall screen lists the packets assigned to the welder, at the supervisor's stall. Migration 0011. Docs: PLAN (also issue 9 confirmed, issue 4 asked), POLICY, SECURITY, SCHEMA, FEED, README, CHANGELOG. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
"""Admin: departments and stations, people's profiles (one at a time and the CSV import), checklist templates.
|
||||
"""Admin: departments and stations, people's profiles (one at a time and the CSV import), the default checklists by
|
||||
kind (weld and plain; each department's own list is at /api/checklists, change C8).
|
||||
|
||||
Every route needs the admin role and a password session. Every change is audited (`admin.*`).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
@@ -171,9 +171,6 @@ def profile_import(
|
||||
|
||||
# ---------------------------------------------------------------- checklists
|
||||
|
||||
RX_BRACES = re.compile(r"\{[^{}]*\}")
|
||||
ALLOWED_PLACEHOLDERS = ("{drawing}", "{rev}", "{wps}")
|
||||
|
||||
|
||||
class ChecklistsPut(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
@@ -183,7 +180,8 @@ class ChecklistsPut(BaseModel):
|
||||
|
||||
|
||||
def check_templates(value: Any) -> tuple[dict[str, list[dict[str, str]]], list[str]]:
|
||||
"""The checklist templates cleaned, and one error line per problem."""
|
||||
"""The default checklists by kind (weld and plain) cleaned, and one error line per problem. A department with
|
||||
its own list (change C8, /api/checklists) does not read these."""
|
||||
errors: list[str] = []
|
||||
out: dict[str, list[dict[str, str]]] = {}
|
||||
if not isinstance(value, dict):
|
||||
@@ -192,41 +190,10 @@ def check_templates(value: Any) -> tuple[dict[str, list[dict[str, str]]], list[s
|
||||
if extra:
|
||||
errors.append(f"unknown list: {', '.join(extra)}")
|
||||
for name in ("weld", "plain"):
|
||||
items = value.get(name)
|
||||
if not isinstance(items, list) or not items:
|
||||
errors.append(f"{name}: at least one item")
|
||||
continue
|
||||
if len(items) > checklists.ITEMS_MAX:
|
||||
errors.append(f"{name}: at most {checklists.ITEMS_MAX} items")
|
||||
seen: set[str] = set()
|
||||
clean: list[dict[str, str]] = []
|
||||
for i, it in enumerate(items, start=1):
|
||||
where = f"{name} item {i}"
|
||||
if not isinstance(it, dict):
|
||||
errors.append(f"{where}: must be an object with group, key and text")
|
||||
continue
|
||||
group = " ".join(str(it.get("group") or "").split())
|
||||
key = str(it.get("key") or "").strip()
|
||||
text = " ".join(str(it.get("text") or "").split())
|
||||
if not checklists.RX_KEY.match(key):
|
||||
errors.append(f"{where}: the key is a lower-case letter, then letters, digits or _ (at most 30)")
|
||||
elif key in seen:
|
||||
errors.append(f"{where}: key {key} is used twice")
|
||||
seen.add(key)
|
||||
if not group:
|
||||
errors.append(f"{where}: the group is empty")
|
||||
if not text:
|
||||
errors.append(f"{where}: the text is empty")
|
||||
if len(group) > checklists.TEXT_MAX or len(text) > checklists.TEXT_MAX:
|
||||
errors.append(f"{where}: at most {checklists.TEXT_MAX} characters")
|
||||
bad = [m for m in RX_BRACES.findall(text) if m not in ALLOWED_PLACEHOLDERS]
|
||||
stray = RX_BRACES.sub("", text)
|
||||
if bad:
|
||||
errors.append(f"{where}: unknown placeholder {', '.join(bad)} (use {{drawing}}, {{rev}} or {{wps}})")
|
||||
elif "{" in stray or "}" in stray:
|
||||
errors.append(f"{where}: a brace without its pair")
|
||||
clean.append({"group": group, "key": key, "text": text})
|
||||
out[name] = clean
|
||||
clean, errs = checklists.check_list(name, value.get(name))
|
||||
errors += errs
|
||||
if clean is not None:
|
||||
out[name] = clean
|
||||
return out, errors
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user