soyba-lib 1.0.9 → 1.0.10
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 +8 -4
- package/src/index.js +63 -181
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "soyba-lib",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Thư viện YBA (Sổ Y Bạ) - Truy vấn lịch sử khám bệnh",
|
|
7
|
-
"keywords": [
|
|
7
|
+
"keywords": [
|
|
8
|
+
"yba",
|
|
9
|
+
"soyba",
|
|
10
|
+
"medical",
|
|
11
|
+
"history"
|
|
12
|
+
],
|
|
8
13
|
"author": "hospital-backend",
|
|
9
14
|
"license": "MIT",
|
|
10
15
|
"dependencies": {
|
|
11
|
-
"axios": "^1.6.0"
|
|
12
|
-
"xml2js": "^0.6.2"
|
|
16
|
+
"axios": "^1.6.0"
|
|
13
17
|
}
|
|
14
18
|
}
|
package/src/index.js
CHANGED
|
@@ -1,235 +1,117 @@
|
|
|
1
1
|
// lib/soyba/src/index.js
|
|
2
2
|
import axios from 'axios';
|
|
3
|
-
import { parseStringPromise } from 'xml2js';
|
|
4
3
|
|
|
5
4
|
class YBAClient {
|
|
6
5
|
constructor(options = {}) {
|
|
7
6
|
this.baseUrl = options.baseUrl || process.env.API_SOYBA;
|
|
8
|
-
this.baseUrlKq = options.baseUrlKq || process.env.API_SOYBA_KQ;
|
|
9
7
|
this.apiKey = options.apiKey || process.env.API_KEY;
|
|
10
|
-
this.timeout = options.timeout ||
|
|
8
|
+
this.timeout = options.timeout || 3000;
|
|
11
9
|
this.logger = options.logger || console;
|
|
12
|
-
|
|
10
|
+
|
|
13
11
|
if (!this.baseUrl) {
|
|
14
12
|
throw new Error('YBA baseUrl is required');
|
|
15
13
|
}
|
|
16
|
-
|
|
17
|
-
// Log configuration for debugging
|
|
18
14
|
this.logger.debug('YBAClient initialized', {
|
|
19
15
|
baseUrl: this.baseUrl,
|
|
20
|
-
baseUrlKq: this.baseUrlKq,
|
|
21
16
|
hasApiKey: !!this.apiKey,
|
|
22
17
|
timeout: this.timeout
|
|
23
18
|
});
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
_buildAuthorizationHeader(token) {
|
|
27
|
-
const apiKey = this.apiKey || '';
|
|
28
|
-
if (token) return `${token}${apiKey}`;
|
|
29
|
-
return apiKey ? `token ${apiKey}` : undefined;
|
|
30
|
-
}
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
// Check if it's a valid base64
|
|
40
|
-
try {
|
|
41
|
-
const decoded = Buffer.from(cleaned, 'base64').toString('utf8');
|
|
42
|
-
// Check if it looks like XML
|
|
43
|
-
return decoded.includes('<') && decoded.includes('>');
|
|
44
|
-
} catch {
|
|
45
|
-
return false;
|
|
22
|
+
_getHeaders() {
|
|
23
|
+
const headers = {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'accept': '*/*',
|
|
26
|
+
};
|
|
27
|
+
if (this.apiKey) {
|
|
28
|
+
headers['Authorization'] = this.apiKey;
|
|
46
29
|
}
|
|
30
|
+
return headers;
|
|
47
31
|
}
|
|
48
32
|
|
|
49
|
-
async
|
|
33
|
+
async getLichSuKham({ cccd, ma_lk = '' }) {
|
|
34
|
+
const url = this.baseUrl;
|
|
35
|
+
const headers = this._getHeaders();
|
|
50
36
|
try {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const json = await parseStringPromise(xmlStr, {
|
|
56
|
-
explicitArray: false,
|
|
57
|
-
mergeAttrs: true,
|
|
58
|
-
trim: true
|
|
37
|
+
const response = await axios.get(url, {
|
|
38
|
+
headers,
|
|
39
|
+
params: { CCCD: cccd, MA_LK: ma_lk },
|
|
40
|
+
timeout: this.timeout,
|
|
59
41
|
});
|
|
60
|
-
|
|
61
|
-
return { xml: xmlStr, json };
|
|
42
|
+
return { status: response.status, data: response.data };
|
|
62
43
|
} catch (error) {
|
|
63
|
-
|
|
64
|
-
|
|
44
|
+
const status = error.response?.status || 500;
|
|
45
|
+
const data = error.response?.data || { error: 'SOYBA request failed' };
|
|
46
|
+
return { status, data };
|
|
65
47
|
}
|
|
66
48
|
}
|
|
67
49
|
|
|
68
|
-
async
|
|
50
|
+
async getChiTietThuoc({ cccd = '', ma_lk }) {
|
|
51
|
+
const url = `${this.baseUrl}/chi_tiet_thuoc`;
|
|
52
|
+
const headers = this._getHeaders();
|
|
69
53
|
try {
|
|
70
|
-
this.logger.debug('Fetching XML from URL', { url });
|
|
71
|
-
|
|
72
54
|
const response = await axios.get(url, {
|
|
55
|
+
headers,
|
|
56
|
+
params: { CCCD: cccd, MA_LK: ma_lk },
|
|
73
57
|
timeout: this.timeout,
|
|
74
|
-
responseType: 'text'
|
|
75
58
|
});
|
|
76
|
-
|
|
77
|
-
// Parse XML to JSON
|
|
78
|
-
const json = await parseStringPromise(response.data, {
|
|
79
|
-
explicitArray: false,
|
|
80
|
-
mergeAttrs: true,
|
|
81
|
-
trim: true
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
return { xml: response.data, json };
|
|
59
|
+
return { status: response.status, data: response.data };
|
|
85
60
|
} catch (error) {
|
|
86
|
-
|
|
87
|
-
|
|
61
|
+
const status = error.response?.status || 500;
|
|
62
|
+
const data = error.response?.data || { error: 'SOYBA request failed' };
|
|
63
|
+
return { status, data };
|
|
88
64
|
}
|
|
89
65
|
}
|
|
90
66
|
|
|
91
|
-
async
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
106
|
-
if (key === 'FILEHOSO' && Array.isArray(value)) {
|
|
107
|
-
// Process FILEHOSO array - decode NOIDUNGFILE in each item
|
|
108
|
-
const processedFiles = [];
|
|
109
|
-
for (const file of value) {
|
|
110
|
-
if (file && file.NOIDUNGFILE && this._isBase64(file.NOIDUNGFILE)) {
|
|
111
|
-
this.logger.debug('Decoding XML file', { LOAIHOSO: file.LOAIHOSO });
|
|
112
|
-
const decoded = await this._decodeBase64ToXml(file.NOIDUNGFILE);
|
|
113
|
-
processedFiles.push({
|
|
114
|
-
...file,
|
|
115
|
-
NOIDUNGFILE: decoded.json || decoded.xml || file.NOIDUNGFILE
|
|
116
|
-
});
|
|
117
|
-
} else {
|
|
118
|
-
processedFiles.push(file);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
result[key] = processedFiles;
|
|
122
|
-
} else if (typeof value === 'object') {
|
|
123
|
-
result[key] = await this._processNestedXmlFiles(value);
|
|
124
|
-
} else {
|
|
125
|
-
result[key] = value;
|
|
126
|
-
}
|
|
67
|
+
async getChiTietDvkt({ cccd = '', ma_lk }) {
|
|
68
|
+
const url = `${this.baseUrl}/chi_tiet_dvkt`;
|
|
69
|
+
const headers = this._getHeaders();
|
|
70
|
+
try {
|
|
71
|
+
const response = await axios.get(url, {
|
|
72
|
+
headers,
|
|
73
|
+
params: { CCCD: cccd, MA_LK: ma_lk },
|
|
74
|
+
timeout: this.timeout,
|
|
75
|
+
});
|
|
76
|
+
return { status: response.status, data: response.data };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const status = error.response?.status || 500;
|
|
79
|
+
const data = error.response?.data || { error: 'SOYBA request failed' };
|
|
80
|
+
return { status, data };
|
|
127
81
|
}
|
|
128
|
-
return result;
|
|
129
82
|
}
|
|
130
83
|
|
|
131
|
-
async
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
if (!this.baseUrl) {
|
|
135
|
-
this.logger.error('SOYBA base url not configured', { correlationId });
|
|
136
|
-
throw new Error('Missing API_SOYBA in environment');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const url = `${this.baseUrl}`;
|
|
140
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
141
|
-
const auth = this._buildAuthorizationHeader(token);
|
|
142
|
-
if (auth) headers.Authorization = auth;
|
|
143
|
-
|
|
84
|
+
async getChiTietCls({ cccd = '', ma_lk }) {
|
|
85
|
+
const url = `${this.baseUrl}/chi_tiet_cls`;
|
|
86
|
+
const headers = this._getHeaders();
|
|
144
87
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
88
|
+
const response = await axios.get(url, {
|
|
89
|
+
headers,
|
|
90
|
+
params: { CCCD: cccd, MA_LK: ma_lk },
|
|
91
|
+
timeout: this.timeout,
|
|
92
|
+
});
|
|
149
93
|
return { status: response.status, data: response.data };
|
|
150
94
|
} catch (error) {
|
|
151
|
-
this.logger.error('SOYBA getLichSuKham failed', {
|
|
152
|
-
correlationId,
|
|
153
|
-
error: error.message,
|
|
154
|
-
status: error.response?.status
|
|
155
|
-
});
|
|
156
|
-
|
|
157
95
|
const status = error.response?.status || 500;
|
|
158
96
|
const data = error.response?.data || { error: 'SOYBA request failed' };
|
|
159
97
|
return { status, data };
|
|
160
98
|
}
|
|
161
99
|
}
|
|
162
100
|
|
|
163
|
-
async
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
// Use baseUrlKq for detail requests if different from baseUrl
|
|
167
|
-
const baseUrlToUse = this.baseUrlKq;
|
|
168
|
-
|
|
169
|
-
if (!baseUrlToUse) {
|
|
170
|
-
this.logger.error('SOYBAKQ base url not configured', { correlationId });
|
|
171
|
-
throw new Error('Missing API_SOYBA_KQ in environment');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// If baseUrlKq is different, use it directly, otherwise append path
|
|
175
|
-
const url = baseUrlToUse
|
|
176
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
177
|
-
const auth = this._buildAuthorizationHeader(token);
|
|
178
|
-
if (auth) headers.Authorization = auth;
|
|
179
|
-
|
|
101
|
+
async getPhieuKham({ cccd = '', ma_lk }) {
|
|
102
|
+
const url = `${this.baseUrl}/phieukham`;
|
|
103
|
+
const headers = this._getHeaders();
|
|
180
104
|
try {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
let processedData = response.data;
|
|
187
|
-
|
|
188
|
-
// Handle different response formats
|
|
189
|
-
if (Array.isArray(processedData)) {
|
|
190
|
-
// Process each item in array
|
|
191
|
-
const processedArray = [];
|
|
192
|
-
for (const item of processedData) {
|
|
193
|
-
if (item.Url && item.ContentType === 'text/xml') {
|
|
194
|
-
// New format: fetch XML from URL
|
|
195
|
-
this.logger.debug('Fetching XML from URL', { Url: item.Url });
|
|
196
|
-
const xmlResult = await this._fetchAndParseXmlFromUrl(item.Url);
|
|
197
|
-
if (xmlResult.json) {
|
|
198
|
-
// Process nested XML files in the fetched content
|
|
199
|
-
const processedXml = await this._processNestedXmlFiles(xmlResult.json);
|
|
200
|
-
processedArray.push({
|
|
201
|
-
...item,
|
|
202
|
-
NOIDUNGFILE: processedXml
|
|
203
|
-
});
|
|
204
|
-
} else {
|
|
205
|
-
processedArray.push(item);
|
|
206
|
-
}
|
|
207
|
-
} else {
|
|
208
|
-
// Process existing NOIDUNGFILE if present
|
|
209
|
-
const processedItem = await this._processNestedXmlFiles(item);
|
|
210
|
-
processedArray.push(processedItem);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
processedData = processedArray;
|
|
214
|
-
} else if (processedData && typeof processedData === 'object') {
|
|
215
|
-
// Single object - process nested files
|
|
216
|
-
processedData = await this._processNestedXmlFiles(processedData);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
this.logger.debug('YBA processing completed', { correlationId });
|
|
220
|
-
return { status: response.status, data: processedData };
|
|
221
|
-
|
|
222
|
-
} catch (error) {
|
|
223
|
-
this.logger.error('SOYBA getChiTietLichSu failed', {
|
|
224
|
-
correlationId,
|
|
225
|
-
error: error.message,
|
|
226
|
-
status: error.response?.status,
|
|
227
|
-
url,
|
|
228
|
-
params: { cccd, ma_lk }
|
|
105
|
+
const response = await axios.get(url, {
|
|
106
|
+
headers,
|
|
107
|
+
params: { CCCD: cccd, MA_LK: ma_lk },
|
|
108
|
+
timeout: this.timeout,
|
|
229
109
|
});
|
|
230
|
-
|
|
110
|
+
return { status: response.status, data: response.data };
|
|
111
|
+
} catch (error) {
|
|
231
112
|
const status = error.response?.status || 500;
|
|
232
|
-
|
|
113
|
+
const data = error.response?.data || { error: 'SOYBA request failed' };
|
|
114
|
+
return { status, data };
|
|
233
115
|
}
|
|
234
116
|
}
|
|
235
117
|
}
|