soyba-lib 1.0.9 → 1.0.11

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.
Files changed (2) hide show
  1. package/package.json +8 -4
  2. package/src/index.js +70 -181
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "soyba-lib",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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": ["yba", "soyba", "medical", "history"],
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,124 @@
1
1
  // lib/soyba/src/index.js
2
2
  import axios from 'axios';
3
- import { parseStringPromise } from 'xml2js';
3
+ import https from 'https';
4
4
 
5
5
  class YBAClient {
6
6
  constructor(options = {}) {
7
7
  this.baseUrl = options.baseUrl || process.env.API_SOYBA;
8
- this.baseUrlKq = options.baseUrlKq || process.env.API_SOYBA_KQ;
9
8
  this.apiKey = options.apiKey || process.env.API_KEY;
10
- this.timeout = options.timeout || 30000;
9
+ this.timeout = options.timeout || 3000;
11
10
  this.logger = options.logger || console;
12
-
11
+
13
12
  if (!this.baseUrl) {
14
13
  throw new Error('YBA baseUrl is required');
15
14
  }
16
-
17
- // Log configuration for debugging
18
15
  this.logger.debug('YBAClient initialized', {
19
16
  baseUrl: this.baseUrl,
20
- baseUrlKq: this.baseUrlKq,
21
17
  hasApiKey: !!this.apiKey,
22
18
  timeout: this.timeout
23
19
  });
20
+ this.httpsAgent = new https.Agent({ rejectUnauthorized: false });
24
21
  }
25
22
 
26
- _buildAuthorizationHeader(token) {
27
- const apiKey = this.apiKey || '';
28
- if (token) return `${token}${apiKey}`;
29
- return apiKey ? `token ${apiKey}` : undefined;
30
- }
31
23
 
32
- _isBase64(str) {
33
- if (typeof str !== 'string' || str.length === 0) return false;
34
-
35
- // Remove whitespace and check format
36
- const cleaned = str.replace(/\s+/g, '');
37
- if (!/^[A-Za-z0-9+/=_-]+$/.test(cleaned)) return false;
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;
24
+ _getHeaders() {
25
+ const headers = {
26
+ 'Content-Type': 'application/json',
27
+ 'accept': '*/*',
28
+ };
29
+ if (this.apiKey) {
30
+ headers['Authorization'] = this.apiKey;
46
31
  }
32
+ return headers;
47
33
  }
48
34
 
49
- async _decodeBase64ToXml(base64Str) {
35
+ async getLichSuKham({ cccd }) {
36
+ const url = this.baseUrl;
37
+ const headers = this._getHeaders();
50
38
  try {
51
- const cleaned = base64Str.replace(/\s+/g, '');
52
- const xmlStr = Buffer.from(cleaned, 'base64').toString('utf8');
53
-
54
- // Parse XML to JSON
55
- const json = await parseStringPromise(xmlStr, {
56
- explicitArray: false,
57
- mergeAttrs: true,
58
- trim: true
39
+ const response = await axios.get(url, {
40
+ headers,
41
+ params: { CCCD: cccd },
42
+ timeout: this.timeout,
43
+ httpsAgent: this.httpsAgent,
59
44
  });
60
-
61
- return { xml: xmlStr, json };
45
+ return { status: response.status, data: response.data };
62
46
  } catch (error) {
63
- this.logger.warn('Failed to decode base64 XML', { error: error.message });
64
- return { xml: null, json: null };
47
+ const status = error.response?.status || 500;
48
+ const data = error.response?.data || { error: 'SOYBA request failed' };
49
+ return { status, data };
65
50
  }
66
51
  }
67
52
 
68
- async _fetchAndParseXmlFromUrl(url) {
53
+ async getChiTietThuoc({ cccd = '', ma_lk }) {
54
+ const url = `${this.baseUrl}/chi_tiet_thuoc`;
55
+ const headers = this._getHeaders();
69
56
  try {
70
- this.logger.debug('Fetching XML from URL', { url });
71
-
72
57
  const response = await axios.get(url, {
58
+ headers,
59
+ params: { CCCD: cccd, MA_LK: ma_lk },
73
60
  timeout: this.timeout,
74
- responseType: 'text'
61
+ httpsAgent: this.httpsAgent,
75
62
  });
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 };
63
+ return { status: response.status, data: response.data };
85
64
  } catch (error) {
86
- this.logger.warn('Failed to fetch/parse XML from URL', { url, error: error.message });
87
- return { xml: null, json: null };
65
+ const status = error.response?.status || 500;
66
+ const data = error.response?.data || { error: 'SOYBA request failed' };
67
+ return { status, data };
88
68
  }
89
69
  }
90
70
 
91
- async _processNestedXmlFiles(obj) {
92
- if (!obj || typeof obj !== 'object') return obj;
93
-
94
- // Process arrays
95
- if (Array.isArray(obj)) {
96
- const processed = [];
97
- for (const item of obj) {
98
- processed.push(await this._processNestedXmlFiles(item));
99
- }
100
- return processed;
101
- }
102
-
103
- // Process objects
104
- const result = {};
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
- }
71
+ async getChiTietDvkt({ cccd = '', ma_lk }) {
72
+ const url = `${this.baseUrl}/chi_tiet_dvkt`;
73
+ const headers = this._getHeaders();
74
+ try {
75
+ const response = await axios.get(url, {
76
+ headers,
77
+ params: { CCCD: cccd, MA_LK: ma_lk },
78
+ timeout: this.timeout,
79
+ httpsAgent: this.httpsAgent,
80
+ });
81
+ return { status: response.status, data: response.data };
82
+ } catch (error) {
83
+ const status = error.response?.status || 500;
84
+ const data = error.response?.data || { error: 'SOYBA request failed' };
85
+ return { status, data };
127
86
  }
128
- return result;
129
87
  }
130
88
 
131
- async getLichSuKham(cccd, token) {
132
- const correlationId = `soyba_hist_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;
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
-
89
+ async getChiTietCls({ cccd = '', ma_lk }) {
90
+ const url = `${this.baseUrl}/chi_tiet_cls`;
91
+ const headers = this._getHeaders();
144
92
  try {
145
- this.logger.debug('SOYBA getLichSuKham request', { correlationId, url, params: { cccd } });
146
- const response = await axios.get(url, { headers, params: { cccd }, timeout: this.timeout });
147
- this.logger.debug('SOYBA getLichSuKham response', { correlationId, status: response.status });
148
-
93
+ const response = await axios.get(url, {
94
+ headers,
95
+ params: { CCCD: cccd, MA_LK: ma_lk },
96
+ timeout: this.timeout,
97
+ httpsAgent: this.httpsAgent,
98
+ });
149
99
  return { status: response.status, data: response.data };
150
100
  } catch (error) {
151
- this.logger.error('SOYBA getLichSuKham failed', {
152
- correlationId,
153
- error: error.message,
154
- status: error.response?.status
155
- });
156
-
157
101
  const status = error.response?.status || 500;
158
102
  const data = error.response?.data || { error: 'SOYBA request failed' };
159
103
  return { status, data };
160
104
  }
161
105
  }
162
106
 
163
- async getChiTietLichSu(cccd, ma_lk, token) {
164
- const correlationId = `soyba_detail_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;
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
-
107
+ async getPhieuKham({ cccd = '', ma_lk }) {
108
+ const url = `${this.baseUrl}/phieukham`;
109
+ const headers = this._getHeaders();
180
110
  try {
181
- this.logger.debug('SOYBA getChiTietLichSu request', { correlationId, url, params: { cccd, ma_lk } });
182
- const response = await axios.get(url, { headers, params: { cccd, ma_lk }, timeout: this.timeout });
183
-
184
- this.logger.debug('YBA raw response received', { correlationId, dataLength: JSON.stringify(response.data).length });
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 }
111
+ const response = await axios.get(url, {
112
+ headers,
113
+ params: { CCCD: cccd, MA_LK: ma_lk },
114
+ timeout: this.timeout,
115
+ httpsAgent: this.httpsAgent,
229
116
  });
230
-
117
+ return { status: response.status, data: response.data };
118
+ } catch (error) {
231
119
  const status = error.response?.status || 500;
232
- return { status, data: [] };
120
+ const data = error.response?.data || { error: 'SOYBA request failed' };
121
+ return { status, data };
233
122
  }
234
123
  }
235
124
  }