363 lines
19 KiB
Python
363 lines
19 KiB
Python
# ruff: noqa: E501
|
|
"""Create one Gitea issue per open decision in docs/PLAN.md.
|
|
|
|
Usage (token from a file, never from the command line or chat):
|
|
python scripts/gitea_issues.py --token-file %USERPROFILE%\\.config\\gitea\\token [--dry-run]
|
|
|
|
Idempotent: an issue whose title already exists in the repo is skipped. Labels are created
|
|
when missing. The token needs the issue read and write scope on the repository.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
REPO = "spencerm/PacketStation"
|
|
PLAN = "https://gt.pescoinc.biz/spencerm/PacketStation/src/branch/main/docs/PLAN.md"
|
|
|
|
LABELS = {
|
|
"decision: Seth": "e11d48",
|
|
"decision: Q.C.": "7c3aed",
|
|
"decision: Clerical": "0ea5e9",
|
|
"decision: IT": "f59e0b",
|
|
"phase 3": "64748b",
|
|
"phase 4": "64748b",
|
|
"phase 5": "64748b",
|
|
"phase 6": "64748b",
|
|
"phase 7": "64748b",
|
|
"phase 8": "64748b",
|
|
"phase 9": "64748b",
|
|
"production": "16a34a",
|
|
}
|
|
|
|
# (plan number or tag, title, labels, body)
|
|
ISSUES: list[tuple[str, str, list[str], str]] = [
|
|
(
|
|
"Q1",
|
|
"Station timer: keep, cross-check against VISUAL labor tickets, or drop",
|
|
["decision: Seth", "phase 5"],
|
|
"The old build has its own clock per station and an Hours vs target report. VISUAL already has labor tickets.\n\n"
|
|
"**Built as the default:** the station timer stays the clock of record and the VISUAL ticket hours are shown beside it in every hours table. "
|
|
'"Over target" means timer hours over the work-order target; ticket hours never decide the flag (plan question 38).\n\n'
|
|
"**To answer:** keep as built, keep the timer but flag on ticket hours, or drop the timer and report from tickets only.",
|
|
),
|
|
(
|
|
"Q4a",
|
|
"Should Q.C. accept and filing require the domain password even when a PIN is set?",
|
|
["decision: Seth", "decision: Q.C.", "phase 6"],
|
|
"Every sign-off (submit, send to Q.C., accept, reject, route, file, ITP block signatures, hand uploads, rev-mismatch approvals) takes a PIN or the domain password as the electronic signature.\n\n"
|
|
"**Built as the default:** the PIN is enough everywhere, including Q.C. accept and filing.\n\n"
|
|
"**To answer:** keep PIN for all, or require the domain password for Q.C. accept and for filing.",
|
|
),
|
|
(
|
|
"Q4b",
|
|
"Tie registered shop devices to a device certificate later?",
|
|
["decision: IT"],
|
|
"PIN sign-in works only on devices an Admin or shop Supervisor registered; the device gets a long-lived cookie.\n\n"
|
|
"**Built as the default:** device cookie.\n\n"
|
|
"**To answer:** whether IT wants an auto-enrolled device certificate checked by the proxy on the domain-joined tablets as a later upgrade, and when.",
|
|
),
|
|
(
|
|
"Q5",
|
|
"Which customer order a packet shows when a project has several open orders",
|
|
["decision: Seth", "phase 3"],
|
|
"VISUAL often has several open customer orders on one project. The packet's customer, PO and dates come from one of them.\n\n"
|
|
"**Built as the default** (one function, `feed/orders.py`): a Clerical pin, else the base's own linked order, else the project order whose top job is the unit, else the project's first open order, else its newest. "
|
|
"The source is stored and shown on the project page, which lists every open order; Clerical can pin another with a reason.\n\n"
|
|
"**To answer:** keep, or give a better rule.",
|
|
),
|
|
(
|
|
"Q6",
|
|
"VISUAL_ENCRYPT=yes once vsql has a trusted certificate",
|
|
["decision: IT", "production"],
|
|
"The VisualKit connection runs with Encrypt=no because vsql has no certificate the container trusts.\n\n"
|
|
"**To answer:** whether IT will issue vsql a certificate from PESCO-PSYLOCKE-CA (now SHA-256) so the production stack can run with VISUAL_ENCRYPT=yes, and when.",
|
|
),
|
|
(
|
|
"Q7",
|
|
"Policy tightenings against the old build (ten rules to keep or veto)",
|
|
["decision: Seth"],
|
|
'The rebuild follows the old screens where they were stricter than the old rules. Each can be vetoed. Table: docs/POLICY.md, section "Tightenings against the old build".\n\n'
|
|
"- [ ] Only the holder of a packet edits shop data (welds, BOM, heat tags, checklist, weld record, timer); a lead of another department cannot.\n"
|
|
"- [ ] Only the assigned welder starts or submits a packet; leads and supervisors still can.\n"
|
|
"- [ ] Rev-mismatch approval and moving back from VISUAL review need a Clerical Supervisor (no Clerical lead group in AD).\n"
|
|
"- [ ] Packets routed to Shipping can be closed and filed; only the Data Book route needs the VISUAL close first.\n"
|
|
"- [ ] The data book date is Clerical or Q.C. only.\n"
|
|
"- [ ] A change-order verification cannot be withdrawn once signed.\n"
|
|
"- [ ] The warehouse cannot release until every BOM line not n/a is pulled or has a short on record.\n"
|
|
"- [ ] A hand-made draft needs a drawing before it leaves Clerical.\n"
|
|
"- [ ] Attaching a drawing by hand needs a signature.\n"
|
|
"- [ ] Admin bypasses custody to fix data but follows the workflow order.\n\n"
|
|
"Tick the ones to keep; comment on any to change.",
|
|
),
|
|
(
|
|
"Q9a",
|
|
"Retention period for packets, data books and audit (SECURITY.md D6)",
|
|
["decision: Seth", "decision: Q.C."],
|
|
"Nothing is deleted today. A retention period lets a later phase close D6 and M5.\n\n**To answer:** how long filed packets, data books, MTRs and the audit log must be kept, and whether anything may ever be purged.",
|
|
),
|
|
(
|
|
"Q9b",
|
|
"Customer-facing data book copy (SECURITY.md D7)",
|
|
["decision: Seth", "decision: Q.C."],
|
|
"The data book carries internal history and the controlled-copy stamp.\n\n**To answer:** whether a customer copy with some sections left out is wanted, and which sections.",
|
|
),
|
|
(
|
|
"Q10",
|
|
"Same-rev drawing re-release on an in-shop packet: change order or silent swap",
|
|
["decision: Seth", "phase 3"],
|
|
"M-Files sometimes re-releases a drawing at the same rev with a new file.\n\n**Built as the default:** a drawing-origin change order, never a silent swap.\n\n**To answer:** keep, or swap silently with an audit entry.",
|
|
),
|
|
(
|
|
"Q11",
|
|
"Cancelled (X) work orders in VISUAL: flag only, or remove drafts",
|
|
["decision: Seth", "phase 3"],
|
|
"**Built as the default:** a cancelled work order is flagged on the packet like a gone one; drafts are not removed.\n\n**To answer:** keep, or retire untouched drafts whose work orders were cancelled.",
|
|
),
|
|
(
|
|
"Q12",
|
|
"Partspecs, title or folded-operation target changes on an in-shop packet",
|
|
["decision: Seth", "phase 3"],
|
|
"**Built as the default:** no change order for these, as the old build; the new values are stored and shown.\n\n**To answer:** keep, or open a change order for some of them.",
|
|
),
|
|
(
|
|
"Q13",
|
|
"Work-order-gone confirmation after 2 runs and the 10 percent suspect-batch guard",
|
|
["decision: Seth", "phase 3"],
|
|
"A work order missing from VISUAL is flagged after two consecutive feed runs. A batch that lost more than 10 percent (and more than 5) of tracked work orders is applied without the gone step and marked degraded.\n\n**To answer:** keep the two numbers or change them.",
|
|
),
|
|
(
|
|
"Q14",
|
|
"Pause scope on the Feed settings tab",
|
|
["decision: Seth", "phase 3"],
|
|
"**Built as the default:** pause stops the feed, the drawing job and auto-release together; run now still works.\n\n**To answer:** keep, or pause only the feed.",
|
|
),
|
|
(
|
|
"Q17",
|
|
"Who may set the data book date (was anyone, even on a filed packet)",
|
|
["decision: Seth", "decision: Clerical"],
|
|
"**Built as the default:** Clerical or Q.C. only; still allowed on a filed packet.\n\n**To answer:** keep or widen.",
|
|
),
|
|
(
|
|
"Q18",
|
|
"Undoing signatures: change-order verifications and ITP line signatures",
|
|
["decision: Seth", "decision: Q.C."],
|
|
"**Built as the default:** change-order verifications cannot be withdrawn; an ITP line signature can be undone by the same inspector or an Admin before the block is signed; a block signature by the signer or an Admin while the ITP is open.\n\n**To answer:** keep or change.",
|
|
),
|
|
(
|
|
"Q19",
|
|
"Warehouse release gate enforced on the server",
|
|
["decision: Seth", "phase 4"],
|
|
"Two rules: every line not N/A is pulled or on an open short, and every pulled line that issued pieces has a heat unless N/A or a sub-assembly. The old build checked both on the screen only.\n\n**Built as the default:** both on the server, so a release cannot bypass them.\n\n**To answer:** keep or relax.",
|
|
),
|
|
(
|
|
"Q20",
|
|
"Hold and short reasons as closed lists",
|
|
["decision: Seth", "phase 4"],
|
|
"**Built as the default:** closed sets (hold: Waiting on material, Waiting on purchasing / PO, Question for engineering or clerical, Drawing / BOM problem, Other with a note; short: Not in stock, On order, Back-ordered, Damaged / rejected, Wrong part received, Needs cutting / prep, Other).\n\n**To answer:** keep, add reasons, or allow free text.",
|
|
),
|
|
(
|
|
"Q21",
|
|
"Who may receive a short on a packet the warehouse no longer holds",
|
|
["decision: Seth", "phase 4"],
|
|
"**Built as the default:** Warehouse keeps the right at any status but filed; the packet's holder (department lead or supervisor, Q.C., Clerical) can receive too.\n\n**To answer:** keep or narrow.",
|
|
),
|
|
(
|
|
"Q22",
|
|
"Upload kinds, size limits and virus scanning",
|
|
["decision: Seth", "decision: IT", "phase 4"],
|
|
"**Built as the default:** MTR is PDF, PNG or JPG up to 25 MB; hand BOM is XLSX or CSV up to 5 MB; ClamAV is optional and off unless CLAMAV_HOST is set, and when set an unreachable scanner refuses uploads.\n\n**To answer:** keep the limits; whether IT wants ClamAV on in production.",
|
|
),
|
|
(
|
|
"Q23",
|
|
"Assets volume quota alert threshold",
|
|
["decision: IT", "phase 4"],
|
|
"**Built as the default:** a notification event at 85 percent of the assets volume.\n\n**To answer:** keep, or set a different threshold and who should receive it.",
|
|
),
|
|
(
|
|
"Q24",
|
|
"Carried marks on routing: computed flag, not copied rows",
|
|
["decision: Seth", "phase 5"],
|
|
"When a packet is routed to another department, welds, heat tags and the heat table stay on the packet and show as carried.\n\n**Built as the default:** computed, nothing copied.\n\n**To answer:** keep.",
|
|
),
|
|
(
|
|
"Q25",
|
|
'The old "copy marks to the new sheet" button on a change order',
|
|
["decision: Seth", "phase 5"],
|
|
"On a page replacement only the heat table placement is copied to the new page; welds and heat tags stay on the VOID page.\n\n**Built as the default:** the manual copy button is not ported.\n\n**To answer:** whether the shop wants it back.",
|
|
),
|
|
(
|
|
"Q26",
|
|
"Checklist templates: who edits them",
|
|
["decision: Seth", "phase 9"],
|
|
"The per-department checklists (weld and non-weld) live in settings with the old lists as defaults.\n\n**Built as the default:** Admin editing arrives in phase 9.\n\n**To answer:** whether Clerical or Q.C. should edit them instead of, or as well as, Admin.",
|
|
),
|
|
(
|
|
"Q27",
|
|
"Q.C. reject: minimum reason or a well-formed NCR number",
|
|
["decision: Q.C.", "phase 6"],
|
|
"**Built as the default:** a reason of at least 8 characters, or an NCR number NCR-YYYY-NNNN, or both; the NCR is linked to pesco-ncr. Validation against pesco-ncr comes later.\n\n**To answer:** keep.",
|
|
),
|
|
(
|
|
"Q28",
|
|
"A filed packet does not lock the unit's ITP",
|
|
["decision: Q.C.", "phase 6"],
|
|
"**Built as the default:** the ITP belongs to the unit, so ITP writes never look at a packet's status.\n\n**To answer:** keep.",
|
|
),
|
|
(
|
|
"Q30",
|
|
"Removing an ITP block signature: signer or Admin only, while the ITP is open",
|
|
["decision: Q.C.", "phase 6"],
|
|
"The old build let any Q.C. editor undo it.\n\n**Built as the default:** the signer or an Admin, while the ITP is open; QN, remarks and customer fields stay.\n\n**To answer:** keep.",
|
|
),
|
|
(
|
|
"Q32",
|
|
"Prints open as a stamped PDF in a new tab",
|
|
["decision: Seth", "decision: Clerical", "phase 7"],
|
|
"The old build pushed page images into the browser print dialog.\n\n**Built as the default:** every print opens as a stamped PDF in a new tab and is printed from the PDF viewer.\n\n**To answer:** keep.",
|
|
),
|
|
(
|
|
"Q33",
|
|
"The data book is built on the server when the packet is filed",
|
|
["decision: Seth", "decision: Clerical", "phase 7"],
|
|
'**Built as the default:** built inside the filing transaction and stored on the seal; a data book that cannot be built fails the filing. Before filing it can be viewed from status complete on, stamped "not yet filed".\n\n**To answer:** keep.',
|
|
),
|
|
(
|
|
"Q34",
|
|
"Who may read the PDFs and the traceability CSV",
|
|
["decision: Seth", "phase 7"],
|
|
"**Built as the default:** every signed-in role that can open the packet, PM included; every read is logged in the append-only print log and the audit.\n\n**To answer:** keep or restrict.",
|
|
),
|
|
(
|
|
"Q35",
|
|
'The old "Original packet PDF" print kind is gone',
|
|
["decision: Clerical", "phase 7"],
|
|
'The rebuild has no VISUAL packet PDF; drawing pages come from M-Files or a hand upload, and "Drawing (no markup)" prints them.\n\n**To answer:** confirm nothing is missed.',
|
|
),
|
|
(
|
|
"Q36",
|
|
"Filing while VISUAL still shows a work order open: warn, not block",
|
|
["decision: Seth", "decision: Clerical", "phase 7"],
|
|
"**Built as the default:** a second confirmation and a warning on both routes; the open work orders are kept on the seal and in the log.\n\n**To answer:** keep, or block until VISUAL shows closed.",
|
|
),
|
|
(
|
|
"Q37",
|
|
"Controlled-copy stamp carries a seal-key signature",
|
|
["decision: Seth", "phase 7"],
|
|
"A printed or exported copy can be checked against the server later. Phase 9 adds the Admin screen that checks a copy.\n\n**To answer:** keep; who should be able to run the check.",
|
|
),
|
|
(
|
|
"Q39",
|
|
"PM reports readable by every signed-in role, every export logged",
|
|
["decision: Seth", "phase 8"],
|
|
"**Built as the default:** any signed-in role may read the Project Management reports, not only PS-ProjectMgmt; every export is logged as report exported with scope and period.\n\n**To answer:** keep or restrict.",
|
|
),
|
|
(
|
|
"Q40",
|
|
"Presence: who is shown and when they drop out",
|
|
["decision: Seth", "phase 8"],
|
|
"**Built as the default:** display name and role; locked sessions drop out at once; a closed tab drops within 25 seconds.\n\n**To answer:** keep.",
|
|
),
|
|
(
|
|
"Q41",
|
|
"KPI numbers follow the project pick only, not the other filters",
|
|
["decision: Seth", "phase 8"],
|
|
"**Built as the default:** as the old build. The find bar filters are applied by the server on every board and list.\n\n**To answer:** keep.",
|
|
),
|
|
(
|
|
"Q42",
|
|
"The stall's Mine flag needs a picked station",
|
|
["decision: Seth", "phase 8"],
|
|
"**Built as the default:** Mine is decided from AD roles and the screen; on the stall screen it shows nothing without a picked station.\n\n**To answer:** keep.",
|
|
),
|
|
(
|
|
"OPS1",
|
|
"docker-1: enlarge the Docker address pool before the production stack",
|
|
["decision: IT", "production"],
|
|
"Docker's default pool on docker-1 holds fifteen /16 bridge networks. Ten are used; each Packet Station stack takes two, the Gitea runner one per job, the CI e2e stack two. The pool ran out on 2026-10-17 and every CI e2e job failed until idle networks were removed.\n\n"
|
|
'**To do (needs sudo and a Docker restart, a short outage for every stack):** add to /etc/docker/daemon.json `{"default-address-pools":[{"base":"10.200.0.0/16","size":24}]}`, restart Docker. Details in docs/DEPLOY.md.',
|
|
),
|
|
(
|
|
"OPS2",
|
|
"Move the test stack files from ~/packetstation/test to /etc/packetstation/test",
|
|
["decision: IT"],
|
|
"The test stack was set up under /home/spencerm/packetstation/test because the session had no sudo. DEPLOY.md describes /etc/packetstation/<stack> with secrets root:10001 mode 0440. The test secrets are world-readable inside the home folder until moved.\n\n**To do:** move stack.env, secrets/, ca/ and backups/, fix ownership and modes, update ENV_FILE, SECRETS_DIR, CA_BUNDLE_FILE and BACKUP_DIR in stack.env, restart the stack.",
|
|
),
|
|
]
|
|
|
|
|
|
def tea(*args: str, output: str | None = "json") -> list | dict | str:
|
|
cmd = ["tea", *args, "--repo", REPO]
|
|
if output:
|
|
cmd += ["--output", output]
|
|
r = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")
|
|
if r.returncode != 0:
|
|
raise SystemExit(f"tea {' '.join(args)} failed: {r.stderr.strip()[:400]}")
|
|
if output == "json":
|
|
return json.loads(r.stdout or "[]")
|
|
return r.stdout
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--dry-run", action="store_true")
|
|
a = ap.parse_args()
|
|
if a.dry_run:
|
|
for key, title, labels, _ in ISSUES:
|
|
print(f"{key:5} {', '.join(labels):40} {title}")
|
|
print(f"{len(ISSUES)} issues")
|
|
return 0
|
|
|
|
existing = {lb["name"] for lb in tea("labels", "list")}
|
|
for name, color in LABELS.items():
|
|
if name not in existing:
|
|
tea("labels", "create", "--name", name, "--color", color, output=None)
|
|
print("label created:", name)
|
|
|
|
titles = {i["title"] for i in tea("issues", "list", "--state", "all", "--limit", "500")}
|
|
|
|
made = 0
|
|
for key, title, labels, body in ISSUES:
|
|
if title in titles:
|
|
print("exists:", title)
|
|
continue
|
|
footer = (
|
|
"Plan reference: "
|
|
+ key
|
|
+ " in [docs/PLAN.md]("
|
|
+ PLAN
|
|
+ ") (open questions). Answer here; the build takes the default until then."
|
|
)
|
|
full = body + "\n\n---\n" + footer + "\n"
|
|
with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as f:
|
|
f.write(full)
|
|
path = f.name
|
|
try:
|
|
out = tea(
|
|
"issues",
|
|
"create",
|
|
"--title",
|
|
title,
|
|
"--labels",
|
|
",".join(labels),
|
|
"--description-file",
|
|
path,
|
|
output=None,
|
|
)
|
|
finally:
|
|
Path(path).unlink(missing_ok=True)
|
|
first = (out or "").strip().splitlines()[0] if out else ""
|
|
print(f"created: {title} {first[:60]}")
|
|
made += 1
|
|
print(f"{made} created, {len(ISSUES) - made} already existed")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|