@driveup/schema 0.2.7 → 0.3.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/catalog/addon/addon.entity.d.ts +2 -2
- package/lib/catalog/addon/addon.entity.js +2 -2
- package/lib/catalog/addon/addon.entity.js.map +1 -1
- package/lib/catalog/addon/country.entity.d.ts +2 -2
- package/lib/catalog/addon/country.entity.js +1 -2
- package/lib/catalog/addon/country.entity.js.map +1 -1
- package/lib/catalog/priceplan/priceplan.entity.d.ts +9 -4
- package/lib/catalog/priceplan/priceplan.entity.js +11 -4
- package/lib/catalog/priceplan/priceplan.entity.js.map +1 -1
- package/lib/catalog/product/country.entity.d.ts +12 -4
- package/lib/catalog/product/country.entity.js +19 -3
- package/lib/catalog/product/country.entity.js.map +1 -1
- package/lib/catalog/product/priceplan.entity.d.ts +6 -18
- package/lib/catalog/product/priceplan.entity.js +11 -33
- package/lib/catalog/product/priceplan.entity.js.map +1 -1
- package/lib/catalog/product/product.entity.d.ts +2 -2
- package/lib/catalog/product/product.entity.js +2 -2
- package/lib/catalog/product/product.entity.js.map +1 -1
- package/lib/catalog/program/program.entity.d.ts +2 -2
- package/lib/catalog/program/program.entity.js +2 -2
- package/lib/catalog/program/program.entity.js.map +1 -1
- package/lib/catalog/seed/assets/asset.entity.d.ts +2 -2
- package/lib/catalog/seed/assets/asset.entity.js +3 -4
- package/lib/catalog/seed/assets/asset.entity.js.map +1 -1
- package/lib/catalog/seed/courses/course.entity.d.ts +2 -2
- package/lib/catalog/seed/courses/course.entity.js +2 -2
- package/lib/catalog/seed/courses/course.entity.js.map +1 -1
- package/lib/catalog/seed/courses/lesson.entity.d.ts +2 -2
- package/lib/catalog/seed/courses/lesson.entity.js +2 -2
- package/lib/catalog/seed/courses/lesson.entity.js.map +1 -1
- package/lib/catalog/seed/courses/topic.entity.d.ts +2 -2
- package/lib/catalog/seed/courses/topic.entity.js +3 -3
- package/lib/catalog/seed/courses/topic.entity.js.map +1 -1
- package/lib/catalog/seed/expense/category.entity.d.ts +2 -2
- package/lib/catalog/seed/expense/category.entity.js +2 -2
- package/lib/catalog/seed/expense/category.entity.js.map +1 -1
- package/lib/index.d.ts +6 -1
- package/lib/index.js +7 -1
- package/lib/index.js.map +1 -1
- package/lib/profile/agent/agent.entity.d.ts +2 -1
- package/lib/profile/agent/agent.entity.js +17 -15
- package/lib/profile/agent/agent.entity.js.map +1 -1
- package/lib/profile/agent/asset.entity.d.ts +2 -2
- package/lib/profile/agent/asset.entity.js +1 -2
- package/lib/profile/agent/asset.entity.js.map +1 -1
- package/lib/profile/agent/payout.entity.d.ts +2 -2
- package/lib/profile/agent/payout.entity.js +2 -2
- package/lib/profile/agent/payout.entity.js.map +1 -1
- package/lib/profile/agent/promotion.entity.d.ts +3 -11
- package/lib/profile/agent/promotion.entity.js +3 -15
- package/lib/profile/agent/promotion.entity.js.map +1 -1
- package/lib/profile/company/company.entity.d.ts +43 -3
- package/lib/profile/company/company.entity.js +106 -33
- package/lib/profile/company/company.entity.js.map +1 -1
- package/lib/profile/company/course/appointment.entity.d.ts +271 -0
- package/lib/profile/company/course/appointment.entity.js +736 -0
- package/lib/profile/company/course/appointment.entity.js.map +1 -0
- package/lib/profile/company/course/course.entity.d.ts +8 -2
- package/lib/profile/company/course/course.entity.js +11 -4
- package/lib/profile/company/course/course.entity.js.map +1 -1
- package/lib/profile/company/course/lesson.entity.d.ts +10 -1
- package/lib/profile/company/course/lesson.entity.js +16 -2
- package/lib/profile/company/course/lesson.entity.js.map +1 -1
- package/lib/profile/company/course/schedule.entity.d.ts +8 -4
- package/lib/profile/company/course/schedule.entity.js +5 -0
- package/lib/profile/company/course/schedule.entity.js.map +1 -1
- package/lib/profile/company/course/topic.entity.d.ts +4 -3
- package/lib/profile/company/course/topic.entity.js +3 -3
- package/lib/profile/company/course/topic.entity.js.map +1 -1
- package/lib/profile/company/finances/billing/bill.entity.d.ts +8 -1
- package/lib/profile/company/finances/billing/bill.entity.js +35 -1
- package/lib/profile/company/finances/billing/bill.entity.js.map +1 -1
- package/lib/profile/company/finances/billing/item.entity.d.ts +15 -1
- package/lib/profile/company/finances/billing/item.entity.js +17 -1
- package/lib/profile/company/finances/billing/item.entity.js.map +1 -1
- package/lib/profile/company/finances/billing/transaction.entity.d.ts +5 -3
- package/lib/profile/company/finances/billing/transaction.entity.js +2 -2
- package/lib/profile/company/finances/billing/transaction.entity.js.map +1 -1
- package/lib/profile/company/finances/invoice/invoice.entity.d.ts +2 -2
- package/lib/profile/company/finances/invoice/invoice.entity.js +8 -8
- package/lib/profile/company/finances/invoice/invoice.entity.js.map +1 -1
- package/lib/profile/company/finances/payment/method.entity.d.ts +16 -2
- package/lib/profile/company/finances/payment/method.entity.js +18 -2
- package/lib/profile/company/finances/payment/method.entity.js.map +1 -1
- package/lib/profile/company/instructor/instructor.entity.d.ts +29 -15
- package/lib/profile/company/instructor/instructor.entity.js +59 -20
- package/lib/profile/company/instructor/instructor.entity.js.map +1 -1
- package/lib/profile/company/medical/examination.entity.d.ts +2 -2
- package/lib/profile/company/medical/examination.entity.js +1 -1
- package/lib/profile/company/medical/examination.entity.js.map +1 -1
- package/lib/profile/company/medical/product.entity.d.ts +2 -3
- package/lib/profile/company/medical/product.entity.js +8 -9
- package/lib/profile/company/medical/product.entity.js.map +1 -1
- package/lib/profile/company/product/addon.entity.d.ts +2 -2
- package/lib/profile/company/product/addon.entity.js +2 -3
- package/lib/profile/company/product/addon.entity.js.map +1 -1
- package/lib/profile/company/product/priceplan.entity.d.ts +14 -13
- package/lib/profile/company/product/priceplan.entity.js +14 -15
- package/lib/profile/company/product/priceplan.entity.js.map +1 -1
- package/lib/profile/company/product/product.entity.d.ts +29 -4
- package/lib/profile/company/product/product.entity.js +54 -6
- package/lib/profile/company/product/product.entity.js.map +1 -1
- package/lib/profile/instructor/instructor.entity.d.ts +2 -2
- package/lib/profile/instructor/instructor.entity.js +4 -3
- package/lib/profile/instructor/instructor.entity.js.map +1 -1
- package/lib/profile/student/program/licence/licence.entity.d.ts +10 -2
- package/lib/profile/student/program/licence/licence.entity.js +14 -3
- package/lib/profile/student/program/licence/licence.entity.js.map +1 -1
- package/lib/profile/student/program/medical/certificate.entity.d.ts +2 -2
- package/lib/profile/student/program/medical/certificate.entity.js +2 -2
- package/lib/profile/student/program/medical/certificate.entity.js.map +1 -1
- package/lib/profile/student/program/medical/examination.entity.d.ts +2 -2
- package/lib/profile/student/program/medical/examination.entity.js +4 -4
- package/lib/profile/student/program/medical/examination.entity.js.map +1 -1
- package/lib/profile/student/program/program.entity.d.ts +4 -4
- package/lib/profile/student/program/program.entity.js +5 -10
- package/lib/profile/student/program/program.entity.js.map +1 -1
- package/lib/profile/student/program/training/appointment.entity.d.ts +21 -13
- package/lib/profile/student/program/training/appointment.entity.js +49 -23
- package/lib/profile/student/program/training/appointment.entity.js.map +1 -1
- package/lib/profile/student/program/training/exam.entity.d.ts +6 -1
- package/lib/profile/student/program/training/exam.entity.js +16 -8
- package/lib/profile/student/program/training/exam.entity.js.map +1 -1
- package/lib/profile/student/program/training/lesson.entity.d.ts +3 -3
- package/lib/profile/student/program/training/lesson.entity.js +4 -4
- package/lib/profile/student/program/training/lesson.entity.js.map +1 -1
- package/lib/profile/student/program/training/topic.entity.d.ts +6 -5
- package/lib/profile/student/program/training/topic.entity.js +9 -6
- package/lib/profile/student/program/training/topic.entity.js.map +1 -1
- package/lib/profile/student/program/training/training-balance.view.d.ts +4 -0
- package/lib/profile/student/program/training/training-balance.view.js +37 -0
- package/lib/profile/student/program/training/training-balance.view.js.map +1 -0
- package/lib/profile/student/program/training/training-cost.view.d.ts +4 -0
- package/lib/profile/student/program/training/training-cost.view.js +45 -0
- package/lib/profile/student/program/training/training-cost.view.js.map +1 -0
- package/lib/profile/student/program/training/training-sessions.view.d.ts +8 -0
- package/lib/profile/student/program/training/training-sessions.view.js +50 -0
- package/lib/profile/student/program/training/training-sessions.view.js.map +1 -0
- package/lib/profile/student/program/training/training.entity.d.ts +4 -1
- package/lib/profile/student/program/training/training.entity.js +43 -36
- package/lib/profile/student/program/training/training.entity.js.map +1 -1
- package/lib/profile/student/program/training/transaction.entity.d.ts +2 -2
- package/lib/profile/student/program/training/transaction.entity.js +3 -3
- package/lib/profile/student/program/training/transaction.entity.js.map +1 -1
- package/lib/profile/student/student.entity.d.ts +2 -2
- package/lib/profile/student/student.entity.js +5 -4
- package/lib/profile/student/student.entity.js.map +1 -1
- package/lib/public/faq.d.ts +25 -0
- package/lib/public/faq.js +60 -0
- package/lib/public/faq.js.map +1 -0
- package/lib/public/feature-item.d.ts +54 -0
- package/lib/public/feature-item.js +78 -0
- package/lib/public/feature-item.js.map +1 -0
- package/lib/public/feature.d.ts +46 -0
- package/lib/public/feature.js +76 -0
- package/lib/public/feature.js.map +1 -0
- package/lib/public/learning-hub.d.ts +30 -0
- package/lib/public/learning-hub.js +77 -0
- package/lib/public/learning-hub.js.map +1 -0
- package/lib/public/testimonial.d.ts +40 -0
- package/lib/public/testimonial.js +85 -0
- package/lib/public/testimonial.js.map +1 -0
- package/lib/system/campaign/campaign.entity.d.ts +3 -3
- package/lib/system/campaign/campaign.entity.js +4 -4
- package/lib/system/campaign/campaign.entity.js.map +1 -1
- package/lib/system/campaign/gifcode.entity.d.ts +2 -2
- package/lib/system/campaign/gifcode.entity.js +3 -3
- package/lib/system/campaign/gifcode.entity.js.map +1 -1
- package/lib/system/country/country.entity.d.ts +7 -5
- package/lib/system/country/country.entity.js +11 -6
- package/lib/system/country/country.entity.js.map +1 -1
- package/lib/system/driving/category.entity.d.ts +2 -2
- package/lib/system/driving/category.entity.js +2 -2
- package/lib/system/driving/category.entity.js.map +1 -1
- package/lib/system/ical/ical.entity.d.ts +63 -0
- package/lib/system/ical/ical.entity.js +70 -0
- package/lib/system/ical/ical.entity.js.map +1 -0
- package/lib/system/policy/policy.entity.d.ts +2 -2
- package/lib/system/policy/policy.entity.js +3 -3
- package/lib/system/policy/policy.entity.js.map +1 -1
- package/lib/system/staff/staff.entity.d.ts +2 -2
- package/lib/system/staff/staff.entity.js +3 -2
- package/lib/system/staff/staff.entity.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/user/alert.entity.d.ts +2 -2
- package/lib/user/alert.entity.js.map +1 -1
- package/lib/user/device.entity.d.ts +2 -2
- package/lib/user/device.entity.js +3 -3
- package/lib/user/device.entity.js.map +1 -1
- package/lib/user/notification.entity.d.ts +2 -2
- package/lib/user/notification.entity.js +4 -4
- package/lib/user/notification.entity.js.map +1 -1
- package/lib/user/ticket.entity.d.ts +2 -2
- package/lib/user/ticket.entity.js +6 -4
- package/lib/user/ticket.entity.js.map +1 -1
- package/lib/user/user.entity.d.ts +14 -11
- package/lib/user/user.entity.js +20 -16
- package/lib/user/user.entity.js.map +1 -1
- package/lib/utils/activity.helper.d.ts +2 -2
- package/lib/utils/activity.helper.js.map +1 -1
- package/lib/utils/trackable.d.ts +12 -1
- package/lib/utils/trackable.js +55 -0
- package/lib/utils/trackable.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CompanyCourseScheduleEntity = void 0;
|
|
13
|
+
const typeorm_1 = require("typeorm");
|
|
14
|
+
const common_1 = require("@driveup/common");
|
|
15
|
+
const lesson_entity_1 = require("./lesson.entity");
|
|
16
|
+
const appointment_entity_1 = require("../../student/program/training/appointment.entity");
|
|
17
|
+
const instructor_entity_1 = require("../instructor/instructor.entity");
|
|
18
|
+
const trackable_1 = require("../../../utils/trackable");
|
|
19
|
+
const activity_helper_1 = require("../../../utils/activity.helper");
|
|
20
|
+
const shared_1 = require("@driveup/shared");
|
|
21
|
+
const luxon_1 = require("luxon");
|
|
22
|
+
/**
|
|
23
|
+
* Represents a series of training appointment offers managed by the company.
|
|
24
|
+
*
|
|
25
|
+
* This entity defines appointment templates that serve as available time slots
|
|
26
|
+
* for student training sessions. Companies manage these appointments to organize
|
|
27
|
+
* their course training schedules, which students can then book.
|
|
28
|
+
*
|
|
29
|
+
* Supports both single and recurring appointments with flexible scheduling options
|
|
30
|
+
* including:
|
|
31
|
+
* - Weekly repetitions on specific weekdays
|
|
32
|
+
* - Custom date ranges for recurring appointments
|
|
33
|
+
* - Exception handling for skipped dates
|
|
34
|
+
* - Per-date overrides for time, instructor, and duration
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* This is a company-managed entity that creates the foundation for student
|
|
38
|
+
* training bookings. Each appointment definition can generate multiple actual
|
|
39
|
+
* student appointments based on its repetition settings.
|
|
40
|
+
*
|
|
41
|
+
* @see {@link CompanyCourseLessonEntity} for the associated course lesson
|
|
42
|
+
* @see {@link StudentTrainingEntity} for student bookings
|
|
43
|
+
*/
|
|
44
|
+
let CompanyCourseScheduleEntity = class CompanyCourseScheduleEntity extends trackable_1.TrackableEntity {
|
|
45
|
+
/**
|
|
46
|
+
* Creates a new CompanyCourseScheduleEntity instance
|
|
47
|
+
* @param props - Partial properties to initialize the entity with
|
|
48
|
+
*/
|
|
49
|
+
constructor(props) {
|
|
50
|
+
super();
|
|
51
|
+
Object.assign(this, props);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Converts the entity to a RecurringAppointment model instance
|
|
55
|
+
* @returns Promise resolving to RecurringAppointment model with lesson, instructor, and scheduling details
|
|
56
|
+
*/
|
|
57
|
+
async toModel() {
|
|
58
|
+
const lesson = this.lessonId ? (await this.lesson).toSummary() : null;
|
|
59
|
+
const instructor = this.instructorId ? await (await this.instructor).toSummary() : null;
|
|
60
|
+
return new common_1.RecurringAppointment({
|
|
61
|
+
id: this.id,
|
|
62
|
+
lesson: lesson,
|
|
63
|
+
instructor: instructor,
|
|
64
|
+
startDate: this.startDate,
|
|
65
|
+
endDate: this.endDate,
|
|
66
|
+
duration: this.duration,
|
|
67
|
+
repetition: this.repetition,
|
|
68
|
+
weekdays: this.weekdays,
|
|
69
|
+
skippedDates: this.skippedDates,
|
|
70
|
+
updates: this.updates,
|
|
71
|
+
instructors: this.instructors,
|
|
72
|
+
durations: this.durations
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Returns a summary representation of the appointment
|
|
77
|
+
* @returns Promise resolving to RecurringAppointment model (same as toModel())
|
|
78
|
+
*/
|
|
79
|
+
async toSummary() {
|
|
80
|
+
return await this.toModel();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Retrieves all tracked activities for this appointment
|
|
84
|
+
* Includes creation and update timestamps and associated users
|
|
85
|
+
* @returns Promise resolving to array of Activity objects filtered to exclude null values
|
|
86
|
+
*/
|
|
87
|
+
async getActivities() {
|
|
88
|
+
const activities = [];
|
|
89
|
+
activities.push(await (0, activity_helper_1.toActivity)(this.createdBy, this.createdOn, shared_1.ActivityType.created));
|
|
90
|
+
activities.push(await (0, activity_helper_1.toActivity)(this.updatedBy, this.updatedOn, shared_1.ActivityType.updated));
|
|
91
|
+
return activities.filter(a => a);
|
|
92
|
+
}
|
|
93
|
+
async getCompany() {
|
|
94
|
+
const lesson = await this.lesson;
|
|
95
|
+
return lesson.getCompany();
|
|
96
|
+
}
|
|
97
|
+
async getCourse() {
|
|
98
|
+
const lesson = await this.lesson;
|
|
99
|
+
return lesson.getCourse();
|
|
100
|
+
}
|
|
101
|
+
async getInstructor() {
|
|
102
|
+
return await this.instructor;
|
|
103
|
+
}
|
|
104
|
+
async getInstructorByDate(date) {
|
|
105
|
+
const d = this.instructors?.find(i => i.date === date);
|
|
106
|
+
return d?.id ? await instructor_entity_1.CompanyInstructorEntity.findOneBy({ id: d.id }) : null;
|
|
107
|
+
}
|
|
108
|
+
/************************* Manage student appointments *************************/
|
|
109
|
+
/**
|
|
110
|
+
* Get list of related student training appointments, which's training is not terminated
|
|
111
|
+
* @param date Date - defaulted to this.startDate - Must be provided for flexible course appointments
|
|
112
|
+
* @returns StudentTrainingAppointmentEntity[]
|
|
113
|
+
*/
|
|
114
|
+
async getStudentAppointments(date = this.startDate) {
|
|
115
|
+
const course = await (await this.lesson).course;
|
|
116
|
+
if (!course.isFixed && !date) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
return await appointment_entity_1.StudentTrainingAppointmentEntity.createQueryBuilder('ta') // training appointment
|
|
120
|
+
.innerJoin('ta.training', 't') // training
|
|
121
|
+
.where('ta.scheduleId = :id', { id: this.id })
|
|
122
|
+
.andWhere('ta.startDate = :date', { date: date })
|
|
123
|
+
.andWhere('t.status != :terminatedStatus', { terminatedStatus: shared_1.StatusType.Terminated })
|
|
124
|
+
.getMany();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get total number of students who attended the appointment in the given date
|
|
128
|
+
* @param date Date - defaulted to this.startDate - Must be provided for flexible course appointments
|
|
129
|
+
*/
|
|
130
|
+
async getTotalAttended(date = this.startDate) {
|
|
131
|
+
const course = await (await this.lesson).course;
|
|
132
|
+
if (!course.isFixed && !date) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return await appointment_entity_1.StudentTrainingAppointmentEntity.countBy({
|
|
136
|
+
scheduleId: this.id,
|
|
137
|
+
startDate: date,
|
|
138
|
+
attended: true
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get total number of students who where absent in the appointment in the given date
|
|
143
|
+
* @param date Date - defaulted to this.startDate - Must be provided for flexible course appointments
|
|
144
|
+
*/
|
|
145
|
+
async getTotalAbsent(date = this.startDate) {
|
|
146
|
+
const course = await (await this.lesson).course;
|
|
147
|
+
if (!course.isFixed && !date) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return await appointment_entity_1.StudentTrainingAppointmentEntity.countBy({
|
|
151
|
+
scheduleId: this.id,
|
|
152
|
+
startDate: date,
|
|
153
|
+
attended: false
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get the count of taken seats for this appointment
|
|
158
|
+
* - Only applicable for preparation catalog type courses. Returns null for other catalog types.
|
|
159
|
+
* @param startDate Date - Optional - Only send for flexible courses to specify the desired appointment's startDate
|
|
160
|
+
* @returns number - count of registered student appointments for this appointment
|
|
161
|
+
*/
|
|
162
|
+
async getTakenSeats(startDate) {
|
|
163
|
+
const course = await this.getCourse();
|
|
164
|
+
const product = await course.product;
|
|
165
|
+
if (product.catalogType !== shared_1.CatalogType.Preparation) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const count = await appointment_entity_1.StudentTrainingAppointmentEntity.countBy({
|
|
169
|
+
scheduleId: this.id,
|
|
170
|
+
startDate: startDate ?? this.startDate
|
|
171
|
+
});
|
|
172
|
+
return count;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the count of present students for this appointment
|
|
176
|
+
* - Only applicable for preparation catalog type courses. Returns null for other catalog types.
|
|
177
|
+
* @param startDate Date - Optional - Only send for flexible courses to specify the desired appointment's startDate
|
|
178
|
+
* @returns number - count of attended student appointments for this appointment
|
|
179
|
+
*/
|
|
180
|
+
async getTotalPresentStudents(startDate) {
|
|
181
|
+
const course = await this.getCourse();
|
|
182
|
+
const product = await course.product;
|
|
183
|
+
if (product.catalogType !== shared_1.CatalogType.Preparation) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const count = await appointment_entity_1.StudentTrainingAppointmentEntity.countBy({
|
|
187
|
+
scheduleId: this.id,
|
|
188
|
+
startDate: startDate ?? this.startDate,
|
|
189
|
+
attended: true
|
|
190
|
+
});
|
|
191
|
+
return count;
|
|
192
|
+
}
|
|
193
|
+
/************************* Flexible courses *************************/
|
|
194
|
+
/**
|
|
195
|
+
* Finds if there is an appointment in the given date (checking all of the updates), and returning it if is found
|
|
196
|
+
* @param date Date - the date to check if belongs to an existing appointment
|
|
197
|
+
* @returns Appointment if an appointment is found in the given date, null otherwise
|
|
198
|
+
*/
|
|
199
|
+
async getAppointment(date) {
|
|
200
|
+
// Note: other parts of the app depend on this method returning the instructor
|
|
201
|
+
// do not remove unless the dependencies are covered
|
|
202
|
+
// step 0 : prepare local variables
|
|
203
|
+
date = new Date(date);
|
|
204
|
+
let appointmentDate = luxon_1.DateTime.fromJSDate(date);
|
|
205
|
+
// step 1 : manage single appointment
|
|
206
|
+
if (!this.repetition) {
|
|
207
|
+
// if the start date matches the given date, return appointment information
|
|
208
|
+
// otherwise, appointment is not found
|
|
209
|
+
if (this.startDate.getTime() === date.getTime()) {
|
|
210
|
+
const instructor = await this.getAppointmentInstructor(appointmentDate);
|
|
211
|
+
const endDate = luxon_1.DateTime.fromJSDate(this.startDate)
|
|
212
|
+
.plus({ minutes: this.duration })
|
|
213
|
+
.toJSDate();
|
|
214
|
+
return new common_1.Appointment({
|
|
215
|
+
id: this.id,
|
|
216
|
+
startDate: this.startDate,
|
|
217
|
+
endDate,
|
|
218
|
+
instructor: instructor
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
// step 2 : make sure the date is inside the definition's time frame
|
|
224
|
+
if (date < this.startDate || (this.endDate && date > this.endDate)) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
// step 3 : check if the weekday of the given date, is one of the weekdays of the series
|
|
228
|
+
const weekdayMatches = this.weekdays.some(i => appointmentDate.weekday === common_1.DateHelper.getWeekdayNumber(i));
|
|
229
|
+
if (!weekdayMatches) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
// step 4 : check for updates in the time of the appointment
|
|
233
|
+
// if an update is made, then the given date doesn't point to an existing appointment
|
|
234
|
+
appointmentDate = this.getUpdatedDate(appointmentDate);
|
|
235
|
+
if (appointmentDate.toJSDate().getTime() !== date.getTime()) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
// step 5 : check if the appointment is deleted
|
|
239
|
+
const isDeleted = this.isDateSkipped(appointmentDate);
|
|
240
|
+
if (isDeleted) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
// step 6 : get the appointment instructor
|
|
244
|
+
const instructor = await this.getAppointmentInstructor(appointmentDate);
|
|
245
|
+
// step 7 : get the appointment duration and calculate the endDate
|
|
246
|
+
const appointmentDuration = this.getAppointmentDuration(appointmentDate);
|
|
247
|
+
const endDate = appointmentDate.plus({ minutes: appointmentDuration });
|
|
248
|
+
return new common_1.Appointment({
|
|
249
|
+
id: this.id,
|
|
250
|
+
startDate: appointmentDate.toJSDate(),
|
|
251
|
+
endDate: endDate.toJSDate(),
|
|
252
|
+
instructor: instructor
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Load all appointments of the definition within the given period
|
|
257
|
+
* @description currently only works for flexible course schedules, and will return an empty array if the related course is fixed. but can and must be extended in the future to return the entire appointment list of the schedule, even if the appointment is fixed, or for another product type entirely.
|
|
258
|
+
* @param period Period
|
|
259
|
+
* @param instructorIds number[] - instructorId to filter by (return only appointments related to this instructor id)
|
|
260
|
+
* @param searchPhrase string - optional search phrase to filter appointments by (course title / part title / registered student name)
|
|
261
|
+
* @returns All appointments of the definition within the given period, filtered by instructorId if provided
|
|
262
|
+
*/
|
|
263
|
+
async getAppointments(period, instructorIds, searchPhrase) {
|
|
264
|
+
// check if course if flexible
|
|
265
|
+
const lesson = await lesson_entity_1.CompanyCourseLessonEntity.findOneBy({ id: this.lessonId });
|
|
266
|
+
const course = await lesson.course;
|
|
267
|
+
if (course.isFixed)
|
|
268
|
+
return [];
|
|
269
|
+
// step 0 : prepare local variables
|
|
270
|
+
period.startDate = new Date(period.startDate);
|
|
271
|
+
period.endDate = new Date(period.endDate);
|
|
272
|
+
const appointments = [];
|
|
273
|
+
// step 1 : get first appointment date
|
|
274
|
+
// if there is no match for given period, first appointment is not found
|
|
275
|
+
// if not found, return empty array (not appointments)
|
|
276
|
+
let current = this.getFirstAppointmentDateInPeriod(period);
|
|
277
|
+
if (!current)
|
|
278
|
+
return [];
|
|
279
|
+
if (current.toJSDate() > period.endDate ||
|
|
280
|
+
(this.endDate && current.toJSDate() > this.endDate))
|
|
281
|
+
return [];
|
|
282
|
+
// step 2 : from the first appointment and forward
|
|
283
|
+
// 1 -> set appointment information (and check for filtered instructor)
|
|
284
|
+
// 2 -> check if there is a filter by course title / part title / student name required and apply if so
|
|
285
|
+
// 3 -> push to the appointments array
|
|
286
|
+
// 4 -> find the next appointment date "inside the given period"
|
|
287
|
+
// repeat the process
|
|
288
|
+
let appointment = await this.getAppointmentInformation(current, instructorIds);
|
|
289
|
+
if (appointment) {
|
|
290
|
+
if (searchPhrase) {
|
|
291
|
+
const isMatch = lesson.title.toLowerCase().includes(searchPhrase.toLowerCase()) ||
|
|
292
|
+
course.title.toLowerCase().includes(searchPhrase.toLowerCase()) ||
|
|
293
|
+
(await this.isStudentRegistered(appointment, searchPhrase));
|
|
294
|
+
if (isMatch) {
|
|
295
|
+
appointments.push(appointment);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
appointments.push(appointment);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
current = this.getNextAppointmentDate(current);
|
|
303
|
+
if (!current) {
|
|
304
|
+
return appointments;
|
|
305
|
+
}
|
|
306
|
+
while (current.toJSDate() <= period.endDate &&
|
|
307
|
+
(!this.endDate || current.toJSDate() <= this.endDate)) {
|
|
308
|
+
appointment = await this.getAppointmentInformation(current, instructorIds);
|
|
309
|
+
if (appointment) {
|
|
310
|
+
if (searchPhrase) {
|
|
311
|
+
const isMatch = lesson.title.toLowerCase().includes(searchPhrase.toLowerCase()) ||
|
|
312
|
+
course.title.toLowerCase().includes(searchPhrase.toLowerCase()) ||
|
|
313
|
+
(await this.isStudentRegistered(appointment, searchPhrase));
|
|
314
|
+
if (isMatch) {
|
|
315
|
+
appointments.push(appointment);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
appointments.push(appointment);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
current = this.getNextAppointmentDate(current);
|
|
323
|
+
}
|
|
324
|
+
// step 3 : when no more appointments are found inside the given period
|
|
325
|
+
// return the found appointments
|
|
326
|
+
return appointments;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Check if there is one appointment with empty seats
|
|
330
|
+
*
|
|
331
|
+
* If the definition has no appointments in the given period, returns true.
|
|
332
|
+
* Since the functions is supposed to return wether any of the definition's appointments have empty seats
|
|
333
|
+
*/
|
|
334
|
+
async hasAppointmentWithEmptySeats(period) {
|
|
335
|
+
// step 0 : prepare local variables
|
|
336
|
+
period.startDate = new Date(period.startDate);
|
|
337
|
+
period.endDate = new Date(period.endDate);
|
|
338
|
+
// step 1 : get first appointment date
|
|
339
|
+
// if there is no match for given period, first appointment is not found
|
|
340
|
+
// if not found, return empty array (not appointments)
|
|
341
|
+
let current = this.getFirstAppointmentDateInPeriod(period);
|
|
342
|
+
if (!current) {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
if (current.toJSDate() > period.endDate ||
|
|
346
|
+
(this.endDate && current.toJSDate() > this.endDate)) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
// step 2 : from the first appointment and forward
|
|
350
|
+
// 1 -> set appointment information (and check for filtered instructor)
|
|
351
|
+
// 2 -> if appointment is found, and there are remaining seats, return true
|
|
352
|
+
// 3 -> find the next appointment date "inside the given period"
|
|
353
|
+
// repeat the process
|
|
354
|
+
let appointment = await this.getAppointmentInformation(current);
|
|
355
|
+
if (appointment) {
|
|
356
|
+
const remainingSeats = await this.getRemainingSeats(current.toJSDate());
|
|
357
|
+
if (remainingSeats > 0) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
current = this.getNextAppointmentDate(current);
|
|
362
|
+
if (!current) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
while (current.toJSDate() <= period.endDate &&
|
|
366
|
+
(!this.endDate || current.toJSDate() <= this.endDate)) {
|
|
367
|
+
appointment = await this.getAppointmentInformation(current);
|
|
368
|
+
if (appointment) {
|
|
369
|
+
const remainingSeats = await this.getRemainingSeats(current.toJSDate());
|
|
370
|
+
if (remainingSeats > 0) {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
current = this.getNextAppointmentDate(current);
|
|
375
|
+
}
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
*
|
|
380
|
+
* @param period Period
|
|
381
|
+
* @returns DateTime if a first appointment is found, null otherwise
|
|
382
|
+
*/
|
|
383
|
+
getFirstAppointmentDateInPeriod(period) {
|
|
384
|
+
// if this is a single appointment
|
|
385
|
+
// only check if the definition's start date is within the given period
|
|
386
|
+
if (!this.repetition) {
|
|
387
|
+
if (common_1.DateHelper.isBetween(this.startDate, period.startDate, period.endDate)) {
|
|
388
|
+
return luxon_1.DateTime.fromJSDate(this.startDate);
|
|
389
|
+
}
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
// check if given period contains some/all of the definition's start and end date
|
|
393
|
+
const periodMatches = common_1.DateHelper.isBetween(this.startDate, period.startDate, period.endDate) ||
|
|
394
|
+
(this.startDate <= period.endDate &&
|
|
395
|
+
(!this.endDate || this.endDate >= period.startDate));
|
|
396
|
+
if (!periodMatches)
|
|
397
|
+
return null;
|
|
398
|
+
const defStart = luxon_1.DateTime.fromJSDate(this.startDate);
|
|
399
|
+
// get the greater from period start date and definition start date
|
|
400
|
+
// to start counting days from that point forward
|
|
401
|
+
let date = period.startDate > this.startDate ? luxon_1.DateTime.fromJSDate(period.startDate) : defStart;
|
|
402
|
+
date = date.set({
|
|
403
|
+
hour: defStart.hour,
|
|
404
|
+
minute: defStart.minute,
|
|
405
|
+
second: defStart.second,
|
|
406
|
+
millisecond: defStart.millisecond
|
|
407
|
+
});
|
|
408
|
+
// weekdays: sorted array of WeekdayNumbers
|
|
409
|
+
const weekdays = this.weekdays
|
|
410
|
+
.map(i => common_1.DateHelper.getWeekdayNumber(i))
|
|
411
|
+
.sort((a, b) => a - b);
|
|
412
|
+
if (this.repetition && !weekdays?.length) {
|
|
413
|
+
// to avoid infinite loop, if the definition is repeated, but somehow no weekdays are assigned, return null
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
// find the first valid appointment date considering weekdays, updates, and skipped dates
|
|
417
|
+
while (!this.endDate || date.toJSDate() <= this.endDate) {
|
|
418
|
+
if (weekdays.includes(common_1.DateHelper.toWeekdayNumber(date.weekday))) {
|
|
419
|
+
const updatedDate = this.getUpdatedDate(date);
|
|
420
|
+
if (!this.isDateSkipped(updatedDate)) {
|
|
421
|
+
return updatedDate;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// move to the next day
|
|
425
|
+
date = date.plus({ days: 1 });
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* @description Get detailed information about an appointment
|
|
431
|
+
* @param date - DateTime - is presumed to be related to an appointment in this definition
|
|
432
|
+
* @param filteredInstructorIds - number - optional - instrucotr id to filter the appointment by
|
|
433
|
+
* @returns Appointment - appointment information (id, start and end date, instructor information)
|
|
434
|
+
*/
|
|
435
|
+
async getAppointmentInformation(date, filteredInstructorIds) {
|
|
436
|
+
date = this.getUpdatedDate(date);
|
|
437
|
+
const isDeleted = this.isDateSkipped(date);
|
|
438
|
+
if (isDeleted)
|
|
439
|
+
return null;
|
|
440
|
+
const instructorId = this.getAppointmentInstructorId(date);
|
|
441
|
+
if (filteredInstructorIds?.length && !filteredInstructorIds.includes(instructorId))
|
|
442
|
+
return null;
|
|
443
|
+
const instructor = await this.getInstructorById(instructorId);
|
|
444
|
+
const duration = this.getAppointmentDuration(date);
|
|
445
|
+
const endDate = date.plus({ minutes: duration });
|
|
446
|
+
const appointment = new common_1.Appointment({
|
|
447
|
+
id: this.id,
|
|
448
|
+
startDate: date.toJSDate(),
|
|
449
|
+
endDate: endDate.toJSDate(),
|
|
450
|
+
instructor: instructor
|
|
451
|
+
});
|
|
452
|
+
return appointment;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* @description Only for repeted definitions (repetition = true)
|
|
456
|
+
* @description Finds the final date of the appointment, considering any date updates (updates in the startDate)
|
|
457
|
+
* @description If no date updates are found, the original date will be returned
|
|
458
|
+
* @param date DateTime - original date of the appointment (default return value, if no date updates are found)
|
|
459
|
+
* @returns DateTime - final date of the appointment
|
|
460
|
+
*/
|
|
461
|
+
getUpdatedDate(date) {
|
|
462
|
+
if (!this.updates?.length)
|
|
463
|
+
return date;
|
|
464
|
+
let updates = this.updates.map(i => {
|
|
465
|
+
return {
|
|
466
|
+
from: new Date(i.from),
|
|
467
|
+
to: new Date(i.to)
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
updates = updates.filter(i => i.from.getTime() === date.toJSDate().getTime());
|
|
471
|
+
if (!updates?.length) {
|
|
472
|
+
return date;
|
|
473
|
+
}
|
|
474
|
+
return common_1.DateHelper.toDateTimeInstance(common_1.ArrayHelper.getLastItem(updates).to);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* @description Only for repeted definitions (repetition = true)
|
|
478
|
+
* @description Checks wether or not the appointment related to the given date has been removed from schedule (pushed in the skippedDates array)
|
|
479
|
+
* @param date DateTime - date of the appointment
|
|
480
|
+
* @returns DateTime - true if the appointment is skipped, false otherwise
|
|
481
|
+
*/
|
|
482
|
+
isDateSkipped(date) {
|
|
483
|
+
if (!this.skippedDates?.length)
|
|
484
|
+
return false;
|
|
485
|
+
const exists = this.skippedDates.some(i => new Date(i).getTime() === date.toJSDate().getTime());
|
|
486
|
+
return exists;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* @description Only for repeted definitions (repetition = true)
|
|
490
|
+
* @description Finds the final duration of the appointment, considering any duration updates
|
|
491
|
+
* @description If no duration updates are found, this.duration will be returned by default
|
|
492
|
+
* @param date DateTime - date of the appointment
|
|
493
|
+
* @returns number - duration of the appointment (in minutes)
|
|
494
|
+
*/
|
|
495
|
+
getAppointmentDuration(date) {
|
|
496
|
+
if (!this.durations?.length)
|
|
497
|
+
return this.duration;
|
|
498
|
+
const durations = this.durations.filter(i => new Date(i.date).getTime() === date.toJSDate().getTime());
|
|
499
|
+
if (!durations?.length)
|
|
500
|
+
return this.duration;
|
|
501
|
+
return common_1.ArrayHelper.getLastItem(durations).duration;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* @description Checks weather or not there is a student with a matching name registered in this schedule for the given appointment
|
|
505
|
+
* @description Currently only works for flexible course trainings (MVP logic), and will always return false for fixed course trainings
|
|
506
|
+
* Must be renamed and extended in the future to support fixed course trainings as well, but will remain as is for the sake of MVP Migration
|
|
507
|
+
* @param appointment Appointment - appointment to check for registered students
|
|
508
|
+
* @param studentName string - name of the student to check for registration
|
|
509
|
+
* @returns boolean - Weather or not there a student with the given name registered for the appointment
|
|
510
|
+
*/
|
|
511
|
+
async isStudentRegistered(appointment, studentName) {
|
|
512
|
+
const course = await this.getCourse();
|
|
513
|
+
return await appointment_entity_1.StudentTrainingAppointmentEntity
|
|
514
|
+
.createQueryBuilder('a')
|
|
515
|
+
.innerJoin('a.training', 't')
|
|
516
|
+
.innerJoin('t.course', 'c')
|
|
517
|
+
.where('c.id = :courseid', { courseId: course.id })
|
|
518
|
+
.andWhere('c.isFixed = :false', { false: false })
|
|
519
|
+
.andWhere('a.lessonId = :lessonId', { lessonId: this.lessonId })
|
|
520
|
+
.andWhere('a.scheduleId = :scheduleId', { scheduleId: this.id })
|
|
521
|
+
.andWhere('a.startDate = :startDate', { startDate: appointment.startDate })
|
|
522
|
+
.andWhere(`t.studentSnapshot ->> '$.name' like :studentname`, { studentname: `%${studentName}%` })
|
|
523
|
+
.getExists();
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* @description Doesn't check for the definition's endDate, or updates and skippedDates
|
|
527
|
+
* @returns the next appointment based on repetition logic and weekdays assigned
|
|
528
|
+
*/
|
|
529
|
+
getNextAppointmentDate(date) {
|
|
530
|
+
if (!this.weekdays?.length)
|
|
531
|
+
return null;
|
|
532
|
+
const startDate = common_1.DateHelper.toDateTimeInstance(this.startDate);
|
|
533
|
+
// after going to the next day, set the time to the startDate's time
|
|
534
|
+
// since the param date can be the last appointment's date, and the last appointment's time could've been updated
|
|
535
|
+
date = date.plus({ days: 1 }).set({
|
|
536
|
+
hour: startDate.hour,
|
|
537
|
+
minute: startDate.minute,
|
|
538
|
+
second: startDate.second,
|
|
539
|
+
millisecond: startDate.millisecond
|
|
540
|
+
});
|
|
541
|
+
// weekdays: sorted array of WeekdayNumbers
|
|
542
|
+
const weekdays = this.weekdays
|
|
543
|
+
.map(i => common_1.DateHelper.getWeekdayNumber(i))
|
|
544
|
+
.sort((a, b) => a - b);
|
|
545
|
+
while (!weekdays.includes(common_1.DateHelper.toWeekdayNumber(date.weekday))) {
|
|
546
|
+
date = date.plus({ days: 1 }).set({
|
|
547
|
+
hour: startDate.hour,
|
|
548
|
+
minute: startDate.minute,
|
|
549
|
+
second: startDate.second,
|
|
550
|
+
millisecond: startDate.millisecond
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
return date;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Get the instructor registered for the appointment (works for repeated and single appointments)
|
|
557
|
+
* @param date Date - the date of the appointment
|
|
558
|
+
* @returns Instructor - simple instructor model
|
|
559
|
+
*/
|
|
560
|
+
async getAppointmentInstructor(date) {
|
|
561
|
+
const instructorId = this.getAppointmentInstructorId(date);
|
|
562
|
+
return await this.getInstructorById(instructorId);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* @param date DateTime - is presumed to belong to an existing appointment
|
|
566
|
+
* @returns number - instructor id for the appointment of the given date
|
|
567
|
+
*/
|
|
568
|
+
getAppointmentInstructorId(date) {
|
|
569
|
+
if (!this.instructors?.length)
|
|
570
|
+
return this.instructorId;
|
|
571
|
+
const instructors = this.instructors.filter(i => new Date(i.date).getTime() === date.toJSDate().getTime());
|
|
572
|
+
if (!instructors?.length)
|
|
573
|
+
return this.instructorId;
|
|
574
|
+
return common_1.ArrayHelper.getLastItem(instructors).id;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Find instructor by id and map with simple information to User model
|
|
578
|
+
* @param id instructor id
|
|
579
|
+
* @returns Object - For the MVP it returns an object mapped like CompanyInstructorMapper.toSimple with the same interface. should be replaced with Person model in the future
|
|
580
|
+
*/
|
|
581
|
+
async getInstructorById(id) {
|
|
582
|
+
const companyInstructor = await instructor_entity_1.CompanyInstructorEntity.findOneBy({ id });
|
|
583
|
+
if (!companyInstructor)
|
|
584
|
+
return null;
|
|
585
|
+
const instructor = await companyInstructor.instructor;
|
|
586
|
+
const user = await instructor.user;
|
|
587
|
+
return {
|
|
588
|
+
id: companyInstructor.id,
|
|
589
|
+
name: instructor.name,
|
|
590
|
+
image: instructor.imageUrl,
|
|
591
|
+
phone: user.phone,
|
|
592
|
+
role: companyInstructor.role,
|
|
593
|
+
status: companyInstructor.status,
|
|
594
|
+
canReinstate: await companyInstructor.canReinstate(),
|
|
595
|
+
calendarLinks: companyInstructor.calendarLinks || []
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Get total number of appointments that have any update (duration/instructor/date) after the given date
|
|
600
|
+
* @param from Date - if null, from definition start date
|
|
601
|
+
* @returns number - total updated appointments
|
|
602
|
+
*/
|
|
603
|
+
getTotalUpdatedAppointments(from) {
|
|
604
|
+
if (!from) {
|
|
605
|
+
from = this.startDate;
|
|
606
|
+
}
|
|
607
|
+
const durationExceptions = this.durations?.filter(i => new Date(i.date) > from);
|
|
608
|
+
const updateExceptions = this.updates?.filter(i => new Date(i.from) > from || new Date(i.to) > from);
|
|
609
|
+
const instructorExceptions = this.instructors?.filter(i => new Date(i.date) > from);
|
|
610
|
+
// Collect unique appointment dates that have any exception
|
|
611
|
+
// This is to avoid counting multiple exceptions for the same appointment more than once
|
|
612
|
+
// i.e if an appointment has duration and instructor and date (updates) exceptions, it should be counted once, not three times
|
|
613
|
+
const uniqueExceptionDates = new Set();
|
|
614
|
+
durationExceptions?.forEach(i => {
|
|
615
|
+
uniqueExceptionDates.add(new Date(i.date).toISOString());
|
|
616
|
+
});
|
|
617
|
+
updateExceptions?.forEach(i => {
|
|
618
|
+
uniqueExceptionDates.add(new Date(i.to).toISOString());
|
|
619
|
+
});
|
|
620
|
+
instructorExceptions?.forEach(i => {
|
|
621
|
+
uniqueExceptionDates.add(new Date(i.date).toISOString());
|
|
622
|
+
});
|
|
623
|
+
// Count unique appointments with exceptions
|
|
624
|
+
return uniqueExceptionDates.size;
|
|
625
|
+
}
|
|
626
|
+
async getRemainingSeats(date) {
|
|
627
|
+
const lesson = await lesson_entity_1.CompanyCourseLessonEntity.findOneBy({ id: this.lessonId });
|
|
628
|
+
const course = await lesson.course;
|
|
629
|
+
if (!course.maxStudents)
|
|
630
|
+
return;
|
|
631
|
+
const takenSeats = await this.getTakenSeats(date);
|
|
632
|
+
return +course.maxStudents - (takenSeats || 0);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* This method must be called anytime when definition's endDate is changed to a value other than null/undefined
|
|
636
|
+
*
|
|
637
|
+
* Removes all 'updates', 'skippedDates', 'instructors' and 'durations' after the definition's endDate
|
|
638
|
+
*
|
|
639
|
+
* - Does not save the definition
|
|
640
|
+
*/
|
|
641
|
+
removeUpdatesAfterEndDate() {
|
|
642
|
+
this.updates = (this.updates ?? []).filter(i => new Date(i.from) <= this.endDate);
|
|
643
|
+
this.skippedDates = (this.skippedDates ?? []).filter(i => new Date(i) <= this.endDate);
|
|
644
|
+
this.instructors = (this.instructors ?? []).filter(i => new Date(i.date) <= this.endDate);
|
|
645
|
+
this.durations = (this.durations ?? []).filter(i => new Date(i.date) <= this.endDate);
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
exports.CompanyCourseScheduleEntity = CompanyCourseScheduleEntity;
|
|
650
|
+
__decorate([
|
|
651
|
+
(0, typeorm_1.Column)(),
|
|
652
|
+
__metadata("design:type", Number)
|
|
653
|
+
], CompanyCourseScheduleEntity.prototype, "lessonId", void 0);
|
|
654
|
+
__decorate([
|
|
655
|
+
(0, typeorm_1.Column)({
|
|
656
|
+
nullable: true,
|
|
657
|
+
default: null
|
|
658
|
+
}),
|
|
659
|
+
__metadata("design:type", Number)
|
|
660
|
+
], CompanyCourseScheduleEntity.prototype, "instructorId", void 0);
|
|
661
|
+
__decorate([
|
|
662
|
+
(0, typeorm_1.Column)(),
|
|
663
|
+
__metadata("design:type", Date)
|
|
664
|
+
], CompanyCourseScheduleEntity.prototype, "startDate", void 0);
|
|
665
|
+
__decorate([
|
|
666
|
+
(0, typeorm_1.Column)({
|
|
667
|
+
nullable: true,
|
|
668
|
+
default: null
|
|
669
|
+
}),
|
|
670
|
+
__metadata("design:type", Date)
|
|
671
|
+
], CompanyCourseScheduleEntity.prototype, "endDate", void 0);
|
|
672
|
+
__decorate([
|
|
673
|
+
(0, typeorm_1.Column)({ default: 60 }),
|
|
674
|
+
__metadata("design:type", Number)
|
|
675
|
+
], CompanyCourseScheduleEntity.prototype, "duration", void 0);
|
|
676
|
+
__decorate([
|
|
677
|
+
(0, typeorm_1.Column)({ default: false }),
|
|
678
|
+
__metadata("design:type", Boolean)
|
|
679
|
+
], CompanyCourseScheduleEntity.prototype, "repetition", void 0);
|
|
680
|
+
__decorate([
|
|
681
|
+
(0, typeorm_1.Column)({
|
|
682
|
+
type: 'simple-json',
|
|
683
|
+
nullable: true,
|
|
684
|
+
default: null
|
|
685
|
+
}),
|
|
686
|
+
__metadata("design:type", Array)
|
|
687
|
+
], CompanyCourseScheduleEntity.prototype, "weekdays", void 0);
|
|
688
|
+
__decorate([
|
|
689
|
+
(0, typeorm_1.Column)({
|
|
690
|
+
type: 'simple-json',
|
|
691
|
+
nullable: true,
|
|
692
|
+
default: null
|
|
693
|
+
}),
|
|
694
|
+
__metadata("design:type", Array)
|
|
695
|
+
], CompanyCourseScheduleEntity.prototype, "skippedDates", void 0);
|
|
696
|
+
__decorate([
|
|
697
|
+
(0, typeorm_1.Column)({
|
|
698
|
+
type: 'simple-json',
|
|
699
|
+
nullable: true,
|
|
700
|
+
default: null
|
|
701
|
+
}),
|
|
702
|
+
__metadata("design:type", Array)
|
|
703
|
+
], CompanyCourseScheduleEntity.prototype, "updates", void 0);
|
|
704
|
+
__decorate([
|
|
705
|
+
(0, typeorm_1.Column)({
|
|
706
|
+
type: 'simple-json',
|
|
707
|
+
nullable: true,
|
|
708
|
+
default: null
|
|
709
|
+
}),
|
|
710
|
+
__metadata("design:type", Array)
|
|
711
|
+
], CompanyCourseScheduleEntity.prototype, "instructors", void 0);
|
|
712
|
+
__decorate([
|
|
713
|
+
(0, typeorm_1.Column)({
|
|
714
|
+
type: 'simple-json',
|
|
715
|
+
nullable: true,
|
|
716
|
+
default: null
|
|
717
|
+
}),
|
|
718
|
+
__metadata("design:type", Array)
|
|
719
|
+
], CompanyCourseScheduleEntity.prototype, "durations", void 0);
|
|
720
|
+
__decorate([
|
|
721
|
+
(0, typeorm_1.ManyToOne)(() => lesson_entity_1.CompanyCourseLessonEntity, lesson => lesson.schedule),
|
|
722
|
+
__metadata("design:type", Promise)
|
|
723
|
+
], CompanyCourseScheduleEntity.prototype, "lesson", void 0);
|
|
724
|
+
__decorate([
|
|
725
|
+
(0, typeorm_1.ManyToOne)(() => instructor_entity_1.CompanyInstructorEntity, lesson => lesson.companyCourseSchedule),
|
|
726
|
+
__metadata("design:type", Promise)
|
|
727
|
+
], CompanyCourseScheduleEntity.prototype, "instructor", void 0);
|
|
728
|
+
__decorate([
|
|
729
|
+
(0, typeorm_1.OneToMany)(() => appointment_entity_1.StudentTrainingAppointmentEntity, appointment => appointment.schedule),
|
|
730
|
+
__metadata("design:type", Promise)
|
|
731
|
+
], CompanyCourseScheduleEntity.prototype, "studentSchedule", void 0);
|
|
732
|
+
exports.CompanyCourseScheduleEntity = CompanyCourseScheduleEntity = __decorate([
|
|
733
|
+
(0, typeorm_1.Entity)('companyCourseSchedules'),
|
|
734
|
+
__metadata("design:paramtypes", [Object])
|
|
735
|
+
], CompanyCourseScheduleEntity);
|
|
736
|
+
//# sourceMappingURL=appointment.entity.js.map
|