Explorar el Código

feat: 服务商多业务凭证表 + AlipayClientFactory bizType路由

alphah hace 2 días
padre
commit
a9cf6498d0
Se han modificado 100 ficheros con 762 adiciones y 7 borrados
  1. 406 0
      .claude/plan/2026-06-06-service-provider-scope-wanlihui.md
  2. 113 0
      docs/superpowers/specs/2026-06-06-service-provider-scope-wanlihui-design.md
  3. 1 0
      frontend/dist/css/EmployeeForm.C87s99Mz.css
  4. 0 0
      frontend/dist/css/index.Bcf-fN4k.css
  5. 0 0
      frontend/dist/js/401.FeHH9syY.js
  6. 0 0
      frontend/dist/js/404.Re3vyHfE.js
  7. 0 0
      frontend/dist/js/AccountOverview.Ccc-o2Vi.js
  8. 0 0
      frontend/dist/js/ChatInput.BvEHgQVq.js
  9. 0 0
      frontend/dist/js/ChatMessages.BQDs4vXr.js
  10. 0 0
      frontend/dist/js/ChatNavbar.C15TNWj9.js
  11. 0 0
      frontend/dist/js/ConfigInfoDrawer.BbD01mPM.js
  12. 0 0
      frontend/dist/js/ConsumeDetail.ql4CGvuy.js
  13. 0 0
      frontend/dist/js/CreateTableDialog.DHpE71p-.js
  14. 0 0
      frontend/dist/js/DataDrawer.5NprjOhw.js
  15. 0 0
      frontend/dist/js/DepartmentDetail.DhAtwRY1.js
  16. 0 0
      frontend/dist/js/DepartmentForm.CBkvtX0C.js
  17. 0 0
      frontend/dist/js/DeptTree.DYsawn5z.js
  18. 0 0
      frontend/dist/js/EdgeConfigPanel.rGKEuLQR.js
  19. 0 0
      frontend/dist/js/EmployeeForm.0ZjMjLy-.js
  20. 0 0
      frontend/dist/js/EnterpriseDetail.DS4FDET9.js
  21. 36 0
      frontend/dist/js/EnterpriseForm.CLk3jcjh.js
  22. 1 0
      frontend/dist/js/ExternalLink.BZdytyho.js
  23. 0 0
      frontend/dist/js/GenBasicStep.o-ICL_mH.js
  24. 0 0
      frontend/dist/js/GenCodeDrawer.CNPD32KO.js
  25. 0 0
      frontend/dist/js/GenColumnsStep.V8e9Mshr.js
  26. 0 0
      frontend/dist/js/GenPreviewStep.CUOZT0Pg.js
  27. 0 0
      frontend/dist/js/GencodeHelpPanel.vtkN6VGv.js
  28. 0 0
      frontend/dist/js/ImportDbTableDialog.CxH7PmV4.js
  29. 0 0
      frontend/dist/js/InstitutionDetail.CB13_t5X.js
  30. 0 0
      frontend/dist/js/InstitutionForm.7f13kcli.js
  31. 0 0
      frontend/dist/js/InviteDialog.BpvOeKZY.js
  32. 0 0
      frontend/dist/js/IssueBatchForm.B27h3FT9.js
  33. 0 0
      frontend/dist/js/Login.o0lWnho9.js
  34. 0 0
      frontend/dist/js/MessageItem.9fcgcwLI.js
  35. 0 0
      frontend/dist/js/NodeConfigPanel.CmOqH2Ff.js
  36. 0 0
      frontend/dist/js/PageContent.NaMrDvKG.js
  37. 0 0
      frontend/dist/js/QuotaList.COC3JQYs.js
  38. 0 0
      frontend/dist/js/RuleForm.D8MgcN5x.js
  39. 0 0
      frontend/dist/js/RuleList.C0P8kao4.js
  40. 0 0
      frontend/dist/js/ScopeDialog.CQjkGAEC.js
  41. 0 0
      frontend/dist/js/Sidebar.21a_4iUj.js
  42. 0 0
      frontend/dist/js/TransferDetail.CDKY1TuM.js
  43. 0 0
      frontend/dist/js/UserTableSelect.CUjZ5PGa.js
  44. 0 0
      frontend/dist/js/WelcomeScreen.BwKWDjo4.js
  45. 0 0
      frontend/dist/js/WorkflowDesignDrawer.CcxFMO2n.js
  46. 0 0
      frontend/dist/js/github.ZczQl71D.js
  47. 1 0
      frontend/dist/js/index copy.Ddg_A9cK.js
  48. 0 0
      frontend/dist/js/index.5dXSQkGc.js
  49. 0 0
      frontend/dist/js/index.6zDv_iho.js
  50. 0 0
      frontend/dist/js/index.B-MUswAI.js
  51. 0 0
      frontend/dist/js/index.B1J14-o5.js
  52. 0 0
      frontend/dist/js/index.B3Pt2yPw.js
  53. 0 0
      frontend/dist/js/index.B9ivVnk9.js
  54. 0 0
      frontend/dist/js/index.BB-zHv7i.js
  55. 1 0
      frontend/dist/js/index.BE8KlSgP.js
  56. 0 0
      frontend/dist/js/index.BEO6A-L6.js
  57. 0 0
      frontend/dist/js/index.BMbHTqzP.js
  58. 0 0
      frontend/dist/js/index.BUD6Pf3q.js
  59. 0 0
      frontend/dist/js/index.BWIPX3TO.js
  60. 1 0
      frontend/dist/js/index.B_SuFpIM.js
  61. 0 0
      frontend/dist/js/index.Bdf_fbht.js
  62. 0 0
      frontend/dist/js/index.Bh-sOdPP.js
  63. 0 0
      frontend/dist/js/index.BnFn_YWr.js
  64. 0 0
      frontend/dist/js/index.C-3U5qKM.js
  65. 1 0
      frontend/dist/js/index.CFZoFYPp.js
  66. 1 0
      frontend/dist/js/index.CQtKMwJA.js
  67. 0 0
      frontend/dist/js/index.CT6t5OYx.js
  68. 0 0
      frontend/dist/js/index.C_Ji98hE.js
  69. 0 0
      frontend/dist/js/index.C_xCeODy.js
  70. 1 0
      frontend/dist/js/index.CeLNGI7F.js
  71. 0 0
      frontend/dist/js/index.CfwBJonW.js
  72. 0 0
      frontend/dist/js/index.CvinFEpw.js
  73. 0 0
      frontend/dist/js/index.D6-EFXZ7.js
  74. 0 0
      frontend/dist/js/index.D8iu8uPn.js
  75. 0 0
      frontend/dist/js/index.DEMv9Qiq.js
  76. 0 0
      frontend/dist/js/index.DFNFECYn.js
  77. 0 0
      frontend/dist/js/index.DTMNW7RO.js
  78. 0 0
      frontend/dist/js/index.DUtyrwbD.js
  79. 0 0
      frontend/dist/js/index.Dga2BcID.js
  80. 0 0
      frontend/dist/js/index.DjSgV9m5.js
  81. 0 0
      frontend/dist/js/index.Dp2tmUU_.js
  82. 0 0
      frontend/dist/js/index.eAAGGHST.js
  83. 0 0
      frontend/dist/js/index.oeiK9NpL.js
  84. 0 0
      frontend/dist/js/index.uMY1hFH8.js
  85. 0 0
      frontend/dist/js/index.w_yZAPtI.js
  86. 0 0
      frontend/dist/js/profile.BnPiay3z.js
  87. 6 0
      frontend/dist/js/project.DLvDsB8k.js
  88. 0 0
      frontend/dist/js/select.hWg0mQrH.js
  89. 0 0
      frontend/dist/js/size.XRnSJLyz.js
  90. 0 0
      frontend/dist/js/tenant.4iUbadNE.js
  91. 25 0
      frontend/dist/js/time.BomZslYM.js
  92. 0 0
      frontend/dist/js/tree.zFjxiaNc.js
  93. 45 0
      frontend/dist/js/vite.C8bgex4Y.js
  94. 0 0
      frontend/dist/js/wechat.AmvA8dYX.js
  95. 0 0
      frontend/dist/js/workplace.mvSs3W1x.js
  96. 17 0
      java/sql/009_service_provider_profile.sql
  97. 53 3
      java/src/main/java/com/payment/platform/core/alipay/AlipayClientFactory.java
  98. 5 4
      java/src/main/java/com/payment/platform/module/payment/facetoface/service/FacetofaceService.java
  99. 26 0
      java/src/main/java/com/payment/platform/module/payment/serviceprovider/entity/ServiceProviderProfileEntity.java
  100. 22 0
      java/src/main/java/com/payment/platform/module/payment/serviceprovider/enums/ServiceProviderBizType.java

