boss před 2 týdny
rodič
revize
d9c1289e8e
30 změnil soubory, kde provedl 543 přidání a 118 odebrání
  1. 2 2
      fs-admin-saas/src/main/java/com/fs/company/controller/workflow/SaasMissingApisStubController.java
  2. 2 2
      fs-admin-saas/src/main/java/com/fs/web/controller/system/AdminMenuController.java
  3. 34 0
      fs-admin/src/main/java/com/fs/admin/controller/AdminCompanyBridgeController.java
  4. 120 6
      fs-admin/src/main/java/com/fs/admin/controller/CompanyAdminController.java
  5. 27 80
      fs-admin/src/main/java/com/fs/admin/controller/CompanyUserAdminController.java
  6. 2 2
      fs-admin/src/main/java/com/fs/web/controller/system/AdminMenuController.java
  7. 1 1
      fs-common/src/main/java/com/fs/common/core/domain/entity/AdminMenu.java
  8. 11 0
      fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java
  9. 2 2
      fs-service/src/main/java/com/fs/hisStore/domain/FsMenuScrm.java
  10. 12 0
      fs-service/src/main/java/com/fs/qw/mapper/QwIpadServerMapper.java
  11. 13 0
      fs-service/src/main/java/com/fs/qw/service/IQwIpadServerService.java
  12. 20 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwIpadServerServiceImpl.java
  13. 39 0
      fs-service/src/main/java/com/fs/qw/vo/IpadTenantStatsVO.java
  14. 22 0
      fs-service/src/main/java/com/fs/qw/vo/QwIpadServerVO.java
  15. 1 1
      fs-service/src/main/java/com/fs/system/mapper/AdminMenuMapper.java
  16. 10 0
      fs-service/src/main/java/com/fs/system/mapper/SysUserMapper.java
  17. 1 1
      fs-service/src/main/java/com/fs/system/service/IAdminMenuService.java
  18. 5 0
      fs-service/src/main/java/com/fs/system/service/ISysUserService.java
  19. 1 1
      fs-service/src/main/java/com/fs/system/service/impl/AdminMenuServiceImpl.java
  20. 5 0
      fs-service/src/main/java/com/fs/system/service/impl/SysUserServiceImpl.java
  21. 8 0
      fs-service/src/main/java/com/fs/tenant/mapper/TenantInfoMapper.java
  22. 8 0
      fs-service/src/main/java/com/fs/tenant/service/TenantInfoService.java
  23. 5 0
      fs-service/src/main/java/com/fs/tenant/service/impl/TenantInfoServiceImpl.java
  24. 5 5
      fs-service/src/main/resources/mapper/hisStore/FsMenuScrmMapper.xml
  25. 33 0
      fs-service/src/main/resources/mapper/qw/QwIpadServerMapper.xml
  26. 15 15
      fs-service/src/main/resources/mapper/system/AdminMenuMapper.xml
  27. 60 0
      fs-service/src/main/resources/mapper/system/SysUserMapper.xml
  28. 5 0
      fs-service/src/main/resources/mapper/tenant/TenantInfoMapper.xml
  29. 24 0
      sql/create_company_miniapp.sql
  30. 50 0
      sql/create_tenant_sys_menu.sql

+ 2 - 2
fs-admin-saas/src/main/java/com/fs/company/controller/workflow/SaasMissingApisStubController.java

