@kernhq/module-hr 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/contract/capabilities.d.ts.map +1 -1
  2. package/dist/contract/capabilities.js +31 -0
  3. package/dist/contract/capabilities.js.map +1 -1
  4. package/dist/contract/index.d.ts +1 -0
  5. package/dist/contract/index.d.ts.map +1 -1
  6. package/dist/contract/index.js +1 -0
  7. package/dist/contract/index.js.map +1 -1
  8. package/dist/contract/permissions.d.ts +23 -0
  9. package/dist/contract/permissions.d.ts.map +1 -1
  10. package/dist/contract/permissions.js +27 -0
  11. package/dist/contract/permissions.js.map +1 -1
  12. package/dist/contract/policies.d.ts +256 -0
  13. package/dist/contract/policies.d.ts.map +1 -0
  14. package/dist/contract/policies.js +175 -0
  15. package/dist/contract/policies.js.map +1 -0
  16. package/dist/contract/router.d.ts +707 -0
  17. package/dist/contract/router.d.ts.map +1 -1
  18. package/dist/contract/router.js +127 -0
  19. package/dist/contract/router.js.map +1 -1
  20. package/dist/policy/accrual.d.ts +127 -0
  21. package/dist/policy/accrual.d.ts.map +1 -0
  22. package/dist/policy/accrual.js +134 -0
  23. package/dist/policy/accrual.js.map +1 -0
  24. package/dist/server/jobs.d.ts.map +1 -1
  25. package/dist/server/jobs.js +186 -1
  26. package/dist/server/jobs.js.map +1 -1
  27. package/dist/server/router.d.ts +772 -0
  28. package/dist/server/router.d.ts.map +1 -1
  29. package/dist/server/router.js +462 -1
  30. package/dist/server/router.js.map +1 -1
  31. package/dist/server/schema.d.ts +612 -1
  32. package/dist/server/schema.d.ts.map +1 -1
  33. package/dist/server/schema.js +76 -0
  34. package/dist/server/schema.js.map +1 -1
  35. package/dist/server/services/policies.d.ts +53 -0
  36. package/dist/server/services/policies.d.ts.map +1 -0
  37. package/dist/server/services/policies.js +158 -0
  38. package/dist/server/services/policies.js.map +1 -0
  39. package/migrations/meta/_journal.json +7 -0
  40. package/package.json +1 -1
  41. package/src/contract/capabilities.ts +31 -0
  42. package/src/contract/index.ts +1 -0
  43. package/src/contract/permissions.ts +28 -0
  44. package/src/contract/router.ts +148 -0
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,cAAc,EAMpB,MAAM,gBAAgB,CAAA;AA8CvB;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuvGxC"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,cAAc,EAMpB,MAAM,gBAAgB,CAAA;AA2DvB;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmxHxC"}
@@ -2,15 +2,18 @@ import { KernError, requires, requiresCapability, uuidv7, workspaceScoped, } fro
2
2
  import { implement } from '@orpc/server';
3
3
  import { and, asc, count, desc, eq, gte, ilike, inArray, isNull, lte, or, sql } from 'drizzle-orm';
4
4
  import { HrSettings, hrContract, hrEvents, MODULE_ID } from '../contract/index.js';
5
+ import { AccrualConfig, CarryForwardConfig, OvertimeConfig, RoundingConfig, WorkingTimeConfig, } from '../contract/policies.js';
6
+ import { accrueForPeriod } from '../policy/accrual.js';
5
7
  import { countWorkingDays, workingDays } from '../policy/calendar.js';
6
8
  import { businessDateFor } from '../policy/working-time.js';
7
9
  import { COUNTRY_PACKS, packDays } from './packs/index.js';
8
- import { approvalChains, approvalDecisions, approvalRequests, approvalSteps, attendanceDays, calendarDays, calendars, costCenters, customFieldDefs, delegations, employments, leaveLedger, leaveRequestDays, leaveRequests, leaveTypes, legalEntities, officeAssignments, offices, orgUnits, people, peopleSensitive, personDocuments, personHistory, positions, punches, regularizations, scheduleAssignments, schedules, } from './schema.js';
10
+ import { approvalChains, approvalDecisions, approvalRequests, approvalSteps, attendanceDays, calendarDays, calendars, costCenters, customFieldDefs, delegations, employments, leaveLedger, leaveRequestDays, leaveRequests, leaveTypes, legalEntities, officeAssignments, offices, orgUnits, people, peopleSensitive, periods, personDocuments, personHistory, policies, policyAssignments, positions, punches, regularizations, scheduleAssignments, schedules, } from './schema.js';
9
11
  import { ApprovalService } from './services/approvals.js';
