@blackcode_sa/metaestetics-api 1.12.22 → 1.12.24
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/dist/admin/index.d.mts +3 -3
- package/dist/admin/index.d.ts +3 -3
- package/dist/admin/index.js +414 -22
- package/dist/admin/index.mjs +414 -22
- package/package.json +1 -1
- package/src/admin/booking/booking.calculator.ts +16 -12
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +459 -104
|
@@ -1,22 +1,414 @@
|
|
|
1
|
-
import * as admin from
|
|
2
|
-
import { BaseMailingService, NewMailgunClient } from
|
|
3
|
-
import { Logger } from
|
|
4
|
-
import { Appointment } from
|
|
5
|
-
import { PatientProfile } from
|
|
6
|
-
import { Practitioner } from
|
|
7
|
-
import { Clinic } from
|
|
8
|
-
import { UserRole } from
|
|
9
|
-
import {
|
|
10
|
-
PractitionerProfileInfo,
|
|
11
|
-
PatientProfileInfo,
|
|
12
|
-
ClinicInfo,
|
|
13
|
-
} from "../../../types/profile";
|
|
1
|
+
import * as admin from 'firebase-admin';
|
|
2
|
+
import { BaseMailingService, NewMailgunClient } from '../base.mailing.service';
|
|
3
|
+
import { Logger } from '../../logger';
|
|
4
|
+
import { Appointment } from '../../../types/appointment';
|
|
5
|
+
import { PatientProfile } from '../../../types/patient';
|
|
6
|
+
import { Practitioner } from '../../../types/practitioner';
|
|
7
|
+
import { Clinic } from '../../../types/clinic';
|
|
8
|
+
import { UserRole } from '../../../types';
|
|
9
|
+
import { PractitionerProfileInfo, PatientProfileInfo, ClinicInfo } from '../../../types/profile';
|
|
14
10
|
|
|
15
11
|
// Simple string literals for placeholders, to be replaced by file loading later
|
|
16
|
-
const patientAppointmentConfirmedTemplate =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
const patientAppointmentConfirmedTemplate = `
|
|
13
|
+
<!DOCTYPE html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="UTF-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18
|
+
<title>Appointment Confirmed</title>
|
|
19
|
+
<style>
|
|
20
|
+
body {
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding: 0;
|
|
23
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
24
|
+
background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
|
|
25
|
+
min-height: 100vh;
|
|
26
|
+
}
|
|
27
|
+
.email-container {
|
|
28
|
+
max-width: 600px;
|
|
29
|
+
margin: 0 auto;
|
|
30
|
+
background: #ffffff;
|
|
31
|
+
border-radius: 20px;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
|
34
|
+
margin-top: 40px;
|
|
35
|
+
margin-bottom: 40px;
|
|
36
|
+
}
|
|
37
|
+
.header {
|
|
38
|
+
background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
|
|
39
|
+
padding: 40px 30px;
|
|
40
|
+
text-align: center;
|
|
41
|
+
color: white;
|
|
42
|
+
}
|
|
43
|
+
.header h1 {
|
|
44
|
+
margin: 0;
|
|
45
|
+
font-size: 28px;
|
|
46
|
+
font-weight: 300;
|
|
47
|
+
letter-spacing: 1px;
|
|
48
|
+
}
|
|
49
|
+
.header .subtitle {
|
|
50
|
+
margin: 10px 0 0 0;
|
|
51
|
+
font-size: 16px;
|
|
52
|
+
opacity: 0.9;
|
|
53
|
+
font-weight: 300;
|
|
54
|
+
}
|
|
55
|
+
.content {
|
|
56
|
+
padding: 40px 30px;
|
|
57
|
+
}
|
|
58
|
+
.greeting {
|
|
59
|
+
font-size: 18px;
|
|
60
|
+
color: #333;
|
|
61
|
+
margin-bottom: 25px;
|
|
62
|
+
font-weight: 400;
|
|
63
|
+
}
|
|
64
|
+
.appointment-card {
|
|
65
|
+
background: linear-gradient(135deg, #f8f6f5 0%, #f5f3f2 100%);
|
|
66
|
+
border-radius: 15px;
|
|
67
|
+
padding: 30px;
|
|
68
|
+
margin: 25px 0;
|
|
69
|
+
border-left: 5px solid #a48a76;
|
|
70
|
+
}
|
|
71
|
+
.appointment-title {
|
|
72
|
+
font-size: 20px;
|
|
73
|
+
color: #a48a76;
|
|
74
|
+
margin-bottom: 20px;
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
}
|
|
77
|
+
.appointment-details {
|
|
78
|
+
display: grid;
|
|
79
|
+
gap: 15px;
|
|
80
|
+
}
|
|
81
|
+
.detail-row {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
padding: 8px 0;
|
|
85
|
+
}
|
|
86
|
+
.detail-label {
|
|
87
|
+
font-weight: 600;
|
|
88
|
+
color: #555;
|
|
89
|
+
min-width: 120px;
|
|
90
|
+
font-size: 14px;
|
|
91
|
+
}
|
|
92
|
+
.detail-value {
|
|
93
|
+
color: #333;
|
|
94
|
+
font-size: 16px;
|
|
95
|
+
font-weight: 500;
|
|
96
|
+
}
|
|
97
|
+
.procedure-name {
|
|
98
|
+
color: #67574A;
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
}
|
|
101
|
+
.clinic-name {
|
|
102
|
+
color: #a48a76;
|
|
103
|
+
font-weight: 600;
|
|
104
|
+
}
|
|
105
|
+
.success-icon {
|
|
106
|
+
text-align: center;
|
|
107
|
+
margin: 20px 0;
|
|
108
|
+
font-size: 48px;
|
|
109
|
+
}
|
|
110
|
+
.footer {
|
|
111
|
+
background: #f8f9fa;
|
|
112
|
+
padding: 25px 30px;
|
|
113
|
+
text-align: center;
|
|
114
|
+
color: #666;
|
|
115
|
+
font-size: 14px;
|
|
116
|
+
border-top: 1px solid #eee;
|
|
117
|
+
}
|
|
118
|
+
.logo {
|
|
119
|
+
font-size: 24px;
|
|
120
|
+
font-weight: 700;
|
|
121
|
+
color: white;
|
|
122
|
+
margin-bottom: 5px;
|
|
123
|
+
}
|
|
124
|
+
.divider {
|
|
125
|
+
height: 2px;
|
|
126
|
+
background: linear-gradient(90deg, #a48a76, #67574A);
|
|
127
|
+
margin: 25px 0;
|
|
128
|
+
border-radius: 1px;
|
|
129
|
+
}
|
|
130
|
+
.thank-you {
|
|
131
|
+
background: linear-gradient(135deg, #f0ede8 0%, #ebe6e0 100%);
|
|
132
|
+
border-radius: 15px;
|
|
133
|
+
padding: 25px;
|
|
134
|
+
margin: 25px 0;
|
|
135
|
+
text-align: center;
|
|
136
|
+
border-left: 5px solid #4CAF50;
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
139
|
+
</head>
|
|
140
|
+
<body>
|
|
141
|
+
<div class="email-container">
|
|
142
|
+
<div class="header">
|
|
143
|
+
<div class="logo">MetaEstetics</div>
|
|
144
|
+
<h1>Appointment Confirmed</h1>
|
|
145
|
+
<div class="subtitle">Your Beauty Journey Awaits</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div class="content">
|
|
149
|
+
<div class="success-icon">✨</div>
|
|
150
|
+
|
|
151
|
+
<div class="greeting">
|
|
152
|
+
Dear <strong>{{patientName}}</strong>,
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<p style="color: #555; font-size: 16px; line-height: 1.6; margin-bottom: 25px;">
|
|
156
|
+
We're delighted to confirm your appointment! Your journey to enhanced beauty and confidence is just around the corner.
|
|
157
|
+
</p>
|
|
158
|
+
|
|
159
|
+
<div class="appointment-card">
|
|
160
|
+
<div class="appointment-title">📅 Your Appointment Details</div>
|
|
161
|
+
<div class="appointment-details">
|
|
162
|
+
<div class="detail-row">
|
|
163
|
+
<div class="detail-label">Procedure:</div>
|
|
164
|
+
<div class="detail-value procedure-name">{{procedureName}}</div>
|
|
165
|
+
</div>
|
|
166
|
+
<div class="detail-row">
|
|
167
|
+
<div class="detail-label">Date:</div>
|
|
168
|
+
<div class="detail-value">{{appointmentDate}}</div>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="detail-row">
|
|
171
|
+
<div class="detail-label">Time:</div>
|
|
172
|
+
<div class="detail-value">{{appointmentTime}}</div>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="detail-row">
|
|
175
|
+
<div class="detail-label">Practitioner:</div>
|
|
176
|
+
<div class="detail-value">{{practitionerName}}</div>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="detail-row">
|
|
179
|
+
<div class="detail-label">Location:</div>
|
|
180
|
+
<div class="detail-value clinic-name">{{clinicName}}</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="divider"></div>
|
|
186
|
+
|
|
187
|
+
<div class="thank-you">
|
|
188
|
+
<h3 style="margin: 0 0 10px 0; color: #4caf50; font-weight: 600;">Thank You for Choosing MetaEstetics!</h3>
|
|
189
|
+
<p style="margin: 0; color: #555; font-size: 14px;">
|
|
190
|
+
We look forward to providing you with exceptional care and outstanding results.
|
|
191
|
+
</p>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<p style="color: #555; font-size: 14px; line-height: 1.6; margin-top: 25px;">
|
|
195
|
+
<strong>Important:</strong> Please arrive 15 minutes early for your appointment. If you need to reschedule or have any questions, please contact us as soon as possible.
|
|
196
|
+
</p>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="footer">
|
|
200
|
+
<p style="margin: 0 0 10px 0;">
|
|
201
|
+
<strong>MetaEstetics</strong> - Premium Aesthetic Services
|
|
202
|
+
</p>
|
|
203
|
+
<p style="margin: 0; font-size: 12px; color: #999;">
|
|
204
|
+
This is an automated message. Please do not reply to this email.
|
|
205
|
+
</p>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</body>
|
|
209
|
+
</html>
|
|
210
|
+
`;
|
|
211
|
+
const clinicAppointmentRequestedTemplate = `
|
|
212
|
+
<!DOCTYPE html>
|
|
213
|
+
<html lang="en">
|
|
214
|
+
<head>
|
|
215
|
+
<meta charset="UTF-8">
|
|
216
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
217
|
+
<title>New Appointment Request</title>
|
|
218
|
+
<style>
|
|
219
|
+
body {
|
|
220
|
+
margin: 0;
|
|
221
|
+
padding: 0;
|
|
222
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
223
|
+
background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
|
|
224
|
+
min-height: 100vh;
|
|
225
|
+
}
|
|
226
|
+
.email-container {
|
|
227
|
+
max-width: 600px;
|
|
228
|
+
margin: 0 auto;
|
|
229
|
+
background: #ffffff;
|
|
230
|
+
border-radius: 20px;
|
|
231
|
+
overflow: hidden;
|
|
232
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
|
233
|
+
margin-top: 40px;
|
|
234
|
+
margin-bottom: 40px;
|
|
235
|
+
}
|
|
236
|
+
.header {
|
|
237
|
+
background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
|
|
238
|
+
padding: 40px 30px;
|
|
239
|
+
text-align: center;
|
|
240
|
+
color: white;
|
|
241
|
+
}
|
|
242
|
+
.header h1 {
|
|
243
|
+
margin: 0;
|
|
244
|
+
font-size: 28px;
|
|
245
|
+
font-weight: 300;
|
|
246
|
+
letter-spacing: 1px;
|
|
247
|
+
}
|
|
248
|
+
.header .subtitle {
|
|
249
|
+
margin: 10px 0 0 0;
|
|
250
|
+
font-size: 16px;
|
|
251
|
+
opacity: 0.9;
|
|
252
|
+
font-weight: 300;
|
|
253
|
+
}
|
|
254
|
+
.content {
|
|
255
|
+
padding: 40px 30px;
|
|
256
|
+
}
|
|
257
|
+
.greeting {
|
|
258
|
+
font-size: 18px;
|
|
259
|
+
color: #333;
|
|
260
|
+
margin-bottom: 25px;
|
|
261
|
+
font-weight: 400;
|
|
262
|
+
}
|
|
263
|
+
.appointment-card {
|
|
264
|
+
background: linear-gradient(135deg, #f8f6f5 0%, #f5f3f2 100%);
|
|
265
|
+
border-radius: 15px;
|
|
266
|
+
padding: 30px;
|
|
267
|
+
margin: 25px 0;
|
|
268
|
+
border-left: 5px solid #a48a76;
|
|
269
|
+
}
|
|
270
|
+
.appointment-title {
|
|
271
|
+
font-size: 20px;
|
|
272
|
+
color: #a48a76;
|
|
273
|
+
margin-bottom: 20px;
|
|
274
|
+
font-weight: 600;
|
|
275
|
+
}
|
|
276
|
+
.appointment-details {
|
|
277
|
+
display: grid;
|
|
278
|
+
gap: 15px;
|
|
279
|
+
}
|
|
280
|
+
.detail-row {
|
|
281
|
+
display: flex;
|
|
282
|
+
align-items: center;
|
|
283
|
+
padding: 8px 0;
|
|
284
|
+
}
|
|
285
|
+
.detail-label {
|
|
286
|
+
font-weight: 600;
|
|
287
|
+
color: #555;
|
|
288
|
+
min-width: 120px;
|
|
289
|
+
font-size: 14px;
|
|
290
|
+
}
|
|
291
|
+
.detail-value {
|
|
292
|
+
color: #333;
|
|
293
|
+
font-size: 16px;
|
|
294
|
+
font-weight: 500;
|
|
295
|
+
}
|
|
296
|
+
.patient-name {
|
|
297
|
+
color: #a48a76;
|
|
298
|
+
font-weight: 600;
|
|
299
|
+
}
|
|
300
|
+
.procedure-name {
|
|
301
|
+
color: #67574A;
|
|
302
|
+
font-weight: 600;
|
|
303
|
+
}
|
|
304
|
+
.cta-section {
|
|
305
|
+
text-align: center;
|
|
306
|
+
margin: 35px 0;
|
|
307
|
+
}
|
|
308
|
+
.cta-button {
|
|
309
|
+
display: inline-block;
|
|
310
|
+
background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
|
|
311
|
+
color: white;
|
|
312
|
+
text-decoration: none;
|
|
313
|
+
padding: 15px 35px;
|
|
314
|
+
border-radius: 50px;
|
|
315
|
+
font-weight: 600;
|
|
316
|
+
font-size: 16px;
|
|
317
|
+
letter-spacing: 0.5px;
|
|
318
|
+
transition: transform 0.3s ease;
|
|
319
|
+
box-shadow: 0 8px 25px rgba(164, 138, 118, 0.3);
|
|
320
|
+
}
|
|
321
|
+
.cta-button:hover {
|
|
322
|
+
transform: translateY(-2px);
|
|
323
|
+
}
|
|
324
|
+
.footer {
|
|
325
|
+
background: #f8f9fa;
|
|
326
|
+
padding: 25px 30px;
|
|
327
|
+
text-align: center;
|
|
328
|
+
color: #666;
|
|
329
|
+
font-size: 14px;
|
|
330
|
+
border-top: 1px solid #eee;
|
|
331
|
+
}
|
|
332
|
+
.logo {
|
|
333
|
+
font-size: 24px;
|
|
334
|
+
font-weight: 700;
|
|
335
|
+
color: white;
|
|
336
|
+
margin-bottom: 5px;
|
|
337
|
+
}
|
|
338
|
+
.divider {
|
|
339
|
+
height: 2px;
|
|
340
|
+
background: linear-gradient(90deg, #a48a76, #67574A);
|
|
341
|
+
margin: 25px 0;
|
|
342
|
+
border-radius: 1px;
|
|
343
|
+
}
|
|
344
|
+
</style>
|
|
345
|
+
</head>
|
|
346
|
+
<body>
|
|
347
|
+
<div class="email-container">
|
|
348
|
+
<div class="header">
|
|
349
|
+
<div class="logo">MetaEstetics</div>
|
|
350
|
+
<h1>New Appointment Request</h1>
|
|
351
|
+
<div class="subtitle">Requires Your Attention</div>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<div class="content">
|
|
355
|
+
<div class="greeting">
|
|
356
|
+
Dear <strong>{{clinicName}}</strong> Team,
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<p style="color: #555; font-size: 16px; line-height: 1.6; margin-bottom: 25px;">
|
|
360
|
+
We hope this message finds you well. You have received a new appointment request that requires your review and confirmation.
|
|
361
|
+
</p>
|
|
362
|
+
|
|
363
|
+
<div class="appointment-card">
|
|
364
|
+
<div class="appointment-title">📅 Appointment Details</div>
|
|
365
|
+
<div class="appointment-details">
|
|
366
|
+
<div class="detail-row">
|
|
367
|
+
<div class="detail-label">Patient:</div>
|
|
368
|
+
<div class="detail-value patient-name">{{patientName}}</div>
|
|
369
|
+
</div>
|
|
370
|
+
<div class="detail-row">
|
|
371
|
+
<div class="detail-label">Procedure:</div>
|
|
372
|
+
<div class="detail-value procedure-name">{{procedureName}}</div>
|
|
373
|
+
</div>
|
|
374
|
+
<div class="detail-row">
|
|
375
|
+
<div class="detail-label">Date:</div>
|
|
376
|
+
<div class="detail-value">{{appointmentDate}}</div>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="detail-row">
|
|
379
|
+
<div class="detail-label">Time:</div>
|
|
380
|
+
<div class="detail-value">{{appointmentTime}}</div>
|
|
381
|
+
</div>
|
|
382
|
+
<div class="detail-row">
|
|
383
|
+
<div class="detail-label">Practitioner:</div>
|
|
384
|
+
<div class="detail-value">{{practitionerName}}</div>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
<div class="divider"></div>
|
|
390
|
+
|
|
391
|
+
<p style="color: #555; font-size: 16px; line-height: 1.6; margin-bottom: 30px;">
|
|
392
|
+
Please review this appointment request and confirm availability in your admin panel. The patient is waiting for your confirmation to finalize their booking.
|
|
393
|
+
</p>
|
|
394
|
+
|
|
395
|
+
<div class="cta-section">
|
|
396
|
+
<a href="#" class="cta-button">Review & Confirm Appointment</a>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<div class="footer">
|
|
401
|
+
<p style="margin: 0 0 10px 0;">
|
|
402
|
+
<strong>MetaEstetics</strong> - Premium Aesthetic Services
|
|
403
|
+
</p>
|
|
404
|
+
<p style="margin: 0; font-size: 12px; color: #999;">
|
|
405
|
+
This is an automated message. Please do not reply to this email.
|
|
406
|
+
</p>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
</body>
|
|
410
|
+
</html>
|
|
411
|
+
`;
|
|
20
412
|
|
|
21
413
|
// --- Interface Definitions for Email Data ---
|
|
22
414
|
|
|
@@ -29,26 +421,22 @@ export interface AppointmentEmailDataBase {
|
|
|
29
421
|
};
|
|
30
422
|
}
|
|
31
423
|
|
|
32
|
-
export interface AppointmentConfirmationEmailData
|
|
33
|
-
extends AppointmentEmailDataBase {
|
|
424
|
+
export interface AppointmentConfirmationEmailData extends AppointmentEmailDataBase {
|
|
34
425
|
recipientProfile: PatientProfileInfo | PractitionerProfileInfo;
|
|
35
|
-
recipientRole:
|
|
426
|
+
recipientRole: 'patient' | 'practitioner';
|
|
36
427
|
}
|
|
37
428
|
|
|
38
|
-
export interface AppointmentRequestedEmailData
|
|
39
|
-
extends AppointmentEmailDataBase {
|
|
429
|
+
export interface AppointmentRequestedEmailData extends AppointmentEmailDataBase {
|
|
40
430
|
clinicProfile: ClinicInfo;
|
|
41
431
|
}
|
|
42
432
|
|
|
43
|
-
export interface AppointmentCancellationEmailData
|
|
44
|
-
extends AppointmentEmailDataBase {
|
|
433
|
+
export interface AppointmentCancellationEmailData extends AppointmentEmailDataBase {
|
|
45
434
|
recipientProfile: PatientProfileInfo | PractitionerProfileInfo;
|
|
46
|
-
recipientRole:
|
|
435
|
+
recipientRole: 'patient' | 'practitioner';
|
|
47
436
|
cancellationReason?: string;
|
|
48
437
|
}
|
|
49
438
|
|
|
50
|
-
export interface AppointmentRescheduledProposalEmailData
|
|
51
|
-
extends AppointmentEmailDataBase {
|
|
439
|
+
export interface AppointmentRescheduledProposalEmailData extends AppointmentEmailDataBase {
|
|
52
440
|
patientProfile: PatientProfileInfo;
|
|
53
441
|
previousStartTime: admin.firestore.Timestamp;
|
|
54
442
|
previousEndTime: admin.firestore.Timestamp;
|
|
@@ -61,7 +449,7 @@ export interface ReviewRequestEmailData extends AppointmentEmailDataBase {
|
|
|
61
449
|
|
|
62
450
|
export interface ReviewAddedEmailData extends AppointmentEmailDataBase {
|
|
63
451
|
recipientProfile: PractitionerProfileInfo | ClinicInfo;
|
|
64
|
-
recipientRole:
|
|
452
|
+
recipientRole: 'practitioner' | 'clinic';
|
|
65
453
|
reviewerName: string;
|
|
66
454
|
reviewRating: number;
|
|
67
455
|
reviewComment?: string;
|
|
@@ -71,59 +459,43 @@ export interface ReviewAddedEmailData extends AppointmentEmailDataBase {
|
|
|
71
459
|
* Service for sending appointment-related emails.
|
|
72
460
|
*/
|
|
73
461
|
export class AppointmentMailingService extends BaseMailingService {
|
|
74
|
-
private readonly DEFAULT_MAILGUN_DOMAIN =
|
|
462
|
+
private readonly DEFAULT_MAILGUN_DOMAIN = 'mg.metaesthetics.net';
|
|
75
463
|
|
|
76
|
-
constructor(
|
|
77
|
-
firestore: admin.firestore.Firestore,
|
|
78
|
-
mailgunClient: NewMailgunClient
|
|
79
|
-
) {
|
|
464
|
+
constructor(firestore: admin.firestore.Firestore, mailgunClient: NewMailgunClient) {
|
|
80
465
|
super(firestore, mailgunClient);
|
|
81
|
-
Logger.info(
|
|
466
|
+
Logger.info('[AppointmentMailingService] Initialized.');
|
|
82
467
|
}
|
|
83
468
|
|
|
84
|
-
async sendAppointmentConfirmedEmail(
|
|
85
|
-
data: AppointmentConfirmationEmailData
|
|
86
|
-
): Promise<any> {
|
|
469
|
+
async sendAppointmentConfirmedEmail(data: AppointmentConfirmationEmailData): Promise<any> {
|
|
87
470
|
Logger.info(
|
|
88
|
-
`[AppointmentMailingService] Preparing to send appointment confirmation email to ${data.recipientRole}: ${data.recipientProfile.id}
|
|
471
|
+
`[AppointmentMailingService] Preparing to send appointment confirmation email to ${data.recipientRole}: ${data.recipientProfile.id}`,
|
|
89
472
|
);
|
|
90
473
|
|
|
91
474
|
const recipientEmail = data.recipientProfile.email;
|
|
92
475
|
|
|
93
476
|
if (!recipientEmail) {
|
|
94
|
-
Logger.error(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
);
|
|
98
|
-
throw new Error(
|
|
477
|
+
Logger.error('[AppointmentMailingService] Recipient email not found for confirmation.', {
|
|
478
|
+
recipientId: data.recipientProfile.id,
|
|
479
|
+
role: data.recipientRole,
|
|
480
|
+
});
|
|
481
|
+
throw new Error('Recipient email address is missing.');
|
|
99
482
|
}
|
|
100
483
|
|
|
101
484
|
const templateVariables = {
|
|
102
485
|
patientName: data.appointment.patientInfo.fullName,
|
|
103
486
|
procedureName: data.appointment.procedureInfo.name,
|
|
104
|
-
appointmentDate: data.appointment.appointmentStartTime
|
|
105
|
-
|
|
106
|
-
.toLocaleDateString(),
|
|
107
|
-
appointmentTime: data.appointment.appointmentStartTime
|
|
108
|
-
.toDate()
|
|
109
|
-
.toLocaleTimeString(),
|
|
487
|
+
appointmentDate: data.appointment.appointmentStartTime.toDate().toLocaleDateString(),
|
|
488
|
+
appointmentTime: data.appointment.appointmentStartTime.toDate().toLocaleTimeString(),
|
|
110
489
|
practitionerName: data.appointment.practitionerInfo.name,
|
|
111
490
|
clinicName: data.appointment.clinicInfo.name,
|
|
112
491
|
};
|
|
113
492
|
|
|
114
|
-
const html = this.renderTemplate(
|
|
115
|
-
|
|
116
|
-
templateVariables
|
|
117
|
-
);
|
|
118
|
-
const subject =
|
|
119
|
-
data.options?.customSubject || "Your Appointment is Confirmed!";
|
|
493
|
+
const html = this.renderTemplate(patientAppointmentConfirmedTemplate, templateVariables);
|
|
494
|
+
const subject = data.options?.customSubject || 'Your Appointment is Confirmed!';
|
|
120
495
|
const fromAddress =
|
|
121
496
|
data.options?.fromAddress ||
|
|
122
|
-
`MetaEstetics <no-reply@${
|
|
123
|
-
|
|
124
|
-
}>`;
|
|
125
|
-
const domainToSendFrom =
|
|
126
|
-
data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN;
|
|
497
|
+
`MetaEstetics <no-reply@${data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN}>`;
|
|
498
|
+
const domainToSendFrom = data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN;
|
|
127
499
|
|
|
128
500
|
const mailgunSendData = {
|
|
129
501
|
to: recipientEmail,
|
|
@@ -135,67 +507,53 @@ export class AppointmentMailingService extends BaseMailingService {
|
|
|
135
507
|
try {
|
|
136
508
|
const result = await this.sendEmail(domainToSendFrom, mailgunSendData);
|
|
137
509
|
await this.logEmailAttempt(
|
|
138
|
-
{ to: recipientEmail, subject, templateName:
|
|
139
|
-
true
|
|
510
|
+
{ to: recipientEmail, subject, templateName: 'appointment_confirmed' },
|
|
511
|
+
true,
|
|
140
512
|
);
|
|
141
513
|
return result;
|
|
142
514
|
} catch (error) {
|
|
143
515
|
await this.logEmailAttempt(
|
|
144
516
|
{
|
|
145
517
|
to: recipientEmail,
|
|
146
|
-
subject:
|
|
147
|
-
|
|
148
|
-
templateName: "appointment_confirmed",
|
|
518
|
+
subject: data.options?.customSubject || 'Your Appointment is Confirmed!',
|
|
519
|
+
templateName: 'appointment_confirmed',
|
|
149
520
|
},
|
|
150
521
|
false,
|
|
151
|
-
error
|
|
522
|
+
error,
|
|
152
523
|
);
|
|
153
524
|
throw error;
|
|
154
525
|
}
|
|
155
526
|
}
|
|
156
527
|
|
|
157
|
-
async sendAppointmentRequestedEmailToClinic(
|
|
158
|
-
data: AppointmentRequestedEmailData
|
|
159
|
-
): Promise<any> {
|
|
528
|
+
async sendAppointmentRequestedEmailToClinic(data: AppointmentRequestedEmailData): Promise<any> {
|
|
160
529
|
Logger.info(
|
|
161
|
-
`[AppointmentMailingService] Preparing to send appointment requested email to clinic: ${data.clinicProfile.id}
|
|
530
|
+
`[AppointmentMailingService] Preparing to send appointment requested email to clinic: ${data.clinicProfile.id}`,
|
|
162
531
|
);
|
|
163
532
|
|
|
164
533
|
const clinicEmail = data.clinicProfile.contactInfo?.email;
|
|
165
534
|
if (!clinicEmail) {
|
|
166
535
|
Logger.error(
|
|
167
|
-
|
|
168
|
-
{ clinicId: data.clinicProfile.id }
|
|
536
|
+
'[AppointmentMailingService] Clinic contact email not found for request notification.',
|
|
537
|
+
{ clinicId: data.clinicProfile.id },
|
|
169
538
|
);
|
|
170
|
-
throw new Error(
|
|
539
|
+
throw new Error('Clinic contact email address is missing.');
|
|
171
540
|
}
|
|
172
541
|
|
|
173
542
|
const templateVariables = {
|
|
174
543
|
clinicName: data.clinicProfile.name,
|
|
175
544
|
patientName: data.appointment.patientInfo.fullName,
|
|
176
545
|
procedureName: data.appointment.procedureInfo.name,
|
|
177
|
-
appointmentDate: data.appointment.appointmentStartTime
|
|
178
|
-
|
|
179
|
-
.toLocaleDateString(),
|
|
180
|
-
appointmentTime: data.appointment.appointmentStartTime
|
|
181
|
-
.toDate()
|
|
182
|
-
.toLocaleTimeString(),
|
|
546
|
+
appointmentDate: data.appointment.appointmentStartTime.toDate().toLocaleDateString(),
|
|
547
|
+
appointmentTime: data.appointment.appointmentStartTime.toDate().toLocaleTimeString(),
|
|
183
548
|
practitionerName: data.appointment.practitionerInfo.name,
|
|
184
549
|
};
|
|
185
550
|
|
|
186
|
-
const html = this.renderTemplate(
|
|
187
|
-
|
|
188
|
-
templateVariables
|
|
189
|
-
);
|
|
190
|
-
const subject =
|
|
191
|
-
data.options?.customSubject || "New Appointment Request Received";
|
|
551
|
+
const html = this.renderTemplate(clinicAppointmentRequestedTemplate, templateVariables);
|
|
552
|
+
const subject = data.options?.customSubject || 'New Appointment Request Received';
|
|
192
553
|
const fromAddress =
|
|
193
554
|
data.options?.fromAddress ||
|
|
194
|
-
`MetaEstetics <no-reply@${
|
|
195
|
-
|
|
196
|
-
}>`;
|
|
197
|
-
const domainToSendFrom =
|
|
198
|
-
data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN;
|
|
555
|
+
`MetaEstetics <no-reply@${data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN}>`;
|
|
556
|
+
const domainToSendFrom = data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN;
|
|
199
557
|
|
|
200
558
|
const mailgunSendData = {
|
|
201
559
|
to: clinicEmail,
|
|
@@ -210,54 +568,51 @@ export class AppointmentMailingService extends BaseMailingService {
|
|
|
210
568
|
{
|
|
211
569
|
to: clinicEmail,
|
|
212
570
|
subject,
|
|
213
|
-
templateName:
|
|
571
|
+
templateName: 'appointment_requested_clinic',
|
|
214
572
|
},
|
|
215
|
-
true
|
|
573
|
+
true,
|
|
216
574
|
);
|
|
217
575
|
return result;
|
|
218
576
|
} catch (error) {
|
|
219
577
|
await this.logEmailAttempt(
|
|
220
578
|
{
|
|
221
579
|
to: clinicEmail,
|
|
222
|
-
subject:
|
|
223
|
-
|
|
224
|
-
templateName: "appointment_requested_clinic",
|
|
580
|
+
subject: data.options?.customSubject || 'New Appointment Request Received',
|
|
581
|
+
templateName: 'appointment_requested_clinic',
|
|
225
582
|
},
|
|
226
583
|
false,
|
|
227
|
-
error
|
|
584
|
+
error,
|
|
228
585
|
);
|
|
229
586
|
throw error;
|
|
230
587
|
}
|
|
231
588
|
}
|
|
232
589
|
|
|
233
|
-
async sendAppointmentCancelledEmail(
|
|
234
|
-
data: AppointmentCancellationEmailData
|
|
235
|
-
): Promise<any> {
|
|
590
|
+
async sendAppointmentCancelledEmail(data: AppointmentCancellationEmailData): Promise<any> {
|
|
236
591
|
Logger.info(
|
|
237
|
-
`[AppointmentMailingService] Placeholder for sendAppointmentCancelledEmail for ${data.recipientRole}: ${data.recipientProfile.id}
|
|
592
|
+
`[AppointmentMailingService] Placeholder for sendAppointmentCancelledEmail for ${data.recipientRole}: ${data.recipientProfile.id}`,
|
|
238
593
|
);
|
|
239
594
|
return Promise.resolve();
|
|
240
595
|
}
|
|
241
596
|
|
|
242
597
|
async sendAppointmentRescheduledProposalEmail(
|
|
243
|
-
data: AppointmentRescheduledProposalEmailData
|
|
598
|
+
data: AppointmentRescheduledProposalEmailData,
|
|
244
599
|
): Promise<any> {
|
|
245
600
|
Logger.info(
|
|
246
|
-
`[AppointmentMailingService] Placeholder for sendAppointmentRescheduledProposalEmail to patient: ${data.patientProfile.id}
|
|
601
|
+
`[AppointmentMailingService] Placeholder for sendAppointmentRescheduledProposalEmail to patient: ${data.patientProfile.id}`,
|
|
247
602
|
);
|
|
248
603
|
return Promise.resolve();
|
|
249
604
|
}
|
|
250
605
|
|
|
251
606
|
async sendReviewRequestEmail(data: ReviewRequestEmailData): Promise<any> {
|
|
252
607
|
Logger.info(
|
|
253
|
-
`[AppointmentMailingService] Placeholder for sendReviewRequestEmail to patient: ${data.patientProfile.id}
|
|
608
|
+
`[AppointmentMailingService] Placeholder for sendReviewRequestEmail to patient: ${data.patientProfile.id}`,
|
|
254
609
|
);
|
|
255
610
|
return Promise.resolve();
|
|
256
611
|
}
|
|
257
612
|
|
|
258
613
|
async sendReviewAddedEmail(data: ReviewAddedEmailData): Promise<any> {
|
|
259
614
|
Logger.info(
|
|
260
|
-
`[AppointmentMailingService] Placeholder for sendReviewAddedEmail to ${data.recipientRole}: ${data.recipientProfile.id}
|
|
615
|
+
`[AppointmentMailingService] Placeholder for sendReviewAddedEmail to ${data.recipientRole}: ${data.recipientProfile.id}`,
|
|
261
616
|
);
|
|
262
617
|
return Promise.resolve();
|
|
263
618
|
}
|