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 }