feat: complete workbench split admin routes
This commit is contained in:
+115
-8
@@ -11,6 +11,10 @@ import (
|
||||
)
|
||||
|
||||
func (a *API) adminWorkbenchApps(w http.ResponseWriter, r *http.Request) {
|
||||
st, ok := a.internalAdminStore(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
page := queryInt(r, "page", 1)
|
||||
@@ -21,7 +25,7 @@ func (a *API) adminWorkbenchApps(w http.ResponseWriter, r *http.Request) {
|
||||
enabled = &value
|
||||
}
|
||||
}
|
||||
items, total, err := a.store.AdminWorkbenchModules(r.Context(), page, perPage, r.URL.Query().Get("code"), r.URL.Query().Get("name"), enabled)
|
||||
items, total, err := st.AdminWorkbenchModules(r.Context(), page, perPage, r.URL.Query().Get("code"), r.URL.Query().Get("name"), enabled)
|
||||
if err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
@@ -32,7 +36,7 @@ func (a *API) adminWorkbenchApps(w http.ResponseWriter, r *http.Request) {
|
||||
if !decodeJSON(w, r, &body) {
|
||||
return
|
||||
}
|
||||
item, err := a.store.UpsertAdminWorkbenchModule(r.Context(), store.AdminWorkbenchModuleInput{
|
||||
item, err := st.UpsertAdminWorkbenchModule(r.Context(), store.AdminWorkbenchModuleInput{
|
||||
ID: body.ID, Code: body.Code, Name: body.Name, IconFileID: body.IconFileID, Route: body.Route,
|
||||
SortOrder: body.SortOrder, UnreadKey: body.UnreadKey, Enabled: body.Enabled, Permission: body.Permission,
|
||||
})
|
||||
@@ -47,6 +51,10 @@ func (a *API) adminWorkbenchApps(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *API) adminWorkbenchAppDisable(w http.ResponseWriter, r *http.Request) {
|
||||
st, ok := a.internalAdminStore(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodPost {
|
||||
methodNotAllowed(w, r)
|
||||
return
|
||||
@@ -58,7 +66,7 @@ func (a *API) adminWorkbenchAppDisable(w http.ResponseWriter, r *http.Request) {
|
||||
if !decodeJSON(w, r, &body) {
|
||||
return
|
||||
}
|
||||
if err := a.store.DisableAdminWorkbenchModule(r.Context(), body.ID, body.Code); err != nil {
|
||||
if err := st.DisableAdminWorkbenchModule(r.Context(), body.ID, body.Code); err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
@@ -66,6 +74,10 @@ func (a *API) adminWorkbenchAppDisable(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *API) adminWorkbenchAppsReorder(w http.ResponseWriter, r *http.Request) {
|
||||
st, ok := a.internalAdminStore(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodPost {
|
||||
methodNotAllowed(w, r)
|
||||
return
|
||||
@@ -83,17 +95,63 @@ func (a *API) adminWorkbenchAppsReorder(w http.ResponseWriter, r *http.Request)
|
||||
for _, item := range body.Items {
|
||||
items = append(items, store.AdminWorkbenchReorderItem{Code: item.Code, SortOrder: item.SortOrder})
|
||||
}
|
||||
if err := a.store.ReorderAdminWorkbenchModules(r.Context(), items); err != nil {
|
||||
if err := st.ReorderAdminWorkbenchModules(r.Context(), items); err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
func (a *API) adminOrganizationDepartments(w http.ResponseWriter, r *http.Request) {
|
||||
st, ok := a.internalAdminStore(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet {
|
||||
methodNotAllowed(w, r)
|
||||
return
|
||||
}
|
||||
page := queryInt(r, "page", 1)
|
||||
perPage := queryInt(r, "per_page", 20)
|
||||
items, total, err := st.AdminOrganizationDepartments(r.Context(), page, perPage,
|
||||
r.URL.Query().Get("parent_id"), r.URL.Query().Get("name"), r.URL.Query().Get("state"))
|
||||
if err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, map[string]any{"items": adminOrganizationDepartmentPayloads(items), "total": total, "page": page, "per_page": perPage})
|
||||
}
|
||||
|
||||
func (a *API) adminOrganizationMembers(w http.ResponseWriter, r *http.Request) {
|
||||
st, ok := a.internalAdminStore(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet {
|
||||
methodNotAllowed(w, r)
|
||||
return
|
||||
}
|
||||
page := queryInt(r, "page", 1)
|
||||
perPage := queryInt(r, "per_page", 20)
|
||||
userID, _ := strconv.ParseInt(strings.TrimSpace(r.URL.Query().Get("user_id")), 10, 64)
|
||||
items, total, err := st.AdminOrganizationMembers(r.Context(), page, perPage,
|
||||
r.URL.Query().Get("department_id"), firstNonEmpty(r.URL.Query().Get("q"), r.URL.Query().Get("keyword")),
|
||||
r.URL.Query().Get("employment_state"), userID)
|
||||
if err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, map[string]any{"items": adminOrganizationMemberPayloads(items), "total": total, "page": page, "per_page": perPage})
|
||||
}
|
||||
|
||||
func (a *API) adminMailboxConfig(w http.ResponseWriter, r *http.Request) {
|
||||
st, ok := a.internalAdminStore(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
cfg, err := a.store.AdminMailboxConfig(r.Context())
|
||||
cfg, err := st.AdminMailboxConfig(r.Context())
|
||||
if err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
@@ -104,7 +162,7 @@ func (a *API) adminMailboxConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if !decodeJSON(w, r, &body) {
|
||||
return
|
||||
}
|
||||
if err := a.store.UpdateAdminMailboxConfig(r.Context(), store.AdminMailboxConfig{
|
||||
if err := st.UpdateAdminMailboxConfig(r.Context(), store.AdminMailboxConfig{
|
||||
Enabled: body.Enabled, AllowAnonymous: body.AllowAnonymous, RequiredFields: body.RequiredFields,
|
||||
MaxLength: body.MaxLength, AttachmentPolicy: body.AttachmentPolicy, NoticeTitle: body.NoticeTitle, NoticeContent: body.NoticeContent,
|
||||
}); err != nil {
|
||||
@@ -118,9 +176,13 @@ func (a *API) adminMailboxConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *API) adminFeedbackConfig(w http.ResponseWriter, r *http.Request) {
|
||||
st, ok := a.internalAdminStore(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
cfg, err := a.store.AdminFeedbackConfig(r.Context())
|
||||
cfg, err := st.AdminFeedbackConfig(r.Context())
|
||||
if err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
@@ -131,7 +193,7 @@ func (a *API) adminFeedbackConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if !decodeJSON(w, r, &body) {
|
||||
return
|
||||
}
|
||||
if err := a.store.UpdateAdminFeedbackConfig(r.Context(), store.AdminFeedbackConfig{
|
||||
if err := st.UpdateAdminFeedbackConfig(r.Context(), store.AdminFeedbackConfig{
|
||||
Categories: body.Categories, MaxLength: body.MaxLength, AttachmentPolicy: body.AttachmentPolicy,
|
||||
AllowContact: body.AllowContact, DiagnosticsNotice: body.DiagnosticsNotice,
|
||||
}); err != nil {
|
||||
@@ -144,6 +206,17 @@ func (a *API) adminFeedbackConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *API) internalAdminStore(w http.ResponseWriter, r *http.Request) (*store.Store, bool) {
|
||||
tenantID, _ := strconv.ParseInt(firstNonEmpty(r.Header.Get("X-Tenant-ID"), r.URL.Query().Get("tenant_id")), 10, 64)
|
||||
tenantCode := firstNonEmpty(r.Header.Get("X-Tenant-Code"), r.URL.Query().Get("tenant_code"), "default")
|
||||
st, err := a.store.ForTenant(r.Context(), tenantID, tenantCode)
|
||||
if err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return nil, false
|
||||
}
|
||||
return st, true
|
||||
}
|
||||
|
||||
type adminWorkbenchModuleRequest struct {
|
||||
ID int64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
@@ -198,6 +271,40 @@ func adminWorkbenchModulePayload(item store.AdminWorkbenchModule) map[string]any
|
||||
}
|
||||
}
|
||||
|
||||
func adminOrganizationDepartmentPayloads(items []store.OrganizationDepartment) []map[string]any {
|
||||
res := make([]map[string]any, 0, len(items))
|
||||
for _, item := range items {
|
||||
res = append(res, map[string]any{
|
||||
"department_id": item.DepartmentID,
|
||||
"name": item.Name,
|
||||
"parent_id": item.ParentID,
|
||||
"child_count": item.ChildCount,
|
||||
"member_count": item.MemberCount,
|
||||
"version": item.Version,
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func adminOrganizationMemberPayloads(items []store.OrganizationMember) []map[string]any {
|
||||
res := make([]map[string]any, 0, len(items))
|
||||
for _, item := range items {
|
||||
res = append(res, map[string]any{
|
||||
"user_id": item.UserID,
|
||||
"department_id": item.DepartmentID,
|
||||
"display_name": item.DisplayName,
|
||||
"position": item.Position,
|
||||
"mobile": item.Mobile,
|
||||
"email": item.Email,
|
||||
"avatar_file_id": item.AvatarFileID,
|
||||
"status": item.Status,
|
||||
"contactable": item.Contactable,
|
||||
"version": item.Version,
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func adminMailboxConfigPayload(cfg store.AdminMailboxConfig) map[string]any {
|
||||
return map[string]any{
|
||||
"id": cfg.ID,
|
||||
|
||||
+39
-18
@@ -64,6 +64,8 @@ func (a *API) Register(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/internal/admin/workbench/apps", a.withInternalAdminAuth(a.adminWorkbenchApps))
|
||||
mux.HandleFunc("/internal/admin/workbench/apps/disable", a.withInternalAdminAuth(a.adminWorkbenchAppDisable))
|
||||
mux.HandleFunc("/internal/admin/workbench/apps/reorder", a.withInternalAdminAuth(a.adminWorkbenchAppsReorder))
|
||||
mux.HandleFunc("/internal/admin/workbench/organization/departments", a.withInternalAdminAuth(a.adminOrganizationDepartments))
|
||||
mux.HandleFunc("/internal/admin/workbench/organization/members", a.withInternalAdminAuth(a.adminOrganizationMembers))
|
||||
mux.HandleFunc("/internal/admin/workbench/mailbox-config", a.withInternalAdminAuth(a.adminMailboxConfig))
|
||||
mux.HandleFunc("/internal/admin/workbench/feedback-config", a.withInternalAdminAuth(a.adminFeedbackConfig))
|
||||
}
|
||||
@@ -698,11 +700,6 @@ func (a *API) dailyReportResource(w http.ResponseWriter, r *http.Request, p *aut
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !a.publishWorkbenchEvent(w, r, p, "daily_report", "daily_report.draft.saved", report.ReportID,
|
||||
[]int64{p.UserID}, "日报", report.ReportDate.Format("2006-01-02"),
|
||||
map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status}) {
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, dailyReportPayload(*report))
|
||||
case tail == "draft" && r.Method == http.MethodPut:
|
||||
var body struct {
|
||||
@@ -718,8 +715,8 @@ func (a *API) dailyReportResource(w http.ResponseWriter, r *http.Request, p *aut
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !a.publishWorkbenchEvent(w, r, p, "daily_report", "daily_report.submitted", report.ReportID,
|
||||
[]int64{p.UserID}, "日报已提交", report.ReportDate.Format("2006-01-02"),
|
||||
if !a.publishWorkbenchEvent(w, r, p, "daily_report", "daily_report.draft.saved", report.ReportID,
|
||||
[]int64{p.UserID}, "日报", report.ReportDate.Format("2006-01-02"),
|
||||
map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status}) {
|
||||
return
|
||||
}
|
||||
@@ -736,6 +733,11 @@ func (a *API) dailyReportResource(w http.ResponseWriter, r *http.Request, p *aut
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !a.publishWorkbenchEvent(w, r, p, "daily_report", "daily_report.submitted", report.ReportID,
|
||||
[]int64{p.UserID}, "日报已提交", report.ReportDate.Format("2006-01-02"),
|
||||
map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status}) {
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, dailyReportPayload(*report))
|
||||
default:
|
||||
methodNotAllowed(w, r)
|
||||
@@ -747,7 +749,17 @@ func (a *API) dailyReportStatistics(w http.ResponseWriter, r *http.Request, p *a
|
||||
methodNotAllowed(w, r)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, map[string]any{"expected_count": 0, "submitted_count": 0, "missing_count": 0, "scope": map[string]any{"department_id": r.URL.Query().Get("department_id")}})
|
||||
date, err := parseDate(firstNonEmpty(r.URL.Query().Get("date"), time.Now().Format("2006-01-02")))
|
||||
if err != nil {
|
||||
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "date"})
|
||||
return
|
||||
}
|
||||
stats, err := st.DailyReportStats(r.Context(), date, r.URL.Query().Get("department_id"))
|
||||
if err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, reportStatsPayload(stats))
|
||||
}
|
||||
|
||||
func (a *API) dailyReportReminders(w http.ResponseWriter, r *http.Request, p *auth.Principal, st *store.Store) {
|
||||
@@ -819,11 +831,6 @@ func (a *API) monthlyReportResource(w http.ResponseWriter, r *http.Request, p *a
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !a.publishWorkbenchEvent(w, r, p, "monthly_report", "monthly_report.draft.saved", report.ReportID,
|
||||
[]int64{p.UserID}, "月报", report.Month,
|
||||
map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status}) {
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, monthlyReportPayload(*report))
|
||||
case tail == "draft" && r.Method == http.MethodPut:
|
||||
var body struct {
|
||||
@@ -840,8 +847,8 @@ func (a *API) monthlyReportResource(w http.ResponseWriter, r *http.Request, p *a
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !a.publishWorkbenchEvent(w, r, p, "monthly_report", "monthly_report.submitted", report.ReportID,
|
||||
[]int64{p.UserID}, "月报已提交", report.Month,
|
||||
if !a.publishWorkbenchEvent(w, r, p, "monthly_report", "monthly_report.draft.saved", report.ReportID,
|
||||
[]int64{p.UserID}, "月报", report.Month,
|
||||
map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status}) {
|
||||
return
|
||||
}
|
||||
@@ -858,8 +865,8 @@ func (a *API) monthlyReportResource(w http.ResponseWriter, r *http.Request, p *a
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !a.publishWorkbenchEvent(w, r, p, "monthly_report", "monthly_report.reopened", report.ReportID,
|
||||
[]int64{p.UserID}, "月报已重开", report.Month,
|
||||
if !a.publishWorkbenchEvent(w, r, p, "monthly_report", "monthly_report.submitted", report.ReportID,
|
||||
[]int64{p.UserID}, "月报已提交", report.Month,
|
||||
map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status}) {
|
||||
return
|
||||
}
|
||||
@@ -876,6 +883,11 @@ func (a *API) monthlyReportResource(w http.ResponseWriter, r *http.Request, p *a
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !a.publishWorkbenchEvent(w, r, p, "monthly_report", "monthly_report.reopened", report.ReportID,
|
||||
[]int64{p.UserID}, "月报已重开", report.Month,
|
||||
map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status}) {
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, monthlyReportPayload(*report))
|
||||
default:
|
||||
methodNotAllowed(w, r)
|
||||
@@ -887,7 +899,16 @@ func (a *API) monthlyReportStatistics(w http.ResponseWriter, r *http.Request, p
|
||||
methodNotAllowed(w, r)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, map[string]any{"expected_count": 0, "submitted_count": 0, "missing_count": 0, "scope": map[string]any{"department_id": r.URL.Query().Get("department_id")}})
|
||||
month := strings.TrimSpace(r.URL.Query().Get("month"))
|
||||
if month == "" {
|
||||
month = time.Now().Format("2006-01")
|
||||
}
|
||||
stats, err := st.MonthlyReportStats(r.Context(), month, r.URL.Query().Get("department_id"))
|
||||
if err != nil {
|
||||
writeStoreError(w, r, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, r, reportStatsPayload(stats))
|
||||
}
|
||||
|
||||
func (a *API) mailboxConfig(w http.ResponseWriter, r *http.Request, p *auth.Principal, st *store.Store) {
|
||||
|
||||
@@ -417,6 +417,23 @@ func monthlyReportPayload(report store.MonthlyReport) map[string]any {
|
||||
return data
|
||||
}
|
||||
|
||||
func reportStatsPayload(stats *store.ReportStats) map[string]any {
|
||||
if stats == nil {
|
||||
return map[string]any{
|
||||
"expected_count": 0,
|
||||
"submitted_count": 0,
|
||||
"missing_count": 0,
|
||||
"scope": map[string]any{"department_id": ""},
|
||||
}
|
||||
}
|
||||
return map[string]any{
|
||||
"expected_count": stats.ExpectedCount,
|
||||
"submitted_count": stats.SubmittedCount,
|
||||
"missing_count": stats.MissingCount,
|
||||
"scope": map[string]any{"department_id": stats.DepartmentID},
|
||||
}
|
||||
}
|
||||
|
||||
func mailboxConfigPayload(config store.MailboxConfig) map[string]any {
|
||||
return map[string]any{
|
||||
"enabled": config.Enabled,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ type ReportStats struct {
|
||||
ExpectedCount int
|
||||
SubmittedCount int
|
||||
MissingCount int
|
||||
DepartmentID string
|
||||
}
|
||||
|
||||
type MailboxConfig struct {
|
||||
|
||||
Reference in New Issue
Block a user