10
12
  import { AttendanceService } from './services/attendance.js';
11
13
  import { inForceOn, todayIn, todayIso } from './services/db.js';
12
14
  import { LedgerService, MINUTES_PER_DAY, yearOf } from './services/ledger.js';
13
15
  import { PeopleService } from './services/people.js';
16
+ import { hashConfig, PolicyService } from './services/policies.js';
14
17
  import { DEFAULT_WORKING_WEEK, ResolveService } from './services/resolve.js';
15
18
  const os = implement(hrContract).$context();
16
19
  /**
@@ -33,6 +36,7 @@ export function implement_(kernel) {
33
36
  const ledger = new LedgerService();
34
37
  const approvals = new ApprovalService(kernel);
35
38
  const attendance = new AttendanceService();
39
+ const policySvc = new PolicyService(resolve);
36
40
  const db = kernel.database;
37
41
  const settingsOf = (workspaceId) => kernel.settings.module(workspaceId, MODULE_ID, HrSettings);
38
42
  const changed = (workspaceId, entity, id, op) => kernel.realtime.change(workspaceId, { module: MODULE_ID, entity, id, op });
@@ -1252,6 +1256,292 @@ export function implement_(kernel) {
1252
1256
  return { ok: true };
1253
1257
  }),
1254
1258
  },
1259
+ // ================================================================= policies
1260
+ policies: {
1261
+ list: scoped.policies.list
1262
+ .use(cap('leave_accrual'))
1263
+ .use(requires('hr.policy.view'))
1264
+ .handler(({ input }) => db.withWorkspace(input.workspaceId, async (tx) => {
1265
+ const where = [eq(policies.workspaceId, input.workspaceId)];
1266
+ if (input.kind)
1267
+ where.push(eq(policies.kind, input.kind));
1268
+ if (!input.includeArchived)
1269
+ where.push(isNull(policies.archivedAt));
1270
+ const rows = await tx
1271
+ .select()
1272
+ .from(policies)
1273
+ .where(and(...where))
1274
+ .orderBy(asc(policies.kind), asc(policies.name));
1275
+ return withAssignments(tx, input.workspaceId, rows);
1276
+ })),
1277
+ get: scoped.policies.get
1278
+ .use(cap('leave_accrual'))
1279
+ .use(requires('hr.policy.view'))
1280
+ .handler(({ input }) => db.withWorkspace(input.workspaceId, async (tx) => {
1281
+ const row = await loadPolicy(tx, input.workspaceId, input.policyId);
1282
+ return (await withAssignments(tx, input.workspaceId, [row]))[0];
1283
+ })),
1284
+ create: scoped.policies.create
1285
+ .use(cap('leave_accrual'))
1286
+ .use(requires('hr.policy.manage'))
1287
+ .handler(async ({ input }) => {
1288
+ const config = validatePolicyConfig(input.kind, input.config);
1289
+ const row = await db.withWorkspace(input.workspaceId, async (tx) => {
1290
+ const [created] = await tx
1291
+ .insert(policies)
1292
+ .values({
1293
+ id: uuidv7(),
1294
+ workspaceId: input.workspaceId,
1295
+ kind: input.kind,
1296
+ name: input.name,
1297
+ config,
1298
+ effectiveFrom: input.effectiveFrom,
1299
+ effectiveTo: input.effectiveTo ?? null,
1300
+ source: 'custom',
1301
+ configHash: hashConfig(config),
1302
+ })
1303
+ .returning();
1304
+ return created;
1305
+ });
1306
+ await changed(input.workspaceId, 'policy', row.id, 'created');
1307
+ return toPolicy(row);
1308
+ }),
1309
+ update: scoped.policies.update
1310
+ .use(cap('leave_accrual'))
1311
+ .use(requires('hr.policy.manage'))
1312
+ .handler(async ({ input }) => {
1313
+ const row = await db.withWorkspace(input.workspaceId, async (tx) => {
1314
+ const existing = await loadPolicy(tx, input.workspaceId, input.policyId);
1315
+ const set = { updatedAt: new Date() };
1316
+ if (input.name !== undefined)
1317
+ set.name = input.name;
1318
+ if (input.effectiveTo !== undefined)
1319
+ set.effectiveTo = input.effectiveTo;
1320
+ if (input.config !== undefined) {
1321
+ const config = validatePolicyConfig(existing.kind, input.config);
1322
+ set.config = config;
1323
+ // The hash changes with the config, which is what marks every row derived from the
1324
+ // old one as stale rather than leaving them silently wrong.
1325
+ set.configHash = hashConfig(config);
1326
+ }
1327
+ const [updated] = await tx
1328
+ .update(policies)
1329
+ .set(set)
1330
+ .where(and(eq(policies.workspaceId, input.workspaceId), eq(policies.id, input.policyId)))
1331
+ .returning();
1332
+ return updated;
1333
+ });
1334
+ await changed(input.workspaceId, 'policy', row.id, 'updated');
1335
+ return toPolicy(row);
1336
+ }),
1337
+ archive: scoped.policies.archive
1338
+ .use(cap('leave_accrual'))
1339
+ .use(requires('hr.policy.manage'))
1340
+ .handler(async ({ input }) => {
1341
+ // Archived, never deleted: ledger entries name the policy that produced them, and a
1342
+ // movement whose policy has vanished is a number nobody can explain.
1343
+ await db.withWorkspace(input.workspaceId, (tx) => tx
1344
+ .update(policies)
1345
+ .set({ archivedAt: new Date() })
1346
+ .where(and(eq(policies.workspaceId, input.workspaceId), eq(policies.id, input.policyId))));
1347
+ await changed(input.workspaceId, 'policy', input.policyId, 'deleted');
1348
+ return { ok: true };
1349
+ }),
1350
+ assign: scoped.policies.assign
1351
+ .use(cap('leave_accrual'))
1352
+ .use(requires('hr.policy.manage'))
1353
+ .handler(async ({ input }) => {
1354
+ const row = await db.withWorkspace(input.workspaceId, async (tx) => {
1355
+ await loadPolicy(tx, input.workspaceId, input.policyId);
1356
+ if (input.subjectKind !== 'workspace' && !input.subjectId)
1357
+ throw KernError.badRequest(`A ${input.subjectKind} assignment needs a subject.`);
1358
+ const [created] = await tx
1359
+ .insert(policyAssignments)
1360
+ .values({
1361
+ id: uuidv7(),
1362
+ workspaceId: input.workspaceId,
1363
+ policyId: input.policyId,
1364
+ subjectKind: input.subjectKind,
1365
+ subjectId: input.subjectKind === 'workspace' ? null : (input.subjectId ?? null),
1366
+ effectiveFrom: input.effectiveFrom,
1367
+ effectiveTo: input.effectiveTo ?? null,
1368
+ // The ladder, written once. Nothing else invents its own order.
1369
+ priority: PolicyService.priorityFor(input.subjectKind),
1370
+ })
1371
+ .returning();
1372
+ return created;
1373
+ });
1374
+ await changed(input.workspaceId, 'policy', input.policyId, 'updated');
1375
+ return toAssignment(row);
1376
+ }),
1377
+ unassign: scoped.policies.unassign
1378
+ .use(cap('leave_accrual'))
1379
+ .use(requires('hr.policy.manage'))
1380
+ .handler(async ({ input }) => {
1381
+ await db.withWorkspace(input.workspaceId, (tx) => tx
1382
+ .delete(policyAssignments)
1383
+ .where(and(eq(policyAssignments.workspaceId, input.workspaceId), eq(policyAssignments.id, input.assignmentId))));
1384
+ await changed(input.workspaceId, 'policy', input.assignmentId, 'deleted');
1385
+ return { ok: true };
1386
+ }),
1387
+ resolveFor: scoped.policies.resolveFor
1388
+ .use(cap('leave_accrual'))
1389
+ .use(requires('hr.policy.view'))
1390
+ .handler(({ input }) => db.withWorkspace(input.workspaceId, async (tx) => {
1391
+ const on = input.on ?? todayIso();
1392
+ const kinds = ['accrual', 'carry_forward', 'overtime', 'rounding', 'working_time'];
1393
+ const out = [];
1394
+ for (const kind of kinds)
1395
+ out.push(await policySvc.forPerson(tx, input.workspaceId, input.personId, kind, on));
1396
+ return out;
1397
+ })),
1398
+ },
1399
+ // ================================================================= accrual
1400
+ accrual: {
1401
+ preview: scoped.accrual.preview
1402
+ .use(cap('leave_accrual'))
1403
+ .use(requires('hr.policy.view'))
1404
+ .handler(({ input }) => db.withWorkspace(input.workspaceId, (tx) => accrualPreview(tx, input.workspaceId, input.from, input.to, input.personId))),
1405
+ run: scoped.accrual.run
1406
+ .use(cap('leave_accrual'))
1407
+ .use(requires('hr.policy.manage'))
1408
+ .handler(async ({ input, context }) => {
1409
+ const result = await db.withWorkspace(input.workspaceId, async (tx) => {
1410
+ const preview = await accrualPreview(tx, input.workspaceId, input.from, input.to, input.personId);
1411
+ let credited = 0;
1412
+ let totalMinutes = 0;
1413
+ for (const row of preview.rows) {
1414
+ // Idempotent per person, per type, per period: a job that double-credits when
1415
+ // somebody clicks twice is worse than one that never ran.
1416
+ if (row.alreadyAccrued || row.minutes <= 0)
1417
+ continue;
1418
+ const year = yearOf(input.from);
1419
+ await ledger.lockAndRead(tx, input.workspaceId, row.personId, row.leaveTypeId, year);
1420
+ await ledger.append(tx, input.workspaceId, {
1421
+ personId: row.personId,
1422
+ leaveTypeId: row.leaveTypeId,
1423
+ kind: 'accrual',
1424
+ amountMinutes: row.minutes,
1425
+ effectiveOn: input.to,
1426
+ periodYear: year,
1427
+ reason: row.reason,
1428
+ createdBy: context.principal.userId ?? null,
1429
+ });
1430
+ credited++;
1431
+ totalMinutes += row.minutes;
1432
+ }
1433
+ return { credited, skipped: preview.rows.length - credited, totalMinutes };
1434
+ });
1435
+ await changed(input.workspaceId, 'leave_balance', input.workspaceId, 'updated');
1436
+ return result;
1437
+ }),
1438
+ },
1439
+ // ================================================================= periods
1440
+ periods: {
1441
+ list: scoped.periods.list
1442
+ .use(cap('periods'))
1443
+ .use(requires('hr.period.manage'))
1444
+ .handler(({ input }) => db.withWorkspace(input.workspaceId, async (tx) => {
1445
+ const where = [eq(periods.workspaceId, input.workspaceId)];
1446
+ if (input.kind)
1447
+ where.push(eq(periods.kind, input.kind));
1448
+ const rows = await tx
1449
+ .select()
1450
+ .from(periods)
1451
+ .where(and(...where))
1452
+ .orderBy(desc(periods.startsOn))
1453
+ .limit(input.limit);
1454
+ return { items: rows.map(toPeriod), nextCursor: null };
1455
+ })),
1456
+ create: scoped.periods.create
1457
+ .use(cap('periods'))
1458
+ .use(requires('hr.period.manage'))
1459
+ .handler(async ({ input }) => {
1460
+ const row = await db.withWorkspace(input.workspaceId, async (tx) => {
1461
+ if (input.endsOn < input.startsOn)
1462
+ throw KernError.badRequest('A period cannot end before it starts.');
1463
+ const [created] = await tx
1464
+ .insert(periods)
1465
+ .values({
1466
+ id: uuidv7(),
1467
+ workspaceId: input.workspaceId,
1468
+ kind: input.kind,
1469
+ legalEntityId: input.legalEntityId ?? null,
1470
+ startsOn: input.startsOn,
1471
+ endsOn: input.endsOn,
1472
+ status: 'open',
1473
+ })
1474
+ .returning();
1475
+ return created;
1476
+ });
1477
+ await changed(input.workspaceId, 'period', row.id, 'created');
1478
+ return toPeriod(row);
1479
+ }),
1480
+ lock: scoped.periods.lock
1481
+ .use(cap('periods'))
1482
+ .use(requires('hr.period.manage'))
1483
+ .handler(async ({ input, context }) => {
1484
+ const result = await db.withWorkspace(input.workspaceId, async (tx) => {
1485
+ const period = await loadPeriod(tx, input.workspaceId, input.periodId);
1486
+ if (period.status === 'locked')
1487
+ throw KernError.conflict('That period is already locked.');
1488
+ const [row] = await tx
1489
+ .update(periods)
1490
+ .set({
1491
+ status: 'locked',
1492
+ lockedAt: new Date(),
1493
+ lockedBy: context.principal.userId ?? null,
1494
+ note: input.note ?? null,
1495
+ })
1496
+ .where(eq(periods.id, period.id))
1497
+ .returning();
1498
+ // The day sheet carries its own flag so a recomputation does not have to join periods
1499
+ // on every row — this is what actually stops a closed month moving.
1500
+ const locked = await tx
1501
+ .update(attendanceDays)
1502
+ .set({ locked: true })
1503
+ .where(and(eq(attendanceDays.workspaceId, input.workspaceId), gte(attendanceDays.businessDate, period.startsOn), lte(attendanceDays.businessDate, period.endsOn)))
1504
+ .returning({ id: attendanceDays.id });
1505
+ return { period: row, lockedDays: locked.length };
1506
+ });
1507
+ await changed(input.workspaceId, 'period', input.periodId, 'updated');
1508
+ return { ...toPeriod(result.period), lockedDays: result.lockedDays };
1509
+ }),
1510
+ unlock: scoped.periods.unlock
1511
+ .use(cap('periods'))
1512
+ .use(requires('hr.period.manage'))
1513
+ .handler(async ({ input, context }) => {
1514
+ const result = await db.withWorkspace(input.workspaceId, async (tx) => {
1515
+ const period = await loadPeriod(tx, input.workspaceId, input.periodId);
1516
+ const [row] = await tx
1517
+ .update(periods)
1518
+ .set({
1519
+ status: 'open',
1520
+ lockedAt: null,
1521
+ lockedBy: null,
1522
+ note: `Reopened: ${input.reason}`,
1523
+ })
1524
+ .where(eq(periods.id, period.id))
1525
+ .returning();
1526
+ const unlocked = await tx
1527
+ .update(attendanceDays)
1528
+ .set({ locked: false })
1529
+ .where(and(eq(attendanceDays.workspaceId, input.workspaceId), gte(attendanceDays.businessDate, period.startsOn), lte(attendanceDays.businessDate, period.endsOn)))
1530
+ .returning({ id: attendanceDays.id });
1531
+ kernel.log.warn({
1532
+ module: MODULE_ID,
1533
+ workspaceId: input.workspaceId,
1534
+ periodId: period.id,
1535
+ days: unlocked.length,
1536
+ actorId: context.principal.userId,
1537
+ reason: input.reason,
1538
+ }, 'payroll period reopened');
1539
+ return { period: row, unlockedDays: unlocked.length };
1540
+ });
1541
+ await changed(input.workspaceId, 'period', input.periodId, 'updated');
1542
+ return { ...toPeriod(result.period), unlockedDays: result.unlockedDays };
1543
+ }),
1544
+ },
1255
1545
  // ================================================================= attendance
1256
1546
  attendance: {
1257
1547
  state: scoped.attendance.state
@@ -2755,6 +3045,160 @@ export function implement_(kernel) {
2755
3045
  .set({ status: 'approved', appliedAt: new Date() })
2756
3046
  .where(eq(regularizations.id, row.id));
2757
3047
  }
3048
+ async function loadPolicy(tx, workspaceId, policyId) {
3049
+ const [row] = await tx
3050
+ .select()
3051
+ .from(policies)
3052
+ .where(and(eq(policies.workspaceId, workspaceId), eq(policies.id, policyId)))
3053
+ .limit(1);
3054
+ if (!row)
3055
+ throw KernError.notFound('Policy');
3056
+ return row;
3057
+ }
3058
+ async function loadPeriod(tx, workspaceId, periodId) {
3059
+ const [row] = await tx
3060
+ .select()
3061
+ .from(periods)
3062
+ .where(and(eq(periods.workspaceId, workspaceId), eq(periods.id, periodId)))
3063
+ .limit(1);
3064
+ if (!row)
3065
+ throw KernError.notFound('Period');
3066
+ return row;
3067
+ }
3068
+ /** Policies with the assignments that decide who they reach — one query for the whole list. */
3069
+ async function withAssignments(tx, workspaceId, rows) {
3070
+ if (!rows.length)
3071
+ return [];
3072
+ const assignments = await tx
3073
+ .select()
3074
+ .from(policyAssignments)
3075
+ .where(and(eq(policyAssignments.workspaceId, workspaceId), inArray(policyAssignments.policyId, rows.map((r) => r.id))));
3076
+ return rows.map((r) => ({
3077
+ ...toPolicy(r),
3078
+ assignments: assignments.filter((a) => a.policyId === r.id).map(toAssignment),
3079
+ }));
3080
+ }
3081
+ /**
3082
+ * What an accrual run would credit, per person.
3083
+ *
3084
+ * **`run` calls this and credits what it returns**, so the preview and the thing it previews are
3085
+ * the same computation. A preview written separately is a preview that eventually disagrees with
3086
+ * the number that lands in somebody's balance.
3087
+ *
3088
+ * Everybody active is considered; the reason a person was skipped is returned rather than the
3089
+ * person being silently absent, because "why did she not accrue" is the question that follows.
3090
+ */
3091
+ async function accrualPreview(tx, workspaceId, from, to, onlyPersonId) {
3092
+ const staff = await tx
3093
+ .select()
3094
+ .from(people)
3095
+ .where(and(eq(people.workspaceId, workspaceId), onlyPersonId ? eq(people.id, onlyPersonId) : inArray(people.status, ['active', 'on_leave'])));
3096
+ const rows = [];
3097
+ const skipped = [];
3098
+ if (!staff.length)
3099
+ return { periodFrom: from, periodTo: to, rows, totalMinutes: 0, skipped };
3100
+ const ids = staff.map((p) => p.id);
3101
+ const resolved = await policySvc.forPeople(tx, workspaceId, ids, 'accrual', to);
3102
+ const employmentRows = await tx
3103
+ .select()
3104
+ .from(employments)
3105
+ .where(and(eq(employments.workspaceId, workspaceId), inArray(employments.personId, ids), inForceOn(employments.effectiveFrom, employments.effectiveTo, to)));
3106
+ const employmentBy = new Map(employmentRows.map((e) => [e.personId, e]));
3107
+ const types = await tx
3108
+ .select()
3109
+ .from(leaveTypes)
3110
+ .where(and(eq(leaveTypes.workspaceId, workspaceId), isNull(leaveTypes.archivedAt)));
3111
+ const typeByKey = new Map(types.map((t) => [t.key, t]));
3112
+ // Everything already credited for this window, so a re-run credits nothing.
3113
+ const existing = await tx
3114
+ .select({ personId: leaveLedger.personId, leaveTypeId: leaveLedger.leaveTypeId })
3115
+ .from(leaveLedger)
3116
+ .where(and(eq(leaveLedger.workspaceId, workspaceId), eq(leaveLedger.kind, 'accrual'), eq(leaveLedger.effectiveOn, to), inArray(leaveLedger.personId, ids)));
3117
+ const already = new Set(existing.map((e) => `${e.personId}:${e.leaveTypeId}`));
3118
+ let totalMinutes = 0;
3119
+ for (const person of staff) {
3120
+ const policy = resolved.get(person.id);
3121
+ if (!policy?.config) {
3122
+ skipped.push({
3123
+ personId: person.id,
3124
+ displayName: person.displayName,
3125
+ reason: 'no accrual policy applies',
3126
+ });
3127
+ continue;
3128
+ }
3129
+ const config = policy.config;
3130
+ const type = typeByKey.get(config.leaveTypeKey);
3131
+ if (!type) {
3132
+ skipped.push({
3133
+ personId: person.id,
3134
+ displayName: person.displayName,
3135
+ reason: `no leave type "${config.leaveTypeKey}"`,
3136
+ });
3137
+ continue;
3138
+ }
3139
+ if (!person.hiredOn) {
3140
+ skipped.push({
3141
+ personId: person.id,
3142
+ displayName: person.displayName,
3143
+ reason: 'no hire date',
3144
+ });
3145
+ continue;
3146
+ }
3147
+ const employment = employmentBy.get(person.id);
3148
+ const result = accrueForPeriod({
3149
+ policy: {
3150
+ frequency: config.frequency,
3151
+ daysPerYear: config.daysPerYear,
3152
+ minutesPerDay: config.minutesPerDay,
3153
+ seniorityTiers: config.seniorityTiers,
3154
+ waitingPeriodMonths: config.waitingPeriodMonths,
3155
+ roundToMinutes: config.roundToMinutes,
3156
+ },
3157
+ period: { from, to },
3158
+ hiredOn: person.hiredOn,
3159
+ terminatedOn: person.terminatedOn,
3160
+ fte: employment ? Number.parseFloat(employment.fte ?? '1') : 1,
3161
+ });
3162
+ const alreadyAccrued = already.has(`${person.id}:${type.id}`);
3163
+ rows.push({
3164
+ personId: person.id,
3165
+ displayName: person.displayName,
3166
+ leaveTypeId: type.id,
3167
+ leaveTypeName: type.name,
3168
+ minutes: result.minutes,
3169
+ days: Math.round((result.minutes / config.minutesPerDay) * 100) / 100,
3170
+ reason: result.reason,
3171
+ alreadyAccrued,
3172
+ });
3173
+ if (!alreadyAccrued)
3174
+ totalMinutes += result.minutes;
3175
+ }
3176
+ return { periodFrom: from, periodTo: to, rows, totalMinutes, skipped };
3177
+ }
3178
+ /**
3179
+ * Validate a config against the schema for its kind.
3180
+ *
3181
+ * A policy is data, and data from an API is not trusted because it arrived in the right-shaped
3182
+ * field. Each kind validates its own shape, which is what makes "policies as data" safe rather
3183
+ * than a jsonb column nobody checks.
3184
+ */
3185
+ function validatePolicyConfig(kind, config) {
3186
+ const schema = kind === 'accrual'
3187
+ ? AccrualConfig
3188
+ : kind === 'carry_forward'
3189
+ ? CarryForwardConfig
3190
+ : kind === 'overtime'
3191
+ ? OvertimeConfig
3192
+ : kind === 'rounding'
3193
+ ? RoundingConfig
3194
+ : WorkingTimeConfig;
3195
+ const parsed = schema.safeParse(config);
3196
+ if (!parsed.success)
3197
+ throw KernError.badRequest(`Invalid ${kind} policy`, {
3198
+ issues: parsed.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`),
3199
+ });
3200
+ return parsed.data;
3201
+ }
2758
3202
  async function emitCalendarChanged(workspaceId, calendarId, from, to, actorId) {
2759
3203
  await kernel.emit(hrEvents.calendarChanged, { calendarId, workspaceId, from, to }, { workspaceId, actorId });
2760
3204
  await changed(workspaceId, 'calendar', calendarId, 'updated');
@@ -2862,4 +3306,21 @@ const toRegularization = (r) => ({
2862
3306
  appliedAt: r.appliedAt?.toISOString() ?? null,
2863
3307
  createdAt: r.createdAt.toISOString(),
2864
3308
  });
3309
+ const toPolicy = (r) => ({
3310
+ ...r,
3311
+ kind: r.kind,
3312
+ source: r.source,
3313
+ config: r.config ?? {},
3314
+ archivedAt: r.archivedAt?.toISOString() ?? null,
3315
+ });
3316
+ const toAssignment = (r) => ({
3317
+ ...r,
3318
+ subjectKind: r.subjectKind,
3319
+ });
3320
+ const toPeriod = (r) => ({
3321
+ ...r,
3322
+ kind: r.kind,
3323
+ status: r.status,
3324
+ lockedAt: r.lockedAt?.toISOString() ?? null,
3325
+ });
2865
3326
  //# sourceMappingURL=router.js.map