@kernhq/module-hr 0.10.0 → 0.10.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.
- package/dist/contract/attendance.d.ts +1 -0
- package/dist/contract/attendance.d.ts.map +1 -1
- package/dist/contract/attendance.js +9 -0
- package/dist/contract/attendance.js.map +1 -1
- package/dist/contract/capabilities.d.ts +16 -0
- package/dist/contract/capabilities.d.ts.map +1 -1
- package/dist/contract/capabilities.js +16 -0
- package/dist/contract/capabilities.js.map +1 -1
- package/dist/contract/router.d.ts +1 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/policy/accrual.d.ts +35 -4
- package/dist/policy/accrual.d.ts.map +1 -1
- package/dist/policy/accrual.js +141 -15
- package/dist/policy/accrual.js.map +1 -1
- package/dist/policy/working-time.d.ts +115 -9
- package/dist/policy/working-time.d.ts.map +1 -1
- package/dist/policy/working-time.js +120 -20
- package/dist/policy/working-time.js.map +1 -1
- package/dist/server/jobs.d.ts +14 -6
- package/dist/server/jobs.d.ts.map +1 -1
- package/dist/server/jobs.js +489 -144
- package/dist/server/jobs.js.map +1 -1
- package/dist/server/packs/index.d.ts +14 -0
- package/dist/server/packs/index.d.ts.map +1 -1
- package/dist/server/packs/index.js +19 -0
- package/dist/server/packs/index.js.map +1 -1
- package/dist/server/router.d.ts +1888 -111
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +154 -63
- package/dist/server/router.js.map +1 -1
- package/dist/server/schema.d.ts +17 -0
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +81 -2
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/approvals.d.ts +0 -2
- package/dist/server/services/approvals.d.ts.map +1 -1
- package/dist/server/services/approvals.js +12 -4
- package/dist/server/services/approvals.js.map +1 -1
- package/dist/server/services/attendance.d.ts +120 -8
- package/dist/server/services/attendance.d.ts.map +1 -1
- package/dist/server/services/attendance.js +226 -14
- package/dist/server/services/attendance.js.map +1 -1
- package/dist/server/services/ledger.d.ts +0 -2
- package/dist/server/services/ledger.d.ts.map +1 -1
- package/dist/server/services/ledger.js +1 -2
- package/dist/server/services/ledger.js.map +1 -1
- package/dist/server/services/policies.d.ts +6 -2
- package/dist/server/services/policies.d.ts.map +1 -1
- package/dist/server/services/policies.js +7 -2
- package/dist/server/services/policies.js.map +1 -1
- package/migrations/0005_approval_requester.sql +11 -1
- package/migrations/0006_schedule_no_overlap.sql +85 -0
- package/migrations/0007_hot_path_indexes.sql +38 -0
- package/migrations/0009_beyond_cap_minutes.sql +15 -0
- package/migrations/meta/0005_snapshot.json +4047 -0
- package/migrations/meta/0007_snapshot.json +4225 -0
- package/migrations/meta/0009_snapshot.json +4231 -0
- package/migrations/meta/_journal.json +21 -0
- package/package.json +7 -7
- package/src/client/components/ClockControls.svelte +127 -20
- package/src/client/components/ClockControls.test.ts +446 -0
- package/src/client/components/DelegationDialog.svelte +12 -3
- package/src/client/components/LeaveRequestDialog.svelte +254 -25
- package/src/client/components/PersonFormDialog.svelte +53 -3
- package/src/client/components/PersonPanel.svelte +134 -26
- package/src/client/i18n.ts +5 -927
- package/src/client/messages.test.ts +154 -0
- package/src/client/messages.ts +3388 -0
- package/src/client/mock.ts +1576 -161
- package/src/client/module.ts +15 -1
- package/src/client/pages/ApprovalsPage.svelte +120 -46
- package/src/client/pages/AttendancePage.svelte +106 -20
- package/src/client/pages/DirectoryPage.svelte +192 -55
- package/src/client/pages/LeavePage.svelte +280 -25
- package/src/client/pages/OfficesPage.svelte +26 -8
- package/src/client/pages/leave-and-attendance.test.ts +588 -0
- package/src/client/query.ts +12 -0
- package/src/client/settings/CalendarsSettings.svelte +1363 -12
- package/src/client/settings/CapabilitiesSettings.svelte +276 -19
- package/src/client/settings/GeneralSettings.svelte +420 -0
- package/src/client/settings/LeaveSettings.svelte +884 -12
- package/src/client/settings/OfficesSettings.svelte +1182 -12
- package/src/client/settings/SchedulesSettings.svelte +1149 -12
- package/src/client/summary.ts +14 -7
- package/src/client/widgets/ApprovalsWidget.svelte +133 -20
- package/src/client/widgets/HeadcountWidget.svelte +74 -8
- package/src/client/widgets/LeaveBalanceWidget.svelte +40 -5
- package/src/client/widgets/WhosOutWidget.svelte +47 -8
- package/src/contract/attendance.ts +9 -0
- package/src/contract/capabilities.ts +17 -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;
|
|
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;AA0DvB;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAw4HxC"}
|
package/dist/server/router.js
CHANGED
|
@@ -5,12 +5,11 @@ import { HrSettings, hrContract, hrEvents, MODULE_ID } from '../contract/index.j
|
|
|
5
5
|
import { AccrualConfig, CarryForwardConfig, OvertimeConfig, RoundingConfig, WorkingTimeConfig, } from '../contract/policies.js';
|
|
6
6
|
import { accrueForPeriod } from '../policy/accrual.js';
|
|
7
7
|
import { countWorkingDays, workingDays } from '../policy/calendar.js';
|
|
8
|
-
import {
|
|
9
|
-
import { COUNTRY_PACKS, packDays } from './packs/index.js';
|
|
8
|
+
import { COUNTRY_PACKS, packDays, packFor } from './packs/index.js';
|
|
10
9
|
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';
|
|
11
10
|
import { ApprovalService } from './services/approvals.js';
|
|
12
11
|
import { AttendanceService } from './services/attendance.js';
|
|
13
|
-
import { inForceOn,
|
|
12
|
+
import { inForceOn, todayIso } from './services/db.js';
|
|
14
13
|
import { LedgerService, MINUTES_PER_DAY, yearOf } from './services/ledger.js';
|
|
15
14
|
import { PeopleService } from './services/people.js';
|
|
16
15
|
import { hashConfig, PolicyService } from './services/policies.js';
|
|
@@ -35,8 +34,8 @@ export function implement_(kernel) {
|
|
|
35
34
|
const svc = new PeopleService(kernel);
|
|
36
35
|
const ledger = new LedgerService();
|
|
37
36
|
const approvals = new ApprovalService(kernel);
|
|
38
|
-
const attendance = new AttendanceService();
|
|
39
37
|
const policySvc = new PolicyService(resolve);
|
|
38
|
+
const attendance = new AttendanceService(resolve, policySvc);
|
|
40
39
|
const db = kernel.database;
|
|
41
40
|
const settingsOf = (workspaceId) => kernel.settings.module(workspaceId, MODULE_ID, HrSettings);
|
|
42
41
|
const changed = (workspaceId, entity, id, op) => kernel.realtime.change(workspaceId, { module: MODULE_ID, entity, id, op });
|
|
@@ -1496,13 +1495,12 @@ export function implement_(kernel) {
|
|
|
1496
1495
|
.where(eq(periods.id, period.id))
|
|
1497
1496
|
.returning();
|
|
1498
1497
|
// 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
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
return { period: row, lockedDays: locked.length };
|
|
1498
|
+
// on every row — this is what actually stops a closed month moving. It is stamped
|
|
1499
|
+
// through `setPeriodLock`, which is also what keeps the flag scoped to the period's
|
|
1500
|
+
// legal entity: filtering on the date range alone closed January for every entity in
|
|
1501
|
+
// the workspace, while `isLocked` went on answering for one.
|
|
1502
|
+
const lockedDays = await attendance.setPeriodLock(tx, input.workspaceId, period, true);
|
|
1503
|
+
return { period: row, lockedDays };
|
|
1506
1504
|
});
|
|
1507
1505
|
await changed(input.workspaceId, 'period', input.periodId, 'updated');
|
|
1508
1506
|
return { ...toPeriod(result.period), lockedDays: result.lockedDays };
|
|
@@ -1523,20 +1521,16 @@ export function implement_(kernel) {
|
|
|
1523
1521
|
})
|
|
1524
1522
|
.where(eq(periods.id, period.id))
|
|
1525
1523
|
.returning();
|
|
1526
|
-
const
|
|
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 });
|
|
1524
|
+
const unlockedDays = await attendance.setPeriodLock(tx, input.workspaceId, period, false);
|
|
1531
1525
|
kernel.log.warn({
|
|
1532
1526
|
module: MODULE_ID,
|
|
1533
1527
|
workspaceId: input.workspaceId,
|
|
1534
1528
|
periodId: period.id,
|
|
1535
|
-
days:
|
|
1529
|
+
days: unlockedDays,
|
|
1536
1530
|
actorId: context.principal.userId,
|
|
1537
1531
|
reason: input.reason,
|
|
1538
1532
|
}, 'payroll period reopened');
|
|
1539
|
-
return { period: row, unlockedDays
|
|
1533
|
+
return { period: row, unlockedDays };
|
|
1540
1534
|
});
|
|
1541
1535
|
await changed(input.workspaceId, 'period', input.periodId, 'updated');
|
|
1542
1536
|
return { ...toPeriod(result.period), unlockedDays: result.unlockedDays };
|
|
@@ -1549,13 +1543,16 @@ export function implement_(kernel) {
|
|
|
1549
1543
|
.use(requires('hr.attendance.view'))
|
|
1550
1544
|
.handler(({ input, context }) => db.withWorkspace(input.workspaceId, async (tx) => {
|
|
1551
1545
|
const personId = await personFor(tx, input.workspaceId, context, input.personId);
|
|
1552
|
-
|
|
1553
|
-
|
|
1546
|
+
// The same attribution the next punch will be judged against, so what a person is shown
|
|
1547
|
+
// and what they are then allowed to do cannot disagree. Somebody who forgot to clock out
|
|
1548
|
+
// last night is told they are still on last night's shift, which is both true and the
|
|
1549
|
+
// thing they need to know before pressing anything.
|
|
1550
|
+
const { timezone, at, attribution } = await clockNow(tx, input.workspaceId, personId);
|
|
1554
1551
|
let open = null;
|
|
1555
1552
|
let onBreak = false;
|
|
1556
1553
|
let workedMs = 0;
|
|
1557
1554
|
let breakOpen = null;
|
|
1558
|
-
for (const r of
|
|
1555
|
+
for (const r of attribution.punches) {
|
|
1559
1556
|
if (r.direction === 'in')
|
|
1560
1557
|
open = r.at;
|
|
1561
1558
|
else if (r.direction === 'out' && open) {
|
|
@@ -1573,15 +1570,15 @@ export function implement_(kernel) {
|
|
|
1573
1570
|
}
|
|
1574
1571
|
}
|
|
1575
1572
|
// An open span counts up to now, so the widget shows time accruing rather than freezing
|
|
1576
|
-
// at the last completed pair.
|
|
1573
|
+
// at the last completed pair. `at` rather than a fresh `Date.now()`: one instant per
|
|
1574
|
+
// request, the one the attribution was made from.
|
|
1577
1575
|
if (open)
|
|
1578
|
-
workedMs +=
|
|
1576
|
+
workedMs += at.getTime() - open.getTime();
|
|
1579
1577
|
if (breakOpen)
|
|
1580
|
-
workedMs -=
|
|
1581
|
-
void schedule;
|
|
1578
|
+
workedMs -= at.getTime() - breakOpen.getTime();
|
|
1582
1579
|
return {
|
|
1583
1580
|
personId,
|
|
1584
|
-
businessDate,
|
|
1581
|
+
businessDate: attribution.businessDate,
|
|
1585
1582
|
clockedIn: open !== null,
|
|
1586
1583
|
onBreak,
|
|
1587
1584
|
since: (open ?? breakOpen)?.toISOString() ?? null,
|
|
@@ -1635,7 +1632,7 @@ export function implement_(kernel) {
|
|
|
1635
1632
|
const me = await svc.byUserId(tx, input.workspaceId, context.principal.userId ?? '');
|
|
1636
1633
|
const { original } = await attendance.voidPunch(tx, input.workspaceId, input.punchId, input.reason, me?.id ?? null);
|
|
1637
1634
|
// The day is derived, so voiding a punch means the sheet is stale until it is rebuilt.
|
|
1638
|
-
const { timezone, schedule } = await
|
|
1635
|
+
const { timezone, schedule } = await personContext(tx, input.workspaceId, original.personId);
|
|
1639
1636
|
await attendance.recomputeDay(tx, input.workspaceId, original.personId, original.businessDate, timezone, schedule);
|
|
1640
1637
|
return original;
|
|
1641
1638
|
});
|
|
@@ -1685,7 +1682,7 @@ export function implement_(kernel) {
|
|
|
1685
1682
|
.use(requires('hr.attendance.manage'))
|
|
1686
1683
|
.handler(({ input, context }) => db.withWorkspace(input.workspaceId, async (tx) => {
|
|
1687
1684
|
const personId = await personFor(tx, input.workspaceId, context, input.personId);
|
|
1688
|
-
const { timezone, schedule } = await
|
|
1685
|
+
const { timezone, schedule } = await personContext(tx, input.workspaceId, personId);
|
|
1689
1686
|
const dates = await attendance.datesWithPunches(tx, input.workspaceId, personId, input.from, input.to);
|
|
1690
1687
|
let recomputed = 0;
|
|
1691
1688
|
const skippedLocked = [];
|
|
@@ -2061,7 +2058,12 @@ export function implement_(kernel) {
|
|
|
2061
2058
|
await ledger.lockAndRead(tx, input.workspaceId, personId, input.leaveTypeId, year);
|
|
2062
2059
|
const sim = await simulate(tx, input.workspaceId, personId, input);
|
|
2063
2060
|
if (sim.blockers.length)
|
|
2064
|
-
|
|
2061
|
+
// `details`, for the reason `refusePunch` gives: `KernError.conflict`'s second
|
|
2062
|
+
// argument is kept on the error object and never serialised, so the blocker code
|
|
2063
|
+
// this line has always meant to send was reaching nobody.
|
|
2064
|
+
throw new KernError('CONFLICT', sim.blockers[0].message, {
|
|
2065
|
+
reason: `hr.leave.${sim.blockers[0].code}`,
|
|
2066
|
+
});
|
|
2065
2067
|
const [request] = await tx
|
|
2066
2068
|
.insert(leaveRequests)
|
|
2067
2069
|
.values({
|
|
@@ -2130,8 +2132,14 @@ export function implement_(kernel) {
|
|
|
2130
2132
|
.handler(async ({ input, context }) => {
|
|
2131
2133
|
const row = await db.withWorkspace(input.workspaceId, async (tx) => {
|
|
2132
2134
|
const request = await loadRequest(tx, input.workspaceId, input.requestId);
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
+
// A reason beside the sentence, because the sentence is English and the reason is what
|
|
2136
|
+
// a client can translate. Two states, not one: "withdrawn" is the requester taking it
|
|
2137
|
+
// back and "cancelled" is somebody else doing so, and telling a person their own
|
|
2138
|
+
// withdrawal was "already cancelled" is a small lie about who did what.
|
|
2139
|
+
if (request.status === 'cancelled')
|
|
2140
|
+
throw KernError.conflict('That request is already cancelled.', 'hr.leave.already_cancelled');
|
|
2141
|
+
if (request.status === 'withdrawn')
|
|
2142
|
+
throw KernError.conflict('That request was already withdrawn.', 'hr.leave.already_withdrawn');
|
|
2135
2143
|
const year = yearOf(request.startsOn);
|
|
2136
2144
|
await ledger.lockAndRead(tx, input.workspaceId, request.personId, request.leaveTypeId, year);
|
|
2137
2145
|
// Approved leave is *reversed*, not deleted. "She booked it and cancelled" and "she
|
|
@@ -2647,7 +2655,31 @@ export function implement_(kernel) {
|
|
|
2647
2655
|
* company's own holidays during a routine yearly refresh, silently, months before anyone notices.
|
|
2648
2656
|
* Showing exactly which days survive is how an administrator can believe the operation.
|
|
2649
2657
|
*/
|
|
2658
|
+
/**
|
|
2659
|
+
* The days a pack would add, change and drop — and the days it must never touch.
|
|
2660
|
+
*
|
|
2661
|
+
* A pack key nobody publishes is refused rather than treated as a pack with no days in it.
|
|
2662
|
+
* `packDays` answers `[]` for an unknown key, which made the diff for a typo "add nothing, change
|
|
2663
|
+
* nothing, **remove every national holiday**" — with Apply live beside it, and `keptCustom` still
|
|
2664
|
+
* promising the company's own days were safe, which was true and beside the point. The screen
|
|
2665
|
+
* could reach that state on its own: `COUNTRY_PACKS` is keyed `TR`, the editor prefilled the
|
|
2666
|
+
* calendar's country lowercased, and so a calendar made by hand offered to empty itself.
|
|
2667
|
+
*
|
|
2668
|
+
* This is the one operation in the module that deletes rows a customer did not ask it to, so it
|
|
2669
|
+
* fails closed: an unknown key is a mistake, never an instruction.
|
|
2670
|
+
*/
|
|
2671
|
+
function requirePack(packKey) {
|
|
2672
|
+
// `packFor` states the rule and is tested beside the packs; this turns its refusal into the
|
|
2673
|
+
// error shape a client can render.
|
|
2674
|
+
try {
|
|
2675
|
+
packFor(packKey);
|
|
2676
|
+
}
|
|
2677
|
+
catch (error) {
|
|
2678
|
+
throw KernError.badRequest(error.message);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2650
2681
|
async function diffPack(tx, workspaceId, calendarId, packKey, year) {
|
|
2682
|
+
requirePack(packKey);
|
|
2651
2683
|
const from = `${year}-01-01`;
|
|
2652
2684
|
const to = `${year}-12-31`;
|
|
2653
2685
|
const existing = await tx
|
|
@@ -2946,17 +2978,55 @@ export function implement_(kernel) {
|
|
|
2946
2978
|
};
|
|
2947
2979
|
}
|
|
2948
2980
|
/**
|
|
2949
|
-
* Everything a punch needs
|
|
2981
|
+
* Everything a punch needs about a person: their zone, and the schedule that shapes their day.
|
|
2950
2982
|
*
|
|
2951
2983
|
* The zone comes from the resolution ladder — their primary office unless they have an override —
|
|
2952
2984
|
* so a punch made on a business trip still counts towards the month they are employed in.
|
|
2985
|
+
*
|
|
2986
|
+
* Everything here is resolved **as of today**, which is what a punch is about. It is therefore
|
|
2987
|
+
* not the place to answer a question about a past date: this used to hand out today's legal
|
|
2988
|
+
* entity as well, and three callers applied it to business dates months back — so a person who
|
|
2989
|
+
* transferred entity had a filed month recomputed against the one they are in now. `recomputeDay`
|
|
2990
|
+
* asks that question of the day it is rebuilding.
|
|
2953
2991
|
*/
|
|
2954
|
-
async function
|
|
2992
|
+
async function personContext(tx, workspaceId, personId) {
|
|
2955
2993
|
const today = todayIso();
|
|
2956
2994
|
const resolution = await resolve.forPerson(tx, workspaceId, personId, today);
|
|
2957
2995
|
const schedule = await attendance.scheduleFor(tx, workspaceId, personId, today);
|
|
2958
|
-
|
|
2959
|
-
|
|
2996
|
+
return { timezone: resolution.timezone, schedule, resolution };
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* The same, plus the one decision every clock procedure has to agree about: which shift *this
|
|
3000
|
+
* instant* belongs to, and what is already filed on it.
|
|
3001
|
+
*
|
|
3002
|
+
* The instant is taken once, here, and travels with its answer — so the guard, the row that is
|
|
3003
|
+
* written and the figures the widget shows all come out of a single attribution. They used to be
|
|
3004
|
+
* three: `clockContext` attributed `Date.now()`, the guard re-read the punches of whatever date
|
|
3005
|
+
* that produced, and `record` attributed its own `new Date()` again on the way to the insert.
|
|
3006
|
+
* Every version of the night-shift bug is those answers disagreeing, and a person being told they
|
|
3007
|
+
* are not clocked in at the end of a shift they have just worked.
|
|
3008
|
+
*/
|
|
3009
|
+
async function clockNow(tx, workspaceId, personId) {
|
|
3010
|
+
const context = await personContext(tx, workspaceId, personId);
|
|
3011
|
+
const at = new Date();
|
|
3012
|
+
const attribution = await attendance.attribute(tx, workspaceId, personId, at.getTime(), context.timezone, context.schedule);
|
|
3013
|
+
return { ...context, at, attribution };
|
|
3014
|
+
}
|
|
3015
|
+
/**
|
|
3016
|
+
* A refused punch: the sentence for the reader, and a stable reason for the screen.
|
|
3017
|
+
*
|
|
3018
|
+
* The reason travels in `details`, not in `KernError.conflict`'s `reason` argument. Only
|
|
3019
|
+
* `details` is on the wire — `kernErrorToORPC` maps it to the oRPC error's `data` and drops
|
|
3020
|
+
* everything else — so a reason passed as `conflict(message, reason)` never leaves this process.
|
|
3021
|
+
* Reasons are dotted and namespaced like `hr.leave.*` above, and are API: renaming one silently
|
|
3022
|
+
* puts the English sentence back in front of every non-English reader.
|
|
3023
|
+
*
|
|
3024
|
+
* A `function`, like every other helper down here: these sit after the router's `return`, so a
|
|
3025
|
+
* `const` is never initialised and the first refusal throws a ReferenceError instead of a
|
|
3026
|
+
* conflict. `noUnreachable` is what catches that — nothing else does until a person punches.
|
|
3027
|
+
*/
|
|
3028
|
+
function refusePunch(reason, message) {
|
|
3029
|
+
return new KernError('CONFLICT', message, { reason });
|
|
2960
3030
|
}
|
|
2961
3031
|
/**
|
|
2962
3032
|
* One punch, and the day rebuilt straight afterwards.
|
|
@@ -2968,24 +3038,35 @@ export function implement_(kernel) {
|
|
|
2968
3038
|
async function punch(input, context, direction) {
|
|
2969
3039
|
const row = await db.withWorkspace(input.workspaceId, async (tx) => {
|
|
2970
3040
|
const personId = await personFor(tx, input.workspaceId, context, input.personId);
|
|
2971
|
-
const { timezone,
|
|
3041
|
+
const { timezone, schedule, resolution, at, attribution } = await clockNow(tx, input.workspaceId, personId);
|
|
2972
3042
|
// Refuse the transitions that make no sense, with a sentence rather than a constraint error:
|
|
2973
3043
|
// clocking in twice, or out when never in, is somebody double-tapping a button.
|
|
2974
|
-
|
|
2975
|
-
|
|
3044
|
+
//
|
|
3045
|
+
// The sentence is written for a person, and it is written in English — a router has no locale.
|
|
3046
|
+
// So every refusal also carries a *reason*, which is what the clock widget translates; the
|
|
3047
|
+
// sentence is what it falls back to for a reason it has no string for, so a sixth refusal
|
|
3048
|
+
// added here reaches the reader in English rather than not at all.
|
|
3049
|
+
//
|
|
3050
|
+
// What it is judged against comes out of the attribution rather than being fetched again.
|
|
3051
|
+
// Re-reading `punchesOn(businessDate)` here is what made the guard circular: it could only
|
|
3052
|
+
// ever confirm the date the attribution had already chosen, so a wrong choice arrived as
|
|
3053
|
+
// "You are not clocked in." at the end of a shift somebody had just worked.
|
|
3054
|
+
const open = attribution.open;
|
|
2976
3055
|
if (direction === 'in' && open.clockedIn)
|
|
2977
|
-
throw
|
|
3056
|
+
throw refusePunch('hr.clock.already_clocked_in', 'You are already clocked in.');
|
|
2978
3057
|
if (direction === 'out' && !open.clockedIn)
|
|
2979
|
-
throw
|
|
3058
|
+
throw refusePunch('hr.clock.not_clocked_in', 'You are not clocked in.');
|
|
2980
3059
|
if (direction === 'break_start' && !open.clockedIn)
|
|
2981
|
-
throw
|
|
3060
|
+
throw refusePunch('hr.clock.break_before_clock_in', 'Clock in before starting a break.');
|
|
2982
3061
|
if (direction === 'break_start' && open.onBreak)
|
|
2983
|
-
throw
|
|
3062
|
+
throw refusePunch('hr.clock.already_on_break', 'You are already on a break.');
|
|
2984
3063
|
if (direction === 'break_end' && !open.onBreak)
|
|
2985
|
-
throw
|
|
3064
|
+
throw refusePunch('hr.clock.not_on_break', 'You are not on a break.');
|
|
2986
3065
|
const punchRow = await attendance.record(tx, input.workspaceId, {
|
|
2987
3066
|
personId,
|
|
2988
3067
|
direction,
|
|
3068
|
+
at,
|
|
3069
|
+
businessDate: attribution.businessDate,
|
|
2989
3070
|
timezone,
|
|
2990
3071
|
method: input.method ?? 'web',
|
|
2991
3072
|
clientReportedAt: input.clientReportedAt ?? null,
|
|
@@ -2993,7 +3074,10 @@ export function implement_(kernel) {
|
|
|
2993
3074
|
geo: input.geo ?? null,
|
|
2994
3075
|
note: input.note ?? null,
|
|
2995
3076
|
idempotencyKey: input.idempotencyKey ?? null,
|
|
2996
|
-
}
|
|
3077
|
+
});
|
|
3078
|
+
// A punch inside a closed month is still recorded — it is a fact about somebody's day, and
|
|
3079
|
+
// refusing it because payroll has been filed loses the fact to protect the report. What must
|
|
3080
|
+
// not move is the derived sheet, and `recomputeDay` is where that is decided.
|
|
2997
3081
|
await attendance.recomputeDay(tx, input.workspaceId, personId, punchRow.businessDate, timezone, schedule);
|
|
2998
3082
|
return punchRow;
|
|
2999
3083
|
});
|
|
@@ -3007,24 +3091,6 @@ export function implement_(kernel) {
|
|
|
3007
3091
|
await changed(input.workspaceId, 'attendance_day', row.personId, 'updated');
|
|
3008
3092
|
return toPunch(row);
|
|
3009
3093
|
}
|
|
3010
|
-
/** Whether a person is currently clocked in or on a break, from their punches so far. */
|
|
3011
|
-
function openState(rows) {
|
|
3012
|
-
let clockedIn = false;
|
|
3013
|
-
let onBreak = false;
|
|
3014
|
-
for (const r of rows) {
|
|
3015
|
-
if (r.direction === 'in')
|
|
3016
|
-
clockedIn = true;
|
|
3017
|
-
else if (r.direction === 'out') {
|
|
3018
|
-
clockedIn = false;
|
|
3019
|
-
onBreak = false;
|
|
3020
|
-
}
|
|
3021
|
-
else if (r.direction === 'break_start')
|
|
3022
|
-
onBreak = true;
|
|
3023
|
-
else if (r.direction === 'break_end')
|
|
3024
|
-
onBreak = false;
|
|
3025
|
-
}
|
|
3026
|
-
return { clockedIn, onBreak };
|
|
3027
|
-
}
|
|
3028
3094
|
/**
|
|
3029
3095
|
* Apply an approved correction: write the proposed punches, void what they replace, rebuild.
|
|
3030
3096
|
*
|
|
@@ -3042,7 +3108,7 @@ export function implement_(kernel) {
|
|
|
3042
3108
|
return;
|
|
3043
3109
|
if (row.punchId)
|
|
3044
3110
|
await attendance.voidPunch(tx, workspaceId, row.punchId, 'Regularized', null);
|
|
3045
|
-
const { timezone, schedule } = await
|
|
3111
|
+
const { timezone, schedule } = await personContext(tx, workspaceId, row.personId);
|
|
3046
3112
|
for (const proposal of row.proposed)
|
|
3047
3113
|
await tx.insert(punches).values({
|
|
3048
3114
|
id: uuidv7(),
|
|
@@ -3121,6 +3187,30 @@ export function implement_(kernel) {
|
|
|
3121
3187
|
.from(employments)
|
|
3122
3188
|
.where(and(eq(employments.workspaceId, workspaceId), inArray(employments.personId, ids), inForceOn(employments.effectiveFrom, employments.effectiveTo, to)));
|
|
3123
3189
|
const employmentBy = new Map(employmentRows.map((e) => [e.personId, e]));
|
|
3190
|
+
/**
|
|
3191
|
+
* Hours worked in the window, for the frequency that accrues against them.
|
|
3192
|
+
*
|
|
3193
|
+
* The preview has to answer with the same numbers the job will credit, so it reads the same
|
|
3194
|
+
* inputs. `per_hour_worked` was fed by neither for as long as it existed, which made the
|
|
3195
|
+
* preview quietly agree with a job that granted nothing. Only queried where a policy asks.
|
|
3196
|
+
*/
|
|
3197
|
+
const hoursBy = new Map();
|
|
3198
|
+
if ([...resolved.values()].some((p) => p?.config?.frequency === 'per_hour_worked')) {
|
|
3199
|
+
const rows = await tx
|
|
3200
|
+
.select({
|
|
3201
|
+
personId: attendanceDays.personId,
|
|
3202
|
+
worked: sql `coalesce(sum(${attendanceDays.workedMinutes}), 0)`,
|
|
3203
|
+
scheduled: sql `coalesce(sum(${attendanceDays.scheduledMinutes}), 0)`,
|
|
3204
|
+
})
|
|
3205
|
+
.from(attendanceDays)
|
|
3206
|
+
.where(and(eq(attendanceDays.workspaceId, workspaceId), inArray(attendanceDays.personId, ids), gte(attendanceDays.businessDate, from), lte(attendanceDays.businessDate, to)))
|
|
3207
|
+
.groupBy(attendanceDays.personId);
|
|
3208
|
+
for (const r of rows)
|
|
3209
|
+
hoursBy.set(r.personId, {
|
|
3210
|
+
workedMinutes: Number(r.worked),
|
|
3211
|
+
scheduledMinutes: Number(r.scheduled),
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3124
3214
|
const types = await tx
|
|
3125
3215
|
.select()
|
|
3126
3216
|
.from(leaveTypes)
|
|
@@ -3175,6 +3265,7 @@ export function implement_(kernel) {
|
|
|
3175
3265
|
hiredOn: person.hiredOn,
|
|
3176
3266
|
terminatedOn: person.terminatedOn,
|
|
3177
3267
|
fte: employment ? Number.parseFloat(employment.fte ?? '1') : 1,
|
|
3268
|
+
...hoursBy.get(person.id),
|
|
3178
3269
|
});
|
|
3179
3270
|
const alreadyAccrued = already.has(`${person.id}:${type.id}`);
|
|
3180
3271
|
rows.push({
|