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 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 } // 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 }