feat: add delivery assignment and rider APIs

This commit is contained in:
王鹏
2026-07-07 15:48:11 +08:00
parent 3646ee9971
commit 9c579b3173
14 changed files with 1152 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
package com.linhelp.delivery;
import com.linhelp.common.api.ApiResponse;
import com.linhelp.common.enums.DeliveryTaskStatus;
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
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"})
public class DeliveryAdminController {
private final DeliveryUserService deliveryUserService;
private final DeliveryAssignmentService assignmentService;
private final AuthService authService;
public DeliveryAdminController(DeliveryUserService deliveryUserService,
DeliveryAssignmentService assignmentService,
AuthService authService) {
this.deliveryUserService = deliveryUserService;
this.assignmentService = assignmentService;
this.authService = authService;
}
@GetMapping("/api/admin/delivery-users")
public ApiResponse<List<DeliveryUserResponse>> listUsers() {
CurrentUser user = authService.currentUser();
return ApiResponse.ok(deliveryUserService.list(user.getCommunityId()));
}
@PostMapping("/api/admin/delivery-users")
public ApiResponse<DeliveryUserResponse> createUser(@Valid @RequestBody DeliveryUserRequest request) {
CurrentUser user = authService.currentUser();
request.setTenantId(user.getTenantId());
request.setCommunityId(user.getCommunityId());
return ApiResponse.ok(deliveryUserService.create(request));
}
@PutMapping("/api/admin/delivery-users/{id}")
public ApiResponse<DeliveryUserResponse> updateUser(@PathVariable Long id, @Valid @RequestBody DeliveryUserRequest request) {
CurrentUser user = authService.currentUser();
request.setTenantId(user.getTenantId());
request.setCommunityId(user.getCommunityId());
return ApiResponse.ok(deliveryUserService.update(id, request));
}
@PutMapping("/api/admin/delivery-users/{id}/enable")
public ApiResponse<DeliveryUserResponse> enableUser(@PathVariable Long id) {
return ApiResponse.ok(deliveryUserService.enable(id));
}
@PutMapping("/api/admin/delivery-users/{id}/disable")
public ApiResponse<DeliveryUserResponse> disableUser(@PathVariable Long id) {
return ApiResponse.ok(deliveryUserService.disable(id));
}
@PostMapping("/api/admin/delivery-orders/assign-goods-order")
public ApiResponse<DeliveryOrderResponse> assignGoodsOrder(@Valid @RequestBody DeliveryAssignRequest request) {
return ApiResponse.ok(assignmentService.assignGoodsOrder(request.getOrderId(), request.getRiderId()));
}
@PostMapping("/api/admin/delivery-orders/assign-express-order")
public ApiResponse<DeliveryOrderResponse> assignExpressOrder(@Valid @RequestBody DeliveryAssignRequest request) {
return ApiResponse.ok(assignmentService.assignExpressOrder(request.getOrderId(), request.getRiderId()));
}
@PostMapping("/api/admin/delivery-orders/assign-group-buy-order")
public ApiResponse<DeliveryOrderResponse> assignGroupBuyOrder(@Valid @RequestBody DeliveryAssignRequest request) {
CurrentUser user = authService.currentUser();
return ApiResponse.ok(assignmentService.assignGroupBuyOrder(user.getCommunityId(), request.getOrderId(), request.getRiderId()));
}
@GetMapping("/api/admin/delivery-orders")
public ApiResponse<List<DeliveryOrderResponse>> listTasks(@RequestParam(required = false) DeliveryTaskStatus status) {
CurrentUser user = authService.currentUser();
return ApiResponse.ok(assignmentService.listAdmin(user.getCommunityId(), status));
}
@GetMapping("/api/admin/delivery-orders/{id}")
public ApiResponse<DeliveryOrderResponse> taskDetail(@PathVariable Long id) {
return ApiResponse.ok(assignmentService.detail(id));
}
}

View File

@@ -0,0 +1,27 @@
package com.linhelp.delivery;
import javax.validation.constraints.NotNull;
public class DeliveryAssignRequest {
@NotNull
private Long orderId;
@NotNull
private Long riderId;
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public Long getRiderId() {
return riderId;
}
public void setRiderId(Long riderId) {
this.riderId = riderId;
}
}

View File

