spin-backend/internal/httpapi/handlers_dashboard.go
theorose49 f83724b995
All checks were successful
build-and-push / build (push) Successful in 39s
feat: spin 백엔드 전체 구현 (근무·프로젝트·인센티브·회계)
- 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>
2026-06-28 08:57:35 +09:00

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)
}