优化日报模块
This commit is contained in:
1 parent
79d5ae6b66
commit
51fd90fc51
17 files changed
+1767
-257
No files matched your search
+114
-49
@@ -2,6 +2,7 @@ package dailyreport
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"server/models"
|
||||
@@ -26,7 +27,7 @@ type TeamStats struct {
|
||||
Rate float64 `json:"rate"` // 提交率百分比,保留一位小数
|
||||
}
|
||||
|
||||
// TeamRow 团队汇总中的一名成员。
|
||||
// TeamRow 团队汇总中的一名成员(仅包含当日已提交日报的成员)。
|
||||
type TeamRow struct {
|
||||
UserID uint64 `json:"user_id"`
|
||||
UserName string `json:"user_name"`
|
||||
@@ -35,9 +36,10 @@ type TeamRow struct {
|
||||
OrgName string `json:"org_name"`
|
||||
Submitted bool `json:"submitted"`
|
||||
ReportID uint64 `json:"report_id"`
|
||||
Status int8 `json:"status"` // -1未填写 0草稿 1已提交
|
||||
Status int8 `json:"status"` // 0草稿 1已提交(团队列表只返回 1)
|
||||
SubmitTime string `json:"submit_time"`
|
||||
HasIssue bool `json:"has_issue"`
|
||||
Summary string `json:"summary"` // 日报内容摘要(优先今日完成)
|
||||
}
|
||||
|
||||
// TeamResult 团队汇总结果。
|
||||
@@ -56,7 +58,8 @@ type OrgOption struct {
|
||||
MemberCount int `json:"member_count"`
|
||||
}
|
||||
|
||||
// TeamStatus 团队日报汇总:按日期返回管辖范围内每位成员的提交情况。
|
||||
// TeamStatus 团队日报日志池:只返回当日已提交的日报,未提交(含仅存草稿)的成员不展示。
|
||||
// 列表按提交时间倒序排列(按“谁发布了内容”展示,不按人分组),右侧根据选中项展示日报内容。
|
||||
// 管理员不选部门时统计全租户(含未分配部门的成员)。
|
||||
func TeamStatus(u *UserContext, q TeamQuery) (*TeamResult, error) {
|
||||
if !u.CanViewTeam {
|
||||
@@ -101,73 +104,135 @@ func TeamStatus(u *UserContext, q TeamQuery) (*TeamResult, error) {
|
||||
return nil, errForbidden("你还没有管辖的部门")
|
||||
}
|
||||
|
||||
cond := orm.NewCondition().
|
||||
// 管辖范围内的成员:用于姓名/部门补全与提交统计,不受关键词与分页影响。
|
||||
memberCond := orm.NewCondition().
|
||||
And("tid", u.Tid).
|
||||
And("delete_time__isnull", true).
|
||||
And("status", 1).
|
||||
AndNot("uid", u.UID)
|
||||
And("status", 1)
|
||||
if scopeIDs != nil {
|
||||
cond = cond.And("org_id__in", scopeIDs)
|
||||
memberCond = memberCond.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 {
|
||||
if _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
SetCond(memberCond).OrderBy("org_id", "uid").All(&allMembers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
memberByUID := make(map[uint64]*models.SystemTenantUser, len(allMembers))
|
||||
for i := range allMembers {
|
||||
memberByUID[allMembers[i].Uid] = &allMembers[i]
|
||||
}
|
||||
|
||||
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++
|
||||
kw := strings.TrimSpace(q.Keyword)
|
||||
rows := make([]TeamRow, 0, len(allMembers))
|
||||
for i := range allMembers {
|
||||
m := &allMembers[i]
|
||||
r := reports[m.Uid]
|
||||
if r == nil || r.Status != 1 {
|
||||
continue
|
||||
}
|
||||
stats.Submitted++
|
||||
row := buildTeamRow(r, m.OrgID, userNameOf(m), ptrString(m.Account), "")
|
||||
if o := orgByID[m.OrgID]; o != nil {
|
||||
row.OrgName = o.OrgName
|
||||
}
|
||||
if kw != "" && !matchTeamKeyword(row.UserName, row.Account, kw) {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
// 作者不在成员表(成员已删除 / 组织映射异常)时用日报快照补齐,
|
||||
// 保证已提交的内容不会因人员变动而看不到。
|
||||
for uid, r := range reports {
|
||||
if r.Status != 1 {
|
||||
continue
|
||||
}
|
||||
if _, ok := memberByUID[uid]; ok {
|
||||
continue
|
||||
}
|
||||
if scopeIDs != nil && !containsID(scopeIDs, r.OrgID) {
|
||||
continue
|
||||
}
|
||||
row := buildTeamRow(r, r.OrgID, r.UserName, "", r.OrgName)
|
||||
if kw != "" && !matchTeamKeyword(row.UserName, row.Account, kw) {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
// 日志池排序:按发布时间(提交时间)倒序,最新提交排在最前。
|
||||
sort.SliceStable(rows, func(i, j int) bool {
|
||||
if rows[i].SubmitTime != rows[j].SubmitTime {
|
||||
return rows[i].SubmitTime > rows[j].SubmitTime
|
||||
}
|
||||
return rows[i].ReportID > rows[j].ReportID
|
||||
})
|
||||
|
||||
stats.Missing = stats.Total - stats.Submitted
|
||||
if stats.Missing < 0 {
|
||||
stats.Missing = 0
|
||||
}
|
||||
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)
|
||||
// 列表分页:只对已提交的日报分页。
|
||||
total := len(rows)
|
||||
start := (q.Page - 1) * q.PageSize
|
||||
if start > total {
|
||||
start = total
|
||||
}
|
||||
return &TeamResult{Date: date, List: list, Total: total, Stats: stats}, nil
|
||||
end := start + q.PageSize
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
return &TeamResult{Date: date, List: rows[start:end], Total: int64(total), Stats: stats}, nil
|
||||
}
|
||||
|
||||
// buildTeamRow 组装一条团队日报行;摘要取日报正文。
|
||||
func buildTeamRow(r *models.OaDailyReport, orgID uint64, userName, account, orgName string) TeamRow {
|
||||
return TeamRow{
|
||||
UserID: r.UserID,
|
||||
UserName: userName,
|
||||
Account: account,
|
||||
OrgID: orgID,
|
||||
OrgName: orgName,
|
||||
Submitted: true,
|
||||
ReportID: r.ID,
|
||||
Status: r.Status,
|
||||
SubmitTime: formatTime(r.SubmitTime),
|
||||
HasIssue: strings.TrimSpace(r.Issues) != "",
|
||||
Summary: reportSummary(r),
|
||||
}
|
||||
}
|
||||
|
||||
// reportSummary 生成团队列表用的内容摘要(优先今日完成),超长截断。
|
||||
func reportSummary(r *models.OaDailyReport) string {
|
||||
for _, text := range []string{r.TodayDone, r.TomorrowPlan, r.Issues} {
|
||||
s := strings.TrimSpace(text)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
runes := []rune(s)
|
||||
if len(runes) > 60 {
|
||||
return string(runes[:60]) + "…"
|
||||
}
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// matchTeamKeyword 按姓名/账号模糊匹配团队筛选项。
|
||||
func matchTeamKeyword(name, account, kw string) bool {
|
||||
kw = strings.ToLower(kw)
|
||||
return strings.Contains(strings.ToLower(name), kw) ||
|
||||
strings.Contains(strings.ToLower(account), kw)
|
||||
}
|
||||
|
||||
// TeamOrgs 返回团队筛选可选部门;管理员为全租户,主管仅自己管辖的部门子树。
|
||||
|
||||
Reference in new issue
Block a user