Pārlūkot izejas kodu

会员管理代码

wwh 1 nedēļu atpakaļ
vecāks
revīzija
b6c9ce52bc

+ 1 - 1
baqing-shop/src/main/java/com/ruoyi/web/modules/merchant/controller/MerchantController.java

@@ -55,7 +55,7 @@ public class MerchantController extends BaseController
55 55
     public AjaxResult add(@RequestBody MerchantCreateDTO merchant)
56 56
     {
57 57
         Map<String, Object> data = merchantService.insertMerchant(merchant, getUsername());
58
-        AjaxResult result = AjaxResult.success("请尽快在编辑中完善商户经营信息后再开设店铺");
58
+        AjaxResult result = AjaxResult.success("保存成功,已可开设店铺");
59 59
         result.put(AjaxResult.DATA_TAG, data);
60 60
         return result;
61 61
     }

+ 23 - 19
baqing-shop/src/main/java/com/ruoyi/web/modules/merchant/service/impl/MerchantServiceImpl.java

@@ -94,12 +94,12 @@ public class MerchantServiceImpl implements IMerchantService
94 94
     @Transactional(rollbackFor = Exception.class)
95 95
     public Map<String, Object> insertMerchant(MerchantCreateDTO merchant, String operator)
96 96
     {
97
-        stripBizFieldsOnInsert(merchant);
98 97
         validateSubjectOnInsert(merchant);
98
+        validateBizOnInsert(merchant);
99 99
         Date now = new Date();
100 100
         merchant.setCertStatus(MerchantConstants.CERT_NORMAL);
101 101
         merchant.setCertTime(now);
102
-        merchant.setBizComplete(MerchantConstants.BIZ_INCOMPLETE);
102
+        merchant.setBizComplete(BizCompleteEvaluator.evaluateCompleteFlag(merchant));
103 103
         merchant.setShopCount(0);
104 104
         merchant.setCreateBy(operator);
105 105
         merchantMapper.insert(merchant);
@@ -297,21 +297,18 @@ public class MerchantServiceImpl implements IMerchantService
297 297
         return vo;
298 298
     }
299 299
 
300
-    private void stripBizFieldsOnInsert(BizMerchant m)
300
+    private void validateBizOnInsert(BizMerchant m)
301 301
     {
302
-        m.setMerchantName(null);
303
-        m.setServicePhone(null);
304
-        m.setBizRegionCode(null);
305
-        m.setBizRegionName(null);
306
-        m.setBizDetailAddress(null);
307
-        m.setContactName(null);
308
-        m.setContactPhone(null);
309
-        m.setContactEmail(null);
310
-        m.setBankName(null);
311
-        m.setBankBranch(null);
312
-        m.setBankAccount(null);
313
-        m.setBusinessLicense(null);
314
-        m.setAccountPermit(null);
302
+        List<String> pending = BizCompleteEvaluator.pendingFields(m);
303
+        if (!pending.isEmpty())
304
+        {
305
+            throw new ServiceException("经营信息不完整,无法保存:" + String.join("、", pending));
306
+        }
307
+        if (StringUtils.isNotEmpty(m.getMerchantName())
308
+                && merchantMapper.countByMerchantName(m.getMerchantName(), null) > 0)
309
+        {
310
+            throw new ServiceException("商户名称已存在");
311
+        }
315 312
     }
316 313
 
317 314
     private void validateSubjectOnInsert(BizMerchant m)
@@ -331,8 +328,11 @@ public class MerchantServiceImpl implements IMerchantService
331 328
             {
332 329
                 throw new ServiceException("请输入企业名称");
333 330
             }
334
-            if (StringUtils.isNotEmpty(m.getCreditCode())
335
-                    && merchantMapper.countByCreditCode(m.getCreditCode(), null) > 0)
331
+            if (StringUtils.isEmpty(m.getCreditCode()))
332
+            {
333
+                throw new ServiceException("请输入统一社会信用代码");
334
+            }
335
+            if (merchantMapper.countByCreditCode(m.getCreditCode(), null) > 0)
336 336
             {
337 337
                 throw new ServiceException("该统一社会信用代码已入驻");
338 338
             }
@@ -343,7 +343,11 @@ public class MerchantServiceImpl implements IMerchantService
343 343
             {
344 344
                 throw new ServiceException("请输入姓名");
345 345
             }
346
-            if (StringUtils.isNotEmpty(m.getIdCardNo()) && merchantMapper.countByIdCardNo(m.getIdCardNo(), null) > 0)
346
+            if (StringUtils.isEmpty(m.getIdCardNo()))
347
+            {
348
+                throw new ServiceException("请输入证件号码");
349
+            }
350
+            if (merchantMapper.countByIdCardNo(m.getIdCardNo(), null) > 0)
347 351
             {
348 352
                 throw new ServiceException("该证件号码已入驻");
349 353
             }

+ 1 - 1
baqing-shop/src/test/java/com/ruoyi/web/modules/merchant/controller/MerchantControllerTest.java

@@ -132,7 +132,7 @@ class MerchantControllerTest
132 132
                 .content(objectMapper.writeValueAsString(dto)))
133 133
                 .andExpect(status().isOk())
134 134
                 .andExpect(jsonPath("$.code").value(200))
135
-                .andExpect(jsonPath("$.msg").value("请尽快在编辑中完善商户经营信息后再开设店铺"));
135
+                .andExpect(jsonPath("$.msg").value("保存成功,已可开设店铺"));
136 136
     }
137 137
 
138 138
     @Test

+ 55 - 8
baqing-shop/src/test/java/com/ruoyi/web/modules/merchant/service/MerchantServiceImplTest.java

@@ -46,15 +46,11 @@ class MerchantServiceImplTest
46 46
     private MerchantServiceImpl merchantService;
47 47
 
48 48
     @Test
