feat: complete workbench split admin routes

This commit is contained in:
richjohnrambo
2026-09-17 23:37:16 +08:00
parent 7099a33cde
commit 5c805c7b09
6 changed files with 334 additions and 26 deletions
+80
View File
@@ -162,6 +162,73 @@ func (s *Store) ReorderAdminWorkbenchModules(ctx context.Context, items []AdminW
return tx.Commit()
}
func (s *Store) AdminOrganizationDepartments(ctx context.Context, page, perPage int, parentID, name, state string) ([]OrganizationDepartment, int, error) {
page, perPage = normalizeAdminPage(page, perPage)
where := []string{"1=1"}
args := []any{}
if parentID = strings.TrimSpace(parentID); parentID != "" {
where = append(where, "parent_id=?")
args = append(args, parentID)
}
if name = strings.TrimSpace(name); name != "" {
where = append(where, "name LIKE ?")
args = append(args, "%"+name+"%")
}
if state = strings.TrimSpace(state); state != "" {
where = append(where, "state=?")
args = append(args, state)
}
whereSQL := strings.Join(where, " AND ")
var total int
if err := s.db.GetContext(ctx, &total, "SELECT COUNT(*) FROM im_org_departments WHERE "+whereSQL, args...); err != nil {
return nil, 0, err
}
queryArgs := append([]any{}, args...)
queryArgs = append(queryArgs, (page-1)*perPage, perPage)
var items []OrganizationDepartment
err := s.db.SelectContext(ctx, &items, `SELECT d.department_id,d.name,d.parent_id,
(SELECT COUNT(*) FROM im_org_departments c WHERE c.parent_id=d.department_id AND c.state='active') AS child_count,
(SELECT COUNT(*) FROM im_org_members m WHERE m.department_id=d.department_id AND m.employment_state='active') AS member_count,
d.version
FROM im_org_departments d WHERE `+whereSQL+` ORDER BY d.parent_id ASC,d.version DESC,d.department_id ASC LIMIT ?,?`, queryArgs...)
return items, total, err
}
func (s *Store) AdminOrganizationMembers(ctx context.Context, page, perPage int, departmentID, keyword, state string, userID int64) ([]OrganizationMember, int, error) {
page, perPage = normalizeAdminPage(page, perPage)
where := []string{"1=1"}
args := []any{}
if departmentID = strings.TrimSpace(departmentID); departmentID != "" {
where = append(where, "department_id=?")
args = append(args, departmentID)
}
if keyword = strings.TrimSpace(keyword); keyword != "" {
where = append(where, "(display_name LIKE ? OR position LIKE ? OR mobile LIKE ? OR email LIKE ?)")
like := "%" + keyword + "%"
args = append(args, like, like, like, like)
}
if state = strings.TrimSpace(state); state != "" {
where = append(where, "employment_state=?")
args = append(args, state)
}
if userID > 0 {
where = append(where, "user_id=?")
args = append(args, userID)
}
whereSQL := strings.Join(where, " AND ")
var total int
if err := s.db.GetContext(ctx, &total, "SELECT COUNT(*) FROM im_org_members WHERE "+whereSQL, args...); err != nil {
return nil, 0, err
}
queryArgs := append([]any{}, args...)
queryArgs = append(queryArgs, (page-1)*perPage, perPage)
var items []OrganizationMember
err := s.db.SelectContext(ctx, &items, `SELECT user_id,department_id,display_name,position,IFNULL(mobile,'') AS mobile,IFNULL(email,'') AS email,
IFNULL(avatar_file_id,'') AS avatar_file_id,employment_state AS status,contactable,version
FROM im_org_members WHERE `+whereSQL+` ORDER BY department_id ASC,user_id ASC LIMIT ?,?`, queryArgs...)
return items, total, err
}
func (s *Store) AdminMailboxConfig(ctx context.Context) (*AdminMailboxConfig, error) {
var cfg AdminMailboxConfig
if err := s.db.GetContext(ctx, &cfg, `SELECT id,enabled+0 AS enabled,allow_anonymous+0 AS allow_anonymous,CAST(required_fields AS CHAR) AS required_fields,max_length,CAST(attachment_policy AS CHAR) AS attachment_policy,notice_title,notice_content,notice_updated_at,updated_at FROM im_mailbox_config WHERE id=1`); err != nil {
@@ -224,3 +291,16 @@ func defaultString(value string, fallback string) string {
}
return value
}
func normalizeAdminPage(page, perPage int) (int, int) {
if page <= 0 {
page = 1
}
if perPage <= 0 {
perPage = 20
}
if perPage > 200 {
perPage = 200
}
return page, perPage
}
+82
View File
@@ -783,6 +783,34 @@ func (s *Store) SubmitDaily(ctx context.Context, userID int64, reportID string,
return s.DailyReport(ctx, userID, reportID)
}
func (s *Store) DailyReportStats(ctx context.Context, date time.Time, departmentID string) (*ReportStats, error) {
stats := &ReportStats{DepartmentID: strings.TrimSpace(departmentID)}
memberWhere, memberArgs := reportMemberScope(stats.DepartmentID)
if err := s.db.GetContext(ctx, &stats.ExpectedCount,
`SELECT COUNT(DISTINCT user_id) FROM im_org_members WHERE `+strings.Join(memberWhere, " AND "), memberArgs...); err != nil {
if isMissingTable(err) {
return stats, nil
}
return nil, err
}
args := append([]any{date.Format("2006-01-02")}, memberArgs...)
if err := s.db.GetContext(ctx, &stats.SubmittedCount,
`SELECT COUNT(DISTINCT r.author_id)
FROM im_daily_reports AS r
INNER JOIN im_org_members AS m ON m.user_id=r.author_id
WHERE r.report_date=? AND r.status='submitted' AND `+strings.Join(prefixReportMemberScope(memberWhere), " AND "), args...); err != nil {
if isMissingTable(err) {
return stats, nil
}
return nil, err
}
stats.MissingCount = stats.ExpectedCount - stats.SubmittedCount
if stats.MissingCount < 0 {
stats.MissingCount = 0
}
return stats, nil
}
func (s *Store) MonthlyReports(ctx context.Context, userID int64, cursor string, limit int) ([]MonthlyReport, string, error) {
var items []MonthlyReport
query := `SELECT report_id,author_id,report_month,work_summary,next_plan,attachments,status,read_state,version,submitted_at,created_at,updated_at FROM im_monthly_reports WHERE author_id=?`
@@ -869,6 +897,34 @@ func (s *Store) ReopenMonthly(ctx context.Context, userID int64, reportID string
return s.MonthlyReport(ctx, userID, reportID)
}
func (s *Store) MonthlyReportStats(ctx context.Context, month, departmentID string) (*ReportStats, error) {
stats := &ReportStats{DepartmentID: strings.TrimSpace(departmentID)}
memberWhere, memberArgs := reportMemberScope(stats.DepartmentID)
if err := s.db.GetContext(ctx, &stats.ExpectedCount,
`SELECT COUNT(DISTINCT user_id) FROM im_org_members WHERE `+strings.Join(memberWhere, " AND "), memberArgs...); err != nil {
if isMissingTable(err) {
return stats, nil
}
return nil, err
}
args := append([]any{strings.TrimSpace(month)}, memberArgs...)
if err := s.db.GetContext(ctx, &stats.SubmittedCount,
`SELECT COUNT(DISTINCT r.author_id)
FROM im_monthly_reports AS r
INNER JOIN im_org_members AS m ON m.user_id=r.author_id
WHERE r.report_month=? AND r.status='submitted' AND `+strings.Join(prefixReportMemberScope(memberWhere), " AND "), args...); err != nil {
if isMissingTable(err) {
return stats, nil
}
return nil, err
}
stats.MissingCount = stats.ExpectedCount - stats.SubmittedCount
if stats.MissingCount < 0 {
stats.MissingCount = 0
}
return stats, nil
}
func (s *Store) MailboxConfig(ctx context.Context) (*MailboxConfig, error) {
var cfg MailboxConfig
if err := s.db.GetContext(ctx, &cfg, `SELECT enabled,allow_anonymous,required_fields,max_length,attachment_policy,notice_title,notice_content,CAST(COALESCE(UNIX_TIMESTAMP(notice_updated_at),1) AS UNSIGNED) AS version FROM im_mailbox_config WHERE id=1`); err != nil {
@@ -1138,6 +1194,32 @@ func trimMemberPage(items []OrganizationMember, limit int) ([]OrganizationMember
return items, next, nil
}
func reportMemberScope(departmentID string) ([]string, []any) {
where := []string{"employment_state='active'"}
args := []any{}
if departmentID = strings.TrimSpace(departmentID); departmentID != "" {
where = append(where, "department_id=?")
args = append(args, departmentID)
}
return where, args
}
func prefixReportMemberScope(where []string) []string {
out := make([]string, 0, len(where))
for _, condition := range where {
condition = strings.TrimSpace(condition)
switch {
case strings.HasPrefix(condition, "employment_state"):
out = append(out, "m."+condition)
case strings.HasPrefix(condition, "department_id"):
out = append(out, "m."+condition)
default:
out = append(out, condition)
}
}
return out
}
func isDuplicate(err error) bool {
return err != nil && strings.Contains(err.Error(), "Duplicate entry")
}
+1
View File
@@ -205,6 +205,7 @@ type ReportStats struct {
ExpectedCount int
SubmittedCount int
MissingCount int
DepartmentID string
}
type MailboxConfig struct {