@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.
- package/dist/contract/capabilities.d.ts.map +1 -1
- package/dist/contract/capabilities.js +31 -0
- package/dist/contract/capabilities.js.map +1 -1
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +1 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/contract/permissions.d.ts +23 -0
- package/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +27 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/policies.d.ts +256 -0
- package/dist/contract/policies.d.ts.map +1 -0
- package/dist/contract/policies.js +175 -0
- package/dist/contract/policies.js.map +1 -0
- package/dist/contract/router.d.ts +707 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +127 -0
- package/dist/contract/router.js.map +1 -1
- package/dist/policy/accrual.d.ts +127 -0
- package/dist/policy/accrual.d.ts.map +1 -0
- package/dist/policy/accrual.js +134 -0
- package/dist/policy/accrual.js.map +1 -0
- package/dist/server/jobs.d.ts.map +1 -1
- package/dist/server/jobs.js +186 -1
- package/dist/server/jobs.js.map +1 -1
- package/dist/server/router.d.ts +772 -0
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +462 -1
- package/dist/server/router.js.map +1 -1
- package/dist/server/schema.d.ts +612 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +76 -0
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/policies.d.ts +53 -0
- package/dist/server/services/policies.d.ts.map +1 -0
- package/dist/server/services/policies.js +158 -0
- package/dist/server/services/policies.js.map +1 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/contract/capabilities.ts +31 -0
- package/src/contract/index.ts +1 -0
- package/src/contract/permissions.ts +28 -0
- package/src/contract/router.ts +148 -0
package/src/contract/router.ts
CHANGED
|
@@ -53,6 +53,15 @@ import {
|
|
|
53
53
|
TimeZone,
|
|
54
54
|
WorkingWeek,
|
|
55
55
|
} from './models.js'
|
|
56
|
+
import {
|
|
57
|
+
AccrualPreview,
|
|
58
|
+
Period,
|
|
59
|
+
Policy,
|
|
60
|
+
PolicyAssignment,
|
|
61
|
+
PolicyKind,
|
|
62
|
+
PolicySubjectKind,
|
|
63
|
+
ResolvedPolicy,
|
|
64
|
+
} from './policies.js'
|
|
56
65
|
|
|
57
66
|
const ws = z.object({ workspaceId: WorkspaceId })
|
|
58
67
|
const t = ['hr'] as const
|
|
@@ -602,6 +611,145 @@ export const hrContract = {
|
|
|
602
611
|
.output(ok),
|
|
603
612
|
},
|
|
604
613
|
|
|
614
|
+
// ---------------------------------------------------------------- policies
|
|
615
|
+
policies: {
|
|
616
|
+
list: baseContract
|
|
617
|
+
.route({ method: 'GET', path: '/policies', tags: t })
|
|
618
|
+
.input(ws.extend({ kind: PolicyKind.optional(), includeArchived: z.boolean().default(false) }))
|
|
619
|
+
.output(z.array(Policy.extend({ assignments: z.array(PolicyAssignment) }))),
|
|
620
|
+
get: baseContract
|
|
621
|
+
.route({ method: 'GET', path: '/policies/{policyId}', tags: t })
|
|
622
|
+
.input(ws.extend({ policyId: z.uuid() }))
|
|
623
|
+
.output(Policy.extend({ assignments: z.array(PolicyAssignment) })),
|
|
624
|
+
create: baseContract
|
|
625
|
+
.route({ method: 'POST', path: '/policies', tags: t })
|
|
626
|
+
.input(
|
|
627
|
+
ws.extend({
|
|
628
|
+
kind: PolicyKind,
|
|
629
|
+
name: z.string().min(1).max(120),
|
|
630
|
+
/** Validated against the schema for `kind` — a config for the wrong kind is refused. */
|
|
631
|
+
config: z.record(z.string(), z.unknown()),
|
|
632
|
+
effectiveFrom: IsoDate,
|
|
633
|
+
effectiveTo: IsoDate.nullish(),
|
|
634
|
+
}),
|
|
635
|
+
)
|
|
636
|
+
.output(Policy),
|
|
637
|
+
/**
|
|
638
|
+
* Edits a policy in place.
|
|
639
|
+
*
|
|
640
|
+
* A change that should apply from a date rather than retroactively is a **new** policy with a
|
|
641
|
+
* later `effectiveFrom`, not an edit — editing rewrites what was true in the past, and anything
|
|
642
|
+
* already derived from it becomes unexplainable.
|
|
643
|
+
*/
|
|
644
|
+
update: baseContract
|
|
645
|
+
.route({ method: 'PATCH', path: '/policies/{policyId}', tags: t })
|
|
646
|
+
.input(
|
|
647
|
+
ws.extend({
|
|
648
|
+
policyId: z.uuid(),
|
|
649
|
+
name: z.string().min(1).max(120).optional(),
|
|
650
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
651
|
+
effectiveTo: IsoDate.nullish(),
|
|
652
|
+
}),
|
|
653
|
+
)
|
|
654
|
+
.output(Policy),
|
|
655
|
+
archive: baseContract
|
|
656
|
+
.route({ method: 'DELETE', path: '/policies/{policyId}', tags: t })
|
|
657
|
+
.input(ws.extend({ policyId: z.uuid() }))
|
|
658
|
+
.output(ok),
|
|
659
|
+
assign: baseContract
|
|
660
|
+
.route({ method: 'POST', path: '/policies/{policyId}/assign', tags: t })
|
|
661
|
+
.input(
|
|
662
|
+
ws.extend({
|
|
663
|
+
policyId: z.uuid(),
|
|
664
|
+
subjectKind: PolicySubjectKind,
|
|
665
|
+
/** Null for `workspace`, which needs no id. */
|
|
666
|
+
subjectId: z.uuid().nullish(),
|
|
667
|
+
effectiveFrom: IsoDate,
|
|
668
|
+
effectiveTo: IsoDate.nullish(),
|
|
669
|
+
}),
|
|
670
|
+
)
|
|
671
|
+
.output(PolicyAssignment),
|
|
672
|
+
unassign: baseContract
|
|
673
|
+
.route({ method: 'DELETE', path: '/policies/assignments/{assignmentId}', tags: t })
|
|
674
|
+
.input(ws.extend({ assignmentId: z.uuid() }))
|
|
675
|
+
.output(ok),
|
|
676
|
+
/**
|
|
677
|
+
* Which policy of each kind applies to this person on this date, and which rung answered.
|
|
678
|
+
*
|
|
679
|
+
* "Why does she accrue differently from her team" is the question this module gets asked, and
|
|
680
|
+
* this is what answers it without a database session.
|
|
681
|
+
*/
|
|
682
|
+
resolveFor: baseContract
|
|
683
|
+
.route({ method: 'GET', path: '/people/{personId}/policies', tags: t })
|
|
684
|
+
.input(ws.extend({ personId: z.uuid(), on: IsoDate.optional() }))
|
|
685
|
+
.output(z.array(ResolvedPolicy)),
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
// ---------------------------------------------------------------- accrual
|
|
689
|
+
accrual: {
|
|
690
|
+
/**
|
|
691
|
+
* What a run would credit, per person, before it writes anything.
|
|
692
|
+
*
|
|
693
|
+
* Runs the same code the run does. A preview computed differently from the thing it previews is
|
|
694
|
+
* a preview that eventually lies.
|
|
695
|
+
*/
|
|
696
|
+
preview: baseContract
|
|
697
|
+
.route({ method: 'POST', path: '/accrual/preview', tags: t })
|
|
698
|
+
.input(ws.extend({ from: IsoDate, to: IsoDate, personId: z.uuid().optional() }))
|
|
699
|
+
.output(AccrualPreview),
|
|
700
|
+
/**
|
|
701
|
+
* Credits the ledger.
|
|
702
|
+
*
|
|
703
|
+
* Idempotent per person, per leave type, per period: a second run for the same window credits
|
|
704
|
+
* nothing, because an accrual job that double-credits when somebody clicks twice is worse than
|
|
705
|
+
* one that never ran.
|
|
706
|
+
*/
|
|
707
|
+
run: baseContract
|
|
708
|
+
.route({ method: 'POST', path: '/accrual/run', tags: t })
|
|
709
|
+
.input(ws.extend({ from: IsoDate, to: IsoDate, personId: z.uuid().optional() }))
|
|
710
|
+
.output(
|
|
711
|
+
z.object({
|
|
712
|
+
credited: z.number().int(),
|
|
713
|
+
skipped: z.number().int(),
|
|
714
|
+
totalMinutes: z.number().int(),
|
|
715
|
+
}),
|
|
716
|
+
),
|
|
717
|
+
},
|
|
718
|
+
|
|
719
|
+
// ---------------------------------------------------------------- periods
|
|
720
|
+
periods: {
|
|
721
|
+
list: baseContract
|
|
722
|
+
.route({ method: 'GET', path: '/periods', tags: t })
|
|
723
|
+
.input(ws.extend({ kind: Period.shape.kind.optional(), ...PageInput.shape }))
|
|
724
|
+
.output(page(Period)),
|
|
725
|
+
create: baseContract
|
|
726
|
+
.route({ method: 'POST', path: '/periods', tags: t })
|
|
727
|
+
.input(
|
|
728
|
+
ws.extend({
|
|
729
|
+
kind: Period.shape.kind.default('payroll'),
|
|
730
|
+
legalEntityId: z.uuid().nullish(),
|
|
731
|
+
startsOn: IsoDate,
|
|
732
|
+
endsOn: IsoDate,
|
|
733
|
+
}),
|
|
734
|
+
)
|
|
735
|
+
.output(Period),
|
|
736
|
+
/** Freezes the month. Every derived day inside it stops being recomputable. */
|
|
737
|
+
lock: baseContract
|
|
738
|
+
.route({ method: 'POST', path: '/periods/{periodId}/lock', tags: t })
|
|
739
|
+
.input(ws.extend({ periodId: z.uuid(), note: z.string().max(500).nullish() }))
|
|
740
|
+
.output(Period.extend({ lockedDays: z.number().int() })),
|
|
741
|
+
/**
|
|
742
|
+
* Reopens it.
|
|
743
|
+
*
|
|
744
|
+
* Dangerous on purpose: a payroll has usually been filed against a locked month, and reopening
|
|
745
|
+
* lets figures move underneath it. The response says how many days became recomputable again.
|
|
746
|
+
*/
|
|
747
|
+
unlock: baseContract
|
|
748
|
+
.route({ method: 'POST', path: '/periods/{periodId}/unlock', tags: t })
|
|
749
|
+
.input(ws.extend({ periodId: z.uuid(), reason: z.string().min(1).max(500) }))
|
|
750
|
+
.output(Period.extend({ unlockedDays: z.number().int() })),
|
|
751
|
+
},
|
|
752
|
+
|
|
605
753
|
// ---------------------------------------------------------------- attendance
|
|
606
754
|
attendance: {
|
|
607
755
|
/** Am I clocked in? The one call a clock widget makes. */
|