iobroker.time-tracker 0.7.9 → 0.7.11

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/README.md CHANGED
@@ -260,13 +260,16 @@ link with the instance token:
260
260
  | **Data** | `calendar.absences` — the same days as JSON (`login`, `name`, `from`, `to`, `code`, `type`, `portion`, `approval`, `status`, `note`) | scripts, Blockly, VIS |
261
261
  | **When** | `calendar.updatedAt` | to see how current the three above are |
262
262
 
263
- The file lives in the `files` folder of the instance data, because the instance folder next to it can only be read by
264
- the adapter itself. The `ical` adapter points at the **path** from `calendar.feedFile`.
263
+ The file lives below `files/time-tracker.<n>/`, because the instance folder next to it can only be read by the adapter
264
+ itself. It is written through the **file API** of ioBroker (`writeFileAsync`), so the file manager knows the folder and
265
+ its `INFO.txt` from the very first start — a folder created behind that API stays an unknown folder. The `ical`
266
+ adapter points at the **path** from `calendar.feedFile`.
265
267
 
266
- The file server of a `web` instance is **not** a way to that file: the written calendar is not offered there as a
267
- download. For a browser, a calendar app or a script use `calendar.feedUrl`, the link with the instance token — the
268
- technical detail of why the file server does not work is written down in D11 of
269
- [`docs/entscheidungen.md`](docs/entscheidungen.md).
268
+ The file server of a `web` instance is **not** a promised way to that file yet: while the folder came from `fs`, it
269
+ answered with an empty archive. The calendar is written through the file API now, and whether a `web` instance hands it
270
+ out after that is the open acceptance case **T28** in [`docs/testplan.md`](docs/testplan.md). Until it is answered, a
271
+ browser, a calendar app or a script uses `calendar.feedUrl`, the link with the instance token — the background stands in
272
+ D11 of [`docs/entscheidungen.md`](docs/entscheidungen.md).
270
273
 
271
274
  The window is a year back and to the end of next year. The link of the **company** only exists after somebody asked
272
275
  for it: write `true` to `commands.rotateCalendarToken`. The first call creates the token, every further one replaces
@@ -421,6 +424,18 @@ local SQLite file, access is role-based, and every correction is written to an a
421
424
 
422
425
  ### **WORK IN PROGRESS**
423
426
 
427
+ ### 0.7.11 (2026-09-27)
428
+
429
+ - (Alex) fix: the calendar file goes through the **file API** of the adapter now — the folder
430
+ `files/time-tracker.<n>/` is known to the file manager and exists from the first start, because a small `INFO.txt`
431
+ keeps it in place; the state `calendar.feedFile` still carries the path for the `ical` adapter
432
+
433
+ ### 0.7.10 (2026-09-26)
434
+
435
+ - (Alex) change: the state `calendar.feedFile` is called “Path of the calendar file” now — in all eleven languages.
436
+ The old name said “calendar file” and let the value look like a download, while it is the **path** that the `ical`
437
+ adapter reads; browsers and calendar apps use the link `calendar.feedUrl`
438
+
424
439
  ### 0.7.9 (2026-09-26)
425
440
 
426
441
  - (Alex) fix: “Feiertag hinzufügen” stayed grey while the day had to be typed as text — it comes from the date field of
@@ -453,20 +468,6 @@ local SQLite file, access is role-based, and every correction is written to an a
453
468
  - (Alex) change: the rule dialog keeps “Aktiv” beside “Wiederholung”, and the validity dates (“Gültig ab”/“Gültig
454
469
  bis”) stand in a row of their own below it — the two date fields no longer wrap around the button
455
470
 
456
- ### 0.7.6 (2026-09-25)
457
-
458
- - (Alex) new: an automatic rule can be limited to a period. The rule dialog knows “Valid from” and “Valid until” as
459
- dates — an empty field means “from now on” or “without an end” — and the adapter compares them with the local
460
- calendar day of the employee, exactly like the weekdays; a holiday stand-in, a seasonal worker or a project phase
461
- therefore ends by itself
462
- - (Alex) change: the list of the last runs below the rules shows the five most recent entries instead of twenty
463
-
464
- ### 0.7.5 (2026-09-24)
465
-
466
- - (Alex) change: the sources of the web app moved from `src-pwa/` to `src-www/`. The repository checker reads every folder it does not know by name and had reported five `W5042` for react, MUI and i18next - packages only the web app needs and that an adapter installation should not carry; the new name is on its list, so the warnings are gone. Nothing changes for a running installation: the built app is still served from `www/`
467
- - (Alex) fix: the periodic refresh and the hourly backup check cannot overlap themselves any more. A tick that finds the previous run still busy is skipped and logged, so a slow run (a big database, a slow disk) can no longer pile a second one on top of itself
468
- - (Alex) change: `onStateChange` no longer writes a debug line for every state of the instance that it does not handle - on a busy system that filled the log at log level `debug` for nothing
469
-
470
471
  Older entries are kept in [`CHANGELOG_OLD.md`](CHANGELOG_OLD.md).
471
472
 
472
473
  ## Provenance
@@ -0,0 +1,57 @@
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 calendarFile_exports = {};
20
+ __export(calendarFile_exports, {
21
+ CALENDAR_FILE_NAME: () => CALENDAR_FILE_NAME,
22
+ CALENDAR_NOTE_FILE_NAME: () => CALENDAR_NOTE_FILE_NAME,
23
+ calendarNote: () => calendarNote,
24
+ ensureCalendarFolder: () => ensureCalendarFolder,
25
+ writeCalendarFile: () => writeCalendarFile
26
+ });
27
+ module.exports = __toCommonJS(calendarFile_exports);
28
+ const CALENDAR_FILE_NAME = "calendar.ics";
29
+ const CALENDAR_NOTE_FILE_NAME = "INFO.txt";
30
+ function calendarNote() {
31
+ return [
32
+ "Files of the ioBroker adapter time-tracker.",
33
+ "",
34
+ `${CALENDAR_FILE_NAME} is the company calendar. The adapter rewrites it on every change and every five`,
35
+ "minutes \u2014 please do not edit or delete it.",
36
+ "The path stands in the state calendar.feedFile, the subscription link in calendar.feedUrl."
37
+ ].join("\n");
38
+ }
39
+ async function ensureCalendarFolder(port, namespace) {
40
+ if (await port.fileExistsAsync(namespace, CALENDAR_NOTE_FILE_NAME)) {
41
+ return false;
42
+ }
43
+ await port.writeFileAsync(namespace, CALENDAR_NOTE_FILE_NAME, calendarNote());
44
+ return true;
45
+ }
46
+ async function writeCalendarFile(port, namespace, document) {
47
+ await port.writeFileAsync(namespace, CALENDAR_FILE_NAME, document);
48
+ }
49
+ // Annotate the CommonJS export names for ESM import in node:
50
+ 0 && (module.exports = {
51
+ CALENDAR_FILE_NAME,
52
+ CALENDAR_NOTE_FILE_NAME,
53
+ calendarNote,
54
+ ensureCalendarFolder,
55
+ writeCalendarFile
56
+ });
57
+ //# sourceMappingURL=calendarFile.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/adapter/calendarFile.ts"],
4
+ "sourcesContent": ["/**\n * The calendar file below `files/`.\n *\n * ioBroker keeps the files of an instance below `<iobroker-data>/files/<namespace>/`. That area is what the file\n * manager of the admin UI shows and what a `web` instance serves \u2014 but only what was created through the **file API**\n * of the adapter is known there: that call registers the folder as well. A folder made with `fs.mkdir` stays a folder\n * nobody knows about (D11). The calendar is therefore written with `writeFileAsync`, and a small note file makes sure\n * the folder exists before the first calendar run \u2014 a new instance has an empty folder and no calendar for a while.\n *\n * The logic works on a tiny port interface (`CalendarFilePort`) \u2014 the adapter instance in production, a recorder in\n * the tests.\n */\n\n/** Subset of the adapter file API this module needs. */\nexport interface CalendarFilePort {\n\t/**\n\t * Checks whether a file of the instance exists.\n\t *\n\t * @param adapter - namespace of the instance (`time-tracker.0`)\n\t * @param path - file name below the `files` folder of the instance\n\t */\n\tfileExistsAsync(adapter: string, path: string): Promise<boolean> | boolean;\n\t/**\n\t * Writes a file into the `files` folder of the instance.\n\t *\n\t * @param adapter - namespace of the instance (`time-tracker.0`)\n\t * @param path - file name below the `files` folder of the instance\n\t * @param data - content of the file\n\t */\n\twriteFileAsync(adapter: string, path: string, data: string): Promise<void> | void;\n}\n\n/** Name of the calendar file below `files/<namespace>/`. */\nexport const CALENDAR_FILE_NAME = \"calendar.ics\";\n\n/** Name of the note that keeps the folder visible in the file manager. */\nexport const CALENDAR_NOTE_FILE_NAME = \"INFO.txt\";\n\n/**\n * Text of the note file \u2014 what a person sees who opens the folder.\n *\n * @returns the content of `INFO.txt`\n */\nexport function calendarNote(): string {\n\treturn [\n\t\t\"Files of the ioBroker adapter time-tracker.\",\n\t\t\"\",\n\t\t`${CALENDAR_FILE_NAME} is the company calendar. The adapter rewrites it on every change and every five`,\n\t\t\"minutes \u2014 please do not edit or delete it.\",\n\t\t\"The path stands in the state calendar.feedFile, the subscription link in calendar.feedUrl.\",\n\t].join(\"\\n\");\n}\n\n/**\n * Makes sure the `files` folder of the instance exists.\n *\n * The folder comes into being through the file API, so the file manager and the web file server know it before the\n * first calendar was written. It runs on every start and writes only when the note is missing.\n *\n * @param port - adapter file API\n * @param namespace - namespace of the instance (`time-tracker.0`)\n * @returns true when the note had to be written\n */\nexport async function ensureCalendarFolder(port: CalendarFilePort, namespace: string): Promise<boolean> {\n\tif (await port.fileExistsAsync(namespace, CALENDAR_NOTE_FILE_NAME)) {\n\t\treturn false;\n\t}\n\tawait port.writeFileAsync(namespace, CALENDAR_NOTE_FILE_NAME, calendarNote());\n\treturn true;\n}\n\n/**\n * Writes the calendar of the company.\n *\n * @param port - adapter file API\n * @param namespace - namespace of the instance (`time-tracker.0`)\n * @param document - the complete iCalendar document\n */\nexport async function writeCalendarFile(port: CalendarFilePort, namespace: string, document: string): Promise<void> {\n\tawait port.writeFileAsync(namespace, CALENDAR_FILE_NAME, document);\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCO,MAAM,qBAAqB;AAG3B,MAAM,0BAA0B;AAOhC,SAAS,eAAuB;AACtC,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,GAAG,kBAAkB;AAAA,IACrB;AAAA,IACA;AAAA,EACD,EAAE,KAAK,IAAI;AACZ;AAYA,eAAsB,qBAAqB,MAAwB,WAAqC;AACvG,MAAI,MAAM,KAAK,gBAAgB,WAAW,uBAAuB,GAAG;AACnE,WAAO;AAAA,EACR;AACA,QAAM,KAAK,eAAe,WAAW,yBAAyB,aAAa,CAAC;AAC5E,SAAO;AACR;AASA,eAAsB,kBAAkB,MAAwB,WAAmB,UAAiC;AACnH,QAAM,KAAK,eAAe,WAAW,oBAAoB,QAAQ;AAClE;",
6
+ "names": []
7
+ }
@@ -540,19 +540,19 @@ const STATE_NAMES = {
540
540
  uk: "\u041F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F \u043F\u0456\u0434\u043F\u0438\u0441\u043A\u0438 \u043A\u043E\u043C\u043F\u0430\u043D\u0456\u0457",
541
541
  "zh-cn": "\u516C\u53F8\u7684\u8BA2\u9605\u94FE\u63A5"
542
542
  },
543
- /** The written calendar file */
543
+ /** Absolute path of the written calendar file (D11) */
544
544
  calendarFeedFile: {
545
- en: "Calendar file (.ics)",
546
- de: "Kalenderdatei (.ics)",
547
- ru: "\u0424\u0430\u0439\u043B \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F (.ics)",
548
- pt: "Ficheiro de calend\xE1rio (.ics)",
549
- nl: "Kalenderbestand (.ics)",
550
- fr: "Fichier de calendrier (.ics)",
551
- it: "File di calendario (.ics)",
552
- es: "Archivo de calendario (.ics)",
553
- pl: "Plik kalendarza (.ics)",
554
- uk: "\u0424\u0430\u0439\u043B \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F (.ics)",
555
- "zh-cn": "\u65E5\u5386\u6587\u4EF6\uFF08.ics\uFF09"
545
+ en: "Path of the calendar file (.ics)",
546
+ de: "Pfad der Kalenderdatei (.ics)",
547
+ ru: "\u041F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F (.ics)",
548
+ pt: "Caminho do ficheiro de calend\xE1rio (.ics)",
549
+ nl: "Pad van het kalenderbestand (.ics)",
550
+ fr: "Chemin du fichier de calendrier (.ics)",
551
+ it: "Percorso del file di calendario (.ics)",
552
+ es: "Ruta del archivo de calendario (.ics)",
553
+ pl: "\u015Acie\u017Cka pliku kalendarza (.ics)",
554
+ uk: "\u0428\u043B\u044F\u0445 \u0434\u043E \u0444\u0430\u0439\u043B\u0443 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F (.ics)",
555
+ "zh-cn": "\u65E5\u5386\u6587\u4EF6\u7684\u8DEF\u5F84\uFF08.ics\uFF09"
556
556
  },
557
557
  /** Instant the calendar was written */
