153 lines
4.8 KiB
Go
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))
|
|
}
|