269 lines
7.8 KiB
Go
269 lines
7.8 KiB
Go
// 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
|
|
} |