Prechádzať zdrojové kódy

refactor(乐收赢): 管理端商户表单去掉「企业 ID」,与后端的去企业化对齐

后端 `LsyMerchantEntity.enterpriseId` 已删(f529b44d),前端这一格必须同步拿掉 ——
留着它**不是**「多传一个被忽略的键」:本仓 `FAIL_ON_UNKNOWN_PROPERTIES` 是 true,
表单带着它提交就是 HTTP 500。

改动:
  · 商户页:删掉表格那一列「企业 ID」(原先把空值渲染成「租户级」标签)、
    表单那一格、白名单里的 `enterprise_id`、`emptyForm()` 的初值、以及那条
    「没有规则,因为它是选填的」注释;关键字搜索的占位符去掉「企业ID」
    (后端 `page()` 的同名 LIKE 已随之删除,留着占位符就是一句空承诺)。
  · 顶部那句说明改了:原来说「企业 ID 留空 = 整个租户共用;填了 = 只有这家企业用」——
    现在改说**一个租户可以登记多个商户号、下单时用 merchant_code 指定**,
    这才是用户接下来要照做的规则。
  · 删除确认框的措辞:原先是 `row.enterprise_id ? 企业「…」: "本租户"`,
    改成被删那一行自己的两项身份(商户号 + 归属租户)—— 比一句笼统的「本租户」
    更能让操作员确认自己删的是哪一行。
  · 进件页 `CREATE_KEYS` 的注释:那份「绝不送的键」列表原先有第五项 `enterprise_id`,
    现在它是**无处可发**(连声明都没有了),列表从五项减到四项。计数跟着改。
  · `api/module_payment/collection.ts` 文件头第 3 条的举例把 `enterprise_id`
    换成 `tenant_id`,并写明「现在发它就是 500」。

★ 部署顺序(与 DDL 那条并列,两条都要守):
    ① 先执行 `db/lsy-merchant-multi-per-tenant.sql`;
    ② 后端 jar 与前端 `dist` **同批上**。
    反过来(新后端 + 老前端)的失败形态是:老页面仍带 `enterprise_id` 提交 ⇒ 保存 500。
    中间的窗口里,用户刷新一次页面就能拿到新前端。

读数:`pnpm ts:check` 289 条(与记录的基线同数),本次改动的三个文件**零报错**。
★ 浏览器渲染未验 —— 那是用户的最终验收。
alphaH 2 týždňov pred
rodič
commit
3fb8df5ef2

+ 3 - 1
frontend/src/api/module_payment/collection.ts

@@ -18,7 +18,9 @@ import request from "@/utils/request";
  *    是本仓已上线的实证。
  *
  * 3. **POST body 也必须 snake_case**(`saveLsyMerchant`):后端 `@RequestBody LsyMerchantEntity`,
- *    同一个 SNAKE_CASE 策略 ⇒ 键名要 `enterprise_id` / `mch_no` / `shop_no` / `merchant_name`。
+ *    同一个 SNAKE_CASE 策略 ⇒ 键名要 `tenant_id` / `mch_no` / `shop_no` / `merchant_name`。
+ *    ★ 举例里原先还有 `enterprise_id` —— 该字段 2026-09-30 去企业化时已从实体删除,
+ *      现在**发它就是 500**(`FAIL_ON_UNKNOWN_PROPERTIES` 是 true),不再是「键名对不对」的问题。
  */
 
 // ==================== 订单 ====================

+ 27 - 34
frontend/src/views/module_payment/collection/merchant/index.vue

@@ -9,7 +9,7 @@
       style="margin-bottom: 12px"
       :title="
         isSuperAdmin
-          ? '手工登记商户号。收款前必须先在这里登记一行 —— 下单时提示「未配置商户号」就是这里还没登记。新增时要选择归属租户;企业 ID 留空 = 整个租户共用这个号;填了 = 只有这家企业用。'
+          ? '手工登记商户号。收款前必须先在这里登记一行 —— 下单时提示「未配置商户号」就是这里还没登记。新增时要选择归属租户;一个租户可以登记多个商户号,下单时用 merchant_code 指定用哪一个(不指定且只有一个时才自动选中)。'
           : '这里显示的是本租户已登记的商户号(只读)。商户号由平台运营录入,如需新增或修改请联系平台运营。'
       "
     />
@@ -19,7 +19,7 @@
         <el-form-item label="关键字">
           <el-input
             v-model="query.keyword"
-            placeholder="商户号 / 商户名 / 企业ID"
+            placeholder="商户号 / 商户名"
             clearable
             style="width: 260px"
             @keyup.enter="handleSearch"
@@ -41,14 +41,6 @@
         <el-table-column v-if="isSuperAdmin" label="租户" min-width="140">
           <template #default="{ row }">{{ tenantName(row.tenant_id) }}</template>
         </el-table-column>