@@ -26,7 +26,7 @@ public class SaasMissingApisStubController extends BaseController {
         "watch_device_info", "watch_iot_card",
         "shop_msg", "shop_records", "shop_role",
         "fs_promotion_order", "fs_store_adv", "fs_health_store_order",
-        "fs_home_article", "fs_home_category", "fs_home_view", "fs_menu",
+        "fs_home_article", "fs_home_category", "fs_home_view", "sys_menu",
         "fs_prescribe_drug", "fs_recommend",
         "fs_shipping_templates", "fs_shipping_templates_free", "fs_shipping_templates_region",
         "fs_store_activity", "fs_store_after_sales_item", "fs_store_after_sales_status",
@@ -112,7 +112,7 @@ public class SaasMissingApisStubController extends BaseController {
     public TableDataInfo storeHomeView() { return safeListFromTable("fs_home_view"); }
 
     @GetMapping("/store/menu/list")
-    public TableDataInfo storeMenu() { return safeListFromTable("fs_menu"); }
+    public TableDataInfo storeMenu() { return safeListFromTable("sys_menu"); }
 
     @GetMapping("/store/prescribeDrug/list")
     public TableDataInfo storePrescribeDrug() { return safeListFromTable("fs_prescribe_drug"); }

+ 2 - 2
fs-admin-saas/src/main/java/com/fs/web/controller/system/AdminMenuController.java

@@ -17,8 +17,8 @@ import com.fs.common.core.domain.entity.AdminMenu;
 import com.fs.system.service.IAdminMenuService;
 
 /**
- * 平台菜单管理Controller (fs_menu)
- * 供adminui使用,与sys_menu物理隔离
+ * 平台菜单管理Controller (sys_menu)
+ * 供adminui使用
  */
 @RestController
 @RequestMapping("/fs/menu")

+ 34 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminCompanyBridgeController.java

@@ -9,6 +9,10 @@ import com.fs.company.domain.*;
 import com.fs.company.param.*;
 import com.fs.company.service.*;
 import com.fs.company.vo.*;
+import com.fs.qw.service.IQwIpadServerService;
+import com.fs.qw.domain.QwIpadServer;
+import com.fs.qw.vo.IpadTenantStatsVO;
+import com.fs.qw.vo.QwIpadServerVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -69,6 +73,9 @@ public class AdminCompanyBridgeController extends BaseController {
     @Autowired(required = false)
     private com.fs.company.service.ICompanyVoicePackageService companyVoicePackageService;
 
+    @Autowired(required = false)
+    private IQwIpadServerService qwIpadServerService;
+
     // ========== 通话接口管理 /company/companyVoiceApi ==========
     @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:list')")
     @GetMapping("/company/companyVoiceApi/list")
@@ -526,4 +533,31 @@ public class AdminCompanyBridgeController extends BaseController {
             companyVoiceMobileService.selectCompanyVoiceMobileVOList(param) : new ArrayList<>();
         return getDataTable(list);
     }
+
+    // ========== [Admin] iPad租户维度统计 /admin/ipad-stats ==========
+    @GetMapping("/admin/ipad-stats")
+    public AjaxResult adminIpadStats() {
+        if (qwIpadServerService == null) return AjaxResult.error("服务不可用");
+        List<IpadTenantStatsVO> stats = qwIpadServerService.selectIpadTenantStats();
+        QwIpadServer padInfo = qwIpadServerService.getPadInfo();
+        long totalCapacity = padInfo != null && padInfo.getTotalCount() != null ? padInfo.getTotalCount() : 0;
+        long remaining = padInfo != null && padInfo.getCount() != null ? padInfo.getCount() : 0;
+        Map<String, Object> result = new HashMap<>();
+        result.put("tenantStats", stats);
+        result.put("totalCapacity", totalCapacity);
+        result.put("used", totalCapacity - remaining);
+        result.put("remaining", remaining);
+        result.put("serverCount", qwIpadServerService.count());
+        return AjaxResult.success(result);
+    }
+
+    // ========== [Admin] iPad服务器列表(含租户统计) /admin/ipad-server-list ==========
+    @GetMapping("/admin/ipad-server-list")
+    public TableDataInfo adminIpadServerList(@RequestParam(required = false) Long companyId,
+                                              @RequestParam(required = false) String ip) {
+        if (qwIpadServerService == null) return getDataTable(new ArrayList<>());
+        startPage();
+        List<QwIpadServerVO> list = qwIpadServerService.selectIpadServerWithTenantStats(companyId, ip);
+        return getDataTable(list);
+    }
 }

+ 120 - 6
fs-admin/src/main/java/com/fs/admin/controller/CompanyAdminController.java

@@ -4,10 +4,15 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysMenu;
+import com.fs.common.core.domain.entity.TenantCompanyMenu;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.framework.datasource.TenantDataSourceManager;
 import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.mapper.TenantInfoMapper;
 import com.fs.tenant.service.TenantInfoService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -15,9 +20,8 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 总后台租户管理控制器(SaaS 多租户版)
@@ -30,6 +34,12 @@ public class CompanyAdminController extends BaseController {
     @Autowired
     private TenantInfoService tenantInfoService;
 
+    @Autowired
+    private TenantInfoMapper tenantInfoMapper;
+
+    @Autowired
+    private TenantDataSourceManager tenantDataSourceManager;
+
     /**
      * 查询所有租户列表
      */
@@ -136,13 +146,117 @@ public class CompanyAdminController extends BaseController {
 
     /**
      * 租户充值/扣款
-     * TODO 后续优化:需要切到租户库查询 company 表余额并操作充值/扣款
      */
     @PreAuthorize("@ss.hasPermi('admin:company:edit')")
     @Log(title = "租户管理", businessType = BusinessType.UPDATE)
     @PostMapping("/{id}/recharge")
     public AjaxResult rechargeCompany(@PathVariable String id, @RequestBody Map<String, Object> params) {
-        // TODO 后续优化:切到租户库执行充值/扣款逻辑
-        return AjaxResult.error("充值/扣款功能暂未对接多租户,后续优化");
+        String operateType = (String) params.get("operateType");
+        Object amountObj = params.get("amount");
+        String remark = params.get("remark") != null ? params.get("remark").toString() : "";
+
+        if (amountObj == null) {
+            return AjaxResult.error("金额不能为空");
+        }
+        java.math.BigDecimal amount;
+        try {
+            amount = new java.math.BigDecimal(amountObj.toString());
+        } catch (NumberFormatException e) {
+            return AjaxResult.error("金额格式不正确");
+        }
+        if (amount.compareTo(java.math.BigDecimal.ZERO) <= 0) {
+            return AjaxResult.error("金额必须大于0");
+        }
+
+        // 扣款时金额取负
+        java.math.BigDecimal delta = "deduct".equals(operateType) ? amount.negate() : amount;
+
+        int rows = tenantInfoService.updateBalance(Long.valueOf(id), delta);
+        if (rows > 0) {
+            return AjaxResult.success("recharge".equals(operateType) ? "充值成功" : "扣款成功");
+        }
+        return AjaxResult.error("recharge".equals(operateType) ? "充值失败,租户不存在" : "扣款失败,余额不足或租户不存在");
+    }
+
+    // ==================== 租户菜单编辑(Bridge → 切租户库) ====================
+
+    /**
+     * 获取租户管理端菜单树(合并模板菜单+租户已有菜单)
+     * flag=sys → 管理端菜单(tenant_sys_menu/租户库sys_menu)
+     * flag=com → 销售端菜单(tenant_company_menu/租户库company_menu)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:edit')")
+    @PostMapping("/{id}/menu")
+    public R tenantMenuChange(@PathVariable String id, @RequestBody Map<String, String> params) {
+        String flag = params.getOrDefault("flag", "sys");
+        TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(id);
+        if (tenantInfo == null) return R.error("租户不存在");
+        if (tenantInfo.getStatus() != null && tenantInfo.getStatus() == 2) return R.error("租户初始化中");
+
+        if ("sys".equals(flag)) {
+            List<SysMenu> sysMenus = tenantInfoMapper.selectMenuList(new SysMenu());
+            tenantDataSourceManager.switchTenant(tenantInfo);
+            return tenantInfoService.menuChange(flag, sysMenus, null);
+        }
+
+        List<TenantCompanyMenu> companyMenus = tenantInfoMapper.selectCompanyMenuList(new TenantCompanyMenu());
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        return tenantInfoService.menuChange(flag, null, companyMenus);
+    }
+
+    /**
+     * 编辑租户菜单(勾选/取消勾选/新增)
+     */
+    @PreAuthorize("@ss.hasPermi('admin:company:edit')")
+    @Log(title = "编辑租户菜单", businessType = BusinessType.UPDATE)
+    @PostMapping("/{id}/menu/edit")
+    public R tenantMenuEdit(@PathVariable String id, @RequestBody Map<String, Object> params) {
+        String flag = (String) params.getOrDefault("flag", "sys");
+        @SuppressWarnings("unchecked")
+        List<Long> selected = params.get("selected") != null ? (List<Long>) params.get("selected") : new ArrayList<>();
+        @SuppressWarnings("unchecked")
+        List<Long> unSelected = params.get("unSelected") != null ? (List<Long>) params.get("unSelected") : new ArrayList<>();
+
+        TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(id);
+        if (tenantInfo == null) return R.error("租户不存在");
+        if (tenantInfo.getStatus() != null && tenantInfo.getStatus() == 2) return R.error("租户初始化中");
+
+        if ("sys".equals(flag)) {
+            List<SysMenu> addSysMenu = getAddSysMenu(tenantInfo, selected);
+            tenantDataSourceManager.switchTenant(tenantInfo);
+            return tenantInfoService.menuEdit(selected, unSelected, flag, addSysMenu, null);
+        }
+
+        List<TenantCompanyMenu> addCompanyMenu = getAddCompanyMenu(tenantInfo, selected);
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        return tenantInfoService.menuEdit(selected, unSelected, flag, null, addCompanyMenu);
+    }
+
+    /**
+     * 获取租户库中需要新增的后台菜单
+     */
+    private List<SysMenu> getAddSysMenu(TenantInfo tenantInfo, List<Long> selected) {
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        List<Long> existIds = tenantInfoMapper.selectExistMenuIds();
+        List<Long> needAddIds = selected.stream().filter(mid -> !existIds.contains(mid)).collect(Collectors.toList());
+        DynamicDataSourceContextHolder.setDataSourceType(com.fs.common.enums.DataSourceType.MASTER.name());
+        if (!needAddIds.isEmpty()) {
+            return tenantInfoMapper.getTenSysMenuByIds(needAddIds);
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * 获取租户库中需要新增的销售菜单
+     */
+    private List<TenantCompanyMenu> getAddCompanyMenu(TenantInfo tenantInfo, List<Long> selected) {
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        List<Long> existIds = tenantInfoMapper.selectExistComMenuIds();
+        List<Long> needAddIds = selected.stream().filter(mid -> !existIds.contains(mid)).collect(Collectors.toList());
+        DynamicDataSourceContextHolder.setDataSourceType(com.fs.common.enums.DataSourceType.MASTER.name());
+        if (!needAddIds.isEmpty()) {
+            return tenantInfoMapper.getTenComMenuByIds(needAddIds);
+        }
+        return new ArrayList<>();
     }
 }

+ 27 - 80
fs-admin/src/main/java/com/fs/admin/controller/CompanyUserAdminController.java

@@ -2,16 +2,12 @@ package com.fs.admin.controller;
 
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.annotation.Log;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
-import com.fs.company.domain.Company;
-import com.fs.company.domain.CompanyUser;
-import com.fs.company.domain.CompanyUserChangeApply;
-import com.fs.company.service.ICompanyService;
-import com.fs.company.service.ICompanyUserChangeApplyService;
-import com.fs.company.service.ICompanyUserService;
+import com.fs.system.service.ISysUserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -19,119 +15,70 @@ import org.springframework.web.bind.annotation.*;
 import java.util.List;
 
 /**
- * 总后台员工账户管理控制器
- * 查看所有租户的员工账户、禁用启用员工账户、查看账户变化记录
+ * 总后台管理员账户管理控制器
+ * 读取 ylrz_saas.sys_user 表中 company_id IS NULL 的记录
+ * 这些是 admin 总后台自己的管理员账号,不属于任何租户
  */
 @RestController
 @RequestMapping("/admin/companyUser")
 public class CompanyUserAdminController extends BaseController {
 
     @Autowired
-    private ICompanyUserService companyUserService;
-
-    @Autowired
-    private ICompanyUserChangeApplyService companyUserChangeApplyService;
-
-    @Autowired
-    private ICompanyService companyService;
+    private ISysUserService sysUserService;
 
     /**
-     * 查询所有租户的员工列表
+     * 查询管理员列表(company_id IS NULL)
      */
     @PreAuthorize("@ss.hasPermi('admin:companyUser:list')")
     @GetMapping("/list")
-    public TableDataInfo list(CompanyUser companyUser) {
+    public TableDataInfo list(SysUser sysUser) {
         startPage();
-        List<CompanyUser> list = companyUserService.selectCompanyUserList(companyUser);
+        List<SysUser> list = sysUserService.selectAdminUserList(sysUser);
         return getDataTable(list);
     }
 
     /**
-     * 导出员列表
+     * 导出管理员列表
      */
-    @Log(title = "导出员列表", businessType = BusinessType.EXPORT)
+    @Log(title = "导出管理员列表", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('admin:companyUser:list')")
     @GetMapping("/export")
-    public AjaxResult export(CompanyUser companyUser) {
-        List<CompanyUser> list = companyUserService.selectCompanyUserList(companyUser);
-        ExcelUtil<CompanyUser> util = new ExcelUtil<>(CompanyUser.class);
-        return util.exportExcel(list, "员账户数据");
+    public AjaxResult export(SysUser sysUser) {
+        List<SysUser> list = sysUserService.selectAdminUserList(sysUser);
+        ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
+        return util.exportExcel(list, "管理员账户数据");
     }
 
     /**
-     * 获取员详细信息
+     * 获取管理员详细信息
      */
     @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
     @GetMapping(value = "/{userId}")
     public AjaxResult getInfo(@PathVariable("userId") Long userId) {
-        return AjaxResult.success(companyUserService.selectCompanyUserById(userId));
-    }
-
-    /**
-     * 根据租户ID查询员工列表
-     */
-    @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
-    @GetMapping("/byCompany/{companyId}")
-    public TableDataInfo getByCompanyId(@PathVariable Long companyId) {
-        startPage();
-        CompanyUser companyUser = new CompanyUser();
-        companyUser.setCompanyId(companyId);
-        List<CompanyUser> list = companyUserService.selectCompanyUserList(companyUser);
-        return getDataTable(list);
+        return AjaxResult.success(sysUserService.selectUserById(userId));
     }
 
     /**
-     * 禁用/启用员工账户
+     * 禁用/启用管理员账户
+     * SysUser 状态:0=正常 1=停用
      */
     @PreAuthorize("@ss.hasPermi('admin:companyUser:edit')")
     @PutMapping("/status/{userId}")
-    public AjaxResult changeStatus(@PathVariable Long userId, @RequestParam Integer status) {
-        CompanyUser companyUser = new CompanyUser();
-        companyUser.setUserId(userId);
-        companyUser.setStatus(status != null ? String.valueOf(status) : "1");
-        return toAjax(companyUserService.updateCompanyUser(companyUser));
-    }
-
-    /**
-     * 查询员工账户变化记录
-     */
-    @PreAuthorize("@ss.hasPermi('admin:companyUser:changeList')")
-    @GetMapping("/changeList")
-    public TableDataInfo changeList() {
-        startPage();
-        List<CompanyUserChangeApply> list = companyUserChangeApplyService.list();
-        return getDataTable(list);
-    }
-
-    /**
-     * 获取员工账户变化记录详情
-     */
-    @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
-    @GetMapping("/change/{id}")
-    public AjaxResult getChangeInfo(@PathVariable Long id) {
-        return AjaxResult.success(companyUserChangeApplyService.getById(id));
-    }
-
-    /**
-     * 获取租户列表(用于筛选)
-     */
-    @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
-    @GetMapping("/companies")
-    public AjaxResult getCompanies() {
-        Company company = new Company();
-        company.setStatus(1);
-        List<Company> list = companyService.selectCompanyList(company);
-        return AjaxResult.success(list);
+    public AjaxResult changeStatus(@PathVariable Long userId, @RequestParam String status) {
+        SysUser sysUser = new SysUser();
+        sysUser.setUserId(userId);
+        sysUser.setStatus(status);
+        return toAjax(sysUserService.updateUserStatus(sysUser));
     }
 
     /**
-     * 统计各租户员工数量
+     * 统计管理员数量
      */
     @PreAuthorize("@ss.hasPermi('admin:companyUser:query')")
     @GetMapping("/statistics")
     public AjaxResult statistics() {
-        CompanyUser query = new CompanyUser();
-        List<CompanyUser> all = companyUserService.selectCompanyUserList(query);
+        SysUser query = new SysUser();
+        List<SysUser> all = sysUserService.selectAdminUserList(query);
         java.util.Map<String, Object> result = new java.util.HashMap<>();
         result.put("totalCount", all.size());
         return AjaxResult.success(result);

+ 2 - 2
fs-admin/src/main/java/com/fs/web/controller/system/AdminMenuController.java

@@ -11,8 +11,8 @@ import org.springframework.web.bind.annotation.*;
 import java.util.List;
 
 /**
- * 平台菜单管理Controller (fs_menu)
- * 供adminui使用,与sys_menu物理隔离
+ * 平台菜单管理Controller (sys_menu)
+ * 供adminui使用
  */
 @RestController
 @RequestMapping("/fs/menu")

+ 1 - 1
fs-common/src/main/java/com/fs/common/core/domain/entity/AdminMenu.java

@@ -4,7 +4,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- * adminui平台总后台菜单实体 fs_menu
+ * adminui平台总后台菜单实体 sys_menu
  */
 public class AdminMenu
 {

+ 11 - 0
fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java

@@ -100,6 +100,9 @@ public class SysUser extends BaseEntity
     /**  公司相关数据权限,如订单类的,为空时查所有 **/
     private Long companyId;
 
+    /** 租户名称(关联查询,非表字段) */
+    private String companyName;
+
     @Excel(name = "角色名称")
     private List<String> roleName;
 
@@ -113,6 +116,14 @@ public class SysUser extends BaseEntity
         this.unionId = unionId;
     }
 
+    public String getCompanyName() {
+        return companyName;
+    }
+
+    public void setCompanyName(String companyName) {
+        this.companyName = companyName;
+    }
+
     public SysUser()
     {
 

+ 2 - 2
fs-service/src/main/java/com/fs/hisStore/domain/FsMenuScrm.java

@@ -5,13 +5,13 @@ import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
 /**
- * 用户端菜单管理对象 fs_menu
+ * 用户端菜单管理对象 sys_menu
  *
  * @author fs
  * @date 2022-03-15
  */
 @Data
-@TableName("fs_menu")
+@TableName("sys_menu")
 public class FsMenuScrm extends BaseEntity
 {
     private static final long serialVersionUID = 1L;

+ 12 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwIpadServerMapper.java

@@ -2,11 +2,13 @@ package com.fs.qw.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.qw.domain.QwIpadServer;
+import com.fs.qw.vo.QwIpadServerVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * ipad服务器Mapper接口
@@ -76,4 +78,14 @@ public interface QwIpadServerMapper extends BaseMapper<QwIpadServer>{
     QwIpadServer getPadInfo();
 
     QwIpadServer selectIpAndPort(@Param("ipAddress") String ipAddress, @Param("port") Long port);
+
+    /**
+     * 查询iPad服务器列表(含租户使用统计)
+     */
+    List<QwIpadServerVO> selectIpadServerWithTenantStats(@Param("companyId") Long companyId, @Param("ip") String ip);
+
+    /**
+     * 查询各租户iPad使用统计
+     */
+    List<Map<String, Object>> selectIpadTenantStats();
 }

+ 13 - 0
fs-service/src/main/java/com/fs/qw/service/IQwIpadServerService.java

@@ -2,8 +2,11 @@ package com.fs.qw.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.qw.domain.QwIpadServer;
+import com.fs.qw.vo.IpadTenantStatsVO;
+import com.fs.qw.vo.QwIpadServerVO;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * ipad服务器Service接口
@@ -71,4 +74,14 @@ public interface IQwIpadServerService extends IService<QwIpadServer>{
     QwIpadServer getPadInfo();
 
     QwIpadServer selectIpAndPort(String ipAddress, Long port);
+
+    /**
+     * 查询iPad服务器列表(含租户使用统计)
+     */
+    List<QwIpadServerVO> selectIpadServerWithTenantStats(Long companyId, String ip);
+
+    /**
+     * 查询各租户iPad使用统计
+     */
+    List<IpadTenantStatsVO> selectIpadTenantStats();
 }

+ 20 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwIpadServerServiceImpl.java

@@ -5,10 +5,13 @@ import com.fs.common.utils.DateUtils;
 import com.fs.qw.domain.QwIpadServer;
 import com.fs.qw.mapper.QwIpadServerMapper;
 import com.fs.qw.service.IQwIpadServerService;
+import com.fs.qw.vo.IpadTenantStatsVO;
+import com.fs.qw.vo.QwIpadServerVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * ipad服务器Service业务层处理
@@ -123,4 +126,21 @@ private QwIpadServerMapper qwIpadServerMapper;
     public QwIpadServer selectIpAndPort(String ipAddress, Long port) {
         return qwIpadServerMapper.selectIpAndPort(ipAddress, port);
     }
+
+    @Override
+    public List<QwIpadServerVO> selectIpadServerWithTenantStats(Long companyId, String ip) {
+        return qwIpadServerMapper.selectIpadServerWithTenantStats(companyId, ip);
+    }
+
+    @Override
+    public List<IpadTenantStatsVO> selectIpadTenantStats() {
+        List<Map<String, Object>> rows = qwIpadServerMapper.selectIpadTenantStats();
+        return rows.stream().map(row -> {
+            Long companyId = row.get("companyId") != null ? Long.valueOf(row.get("companyId").toString()) : null;
+            String companyName = row.get("companyName") != null ? row.get("companyName").toString() : "";
+            Integer usedCount = row.get("usedCount") != null ? Integer.valueOf(row.get("usedCount").toString()) : 0;
+            Integer maxPadNum = row.get("maxPadNum") != null ? Integer.valueOf(row.get("maxPadNum").toString()) : -1;
+            return new IpadTenantStatsVO(companyId, companyName, usedCount, maxPadNum);
+        }).collect(java.util.stream.Collectors.toList());
+    }
 }

+ 39 - 0
fs-service/src/main/java/com/fs/qw/vo/IpadTenantStatsVO.java

@@ -0,0 +1,39 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+
+/**
+ * 租户 iPad 使用统计 VO
+ */
+@Data
+public class IpadTenantStatsVO {
+
+    /** 公司ID */
+    private Long companyId;
+
+    /** 公司名称 */
+    private String companyName;
+
+    /** 已绑定iPad数 */
+    private Integer usedCount;
+
+    /** 限制数(来自 company.max_pad_num,-1表示不限) */
+    private Integer maxPadNum;
+
+    /** 状态:正常/超限 */
+    private String status;
+
+    public IpadTenantStatsVO() {}
+
+    public IpadTenantStatsVO(Long companyId, String companyName, Integer usedCount, Integer maxPadNum) {
+        this.companyId = companyId;
+        this.companyName = companyName;
+        this.usedCount = usedCount;
+        this.maxPadNum = maxPadNum;
+        if (maxPadNum != null && maxPadNum != -1 && usedCount != null && usedCount >= maxPadNum) {
+            this.status = "超限";
+        } else {
+            this.status = "正常";
+        }
+    }
+}

+ 22 - 0
fs-service/src/main/java/com/fs/qw/vo/QwIpadServerVO.java

@@ -0,0 +1,22 @@
+package com.fs.qw.vo;
+
+import com.fs.qw.domain.QwIpadServer;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * iPad服务器 + 租户统计 VO
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class QwIpadServerVO extends QwIpadServer {
+
+    /** 使用该服务器的租户数 */
+    private Integer tenantCount;
+
+    /** 已绑定用户数 */
+    private Integer usedCount;
+
+    /** 租户名称(逗号分隔) */
+    private String tenantNames;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/system/mapper/AdminMenuMapper.java

@@ -5,7 +5,7 @@ import com.fs.common.core.domain.entity.AdminMenu;
 import org.apache.ibatis.annotations.Param;
 
 /**
- * adminui平台总后台菜单Mapper接口 (fs_menu)
+ * adminui平台总后台菜单Mapper接口 (sys_menu)
  */
 public interface AdminMenuMapper
 {

+ 10 - 0
fs-service/src/main/java/com/fs/system/mapper/SysUserMapper.java

@@ -135,4 +135,14 @@ public interface SysUserMapper
     List<SysUser> selectUserByPhone(String phone);
 
     TenantInfo getTenantInfo(String tenantCode);
+
+    /**
+     * 总后台员工列表(JOIN tenant_info 获取租户名称)
+     */
+    List<SysUser> selectAdminUserList(SysUser sysUser);
+
+    /**
+     * 总后台员工列表总数
+     */
+    Long selectAdminUserCount(SysUser sysUser);
 }

+ 1 - 1
fs-service/src/main/java/com/fs/system/service/IAdminMenuService.java

@@ -8,7 +8,7 @@ import com.fs.system.domain.vo.MetaVo;
 import com.fs.system.domain.vo.RouterVo;
 
 /**
- * adminui平台总后台菜单服务接口 (fs_menu)
+ * adminui平台总后台菜单服务接口 (sys_menu)
  */
 public interface IAdminMenuService
 {

+ 5 - 0
fs-service/src/main/java/com/fs/system/service/ISysUserService.java

@@ -219,4 +219,9 @@ public interface ISysUserService
     List<SysUser> selectUserByPhone(String phone);
 
     TenantInfo getTenantInfo(String tenantCode);
+
+    /**
+     * 总后台员工列表(JOIN tenant_info 获取租户名称)
+     */
+    List<SysUser> selectAdminUserList(SysUser sysUser);
 }

+ 1 - 1
fs-service/src/main/java/com/fs/system/service/impl/AdminMenuServiceImpl.java

@@ -18,7 +18,7 @@ import com.fs.system.mapper.AdminMenuMapper;
 import com.fs.system.service.IAdminMenuService;
 
 /**
- * adminui平台总后台菜单服务实现 (fs_menu)
+ * adminui平台总后台菜单服务实现 (sys_menu)
  */
 @Service
 public class AdminMenuServiceImpl implements IAdminMenuService

+ 5 - 0
fs-service/src/main/java/com/fs/system/service/impl/SysUserServiceImpl.java

@@ -596,4 +596,9 @@ public class SysUserServiceImpl implements ISysUserService
     public TenantInfo getTenantInfo(String tenantCode) {
         return userMapper.getTenantInfo(tenantCode);
     }
+
+    @Override
+    public List<SysUser> selectAdminUserList(SysUser sysUser) {
+        return userMapper.selectAdminUserList(sysUser);
+    }
 }

+ 8 - 0
fs-service/src/main/java/com/fs/tenant/mapper/TenantInfoMapper.java

@@ -143,6 +143,14 @@ public interface TenantInfoMapper extends BaseMapper<TenantInfo> {
     long getYesterAiReplyToken(@Param("yesterDayBegin") DateTime yesterDayBegin,@Param("yesterdayEnd") DateTime yesterdayEnd);
 
     List<SopTokenDto> getYesterSopToken(@Param("yesterDayBegin") DateTime yesterDayBegin, @Param("yesterdayEnd") DateTime yesterdayEnd);
+
+    /**
+     * 原子更新余额(充值/扣款)
+     * @param id 租户ID
+     * @param amount 变动金额(正数充值,负数扣款)
+     * @return 影响行数
+     */
+    int updateBalance(@Param("id") Long id, @Param("amount") java.math.BigDecimal amount);
 }
 
 

+ 8 - 0
fs-service/src/main/java/com/fs/tenant/service/TenantInfoService.java

@@ -119,4 +119,12 @@ public interface TenantInfoService extends IService<TenantInfo> {
     Map<String, Object> getYesterSopToken(DateTime yesterDayBegin, DateTime yesterdayEnd, FeePlanItem item,TenantInfo tenant);
 
     TenantInfo selectTenantInfoByCode(String tenantCode);
+
+    /**
+     * 原子更新余额(充值/扣款)
+     * @param id 租户ID
+     * @param amount 变动金额(正数充值,负数扣款)
+     * @return 影响行数
+     */
+    int updateBalance(Long id, BigDecimal amount);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/tenant/service/impl/TenantInfoServiceImpl.java

@@ -666,6 +666,11 @@ public class TenantInfoServiceImpl extends ServiceImpl<TenantInfoMapper, TenantI
             // ignore
         }
     }
+
+    @Override
+    public int updateBalance(Long id, BigDecimal amount) {
+        return baseMapper.updateBalance(id, amount);
+    }
 }
 
 

+ 5 - 5
fs-service/src/main/resources/mapper/hisStore/FsMenuScrmMapper.xml

@@ -19,7 +19,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectFsMenuVo">
-        select menu_id, menu_name,menu_type, icon, is_show,link_type,link_url, create_time, update_time,sort,app_id from fs_menu
+        select menu_id, menu_name,menu_type, icon, is_show,link_type,link_url, create_time, update_time,sort,app_id from sys_menu
     </sql>
 
     <select id="selectFsMenuList" resultMap="FsMenuResult">
@@ -43,7 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
     <insert id="insertFsMenu" useGeneratedKeys="true" keyProperty="menuId">
-        insert into fs_menu
+        insert into sys_menu
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="menuName != null">menu_name,</if>
             <if test="icon != null">icon,</if>
@@ -71,7 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </insert>
 
     <update id="updateFsMenu">
-        update fs_menu
+        update sys_menu
         <trim prefix="SET" suffixOverrides=",">
             <if test="menuName != null">menu_name = #{menuName},</if>
             <if test="icon != null">icon = #{icon},</if>
@@ -88,11 +88,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </update>
 
     <delete id="deleteFsMenuById">
-        delete from fs_menu where menu_id = #{menuId}
+        delete from sys_menu where menu_id = #{menuId}
     </delete>
 
     <delete id="deleteFsMenuByIds">
-        delete from fs_menu where menu_id in
+        delete from sys_menu where menu_id in
         <foreach item="menuId" collection="array" open="(" separator="," close=")">
             #{menuId}
         </foreach>

+ 33 - 0
fs-service/src/main/resources/mapper/qw/QwIpadServerMapper.xml

@@ -97,4 +97,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{id}
         </foreach>
     </delete>
+
+    <!-- 查询iPad服务器列表(含租户使用统计) -->
+    <resultMap type="com.fs.qw.vo.QwIpadServerVO" id="QwIpadServerVOResult" extends="QwIpadServerResult">
+        <result property="tenantCount"    column="tenant_count"    />
+        <result property="usedCount"    column="used_count"    />
+        <result property="tenantNames"    column="tenant_names"    />
+    </resultMap>
+
+    <select id="selectIpadServerWithTenantStats" resultMap="QwIpadServerVOResult">
+        SELECT s.id, s.title, s.address_id, s.ip, s.port, s.url, s.total_count, s.count, s.create_time, s.update_time,
+            COUNT(DISTINCT su.company_id) as tenant_count,
+            COUNT(su.id) as used_count,
+            GROUP_CONCAT(DISTINCT c.company_name) as tenant_names
+        FROM qw_ipad_server s
+        LEFT JOIN qw_ipad_server_user su ON s.id = su.server_id
+        LEFT JOIN company c ON su.company_id = c.company_id
+        <where>
+            <if test="companyId != null">AND su.company_id = #{companyId}</if>
+            <if test="ip != null and ip != ''">AND s.ip LIKE CONCAT('%', #{ip}, '%')</if>
+        </where>
+        GROUP BY s.id
+        ORDER BY s.count DESC
+    </select>
+
+    <!-- 查询各租户iPad使用统计 -->
+    <select id="selectIpadTenantStats" resultType="java.util.Map">
+        SELECT su.company_id as companyId, c.company_name as companyName,
+            COUNT(su.id) as usedCount, c.max_pad_num as maxPadNum
+        FROM qw_ipad_server_user su
+        JOIN company c ON su.company_id = c.company_id
+        GROUP BY su.company_id, c.company_name, c.max_pad_num
+        ORDER BY usedCount DESC
+    </select>
 </mapper>

+ 15 - 15
fs-service/src/main/resources/mapper/system/AdminMenuMapper.xml

@@ -26,14 +26,14 @@
 
     <select id="selectMenuTreeAll" resultMap="AdminMenuResult">
         select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
-        from fs_menu m where m.menu_type in ('M', 'C') and m.status = '0' and m.visible = '0'
+        from sys_menu m where m.menu_type in ('M', 'C') and m.status = '0' and m.visible = '0'
         order by m.parent_id, m.order_num
     </select>
 
     <select id="selectMenuTreeByUserId" resultMap="AdminMenuResult">
         select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
-        from fs_menu m
-             left join fs_role_menu rm on m.menu_id = rm.menu_id
+        from sys_menu m
+             left join sys_role_menu rm on m.menu_id = rm.menu_id
              left join sys_user_role ur on rm.role_id = ur.role_id
              left join sys_role ro on ur.role_id = ro.role_id
         where m.menu_type in ('M', 'C') and m.status = '0'
@@ -44,8 +44,8 @@
 
     <select id="selectMenuListByUserId" resultMap="AdminMenuResult">
         select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
-        from fs_menu m
-             left join fs_role_menu rm on m.menu_id = rm.menu_id
+        from sys_menu m
+             left join sys_role_menu rm on m.menu_id = rm.menu_id
              left join sys_user_role ur on rm.role_id = ur.role_id
              left join sys_role ro on ur.role_id = ro.role_id
         where ur.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = '0'
@@ -55,8 +55,8 @@
 
     <select id="selectMenuPermsByUserId" resultType="String">
         select distinct m.perms
-        from fs_menu m
-             left join fs_role_menu rm on m.menu_id = rm.menu_id
+        from sys_menu m
+             left join sys_role_menu rm on m.menu_id = rm.menu_id
              left join sys_user_role ur on rm.role_id = ur.role_id
              left join sys_role ro on ur.role_id = ro.role_id
         where ur.user_id = #{userId} and m.status = '0' and ro.status = '0'
@@ -66,7 +66,7 @@
         select m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.visible, m.status,
                ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num,
                m.create_by, m.create_time, m.update_by, m.update_time, m.remark
-        from fs_menu m
+        from sys_menu m
     </sql>
 
     <select id="selectMenuList" parameterType="com.fs.common.core.domain.entity.AdminMenu" resultMap="AdminMenuResult">
@@ -92,8 +92,8 @@
 
     <select id="selectMenuListByRoleId" resultType="Long">
         select m.menu_id
-        from fs_menu m
-             left join fs_role_menu rm on m.menu_id = rm.menu_id
+        from sys_menu m
+             left join sys_role_menu rm on m.menu_id = rm.menu_id
         where rm.role_id = #{roleId}
         order by m.parent_id, m.order_num
     </select>
@@ -104,7 +104,7 @@
     </select>
 
     <insert id="insertMenu" parameterType="com.fs.common.core.domain.entity.AdminMenu" useGeneratedKeys="true" keyProperty="menuId">
-        insert into fs_menu (
+        insert into sys_menu (
             <if test="menuName != null and menuName != ''">menu_name,</if>
             <if test="parentId != null">parent_id,</if>
             <if test="orderNum != null">order_num,</if>
@@ -142,7 +142,7 @@
     </insert>
 
     <update id="updateMenu" parameterType="com.fs.common.core.domain.entity.AdminMenu">
-        update fs_menu
+        update sys_menu
         <set>
             <if test="menuName != null and menuName != ''">menu_name = #{menuName},</if>
             <if test="parentId != null">parent_id = #{parentId},</if>
@@ -165,15 +165,15 @@
     </update>
 
     <delete id="deleteMenuById" parameterType="Long">
-        delete from fs_menu where menu_id = #{menuId}
+        delete from sys_menu where menu_id = #{menuId}
     </delete>
 
     <select id="hasChildByMenuId" parameterType="Long" resultType="int">
-        select count(1) from fs_menu where parent_id = #{menuId}
+        select count(1) from sys_menu where parent_id = #{menuId}
     </select>
 
     <select id="checkMenuExistRole" parameterType="Long" resultType="int">
-        select count(1) from fs_role_menu where menu_id = #{menuId}
+        select count(1) from sys_role_menu where menu_id = #{menuId}
     </select>
 
 </mapper>

+ 60 - 0
fs-service/src/main/resources/mapper/system/SysUserMapper.xml

@@ -337,4 +337,64 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
  	</delete>
 
+	<!-- 总后台管理员列表(sys_user 中 company_id IS NULL 的记录) -->
+	<resultMap id="AdminUserResult" type="SysUser">
+		<id     property="userId"       column="user_id"      />
+		<result property="deptId"       column="dept_id"      />
+		<result property="userName"     column="user_name"    />
+		<result property="nickName"     column="nick_name"    />
+		<result property="email"        column="email"        />
+		<result property="phonenumber"  column="phonenumber"  />
+		<result property="sex"          column="sex"          />
+		<result property="avatar"       column="avatar"       />
+		<result property="status"       column="status"       />
+		<result property="delFlag"      column="del_flag"     />
+		<result property="loginIp"      column="login_ip"     />
+		<result property="loginDate"    column="login_date"   />
+		<result property="createBy"     column="create_by"    />
+		<result property="createTime"   column="create_time"  />
+		<result property="updateBy"     column="update_by"    />
+		<result property="updateTime"   column="update_time"  />
+		<result property="remark"       column="remark"       />
+	</resultMap>
+
+	<select id="selectAdminUserList" resultMap="AdminUserResult">
+		SELECT u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber,
+		       u.sex, u.avatar, u.status, u.del_flag, u.login_ip, u.login_date,
+		       u.create_by, u.create_time, u.update_by, u.update_time, u.remark
+		FROM sys_user u
+		WHERE u.del_flag = '0' AND u.company_id IS NULL
+		<if test="userName != null and userName != ''">
+			AND u.user_name LIKE CONCAT('%', #{userName}, '%')
+		</if>
+		<if test="nickName != null and nickName != ''">
+			AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')
+		</if>
+		<if test="status != null and status != ''">
+			AND u.status = #{status}
+		</if>
+		<if test="phonenumber != null and phonenumber != ''">
+			AND u.phonenumber LIKE CONCAT('%', #{phonenumber}, '%')
+		</if>
+		ORDER BY u.create_time DESC
+	</select>
+
+	<select id="selectAdminUserCount" resultType="java.lang.Long">
+		SELECT COUNT(1)
+		FROM sys_user u
+		WHERE u.del_flag = '0' AND u.company_id IS NULL
+		<if test="userName != null and userName != ''">
+			AND u.user_name LIKE CONCAT('%', #{userName}, '%')
+		</if>
+		<if test="nickName != null and nickName != ''">
+			AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')
+		</if>
+		<if test="status != null and status != ''">
+			AND u.status = #{status}
+		</if>
+		<if test="phonenumber != null and phonenumber != ''">
+			AND u.phonenumber LIKE CONCAT('%', #{phonenumber}, '%')
+		</if>
+	</select>
+
 </mapper>

+ 5 - 0
fs-service/src/main/resources/mapper/tenant/TenantInfoMapper.xml

@@ -614,4 +614,9 @@
     <delete id="deleteComMenuById">
         delete from tenant_company_menu where menu_id = #{menuId}
     </delete>
+
+    <update id="updateBalance">
+        UPDATE tenant_info SET balance = balance + #{amount}, update_time = NOW()
+        WHERE id = #{id} AND (balance + #{amount}) >= 0
+    </update>
 </mapper>

+ 24 - 0
sql/create_company_miniapp.sql

@@ -0,0 +1,24 @@
+-- ============================================================
+-- 创建缺失的 company_miniapp 表
+-- 问题:租户列表点击编辑时提示 Table 'ylrz_saas.company_miniapp' doesn't exist
+-- 原因:后端 CompanyMiniapp 域对象/Mapper/Service 已存在,
+--       但数据库中缺少对应的物理表
+-- 调用链:CompanyServiceImpl.selectCompanyById()
+--       → companyMiniappService.getMiniAppListByCompanyList()
+--       → SELECT ... FROM company_miniapp WHERE company_id IN (...)
+-- ============================================================
+
+CREATE TABLE IF NOT EXISTS `company_miniapp` (
+    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `company_id`  BIGINT       DEFAULT NULL COMMENT '客服公司ID',
+    `app_id`      VARCHAR(100) DEFAULT NULL COMMENT '小程序appid',
+    `type`        INT          DEFAULT NULL COMMENT '主从 0主小程序 1备用小程序',
+    `sort_num`    INT          DEFAULT NULL COMMENT '排序',
+    `create_time` DATETIME     DEFAULT NULL COMMENT '创建时间',
+    `create_by`   VARCHAR(64)  DEFAULT '' COMMENT '创建者',
+    `update_by`   VARCHAR(64)  DEFAULT '' COMMENT '更新者',
+    `update_time` DATETIME     DEFAULT NULL COMMENT '更新时间',
+    `remark`      VARCHAR(500) DEFAULT '' COMMENT '备注',
+    PRIMARY KEY (`id`),
+    INDEX `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客服公司小程序';

+ 50 - 0
sql/create_tenant_sys_menu.sql

@@ -0,0 +1,50 @@
+-- ============================================================
+-- 将 ylrz_saas.sys_menu 的数据迁移到 ylrz_saas.tenant_sys_menu
+-- 将 ylrz_saas.company_menu 的数据迁移到 ylrz_saas.tenant_company_menu
+-- 目的:租户初始化动态拷贝的表统一为 tenant_sys_menu + tenant_company_menu
+--       不再直接依赖 sys_menu / company_menu(仅用于 admin 总后台路由)
+-- ============================================================
+
+-- 1. 创建 tenant_sys_menu 表(结构同 sys_menu)
+CREATE TABLE IF NOT EXISTS `tenant_sys_menu` (
+    `menu_id`     bigint                                                       NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
+    `menu_name`   varchar(50)   NOT NULL COMMENT '菜单名称',
+    `parent_id`   bigint NULL DEFAULT 0 COMMENT '父菜单ID',
+    `order_num`   int NULL DEFAULT 0 COMMENT '显示顺序',
+    `path`        varchar(200)   NULL DEFAULT '' COMMENT '路由地址',
+    `component`   varchar(255)   NULL DEFAULT NULL COMMENT '组件路径',
+    `query`       varchar(255)   NULL DEFAULT NULL COMMENT '路由参数',
+    `is_frame`    int NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)',
+    `is_cache`    int NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)',
+    `menu_type`   char(1)   NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
+    `visible`     char(1)   NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
+    `status`      char(1)   NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
+    `perms`       varchar(100)   NULL DEFAULT NULL COMMENT '权限标识',
+    `icon`        varchar(100)   NULL DEFAULT '#' COMMENT '菜单图标',
+    `create_by`   varchar(64)   NULL DEFAULT '' COMMENT '创建者',
+    `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
+    `update_by`   varchar(64)   NULL DEFAULT '' COMMENT '更新者',
+    `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
+    `remark`      varchar(500)   NULL DEFAULT '' COMMENT '备注',
+    PRIMARY KEY (`menu_id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 2922 COMMENT = '租户菜单模板表(新租户创建时从此表复制)' ROW_FORMAT = Dynamic;
+
+-- 2. 从 sys_menu 拷贝数据到 tenant_sys_menu(仅首次,忽略已存在的记录)
+INSERT IGNORE INTO `tenant_sys_menu`
+    (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`,
+     `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`,
+     `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
+SELECT `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`,
+       `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`,
+       `create_by`, `create_time`, `update_by`, `update_time`, `remark`
+FROM `sys_menu`;
+
+-- 3. 从 company_menu 拷贝数据到 tenant_company_menu(仅首次,忽略已存在的记录)
+INSERT IGNORE INTO `tenant_company_menu`
+    (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `is_frame`,
+     `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`,
+     `create_time`, `update_by`, `update_time`, `remark`, `company_id`)
+SELECT `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `is_frame`,
+       `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`,
+       `create_time`, `update_by`, `update_time`, `remark`, `company_id`
+FROM `company_menu`;