iobroker.time-tracker 0.2.0 → 0.2.2

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.
@@ -32,6 +32,7 @@ __export(states_exports, {
32
32
  readUserSnapshot: () => readUserSnapshot
33
33
  });
34
34
  module.exports = __toCommonJS(states_exports);
35
+ var import_stateNames = require("./stateNames");
35
36
  var import_time = require("../util/time");
36
37
  const COMMAND_IDS = {
37
38
  /** Employee the commands apply to */
@@ -64,6 +65,11 @@ function stateObject(name, type, role, options = {}) {
64
65
  function channelObject(name) {
65
66
  return { type: "channel", common: { name }, native: {} };
66
67
  }
68
+ async function ensureObject(port, id, object) {
69
+ var _a;
70
+ await port.setObjectNotExists(id, object);
71
+ await port.extendObject(id, { common: { name: (_a = object.common) == null ? void 0 : _a.name } });
72
+ }
67
73
  function readUserSnapshot(args) {
68
74
  var _a, _b, _c;
69
75
  const user = args.users.findById(args.userId);
@@ -125,143 +131,108 @@ async function publishAllUserStates(args) {
125
131
  }
126
132
  async function createUserChannel(port, userId) {
127
133
  const id = `users.${userId}`;
128
- await port.setObjectNotExists("users", channelObject({ en: "Employees", de: "Mitarbeiter" }));
129
- await port.setObjectNotExists(id, channelObject({ en: "Employee", de: "Mitarbeiter" }));
130
- await port.setObjectNotExists(`${id}.displayName`, stateObject({ en: "Name", de: "Name" }, "string", "info.name"));
131
- await port.setObjectNotExists(
134
+ await ensureObject(port, "users", channelObject(import_stateNames.STATE_NAMES.usersChannel));
135
+ await ensureObject(port, id, channelObject(import_stateNames.STATE_NAMES.employeeChannel));
136
+ await ensureObject(port, `${id}.displayName`, stateObject(import_stateNames.STATE_NAMES.displayName, "string", "info.name"));
137
+ await ensureObject(
138
+ port,
132
139
  `${id}.present`,
133
- stateObject(
134
- { en: "Present (working time is running)", de: "Anwesenheit (Arbeitszeit l\xE4uft)" },
135
- "boolean",
136
- "switch",
137
- { write: true }
138
- )
140
+ stateObject(import_stateNames.STATE_NAMES.userPresent, "boolean", "switch", { write: true })
139
141
  );
140
- await port.setObjectNotExists(
142
+ await ensureObject(
143
+ port,
141
144
  `${id}.hasOpenEntry`,
142
- stateObject({ en: "Punched in", de: "Eingestempelt" }, "boolean", "indicator.working")
145
+ stateObject(import_stateNames.STATE_NAMES.hasOpenEntry, "boolean", "indicator.working")
143
146
  );
144
- await port.setObjectNotExists(
145
- `${id}.lastPunch`,
146
- stateObject({ en: "Last punch", de: "Letzte Buchung" }, "number", "value.time")
147
- );
148
- await port.setObjectNotExists(
147
+ await ensureObject(port, `${id}.lastPunch`, stateObject(import_stateNames.STATE_NAMES.lastPunch, "number", "value.time"));
148
+ await ensureObject(
149
+ port,
149
150
  `${id}.todayWorkedMinutes`,
150
- stateObject({ en: "Worked today", de: "Heute gearbeitet" }, "number", "value", { unit: "min" })
151
+ stateObject(import_stateNames.STATE_NAMES.todayWorkedMinutes, "number", "value", { unit: "min" })
151
152
  );
152
- await port.setObjectNotExists(
153
+ await ensureObject(
154
+ port,
153
155
  `${id}.todayBalanceMinutes`,
154
- stateObject({ en: "Balance today", de: "Saldo heute" }, "number", "value", { unit: "min" })
156
+ stateObject(import_stateNames.STATE_NAMES.todayBalanceMinutes, "number", "value", { unit: "min" })
155
157
  );
156
- await port.setObjectNotExists(
158
+ await ensureObject(
159
+ port,
157
160
  `${id}.monthWorkedMinutes`,
158
- stateObject({ en: "Worked this month", de: "Diesen Monat gearbeitet" }, "number", "value", { unit: "min" })
161
+ stateObject(import_stateNames.STATE_NAMES.monthWorkedMinutes, "number", "value", { unit: "min" })
159
162
  );
160
- await port.setObjectNotExists(
163
+ await ensureObject(
164
+ port,
161
165
  `${id}.monthBalanceMinutes`,
162
- stateObject({ en: "Balance this month", de: "Saldo diesen Monat" }, "number", "value", { unit: "min" })
166
+ stateObject(import_stateNames.STATE_NAMES.monthBalanceMinutes, "number", "value", { unit: "min" })
163
167
  );
164
- await port.setObjectNotExists(
168
+ await ensureObject(
169
+ port,
165
170
  `${id}.yearBalanceMinutes`,
166
- stateObject({ en: "Balance this year", de: "Saldo dieses Jahr" }, "number", "value", { unit: "min" })
167
- );
168
- await port.setObjectNotExists(
169
- `${id}.openConflicts`,
170
- stateObject({ en: "Open conflicts", de: "Offene Konflikte" }, "number", "value")
171
+ stateObject(import_stateNames.STATE_NAMES.yearBalanceMinutes, "number", "value", { unit: "min" })
171
172
  );
173
+ await ensureObject(port, `${id}.openConflicts`, stateObject(import_stateNames.STATE_NAMES.openConflicts, "number", "value"));
172
174
  return id;
173
175
  }
174
176
  async function createCommandStates(port) {
175
- await port.setObjectNotExists("commands", channelObject({ en: "Commands", de: "Befehle" }));
176
- await port.setObjectNotExists(
177
+ await ensureObject(port, "commands", channelObject(import_stateNames.STATE_NAMES.commandsChannel));
178
+ await ensureObject(
179
+ port,
177
180
  COMMAND_IDS.punchUserId,
178
- stateObject(
179
- { en: "Employee id for punch commands", de: "Mitarbeiter-Id f\xFCr Stempelbefehle" },
180
- "number",
181
- "value",
182
- {
183
- write: true
184
- }
185
- )
181
+ stateObject(import_stateNames.STATE_NAMES.punchUserId, "number", "level", {
182
+ write: true
183
+ })
186
184
  );
187
- await port.setObjectNotExists(
185
+ await ensureObject(
186
+ port,
188
187
  COMMAND_IDS.punch,
189
- stateObject({ en: "Punch in or out", de: "Ein- oder ausstempeln" }, "boolean", "button", {
188
+ stateObject(import_stateNames.STATE_NAMES.punch, "boolean", "button", {
190
189
  read: false,
191
190
  write: true
192
191
  })
193
192
  );
194
- await port.setObjectNotExists(
193
+ await ensureObject(
194
+ port,
195
195
  COMMAND_IDS.quickPunch,
196
- stateObject({ en: "Punch with quick rounding", de: "Stempeln mit Schnellrundung" }, "boolean", "button", {
196
+ stateObject(import_stateNames.STATE_NAMES.quickPunch, "boolean", "button", {
197
197
  read: false,
198
198
  write: true
199
199
  })
200
200
  );
201
- await port.setObjectNotExists(
201
+ await ensureObject(
202
+ port,
202
203
  COMMAND_IDS.closeMonth,
203
- stateObject({ en: "Close month (YYYY-MM)", de: "Monat abschlie\xDFen (JJJJ-MM)" }, "string", "text", {
204
+ stateObject(import_stateNames.STATE_NAMES.closeMonth, "string", "text", {
204
205
  write: true
205
206
  })
206
207
  );
207
- await port.setObjectNotExists(
208
- COMMAND_IDS.recalc,
209
- stateObject(
210
- { en: "Recalculate period (YYYY-MM or YYYY)", de: "Zeitraum neu berechnen (JJJJ-MM oder JJJJ)" },
211
- "string",
212
- "text",
213
- { write: true }
214
- )
215
- );
216
- await port.setObjectNotExists(
208
+ await ensureObject(port, COMMAND_IDS.recalc, stateObject(import_stateNames.STATE_NAMES.recalc, "string", "text", { write: true }));
209
+ await ensureObject(
210
+ port,
217
211
  COMMAND_IDS.backup,
218
- stateObject({ en: "Write a database backup", de: "Datenbank-Sicherung schreiben" }, "boolean", "button", {
212
+ stateObject(import_stateNames.STATE_NAMES.backupCommand, "boolean", "button", {
219
213
  read: false,
220
214
  write: true
221
215
  })
222
216
  );
223
217
  }
224
218
  async function createInfoStates(port) {
225
- await port.setObjectNotExists("info", channelObject({ en: "Information", de: "Information" }));
226
- await port.setObjectNotExists(
227
- "info.version",
228
- stateObject({ en: "Adapter version", de: "Adapter-Version" }, "string", "text")
229
- );
230
- await port.setObjectNotExists(
231
- "info.schemaVersion",
232
- stateObject({ en: "Database schema version", de: "Datenbank-Schemaversion" }, "string", "text")
233
- );
234
- await port.setObjectNotExists(
219
+ await ensureObject(port, "info", channelObject(import_stateNames.STATE_NAMES.infoChannel));
220
+ await ensureObject(port, "info.version", stateObject(import_stateNames.STATE_NAMES.version, "string", "text"));
221
+ await ensureObject(port, "info.schemaVersion", stateObject(import_stateNames.STATE_NAMES.schemaVersion, "string", "text"));
222
+ await ensureObject(
223
+ port,
235
224
  "info.dbSizeBytes",
236
- stateObject({ en: "Database size", de: "Datenbank-Gr\xF6\xDFe" }, "number", "value", { unit: "bytes" })
237
- );
238
- await port.setObjectNotExists(
239
- "info.lastError",
240
- stateObject({ en: "Last error", de: "Letzter Fehler" }, "string", "text")
241
- );
242
- await port.setObjectNotExists(
243
- "info.lastBackup",
244
- stateObject({ en: "Last backup", de: "Letzte Sicherung" }, "number", "value.time")
225
+ stateObject(import_stateNames.STATE_NAMES.dbSizeBytes, "number", "value", { unit: "bytes" })
245
226
  );
227
+ await ensureObject(port, "info.lastError", stateObject(import_stateNames.STATE_NAMES.lastError, "string", "text"));
228
+ await ensureObject(port, "info.lastBackup", stateObject(import_stateNames.STATE_NAMES.lastBackup, "number", "value.time"));
246
229
  }
247
230
  async function createCompanyStates(port) {
248
- await port.setObjectNotExists("company", channelObject({ en: "Company", de: "Firma" }));
249
- await port.setObjectNotExists(
250
- "company.presentCount",
251
- stateObject({ en: "Present employees", de: "Anwesende Mitarbeiter" }, "number", "value")
252
- );
253
- await port.setObjectNotExists(
254
- "company.present",
255
- stateObject({ en: "Who is present", de: "Wer ist anwesend" }, "string", "text")
256
- );
257
- await port.setObjectNotExists(
258
- "company.openConflicts",
259
- stateObject({ en: "Open conflicts", de: "Offene Konflikte" }, "number", "value")
260
- );
261
- await port.setObjectNotExists(
262
- "company.lastPunch",
263
- stateObject({ en: "Last punch", de: "Letzte Buchung" }, "number", "value.time")
264
- );
231
+ await ensureObject(port, "company", channelObject(import_stateNames.STATE_NAMES.companyChannel));
232
+ await ensureObject(port, "company.presentCount", stateObject(import_stateNames.STATE_NAMES.companyPresentCount, "number", "value"));
233
+ await ensureObject(port, "company.present", stateObject(import_stateNames.STATE_NAMES.companyPresent, "string", "text"));
234
+ await ensureObject(port, "company.openConflicts", stateObject(import_stateNames.STATE_NAMES.openConflicts, "number", "value"));
235
+ await ensureObject(port, "company.lastPunch", stateObject(import_stateNames.STATE_NAMES.companyLastPunch, "number", "value.time"));
265
236
  }
266
237
  function readCompanySnapshot(snapshots) {
267
238
  const present = snapshots.filter((snapshot) => snapshot.hasOpenEntry);
@@ -283,27 +254,12 @@ async function publishCompanySnapshot(port, snapshot) {
283
254
  await port.setState("company.lastPunch", (_a = snapshot.lastPunchUtc) != null ? _a : 0, true);
284
255
  }
285
256
  async function createEventStates(port) {
286
- await port.setObjectNotExists("events", channelObject({ en: "Events", de: "Ereignisse" }));
287
- await port.setObjectNotExists(
288
- "events.lastAt",
289
- stateObject({ en: "Last event at", de: "Letztes Ereignis um" }, "number", "value.time")
290
- );
291
- await port.setObjectNotExists(
292
- "events.lastType",
293
- stateObject({ en: "Kind of the last event", de: "Art des letzten Ereignisses" }, "string", "text")
294
- );
295
- await port.setObjectNotExists(
296
- "events.lastUser",
297
- stateObject({ en: "Employee of the last event", de: "Mitarbeiter des letzten Ereignisses" }, "string", "text")
298
- );
299
- await port.setObjectNotExists(
300
- "events.lastDirection",
301
- stateObject({ en: "Direction of the last punch", de: "Richtung der letzten Buchung" }, "string", "text")
302
- );
303
- await port.setObjectNotExists(
304
- "events.lastSource",
305
- stateObject({ en: "Source of the last event", de: "Quelle des letzten Ereignisses" }, "string", "text")
306
- );
257
+ await ensureObject(port, "events", channelObject(import_stateNames.STATE_NAMES.eventsChannel));
258
+ await ensureObject(port, "events.lastAt", stateObject(import_stateNames.STATE_NAMES.lastAt, "number", "value.time"));
259
+ await ensureObject(port, "events.lastType", stateObject(import_stateNames.STATE_NAMES.lastType, "string", "text"));
260
+ await ensureObject(port, "events.lastUser", stateObject(import_stateNames.STATE_NAMES.lastUser, "string", "text"));
261
+ await ensureObject(port, "events.lastDirection", stateObject(import_stateNames.STATE_NAMES.lastDirection, "string", "text"));
262
+ await ensureObject(port, "events.lastSource", stateObject(import_stateNames.STATE_NAMES.lastSource, "string", "text"));
307
263
  }
308
264
  async function publishEventSnapshot(port, snapshot) {
309
265
  await port.setState("events.lastAt", snapshot.atUtc, true);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/adapter/states.ts"],
4
- "sourcesContent": ["/**\n * States published by the adapter.\n *\n * The adapter exposes a small, stable state tree so that other adapters (and the user's dashboards) can show\n * and trigger time tracking without speaking to the REST API:\n *\n * ```\n * users.<userId>.displayName / present / hasOpenEntry / lastPunch / todayWorkedMinutes / todayBalanceMinutes\n * users.<userId>.openConflicts\n * commands.punchUserId / punch / quickPunch / closeMonth / recalc\n * ```\n *\n * All objects are created with `setObjectNotExists`, so existing instances keep their settings. The logic works\n * on a tiny port interface (`StatePort`) \u2014 the adapter instance in production, a recorder in the tests.\n */\n\nimport type { UsersRepository } from \"../db/repositories/users\";\nimport type { AggregationService } from \"../services/aggregation\";\nimport type { SyncService } from \"../services/sync\";\nimport { localDate as resolveLocalDate } from \"../util/time\";\n\n/** Subset of the adapter API the state layer needs. */\nexport interface StatePort {\n\t/**\n\t * Creates an object when it does not exist yet.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param object - object definition\n\t */\n\tsetObjectNotExists(id: string, object: ioBroker.SettableObject): Promise<unknown> | void;\n\t/**\n\t * Writes a state value.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param value - value to write\n\t * @param ack - true for confirmed values\n\t */\n\tsetState(id: string, value: ioBroker.StateValue, ack?: boolean): Promise<unknown> | void;\n}\n\n/** Figures of one employee shown in the state tree. */\nexport interface UserSnapshot {\n\t/** Database id of the employee */\n\tuserId: number;\n\t/** Shown name */\n\tdisplayName: string;\n\t/** True when the last punch of today has no counterpart */\n\thasOpenEntry: boolean;\n\t/** Instant of the last punch of the day, `null` when there is none */\n\tlastPunchUtc: number | null;\n\t/** Net working time of today in minutes */\n\tworkedMinutes: number;\n\t/** Balance of today in minutes */\n\tbalanceMinutes: number;\n\t/** Punches waiting for a decision */\n\topenConflicts: number;\n\t/** Net working time of the current month in minutes */\n\tmonthWorkedMinutes: number;\n\t/** Balance of the current month in minutes */\n\tmonthBalanceMinutes: number;\n\t/** Balance of the current year in minutes */\n\tyearBalanceMinutes: number;\n}\n\n/** Figures of the whole company, published next to the employees. */\nexport interface CompanySnapshot {\n\t/** Employees that are clocked in right now */\n\tpresentCount: number;\n\t/** Names of those employees, separated by a comma (empty when nobody is present) */\n\tpresent: string;\n\t/** Punches of all employees waiting for a decision */\n\topenConflicts: number;\n\t/** Instant of the newest punch of today, `null` when nobody punched */\n\tlastPunchUtc: number | null;\n}\n\n/** Newest event of the instance, mirrored into the state tree. */\nexport interface EventSnapshot {\n\t/** Kind of the event, e.g. `punch` */\n\ttype: string;\n\t/** Instant the event belongs to */\n\tatUtc: number;\n\t/** Name of the employee the event belongs to, empty for instance wide events */\n\tuserName: string;\n\t/** Direction of a punch (`in`/`out`), empty for other events */\n\tdirection: string;\n\t/** Where the change came from, e.g. `web` or `terminal`, empty when unknown */\n\tsource: string;\n}\n\n/** Names of the writable command states. */\nexport const COMMAND_IDS = {\n\t/** Employee the commands apply to */\n\tpunchUserId: \"commands.punchUserId\",\n\t/** Punch in or out for `punchUserId` */\n\tpunch: \"commands.punch\",\n\t/** Punch in or out with the configured quick rounding */\n\tquickPunch: \"commands.quickPunch\",\n\t/** Close a month, value `YYYY-MM` */\n\tcloseMonth: \"commands.closeMonth\",\n\t/** Recalculate a period, value `YYYY-MM` or `YYYY` */\n\trecalc: \"commands.recalc\",\n\t/** Write a backup of the database */\n\tbackup: \"commands.backup\",\n} as const;\n\n/**\n * Builds a state object definition.\n *\n * @param name - name in English and German\n * @param name.en - English name\n * @param name.de - German name\n * @param type - state type\n * @param role - state role\n * @param options - additional flags\n * @param options.read - value can be read (default true)\n * @param options.write - value can be written (default false)\n * @param options.unit - unit of the value\n * @returns object definition\n */\nfunction stateObject(\n\tname: { en: string; de: string },\n\ttype: ioBroker.CommonType,\n\trole: string,\n\toptions: { read?: boolean; write?: boolean; unit?: string } = {},\n): ioBroker.SettableObject {\n\treturn {\n\t\ttype: \"state\",\n\t\tcommon: {\n\t\t\tname,\n\t\t\ttype,\n\t\t\trole,\n\t\t\tread: options.read !== false,\n\t\t\twrite: options.write === true,\n\t\t\t...(options.unit ? { unit: options.unit } : {}),\n\t\t},\n\t\tnative: {},\n\t};\n}\n\n/**\n * Builds a channel object definition.\n *\n * @param name - name in English and German\n * @param name.en - English name\n * @param name.de - German name\n * @returns object definition\n */\nfunction channelObject(name: { en: string; de: string }): ioBroker.SettableObject {\n\treturn { type: \"channel\", common: { name }, native: {} };\n}\n\n/**\n * Reads the figures of an employee for the state tree.\n *\n * @param args - data sources and employee\n * @param args.aggregation - aggregation service\n * @param args.users - user storage\n * @param args.sync - synchronisation service (open conflicts)\n * @param args.userId - database id of the employee\n * @param args.now - instant of the snapshot\n * @returns snapshot of the figures\n */\nexport function readUserSnapshot(args: {\n\taggregation: AggregationService;\n\tusers: UsersRepository;\n\tsync: SyncService;\n\tuserId: number;\n\tnow?: number;\n}): UserSnapshot | null {\n\tconst user = args.users.findById(args.userId);\n\tif (!user) {\n\t\treturn null;\n\t}\n\n\tconst timestamp = args.now ?? Math.floor(Date.now() / 1000);\n\tconst today = resolveLocalDate(timestamp, user.timezone);\n\tconst day = args.aggregation.recalculateDay(user.id, today, { now: timestamp });\n\t// the month is recalculated like the month view does it; the year is read, so the publishing stays cheap\n\tconst year = Number(today.slice(0, 4));\n\tconst month = Number(today.slice(5, 7));\n\tconst monthAggregate = args.aggregation.recalculateMonth(user.id, year, month, { now: timestamp });\n\tconst yearAggregate = args.aggregation.year(user.id, year);\n\n\treturn {\n\t\tuserId: user.id,\n\t\tdisplayName: user.displayName,\n\t\thasOpenEntry: day.hasOpenEntry,\n\t\tlastPunchUtc: Math.max(day.firstInUtc ?? 0, day.lastOutUtc ?? 0) || null,\n\t\tworkedMinutes: day.workedMin,\n\t\tbalanceMinutes: day.balanceMin,\n\t\topenConflicts: args.sync.conflicts(user.id).length,\n\t\tmonthWorkedMinutes: monthAggregate.workedMin,\n\t\tmonthBalanceMinutes: monthAggregate.balanceMin,\n\t\tyearBalanceMinutes: yearAggregate ? yearAggregate.workedMin - yearAggregate.targetMin : 0,\n\t};\n}\n\n/**\n * Publishes the figures of one employee.\n *\n * @param port - state port\n * @param snapshot - figures to publish\n */\nexport async function publishUserSnapshot(port: StatePort, snapshot: UserSnapshot): Promise<void> {\n\tconst id = `users.${snapshot.userId}`;\n\tawait port.setState(`${id}.displayName`, snapshot.displayName, true);\n\tawait port.setState(`${id}.present`, snapshot.hasOpenEntry, true);\n\tawait port.setState(`${id}.hasOpenEntry`, snapshot.hasOpenEntry, true);\n\tawait port.setState(`${id}.lastPunch`, snapshot.lastPunchUtc ?? 0, true);\n\tawait port.setState(`${id}.todayWorkedMinutes`, snapshot.workedMinutes, true);\n\tawait port.setState(`${id}.todayBalanceMinutes`, snapshot.balanceMinutes, true);\n\tawait port.setState(`${id}.monthWorkedMinutes`, snapshot.monthWorkedMinutes, true);\n\tawait port.setState(`${id}.monthBalanceMinutes`, snapshot.monthBalanceMinutes, true);\n\tawait port.setState(`${id}.yearBalanceMinutes`, snapshot.yearBalanceMinutes, true);\n\tawait port.setState(`${id}.openConflicts`, snapshot.openConflicts, true);\n}\n\n/**\n * Creates the channels of all employees and publishes their current figures.\n *\n * @param args - state port and data sources\n * @param args.port - state port\n * @param args.aggregation - aggregation service\n * @param args.users - user storage\n * @param args.sync - synchronisation service\n * @param args.now - instant of the snapshot\n * @returns the published snapshots\n */\nexport async function publishAllUserStates(args: {\n\tport: StatePort;\n\taggregation: AggregationService;\n\tusers: UsersRepository;\n\tsync: SyncService;\n\tnow?: number;\n}): Promise<UserSnapshot[]> {\n\tconst snapshots: UserSnapshot[] = [];\n\tfor (const user of args.users.list({ includeInactive: true })) {\n\t\tawait createUserChannel(args.port, user.id);\n\t\tconst snapshot = readUserSnapshot({\n\t\t\taggregation: args.aggregation,\n\t\t\tusers: args.users,\n\t\t\tsync: args.sync,\n\t\t\tuserId: user.id,\n\t\t\tnow: args.now,\n\t\t});\n\t\tif (!snapshot) {\n\t\t\tcontinue;\n\t\t}\n\t\tawait publishUserSnapshot(args.port, snapshot);\n\t\tsnapshots.push(snapshot);\n\t}\n\treturn snapshots;\n}\n\n/**\n * Creates the channel of one employee, including the shared parent channel `users`.\n *\n * The parent channel matters: the ioBroker object structure check (`E3009`) requires every state to have its\n * parents, so `users.<userId>.*` below a missing `users` produced 126 findings in the repository check\n * (PR #6697). `setObjectNotExists` keeps the call idempotent.\n *\n * @param port - state port\n * @param userId - database id of the employee\n * @returns the channel id\n */\nexport async function createUserChannel(port: StatePort, userId: number): Promise<string> {\n\tconst id = `users.${userId}`;\n\tawait port.setObjectNotExists(\"users\", channelObject({ en: \"Employees\", de: \"Mitarbeiter\" }));\n\tawait port.setObjectNotExists(id, channelObject({ en: \"Employee\", de: \"Mitarbeiter\" }));\n\tawait port.setObjectNotExists(`${id}.displayName`, stateObject({ en: \"Name\", de: \"Name\" }, \"string\", \"info.name\"));\n\t// the writable twin of `hasOpenEntry`: a script, a fingerprint reader or a dashboard writes it to say that\n\t// somebody arrived (`true`) or left (`false`) \u2014 see `presence.ts` for what the adapter does with it\n\tawait port.setObjectNotExists(\n\t\t`${id}.present`,\n\t\tstateObject(\n\t\t\t{ en: \"Present (working time is running)\", de: \"Anwesenheit (Arbeitszeit l\u00E4uft)\" },\n\t\t\t\"boolean\",\n\t\t\t\"switch\",\n\t\t\t{ write: true },\n\t\t),\n\t);\n\tawait port.setObjectNotExists(\n\t\t`${id}.hasOpenEntry`,\n\t\tstateObject({ en: \"Punched in\", de: \"Eingestempelt\" }, \"boolean\", \"indicator.working\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t`${id}.lastPunch`,\n\t\tstateObject({ en: \"Last punch\", de: \"Letzte Buchung\" }, \"number\", \"value.time\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t`${id}.todayWorkedMinutes`,\n\t\tstateObject({ en: \"Worked today\", de: \"Heute gearbeitet\" }, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait port.setObjectNotExists(\n\t\t`${id}.todayBalanceMinutes`,\n\t\tstateObject({ en: \"Balance today\", de: \"Saldo heute\" }, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait port.setObjectNotExists(\n\t\t`${id}.monthWorkedMinutes`,\n\t\tstateObject({ en: \"Worked this month\", de: \"Diesen Monat gearbeitet\" }, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait port.setObjectNotExists(\n\t\t`${id}.monthBalanceMinutes`,\n\t\tstateObject({ en: \"Balance this month\", de: \"Saldo diesen Monat\" }, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait port.setObjectNotExists(\n\t\t`${id}.yearBalanceMinutes`,\n\t\tstateObject({ en: \"Balance this year\", de: \"Saldo dieses Jahr\" }, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait port.setObjectNotExists(\n\t\t`${id}.openConflicts`,\n\t\tstateObject({ en: \"Open conflicts\", de: \"Offene Konflikte\" }, \"number\", \"value\"),\n\t);\n\treturn id;\n}\n\n/**\n * Creates the writable command states.\n *\n * @param port - state port\n */\nexport async function createCommandStates(port: StatePort): Promise<void> {\n\tawait port.setObjectNotExists(\"commands\", channelObject({ en: \"Commands\", de: \"Befehle\" }));\n\tawait port.setObjectNotExists(\n\t\tCOMMAND_IDS.punchUserId,\n\t\tstateObject(\n\t\t\t{ en: \"Employee id for punch commands\", de: \"Mitarbeiter-Id f\u00FCr Stempelbefehle\" },\n\t\t\t\"number\",\n\t\t\t\"value\",\n\t\t\t{\n\t\t\t\twrite: true,\n\t\t\t},\n\t\t),\n\t);\n\tawait port.setObjectNotExists(\n\t\tCOMMAND_IDS.punch,\n\t\tstateObject({ en: \"Punch in or out\", de: \"Ein- oder ausstempeln\" }, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait port.setObjectNotExists(\n\t\tCOMMAND_IDS.quickPunch,\n\t\tstateObject({ en: \"Punch with quick rounding\", de: \"Stempeln mit Schnellrundung\" }, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait port.setObjectNotExists(\n\t\tCOMMAND_IDS.closeMonth,\n\t\tstateObject({ en: \"Close month (YYYY-MM)\", de: \"Monat abschlie\u00DFen (JJJJ-MM)\" }, \"string\", \"text\", {\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait port.setObjectNotExists(\n\t\tCOMMAND_IDS.recalc,\n\t\tstateObject(\n\t\t\t{ en: \"Recalculate period (YYYY-MM or YYYY)\", de: \"Zeitraum neu berechnen (JJJJ-MM oder JJJJ)\" },\n\t\t\t\"string\",\n\t\t\t\"text\",\n\t\t\t{ write: true },\n\t\t),\n\t);\n\tawait port.setObjectNotExists(\n\t\tCOMMAND_IDS.backup,\n\t\tstateObject({ en: \"Write a database backup\", de: \"Datenbank-Sicherung schreiben\" }, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n}\n\n/**\n * Creates the informational states of the instance.\n *\n * @param port - state port\n */\nexport async function createInfoStates(port: StatePort): Promise<void> {\n\tawait port.setObjectNotExists(\"info\", channelObject({ en: \"Information\", de: \"Information\" }));\n\tawait port.setObjectNotExists(\n\t\t\"info.version\",\n\t\tstateObject({ en: \"Adapter version\", de: \"Adapter-Version\" }, \"string\", \"text\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"info.schemaVersion\",\n\t\tstateObject({ en: \"Database schema version\", de: \"Datenbank-Schemaversion\" }, \"string\", \"text\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"info.dbSizeBytes\",\n\t\tstateObject({ en: \"Database size\", de: \"Datenbank-Gr\u00F6\u00DFe\" }, \"number\", \"value\", { unit: \"bytes\" }),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"info.lastError\",\n\t\tstateObject({ en: \"Last error\", de: \"Letzter Fehler\" }, \"string\", \"text\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"info.lastBackup\",\n\t\tstateObject({ en: \"Last backup\", de: \"Letzte Sicherung\" }, \"number\", \"value.time\"),\n\t);\n}\n\n/**\n * Creates the states of the company.\n *\n * @param port - state port\n */\nexport async function createCompanyStates(port: StatePort): Promise<void> {\n\tawait port.setObjectNotExists(\"company\", channelObject({ en: \"Company\", de: \"Firma\" }));\n\tawait port.setObjectNotExists(\n\t\t\"company.presentCount\",\n\t\tstateObject({ en: \"Present employees\", de: \"Anwesende Mitarbeiter\" }, \"number\", \"value\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"company.present\",\n\t\tstateObject({ en: \"Who is present\", de: \"Wer ist anwesend\" }, \"string\", \"text\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"company.openConflicts\",\n\t\tstateObject({ en: \"Open conflicts\", de: \"Offene Konflikte\" }, \"number\", \"value\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"company.lastPunch\",\n\t\tstateObject({ en: \"Last punch\", de: \"Letzte Buchung\" }, \"number\", \"value.time\"),\n\t);\n}\n\n/**\n * Reads the company figures out of the figures of the employees.\n *\n * @param snapshots - figures of all employees\n * @returns the company figures\n */\nexport function readCompanySnapshot(snapshots: UserSnapshot[]): CompanySnapshot {\n\tconst present = snapshots.filter(snapshot => snapshot.hasOpenEntry);\n\treturn {\n\t\tpresentCount: present.length,\n\t\tpresent: present.map(snapshot => snapshot.displayName).join(\", \"),\n\t\topenConflicts: snapshots.reduce((sum, snapshot) => sum + snapshot.openConflicts, 0),\n\t\tlastPunchUtc: snapshots.reduce<number | null>(\n\t\t\t(newest, snapshot) =>\n\t\t\t\tsnapshot.lastPunchUtc !== null && (newest === null || snapshot.lastPunchUtc > newest)\n\t\t\t\t\t? snapshot.lastPunchUtc\n\t\t\t\t\t: newest,\n\t\t\tnull,\n\t\t),\n\t};\n}\n\n/**\n * Publishes the company figures.\n *\n * @param port - state port\n * @param snapshot - figures to publish\n */\nexport async function publishCompanySnapshot(port: StatePort, snapshot: CompanySnapshot): Promise<void> {\n\tawait port.setState(\"company.presentCount\", snapshot.presentCount, true);\n\tawait port.setState(\"company.present\", snapshot.present, true);\n\tawait port.setState(\"company.openConflicts\", snapshot.openConflicts, true);\n\tawait port.setState(\"company.lastPunch\", snapshot.lastPunchUtc ?? 0, true);\n}\n\n/**\n * Creates the event states.\n *\n * @param port - state port\n */\nexport async function createEventStates(port: StatePort): Promise<void> {\n\tawait port.setObjectNotExists(\"events\", channelObject({ en: \"Events\", de: \"Ereignisse\" }));\n\tawait port.setObjectNotExists(\n\t\t\"events.lastAt\",\n\t\tstateObject({ en: \"Last event at\", de: \"Letztes Ereignis um\" }, \"number\", \"value.time\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"events.lastType\",\n\t\tstateObject({ en: \"Kind of the last event\", de: \"Art des letzten Ereignisses\" }, \"string\", \"text\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"events.lastUser\",\n\t\tstateObject({ en: \"Employee of the last event\", de: \"Mitarbeiter des letzten Ereignisses\" }, \"string\", \"text\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"events.lastDirection\",\n\t\tstateObject({ en: \"Direction of the last punch\", de: \"Richtung der letzten Buchung\" }, \"string\", \"text\"),\n\t);\n\tawait port.setObjectNotExists(\n\t\t\"events.lastSource\",\n\t\tstateObject({ en: \"Source of the last event\", de: \"Quelle des letzten Ereignisses\" }, \"string\", \"text\"),\n\t);\n}\n\n/**\n * Publishes the newest event.\n *\n * @param port - state port\n * @param snapshot - event to publish\n */\nexport async function publishEventSnapshot(port: StatePort, snapshot: EventSnapshot): Promise<void> {\n\tawait port.setState(\"events.lastAt\", snapshot.atUtc, true);\n\tawait port.setState(\"events.lastType\", snapshot.type, true);\n\tawait port.setState(\"events.lastUser\", snapshot.userName, true);\n\tawait port.setState(\"events.lastDirection\", snapshot.direction, true);\n\tawait port.setState(\"events.lastSource\", snapshot.source, true);\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,kBAA8C;AAwEvC,MAAM,cAAc;AAAA;AAAA,EAE1B,aAAa;AAAA;AAAA,EAEb,OAAO;AAAA;AAAA,EAEP,YAAY;AAAA;AAAA,EAEZ,YAAY;AAAA;AAAA,EAEZ,QAAQ;AAAA;AAAA,EAER,QAAQ;AACT;AAgBA,SAAS,YACR,MACA,MACA,MACA,UAA8D,CAAC,GACrC;AAC1B,SAAO;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM,QAAQ,SAAS;AAAA,MACvB,OAAO,QAAQ,UAAU;AAAA,MACzB,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC9C;AAAA,IACA,QAAQ,CAAC;AAAA,EACV;AACD;AAUA,SAAS,cAAc,MAA2D;AACjF,SAAO,EAAE,MAAM,WAAW,QAAQ,EAAE,KAAK,GAAG,QAAQ,CAAC,EAAE;AACxD;AAaO,SAAS,iBAAiB,MAMT;AAzKxB;AA0KC,QAAM,OAAO,KAAK,MAAM,SAAS,KAAK,MAAM;AAC5C,MAAI,CAAC,MAAM;AACV,WAAO;AAAA,EACR;AAEA,QAAM,aAAY,UAAK,QAAL,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC1D,QAAM,YAAQ,YAAAA,WAAiB,WAAW,KAAK,QAAQ;AACvD,QAAM,MAAM,KAAK,YAAY,eAAe,KAAK,IAAI,OAAO,EAAE,KAAK,UAAU,CAAC;AAE9E,QAAM,OAAO,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC;AACrC,QAAM,QAAQ,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC;AACtC,QAAM,iBAAiB,KAAK,YAAY,iBAAiB,KAAK,IAAI,MAAM,OAAO,EAAE,KAAK,UAAU,CAAC;AACjG,QAAM,gBAAgB,KAAK,YAAY,KAAK,KAAK,IAAI,IAAI;AAEzD,SAAO;AAAA,IACN,QAAQ,KAAK;AAAA,IACb,aAAa,KAAK;AAAA,IAClB,cAAc,IAAI;AAAA,IAClB,cAAc,KAAK,KAAI,SAAI,eAAJ,YAAkB,IAAG,SAAI,eAAJ,YAAkB,CAAC,KAAK;AAAA,IACpE,eAAe,IAAI;AAAA,IACnB,gBAAgB,IAAI;AAAA,IACpB,eAAe,KAAK,KAAK,UAAU,KAAK,EAAE,EAAE;AAAA,IAC5C,oBAAoB,eAAe;AAAA,IACnC,qBAAqB,eAAe;AAAA,IACpC,oBAAoB,gBAAgB,cAAc,YAAY,cAAc,YAAY;AAAA,EACzF;AACD;AAQA,eAAsB,oBAAoB,MAAiB,UAAuC;AA5MlG;AA6MC,QAAM,KAAK,SAAS,SAAS,MAAM;AACnC,QAAM,KAAK,SAAS,GAAG,EAAE,gBAAgB,SAAS,aAAa,IAAI;AACnE,QAAM,KAAK,SAAS,GAAG,EAAE,YAAY,SAAS,cAAc,IAAI;AAChE,QAAM,KAAK,SAAS,GAAG,EAAE,iBAAiB,SAAS,cAAc,IAAI;AACrE,QAAM,KAAK,SAAS,GAAG,EAAE,eAAc,cAAS,iBAAT,YAAyB,GAAG,IAAI;AACvE,QAAM,KAAK,SAAS,GAAG,EAAE,uBAAuB,SAAS,eAAe,IAAI;AAC5E,QAAM,KAAK,SAAS,GAAG,EAAE,wBAAwB,SAAS,gBAAgB,IAAI;AAC9E,QAAM,KAAK,SAAS,GAAG,EAAE,uBAAuB,SAAS,oBAAoB,IAAI;AACjF,QAAM,KAAK,SAAS,GAAG,EAAE,wBAAwB,SAAS,qBAAqB,IAAI;AACnF,QAAM,KAAK,SAAS,GAAG,EAAE,uBAAuB,SAAS,oBAAoB,IAAI;AACjF,QAAM,KAAK,SAAS,GAAG,EAAE,kBAAkB,SAAS,eAAe,IAAI;AACxE;AAaA,eAAsB,qBAAqB,MAMf;AAC3B,QAAM,YAA4B,CAAC;AACnC,aAAW,QAAQ,KAAK,MAAM,KAAK,EAAE,iBAAiB,KAAK,CAAC,GAAG;AAC9D,UAAM,kBAAkB,KAAK,MAAM,KAAK,EAAE;AAC1C,UAAM,WAAW,iBAAiB;AAAA,MACjC,aAAa,KAAK;AAAA,MAClB,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,KAAK,KAAK;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAU;AACd;AAAA,IACD;AACA,UAAM,oBAAoB,KAAK,MAAM,QAAQ;AAC7C,cAAU,KAAK,QAAQ;AAAA,EACxB;AACA,SAAO;AACR;AAaA,eAAsB,kBAAkB,MAAiB,QAAiC;AACzF,QAAM,KAAK,SAAS,MAAM;AAC1B,QAAM,KAAK,mBAAmB,SAAS,cAAc,EAAE,IAAI,aAAa,IAAI,cAAc,CAAC,CAAC;AAC5F,QAAM,KAAK,mBAAmB,IAAI,cAAc,EAAE,IAAI,YAAY,IAAI,cAAc,CAAC,CAAC;AACtF,QAAM,KAAK,mBAAmB,GAAG,EAAE,gBAAgB,YAAY,EAAE,IAAI,QAAQ,IAAI,OAAO,GAAG,UAAU,WAAW,CAAC;AAGjH,QAAM,KAAK;AAAA,IACV,GAAG,EAAE;AAAA,IACL;AAAA,MACC,EAAE,IAAI,qCAAqC,IAAI,qCAAkC;AAAA,MACjF;AAAA,MACA;AAAA,MACA,EAAE,OAAO,KAAK;AAAA,IACf;AAAA,EACD;AACA,QAAM,KAAK;AAAA,IACV,GAAG,EAAE;AAAA,IACL,YAAY,EAAE,IAAI,cAAc,IAAI,gBAAgB,GAAG,WAAW,mBAAmB;AAAA,EACtF;AACA,QAAM,KAAK;AAAA,IACV,GAAG,EAAE;AAAA,IACL,YAAY,EAAE,IAAI,cAAc,IAAI,iBAAiB,GAAG,UAAU,YAAY;AAAA,EAC/E;AACA,QAAM,KAAK;AAAA,IACV,GAAG,EAAE;AAAA,IACL,YAAY,EAAE,IAAI,gBAAgB,IAAI,mBAAmB,GAAG,UAAU,SAAS,EAAE,MAAM,MAAM,CAAC;AAAA,EAC/F;AACA,QAAM,KAAK;AAAA,IACV,GAAG,EAAE;AAAA,IACL,YAAY,EAAE,IAAI,iBAAiB,IAAI,cAAc,GAAG,UAAU,SAAS,EAAE,MAAM,MAAM,CAAC;AAAA,EAC3F;AACA,QAAM,KAAK;AAAA,IACV,GAAG,EAAE;AAAA,IACL,YAAY,EAAE,IAAI,qBAAqB,IAAI,0BAA0B,GAAG,UAAU,SAAS,EAAE,MAAM,MAAM,CAAC;AAAA,EAC3G;AACA,QAAM,KAAK;AAAA,IACV,GAAG,EAAE;AAAA,IACL,YAAY,EAAE,IAAI,sBAAsB,IAAI,qBAAqB,GAAG,UAAU,SAAS,EAAE,MAAM,MAAM,CAAC;AAAA,EACvG;AACA,QAAM,KAAK;AAAA,IACV,GAAG,EAAE;AAAA,IACL,YAAY,EAAE,IAAI,qBAAqB,IAAI,oBAAoB,GAAG,UAAU,SAAS,EAAE,MAAM,MAAM,CAAC;AAAA,EACrG;AACA,QAAM,KAAK;AAAA,IACV,GAAG,EAAE;AAAA,IACL,YAAY,EAAE,IAAI,kBAAkB,IAAI,mBAAmB,GAAG,UAAU,OAAO;AAAA,EAChF;AACA,SAAO;AACR;AAOA,eAAsB,oBAAoB,MAAgC;AACzE,QAAM,KAAK,mBAAmB,YAAY,cAAc,EAAE,IAAI,YAAY,IAAI,UAAU,CAAC,CAAC;AAC1F,QAAM,KAAK;AAAA,IACV,YAAY;AAAA,IACZ;AAAA,MACC,EAAE,IAAI,kCAAkC,IAAI,uCAAoC;AAAA,MAChF;AAAA,MACA;AAAA,MACA;AAAA,QACC,OAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AACA,QAAM,KAAK;AAAA,IACV,YAAY;AAAA,IACZ,YAAY,EAAE,IAAI,mBAAmB,IAAI,wBAAwB,GAAG,WAAW,UAAU;AAAA,MACxF,MAAM;AAAA,MACN,OAAO;AAAA,IACR,CAAC;AAAA,EACF;AACA,QAAM,KAAK;AAAA,IACV,YAAY;AAAA,IACZ,YAAY,EAAE,IAAI,6BAA6B,IAAI,8BAA8B,GAAG,WAAW,UAAU;AAAA,MACxG,MAAM;AAAA,MACN,OAAO;AAAA,IACR,CAAC;AAAA,EACF;AACA,QAAM,KAAK;AAAA,IACV,YAAY;AAAA,IACZ,YAAY,EAAE,IAAI,yBAAyB,IAAI,iCAA8B,GAAG,UAAU,QAAQ;AAAA,MACjG,OAAO;AAAA,IACR,CAAC;AAAA,EACF;AACA,QAAM,KAAK;AAAA,IACV,YAAY;AAAA,IACZ;AAAA,MACC,EAAE,IAAI,wCAAwC,IAAI,6CAA6C;AAAA,MAC/F;AAAA,MACA;AAAA,MACA,EAAE,OAAO,KAAK;AAAA,IACf;AAAA,EACD;AACA,QAAM,KAAK;AAAA,IACV,YAAY;AAAA,IACZ,YAAY,EAAE,IAAI,2BAA2B,IAAI,gCAAgC,GAAG,WAAW,UAAU;AAAA,MACxG,MAAM;AAAA,MACN,OAAO;AAAA,IACR,CAAC;AAAA,EACF;AACD;AAOA,eAAsB,iBAAiB,MAAgC;AACtE,QAAM,KAAK,mBAAmB,QAAQ,cAAc,EAAE,IAAI,eAAe,IAAI,cAAc,CAAC,CAAC;AAC7F,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,mBAAmB,IAAI,kBAAkB,GAAG,UAAU,MAAM;AAAA,EAC/E;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,2BAA2B,IAAI,0BAA0B,GAAG,UAAU,MAAM;AAAA,EAC/F;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,iBAAiB,IAAI,wBAAkB,GAAG,UAAU,SAAS,EAAE,MAAM,QAAQ,CAAC;AAAA,EACjG;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,cAAc,IAAI,iBAAiB,GAAG,UAAU,MAAM;AAAA,EACzE;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,eAAe,IAAI,mBAAmB,GAAG,UAAU,YAAY;AAAA,EAClF;AACD;AAOA,eAAsB,oBAAoB,MAAgC;AACzE,QAAM,KAAK,mBAAmB,WAAW,cAAc,EAAE,IAAI,WAAW,IAAI,QAAQ,CAAC,CAAC;AACtF,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,qBAAqB,IAAI,wBAAwB,GAAG,UAAU,OAAO;AAAA,EACxF;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,kBAAkB,IAAI,mBAAmB,GAAG,UAAU,MAAM;AAAA,EAC/E;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,kBAAkB,IAAI,mBAAmB,GAAG,UAAU,OAAO;AAAA,EAChF;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,cAAc,IAAI,iBAAiB,GAAG,UAAU,YAAY;AAAA,EAC/E;AACD;AAQO,SAAS,oBAAoB,WAA4C;AAC/E,QAAM,UAAU,UAAU,OAAO,cAAY,SAAS,YAAY;AAClE,SAAO;AAAA,IACN,cAAc,QAAQ;AAAA,IACtB,SAAS,QAAQ,IAAI,cAAY,SAAS,WAAW,EAAE,KAAK,IAAI;AAAA,IAChE,eAAe,UAAU,OAAO,CAAC,KAAK,aAAa,MAAM,SAAS,eAAe,CAAC;AAAA,IAClF,cAAc,UAAU;AAAA,MACvB,CAAC,QAAQ,aACR,SAAS,iBAAiB,SAAS,WAAW,QAAQ,SAAS,eAAe,UAC3E,SAAS,eACT;AAAA,MACJ;AAAA,IACD;AAAA,EACD;AACD;AAQA,eAAsB,uBAAuB,MAAiB,UAA0C;AAvcxG;AAwcC,QAAM,KAAK,SAAS,wBAAwB,SAAS,cAAc,IAAI;AACvE,QAAM,KAAK,SAAS,mBAAmB,SAAS,SAAS,IAAI;AAC7D,QAAM,KAAK,SAAS,yBAAyB,SAAS,eAAe,IAAI;AACzE,QAAM,KAAK,SAAS,sBAAqB,cAAS,iBAAT,YAAyB,GAAG,IAAI;AAC1E;AAOA,eAAsB,kBAAkB,MAAgC;AACvE,QAAM,KAAK,mBAAmB,UAAU,cAAc,EAAE,IAAI,UAAU,IAAI,aAAa,CAAC,CAAC;AACzF,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,iBAAiB,IAAI,sBAAsB,GAAG,UAAU,YAAY;AAAA,EACvF;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,0BAA0B,IAAI,8BAA8B,GAAG,UAAU,MAAM;AAAA,EAClG;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,8BAA8B,IAAI,sCAAsC,GAAG,UAAU,MAAM;AAAA,EAC9G;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,+BAA+B,IAAI,+BAA+B,GAAG,UAAU,MAAM;AAAA,EACxG;AACA,QAAM,KAAK;AAAA,IACV;AAAA,IACA,YAAY,EAAE,IAAI,4BAA4B,IAAI,iCAAiC,GAAG,UAAU,MAAM;AAAA,EACvG;AACD;AAQA,eAAsB,qBAAqB,MAAiB,UAAwC;AACnG,QAAM,KAAK,SAAS,iBAAiB,SAAS,OAAO,IAAI;AACzD,QAAM,KAAK,SAAS,mBAAmB,SAAS,MAAM,IAAI;AAC1D,QAAM,KAAK,SAAS,mBAAmB,SAAS,UAAU,IAAI;AAC9D,QAAM,KAAK,SAAS,wBAAwB,SAAS,WAAW,IAAI;AACpE,QAAM,KAAK,SAAS,qBAAqB,SAAS,QAAQ,IAAI;AAC/D;",
4
+ "sourcesContent": ["/**\n * States published by the adapter.\n *\n * The adapter exposes a small, stable state tree so that other adapters (and the user's dashboards) can show\n * and trigger time tracking without speaking to the REST API:\n *\n * ```\n * users.<userId>.displayName / present / hasOpenEntry / lastPunch / todayWorkedMinutes / todayBalanceMinutes\n * users.<userId>.openConflicts\n * commands.punchUserId / punch / quickPunch / closeMonth / recalc\n * ```\n *\n * All objects are created with `setObjectNotExists`, so existing instances keep their settings. The logic works\n * on a tiny port interface (`StatePort`) \u2014 the adapter instance in production, a recorder in the tests.\n */\n\nimport type { UsersRepository } from \"../db/repositories/users\";\nimport type { AggregationService } from \"../services/aggregation\";\nimport type { SyncService } from \"../services/sync\";\nimport { STATE_NAMES, type StateName } from \"./stateNames\";\nimport { localDate as resolveLocalDate } from \"../util/time\";\n\n/** Subset of the adapter API the state layer needs. */\nexport interface StatePort {\n\t/**\n\t * Creates an object when it does not exist yet.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param object - object definition\n\t */\n\tsetObjectNotExists(id: string, object: ioBroker.SettableObject): Promise<unknown> | void;\n\t/**\n\t * Merges fields into an object that already exists.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param object - fields to merge\n\t */\n\textendObject(id: string, object: ioBroker.PartialObject): Promise<unknown> | void;\n\t/**\n\t * Writes a state value.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param value - value to write\n\t * @param ack - true for confirmed values\n\t */\n\tsetState(id: string, value: ioBroker.StateValue, ack?: boolean): Promise<unknown> | void;\n}\n\n/** Figures of one employee shown in the state tree. */\nexport interface UserSnapshot {\n\t/** Database id of the employee */\n\tuserId: number;\n\t/** Shown name */\n\tdisplayName: string;\n\t/** True when the last punch of today has no counterpart */\n\thasOpenEntry: boolean;\n\t/** Instant of the last punch of the day, `null` when there is none */\n\tlastPunchUtc: number | null;\n\t/** Net working time of today in minutes */\n\tworkedMinutes: number;\n\t/** Balance of today in minutes */\n\tbalanceMinutes: number;\n\t/** Punches waiting for a decision */\n\topenConflicts: number;\n\t/** Net working time of the current month in minutes */\n\tmonthWorkedMinutes: number;\n\t/** Balance of the current month in minutes */\n\tmonthBalanceMinutes: number;\n\t/** Balance of the current year in minutes */\n\tyearBalanceMinutes: number;\n}\n\n/** Figures of the whole company, published next to the employees. */\nexport interface CompanySnapshot {\n\t/** Employees that are clocked in right now */\n\tpresentCount: number;\n\t/** Names of those employees, separated by a comma (empty when nobody is present) */\n\tpresent: string;\n\t/** Punches of all employees waiting for a decision */\n\topenConflicts: number;\n\t/** Instant of the newest punch of today, `null` when nobody punched */\n\tlastPunchUtc: number | null;\n}\n\n/** Newest event of the instance, mirrored into the state tree. */\nexport interface EventSnapshot {\n\t/** Kind of the event, e.g. `punch` */\n\ttype: string;\n\t/** Instant the event belongs to */\n\tatUtc: number;\n\t/** Name of the employee the event belongs to, empty for instance wide events */\n\tuserName: string;\n\t/** Direction of a punch (`in`/`out`), empty for other events */\n\tdirection: string;\n\t/** Where the change came from, e.g. `web` or `terminal`, empty when unknown */\n\tsource: string;\n}\n\n/** Names of the writable command states. */\nexport const COMMAND_IDS = {\n\t/** Employee the commands apply to */\n\tpunchUserId: \"commands.punchUserId\",\n\t/** Punch in or out for `punchUserId` */\n\tpunch: \"commands.punch\",\n\t/** Punch in or out with the configured quick rounding */\n\tquickPunch: \"commands.quickPunch\",\n\t/** Close a month, value `YYYY-MM` */\n\tcloseMonth: \"commands.closeMonth\",\n\t/** Recalculate a period, value `YYYY-MM` or `YYYY` */\n\trecalc: \"commands.recalc\",\n\t/** Write a backup of the database */\n\tbackup: \"commands.backup\",\n} as const;\n\n/**\n * Builds a state object definition.\n *\n * @param name - name in English and German\n * @param name.en - English name\n * @param name.de - German name\n * @param type - state type\n * @param role - state role\n * @param options - additional flags\n * @param options.read - value can be read (default true)\n * @param options.write - value can be written (default false)\n * @param options.unit - unit of the value\n * @returns object definition\n */\nfunction stateObject(\n\tname: StateName,\n\ttype: ioBroker.CommonType,\n\trole: string,\n\toptions: { read?: boolean; write?: boolean; unit?: string } = {},\n): ioBroker.SettableObject {\n\treturn {\n\t\ttype: \"state\",\n\t\tcommon: {\n\t\t\tname,\n\t\t\ttype,\n\t\t\trole,\n\t\t\tread: options.read !== false,\n\t\t\twrite: options.write === true,\n\t\t\t...(options.unit ? { unit: options.unit } : {}),\n\t\t},\n\t\tnative: {},\n\t};\n}\n\n/**\n * Builds a channel object definition.\n *\n * @param name - name in English and German\n * @param name.en - English name\n * @param name.de - German name\n * @returns object definition\n */\nfunction channelObject(name: StateName): ioBroker.SettableObject {\n\treturn { type: \"channel\", common: { name }, native: {} };\n}\n\n/**\n * Creates an object when it is missing and keeps its name current.\n *\n * `setObjectNotExists` alone would leave the names of existing installations untouched \u2014 the ioBroker object\n * structure check (`E6001`) reads them from the running instance, so the name is refreshed on every start. The\n * merge only touches `common.name`, existing links (for example in `vis`) stay as they are.\n *\n * @param port - state port\n * @param id - full state id (including the instance prefix)\n * @param object - object definition\n */\nasync function ensureObject(port: StatePort, id: string, object: ioBroker.SettableObject): Promise<void> {\n\tawait port.setObjectNotExists(id, object);\n\tawait port.extendObject(id, { common: { name: object.common?.name } });\n}\n\n/**\n * Reads the figures of an employee for the state tree.\n *\n * @param args - data sources and employee\n * @param args.aggregation - aggregation service\n * @param args.users - user storage\n * @param args.sync - synchronisation service (open conflicts)\n * @param args.userId - database id of the employee\n * @param args.now - instant of the snapshot\n * @returns snapshot of the figures\n */\nexport function readUserSnapshot(args: {\n\taggregation: AggregationService;\n\tusers: UsersRepository;\n\tsync: SyncService;\n\tuserId: number;\n\tnow?: number;\n}): UserSnapshot | null {\n\tconst user = args.users.findById(args.userId);\n\tif (!user) {\n\t\treturn null;\n\t}\n\n\tconst timestamp = args.now ?? Math.floor(Date.now() / 1000);\n\tconst today = resolveLocalDate(timestamp, user.timezone);\n\tconst day = args.aggregation.recalculateDay(user.id, today, { now: timestamp });\n\t// the month is recalculated like the month view does it; the year is read, so the publishing stays cheap\n\tconst year = Number(today.slice(0, 4));\n\tconst month = Number(today.slice(5, 7));\n\tconst monthAggregate = args.aggregation.recalculateMonth(user.id, year, month, { now: timestamp });\n\tconst yearAggregate = args.aggregation.year(user.id, year);\n\n\treturn {\n\t\tuserId: user.id,\n\t\tdisplayName: user.displayName,\n\t\thasOpenEntry: day.hasOpenEntry,\n\t\tlastPunchUtc: Math.max(day.firstInUtc ?? 0, day.lastOutUtc ?? 0) || null,\n\t\tworkedMinutes: day.workedMin,\n\t\tbalanceMinutes: day.balanceMin,\n\t\topenConflicts: args.sync.conflicts(user.id).length,\n\t\tmonthWorkedMinutes: monthAggregate.workedMin,\n\t\tmonthBalanceMinutes: monthAggregate.balanceMin,\n\t\tyearBalanceMinutes: yearAggregate ? yearAggregate.workedMin - yearAggregate.targetMin : 0,\n\t};\n}\n\n/**\n * Publishes the figures of one employee.\n *\n * @param port - state port\n * @param snapshot - figures to publish\n */\nexport async function publishUserSnapshot(port: StatePort, snapshot: UserSnapshot): Promise<void> {\n\tconst id = `users.${snapshot.userId}`;\n\tawait port.setState(`${id}.displayName`, snapshot.displayName, true);\n\tawait port.setState(`${id}.present`, snapshot.hasOpenEntry, true);\n\tawait port.setState(`${id}.hasOpenEntry`, snapshot.hasOpenEntry, true);\n\tawait port.setState(`${id}.lastPunch`, snapshot.lastPunchUtc ?? 0, true);\n\tawait port.setState(`${id}.todayWorkedMinutes`, snapshot.workedMinutes, true);\n\tawait port.setState(`${id}.todayBalanceMinutes`, snapshot.balanceMinutes, true);\n\tawait port.setState(`${id}.monthWorkedMinutes`, snapshot.monthWorkedMinutes, true);\n\tawait port.setState(`${id}.monthBalanceMinutes`, snapshot.monthBalanceMinutes, true);\n\tawait port.setState(`${id}.yearBalanceMinutes`, snapshot.yearBalanceMinutes, true);\n\tawait port.setState(`${id}.openConflicts`, snapshot.openConflicts, true);\n}\n\n/**\n * Creates the channels of all employees and publishes their current figures.\n *\n * @param args - state port and data sources\n * @param args.port - state port\n * @param args.aggregation - aggregation service\n * @param args.users - user storage\n * @param args.sync - synchronisation service\n * @param args.now - instant of the snapshot\n * @returns the published snapshots\n */\nexport async function publishAllUserStates(args: {\n\tport: StatePort;\n\taggregation: AggregationService;\n\tusers: UsersRepository;\n\tsync: SyncService;\n\tnow?: number;\n}): Promise<UserSnapshot[]> {\n\tconst snapshots: UserSnapshot[] = [];\n\tfor (const user of args.users.list({ includeInactive: true })) {\n\t\tawait createUserChannel(args.port, user.id);\n\t\tconst snapshot = readUserSnapshot({\n\t\t\taggregation: args.aggregation,\n\t\t\tusers: args.users,\n\t\t\tsync: args.sync,\n\t\t\tuserId: user.id,\n\t\t\tnow: args.now,\n\t\t});\n\t\tif (!snapshot) {\n\t\t\tcontinue;\n\t\t}\n\t\tawait publishUserSnapshot(args.port, snapshot);\n\t\tsnapshots.push(snapshot);\n\t}\n\treturn snapshots;\n}\n\n/**\n * Creates the channel of one employee, including the shared parent channel `users`.\n *\n * The parent channel matters: the ioBroker object structure check (`E3009`) requires every state to have its\n * parents, so `users.<userId>.*` below a missing `users` produced 126 findings in the repository check\n * (PR #6697). `setObjectNotExists` keeps the call idempotent.\n *\n * @param port - state port\n * @param userId - database id of the employee\n * @returns the channel id\n */\nexport async function createUserChannel(port: StatePort, userId: number): Promise<string> {\n\tconst id = `users.${userId}`;\n\tawait ensureObject(port, \"users\", channelObject(STATE_NAMES.usersChannel));\n\tawait ensureObject(port, id, channelObject(STATE_NAMES.employeeChannel));\n\tawait ensureObject(port, `${id}.displayName`, stateObject(STATE_NAMES.displayName, \"string\", \"info.name\"));\n\t// the writable twin of `hasOpenEntry`: a script, a fingerprint reader or a dashboard writes it to say that\n\t// somebody arrived (`true`) or left (`false`) \u2014 see `presence.ts` for what the adapter does with it\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.present`,\n\t\tstateObject(STATE_NAMES.userPresent, \"boolean\", \"switch\", { write: true }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.hasOpenEntry`,\n\t\tstateObject(STATE_NAMES.hasOpenEntry, \"boolean\", \"indicator.working\"),\n\t);\n\tawait ensureObject(port, `${id}.lastPunch`, stateObject(STATE_NAMES.lastPunch, \"number\", \"value.time\"));\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.todayWorkedMinutes`,\n\t\tstateObject(STATE_NAMES.todayWorkedMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.todayBalanceMinutes`,\n\t\tstateObject(STATE_NAMES.todayBalanceMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.monthWorkedMinutes`,\n\t\tstateObject(STATE_NAMES.monthWorkedMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.monthBalanceMinutes`,\n\t\tstateObject(STATE_NAMES.monthBalanceMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.yearBalanceMinutes`,\n\t\tstateObject(STATE_NAMES.yearBalanceMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(port, `${id}.openConflicts`, stateObject(STATE_NAMES.openConflicts, \"number\", \"value\"));\n\treturn id;\n}\n\n/**\n * Creates the writable command states.\n *\n * @param port - state port\n */\nexport async function createCommandStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"commands\", channelObject(STATE_NAMES.commandsChannel));\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.punchUserId,\n\t\tstateObject(STATE_NAMES.punchUserId, \"number\", \"level\", {\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.punch,\n\t\tstateObject(STATE_NAMES.punch, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.quickPunch,\n\t\tstateObject(STATE_NAMES.quickPunch, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.closeMonth,\n\t\tstateObject(STATE_NAMES.closeMonth, \"string\", \"text\", {\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(port, COMMAND_IDS.recalc, stateObject(STATE_NAMES.recalc, \"string\", \"text\", { write: true }));\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.backup,\n\t\tstateObject(STATE_NAMES.backupCommand, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n}\n\n/**\n * Creates the informational states of the instance.\n *\n * @param port - state port\n */\nexport async function createInfoStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"info\", channelObject(STATE_NAMES.infoChannel));\n\tawait ensureObject(port, \"info.version\", stateObject(STATE_NAMES.version, \"string\", \"text\"));\n\tawait ensureObject(port, \"info.schemaVersion\", stateObject(STATE_NAMES.schemaVersion, \"string\", \"text\"));\n\tawait ensureObject(\n\t\tport,\n\t\t\"info.dbSizeBytes\",\n\t\tstateObject(STATE_NAMES.dbSizeBytes, \"number\", \"value\", { unit: \"bytes\" }),\n\t);\n\tawait ensureObject(port, \"info.lastError\", stateObject(STATE_NAMES.lastError, \"string\", \"text\"));\n\tawait ensureObject(port, \"info.lastBackup\", stateObject(STATE_NAMES.lastBackup, \"number\", \"value.time\"));\n}\n\n/**\n * Creates the states of the company.\n *\n * @param port - state port\n */\nexport async function createCompanyStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"company\", channelObject(STATE_NAMES.companyChannel));\n\tawait ensureObject(port, \"company.presentCount\", stateObject(STATE_NAMES.companyPresentCount, \"number\", \"value\"));\n\tawait ensureObject(port, \"company.present\", stateObject(STATE_NAMES.companyPresent, \"string\", \"text\"));\n\tawait ensureObject(port, \"company.openConflicts\", stateObject(STATE_NAMES.openConflicts, \"number\", \"value\"));\n\tawait ensureObject(port, \"company.lastPunch\", stateObject(STATE_NAMES.companyLastPunch, \"number\", \"value.time\"));\n}\n\n/**\n * Reads the company figures out of the figures of the employees.\n *\n * @param snapshots - figures of all employees\n * @returns the company figures\n */\nexport function readCompanySnapshot(snapshots: UserSnapshot[]): CompanySnapshot {\n\tconst present = snapshots.filter(snapshot => snapshot.hasOpenEntry);\n\treturn {\n\t\tpresentCount: present.length,\n\t\tpresent: present.map(snapshot => snapshot.displayName).join(\", \"),\n\t\topenConflicts: snapshots.reduce((sum, snapshot) => sum + snapshot.openConflicts, 0),\n\t\tlastPunchUtc: snapshots.reduce<number | null>(\n\t\t\t(newest, snapshot) =>\n\t\t\t\tsnapshot.lastPunchUtc !== null && (newest === null || snapshot.lastPunchUtc > newest)\n\t\t\t\t\t? snapshot.lastPunchUtc\n\t\t\t\t\t: newest,\n\t\t\tnull,\n\t\t),\n\t};\n}\n\n/**\n * Publishes the company figures.\n *\n * @param port - state port\n * @param snapshot - figures to publish\n */\nexport async function publishCompanySnapshot(port: StatePort, snapshot: CompanySnapshot): Promise<void> {\n\tawait port.setState(\"company.presentCount\", snapshot.presentCount, true);\n\tawait port.setState(\"company.present\", snapshot.present, true);\n\tawait port.setState(\"company.openConflicts\", snapshot.openConflicts, true);\n\tawait port.setState(\"company.lastPunch\", snapshot.lastPunchUtc ?? 0, true);\n}\n\n/**\n * Creates the event states.\n *\n * @param port - state port\n */\nexport async function createEventStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"events\", channelObject(STATE_NAMES.eventsChannel));\n\tawait ensureObject(port, \"events.lastAt\", stateObject(STATE_NAMES.lastAt, \"number\", \"value.time\"));\n\tawait ensureObject(port, \"events.lastType\", stateObject(STATE_NAMES.lastType, \"string\", \"text\"));\n\tawait ensureObject(port, \"events.lastUser\", stateObject(STATE_NAMES.lastUser, \"string\", \"text\"));\n\tawait ensureObject(port, \"events.lastDirection\", stateObject(STATE_NAMES.lastDirection, \"string\", \"text\"));\n\tawait ensureObject(port, \"events.lastSource\", stateObject(STATE_NAMES.lastSource, \"string\", \"text\"));\n}\n\n/**\n * Publishes the newest event.\n *\n * @param port - state port\n * @param snapshot - event to publish\n */\nexport async function publishEventSnapshot(port: StatePort, snapshot: EventSnapshot): Promise<void> {\n\tawait port.setState(\"events.lastAt\", snapshot.atUtc, true);\n\tawait port.setState(\"events.lastType\", snapshot.type, true);\n\tawait port.setState(\"events.lastUser\", snapshot.userName, true);\n\tawait port.setState(\"events.lastDirection\", snapshot.direction, true);\n\tawait port.setState(\"events.lastSource\", snapshot.source, true);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["resolveLocalDate"]
7
7
  }