-        <!-- ★ 留空的 enterprise_id 必须显示成「租户级」而不是空白格:
-             空单元格与「这一列没数据」无法区分,管理员看不出哪一行是租户级兜底映射。 -->
-        <el-table-column label="企业 ID" min-width="180">
-          <template #default="{ row }">
-            <el-tag v-if="!row.enterprise_id" type="warning" size="small">租户级</el-tag>
-            <span v-else>{{ row.enterprise_id }}</span>
-          </template>
-        </el-table-column>
         <el-table-column prop="mch_no" label="商户号" min-width="160" />
         <el-table-column prop="shop_no" label="门店号" min-width="140" />
         <el-table-column prop="merchant_name" label="商户名称" min-width="180" />
@@ -110,14 +102,12 @@
             />
           </el-select>
         </el-form-item>
-        <!-- ★ 选填(2026-09-22 去企业化):留空 ⇒ 这一行是**租户级**映射,
-             下单时不带 enterprise_id 的请求都会落到它上面。 -->
-        <el-form-item label="企业 ID" prop="enterprise_id">
-          <el-input
-            v-model="form.enterprise_id"
-            placeholder="可空 —— 留空即租户级(该租户下所有下单可用)"
-          />
-        </el-form-item>
+        <!-- ★ 2026-09-30 去企业化:这里原先还有一个「企业 ID」(选填,留空 = 租户级)。
+             乐收赢与企业概念无关,那一格是整个模块里最后两个企业维度的消费者之一
+             (另一个是下单入口的寻址),两者本批都已拿掉;后端 `LsyMerchantEntity`
+             的 `enterpriseId` 字段也已删。**别再把它加回来**:加了就是一个
+             填了也无效的输入框 —— 后端没有这个字段,`FAIL_ON_UNKNOWN_PROPERTIES`
+             会让整个保存请求报错(见 `LsyMerchantService.save` 的 javadoc)。 -->
         <el-form-item label="商户号" prop="mch_no">
           <el-input v-model="form.mch_no" placeholder="请输入商户号" />
         </el-form-item>