49
-    void insert_setsCertNormalAndBizIncomplete()
49
+    void insert_setsCertNormalAndBizComplete_whenBizFieldsComplete()
50 50
     {
51
-        MerchantCreateDTO dto = new MerchantCreateDTO();
52
-        dto.setMerchantType(MerchantConstants.TYPE_PERSON);
53
-        dto.setPersonName("张三");
54
-        dto.setIdCardNo("110101199001011234");
55
-        dto.setBindType("SYS_USER");
56
-        dto.setBindUserId(10L);
51
+        MerchantCreateDTO dto = fullPersonCreateDto();
57 52
         when(merchantMapper.countByIdCardNo(any(), eq(null))).thenReturn(0);
53
+        when(merchantMapper.countByMerchantName(any(), eq(null))).thenReturn(0);
58 54
         when(merchantMapper.insert(any())).thenAnswer(inv -> {
59 55
             BizMerchant m = inv.getArgument(0);
60 56
             m.setMerchantId(1L);
@@ -66,10 +62,61 @@ class MerchantServiceImplTest
66 62
         verify(accountBindService).bindAccountOnMerchantCreate(eq(1L), eq(dto), eq("admin"));
67 63
 
68 64
         assertEquals(MerchantConstants.CERT_NORMAL, dto.getCertStatus());
69
-        assertEquals(MerchantConstants.BIZ_INCOMPLETE, dto.getBizComplete());
65
+        assertEquals(MerchantConstants.BIZ_COMPLETE, dto.getBizComplete());
70 66
         assertEquals(0, dto.getShopCount());
71 67
     }
72 68
 
69
+    @Test
70
+    void insert_rejectsIncompleteBiz()
71
+    {
72
+        MerchantCreateDTO dto = new MerchantCreateDTO();
73
+        dto.setMerchantType(MerchantConstants.TYPE_PERSON);
74
+        dto.setPersonName("张三");
75
+        dto.setIdCardNo("110101199001011234");
76
+        dto.setBindType("SYS_USER");
77
+        dto.setBindUserId(10L);
78
+        when(merchantMapper.countByIdCardNo(any(), eq(null))).thenReturn(0);
79
+
80
+        ServiceException ex = assertThrows(ServiceException.class,
81
+                () -> merchantService.insertMerchant(dto, "admin"));
82
+        assertTrue(ex.getMessage().contains("经营信息不完整"));
83
+        verify(merchantMapper, never()).insert(any());
84
+    }
85
+
86
+    @Test
87
+    void insert_rejectsDuplicateIdCard()
88
+    {
89
+        MerchantCreateDTO dto = fullPersonCreateDto();
90
+        when(merchantMapper.countByIdCardNo(any(), eq(null))).thenReturn(1);
91
+
92
+        ServiceException ex = assertThrows(ServiceException.class,
93
+                () -> merchantService.insertMerchant(dto, "admin"));
94
+        assertEquals("该证件号码已入驻", ex.getMessage());
95
+        verify(merchantMapper, never()).insert(any());
96
+    }
97
+
98
+    private MerchantCreateDTO fullPersonCreateDto()
99
+    {
100
+        MerchantCreateDTO dto = new MerchantCreateDTO();
101
+        dto.setMerchantType(MerchantConstants.TYPE_PERSON);
102
+        dto.setPersonName("张三");
103
+        dto.setIdCardNo("110101199001011234");
104
+        dto.setMerchantName("张三农资");
105
+        dto.setServicePhone("400-800");
106
+        dto.setBizRegionCode("110");
107
+        dto.setBizRegionName("北京");
108
+        dto.setBizDetailAddress("路1号");
109
+        dto.setContactName("张三");
110
+        dto.setContactPhone("13800138000");
111
+        dto.setContactEmail("a@b.com");
112
+        dto.setBankName("工行");
113
+        dto.setBankBranch("支行");
114
+        dto.setBankAccount("6222021234567890");
115
+        dto.setBindType("SYS_USER");
116
+        dto.setBindUserId(10L);
117
+        return dto;
118
+    }
119
+
73 120
     @Test
74 121
     void update_cancelledMerchant_rejected()
75 122
     {

+ 1 - 1
doc/平台后台/关联需求分析.md

@@ -72,7 +72,7 @@
72 72
 
73 73
 | 模块 | 端 | 核心职责 | 明确不做 |
74 74
 |------|-----|----------|----------|
75
-| **商户管理** | 平台 | 主体入驻(仅主体资质)、补全经营信息、认证状态、删除商户主体 | 不配置商家登录账号;不管理店铺/商品/分类 |
75
+| **商户管理** | 平台 | 添加/维护商户(主体+经营信息)、认证状态、删除商户主体 | 不配置商家登录账号;不管理店铺/商品/分类 |
76 76
 | **店铺管理** | 平台 | 为合格商户开店、店铺开业/停业、**配置商户级经营账号**、删店;商家端可改店资料(列表同源) | 不维护商品分类;店铺策略见《店铺设置》;不审核商品 |
77 77
 | **店铺设置** | 平台 | 全平台 **商品默认审核**、**子管理员上限**(针对所有店铺) | 非逐店配置;见 `组织管理/店铺设置/` |
78 78
 | **入驻审核** | 平台 + C 端 | 用户商城入驻申请、平台审核→公示→首店创建 | 不替代商户管理日常维护;协议见内容管理 |

+ 1 - 1
doc/平台后台/组织管理/入驻审核/商户入驻审核功能需求.md

@@ -317,7 +317,7 @@
317 317
 
318 318
 ```text
319 319
 路径 A:C 端 → 入驻协议 → 申请 →【待审】→【公示】→ 商户 + 首店 + 绑会员账号
320
-路径 B:平台 → 商户管理(仅主体)→ 编辑经营信息 → 店铺管理开店
320
+路径 B:平台 → 商户管理(主体+经营完整)→ 店铺管理开店
321 321
 ```
322 322
 
323 323
 ---

+ 16 - 17
doc/平台后台/组织管理/商户管理/商户管理前端技术方案.md

@@ -1,6 +1,6 @@
1 1
 # 商户管理 — 前端技术方案
2 2
 
3
-> **依据:** 《商户管理功能需求.md》v1.7、《商户管理技术方案.md》v1.7  
3
+> **依据:** 《商户管理功能需求.md》v1.8、《商户管理技术方案.md》v1.8  
4 4
 > **前端规范:** `doc/前端设计/前端设计.md`  
5 5
 > **范围:** 仅 **ruoyi-ui 平台管理端** 列表、添加、编辑、详情;商家端、C 端 **不在 ruoyi-ui 实现**。  
6 6
 > **实现状态:** 页面与 API 封装已落地,待后端 `/agri/merchant` 联调。
@@ -66,7 +66,7 @@
66 66
 │   └── 操作:详情、编辑(canEdit)、删除(canDelete)
67 67
 ├── 添加/编辑弹窗(860px · el-tabs)
68 68
 │   ├── Tab「主体资质」:个人 / 企业字段 + 证件照上传;企业注册地区 el-cascader
69
-│   ├── Tab「商户经营信息」(仅编辑):名称、经营地区 cascader、联系人、结算等
69
+│   ├── Tab「商户经营信息」:名称、经营地区 cascader、联系人、结算等(**新增/编辑均展示**)
70 70
 │   └── Tab「绑定经营账号」(仅新增):管理员 / 会员远程搜索
71 71
 └── 详情抽屉(detail.vue · 72% 宽)
72 72
     ├── 概要、主体资质、经营信息、关联店铺
@@ -106,7 +106,7 @@
106 106
 | 场景 | 组件 | 数据源 | 落库字段 |
107 107
 |------|------|--------|----------|
108 108
 | 企业注册地址 | `form.regRegionCascader` | `getRegionTree()` | `regRegionCode`(区县 `code`)、`regRegionName`(省/市/区县 `/` 连接) |
109
-| 经营地址(编辑 Tab) | `form.bizRegionCascader` | 同上 | `bizRegionCode`、`bizRegionName` |
109
+| 经营地址(新增/编辑 Tab) | `form.bizRegionCascader` | 同上 | `bizRegionCode`、`bizRegionName` |
110 110
 | 详细地址 | `el-input` | — | `companyDetailAddress` / `bizDetailAddress` |
111 111
 
112 112
 级联配置:`value: code`、`label: name`、`children: children`、`emitPath: true`;支持 `filterable`、`clearable`。编辑回显按已存 `*RegionCode` 在树中查找路径;提交前剔除 `regRegionCascader` / `bizRegionCascader` 仅 UI 字段。
@@ -121,7 +121,7 @@
121 121
 |----------|------|------|----------|
122 122
 | `listMerchant` | GET | `/list` | 列表、检索、分页 |
123 123
 | `getMerchant` | GET | `/{merchantId}` | 编辑回显、详情 |
124
-| `addMerchant` | POST | `/` | 添加(主体 + 绑定) |
124
+| `addMerchant` | POST | `/` | 添加(主体 + 经营 + 绑定) |
125 125
 | `updateMerchant` | PUT | `/` | 编辑(主体 + 经营) |
126 126
 | `updateMerchantCertStatus` | PUT | `/{merchantId}/certStatus` | 详情改认证 |
127 127
 | `deleteMerchantCheck` | GET | `/{merchantId}/deleteCheck` | 可选预检 |
@@ -157,13 +157,12 @@
157 157
 | contactName, contactPhone | 未完善显示「—」 |
158 158
 | canEdit, canDelete | 后端布尔,控制按钮显隐 |
159 159
 
160
-### 6.4 新增请求体要点(v1.5 / v1.6
160
+### 6.4 新增请求体要点(v1.8
161 161
 
162
-- **仅** 主体资质 + `bindType`(`SYS_USER` / `MEMBER`)+ `bindUserId` 或 `bindMemberId`
163
-- **不含** 经营信息字段
164
-- **平台新增最小必填**(`validateSubjectOnInsert`):
165
-  - 个人:仅 `personName`
166
-  - 企业:仅 `legalName` + `companyName`
162
+- **主体资质** + **商户经营信息**(§9.2 全部必填项)+ `bindType`(`SYS_USER` / `MEMBER`)+ `bindUserId` 或 `bindMemberId`
163
+- **平台新增必填**(前后端一致):
164
+  - 个人:`personName`、`idCardNo` + §9.2 个人经营字段
165
+  - 企业:`legalName`、`companyName`、`creditCode` + §9.2 企业经营字段(含营业执照、开户许可证)
167 166
 - **v1.5 新增主体字段**:`idCardType` / `legalIdCardType`(1 大陆身份证 / 2 来往内地通行证)、`regRegionCode` / `regRegionName`(v1.7 由级联写入)、`corpBankAccount`、法人证件有效期区间等
168 167
 - **会员绑定(v1.6)**:登录名=会员名称、管理员姓名=会员昵称;密码复制会员 `sys_user.password`;**不传** `sysUserInitPassword`
169 168
 - **管理员绑定(v1.6)**:检索范围 `role_key=merchant`;登录名=`user_name`、管理员姓名=`nick_name`
@@ -196,8 +195,8 @@
196 195
 | response.data | 前端行为 |
197 196
 |---------------|----------|
198 197
 | `warnExpired` | `msgWarning` 证件/营业期限过期 |
199
-| `bizCompleteChanged` | `msgSuccess`「已可开设店铺」 |
200
-| 新增成功 | 固定提示「请尽快在编辑中完善商户经营信息后再开设店铺」 |
198
+| `bizCompleteChanged` | `msgSuccess`「已可开设店铺」(编辑) |
199
+| 新增成功 | 固定提示「**保存成功,已可开设店铺**」(`response.msg`) |
201 200
 
202 201
 ---
203 202
 
@@ -205,8 +204,8 @@
205 204
 
206 205
 | 场景 | 前端行为 |
207 206
 |------|----------|
208
-| 添加 Tab | 默认「主体资质」;切换个人/企业重置 subject 字段 |
209
-| 新增校验 | 个人仅姓名必填;企业仅法人姓名+企业名称必填;证件号/信用代码选填 |
207
+| 添加 Tab | 默认「主体资质」;含「商户经营信息」「绑定经营账号」;切换个人/企业重置 subject 字段 |
208
+| 新增校验 | 个人:姓名+证件号+§9.2 经营字段;企业:法人姓名+企业名称+信用代码+§9.2 经营字段(含证照);绑定必填 |
210 209
 | 绑定搜索 | `el-select` + `remote` + `filterable`;keyword 空不请求 |
211 210
 | 编辑 Tab | 「主体资质」+「商户经营信息」;无绑定 Tab |
212 211
 | 注册/经营地址 | 企业主体 Tab 选注册地区;编辑经营 Tab 选经营地区;均为 `el-cascader` + 详细地址输入 |
@@ -241,13 +240,13 @@ v-hasPermi="['agri:merchant:cert']"
241 240
 
242 241
 ## 10. 联调检查清单
243 242
 
244
-- [ ] 新增个人仅填姓名可提交;企业仅法人姓名+企业名称可提交  
243
+- [ ] 新增个人须填姓名+证件号+完整经营信息;企业须法人+企业名称+信用代码+完整经营信息  
245 244
 - [ ] 绑定会员无初始密码字段;admin/member 远程搜索展示正确  
246 245
 - [ ] 删除 deleteCheck 阻断并展示 reasons  
247 246
 - [ ] 详情展示 idCardType、regRegion、bizRegion、corpBankAccount、法人完整字段  
248 247
 - [ ] 菜单组件路径 `agri/org/merchant/index` 可打开  
249 248
 - [ ] 列表分页、检索、canEdit/canDelete 正确  
250
-- [ ] 新增仅主体 + 绑定;成功后提示完善经营信息  
249
+- [ ] 新增主体+经营+绑定;成功后提示「保存成功,已可开设店铺」  
251 250
 - [ ] 编辑补全经营信息后 `bizCompleteChanged`  
252 251
 - [ ] 详情 `allowedCertStatuses` 流转与二次确认  
253 252
 - [ ] 证件号/信用代码编辑页 disabled  
@@ -277,5 +276,5 @@ v-hasPermi="['agri:merchant:cert']"
277 276
 
278 277
 ---
279 278
 
280
-*文档版本:v1.3 · ruoyi-ui · 关联《商户管理功能需求.md》v1.7 / 《商户管理技术方案.md》v1.7*
279
+*文档版本:v1.4 · ruoyi-ui · 关联《商户管理功能需求.md》v1.8 / 《商户管理技术方案.md》v1.8*
281 280
 

+ 40 - 37
doc/平台后台/组织管理/商户管理/商户管理功能需求.md

@@ -2,13 +2,14 @@
2 2
 
3 3
 > 本文档在《商户管理功能需求-草稿》基础上整理,并关联《农资商城web》目录下店铺管理、商品管理等模块需求做边界与流程对齐。  
4 4
 > 范围:平台侧 **商户管理** 功能需求;不涉及数据库结构、接口定义及技术实现细节。  
5
+> **v1.8:** 平台端 **添加商户** 须 **主体 + 经营信息完整** 方可保存;证件号/统一社会信用代码在 `biz_merchant` 已存在则拒绝;保存成功 `biz_complete=1`,可直接开设店铺。C 端入驻流程不变。  
5 6
 > **v1.7:** 企业 **注册地址**、个人/企业 **经营地址** 的省市区选项统一由 **`GET /agri/region/tree`**(平台端)获取;落库 `reg_region_*` / `biz_region_*`。  
6 7
 > **v1.6:** 绑定经营账号角色权限字符统一为 **merchant** / **member**(替代历史数字编码);会员绑定 **不新建 sys_user**,追加 merchant 角色并复制密码。  
7
-> **v1.5:** 明确 **移动端 C 端** 与 **平台端** 入驻字段差异;C 端须完整填写并走 **审核+公示**;平台端仅主体最小必填 + 强制绑定账号;**数据库字段注释** 以 `sql/biz_merchant.sql` 为准。  
8
+> **v1.5:** 明确 **移动端 C 端** 与 **平台端** 入驻字段差异;C 端须完整填写并走 **审核+公示**;平台端须完整填写主体+经营信息并强制绑定账号;**数据库字段注释** 以 `sql/biz_merchant.sql` 为准。  
8 9
 > **v1.5:** 对齐 `biz_merchant_account` 新结构:绑定仅写关联行(`account_id=user_id`,`shop_id=NULL`);登录凭证在 `sys_user`。  
9 10
 > **v1.4:** 平台 **新增商户** 须强制绑定 **平台管理员账号** 或 **C 端会员账号**,并同步创建该商户下首条 **商户经营账号**(`biz_merchant_account`)。  
10 11
 > **v1.3:** 除入驻绑定外,各模块 **不展开** 会员注册、会员运营等独立能力。  
11
-> **v1.2:** 商户入驻 **仅采集主体资质**;经营与结算信息在 **编辑商户** 中补全
12
+> **v1.2:** 历史版本曾规定平台入驻仅采集主体资质;**v1.8 起** 平台添加须主体+经营信息一并完整提交;经营信息仍可在 **编辑商户** 中维护
12 13
 
13 14
 ---
14 15
 
@@ -24,7 +25,7 @@
24 25
 
25 26
 ```text
26 27
 平台运营
27
-    └── 商户管理(本模块)── 创建/维护商户主体(本期:仅主体资质入驻
28
+    └── 商户管理(本模块)── 创建/维护商户(主体资质 + 经营信息
28 29
             └── 店铺管理 ── 按商户开设店铺(店铺经营账号在店铺侧配置,见店铺管理需求)
29 30
                     └── 商品管理 ── 店铺下商品上架与审核
30 31
                     └── 商品分类 ── 店铺侧分类维护
@@ -41,7 +42,7 @@
41 42
 
42 43
 | 角色 | 说明 |
43 44
 |------|------|
44
-| 平台管理员 | 商户列表、**主体入驻**、详情、认证状态、编辑(含补全经营信息)、删除 |
45
+| 平台管理员 | 商户列表、**添加商户(主体+经营)**、详情、认证状态、编辑、删除 |
45 46
 
46 47
 ---
47 48
 
@@ -51,12 +52,12 @@
51 52
 |------|------|
52 53
 | 商户 | 平台入驻的经营主体,分 **个人**、**企业** |
53 54
 | 主体资质 | 本期入驻 **必须** 完成的法定/身份资料:个人为 **个人信息**;企业为 **法定代表人信息 + 企业信息** |
54
-| 商户经营信息 | 商户名称、客服电话、经营地址、联系人、结算银行等;**本期不入驻表单**,在 **编辑商户** 中补全 |
55
+| 商户经营信息 | 商户名称、客服电话、经营地址、联系人、结算银行等;**平台添加时必填**(§6.6);**编辑** 中可维护 |
55 56
 | 商户所属单位 | 列表展示:个人 = **姓名**;企业 = **企业名称**(来自主体资质) |
56
-| 商户名称 | 对外经营名称;在 **编辑** 中填写;未填时列表可用所属单位代显或显示「待完善」 |
57
+| 商户名称 | 对外经营名称;**添加/编辑** 中填写;平台唯一 |
57 58
 | 认证状态 | 正常 / 已冻结 / 已注销;新建提交后默认 **正常** |
58 59
 | 认证时间 | 见第 7.3 节 |
59
-| 资料完整度 | **主体资质**(入驻即有)+ **商户经营信息**(编辑补全);**开店前** 须后者完整 |
60
+| 资料完整度 | **主体资质** + **商户经营信息**;平台 **添加** 成功时两者均须完整(`biz_complete=1`) |
60 61
 | 已绑定店铺数量 | 该商户下未删除店铺数 |
61 62
 | 商户经营账号 | 商家端登录账号;**新增商户成功时自动创建 1 条**(见第 6.7 节) |
62 63
 | 绑定类型 | 新增时二选一:**平台管理员** / **会员** |
@@ -69,7 +70,7 @@
69 70
 ```text
70 71
 商户管理
71 72
 ├── 商户列表(含高级检索)
72
-├── 添加商户(本期:主体资质 + 强制绑定经营账号)
73
+├── 添加商户(主体资质 + 商户经营信息 + 强制绑定经营账号)
73 74
 │   ├── 个人入驻 / 企业入驻
74 75
 │   └── 绑定经营账号(平台管理员 或 会员)
75 76
 ├── 查看详情(含认证状态调整)
@@ -120,43 +121,44 @@
120 121
 
121 122
 ---
122 123
 
123
-## 6. 添加商户(平台端 · 仅主体最小必填)
124
+## 6. 添加商户(平台端 · 主体 + 经营信息完整必填)
124 125
 
125
-新增时 **必须** 选择 **个人 / 企业** 主体类型,**必须** 绑定经营账号(§6.7);**无需审核**;**不强制** 填写完整商户经营信息(可在编辑中补全)
126
+新增时 **必须** 选择 **个人 / 企业** 主体类型,**必须** 填写完整 **主体资质** 与 **商户经营信息**(§6.6),**必须** 绑定经营账号(§6.7);**无需审核**。
126 127
 
127 128
 ### 6.1 公共流程
128 129
 
129 130
 ```text
130 131
 选择主体类型(个人 / 企业)
131 132
     → 填写主体资质(见 6.2 / 6.3)
133
+    → 填写商户经营信息(见 6.6)
132 134
     → 选择绑定类型并检索选定账号(见 6.7)
133 135
     → 提交
134
-    → 系统校验(6.4、6.5、6.7)
135
-    ├── 失败 → 逐项提示
136
-    └── 成功 → 认证状态=正常,记录认证时间
136
+    → 系统校验(6.4、6.5、6.6、6.7)
137
+    ├── 失败 → 逐项提示(含经营信息缺失字段清单)
138
+    └── 成功 → 认证状态=正常,记录认证时间,biz_complete=1
137 139
             → 创建该商户下首条商户经营账号
138
-            → 提示:「请尽快在编辑中完善商户经营信息后再开设店铺」
140
+            → 提示:「保存成功,已可开设店铺」
139 141
 ```
140 142
 
141
-### 6.2 个人入驻 — 平台端填写项(最小必填)
143
+### 6.2 个人入驻 — 平台端填写项
142 144
 
143 145
 | 区块 | 字段 | 平台端 |
144 146
 |------|------|--------|
145
-| 个人信息 | 姓名 | **必填** |
146
-| 个人信息 | 其余(证件、地址、联系人、银行等) | **选填** |
147
-| 商户经营信息 | 全部 | **不填**(编辑补全) |
147
+| 个人信息 | 姓名、证件号码 | **必填** |
148
+| 个人信息 | 其余(证件、地址等) | **选填** |
149
+| 商户经营信息 | 全部(§6.6) | **必填** |
148 150
 
149 151
 > **对比移动端:** C 端个人入驻须完整填写个人信息 + 经营信息 + 店铺信息,并走 **入驻审核 + 公示**(见《商户入驻审核功能需求》v1.1)。
150 152
 
151
-### 6.3 企业入驻 — 平台端填写项(最小必填)
153
+### 6.3 企业入驻 — 平台端填写项
152 154
 
153 155
 | 区块 | 字段 | 平台端 |
154 156
 |------|------|--------|
155 157
 | 法人信息 | 法人姓名 | **必填** |
156 158
 | 法人信息 | 其余 | **选填** |
157
-| 企业信息 | 企业名称 | **必填** |
158
-| 企业信息 | 其余(信用代码、注册地址等) | **选填** |
159
-| 商户经营信息 | 全部 | **不填**(编辑补全) |
159
+| 企业信息 | 企业名称、统一社会信用代码 | **必填** |
160
+| 企业信息 | 其余(注册地址等) | **选填** |
161
+| 商户经营信息 | 全部(§6.6,含营业执照、开户许可证) | **必填** |
160 162
 
161 163
 > **对比移动端:** C 端企业须完整填写企业信息 + 法人信息 + 经营信息 + 店铺信息,并走 **入驻审核 + 公示**。
162 164
 
@@ -169,16 +171,16 @@
169 171
 
170 172
 | 校验项 | 规则 | 提示示例 |
171 173
 |--------|------|----------|
172
-| 统一社会信用代码 | 企业类型;未删除企业商户不可重复 | 该统一社会信用代码已入驻 |
173
-
174
-> **商户名称** 入驻时不采集;在 **编辑** 填写时校验平台唯一(第 9 节)。
174
+| 证件号码 | 个人类型 **必填**;未删除个人商户不可重复 | 该证件号码已入驻 |
175
+| 统一社会信用代码 | 企业类型 **必填**;未删除企业商户不可重复 | 该统一社会信用代码已入驻 |
176
+| 商户名称 | 经营信息完整判定项;平台不可重复 | 商户名称已存在 |
175 177
 
176 178
 ### 6.6 资料完整度与开店前置
177 179
 
178 180
 | 资料块 | 完成时机 | 开店要求 |
179 181
 |--------|----------|----------|
180
-| 主体资质 | **入驻** 提交成功 | 已有 |
181
-| 商户经营信息 | **编辑商户** 中填写并保存 | **开设店铺前必须完整** |
182
+| 主体资质 | **添加** 提交成功 | 已有 |
183
+| 商户经营信息 | **添加** 提交成功(须完整) | **已有**(`biz_complete=1`) |
182 184
 
183 185
 **商户经营信息 — 完整判定(全部有值且校验通过):**
184 186
 
@@ -387,10 +389,10 @@
387 389
 | R4 | 个人/企业类型创建后不可互转 |
388 390
 | R5 | 认证状态仅在 **查看详情** 修改 |
389 391
 | R6 | 开店可选商户:未删除 + **正常** + **经营信息完整** |
390
-| R7 | **商户名称** 在编辑时校验平台唯一;企业 **统一社会信用代码** 入驻时唯一 |
392
+| R7 | **商户名称** 平台唯一(添加/编辑);个人 **证件号码**、企业 **统一社会信用代码** 添加时必填且唯一 |
391 393
 | R8 | 删除为逻辑删除,满足第 10 节 |
392 394
 | R9 | 商户认证变更不自动停业、不下架商品 |
393
-| R10 | 入驻 **仅采主体资质**;经营与结算信息 **编辑补全** |
395
+| R10 | 平台 **添加** 须 **主体 + 经营信息完整**;不完整不可保存;主体证件号/信用代码在库中已存在不可保存 |
394 396
 | R11 | 主体关键标识(证件号、统一社会信用代码)入驻后 **不可改** |
395 397
 
396 398
 ---
@@ -400,16 +402,16 @@
400 402
 ### 12.1 商户入驻 → 补全资料 → 开店
401 403
 
402 404
 ```text
403
-平台:添加商户 → 主体资质 + 绑定管理员/会员 → 正常 + 首条经营账号
404
-    ↓
405
-平台:编辑商户 → 补全商户经营信息 → 保存
405
+平台:添加商户 → 主体资质 + 经营信息 + 绑定管理员/会员 → 正常 + biz_complete=1 + 首条经营账号
406 406
407 407
 平台:店铺管理 → 添加店铺 → 选择商户(可继续维护经营账号)
408
-    → 校验经营信息完整
408
+    → 校验经营信息完整(添加成功即满足)
409 409
410 410
 店铺创建成功;已绑定店铺数量 +1
411 411
 ```
412 412
 
413
+(若历史数据或编辑后经营信息不完整,须 **编辑商户** 补全后再开店。)
414
+
413 415
 ### 12.2 商户冻结 / 注销
414 416
 
415 417
 (与 v1.1 相同。)
@@ -420,8 +422,8 @@
420 422
 
421 423
 | 场景 | 要求 |
422 424
 |------|------|
423
-| 添加商户 | **主体资质** + **绑定类型**(管理员/会员)+ 远程搜索选人;**无** 经营结算字段 |
424
-| 添加成功 | 提示去 **编辑** 完善经营信息后再开店 |
425
+| 添加商户 | **主体资质** + **商户经营信息** + **绑定类型**(管理员/会员)+ 远程搜索选人 |
426
+| 添加成功 | 提示 **「保存成功,已可开设店铺」** |
425 427
 | 编辑商户 | **主体资质** + **商户经营信息**;认证状态只读;企业注册地址/经营地址省市区通过 §6.8 接口选择 |
426 428
 | 列表 | **联系人/联系人手机**(来自经营信息);无会员相关列 |
427 429
 | 详情 | 经营信息未完成时 **待完善** 提示 |
@@ -439,7 +441,7 @@
439 441
 | 【个人】 | `person_name`, `id_card_type`, `id_card_no`, `birth_date`, `id_valid_*`, `residence_address`, `gender`, `id_card_front/back` | `id_card_type`:1大陆身份证 2来往内地通行证 |
440 442
 | 【企业·法人】 | `legal_*`, `corp_bank_account`, `account_permit` | 对公账号、开户许可证 |
441 443
 | 【企业】 | `company_name`, `credit_code`, `reg_region_*`, `company_detail_address`, `business_scope`, `license_valid_*` | 注册地址:`reg_region_code/name`(§6.8 区县 code + 省/市/区 name 拼接)+ 详细地址 |
442
-| 经营信息 | `merchant_name`, `service_phone`, `biz_region_*`, `contact_*`, `bank_*`, `business_license` | 经营地址:`biz_region_code/name`(§6.8);平台新增可不填 |
444
+| 经营信息 | `merchant_name`, `service_phone`, `biz_region_*`, `contact_*`, `bank_*`, `business_license` | 经营地址:`biz_region_code/name`(§6.8);平台 **添加时必填** |
443 445
 | 经营账号 | `biz_merchant_account`(`account_id`、`merchant_id`、`shop_id`)+ `sys_user` 登录凭证 | 见 §6.7;**不在** `biz_shop` |
444 446
 
445 447
 **枚举约定:** `gender`/`legal_gender`:0男 1女;`*_valid_type`:1区间 2长期;`del_flag`:0存在 2删除。
@@ -456,9 +458,10 @@
456 458
 | **v1.3.1** | **取消** 单商户最多 3 店限制;`shop_count` 仅统计 |
457 459
 | **v1.4** | 新增商户强制绑定平台管理员或会员,并创建首条 `biz_merchant_account` |
458 460
 | **v1.5** | 平台/移动端字段差异定稿;§15 数据库字段与 SQL 对齐 |
461
+| **v1.8** | 平台添加须主体+经营信息完整;`biz_complete=1` 方可保存成功;证件号/信用代码重复拒绝 |
459 462
 | **v1.7** | 注册地址、经营地址省市区统一 **`GET /agri/region/tree`** 选择(§6.8) |
460 463
 | **v1.6** | 角色权限字符统一为 **merchant** / **member**;会员绑定密码复制会员 `sys_user.password`(不使用初始密码字段) |
461 464
 
462 465
 ---
463 466
 
464
-*文档版本:v1.7 · 关联《商户管理技术方案.md》v1.7、《商户入驻审核功能需求.md》v1.1、《店铺管理功能需求.md》v1.3.6*
467
+*文档版本:v1.8 · 关联《商户管理技术方案.md》v1.8、《商户入驻审核功能需求.md》v1.1、《店铺管理功能需求.md》v1.3.6*

+ 49 - 26
doc/平台后台/组织管理/商户管理/商户管理技术方案.md

@@ -1,6 +1,7 @@
1 1
 # 商户管理 — 技术方案
2 2
 
3
-> **依据:** 《商户管理功能需求.md》v1.7  
3
+> **依据:** 《商户管理功能需求.md》v1.8  
4
+> **v1.8:** 平台 **POST /** 须 **主体 + 经营信息完整**(`validateBizOnInsert` + `BizCompleteEvaluator`);证件号/信用代码必填且唯一;成功 `biz_complete=1`;msg「保存成功,已可开设店铺」。  
4 5
 > **v1.7:** 省市区三级数据 **`GET /agri/region/tree`**(平台)、**`GET /api/region/tree`**(C 端);商户 **注册地址** `reg_region_*`、**经营地址** `biz_region_*` 落库约定见 §9.9。  
5 6
 > **v1.7:** 对齐 `biz_merchant_account` 新结构:`account_id=sys_user.user_id`;绑定仅 insert 关联行,登录凭证在 `sys_user`。
6 7
 > **v1.6:** 角色 **role_key=merchant/member**;`MerchantBindMapper` 严格按 `role_key` 过滤;会员绑定复用会员 `sys_user` 并追加 merchant 角色;`sysUserInitPassword` DTO 字段 **后端未使用**。  
@@ -183,11 +184,12 @@ biz_merchant(商户)
183 184
 
184 185
 ### 3.4 新增 `POST /`
185 186
 
186
-- Body:`MerchantCreateDTO` = 主体字段(§9.3)+ 绑定字段(§9.8)。拒绝经营字段入库。  
187
-- **平台主体校验**(`MerchantServiceImpl.validateSubjectOnInsert`):个人 **仅姓名** 必填;企业 **仅法人姓名 + 企业名称** 必填;信用代码有值才验唯一;经营信息 **非必填**。  
188
-- 成功:`cert_status=0`, `cert_time=now()`, `biz_complete=0`;同事务调用 `IMerchantAccountBindService.bindAccountOnMerchantCreate`。  
187
+- Body:`MerchantCreateDTO` = 主体字段(§9.3)+ **经营信息字段**(§9.2)+ 绑定字段(§9.8)。  
188
+- **主体校验**(`MerchantServiceImpl.validateSubjectOnInsert`):个人 **姓名 + 证件号码** 必填;企业 **法人姓名 + 企业名称 + 统一社会信用代码** 必填;证件号/信用代码在 `biz_merchant` 已存在则拒绝。  
189
+- **经营校验**(`validateBizOnInsert`):`BizCompleteEvaluator.pendingFields` 须为空,否则抛 `经营信息不完整,无法保存:{字段清单}`;**商户名称** 须平台唯一。  
190
+- 成功:`cert_status=0`, `cert_time=now()`, **`biz_complete=1`**;同事务调用 `IMerchantAccountBindService.bindAccountOnMerchantCreate`。  
189 191
 - 过期:`data.warnExpired=true`, `data.expiredItems=[...]`,**仍 200 成功**(绑定已执行)。  
190
-- 响应 msg:「请尽快在编辑中完善商户经营信息后再开设店铺」。
192
+- 响应 msg:「**保存成功,已可开设店铺**」。
191 193
 
192 194
 ### 3.4.1 管理员选项 `GET /adminUserOptions`
193 195
 
@@ -278,10 +280,10 @@ biz_merchant(商户)
278 280
 | R4 | update 忽略 `merchantType` |
279 281
 | R5 | `certStatus` 仅 `PUT .../certStatus` |
280 282
 | R6 | selectList 三条件过滤 |
281
-| R7 | 编辑校验 `merchant_name` 唯一;入驻校验 `credit_code` 唯一 |
283
+| R7 | insert 校验 `merchant_name`、`id_card_no`、`credit_code` 唯一;update 校验 `merchant_name` 唯一 |
282 284
 | R8 | deleteCheck + DELETE + OrderFacade |
283 285
 | R9 | 无级联改 shop/goods |
284
-| R10 | POST 主体白名单 |
286
+| R10 | POST 须 **主体 + 经营信息完整**;`BizCompleteEvaluator` 判定;不完整拒绝 |
285 287
 | R11 | SQL 不更新 `id_card_no`、`credit_code` |
286 288
 
287 289
 ---
@@ -303,8 +305,8 @@ biz_merchant(商户)
303 305
 
304 306
 | 文档 | 版本 |
305 307
 |------|------|
306
-| 商户管理功能需求.md | v1.7 |
307
-| 商户管理测试用例.md | v1.4 |
308
+| 商户管理功能需求.md | v1.8 |
309
+| 商户管理测试用例.md | v1.5 |
308 310
 | 店铺管理功能需求/技术方案 | v1.3.6 / v1.2.5 |
309 311
 | 商品分类功能需求/技术方案 | v1.3.1 / v1.1 |
310 312
 | 商品管理功能需求/技术方案 | v1.3.3 / v1.2 |
@@ -355,7 +357,7 @@ biz_merchant(商户)
355 357
 
356 358
 ### 9.3 请求 JSON 样例
357 359
 
358
-**个人 — 新增 `POST /`(主体)**
360
+**个人 — 新增 `POST /`(主体 + 经营 + 绑定)**
359 361
 
360 362
 ```json
361 363
 {
@@ -369,34 +371,52 @@ biz_merchant(商户)
369 371
   "residenceAddress": "北京市朝阳区xxx",
370 372
   "gender": "0",
371 373
   "idCardFront": "/profile/upload/2025/01/a.jpg",
372
-  "idCardBack": "/profile/upload/2025/01/b.jpg"
374
+  "idCardBack": "/profile/upload/2025/01/b.jpg",
375
+  "merchantName": "张三农资店",
376
+  "servicePhone": "400-800-1234",
377
+  "bizRegionCode": "110105",
378
+  "bizRegionName": "北京市/朝阳区",
379
+  "bizDetailAddress": "xx路1号",
380
+  "contactName": "张三",
381
+  "contactPhone": "13800138000",
382
+  "contactEmail": "zhang@example.com",
383
+  "bankName": "中国工商银行",
384
+  "bankBranch": "朝阳支行",
385
+  "bankAccount": "622202xxxxxxxxxxxx",
386
+  "bindType": "SYS_USER",
387
+  "bindUserId": 10
373 388
 }
374 389
 ```
375 390
 
376
-**企业 — 新增 `POST /`**
391
+**企业 — 新增 `POST /`(主体 + 经营 + 绑定)**
377 392
 
378 393
 ```json
379 394
 {
380 395
   "merchantType": "2",
381 396
   "legalName": "李四",
382 397
   "legalIdCardNo": "110101198001011234",
383
-  "legalBirthDate": "1980-01-01",
384
-  "legalIdValidType": "2",
385
-  "legalIdValidStart": "2015-01-01",
386
-  "legalIdValidEnd": null,
387
-  "legalResidence": "北京市海淀区xxx",
388
-  "legalGender": "0",
389
-  "legalIdCardFront": "/profile/upload/legal_f.jpg",
390
-  "legalIdCardBack": "/profile/upload/legal_b.jpg",
391
-  "legalExtraPhoto": null,
392 398
   "companyName": "某某农资有限公司",
393 399
   "creditCode": "91110000MA01234567",
394
-  "regAddress": "北京市西城区xxx",
400
+  "regRegionCode": "110101",
401
+  "regRegionName": "北京市/市辖区/东城区",
395 402
   "companyDetailAddress": "xx大厦10层",
396
-  "businessScope": "农资销售",
397
-  "licenseValidType": "1",
403
+  "licenseValidType": "2",
398 404
   "licenseValidStart": "2020-01-01",
399
-  "licenseValidEnd": "2030-12-31"
405
+  "merchantName": "某某农资",
406
+  "servicePhone": "400-800-5678",
407
+  "bizRegionCode": "110101",
408
+  "bizRegionName": "北京市/市辖区/东城区",
409
+  "bizDetailAddress": "yy路2号",
410
+  "contactName": "李四",
411
+  "contactPhone": "13900139000",
412
+  "contactEmail": "li@example.com",
413
+  "bankName": "中国建设银行",
414
+  "bankBranch": "东城支行",
415
+  "bankAccount": "622700xxxxxxxxxxxx",
416
+  "businessLicense": "/profile/upload/license.jpg",
417
+  "accountPermit": "/profile/upload/permit.jpg",
418
+  "bindType": "MEMBER",
419
+  "bindMemberId": 20
400 420
 }
401 421
 ```
402 422
 
@@ -439,8 +459,11 @@ biz_merchant(商户)
439 459
 
440 460
 | 场景 | msg |
441 461
 |------|-----|
462
+| 经营信息不完整 | 经营信息不完整,无法保存:{缺失字段} |
442 463
 | 信用代码重复 | 该统一社会信用代码已入驻 |
443 464
 | 证件号重复 | 该证件号码已入驻 |
465
+| 证件号未填 | 请输入证件号码 |
466
+| 信用代码未填 | 请输入统一社会信用代码 |
444 467
 | 商户名称重复 | 商户名称已存在 |
445 468
 | 已注销不可编辑 | 商户已注销,不可编辑 |
446 469
 | 不可改认证 | 请在查看详情中修改认证状态 |
@@ -591,4 +614,4 @@ SQL:`mapper/merchant/MerchantBindMapper.xml`
591 614
 
592 615
 ---
593 616
 
594
-*文档版本:v1.7 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 关联《商户管理功能需求.md》v1.7、《商户管理测试用例.md》v1.4*
617
+*文档版本:v1.8 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 关联《商户管理功能需求.md》v1.8、《商户管理测试用例.md》v1.5*

+ 44 - 44
doc/平台后台/组织管理/商户管理/商户管理测试用例.md

@@ -1,6 +1,6 @@
1 1
 # 商户管理 — 测试用例
2 2
 
3
-> **依据:** 《商户管理功能需求.md》v1.7、《商户管理技术方案.md》v1.7  
3
+> **依据:** 《商户管理功能需求.md》v1.8、《商户管理技术方案.md》v1.8  
4 4
 > **范围:** 平台管理端 **商户管理** 模块(列表、入驻含 **绑定经营账号**、详情、编辑、认证、删除、协作接口);**省市区依赖** `GET /agri/region/tree`(§6.8 / §9.9)  
5 5
 > **排除:** C 端商城、会员模块独立 CRUD、商品/店铺/分类/订单模块的独立功能用例(订单 Facade 未建时按桩 `false` 测删商户正向)  
6 6
 > **环境:** RuoYi v3.9.2 平台端;接口基路径 `/agri/merchant`;UI 默认 `http://{host}/` + 农资管理菜单(以实际路由为准)
@@ -71,7 +71,7 @@
71 71
 | **测试步骤** | 评估完整度 |
72 72
 | **预期结果** | `biz_complete=0`;待完善含「经营地址」 |
73 73
 
74
-### MER-UT-005 新增入驻写入认证正常与认证时间
74
+### MER-UT-005 新增入驻写入认证正常、biz_complete=1 与认证时间
75 75
 
76 76
 | 要素 | 内容 |
77 77
 |------|------|
@@ -79,23 +79,23 @@
79 79
 | **测试项** | 新增 Service |
80 80
 | **测试类型** | 单元测试 |
81 81
 | **测试工具** | JUnit + Mockito |
82
-| **测试目的** | 验证 R3:入驻即正常并记 `cert_time` |
83
-| **前置条件** | Mock Mapper;合法个人主体 DTO |
84
-| **测试步骤** | `insertMerchant(dto)`(`MerchantCreateDTO` 含 `bindType`+`bindUserId`) |
85
-| **预期结果** | `cert_status=0`;`cert_time` 非空;`shop_count=0`;`biz_complete=0`;调用 `accountBindService.bindAccountOnMerchantCreate` |
82
+| **测试目的** | 验证 R3 + R10:入驻即正常、经营完整、`biz_complete=1` |
83
+| **前置条件** | Mock Mapper;合法个人主体+经营完整 DTO |
84
+| **测试步骤** | `insertMerchant(dto)`(含 `bindType`+`bindUserId` 及 §9.2 全部经营字段) |
85
+| **预期结果** | `cert_status=0`;`cert_time` 非空;`shop_count=0`;**`biz_complete=1`**;调用 `accountBindService.bindAccountOnMerchantCreate` |
86 86
 
87
-### MER-UT-006 新增拒绝经营字段入库
87
+### MER-UT-006 新增经营信息不完整拒绝保存
88 88
 
89 89
 | 要素 | 内容 |
90 90
 |------|------|
91 91
 | **测试模块** | 商户管理 |
92
-| **测试项** | 新增白名单 |
92
+| **测试项** | 新增 Service |
93 93
 | **测试类型** | 单元测试 |
94 94
 | **测试工具** | JUnit |
95
-| **测试目的** | 验证 R10:POST 仅主体字段 |
96
-| **前置条件** | 新增 DTO 含 `merchantName`、`contactPhone` |
97
-| **测试步骤** | 调用新增;检查持久化实体 |
98
-| **预期结果** | 经营字段不入库或为 null;或 Service 抛业务异常拒绝 |
95
+| **测试目的** | 验证 R10:经营信息不完整不可保存 |
96
+| **前置条件** | 新增 DTO 仅主体+绑定,缺经营字段 |
97
+| **测试步骤** | 调用 `insertMerchant` |
98
+| **预期结果** | 抛 `ServiceException`;msg 含「经营信息不完整,无法保存」及缺失字段;**不** 调用 `insert` |
99 99
 
100 100
 ### MER-UT-007 编辑忽略 `merchantType` 变更
101 101
 
@@ -538,10 +538,10 @@
538 538
 | **测试项** | POST / |
539 539
 | **测试类型** | 接口测试 |
540 540
 | **测试工具** | Apifox |
541
-| **测试目的** | 验证个人入驻主流程 |
541
+| **测试目的** | 验证个人入驻主流程(主体+经营完整) |
542 542
 | **前置条件** | `agri:merchant:add`;证件号未占用 |
543
-| **测试步骤** | `POST /` 提交 §9.3 个人样例 + `bindType=SYS_USER`+`bindUserId` |
544
-| **预期结果** | `code=200`;msg 含「请尽快在编辑中完善商户经营信息」;详情 `certStatus=0`,`bizComplete=0`;`biz_merchant_account` 已 1 条 |
543
+| **测试步骤** | `POST /` 提交 §9.3 个人完整样例 + `bindType=SYS_USER`+`bindUserId` |
544
+| **预期结果** | `code=200`;msg「**保存成功,已可开设店铺**」;详情 `certStatus=0`,**`bizComplete=1`**;`biz_merchant_account` 已 1 条 |
545 545
 
546 546
 ### MER-API-006 企业商户新增成功
547 547
 
@@ -551,10 +551,10 @@
551 551
 | **测试项** | POST / |
552 552
 | **测试类型** | 接口测试 |
553 553
 | **测试工具** | Apifox |
554
-| **测试目的** | 验证企业入驻主流程 |
554
+| **测试目的** | 验证企业入驻主流程(主体+经营完整) |
555 555
 | **前置条件** | 信用代码未占用 |
556
-| **测试步骤** | `POST /` 提交 §9.3 企业样例 JSON |
557
-| **预期结果** | `code=200`;`companyName` 入库;列表 `unitName` 为企业名称 |
556
+| **测试步骤** | `POST /` 提交 §9.3 企业完整样例 JSON + 绑定字段 |
557
+| **预期结果** | `code=200`;`bizComplete=1`;`companyName` 入库;列表 `unitName` 为企业名称 |
558 558
 
559 559
 ### MER-API-007 新增缺少必填字段失败
560 560
 
@@ -566,8 +566,8 @@
566 566
 | **测试工具** | Apifox |
567 567
 | **测试目的** | 验证必填校验 |
568 568
 | **前置条件** | — |
569
-| **测试步骤** | `POST /` 仅传 `merchantType=1`,无姓名证件 |
570
-| **预期结果** | `code≠200` 或字段级错误;未新增记录 |
569
+| **测试步骤** | `POST /` 仅传 `merchantType=1`+`personName`,无证件号与经营字段 |
570
+| **预期结果** | `code≠200`;msg 含必填/经营不完整提示;未新增记录 |
571 571
 
572 572
 ### MER-API-008 企业信用代码重复
573 573
 
@@ -582,7 +582,7 @@
582 582
 | **测试步骤** | 再次 `POST` 相同 `creditCode` |
583 583
 | **预期结果** | 失败;msg「该统一社会信用代码已入驻」 |
584 584
 
585
-### MER-API-009 新增携带经营字段被拒绝或忽略
585
+### MER-API-009 新增经营信息不完整拒绝
586 586
 
587 587
 | 要素 | 内容 |
588 588
 |------|------|
@@ -590,10 +590,10 @@
590 590
 | **测试项** | POST / |
591 591
 | **测试类型** | 接口测试 |
592 592
 | **测试工具** | Apifox |
593
-| **测试目的** | 验证 R10 |
593
+| **测试目的** | 验证 R10:缺经营字段不可保存 |
594 594
 | **前置条件** | — |
595
-| **测试步骤** | 1. `POST /` 主体合法且带 `merchantName`、`bankAccount` 2. `GET /{id}` 查详情 |
596
-| **预期结果** | 成功时经营字段仍为空/`bizComplete=0`;或整单拒绝 |
595
+| **测试步骤** | `POST /` 主体+绑定合法,但缺 `merchantName` 或 `contactPhone` 等经营字段 |
596
+| **预期结果** | 失败;msg 含「经营信息不完整,无法保存」及缺失项;未新增记录 |
597 597
 
598 598
 ### MER-API-010 证件过期仍 200 且带 warn
599 599
 
@@ -617,7 +617,7 @@
617 617
 | **测试类型** | 接口测试 |
618 618
 | **测试工具** | Apifox |
619 619
 | **测试目的** | 验证详情契约 |
620
-| **前置条件** | 仅主体、未补经营的新商户 |
620
+| **前置条件** | 编辑后清空某经营字段的商户,或历史不完整数据 |
621 621
 | **测试步骤** | `GET /agri/merchant/{id}` |
622 622
 | **预期结果** | `bizComplete=0`;`pendingBizFields` 为非空中文字符串数组 |
623 623
 
@@ -1150,7 +1150,7 @@
1150 1150
 | **测试类型** | 接口测试 |
1151 1151
 | **测试工具** | Apifox |
1152 1152
 | **测试目的** | 验证 §9.9 落库与 `biz_complete` |
1153
-| **前置条件** | 正常商户仅主体;从 MER-API-051 选取区县 |
1153
+| **前置条件** | 经营信息不完整的正常商户;从 MER-API-051 选取区县 |
1154 1154
 | **测试步骤** | `PUT /` 提交 `bizRegionCode=110101`、`bizRegionName=北京市/市辖区/东城区` 及其余经营必填项 |
1155 1155
 | **预期结果** | 成功;库表 `biz_region_code/name` 一致;`biz_complete=1` 时 msg 可含「已可开设店铺」 |
1156 1156
 
@@ -1199,7 +1199,7 @@
1199 1199
 | **测试步骤** | 认证状态选「已冻结」→ 查询 |
1200 1200
 | **预期结果** | 列表均为已冻结;标签样式正确 |
1201 1201
 
1202
-### MER-UI-004 添加个人商户-仅主体表单
1202
+### MER-UI-004 添加个人商户-含主体与经营表单
1203 1203
 
1204 1204
 | 要素 | 内容 |
1205 1205
 |------|------|
@@ -1207,10 +1207,10 @@
1207 1207
 | **测试项** | 添加商户 |
1208 1208
 | **测试类型** | 界面测试 |
1209 1209
 | **测试工具** | Playwright |
1210
-| **测试目的** | 验证入驻表单范围 R10 |
1210
+| **测试目的** | 验证 R10 新增表单范围 |
1211 1211
 | **前置条件** | 有 add 权限 |
1212
-| **测试步骤** | 1. 点击「新增」 2. 选个人 3. 查看表单字段 |
1213
-| **预期结果** | **无** 经营结算字段;有个人证件、证照;有 **绑定类型**(管理员/会员)及远程搜索框 |
1212
+| **测试步骤** | 1. 点击「新增」 2. 选个人 3. 查看 Tab 与表单项 |
1213
+| **预期结果** | 有「主体资质」「**商户经营信息**」「绑定经营账号」三个 Tab;经营 Tab 含商户名称、经营地区、联系人、银行等必填项 |
1214 1214
 
1215 1215
 ### MER-UI-005 添加个人商户成功提示
1216 1216
 
@@ -1222,8 +1222,8 @@
1222 1222
 | **测试工具** | Playwright |
1223 1223
 | **测试目的** | 验证入驻成功交互 |
1224 1224
 | **前置条件** | 合法个人数据;证照已上传 |
1225
-| **测试步骤** | 填写必填项→提交 |
1226
-| **预期结果** | 成功提示含「完善商户经营信息」「开设店铺」;列表新增行认证=正常;商户名称显示待完善或所属单位 |
1225
+| **测试步骤** | 填写主体+经营+绑定全部必填项→提交 |
1226
+| **预期结果** | 成功提示「**保存成功,已可开设店铺**」;列表新增行认证=正常;商户名称显示所填名称 |
1227 1227
 
1228 1228
 ### MER-UI-006 添加企业商户成功
1229 1229
 
@@ -1235,8 +1235,8 @@
1235 1235
 | **测试工具** | Playwright |
1236 1236
 | **测试目的** | 验证企业入驻 UI |
1237 1237
 | **前置条件** | 未占用信用代码 |
1238
-| **测试步骤** | 选企业→填法人+企业信息→提交 |
1239
-| **预期结果** | 成功;列表所属单位=企业名称 |
1238
+| **测试步骤** | 选企业→填法人+企业+经营信息+绑定→提交 |
1239
+| **预期结果** | 成功;`bizComplete=1`;列表所属单位=企业名称 |
1240 1240
 
1241 1241
 ### MER-UI-007 添加必填项为空提交校验
1242 1242
 
@@ -1248,7 +1248,7 @@
1248 1248
 | **测试工具** | Playwright |
1249 1249
 | **测试目的** | 验证前端/后端必填提示 |
1250 1250
 | **前置条件** | 打开新增页 |
1251
-| **测试步骤** | 不填姓名直接提交 |
1251
+| **测试步骤** | 不填姓名或经营必填项直接提交 |
1252 1252
 | **预期结果** | 表单项标红或 Toast 提示;不关闭弹窗/不跳转 |
1253 1253
 
1254 1254
 ### MER-UI-008 查看详情-经营待完善展示
@@ -1260,7 +1260,7 @@
1260 1260
 | **测试类型** | 界面测试 |
1261 1261
 | **测试工具** | Playwright |
1262 1262
 | **测试目的** | 验证 §8.1 待完善 |
1263
-| **前置条件** | 仅主体入驻的商户 |
1263
+| **前置条件** | 经营信息不完整的商户(编辑清空某字段或历史数据) |
1264 1264
 | **测试步骤** | 列表点「查看详情」 |
1265 1265
 | **预期结果** | 经营信息区显示「待完善」及缺失字段清单;主体资质只读展示 |
1266 1266
 
@@ -1537,7 +1537,7 @@
1537 1537
 | **测试步骤** | 详情改认证为「正常」并确认→返回列表点编辑 |
1538 1538
 | **预期结果** | 确认文案含「恢复后可编辑、可开店」;编辑页可打开并保存 |
1539 1539
 
1540
-### MER-UI-027 添加企业表单经营结算字段
1540
+### MER-UI-027 添加企业表单经营结算字段
1541 1541
 
1542 1542
 | 要素 | 内容 |
1543 1543
 |------|------|
@@ -1545,10 +1545,10 @@
1545 1545
 | **测试项** | 添加商户 |
1546 1546
 | **测试类型** | 界面测试 |
1547 1547
 | **测试工具** | Playwright |
1548
-| **测试目的** | 验证 R10 企业入驻范围 |
1548
+| **测试目的** | 验证 R10 企业新增须含经营 Tab |
1549 1549
 | **前置条件** | 有 add 权限 |
1550
-| **测试步骤** | 新增→选企业→检查表单项 |
1551
-| **预期结果** | 含法人、企业信息;**无** 商户名称、开户银行、营业执照(经营项在编辑页) |
1550
+| **测试步骤** | 新增→选企业→打开「商户经营信息」Tab |
1551
+| **预期结果** | 含商户名称、开户银行、**营业执照电子版**、**开户许可证** 等经营项;均为必填 |
1552 1552
 
1553 1553
 ### MER-UI-028 详情展示店铺数与下属店铺
1554 1554
 
@@ -1591,7 +1591,7 @@
1591 1591
 | R7 | 名称/信用代码唯一 | MER-UT-016/017/029, MER-API-008/014/037, MER-UI-023/024 |
1592 1592
 | R8 | 逻辑删除前置 | MER-UT-021~023, MER-API-021~025/042, MER-UI-016/017 |
1593 1593
 | R9 | 认证变更不级联店/商品 | (店铺/商品模块联调;本文档不重复) |
1594
-| R10 | 入驻仅主体 | MER-UT-006, MER-API-009, MER-UI-004/027 |
1594
+| R10 | 添加须主体+经营完整 | MER-UT-005/006, MER-API-005/009, MER-UI-004/027 |
1595 1595
 | R11 | 证件号/信用代码不可改 | MER-UT-008, MER-UI-014 |
1596 1596
 
1597 1597
 | 功能域 | 正常流程 | 异常/约束 |
@@ -1611,8 +1611,8 @@
1611 1611
 
1612 1612
 | 数据 | 说明 |
1613 1613
 |------|------|
1614
-| 个人商户-仅主体 | 用于入驻、待完善详情 |
1615
-| 个人商户-经营完整 | 用于 openShopCheck/selectList 正向 |
1614
+| 个人商户-经营不完整 | 编辑清空某经营字段后,用于待完善详情/openShopCheck 负向 |
1615
+| 个人商户-经营完整 | 用于新增成功、openShopCheck/selectList 正向 |
1616 1616
 | 企业商户-信用代码唯一 | 用于重复校验 |
1617 1617
 | 冻结/注销商户 | 用于状态机与权限 |
1618 1618
 | 高 shop_count 商户 | 用于 R1 无上限(≥5,由店铺测试数据准备,本文仅断言 openShopCheck) |
@@ -1666,4 +1666,4 @@ test('MER-UI-001 列表加载', async ({ page }) => {
1666 1666
 
1667 1667
 ---
1668 1668
 
1669
-*文档版本:v1.4 · 关联《商户管理功能需求.md》v1.7、《商户管理技术方案.md》v1.7*
1669
+*文档版本:v1.5 · 关联《商户管理功能需求.md》v1.8、《商户管理技术方案.md》v1.8*

+ 1 - 3
doc/平台后台/组织管理/店铺管理/店铺管理功能需求.md

@@ -388,9 +388,7 @@
388 388
 ### 13.1 商户入驻 → 补全资料 → 开店(端到端)
389 389
 
390 390
 ```text
391
-商户管理:添加商户(仅主体资质)→ 认证=正常
392
-    ↓
393
-商户管理:编辑商户 → 补全经营与结算信息
391
+商户管理:添加商户(主体+经营信息完整)→ 认证=正常,biz_complete=1
394 392
395 393
 店铺管理:添加店铺 → 选择商户(校验经营信息完整 **且已有经营账号**)
396 394

+ 50 - 10
ruoyi-ui/src/views/agri/org/merchant/index.vue

@@ -100,7 +100,7 @@
100 100
                 </el-col>
101 101
                 <el-col :span="12">
102 102
                   <el-form-item label="证件号码" prop="idCardNo">
103
-                    <el-input v-model="form.idCardNo" maxlength="18" :disabled="!!form.merchantId" placeholder="选填" />
103
+                    <el-input v-model="form.idCardNo" maxlength="18" :disabled="!!form.merchantId" :placeholder="form.merchantId ? '选填' : '必填'" />
104 104
                   </el-form-item>
105 105
                 </el-col>
106 106
                 <el-col :span="12">
@@ -206,7 +206,7 @@
206 206
                 <el-col :span="12"><el-form-item label="企业名称" prop="companyName"><el-input v-model="form.companyName" maxlength="128" /></el-form-item></el-col>
207 207
                 <el-col :span="12">
208 208
                   <el-form-item label="统一社会信用代码" prop="creditCode">
209
-                    <el-input v-model="form.creditCode" maxlength="18" :disabled="!!form.merchantId" placeholder="选填" />
209
+                    <el-input v-model="form.creditCode" maxlength="18" :disabled="!!form.merchantId" :placeholder="form.merchantId ? '选填' : '必填'" />
210 210
                   </el-form-item>
211 211
                 </el-col>
212 212
                 <el-col :span="24">
@@ -247,8 +247,8 @@
247 247
             </template>
248 248
           </el-tab-pane>
249 249
 
250
-          <!-- 经营信息(仅编辑) -->
251
-          <el-tab-pane v-if="form.merchantId" label="商户经营信息" name="biz">
250
+          <!-- 经营信息 -->
251
+          <el-tab-pane label="商户经营信息" name="biz">
252 252
             <el-row :gutter="16">
253 253
               <el-col :span="12"><el-form-item label="商户名称" prop="merchantName"><el-input v-model="form.merchantName" maxlength="128" /></el-form-item></el-col>
254 254
               <el-col :span="12"><el-form-item label="客服电话" prop="servicePhone"><el-input v-model="form.servicePhone" maxlength="20" /></el-form-item></el-col>
@@ -372,7 +372,7 @@ export default {
372 372
     }
373 373
   },
374 374
   computed: {
375
-    /** 表单校验:新增个人仅姓名必填;新增企业仅法人姓名+企业名称必填;绑定仅新增时校验 */
375
+    /** 表单校验:新增须完整主体+经营信息;编辑经营信息在 biz 页签校验 */
376 376
     formRules() {
377 377
       const rules = {
378 378
         merchantType: [{ required: true, message: "请选择主体类型", trigger: "change" }]
@@ -380,11 +380,41 @@ export default {
380 380
       const isAdd = !this.form.merchantId
381 381
       if (this.form.merchantType === "1") {
382 382
         rules.personName = [{ required: true, message: "请输入姓名", trigger: "blur" }]
383
+        if (isAdd) {
384
+          rules.idCardNo = [{ required: true, message: "请输入证件号码", trigger: "blur" }]
385
+        }
383 386
       } else {
384 387
         rules.legalName = [{ required: true, message: "请输入法人姓名", trigger: "blur" }]
385 388
         rules.companyName = [{ required: true, message: "请输入企业名称", trigger: "blur" }]
389
+        if (isAdd) {
390
+          rules.creditCode = [{ required: true, message: "请输入统一社会信用代码", trigger: "blur" }]
391
+        }
386 392
       }
387 393
       if (isAdd) {
394
+        rules.merchantName = [{ required: true, message: "请输入商户名称", trigger: "blur" }]
395
+        rules.servicePhone = [{ required: true, message: "请输入客服电话", trigger: "blur" }]
396
+        rules.bizRegionCascader = [{
397
+          required: true,
398
+          validator: (rule, value, callback) => {
399
+            if (!value || value.length === 0) {
400
+              callback(new Error("请选择经营地区"))
401
+            } else {
402
+              callback()
403
+            }
404
+          },
405
+          trigger: "change"
406
+        }]
407
+        rules.bizDetailAddress = [{ required: true, message: "请输入经营详细地址", trigger: "blur" }]
408
+        rules.contactName = [{ required: true, message: "请输入联系人姓名", trigger: "blur" }]
409
+        rules.contactPhone = [{ required: true, message: "请输入联系人手机", trigger: "blur" }]
410
+        rules.contactEmail = [{ required: true, message: "请输入联系人邮箱", trigger: "blur" }]
411
+        rules.bankName = [{ required: true, message: "请输入开户银行", trigger: "blur" }]
412
+        rules.bankBranch = [{ required: true, message: "请输入支行名称", trigger: "blur" }]
413
+        rules.bankAccount = [{ required: true, message: "请输入银行账号", trigger: "blur" }]
414
+        if (this.form.merchantType === "2") {
415
+          rules.businessLicense = [{ required: true, message: "请上传营业执照电子版", trigger: "change" }]
416
+          rules.accountPermit = [{ required: true, message: "请上传开户许可证", trigger: "change" }]
417
+        }
388 418
         rules.bindType = [{ required: true, message: "请选择绑定类型", trigger: "change" }]
389 419
         if (this.form.bindType === "SYS_USER") {
390 420
           rules.bindUserId = [{ required: true, message: "请选择平台管理员", trigger: "change" }]
@@ -465,6 +495,19 @@ export default {
465 495
         bindType: "SYS_USER",
466 496
         bindUserId: undefined,
467 497
         bindMemberId: undefined,
498
+        merchantName: undefined,
499
+        servicePhone: undefined,
500
+        bizRegionCode: undefined,
501
+        bizRegionName: undefined,
502
+        bizDetailAddress: undefined,
503
+        contactName: undefined,
504
+        contactPhone: undefined,
505
+        contactEmail: undefined,
506
+        bankName: undefined,
507
+        bankBranch: undefined,
508
+        bankAccount: undefined,
509
+        businessLicense: undefined,
510
+        accountPermit: undefined,
468 511
         bizRegionCascader: []
469 512
       }
470 513
       this.activeTab = "subject"
@@ -621,13 +664,10 @@ export default {
621 664
         const payload = this.buildSubmitPayload()
622 665
         const submitFn = payload.merchantId ? updateMerchant : addMerchant
623 666
         submitFn(payload).then(response => {
624
-          let msg = payload.merchantId ? "修改成功" : "新增成功"
625
-          if (response.data && response.data.bizCompleteChanged) {
667
+          let msg = response.msg || (payload.merchantId ? "修改成功" : "保存成功,已可开设店铺")
668
+          if (payload.merchantId && response.data && response.data.bizCompleteChanged) {
626 669
             msg = "保存成功,已可开设店铺"
627 670
           }
628
-          if (!payload.merchantId) {
629
-            msg = "请尽快在编辑中完善商户经营信息后再开设店铺"
630
-          }
631 671
           if (response.data && response.data.warnExpired) {
632 672
             this.$modal.msgWarning("证件或营业期限已过期,请注意风险")
633 673
           }