All checks were successful
build-and-push / build (push) Successful in 33s
- 알림(Notification) 모델/이벤트 발행(프로젝트 추가·휴가/초과근무 승인·인센티브 반영/지급·정산 확정) + 메일함 API - 근무상태 기록(WorkStatusEvent: 출근/퇴근/휴식/미팅/이동), 출퇴근은 Attendance도 갱신 - 남은 연차(소수점) 엔드포인트, 관리자 근무관리용 집계/로그 조회 - 프로필 사진(Member.AvatarKey) 업로드/스트리밍 - Keycloak 최초 로그인 자동 Member 프로비저닝(ensureMember, rank/부서 nullable) - 프로젝트 scope=mine(나의 업무는 관리자도 본인 참여분만), nav에 메일함·근무관리·프로젝트관리·내프로필 추가 - 운영 안전: SEED 기본값 false(로컬만 SEED=true), ADMIN_GROUPS 기본 'admin' Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
3.9 KiB
Go
102 lines
3.9 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
|
|
AvatarKey string `json:"avatarKey"` // S3 key of profile photo (empty = none)
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// HasAvatar reports whether a profile photo is set.
|
|
func (m *Member) HasAvatar() bool { return m.AvatarKey != "" }
|
|
|
|
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 }
|
|
|
|
// Notification is a per-member inbox event (프로젝트 추가·휴가 승인·인센티브 반영 등).
|
|
type Notification struct {
|
|
Base
|
|
Recipient string `gorm:"index" json:"recipient"` // member email
|
|
Type string `json:"type"` // project | leave | overtime | incentive | settlement
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
Link string `json:"link"` // in-app path, e.g. /projects/{id}
|
|
Read bool `gorm:"index" json:"read"`
|
|
CreatedAt time.Time `gorm:"index" json:"createdAt"`
|
|
}
|
|
|
|
func (m *Notification) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
|
|
|
// WorkStatusEvent logs a member's presence change (출근/퇴근/휴식/미팅/이동) for the
|
|
// admin work-management timeline. 출근/퇴근 additionally update the Attendance row.
|
|
type WorkStatusEvent struct {
|
|
Base
|
|
MemberEmail string `gorm:"index" json:"memberEmail"`
|
|
Date string `gorm:"index" json:"date"` // YYYY-MM-DD (KST)
|
|
Status string `json:"status"` // in|out|break|meeting|move
|
|
At time.Time `json:"at"`
|
|
Note string `json:"note"`
|
|
}
|
|
|
|
func (m *WorkStatusEvent) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|