@isofh/chuyen-doi-dia-chi-2-cap 1.0.10 → 2.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/index.js +65 -44
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -11,14 +11,21 @@ let dataXa = null;
|
|
|
11
11
|
let dataXaPromise = null;
|
|
12
12
|
|
|
13
13
|
// Resolve global runtime để hỗ trợ các hook override giống contract gốc ở repo cũ.
|
|
14
|
-
const getGlobalTarget = () =>
|
|
15
|
-
typeof
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
const getGlobalTarget = () => {
|
|
15
|
+
if (typeof self !== "undefined") {
|
|
16
|
+
return self;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof window !== "undefined") {
|
|
20
|
+
return window;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof global !== "undefined") {
|
|
24
|
+
return global;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
22
29
|
|
|
23
30
|
// Nạp dataset hành chính ở thời điểm cần dùng thay vì load ngay khi require package.
|
|
24
31
|
const getDataXaSync = () => {
|
|
@@ -43,11 +50,11 @@ const loadDataXa = async (customDataXa) => {
|
|
|
43
50
|
};
|
|
44
51
|
|
|
45
52
|
// Dùng helper của string-utils để tạo key tìm kiếm không dấu, không khoảng trắng.
|
|
46
|
-
const createUniqueText = (text = "") => `${text
|
|
53
|
+
const createUniqueText = (text = "") => `${text == null ? "" : text}`.createUniqueText();
|
|
47
54
|
|
|
48
55
|
// Bóc các tiền tố hành chính để so sánh tên xã/tỉnh ổn định hơn.
|
|
49
56
|
const stripAdministrativePrefix = (value = "") => {
|
|
50
|
-
let text = `${value
|
|
57
|
+
let text = `${value == null ? "" : value}`.trim();
|
|
51
58
|
let previous = "";
|
|
52
59
|
|
|
53
60
|
while (text !== previous) {
|
|
@@ -76,10 +83,10 @@ const normalizeAdministrativeCode = (value = "") => {
|
|
|
76
83
|
// Tách input địa chỉ về mảng các vế để các hàm phía sau xử lý thống nhất.
|
|
77
84
|
const splitAddressParts = (input) => {
|
|
78
85
|
if (Array.isArray(input)) {
|
|
79
|
-
return input.map((item) => `${item
|
|
86
|
+
return input.map((item) => `${item == null ? "" : item}`.trim()).filter(Boolean);
|
|
80
87
|
}
|
|
81
88
|
|
|
82
|
-
return `${input
|
|
89
|
+
return `${input == null ? "" : input}`
|
|
83
90
|
.split(",")
|
|
84
91
|
.map((item) => item.trim())
|
|
85
92
|
.filter(Boolean);
|
|
@@ -87,7 +94,7 @@ const splitAddressParts = (input) => {
|
|
|
87
94
|
|
|
88
95
|
// Parse chuỗi QR CCCD dạng `soGiayTo||hoTen|ngaySinh|gioiTinh|diaChi|ngayCap|...`.
|
|
89
96
|
const parseCccdQrString = (input = "") => {
|
|
90
|
-
const sourceText = `${input
|
|
97
|
+
const sourceText = `${input == null ? "" : input}`.replace(/\r?\n/g, "").trim();
|
|
91
98
|
const fields = sourceText.split("|");
|
|
92
99
|
|
|
93
100
|
if (fields.length !== 7 && fields.length !== 11) {
|
|
@@ -108,7 +115,7 @@ const parseCccdQrString = (input = "") => {
|
|
|
108
115
|
|
|
109
116
|
// Chuẩn hóa ngày `DDMMYYYY` từ QR CCCD về format parse được bởi `new Date()` trong `toAddress`.
|
|
110
117
|
const normalizeCccdIssueDate = (value = "") => {
|
|
111
|
-
const text = `${value
|
|
118
|
+
const text = `${value == null ? "" : value}`.trim();
|
|
112
119
|
|
|
113
120
|
if (!/^\d{8}$/.test(text)) {
|
|
114
121
|
return text;
|
|
@@ -124,7 +131,7 @@ const normalizeCccdIssueDate = (value = "") => {
|
|
|
124
131
|
// Lấy riêng field địa chỉ từ chuỗi QR CCCD dạng pipe.
|
|
125
132
|
const extractCccdAddressText = (input = "") => {
|
|
126
133
|
const parsedQr = parseCccdQrString(input);
|
|
127
|
-
return parsedQr
|
|
134
|
+
return (parsedQr && parsedQr.diaChi ? parsedQr.diaChi.trim() : "") || "";
|
|
128
135
|
};
|
|
129
136
|
|
|
130
137
|
// Gom chữ ký hàm để support cả kiểu truyền `(tenXa, tinh, options)` và object options.
|
|
@@ -155,6 +162,14 @@ const getProvinceSearchKeys = (value = "") => {
|
|
|
155
162
|
return [...new Set(keys)];
|
|
156
163
|
};
|
|
157
164
|
|
|
165
|
+
const getLegacyProvinces = (province) => {
|
|
166
|
+
return province && Array.isArray(province.dsTinh) ? province.dsTinh : [];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const getMergedFromList = (xa) => {
|
|
170
|
+
return xa && Array.isArray(xa.satNhapTu) ? xa.satNhapTu : [];
|
|
171
|
+
};
|
|
172
|
+
|
|
158
173
|
// Thu hẹp danh sách tỉnh cần dò, bao gồm cả tỉnh hiện tại và các tỉnh cũ trong `dsTinh`.
|
|
159
174
|
const getProvinceCandidates = (tinh, sourceDataXa) => {
|
|
160
175
|
const provinceKeys = getProvinceSearchKeys(tinh);
|
|
@@ -164,16 +179,17 @@ const getProvinceCandidates = (tinh, sourceDataXa) => {
|
|
|
164
179
|
}
|
|
165
180
|
|
|
166
181
|
return sourceDataXa.filter((item) => {
|
|
182
|
+
const legacyKeys = getLegacyProvinces(item).reduce((result, item2) => {
|
|
183
|
+
result.push(item2.timKiem, createUniqueText(item2.ten), normalizeAdministrativeCode(item2.ten));
|
|
184
|
+
return result;
|
|
185
|
+
}, []);
|
|
167
186
|
const candidateKeys = [
|
|
168
187
|
item.timKiem,
|
|
169
188
|
createUniqueText(item.ten),
|
|
170
189
|
normalizeAdministrativeCode(item.ten),
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
normalizeAdministrativeCode(item2.ten),
|
|
175
|
-
]),
|
|
176
|
-
].filter(Boolean);
|
|
190
|
+
]
|
|
191
|
+
.concat(legacyKeys)
|
|
192
|
+
.filter(Boolean);
|
|
177
193
|
|
|
178
194
|
return provinceKeys.some((key) => candidateKeys.includes(key));
|
|
179
195
|
});
|
|
@@ -191,7 +207,7 @@ const findProvinceByText = (value = "", sourceDataXa = []) => {
|
|
|
191
207
|
sourceDataXa.find(
|
|
192
208
|
(item) =>
|
|
193
209
|
normalizedValue.indexOf(item.timKiem) !== -1 ||
|
|
194
|
-
item.
|
|
210
|
+
getLegacyProvinces(item).some((item2) => normalizedValue.indexOf(item2.timKiem) !== -1)
|
|
195
211
|
) || null
|
|
196
212
|
);
|
|
197
213
|
};
|
|
@@ -314,7 +330,7 @@ const verifyXaName = (tenXa, tinhOrOptions, maybeOptions) => {
|
|
|
314
330
|
});
|
|
315
331
|
}
|
|
316
332
|
|
|
317
|
-
xa.
|
|
333
|
+
getMergedFromList(xa).forEach((satNhapItem) => {
|
|
318
334
|
if (
|
|
319
335
|
satNhapItem.timKiem === normalizedXa &&
|
|
320
336
|
isDistrictCompatible(satNhapItem.huyen || xa.huyen)
|
|
@@ -411,14 +427,14 @@ const splitAddressByValidXa = (input, tinhOrOptions, maybeOptions) => {
|
|
|
411
427
|
|
|
412
428
|
// Bóc địa chỉ từ text căn cước rồi trả về output theo đúng contract của `String.prototype.toAddress`.
|
|
413
429
|
const extractAndConvertCccdAddress = async (input, ngayCap, options = {}) => {
|
|
414
|
-
const sourceText = `${input
|
|
430
|
+
const sourceText = `${input == null ? "" : input}`;
|
|
415
431
|
const parsedQr = parseCccdQrString(sourceText);
|
|
416
432
|
const extractedText = extractCccdAddressText(sourceText);
|
|
417
|
-
const issueDate = normalizeCccdIssueDate(ngayCap || parsedQr
|
|
433
|
+
const issueDate = normalizeCccdIssueDate(ngayCap || (parsedQr ? parsedQr.ngayCap : ""));
|
|
418
434
|
const parsedAddress = extractedText.toAddress({ ngayCap: issueDate, verifyXaName });
|
|
419
|
-
const soNha = parsedAddress
|
|
420
|
-
const diaChiGoc = parsedAddress
|
|
421
|
-
const convertedDiaChi = parsedAddress
|
|
435
|
+
const soNha = parsedAddress && parsedAddress.soNha ? parsedAddress.soNha : "";
|
|
436
|
+
const diaChiGoc = parsedAddress && parsedAddress.diaChi ? parsedAddress.diaChi : "";
|
|
437
|
+
const convertedDiaChi = parsedAddress && parsedAddress.diaChi
|
|
422
438
|
? await convertDiaChiMoi(parsedAddress.diaChi, options)
|
|
423
439
|
: "";
|
|
424
440
|
|
|
@@ -427,9 +443,9 @@ const extractAndConvertCccdAddress = async (input, ngayCap, options = {}) => {
|
|
|
427
443
|
soNha,
|
|
428
444
|
diaChi: convertedDiaChi,
|
|
429
445
|
data: {
|
|
430
|
-
...(parsedAddress
|
|
446
|
+
...((parsedAddress && parsedAddress.data) || {}),
|
|
431
447
|
cccdText: sourceText,
|
|
432
|
-
cccdFields: parsedQr
|
|
448
|
+
cccdFields: parsedQr ? parsedQr.fields || [] : [],
|
|
433
449
|
extractedText,
|
|
434
450
|
issueDate,
|
|
435
451
|
soNha,
|
|
@@ -473,7 +489,7 @@ const convertDiaChiMoi = async (diaChi2CapCu, options = {}) => {
|
|
|
473
489
|
const globalTarget = getGlobalTarget();
|
|
474
490
|
|
|
475
491
|
if (
|
|
476
|
-
!globalTarget
|
|
492
|
+
!(globalTarget && globalTarget.convertDiaChiMoi) &&
|
|
477
493
|
typeof diaChi2CapCu === "string" &&
|
|
478
494
|
diaChi2CapCu.length <= 6
|
|
479
495
|
) {
|
|
@@ -482,11 +498,11 @@ const convertDiaChiMoi = async (diaChi2CapCu, options = {}) => {
|
|
|
482
498
|
|
|
483
499
|
let sourceDataXa = await loadDataXa(options.dataXa);
|
|
484
500
|
|
|
485
|
-
if (globalTarget
|
|
501
|
+
if (globalTarget && globalTarget.modifyDataXa) {
|
|
486
502
|
sourceDataXa = globalTarget.modifyDataXa(sourceDataXa);
|
|
487
503
|
}
|
|
488
504
|
|
|
489
|
-
if (globalTarget
|
|
505
|
+
if (globalTarget && globalTarget.convertDiaChiMoi) {
|
|
490
506
|
return await Promise.resolve(globalTarget.convertDiaChiMoi(diaChi2CapCu, sourceDataXa));
|
|
491
507
|
}
|
|
492
508
|
|
|
@@ -504,7 +520,7 @@ const convertDiaChiMoi = async (diaChi2CapCu, options = {}) => {
|
|
|
504
520
|
const tinh = sourceDataXa.find(
|
|
505
521
|
(item) =>
|
|
506
522
|
tinhCu.indexOf(item.timKiem) !== -1 ||
|
|
507
|
-
item.
|
|
523
|
+
getLegacyProvinces(item).find((item2) => tinhCu.indexOf(item2.timKiem) !== -1)
|
|
508
524
|
);
|
|
509
525
|
|
|
510
526
|
if (!tinh) {
|
|
@@ -524,7 +540,8 @@ const convertDiaChiMoi = async (diaChi2CapCu, options = {}) => {
|
|
|
524
540
|
xa = tinh.dsXa.find((item) =>
|
|
525
541
|
//khi đó tinhCu chính là xaCu
|
|
526
542
|
/^\d+$/.test(tinhCu) ? tinhCu === item.timKiem : tinhCu.indexOf(item.timKiem) !== -1
|
|
527
|
-
)
|
|
543
|
+
);
|
|
544
|
+
xa = xa ? xa.ten : xa;
|
|
528
545
|
}
|
|
529
546
|
|
|
530
547
|
//B2: Tìm kiếm theo tên sau sát nhập kèm theo huyện vì một số trường hợp có case sau
|
|
@@ -539,8 +556,9 @@ const convertDiaChiMoi = async (diaChi2CapCu, options = {}) => {
|
|
|
539
556
|
(item) =>
|
|
540
557
|
xaCu === item.timKiem &&
|
|
541
558
|
(!huyenCu || !item.huyen || `${item.huyen}`.indexOf(huyenCu) !== -1)
|
|
542
|
-
)
|
|
559
|
+
)
|
|
543
560
|
: ""; //mục đích tách riêng ra không tìm kiếm chung với xã sát nhập để ưu tiên xã có tên trùng trước
|
|
561
|
+
xa = xa ? xa.ten : xa;
|
|
544
562
|
}
|
|
545
563
|
|
|
546
564
|
//B3: Nếu B2 không ra kết quả nào thì tiếp tục tìm kiếm theo các xã bị sát nhập xem có xã nào trùng không
|
|
@@ -551,7 +569,7 @@ const convertDiaChiMoi = async (diaChi2CapCu, options = {}) => {
|
|
|
551
569
|
//đầu tiên là lấy ra tất cả các xã phù hợp
|
|
552
570
|
dsXa = xaCu
|
|
553
571
|
? tinh.dsXa.filter((item) =>
|
|
554
|
-
item.
|
|
572
|
+
getMergedFromList(item).find(
|
|
555
573
|
(item2) =>
|
|
556
574
|
xaCu === item2.timKiem &&
|
|
557
575
|
(!huyenCu || !item2.huyen || `${item2.huyen}`.indexOf(huyenCu) !== -1)
|
|
@@ -567,16 +585,17 @@ const convertDiaChiMoi = async (diaChi2CapCu, options = {}) => {
|
|
|
567
585
|
if (huyenCu) {
|
|
568
586
|
//thì ưu tiên xã nào trùng huyện
|
|
569
587
|
xa = dsXa.find((item) =>
|
|
570
|
-
item.
|
|
588
|
+
getMergedFromList(item).some(
|
|
571
589
|
(satNhapItem) =>
|
|
572
590
|
satNhapItem.huyen && `${satNhapItem.huyen}`.indexOf(huyenCu) !== -1
|
|
573
591
|
)
|
|
574
|
-
)
|
|
592
|
+
);
|
|
593
|
+
xa = xa ? xa.ten : xa;
|
|
575
594
|
}
|
|
576
595
|
|
|
577
596
|
//ngược lại thì trả về bản ghi đầu tiên
|
|
578
597
|
if (!xa) {
|
|
579
|
-
xa = dsXa[0]
|
|
598
|
+
xa = dsXa[0] ? dsXa[0].ten : "";
|
|
580
599
|
}
|
|
581
600
|
}
|
|
582
601
|
}
|
|
@@ -585,18 +604,20 @@ const convertDiaChiMoi = async (diaChi2CapCu, options = {}) => {
|
|
|
585
604
|
if (!xa) {
|
|
586
605
|
xa = xaCu
|
|
587
606
|
? tinh.dsXa.find((item) =>
|
|
588
|
-
item.
|
|
589
|
-
)
|
|
607
|
+
getMergedFromList(item).find((item2) => xaCu.indexOf(item2.timKiem) !== -1)
|
|
608
|
+
)
|
|
590
609
|
: "";
|
|
610
|
+
xa = xa ? xa.ten : xa;
|
|
591
611
|
}
|
|
592
612
|
|
|
593
613
|
//B5 thì lúc này sẽ tìm kiếm những xã sau sát nhập nhưng không theo điều kiện huyện nữa
|
|
594
614
|
//Bước này là bước mở rộng của B2 sau khi tất cả đều không ra kết quả thì bỏ bớt điều kiện đi
|
|
595
615
|
if (!xa) {
|
|
596
|
-
xa = xaCu ? tinh.dsXa.find((item) => xaCu === item.timKiem)
|
|
616
|
+
xa = xaCu ? tinh.dsXa.find((item) => xaCu === item.timKiem) : ""; //mục đích tách riêng ra không tìm kiếm chung với xã sát nhập để ưu tiên xã có tên trùng trước
|
|
617
|
+
xa = xa ? xa.ten : xa;
|
|
597
618
|
}
|
|
598
619
|
|
|
599
|
-
return [xa, tinh
|
|
620
|
+
return [xa, tinh ? tinh.ten : ""].filter(Boolean).join(", ");
|
|
600
621
|
};
|
|
601
622
|
|
|
602
623
|
// Mount helper lên global để các package khác có thể dùng runtime mà không cần dependency ngược.
|