package models import ( "time" "gorm.io/datatypes" "gorm.io/gorm" ) // Payment stage kinds (계약금/중도금/잔금) and scopes (BE / non-BE). const ( StageDeposit = "deposit" // 계약금 StageMiddle = "middle" // 중도금 StageFinal = "final" // 잔금 ScopeBE = "be" // 손익분기 금액분 → 작업자 인센티브 포인트 풀 ScopeNonBE = "non_be" // BE 초과분 → 회사:파트너 분배 ) // Fix lifecycle for a user's incentive on a project stage: // 예정 → 반영중(회사 입금) → 반영완료(포인트 반영) → 지급완료(급여 지급) const ( FixPlanned = "planned" // 예정 FixApplying = "applying" // 반영중 FixApplied = "applied" // 반영완료 FixPaid = "paid" // 지급완료 ) // IncentiveConfig is the per-year rule set. Admin tunes it; once happy it gets // frozen for the year. RankQuota maps rank label → annual point quota. type IncentiveConfig struct { Base Year int `gorm:"uniqueIndex" json:"year"` PointRate float64 `json:"pointRate"` // KRW per 1 incentive point (환율) DepositPct float64 `json:"depositPct"` // 계약금 비율 (%) MiddlePct float64 `json:"middlePct"` // 중도금 비율 (%) FinalPct float64 `json:"finalPct"` // 잔금 비율 (%) NonBECompanyPct float64 `json:"nonBeCompanyPct"` // non-BE 회사 몫 (%) NonBEPartnerPct float64 `json:"nonBePartnerPct"` // non-BE 파트너 몫 (%) RankQuota datatypes.JSONMap `json:"rankQuota"` // {"주임":n,...} annual point quota Frozen bool `json:"frozen"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } func (m *IncentiveConfig) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil } // PaymentStage is a project-level stage bucket (one per kind×scope). The admin // toggles Status as money arrives ("계약금 들어옴", "중도금까지 들어옴"). type PaymentStage struct { Base ProjectID string `gorm:"index" json:"projectId"` Kind string `json:"kind"` // deposit | middle | final Scope string `json:"scope"` // be | non_be Amount float64 `json:"amount"` // KRW allocated to this bucket Pct float64 `json:"pct"` // % of scope total ExpectedDate string `json:"expectedDate"` FixedDate string `json:"fixedDate"` Status string `json:"status"` // planned | applying | applied | paid CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } func (m *PaymentStage) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil } // UserIncentive is the per (project, member, stage, scope) incentive record. It // is derived from the member's portion but fully overridable per the spec // ("특정 유저만 픽스하지 않는 상황" etc). Points = Amount × portion ÷ pointRate. type UserIncentive struct { Base ProjectID string `gorm:"index" json:"projectId"` MemberEmail string `gorm:"index" json:"memberEmail"` StageID string `gorm:"index" json:"stageId"` Kind string `json:"kind"` Scope string `json:"scope"` Year int `gorm:"index" json:"year"` Quarter int `json:"quarter"` // 1..4 settlement bucket Portion float64 `json:"portion"` Amount float64 `json:"amount"` // KRW share Points float64 `json:"points"` FixStatus string `json:"fixStatus"` // planned | applying | applied | paid Override bool `json:"override"` // manually adjusted (engine won't recompute) Memo string `json:"memo"` AppliedAt *time.Time `json:"appliedAt"` PaidAt *time.Time `json:"paidAt"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } func (m *UserIncentive) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil } // QuarterlySettlement is the 3/6/9/12월 calculation snapshot per member: how much // of their accumulated points exceed the rank quota, and the incremental payout. type QuarterlySettlement struct { Base MemberEmail string `gorm:"index" json:"memberEmail"` Year int `gorm:"index" json:"year"` Quarter int `gorm:"index" json:"quarter"` Rank string `json:"rank"` Quota float64 `json:"quota"` PointsCumul float64 `json:"pointsCumul"` // cumulative applied points to date ExcessPoints float64 `json:"excessPoints"` // cumul − quota (floored at 0) PaidPointsYTD float64 `json:"paidPointsYtd"` // already paid in prior quarters PayoutPoints float64 `json:"payoutPoints"` // excess − paidYTD (this quarter's delta) PayoutAmount float64 `json:"payoutAmount"` // KRW = payoutPoints × pointRate Fixed bool `json:"fixed"` FixedAt *time.Time `json:"fixedAt"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } func (m *QuarterlySettlement) BeforeCreate(*gorm.DB) error { m.ensureID(); return nil }