| 1 | package com.rbctcsworld.ecommerce.fulfillment; | |
| 2 | ||
| 3 | import com.rbctcsworld.ecommerce.audit.AuditEvent; | |
| 4 | import com.rbctcsworld.ecommerce.common.exception.NotFoundException; | |
| 5 | import com.rbctcsworld.ecommerce.order.CustomerOrder; | |
| 6 | import com.rbctcsworld.ecommerce.order.OrderDtos.OrderResponse; | |
| 7 | import com.rbctcsworld.ecommerce.order.OrderDtos.TrackingResponse; | |
| 8 | import com.rbctcsworld.ecommerce.order.OrderRepository; | |
| 9 | import com.rbctcsworld.ecommerce.order.OrderStatus; | |
| 10 | import com.rbctcsworld.ecommerce.notification.OrderNotificationEvent; | |
| 11 | import org.springframework.context.ApplicationEventPublisher; | |
| 12 | import org.springframework.stereotype.Service; | |
| 13 | import org.springframework.transaction.annotation.Transactional; | |
| 14 | ||
| 15 | import java.security.SecureRandom; | |
| 16 | ||
| 17 | /** | |
| 18 | * Warehouse side (ADMIN): ship a PAID order, mark a SHIPPED order delivered. | |
| 19 | * Plus the PUBLIC tracking lookup by tracking number. | |
| 20 | */ | |
| 21 | @Service | |
| 22 | @Transactional | |
| 23 | public class FulfillmentService { | |
| 24 | ||
| 25 | private static final SecureRandom RANDOM = new SecureRandom(); | |
| 26 | ||
| 27 | private final OrderRepository orders; | |
| 28 | private final ApplicationEventPublisher events; | |
| 29 | ||
| 30 | public FulfillmentService(OrderRepository orders, ApplicationEventPublisher events) { | |
| 31 | this.orders = orders; | |
| 32 | this.events = events; | |
| 33 | } | |
| 34 | ||
| 35 | /** 200 SHIPPED | 404 | 409 unless PAID (an unpaid or cancelled order must never leave the warehouse). */ | |
| 36 | public OrderResponse ship(Long orderId, String carrier) { | |
| 37 | CustomerOrder order = order(orderId); | |
| 38 |
1
1. ship : removed call to com/rbctcsworld/ecommerce/order/CustomerOrder::assertCanMoveTo → KILLED |
order.assertCanMoveTo(OrderStatus.SHIPPED); |
| 39 | String from = order.getStatus(); | |
| 40 |
1
1. ship : removed call to com/rbctcsworld/ecommerce/order/CustomerOrder::ship → KILLED |
order.ship(carrier, newTrackingNumber(carrier)); |
| 41 |
1
1. ship : removed call to org/springframework/context/ApplicationEventPublisher::publishEvent → KILLED |
events.publishEvent(AuditEvent.orderStatus(order.getId(), from, order.getStatus())); |
| 42 |
1
1. ship : removed call to org/springframework/context/ApplicationEventPublisher::publishEvent → KILLED |
events.publishEvent(OrderNotificationEvent.of(order.getId(), OrderNotificationEvent.Type.ORDER_SHIPPED)); |
| 43 |
1
1. ship : replaced return value with null for com/rbctcsworld/ecommerce/fulfillment/FulfillmentService::ship → KILLED |
return OrderResponse.from(order); |
| 44 | } | |
| 45 | ||
| 46 | /** 200 DELIVERED | 404 | 409 unless SHIPPED. */ | |
| 47 | public OrderResponse deliver(Long orderId) { | |
| 48 | CustomerOrder order = order(orderId); | |
| 49 | String from = order.getStatus(); | |
| 50 |
1
1. deliver : removed call to com/rbctcsworld/ecommerce/order/CustomerOrder::deliver → KILLED |
order.deliver(); |
| 51 |
1
1. deliver : removed call to org/springframework/context/ApplicationEventPublisher::publishEvent → KILLED |
events.publishEvent(AuditEvent.orderStatus(order.getId(), from, order.getStatus())); |
| 52 |
1
1. deliver : removed call to org/springframework/context/ApplicationEventPublisher::publishEvent → KILLED |
events.publishEvent(OrderNotificationEvent.of(order.getId(), OrderNotificationEvent.Type.ORDER_DELIVERED)); |
| 53 |
1
1. deliver : replaced return value with null for com/rbctcsworld/ecommerce/fulfillment/FulfillmentService::deliver → KILLED |
return OrderResponse.from(order); |
| 54 | } | |
| 55 | ||
| 56 | /** Public: anyone with the tracking number sees status + timeline, but no personal or price data. */ | |
| 57 | @Transactional(readOnly = true) | |
| 58 | public TrackingResponse track(String trackingNumber) { | |
| 59 |
1
1. track : replaced return value with null for com/rbctcsworld/ecommerce/fulfillment/FulfillmentService::track → NO_COVERAGE |
return orders.findByTrackingNumber(trackingNumber.trim().toUpperCase()) |
| 60 | .map(TrackingResponse::from) | |
| 61 |
1
1. lambda$track$0 : replaced return value with null for com/rbctcsworld/ecommerce/fulfillment/FulfillmentService::lambda$track$0 → KILLED |
.orElseThrow(() -> new NotFoundException("Tracking number not found")); |
| 62 | } | |
| 63 | ||
| 64 | private CustomerOrder order(Long id) { | |
| 65 |
2
1. lambda$order$1 : replaced return value with null for com/rbctcsworld/ecommerce/fulfillment/FulfillmentService::lambda$order$1 → NO_COVERAGE 2. order : replaced return value with null for com/rbctcsworld/ecommerce/fulfillment/FulfillmentService::order → KILLED |
return orders.findById(id).orElseThrow(() -> new NotFoundException("Order not found: " + id)); |
| 66 | } | |
| 67 | ||
| 68 | /** e.g. UPS-482910384756 (carrier + 12 random digits, unique). */ | |
| 69 | String newTrackingNumber(String carrier) { | |
| 70 | String tn; | |
| 71 | do { | |
| 72 | StringBuilder sb = new StringBuilder(carrier).append('-'); | |
| 73 |
3
1. newTrackingNumber : Changed increment from 1 to -1 → TIMED_OUT 2. newTrackingNumber : removed conditional - replaced comparison check with false → KILLED 3. newTrackingNumber : changed conditional boundary → KILLED |
for (int i = 0; i < 12; i++) sb.append(RANDOM.nextInt(10)); |
| 74 | tn = sb.toString(); | |
| 75 |
1
1. newTrackingNumber : removed conditional - replaced equality check with false → TIMED_OUT |
} while (orders.existsByTrackingNumber(tn)); |
| 76 |
1
1. newTrackingNumber : replaced return value with "" for com/rbctcsworld/ecommerce/fulfillment/FulfillmentService::newTrackingNumber → KILLED |
return tn; |
| 77 | } | |
| 78 | } | |
Mutations | ||
| 38 |
1.1 |
|
| 40 |
1.1 |
|
| 41 |
1.1 |
|
| 42 |
1.1 |
|
| 43 |
1.1 |
|
| 50 |
1.1 |
|
| 51 |
1.1 |
|
| 52 |
1.1 |
|
| 53 |
1.1 |
|
| 59 |
1.1 |
|
| 61 |
1.1 |
|
| 65 |
1.1 2.2 |
|
| 73 |
1.1 2.2 3.3 |
|
| 75 |
1.1 |
|
| 76 |
1.1 |