Quellcode durchsuchen

外呼接口API调整

lmx vor 2 Monaten
Ursprung
Commit
8045328e9e

+ 69 - 10
fs-admin/src/main/java/com/fs/admin/controller/AdminCompanyBridgeController.java

@@ -117,11 +117,11 @@ public class AdminCompanyBridgeController extends BaseController {
     }
 
     /** 查询租户已分配的接口列表(含定价信息) */
-    @GetMapping("/admin/voice-api/apis/{companyId}")
-    public AjaxResult voiceApiApis(@PathVariable Long companyId) {
+    @GetMapping("/admin/voice-api/apis/{tenantId}")
+    public AjaxResult voiceApiApis(@PathVariable Long tenantId) {
         DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
         List<CompanyVoiceApiTenant> list = companyVoiceApiTenantService != null ?
-            companyVoiceApiTenantService.selectEnabledApisByCompanyId(companyId) : new ArrayList<>();
+            companyVoiceApiTenantService.selectEnabledApisByTenantId(tenantId) : new ArrayList<>();
         return AjaxResult.success(list);
     }
 
@@ -133,22 +133,81 @@ public class AdminCompanyBridgeController extends BaseController {
         DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
         if (companyVoiceApiTenantService == null) return AjaxResult.error("服务未就绪");
         Long apiId = Long.valueOf(body.get("apiId").toString());
-        @SuppressWarnings("unchecked")
-        List<Integer> companyIds = (List<Integer>) body.get("companyIds");
-        List<Long> ids = new ArrayList<>();
-        for (Integer id : companyIds) { ids.add(id.longValue()); }
-        companyVoiceApiTenantService.batchAssignTenants(apiId, ids);
+        List<Long> tenantIds = parseIdList(body.get("tenantIds"));
+        if (tenantIds.isEmpty()) {
+            tenantIds = parseIdList(body.get("companyIds"));
+        }
+        if (tenantIds.isEmpty()) {
+            return AjaxResult.error("请选择租户");
+        }
+        companyVoiceApiTenantService.batchAssignTenants(apiId, parseTenantAssignList(body, apiId, tenantIds));
         return AjaxResult.success();
     }
 
+    @SuppressWarnings("unchecked")
+    private List<CompanyVoiceApiTenant> parseTenantAssignList(Map<String, Object> body, Long apiId, List<Long> tenantIds) {
+        List<CompanyVoiceApiTenant> list = new ArrayList<>();
+        Object rawTenants = body.get("tenants");
+        if (rawTenants instanceof List) {
+            for (Object item : (List<?>) rawTenants) {
+                if (!(item instanceof Map)) {
+                    continue;
+                }
+                Map<String, Object> tenantMap = (Map<String, Object>) item;
+                CompanyVoiceApiTenant tenant = new CompanyVoiceApiTenant();
+                tenant.setApiId(apiId);
+                if (tenantMap.get("tenantId") != null) {
+                    tenant.setTenantId(Long.valueOf(tenantMap.get("tenantId").toString()));
+                }
+                if (tenantMap.get("tenantCode") != null) {
+                    tenant.setTenantCode(tenantMap.get("tenantCode").toString());
+                }
+                if (tenantMap.get("tenantName") != null) {
+                    tenant.setTenantName(tenantMap.get("tenantName").toString());
+                }
+                if (tenant.getTenantId() != null) {
+                    list.add(tenant);
+                }
+            }
+        }
+        if (list.isEmpty()) {
+            for (Long tenantId : tenantIds) {
+                CompanyVoiceApiTenant tenant = new CompanyVoiceApiTenant();
+                tenant.setApiId(apiId);
+                tenant.setTenantId(tenantId);
+                list.add(tenant);
+            }
+        }
+        return list;
+    }
+
     /** 取消分配 */
     @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:edit')")
     @Log(title = "取消通话接口分配", businessType = BusinessType.DELETE)
     @DeleteMapping("/admin/voice-api/unassignTenant")
-    public AjaxResult unassignTenant(@RequestParam Long apiId, @RequestParam Long companyId) {
+    public AjaxResult unassignTenant(@RequestParam Long apiId,
+                                     @RequestParam(required = false) Long tenantId,
+                                     @RequestParam(required = false) Long companyId) {
         DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
         if (companyVoiceApiTenantService == null) return AjaxResult.error("服务未就绪");
-        return toAjax(companyVoiceApiTenantService.unassignTenant(apiId, companyId));
+        Long tid = tenantId != null ? tenantId : companyId;
+        return toAjax(companyVoiceApiTenantService.unassignTenant(apiId, tid));
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<Long> parseIdList(Object raw) {
+        List<Long> ids = new ArrayList<>();
+        if (raw == null) {
+            return ids;
+        }
+        if (raw instanceof List) {
+            for (Object item : (List<?>) raw) {
+                if (item != null) {
+                    ids.add(Long.valueOf(item.toString()));
+                }
+            }
+        }
+        return ids;
     }
 
     /** 查询接口已分配租户数量 */

+ 245 - 0
fs-admin/src/main/java/com/fs/admin/controller/CompanyVoiceApiTenantController.java

@@ -0,0 +1,245 @@
+package com.fs.admin.controller;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.enums.DataSourceType;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.CompanyVoiceApi;
+import com.fs.company.domain.CompanyVoiceApiTenant;
+import com.fs.company.service.ICompanyVoiceApiService;
+import com.fs.company.service.ICompanyVoiceApiTenantService;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 外呼接口-租户分配与定价管理(新版 admin 专用)
+ *
+ * @author MixLiu
+ */
+@RestController
+@RequestMapping("/admin/companyVoiceApiTenant")
+public class CompanyVoiceApiTenantController extends BaseController {
+
+    @Autowired(required = false)
+    private ICompanyVoiceApiTenantService companyVoiceApiTenantService;
+
+    @Autowired(required = false)
+    private ICompanyVoiceApiService companyVoiceApiService;
+
+    /**
+     * 分页查询租户-接口绑定/定价列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyVoiceApiTenant param) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        startPage();
+        List<CompanyVoiceApiTenant> list = companyVoiceApiTenantService != null
+                ? companyVoiceApiTenantService.selectCompanyVoiceApiTenantList(param)
+                : java.util.Collections.emptyList();
+        return getDataTable(list);
+    }
+
+    /**
+     * 外呼接口下拉选项(新增绑定时选择接口)
+     */
+    @GetMapping("/apiList")
+    public TableDataInfo apiList() {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        List<CompanyVoiceApi> list = companyVoiceApiService != null
+                ? companyVoiceApiService.selectCompanyVoiceApiList(new CompanyVoiceApi())
+                : java.util.Collections.emptyList();
+        return getDataTable(list);
+    }
+
+    /**
+     * 分配接口给租户(批量)
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:edit')")
+    @Log(title = "分配通话接口给租户", businessType = BusinessType.INSERT)
+    @PostMapping("/assign")
+    public AjaxResult assign(@RequestBody Map<String, Object> body) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        if (companyVoiceApiTenantService == null) {
+            return AjaxResult.error("服务未就绪");
+        }
+        Long apiId = Long.valueOf(body.get("apiId").toString());
+        List<Long> tenantIds = parseIdList(body.get("tenantIds"));
+        if (tenantIds.isEmpty()) {
+            tenantIds = parseIdList(body.get("companyIds"));
+        }
+        if (tenantIds.isEmpty()) {
+            return AjaxResult.error("请选择租户");
+        }
+        companyVoiceApiTenantService.batchAssignTenants(apiId, parseTenantAssignList(body, apiId, tenantIds));
+        return AjaxResult.success();
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<CompanyVoiceApiTenant> parseTenantAssignList(Map<String, Object> body, Long apiId, List<Long> tenantIds) {
+        List<CompanyVoiceApiTenant> list = new ArrayList<>();
+        Object rawTenants = body.get("tenants");
+        if (rawTenants instanceof List) {
+            for (Object item : (List<?>) rawTenants) {
+                if (!(item instanceof Map)) {
+                    continue;
+                }
+                Map<String, Object> tenantMap = (Map<String, Object>) item;
+                CompanyVoiceApiTenant tenant = new CompanyVoiceApiTenant();
+                tenant.setApiId(apiId);
+                if (tenantMap.get("tenantId") != null) {
+                    tenant.setTenantId(Long.valueOf(tenantMap.get("tenantId").toString()));
+                }
+                if (tenantMap.get("tenantCode") != null) {
+                    tenant.setTenantCode(tenantMap.get("tenantCode").toString());
+                }
+                if (tenantMap.get("tenantName") != null) {
+                    tenant.setTenantName(tenantMap.get("tenantName").toString());
+                }
+                if (tenant.getTenantId() != null) {
+                    list.add(tenant);
+                }
+            }
+        }
+        if (list.isEmpty()) {
+            for (Long tenantId : tenantIds) {
+                CompanyVoiceApiTenant tenant = new CompanyVoiceApiTenant();
+                tenant.setApiId(apiId);
+                tenant.setTenantId(tenantId);
+                list.add(tenant);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 取消分配
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:edit')")
+    @Log(title = "取消通话接口分配", businessType = BusinessType.DELETE)
+    @DeleteMapping("/unassign")
+    public AjaxResult unassign(@RequestParam Long apiId,
+                               @RequestParam(required = false) Long tenantId,
+                               @RequestParam(required = false) Long companyId) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        if (companyVoiceApiTenantService == null) {
+            return AjaxResult.error("服务未就绪");
+        }
+        Long tid = tenantId != null ? tenantId : companyId;
+        return toAjax(companyVoiceApiTenantService.unassignTenant(apiId, tid));
+    }
+
+    /**
+     * 更新租户定价/绑定配置
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:edit')")
+    @Log(title = "更新外呼租户定价", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult update(@RequestBody CompanyVoiceApiTenant data) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        if (companyVoiceApiTenantService == null) {
+            return AjaxResult.error("服务未就绪");
+        }
+        if (data.getId() == null && data.getApiId() != null && data.getTenantId() != null) {
+            CompanyVoiceApiTenant existing = companyVoiceApiTenantService.selectByApiAndTenant(
+                    data.getApiId(), data.getTenantId());
+            if (existing != null) {
+                data.setId(existing.getId());
+            }
+        }
+        return toAjax(companyVoiceApiTenantService.updateCompanyVoiceApiTenant(data));
+    }
+
+    /**
+     * 批量更新租户定价/绑定配置
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:edit')")
+    @Log(title = "批量更新外呼租户定价", businessType = BusinessType.UPDATE)
+    @PutMapping("/batchPricing")
+    public AjaxResult batchPricing(@RequestBody Map<String, Object> body) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        if (companyVoiceApiTenantService == null) {
+            return AjaxResult.error("服务未就绪");
+        }
+        List<Long> ids = parseIdList(body.get("ids"));
+        if (ids.isEmpty()) {
+            return AjaxResult.error("请选择要更新的记录");
+        }
+        CompanyVoiceApiTenant pricing = parsePricingFields(body);
+        if (pricing.getSalePrice() == null && pricing.getPriority() == null
+                && pricing.getIsPrimary() == null && pricing.getSelectable() == null) {
+            return AjaxResult.error("请至少填写一项要批量更新的配置");
+        }
+        return toAjax(companyVoiceApiTenantService.batchUpdatePricing(ids, pricing));
+    }
+
+    /**
+     * 批量更新状态
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:edit')")
+    @Log(title = "批量更新外呼租户状态", businessType = BusinessType.UPDATE)
+    @PutMapping("/batchStatus")
+    public AjaxResult batchStatus(@RequestBody Map<String, Object> body) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        if (companyVoiceApiTenantService == null) {
+            return AjaxResult.error("服务未就绪");
+        }
+        List<Long> ids = parseIdList(body.get("ids"));
+        if (ids.isEmpty()) {
+            return AjaxResult.error("请选择要更新的记录");
+        }
+        if (body.get("status") == null) {
+            return AjaxResult.error("请指定状态");
+        }
+        Integer status = Integer.valueOf(body.get("status").toString());
+        if (status != 0 && status != 1) {
+            return AjaxResult.error("状态值无效");
+        }
+        return toAjax(companyVoiceApiTenantService.batchUpdateStatus(ids, status));
+    }
+
+    private CompanyVoiceApiTenant parsePricingFields(Map<String, Object> body) {
+        CompanyVoiceApiTenant pricing = new CompanyVoiceApiTenant();
+        if (body.get("salePrice") != null && StringUtils.isNotEmpty(body.get("salePrice").toString())) {
+            pricing.setSalePrice(new java.math.BigDecimal(body.get("salePrice").toString()));
+        }
+        if (body.get("priority") != null && StringUtils.isNotEmpty(body.get("priority").toString())) {
+            pricing.setPriority(Integer.valueOf(body.get("priority").toString()));
+        }
+        if (body.get("isPrimary") != null && StringUtils.isNotEmpty(body.get("isPrimary").toString())) {
+            pricing.setIsPrimary(Integer.valueOf(body.get("isPrimary").toString()));
+        }
+        Object selectable = body.get("selectable");
+        if (selectable == null) {
+            selectable = body.get("allowManual");
+        }
+        if (selectable != null && StringUtils.isNotEmpty(selectable.toString())) {
+            pricing.setSelectable(selectable.toString());
+        }
+        return pricing;
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<Long> parseIdList(Object raw) {
+        List<Long> ids = new ArrayList<>();
+        if (raw == null) {
+            return ids;
+        }
+        if (raw instanceof List) {
+            for (Object item : (List<?>) raw) {
+                if (item != null) {
+                    ids.add(Long.valueOf(item.toString()));
+                }
+            }
+        }
+        return ids;
+    }
+}

+ 93 - 0
fs-admin/src/main/java/com/fs/admin/controller/CompanyVoiceController.java

@@ -0,0 +1,93 @@
+package com.fs.admin.controller;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.enums.DataSourceType;
+import com.fs.company.domain.CompanyVoiceApi;
+import com.fs.company.service.ICompanyVoiceApiService;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 外呼接口管理(新版 admin 专用)
+ *
+ * @author MixLiu
+ */
+@RestController
+@RequestMapping("/admin/companyVoice")
+public class CompanyVoiceController extends BaseController {
+
+    @Autowired(required = false)
+    private ICompanyVoiceApiService companyVoiceApiService;
+
+    /**
+     * 分页查询外呼接口列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyVoiceApi param) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        startPage();
+        List<CompanyVoiceApi> list = companyVoiceApiService != null
+                ? companyVoiceApiService.selectCompanyVoiceApiList(param)
+                : java.util.Collections.emptyList();
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取外呼接口详情
+     */
+    @GetMapping("/{apiId}")
+    public AjaxResult getInfo(@PathVariable Long apiId) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        if (companyVoiceApiService == null) {
+            return AjaxResult.error("服务未就绪");
+        }
+        CompanyVoiceApi api = companyVoiceApiService.selectCompanyVoiceApiById(apiId);
+        return AjaxResult.success(api);
+    }
+
+    /**
+     * 新增外呼接口
+     */
+    @Log(title = "外呼接口", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyVoiceApi companyVoiceApi) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        if (companyVoiceApiService == null) {
+            return AjaxResult.error("服务未就绪");
+        }
+        return toAjax(companyVoiceApiService.insertCompanyVoiceApi(companyVoiceApi));
+    }
+
+    /**
+     * 修改外呼接口
+     */
+    @Log(title = "外呼接口", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyVoiceApi companyVoiceApi) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        if (companyVoiceApiService == null) {
+            return AjaxResult.error("服务未就绪");
+        }
+        return toAjax(companyVoiceApiService.updateCompanyVoiceApi(companyVoiceApi));
+    }
+
+    /**
+     * 删除外呼接口(逻辑删除)
+     */
+    @Log(title = "外呼接口", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{apiId}")
+    public AjaxResult remove(@PathVariable Long apiId) {
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        if (companyVoiceApiService == null) {
+            return AjaxResult.error("服务未就绪");
+        }
+        return toAjax(companyVoiceApiService.deleteCompanyVoiceApiById(apiId));
+    }
+}

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceApiController.java

@@ -87,7 +87,7 @@ public class CompanyVoiceApiController extends BaseController {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long companyId = loginUser.getCompany() != null ? loginUser.getCompany().getCompanyId() : null;
         if (companyId == null) { return AjaxResult.error("请选择租户"); }
-        List<CompanyVoiceApiTenant> list = companyVoiceApiTenantService.selectEnabledApisByCompanyId(companyId);
+        List<CompanyVoiceApiTenant> list = companyVoiceApiTenantService.selectEnabledApisByTenantId(companyId);
         return AjaxResult.success(list);
     }
 

+ 53 - 60
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceApi.java

@@ -2,79 +2,72 @@ package com.fs.company.domain;
 
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
+import java.math.BigDecimal;
+
 /**
  * 呼叫接口对象 company_voice_api
- * 
+ *
  * @author fs
  * @date 2021-10-04
  */
-public class CompanyVoiceApi extends BaseEntity
-{
+@Data
+public class CompanyVoiceApi extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
-    /** ID */
     private Long apiId;
-
-    /** API接口名 */
-    @Excel(name = "API接口名")
+    /**
+     * API名称
+     */
     private String apiName;
-
-    /** KEY */
-    @Excel(name = "apiType")
-    private String apiType;
-
-    /** JSON */
-    @Excel(name = "JSON")
-    private String apiJson;
-
-    /** 状态 */
-    @Excel(name = "状态")
+    /**
+     * 接口类型:0 SIP,1 网关,2 API
+     */
+    private Integer apiType;
+
+    /**
+     * 状态: 0 禁用 ,1 启用
+     */
     private Integer status;
+    /**
+     * 备注
+     */
+    private String remark;
+    /**
+     * 成本价
+     */
+    private BigDecimal costPrice;
+    /**
+     * 账户
+     */
+    private String account;
+    /**
+     * 密码
+     */
+    private String password;
+    /**
+     * api地址
+     */
+    private String apiUrl;
+    /**
+     * 话术跳转地址(API类型)
+     */
+    private String dialogUrl;
+    /**
+     * 服务商
+     */
+    private String provider;
+    /**
+     * 历史字段,保留列暂不读写
+     */
+    private String apiJson;
 
-    public static long getSerialVersionUID() {
-        return serialVersionUID;
-    }
-
-    public Long getApiId() {
-        return apiId;
-    }
-
-    public void setApiId(Long apiId) {
-        this.apiId = apiId;
-    }
-
-    public String getApiName() {
-        return apiName;
-    }
-
-    public void setApiName(String apiName) {
-        this.apiName = apiName;
-    }
-
-    public String getApiType() {
-        return apiType;
-    }
-
-    public void setApiType(String apiType) {
-        this.apiType = apiType;
-    }
-
-    public String getApiJson() {
-        return apiJson;
-    }
-
-    public void setApiJson(String apiJson) {
-        this.apiJson = apiJson;
-    }
-
-    public Integer getStatus() {
-        return status;
-    }
+    /**
+     * 是否删除,0否 1是
+     */
+    private Integer isDel;
 
-    public void setStatus(Integer status) {
-        this.status = status;
-    }
 }

+ 58 - 133
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceApiTenant.java

@@ -1,7 +1,9 @@
 package com.fs.company.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
 
 import java.math.BigDecimal;
 import java.util.Date;
@@ -12,164 +14,87 @@ import java.util.Date;
  * @author fs
  * @date 2026-05-21
  */
-public class CompanyVoiceApiTenant extends BaseEntity
-{
+@Data
+public class CompanyVoiceApiTenant extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
-    /** 主键 */
+    /**
+     * 主键
+     */
     private Long id;
 
-    /** 通话接口ID */
+    /**
+     * 通话接口ID
+     */
     private Long apiId;
 
-    /** 租户ID */
-    private Long companyId;
+    /**
+     * 租户ID(tenant_info.id)
+     */
+    private Long tenantId;
 
-    /** 售价(元/分钟) */
-    private BigDecimal price;
+    /**
+     * 售价(元/分钟)
+     */
+    private BigDecimal salePrice;
 
-    /** 优先级 */
+    /**
+     * 优先级
+     */
     private Integer priority;
 
-    /** 是否主线路 1是 0否 */
+    /**
+     * 是否主线路 1是 0否
+     */
     private Integer isPrimary;
 
-    /** 是否允许手动选择 1允许 0禁止 */
-    private Integer allowManual;
-
-    /** 状态 1启用 0禁用 */
+    /**
+     * 状态 1启用 0禁用
+     */
     private Integer status;
 
-    /** 创建时间 */
+    /**
+     * 创建时间
+     */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
 
-    /** 租户名(非表字段,关联查询用) */
-    private String companyName;
-
-    /** 接口名(非表字段,关联查询用) */
+    /**
+     * 接口名(非表字段,关联 company_voice_api 查询)
+     */
+    @TableField(exist = false)
     private String apiName;
 
-    /** 成本价(非表字段,关联查询用) */
+    /**
+     * 成本价(非表字段,关联 company_voice_api 查询)
+     */
+    @TableField(exist = false)
     private BigDecimal costPrice;
 
-    /** 接口类型(非表字段,关联查询用) */
+    /**
+     * 接口类型(非表字段,关联 company_voice_api 查询)
+     */
+    @TableField(exist = false)
     private Integer apiType;
 
-    /** 服务商(非表字段,关联查询用) */
+    /**
+     * 服务商(非表字段,关联 company_voice_api 查询)
+     */
+    @TableField(exist = false)
     private String provider;
 
-    public Long getId() {
-        return id;
-    }
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public Long getApiId() {
-        return apiId;
-    }
-
-    public void setApiId(Long apiId) {
-        this.apiId = apiId;
-    }
-
-    public Long getCompanyId() {
-        return companyId;
-    }
-
-    public void setCompanyId(Long companyId) {
-        this.companyId = companyId;
-    }
-
-    public Integer getStatus() {
-        return status;
-    }
-
-    public void setStatus(Integer status) {
-        this.status = status;
-    }
-
-    @Override
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    @Override
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    public String getCompanyName() {
-        return companyName;
-    }
-
-    public void setCompanyName(String companyName) {
-        this.companyName = companyName;
-    }
-
-    public String getApiName() {
-        return apiName;
-    }
-
-    public void setApiName(String apiName) {
-        this.apiName = apiName;
-    }
-
-    public BigDecimal getPrice() {
-        return price;
-    }
-
-    public void setPrice(BigDecimal price) {
-        this.price = price;
-    }
-
-    public Integer getPriority() {
-        return priority;
-    }
-
-    public void setPriority(Integer priority) {
-        this.priority = priority;
-    }
-
-    public Integer getIsPrimary() {
-        return isPrimary;
-    }
-
-    public void setIsPrimary(Integer isPrimary) {
-        this.isPrimary = isPrimary;
-    }
-
-    public Integer getAllowManual() {
-        return allowManual;
-    }
-
-    public void setAllowManual(Integer allowManual) {
-        this.allowManual = allowManual;
-    }
-
-    public BigDecimal getCostPrice() {
-        return costPrice;
-    }
+    /**
+     * 租户编码(冗余字段,关联 tenant_info 补全)
+     */
+    private String tenantCode;
 
-    public void setCostPrice(BigDecimal costPrice) {
-        this.costPrice = costPrice;
-    }
+    /**
+     * 租户名称(冗余字段,关联 tenant_info 补全)
+     */
+    private String tenantName;
+    /**
+     * 是否可选
+     */
+    private String selectable;
 
-    public Integer getApiType() {
-        return apiType;
-    }
-
-    public void setApiType(Integer apiType) {
-        this.apiType = apiType;
-    }
-
-    public String getProvider() {
-        return provider;
-    }
-
-    public void setProvider(String provider) {
-        this.provider = provider;
-    }
 }

+ 1 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceApiMapper.java

@@ -59,6 +59,6 @@ public interface CompanyVoiceApiMapper
      * @return 结果
      */
     public int deleteCompanyVoiceApiByIds(Long[] apiIds);
-    @Select("select count(1) from company_voice_api")
+    @Select("select count(1) from company_voice_api where (is_del = 0 or is_del is null)")
     Integer selectCompanyVoiceApiCount();
 }

+ 32 - 58
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceApiTenantMapper.java

@@ -14,64 +14,38 @@ import java.util.List;
  */
 public interface CompanyVoiceApiTenantMapper
 {
-    /**
-     * 查询分配关系
-     */
-    public CompanyVoiceApiTenant selectCompanyVoiceApiTenantById(Long id);
-
-    /**
-     * 按接口ID+租户ID查询分配关系
-     */
-    public CompanyVoiceApiTenant selectByApiAndCompany(@Param("apiId") Long apiId, @Param("companyId") Long companyId);
-
-    /**
-     * 查询接口已分配的租户列表(含租户名)
-     */
-    public List<CompanyVoiceApiTenant> selectTenantsByApiId(Long apiId);
-
-    /**
-     * 查询租户已分配的接口列表(含接口名)
-     */
-    public List<CompanyVoiceApiTenant> selectApisByCompanyId(Long companyId);
-
-    /**
-     * 查询分配关系列表
-     */
-    public List<CompanyVoiceApiTenant> selectCompanyVoiceApiTenantList(CompanyVoiceApiTenant param);
-
-    /**
-     * 新增分配关系
-     */
-    public int insertCompanyVoiceApiTenant(CompanyVoiceApiTenant companyVoiceApiTenant);
-
-    /**
-     * 批量新增分配关系
-     */
-    public int batchInsertCompanyVoiceApiTenant(@Param("list") List<CompanyVoiceApiTenant> list);
-
-    /**
-     * 修改分配关系
-     */
-    public int updateCompanyVoiceApiTenant(CompanyVoiceApiTenant companyVoiceApiTenant);
-
-    /**
-     * 删除分配关系
-     */
-    public int deleteCompanyVoiceApiTenantById(Long id);
-
-    /**
-     * 按接口ID+租户ID删除分配关系
-     */
-    public int deleteByApiAndCompany(@Param("apiId") Long apiId, @Param("companyId") Long companyId);
-
-    /**
-     * 按接口ID删除所有分配关系
-     */
-    public int deleteByApiId(Long apiId);
-
-    /**
-     * 查询接口已分配的租户数量
-     */
+    CompanyVoiceApiTenant selectCompanyVoiceApiTenantById(Long id);
+
+    CompanyVoiceApiTenant selectByApiAndTenant(@Param("apiId") Long apiId, @Param("tenantId") Long tenantId);
+
+    List<CompanyVoiceApiTenant> selectTenantsByApiId(Long apiId);
+
+    List<CompanyVoiceApiTenant> selectApisByTenantId(Long tenantId);
+
+    List<CompanyVoiceApiTenant> selectEnabledApisByTenantId(Long tenantId);
+
+    List<CompanyVoiceApiTenant> selectCompanyVoiceApiTenantList(CompanyVoiceApiTenant param);
+
+    int insertCompanyVoiceApiTenant(CompanyVoiceApiTenant companyVoiceApiTenant);
+
+    int batchInsertCompanyVoiceApiTenant(@Param("list") List<CompanyVoiceApiTenant> list);
+
+    int updateCompanyVoiceApiTenant(CompanyVoiceApiTenant companyVoiceApiTenant);
+
+    int deleteCompanyVoiceApiTenantById(Long id);
+
+    int deleteByApiAndTenant(@Param("apiId") Long apiId, @Param("tenantId") Long tenantId);
+
+    int deleteByApiId(Long apiId);
+
+    int disableByApiId(Long apiId);
+
+    int disableByApiIds(@Param("apiIds") Long[] apiIds);
+
+    int batchUpdatePricing(@Param("ids") List<Long> ids, @Param("data") CompanyVoiceApiTenant data);
+
+    int batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") Integer status);
+
     @Select("SELECT COUNT(1) FROM company_voice_api_tenant WHERE api_id = #{apiId} AND status = 1")
     Integer selectTenantCountByApiId(Long apiId);
 }

+ 22 - 32
fs-service/src/main/java/com/fs/company/service/ICompanyVoiceApiTenantService.java

@@ -12,53 +12,43 @@ import java.util.List;
  */
 public interface ICompanyVoiceApiTenantService
 {
-    /**
-     * 查询分配关系
-     */
-    public CompanyVoiceApiTenant selectCompanyVoiceApiTenantById(Long id);
+    CompanyVoiceApiTenant selectCompanyVoiceApiTenantById(Long id);
 
-    /**
-     * 按接口ID+租户ID查询分配关系
-     */
-    public CompanyVoiceApiTenant selectByApiAndCompany(Long apiId, Long companyId);
+    CompanyVoiceApiTenant selectByApiAndTenant(Long apiId, Long tenantId);
 
-    /**
-     * 查询接口已分配的租户列表(含租户名)
-     */
-    public List<CompanyVoiceApiTenant> selectTenantsByApiId(Long apiId);
+    List<CompanyVoiceApiTenant> selectTenantsByApiId(Long apiId);
 
-    /**
-     * 查询租户已分配的接口列表(含接口名,仅启用的)
-     */
-    public List<CompanyVoiceApiTenant> selectEnabledApisByCompanyId(Long companyId);
+    List<CompanyVoiceApiTenant> selectEnabledApisByTenantId(Long tenantId);
 
-    /**
-     * 查询分配关系列表
-     */
-    public List<CompanyVoiceApiTenant> selectCompanyVoiceApiTenantList(CompanyVoiceApiTenant param);
+    List<CompanyVoiceApiTenant> selectCompanyVoiceApiTenantList(CompanyVoiceApiTenant param);
 
-    /**
-     * 分配接口给租户
-     */
-    public int assignTenant(CompanyVoiceApiTenant companyVoiceApiTenant);
+    int assignTenant(CompanyVoiceApiTenant companyVoiceApiTenant);
+
+    int batchAssignTenants(Long apiId, List<CompanyVoiceApiTenant> assignList);
+
+    int unassignTenant(Long apiId, Long tenantId);
 
     /**
-     * 批量分配接口给租户
+     * 停用接口下所有已启用的租户分配关系
      */
-    public int batchAssignTenants(Long apiId, List<Long> companyIds);
+    int disableTenantsByApiId(Long apiId);
 
     /**
-     * 取消分配
+     * 批量停用接口下所有已启用的租户分配关系
      */
-    public int unassignTenant(Long apiId, Long companyId);
+    int disableTenantsByApiIds(Long[] apiIds);
+
+    int updateCompanyVoiceApiTenant(CompanyVoiceApiTenant companyVoiceApiTenant);
 
     /**
-     * 修改分配关系状态
+     * 批量更新定价配置(仅更新非空字段)
      */
-    public int updateCompanyVoiceApiTenant(CompanyVoiceApiTenant companyVoiceApiTenant);
+    int batchUpdatePricing(List<Long> ids, CompanyVoiceApiTenant pricing);
 
     /**
-     * 查询接口已分配的租户数量(启用状态)
+     * 批量更新状态
      */
-    public Integer selectTenantCountByApiId(Long apiId);
+    int batchUpdateStatus(List<Long> ids, Integer status);
+
+    Integer selectTenantCountByApiId(Long apiId);
 }

+ 39 - 4
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceApiServiceImpl.java

@@ -1,11 +1,15 @@
 package com.fs.company.service.impl;
 
-import java.util.List;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import com.fs.company.mapper.CompanyVoiceApiMapper;
+import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyVoiceApi;
+import com.fs.company.mapper.CompanyVoiceApiMapper;
 import com.fs.company.service.ICompanyVoiceApiService;
+import com.fs.company.service.ICompanyVoiceApiTenantService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
 
 /**
  * 呼叫接口Service业务层处理
@@ -19,6 +23,9 @@ public class CompanyVoiceApiServiceImpl implements ICompanyVoiceApiService
     @Autowired
     private CompanyVoiceApiMapper companyVoiceApiMapper;
 
+    @Autowired
+    private ICompanyVoiceApiTenantService companyVoiceApiTenantService;
+
     /**
      * 查询呼叫接口
      * 
@@ -52,6 +59,7 @@ public class CompanyVoiceApiServiceImpl implements ICompanyVoiceApiService
     @Override
     public int insertCompanyVoiceApi(CompanyVoiceApi companyVoiceApi)
     {
+        prepareForSave(companyVoiceApi);
         return companyVoiceApiMapper.insertCompanyVoiceApi(companyVoiceApi);
     }
 
@@ -64,9 +72,24 @@ public class CompanyVoiceApiServiceImpl implements ICompanyVoiceApiService
     @Override
     public int updateCompanyVoiceApi(CompanyVoiceApi companyVoiceApi)
     {
+        prepareForSave(companyVoiceApi);
         return companyVoiceApiMapper.updateCompanyVoiceApi(companyVoiceApi);
     }
 
+    /** 保存前补全默认值(数据写入 account/password/api_url/dialog_url 等新字段,不使用 apiJson) */
+    private void prepareForSave(CompanyVoiceApi api) {
+        if (api == null) {
+            return;
+        }
+        if (StringUtils.isEmpty(api.getProvider())) {
+            api.setProvider("platform");
+        }
+        if (api.getIsDel() == null) {
+            api.setIsDel(0);
+        }
+//        api.setApiJson(null);
+    }
+
     /**
      * 批量删除呼叫接口
      * 
@@ -74,8 +97,14 @@ public class CompanyVoiceApiServiceImpl implements ICompanyVoiceApiService
      * @return 结果
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public int deleteCompanyVoiceApiByIds(Long[] apiIds)
     {
+        if (apiIds == null || apiIds.length == 0)
+        {
+            return 0;
+        }
+        companyVoiceApiTenantService.disableTenantsByApiIds(apiIds);
         return companyVoiceApiMapper.deleteCompanyVoiceApiByIds(apiIds);
     }
 
@@ -86,8 +115,14 @@ public class CompanyVoiceApiServiceImpl implements ICompanyVoiceApiService
      * @return 结果
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public int deleteCompanyVoiceApiById(Long apiId)
     {
+        if (apiId == null)
+        {
+            return 0;
+        }
+        companyVoiceApiTenantService.disableTenantsByApiId(apiId);
         return companyVoiceApiMapper.deleteCompanyVoiceApiById(apiId);
     }
 

+ 218 - 125
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceApiTenantServiceImpl.java

@@ -1,125 +1,218 @@
-package com.fs.company.service.impl;
-
-import com.fs.company.domain.CompanyVoiceApiTenant;
-import com.fs.company.mapper.CompanyVoiceApiTenantMapper;
-import com.fs.company.service.ICompanyVoiceApiTenantService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 通话接口-租户分配关系Service实现
- *
- * @author fs
- * @date 2026-05-21
- */
-@Service
-public class CompanyVoiceApiTenantServiceImpl implements ICompanyVoiceApiTenantService
-{
-    @Autowired
-    private CompanyVoiceApiTenantMapper companyVoiceApiTenantMapper;
-
-    @Override
-    public CompanyVoiceApiTenant selectCompanyVoiceApiTenantById(Long id)
-    {
-        return companyVoiceApiTenantMapper.selectCompanyVoiceApiTenantById(id);
-    }
-
-    @Override
-    public CompanyVoiceApiTenant selectByApiAndCompany(Long apiId, Long companyId)
-    {
-        return companyVoiceApiTenantMapper.selectByApiAndCompany(apiId, companyId);
-    }
-
-    @Override
-    public List<CompanyVoiceApiTenant> selectTenantsByApiId(Long apiId)
-    {
-        return companyVoiceApiTenantMapper.selectTenantsByApiId(apiId);
-    }
-
-    @Override
-    public List<CompanyVoiceApiTenant> selectEnabledApisByCompanyId(Long companyId)
-    {
-        CompanyVoiceApiTenant param = new CompanyVoiceApiTenant();
-        param.setCompanyId(companyId);
-        param.setStatus(1);
-        return companyVoiceApiTenantMapper.selectApisByCompanyId(companyId);
-    }
-
-    @Override
-    public List<CompanyVoiceApiTenant> selectCompanyVoiceApiTenantList(CompanyVoiceApiTenant param)
-    {
-        return companyVoiceApiTenantMapper.selectCompanyVoiceApiTenantList(param);
-    }
-
-    @Override
-    public int assignTenant(CompanyVoiceApiTenant companyVoiceApiTenant)
-    {
-        // 检查是否已存在分配关系
-        CompanyVoiceApiTenant existing = companyVoiceApiTenantMapper.selectByApiAndCompany(
-                companyVoiceApiTenant.getApiId(), companyVoiceApiTenant.getCompanyId());
-        if (existing != null)
-        {
-            // 已存在则更新状态为启用
-            existing.setStatus(1);
-            return companyVoiceApiTenantMapper.updateCompanyVoiceApiTenant(existing);
-        }
-        if (companyVoiceApiTenant.getStatus() == null)
-        {
-            companyVoiceApiTenant.setStatus(1);
-        }
-        return companyVoiceApiTenantMapper.insertCompanyVoiceApiTenant(companyVoiceApiTenant);
-    }
-
-    @Override
-    public int batchAssignTenants(Long apiId, List<Long> companyIds)
-    {
-        if (companyIds == null || companyIds.isEmpty())
-        {
-            return 0;
-        }
-        List<CompanyVoiceApiTenant> toInsert = new ArrayList<>();
-        for (Long companyId : companyIds)
-        {
-            CompanyVoiceApiTenant existing = companyVoiceApiTenantMapper.selectByApiAndCompany(apiId, companyId);
-            if (existing == null)
-            {
-                CompanyVoiceApiTenant rel = new CompanyVoiceApiTenant();
-                rel.setApiId(apiId);
-                rel.setCompanyId(companyId);
-                rel.setStatus(1);
-                toInsert.add(rel);
-            }
-            else if (existing.getStatus() == null || existing.getStatus() == 0)
-            {
-                existing.setStatus(1);
-                companyVoiceApiTenantMapper.updateCompanyVoiceApiTenant(existing);
-            }
-        }
-        if (!toInsert.isEmpty())
-        {
-            return companyVoiceApiTenantMapper.batchInsertCompanyVoiceApiTenant(toInsert);
-        }
-        return 0;
-    }
-
-    @Override
-    public int unassignTenant(Long apiId, Long companyId)
-    {
-        return companyVoiceApiTenantMapper.deleteByApiAndCompany(apiId, companyId);
-    }
-
-    @Override
-    public int updateCompanyVoiceApiTenant(CompanyVoiceApiTenant companyVoiceApiTenant)
-    {
-        return companyVoiceApiTenantMapper.updateCompanyVoiceApiTenant(companyVoiceApiTenant);
-    }
-
-    @Override
-    public Integer selectTenantCountByApiId(Long apiId)
-    {
-        return companyVoiceApiTenantMapper.selectTenantCountByApiId(apiId);
-    }
-}
+package com.fs.company.service.impl;

+

+import com.fs.common.utils.StringUtils;

+import com.fs.company.domain.CompanyVoiceApiTenant;

+import com.fs.company.mapper.CompanyVoiceApiTenantMapper;

+import com.fs.company.service.ICompanyVoiceApiTenantService;

+import com.fs.tenant.domain.TenantInfo;

+import com.fs.tenant.mapper.TenantInfoMapper;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Service;

+import org.springframework.transaction.annotation.Transactional;

+

+import java.util.ArrayList;

+import java.util.List;

+

+/**

+ * 通话接口-租户分配关系Service实现

+ *

+ * @author fs

+ * @date 2026-05-21

+ */

+@Service

+public class CompanyVoiceApiTenantServiceImpl implements ICompanyVoiceApiTenantService

+{

+    @Autowired

+    private CompanyVoiceApiTenantMapper companyVoiceApiTenantMapper;

+

+    @Autowired

+    private TenantInfoMapper tenantInfoMapper;

+

+    @Override

+    public CompanyVoiceApiTenant selectCompanyVoiceApiTenantById(Long id)

+    {

+        return companyVoiceApiTenantMapper.selectCompanyVoiceApiTenantById(id);

+    }

+

+    @Override

+    public CompanyVoiceApiTenant selectByApiAndTenant(Long apiId, Long tenantId)

+    {

+        return companyVoiceApiTenantMapper.selectByApiAndTenant(apiId, tenantId);

+    }

+

+    @Override

+    public List<CompanyVoiceApiTenant> selectTenantsByApiId(Long apiId)

+    {

+        return companyVoiceApiTenantMapper.selectTenantsByApiId(apiId);

+    }

+

+    @Override

+    public List<CompanyVoiceApiTenant> selectEnabledApisByTenantId(Long tenantId)

+    {

+        return companyVoiceApiTenantMapper.selectEnabledApisByTenantId(tenantId);

+    }

+

+    @Override

+    public List<CompanyVoiceApiTenant> selectCompanyVoiceApiTenantList(CompanyVoiceApiTenant param)

+    {

+        return companyVoiceApiTenantMapper.selectCompanyVoiceApiTenantList(param);

+    }

+

+    @Override

+    public int assignTenant(CompanyVoiceApiTenant companyVoiceApiTenant)

+    {

+        fillTenantFields(companyVoiceApiTenant);

+        CompanyVoiceApiTenant existing = companyVoiceApiTenantMapper.selectByApiAndTenant(

+                companyVoiceApiTenant.getApiId(), companyVoiceApiTenant.getTenantId());

+        if (existing != null)

+        {

+            existing.setStatus(1);

+            copyTenantFields(companyVoiceApiTenant, existing);

+            return companyVoiceApiTenantMapper.updateCompanyVoiceApiTenant(existing);

+        }

+        if (companyVoiceApiTenant.getStatus() == null)

+        {

+            companyVoiceApiTenant.setStatus(1);

+        }

+        return companyVoiceApiTenantMapper.insertCompanyVoiceApiTenant(companyVoiceApiTenant);

+    }

+

+    @Override

+    public int batchAssignTenants(Long apiId, List<CompanyVoiceApiTenant> assignList)

+    {

+        if (apiId == null || assignList == null || assignList.isEmpty())

+        {

+            return 0;

+        }

+        List<CompanyVoiceApiTenant> toInsert = new ArrayList<>();

+        for (CompanyVoiceApiTenant item : assignList)

+        {

+            if (item == null || item.getTenantId() == null)

+            {

+                continue;

+            }

+            item.setApiId(apiId);

+            fillTenantFields(item);

+            CompanyVoiceApiTenant existing = companyVoiceApiTenantMapper.selectByApiAndTenant(apiId, item.getTenantId());

+            if (existing == null)

+            {

+                if (item.getStatus() == null)

+                {

+                    item.setStatus(1);

+                }

+                toInsert.add(item);

+            }

+            else if (existing.getStatus() == null || existing.getStatus() == 0)

+            {

+                existing.setStatus(1);

+                copyTenantFields(item, existing);

+                companyVoiceApiTenantMapper.updateCompanyVoiceApiTenant(existing);

+            }

+        }

+        if (!toInsert.isEmpty())

+        {

+            return companyVoiceApiTenantMapper.batchInsertCompanyVoiceApiTenant(toInsert);

+        }

+        return 0;

+    }

+

+    private void fillTenantFields(CompanyVoiceApiTenant rel)

+    {

+        if (rel == null || rel.getTenantId() == null

+                || (!StringUtils.isEmpty(rel.getTenantCode()) && !StringUtils.isEmpty(rel.getTenantName())))

+        {

+            return;

+        }

+        TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(String.valueOf(rel.getTenantId()));

+        if (tenantInfo != null)

+        {

+            if (StringUtils.isEmpty(rel.getTenantCode()))

+            {

+                rel.setTenantCode(tenantInfo.getTenantCode());

+            }

+            if (StringUtils.isEmpty(rel.getTenantName()))

+            {

+                rel.setTenantName(tenantInfo.getTenantName());

+            }

+        }

+    }

+

+    private void copyTenantFields(CompanyVoiceApiTenant source, CompanyVoiceApiTenant target)

+    {

+        if (source == null || target == null)

+        {

+            return;

+        }

+        target.setTenantCode(source.getTenantCode());

+        target.setTenantName(source.getTenantName());

+    }

+

+    @Override

+    public int unassignTenant(Long apiId, Long tenantId)

+    {

+        return companyVoiceApiTenantMapper.deleteByApiAndTenant(apiId, tenantId);

+    }

+

+    @Override

+    public int disableTenantsByApiId(Long apiId)

+    {

+        if (apiId == null)

+        {

+            return 0;

+        }

+        return companyVoiceApiTenantMapper.disableByApiId(apiId);

+    }

+

+    @Override

+    public int disableTenantsByApiIds(Long[] apiIds)

+    {

+        if (apiIds == null || apiIds.length == 0)

+        {

+            return 0;

+        }

+        return companyVoiceApiTenantMapper.disableByApiIds(apiIds);

+    }

+

+    @Override

+    public int updateCompanyVoiceApiTenant(CompanyVoiceApiTenant companyVoiceApiTenant)

+    {

+        return companyVoiceApiTenantMapper.updateCompanyVoiceApiTenant(companyVoiceApiTenant);

+    }

+

+    @Override

+    @Transactional(rollbackFor = Exception.class)

+    public int batchUpdatePricing(List<Long> ids, CompanyVoiceApiTenant pricing)

+    {

+        if (ids == null || ids.isEmpty() || pricing == null)

+        {

+            return 0;

+        }

+        boolean hasField = pricing.getSalePrice() != null

+                || pricing.getPriority() != null

+                || pricing.getIsPrimary() != null

+                || pricing.getSelectable() != null;

+        if (!hasField)

+        {

+            return 0;

+        }

+        return companyVoiceApiTenantMapper.batchUpdatePricing(ids, pricing);

+    }

+

+    @Override

+    @Transactional(rollbackFor = Exception.class)

+    public int batchUpdateStatus(List<Long> ids, Integer status)

+    {

+        if (ids == null || ids.isEmpty() || status == null)

+        {

+            return 0;

+        }

+        return companyVoiceApiTenantMapper.batchUpdateStatus(ids, status);

+    }

+

+    @Override

+    public Integer selectTenantCountByApiId(Long apiId)

+    {

+        return companyVoiceApiTenantMapper.selectTenantCountByApiId(apiId);

+    }

+}

+

+ 3 - 3
fs-service/src/main/java/com/fs/proxy/service/impl/BalanceServiceImpl.java

@@ -206,11 +206,11 @@ public class BalanceServiceImpl implements BalanceService {
 
         // AI外呼:复合定价 = 语音单价(租户绑定) + AI附加费(租户 > 全局 service_fee_config)
         if (consumeType == ConsumeTypeEnum.AI_CALL) {
-            List<CompanyVoiceApiTenant> voiceBindings = voiceApiTenantMapper.selectApisByCompanyId(tenantId);
+            List<CompanyVoiceApiTenant> voiceBindings = voiceApiTenantMapper.selectEnabledApisByTenantId(tenantId);
             if (voiceBindings != null && !voiceBindings.isEmpty()) {
                 CompanyVoiceApiTenant voiceBinding = voiceBindings.get(0);
-                if (voiceBinding.getPrice() != null && voiceBinding.getPrice().compareTo(BigDecimal.ZERO) > 0) {
-                    BigDecimal voicePrice = voiceBinding.getPrice();
+                if (voiceBinding.getSalePrice() != null && voiceBinding.getSalePrice().compareTo(BigDecimal.ZERO) > 0) {
+                    BigDecimal voicePrice = voiceBinding.getSalePrice();
                     BigDecimal voiceCost = voiceBinding.getCostPrice() != null ? voiceBinding.getCostPrice() : BigDecimal.ZERO;
                     // AI附加费:优先查租户定价,未配置则使用全局 service_fee_config
                     BigDecimal aiSurcharge = config.getFeeStandard();

+ 2 - 2
fs-service/src/main/java/com/fs/voice/service/impl/VoiceServiceImpl.java

@@ -100,7 +100,7 @@ public class VoiceServiceImpl implements IVoiceService
             return R.error("未配置外呼接口");
         }
         // 验证接口已分配给该租户
-        CompanyVoiceApiTenant apiTenant = companyVoiceApiTenantService.selectByApiAndCompany(apiId, companyId);
+        CompanyVoiceApiTenant apiTenant = companyVoiceApiTenantService.selectByApiAndTenant(apiId, companyId);
         if(apiTenant == null || apiTenant.getStatus() == 0){
             return R.error("该通话接口未分配给当前租户");
         }
@@ -346,7 +346,7 @@ public class VoiceServiceImpl implements IVoiceService
             return R.error("未配置外呼接口");
         }
         // 验证接口已分配给该租户
-        CompanyVoiceApiTenant apiTenant = companyVoiceApiTenantService.selectByApiAndCompany(apiId, companyId);
+        CompanyVoiceApiTenant apiTenant = companyVoiceApiTenantService.selectByApiAndTenant(apiId, companyId);
         if(apiTenant == null || apiTenant.getStatus() == 0){
             return R.error("该通话接口未分配给当前租户");
         }

+ 68 - 30
fs-service/src/main/resources/mapper/company/CompanyVoiceApiMapper.xml

@@ -3,52 +3,81 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.company.mapper.CompanyVoiceApiMapper">
-    
+
     <resultMap type="CompanyVoiceApi" id="CompanyVoiceApiResult">
-        <result property="apiId"    column="api_id"    />
-        <result property="apiName"    column="api_name"    />
-        <result property="apiType"    column="api_type"    />
-        <result property="apiJson"    column="api_json"    />
-        <result property="status"    column="status"    />
-        <result property="remark"    column="remark"    />
+        <result property="apiId"       column="api_id"       />
+        <result property="apiName"     column="api_name"     />
+        <result property="apiType"     column="api_type"     />
+        <result property="apiJson"     column="api_json"     />
+        <result property="status"      column="status"       />
+        <result property="remark"      column="remark"       />
+        <result property="provider"    column="provider"     />
+        <result property="costPrice"   column="cost_price"   />
+        <result property="account"     column="account"      />
+        <result property="password"    column="password"     />
+        <result property="apiUrl"      column="api_url"      />
+        <result property="dialogUrl"   column="dialog_url"   />
+        <result property="isDel"       column="is_del"       />
+        <result property="createTime"  column="create_time"  />
+        <result property="updateTime"  column="update_time"  />
     </resultMap>
 
     <sql id="selectCompanyVoiceApiVo">
-        select api_id, api_name, api_type, api_json, status, remark from company_voice_api
+        select api_id, api_name, api_type, status, remark,
+               provider, cost_price, account, password, api_url, dialog_url, is_del, create_time, update_time
+        from company_voice_api
     </sql>
 
     <select id="selectCompanyVoiceApiList" resultMap="CompanyVoiceApiResult">
         <include refid="selectCompanyVoiceApiVo"/>
-        <where>  
-            <if test="apiName != null  and apiName != ''"> and api_name like concat('%', #{apiName}, '%')</if>
-            <if test="apiType != null  and apiType != ''"> and api_type = #{apiType}</if>
-            <if test="apiJson != null  and apiJson != ''"> and api_json = #{apiJson}</if>
-            <if test="status != null "> and status = #{status}</if>
+        <where>
+            and (is_del = 0 or is_del is null)
+            <if test="apiName != null and apiName != ''"> and api_name like concat('%', #{apiName}, '%')</if>
+            <if test="apiType != null"> and api_type = #{apiType}</if>
+            <if test="provider != null and provider != ''"> and provider = #{provider}</if>
+            <if test="status != null"> and status = #{status}</if>
         </where>
         order by api_id desc
     </select>
-    
+
     <select id="selectCompanyVoiceApiById" resultMap="CompanyVoiceApiResult">
         <include refid="selectCompanyVoiceApiVo"/>
-        where api_id = #{apiId}
+        where api_id = #{apiId} and (is_del = 0 or is_del is null)
     </select>
-        
+
     <insert id="insertCompanyVoiceApi" useGeneratedKeys="true" keyProperty="apiId">
         insert into company_voice_api
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="apiName != null">api_name,</if>
             <if test="apiType != null">api_type,</if>
-            <if test="apiJson != null">api_json,</if>
             <if test="status != null">status,</if>
             <if test="remark != null">remark,</if>
-         </trim>
+            <if test="provider != null">provider,</if>
+            <if test="costPrice != null">cost_price,</if>
+            <if test="account != null">account,</if>
+            <if test="password != null">password,</if>
+            <if test="apiUrl != null">api_url,</if>
+            <if test="dialogUrl != null">dialog_url,</if>
+            is_del,
+            create_time,
+        </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="apiName != null">#{apiName},</if>
             <if test="apiType != null">#{apiType},</if>
-            <if test="apiJson != null">#{apiJson},</if>
             <if test="status != null">#{status},</if>
             <if test="remark != null">#{remark},</if>
-         </trim>
+            <if test="provider != null">#{provider},</if>
+            <if test="costPrice != null">#{costPrice},</if>
+            <if test="account != null">#{account},</if>
+            <if test="password != null">#{password},</if>
+            <if test="apiUrl != null">#{apiUrl},</if>
+            <if test="dialogUrl != null">#{dialogUrl},</if>
+            <choose>
+                <when test="isDel != null">#{isDel},</when>
+                <otherwise>0,</otherwise>
+            </choose>
+            sysdate(),
+        </trim>
     </insert>
 
     <update id="updateCompanyVoiceApi">
@@ -56,22 +85,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <trim prefix="SET" suffixOverrides=",">
             <if test="apiName != null">api_name = #{apiName},</if>
             <if test="apiType != null">api_type = #{apiType},</if>
-            <if test="apiJson != null">api_json = #{apiJson},</if>
             <if test="status != null">status = #{status},</if>
             <if test="remark != null">remark = #{remark},</if>
+            <if test="provider != null">provider = #{provider},</if>
+            <if test="costPrice != null">cost_price = #{costPrice},</if>
+            <if test="account != null">account = #{account},</if>
+            <if test="password != null">password = #{password},</if>
+            <if test="apiUrl != null">api_url = #{apiUrl},</if>
+            <if test="dialogUrl != null">dialog_url = #{dialogUrl},</if>
+            <if test="isDel != null">is_del = #{isDel},</if>
+            update_time = sysdate(),
         </trim>
-        where api_id = #{apiId}
+        where api_id = #{apiId} and (is_del = 0 or is_del is null)
     </update>
 
-    <delete id="deleteCompanyVoiceApiById">
-        delete from company_voice_api where api_id = #{apiId}
-    </delete>
+    <update id="deleteCompanyVoiceApiById">
+        update company_voice_api set is_del = 1, update_time = sysdate()
+        where api_id = #{apiId} and (is_del = 0 or is_del is null)
+    </update>
 
-    <delete id="deleteCompanyVoiceApiByIds">
-        delete from company_voice_api where api_id in 
+    <update id="deleteCompanyVoiceApiByIds">
+        update company_voice_api set is_del = 1, update_time = sysdate()
+        where (is_del = 0 or is_del is null) and api_id in
         <foreach item="apiId" collection="array" open="(" separator="," close=")">
             #{apiId}
         </foreach>
-    </delete>
-    
-</mapper>
+    </update>
+
+</mapper>

+ 101 - 34
fs-service/src/main/resources/mapper/company/CompanyVoiceApiTenantMapper.xml

@@ -7,14 +7,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <resultMap type="CompanyVoiceApiTenant" id="CompanyVoiceApiTenantResult">
         <result property="id"          column="id"          />
         <result property="apiId"       column="api_id"      />
-        <result property="companyId"   column="company_id"  />
-        <result property="price"       column="price"       />
+        <result property="tenantId"    column="tenant_id"   />
+        <result property="salePrice"   column="sale_price"  />
         <result property="priority"    column="priority"    />
         <result property="isPrimary"   column="is_primary"  />
-        <result property="allowManual" column="allow_manual"/>
+        <result property="selectable"  column="selectable"  />
         <result property="status"      column="status"      />
         <result property="createTime"  column="create_time" />
-        <result property="companyName" column="company_name"/>
+        <result property="tenantName"  column="tenant_name" />
+        <result property="tenantCode"  column="tenant_code" />
         <result property="apiName"     column="api_name"    />
         <result property="costPrice"   column="cost_price"  />
         <result property="apiType"     column="api_type"    />
@@ -22,8 +23,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectCompanyVoiceApiTenantVo">
-        select t.id, t.api_id, t.company_id, t.price, t.priority, t.is_primary, t.allow_manual, t.status, t.create_time
+        select t.id, t.api_id, t.tenant_id, t.tenant_code, t.tenant_name,
+               t.sale_price, t.priority, t.is_primary, t.selectable, t.status, t.create_time,
+               a.api_name, a.cost_price, a.provider, a.api_type
         from company_voice_api_tenant t
+        left join company_voice_api a on a.api_id = t.api_id and (a.is_del = 0 or a.is_del is null)
     </sql>
 
     <select id="selectCompanyVoiceApiTenantById" resultMap="CompanyVoiceApiTenantResult">
@@ -31,41 +35,65 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where t.id = #{id}
     </select>
 
-    <select id="selectByApiAndCompany" resultMap="CompanyVoiceApiTenantResult">
+    <select id="selectByApiAndTenant" resultMap="CompanyVoiceApiTenantResult">
         <include refid="selectCompanyVoiceApiTenantVo"/>
-        where t.api_id = #{apiId} and t.company_id = #{companyId}
+        where t.api_id = #{apiId} and t.tenant_id = #{tenantId}
     </select>
 
     <!-- 查询接口已分配的租户列表(含租户名、定价信息) -->
     <select id="selectTenantsByApiId" resultMap="CompanyVoiceApiTenantResult">
-        select t.id, t.api_id, t.company_id, t.price, t.priority, t.is_primary, t.allow_manual, t.status, t.create_time,
-               c.company_name, a.cost_price, a.provider, a.api_type
+        select t.id, t.api_id, t.tenant_id, t.sale_price, t.priority, t.is_primary, t.selectable, t.status, t.create_time,
+               a.api_name,
+               coalesce(t.tenant_name, ti.tenant_name) as tenant_name,
+               coalesce(t.tenant_code, ti.tenant_code) as tenant_code,
+               a.cost_price, a.provider, a.api_type
         from company_voice_api_tenant t
-        left join company c on c.company_id = t.company_id
-        left join company_voice_api a on a.api_id = t.api_id
+        left join tenant_info ti on ti.id = t.tenant_id
+        left join company_voice_api a on a.api_id = t.api_id and (a.is_del = 0 or a.is_del is null)
         where t.api_id = #{apiId}
         order by t.priority asc, t.id desc
     </select>
 
     <!-- 查询租户已分配的接口列表(含接口名、成本价、服务商) -->
-    <select id="selectApisByCompanyId" resultMap="CompanyVoiceApiTenantResult">
-        select t.id, t.api_id, t.company_id, t.price, t.priority, t.is_primary, t.allow_manual, t.status, t.create_time,
-               a.api_name, a.cost_price, a.provider, a.api_type
+    <select id="selectApisByTenantId" resultMap="CompanyVoiceApiTenantResult">
+        select t.id, t.api_id, t.tenant_id, t.sale_price, t.priority, t.is_primary, t.selectable, t.status, t.create_time,
+               a.api_name,
+               coalesce(t.tenant_name, ti.tenant_name) as tenant_name,
+               coalesce(t.tenant_code, ti.tenant_code) as tenant_code,
+               a.cost_price, a.provider, a.api_type
+        from company_voice_api_tenant t
+        left join tenant_info ti on ti.id = t.tenant_id
+        left join company_voice_api a on a.api_id = t.api_id and (a.is_del = 0 or a.is_del is null)
+        where t.tenant_id = #{tenantId}
+        order by t.priority asc, t.id desc
+    </select>
+
+    <!-- 查询租户已分配且启用的接口列表 -->
+    <select id="selectEnabledApisByTenantId" resultMap="CompanyVoiceApiTenantResult">
+        select t.id, t.api_id, t.tenant_id, t.sale_price, t.priority, t.is_primary, t.selectable, t.status, t.create_time,
+               a.api_name,
+               coalesce(t.tenant_name, ti.tenant_name) as tenant_name,
+               coalesce(t.tenant_code, ti.tenant_code) as tenant_code,
+               a.cost_price, a.provider, a.api_type
         from company_voice_api_tenant t
-        left join company_voice_api a on a.api_id = t.api_id
-        where t.company_id = #{companyId}
+        left join tenant_info ti on ti.id = t.tenant_id
+        left join company_voice_api a on a.api_id = t.api_id and (a.is_del = 0 or a.is_del is null)
+        where t.tenant_id = #{tenantId} and t.status = 1
         order by t.priority asc, t.id desc
     </select>
 
     <select id="selectCompanyVoiceApiTenantList" resultMap="CompanyVoiceApiTenantResult">
-        select t.id, t.api_id, t.company_id, t.price, t.priority, t.is_primary, t.allow_manual, t.status, t.create_time,
-               c.company_name, a.api_name, a.cost_price, a.provider, a.api_type
+        select t.id, t.api_id, t.tenant_id, t.sale_price, t.priority, t.is_primary, t.selectable, t.status, t.create_time,
+               a.api_name,
+               coalesce(t.tenant_name, ti.tenant_name) as tenant_name,
+               coalesce(t.tenant_code, ti.tenant_code) as tenant_code,
+               a.cost_price, a.provider, a.api_type
         from company_voice_api_tenant t
-        left join company c on c.company_id = t.company_id
-        left join company_voice_api a on a.api_id = t.api_id
+        left join tenant_info ti on ti.id = t.tenant_id
+        left join company_voice_api a on a.api_id = t.api_id and (a.is_del = 0 or a.is_del is null)
         <where>
             <if test="apiId != null"> and t.api_id = #{apiId}</if>
-            <if test="companyId != null"> and t.company_id = #{companyId}</if>
+            <if test="tenantId != null"> and t.tenant_id = #{tenantId}</if>
             <if test="status != null"> and t.status = #{status}</if>
         </where>
         order by t.priority asc, t.id desc
@@ -75,31 +103,35 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         insert into company_voice_api_tenant
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="apiId != null">api_id,</if>
-            <if test="companyId != null">company_id,</if>
-            <if test="price != null">price,</if>
+            <if test="tenantId != null">tenant_id,</if>
+            <if test="tenantCode != null">tenant_code,</if>
+            <if test="tenantName != null">tenant_name,</if>
+            <if test="salePrice != null">sale_price,</if>
             <if test="priority != null">priority,</if>
             <if test="isPrimary != null">is_primary,</if>
-            <if test="allowManual != null">allow_manual,</if>
+            <if test="selectable != null">selectable,</if>
             <if test="status != null">status,</if>
             <if test="createTime != null">create_time,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="apiId != null">#{apiId},</if>
-            <if test="companyId != null">#{companyId},</if>
-            <if test="price != null">#{price},</if>
+            <if test="tenantId != null">#{tenantId},</if>
+            <if test="tenantCode != null">#{tenantCode},</if>
+            <if test="tenantName != null">#{tenantName},</if>
+            <if test="salePrice != null">#{salePrice},</if>
             <if test="priority != null">#{priority},</if>
             <if test="isPrimary != null">#{isPrimary},</if>
-            <if test="allowManual != null">#{allowManual},</if>
+            <if test="selectable != null">#{selectable},</if>
             <if test="status != null">#{status},</if>
             <if test="createTime != null">#{createTime},</if>
         </trim>
     </insert>
 
     <insert id="batchInsertCompanyVoiceApiTenant">
-        insert into company_voice_api_tenant (api_id, company_id, price, priority, is_primary, allow_manual, status, create_time)
+        insert into company_voice_api_tenant (api_id, tenant_id, tenant_code, tenant_name, sale_price, priority, is_primary, selectable, status, create_time)
         values
         <foreach item="item" collection="list" separator=",">
-            (#{item.apiId}, #{item.companyId}, #{item.price}, #{item.priority}, #{item.isPrimary}, #{item.allowManual}, #{item.status}, NOW())
+            (#{item.apiId}, #{item.tenantId}, #{item.tenantCode}, #{item.tenantName}, #{item.salePrice}, #{item.priority}, #{item.isPrimary}, #{item.selectable}, #{item.status}, NOW())
         </foreach>
     </insert>
 
@@ -107,11 +139,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         update company_voice_api_tenant
         <trim prefix="SET" suffixOverrides=",">
             <if test="apiId != null">api_id = #{apiId},</if>
-            <if test="companyId != null">company_id = #{companyId},</if>
-            <if test="price != null">price = #{price},</if>
+            <if test="tenantId != null">tenant_id = #{tenantId},</if>
+            <if test="tenantCode != null">tenant_code = #{tenantCode},</if>
+            <if test="tenantName != null">tenant_name = #{tenantName},</if>
+            <if test="salePrice != null">sale_price = #{salePrice},</if>
             <if test="priority != null">priority = #{priority},</if>
             <if test="isPrimary != null">is_primary = #{isPrimary},</if>
-            <if test="allowManual != null">allow_manual = #{allowManual},</if>
+            <if test="selectable != null">selectable = #{selectable},</if>
             <if test="status != null">status = #{status},</if>
         </trim>
         where id = #{id}
@@ -121,12 +155,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         delete from company_voice_api_tenant where id = #{id}
     </delete>
 
-    <delete id="deleteByApiAndCompany">
-        delete from company_voice_api_tenant where api_id = #{apiId} and company_id = #{companyId}
+    <delete id="deleteByApiAndTenant">
+        delete from company_voice_api_tenant where api_id = #{apiId} and tenant_id = #{tenantId}
     </delete>
 
     <delete id="deleteByApiId">
         delete from company_voice_api_tenant where api_id = #{apiId}
     </delete>
 
+    <update id="disableByApiId">
+        update company_voice_api_tenant set status = 0 where api_id = #{apiId} and status = 1
+    </update>
+
+    <update id="disableByApiIds">
+        update company_voice_api_tenant set status = 0 where status = 1 and api_id in
+        <foreach item="apiId" collection="apiIds" open="(" separator="," close=")">
+            #{apiId}
+        </foreach>
+    </update>
+
+    <update id="batchUpdatePricing">
+        update company_voice_api_tenant
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="data.salePrice != null">sale_price = #{data.salePrice},</if>
+            <if test="data.priority != null">priority = #{data.priority},</if>
+            <if test="data.isPrimary != null">is_primary = #{data.isPrimary},</if>
+            <if test="data.selectable != null">selectable = #{data.selectable},</if>
+        </trim>
+        where id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
+
+    <update id="batchUpdateStatus">
+        update company_voice_api_tenant set status = #{status}
+        where id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
+
 </mapper>