From 94961cf98a1d070349dd067a41abf10cf524f5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Louren=C3=A7o?= Date: Mon, 24 Aug 2026 16:35:20 +0100 Subject: [PATCH] Registo de chaves --- README.md | 4 ++-- public/admin.js | 7 ++++--- public/i18n.js | 2 ++ server.js | 21 +++++++++++++++++---- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bf7d325..ab906ed 100644 --- a/README.md +++ b/README.md @@ -163,10 +163,10 @@ One row per **physical piece of metal**, not per door. | Column | Required | What it is | |---|---|---| | `tag` | **yes** | The number stamped on that copy, e.g. `12-A`. Must be unique — this is what makes the register able to say *which* copy is out. | -| `door` | **yes** | The door **name**, matched against the doors you already imported. If no door matches, one is created. | +| `door` | **yes** | The door **number** or its **name** — whichever your list uses. Matched against doors already imported, number first. A number that matches nothing is refused (not turned into a door called `12`); a name that matches nothing creates the door. | | `building` | **yes** | Used together with `door` to find the right door, since two buildings may both have a `Cozinha`. | | `lock_group` | no | **Only used if this row has to create a new door.** For doors that already exist it is ignored — edit the door instead. | -| `cabinet` | no | The cabinet `id`, e.g. `SAC`. Created if it does not exist, but then it gets no proper name, so import cabinets first. | +| `cabinet` | no | The cabinet **code**, e.g. `SAC`. Case does not matter. A code that does not exist is **refused** and the row is listed as skipped — import cabinets first (step 1). | | `hook` | no | Position in the cabinet, e.g. `SAC·R2·05`. Sets the order keys appear on the board and on the audit sheet. Blank falls back to tag order. | | `master` | no | yes/no. Marks it as a master key on the board. | | `status` | no | `in` (default), `out`, `reserve`, `lost`, `retired`. Use `reserve` for copies in the off-site box. Do **not** import `out` — issue the key in the app instead, so there is a handover record. | diff --git a/public/admin.js b/public/admin.js index 97c4295..ffa3f3c 100644 --- a/public/admin.js +++ b/public/admin.js @@ -322,7 +322,7 @@ function sheetKind(id) { } /* --------------------------------------------------------- import / backup */ -let impKind = "items"; +let impKind = "cabinets"; // the first step, not the last const IMP_SPEC = { items: { url: "/api/admin/import", req: "tag, door, building", @@ -344,9 +344,10 @@ function pageData() { $body.innerHTML = `

${T.impTitle}

${T.impWhat} +

${T.impOrder}

- ${[["items", T.impItems], ["doors", T.impDoors], ["people", T.impPeople], ["cabinets", T.impCabinets]] - .map(([k, l]) => ``).join("")} + ${[["cabinets", T.impCabinets], ["doors", T.impDoors], ["people", T.impPeople], ["items", T.impItems]] + .map(([k, l], n) => ``).join("")}

${esc(spec.req)}${spec.opt ? " · " + esc(spec.opt) : ""}

diff --git a/public/i18n.js b/public/i18n.js index 1eafba4..720f37b 100644 --- a/public/i18n.js +++ b/public/i18n.js @@ -232,6 +232,7 @@ var I18N = { holdsWarn: (n, tags) => `Tem ${n} ${n === 1 ? "item" : "itens"}: ${tags}. Receba-os de volta antes de desativar.`, impWhat: "O que quer importar?", + impOrder: "Importe por esta ordem — as chaves apontam para portas e armários, que têm de existir primeiro.", impItems: "Chaves e etiquetas", impDoors: "Portas", impPeople: "Pessoas", impCabinets: "Armários", impResult2: (c, u) => `Criados ${c}, atualizados ${u}.`, impTitle: "Importar de uma folha de cálculo", @@ -425,6 +426,7 @@ var I18N = { offSiteNote: "Keys returned to an off-site store go back to reserve rather than in cabinet, so they stay out of everyday circulation.", holdsWarn: (n, tags) => `Currently holds ${n} item${n === 1 ? "" : "s"}: ${tags}. Take those back before deactivating.`, impWhat: "What do you want to import?", + impOrder: "Import in this order — keys point at doors and cabinets, which must exist first.", impItems: "Keys & tags", impDoors: "Doors", impPeople: "People", impCabinets: "Cabinets", impResult2: (c, u) => `Created ${c}, updated ${u}.`, impTitle: "Import from a spreadsheet", diff --git a/server.js b/server.js index c7613cc..ed6720a 100644 --- a/server.js +++ b/server.js @@ -666,16 +666,29 @@ app.post(A + "/import", (req, res) => { out.skipped.push(tag + " (already exists)"); continue; } const group = col("lock_group") >= 0 ? (r[col("lock_group")] || "").trim() : ""; - let door = findDoor.get(doorName, building); + /* The `door` column takes either the door number or its name. Numbering + is how a paper key list works, so `12` is usually what people have to + hand; the name still works for lists that have no numbers. */ + let door = db.prepare("SELECT id FROM doors WHERE num=? AND building=?").get(doorName, building) + || findDoor.get(doorName, building); if (!door) { + /* A bare number that matches no door is a mistake, not a new door + called "12" — refuse it rather than quietly creating junk. */ + if (/^\d+$/.test(doorName)) { + out.skipped.push(`${tag} (unknown door: ${doorName})`); + continue; + } const id = db.prepare("INSERT INTO doors (num,building,name,lock_group) VALUES (?,?,?,?)") .run(nextNum(), building, doorName, group).lastInsertRowid; door = { id }; out.doors++; } - let cabId = col("cabinet") >= 0 ? (r[col("cabinet")] || "").trim() : ""; + /* An unknown cabinet code is refused rather than created. A typo would + otherwise produce a nameless cabinet and quietly scatter keys into it — + and cabinets have their own importer, one step earlier. */ + let cabId = col("cabinet") >= 0 ? (r[col("cabinet")] || "").trim().toUpperCase() : ""; if (cabId && !db.prepare("SELECT 1 FROM cabinets WHERE id=?").get(cabId)) { - db.prepare("INSERT INTO cabinets (id,name,full_name) VALUES (?,?,?)").run(cabId, cabId, cabId); - out.cabinets++; + out.skipped.push(`${tag} (unknown cabinet: ${cabId})`); + continue; } /* An unknown type is refused rather than created. A malformed row or a typo would otherwise litter the type list with junk, and types are now