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

71 lines
2.5 KiB
Go

package models
import (
"time"
"gorm.io/gorm"
)
// Rank is the career grade that drives incentive point quotas.
// 주임(junior) · 선임(senior) · 책임(lead) · 파트너(partner)
// Stored as the Korean label so it round-trips to the UI directly.
const (
RankJunior = "주임"
RankSenior = "선임"
RankLead = "책임"
RankPartner = "파트너"
)
// Member roles within spin. Account lifecycle (create/disable) is Keycloak's
// job; spin only distinguishes admin from regular member for authorization.
const (
RoleAdmin = "admin"
RoleMember = "user"
)
// Member is a company employee who uses spin, matched to the logged-in Keycloak
// identity by email (case-insensitive). It carries the org/HR profile spin needs
// (rank, department, partner flag) that Keycloak does not hold.
type Member struct {
Base
Email string `gorm:"index" json:"email"`
DisplayName string `json:"displayName"`
Rank string `json:"rank"` // 주임/선임/책임/파트너
DepartmentID *string `json:"departmentId"`
Role string `json:"role"` // admin | user
IsPartner bool `json:"isPartner"` // shares non-BE profit pool
Phone string `json:"phone"`
Position string `json:"position"` // free-text job title
Status string `json:"status"` // active | inactive
JoinDate *time.Time `json:"joinDate"`
AnnualLeave float64 `json:"annualLeave"` // granted 연차 days for the year
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (m *Member) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
// Department is an org unit. Lightweight; the lead is a Member email.
type Department struct {
Base
Name string `json:"name"`
LeadEmail string `json:"leadEmail"`
CreatedAt time.Time `json:"createdAt"`
}
func (m *Department) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
// AuditLog records sensitive actions (approvals, incentive fixes, contract edits)
// for the admin trail. Entity/EntityID point at the affected row.
type AuditLog struct {
Base
Actor string `gorm:"index" json:"actor"`
Action string `json:"action"`
Entity string `gorm:"index" json:"entity"`
EntityID string `gorm:"index" json:"entityId"`
Detail string `json:"detail"`
CreatedAt time.Time `gorm:"index" json:"createdAt"`
}
func (m *AuditLog) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }