@kernhq/module-hr 0.9.3 → 0.10.1
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/approvals.d.ts +3 -0
- package/dist/contract/approvals.d.ts.map +1 -1
- package/dist/contract/approvals.js +13 -1
- package/dist/contract/approvals.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 +9 -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 +458 -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 +1896 -111
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +146 -63
- package/dist/server/router.js.map +1 -1
- package/dist/server/schema.d.ts +53 -0
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +101 -2
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/approvals.d.ts +14 -0
- package/dist/server/services/approvals.d.ts.map +1 -1
- package/dist/server/services/approvals.js +14 -3
- package/dist/server/services/approvals.js.map +1 -1
- package/dist/server/services/attendance.d.ts +120 -6
- package/dist/server/services/attendance.d.ts.map +1 -1
- package/dist/server/services/attendance.js +226 -13
- package/dist/server/services/attendance.js.map +1 -1
- package/dist/server/services/policies.d.ts +6 -0
- package/dist/server/services/policies.d.ts.map +1 -1
- package/dist/server/services/policies.js +6 -0
- package/dist/server/services/policies.js.map +1 -1
- package/migrations/0005_approval_requester.sql +30 -0
- 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 +28 -0
- package/package.json +7 -7
- package/src/client/capabilities.ts +34 -0
- package/src/client/components/ClockControls.svelte +127 -20
- package/src/client/components/ClockControls.test.ts +446 -0
- package/src/client/components/DecisionDialog.svelte +90 -0
- package/src/client/components/DelegationDialog.svelte +219 -0
- 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 -625
- package/src/client/index.ts +1 -20
- package/src/client/messages.test.ts +154 -0
- package/src/client/messages.ts +3388 -0
- package/src/client/mock.ts +1691 -162
- package/src/client/module.ts +17 -2
- package/src/client/pages/ApprovalsPage.svelte +332 -60
- 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 +181 -39
- package/src/client/pages/leave-and-attendance.test.ts +588 -0
- package/src/client/permissions.ts +2 -1
- package/src/client/query.ts +22 -1
- 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 +43 -0
- 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/approvals.ts +13 -1
- 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAm2HxC"}
|
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 = [];
|
|
@@ -1849,6 +1846,7 @@ export function implement_(kernel) {
|
|
|
1849
1846
|
subjectType: 'regularization',
|
|
1850
1847
|
subjectId: created.id,
|
|
1851
1848
|
summary: `Correction for ${input.businessDate}`,
|
|
1849
|
+
summaryParams: { date: input.businessDate },
|
|
1852
1850
|
requesterPersonId: personId,
|
|
1853
1851
|
requestedBy: context.principal.userId ?? null,
|
|
1854
1852
|
on: input.businessDate,
|
|
@@ -2060,7 +2058,12 @@ export function implement_(kernel) {
|
|
|
2060
2058
|
await ledger.lockAndRead(tx, input.workspaceId, personId, input.leaveTypeId, year);
|
|
2061
2059
|
const sim = await simulate(tx, input.workspaceId, personId, input);
|
|
2062
2060
|
if (sim.blockers.length)
|
|
2063
|
-
|
|
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
|
+
});
|
|
2064
2067
|
const [request] = await tx
|
|
2065
2068
|
.insert(leaveRequests)
|
|
2066
2069
|
.values({
|
|
@@ -2097,6 +2100,7 @@ export function implement_(kernel) {
|
|
|
2097
2100
|
subjectType: 'leave',
|
|
2098
2101
|
subjectId: request.id,
|
|
2099
2102
|
summary: `${sim.workingDays} day(s) from ${input.startsOn}`,
|
|
2103
|
+
summaryParams: { days: sim.workingDays, from: input.startsOn, to: input.endsOn },
|
|
2100
2104
|
requesterPersonId: personId,
|
|
2101
2105
|
requestedBy: context.principal.userId ?? null,
|
|
2102
2106
|
on: input.startsOn,
|
|
@@ -2128,8 +2132,14 @@ export function implement_(kernel) {
|
|
|
2128
2132
|
.handler(async ({ input, context }) => {
|
|
2129
2133
|
const row = await db.withWorkspace(input.workspaceId, async (tx) => {
|
|
2130
2134
|
const request = await loadRequest(tx, input.workspaceId, input.requestId);
|
|
2131
|
-
|
|
2132
|
-
|
|
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');
|
|
2133
2143
|
const year = yearOf(request.startsOn);
|
|
2134
2144
|
await ledger.lockAndRead(tx, input.workspaceId, request.personId, request.leaveTypeId, year);
|
|
2135
2145
|
// Approved leave is *reversed*, not deleted. "She booked it and cancelled" and "she
|
|
@@ -2645,7 +2655,31 @@ export function implement_(kernel) {
|
|
|
2645
2655
|
* company's own holidays during a routine yearly refresh, silently, months before anyone notices.
|
|
2646
2656
|
* Showing exactly which days survive is how an administrator can believe the operation.
|
|
2647
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
|
+
}
|
|
2648
2681
|
async function diffPack(tx, workspaceId, calendarId, packKey, year) {
|
|
2682
|
+
requirePack(packKey);
|
|
2649
2683
|
const from = `${year}-01-01`;
|
|
2650
2684
|
const to = `${year}-12-31`;
|
|
2651
2685
|
const existing = await tx
|
|
@@ -2896,6 +2930,13 @@ export function implement_(kernel) {
|
|
|
2896
2930
|
.where(and(eq(approvalChains.workspaceId, workspaceId), eq(approvalChains.subjectType, subjectType), eq(approvalChains.isDefault, true)));
|
|
2897
2931
|
}
|
|
2898
2932
|
/** An approval request with its steps and decisions, which is the only useful shape. */
|
|
2933
|
+
/**
|
|
2934
|
+
* One request, with its steps, its decisions and the requester's name.
|
|
2935
|
+
*
|
|
2936
|
+
* The name is resolved here rather than left to the client: an inbox is one call, and a client
|
|
2937
|
+
* that has to fetch the directory to read it will show ids for the moment the directory is in
|
|
2938
|
+
* flight — which is the moment somebody clicks approve.
|
|
2939
|
+
*/
|
|
2899
2940
|
async function hydrateApproval(tx, row) {
|
|
2900
2941
|
const steps = await tx
|
|
2901
2942
|
.select()
|
|
@@ -2906,10 +2947,18 @@ export function implement_(kernel) {
|
|
|
2906
2947
|
const decisions = stepIds.length
|
|
2907
2948
|
? await tx.select().from(approvalDecisions).where(inArray(approvalDecisions.stepId, stepIds))
|
|
2908
2949
|
: [];
|
|
2950
|
+
const [requester] = row.requesterPersonId
|
|
2951
|
+
? await tx
|
|
2952
|
+
.select({ displayName: people.displayName })
|
|
2953
|
+
.from(people)
|
|
2954
|
+
.where(and(eq(people.workspaceId, row.workspaceId), eq(people.id, row.requesterPersonId)))
|
|
2955
|
+
.limit(1)
|
|
2956
|
+
: [];
|
|
2909
2957
|
return {
|
|
2910
2958
|
...row,
|
|
2911
2959
|
subjectType: row.subjectType,
|
|
2912
2960
|
status: row.status,
|
|
2961
|
+
requesterName: requester?.displayName ?? null,
|
|
2913
2962
|
requestedAt: row.requestedAt.toISOString(),
|
|
2914
2963
|
decidedAt: row.decidedAt?.toISOString() ?? null,
|
|
2915
2964
|
steps: steps.map((s) => ({
|
|
@@ -2929,17 +2978,55 @@ export function implement_(kernel) {
|
|
|
2929
2978
|
};
|
|
2930
2979
|
}
|
|
2931
2980
|
/**
|
|
2932
|
-
* Everything a punch needs
|
|
2981
|
+
* Everything a punch needs about a person: their zone, and the schedule that shapes their day.
|
|
2933
2982
|
*
|
|
2934
2983
|
* The zone comes from the resolution ladder — their primary office unless they have an override —
|
|
2935
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.
|
|
2936
2991
|
*/
|
|
2937
|
-
async function
|
|
2992
|
+
async function personContext(tx, workspaceId, personId) {
|
|
2938
2993
|
const today = todayIso();
|
|
2939
2994
|
const resolution = await resolve.forPerson(tx, workspaceId, personId, today);
|
|
2940
2995
|
const schedule = await attendance.scheduleFor(tx, workspaceId, personId, today);
|
|
2941
|
-
|
|
2942
|
-
|
|
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 });
|
|
2943
3030
|
}
|
|
2944
3031
|
/**
|
|
2945
3032
|
* One punch, and the day rebuilt straight afterwards.
|
|
@@ -2951,24 +3038,35 @@ export function implement_(kernel) {
|
|
|
2951
3038
|
async function punch(input, context, direction) {
|
|
2952
3039
|
const row = await db.withWorkspace(input.workspaceId, async (tx) => {
|
|
2953
3040
|
const personId = await personFor(tx, input.workspaceId, context, input.personId);
|
|
2954
|
-
const { timezone,
|
|
3041
|
+
const { timezone, schedule, resolution, at, attribution } = await clockNow(tx, input.workspaceId, personId);
|
|
2955
3042
|
// Refuse the transitions that make no sense, with a sentence rather than a constraint error:
|
|
2956
3043
|
// clocking in twice, or out when never in, is somebody double-tapping a button.
|
|
2957
|
-
|
|
2958
|
-
|
|
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;
|
|
2959
3055
|
if (direction === 'in' && open.clockedIn)
|
|
2960
|
-
throw
|
|
3056
|
+
throw refusePunch('hr.clock.already_clocked_in', 'You are already clocked in.');
|
|
2961
3057
|
if (direction === 'out' && !open.clockedIn)
|
|
2962
|
-
throw
|
|
3058
|
+
throw refusePunch('hr.clock.not_clocked_in', 'You are not clocked in.');
|
|
2963
3059
|
if (direction === 'break_start' && !open.clockedIn)
|
|
2964
|
-
throw
|
|
3060
|
+
throw refusePunch('hr.clock.break_before_clock_in', 'Clock in before starting a break.');
|
|
2965
3061
|
if (direction === 'break_start' && open.onBreak)
|
|
2966
|
-
throw
|
|
3062
|
+
throw refusePunch('hr.clock.already_on_break', 'You are already on a break.');
|
|
2967
3063
|
if (direction === 'break_end' && !open.onBreak)
|
|
2968
|
-
throw
|
|
3064
|
+
throw refusePunch('hr.clock.not_on_break', 'You are not on a break.');
|
|
2969
3065
|
const punchRow = await attendance.record(tx, input.workspaceId, {
|
|
2970
3066
|
personId,
|
|
2971
3067
|
direction,
|
|
3068
|
+
at,
|
|
3069
|
+
businessDate: attribution.businessDate,
|
|
2972
3070
|
timezone,
|
|
2973
3071
|
method: input.method ?? 'web',
|
|
2974
3072
|
clientReportedAt: input.clientReportedAt ?? null,
|
|
@@ -2976,7 +3074,10 @@ export function implement_(kernel) {
|
|
|
2976
3074
|
geo: input.geo ?? null,
|
|
2977
3075
|
note: input.note ?? null,
|
|
2978
3076
|
idempotencyKey: input.idempotencyKey ?? null,
|
|
2979
|
-
}
|
|
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.
|
|
2980
3081
|
await attendance.recomputeDay(tx, input.workspaceId, personId, punchRow.businessDate, timezone, schedule);
|
|
2981
3082
|
return punchRow;
|
|
2982
3083
|
});
|
|
@@ -2990,24 +3091,6 @@ export function implement_(kernel) {
|
|
|
2990
3091
|
await changed(input.workspaceId, 'attendance_day', row.personId, 'updated');
|
|
2991
3092
|
return toPunch(row);
|
|
2992
3093
|
}
|
|
2993
|
-
/** Whether a person is currently clocked in or on a break, from their punches so far. */
|
|
2994
|
-
function openState(rows) {
|
|
2995
|
-
let clockedIn = false;
|
|
2996
|
-
let onBreak = false;
|
|
2997
|
-
for (const r of rows) {
|
|
2998
|
-
if (r.direction === 'in')
|
|
2999
|
-
clockedIn = true;
|
|
3000
|
-
else if (r.direction === 'out') {
|
|
3001
|
-
clockedIn = false;
|
|
3002
|
-
onBreak = false;
|
|
3003
|
-
}
|
|
3004
|
-
else if (r.direction === 'break_start')
|
|
3005
|
-
onBreak = true;
|
|
3006
|
-
else if (r.direction === 'break_end')
|
|
3007
|
-
onBreak = false;
|
|
3008
|
-
}
|
|
3009
|
-
return { clockedIn, onBreak };
|
|
3010
|
-
}
|
|
3011
3094
|
/**
|
|
3012
3095
|
* Apply an approved correction: write the proposed punches, void what they replace, rebuild.
|
|
3013
3096
|
*
|
|
@@ -3025,7 +3108,7 @@ export function implement_(kernel) {
|
|
|
3025
3108
|
return;
|
|
3026
3109
|
if (row.punchId)
|
|
3027
3110
|
await attendance.voidPunch(tx, workspaceId, row.punchId, 'Regularized', null);
|
|
3028
|
-
const { timezone, schedule } = await
|
|
3111
|
+
const { timezone, schedule } = await personContext(tx, workspaceId, row.personId);
|
|
3029
3112
|
for (const proposal of row.proposed)
|
|
3030
3113
|
await tx.insert(punches).values({
|
|
3031
3114
|
id: uuidv7(),
|