2025-12-20 00:01:08 +07:00

235 lines
7.7 KiB
Go

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