优化日报模块
This commit is contained in:
1 parent
79d5ae6b66
commit
51fd90fc51
17 files changed
+1767
-257
No files matched your search
@@ -22,21 +22,21 @@ type Attachment struct {
|
||||
|
||||
// ReportItem 日报列表/详情中的主体。
|
||||
type ReportItem struct {
|
||||
ID uint64 `json:"id"`
|
||||
UserID uint64 `json:"user_id"`
|
||||
UserName string `json:"user_name"`
|
||||
OrgID uint64 `json:"org_id"`
|
||||
OrgName string `json:"org_name"`
|
||||
ReportDate string `json:"report_date"`
|
||||
TodayDone string `json:"today_done"`
|
||||
TomorrowPlan string `json:"tomorrow_plan"`
|
||||
Issues string `json:"issues"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
Status int8 `json:"status"`
|
||||
SubmitTime string `json:"submit_time"`
|
||||
CreateTime string `json:"create_time"`
|
||||
UpdateTime string `json:"update_time"`
|
||||
CommentCount int `json:"comment_count"`
|
||||
ID uint64 `json:"id"`
|
||||
UserID uint64 `json:"user_id"`
|
||||
UserName string `json:"user_name"`
|
||||
OrgID uint64 `json:"org_id"`
|
||||
OrgName string `json:"org_name"`
|
||||
ReportDate string `json:"report_date"`
|
||||
TodayDone string `json:"today_done"`
|
||||
TomorrowPlan string `json:"tomorrow_plan"`
|
||||
Issues string `json:"issues"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
Status int8 `json:"status"`
|
||||
SubmitTime string `json:"submit_time"`
|
||||
CreateTime string `json:"create_time"`
|
||||
UpdateTime string `json:"update_time"`
|
||||
CommentCount int `json:"comment_count"`
|
||||
}
|
||||
|
||||
// Comment 日报评论(两级回复)。
|
||||
@@ -52,13 +52,29 @@ type Comment struct {
|
||||
CanDelete bool `json:"can_delete"`
|
||||
}
|
||||
|
||||
// Detail 日报详情:主体 + 评论 + 当前用户可执行的操作。
|
||||
// Detail 日报详情:主体 + 评论 + 修改痕迹 + 当前用户可执行的操作。
|
||||
type Detail struct {
|
||||
Report ReportItem `json:"report"`
|
||||
Comments []Comment `json:"comments"`
|
||||
CanEdit bool `json:"can_edit"`
|
||||
CanDelete bool `json:"can_delete"`
|
||||
CanComment bool `json:"can_comment"`
|
||||
Report ReportItem `json:"report"`
|
||||
Comments []Comment `json:"comments"`
|
||||
Histories []HistoryItem `json:"histories"`
|
||||
IsAuthor bool `json:"is_author"`
|
||||
CanEdit bool `json:"can_edit"`
|
||||
CanDelete bool `json:"can_delete"`
|
||||
CanComment bool `json:"can_comment"`
|
||||
CanWithdraw bool `json:"can_withdraw"`
|
||||
}
|
||||
|
||||
// HistoryItem 日报修改痕迹:撤销提交 / 撤销后重新提交时的内容快照。
|
||||
type HistoryItem struct {
|
||||
ID uint64 `json:"id"`
|
||||
Action int8 `json:"action"` // 1撤销提交 2重新提交
|
||||
UserName string `json:"user_name"`
|
||||
TodayDone string `json:"today_done"`
|
||||
TomorrowPlan string `json:"tomorrow_plan"`
|
||||
Issues string `json:"issues"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
SubmitTime string `json:"submit_time"`
|
||||
CreateTime string `json:"create_time"`
|
||||
}
|
||||
|
||||
// SaveInput 保存日报入参;Submit=true 表示提交,否则保存草稿。
|
||||
@@ -97,6 +113,7 @@ type MineResult struct {
|
||||
}
|
||||
|
||||
// Save 新建或更新一条日报(同一人同一天唯一)。
|
||||
// 已提交的日报处于锁定状态,必须先撤销提交退回草稿才能修改。
|
||||
func Save(u *UserContext, in SaveInput) (*ReportItem, error) {
|
||||
date, err := ParseDate(in.Date)
|
||||
if err != nil {
|
||||
@@ -166,11 +183,18 @@ func Save(u *UserContext, in SaveInput) (*ReportItem, error) {
|
||||
return nil, findErr
|
||||
}
|
||||
|
||||
// 已提交的日报在窗口内允许继续修改,状态保持已提交。
|
||||
// 已提交的日报锁定:必须先撤销提交退回草稿,才能修改内容。
|
||||
if existing.Status == 1 {
|
||||
status = 1
|
||||
return nil, errBad("日报已提交并锁定,如需修改请先撤销提交")
|
||||
}
|
||||
if status == 1 && existing.SubmitTime == nil {
|
||||
// 撤销过提交的日报再次提交时,留下重新提交痕迹。
|
||||
resubmit := false
|
||||
if in.Submit {
|
||||
if existing.SubmitTime != nil {
|
||||
resubmit = true
|
||||
} else {
|
||||
resubmit = hasWithdrawHistory(u.Tid, existing.ID)
|
||||
}
|
||||
existing.SubmitTime = &now
|
||||
}
|
||||
existing.TodayDone = in.TodayDone
|
||||
@@ -182,8 +206,35 @@ func Save(u *UserContext, in SaveInput) (*ReportItem, error) {
|
||||
existing.OrgID = u.OrgID
|
||||
existing.OrgName = u.OrgName
|
||||
|
||||
if _, err := models.Orm.Update(&existing, "today_done", "tomorrow_plan", "issues",
|
||||
tx, err := models.Orm.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tx.Update(&existing, "today_done", "tomorrow_plan", "issues",
|
||||
"attachments", "status", "submit_time", "user_name", "org_id", "org_name"); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
if resubmit {
|
||||
history := models.OaDailyReportHistory{
|
||||
Tid: u.Tid,
|
||||
ReportID: existing.ID,
|
||||
UserID: u.UID,
|
||||
UserName: u.Name,
|
||||
ReportDate: date,
|
||||
Action: 2,
|
||||
TodayDone: existing.TodayDone,
|
||||
TomorrowPlan: existing.TomorrowPlan,
|
||||
Issues: existing.Issues,
|
||||
Attachments: existing.Attachments,
|
||||
SubmitTime: existing.SubmitTime,
|
||||
}
|
||||
if _, err := tx.Insert(&history); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count := commentCount(u.Tid, existing.ID)
|
||||
@@ -245,6 +296,67 @@ func ListMine(u *UserContext, q MineQuery) (*MineResult, error) {
|
||||
return &MineResult{List: list, Total: total, Summary: *summary}, nil
|
||||
}
|
||||
|
||||
// CalendarDay 我的日报日历热力图单日状态。
|
||||
type CalendarDay struct {
|
||||
Date string `json:"date"`
|
||||
Status int8 `json:"status"` // 0草稿 1已提交
|
||||
ID uint64 `json:"id"`
|
||||
}
|
||||
|
||||
// CalendarResult 近期热力图数据(默认近三个月)。
|
||||
type CalendarResult struct {
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
Total int `json:"total"`
|
||||
Submitted int `json:"submitted"`
|
||||
Days []CalendarDay `json:"days"`
|
||||
}
|
||||
|
||||
// Calendar 返回最近 months 个月(含本月)本人日报的逐日状态,供热力图按月/周渲染。
|
||||
// months 越界时回落到 3 个月。
|
||||
func Calendar(u *UserContext, months int) (*CalendarResult, error) {
|
||||
if months < 1 || months > 12 {
|
||||
months = 3
|
||||
}
|
||||
now := time.Now()
|
||||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
start := monthStart.AddDate(0, -(months - 1), 0)
|
||||
end := monthStart.AddDate(0, 1, -1)
|
||||
startDate := start.Format("2006-01-02")
|
||||
endDate := end.Format("2006-01-02")
|
||||
|
||||
var rows []models.OaDailyReport
|
||||
if _, err := models.Orm.QueryTable(new(models.OaDailyReport)).
|
||||
Filter("tid", u.Tid).
|
||||
Filter("user_id", u.UID).
|
||||
Filter("is_deleted", 0).
|
||||
Filter("report_date__gte", startDate).
|
||||
Filter("report_date__lte", endDate).
|
||||
OrderBy("report_date").
|
||||
All(&rows, "id", "report_date", "status"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := &CalendarResult{
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
Days: make([]CalendarDay, 0, len(rows)),
|
||||
}
|
||||
for i := range rows {
|
||||
res.Days = append(res.Days, CalendarDay{
|
||||
// DATE 列在 parseTime=true 下会带上时间与时区,必须归一化为 YYYY-MM-DD,
|
||||
// 否则前端按日期匹配热力图格子时永远匹配不到。
|
||||
Date: dateOnly(rows[i].ReportDate),
|
||||
Status: rows[i].Status,
|
||||
ID: rows[i].ID,
|
||||
})
|
||||
if rows[i].Status == 1 {
|
||||
res.Submitted++
|
||||
}
|
||||
}
|
||||
res.Total = len(rows)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// mineSummary 本月提交统计与今天的状态。
|
||||
func mineSummary(u *UserContext) (*MineSummary, error) {
|
||||
now := time.Now()
|
||||
@@ -293,15 +405,85 @@ func GetDetail(u *UserContext, id uint64) (*Detail, error) {
|
||||
}
|
||||
|
||||
author := report.UserID == u.UID
|
||||
canEdit := author && report.Status == 0 && InWindow(report.ReportDate)
|
||||
canWithdraw := author && report.Status == 1 && InWindow(report.ReportDate)
|
||||
// 删除仅限草稿:已提交日报需先撤销提交才能删除。
|
||||
canDelete := report.Status == 0 && (u.IsAdmin || (author && InWindow(report.ReportDate)))
|
||||
|
||||
// 修改痕迹只对作者本人与租户管理员可见。
|
||||
histories := make([]HistoryItem, 0)
|
||||
if author || u.IsAdmin {
|
||||
histories, err = loadHistories(u.Tid, report.ID)
|
||||
if err != nil {
|
||||
// 痕迹表缺失或查询异常时降级为空,避免详情整体 500(部署时需执行 create_oa_dailyreport_history.sql)。
|
||||
histories = make([]HistoryItem, 0)
|
||||
}
|
||||
}
|
||||
return &Detail{
|
||||
Report: *buildItem(report, len(comments)),
|
||||
Comments: comments,
|
||||
CanEdit: author && InWindow(report.ReportDate),
|
||||
CanDelete: (author && InWindow(report.ReportDate)) || u.IsAdmin,
|
||||
CanComment: report.Status == 1 && (author || u.IsAdmin || u.InTeamOrg(report.OrgID)),
|
||||
Report: *buildItem(report, len(comments)),
|
||||
Comments: comments,
|
||||
Histories: histories,
|
||||
IsAuthor: author,
|
||||
CanEdit: canEdit,
|
||||
CanDelete: canDelete,
|
||||
CanComment: report.Status == 1 && (author || u.IsAdmin || u.InTeamOrg(report.OrgID)),
|
||||
CanWithdraw: canWithdraw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Withdraw 撤销提交:把已提交日报退回草稿,并把本次提交的内容快照写入修改痕迹。
|
||||
// 仅作者本人、且日报在补写窗口内时可操作;撤销后需修改并重新提交。
|
||||
func Withdraw(u *UserContext, id uint64) (*ReportItem, error) {
|
||||
report, err := loadReport(u.Tid, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if report.UserID != u.UID {
|
||||
return nil, errForbidden("只能撤销自己提交的日报")
|
||||
}
|
||||
if report.Status != 1 {
|
||||
return nil, errBad("日报尚未提交,无需撤销")
|
||||
}
|
||||
if !InWindow(report.ReportDate) {
|
||||
return nil, errBad(fmt.Sprintf("只能撤销最近%d天(%s 至 %s)的日报", BackfillDays, WindowStart(), Today()))
|
||||
}
|
||||
|
||||
tx, err := models.Orm.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
history := models.OaDailyReportHistory{
|
||||
Tid: u.Tid,
|
||||
ReportID: report.ID,
|
||||
UserID: u.UID,
|
||||
UserName: u.Name,
|
||||
ReportDate: report.ReportDate,
|
||||
Action: 1,
|
||||
TodayDone: report.TodayDone,
|
||||
TomorrowPlan: report.TomorrowPlan,
|
||||
Issues: report.Issues,
|
||||
Attachments: report.Attachments,
|
||||
SubmitTime: report.SubmitTime,
|
||||
}
|
||||
if _, err := tx.Insert(&history); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tx.QueryTable(new(models.OaDailyReport)).
|
||||
Filter("tid", u.Tid).Filter("id", report.ID).
|
||||
Update(map[string]interface{}{"status": 0, "submit_time": nil}); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
report.Status = 0
|
||||
count := commentCount(u.Tid, report.ID)
|
||||
return buildItem(report, count), nil
|
||||
}
|
||||
|
||||
// Delete 软删除日报及其评论:作者限补写窗口内,管理员不受限。
|
||||
//
|
||||
// 日报的 is_deleted 写入记录 ID(而非 1),既标记删除,又让唯一键
|
||||
@@ -311,6 +493,10 @@ func Delete(u *UserContext, id uint64) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 已提交日报锁定:不允许直接删除,需先撤销提交退回草稿。
|
||||
if report.Status == 1 {
|
||||
return errBad("日报已提交并锁定,请先撤销提交后再删除")
|
||||
}
|
||||
author := report.UserID == u.UID
|
||||
if !author && !u.IsAdmin {
|
||||
return errForbidden("无权删除该日报")
|
||||
@@ -462,6 +648,47 @@ func buildComment(u *UserContext, r *models.OaDailyReportComment) Comment {
|
||||
}
|
||||
}
|
||||
|
||||
// hasWithdrawHistory 判断一条日报是否被撤销过提交,用于识别“重新提交”。
|
||||
func hasWithdrawHistory(tid, reportID uint64) bool {
|
||||
n, err := models.Orm.QueryTable(new(models.OaDailyReportHistory)).
|
||||
Filter("tid", tid).Filter("report_id", reportID).Filter("action", 1).
|
||||
Count()
|
||||
return err == nil && n > 0
|
||||
}
|
||||
|
||||
// loadHistories 读取一条日报的全部修改痕迹,按写入顺序返回。
|
||||
func loadHistories(tid, reportID uint64) ([]HistoryItem, error) {
|
||||
var rows []models.OaDailyReportHistory
|
||||
if _, err := models.Orm.QueryTable(new(models.OaDailyReportHistory)).
|
||||
Filter("tid", tid).Filter("report_id", reportID).
|
||||
OrderBy("id").All(&rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]HistoryItem, 0, len(rows))
|
||||
for i := range rows {
|
||||
out = append(out, buildHistory(&rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func buildHistory(h *models.OaDailyReportHistory) HistoryItem {
|
||||
attachments := make([]Attachment, 0)
|
||||
if strings.TrimSpace(h.Attachments) != "" {
|
||||
_ = json.Unmarshal([]byte(h.Attachments), &attachments)
|
||||
}
|
||||
return HistoryItem{
|
||||
ID: h.ID,
|
||||
Action: h.Action,
|
||||
UserName: h.UserName,
|
||||
TodayDone: h.TodayDone,
|
||||
TomorrowPlan: h.TomorrowPlan,
|
||||
Issues: h.Issues,
|
||||
Attachments: attachments,
|
||||
SubmitTime: formatTime(h.SubmitTime),
|
||||
CreateTime: h.CreateTime.Format("2006-01-02 15:04"),
|
||||
}
|
||||
}
|
||||
|
||||
func buildItem(r *models.OaDailyReport, commentCount int) *ReportItem {
|
||||
attachments := make([]Attachment, 0)
|
||||
if strings.TrimSpace(r.Attachments) != "" {
|
||||
|
||||
+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