@wener/common 2.0.1 → 2.0.2
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/lib/cn/ChineseResidentIdNo.js +41 -0
- package/lib/cn/ChineseResidentIdNo.mod.js +1 -0
- package/lib/cn/{ResidentIdentityCardNumber.test.js → ChineseResidentIdNo.test.js} +7 -6
- package/lib/cn/DivisionCode.js +211 -300
- package/lib/cn/DivisionCode.mod.js +1 -0
- package/lib/cn/DivisionCode.test.js +9 -15
- package/lib/cn/{Mod11Checksum.js → Mod11.js} +1 -0
- package/lib/cn/{Mod31Checksum.js → Mod31.js} +1 -0
- package/lib/cn/UnifiedSocialCreditCode.js +130 -115
- package/lib/cn/UnifiedSocialCreditCode.mod.js +1 -0
- package/lib/cn/UnifiedSocialCreditCode.test.js +1 -1
- package/lib/cn/index.js +1 -3
- package/lib/cn/mod.js +6 -0
- package/lib/consola/formatLogObject.js +18 -6
- package/lib/dayjs/dayjs.js +38 -0
- package/lib/dayjs/formatDuration.js +58 -0
- package/lib/dayjs/formatDuration.test.js +90 -0
- package/lib/dayjs/index.js +3 -0
- package/lib/dayjs/parseDuration.js +32 -0
- package/lib/decimal/index.js +1 -0
- package/lib/decimal/parseDecimal.js +13 -0
- package/lib/{resource → foundation}/schema/SexType.js +5 -1
- package/lib/foundation/schema/index.js +1 -0
- package/lib/foundation/schema/parseSexType.js +18 -0
- package/lib/foundation/schema/types.js +5 -0
- package/lib/password/Password.js +10 -7
- package/lib/resource/ListQuery.js +119 -0
- package/lib/resource/index.js +1 -0
- package/lib/resource/schema/AnyResourceSchema.js +1 -1
- package/lib/resource/schema/index.js +5 -0
- package/lib/{resource/schema → schema}/SchemaRegistry.js +12 -5
- package/lib/schema/SchemaRegistry.mod.js +2 -0
- package/lib/schema/createSchemaData.js +1 -1
- package/lib/schema/getSchemaOptions.js +2 -2
- package/lib/schema/index.js +1 -0
- package/lib/utils/getEstimateProcessTime.js +20 -0
- package/lib/utils/index.js +1 -0
- package/package.json +23 -14
- package/src/cn/ChineseResidentIdNo.mod.ts +7 -0
- package/src/cn/ChineseResidentIdNo.test.ts +18 -0
- package/src/cn/ChineseResidentIdNo.ts +66 -0
- package/src/cn/DivisionCode.mod.ts +7 -0
- package/src/cn/DivisionCode.test.ts +3 -13
- package/src/cn/DivisionCode.ts +132 -195
- package/src/cn/{Mod11Checksum.ts → Mod11.ts} +3 -1
- package/src/cn/{Mod31Checksum.ts → Mod31.ts} +2 -0
- package/src/cn/UnifiedSocialCreditCode.mod.ts +7 -0
- package/src/cn/UnifiedSocialCreditCode.test.ts +2 -2
- package/src/cn/UnifiedSocialCreditCode.ts +105 -125
- package/src/cn/__snapshots__/ChineseResidentIdNo.test.ts.snap +14 -0
- package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +18 -12
- package/src/cn/index.ts +1 -3
- package/src/cn/mod.ts +3 -0
- package/src/consola/formatLogObject.ts +12 -4
- package/src/dayjs/dayjs.ts +40 -0
- package/src/dayjs/formatDuration.test.ts +14 -0
- package/src/dayjs/formatDuration.ts +86 -0
- package/src/dayjs/index.ts +3 -0
- package/src/dayjs/parseDuration.ts +40 -0
- package/src/decimal/index.ts +1 -0
- package/src/decimal/parseDecimal.ts +16 -0
- package/src/foundation/schema/SexType.ts +21 -0
- package/src/foundation/schema/index.ts +1 -0
- package/src/foundation/schema/parseSexType.ts +19 -0
- package/src/foundation/schema/types.ts +8 -0
- package/src/password/Password.ts +2 -2
- package/src/resource/ListQuery.ts +53 -0
- package/src/resource/index.ts +1 -0
- package/src/resource/schema/AnyResourceSchema.ts +1 -1
- package/src/resource/schema/index.ts +5 -0
- package/src/schema/SchemaRegistry.mod.ts +1 -0
- package/src/{resource/schema → schema}/SchemaRegistry.ts +12 -8
- package/src/schema/createSchemaData.ts +1 -1
- package/src/schema/getSchemaOptions.ts +2 -2
- package/src/schema/index.ts +1 -0
- package/src/utils/getEstimateProcessTime.ts +36 -0
- package/src/utils/index.ts +1 -0
- package/lib/cn/ResidentIdentityCardNumber.js +0 -50
- package/lib/cn/formatDate.js +0 -13
- package/lib/cn/parseSex.js +0 -20
- package/lib/search/AdvanceSearch.js +0 -9
- package/lib/search/AdvanceSearch.test.js +0 -435
- package/lib/search/formatAdvanceSearch.js +0 -78
- package/lib/search/index.js +0 -1
- package/lib/search/optimizeAdvanceSearch.js +0 -143
- package/lib/search/parseAdvanceSearch.js +0 -20
- package/lib/search/parser.d.js +0 -1
- package/lib/search/parser.js +0 -3088
- package/lib/search/types.d.js +0 -1
- package/src/cn/ResidentIdentityCardNumber.test.ts +0 -17
- package/src/cn/ResidentIdentityCardNumber.ts +0 -96
- package/src/cn/__snapshots__/ResidentIdentityCardNumber.test.ts.snap +0 -15
- package/src/cn/formatDate.ts +0 -12
- package/src/cn/parseSex.ts +0 -13
- package/src/resource/schema/SexType.ts +0 -13
- package/src/search/AdvanceSearch.test.ts +0 -149
- package/src/search/AdvanceSearch.ts +0 -14
- package/src/search/Makefile +0 -2
- package/src/search/__snapshots__/AdvanceSearch.test.ts.snap +0 -675
- package/src/search/formatAdvanceSearch.ts +0 -52
- package/src/search/index.ts +0 -1
- package/src/search/optimizeAdvanceSearch.ts +0 -77
- package/src/search/parseAdvanceSearch.ts +0 -23
- package/src/search/parser.d.ts +0 -8
- package/src/search/parser.js +0 -2794
- package/src/search/parser.peggy +0 -237
- package/src/search/types.d.ts +0 -45
package/src/cn/DivisionCode.ts
CHANGED
|
@@ -1,209 +1,146 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import z from 'zod/v4';
|
|
2
|
+
import type { EnumValues } from '../resource/schema';
|
|
3
|
+
|
|
4
|
+
export const DivisionLevel = Object.freeze({
|
|
5
|
+
__proto__: null,
|
|
6
|
+
Province: 'Province',
|
|
7
|
+
City: 'City',
|
|
8
|
+
County: 'County',
|
|
9
|
+
Town: 'Town',
|
|
10
|
+
Village: 'Village',
|
|
11
|
+
});
|
|
12
|
+
const DivisionLevels = [
|
|
13
|
+
DivisionLevel.Province,
|
|
14
|
+
DivisionLevel.City,
|
|
15
|
+
DivisionLevel.County,
|
|
16
|
+
DivisionLevel.Town, // 3
|
|
17
|
+
DivisionLevel.Village, // 3
|
|
18
|
+
];
|
|
19
|
+
export type DivisionLevel = EnumValues<typeof DivisionLevel>;
|
|
20
|
+
export const DivisionLevelSchema = z
|
|
21
|
+
.union([
|
|
22
|
+
z.literal(DivisionLevel.Province).describe('省级').meta({ ordinal: 1 }),
|
|
23
|
+
z.literal(DivisionLevel.City).describe('地级').meta({ ordinal: 2 }),
|
|
24
|
+
z.literal(DivisionLevel.County).describe('县级').meta({ ordinal: 3 }),
|
|
25
|
+
z.literal(DivisionLevel.Town).describe('乡镇级').meta({ ordinal: 4 }),
|
|
26
|
+
z.literal(DivisionLevel.Village).describe('村级').meta({ ordinal: 5 }),
|
|
27
|
+
])
|
|
28
|
+
.describe('行政区划级别');
|
|
29
|
+
|
|
30
|
+
export type ParsedDivisionCode = z.infer<typeof ParsedDivisionCodeSchema>;
|
|
31
|
+
export const ParsedDivisionCodeSchema = z.object({
|
|
32
|
+
code: z.string().describe('行政区划代码'),
|
|
33
|
+
name: z.string().describe('行政区划名称'),
|
|
34
|
+
level: DivisionLevelSchema,
|
|
35
|
+
codes: z.string().array(),
|
|
36
|
+
names: z.string().array(),
|
|
37
|
+
latitude: z.number().optional().describe('纬度'),
|
|
38
|
+
longitude: z.number().optional().describe('经度'),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
interface CodeName {
|
|
42
|
+
value: string;
|
|
43
|
+
name: string;
|
|
44
|
+
children?: Array<CodeName>;
|
|
45
|
+
}
|
|
11
46
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
47
|
+
export const DivisionCodeRegex =
|
|
48
|
+
/^(?<province>\d{2})(?<city>\d{2})?(?<county>\d{2})?(?<town>\d{3})?(?<village>\d{3})?$/;
|
|
49
|
+
|
|
50
|
+
const CodeNames: CodeName[] = [
|
|
51
|
+
{ value: '11', name: '北京市' },
|
|
52
|
+
{ value: '12', name: '天津市' },
|
|
53
|
+
{ value: '13', name: '河北省' },
|
|
54
|
+
{ value: '14', name: '山西省' },
|
|
55
|
+
{ value: '15', name: '内蒙古自治区' },
|
|
56
|
+
{ value: '21', name: '辽宁省' },
|
|
57
|
+
{ value: '22', name: '吉林省' },
|
|
58
|
+
{ value: '23', name: '黑龙江省' },
|
|
59
|
+
{ value: '31', name: '上海市' },
|
|
60
|
+
{ value: '32', name: '江苏省' },
|
|
61
|
+
{ value: '33', name: '浙江省' },
|
|
62
|
+
{ value: '34', name: '安徽省' },
|
|
63
|
+
{ value: '35', name: '福建省' },
|
|
64
|
+
{ value: '36', name: '江西省' },
|
|
65
|
+
{ value: '37', name: '山东省' },
|
|
66
|
+
{ value: '41', name: '河南省' },
|
|
67
|
+
{ value: '42', name: '湖北省' },
|
|
68
|
+
{ value: '43', name: '湖南省' },
|
|
69
|
+
{ value: '44', name: '广东省' },
|
|
70
|
+
{ value: '45', name: '广西壮族自治区' },
|
|
71
|
+
{ value: '46', name: '海南省' },
|
|
72
|
+
{ value: '50', name: '重庆市' },
|
|
73
|
+
{ value: '51', name: '四川省' },
|
|
74
|
+
{ value: '52', name: '贵州省' },
|
|
75
|
+
{ value: '53', name: '云南省' },
|
|
76
|
+
{ value: '54', name: '西藏自治区' },
|
|
77
|
+
{ value: '61', name: '陕西省' },
|
|
78
|
+
{ value: '62', name: '甘肃省' },
|
|
79
|
+
{ value: '63', name: '青海省' },
|
|
80
|
+
{ value: '64', name: '宁夏回族自治区' },
|
|
81
|
+
{ value: '65', name: '新疆维吾尔自治区' },
|
|
82
|
+
{ value: '71', name: '台湾省' },
|
|
83
|
+
{ value: '81', name: '香港特别行政区' },
|
|
84
|
+
{ value: '82', name: '澳门特别行政区' },
|
|
85
|
+
// 9 国外
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
export function parseDivisionCode(code: string): ParsedDivisionCode | undefined {
|
|
89
|
+
const match = DivisionCodeRegex.exec(code);
|
|
90
|
+
if (!match) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const { province, city, county, town, village } = match.groups ?? {};
|
|
94
|
+
if (!province) {
|
|
95
|
+
return undefined;
|
|
24
96
|
}
|
|
25
97
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{ value: '13', label: '河北省' },
|
|
34
|
-
{ value: '14', label: '山西省' },
|
|
35
|
-
{ value: '15', label: '内蒙古自治区' },
|
|
36
|
-
{ value: '21', label: '辽宁省' },
|
|
37
|
-
{ value: '22', label: '吉林省' },
|
|
38
|
-
{ value: '23', label: '黑龙江省' },
|
|
39
|
-
{ value: '31', label: '上海市' },
|
|
40
|
-
{ value: '32', label: '江苏省' },
|
|
41
|
-
{ value: '33', label: '浙江省' },
|
|
42
|
-
{ value: '34', label: '安徽省' },
|
|
43
|
-
{ value: '35', label: '福建省' },
|
|
44
|
-
{ value: '36', label: '江西省' },
|
|
45
|
-
{ value: '37', label: '山东省' },
|
|
46
|
-
{ value: '41', label: '河南省' },
|
|
47
|
-
{ value: '42', label: '湖北省' },
|
|
48
|
-
{ value: '43', label: '湖南省' },
|
|
49
|
-
{ value: '44', label: '广东省' },
|
|
50
|
-
{ value: '45', label: '广西壮族自治区' },
|
|
51
|
-
{ value: '46', label: '海南省' },
|
|
52
|
-
{ value: '50', label: '重庆市' },
|
|
53
|
-
{ value: '51', label: '四川省' },
|
|
54
|
-
{ value: '52', label: '贵州省' },
|
|
55
|
-
{ value: '53', label: '云南省' },
|
|
56
|
-
{ value: '54', label: '西藏自治区' },
|
|
57
|
-
{ value: '61', label: '陕西省' },
|
|
58
|
-
{ value: '62', label: '甘肃省' },
|
|
59
|
-
{ value: '63', label: '青海省' },
|
|
60
|
-
{ value: '64', label: '宁夏回族自治区' },
|
|
61
|
-
{ value: '65', label: '新疆维吾尔自治区' },
|
|
62
|
-
{ value: '71', label: '台湾省' },
|
|
63
|
-
{ value: '81', label: '香港特别行政区' },
|
|
64
|
-
{ value: '82', label: '澳门特别行政区' },
|
|
65
|
-
// 9 国外
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
export type ParsedCode = {
|
|
69
|
-
province: string;
|
|
70
|
-
city?: string;
|
|
71
|
-
county?: string;
|
|
72
|
-
town?: string;
|
|
73
|
-
village?: string;
|
|
74
|
-
codes: string[];
|
|
75
|
-
level: DivisionCodeLevel;
|
|
76
|
-
labels: string[];
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export function parse(code: string | undefined | null | number): ParsedCode | undefined {
|
|
80
|
-
if (!code) return;
|
|
81
|
-
code = String(code);
|
|
82
|
-
const match = regex.exec(code);
|
|
83
|
-
if (!match) return;
|
|
84
|
-
const { province, city, county, town, village } = match.groups ?? {};
|
|
85
|
-
if (!province) return;
|
|
86
|
-
|
|
87
|
-
let codes = [province, city, county, town, village].filter(Boolean);
|
|
88
|
-
return { province, city, county, town, village, codes: codes, level: codes.length as DivisionCodeLevel };
|
|
98
|
+
const codes = [province, city, county, town, village].filter(Boolean);
|
|
99
|
+
const names: string[] = [];
|
|
100
|
+
{
|
|
101
|
+
let name = CodeNames.find((cn) => cn.value === province);
|
|
102
|
+
if (name) {
|
|
103
|
+
names.push(name.name);
|
|
104
|
+
}
|
|
89
105
|
}
|
|
106
|
+
let level: DivisionLevel = DivisionLevels[codes.length - 1];
|
|
107
|
+
return {
|
|
108
|
+
code,
|
|
109
|
+
name: names.join(''),
|
|
110
|
+
level,
|
|
111
|
+
codes,
|
|
112
|
+
names,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
90
115
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
export function formatDivisionCode({
|
|
117
|
+
province,
|
|
118
|
+
city,
|
|
119
|
+
county,
|
|
120
|
+
town,
|
|
121
|
+
village,
|
|
122
|
+
codes = [],
|
|
123
|
+
}: {
|
|
124
|
+
province?: string | number;
|
|
125
|
+
city?: string | number;
|
|
126
|
+
county?: string | number;
|
|
127
|
+
town?: string | number;
|
|
128
|
+
village?: string | number;
|
|
129
|
+
codes?: string[];
|
|
130
|
+
}): string {
|
|
131
|
+
if (!codes.length) {
|
|
105
132
|
for (let i = 0; i < [province, city, county, town, village].length; i++) {
|
|
106
133
|
let x = [province, city, county, town, village][i];
|
|
107
134
|
if (x === undefined || x === null || x === '') {
|
|
108
135
|
break;
|
|
109
136
|
}
|
|
110
|
-
let
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// export function random(level: DivisionCodeLevel = 'County'): string {
|
|
117
|
-
// const l = DivisionCodeLevels.find((v) => v.code === level) || DivisionCodeLevels[2];
|
|
118
|
-
// const l1 = randomPick(provinces);
|
|
119
|
-
// if (l.level === 1) {
|
|
120
|
-
// return String(l1[0]);
|
|
121
|
-
// }
|
|
122
|
-
// return l1 + String(Math.floor(Math.random() * parseFloat(`1e${l.length - 2}`) - 1));
|
|
123
|
-
// }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
interface DivisionTreeNode {
|
|
127
|
-
sub: string; // sub code
|
|
128
|
-
children?: Record<string, DivisionTreeNode>;
|
|
129
|
-
|
|
130
|
-
code: string; // full code
|
|
131
|
-
name?: string; // name of division
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// export type DivisionCodeLevel = 'Village' | 'Town' | 'County' | 'City' | 'Province';
|
|
135
|
-
|
|
136
|
-
// export interface ParsedDivisionCode {
|
|
137
|
-
// level: DivisionCodeLevel;
|
|
138
|
-
// code: string;
|
|
139
|
-
// name?: string;
|
|
140
|
-
// fullName?: string;
|
|
141
|
-
// names: string[];
|
|
142
|
-
// // 村级(村委会、居委会)
|
|
143
|
-
// // 12 位
|
|
144
|
-
// village?: CodeName;
|
|
145
|
-
// // 乡级(乡镇、街道)
|
|
146
|
-
// // 9 位
|
|
147
|
-
// town?: CodeName;
|
|
148
|
-
// // 县级(区县)
|
|
149
|
-
// // 6 位 - 常用 - 2985 个
|
|
150
|
-
// county?: CodeName;
|
|
151
|
-
// // 地级(城市)
|
|
152
|
-
// // 4 位 - 343 个
|
|
153
|
-
// city?: CodeName;
|
|
154
|
-
// // 省级(省份、直辖市、自治区)
|
|
155
|
-
// // 2 位 - 32 个
|
|
156
|
-
// province: CodeName;
|
|
157
|
-
//
|
|
158
|
-
// children?: Array<{ code: string; name?: string }>;
|
|
159
|
-
// }
|
|
160
|
-
|
|
161
|
-
// export interface CodeName {
|
|
162
|
-
// code: string;
|
|
163
|
-
// name: string;
|
|
164
|
-
// }
|
|
165
|
-
|
|
166
|
-
// export function getSimpleProvinceLabel(value: string) {
|
|
167
|
-
// if (!value) {
|
|
168
|
-
// return;
|
|
169
|
-
// }
|
|
170
|
-
// let label = value;
|
|
171
|
-
// if (/[0-9]/.test(label)) {
|
|
172
|
-
// label = label.slice(0, 2);
|
|
173
|
-
// label = options.find((v) => v.value === label)?.label || label;
|
|
174
|
-
// }
|
|
175
|
-
// return label.replace(/省|市|(回族|维吾尔|壮族)?自治区|特别行政区$/, '');
|
|
176
|
-
// }
|
|
177
|
-
|
|
178
|
-
export function randomPick<T>(s: T[]) {
|
|
179
|
-
return s[Math.floor(Math.random() * s.length)];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function lookup(opts: { values: string[]; root: CodeValue[] }): { found: CodeValue[] } {
|
|
183
|
-
const { values, root } = opts;
|
|
184
|
-
const found: CodeValue[] = [];
|
|
185
|
-
let currentLevel = root;
|
|
186
|
-
|
|
187
|
-
for (const v of values) {
|
|
188
|
-
const node = currentLevel.find((n) => n.value === v);
|
|
189
|
-
if (!node) {
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
found.push(node);
|
|
194
|
-
|
|
195
|
-
if (node.children) {
|
|
196
|
-
currentLevel = node.children;
|
|
197
|
-
} else {
|
|
198
|
-
break;
|
|
137
|
+
let size = 2;
|
|
138
|
+
if (i > 2) {
|
|
139
|
+
size = 3;
|
|
140
|
+
}
|
|
141
|
+
codes.push(String(x).padStart(size, '0').slice(0, size));
|
|
199
142
|
}
|
|
200
143
|
}
|
|
201
144
|
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
interface CodeValue {
|
|
206
|
-
value: string;
|
|
207
|
-
label: string;
|
|
208
|
-
children?: Array<CodeValue>;
|
|
145
|
+
return codes.join('');
|
|
209
146
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ISO 7064:1983, MOD 11-2.
|
|
3
3
|
*/
|
|
4
4
|
export class Mod11Checksum {
|
|
5
|
-
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1];
|
|
5
|
+
protected weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1];
|
|
6
6
|
|
|
7
7
|
validate(s: string) {
|
|
8
8
|
return s.at(-1) === this.compute(s.slice(0, s.length - 1));
|
|
@@ -22,3 +22,5 @@ export class Mod11Checksum {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
export const Mod11 = new Mod11Checksum();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type ParsedUnifiedSocialCreditCode as Result,
|
|
3
|
+
UnifiedSocialCreditCodeRegex as regex,
|
|
4
|
+
ParsedUnifiedSocialCreditCodeSchema as ResultSchema,
|
|
5
|
+
parseUnifiedSocialCreditCode as parse,
|
|
6
|
+
formatUnifiedSocialCreditCode as format,
|
|
7
|
+
} from './UnifiedSocialCreditCode';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { UnifiedSocialCreditCode } from './
|
|
2
|
+
import { UnifiedSocialCreditCode } from './mod';
|
|
3
3
|
|
|
4
4
|
describe('UnifiedSocialCreditCode', () => {
|
|
5
5
|
it('should parse', () => {
|
|
@@ -9,7 +9,7 @@ describe('UnifiedSocialCreditCode', () => {
|
|
|
9
9
|
'91330106MA2CFLDG4R',
|
|
10
10
|
]) {
|
|
11
11
|
let out = UnifiedSocialCreditCode.parse(a);
|
|
12
|
-
expect(UnifiedSocialCreditCode.format(out)).toBe(a);
|
|
12
|
+
expect(UnifiedSocialCreditCode.format(out!)).toBe(a);
|
|
13
13
|
expect(out).toMatchSnapshot();
|
|
14
14
|
}
|
|
15
15
|
});
|
|
@@ -1,143 +1,123 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { Mod31 } from './Mod31';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
8
|
-
export namespace UnifiedSocialCreditCode {
|
|
9
|
-
interface CodeValue {
|
|
10
|
-
label: string;
|
|
11
|
-
children?: Record<string, CodeValue>;
|
|
12
|
-
}
|
|
4
|
+
interface CodeValue {
|
|
5
|
+
name: string;
|
|
6
|
+
children?: Record<string, CodeValue>;
|
|
7
|
+
}
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
},
|
|
9
|
+
const CodeNames: Record<string, CodeValue> = {
|
|
10
|
+
1: {
|
|
11
|
+
name: '机构编制',
|
|
12
|
+
children: {
|
|
13
|
+
1: { name: '机关' },
|
|
14
|
+
2: { name: '事业单位' },
|
|
15
|
+
3: { name: '中央编办直接管理机构编制的群众团体' },
|
|
16
|
+
9: { name: '其他' },
|
|
23
17
|
},
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
},
|
|
18
|
+
},
|
|
19
|
+
5: {
|
|
20
|
+
name: '民政',
|
|
21
|
+
children: {
|
|
22
|
+
1: { name: '社会团体' },
|
|
23
|
+
2: { name: '民办非企业单位' },
|
|
24
|
+
3: { name: '基金会' },
|
|
25
|
+
9: { name: '其他' },
|
|
32
26
|
},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
},
|
|
27
|
+
},
|
|
28
|
+
9: {
|
|
29
|
+
name: '工商',
|
|
30
|
+
children: {
|
|
31
|
+
1: { name: '企业' },
|
|
32
|
+
2: { name: '个体工商户' },
|
|
33
|
+
3: { name: '农民专业合作社' },
|
|
34
|
+
9: { name: '其他' },
|
|
41
35
|
},
|
|
42
|
-
|
|
43
|
-
}
|
|
36
|
+
},
|
|
37
|
+
Y: { name: '其他', children: { 1: { name: '其他' } } },
|
|
38
|
+
};
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
export type ParsedUnifiedSocialCreditCode = z.infer<typeof ParsedUnifiedSocialCreditCodeSchema>;
|
|
41
|
+
export const ParsedUnifiedSocialCreditCodeSchema = z
|
|
42
|
+
.object({
|
|
43
|
+
raw: z.string().length(18).describe('统一社会信用代码'),
|
|
44
|
+
registrationAuthorityCode: z.string().length(1).describe('登记管理部门代码'),
|
|
45
|
+
entityCategoryCode: z.string().length(1).describe('机构类别代码'),
|
|
46
|
+
adminDivisionCode: z.string().length(6).describe('登记管理机关行政区划码 '),
|
|
47
|
+
organizationCode: z.string().length(9).describe('主体标识码/组织机构代码'),
|
|
48
|
+
checkDigit: z.string().length(1).describe('校验码'),
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
codes: z.string().array().describe('代码数组'),
|
|
51
|
+
names: z.string().array().describe('代码名称'),
|
|
52
|
+
valid: z.boolean().optional().describe('是否有效'),
|
|
53
|
+
})
|
|
54
|
+
.describe('统一社会信用代码');
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
let bureau = s[0];
|
|
51
|
-
let subjectType = s[1];
|
|
52
|
-
let division = s.slice(2, 8); // 第3~8位,共6位,正确
|
|
53
|
-
let subject = s.slice(8, 17); // 第9~17位,共9位
|
|
54
|
-
let checksum = s.slice(17, 18); // 第18位,校验码
|
|
56
|
+
export const UnifiedSocialCreditCodeRegex = /^([159][1239]|Y1)[0-9]{6}[0-9A-HJ-NP-RTUWXY]{10}$/;
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
l1 && labels.push(l1?.label);
|
|
59
|
-
let l2 = l1?.children?.[subjectType];
|
|
60
|
-
l2 && labels.push(l2?.label);
|
|
58
|
+
export function parseUnifiedSocialCreditCode(s: string): ParsedUnifiedSocialCreditCode | undefined {
|
|
59
|
+
if (!s || s.length !== 18) return undefined;
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
checksum,
|
|
68
|
-
codes: [bureau, subjectType, division, subject],
|
|
69
|
-
labels,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
61
|
+
const registrationAuthorityCode = s[0];
|
|
62
|
+
const entityCategoryCode = s[1];
|
|
63
|
+
const adminDivisionCode = s.slice(2, 8);
|
|
64
|
+
const organizationCode = s.slice(8, 17);
|
|
65
|
+
const checkDigit = s[17];
|
|
72
66
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
subjectType,
|
|
76
|
-
division,
|
|
77
|
-
subject,
|
|
78
|
-
checksum,
|
|
79
|
-
}: {
|
|
80
|
-
bureau: string;
|
|
81
|
-
subjectType: string;
|
|
82
|
-
division: string;
|
|
83
|
-
subject: string;
|
|
84
|
-
checksum?: string;
|
|
85
|
-
}): string {
|
|
86
|
-
const base = `${bureau}${subjectType}${division}${subject}`;
|
|
87
|
-
checksum ||= Checksum.compute(base);
|
|
88
|
-
return `${base}${checksum}`;
|
|
89
|
-
}
|
|
67
|
+
const codes = [registrationAuthorityCode, entityCategoryCode, adminDivisionCode, organizationCode, checkDigit];
|
|
68
|
+
const names: string[] = [];
|
|
90
69
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
} else if (delta > 0 && sp[i] === 30) {
|
|
98
|
-
sp[i] = 0;
|
|
99
|
-
} else if (delta < 0 && sp[i] === 0) {
|
|
100
|
-
sp[i] = 30;
|
|
101
|
-
}
|
|
70
|
+
const bureau = CodeNames[registrationAuthorityCode];
|
|
71
|
+
if (bureau) {
|
|
72
|
+
names.push(bureau.name);
|
|
73
|
+
const subject = bureau.children?.[entityCategoryCode];
|
|
74
|
+
if (subject) {
|
|
75
|
+
names.push(subject.name);
|
|
102
76
|
}
|
|
103
|
-
return sp.map((v) => Checksum.chars[v]).join('');
|
|
104
77
|
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* 解析后的统一社会信用代码信息
|
|
109
|
-
*/
|
|
110
|
-
interface ParsedUnifiedSocialCreditCode {
|
|
111
|
-
/**
|
|
112
|
-
* @title 登记管理部门代码
|
|
113
|
-
* 对应统一社会信用代码的第1位字符,用于标识发证机构类别(登记管理部门)。
|
|
114
|
-
*/
|
|
115
|
-
bureau: string;
|
|
116
78
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* @title 主体标识码
|
|
131
|
-
* 对应统一社会信用代码的第9至第17位字符,用于标识特定的市场主体或法人单位。
|
|
132
|
-
*/
|
|
133
|
-
subject: string;
|
|
79
|
+
return {
|
|
80
|
+
raw: s,
|
|
81
|
+
registrationAuthorityCode,
|
|
82
|
+
entityCategoryCode,
|
|
83
|
+
adminDivisionCode,
|
|
84
|
+
organizationCode,
|
|
85
|
+
checkDigit,
|
|
86
|
+
codes,
|
|
87
|
+
names,
|
|
88
|
+
valid: UnifiedSocialCreditCodeRegex.test(s) && Mod31.validate(s),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
134
91
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
92
|
+
export function formatUnifiedSocialCreditCode({
|
|
93
|
+
registrationAuthorityCode,
|
|
94
|
+
entityCategoryCode,
|
|
95
|
+
adminDivisionCode,
|
|
96
|
+
organizationCode,
|
|
97
|
+
checkDigit,
|
|
98
|
+
}: {
|
|
99
|
+
registrationAuthorityCode: string;
|
|
100
|
+
entityCategoryCode: string;
|
|
101
|
+
adminDivisionCode: string;
|
|
102
|
+
organizationCode: string;
|
|
103
|
+
checkDigit?: string;
|
|
104
|
+
}): string {
|
|
105
|
+
const base = `${registrationAuthorityCode}${entityCategoryCode}${adminDivisionCode}${organizationCode}`;
|
|
106
|
+
checkDigit ||= Mod31.compute(base);
|
|
107
|
+
return `${base}${checkDigit}`;
|
|
108
|
+
}
|
|
140
109
|
|
|
141
|
-
|
|
142
|
-
|
|
110
|
+
export function next(s: string, delta: number = 1) {
|
|
111
|
+
const sp = s.split('').map((v) => Mod31.numbers[v]);
|
|
112
|
+
for (let i = sp.length - 1; i >= 0; i--) {
|
|
113
|
+
if ((delta > 0 && sp[i] < 30) || (delta < 0 && sp[i] > 0)) {
|
|
114
|
+
sp[i] += delta;
|
|
115
|
+
break;
|
|
116
|
+
} else if (delta > 0 && sp[i] === 30) {
|
|
117
|
+
sp[i] = 0;
|
|
118
|
+
} else if (delta < 0 && sp[i] === 0) {
|
|
119
|
+
sp[i] = 30;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return sp.map((v) => Mod31.chars[v]).join('');
|
|
143
123
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ChineseResidentIdNo > should parse 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"addressCode": "110105",
|
|
6
|
+
"age": 76,
|
|
7
|
+
"birthDate": "1949-12-31",
|
|
8
|
+
"checkDigit": 10,
|
|
9
|
+
"raw": "11010519491231002X",
|
|
10
|
+
"sequence": 2,
|
|
11
|
+
"sex": "Male",
|
|
12
|
+
"valid": true,
|
|
13
|
+
}
|
|
14
|
+
`;
|