@isofh/chuyen-doi-dia-chi-2-cap 0.1.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/README.md +251 -0
- package/dataXa.json +30104 -0
- package/index.js +460 -0
- package/package.json +25 -0
package/index.js
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
require("mainam-react-native-string-utils");
|
|
2
|
+
|
|
3
|
+
const dataXa = require("./dataXa.json");
|
|
4
|
+
|
|
5
|
+
// Dùng helper của string-utils để tạo key tìm kiếm không dấu, không khoảng trắng.
|
|
6
|
+
const createUniqueText = (text = "") => `${text ?? ""}`.createUniqueText();
|
|
7
|
+
|
|
8
|
+
// Bóc các tiền tố hành chính để so sánh tên xã/tỉnh ổn định hơn.
|
|
9
|
+
const stripAdministrativePrefix = (value = "") => {
|
|
10
|
+
let text = `${value ?? ""}`.trim();
|
|
11
|
+
let previous = "";
|
|
12
|
+
|
|
13
|
+
while (text !== previous) {
|
|
14
|
+
previous = text;
|
|
15
|
+
text = text
|
|
16
|
+
.replace(/^(xã|phường|thị trấn|huyện|quận|thị xã|tỉnh|thành phố)\s+/i, "")
|
|
17
|
+
.replace(/^(tp|tt|tx)\.?\s*/i, "")
|
|
18
|
+
.replace(/^([xXpPqQhH])\.?(?=\s*\d+\b|\s|$)\s*/i, "")
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return text;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Chuẩn hóa mã tìm kiếm hành chính và co gọn các case chỉ chứa số.
|
|
26
|
+
const normalizeAdministrativeCode = (value = "") => {
|
|
27
|
+
const text = createUniqueText(stripAdministrativePrefix(value));
|
|
28
|
+
|
|
29
|
+
if (/^\d+$/.test(text)) {
|
|
30
|
+
return `${Number(text)}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return text;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Tách input địa chỉ về mảng các vế để các hàm phía sau xử lý thống nhất.
|
|
37
|
+
const splitAddressParts = (input) => {
|
|
38
|
+
if (Array.isArray(input)) {
|
|
39
|
+
return input.map((item) => `${item ?? ""}`.trim()).filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return `${input ?? ""}`
|
|
43
|
+
.split(",")
|
|
44
|
+
.map((item) => item.trim())
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Parse chuỗi QR CCCD dạng `soGiayTo||hoTen|ngaySinh|gioiTinh|diaChi|ngayCap|...`.
|
|
49
|
+
const parseCccdQrString = (input = "") => {
|
|
50
|
+
const sourceText = `${input ?? ""}`.replace(/\r?\n/g, "").trim();
|
|
51
|
+
const fields = sourceText.split("|");
|
|
52
|
+
|
|
53
|
+
if (fields.length !== 7 && fields.length !== 11) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
sourceText,
|
|
59
|
+
fields,
|
|
60
|
+
soGiayTo: fields[0] || "",
|
|
61
|
+
hoTen: fields[2] || "",
|
|
62
|
+
ngaySinh: fields[3] || "",
|
|
63
|
+
gioiTinh: fields[4] || "",
|
|
64
|
+
diaChi: fields[5] || "",
|
|
65
|
+
ngayCap: fields[6] || "",
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Chuẩn hóa ngày `DDMMYYYY` từ QR CCCD về format parse được bởi `new Date()` trong `toAddress`.
|
|
70
|
+
const normalizeCccdIssueDate = (value = "") => {
|
|
71
|
+
const text = `${value ?? ""}`.trim();
|
|
72
|
+
|
|
73
|
+
if (!/^\d{8}$/.test(text)) {
|
|
74
|
+
return text;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const day = text.slice(0, 2);
|
|
78
|
+
const month = text.slice(2, 4);
|
|
79
|
+
const year = text.slice(4, 8);
|
|
80
|
+
|
|
81
|
+
return `${year}-${month}-${day}`;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Lấy riêng field địa chỉ từ chuỗi QR CCCD dạng pipe.
|
|
85
|
+
const extractCccdAddressText = (input = "") => {
|
|
86
|
+
const parsedQr = parseCccdQrString(input);
|
|
87
|
+
return parsedQr?.diaChi?.trim() || "";
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Gom chữ ký hàm để support cả kiểu truyền `(tenXa, tinh, options)` và object options.
|
|
91
|
+
const getVerifyOptions = (tinhOrOptions, maybeOptions) => {
|
|
92
|
+
if (
|
|
93
|
+
tinhOrOptions &&
|
|
94
|
+
typeof tinhOrOptions === "object" &&
|
|
95
|
+
!Array.isArray(tinhOrOptions)
|
|
96
|
+
) {
|
|
97
|
+
return {
|
|
98
|
+
tinh: tinhOrOptions.tinh,
|
|
99
|
+
options: tinhOrOptions,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
tinh: tinhOrOptions,
|
|
105
|
+
options: maybeOptions || {},
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Tạo tập khóa tìm kiếm của tỉnh từ cả tên gốc lẫn tên đã bỏ prefix.
|
|
110
|
+
const getProvinceSearchKeys = (value = "") => {
|
|
111
|
+
const keys = [createUniqueText(value), normalizeAdministrativeCode(value)].filter(Boolean);
|
|
112
|
+
return [...new Set(keys)];
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// 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`.
|
|
116
|
+
const getProvinceCandidates = (tinh, sourceDataXa) => {
|
|
117
|
+
const provinceKeys = getProvinceSearchKeys(tinh);
|
|
118
|
+
|
|
119
|
+
if (!provinceKeys.length) {
|
|
120
|
+
return sourceDataXa;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return sourceDataXa.filter((item) => {
|
|
124
|
+
const candidateKeys = [
|
|
125
|
+
item.timKiem,
|
|
126
|
+
createUniqueText(item.ten),
|
|
127
|
+
normalizeAdministrativeCode(item.ten),
|
|
128
|
+
...(item.dsTinh || []).flatMap((item2) => [
|
|
129
|
+
item2.timKiem,
|
|
130
|
+
createUniqueText(item2.ten),
|
|
131
|
+
normalizeAdministrativeCode(item2.ten),
|
|
132
|
+
]),
|
|
133
|
+
].filter(Boolean);
|
|
134
|
+
|
|
135
|
+
return provinceKeys.some((key) => candidateKeys.includes(key));
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Loại bỏ kết quả trùng và ưu tiên match theo tên xã hiện tại trước `satNhapTu`.
|
|
140
|
+
const dedupeMatches = (matches = []) => {
|
|
141
|
+
const uniqueMatches = matches.filter((item, index, arr) => {
|
|
142
|
+
const key = [item.matchType, item.tenXa, item.tenTinh, item.tenXaCu || ""].join("|");
|
|
143
|
+
return (
|
|
144
|
+
arr.findIndex(
|
|
145
|
+
(candidate) =>
|
|
146
|
+
[
|
|
147
|
+
candidate.matchType,
|
|
148
|
+
candidate.tenXa,
|
|
149
|
+
candidate.tenTinh,
|
|
150
|
+
candidate.tenXaCu || "",
|
|
151
|
+
].join("|") === key
|
|
152
|
+
) === index
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return uniqueMatches.sort((a, b) => {
|
|
157
|
+
if (a.matchType === b.matchType) return 0;
|
|
158
|
+
if (a.matchType === "current") return -1;
|
|
159
|
+
if (b.matchType === "current") return 1;
|
|
160
|
+
return 0;
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Kiểm tra một tên xã có hợp lệ trong phạm vi tỉnh đích hay không.
|
|
165
|
+
const verifyXaName = (tenXa, tinhOrOptions, maybeOptions) => {
|
|
166
|
+
const { tinh, options } = getVerifyOptions(tinhOrOptions, maybeOptions);
|
|
167
|
+
const sourceDataXa = options.dataXa || dataXa;
|
|
168
|
+
const normalizedXa = normalizeAdministrativeCode(tenXa);
|
|
169
|
+
const normalizedTinh = normalizeAdministrativeCode(tinh);
|
|
170
|
+
const matchedProvinces = getProvinceCandidates(tinh, sourceDataXa);
|
|
171
|
+
|
|
172
|
+
if (!normalizedXa) {
|
|
173
|
+
return {
|
|
174
|
+
input: tenXa,
|
|
175
|
+
provinceInput: tinh || "",
|
|
176
|
+
normalizedInput: normalizedXa,
|
|
177
|
+
normalizedProvince: normalizedTinh,
|
|
178
|
+
isValid: false,
|
|
179
|
+
matches: [],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const matches = [];
|
|
184
|
+
|
|
185
|
+
matchedProvinces.forEach((province) => {
|
|
186
|
+
province.dsXa.forEach((xa) => {
|
|
187
|
+
if (xa.timKiem === normalizedXa) {
|
|
188
|
+
matches.push({
|
|
189
|
+
matchType: "current",
|
|
190
|
+
tenXa: xa.ten,
|
|
191
|
+
tenTinh: province.ten,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
xa.satNhapTu?.forEach((satNhapItem) => {
|
|
196
|
+
if (satNhapItem.timKiem === normalizedXa) {
|
|
197
|
+
matches.push({
|
|
198
|
+
matchType: "satNhapTu",
|
|
199
|
+
tenXa: xa.ten,
|
|
200
|
+
tenTinh: province.ten,
|
|
201
|
+
tenXaCu: satNhapItem.ten,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const uniqueMatches = dedupeMatches(matches);
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
input: tenXa,
|
|
212
|
+
provinceInput: tinh || "",
|
|
213
|
+
normalizedInput: normalizedXa,
|
|
214
|
+
normalizedProvince: normalizedTinh,
|
|
215
|
+
isValid: uniqueMatches.length > 0,
|
|
216
|
+
matches: uniqueMatches,
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Tìm xã hợp lệ đầu tiên trong chuỗi địa chỉ để tách các vế đứng trước thành `soNha`.
|
|
221
|
+
const splitAddressByValidXa = (input, tinhOrOptions, maybeOptions) => {
|
|
222
|
+
const { tinh, options } = getVerifyOptions(tinhOrOptions, maybeOptions);
|
|
223
|
+
const sourceDataXa = options.dataXa || dataXa;
|
|
224
|
+
const parts = splitAddressParts(input);
|
|
225
|
+
|
|
226
|
+
if (!parts.length) {
|
|
227
|
+
return {
|
|
228
|
+
input,
|
|
229
|
+
soNha: "",
|
|
230
|
+
diaChi: "",
|
|
231
|
+
parts: [],
|
|
232
|
+
matchedXaIndex: -1,
|
|
233
|
+
matchedXa: null,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (parts.length < 2) {
|
|
238
|
+
return {
|
|
239
|
+
input,
|
|
240
|
+
soNha: "",
|
|
241
|
+
diaChi: parts.join(", "),
|
|
242
|
+
parts,
|
|
243
|
+
matchedXaIndex: -1,
|
|
244
|
+
matchedXa: null,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const provinceCandidate = tinh || parts[parts.length - 1];
|
|
249
|
+
let matchedXaIndex = -1;
|
|
250
|
+
let matchedXa = null;
|
|
251
|
+
|
|
252
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
253
|
+
const verifyResult = verifyXaName(parts[index], provinceCandidate, {
|
|
254
|
+
dataXa: sourceDataXa,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (verifyResult.isValid) {
|
|
258
|
+
matchedXaIndex = index;
|
|
259
|
+
matchedXa = verifyResult.matches[0] || null;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (matchedXaIndex <= 0) {
|
|
265
|
+
return {
|
|
266
|
+
input,
|
|
267
|
+
soNha: "",
|
|
268
|
+
diaChi: parts.join(", "),
|
|
269
|
+
parts,
|
|
270
|
+
matchedXaIndex,
|
|
271
|
+
matchedXa,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
input,
|
|
277
|
+
soNha: parts.slice(0, matchedXaIndex).join(", "),
|
|
278
|
+
diaChi: parts.slice(matchedXaIndex).join(", "),
|
|
279
|
+
parts,
|
|
280
|
+
matchedXaIndex,
|
|
281
|
+
matchedXa,
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Bóc địa chỉ từ text căn cước rồi trả về output theo đúng contract của `String.prototype.toAddress`.
|
|
286
|
+
const extractAndConvertCccdAddress = (input, ngayCap, options = {}) => {
|
|
287
|
+
const sourceText = `${input ?? ""}`;
|
|
288
|
+
const parsedQr = parseCccdQrString(sourceText);
|
|
289
|
+
const extractedText = extractCccdAddressText(sourceText);
|
|
290
|
+
const issueDate = normalizeCccdIssueDate(ngayCap || parsedQr?.ngayCap);
|
|
291
|
+
const parsedAddress = extractedText.toAddress({ ngayCap: issueDate, verifyXaName });
|
|
292
|
+
const convertedDiaChi = parsedAddress?.diaChi
|
|
293
|
+
? convertDiaChiMoi(parsedAddress.diaChi, options)
|
|
294
|
+
: "";
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
...(parsedAddress || {}),
|
|
298
|
+
diaChi: convertedDiaChi,
|
|
299
|
+
data: {
|
|
300
|
+
...(parsedAddress?.data || {}),
|
|
301
|
+
cccdText: sourceText,
|
|
302
|
+
cccdFields: parsedQr?.fields || [],
|
|
303
|
+
extractedText,
|
|
304
|
+
issueDate,
|
|
305
|
+
convertedDiaChi,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Nghiệp vụ chuẩn hóa địa chỉ hành chính cũ về format 2 cấp mới.
|
|
312
|
+
*
|
|
313
|
+
* Logic phần này được bê ra từ repo cũ, chỉ thay phần lấy dữ liệu sang `dataXa` local
|
|
314
|
+
* và giữ khả năng override bằng `options.dataXa` để tiện test/publish package.
|
|
315
|
+
*/
|
|
316
|
+
const convertDiaChiMoi = (diaChi2CapCu, options = {}) => {
|
|
317
|
+
const sourceDataXa = options.dataXa || dataXa;
|
|
318
|
+
|
|
319
|
+
if (typeof diaChi2CapCu === "string" && diaChi2CapCu.length <= 6) {
|
|
320
|
+
return diaChi2CapCu;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let diaChi = [];
|
|
324
|
+
const arr = splitAddressParts(diaChi2CapCu);
|
|
325
|
+
|
|
326
|
+
if (arr.length === 2) diaChi = [arr[0], arr[1]];
|
|
327
|
+
if (arr.length === 3) diaChi = [arr[0], arr[2]];
|
|
328
|
+
if (arr.length === 1) diaChi = [arr[0]];
|
|
329
|
+
|
|
330
|
+
const tinhCu = createUniqueText(diaChi[diaChi.length - 1] || "");
|
|
331
|
+
const xaCu = diaChi.length > 1 ? normalizeAdministrativeCode(diaChi[0]) : "";
|
|
332
|
+
const huyenCu = arr.length === 3 ? normalizeAdministrativeCode(arr[1]) : "";
|
|
333
|
+
|
|
334
|
+
const tinh = sourceDataXa.find(
|
|
335
|
+
(item) =>
|
|
336
|
+
tinhCu.indexOf(item.timKiem) !== -1 ||
|
|
337
|
+
item.dsTinh?.find((item2) => tinhCu.indexOf(item2.timKiem) !== -1)
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
if (!tinh) {
|
|
341
|
+
//nếu không có tỉnh, thì có thể đó là xã
|
|
342
|
+
return diaChi2CapCu;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
let xa = null;
|
|
346
|
+
let dsXa = null;
|
|
347
|
+
|
|
348
|
+
//B1: Tìm kiếm theo tên tỉnh trước
|
|
349
|
+
if (!xaCu) {
|
|
350
|
+
/* có một số trường hợp Điện Biên Phủ, tỉnh Điện Biên
|
|
351
|
+
nhập vào Điện Biên Phủ nhưng lại đang hiểu là tỉnh Điện Biên
|
|
352
|
+
trong trường hợp này thì thử tìm xem có xã nào trong tỉnh đó trùng với tên vừa nhập không
|
|
353
|
+
*/
|
|
354
|
+
xa = tinh.dsXa.find((item) =>
|
|
355
|
+
//khi đó tinhCu chính là xaCu
|
|
356
|
+
/^\d+$/.test(tinhCu) ? tinhCu === item.timKiem : tinhCu.indexOf(item.timKiem) !== -1
|
|
357
|
+
)?.ten;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
//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
|
|
361
|
+
//Xuân Thành, Xuân Lộc, Đồng Nai -> Xuân Thành, Đồng Nai
|
|
362
|
+
//Xuân Thanh, Long Khánh, Đồng Nai -> Long Khánh, Đồng Nai
|
|
363
|
+
//Địa chỉ người dùng là Xuân Thanh, Long Khánh, Đồng Nai nhưng lại tìm ra Xuân Thành, Đồng Nai
|
|
364
|
+
//Vì tìm theo tên sau sát nhập vô tình lại trùng Xuân Thành, Đồng Nai vì xóa dấu rồi
|
|
365
|
+
//Nên để chắc chắn thì B2 này sẽ search thêm cả huyện (nếu có) trùng với huyện từ địa chỉ của người dùng
|
|
366
|
+
if (!xa) {
|
|
367
|
+
xa = xaCu
|
|
368
|
+
? tinh.dsXa.find(
|
|
369
|
+
(item) =>
|
|
370
|
+
xaCu === item.timKiem &&
|
|
371
|
+
(!huyenCu || !item.huyen || `${item.huyen}`.indexOf(huyenCu) !== -1)
|
|
372
|
+
)?.ten
|
|
373
|
+
: ""; //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
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
//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
|
|
377
|
+
//Ở bước này thì cũng so sanh luôn nếu xã có thông tin huyện, vì trong 1 tỉnh có nhiều xã trùng tên nhau
|
|
378
|
+
//Trả ra danh sách xã, và sẽ ưu tiên xã nào trùng huyện, nếu không có thì lấy bản ghi đầu tiên tim được
|
|
379
|
+
if (!xa) {
|
|
380
|
+
//nếu không có xã nào trùng tên thì mới tìm kiếm các xã đã bị sát nhập tìm kiếm chính xác
|
|
381
|
+
//đầu tiên là lấy ra tất cả các xã phù hợp
|
|
382
|
+
dsXa = xaCu
|
|
383
|
+
? tinh.dsXa.filter((item) =>
|
|
384
|
+
item.satNhapTu?.find(
|
|
385
|
+
(item2) =>
|
|
386
|
+
xaCu === item2.timKiem &&
|
|
387
|
+
(!huyenCu || !item2.huyen || `${item2.huyen}`.indexOf(huyenCu) !== -1)
|
|
388
|
+
)
|
|
389
|
+
)
|
|
390
|
+
: [];
|
|
391
|
+
|
|
392
|
+
//nếu chỉ ra 1 kết quả thì trả về luôn
|
|
393
|
+
if (dsXa.length === 1) {
|
|
394
|
+
xa = dsXa[0].ten;
|
|
395
|
+
} else if (dsXa.length) {
|
|
396
|
+
//nếu có thông tin huyện
|
|
397
|
+
if (huyenCu) {
|
|
398
|
+
//thì ưu tiên xã nào trùng huyện
|
|
399
|
+
xa = dsXa.find((item) =>
|
|
400
|
+
item.satNhapTu.some(
|
|
401
|
+
(satNhapItem) =>
|
|
402
|
+
satNhapItem.huyen && `${satNhapItem.huyen}`.indexOf(huyenCu) !== -1
|
|
403
|
+
)
|
|
404
|
+
)?.ten;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
//ngược lại thì trả về bản ghi đầu tiên
|
|
408
|
+
if (!xa) {
|
|
409
|
+
xa = dsXa[0]?.ten;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
//B4: Nếu B3 không ra kết quả thì lúc này sẽ không tìm kiếm theo điều kiện huyện nữa
|
|
415
|
+
if (!xa) {
|
|
416
|
+
xa = xaCu
|
|
417
|
+
? tinh.dsXa.find((item) =>
|
|
418
|
+
item.satNhapTu?.find((item2) => xaCu.indexOf(item2.timKiem) !== -1)
|
|
419
|
+
)?.ten
|
|
420
|
+
: "";
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
//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
|
|
424
|
+
//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
|
|
425
|
+
if (!xa) {
|
|
426
|
+
xa = xaCu ? tinh.dsXa.find((item) => xaCu === item.timKiem)?.ten : ""; //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
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return [xa, tinh?.ten].filter(Boolean).join(", ");
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// Mount helper lên global để các package khác có thể dùng runtime mà không cần dependency ngược.
|
|
433
|
+
const globalTarget =
|
|
434
|
+
typeof globalThis !== "undefined"
|
|
435
|
+
? globalThis
|
|
436
|
+
: typeof window !== "undefined"
|
|
437
|
+
? window
|
|
438
|
+
: typeof global !== "undefined"
|
|
439
|
+
? global
|
|
440
|
+
: null;
|
|
441
|
+
|
|
442
|
+
if (globalTarget) {
|
|
443
|
+
globalTarget.verifyXaName = verifyXaName;
|
|
444
|
+
globalTarget.splitAddressByValidXa = splitAddressByValidXa;
|
|
445
|
+
globalTarget.extractAndConvertCccdAddress = extractAndConvertCccdAddress;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
module.exports = {
|
|
449
|
+
dataXa,
|
|
450
|
+
createUniqueText,
|
|
451
|
+
stripAdministrativePrefix,
|
|
452
|
+
normalizeAdministrativeCode,
|
|
453
|
+
convertDiaChiMoi,
|
|
454
|
+
verifyXaName,
|
|
455
|
+
splitAddressByValidXa,
|
|
456
|
+
parseCccdQrString,
|
|
457
|
+
normalizeCccdIssueDate,
|
|
458
|
+
extractCccdAddressText,
|
|
459
|
+
extractAndConvertCccdAddress,
|
|
460
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@isofh/chuyen-doi-dia-chi-2-cap",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Chuan hoa dia chi hanh chinh cu sang format 2 cap moi",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"index.js",
|
|
8
|
+
"dataXa.json",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"address",
|
|
16
|
+
"vietnam",
|
|
17
|
+
"administrative",
|
|
18
|
+
"isofh"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"mainam-react-native-string-utils": "^4.0.12"
|
|
22
|
+
},
|
|
23
|
+
"author": "ISOFH",
|
|
24
|
+
"license": "ISC"
|
|
25
|
+
}
|