@@ -0,0 +1,181 @@
package com.linhelp.delivery;
import com.linhelp.common.enums.DeliveryMethod;
import com.linhelp.common.enums.DeliveryTaskStatus;
import com.linhelp.common.enums.ExpressOrderStatus;
import com.linhelp.common.enums.GoodsOrderStatus;
import com.linhelp.common.exception.BizException;
import com.linhelp.express.ExpressOrderResponse;
import com.linhelp.express.ExpressOrderService;
import com.linhelp.order.GoodsOrderResponse;
import com.linhelp.order.GoodsOrderService;
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 DeliveryAssignmentService {
public static final String BIZ_TYPE_GOODS_ORDER = "GOODS_ORDER";
public static final String BIZ_TYPE_EXPRESS_ORDER = "EXPRESS_ORDER";
public static final String BIZ_TYPE_GROUP_BUY_ORDER = "GROUP_BUY_ORDER";
private final DeliveryUserService deliveryUserService;
private final GoodsOrderService goodsOrderService;
private final ExpressOrderService expressOrderService;
private final AtomicLong ids = new AtomicLong(1L);
private final Map<Long, DeliveryOrderResponse> tasks = new LinkedHashMap<Long, DeliveryOrderResponse>();
public DeliveryAssignmentService(DeliveryUserService deliveryUserService,
GoodsOrderService goodsOrderService,
ExpressOrderService expressOrderService) {
this.deliveryUserService = deliveryUserService;
this.goodsOrderService = goodsOrderService;
this.expressOrderService = expressOrderService;
}
public synchronized DeliveryOrderResponse assignGoodsOrder(Long goodsOrderId, Long riderId) {
DeliveryUserResponse rider = deliveryUserService.requireEnabled(riderId);
GoodsOrderResponse order = goodsOrderService.detail(goodsOrderId);
if (DeliveryMethod.SELF_PICKUP.equals(order.getDeliveryMethod())) {
throw new BizException("自提订单无需派单");
}
if (GoodsOrderStatus.PENDING_DELIVERY != order.getStatus()) {
throw new BizException("订单未待配送");
}
DeliveryOrderResponse task = createBaseTask(order.getTenantId(), order.getCommunityId(), rider);
task.setBizType(BIZ_TYPE_GOODS_ORDER);
task.setBizOrderId(order.getId());
task.setBizOrderNo(order.getOrderNo());
task.setAddress(order.getAddressId() == null ? null : "地址ID " + order.getAddressId());
task.setAmountCent(order.getPayableAmountCent());
task.setRemark(order.getRemark());
tasks.put(task.getId(), task);
return copy(task);
}
public synchronized DeliveryOrderResponse assignExpressOrder(Long expressOrderId, Long riderId) {
if (expressOrderService == null) {
throw new BizException("快递代取服务未启用");
}
DeliveryUserResponse rider = deliveryUserService.requireEnabled(riderId);
ExpressOrderResponse order = expressOrderService.detail(expressOrderId);
if (ExpressOrderStatus.PENDING_PICKUP != order.getStatus()) {
throw new BizException("快递订单未待取件");
}
DeliveryOrderResponse task = createBaseTask(order.getTenantId(), order.getCommunityId(), rider);
task.setBizType(BIZ_TYPE_EXPRESS_ORDER);
task.setBizOrderId(order.getId());
task.setBizOrderNo(order.getOrderNo());
task.setContactName(order.getContactName());
task.setContactPhone(order.getContactPhone());
task.setAddress(order.getDeliveryAddress());
task.setAmountCent(order.getFeeCent());
task.setRemark(order.getRemark());
tasks.put(task.getId(), task);
return copy(task);
}
public synchronized DeliveryOrderResponse assignGroupBuyOrder(Long communityId, Long groupBuyOrderId, Long riderId) {
DeliveryUserResponse rider = deliveryUserService.requireEnabled(riderId);
DeliveryOrderResponse task = createBaseTask(rider.getTenantId(), communityId, rider);
task.setBizType(BIZ_TYPE_GROUP_BUY_ORDER);
task.setBizOrderId(groupBuyOrderId);
task.setBizOrderNo("GB" + String.format("%08d", groupBuyOrderId));
tasks.put(task.getId(), task);
return copy(task);
}
public synchronized List<DeliveryOrderResponse> listAdmin(Long communityId, DeliveryTaskStatus status) {
List<DeliveryOrderResponse> result = new ArrayList<DeliveryOrderResponse>();
for (DeliveryOrderResponse task : tasks.values()) {
if (!communityId.equals(task.getCommunityId())) {
continue;
}
if (status != null && status != task.getStatus()) {
continue;
}
result.add(copy(task));
}
return result;
}
public synchronized DeliveryOrderResponse detail(Long id) {
return copy(require(id));
}
public synchronized List<DeliveryOrderResponse> listRiderTasks(Long riderId) {
List<DeliveryOrderResponse> result = new ArrayList<DeliveryOrderResponse>();
for (DeliveryOrderResponse task : tasks.values()) {
if (riderId.equals(task.getRiderId())) {
result.add(copy(task));
}
}
return result;
}
public synchronized DeliveryOrderResponse detailForRider(Long riderId, Long taskId) {
return copy(requireForRider(riderId, taskId));
}
synchronized DeliveryOrderResponse requireForRider(Long riderId, Long taskId) {
DeliveryOrderResponse task = require(taskId);
if (!riderId.equals(task.getRiderId())) {
throw new BizException(404, "配送任务不存在");
}
return task;
}
synchronized DeliveryOrderResponse copyTask(DeliveryOrderResponse task) {
return copy(task);
}
private DeliveryOrderResponse createBaseTask(Long tenantId, Long communityId, DeliveryUserResponse rider) {
DeliveryOrderResponse task = new DeliveryOrderResponse();
task.setId(ids.getAndIncrement());
task.setTenantId(tenantId);
task.setCommunityId(communityId);
task.setRiderId(rider.getId());
task.setRiderName(rider.getName());
task.setStatus(DeliveryTaskStatus.ASSIGNED);
task.setCreatedAt(LocalDateTime.now());
task.setUpdatedAt(task.getCreatedAt());
return task;
}
private DeliveryOrderResponse require(Long id) {
DeliveryOrderResponse task = tasks.get(id);
if (task == null) {
throw new BizException(404, "配送任务不存在");
}
return task;
}
private DeliveryOrderResponse copy(DeliveryOrderResponse source) {
DeliveryOrderResponse target = new DeliveryOrderResponse();
target.setId(source.getId());
target.setTenantId(source.getTenantId());
target.setCommunityId(source.getCommunityId());
target.setBizType(source.getBizType());
target.setBizOrderId(source.getBizOrderId());
target.setBizOrderNo(source.getBizOrderNo());
target.setRiderId(source.getRiderId());
target.setRiderName(source.getRiderName());
target.setContactName(source.getContactName());
target.setContactPhone(source.getContactPhone());
target.setAddress(source.getAddress());
target.setAmountCent(source.getAmountCent());
target.setRemark(source.getRemark());
target.setStatus(source.getStatus());
target.setDeliveredPhotoUrl(source.getDeliveredPhotoUrl());
target.setExceptionType(source.getExceptionType());
target.setExceptionNote(source.getExceptionNote());
target.setCreatedAt(source.getCreatedAt());
target.setUpdatedAt(source.getUpdatedAt());
return target;
}
}

