@isofh/chuyen-doi-dia-chi-2-cap 1.0.9 → 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.
Files changed (3) hide show
  1. package/dataXa.json +3 -0
  2. package/index.js +65 -44
  3. package/package.json +1 -1
package/dataXa.json CHANGED
@@ -24814,6 +24814,9 @@
24814
24814
  "ten": "Chánh Hưng",
24815
24815
  "timKiem": "chanhhung",
24816
24816
  "satNhapTu": [
24817
+ { "ten": "8", "timKiem": "8", "huyen": "8" },
24818
+ { "ten": "9", "timKiem": "9", "huyen": "8" },
24819
+ { "ten": "10", "timKiem": "10", "huyen": "8" },
24817
24820
  { "ten": "4", "timKiem": "4", "huyen": "8" },
24818
24821
  { "ten": "1", "timKiem": "1", "huyen": "8" },
24819
24822
  { "ten": "2", "timKiem": "2", "huyen": "8" },
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 globalThis !== "undefined"
16
- ? globalThis
17
- : typeof window !== "undefined"
18
- ? window
19
- : typeof global !== "undefined"
20
- ? global
21
- : null;
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 ?? ""}`.createUniqueText();
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 ?? ""}`.trim();
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 ?? ""}`.trim()).filter(Boolean);
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 ?? ""}`.replace(/\r?\n/g, "").trim();
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 ?? ""}`.trim();
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?.diaChi?.trim() || "";
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
- ...(item.dsTinh || []).flatMap((item2) => [
172
- item2.timKiem,
173
- createUniqueText(item2.ten),
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.dsTinh?.some((item2) => normalizedValue.indexOf(item2.timKiem) !== -1)
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.satNhapTu?.forEach((satNhapItem) => {
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?.ngayCap);
433
+ const issueDate = normalizeCccdIssueDate(ngayCap || (parsedQr ? parsedQr.ngayCap : ""));
418
434
  const parsedAddress = extractedText.toAddress({ ngayCap: issueDate, verifyXaName });
419
- const soNha = parsedAddress?.soNha || "";
420
- const diaChiGoc = parsedAddress?.diaChi || "";
421
- const convertedDiaChi = parsedAddress?.diaChi
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?.data || {}),
446
+ ...((parsedAddress && parsedAddress.data) || {}),
431
447
  cccdText: sourceText,
432
- cccdFields: parsedQr?.fields || [],
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?.convertDiaChiMoi &&
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?.modifyDataXa) {
501
+ if (globalTarget && globalTarget.modifyDataXa) {
486
502
  sourceDataXa = globalTarget.modifyDataXa(sourceDataXa);
487
503
  }
488
504
 
489
- if (globalTarget?.convertDiaChiMoi) {
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.dsTinh?.find((item2) => tinhCu.indexOf(item2.timKiem) !== -1)
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
- )?.ten;
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
- )?.ten
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.satNhapTu?.find(
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.satNhapTu.some(
588
+ getMergedFromList(item).some(
571
589
  (satNhapItem) =>
572
590
  satNhapItem.huyen && `${satNhapItem.huyen}`.indexOf(huyenCu) !== -1
573
591
  )
574
- )?.ten;
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]?.ten;
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.satNhapTu?.find((item2) => xaCu.indexOf(item2.timKiem) !== -1)
589
- )?.ten
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)?.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
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?.ten].filter(Boolean).join(", ");
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isofh/chuyen-doi-dia-chi-2-cap",
3
- "version": "1.0.9",
3
+ "version": "2.0.0",
4
4
  "description": "Chuan hoa dia chi hanh chinh cu sang format 2 cap moi",
5
5
  "main": "index.js",
6
6
  "files": [