package models import ( "fmt" "time" "gorm.io/gorm" ) // Claim represents a claim for a found item or a direct claim on a lost item type Claim struct { ID uint `gorm:"primaryKey" json:"id"` // ✅ UBAH: Jadi Pointer (*uint) agar bisa NULL ItemID *uint `json:"item_id"` // ✅ UBAH: Jadi Pointer (*Item) untuk handling relasi opsional Item *Item `gorm:"foreignKey:ItemID" json:"item,omitempty"` // ✅ BARU: Relasi ke LostItem (untuk Direct Claim) LostItemID *uint `json:"lost_item_id"` LostItem *LostItem `gorm:"foreignKey:LostItemID" json:"lost_item,omitempty"` UserID uint `gorm:"not null" json:"user_id"` // Ini adalah Finder (Penemu) User User `gorm:"foreignKey:UserID" json:"user,omitempty"` Description string `gorm:"type:text;not null" json:"description"` ProofURL string `gorm:"type:varchar(255)" json:"proof_url"` Contact string `gorm:"type:varchar(50);not null" json:"contact"` Status string `gorm:"type:varchar(50);default:'pending'" json:"status"` Notes string `gorm:"type:text" json:"notes"` VerifiedAt *time.Time `json:"verified_at"` VerifiedBy *uint `json:"verified_by"` Verifier *User `gorm:"foreignKey:VerifiedBy" json:"verifier,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // Relationships Verification *ClaimVerification `gorm:"foreignKey:ClaimID" json:"verification,omitempty"` } func (Claim) TableName() string { return "claims" } // Claim status constants const ( ClaimStatusPending = "pending" ClaimStatusApproved = "approved" ClaimStatusRejected = "rejected" ClaimStatusWaitingOwner = "waiting_owner" ClaimStatusVerified = "verified" ) func (c *Claim) BeforeCreate(tx *gorm.DB) error { if c.Status == "" { c.Status = ClaimStatusPending } return nil } // Helper methods status check func (c *Claim) IsPending() bool { return c.Status == ClaimStatusPending } func (c *Claim) IsApproved() bool { return c.Status == ClaimStatusApproved } func (c *Claim) IsRejected() bool { return c.Status == ClaimStatusRejected } func (c *Claim) IsWaitingOwner() bool { return c.Status == ClaimStatusWaitingOwner } func (c *Claim) IsVerified() bool { return c.Status == ClaimStatusVerified } // Approve approves the claim func (c *Claim) Approve(verifierID uint, notes string) { c.Status = ClaimStatusApproved c.VerifiedBy = &verifierID now := time.Now() c.VerifiedAt = &now c.Notes = notes } // Reject rejects the claim func (c *Claim) Reject(verifierID uint, notes string) { c.Status = ClaimStatusRejected c.VerifiedBy = &verifierID now := time.Now() c.VerifiedAt = &now c.Notes = notes } // ClaimResponse represents claim data for API responses type ClaimResponse struct { ID uint `json:"id"` ItemID uint `json:"item_id"` // 0 jika Direct Claim LostItemID uint `json:"lost_item_id"` // ✅ BARU: 0 jika Regular Claim LostItemUserID *uint `json:"lost_item_user_id"` ItemName string `json:"item_name"` UserID uint `json:"user_id"` UserName string `json:"user_name"` Description string `json:"description"` ItemSecretDetails string `json:"item_secret_details"` ProofURL string `json:"proof_url"` Contact string `json:"contact"` Status string `json:"status"` Notes string `json:"notes"` MatchPercentage *float64 `json:"match_percentage,omitempty"` VerifiedAt *time.Time `json:"verified_at"` VerifiedBy *uint `json:"verified_by"` VerifierName string `json:"verifier_name,omitempty"` CreatedAt time.Time `json:"created_at"` // Administrative Fields BeritaAcaraNo string `json:"berita_acara_no,omitempty"` BuktiSerahTerima string `json:"bukti_serah_terima,omitempty"` CaseClosedAt *time.Time `json:"case_closed_at,omitempty"` ReporterName string `json:"reporter_name"` CaseClosedByName string `json:"case_closed_by_name,omitempty"` Type string `json:"type"` // ✅ BARU: "regular" atau "direct" } // ToResponse converts Claim to ClaimResponse with SAFETY CHECKS func (c *Claim) ToResponse() ClaimResponse { // 1. Initialize Default Values var itemID, lostItemID uint itemName := "Unknown Item" itemSecret := "" beritaAcara := "" buktiSerahTerima := "" var caseClosedAt *time.Time caseClosedByName := "" reporterName := "" claimType := "unknown" // 2. LOGIC PENTING: Tentukan sumber data (Item vs LostItem) // KASUS A: REGULAR CLAIM (Dari Barang Temuan) if c.Item != nil { claimType = "regular" itemID = c.Item.ID itemName = c.Item.Name itemSecret = c.Item.SecretDetails // Admin details hanya ada di Item Temuan beritaAcara = c.Item.BeritaAcaraNo buktiSerahTerima = c.Item.BuktiSerahTerima caseClosedAt = c.Item.CaseClosedAt reporterName = c.Item.ReporterName if c.Item.CaseClosedBy_User != nil { caseClosedByName = c.Item.CaseClosedBy_User.Name } } else if c.LostItem != nil { // KASUS B: DIRECT CLAIM (Dari Barang Hilang) claimType = "direct" lostItemID = c.LostItem.ID itemName = fmt.Sprintf("[DICARI] %s", c.LostItem.Name) itemSecret = c.LostItem.Description // Gunakan deskripsi lost item sebagai rahasia // Untuk direct claim, ReporterName adalah si User (Finder) yang membuat claim ini if c.User.ID != 0 { reporterName = c.User.Name } } // 3. User Info (Claimant) userName := "" if c.User.ID != 0 { userName = c.User.Name } // 4. Verifier Info verifierName := "" if c.Verifier != nil { verifierName = c.Verifier.Name } // 5. Match Info var matchPercentage *float64 if c.Verification != nil { matchPercentage = &c.Verification.SimilarityScore } return ClaimResponse{ ID: c.ID, ItemID: itemID, LostItemID: lostItemID, // ✅ Field Baru ItemName: itemName, UserID: c.UserID, UserName: userName, Description: c.Description, ItemSecretDetails: itemSecret, ProofURL: c.ProofURL, Contact: c.Contact, Status: c.Status, Notes: c.Notes, MatchPercentage: matchPercentage, VerifiedAt: c.VerifiedAt, VerifiedBy: c.VerifiedBy, VerifierName: verifierName, CreatedAt: c.CreatedAt, BeritaAcaraNo: beritaAcara, BuktiSerahTerima: buktiSerahTerima, CaseClosedAt: caseClosedAt, CaseClosedByName: caseClosedByName, ReporterName: reporterName, Type: claimType, // ✅ Field Baru } } // ClaimDetailResponse includes item description for verification type ClaimDetailResponse struct { ClaimResponse ItemDescription string `json:"item_description"` ItemSecretDetails string `json:"item_secret_details"` } // ToDetailResponse converts Claim to ClaimDetailResponse func (c *Claim) ToDetailResponse() ClaimDetailResponse { baseResponse := c.ToResponse() itemDescription := "" itemSecretDetails := "" // ✅ Logic Aman: Cek Item dulu, kalau nil cek LostItem if c.Item != nil { itemDescription = c.Item.Description itemSecretDetails = c.Item.SecretDetails } else if c.LostItem != nil { itemDescription = c.LostItem.Description itemSecretDetails = c.LostItem.Description // Fallback } return ClaimDetailResponse{ ClaimResponse: baseResponse, ItemDescription: itemDescription, ItemSecretDetails: itemSecretDetails, } }