Files
iotlcm/admin-svr/server/internal/logic/iot/simcardquery/dept.go
T

153 lines
4.8 KiB
Go

package simcardquery
import (
"context"
"fmt"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
)
// DeptOptions 所属公司下拉项(树形)。
//
// 两点关键口径:
// 1. 可见范围交给 handler.FilterAuth:顶级看全部,其他角色按自身数据范围。
// 2. CardCount 是「含下级」的累计量,不是本部门直接挂的卡。
// 原因:顶级公司(如示例企业)自身往往不挂卡,卡都在下级代理名下,
// 只统计本部门会显示 0,看起来像没数据。
func (s *Service) DeptOptions(ctx context.Context) ([]*model.SimCardQueryDeptOption, error) {
deptColumns := dao.SysDept.Columns()
depts := make([]*struct {
DeptId int64 `orm:"dept_id"`
DeptName string `orm:"dept_name"`
ParentId int64 `orm:"parent_id"`
}, 0, 256)
err := handler.Model(dao.SysDept.Ctx(ctx), &handler.Option{
FilterTenant: true,
FilterAuth: true,
}).
Fields(deptColumns.DeptId, deptColumns.DeptName, deptColumns.ParentId).
Where(deptColumns.Status, 0).
WhereNull(deptColumns.DeletedAt).
OrderAsc(deptColumns.OrderNum).
OrderAsc(deptColumns.DeptId).
Scan(&depts)
if err != nil {
return nil, gerror.Wrap(err, "获取可选公司失败,请稍后重试")
}
if len(depts) == 0 {
return []*model.SimCardQueryDeptOption{}, nil
}
direct, err := directCardCountByDept(ctx)
if err != nil {
return nil, err
}
subtree := accumulateSubtreeCount(depts, direct)
options := make([]*model.SimCardQueryDeptOption, 0, len(depts))
for _, dept := range depts {
options = append(options, &model.SimCardQueryDeptOption{
DeptId: dept.DeptId,
DeptName: dept.DeptName,
CardCount: subtree[dept.DeptId],
ParentId: dept.ParentId,
})
}
return options, nil
}
// directCardCountByDept 按部门统计直接挂载的卡数量。
// 卡表同样过数据权限,防止公司列表被收窄、但计数把无权卡的量带出来。
func directCardCountByDept(ctx context.Context) (map[int64]int64, error) {
cardColumns := dao.TSimCardInfo.Columns()
rows := make([]*struct {
DeptId int64 `orm:"dept_id"`
Total int64 `orm:"total"`
}, 0, 256)
err := handler.Model(dao.TSimCardInfo.Ctx(ctx), &handler.Option{
FilterTenant: true,
FilterAuth: true,
}).
Fields(fmt.Sprintf("%s AS dept_id, COUNT(1) AS total", cardColumns.DeptId)).
Group(cardColumns.DeptId).
Scan(&rows)
if err != nil {
return nil, gerror.Wrap(err, "统计公司卡数量失败")
}
result := make(map[int64]int64, len(rows))
for _, row := range rows {
result[row.DeptId] = row.Total
}
return result, nil
}
// accumulateSubtreeCount 把「本部门直接卡数」沿部门树向上累加,得到含下级的卡数。
//
// 只累加本次查询可见的部门:无权部门根本不在 depts 里,其卡数也不会被计入,
// 因此不会通过父级汇总把无权数据带出来。
func accumulateSubtreeCount(
depts []*struct {
DeptId int64 `orm:"dept_id"`
DeptName string `orm:"dept_name"`
ParentId int64 `orm:"parent_id"`
},
direct map[int64]int64,
) map[int64]int64 {
visible := make(map[int64]struct{}, len(depts))
children := make(map[int64][]int64, len(depts))
roots := make([]int64, 0, len(depts))
for _, dept := range depts {
visible[dept.DeptId] = struct{}{}
}
for _, dept := range depts {
if _, ok := visible[dept.ParentId]; ok && dept.ParentId != dept.DeptId {
children[dept.ParentId] = append(children[dept.ParentId], dept.DeptId)
continue
}
roots = append(roots, dept.DeptId)
}
subtree := make(map[int64]int64, len(depts))
// 部门树很浅,递归深度可控;用显式栈避免深层树爆栈。
type frame struct {
id int64
child int
amount int64
}
for _, rootID := range roots {
stack := []*frame{{id: rootID, amount: direct[rootID]}}
for len(stack) > 0 {
top := stack[len(stack)-1]
if kids := children[top.id]; top.child < len(kids) {
childID := kids[top.child]
top.child++
stack = append(stack, &frame{id: childID, amount: direct[childID]})
continue
}
subtree[top.id] = top.amount
stack = stack[:len(stack)-1]
if len(stack) > 0 {
stack[len(stack)-1].amount += top.amount
}
}
}
return subtree
}
// deptSubtreeIDs 返回某部门及其全部下级的 dept_id 子查询。
// 与 DeptOptions 的统计口径保持一致:选中公司即包含下级公司。
func deptSubtreeIDs(ctx context.Context, deptId int64) *gdb.Model {
deptColumns := dao.SysDept.Columns()
return handler.Model(dao.SysDept.Ctx(ctx), &handler.Option{
FilterTenant: true,
}).Fields(deptColumns.DeptId).
Where(fmt.Sprintf("%s = ? OR %s LIKE ?", deptColumns.DeptId, deptColumns.Ancestors),
deptId, fmt.Sprintf("%%,%d,%%", deptId))
}