theorose49 ed4ef537e6
All checks were successful
build-and-push / build (push) Successful in 33s
feat(calendar): 일정에 참여자(participants) — 그들 캘린더에도 표시
- CalendarEvent.Participants(JSON 이메일 배열), patch 시 JSON 변환
- 목록은 전 구성원 공유(이미) → 프론트가 소유자 OR 참여자로 필터/표시

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 16:32:02 +09:00

241 lines
10 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 (기여도, 0100) 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"` // 0100
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 }