From 3646ee9971af6877245633ed27fd2dbac24b948f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?= Date: Tue, 7 Jul 2026 15:42:31 +0800 Subject: [PATCH] feat: add express pickup orders --- .../linhelp/express/ExpressCancelRequest.java | 13 + .../express/ExpressFeeAdjustRequest.java | 28 +++ .../linhelp/express/ExpressFeeCalculator.java | 13 + .../express/ExpressOfflinePayRequest.java | 18 ++ .../express/ExpressOrderController.java | 96 ++++++++ .../express/ExpressOrderLogResponse.java | 51 ++++ .../linhelp/express/ExpressOrderRequest.java | 101 ++++++++ .../linhelp/express/ExpressOrderResponse.java | 218 +++++++++++++++++ .../linhelp/express/ExpressOrderService.java | 229 ++++++++++++++++++ .../com/linhelp/user/UserAddressService.java | 4 + .../express/ExpressFeeCalculatorTests.java | 24 ++ .../express/ExpressOrderServiceTests.java | 79 ++++++ 12 files changed, 874 insertions(+) create mode 100644 backend/src/main/java/com/linhelp/express/ExpressCancelRequest.java create mode 100644 backend/src/main/java/com/linhelp/express/ExpressFeeAdjustRequest.java create mode 100644 backend/src/main/java/com/linhelp/express/ExpressFeeCalculator.java create mode 100644 backend/src/main/java/com/linhelp/express/ExpressOfflinePayRequest.java create mode 100644 backend/src/main/java/com/linhelp/express/ExpressOrderController.java create mode 100644 backend/src/main/java/com/linhelp/express/ExpressOrderLogResponse.java create mode 100644 backend/src/main/java/com/linhelp/express/ExpressOrderRequest.java create mode 100644 backend/src/main/java/com/linhelp/express/ExpressOrderResponse.java create mode 100644 backend/src/main/java/com/linhelp/express/ExpressOrderService.java create mode 100644 backend/src/test/java/com/linhelp/express/ExpressFeeCalculatorTests.java create mode 100644 backend/src/test/java/com/linhelp/express/ExpressOrderServiceTests.java diff --git a/backend/src/main/java/com/linhelp/express/ExpressCancelRequest.java b/backend/src/main/java/com/linhelp/express/ExpressCancelRequest.java new file mode 100644 index 0000000..57bb975 --- /dev/null +++ b/backend/src/main/java/com/linhelp/express/ExpressCancelRequest.java @@ -0,0 +1,13 @@ +package com.linhelp.express; + +public class ExpressCancelRequest { + private String reason; + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/backend/src/main/java/com/linhelp/express/ExpressFeeAdjustRequest.java b/backend/src/main/java/com/linhelp/express/ExpressFeeAdjustRequest.java new file mode 100644 index 0000000..33292fd --- /dev/null +++ b/backend/src/main/java/com/linhelp/express/ExpressFeeAdjustRequest.java @@ -0,0 +1,28 @@ +package com.linhelp.express; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; + +public class ExpressFeeAdjustRequest { + @Min(0) + private int feeCent; + + @NotBlank + private String reason; + + public int getFeeCent() { + return feeCent; + } + + public void setFeeCent(int feeCent) { + this.feeCent = feeCent; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/backend/src/main/java/com/linhelp/express/ExpressFeeCalculator.java b/backend/src/main/java/com/linhelp/express/ExpressFeeCalculator.java new file mode 100644 index 0000000..42a320e --- /dev/null +++ b/backend/src/main/java/com/linhelp/express/ExpressFeeCalculator.java @@ -0,0 +1,13 @@ +package com.linhelp.express; + +public final class ExpressFeeCalculator { + private ExpressFeeCalculator() { + } + + public static int calculateCent(int packageCount) { + if (packageCount <= 0) { + throw new IllegalArgumentException("件数必须大于0"); + } + return 300 + Math.max(0, packageCount - 1) * 100; + } +} diff --git a/backend/src/main/java/com/linhelp/express/ExpressOfflinePayRequest.java b/backend/src/main/java/com/linhelp/express/ExpressOfflinePayRequest.java new file mode 100644 index 0000000..6eb534d --- /dev/null +++ b/backend/src/main/java/com/linhelp/express/ExpressOfflinePayRequest.java @@ -0,0 +1,18 @@ +package com.linhelp.express; + +import com.linhelp.common.enums.OfflinePayStatus; + +import javax.validation.constraints.NotNull; + +public class ExpressOfflinePayRequest { + @NotNull + private OfflinePayStatus status; + + public OfflinePayStatus getStatus() { + return status; + } + + public void setStatus(OfflinePayStatus status) { + this.status = status; + } +} diff --git a/backend/src/main/java/com/linhelp/express/ExpressOrderController.java b/backend/src/main/java/com/linhelp/express/ExpressOrderController.java new file mode 100644 index 0000000..174339f --- /dev/null +++ b/backend/src/main/java/com/linhelp/express/ExpressOrderController.java @@ -0,0 +1,96 @@ +package com.linhelp.express; + +import com.linhelp.common.api.ApiResponse; +import com.linhelp.common.enums.ExpressOrderStatus; +import com.linhelp.common.security.AuthService; +import com.linhelp.common.security.CurrentUser; +import com.linhelp.common.security.RequireRole; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.List; + +@RestController +public class ExpressOrderController { + private final ExpressOrderService expressOrderService; + private final AuthService authService; + + public ExpressOrderController(ExpressOrderService expressOrderService, AuthService authService) { + this.expressOrderService = expressOrderService; + this.authService = authService; + } + + @PostMapping("/api/mini/express-orders") + public ApiResponse create(@Valid @RequestBody ExpressOrderRequest request) { + CurrentUser user = authService.currentUser(); + request.setTenantId(user.getTenantId()); + request.setCommunityId(user.getCommunityId()); + return ApiResponse.ok(expressOrderService.create(user.getUserId(), request)); + } + + @GetMapping("/api/mini/express-orders") + public ApiResponse> listMine() { + CurrentUser user = authService.currentUser(); + return ApiResponse.ok(expressOrderService.listMine(user.getUserId())); + } + + @GetMapping("/api/mini/express-orders/{id}") + public ApiResponse miniDetail(@PathVariable Long id) { + return ApiResponse.ok(expressOrderService.detail(id)); + } + + @PutMapping("/api/mini/express-orders/{id}/cancel") + public ApiResponse miniCancel(@PathVariable Long id, @RequestBody(required = false) ExpressCancelRequest request) { + return ApiResponse.ok(expressOrderService.cancel(id, request == null ? null : request.getReason())); + } + + @PutMapping("/api/mini/express-orders/{id}/complete") + public ApiResponse miniComplete(@PathVariable Long id) { + return ApiResponse.ok(expressOrderService.complete(id)); + } + + @GetMapping("/api/admin/express-orders") + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"}) + public ApiResponse> listAdmin(@RequestParam(required = false) ExpressOrderStatus status) { + CurrentUser user = authService.currentUser(); + return ApiResponse.ok(expressOrderService.listAdmin(user.getCommunityId(), status)); + } + + @GetMapping("/api/admin/express-orders/{id}") + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"}) + public ApiResponse adminDetail(@PathVariable Long id) { + return ApiResponse.ok(expressOrderService.detail(id)); + } + + @PutMapping("/api/admin/express-orders/{id}/confirm") + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"}) + public ApiResponse confirm(@PathVariable Long id) { + return ApiResponse.ok(expressOrderService.confirm(id)); + } + + @PutMapping("/api/admin/express-orders/{id}/adjust-fee") + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"}) + public ApiResponse adjustFee(@PathVariable Long id, + @Valid @RequestBody ExpressFeeAdjustRequest request) { + return ApiResponse.ok(expressOrderService.adjustFee(id, request.getFeeCent(), request.getReason())); + } + + @PutMapping("/api/admin/express-orders/{id}/cancel") + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"}) + public ApiResponse adminCancel(@PathVariable Long id, @RequestBody(required = false) ExpressCancelRequest request) { + return ApiResponse.ok(expressOrderService.cancel(id, request == null ? null : request.getReason())); + } + + @PutMapping("/api/admin/express-orders/{id}/offline-pay-status") + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"}) + public ApiResponse updateOfflinePayStatus(@PathVariable Long id, + @Valid @RequestBody ExpressOfflinePayRequest request) { + return ApiResponse.ok(expressOrderService.updateOfflinePayStatus(id, request.getStatus())); + } +} diff --git a/backend/src/main/java/com/linhelp/express/ExpressOrderLogResponse.java b/backend/src/main/java/com/linhelp/express/ExpressOrderLogResponse.java new file mode 100644 index 0000000..916c1a7 --- /dev/null +++ b/backend/src/main/java/com/linhelp/express/ExpressOrderLogResponse.java @@ -0,0 +1,51 @@ +package com.linhelp.express; + +import java.time.LocalDateTime; + +public class ExpressOrderLogResponse { + private Long id; + private Long orderId; + private String action; + private String content; + private LocalDateTime createdAt; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } +} diff --git a/backend/src/main/java/com/linhelp/express/ExpressOrderRequest.java b/backend/src/main/java/com/linhelp/express/ExpressOrderRequest.java new file mode 100644 index 0000000..f1d5b9a --- /dev/null +++ b/backend/src/main/java/com/linhelp/express/ExpressOrderRequest.java @@ -0,0 +1,101 @@ +package com.linhelp.express; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +public class ExpressOrderRequest { + private Long tenantId; + private Long communityId; + + @NotNull + private Long addressId; + + @NotBlank + private String expressCompany; + + @NotBlank + private String pickupCode; + + @NotBlank + private String pickupAddress; + + private String receiverPhone; + + @Min(1) + private int packageCount = 1; + + private String remark; + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getCommunityId() { + return communityId; + } + + public void setCommunityId(Long communityId) { + this.communityId = communityId; + } + + public Long getAddressId() { + return addressId; + } + + public void setAddressId(Long addressId) { + this.addressId = addressId; + } + + public String getExpressCompany() { + return expressCompany; + } + + public void setExpressCompany(String expressCompany) { + this.expressCompany = expressCompany; + } + + public String getPickupCode() { + return pickupCode; + } + + public void setPickupCode(String pickupCode) { + this.pickupCode = pickupCode; + } + + public String getPickupAddress() { + return pickupAddress; + } + + public void setPickupAddress(String pickupAddress) { + this.pickupAddress = pickupAddress; + } + + public String getReceiverPhone() { + return receiverPhone; + } + + public void setReceiverPhone(String receiverPhone) { + this.receiverPhone = receiverPhone; + } + + public int getPackageCount() { + return packageCount; + } + + public void setPackageCount(int packageCount) { + this.packageCount = packageCount; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } +} diff --git a/backend/src/main/java/com/linhelp/express/ExpressOrderResponse.java b/backend/src/main/java/com/linhelp/express/ExpressOrderResponse.java new file mode 100644 index 0000000..ddb758e --- /dev/null +++ b/backend/src/main/java/com/linhelp/express/ExpressOrderResponse.java @@ -0,0 +1,218 @@ +package com.linhelp.express; + +import com.linhelp.common.enums.ExpressOrderStatus; +import com.linhelp.common.enums.OfflinePayStatus; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public class ExpressOrderResponse { + private Long id; + private String orderNo; + private Long tenantId; + private Long communityId; + private Long userId; + private Long addressId; + private String contactName; + private String contactPhone; + private String deliveryAddress; + private String expressCompany; + private String pickupCode; + private String pickupAddress; + private String receiverPhone; + private int packageCount; + private int feeCent; + private OfflinePayStatus offlinePayStatus; + private ExpressOrderStatus status; + private String remark; + private String cancelReason; + private String deliveredPhotoUrl; + private List logs = new ArrayList(); + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getOrderNo() { + return orderNo; + } + + public void setOrderNo(String orderNo) { + this.orderNo = orderNo; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getCommunityId() { + return communityId; + } + + public void setCommunityId(Long communityId) { + this.communityId = communityId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getAddressId() { + return addressId; + } + + public void setAddressId(Long addressId) { + this.addressId = addressId; + } + + public String getContactName() { + return contactName; + } + + public void setContactName(String contactName) { + this.contactName = contactName; + } + + public String getContactPhone() { + return contactPhone; + } + + public void setContactPhone(String contactPhone) { + this.contactPhone = contactPhone; + } + + public String getDeliveryAddress() { + return deliveryAddress; + } + + public void setDeliveryAddress(String deliveryAddress) { + this.deliveryAddress = deliveryAddress; + } + + public String getExpressCompany() { + return expressCompany; + } + + public void setExpressCompany(String expressCompany) { + this.expressCompany = expressCompany; + } + + public String getPickupCode() { + return pickupCode; + } + + public void setPickupCode(String pickupCode) { + this.pickupCode = pickupCode; + } + + public String getPickupAddress() { + return pickupAddress; + } + + public void setPickupAddress(String pickupAddress) { + this.pickupAddress = pickupAddress; + } + + public String getReceiverPhone() { + return receiverPhone; + } + + public void setReceiverPhone(String receiverPhone) { + this.receiverPhone = receiverPhone; + } + + public int getPackageCount() { + return packageCount; + } + + public void setPackageCount(int packageCount) { + this.packageCount = packageCount; + } + + public int getFeeCent() { + return feeCent; + } + + public void setFeeCent(int feeCent) { + this.feeCent = feeCent; + } + + public OfflinePayStatus getOfflinePayStatus() { + return offlinePayStatus; + } + + public void setOfflinePayStatus(OfflinePayStatus offlinePayStatus) { + this.offlinePayStatus = offlinePayStatus; + } + + public ExpressOrderStatus getStatus() { + return status; + } + + public void setStatus(ExpressOrderStatus status) { + this.status = status; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public String getCancelReason() { + return cancelReason; + } + + public void setCancelReason(String cancelReason) { + this.cancelReason = cancelReason; + } + + public String getDeliveredPhotoUrl() { + return deliveredPhotoUrl; + } + + public void setDeliveredPhotoUrl(String deliveredPhotoUrl) { + this.deliveredPhotoUrl = deliveredPhotoUrl; + } + + public List getLogs() { + return logs; + } + + public void setLogs(List logs) { + this.logs = logs; + } + + 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; + } +} diff --git a/backend/src/main/java/com/linhelp/express/ExpressOrderService.java b/backend/src/main/java/com/linhelp/express/ExpressOrderService.java new file mode 100644 index 0000000..292450c --- /dev/null +++ b/backend/src/main/java/com/linhelp/express/ExpressOrderService.java @@ -0,0 +1,229 @@ +package com.linhelp.express; + +import com.linhelp.common.enums.ExpressOrderStatus; +import com.linhelp.common.enums.OfflinePayStatus; +import com.linhelp.common.exception.BizException; +import com.linhelp.user.AddressResponse; +import com.linhelp.user.UserAddressService; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +@Service +public class ExpressOrderService { + private final UserAddressService addressService; + private final AtomicLong orderIds = new AtomicLong(1L); + private final AtomicLong logIds = new AtomicLong(1L); + private final Map orders = new LinkedHashMap(); + + public ExpressOrderService(UserAddressService addressService) { + this.addressService = addressService; + } + + public synchronized ExpressOrderResponse create(Long userId, ExpressOrderRequest request) { + AddressResponse address = addressService.detailMine(userId, request.getAddressId()); + Long orderId = orderIds.getAndIncrement(); + ExpressOrderResponse order = new ExpressOrderResponse(); + order.setId(orderId); + order.setOrderNo("EO" + String.format("%08d", orderId)); + order.setTenantId(request.getTenantId()); + order.setCommunityId(request.getCommunityId()); + order.setUserId(userId); + order.setAddressId(request.getAddressId()); + order.setContactName(address.getContactName()); + order.setContactPhone(address.getPhone()); + order.setDeliveryAddress(buildDeliveryAddress(address)); + order.setExpressCompany(request.getExpressCompany()); + order.setPickupCode(request.getPickupCode()); + order.setPickupAddress(request.getPickupAddress()); + order.setReceiverPhone(request.getReceiverPhone()); + order.setPackageCount(request.getPackageCount()); + order.setFeeCent(ExpressFeeCalculator.calculateCent(request.getPackageCount())); + order.setOfflinePayStatus(OfflinePayStatus.UNPAID); + order.setStatus(ExpressOrderStatus.PENDING_CONFIRM); + order.setRemark(request.getRemark()); + order.setCreatedAt(LocalDateTime.now()); + order.setUpdatedAt(order.getCreatedAt()); + addLog(order, "CREATE", "用户提交快递代取"); + orders.put(order.getId(), order); + return copyOrder(order); + } + + public synchronized ExpressOrderResponse detail(Long id) { + return copyOrder(requireOrder(id)); + } + + public synchronized List listMine(Long userId) { + List result = new ArrayList(); + for (ExpressOrderResponse order : orders.values()) { + if (userId.equals(order.getUserId())) { + result.add(copyOrder(order)); + } + } + return result; + } + + public synchronized List listAdmin(Long communityId, ExpressOrderStatus status) { + List result = new ArrayList(); + for (ExpressOrderResponse order : orders.values()) { + if (!communityId.equals(order.getCommunityId())) { + continue; + } + if (status != null && status != order.getStatus()) { + continue; + } + result.add(copyOrder(order)); + } + return result; + } + + public synchronized ExpressOrderResponse confirm(Long id) { + ExpressOrderResponse order = requireOrder(id); + move(order, ExpressOrderStatus.PENDING_PICKUP, "CONFIRM", "后台确认快递代取"); + return copyOrder(order); + } + + public synchronized ExpressOrderResponse adjustFee(Long id, int feeCent, String reason) { + ExpressOrderResponse order = requireOrder(id); + order.setFeeCent(feeCent); + order.setUpdatedAt(LocalDateTime.now()); + addLog(order, "ADJUST_FEE", "调整服务费为" + feeCent + "分:" + reason); + return copyOrder(order); + } + + public synchronized ExpressOrderResponse cancel(Long id, String reason) { + ExpressOrderResponse order = requireOrder(id); + order.setCancelReason(reason); + move(order, ExpressOrderStatus.CANCELED, "CANCEL", "取消订单:" + nullToEmpty(reason)); + return copyOrder(order); + } + + public synchronized ExpressOrderResponse complete(Long id) { + ExpressOrderResponse order = requireOrder(id); + move(order, ExpressOrderStatus.COMPLETED, "COMPLETE", "订单完成"); + return copyOrder(order); + } + + public synchronized ExpressOrderResponse markPickingUp(Long id) { + ExpressOrderResponse order = requireOrder(id); + move(order, ExpressOrderStatus.PICKING_UP, "PICKING_UP", "配送员开始取件"); + return copyOrder(order); + } + + public synchronized ExpressOrderResponse markDelivering(Long id) { + ExpressOrderResponse order = requireOrder(id); + move(order, ExpressOrderStatus.DELIVERING, "DELIVERING", "配送员开始配送"); + return copyOrder(order); + } + + public synchronized ExpressOrderResponse markDelivered(Long id, String photoUrl) { + ExpressOrderResponse order = requireOrder(id); + order.setDeliveredPhotoUrl(photoUrl); + move(order, ExpressOrderStatus.DELIVERED, "DELIVERED", "配送员送达"); + return copyOrder(order); + } + + public synchronized ExpressOrderResponse updateOfflinePayStatus(Long id, OfflinePayStatus status) { + ExpressOrderResponse order = requireOrder(id); + order.setOfflinePayStatus(status); + order.setUpdatedAt(LocalDateTime.now()); + addLog(order, "OFFLINE_PAY", "线下收款状态:" + status.name()); + return copyOrder(order); + } + + private void move(ExpressOrderResponse order, ExpressOrderStatus target, String action, String content) { + if (!order.getStatus().canMoveTo(target)) { + throw new BizException("订单状态不允许操作"); + } + order.setStatus(target); + order.setUpdatedAt(LocalDateTime.now()); + addLog(order, action, content); + } + + private ExpressOrderResponse requireOrder(Long id) { + ExpressOrderResponse order = orders.get(id); + if (order == null) { + throw new BizException(404, "快递代取订单不存在"); + } + return order; + } + + private void addLog(ExpressOrderResponse order, String action, String content) { + ExpressOrderLogResponse log = new ExpressOrderLogResponse(); + log.setId(logIds.getAndIncrement()); + log.setOrderId(order.getId()); + log.setAction(action); + log.setContent(content); + log.setCreatedAt(LocalDateTime.now()); + order.getLogs().add(log); + } + + private String buildDeliveryAddress(AddressResponse address) { + StringBuilder builder = new StringBuilder(); + append(builder, address.getBuilding()); + append(builder, address.getRoom()); + append(builder, address.getDetail()); + return builder.toString(); + } + + private void append(StringBuilder builder, String value) { + if (value == null || value.trim().isEmpty()) { + return; + } + if (builder.length() > 0) { + builder.append(' '); + } + builder.append(value); + } + + private String nullToEmpty(String value) { + return value == null ? "" : value; + } + + private ExpressOrderResponse copyOrder(ExpressOrderResponse source) { + ExpressOrderResponse target = new ExpressOrderResponse(); + target.setId(source.getId()); + target.setOrderNo(source.getOrderNo()); + target.setTenantId(source.getTenantId()); + target.setCommunityId(source.getCommunityId()); + target.setUserId(source.getUserId()); + target.setAddressId(source.getAddressId()); + target.setContactName(source.getContactName()); + target.setContactPhone(source.getContactPhone()); + target.setDeliveryAddress(source.getDeliveryAddress()); + target.setExpressCompany(source.getExpressCompany()); + target.setPickupCode(source.getPickupCode()); + target.setPickupAddress(source.getPickupAddress()); + target.setReceiverPhone(source.getReceiverPhone()); + target.setPackageCount(source.getPackageCount()); + target.setFeeCent(source.getFeeCent()); + target.setOfflinePayStatus(source.getOfflinePayStatus()); + target.setStatus(source.getStatus()); + target.setRemark(source.getRemark()); + target.setCancelReason(source.getCancelReason()); + target.setDeliveredPhotoUrl(source.getDeliveredPhotoUrl()); + target.setCreatedAt(source.getCreatedAt()); + target.setUpdatedAt(source.getUpdatedAt()); + target.setLogs(copyLogs(source.getLogs())); + return target; + } + + private List copyLogs(List sourceLogs) { + List logs = new ArrayList(); + for (ExpressOrderLogResponse source : sourceLogs) { + ExpressOrderLogResponse target = new ExpressOrderLogResponse(); + target.setId(source.getId()); + target.setOrderId(source.getOrderId()); + target.setAction(source.getAction()); + target.setContent(source.getContent()); + target.setCreatedAt(source.getCreatedAt()); + logs.add(target); + } + return logs; + } +} diff --git a/backend/src/main/java/com/linhelp/user/UserAddressService.java b/backend/src/main/java/com/linhelp/user/UserAddressService.java index 5471353..77f239f 100644 --- a/backend/src/main/java/com/linhelp/user/UserAddressService.java +++ b/backend/src/main/java/com/linhelp/user/UserAddressService.java @@ -39,6 +39,10 @@ public class UserAddressService { return result; } + public synchronized AddressResponse detailMine(Long userId, Long id) { + return requireMine(userId, id); + } + public synchronized AddressResponse update(Long userId, Long id, AddressRequest request) { AddressResponse address = requireMine(userId, id); copy(request, address); diff --git a/backend/src/test/java/com/linhelp/express/ExpressFeeCalculatorTests.java b/backend/src/test/java/com/linhelp/express/ExpressFeeCalculatorTests.java new file mode 100644 index 0000000..4af0741 --- /dev/null +++ b/backend/src/test/java/com/linhelp/express/ExpressFeeCalculatorTests.java @@ -0,0 +1,24 @@ +package com.linhelp.express; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ExpressFeeCalculatorTests { + @Test + void firstPackageCostsThreeYuan() { + assertThat(ExpressFeeCalculator.calculateCent(1)).isEqualTo(300); + } + + @Test + void extraPackagesCostOneYuanEach() { + assertThat(ExpressFeeCalculator.calculateCent(3)).isEqualTo(500); + } + + @Test + void packageCountMustBePositive() { + assertThatThrownBy(() -> ExpressFeeCalculator.calculateCent(0)) + .hasMessageContaining("件数必须大于0"); + } +} diff --git a/backend/src/test/java/com/linhelp/express/ExpressOrderServiceTests.java b/backend/src/test/java/com/linhelp/express/ExpressOrderServiceTests.java new file mode 100644 index 0000000..8658b6e --- /dev/null +++ b/backend/src/test/java/com/linhelp/express/ExpressOrderServiceTests.java @@ -0,0 +1,79 @@ +package com.linhelp.express; + +import com.linhelp.common.enums.ExpressOrderStatus; +import com.linhelp.user.AddressRequest; +import com.linhelp.user.AddressResponse; +import com.linhelp.user.UserAddressService; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ExpressOrderServiceTests { + @Test + void confirmedExpressOrderMovesToPendingPickup() { + UserAddressService addressService = new UserAddressService(); + AddressResponse address = addressService.create(1L, addressRequest()); + ExpressOrderService expressOrderService = new ExpressOrderService(addressService); + + ExpressOrderRequest request = expressRequest(address.getId()); + request.setPackageCount(3); + ExpressOrderResponse order = expressOrderService.create(1L, request); + + assertThat(order.getFeeCent()).isEqualTo(500); + + expressOrderService.confirm(order.getId()); + + assertThat(expressOrderService.detail(order.getId()).getStatus()).isEqualTo(ExpressOrderStatus.PENDING_PICKUP); + } + + @Test + void addressMustBelongToCurrentUser() { + UserAddressService addressService = new UserAddressService(); + AddressResponse otherUserAddress = addressService.create(2L, addressRequest()); + ExpressOrderService expressOrderService = new ExpressOrderService(addressService); + + assertThatThrownBy(() -> expressOrderService.create(1L, expressRequest(otherUserAddress.getId()))) + .hasMessageContaining("地址不存在"); + } + + @Test + void feeAdjustmentWritesLog() { + UserAddressService addressService = new UserAddressService(); + AddressResponse address = addressService.create(1L, addressRequest()); + ExpressOrderService expressOrderService = new ExpressOrderService(addressService); + ExpressOrderResponse order = expressOrderService.create(1L, expressRequest(address.getId())); + + expressOrderService.adjustFee(order.getId(), 800, "大件加价"); + + ExpressOrderResponse detail = expressOrderService.detail(order.getId()); + assertThat(detail.getFeeCent()).isEqualTo(800); + assertThat(detail.getLogs()).extracting(ExpressOrderLogResponse::getContent) + .anyMatch(content -> content.contains("大件加价")); + } + + private ExpressOrderRequest expressRequest(Long addressId) { + ExpressOrderRequest request = new ExpressOrderRequest(); + request.setTenantId(1L); + request.setCommunityId(1L); + request.setExpressCompany("顺丰"); + request.setPickupCode("A-1234"); + request.setPickupAddress("北门快递站"); + request.setReceiverPhone("13800000000"); + request.setAddressId(addressId); + request.setPackageCount(1); + return request; + } + + private AddressRequest addressRequest() { + AddressRequest request = new AddressRequest(); + request.setTenantId(1L); + request.setCommunityId(1L); + request.setContactName("张三"); + request.setPhone("13800000000"); + request.setBuilding("1栋"); + request.setRoom("101"); + request.setDetail("门口"); + return request; + } +}