iobroker.time-tracker 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +386 -0
- package/admin/i18n/de.json +34 -0
- package/admin/i18n/en.json +1 -0
- package/admin/i18n/es.json +34 -0
- package/admin/i18n/fr.json +34 -0
- package/admin/i18n/it.json +34 -0
- package/admin/i18n/nl.json +34 -0
- package/admin/i18n/pl.json +34 -0
- package/admin/i18n/pt.json +34 -0
- package/admin/i18n/ru.json +34 -0
- package/admin/i18n/uk.json +34 -0
- package/admin/i18n/zh-cn.json +34 -0
- package/admin/jsonConfig.json +266 -0
- package/admin/time-tracker.png +0 -0
- package/build/lib/adapter/automation.js +100 -0
- package/build/lib/adapter/automation.js.map +7 -0
- package/build/lib/adapter/commands.js +160 -0
- package/build/lib/adapter/commands.js.map +7 -0
- package/build/lib/adapter/messages.js +207 -0
- package/build/lib/adapter/messages.js.map +7 -0
- package/build/lib/adapter/presence.js +129 -0
- package/build/lib/adapter/presence.js.map +7 -0
- package/build/lib/adapter/states.js +330 -0
- package/build/lib/adapter/states.js.map +7 -0
- package/build/lib/adapter/triggers.js +92 -0
- package/build/lib/adapter/triggers.js.map +7 -0
- package/build/lib/db/database.js +96 -0
- package/build/lib/db/database.js.map +7 -0
- package/build/lib/db/migrations.js +603 -0
- package/build/lib/db/migrations.js.map +7 -0
- package/build/lib/db/repositories/absences.js +432 -0
- package/build/lib/db/repositories/absences.js.map +7 -0
- package/build/lib/db/repositories/audit.js +90 -0
- package/build/lib/db/repositories/audit.js.map +7 -0
- package/build/lib/db/repositories/automations.js +326 -0
- package/build/lib/db/repositories/automations.js.map +7 -0
- package/build/lib/db/repositories/entries.js +335 -0
- package/build/lib/db/repositories/entries.js.map +7 -0
- package/build/lib/db/repositories/holidays.js +156 -0
- package/build/lib/db/repositories/holidays.js.map +7 -0
- package/build/lib/db/repositories/payouts.js +221 -0
- package/build/lib/db/repositories/payouts.js.map +7 -0
- package/build/lib/db/repositories/rfid.js +264 -0
- package/build/lib/db/repositories/rfid.js.map +7 -0
- package/build/lib/db/repositories/rules.js +356 -0
- package/build/lib/db/repositories/rules.js.map +7 -0
- package/build/lib/db/repositories/settings.js +91 -0
- package/build/lib/db/repositories/settings.js.map +7 -0
- package/build/lib/db/repositories/terminals.js +243 -0
- package/build/lib/db/repositories/terminals.js.map +7 -0
- package/build/lib/db/repositories/triggers.js +240 -0
- package/build/lib/db/repositories/triggers.js.map +7 -0
- package/build/lib/db/repositories/users.js +491 -0
- package/build/lib/db/repositories/users.js.map +7 -0
- package/build/lib/db/seed.js +180 -0
- package/build/lib/db/seed.js.map +7 -0
- package/build/lib/domain/avatar.js +63 -0
- package/build/lib/domain/avatar.js.map +7 -0
- package/build/lib/domain/breaks.js +70 -0
- package/build/lib/domain/breaks.js.map +7 -0
- package/build/lib/domain/calculation.js +150 -0
- package/build/lib/domain/calculation.js.map +7 -0
- package/build/lib/domain/holidays.js +113 -0
- package/build/lib/domain/holidays.js.map +7 -0
- package/build/lib/domain/punch.js +99 -0
- package/build/lib/domain/punch.js.map +7 -0
- package/build/lib/domain/target.js +89 -0
- package/build/lib/domain/target.js.map +7 -0
- package/build/lib/errors.js +56 -0
- package/build/lib/errors.js.map +7 -0
- package/build/lib/reports/labels.js +358 -0
- package/build/lib/reports/labels.js.map +7 -0
- package/build/lib/reports/pdf.js +308 -0
- package/build/lib/reports/pdf.js.map +7 -0
- package/build/lib/reports/types.js +32 -0
- package/build/lib/reports/types.js.map +7 -0
- package/build/lib/reports/xls.js +211 -0
- package/build/lib/reports/xls.js.map +7 -0
- package/build/lib/services/aggregation.js +495 -0
- package/build/lib/services/aggregation.js.map +7 -0
- package/build/lib/services/auth.js +381 -0
- package/build/lib/services/auth.js.map +7 -0
- package/build/lib/services/backup.js +372 -0
- package/build/lib/services/backup.js.map +7 -0
- package/build/lib/services/closing.js +106 -0
- package/build/lib/services/closing.js.map +7 -0
- package/build/lib/services/sessionSecret.js +100 -0
- package/build/lib/services/sessionSecret.js.map +7 -0
- package/build/lib/services/sync.js +278 -0
- package/build/lib/services/sync.js.map +7 -0
- package/build/lib/util/time.js +125 -0
- package/build/lib/util/time.js.map +7 -0
- package/build/lib/web/api.js +2589 -0
- package/build/lib/web/api.js.map +7 -0
- package/build/lib/web/cookies.js +80 -0
- package/build/lib/web/cookies.js.map +7 -0
- package/build/lib/web/events.js +50 -0
- package/build/lib/web/events.js.map +7 -0
- package/build/lib/web/pin-guard.js +86 -0
- package/build/lib/web/pin-guard.js.map +7 -0
- package/build/lib/web/problem.js +129 -0
- package/build/lib/web/problem.js.map +7 -0
- package/build/lib/web/rate-limit.js +65 -0
- package/build/lib/web/rate-limit.js.map +7 -0
- package/build/lib/web/router.js +274 -0
- package/build/lib/web/router.js.map +7 -0
- package/build/lib/web/server.js +189 -0
- package/build/lib/web/server.js.map +7 -0
- package/build/lib/web/static.js +151 -0
- package/build/lib/web/static.js.map +7 -0
- package/build/lib/web/stream.js +135 -0
- package/build/lib/web/stream.js.map +7 -0
- package/build/main.js +986 -0
- package/build/main.js.map +7 -0
- package/io-package.json +221 -0
- package/package.json +131 -0
- package/src-pwa/src/i18n/de.json +359 -0
- package/src-pwa/src/i18n/en.json +359 -0
- package/src-pwa/src/i18n/es.json +359 -0
- package/src-pwa/src/i18n/fr.json +359 -0
- package/src-pwa/src/i18n/index.ts +96 -0
- package/src-pwa/src/i18n/it.json +359 -0
- package/src-pwa/src/i18n/nl.json +359 -0
- package/src-pwa/src/i18n/pl.json +359 -0
- package/src-pwa/src/i18n/pt.json +359 -0
- package/src-pwa/src/i18n/ru.json +359 -0
- package/src-pwa/src/i18n/uk.json +359 -0
- package/src-pwa/src/i18n/zh-cn.json +359 -0
- package/www/assets/index-QI3wrEvW.js +119 -0
- package/www/assets/index-QI3wrEvW.js.map +1 -0
- package/www/assets/workbox-window.prod.es5-Bd17z0YL.js +3 -0
- package/www/assets/workbox-window.prod.es5-Bd17z0YL.js.map +1 -0
- package/www/favicon.svg +16 -0
- package/www/icon-192.png +0 -0
- package/www/icon-512.png +0 -0
- package/www/index.html +38 -0
- package/www/manifest.webmanifest +1 -0
- package/www/person.png +0 -0
- package/www/sw.js +3 -0
- package/www/sw.js.map +1 -0
- package/www/workbox-9c191d2f.js +3 -0
- package/www/workbox-9c191d2f.js.map +1 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var rules_exports = {};
|
|
20
|
+
__export(rules_exports, {
|
|
21
|
+
createRulesRepository: () => createRulesRepository,
|
|
22
|
+
mapPauseRuleRow: () => mapPauseRuleRow,
|
|
23
|
+
mapShiftRuleRow: () => mapShiftRuleRow
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(rules_exports);
|
|
26
|
+
var import_errors = require("../../errors");
|
|
27
|
+
var import_audit = require("./audit");
|
|
28
|
+
const PAUSE_COLUMNS = "id, user_id, from_min, to_min, pause_min, is_active";
|
|
29
|
+
const SHIFT_COLUMNS = "id, user_id, day_of_week, from_min, to_min, surcharge, is_active";
|
|
30
|
+
const AUDITED_PAUSE_FIELDS = ["userId", "fromMin", "toMin", "pauseMin", "isActive"];
|
|
31
|
+
const AUDITED_SHIFT_FIELDS = [
|
|
32
|
+
"userId",
|
|
33
|
+
"dayOfWeek",
|
|
34
|
+
"fromMin",
|
|
35
|
+
"toMin",
|
|
36
|
+
"surcharge",
|
|
37
|
+
"isActive"
|
|
38
|
+
];
|
|
39
|
+
function mapPauseRuleRow(row) {
|
|
40
|
+
return {
|
|
41
|
+
id: row.id,
|
|
42
|
+
userId: row.user_id,
|
|
43
|
+
fromMin: row.from_min,
|
|
44
|
+
toMin: row.to_min,
|
|
45
|
+
pauseMin: row.pause_min,
|
|
46
|
+
isActive: row.is_active !== 0
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function mapShiftRuleRow(row) {
|
|
50
|
+
return {
|
|
51
|
+
id: row.id,
|
|
52
|
+
userId: row.user_id,
|
|
53
|
+
dayOfWeek: row.day_of_week,
|
|
54
|
+
fromMin: row.from_min,
|
|
55
|
+
toMin: row.to_min,
|
|
56
|
+
surcharge: row.surcharge,
|
|
57
|
+
isActive: row.is_active !== 0
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function requirePauseWindow(fromMin, toMin) {
|
|
61
|
+
if (!Number.isInteger(fromMin) || fromMin < 0) {
|
|
62
|
+
throw new import_errors.ValidationError(`fromMin must be a whole number of minutes, 0 or more (got ${fromMin})`);
|
|
63
|
+
}
|
|
64
|
+
if (toMin !== null) {
|
|
65
|
+
if (!Number.isInteger(toMin) || toMin <= fromMin) {
|
|
66
|
+
throw new import_errors.ValidationError(`toMin must be greater than fromMin (got ${toMin} and ${fromMin})`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { fromMin, toMin };
|
|
70
|
+
}
|
|
71
|
+
function requireShiftWindow(fromMin, toMin) {
|
|
72
|
+
const check = (value, label) => {
|
|
73
|
+
if (value !== null && (!Number.isInteger(value) || value < 0 || value > 1440)) {
|
|
74
|
+
throw new import_errors.ValidationError(`${label} must be between 0 and 1440 minutes (got ${value})`);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
check(fromMin, "fromMin");
|
|
78
|
+
check(toMin, "toMin");
|
|
79
|
+
if (fromMin !== null && toMin !== null && toMin <= fromMin) {
|
|
80
|
+
throw new import_errors.ValidationError(`toMin must be greater than fromMin (got ${toMin} and ${fromMin})`);
|
|
81
|
+
}
|
|
82
|
+
return { fromMin, toMin };
|
|
83
|
+
}
|
|
84
|
+
function requireSurcharge(surcharge) {
|
|
85
|
+
if (!Number.isFinite(surcharge) || surcharge < 0) {
|
|
86
|
+
throw new import_errors.ValidationError(`surcharge must be 0 or more (got ${surcharge})`);
|
|
87
|
+
}
|
|
88
|
+
return surcharge;
|
|
89
|
+
}
|
|
90
|
+
function createRulesRepository(db) {
|
|
91
|
+
const selectPauseById = db.prepare(`SELECT ${PAUSE_COLUMNS} FROM pause_rules WHERE id = ?`);
|
|
92
|
+
const selectPauseScope = db.prepare(
|
|
93
|
+
`SELECT ${PAUSE_COLUMNS} FROM pause_rules
|
|
94
|
+
WHERE (user_id IS NULL OR user_id = ?)
|
|
95
|
+
ORDER BY from_min, id`
|
|
96
|
+
);
|
|
97
|
+
const insertPause = db.prepare(
|
|
98
|
+
"INSERT INTO pause_rules (user_id, from_min, to_min, pause_min, is_active) VALUES (?, ?, ?, ?, ?)"
|
|
99
|
+
);
|
|
100
|
+
const updatePause = db.prepare(
|
|
101
|
+
"UPDATE pause_rules SET user_id = ?, from_min = ?, to_min = ?, pause_min = ?, is_active = ? WHERE id = ?"
|
|
102
|
+
);
|
|
103
|
+
const deletePause = db.prepare("DELETE FROM pause_rules WHERE id = ?");
|
|
104
|
+
const selectShiftById = db.prepare(`SELECT ${SHIFT_COLUMNS} FROM shift_rules WHERE id = ?`);
|
|
105
|
+
const selectShiftScope = db.prepare(
|
|
106
|
+
`SELECT ${SHIFT_COLUMNS} FROM shift_rules
|
|
107
|
+
WHERE (user_id IS NULL OR user_id = ?)
|
|
108
|
+
ORDER BY (day_of_week IS NULL), day_of_week, (from_min IS NULL), from_min, id`
|
|
109
|
+
);
|
|
110
|
+
const insertShift = db.prepare(
|
|
111
|
+
`INSERT INTO shift_rules (user_id, day_of_week, from_min, to_min, surcharge, is_active)
|
|
112
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
113
|
+
);
|
|
114
|
+
const updateShift = db.prepare(
|
|
115
|
+
`UPDATE shift_rules SET user_id = ?, day_of_week = ?, from_min = ?, to_min = ?, surcharge = ?, is_active = ?
|
|
116
|
+
WHERE id = ?`
|
|
117
|
+
);
|
|
118
|
+
const deleteShift = db.prepare("DELETE FROM shift_rules WHERE id = ?");
|
|
119
|
+
const readPause = (id) => {
|
|
120
|
+
const row = selectPauseById.get(id);
|
|
121
|
+
return row ? mapPauseRuleRow(row) : null;
|
|
122
|
+
};
|
|
123
|
+
const readShift = (id) => {
|
|
124
|
+
const row = selectShiftById.get(id);
|
|
125
|
+
return row ? mapShiftRuleRow(row) : null;
|
|
126
|
+
};
|
|
127
|
+
const scopePauseRules = (userId, includeInactive) => selectPauseScope.all(userId).map(mapPauseRuleRow).filter((rule) => userId === null ? rule.userId === null : rule.userId === userId).filter((rule) => includeInactive || rule.isActive);
|
|
128
|
+
return {
|
|
129
|
+
pauseRules(userId, options) {
|
|
130
|
+
const includeInactive = (options == null ? void 0 : options.includeInactive) === true;
|
|
131
|
+
const owner = userId != null ? userId : null;
|
|
132
|
+
const merged = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const rule of scopePauseRules(null, includeInactive)) {
|
|
134
|
+
merged.set(rule.fromMin, rule);
|
|
135
|
+
}
|
|
136
|
+
if (owner !== null) {
|
|
137
|
+
for (const rule of scopePauseRules(owner, includeInactive)) {
|
|
138
|
+
merged.set(rule.fromMin, rule);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return [...merged.values()].sort((a, b) => a.fromMin - b.fromMin || a.id - b.id);
|
|
142
|
+
},
|
|
143
|
+
listPauseRules(input) {
|
|
144
|
+
var _a;
|
|
145
|
+
return scopePauseRules((_a = input == null ? void 0 : input.userId) != null ? _a : null, (input == null ? void 0 : input.includeInactive) === true);
|
|
146
|
+
},
|
|
147
|
+
savePauseRule(input) {
|
|
148
|
+
var _a, _b, _c, _d, _e;
|
|
149
|
+
const window = requirePauseWindow(input.fromMin, (_a = input.toMin) != null ? _a : null);
|
|
150
|
+
const pauseMin = Math.round(input.pauseMin);
|
|
151
|
+
if (!Number.isFinite(input.pauseMin) || pauseMin <= 0) {
|
|
152
|
+
throw new import_errors.ValidationError(`pauseMin must be greater than 0 (got ${input.pauseMin})`);
|
|
153
|
+
}
|
|
154
|
+
const userId = (_b = input.userId) != null ? _b : null;
|
|
155
|
+
const isActive = input.isActive !== false;
|
|
156
|
+
const now = (_c = input.now) != null ? _c : Math.floor(Date.now() / 1e3);
|
|
157
|
+
const current = input.id === void 0 ? null : readPause(input.id);
|
|
158
|
+
if (input.id !== void 0 && !current) {
|
|
159
|
+
throw new import_errors.NotFoundError(`pause rule ${input.id} not found`);
|
|
160
|
+
}
|
|
161
|
+
const next = {
|
|
162
|
+
id: (_d = current == null ? void 0 : current.id) != null ? _d : 0,
|
|
163
|
+
userId,
|
|
164
|
+
fromMin: window.fromMin,
|
|
165
|
+
toMin: window.toMin,
|
|
166
|
+
pauseMin,
|
|
167
|
+
isActive
|
|
168
|
+
};
|
|
169
|
+
const changes = current ? (0, import_audit.diffFields)(
|
|
170
|
+
current,
|
|
171
|
+
next,
|
|
172
|
+
AUDITED_PAUSE_FIELDS
|
|
173
|
+
) : null;
|
|
174
|
+
if (current && Object.keys(changes != null ? changes : {}).length === 0) {
|
|
175
|
+
return current;
|
|
176
|
+
}
|
|
177
|
+
let ruleId = (_e = current == null ? void 0 : current.id) != null ? _e : 0;
|
|
178
|
+
const run = db.transaction(() => {
|
|
179
|
+
var _a2;
|
|
180
|
+
if (current) {
|
|
181
|
+
updatePause.run(userId, next.fromMin, next.toMin, next.pauseMin, isActive ? 1 : 0, current.id);
|
|
182
|
+
} else {
|
|
183
|
+
ruleId = Number(
|
|
184
|
+
insertPause.run(userId, next.fromMin, next.toMin, next.pauseMin, isActive ? 1 : 0).lastInsertRowid
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
188
|
+
atUtc: now,
|
|
189
|
+
actorId: input.actorId,
|
|
190
|
+
action: current ? "pause_rule.update" : "pause_rule.create",
|
|
191
|
+
entity: "pause_rule",
|
|
192
|
+
entityId: ruleId,
|
|
193
|
+
detail: current ? { changes } : { userId, fromMin: next.fromMin, toMin: next.toMin, pauseMin: next.pauseMin },
|
|
194
|
+
ip: (_a2 = input.actorIp) != null ? _a2 : null
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
run();
|
|
198
|
+
const saved = readPause(ruleId);
|
|
199
|
+
if (!saved) {
|
|
200
|
+
throw new Error(`pause rule ${ruleId} disappeared right after the write`);
|
|
201
|
+
}
|
|
202
|
+
return saved;
|
|
203
|
+
},
|
|
204
|
+
removePauseRule(input) {
|
|
205
|
+
var _a;
|
|
206
|
+
const current = readPause(input.id);
|
|
207
|
+
if (!current) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
const now = (_a = input.now) != null ? _a : Math.floor(Date.now() / 1e3);
|
|
211
|
+
const run = db.transaction(() => {
|
|
212
|
+
var _a2;
|
|
213
|
+
deletePause.run(input.id);
|
|
214
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
215
|
+
atUtc: now,
|
|
216
|
+
actorId: input.actorId,
|
|
217
|
+
action: "pause_rule.delete",
|
|
218
|
+
entity: "pause_rule",
|
|
219
|
+
entityId: input.id,
|
|
220
|
+
detail: { userId: current.userId, fromMin: current.fromMin, pauseMin: current.pauseMin },
|
|
221
|
+
ip: (_a2 = input.actorIp) != null ? _a2 : null
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
run();
|
|
225
|
+
return true;
|
|
226
|
+
},
|
|
227
|
+
shiftRules(userId, options) {
|
|
228
|
+
const includeInactive = (options == null ? void 0 : options.includeInactive) === true;
|
|
229
|
+
const owner = userId != null ? userId : null;
|
|
230
|
+
const merged = /* @__PURE__ */ new Map();
|
|
231
|
+
const keyOf = (rule) => {
|
|
232
|
+
var _a, _b;
|
|
233
|
+
return `${(_a = rule.dayOfWeek) != null ? _a : "*"}:${(_b = rule.fromMin) != null ? _b : "*"}`;
|
|
234
|
+
};
|
|
235
|
+
const scope = (scopeUserId) => selectShiftScope.all(scopeUserId).map(mapShiftRuleRow).filter((rule) => scopeUserId === null ? rule.userId === null : rule.userId === scopeUserId).filter((rule) => includeInactive || rule.isActive);
|
|
236
|
+
for (const rule of scope(null)) {
|
|
237
|
+
merged.set(keyOf(rule), rule);
|
|
238
|
+
}
|
|
239
|
+
if (owner !== null) {
|
|
240
|
+
for (const rule of scope(owner)) {
|
|
241
|
+
merged.set(keyOf(rule), rule);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return [...merged.values()].sort(
|
|
245
|
+
(a, b) => {
|
|
246
|
+
var _a, _b, _c, _d;
|
|
247
|
+
return ((_a = a.dayOfWeek) != null ? _a : 7) - ((_b = b.dayOfWeek) != null ? _b : 7) || ((_c = a.fromMin) != null ? _c : -1) - ((_d = b.fromMin) != null ? _d : -1) || a.id - b.id;
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
},
|
|
251
|
+
saveShiftRule(input) {
|
|
252
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
253
|
+
const dayOfWeek = (_a = input.dayOfWeek) != null ? _a : null;
|
|
254
|
+
if (dayOfWeek !== null && (!Number.isInteger(dayOfWeek) || dayOfWeek < 0 || dayOfWeek > 6)) {
|
|
255
|
+
throw new import_errors.ValidationError(`dayOfWeek must be between 0 and 6 (got ${dayOfWeek})`);
|
|
256
|
+
}
|
|
257
|
+
const window = requireShiftWindow((_b = input.fromMin) != null ? _b : null, (_c = input.toMin) != null ? _c : null);
|
|
258
|
+
const surcharge = requireSurcharge(input.surcharge);
|
|
259
|
+
const userId = (_d = input.userId) != null ? _d : null;
|
|
260
|
+
const isActive = input.isActive !== false;
|
|
261
|
+
const now = (_e = input.now) != null ? _e : Math.floor(Date.now() / 1e3);
|
|
262
|
+
const current = input.id === void 0 ? null : readShift(input.id);
|
|
263
|
+
if (input.id !== void 0 && !current) {
|
|
264
|
+
throw new import_errors.NotFoundError(`shift rule ${input.id} not found`);
|
|
265
|
+
}
|
|
266
|
+
const next = {
|
|
267
|
+
id: (_f = current == null ? void 0 : current.id) != null ? _f : 0,
|
|
268
|
+
userId,
|
|
269
|
+
dayOfWeek,
|
|
270
|
+
fromMin: window.fromMin,
|
|
271
|
+
toMin: window.toMin,
|
|
272
|
+
surcharge,
|
|
273
|
+
isActive
|
|
274
|
+
};
|
|
275
|
+
const changes = current ? (0, import_audit.diffFields)(
|
|
276
|
+
current,
|
|
277
|
+
next,
|
|
278
|
+
AUDITED_SHIFT_FIELDS
|
|
279
|
+
) : null;
|
|
280
|
+
if (current && Object.keys(changes != null ? changes : {}).length === 0) {
|
|
281
|
+
return current;
|
|
282
|
+
}
|
|
283
|
+
let ruleId = (_g = current == null ? void 0 : current.id) != null ? _g : 0;
|
|
284
|
+
const run = db.transaction(() => {
|
|
285
|
+
var _a2;
|
|
286
|
+
if (current) {
|
|
287
|
+
updateShift.run(
|
|
288
|
+
userId,
|
|
289
|
+
next.dayOfWeek,
|
|
290
|
+
next.fromMin,
|
|
291
|
+
next.toMin,
|
|
292
|
+
next.surcharge,
|
|
293
|
+
isActive ? 1 : 0,
|
|
294
|
+
current.id
|
|
295
|
+
);
|
|
296
|
+
} else {
|
|
297
|
+
ruleId = Number(
|
|
298
|
+
insertShift.run(
|
|
299
|
+
userId,
|
|
300
|
+
next.dayOfWeek,
|
|
301
|
+
next.fromMin,
|
|
302
|
+
next.toMin,
|
|
303
|
+
next.surcharge,
|
|
304
|
+
isActive ? 1 : 0
|
|
305
|
+
).lastInsertRowid
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
309
|
+
atUtc: now,
|
|
310
|
+
actorId: input.actorId,
|
|
311
|
+
action: current ? "shift_rule.update" : "shift_rule.create",
|
|
312
|
+
entity: "shift_rule",
|
|
313
|
+
entityId: ruleId,
|
|
314
|
+
detail: current ? { changes } : { userId, dayOfWeek, fromMin: next.fromMin, toMin: next.toMin, surcharge },
|
|
315
|
+
ip: (_a2 = input.actorIp) != null ? _a2 : null
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
run();
|
|
319
|
+
const saved = readShift(ruleId);
|
|
320
|
+
if (!saved) {
|
|
321
|
+
throw new Error(`shift rule ${ruleId} disappeared right after the write`);
|
|
322
|
+
}
|
|
323
|
+
return saved;
|
|
324
|
+
},
|
|
325
|
+
removeShiftRule(input) {
|
|
326
|
+
var _a;
|
|
327
|
+
const current = readShift(input.id);
|
|
328
|
+
if (!current) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
const now = (_a = input.now) != null ? _a : Math.floor(Date.now() / 1e3);
|
|
332
|
+
const run = db.transaction(() => {
|
|
333
|
+
var _a2;
|
|
334
|
+
deleteShift.run(input.id);
|
|
335
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
336
|
+
atUtc: now,
|
|
337
|
+
actorId: input.actorId,
|
|
338
|
+
action: "shift_rule.delete",
|
|
339
|
+
entity: "shift_rule",
|
|
340
|
+
entityId: input.id,
|
|
341
|
+
detail: { userId: current.userId, dayOfWeek: current.dayOfWeek, surcharge: current.surcharge },
|
|
342
|
+
ip: (_a2 = input.actorIp) != null ? _a2 : null
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
run();
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
351
|
+
0 && (module.exports = {
|
|
352
|
+
createRulesRepository,
|
|
353
|
+
mapPauseRuleRow,
|
|
354
|
+
mapShiftRuleRow
|
|
355
|
+
});
|
|
356
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lib/db/repositories/rules.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Rule repository (`pause_rules`, `shift_rules`).\n *\n * Rules exist globally (`user_id IS NULL`) or for a single employee. For the break rules the user specific\n * rule replaces the global rule with the same `fromMin`, so an employee can deviate from the company\n * default. Shift rules describe surcharges per weekday and time window; they are pure master data for the\n * reports (they never change the balance).\n */\n\nimport type { Db } from \"../database\";\nimport { NotFoundError, ValidationError } from \"../../errors\";\nimport { diffFields, writeAuditLog } from \"./audit\";\n\n/** A graduated break rule. */\nexport interface PauseRuleRecord {\n\t/** Primary key */\n\tid: number;\n\t/** Owner, `null` for the company default */\n\tuserId: number | null;\n\t/** Rule applies from this pair duration in minutes (inclusive) */\n\tfromMin: number;\n\t/** Rule applies below this pair duration in minutes (exclusive), `null` = open end */\n\ttoMin: number | null;\n\t/** Minutes deducted from the pair */\n\tpauseMin: number;\n\t/** Inactive rules are ignored but keep their history */\n\tisActive: boolean;\n}\n\n/** A surcharge rule for a shift window. */\nexport interface ShiftRuleRecord {\n\t/** Primary key */\n\tid: number;\n\t/** Owner, `null` for the company default */\n\tuserId: number | null;\n\t/** Weekday the rule applies to (0 = Sunday), `null` = every day */\n\tdayOfWeek: number | null;\n\t/** Start of the window in minutes since midnight, `null` = open start */\n\tfromMin: number | null;\n\t/** End of the window in minutes since midnight, `null` = open end */\n\ttoMin: number | null;\n\t/** Surcharge in percent */\n\tsurcharge: number;\n\t/** Inactive rules are ignored but keep their history */\n\tisActive: boolean;\n}\n\n/** Input for creating or changing a break rule. */\nexport interface SavePauseRuleInput {\n\t/** Id of an existing rule, omitted when a new rule is created */\n\tid?: number;\n\t/** Owner, `null` for the company default */\n\tuserId?: number | null;\n\t/** Rule applies from this pair duration in minutes (inclusive) */\n\tfromMin: number;\n\t/** Rule applies below this pair duration in minutes (exclusive) */\n\ttoMin?: number | null;\n\t/** Minutes deducted from the pair */\n\tpauseMin: number;\n\t/** `false` disables the rule without deleting it */\n\tisActive?: boolean;\n\t/** Who changes the rule */\n\tactorId: number;\n\t/** Client IP address of the actor */\n\tactorIp?: string | null;\n\t/** Instant of the change, defaults to now */\n\tnow?: number;\n}\n\n/** Input for creating or changing a surcharge rule. */\nexport interface SaveShiftRuleInput {\n\t/** Id of an existing rule, omitted when a new rule is created */\n\tid?: number;\n\t/** Owner, `null` for the company default */\n\tuserId?: number | null;\n\t/** Weekday (0 = Sunday), `null` for every day */\n\tdayOfWeek?: number | null;\n\t/** Start of the window in minutes since midnight */\n\tfromMin?: number | null;\n\t/** End of the window in minutes since midnight */\n\ttoMin?: number | null;\n\t/** Surcharge in percent */\n\tsurcharge: number;\n\t/** `false` disables the rule without deleting it */\n\tisActive?: boolean;\n\t/** Who changes the rule */\n\tactorId: number;\n\t/** Client IP address of the actor */\n\tactorIp?: string | null;\n\t/** Instant of the change, defaults to now */\n\tnow?: number;\n}\n\n/** Rule storage operations. */\nexport interface RulesRepository {\n\t/** Effective break rules for an employee (user specific rules win), sorted by `fromMin` */\n\tpauseRules(userId?: number | null, options?: { includeInactive?: boolean }): PauseRuleRecord[];\n\t/** All break rules of one scope without merging */\n\tlistPauseRules(input?: { userId?: number | null; includeInactive?: boolean }): PauseRuleRecord[];\n\t/** Creates or updates a break rule */\n\tsavePauseRule(input: SavePauseRuleInput): PauseRuleRecord;\n\t/** Deletes a break rule */\n\tremovePauseRule(input: { id: number; actorId: number; actorIp?: string | null; now?: number }): boolean;\n\t/** Effective surcharge rules for an employee */\n\tshiftRules(userId?: number | null, options?: { includeInactive?: boolean }): ShiftRuleRecord[];\n\t/** Creates or updates a surcharge rule */\n\tsaveShiftRule(input: SaveShiftRuleInput): ShiftRuleRecord;\n\t/** Deletes a surcharge rule */\n\tremoveShiftRule(input: { id: number; actorId: number; actorIp?: string | null; now?: number }): boolean;\n}\n\ninterface PauseRuleRow {\n\tid: number;\n\tuser_id: number | null;\n\tfrom_min: number;\n\tto_min: number | null;\n\tpause_min: number;\n\tis_active: number;\n}\n\ninterface ShiftRuleRow {\n\tid: number;\n\tuser_id: number | null;\n\tday_of_week: number | null;\n\tfrom_min: number | null;\n\tto_min: number | null;\n\tsurcharge: number;\n\tis_active: number;\n}\n\nconst PAUSE_COLUMNS = \"id, user_id, from_min, to_min, pause_min, is_active\";\nconst SHIFT_COLUMNS = \"id, user_id, day_of_week, from_min, to_min, surcharge, is_active\";\n\n/** Field names of a break rule that are compared for the audit trail. */\nconst AUDITED_PAUSE_FIELDS: (keyof PauseRuleRecord)[] = [\"userId\", \"fromMin\", \"toMin\", \"pauseMin\", \"isActive\"];\n\n/** Field names of a surcharge rule that are compared for the audit trail. */\nconst AUDITED_SHIFT_FIELDS: (keyof ShiftRuleRecord)[] = [\n\t\"userId\",\n\t\"dayOfWeek\",\n\t\"fromMin\",\n\t\"toMin\",\n\t\"surcharge\",\n\t\"isActive\",\n];\n\n/**\n * Maps a database row to a break rule record.\n *\n * @param row - raw database row\n * @returns break rule record\n */\nexport function mapPauseRuleRow(row: PauseRuleRow): PauseRuleRecord {\n\treturn {\n\t\tid: row.id,\n\t\tuserId: row.user_id,\n\t\tfromMin: row.from_min,\n\t\ttoMin: row.to_min,\n\t\tpauseMin: row.pause_min,\n\t\tisActive: row.is_active !== 0,\n\t};\n}\n\n/**\n * Maps a database row to a surcharge rule record.\n *\n * @param row - raw database row\n * @returns surcharge rule record\n */\nexport function mapShiftRuleRow(row: ShiftRuleRow): ShiftRuleRecord {\n\treturn {\n\t\tid: row.id,\n\t\tuserId: row.user_id,\n\t\tdayOfWeek: row.day_of_week,\n\t\tfromMin: row.from_min,\n\t\ttoMin: row.to_min,\n\t\tsurcharge: row.surcharge,\n\t\tisActive: row.is_active !== 0,\n\t};\n}\n\n/**\n * Validates the duration window of a break rule.\n *\n * @param fromMin - lower bound in minutes\n * @param toMin - upper bound in minutes or `null`\n * @returns the validated values\n */\nfunction requirePauseWindow(fromMin: number, toMin: number | null): { fromMin: number; toMin: number | null } {\n\tif (!Number.isInteger(fromMin) || fromMin < 0) {\n\t\tthrow new ValidationError(`fromMin must be a whole number of minutes, 0 or more (got ${fromMin})`);\n\t}\n\tif (toMin !== null) {\n\t\tif (!Number.isInteger(toMin) || toMin <= fromMin) {\n\t\t\tthrow new ValidationError(`toMin must be greater than fromMin (got ${toMin} and ${fromMin})`);\n\t\t}\n\t}\n\treturn { fromMin, toMin };\n}\n\n/**\n * Validates the window of a surcharge rule.\n *\n * @param fromMin - start of the window in minutes or `null`\n * @param toMin - end of the window in minutes or `null`\n * @returns the validated values\n */\nfunction requireShiftWindow(\n\tfromMin: number | null,\n\ttoMin: number | null,\n): { fromMin: number | null; toMin: number | null } {\n\tconst check = (value: number | null, label: string): void => {\n\t\tif (value !== null && (!Number.isInteger(value) || value < 0 || value > 1440)) {\n\t\t\tthrow new ValidationError(`${label} must be between 0 and 1440 minutes (got ${value})`);\n\t\t}\n\t};\n\tcheck(fromMin, \"fromMin\");\n\tcheck(toMin, \"toMin\");\n\tif (fromMin !== null && toMin !== null && toMin <= fromMin) {\n\t\tthrow new ValidationError(`toMin must be greater than fromMin (got ${toMin} and ${fromMin})`);\n\t}\n\treturn { fromMin, toMin };\n}\n\n/**\n * Validates a surcharge percentage.\n *\n * @param surcharge - surcharge in percent\n * @returns the validated value\n */\nfunction requireSurcharge(surcharge: number): number {\n\tif (!Number.isFinite(surcharge) || surcharge < 0) {\n\t\tthrow new ValidationError(`surcharge must be 0 or more (got ${surcharge})`);\n\t}\n\treturn surcharge;\n}\n\n/**\n * Creates the rule repository.\n *\n * @param db - open database handle\n * @returns repository instance\n */\nexport function createRulesRepository(db: Db): RulesRepository {\n\tconst selectPauseById = db.prepare(`SELECT ${PAUSE_COLUMNS} FROM pause_rules WHERE id = ?`);\n\tconst selectPauseScope = db.prepare(\n\t\t`SELECT ${PAUSE_COLUMNS} FROM pause_rules\n\t\t WHERE (user_id IS NULL OR user_id = ?)\n\t\t ORDER BY from_min, id`,\n\t);\n\tconst insertPause = db.prepare(\n\t\t\"INSERT INTO pause_rules (user_id, from_min, to_min, pause_min, is_active) VALUES (?, ?, ?, ?, ?)\",\n\t);\n\tconst updatePause = db.prepare(\n\t\t\"UPDATE pause_rules SET user_id = ?, from_min = ?, to_min = ?, pause_min = ?, is_active = ? WHERE id = ?\",\n\t);\n\tconst deletePause = db.prepare(\"DELETE FROM pause_rules WHERE id = ?\");\n\n\tconst selectShiftById = db.prepare(`SELECT ${SHIFT_COLUMNS} FROM shift_rules WHERE id = ?`);\n\tconst selectShiftScope = db.prepare(\n\t\t`SELECT ${SHIFT_COLUMNS} FROM shift_rules\n\t\t WHERE (user_id IS NULL OR user_id = ?)\n\t\t ORDER BY (day_of_week IS NULL), day_of_week, (from_min IS NULL), from_min, id`,\n\t);\n\tconst insertShift = db.prepare(\n\t\t`INSERT INTO shift_rules (user_id, day_of_week, from_min, to_min, surcharge, is_active)\n\t\t VALUES (?, ?, ?, ?, ?, ?)`,\n\t);\n\tconst updateShift = db.prepare(\n\t\t`UPDATE shift_rules SET user_id = ?, day_of_week = ?, from_min = ?, to_min = ?, surcharge = ?, is_active = ?\n\t\t WHERE id = ?`,\n\t);\n\tconst deleteShift = db.prepare(\"DELETE FROM shift_rules WHERE id = ?\");\n\n\tconst readPause = (id: number): PauseRuleRecord | null => {\n\t\tconst row = selectPauseById.get(id) as PauseRuleRow | undefined;\n\t\treturn row ? mapPauseRuleRow(row) : null;\n\t};\n\n\tconst readShift = (id: number): ShiftRuleRecord | null => {\n\t\tconst row = selectShiftById.get(id) as ShiftRuleRow | undefined;\n\t\treturn row ? mapShiftRuleRow(row) : null;\n\t};\n\n\t/**\n\t * Reads the break rules of one scope.\n\t *\n\t * @param userId - owner of the user specific rules\n\t * @param includeInactive - true to keep disabled rules\n\t * @returns rules of the scope\n\t */\n\tconst scopePauseRules = (userId: number | null, includeInactive: boolean): PauseRuleRecord[] =>\n\t\t(selectPauseScope.all(userId) as PauseRuleRow[])\n\t\t\t.map(mapPauseRuleRow)\n\t\t\t.filter(rule => (userId === null ? rule.userId === null : rule.userId === userId))\n\t\t\t.filter(rule => includeInactive || rule.isActive);\n\n\treturn {\n\t\tpauseRules(userId?: number | null, options?: { includeInactive?: boolean }): PauseRuleRecord[] {\n\t\t\tconst includeInactive = options?.includeInactive === true;\n\t\t\tconst owner = userId ?? null;\n\t\t\tconst merged = new Map<number, PauseRuleRecord>();\n\n\t\t\t// company defaults first, then the user specific rules replace them by `fromMin`\n\t\t\tfor (const rule of scopePauseRules(null, includeInactive)) {\n\t\t\t\tmerged.set(rule.fromMin, rule);\n\t\t\t}\n\t\t\tif (owner !== null) {\n\t\t\t\tfor (const rule of scopePauseRules(owner, includeInactive)) {\n\t\t\t\t\tmerged.set(rule.fromMin, rule);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn [...merged.values()].sort((a, b) => a.fromMin - b.fromMin || a.id - b.id);\n\t\t},\n\n\t\tlistPauseRules(input?: { userId?: number | null; includeInactive?: boolean }): PauseRuleRecord[] {\n\t\t\treturn scopePauseRules(input?.userId ?? null, input?.includeInactive === true);\n\t\t},\n\n\t\tsavePauseRule(input: SavePauseRuleInput): PauseRuleRecord {\n\t\t\tconst window = requirePauseWindow(input.fromMin, input.toMin ?? null);\n\t\t\tconst pauseMin = Math.round(input.pauseMin);\n\t\t\tif (!Number.isFinite(input.pauseMin) || pauseMin <= 0) {\n\t\t\t\tthrow new ValidationError(`pauseMin must be greater than 0 (got ${input.pauseMin})`);\n\t\t\t}\n\t\t\tconst userId = input.userId ?? null;\n\t\t\tconst isActive = input.isActive !== false;\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst current = input.id === undefined ? null : readPause(input.id);\n\t\t\tif (input.id !== undefined && !current) {\n\t\t\t\tthrow new NotFoundError(`pause rule ${input.id} not found`);\n\t\t\t}\n\n\t\t\tconst next: PauseRuleRecord = {\n\t\t\t\tid: current?.id ?? 0,\n\t\t\t\tuserId,\n\t\t\t\tfromMin: window.fromMin,\n\t\t\t\ttoMin: window.toMin,\n\t\t\t\tpauseMin,\n\t\t\t\tisActive,\n\t\t\t};\n\t\t\tconst changes = current\n\t\t\t\t? diffFields(\n\t\t\t\t\t\tcurrent as unknown as Record<string, unknown>,\n\t\t\t\t\t\tnext as unknown as Record<string, unknown>,\n\t\t\t\t\t\tAUDITED_PAUSE_FIELDS as unknown as (keyof Record<string, unknown>)[],\n\t\t\t\t\t)\n\t\t\t\t: null;\n\t\t\tif (current && Object.keys(changes ?? {}).length === 0) {\n\t\t\t\treturn current;\n\t\t\t}\n\n\t\t\tlet ruleId = current?.id ?? 0;\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tif (current) {\n\t\t\t\t\tupdatePause.run(userId, next.fromMin, next.toMin, next.pauseMin, isActive ? 1 : 0, current.id);\n\t\t\t\t} else {\n\t\t\t\t\truleId = Number(\n\t\t\t\t\t\tinsertPause.run(userId, next.fromMin, next.toMin, next.pauseMin, isActive ? 1 : 0)\n\t\t\t\t\t\t\t.lastInsertRowid,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId,\n\t\t\t\t\taction: current ? \"pause_rule.update\" : \"pause_rule.create\",\n\t\t\t\t\tentity: \"pause_rule\",\n\t\t\t\t\tentityId: ruleId,\n\t\t\t\t\tdetail: current\n\t\t\t\t\t\t? { changes }\n\t\t\t\t\t\t: { userId, fromMin: next.fromMin, toMin: next.toMin, pauseMin: next.pauseMin },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\n\t\t\tconst saved = readPause(ruleId);\n\t\t\tif (!saved) {\n\t\t\t\tthrow new Error(`pause rule ${ruleId} disappeared right after the write`);\n\t\t\t}\n\t\t\treturn saved;\n\t\t},\n\n\t\tremovePauseRule(input: { id: number; actorId: number; actorIp?: string | null; now?: number }): boolean {\n\t\t\tconst current = readPause(input.id);\n\t\t\tif (!current) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tdeletePause.run(input.id);\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId,\n\t\t\t\t\taction: \"pause_rule.delete\",\n\t\t\t\t\tentity: \"pause_rule\",\n\t\t\t\t\tentityId: input.id,\n\t\t\t\t\tdetail: { userId: current.userId, fromMin: current.fromMin, pauseMin: current.pauseMin },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\t\t\treturn true;\n\t\t},\n\n\t\tshiftRules(userId?: number | null, options?: { includeInactive?: boolean }): ShiftRuleRecord[] {\n\t\t\tconst includeInactive = options?.includeInactive === true;\n\t\t\tconst owner = userId ?? null;\n\t\t\tconst merged = new Map<string, ShiftRuleRecord>();\n\t\t\tconst keyOf = (rule: ShiftRuleRecord): string => `${rule.dayOfWeek ?? \"*\"}:${rule.fromMin ?? \"*\"}`;\n\n\t\t\tconst scope = (scopeUserId: number | null): ShiftRuleRecord[] =>\n\t\t\t\t(selectShiftScope.all(scopeUserId) as ShiftRuleRow[])\n\t\t\t\t\t.map(mapShiftRuleRow)\n\t\t\t\t\t.filter(rule => (scopeUserId === null ? rule.userId === null : rule.userId === scopeUserId))\n\t\t\t\t\t.filter(rule => includeInactive || rule.isActive);\n\n\t\t\tfor (const rule of scope(null)) {\n\t\t\t\tmerged.set(keyOf(rule), rule);\n\t\t\t}\n\t\t\tif (owner !== null) {\n\t\t\t\tfor (const rule of scope(owner)) {\n\t\t\t\t\tmerged.set(keyOf(rule), rule);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn [...merged.values()].sort(\n\t\t\t\t(a, b) =>\n\t\t\t\t\t(a.dayOfWeek ?? 7) - (b.dayOfWeek ?? 7) || (a.fromMin ?? -1) - (b.fromMin ?? -1) || a.id - b.id,\n\t\t\t);\n\t\t},\n\n\t\tsaveShiftRule(input: SaveShiftRuleInput): ShiftRuleRecord {\n\t\t\tconst dayOfWeek = input.dayOfWeek ?? null;\n\t\t\tif (dayOfWeek !== null && (!Number.isInteger(dayOfWeek) || dayOfWeek < 0 || dayOfWeek > 6)) {\n\t\t\t\tthrow new ValidationError(`dayOfWeek must be between 0 and 6 (got ${dayOfWeek})`);\n\t\t\t}\n\t\t\tconst window = requireShiftWindow(input.fromMin ?? null, input.toMin ?? null);\n\t\t\tconst surcharge = requireSurcharge(input.surcharge);\n\t\t\tconst userId = input.userId ?? null;\n\t\t\tconst isActive = input.isActive !== false;\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst current = input.id === undefined ? null : readShift(input.id);\n\t\t\tif (input.id !== undefined && !current) {\n\t\t\t\tthrow new NotFoundError(`shift rule ${input.id} not found`);\n\t\t\t}\n\n\t\t\tconst next: ShiftRuleRecord = {\n\t\t\t\tid: current?.id ?? 0,\n\t\t\t\tuserId,\n\t\t\t\tdayOfWeek,\n\t\t\t\tfromMin: window.fromMin,\n\t\t\t\ttoMin: window.toMin,\n\t\t\t\tsurcharge,\n\t\t\t\tisActive,\n\t\t\t};\n\t\t\tconst changes = current\n\t\t\t\t? diffFields(\n\t\t\t\t\t\tcurrent as unknown as Record<string, unknown>,\n\t\t\t\t\t\tnext as unknown as Record<string, unknown>,\n\t\t\t\t\t\tAUDITED_SHIFT_FIELDS as unknown as (keyof Record<string, unknown>)[],\n\t\t\t\t\t)\n\t\t\t\t: null;\n\t\t\tif (current && Object.keys(changes ?? {}).length === 0) {\n\t\t\t\treturn current;\n\t\t\t}\n\n\t\t\tlet ruleId = current?.id ?? 0;\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tif (current) {\n\t\t\t\t\tupdateShift.run(\n\t\t\t\t\t\tuserId,\n\t\t\t\t\t\tnext.dayOfWeek,\n\t\t\t\t\t\tnext.fromMin,\n\t\t\t\t\t\tnext.toMin,\n\t\t\t\t\t\tnext.surcharge,\n\t\t\t\t\t\tisActive ? 1 : 0,\n\t\t\t\t\t\tcurrent.id,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\truleId = Number(\n\t\t\t\t\t\tinsertShift.run(\n\t\t\t\t\t\t\tuserId,\n\t\t\t\t\t\t\tnext.dayOfWeek,\n\t\t\t\t\t\t\tnext.fromMin,\n\t\t\t\t\t\t\tnext.toMin,\n\t\t\t\t\t\t\tnext.surcharge,\n\t\t\t\t\t\t\tisActive ? 1 : 0,\n\t\t\t\t\t\t).lastInsertRowid,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId,\n\t\t\t\t\taction: current ? \"shift_rule.update\" : \"shift_rule.create\",\n\t\t\t\t\tentity: \"shift_rule\",\n\t\t\t\t\tentityId: ruleId,\n\t\t\t\t\tdetail: current\n\t\t\t\t\t\t? { changes }\n\t\t\t\t\t\t: { userId, dayOfWeek, fromMin: next.fromMin, toMin: next.toMin, surcharge },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\n\t\t\tconst saved = readShift(ruleId);\n\t\t\tif (!saved) {\n\t\t\t\tthrow new Error(`shift rule ${ruleId} disappeared right after the write`);\n\t\t\t}\n\t\t\treturn saved;\n\t\t},\n\n\t\tremoveShiftRule(input: { id: number; actorId: number; actorIp?: string | null; now?: number }): boolean {\n\t\t\tconst current = readShift(input.id);\n\t\t\tif (!current) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tdeleteShift.run(input.id);\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId,\n\t\t\t\t\taction: \"shift_rule.delete\",\n\t\t\t\t\tentity: \"shift_rule\",\n\t\t\t\t\tentityId: input.id,\n\t\t\t\t\tdetail: { userId: current.userId, dayOfWeek: current.dayOfWeek, surcharge: current.surcharge },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\t\t\treturn true;\n\t\t},\n\t};\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["_a"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var settings_exports = {};
|
|
20
|
+
__export(settings_exports, {
|
|
21
|
+
createSettingsRepository: () => createSettingsRepository
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(settings_exports);
|
|
24
|
+
var import_audit = require("./audit");
|
|
25
|
+
function createSettingsRepository(db) {
|
|
26
|
+
const selectValue = db.prepare("SELECT value FROM app_settings WHERE key = ?");
|
|
27
|
+
const selectAll = db.prepare("SELECT key, value FROM app_settings");
|
|
28
|
+
const upsert = db.prepare(
|
|
29
|
+
`INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, ?)
|
|
30
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
|
|
31
|
+
);
|
|
32
|
+
const read = (key) => {
|
|
33
|
+
const row = selectValue.get(key);
|
|
34
|
+
return row ? row.value : null;
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
all() {
|
|
38
|
+
const rows = selectAll.all();
|
|
39
|
+
return Object.fromEntries(rows.map((row) => [row.key, row.value]));
|
|
40
|
+
},
|
|
41
|
+
get: read,
|
|
42
|
+
getNumber(key, fallback) {
|
|
43
|
+
const raw = read(key);
|
|
44
|
+
if (raw === null) {
|
|
45
|
+
return fallback;
|
|
46
|
+
}
|
|
47
|
+
const parsed = Number(raw);
|
|
48
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
49
|
+
},
|
|
50
|
+
getBoolean(key, fallback) {
|
|
51
|
+
const raw = read(key);
|
|
52
|
+
if (raw === null) {
|
|
53
|
+
return fallback;
|
|
54
|
+
}
|
|
55
|
+
return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
|
|
56
|
+
},
|
|
57
|
+
getJson(key, fallback) {
|
|
58
|
+
const raw = read(key);
|
|
59
|
+
if (raw === null) {
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(raw);
|
|
64
|
+
} catch {
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
set(key, value, actorId = null, now) {
|
|
69
|
+
const previous = read(key);
|
|
70
|
+
const serialised = typeof value === "string" ? value : JSON.stringify(value);
|
|
71
|
+
const atUtc = now != null ? now : Math.floor(Date.now() / 1e3);
|
|
72
|
+
const run = db.transaction(() => {
|
|
73
|
+
upsert.run(key, serialised, atUtc);
|
|
74
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
75
|
+
atUtc,
|
|
76
|
+
actorId,
|
|
77
|
+
action: "settings.set",
|
|
78
|
+
entity: "app_settings",
|
|
79
|
+
entityId: key,
|
|
80
|
+
detail: { old: previous, new: serialised }
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
run();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
88
|
+
0 && (module.exports = {
|
|
89
|
+
createSettingsRepository
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lib/db/repositories/settings.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Instance settings (`app_settings`), stored as key/value pairs.\n *\n * Seeds provide the defaults; this repository never overwrites a value that an administrator changed.\n */\n\nimport type { Db } from \"../database\";\nimport { writeAuditLog } from \"./audit\";\n\n/** Value that can be stored: strings are kept verbatim, everything else is JSON serialised. */\nexport type SettingValue = string | number | boolean | null | SettingValue[] | { [key: string]: SettingValue };\n\n/** Typed access to the instance settings. */\nexport interface SettingsRepository {\n\t/** All settings as string map */\n\tall(): Record<string, string>;\n\t/** Raw string value or `null` */\n\tget(key: string): string | null;\n\t/** Number value with fallback when missing or not numeric */\n\tgetNumber(key: string, fallback: number): number;\n\t/** Boolean value (`1`/`true`/`yes` are true) with fallback */\n\tgetBoolean(key: string, fallback: boolean): boolean;\n\t/** JSON value with fallback when missing or invalid */\n\tgetJson<T>(key: string, fallback: T): T;\n\t/** Stores a value (strings as-is, everything else JSON encoded) */\n\tset(key: string, value: SettingValue, actorId?: number | null, now?: number): void;\n}\n\n/**\n * Creates the settings repository.\n *\n * @param db - open database handle\n * @returns repository instance\n */\nexport function createSettingsRepository(db: Db): SettingsRepository {\n\tconst selectValue = db.prepare(\"SELECT value FROM app_settings WHERE key = ?\");\n\tconst selectAll = db.prepare(\"SELECT key, value FROM app_settings\");\n\tconst upsert = db.prepare(\n\t\t`INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, ?)\n\t\t ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n\t);\n\n\tconst read = (key: string): string | null => {\n\t\tconst row = selectValue.get(key) as { value: string } | undefined;\n\t\treturn row ? row.value : null;\n\t};\n\n\treturn {\n\t\tall(): Record<string, string> {\n\t\t\tconst rows = selectAll.all() as { key: string; value: string }[];\n\t\t\treturn Object.fromEntries(rows.map(row => [row.key, row.value]));\n\t\t},\n\n\t\tget: read,\n\n\t\tgetNumber(key: string, fallback: number): number {\n\t\t\tconst raw = read(key);\n\t\t\tif (raw === null) {\n\t\t\t\treturn fallback;\n\t\t\t}\n\t\t\tconst parsed = Number(raw);\n\t\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t\t},\n\n\t\tgetBoolean(key: string, fallback: boolean): boolean {\n\t\t\tconst raw = read(key);\n\t\t\tif (raw === null) {\n\t\t\t\treturn fallback;\n\t\t\t}\n\t\t\treturn [\"1\", \"true\", \"yes\", \"on\"].includes(raw.trim().toLowerCase());\n\t\t},\n\n\t\tgetJson<T>(key: string, fallback: T): T {\n\t\t\tconst raw = read(key);\n\t\t\tif (raw === null) {\n\t\t\t\treturn fallback;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(raw) as T;\n\t\t\t} catch {\n\t\t\t\treturn fallback;\n\t\t\t}\n\t\t},\n\n\t\tset(key: string, value: SettingValue, actorId: number | null = null, now?: number): void {\n\t\t\tconst previous = read(key);\n\t\t\tconst serialised = typeof value === \"string\" ? value : JSON.stringify(value);\n\t\t\tconst atUtc = now ?? Math.floor(Date.now() / 1000);\n\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tupsert.run(key, serialised, atUtc);\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc,\n\t\t\t\t\tactorId,\n\t\t\t\t\taction: \"settings.set\",\n\t\t\t\t\tentity: \"app_settings\",\n\t\t\t\t\tentityId: key,\n\t\t\t\t\tdetail: { old: previous, new: serialised },\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\t\t},\n\t};\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,mBAA8B;AA2BvB,SAAS,yBAAyB,IAA4B;AACpE,QAAM,cAAc,GAAG,QAAQ,8CAA8C;AAC7E,QAAM,YAAY,GAAG,QAAQ,qCAAqC;AAClE,QAAM,SAAS,GAAG;AAAA,IACjB;AAAA;AAAA,EAED;AAEA,QAAM,OAAO,CAAC,QAA+B;AAC5C,UAAM,MAAM,YAAY,IAAI,GAAG;AAC/B,WAAO,MAAM,IAAI,QAAQ;AAAA,EAC1B;AAEA,SAAO;AAAA,IACN,MAA8B;AAC7B,YAAM,OAAO,UAAU,IAAI;AAC3B,aAAO,OAAO,YAAY,KAAK,IAAI,SAAO,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC;AAAA,IAChE;AAAA,IAEA,KAAK;AAAA,IAEL,UAAU,KAAa,UAA0B;AAChD,YAAM,MAAM,KAAK,GAAG;AACpB,UAAI,QAAQ,MAAM;AACjB,eAAO;AAAA,MACR;AACA,YAAM,SAAS,OAAO,GAAG;AACzB,aAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAAA,IAC3C;AAAA,IAEA,WAAW,KAAa,UAA4B;AACnD,YAAM,MAAM,KAAK,GAAG;AACpB,UAAI,QAAQ,MAAM;AACjB,eAAO;AAAA,MACR;AACA,aAAO,CAAC,KAAK,QAAQ,OAAO,IAAI,EAAE,SAAS,IAAI,KAAK,EAAE,YAAY,CAAC;AAAA,IACpE;AAAA,IAEA,QAAW,KAAa,UAAgB;AACvC,YAAM,MAAM,KAAK,GAAG;AACpB,UAAI,QAAQ,MAAM;AACjB,eAAO;AAAA,MACR;AACA,UAAI;AACH,eAAO,KAAK,MAAM,GAAG;AAAA,MACtB,QAAQ;AACP,eAAO;AAAA,MACR;AAAA,IACD;AAAA,IAEA,IAAI,KAAa,OAAqB,UAAyB,MAAM,KAAoB;AACxF,YAAM,WAAW,KAAK,GAAG;AACzB,YAAM,aAAa,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK;AAC3E,YAAM,QAAQ,oBAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAEjD,YAAM,MAAM,GAAG,YAAY,MAAY;AACtC,eAAO,IAAI,KAAK,YAAY,KAAK;AACjC,wCAAc,IAAI;AAAA,UACjB;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,QAAQ,EAAE,KAAK,UAAU,KAAK,WAAW;AAAA,QAC1C,CAAC;AAAA,MACF,CAAC;AACD,UAAI;AAAA,IACL;AAAA,EACD;AACD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|