瀏覽代碼

chore(乐收赢): 补交租户角色页面授权 SQL(此前一直只在工作区)

`lsy-tenant-page-grants.sql` 是 2026-09-28 那批交付里唯一**没有被跟踪**的文件 ——
本仓其余 13 份 `db/lsy-*.sql` 全部入库,只有它挂在 `?? ` 下。交付件不进版本库
就会静默消失(本仓已有前例),所以在用户执行它之前先把它落进 git。**只落文件,
没有对任何库跑过。**

内容:把「银联收单」目录(`route_name='Collection'`)连同其下三个只读页面
(`CollectionOrder` / `CollectionRefund` / `CollectionMerchant`)补授给
租户角色(`role_id = 2`)。

为什么必须连目录一起授:`UserService.buildMenuTree` 对「父不在集合里」的子节点
是**静默丢弃**(无 else 分支、不报错)⇒ 只授页面等于没授。

已知的行为取舍(脚本头部第 38-48 行已写明,此处不重复论证):三个页面里
`CollectionMerchant` 是唯一带写权限的(page/save/delete 共用同一个权限串),
执行前可以只删掉 `pages` 的 VALUES 里那一行。

幂等:末条 INSERT 逐行 `NOT EXISTS`,重跑不插重复行也不报错。
alphaH 2 周之前
父節點
當前提交
c1afbd1595
共有 1 個文件被更改,包括 103 次插入 和 0 次删除
  1. 103 0
      java/src/main/resources/db/lsy-tenant-page-grants.sql

+ 103 - 0
java/src/main/resources/db/lsy-tenant-page-grants.sql

