From 83c056d67e63208545773fbb8d3b5a6cbe0225f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Pittet?= Date: Fri, 16 Jan 2026 16:37:23 -0800 Subject: [PATCH] Fix #183 CN municipality locality hierarchy - Restructure BJ/SH/TJ/CQ subdivisions so districts are dependent localities. - Adds Shanghai regression test. --- .../CN--15520803106bc7ab44c0a4ba2c76f0c6.json | 129 ++++++++++++++++++ .../CN--6792acc39a38c41fb7873932152c4403.json | 62 +++++++++ .../CN--77fe6ffd318f906de0ecca8ad834c875.json | 59 ++++++++ .../CN--941171ccb2dc3877024608c682ea344b.json | 59 ++++++++ resources/subdivision/CN-BJ.json | 52 +------ resources/subdivision/CN-CQ.json | 122 +---------------- resources/subdivision/CN-SH.json | 55 +------- resources/subdivision/CN-TJ.json | 52 +------ .../AddressFormatConstraintValidatorTest.php | 38 +++++- 9 files changed, 357 insertions(+), 271 deletions(-) create mode 100644 resources/subdivision/CN--15520803106bc7ab44c0a4ba2c76f0c6.json create mode 100644 resources/subdivision/CN--6792acc39a38c41fb7873932152c4403.json create mode 100644 resources/subdivision/CN--77fe6ffd318f906de0ecca8ad834c875.json create mode 100644 resources/subdivision/CN--941171ccb2dc3877024608c682ea344b.json diff --git a/resources/subdivision/CN--15520803106bc7ab44c0a4ba2c76f0c6.json b/resources/subdivision/CN--15520803106bc7ab44c0a4ba2c76f0c6.json new file mode 100644 index 00000000..aa813fcc --- /dev/null +++ b/resources/subdivision/CN--15520803106bc7ab44c0a4ba2c76f0c6.json @@ -0,0 +1,129 @@ +{ + "country_code": "CN", + "parents": [ + "CN", + "CQ", + "Chongqing Shi" + ], + "locale": "zh-Hans", + "subdivisions": { + "Banan Qu": { + "local_name": "巴南区" + }, + "Beibei Qu": { + "local_name": "北碚区" + }, + "Bishan Xian": { + "local_name": "璧山县" + }, + "Chengkou Xian": { + "local_name": "城口县" + }, + "Dadukou Qu": { + "local_name": "大渡口区" + }, + "Dazu Qu": { + "local_name": "大足区" + }, + "Dianjiang Xian": { + "local_name": "垫江县" + }, + "Fengdu Xian": { + "local_name": "丰都县" + }, + "Fengjie Xian": { + "local_name": "奉节县" + }, + "Fuling Qu": { + "local_name": "涪陵区" + }, + "Hechuan Qu": { + "local_name": "合川区" + }, + "Jiangbei Qu": { + "local_name": "江北区" + }, + "Jiangjin Qu": { + "local_name": "江津区" + }, + "Jiulongpo Qu": { + "local_name": "九龙坡区" + }, + "Kai Xian": { + "local_name": "开县" + }, + "Liangping Xian": { + "local_name": "梁平县" + }, + "Nan'an Qu": { + "local_name": "南岸区" + }, + "Nanchuan Qu": { + "local_name": "南川区" + }, + "Pengshui Xian": { + "local_code": "彭水苗族土家族自治县", + "local_name": "彭水县" + }, + "Qijiang Qu": { + "local_name": "綦江区" + }, + "Qianjiang Qu": { + "local_name": "黔江区" + }, + "Rongchang Xian": { + "local_name": "荣昌县" + }, + "Shapingba Qu": { + "local_name": "沙坪坝区" + }, + "Shizhu Xian": { + "local_code": "石柱土家族自治县", + "local_name": "石柱县" + }, + "Tongliang Xian": { + "local_name": "铜梁县" + }, + "Tongnan Xian": { + "local_name": "潼南县" + }, + "Wanzhou Qu": { + "local_name": "万州区" + }, + "Wushan Xian": { + "local_name": "巫山县" + }, + "Wuxi Xian": { + "local_name": "巫溪县" + }, + "Wulong Xian": { + "local_name": "武隆县" + }, + "Xiushan Xian": { + "local_code": "秀山土家族苗族自治县", + "local_name": "秀山县" + }, + "Yongchuan Qu": { + "local_name": "永川区" + }, + "Youyang Xian": { + "local_code": "酉阳土家族苗族自治县", + "local_name": "酉阳县" + }, + "Yubei Qu": { + "local_name": "渝北区" + }, + "Yuzhong Qu": { + "local_name": "渝中区" + }, + "Yunyang Xian": { + "local_name": "云阳县" + }, + "Changshou Qu": { + "local_name": "长寿区" + }, + "Zhong Xian": { + "local_name": "忠县" + } + } +} diff --git a/resources/subdivision/CN--6792acc39a38c41fb7873932152c4403.json b/resources/subdivision/CN--6792acc39a38c41fb7873932152c4403.json new file mode 100644 index 00000000..2a2ce327 --- /dev/null +++ b/resources/subdivision/CN--6792acc39a38c41fb7873932152c4403.json @@ -0,0 +1,62 @@ +{ + "country_code": "CN", + "parents": [ + "CN", + "SH", + "Shanghai Shi" + ], + "locale": "zh-Hans", + "subdivisions": { + "Baoshan Qu": { + "local_name": "宝山区" + }, + "Chongming Xian": { + "local_name": "崇明县" + }, + "Fengxian Qu": { + "local_name": "奉贤区" + }, + "Hongkou Qu": { + "local_name": "虹口区" + }, + "Huangpu Qu": { + "local_name": "黄浦区" + }, + "Jiading Qu": { + "local_name": "嘉定区" + }, + "Jinshan Qu": { + "local_name": "金山区" + }, + "Jing'an Qu": { + "local_name": "静安区" + }, + "Minhang Qu": { + "local_name": "闵行区" + }, + "Pudong Xinqu": { + "local_name": "浦东新区" + }, + "Putuo Qu": { + "local_name": "普陀区" + }, + "Qingpu Qu": { + "local_name": "青浦区" + }, + "Songjiang Qu": { + "local_name": "松江区" + }, + "Xuhui Qu": { + "local_name": "徐汇区" + }, + "Yangpu Qu": { + "local_name": "杨浦区" + }, + "Zhabei Qu": { + "local_name": "闸北区" + }, + "Changning Qu": { + "local_name": "长宁区" + } + } +} diff --git a/resources/subdivision/CN--77fe6ffd318f906de0ecca8ad834c875.json b/resources/subdivision/CN--77fe6ffd318f906de0ecca8ad834c875.json new file mode 100644 index 00000000..f8ed97f3 --- /dev/null +++ b/resources/subdivision/CN--77fe6ffd318f906de0ecca8ad834c875.json @@ -0,0 +1,59 @@ +{ + "country_code": "CN", + "parents": [ + "CN", + "BJ", + "Beijing Shi" + ], + "locale": "zh-Hans", + "subdivisions": { + "Changping Qu": { + "local_name": "昌平区" + }, + "Chaoyang Qu": { + "local_name": "朝阳区" + }, + "Daxing Qu": { + "local_name": "大兴区" + }, + "Dongcheng Qu": { + "local_name": "东城区" + }, + "Fangshan Qu": { + "local_name": "房山区" + }, + "Fengtai Qu": { + "local_name": "丰台区" + }, + "Haidian Qu": { + "local_name": "海淀区" + }, + "Huairou Qu": { + "local_name": "怀柔区" + }, + "Mentougou Qu": { + "local_name": "门头沟区" + }, + "Miyun Xian": { + "local_name": "密云县" + }, + "Pinggu Qu": { + "local_name": "平谷区" + }, + "Shijingshan Qu": { + "local_name": "石景山区" + }, + "Shunyi Qu": { + "local_name": "顺义区" + }, + "Tongzhou Qu": { + "local_name": "通州区" + }, + "Xicheng Qu": { + "local_name": "西城区" + }, + "Yanqing Xian": { + "local_name": "延庆县" + } + } +} diff --git a/resources/subdivision/CN--941171ccb2dc3877024608c682ea344b.json b/resources/subdivision/CN--941171ccb2dc3877024608c682ea344b.json new file mode 100644 index 00000000..4415b0c3 --- /dev/null +++ b/resources/subdivision/CN--941171ccb2dc3877024608c682ea344b.json @@ -0,0 +1,59 @@ +{ + "country_code": "CN", + "parents": [ + "CN", + "TJ", + "Tianjin Shi" + ], + "locale": "zh-Hans", + "subdivisions": { + "Baodi Qu": { + "local_name": "宝坻区" + }, + "Beichen Qu": { + "local_name": "北辰区" + }, + "Binhai Xinqu": { + "local_name": "滨海新区" + }, + "Dongli Qu": { + "local_name": "东丽区" + }, + "Heping Qu": { + "local_name": "和平区" + }, + "Hebei Qu": { + "local_name": "河北区" + }, + "Hedong Qu": { + "local_name": "河东区" + }, + "Hexi Qu": { + "local_name": "河西区" + }, + "Hongqiao Qu": { + "local_name": "红桥区" + }, + "Ji Xian": { + "local_name": "蓟县" + }, + "Jinnan Qu": { + "local_name": "津南区" + }, + "Jinghai Xian": { + "local_name": "静海县" + }, + "Nankai Qu": { + "local_name": "南开区" + }, + "Ninghe Xian": { + "local_name": "宁河县" + }, + "Wuqing Qu": { + "local_name": "武清区" + }, + "Xiqing Qu": { + "local_name": "西青区" + } + } +} diff --git a/resources/subdivision/CN-BJ.json b/resources/subdivision/CN-BJ.json index 37503146..36f13836 100644 --- a/resources/subdivision/CN-BJ.json +++ b/resources/subdivision/CN-BJ.json @@ -6,53 +6,9 @@ ], "locale": "zh-Hans", "subdivisions": { - "Changping Qu": { - "local_name": "昌平区" - }, - "Chaoyang Qu": { - "local_name": "朝阳区" - }, - "Daxing Qu": { - "local_name": "大兴区" - }, - "Dongcheng Qu": { - "local_name": "东城区" - }, - "Fangshan Qu": { - "local_name": "房山区" - }, - "Fengtai Qu": { - "local_name": "丰台区" - }, - "Haidian Qu": { - "local_name": "海淀区" - }, - "Huairou Qu": { - "local_name": "怀柔区" - }, - "Mentougou Qu": { - "local_name": "门头沟区" - }, - "Miyun Xian": { - "local_name": "密云县" - }, - "Pinggu Qu": { - "local_name": "平谷区" - }, - "Shijingshan Qu": { - "local_name": "石景山区" - }, - "Shunyi Qu": { - "local_name": "顺义区" - }, - "Tongzhou Qu": { - "local_name": "通州区" - }, - "Xicheng Qu": { - "local_name": "西城区" - }, - "Yanqing Xian": { - "local_name": "延庆县" + "Beijing Shi": { + "local_name": "北京市", + "has_children": true } } -} \ No newline at end of file +} diff --git a/resources/subdivision/CN-CQ.json b/resources/subdivision/CN-CQ.json index 139d1811..df7f3cb6 100644 --- a/resources/subdivision/CN-CQ.json +++ b/resources/subdivision/CN-CQ.json @@ -6,123 +6,9 @@ ], "locale": "zh-Hans", "subdivisions": { - "Banan Qu": { - "local_name": "巴南区" - }, - "Beibei Qu": { - "local_name": "北碚区" - }, - "Bishan Xian": { - "local_name": "璧山县" - }, - "Chengkou Xian": { - "local_name": "城口县" - }, - "Dadukou Qu": { - "local_name": "大渡口区" - }, - "Dazu Qu": { - "local_name": "大足区" - }, - "Dianjiang Xian": { - "local_name": "垫江县" - }, - "Fengdu Xian": { - "local_name": "丰都县" - }, - "Fengjie Xian": { - "local_name": "奉节县" - }, - "Fuling Qu": { - "local_name": "涪陵区" - }, - "Hechuan Qu": { - "local_name": "合川区" - }, - "Jiangbei Qu": { - "local_name": "江北区" - }, - "Jiangjin Qu": { - "local_name": "江津区" - }, - "Jiulongpo Qu": { - "local_name": "九龙坡区" - }, - "Kai Xian": { - "local_name": "开县" - }, - "Liangping Xian": { - "local_name": "梁平县" - }, - "Nan'an Qu": { - "local_name": "南岸区" - }, - "Nanchuan Qu": { - "local_name": "南川区" - }, - "Pengshui Xian": { - "local_code": "彭水苗族土家族自治县", - "local_name": "彭水县" - }, - "Qijiang Qu": { - "local_name": "綦江区" - }, - "Qianjiang Qu": { - "local_name": "黔江区" - }, - "Rongchang Xian": { - "local_name": "荣昌县" - }, - "Shapingba Qu": { - "local_name": "沙坪坝区" - }, - "Shizhu Xian": { - "local_code": "石柱土家族自治县", - "local_name": "石柱县" - }, - "Tongliang Xian": { - "local_name": "铜梁县" - }, - "Tongnan Xian": { - "local_name": "潼南县" - }, - "Wanzhou Qu": { - "local_name": "万州区" - }, - "Wushan Xian": { - "local_name": "巫山县" - }, - "Wuxi Xian": { - "local_name": "巫溪县" - }, - "Wulong Xian": { - "local_name": "武隆县" - }, - "Xiushan Xian": { - "local_code": "秀山土家族苗族自治县", - "local_name": "秀山县" - }, - "Yongchuan Qu": { - "local_name": "永川区" - }, - "Youyang Xian": { - "local_code": "酉阳土家族苗族自治县", - "local_name": "酉阳县" - }, - "Yubei Qu": { - "local_name": "渝北区" - }, - "Yuzhong Qu": { - "local_name": "渝中区" - }, - "Yunyang Xian": { - "local_name": "云阳县" - }, - "Changshou Qu": { - "local_name": "长寿区" - }, - "Zhong Xian": { - "local_name": "忠县" + "Chongqing Shi": { + "local_name": "重庆市", + "has_children": true } } -} \ No newline at end of file +} diff --git a/resources/subdivision/CN-SH.json b/resources/subdivision/CN-SH.json index 06612c20..cff8071b 100644 --- a/resources/subdivision/CN-SH.json +++ b/resources/subdivision/CN-SH.json @@ -6,56 +6,9 @@ ], "locale": "zh-Hans", "subdivisions": { - "Baoshan Qu": { - "local_name": "宝山区" - }, - "Chongming Xian": { - "local_name": "崇明县" - }, - "Fengxian Qu": { - "local_name": "奉贤区" - }, - "Hongkou Qu": { - "local_name": "虹口区" - }, - "Huangpu Qu": { - "local_name": "黄浦区" - }, - "Jiading Qu": { - "local_name": "嘉定区" - }, - "Jinshan Qu": { - "local_name": "金山区" - }, - "Jing'an Qu": { - "local_name": "静安区" - }, - "Minhang Qu": { - "local_name": "闵行区" - }, - "Pudong Xinqu": { - "local_name": "浦东新区" - }, - "Putuo Qu": { - "local_name": "普陀区" - }, - "Qingpu Qu": { - "local_name": "青浦区" - }, - "Songjiang Qu": { - "local_name": "松江区" - }, - "Xuhui Qu": { - "local_name": "徐汇区" - }, - "Yangpu Qu": { - "local_name": "杨浦区" - }, - "Zhabei Qu": { - "local_name": "闸北区" - }, - "Changning Qu": { - "local_name": "长宁区" + "Shanghai Shi": { + "local_name": "上海市", + "has_children": true } } -} \ No newline at end of file +} diff --git a/resources/subdivision/CN-TJ.json b/resources/subdivision/CN-TJ.json index 9ce9d861..02eec525 100644 --- a/resources/subdivision/CN-TJ.json +++ b/resources/subdivision/CN-TJ.json @@ -6,53 +6,9 @@ ], "locale": "zh-Hans", "subdivisions": { - "Baodi Qu": { - "local_name": "宝坻区" - }, - "Beichen Qu": { - "local_name": "北辰区" - }, - "Binhai Xinqu": { - "local_name": "滨海新区" - }, - "Dongli Qu": { - "local_name": "东丽区" - }, - "Heping Qu": { - "local_name": "和平区" - }, - "Hebei Qu": { - "local_name": "河北区" - }, - "Hedong Qu": { - "local_name": "河东区" - }, - "Hexi Qu": { - "local_name": "河西区" - }, - "Hongqiao Qu": { - "local_name": "红桥区" - }, - "Ji Xian": { - "local_name": "蓟县" - }, - "Jinnan Qu": { - "local_name": "津南区" - }, - "Jinghai Xian": { - "local_name": "静海县" - }, - "Nankai Qu": { - "local_name": "南开区" - }, - "Ninghe Xian": { - "local_name": "宁河县" - }, - "Wuqing Qu": { - "local_name": "武清区" - }, - "Xiqing Qu": { - "local_name": "西青区" + "Tianjin Shi": { + "local_name": "天津市", + "has_children": true } } -} \ No newline at end of file +} diff --git a/tests/Validator/Constraints/AddressFormatConstraintValidatorTest.php b/tests/Validator/Constraints/AddressFormatConstraintValidatorTest.php index 1591042d..a4a7ee3b 100644 --- a/tests/Validator/Constraints/AddressFormatConstraintValidatorTest.php +++ b/tests/Validator/Constraints/AddressFormatConstraintValidatorTest.php @@ -188,7 +188,8 @@ public function testChinaOK(): void $address = $address ->withCountryCode('CN') ->withAdministrativeArea('BJ') - ->withLocality('Xicheng Qu') + ->withLocality('Beijing Shi') + ->withDependentLocality('Xicheng Qu') ->withPostalCode('123456') ->withAddressLine1('Yitiao Lu') ->withGivenName('John') @@ -198,6 +199,26 @@ public function testChinaOK(): void $this->assertNoViolation(); } + /** + * @covers \CommerceGuys\Addressing\Validator\Constraints\AddressFormatConstraintValidator + */ + public function testChinaShanghai(): void + { + $address = new Address(); + $address = $address + ->withCountryCode('CN') + ->withAdministrativeArea('SH') + ->withLocality('Shanghai Shi') + ->withDependentLocality('Huangpu Qu') + ->withPostalCode('200003') + ->withAddressLine1('555 Middle Xizang Road') + ->withGivenName('John') + ->withFamilyName('Doe'); + + $this->validator->validate($address, $this->constraint); + $this->assertNoViolation(); + } + /** * @covers \CommerceGuys\Addressing\Validator\Constraints\AddressFormatConstraintValidator */ @@ -244,7 +265,8 @@ public function testChinaPostalCodeBadFormat(): void $address = $address ->withCountryCode('CN') ->withAdministrativeArea('BJ') - ->withLocality('Xicheng Qu') + ->withLocality('Beijing Shi') + ->withDependentLocality('Xicheng Qu') ->withPostalCode('InvalidValue') ->withGivenName('John') ->withFamilyName('Smith'); @@ -428,7 +450,8 @@ public function testOverriddenRequiredFields(): void $address = $address ->withCountryCode('CN') ->withAdministrativeArea('BJ') - ->withLocality('Xicheng Qu') + ->withLocality('Beijing Shi') + ->withDependentLocality('Xicheng Qu') ->withPostalCode('123456') ->withAddressLine1('Yitiao Lu'); @@ -453,7 +476,8 @@ public function testHiddenPostalCodeField(): void $address = $address ->withCountryCode('CN') ->withAdministrativeArea('BJ') - ->withLocality('Xicheng Qu') + ->withLocality('Beijing Shi') + ->withDependentLocality('Xicheng Qu') ->withAddressLine1('Yitiao Lu') ->withGivenName('John') ->withFamilyName('Smith') @@ -477,7 +501,8 @@ public function testHiddenSubdivisionField(): void $address = new Address(); $address = $address ->withCountryCode('CN') - ->withLocality('Xicheng Qu') + ->withLocality('Beijing Shi') + ->withDependentLocality('Xicheng Qu') ->withPostalCode('123456') ->withAddressLine1('Yitiao Lu') ->withGivenName('John') @@ -501,7 +526,8 @@ public function testNoPostalCodeValidation(): void $address = $address ->withCountryCode('CN') ->withAdministrativeArea('BJ') - ->withLocality('Xicheng Qu') + ->withLocality('Beijing Shi') + ->withDependentLocality('Xicheng Qu') ->withAddressLine1('Yitiao Lu') ->withGivenName('John') ->withFamilyName('Smith')