View File

@@ -0,0 +1,179 @@
package com.linhelp.delivery;
import com.linhelp.common.enums.DeliveryTaskStatus;
import java.time.LocalDateTime;
public class DeliveryOrderResponse {
private Long id;
private Long tenantId;
private Long communityId;
private String bizType;
private Long bizOrderId;
private String bizOrderNo;
private Long riderId;
private String riderName;
private String contactName;
private String contactPhone;
private String address;
private int amountCent;
private String remark;
private DeliveryTaskStatus status;
private String deliveredPhotoUrl;
private String exceptionType;
private String exceptionNote;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
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 String getBizType() {
return bizType;
}
public void setBizType(String bizType) {
this.bizType = bizType;
}
public Long getBizOrderId() {
return bizOrderId;
}
public void setBizOrderId(Long bizOrderId) {
this.bizOrderId = bizOrderId;
}
public String getBizOrderNo() {
return bizOrderNo;
}
public void setBizOrderNo(String bizOrderNo) {
this.bizOrderNo = bizOrderNo;
}
public Long getRiderId() {
return riderId;
}
public void setRiderId(Long riderId) {
this.riderId = riderId;
}
public String getRiderName() {
return riderName;
}
public void setRiderName(String riderName) {
this.riderName = riderName;
}
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 getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAmountCent() {
return amountCent;
}
public void setAmountCent(int amountCent) {
this.amountCent = amountCent;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public DeliveryTaskStatus getStatus() {
return status;
}
public void setStatus(DeliveryTaskStatus status) {
this.status = status;
}
public String getDeliveredPhotoUrl() {
return deliveredPhotoUrl;
}
public void setDeliveredPhotoUrl(String deliveredPhotoUrl) {
this.deliveredPhotoUrl = deliveredPhotoUrl;
}
public String getExceptionType() {
return exceptionType;
}
public void setExceptionType(String exceptionType) {
this.exceptionType = exceptionType;
}
public String getExceptionNote() {
return exceptionNote;
}
public void setExceptionNote(String exceptionNote) {
this.exceptionNote = exceptionNote;
}
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

@@ -0,0 +1,65 @@
package com.linhelp.delivery;
import javax.validation.constraints.NotBlank;
public class DeliveryUserRequest {
private Long tenantId;
private Long communityId;
private Long userId;
@NotBlank
private String name;
@NotBlank
private String phone;
private boolean enabled = true;
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 String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

View File

@@ -0,0 +1,67 @@
package com.linhelp.delivery;
public class DeliveryUserResponse {
private Long id;
private Long tenantId;
private Long communityId;
private Long userId;
private String name;
private String phone;
private boolean enabled;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
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 String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

View File

@@ -0,0 +1,104 @@
package com.linhelp.delivery;
import com.linhelp.common.exception.BizException;
import org.springframework.stereotype.Service;
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 DeliveryUserService {
private final AtomicLong ids = new AtomicLong(1L);
private final Map<Long, DeliveryUserResponse> users = new LinkedHashMap<Long, DeliveryUserResponse>();
public synchronized DeliveryUserResponse create(DeliveryUserRequest request) {
DeliveryUserResponse user = new DeliveryUserResponse();
user.setId(ids.getAndIncrement());
user.setTenantId(request.getTenantId());
user.setCommunityId(request.getCommunityId());
copyFields(request, user);
users.put(user.getId(), user);
return copy(user);
}
public synchronized DeliveryUserResponse update(Long id, DeliveryUserRequest request) {
DeliveryUserResponse user = require(id);
if (request.getTenantId() != null) {
user.setTenantId(request.getTenantId());
}
if (request.getCommunityId() != null) {
user.setCommunityId(request.getCommunityId());
}
copyFields(request, user);
return copy(user);
}
public synchronized List<DeliveryUserResponse> list(Long communityId) {
List<DeliveryUserResponse> result = new ArrayList<DeliveryUserResponse>();
for (DeliveryUserResponse user : users.values()) {
if (communityId.equals(user.getCommunityId())) {
result.add(copy(user));
}
}
return result;
}
public synchronized DeliveryUserResponse enable(Long id) {
DeliveryUserResponse user = require(id);
user.setEnabled(true);
return copy(user);
}
public synchronized DeliveryUserResponse disable(Long id) {
DeliveryUserResponse user = require(id);
user.setEnabled(false);
return copy(user);
}
public synchronized DeliveryUserResponse requireEnabled(Long id) {
DeliveryUserResponse user = require(id);
if (!user.isEnabled()) {
throw new BizException("配送员已停用");
}
return copy(user);
}
public synchronized DeliveryUserResponse requireByUserId(Long userId) {
for (DeliveryUserResponse user : users.values()) {
if (userId.equals(user.getUserId())) {
return copy(user);
}
}
throw new BizException(404, "配送员不存在");
}
private DeliveryUserResponse require(Long id) {
DeliveryUserResponse user = users.get(id);
if (user == null) {
throw new BizException(404, "配送员不存在");
}
return user;
}
private void copyFields(DeliveryUserRequest request, DeliveryUserResponse user) {
user.setUserId(request.getUserId());
user.setName(request.getName());
user.setPhone(request.getPhone());
user.setEnabled(request.isEnabled());
}
private DeliveryUserResponse copy(DeliveryUserResponse source) {
DeliveryUserResponse target = new DeliveryUserResponse();
target.setId(source.getId());
target.setTenantId(source.getTenantId());
target.setCommunityId(source.getCommunityId());
target.setUserId(source.getUserId());
target.setName(source.getName());
target.setPhone(source.getPhone());
target.setEnabled(source.isEnabled());
return target;
}
}

View File

@@ -0,0 +1,13 @@
package com.linhelp.delivery;
public class RiderDeliveredRequest {
private String photoUrl;
public String getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(String photoUrl) {
this.photoUrl = photoUrl;
}
}

View File

@@ -0,0 +1,26 @@
package com.linhelp.delivery;
import javax.validation.constraints.NotBlank;
public class RiderExceptionRequest {
@NotBlank
private String exceptionType;
private String exceptionNote;
public String getExceptionType() {
return exceptionType;
}
public void setExceptionType(String exceptionType) {
this.exceptionType = exceptionType;
}
public String getExceptionNote() {
return exceptionNote;
}
public void setExceptionNote(String exceptionNote) {
this.exceptionNote = exceptionNote;
}
}

View File

@@ -0,0 +1,40 @@
package com.linhelp.delivery;
public class RiderIncomeSummaryResponse {
private int assignedCount;
private int deliveringCount;
private int deliveredCount;
private int estimatedIncomeCent;
public int getAssignedCount() {
return assignedCount;
}
public void setAssignedCount(int assignedCount) {
this.assignedCount = assignedCount;
}
public int getDeliveringCount() {
return deliveringCount;
}
public void setDeliveringCount(int deliveringCount) {
this.deliveringCount = deliveringCount;
}
public int getDeliveredCount() {
return deliveredCount;
}
public void setDeliveredCount(int deliveredCount) {
this.deliveredCount = deliveredCount;
}
public int getEstimatedIncomeCent() {
return estimatedIncomeCent;
}
public void setEstimatedIncomeCent(int estimatedIncomeCent) {
this.estimatedIncomeCent = estimatedIncomeCent;
}
}

View File

@@ -0,0 +1,65 @@
package com.linhelp.delivery;
import com.linhelp.common.api.ApiResponse;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequireRole("RIDER")
public class RiderTaskController {
private final RiderTaskService riderTaskService;
private final DeliveryUserService deliveryUserService;
private final AuthService authService;
public RiderTaskController(RiderTaskService riderTaskService,
DeliveryUserService deliveryUserService,
AuthService authService) {
this.riderTaskService = riderTaskService;
this.deliveryUserService = deliveryUserService;
this.authService = authService;
}
@GetMapping("/api/rider/tasks")
public ApiResponse<List<DeliveryOrderResponse>> listTasks() {
return ApiResponse.ok(riderTaskService.listTasks(currentDeliveryUserId()));
}
@GetMapping("/api/rider/tasks/{id}")
public ApiResponse<DeliveryOrderResponse> detail(@PathVariable Long id) {
return ApiResponse.ok(riderTaskService.detail(currentDeliveryUserId(), id));
}
@PutMapping("/api/rider/tasks/{id}/start")
public ApiResponse<DeliveryOrderResponse> start(@PathVariable Long id) {
return ApiResponse.ok(riderTaskService.start(currentDeliveryUserId(), id));
}
@PutMapping("/api/rider/tasks/{id}/delivered")
public ApiResponse<DeliveryOrderResponse> delivered(@PathVariable Long id, @RequestBody(required = false) RiderDeliveredRequest request) {
return ApiResponse.ok(riderTaskService.delivered(currentDeliveryUserId(), id, request == null ? null : request.getPhotoUrl()));
}
@PutMapping("/api/rider/tasks/{id}/exception")
public ApiResponse<DeliveryOrderResponse> exception(@PathVariable Long id, @Valid @RequestBody RiderExceptionRequest request) {
return ApiResponse.ok(riderTaskService.exception(currentDeliveryUserId(), id, request.getExceptionType(), request.getExceptionNote()));
}
@GetMapping("/api/rider/income-summary")
public ApiResponse<RiderIncomeSummaryResponse> incomeSummary() {
return ApiResponse.ok(riderTaskService.incomeSummary(currentDeliveryUserId()));
}
private Long currentDeliveryUserId() {
CurrentUser user = authService.currentUser();
return deliveryUserService.requireByUserId(user.getUserId()).getId();
}
}

View File

@@ -0,0 +1,120 @@
package com.linhelp.delivery;
import com.linhelp.common.enums.DeliveryTaskStatus;
import com.linhelp.common.enums.ExpressOrderStatus;
import com.linhelp.common.exception.BizException;
import com.linhelp.express.ExpressOrderResponse;
import com.linhelp.express.ExpressOrderService;
import com.linhelp.order.GoodsOrderService;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class RiderTaskService {
private static final int INCOME_PER_DELIVERED_TASK_CENT = 300;
private final DeliveryAssignmentService assignmentService;
private final GoodsOrderService goodsOrderService;
private final ExpressOrderService expressOrderService;
public RiderTaskService(DeliveryAssignmentService assignmentService,
GoodsOrderService goodsOrderService,
ExpressOrderService expressOrderService) {
this.assignmentService = assignmentService;
this.goodsOrderService = goodsOrderService;
this.expressOrderService = expressOrderService;
}
public List<DeliveryOrderResponse> listTasks(Long riderId) {
return assignmentService.listRiderTasks(riderId);
}
public DeliveryOrderResponse detail(Long riderId, Long taskId) {
return assignmentService.detailForRider(riderId, taskId);
}
public DeliveryOrderResponse start(Long riderId, Long taskId) {
DeliveryOrderResponse task = assignmentService.requireForRider(riderId, taskId);
if (task.getStatus() != DeliveryTaskStatus.ASSIGNED && task.getStatus() != DeliveryTaskStatus.ACCEPTED) {
throw new BizException("任务状态不允许操作");
}
if (DeliveryAssignmentService.BIZ_TYPE_GOODS_ORDER.equals(task.getBizType())) {
goodsOrderService.markDelivering(task.getBizOrderId());
} else if (DeliveryAssignmentService.BIZ_TYPE_EXPRESS_ORDER.equals(task.getBizType())) {
startExpressOrder(task.getBizOrderId());
}
task.setStatus(DeliveryTaskStatus.DELIVERING);
task.setUpdatedAt(LocalDateTime.now());
return assignmentService.copyTask(task);
}
public DeliveryOrderResponse delivered(Long riderId, Long taskId, String photoUrl) {
DeliveryOrderResponse task = assignmentService.requireForRider(riderId, taskId);
if (task.getStatus() != DeliveryTaskStatus.DELIVERING) {
throw new BizException("任务状态不允许操作");
}
if (DeliveryAssignmentService.BIZ_TYPE_GOODS_ORDER.equals(task.getBizType())) {
goodsOrderService.complete(task.getBizOrderId());
} else if (DeliveryAssignmentService.BIZ_TYPE_EXPRESS_ORDER.equals(task.getBizType())) {
deliverExpressOrder(task.getBizOrderId(), photoUrl);
}
task.setDeliveredPhotoUrl(photoUrl);
task.setStatus(DeliveryTaskStatus.DELIVERED);
task.setUpdatedAt(LocalDateTime.now());
return assignmentService.copyTask(task);
}
public DeliveryOrderResponse exception(Long riderId, Long taskId, String exceptionType, String exceptionNote) {
DeliveryOrderResponse task = assignmentService.requireForRider(riderId, taskId);
task.setExceptionType(exceptionType);
task.setExceptionNote(exceptionNote);
task.setStatus(DeliveryTaskStatus.EXCEPTION);
task.setUpdatedAt(LocalDateTime.now());
return assignmentService.copyTask(task);
}
public RiderIncomeSummaryResponse incomeSummary(Long riderId) {
RiderIncomeSummaryResponse response = new RiderIncomeSummaryResponse();
for (DeliveryOrderResponse task : listTasks(riderId)) {
if (task.getStatus() == DeliveryTaskStatus.ASSIGNED || task.getStatus() == DeliveryTaskStatus.ACCEPTED) {
response.setAssignedCount(response.getAssignedCount() + 1);
} else if (task.getStatus() == DeliveryTaskStatus.DELIVERING) {
response.setDeliveringCount(response.getDeliveringCount() + 1);
} else if (task.getStatus() == DeliveryTaskStatus.DELIVERED || task.getStatus() == DeliveryTaskStatus.COMPLETED) {
response.setDeliveredCount(response.getDeliveredCount() + 1);
}
}
response.setEstimatedIncomeCent(response.getDeliveredCount() * INCOME_PER_DELIVERED_TASK_CENT);
return response;
}
private void startExpressOrder(Long orderId) {
if (expressOrderService == null) {
throw new BizException("快递代取服务未启用");
}
ExpressOrderResponse order = expressOrderService.detail(orderId);
if (order.getStatus() == ExpressOrderStatus.PENDING_PICKUP) {
expressOrderService.markPickingUp(orderId);
} else if (order.getStatus() == ExpressOrderStatus.PICKING_UP) {
expressOrderService.markDelivering(orderId);
} else {
throw new BizException("快递订单状态不允许操作");
}
}
private void deliverExpressOrder(Long orderId, String photoUrl) {
if (expressOrderService == null) {
throw new BizException("快递代取服务未启用");
}
ExpressOrderResponse order = expressOrderService.detail(orderId);
if (order.getStatus() == ExpressOrderStatus.PENDING_PICKUP) {
expressOrderService.markPickingUp(orderId);
expressOrderService.markDelivering(orderId);
} else if (order.getStatus() == ExpressOrderStatus.PICKING_UP) {
expressOrderService.markDelivering(orderId);
}
expressOrderService.markDelivered(orderId, photoUrl);
}
}

View File

@@ -0,0 +1,81 @@
package com.linhelp.delivery;
import com.linhelp.common.enums.DeliveryMethod;
import com.linhelp.common.enums.DeliveryTaskStatus;
import com.linhelp.order.CreateGoodsOrderItemRequest;
import com.linhelp.order.CreateGoodsOrderRequest;
import com.linhelp.order.GoodsOrderResponse;
import com.linhelp.order.GoodsOrderService;
import com.linhelp.product.ProductService;
import com.linhelp.product.ProductSkuResponse;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DeliveryAssignmentServiceTests {
@Test
void adminCanAssignPendingDeliveryGoodsOrderToRider() {
TestFixture fixture = new TestFixture();
GoodsOrderResponse order = fixture.preparedDeliveryOrder();
DeliveryUserResponse rider = fixture.rider();
DeliveryOrderResponse task = fixture.assignmentService.assignGoodsOrder(order.getId(), rider.getId());
assertThat(task.getBizType()).isEqualTo(DeliveryAssignmentService.BIZ_TYPE_GOODS_ORDER);
assertThat(task.getStatus()).isEqualTo(DeliveryTaskStatus.ASSIGNED);
assertThat(task.getRiderId()).isEqualTo(rider.getId());
}
@Test
void cannotAssignSelfPickupGoodsOrder() {
TestFixture fixture = new TestFixture();
GoodsOrderResponse order = fixture.preparedSelfPickupOrder();
DeliveryUserResponse rider = fixture.rider();
assertThatThrownBy(() -> fixture.assignmentService.assignGoodsOrder(order.getId(), rider.getId()))
.hasMessageContaining("自提订单无需派单");
}
private static class TestFixture {
private final ProductService productService = new ProductService();
private final GoodsOrderService goodsOrderService = new GoodsOrderService(productService);
private final DeliveryUserService deliveryUserService = new DeliveryUserService();
private final DeliveryAssignmentService assignmentService =
new DeliveryAssignmentService(deliveryUserService, goodsOrderService, null);
private GoodsOrderResponse preparedDeliveryOrder() {
return preparedOrder(DeliveryMethod.IMMEDIATE, 1L);
}
private GoodsOrderResponse preparedSelfPickupOrder() {
return preparedOrder(DeliveryMethod.SELF_PICKUP, null);
}
private GoodsOrderResponse preparedOrder(DeliveryMethod deliveryMethod, Long addressId) {
ProductSkuResponse sku = productService.createDemoSku(1L, 1L, 1L, "500g", 990, 5);
CreateGoodsOrderRequest request = new CreateGoodsOrderRequest();
request.setTenantId(1L);
request.setCommunityId(1L);
request.setMerchantId(1L);
request.setAddressId(addressId);
request.setDeliveryMethod(deliveryMethod);
request.setItems(Collections.singletonList(new CreateGoodsOrderItemRequest(1L, sku.getId(), 1)));
GoodsOrderResponse order = goodsOrderService.create(1L, request);
goodsOrderService.confirm(order.getId());
return goodsOrderService.markPrepared(order.getId());
}
private DeliveryUserResponse rider() {
DeliveryUserRequest request = new DeliveryUserRequest();
request.setTenantId(1L);
request.setCommunityId(1L);
request.setUserId(3L);
request.setName("骑手一号");
request.setPhone("13900000000");
return deliveryUserService.create(request);
}
}
}

View File

@@ -0,0 +1,92 @@
package com.linhelp.delivery;
import com.linhelp.common.enums.DeliveryMethod;
import com.linhelp.common.enums.DeliveryTaskStatus;
import com.linhelp.common.enums.GoodsOrderStatus;
import com.linhelp.order.CreateGoodsOrderItemRequest;
import com.linhelp.order.CreateGoodsOrderRequest;
import com.linhelp.order.GoodsOrderResponse;
import com.linhelp.order.GoodsOrderService;
import com.linhelp.product.ProductService;
import com.linhelp.product.ProductSkuResponse;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
class RiderTaskServiceTests {
@Test
void riderOnlySeesOwnTasks() {
TestFixture fixture = new TestFixture();
GoodsOrderResponse order = fixture.preparedDeliveryOrder();
DeliveryUserResponse firstRider = fixture.rider(3L, "骑手一号");
DeliveryUserResponse secondRider = fixture.rider(4L, "骑手二号");
fixture.assignmentService.assignGoodsOrder(order.getId(), firstRider.getId());
assertThat(fixture.riderTaskService.listTasks(firstRider.getId())).hasSize(1);
assertThat(fixture.riderTaskService.listTasks(secondRider.getId())).isEmpty();
}
@Test
void startingGoodsOrderTaskMovesGoodsOrderToDelivering() {
TestFixture fixture = new TestFixture();
GoodsOrderResponse order = fixture.preparedDeliveryOrder();
DeliveryUserResponse rider = fixture.rider(3L, "骑手一号");
DeliveryOrderResponse task = fixture.assignmentService.assignGoodsOrder(order.getId(), rider.getId());
DeliveryOrderResponse started = fixture.riderTaskService.start(rider.getId(), task.getId());
assertThat(started.getStatus()).isEqualTo(DeliveryTaskStatus.DELIVERING);
assertThat(fixture.goodsOrderService.detail(order.getId()).getStatus()).isEqualTo(GoodsOrderStatus.DELIVERING);
}
@Test
void deliveredTaskStoresPhotoUrl() {
TestFixture fixture = new TestFixture();
GoodsOrderResponse order = fixture.preparedDeliveryOrder();
DeliveryUserResponse rider = fixture.rider(3L, "骑手一号");
DeliveryOrderResponse task = fixture.assignmentService.assignGoodsOrder(order.getId(), rider.getId());
fixture.riderTaskService.start(rider.getId(), task.getId());
DeliveryOrderResponse delivered = fixture.riderTaskService.delivered(rider.getId(), task.getId(), "https://img.test/done.jpg");
assertThat(delivered.getStatus()).isEqualTo(DeliveryTaskStatus.DELIVERED);
assertThat(delivered.getDeliveredPhotoUrl()).isEqualTo("https://img.test/done.jpg");
}
private static class TestFixture {
private final ProductService productService = new ProductService();
private final GoodsOrderService goodsOrderService = new GoodsOrderService(productService);
private final DeliveryUserService deliveryUserService = new DeliveryUserService();
private final DeliveryAssignmentService assignmentService =
new DeliveryAssignmentService(deliveryUserService, goodsOrderService, null);
private final RiderTaskService riderTaskService =
new RiderTaskService(assignmentService, goodsOrderService, null);
private GoodsOrderResponse preparedDeliveryOrder() {
ProductSkuResponse sku = productService.createDemoSku(1L, 1L, 1L, "500g", 990, 5);
CreateGoodsOrderRequest request = new CreateGoodsOrderRequest();
request.setTenantId(1L);
request.setCommunityId(1L);
request.setMerchantId(1L);
request.setAddressId(1L);
request.setDeliveryMethod(DeliveryMethod.IMMEDIATE);
request.setItems(Collections.singletonList(new CreateGoodsOrderItemRequest(1L, sku.getId(), 1)));
GoodsOrderResponse order = goodsOrderService.create(1L, request);
goodsOrderService.confirm(order.getId());
return goodsOrderService.markPrepared(order.getId());
}
private DeliveryUserResponse rider(Long userId, String name) {
DeliveryUserRequest request = new DeliveryUserRequest();
request.setTenantId(1L);
request.setCommunityId(1L);
request.setUserId(userId);
request.setName(name);
request.setPhone("13900000000");
return deliveryUserService.create(request);
}
}
}