@phenyxhealth/sdk 1.0.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/package.json +18 -0
- package/src/PhenyxApi.js +483 -0
- package/src/controllers/doctors.js +24 -0
- package/src/controllers/global.js +83 -0
- package/src/controllers/linacs.js +32 -0
- package/src/index.js +6 -0
- package/src/utils/colors.js +42 -0
- package/src/utils/dates.js +21 -0
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phenyxhealth/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "PhenyxHealth API client SDK for interacting with PhenyxHealth instances.",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"author": "Diego M. Béjar",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"uuid": "13.0.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/PhenyxApi.js
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { v4 } from 'uuid';
|
|
2
|
+
import { today } from './utils/dates.js';
|
|
3
|
+
|
|
4
|
+
class PhenyxApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.apiToken = null;
|
|
7
|
+
this.apiHost = null;
|
|
8
|
+
this.user = null;
|
|
9
|
+
this.config = {};
|
|
10
|
+
this.globalInfo = null;
|
|
11
|
+
this.activeTables = null;
|
|
12
|
+
this.doctors = [];
|
|
13
|
+
this.defaultDuration = {
|
|
14
|
+
firstSession: '30',
|
|
15
|
+
regularSessions: '15',
|
|
16
|
+
};
|
|
17
|
+
this.defaultCompatibleLinacs = [];
|
|
18
|
+
this.oars = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
peerReviewedStatus = {
|
|
22
|
+
'Discussion without changes': 'Discussion without changes',
|
|
23
|
+
'No changes': 'No changes',
|
|
24
|
+
'Discusión sin cambios': 'Discussion without changes',
|
|
25
|
+
'No cambios': 'No changes',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
globalTypes = {
|
|
29
|
+
humanResources: {
|
|
30
|
+
type: 'Global',
|
|
31
|
+
name: 'Human Resources Types',
|
|
32
|
+
values: {
|
|
33
|
+
RadOnc: 'RadOnc',
|
|
34
|
+
MedPhys: 'MedPhys',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
sendingDepartment: {
|
|
38
|
+
type: 'Global',
|
|
39
|
+
name: 'Sending Department',
|
|
40
|
+
},
|
|
41
|
+
cei9: {
|
|
42
|
+
type: 'Global',
|
|
43
|
+
name: 'CEI9',
|
|
44
|
+
},
|
|
45
|
+
treatmentClass: {
|
|
46
|
+
type: 'Global',
|
|
47
|
+
name: 'Treatment Class',
|
|
48
|
+
},
|
|
49
|
+
treatmentSubClass: {
|
|
50
|
+
type: 'Global',
|
|
51
|
+
name: 'Treatment SubClass',
|
|
52
|
+
},
|
|
53
|
+
treatmentTechnique: {
|
|
54
|
+
type: 'Global',
|
|
55
|
+
name: 'Treatment Technique',
|
|
56
|
+
},
|
|
57
|
+
treatmentType: {
|
|
58
|
+
type: 'Global',
|
|
59
|
+
name: 'Treatment Type',
|
|
60
|
+
},
|
|
61
|
+
treatmentGoal: {
|
|
62
|
+
type: 'Global',
|
|
63
|
+
name: 'Treatment Goal',
|
|
64
|
+
},
|
|
65
|
+
treatmentParticles: {
|
|
66
|
+
type: 'Global',
|
|
67
|
+
name: 'Treatment Particle',
|
|
68
|
+
},
|
|
69
|
+
noTreatmentReasons: {
|
|
70
|
+
type: 'Global',
|
|
71
|
+
name: 'No Treatment Reasons',
|
|
72
|
+
},
|
|
73
|
+
ctSimProtocols: {
|
|
74
|
+
type: 'Global',
|
|
75
|
+
name: 'CT Sim Protocols',
|
|
76
|
+
},
|
|
77
|
+
linacsAllocationCriteria: {
|
|
78
|
+
type: 'Global',
|
|
79
|
+
name: 'LINACs Allocation Criteria',
|
|
80
|
+
values: {
|
|
81
|
+
default: 'Default',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
sessionDurationCriteria: {
|
|
85
|
+
type: 'Global',
|
|
86
|
+
name: 'Session Duration Criteria',
|
|
87
|
+
values: {
|
|
88
|
+
default: 'Default',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
contouringTools: {
|
|
92
|
+
type: 'Global',
|
|
93
|
+
name: 'Contouring Tools',
|
|
94
|
+
},
|
|
95
|
+
fractionationTypes: {
|
|
96
|
+
type: 'Global',
|
|
97
|
+
name: 'Fractionation Types',
|
|
98
|
+
},
|
|
99
|
+
dosimetryCalculationMethods: {
|
|
100
|
+
type: 'Global',
|
|
101
|
+
name: 'Dosimetry Calculation Methods',
|
|
102
|
+
},
|
|
103
|
+
dosimetrySecondaryCalculationMethods: {
|
|
104
|
+
type: 'Global',
|
|
105
|
+
name: 'Dosimetry 2nd Calculation Methods',
|
|
106
|
+
},
|
|
107
|
+
tps: {
|
|
108
|
+
type: 'Global',
|
|
109
|
+
name: 'TPS available',
|
|
110
|
+
},
|
|
111
|
+
treatmentVerificationProtocols: {
|
|
112
|
+
type: 'Global',
|
|
113
|
+
name: 'Treatment Verification Protocols',
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
apiCall = async ({ method, url, data }) => {
|
|
118
|
+
const fullUrl = `${this.apiHost}${url}`;
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const params = {
|
|
122
|
+
method,
|
|
123
|
+
headers: {
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
...(this.apiToken ? { Authorization: `Bearer ${this.apiToken}` } : {})
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (data && method.toUpperCase() !== 'GET') params.body = JSON.stringify(data);
|
|
130
|
+
|
|
131
|
+
if (this.config.showLogs) console.log(`Calling to: ${method.toUpperCase()} ${fullUrl}\n`, params);
|
|
132
|
+
|
|
133
|
+
const response = await fetch(fullUrl, params);
|
|
134
|
+
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
const errorText = await response.text();
|
|
137
|
+
console.error(`Error: ${response.status} - ${errorText}`);
|
|
138
|
+
console.log(`Calling to: ${method.toUpperCase()} ${fullUrl}\n`, params);
|
|
139
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const output = await response.json();
|
|
143
|
+
|
|
144
|
+
return output;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('Request failed', error);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
showLogs = (showLogs) => this.config.showLogs = showLogs;
|
|
152
|
+
|
|
153
|
+
login = async ({ apiHost, user, password }) => {
|
|
154
|
+
if (!apiHost) {
|
|
155
|
+
throw new Error('Var apiHost is required');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!user) {
|
|
159
|
+
throw new Error('Var user is required');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!password) {
|
|
163
|
+
throw new Error('Var password is required');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.apiHost = apiHost.endsWith('/') ? apiHost : `${apiHost}/`;
|
|
167
|
+
|
|
168
|
+
const response = await this.apiCall({
|
|
169
|
+
method: 'POST',
|
|
170
|
+
url: `users/login`,
|
|
171
|
+
data: { user, password },
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
this.apiToken = response.token;
|
|
175
|
+
this.user = response.user;
|
|
176
|
+
|
|
177
|
+
await this.retrieveGlobalInfo();
|
|
178
|
+
|
|
179
|
+
await this.retrieveActiveTables();
|
|
180
|
+
|
|
181
|
+
await this.retrieveDefaults();
|
|
182
|
+
|
|
183
|
+
await this.retrieveOars();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
getToken = () => {
|
|
187
|
+
return this.apiToken;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
retrieveGlobalInfo = async () => {
|
|
191
|
+
const globalInfo = await this.apiCall({
|
|
192
|
+
method: 'GET',
|
|
193
|
+
url: `global-info`,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
this.globalInfo = globalInfo;
|
|
197
|
+
|
|
198
|
+
return globalInfo;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
retrieveOars = async () => {
|
|
202
|
+
const response = await this.apiCall({
|
|
203
|
+
method: 'GET',
|
|
204
|
+
url: `tables/Oar`,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
this.oars = response;
|
|
208
|
+
|
|
209
|
+
return response;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
retrieveActiveTables = async () => {
|
|
213
|
+
const activeTables = await this.apiCall({
|
|
214
|
+
method: 'GET',
|
|
215
|
+
url: `tables/actives`,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
this.activeTables = activeTables;
|
|
219
|
+
|
|
220
|
+
return activeTables;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
retrieveDefaults = async () => {
|
|
224
|
+
try {
|
|
225
|
+
this.defaultCompatibleLinacs = this
|
|
226
|
+
.activeTables
|
|
227
|
+
.find(t => t.type.name === 'LINACs Allocation Table')
|
|
228
|
+
.data
|
|
229
|
+
.items
|
|
230
|
+
.find(d => d.criteriaElementName === 'Default')
|
|
231
|
+
.resources
|
|
232
|
+
.filter(r => !!r.isActive)
|
|
233
|
+
.map(r => ({
|
|
234
|
+
id: r.resourceId,
|
|
235
|
+
name: r.resourceName,
|
|
236
|
+
}));
|
|
237
|
+
} catch (err) {
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
this.defaultDuration = this
|
|
242
|
+
.activeTables
|
|
243
|
+
.find(t => t.type.name === 'Session Duration Table')
|
|
244
|
+
.data
|
|
245
|
+
.items
|
|
246
|
+
.find(d => d.sessionDurationCriteria === 'Default');
|
|
247
|
+
} catch (err) {
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
defaultDuration: this.defaultDuration,
|
|
252
|
+
defaultCompatibleLinacs: this.defaultCompatibleLinacs,
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
updateGlobalInfo = async (globalTypeId, data) => {
|
|
257
|
+
await this.apiCall({
|
|
258
|
+
method: 'PUT',
|
|
259
|
+
url: `global-info/${globalTypeId}`,
|
|
260
|
+
data,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await this.retrieveGlobalInfo();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getGlobalInfo = (globalType) => {
|
|
267
|
+
return this.globalInfo
|
|
268
|
+
?.find(item => item.typeName === globalType.type)
|
|
269
|
+
?.globalInfos
|
|
270
|
+
?.find(item => item.name === globalType.name);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
getGlobalInfoElement = (globalType, elementName) => {
|
|
274
|
+
return this.getGlobalInfo(globalType)
|
|
275
|
+
?.elements
|
|
276
|
+
?.find(item => item.name.toLowerCase() === elementName.toLowerCase());
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
getGlobalInfoElementId = (globalType, elementName) => {
|
|
280
|
+
if (!elementName) return null;
|
|
281
|
+
|
|
282
|
+
return this.getGlobalInfoElement(globalType, elementName)?.id;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
getHumanResources = async () => {
|
|
286
|
+
const response= await this.apiCall({
|
|
287
|
+
method: 'GET',
|
|
288
|
+
url: `human-resources`,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
this.doctors = response;
|
|
292
|
+
|
|
293
|
+
return response;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
createHumanResource = async ({ name, typeId, agenda = [] }) => {
|
|
297
|
+
return this.apiCall({
|
|
298
|
+
method: 'POST',
|
|
299
|
+
url: `human-resources`,
|
|
300
|
+
data: {
|
|
301
|
+
name,
|
|
302
|
+
typeId,
|
|
303
|
+
agenda,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
getResourcesTypes = async () => {
|
|
309
|
+
return this.apiCall({
|
|
310
|
+
method: 'GET',
|
|
311
|
+
url: `resource-types`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
getResources = async () => {
|
|
316
|
+
const response = await this.apiCall({
|
|
317
|
+
method: 'GET',
|
|
318
|
+
url: `resources`,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return response;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
createResource = async ({ name, author = 'ADMIN', typeId, agenda = [] }) => {
|
|
325
|
+
return this.apiCall({
|
|
326
|
+
method: 'POST',
|
|
327
|
+
url: `resources`,
|
|
328
|
+
data: {
|
|
329
|
+
name,
|
|
330
|
+
author,
|
|
331
|
+
typeId,
|
|
332
|
+
agenda,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
getSendingDepartmentIdByName = (name) => {
|
|
338
|
+
const sendingDepartment = this.getGlobalInfoElement(this.globalTypes.sendingDepartment, name);
|
|
339
|
+
if (!sendingDepartment) {
|
|
340
|
+
throw new Error(`Sending Department ${name} not found`);
|
|
341
|
+
}
|
|
342
|
+
return sendingDepartment.id;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
getCei9IdByName = (name) => {
|
|
346
|
+
const cei9 = this.getGlobalInfoElement(this.globalTypes.cei9, name);
|
|
347
|
+
if (!cei9) {
|
|
348
|
+
throw new Error(`CEI9 ${name} not found`);
|
|
349
|
+
}
|
|
350
|
+
return cei9.id;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
createPatient = async (data) => {
|
|
354
|
+
const response = await this.apiCall({
|
|
355
|
+
method: 'POST',
|
|
356
|
+
url: `patients`,
|
|
357
|
+
data,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return response.id;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
getPatients = async () => {
|
|
364
|
+
return this.apiCall({
|
|
365
|
+
method: 'GET',
|
|
366
|
+
url: `patients`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
getPatient = async (id) => {
|
|
371
|
+
return this.apiCall({
|
|
372
|
+
method: 'GET',
|
|
373
|
+
url: `patients/${id}/clinical-data`,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
deletePatient = async (id) => {
|
|
378
|
+
return this.apiCall({
|
|
379
|
+
method: 'DELETE',
|
|
380
|
+
url: `patients/${id}`,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
createPatientStateChanges = ({ formDataId, date = today(), humanResourceId = null, nextStateCode }) => {
|
|
385
|
+
return this.apiCall({
|
|
386
|
+
method: 'POST',
|
|
387
|
+
url: `patients/state-changes`,
|
|
388
|
+
data: {
|
|
389
|
+
date,
|
|
390
|
+
formDataId,
|
|
391
|
+
humanResourceId,
|
|
392
|
+
nextStateCode,
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
getRewiewedStatus = (status) => {
|
|
398
|
+
const saveStatus = this.peerReviewedStatus[status];
|
|
399
|
+
if (!saveStatus) return [];
|
|
400
|
+
|
|
401
|
+
return [{
|
|
402
|
+
id: v4(),
|
|
403
|
+
name: saveStatus,
|
|
404
|
+
coments: '',
|
|
405
|
+
date: new Date().toLocaleDateString(),
|
|
406
|
+
hour: new Date().toLocaleTimeString(),
|
|
407
|
+
}];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
allStatus = {
|
|
411
|
+
NOTSTARTED: 'NOT_STARTED',
|
|
412
|
+
COMPLETED: 'COMPLETED',
|
|
413
|
+
INPROGRESS: 'IN_PROGRESS',
|
|
414
|
+
ONREQUEST: 'ON_REQUEST',
|
|
415
|
+
READYTOSTART: 'READY_TO_START',
|
|
416
|
+
DONE: 'DONE',
|
|
417
|
+
REVIEWED: 'REVIEWED',
|
|
418
|
+
VALIDATED: 'VALIDATED',
|
|
419
|
+
PEERREVIEWING: 'PEER_REVIEWING',
|
|
420
|
+
PEERREVIEWED: 'PEER_REVIEWED',
|
|
421
|
+
DONEMAIN: 'DONE',
|
|
422
|
+
DONESECONDCALCULATION: 'DONE_SECOND',
|
|
423
|
+
STARTPROPOSED: 'START_PROPOSED',
|
|
424
|
+
STARTCONFIRMED: 'START_CONFIRMED',
|
|
425
|
+
CANCELED: 'CANCELED',
|
|
426
|
+
CANCELLED: 'CANCELED',
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
getQaStatus = (section) => {
|
|
430
|
+
const currentStatus = section.qa.currentStatus || null;
|
|
431
|
+
return currentStatus
|
|
432
|
+
? this.allStatus[currentStatus.toUpperCase().replaceAll(' ', '').replaceAll('_', '')]
|
|
433
|
+
: null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
getDoctorIdByName = (name) => {
|
|
437
|
+
const doc = this.doctors.find(d => d.name === name);
|
|
438
|
+
return doc ? doc.id : null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
getDoctorTypeIdByName = (name) => {
|
|
442
|
+
const doc = this.doctors.find(d => d.name === name);
|
|
443
|
+
return doc ? doc.typeId : null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
getDoctorTypeNameByName = (name) => {
|
|
447
|
+
const doc = this.doctors.find(d => d.name === name);
|
|
448
|
+
return doc ? doc.typeName : null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
getSortedQa = (qa) => {
|
|
452
|
+
const qaArray = [];
|
|
453
|
+
for (const qaKey of Object.keys(qa)) {
|
|
454
|
+
if (qa[qaKey].date) qaArray.push({
|
|
455
|
+
status: this.allStatus[qaKey.toUpperCase()],
|
|
456
|
+
...qa[qaKey]
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
const sortedQa = qaArray.sort((a, b) => new Date(a.data) - new Date(b.data));
|
|
460
|
+
|
|
461
|
+
return sortedQa;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
updateSectionQaStatus = async (qa, formDataId) => {
|
|
465
|
+
const sortedQa = this.getSortedQa(qa);
|
|
466
|
+
|
|
467
|
+
for (const item of sortedQa) {
|
|
468
|
+
await this.createPatientStateChanges({
|
|
469
|
+
formDataId,
|
|
470
|
+
date: item.date,
|
|
471
|
+
humanResourceId: this.getDoctorIdByName(item.who),
|
|
472
|
+
nextStateCode: item.status,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
getElementIdByName = (type, name) => {
|
|
478
|
+
const el = this[type].find(e => e.name === name);
|
|
479
|
+
return el?.id || null;
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
export default PhenyxApi;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const beSureDoctorExists = async (apiInstance, toBe) => {
|
|
2
|
+
const currentDoctors = await apiInstance.getHumanResources();
|
|
3
|
+
|
|
4
|
+
for (const doctor of toBe) {
|
|
5
|
+
const doctorName = doctor.name;
|
|
6
|
+
const doctorTypeId = apiInstance.getGlobalInfoElementId(apiInstance.globalTypes.humanResources, apiInstance.globalTypes.humanResources.values[doctor.hrGroup]);
|
|
7
|
+
const doctorExists = !!currentDoctors
|
|
8
|
+
.find(item => item.name === doctorName && item.typeId === doctorTypeId);
|
|
9
|
+
|
|
10
|
+
if (!doctorExists) {
|
|
11
|
+
console.log(`Adding ${doctor.name}...`);
|
|
12
|
+
await apiInstance.createHumanResource({
|
|
13
|
+
name: doctor.name,
|
|
14
|
+
typeId: doctorTypeId,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return await apiInstance.getHumanResources();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
beSureDoctorExists,
|
|
24
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { v4 } from 'uuid';
|
|
2
|
+
import { getContrastingColor } from '../utils/colors.js';
|
|
3
|
+
|
|
4
|
+
const checkGlobals = async (apiInstance, mustBe, extraValues = []) => {
|
|
5
|
+
const {
|
|
6
|
+
name,
|
|
7
|
+
values = {},
|
|
8
|
+
} = mustBe;
|
|
9
|
+
|
|
10
|
+
// Sacar los valores de global
|
|
11
|
+
const currentContent = apiInstance.getGlobalInfo(mustBe);
|
|
12
|
+
|
|
13
|
+
if (!currentContent) throw new Error(`Global info ${name} not found`);
|
|
14
|
+
|
|
15
|
+
// Añadir los valores extra
|
|
16
|
+
for (const extraValue of extraValues) {
|
|
17
|
+
if (!values[extraValue]) {
|
|
18
|
+
values[extraValue] = extraValue;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Asegurarnos que existen los tipos especificadas
|
|
23
|
+
const valuesToCheck = [];
|
|
24
|
+
for (const key of Object.keys(values)) {
|
|
25
|
+
valuesToCheck.push(values[key]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Meter la info
|
|
29
|
+
if (valuesToCheck.length) await beSureGlobalExists(apiInstance, currentContent, valuesToCheck);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const beSureGlobalExists = async (apiInstance, currentList, toBeList) => {
|
|
33
|
+
const {
|
|
34
|
+
id,
|
|
35
|
+
name,
|
|
36
|
+
elements,
|
|
37
|
+
} = currentList;
|
|
38
|
+
|
|
39
|
+
let listUpdated = false;
|
|
40
|
+
|
|
41
|
+
for (const element of currentList.elements) {
|
|
42
|
+
if (!element.modality) {
|
|
43
|
+
element.modality = '';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const item of toBeList) {
|
|
48
|
+
const element = elements.find(element => element.name === item && !element.isDeleted);
|
|
49
|
+
|
|
50
|
+
if (!element) {
|
|
51
|
+
console.log(`Adding ${item} to ${name}...`);
|
|
52
|
+
const creationDate = new Date().toISOString();
|
|
53
|
+
elements.push({
|
|
54
|
+
id: v4(),
|
|
55
|
+
color: getContrastingColor(),
|
|
56
|
+
name: item,
|
|
57
|
+
createdAt: creationDate,
|
|
58
|
+
updatedAt: creationDate,
|
|
59
|
+
isDeleted: false,
|
|
60
|
+
modality: '',
|
|
61
|
+
})
|
|
62
|
+
listUpdated = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!element.color) {
|
|
67
|
+
element.color = getContrastingColor();
|
|
68
|
+
element.updatedAt = new Date().toISOString();
|
|
69
|
+
listUpdated = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if(!element.modality) {
|
|
73
|
+
element.modality = '';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (listUpdated) await apiInstance.updateGlobalInfo(id, currentList);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
checkGlobals,
|
|
82
|
+
beSureGlobalExists,
|
|
83
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const beSureLinacsExists = async (apiInstance, toBe) => {
|
|
2
|
+
// Sacar todos los recursos
|
|
3
|
+
const resources = await apiInstance.getResources();
|
|
4
|
+
// Sacar el typeId de LINAC
|
|
5
|
+
const linacsTypeId = (await apiInstance.getResourcesTypes())
|
|
6
|
+
.find(type => type.name === 'LINAC')
|
|
7
|
+
.id;
|
|
8
|
+
|
|
9
|
+
// Sacar solo los LINACs
|
|
10
|
+
const existingLinacs = resources.filter(item => item.typeId === linacsTypeId);
|
|
11
|
+
|
|
12
|
+
// Crear los que falten
|
|
13
|
+
for (const linac of toBe) {
|
|
14
|
+
if (!existingLinacs.find(item => item.name === linac)) {
|
|
15
|
+
console.log(`Adding ${linac}...`);
|
|
16
|
+
await apiInstance.createResource({
|
|
17
|
+
name: linac,
|
|
18
|
+
typeId: linacsTypeId,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const updatedLinacs = await apiInstance.getResources();
|
|
24
|
+
|
|
25
|
+
const response = updatedLinacs.filter(item => item.typeId === linacsTypeId);
|
|
26
|
+
|
|
27
|
+
return response;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
beSureLinacsExists,
|
|
32
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as PhenyxApi } from './PhenyxApi.js';
|
|
2
|
+
export { checkGlobals, beSureGlobalExists } from './controllers/global.js';
|
|
3
|
+
export { beSureDoctorExists } from './controllers/doctors.js';
|
|
4
|
+
export { beSureLinacsExists } from './controllers/linacs.js';
|
|
5
|
+
export { today, formatDateString, formatDate } from './utils/dates.js';
|
|
6
|
+
export { getContrastingColor } from './utils/colors.js';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Genera un color aleatorio en formato RGB que contrasta bien tanto con fondo blanco como negro
|
|
3
|
+
* @returns {string} Color en formato "R, G, B"
|
|
4
|
+
*/
|
|
5
|
+
const getContrastingColor = () => {
|
|
6
|
+
// Colores base que contrastan bien tanto en blanco como en negro
|
|
7
|
+
const baseColors = [
|
|
8
|
+
[244, 67, 54], // Rojo
|
|
9
|
+
[233, 30, 99], // Rosa
|
|
10
|
+
[156, 39, 176], // Morado
|
|
11
|
+
[103, 58, 183], // Morado profundo
|
|
12
|
+
[63, 81, 181], // Índigo
|
|
13
|
+
[33, 150, 243], // Azul
|
|
14
|
+
[0, 188, 212], // Cian
|
|
15
|
+
[0, 150, 136], // Verde azulado
|
|
16
|
+
[76, 175, 80], // Verde
|
|
17
|
+
[139, 195, 74], // Verde lima
|
|
18
|
+
[205, 220, 57], // Lima
|
|
19
|
+
[255, 193, 7], // Ámbar
|
|
20
|
+
[255, 152, 0], // Naranja
|
|
21
|
+
[255, 87, 34], // Naranja profundo
|
|
22
|
+
[121, 85, 72], // Marrón
|
|
23
|
+
[96, 125, 139] // Azul grisáceo
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Seleccionar un color base aleatorio
|
|
27
|
+
const color = baseColors[Math.floor(Math.random() * baseColors.length)];
|
|
28
|
+
|
|
29
|
+
// Añadir una pequeña variación aleatoria para más diversidad
|
|
30
|
+
// manteniendo el contraste dentro de límites seguros
|
|
31
|
+
const variation = () => Math.floor(Math.random() * 30) - 15;
|
|
32
|
+
|
|
33
|
+
const r = Math.max(0, Math.min(255, color[0] + variation()));
|
|
34
|
+
const g = Math.max(0, Math.min(255, color[1] + variation()));
|
|
35
|
+
const b = Math.max(0, Math.min(255, color[2] + variation()));
|
|
36
|
+
|
|
37
|
+
return `${r}, ${g}, ${b}`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
getContrastingColor,
|
|
42
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const formatDateString = (date) => {
|
|
2
|
+
if (!date) return '';
|
|
3
|
+
const [day, month, year] = date.split('/');
|
|
4
|
+
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const formatDate = (date) => {
|
|
8
|
+
const year = date.getFullYear();
|
|
9
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
10
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
11
|
+
|
|
12
|
+
return `${year}-${month}-${day}`;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const today = () => formatDate(new Date());
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
formatDateString,
|
|
19
|
+
formatDate,
|
|
20
|
+
today,
|
|
21
|
+
};
|