All checks were successful
build-and-push / build (push) Successful in 33s
- CalendarEvent.Participants(JSON 이메일 배열), patch 시 JSON 변환 - 목록은 전 구성원 공유(이미) → 프론트가 소유자 OR 참여자로 필터/표시 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
241 lines
10 KiB
Go
241 lines
10 KiB
Go
package models
|
||
|
||
import (
|
||
"time"
|
||
|
||
"gorm.io/datatypes"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// Company → Product → Version is the consulting hierarchy. A Project is created
|
||
// per (company, product, version) engagement and is otherwise independent.
|
||
type Company struct {
|
||
Base
|
||
Name string `json:"name"`
|
||
Code string `json:"code"`
|
||
Note string `json:"note"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
}
|
||
|
||
func (m *Company) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
type Product struct {
|
||
Base
|
||
CompanyID string `gorm:"index" json:"companyId"`
|
||
Name string `json:"name"`
|
||
Code string `json:"code"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
}
|
||
|
||
func (m *Product) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
type Version struct {
|
||
Base
|
||
ProductID string `gorm:"index" json:"productId"`
|
||
Label string `json:"label"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
}
|
||
|
||
func (m *Version) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// Project is the core engagement record. The bracketed [admin-only] fields in the
|
||
// spec live on Contract / PaymentSplit, not here, so Project is safe to expose to
|
||
// any member who belongs to it.
|
||
type Project struct {
|
||
Base
|
||
Name string `json:"name"`
|
||
CompanyID string `gorm:"index" json:"companyId"`
|
||
ProductID string `json:"productId"`
|
||
VersionID string `json:"versionId"`
|
||
CompanyName string `json:"companyName"` // denormalized 업체명 snapshot
|
||
ProductName string `json:"productName"`
|
||
VersionName string `json:"versionName"`
|
||
ConsultingType string `json:"consultingType"` // 컨설팅 종류
|
||
Country string `json:"country"` // 제출 국가
|
||
// 계약 범위: 글/그림 각각 자유 입력(무엇을 포함하는지 텍스트로 기술).
|
||
ScopeText string `json:"scopeText"` // 글 계약 범위
|
||
ScopeGraphic string `json:"scopeGraphic"` // 그림 계약 범위
|
||
PMEmail string `json:"pmEmail"` // 프로젝트 PM
|
||
ClientDomain string `json:"clientDomain"` // 고객사 메일 도메인(postfix) — 관련 메일 집계용
|
||
Cautions string `json:"cautions"` // 주의사항 (구성원 공개)
|
||
Status string `json:"status"` // planned | active | hold | done | dropped
|
||
StartDate string `json:"startDate"`
|
||
DueDate string `json:"dueDate"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
UpdatedAt time.Time `json:"updatedAt"`
|
||
}
|
||
|
||
func (m *Project) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// ProjectMember links a Member to a Project with a portion (기여도, 0–100) that
|
||
// drives incentive distribution. Role is a free label (작업자/리뷰어/...).
|
||
type ProjectMember struct {
|
||
Base
|
||
ProjectID string `gorm:"index" json:"projectId"`
|
||
MemberEmail string `gorm:"index" json:"memberEmail"`
|
||
Portion float64 `json:"portion"` // 기여도 percent
|
||
Role string `json:"role"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
}
|
||
|
||
func (m *ProjectMember) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// ClientContact is a 업체 담당자 (직무/이름/연락처).
|
||
type ClientContact struct {
|
||
Base
|
||
ProjectID string `gorm:"index" json:"projectId"`
|
||
Name string `json:"name"`
|
||
Title string `json:"title"` // 직무
|
||
Phone string `json:"phone"`
|
||
Email string `json:"email"`
|
||
}
|
||
|
||
func (m *ClientContact) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// ProjectTask feeds both the Gantt and Kanban views and matches the real
|
||
// calendar via Start/End (YYYY-MM-DD). Lane is the Kanban column.
|
||
type ProjectTask struct {
|
||
Base
|
||
ProjectID string `gorm:"index" json:"projectId"`
|
||
Title string `json:"title"`
|
||
Description string `json:"description"` // 상세 설명 (JIRA형 카드 본문)
|
||
Lane string `json:"lane"` // todo | doing | review | done
|
||
Priority string `json:"priority"` // low | medium | high | urgent
|
||
Labels datatypes.JSONSlice[string] `json:"labels"` // 라벨/태그
|
||
Start string `json:"start"` // YYYY-MM-DD
|
||
End string `json:"end"`
|
||
Assignee string `json:"assignee"`
|
||
OrderIdx int `json:"orderIdx"`
|
||
Progress int `json:"progress"` // 0–100
|
||
DependsOn datatypes.JSONSlice[string] `json:"dependsOn"`
|
||
Color string `json:"color"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
UpdatedAt time.Time `json:"updatedAt"`
|
||
}
|
||
|
||
func (m *ProjectTask) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// TaskComment is a JIRA-style comment thread on a task (작성자·본문·시각).
|
||
type TaskComment struct {
|
||
Base
|
||
TaskID string `gorm:"index" json:"taskId"`
|
||
AuthorEmail string `json:"authorEmail"`
|
||
Body string `json:"body"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
}
|
||
|
||
func (m *TaskComment) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// MailNote is a project's SHARED memo on one client-domain email — any project
|
||
// member can edit it (협업 메모). Keyed by (project, RFC822 Message-ID).
|
||
type MailNote struct {
|
||
Base
|
||
ProjectID string `gorm:"index:idx_mailnote_pm,unique" json:"projectId"`
|
||
MessageID string `gorm:"index:idx_mailnote_pm,unique" json:"messageId"`
|
||
Body string `json:"body"`
|
||
LastEditedBy string `json:"lastEditedBy"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
UpdatedAt time.Time `json:"updatedAt"`
|
||
}
|
||
|
||
func (m *MailNote) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// ProjectMailMsg is a stored mail header for a project's client domain (본문 미저장,
|
||
// 헤더/스니펫만). 동기화 시 (project, Message-ID)로 upsert. Hidden=프로젝트에서 숨김.
|
||
type ProjectMailMsg struct {
|
||
Base
|
||
ProjectID string `gorm:"index:idx_pmm,unique" json:"projectId"`
|
||
MessageID string `gorm:"index:idx_pmm,unique" json:"messageId"`
|
||
ThreadID string `json:"threadId"`
|
||
FromAddr string `json:"from"`
|
||
ToAddr string `json:"to"`
|
||
CcAddr string `json:"cc"`
|
||
Subject string `json:"subject"`
|
||
Snippet string `json:"snippet"`
|
||
Summary string `json:"summary"` // AI 자동 요약(한 줄)
|
||
DateHdr string `json:"date"`
|
||
Mailbox string `json:"mailbox"`
|
||
TS int64 `gorm:"index" json:"ts"`
|
||
Hidden bool `json:"hidden"`
|
||
HiddenBy string `json:"hiddenBy"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
UpdatedAt time.Time `json:"updatedAt"`
|
||
}
|
||
|
||
func (m *ProjectMailMsg) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// ProjectMailState tracks the last mail sync per project.
|
||
type ProjectMailState struct {
|
||
ProjectID string `gorm:"primaryKey" json:"projectId"`
|
||
LastSyncedAt *time.Time `json:"lastSyncedAt"`
|
||
LastError string `json:"lastError"`
|
||
Count int `json:"count"`
|
||
}
|
||
|
||
// CalendarEvent is a personal calendar entry tagged by category(프로젝트/기타/개인)
|
||
// with a color. Owned by one member.
|
||
type CalendarEvent struct {
|
||
Base
|
||
OwnerEmail string `gorm:"index" json:"ownerEmail"`
|
||
Title string `json:"title"`
|
||
Category string `json:"category"` // project | etc | personal (분류 키)
|
||
ProjectID string `json:"projectId"` // category=project일 때 연결
|
||
Color string `json:"color"` // 표시 색
|
||
Start string `json:"start"` // YYYY-MM-DD
|
||
End string `json:"end"` // YYYY-MM-DD (빈값=하루)
|
||
AllDay bool `json:"allDay"`
|
||
Memo string `json:"memo"`
|
||
Participants datatypes.JSONSlice[string] `json:"participants"` // 참여자 이메일 — 그들 캘린더에도 표시
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
UpdatedAt time.Time `json:"updatedAt"`
|
||
}
|
||
|
||
func (m *CalendarEvent) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// Contract holds the [admin-only] commercial terms of a project. BE is the
|
||
// break-even floor (손해가 안 나는 최소 금액). Exposed ONLY to admins.
|
||
type Contract struct {
|
||
Base
|
||
ProjectID string `gorm:"uniqueIndex" json:"projectId"`
|
||
TotalAmount float64 `json:"totalAmount"` // 계약 금액
|
||
BEAmount float64 `json:"beAmount"` // BE (break-even 최소 금액)
|
||
AdminCaution string `json:"adminCaution"` // 관리자 주의사항
|
||
Memo string `json:"memo"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
UpdatedAt time.Time `json:"updatedAt"`
|
||
}
|
||
|
||
func (m *Contract) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// ContractFile is an [admin-only] attachment (계약서/기타 자료, 여러개 가능) in S3.
|
||
type ContractFile struct {
|
||
Base
|
||
ProjectID string `gorm:"index" json:"projectId"`
|
||
Kind string `json:"kind"` // contract | other
|
||
Filename string `json:"filename"`
|
||
S3Key string `json:"s3Key"`
|
||
Size int64 `json:"size"`
|
||
UploadedBy string `json:"uploadedBy"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
}
|
||
|
||
func (m *ContractFile) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|
||
|
||
// PaymentSplit is one [admin-only] custom installment of the contract amount.
|
||
// Amounts arrive in arbitrary splits across dates, so every field is editable.
|
||
type PaymentSplit struct {
|
||
Base
|
||
ProjectID string `gorm:"index" json:"projectId"`
|
||
Label string `json:"label"`
|
||
Amount float64 `json:"amount"`
|
||
ExpectedDate string `json:"expectedDate"` // 예상 일정
|
||
PaidDate string `json:"paidDate"` // 실제 입금일 (빈값=미입금)
|
||
Paid bool `json:"paid"`
|
||
Memo string `json:"memo"`
|
||
OrderIdx int `json:"orderIdx"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
UpdatedAt time.Time `json:"updatedAt"`
|
||
}
|
||
|
||
func (m *PaymentSplit) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }
|