|
|
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.ruoyi.app.flashdelivery.dto.*;
|
|
|
+import com.ruoyi.app.flashdelivery.exception.FlashDeliveryQuoteChangedException;
|
|
|
import com.ruoyi.app.flashdelivery.route.FlashDeliveryRouteService;
|
|
|
import com.ruoyi.app.flashdelivery.route.GeoPoint;
|
|
|
import com.ruoyi.app.flashdelivery.route.RouteDistance;
|
|
|
@@ -27,6 +28,9 @@ import java.net.URI;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
import java.security.SecureRandom;
|
|
|
+import java.time.Instant;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
|
import java.util.function.Function;
|
|
|
|
|
|
@@ -40,11 +44,14 @@ import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.*;
|
|
|
*/
|
|
|
@Service
|
|
|
public class FlashDeliveryApplicationService {
|
|
|
- private static final List<String> SERVICE_TYPES = List.of("HELP_SEND", "HELP_PICKUP", "URGENT");
|
|
|
+ private static final List<String> SERVICE_TYPES = List.of("HELP_SEND", "HELP_PICKUP");
|
|
|
+ private static final Set<String> DELIVERY_TYPES = Set.of("NORMAL", "URGENT");
|
|
|
private static final Set<String> PACKAGE_TYPES = Set.of("DOCUMENT", "GIFT", "CLOTHING", "BEAUTY",
|
|
|
"DAILY_NECESSITIES", "FOOD_INGREDIENTS", "ELECTRONICS", "SMALL_APPLIANCE", "OTHER");
|
|
|
- private static final Set<String> PACKAGE_SIZES = Set.of("SMALL", "MEDIUM", "LARGE");
|
|
|
private static final Set<String> DELIVERY_MODES = Set.of("NOW", "SCHEDULED");
|
|
|
+ private static final BigDecimal MAX_TOTAL_WEIGHT_KG = new BigDecimal("20.00");
|
|
|
+ private static final DateTimeFormatter PRICING_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
|
|
|
+ private static final ZoneId TAIPEI = ZoneId.of("Asia/Taipei");
|
|
|
private static final int MAX_DISTANCE_METERS = 40_000;
|
|
|
private static final BigDecimal MAX_PRICING_DISTANCE = new BigDecimal("999999.99");
|
|
|
private static final long SCHEDULE_SLOT_MILLIS = 30L * 60L * 1000L;
|
|
|
@@ -82,19 +89,27 @@ public class FlashDeliveryApplicationService {
|
|
|
|
|
|
public FlashDeliveryHomeView home() {
|
|
|
FlashDeliveryHomeView view = new FlashDeliveryHomeView();
|
|
|
- view.setServices(SERVICE_TYPES.stream()
|
|
|
- .map(this::findCurrentPricingOrNull)
|
|
|
- .filter(Objects::nonNull)
|
|
|
- .map(this::serviceView)
|
|
|
- .toList());
|
|
|
+ view.setServiceTypes(SERVICE_TYPES);
|
|
|
+ view.setDeliveryTypes(List.of("NORMAL", "URGENT"));
|
|
|
+ FlashDeliveryPricing pricing = findPricingAtOrNull(pricingTargetTime(new Date()));
|
|
|
+ view.setServices(pricing == null ? List.of() : SERVICE_TYPES.stream()
|
|
|
+ .map(serviceType -> serviceView(pricing, serviceType)).toList());
|
|
|
return view;
|
|
|
}
|
|
|
|
|
|
public FlashDeliveryQuoteView quote(FlashDeliveryQuoteRequest request) {
|
|
|
// 报价只使用服务端路线距离和当前启用的计价配置,避免客户端篡改金额。
|
|
|
QuoteContext context = calculateQuote(request);
|
|
|
+ return quoteView(request, context);
|
|
|
+ }
|
|
|
+
|
|
|
+ private FlashDeliveryQuoteView quoteView(FlashDeliveryQuoteRequest request, QuoteContext context) {
|
|
|
FlashDeliveryQuoteView view = new FlashDeliveryQuoteView();
|
|
|
- view.setServiceType(context.pricing().getServiceType());
|
|
|
+ view.setServiceType(request.getServiceType());
|
|
|
+ view.setDeliveryType(request.getDeliveryType());
|
|
|
+ view.setDeliveryMode(normalizeDeliveryMode(request.getDeliveryMode()));
|
|
|
+ view.setScheduledPickupStartAt(request.getScheduledPickupStartAt());
|
|
|
+ view.setScheduledPickupEndAt(request.getScheduledPickupEndAt());
|
|
|
view.setPricingId(context.pricing().getId());
|
|
|
view.setStartTime(context.pricing().getStartTime());
|
|
|
view.setEndTime(context.pricing().getEndTime());
|
|
|
@@ -105,6 +120,8 @@ public class FlashDeliveryApplicationService {
|
|
|
view.setStartingFare(context.pricing().getStartingFare());
|
|
|
view.setDistance(context.pricing().getDistance());
|
|
|
view.setFreight(context.pricing().getFreight());
|
|
|
+ view.setUrgentRate(context.pricing().getUrgentRate());
|
|
|
+ view.setMinimumUrgentFee(context.pricing().getMinimumUrgentFee());
|
|
|
applyBreakdown(view, context.breakdown());
|
|
|
view.setCurrency("TWD");
|
|
|
view.setPricingVersion(context.pricing().getConfigVersion());
|
|
|
@@ -126,6 +143,10 @@ public class FlashDeliveryApplicationService {
|
|
|
throw fail("flash.delivery.note.too.long");
|
|
|
}
|
|
|
QuoteContext quote = calculateQuote(request);
|
|
|
+ FlashDeliveryQuoteView latestQuote = quoteView(request, quote);
|
|
|
+ if (!matchesQuotedPrice(request, latestQuote)) {
|
|
|
+ throw new FlashDeliveryQuoteChangedException(latestQuote);
|
|
|
+ }
|
|
|
Date now = new Date();
|
|
|
String deliveryMode = normalizeDeliveryMode(request.getDeliveryMode());
|
|
|
validateSchedule(deliveryMode, request.getScheduledPickupStartAt(), request.getScheduledPickupEndAt(), now);
|
|
|
@@ -136,9 +157,13 @@ public class FlashDeliveryApplicationService {
|
|
|
order.setClientRequestId(requestId);
|
|
|
order.setUserId(userId);
|
|
|
order.setServiceType(request.getServiceType());
|
|
|
+ order.setDeliveryType(request.getDeliveryType());
|
|
|
order.setStatus(WAITING_ACCEPTANCE);
|
|
|
order.setPackageType(request.getPackageType());
|
|
|
- order.setPackageSize(request.getPackageSize());
|
|
|
+ order.setQuantity(request.getQuantity());
|
|
|
+ order.setTotalWeightKg(scaleWeight(request.getTotalWeightKg()));
|
|
|
+ order.setSpecification(trimToNull(request.getSpecification()));
|
|
|
+ order.setPackageSize(packageSize(request.getTotalWeightKg()));
|
|
|
order.setDeliveryMode(deliveryMode);
|
|
|
order.setScheduledPickupStartAt("SCHEDULED".equals(deliveryMode) ? request.getScheduledPickupStartAt() : null);
|
|
|
order.setScheduledPickupEndAt("SCHEDULED".equals(deliveryMode) ? request.getScheduledPickupEndAt() : null);
|
|
|
@@ -162,6 +187,11 @@ public class FlashDeliveryApplicationService {
|
|
|
order.setFreight(quote.pricing().getFreight());
|
|
|
// 保存计价快照,后台调价只影响新订单,不追溯修改历史订单金额。
|
|
|
order.setDistanceFee(quote.breakdown().getDistanceFee());
|
|
|
+ order.setBaseDeliveryFee(quote.breakdown().getBaseDeliveryFee());
|
|
|
+ order.setUrgentRate(quote.pricing().getUrgentRate());
|
|
|
+ order.setMinimumUrgentFee(quote.pricing().getMinimumUrgentFee());
|
|
|
+ order.setUrgentFee(quote.breakdown().getUrgentFee());
|
|
|
+ order.setTipAmount(quote.breakdown().getTipAmount());
|
|
|
order.setUserNote(trimToNull(request.getUserNote()));
|
|
|
order.setVersion(0);
|
|
|
order.setCreateTime(now);
|
|
|
@@ -274,7 +304,7 @@ public class FlashDeliveryApplicationService {
|
|
|
if (!rider.supportsDeliveryType("FLASH")) throw fail("flash.delivery.rider.type.not.enabled");
|
|
|
return riderDeliveryLockService.withLock(riderId, () -> {
|
|
|
FlashDeliveryOrder target = requireOrder(orderId);
|
|
|
- riderDeliveryExclusivityService.assertCanAcceptFlash(riderId, target.getServiceType());
|
|
|
+ riderDeliveryExclusivityService.assertCanAcceptFlash(riderId, target.getDeliveryType());
|
|
|
Date now = new Date();
|
|
|
// Mapper 使用“待接单且 rider_id 为空”的条件更新,受影响行数为 1 才表示抢单成功。
|
|
|
if (orderMapper.accept(orderId, riderId, now) != 1) {
|
|
|
@@ -306,13 +336,13 @@ public class FlashDeliveryApplicationService {
|
|
|
|
|
|
public List<FlashDeliveryPricing> adminPricing() {
|
|
|
return pricingMapper.selectList(new QueryWrapper<FlashDeliveryPricing>()
|
|
|
- .orderByAsc("service_type", "start_time", "id"));
|
|
|
+ .orderByAsc("start_time", "id"));
|
|
|
}
|
|
|
|
|
|
@Transactional
|
|
|
public FlashDeliveryPricing createPricing(Long adminId, FlashDeliveryPricingRequest request) {
|
|
|
validatePricingRequest(request, false);
|
|
|
- lockPricingServices(request.getServiceType());
|
|
|
+ lockPricing();
|
|
|
ensureNoPricingOverlap(request, null);
|
|
|
Date now = new Date();
|
|
|
FlashDeliveryPricing pricing = pricingFrom(request);
|
|
|
@@ -330,35 +360,34 @@ public class FlashDeliveryApplicationService {
|
|
|
validatePricingRequest(request, true);
|
|
|
FlashDeliveryPricing preliminary = pricingId == null ? null : pricingMapper.selectById(pricingId);
|
|
|
if (preliminary == null) throw fail("flash.delivery.pricing.not.found");
|
|
|
- lockPricingServices(preliminary.getServiceType(), request.getServiceType());
|
|
|
+ lockPricing();
|
|
|
FlashDeliveryPricing pricing = pricingMapper.selectById(pricingId);
|
|
|
if (pricing == null) throw fail("flash.delivery.pricing.not.found");
|
|
|
- if (!Objects.equals(preliminary.getServiceType(), pricing.getServiceType())) {
|
|
|
- throw fail("flash.delivery.pricing.changed");
|
|
|
- }
|
|
|
ensureNoPricingOverlap(request, pricingId);
|
|
|
Date now = new Date();
|
|
|
UpdateWrapper<FlashDeliveryPricing> update = new UpdateWrapper<FlashDeliveryPricing>()
|
|
|
.eq("id", pricingId).eq("config_version", request.getConfigVersion())
|
|
|
- .set("service_type", request.getServiceType())
|
|
|
.set("start_time", request.getStartTime())
|
|
|
.set("end_time", request.getEndTime())
|
|
|
.set("starting_distance", scaleDistance(request.getStartingDistance()))
|
|
|
.set("starting_fare", request.getStartingFare())
|
|
|
.set("distance", scaleDistance(request.getDistance()))
|
|
|
.set("freight", request.getFreight())
|
|
|
+ .set("urgent_rate", normalizeRate(request.getUrgentRate()))
|
|
|
+ .set("minimum_urgent_fee", request.getMinimumUrgentFee())
|
|
|
.set("updated_by", adminId)
|
|
|
.set("update_time", now)
|
|
|
.setSql("config_version = config_version + 1");
|
|
|
// 版本条件防止两个管理员同时保存时后提交者静默覆盖先提交者。
|
|
|
if (pricingMapper.update(null, update) != 1) throw fail("flash.delivery.pricing.changed");
|
|
|
- pricing.setServiceType(request.getServiceType());
|
|
|
pricing.setStartTime(request.getStartTime());
|
|
|
pricing.setEndTime(request.getEndTime());
|
|
|
pricing.setStartingDistance(scaleDistance(request.getStartingDistance()));
|
|
|
pricing.setStartingFare(request.getStartingFare());
|
|
|
pricing.setDistance(scaleDistance(request.getDistance()));
|
|
|
pricing.setFreight(request.getFreight());
|
|
|
+ pricing.setUrgentRate(normalizeRate(request.getUrgentRate()));
|
|
|
+ pricing.setMinimumUrgentFee(request.getMinimumUrgentFee());
|
|
|
pricing.setConfigVersion(request.getConfigVersion() + 1);
|
|
|
pricing.setUpdatedBy(adminId);
|
|
|
pricing.setUpdateTime(now);
|
|
|
@@ -371,12 +400,9 @@ public class FlashDeliveryApplicationService {
|
|
|
if (preliminary == null) {
|
|
|
throw fail("flash.delivery.pricing.not.found");
|
|
|
}
|
|
|
- lockPricingServices(preliminary.getServiceType());
|
|
|
+ lockPricing();
|
|
|
FlashDeliveryPricing pricing = pricingMapper.selectById(pricingId);
|
|
|
if (pricing == null) throw fail("flash.delivery.pricing.not.found");
|
|
|
- if (!Objects.equals(preliminary.getServiceType(), pricing.getServiceType())) {
|
|
|
- throw fail("flash.delivery.pricing.changed");
|
|
|
- }
|
|
|
if (pricingMapper.deleteById(pricingId) != 1) throw fail("flash.delivery.pricing.not.found");
|
|
|
}
|
|
|
|
|
|
@@ -431,12 +457,14 @@ public class FlashDeliveryApplicationService {
|
|
|
private QuoteContext calculateQuote(FlashDeliveryQuoteRequest request) {
|
|
|
if (request == null) throw fail("flash.delivery.request.required");
|
|
|
validateServiceType(request.getServiceType());
|
|
|
+ validateDeliveryType(request.getDeliveryType());
|
|
|
if (!hasText(request.getPackageType()) || !PACKAGE_TYPES.contains(request.getPackageType())) {
|
|
|
throw fail("flash.delivery.package.type.invalid");
|
|
|
}
|
|
|
- if (!hasText(request.getPackageSize()) || !PACKAGE_SIZES.contains(request.getPackageSize())) {
|
|
|
- throw fail("flash.delivery.package.size.invalid");
|
|
|
- }
|
|
|
+ validateItem(request);
|
|
|
+ Date now = new Date();
|
|
|
+ String deliveryMode = normalizeDeliveryMode(request.getDeliveryMode());
|
|
|
+ validateSchedule(deliveryMode, request.getScheduledPickupStartAt(), request.getScheduledPickupEndAt(), now);
|
|
|
validateAddress(request.getPickup());
|
|
|
validateAddress(request.getDelivery());
|
|
|
if (sameAddressText(request.getPickup(), request.getDelivery())
|
|
|
@@ -444,25 +472,26 @@ public class FlashDeliveryApplicationService {
|
|
|
&& request.getPickup().getLongitude().compareTo(request.getDelivery().getLongitude()) == 0) {
|
|
|
throw fail("flash.delivery.address.same");
|
|
|
}
|
|
|
- FlashDeliveryPricing pricing = findCurrentPricing(request.getServiceType());
|
|
|
+ Date pricingDate = "SCHEDULED".equals(deliveryMode) ? request.getScheduledPickupStartAt() : now;
|
|
|
+ FlashDeliveryPricing pricing = findPricingAt(pricingTargetTime(pricingDate));
|
|
|
// 路线服务优先使用 Google Routes;不可用时在服务内部降级为球面直线距离。
|
|
|
RouteDistance route = routeService.calculate(
|
|
|
new GeoPoint(request.getPickup().getLatitude(), request.getPickup().getLongitude()),
|
|
|
new GeoPoint(request.getDelivery().getLatitude(), request.getDelivery().getLongitude()));
|
|
|
if (route.distanceMeters() > MAX_DISTANCE_METERS) throw fail("flash.delivery.distance.too.far");
|
|
|
FlashDeliveryPriceBreakdown breakdown = pricingCalculator.calculateBreakdown(pricing,
|
|
|
- route.distanceMeters());
|
|
|
+ route.distanceMeters(), request.getDeliveryType(), request.getTipAmount());
|
|
|
return new QuoteContext(pricing, route, breakdown);
|
|
|
}
|
|
|
|
|
|
- private FlashDeliveryPricing findCurrentPricing(String serviceType) {
|
|
|
- FlashDeliveryPricing pricing = findCurrentPricingOrNull(serviceType);
|
|
|
+ private FlashDeliveryPricing findPricingAt(String targetTime) {
|
|
|
+ FlashDeliveryPricing pricing = findPricingAtOrNull(targetTime);
|
|
|
if (pricing == null) throw fail("flash.delivery.pricing.not.available");
|
|
|
return pricing;
|
|
|
}
|
|
|
|
|
|
- private FlashDeliveryPricing findCurrentPricingOrNull(String serviceType) {
|
|
|
- List<FlashDeliveryPricing> pricing = pricingMapper.selectCurrent(serviceType);
|
|
|
+ private FlashDeliveryPricing findPricingAtOrNull(String targetTime) {
|
|
|
+ List<FlashDeliveryPricing> pricing = pricingMapper.selectAtTime(targetTime);
|
|
|
if (pricing == null || pricing.isEmpty()) return null;
|
|
|
if (pricing.size() > 1) throw fail("flash.delivery.pricing.overlap");
|
|
|
return pricing.get(0);
|
|
|
@@ -470,9 +499,9 @@ public class FlashDeliveryApplicationService {
|
|
|
|
|
|
private void validatePricingRequest(FlashDeliveryPricingRequest request, boolean requireVersion) {
|
|
|
if (request == null) throw fail("flash.delivery.pricing.invalid");
|
|
|
- validateServiceType(request.getServiceType());
|
|
|
BigDecimal startingDistance = normalizeDistance(request.getStartingDistance());
|
|
|
BigDecimal distance = normalizeDistance(request.getDistance());
|
|
|
+ BigDecimal urgentRate = normalizeRate(request.getUrgentRate());
|
|
|
if (!validStartTime(request.getStartTime()) || !validEndTime(request.getEndTime())
|
|
|
|| toMinute(request.getStartTime()) >= toMinute(request.getEndTime())
|
|
|
|| startingDistance == null || startingDistance.signum() <= 0
|
|
|
@@ -481,39 +510,36 @@ public class FlashDeliveryApplicationService {
|
|
|
|| distance == null || distance.signum() <= 0
|
|
|
|| distance.compareTo(MAX_PRICING_DISTANCE) > 0
|
|
|
|| request.getFreight() == null || request.getFreight() <= 0
|
|
|
+ || urgentRate == null || urgentRate.signum() < 0
|
|
|
+ || urgentRate.compareTo(MAX_PRICING_DISTANCE) > 0
|
|
|
+ || request.getMinimumUrgentFee() == null || request.getMinimumUrgentFee() < 0
|
|
|
|| requireVersion && (request.getConfigVersion() == null || request.getConfigVersion() <= 0)) {
|
|
|
throw fail("flash.delivery.pricing.invalid");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void ensureNoPricingOverlap(FlashDeliveryPricingRequest request, Long excludedId) {
|
|
|
- if (pricingMapper.countOverlapping(request.getServiceType(), request.getStartTime(),
|
|
|
- request.getEndTime(), excludedId) > 0) {
|
|
|
+ if (pricingMapper.countOverlapping(request.getStartTime(), request.getEndTime(), excludedId) > 0) {
|
|
|
throw fail("flash.delivery.pricing.overlap");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void lockPricingServices(String... serviceTypes) {
|
|
|
- Arrays.stream(serviceTypes)
|
|
|
- .filter(Objects::nonNull)
|
|
|
- .distinct()
|
|
|
- .sorted()
|
|
|
- .forEach(serviceType -> {
|
|
|
- if (!Objects.equals(serviceType, pricingMapper.lockServiceType(serviceType))) {
|
|
|
- throw fail("flash.delivery.pricing.invalid");
|
|
|
- }
|
|
|
- });
|
|
|
+ private void lockPricing() {
|
|
|
+ if (!Objects.equals("GLOBAL", pricingMapper.lockPricing())) {
|
|
|
+ throw fail("flash.delivery.pricing.invalid");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private FlashDeliveryPricing pricingFrom(FlashDeliveryPricingRequest request) {
|
|
|
FlashDeliveryPricing pricing = new FlashDeliveryPricing();
|
|
|
- pricing.setServiceType(request.getServiceType());
|
|
|
pricing.setStartTime(request.getStartTime());
|
|
|
pricing.setEndTime(request.getEndTime());
|
|
|
pricing.setStartingDistance(scaleDistance(request.getStartingDistance()));
|
|
|
pricing.setStartingFare(request.getStartingFare());
|
|
|
pricing.setDistance(scaleDistance(request.getDistance()));
|
|
|
pricing.setFreight(request.getFreight());
|
|
|
+ pricing.setUrgentRate(normalizeRate(request.getUrgentRate()));
|
|
|
+ pricing.setMinimumUrgentFee(request.getMinimumUrgentFee());
|
|
|
return pricing;
|
|
|
}
|
|
|
|
|
|
@@ -538,6 +564,10 @@ public class FlashDeliveryApplicationService {
|
|
|
return value == null ? null : value.setScale(2, RoundingMode.HALF_UP);
|
|
|
}
|
|
|
|
|
|
+ private BigDecimal normalizeRate(BigDecimal value) {
|
|
|
+ return value == null ? null : value.setScale(2, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+
|
|
|
private void transitionWithProof(Long riderId, Long orderId, FlashDeliveryProofRequest request,
|
|
|
String expected, String next, String proofType, String timeColumn) {
|
|
|
List<String> urls = validateProofUrls(request == null ? null : request.getImageUrls(), true);
|
|
|
@@ -609,8 +639,11 @@ public class FlashDeliveryApplicationService {
|
|
|
view.setId(order.getId());
|
|
|
view.setOrderNo(order.getOrderNo());
|
|
|
view.setServiceType(order.getServiceType());
|
|
|
+ view.setDeliveryType(order.getDeliveryType());
|
|
|
view.setStatus(order.getStatus());
|
|
|
view.setPackageType(order.getPackageType());
|
|
|
+ view.setQuantity(order.getQuantity());
|
|
|
+ view.setTotalWeightKg(order.getTotalWeightKg());
|
|
|
view.setDeliveryMode(order.getDeliveryMode());
|
|
|
view.setScheduledPickupStartAt(order.getScheduledPickupStartAt());
|
|
|
view.setScheduledPickupEndAt(order.getScheduledPickupEndAt());
|
|
|
@@ -619,6 +652,9 @@ public class FlashDeliveryApplicationService {
|
|
|
view.setDeliveryAddress(order.getDeliveryAddress());
|
|
|
view.setDeliveryDetailAddress(order.getDeliveryAddressDetail());
|
|
|
view.setEstimatedDurationSeconds(order.getEstimatedDurationSeconds());
|
|
|
+ view.setBaseDeliveryFee(order.getBaseDeliveryFee());
|
|
|
+ view.setUrgentFee(order.getUrgentFee());
|
|
|
+ view.setTipAmount(order.getTipAmount());
|
|
|
view.setAmount(order.getAmount());
|
|
|
view.setCurrency(order.getCurrency());
|
|
|
view.setDeliveredAt(order.getDeliveredAt());
|
|
|
@@ -632,8 +668,12 @@ public class FlashDeliveryApplicationService {
|
|
|
view.setId(order.getId());
|
|
|
view.setOrderNo(order.getOrderNo());
|
|
|
view.setServiceType(order.getServiceType());
|
|
|
+ view.setDeliveryType(order.getDeliveryType());
|
|
|
view.setStatus(order.getStatus());
|
|
|
view.setPackageType(order.getPackageType());
|
|
|
+ view.setQuantity(order.getQuantity());
|
|
|
+ view.setTotalWeightKg(order.getTotalWeightKg());
|
|
|
+ view.setSpecification(order.getSpecification());
|
|
|
view.setPackageSize(order.getPackageSize());
|
|
|
view.setDeliveryMode(order.getDeliveryMode());
|
|
|
view.setScheduledPickupStartAt(order.getScheduledPickupStartAt());
|
|
|
@@ -647,6 +687,10 @@ public class FlashDeliveryApplicationService {
|
|
|
order.getPickupLongitude(), order.getPickupLatitude()));
|
|
|
view.setDistanceMeters(order.getDistanceMeters());
|
|
|
view.setEstimatedDurationSeconds(order.getEstimatedDurationSeconds());
|
|
|
+ view.setBaseDeliveryFee(order.getBaseDeliveryFee());
|
|
|
+ view.setDistanceFee(order.getDistanceFee());
|
|
|
+ view.setUrgentFee(order.getUrgentFee());
|
|
|
+ view.setTipAmount(order.getTipAmount());
|
|
|
view.setAmount(order.getAmount());
|
|
|
view.setCurrency(order.getCurrency());
|
|
|
view.setCreateTime(order.getCreateTime());
|
|
|
@@ -673,9 +717,9 @@ public class FlashDeliveryApplicationService {
|
|
|
view.setDeliveryImageUrls(delivery);
|
|
|
}
|
|
|
|
|
|
- private FlashDeliveryServiceView serviceView(FlashDeliveryPricing pricing) {
|
|
|
+ private FlashDeliveryServiceView serviceView(FlashDeliveryPricing pricing, String serviceType) {
|
|
|
FlashDeliveryServiceView view = new FlashDeliveryServiceView();
|
|
|
- view.setServiceType(pricing.getServiceType());
|
|
|
+ view.setServiceType(serviceType);
|
|
|
view.setPricingId(pricing.getId());
|
|
|
view.setStartTime(pricing.getStartTime());
|
|
|
view.setEndTime(pricing.getEndTime());
|
|
|
@@ -683,6 +727,8 @@ public class FlashDeliveryApplicationService {
|
|
|
view.setStartingFare(pricing.getStartingFare());
|
|
|
view.setDistance(pricing.getDistance());
|
|
|
view.setFreight(pricing.getFreight());
|
|
|
+ view.setUrgentRate(pricing.getUrgentRate());
|
|
|
+ view.setMinimumUrgentFee(pricing.getMinimumUrgentFee());
|
|
|
view.setPricingVersion(pricing.getConfigVersion());
|
|
|
view.setCurrency("TWD");
|
|
|
return view;
|
|
|
@@ -693,8 +739,12 @@ public class FlashDeliveryApplicationService {
|
|
|
view.setId(order.getId());
|
|
|
view.setOrderNo(order.getOrderNo());
|
|
|
view.setServiceType(order.getServiceType());
|
|
|
+ view.setDeliveryType(order.getDeliveryType());
|
|
|
view.setStatus(order.getStatus());
|
|
|
view.setPackageType(order.getPackageType());
|
|
|
+ view.setQuantity(order.getQuantity());
|
|
|
+ view.setTotalWeightKg(order.getTotalWeightKg());
|
|
|
+ view.setSpecification(order.getSpecification());
|
|
|
view.setPackageSize(order.getPackageSize());
|
|
|
view.setDeliveryMode(order.getDeliveryMode());
|
|
|
view.setScheduledPickupStartAt(order.getScheduledPickupStartAt());
|
|
|
@@ -705,6 +755,10 @@ public class FlashDeliveryApplicationService {
|
|
|
view.setDistanceMeters(order.getDistanceMeters());
|
|
|
view.setDistanceSource(order.getDistanceSource());
|
|
|
view.setEstimatedDurationSeconds(order.getEstimatedDurationSeconds());
|
|
|
+ view.setBaseDeliveryFee(order.getBaseDeliveryFee());
|
|
|
+ view.setDistanceFee(order.getDistanceFee());
|
|
|
+ view.setUrgentFee(order.getUrgentFee());
|
|
|
+ view.setTipAmount(order.getTipAmount());
|
|
|
view.setAmount(order.getAmount());
|
|
|
view.setCurrency(order.getCurrency());
|
|
|
view.setUserNote(includeSensitiveFields ? order.getUserNote() : null);
|
|
|
@@ -760,6 +814,9 @@ public class FlashDeliveryApplicationService {
|
|
|
private void applyBreakdown(FlashDeliveryQuoteView view, FlashDeliveryPriceBreakdown breakdown) {
|
|
|
view.setBillableDistance(breakdown.getBillableDistance());
|
|
|
view.setDistanceFee(breakdown.getDistanceFee());
|
|
|
+ view.setBaseDeliveryFee(breakdown.getBaseDeliveryFee());
|
|
|
+ view.setUrgentFee(breakdown.getUrgentFee());
|
|
|
+ view.setTipAmount(breakdown.getTipAmount());
|
|
|
view.setAmount(breakdown.getAmount());
|
|
|
}
|
|
|
|
|
|
@@ -848,6 +905,49 @@ public class FlashDeliveryApplicationService {
|
|
|
if (!hasText(value) || !SERVICE_TYPES.contains(value)) throw fail("flash.delivery.service.type.invalid");
|
|
|
}
|
|
|
|
|
|
+ private void validateDeliveryType(String value) {
|
|
|
+ if (!hasText(value) || !DELIVERY_TYPES.contains(value)) {
|
|
|
+ throw fail("flash.delivery.delivery.type.invalid");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validateItem(FlashDeliveryQuoteRequest request) {
|
|
|
+ BigDecimal weight = request.getTotalWeightKg();
|
|
|
+ if (request.getQuantity() == null || request.getQuantity() <= 0
|
|
|
+ || weight == null || weight.signum() <= 0
|
|
|
+ || weight.scale() > 2 || weight.compareTo(MAX_TOTAL_WEIGHT_KG) > 0
|
|
|
+ || tooLong(request.getSpecification(), 255)) {
|
|
|
+ throw fail("flash.delivery.item.invalid");
|
|
|
+ }
|
|
|
+ if (request.getTipAmount() == null || request.getTipAmount() < 0) {
|
|
|
+ throw fail("flash.delivery.tip.invalid");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String packageSize(BigDecimal weight) {
|
|
|
+ if (weight.compareTo(new BigDecimal("5.00")) <= 0) return "SMALL";
|
|
|
+ if (weight.compareTo(new BigDecimal("12.00")) <= 0) return "MEDIUM";
|
|
|
+ return "LARGE";
|
|
|
+ }
|
|
|
+
|
|
|
+ private BigDecimal scaleWeight(BigDecimal weight) {
|
|
|
+ return weight.setScale(2, RoundingMode.UNNECESSARY);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String pricingTargetTime(Date date) {
|
|
|
+ return PRICING_TIME_FORMAT.format(Instant.ofEpochMilli(date.getTime())
|
|
|
+ .atZone(TAIPEI).toLocalTime());
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean matchesQuotedPrice(FlashDeliveryCreateRequest request, FlashDeliveryQuoteView latest) {
|
|
|
+ return Objects.equals(request.getPricingId(), latest.getPricingId())
|
|
|
+ && Objects.equals(request.getPricingVersion(), latest.getPricingVersion())
|
|
|
+ && Objects.equals(request.getQuotedBaseDeliveryFee(), latest.getBaseDeliveryFee())
|
|
|
+ && Objects.equals(request.getQuotedDistanceFee(), latest.getDistanceFee())
|
|
|
+ && Objects.equals(request.getQuotedUrgentFee(), latest.getUrgentFee())
|
|
|
+ && Objects.equals(request.getQuotedAmount(), latest.getAmount());
|
|
|
+ }
|
|
|
+
|
|
|
private void validateOptionalServiceType(String value) {
|
|
|
if (hasText(value)) validateServiceType(value);
|
|
|
}
|