All checks were successful
build-and-push / build (push) Successful in 39s
- config/db/storage/auth/router/perms: eQMS 규약 미러링, 권한 2-tier (관리자 전체 / 구성원 본인·신청만), oauth2-proxy 헤더 인증 + DEV_AUTH mock - 모델: 구성원/부서, 근무(출퇴근·휴가·공가·초과), 프로젝트(회사/제품/버전· 작업자portion·담당자·태스크·계약·첨부·분할입금), 인센티브(설정·단계· 유저배분·분기정산), 회계(거래·세금) - internal/worktime: 근로기준법 월 집계 엔진 - internal/incentive: BE/non-BE × 계약금/중도금/잔금 3단계 계산 + 시뮬레이션 - 시드 데이터, Go 멀티스테이지 Dockerfile - ADMIN_GROUPS 기본값 'admin' (전 내부 앱 공통 그룹) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
60 lines
2.2 KiB
Go
60 lines
2.2 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"spin/internal/models"
|
|
)
|
|
|
|
// handleDashboard returns a role-tailored summary. Members get their own work /
|
|
// incentive / project snapshot; admins additionally get company-wide widgets.
|
|
func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
|
email := s.email(r)
|
|
year := time.Now().Year()
|
|
out := map[string]interface{}{"isAdmin": s.isAdmin(r), "year": year}
|
|
|
|
// my projects count
|
|
out["myProjects"] = len(s.myProjectIDs(email))
|
|
|
|
// my applied incentive points this year
|
|
var myPoints float64
|
|
s.db.Model(&models.UserIncentive{}).
|
|
Where("lower(member_email) = ? AND year = ? AND (fix_status = ? OR fix_status = ?)",
|
|
email, year, models.FixApplied, models.FixPaid).
|
|
Select("COALESCE(SUM(points),0)").Scan(&myPoints)
|
|
out["myPoints"] = myPoints
|
|
|
|
// my pending requests
|
|
var myPending int64
|
|
s.db.Model(&models.LeaveRequest{}).Where("lower(member_email) = ? AND status = ?", email, models.StatusPending).Count(&myPending)
|
|
out["myPendingRequests"] = myPending
|
|
|
|
if s.isAdmin(r) {
|
|
var pendingLeave, pendingOT, activeProjects int64
|
|
s.db.Model(&models.LeaveRequest{}).Where("status = ?", models.StatusPending).Count(&pendingLeave)
|
|
s.db.Model(&models.OvertimeRequest{}).Where("status = ?", models.StatusPending).Count(&pendingOT)
|
|
s.db.Model(&models.Project{}).Where("status = ?", "active").Count(&activeProjects)
|
|
out["pendingApprovals"] = pendingLeave + pendingOT
|
|
out["activeProjects"] = activeProjects
|
|
|
|
// cash position this year
|
|
var cashIn, cashOut float64
|
|
yr := strconv.Itoa(year)
|
|
s.db.Model(&models.Transaction{}).Where("date LIKE ? AND kind = ?", yr+"%", models.TxnIncome).
|
|
Select("COALESCE(SUM(amount),0)").Scan(&cashIn)
|
|
s.db.Model(&models.Transaction{}).Where("date LIKE ? AND kind <> ?", yr+"%", models.TxnIncome).
|
|
Select("COALESCE(SUM(ABS(amount)),0)").Scan(&cashOut)
|
|
out["cashIn"] = cashIn
|
|
out["cashOut"] = cashOut
|
|
out["cashNet"] = cashIn - cashOut
|
|
|
|
// upcoming payment splits (expected, unpaid)
|
|
var upcoming []models.PaymentSplit
|
|
s.db.Where("paid = ? AND expected_date <> ''", false).Order("expected_date asc").Limit(8).Find(&upcoming)
|
|
out["upcomingPayments"] = upcoming
|
|
}
|
|
writeJSON(w, http.StatusOK, out)
|
|
}
|