235 lines
7.7 KiB
Go
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,
|
|
}
|
|
} |