@managemint-solutions/sdk 0.39.0 → 0.40.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/README.md +75 -3
- package/dist/client.d.ts +3 -0
- package/dist/client.js +5 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +14 -1
- package/dist/leave/dto.d.ts +4 -0
- package/dist/leave/dto.js +24 -0
- package/dist/leave/leave-entitlements.d.ts +10 -3
- package/dist/leave/leave-entitlements.js +26 -6
- package/dist/leave/leave-requests.d.ts +2 -3
- package/dist/leave/leave-requests.js +2 -1
- package/dist/leave/leave-types.d.ts +2 -3
- package/dist/leave/leave-types.js +3 -0
- package/dist/leave/working-days.d.ts +4 -0
- package/dist/leave/working-days.js +13 -5
- package/dist/permissions/dto.d.ts +4 -0
- package/dist/permissions/dto.js +20 -0
- package/dist/service-client/index.d.ts +13 -0
- package/dist/service-client/index.js +22 -0
- package/dist/timesheets/dto.d.ts +42 -0
- package/dist/timesheets/dto.js +211 -0
- package/dist/timesheets/index.d.ts +4 -0
- package/dist/timesheets/index.js +27 -0
- package/dist/timesheets/timesheet-entries.d.ts +54 -0
- package/dist/timesheets/timesheet-entries.js +141 -0
- package/dist/timesheets/timesheets.d.ts +67 -0
- package/dist/timesheets/timesheets.js +201 -0
- package/dist/timesheets/weeks.d.ts +7 -0
- package/dist/timesheets/weeks.js +26 -0
- package/dist/transforms.d.ts +13 -0
- package/dist/transforms.js +24 -1
- package/dist/users/dto.d.ts +1 -0
- package/dist/users/dto.js +7 -0
- package/dist/users/index.d.ts +8 -1
- package/dist/users/index.js +17 -2
- package/dist/validators.d.ts +10 -0
- package/dist/validators.js +21 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -275,8 +275,11 @@ const deleted = await users.remove(userId); // hard
|
|
|
275
275
|
```
|
|
276
276
|
|
|
277
277
|
Cross-tenant lookups - the organization behind a Paystack code, a ledger row by id, every
|
|
278
|
-
organization with a Paystack customer
|
|
279
|
-
|
|
278
|
+
organization with a Paystack customer, every organization whose leave year starts in a given month
|
|
279
|
+
(`organizationsWithLeaveYearStartingIn(month)` → `{ mms_id, modules }[]`)
|
|
280
|
+
- hang off `service` itself, because a webhook or cron does not know its tenant until it looks one
|
|
281
|
+
up. The scope also carries `leaveTypes`, `leaveEntitlements` and `leaveRequests` for the leave-year
|
|
282
|
+
rollover, whose writes are attributed to the system.
|
|
280
283
|
|
|
281
284
|
`update` / `transition` / `remove` pre-read the flat row (by `mms_id`, and by `organization_id`
|
|
282
285
|
unless `scopeByOrganization: false` - the `organizations` table is its own tenant), so a missing
|
|
@@ -424,7 +427,7 @@ const today = todayInJohannesburg(); // "
|
|
|
424
427
|
|
|
425
428
|
```ts
|
|
426
429
|
const types = await supabase.leaveTypes.list({ active: true });
|
|
427
|
-
const annual = await supabase.leaveTypes.create({ name: 'Annual', description: null, default_days: 15, is_paid: true, requires_approval: true });
|
|
430
|
+
const annual = await supabase.leaveTypes.create({ name: 'Annual', description: null, default_days: 15, is_paid: true, requires_approval: true, requires_document: false });
|
|
428
431
|
await supabase.leaveTypes.remove(annual.mms_id); // hard delete; 409 once a request or entitlement references it
|
|
429
432
|
|
|
430
433
|
const taken = await supabase.leaveEntitlements.overlaps(userId, annual.mms_id, '2026-01-01', '2026-12-31'); // an entitlement of that type already covers part of the period
|
|
@@ -440,6 +443,27 @@ const annualOnly = await supabase.leaveRequests.list({ ...query, leave_type_id:
|
|
|
440
443
|
const reports = await supabase.leaveRequests.list(query, { userIds: [me, ...(await supabase.users.idsManagedBy(me))] });
|
|
441
444
|
```
|
|
442
445
|
|
|
446
|
+
A leave type with `requires_document` set is one the api will not approve until a supporting file
|
|
447
|
+
is attached to the request; the `leave_type` embed on requests and entitlements carries the flag so
|
|
448
|
+
a caller need not fetch the type. The reporting line is two deep on the user: `manager_id` and
|
|
449
|
+
`secondary_manager_id` (`UpdateUserDto`, both v4 user ids, nullable), and
|
|
450
|
+
`users.idsManagedBy(managerId)` returns everyone naming that person as either - one level down,
|
|
451
|
+
`active` ignored.
|
|
452
|
+
|
|
453
|
+
An entitlement carries `max_carry_over_days` (HR-set, default 0): on the leave-year boundary the
|
|
454
|
+
api's nightly rollover creates next period's entitlement for each person and type as
|
|
455
|
+
`default_days + min(unused, cap)`. What it runs on:
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
import { leavePeriodFor, shiftDays } from '@managemint-solutions/sdk';
|
|
459
|
+
|
|
460
|
+
const scope = service.forOrganization(organizationId); // system actor: created_by stays null
|
|
461
|
+
const ended = leavePeriodFor(shiftDays(today, -1), startMonth); // the period that closed yesterday
|
|
462
|
+
const rows = await scope.leaveEntitlements.endingOn(ended.period_end); // every entitlement ending that day, balances included, paged
|
|
463
|
+
const active = new Set(await scope.users.activeIds()); // who still gets a new period
|
|
464
|
+
await scope.leaveEntitlements.create({ ...next, user_id, leave_type_id, days_entitled, max_carry_over_days, notes });
|
|
465
|
+
```
|
|
466
|
+
|
|
443
467
|
`review`, `cancel` and `update` are compare-and-set writes (`updateWhere`): they land only while
|
|
444
468
|
the request is still in the status they expect and resolve to `null` otherwise, so two reviewers
|
|
445
469
|
cannot both decide the same request. The `leave_requests` table also carries an exclusion
|
|
@@ -448,6 +472,54 @@ constraint so two overlapping live requests of one user cannot both be inserted
|
|
|
448
472
|
Notes, additional details and supporting files may attach to leave as well: their `entity`
|
|
449
473
|
accepts `leave_requests` and `leave_entitlements` alongside the CRM entity types.
|
|
450
474
|
|
|
475
|
+
## Timesheets
|
|
476
|
+
|
|
477
|
+
The second slice of the HR module: `timesheet_entries` (what an employee logs - a date, a start and
|
|
478
|
+
end `HH:MM`, optionally a client / project / task, a description) grouped into `timesheets`, one
|
|
479
|
+
row per person and Monday-to-Sunday week, which the employee submits and a manager approves or
|
|
480
|
+
rejects with a note. Both are org-scoped audited resources on the caller's client. A week's
|
|
481
|
+
`total_minutes` / `entry_count` are counted on read from its entries, never stored;
|
|
482
|
+
`duration_minutes` on an entry is generated by Postgres. Times are read back trimmed to `HH:MM`
|
|
483
|
+
(the column is `time`), so a row can be sent straight back in; `24:00` is a valid end for a shift
|
|
484
|
+
finishing at midnight, and an entry never crosses midnight - split it.
|
|
485
|
+
|
|
486
|
+
The week arithmetic is plain exported functions, shared with the api:
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
import { minutesBetween, weekStartOf } from '@managemint-solutions/sdk';
|
|
490
|
+
|
|
491
|
+
weekStartOf('2026-09-27'); // '2026-09-21' - a Sunday belongs to the week before it
|
|
492
|
+
minutesBetween('22:00', '24:00'); // 120; zero or negative when the end is not after the start
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
const weekStart = weekStartOf(body.date);
|
|
497
|
+
const week =
|
|
498
|
+
(await supabase.timesheets.findFor(me, weekStart)) ?? // { mms_id, status, week_start } or null
|
|
499
|
+
(await supabase.timesheets.create({ user_id: me, week_start: weekStart })); // opened as a draft
|
|
500
|
+
|
|
501
|
+
const clash = await supabase.timesheetEntries.overlaps(me, body.date, body.start_time, body.end_time); // same day, sharing a minute
|
|
502
|
+
const entry = await supabase.timesheetEntries.create({ timesheet_id: week.mms_id, user_id: me, ...body });
|
|
503
|
+
await supabase.timesheetEntries.update(entry.mms_id, { timesheet_id: week.mms_id, ...body });
|
|
504
|
+
await supabase.timesheetEntries.remove(entry.mms_id);
|
|
505
|
+
|
|
506
|
+
const mine = await supabase.timesheets.list({ ...query, user_id: me }); // one list; user_id / status / week_start narrow it
|
|
507
|
+
const reports = await supabase.timesheets.list(query, { userIds: [me, ...(await supabase.users.idsManagedBy(me))] });
|
|
508
|
+
const detail = await supabase.timesheets.getById(week.mms_id); // summary + entries
|
|
509
|
+
const submitted = await supabase.timesheets.submit(week.mms_id); // null = no longer draft or rejected
|
|
510
|
+
const reviewed = await supabase.timesheets.review(week.mms_id, { status: TimesheetReviewDecision.APPROVED, reviewed_by: managerId, review_note: null }); // null = no longer submitted
|
|
511
|
+
|
|
512
|
+
// The report: the api aggregates flat rows, keeping only the entries of approved weeks.
|
|
513
|
+
const approved = await supabase.timesheets.approvedIds(userIds, date_from, date_to);
|
|
514
|
+
const rows = (await supabase.timesheetEntries.forReport(userIds, date_from, date_to)).filter((row) => approved.has(row.timesheet_id));
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
`submit` and `review` are compare-and-set writes (`updateWhere`): `submit` lands only on a draft or
|
|
518
|
+
rejected week (and clears the previous decision), `review` only on a submitted one, and each
|
|
519
|
+
resolves to `null` otherwise. The `timesheet_entries` table also carries an exclusion constraint so
|
|
520
|
+
two overlapping entries of one user cannot both be inserted in a race. `forReport`, `approvedIds`
|
|
521
|
+
and the per-page totals walk their tables in pages of 1000, PostgREST's row cap.
|
|
522
|
+
|
|
451
523
|
## Public holidays
|
|
452
524
|
|
|
453
525
|
The public-holiday calendar, `public_holidays`, on the caller's client as `supabase.publicHolidays`.
|
package/dist/client.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { PublicHolidaysResource } from './public-holidays';
|
|
|
16
16
|
import { StatusesResource } from './statuses';
|
|
17
17
|
import { SupportingFilesResource } from './supporting-files';
|
|
18
18
|
import { TasksResource } from './tasks';
|
|
19
|
+
import { TimesheetEntriesResource, TimesheetsResource } from './timesheets';
|
|
19
20
|
import { UsersResource } from './users';
|
|
20
21
|
export type TypedSupabaseClient = SupabaseJsClient;
|
|
21
22
|
export type SupabaseClientConfig = {
|
|
@@ -62,6 +63,8 @@ export declare class SupabaseClient {
|
|
|
62
63
|
readonly publicHolidays: PublicHolidaysResource;
|
|
63
64
|
readonly leaveEntitlements: LeaveEntitlementsResource;
|
|
64
65
|
readonly leaveRequests: LeaveRequestsResource;
|
|
66
|
+
readonly timesheets: TimesheetsResource;
|
|
67
|
+
readonly timesheetEntries: TimesheetEntriesResource;
|
|
65
68
|
constructor(config: SupabaseClientConfig);
|
|
66
69
|
}
|
|
67
70
|
export declare const createSupabaseClient: (config: SupabaseClientConfig) => SupabaseClient;
|
package/dist/client.js
CHANGED
|
@@ -18,6 +18,7 @@ const public_holidays_1 = require("./public-holidays");
|
|
|
18
18
|
const statuses_1 = require("./statuses");
|
|
19
19
|
const supporting_files_1 = require("./supporting-files");
|
|
20
20
|
const tasks_1 = require("./tasks");
|
|
21
|
+
const timesheets_1 = require("./timesheets");
|
|
21
22
|
const users_1 = require("./users");
|
|
22
23
|
/**
|
|
23
24
|
* The caller's data client: every query carries their bearer token, so RLS decides what they
|
|
@@ -47,6 +48,8 @@ class SupabaseClient {
|
|
|
47
48
|
publicHolidays;
|
|
48
49
|
leaveEntitlements;
|
|
49
50
|
leaveRequests;
|
|
51
|
+
timesheets;
|
|
52
|
+
timesheetEntries;
|
|
50
53
|
constructor(config) {
|
|
51
54
|
this.auth = config.auth;
|
|
52
55
|
this.supabase =
|
|
@@ -72,6 +75,8 @@ class SupabaseClient {
|
|
|
72
75
|
this.publicHolidays = new public_holidays_1.PublicHolidaysResource(this.supabase);
|
|
73
76
|
this.leaveEntitlements = new leave_1.LeaveEntitlementsResource(this.supabase, this.auth, this.audit);
|
|
74
77
|
this.leaveRequests = new leave_1.LeaveRequestsResource(this.supabase, this.auth, this.audit);
|
|
78
|
+
this.timesheets = new timesheets_1.TimesheetsResource(this.supabase, this.auth, this.audit);
|
|
79
|
+
this.timesheetEntries = new timesheets_1.TimesheetEntriesResource(this.supabase, this.auth, this.audit);
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
exports.SupabaseClient = SupabaseClient;
|
package/dist/index.d.ts
CHANGED
|
@@ -39,5 +39,8 @@ export { CreateTaskDto, GetTasksDto, TaskIdDto, UpdateTaskDto } from './tasks';
|
|
|
39
39
|
export type { CreateLeaveEntitlementInput, CreateLeaveRequestRow, LeaveEntitlementCover, ReviewLeaveRequestRow, UpdateLeaveRequestRow, } from './leave';
|
|
40
40
|
export { CreateLeaveEntitlementDto, CreateLeaveRequestDto, CreateLeaveTypeDto, GetLeaveEntitlementsDto, GetLeaveRequestsDto, GetLeaveTypesDto, LeaveEntitlementIdDto, LeaveRequestIdDto, LeaveTypeIdDto, ReviewLeaveRequestDto, UpdateLeaveEntitlementDto, UpdateLeaveRequestDto, UpdateLeaveTypeDto, } from './leave';
|
|
41
41
|
export { GetPublicHolidaysDto } from './public-holidays';
|
|
42
|
-
export { addOneYearMinusDay, countWorkingDays, isWithinPeriod, leavePeriodFor, todayInJohannesburg, } from './leave';
|
|
42
|
+
export { addOneYearMinusDay, countWorkingDays, isWithinPeriod, leavePeriodFor, shiftDays, todayInJohannesburg, } from './leave';
|
|
43
|
+
export type { CreateTimesheetEntryRow, CreateTimesheetRow, ReviewTimesheetRow, TimesheetCover, TimesheetReportRow, UpdateTimesheetEntryRow, } from './timesheets';
|
|
44
|
+
export { CreateTimesheetEntryDto, GetTimesheetReportDto, GetTimesheetsDto, ReviewTimesheetDto, TimesheetEntryIdDto, TimesheetIdDto, UpdateTimesheetEntryDto, } from './timesheets';
|
|
45
|
+
export { minutesBetween, weekStartOf } from './timesheets';
|
|
43
46
|
export { monthlyTotalMinor } from './pricebooks';
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// to keep. The row and input types they produce and accept are exported for annotations.
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.UserIdDto = exports.UpdateUserDto = exports.UpdateMyProfileDto = exports.GetUsersQueryDto = exports.AddUserDto = exports.UpdatePermissionsDto = exports.PermissionsResource = exports.UpdateOrganizationDto = exports.CreateOrganizationDto = exports.NotificationIdDto = exports.GetNotificationsDto = exports.NoteIdDto = exports.GetNotesDto = exports.AddNoteDto = exports.UpdateAdditionalDetailsDto = exports.IsAdditionalDetailsValueByType = exports.GetAdditionalDetailsDto = exports.AdditionalDetailsIdDto = exports.AddAdditionalDetailsDto = exports.UpdateStatusDto = exports.StatusIdDto = exports.GetStatusesDto = exports.AddStatusDto = exports.CreateSupportRequestDto = exports.UpdateConfigDto = exports.CreateConfigDto = exports.CreateFeatureRequestDto = exports.InvoiceIdDto = exports.GetInvoicesQueryDto = exports.ReinstateSubscriptionDto = exports.PurchaseModuleDto = exports.PaymentIdDto = exports.InitialPaymentDto = exports.GetPaymentsQueryDto = exports.CancelSubscriptionDto = exports.GetAuditTrailDto = exports.IsNullable = exports.EmptyToNull = exports.SupabaseClientError = exports.createSupabaseServiceClient = exports.SupabaseServiceClient = exports.SignInDto = exports.SendPasswordResetEmailDto = exports.ResetPasswordDto = exports.ExchangeTokenHashDto = exports.ExchangeRefreshTokenDto = exports.createSupabaseAuthClient = exports.SupabaseAuthClient = exports.createSupabaseClient = exports.SupabaseClient = void 0;
|
|
11
|
-
exports.monthlyTotalMinor = exports.todayInJohannesburg = exports.leavePeriodFor = exports.isWithinPeriod = exports.countWorkingDays = exports.addOneYearMinusDay = exports.GetPublicHolidaysDto = exports.UpdateLeaveTypeDto = exports.UpdateLeaveRequestDto = exports.UpdateLeaveEntitlementDto = exports.ReviewLeaveRequestDto = exports.LeaveTypeIdDto = exports.LeaveRequestIdDto = exports.LeaveEntitlementIdDto = exports.GetLeaveTypesDto = exports.GetLeaveRequestsDto = exports.GetLeaveEntitlementsDto = exports.CreateLeaveTypeDto = exports.CreateLeaveRequestDto = exports.CreateLeaveEntitlementDto = exports.UpdateTaskDto = exports.TaskIdDto = exports.GetTasksDto = exports.CreateTaskDto = exports.UpdateProjectDto = exports.ProjectIdDto = exports.GetProjectsDto = exports.CreateProjectDto = exports.UpdateClientDto = exports.GetClientsDto = exports.CreateClientDto = exports.ClientIdDto = exports.SupportingFileIdDto = exports.GetSupportingFilesDto = void 0;
|
|
11
|
+
exports.monthlyTotalMinor = exports.weekStartOf = exports.minutesBetween = exports.UpdateTimesheetEntryDto = exports.TimesheetIdDto = exports.TimesheetEntryIdDto = exports.ReviewTimesheetDto = exports.GetTimesheetsDto = exports.GetTimesheetReportDto = exports.CreateTimesheetEntryDto = exports.todayInJohannesburg = exports.shiftDays = exports.leavePeriodFor = exports.isWithinPeriod = exports.countWorkingDays = exports.addOneYearMinusDay = exports.GetPublicHolidaysDto = exports.UpdateLeaveTypeDto = exports.UpdateLeaveRequestDto = exports.UpdateLeaveEntitlementDto = exports.ReviewLeaveRequestDto = exports.LeaveTypeIdDto = exports.LeaveRequestIdDto = exports.LeaveEntitlementIdDto = exports.GetLeaveTypesDto = exports.GetLeaveRequestsDto = exports.GetLeaveEntitlementsDto = exports.CreateLeaveTypeDto = exports.CreateLeaveRequestDto = exports.CreateLeaveEntitlementDto = exports.UpdateTaskDto = exports.TaskIdDto = exports.GetTasksDto = exports.CreateTaskDto = exports.UpdateProjectDto = exports.ProjectIdDto = exports.GetProjectsDto = exports.CreateProjectDto = exports.UpdateClientDto = exports.GetClientsDto = exports.CreateClientDto = exports.ClientIdDto = exports.SupportingFileIdDto = exports.GetSupportingFilesDto = void 0;
|
|
12
12
|
var client_1 = require("./client");
|
|
13
13
|
Object.defineProperty(exports, "SupabaseClient", { enumerable: true, get: function () { return client_1.SupabaseClient; } });
|
|
14
14
|
Object.defineProperty(exports, "createSupabaseClient", { enumerable: true, get: function () { return client_1.createSupabaseClient; } });
|
|
@@ -122,6 +122,19 @@ Object.defineProperty(exports, "addOneYearMinusDay", { enumerable: true, get: fu
|
|
|
122
122
|
Object.defineProperty(exports, "countWorkingDays", { enumerable: true, get: function () { return leave_2.countWorkingDays; } });
|
|
123
123
|
Object.defineProperty(exports, "isWithinPeriod", { enumerable: true, get: function () { return leave_2.isWithinPeriod; } });
|
|
124
124
|
Object.defineProperty(exports, "leavePeriodFor", { enumerable: true, get: function () { return leave_2.leavePeriodFor; } });
|
|
125
|
+
Object.defineProperty(exports, "shiftDays", { enumerable: true, get: function () { return leave_2.shiftDays; } });
|
|
125
126
|
Object.defineProperty(exports, "todayInJohannesburg", { enumerable: true, get: function () { return leave_2.todayInJohannesburg; } });
|
|
127
|
+
var timesheets_1 = require("./timesheets");
|
|
128
|
+
Object.defineProperty(exports, "CreateTimesheetEntryDto", { enumerable: true, get: function () { return timesheets_1.CreateTimesheetEntryDto; } });
|
|
129
|
+
Object.defineProperty(exports, "GetTimesheetReportDto", { enumerable: true, get: function () { return timesheets_1.GetTimesheetReportDto; } });
|
|
130
|
+
Object.defineProperty(exports, "GetTimesheetsDto", { enumerable: true, get: function () { return timesheets_1.GetTimesheetsDto; } });
|
|
131
|
+
Object.defineProperty(exports, "ReviewTimesheetDto", { enumerable: true, get: function () { return timesheets_1.ReviewTimesheetDto; } });
|
|
132
|
+
Object.defineProperty(exports, "TimesheetEntryIdDto", { enumerable: true, get: function () { return timesheets_1.TimesheetEntryIdDto; } });
|
|
133
|
+
Object.defineProperty(exports, "TimesheetIdDto", { enumerable: true, get: function () { return timesheets_1.TimesheetIdDto; } });
|
|
134
|
+
Object.defineProperty(exports, "UpdateTimesheetEntryDto", { enumerable: true, get: function () { return timesheets_1.UpdateTimesheetEntryDto; } });
|
|
135
|
+
// The week arithmetic, like the leave calendar: plain functions the api resolves a week with.
|
|
136
|
+
var timesheets_2 = require("./timesheets");
|
|
137
|
+
Object.defineProperty(exports, "minutesBetween", { enumerable: true, get: function () { return timesheets_2.minutesBetween; } });
|
|
138
|
+
Object.defineProperty(exports, "weekStartOf", { enumerable: true, get: function () { return timesheets_2.weekStartOf; } });
|
|
126
139
|
var pricebooks_1 = require("./pricebooks");
|
|
127
140
|
Object.defineProperty(exports, "monthlyTotalMinor", { enumerable: true, get: function () { return pricebooks_1.monthlyTotalMinor; } });
|
package/dist/leave/dto.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare class CreateLeaveTypeDto implements CreateLeaveTypeDtoType {
|
|
|
12
12
|
readonly default_days: number;
|
|
13
13
|
readonly is_paid: boolean;
|
|
14
14
|
readonly requires_approval: boolean;
|
|
15
|
+
readonly requires_document: boolean;
|
|
15
16
|
}
|
|
16
17
|
export declare class UpdateLeaveTypeDto implements UpdateLeaveTypeDtoType {
|
|
17
18
|
readonly name: string;
|
|
@@ -19,6 +20,7 @@ export declare class UpdateLeaveTypeDto implements UpdateLeaveTypeDtoType {
|
|
|
19
20
|
readonly default_days: number;
|
|
20
21
|
readonly is_paid: boolean;
|
|
21
22
|
readonly requires_approval: boolean;
|
|
23
|
+
readonly requires_document: boolean;
|
|
22
24
|
readonly active: boolean;
|
|
23
25
|
}
|
|
24
26
|
export declare class LeaveEntitlementIdDto implements LeaveEntitlementIdDtoType {
|
|
@@ -35,12 +37,14 @@ export declare class CreateLeaveEntitlementDto implements CreateLeaveEntitlement
|
|
|
35
37
|
readonly user_id: string;
|
|
36
38
|
readonly leave_type_id: string;
|
|
37
39
|
readonly days_entitled: number;
|
|
40
|
+
readonly max_carry_over_days: number;
|
|
38
41
|
readonly period_start: string | null;
|
|
39
42
|
readonly period_end: string | null;
|
|
40
43
|
readonly notes: string | null;
|
|
41
44
|
}
|
|
42
45
|
export declare class UpdateLeaveEntitlementDto implements UpdateLeaveEntitlementDtoType {
|
|
43
46
|
readonly days_entitled: number;
|
|
47
|
+
readonly max_carry_over_days: number;
|
|
44
48
|
readonly period_end: string;
|
|
45
49
|
readonly notes: string | null;
|
|
46
50
|
}
|
package/dist/leave/dto.js
CHANGED
|
@@ -52,6 +52,7 @@ class CreateLeaveTypeDto {
|
|
|
52
52
|
default_days;
|
|
53
53
|
is_paid;
|
|
54
54
|
requires_approval;
|
|
55
|
+
requires_document;
|
|
55
56
|
}
|
|
56
57
|
exports.CreateLeaveTypeDto = CreateLeaveTypeDto;
|
|
57
58
|
__decorate([
|
|
@@ -82,12 +83,17 @@ __decorate([
|
|
|
82
83
|
(0, class_validator_1.IsBoolean)({ message: 'Requires approval must be true or false' }),
|
|
83
84
|
__metadata("design:type", Boolean)
|
|
84
85
|
], CreateLeaveTypeDto.prototype, "requires_approval", void 0);
|
|
86
|
+
__decorate([
|
|
87
|
+
(0, class_validator_1.IsBoolean)({ message: 'Requires document must be true or false' }),
|
|
88
|
+
__metadata("design:type", Boolean)
|
|
89
|
+
], CreateLeaveTypeDto.prototype, "requires_document", void 0);
|
|
85
90
|
class UpdateLeaveTypeDto {
|
|
86
91
|
name;
|
|
87
92
|
description;
|
|
88
93
|
default_days;
|
|
89
94
|
is_paid;
|
|
90
95
|
requires_approval;
|
|
96
|
+
requires_document;
|
|
91
97
|
active;
|
|
92
98
|
}
|
|
93
99
|
exports.UpdateLeaveTypeDto = UpdateLeaveTypeDto;
|
|
@@ -119,6 +125,10 @@ __decorate([
|
|
|
119
125
|
(0, class_validator_1.IsBoolean)({ message: 'Requires approval must be true or false' }),
|
|
120
126
|
__metadata("design:type", Boolean)
|
|
121
127
|
], UpdateLeaveTypeDto.prototype, "requires_approval", void 0);
|
|
128
|
+
__decorate([
|
|
129
|
+
(0, class_validator_1.IsBoolean)({ message: 'Requires document must be true or false' }),
|
|
130
|
+
__metadata("design:type", Boolean)
|
|
131
|
+
], UpdateLeaveTypeDto.prototype, "requires_document", void 0);
|
|
122
132
|
__decorate([
|
|
123
133
|
(0, class_validator_1.IsBoolean)({ message: 'Active must be true or false' }),
|
|
124
134
|
__metadata("design:type", Boolean)
|
|
@@ -177,6 +187,7 @@ class CreateLeaveEntitlementDto {
|
|
|
177
187
|
user_id;
|
|
178
188
|
leave_type_id;
|
|
179
189
|
days_entitled;
|
|
190
|
+
max_carry_over_days;
|
|
180
191
|
period_start;
|
|
181
192
|
period_end;
|
|
182
193
|
notes;
|
|
@@ -196,6 +207,12 @@ __decorate([
|
|
|
196
207
|
(0, class_validator_1.Max)(999.9, { message: 'Days entitled cannot exceed 999.9' }),
|
|
197
208
|
__metadata("design:type", Number)
|
|
198
209
|
], CreateLeaveEntitlementDto.prototype, "days_entitled", void 0);
|
|
210
|
+
__decorate([
|
|
211
|
+
(0, class_validator_1.IsNumber)({ maxDecimalPlaces: 1 }, { message: 'Max carry over days must be a number with at most one decimal' }),
|
|
212
|
+
(0, class_validator_1.Min)(0, { message: 'Max carry over days cannot be negative' }),
|
|
213
|
+
(0, class_validator_1.Max)(999.9, { message: 'Max carry over days cannot exceed 999.9' }),
|
|
214
|
+
__metadata("design:type", Number)
|
|
215
|
+
], CreateLeaveEntitlementDto.prototype, "max_carry_over_days", void 0);
|
|
199
216
|
__decorate([
|
|
200
217
|
(0, validators_1.IsNullable)(),
|
|
201
218
|
(0, validators_1.IsCalendarDate)({ message: 'Period start must be a calendar date (YYYY-MM-DD)' }),
|
|
@@ -215,6 +232,7 @@ __decorate([
|
|
|
215
232
|
], CreateLeaveEntitlementDto.prototype, "notes", void 0);
|
|
216
233
|
class UpdateLeaveEntitlementDto {
|
|
217
234
|
days_entitled;
|
|
235
|
+
max_carry_over_days;
|
|
218
236
|
period_end;
|
|
219
237
|
notes;
|
|
220
238
|
}
|
|
@@ -225,6 +243,12 @@ __decorate([
|
|
|
225
243
|
(0, class_validator_1.Max)(999.9, { message: 'Days entitled cannot exceed 999.9' }),
|
|
226
244
|
__metadata("design:type", Number)
|
|
227
245
|
], UpdateLeaveEntitlementDto.prototype, "days_entitled", void 0);
|
|
246
|
+
__decorate([
|
|
247
|
+
(0, class_validator_1.IsNumber)({ maxDecimalPlaces: 1 }, { message: 'Max carry over days must be a number with at most one decimal' }),
|
|
248
|
+
(0, class_validator_1.Min)(0, { message: 'Max carry over days cannot be negative' }),
|
|
249
|
+
(0, class_validator_1.Max)(999.9, { message: 'Max carry over days cannot exceed 999.9' }),
|
|
250
|
+
__metadata("design:type", Number)
|
|
251
|
+
], UpdateLeaveEntitlementDto.prototype, "max_carry_over_days", void 0);
|
|
228
252
|
__decorate([
|
|
229
253
|
(0, validators_1.IsCalendarDate)({ message: 'Period end must be a calendar date (YYYY-MM-DD)' }),
|
|
230
254
|
__metadata("design:type", String)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { MmsList } from '@managemint-solutions/entities/common';
|
|
2
2
|
import type { LeaveEntitlementEntity, LeavePeriod } from '@managemint-solutions/entities/leave';
|
|
3
|
-
import type
|
|
4
|
-
import { type AuditResource } from '../audit';
|
|
3
|
+
import { type AuditActor, type AuditResource } from '../audit';
|
|
5
4
|
import type { TypedSupabaseClient } from '../client';
|
|
6
5
|
import type { GetLeaveEntitlementsDto, UpdateLeaveEntitlementDto } from './dto';
|
|
7
6
|
/** What the request path needs of the entitlement covering a date: the period and the cap. */
|
|
@@ -14,13 +13,14 @@ export type CreateLeaveEntitlementInput = LeavePeriod & {
|
|
|
14
13
|
user_id: string;
|
|
15
14
|
leave_type_id: string;
|
|
16
15
|
days_entitled: number;
|
|
16
|
+
max_carry_over_days: number;
|
|
17
17
|
notes?: string | null;
|
|
18
18
|
};
|
|
19
19
|
export declare class LeaveEntitlementsResource {
|
|
20
20
|
private readonly supabase;
|
|
21
21
|
private readonly auth;
|
|
22
22
|
private readonly table;
|
|
23
|
-
constructor(supabase: TypedSupabaseClient, auth:
|
|
23
|
+
constructor(supabase: TypedSupabaseClient, auth: AuditActor, audit: AuditResource);
|
|
24
24
|
list(query: GetLeaveEntitlementsDto): Promise<MmsList<LeaveEntitlementEntity>>;
|
|
25
25
|
getById(leaveEntitlementId: string): Promise<LeaveEntitlementEntity>;
|
|
26
26
|
/**
|
|
@@ -35,6 +35,12 @@ export declare class LeaveEntitlementsResource {
|
|
|
35
35
|
* that survives a race. `excludeId` leaves out the entitlement being edited.
|
|
36
36
|
*/
|
|
37
37
|
overlaps(userId: string, leaveTypeId: string, start: string, end: string, excludeId?: string): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Every entitlement of the organization whose period ends on `periodEnd`, balances included -
|
|
40
|
+
* what the leave-year rollover carries forward. Walks the table in pages so a large tenant is
|
|
41
|
+
* not cut short by the row cap.
|
|
42
|
+
*/
|
|
43
|
+
endingOn(periodEnd: string): Promise<LeaveEntitlementEntity[]>;
|
|
38
44
|
create(input: CreateLeaveEntitlementInput): Promise<LeaveEntitlementEntity>;
|
|
39
45
|
update(leaveEntitlementId: string, input: UpdateLeaveEntitlementDto): Promise<LeaveEntitlementEntity>;
|
|
40
46
|
/** Hard delete: the type simply becomes untracked for that user and period. */
|
|
@@ -43,6 +49,7 @@ export declare class LeaveEntitlementsResource {
|
|
|
43
49
|
* Adds the computed balance to each row from one query over the requests that could count
|
|
44
50
|
* against any of them - the approved and pending ones of these users and types, starting in
|
|
45
51
|
* the span the periods cover. Nothing is stored: a balance is always what the requests say.
|
|
52
|
+
* Paged, since `endingOn` hands it a whole tenant at once.
|
|
46
53
|
*/
|
|
47
54
|
private withBalances;
|
|
48
55
|
}
|
|
@@ -15,11 +15,13 @@ const LEAVE_ENTITLEMENT_SELECT = `
|
|
|
15
15
|
user:user_profiles!user_id (*),
|
|
16
16
|
leave_type:leave_types!leave_type_id (
|
|
17
17
|
mms_id,
|
|
18
|
-
name
|
|
18
|
+
name,
|
|
19
|
+
requires_document
|
|
19
20
|
),
|
|
20
21
|
period_start,
|
|
21
22
|
period_end,
|
|
22
23
|
days_entitled,
|
|
24
|
+
max_carry_over_days,
|
|
23
25
|
notes
|
|
24
26
|
`;
|
|
25
27
|
class LeaveEntitlementsResource {
|
|
@@ -116,6 +118,21 @@ class LeaveEntitlementsResource {
|
|
|
116
118
|
throw (0, errors_1.mapPostgrestError)(error);
|
|
117
119
|
return (count ?? 0) > 0;
|
|
118
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Every entitlement of the organization whose period ends on `periodEnd`, balances included -
|
|
123
|
+
* what the leave-year rollover carries forward. Walks the table in pages so a large tenant is
|
|
124
|
+
* not cut short by the row cap.
|
|
125
|
+
*/
|
|
126
|
+
async endingOn(periodEnd) {
|
|
127
|
+
const rows = await (0, transforms_1.allRows)((from, to) => this.supabase
|
|
128
|
+
.from('leave_entitlements')
|
|
129
|
+
.select(LEAVE_ENTITLEMENT_SELECT)
|
|
130
|
+
.eq('organization_id', this.auth.organization_id)
|
|
131
|
+
.eq('period_end', periodEnd)
|
|
132
|
+
.order('mms_id')
|
|
133
|
+
.range(from, to));
|
|
134
|
+
return this.withBalances(rows);
|
|
135
|
+
}
|
|
119
136
|
async create(input) {
|
|
120
137
|
const row = await this.table.insert({
|
|
121
138
|
user_id: input.user_id,
|
|
@@ -123,6 +140,7 @@ class LeaveEntitlementsResource {
|
|
|
123
140
|
period_start: input.period_start,
|
|
124
141
|
period_end: input.period_end,
|
|
125
142
|
days_entitled: input.days_entitled,
|
|
143
|
+
max_carry_over_days: input.max_carry_over_days,
|
|
126
144
|
notes: input.notes ?? null,
|
|
127
145
|
organization_id: this.auth.organization_id,
|
|
128
146
|
created_by: this.auth.mms_id,
|
|
@@ -133,6 +151,7 @@ class LeaveEntitlementsResource {
|
|
|
133
151
|
async update(leaveEntitlementId, input) {
|
|
134
152
|
await this.table.update(leaveEntitlementId, {
|
|
135
153
|
days_entitled: input.days_entitled,
|
|
154
|
+
max_carry_over_days: input.max_carry_over_days,
|
|
136
155
|
period_end: input.period_end,
|
|
137
156
|
notes: input.notes,
|
|
138
157
|
last_updated_by: this.auth.mms_id,
|
|
@@ -148,6 +167,7 @@ class LeaveEntitlementsResource {
|
|
|
148
167
|
* Adds the computed balance to each row from one query over the requests that could count
|
|
149
168
|
* against any of them - the approved and pending ones of these users and types, starting in
|
|
150
169
|
* the span the periods cover. Nothing is stored: a balance is always what the requests say.
|
|
170
|
+
* Paged, since `endingOn` hands it a whole tenant at once.
|
|
151
171
|
*/
|
|
152
172
|
async withBalances(rows) {
|
|
153
173
|
if (rows.length === 0)
|
|
@@ -156,7 +176,7 @@ class LeaveEntitlementsResource {
|
|
|
156
176
|
const typeIds = [...new Set(rows.map((row) => row.leave_type.mms_id))];
|
|
157
177
|
const earliest = rows.map((row) => row.period_start).sort()[0];
|
|
158
178
|
const latest = rows.map((row) => row.period_end).sort().reverse()[0];
|
|
159
|
-
const
|
|
179
|
+
const requests = await (0, transforms_1.allRows)((from, to) => this.supabase
|
|
160
180
|
.from('leave_requests')
|
|
161
181
|
.select('user_id, leave_type_id, start_date, days, status')
|
|
162
182
|
.eq('organization_id', this.auth.organization_id)
|
|
@@ -164,10 +184,9 @@ class LeaveEntitlementsResource {
|
|
|
164
184
|
.in('leave_type_id', typeIds)
|
|
165
185
|
.in('status', [enum_2.LeaveRequestStatus.APPROVED, enum_2.LeaveRequestStatus.PENDING])
|
|
166
186
|
.gte('start_date', earliest)
|
|
167
|
-
.lte('start_date', latest)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const requests = (data ?? []);
|
|
187
|
+
.lte('start_date', latest)
|
|
188
|
+
.order('mms_id')
|
|
189
|
+
.range(from, to));
|
|
171
190
|
return rows.map((row) => {
|
|
172
191
|
const mine = requests.filter((request) => request.user_id === row.user.mms_id &&
|
|
173
192
|
request.leave_type_id === row.leave_type.mms_id &&
|
|
@@ -182,6 +201,7 @@ class LeaveEntitlementsResource {
|
|
|
182
201
|
return {
|
|
183
202
|
...row,
|
|
184
203
|
days_entitled: daysEntitled,
|
|
204
|
+
max_carry_over_days: Number(row.max_carry_over_days),
|
|
185
205
|
days_used: daysUsed,
|
|
186
206
|
days_pending: daysPending,
|
|
187
207
|
days_remaining: daysEntitled - daysUsed - daysPending,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { MmsList } from '@managemint-solutions/entities/common';
|
|
2
2
|
import type { LeavePeriod, LeaveRequestEntity } from '@managemint-solutions/entities/leave';
|
|
3
3
|
import { LeaveRequestStatus, LeaveReviewDecision } from '@managemint-solutions/entities/leave/enum';
|
|
4
|
-
import type
|
|
5
|
-
import { type AuditResource } from '../audit';
|
|
4
|
+
import { type AuditActor, type AuditResource } from '../audit';
|
|
6
5
|
import type { TypedSupabaseClient } from '../client';
|
|
7
6
|
import type { GetLeaveRequestsDto } from './dto';
|
|
8
7
|
/** A request as the api inserts it: the caller's own, with the days already counted. */
|
|
@@ -40,7 +39,7 @@ export declare class LeaveRequestsResource {
|
|
|
40
39
|
private readonly supabase;
|
|
41
40
|
private readonly auth;
|
|
42
41
|
private readonly table;
|
|
43
|
-
constructor(supabase: TypedSupabaseClient, auth:
|
|
42
|
+
constructor(supabase: TypedSupabaseClient, auth: AuditActor, audit: AuditResource);
|
|
44
43
|
list(query: GetLeaveRequestsDto, options?: ListOptions): Promise<MmsList<LeaveRequestEntity>>;
|
|
45
44
|
getById(leaveRequestId: string): Promise<LeaveRequestEntity>;
|
|
46
45
|
/**
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { LeaveTypeEntity } from '@managemint-solutions/entities/leave';
|
|
2
|
-
import type
|
|
3
|
-
import { type AuditResource } from '../audit';
|
|
2
|
+
import { type AuditActor, type AuditResource } from '../audit';
|
|
4
3
|
import type { TypedSupabaseClient } from '../client';
|
|
5
4
|
import type { CreateLeaveTypeDto, GetLeaveTypesDto, UpdateLeaveTypeDto } from './dto';
|
|
6
5
|
export declare class LeaveTypesResource {
|
|
7
6
|
private readonly supabase;
|
|
8
7
|
private readonly auth;
|
|
9
8
|
private readonly table;
|
|
10
|
-
constructor(supabase: TypedSupabaseClient, auth:
|
|
9
|
+
constructor(supabase: TypedSupabaseClient, auth: AuditActor, audit: AuditResource);
|
|
11
10
|
/** Every type by name; `active` narrows to one side of the flag. Small enough not to page. */
|
|
12
11
|
list(query: GetLeaveTypesDto): Promise<LeaveTypeEntity[]>;
|
|
13
12
|
getById(leaveTypeId: string): Promise<LeaveTypeEntity>;
|
|
@@ -18,6 +18,7 @@ const LEAVE_TYPE_SELECT = `
|
|
|
18
18
|
default_days,
|
|
19
19
|
is_paid,
|
|
20
20
|
requires_approval,
|
|
21
|
+
requires_document,
|
|
21
22
|
active
|
|
22
23
|
`;
|
|
23
24
|
class LeaveTypesResource {
|
|
@@ -65,6 +66,7 @@ class LeaveTypesResource {
|
|
|
65
66
|
default_days: input.default_days,
|
|
66
67
|
is_paid: input.is_paid,
|
|
67
68
|
requires_approval: input.requires_approval,
|
|
69
|
+
requires_document: input.requires_document,
|
|
68
70
|
organization_id: this.auth.organization_id,
|
|
69
71
|
created_by: this.auth.mms_id,
|
|
70
72
|
created_at: new Date().toISOString(),
|
|
@@ -78,6 +80,7 @@ class LeaveTypesResource {
|
|
|
78
80
|
default_days: input.default_days,
|
|
79
81
|
is_paid: input.is_paid,
|
|
80
82
|
requires_approval: input.requires_approval,
|
|
83
|
+
requires_document: input.requires_document,
|
|
81
84
|
active: input.active,
|
|
82
85
|
last_updated_by: this.auth.mms_id,
|
|
83
86
|
updated_at: new Date().toISOString(),
|
|
@@ -4,6 +4,8 @@ import type { LeavePeriod } from '@managemint-solutions/entities/leave';
|
|
|
4
4
|
* customer is South African; this is the one place to change when a column arrives.
|
|
5
5
|
*/
|
|
6
6
|
export declare const LEAVE_TIME_ZONE = "Africa/Johannesburg";
|
|
7
|
+
/** A `Date` at UTC midnight of a `YYYY-MM-DD` string (a longer ISO string is cut to its date). */
|
|
8
|
+
export declare const parseIsoDate: (value: string) => Date;
|
|
7
9
|
/** The `YYYY-MM-DD` of a `Date`'s UTC calendar day. */
|
|
8
10
|
export declare const toIsoDate: (date: Date) => string;
|
|
9
11
|
/** Today's calendar date in `LEAVE_TIME_ZONE`. */
|
|
@@ -18,6 +20,8 @@ export declare const isWorkingDay: (isoDate: string, holidays: ReadonlySet<strin
|
|
|
18
20
|
* on a weekend or holiday - the caller rejects a zero either way.
|
|
19
21
|
*/
|
|
20
22
|
export declare const countWorkingDays: (start: string, end: string, holidays: ReadonlySet<string>, isHalfDay?: boolean) => number;
|
|
23
|
+
/** `isoDate` moved by `days` calendar days, either way; `shiftDays('2026-03-01', -1)` is Feb 28. */
|
|
24
|
+
export declare const shiftDays: (isoDate: string, days: number) => string;
|
|
21
25
|
/** The day before the same date next year - the end of a period that starts on `isoDate`. */
|
|
22
26
|
export declare const addOneYearMinusDay: (isoDate: string) => string;
|
|
23
27
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isWithinPeriod = exports.leavePeriodFor = exports.addOneYearMinusDay = exports.countWorkingDays = exports.isWorkingDay = exports.eachDate = exports.todayInJohannesburg = exports.toIsoDate = exports.LEAVE_TIME_ZONE = void 0;
|
|
3
|
+
exports.isWithinPeriod = exports.leavePeriodFor = exports.addOneYearMinusDay = exports.shiftDays = exports.countWorkingDays = exports.isWorkingDay = exports.eachDate = exports.todayInJohannesburg = exports.toIsoDate = exports.parseIsoDate = exports.LEAVE_TIME_ZONE = void 0;
|
|
4
4
|
// The calendar arithmetic behind leave: which days of a request count, and which leave year a
|
|
5
5
|
// date belongs to. Pure functions over `YYYY-MM-DD` strings - the columns are `date`, so a
|
|
6
6
|
// request never carries a time and nothing here depends on the caller's clock zone.
|
|
@@ -17,6 +17,7 @@ const parseIsoDate = (value) => {
|
|
|
17
17
|
const [year, month, day] = value.slice(0, 10).split('-').map(Number);
|
|
18
18
|
return new Date(Date.UTC(year, month - 1, day));
|
|
19
19
|
};
|
|
20
|
+
exports.parseIsoDate = parseIsoDate;
|
|
20
21
|
/** The `YYYY-MM-DD` of a `Date`'s UTC calendar day. */
|
|
21
22
|
const toIsoDate = (date) => `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}`;
|
|
22
23
|
exports.toIsoDate = toIsoDate;
|
|
@@ -34,14 +35,14 @@ exports.todayInJohannesburg = todayInJohannesburg;
|
|
|
34
35
|
const eachDate = (start, end) => {
|
|
35
36
|
const dates = [];
|
|
36
37
|
const last = end.slice(0, 10);
|
|
37
|
-
for (const cursor = parseIsoDate(start); (0, exports.toIsoDate)(cursor) <= last; cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
38
|
+
for (const cursor = (0, exports.parseIsoDate)(start); (0, exports.toIsoDate)(cursor) <= last; cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
38
39
|
dates.push((0, exports.toIsoDate)(cursor));
|
|
39
40
|
}
|
|
40
41
|
return dates;
|
|
41
42
|
};
|
|
42
43
|
exports.eachDate = eachDate;
|
|
43
44
|
/** Monday to Friday and not one of the organization's holidays. */
|
|
44
|
-
const isWorkingDay = (isoDate, holidays) => !WEEKEND_DAYS.has(parseIsoDate(isoDate).getUTCDay()) && !holidays.has(isoDate.slice(0, 10));
|
|
45
|
+
const isWorkingDay = (isoDate, holidays) => !WEEKEND_DAYS.has((0, exports.parseIsoDate)(isoDate).getUTCDay()) && !holidays.has(isoDate.slice(0, 10));
|
|
45
46
|
exports.isWorkingDay = isWorkingDay;
|
|
46
47
|
/**
|
|
47
48
|
* The days a request is worth: the working days from `start` to `end` inclusive, halved for a
|
|
@@ -53,9 +54,16 @@ const countWorkingDays = (start, end, holidays, isHalfDay = false) => {
|
|
|
53
54
|
return isHalfDay ? working * 0.5 : working;
|
|
54
55
|
};
|
|
55
56
|
exports.countWorkingDays = countWorkingDays;
|
|
57
|
+
/** `isoDate` moved by `days` calendar days, either way; `shiftDays('2026-03-01', -1)` is Feb 28. */
|
|
58
|
+
const shiftDays = (isoDate, days) => {
|
|
59
|
+
const date = (0, exports.parseIsoDate)(isoDate);
|
|
60
|
+
date.setUTCDate(date.getUTCDate() + days);
|
|
61
|
+
return (0, exports.toIsoDate)(date);
|
|
62
|
+
};
|
|
63
|
+
exports.shiftDays = shiftDays;
|
|
56
64
|
/** The day before the same date next year - the end of a period that starts on `isoDate`. */
|
|
57
65
|
const addOneYearMinusDay = (isoDate) => {
|
|
58
|
-
const date = parseIsoDate(isoDate);
|
|
66
|
+
const date = (0, exports.parseIsoDate)(isoDate);
|
|
59
67
|
date.setUTCFullYear(date.getUTCFullYear() + 1);
|
|
60
68
|
date.setUTCDate(date.getUTCDate() - 1);
|
|
61
69
|
return (0, exports.toIsoDate)(date);
|
|
@@ -67,7 +75,7 @@ exports.addOneYearMinusDay = addOneYearMinusDay;
|
|
|
67
75
|
* 2026-02-10 in the year that began 2025-03-01.
|
|
68
76
|
*/
|
|
69
77
|
const leavePeriodFor = (date, startMonth) => {
|
|
70
|
-
const parsed = parseIsoDate(date);
|
|
78
|
+
const parsed = (0, exports.parseIsoDate)(date);
|
|
71
79
|
const year = parsed.getUTCMonth() + 1 >= startMonth ? parsed.getUTCFullYear() : parsed.getUTCFullYear() - 1;
|
|
72
80
|
const period_start = (0, exports.toIsoDate)(new Date(Date.UTC(year, startMonth - 1, 1)));
|
|
73
81
|
return { period_start, period_end: (0, exports.addOneYearMinusDay)(period_start) };
|
|
@@ -12,6 +12,10 @@ export declare class UpdatePermissionsDto implements UpdatePermissionsDtoType {
|
|
|
12
12
|
readonly read_tasks: boolean;
|
|
13
13
|
readonly update_tasks: boolean;
|
|
14
14
|
readonly delete_tasks: boolean;
|
|
15
|
+
readonly create_timesheets: boolean;
|
|
16
|
+
readonly read_timesheets: boolean;
|
|
17
|
+
readonly update_timesheets: boolean;
|
|
18
|
+
readonly delete_timesheets: boolean;
|
|
15
19
|
readonly create_leave: boolean;
|
|
16
20
|
readonly read_leave: boolean;
|
|
17
21
|
readonly update_leave: boolean;
|
package/dist/permissions/dto.js
CHANGED
|
@@ -34,6 +34,10 @@ class UpdatePermissionsDto {
|
|
|
34
34
|
read_tasks;
|
|
35
35
|
update_tasks;
|
|
36
36
|
delete_tasks;
|
|
37
|
+
create_timesheets;
|
|
38
|
+
read_timesheets;
|
|
39
|
+
update_timesheets;
|
|
40
|
+
delete_timesheets;
|
|
37
41
|
// @IsBoolean({ message: 'Create Documents must be a boolean' })
|
|
38
42
|
// readonly create_documents!: boolean;
|
|
39
43
|
// @IsBoolean({ message: 'Read Documents must be a boolean' })
|
|
@@ -152,6 +156,22 @@ __decorate([
|
|
|
152
156
|
(0, class_validator_1.IsBoolean)({ message: 'Delete Tasks must be a boolean' }),
|
|
153
157
|
__metadata("design:type", Boolean)
|
|
154
158
|
], UpdatePermissionsDto.prototype, "delete_tasks", void 0);
|
|
159
|
+
__decorate([
|
|
160
|
+
(0, class_validator_1.IsBoolean)({ message: 'Create Timesheets must be a boolean' }),
|
|
161
|
+
__metadata("design:type", Boolean)
|
|
162
|
+
], UpdatePermissionsDto.prototype, "create_timesheets", void 0);
|
|
163
|
+
__decorate([
|
|
164
|
+
(0, class_validator_1.IsBoolean)({ message: 'Read Timesheets must be a boolean' }),
|
|
165
|
+
__metadata("design:type", Boolean)
|
|
166
|
+
], UpdatePermissionsDto.prototype, "read_timesheets", void 0);
|
|
167
|
+
__decorate([
|
|
168
|
+
(0, class_validator_1.IsBoolean)({ message: 'Update Timesheets must be a boolean' }),
|
|
169
|
+
__metadata("design:type", Boolean)
|
|
170
|
+
], UpdatePermissionsDto.prototype, "update_timesheets", void 0);
|
|
171
|
+
__decorate([
|
|
172
|
+
(0, class_validator_1.IsBoolean)({ message: 'Delete Timesheets must be a boolean' }),
|
|
173
|
+
__metadata("design:type", Boolean)
|
|
174
|
+
], UpdatePermissionsDto.prototype, "delete_timesheets", void 0);
|
|
155
175
|
__decorate([
|
|
156
176
|
(0, class_validator_1.IsBoolean)({ message: 'Create Leave must be a boolean' }),
|
|
157
177
|
__metadata("design:type", Boolean)
|