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:
@@ -88,7 +88,8 @@ NOTE_AGING_STEPS = "How long packets have been sitting where they are right now.
|
||||
NOTE_AGING_OPEN = "Over 3 days in one step is flagged."
|
||||
NOTE_AGING_HISTORY = "From the step history recorded on every move."
|
||||
NOTE_HOURS_OVER = (
|
||||
"Station timer hours and VISUAL ticket hours against the work-order target hours. The timer is the clock of record."
|
||||
"VISUAL ticket hours against the work-order target hours decide over target. The station timer is shown beside"
|
||||
" them. A packet with no ticket hours is not over target."
|
||||
)
|
||||
|
||||
PROJECTS_HEAD = [
|
||||
@@ -108,7 +109,16 @@ PROJECTS_HEAD = [
|
||||
"Health",
|
||||
]
|
||||
ATTENTION_HEAD = ["Unit", "Description", "Due", "Left", "Packets", "% done", "Health"]
|
||||
HOURS_HEAD = ["Packets", "Timer h", "Ticket h", "Target h", "Variance h", "Timer / target", "Over target"]
|
||||
HOURS_HEAD = [
|
||||
"Packets",
|
||||
"Timer h",
|
||||
"Ticket h",
|
||||
"Target h",
|
||||
"Ticket variance h",
|
||||
"Ticket h / target",
|
||||
"Over target (tickets)",
|
||||
]
|
||||
OVER_HEAD = ["Job", "Station / worker", "Status", "Timer h", "Ticket h", "Target h", "Over by (tickets)"]
|
||||
JOBS_HEAD = [
|
||||
"Job",
|
||||
"Project",
|
||||
@@ -849,6 +859,12 @@ def _counts_in_hours(c: Mapping[str, Any], h: Mapping[str, float]) -> bool:
|
||||
return h["target_h"] > 0 and (h["timer_h"] > 0 or h["ticket_h"] > 0 or c["status"] in HOURS_STATUSES)
|
||||
|
||||
|
||||
def is_over(h: Mapping[str, float]) -> bool:
|
||||
"""Over target: the VISUAL ticket hours are over the work-order target (change C1). The timer never decides, and
|
||||
a packet with no ticket hours is not over."""
|
||||
return h["target_h"] > 0 and h["ticket_h"] > 0 and h["ticket_h"] > h["target_h"]
|
||||
|
||||
|
||||
def station_worker(c: Mapping[str, Any]) -> str:
|
||||
parts = [(c.get("station") or {}).get("name"), (c.get("welder") or {}).get("display_name")]
|
||||
return " · ".join(str(x) for x in parts if x) or DASH
|
||||
@@ -856,7 +872,7 @@ def station_worker(c: Mapping[str, Any]) -> str:
|
||||
|
||||
def hours_tables(rep: Report) -> list[dict[str, Any]]:
|
||||
"""The old rptHours, with the station timer and the VISUAL ticket hours side by side against the target. The
|
||||
timer decides "over target"."""
|
||||
ticket hours decide "over target" (change C1); the timer is shown as today."""
|
||||
hrs = rep.hours()
|
||||
zero = {"timer_h": 0.0, "ticket_h": 0.0, "target_h": 0.0}
|
||||
hp = [(c, hrs.get(int(c["id"]), zero)) for c in rep.cards]
|
||||
@@ -871,9 +887,9 @@ def hours_tables(rep: Report) -> list[dict[str, Any]]:
|
||||
g["ticket"] += h["ticket_h"]
|
||||
g["target"] += h["target_h"]
|
||||
g["n"] += 1
|
||||
if h["timer_h"] > h["target_h"]:
|
||||
if is_over(h):
|
||||
g["over"] += 1
|
||||
return sorted(m.items(), key=lambda kv: -(kv[1]["timer"] - kv[1]["target"]))
|
||||
return sorted(m.items(), key=lambda kv: -(kv[1]["ticket"] - kv[1]["target"]))
|
||||
|
||||
def rows_of(groups: list[tuple[str, dict[str, float]]]) -> list[dict[str, Any]]:
|
||||
return [
|
||||
@@ -884,11 +900,11 @@ def hours_tables(rep: Report) -> list[dict[str, Any]]:
|
||||
fmt_h(v["timer"]),
|
||||
fmt_h(v["ticket"]),
|
||||
fmt_h(v["target"]),
|
||||
signed_h(v["timer"] - v["target"]),
|
||||
f"{half_up(v['timer'] / v['target'] * 100)}%" if v["target"] else DASH,
|
||||
signed_h(v["ticket"] - v["target"]),
|
||||
f"{half_up(v['ticket'] / v['target'] * 100)}%" if v["target"] else DASH,
|
||||
int(v["over"]),
|
||||
],
|
||||
flag=v["timer"] > v["target"],
|
||||
flag=v["ticket"] > v["target"],
|
||||
)
|
||||
for k, v in groups
|
||||
]
|
||||
@@ -911,13 +927,13 @@ def hours_tables(rep: Report) -> list[dict[str, Any]]:
|
||||
)
|
||||
t3 = table("hours_project", "By project", ["Project", *HOURS_HEAD], rows_of(group(project_key)))
|
||||
over = sorted(
|
||||
((c, h) for c, h in hp if h["timer_h"] > h["target_h"]),
|
||||
key=lambda ch: -(ch[1]["timer_h"] - ch[1]["target_h"]),
|
||||
((c, h) for c, h in hp if is_over(h)),
|
||||
key=lambda ch: -(ch[1]["ticket_h"] - ch[1]["target_h"]),
|
||||
)
|
||||
t4 = table(
|
||||
"hours_over",
|
||||
"Packets over target",
|
||||
["Job", "Station / worker", "Status", "Timer h", "Ticket h", "Target h", "Over by"],
|
||||
OVER_HEAD,
|
||||
[
|
||||
row(
|
||||
[
|
||||
@@ -927,7 +943,7 @@ def hours_tables(rep: Report) -> list[dict[str, Any]]:
|
||||
fmt_h(h["timer_h"]),
|
||||
fmt_h(h["ticket_h"]),
|
||||
fmt_h(h["target_h"]),
|
||||
"+" + fmt_h(h["timer_h"] - h["target_h"]) + " h",
|
||||
"+" + fmt_h(h["ticket_h"] - h["target_h"]) + " h",
|
||||
],
|
||||
pid=c["id"],
|
||||
flag=True,
|
||||
@@ -944,7 +960,7 @@ def over_target_count(rep: Report) -> int:
|
||||
n = 0
|
||||
for c in rep.cards:
|
||||
h = hrs.get(int(c["id"]))
|
||||
if h and h["target_h"] > 0 and h["timer_h"] > h["target_h"]:
|
||||
if h and is_over(h):
|
||||
n += 1
|
||||
return n
|
||||
|
||||
@@ -1177,7 +1193,7 @@ def kpis(rep: Report) -> list[dict[str, Any]]:
|
||||
{
|
||||
"key": "over",
|
||||
"n": over_target_count(rep),
|
||||
"label": "Packets over target hours",
|
||||
"label": "Packets over target hours (tickets)",
|
||||
"tab": "hours",
|
||||
"alert": True,
|
||||
},
|
||||
@@ -1230,7 +1246,7 @@ def summary(rep: Report, k: list[dict[str, Any]] | None = None, pipe: list[dict[
|
||||
)
|
||||
over = int(n["over"])
|
||||
if over:
|
||||
out.append(f"{over} packet{' is' if over == 1 else 's are'} over target hours.")
|
||||
out.append(f"{over} packet{' is' if over == 1 else 's are'} over target hours on the VISUAL tickets.")
|
||||
return out
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user