+ 406 - 0
.claude/plan/2026-06-06-service-provider-scope-wanlihui.md

@@ -0,0 +1,406 @@
+# 服务商业务范围下拉 & 企业万里汇uid 实现计划
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 1) 服务商新增/编辑弹窗的业务范围从输入框改为国内/国外下拉;2) 企业表单选国外服务商时出现万里汇uid输入框并落库回显
+
+**Architecture:** 前后端改动:DB新增 `pay_enterprise.wanlihui_uid` 列 → Java Entity/DTO/VO 补充字段 → Service 处理映射 → 前端服务商表单改下拉、企业表单条件显隐万里汇uid、详情回显
+
+**Tech Stack:** Vue3 + Element Plus, Java Spring Boot + MyBatis-Plus, PostgreSQL
+
+---
+
+### Task 1: 数据库迁移 — pay_enterprise 新增 wanlihui_uid 列
+
+**Files:**
+- Create: `java/sql/007_enterprise_wanlihui_uid.sql`
+
+- [ ] **Step 1: 创建 SQL 迁移文件**
+
+```sql
+-- 企业表新增万里汇uid字段
+ALTER TABLE pay_enterprise ADD COLUMN IF NOT EXISTS wanlihui_uid VARCHAR(128);
+```
+
+- [ ] **Step 2: 执行迁移**
+
+Run: `psql -h <host> -U <user> -d <database> -f java/sql/007_enterprise_wanlihui_uid.sql`
+
+- [ ] **Step 3: 验证列已存在**
+
+```sql
+SELECT column_name, data_type FROM information_schema.columns
+WHERE table_name = 'pay_enterprise' AND column_name = 'wanlihui_uid';
+```
+
+Expected: 一行结果,`wanlihui_uid | character varying`
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add java/sql/007_enterprise_wanlihui_uid.sql
+git commit -m "feat: pay_enterprise 新增 wanlihui_uid 列"
+```
+
+---
+
+### Task 2: Backend — Entity + DTO + VO 补充 wanlihuiUid 字段
+
+**Files:**
+- Modify: `java/src/main/java/com/payment/platform/module/payment/enterprise/entity/EnterpriseEntity.java`
+- Modify: `java/src/main/java/com/payment/platform/module/payment/enterprise/dto/EnterpriseCreateDTO.java`
+- Modify: `java/src/main/java/com/payment/platform/module/payment/enterprise/dto/EnterpriseUpdateDTO.java`
+- Modify: `java/src/main/java/com/payment/platform/module/payment/enterprise/dto/EnterpriseVO.java`
+
+- [ ] **Step 1: EnterpriseEntity.java — 在 `scopeLabel` 字段后添加 `wanlihuiUid`**
+
+```java
+// 位置: scopeLabel 字段后(line 34 之后)
+
+/** 万里汇uid(国外服务商必填) */
+private String wanlihuiUid;
+```
+
+- [ ] **Step 2: EnterpriseCreateDTO.java — 添加 `wanlihuiUid`**
+
+```java
+// 位置: legalRepIdPhoto 字段后(line 42 之后)
+
+@Schema(description = "万里汇uid")
+private String wanlihuiUid;
+```
+
+- [ ] **Step 3: EnterpriseUpdateDTO.java — 添加 `wanlihuiUid`**
+
+```java
+// 位置: legalRepIdPhoto 字段后(line 43 之后)
+
+@Schema(description = "万里汇uid")
+private String wanlihuiUid;
+```
+
+- [ ] **Step 4: EnterpriseVO.java — 添加 `wanlihuiUid`**
+
+```java
+// 位置: legalRepIdPhoto 字段后(line 91 之后)
+
+@Schema(description = "万里汇uid")
+@com.fasterxml.jackson.annotation.JsonProperty("wanlihui_uid")
+private String wanlihuiUid;
+```
+
+- [ ] **Step 5: 编译验证**
+
+Run: `cd java && mvn compile -q`
+
+Expected: BUILD SUCCESS
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add java/src/main/java/com/payment/platform/module/payment/enterprise/entity/EnterpriseEntity.java \
+        java/src/main/java/com/payment/platform/module/payment/enterprise/dto/EnterpriseCreateDTO.java \
+        java/src/main/java/com/payment/platform/module/payment/enterprise/dto/EnterpriseUpdateDTO.java \
+        java/src/main/java/com/payment/platform/module/payment/enterprise/dto/EnterpriseVO.java
+git commit -m "feat: Enterprise Entity/DTO/VO 补充 wanlihuiUid 字段"
+```
+
+---
+
+### Task 3: Backend — Service 处理 wanlihuiUid 映射
+
+**Files:**
+- Modify: `java/src/main/java/com/payment/platform/module/payment/enterprise/service/EnterpriseService.java`
+- Modify: `java/src/main/java/com/payment/platform/module/payment/enterprise/service/AlipayEnterpriseService.java`
+
+- [ ] **Step 1: EnterpriseService.create() — 添加 wanlihuiUid 映射**
+
+在方法 `create()` 中 `e.setLegalRepIdPhoto(dto.getLegalRepIdPhoto());` 之后添加:
+
+```java
+e.setWanlihuiUid(dto.getWanlihuiUid());
+```
+
+- [ ] **Step 2: EnterpriseService.update() — 添加 wanlihuiUid 映射**
+
+在方法 `update()` 中 `if (dto.getLegalRepIdPhoto() != null) e.setLegalRepIdPhoto(dto.getLegalRepIdPhoto());` 之后添加:
+
+```java
+if (dto.getWanlihuiUid() != null) e.setWanlihuiUid(dto.getWanlihuiUid());
+```
+
+- [ ] **Step 3: EnterpriseService.toListVO() — 添加 wanlihuiUid 映射**
+
+在 `vo.setLegalRepIdPhoto(e.getLegalRepIdPhoto());` 之后添加:
+
+```java
+vo.setWanlihuiUid(e.getWanlihuiUid());
+```
+
+- [ ] **Step 4: EnterpriseService.toVO() — 添加 wanlihuiUid 映射**
+
+在 `vo.setLegalRepIdPhoto(e.getLegalRepIdPhoto());` 之后添加:
+
+```java
+vo.setWanlihuiUid(e.getWanlihuiUid());
+```
+
+- [ ] **Step 5: AlipayEnterpriseService.applyInvite() — 保存 wanlihuiUid**
+
+在 `pending.setLegalRepIdPhoto(...)` 之后添加:
+
+```java
+pending.setWanlihuiUid((String) data.get("wanlihui_uid"));
+```
+
+- [ ] **Step 6: 编译验证**
+
+Run: `cd java && mvn compile -q`
+
+Expected: BUILD SUCCESS
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add java/src/main/java/com/payment/platform/module/payment/enterprise/service/EnterpriseService.java \
+        java/src/main/java/com/payment/platform/module/payment/enterprise/service/AlipayEnterpriseService.java
+git commit -m "feat: EnterpriseService/AlipayEnterpriseService 处理 wanlihuiUid 字段映射"
+```
+
+---
+
+### Task 4: Frontend — API 类型 & Schema 补充 wanlihui_uid
+
+**Files:**
+- Modify: `frontend/src/api/module_payment/enterprise.ts`
+- Modify: `frontend/src/api/module_payment/enterprise/schema.ts`
+
+- [ ] **Step 1: enterprise.ts EnterpriseDetail 接口 — 添加 `wanlihui_uid`**
+
+在 `EnterpriseDetail` 接口末尾(`legal_rep_id_photo` 之后)添加:
+
+```ts
+/** 万里汇uid */
+wanlihui_uid?: string;
+```
+
+- [ ] **Step 2: schema.ts EnterpriseOutSchema — 添加 `wanlihui_uid`**
+
+在 `EnterpriseOutSchema` 接口末尾添加:
+
+```ts
+/**
+ * 万里汇uid
+ */
+wanlihui_uid?: string;
+```
+
+- [ ] **Step 3: 验证前端编译**
+
+Run: `cd frontend && npx vue-tsc --noEmit 2>&1 | Select-Object -Last 10`
+
+Expected: No new type errors
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add frontend/src/api/module_payment/enterprise.ts \
+        frontend/src/api/module_payment/enterprise/schema.ts
+git commit -m "feat: 前端 enterprise API 类型补充 wanlihui_uid"
+```
+
+---
+
+### Task 5: Frontend — 服务商表单 scope_label 改为下拉框
+
+**Files:**
+- Modify: `frontend/src/views/module_system/service_provider/index.vue`
+
+- [ ] **Step 1: 修改"新增"弹窗中的 scope_label 输入框(约 line 117-118)**
+
+Before:
+```html
+<el-form-item label="业务范围标签" prop="scope_label">
+  <el-input v-model="formData.scope_label" placeholder="如 DOMESTIC / OVERSEAS" :maxlength="64" />
+</el-form-item>
+```
+
+After:
+```html
+<el-form-item label="业务范围" prop="scope_label">
+  <el-select v-model="formData.scope_label" placeholder="请选择业务范围" style="width: 100%">
+    <el-option label="国内" value="DOMESTIC" />
+    <el-option label="国外" value="OVERSEAS" />
+  </el-select>
+</el-form-item>
+```
+
+- [ ] **Step 2: 修改"编辑"弹窗中的 scope_label 输入框(约 line 150-151)**
+
+Before:
+```html
+<el-form-item label="业务范围标签" prop="scope_label">
+  <el-input v-model="formData.scope_label" placeholder="如 DOMESTIC / OVERSEAS" :maxlength="64" />
+</el-form-item>
+```
+
+After:
+```html
+<el-form-item label="业务范围" prop="scope_label">
+  <el-select v-model="formData.scope_label" placeholder="请选择业务范围" style="width: 100%">
+    <el-option label="国内" value="DOMESTIC" />
+    <el-option label="国外" value="OVERSEAS" />
+  </el-select>
+</el-form-item>
+```
+
+- [ ] **Step 3: 修改验证规则文案(约 line 252, 260)**
+
+将 `"请输入业务范围"` 改为 `"请选择业务范围"`(两处:`rules.scope_label` 和 `updateRules.scope_label`)
+
+- [ ] **Step 4: 验证编译**
+
+Run: `cd frontend && npx vue-tsc --noEmit 2>&1 | Select-Object -Last 10`
+
+Expected: No new type errors
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add frontend/src/views/module_system/service_provider/index.vue
+git commit -m "feat: 服务商表单业务范围改为国内/国外下拉框"
+```
+
+---
+
+### Task 6: Frontend — EnterpriseForm 万里汇uid 条件显隐 + 提交
+
+**Files:**
+- Modify: `frontend/src/views/module_payment/enterprise/components/EnterpriseForm.vue`
+
+- [ ] **Step 1: 在 `initialFormData` 中添加 `wanlihui_uid` 字段(约 line 201-208)**
+
+```ts
+const initialFormData = {
+  identity_type: "ALIPAY_USER_ID",
+  alipay_id_type: "uid",
+  identity: undefined,
+  identity_open_id: undefined,
+  service_provider_id: null as number | null,
+  scope_label: "",
+  wanlihui_uid: "",
+};
+```
+
+- [ ] **Step 2: 在 `handleProviderChange` 中添加切换时清空逻辑(约 line 214-217)**
+
+```ts
+function handleProviderChange(val: number) {
+  const provider = providerOptions.value.find(p => p.id === val);
+  formData.scope_label = provider?.scope_label || "";
+  // 切换服务商时若不再是国外,清空万里汇uid
+  if (formData.scope_label !== "OVERSEAS") {
+    formData.wanlihui_uid = "";
+  }
+}
+```
+
+- [ ] **Step 3: 在模板中添加万里汇uid输入框 — 放在"身份类型"行之前,选择国外服务商后显示**
+
+在服务商选择器 `</el-row>` 之后、身份类型 `<el-row>` 之前插入:
+
+```html
+      <!-- 万里汇uid:仅国外服务商显示 -->
+      <el-row v-if="formData.scope_label === 'OVERSEAS'" :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="万里汇uid" prop="wanlihui_uid">
+            <el-input v-model="formData.wanlihui_uid" placeholder="请输入万里汇uid" :maxlength="128" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+```
+
+- [ ] **Step 4: 在 `submitForm` 的 `submitData` 中添加 `wanlihui_uid`(约 line 430 后)**
+
+在 `scope_label: formData.scope_label,` 之后添加:
+
+```ts
+wanlihui_uid: formData.wanlihui_uid || undefined,
+```
+
+- [ ] **Step 5: 在 `update` 类型的 watch 回显中添加 `wanlihui_uid`(约 line 253 后)**
+
+在 `materialForm.legal_rep_id_photo = data.legal_rep_id_photo || "";` 之后添加:
+
+```ts
+formData.wanlihui_uid = data.wanlihui_uid || "";
+```
+
+- [ ] **Step 6: 在 `resetForm` 中添加重置逻辑(约 line 477-488)**
+
+确保 `resetForm` 使用的 `JSON.parse(JSON.stringify(initialFormData))` 自动包含 `wanlihui_uid: ""`(无需额外改动,Step 1 已覆盖)
+
+- [ ] **Step 7: 验证编译**
+
+Run: `cd frontend && npx vue-tsc --noEmit 2>&1 | Select-Object -Last 10`
+
+Expected: No new type errors
+
+- [ ] **Step 8: Commit**
+
+```bash
+git add frontend/src/views/module_payment/enterprise/components/EnterpriseForm.vue
+git commit -m "feat: 企业表单选国外服务商时显示万里汇uid输入框"
+```
+
+---
+
+### Task 7: Frontend — EnterpriseDetail 回显万里汇uid
+
+**Files:**
+- Modify: `frontend/src/views/module_payment/enterprise/components/EnterpriseDetail.vue`
+
+- [ ] **Step 1: 在详情模板的"签约信息"区块中添加万里汇uid显示**
+
+在"账号ID"描述项之后(约 line 40 后)添加:
+
+```html
+      <el-descriptions-item v-if="detailData.wanlihui_uid" label="万里汇uid" :span="1">
+        {{ detailData.wanlihui_uid }}
+      </el-descriptions-item>
+```
+
+- [ ] **Step 2: 验证编译**
+
+Run: `cd frontend && npx vue-tsc --noEmit 2>&1 | Select-Object -Last 10`
+
+Expected: No new type errors
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add frontend/src/views/module_payment/enterprise/components/EnterpriseDetail.vue
+git commit -m "feat: 企业详情回显万里汇uid"
+```
+
+---
+
+### 验证汇总
+
+完成后运行完整构建确认所有层级通过:
+
+```bash
+# 后端
+cd java && mvn compile
+
+# 前端
+cd frontend && npm run build
+```
+
+### 手动测试要点
+
+1. 服务商管理 → 新增/编辑弹窗 → 业务范围为下拉框,选项为"国内"/"国外"
+2. 企业入驻 → 先选"国内"服务商 → 无万里汇uid输入框
+3. 企业入驻 → 切换到"国外"服务商 → 出现万里汇uid输入框
+4. 填写万里汇uid → 提交 → 数据库 `pay_enterprise.wanlihui_uid` 有值
+5. 企业详情 → 万里汇uid正确回显