@@ -0,0 +1,103 @@
+-- ============================================================================
+-- 乐收赢 · 把「银联收单」目录下的三个**只读**页面补授给租户角色(role_id = 2)
+--
+-- 【本脚本尚未执行】执行由用户做。执行前请先读下面「★ 为什么补」与「★ 你可以否掉哪一条」。
+--
+-- 交付日期:2026-09-28
+-- 背景批次:收银台(扫码收款)页面联调。执行完 `lsy-cashier-menu.sql` 之后,
+--           租户角色拿到了「银联收单 → 扫码收款」,但**看不到**同目录下的
+--           支付订单 / 退款记录 / 商户映射 —— 本脚本补上。
+-- ============================================================================
+--
+-- ── ★ 为什么补:三条实测,不是推测 ─────────────────────────────────────────
+--
+-- 1) 活库只读实测(2026-09-28,`GET /system/role/detail/2` 递归遍历):
+--      角色 2(租户角色,17 个用户持有)持 54 个菜单,**268/269/270/271/272 一个都没有**;
+--      同法量角色 1(管理员角色):120 个菜单,五个 id 全有。
+--      ⇒ 两向读数,量具能分辨,不是「查不出来」。
+--
+-- 2) 文档记的**原本**形态是两个角色都有:`lsy-onboarding-menu.sql:105` 写
+--      「「收付款」目录(id=268)与三个 lsy 兄弟页(269/270/271)**也都授给了两个角色**」。
+--      (`lsy-menu.sql` 自己那句「授权给 role_id = 1」是它初版写的,对不上活库现状 ——
+--        同一份脚本第 141 行也承认了这一点。)
+--      ★ 成因**未定,不猜**:可能是有人改过角色配置,也可能是
+--        「角色详情不带授权 ⇒ 抽屉全不勾且保存清空授权」那个缺陷(`1bda59a`,2026-09-28 才修)
+--        期间被谁点过一次保存。两种都不改变本脚本要恢复的目标形态。
+--
+-- 3) 界面文案**要求**租户去那一页:收银台在没有商户号时抛
+--      「该租户未配置商户号,请先在「手工登记商户号」中登记」
+--      (`LsyMerchantService.requireByTenant`)。而「手工登记商户号」正是
+--      `route_name = 'CollectionMerchant'` 那一页改名后的名字。
+--      ⇒ 租户没有这一页时,这句话是**一条死路**:它指的地方租户进不去。
+--
+-- ── ★ 你可以否掉哪一条 ────────────────────────────────────────────────────
+--
+-- 前两页(支付订单 / 退款记录)是**纯读**:控制器只有 `GET /page` 与 `GET /{...}`,
+-- 没有写端点,租户看到的是自己租户的行(实体继承 PaymentTenantBaseEntity,租户拦截器追加条件)。
+--
+-- **第三页(商户映射)是唯一有写权限的**,所以单独说明,你可以只删这一段:
+--   · 该页三个端点(page / save / delete)**共用同一个权限串**
+--     `module_payment:leshouying:merchant:list` ⇒ 授了页面就等于授了新增与删除;
+--   · 新增时归属被强制取自认证上下文(`LeshouyingMerchantController.save` 里
+--     `entity.setTenantId(null)`),请求体里写 tenant_id 无效;
+--   · `uk_lsy_merchant_mch_no` 是 **mch_no 全局唯一**索引 ⇒ 别人已登记的商户号抢不走。
+--   · 残余风险(我认为可接受,但这是判断):租户可以登记一个**尚无人登记**的商户号。
+--     最坏后果是让那个商户号的账户收到本租户顾客的钱(钱进的是对方账户),
+--     以及**占位**(该号之后别人登记不了,需管理员 delete 释放)。
+--   · 若你不接受这一点:把本文件里 `CollectionMerchant` 那一行从 `pages` 的 VALUES 中删掉再执行,
+--     并把上面第 3 条的文案改成「请联系平台管理员开通」。
+--
+-- ── ★ 幂等 ────────────────────────────────────────────────────────────────
+--
+-- 末条 INSERT 逐行 `NOT EXISTS`:**重跑不插重复行,也不报错**。
+-- 与 `lsy-cashier-menu.sql` 不同,本脚本**不依赖任何 CTE 的非空性** ——
+-- 三个页面若已被手工删除,本脚本只授它找得到的行,不会去重建页面(重建请重跑 `lsy-menu.sql`,
+-- 但那份脚本在「目录在、页面被删」时也不会恢复,见它自己的注释)。
+--
+-- ── ★ 只读核对(执行后跑这三条)────────────────────────────────────────────
+--
+-- 1) 目录与三个页面在不在、route_name 是什么(锚点用身份,不用会变的 name):
+--   SELECT id, name, type, route_name, route_path, status
+--     FROM sys_menu
+--    WHERE route_name IN ('Collection','CollectionOrder','CollectionRefund','CollectionMerchant')
+--    ORDER BY id;
+--   期望:**4 行**,status 全 '0'。(本次实测 id:268 目录 / 269 / 270 / 271,
+--         但 id 不是锚点,别把它当判据。)
+--
+-- 2) 授权行:
+--   SELECT rm.role_id, m.name, m.permission
+--     FROM sys_role_menus rm JOIN sys_menu m ON m.id = rm.menu_id
+--    WHERE m.route_name IN ('Collection','CollectionOrder','CollectionRefund','CollectionMerchant')
+--    ORDER BY m.id, rm.role_id;
+--   期望:role 1 与 role 2 **各 4 行 = 共 8 行**。
+--
+-- 3) 端到端(界面):以租户账号重新登录 ⇒ 「银联收单」下应出现 4 个页面。
+--    ★ 必须**重新登录**:路由与权限来自登录时下发的 user 快照,不重登看不到变化。
+--
+-- ── ★ 收回 ────────────────────────────────────────────────────────────────
+--
+--   DELETE FROM sys_role_menus
+--    WHERE role_id = 2
+--      AND menu_id IN (SELECT id FROM sys_menu
+--                       WHERE route_name IN ('Collection','CollectionOrder',
+--                                            'CollectionRefund','CollectionMerchant'));
+--   ★ 收回**目录行**(`route_name='Collection'`)才是让整块从角色 2 视野里消失的那一下 ——
+--     `UserService.buildMenuTree` 对「父不在集合里」的子节点是**静默丢弃**(无 else 分支,不报错)。
+--   ★ 若只想收回商户映射一页,把上面 IN 列表里的 'CollectionMerchant' 去掉即可;
+--     但**不要**连 'Collection' 一起收回,否则另外两页也会一起消失。
+-- ============================================================================
+
+-- 目录 + 三个页面,一次算出要授的 menu_id 集合。锚 route_name(身份),不锚 name(标签)。
+WITH pages AS (
+    SELECT id FROM sys_menu
+     WHERE route_name IN ('CollectionOrder', 'CollectionRefund', 'CollectionMerchant')
+       AND type = 2 AND status = '0'
+    UNION ALL
+    SELECT id FROM sys_menu
+     WHERE route_name = 'Collection' AND type = 1 AND status = '0'
+)
+INSERT INTO sys_role_menus (role_id, menu_id)
+SELECT 2, p.id
+  FROM pages p
+ WHERE NOT EXISTS (SELECT 1 FROM sys_role_menus rm
+                    WHERE rm.role_id = 2 AND rm.menu_id = p.id);