@happyvertical/smrt-timesheets 0.57.0 → 0.57.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/AGENTS.md +9 -3
- package/README.md +34 -5
- package/agents/period-rollup.md +13 -0
- package/dist/attendance/correction-service.d.ts +50 -9
- package/dist/attendance/correction-service.d.ts.map +1 -1
- package/dist/attendance/models.d.ts +6 -0
- package/dist/attendance/models.d.ts.map +1 -1
- package/dist/attendance/segments.d.ts.map +1 -1
- package/dist/attendance/service.d.ts +7 -1
- package/dist/attendance/service.d.ts.map +1 -1
- package/dist/attendance.js +2 -2
- package/dist/chunks/{attendance-CfIgcl26.js → attendance-DAitZkht.js} +6 -6
- package/dist/chunks/attendance-DAitZkht.js.map +1 -0
- package/dist/chunks/{attendance-Cmk-6dTV.js → attendance-UY7sTXFx.js} +192 -38
- package/dist/chunks/attendance-UY7sTXFx.js.map +1 -0
- package/dist/chunks/{models-BCrk0kZQ.js → models-BJcnFMOL.js} +14 -3
- package/dist/chunks/models-BJcnFMOL.js.map +1 -0
- package/dist/chunks/{rollup-CII9Hl1R.js → rollup-B-qdcGDs.js} +15 -7
- package/dist/chunks/rollup-B-qdcGDs.js.map +1 -0
- package/dist/chunks/service-time-entry-CWAVYIID.js +286 -0
- package/dist/chunks/service-time-entry-CWAVYIID.js.map +1 -0
- package/dist/chunks/{time-entry-actions-Lb8OYIS8.js → time-entry-actions-BgaZCLL4.js} +2 -2
- package/dist/chunks/{time-entry-actions-Lb8OYIS8.js.map → time-entry-actions-BgaZCLL4.js.map} +1 -1
- package/dist/index.js +5 -5
- package/dist/manifest.json +58 -5
- package/dist/rollup/attendance.d.ts +2 -2
- package/dist/rollup/attendance.d.ts.map +1 -1
- package/dist/rollup/calculation.d.ts +7 -1
- package/dist/rollup/calculation.d.ts.map +1 -1
- package/dist/rollup/service.d.ts.map +1 -1
- package/dist/rollup/types.d.ts +10 -0
- package/dist/rollup/types.d.ts.map +1 -1
- package/dist/rollup.d.ts +1 -1
- package/dist/rollup.d.ts.map +1 -1
- package/dist/rollup.js +1 -1
- package/dist/smrt-knowledge.json +25 -10
- package/package.json +7 -7
- package/dist/chunks/attendance-CfIgcl26.js.map +0 -1
- package/dist/chunks/attendance-Cmk-6dTV.js.map +0 -1
- package/dist/chunks/models-BCrk0kZQ.js.map +0 -1
- package/dist/chunks/rollup-CII9Hl1R.js.map +0 -1
- package/dist/chunks/service-time-entry-DzQsRZc_.js +0 -286
- package/dist/chunks/service-time-entry-DzQsRZc_.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as ServiceTimeEntryCollection, o as parseTimesheetJson } from "./service-time-entry-
|
|
2
|
-
import { c as AttendanceSegmentCollection, d as persistAutoClose, f as persistCorrection, i as AttendancePunchCollection, l as deleteCorrection, n as AttendanceBreakCollection, o as AttendanceReplayCollection, r as AttendancePunch, s as AttendanceSegment, t as AttendanceBreak, u as isCorrectionWrite } from "./models-
|
|
1
|
+
import { i as ServiceTimeEntryCollection, o as parseTimesheetJson } from "./service-time-entry-CWAVYIID.js";
|
|
2
|
+
import { c as AttendanceSegmentCollection, d as persistAutoClose, f as persistCorrection, i as AttendancePunchCollection, l as deleteCorrection, n as AttendanceBreakCollection, o as AttendanceReplayCollection, r as AttendancePunch, s as AttendanceSegment, t as AttendanceBreak, u as isCorrectionWrite } from "./models-BJcnFMOL.js";
|
|
3
3
|
import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, isEmbeddedDatabase, isPostgresDatabase, smrt, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
|
|
4
4
|
import { TenantScoped, getTenantId, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
|
|
5
5
|
//#region src/attendance/correction-models.ts
|
|
@@ -199,6 +199,7 @@ async function beginSegment(tx, punch, at, work) {
|
|
|
199
199
|
return tx.segments.create({
|
|
200
200
|
tenantId: punch.tenantId,
|
|
201
201
|
punchId: punch.id ?? void 0,
|
|
202
|
+
profileId: punch.profileId,
|
|
202
203
|
workRefType: work.workRefType,
|
|
203
204
|
workRefId: work.workRefId,
|
|
204
205
|
taskRefType: work.taskRefType ?? "",
|
|
@@ -713,6 +714,8 @@ var AttendanceService = class {
|
|
|
713
714
|
const deviceAt = new Date(tap.at);
|
|
714
715
|
let at = deviceAt;
|
|
715
716
|
let review = false;
|
|
717
|
+
let punchReview = true;
|
|
718
|
+
let offline = true;
|
|
716
719
|
if (options.resolveAt) {
|
|
717
720
|
const serverNow = this.date(options.now?.() ?? /* @__PURE__ */ new Date());
|
|
718
721
|
const resolved = await options.resolveAt({
|
|
@@ -725,6 +728,8 @@ var AttendanceService = class {
|
|
|
725
728
|
if (!resolved || !(resolved.at instanceof Date) || !Number.isFinite(resolved.at.getTime())) throw new Error("resolveAt must return a valid date.");
|
|
726
729
|
at = resolved.at;
|
|
727
730
|
review = resolved.reviewRequired === true;
|
|
731
|
+
punchReview = resolved.reviewRequired !== false;
|
|
732
|
+
offline = resolved.recordedOffline !== false;
|
|
728
733
|
}
|
|
729
734
|
let outcome;
|
|
730
735
|
try {
|
|
@@ -737,8 +742,8 @@ var AttendanceService = class {
|
|
|
737
742
|
replays: tx.replays
|
|
738
743
|
};
|
|
739
744
|
const { punch, segment } = await this.apply(inner, tap, at);
|
|
740
|
-
punch.recordedOffline
|
|
741
|
-
punch.reviewRequired
|
|
745
|
+
punch.recordedOffline ||= offline;
|
|
746
|
+
punch.reviewRequired ||= punchReview;
|
|
742
747
|
await punch.save();
|
|
743
748
|
if (segment && review) {
|
|
744
749
|
segment.reviewRequired = true;
|
|
@@ -774,6 +779,7 @@ var AttendanceService = class {
|
|
|
774
779
|
};
|
|
775
780
|
//#endregion
|
|
776
781
|
//#region src/attendance/correction-service.ts
|
|
782
|
+
var FOREVER = /* @__PURE__ */ new Date(864e13);
|
|
777
783
|
var iso = (d) => d ? d.toISOString() : null;
|
|
778
784
|
var punchState = (p) => ({
|
|
779
785
|
startedAt: iso(p.startedAt),
|
|
@@ -837,11 +843,16 @@ var AttendanceCorrectionService = class {
|
|
|
837
843
|
db;
|
|
838
844
|
options;
|
|
839
845
|
actor;
|
|
840
|
-
/**
|
|
846
|
+
/**
|
|
847
|
+
* Change a closed punch's start and/or end. A reviewed punch no longer needs
|
|
848
|
+
* review. An open punch may change `startedAt` only: it cannot be closed
|
|
849
|
+
* here, and its start cannot move past its earliest break or segment.
|
|
850
|
+
*/
|
|
841
851
|
async correctPunch(input) {
|
|
842
852
|
return this.run(input, async (tx, subject, record) => {
|
|
843
|
-
const punch = await this.
|
|
853
|
+
const punch = await this.anyPunch(tx, subject, input.punchId);
|
|
844
854
|
const before = punchState(punch);
|
|
855
|
+
if (!punch.endedAt) return this.correctOpenStart(tx, subject, record, punch, input, before);
|
|
845
856
|
const startedAt = input.startedAt === void 0 ? punch.startedAt : instant(input.startedAt, "startedAt");
|
|
846
857
|
const endedAt = input.endedAt === void 0 ? punch.endedAt : instant(input.endedAt, "endedAt");
|
|
847
858
|
if (endedAt <= startedAt) throw new AttendanceConflict("A punch must end after it starts.");
|
|
@@ -872,8 +883,13 @@ var AttendanceCorrectionService = class {
|
|
|
872
883
|
return record("punch.corrected", punch, punch.id, before, punchState(punch));
|
|
873
884
|
});
|
|
874
885
|
}
|
|
875
|
-
/**
|
|
886
|
+
/**
|
|
887
|
+
* Record a missed punch retroactively: a closed interval, or with
|
|
888
|
+
* `{ open: true }` one that is still running, carrying its closed breaks and
|
|
889
|
+
* segments.
|
|
890
|
+
*/
|
|
876
891
|
async addPunch(input) {
|
|
892
|
+
if (input.open === true) return this.addOpenPunch(input);
|
|
877
893
|
return this.run(input, async (tx, subject, record) => {
|
|
878
894
|
const startedAt = instant(input.startedAt, "startedAt");
|
|
879
895
|
const endedAt = instant(input.endedAt, "endedAt");
|
|
@@ -905,13 +921,14 @@ var AttendanceCorrectionService = class {
|
|
|
905
921
|
return record("punch.added", punch, punch.id, null, punchState(punch));
|
|
906
922
|
});
|
|
907
923
|
}
|
|
908
|
-
/** Add a break to a
|
|
924
|
+
/** Add a closed break to a punch, or change one (times and/or paid). */
|
|
909
925
|
async saveBreak(input) {
|
|
910
926
|
return this.run(input, async (tx, subject, record) => {
|
|
911
|
-
const punch = await this.
|
|
927
|
+
const punch = await this.anyPunch(tx, subject, input.punchId);
|
|
912
928
|
const children = await this.children(tx, punch);
|
|
913
929
|
const existing = input.breakId ? children.breaks.find((b) => b.id === input.breakId) : void 0;
|
|
914
930
|
if (input.breakId && !existing) throw new AttendanceConflict("Break not found in that punch.");
|
|
931
|
+
if (existing && !existing.endedAt) throw new AttendanceConflict("The open break is changed through AttendanceService.");
|
|
915
932
|
if (!existing && (input.startedAt === void 0 || input.endedAt === void 0)) throw new AttendanceConflict("A new break needs startedAt and endedAt.");
|
|
916
933
|
if (input.paid !== void 0 && typeof input.paid !== "boolean") throw new AttendanceConflict("paid must be boolean.");
|
|
917
934
|
const next = {
|
|
@@ -925,10 +942,7 @@ var AttendanceCorrectionService = class {
|
|
|
925
942
|
breaks: [...rest, next],
|
|
926
943
|
segments: children.segments
|
|
927
944
|
}));
|
|
928
|
-
await this.guard(tx, subject, [
|
|
929
|
-
startsAt: punch.startedAt,
|
|
930
|
-
endsAt: punch.endedAt
|
|
931
|
-
}]);
|
|
945
|
+
await this.guard(tx, subject, [this.span(punch)]);
|
|
932
946
|
const entry = await this.editableEntry(tx, punch);
|
|
933
947
|
const before = existing ? breakState(existing) : null;
|
|
934
948
|
let target;
|
|
@@ -956,7 +970,7 @@ var AttendanceCorrectionService = class {
|
|
|
956
970
|
return record(existing ? "break.changed" : "break.added", punch, target.id, before, breakState(target));
|
|
957
971
|
});
|
|
958
972
|
}
|
|
959
|
-
/** Remove a break from a
|
|
973
|
+
/** Remove a closed break from a punch. */
|
|
960
974
|
async removeBreak(input) {
|
|
961
975
|
return this.run(input, async (tx, subject, record) => {
|
|
962
976
|
const [target] = await tx.breaks.list({
|
|
@@ -967,12 +981,10 @@ var AttendanceCorrectionService = class {
|
|
|
967
981
|
limit: 1
|
|
968
982
|
});
|
|
969
983
|
if (!target) throw new AttendanceConflict("Break not found.");
|
|
970
|
-
|
|
984
|
+
if (!target.endedAt) throw new AttendanceConflict("The open break is ended through AttendanceService.");
|
|
985
|
+
const punch = await this.anyPunch(tx, subject, target.punchId);
|
|
971
986
|
const children = await this.children(tx, punch);
|
|
972
|
-
await this.guard(tx, subject, [
|
|
973
|
-
startsAt: punch.startedAt,
|
|
974
|
-
endsAt: punch.endedAt
|
|
975
|
-
}]);
|
|
987
|
+
await this.guard(tx, subject, [this.span(punch)]);
|
|
976
988
|
const entry = await this.editableEntry(tx, punch);
|
|
977
989
|
const before = breakState(target);
|
|
978
990
|
await deleteCorrection(target);
|
|
@@ -980,13 +992,14 @@ var AttendanceCorrectionService = class {
|
|
|
980
992
|
return record("break.removed", punch, target.id, before, null);
|
|
981
993
|
});
|
|
982
994
|
}
|
|
983
|
-
/** Add a work segment to a
|
|
995
|
+
/** Add a closed work segment to a punch, or change one (times and/or work attribution). */
|
|
984
996
|
async saveSegment(input) {
|
|
985
997
|
return this.run(input, async (tx, subject, record) => {
|
|
986
|
-
const punch = await this.
|
|
998
|
+
const punch = await this.anyPunch(tx, subject, input.punchId);
|
|
987
999
|
const children = await this.children(tx, punch);
|
|
988
1000
|
const existing = input.segmentId ? children.segments.find((s) => s.id === input.segmentId) : void 0;
|
|
989
1001
|
if (input.segmentId && !existing) throw new AttendanceConflict("Work segment not found in that punch.");
|
|
1002
|
+
if (existing && !existing.endedAt) throw new AttendanceConflict("The open work segment is changed through AttendanceService.");
|
|
990
1003
|
if (!existing && (input.startedAt === void 0 || input.endedAt === void 0 || !input.work)) throw new AttendanceConflict("A new work segment needs startedAt, endedAt and work.");
|
|
991
1004
|
if (input.work) validateAttendanceWork(input.work);
|
|
992
1005
|
const next = {
|
|
@@ -998,10 +1011,7 @@ var AttendanceCorrectionService = class {
|
|
|
998
1011
|
breaks: children.breaks,
|
|
999
1012
|
segments: [...rest, next]
|
|
1000
1013
|
}));
|
|
1001
|
-
await this.guard(tx, subject, [
|
|
1002
|
-
startsAt: punch.startedAt,
|
|
1003
|
-
endsAt: punch.endedAt
|
|
1004
|
-
}]);
|
|
1014
|
+
await this.guard(tx, subject, [this.span(punch)]);
|
|
1005
1015
|
const before = existing ? segmentState(existing) : null;
|
|
1006
1016
|
let target;
|
|
1007
1017
|
if (existing) {
|
|
@@ -1019,6 +1029,7 @@ var AttendanceCorrectionService = class {
|
|
|
1019
1029
|
Object.assign(target, {
|
|
1020
1030
|
tenantId: subject.tenantId,
|
|
1021
1031
|
punchId: punch.id ?? void 0,
|
|
1032
|
+
profileId: punch.profileId,
|
|
1022
1033
|
workRefType: input.work.workRefType,
|
|
1023
1034
|
workRefId: input.work.workRefId,
|
|
1024
1035
|
taskRefType: input.work.taskRefType ?? "",
|
|
@@ -1033,7 +1044,7 @@ var AttendanceCorrectionService = class {
|
|
|
1033
1044
|
return record(existing ? "segment.changed" : "segment.added", punch, target.id, before, segmentState(target));
|
|
1034
1045
|
});
|
|
1035
1046
|
}
|
|
1036
|
-
/** Remove a work segment from a
|
|
1047
|
+
/** Remove a closed work segment from a punch. */
|
|
1037
1048
|
async removeSegment(input) {
|
|
1038
1049
|
return this.run(input, async (tx, subject, record) => {
|
|
1039
1050
|
const [target] = await tx.segments.list({
|
|
@@ -1044,11 +1055,9 @@ var AttendanceCorrectionService = class {
|
|
|
1044
1055
|
limit: 1
|
|
1045
1056
|
});
|
|
1046
1057
|
if (!target) throw new AttendanceConflict("Work segment not found.");
|
|
1047
|
-
|
|
1048
|
-
await this.
|
|
1049
|
-
|
|
1050
|
-
endsAt: punch.endedAt
|
|
1051
|
-
}]);
|
|
1058
|
+
if (!target.endedAt) throw new AttendanceConflict("The open work segment is stopped through AttendanceService.");
|
|
1059
|
+
const punch = await this.anyPunch(tx, subject, target.punchId);
|
|
1060
|
+
await this.guard(tx, subject, [this.span(punch)]);
|
|
1052
1061
|
const before = segmentState(target);
|
|
1053
1062
|
await deleteCorrection(target);
|
|
1054
1063
|
return record("segment.removed", punch, target.id, before, null);
|
|
@@ -1099,6 +1108,148 @@ var AttendanceCorrectionService = class {
|
|
|
1099
1108
|
orderBy: "created_at ASC"
|
|
1100
1109
|
})).filter((row) => row.acknowledgementRequired && !row.acknowledgedAt));
|
|
1101
1110
|
}
|
|
1111
|
+
/** The range a correction touches, for period-lock checks: an open punch runs to now. */
|
|
1112
|
+
span(punch) {
|
|
1113
|
+
const now = /* @__PURE__ */ new Date();
|
|
1114
|
+
return {
|
|
1115
|
+
startsAt: punch.startedAt,
|
|
1116
|
+
endsAt: punch.endedAt ?? (now > punch.startedAt ? now : punch.startedAt)
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
async openPunchOf(tx, subject) {
|
|
1120
|
+
return (await tx.punches.list({
|
|
1121
|
+
where: {
|
|
1122
|
+
tenantId: subject.tenantId,
|
|
1123
|
+
profileId: subject.profileId,
|
|
1124
|
+
endedAt: null
|
|
1125
|
+
},
|
|
1126
|
+
limit: 1
|
|
1127
|
+
})).length > 0;
|
|
1128
|
+
}
|
|
1129
|
+
async correctOpenStart(tx, subject, record, punch, input, before) {
|
|
1130
|
+
if (input.endedAt !== void 0) throw new AttendanceConflict("An open punch cannot be closed by a correction; use AttendanceService.");
|
|
1131
|
+
if (input.startedAt === void 0) throw new AttendanceConflict("An open punch is corrected by startedAt.");
|
|
1132
|
+
const startedAt = instant(input.startedAt, "startedAt");
|
|
1133
|
+
const children = await this.children(tx, punch);
|
|
1134
|
+
validateLayout(this.layout(startedAt, null, children));
|
|
1135
|
+
for (const other of await tx.punches.list({ where: {
|
|
1136
|
+
tenantId: subject.tenantId,
|
|
1137
|
+
profileId: subject.profileId
|
|
1138
|
+
} })) if (other.id !== punch.id && overlapping({
|
|
1139
|
+
start: startedAt.getTime(),
|
|
1140
|
+
end: FOREVER.getTime()
|
|
1141
|
+
}, {
|
|
1142
|
+
start: other.startedAt.getTime(),
|
|
1143
|
+
end: other.endedAt?.getTime() ?? FOREVER.getTime()
|
|
1144
|
+
})) throw new AttendanceConflict("Punches must not overlap.");
|
|
1145
|
+
const earliest = punch.startedAt < startedAt ? punch.startedAt : startedAt;
|
|
1146
|
+
await this.guard(tx, subject, [{
|
|
1147
|
+
startsAt: earliest,
|
|
1148
|
+
endsAt: this.span(punch).endsAt
|
|
1149
|
+
}]);
|
|
1150
|
+
punch.startedAt = startedAt;
|
|
1151
|
+
punch.reviewRequired = false;
|
|
1152
|
+
await this.settle(tx, punch, children.breaks, null);
|
|
1153
|
+
return record("punch.corrected", punch, punch.id, before, punchState(punch));
|
|
1154
|
+
}
|
|
1155
|
+
async addOpenPunch(input) {
|
|
1156
|
+
return this.run(input, async (tx, subject, record) => {
|
|
1157
|
+
const startedAt = instant(input.startedAt, "startedAt");
|
|
1158
|
+
const meta = attendanceProvenance(input);
|
|
1159
|
+
if (input.endedAt !== void 0) throw new AttendanceConflict("An open punch has no endedAt.");
|
|
1160
|
+
if (await this.openPunchOf(tx, subject)) throw new AttendanceConflict("A punch is already open.", "punch_open");
|
|
1161
|
+
const breaks = (input.breaks ?? []).map((b) => ({
|
|
1162
|
+
startedAt: instant(b.startedAt, "startedAt"),
|
|
1163
|
+
endedAt: instant(b.endedAt, "endedAt"),
|
|
1164
|
+
paid: b.paid ?? false,
|
|
1165
|
+
kind: attendanceLabel(b.kind, "kind")
|
|
1166
|
+
}));
|
|
1167
|
+
for (const b of input.breaks ?? []) if (b.paid !== void 0 && typeof b.paid !== "boolean") throw new AttendanceConflict("paid must be boolean.");
|
|
1168
|
+
const segments = (input.segments ?? []).map((g) => {
|
|
1169
|
+
validateAttendanceWork(g.work);
|
|
1170
|
+
return {
|
|
1171
|
+
startedAt: instant(g.startedAt, "startedAt"),
|
|
1172
|
+
endedAt: instant(g.endedAt, "endedAt"),
|
|
1173
|
+
work: g.work
|
|
1174
|
+
};
|
|
1175
|
+
});
|
|
1176
|
+
validateLayout({
|
|
1177
|
+
startsAt: startedAt.getTime(),
|
|
1178
|
+
endsAt: FOREVER.getTime(),
|
|
1179
|
+
breaks,
|
|
1180
|
+
segments
|
|
1181
|
+
});
|
|
1182
|
+
for (const other of await tx.punches.list({ where: {
|
|
1183
|
+
tenantId: subject.tenantId,
|
|
1184
|
+
profileId: subject.profileId
|
|
1185
|
+
} })) if (overlapping({
|
|
1186
|
+
start: startedAt.getTime(),
|
|
1187
|
+
end: FOREVER.getTime()
|
|
1188
|
+
}, {
|
|
1189
|
+
start: other.startedAt.getTime(),
|
|
1190
|
+
end: other.endedAt?.getTime() ?? FOREVER.getTime()
|
|
1191
|
+
})) throw new AttendanceConflict("Punches must not overlap.");
|
|
1192
|
+
const now = /* @__PURE__ */ new Date();
|
|
1193
|
+
await this.guard(tx, subject, [{
|
|
1194
|
+
startsAt: startedAt,
|
|
1195
|
+
endsAt: now > startedAt ? now : startedAt
|
|
1196
|
+
}]);
|
|
1197
|
+
const punch = await tx.punches.create({
|
|
1198
|
+
tenantId: subject.tenantId,
|
|
1199
|
+
profileId: subject.profileId,
|
|
1200
|
+
startedAt,
|
|
1201
|
+
...meta,
|
|
1202
|
+
_insertOnly: true
|
|
1203
|
+
});
|
|
1204
|
+
for (const b of breaks) {
|
|
1205
|
+
const row = await new AttendanceBreak({
|
|
1206
|
+
db: tx.db,
|
|
1207
|
+
id: crypto.randomUUID(),
|
|
1208
|
+
_skipLoad: true
|
|
1209
|
+
}).initialize();
|
|
1210
|
+
Object.assign(row, {
|
|
1211
|
+
tenantId: subject.tenantId,
|
|
1212
|
+
punchId: punch.id ?? void 0,
|
|
1213
|
+
...b
|
|
1214
|
+
});
|
|
1215
|
+
await persistCorrection(row);
|
|
1216
|
+
}
|
|
1217
|
+
const segmentStates = [];
|
|
1218
|
+
for (const g of segments) {
|
|
1219
|
+
const row = await new AttendanceSegment({
|
|
1220
|
+
db: tx.db,
|
|
1221
|
+
id: crypto.randomUUID(),
|
|
1222
|
+
_skipLoad: true
|
|
1223
|
+
}).initialize();
|
|
1224
|
+
Object.assign(row, {
|
|
1225
|
+
tenantId: subject.tenantId,
|
|
1226
|
+
punchId: punch.id ?? void 0,
|
|
1227
|
+
profileId: punch.profileId,
|
|
1228
|
+
workRefType: g.work.workRefType,
|
|
1229
|
+
workRefId: g.work.workRefId,
|
|
1230
|
+
taskRefType: g.work.taskRefType ?? "",
|
|
1231
|
+
taskRefId: g.work.taskRefId ?? "",
|
|
1232
|
+
kind: g.work.kind ?? "",
|
|
1233
|
+
description: g.work.description,
|
|
1234
|
+
startedAt: g.startedAt,
|
|
1235
|
+
endedAt: g.endedAt,
|
|
1236
|
+
closedReason: "stop"
|
|
1237
|
+
});
|
|
1238
|
+
await persistCorrection(row);
|
|
1239
|
+
segmentStates.push(segmentState(row));
|
|
1240
|
+
}
|
|
1241
|
+
return record("punch.added", punch, punch.id, null, {
|
|
1242
|
+
...punchState(punch),
|
|
1243
|
+
...breaks.length ? { breaks: breaks.map((b) => ({
|
|
1244
|
+
startedAt: iso(b.startedAt),
|
|
1245
|
+
endedAt: iso(b.endedAt),
|
|
1246
|
+
paid: b.paid,
|
|
1247
|
+
...b.kind ? { kind: b.kind } : {}
|
|
1248
|
+
})) } : {},
|
|
1249
|
+
...segmentStates.length ? { segments: segmentStates } : {}
|
|
1250
|
+
});
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1102
1253
|
attendance(subjectProfileId) {
|
|
1103
1254
|
return new AttendanceService(this.db, {
|
|
1104
1255
|
tenantId: this.actor.tenantId,
|
|
@@ -1153,7 +1304,7 @@ var AttendanceCorrectionService = class {
|
|
|
1153
1304
|
});
|
|
1154
1305
|
return this.read(id);
|
|
1155
1306
|
}
|
|
1156
|
-
async
|
|
1307
|
+
async anyPunch(tx, subject, punchId) {
|
|
1157
1308
|
const [punch] = await tx.punches.list({
|
|
1158
1309
|
where: {
|
|
1159
1310
|
tenantId: subject.tenantId,
|
|
@@ -1163,7 +1314,6 @@ var AttendanceCorrectionService = class {
|
|
|
1163
1314
|
limit: 1
|
|
1164
1315
|
});
|
|
1165
1316
|
if (!punch) throw new AttendanceConflict("Punch not found for subject.");
|
|
1166
|
-
if (!punch.endedAt) throw new AttendanceConflict("Only closed punches are corrected; use AttendanceService for an open one.");
|
|
1167
1317
|
return punch;
|
|
1168
1318
|
}
|
|
1169
1319
|
async children(tx, punch) {
|
|
@@ -1179,7 +1329,7 @@ var AttendanceCorrectionService = class {
|
|
|
1179
1329
|
where,
|
|
1180
1330
|
orderBy: "started_at ASC"
|
|
1181
1331
|
});
|
|
1182
|
-
if (breaks.some((b) => !b.endedAt) || segments.some((s) => !s.endedAt)) throw new AttendanceConflict("Closed punch has an open break or segment.");
|
|
1332
|
+
if (punch.endedAt && (breaks.some((b) => !b.endedAt) || segments.some((s) => !s.endedAt))) throw new AttendanceConflict("Closed punch has an open break or segment.");
|
|
1183
1333
|
return {
|
|
1184
1334
|
breaks,
|
|
1185
1335
|
segments
|
|
@@ -1188,15 +1338,15 @@ var AttendanceCorrectionService = class {
|
|
|
1188
1338
|
layout(startedAt, endedAt, children) {
|
|
1189
1339
|
return {
|
|
1190
1340
|
startsAt: startedAt.getTime(),
|
|
1191
|
-
endsAt: endedAt.getTime(),
|
|
1341
|
+
endsAt: endedAt ? endedAt.getTime() : FOREVER.getTime(),
|
|
1192
1342
|
breaks: children.breaks.map((b) => ({
|
|
1193
1343
|
startedAt: b.startedAt,
|
|
1194
|
-
endedAt: b.endedAt,
|
|
1344
|
+
endedAt: b.endedAt ?? FOREVER,
|
|
1195
1345
|
paid: b.paid
|
|
1196
1346
|
})),
|
|
1197
1347
|
segments: children.segments.map((s) => ({
|
|
1198
1348
|
startedAt: s.startedAt,
|
|
1199
|
-
endedAt: s.endedAt
|
|
1349
|
+
endedAt: s.endedAt ?? FOREVER
|
|
1200
1350
|
}))
|
|
1201
1351
|
};
|
|
1202
1352
|
}
|
|
@@ -1228,6 +1378,10 @@ var AttendanceCorrectionService = class {
|
|
|
1228
1378
|
}
|
|
1229
1379
|
/** Recompute the punch's durations from its breaks and persist it and its draft entry. */
|
|
1230
1380
|
async settle(tx, punch, breaks, entry) {
|
|
1381
|
+
if (!punch.endedAt) {
|
|
1382
|
+
await persistCorrection(punch);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1231
1385
|
const unpaidMs = breaks.filter((b) => !b.paid).reduce((sum, b) => sum + b.endedAt.getTime() - b.startedAt.getTime(), 0);
|
|
1232
1386
|
punch.unpaidBreakSeconds = Math.round(unpaidMs / 1e3);
|
|
1233
1387
|
punch.durationSeconds = Math.round((punch.endedAt.getTime() - punch.startedAt.getTime() - unpaidMs) / 1e3);
|
|
@@ -1243,4 +1397,4 @@ var AttendanceCorrectionService = class {
|
|
|
1243
1397
|
//#endregion
|
|
1244
1398
|
export { continueSegment as a, openSegment as c, validateAttendanceWork as d, ATTENDANCE_CORRECTION_KINDS as f, closeOpenSegment as i, replaceSegment as l, AttendanceCorrectionCollection as m, AttendanceService as n, endSegment as o, AttendanceCorrection as p, beginSegment as r, listSegments as s, AttendanceCorrectionService as t, AttendanceConflict as u };
|
|
1245
1399
|
|
|
1246
|
-
//# sourceMappingURL=attendance-
|
|
1400
|
+
//# sourceMappingURL=attendance-UY7sTXFx.js.map
|