+ 113 - 0
docs/superpowers/specs/2026-06-06-service-provider-scope-wanlihui-design.md

@@ -0,0 +1,113 @@
+# 服务商业务范围下拉 & 企业万里汇uid
+
+**日期**: 2026-06-06  
+**分支**: java
+
+---
+
+## 概述
+
+1. 服务商管理页面(新增/编辑弹窗)的"业务范围标签"从输入框改为下拉框,仅映射"国内" / "国外"
+2. 企业入驻/新增企业弹窗中,若选择"国外"标签的服务商,新增"万里汇uid"输入框,该字段落库并在企业详情中回显
+
+---
+
+## 改动清单
+
+### 数据库
+
+- `pay_enterprise` 新增列 `wanlihui_uid VARCHAR(128)`(无唯一约束,允许为空)
+- 新 SQL 迁移文件: `java/sql/007_enterprise_wanlihui_uid.sql`
+
+```sql
+ALTER TABLE pay_enterprise ADD COLUMN IF NOT EXISTS wanlihui_uid VARCHAR(128);
+```
+
+### 后端 (Java)
+
+| 文件 | 改动 |
+|---|---|
+| `EnterpriseEntity.java` | 新增 `@TableField("wanlihui_uid") private String wanlihuiUid;` |
+| `EnterpriseCreateDTO.java` | 新增 `private String wanlihuiUid;` |
+| `EnterpriseUpdateDTO.java` | 新增 `private String wanlihuiUid;` |
+| `EnterpriseVO.java` | 新增 `@JsonProperty("wanlihui_uid") private String wanlihuiUid;` |
+| `EnterpriseService.java` | `create()` / `update()` 处理 `wanlihuiUid` 字段映射 |
+| `AlipayEnterpriseService.java` | `applyInvite()` 保存 `wanlihuiUid` 到实体 |
+
+### 前端
+
+| 文件 | 改动 |
+|---|---|
+| `service_provider/index.vue` | `scope_label` 字段从 `<el-input>` 改为 `<el-select>`: `国内`→`DOMESTIC`, `国外`→`OVERSEAS` |
+| `EnterpriseForm.vue` | 监听 `service_provider_id` 变化,当选中服务商的 `scope_label === 'OVERSEAS'` 时显示万里汇uid输入框;提交时携带 `wanlihui_uid` |
+| `EnterpriseDetail.vue` | 回显 `wanlihui_uid` 字段 |
+| `enterprise.ts` | `EnterpriseDetail` 接口补充 `wanlihui_uid` |
+| `schema.ts` | 补充 `wanlihui_uid` 类型 |
+
+---
+
+## 关键逻辑
+
+### 服务商表单 scope_label
+
+```
+创建/编辑服务商时:
+  scope_label 字段类型: el-select (替代原 el-input)
+  选项:
+    { label: '国内', value: 'DOMESTIC' }
+    { label: '国外', value: 'OVERSEAS' }
+  DOMESTIC 保持为默认值
+```
+
+### 企业表单万里汇uid显隐
+
+```
+EnterpriseForm.vue:
+
+watch service_provider_id:
+  → 从 providerOptions 查找选中服务商的 scope_label
+  → formData.scope_label = provider.scope_label
+  
+computed showWanlihuiUid:
+  → formData.scope_label === 'OVERSEAS'
+
+模板:
+  <el-form-item v-if="showWanlihuiUid" label="万里汇uid" prop="wanlihui_uid">
+    <el-input v-model="formData.wanlihui_uid" placeholder="请输入万里汇uid" />
+  </el-form-item>
+
+handleProviderChange():
+  → 切换服务商时若 scope_label !== 'OVERSEAS',清空 formData.wanlihui_uid
+```
+
+### 数据流
+
+```
+提交企业:
+  EnterpriseForm → { ..., wanlihui_uid: "xxx" }
+    → EnterpriseAPI.applyInvite(payload)
+      → POST /payment/enterprise/invite
+        → AlipayEnterpriseService.applyInvite()
+          → entity.setWanlihuiUid(dto.getWanlihuiUid())
+          → enterpriseMapper.insert(entity)
+            → pay_enterprise.wanlihui_uid = "xxx"
+
+回显企业详情:
+  EnterpriseDetail → EnterpriseAPI.detail(id)
+    → GET /payment/enterprise/{id}
+      → EnterpriseService.detail()
+        → enterpriseMapper.selectById(id)
+        → toVO(entity) → EnterpriseVO { wanlihui_uid: "xxx" }
+          → 前端展示
+```
+
+---
+
+## 测试要点
+
+1. 服务商新增/编辑 → scope_label 下拉框正常展示国内/国外,提交保存正确
+2. 选择国外服务商 → 企业表单出现万里汇uid输入框
+3. 切换回国内服务商 → 万里汇uid输入框消失并清空值
+4. 填写万里汇uid后提交 → 数据成功写入 `pay_enterprise.wanlihui_uid`
+5. 企业详情页面 → 万里汇uid正确回显
+6. 不填万里汇uid提交 → 不报错(非必填)

