fixed staff accounts and added coupons andriod

This commit is contained in:
Alex
2026-04-10 07:17:19 -06:00
parent 32e41397d4
commit 5850adedc3
30 changed files with 1939 additions and 368 deletions

View File

@@ -0,0 +1,65 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.common.CouponRequest;
import com.petshop.backend.dto.common.CouponResponse;
import com.petshop.backend.service.CouponService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/coupons")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public class CouponController {
private final CouponService couponService;
public CouponController(CouponService couponService) {
this.couponService = couponService;
}
@GetMapping
public ResponseEntity<Page<CouponResponse>> getAllCoupons(
@RequestParam(required = false) String q,
@RequestParam(required = false) Boolean active,
Pageable pageable) {
return ResponseEntity.ok(couponService.getAllCoupons(q, active, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<CouponResponse> getCouponById(@PathVariable Long id) {
return ResponseEntity.ok(couponService.getCouponById(id));
}
@GetMapping("/code/{code}")
public ResponseEntity<CouponResponse> getCouponByCode(@PathVariable String code) {
return ResponseEntity.ok(couponService.getCouponByCode(code));
}
@PostMapping
public ResponseEntity<CouponResponse> createCoupon(@Valid @RequestBody CouponRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(couponService.createCoupon(request));
}
@PutMapping("/{id}")
public ResponseEntity<CouponResponse> updateCoupon(@PathVariable Long id, @Valid @RequestBody CouponRequest request) {
return ResponseEntity.ok(couponService.updateCoupon(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteCoupon(@PathVariable Long id) {
couponService.deleteCoupon(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
public ResponseEntity<Void> bulkDeleteCoupons(@Valid @RequestBody BulkDeleteRequest request) {
couponService.bulkDeleteCoupons(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,53 @@
package com.petshop.backend.dto.common;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class CouponRequest {
@NotBlank(message = "Coupon code is required")
private String couponCode;
@NotBlank(message = "Discount type is required")
private String discountType;
@NotNull(message = "Discount value is required")
@Positive(message = "Discount value must be positive")
private BigDecimal discountValue;
private BigDecimal minOrderAmount;
private Boolean active = true;
private LocalDateTime startsAt;
private LocalDateTime endsAt;
private Integer usageLimit;
public String getCouponCode() { return couponCode; }
public void setCouponCode(String couponCode) { this.couponCode = couponCode; }
public String getDiscountType() { return discountType; }
public void setDiscountType(String discountType) { this.discountType = discountType; }
public BigDecimal getDiscountValue() { return discountValue; }
public void setDiscountValue(BigDecimal discountValue) { this.discountValue = discountValue; }
public BigDecimal getMinOrderAmount() { return minOrderAmount; }
public void setMinOrderAmount(BigDecimal minOrderAmount) { this.minOrderAmount = minOrderAmount; }
public Boolean getActive() { return active; }
public void setActive(Boolean active) { this.active = active; }
public LocalDateTime getStartsAt() { return startsAt; }
public void setStartsAt(LocalDateTime startsAt) { this.startsAt = startsAt; }
public LocalDateTime getEndsAt() { return endsAt; }
public void setEndsAt(LocalDateTime endsAt) { this.endsAt = endsAt; }
public Integer getUsageLimit() { return usageLimit; }
public void setUsageLimit(Integer usageLimit) { this.usageLimit = usageLimit; }
}

View File

@@ -0,0 +1,65 @@
package com.petshop.backend.dto.common;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class CouponResponse {
private Long couponId;
private String couponCode;
private String discountType;
private BigDecimal discountValue;
private BigDecimal minOrderAmount;
private Boolean active;
private LocalDateTime startsAt;
private LocalDateTime endsAt;
private Integer usageLimit;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public CouponResponse(Long couponId, String couponCode, String discountType, BigDecimal discountValue, BigDecimal minOrderAmount, Boolean active, LocalDateTime startsAt, LocalDateTime endsAt, Integer usageLimit, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.couponId = couponId;
this.couponCode = couponCode;
this.discountType = discountType;
this.discountValue = discountValue;
this.minOrderAmount = minOrderAmount;
this.active = active;
this.startsAt = startsAt;
this.endsAt = endsAt;
this.usageLimit = usageLimit;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getCouponId() { return couponId; }
public void setCouponId(Long couponId) { this.couponId = couponId; }
public String getCouponCode() { return couponCode; }
public void setCouponCode(String couponCode) { this.couponCode = couponCode; }
public String getDiscountType() { return discountType; }
public void setDiscountType(String discountType) { this.discountType = discountType; }
public BigDecimal getDiscountValue() { return discountValue; }
public void setDiscountValue(BigDecimal discountValue) { this.discountValue = discountValue; }
public BigDecimal getMinOrderAmount() { return minOrderAmount; }
public void setMinOrderAmount(BigDecimal minOrderAmount) { this.minOrderAmount = minOrderAmount; }
public Boolean getActive() { return active; }
public void setActive(Boolean active) { this.active = active; }
public LocalDateTime getStartsAt() { return startsAt; }
public void setStartsAt(LocalDateTime startsAt) { this.startsAt = startsAt; }
public LocalDateTime getEndsAt() { return endsAt; }
public void setEndsAt(LocalDateTime endsAt) { this.endsAt = endsAt; }
public Integer getUsageLimit() { return usageLimit; }
public void setUsageLimit(Integer usageLimit) { this.usageLimit = usageLimit; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}

View File

@@ -1,7 +1,11 @@
package com.petshop.backend.repository;
import com.petshop.backend.entity.Coupon;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@@ -10,4 +14,9 @@ import java.util.Optional;
public interface CouponRepository extends JpaRepository<Coupon, Long> {
Optional<Coupon> findByCouponCode(String couponCode);
@Query("SELECT c FROM Coupon c WHERE " +
"(:q IS NULL OR LOWER(c.couponCode) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
"(:active IS NULL OR c.active = :active)")
Page<Coupon> searchCoupons(@Param("q") String query, @Param("active") Boolean active, Pageable pageable);
}

View File

@@ -0,0 +1,106 @@
package com.petshop.backend.service;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.common.CouponRequest;
import com.petshop.backend.dto.common.CouponResponse;
import com.petshop.backend.entity.Coupon;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.repository.CouponRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CouponService {
private final CouponRepository couponRepository;
public CouponService(CouponRepository couponRepository) {
this.couponRepository = couponRepository;
}
public Page<CouponResponse> getAllCoupons(String query, Boolean active, Pageable pageable) {
return couponRepository.searchCoupons(query, active, pageable).map(this::mapToResponse);
}
public CouponResponse getCouponById(Long id) {
Coupon coupon = couponRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + id));
return mapToResponse(coupon);
}
public CouponResponse getCouponByCode(String code) {
Coupon coupon = couponRepository.findByCouponCode(code)
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with code: " + code));
return mapToResponse(coupon);
}
@Transactional
public CouponResponse createCoupon(CouponRequest request) {
if (couponRepository.findByCouponCode(request.getCouponCode()).isPresent()) {
throw new IllegalArgumentException("Coupon code already exists: " + request.getCouponCode());
}
Coupon coupon = new Coupon();
updateCouponFields(coupon, request);
coupon = couponRepository.save(coupon);
return mapToResponse(coupon);
}
@Transactional
public CouponResponse updateCoupon(Long id, CouponRequest request) {
Coupon coupon = couponRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + id));
couponRepository.findByCouponCode(request.getCouponCode()).ifPresent(existing -> {
if (!existing.getCouponId().equals(id)) {
throw new IllegalArgumentException("Coupon code already exists: " + request.getCouponCode());
}
});
updateCouponFields(coupon, request);
coupon = couponRepository.save(coupon);
return mapToResponse(coupon);
}
@Transactional
public void deleteCoupon(Long id) {
if (!couponRepository.existsById(id)) {
throw new ResourceNotFoundException("Coupon not found with id: " + id);
}
couponRepository.deleteById(id);
}
@Transactional
public void bulkDeleteCoupons(BulkDeleteRequest request) {
couponRepository.deleteAllById(request.getIds());
}
private void updateCouponFields(Coupon coupon, CouponRequest request) {
coupon.setCouponCode(request.getCouponCode());
coupon.setDiscountType(request.getDiscountType());
coupon.setDiscountValue(request.getDiscountValue());
coupon.setMinOrderAmount(request.getMinOrderAmount());
coupon.setActive(request.getActive());
coupon.setStartsAt(request.getStartsAt());
coupon.setEndsAt(request.getEndsAt());
coupon.setUsageLimit(request.getUsageLimit());
}
private CouponResponse mapToResponse(Coupon coupon) {
return new CouponResponse(
coupon.getCouponId(),
coupon.getCouponCode(),
coupon.getDiscountType(),
coupon.getDiscountValue(),
coupon.getMinOrderAmount(),
coupon.getActive(),
coupon.getStartsAt(),
coupon.getEndsAt(),
coupon.getUsageLimit(),
coupon.getCreatedAt(),
coupon.getUpdatedAt()
);
}
}