Add facilities, contracts, orders, value lists, audit log, and desktop app modules
Extends omsorgCore with full CRUD for Facility/Contract/Order plus configurable value lists and an audit trail, and wires the omsorgapp frontend up to the new facilities, settings, and audit-log modules; includes a sidebar active-nav-item highlight. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ee74ed65f5
commit
e9e96a57dc
+98
-54
@@ -1,10 +1,15 @@
|
||||
const { app, BrowserWindow, ipcMain, shell, safeStorage } = require('electron');
|
||||
const { app, BrowserWindow, ipcMain, safeStorage } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const authClient = require('./backend/authClient.cjs');
|
||||
const httpClient = require('./backend/httpClient.cjs');
|
||||
const employeesClient = require('./backend/employeesClient.cjs');
|
||||
const facilitiesClient = require('./backend/facilitiesClient.cjs');
|
||||
const facilityContactsClient = require('./backend/facilityContactsClient.cjs');
|
||||
const usersClient = require('./backend/usersClient.cjs');
|
||||
const rolesClient = require('./backend/rolesClient.cjs');
|
||||
const auditLogClient = require('./backend/auditLogClient.cjs');
|
||||
const valueListsClient = require('./backend/valueListsClient.cjs');
|
||||
|
||||
app.commandLine.appendSwitch('lang', 'de');
|
||||
|
||||
@@ -144,44 +149,6 @@ function authorizedRequest(method, resourcePath, body) {
|
||||
return withAuthRetry((accessToken) => httpClient.request(method, resourcePath, { body, accessToken }));
|
||||
}
|
||||
|
||||
function ensureDir(dir) { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); }
|
||||
function appRoot() {
|
||||
const base = path.join(app.getPath('documents'), 'Omsorg Business Controls Pro');
|
||||
ensureDir(base);
|
||||
ensureDir(path.join(base, 'database'));
|
||||
ensureDir(path.join(base, 'documents'));
|
||||
ensureDir(path.join(base, 'documents', 'employees'));
|
||||
ensureDir(path.join(base, 'documents', 'customers'));
|
||||
ensureDir(path.join(base, 'backups'));
|
||||
return base;
|
||||
}
|
||||
function dbPath() { return path.join(appRoot(), 'database', 'omsorg-local-db.json'); }
|
||||
function defaultData() {
|
||||
return {
|
||||
version: '0.1.1',
|
||||
createdAt: new Date().toISOString(),
|
||||
users: [
|
||||
{ id:'u1', name:'Sabina Dautovic', role:'Geschäftsführung' },
|
||||
{ id:'u2', name:'Malik Neumann', role:'Geschäftsführung' },
|
||||
{ id:'u3', name:'Sabrina Berggötz', role:'Disposition' }
|
||||
],
|
||||
employees: [
|
||||
{ id:'e1', name:'Dirk Artmann', qualification:'3-jährige Pflegefachkraft', status:'Aktiv' },
|
||||
{ id:'e2', name:'Carina Vetter', qualification:'3-jährige Pflegefachkraft', status:'Aktiv' },
|
||||
{ id:'e3', name:'Dilara Anders', qualification:'1-jährige Pflegekraft', status:'Aktiv' },
|
||||
{ id:'e4', name:'Pascal Galliot', qualification:'1-jährige Pflegekraft', status:'Aktiv' }
|
||||
],
|
||||
customers: [],
|
||||
assignments: [],
|
||||
notes: []
|
||||
};
|
||||
}
|
||||
function readDb() {
|
||||
const file = dbPath();
|
||||
if (!fs.existsSync(file)) fs.writeFileSync(file, JSON.stringify(defaultData(), null, 2));
|
||||
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||
}
|
||||
function writeDb(data) { fs.writeFileSync(dbPath(), JSON.stringify(data, null, 2)); return data; }
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1440,
|
||||
@@ -200,7 +167,6 @@ function createWindow() {
|
||||
else mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
|
||||
}
|
||||
app.whenReady().then(async () => {
|
||||
appRoot();
|
||||
createWindow();
|
||||
await bootstrapSession();
|
||||
broadcastSession();
|
||||
@@ -208,18 +174,6 @@ app.whenReady().then(async () => {
|
||||
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });
|
||||
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
|
||||
|
||||
ipcMain.handle('db:get', () => readDb());
|
||||
ipcMain.handle('db:set', (_event, data) => writeDb(data));
|
||||
ipcMain.handle('app:paths', () => ({ root: appRoot(), db: dbPath() }));
|
||||
ipcMain.handle('app:openRoot', () => shell.openPath(appRoot()));
|
||||
ipcMain.handle('backup:create', () => {
|
||||
const root = appRoot();
|
||||
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupFile = path.join(root, 'backups', `backup-${stamp}.json`);
|
||||
fs.copyFileSync(dbPath(), backupFile);
|
||||
return backupFile;
|
||||
});
|
||||
|
||||
ipcMain.handle('auth:login', async (_event, { username, password }) => {
|
||||
const result = await authClient.login(username, password);
|
||||
if (!result.ok) {
|
||||
@@ -263,7 +217,17 @@ ipcMain.handle('auth:verifyPasswordResetCode', async (_event, { username, pin })
|
||||
|
||||
ipcMain.handle('auth:resetPassword', async (_event, { resetToken, newPassword }) => {
|
||||
const result = await authClient.resetPassword(resetToken, newPassword);
|
||||
return { success: result.ok };
|
||||
if (!result.ok) {
|
||||
const error = result.status === 400
|
||||
? 'password_too_short'
|
||||
: 'invalid_or_expired';
|
||||
return {
|
||||
success: false,
|
||||
error,
|
||||
message: result.status === 400 && typeof result.data === 'string' ? result.data : undefined
|
||||
};
|
||||
}
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// Erfolgreicher Wechsel widerruft serverseitig alle Sessions (siehe UserService.ChangeOwnPasswordAsync) -
|
||||
@@ -272,12 +236,27 @@ ipcMain.handle('auth:changePassword', async (_event, { currentPassword, newPassw
|
||||
if (!session.accessToken) return { success: false, error: 'not_authenticated' };
|
||||
const result = await authClient.changePassword(session.accessToken, currentPassword, newPassword);
|
||||
if (!result.ok) {
|
||||
return { success: false, error: result.status === 401 ? 'invalid_current_password' : 'unknown' };
|
||||
const error = result.status === 401
|
||||
? 'invalid_current_password'
|
||||
: result.status === 400
|
||||
? 'password_too_short'
|
||||
: 'unknown';
|
||||
return {
|
||||
success: false,
|
||||
error,
|
||||
message: result.status === 400 && typeof result.data === 'string' ? result.data : undefined
|
||||
};
|
||||
}
|
||||
clearSession();
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
ipcMain.handle('auth:getPasswordPolicy', async () => {
|
||||
const result = await authClient.getPasswordPolicy();
|
||||
if (!result.ok) return { success: false };
|
||||
return { success: true, minLength: result.minLength };
|
||||
});
|
||||
|
||||
// Generischer Proxy für Ressourcen ohne eigene <kategorie>Client.cjs-Datei (siehe Plan).
|
||||
ipcMain.handle('api:get', (_event, resourcePath) => authorizedRequest('GET', resourcePath));
|
||||
ipcMain.handle('api:post', (_event, resourcePath, body) => authorizedRequest('POST', resourcePath, body));
|
||||
@@ -290,7 +269,72 @@ ipcMain.handle('employees:update', (_event, id, payload) =>
|
||||
withAuthRetry((accessToken) => employeesClient.updateEmployee(accessToken, id, payload))
|
||||
);
|
||||
|
||||
ipcMain.handle('facilities:list', (_event, params) => withAuthRetry((accessToken) => facilitiesClient.listFacilities(accessToken, params)));
|
||||
ipcMain.handle('facilities:create', (_event, payload) =>
|
||||
withAuthRetry((accessToken) => facilitiesClient.createFacility(accessToken, payload))
|
||||
);
|
||||
ipcMain.handle('facilities:update', (_event, id, payload) =>
|
||||
withAuthRetry((accessToken) => facilitiesClient.updateFacility(accessToken, id, payload))
|
||||
);
|
||||
|
||||
ipcMain.handle('facilityContacts:list', (_event, facilityId) =>
|
||||
withAuthRetry((accessToken) => facilityContactsClient.listFacilityContacts(accessToken, facilityId))
|
||||
);
|
||||
ipcMain.handle('facilityContacts:create', (_event, facilityId, payload) =>
|
||||
withAuthRetry((accessToken) => facilityContactsClient.createFacilityContact(accessToken, facilityId, payload))
|
||||
);
|
||||
ipcMain.handle('facilityContacts:update', (_event, facilityId, id, payload) =>
|
||||
withAuthRetry((accessToken) => facilityContactsClient.updateFacilityContact(accessToken, facilityId, id, payload))
|
||||
);
|
||||
|
||||
ipcMain.handle('users:list', () => withAuthRetry((accessToken) => usersClient.listUsers(accessToken)));
|
||||
ipcMain.handle('users:create', (_event, payload) =>
|
||||
withAuthRetry((accessToken) => usersClient.createUser(accessToken, payload))
|
||||
);
|
||||
ipcMain.handle('users:listPermissionOverrides', (_event, userId) =>
|
||||
withAuthRetry((accessToken) => usersClient.listPermissionOverrides(accessToken, userId))
|
||||
);
|
||||
ipcMain.handle('users:addPermissionOverride', (_event, userId, payload) =>
|
||||
withAuthRetry((accessToken) => usersClient.addPermissionOverride(accessToken, userId, payload))
|
||||
);
|
||||
ipcMain.handle('users:deletePermissionOverride', (_event, userId, overrideId) =>
|
||||
withAuthRetry((accessToken) => usersClient.deletePermissionOverride(accessToken, userId, overrideId))
|
||||
);
|
||||
|
||||
ipcMain.handle('roles:list', () => withAuthRetry((accessToken) => rolesClient.listRoles(accessToken)));
|
||||
ipcMain.handle('roles:get', (_event, roleId) =>
|
||||
withAuthRetry((accessToken) => rolesClient.getRole(accessToken, roleId))
|
||||
);
|
||||
ipcMain.handle('roles:create', (_event, payload) =>
|
||||
withAuthRetry((accessToken) => rolesClient.createRole(accessToken, payload))
|
||||
);
|
||||
ipcMain.handle('roles:updatePermissions', (_event, roleId, payload) =>
|
||||
withAuthRetry((accessToken) => rolesClient.updateRolePermissions(accessToken, roleId, payload))
|
||||
);
|
||||
|
||||
ipcMain.handle('auditLog:list', (_event, params) =>
|
||||
withAuthRetry((accessToken) => auditLogClient.listAuditLog(accessToken, params))
|
||||
);
|
||||
|
||||
ipcMain.handle('valueLists:list', () => withAuthRetry((accessToken) => valueListsClient.listValueLists(accessToken)));
|
||||
ipcMain.handle('valueLists:listItems', (_event, key) =>
|
||||
withAuthRetry((accessToken) => valueListsClient.listItems(accessToken, key))
|
||||
);
|
||||
ipcMain.handle('valueLists:createItem', (_event, key, payload) =>
|
||||
withAuthRetry((accessToken) => valueListsClient.createItem(accessToken, key, payload))
|
||||
);
|
||||
ipcMain.handle('valueLists:updateItem', (_event, key, id, payload) =>
|
||||
withAuthRetry((accessToken) => valueListsClient.updateItem(accessToken, key, id, payload))
|
||||
);
|
||||
ipcMain.handle('valueLists:deleteItem', (_event, key, id) =>
|
||||
withAuthRetry((accessToken) => valueListsClient.deleteItem(accessToken, key, id))
|
||||
);
|
||||
ipcMain.handle('valueLists:getUsages', (_event, key, id) =>
|
||||
withAuthRetry((accessToken) => valueListsClient.getUsages(accessToken, key, id))
|
||||
);
|
||||
ipcMain.handle('valueLists:listTransitions', (_event, key) =>
|
||||
withAuthRetry((accessToken) => valueListsClient.listTransitions(accessToken, key))
|
||||
);
|
||||
ipcMain.handle('valueLists:replaceTransitions', (_event, key, payload) =>
|
||||
withAuthRetry((accessToken) => valueListsClient.replaceTransitions(accessToken, key, payload))
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user