Registo de chaves
This commit is contained in:
parent
ef83aa153f
commit
94961cf98a
|
|
@ -163,10 +163,10 @@ One row per **physical piece of metal**, not per door.
|
||||||
| Column | Required | What it is |
|
| 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. |
|
| `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`. |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,7 @@ function sheetKind(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------- import / backup */
|
/* --------------------------------------------------------- import / backup */
|
||||||
let impKind = "items";
|
let impKind = "cabinets"; // the first step, not the last
|
||||||
|
|
||||||
const IMP_SPEC = {
|
const IMP_SPEC = {
|
||||||
items: { url: "/api/admin/import", req: "tag, door, building",
|
items: { url: "/api/admin/import", req: "tag, door, building",
|
||||||
|
|
@ -344,9 +344,10 @@ function pageData() {
|
||||||
$body.innerHTML = `
|
$body.innerHTML = `
|
||||||
<div class="card"><h3>${T.impTitle}</h3>
|
<div class="card"><h3>${T.impTitle}</h3>
|
||||||
<span class="lab">${T.impWhat}</span>
|
<span class="lab">${T.impWhat}</span>
|
||||||
|
<p style="color:var(--slate);font-size:12.5px;margin:0 0 8px">${T.impOrder}</p>
|
||||||
<div class="adm-nav" style="padding:0 0 12px">
|
<div class="adm-nav" style="padding:0 0 12px">
|
||||||
${[["items", T.impItems], ["doors", T.impDoors], ["people", T.impPeople], ["cabinets", T.impCabinets]]
|
${[["cabinets", T.impCabinets], ["doors", T.impDoors], ["people", T.impPeople], ["items", T.impItems]]
|
||||||
.map(([k, l]) => `<button data-imp="${k}" aria-pressed="${impKind === k}">${l}</button>`).join("")}
|
.map(([k, l], n) => `<button data-imp="${k}" aria-pressed="${impKind === k}">${n + 1}. ${l}</button>`).join("")}
|
||||||
</div>
|
</div>
|
||||||
<p style="color:var(--slate);font-size:13px">
|
<p style="color:var(--slate);font-size:13px">
|
||||||
<b>${esc(spec.req)}</b>${spec.opt ? " · " + esc(spec.opt) : ""}</p>
|
<b>${esc(spec.req)}</b>${spec.opt ? " · " + esc(spec.opt) : ""}</p>
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,7 @@ var I18N = {
|
||||||
holdsWarn: (n, tags) => `Tem ${n} ${n === 1 ? "item" : "itens"}: ${tags}. Receba-os de volta antes de desativar.`,
|
holdsWarn: (n, tags) => `Tem ${n} ${n === 1 ? "item" : "itens"}: ${tags}. Receba-os de volta antes de desativar.`,
|
||||||
|
|
||||||
impWhat: "O que quer importar?",
|
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",
|
impItems: "Chaves e etiquetas", impDoors: "Portas", impPeople: "Pessoas", impCabinets: "Armários",
|
||||||
impResult2: (c, u) => `Criados <b>${c}</b>, atualizados <b>${u}</b>.`,
|
impResult2: (c, u) => `Criados <b>${c}</b>, atualizados <b>${u}</b>.`,
|
||||||
impTitle: "Importar de uma folha de cálculo",
|
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 <em>reserve</em> rather than <em>in cabinet</em>, so they stay out of everyday circulation.",
|
offSiteNote: "Keys returned to an off-site store go back to <em>reserve</em> rather than <em>in cabinet</em>, so they stay out of everyday circulation.",
|
||||||
holdsWarn: (n, tags) => `Currently holds ${n} item${n === 1 ? "" : "s"}: ${tags}. Take those back before deactivating.`,
|
holdsWarn: (n, tags) => `Currently holds ${n} item${n === 1 ? "" : "s"}: ${tags}. Take those back before deactivating.`,
|
||||||
impWhat: "What do you want to import?",
|
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",
|
impItems: "Keys & tags", impDoors: "Doors", impPeople: "People", impCabinets: "Cabinets",
|
||||||
impResult2: (c, u) => `Created <b>${c}</b>, updated <b>${u}</b>.`,
|
impResult2: (c, u) => `Created <b>${c}</b>, updated <b>${u}</b>.`,
|
||||||
impTitle: "Import from a spreadsheet",
|
impTitle: "Import from a spreadsheet",
|
||||||
|
|
|
||||||
21
server.js
21
server.js
|
|
@ -666,16 +666,29 @@ app.post(A + "/import", (req, res) => {
|
||||||
out.skipped.push(tag + " (already exists)"); continue;
|
out.skipped.push(tag + " (already exists)"); continue;
|
||||||
}
|
}
|
||||||
const group = col("lock_group") >= 0 ? (r[col("lock_group")] || "").trim() : "";
|
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) {
|
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 (?,?,?,?)")
|
const id = db.prepare("INSERT INTO doors (num,building,name,lock_group) VALUES (?,?,?,?)")
|
||||||
.run(nextNum(), building, doorName, group).lastInsertRowid;
|
.run(nextNum(), building, doorName, group).lastInsertRowid;
|
||||||
door = { id }; out.doors++;
|
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)) {
|
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.skipped.push(`${tag} (unknown cabinet: ${cabId})`);
|
||||||
out.cabinets++;
|
continue;
|
||||||
}
|
}
|
||||||
/* An unknown type is refused rather than created. A malformed row or a
|
/* 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
|
typo would otherwise litter the type list with junk, and types are now
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue