251 lines
6.3 KiB
Go
251 lines
6.3 KiB
Go
package dailyreport
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
|
|
"server/models"
|
|
|
|
"github.com/beego/beego/v2/client/orm"
|
|
)
|
|
|
|
// TeamQuery 团队日报汇总查询。
|
|
type TeamQuery struct {
|
|
Date string
|
|
OrgID uint64
|
|
Keyword string
|
|
Page int
|
|
PageSize int
|
|
}
|
|
|
|
// TeamStats 团队提交统计。
|
|
type TeamStats struct {
|
|
Total int `json:"total"`
|
|
Submitted int `json:"submitted"`
|
|
Missing int `json:"missing"`
|
|
Rate float64 `json:"rate"` // 提交率百分比,保留一位小数
|
|
}
|
|
|
|
// TeamRow 团队汇总中的一名成员。
|
|
type TeamRow struct {
|
|
UserID uint64 `json:"user_id"`
|
|
UserName string `json:"user_name"`
|
|
Account string `json:"account"`
|
|
OrgID uint64 `json:"org_id"`
|
|
OrgName string `json:"org_name"`
|
|
Submitted bool `json:"submitted"`
|
|
ReportID uint64 `json:"report_id"`
|
|
Status int8 `json:"status"` // -1未填写 0草稿 1已提交
|
|
SubmitTime string `json:"submit_time"`
|
|
HasIssue bool `json:"has_issue"`
|
|
}
|
|
|
|
// TeamResult 团队汇总结果。
|
|
type TeamResult struct {
|
|
Date string `json:"date"`
|
|
List []TeamRow `json:"list"`
|
|
Total int64 `json:"total"`
|
|
Stats TeamStats `json:"stats"`
|
|
}
|
|
|
|
// OrgOption 团队筛选用的部门选项(前端按 parent_id 组装树)。
|
|
type OrgOption struct {
|
|
ID uint64 `json:"id"`
|
|
ParentID uint64 `json:"parent_id"`
|
|
Name string `json:"name"`
|
|
MemberCount int `json:"member_count"`
|
|
}
|
|
|
|
// TeamStatus 团队日报汇总:按日期返回管辖范围内每位成员的提交情况。
|
|
// 管理员不选部门时统计全租户(含未分配部门的成员)。
|
|
func TeamStatus(u *UserContext, q TeamQuery) (*TeamResult, error) {
|
|
if !u.CanViewTeam {
|
|
return nil, errForbidden("无权查看团队日报")
|
|
}
|
|
date, err := ParseDate(q.Date)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if date > Today() {
|
|
return nil, errBad("不能查看未来日期的团队日报")
|
|
}
|
|
if q.Page < 1 {
|
|
q.Page = 1
|
|
}
|
|
if q.PageSize < 1 || q.PageSize > 100 {
|
|
q.PageSize = 20
|
|
}
|
|
|
|
orgs, err := loadOrgs(u.Tid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
orgByID := make(map[uint64]*models.BackendOrganization, len(orgs))
|
|
for i := range orgs {
|
|
orgByID[orgs[i].ID] = &orgs[i]
|
|
}
|
|
|
|
var scopeIDs []uint64
|
|
if q.OrgID > 0 {
|
|
if !u.IsAdmin && !u.InTeamOrg(q.OrgID) {
|
|
return nil, errForbidden("无权查看该部门")
|
|
}
|
|
if _, ok := orgByID[q.OrgID]; !ok {
|
|
return nil, errBad("部门不存在")
|
|
}
|
|
scopeIDs = collectSubtree([]uint64{q.OrgID}, orgs)
|
|
} else if !u.IsAdmin {
|
|
scopeIDs = u.TeamOrgIDs
|
|
}
|
|
if scopeIDs != nil && len(scopeIDs) == 0 {
|
|
return nil, errForbidden("你还没有管辖的部门")
|
|
}
|
|
|
|
cond := orm.NewCondition().
|
|
And("tid", u.Tid).
|
|
And("delete_time__isnull", true).
|
|
And("status", 1).
|
|
AndNot("uid", u.UID)
|
|
if scopeIDs != nil {
|
|
cond = cond.And("org_id__in", scopeIDs)
|
|
}
|
|
if kw := strings.TrimSpace(q.Keyword); kw != "" {
|
|
cond = cond.AndCond(orm.NewCondition().
|
|
Or("name__icontains", kw).
|
|
Or("account__icontains", kw))
|
|
}
|
|
base := models.Orm.QueryTable(new(models.SystemTenantUser)).SetCond(cond)
|
|
|
|
total, _ := base.Count()
|
|
var members []models.SystemTenantUser
|
|
if _, err := base.OrderBy("org_id", "uid").
|
|
Limit(q.PageSize).Offset((q.Page - 1) * q.PageSize).
|
|
All(&members); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 统计口径覆盖全部成员,不受分页影响。
|
|
var allMembers []models.SystemTenantUser
|
|
if _, err := base.OrderBy("uid").All(&allMembers, "uid"); err != nil {
|
|
return nil, err
|
|
}
|
|
reports, err := loadReportsByDate(u.Tid, date)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stats := TeamStats{Total: len(allMembers)}
|
|
for _, m := range allMembers {
|
|
if r := reports[m.Uid]; r != nil && r.Status == 1 {
|
|
stats.Submitted++
|
|
}
|
|
}
|
|
stats.Missing = stats.Total - stats.Submitted
|
|
if stats.Total > 0 {
|
|
stats.Rate = math.Round(float64(stats.Submitted)*1000/float64(stats.Total)) / 10
|
|
}
|
|
|
|
list := make([]TeamRow, 0, len(members))
|
|
for i := range members {
|
|
m := &members[i]
|
|
row := TeamRow{
|
|
UserID: m.Uid,
|
|
UserName: userNameOf(m),
|
|
Account: ptrString(m.Account),
|
|
OrgID: m.OrgID,
|
|
Status: -1,
|
|
}
|
|
if o := orgByID[m.OrgID]; o != nil {
|
|
row.OrgName = o.OrgName
|
|
}
|
|
if r := reports[m.Uid]; r != nil {
|
|
row.ReportID = r.ID
|
|
row.Status = r.Status
|
|
row.Submitted = r.Status == 1
|
|
row.SubmitTime = formatTime(r.SubmitTime)
|
|
row.HasIssue = strings.TrimSpace(r.Issues) != ""
|
|
}
|
|
list = append(list, row)
|
|
}
|
|
return &TeamResult{Date: date, List: list, Total: total, Stats: stats}, nil
|
|
}
|
|
|
|
// TeamOrgs 返回团队筛选可选部门;管理员为全租户,主管仅自己管辖的部门子树。
|
|
func TeamOrgs(u *UserContext) ([]OrgOption, error) {
|
|
if !u.CanViewTeam {
|
|
return nil, errForbidden("无权查看团队日报")
|
|
}
|
|
orgs, err := loadOrgs(u.Tid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
allow := make(map[uint64]bool, len(orgs))
|
|
if u.IsAdmin {
|
|
for _, o := range orgs {
|
|
allow[o.ID] = true
|
|
}
|
|
} else {
|
|
for _, id := range u.TeamOrgIDs {
|
|
allow[id] = true
|
|
}
|
|
}
|
|
|
|
var users []models.SystemTenantUser
|
|
if _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
|
Filter("tid", u.Tid).
|
|
Filter("delete_time__isnull", true).
|
|
Filter("status", 1).
|
|
All(&users, "org_id"); err != nil {
|
|
return nil, err
|
|
}
|
|
counts := make(map[uint64]int, len(orgs))
|
|
for _, m := range users {
|
|
if m.OrgID > 0 {
|
|
counts[m.OrgID]++
|
|
}
|
|
}
|
|
|
|
out := make([]OrgOption, 0, len(allow))
|
|
for i := range orgs {
|
|
o := &orgs[i]
|
|
if !allow[o.ID] {
|
|
continue
|
|
}
|
|
out = append(out, OrgOption{
|
|
ID: o.ID,
|
|
ParentID: o.ParentID,
|
|
Name: o.OrgName,
|
|
MemberCount: counts[o.ID],
|
|
})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// loadReportsByDate 一次取回某日全部已提交/草稿日报,按作者 uid 建索引。
|
|
func loadReportsByDate(tid uint64, date string) (map[uint64]*models.OaDailyReport, error) {
|
|
var rows []models.OaDailyReport
|
|
if _, err := models.Orm.QueryTable(new(models.OaDailyReport)).
|
|
Filter("tid", tid).
|
|
Filter("report_date", date).
|
|
Filter("is_deleted", 0).
|
|
All(&rows); err != nil {
|
|
return nil, err
|
|
}
|
|
out := make(map[uint64]*models.OaDailyReport, len(rows))
|
|
for i := range rows {
|
|
out[rows[i].UserID] = &rows[i]
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func userNameOf(m *models.SystemTenantUser) string {
|
|
if n := strings.TrimSpace(ptrString(m.Name)); n != "" {
|
|
return n
|
|
}
|
|
if a := strings.TrimSpace(ptrString(m.Account)); a != "" {
|
|
return a
|
|
}
|
|
return "未命名成员"
|
|
}
|