// internal/models/item.go - FIXED VERSION dengan SecretDetails package models import ( "time" "gorm.io/gorm" ) // Item represents a found item type Item struct { ID uint `gorm:"primaryKey" json:"id"` Name string `gorm:"type:varchar(100);not null" json:"name"` CategoryID uint `gorm:"not null" json:"category_id"` Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"` PhotoURL string `gorm:"type:varchar(255)" json:"photo_url"` Location string `gorm:"type:varchar(200);not null" json:"location"` Description string `gorm:"type:text;not null" json:"description"` SecretDetails string `gorm:"type:text;column:secret_details" json:"secret_details,omitempty"` DateFound time.Time `gorm:"not null" json:"date_found"` Status string `gorm:"type:varchar(50);default:'unclaimed'" json:"status"` ReporterID uint `gorm:"not null" json:"reporter_id"` Reporter User `gorm:"foreignKey:ReporterID" json:"reporter,omitempty"` ReporterName string `gorm:"type:varchar(100);not null" json:"reporter_name"` ReporterContact string `gorm:"type:varchar(50);not null" json:"reporter_contact"` ExpiresAt *time.Time `json:"expires_at"` // ✅ NEW FIELDS FOR CASE CLOSE BeritaAcaraNo string `gorm:"type:varchar(100)" json:"berita_acara_no"` BuktiSerahTerima string `gorm:"type:varchar(255)" json:"bukti_serah_terima"` CaseClosedAt *time.Time `json:"case_closed_at"` CaseClosedBy *uint `json:"case_closed_by"` CaseClosedBy_User *User `gorm:"foreignKey:CaseClosedBy" json:"case_closed_by_user,omitempty"` CaseClosedNotes string `gorm:"type:text" json:"case_closed_notes"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // Relationships Claims []Claim `gorm:"foreignKey:ItemID" json:"claims,omitempty"` MatchResults []MatchResult `gorm:"foreignKey:ItemID" json:"match_results,omitempty"` RevisionLogs []RevisionLog `gorm:"foreignKey:ItemID" json:"revision_logs,omitempty"` } // TableName specifies the table name for Item model func (Item) TableName() string { return "items" } // Status constants const ( ItemStatusUnclaimed = "unclaimed" ItemStatusPendingClaim = "pending_claim" ItemStatusVerified = "verified" ItemStatusCaseClosed = "case_closed" ItemStatusExpired = "expired" ) // BeforeCreate hook to set expiration date func (i *Item) BeforeCreate(tx *gorm.DB) error { if i.Status == "" { i.Status = ItemStatusUnclaimed } if i.ExpiresAt == nil { expiresAt := i.DateFound.AddDate(0, 0, 90) i.ExpiresAt = &expiresAt } return nil } // IsExpired checks if item has expired func (i *Item) IsExpired() bool { if i.ExpiresAt == nil { return false } return time.Now().After(*i.ExpiresAt) } // CanBeClaimed checks if item can be claimed func (i *Item) CanBeClaimed() bool { return i.Status == ItemStatusUnclaimed && !i.IsExpired() } // CanBeEdited checks if item can be edited func (i *Item) CanBeEdited() bool { return i.Status != ItemStatusCaseClosed && i.Status != ItemStatusExpired } // ItemPublicResponse represents item data for public view (without sensitive info) type ItemPublicResponse struct { ID uint `json:"id"` Name string `json:"name"` Category string `json:"category"` PhotoURL string `json:"photo_url"` Location string `json:"location"` DateFound time.Time `json:"date_found"` Status string `json:"status"` ReporterID uint `json:"reporter_id"` CreatedAt time.Time `json:"created_at"` } func (i *Item) GetDisplayStatus() string { // Jika case closed atau expired, return as-is if i.Status == ItemStatusCaseClosed || i.Status == ItemStatusExpired { return i.Status } // ✅ LOGIKA BARU: Check claims if len(i.Claims) > 0 { // Check apakah ada claim yang approved/completed for _, claim := range i.Claims { if claim.Status == ClaimStatusApproved || claim.Status == ClaimStatusVerified { return ItemStatusVerified // atau "completed" } if claim.Status == ClaimStatusPending || claim.Status == ClaimStatusWaitingOwner { return ItemStatusPendingClaim // "Sedang Diklaim" } } } // Default: return status dari database return i.Status } // ToPublicResponse converts Item to ItemPublicResponse (hides description, reporter details) func (i *Item) ToPublicResponse() ItemPublicResponse { categoryName := "" if i.Category.ID != 0 { categoryName = i.Category.Name } return ItemPublicResponse{ ID: i.ID, Name: i.Name, Category: categoryName, PhotoURL: i.PhotoURL, Location: i.Location, DateFound: i.DateFound, Status: i.GetDisplayStatus(), // ✅ GANTI JADI GetDisplayStatus() ReporterID: i.ReporterID, CreatedAt: i.CreatedAt, } } type ItemDetailResponse struct { ID uint `json:"id"` Name string `json:"name"` Category string `json:"category"` PhotoURL string `json:"photo_url"` Location string `json:"location"` Description string `json:"description"` SecretDetails string `json:"secret_details"` DateFound time.Time `json:"date_found"` Status string `json:"status"` ReporterName string `json:"reporter_name"` ReporterContact string `json:"reporter_contact"` ExpiresAt *time.Time `json:"expires_at"` // ✅ NEW FIELDS BeritaAcaraNo string `json:"berita_acara_no,omitempty"` BuktiSerahTerima string `json:"bukti_serah_terima,omitempty"` CaseClosedAt *time.Time `json:"case_closed_at,omitempty"` CaseClosedByName string `json:"case_closed_by_name,omitempty"` CaseClosedNotes string `json:"case_closed_notes,omitempty"` CreatedAt time.Time `json:"created_at"` } // ✅ FIXED ToDetailResponse - MAPPING LENGKAP func (i *Item) ToDetailResponse() ItemDetailResponse { categoryName := "" if i.Category.ID != 0 { categoryName = i.Category.Name } reporterName := i.ReporterName if reporterName == "" && i.Reporter.ID != 0 { reporterName = i.Reporter.Name } reporterContact := i.ReporterContact if reporterContact == "" && i.Reporter.ID != 0 { reporterContact = i.Reporter.Phone } caseClosedByName := "" if i.CaseClosedBy_User != nil && i.CaseClosedBy_User.ID != 0 { caseClosedByName = i.CaseClosedBy_User.Name } return ItemDetailResponse{ ID: i.ID, Name: i.Name, Category: categoryName, PhotoURL: i.PhotoURL, Location: i.Location, Description: i.Description, SecretDetails: i.SecretDetails, DateFound: i.DateFound, Status: i.GetDisplayStatus(), // ✅ GANTI JADI GetDisplayStatus() ReporterName: reporterName, ReporterContact: reporterContact, ExpiresAt: i.ExpiresAt, BeritaAcaraNo: i.BeritaAcaraNo, BuktiSerahTerima: i.BuktiSerahTerima, CaseClosedAt: i.CaseClosedAt, CaseClosedByName: caseClosedByName, CaseClosedNotes: i.CaseClosedNotes, CreatedAt: i.CreatedAt, } }