Upload via GUI (16 Dateien)
This commit is contained in:
+320
@@ -0,0 +1,320 @@
|
||||
"use strict";
|
||||
|
||||
const { app, ipcMain, dialog, shell, BrowserWindow } = require("electron");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const db = require("./db");
|
||||
const mailer = require("./mailer");
|
||||
const backup = require("./backup");
|
||||
const webserver = require("./webserver");
|
||||
const { filesDirFor, copyIntoStore, fileToDataUri, savePdfAsDocument } = require("./documents");
|
||||
|
||||
module.exports = function registerIpc({ dataFolder }) {
|
||||
ipcMain.handle("directory:list", () => db.listOwnersWithAnimals());
|
||||
|
||||
ipcMain.handle("owner:create", (_e, data) => db.createOwner(data));
|
||||
|
||||
ipcMain.handle("animal:create", (_e, { ownerId, data }) => db.createAnimal(ownerId, data));
|
||||
|
||||
ipcMain.handle("animal:bundle", (_e, animalId) => {
|
||||
const bundle = db.getAnimalBundle(animalId);
|
||||
if (!bundle) return null;
|
||||
const toFileUrl = (relPath) => "file://" + path.join(dataFolder, relPath).replace(/\\/g, "/");
|
||||
bundle.photos = bundle.photos.map((p) => ({ ...p, fileUrl: toFileUrl(p.relative_path) }));
|
||||
if (bundle.consent && bundle.consent.signature_path) {
|
||||
bundle.consent.signature_url = toFileUrl(bundle.consent.signature_path);
|
||||
}
|
||||
if (bundle.contract && bundle.contract.signature_path) {
|
||||
bundle.contract.signature_url = toFileUrl(bundle.contract.signature_path);
|
||||
}
|
||||
return bundle;
|
||||
});
|
||||
|
||||
ipcMain.handle("animal:updateField", (_e, { animalId, field, value }) => {
|
||||
db.updateAnimalField(animalId, field, value);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("owner:updateField", (_e, { ownerId, field, value }) => {
|
||||
db.updateOwnerField(ownerId, field, value);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("animal:delete", (_e, animalId) => {
|
||||
db.deleteAnimal(animalId);
|
||||
fs.rmSync(path.join(dataFolder, "files", animalId), { recursive: true, force: true });
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("owner:delete", (_e, ownerId) => {
|
||||
const animalIds = db.deleteOwner(ownerId);
|
||||
for (const animalId of animalIds) {
|
||||
fs.rmSync(path.join(dataFolder, "files", animalId), { recursive: true, force: true });
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("note:add", (_e, { animalId, text }) => {
|
||||
db.addNote(animalId, text);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("history:add", (_e, { animalId, title, text }) => {
|
||||
db.addHistoryEntry(animalId, title, text);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("lab:addResult", (_e, { animalId, name }) => db.addLabResult(animalId, name));
|
||||
ipcMain.handle("lab:addValue", (_e, { labResultId, parameter, value, reference }) => {
|
||||
db.addLabValue(labResultId, parameter, value, reference);
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle("lab:deleteResult", (_e, labResultId) => {
|
||||
db.deleteLabResult(labResultId);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("finding:add", (_e, { animalId, x, y, severity, text }) =>
|
||||
db.addFinding(animalId, { x, y, severity, text })
|
||||
);
|
||||
ipcMain.handle("finding:update", (_e, { id, severity, text }) => {
|
||||
db.updateFinding(id, { severity, text });
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle("finding:delete", (_e, id) => {
|
||||
db.deleteFinding(id);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("exam:add", (_e, { animalId, fields }) => db.addExam(animalId, fields));
|
||||
|
||||
ipcMain.handle("consent:set", (_e, { animalId, granted, method, signaturePath, marketingOk, itemsJson }) => {
|
||||
db.setConsent(animalId, { granted, method, signaturePath, marketingOk, itemsJson });
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("consent:saveSignature", (_e, { animalId, dataUrl }) => {
|
||||
const base64 = dataUrl.replace(/^data:image\/png;base64,/, "");
|
||||
const dir = filesDirFor(dataFolder, animalId);
|
||||
const filename = `unterschrift-${Date.now()}.png`;
|
||||
const absPath = path.join(dir, filename);
|
||||
fs.writeFileSync(absPath, Buffer.from(base64, "base64"));
|
||||
const relativePath = path.join("files", animalId, filename);
|
||||
|
||||
const existing = db.getAnimalBundle(animalId).consent;
|
||||
db.setConsent(animalId, {
|
||||
granted: true,
|
||||
method: "Unterschrift auf dem Tablet erfasst",
|
||||
signaturePath: relativePath,
|
||||
marketingOk: existing ? !!existing.marketing_ok : false,
|
||||
});
|
||||
return db.getAnimalBundle(animalId).consent;
|
||||
});
|
||||
|
||||
ipcMain.handle("contract:set", (_e, { animalId, granted, method, signaturePath, briefingConfirmed }) => {
|
||||
db.setContract(animalId, { granted, method, signaturePath, briefingConfirmed });
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("contract:saveSignature", (_e, { animalId, dataUrl }) => {
|
||||
const base64 = dataUrl.replace(/^data:image\/png;base64,/, "");
|
||||
const dir = filesDirFor(dataFolder, animalId);
|
||||
const filename = `vertrag-unterschrift-${Date.now()}.png`;
|
||||
const absPath = path.join(dir, filename);
|
||||
fs.writeFileSync(absPath, Buffer.from(base64, "base64"));
|
||||
const relativePath = path.join("files", animalId, filename);
|
||||
|
||||
const existing = db.getAnimalBundle(animalId).contract;
|
||||
db.setContract(animalId, {
|
||||
granted: true,
|
||||
method: "Unterschrift auf dem Tablet erfasst",
|
||||
signaturePath: relativePath,
|
||||
briefingConfirmed: existing ? !!existing.briefing_confirmed : true,
|
||||
});
|
||||
return db.getAnimalBundle(animalId).contract;
|
||||
});
|
||||
|
||||
ipcMain.handle("document:generatePdf", async (_e, { animalId, kind, title, ownerBlockHtml, bodyHtml, signedInfoHtml }) => {
|
||||
const bundle = db.getAnimalBundle(animalId);
|
||||
const record = kind === "vertrag" ? bundle.contract : bundle.consent;
|
||||
const signatureDataUri = record && record.signature_path ? fileToDataUri(path.join(dataFolder, record.signature_path)) : null;
|
||||
const docLabel = kind === "vertrag" ? "Behandlungsvertrag" : "Einwilligung";
|
||||
return savePdfAsDocument(dataFolder, animalId, docLabel, { title, ownerBlockHtml, bodyHtml, signedInfoHtml, signatureDataUri });
|
||||
});
|
||||
|
||||
ipcMain.handle("document:generateTemplatePdf", async (_e, { animalId, filenamePrefix, title, ownerBlockHtml, bodyHtml }) => {
|
||||
return savePdfAsDocument(dataFolder, animalId, filenamePrefix, { title, ownerBlockHtml, bodyHtml, signedInfoHtml: "", signatureDataUri: null });
|
||||
});
|
||||
|
||||
ipcMain.handle("email:send", async (_e, { animalId, kind, absPath, filename }) => {
|
||||
const smtpJson = db.getSetting("smtp");
|
||||
if (!smtpJson) return { ok: false, error: "not-configured" };
|
||||
const smtp = JSON.parse(smtpJson);
|
||||
const bundle = db.getAnimalBundle(animalId);
|
||||
if (!bundle.owner.email) return { ok: false, error: "no-owner-email" };
|
||||
const docLabel = kind === "vertrag" ? "Behandlungsvertrag" : "DSGVO-Einwilligung";
|
||||
try {
|
||||
await mailer.sendMail(smtp, {
|
||||
to: bundle.owner.email,
|
||||
subject: `${docLabel} – ${bundle.animal.name} – Tierheilpraxis Iris Hack`,
|
||||
text: `Guten Tag ${bundle.owner.name},\n\nanbei erhalten Sie die unterschriebenen Unterlagen (${docLabel}) für ${bundle.animal.name}.\n\nMit freundlichen Grüßen\nIris Hack\nTierheilpraxis Iris Hack`,
|
||||
attachments: [{ filename, path: absPath }],
|
||||
});
|
||||
if (kind === "vertrag") db.markContractEmailed(animalId);
|
||||
else db.markConsentEmailed(animalId);
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
return { ok: false, error: String((e && e.message) || e) };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("remedy:list", (_e, query) => db.listRemedies(query));
|
||||
ipcMain.handle("remedy:add", (_e, data) => db.addRemedy(data));
|
||||
ipcMain.handle("remedy:update", (_e, { id, data }) => {
|
||||
db.updateRemedy(id, data);
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle("remedy:delete", (_e, id) => {
|
||||
db.deleteRemedy(id);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("appointment:list", (_e, { fromDate, toDate }) => db.listAppointments(fromDate, toDate));
|
||||
ipcMain.handle("appointment:add", (_e, data) => db.addAppointment(data));
|
||||
ipcMain.handle("appointment:update", (_e, { id, data }) => {
|
||||
db.updateAppointment(id, data);
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle("appointment:delete", (_e, id) => {
|
||||
db.deleteAppointment(id);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("lexware:getPath", () => db.getSetting("lexwarePath"));
|
||||
ipcMain.handle("lexware:setPath", (_e, value) => {
|
||||
db.setSetting("lexwarePath", value);
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle("lexware:open", async () => {
|
||||
const target = db.getSetting("lexwarePath");
|
||||
if (!target) return { ok: false, error: "not-configured" };
|
||||
try {
|
||||
if (/^https?:\/\//i.test(target)) {
|
||||
await shell.openExternal(target);
|
||||
} else {
|
||||
const result = await shell.openPath(target);
|
||||
if (result) return { ok: false, error: result }; // openPath resolves with an error string on failure
|
||||
}
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
return { ok: false, error: String((e && e.message) || e) };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("settings:getSmtp", () => {
|
||||
const json = db.getSetting("smtp");
|
||||
return json ? JSON.parse(json) : null;
|
||||
});
|
||||
ipcMain.handle("settings:setSmtp", (_e, smtp) => {
|
||||
db.setSetting("smtp", JSON.stringify(smtp));
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle("settings:testSmtp", async (_e, smtp) => {
|
||||
try {
|
||||
await mailer.verifySmtp(smtp);
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
return { ok: false, error: String((e && e.message) || e) };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("document:add", async (_e, { animalId, kind }) => {
|
||||
const win = BrowserWindow.getFocusedWindow();
|
||||
const filters =
|
||||
kind === "photo"
|
||||
? [{ name: "Bilder", extensions: ["png", "jpg", "jpeg", "heic", "webp"] }]
|
||||
: [{ name: "Dokumente", extensions: ["pdf", "png", "jpg", "jpeg", "docx", "doc"] }];
|
||||
const result = await dialog.showOpenDialog(win, {
|
||||
title: kind === "photo" ? "Foto auswählen" : "Dokument auswählen",
|
||||
properties: ["openFile", "multiSelections"],
|
||||
filters,
|
||||
});
|
||||
if (result.canceled) return [];
|
||||
return result.filePaths.map((sourcePath) => {
|
||||
const { original, relativePath, size } = copyIntoStore(dataFolder, animalId, sourcePath);
|
||||
const id = db.addDocumentRow(animalId, kind, original, relativePath, size);
|
||||
return db.getDocument(id);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle("document:open", (_e, documentId) => {
|
||||
const doc = db.getDocument(documentId);
|
||||
if (!doc) return false;
|
||||
shell.openPath(path.join(dataFolder, doc.relative_path));
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("document:absPath", (_e, documentId) => {
|
||||
const doc = db.getDocument(documentId);
|
||||
if (!doc) return null;
|
||||
return path.join(dataFolder, doc.relative_path);
|
||||
});
|
||||
|
||||
ipcMain.handle("app:dataFolder", () => dataFolder);
|
||||
|
||||
ipcMain.handle("search:notes", (_e, query) => db.searchAnimalIdsByNoteText(query));
|
||||
|
||||
ipcMain.handle("backup:now", () => {
|
||||
try {
|
||||
const dest = backup.runBackupNow(dataFolder);
|
||||
return { ok: true, dest };
|
||||
} catch (e) {
|
||||
return { ok: false, error: String((e && e.message) || e) };
|
||||
}
|
||||
});
|
||||
ipcMain.handle("backup:info", () => backup.backupInfo(dataFolder));
|
||||
ipcMain.handle("backup:list", () => backup.listBackups(dataFolder).slice().reverse());
|
||||
ipcMain.handle("backup:openFolder", () => {
|
||||
const dir = backup.backupsDir(dataFolder);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
shell.openPath(dir);
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle("backup:restore", (_e, name) => {
|
||||
try {
|
||||
backup.restoreBackup(dataFolder, name);
|
||||
// The in-memory database must be reloaded from the restored file, so the whole
|
||||
// app restarts — a plain page reload would keep the stale data and overwrite
|
||||
// the restore on its next save().
|
||||
setTimeout(() => {
|
||||
app.relaunch();
|
||||
app.exit(0);
|
||||
}, 300);
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
return { ok: false, error: String((e && e.message) || e) };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("webaccess:startPairing", async () => {
|
||||
const info = webserver.startPairing();
|
||||
const qr = await webserver.pairingQrDataUri();
|
||||
return { ...info, qr };
|
||||
});
|
||||
ipcMain.handle("webaccess:pairingStatus", () => webserver.pairingInfo());
|
||||
ipcMain.handle("webaccess:listDevices", () => webserver.listDevices());
|
||||
ipcMain.handle("webaccess:revokeDevice", (_e, id) => {
|
||||
webserver.revokeDevice(id);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle("settings:getReminderLead", () => {
|
||||
const v = db.getSetting("reminderLeadMinutes");
|
||||
return v ? Number(v) : 30;
|
||||
});
|
||||
ipcMain.handle("settings:setReminderLead", (_e, minutes) => {
|
||||
db.setSetting("reminderLeadMinutes", String(minutes));
|
||||
return true;
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user