ThreadCount Community edition

Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
This commit is contained in:
ThreadCount
2026-09-13 08:45:19 +10:00
commit 1bc2de655a
505 changed files with 56223 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
import { prisma } from "../db";
import { sendTo } from "../mail";
/* Notices to the owner, by email, over the SMTP the product already has.
*
* Who gets them: OPS_ALERT_TO when set, otherwise every active OWNER operator. What they carry:
* the name of a facility and what happened to it — never a coordinator's name or email, never a
* wearer, never a record. A notice must be safe to sit in a mailbox. Each caller names its own
* condition; nothing here fires on a timer, so there is nothing to dedupe — one event, one mail.
*
* Never throws and never awaited for its own sake: a notice that could fail the action it
* describes would be a reason to skip sending it. */
export async function ownerAddresses(): Promise<string[]> {
const fixed = (process.env.OPS_ALERT_TO || "").split(",").map((s) => s.trim()).filter(Boolean);
if (fixed.length) return fixed;
try {
const owners = await prisma.operator.findMany({ where: { role: "OWNER", inactive: false }, select: { email: true } });
return owners.map((o) => o.email);
} catch {
return [];
}
}
export function notifyOwners(subject: string, text: string): void {
ownerAddresses()
.then((to) => Promise.all(to.map((a) => sendTo(a, subject, text))))
.catch(() => { /* a notice must not fail the action */ });
}
const when = () => new Date().toLocaleString("en-AU", { timeZone: "Australia/Brisbane", dateStyle: "medium", timeStyle: "short" });
/** A facility signed up. Name only — the person who did it is a contact, and contacts are revealed, not mailed. */
export function alertNewSignup(facility: { id: string; name: string }): void {
notifyOwners(`[ops] New facility — ${facility.name}`, [
`A facility signed up for ThreadCount.`,
"",
`Facility: ${facility.name}`,
`When: ${when()} (Brisbane)`,
`Console: https://ops.threadcount.tech/ops/facilities/${facility.id}`,
"",
"It appears on the console as \"never set up\" until its staff groups are named.",
].join("\n"));
}
/** A facility was deleted from the console. Sent to every owner so a second operator's deletion reaches the first. */
export function alertFacilityDeleted(args: { name: string; by: string; ip: string; counts: string }): void {
notifyOwners(`[ops] Facility deleted — ${args.name}`, [
`${args.by} deleted a facility from the ThreadCount operations console.`,
"",
`Facility: ${args.name}`,
`Had: ${args.counts}`,
`When: ${when()} (Brisbane)`,
`From: ${args.ip || "unknown address"}`,
"",
"Every record, image and account belonging to it is gone. The pre-deploy database dumps on the box are the only copies left.",
].join("\n"));
}