@@ -199,18 +189,20 @@ const formVisible = ref(false);
 const formRef = ref();
 
 /**
- * 表单**只认**这 7 个键(`id` + `tenant_id` + 5 个业务字段)。其余字段(含服务端将来新增的)一律不得进入表单模型 ——
+ * 表单**只认**这 6 个键(`id` + `tenant_id` + 4 个业务字段)。其余字段(含服务端将来新增的)一律不得进入表单模型 ——
  * `buildForm` 是按这张白名单**逐键取值**的,不是把服务端整行摊进来。
  *
  * ★ `tenant_id` 是 2026-09-30 加进来的(超管代录要选归属)。加了它就会**在编辑态被回传**,
  *   而后端编辑分支一律忽略这个值(改归属在结构上不成立,见 `LsyMerchantService#save` 的
  *   javadoc)—— 那不是「多传了一个没用的键」:不放进白名单的话,`buildForm` 会把它丢掉,
  *   下拉框在编辑态就是空的。
+ *
+ * ★ 同一天去掉的 `enterprise_id`(原第 3 项):后端实体已删该字段,留着它会让保存请求
+ *   撞 `FAIL_ON_UNKNOWN_PROPERTIES` 直接报错 —— 不是「多传一个被忽略的键」。
  */
 const FORM_FIELDS = [
   "id",
   "tenant_id",
-  "enterprise_id",
   "mch_no",
   "shop_no",
   "merchant_name",
@@ -220,13 +212,12 @@ const FORM_FIELDS = [
 /**
  * ★ 键名必须 snake_case:后端 `@RequestBody LsyMerchantEntity` 走全局 SNAKE_CASE 反序列化。
  *
- * ★ 可空列(V1.8 DDL:`shop_no` / `merchant_name` / `description`,2026-09-22 起加上
- *   `enterprise_id`)的初值是 `null` 而**不是** `""`:
- *   这四列的语义是「空 ⇒ 库里 NULL」,而 `""` 与 `NULL` 在库里是两回事 ——
+ * ★ 可空列(V1.8 DDL:`shop_no` / `merchant_name` / `description`)的初值是 `null`
+ *   而**不是** `""`:这三列的语义是「空 ⇒ 库里 NULL」,而 `""` 与 `NULL` 在库里是两回事 ——
  *   表格里都显示空白,但 `WHERE shop_no IS NULL` 查不到 `''`,于是数据悄悄查不出来。
  *   初值给 `""` 会让「打开新增表单、什么都不填直接保存」写出空串。
- *   `enterprise_id` 上这条尤其致命:租户级映射就是靠 `enterprise_id IS NULL` 找的,
- *   存成 `''` 之后下单方直接报「未配置商户号」。
+ *   (这句话原先还举了 `enterprise_id` 当「尤其致命」的例子 —— 那个字段已随去企业化删除,
+ *     例子连同字段一起作废,但规则本身对剩下这三列一字不变。)
  *   `el-input` 把 `null` 当空串显示(不会报错也不会显示 "null"),界面无差别。
  */
 function emptyForm(): Record<string, any> {
@@ -236,7 +227,6 @@ function emptyForm(): Record<string, any> {
     // 空串会绕过那道判断并以 "" 进 INSERT —— 而 tenant_id 是 bigint,""
     // 在 PG 上直接是类型错误(报错位置离现场很远)。
     tenant_id: null,
-    enterprise_id: null,
     mch_no: "",
     shop_no: null,
     merchant_name: null,
@@ -247,7 +237,7 @@ function emptyForm(): Record<string, any> {
 /**
  * ★ 白名单投影 —— **不要**改回 `Object.assign(form, row)` / `{ ...row }`。
  *
- * 服务端回的是**整行**:除这 7 个键外还带 status / review_status / created_time / updated_time,
+ * 服务端回的是**整行**:除这 6 个键外还带 status / review_status / created_time / updated_time,
  * 以及**以后新增的任何字段**。整行摊进表单会静默写坏数据(每一跳都有依据):
  *   · `status` / `review_status` —— 新增时,从上一行残留的非空值会**直接进 INSERT**
  *     (后端只在空值时才补默认值);编辑时后端已改成定向更新、不写这两列,
@@ -276,9 +266,7 @@ function buildForm(row?: any): Record<string, any> {
 
 const form = ref<Record<string, any>>(emptyForm());
 
-// ★ `enterprise_id` 没有规则 —— 它是选填的(留空 = 租户级)。
-//   不要加回 required:那会把「配租户级映射」这条正常路径堵死在表单校验上,
-//   而服务端早就不认这个前提了(LsyMerchantService.save 只校验 mch_no)。
+// ★ 其余字段(shop_no / merchant_name / description)都没有规则:它们是选填的。
 //
 // ★ `tenant_id` 只在**新增**时必填(与后端 `save` 的新增分支同一条件)。
 //   编辑态不加规则:归属改不了(下拉已置灰),而给旧数据卡一条必填会让超管
@@ -331,10 +319,15 @@ async function handleSubmit() {
 }
 
 async function handleDelete(row: any) {
-  const scope = row.enterprise_id ? `企业「${row.enterprise_id}」` : "本租户";
-  await ElMessageBox.confirm(`确认删除登记「${row.mch_no}」?删除后${scope}将无法下单。`, "提示", {
-    type: "warning",
-  });
+  // ★ 2026-09-30 去企业化:这里原先是 `row.enterprise_id ? \`企业「…」\` : "本租户"`。
+  //   企业那一维度没了,措辞改成被删的那一行自己的两项身份(商户号 + 归属租户),
+  //   比一句笼统的「本租户」更能让操作员确认自己删的是哪一行。
+  const owner = isSuperAdmin.value ? `(租户 ${tenantName(row.tenant_id)})` : "(本租户)";
+  await ElMessageBox.confirm(
+    `确认删除登记「${row.mch_no}」${owner}?删除后该商户号将无法下单。`,
+    "提示",
+    { type: "warning" }
+  );
   await deleteLsyMerchant(row.id);
   // 成功提示同上:由拦截器统一弹,页面不重复弹。
   loadData();

+ 5 - 3
frontend/src/views/module_payment/collection/onboarding/index.vue

@@ -869,11 +869,13 @@ const SECTIONS: Section[] = [
  *   而且这里的代价是**硬失败**:本仓 `FAIL_ON_UNKNOWN_PROPERTIES` 是 `true`
  *   (`JacksonConfig` 绕过了 Spring 的 builder)⇒ 多送**一个**键就是 **HTTP 500**(不是 400)。
  *
- * ★ 五个**绝不送**的键根本不在这张表里,也就无处可送(§五):
+ * ★ 四个**绝不送**的键根本不在这张表里,也就无处可送(§五):
  *   `tenant_id`(控制器 `rejectBodySuppliedTenantId` 当场拒)、
  *   `rate_code`(`rejectBodySuppliedRateCode` 当场拒)、
- *   `merchant_type` / `account_type`(由 `category` **派生**,DTO 上根本没有这两个字段)、
- *   `enterprise_id`(控制器内类的本地业务字段,非必填,本任务不发)。
+ *   `merchant_type` / `account_type`(由 `category` **派生**,DTO 上根本没有这两个字段)。
+ *   ★ 2026-09-30:原先这里还有第五项 `enterprise_id`(控制器内类的本地业务字段)。
+ *     那个字段已随去企业化从 `CreateMerchantRequest` 上**删除**,不再是「不发」,而是
+ *     「无处可发」—— 连声明都没有了,所以本列表从五项变四项。
  *
  * ★ 键名一律 snake_case(见 `api/module_payment/onboarding.ts` 文件头指回的三条契约)。
  */