2025-11-17 12:17:44 +07:00

268 lines
7.8 KiB
Go

package services
import (
"errors"
"lost-and-found/internal/models"
"lost-and-found/internal/repositories"
"gorm.io/gorm"
)
type ClaimService struct {
db *gorm.DB // Tambahkan ini
claimRepo *repositories.ClaimRepository
itemRepo *repositories.ItemRepository
verificationRepo *repositories.ClaimVerificationRepository
notificationRepo *repositories.NotificationRepository
auditLogRepo *repositories.AuditLogRepository
}
func NewClaimService(db *gorm.DB) *ClaimService {
return &ClaimService{
db: db, // Tambahkan ini
claimRepo: repositories.NewClaimRepository(db),
itemRepo: repositories.NewItemRepository(db),
verificationRepo: repositories.NewClaimVerificationRepository(db),
notificationRepo: repositories.NewNotificationRepository(db),
auditLogRepo: repositories.NewAuditLogRepository(db),
}
}
// CreateClaimRequest represents claim creation data
type CreateClaimRequest struct {
ItemID uint `json:"item_id" binding:"required"`
Description string `json:"description" binding:"required"`
ProofURL string `json:"proof_url"`
Contact string `json:"contact" binding:"required"`
}
// VerifyClaimRequest represents claim verification data
type VerifyClaimRequest struct {
Status string `json:"status" binding:"required"` // approved or rejected
Notes string `json:"notes"`
}
// CreateClaim creates a new claim
func (s *ClaimService) CreateClaim(userID uint, req CreateClaimRequest, ipAddress, userAgent string) (*models.Claim, error) {
// Check if item exists
item, err := s.itemRepo.FindByID(req.ItemID)
if err != nil {
return nil, err
}
// Check if item can be claimed
if !item.CanBeClaimed() {
return nil, errors.New("item cannot be claimed")
}
// Check if user already claimed this item
exists, err := s.claimRepo.CheckExistingClaim(userID, req.ItemID)
if err != nil {
return nil, err
}
if exists {
return nil, errors.New("you already claimed this item")
}
// Create claim
claim := &models.Claim{
ItemID: req.ItemID,
UserID: userID,
Description: req.Description,
ProofURL: req.ProofURL,
Contact: req.Contact,
Status: models.ClaimStatusPending,
}
if err := s.claimRepo.Create(claim); err != nil {
return nil, errors.New("failed to create claim")
}
// Update item status to pending claim
s.itemRepo.UpdateStatus(req.ItemID, models.ItemStatusPendingClaim)
// Log audit
s.auditLogRepo.Log(&userID, models.ActionCreate, models.EntityClaim, &claim.ID,
"Claim created for item: "+item.Name, ipAddress, userAgent)
// Load claim with relations
return s.claimRepo.FindByID(claim.ID)
}
// GetAllClaims gets all claims
func (s *ClaimService) GetAllClaims(page, limit int, status string, itemID, userID *uint) ([]models.ClaimResponse, int64, error) {
claims, total, err := s.claimRepo.FindAll(page, limit, status, itemID, userID)
if err != nil {
return nil, 0, err
}
var responses []models.ClaimResponse
for _, claim := range claims {
responses = append(responses, claim.ToResponse())
}
return responses, total, nil
}
// GetClaimByID gets claim by ID
func (s *ClaimService) GetClaimByID(id uint, isManager bool) (interface{}, error) {
claim, err := s.claimRepo.FindByID(id)
if err != nil {
return nil, err
}
// Manager can see full details including item description
if isManager {
return claim.ToDetailResponse(), nil
}
return claim.ToResponse(), nil
}
// GetClaimsByUser gets claims by user
func (s *ClaimService) GetClaimsByUser(userID uint, page, limit int) ([]models.ClaimResponse, int64, error) {
claims, total, err := s.claimRepo.FindByUser(userID, page, limit)
if err != nil {
return nil, 0, err
}
var responses []models.ClaimResponse
for _, claim := range claims {
responses = append(responses, claim.ToResponse())
}
return responses, total, nil
}
// VerifyClaim verifies a claim (manager only)
func (s *ClaimService) VerifyClaim(managerID, claimID uint, req VerifyClaimRequest, similarityScore float64, matchedKeywords string, ipAddress, userAgent string) error {
claim, err := s.claimRepo.FindByID(claimID)
if err != nil {
return err
}
if !claim.IsPending() {
return errors.New("claim is not pending")
}
// Create or update verification record
verification, _ := s.verificationRepo.FindByClaimID(claimID)
if verification == nil {
verification = &models.ClaimVerification{
ClaimID: claimID,
SimilarityScore: similarityScore,
MatchedKeywords: matchedKeywords,
VerificationNotes: req.Notes,
IsAutoMatched: false,
}
s.verificationRepo.Create(verification)
} else {
verification.VerificationNotes = req.Notes
s.verificationRepo.Update(verification)
}
// Update claim status
if req.Status == models.ClaimStatusApproved {
claim.Approve(managerID, req.Notes)
// Update item status to verified
s.itemRepo.UpdateStatus(claim.ItemID, models.ItemStatusVerified)
// Send approval notification - PERBAIKAN DI SINI
item, _ := s.itemRepo.FindByID(claim.ItemID)
models.CreateClaimApprovedNotification(s.db, claim.UserID, item.Name, claimID)
// Log audit
s.auditLogRepo.Log(&managerID, models.ActionApprove, models.EntityClaim, &claimID,
"Claim approved", ipAddress, userAgent)
} else if req.Status == models.ClaimStatusRejected {
claim.Reject(managerID, req.Notes)
// Check if there are other pending claims for this item
otherClaims, _ := s.claimRepo.FindByItem(claim.ItemID)
hasPendingClaims := false
for _, c := range otherClaims {
if c.ID != claimID && c.IsPending() {
hasPendingClaims = true
break
}
}
// If no other pending claims, set item back to unclaimed
if !hasPendingClaims {
s.itemRepo.UpdateStatus(claim.ItemID, models.ItemStatusUnclaimed)
}
// Send rejection notification - PERBAIKAN DI SINI
item, _ := s.itemRepo.FindByID(claim.ItemID)
models.CreateClaimRejectedNotification(s.db, claim.UserID, item.Name, req.Notes, claimID)
// Log audit
s.auditLogRepo.Log(&managerID, models.ActionReject, models.EntityClaim, &claimID,
"Claim rejected: "+req.Notes, ipAddress, userAgent)
} else {
return errors.New("invalid status")
}
if err := s.claimRepo.Update(claim); err != nil {
return errors.New("failed to verify claim")
}
return nil
}
// CloseClaim closes a claim and moves item to archive (manager only)
func (s *ClaimService) CloseClaim(managerID, claimID uint, ipAddress, userAgent string) error {
claim, err := s.claimRepo.FindByID(claimID)
if err != nil {
return err
}
if !claim.IsApproved() {
return errors.New("only approved claims can be closed")
}
// Update item status to case_closed
if err := s.itemRepo.UpdateStatus(claim.ItemID, models.ItemStatusCaseClosed); err != nil {
return errors.New("failed to close case")
}
// Archive the item
item, _ := s.itemRepo.FindByID(claim.ItemID)
s.itemRepo.ArchiveItem(item, models.ArchiveReasonCaseClosed, &claim.UserID)
// Log audit
s.auditLogRepo.Log(&managerID, models.ActionUpdate, models.EntityItem, &item.ID,
"Case closed and archived", ipAddress, userAgent)
return nil
}
// DeleteClaim deletes a claim
func (s *ClaimService) DeleteClaim(userID, claimID uint, ipAddress, userAgent string) error {
claim, err := s.claimRepo.FindByID(claimID)
if err != nil {
return err
}
// Only pending claims can be deleted by users
if !claim.IsPending() && claim.UserID == userID {
return errors.New("cannot delete non-pending claim")
}
if err := s.claimRepo.Delete(claimID); err != nil {
return errors.New("failed to delete claim")
}
// Check if item should go back to unclaimed
otherClaims, _ := s.claimRepo.FindByItem(claim.ItemID)
if len(otherClaims) == 0 {
s.itemRepo.UpdateStatus(claim.ItemID, models.ItemStatusUnclaimed)
}
// Log audit
s.auditLogRepo.Log(&userID, models.ActionDelete, models.EntityClaim, &claimID,
"Claim deleted", ipAddress, userAgent)
return nil
}