增加租户套餐
This commit is contained in:
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -373,6 +374,20 @@ func (c *BackendAdminUserController) AddUser() {
|
||||
status = *p.Status
|
||||
}
|
||||
|
||||
// 用户数配额校验:达到上限后需要平台端为租户增购用户数才能继续添加
|
||||
if check, err := services.CheckTenantUserQuota(p.Tid, p.Uid); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "用户数校验失败: " + err.Error()}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
} else if !check.Allowed {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 400,
|
||||
"msg": fmt.Sprintf("当前租户用户数已达上限(%d/%d),请联系平台管理员增购用户数", check.Used, check.Quota),
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
id, err := services.BindTenantUser(
|
||||
p.Tid,
|
||||
p.Uid,
|
||||
|
||||
@@ -3,6 +3,8 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
"server/services"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -91,26 +93,74 @@ func (c *BackendMenuController) GetBackendMenu() {
|
||||
// 否则仅返回角色允许访问的菜单(含其祖先节点,保证树结构完整)。
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
uid, _ := strconv.ParseUint(idStr, 10, 64)
|
||||
tid := uint64(0)
|
||||
if uid > 0 {
|
||||
var tu models.SystemTenantUser
|
||||
if e := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
Filter("id", uid).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&tu); e == nil && tu.GroupID > 0 {
|
||||
var role models.AdminRole
|
||||
if re := models.Orm.QueryTable(new(models.AdminRole)).
|
||||
Filter("id", tu.GroupID).
|
||||
Filter("cid", 2).
|
||||
One(&role); re == nil {
|
||||
backendMenus = filterMenusByRights(backendMenus, role.Rights)
|
||||
One(&tu); e == nil {
|
||||
tid = tu.Tid
|
||||
if tu.GroupID > 0 {
|
||||
var role models.AdminRole
|
||||
if re := models.Orm.QueryTable(new(models.AdminRole)).
|
||||
Filter("id", tu.GroupID).
|
||||
Filter("cid", 2).
|
||||
One(&role); re == nil {
|
||||
backendMenus = filterMenusByRights(backendMenus, role.Rights)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 套餐功能开通过滤:仅展示租户套餐已包含的功能模块对应的菜单(未包含的整块菜单不展示)
|
||||
backendMenus = filterMenusByTenantPackage(c.Ctx.Request.Header.Get("Authorization"), backendMenus, tid)
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(backendMenus, 0)}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// backendJWTTenantID 从 Authorization 的 Bearer Token 中解析租户ID;取不到时返回 0。
|
||||
// 说明:backend 端登录时 token 里写入了租户ID(GenerateToken 的 tenantId),
|
||||
// 用它做套餐功能过滤比依赖前端传参更可靠。
|
||||
func backendJWTTenantID(auth string) uint64 {
|
||||
auth = strings.TrimSpace(auth)
|
||||
if auth == "" {
|
||||
return 0
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return 0
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil || claims == nil || claims.TenantId <= 0 {
|
||||
return 0
|
||||
}
|
||||
return uint64(claims.TenantId)
|
||||
}
|
||||
|
||||
// filterMenusByTenantPackage 按租户套餐包含的功能模块过滤菜单。
|
||||
// tid 优先取 token 中的租户ID,其次用调用方传入的兜底值;两者都取不到时不处理(保持原行为)。
|
||||
func filterMenusByTenantPackage(authHeader string, menus []models.SystemMenu, tid uint64) []models.SystemMenu {
|
||||
if jwtTid := backendJWTTenantID(authHeader); jwtTid > 0 {
|
||||
tid = jwtTid
|
||||
}
|
||||
if tid == 0 {
|
||||
return menus
|
||||
}
|
||||
allModules, err := services.ListEnabledModules()
|
||||
if err != nil || len(allModules) == 0 {
|
||||
return menus
|
||||
}
|
||||
allowed := services.GetTenantModuleCodes(tid)
|
||||
// 安全兜底:租户没有绑定套餐或套餐未配置任何功能模块时不做过滤,
|
||||
// 避免因套餐数据缺失导致租户端菜单整体消失。
|
||||
if len(allowed) == 0 {
|
||||
return menus
|
||||
}
|
||||
return services.FilterMenusByTenantModules(menus, allModules, allowed)
|
||||
}
|
||||
|
||||
// parseRightsToSet 将角色 rights 解析为菜单 ID 集合(兼容 JSON 数组 / 逗号分隔)
|
||||
func parseRightsToSet(raw string) map[uint64]bool {
|
||||
set := map[uint64]bool{}
|
||||
@@ -267,6 +317,10 @@ func (c *BackendMenuController) GetAssignableMenus() {
|
||||
menus = filterMenusByView(menus, cid)
|
||||
}
|
||||
menus = filterAssignableMenus(menus)
|
||||
// 租户端:仅可分配当前租户套餐已开通的功能模块下的菜单
|
||||
if cid == 2 {
|
||||
menus = filterMenusByTenantPackage(c.Ctx.Request.Header.Get("Authorization"), menus, 0)
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(menus, 0)}
|
||||
_ = c.ServeJSON()
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"server/services"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// BackendTenantQuotaController 租户端「用户数配额」信息(只读)
|
||||
// 供租户端用户管理页展示「已用/上限」;增购由平台端操作。
|
||||
type BackendTenantQuotaController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// GetQuotaInfo GET /backend/tenant/quotaInfo
|
||||
// 返回当前登录租户的用户数使用情况与生效套餐。
|
||||
func (c *BackendTenantQuotaController) GetQuotaInfo() {
|
||||
tid := backendJWTTenantID(c.Ctx.Request.Header.Get("Authorization"))
|
||||
if tid == 0 {
|
||||
// 兜底:从 tid 参数取(部分历史调用未带 token 场景)
|
||||
if v, err := c.GetUint64("tid"); err == nil {
|
||||
tid = v
|
||||
}
|
||||
}
|
||||
if tid == 0 {
|
||||
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录或非法请求"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
info, err := services.GetTenantQuotaInfo(tid)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "租户不存在"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
modules, _ := services.GetTenantPackageModules(tid)
|
||||
moduleCodes := make([]string, 0, len(modules))
|
||||
moduleNames := make([]string, 0, len(modules))
|
||||
for _, m := range modules {
|
||||
moduleCodes = append(moduleCodes, m.ModuleCode)
|
||||
moduleNames = append(moduleNames, m.ModuleName)
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{
|
||||
"tid": info.Tid,
|
||||
"package_id": info.EffectivePackageID,
|
||||
"package_name": info.PackageName,
|
||||
"quota": info.Quota,
|
||||
"used": info.Used,
|
||||
"remaining": info.Remaining,
|
||||
"extra_user_price": info.ExtraUserPrice,
|
||||
"module_codes": moduleCodes,
|
||||
"module_names": moduleNames,
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
@@ -2,12 +2,14 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/services"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
@@ -27,10 +29,43 @@ type tenantDTO struct {
|
||||
Address string `json:"address"`
|
||||
Worktime string `json:"worktime"`
|
||||
Status int8 `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
CreateTime *time.Time `json:"create_time,omitempty"`
|
||||
UpdateTime *time.Time `json:"update_time,omitempty"`
|
||||
DeleteTime *time.Time `json:"delete_time,omitempty"`
|
||||
// 套餐与用户数配额
|
||||
PackageID uint64 `json:"package_id"`
|
||||
PackageName string `json:"package_name"`
|
||||
UserQuota int `json:"user_quota"`
|
||||
UserUsed int `json:"user_used"`
|
||||
Remark string `json:"remark"`
|
||||
CreateTime *time.Time `json:"create_time,omitempty"`
|
||||
UpdateTime *time.Time `json:"update_time,omitempty"`
|
||||
DeleteTime *time.Time `json:"delete_time,omitempty"`
|
||||
}
|
||||
|
||||
// tenantPackageNameMap 套餐ID -> 套餐名称
|
||||
func tenantPackageNameMap() map[uint64]string {
|
||||
result := map[uint64]string{}
|
||||
var rows []models.SystemTenantPackage
|
||||
if _, err := models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&rows, "ID", "Name"); err == nil {
|
||||
for _, r := range rows {
|
||||
result[r.ID] = r.Name
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// tenantUserCountMap 租户ID -> 有效用户数
|
||||
func tenantUserCountMap() map[uint64]int {
|
||||
result := map[uint64]int{}
|
||||
var rows []models.SystemTenantUser
|
||||
if _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&rows, "ID", "Tid"); err == nil {
|
||||
for _, r := range rows {
|
||||
result[r.Tid]++
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func stringValue(s *string) string {
|
||||
@@ -57,6 +92,8 @@ func toTenantDTO(t models.SystemTenant) tenantDTO {
|
||||
Address: stringValue(t.Address),
|
||||
Worktime: stringValue(t.Worktime),
|
||||
Status: t.Status,
|
||||
PackageID: t.PackageID,
|
||||
UserQuota: t.UserQuota,
|
||||
Remark: stringValue(t.Remark),
|
||||
CreateTime: &ct,
|
||||
UpdateTime: &ut,
|
||||
@@ -110,9 +147,14 @@ func (c *PlatformTenantController) GetTenant() {
|
||||
return
|
||||
}
|
||||
|
||||
packageNames := tenantPackageNameMap()
|
||||
userCounts := tenantUserCountMap()
|
||||
list := make([]tenantDTO, 0, len(rows))
|
||||
for _, t := range rows {
|
||||
list = append(list, toTenantDTO(t))
|
||||
dto := toTenantDTO(t)
|
||||
dto.PackageName = packageNames[t.PackageID]
|
||||
dto.UserUsed = userCounts[t.ID]
|
||||
list = append(list, dto)
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
@@ -144,10 +186,19 @@ func (c *PlatformTenantController) GetTenantDetail() {
|
||||
return
|
||||
}
|
||||
|
||||
dto := toTenantDTO(t)
|
||||
if pkg, err := services.GetTenantPackage(t.ID); err == nil && pkg != nil {
|
||||
dto.PackageID = pkg.ID
|
||||
dto.PackageName = pkg.Name
|
||||
}
|
||||
if used, err := services.CountTenantUsers(t.ID); err == nil {
|
||||
dto.UserUsed = used
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": toTenantDTO(t),
|
||||
"data": dto,
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
@@ -161,7 +212,10 @@ type tenantPayload struct {
|
||||
Address string `json:"address"`
|
||||
Worktime string `json:"worktime"`
|
||||
Status *int8 `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
// 套餐与用户数上限(可选)
|
||||
PackageID *uint64 `json:"package_id"`
|
||||
UserQuota *int `json:"user_quota"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func (c *PlatformTenantController) parseTenantPayload() (tenantPayload, error) {
|
||||
@@ -182,6 +236,16 @@ func (c *PlatformTenantController) parseTenantPayload() (tenantPayload, error) {
|
||||
p.Status = &tmp
|
||||
}
|
||||
}
|
||||
if s := strings.TrimSpace(c.GetString("package_id")); s != "" {
|
||||
if v, err := strconv.ParseUint(s, 10, 64); err == nil {
|
||||
p.PackageID = &v
|
||||
}
|
||||
}
|
||||
if s := strings.TrimSpace(c.GetString("user_quota")); s != "" {
|
||||
if v, err := strconv.Atoi(s); err == nil {
|
||||
p.UserQuota = &v
|
||||
}
|
||||
}
|
||||
|
||||
// ĺŚćĺ
łéŽĺ掾为犺ďźĺ°čŻäť JSON body č§ŁćďźeditTenant éťčޤ JSONďź?
|
||||
if p.TenantName == "" && p.TenantCode == "" {
|
||||
@@ -226,6 +290,36 @@ func (c *PlatformTenantController) CreateTenant() {
|
||||
status = *p.Status
|
||||
}
|
||||
|
||||
// 套餐与用户数上限:未指定套餐时使用默认套餐,用户数取其包含用户数(兜底 20)
|
||||
packageID := uint64(0)
|
||||
userQuota := services.DefaultTenantUserQuota
|
||||
if p.PackageID != nil && *p.PackageID > 0 {
|
||||
packageID = *p.PackageID
|
||||
}
|
||||
if packageID == 0 {
|
||||
if pkg, err := services.GetDefaultTenantPackage(); err == nil && pkg != nil {
|
||||
packageID = pkg.ID
|
||||
if pkg.UserQuota > 0 {
|
||||
userQuota = pkg.UserQuota
|
||||
}
|
||||
}
|
||||
}
|
||||
if packageID > 0 {
|
||||
var pkg models.SystemTenantPackage
|
||||
if err := models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("id", packageID).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&pkg); err == nil && pkg.UserQuota > 0 {
|
||||
userQuota = pkg.UserQuota
|
||||
}
|
||||
}
|
||||
if p.UserQuota != nil && *p.UserQuota > 0 {
|
||||
userQuota = *p.UserQuota
|
||||
}
|
||||
if userQuota <= 0 {
|
||||
userQuota = services.DefaultTenantUserQuota
|
||||
}
|
||||
|
||||
t := models.SystemTenant{
|
||||
TenantCode: p.TenantCode,
|
||||
TenantName: p.TenantName,
|
||||
@@ -235,6 +329,8 @@ func (c *PlatformTenantController) CreateTenant() {
|
||||
Address: stringPtr(p.Address),
|
||||
Worktime: stringPtr(p.Worktime),
|
||||
Status: status,
|
||||
PackageID: packageID,
|
||||
UserQuota: userQuota,
|
||||
Remark: stringPtr(p.Remark),
|
||||
}
|
||||
|
||||
@@ -282,6 +378,37 @@ func (c *PlatformTenantController) EditTenant() {
|
||||
if p.Status != nil {
|
||||
update["status"] = *p.Status
|
||||
}
|
||||
if p.PackageID != nil {
|
||||
pkgID := *p.PackageID
|
||||
if pkgID > 0 {
|
||||
var pkg models.SystemTenantPackage
|
||||
if err := models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("id", pkgID).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&pkg); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "套餐不存在"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
}
|
||||
update["package_id"] = pkgID
|
||||
}
|
||||
if p.UserQuota != nil {
|
||||
// 用户数上限只增不减:不允许调低到当前已开用户数以下
|
||||
used, _ := services.CountTenantUsers(id)
|
||||
quota := *p.UserQuota
|
||||
if quota > 0 && quota < used {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 400,
|
||||
"msg": fmt.Sprintf("用户数上限不能小于该租户已开用户数(%d)", used),
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if quota > 0 {
|
||||
update["user_quota"] = quota
|
||||
}
|
||||
}
|
||||
|
||||
_, err = models.Orm.QueryTable(new(models.SystemTenant)).Filter("id", id).Update(update)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,837 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
"server/services"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// PlatformTenantPackageController 平台端「租户套餐」管理
|
||||
//
|
||||
// 两块能力:
|
||||
// 1. 功能套餐(yz_system_tenant_package + yz_system_tenant_package_module):
|
||||
// 勾选套餐包含的功能模块(erp / oa / crm / cms ...),租户绑定后按其开通功能。
|
||||
// 2. 用户数配额(yz_system_tenant_user_quota_package + yz_system_tenant_quota_order):
|
||||
// 每个租户有用户数上限(默认 20),支持按单个用户或按加购套餐增购。
|
||||
type PlatformTenantPackageController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (c *PlatformTenantPackageController) jsonErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *PlatformTenantPackageController) jsonOK(data interface{}, msg string) {
|
||||
if msg == "" {
|
||||
msg = "success"
|
||||
}
|
||||
resp := map[string]interface{}{"code": 200, "msg": msg}
|
||||
if data != nil {
|
||||
resp["data"] = data
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// currentOperator 解析当前平台账号名(用于增购记录留痕;取不到时返回空串,不阻断请求)
|
||||
func (c *PlatformTenantPackageController) currentOperator() string {
|
||||
auth := c.Ctx.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return ""
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil || claims == nil {
|
||||
return ""
|
||||
}
|
||||
if strings.TrimSpace(claims.Username) != "" {
|
||||
return claims.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *PlatformTenantPackageController) readBody(target interface{}) bool {
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return false
|
||||
}
|
||||
if err := json.Unmarshal(raw, target); err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func parseUint64Param(raw string) uint64 {
|
||||
v, _ := strconv.ParseUint(strings.TrimSpace(raw), 10, 64)
|
||||
return v
|
||||
}
|
||||
|
||||
// ============================ 功能套餐 ============================
|
||||
|
||||
type tenantPackageModuleItem struct {
|
||||
ModuleCode string `json:"module_code"`
|
||||
ModuleName string `json:"module_name"`
|
||||
}
|
||||
|
||||
type tenantPackageDTO struct {
|
||||
models.SystemTenantPackage
|
||||
Modules []tenantPackageModuleItem `json:"modules"`
|
||||
}
|
||||
|
||||
type tenantPackagePayload struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description"`
|
||||
Price *float64 `json:"price"`
|
||||
UserQuota *int `json:"user_quota"`
|
||||
ExtraUserPrice *float64 `json:"extra_user_price"`
|
||||
IsDefault *int8 `json:"is_default"`
|
||||
Sort *int `json:"sort"`
|
||||
Status *int8 `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
Modules []string `json:"modules"`
|
||||
}
|
||||
|
||||
// loadPackageModules 批量加载套餐模块(packageID -> 模块列表)
|
||||
func loadPackageModules(packageIDs []uint64) map[uint64][]tenantPackageModuleItem {
|
||||
result := map[uint64][]tenantPackageModuleItem{}
|
||||
if len(packageIDs) == 0 {
|
||||
return result
|
||||
}
|
||||
var rows []models.SystemTenantPackageModule
|
||||
_, err := models.Orm.QueryTable(new(models.SystemTenantPackageModule)).
|
||||
Filter("package_id__in", packageIDs).
|
||||
OrderBy("id").
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
for _, r := range rows {
|
||||
result[r.PackageID] = append(result[r.PackageID], tenantPackageModuleItem{
|
||||
ModuleCode: r.ModuleCode,
|
||||
ModuleName: r.ModuleName,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetPackageList GET /platform/tenantPackage/list
|
||||
func (c *PlatformTenantPackageController) GetPackageList() {
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
status := strings.TrimSpace(c.GetString("status"))
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.SystemTenantPackage)).Filter("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
qs = qs.Filter("name__icontains", keyword)
|
||||
}
|
||||
if status == "0" || status == "1" {
|
||||
v, _ := strconv.Atoi(status)
|
||||
qs = qs.Filter("status", v)
|
||||
}
|
||||
|
||||
var rows []models.SystemTenantPackage
|
||||
if _, err := qs.OrderBy("sort", "id").All(&rows); err != nil {
|
||||
c.jsonErr(500, 500, "获取失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]uint64, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
ids = append(ids, r.ID)
|
||||
}
|
||||
moduleMap := loadPackageModules(ids)
|
||||
|
||||
// 每个套餐的租户数
|
||||
tenantCount := map[uint64]int{}
|
||||
if len(ids) > 0 {
|
||||
var tenants []models.SystemTenant
|
||||
if _, err := models.Orm.QueryTable(new(models.SystemTenant)).All(&tenants, "ID", "PackageID"); err == nil {
|
||||
for _, t := range tenants {
|
||||
tenantCount[t.PackageID]++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
modules := moduleMap[r.ID]
|
||||
if modules == nil {
|
||||
modules = []tenantPackageModuleItem{}
|
||||
}
|
||||
list = append(list, map[string]interface{}{
|
||||
"id": r.ID,
|
||||
"name": r.Name,
|
||||
"code": r.Code,
|
||||
"description": r.Description,
|
||||
"price": r.Price,
|
||||
"user_quota": r.UserQuota,
|
||||
"extra_user_price": r.ExtraUserPrice,
|
||||
"is_default": r.IsDefault,
|
||||
"sort": r.Sort,
|
||||
"status": r.Status,
|
||||
"remark": r.Remark,
|
||||
"create_time": r.CreateTime,
|
||||
"update_time": r.UpdateTime,
|
||||
"modules": modules,
|
||||
"module_codes": moduleCodes(modules),
|
||||
"tenant_count": tenantCount[r.ID],
|
||||
})
|
||||
}
|
||||
|
||||
c.jsonOK(map[string]interface{}{"list": list, "total": len(list)}, "获取成功")
|
||||
}
|
||||
|
||||
func moduleCodes(items []tenantPackageModuleItem) []string {
|
||||
codes := make([]string, 0, len(items))
|
||||
for _, it := range items {
|
||||
codes = append(codes, it.ModuleCode)
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
// GetPackageSelectList GET /platform/tenantPackage/select/list
|
||||
func (c *PlatformTenantPackageController) GetPackageSelectList() {
|
||||
var rows []models.SystemTenantPackage
|
||||
_, err := models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("status", 1).
|
||||
OrderBy("sort", "id").
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "获取失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
ids := make([]uint64, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
ids = append(ids, r.ID)
|
||||
}
|
||||
moduleMap := loadPackageModules(ids)
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
modules := moduleMap[r.ID]
|
||||
if modules == nil {
|
||||
modules = []tenantPackageModuleItem{}
|
||||
}
|
||||
list = append(list, map[string]interface{}{
|
||||
"id": r.ID,
|
||||
"name": r.Name,
|
||||
"code": r.Code,
|
||||
"description": r.Description,
|
||||
"price": r.Price,
|
||||
"user_quota": r.UserQuota,
|
||||
"extra_user_price": r.ExtraUserPrice,
|
||||
"is_default": r.IsDefault,
|
||||
"modules": modules,
|
||||
"module_codes": moduleCodes(modules),
|
||||
})
|
||||
}
|
||||
c.jsonOK(list, "获取成功")
|
||||
}
|
||||
|
||||
// GetPackageDetail GET /platform/tenantPackage/detail/:id
|
||||
func (c *PlatformTenantPackageController) GetPackageDetail() {
|
||||
id := parseUint64Param(c.Ctx.Input.Param(":id"))
|
||||
if id == 0 {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var row models.SystemTenantPackage
|
||||
if err := models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row); err != nil {
|
||||
c.jsonErr(404, 404, "套餐不存在")
|
||||
return
|
||||
}
|
||||
modules := loadPackageModules([]uint64{id})[id]
|
||||
if modules == nil {
|
||||
modules = []tenantPackageModuleItem{}
|
||||
}
|
||||
c.jsonOK(tenantPackageDTO{SystemTenantPackage: row, Modules: modules}, "获取成功")
|
||||
}
|
||||
|
||||
// GetModuleOptions GET /platform/tenantPackage/moduleOptions
|
||||
// 可加入套餐的功能模块清单(取 yz_system_modules 中启用且未删除的模块)
|
||||
func (c *PlatformTenantPackageController) GetModuleOptions() {
|
||||
var rows []models.SystemModules
|
||||
_, err := models.Orm.QueryTable(new(models.SystemModules)).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("status", 1).
|
||||
OrderBy("sort", "id").
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "获取失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, map[string]interface{}{
|
||||
"id": r.ID,
|
||||
"name": r.Name,
|
||||
"code": r.Code,
|
||||
"path": r.Path,
|
||||
"icon": r.Icon,
|
||||
"description": r.Description,
|
||||
"type": r.Type,
|
||||
"sort": r.Sort,
|
||||
})
|
||||
}
|
||||
c.jsonOK(map[string]interface{}{"list": list, "total": len(list)}, "获取成功")
|
||||
}
|
||||
|
||||
// savePackageModules 覆盖式保存套餐模块
|
||||
func savePackageModules(packageID uint64, modules []string) error {
|
||||
if _, err := models.Orm.QueryTable(new(models.SystemTenantPackageModule)).
|
||||
Filter("package_id", packageID).
|
||||
Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(modules) == 0 {
|
||||
return nil
|
||||
}
|
||||
nameMap := map[string]string{}
|
||||
var allModules []models.SystemModules
|
||||
if _, err := models.Orm.QueryTable(new(models.SystemModules)).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&allModules, "ID", "Name", "Code"); err == nil {
|
||||
for _, m := range allModules {
|
||||
nameMap[m.Code] = m.Name
|
||||
}
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, code := range modules {
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" || seen[code] {
|
||||
continue
|
||||
}
|
||||
seen[code] = true
|
||||
row := &models.SystemTenantPackageModule{
|
||||
PackageID: packageID,
|
||||
ModuleCode: code,
|
||||
ModuleName: nameMap[code],
|
||||
}
|
||||
if _, err := models.Orm.Insert(row); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePackage POST /platform/tenantPackage/create
|
||||
func (c *PlatformTenantPackageController) CreatePackage() {
|
||||
var p tenantPackagePayload
|
||||
if !c.readBody(&p) {
|
||||
return
|
||||
}
|
||||
p.Name = strings.TrimSpace(p.Name)
|
||||
p.Code = strings.TrimSpace(p.Code)
|
||||
if p.Name == "" {
|
||||
c.jsonErr(400, 400, "套餐名称不能为空")
|
||||
return
|
||||
}
|
||||
if p.Code == "" {
|
||||
c.jsonErr(400, 400, "套餐编码不能为空")
|
||||
return
|
||||
}
|
||||
cnt, _ := models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("code", p.Code).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if cnt > 0 {
|
||||
c.jsonErr(400, 400, "套餐编码已存在")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
row := &models.SystemTenantPackage{
|
||||
Name: p.Name,
|
||||
Code: p.Code,
|
||||
Description: strings.TrimSpace(p.Description),
|
||||
Price: valueFloat(p.Price, 0),
|
||||
UserQuota: valueInt(p.UserQuota, services.DefaultTenantUserQuota),
|
||||
ExtraUserPrice: valueFloat(p.ExtraUserPrice, services.DefaultTenantExtraUserPrice),
|
||||
IsDefault: valueInt8(p.IsDefault, 0),
|
||||
Sort: valueInt(p.Sort, 0),
|
||||
Status: valueInt8(p.Status, 1),
|
||||
Remark: strings.TrimSpace(p.Remark),
|
||||
UpdateTime: &now,
|
||||
}
|
||||
if row.UserQuota <= 0 {
|
||||
row.UserQuota = services.DefaultTenantUserQuota
|
||||
}
|
||||
id, err := models.Orm.Insert(row)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "创建失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
if err := savePackageModules(uint64(id), p.Modules); err != nil {
|
||||
c.jsonErr(500, 500, "套餐功能保存失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
if row.IsDefault == 1 {
|
||||
clearOtherDefaultPackages(uint64(id))
|
||||
}
|
||||
c.jsonOK(map[string]interface{}{"id": uint64(id)}, "创建成功")
|
||||
}
|
||||
|
||||
// EditPackage POST /platform/tenantPackage/edit/:id
|
||||
func (c *PlatformTenantPackageController) EditPackage() {
|
||||
id := parseUint64Param(c.Ctx.Input.Param(":id"))
|
||||
if id == 0 {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p tenantPackagePayload
|
||||
if !c.readBody(&p) {
|
||||
return
|
||||
}
|
||||
p.Name = strings.TrimSpace(p.Name)
|
||||
p.Code = strings.TrimSpace(p.Code)
|
||||
if p.Name == "" {
|
||||
c.jsonErr(400, 400, "套餐名称不能为空")
|
||||
return
|
||||
}
|
||||
if p.Code != "" {
|
||||
cnt, _ := models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("code", p.Code).
|
||||
Filter("id__ne", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if cnt > 0 {
|
||||
c.jsonErr(400, 400, "套餐编码已存在")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
update := map[string]interface{}{
|
||||
"name": p.Name,
|
||||
"description": strings.TrimSpace(p.Description),
|
||||
"extra_user_price": valueFloat(p.ExtraUserPrice, services.DefaultTenantExtraUserPrice),
|
||||
"sort": valueInt(p.Sort, 0),
|
||||
"status": valueInt8(p.Status, 1),
|
||||
"remark": strings.TrimSpace(p.Remark),
|
||||
"update_time": now,
|
||||
}
|
||||
if p.Code != "" {
|
||||
update["code"] = p.Code
|
||||
}
|
||||
if p.Price != nil {
|
||||
update["price"] = *p.Price
|
||||
}
|
||||
if p.UserQuota != nil {
|
||||
quota := *p.UserQuota
|
||||
if quota <= 0 {
|
||||
quota = services.DefaultTenantUserQuota
|
||||
}
|
||||
update["user_quota"] = quota
|
||||
}
|
||||
if p.IsDefault != nil {
|
||||
update["is_default"] = *p.IsDefault
|
||||
update["status"] = 1
|
||||
}
|
||||
|
||||
if _, err := models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(update); err != nil {
|
||||
c.jsonErr(500, 500, "编辑失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
if p.Modules != nil {
|
||||
if err := savePackageModules(id, p.Modules); err != nil {
|
||||
c.jsonErr(500, 500, "套餐功能保存失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.IsDefault != nil && *p.IsDefault == 1 {
|
||||
clearOtherDefaultPackages(id)
|
||||
}
|
||||
c.jsonOK(nil, "编辑成功")
|
||||
}
|
||||
|
||||
// clearOtherDefaultPackages 保证全局只有一个默认套餐
|
||||
func clearOtherDefaultPackages(keepID uint64) {
|
||||
_, _ = models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("id__ne", keepID).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"is_default": 0})
|
||||
}
|
||||
|
||||
// DeletePackage DELETE /platform/tenantPackage/delete/:id(软删)
|
||||
func (c *PlatformTenantPackageController) DeletePackage() {
|
||||
id := parseUint64Param(c.Ctx.Input.Param(":id"))
|
||||
if id == 0 {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
// 有租户正在使用该套餐时不允许删除
|
||||
used, _ := models.Orm.QueryTable(new(models.SystemTenant)).Filter("package_id", id).Count()
|
||||
if used > 0 {
|
||||
c.jsonErr(400, 400, fmt.Sprintf("该套餐已被 %d 个租户使用,请先调整租户套餐", used))
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.SystemTenantPackage)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now, "update_time": now, "is_default": 0})
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "删除失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.jsonErr(404, 404, "套餐不存在")
|
||||
return
|
||||
}
|
||||
_, _ = models.Orm.QueryTable(new(models.SystemTenantPackageModule)).Filter("package_id", id).Delete()
|
||||
c.jsonOK(nil, "删除成功")
|
||||
}
|
||||
|
||||
func valueFloat(v *float64, def float64) float64 {
|
||||
if v == nil {
|
||||
return def
|
||||
}
|
||||
return *v
|
||||
}
|
||||
|
||||
func valueInt(v *int, def int) int {
|
||||
if v == nil {
|
||||
return def
|
||||
}
|
||||
return *v
|
||||
}
|
||||
|
||||
// ============================ 用户数加购套餐 ============================
|
||||
|
||||
type quotaPackagePayload struct {
|
||||
Name string `json:"name"`
|
||||
UserCount *int `json:"user_count"`
|
||||
Price *float64 `json:"price"`
|
||||
Sort *int `json:"sort"`
|
||||
Status *int8 `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// GetQuotaPackageList GET /platform/tenantQuotaPackage/list
|
||||
func (c *PlatformTenantPackageController) GetQuotaPackageList() {
|
||||
onlyEnabled := c.GetString("enabled") == "1"
|
||||
qs := models.Orm.QueryTable(new(models.SystemTenantUserQuotaPackage)).Filter("delete_time__isnull", true)
|
||||
if onlyEnabled {
|
||||
qs = qs.Filter("status", 1)
|
||||
}
|
||||
var rows []models.SystemTenantUserQuotaPackage
|
||||
if _, err := qs.OrderBy("sort", "id").All(&rows); err != nil {
|
||||
c.jsonErr(500, 500, "获取失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
c.jsonOK(map[string]interface{}{"list": rows, "total": len(rows)}, "获取成功")
|
||||
}
|
||||
|
||||
// CreateQuotaPackage POST /platform/tenantQuotaPackage/create
|
||||
func (c *PlatformTenantPackageController) CreateQuotaPackage() {
|
||||
var p quotaPackagePayload
|
||||
if !c.readBody(&p) {
|
||||
return
|
||||
}
|
||||
p.Name = strings.TrimSpace(p.Name)
|
||||
if p.Name == "" {
|
||||
c.jsonErr(400, 400, "规格名称不能为空")
|
||||
return
|
||||
}
|
||||
if p.UserCount == nil || *p.UserCount <= 0 {
|
||||
c.jsonErr(400, 400, "包含用户数必须大于 0")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
row := &models.SystemTenantUserQuotaPackage{
|
||||
Name: p.Name,
|
||||
UserCount: *p.UserCount,
|
||||
Price: valueFloat(p.Price, 0),
|
||||
Sort: valueInt(p.Sort, 0),
|
||||
Status: valueInt8(p.Status, 1),
|
||||
Remark: strings.TrimSpace(p.Remark),
|
||||
UpdateTime: &now,
|
||||
}
|
||||
id, err := models.Orm.Insert(row)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "创建失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
c.jsonOK(map[string]interface{}{"id": uint64(id)}, "创建成功")
|
||||
}
|
||||
|
||||
// EditQuotaPackage POST /platform/tenantQuotaPackage/edit/:id
|
||||
func (c *PlatformTenantPackageController) EditQuotaPackage() {
|
||||
id := parseUint64Param(c.Ctx.Input.Param(":id"))
|
||||
if id == 0 {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p quotaPackagePayload
|
||||
if !c.readBody(&p) {
|
||||
return
|
||||
}
|
||||
p.Name = strings.TrimSpace(p.Name)
|
||||
if p.Name == "" {
|
||||
c.jsonErr(400, 400, "规格名称不能为空")
|
||||
return
|
||||
}
|
||||
if p.UserCount != nil && *p.UserCount <= 0 {
|
||||
c.jsonErr(400, 400, "包含用户数必须大于 0")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
update := map[string]interface{}{
|
||||
"name": p.Name,
|
||||
"sort": valueInt(p.Sort, 0),
|
||||
"status": valueInt8(p.Status, 1),
|
||||
"remark": strings.TrimSpace(p.Remark),
|
||||
"update_time": now,
|
||||
}
|
||||
if p.UserCount != nil {
|
||||
update["user_count"] = *p.UserCount
|
||||
}
|
||||
if p.Price != nil {
|
||||
update["price"] = *p.Price
|
||||
}
|
||||
n, err := models.Orm.QueryTable(new(models.SystemTenantUserQuotaPackage)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(update)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "编辑失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.jsonErr(404, 404, "规格不存在")
|
||||
return
|
||||
}
|
||||
c.jsonOK(nil, "编辑成功")
|
||||
}
|
||||
|
||||
// DeleteQuotaPackage DELETE /platform/tenantQuotaPackage/delete/:id(软删)
|
||||
func (c *PlatformTenantPackageController) DeleteQuotaPackage() {
|
||||
id := parseUint64Param(c.Ctx.Input.Param(":id"))
|
||||
if id == 0 {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.SystemTenantUserQuotaPackage)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "删除失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.jsonErr(404, 404, "规格不存在")
|
||||
return
|
||||
}
|
||||
c.jsonOK(nil, "删除成功")
|
||||
}
|
||||
|
||||
// ============================ 租户套餐 / 用户数配额 ============================
|
||||
|
||||
type setTenantPackagePayload struct {
|
||||
Tid uint64 `json:"tid"`
|
||||
PackageID uint64 `json:"package_id"`
|
||||
SyncQuota *bool `json:"sync_quota"`
|
||||
}
|
||||
|
||||
// SetTenantPackage POST /platform/tenant/setPackage
|
||||
// 为租户绑定套餐;sync_quota 为 true(默认)时按套餐包含用户数与现有上限取较大值同步。
|
||||
func (c *PlatformTenantPackageController) SetTenantPackage() {
|
||||
var p setTenantPackagePayload
|
||||
if !c.readBody(&p) {
|
||||
return
|
||||
}
|
||||
if p.Tid == 0 || p.PackageID == 0 {
|
||||
c.jsonErr(400, 400, "租户与套餐不能为空")
|
||||
return
|
||||
}
|
||||
syncQuota := true
|
||||
if p.SyncQuota != nil {
|
||||
syncQuota = *p.SyncQuota
|
||||
}
|
||||
newQuota, err := services.ApplyPackageToTenant(p.Tid, p.PackageID, syncQuota)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, err.Error())
|
||||
return
|
||||
}
|
||||
c.jsonOK(map[string]interface{}{"user_quota": newQuota}, "套餐设置成功")
|
||||
}
|
||||
|
||||
// GetTenantQuotaInfo GET /platform/tenant/quotaInfo?tid=1
|
||||
// 返回租户套餐、用户数使用情况、可增购规格与单价
|
||||
func (c *PlatformTenantPackageController) GetTenantQuotaInfo() {
|
||||
tid, _ := c.GetUint64("tid")
|
||||
if tid == 0 {
|
||||
c.jsonErr(400, 400, "tid 不能为空")
|
||||
return
|
||||
}
|
||||
info, err := services.GetTenantQuotaInfo(tid)
|
||||
if err != nil {
|
||||
c.jsonErr(404, 404, "租户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var quotaPackages []models.SystemTenantUserQuotaPackage
|
||||
_, _ = models.Orm.QueryTable(new(models.SystemTenantUserQuotaPackage)).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("status", 1).
|
||||
OrderBy("sort", "id").
|
||||
All("aPackages)
|
||||
|
||||
modules, _ := services.GetTenantPackageModules(tid)
|
||||
moduleList := make([]tenantPackageModuleItem, 0, len(modules))
|
||||
for _, m := range modules {
|
||||
moduleList = append(moduleList, tenantPackageModuleItem{ModuleCode: m.ModuleCode, ModuleName: m.ModuleName})
|
||||
}
|
||||
|
||||
c.jsonOK(map[string]interface{}{
|
||||
"tid": info.Tid,
|
||||
"package_id": info.PackageID,
|
||||
"effective_package_id": info.EffectivePackageID,
|
||||
"package_name": info.PackageName,
|
||||
"modules": moduleList,
|
||||
"quota": info.Quota,
|
||||
"used": info.Used,
|
||||
"remaining": info.Remaining,
|
||||
"extra_user_price": info.ExtraUserPrice,
|
||||
"quota_packages": quotaPackages,
|
||||
}, "获取成功")
|
||||
}
|
||||
|
||||
type rechargeQuotaPayload struct {
|
||||
Tid uint64 `json:"tid"`
|
||||
Type int8 `json:"type"` // 1 单个增购 2 套餐增购
|
||||
QuotaPackageID uint64 `json:"quota_package_id"`
|
||||
UserCount int `json:"user_count"`
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// RechargeTenantQuota POST /platform/tenant/rechargeQuota
|
||||
// 用户数增购:单个增购(单价 x 人数)或 按加购套餐(如 5人/500元)
|
||||
func (c *PlatformTenantPackageController) RechargeTenantQuota() {
|
||||
var p rechargeQuotaPayload
|
||||
if !c.readBody(&p) {
|
||||
return
|
||||
}
|
||||
if p.Tid == 0 {
|
||||
c.jsonErr(400, 400, "租户不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
order := models.SystemTenantQuotaOrder{
|
||||
Tid: p.Tid,
|
||||
Type: p.Type,
|
||||
Operator: c.currentOperator(),
|
||||
Remark: strings.TrimSpace(p.Remark),
|
||||
}
|
||||
if order.Type != 2 {
|
||||
order.Type = 1
|
||||
}
|
||||
|
||||
if order.Type == 2 {
|
||||
var pkg models.SystemTenantUserQuotaPackage
|
||||
if err := models.Orm.QueryTable(new(models.SystemTenantUserQuotaPackage)).
|
||||
Filter("id", p.QuotaPackageID).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&pkg); err != nil {
|
||||
c.jsonErr(400, 400, "加购规格不存在")
|
||||
return
|
||||
}
|
||||
if pkg.UserCount <= 0 {
|
||||
c.jsonErr(400, 400, "加购规格用户数配置有误")
|
||||
return
|
||||
}
|
||||
order.QuotaPackageID = pkg.ID
|
||||
order.PackageName = pkg.Name
|
||||
order.UserCount = pkg.UserCount
|
||||
order.Amount = pkg.Price
|
||||
order.UnitPrice = round2(pkg.Price / float64(pkg.UserCount))
|
||||
} else {
|
||||
if p.UserCount <= 0 {
|
||||
c.jsonErr(400, 400, "增购用户数必须大于 0")
|
||||
return
|
||||
}
|
||||
unitPrice := p.UnitPrice
|
||||
if unitPrice <= 0 {
|
||||
// 未传单价时取租户套餐的单个用户增购价
|
||||
if info, err := services.GetTenantQuotaInfo(p.Tid); err == nil && info.ExtraUserPrice > 0 {
|
||||
unitPrice = info.ExtraUserPrice
|
||||
} else {
|
||||
unitPrice = services.DefaultTenantExtraUserPrice
|
||||
}
|
||||
}
|
||||
order.UserCount = p.UserCount
|
||||
order.UnitPrice = unitPrice
|
||||
order.Amount = round2(unitPrice * float64(p.UserCount))
|
||||
order.PackageName = "单个增购"
|
||||
}
|
||||
|
||||
before, after, err := services.IncreaseTenantQuota(p.Tid, order.UserCount)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, err.Error())
|
||||
return
|
||||
}
|
||||
order.QuotaBefore = before
|
||||
order.QuotaAfter = after
|
||||
|
||||
if _, err := models.Orm.Insert(&order); err != nil {
|
||||
c.jsonErr(500, 500, "增购记录写入失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if info, err := services.GetTenantQuotaInfo(p.Tid); err == nil {
|
||||
c.jsonOK(map[string]interface{}{
|
||||
"quota": info.Quota,
|
||||
"used": info.Used,
|
||||
"order": order,
|
||||
"amount": order.Amount,
|
||||
"add": order.UserCount,
|
||||
}, "增购成功")
|
||||
return
|
||||
}
|
||||
c.jsonOK(map[string]interface{}{"quota": after, "add": order.UserCount, "amount": order.Amount}, "增购成功")
|
||||
}
|
||||
|
||||
// GetQuotaOrders GET /platform/tenant/quotaOrders?tid=1
|
||||
func (c *PlatformTenantPackageController) GetQuotaOrders() {
|
||||
tid, _ := c.GetUint64("tid")
|
||||
if tid == 0 {
|
||||
c.jsonErr(400, 400, "tid 不能为空")
|
||||
return
|
||||
}
|
||||
var rows []models.SystemTenantQuotaOrder
|
||||
_, err := models.Orm.QueryTable(new(models.SystemTenantQuotaOrder)).
|
||||
Filter("tid", tid).
|
||||
OrderBy("-id").
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "获取失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
c.jsonOK(map[string]interface{}{"list": rows, "total": len(rows)}, "获取成功")
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
@@ -186,6 +187,20 @@ func (c *PlatformTenantUserController) CreateTenantUser() {
|
||||
p.Uid = uid
|
||||
}
|
||||
|
||||
// 用户数配额校验:租户用户数达到上限时不允许新增(增购用户数后放开)
|
||||
if check, err := services.CheckTenantUserQuota(p.Tid, p.Uid); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "用户数校验失败: " + err.Error()}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
} else if !check.Allowed {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 400,
|
||||
"msg": fmt.Sprintf("该租户用户数已达上限(%d/%d),请先为租户增购用户数", check.Used, check.Quota),
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
isDefault := int8(0)
|
||||
status := int8(1)
|
||||
if p.IsDefault != nil {
|
||||
|
||||
Reference in New Issue
Block a user