@lilaquadrat/studio 13.0.1 → 13.1.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/lib/helpers.d.ts +2 -1
- package/lib/helpers.js +2 -1
- package/lib/helpers.js.map +1 -1
- package/lib/models.d.ts +3 -1
- package/lib/models.js +3 -1
- package/lib/models.js.map +1 -1
- package/lib/services.d.ts +4 -1
- package/lib/services.js +4 -1
- package/lib/services.js.map +1 -1
- package/lib/src/aggegations/genericData.aggregation.js +1 -1
- package/lib/src/aggegations/genericData.aggregation.js.map +1 -1
- package/lib/src/helpers/escapeRegExp.d.ts +2 -0
- package/lib/src/helpers/escapeRegExp.js +5 -0
- package/lib/src/helpers/escapeRegExp.js.map +1 -0
- package/lib/src/helpers/shiftPlanningSchemas.d.ts +6 -0
- package/lib/src/helpers/shiftPlanningSchemas.js +1 -0
- package/lib/src/helpers/shiftPlanningSchemas.js.map +1 -1
- package/lib/src/interfaces/CategoryBookedTime.d.ts +5 -0
- package/lib/src/interfaces/CategoryBookedTime.js +2 -0
- package/lib/src/interfaces/CategoryBookedTime.js.map +1 -0
- package/lib/src/interfaces/TrackerDailyLimitGroup.d.ts +7 -0
- package/lib/src/interfaces/TrackerDailyLimitGroup.js +2 -0
- package/lib/src/interfaces/TrackerDailyLimitGroup.js.map +1 -0
- package/lib/src/interfaces/TrackerDailyLimitInput.d.ts +8 -0
- package/lib/src/interfaces/TrackerDailyLimitInput.js +2 -0
- package/lib/src/interfaces/TrackerDailyLimitInput.js.map +1 -0
- package/lib/src/interfaces/TrackerStatisticsBucket.d.ts +13 -0
- package/lib/src/interfaces/TrackerStatisticsBucket.js +2 -0
- package/lib/src/interfaces/TrackerStatisticsBucket.js.map +1 -0
- package/lib/src/interfaces/TrackerStatisticsRange.d.ts +6 -0
- package/lib/src/interfaces/TrackerStatisticsRange.js +2 -0
- package/lib/src/interfaces/TrackerStatisticsRange.js.map +1 -0
- package/lib/src/interfaces/TrackerTimeSum.d.ts +4 -0
- package/lib/src/interfaces/TrackerTimeSum.js +2 -0
- package/lib/src/interfaces/TrackerTimeSum.js.map +1 -0
- package/lib/src/models/editor.model.js +2 -0
- package/lib/src/models/editor.model.js.map +1 -1
- package/lib/src/models/permissions.model.js +10 -0
- package/lib/src/models/permissions.model.js.map +1 -1
- package/lib/src/models/shift.model.js +1 -1
- package/lib/src/models/shift.model.js.map +1 -1
- package/lib/src/models/timeTrackingProject.model.d.ts +4 -0
- package/lib/src/models/timeTrackingProject.model.js +57 -0
- package/lib/src/models/timeTrackingProject.model.js.map +1 -0
- package/lib/src/models/timeTrackingProjectAllocation.model.d.ts +4 -0
- package/lib/src/models/timeTrackingProjectAllocation.model.js +38 -0
- package/lib/src/models/timeTrackingProjectAllocation.model.js.map +1 -0
- package/lib/src/models/tracker.model.js +18 -2
- package/lib/src/models/tracker.model.js.map +1 -1
- package/lib/src/services/lists.service.js +33 -0
- package/lib/src/services/lists.service.js.map +1 -1
- package/lib/src/services/shiftPlans.service.d.ts +1 -1
- package/lib/src/services/shiftPlans.service.js +10 -4
- package/lib/src/services/shiftPlans.service.js.map +1 -1
- package/lib/src/services/shifts.service.d.ts +3 -0
- package/lib/src/services/shifts.service.js +25 -6
- package/lib/src/services/shifts.service.js.map +1 -1
- package/lib/src/services/timeTrackingProjectAllocations.service.d.ts +20 -0
- package/lib/src/services/timeTrackingProjectAllocations.service.js +85 -0
- package/lib/src/services/timeTrackingProjectAllocations.service.js.map +1 -0
- package/lib/src/services/timeTrackingProjects.service.d.ts +18 -0
- package/lib/src/services/timeTrackingProjects.service.js +78 -0
- package/lib/src/services/timeTrackingProjects.service.js.map +1 -0
- package/lib/src/services/tracker.service.d.ts +150 -0
- package/lib/src/services/tracker.service.js +732 -0
- package/lib/src/services/tracker.service.js.map +1 -0
- package/lib/tests/listsSingle.spec.d.ts +1 -0
- package/lib/tests/listsSingle.spec.js +56 -0
- package/lib/tests/listsSingle.spec.js.map +1 -0
- package/lib/tests/shiftPlanning.spec.js +63 -11
- package/lib/tests/shiftPlanning.spec.js.map +1 -1
- package/lib/tests/shiftPlansList.spec.js +1 -1
- package/lib/tests/shiftPlansList.spec.js.map +1 -1
- package/lib/tests/timeTrackingProjectAllocationModel.spec.d.ts +1 -0
- package/lib/tests/timeTrackingProjectAllocationModel.spec.js +54 -0
- package/lib/tests/timeTrackingProjectAllocationModel.spec.js.map +1 -0
- package/lib/tests/timeTrackingProjectAllocationsService.spec.d.ts +1 -0
- package/lib/tests/timeTrackingProjectAllocationsService.spec.js +153 -0
- package/lib/tests/timeTrackingProjectAllocationsService.spec.js.map +1 -0
- package/lib/tests/timeTrackingProjectModel.spec.d.ts +1 -0
- package/lib/tests/timeTrackingProjectModel.spec.js +151 -0
- package/lib/tests/timeTrackingProjectModel.spec.js.map +1 -0
- package/lib/tests/timeTrackingProjectsService.spec.d.ts +1 -0
- package/lib/tests/timeTrackingProjectsService.spec.js +256 -0
- package/lib/tests/timeTrackingProjectsService.spec.js.map +1 -0
- package/lib/tests/trackerAllUsers.spec.d.ts +1 -0
- package/lib/tests/trackerAllUsers.spec.js +46 -0
- package/lib/tests/trackerAllUsers.spec.js.map +1 -0
- package/lib/tests/trackerAllocationUsage.spec.d.ts +1 -0
- package/lib/tests/trackerAllocationUsage.spec.js +36 -0
- package/lib/tests/trackerAllocationUsage.spec.js.map +1 -0
- package/lib/tests/trackerBookings.spec.d.ts +1 -0
- package/lib/tests/trackerBookings.spec.js +67 -0
- package/lib/tests/trackerBookings.spec.js.map +1 -0
- package/lib/tests/trackerCategories.spec.d.ts +1 -0
- package/lib/tests/trackerCategories.spec.js +40 -0
- package/lib/tests/trackerCategories.spec.js.map +1 -0
- package/lib/tests/trackerDailyLimit.spec.d.ts +1 -0
- package/lib/tests/trackerDailyLimit.spec.js +84 -0
- package/lib/tests/trackerDailyLimit.spec.js.map +1 -0
- package/lib/tests/trackerModel.spec.d.ts +1 -0
- package/lib/tests/trackerModel.spec.js +73 -0
- package/lib/tests/trackerModel.spec.js.map +1 -0
- package/lib/tests/trackerShiftAssignment.spec.d.ts +1 -0
- package/lib/tests/trackerShiftAssignment.spec.js +125 -0
- package/lib/tests/trackerShiftAssignment.spec.js.map +1 -0
- package/lib/tests/trackerShiftBookings.spec.d.ts +1 -0
- package/lib/tests/trackerShiftBookings.spec.js +58 -0
- package/lib/tests/trackerShiftBookings.spec.js.map +1 -0
- package/lib/tests/trackerShiftPlans.spec.d.ts +1 -0
- package/lib/tests/trackerShiftPlans.spec.js +251 -0
- package/lib/tests/trackerShiftPlans.spec.js.map +1 -0
- package/lib/tests/trackerWholePlanStatistics.spec.d.ts +1 -0
- package/lib/tests/trackerWholePlanStatistics.spec.js +41 -0
- package/lib/tests/trackerWholePlanStatistics.spec.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Immutable from '../Immutable.class.js';
|
|
2
|
+
import ImmutableError from '../classes/errors/Immutable.error.js';
|
|
3
|
+
import TimeTrackingProjectAllocationModel from '../models/timeTrackingProjectAllocation.model.js';
|
|
4
|
+
import TimeTrackingProjectModel from '../models/timeTrackingProject.model.js';
|
|
5
|
+
/** An allocation is one person's hour budget inside a category: plan > category > allocation. */
|
|
6
|
+
export class TimeTrackingProjectAllocationsService extends Immutable {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.model = TimeTrackingProjectAllocationModel;
|
|
10
|
+
}
|
|
11
|
+
singleTimeTrackingProjectAllocation(id, options) {
|
|
12
|
+
return this.model.db.findOne({ _id: id, company: options.company, project: options.project }, { projection: { history: 0 } });
|
|
13
|
+
}
|
|
14
|
+
listTimeTrackingProjectAllocations(timeTrackingProject, category, options) {
|
|
15
|
+
const query = {
|
|
16
|
+
company: options.company,
|
|
17
|
+
project: options.project,
|
|
18
|
+
timeTrackingProject,
|
|
19
|
+
};
|
|
20
|
+
if (category)
|
|
21
|
+
query.category = category;
|
|
22
|
+
return this.model.db.find(query, { projection: { history: 0 } }).sort({ user: 1 }).toArray();
|
|
23
|
+
}
|
|
24
|
+
/** Resolve the category inside the plan so allocation hours can be measured against its budget. */
|
|
25
|
+
async categoryBudget(timeTrackingProject, category, options) {
|
|
26
|
+
const project = await TimeTrackingProjectModel.db.findOne({
|
|
27
|
+
id: timeTrackingProject,
|
|
28
|
+
company: options.company,
|
|
29
|
+
project: options.project,
|
|
30
|
+
});
|
|
31
|
+
if (!project)
|
|
32
|
+
throw new ImmutableError('TIME_TRACKING_PROJECT_NOT_FOUND');
|
|
33
|
+
// a plan without categories books on shifts, where a category allocation has no budget to hold
|
|
34
|
+
const found = project.categories?.find((single) => single.id === category);
|
|
35
|
+
if (!found)
|
|
36
|
+
throw new ImmutableError('TIME_TRACKING_CATEGORY_NOT_FOUND');
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
/** The sum of a category's allocations may not exceed the category budget it was granted in the plan. */
|
|
40
|
+
async assertCategoryBudget(timeTrackingProject, category, hours, options, exclude) {
|
|
41
|
+
const budget = await this.categoryBudget(timeTrackingProject, category, options);
|
|
42
|
+
const query = {
|
|
43
|
+
company: options.company,
|
|
44
|
+
project: options.project,
|
|
45
|
+
timeTrackingProject,
|
|
46
|
+
category,
|
|
47
|
+
};
|
|
48
|
+
if (exclude)
|
|
49
|
+
query._id = { $ne: exclude };
|
|
50
|
+
const existing = await this.model.db.find(query).toArray();
|
|
51
|
+
// an allocation without hours has no personal limit and contributes nothing to the category budget
|
|
52
|
+
const used = existing.reduce((total, single) => total + (single.hours ?? 0), 0);
|
|
53
|
+
// a category without a maximum hours budget is unbounded
|
|
54
|
+
if (budget.maximumHours !== undefined && used + (hours ?? 0) > budget.maximumHours)
|
|
55
|
+
throw new ImmutableError('TIME_TRACKING_ALLOCATION_HOURS_EXCEEDED');
|
|
56
|
+
}
|
|
57
|
+
async createTimeTrackingProjectAllocation(input, options) {
|
|
58
|
+
await this.assertCategoryBudget(input.timeTrackingProject, input.category, input.hours, options);
|
|
59
|
+
const created = await this.create({ ...input, company: options.company, project: options.project }, options.user, options.app);
|
|
60
|
+
return (await this.singleTimeTrackingProjectAllocation(created._id, options));
|
|
61
|
+
}
|
|
62
|
+
async changeTimeTrackingProjectAllocation(id, input, options) {
|
|
63
|
+
const existing = await this.singleTimeTrackingProjectAllocation(id, options);
|
|
64
|
+
if (!existing)
|
|
65
|
+
throw new ImmutableError('TIME_TRACKING_ALLOCATION_NOT_FOUND');
|
|
66
|
+
await this.assertCategoryBudget(existing.timeTrackingProject, existing.category, input.hours ?? 0, options, id);
|
|
67
|
+
const filter = { _id: id, company: options.company, project: options.project };
|
|
68
|
+
if (input.hours === null) {
|
|
69
|
+
// `null` passes the change schema, options.unset then removes the field instead of storing it
|
|
70
|
+
await this.immutable(filter, { hours: input.hours }, options.user, 'change', options.app, undefined, undefined, undefined, { unset: ['hours'] });
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
await this.immutable(filter, { hours: input.hours }, options.user, 'change', options.app);
|
|
74
|
+
}
|
|
75
|
+
return (await this.singleTimeTrackingProjectAllocation(id, options));
|
|
76
|
+
}
|
|
77
|
+
async removeTimeTrackingProjectAllocation(id, options) {
|
|
78
|
+
const existing = await this.singleTimeTrackingProjectAllocation(id, options);
|
|
79
|
+
if (!existing)
|
|
80
|
+
throw new ImmutableError('TIME_TRACKING_ALLOCATION_NOT_FOUND');
|
|
81
|
+
await this.delete(id, options.user, options.app);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export default new TimeTrackingProjectAllocationsService();
|
|
85
|
+
//# sourceMappingURL=timeTrackingProjectAllocations.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeTrackingProjectAllocations.service.js","sourceRoot":"","sources":["../../../src/services/timeTrackingProjectAllocations.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TimeTrackingProject, TimeTrackingProjectInput, UserAppOptionsRequired } from '@lilaquadrat/interfaces';
|
|
2
|
+
import Immutable from '../Immutable.class.js';
|
|
3
|
+
/** Time-tracking projects group tracked time and cap it through maximumHours. Distinct from the content project model. */
|
|
4
|
+
export declare class TimeTrackingProjectsService extends Immutable<TimeTrackingProject> {
|
|
5
|
+
model: import("../classes/modelv2.class.js").default<TimeTrackingProject, string>;
|
|
6
|
+
/** Categories partition the plan's budget: their ids must be unique and their total must not exceed the plan maximum (when one is set). */
|
|
7
|
+
private assertCategories;
|
|
8
|
+
/** A plan is shift-based when it tracks on shifts and links a shift plan; only then may it skip categories. */
|
|
9
|
+
private assertCategoryRequired;
|
|
10
|
+
/** Only a shifts project may reference a plan: other modes clear the link, and shifts must point at an existing plan in this project. */
|
|
11
|
+
private normalizeShiftPlan;
|
|
12
|
+
singleTimeTrackingProject(id: string, options: UserAppOptionsRequired): Promise<import("mongodb").WithId<TimeTrackingProject> | null>;
|
|
13
|
+
createTimeTrackingProject(input: TimeTrackingProjectInput, options: UserAppOptionsRequired): Promise<import("mongodb").WithId<TimeTrackingProject>>;
|
|
14
|
+
changeTimeTrackingProject(id: string, input: Omit<TimeTrackingProjectInput, 'id'>, options: UserAppOptionsRequired): Promise<import("mongodb").WithId<TimeTrackingProject>>;
|
|
15
|
+
removeTimeTrackingProject(id: string, options: UserAppOptionsRequired): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
declare const _default: TimeTrackingProjectsService;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ObjectId } from 'mongodb';
|
|
2
|
+
import Immutable from '../Immutable.class.js';
|
|
3
|
+
import ImmutableError from '../classes/errors/Immutable.error.js';
|
|
4
|
+
import TimeTrackingProjectModel from '../models/timeTrackingProject.model.js';
|
|
5
|
+
import ShiftPlansService from './shiftPlans.service.js';
|
|
6
|
+
/** Time-tracking projects group tracked time and cap it through maximumHours. Distinct from the content project model. */
|
|
7
|
+
export class TimeTrackingProjectsService extends Immutable {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.model = TimeTrackingProjectModel;
|
|
11
|
+
}
|
|
12
|
+
/** Categories partition the plan's budget: their ids must be unique and their total must not exceed the plan maximum (when one is set). */
|
|
13
|
+
assertCategories(maximumHours, categories) {
|
|
14
|
+
// a plan without categories has nothing to partition, shift-based plans may leave the list empty
|
|
15
|
+
if (!categories?.length)
|
|
16
|
+
return;
|
|
17
|
+
const ids = new Set();
|
|
18
|
+
let allocated = 0;
|
|
19
|
+
categories.forEach((category) => {
|
|
20
|
+
if (ids.has(category.id))
|
|
21
|
+
throw new ImmutableError('TIME_TRACKING_CATEGORY_ID_DUPLICATE');
|
|
22
|
+
ids.add(category.id);
|
|
23
|
+
allocated += category.maximumHours || 0;
|
|
24
|
+
});
|
|
25
|
+
if (maximumHours !== undefined && allocated > maximumHours)
|
|
26
|
+
throw new ImmutableError('TIME_TRACKING_CATEGORY_HOURS_EXCEEDED');
|
|
27
|
+
}
|
|
28
|
+
/** A plan is shift-based when it tracks on shifts and links a shift plan; only then may it skip categories. */
|
|
29
|
+
assertCategoryRequired(modes, shiftPlan, categories) {
|
|
30
|
+
if (modes === 'shifts' && shiftPlan)
|
|
31
|
+
return;
|
|
32
|
+
if (!categories?.length)
|
|
33
|
+
throw new ImmutableError('TIME_TRACKING_CATEGORY_REQUIRED');
|
|
34
|
+
}
|
|
35
|
+
/** Only a shifts project may reference a plan: other modes clear the link, and shifts must point at an existing plan in this project. */
|
|
36
|
+
async normalizeShiftPlan(input, options) {
|
|
37
|
+
if (input.modes !== 'shifts' || !input.shiftPlan) {
|
|
38
|
+
const normalized = { ...input };
|
|
39
|
+
delete normalized.shiftPlan;
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
if (!ObjectId.isValid(input.shiftPlan))
|
|
43
|
+
throw new ImmutableError('TIME_TRACKING_SHIFT_PLAN_NOT_FOUND');
|
|
44
|
+
const plan = await ShiftPlansService.planForScheduling(new ObjectId(input.shiftPlan), options, false);
|
|
45
|
+
if (!plan)
|
|
46
|
+
throw new ImmutableError('TIME_TRACKING_SHIFT_PLAN_NOT_FOUND');
|
|
47
|
+
return input;
|
|
48
|
+
}
|
|
49
|
+
singleTimeTrackingProject(id, options) {
|
|
50
|
+
return this.model.db.findOne({ id, company: options.company, project: options.project }, { projection: { history: 0 } });
|
|
51
|
+
}
|
|
52
|
+
async createTimeTrackingProject(input, options) {
|
|
53
|
+
this.assertCategories(input.maximumHours, input.categories);
|
|
54
|
+
const normalized = await this.normalizeShiftPlan(input, options);
|
|
55
|
+
this.assertCategoryRequired(normalized.modes, normalized.shiftPlan, input.categories);
|
|
56
|
+
await this.create({ ...normalized, company: options.company, project: options.project }, options.user, options.app);
|
|
57
|
+
return (await this.singleTimeTrackingProject(input.id, options));
|
|
58
|
+
}
|
|
59
|
+
async changeTimeTrackingProject(id, input, options) {
|
|
60
|
+
this.assertCategories(input.maximumHours, input.categories);
|
|
61
|
+
const existing = await this.singleTimeTrackingProject(id, options);
|
|
62
|
+
if (!existing?._id)
|
|
63
|
+
throw new ImmutableError('TIME_TRACKING_PROJECT_NOT_FOUND');
|
|
64
|
+
const normalized = await this.normalizeShiftPlan(input, options);
|
|
65
|
+
// modes are immutable, so the stored mode decides whether the changed plan still books on shifts
|
|
66
|
+
this.assertCategoryRequired(existing.modes, normalized.shiftPlan, input.categories ?? existing.categories);
|
|
67
|
+
await this.immutable({ _id: existing._id, company: options.company, project: options.project }, normalized, options.user, 'change', options.app);
|
|
68
|
+
return (await this.singleTimeTrackingProject(id, options));
|
|
69
|
+
}
|
|
70
|
+
async removeTimeTrackingProject(id, options) {
|
|
71
|
+
const existing = await this.singleTimeTrackingProject(id, options);
|
|
72
|
+
if (!existing?._id)
|
|
73
|
+
throw new ImmutableError('TIME_TRACKING_PROJECT_NOT_FOUND');
|
|
74
|
+
await this.delete(existing._id, options.user, options.app);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export default new TimeTrackingProjectsService();
|
|
78
|
+
//# sourceMappingURL=timeTrackingProjects.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeTrackingProjects.service.js","sourceRoot":"","sources":["../../../src/services/timeTrackingProjects.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Options, Scope, Tracker, TrackerBooking, TrackerBookingShift, TrackerShiftBooking, TrackerData, TrackerGroup, TrackerInput, TrackerStatistics, TimeTrackingProjectAllocationUsage, UserApp, UserAppOptionsRequired, BasicData, BookingWindow, ProjectIdParams, ProjectParams, TrackerUserQuery } from '@lilaquadrat/interfaces';
|
|
2
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
3
|
+
import { ObjectId, WithId } from 'mongodb';
|
|
4
|
+
import Immutable from '../Immutable.class.js';
|
|
5
|
+
import type { TrackerDailyLimitInput } from '../interfaces/TrackerDailyLimitInput.js';
|
|
6
|
+
import type { TrackerStatisticsBucket } from '../interfaces/TrackerStatisticsBucket.js';
|
|
7
|
+
import type { TrackerStatisticsRange } from '../interfaces/TrackerStatisticsRange.js';
|
|
8
|
+
/** the `user` query value that lists every person's entries; `filterAdmin` removes `user` for anyone without the admin scope */
|
|
9
|
+
export declare const TRACKER_ALL_USERS = "all";
|
|
10
|
+
export declare class TrackerService extends Immutable<Tracker> {
|
|
11
|
+
model: import("../classes/modelv2.class.js").default<Tracker, string>;
|
|
12
|
+
/** writes are pinned to the company and project, so an id from another tenant never matches */
|
|
13
|
+
private scopeFilter;
|
|
14
|
+
/** the stored entry of this company and project; it carries the owner and plan, which a change payload does not */
|
|
15
|
+
scopedBooking(id: ObjectId, options: Pick<UserAppOptionsRequired, 'company' | 'project'>): Promise<WithId<Tracker> | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Turns the request entries into bookings of the caller and runs every guard before anything is stored.
|
|
18
|
+
* Only an admin books for another person, so everybody else loses the `customer` the body names.
|
|
19
|
+
*/
|
|
20
|
+
private prepareBookings;
|
|
21
|
+
createBooking(input: Tracker, options: UserAppOptionsRequired): Promise<import("@lilaquadrat/interfaces").CreateResult<BasicData<Tracker>>>;
|
|
22
|
+
createBookings(input: Tracker[], options: UserAppOptionsRequired): Promise<{
|
|
23
|
+
ids: any;
|
|
24
|
+
documentsInserted: number;
|
|
25
|
+
documentsProvided: number;
|
|
26
|
+
}>;
|
|
27
|
+
/** the change payload with real dates, without the keys the model keeps as booked */
|
|
28
|
+
private changeOf;
|
|
29
|
+
/**
|
|
30
|
+
* Changes a booking of this company and project. Access is decided by `ownBookingOrAdmin` before.
|
|
31
|
+
* The shift assignment is only checked when the shift or the person changes, so entries whose
|
|
32
|
+
* assignment was removed later can still get their time or description corrected.
|
|
33
|
+
*/
|
|
34
|
+
changeBooking(id: ObjectId, input: TrackerInput, options: UserAppOptionsRequired): Promise<import("mongodb").UpdateResult<import("bson").Document>>;
|
|
35
|
+
changeDescription(id: ObjectId, input: Pick<Tracker, 'description'>, options: UserAppOptionsRequired): Promise<import("mongodb").UpdateResult<import("bson").Document>>;
|
|
36
|
+
removeBooking(id: ObjectId, options: UserAppOptionsRequired): Promise<import("mongodb").DeleteResult>;
|
|
37
|
+
/** Uses the tracked span when both ends are present, mirroring the duration stored by changeBooking(). */
|
|
38
|
+
private dailyLimitDuration;
|
|
39
|
+
/**
|
|
40
|
+
* The entries that count as one person's time: the ones booked for that customer, which an admin
|
|
41
|
+
* stores under their own account, and the ones the person's own accounts booked. Counting by the
|
|
42
|
+
* booking account alone charges everything an admin books for others to the admin.
|
|
43
|
+
*/
|
|
44
|
+
private personFilter;
|
|
45
|
+
/**
|
|
46
|
+
* Rejects entries that would push a user's tracked time for one plan and calendar day above the
|
|
47
|
+
* plan's maximumHoursPerDay. Missing plans, plans without the option and entries without a plan
|
|
48
|
+
* fall back to the 24 hour ceiling so existing trackers keep working.
|
|
49
|
+
*/
|
|
50
|
+
assertDailyLimit(entries: TrackerDailyLimitInput[], options: UserAppOptionsRequired, excludeId?: ObjectId): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* The caller's own customer in this project, used to check the shifts the caller is assigned to.
|
|
53
|
+
* App is intentionally not pinned: the tracker client reaches the same flow with different
|
|
54
|
+
* `studio-app` ids (entry save uses the editor app, shift and allocation lookups use the tracker
|
|
55
|
+
* app), and the customer connection is what carries the assignment, not the app that asked.
|
|
56
|
+
*/
|
|
57
|
+
private callerCustomer;
|
|
58
|
+
/**
|
|
59
|
+
* Rejects entries referencing a shift the caller may not book on. A member only books their own
|
|
60
|
+
* customer, so every shift entry has to carry that customer in `assignments.customer`. A project
|
|
61
|
+
* admin books for the person on the shift, so the entry's own `customer` is what gets checked
|
|
62
|
+
* (an admin entry without one still falls back to their own customer, the "record time" screen).
|
|
63
|
+
* Entries without a shift are ignored, so every other tracking mode is unaffected.
|
|
64
|
+
*/
|
|
65
|
+
assertShiftAssignment(entries: Array<Pick<Tracker, 'shift' | 'customer'>>, options: UserAppOptionsRequired, admin?: boolean): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Rejects entries whose grouping does not match their plan. A shift-based plan books on shifts
|
|
68
|
+
* and must not carry a category; every other plan, and an entry without a plan, groups by
|
|
69
|
+
* category. Entries are checked against one plan lookup per distinct plan.
|
|
70
|
+
*
|
|
71
|
+
* A change is lenient: entries created before shift bookings existed carry a category next to
|
|
72
|
+
* their shift, so it only requires the result to still be grouped at all.
|
|
73
|
+
*/
|
|
74
|
+
assertPlanGrouping(entries: Array<Pick<Tracker, 'shift' | 'category'> & Partial<Pick<Tracker, 'timeTrackingProject'>>>, options: UserAppOptionsRequired, assertion?: 'create' | 'change'): Promise<void>;
|
|
75
|
+
/** the owner of an entry, the id when the user is not resolved to a profile */
|
|
76
|
+
ownerOf(tracker: Pick<Tracker, 'user'>): string | undefined;
|
|
77
|
+
/** `tracker:admin` reads and changes the entries of everybody, not just the caller's own */
|
|
78
|
+
isTrackerAdmin(options: Pick<UserAppOptionsRequired, 'user' | 'company' | 'project'>): Promise<boolean>;
|
|
79
|
+
/**
|
|
80
|
+
* Changing or removing a booking needs the route's scope and, on top of it, the booking has to be the caller's
|
|
81
|
+
* own or the caller has `tracker:admin`. The scope guard runs first, so owning a booking never replaces it.
|
|
82
|
+
* A booking that does not exist is not decided here, the service answers it as missing.
|
|
83
|
+
*/
|
|
84
|
+
mayChangeBooking(stored: Tracker | null | undefined, options: Pick<UserAppOptionsRequired, 'user' | 'company' | 'project'>): Promise<boolean>;
|
|
85
|
+
/** preHandler for the routes that change or remove one booking, see mayChangeBooking() */
|
|
86
|
+
ownBookingOrAdmin(): (request: FastifyRequest<{
|
|
87
|
+
Params: ProjectIdParams;
|
|
88
|
+
}>, reply: FastifyReply) => Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* An admin may book for another person: on a shift for the person assigned to it, or anywhere by
|
|
91
|
+
* naming their customer. Only asked when an entry carries a shift or a customer, so the ordinary
|
|
92
|
+
* tracking modes cost no permission lookup.
|
|
93
|
+
*/
|
|
94
|
+
private bookingAdmin;
|
|
95
|
+
private withoutCustomer;
|
|
96
|
+
/**
|
|
97
|
+
* ISO-normalized booking window; the same overlap convention the shift planning filters use.
|
|
98
|
+
* Both ends are required, the tracker only works with windows it can bound.
|
|
99
|
+
*/
|
|
100
|
+
bookingInterval(from: string | undefined, to: string | undefined): {
|
|
101
|
+
from: string;
|
|
102
|
+
to: string;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* The shifts the caller may book tracked time on: the shifts of the plan that carry the caller's
|
|
106
|
+
* own customer in `assignments.customer` and overlap the booking window. The narrow projection
|
|
107
|
+
* lets the tracker app offer them without `shifts:read`.
|
|
108
|
+
*/
|
|
109
|
+
bookableShifts(timeTrackingProject: string, from: string | undefined, to: string | undefined, options: UserAppOptionsRequired): Promise<TrackerBookingShift[]>;
|
|
110
|
+
/**
|
|
111
|
+
* Every time entry booked on a shift of a time tracking project, so each booking can be edited
|
|
112
|
+
* or removed on its own. An entry names its person in `customer` only when an admin booked it;
|
|
113
|
+
* a member's own entry carries just the account, so that account is resolved to its connected
|
|
114
|
+
* customer.
|
|
115
|
+
*/
|
|
116
|
+
shiftBookings(timeTrackingProject: string, options: Required<Options>): Promise<TrackerShiftBooking[]>;
|
|
117
|
+
single(internalId: ObjectId, options: Required<Options>): Promise<Tracker | null>;
|
|
118
|
+
/**
|
|
119
|
+
* The entries of one plan inside a range, newest last, each with the name of the person who booked it.
|
|
120
|
+
* `user` is the person to list, or TRACKER_ALL_USERS; the caller decides who may ask for more than their own.
|
|
121
|
+
*/
|
|
122
|
+
bookings(timeTrackingProject: string, range: BookingWindow, user: string, options: Required<Options>): Promise<TrackerBooking[]>;
|
|
123
|
+
/**
|
|
124
|
+
* Hours a person was allocated per category against everything they booked on the plan so far.
|
|
125
|
+
* One aggregation sums all categories, so the cost does not grow with the number of allocations.
|
|
126
|
+
*/
|
|
127
|
+
allocationUsage(timeTrackingProject: string, user: string, options: Required<Options>): Promise<TimeTrackingProjectAllocationUsage[]>;
|
|
128
|
+
statistics(user: string, company?: string, project?: string, timeTrackingProject?: string, dateRange?: TrackerStatisticsRange): Promise<TrackerStatistics>;
|
|
129
|
+
day(day: string, options: Options & UserApp): Promise<{
|
|
130
|
+
data: WithId<Tracker>[];
|
|
131
|
+
count: number;
|
|
132
|
+
statistics: Partial<Record<string, TrackerData>>;
|
|
133
|
+
date: Date;
|
|
134
|
+
}>;
|
|
135
|
+
/** the categories used in this company and project; the search matches literally, it is no pattern */
|
|
136
|
+
categories(search: string, options: Options & UserApp): Promise<string[]>;
|
|
137
|
+
/**
|
|
138
|
+
* Turns the day and group buckets of the statistics aggregation into the summary. Entries without a
|
|
139
|
+
* group count towards `count` only, the same as entries the tracker cannot place in any bucket.
|
|
140
|
+
*/
|
|
141
|
+
statisticsOf(buckets: TrackerStatisticsBucket[]): Omit<TrackerStatistics, 'start' | 'end'>;
|
|
142
|
+
createStatistics(data: Tracker[], range: 'day' | 'month', rangeIdentifier: string): Partial<Record<TrackerGroup, TrackerData>>;
|
|
143
|
+
/** preHandler that removes the `user` query for anybody without the given scopes, so they only see their own entries */
|
|
144
|
+
filterAdmin(scope: Scope): (request: FastifyRequest<{
|
|
145
|
+
Params: ProjectParams;
|
|
146
|
+
Querystring: TrackerUserQuery;
|
|
147
|
+
}>, reply: FastifyReply) => Promise<undefined>;
|
|
148
|
+
}
|
|
149
|
+
declare const _default: TrackerService;
|
|
150
|
+
export default _default;
|