@managemint-solutions/sdk 0.44.0 → 0.49.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 +9 -8
- package/dist/client.d.ts +2 -1
- package/dist/client.js +2 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +12 -6
- package/dist/leave/dto.d.ts +44 -16
- package/dist/leave/dto.js +183 -57
- package/dist/leave/index.d.ts +3 -1
- package/dist/leave/index.js +7 -3
- package/dist/leave/leave-adjustments.d.ts +18 -0
- package/dist/leave/leave-adjustments.js +59 -0
- package/dist/leave/leave-entitlements.d.ts +24 -18
- package/dist/leave/leave-entitlements.js +76 -44
- package/dist/leave/leave-policy.d.ts +50 -0
- package/dist/leave/leave-policy.js +98 -0
- package/dist/leave/leave-requests.d.ts +9 -10
- package/dist/leave/leave-requests.js +41 -26
- package/dist/leave/leave-types.js +40 -5
- package/dist/leave/working-days.d.ts +14 -10
- package/dist/leave/working-days.js +31 -24
- package/dist/organizations/dto.d.ts +1 -0
- package/dist/organizations/dto.js +8 -0
- package/dist/pricebooks/index.js +5 -3
- package/dist/service-client/index.d.ts +19 -5
- package/dist/service-client/index.js +35 -6
- package/dist/users/dto.d.ts +2 -0
- package/dist/users/dto.js +20 -0
- package/dist/users/index.d.ts +12 -2
- package/dist/users/index.js +22 -7
- package/package.json +2 -2
|
@@ -16,7 +16,7 @@ const LEAVE_REQUEST_SELECT = `
|
|
|
16
16
|
leave_type:leave_types!leave_type_id (
|
|
17
17
|
mms_id,
|
|
18
18
|
name,
|
|
19
|
-
|
|
19
|
+
document_after_days
|
|
20
20
|
),
|
|
21
21
|
start_date,
|
|
22
22
|
end_date,
|
|
@@ -30,6 +30,19 @@ const LEAVE_REQUEST_SELECT = `
|
|
|
30
30
|
cancelled_by:user_profiles!cancelled_by (*),
|
|
31
31
|
cancelled_at
|
|
32
32
|
`;
|
|
33
|
+
const LEAVE_CALENDAR_SELECT = `
|
|
34
|
+
mms_id,
|
|
35
|
+
user:user_profiles!user_id (*),
|
|
36
|
+
created_by:user_profiles!created_by (*),
|
|
37
|
+
leave_type:leave_types!leave_type_id (
|
|
38
|
+
mms_id,
|
|
39
|
+
name
|
|
40
|
+
),
|
|
41
|
+
start_date,
|
|
42
|
+
end_date,
|
|
43
|
+
is_half_day,
|
|
44
|
+
status
|
|
45
|
+
`;
|
|
33
46
|
/** The statuses that hold days: a pending request reserves them, an approved one uses them. */
|
|
34
47
|
const LIVE_STATUSES = [enum_2.LeaveRequestStatus.PENDING, enum_2.LeaveRequestStatus.APPROVED];
|
|
35
48
|
class LeaveRequestsResource {
|
|
@@ -110,28 +123,27 @@ class LeaveRequestsResource {
|
|
|
110
123
|
throw (0, errors_1.mapPostgrestError)(error);
|
|
111
124
|
return (count ?? 0) > 0;
|
|
112
125
|
}
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return (data ?? []).reduce((total, row) => total + Number(row.days), 0);
|
|
126
|
+
/** Live requests touching `date_from`..`date_to`, for everyone in the organization - the leave calendar. */
|
|
127
|
+
async calendar(query) {
|
|
128
|
+
return (0, transforms_1.allRows)((from, to) => {
|
|
129
|
+
let calendarQuery = this.supabase
|
|
130
|
+
.from('leave_requests')
|
|
131
|
+
.select(LEAVE_CALENDAR_SELECT)
|
|
132
|
+
.eq('organization_id', this.auth.organization_id)
|
|
133
|
+
.in('status', LIVE_STATUSES)
|
|
134
|
+
.lte('start_date', query.date_to)
|
|
135
|
+
.gte('end_date', query.date_from)
|
|
136
|
+
.order('start_date')
|
|
137
|
+
.order('mms_id')
|
|
138
|
+
.range(from, to);
|
|
139
|
+
if (query.user_id) {
|
|
140
|
+
calendarQuery = calendarQuery.eq('user_id', query.user_id);
|
|
141
|
+
}
|
|
142
|
+
if (query.leave_type_id) {
|
|
143
|
+
calendarQuery = calendarQuery.eq('leave_type_id', query.leave_type_id);
|
|
144
|
+
}
|
|
145
|
+
return calendarQuery;
|
|
146
|
+
});
|
|
135
147
|
}
|
|
136
148
|
async create(input) {
|
|
137
149
|
const row = await this.table.insert({
|
|
@@ -169,10 +181,13 @@ class LeaveRequestsResource {
|
|
|
169
181
|
});
|
|
170
182
|
return row ? this.getById(leaveRequestId) : null;
|
|
171
183
|
}
|
|
172
|
-
/**
|
|
173
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Cancels a request that is still in `expected` - the status the api read and decided on.
|
|
186
|
+
* Null when it has moved on since (reviewed, or cancelled by someone else).
|
|
187
|
+
*/
|
|
188
|
+
async cancel(leaveRequestId, cancelledBy, expected) {
|
|
174
189
|
const now = new Date().toISOString();
|
|
175
|
-
const row = await this.table.updateWhere(leaveRequestId, (query) => query.eq('status',
|
|
190
|
+
const row = await this.table.updateWhere(leaveRequestId, (query) => query.eq('status', expected), {
|
|
176
191
|
status: enum_2.LeaveRequestStatus.CANCELLED,
|
|
177
192
|
cancelled_by: cancelledBy,
|
|
178
193
|
cancelled_at: now,
|
|
@@ -18,9 +18,26 @@ const LEAVE_TYPE_SELECT = `
|
|
|
18
18
|
default_days,
|
|
19
19
|
is_paid,
|
|
20
20
|
requires_approval,
|
|
21
|
-
|
|
21
|
+
document_after_days,
|
|
22
|
+
cycle_years,
|
|
23
|
+
grant_method,
|
|
24
|
+
max_carry_over_days,
|
|
25
|
+
carry_over_expiry_months,
|
|
26
|
+
min_service_months,
|
|
27
|
+
min_days_per_week,
|
|
28
|
+
initial_months,
|
|
29
|
+
initial_days_per_month,
|
|
30
|
+
paid_on_exit,
|
|
22
31
|
active
|
|
23
32
|
`;
|
|
33
|
+
/** Postgres hands `numeric` back as a string; the entity promises numbers. */
|
|
34
|
+
const toLeaveType = (row) => ({
|
|
35
|
+
...row,
|
|
36
|
+
default_days: Number(row.default_days),
|
|
37
|
+
max_carry_over_days: Number(row.max_carry_over_days),
|
|
38
|
+
document_after_days: row.document_after_days === null ? null : Number(row.document_after_days),
|
|
39
|
+
initial_days_per_month: row.initial_days_per_month === null ? null : Number(row.initial_days_per_month),
|
|
40
|
+
});
|
|
24
41
|
class LeaveTypesResource {
|
|
25
42
|
supabase;
|
|
26
43
|
auth;
|
|
@@ -46,7 +63,7 @@ class LeaveTypesResource {
|
|
|
46
63
|
const { data, error } = await typesQuery;
|
|
47
64
|
if (error)
|
|
48
65
|
throw (0, errors_1.mapPostgrestError)(error);
|
|
49
|
-
return data;
|
|
66
|
+
return (data ?? []).map(toLeaveType);
|
|
50
67
|
}
|
|
51
68
|
async getById(leaveTypeId) {
|
|
52
69
|
const { data, error } = await this.supabase
|
|
@@ -57,7 +74,7 @@ class LeaveTypesResource {
|
|
|
57
74
|
.single();
|
|
58
75
|
if (error)
|
|
59
76
|
throw (0, errors_1.mapPostgrestError)(error);
|
|
60
|
-
return data;
|
|
77
|
+
return toLeaveType(data);
|
|
61
78
|
}
|
|
62
79
|
async create(input) {
|
|
63
80
|
const row = await this.table.insert({
|
|
@@ -66,7 +83,16 @@ class LeaveTypesResource {
|
|
|
66
83
|
default_days: input.default_days,
|
|
67
84
|
is_paid: input.is_paid,
|
|
68
85
|
requires_approval: input.requires_approval,
|
|
69
|
-
|
|
86
|
+
document_after_days: input.document_after_days,
|
|
87
|
+
cycle_years: input.cycle_years,
|
|
88
|
+
grant_method: input.grant_method,
|
|
89
|
+
max_carry_over_days: input.max_carry_over_days,
|
|
90
|
+
carry_over_expiry_months: input.carry_over_expiry_months,
|
|
91
|
+
min_service_months: input.min_service_months,
|
|
92
|
+
min_days_per_week: input.min_days_per_week,
|
|
93
|
+
initial_months: input.initial_months,
|
|
94
|
+
initial_days_per_month: input.initial_days_per_month,
|
|
95
|
+
paid_on_exit: input.paid_on_exit,
|
|
70
96
|
organization_id: this.auth.organization_id,
|
|
71
97
|
created_by: this.auth.mms_id,
|
|
72
98
|
created_at: new Date().toISOString(),
|
|
@@ -80,7 +106,16 @@ class LeaveTypesResource {
|
|
|
80
106
|
default_days: input.default_days,
|
|
81
107
|
is_paid: input.is_paid,
|
|
82
108
|
requires_approval: input.requires_approval,
|
|
83
|
-
|
|
109
|
+
document_after_days: input.document_after_days,
|
|
110
|
+
cycle_years: input.cycle_years,
|
|
111
|
+
grant_method: input.grant_method,
|
|
112
|
+
max_carry_over_days: input.max_carry_over_days,
|
|
113
|
+
carry_over_expiry_months: input.carry_over_expiry_months,
|
|
114
|
+
min_service_months: input.min_service_months,
|
|
115
|
+
min_days_per_week: input.min_days_per_week,
|
|
116
|
+
initial_months: input.initial_months,
|
|
117
|
+
initial_days_per_month: input.initial_days_per_month,
|
|
118
|
+
paid_on_exit: input.paid_on_exit,
|
|
84
119
|
active: input.active,
|
|
85
120
|
last_updated_by: this.auth.mms_id,
|
|
86
121
|
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
|
+
/** `users.working_days`' default: ISO weekdays, Monday (1) to Friday (5). */
|
|
8
|
+
export declare const MONDAY_TO_FRIDAY: readonly number[];
|
|
7
9
|
/** A `Date` at UTC midnight of a `YYYY-MM-DD` string (a longer ISO string is cut to its date). */
|
|
8
10
|
export declare const parseIsoDate: (value: string) => Date;
|
|
9
11
|
/** The `YYYY-MM-DD` of a `Date`'s UTC calendar day. */
|
|
@@ -12,22 +14,24 @@ export declare const toIsoDate: (date: Date) => string;
|
|
|
12
14
|
export declare const todayInJohannesburg: (now?: Date) => string;
|
|
13
15
|
/** Every calendar date from `start` to `end` inclusive; empty when `end` is before `start`. */
|
|
14
16
|
export declare const eachDate: (start: string, end: string) => string[];
|
|
15
|
-
/**
|
|
16
|
-
export declare const isWorkingDay: (isoDate: string, holidays: ReadonlySet<string
|
|
17
|
+
/** One of the person's `workingDays` (ISO weekdays) and not a public holiday. */
|
|
18
|
+
export declare const isWorkingDay: (isoDate: string, holidays: ReadonlySet<string>, workingDays?: readonly number[]) => boolean;
|
|
17
19
|
/**
|
|
18
20
|
* The days a request is worth: the working days from `start` to `end` inclusive, halved for a
|
|
19
21
|
* half day. A half day is only meaningful on a single working day, so it yields 0.5 there and 0
|
|
20
|
-
* on a
|
|
22
|
+
* on a day off or holiday - the caller rejects a zero either way.
|
|
21
23
|
*/
|
|
22
|
-
export declare const countWorkingDays: (start: string, end: string, holidays: ReadonlySet<string>, isHalfDay?: boolean) => number;
|
|
24
|
+
export declare const countWorkingDays: (start: string, end: string, holidays: ReadonlySet<string>, isHalfDay?: boolean, workingDays?: readonly number[]) => number;
|
|
23
25
|
/** `isoDate` moved by `days` calendar days, either way; `shiftDays('2026-03-01', -1)` is Feb 28. */
|
|
24
26
|
export declare const shiftDays: (isoDate: string, days: number) => string;
|
|
25
|
-
/** The day before the same date next year - the end of a period that starts on `isoDate`. */
|
|
26
|
-
export declare const addOneYearMinusDay: (isoDate: string) => string;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
30
|
-
* 2026-02-10 in the year that began 2025-03-01.
|
|
28
|
+
* `isoDate` moved by `months` calendar months, the day clamped to the target month's length:
|
|
29
|
+
* `addMonths('2026-01-31', 1)` is Feb 28.
|
|
31
30
|
*/
|
|
32
|
-
export declare const
|
|
31
|
+
export declare const addMonths: (isoDate: string, months: number) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Whole months from `start` completed by the end of `asOf`: 0 until the day before the
|
|
34
|
+
* one-month mark, 1 from then. A month counts once its last day has been lived.
|
|
35
|
+
*/
|
|
36
|
+
export declare const completedMonths: (start: string, asOf: string) => number;
|
|
33
37
|
export declare const isWithinPeriod: (date: string, period: LeavePeriod) => boolean;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isWithinPeriod = exports.
|
|
4
|
-
// The calendar arithmetic behind leave: which days of a request count, and
|
|
5
|
-
//
|
|
3
|
+
exports.isWithinPeriod = exports.completedMonths = exports.addMonths = exports.shiftDays = exports.countWorkingDays = exports.isWorkingDay = exports.eachDate = exports.todayInJohannesburg = exports.toIsoDate = exports.parseIsoDate = exports.MONDAY_TO_FRIDAY = exports.LEAVE_TIME_ZONE = void 0;
|
|
4
|
+
// The calendar arithmetic behind leave: which days of a request count, and how dates move by
|
|
5
|
+
// days and months. 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.
|
|
7
7
|
/**
|
|
8
8
|
* Where "today" is read for the cancel rule. Organizations carry no time zone yet, and every
|
|
9
9
|
* customer is South African; this is the one place to change when a column arrives.
|
|
10
10
|
*/
|
|
11
11
|
exports.LEAVE_TIME_ZONE = 'Africa/Johannesburg';
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/** `users.working_days`' default: ISO weekdays, Monday (1) to Friday (5). */
|
|
13
|
+
exports.MONDAY_TO_FRIDAY = [1, 2, 3, 4, 5];
|
|
14
14
|
const pad = (value) => String(value).padStart(2, '0');
|
|
15
15
|
/** A `Date` at UTC midnight of a `YYYY-MM-DD` string (a longer ISO string is cut to its date). */
|
|
16
16
|
const parseIsoDate = (value) => {
|
|
@@ -41,16 +41,16 @@ const eachDate = (start, end) => {
|
|
|
41
41
|
return dates;
|
|
42
42
|
};
|
|
43
43
|
exports.eachDate = eachDate;
|
|
44
|
-
/**
|
|
45
|
-
const isWorkingDay = (isoDate, holidays) =>
|
|
44
|
+
/** One of the person's `workingDays` (ISO weekdays) and not a public holiday. */
|
|
45
|
+
const isWorkingDay = (isoDate, holidays, workingDays = exports.MONDAY_TO_FRIDAY) => workingDays.includes((0, exports.parseIsoDate)(isoDate).getUTCDay() || 7) && !holidays.has(isoDate.slice(0, 10));
|
|
46
46
|
exports.isWorkingDay = isWorkingDay;
|
|
47
47
|
/**
|
|
48
48
|
* The days a request is worth: the working days from `start` to `end` inclusive, halved for a
|
|
49
49
|
* half day. A half day is only meaningful on a single working day, so it yields 0.5 there and 0
|
|
50
|
-
* on a
|
|
50
|
+
* on a day off or holiday - the caller rejects a zero either way.
|
|
51
51
|
*/
|
|
52
|
-
const countWorkingDays = (start, end, holidays, isHalfDay = false) => {
|
|
53
|
-
const working = (0, exports.eachDate)(start, end).filter((date) => (0, exports.isWorkingDay)(date, holidays)).length;
|
|
52
|
+
const countWorkingDays = (start, end, holidays, isHalfDay = false, workingDays = exports.MONDAY_TO_FRIDAY) => {
|
|
53
|
+
const working = (0, exports.eachDate)(start, end).filter((date) => (0, exports.isWorkingDay)(date, holidays, workingDays)).length;
|
|
54
54
|
return isHalfDay ? working * 0.5 : working;
|
|
55
55
|
};
|
|
56
56
|
exports.countWorkingDays = countWorkingDays;
|
|
@@ -61,26 +61,33 @@ const shiftDays = (isoDate, days) => {
|
|
|
61
61
|
return (0, exports.toIsoDate)(date);
|
|
62
62
|
};
|
|
63
63
|
exports.shiftDays = shiftDays;
|
|
64
|
-
/**
|
|
65
|
-
|
|
64
|
+
/**
|
|
65
|
+
* `isoDate` moved by `months` calendar months, the day clamped to the target month's length:
|
|
66
|
+
* `addMonths('2026-01-31', 1)` is Feb 28.
|
|
67
|
+
*/
|
|
68
|
+
const addMonths = (isoDate, months) => {
|
|
66
69
|
const date = (0, exports.parseIsoDate)(isoDate);
|
|
67
|
-
date.
|
|
68
|
-
date.setUTCDate(
|
|
70
|
+
const day = date.getUTCDate();
|
|
71
|
+
date.setUTCDate(1);
|
|
72
|
+
date.setUTCMonth(date.getUTCMonth() + months);
|
|
73
|
+
const lastDay = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)).getUTCDate();
|
|
74
|
+
date.setUTCDate(Math.min(day, lastDay));
|
|
69
75
|
return (0, exports.toIsoDate)(date);
|
|
70
76
|
};
|
|
71
|
-
exports.
|
|
77
|
+
exports.addMonths = addMonths;
|
|
72
78
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* 2026-02-10 in the year that began 2025-03-01.
|
|
79
|
+
* Whole months from `start` completed by the end of `asOf`: 0 until the day before the
|
|
80
|
+
* one-month mark, 1 from then. A month counts once its last day has been lived.
|
|
76
81
|
*/
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
const completedMonths = (start, asOf) => {
|
|
83
|
+
const dayAfter = (0, exports.shiftDays)(asOf, 1);
|
|
84
|
+
let months = 0;
|
|
85
|
+
while ((0, exports.addMonths)(start, months + 1) <= dayAfter) {
|
|
86
|
+
months += 1;
|
|
87
|
+
}
|
|
88
|
+
return months;
|
|
82
89
|
};
|
|
83
|
-
exports.
|
|
90
|
+
exports.completedMonths = completedMonths;
|
|
84
91
|
const isWithinPeriod = (date, period) => {
|
|
85
92
|
const day = date.slice(0, 10);
|
|
86
93
|
return day >= period.period_start && day <= period.period_end;
|
|
@@ -225,6 +225,7 @@ class UpdateOrganizationDto {
|
|
|
225
225
|
industry;
|
|
226
226
|
marketing_consent;
|
|
227
227
|
currency;
|
|
228
|
+
leave_calendar_start_month;
|
|
228
229
|
}
|
|
229
230
|
exports.UpdateOrganizationDto = UpdateOrganizationDto;
|
|
230
231
|
__decorate([
|
|
@@ -442,3 +443,10 @@ __decorate([
|
|
|
442
443
|
(0, class_validator_1.MaxLength)(255, { message: 'Organization currency must be less than 255 characters' }),
|
|
443
444
|
__metadata("design:type", String)
|
|
444
445
|
], UpdateOrganizationDto.prototype, "currency", void 0);
|
|
446
|
+
__decorate([
|
|
447
|
+
(0, class_validator_1.IsOptional)(),
|
|
448
|
+
(0, class_validator_1.IsInt)({ message: 'Leave year start month must be a whole number' }),
|
|
449
|
+
(0, class_validator_1.Min)(1, { message: 'Leave year start month must be between 1 and 12' }),
|
|
450
|
+
(0, class_validator_1.Max)(12, { message: 'Leave year start month must be between 1 and 12' }),
|
|
451
|
+
__metadata("design:type", Number)
|
|
452
|
+
], UpdateOrganizationDto.prototype, "leave_calendar_start_month", void 0);
|
package/dist/pricebooks/index.js
CHANGED
|
@@ -15,12 +15,13 @@ const PRICEBOOK_SELECT = `
|
|
|
15
15
|
unit_amount_minor,
|
|
16
16
|
currency,
|
|
17
17
|
billing_modules (
|
|
18
|
-
module_key
|
|
18
|
+
module_key,
|
|
19
|
+
name
|
|
19
20
|
)
|
|
20
21
|
)
|
|
21
22
|
`;
|
|
22
|
-
// A pricebook_modules row whose catalog entry did not come back leaves `module_key`
|
|
23
|
-
// the cast keeps the mapping honest about that rather than inventing
|
|
23
|
+
// A pricebook_modules row whose catalog entry did not come back leaves `module_key` and `name`
|
|
24
|
+
// undefined - the cast keeps the mapping honest about that rather than inventing them.
|
|
24
25
|
const toPricebookWithModules = (row) => ({
|
|
25
26
|
mms_id: row.mms_id,
|
|
26
27
|
name: row.name,
|
|
@@ -29,6 +30,7 @@ const toPricebookWithModules = (row) => ({
|
|
|
29
30
|
modules: (row.pricebook_modules || []).map((pricedModule) => ({
|
|
30
31
|
module_id: pricedModule.module_id,
|
|
31
32
|
module_key: pricedModule.billing_modules?.module_key,
|
|
33
|
+
name: pricedModule.billing_modules?.name,
|
|
32
34
|
unit_amount_minor: pricedModule.unit_amount_minor,
|
|
33
35
|
currency: pricedModule.currency,
|
|
34
36
|
})),
|
|
@@ -46,6 +46,11 @@ export declare class SupabaseOrganizationScope {
|
|
|
46
46
|
* subscriptions and both webhook handler services.
|
|
47
47
|
*/
|
|
48
48
|
table(options: AuditedTableOptions): AuditedTable;
|
|
49
|
+
/**
|
|
50
|
+
* Gives `userId` a new leave calendar feed token (stored as its SHA-256), which also retires
|
|
51
|
+
* any address they subscribed with before. Service role only: the token is a credential.
|
|
52
|
+
*/
|
|
53
|
+
replaceLeaveCalendarFeed(userId: string, tokenHash: string): Promise<void>;
|
|
49
54
|
/**
|
|
50
55
|
* Clients written service-role on a user's behalf: converting a lead needs only
|
|
51
56
|
* `update_leads`, so the client it becomes is created here rather than through the caller's
|
|
@@ -97,14 +102,23 @@ export declare class SupabaseServiceClient {
|
|
|
97
102
|
*/
|
|
98
103
|
organizationsWithPaystackCustomer(): Promise<string[]>;
|
|
99
104
|
/**
|
|
100
|
-
* Every organization
|
|
101
|
-
* nightly rollover visits
|
|
102
|
-
* from here.
|
|
105
|
+
* Every organization subscribed to `moduleKey`, with its leave-year start month - the tenants
|
|
106
|
+
* the nightly leave rollover visits. Unscoped like the reconcile: the cron picks its tenants
|
|
107
|
+
* from here.
|
|
103
108
|
*/
|
|
104
|
-
|
|
109
|
+
organizationsWithModule(moduleKey: string): Promise<{
|
|
105
110
|
mms_id: string;
|
|
106
|
-
|
|
111
|
+
leave_calendar_start_month: number;
|
|
107
112
|
}[]>;
|
|
113
|
+
/**
|
|
114
|
+
* Whose leave calendar feed a token opens, by the token's SHA-256. Unscoped by necessity: the
|
|
115
|
+
* feed is fetched by a calendar app with no session, and the row is what says which tenant
|
|
116
|
+
* and person it belongs to. `leave_calendar_feeds` has no `authenticated` grant at all.
|
|
117
|
+
*/
|
|
118
|
+
leaveCalendarFeedFor(tokenHash: string): Promise<{
|
|
119
|
+
user_id: string;
|
|
120
|
+
organization_id: string;
|
|
121
|
+
} | null>;
|
|
108
122
|
/**
|
|
109
123
|
* A ledger row by id alone. Also unscoped by necessity: a `charge.success` carries the
|
|
110
124
|
* transaction id in its metadata, and the row is what says which tenant it belongs to.
|
|
@@ -61,6 +61,20 @@ class SupabaseOrganizationScope {
|
|
|
61
61
|
table(options) {
|
|
62
62
|
return (0, audit_1.auditedTable)(this.supabase, this.actor, this.audit, options);
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Gives `userId` a new leave calendar feed token (stored as its SHA-256), which also retires
|
|
66
|
+
* any address they subscribed with before. Service role only: the token is a credential.
|
|
67
|
+
*/
|
|
68
|
+
async replaceLeaveCalendarFeed(userId, tokenHash) {
|
|
69
|
+
const { error } = await this.supabase.from('leave_calendar_feeds').upsert({
|
|
70
|
+
user_id: userId,
|
|
71
|
+
organization_id: this.actor.organization_id,
|
|
72
|
+
token_hash: tokenHash,
|
|
73
|
+
created_at: new Date().toISOString(),
|
|
74
|
+
}, { onConflict: 'user_id' });
|
|
75
|
+
if (error)
|
|
76
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
77
|
+
}
|
|
64
78
|
/**
|
|
65
79
|
* Clients written service-role on a user's behalf: converting a lead needs only
|
|
66
80
|
* `update_leads`, so the client it becomes is created here rather than through the caller's
|
|
@@ -167,19 +181,34 @@ class SupabaseServiceClient {
|
|
|
167
181
|
return (data ?? []).map((row) => row.organization_id);
|
|
168
182
|
}
|
|
169
183
|
/**
|
|
170
|
-
* Every organization
|
|
171
|
-
* nightly rollover visits
|
|
172
|
-
* from here.
|
|
184
|
+
* Every organization subscribed to `moduleKey`, with its leave-year start month - the tenants
|
|
185
|
+
* the nightly leave rollover visits. Unscoped like the reconcile: the cron picks its tenants
|
|
186
|
+
* from here.
|
|
173
187
|
*/
|
|
174
|
-
async
|
|
188
|
+
async organizationsWithModule(moduleKey) {
|
|
175
189
|
const { data, error } = await this.supabase
|
|
176
190
|
.from('organizations')
|
|
177
|
-
.select('mms_id,
|
|
178
|
-
.
|
|
191
|
+
.select('mms_id, leave_calendar_start_month')
|
|
192
|
+
.contains('modules', [moduleKey]);
|
|
179
193
|
if (error)
|
|
180
194
|
throw (0, errors_1.mapPostgrestError)(error);
|
|
181
195
|
return (data ?? []);
|
|
182
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Whose leave calendar feed a token opens, by the token's SHA-256. Unscoped by necessity: the
|
|
199
|
+
* feed is fetched by a calendar app with no session, and the row is what says which tenant
|
|
200
|
+
* and person it belongs to. `leave_calendar_feeds` has no `authenticated` grant at all.
|
|
201
|
+
*/
|
|
202
|
+
async leaveCalendarFeedFor(tokenHash) {
|
|
203
|
+
const { data, error } = await this.supabase
|
|
204
|
+
.from('leave_calendar_feeds')
|
|
205
|
+
.select('user_id, organization_id')
|
|
206
|
+
.eq('token_hash', tokenHash)
|
|
207
|
+
.maybeSingle();
|
|
208
|
+
if (error)
|
|
209
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
210
|
+
return (data ?? null);
|
|
211
|
+
}
|
|
183
212
|
/**
|
|
184
213
|
* A ledger row by id alone. Also unscoped by necessity: a `charge.success` carries the
|
|
185
214
|
* transaction id in its metadata, and the row is what says which tenant it belongs to.
|
package/dist/users/dto.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export declare class GetUsersQueryDto implements Omit<GetUsersDtoType, 'active_i
|
|
|
26
26
|
readonly page: number;
|
|
27
27
|
readonly page_size: number;
|
|
28
28
|
readonly active_in?: boolean[] | null;
|
|
29
|
+
readonly manager_id?: string | null;
|
|
29
30
|
}
|
|
30
31
|
export declare class UpdateUserDto implements UpdateUserDtoType {
|
|
31
32
|
readonly name: string;
|
|
@@ -39,6 +40,7 @@ export declare class UpdateUserDto implements UpdateUserDtoType {
|
|
|
39
40
|
readonly job_title?: string | null;
|
|
40
41
|
readonly manager_id?: string | null;
|
|
41
42
|
readonly secondary_manager_id?: string | null;
|
|
43
|
+
readonly working_days?: number[];
|
|
42
44
|
readonly social_facebook?: string | null;
|
|
43
45
|
readonly social_linkedin?: string | null;
|
|
44
46
|
readonly social_twitter?: string | null;
|
package/dist/users/dto.js
CHANGED
|
@@ -94,6 +94,8 @@ class GetUsersQueryDto {
|
|
|
94
94
|
page_size = 10;
|
|
95
95
|
// Comma-joined booleans on the wire, e.g. "true,false". Absent = all users.
|
|
96
96
|
active_in;
|
|
97
|
+
// Still v4: a manager is a `users.mms_id`, which is the GoTrue auth uid (see UserIdDto).
|
|
98
|
+
manager_id;
|
|
97
99
|
}
|
|
98
100
|
exports.GetUsersQueryDto = GetUsersQueryDto;
|
|
99
101
|
__decorate([
|
|
@@ -126,6 +128,12 @@ __decorate([
|
|
|
126
128
|
(0, class_validator_1.IsBoolean)({ each: true, message: 'Active filter values must be booleans' }),
|
|
127
129
|
__metadata("design:type", Object)
|
|
128
130
|
], GetUsersQueryDto.prototype, "active_in", void 0);
|
|
131
|
+
__decorate([
|
|
132
|
+
(0, class_validator_1.IsOptional)(),
|
|
133
|
+
(0, validators_1.IsNullable)(),
|
|
134
|
+
(0, class_validator_1.IsUUID)('4', { message: 'Invalid manager ID format' }),
|
|
135
|
+
__metadata("design:type", Object)
|
|
136
|
+
], GetUsersQueryDto.prototype, "manager_id", void 0);
|
|
129
137
|
class UpdateUserDto {
|
|
130
138
|
name;
|
|
131
139
|
surname;
|
|
@@ -138,6 +146,7 @@ class UpdateUserDto {
|
|
|
138
146
|
job_title;
|
|
139
147
|
manager_id;
|
|
140
148
|
secondary_manager_id;
|
|
149
|
+
working_days;
|
|
141
150
|
social_facebook;
|
|
142
151
|
social_linkedin;
|
|
143
152
|
social_twitter;
|
|
@@ -216,6 +225,17 @@ __decorate([
|
|
|
216
225
|
(0, class_validator_1.IsUUID)('4', { message: 'Invalid secondary manager ID format' }),
|
|
217
226
|
__metadata("design:type", Object)
|
|
218
227
|
], UpdateUserDto.prototype, "secondary_manager_id", void 0);
|
|
228
|
+
__decorate([
|
|
229
|
+
(0, class_validator_1.IsOptional)(),
|
|
230
|
+
(0, class_validator_1.IsArray)({ message: 'Working days must be a list of weekdays' }),
|
|
231
|
+
(0, class_validator_1.ArrayMinSize)(1, { message: 'Choose at least one working day' }),
|
|
232
|
+
(0, class_validator_1.ArrayMaxSize)(7, { message: 'A week has seven days' }),
|
|
233
|
+
(0, class_validator_1.ArrayUnique)({ message: 'Each working day may appear once' }),
|
|
234
|
+
(0, class_validator_1.IsInt)({ each: true, message: 'Working days are weekday numbers' }),
|
|
235
|
+
(0, class_validator_1.Min)(1, { each: true, message: 'Working days run from 1 (Monday) to 7 (Sunday)' }),
|
|
236
|
+
(0, class_validator_1.Max)(7, { each: true, message: 'Working days run from 1 (Monday) to 7 (Sunday)' }),
|
|
237
|
+
__metadata("design:type", Array)
|
|
238
|
+
], UpdateUserDto.prototype, "working_days", void 0);
|
|
219
239
|
__decorate([
|
|
220
240
|
(0, class_validator_1.IsOptional)(),
|
|
221
241
|
(0, validators_1.EmptyToNull)(),
|
package/dist/users/index.d.ts
CHANGED
|
@@ -5,6 +5,13 @@ import type { TypedSupabaseClient } from '../client';
|
|
|
5
5
|
import type { GetUsersQueryDto } from './dto';
|
|
6
6
|
export * from './dto';
|
|
7
7
|
export * from './paths';
|
|
8
|
+
/** What leave needs to know about someone's employment. Dates are Johannesburg calendar dates. */
|
|
9
|
+
export type UserEmploymentRow = UserIdentity & {
|
|
10
|
+
active: boolean;
|
|
11
|
+
start_date: string;
|
|
12
|
+
end_date: string | null;
|
|
13
|
+
working_days: number[];
|
|
14
|
+
};
|
|
8
15
|
/** The full users row. The api resolves its manager/created_by/last_updated_by ids into identities. */
|
|
9
16
|
export type UserRow = Record<string, unknown> & {
|
|
10
17
|
mms_id: string;
|
|
@@ -33,8 +40,11 @@ export declare class UsersResource {
|
|
|
33
40
|
* `active` is not consulted: a deactivated report's history stays visible to their manager.
|
|
34
41
|
*/
|
|
35
42
|
idsManagedBy(managerId: string): Promise<string[]>;
|
|
36
|
-
/**
|
|
37
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Employment details for leave: start and end as calendar dates in Johannesburg (the columns
|
|
45
|
+
* are timestamps), and the working week. Everyone in the organization, or just `userIds`.
|
|
46
|
+
*/
|
|
47
|
+
employment(userIds?: string[]): Promise<UserEmploymentRow[]>;
|
|
38
48
|
/** Seats in use. Counts rows rather than fetching them. */
|
|
39
49
|
activeCount(): Promise<number>;
|
|
40
50
|
isActive(userId: string): Promise<boolean>;
|
package/dist/users/index.js
CHANGED
|
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.UsersResource = void 0;
|
|
18
18
|
const enum_1 = require("@managemint-solutions/entities/audit-trail/enum");
|
|
19
|
+
const working_days_1 = require("../leave/working-days");
|
|
19
20
|
const audit_1 = require("../audit");
|
|
20
21
|
const errors_1 = require("../errors");
|
|
21
22
|
const paths_1 = require("../supporting-files/paths");
|
|
@@ -58,6 +59,10 @@ class UsersResource {
|
|
|
58
59
|
if (trimmedSearch) {
|
|
59
60
|
usersQuery = usersQuery.or((0, transforms_1.orIlike)(['name', 'surname', 'email'], trimmedSearch));
|
|
60
61
|
}
|
|
62
|
+
// Either manager line, as `idsManagedBy` reads it. PostgREST ANDs this with the search.
|
|
63
|
+
if (query.manager_id) {
|
|
64
|
+
usersQuery = usersQuery.or(`manager_id.eq.${query.manager_id},secondary_manager_id.eq.${query.manager_id}`);
|
|
65
|
+
}
|
|
61
66
|
const { data, error, count } = await usersQuery;
|
|
62
67
|
if (error)
|
|
63
68
|
throw (0, errors_1.mapPostgrestError)(error);
|
|
@@ -107,16 +112,26 @@ class UsersResource {
|
|
|
107
112
|
throw (0, errors_1.mapPostgrestError)(error);
|
|
108
113
|
return (data ?? []).map((row) => row.mms_id);
|
|
109
114
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Employment details for leave: start and end as calendar dates in Johannesburg (the columns
|
|
117
|
+
* are timestamps), and the working week. Everyone in the organization, or just `userIds`.
|
|
118
|
+
*/
|
|
119
|
+
async employment(userIds) {
|
|
120
|
+
let employmentQuery = this.supabase
|
|
113
121
|
.from('users')
|
|
114
|
-
.select('mms_id')
|
|
115
|
-
.eq('organization_id', this.auth.organization_id)
|
|
116
|
-
|
|
122
|
+
.select('mms_id, name, surname, email, profile_image, active, start_date, end_date, working_days')
|
|
123
|
+
.eq('organization_id', this.auth.organization_id);
|
|
124
|
+
if (userIds) {
|
|
125
|
+
employmentQuery = employmentQuery.in('mms_id', userIds);
|
|
126
|
+
}
|
|
127
|
+
const { data, error } = await employmentQuery;
|
|
117
128
|
if (error)
|
|
118
129
|
throw (0, errors_1.mapPostgrestError)(error);
|
|
119
|
-
return (data ?? []).map((row) =>
|
|
130
|
+
return (data ?? []).map((row) => ({
|
|
131
|
+
...row,
|
|
132
|
+
start_date: (0, working_days_1.todayInJohannesburg)(new Date(row.start_date)),
|
|
133
|
+
end_date: row.end_date ? (0, working_days_1.todayInJohannesburg)(new Date(row.end_date)) : null,
|
|
134
|
+
}));
|
|
120
135
|
}
|
|
121
136
|
/** Seats in use. Counts rows rather than fetching them. */
|
|
122
137
|
async activeCount() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@managemint-solutions/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "Typed Supabase data-access SDK for ManageMint Solutions",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Scott Bebington <scottbebington@gmail.com>",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"testEnvironment": "node"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@managemint-solutions/entities": "^1.
|
|
54
|
+
"@managemint-solutions/entities": "^1.32.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@nestjs/common": "^11.0.0",
|