@t4dhg/mcp-factorial 9.0.0 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +76 -14
  2. package/dist/api/attendance.d.ts +37 -13
  3. package/dist/api/attendance.d.ts.map +1 -1
  4. package/dist/api/attendance.js +161 -31
  5. package/dist/api/attendance.js.map +1 -1
  6. package/dist/api/documents.d.ts +4 -3
  7. package/dist/api/documents.d.ts.map +1 -1
  8. package/dist/api/documents.js +4 -3
  9. package/dist/api/documents.js.map +1 -1
  10. package/dist/api/employees.d.ts +5 -3
  11. package/dist/api/employees.d.ts.map +1 -1
  12. package/dist/api/employees.js +5 -3
  13. package/dist/api/employees.js.map +1 -1
  14. package/dist/api/index.d.ts +1 -1
  15. package/dist/api/index.d.ts.map +1 -1
  16. package/dist/api/index.js +1 -1
  17. package/dist/api/index.js.map +1 -1
  18. package/dist/api/time-off.d.ts +6 -1
  19. package/dist/api/time-off.d.ts.map +1 -1
  20. package/dist/api/time-off.js +20 -16
  21. package/dist/api/time-off.js.map +1 -1
  22. package/dist/attendance/backfill.d.ts +36 -0
  23. package/dist/attendance/backfill.d.ts.map +1 -0
  24. package/dist/attendance/backfill.js +104 -0
  25. package/dist/attendance/backfill.js.map +1 -0
  26. package/dist/attendance/gate.d.ts +40 -0
  27. package/dist/attendance/gate.d.ts.map +1 -0
  28. package/dist/attendance/gate.js +63 -0
  29. package/dist/attendance/gate.js.map +1 -0
  30. package/dist/attendance/identity.d.ts +27 -0
  31. package/dist/attendance/identity.d.ts.map +1 -0
  32. package/dist/attendance/identity.js +73 -0
  33. package/dist/attendance/identity.js.map +1 -0
  34. package/dist/attendance/planner.d.ts +147 -0
  35. package/dist/attendance/planner.d.ts.map +1 -0
  36. package/dist/attendance/planner.js +316 -0
  37. package/dist/attendance/planner.js.map +1 -0
  38. package/dist/endpoints.d.ts +5 -0
  39. package/dist/endpoints.d.ts.map +1 -1
  40. package/dist/endpoints.js +5 -0
  41. package/dist/endpoints.js.map +1 -1
  42. package/dist/http-client.d.ts +14 -1
  43. package/dist/http-client.d.ts.map +1 -1
  44. package/dist/http-client.js +36 -7
  45. package/dist/http-client.js.map +1 -1
  46. package/dist/schemas/ats.d.ts +4 -4
  47. package/dist/schemas/attendance.d.ts +367 -0
  48. package/dist/schemas/attendance.d.ts.map +1 -0
  49. package/dist/schemas/attendance.js +159 -0
  50. package/dist/schemas/attendance.js.map +1 -0
  51. package/dist/schemas/documents.d.ts +2 -2
  52. package/dist/schemas/index.d.ts +2 -1
  53. package/dist/schemas/index.d.ts.map +1 -1
  54. package/dist/schemas/index.js +3 -1
  55. package/dist/schemas/index.js.map +1 -1
  56. package/dist/schemas/projects.d.ts +4 -4
  57. package/dist/schemas/time-off.d.ts +59 -148
  58. package/dist/schemas/time-off.d.ts.map +1 -1
  59. package/dist/schemas/time-off.js +22 -53
  60. package/dist/schemas/time-off.js.map +1 -1
  61. package/dist/schemas/training.d.ts +2 -2
  62. package/dist/tools/attendance.d.ts +9 -1
  63. package/dist/tools/attendance.d.ts.map +1 -1
  64. package/dist/tools/attendance.js +314 -24
  65. package/dist/tools/attendance.js.map +1 -1
  66. package/dist/tools/index.js +1 -1
  67. package/dist/tools/index.js.map +1 -1
  68. package/dist/tools/shared.d.ts +2 -2
  69. package/dist/tools/shared.d.ts.map +1 -1
  70. package/dist/tools/shared.js +14 -2
  71. package/dist/tools/shared.js.map +1 -1
  72. package/dist/tools/time-off.d.ts.map +1 -1
  73. package/dist/tools/time-off.js +15 -6
  74. package/dist/tools/time-off.js.map +1 -1
  75. package/dist/types.d.ts +28 -13
  76. package/dist/types.d.ts.map +1 -1
  77. package/dist/write-safety.d.ts +18 -0
  78. package/dist/write-safety.d.ts.map +1 -1
  79. package/dist/write-safety.js +19 -0
  80. package/dist/write-safety.js.map +1 -1
  81. package/llms.txt +6 -4
  82. package/package.json +1 -1
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Backfill planner for attendance shifts (registro horario).
3
+ *
4
+ * Everything in this module is pure: no network, no clock, no configuration.
5
+ * The caller gathers facts from the API (worked_times, estimated_times,
6
+ * existing shifts, approved leaves) and the planner turns them plus the
7
+ * requested segments into an explicit list of writes and skips. Every rule is
8
+ * a unit test in src/__tests__/unit/attendance/planner.test.ts.
9
+ */
10
+ /** A working segment on one day, in company local wall-clock time */
11
+ export interface Segment {
12
+ clock_in: string;
13
+ clock_out: string;
14
+ }
15
+ /** One planned shift record */
16
+ export interface PlannedWrite extends Segment {
17
+ date: string;
18
+ }
19
+ /** What is known about one calendar day for one employee */
20
+ export interface DayFacts {
21
+ /** worked_times.day_type: workday | saturday | sunday | bank_holiday */
22
+ day_type: string;
23
+ /** estimated_times.expected_minutes, from the contract pattern */
24
+ expected_minutes: number;
25
+ /** worked_times.tracked_minutes */
26
+ tracked_minutes: number;
27
+ }
28
+ /** An existing shift as the API reports it; clock_out null means still open */
29
+ export interface ExistingShift {
30
+ date: string;
31
+ clock_in: string;
32
+ clock_out: string | null;
33
+ }
34
+ /** How an approved leave covers a date */
35
+ export type LeaveCover = 'full' | 'beggining_of_day' | 'end_of_day';
36
+ /** The subset of a leave record the planner needs */
37
+ export interface LeaveLike {
38
+ start_on: string;
39
+ finish_on: string;
40
+ half_day: string | null;
41
+ approved: boolean | null;
42
+ deleted_at: string | null;
43
+ }
44
+ export interface PlanFacts {
45
+ /** Today's date, YYYY-MM-DD, in the zone the caller considers local */
46
+ today: string;
47
+ /** Per-date facts; a date missing from the map has no contract data */
48
+ days: Map<string, DayFacts>;
49
+ /** Existing shifts for the employee within the planned dates */
50
+ shifts: ExistingShift[];
51
+ /** Approved leave cover per date, see expandLeaves */
52
+ leaves: Map<string, LeaveCover>;
53
+ }
54
+ export interface RangeRequest {
55
+ mode: 'range';
56
+ employee_id: number;
57
+ dates: string[];
58
+ segments: Segment[];
59
+ skip_leave: boolean;
60
+ }
61
+ export interface DaysRequest {
62
+ mode: 'days';
63
+ employee_id: number;
64
+ days: Array<{
65
+ date: string;
66
+ segments: Segment[];
67
+ }>;
68
+ skip_leave: boolean;
69
+ }
70
+ export type PlanRequest = RangeRequest | DaysRequest;
71
+ export type SkipReason = 'future_date' | 'weekend' | 'bank_holiday' | 'not_workable' | 'on_leave' | 'half_day_leave';
72
+ export interface SkippedDay {
73
+ date: string;
74
+ reason: SkipReason;
75
+ detail?: string;
76
+ }
77
+ export interface SkippedSegment extends Segment {
78
+ date: string;
79
+ reason: 'overlaps_existing';
80
+ detail: string;
81
+ }
82
+ export interface BackfillPlan {
83
+ writes: PlannedWrite[];
84
+ skippedDays: SkippedDay[];
85
+ skippedSegments: SkippedSegment[];
86
+ totals: {
87
+ days: number;
88
+ records: number;
89
+ minutes: number;
90
+ };
91
+ }
92
+ export interface Gap {
93
+ date: string;
94
+ expected_minutes: number;
95
+ tracked_minutes: number;
96
+ missing_minutes: number;
97
+ half_day_leave: 'beggining_of_day' | 'end_of_day' | null;
98
+ }
99
+ /** Parse "HH:MM" into minutes since midnight; anything else is rejected */
100
+ export declare function parseHHMM(value: string): number;
101
+ /** True when two [start, end) intervals share any time; touching does not count */
102
+ export declare function intervalsOverlap(a: [number, number], b: [number, number]): boolean;
103
+ /** Every date from start to end inclusive */
104
+ export declare function enumerateDates(start: string, end: string): string[];
105
+ /**
106
+ * Validate the segments of one day: HH:MM only, clock_in strictly before
107
+ * clock_out, no overnight shifts, no overlap between segments.
108
+ */
109
+ export declare function validateSegments(segments: Segment[]): void;
110
+ /**
111
+ * Turn leave records into a per-date cover map. Only approved, undeleted
112
+ * leaves count; a pending (approved: null) or rejected leave does not block
113
+ * anything. A later leave over the same date never downgrades a full cover.
114
+ */
115
+ export declare function expandLeaves(leaves: LeaveLike[]): Map<string, LeaveCover>;
116
+ /**
117
+ * Build the plan. Skip reasons are evaluated in the documented order and the
118
+ * first match wins. Explicit days skip the weekday, holiday and workability
119
+ * rules on purpose: migration must be able to write a Saturday someone worked.
120
+ */
121
+ export declare function buildBackfillPlan(request: PlanRequest, facts: PlanFacts): BackfillPlan;
122
+ /**
123
+ * Stable digest of who and what a plan writes, including the note stored on
124
+ * every record; the confirmation token is bound to it.
125
+ */
126
+ export declare function planFingerprint(employeeId: number, writes: PlannedWrite[], observations?: string): string;
127
+ /**
128
+ * Workdays up to today where the contract expects more than was tracked and no
129
+ * full-day leave applies. Future dates are left out, since log_range would
130
+ * refuse them anyway.
131
+ */
132
+ export declare function computeGaps(facts: PlanFacts): Gap[];
133
+ /** 720 -> "12h", 750 -> "12h30" */
134
+ export declare function hours(minutes: number): string;
135
+ /**
136
+ * Render the plan the way the confirmation step shows it. It always names the
137
+ * person, because the company-wide key makes writing someone else's hours the
138
+ * mistake worth catching.
139
+ */
140
+ export declare function formatPlanPreview(plan: BackfillPlan, employee: {
141
+ id: number;
142
+ name: string;
143
+ }, range: {
144
+ start: string;
145
+ end: string;
146
+ }, request: PlanRequest, observations?: string): string;
147
+ //# sourceMappingURL=planner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner.d.ts","sourceRoot":"","sources":["../../src/attendance/planner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Backfill planner for attendance shifts (registro horario).
3
+ *
4
+ * Everything in this module is pure: no network, no clock, no configuration.
5
+ * The caller gathers facts from the API (worked_times, estimated_times,
6
+ * existing shifts, approved leaves) and the planner turns them plus the
7
+ * requested segments into an explicit list of writes and skips. Every rule is
8
+ * a unit test in src/__tests__/unit/attendance/planner.test.ts.
9
+ */
10
+ import { createHash } from 'node:crypto';
11
+ const HHMM = /^([01]\d|2[0-3]):([0-5]\d)$/;
12
+ const DATE = /^\d{4}-\d{2}-\d{2}$/;
13
+ const END_OF_DAY = 24 * 60;
14
+ /** Parse "HH:MM" into minutes since midnight; anything else is rejected */
15
+ export function parseHHMM(value) {
16
+ const match = HHMM.exec(value);
17
+ if (!match) {
18
+ throw new Error(`Time "${value}" must be HH:MM in 24-hour company local time`);
19
+ }
20
+ return Number(match[1]) * 60 + Number(match[2]);
21
+ }
22
+ /** True when two [start, end) intervals share any time; touching does not count */
23
+ export function intervalsOverlap(a, b) {
24
+ return a[0] < b[1] && b[0] < a[1];
25
+ }
26
+ function assertDate(value) {
27
+ if (!DATE.test(value) || Number.isNaN(Date.parse(`${value}T00:00:00Z`))) {
28
+ throw new Error(`Date "${value}" must be YYYY-MM-DD`);
29
+ }
30
+ }
31
+ /** Every date from start to end inclusive */
32
+ export function enumerateDates(start, end) {
33
+ assertDate(start);
34
+ assertDate(end);
35
+ if (start > end) {
36
+ throw new Error(`start_on ${start} is after end_on ${end}`);
37
+ }
38
+ const dates = [];
39
+ const cursor = new Date(`${start}T00:00:00Z`);
40
+ const last = new Date(`${end}T00:00:00Z`);
41
+ while (cursor <= last) {
42
+ dates.push(cursor.toISOString().slice(0, 10));
43
+ cursor.setUTCDate(cursor.getUTCDate() + 1);
44
+ }
45
+ return dates;
46
+ }
47
+ /**
48
+ * Validate the segments of one day: HH:MM only, clock_in strictly before
49
+ * clock_out, no overnight shifts, no overlap between segments.
50
+ */
51
+ export function validateSegments(segments) {
52
+ if (segments.length === 0) {
53
+ throw new Error('segments must contain at least one {clock_in, clock_out} entry');
54
+ }
55
+ const intervals = segments.map(segment => {
56
+ const start = parseHHMM(segment.clock_in);
57
+ const end = parseHHMM(segment.clock_out);
58
+ if (end < start) {
59
+ throw new Error(`Segment ${segment.clock_in}-${segment.clock_out} ends before it starts. Overnight shifts ` +
60
+ 'are not supported by the bulk actions; use the create action with reference_date instead.');
61
+ }
62
+ if (end === start) {
63
+ throw new Error(`Segment ${segment.clock_in}-${segment.clock_out}: clock_in must be before clock_out`);
64
+ }
65
+ return [start, end];
66
+ });
67
+ for (let i = 0; i < intervals.length; i++) {
68
+ for (let j = i + 1; j < intervals.length; j++) {
69
+ if (intervalsOverlap(intervals[i], intervals[j])) {
70
+ throw new Error(`Segments ${segments[i].clock_in}-${segments[i].clock_out} and ` +
71
+ `${segments[j].clock_in}-${segments[j].clock_out} overlap each other`);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ /**
77
+ * Turn leave records into a per-date cover map. Only approved, undeleted
78
+ * leaves count; a pending (approved: null) or rejected leave does not block
79
+ * anything. A later leave over the same date never downgrades a full cover.
80
+ */
81
+ export function expandLeaves(leaves) {
82
+ const cover = new Map();
83
+ for (const leave of leaves) {
84
+ if (leave.approved !== true || leave.deleted_at !== null)
85
+ continue;
86
+ const kind = leave.half_day === 'beggining_of_day' || leave.half_day === 'end_of_day'
87
+ ? leave.half_day
88
+ : 'full';
89
+ for (const date of enumerateDates(leave.start_on, leave.finish_on)) {
90
+ const existing = cover.get(date);
91
+ if (existing === 'full')
92
+ continue;
93
+ cover.set(date, kind);
94
+ }
95
+ }
96
+ return cover;
97
+ }
98
+ function daySkipReason(date, request, facts) {
99
+ if (date > facts.today) {
100
+ return {
101
+ date,
102
+ reason: 'future_date',
103
+ detail: `today is ${facts.today} in the zone of the machine running the server`,
104
+ };
105
+ }
106
+ const day = facts.days.get(date);
107
+ if (request.mode === 'range') {
108
+ if (day?.day_type === 'saturday' || day?.day_type === 'sunday') {
109
+ return { date, reason: 'weekend' };
110
+ }
111
+ if (day?.day_type === 'bank_holiday') {
112
+ return { date, reason: 'bank_holiday' };
113
+ }
114
+ if (!day || day.expected_minutes <= 0) {
115
+ return {
116
+ date,
117
+ reason: 'not_workable',
118
+ detail: day ? 'contract expects 0 minutes' : 'no contract data for this date',
119
+ };
120
+ }
121
+ }
122
+ if (request.skip_leave) {
123
+ const cover = facts.leaves.get(date);
124
+ if (cover === 'full') {
125
+ return { date, reason: 'on_leave', detail: 'approved leave' };
126
+ }
127
+ if (cover && request.mode === 'range') {
128
+ return {
129
+ date,
130
+ reason: 'half_day_leave',
131
+ detail: `approved half-day leave (${cover}); write it with log_days if the other half was worked`,
132
+ };
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+ function existingIntervals(shifts, date) {
138
+ return shifts
139
+ .filter(shift => shift.date === date)
140
+ .map(shift => {
141
+ let start;
142
+ let end;
143
+ try {
144
+ start = parseHHMM(shift.clock_in);
145
+ end = shift.clock_out === null ? END_OF_DAY : parseHHMM(shift.clock_out);
146
+ }
147
+ catch {
148
+ throw new Error(`An existing shift on ${date} has times the planner cannot read ` +
149
+ `(${shift.clock_in}-${shift.clock_out ?? 'open'}). Fix or delete that record first.`);
150
+ }
151
+ // An overnight existing shift (clock_out before clock_in) occupies to end of day.
152
+ if (end < start)
153
+ end = END_OF_DAY;
154
+ const label = `${shift.clock_in}-${shift.clock_out ?? 'open'}`;
155
+ return [start, end, label];
156
+ });
157
+ }
158
+ /**
159
+ * Build the plan. Skip reasons are evaluated in the documented order and the
160
+ * first match wins. Explicit days skip the weekday, holiday and workability
161
+ * rules on purpose: migration must be able to write a Saturday someone worked.
162
+ */
163
+ export function buildBackfillPlan(request, facts) {
164
+ const requested = request.mode === 'range'
165
+ ? request.dates.map(date => ({ date, segments: request.segments }))
166
+ : request.days;
167
+ if (request.mode === 'range') {
168
+ validateSegments(request.segments);
169
+ }
170
+ else {
171
+ const seen = new Set();
172
+ for (const day of request.days) {
173
+ assertDate(day.date);
174
+ if (seen.has(day.date)) {
175
+ throw new Error(`Date ${day.date} appears more than once in days`);
176
+ }
177
+ seen.add(day.date);
178
+ validateSegments(day.segments);
179
+ }
180
+ }
181
+ const writes = [];
182
+ const skippedDays = [];
183
+ const skippedSegments = [];
184
+ const writtenDates = new Set();
185
+ let minutes = 0;
186
+ for (const { date, segments } of requested) {
187
+ const skip = daySkipReason(date, request, facts);
188
+ if (skip) {
189
+ skippedDays.push(skip);
190
+ continue;
191
+ }
192
+ const existing = existingIntervals(facts.shifts, date);
193
+ for (const segment of segments) {
194
+ const interval = [
195
+ parseHHMM(segment.clock_in),
196
+ parseHHMM(segment.clock_out),
197
+ ];
198
+ const clash = existing.find(([start, end]) => intervalsOverlap(interval, [start, end]));
199
+ if (clash) {
200
+ skippedSegments.push({
201
+ date,
202
+ ...segment,
203
+ reason: 'overlaps_existing',
204
+ detail: `overlaps existing ${clash[2]}`,
205
+ });
206
+ continue;
207
+ }
208
+ writes.push({ date, ...segment });
209
+ writtenDates.add(date);
210
+ minutes += interval[1] - interval[0];
211
+ }
212
+ }
213
+ return {
214
+ writes,
215
+ skippedDays,
216
+ skippedSegments,
217
+ totals: { days: writtenDates.size, records: writes.length, minutes },
218
+ };
219
+ }
220
+ /**
221
+ * Stable digest of who and what a plan writes, including the note stored on
222
+ * every record; the confirmation token is bound to it.
223
+ */
224
+ export function planFingerprint(employeeId, writes, observations) {
225
+ const canonical = writes
226
+ .map(w => `${w.date} ${w.clock_in} ${w.clock_out}`)
227
+ .sort()
228
+ .join('\n');
229
+ return createHash('sha256')
230
+ .update(`${employeeId}\n${observations ?? ''}\n${canonical}`)
231
+ .digest('hex');
232
+ }
233
+ /**
234
+ * Workdays up to today where the contract expects more than was tracked and no
235
+ * full-day leave applies. Future dates are left out, since log_range would
236
+ * refuse them anyway.
237
+ */
238
+ export function computeGaps(facts) {
239
+ const gaps = [];
240
+ for (const [date, day] of [...facts.days.entries()].sort(([a], [b]) => a.localeCompare(b))) {
241
+ if (date > facts.today)
242
+ continue;
243
+ if (day.day_type !== 'workday')
244
+ continue;
245
+ if (day.expected_minutes <= day.tracked_minutes)
246
+ continue;
247
+ const cover = facts.leaves.get(date);
248
+ if (cover === 'full')
249
+ continue;
250
+ gaps.push({
251
+ date,
252
+ expected_minutes: day.expected_minutes,
253
+ tracked_minutes: day.tracked_minutes,
254
+ missing_minutes: day.expected_minutes - day.tracked_minutes,
255
+ half_day_leave: cover ?? null,
256
+ });
257
+ }
258
+ return gaps;
259
+ }
260
+ /** 720 -> "12h", 750 -> "12h30" */
261
+ export function hours(minutes) {
262
+ const whole = Math.floor(minutes / 60);
263
+ const rest = minutes % 60;
264
+ return rest === 0 ? `${whole}h` : `${whole}h${String(rest).padStart(2, '0')}`;
265
+ }
266
+ function countBy(items) {
267
+ const counts = new Map();
268
+ for (const item of items)
269
+ counts.set(item.reason, (counts.get(item.reason) ?? 0) + 1);
270
+ return counts;
271
+ }
272
+ /**
273
+ * Render the plan the way the confirmation step shows it. It always names the
274
+ * person, because the company-wide key makes writing someone else's hours the
275
+ * mistake worth catching.
276
+ */
277
+ export function formatPlanPreview(plan, employee, range, request, observations) {
278
+ const lines = [];
279
+ lines.push(`Plan for ${employee.name} (${employee.id})`);
280
+ lines.push(`${range.start} .. ${range.end}`);
281
+ lines.push('');
282
+ lines.push(` ${plan.totals.days} days to write, ${plan.totals.records} shift records, ${hours(plan.totals.minutes)}`);
283
+ if (request.mode === 'range') {
284
+ lines.push(` ${request.segments.map(s => `${s.clock_in}-${s.clock_out}`).join(' and ')}`);
285
+ }
286
+ if (observations) {
287
+ lines.push(` Note on every record: "${observations}"`);
288
+ }
289
+ if (plan.skippedDays.length > 0) {
290
+ lines.push('');
291
+ lines.push(` Skipping ${plan.skippedDays.length} days:`);
292
+ const byReason = countBy(plan.skippedDays);
293
+ const labels = {
294
+ future_date: 'in the future',
295
+ weekend: 'weekend',
296
+ bank_holiday: 'bank holiday',
297
+ not_workable: 'not workable under the contract',
298
+ on_leave: 'approved leave',
299
+ half_day_leave: 'half-day leave, write it with log_days if the other half was worked',
300
+ };
301
+ for (const [reason, count] of byReason) {
302
+ const dates = plan.skippedDays.filter(d => d.reason === reason).map(d => d.date);
303
+ const suffix = reason === 'weekend' ? '' : ` (${dates.join(', ')})`;
304
+ lines.push(` ${count} ${labels[reason]}${suffix}`);
305
+ }
306
+ }
307
+ if (plan.skippedSegments.length > 0) {
308
+ lines.push('');
309
+ lines.push(` Skipping ${plan.skippedSegments.length} segments that overlap existing shifts:`);
310
+ for (const segment of plan.skippedSegments) {
311
+ lines.push(` ${segment.date} ${segment.clock_in}-${segment.clock_out} ${segment.detail}`);
312
+ }
313
+ }
314
+ return lines.join('\n');
315
+ }
316
+ //# sourceMappingURL=planner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner.js","sourceRoot":"","sources":["../../src/attendance/planner.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,11 @@ export declare const ENDPOINTS: {
17
17
  readonly leaveTypes: "/timeoff/leave-types";
18
18
  readonly allowances: "/timeoff/allowances";
19
19
  readonly shifts: "/attendance/shifts";
20
+ readonly openShifts: "/attendance/open_shifts";
21
+ readonly estimatedTimes: "/attendance/estimated_times";
22
+ readonly workedTimes: "/attendance/worked_times";
23
+ readonly clockIn: "/attendance/shifts/clock_in";
24
+ readonly clockOut: "/attendance/shifts/clock_out";
20
25
  readonly folders: "/documents/folders";
21
26
  readonly documents: "/documents/documents";
22
27
  readonly jobRoles: "/job_catalog/roles";
@@ -1 +1 @@
1
- {"version":3,"file":"endpoints.d.ts","sourceRoot":"","sources":["../src/endpoints.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDZ,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,SAAS,CAAC;AAEjD;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvF"}
1
+ {"version":3,"file":"endpoints.d.ts","sourceRoot":"","sources":["../src/endpoints.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDZ,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,SAAS,CAAC;AAEjD;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvF"}
package/dist/endpoints.js CHANGED
@@ -23,6 +23,11 @@ export const ENDPOINTS = {
23
23
  allowances: '/timeoff/allowances',
24
24
  // Attendance endpoints
25
25
  shifts: '/attendance/shifts',
26
+ openShifts: '/attendance/open_shifts',
27
+ estimatedTimes: '/attendance/estimated_times',
28
+ workedTimes: '/attendance/worked_times',
29
+ clockIn: '/attendance/shifts/clock_in',
30
+ clockOut: '/attendance/shifts/clock_out',
26
31
  // Document endpoints
27
32
  folders: '/documents/folders',
28
33
  documents: '/documents/documents',
@@ -1 +1 @@
1
- {"version":3,"file":"endpoints.js","sourceRoot":"","sources":["../src/endpoints.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,qBAAqB;IACrB,SAAS,EAAE,sBAAsB;IAEjC,iBAAiB;IACjB,KAAK,EAAE,cAAc;IAErB,qBAAqB;IACrB,SAAS,EAAE,sBAAsB;IACjC,SAAS,EAAE,uBAAuB;IAElC,qBAAqB;IACrB,SAAS,EAAE,8BAA8B;IAEzC,qBAAqB;IACrB,MAAM,EAAE,iBAAiB;IACzB,UAAU,EAAE,sBAAsB;IAClC,UAAU,EAAE,qBAAqB;IAEjC,uBAAuB;IACvB,MAAM,EAAE,oBAAoB;IAE5B,qBAAqB;IACrB,OAAO,EAAE,oBAAoB;IAC7B,SAAS,EAAE,sBAAsB;IAEjC,wBAAwB;IACxB,QAAQ,EAAE,oBAAoB;IAC9B,SAAS,EAAE,qBAAqB;IAEhC,+BAA+B;IAC/B,QAAQ,EAAE,8BAA8B;IACxC,YAAY,EAAE,mCAAmC;IACjD,cAAc,EAAE,qCAAqC;IACrD,WAAW,EAAE,kCAAkC;IAE/C,qBAAqB;IACrB,SAAS,EAAE,sBAAsB;IACjC,gBAAgB,EAAE,qBAAqB;IACvC,mBAAmB,EAAE,wBAAwB;IAE7C,6BAA6B;IAC7B,WAAW,EAAE,mBAAmB;IAChC,UAAU,EAAE,iBAAiB;IAC7B,YAAY,EAAE,mBAAmB;IACjC,YAAY,EAAE,oBAAoB;IAElC,oBAAoB;IACpB,kBAAkB,EAAE,sBAAsB;IAC1C,cAAc,EAAE,gCAAgC;IAChD,gBAAgB,EAAE,4BAA4B;CACtC,CAAC;AAOX;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,EAAU;IACzD,OAAO,GAAG,QAAQ,IAAI,EAAE,EAAE,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,EAAU,EAAE,MAAc;IAC7E,OAAO,GAAG,QAAQ,IAAI,EAAE,IAAI,MAAM,EAAE,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"endpoints.js","sourceRoot":"","sources":["../src/endpoints.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,qBAAqB;IACrB,SAAS,EAAE,sBAAsB;IAEjC,iBAAiB;IACjB,KAAK,EAAE,cAAc;IAErB,qBAAqB;IACrB,SAAS,EAAE,sBAAsB;IACjC,SAAS,EAAE,uBAAuB;IAElC,qBAAqB;IACrB,SAAS,EAAE,8BAA8B;IAEzC,qBAAqB;IACrB,MAAM,EAAE,iBAAiB;IACzB,UAAU,EAAE,sBAAsB;IAClC,UAAU,EAAE,qBAAqB;IAEjC,uBAAuB;IACvB,MAAM,EAAE,oBAAoB;IAC5B,UAAU,EAAE,yBAAyB;IACrC,cAAc,EAAE,6BAA6B;IAC7C,WAAW,EAAE,0BAA0B;IACvC,OAAO,EAAE,6BAA6B;IACtC,QAAQ,EAAE,8BAA8B;IAExC,qBAAqB;IACrB,OAAO,EAAE,oBAAoB;IAC7B,SAAS,EAAE,sBAAsB;IAEjC,wBAAwB;IACxB,QAAQ,EAAE,oBAAoB;IAC9B,SAAS,EAAE,qBAAqB;IAEhC,+BAA+B;IAC/B,QAAQ,EAAE,8BAA8B;IACxC,YAAY,EAAE,mCAAmC;IACjD,cAAc,EAAE,qCAAqC;IACrD,WAAW,EAAE,kCAAkC;IAE/C,qBAAqB;IACrB,SAAS,EAAE,sBAAsB;IACjC,gBAAgB,EAAE,qBAAqB;IACvC,mBAAmB,EAAE,wBAAwB;IAE7C,6BAA6B;IAC7B,WAAW,EAAE,mBAAmB;IAChC,UAAU,EAAE,iBAAiB;IAC7B,YAAY,EAAE,mBAAmB;IACjC,YAAY,EAAE,oBAAoB;IAElC,oBAAoB;IACpB,kBAAkB,EAAE,sBAAsB;IAC1C,cAAc,EAAE,gCAAgC;IAChD,gBAAgB,EAAE,4BAA4B;CACtC,CAAC;AAOX;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,EAAU;IACzD,OAAO,GAAG,QAAQ,IAAI,EAAE,EAAE,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,EAAU,EAAE,MAAc;IAC7E,OAAO,GAAG,QAAQ,IAAI,EAAE,IAAI,MAAM,EAAE,CAAC;AACvC,CAAC"}
@@ -10,9 +10,11 @@ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
10
10
  /**
11
11
  * Request options for the HTTP client
12
12
  */
13
+ /** A query parameter value; arrays are sent as repeated `key[]=value` pairs */
14
+ export type QueryParamValue = string | number | boolean | undefined | Array<string | number | boolean>;
13
15
  export interface RequestOptions {
14
16
  /** Query parameters */
15
- params?: Record<string, string | number | boolean | undefined>;
17
+ params?: Record<string, QueryParamValue>;
16
18
  /** Request timeout in milliseconds (overrides default) */
17
19
  timeout?: number;
18
20
  /** Maximum retry attempts (overrides default) */
@@ -60,6 +62,17 @@ export interface ApiResponse<T> {
60
62
  export interface ApiListResponse<T> {
61
63
  data: T[];
62
64
  }
65
+ /**
66
+ * Extract a single record from a response.
67
+ *
68
+ * Factorial returns single records at the top level: `GET /{resource}/{id}`
69
+ * and every `POST`, `PUT` and `PATCH` respond with the record itself, and only
70
+ * list endpoints wrap their payload as `{ data: [...], meta: {...} }`. This was
71
+ * verified live on 2026-09-06 on API 2026-07-01 and the reference agrees. The
72
+ * `{ data: record }` envelope is still accepted so that a tenant or version
73
+ * that does wrap single records keeps working.
74
+ */
75
+ export declare function unwrapRecord<T>(response: unknown): T;
63
76
  /**
64
77
  * Make a request expecting a single item response
65
78
  */
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkBH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE;;GAEG;AACH,+EAA+E;AAC/E,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,MAAM,GACN,OAAO,GACP,SAAS,GACT,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAErC,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACzC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IACzD,kBAAkB;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAkHD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAgB1E;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,CAAC,CAAC,CAiFZ;AAED;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,IAAI,EAAE,CAAC,CAAC;CACT;AAED;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,IAAI,EAAE,CAAC,EAAE,CAAC;CACX;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,GAAG,CAAC,CAapD;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,CAGxF;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAG3F;AAMD;;GAEG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,MAAM,CAAC,GACrD,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,MAAM,CAAC,GACrD,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,MAAM,CAAC,GACrD,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,MAAM,CAAC,GACrD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,MAAM,CAAC,GACrD,OAAO,CAAC,CAAC,CAAC,CAOZ"}
@@ -33,9 +33,16 @@ function buildUrl(endpoint, params) {
33
33
  const url = new URL(`${config.baseUrl}${endpoint}`);
34
34
  if (params) {
35
35
  Object.entries(params).forEach(([key, value]) => {
36
- if (value !== undefined && value !== null) {
37
- url.searchParams.append(key, String(value));
36
+ if (value === undefined || value === null)
37
+ return;
38
+ if (Array.isArray(value)) {
39
+ // Factorial expects Rails-style arrays: employee_ids[]=1&employee_ids[]=2
40
+ const name = key.endsWith('[]') ? key : `${key}[]`;
41
+ for (const item of value)
42
+ url.searchParams.append(name, String(item));
43
+ return;
38
44
  }
45
+ url.searchParams.append(key, String(value));
39
46
  });
40
47
  }
41
48
  return url.toString();
@@ -200,12 +207,34 @@ export async function factorialRequest(endpoint, options = {}) {
200
207
  }
201
208
  throw lastError ?? new NetworkError('Request failed after all retries');
202
209
  }
210
+ /**
211
+ * Extract a single record from a response.
212
+ *
213
+ * Factorial returns single records at the top level: `GET /{resource}/{id}`
214
+ * and every `POST`, `PUT` and `PATCH` respond with the record itself, and only
215
+ * list endpoints wrap their payload as `{ data: [...], meta: {...} }`. This was
216
+ * verified live on 2026-09-06 on API 2026-07-01 and the reference agrees. The
217
+ * `{ data: record }` envelope is still accepted so that a tenant or version
218
+ * that does wrap single records keeps working.
219
+ */
220
+ export function unwrapRecord(response) {
221
+ if (response !== null &&
222
+ typeof response === 'object' &&
223
+ !Array.isArray(response) &&
224
+ 'data' in response) {
225
+ const inner = response.data;
226
+ if (inner !== null && typeof inner === 'object' && !Array.isArray(inner)) {
227
+ return inner;
228
+ }
229
+ }
230
+ return response;
231
+ }
203
232
  /**
204
233
  * Make a request expecting a single item response
205
234
  */
206
235
  export async function fetchOne(endpoint, options) {
207
236
  const response = await factorialRequest(endpoint, options);
208
- return response.data;
237
+ return unwrapRecord(response);
209
238
  }
210
239
  /**
211
240
  * Make a request expecting a list response
@@ -226,7 +255,7 @@ export async function postOne(endpoint, body, options) {
226
255
  method: 'POST',
227
256
  body,
228
257
  });
229
- return response.data;
258
+ return unwrapRecord(response);
230
259
  }
231
260
  /**
232
261
  * Update a resource (PUT request expecting single item response)
@@ -237,7 +266,7 @@ export async function putOne(endpoint, body, options) {
237
266
  method: 'PUT',
238
267
  body,
239
268
  });
240
- return response.data;
269
+ return unwrapRecord(response);
241
270
  }
242
271
  /**
243
272
  * Partially update a resource (PATCH request expecting single item response)
@@ -248,7 +277,7 @@ export async function patchOne(endpoint, body, options) {
248
277
  method: 'PATCH',
249
278
  body,
250
279
  });
251
- return response.data;
280
+ return unwrapRecord(response);
252
281
  }
253
282
  /**
254
283
  * Delete a resource (DELETE request, typically returns 204 No Content)
@@ -269,6 +298,6 @@ export async function postAction(endpoint, body, options) {
269
298
  method: 'POST',
270
299
  body: body || {},
271
300
  });
272
- return response.data;
301
+ return unwrapRecord(response);
273
302
  }
274
303
  //# sourceMappingURL=http-client.js.map