558
558
  calendarUpdatedAt: {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/adapter/stateNames.ts"],
4
- "sourcesContent": ["/**\n * Names of the adapter's objects, in the eleven languages ioBroker recommends (checker `E6001`).\n *\n * `states.ts` creates the tree; this module is the single place that carries the texts, so every name has the same\n * set of languages. `states.test.ts` walks the whole tree and fails when a name misses one of the eleven keys \u2014\n * that is how the recommendation stays satisfied without anyone remembering it.\n */\n\n/** One object name: the eleven languages the ioBroker checker recommends. */\nexport interface StateName {\n\t/** English */\n\ten: string;\n\t/** German */\n\tde: string;\n\t/** Russian */\n\tru: string;\n\t/** Portuguese */\n\tpt: string;\n\t/** Dutch */\n\tnl: string;\n\t/** French */\n\tfr: string;\n\t/** Italian */\n\tit: string;\n\t/** Spanish */\n\tes: string;\n\t/** Polish */\n\tpl: string;\n\t/** Ukrainian */\n\tuk: string;\n\t/** Chinese (simplified) */\n\t\"zh-cn\": string;\n}\n\n/** Object names of the adapter, grouped like the state tree. */\nexport const STATE_NAMES = {\n\t/** Channel holding the employees */\n\tusersChannel: {\n\t\ten: \"Employees\",\n\t\tde: \"Mitarbeiter\",\n\t\tru: \"\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A\u0438\",\n\t\tpt: \"Funcion\u00E1rios\",\n\t\tnl: \"Medewerkers\",\n\t\tfr: \"Employ\u00E9s\",\n\t\tit: \"Dipendenti\",\n\t\tes: \"Empleados\",\n\t\tpl: \"Pracownicy\",\n\t\tuk: \"\u0421\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A\u0438\",\n\t\t\"zh-cn\": \"\u5458\u5DE5\",\n\t},\n\t/** Channel of one employee */\n\temployeeChannel: {\n\t\ten: \"Employee\",\n\t\tde: \"Mitarbeiter\",\n\t\tru: \"\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A\",\n\t\tpt: \"Funcion\u00E1rio\",\n\t\tnl: \"Medewerker\",\n\t\tfr: \"Employ\u00E9\",\n\t\tit: \"Dipendente\",\n\t\tes: \"Empleado\",\n\t\tpl: \"Pracownik\",\n\t\tuk: \"\u0421\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A\",\n\t\t\"zh-cn\": \"\u5458\u5DE5\",\n\t},\n\t/** Channel of the writable commands */\n\tcommandsChannel: {\n\t\ten: \"Commands\",\n\t\tde: \"Befehle\",\n\t\tru: \"\u041A\u043E\u043C\u0430\u043D\u0434\u044B\",\n\t\tpt: \"Comandos\",\n\t\tnl: \"Opdrachten\",\n\t\tfr: \"Commandes\",\n\t\tit: \"Comandi\",\n\t\tes: \"Comandos\",\n\t\tpl: \"Polecenia\",\n\t\tuk: \"\u041A\u043E\u043C\u0430\u043D\u0434\u0438\",\n\t\t\"zh-cn\": \"\u547D\u4EE4\",\n\t},\n\t/** Channel with the figures of the instance */\n\tinfoChannel: {\n\t\ten: \"Information\",\n\t\tde: \"Information\",\n\t\tru: \"\u0418\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F\",\n\t\tpt: \"Informa\u00E7\u00E3o\",\n\t\tnl: \"Informatie\",\n\t\tfr: \"Informations\",\n\t\tit: \"Informazioni\",\n\t\tes: \"Informaci\u00F3n\",\n\t\tpl: \"Informacje\",\n\t\tuk: \"\u0406\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0456\u044F\",\n\t\t\"zh-cn\": \"\u4FE1\u606F\",\n\t},\n\t/** Channel with the figures of the whole company */\n\tcompanyChannel: {\n\t\ten: \"Company\",\n\t\tde: \"Firma\",\n\t\tru: \"\u041A\u043E\u043C\u043F\u0430\u043D\u0438\u044F\",\n\t\tpt: \"Empresa\",\n\t\tnl: \"Bedrijf\",\n\t\tfr: \"Entreprise\",\n\t\tit: \"Azienda\",\n\t\tes: \"Empresa\",\n\t\tpl: \"Firma\",\n\t\tuk: \"\u041A\u043E\u043C\u043F\u0430\u043D\u0456\u044F\",\n\t\t\"zh-cn\": \"\u516C\u53F8\",\n\t},\n\t/** Channel with the newest event of the instance */\n\teventsChannel: {\n\t\ten: \"Events\",\n\t\tde: \"Ereignisse\",\n\t\tru: \"\u0421\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Eventos\",\n\t\tnl: \"Gebeurtenissen\",\n\t\tfr: \"\u00C9v\u00E9nements\",\n\t\tit: \"Eventi\",\n\t\tes: \"Eventos\",\n\t\tpl: \"Zdarzenia\",\n\t\tuk: \"\u041F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u4E8B\u4EF6\",\n\t},\n\t/** Shown name of the employee */\n\tdisplayName: {\n\t\ten: \"Name\",\n\t\tde: \"Name\",\n\t\tru: \"\u0418\u043C\u044F\",\n\t\tpt: \"Nome\",\n\t\tnl: \"Naam\",\n\t\tfr: \"Nom\",\n\t\tit: \"Nome\",\n\t\tes: \"Nombre\",\n\t\tpl: \"Nazwa\",\n\t\tuk: \"\u0406\u043C'\u044F\",\n\t\t\"zh-cn\": \"\u59D3\u540D\",\n\t},\n\t/** Writable presence of the employee (working time runs) */\n\tuserPresent: {\n\t\ten: \"Present (working time is running)\",\n\t\tde: \"Anwesenheit (Arbeitszeit l\u00E4uft)\",\n\t\tru: \"\u041F\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 (\u0440\u0430\u0431\u043E\u0447\u0435\u0435 \u0432\u0440\u0435\u043C\u044F \u0438\u0434\u0451\u0442)\",\n\t\tpt: \"Presente (o tempo de trabalho corre)\",\n\t\tnl: \"Aanwezig (werktijd loopt)\",\n\t\tfr: \"Pr\u00E9sent (temps de travail en cours)\",\n\t\tit: \"Presente (tempo di lavoro in corso)\",\n\t\tes: \"Presente (tiempo de trabajo en curso)\",\n\t\tpl: \"Obecno\u015B\u0107 (czas pracy trwa)\",\n\t\tuk: \"\u041F\u0440\u0438\u0441\u0443\u0442\u043D\u0456\u0441\u0442\u044C (\u0440\u043E\u0431\u043E\u0447\u0438\u0439 \u0447\u0430\u0441 \u0456\u0434\u0435)\",\n\t\t\"zh-cn\": \"\u5728\u5C97\uFF08\u5DE5\u4F5C\u65F6\u95F4\u8BA1\u65F6\u4E2D\uFF09\",\n\t},\n\t/** True while the last punch of the day has no counterpart */\n\thasOpenEntry: {\n\t\ten: \"Punched in\",\n\t\tde: \"Eingestempelt\",\n\t\tru: \"\u041F\u0440\u0438\u0445\u043E\u0434 \u043E\u0442\u043C\u0435\u0447\u0435\u043D\",\n\t\tpt: \"Entrada registada\",\n\t\tnl: \"Ingeklokt\",\n\t\tfr: \"Pointage d'arriv\u00E9e\",\n\t\tit: \"Entrata registrata\",\n\t\tes: \"Entrada registrada\",\n\t\tpl: \"Zarejestrowane wej\u015Bcie\",\n\t\tuk: \"\u0412\u0456\u0434\u043C\u0456\u0447\u0435\u043D\u043E \u043F\u0440\u0438\u0445\u0456\u0434\",\n\t\t\"zh-cn\": \"\u5DF2\u7B7E\u5230\",\n\t},\n\t/** Instant of the last punch */\n\tlastPunch: {\n\t\ten: \"Last punch\",\n\t\tde: \"Letzte Buchung\",\n\t\tru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0442\u043C\u0435\u0442\u043A\u0430\",\n\t\tpt: \"\u00DAltimo registo\",\n\t\tnl: \"Laatste stempeling\",\n\t\tfr: \"Dernier pointage\",\n\t\tit: \"Ultima timbratura\",\n\t\tes: \"\u00DAltimo registro\",\n\t\tpl: \"Ostatni wpis\",\n\t\tuk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043C\u0456\u0442\u043A\u0430\",\n\t\t\"zh-cn\": \"\u6700\u540E\u6253\u5361\",\n\t},\n\t/** Net working time of today */\n\ttodayWorkedMinutes: {\n\t\ten: \"Worked today\",\n\t\tde: \"Heute gearbeitet\",\n\t\tru: \"\u041E\u0442\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043E \u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n\t\tpt: \"Trabalhado hoje\",\n\t\tnl: \"Vandaag gewerkt\",\n\t\tfr: \"Travaill\u00E9 aujourd'hui\",\n\t\tit: \"Lavorato oggi\",\n\t\tes: \"Trabajado hoy\",\n\t\tpl: \"Przepracowane dzi\u015B\",\n\t\tuk: \"\u0412\u0456\u0434\u043F\u0440\u0430\u0446\u044C\u043E\u0432\u0430\u043D\u043E \u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n\t\t\"zh-cn\": \"\u4ECA\u65E5\u5DF2\u5DE5\u4F5C\",\n\t},\n\t/** Balance of today */\n\ttodayBalanceMinutes: {\n\t\ten: \"Balance today\",\n\t\tde: \"Saldo heute\",\n\t\tru: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n\t\tpt: \"Saldo de hoje\",\n\t\tnl: \"Saldo vandaag\",\n\t\tfr: \"Solde du jour\",\n\t\tit: \"Saldo di oggi\",\n\t\tes: \"Saldo de hoy\",\n\t\tpl: \"Saldo dzisiaj\",\n\t\tuk: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n\t\t\"zh-cn\": \"\u4ECA\u65E5\u4F59\u989D\",\n\t},\n\t/** Net working time of the current month */\n\tmonthWorkedMinutes: {\n\t\ten: \"Worked this month\",\n\t\tde: \"Diesen Monat gearbeitet\",\n\t\tru: \"\u041E\u0442\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043E \u0437\u0430 \u043C\u0435\u0441\u044F\u0446\",\n\t\tpt: \"Trabalhado este m\u00EAs\",\n\t\tnl: \"Deze maand gewerkt\",\n\t\tfr: \"Travaill\u00E9 ce mois\",\n\t\tit: \"Lavorato questo mese\",\n\t\tes: \"Trabajado este mes\",\n\t\tpl: \"Przepracowane w tym miesi\u0105cu\",\n\t\tuk: \"\u0412\u0456\u0434\u043F\u0440\u0430\u0446\u044C\u043E\u0432\u0430\u043D\u043E \u0446\u044C\u043E\u0433\u043E \u043C\u0456\u0441\u044F\u0446\u044F\",\n\t\t\"zh-cn\": \"\u672C\u6708\u5DF2\u5DE5\u4F5C\",\n\t},\n\t/** Balance of the current month */\n\tmonthBalanceMinutes: {\n\t\ten: \"Balance this month\",\n\t\tde: \"Saldo diesen Monat\",\n\t\tru: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u043C\u0435\u0441\u044F\u0446\",\n\t\tpt: \"Saldo deste m\u00EAs\",\n\t\tnl: \"Saldo deze maand\",\n\t\tfr: \"Solde du mois\",\n\t\tit: \"Saldo di questo mese\",\n\t\tes: \"Saldo de este mes\",\n\t\tpl: \"Saldo w tym miesi\u0105cu\",\n\t\tuk: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0446\u0435\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\",\n\t\t\"zh-cn\": \"\u672C\u6708\u4F59\u989D\",\n\t},\n\t/** Balance of the current year */\n\tyearBalanceMinutes: {\n\t\ten: \"Balance this year\",\n\t\tde: \"Saldo dieses Jahr\",\n\t\tru: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0433\u043E\u0434\",\n\t\tpt: \"Saldo deste ano\",\n\t\tnl: \"Saldo dit jaar\",\n\t\tfr: \"Solde de l'ann\u00E9e\",\n\t\tit: \"Saldo di quest'anno\",\n\t\tes: \"Saldo de este a\u00F1o\",\n\t\tpl: \"Saldo w tym roku\",\n\t\tuk: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0446\u0435\u0439 \u0440\u0456\u043A\",\n\t\t\"zh-cn\": \"\u672C\u5E74\u4F59\u989D\",\n\t},\n\t/** Punches waiting for a decision */\n\topenConflicts: {\n\t\ten: \"Open conflicts\",\n\t\tde: \"Offene Konflikte\",\n\t\tru: \"\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B\",\n\t\tpt: \"Conflitos em aberto\",\n\t\tnl: \"Openstaande conflicten\",\n\t\tfr: \"Conflits en attente\",\n\t\tit: \"Conflitti aperti\",\n\t\tes: \"Conflictos abiertos\",\n\t\tpl: \"Otwarte konflikty\",\n\t\tuk: \"\u0412\u0456\u0434\u043A\u0440\u0438\u0442\u0456 \u043A\u043E\u043D\u0444\u043B\u0456\u043A\u0442\u0438\",\n\t\t\"zh-cn\": \"\u672A\u89E3\u51B3\u7684\u51B2\u7A81\",\n\t},\n\t/** Employee the punch commands apply to */\n\tpunchUserId: {\n\t\ten: \"Employee id for punch commands\",\n\t\tde: \"Mitarbeiter-Id f\u00FCr Stempelbefehle\",\n\t\tru: \"\u0418\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0441\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A\u0430 \u0434\u043B\u044F \u043A\u043E\u043C\u0430\u043D\u0434\",\n\t\tpt: \"ID do funcion\u00E1rio para comandos\",\n\t\tnl: \"Medewerker-id voor opdrachten\",\n\t\tfr: \"Identifiant de l'employ\u00E9 pour les commandes\",\n\t\tit: \"ID del dipendente per i comandi\",\n\t\tes: \"ID del empleado para comandos\",\n\t\tpl: \"Identyfikator pracownika dla polece\u0144\",\n\t\tuk: \"\u0406\u0434\u0435\u043D\u0442\u0438\u0444\u0456\u043A\u0430\u0442\u043E\u0440 \u0441\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A\u0430 \u0434\u043B\u044F \u043A\u043E\u043C\u0430\u043D\u0434\",\n\t\t\"zh-cn\": \"\u6253\u5361\u547D\u4EE4\u7684\u5458\u5DE5 ID\",\n\t},\n\t/** Button: punch in or out */\n\tpunch: {\n\t\ten: \"Punch in or out\",\n\t\tde: \"Ein- oder ausstempeln\",\n\t\tru: \"\u041E\u0442\u043C\u0435\u0442\u0438\u0442\u044C\u0441\u044F (\u043F\u0440\u0438\u0445\u043E\u0434 \u0438\u043B\u0438 \u0443\u0445\u043E\u0434)\",\n\t\tpt: \"Marcar entrada ou sa\u00EDda\",\n\t\tnl: \"In- of uitklokken\",\n\t\tfr: \"Pointer l'arriv\u00E9e ou le d\u00E9part\",\n\t\tit: \"Timbrare entrata o uscita\",\n\t\tes: \"Fichar entrada o salida\",\n\t\tpl: \"Zarejestruj wej\u015Bcie lub wyj\u015Bcie\",\n\t\tuk: \"\u0412\u0456\u0434\u043C\u0456\u0442\u0438\u0442\u0438 \u043F\u0440\u0438\u0445\u0456\u0434 \u0430\u0431\u043E \u0432\u0438\u0445\u0456\u0434\",\n\t\t\"zh-cn\": \"\u4E0A\u73ED\u6216\u4E0B\u73ED\u6253\u5361\",\n\t},\n\t/** Button: punch and round the time */\n\tquickPunch: {\n\t\ten: \"Punch with quick rounding\",\n\t\tde: \"Stempeln mit Schnellrundung\",\n\t\tru: \"\u041E\u0442\u043C\u0435\u0442\u0438\u0442\u044C\u0441\u044F \u0441 \u0431\u044B\u0441\u0442\u0440\u044B\u043C \u043E\u043A\u0440\u0443\u0433\u043B\u0435\u043D\u0438\u0435\u043C\",\n\t\tpt: \"Marcar com arredondamento r\u00E1pido\",\n\t\tnl: \"Klokken met snelle afronding\",\n\t\tfr: \"Pointer avec arrondi rapide\",\n\t\tit: \"Timbrare con arrotondamento rapido\",\n\t\tes: \"Fichar con redondeo r\u00E1pido\",\n\t\tpl: \"Zarejestruj z szybkim zaokr\u0105gleniem\",\n\t\tuk: \"\u0412\u0456\u0434\u043C\u0456\u0442\u0438\u0442\u0438 \u0437\u0456 \u0448\u0432\u0438\u0434\u043A\u0438\u043C \u043E\u043A\u0440\u0443\u0433\u043B\u0435\u043D\u043D\u044F\u043C\",\n\t\t\"zh-cn\": \"\u5FEB\u901F\u53D6\u6574\u6253\u5361\",\n\t},\n\t/** Month to close */\n\tcloseMonth: {\n\t\ten: \"Close month (YYYY-MM)\",\n\t\tde: \"Monat abschlie\u00DFen (JJJJ-MM)\",\n\t\tru: \"\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u043C\u0435\u0441\u044F\u0446 (\u0413\u0413\u0413\u0413-\u041C\u041C)\",\n\t\tpt: \"Fechar m\u00EAs (AAAA-MM)\",\n\t\tnl: \"Maand afsluiten (JJJJ-MM)\",\n\t\tfr: \"Cl\u00F4turer le mois (AAAA-MM)\",\n\t\tit: \"Chiudere il mese (AAAA-MM)\",\n\t\tes: \"Cerrar mes (AAAA-MM)\",\n\t\tpl: \"Zamknij miesi\u0105c (RRRR-MM)\",\n\t\tuk: \"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043C\u0456\u0441\u044F\u0446\u044C (\u0420\u0420\u0420\u0420-\u041C\u041C)\",\n\t\t\"zh-cn\": \"\u5173\u95ED\u6708\u4EFD\uFF08YYYY-MM\uFF09\",\n\t},\n\t/** Period to recalculate */\n\trecalc: {\n\t\ten: \"Recalculate period (YYYY-MM or YYYY)\",\n\t\tde: \"Zeitraum neu berechnen (JJJJ-MM oder JJJJ)\",\n\t\tru: \"\u041F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043F\u0435\u0440\u0438\u043E\u0434 (\u0413\u0413\u0413\u0413-\u041C\u041C \u0438\u043B\u0438 \u0413\u0413\u0413\u0413)\",\n\t\tpt: \"Recalcular per\u00EDodo (AAAA-MM ou AAAA)\",\n\t\tnl: \"Periode opnieuw berekenen (JJJJ-MM of JJJJ)\",\n\t\tfr: \"Recalculer la p\u00E9riode (AAAA-MM ou AAAA)\",\n\t\tit: \"Ricalcolare il periodo (AAAA-MM o AAAA)\",\n\t\tes: \"Recalcular periodo (AAAA-MM o AAAA)\",\n\t\tpl: \"Przelicz okres (RRRR-MM lub RRRR)\",\n\t\tuk: \"\u041F\u0435\u0440\u0435\u0440\u0430\u0445\u0443\u0432\u0430\u0442\u0438 \u043F\u0435\u0440\u0456\u043E\u0434 (\u0420\u0420\u0420\u0420-\u041C\u041C \u0430\u0431\u043E \u0420\u0420\u0420\u0420)\",\n\t\t\"zh-cn\": \"\u91CD\u65B0\u8BA1\u7B97\u671F\u95F4\uFF08YYYY-MM \u6216 YYYY\uFF09\",\n\t},\n\t/** Button: write a database backup */\n\tbackupCommand: {\n\t\ten: \"Write a database backup\",\n\t\tde: \"Datenbank-Sicherung schreiben\",\n\t\tru: \"\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u0440\u0435\u0437\u0435\u0440\u0432\u043D\u0443\u044E \u043A\u043E\u043F\u0438\u044E \u0431\u0430\u0437\u044B\",\n\t\tpt: \"Criar c\u00F3pia de seguran\u00E7a da base de dados\",\n\t\tnl: \"Databasback-up maken\",\n\t\tfr: \"Cr\u00E9er une sauvegarde de la base\",\n\t\tit: \"Creare un backup del database\",\n\t\tes: \"Crear copia de seguridad de la base\",\n\t\tpl: \"Utw\u00F3rz kopi\u0119 zapasow\u0105 bazy\",\n\t\tuk: \"\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0440\u0435\u0437\u0435\u0440\u0432\u043D\u0443 \u043A\u043E\u043F\u0456\u044E \u0431\u0430\u0437\u0438\",\n\t\t\"zh-cn\": \"\u5199\u5165\u6570\u636E\u5E93\u5907\u4EFD\",\n\t},\n\t/** Version of the adapter */\n\tversion: {\n\t\ten: \"Adapter version\",\n\t\tde: \"Adapter-Version\",\n\t\tru: \"\u0412\u0435\u0440\u0441\u0438\u044F \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u0430\",\n\t\tpt: \"Vers\u00E3o do adaptador\",\n\t\tnl: \"Adapterversie\",\n\t\tfr: \"Version de l'adaptateur\",\n\t\tit: \"Versione dell'adattatore\",\n\t\tes: \"Versi\u00F3n del adaptador\",\n\t\tpl: \"Wersja adaptera\",\n\t\tuk: \"\u0412\u0435\u0440\u0441\u0456\u044F \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u0430\",\n\t\t\"zh-cn\": \"\u9002\u914D\u5668\u7248\u672C\",\n\t},\n\t/** Version of the database schema */\n\tschemaVersion: {\n\t\ten: \"Database schema version\",\n\t\tde: \"Datenbank-Schemaversion\",\n\t\tru: \"\u0412\u0435\u0440\u0441\u0438\u044F \u0441\u0445\u0435\u043C\u044B \u0431\u0430\u0437\u044B \u0434\u0430\u043D\u043D\u044B\u0445\",\n\t\tpt: \"Vers\u00E3o do esquema da base de dados\",\n\t\tnl: \"Schemaversie van de database\",\n\t\tfr: \"Version du sch\u00E9ma de la base\",\n\t\tit: \"Versione dello schema del database\",\n\t\tes: \"Versi\u00F3n del esquema de la base\",\n\t\tpl: \"Wersja schematu bazy\",\n\t\tuk: \"\u0412\u0435\u0440\u0441\u0456\u044F \u0441\u0445\u0435\u043C\u0438 \u0431\u0430\u0437\u0438 \u0434\u0430\u043D\u0438\u0445\",\n\t\t\"zh-cn\": \"\u6570\u636E\u5E93\u67B6\u6784\u7248\u672C\",\n\t},\n\t/** Size of the database file */\n\tdbSizeBytes: {\n\t\ten: \"Database size\",\n\t\tde: \"Datenbank-Gr\u00F6\u00DFe\",\n\t\tru: \"\u0420\u0430\u0437\u043C\u0435\u0440 \u0431\u0430\u0437\u044B \u0434\u0430\u043D\u043D\u044B\u0445\",\n\t\tpt: \"Tamanho da base de dados\",\n\t\tnl: \"Grootte van de database\",\n\t\tfr: \"Taille de la base\",\n\t\tit: \"Dimensione del database\",\n\t\tes: \"Tama\u00F1o de la base\",\n\t\tpl: \"Rozmiar bazy\",\n\t\tuk: \"\u0420\u043E\u0437\u043C\u0456\u0440 \u0431\u0430\u0437\u0438 \u0434\u0430\u043D\u0438\u0445\",\n\t\t\"zh-cn\": \"\u6570\u636E\u5E93\u5927\u5C0F\",\n\t},\n\t/** Last error of the instance */\n\tlastError: {\n\t\ten: \"Last error\",\n\t\tde: \"Letzter Fehler\",\n\t\tru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430\",\n\t\tpt: \"\u00DAltimo erro\",\n\t\tnl: \"Laatste fout\",\n\t\tfr: \"Derni\u00E8re erreur\",\n\t\tit: \"Ultimo errore\",\n\t\tes: \"\u00DAltimo error\",\n\t\tpl: \"Ostatni b\u0142\u0105d\",\n\t\tuk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u043F\u043E\u043C\u0438\u043B\u043A\u0430\",\n\t\t\"zh-cn\": \"\u6700\u540E\u7684\u9519\u8BEF\",\n\t},\n\t/** Instant of the last backup */\n\tlastBackup: {\n\t\ten: \"Last backup\",\n\t\tde: \"Letzte Sicherung\",\n\t\tru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u0440\u0435\u0437\u0435\u0440\u0432\u043D\u0430\u044F \u043A\u043E\u043F\u0438\u044F\",\n\t\tpt: \"\u00DAltima c\u00F3pia de seguran\u00E7a\",\n\t\tnl: \"Laatste back-up\",\n\t\tfr: \"Derni\u00E8re sauvegarde\",\n\t\tit: \"Ultimo backup\",\n\t\tes: \"\u00DAltima copia de seguridad\",\n\t\tpl: \"Ostatnia kopia zapasowa\",\n\t\tuk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u0440\u0435\u0437\u0435\u0440\u0432\u043D\u0430 \u043A\u043E\u043F\u0456\u044F\",\n\t\t\"zh-cn\": \"\u6700\u540E\u7684\u5907\u4EFD\",\n\t},\n\t/** Number of employees that are present */\n\tcompanyPresentCount: {\n\t\ten: \"Present employees\",\n\t\tde: \"Anwesende Mitarbeiter\",\n\t\tru: \"\u041F\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u0441\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A\u0438\",\n\t\tpt: \"Funcion\u00E1rios presentes\",\n\t\tnl: \"Aanwezige medewerkers\",\n\t\tfr: \"Employ\u00E9s pr\u00E9sents\",\n\t\tit: \"Dipendenti presenti\",\n\t\tes: \"Empleados presentes\",\n\t\tpl: \"Obecni pracownicy\",\n\t\tuk: \"\u041F\u0440\u0438\u0441\u0443\u0442\u043D\u0456 \u0441\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A\u0438\",\n\t\t\"zh-cn\": \"\u5728\u5C97\u5458\u5DE5\",\n\t},\n\t/** Names of the employees that are present */\n\tcompanyPresent: {\n\t\ten: \"Who is present\",\n\t\tde: \"Wer ist anwesend\",\n\t\tru: \"\u041A\u0442\u043E \u043F\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442\",\n\t\tpt: \"Quem est\u00E1 presente\",\n\t\tnl: \"Wie is aanwezig\",\n\t\tfr: \"Qui est pr\u00E9sent\",\n\t\tit: \"Chi \u00E8 presente\",\n\t\tes: \"Qui\u00E9n est\u00E1 presente\",\n\t\tpl: \"Kto jest obecny\",\n\t\tuk: \"\u0425\u0442\u043E \u043F\u0440\u0438\u0441\u0443\u0442\u043D\u0456\u0439\",\n\t\t\"zh-cn\": \"\u8C01\u5728\u5C97\",\n\t},\n\t/** Instant of the newest punch of the company */\n\tcompanyLastPunch: {\n\t\ten: \"Last punch\",\n\t\tde: \"Letzte Buchung\",\n\t\tru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0442\u043C\u0435\u0442\u043A\u0430\",\n\t\tpt: \"\u00DAltimo registo\",\n\t\tnl: \"Laatste stempeling\",\n\t\tfr: \"Dernier pointage\",\n\t\tit: \"Ultima timbratura\",\n\t\tes: \"\u00DAltimo registro\",\n\t\tpl: \"Ostatni wpis\",\n\t\tuk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043C\u0456\u0442\u043A\u0430\",\n\t\t\"zh-cn\": \"\u6700\u540E\u6253\u5361\",\n\t},\n\t/** Instant of the newest event */\n\tlastAt: {\n\t\ten: \"Last event at\",\n\t\tde: \"Letztes Ereignis um\",\n\t\tru: \"\u0412\u0440\u0435\u043C\u044F \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Hora do \u00FAltimo evento\",\n\t\tnl: \"Tijdstip laatste gebeurtenis\",\n\t\tfr: \"Heure du dernier \u00E9v\u00E9nement\",\n\t\tit: \"Ora dell'ultimo evento\",\n\t\tes: \"Hora del \u00FAltimo evento\",\n\t\tpl: \"Czas ostatniego zdarzenia\",\n\t\tuk: \"\u0427\u0430\u0441 \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u043F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u6700\u540E\u4E8B\u4EF6\u65F6\u95F4\",\n\t},\n\t/** Kind of the newest event */\n\tlastType: {\n\t\ten: \"Kind of the last event\",\n\t\tde: \"Art des letzten Ereignisses\",\n\t\tru: \"\u0422\u0438\u043F \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Tipo do \u00FAltimo evento\",\n\t\tnl: \"Soort laatste gebeurtenis\",\n\t\tfr: \"Type du dernier \u00E9v\u00E9nement\",\n\t\tit: \"Tipo dell'ultimo evento\",\n\t\tes: \"Tipo del \u00FAltimo evento\",\n\t\tpl: \"Rodzaj ostatniego zdarzenia\",\n\t\tuk: \"\u0422\u0438\u043F \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u043F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u6700\u540E\u4E8B\u4EF6\u7C7B\u578B\",\n\t},\n\t/** Employee of the newest event */\n\tlastUser: {\n\t\ten: \"Employee of the last event\",\n\t\tde: \"Mitarbeiter des letzten Ereignisses\",\n\t\tru: \"\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Funcion\u00E1rio do \u00FAltimo evento\",\n\t\tnl: \"Medewerker van de laatste gebeurtenis\",\n\t\tfr: \"Employ\u00E9 du dernier \u00E9v\u00E9nement\",\n\t\tit: \"Dipendente dell'ultimo evento\",\n\t\tes: \"Empleado del \u00FAltimo evento\",\n\t\tpl: \"Pracownik ostatniego zdarzenia\",\n\t\tuk: \"\u0421\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u043F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u6700\u540E\u4E8B\u4EF6\u7684\u5458\u5DE5\",\n\t},\n\t/** Direction of the newest punch */\n\tlastDirection: {\n\t\ten: \"Direction of the last punch\",\n\t\tde: \"Richtung der letzten Buchung\",\n\t\tru: \"\u041D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0439 \u043E\u0442\u043C\u0435\u0442\u043A\u0438\",\n\t\tpt: \"Dire\u00E7\u00E3o do \u00FAltimo registo\",\n\t\tnl: \"Richting van de laatste stempeling\",\n\t\tfr: \"Sens du dernier pointage\",\n\t\tit: \"Direzione dell'ultima timbratura\",\n\t\tes: \"Direcci\u00F3n del \u00FAltimo registro\",\n\t\tpl: \"Kierunek ostatniego wpisu\",\n\t\tuk: \"\u041D\u0430\u043F\u0440\u044F\u043C\u043E\u043A \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u0432\u0456\u0434\u043C\u0456\u0442\u043A\u0438\",\n\t\t\"zh-cn\": \"\u6700\u540E\u6253\u5361\u7684\u65B9\u5411\",\n\t},\n\t/** Source of the newest event */\n\tlastSource: {\n\t\ten: \"Source of the last event\",\n\t\tde: \"Quelle des letzten Ereignisses\",\n\t\tru: \"\u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Origem do \u00FAltimo evento\",\n\t\tnl: \"Bron van de laatste gebeurtenis\",\n\t\tfr: \"Source du dernier \u00E9v\u00E9nement\",\n\t\tit: \"Origine dell'ultimo evento\",\n\t\tes: \"Origen del \u00FAltimo evento\",\n\t\tpl: \"\u0179r\u00F3d\u0142o ostatniego zdarzenia\",\n\t\tuk: \"\u0414\u0436\u0435\u0440\u0435\u043B\u043E \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u043F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u6700\u540E\u4E8B\u4EF6\u7684\u6765\u6E90\",\n\t},\n\t/** Channel with the calendar of the instance */\n\tcalendarChannel: {\n\t\ten: \"Calendar\",\n\t\tde: \"Kalender\",\n\t\tru: \"\u041A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044C\",\n\t\tpt: \"Calend\u00E1rio\",\n\t\tnl: \"Kalender\",\n\t\tfr: \"Calendrier\",\n\t\tit: \"Calendario\",\n\t\tes: \"Calendario\",\n\t\tpl: \"Kalendarz\",\n\t\tuk: \"\u041A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\",\n\t\t\"zh-cn\": \"\u65E5\u5386\",\n\t},\n\t/** Subscription link of the company calendar */\n\tcalendarFeedUrl: {\n\t\ten: \"Subscription link of the company\",\n\t\tde: \"Abo-Link der Firma\",\n\t\tru: \"\u0421\u0441\u044B\u043B\u043A\u0430 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u0444\u0438\u0440\u043C\u044B\",\n\t\tpt: \"Liga\u00E7\u00E3o de subscri\u00E7\u00E3o da empresa\",\n\t\tnl: \"Abonnementslink van het bedrijf\",\n\t\tfr: \"Lien d'abonnement de l'entreprise\",\n\t\tit: \"Link di sottoscrizione dell'azienda\",\n\t\tes: \"Enlace de suscripci\u00F3n de la empresa\",\n\t\tpl: \"Link subskrypcji firmy\",\n\t\tuk: \"\u041F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F \u043F\u0456\u0434\u043F\u0438\u0441\u043A\u0438 \u043A\u043E\u043C\u043F\u0430\u043D\u0456\u0457\",\n\t\t\"zh-cn\": \"\u516C\u53F8\u7684\u8BA2\u9605\u94FE\u63A5\",\n\t},\n\t/** The written calendar file */\n\tcalendarFeedFile: {\n\t\ten: \"Calendar file (.ics)\",\n\t\tde: \"Kalenderdatei (.ics)\",\n\t\tru: \"\u0424\u0430\u0439\u043B \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F (.ics)\",\n\t\tpt: \"Ficheiro de calend\u00E1rio (.ics)\",\n\t\tnl: \"Kalenderbestand (.ics)\",\n\t\tfr: \"Fichier de calendrier (.ics)\",\n\t\tit: \"File di calendario (.ics)\",\n\t\tes: \"Archivo de calendario (.ics)\",\n\t\tpl: \"Plik kalendarza (.ics)\",\n\t\tuk: \"\u0424\u0430\u0439\u043B \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F (.ics)\",\n\t\t\"zh-cn\": \"\u65E5\u5386\u6587\u4EF6\uFF08.ics\uFF09\",\n\t},\n\t/** Instant the calendar was written */\n\tcalendarUpdatedAt: {\n\t\ten: \"Calendar written at\",\n\t\tde: \"Kalender geschrieben um\",\n\t\tru: \"\u0412\u0440\u0435\u043C\u044F \u0437\u0430\u043F\u0438\u0441\u0438 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F\",\n\t\tpt: \"Hora de escrita do calend\u00E1rio\",\n\t\tnl: \"Tijdstip kalender geschreven\",\n\t\tfr: \"Heure d'\u00E9criture du calendrier\",\n\t\tit: \"Ora di scrittura del calendario\",\n\t\tes: \"Hora de escritura del calendario\",\n\t\tpl: \"Czas zapisu kalendarza\",\n\t\tuk: \"\u0427\u0430\u0441 \u0437\u0430\u043F\u0438\u0441\u0443 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F\",\n\t\t\"zh-cn\": \"\u65E5\u5386\u5199\u5165\u65F6\u95F4\",\n\t},\n\t/** The absences as JSON */\n\tcalendarAbsences: {\n\t\ten: \"Absences (JSON)\",\n\t\tde: \"Abwesenheiten (JSON)\",\n\t\tru: \"\u041E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044F (JSON)\",\n\t\tpt: \"Aus\u00EAncias (JSON)\",\n\t\tnl: \"Afwezigheden (JSON)\",\n\t\tfr: \"Absences (JSON)\",\n\t\tit: \"Assenze (JSON)\",\n\t\tes: \"Ausencias (JSON)\",\n\t\tpl: \"Nieobecno\u015Bci (JSON)\",\n\t\tuk: \"\u0412\u0456\u0434\u0441\u0443\u0442\u043D\u043E\u0441\u0442\u0456 (JSON)\",\n\t\t\"zh-cn\": \"\u7F3A\u52E4\uFF08JSON\uFF09\",\n\t},\n\t/** Command that creates or renews the calendar link of the company */\n\trotateCalendarToken: {\n\t\ten: \"Create or renew the calendar link of the company\",\n\t\tde: \"Kalender-Link der Firma anlegen oder erneuern\",\n\t\tru: \"\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u0438\u043B\u0438 \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F \u0444\u0438\u0440\u043C\u044B\",\n\t\tpt: \"Criar ou renovar a liga\u00E7\u00E3o do calend\u00E1rio da empresa\",\n\t\tnl: \"Kalenderlink van het bedrijf aanmaken of vernieuwen\",\n\t\tfr: \"Cr\u00E9er ou renouveler le lien du calendrier de l'entreprise\",\n\t\tit: \"Creare o rinnovare il link del calendario dell'azienda\",\n\t\tes: \"Crear o renovar el enlace del calendario de la empresa\",\n\t\tpl: \"Utw\u00F3rz lub od\u015Bwie\u017C link kalendarza firmy\",\n\t\tuk: \"\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0430\u0431\u043E \u043E\u043D\u043E\u0432\u0438\u0442\u0438 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F \u043A\u043E\u043C\u043F\u0430\u043D\u0456\u0457\",\n\t\t\"zh-cn\": \"\u521B\u5EFA\u6216\u66F4\u65B0\u516C\u53F8\u7684\u65E5\u5386\u94FE\u63A5\",\n\t},\n} satisfies Record<string, StateName>;\n"],
4
+ "sourcesContent": ["/**\n * Names of the adapter's objects, in the eleven languages ioBroker recommends (checker `E6001`).\n *\n * `states.ts` creates the tree; this module is the single place that carries the texts, so every name has the same\n * set of languages. `states.test.ts` walks the whole tree and fails when a name misses one of the eleven keys \u2014\n * that is how the recommendation stays satisfied without anyone remembering it.\n */\n\n/** One object name: the eleven languages the ioBroker checker recommends. */\nexport interface StateName {\n\t/** English */\n\ten: string;\n\t/** German */\n\tde: string;\n\t/** Russian */\n\tru: string;\n\t/** Portuguese */\n\tpt: string;\n\t/** Dutch */\n\tnl: string;\n\t/** French */\n\tfr: string;\n\t/** Italian */\n\tit: string;\n\t/** Spanish */\n\tes: string;\n\t/** Polish */\n\tpl: string;\n\t/** Ukrainian */\n\tuk: string;\n\t/** Chinese (simplified) */\n\t\"zh-cn\": string;\n}\n\n/** Object names of the adapter, grouped like the state tree. */\nexport const STATE_NAMES = {\n\t/** Channel holding the employees */\n\tusersChannel: {\n\t\ten: \"Employees\",\n\t\tde: \"Mitarbeiter\",\n\t\tru: \"\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A\u0438\",\n\t\tpt: \"Funcion\u00E1rios\",\n\t\tnl: \"Medewerkers\",\n\t\tfr: \"Employ\u00E9s\",\n\t\tit: \"Dipendenti\",\n\t\tes: \"Empleados\",\n\t\tpl: \"Pracownicy\",\n\t\tuk: \"\u0421\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A\u0438\",\n\t\t\"zh-cn\": \"\u5458\u5DE5\",\n\t},\n\t/** Channel of one employee */\n\temployeeChannel: {\n\t\ten: \"Employee\",\n\t\tde: \"Mitarbeiter\",\n\t\tru: \"\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A\",\n\t\tpt: \"Funcion\u00E1rio\",\n\t\tnl: \"Medewerker\",\n\t\tfr: \"Employ\u00E9\",\n\t\tit: \"Dipendente\",\n\t\tes: \"Empleado\",\n\t\tpl: \"Pracownik\",\n\t\tuk: \"\u0421\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A\",\n\t\t\"zh-cn\": \"\u5458\u5DE5\",\n\t},\n\t/** Channel of the writable commands */\n\tcommandsChannel: {\n\t\ten: \"Commands\",\n\t\tde: \"Befehle\",\n\t\tru: \"\u041A\u043E\u043C\u0430\u043D\u0434\u044B\",\n\t\tpt: \"Comandos\",\n\t\tnl: \"Opdrachten\",\n\t\tfr: \"Commandes\",\n\t\tit: \"Comandi\",\n\t\tes: \"Comandos\",\n\t\tpl: \"Polecenia\",\n\t\tuk: \"\u041A\u043E\u043C\u0430\u043D\u0434\u0438\",\n\t\t\"zh-cn\": \"\u547D\u4EE4\",\n\t},\n\t/** Channel with the figures of the instance */\n\tinfoChannel: {\n\t\ten: \"Information\",\n\t\tde: \"Information\",\n\t\tru: \"\u0418\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F\",\n\t\tpt: \"Informa\u00E7\u00E3o\",\n\t\tnl: \"Informatie\",\n\t\tfr: \"Informations\",\n\t\tit: \"Informazioni\",\n\t\tes: \"Informaci\u00F3n\",\n\t\tpl: \"Informacje\",\n\t\tuk: \"\u0406\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0456\u044F\",\n\t\t\"zh-cn\": \"\u4FE1\u606F\",\n\t},\n\t/** Channel with the figures of the whole company */\n\tcompanyChannel: {\n\t\ten: \"Company\",\n\t\tde: \"Firma\",\n\t\tru: \"\u041A\u043E\u043C\u043F\u0430\u043D\u0438\u044F\",\n\t\tpt: \"Empresa\",\n\t\tnl: \"Bedrijf\",\n\t\tfr: \"Entreprise\",\n\t\tit: \"Azienda\",\n\t\tes: \"Empresa\",\n\t\tpl: \"Firma\",\n\t\tuk: \"\u041A\u043E\u043C\u043F\u0430\u043D\u0456\u044F\",\n\t\t\"zh-cn\": \"\u516C\u53F8\",\n\t},\n\t/** Channel with the newest event of the instance */\n\teventsChannel: {\n\t\ten: \"Events\",\n\t\tde: \"Ereignisse\",\n\t\tru: \"\u0421\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Eventos\",\n\t\tnl: \"Gebeurtenissen\",\n\t\tfr: \"\u00C9v\u00E9nements\",\n\t\tit: \"Eventi\",\n\t\tes: \"Eventos\",\n\t\tpl: \"Zdarzenia\",\n\t\tuk: \"\u041F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u4E8B\u4EF6\",\n\t},\n\t/** Shown name of the employee */\n\tdisplayName: {\n\t\ten: \"Name\",\n\t\tde: \"Name\",\n\t\tru: \"\u0418\u043C\u044F\",\n\t\tpt: \"Nome\",\n\t\tnl: \"Naam\",\n\t\tfr: \"Nom\",\n\t\tit: \"Nome\",\n\t\tes: \"Nombre\",\n\t\tpl: \"Nazwa\",\n\t\tuk: \"\u0406\u043C'\u044F\",\n\t\t\"zh-cn\": \"\u59D3\u540D\",\n\t},\n\t/** Writable presence of the employee (working time runs) */\n\tuserPresent: {\n\t\ten: \"Present (working time is running)\",\n\t\tde: \"Anwesenheit (Arbeitszeit l\u00E4uft)\",\n\t\tru: \"\u041F\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 (\u0440\u0430\u0431\u043E\u0447\u0435\u0435 \u0432\u0440\u0435\u043C\u044F \u0438\u0434\u0451\u0442)\",\n\t\tpt: \"Presente (o tempo de trabalho corre)\",\n\t\tnl: \"Aanwezig (werktijd loopt)\",\n\t\tfr: \"Pr\u00E9sent (temps de travail en cours)\",\n\t\tit: \"Presente (tempo di lavoro in corso)\",\n\t\tes: \"Presente (tiempo de trabajo en curso)\",\n\t\tpl: \"Obecno\u015B\u0107 (czas pracy trwa)\",\n\t\tuk: \"\u041F\u0440\u0438\u0441\u0443\u0442\u043D\u0456\u0441\u0442\u044C (\u0440\u043E\u0431\u043E\u0447\u0438\u0439 \u0447\u0430\u0441 \u0456\u0434\u0435)\",\n\t\t\"zh-cn\": \"\u5728\u5C97\uFF08\u5DE5\u4F5C\u65F6\u95F4\u8BA1\u65F6\u4E2D\uFF09\",\n\t},\n\t/** True while the last punch of the day has no counterpart */\n\thasOpenEntry: {\n\t\ten: \"Punched in\",\n\t\tde: \"Eingestempelt\",\n\t\tru: \"\u041F\u0440\u0438\u0445\u043E\u0434 \u043E\u0442\u043C\u0435\u0447\u0435\u043D\",\n\t\tpt: \"Entrada registada\",\n\t\tnl: \"Ingeklokt\",\n\t\tfr: \"Pointage d'arriv\u00E9e\",\n\t\tit: \"Entrata registrata\",\n\t\tes: \"Entrada registrada\",\n\t\tpl: \"Zarejestrowane wej\u015Bcie\",\n\t\tuk: \"\u0412\u0456\u0434\u043C\u0456\u0447\u0435\u043D\u043E \u043F\u0440\u0438\u0445\u0456\u0434\",\n\t\t\"zh-cn\": \"\u5DF2\u7B7E\u5230\",\n\t},\n\t/** Instant of the last punch */\n\tlastPunch: {\n\t\ten: \"Last punch\",\n\t\tde: \"Letzte Buchung\",\n\t\tru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0442\u043C\u0435\u0442\u043A\u0430\",\n\t\tpt: \"\u00DAltimo registo\",\n\t\tnl: \"Laatste stempeling\",\n\t\tfr: \"Dernier pointage\",\n\t\tit: \"Ultima timbratura\",\n\t\tes: \"\u00DAltimo registro\",\n\t\tpl: \"Ostatni wpis\",\n\t\tuk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043C\u0456\u0442\u043A\u0430\",\n\t\t\"zh-cn\": \"\u6700\u540E\u6253\u5361\",\n\t},\n\t/** Net working time of today */\n\ttodayWorkedMinutes: {\n\t\ten: \"Worked today\",\n\t\tde: \"Heute gearbeitet\",\n\t\tru: \"\u041E\u0442\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043E \u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n\t\tpt: \"Trabalhado hoje\",\n\t\tnl: \"Vandaag gewerkt\",\n\t\tfr: \"Travaill\u00E9 aujourd'hui\",\n\t\tit: \"Lavorato oggi\",\n\t\tes: \"Trabajado hoy\",\n\t\tpl: \"Przepracowane dzi\u015B\",\n\t\tuk: \"\u0412\u0456\u0434\u043F\u0440\u0430\u0446\u044C\u043E\u0432\u0430\u043D\u043E \u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n\t\t\"zh-cn\": \"\u4ECA\u65E5\u5DF2\u5DE5\u4F5C\",\n\t},\n\t/** Balance of today */\n\ttodayBalanceMinutes: {\n\t\ten: \"Balance today\",\n\t\tde: \"Saldo heute\",\n\t\tru: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n\t\tpt: \"Saldo de hoje\",\n\t\tnl: \"Saldo vandaag\",\n\t\tfr: \"Solde du jour\",\n\t\tit: \"Saldo di oggi\",\n\t\tes: \"Saldo de hoy\",\n\t\tpl: \"Saldo dzisiaj\",\n\t\tuk: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n\t\t\"zh-cn\": \"\u4ECA\u65E5\u4F59\u989D\",\n\t},\n\t/** Net working time of the current month */\n\tmonthWorkedMinutes: {\n\t\ten: \"Worked this month\",\n\t\tde: \"Diesen Monat gearbeitet\",\n\t\tru: \"\u041E\u0442\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043E \u0437\u0430 \u043C\u0435\u0441\u044F\u0446\",\n\t\tpt: \"Trabalhado este m\u00EAs\",\n\t\tnl: \"Deze maand gewerkt\",\n\t\tfr: \"Travaill\u00E9 ce mois\",\n\t\tit: \"Lavorato questo mese\",\n\t\tes: \"Trabajado este mes\",\n\t\tpl: \"Przepracowane w tym miesi\u0105cu\",\n\t\tuk: \"\u0412\u0456\u0434\u043F\u0440\u0430\u0446\u044C\u043E\u0432\u0430\u043D\u043E \u0446\u044C\u043E\u0433\u043E \u043C\u0456\u0441\u044F\u0446\u044F\",\n\t\t\"zh-cn\": \"\u672C\u6708\u5DF2\u5DE5\u4F5C\",\n\t},\n\t/** Balance of the current month */\n\tmonthBalanceMinutes: {\n\t\ten: \"Balance this month\",\n\t\tde: \"Saldo diesen Monat\",\n\t\tru: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u043C\u0435\u0441\u044F\u0446\",\n\t\tpt: \"Saldo deste m\u00EAs\",\n\t\tnl: \"Saldo deze maand\",\n\t\tfr: \"Solde du mois\",\n\t\tit: \"Saldo di questo mese\",\n\t\tes: \"Saldo de este mes\",\n\t\tpl: \"Saldo w tym miesi\u0105cu\",\n\t\tuk: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0446\u0435\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\",\n\t\t\"zh-cn\": \"\u672C\u6708\u4F59\u989D\",\n\t},\n\t/** Balance of the current year */\n\tyearBalanceMinutes: {\n\t\ten: \"Balance this year\",\n\t\tde: \"Saldo dieses Jahr\",\n\t\tru: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0433\u043E\u0434\",\n\t\tpt: \"Saldo deste ano\",\n\t\tnl: \"Saldo dit jaar\",\n\t\tfr: \"Solde de l'ann\u00E9e\",\n\t\tit: \"Saldo di quest'anno\",\n\t\tes: \"Saldo de este a\u00F1o\",\n\t\tpl: \"Saldo w tym roku\",\n\t\tuk: \"\u0411\u0430\u043B\u0430\u043D\u0441 \u0437\u0430 \u0446\u0435\u0439 \u0440\u0456\u043A\",\n\t\t\"zh-cn\": \"\u672C\u5E74\u4F59\u989D\",\n\t},\n\t/** Punches waiting for a decision */\n\topenConflicts: {\n\t\ten: \"Open conflicts\",\n\t\tde: \"Offene Konflikte\",\n\t\tru: \"\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B\",\n\t\tpt: \"Conflitos em aberto\",\n\t\tnl: \"Openstaande conflicten\",\n\t\tfr: \"Conflits en attente\",\n\t\tit: \"Conflitti aperti\",\n\t\tes: \"Conflictos abiertos\",\n\t\tpl: \"Otwarte konflikty\",\n\t\tuk: \"\u0412\u0456\u0434\u043A\u0440\u0438\u0442\u0456 \u043A\u043E\u043D\u0444\u043B\u0456\u043A\u0442\u0438\",\n\t\t\"zh-cn\": \"\u672A\u89E3\u51B3\u7684\u51B2\u7A81\",\n\t},\n\t/** Employee the punch commands apply to */\n\tpunchUserId: {\n\t\ten: \"Employee id for punch commands\",\n\t\tde: \"Mitarbeiter-Id f\u00FCr Stempelbefehle\",\n\t\tru: \"\u0418\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0441\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A\u0430 \u0434\u043B\u044F \u043A\u043E\u043C\u0430\u043D\u0434\",\n\t\tpt: \"ID do funcion\u00E1rio para comandos\",\n\t\tnl: \"Medewerker-id voor opdrachten\",\n\t\tfr: \"Identifiant de l'employ\u00E9 pour les commandes\",\n\t\tit: \"ID del dipendente per i comandi\",\n\t\tes: \"ID del empleado para comandos\",\n\t\tpl: \"Identyfikator pracownika dla polece\u0144\",\n\t\tuk: \"\u0406\u0434\u0435\u043D\u0442\u0438\u0444\u0456\u043A\u0430\u0442\u043E\u0440 \u0441\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A\u0430 \u0434\u043B\u044F \u043A\u043E\u043C\u0430\u043D\u0434\",\n\t\t\"zh-cn\": \"\u6253\u5361\u547D\u4EE4\u7684\u5458\u5DE5 ID\",\n\t},\n\t/** Button: punch in or out */\n\tpunch: {\n\t\ten: \"Punch in or out\",\n\t\tde: \"Ein- oder ausstempeln\",\n\t\tru: \"\u041E\u0442\u043C\u0435\u0442\u0438\u0442\u044C\u0441\u044F (\u043F\u0440\u0438\u0445\u043E\u0434 \u0438\u043B\u0438 \u0443\u0445\u043E\u0434)\",\n\t\tpt: \"Marcar entrada ou sa\u00EDda\",\n\t\tnl: \"In- of uitklokken\",\n\t\tfr: \"Pointer l'arriv\u00E9e ou le d\u00E9part\",\n\t\tit: \"Timbrare entrata o uscita\",\n\t\tes: \"Fichar entrada o salida\",\n\t\tpl: \"Zarejestruj wej\u015Bcie lub wyj\u015Bcie\",\n\t\tuk: \"\u0412\u0456\u0434\u043C\u0456\u0442\u0438\u0442\u0438 \u043F\u0440\u0438\u0445\u0456\u0434 \u0430\u0431\u043E \u0432\u0438\u0445\u0456\u0434\",\n\t\t\"zh-cn\": \"\u4E0A\u73ED\u6216\u4E0B\u73ED\u6253\u5361\",\n\t},\n\t/** Button: punch and round the time */\n\tquickPunch: {\n\t\ten: \"Punch with quick rounding\",\n\t\tde: \"Stempeln mit Schnellrundung\",\n\t\tru: \"\u041E\u0442\u043C\u0435\u0442\u0438\u0442\u044C\u0441\u044F \u0441 \u0431\u044B\u0441\u0442\u0440\u044B\u043C \u043E\u043A\u0440\u0443\u0433\u043B\u0435\u043D\u0438\u0435\u043C\",\n\t\tpt: \"Marcar com arredondamento r\u00E1pido\",\n\t\tnl: \"Klokken met snelle afronding\",\n\t\tfr: \"Pointer avec arrondi rapide\",\n\t\tit: \"Timbrare con arrotondamento rapido\",\n\t\tes: \"Fichar con redondeo r\u00E1pido\",\n\t\tpl: \"Zarejestruj z szybkim zaokr\u0105gleniem\",\n\t\tuk: \"\u0412\u0456\u0434\u043C\u0456\u0442\u0438\u0442\u0438 \u0437\u0456 \u0448\u0432\u0438\u0434\u043A\u0438\u043C \u043E\u043A\u0440\u0443\u0433\u043B\u0435\u043D\u043D\u044F\u043C\",\n\t\t\"zh-cn\": \"\u5FEB\u901F\u53D6\u6574\u6253\u5361\",\n\t},\n\t/** Month to close */\n\tcloseMonth: {\n\t\ten: \"Close month (YYYY-MM)\",\n\t\tde: \"Monat abschlie\u00DFen (JJJJ-MM)\",\n\t\tru: \"\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u043C\u0435\u0441\u044F\u0446 (\u0413\u0413\u0413\u0413-\u041C\u041C)\",\n\t\tpt: \"Fechar m\u00EAs (AAAA-MM)\",\n\t\tnl: \"Maand afsluiten (JJJJ-MM)\",\n\t\tfr: \"Cl\u00F4turer le mois (AAAA-MM)\",\n\t\tit: \"Chiudere il mese (AAAA-MM)\",\n\t\tes: \"Cerrar mes (AAAA-MM)\",\n\t\tpl: \"Zamknij miesi\u0105c (RRRR-MM)\",\n\t\tuk: \"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043C\u0456\u0441\u044F\u0446\u044C (\u0420\u0420\u0420\u0420-\u041C\u041C)\",\n\t\t\"zh-cn\": \"\u5173\u95ED\u6708\u4EFD\uFF08YYYY-MM\uFF09\",\n\t},\n\t/** Period to recalculate */\n\trecalc: {\n\t\ten: \"Recalculate period (YYYY-MM or YYYY)\",\n\t\tde: \"Zeitraum neu berechnen (JJJJ-MM oder JJJJ)\",\n\t\tru: \"\u041F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043F\u0435\u0440\u0438\u043E\u0434 (\u0413\u0413\u0413\u0413-\u041C\u041C \u0438\u043B\u0438 \u0413\u0413\u0413\u0413)\",\n\t\tpt: \"Recalcular per\u00EDodo (AAAA-MM ou AAAA)\",\n\t\tnl: \"Periode opnieuw berekenen (JJJJ-MM of JJJJ)\",\n\t\tfr: \"Recalculer la p\u00E9riode (AAAA-MM ou AAAA)\",\n\t\tit: \"Ricalcolare il periodo (AAAA-MM o AAAA)\",\n\t\tes: \"Recalcular periodo (AAAA-MM o AAAA)\",\n\t\tpl: \"Przelicz okres (RRRR-MM lub RRRR)\",\n\t\tuk: \"\u041F\u0435\u0440\u0435\u0440\u0430\u0445\u0443\u0432\u0430\u0442\u0438 \u043F\u0435\u0440\u0456\u043E\u0434 (\u0420\u0420\u0420\u0420-\u041C\u041C \u0430\u0431\u043E \u0420\u0420\u0420\u0420)\",\n\t\t\"zh-cn\": \"\u91CD\u65B0\u8BA1\u7B97\u671F\u95F4\uFF08YYYY-MM \u6216 YYYY\uFF09\",\n\t},\n\t/** Button: write a database backup */\n\tbackupCommand: {\n\t\ten: \"Write a database backup\",\n\t\tde: \"Datenbank-Sicherung schreiben\",\n\t\tru: \"\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u0440\u0435\u0437\u0435\u0440\u0432\u043D\u0443\u044E \u043A\u043E\u043F\u0438\u044E \u0431\u0430\u0437\u044B\",\n\t\tpt: \"Criar c\u00F3pia de seguran\u00E7a da base de dados\",\n\t\tnl: \"Databasback-up maken\",\n\t\tfr: \"Cr\u00E9er une sauvegarde de la base\",\n\t\tit: \"Creare un backup del database\",\n\t\tes: \"Crear copia de seguridad de la base\",\n\t\tpl: \"Utw\u00F3rz kopi\u0119 zapasow\u0105 bazy\",\n\t\tuk: \"\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0440\u0435\u0437\u0435\u0440\u0432\u043D\u0443 \u043A\u043E\u043F\u0456\u044E \u0431\u0430\u0437\u0438\",\n\t\t\"zh-cn\": \"\u5199\u5165\u6570\u636E\u5E93\u5907\u4EFD\",\n\t},\n\t/** Version of the adapter */\n\tversion: {\n\t\ten: \"Adapter version\",\n\t\tde: \"Adapter-Version\",\n\t\tru: \"\u0412\u0435\u0440\u0441\u0438\u044F \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u0430\",\n\t\tpt: \"Vers\u00E3o do adaptador\",\n\t\tnl: \"Adapterversie\",\n\t\tfr: \"Version de l'adaptateur\",\n\t\tit: \"Versione dell'adattatore\",\n\t\tes: \"Versi\u00F3n del adaptador\",\n\t\tpl: \"Wersja adaptera\",\n\t\tuk: \"\u0412\u0435\u0440\u0441\u0456\u044F \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u0430\",\n\t\t\"zh-cn\": \"\u9002\u914D\u5668\u7248\u672C\",\n\t},\n\t/** Version of the database schema */\n\tschemaVersion: {\n\t\ten: \"Database schema version\",\n\t\tde: \"Datenbank-Schemaversion\",\n\t\tru: \"\u0412\u0435\u0440\u0441\u0438\u044F \u0441\u0445\u0435\u043C\u044B \u0431\u0430\u0437\u044B \u0434\u0430\u043D\u043D\u044B\u0445\",\n\t\tpt: \"Vers\u00E3o do esquema da base de dados\",\n\t\tnl: \"Schemaversie van de database\",\n\t\tfr: \"Version du sch\u00E9ma de la base\",\n\t\tit: \"Versione dello schema del database\",\n\t\tes: \"Versi\u00F3n del esquema de la base\",\n\t\tpl: \"Wersja schematu bazy\",\n\t\tuk: \"\u0412\u0435\u0440\u0441\u0456\u044F \u0441\u0445\u0435\u043C\u0438 \u0431\u0430\u0437\u0438 \u0434\u0430\u043D\u0438\u0445\",\n\t\t\"zh-cn\": \"\u6570\u636E\u5E93\u67B6\u6784\u7248\u672C\",\n\t},\n\t/** Size of the database file */\n\tdbSizeBytes: {\n\t\ten: \"Database size\",\n\t\tde: \"Datenbank-Gr\u00F6\u00DFe\",\n\t\tru: \"\u0420\u0430\u0437\u043C\u0435\u0440 \u0431\u0430\u0437\u044B \u0434\u0430\u043D\u043D\u044B\u0445\",\n\t\tpt: \"Tamanho da base de dados\",\n\t\tnl: \"Grootte van de database\",\n\t\tfr: \"Taille de la base\",\n\t\tit: \"Dimensione del database\",\n\t\tes: \"Tama\u00F1o de la base\",\n\t\tpl: \"Rozmiar bazy\",\n\t\tuk: \"\u0420\u043E\u0437\u043C\u0456\u0440 \u0431\u0430\u0437\u0438 \u0434\u0430\u043D\u0438\u0445\",\n\t\t\"zh-cn\": \"\u6570\u636E\u5E93\u5927\u5C0F\",\n\t},\n\t/** Last error of the instance */\n\tlastError: {\n\t\ten: \"Last error\",\n\t\tde: \"Letzter Fehler\",\n\t\tru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430\",\n\t\tpt: \"\u00DAltimo erro\",\n\t\tnl: \"Laatste fout\",\n\t\tfr: \"Derni\u00E8re erreur\",\n\t\tit: \"Ultimo errore\",\n\t\tes: \"\u00DAltimo error\",\n\t\tpl: \"Ostatni b\u0142\u0105d\",\n\t\tuk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u043F\u043E\u043C\u0438\u043B\u043A\u0430\",\n\t\t\"zh-cn\": \"\u6700\u540E\u7684\u9519\u8BEF\",\n\t},\n\t/** Instant of the last backup */\n\tlastBackup: {\n\t\ten: \"Last backup\",\n\t\tde: \"Letzte Sicherung\",\n\t\tru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u0440\u0435\u0437\u0435\u0440\u0432\u043D\u0430\u044F \u043A\u043E\u043F\u0438\u044F\",\n\t\tpt: \"\u00DAltima c\u00F3pia de seguran\u00E7a\",\n\t\tnl: \"Laatste back-up\",\n\t\tfr: \"Derni\u00E8re sauvegarde\",\n\t\tit: \"Ultimo backup\",\n\t\tes: \"\u00DAltima copia de seguridad\",\n\t\tpl: \"Ostatnia kopia zapasowa\",\n\t\tuk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u0440\u0435\u0437\u0435\u0440\u0432\u043D\u0430 \u043A\u043E\u043F\u0456\u044F\",\n\t\t\"zh-cn\": \"\u6700\u540E\u7684\u5907\u4EFD\",\n\t},\n\t/** Number of employees that are present */\n\tcompanyPresentCount: {\n\t\ten: \"Present employees\",\n\t\tde: \"Anwesende Mitarbeiter\",\n\t\tru: \"\u041F\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u0441\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A\u0438\",\n\t\tpt: \"Funcion\u00E1rios presentes\",\n\t\tnl: \"Aanwezige medewerkers\",\n\t\tfr: \"Employ\u00E9s pr\u00E9sents\",\n\t\tit: \"Dipendenti presenti\",\n\t\tes: \"Empleados presentes\",\n\t\tpl: \"Obecni pracownicy\",\n\t\tuk: \"\u041F\u0440\u0438\u0441\u0443\u0442\u043D\u0456 \u0441\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A\u0438\",\n\t\t\"zh-cn\": \"\u5728\u5C97\u5458\u5DE5\",\n\t},\n\t/** Names of the employees that are present */\n\tcompanyPresent: {\n\t\ten: \"Who is present\",\n\t\tde: \"Wer ist anwesend\",\n\t\tru: \"\u041A\u0442\u043E \u043F\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442\",\n\t\tpt: \"Quem est\u00E1 presente\",\n\t\tnl: \"Wie is aanwezig\",\n\t\tfr: \"Qui est pr\u00E9sent\",\n\t\tit: \"Chi \u00E8 presente\",\n\t\tes: \"Qui\u00E9n est\u00E1 presente\",\n\t\tpl: \"Kto jest obecny\",\n\t\tuk: \"\u0425\u0442\u043E \u043F\u0440\u0438\u0441\u0443\u0442\u043D\u0456\u0439\",\n\t\t\"zh-cn\": \"\u8C01\u5728\u5C97\",\n\t},\n\t/** Instant of the newest punch of the company */\n\tcompanyLastPunch: {\n\t\ten: \"Last punch\",\n\t\tde: \"Letzte Buchung\",\n\t\tru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0442\u043C\u0435\u0442\u043A\u0430\",\n\t\tpt: \"\u00DAltimo registo\",\n\t\tnl: \"Laatste stempeling\",\n\t\tfr: \"Dernier pointage\",\n\t\tit: \"Ultima timbratura\",\n\t\tes: \"\u00DAltimo registro\",\n\t\tpl: \"Ostatni wpis\",\n\t\tuk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043C\u0456\u0442\u043A\u0430\",\n\t\t\"zh-cn\": \"\u6700\u540E\u6253\u5361\",\n\t},\n\t/** Instant of the newest event */\n\tlastAt: {\n\t\ten: \"Last event at\",\n\t\tde: \"Letztes Ereignis um\",\n\t\tru: \"\u0412\u0440\u0435\u043C\u044F \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Hora do \u00FAltimo evento\",\n\t\tnl: \"Tijdstip laatste gebeurtenis\",\n\t\tfr: \"Heure du dernier \u00E9v\u00E9nement\",\n\t\tit: \"Ora dell'ultimo evento\",\n\t\tes: \"Hora del \u00FAltimo evento\",\n\t\tpl: \"Czas ostatniego zdarzenia\",\n\t\tuk: \"\u0427\u0430\u0441 \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u043F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u6700\u540E\u4E8B\u4EF6\u65F6\u95F4\",\n\t},\n\t/** Kind of the newest event */\n\tlastType: {\n\t\ten: \"Kind of the last event\",\n\t\tde: \"Art des letzten Ereignisses\",\n\t\tru: \"\u0422\u0438\u043F \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Tipo do \u00FAltimo evento\",\n\t\tnl: \"Soort laatste gebeurtenis\",\n\t\tfr: \"Type du dernier \u00E9v\u00E9nement\",\n\t\tit: \"Tipo dell'ultimo evento\",\n\t\tes: \"Tipo del \u00FAltimo evento\",\n\t\tpl: \"Rodzaj ostatniego zdarzenia\",\n\t\tuk: \"\u0422\u0438\u043F \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u043F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u6700\u540E\u4E8B\u4EF6\u7C7B\u578B\",\n\t},\n\t/** Employee of the newest event */\n\tlastUser: {\n\t\ten: \"Employee of the last event\",\n\t\tde: \"Mitarbeiter des letzten Ereignisses\",\n\t\tru: \"\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Funcion\u00E1rio do \u00FAltimo evento\",\n\t\tnl: \"Medewerker van de laatste gebeurtenis\",\n\t\tfr: \"Employ\u00E9 du dernier \u00E9v\u00E9nement\",\n\t\tit: \"Dipendente dell'ultimo evento\",\n\t\tes: \"Empleado del \u00FAltimo evento\",\n\t\tpl: \"Pracownik ostatniego zdarzenia\",\n\t\tuk: \"\u0421\u043F\u0456\u0432\u0440\u043E\u0431\u0456\u0442\u043D\u0438\u043A \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u043F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u6700\u540E\u4E8B\u4EF6\u7684\u5458\u5DE5\",\n\t},\n\t/** Direction of the newest punch */\n\tlastDirection: {\n\t\ten: \"Direction of the last punch\",\n\t\tde: \"Richtung der letzten Buchung\",\n\t\tru: \"\u041D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0439 \u043E\u0442\u043C\u0435\u0442\u043A\u0438\",\n\t\tpt: \"Dire\u00E7\u00E3o do \u00FAltimo registo\",\n\t\tnl: \"Richting van de laatste stempeling\",\n\t\tfr: \"Sens du dernier pointage\",\n\t\tit: \"Direzione dell'ultima timbratura\",\n\t\tes: \"Direcci\u00F3n del \u00FAltimo registro\",\n\t\tpl: \"Kierunek ostatniego wpisu\",\n\t\tuk: \"\u041D\u0430\u043F\u0440\u044F\u043C\u043E\u043A \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u0432\u0456\u0434\u043C\u0456\u0442\u043A\u0438\",\n\t\t\"zh-cn\": \"\u6700\u540E\u6253\u5361\u7684\u65B9\u5411\",\n\t},\n\t/** Source of the newest event */\n\tlastSource: {\n\t\ten: \"Source of the last event\",\n\t\tde: \"Quelle des letzten Ereignisses\",\n\t\tru: \"\u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u0431\u044B\u0442\u0438\u044F\",\n\t\tpt: \"Origem do \u00FAltimo evento\",\n\t\tnl: \"Bron van de laatste gebeurtenis\",\n\t\tfr: \"Source du dernier \u00E9v\u00E9nement\",\n\t\tit: \"Origine dell'ultimo evento\",\n\t\tes: \"Origen del \u00FAltimo evento\",\n\t\tpl: \"\u0179r\u00F3d\u0142o ostatniego zdarzenia\",\n\t\tuk: \"\u0414\u0436\u0435\u0440\u0435\u043B\u043E \u043E\u0441\u0442\u0430\u043D\u043D\u044C\u043E\u0457 \u043F\u043E\u0434\u0456\u0457\",\n\t\t\"zh-cn\": \"\u6700\u540E\u4E8B\u4EF6\u7684\u6765\u6E90\",\n\t},\n\t/** Channel with the calendar of the instance */\n\tcalendarChannel: {\n\t\ten: \"Calendar\",\n\t\tde: \"Kalender\",\n\t\tru: \"\u041A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044C\",\n\t\tpt: \"Calend\u00E1rio\",\n\t\tnl: \"Kalender\",\n\t\tfr: \"Calendrier\",\n\t\tit: \"Calendario\",\n\t\tes: \"Calendario\",\n\t\tpl: \"Kalendarz\",\n\t\tuk: \"\u041A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\",\n\t\t\"zh-cn\": \"\u65E5\u5386\",\n\t},\n\t/** Subscription link of the company calendar */\n\tcalendarFeedUrl: {\n\t\ten: \"Subscription link of the company\",\n\t\tde: \"Abo-Link der Firma\",\n\t\tru: \"\u0421\u0441\u044B\u043B\u043A\u0430 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u0444\u0438\u0440\u043C\u044B\",\n\t\tpt: \"Liga\u00E7\u00E3o de subscri\u00E7\u00E3o da empresa\",\n\t\tnl: \"Abonnementslink van het bedrijf\",\n\t\tfr: \"Lien d'abonnement de l'entreprise\",\n\t\tit: \"Link di sottoscrizione dell'azienda\",\n\t\tes: \"Enlace de suscripci\u00F3n de la empresa\",\n\t\tpl: \"Link subskrypcji firmy\",\n\t\tuk: \"\u041F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F \u043F\u0456\u0434\u043F\u0438\u0441\u043A\u0438 \u043A\u043E\u043C\u043F\u0430\u043D\u0456\u0457\",\n\t\t\"zh-cn\": \"\u516C\u53F8\u7684\u8BA2\u9605\u94FE\u63A5\",\n\t},\n\t/** Absolute path of the written calendar file (D11) */\n\tcalendarFeedFile: {\n\t\ten: \"Path of the calendar file (.ics)\",\n\t\tde: \"Pfad der Kalenderdatei (.ics)\",\n\t\tru: \"\u041F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F (.ics)\",\n\t\tpt: \"Caminho do ficheiro de calend\u00E1rio (.ics)\",\n\t\tnl: \"Pad van het kalenderbestand (.ics)\",\n\t\tfr: \"Chemin du fichier de calendrier (.ics)\",\n\t\tit: \"Percorso del file di calendario (.ics)\",\n\t\tes: \"Ruta del archivo de calendario (.ics)\",\n\t\tpl: \"\u015Acie\u017Cka pliku kalendarza (.ics)\",\n\t\tuk: \"\u0428\u043B\u044F\u0445 \u0434\u043E \u0444\u0430\u0439\u043B\u0443 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F (.ics)\",\n\t\t\"zh-cn\": \"\u65E5\u5386\u6587\u4EF6\u7684\u8DEF\u5F84\uFF08.ics\uFF09\",\n\t},\n\t/** Instant the calendar was written */\n\tcalendarUpdatedAt: {\n\t\ten: \"Calendar written at\",\n\t\tde: \"Kalender geschrieben um\",\n\t\tru: \"\u0412\u0440\u0435\u043C\u044F \u0437\u0430\u043F\u0438\u0441\u0438 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F\",\n\t\tpt: \"Hora de escrita do calend\u00E1rio\",\n\t\tnl: \"Tijdstip kalender geschreven\",\n\t\tfr: \"Heure d'\u00E9criture du calendrier\",\n\t\tit: \"Ora di scrittura del calendario\",\n\t\tes: \"Hora de escritura del calendario\",\n\t\tpl: \"Czas zapisu kalendarza\",\n\t\tuk: \"\u0427\u0430\u0441 \u0437\u0430\u043F\u0438\u0441\u0443 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F\",\n\t\t\"zh-cn\": \"\u65E5\u5386\u5199\u5165\u65F6\u95F4\",\n\t},\n\t/** The absences as JSON */\n\tcalendarAbsences: {\n\t\ten: \"Absences (JSON)\",\n\t\tde: \"Abwesenheiten (JSON)\",\n\t\tru: \"\u041E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044F (JSON)\",\n\t\tpt: \"Aus\u00EAncias (JSON)\",\n\t\tnl: \"Afwezigheden (JSON)\",\n\t\tfr: \"Absences (JSON)\",\n\t\tit: \"Assenze (JSON)\",\n\t\tes: \"Ausencias (JSON)\",\n\t\tpl: \"Nieobecno\u015Bci (JSON)\",\n\t\tuk: \"\u0412\u0456\u0434\u0441\u0443\u0442\u043D\u043E\u0441\u0442\u0456 (JSON)\",\n\t\t\"zh-cn\": \"\u7F3A\u52E4\uFF08JSON\uFF09\",\n\t},\n\t/** Command that creates or renews the calendar link of the company */\n\trotateCalendarToken: {\n\t\ten: \"Create or renew the calendar link of the company\",\n\t\tde: \"Kalender-Link der Firma anlegen oder erneuern\",\n\t\tru: \"\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u0438\u043B\u0438 \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F \u0444\u0438\u0440\u043C\u044B\",\n\t\tpt: \"Criar ou renovar a liga\u00E7\u00E3o do calend\u00E1rio da empresa\",\n\t\tnl: \"Kalenderlink van het bedrijf aanmaken of vernieuwen\",\n\t\tfr: \"Cr\u00E9er ou renouveler le lien du calendrier de l'entreprise\",\n\t\tit: \"Creare o rinnovare il link del calendario dell'azienda\",\n\t\tes: \"Crear o renovar el enlace del calendario de la empresa\",\n\t\tpl: \"Utw\u00F3rz lub od\u015Bwie\u017C link kalendarza firmy\",\n\t\tuk: \"\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0430\u0431\u043E \u043E\u043D\u043E\u0432\u0438\u0442\u0438 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F \u043A\u043E\u043C\u043F\u0430\u043D\u0456\u0457\",\n\t\t\"zh-cn\": \"\u521B\u5EFA\u6216\u66F4\u65B0\u516C\u53F8\u7684\u65E5\u5386\u94FE\u63A5\",\n\t},\n} satisfies Record<string, StateName>;\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCO,MAAM,cAAc;AAAA;AAAA,EAE1B,cAAc;AAAA,IACb,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,iBAAiB;AAAA,IAChB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,iBAAiB;AAAA,IAChB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,aAAa;AAAA,IACZ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,gBAAgB;AAAA,IACf,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,eAAe;AAAA,IACd,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,aAAa;AAAA,IACZ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,aAAa;AAAA,IACZ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,cAAc;AAAA,IACb,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,WAAW;AAAA,IACV,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,oBAAoB;AAAA,IACnB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,qBAAqB;AAAA,IACpB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,oBAAoB;AAAA,IACnB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,qBAAqB;AAAA,IACpB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,oBAAoB;AAAA,IACnB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,eAAe;AAAA,IACd,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,aAAa;AAAA,IACZ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,OAAO;AAAA,IACN,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,YAAY;AAAA,IACX,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,YAAY;AAAA,IACX,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,QAAQ;AAAA,IACP,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,eAAe;AAAA,IACd,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,SAAS;AAAA,IACR,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,eAAe;AAAA,IACd,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,aAAa;AAAA,IACZ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,WAAW;AAAA,IACV,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,YAAY;AAAA,IACX,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,qBAAqB;AAAA,IACpB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,gBAAgB;AAAA,IACf,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,kBAAkB;AAAA,IACjB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,QAAQ;AAAA,IACP,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,UAAU;AAAA,IACT,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,UAAU;AAAA,IACT,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,eAAe;AAAA,IACd,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,YAAY;AAAA,IACX,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,iBAAiB;AAAA,IAChB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,iBAAiB;AAAA,IAChB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,kBAAkB;AAAA,IACjB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,mBAAmB;AAAA,IAClB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,kBAAkB;AAAA,IACjB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AAAA;AAAA,EAEA,qBAAqB;AAAA,IACpB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS;AAAA,EACV;AACD;",
6
6
  "names": []
7
7
  }
package/build/main.js CHANGED
@@ -47,6 +47,7 @@ var import_closing = require("./lib/services/closing");
47
47
  var import_backup = require("./lib/services/backup");
48
48
  var import_sync = require("./lib/services/sync");
49
49
  var import_calendar = require("./lib/services/calendar");
50
+ var import_calendarFile = require("./lib/adapter/calendarFile");
50
51
  var import_states = require("./lib/adapter/states");
51
52
  var import_presence = require("./lib/adapter/presence");
52
53
  var import_commands = require("./lib/adapter/commands");
@@ -60,7 +61,6 @@ var import_static = require("./lib/web/static");
60
61
  const SUPPORTED_COUNTRIES = ["CH", "DE", "AT"];
61
62
  const SESSION_PURGE_MINUTES = 30;
62
63
  const STATE_REFRESH_MINUTES = 5;
63
- const CALENDAR_FILE_NAME = "calendar.ics";
64
64
  const BACKUP_MAX_AGE_HOURS = 20;
65
65
  const AUTOMATION_CHECK_MINUTES = 1;
66
66
  const TAG_SECRET_FILE_NAME = "tag-secret";
@@ -145,6 +145,15 @@ class TimeTracker extends utils.Adapter {
145
145
  this.runAutomationRules();
146
146
  this.setInterval(() => this.runAutomationRules(), AUTOMATION_CHECK_MINUTES * 60 * 1e3);
147
147
  await this.publishInstanceInfo();
148
+ try {
149
+ if (await (0, import_calendarFile.ensureCalendarFolder)(this, this.namespace)) {
150
+ this.log.debug(`prepared the folder 'files/${this.namespace}'`);
151
+ }
152
+ } catch (error) {
153
+ this.log.warn(
154
+ `the folder 'files/${this.namespace}' could not be prepared: ${error.message}`
155
+ );
156
+ }
148
157
  await this.refreshStates();
149
158
  this.setInterval(
150
159
  () => {
@@ -619,14 +628,9 @@ class TimeTracker extends utils.Adapter {
619
628
  services.users.list().map((user) => [user.id, { id: user.id, displayName: user.displayName, login: user.login }])
620
629
  );
621
630
  const list = services.absences.allInRange(from, to);
622
- const folder = path.join(utils.getAbsoluteDefaultDataDir(), "files", `${this.name}.${this.instance}`);
623
- const file = path.join(folder, CALENDAR_FILE_NAME);
624
- fs.mkdirSync(folder, { recursive: true });
625
- fs.writeFileSync(
626
- file,
627
- (0, import_calendar.calendarDocument)(import_calendar.COMPANY_CALENDAR_NAME, (0, import_calendar.absenceEvents)(list, employees, true), stamp),
628
- "utf8"
629
- );
631
+ const file = path.join(utils.getAbsoluteDefaultDataDir(), "files", this.namespace, import_calendarFile.CALENDAR_FILE_NAME);
632
+ const document = (0, import_calendar.calendarDocument)(import_calendar.COMPANY_CALENDAR_NAME, (0, import_calendar.absenceEvents)(list, employees, true), stamp);
633
+ await (0, import_calendarFile.writeCalendarFile)(this, this.namespace, document);
630
634
  const token = services.settings.get("calendar_token");
631
635
  await (0, import_states.publishCalendarSnapshot)(this, {
632
636
  // the host part is the name of this machine: an address that is reachable from the LAN and simple enough
package/build/main.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/main.ts"],
4
- "sourcesContent": ["/*\n * ioBroker adapter \"time-tracker\" \u2013 time tracking\n * Scaffolded with @iobroker/create-adapter v3.1.5\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { hostname } from \"node:os\";\nimport * as utils from \"@iobroker/adapter-core\";\nimport { currentSchemaVersion, openAndMigrate, type Db } from \"./lib/db/database\";\nimport { seed } from \"./lib/db/seed\";\nimport type { HolidayCountry } from \"./lib/domain/holidays\";\nimport { createAbsencesRepository } from \"./lib/db/repositories/absences\";\nimport { createDayNotesRepository, type DayNotesRepository } from \"./lib/db/repositories/dayNotes\";\nimport { createEntriesRepository } from \"./lib/db/repositories/entries\";\nimport { createHolidaysRepository } from \"./lib/db/repositories/holidays\";\nimport { createPayoutsRepository } from \"./lib/db/repositories/payouts\";\nimport { createRulesRepository } from \"./lib/db/repositories/rules\";\nimport { createSettingsRepository } from \"./lib/db/repositories/settings\";\nimport { createUsersRepository, type UserRecord, type UsersRepository } from \"./lib/db/repositories/users\";\nimport {\n\tcreateAutomationsRepository,\n\ttype AutomationRuleRecord,\n\ttype AutomationsRepository,\n} from \"./lib/db/repositories/automations\";\nimport { createTerminalsRepository } from \"./lib/db/repositories/terminals\";\nimport { createRfidRepository } from \"./lib/db/repositories/rfid\";\nimport {\n\tcreateTriggersRepository,\n\ttype TriggerRuleRecord,\n\ttype TriggersRepository,\n} from \"./lib/db/repositories/triggers\";\nimport type { EntriesRepository } from \"./lib/db/repositories/entries\";\nimport type { AbsencesRepository } from \"./lib/db/repositories/absences\";\nimport type { SettingsRepository } from \"./lib/db/repositories/settings\";\nimport { createAggregationService, type AggregationService } from \"./lib/services/aggregation\";\nimport { createAuthService, hashPassword } from \"./lib/services/auth\";\nimport { SECRET_FILE_NAME, resolveSessionSecret } from \"./lib/services/sessionSecret\";\nimport { createClosingService, type ClosingService } from \"./lib/services/closing\";\nimport {\n\tapplyPendingRestore,\n\tcreateBackupService,\n\trecordRestore,\n\ttype AppliedRestore,\n\ttype BackupService,\n} from \"./lib/services/backup\";\nimport { createSyncService, type SyncService } from \"./lib/services/sync\";\nimport {\n\tabsenceEvents,\n\tabsenceFeed,\n\tAPI_PREFIX,\n\tcalendarDocument,\n\tcompanyFeedUrl,\n\tCOMPANY_CALENDAR_NAME,\n} from \"./lib/services/calendar\";\nimport {\n\tCOMMAND_IDS,\n\tcreateCalendarStates,\n\tcreateCommandStates,\n\tcreateCompanyStates,\n\tcreateEventStates,\n\tcreateInfoStates,\n\tpublishAllUserStates,\n\tpublishCalendarSnapshot,\n\tpublishCompanySnapshot,\n\tpublishEventSnapshot,\n\treadCompanySnapshot,\n} from \"./lib/adapter/states\";\nimport { PRESENCE_SUFFIX, handlePresenceState, parsePresenceStateId, presenceEvent } from \"./lib/adapter/presence\";\nimport { handleCommand, punchEmployee, punchEvent, type CommandResult } from \"./lib/adapter/commands\";\nimport { evaluateTrigger, triggerText } from \"./lib/adapter/triggers\";\nimport { evaluateAutomation, isoWeekday, runPeriod, workBlock } from \"./lib/adapter/automation\";\nimport { handleMessage } from \"./lib/adapter/messages\";\nimport { createApi, MAX_BACKUP_UPLOAD_BYTES } from \"./lib/web/api\";\nimport type { ApiEvent, EventBus } from \"./lib/web/events\";\nimport { startWebServer, type WebServer } from \"./lib/web/server\";\nimport { localDateTime } from \"./lib/util/time\";\nimport { createStaticHandler } from \"./lib/web/static\";\n\nconst SUPPORTED_COUNTRIES: HolidayCountry[] = [\"CH\", \"DE\", \"AT\"];\n\n/** How often expired sessions are removed (minutes). */\nconst SESSION_PURGE_MINUTES = 30;\n\n/** How often the published figures are refreshed (minutes). */\nconst STATE_REFRESH_MINUTES = 5;\n\n/** File the company calendar is written into (inside the instance folder). */\nconst CALENDAR_FILE_NAME = \"calendar.ics\";\n\n/** How old the newest backup may be before the daily check writes a new one. */\nconst BACKUP_MAX_AGE_HOURS = 20;\n\n/** How often the automation rules are checked (minutes). */\nconst AUTOMATION_CHECK_MINUTES = 1;\n\n/** Name of the file that holds a generated badge (HMAC) secret next to the database. */\nconst TAG_SECRET_FILE_NAME = \"tag-secret\";\n\n/**\n * Events of the API that change the published figures.\n *\n * A punch or a correction arrives through the API of this very process, so nothing would republish the states\n * until the five minute timer fires; these events say exactly when data changed.\n */\nconst FIGURES_EVENT_TYPES = [\"punch\", \"terminal.punch\", \"rfid.scan\", \"entry.update\", \"entry.delete\", \"absence.change\"];\n\n/** Services created at startup. */\ninterface AdapterServices {\n\tusers: UsersRepository;\n\tentries: EntriesRepository;\n\tabsences: AbsencesRepository;\n\tdayNotes: DayNotesRepository;\n\tsettings: SettingsRepository;\n\taggregation: AggregationService;\n\tsync: SyncService;\n\tclosing: ClosingService;\n\tbackup: BackupService;\n\ttriggers: TriggersRepository;\n\tautomations: AutomationsRepository;\n}\n\nclass TimeTracker extends utils.Adapter {\n\tprivate db: Db | null = null;\n\tprivate webServer: WebServer | null = null;\n\tprivate services: AdapterServices | null = null;\n\t/** Bus of the API; `null` until the API is created */\n\tprivate events: EventBus | null = null;\n\t/** Last value seen per watched trigger state: only a change fires a rule */\n\tprivate readonly triggerValues = new Map<string, string>();\n\t/** Trigger states the adapter currently subscribes to */\n\tprivate readonly triggerStates = new Set<string>();\n\t/** Pending refresh of the figures after a change through the API */\n\tprivate stateRefreshTimer: ioBroker.Timeout | undefined = undefined;\n\n\t/** A refresh tick that is still running; the next tick is skipped instead of piling up on it. */\n\tprivate stateRefreshRunning = false;\n\n\t/** A backup run that is still running; the hourly check must not start a second one. */\n\tprivate backupRunning = false;\n\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({\n\t\t\t...options,\n\t\t\tname: \"time-tracker\",\n\t\t});\n\t\tthis.on(\"ready\", this.onReady.bind(this));\n\t\tthis.on(\"stateChange\", this.onStateChange.bind(this));\n\t\t// this.on(\"objectChange\", this.onObjectChange.bind(this));\n\t\t// scripts, Blockly and other adapters reach the instance through `sendTo` messages\n\t\tthis.on(\"message\", this.onMessage.bind(this));\n\t\tthis.on(\"unload\", this.onUnload.bind(this));\n\t}\n\n\t/** Database file: configured path or `<adapter instance data dir>/time-tracker.sqlite`. */\n\tprivate databaseFile(): string {\n\t\tconst configured = (this.config.dbPath ?? \"\").trim();\n\t\tif (configured) {\n\t\t\treturn path.resolve(configured);\n\t\t}\n\n\t\tconst dir = utils.getAbsoluteInstanceDataDir(this);\n\t\tfs.mkdirSync(dir, { recursive: true });\n\t\treturn path.join(dir, \"time-tracker.sqlite\");\n\t}\n\n\t/** Configured holiday country, falling back to Switzerland. */\n\tprivate holidayCountry(): HolidayCountry {\n\t\tconst configured = (this.config.holidayCountry ?? \"\").toUpperCase() as HolidayCountry;\n\t\treturn SUPPORTED_COUNTRIES.includes(configured) ? configured : \"CH\";\n\t}\n\n\t/**\n\t * Is called when databases are connected and adapter received configuration.\n\t */\n\tprivate async onReady(): Promise<void> {\n\t\ttry {\n\t\t\t// Reset the connection indicator during startup\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\n\t\t\tconst file = this.databaseFile();\n\t\t\t// A restore that the administration queued is applied before the database is opened: the swap needs a\n\t\t\t// closed file, and that is the only moment the adapter can offer it (see the backup service).\n\t\t\tlet applied: AppliedRestore | null = null;\n\t\t\ttry {\n\t\t\t\tapplied = applyPendingRestore(file);\n\t\t\t\tif (applied) {\n\t\t\t\t\tthis.log.info(\n\t\t\t\t\t\t`restored the database from ${applied.restored.name}: ${applied.restored.users} users, ${applied.restored.entries} punches, previous file ${applied.previous ?? \"(none)\"}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// the queued files stay in place, so a working backup can be handed in afterwards\n\t\t\t\tthis.log.error(`the queued restore was refused: ${(error as Error).message}`);\n\t\t\t}\n\t\t\tthis.db = openAndMigrate(file, message => this.log.debug(message));\n\t\t\tif (applied) {\n\t\t\t\t// the audit entry can only be written once the restored database is open\n\t\t\t\trecordRestore(this.db, applied.restored, applied.actorId);\n\t\t\t}\n\n\t\t\tconst year = new Date().getUTCFullYear();\n\t\t\tconst country = this.holidayCountry();\n\t\t\tconst result = seed(this.db, { holidayCountry: country, holidayYears: [year, year + 1] });\n\t\t\tthis.log.info(\n\t\t\t\t`database ready at ${file} (${result.permissions} permissions, ${result.holidays} holidays added for ${country})`,\n\t\t\t);\n\n\t\t\t// Without an account nobody can log in, so the first administrator is created here (Phase 2 of the\n\t\t\t// specification: \"Standard-Admin, Passwort mit Pflichtwechsel\").\n\t\t\tthis.ensureAdministrator(this.db);\n\n\t\t\t// the settings of the admin UI win over the stored values (before the services are built)\n\t\t\tthis.applyConfiguration(createSettingsRepository(this.db));\n\n\t\t\t// NOTE: The implementation follows the internal specification (not part of this repository):\n\t\t\t// Phase 1\u20133 \u2013 storage, punch logic, target time, breaks, overtime, vacation\n\t\t\t// Phase 4 \u2013 REST API on an own port (router, sessions, RBAC)\n\t\t\t// Phase 5 \u2013 PWA delivered from www/\n\t\t\t// Phase 8 \u2013 publish aggregates as states and react to command.*\n\n\t\t\tawait this.startApi();\n\t\t\t// creates the command and info states, so the instance information can be published afterwards\n\t\t\tawait this.subscribeCommands();\n\t\t\t// watches the states the trigger rules name (fingerprint reader, button, door contact, \u2026)\n\t\t\tawait this.refreshTriggerSubscriptions();\n\t\t\t// the automation rules look at the clock, so they are checked once a minute\n\t\t\tthis.runAutomationRules();\n\t\t\tthis.setInterval(() => this.runAutomationRules(), AUTOMATION_CHECK_MINUTES * 60 * 1000);\n\t\t\tawait this.publishInstanceInfo();\n\t\t\tawait this.refreshStates();\n\n\t\t\t// figures are refreshed regularly (the timer is cleared automatically on unload). A tick that finds the\n\t\t\t// previous run still busy is skipped: a slow run must not pile a second one on top of itself.\n\t\t\tthis.setInterval(\n\t\t\t\t() => {\n\t\t\t\t\tif (this.stateRefreshRunning) {\n\t\t\t\t\t\tthis.log.debug(`state refresh still running, skipping this ${STATE_REFRESH_MINUTES} min tick`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.stateRefreshRunning = true;\n\t\t\t\t\tvoid Promise.allSettled([this.publishInstanceInfo(), this.refreshStates()]).then(results => {\n\t\t\t\t\t\tthis.stateRefreshRunning = false;\n\t\t\t\t\t\tfor (const result of results) {\n\t\t\t\t\t\t\tif (result.status === \"rejected\") {\n\t\t\t\t\t\t\t\tthis.log.warn(`state refresh failed: ${(result.reason as Error).message}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tSTATE_REFRESH_MINUTES * 60 * 1000,\n\t\t\t);\n\n\t\t\t// one backup per day: the check runs every hour, so a missed run is caught up after a restart. A run that\n\t\t\t// is still going (a big database, a slow disk) blocks the next check instead of starting a second copy.\n\t\t\tawait this.runScheduledBackup();\n\t\t\tthis.setInterval(\n\t\t\t\t() => {\n\t\t\t\t\tif (this.backupRunning) {\n\t\t\t\t\t\tthis.log.debug(\"scheduled backup still running, skipping this hour\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.backupRunning = true;\n\t\t\t\t\tvoid this.runScheduledBackup()\n\t\t\t\t\t\t.catch(error => this.log.warn(`scheduled backup failed: ${(error as Error).message}`))\n\t\t\t\t\t\t.finally(() => {\n\t\t\t\t\t\t\tthis.backupRunning = false;\n\t\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\t60 * 60 * 1000,\n\t\t\t);\n\n\t\t\t// Service is ready\n\t\t\tawait this.setState(\"info.connection\", true, true);\n\t\t} catch (error) {\n\t\t\tconst message = (error as Error).message;\n\t\t\tthis.log.error(`Startup failed: ${message}`);\n\t\t\tawait this.setState(\"info.lastError\", message, true);\n\t\t}\n\t}\n\n\t/**\n\t * Creates the first administrator when the instance has none.\n\t *\n\t * The account is created with `must_change_pw = 1`, so the start password opens the door exactly once.\n\t * Login and password can be configured; without a configured password a random one is generated and written\n\t * to the log, because there is no other way to hand it to the operator.\n\t *\n\t * @param db - open database handle\n\t */\n\tprivate ensureAdministrator(db: Db): void {\n\t\tconst users = createUsersRepository(db);\n\t\tconst administrators = users.list().filter(user => users.roles(user.id).includes(\"admin\"));\n\t\tif (administrators.length > 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst login = (this.config.adminLogin ?? \"\").trim() || \"admin\";\n\t\tconst configured = (this.config.adminPassword ?? \"\").trim();\n\t\tconst password = configured || `Zf-${randomBytes(9).toString(\"base64url\")}-7`;\n\n\t\ttry {\n\t\t\tusers.create({\n\t\t\t\tlogin,\n\t\t\t\tdisplayName: login,\n\t\t\t\tpasswordHash: hashPassword(password),\n\t\t\t\tmustChangePw: true,\n\t\t\t\ttimezone: this.config.timezone || \"Europe/Berlin\",\n\t\t\t\troleKeys: [\"admin\"],\n\t\t\t\tnow: Math.floor(Date.now() / 1000),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`the administrator \"${login}\" could not be created: ${(error as Error).message}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (configured) {\n\t\t\tthis.log.info(\n\t\t\t\t`administrator \"${login}\" created with the configured start password - it has to be changed at the first login`,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.log.warn(\n\t\t\t\t`administrator \"${login}\" created with the start password \"${password}\" - change it at the first login`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Copies the instance configuration into the stored settings and publishes the instance information.\n\t *\n\t * The ioBroker admin UI is where an operator configures the instance, so a configured value wins over what\n\t * is stored in the database; `PUT /api/settings` stays for the keys the admin UI does not offer. Empty\n\t * fields are ignored, so clearing a field never wipes a stored setting.\n\t *\n\t * @param settings - instance settings\n\t */\n\tprivate applyConfiguration(settings: SettingsRepository): void {\n\t\tconst text = (value: string | undefined): string | null => (value?.trim() ? value.trim() : null);\n\t\tconst number = (value: number | undefined): string | null =>\n\t\t\ttypeof value === \"number\" && Number.isFinite(value) ? String(value) : null;\n\t\tconst flag = (value: boolean | undefined): string | null => (value === undefined ? null : value ? \"1\" : \"0\");\n\n\t\tconst mapped: [string, string | null][] = [\n\t\t\t[\"holiday_country\", text(this.config.holidayCountry)?.toUpperCase() ?? null],\n\t\t\t[\"timezone\", text(this.config.timezone)],\n\t\t\t[\"default_language\", text(this.config.defaultLanguage)],\n\t\t\t[\"edit_window_days\", number(this.config.editWindowDays)],\n\t\t\t[\"quick_round_minutes\", number(this.config.quickRoundMinutes)],\n\t\t\t[\"session_ttl_minutes\", number(this.config.sessionTtlMinutes)],\n\t\t\t[\"backup_retention_days\", number(this.config.backupRetentionDays)],\n\t\t\t[\"absence_calc_until_today\", flag(this.config.absenceCalcUntilToday)],\n\t\t\t[\"absence_deduct_worktime\", flag(this.config.absenceDeductWorktime)],\n\t\t];\n\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\tfor (const [key, value] of mapped) {\n\t\t\tif (value !== null && settings.get(key) !== value) {\n\t\t\t\tsettings.set(key, value, null, now);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Publishes version, database size and schema version as states (specification 5.1).\n\t *\n\t * The objects are created by `createInfoStates`, so this runs after the command states were set up.\n\t */\n\tprivate async publishInstanceInfo(): Promise<void> {\n\t\tawait this.setState(\"info.version\", this.version ?? \"0.0.0\", true);\n\t\tconst file = this.db ? this.databaseFile() : \"\";\n\t\tawait this.setState(\"info.dbSizeBytes\", file && fs.existsSync(file) ? fs.statSync(file).size : 0, true);\n\t\tif (this.db) {\n\t\t\tawait this.setState(\"info.schemaVersion\", String(currentSchemaVersion(this.db)), true);\n\t\t}\n\t}\n\n\t/**\n\t * Secret used to sign CSRF tokens. A value from the instance settings wins; without one the adapter generates a\n\t * secret on the first start and stores it next to the database, so a restart keeps the CSRF tokens of clients\n\t * that are already open valid.\n\t */\n\tprivate sessionSecret(): string {\n\t\tconst resolved = resolveSessionSecret({\n\t\t\tconfigured: this.config.sessionSecret,\n\t\t\tfile: path.join(path.dirname(this.databaseFile()), SECRET_FILE_NAME),\n\t\t});\n\n\t\tif (resolved.source === \"configured\") {\n\t\t\tthis.log.debug(\"session secret taken from the instance settings\");\n\t\t} else if (resolved.source === \"stored\") {\n\t\t\tthis.log.debug(`session secret taken from ${resolved.file}`);\n\t\t} else if (resolved.file) {\n\t\t\tthis.log.info(\n\t\t\t\t`no session secret configured - generated one and stored it at ${resolved.file}, so sessions and CSRF tokens survive restarts`,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.log.warn(\n\t\t\t\t`no session secret configured and it could not be stored (${resolved.error ?? \"unknown\"}) - a temporary one is used, so all sessions end with the next restart`,\n\t\t\t);\n\t\t}\n\n\t\treturn resolved.secret;\n\t}\n\n\t/**\n\t * Secret that signs the badge/NFC links.\n\t *\n\t * A value from the instance settings wins. Without one a secret is generated on the first start and stored next\n\t * to the database \u2014 otherwise a fresh installation could not create a badge at all and the administration would\n\t * only report \u201Cnot configured\u201D.\n\t */\n\tprivate tagSecret(): string {\n\t\tconst resolved = resolveSessionSecret({\n\t\t\tconfigured: this.config.hmacSecret,\n\t\t\tfile: path.join(path.dirname(this.databaseFile()), TAG_SECRET_FILE_NAME),\n\t\t});\n\n\t\tif (resolved.source === \"configured\") {\n\t\t\tthis.log.debug(\"badge link secret taken from the instance settings\");\n\t\t} else if (resolved.source === \"stored\") {\n\t\t\tthis.log.debug(`badge link secret taken from ${resolved.file}`);\n\t\t} else if (resolved.file) {\n\t\t\tthis.log.info(\n\t\t\t\t`no badge link secret configured - generated one and stored it at ${resolved.file}, so badges stay valid across restarts`,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.log.warn(\n\t\t\t\t`no badge link secret configured and it could not be stored (${resolved.error ?? \"unknown\"}) - a temporary one is used, so badges stop working with the next restart`,\n\t\t\t);\n\t\t}\n\n\t\treturn resolved.secret;\n\t}\n\n\t/**\n\t * Builds the services and starts the HTTP server on the configured port.\n\t *\n\t * Binding can fail (port in use); that must never stop the adapter, so the failure is only logged.\n\t */\n\tprivate async startApi(): Promise<void> {\n\t\tconst db = this.db;\n\t\tif (!db) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst users = createUsersRepository(db);\n\t\tconst entries = createEntriesRepository(db);\n\t\tconst absences = createAbsencesRepository(db);\n\t\tconst dayNotes = createDayNotesRepository(db);\n\t\tconst holidays = createHolidaysRepository(db);\n\t\tconst rules = createRulesRepository(db);\n\t\tconst payouts = createPayoutsRepository(db);\n\t\tconst terminals = createTerminalsRepository(db);\n\t\tconst rfid = createRfidRepository(db);\n\t\tconst settings = createSettingsRepository(db);\n\t\tconst auth = createAuthService({\n\t\t\tdb,\n\t\t\tusers,\n\t\t\tsettings,\n\t\t\tsecret: this.sessionSecret(),\n\t\t\tdefaultTtlMinutes: this.config.sessionTtlMinutes || 720,\n\t\t});\n\t\tconst aggregation = createAggregationService({\n\t\t\tdb,\n\t\t\tusers,\n\t\t\tentries,\n\t\t\tabsences,\n\t\t\tholidays,\n\t\t\trules,\n\t\t\tsettings,\n\t\t});\n\t\tconst triggers = createTriggersRepository(db);\n\t\tconst automations = createAutomationsRepository(db);\n\t\tconst sync = createSyncService({ db, entries, users, aggregation });\n\t\tconst closing = createClosingService({ db, aggregation, payouts });\n\t\t// backups live next to the database file: `<data dir>/backups/time-tracker-<timestamp>.sqlite`\n\t\tconst backup = createBackupService({\n\t\t\tdb,\n\t\t\tdir: path.join(path.dirname(this.databaseFile()), \"backups\"),\n\t\t\tretentionDays: Math.max(0, Number(this.config.backupRetentionDays ?? 30) || 0),\n\t\t});\n\t\tconst api = createApi({\n\t\t\tdb,\n\t\t\tauth,\n\t\t\tusers,\n\t\t\tentries,\n\t\t\tabsences,\n\t\t\tdayNotes,\n\t\t\tholidays,\n\t\t\trules,\n\t\t\tpayouts,\n\t\t\tterminals,\n\t\t\trfid,\n\t\t\ttriggers,\n\t\t\tautomations,\n\t\t\taggregation,\n\t\t\tsync,\n\t\t\tsettings,\n\t\t\tbackup,\n\t\t\tkioskEnabled: this.config.kioskEnabled === true,\n\t\t\t// a reverse proxy in front is the normal case for HTTPS; without the switch the forwarded\n\t\t\t// headers are ignored, so a client cannot choose its own address or the HTTPS flag\n\t\t\ttrustProxy: this.config.trustProxy === true,\n\t\t\thmacSecret: this.tagSecret(),\n\t\t\tversion: this.version,\n\t\t\t// a saved trigger rule changes the states the adapter has to watch\n\t\t\tonTriggerRulesChanged: () => void this.refreshTriggerSubscriptions(),\n\t\t});\n\n\t\tthis.services = {\n\t\t\tusers,\n\t\t\tentries,\n\t\t\tabsences,\n\t\t\tdayNotes,\n\t\t\tsettings,\n\t\t\taggregation,\n\t\t\tsync,\n\t\t\tclosing,\n\t\t\tbackup,\n\t\t\ttriggers,\n\t\t\tautomations,\n\t\t};\n\t\tthis.events = api.events;\n\t\t// the newest event is mirrored into the state tree, so a notification only has to watch `events.*`;\n\t\t// a punch, a correction or an absence changes the figures, so they are republished right away\n\t\tapi.events.subscribe(event => {\n\t\t\tvoid this.publishEventState(event);\n\t\t\tif (FIGURES_EVENT_TYPES.includes(event.type)) {\n\t\t\t\tthis.scheduleStateRefresh();\n\t\t\t}\n\t\t});\n\t\tthis.log.debug(`API routes: ${api.routes().length}`);\n\n\t\t// the web interface is delivered from `www/` next to the compiled code (built by the PWA project);\n\t\t// without it the adapter still provides the API, e.g. for the terminal\n\t\tconst webDir = path.join(__dirname, \"..\", \"www\");\n\t\tconst staticFiles = fs.existsSync(path.join(webDir, \"index.html\"))\n\t\t\t? createStaticHandler({ root: webDir })\n\t\t\t: undefined;\n\t\tthis.log.info(\n\t\t\tstaticFiles\n\t\t\t\t? `web interface found at ${webDir}`\n\t\t\t\t: `no web interface at ${webDir} - only the API (and the terminal) is available`,\n\t\t);\n\n\t\ttry {\n\t\t\tthis.webServer = await startWebServer({\n\t\t\t\trouter: api.router,\n\t\t\t\tport: this.config.port || 8092,\n\t\t\t\tbind: this.config.bind || \"127.0.0.1\",\n\t\t\t\t// the same prefix the calendar link carries (`/api`) \u2014 the web app and its login live beside it\n\t\t\t\tapiPrefix: API_PREFIX,\n\t\t\t\tstaticFiles,\n\t\t\t\t// an uploaded backup is bigger than the default limit; the route carries the same bound\n\t\t\t\tmaxBodyBytes: MAX_BACKUP_UPLOAD_BYTES,\n\t\t\t\t// live events for the PWA and the terminal: `/api/stream?token=...`\n\t\t\t\tstream: {\n\t\t\t\t\tauth,\n\t\t\t\t\tevents: api.events,\n\t\t\t\t\tversion: this.version,\n\t\t\t\t\ttimers: {\n\t\t\t\t\t\t// the adapter's own timer functions: they are cleared with the adapter on unload\n\t\t\t\t\t\tsetInterval: (handler, milliseconds) => this.setInterval(handler, milliseconds),\n\t\t\t\t\t\tclearInterval: handle => this.clearInterval(handle as ioBroker.Interval),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tlog: {\n\t\t\t\t\tinfo: message => this.log.info(message),\n\t\t\t\t\twarn: message => this.log.warn(message),\n\t\t\t\t\terror: message => this.log.error(message),\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.error(\n\t\t\t\t`API could not be started on port ${this.config.port}: ${(error as Error).message} - the web interface and the terminal stay unavailable`,\n\t\t\t);\n\t\t\tthis.webServer = null;\n\t\t\treturn;\n\t\t}\n\n\t\t// expired sessions are removed regularly; the timer is cleared automatically on unload\n\t\tthis.setInterval(\n\t\t\t() => {\n\t\t\t\ttry {\n\t\t\t\t\tconst removed = auth.purge();\n\t\t\t\t\tif (removed > 0) {\n\t\t\t\t\t\tthis.log.debug(`removed ${removed} expired session(s)`);\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.log.warn(`session cleanup failed: ${(error as Error).message}`);\n\t\t\t\t}\n\t\t\t},\n\t\t\tSESSION_PURGE_MINUTES * 60 * 1000,\n\t\t);\n\t}\n\n\t/**\n\t * Creates the command states and subscribes to them.\n\t */\n\tprivate async subscribeCommands(): Promise<void> {\n\t\ttry {\n\t\t\tawait createCommandStates(this);\n\t\t\tawait createInfoStates(this);\n\t\t\tawait createCompanyStates(this);\n\t\t\tawait createEventStates(this);\n\t\t\tawait createCalendarStates(this);\n\t\t\tawait this.subscribeStatesAsync(\"commands.*\");\n\t\t\t// the presence switch of every employee: written by a script, a fingerprint reader or a dashboard\n\t\t\tawait this.subscribeStatesAsync(`users.*.${PRESENCE_SUFFIX}`);\n\t\t\tthis.log.debug(\"command states ready\");\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`command states could not be created: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Writes a backup unless a recent one exists.\n\t *\n\t * The check runs hourly and once at start: a nightly backup therefore happens within an hour at the latest,\n\t * and an instance that was down for days does not write a burst of copies.\n\t */\n\tprivate async runScheduledBackup(): Promise<void> {\n\t\tconst backup = this.services?.backup;\n\t\tif (!backup) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst newest = backup.list()[0];\n\t\t\tconst ageHours = newest ? (Date.now() / 1000 - newest.createdAt) / 3600 : Number.POSITIVE_INFINITY;\n\t\t\tif (ageHours < BACKUP_MAX_AGE_HOURS) {\n\t\t\t\tthis.log.debug(`backup ${newest?.name ?? \"\"} is ${ageHours.toFixed(1)} h old, nothing to do`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst created = backup.create({ actorId: null, reason: \"scheduled\" });\n\t\t\tthis.log.info(\n\t\t\t\t`backup ${created.backup.name} written (${created.backup.sizeBytes} bytes, ${created.backup.users} employees, ${created.backup.entries} punches), ${created.removed.length} old file(s) removed`,\n\t\t\t);\n\t\t\tawait this.announceBackup(created.backup.name, created.backup.createdAt);\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`backup failed: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Publishes the instant of a written backup.\n\t *\n\t * @param name - file name of the backup\n\t * @param createdAt - instant the backup belongs to\n\t */\n\tprivate async announceBackup(name: string, createdAt: number): Promise<void> {\n\t\tawait this.setState(\"info.lastBackup\", createdAt, true);\n\t\tthis.events?.publish({\n\t\t\ttype: \"backup.create\",\n\t\t\tatUtc: createdAt,\n\t\t\tuserId: null,\n\t\t\tdata: { backup: name },\n\t\t});\n\t}\n\n\t/**\n\t * Applies a write on the presence state of an employee.\n\t *\n\t * `users.<id>.present` is the writable twin of `users.<id>.hasOpenEntry`: `true` opens an entry, `false` closes\n\t * it. Afterwards the figures of all employees are republished, so a dashboard sees the new state right away.\n\t *\n\t * @param id - state id without the instance prefix\n\t * @param value - value written by the user or a script\n\t */\n\tprivate async runPresence(id: string, value: ioBroker.StateValue): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = handlePresenceState(\n\t\t\t\t{\n\t\t\t\t\tentries: services.entries,\n\t\t\t\t\tusers: services.users,\n\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t},\n\t\t\t\tid,\n\t\t\t\tvalue,\n\t\t\t);\n\t\t\tif (result.ok) {\n\t\t\t\tthis.log.info(`presence ${id}: ${result.message}`);\n\t\t\t\t// A punch from the presence state is published like every other one: the subscription in `onReady`\n\t\t\t\t// mirrors it into `events.*` and tells the connected web clients.\n\t\t\t\tconst event = presenceEvent(result, Math.floor(Date.now() / 1000));\n\t\t\t\tif (event) {\n\t\t\t\t\tthis.events?.publish(event);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// a value the state does not understand, an unknown employee: worth a warning, not an error\n\t\t\t\tthis.log.warn(`presence ${id}: ${result.message}`);\n\t\t\t}\n\t\t\tawait this.refreshStates();\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Error in runPresence for ${id}: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Publishes the figures of all employees.\n\t */\n\tprivate async refreshStates(): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst snapshots = await publishAllUserStates({\n\t\t\t\tport: this,\n\t\t\t\taggregation: services.aggregation,\n\t\t\t\tusers: services.users,\n\t\t\t\tsync: services.sync,\n\t\t\t});\n\t\t\tthis.log.debug(`published ${snapshots.length} employee state(s)`);\n\t\t\t// the company figures are derived from the same snapshots, so they never disagree\n\t\t\tawait publishCompanySnapshot(this, readCompanySnapshot(snapshots));\n\t\t\t// the calendar of the instance: the file the `ical` adapter reads and the states around it\n\t\t\tawait this.publishCalendar();\n\t\t\t// the target employee of the punch commands is mirrored into the state the user writes (acknowledged, so\n\t\t\t// the adapter ignores its own write \u2014 see `onStateChange`). `0` means \"the only employee\".\n\t\t\tawait this.setState(COMMAND_IDS.punchUserId, services.settings.getNumber(\"command_punch_user_id\", 0), true);\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`states could not be published: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Writes the calendar of the instance and publishes what ioBroker needs.\n\t *\n\t * The `.ics` file is what the `ical` adapter reads as a **local source** \u2014 no URL, no token, no network \u2014 and what\n\t * a script can hand to another consumer. `calendar.absences` carries the same days as JSON for scripts and\n\t * dashboards. The subscription link is published only once the administration created it\n\t * (`commands.rotateCalendarToken`), because that link opens the absences of **all** employees to whoever holds it.\n\t */\n\tprivate async publishCalendar(): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\n\t\t// the same window the feed route uses: a year back and to the end of next year\n\t\tconst stamp = Math.floor(Date.now() / 1000);\n\t\tconst year = new Date(stamp * 1000).getUTCFullYear();\n\t\tconst from = `${year - 1}-01-01`;\n\t\tconst to = `${year + 1}-12-31`;\n\n\t\t// deactivated accounts stay out (`list()` returns the active ones)\n\t\tconst employees = new Map(\n\t\t\tservices.users\n\t\t\t\t.list()\n\t\t\t\t.map(user => [user.id, { id: user.id, displayName: user.displayName, login: user.login }]),\n\t\t);\n\t\tconst list = services.absences.allInRange(from, to);\n\n\t\t// The file lives in the `files` folder of the instance data, so the web file server hands it out as well\n\t\t// (`http://<host>:8081/files/<adapter>.<instance>/calendar.ics`) while the `ical` adapter reads it as a local\n\t\t// file \u2014 the instance folder itself is readable for the adapter only.\n\t\tconst folder = path.join(utils.getAbsoluteDefaultDataDir(), \"files\", `${this.name}.${this.instance}`);\n\t\tconst file = path.join(folder, CALENDAR_FILE_NAME);\n\t\tfs.mkdirSync(folder, { recursive: true });\n\t\tfs.writeFileSync(\n\t\t\tfile,\n\t\t\tcalendarDocument(COMPANY_CALENDAR_NAME, absenceEvents(list, employees, true), stamp),\n\t\t\t\"utf8\",\n\t\t);\n\n\t\tconst token = services.settings.get(\"calendar_token\");\n\t\tawait publishCalendarSnapshot(this, {\n\t\t\t// the host part is the name of this machine: an address that is reachable from the LAN and simple enough\n\t\t\t// to correct by hand (a reverse proxy or another address is a matter of the copy in the calendar app)\n\t\t\tfeedUrl: token ? companyFeedUrl(hostname(), this.config.port || 8092, token) : \"\",\n\t\t\tfeedFile: file,\n\t\t\tupdatedAt: stamp,\n\t\t\tabsences: JSON.stringify(absenceFeed(list, employees)),\n\t\t});\n\t}\n\n\t/**\n\t * Runs a command state and reports the outcome.\n\t *\n\t * @param id - state id without the instance prefix\n\t * @param value - value written by the user or a script\n\t */\n\tprivate async runCommand(id: string, value: ioBroker.StateValue): Promise<void> {\n\t\tconst services = this.services;\n\t\tconst db = this.db;\n\t\tif (!services || !db) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = handleCommand(\n\t\t\t\t{\n\t\t\t\t\tdb,\n\t\t\t\t\tentries: services.entries,\n\t\t\t\t\tusers: services.users,\n\t\t\t\t\tsettings: services.settings,\n\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t\tclosing: services.closing,\n\t\t\t\t\tbackup: services.backup,\n\t\t\t\t},\n\t\t\t\tid,\n\t\t\t\tvalue,\n\t\t\t);\n\t\t\tthis.log.info(`command ${id}: ${result.message}`);\n\t\t\t// a punch written by a command state lands in `events.*` and in the web app like every other one\n\t\t\tthis.publishPunch(punchEvent(result, `commands.${id}`, Math.floor(Date.now() / 1000)));\n\t\t\t// a closing is triggered from a state, not from a request: tell the connected clients about it\n\t\t\t// (only the period is published, never the figures of the employee it belongs to)\n\t\t\tif (id === COMMAND_IDS.closeMonth) {\n\t\t\t\tthis.events?.publish({\n\t\t\t\t\ttype: \"month.close\",\n\t\t\t\t\tatUtc: Math.floor(Date.now() / 1000),\n\t\t\t\t\tuserId: null,\n\t\t\t\t\tdata: { period: String(value ?? \"\") },\n\t\t\t\t});\n\t\t\t}\n\t\t\t// a manual backup reports its file the same way the scheduled one does\n\t\t\tif (id === COMMAND_IDS.backup && result.ok) {\n\t\t\t\tconst newest = services.backup.list()[0];\n\t\t\t\tif (newest) {\n\t\t\t\t\tawait this.announceBackup(newest.name, newest.createdAt);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`command ${id} failed: ${(error as Error).message}`);\n\t\t}\n\n\t\t// buttons are stateless: always release them again\n\t\tif (\n\t\t\tid === COMMAND_IDS.punch ||\n\t\t\tid === COMMAND_IDS.quickPunch ||\n\t\t\tid === COMMAND_IDS.backup ||\n\t\t\tid === COMMAND_IDS.rotateCalendarToken\n\t\t) {\n\t\t\tawait this.setState(id, false, true);\n\t\t}\n\t\tawait this.refreshStates();\n\t}\n\n\t/**\n\t * Watches exactly the states the active trigger rules name.\n\t *\n\t * Runs at startup and whenever the administration saves the rules, so an added, changed or removed rule takes\n\t * effect without a restart. A state that no rule names any more is dropped from the watch list.\n\t */\n\tprivate async refreshTriggerSubscriptions(): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet rules: TriggerRuleRecord[] = [];\n\t\ttry {\n\t\t\trules = services.triggers.list();\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`trigger rules could not be read: ${(error as Error).message}`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst wanted = new Set(rules.map(rule => rule.sourceState));\n\t\tfor (const id of [...this.triggerStates]) {\n\t\t\tif (wanted.has(id)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait this.unsubscribeForeignStatesAsync(id);\n\t\t\t} catch (error) {\n\t\t\t\tthis.log.debug(`trigger state ${id} could not be released: ${(error as Error).message}`);\n\t\t\t}\n\t\t\tthis.triggerStates.delete(id);\n\t\t\tthis.triggerValues.delete(id);\n\t\t}\n\n\t\tfor (const id of wanted) {\n\t\t\tif (this.triggerStates.has(id)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait this.subscribeForeignStatesAsync(id);\n\t\t\t\tthis.triggerStates.add(id);\n\t\t\t} catch (error) {\n\t\t\t\tthis.log.warn(`trigger state ${id} could not be watched: ${(error as Error).message}`);\n\t\t\t}\n\t\t}\n\n\t\tthis.log.debug(`watching ${this.triggerStates.size} trigger state(s) for ${rules.length} active rule(s)`);\n\t}\n\n\t/**\n\t * Applies a write on a state that trigger rules watch.\n\t *\n\t * Such a state belongs to another adapter, so its `ack` says nothing about whether the value was set by a\n\t * script or by the device. What counts is that the value **changed**: a reader that repeats itself, a\n\t * dashboard that refreshes or a script that writes the same value twice does not punch twice. Together with\n\t * the cooldown of a rule even a rapidly blinking state stays harmless.\n\t *\n\t * @param id - full state id\n\t * @param state - state object of the write\n\t * @returns true when the state belongs to a rule and was handled here\n\t */\n\tprivate handleTriggerState(id: string, state: ioBroker.State): boolean {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst previous = this.triggerValues.get(id) ?? null;\n\t\tthis.triggerValues.set(id, triggerText(state.val));\n\n\t\tconst rules = services.triggers.list().filter(rule => rule.sourceState === id);\n\t\tif (rules.length === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\tfor (const rule of rules) {\n\t\t\tconst decision = evaluateTrigger(rule, { value: state.val, previous, now }, services.users);\n\t\t\tif (!decision.fire || decision.userId === null) {\n\t\t\t\tthis.log.debug(`trigger ${rule.id} (${rule.sourceState}) did not fire: ${decision.reason}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.runTrigger(rule, decision.userId);\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Runs the action of a rule that fired.\n\t *\n\t * The punch goes through the same path as the command state and a presence change through the same handler as\n\t * the `users.<id>.present` state, so direction, rounding, notes and the recalculation behave identically.\n\t *\n\t * @param rule - rule that fired\n\t * @param userId - employee the action applies to\n\t */\n\tprivate runTrigger(rule: TriggerRuleRecord, userId: number): void {\n\t\tvoid (async (): Promise<void> => {\n\t\t\tconst services = this.services;\n\t\t\tif (!services) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result =\n\t\t\t\t\trule.action === \"present\" || rule.action === \"absent\"\n\t\t\t\t\t\t? handlePresenceState(\n\t\t\t\t\t\t\t\t{ entries: services.entries, users: services.users, aggregation: services.aggregation },\n\t\t\t\t\t\t\t\t`users.${userId}.${PRESENCE_SUFFIX}`,\n\t\t\t\t\t\t\t\trule.action === \"present\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: punchEmployee(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tentries: services.entries,\n\t\t\t\t\t\t\t\t\tusers: services.users,\n\t\t\t\t\t\t\t\t\tsettings: services.settings,\n\t\t\t\t\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{ userId, quick: rule.action === \"quickPunch\", note: `trigger.${rule.id}` },\n\t\t\t\t\t\t\t);\n\t\t\t\tthis.log.info(`trigger ${rule.id} (${rule.sourceState}) \u2192 ${result.message}`);\n\t\t\t\t// a punch written by a rule lands in `events.*` too; a presence write that changed nothing has no\n\t\t\t\t// event (the builder answers `null` then)\n\t\t\t\tconst atUtc = Math.floor(Date.now() / 1000);\n\t\t\t\tif (\"changed\" in result) {\n\t\t\t\t\tthis.publishPunch(presenceEvent(result, atUtc));\n\t\t\t\t} else {\n\t\t\t\t\tthis.publishPunch(punchEvent(result, `trigger.${rule.id}`, atUtc));\n\t\t\t\t}\n\t\t\t\tservices.triggers.markFired({ id: rule.id });\n\t\t\t\tawait this.refreshStates();\n\t\t\t} catch (error) {\n\t\t\t\t// a deleted employee, a deactivated one, a value that cannot be used: never take the adapter down\n\t\t\t\tthis.log.warn(`trigger ${rule.id} failed: ${(error as Error).message}`);\n\t\t\t}\n\t\t})();\n\t}\n\n\t/**\n\t * Stops the HTTP server.\n\t */\n\tprivate async stopApi(): Promise<void> {\n\t\tconst server = this.webServer;\n\t\tthis.webServer = null;\n\t\tif (server) {\n\t\t\tawait server.close();\n\t\t\tthis.log.debug(\"API stopped\");\n\t\t}\n\t}\n\n\t/**\n\t * Is called when adapter shuts down - callback has to be called under any circumstances!\n\t *\n\t * @param callback - Callback function\n\t */\n\tprivate onUnload(callback: () => void): void {\n\t\ttry {\n\t\t\t// Timers created with this.setTimeout/this.setInterval are cleared automatically on unload.\n\t\t\tvoid this.stopApi()\n\t\t\t\t.catch(error => this.log.warn(`stopping the API failed: ${(error as Error).message}`))\n\t\t\t\t.finally(() => {\n\t\t\t\t\tthis.db?.close();\n\t\t\t\t\tthis.db = null;\n\t\t\t\t\tcallback();\n\t\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Error during unloading: ${(error as Error).message}`);\n\t\t\tcallback();\n\t\t}\n\t}\n\n\t/**\n\t * Runs the automation rules that are due.\n\t *\n\t * A rule acts at most once per employee and period \u2014 the local date, or the ISO week for a weekly rule;\n\t * `automation_runs` holds that decision, so a restart\n\t * cannot punch twice. The run is noted **before** the action (a double punch would be worse than a missed\n\t * reminder) and taken back when the action fails, so the next minute retries it.\n\t */\n\tprivate runAutomationRules(): void {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet rules: AutomationRuleRecord[] = [];\n\t\ttry {\n\t\t\trules = services.automations.list();\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`automation rules could not be read: ${(error as Error).message}`);\n\t\t\treturn;\n\t\t}\n\t\tif (rules.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst nowUtc = Math.floor(Date.now() / 1000);\n\t\tlet acted = false;\n\n\t\tfor (const rule of rules) {\n\t\t\tconst candidates =\n\t\t\t\trule.userId === null\n\t\t\t\t\t? services.users.list()\n\t\t\t\t\t: [services.users.findById(rule.userId)].filter((user): user is UserRecord => user !== null);\n\n\t\t\tfor (const user of candidates) {\n\t\t\t\tif (!user.isActive) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst local = localDateTime(nowUtc, user.timezone);\n\t\t\t\t// a weekly rule is guarded per ISO week, a daily rule per local date\n\t\t\t\tconst period = runPeriod(rule.repeat, local.date);\n\t\t\t\tif (services.automations.hasRun({ ruleId: rule.id, userId: user.id, period })) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst punches = services.entries\n\t\t\t\t\t.listByDate(user.id, local.date)\n\t\t\t\t\t.map(entry => ({ tsUtc: entry.tsUtc, direction: entry.direction }));\n\t\t\t\tconst block = workBlock(punches, nowUtc);\n\t\t\t\tconst decision = evaluateAutomation(rule, {\n\t\t\t\t\tlocalDate: local.date,\n\t\t\t\t\tweekday: isoWeekday(local.date),\n\t\t\t\t\tminuteOfDay: local.minutes,\n\t\t\t\t\thasOpenEntry: block.hasOpenEntry,\n\t\t\t\t\tblockMinutes: block.blockMinutes,\n\t\t\t\t\talreadyRan: false,\n\t\t\t\t});\n\t\t\t\tif (!decision.fire) {\n\t\t\t\t\tthis.log.debug(`automation ${rule.id} (${user.displayName}): ${decision.reason}`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst action = this.automationAction(rule, block.blockMinutes);\n\t\t\t\tif (\n\t\t\t\t\t!services.automations.recordRun({\n\t\t\t\t\t\truleId: rule.id,\n\t\t\t\t\t\tuserId: user.id,\n\t\t\t\t\t\tperiod,\n\t\t\t\t\t\taction,\n\t\t\t\t\t\tnow: nowUtc,\n\t\t\t\t\t})\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tlet punched: CommandResult | null = null;\n\t\t\t\t\tif (rule.kind === \"clockOut\" || rule.kind === \"clockIn\") {\n\t\t\t\t\t\tpunched = punchEmployee(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tentries: services.entries,\n\t\t\t\t\t\t\t\tusers: services.users,\n\t\t\t\t\t\t\t\tsettings: services.settings,\n\t\t\t\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ userId: user.id, note: `auto.${rule.kind}`, now: nowUtc },\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthis.log.info(`automation ${rule.id} for ${user.displayName}: ${punched.message}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.log.info(`automation ${rule.id} for ${user.displayName}: ${action}`);\n\t\t\t\t\t}\n\t\t\t\t\tacted = true;\n\t\t\t\t\tthis.events?.publish({\n\t\t\t\t\t\ttype: `automation.${rule.kind}`,\n\t\t\t\t\t\tatUtc: nowUtc,\n\t\t\t\t\t\tuserId: user.id,\n\t\t\t\t\t\t// the direction belongs into the event when the rule wrote a punch (checker: `events.lastDirection`)\n\t\t\t\t\t\tdata: { ruleId: rule.id, action, reason: decision.reason, direction: punched?.direction },\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// give the next check a chance instead of losing the period\n\t\t\t\t\tservices.automations.forgetRun({ ruleId: rule.id, userId: user.id, period });\n\t\t\t\t\tthis.log.warn(`automation ${rule.id} for ${user.displayName} failed: ${(error as Error).message}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (acted) {\n\t\t\t// a punch written by a rule changes the published figures\n\t\t\tthis.scheduleStateRefresh();\n\t\t}\n\t}\n\n\t/**\n\t * Short description of what a rule did (the log entry of `automation_runs`).\n\t *\n\t * @param rule - rule that fired\n\t * @param blockMinutes - length of the running work block, `null` when the employee is not clocked in\n\t * @returns text for the log\n\t */\n\tprivate automationAction(rule: AutomationRuleRecord, blockMinutes: number | null): string {\n\t\tif (rule.kind === \"clockOut\") {\n\t\t\treturn \"clocked out\";\n\t\t}\n\t\tif (rule.kind === \"clockIn\") {\n\t\t\treturn \"clocked in\";\n\t\t}\n\t\tif (rule.kind === \"breakReminder\") {\n\t\t\treturn `reminded after ${blockMinutes ?? 0} min without a break`;\n\t\t}\n\t\treturn \"reported a missing punch\";\n\t}\n\n\t/**\n\t * Republishes the figures shortly after data changed.\n\t *\n\t * A punch from the web app, the terminal or a badge arrives through the API of this very process, so without\n\t * this the states would only change with the five minute timer. One action often produces several events, so\n\t * the refresh is collected for a moment instead of running once per event.\n\t */\n\tprivate scheduleStateRefresh(): void {\n\t\tif (this.stateRefreshTimer) {\n\t\t\treturn;\n\t\t}\n\t\tthis.stateRefreshTimer = this.setTimeout(() => {\n\t\t\tthis.stateRefreshTimer = undefined;\n\t\t\tvoid this.refreshStates();\n\t\t}, 1000);\n\t}\n\n\t/**\n\t * Publishes the newest event of the instance into the state tree.\n\t *\n\t * A notification adapter, a dashboard or a Blockly script then only has to watch `events.*` instead of\n\t * following the live stream of the web app.\n\t *\n\t * @param event - event published by the API\n\t */\n\tprivate async publishEventState(event: ApiEvent): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst data = event.data ?? {};\n\t\t\tconst user =\n\t\t\t\tevent.userId === null || event.userId === undefined ? null : services.users.findById(event.userId);\n\t\t\tawait publishEventSnapshot(this, {\n\t\t\t\ttype: event.type,\n\t\t\t\tatUtc: event.atUtc,\n\t\t\t\tuserName: user?.displayName ?? \"\",\n\t\t\t\tdirection: typeof data.direction === \"string\" ? data.direction : \"\",\n\t\t\t\tsource: typeof data.source === \"string\" ? data.source : \"\",\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.debug(`event state could not be published: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Publishes a punch on the event bus, so it reaches `events.*` and the connected web clients.\n\t *\n\t * A punch can be written from several places \u2014 the command states, a `sendTo` message, a trigger rule, an\n\t * automation rule and the presence state. Every one of them passes its result through here; a path that forgets\n\t * it leaves the event states (`lastType`, `lastUser`, \u2026) on the punch before.\n\t *\n\t * @param event - event built from the punch, `null` when nothing was written\n\t */\n\tprivate publishPunch(event: ApiEvent | null): void {\n\t\tif (event) {\n\t\t\tthis.events?.publish(event);\n\t\t}\n\t}\n\n\t/**\n\t * Is called when another adapter sends a message (`sendTo`).\n\t *\n\t * @param obj - message object of the adapter framework\n\t */\n\tprivate onMessage(obj: ioBroker.Message): void {\n\t\tvoid (async (): Promise<void> => {\n\t\t\tconst services = this.services;\n\t\t\tif (!services) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst answer = await handleMessage(\n\t\t\t\t\t{\n\t\t\t\t\t\tusers: services.users,\n\t\t\t\t\t\tentries: services.entries,\n\t\t\t\t\t\tabsences: services.absences,\n\t\t\t\t\t\tsettings: services.settings,\n\t\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t\t\tsync: services.sync,\n\t\t\t\t\t\tbackup: services.backup,\n\t\t\t\t\t\tversion: this.version,\n\t\t\t\t\t},\n\t\t\t\t\tobj.command,\n\t\t\t\t\tobj.message,\n\t\t\t\t);\n\t\t\t\tthis.log.info(`message ${obj.command}: ${answer.message}`);\n\t\t\t\t// a punch answered by a message reaches `events.*` and the web app like every other one\n\t\t\t\tconst direction = answer.data?.direction;\n\t\t\t\tconst userId = answer.data?.userId;\n\t\t\t\tif (typeof direction === \"string\" && typeof userId === \"number\") {\n\t\t\t\t\tthis.publishPunch({\n\t\t\t\t\t\ttype: \"punch\",\n\t\t\t\t\t\tatUtc: Math.floor(Date.now() / 1000),\n\t\t\t\t\t\tuserId,\n\t\t\t\t\t\tdata: { direction, source: `sendTo.${obj.command}` },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (obj.callback) {\n\t\t\t\t\tthis.sendTo(obj.from, obj.command, answer, obj.callback);\n\t\t\t\t}\n\t\t\t\tawait this.refreshStates();\n\t\t\t} catch (error) {\n\t\t\t\tconst message = (error as Error).message;\n\t\t\t\tthis.log.warn(`message ${obj.command} failed: ${message}`);\n\t\t\t\tif (obj.callback) {\n\t\t\t\t\tthis.sendTo(obj.from, obj.command, { ok: false, message }, obj.callback);\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\t}\n\n\t/**\n\t * Is called if a subscribed state changes\n\t *\n\t * @param id - State ID\n\t * @param state - State object\n\t */\n\tprivate onStateChange(id: string, state: ioBroker.State | null | undefined): void {\n\t\ttry {\n\t\t\tif (!state) {\n\t\t\t\t// deletions carry no value\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// a watched state of another adapter is handled first: such a state is acknowledged by its own\n\t\t\t// adapter, and what counts there is the change of the value\n\t\t\tif (this.handleTriggerState(id, state)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (state.ack) {\n\t\t\t\t// Ignore acknowledged (status) states of this instance\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst prefix = `${this.namespace}.`;\n\t\t\tconst localId = id.startsWith(prefix) ? id.slice(prefix.length) : id;\n\t\t\tif (localId.startsWith(\"commands.\")) {\n\t\t\t\tvoid this.runCommand(localId, state.val);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (parsePresenceStateId(localId) !== null) {\n\t\t\t\tvoid this.runPresence(localId, state.val);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Error in onStateChange for ${id}: ${(error as Error).message}`);\n\t\t}\n\t}\n}\n\nif (require.main !== module) {\n\t// Export the constructor in compact mode\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new TimeTracker(options);\n} else {\n\t// otherwise start the instance directly\n\t(() => new TimeTracker())();\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/*\n * ioBroker adapter \"time-tracker\" \u2013 time tracking\n * Scaffolded with @iobroker/create-adapter v3.1.5\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { hostname } from \"node:os\";\nimport * as utils from \"@iobroker/adapter-core\";\nimport { currentSchemaVersion, openAndMigrate, type Db } from \"./lib/db/database\";\nimport { seed } from \"./lib/db/seed\";\nimport type { HolidayCountry } from \"./lib/domain/holidays\";\nimport { createAbsencesRepository } from \"./lib/db/repositories/absences\";\nimport { createDayNotesRepository, type DayNotesRepository } from \"./lib/db/repositories/dayNotes\";\nimport { createEntriesRepository } from \"./lib/db/repositories/entries\";\nimport { createHolidaysRepository } from \"./lib/db/repositories/holidays\";\nimport { createPayoutsRepository } from \"./lib/db/repositories/payouts\";\nimport { createRulesRepository } from \"./lib/db/repositories/rules\";\nimport { createSettingsRepository } from \"./lib/db/repositories/settings\";\nimport { createUsersRepository, type UserRecord, type UsersRepository } from \"./lib/db/repositories/users\";\nimport {\n\tcreateAutomationsRepository,\n\ttype AutomationRuleRecord,\n\ttype AutomationsRepository,\n} from \"./lib/db/repositories/automations\";\nimport { createTerminalsRepository } from \"./lib/db/repositories/terminals\";\nimport { createRfidRepository } from \"./lib/db/repositories/rfid\";\nimport {\n\tcreateTriggersRepository,\n\ttype TriggerRuleRecord,\n\ttype TriggersRepository,\n} from \"./lib/db/repositories/triggers\";\nimport type { EntriesRepository } from \"./lib/db/repositories/entries\";\nimport type { AbsencesRepository } from \"./lib/db/repositories/absences\";\nimport type { SettingsRepository } from \"./lib/db/repositories/settings\";\nimport { createAggregationService, type AggregationService } from \"./lib/services/aggregation\";\nimport { createAuthService, hashPassword } from \"./lib/services/auth\";\nimport { SECRET_FILE_NAME, resolveSessionSecret } from \"./lib/services/sessionSecret\";\nimport { createClosingService, type ClosingService } from \"./lib/services/closing\";\nimport {\n\tapplyPendingRestore,\n\tcreateBackupService,\n\trecordRestore,\n\ttype AppliedRestore,\n\ttype BackupService,\n} from \"./lib/services/backup\";\nimport { createSyncService, type SyncService } from \"./lib/services/sync\";\nimport {\n\tabsenceEvents,\n\tabsenceFeed,\n\tAPI_PREFIX,\n\tcalendarDocument,\n\tcompanyFeedUrl,\n\tCOMPANY_CALENDAR_NAME,\n} from \"./lib/services/calendar\";\nimport { CALENDAR_FILE_NAME, ensureCalendarFolder, writeCalendarFile } from \"./lib/adapter/calendarFile\";\nimport {\n\tCOMMAND_IDS,\n\tcreateCalendarStates,\n\tcreateCommandStates,\n\tcreateCompanyStates,\n\tcreateEventStates,\n\tcreateInfoStates,\n\tpublishAllUserStates,\n\tpublishCalendarSnapshot,\n\tpublishCompanySnapshot,\n\tpublishEventSnapshot,\n\treadCompanySnapshot,\n} from \"./lib/adapter/states\";\nimport { PRESENCE_SUFFIX, handlePresenceState, parsePresenceStateId, presenceEvent } from \"./lib/adapter/presence\";\nimport { handleCommand, punchEmployee, punchEvent, type CommandResult } from \"./lib/adapter/commands\";\nimport { evaluateTrigger, triggerText } from \"./lib/adapter/triggers\";\nimport { evaluateAutomation, isoWeekday, runPeriod, workBlock } from \"./lib/adapter/automation\";\nimport { handleMessage } from \"./lib/adapter/messages\";\nimport { createApi, MAX_BACKUP_UPLOAD_BYTES } from \"./lib/web/api\";\nimport type { ApiEvent, EventBus } from \"./lib/web/events\";\nimport { startWebServer, type WebServer } from \"./lib/web/server\";\nimport { localDateTime } from \"./lib/util/time\";\nimport { createStaticHandler } from \"./lib/web/static\";\n\nconst SUPPORTED_COUNTRIES: HolidayCountry[] = [\"CH\", \"DE\", \"AT\"];\n\n/** How often expired sessions are removed (minutes). */\nconst SESSION_PURGE_MINUTES = 30;\n\n/** How often the published figures are refreshed (minutes). */\nconst STATE_REFRESH_MINUTES = 5;\n\n/** How old the newest backup may be before the daily check writes a new one. */\nconst BACKUP_MAX_AGE_HOURS = 20;\n\n/** How often the automation rules are checked (minutes). */\nconst AUTOMATION_CHECK_MINUTES = 1;\n\n/** Name of the file that holds a generated badge (HMAC) secret next to the database. */\nconst TAG_SECRET_FILE_NAME = \"tag-secret\";\n\n/**\n * Events of the API that change the published figures.\n *\n * A punch or a correction arrives through the API of this very process, so nothing would republish the states\n * until the five minute timer fires; these events say exactly when data changed.\n */\nconst FIGURES_EVENT_TYPES = [\"punch\", \"terminal.punch\", \"rfid.scan\", \"entry.update\", \"entry.delete\", \"absence.change\"];\n\n/** Services created at startup. */\ninterface AdapterServices {\n\tusers: UsersRepository;\n\tentries: EntriesRepository;\n\tabsences: AbsencesRepository;\n\tdayNotes: DayNotesRepository;\n\tsettings: SettingsRepository;\n\taggregation: AggregationService;\n\tsync: SyncService;\n\tclosing: ClosingService;\n\tbackup: BackupService;\n\ttriggers: TriggersRepository;\n\tautomations: AutomationsRepository;\n}\n\nclass TimeTracker extends utils.Adapter {\n\tprivate db: Db | null = null;\n\tprivate webServer: WebServer | null = null;\n\tprivate services: AdapterServices | null = null;\n\t/** Bus of the API; `null` until the API is created */\n\tprivate events: EventBus | null = null;\n\t/** Last value seen per watched trigger state: only a change fires a rule */\n\tprivate readonly triggerValues = new Map<string, string>();\n\t/** Trigger states the adapter currently subscribes to */\n\tprivate readonly triggerStates = new Set<string>();\n\t/** Pending refresh of the figures after a change through the API */\n\tprivate stateRefreshTimer: ioBroker.Timeout | undefined = undefined;\n\n\t/** A refresh tick that is still running; the next tick is skipped instead of piling up on it. */\n\tprivate stateRefreshRunning = false;\n\n\t/** A backup run that is still running; the hourly check must not start a second one. */\n\tprivate backupRunning = false;\n\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({\n\t\t\t...options,\n\t\t\tname: \"time-tracker\",\n\t\t});\n\t\tthis.on(\"ready\", this.onReady.bind(this));\n\t\tthis.on(\"stateChange\", this.onStateChange.bind(this));\n\t\t// this.on(\"objectChange\", this.onObjectChange.bind(this));\n\t\t// scripts, Blockly and other adapters reach the instance through `sendTo` messages\n\t\tthis.on(\"message\", this.onMessage.bind(this));\n\t\tthis.on(\"unload\", this.onUnload.bind(this));\n\t}\n\n\t/** Database file: configured path or `<adapter instance data dir>/time-tracker.sqlite`. */\n\tprivate databaseFile(): string {\n\t\tconst configured = (this.config.dbPath ?? \"\").trim();\n\t\tif (configured) {\n\t\t\treturn path.resolve(configured);\n\t\t}\n\n\t\tconst dir = utils.getAbsoluteInstanceDataDir(this);\n\t\tfs.mkdirSync(dir, { recursive: true });\n\t\treturn path.join(dir, \"time-tracker.sqlite\");\n\t}\n\n\t/** Configured holiday country, falling back to Switzerland. */\n\tprivate holidayCountry(): HolidayCountry {\n\t\tconst configured = (this.config.holidayCountry ?? \"\").toUpperCase() as HolidayCountry;\n\t\treturn SUPPORTED_COUNTRIES.includes(configured) ? configured : \"CH\";\n\t}\n\n\t/**\n\t * Is called when databases are connected and adapter received configuration.\n\t */\n\tprivate async onReady(): Promise<void> {\n\t\ttry {\n\t\t\t// Reset the connection indicator during startup\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\n\t\t\tconst file = this.databaseFile();\n\t\t\t// A restore that the administration queued is applied before the database is opened: the swap needs a\n\t\t\t// closed file, and that is the only moment the adapter can offer it (see the backup service).\n\t\t\tlet applied: AppliedRestore | null = null;\n\t\t\ttry {\n\t\t\t\tapplied = applyPendingRestore(file);\n\t\t\t\tif (applied) {\n\t\t\t\t\tthis.log.info(\n\t\t\t\t\t\t`restored the database from ${applied.restored.name}: ${applied.restored.users} users, ${applied.restored.entries} punches, previous file ${applied.previous ?? \"(none)\"}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// the queued files stay in place, so a working backup can be handed in afterwards\n\t\t\t\tthis.log.error(`the queued restore was refused: ${(error as Error).message}`);\n\t\t\t}\n\t\t\tthis.db = openAndMigrate(file, message => this.log.debug(message));\n\t\t\tif (applied) {\n\t\t\t\t// the audit entry can only be written once the restored database is open\n\t\t\t\trecordRestore(this.db, applied.restored, applied.actorId);\n\t\t\t}\n\n\t\t\tconst year = new Date().getUTCFullYear();\n\t\t\tconst country = this.holidayCountry();\n\t\t\tconst result = seed(this.db, { holidayCountry: country, holidayYears: [year, year + 1] });\n\t\t\tthis.log.info(\n\t\t\t\t`database ready at ${file} (${result.permissions} permissions, ${result.holidays} holidays added for ${country})`,\n\t\t\t);\n\n\t\t\t// Without an account nobody can log in, so the first administrator is created here (Phase 2 of the\n\t\t\t// specification: \"Standard-Admin, Passwort mit Pflichtwechsel\").\n\t\t\tthis.ensureAdministrator(this.db);\n\n\t\t\t// the settings of the admin UI win over the stored values (before the services are built)\n\t\t\tthis.applyConfiguration(createSettingsRepository(this.db));\n\n\t\t\t// NOTE: The implementation follows the internal specification (not part of this repository):\n\t\t\t// Phase 1\u20133 \u2013 storage, punch logic, target time, breaks, overtime, vacation\n\t\t\t// Phase 4 \u2013 REST API on an own port (router, sessions, RBAC)\n\t\t\t// Phase 5 \u2013 PWA delivered from www/\n\t\t\t// Phase 8 \u2013 publish aggregates as states and react to command.*\n\n\t\t\tawait this.startApi();\n\t\t\t// creates the command and info states, so the instance information can be published afterwards\n\t\t\tawait this.subscribeCommands();\n\t\t\t// watches the states the trigger rules name (fingerprint reader, button, door contact, \u2026)\n\t\t\tawait this.refreshTriggerSubscriptions();\n\t\t\t// the automation rules look at the clock, so they are checked once a minute\n\t\t\tthis.runAutomationRules();\n\t\t\tthis.setInterval(() => this.runAutomationRules(), AUTOMATION_CHECK_MINUTES * 60 * 1000);\n\t\t\tawait this.publishInstanceInfo();\n\t\t\t// The folder below `files/` is created through the file API of the adapter, and the note file makes sure it\n\t\t\t// exists before the first calendar is written: a folder made with `fs.mkdir` stays unknown to the file\n\t\t\t// manager (D11), and a folder of a fresh instance would stay empty until the first absence shows up.\n\t\t\ttry {\n\t\t\t\tif (await ensureCalendarFolder(this, this.namespace)) {\n\t\t\t\t\tthis.log.debug(`prepared the folder 'files/${this.namespace}'`);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthis.log.warn(\n\t\t\t\t\t`the folder 'files/${this.namespace}' could not be prepared: ${(error as Error).message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait this.refreshStates();\n\n\t\t\t// figures are refreshed regularly (the timer is cleared automatically on unload). A tick that finds the\n\t\t\t// previous run still busy is skipped: a slow run must not pile a second one on top of itself.\n\t\t\tthis.setInterval(\n\t\t\t\t() => {\n\t\t\t\t\tif (this.stateRefreshRunning) {\n\t\t\t\t\t\tthis.log.debug(`state refresh still running, skipping this ${STATE_REFRESH_MINUTES} min tick`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.stateRefreshRunning = true;\n\t\t\t\t\tvoid Promise.allSettled([this.publishInstanceInfo(), this.refreshStates()]).then(results => {\n\t\t\t\t\t\tthis.stateRefreshRunning = false;\n\t\t\t\t\t\tfor (const result of results) {\n\t\t\t\t\t\t\tif (result.status === \"rejected\") {\n\t\t\t\t\t\t\t\tthis.log.warn(`state refresh failed: ${(result.reason as Error).message}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tSTATE_REFRESH_MINUTES * 60 * 1000,\n\t\t\t);\n\n\t\t\t// one backup per day: the check runs every hour, so a missed run is caught up after a restart. A run that\n\t\t\t// is still going (a big database, a slow disk) blocks the next check instead of starting a second copy.\n\t\t\tawait this.runScheduledBackup();\n\t\t\tthis.setInterval(\n\t\t\t\t() => {\n\t\t\t\t\tif (this.backupRunning) {\n\t\t\t\t\t\tthis.log.debug(\"scheduled backup still running, skipping this hour\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.backupRunning = true;\n\t\t\t\t\tvoid this.runScheduledBackup()\n\t\t\t\t\t\t.catch(error => this.log.warn(`scheduled backup failed: ${(error as Error).message}`))\n\t\t\t\t\t\t.finally(() => {\n\t\t\t\t\t\t\tthis.backupRunning = false;\n\t\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\t60 * 60 * 1000,\n\t\t\t);\n\n\t\t\t// Service is ready\n\t\t\tawait this.setState(\"info.connection\", true, true);\n\t\t} catch (error) {\n\t\t\tconst message = (error as Error).message;\n\t\t\tthis.log.error(`Startup failed: ${message}`);\n\t\t\tawait this.setState(\"info.lastError\", message, true);\n\t\t}\n\t}\n\n\t/**\n\t * Creates the first administrator when the instance has none.\n\t *\n\t * The account is created with `must_change_pw = 1`, so the start password opens the door exactly once.\n\t * Login and password can be configured; without a configured password a random one is generated and written\n\t * to the log, because there is no other way to hand it to the operator.\n\t *\n\t * @param db - open database handle\n\t */\n\tprivate ensureAdministrator(db: Db): void {\n\t\tconst users = createUsersRepository(db);\n\t\tconst administrators = users.list().filter(user => users.roles(user.id).includes(\"admin\"));\n\t\tif (administrators.length > 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst login = (this.config.adminLogin ?? \"\").trim() || \"admin\";\n\t\tconst configured = (this.config.adminPassword ?? \"\").trim();\n\t\tconst password = configured || `Zf-${randomBytes(9).toString(\"base64url\")}-7`;\n\n\t\ttry {\n\t\t\tusers.create({\n\t\t\t\tlogin,\n\t\t\t\tdisplayName: login,\n\t\t\t\tpasswordHash: hashPassword(password),\n\t\t\t\tmustChangePw: true,\n\t\t\t\ttimezone: this.config.timezone || \"Europe/Berlin\",\n\t\t\t\troleKeys: [\"admin\"],\n\t\t\t\tnow: Math.floor(Date.now() / 1000),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`the administrator \"${login}\" could not be created: ${(error as Error).message}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (configured) {\n\t\t\tthis.log.info(\n\t\t\t\t`administrator \"${login}\" created with the configured start password - it has to be changed at the first login`,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.log.warn(\n\t\t\t\t`administrator \"${login}\" created with the start password \"${password}\" - change it at the first login`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Copies the instance configuration into the stored settings and publishes the instance information.\n\t *\n\t * The ioBroker admin UI is where an operator configures the instance, so a configured value wins over what\n\t * is stored in the database; `PUT /api/settings` stays for the keys the admin UI does not offer. Empty\n\t * fields are ignored, so clearing a field never wipes a stored setting.\n\t *\n\t * @param settings - instance settings\n\t */\n\tprivate applyConfiguration(settings: SettingsRepository): void {\n\t\tconst text = (value: string | undefined): string | null => (value?.trim() ? value.trim() : null);\n\t\tconst number = (value: number | undefined): string | null =>\n\t\t\ttypeof value === \"number\" && Number.isFinite(value) ? String(value) : null;\n\t\tconst flag = (value: boolean | undefined): string | null => (value === undefined ? null : value ? \"1\" : \"0\");\n\n\t\tconst mapped: [string, string | null][] = [\n\t\t\t[\"holiday_country\", text(this.config.holidayCountry)?.toUpperCase() ?? null],\n\t\t\t[\"timezone\", text(this.config.timezone)],\n\t\t\t[\"default_language\", text(this.config.defaultLanguage)],\n\t\t\t[\"edit_window_days\", number(this.config.editWindowDays)],\n\t\t\t[\"quick_round_minutes\", number(this.config.quickRoundMinutes)],\n\t\t\t[\"session_ttl_minutes\", number(this.config.sessionTtlMinutes)],\n\t\t\t[\"backup_retention_days\", number(this.config.backupRetentionDays)],\n\t\t\t[\"absence_calc_until_today\", flag(this.config.absenceCalcUntilToday)],\n\t\t\t[\"absence_deduct_worktime\", flag(this.config.absenceDeductWorktime)],\n\t\t];\n\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\tfor (const [key, value] of mapped) {\n\t\t\tif (value !== null && settings.get(key) !== value) {\n\t\t\t\tsettings.set(key, value, null, now);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Publishes version, database size and schema version as states (specification 5.1).\n\t *\n\t * The objects are created by `createInfoStates`, so this runs after the command states were set up.\n\t */\n\tprivate async publishInstanceInfo(): Promise<void> {\n\t\tawait this.setState(\"info.version\", this.version ?? \"0.0.0\", true);\n\t\tconst file = this.db ? this.databaseFile() : \"\";\n\t\tawait this.setState(\"info.dbSizeBytes\", file && fs.existsSync(file) ? fs.statSync(file).size : 0, true);\n\t\tif (this.db) {\n\t\t\tawait this.setState(\"info.schemaVersion\", String(currentSchemaVersion(this.db)), true);\n\t\t}\n\t}\n\n\t/**\n\t * Secret used to sign CSRF tokens. A value from the instance settings wins; without one the adapter generates a\n\t * secret on the first start and stores it next to the database, so a restart keeps the CSRF tokens of clients\n\t * that are already open valid.\n\t */\n\tprivate sessionSecret(): string {\n\t\tconst resolved = resolveSessionSecret({\n\t\t\tconfigured: this.config.sessionSecret,\n\t\t\tfile: path.join(path.dirname(this.databaseFile()), SECRET_FILE_NAME),\n\t\t});\n\n\t\tif (resolved.source === \"configured\") {\n\t\t\tthis.log.debug(\"session secret taken from the instance settings\");\n\t\t} else if (resolved.source === \"stored\") {\n\t\t\tthis.log.debug(`session secret taken from ${resolved.file}`);\n\t\t} else if (resolved.file) {\n\t\t\tthis.log.info(\n\t\t\t\t`no session secret configured - generated one and stored it at ${resolved.file}, so sessions and CSRF tokens survive restarts`,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.log.warn(\n\t\t\t\t`no session secret configured and it could not be stored (${resolved.error ?? \"unknown\"}) - a temporary one is used, so all sessions end with the next restart`,\n\t\t\t);\n\t\t}\n\n\t\treturn resolved.secret;\n\t}\n\n\t/**\n\t * Secret that signs the badge/NFC links.\n\t *\n\t * A value from the instance settings wins. Without one a secret is generated on the first start and stored next\n\t * to the database \u2014 otherwise a fresh installation could not create a badge at all and the administration would\n\t * only report \u201Cnot configured\u201D.\n\t */\n\tprivate tagSecret(): string {\n\t\tconst resolved = resolveSessionSecret({\n\t\t\tconfigured: this.config.hmacSecret,\n\t\t\tfile: path.join(path.dirname(this.databaseFile()), TAG_SECRET_FILE_NAME),\n\t\t});\n\n\t\tif (resolved.source === \"configured\") {\n\t\t\tthis.log.debug(\"badge link secret taken from the instance settings\");\n\t\t} else if (resolved.source === \"stored\") {\n\t\t\tthis.log.debug(`badge link secret taken from ${resolved.file}`);\n\t\t} else if (resolved.file) {\n\t\t\tthis.log.info(\n\t\t\t\t`no badge link secret configured - generated one and stored it at ${resolved.file}, so badges stay valid across restarts`,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.log.warn(\n\t\t\t\t`no badge link secret configured and it could not be stored (${resolved.error ?? \"unknown\"}) - a temporary one is used, so badges stop working with the next restart`,\n\t\t\t);\n\t\t}\n\n\t\treturn resolved.secret;\n\t}\n\n\t/**\n\t * Builds the services and starts the HTTP server on the configured port.\n\t *\n\t * Binding can fail (port in use); that must never stop the adapter, so the failure is only logged.\n\t */\n\tprivate async startApi(): Promise<void> {\n\t\tconst db = this.db;\n\t\tif (!db) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst users = createUsersRepository(db);\n\t\tconst entries = createEntriesRepository(db);\n\t\tconst absences = createAbsencesRepository(db);\n\t\tconst dayNotes = createDayNotesRepository(db);\n\t\tconst holidays = createHolidaysRepository(db);\n\t\tconst rules = createRulesRepository(db);\n\t\tconst payouts = createPayoutsRepository(db);\n\t\tconst terminals = createTerminalsRepository(db);\n\t\tconst rfid = createRfidRepository(db);\n\t\tconst settings = createSettingsRepository(db);\n\t\tconst auth = createAuthService({\n\t\t\tdb,\n\t\t\tusers,\n\t\t\tsettings,\n\t\t\tsecret: this.sessionSecret(),\n\t\t\tdefaultTtlMinutes: this.config.sessionTtlMinutes || 720,\n\t\t});\n\t\tconst aggregation = createAggregationService({\n\t\t\tdb,\n\t\t\tusers,\n\t\t\tentries,\n\t\t\tabsences,\n\t\t\tholidays,\n\t\t\trules,\n\t\t\tsettings,\n\t\t});\n\t\tconst triggers = createTriggersRepository(db);\n\t\tconst automations = createAutomationsRepository(db);\n\t\tconst sync = createSyncService({ db, entries, users, aggregation });\n\t\tconst closing = createClosingService({ db, aggregation, payouts });\n\t\t// backups live next to the database file: `<data dir>/backups/time-tracker-<timestamp>.sqlite`\n\t\tconst backup = createBackupService({\n\t\t\tdb,\n\t\t\tdir: path.join(path.dirname(this.databaseFile()), \"backups\"),\n\t\t\tretentionDays: Math.max(0, Number(this.config.backupRetentionDays ?? 30) || 0),\n\t\t});\n\t\tconst api = createApi({\n\t\t\tdb,\n\t\t\tauth,\n\t\t\tusers,\n\t\t\tentries,\n\t\t\tabsences,\n\t\t\tdayNotes,\n\t\t\tholidays,\n\t\t\trules,\n\t\t\tpayouts,\n\t\t\tterminals,\n\t\t\trfid,\n\t\t\ttriggers,\n\t\t\tautomations,\n\t\t\taggregation,\n\t\t\tsync,\n\t\t\tsettings,\n\t\t\tbackup,\n\t\t\tkioskEnabled: this.config.kioskEnabled === true,\n\t\t\t// a reverse proxy in front is the normal case for HTTPS; without the switch the forwarded\n\t\t\t// headers are ignored, so a client cannot choose its own address or the HTTPS flag\n\t\t\ttrustProxy: this.config.trustProxy === true,\n\t\t\thmacSecret: this.tagSecret(),\n\t\t\tversion: this.version,\n\t\t\t// a saved trigger rule changes the states the adapter has to watch\n\t\t\tonTriggerRulesChanged: () => void this.refreshTriggerSubscriptions(),\n\t\t});\n\n\t\tthis.services = {\n\t\t\tusers,\n\t\t\tentries,\n\t\t\tabsences,\n\t\t\tdayNotes,\n\t\t\tsettings,\n\t\t\taggregation,\n\t\t\tsync,\n\t\t\tclosing,\n\t\t\tbackup,\n\t\t\ttriggers,\n\t\t\tautomations,\n\t\t};\n\t\tthis.events = api.events;\n\t\t// the newest event is mirrored into the state tree, so a notification only has to watch `events.*`;\n\t\t// a punch, a correction or an absence changes the figures, so they are republished right away\n\t\tapi.events.subscribe(event => {\n\t\t\tvoid this.publishEventState(event);\n\t\t\tif (FIGURES_EVENT_TYPES.includes(event.type)) {\n\t\t\t\tthis.scheduleStateRefresh();\n\t\t\t}\n\t\t});\n\t\tthis.log.debug(`API routes: ${api.routes().length}`);\n\n\t\t// the web interface is delivered from `www/` next to the compiled code (built by the PWA project);\n\t\t// without it the adapter still provides the API, e.g. for the terminal\n\t\tconst webDir = path.join(__dirname, \"..\", \"www\");\n\t\tconst staticFiles = fs.existsSync(path.join(webDir, \"index.html\"))\n\t\t\t? createStaticHandler({ root: webDir })\n\t\t\t: undefined;\n\t\tthis.log.info(\n\t\t\tstaticFiles\n\t\t\t\t? `web interface found at ${webDir}`\n\t\t\t\t: `no web interface at ${webDir} - only the API (and the terminal) is available`,\n\t\t);\n\n\t\ttry {\n\t\t\tthis.webServer = await startWebServer({\n\t\t\t\trouter: api.router,\n\t\t\t\tport: this.config.port || 8092,\n\t\t\t\tbind: this.config.bind || \"127.0.0.1\",\n\t\t\t\t// the same prefix the calendar link carries (`/api`) \u2014 the web app and its login live beside it\n\t\t\t\tapiPrefix: API_PREFIX,\n\t\t\t\tstaticFiles,\n\t\t\t\t// an uploaded backup is bigger than the default limit; the route carries the same bound\n\t\t\t\tmaxBodyBytes: MAX_BACKUP_UPLOAD_BYTES,\n\t\t\t\t// live events for the PWA and the terminal: `/api/stream?token=...`\n\t\t\t\tstream: {\n\t\t\t\t\tauth,\n\t\t\t\t\tevents: api.events,\n\t\t\t\t\tversion: this.version,\n\t\t\t\t\ttimers: {\n\t\t\t\t\t\t// the adapter's own timer functions: they are cleared with the adapter on unload\n\t\t\t\t\t\tsetInterval: (handler, milliseconds) => this.setInterval(handler, milliseconds),\n\t\t\t\t\t\tclearInterval: handle => this.clearInterval(handle as ioBroker.Interval),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tlog: {\n\t\t\t\t\tinfo: message => this.log.info(message),\n\t\t\t\t\twarn: message => this.log.warn(message),\n\t\t\t\t\terror: message => this.log.error(message),\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.error(\n\t\t\t\t`API could not be started on port ${this.config.port}: ${(error as Error).message} - the web interface and the terminal stay unavailable`,\n\t\t\t);\n\t\t\tthis.webServer = null;\n\t\t\treturn;\n\t\t}\n\n\t\t// expired sessions are removed regularly; the timer is cleared automatically on unload\n\t\tthis.setInterval(\n\t\t\t() => {\n\t\t\t\ttry {\n\t\t\t\t\tconst removed = auth.purge();\n\t\t\t\t\tif (removed > 0) {\n\t\t\t\t\t\tthis.log.debug(`removed ${removed} expired session(s)`);\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.log.warn(`session cleanup failed: ${(error as Error).message}`);\n\t\t\t\t}\n\t\t\t},\n\t\t\tSESSION_PURGE_MINUTES * 60 * 1000,\n\t\t);\n\t}\n\n\t/**\n\t * Creates the command states and subscribes to them.\n\t */\n\tprivate async subscribeCommands(): Promise<void> {\n\t\ttry {\n\t\t\tawait createCommandStates(this);\n\t\t\tawait createInfoStates(this);\n\t\t\tawait createCompanyStates(this);\n\t\t\tawait createEventStates(this);\n\t\t\tawait createCalendarStates(this);\n\t\t\tawait this.subscribeStatesAsync(\"commands.*\");\n\t\t\t// the presence switch of every employee: written by a script, a fingerprint reader or a dashboard\n\t\t\tawait this.subscribeStatesAsync(`users.*.${PRESENCE_SUFFIX}`);\n\t\t\tthis.log.debug(\"command states ready\");\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`command states could not be created: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Writes a backup unless a recent one exists.\n\t *\n\t * The check runs hourly and once at start: a nightly backup therefore happens within an hour at the latest,\n\t * and an instance that was down for days does not write a burst of copies.\n\t */\n\tprivate async runScheduledBackup(): Promise<void> {\n\t\tconst backup = this.services?.backup;\n\t\tif (!backup) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst newest = backup.list()[0];\n\t\t\tconst ageHours = newest ? (Date.now() / 1000 - newest.createdAt) / 3600 : Number.POSITIVE_INFINITY;\n\t\t\tif (ageHours < BACKUP_MAX_AGE_HOURS) {\n\t\t\t\tthis.log.debug(`backup ${newest?.name ?? \"\"} is ${ageHours.toFixed(1)} h old, nothing to do`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst created = backup.create({ actorId: null, reason: \"scheduled\" });\n\t\t\tthis.log.info(\n\t\t\t\t`backup ${created.backup.name} written (${created.backup.sizeBytes} bytes, ${created.backup.users} employees, ${created.backup.entries} punches), ${created.removed.length} old file(s) removed`,\n\t\t\t);\n\t\t\tawait this.announceBackup(created.backup.name, created.backup.createdAt);\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`backup failed: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Publishes the instant of a written backup.\n\t *\n\t * @param name - file name of the backup\n\t * @param createdAt - instant the backup belongs to\n\t */\n\tprivate async announceBackup(name: string, createdAt: number): Promise<void> {\n\t\tawait this.setState(\"info.lastBackup\", createdAt, true);\n\t\tthis.events?.publish({\n\t\t\ttype: \"backup.create\",\n\t\t\tatUtc: createdAt,\n\t\t\tuserId: null,\n\t\t\tdata: { backup: name },\n\t\t});\n\t}\n\n\t/**\n\t * Applies a write on the presence state of an employee.\n\t *\n\t * `users.<id>.present` is the writable twin of `users.<id>.hasOpenEntry`: `true` opens an entry, `false` closes\n\t * it. Afterwards the figures of all employees are republished, so a dashboard sees the new state right away.\n\t *\n\t * @param id - state id without the instance prefix\n\t * @param value - value written by the user or a script\n\t */\n\tprivate async runPresence(id: string, value: ioBroker.StateValue): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = handlePresenceState(\n\t\t\t\t{\n\t\t\t\t\tentries: services.entries,\n\t\t\t\t\tusers: services.users,\n\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t},\n\t\t\t\tid,\n\t\t\t\tvalue,\n\t\t\t);\n\t\t\tif (result.ok) {\n\t\t\t\tthis.log.info(`presence ${id}: ${result.message}`);\n\t\t\t\t// A punch from the presence state is published like every other one: the subscription in `onReady`\n\t\t\t\t// mirrors it into `events.*` and tells the connected web clients.\n\t\t\t\tconst event = presenceEvent(result, Math.floor(Date.now() / 1000));\n\t\t\t\tif (event) {\n\t\t\t\t\tthis.events?.publish(event);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// a value the state does not understand, an unknown employee: worth a warning, not an error\n\t\t\t\tthis.log.warn(`presence ${id}: ${result.message}`);\n\t\t\t}\n\t\t\tawait this.refreshStates();\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Error in runPresence for ${id}: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Publishes the figures of all employees.\n\t */\n\tprivate async refreshStates(): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst snapshots = await publishAllUserStates({\n\t\t\t\tport: this,\n\t\t\t\taggregation: services.aggregation,\n\t\t\t\tusers: services.users,\n\t\t\t\tsync: services.sync,\n\t\t\t});\n\t\t\tthis.log.debug(`published ${snapshots.length} employee state(s)`);\n\t\t\t// the company figures are derived from the same snapshots, so they never disagree\n\t\t\tawait publishCompanySnapshot(this, readCompanySnapshot(snapshots));\n\t\t\t// the calendar of the instance: the file the `ical` adapter reads and the states around it\n\t\t\tawait this.publishCalendar();\n\t\t\t// the target employee of the punch commands is mirrored into the state the user writes (acknowledged, so\n\t\t\t// the adapter ignores its own write \u2014 see `onStateChange`). `0` means \"the only employee\".\n\t\t\tawait this.setState(COMMAND_IDS.punchUserId, services.settings.getNumber(\"command_punch_user_id\", 0), true);\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`states could not be published: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Writes the calendar of the instance and publishes what ioBroker needs.\n\t *\n\t * The `.ics` file is what the `ical` adapter reads as a **local source** \u2014 no URL, no token, no network \u2014 and what\n\t * a script can hand to another consumer. `calendar.absences` carries the same days as JSON for scripts and\n\t * dashboards. The subscription link is published only once the administration created it\n\t * (`commands.rotateCalendarToken`), because that link opens the absences of **all** employees to whoever holds it.\n\t */\n\tprivate async publishCalendar(): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\n\t\t// the same window the feed route uses: a year back and to the end of next year\n\t\tconst stamp = Math.floor(Date.now() / 1000);\n\t\tconst year = new Date(stamp * 1000).getUTCFullYear();\n\t\tconst from = `${year - 1}-01-01`;\n\t\tconst to = `${year + 1}-12-31`;\n\n\t\t// deactivated accounts stay out (`list()` returns the active ones)\n\t\tconst employees = new Map(\n\t\t\tservices.users\n\t\t\t\t.list()\n\t\t\t\t.map(user => [user.id, { id: user.id, displayName: user.displayName, login: user.login }]),\n\t\t);\n\t\tconst list = services.absences.allInRange(from, to);\n\n\t\t// The file lives below `files/<namespace>/` in the instance data, because the instance folder next to it can\n\t\t// only be read by the adapter itself. It is written through the file API of the adapter: that call registers\n\t\t// the folder, so the file manager shows it and a `web` instance can serve it (D11) \u2014 a file written with `fs`\n\t\t// stays a private file. The absolute path is what `calendar.feedFile` carries for the `ical` adapter.\n\t\tconst file = path.join(utils.getAbsoluteDefaultDataDir(), \"files\", this.namespace, CALENDAR_FILE_NAME);\n\t\tconst document = calendarDocument(COMPANY_CALENDAR_NAME, absenceEvents(list, employees, true), stamp);\n\t\tawait writeCalendarFile(this, this.namespace, document);\n\n\t\tconst token = services.settings.get(\"calendar_token\");\n\t\tawait publishCalendarSnapshot(this, {\n\t\t\t// the host part is the name of this machine: an address that is reachable from the LAN and simple enough\n\t\t\t// to correct by hand (a reverse proxy or another address is a matter of the copy in the calendar app)\n\t\t\tfeedUrl: token ? companyFeedUrl(hostname(), this.config.port || 8092, token) : \"\",\n\t\t\tfeedFile: file,\n\t\t\tupdatedAt: stamp,\n\t\t\tabsences: JSON.stringify(absenceFeed(list, employees)),\n\t\t});\n\t}\n\n\t/**\n\t * Runs a command state and reports the outcome.\n\t *\n\t * @param id - state id without the instance prefix\n\t * @param value - value written by the user or a script\n\t */\n\tprivate async runCommand(id: string, value: ioBroker.StateValue): Promise<void> {\n\t\tconst services = this.services;\n\t\tconst db = this.db;\n\t\tif (!services || !db) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = handleCommand(\n\t\t\t\t{\n\t\t\t\t\tdb,\n\t\t\t\t\tentries: services.entries,\n\t\t\t\t\tusers: services.users,\n\t\t\t\t\tsettings: services.settings,\n\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t\tclosing: services.closing,\n\t\t\t\t\tbackup: services.backup,\n\t\t\t\t},\n\t\t\t\tid,\n\t\t\t\tvalue,\n\t\t\t);\n\t\t\tthis.log.info(`command ${id}: ${result.message}`);\n\t\t\t// a punch written by a command state lands in `events.*` and in the web app like every other one\n\t\t\tthis.publishPunch(punchEvent(result, `commands.${id}`, Math.floor(Date.now() / 1000)));\n\t\t\t// a closing is triggered from a state, not from a request: tell the connected clients about it\n\t\t\t// (only the period is published, never the figures of the employee it belongs to)\n\t\t\tif (id === COMMAND_IDS.closeMonth) {\n\t\t\t\tthis.events?.publish({\n\t\t\t\t\ttype: \"month.close\",\n\t\t\t\t\tatUtc: Math.floor(Date.now() / 1000),\n\t\t\t\t\tuserId: null,\n\t\t\t\t\tdata: { period: String(value ?? \"\") },\n\t\t\t\t});\n\t\t\t}\n\t\t\t// a manual backup reports its file the same way the scheduled one does\n\t\t\tif (id === COMMAND_IDS.backup && result.ok) {\n\t\t\t\tconst newest = services.backup.list()[0];\n\t\t\t\tif (newest) {\n\t\t\t\t\tawait this.announceBackup(newest.name, newest.createdAt);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`command ${id} failed: ${(error as Error).message}`);\n\t\t}\n\n\t\t// buttons are stateless: always release them again\n\t\tif (\n\t\t\tid === COMMAND_IDS.punch ||\n\t\t\tid === COMMAND_IDS.quickPunch ||\n\t\t\tid === COMMAND_IDS.backup ||\n\t\t\tid === COMMAND_IDS.rotateCalendarToken\n\t\t) {\n\t\t\tawait this.setState(id, false, true);\n\t\t}\n\t\tawait this.refreshStates();\n\t}\n\n\t/**\n\t * Watches exactly the states the active trigger rules name.\n\t *\n\t * Runs at startup and whenever the administration saves the rules, so an added, changed or removed rule takes\n\t * effect without a restart. A state that no rule names any more is dropped from the watch list.\n\t */\n\tprivate async refreshTriggerSubscriptions(): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet rules: TriggerRuleRecord[] = [];\n\t\ttry {\n\t\t\trules = services.triggers.list();\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`trigger rules could not be read: ${(error as Error).message}`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst wanted = new Set(rules.map(rule => rule.sourceState));\n\t\tfor (const id of [...this.triggerStates]) {\n\t\t\tif (wanted.has(id)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait this.unsubscribeForeignStatesAsync(id);\n\t\t\t} catch (error) {\n\t\t\t\tthis.log.debug(`trigger state ${id} could not be released: ${(error as Error).message}`);\n\t\t\t}\n\t\t\tthis.triggerStates.delete(id);\n\t\t\tthis.triggerValues.delete(id);\n\t\t}\n\n\t\tfor (const id of wanted) {\n\t\t\tif (this.triggerStates.has(id)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait this.subscribeForeignStatesAsync(id);\n\t\t\t\tthis.triggerStates.add(id);\n\t\t\t} catch (error) {\n\t\t\t\tthis.log.warn(`trigger state ${id} could not be watched: ${(error as Error).message}`);\n\t\t\t}\n\t\t}\n\n\t\tthis.log.debug(`watching ${this.triggerStates.size} trigger state(s) for ${rules.length} active rule(s)`);\n\t}\n\n\t/**\n\t * Applies a write on a state that trigger rules watch.\n\t *\n\t * Such a state belongs to another adapter, so its `ack` says nothing about whether the value was set by a\n\t * script or by the device. What counts is that the value **changed**: a reader that repeats itself, a\n\t * dashboard that refreshes or a script that writes the same value twice does not punch twice. Together with\n\t * the cooldown of a rule even a rapidly blinking state stays harmless.\n\t *\n\t * @param id - full state id\n\t * @param state - state object of the write\n\t * @returns true when the state belongs to a rule and was handled here\n\t */\n\tprivate handleTriggerState(id: string, state: ioBroker.State): boolean {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst previous = this.triggerValues.get(id) ?? null;\n\t\tthis.triggerValues.set(id, triggerText(state.val));\n\n\t\tconst rules = services.triggers.list().filter(rule => rule.sourceState === id);\n\t\tif (rules.length === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\tfor (const rule of rules) {\n\t\t\tconst decision = evaluateTrigger(rule, { value: state.val, previous, now }, services.users);\n\t\t\tif (!decision.fire || decision.userId === null) {\n\t\t\t\tthis.log.debug(`trigger ${rule.id} (${rule.sourceState}) did not fire: ${decision.reason}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.runTrigger(rule, decision.userId);\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Runs the action of a rule that fired.\n\t *\n\t * The punch goes through the same path as the command state and a presence change through the same handler as\n\t * the `users.<id>.present` state, so direction, rounding, notes and the recalculation behave identically.\n\t *\n\t * @param rule - rule that fired\n\t * @param userId - employee the action applies to\n\t */\n\tprivate runTrigger(rule: TriggerRuleRecord, userId: number): void {\n\t\tvoid (async (): Promise<void> => {\n\t\t\tconst services = this.services;\n\t\t\tif (!services) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result =\n\t\t\t\t\trule.action === \"present\" || rule.action === \"absent\"\n\t\t\t\t\t\t? handlePresenceState(\n\t\t\t\t\t\t\t\t{ entries: services.entries, users: services.users, aggregation: services.aggregation },\n\t\t\t\t\t\t\t\t`users.${userId}.${PRESENCE_SUFFIX}`,\n\t\t\t\t\t\t\t\trule.action === \"present\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: punchEmployee(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tentries: services.entries,\n\t\t\t\t\t\t\t\t\tusers: services.users,\n\t\t\t\t\t\t\t\t\tsettings: services.settings,\n\t\t\t\t\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{ userId, quick: rule.action === \"quickPunch\", note: `trigger.${rule.id}` },\n\t\t\t\t\t\t\t);\n\t\t\t\tthis.log.info(`trigger ${rule.id} (${rule.sourceState}) \u2192 ${result.message}`);\n\t\t\t\t// a punch written by a rule lands in `events.*` too; a presence write that changed nothing has no\n\t\t\t\t// event (the builder answers `null` then)\n\t\t\t\tconst atUtc = Math.floor(Date.now() / 1000);\n\t\t\t\tif (\"changed\" in result) {\n\t\t\t\t\tthis.publishPunch(presenceEvent(result, atUtc));\n\t\t\t\t} else {\n\t\t\t\t\tthis.publishPunch(punchEvent(result, `trigger.${rule.id}`, atUtc));\n\t\t\t\t}\n\t\t\t\tservices.triggers.markFired({ id: rule.id });\n\t\t\t\tawait this.refreshStates();\n\t\t\t} catch (error) {\n\t\t\t\t// a deleted employee, a deactivated one, a value that cannot be used: never take the adapter down\n\t\t\t\tthis.log.warn(`trigger ${rule.id} failed: ${(error as Error).message}`);\n\t\t\t}\n\t\t})();\n\t}\n\n\t/**\n\t * Stops the HTTP server.\n\t */\n\tprivate async stopApi(): Promise<void> {\n\t\tconst server = this.webServer;\n\t\tthis.webServer = null;\n\t\tif (server) {\n\t\t\tawait server.close();\n\t\t\tthis.log.debug(\"API stopped\");\n\t\t}\n\t}\n\n\t/**\n\t * Is called when adapter shuts down - callback has to be called under any circumstances!\n\t *\n\t * @param callback - Callback function\n\t */\n\tprivate onUnload(callback: () => void): void {\n\t\ttry {\n\t\t\t// Timers created with this.setTimeout/this.setInterval are cleared automatically on unload.\n\t\t\tvoid this.stopApi()\n\t\t\t\t.catch(error => this.log.warn(`stopping the API failed: ${(error as Error).message}`))\n\t\t\t\t.finally(() => {\n\t\t\t\t\tthis.db?.close();\n\t\t\t\t\tthis.db = null;\n\t\t\t\t\tcallback();\n\t\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Error during unloading: ${(error as Error).message}`);\n\t\t\tcallback();\n\t\t}\n\t}\n\n\t/**\n\t * Runs the automation rules that are due.\n\t *\n\t * A rule acts at most once per employee and period \u2014 the local date, or the ISO week for a weekly rule;\n\t * `automation_runs` holds that decision, so a restart\n\t * cannot punch twice. The run is noted **before** the action (a double punch would be worse than a missed\n\t * reminder) and taken back when the action fails, so the next minute retries it.\n\t */\n\tprivate runAutomationRules(): void {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet rules: AutomationRuleRecord[] = [];\n\t\ttry {\n\t\t\trules = services.automations.list();\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`automation rules could not be read: ${(error as Error).message}`);\n\t\t\treturn;\n\t\t}\n\t\tif (rules.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst nowUtc = Math.floor(Date.now() / 1000);\n\t\tlet acted = false;\n\n\t\tfor (const rule of rules) {\n\t\t\tconst candidates =\n\t\t\t\trule.userId === null\n\t\t\t\t\t? services.users.list()\n\t\t\t\t\t: [services.users.findById(rule.userId)].filter((user): user is UserRecord => user !== null);\n\n\t\t\tfor (const user of candidates) {\n\t\t\t\tif (!user.isActive) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst local = localDateTime(nowUtc, user.timezone);\n\t\t\t\t// a weekly rule is guarded per ISO week, a daily rule per local date\n\t\t\t\tconst period = runPeriod(rule.repeat, local.date);\n\t\t\t\tif (services.automations.hasRun({ ruleId: rule.id, userId: user.id, period })) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst punches = services.entries\n\t\t\t\t\t.listByDate(user.id, local.date)\n\t\t\t\t\t.map(entry => ({ tsUtc: entry.tsUtc, direction: entry.direction }));\n\t\t\t\tconst block = workBlock(punches, nowUtc);\n\t\t\t\tconst decision = evaluateAutomation(rule, {\n\t\t\t\t\tlocalDate: local.date,\n\t\t\t\t\tweekday: isoWeekday(local.date),\n\t\t\t\t\tminuteOfDay: local.minutes,\n\t\t\t\t\thasOpenEntry: block.hasOpenEntry,\n\t\t\t\t\tblockMinutes: block.blockMinutes,\n\t\t\t\t\talreadyRan: false,\n\t\t\t\t});\n\t\t\t\tif (!decision.fire) {\n\t\t\t\t\tthis.log.debug(`automation ${rule.id} (${user.displayName}): ${decision.reason}`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst action = this.automationAction(rule, block.blockMinutes);\n\t\t\t\tif (\n\t\t\t\t\t!services.automations.recordRun({\n\t\t\t\t\t\truleId: rule.id,\n\t\t\t\t\t\tuserId: user.id,\n\t\t\t\t\t\tperiod,\n\t\t\t\t\t\taction,\n\t\t\t\t\t\tnow: nowUtc,\n\t\t\t\t\t})\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tlet punched: CommandResult | null = null;\n\t\t\t\t\tif (rule.kind === \"clockOut\" || rule.kind === \"clockIn\") {\n\t\t\t\t\t\tpunched = punchEmployee(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tentries: services.entries,\n\t\t\t\t\t\t\t\tusers: services.users,\n\t\t\t\t\t\t\t\tsettings: services.settings,\n\t\t\t\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ userId: user.id, note: `auto.${rule.kind}`, now: nowUtc },\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthis.log.info(`automation ${rule.id} for ${user.displayName}: ${punched.message}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.log.info(`automation ${rule.id} for ${user.displayName}: ${action}`);\n\t\t\t\t\t}\n\t\t\t\t\tacted = true;\n\t\t\t\t\tthis.events?.publish({\n\t\t\t\t\t\ttype: `automation.${rule.kind}`,\n\t\t\t\t\t\tatUtc: nowUtc,\n\t\t\t\t\t\tuserId: user.id,\n\t\t\t\t\t\t// the direction belongs into the event when the rule wrote a punch (checker: `events.lastDirection`)\n\t\t\t\t\t\tdata: { ruleId: rule.id, action, reason: decision.reason, direction: punched?.direction },\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// give the next check a chance instead of losing the period\n\t\t\t\t\tservices.automations.forgetRun({ ruleId: rule.id, userId: user.id, period });\n\t\t\t\t\tthis.log.warn(`automation ${rule.id} for ${user.displayName} failed: ${(error as Error).message}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (acted) {\n\t\t\t// a punch written by a rule changes the published figures\n\t\t\tthis.scheduleStateRefresh();\n\t\t}\n\t}\n\n\t/**\n\t * Short description of what a rule did (the log entry of `automation_runs`).\n\t *\n\t * @param rule - rule that fired\n\t * @param blockMinutes - length of the running work block, `null` when the employee is not clocked in\n\t * @returns text for the log\n\t */\n\tprivate automationAction(rule: AutomationRuleRecord, blockMinutes: number | null): string {\n\t\tif (rule.kind === \"clockOut\") {\n\t\t\treturn \"clocked out\";\n\t\t}\n\t\tif (rule.kind === \"clockIn\") {\n\t\t\treturn \"clocked in\";\n\t\t}\n\t\tif (rule.kind === \"breakReminder\") {\n\t\t\treturn `reminded after ${blockMinutes ?? 0} min without a break`;\n\t\t}\n\t\treturn \"reported a missing punch\";\n\t}\n\n\t/**\n\t * Republishes the figures shortly after data changed.\n\t *\n\t * A punch from the web app, the terminal or a badge arrives through the API of this very process, so without\n\t * this the states would only change with the five minute timer. One action often produces several events, so\n\t * the refresh is collected for a moment instead of running once per event.\n\t */\n\tprivate scheduleStateRefresh(): void {\n\t\tif (this.stateRefreshTimer) {\n\t\t\treturn;\n\t\t}\n\t\tthis.stateRefreshTimer = this.setTimeout(() => {\n\t\t\tthis.stateRefreshTimer = undefined;\n\t\t\tvoid this.refreshStates();\n\t\t}, 1000);\n\t}\n\n\t/**\n\t * Publishes the newest event of the instance into the state tree.\n\t *\n\t * A notification adapter, a dashboard or a Blockly script then only has to watch `events.*` instead of\n\t * following the live stream of the web app.\n\t *\n\t * @param event - event published by the API\n\t */\n\tprivate async publishEventState(event: ApiEvent): Promise<void> {\n\t\tconst services = this.services;\n\t\tif (!services) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst data = event.data ?? {};\n\t\t\tconst user =\n\t\t\t\tevent.userId === null || event.userId === undefined ? null : services.users.findById(event.userId);\n\t\t\tawait publishEventSnapshot(this, {\n\t\t\t\ttype: event.type,\n\t\t\t\tatUtc: event.atUtc,\n\t\t\t\tuserName: user?.displayName ?? \"\",\n\t\t\t\tdirection: typeof data.direction === \"string\" ? data.direction : \"\",\n\t\t\t\tsource: typeof data.source === \"string\" ? data.source : \"\",\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.debug(`event state could not be published: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t/**\n\t * Publishes a punch on the event bus, so it reaches `events.*` and the connected web clients.\n\t *\n\t * A punch can be written from several places \u2014 the command states, a `sendTo` message, a trigger rule, an\n\t * automation rule and the presence state. Every one of them passes its result through here; a path that forgets\n\t * it leaves the event states (`lastType`, `lastUser`, \u2026) on the punch before.\n\t *\n\t * @param event - event built from the punch, `null` when nothing was written\n\t */\n\tprivate publishPunch(event: ApiEvent | null): void {\n\t\tif (event) {\n\t\t\tthis.events?.publish(event);\n\t\t}\n\t}\n\n\t/**\n\t * Is called when another adapter sends a message (`sendTo`).\n\t *\n\t * @param obj - message object of the adapter framework\n\t */\n\tprivate onMessage(obj: ioBroker.Message): void {\n\t\tvoid (async (): Promise<void> => {\n\t\t\tconst services = this.services;\n\t\t\tif (!services) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst answer = await handleMessage(\n\t\t\t\t\t{\n\t\t\t\t\t\tusers: services.users,\n\t\t\t\t\t\tentries: services.entries,\n\t\t\t\t\t\tabsences: services.absences,\n\t\t\t\t\t\tsettings: services.settings,\n\t\t\t\t\t\taggregation: services.aggregation,\n\t\t\t\t\t\tsync: services.sync,\n\t\t\t\t\t\tbackup: services.backup,\n\t\t\t\t\t\tversion: this.version,\n\t\t\t\t\t},\n\t\t\t\t\tobj.command,\n\t\t\t\t\tobj.message,\n\t\t\t\t);\n\t\t\t\tthis.log.info(`message ${obj.command}: ${answer.message}`);\n\t\t\t\t// a punch answered by a message reaches `events.*` and the web app like every other one\n\t\t\t\tconst direction = answer.data?.direction;\n\t\t\t\tconst userId = answer.data?.userId;\n\t\t\t\tif (typeof direction === \"string\" && typeof userId === \"number\") {\n\t\t\t\t\tthis.publishPunch({\n\t\t\t\t\t\ttype: \"punch\",\n\t\t\t\t\t\tatUtc: Math.floor(Date.now() / 1000),\n\t\t\t\t\t\tuserId,\n\t\t\t\t\t\tdata: { direction, source: `sendTo.${obj.command}` },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (obj.callback) {\n\t\t\t\t\tthis.sendTo(obj.from, obj.command, answer, obj.callback);\n\t\t\t\t}\n\t\t\t\tawait this.refreshStates();\n\t\t\t} catch (error) {\n\t\t\t\tconst message = (error as Error).message;\n\t\t\t\tthis.log.warn(`message ${obj.command} failed: ${message}`);\n\t\t\t\tif (obj.callback) {\n\t\t\t\t\tthis.sendTo(obj.from, obj.command, { ok: false, message }, obj.callback);\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\t}\n\n\t/**\n\t * Is called if a subscribed state changes\n\t *\n\t * @param id - State ID\n\t * @param state - State object\n\t */\n\tprivate onStateChange(id: string, state: ioBroker.State | null | undefined): void {\n\t\ttry {\n\t\t\tif (!state) {\n\t\t\t\t// deletions carry no value\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// a watched state of another adapter is handled first: such a state is acknowledged by its own\n\t\t\t// adapter, and what counts there is the change of the value\n\t\t\tif (this.handleTriggerState(id, state)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (state.ack) {\n\t\t\t\t// Ignore acknowledged (status) states of this instance\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst prefix = `${this.namespace}.`;\n\t\t\tconst localId = id.startsWith(prefix) ? id.slice(prefix.length) : id;\n\t\t\tif (localId.startsWith(\"commands.\")) {\n\t\t\t\tvoid this.runCommand(localId, state.val);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (parsePresenceStateId(localId) !== null) {\n\t\t\t\tvoid this.runPresence(localId, state.val);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Error in onStateChange for ${id}: ${(error as Error).message}`);\n\t\t}\n\t}\n}\n\nif (require.main !== module) {\n\t// Export the constructor in compact mode\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new TimeTracker(options);\n} else {\n\t// otherwise start the instance directly\n\t(() => new TimeTracker())();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["import_triggers", "result"]
7
7
  }
package/io-package.json CHANGED
@@ -1,9 +1,35 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "time-tracker",
4
- "version": "0.7.9",
4
+ "version": "0.7.11",
5
5
  "nogit": true,
6
6
  "news": {
7
+ "0.7.11": {
8
+ "en": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
9
+ "de": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
10
+ "ru": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
11
+ "pt": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
12
+ "nl": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
13
+ "fr": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
14
+ "it": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
15
+ "es": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
16
+ "pl": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
17
+ "uk": "fix: the calendar file goes through the **file API** of the adapter now — the folder",
18
+ "zh-cn": "fix: the calendar file goes through the **file API** of the adapter now — the folder"
19
+ },
20
+ "0.7.10": {
21
+ "en": "change: the state `calendar.feedFile` is called “Path of the calendar file (.ics)” now — the old name “calendar file” made the value look like a download, while it is the path that the `ical` adapter reads; a browser, a calendar app or a script uses the link `calendar.feedUrl`.",
22
+ "de": "change: Der Zustand `calendar.feedFile` heißt jetzt „Pfad der Kalenderdatei (.ics)“ – der alte Name „Kalenderdatei“ ließ den Wert wie einen Download aussehen, obwohl er der Pfad ist, den der `ical`-Adapter liest; ein Browser, eine Kalender-App oder ein Skript nutzt den Link `calendar.feedUrl`.",
23
+ "ru": "изменение: состояние `calendar.feedFile` теперь называется «Путь к файлу календаря (.ics)» — прежнее имя «файл календаря» выглядело как загрузка, хотя это путь, который читает адаптер `ical`; браузер, календарное приложение или скрипт использует ссылку `calendar.feedUrl`.",
24
+ "pt": "alteração: o estado `calendar.feedFile` chama-se agora «Caminho do ficheiro de calendário (.ics)» — o nome antigo «ficheiro de calendário» fazia o valor parecer uma transferência, embora seja o caminho que o adaptador `ical` lê; um navegador, uma aplicação de calendário ou um script usa a ligação `calendar.feedUrl`.",
25
+ "nl": "wijziging: de toestand `calendar.feedFile` heet nu ‘Pad van het kalenderbestand (.ics)’ — de oude naam ‘kalenderbestand’ deed de waarde als een download lijken, terwijl het het pad is dat de `ical`-adapter leest; een browser, een agenda-app of een script gebruikt de link `calendar.feedUrl`.",
26
+ "fr": "changement : l'état `calendar.feedFile` s'appelle désormais « Chemin du fichier de calendrier (.ics) » — l'ancien nom « fichier de calendrier » faisait passer la valeur pour un téléchargement, alors que c'est le chemin que lit l'adaptateur `ical` ; un navigateur, une application d'agenda ou un script utilise le lien `calendar.feedUrl`.",
27
+ "it": "modifica: lo stato `calendar.feedFile` ora si chiama «Percorso del file di calendario (.ics)» — il vecchio nome «file di calendario» faceva sembrare il valore un download, mentre è il percorso che legge l'adattatore `ical`; un browser, un'app di calendario o uno script usa il link `calendar.feedUrl`.",
28
+ "es": "cambio: el estado `calendar.feedFile` ahora se llama «Ruta del archivo de calendario (.ics)» — el nombre antiguo «archivo de calendario» hacía que el valor pareciera una descarga, aunque es la ruta que lee el adaptador `ical`; un navegador, una app de calendario o un script usa el enlace `calendar.feedUrl`.",
29
+ "pl": "zmiana: stan `calendar.feedFile` nazywa się teraz „Ścieżka pliku kalendarza (.ics)” — dawna nazwa „plik kalendarza” wyglądała jak pobieranie, a to ścieżka, którą czyta adapter `ical`; przeglądarka, aplikacja kalendarza lub skrypt używa linku `calendar.feedUrl`.",
30
+ "uk": "зміна: стан `calendar.feedFile` тепер називається «Шлях до файлу календаря (.ics)» — стара назва «файл календаря» виглядала як завантаження, хоча це шлях, який читає адаптер `ical`; браузер, календарний застосунок або скрипт використовує посилання `calendar.feedUrl`.",
31
+ "zh-cn": "变更:状态 `calendar.feedFile` 现在名为“日历文件的路径(.ics)”——旧名称“日历文件”让人以为该值是一个下载文件,实际上它是 `ical` 适配器读取的路径;浏览器、日历应用或脚本使用链接 `calendar.feedUrl`。"
32
+ },
7
33
  "0.7.9": {
8
34
  "en": "fix: “Feiertag hinzufügen” stayed grey as long as the day had to be typed as text — it comes from the date field of the browser now, and the label no longer names a format. A holiday entered without a region belongs to the country of the instance instead of always “DE”, the list shows the days of every region of the year, and a day of the next year takes the shown year along. “Abbrechen” closes the form of an absence while it is being changed as well.",
9
35
  "de": "fix: „Feiertag hinzufügen“ blieb grau, solange der Tag als Text eingetippt werden musste – jetzt kommt er aus dem Datumsfeld des Browsers, und die Beschriftung nennt kein Format mehr. Ein ohne Region erfasster Feiertag gehört dem Land der Instanz statt immer „DE“, die Liste zeigt die Tage aller Regionen des Jahres, und ein Tag des nächsten Jahres holt das angezeigte Jahr mit. „Abbrechen“ schließt auch das Fenster einer Änderung.",
@@ -68,32 +94,6 @@
68
94
  "pl": "zmiana: źródła interfejsu web znajdują się teraz w `src-www/` (wcześniej `src-pwa/`) — kontrola repozytorium czyta każdy folder, którego nazwy nie zna, i zgłaszała pięć `W5042` dla react, MUI i i18next, których instalacja adaptera nie powinna zawierać; dla działającej instalacji nic się nie zmienia, zbudowana aplikacja jest nadal serwowana z `www/`. Okresowe odświeżanie i godzinne sprawdzanie kopii zapasowych pomijają teraz przebieg zamiast uruchamiać drugą kopię, a `onStateChange` nie zapisuje już wiersza debugowania dla każdego obcego stanu.",
69
95
  "uk": "зміна: вихідний код веб-інтерфейсу тепер у `src-www/` (раніше `src-pwa/`) — перевірка репозиторію читає кожну теку, назви якої не знає, і повідомляла про п'ять `W5042` для react, MUI та i18next, яких не повинна містити інсталяція адаптера; для працюючої інсталяції нічого не змінюється, зібраний застосунок і надалі віддається з `www/`. Періодичне оновлення та щогодинна перевірка резервних копій тепер пропускають прохід замість запуску другої копії, а `onStateChange` більше не записує рядок налагодження для кожного чужого стану.",
70
96
  "zh-cn": "变更:网页界面的源码现在位于 `src-www/`(此前为 `src-pwa/`)——仓库检查会读取每个它不知道名称的文件夹,并报告了五个 `W5042`(react、MUI 和 i18next),适配器安装不应携带这些依赖;运行中的安装不受影响,构建后的应用仍从 `www/` 提供。定期刷新和每小时的备份检查现在会跳过一次,而不是启动第二份,`onStateChange` 也不再为每个外部状态写入调试行。"
71
- },
72
- "0.7.4": {
73
- "en": "fix: the subscription link of the company (`calendar.feedUrl`) carries the API prefix now (`/api/calendar.ics?token=…`) — without it the browser showed the web app and its login instead of the calendar. The written calendar file sits in `files/time-tracker.<n>/calendar.ics`, where a `web` instance hands it out as a download while the `ical` adapter keeps reading it as a local file.",
74
- "de": "fix: Der Abo-Link der Firma (`calendar.feedUrl`) trägt jetzt das API-Präfix (`/api/calendar.ics?token=…`) – ohne es zeigte der Browser die Web-Oberfläche samt Anmeldung statt des Kalenders. Die geschriebene Kalenderdatei liegt in `files/time-tracker.<n>/calendar.ics`, wo eine `web`-Instanz sie als Download ausliefert, während der `ical`-Adapter sie weiter als lokale Datei liest.",
75
- "ru": "исправление: ссылка подписки фирмы (`calendar.feedUrl`) теперь содержит префикс API (`/api/calendar.ics?token=…`) — без него браузер показывал веб-интерфейс с входом вместо календаря. Записываемый файл календаря лежит в `files/time-tracker.<n>/calendar.ics`, откуда экземпляр `web` отдаёт его на скачивание, а адаптер `ical` по-прежнему читает его как локальный файл.",
76
- "pt": "correção: a ligação de subscrição da empresa (`calendar.feedUrl`) passa a levar o prefixo da API (`/api/calendar.ics?token=…`) — sem ele o navegador mostrava a aplicação web e o seu login em vez do calendário. O ficheiro de calendário escrito fica em `files/time-tracker.<n>/calendar.ics`, de onde uma instância `web` o entrega como download enquanto o adaptador `ical` continua a lê-lo como ficheiro local.",
77
- "nl": "fix: de abonnementslink van het bedrijf (`calendar.feedUrl`) bevat nu het API-voorvoegsel (`/api/calendar.ics?token=…`) — zonder dat toonde de browser de webapp en zijn login in plaats van de kalender. Het geschreven kalenderbestand staat in `files/time-tracker.<n>/calendar.ics`, waar een `web`-instantie het als download aanbiedt terwijl de `ical`-adapter het als lokaal bestand blijft lezen.",
78
- "fr": "correctif : le lien d'abonnement de l'entreprise (`calendar.feedUrl`) porte désormais le préfixe de l'API (`/api/calendar.ics?token=…`) — sans lui, le navigateur affichait l'application web et sa connexion au lieu du calendrier. Le fichier de calendrier écrit se trouve dans `files/time-tracker.<n>/calendar.ics`, où une instance `web` le propose en téléchargement tandis que l'adaptateur `ical` continue à le lire comme fichier local.",
79
- "it": "correzione: il link di sottoscrizione dell'azienda (`calendar.feedUrl`) ora porta il prefisso dell'API (`/api/calendar.ics?token=…`) — senza di esso il browser mostrava l'app web e il suo login invece del calendario. Il file di calendario scritto si trova in `files/time-tracker.<n>/calendar.ics`, da dove un'istanza `web` lo offre come download mentre l'adattatore `ical` continua a leggerlo come file locale.",
80
- "es": "corrección: el enlace de suscripción de la empresa (`calendar.feedUrl`) ahora lleva el prefijo de la API (`/api/calendar.ics?token=…`) — sin él el navegador mostraba la aplicación web y su inicio de sesión en lugar del calendario. El archivo de calendario escrito está en `files/time-tracker.<n>/calendar.ics`, desde donde una instancia `web` lo entrega como descarga mientras que el adaptador `ical` sigue leyéndolo como archivo local.",
81
- "pl": "poprawka: link subskrypcji firmy (`calendar.feedUrl`) zawiera teraz prefiks API (`/api/calendar.ics?token=…`) — bez niego przeglądarka pokazywała aplikację web z logowaniem zamiast kalendarza. Zapisywany plik kalendarza znajduje się w `files/time-tracker.<n>/calendar.ics`, skąd instancja `web` udostępnia go do pobrania, a adapter `ical` nadal czyta go jako plik lokalny.",
82
- "uk": "виправлення: посилання підписки компанії (`calendar.feedUrl`) тепер містить префікс API (`/api/calendar.ics?token=…`) — без нього браузер показував веб-застосунок із входом замість календаря. Записуваний файл календаря лежить у `files/time-tracker.<n>/calendar.ics`, звідки екземпляр `web` віддає його на завантаження, а адаптер `ical` далі читає його як локальний файл.",
83
- "zh-cn": "修复:公司的订阅链接(`calendar.feedUrl`)现在带有 API 前缀(`/api/calendar.ics?token=…`)—— 否则浏览器显示的是 Web 应用及其登录页而非日历。写出的日历文件位于 `files/time-tracker.<n>/calendar.ics`,由 `web` 实例提供下载,而 `ical` 适配器仍将其作为本地文件读取。"
84
- },
85
- "0.7.3": {
86
- "en": "new: the calendar goes to ioBroker. The adapter writes `calendar.ics` into its instance folder — the `ical` adapter reads that as a local file, without URL, token or network — and publishes the subscription link of the company in `calendar.feedUrl` plus the same days as JSON in `calendar.absences` (`calendar.updatedAt` says how fresh they are). That link opens the absences of all employees, so nothing happens by itself: `commands.rotateCalendarToken` creates the token and replaces it on every further call, which kills an old link at once. Besides, the guard `requireInsideEditWindow` is gone — it had been unreachable since times belong to the administration (`edit_window_days` only feeds the offline queue).",
87
- "de": "neu: Der Kalender geht an ioBroker. Der Adapter schreibt `calendar.ics` in seinen Instanz-Ordner — der `ical`-Adapter liest das als lokale Datei, ohne URL, Token und Netz — und veröffentlicht den Abo-Link der Firma als `calendar.feedUrl` sowie dieselben Tage als JSON in `calendar.absences` (`calendar.updatedAt` sagt, wie frisch sie sind). Dieser Link öffnet die Abwesenheiten aller Mitarbeiter, deshalb passiert nichts von selbst: `commands.rotateCalendarToken` erzeugt den Token und ersetzt ihn bei jedem weiteren Aufruf, womit ein alter Link sofort tot ist. Außerdem ist die Prüfung `requireInsideEditWindow` entfernt: sie war seit „Zeiten gehören der Verwaltung“ nicht mehr erreichbar (`edit_window_days` wirkt nur noch auf die Offline-Warteschlange).",
88
- "ru": "новое: календарь передаётся в ioBroker. Адаптер пишет `calendar.ics` в папку своего экземпляра — адаптер `ical` читает его как локальный файл, без URL, токена и сети — и публикует ссылку подписки фирмы в `calendar.feedUrl`, а те же дни в JSON — в `calendar.absences` (`calendar.updatedAt` показывает, насколько они свежие). Эта ссылка открывает отсутствия всех сотрудников, поэтому ничего не происходит само: `commands.rotateCalendarToken` создаёт токен и заменяет его при каждом следующем вызове, отчего старая ссылка сразу перестаёт работать. Кроме того, удалена проверка `requireInsideEditWindow`: она была недостижима с момента «время принадлежит администрации» (`edit_window_days` влияет только на офлайн-очередь).",
89
- "pt": "novo: o calendário vai para o ioBroker. O adaptador escreve `calendar.ics` na pasta da sua instância — o adaptador `ical` lê-o como ficheiro local, sem URL, token nem rede — e publica a ligação de subscrição da empresa em `calendar.feedUrl` e os mesmos dias em JSON em `calendar.absences` (`calendar.updatedAt` diz quão recentes são). Essa ligação abre as ausências de todos os funcionários, por isso nada acontece por si: `commands.rotateCalendarToken` cria o token e substitui-o a cada chamada seguinte, o que mata logo uma ligação antiga. Além disso, a verificação `requireInsideEditWindow` desapareceu: era inalcançável desde que «os tempos pertencem à administração» (`edit_window_days` só alimenta a fila offline).",
90
- "nl": "nieuw: de kalender gaat naar ioBroker. De adapter schrijft `calendar.ics` in de map van zijn instantie — de `ical`-adapter leest dat als lokaal bestand, zonder URL, token of netwerk — en publiceert de abonnementslink van het bedrijf in `calendar.feedUrl` plus dezelfde dagen als JSON in `calendar.absences` (`calendar.updatedAt` zegt hoe vers ze zijn). Die link opent de afwezigheden van alle medewerkers, dus er gebeurt niets vanzelf: `commands.rotateCalendarToken` maakt de token aan en vervangt hem bij elke volgende aanroep, waardoor een oude link meteen dood is. Daarnaast is de controle `requireInsideEditWindow` verdwenen: die was onbereikbaar sinds „tijden zijn van de administratie“ (`edit_window_days` voedt alleen de offline wachtrij).",
91
- "fr": "nouveau : le calendrier va vers ioBroker. L'adaptateur écrit `calendar.ics` dans le dossier de son instance — l'adaptateur `ical` le lit comme fichier local, sans URL, jeton ni réseau — et publie le lien d'abonnement de l'entreprise dans `calendar.feedUrl` ainsi que les mêmes jours en JSON dans `calendar.absences` (`calendar.updatedAt` dit leur fraîcheur). Ce lien ouvre les absences de tous les employés, donc rien ne se fait tout seul : `commands.rotateCalendarToken` crée le jeton et le remplace à chaque appel suivant, ce qui tue aussitôt un ancien lien. En outre, le contrôle `requireInsideEditWindow` a disparu : il était inatteignable depuis « les horaires appartiennent à l'administration » (`edit_window_days` n'alimente que la file hors ligne).",
92
- "it": "novità: il calendario va a ioBroker. L'adattatore scrive `calendar.ics` nella cartella della sua istanza — l'adattatore `ical` lo legge come file locale, senza URL, token o rete — e pubblica il link di sottoscrizione dell'azienda in `calendar.feedUrl` e gli stessi giorni in JSON in `calendar.absences` (`calendar.updatedAt` dice quanto sono freschi). Quel link apre le assenze di tutti i dipendenti, quindi non succede nulla da solo: `commands.rotateCalendarToken` crea il token e lo sostituisce a ogni chiamata successiva, il che rende subito inutile un vecchio link. Inoltre il controllo `requireInsideEditWindow` è stato rimosso: era irraggiungibile da quando «gli orari appartengono all'amministrazione» (`edit_window_days` alimenta solo la coda offline).",
93
- "es": "novedad: el calendario va a ioBroker. El adaptador escribe `calendar.ics` en la carpeta de su instancia — el adaptador `ical` lo lee como archivo local, sin URL, token ni red — y publica el enlace de suscripción de la empresa en `calendar.feedUrl` y los mismos días como JSON en `calendar.absences` (`calendar.updatedAt` dice su antigüedad). Ese enlace abre las ausencias de todos los empleados, así que nada ocurre por sí solo: `commands.rotateCalendarToken` crea el token y lo sustituye en cada llamada posterior, lo que anula al instante un enlace antiguo. Además se ha eliminado la comprobación `requireInsideEditWindow`: era inalcanzable desde «los horarios pertenecen a la administración» (`edit_window_days` solo alimenta la cola sin conexión).",
94
- "pl": "nowość: kalendarz trafia do ioBroker. Adapter zapisuje `calendar.ics` w folderze swojej instancji — adapter `ical` czyta go jako plik lokalny, bez URL, tokenu i sieci — i publikuje link subskrypcji firmy w `calendar.feedUrl` oraz te same dni jako JSON w `calendar.absences` (`calendar.updatedAt` mówi, jak świeże są). Ten link otwiera nieobecności wszystkich pracowników, więc nic nie dzieje się samo: `commands.rotateCalendarToken` tworzy token i zastępuje go przy każdym kolejnym wywołaniu, co natychmiast unieważnia stary link. Dodatkowo usunięto kontrolę `requireInsideEditWindow`: była nieosiągalna od czasu „czas należy do administracji” (`edit_window_days` zasila tylko kolejkę offline).",
95
- "uk": "новинка: календар передається в ioBroker. Адаптер пише `calendar.ics` у папці свого екземпляра — адаптер `ical` читає його як локальний файл, без URL, токена й мережі — і публікує посилання підписки компанії в `calendar.feedUrl`, а ті самі дні як JSON — у `calendar.absences` (`calendar.updatedAt` показує, наскільки вони свіжі). Це посилання відкриває відсутності всіх працівників, тому нічого не відбувається саме собою: `commands.rotateCalendarToken` створює токен і замінює його за кожного наступного виклику, від чого старе посилання одразу перестає працювати. Крім того, прибрано перевірку `requireInsideEditWindow`: вона була недосяжною відколи «час належить адміністрації» (`edit_window_days` живить лише офлайн-чергу).",
96
- "zh-cn": "新增:日历接入 ioBroker。适配器把 `calendar.ics` 写入其实例文件夹 —— `ical` 适配器可将其作为本地文件读取,无需网址、令牌或网络 —— 并把公司的订阅链接发布在 `calendar.feedUrl`,同样的日期以 JSON 形式发布在 `calendar.absences`(`calendar.updatedAt` 显示其新鲜度)。该链接会打开所有员工的缺勤,因此不会自动生成:`commands.rotateCalendarToken` 创建令牌,并在之后每次调用时替换它,旧链接立即失效。此外移除了 `requireInsideEditWindow` 检查:自「时间属于管理方」以来它已无法触发(`edit_window_days` 现在只影响离线队列)。"
97
97
  }
98
98
  },
99
99
  "titleLang": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.time-tracker",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
4
4
  "description": "Time tracking (clock-in/out) for ioBroker with web app, badge terminal and reports",
5
5
  "author": {
6
6
  "name": "Alex",
package/www/sw.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sw.js","sources":["../../../../../../tmp/40a6df6facf2de28811807290cb6213b/sw.js"],"sourcesContent":["import {clientsClaim as workbox_core_clientsClaim} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {registerRoute as workbox_routing_registerRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-routing/registerRoute.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"person.png\",\n \"revision\": \"771d3283ece4739e203e02aac535855e\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"6d2083467c5cde28f5f7b04204705994\"\n },\n {\n \"url\": \"icon-512.png\",\n \"revision\": \"d8e3715779f4cd7e67e1f82f215a2f22\"\n },\n {\n \"url\": \"icon-192.png\",\n \"revision\": \"940ef37caef09c52272ddab844b9ea9b\"\n },\n {\n \"url\": \"favicon.svg\",\n \"revision\": \"309e9efece69d60920618512ab9d57bf\"\n },\n {\n \"url\": \"assets/workbox-window.prod.es5-Bd17z0YL.js\",\n \"revision\": null\n },\n {\n \"url\": \"assets/index-D5c1Xz-S.js\",\n \"revision\": null\n },\n {\n \"url\": \"icon-192.png\",\n \"revision\": \"940ef37caef09c52272ddab844b9ea9b\"\n },\n {\n \"url\": \"icon-512.png\",\n \"revision\": \"d8e3715779f4cd7e67e1f82f215a2f22\"\n },\n {\n \"url\": \"manifest.webmanifest\",\n \"revision\": \"6f40f1ce0ce138d5af307fb0dbb1e6f9\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], denylist: [/^\\/api\\//],}));\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","denylist"],"mappings":"inBAoBAA,KAAKC,cACLC,EAAAA,eAMAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,MAEd,CACED,IAAO,2BACPC,SAAY,MAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,qCAEb,CAAA,GACHC,EAAAA,wBAA2CC,EAAAA,cAA8B,IAAIC,EAAAA,gBAAgCC,0BAA2C,cAAe,CACrKC,UAAW,CAAC,QAAUC,SAAU,CAAC"}
1
+ {"version":3,"file":"sw.js","sources":["../../../../../../tmp/e785ede677a63c37d77757a4ee1d1ea4/sw.js"],"sourcesContent":["import {clientsClaim as workbox_core_clientsClaim} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {registerRoute as workbox_routing_registerRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-routing/registerRoute.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-www/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"person.png\",\n \"revision\": \"771d3283ece4739e203e02aac535855e\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"6d2083467c5cde28f5f7b04204705994\"\n },\n {\n \"url\": \"icon-512.png\",\n \"revision\": \"d8e3715779f4cd7e67e1f82f215a2f22\"\n },\n {\n \"url\": \"icon-192.png\",\n \"revision\": \"940ef37caef09c52272ddab844b9ea9b\"\n },\n {\n \"url\": \"favicon.svg\",\n \"revision\": \"309e9efece69d60920618512ab9d57bf\"\n },\n {\n \"url\": \"assets/workbox-window.prod.es5-Bd17z0YL.js\",\n \"revision\": null\n },\n {\n \"url\": \"assets/index-D5c1Xz-S.js\",\n \"revision\": null\n },\n {\n \"url\": \"icon-192.png\",\n \"revision\": \"940ef37caef09c52272ddab844b9ea9b\"\n },\n {\n \"url\": \"icon-512.png\",\n \"revision\": \"d8e3715779f4cd7e67e1f82f215a2f22\"\n },\n {\n \"url\": \"manifest.webmanifest\",\n \"revision\": \"6f40f1ce0ce138d5af307fb0dbb1e6f9\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], denylist: [/^\\/api\\//],}));\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","denylist"],"mappings":"inBAoBAA,KAAKC,cACLC,EAAAA,eAMAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,MAEd,CACED,IAAO,2BACPC,SAAY,MAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,qCAEb,CAAA,GACHC,EAAAA,wBAA2CC,EAAAA,cAA8B,IAAIC,EAAAA,gBAAgCC,0BAA2C,cAAe,CACrKC,UAAW,CAAC,QAAUC,SAAU,CAAC"}