diff --git a/src/main/java/com/crimeLink/analyzer/controller/BulletController.java b/src/main/java/com/crimeLink/analyzer/controller/BulletController.java new file mode 100644 index 0000000..7f90c22 --- /dev/null +++ b/src/main/java/com/crimeLink/analyzer/controller/BulletController.java @@ -0,0 +1,97 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.dto.BulletAddDTO; +import com.crimeLink.analyzer.dto.BulletResponseDTO; +import com.crimeLink.analyzer.dto.BulletUpdateDTO; +import com.crimeLink.analyzer.entity.Bullet; +import com.crimeLink.analyzer.service.BulletService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/bullet") +@RequiredArgsConstructor +@CrossOrigin(origins = "*") +public class BulletController { + + private final BulletService bulletService; + + @PostMapping("/add-bullet") + public ResponseEntity addBullet(@RequestBody BulletAddDTO dto) { + try { + Bullet bullet = bulletService.addBullet(dto); + return ResponseEntity.status(HttpStatus.CREATED).body(bullet); + } catch (RuntimeException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(createErrorResponse(e.getMessage())); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(createErrorResponse("An unexpected error occurred")); + } + } + + @PutMapping("/bullet-update/{bulletId}") + public ResponseEntity updateBullet( + @PathVariable Integer bulletId, + @RequestBody BulletUpdateDTO dto) { + try { + Bullet bullet = bulletService.updateBullet(bulletId, dto); + return ResponseEntity.ok(bullet); + } catch (RuntimeException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(createErrorResponse(e.getMessage())); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(createErrorResponse("An unexpected error occurred")); + } + } + + @GetMapping("/all") + public ResponseEntity getAllBullets() { + try { + List bullets = bulletService.getAllBullets(); + return ResponseEntity.ok(bullets); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(createErrorResponse("Failed to fetch bullets")); + } + } + + @GetMapping("/all-with-details") + public ResponseEntity getAllBulletsWithDetails() { + try { + List bullets = bulletService.getAllBulletsWithDetails(); + return ResponseEntity.ok(bullets); + } catch (Exception e) { + e.printStackTrace(); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(createErrorResponse("Failed to fetch bullets with details: " + e.getMessage())); + } + } + + @GetMapping("/{bulletId}") + public ResponseEntity getBulletById(@PathVariable Integer bulletId) { + try { + Bullet bullet = bulletService.getBulletById(bulletId); + return ResponseEntity.ok(bullet); + } catch (RuntimeException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(createErrorResponse(e.getMessage())); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(createErrorResponse("Failed to fetch bullet")); + } + } + + private Map createErrorResponse(String message) { + Map response = new HashMap<>(); + response.put("error", message); + return response; + } +} \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/controller/UserController.java b/src/main/java/com/crimeLink/analyzer/controller/UserController.java index 48f83cb..ab64e04 100644 --- a/src/main/java/com/crimeLink/analyzer/controller/UserController.java +++ b/src/main/java/com/crimeLink/analyzer/controller/UserController.java @@ -1,10 +1,7 @@ package com.crimeLink.analyzer.controller; - import com.crimeLink.analyzer.entity.User; import com.crimeLink.analyzer.service.UserService; -import com.crimeLink.analyzer.service.WeaponIssueService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -19,11 +16,13 @@ public UserController(UserService service) { this.service = service; } - @GetMapping("/field-officers") public List getFieldOfficers() { return service.getFieldOfficers(); } + @GetMapping("/all-officers") + public List getAllOfficers() { + return service.getAllOfficers(); + } } - diff --git a/src/main/java/com/crimeLink/analyzer/dto/BulletAddDTO.java b/src/main/java/com/crimeLink/analyzer/dto/BulletAddDTO.java new file mode 100644 index 0000000..85e1048 --- /dev/null +++ b/src/main/java/com/crimeLink/analyzer/dto/BulletAddDTO.java @@ -0,0 +1,10 @@ +package com.crimeLink.analyzer.dto; + +import lombok.Data; + +@Data +public class BulletAddDTO { + private String bulletType; + private Integer numberOfMagazines; + private String remarks; +} \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/dto/BulletResponseDTO.java b/src/main/java/com/crimeLink/analyzer/dto/BulletResponseDTO.java new file mode 100644 index 0000000..fe289ab --- /dev/null +++ b/src/main/java/com/crimeLink/analyzer/dto/BulletResponseDTO.java @@ -0,0 +1,16 @@ +package com.crimeLink.analyzer.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BulletResponseDTO { + private Integer bulletId; + private String bulletType; + private Integer numberOfMagazines; + private String remarks; + private String registerDate; +} \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/dto/BulletUpdateDTO.java b/src/main/java/com/crimeLink/analyzer/dto/BulletUpdateDTO.java new file mode 100644 index 0000000..6f63ab5 --- /dev/null +++ b/src/main/java/com/crimeLink/analyzer/dto/BulletUpdateDTO.java @@ -0,0 +1,10 @@ +package com.crimeLink.analyzer.dto; + +import lombok.Data; + +@Data +public class BulletUpdateDTO { + private String bulletType; + private Integer numberOfMagazines; + private String remarks; +} \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/dto/IssueWeaponRequestDTO.java b/src/main/java/com/crimeLink/analyzer/dto/IssueWeaponRequestDTO.java index 896d5d8..a70650f 100644 --- a/src/main/java/com/crimeLink/analyzer/dto/IssueWeaponRequestDTO.java +++ b/src/main/java/com/crimeLink/analyzer/dto/IssueWeaponRequestDTO.java @@ -10,4 +10,9 @@ public class IssueWeaponRequestDTO { private Integer handedOverById; private LocalDate dueDate; private String issueNote; + + // Bullet details (optional - can issue weapon without bullets) + private String bulletType; + private Integer numberOfMagazines; + private String bulletRemarks; } \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/dto/ReturnWeaponRequestDTO.java b/src/main/java/com/crimeLink/analyzer/dto/ReturnWeaponRequestDTO.java index d377065..c0b9400 100644 --- a/src/main/java/com/crimeLink/analyzer/dto/ReturnWeaponRequestDTO.java +++ b/src/main/java/com/crimeLink/analyzer/dto/ReturnWeaponRequestDTO.java @@ -7,4 +7,10 @@ public class ReturnWeaponRequestDTO { private String weaponSerial; private Integer receivedByUserId; private String returnNote; + + // Bullet return details (optional) + private Integer returnedMagazines; + private Integer usedBullets; + private String bulletCondition; + private String bulletRemarks; } \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/dto/WeaponResponseDTO.java b/src/main/java/com/crimeLink/analyzer/dto/WeaponResponseDTO.java index c1bea33..6e3b831 100644 --- a/src/main/java/com/crimeLink/analyzer/dto/WeaponResponseDTO.java +++ b/src/main/java/com/crimeLink/analyzer/dto/WeaponResponseDTO.java @@ -19,4 +19,8 @@ public class WeaponResponseDTO { private String issuedDate; private String dueDate; private String issueNote; + + // Bullet details (if issued with bullets) + private String issuedBulletType; + private Integer issuedMagazines; } \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/dto/WeaponReturnResponseDTO.java b/src/main/java/com/crimeLink/analyzer/dto/WeaponReturnResponseDTO.java deleted file mode 100644 index c0fd919..0000000 --- a/src/main/java/com/crimeLink/analyzer/dto/WeaponReturnResponseDTO.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.crimeLink.analyzer.dto; - -import java.time.LocalDate; -import java.time.LocalTime; - -public class WeaponReturnResponseDTO { - - // Weapon - private String weaponSerial; - private String weaponType; - - // Issued officer - private Integer issuedToId; - private String issuedToName; - private String issuedToBadge; - private String issuedToRole; - - // Received officer - private Integer receivedById; - private String receivedByName; - private String receivedByBadge; - - private LocalDate dueDate; - private LocalDate returnedDate; - private LocalTime returnedTime; - - private String returnNote; - private String status; -} diff --git a/src/main/java/com/crimeLink/analyzer/entity/Bullet.java b/src/main/java/com/crimeLink/analyzer/entity/Bullet.java new file mode 100644 index 0000000..6c19905 --- /dev/null +++ b/src/main/java/com/crimeLink/analyzer/entity/Bullet.java @@ -0,0 +1,47 @@ +package com.crimeLink.analyzer.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "bullets") +@Data +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class Bullet { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "bullet_id") + private Integer bulletId; + + @Column(name = "bullet_type", nullable = false) + private String bulletType; + + @Column(name = "number_of_magazines", nullable = false) + private Integer numberOfMagazines; + + @Column(name = "register_date", updatable = false) + private LocalDateTime registerDate; + + @Column(name = "updated_date") + private LocalDateTime updatedDate; + + @Column(name = "remarks") + private String remarks; + + @PrePersist + void onCreate() { + registerDate = LocalDateTime.now(); + updatedDate = LocalDateTime.now(); + } + + @PreUpdate + void onUpdate() { + updatedDate = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/entity/WeaponIssue.java b/src/main/java/com/crimeLink/analyzer/entity/WeaponIssue.java index 87b5381..6e39ce8 100644 --- a/src/main/java/com/crimeLink/analyzer/entity/WeaponIssue.java +++ b/src/main/java/com/crimeLink/analyzer/entity/WeaponIssue.java @@ -6,7 +6,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; + @Entity @Table(name = "weapon_issues") @@ -48,4 +48,24 @@ public class WeaponIssue { @Enumerated(EnumType.STRING) private WeaponStatus status; + + + // ===== INTEGRATED BULLET TRACKING ===== + @Column(name = "bullet_type") + private String bulletType; + + @Column(name = "issued_magazines") + private Integer issuedMagazines; + + @Column(name = "returned_magazines") + private Integer returnedMagazines; + + @Column(name = "used_bullets") + private Integer usedBullets; + + @Column(name = "bullet_condition") + private String bulletCondition; + + @Column(name = "bullet_remarks") + private String bulletRemarks; } \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/repository/BulletRepository.java b/src/main/java/com/crimeLink/analyzer/repository/BulletRepository.java new file mode 100644 index 0000000..79a8371 --- /dev/null +++ b/src/main/java/com/crimeLink/analyzer/repository/BulletRepository.java @@ -0,0 +1,15 @@ +package com.crimeLink.analyzer.repository; + +import com.crimeLink.analyzer.entity.Bullet; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface BulletRepository extends JpaRepository { + + Optional findByBulletType(String bulletType); + + boolean existsByBulletTypeIgnoreCase(String bulletType); +} diff --git a/src/main/java/com/crimeLink/analyzer/service/BulletService.java b/src/main/java/com/crimeLink/analyzer/service/BulletService.java new file mode 100644 index 0000000..7ad5db6 --- /dev/null +++ b/src/main/java/com/crimeLink/analyzer/service/BulletService.java @@ -0,0 +1,16 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.dto.BulletAddDTO; +import com.crimeLink.analyzer.dto.BulletResponseDTO; +import com.crimeLink.analyzer.dto.BulletUpdateDTO; +import com.crimeLink.analyzer.entity.Bullet; + +import java.util.List; + +public interface BulletService { + Bullet addBullet(BulletAddDTO dto); + Bullet updateBullet(Integer bulletId, BulletUpdateDTO dto); + List getAllBullets(); + List getAllBulletsWithDetails(); + Bullet getBulletById(Integer bulletId); +} diff --git a/src/main/java/com/crimeLink/analyzer/service/UserService.java b/src/main/java/com/crimeLink/analyzer/service/UserService.java index 4b42291..8d0a5af 100644 --- a/src/main/java/com/crimeLink/analyzer/service/UserService.java +++ b/src/main/java/com/crimeLink/analyzer/service/UserService.java @@ -18,4 +18,8 @@ public UserService(UserRepository repo) { public List getFieldOfficers() { return repo.findByRole("FieldOfficer"); } + + public List getAllOfficers() { + return repo.findAll(); + } } diff --git a/src/main/java/com/crimeLink/analyzer/service/WeaponIssueService.java b/src/main/java/com/crimeLink/analyzer/service/WeaponIssueService.java index cab2581..170c51e 100644 --- a/src/main/java/com/crimeLink/analyzer/service/WeaponIssueService.java +++ b/src/main/java/com/crimeLink/analyzer/service/WeaponIssueService.java @@ -8,9 +8,9 @@ import java.util.List; public interface WeaponIssueService { - List getAllActiveWeapons(); - List getAvailableWeapons(); void issueWeapon(IssueWeaponRequestDTO dto); void returnWeapon(ReturnWeaponRequestDTO dto); + List getAllActiveWeapons(); + List getAvailableWeapons(); List getAllOfficers(); } \ No newline at end of file diff --git a/src/main/java/com/crimeLink/analyzer/service/impl/BulletserviceImpl.java b/src/main/java/com/crimeLink/analyzer/service/impl/BulletserviceImpl.java new file mode 100644 index 0000000..0583db5 --- /dev/null +++ b/src/main/java/com/crimeLink/analyzer/service/impl/BulletserviceImpl.java @@ -0,0 +1,103 @@ +package com.crimeLink.analyzer.service.impl; + +import com.crimeLink.analyzer.dto.BulletAddDTO; +import com.crimeLink.analyzer.dto.BulletResponseDTO; +import com.crimeLink.analyzer.dto.BulletUpdateDTO; +import com.crimeLink.analyzer.entity.Bullet; +import com.crimeLink.analyzer.repository.BulletRepository; +import com.crimeLink.analyzer.service.BulletService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class BulletserviceImpl implements BulletService { + + private final BulletRepository bulletRepository; + + @Override + @Transactional + public Bullet addBullet(BulletAddDTO dto) { + if (dto.getBulletType() == null || dto.getBulletType().trim().isEmpty()) { + throw new RuntimeException("Bullet type is required"); + } + if (dto.getNumberOfMagazines() == null || dto.getNumberOfMagazines() < 0) { + throw new RuntimeException("Number of magazines must be 0 or more"); + } + if (bulletRepository.existsByBulletTypeIgnoreCase(dto.getBulletType().trim())) { + throw new RuntimeException("Bullet type already exists"); + } + + Bullet bullet = new Bullet(); + bullet.setBulletType(dto.getBulletType().trim()); + bullet.setNumberOfMagazines(dto.getNumberOfMagazines()); + bullet.setRemarks(dto.getRemarks()); + + return bulletRepository.save(bullet); + } + + @Override + @Transactional + public Bullet updateBullet(Integer bulletId, BulletUpdateDTO dto) { + Bullet bullet = bulletRepository.findById(bulletId) + .orElseThrow(() -> new RuntimeException("Bullet not found with ID: " + bulletId)); + + if (dto.getBulletType() == null || dto.getBulletType().trim().isEmpty()) { + throw new RuntimeException("Bullet type is required"); + } + if (dto.getNumberOfMagazines() == null || dto.getNumberOfMagazines() < 0) { + throw new RuntimeException("Number of magazines must be 0 or more"); + } + + // if changing type -> check duplicates + String newType = dto.getBulletType().trim(); + if (!bullet.getBulletType().equalsIgnoreCase(newType) + && bulletRepository.existsByBulletTypeIgnoreCase(newType)) { + throw new RuntimeException("Bullet type already exists"); + } + + bullet.setBulletType(newType); + bullet.setNumberOfMagazines(dto.getNumberOfMagazines()); + bullet.setRemarks(dto.getRemarks()); + + return bulletRepository.save(bullet); + } + + @Override + public List getAllBullets() { + return bulletRepository.findAll(); + } + + @Override + public List getAllBulletsWithDetails() { + return bulletRepository.findAll() + .stream() + .map(this::convertToBulletResponseDTO) + .collect(Collectors.toList()); + } + + @Override + public Bullet getBulletById(Integer bulletId) { + return bulletRepository.findById(bulletId) + .orElseThrow(() -> new RuntimeException("Bullet not found with ID: " + bulletId)); + } + + private BulletResponseDTO convertToBulletResponseDTO(Bullet bullet) { + BulletResponseDTO dto = new BulletResponseDTO(); + dto.setBulletId(bullet.getBulletId()); + dto.setBulletType(bullet.getBulletType()); + dto.setNumberOfMagazines(bullet.getNumberOfMagazines()); + dto.setRemarks(bullet.getRemarks()); + + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + if (bullet.getRegisterDate() != null) { + dto.setRegisterDate(bullet.getRegisterDate().format(dateFormatter)); + } + return dto; + } +} diff --git a/src/main/java/com/crimeLink/analyzer/service/impl/WeaponIssueServiceImpl.java b/src/main/java/com/crimeLink/analyzer/service/impl/WeaponIssueServiceImpl.java index 9b597de..0095f20 100644 --- a/src/main/java/com/crimeLink/analyzer/service/impl/WeaponIssueServiceImpl.java +++ b/src/main/java/com/crimeLink/analyzer/service/impl/WeaponIssueServiceImpl.java @@ -1,28 +1,41 @@ package com.crimeLink.analyzer.service.impl; import com.crimeLink.analyzer.dto.*; +import com.crimeLink.analyzer.entity.Bullet; import com.crimeLink.analyzer.entity.User; import com.crimeLink.analyzer.entity.Weapon; import com.crimeLink.analyzer.entity.WeaponIssue; import com.crimeLink.analyzer.enums.WeaponStatus; +import com.crimeLink.analyzer.repository.BulletRepository; import com.crimeLink.analyzer.repository.UserRepository; import com.crimeLink.analyzer.repository.WeaponIssueRepository; import com.crimeLink.analyzer.repository.WeaponRepository; import com.crimeLink.analyzer.service.WeaponIssueService; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @Service -@RequiredArgsConstructor + public class WeaponIssueServiceImpl implements WeaponIssueService { private final WeaponRepository weaponRepository; private final WeaponIssueRepository weaponIssueRepository; private final UserRepository userRepository; + private final BulletRepository bulletRepository; + + public WeaponIssueServiceImpl(WeaponRepository weaponRepository, + WeaponIssueRepository weaponIssueRepository, + UserRepository userRepository, + BulletRepository bulletRepository) { + this.weaponRepository = weaponRepository; + this.weaponIssueRepository = weaponIssueRepository; + this.userRepository = userRepository; + this.bulletRepository = bulletRepository; + } @Override public List getAllActiveWeapons() { @@ -55,20 +68,45 @@ public List getAvailableWeapons() { } @Override + @Transactional public void issueWeapon(IssueWeaponRequestDTO dto) { - Weapon weapon = weaponRepository - .findBySerialNumber(dto.getWeaponSerial()) + + Weapon weapon = weaponRepository.findBySerialNumber(dto.getWeaponSerial()) .orElseThrow(() -> new RuntimeException("Weapon not found with serial: " + dto.getWeaponSerial())); if (weapon.getStatus() != WeaponStatus.AVAILABLE) { - throw new RuntimeException("Weapon is not available for issue. Current status: " + weapon.getStatus()); + throw new RuntimeException("Weapon not available. Current status: " + weapon.getStatus()); } User issuedTo = userRepository.findById(dto.getIssuedToId()) - .orElseThrow(() -> new RuntimeException("Issued-to user not found with ID: " + dto.getIssuedToId())); + .orElseThrow(() -> new RuntimeException("Issued-to user not found: " + dto.getIssuedToId())); User handedOverBy = userRepository.findById(dto.getHandedOverById()) - .orElseThrow(() -> new RuntimeException("Handed-over user not found with ID: " + dto.getHandedOverById())); + .orElseThrow(() -> new RuntimeException("Handed-over user not found: " + dto.getHandedOverById())); + + // ===== BULLET STOCK VALIDATION + DECREMENT ===== + String bulletType = dto.getBulletType(); + Integer magsToIssue = dto.getNumberOfMagazines(); + + Bullet stockBullet = null; + if (bulletType != null && !bulletType.trim().isEmpty() && magsToIssue != null) { + if (magsToIssue <= 0) { + throw new RuntimeException("Magazines to issue must be greater than 0"); + } + + stockBullet = bulletRepository.findByBulletType(bulletType.trim()) + .orElseThrow(() -> new RuntimeException("Bullet type not found: " + bulletType)); + + int available = stockBullet.getNumberOfMagazines() != null ? stockBullet.getNumberOfMagazines() : 0; + + if (magsToIssue > available) { + throw new RuntimeException( + "Not enough magazines. Available: " + available + ", Requested: " + magsToIssue); + } + + stockBullet.setNumberOfMagazines(available - magsToIssue); + bulletRepository.save(stockBullet); + } WeaponIssue issue = new WeaponIssue(); issue.setWeapon(weapon); @@ -77,8 +115,17 @@ public void issueWeapon(IssueWeaponRequestDTO dto) { issue.setIssuedAt(LocalDateTime.now()); issue.setDueDate(dto.getDueDate()); issue.setIssueNote(dto.getIssueNote()); + + // ✅ Set status to ISSUED for new issue issue.setStatus(WeaponStatus.ISSUED); + // Save bullet info into weapon_issues row + if (stockBullet != null) { + issue.setBulletType(stockBullet.getBulletType()); + issue.setIssuedMagazines(magsToIssue); + issue.setBulletRemarks(dto.getBulletRemarks()); + } + weapon.setStatus(WeaponStatus.ISSUED); weaponIssueRepository.save(issue); @@ -86,31 +133,100 @@ public void issueWeapon(IssueWeaponRequestDTO dto) { } @Override + @Transactional public void returnWeapon(ReturnWeaponRequestDTO dto) { - Weapon weapon = weaponRepository - .findBySerialNumber(dto.getWeaponSerial()) + + // ===== VALIDATION ===== + if (dto.getWeaponSerial() == null || dto.getWeaponSerial().trim().isEmpty()) { + throw new RuntimeException("Weapon serial number is required"); + } + + if (dto.getReceivedByUserId() == null) { + throw new RuntimeException("Receiving user ID is required"); + } + + Weapon weapon = weaponRepository.findBySerialNumber(dto.getWeaponSerial()) .orElseThrow(() -> new RuntimeException("Weapon not found with serial: " + dto.getWeaponSerial())); - WeaponIssue issue = weaponIssueRepository - .findByWeapon_SerialNumberAndReturnedAtIsNull(weapon.getSerialNumber()) + WeaponIssue issue = weaponIssueRepository.findByWeapon_SerialNumberAndReturnedAtIsNull(weapon.getSerialNumber()) .orElseThrow(() -> new RuntimeException("No active issue found for this weapon")); User receivedBy = userRepository.findById(dto.getReceivedByUserId()) - .orElseThrow(() -> new RuntimeException("Receiving user not found with ID: " + dto.getReceivedByUserId())); + .orElseThrow(() -> new RuntimeException("Receiving user not found: " + dto.getReceivedByUserId())); + // Set return timestamp and receiving officer issue.setReturnedAt(LocalDateTime.now()); issue.setReceivedBy(receivedBy); - issue.setReturnNote(dto.getReturnNote()); + issue.setReturnNote(dto.getReturnNote() != null ? dto.getReturnNote() : "Returned"); + + // ✅ CRITICAL FIX: Do NOT change the status field on return + // The status should remain ISSUED (the original issue status) + // The returnedAt timestamp indicates the weapon has been returned + // DO NOT SET: issue.setStatus(WeaponStatus.AVAILABLE); + // The weapon_issues table tracks the issue record, not the current weapon state + // The weapon entity status is what gets updated to AVAILABLE + + // ===== BULLET RETURN VALIDATION + INCREMENT ===== + Integer issuedMags = issue.getIssuedMagazines(); + String issuedBulletType = issue.getBulletType(); + + // Check if bullets were actually issued + boolean bulletsWereIssued = (issuedBulletType != null && !issuedBulletType.trim().isEmpty() && + issuedMags != null && issuedMags > 0); + + // ✅ Only process bullet returns if bullets were issued + if (bulletsWereIssued) { + Integer returnedMags = dto.getReturnedMagazines(); + + // If bullets were issued, we expect returnedMagazines to be provided + if (returnedMags == null) { + throw new RuntimeException("Returned magazines count is required when bullets were issued"); + } + + if (returnedMags < 0) { + throw new RuntimeException("Returned magazines cannot be negative"); + } + + if (returnedMags > issuedMags) { + throw new RuntimeException("Returned magazines (" + returnedMags + + ") cannot exceed issued magazines (" + issuedMags + ")"); + } + + // Normalize bullet type for matching + String normalizedBulletType = issuedBulletType.trim(); + + Bullet stockBullet = bulletRepository.findByBulletType(normalizedBulletType) + .orElseThrow( + () -> new RuntimeException("Bullet stock not found for type: " + normalizedBulletType)); + + // Add returned magazines back to stock + int currentStock = stockBullet.getNumberOfMagazines() != null ? stockBullet.getNumberOfMagazines() : 0; + stockBullet.setNumberOfMagazines(currentStock + returnedMags); + bulletRepository.save(stockBullet); + + // Record bullet return details + issue.setReturnedMagazines(returnedMags); + issue.setUsedBullets(dto.getUsedBullets() != null ? dto.getUsedBullets() : 0); + issue.setBulletCondition(dto.getBulletCondition() != null ? dto.getBulletCondition() : "good"); + + // Append bullet remarks if provided + if (dto.getBulletRemarks() != null && !dto.getBulletRemarks().trim().isEmpty()) { + String existing = issue.getBulletRemarks() != null ? issue.getBulletRemarks() : ""; + String separator = (existing.isEmpty()) ? "" : "\n"; + issue.setBulletRemarks(existing + separator + "[Return] " + dto.getBulletRemarks().trim()); + } + } + // ✅ Update the WEAPON status to AVAILABLE (not the issue status) weapon.setStatus(WeaponStatus.AVAILABLE); + // Save both - but issue.status remains ISSUED weaponIssueRepository.save(issue); weaponRepository.save(weapon); } @Override public List getAllOfficers() { - // Get all active users List users = userRepository.findByStatus("Active"); return users.stream() diff --git a/src/main/java/com/crimeLink/analyzer/service/impl/WeaponServiceImpl.java b/src/main/java/com/crimeLink/analyzer/service/impl/WeaponServiceImpl.java index d9fdbb5..3cdb9a7 100644 --- a/src/main/java/com/crimeLink/analyzer/service/impl/WeaponServiceImpl.java +++ b/src/main/java/com/crimeLink/analyzer/service/impl/WeaponServiceImpl.java @@ -12,9 +12,9 @@ import com.crimeLink.analyzer.service.WeaponService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; @Service @@ -25,6 +25,7 @@ public class WeaponServiceImpl implements WeaponService { private final WeaponIssueRepository weaponIssueRepository; @Override + @Transactional public Weapon addWeapon(WeaponAddDTO dto) { if (weaponRepository.existsById(dto.getSerialNumber())) { throw new RuntimeException("Weapon already exists with serial number: " + dto.getSerialNumber()); @@ -40,6 +41,7 @@ public Weapon addWeapon(WeaponAddDTO dto) { } @Override + @Transactional public Weapon updateWeapon(String serialNumber, WeaponUpdateDTO dto) { Weapon weapon = weaponRepository.findById(serialNumber) .orElseThrow(() -> new RuntimeException("Weapon not found with serial number: " + serialNumber)); @@ -116,6 +118,10 @@ public List getAllWeaponsWithDetails() { dto.setDueDate(issue.getDueDate().toString()); } dto.setIssueNote(issue.getIssueNote()); + + // ===== ADD BULLET INFORMATION ===== + dto.setIssuedBulletType(issue.getBulletType()); + dto.setIssuedMagazines(issue.getIssuedMagazines()); } }