+ 1 - 0
frontend/dist/css/EmployeeForm.C87s99Mz.css

@@ -0,0 +1 @@
+.employee-form[data-v-1d1f9db2]{width:100%;max-width:600px;margin:0 auto}.employee-form__inner[data-v-1d1f9db2]{padding:20px}.employee-form__hint[data-v-1d1f9db2]{font-size:12px;color:#909399;margin-top:4px}.employee-form__actions[data-v-1d1f9db2]{display:flex;justify-content:flex-end;gap:12px;margin-top:30px;padding-top:20px;border-top:1px solid #ebeef5}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/css/index.Bcf-fN4k.css


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/401.FeHH9syY.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/404.Re3vyHfE.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/AccountOverview.Ccc-o2Vi.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/ChatInput.BvEHgQVq.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/ChatMessages.BQDs4vXr.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/ChatNavbar.C15TNWj9.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/ConfigInfoDrawer.BbD01mPM.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/ConsumeDetail.ql4CGvuy.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/CreateTableDialog.DHpE71p-.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/DataDrawer.5NprjOhw.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/DepartmentDetail.DhAtwRY1.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/DepartmentForm.CBkvtX0C.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/DeptTree.DYsawn5z.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/EdgeConfigPanel.rGKEuLQR.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/EmployeeForm.0ZjMjLy-.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/EnterpriseDetail.DS4FDET9.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 36 - 0
frontend/dist/js/EnterpriseForm.CLk3jcjh.js


+ 1 - 0
frontend/dist/js/ExternalLink.BZdytyho.js

@@ -0,0 +1 @@
+import{u as s}from"./vue-router.yxIMtaxH.js";import{M as i,aL as r,u as t,v as o,q as e}from"./@vue.DbmRtknU.js";import{_ as p}from"./index.DFNFECYn.js";import"./dayjs.DCWbrgJ0.js";import"./element-plus.DYHj_7Hl.js";import"./lodash-es.DaM9m3L-.js";import"./async-validator.j0i5Y79Y.js";import"./@popperjs.DxtSUbXb.js";import"./@ctrl.BEgk5vdO.js";import"./memoize-one.BAtLgO95.js";import"./normalize-wheel-es.TzhA1irr.js";import"./@floating-ui.8vigAAFV.js";import"./pinia.BlfmsheH.js";import"./@vueuse.0G3dmZIi.js";import"./vue-i18n.DXLOBfKS.js";import"./@intlify.DPMNdUn_.js";import"./nprogress.BTjJXJ-u.js";import"./codemirror.CYSLATvI.js";import"./diff-match-patch.DkK4wJpa.js";import"./vue-web-terminal.D-rog7dz.js";import"./vue.MGxsMDTR.js";/* empty css                    */import"./axios.Da-QW0H8.js";import"./qs.BQjOrGHM.js";import"./side-channel.4q28KFJj.js";import"./es-errors.DTEWvbA_.js";import"./object-inspect.DvQZIv3_.js";import"./side-channel-list.Do0-XmF5.js";import"./side-channel-map.DNHQ53lO.js";import"./get-intrinsic.Bbe5x-9b.js";import"./es-object-atoms.CyiuHMUS.js";import"./math-intrinsics.pM-JTNwN.js";import"./gopd.BudZp56J.js";import"./es-define-property.F0aoeP8o.js";import"./has-symbols.BcO-SUVM.js";import"./get-proto.Cb_fpw-j.js";import"./dunder-proto.WEH3rgQR.js";import"./call-bind-apply-helpers.DJjIjCF_.js";import"./function-bind.DrnB-baK.js";import"./hasown.BXcyoiLU.js";import"./call-bound.22gFUC2Q.js";import"./side-channel-weakmap.DpSeWE6i.js";import"./pinia-plugin-persistedstate.COWkwNh5.js";const m={class:"external-link-container"},a=["src"],n=p(i({__name:"ExternalLink",setup(i){const p=s(),n=e(()=>{var s;const i=null==(s=p.meta)?void 0:s.params,r=null==i?void 0:i.find(s=>"url"===s.key);return(null==r?void 0:r.value)||""});return(s,i)=>(r(),t("div",m,[o("iframe",{src:n.value,class:"external-link-iframe",frameborder:"0",width:"100%",height:"100%"},null,8,a)]))}}),[["__scopeId","data-v-ddb74517"]]);export{n as default};

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/GenBasicStep.o-ICL_mH.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/GenCodeDrawer.CNPD32KO.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/GenColumnsStep.V8e9Mshr.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/GenPreviewStep.CUOZT0Pg.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/GencodeHelpPanel.vtkN6VGv.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/ImportDbTableDialog.CxH7PmV4.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/InstitutionDetail.CB13_t5X.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/InstitutionForm.7f13kcli.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/InviteDialog.BpvOeKZY.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/IssueBatchForm.B27h3FT9.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/Login.o0lWnho9.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/MessageItem.9fcgcwLI.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/NodeConfigPanel.CmOqH2Ff.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/PageContent.NaMrDvKG.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/QuotaList.COC3JQYs.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/RuleForm.D8MgcN5x.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/RuleList.C0P8kao4.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/ScopeDialog.CQjkGAEC.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/Sidebar.21a_4iUj.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/TransferDetail.CDKY1TuM.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/UserTableSelect.CUjZ5PGa.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/WelcomeScreen.BwKWDjo4.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/WorkflowDesignDrawer.CcxFMO2n.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/github.ZczQl71D.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 0
frontend/dist/js/index copy.Ddg_A9cK.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.5dXSQkGc.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.6zDv_iho.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.B-MUswAI.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.B1J14-o5.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.B3Pt2yPw.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.B9ivVnk9.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.BB-zHv7i.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 0
frontend/dist/js/index.BE8KlSgP.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.BEO6A-L6.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.BMbHTqzP.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.BUD6Pf3q.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.BWIPX3TO.js


+ 1 - 0
frontend/dist/js/index.B_SuFpIM.js

@@ -0,0 +1 @@
+import{P as e}from"./vue-json-pretty.DEqWvEy3.js";import{M as t,aL as a,u as s,aw as r,s as o,bk as n,bb as l,q as u}from"./@vue.DbmRtknU.js";import{_ as p}from"./index.DFNFECYn.js";const i={key:1,class:"json-pretty-fallback"},y=p(t({__name:"index",props:{value:{type:[String,Object,Array,Number,Boolean],default:""},height:{type:String,default:"240px"}},setup(t){const p=t,y=u(()=>{const e=p.value;if("string"==typeof e)try{return JSON.parse(e)}catch{return e}return e}),c=u(()=>"object"==typeof y.value&&null!==y.value),d=u(()=>{const e=p.value;return"string"==typeof e?e:JSON.stringify(e,null,2)});return(u,p)=>(a(),s("div",{class:"json-pretty-wrapper",style:r({maxHeight:t.height})},[c.value?(a(),o(n(e),{key:0,data:y.value,"show-line":!0,"show-icon":!0,"show-double-quotes":!1,"show-length":!0,deep:3},null,8,["data"])):(a(),s("pre",i,l(d.value),1))],4))}}),[["__scopeId","data-v-a0d23118"]]);export{y as J};

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.Bdf_fbht.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.Bh-sOdPP.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.BnFn_YWr.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.C-3U5qKM.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 0
frontend/dist/js/index.CFZoFYPp.js


+ 1 - 0
frontend/dist/js/index.CQtKMwJA.js

@@ -0,0 +1 @@
+import{aU as e,aD as t,bL as r,aL as n,u as s,v as a,aw as o,q as i,M as l,az as c,I as u,bk as d}from"./@vue.DbmRtknU.js";import{O as p}from"./element-plus.DYHj_7Hl.js";import{_ as m}from"./index.DFNFECYn.js";const _=["src"],v=m({__name:"index",props:{src:{type:String,required:!0}},setup(l){const c=l,u=e(document.documentElement.clientHeight-94.5+"px;"),d=e(!0),m=i(()=>c.src);return t(()=>{setTimeout(()=>{d.value=!1},300)}),(e,t)=>{const i=p;return r((n(),s("div",{style:o("height:"+u.value)},[a("iframe",{src:m.value,frameborder:"0",width:"100%",height:"100%",scrolling:"auto"},null,8,_)],4)),[[i,d.value]])}}},[["__scopeId","data-v-c20c8d45"]]),f={class:"app-container"},b=l({name:"Docs",inheritAttrs:!1,__name:"index",setup(t){const r=e("/api/v1/docs");return c(()=>{window.onresize=null}),(e,t)=>{const a=v;return n(),s("div",f,[u(a,{src:d(r)},null,8,["src"])])}}}),g=Object.freeze(Object.defineProperty({__proto__:null,default:b},Symbol.toStringTag,{value:"Module"})),j={class:"app-container"},h=l({name:"Docs",inheritAttrs:!1,__name:"index",setup(t){const r=e("/api/v1/ljdoc");return c(()=>{window.onresize=null}),(e,t)=>{const a=v;return n(),s("div",j,[u(a,{src:d(r)},null,8,["src"])])}}}),w=Object.freeze(Object.defineProperty({__proto__:null,default:h},Symbol.toStringTag,{value:"Module"})),y={class:"app-container"},x=l({name:"Redoc",inheritAttrs:!1,__name:"index",setup(t){const r=e("/api/v1/redoc");return c(()=>{window.onresize=null}),(e,t)=>{const a=v;return n(),s("div",y,[u(a,{src:d(r)},null,8,["src"])])}}}),z=Object.freeze(Object.defineProperty({__proto__:null,default:x},Symbol.toStringTag,{value:"Module"}));export{w as a,z as b,g as i};

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.CT6t5OYx.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.C_Ji98hE.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.C_xCeODy.js


+ 1 - 0
frontend/dist/js/index.CeLNGI7F.js

@@ -0,0 +1 @@
+import{e,f as a}from"./element-plus.DYHj_7Hl.js";import{M as t,bq as i,bE as u,aL as l,s as n,bJ as o,v as p,I as s,au as r,aq as d}from"./@vue.DbmRtknU.js";import{_ as g}from"./index.DFNFECYn.js";const m=g(t({__name:"index",props:d({total:{type:Number,default:0},pageSizes:{type:Array,default:()=>[10,20,30,50]},layout:{type:String,default:"total, sizes, prev, pager, next, jumper"},background:{type:Boolean,default:!0},autoScroll:{type:Boolean,default:!0},hidden:{type:Boolean,default:!1}},{page:{type:Number,required:!0,default:1},pageModifiers:{},limit:{type:Number,required:!0,default:10},limitModifiers:{}}),emits:d(["pagination"],["update:page","update:limit"]),setup(t,{emit:d}){const g=t,m=d,c=i(t,"page"),f=i(t,"limit");function v(e){c.value=1,m("pagination",{page:c.value,limit:e})}function y(e){m("pagination",{page:e,limit:f.value})}return u(()=>g.total,e=>{const a=Math.ceil(e/f.value);e>0&&c.value>a&&(c.value=a,m("pagination",{page:c.value,limit:f.value}))}),(i,u)=>{const d=a,g=e;return l(),n(g,null,{default:o(()=>[p("div",{class:r([{hidden:t.hidden},"pagination"])},[s(d,{"current-page":c.value,"onUpdate:currentPage":u[0]||(u[0]=e=>c.value=e),"page-size":f.value,"onUpdate:pageSize":u[1]||(u[1]=e=>f.value=e),background:t.background,layout:t.layout,"page-sizes":t.pageSizes,total:t.total,onSizeChange:v,onCurrentChange:y},null,8,["current-page","page-size","background","layout","page-sizes","total"])],2)]),_:1})}}}),[["__scopeId","data-v-02079684"]]);export{m as _};

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.CfwBJonW.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.CvinFEpw.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.D6-EFXZ7.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.D8iu8uPn.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.DEMv9Qiq.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.DFNFECYn.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.DTMNW7RO.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.DUtyrwbD.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.Dga2BcID.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.DjSgV9m5.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.Dp2tmUU_.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.eAAGGHST.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.oeiK9NpL.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.uMY1hFH8.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/index.w_yZAPtI.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/profile.BnPiay3z.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 6 - 0
frontend/dist/js/project.DLvDsB8k.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/select.hWg0mQrH.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/size.XRnSJLyz.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/tenant.4iUbadNE.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 25 - 0
frontend/dist/js/time.BomZslYM.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/tree.zFjxiaNc.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 45 - 0
frontend/dist/js/vite.C8bgex4Y.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/wechat.AmvA8dYX.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
frontend/dist/js/workplace.mvSs3W1x.js


+ 17 - 0
java/sql/009_service_provider_profile.sql

@@ -0,0 +1,17 @@
+-- 服务商多业务凭证配置表
+CREATE TABLE IF NOT EXISTS pay_service_provider_profile (
+    id                  BIGSERIAL PRIMARY KEY,
+    service_provider_id BIGINT NOT NULL,
+    biz_type            VARCHAR(32) NOT NULL,
+    app_id              VARCHAR(64),
+    app_private_key     TEXT,
+    alipay_public_key   TEXT,
+    server_url          VARCHAR(256),
+    format              VARCHAR(16)  DEFAULT 'JSON',
+    charset             VARCHAR(16)  DEFAULT 'UTF-8',
+    sign_type           VARCHAR(16)  DEFAULT 'RSA2',
+    description         VARCHAR(512),
+    created_time        TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_time        TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE(service_provider_id, biz_type)
+);

+ 53 - 3
java/src/main/java/com/payment/platform/core/alipay/AlipayClientFactory.java

@@ -9,7 +9,9 @@ import com.payment.platform.module.payment.enterprise.mapper.EnterpriseMapper;
 import com.payment.platform.module.payment.openapi.entity.OpenConfEntity;
 import com.payment.platform.module.payment.openapi.mapper.OpenConfMapper;
 import com.payment.platform.module.payment.serviceprovider.entity.ServiceProviderEntity;
+import com.payment.platform.module.payment.serviceprovider.entity.ServiceProviderProfileEntity;
 import com.payment.platform.module.payment.serviceprovider.mapper.ServiceProviderMapper;
+import com.payment.platform.module.payment.serviceprovider.mapper.ServiceProviderProfileMapper;
 import jakarta.annotation.PostConstruct;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -35,9 +37,11 @@ public class AlipayClientFactory {
     private final OpenConfMapper openConfMapper;
     private final EnterpriseMapper enterpriseMapper;
     private final ServiceProviderMapper serviceProviderMapper;
+    private final ServiceProviderProfileMapper profileMapper;
 
     private final ConcurrentMap<Long, AlipayClient> providerClients = new ConcurrentHashMap<>();
     private final ConcurrentMap<String, AlipayClient> tenantClients = new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, AlipayClient> profileClients = new ConcurrentHashMap<>();
     private AlipayClient defaultClient;
 
     @PostConstruct
@@ -66,15 +70,32 @@ public class AlipayClientFactory {
     // ==================== 按 enterpriseId 获取(主入口) ====================
 
     public AlipayClient getClient(String enterpriseId) {
+        return getClient(enterpriseId, null);
+    }
+
+    /**
+     * 按企业 + 业务类型获取客户端
+     * <p>
+     * 解析优先级:
+     *   enterprise.serviceProviderId + bizType → pay_service_provider_profile(业务专属凭证)
+     *   enterprise.serviceProviderId → pay_service_provider(默认凭证)
+     *   yml / 默认 fallback
+     */
+    public AlipayClient getClient(String enterpriseId, String bizType) {
         if (enterpriseId == null || enterpriseId.isBlank()) return getClient();
-        // 查企业 → 取 serviceProviderId
         EnterpriseEntity ent = enterpriseMapper.selectOne(
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<EnterpriseEntity>()
                         .eq(EnterpriseEntity::getEnterpriseId, enterpriseId));
         if (ent != null && ent.getServiceProviderId() != null) {
-            return getClientByProvider(ent.getServiceProviderId());
+            Long spId = ent.getServiceProviderId();
+            // 1. 尝试业务专属凭证
+            if (bizType != null) {
+                AlipayClient profileClient = getClientByProfile(spId, bizType);
+                if (profileClient != null) return profileClient;
+            }
+            // 2. 回退到服务商默认凭证
+            return getClientByProvider(spId);
         }
-        // 回退: 找不到企业或无服务商绑定,用默认
         log.debug("企业[{}]未绑定服务商,使用默认客户端", enterpriseId);
         return getClient();
     }
@@ -127,6 +148,35 @@ public class AlipayClientFactory {
 
     // ==================== 内部方法 ====================
 
+    /** 按服务商 + 业务类型获取专属客户端,无配置返回 null */
+    private AlipayClient getClientByProfile(Long spId, String bizType) {
+        String cacheKey = spId + ":" + bizType;
+        AlipayClient cached = profileClients.get(cacheKey);
+        if (cached != null) return cached;
+
+        ServiceProviderProfileEntity profile = profileMapper.selectOne(
+                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ServiceProviderProfileEntity>()
+                        .eq(ServiceProviderProfileEntity::getServiceProviderId, spId)
+                        .eq(ServiceProviderProfileEntity::getBizType, bizType));
+        if (profile == null) return null;
+
+        return profileClients.computeIfAbsent(cacheKey, k -> createClientForProfile(profile));
+    }
+
+    private AlipayClient createClientForProfile(ServiceProviderProfileEntity profile) {
+        AlipayConfig config = buildSdkConfig(
+                profile.getAppId(), profile.getAppPrivateKey(), profile.getAlipayPublicKey(),
+                profile.getServerUrl(), profile.getFormat(), profile.getCharset(), profile.getSignType());
+        try {
+            AlipayClient client = new DefaultAlipayClient(config);
+            log.info("服务商[{}]业务[{}]客户端创建成功, appId={}", profile.getServiceProviderId(), profile.getBizType(), profile.getAppId());
+            return client;
+        } catch (AlipayApiException e) {
+            log.error("服务商[{}]业务[{}]客户端创建失败", profile.getServiceProviderId(), profile.getBizType(), e);
+            return null;
+        }
+    }
+
     private AlipayClient createClientForProvider(Long providerId) {
         ServiceProviderEntity sp = serviceProviderMapper.selectById(providerId);
         if (sp == null) {

+ 5 - 4
java/src/main/java/com/payment/platform/module/payment/facetoface/service/FacetofaceService.java

@@ -17,6 +17,7 @@ import com.payment.platform.module.payment.facetoface.dto.*;
 import com.payment.platform.module.payment.facetoface.entity.FacetofaceOrderEntity;
 import com.payment.platform.module.payment.facetoface.enums.FacetofaceOrderStatus;
 import com.payment.platform.module.payment.facetoface.mapper.FacetofaceOrderMapper;
+import com.payment.platform.module.payment.serviceprovider.enums.ServiceProviderBizType;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -132,7 +133,7 @@ public class FacetofaceService {
                 AlipayOpenAgentCreateRequest createRequest = new AlipayOpenAgentCreateRequest();
                 createRequest.setBizModel(createModel);
 
-                AlipayOpenAgentCreateResponse createResponse = alipayClientFactory.getClient(dto.getEnterpriseId()).execute(createRequest);
+                AlipayOpenAgentCreateResponse createResponse = alipayClientFactory.getClient(dto.getEnterpriseId(), ServiceProviderBizType.FACETOFACE_AGENT.getValue()).execute(createRequest);
                 if (!createResponse.isSuccess())
                     throw new BusinessException(400, "创建应用事务失败: " +
                             (createResponse.getSubMsg() != null ? createResponse.getSubMsg() : createResponse.getMsg()));
@@ -153,7 +154,7 @@ public class FacetofaceService {
                     signRequest.setRate(dto.getRate());
                 }
 
-                AlipayOpenAgentFacetofaceSignResponse signResponse = alipayClientFactory.getClient(dto.getEnterpriseId()).execute(signRequest);
+                AlipayOpenAgentFacetofaceSignResponse signResponse = alipayClientFactory.getClient(dto.getEnterpriseId(), ServiceProviderBizType.FACETOFACE_AGENT.getValue()).execute(signRequest);
                 if (!signResponse.isSuccess())
                     throw new BusinessException(400, "提交当面付签约失败: " +
                             (signResponse.getSubMsg() != null ? signResponse.getSubMsg() : signResponse.getMsg()));
@@ -166,7 +167,7 @@ public class FacetofaceService {
                 AlipayOpenAgentConfirmRequest confirmRequest = new AlipayOpenAgentConfirmRequest();
                 confirmRequest.setBizModel(confirmModel);
 
-                AlipayOpenAgentConfirmResponse confirmResponse = alipayClientFactory.getClient(dto.getEnterpriseId()).execute(confirmRequest);
+                AlipayOpenAgentConfirmResponse confirmResponse = alipayClientFactory.getClient(dto.getEnterpriseId(), ServiceProviderBizType.FACETOFACE_AGENT.getValue()).execute(confirmRequest);
                 if (!confirmResponse.isSuccess())
                     throw new BusinessException(400, "确认提交事务失败: " +
                             (confirmResponse.getSubMsg() != null ? confirmResponse.getSubMsg() : confirmResponse.getMsg()));
@@ -257,7 +258,7 @@ public class FacetofaceService {
             AlipayOpenAgentOrderQueryRequest queryRequest = new AlipayOpenAgentOrderQueryRequest();
             queryRequest.setBizModel(queryModel);
 
-            AlipayOpenAgentOrderQueryResponse queryResponse = alipayClientFactory.getClient(e.getEnterpriseId()).execute(queryRequest);
+            AlipayOpenAgentOrderQueryResponse queryResponse = alipayClientFactory.getClient(e.getEnterpriseId(), ServiceProviderBizType.FACETOFACE_AGENT.getValue()).execute(queryRequest);
 
             if (queryResponse.isSuccess()) {
                 OffsetDateTime now = OffsetDateTime.now();

+ 26 - 0
java/src/main/java/com/payment/platform/module/payment/serviceprovider/entity/ServiceProviderProfileEntity.java

@@ -0,0 +1,26 @@
+package com.payment.platform.module.payment.serviceprovider.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.payment.platform.common.base.PaymentBaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 服务商多业务凭证配置 — 同一服务商不同业务场景可使用不同 app_id/密钥
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("pay_service_provider_profile")
+public class ServiceProviderProfileEntity extends PaymentBaseEntity {
+
+    private Long serviceProviderId;
+    private String bizType;
+    private String appId;
+    private String appPrivateKey;
+    private String alipayPublicKey;
+    private String serverUrl;
+    private String format;
+    private String charset;
+    private String signType;
+    private String description;
+}

+ 22 - 0
java/src/main/java/com/payment/platform/module/payment/serviceprovider/enums/ServiceProviderBizType.java

@@ -0,0 +1,22 @@
+package com.payment.platform.module.payment.serviceprovider.enums;
+
+import lombok.Getter;
+
+/**
+ * 服务商业务类型 — 用于 pay_service_provider_profile.biz_type
+ */
+@Getter
+public enum ServiceProviderBizType {
+
+    /** 当面付代开通 ISV Agent */
+    FACETOFACE_AGENT("FACETOFACE_AGENT"),
+
+    /** 当面付收款通知 */
+    FACETOFACE_NOTIFY("FACETOFACE_NOTIFY");
+
+    private final String value;
+
+    ServiceProviderBizType(String value) {
+        this.value = value;
+    }
+}

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio