Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
Expand Down Expand Up @@ -42,6 +43,11 @@ public ResponseEntity<Map<String, Object>> handleBadRequest(BadRequestException
return buildError(HttpStatus.BAD_REQUEST, ex.getMessage());
}

@ExceptionHandler(PropertyReferenceException.class)
public ResponseEntity<Map<String, Object>> handleInvalidSortField(PropertyReferenceException ex) {
return buildError(HttpStatus.BAD_REQUEST, "Invalid sort field: " + ex.getPropertyName());
}

@ExceptionHandler(HttpClientErrorException.class)
public ResponseEntity<Map<String, Object>> handleHttpClientError(HttpClientErrorException ex) {
HttpStatus status = HttpStatus.resolve(ex.getStatusCode().value());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.net.URI;
import java.time.LocalDateTime;
import java.util.List;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
Expand Down Expand Up @@ -87,7 +88,7 @@ public ResponseEntity<FreightOrderResponse> getById(@PathVariable Long id) {
@GetMapping
public ResponseEntity<PageResponse<FreightOrderResponse>> list(
@RequestParam(required = false) Long voyageId,
@PageableDefault(size = 20) Pageable pageable) {
@ParameterObject @PageableDefault(size = 20) Pageable pageable) {
Page<FreightOrder> orders =
(voyageId != null)
? service.getOrdersByVoyage(voyageId, pageable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
Expand Down Expand Up @@ -68,7 +69,7 @@ public ResponseEntity<VoyageResponse> getById(@PathVariable Long voyageId) {
})
@GetMapping("/{voyageId}/containers")
public ResponseEntity<PageResponse<VoyageContainerResponse>> getAllContainersByVoyageId(
@PathVariable Long voyageId, @PageableDefault(size = 20) Pageable pageable) {
@PathVariable Long voyageId, @ParameterObject @PageableDefault(size = 20) Pageable pageable) {

Page<FreightOrder> order = freightOrderService.getOrdersByVoyage(voyageId, pageable);
Page<VoyageContainerResponse> containers = order.map(VoyageContainerResponse::fromEntity);
Expand Down Expand Up @@ -131,7 +132,7 @@ public ResponseEntity<VoyagePriceResponse> setPriceForContainer(
})
@GetMapping("/{voyageId}/prices")
public ResponseEntity<PageResponse<VoyagePriceResponse>> getVoyagePrices(
@PathVariable Long voyageId, @PageableDefault(size = 20) Pageable pageable) {
@PathVariable Long voyageId, @ParameterObject @PageableDefault(size = 20) Pageable pageable) {
Page<VoyagePrice> voyagePrices = voyageService.getAllPricesByVoyageId(voyageId, pageable);
Page<VoyagePriceResponse> mapped = voyagePrices.map(VoyagePriceResponse::fromEntity);
return ResponseEntity.ok(PageResponse.from(mapped));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,37 @@ public interface FreightOrderRepository extends JpaRepository<FreightOrder, Long

long countByVoyageId(Long voyageId);

@Query(
value =
"SELECT fo FROM FreightOrder fo"
+ " JOIN FETCH fo.voyage"
+ " JOIN FETCH fo.container"
+ " JOIN FETCH fo.agent"
+ " JOIN FETCH fo.customer",
countQuery = "SELECT COUNT(fo) FROM FreightOrder fo")
Page<FreightOrder> findAllWithAssociations(Pageable pageable);

@Query(
value =
"SELECT fo FROM FreightOrder fo"
+ " JOIN FETCH fo.voyage"
+ " JOIN FETCH fo.container"
+ " JOIN FETCH fo.agent"
+ " JOIN FETCH fo.customer"
+ " WHERE fo.voyage.id = :voyageId",
countQuery = "SELECT COUNT(fo) FROM FreightOrder fo WHERE fo.voyage.id = :voyageId")
Page<FreightOrder> findByVoyageIdWithAssociations(
@Param("voyageId") Long voyageId, Pageable pageable);

@Query(
"SELECT fo FROM FreightOrder fo"
+ " JOIN FETCH fo.voyage"
+ " JOIN FETCH fo.container"
+ " JOIN FETCH fo.agent"
+ " JOIN FETCH fo.customer"
+ " WHERE fo.id = :id")
Optional<FreightOrder> findByIdWithAssociations(@Param("id") Long id);

Page<FreightOrder> findByVoyageId(Long voyageId, Pageable pageable);

Page<FreightOrder> findByStatus(OrderStatus status, Pageable pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,29 @@ public interface VoyageRepository extends JpaRepository<Voyage, Long> {
@Query("SELECT v FROM Voyage v WHERE v.id = :id")
Optional<Voyage> findByIdForUpdate(@Param("id") Long id);

@Query(
"SELECT v FROM Voyage v"
+ " JOIN FETCH v.vessel"
+ " JOIN FETCH v.departurePort"
+ " JOIN FETCH v.arrivalPort"
+ " WHERE v.id = :id")
Optional<Voyage> findByIdWithAssociations(@Param("id") Long id);

@Query(
"SELECT v FROM Voyage v"
+ " JOIN FETCH v.vessel"
+ " JOIN FETCH v.departurePort"
+ " JOIN FETCH v.arrivalPort")
List<Voyage> findAllWithAssociations();

@Query(
"SELECT v FROM Voyage v"
+ " JOIN FETCH v.vessel"
+ " JOIN FETCH v.departurePort"
+ " JOIN FETCH v.arrivalPort"
+ " WHERE v.status = :status")
List<Voyage> findAllByStatusWithAssociations(@Param("status") VoyageStatus status);

Optional<Voyage> findByVoyageNumber(String voyageNumber);

List<Voyage> findAllByStatus(VoyageStatus status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,21 +143,21 @@ public FreightOrder createOrder(CreateFreightOrderRequest request) {
@Transactional(readOnly = true)
public FreightOrder getOrder(Long id) {
return orderRepository
.findById(id)
.findByIdWithAssociations(id)
.orElseThrow(() -> new IllegalArgumentException("Freight order not found: " + id));
}

@Transactional(readOnly = true)
public Page<FreightOrder> getAllOrders(Pageable pageable) {
return orderRepository.findAll(pageable);
return orderRepository.findAllWithAssociations(pageable);
}

@Transactional(readOnly = true)
public Page<FreightOrder> getOrdersByVoyage(Long voyageId, Pageable pageable) {
voyageRepository
.findById(voyageId)
.orElseThrow(() -> new IllegalArgumentException("Voyage not found"));
return orderRepository.findByVoyageId(voyageId, pageable);
return orderRepository.findByVoyageIdWithAssociations(voyageId, pageable);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.shipping.freightops.repository.TrackingEventRepository;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TrackingEventService {
Expand All @@ -13,10 +14,12 @@ public TrackingEventService(TrackingEventRepository trackingEventRepository) {
this.trackingEventRepository = trackingEventRepository;
}

@Transactional
public TrackingEvent createEvent(TrackingEvent event) {
return trackingEventRepository.save(event);
}

@Transactional(readOnly = true)
public List<TrackingEvent> getAllEventsByOrderId(Long id) {
return trackingEventRepository.findAllByFreightOrder_IdOrderByCreatedAtAsc(id);
}
Expand Down
26 changes: 20 additions & 6 deletions src/main/java/com/shipping/freightops/service/VoyageService.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,35 +83,46 @@ public VoyageService(
this.vesselOwnerRepository = vesselOwnerRepository;
}

@Transactional(readOnly = true)
public List<Voyage> getAll() {
return voyageRepository.findAll();
return voyageRepository.findAllWithAssociations();
}

@Transactional(readOnly = true)
public List<Voyage> getAllByStatus(VoyageStatus status) {
return voyageRepository.findAllByStatus(status);
return voyageRepository.findAllByStatusWithAssociations(status);
}

@Transactional(readOnly = true)
public Voyage getById(Long id) {
return voyageRepository
.findById(id)
.findByIdWithAssociations(id)
.orElseThrow(() -> new IllegalArgumentException("Voyage not found"));
}

@Transactional
public Voyage addVoyage(@Valid CreateVoyageRequest voyageRequest) {
Voyage voyage = mapCreateVoyageRequestToVoyage(voyageRequest);
return voyageRepository.save(voyage);
Voyage saved = voyageRepository.save(voyage);
return voyageRepository
.findByIdWithAssociations(saved.getId())
.orElseThrow(() -> new IllegalArgumentException("Voyage not found"));
}

@Transactional
public Voyage updateStatus(VoyageStatus status, Long voyageId) {
Voyage voyage =
voyageRepository
.findById(voyageId)
.orElseThrow(() -> new IllegalArgumentException("voyage not found"));
voyage.setStatus(status);
return voyageRepository.save(voyage);
voyageRepository.save(voyage);
return voyageRepository
.findByIdWithAssociations(voyageId)
.orElseThrow(() -> new IllegalArgumentException("Voyage not found"));
}

@Transactional
public void delete(Long voyageId) {
boolean exists = voyageRepository.existsById(voyageId);
if (!exists) throw new IllegalArgumentException("Voyage not found");
Expand Down Expand Up @@ -165,7 +176,10 @@ public Voyage updateBookingStatus(Long voyageId, BookingStatusUpdateRequest requ
.findById(voyageId)
.orElseThrow(() -> new IllegalArgumentException("Voyage not found"));
voyage.setBookingOpen(request.isBookingOpen());
return voyageRepository.save(voyage);
voyageRepository.save(voyage);
return voyageRepository
.findByIdWithAssociations(voyageId)
.orElseThrow(() -> new IllegalArgumentException("Voyage not found"));
}

@Transactional
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ spring.sql.init.mode=always
spring.jpa.defer-datasource-initialization=true
# ── Jackson ──
spring.jackson.serialization.write-dates-as-timestamps=false
# SpringDoc — disable pageable converter so sort renders as plain string, not array
springdoc.model-converters.pageable-customizer.enabled=false
# Booking settings
app.booking.auto-cutoff-percent=95
# AI
Expand Down
Loading