// internal/services/claim_service.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 }