Merge branch 'master' of https://gitea.zianxin.com/zax/biz-api-cloud-zax
This commit is contained in:
commit
1ae623ce66
38 files changed
+1422
-6
No files matched your search
+26
@@ -0,0 +1,26 @@
|
||||
package com.bsd.pjcloud.canteen.api.feign;
|
||||
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryOrderSyncDTO;
|
||||
import com.bsd.pjcloud.common.core.constant.ServiceNameConstants;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.feign.annotation.NoToken;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/**
|
||||
* 订单服务调用:同步外送单 / 取消外送单
|
||||
*/
|
||||
@FeignClient(contextId = "remoteCanteenDeliveryOrderService", value = ServiceNameConstants.CANTEEN_SERVICE)
|
||||
public interface RemoteCanteenDeliveryOrderService {
|
||||
|
||||
|
||||
@PostMapping("/canteenDeliveryOrder/inner/sync-from-order")
|
||||
@NoToken
|
||||
R<Long> syncFromOrder(@RequestBody CanteenDeliveryOrderSyncDTO dto);
|
||||
|
||||
@PostMapping("/canteenDeliveryOrder/inner/cancel-by-source-id")
|
||||
@NoToken
|
||||
R<Void> cancelBySourceOrderId(@RequestParam("sourceOrderId") String sourceOrderId);
|
||||
}
|
||||
+2
@@ -19,12 +19,14 @@ public interface RemoteCanteenProductionTemplateService {
|
||||
/**
|
||||
* 根据菜品id列表获取加工模版详情
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/canteenProductionTemplate/inner/getDetailsByDishIds")
|
||||
R<List<ProductTemplateVO>> getDetailsByDishIds(@RequestBody List<Long> dishIds);
|
||||
|
||||
/**
|
||||
* 根据菜品id列表获取线上加工模版详情(仅 template_type=2,用于线上订餐统计)
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/canteenProductionTemplate/inner/getDetailsByDishIdsForOnline")
|
||||
R<List<ProductTemplateVO>> getDetailsByDishIdsForOnline(@RequestBody List<Long> dishIds);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.bsd.pjcloud.canteen.constants;
|
||||
|
||||
public final class CanteenDeliveryAssignType {
|
||||
|
||||
private CanteenDeliveryAssignType() {
|
||||
}
|
||||
|
||||
public static final String NONE = "NONE";
|
||||
public static final String BIND = "BIND";
|
||||
public static final String ROUND_ROBIN = "ROUND_ROBIN";
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.bsd.pjcloud.canteen.constants;
|
||||
|
||||
/**
|
||||
* 外送单状态:待分配 → 待取餐 → 配送中 → 完成/取消
|
||||
*/
|
||||
public final class CanteenDeliveryOrderStatus {
|
||||
|
||||
private CanteenDeliveryOrderStatus() {
|
||||
}
|
||||
|
||||
/** 待分配骑手 */
|
||||
public static final int PENDING_ASSIGN = 0;
|
||||
/** 已指派,待到店取餐(上传取餐照后进入配送中) */
|
||||
public static final int WAIT_PICKUP = 1;
|
||||
/** 配送中 */
|
||||
public static final int DELIVERING = 2;
|
||||
/** 已完成 */
|
||||
public static final int COMPLETED = 3;
|
||||
/** 已取消 */
|
||||
public static final int CANCELLED = 4;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.bsd.pjcloud.canteen.constants;
|
||||
|
||||
/**
|
||||
* 食堂外送 — 推送给骑手的 WebSocket 业务类型(messageText JSON 内字段 bizType)
|
||||
*/
|
||||
public final class CanteenDeliveryPushConstants {
|
||||
|
||||
private CanteenDeliveryPushConstants() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 新外送单已指派给当前骑手(支付同步成功且默认池派单成功)
|
||||
*/
|
||||
public static final String BIZ_TYPE_ORDER_ASSIGNED = "CANTEEN_DELIVERY_ORDER_ASSIGNED";
|
||||
|
||||
/** messageText 内 schema 版本,前端兼容用 */
|
||||
public static final int PAYLOAD_VERSION = 1;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.bsd.pjcloud.canteen.dto.delivery;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Schema(description = "外送购物车行")
|
||||
public class CanteenDeliveryCartItemDTO {
|
||||
|
||||
private String id;
|
||||
private String parentCarId;
|
||||
/** 商品/SPU id */
|
||||
private String productId;
|
||||
private String productName;
|
||||
private String productPic;
|
||||
/** 单价,分 */
|
||||
private Integer productPrice;
|
||||
private Integer productCount;
|
||||
private BigDecimal productWeight;
|
||||
/** 行总价,分(兼容字符串) */
|
||||
private String totalPrice;
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.bsd.pjcloud.canteen.dto.delivery;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单支付成功后由订单服务 Inner 调用同步外送单。
|
||||
* <p>
|
||||
* 派单规则:
|
||||
* 1) 若传入 buildingId,则优先按「单位 + 楼栋绑定」指派骑手;
|
||||
* 2) 否则(或绑定无效)按单位默认骑手池轮询指派。
|
||||
* <p>
|
||||
* 收货展示用 fullAddress 快照;性别匹配用 {@link #buyerUserId} 查询用户表,不要求订单侧传 gender。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "外送单同步(订单回调)")
|
||||
public class CanteenDeliveryOrderSyncDTO {
|
||||
|
||||
@NotBlank
|
||||
@Schema(description = "来源订单号(业务唯一)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String id;
|
||||
|
||||
@Schema(description = "商品摘要")
|
||||
private String productDesc;
|
||||
|
||||
@Schema(description = "收货人姓名")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "收货人手机")
|
||||
private String userMobile;
|
||||
|
||||
@Schema(description = "商户名称")
|
||||
private String merchantName;
|
||||
|
||||
@Schema(description = "商户logo")
|
||||
private String logoPic;
|
||||
|
||||
@Schema(description = "购物车")
|
||||
private List<CanteenDeliveryCartItemDTO> carList;
|
||||
|
||||
// --------- 骑手/派单补充字段(由订单服务尽量传入)---------
|
||||
|
||||
@NotNull
|
||||
@Schema(description = "所属单位部门ID(仅该单位下骑手可配送)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long deptId;
|
||||
|
||||
@Schema(description = "下单用户ID")
|
||||
private Long buyerUserId;
|
||||
|
||||
@Schema(description = "商户ID(字符串)")
|
||||
private String merchantId;
|
||||
|
||||
@Schema(description = "配送楼栋ID(可选;有则优先按楼栋绑定骑手)")
|
||||
private Long buildingId;
|
||||
|
||||
@Schema(description = "完整收货地址(展示用)")
|
||||
private String fullAddress;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.bsd.pjcloud.canteen.dto.delivery;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "外送上传照片")
|
||||
public class CanteenDeliveryPhotoDTO {
|
||||
|
||||
@NotNull
|
||||
private Long id;
|
||||
|
||||
@NotBlank
|
||||
@Schema(description = "图片URL(先走你们统一上传拿地址)")
|
||||
private String photoUrl;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.bsd.pjcloud.canteen.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 按单位轮询派单序号(无外键,不按租户拆分时以 dept_id 唯一)
|
||||
*/
|
||||
@Data
|
||||
@TableName("canteen_delivery_assign_seq")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CanteenDeliveryAssignSeqEntity extends Model<CanteenDeliveryAssignSeqEntity> {
|
||||
|
||||
@TableId(value = "dept_id", type = IdType.INPUT)
|
||||
private Long deptId;
|
||||
private Long seq;
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.bsd.pjcloud.canteen.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_delivery_order")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "食堂外送单")
|
||||
public class CanteenDeliveryOrderEntity extends Model<CanteenDeliveryOrderEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private String sourceOrderId;
|
||||
private Long deptId;
|
||||
private Long buyerUserId;
|
||||
private String merchantId;
|
||||
private String productDesc;
|
||||
private String receiverName;
|
||||
private String receiverMobile;
|
||||
private String merchantName;
|
||||
private String merchantLogo;
|
||||
private Long buildingId;
|
||||
private Long unitId;
|
||||
private Long roomId;
|
||||
private String fullAddress;
|
||||
private Integer buyerGender;
|
||||
private String carListJson;
|
||||
private String assignType;
|
||||
private Long riderUserId;
|
||||
private Integer status;
|
||||
private String pickupPhotoUrl;
|
||||
private String deliveryPhotoUrl;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
private Long tenantId;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.bsd.pjcloud.canteen.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_rider_building_bind")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CanteenRiderBuildingBindEntity extends Model<CanteenRiderBuildingBindEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long deptId;
|
||||
private Long buildingId;
|
||||
private Long riderUserId;
|
||||
private Long tenantId;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.bsd.pjcloud.canteen.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_rider_pool")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CanteenRiderPoolEntity extends Model<CanteenRiderPoolEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long deptId;
|
||||
private Long riderUserId;
|
||||
private Integer sortOrder;
|
||||
private Long tenantId;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.bsd.pjcloud.canteen.vo.app;
|
||||
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryCartItemDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "骑手端外送单摘要")
|
||||
public class CanteenDeliveryOrderAppVO {
|
||||
|
||||
private Long id;
|
||||
private String sourceOrderId;
|
||||
private Long deptId;
|
||||
private String productDesc;
|
||||
private String receiverName;
|
||||
private String receiverMobile;
|
||||
private String merchantName;
|
||||
private String merchantLogo;
|
||||
private String fullAddress;
|
||||
private Integer status;
|
||||
private String pickupPhotoUrl;
|
||||
private String deliveryPhotoUrl;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "购物车行项目(由 car_list_json 反序列化)")
|
||||
private List<CanteenDeliveryCartItemDTO> carList;
|
||||
}
|
||||
+2
@@ -3,4 +3,6 @@ com.bsd.pjcloud.canteen.api.feign.RemoteCanteenOnlineCategoryService
|
||||
com.bsd.pjcloud.canteen.api.feign.RemoteCanteenSpuService
|
||||
com.bsd.pjcloud.canteen.api.feign.RemoteOnlineCanteenSpuService
|
||||
com.bsd.pjcloud.canteen.api.feign.RemoteDailyMenuService
|
||||
com.bsd.pjcloud.canteen.api.feign.RemoteCanteenDeliveryOrderService
|
||||
com.bsd.pjcloud.canteen.api.feign.RemoteCanteenProductionTemplateService
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package com.bsd.pjcloud.canteen.controller;
|
||||
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryOrderSyncDTO;
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryPhotoDTO;
|
||||
import com.bsd.pjcloud.canteen.push.CanteenDeliveryRiderPushPublisher;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenDeliveryOrderService;
|
||||
import com.bsd.pjcloud.canteen.vo.app.CanteenDeliveryOrderAppVO;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.common.security.annotation.Inner;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 食堂外送:订单同步 + 骑手 App
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/canteenDeliveryOrder")
|
||||
@Tag(name = "食堂外送订单")
|
||||
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
|
||||
public class CanteenDeliveryOrderController {
|
||||
|
||||
private final CanteenDeliveryOrderService canteenDeliveryOrderService;
|
||||
|
||||
private final CanteenDeliveryRiderPushPublisher riderPushPublisher;
|
||||
|
||||
/**
|
||||
* 联调:模拟「新单已指派」的 WebSocket 推送(与正式推送同一 Redis 频道与 messageText 结构)。
|
||||
* 骑手端须已用相同 userId 建立 WS;需管理端权限 {@code canteen_canteenDelivery_config}。
|
||||
*/
|
||||
@SysLog("测试骑手WS推送")
|
||||
@PostMapping("/test/mock-rider-push")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDelivery_config')")
|
||||
@Operation(summary = "测试-模拟向骑手推送新单 WS 消息", description = "参数 riderUserId 为骑手用户主键;deliveryOrderId 可选,不传则用时间戳占位(详情页可能 404,仅测消息与跳转)")
|
||||
public R<Void> mockRiderPush(@RequestParam Long riderUserId,
|
||||
@RequestParam(required = false) Long deliveryOrderId) {
|
||||
long fakeId = deliveryOrderId != null ? deliveryOrderId : System.currentTimeMillis();
|
||||
riderPushPublisher.publishOrderAssigned(riderUserId, fakeId, "MOCK-TEST-" + fakeId, "【测试】食堂", "【测试】收货地址");
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Inner
|
||||
@PostMapping("/inner/sync-from-order")
|
||||
@Operation(summary = "订单支付成功回调(内部服务)",
|
||||
description = "由订单服务 Feign 调用:落库、按单位默认骑手池指派骑手;指派成功且事务提交后,经 Redis 向骑手 WebSocket 下发新单通知(见食堂文档「骑手推送与联调规范」)。未指派(无池)则不推送。")
|
||||
public R<Long> syncFromOrder(@Valid @RequestBody CanteenDeliveryOrderSyncDTO dto) {
|
||||
return canteenDeliveryOrderService.syncFromOrder(dto);
|
||||
}
|
||||
|
||||
@Inner
|
||||
@PostMapping("/inner/cancel-by-source-id")
|
||||
@Operation(summary = "订单取消回调(内部服务)")
|
||||
public R<Void> cancelBySourceOrderId(@RequestParam("sourceOrderId") String sourceOrderId) {
|
||||
return canteenDeliveryOrderService.cancelBySourceOrderId(sourceOrderId);
|
||||
}
|
||||
|
||||
@GetMapping("/app/list")
|
||||
@Operation(summary = "骑手端-我的配送单", description = "tab: pending待配送 | all全部 | completed已完成 | cancelled已取消")
|
||||
public R<List<CanteenDeliveryOrderAppVO>> appList(@RequestParam(defaultValue = "pending") String tab) {
|
||||
Long uid = SecurityUtils.getUser().getId();
|
||||
return R.ok(canteenDeliveryOrderService.listForRider(uid, tab));
|
||||
}
|
||||
|
||||
@GetMapping("/app/{id}")
|
||||
@Operation(summary = "骑手端-配送单详情")
|
||||
public R<CanteenDeliveryOrderAppVO> appDetail(@PathVariable Long id) {
|
||||
Long uid = SecurityUtils.getUser().getId();
|
||||
CanteenDeliveryOrderAppVO vo = canteenDeliveryOrderService.detailForRider(uid, id);
|
||||
if (vo == null) {
|
||||
return R.failed("订单不存在或无权查看");
|
||||
}
|
||||
return R.ok(vo);
|
||||
}
|
||||
|
||||
@SysLog("取餐拍照")
|
||||
@PostMapping("/app/pickup")
|
||||
@Operation(summary = "骑手端-到店取餐拍照")
|
||||
public R<Void> pickup(@Valid @RequestBody CanteenDeliveryPhotoDTO dto) {
|
||||
return canteenDeliveryOrderService.pickup(SecurityUtils.getUser().getId(), dto);
|
||||
}
|
||||
|
||||
@SysLog("送达拍照")
|
||||
@PostMapping("/app/deliver")
|
||||
@Operation(summary = "骑手端-送达拍照")
|
||||
public R<Void> deliver(@Valid @RequestBody CanteenDeliveryPhotoDTO dto) {
|
||||
return canteenDeliveryOrderService.deliver(SecurityUtils.getUser().getId(), dto);
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package com.bsd.pjcloud.canteen.controller;
|
||||
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderBuildingBindEntity;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderPoolEntity;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenRiderConfigService;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 骑手与楼栋绑定、默认骑手池(管理端)
|
||||
* <p>权限码 canteen_canteenDelivery_config 需在 sys_menu/sys_role_menu 中配置</p>
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/canteenRiderConfig")
|
||||
@Tag(name = "食堂骑手配置")
|
||||
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
|
||||
public class CanteenRiderConfigController {
|
||||
|
||||
private final CanteenRiderConfigService canteenRiderConfigService;
|
||||
|
||||
@GetMapping("/building-bind/list")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDelivery_config')")
|
||||
@Operation(summary = "楼栋绑定列表")
|
||||
public R<List<CanteenRiderBuildingBindEntity>> listBind(@RequestParam Long deptId) {
|
||||
return R.ok(canteenRiderConfigService.listBuildingBind(deptId));
|
||||
}
|
||||
|
||||
@PostMapping("/building-bind")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDelivery_config')")
|
||||
@Operation(summary = "新增楼栋-骑手绑定")
|
||||
public R<Long> saveBind(@RequestParam Long deptId, @RequestParam Long buildingId, @RequestParam Long riderUserId) {
|
||||
return canteenRiderConfigService.saveBuildingBind(deptId, buildingId, riderUserId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/building-bind/{id}")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDelivery_config')")
|
||||
@Operation(summary = "删除楼栋绑定")
|
||||
public R<Void> removeBind(@PathVariable Long id) {
|
||||
return canteenRiderConfigService.removeBuildingBind(id);
|
||||
}
|
||||
|
||||
@GetMapping("/pool/list")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDelivery_config')")
|
||||
@Operation(summary = "默认骑手池列表")
|
||||
public R<List<CanteenRiderPoolEntity>> listPool(@RequestParam Long deptId) {
|
||||
return R.ok(canteenRiderConfigService.listPool(deptId));
|
||||
}
|
||||
|
||||
@PostMapping("/pool")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDelivery_config')")
|
||||
@Operation(summary = "新增默认骑手(轮询顺序)")
|
||||
public R<Long> savePool(@RequestParam Long deptId, @RequestParam Long riderUserId,
|
||||
@RequestParam(required = false, defaultValue = "0") Integer sortOrder) {
|
||||
return canteenRiderConfigService.savePool(deptId, riderUserId, sortOrder);
|
||||
}
|
||||
|
||||
@DeleteMapping("/pool/{id}")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDelivery_config')")
|
||||
@Operation(summary = "从默认池移除骑手")
|
||||
public R<Void> removePool(@PathVariable Long id) {
|
||||
return canteenRiderConfigService.removePool(id);
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.bsd.pjcloud.canteen.mapper;
|
||||
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenDeliveryAssignSeqEntity;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface CanteenDeliveryAssignSeqMapper extends BaseMapper<CanteenDeliveryAssignSeqEntity> {
|
||||
|
||||
@Update("UPDATE canteen_delivery_assign_seq SET seq = seq + 1 WHERE dept_id = #{deptId}")
|
||||
int incrSeq(@Param("deptId") Long deptId);
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.canteen.mapper;
|
||||
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenDeliveryOrderEntity;
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CanteenDeliveryOrderMapper extends PjcloudBaseMapper<CanteenDeliveryOrderEntity> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.canteen.mapper;
|
||||
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderBuildingBindEntity;
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CanteenRiderBuildingBindMapper extends PjcloudBaseMapper<CanteenRiderBuildingBindEntity> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.canteen.mapper;
|
||||
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderPoolEntity;
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CanteenRiderPoolMapper extends PjcloudBaseMapper<CanteenRiderPoolEntity> {
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.bsd.pjcloud.canteen.push;
|
||||
|
||||
import com.bsd.pjcloud.canteen.constants.CanteenDeliveryPushConstants;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 向骑手 WebSocket 会话发通知:通过 Redis 频道广播,由已连接 WebSocket 的网关服务(通常为 UPMS)订阅并下发。
|
||||
* 频道名与 {@code com.bsd.pjcloud.common.websocket.distribute.RedisWebsocketMessageListener#CHANNEL} 一致。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CanteenDeliveryRiderPushPublisher {
|
||||
|
||||
/** 与 pjcloud-common-websocket 中 RedisWebsocketMessageListener.CHANNEL 保持一致 */
|
||||
public static final String REDIS_CHANNEL_WEBSOCKET_SEND = "websocket-send";
|
||||
|
||||
private final ObjectProvider<StringRedisTemplate> stringRedisTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 新外送单已指派:推给指定骑手 userId(与 WS SessionKey 一致,为字符串形式的用户主键)
|
||||
*/
|
||||
public void publishOrderAssigned(Long riderUserId,
|
||||
Long deliveryOrderId,
|
||||
String sourceOrderId,
|
||||
String merchantName,
|
||||
String fullAddress) {
|
||||
if (riderUserId == null) {
|
||||
return;
|
||||
}
|
||||
StringRedisTemplate redis = stringRedisTemplate.getIfAvailable();
|
||||
if (redis == null) {
|
||||
log.debug("StringRedisTemplate 不可用,跳过外送骑手 WS 推送");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ObjectNode envelope = objectMapper.createObjectNode();
|
||||
envelope.put("bizType", CanteenDeliveryPushConstants.BIZ_TYPE_ORDER_ASSIGNED);
|
||||
envelope.put("version", CanteenDeliveryPushConstants.PAYLOAD_VERSION);
|
||||
envelope.put("title", "新配送任务");
|
||||
envelope.put("body", "您有一单新的食堂外送,请及时处理");
|
||||
envelope.set("payload", objectMapper.valueToTree(Map.of(
|
||||
"deliveryOrderId", deliveryOrderId,
|
||||
"sourceOrderId", sourceOrderId != null ? sourceOrderId : "",
|
||||
"merchantName", merchantName != null ? merchantName : "",
|
||||
"fullAddress", fullAddress != null ? fullAddress : ""
|
||||
)));
|
||||
ObjectNode route = objectMapper.createObjectNode();
|
||||
route.put("type", "navigate");
|
||||
route.put("path", "/pages/canteen/rider/delivery-detail");
|
||||
route.set("query", objectMapper.valueToTree(Map.of("id", String.valueOf(deliveryOrderId))));
|
||||
envelope.set("route", route);
|
||||
|
||||
String messageText = objectMapper.writeValueAsString(envelope);
|
||||
Map<String, Object> messageDo = Map.of(
|
||||
"needBroadcast", false,
|
||||
"sessionKeys", List.of(String.valueOf(riderUserId)),
|
||||
"messageText", messageText
|
||||
);
|
||||
redis.convertAndSend(REDIS_CHANNEL_WEBSOCKET_SEND, objectMapper.writeValueAsString(messageDo));
|
||||
} catch (Exception e) {
|
||||
log.warn("外送单指派推送 Redis 发布失败 riderUserId={} deliveryOrderId={}", riderUserId, deliveryOrderId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.bsd.pjcloud.canteen.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryOrderSyncDTO;
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryPhotoDTO;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenDeliveryOrderEntity;
|
||||
import com.bsd.pjcloud.canteen.vo.app.CanteenDeliveryOrderAppVO;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CanteenDeliveryOrderService extends IService<CanteenDeliveryOrderEntity> {
|
||||
|
||||
/**
|
||||
* 订单服务支付成功回调,落库并自动派单
|
||||
*/
|
||||
R<Long> syncFromOrder(CanteenDeliveryOrderSyncDTO dto);
|
||||
|
||||
R<Void> cancelBySourceOrderId(String sourceOrderId);
|
||||
|
||||
/**
|
||||
* @param tab pending | all | completed | cancelled
|
||||
*/
|
||||
List<CanteenDeliveryOrderAppVO> listForRider(Long riderUserId, String tab);
|
||||
|
||||
CanteenDeliveryOrderAppVO detailForRider(Long riderUserId, Long id);
|
||||
|
||||
R<Void> pickup(Long riderUserId, CanteenDeliveryPhotoDTO dto);
|
||||
|
||||
R<Void> deliver(Long riderUserId, CanteenDeliveryPhotoDTO dto);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.bsd.pjcloud.canteen.service;
|
||||
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderBuildingBindEntity;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderPoolEntity;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CanteenRiderConfigService {
|
||||
|
||||
R<Long> saveBuildingBind(Long deptId, Long buildingId, Long riderUserId);
|
||||
|
||||
R<Void> removeBuildingBind(Long id);
|
||||
|
||||
List<CanteenRiderBuildingBindEntity> listBuildingBind(Long deptId);
|
||||
|
||||
R<Long> savePool(Long deptId, Long riderUserId, Integer sortOrder);
|
||||
|
||||
R<Void> removePool(Long id);
|
||||
|
||||
List<CanteenRiderPoolEntity> listPool(Long deptId);
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
package com.bsd.pjcloud.canteen.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.admin.api.dto.UserInfo;
|
||||
import com.bsd.pjcloud.admin.api.entity.SysDept;
|
||||
import com.bsd.pjcloud.admin.api.entity.SysUser;
|
||||
import com.bsd.pjcloud.admin.api.feign.RemoteDeptService;
|
||||
import com.bsd.pjcloud.admin.api.feign.RemoteUserService;
|
||||
import com.bsd.pjcloud.canteen.constants.CanteenDeliveryAssignType;
|
||||
import com.bsd.pjcloud.canteen.constants.CanteenDeliveryOrderStatus;
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryCartItemDTO;
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryOrderSyncDTO;
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryPhotoDTO;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenDeliveryAssignSeqEntity;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenDeliveryOrderEntity;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderBuildingBindEntity;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderPoolEntity;
|
||||
import com.bsd.pjcloud.canteen.mapper.CanteenDeliveryAssignSeqMapper;
|
||||
import com.bsd.pjcloud.canteen.mapper.CanteenDeliveryOrderMapper;
|
||||
import com.bsd.pjcloud.canteen.mapper.CanteenRiderBuildingBindMapper;
|
||||
import com.bsd.pjcloud.canteen.mapper.CanteenRiderPoolMapper;
|
||||
import com.bsd.pjcloud.canteen.push.CanteenDeliveryRiderPushPublisher;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenDeliveryOrderService;
|
||||
import com.bsd.pjcloud.canteen.vo.app.CanteenDeliveryOrderAppVO;
|
||||
import com.bsd.pjcloud.common.core.util.BeanUtils;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CanteenDeliveryOrderServiceImpl extends ServiceImpl<CanteenDeliveryOrderMapper, CanteenDeliveryOrderEntity>
|
||||
implements CanteenDeliveryOrderService {
|
||||
|
||||
private final CanteenRiderBuildingBindMapper riderBuildingBindMapper;
|
||||
private final CanteenRiderPoolMapper riderPoolMapper;
|
||||
private final CanteenDeliveryAssignSeqMapper assignSeqMapper;
|
||||
private final RemoteUserService remoteUserService;
|
||||
private final RemoteDeptService remoteDeptService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final CanteenDeliveryRiderPushPublisher riderPushPublisher;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Long> syncFromOrder(CanteenDeliveryOrderSyncDTO dto) {
|
||||
long exists = count(Wrappers.<CanteenDeliveryOrderEntity>lambdaQuery()
|
||||
.eq(CanteenDeliveryOrderEntity::getSourceOrderId, dto.getId()));
|
||||
if (exists > 0) {
|
||||
return R.failed("订单已同步过");
|
||||
}
|
||||
CanteenDeliveryOrderEntity entity = new CanteenDeliveryOrderEntity();
|
||||
entity.setSourceOrderId(dto.getId());
|
||||
entity.setDeptId(dto.getDeptId());
|
||||
entity.setBuyerUserId(dto.getBuyerUserId());
|
||||
entity.setMerchantId(dto.getMerchantId());
|
||||
entity.setProductDesc(dto.getProductDesc());
|
||||
// 收货人信息:优先使用订单侧回传;缺失时根据 buyerUserId 自动补齐
|
||||
entity.setReceiverName(dto.getUsername());
|
||||
entity.setReceiverMobile(dto.getUserMobile());
|
||||
if ((StrUtil.isBlank(entity.getReceiverName()) || StrUtil.isBlank(entity.getReceiverMobile()))
|
||||
&& dto.getBuyerUserId() != null) {
|
||||
R<UserInfo> r = remoteUserService.getInnerByUserId(dto.getBuyerUserId());
|
||||
if (r != null && r.isOk() && r.getData() != null && r.getData().getSysUser() != null) {
|
||||
SysUser u = r.getData().getSysUser();
|
||||
if (StrUtil.isBlank(entity.getReceiverName())) {
|
||||
entity.setReceiverName(u.getName());
|
||||
}
|
||||
if (StrUtil.isBlank(entity.getReceiverMobile())) {
|
||||
entity.setReceiverMobile(u.getPhone());
|
||||
}
|
||||
}
|
||||
}
|
||||
entity.setMerchantName(dto.getMerchantName());
|
||||
entity.setMerchantLogo(dto.getLogoPic());
|
||||
entity.setBuildingId(dto.getBuildingId());
|
||||
entity.setFullAddress(dto.getFullAddress());
|
||||
try {
|
||||
entity.setCarListJson(objectMapper.writeValueAsString(dto.getCarList()));
|
||||
} catch (JsonProcessingException e) {
|
||||
return R.failed("购物车序列化失败");
|
||||
}
|
||||
assignRiderOnCreate(entity);
|
||||
save(entity);
|
||||
scheduleRiderPushAfterAssign(entity);
|
||||
return R.ok(entity.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务提交后再推送,避免回滚后骑手仍收到通知。
|
||||
*/
|
||||
private void scheduleRiderPushAfterAssign(CanteenDeliveryOrderEntity entity) {
|
||||
if (entity.getRiderUserId() == null
|
||||
|| !Objects.equals(entity.getStatus(), CanteenDeliveryOrderStatus.WAIT_PICKUP)) {
|
||||
return;
|
||||
}
|
||||
final Long riderId = entity.getRiderUserId();
|
||||
final Long deliveryId = entity.getId();
|
||||
final String sourceOrderId = entity.getSourceOrderId();
|
||||
final String merchantName = entity.getMerchantName();
|
||||
final String fullAddress = entity.getFullAddress();
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
riderPushPublisher.publishOrderAssigned(riderId, deliveryId, sourceOrderId, merchantName, fullAddress);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
riderPushPublisher.publishOrderAssigned(riderId, deliveryId, sourceOrderId, merchantName, fullAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private void assignRiderOnCreate(CanteenDeliveryOrderEntity entity) {
|
||||
Long deptId = entity.getDeptId();
|
||||
entity.setAssignType(CanteenDeliveryAssignType.NONE);
|
||||
entity.setRiderUserId(null);
|
||||
entity.setStatus(CanteenDeliveryOrderStatus.PENDING_ASSIGN);
|
||||
|
||||
// 1) 优先楼栋绑定(有 buildingId 才有意义)
|
||||
if (entity.getBuildingId() != null) {
|
||||
CanteenRiderBuildingBindEntity bind = riderBuildingBindMapper.selectOne(
|
||||
Wrappers.<CanteenRiderBuildingBindEntity>lambdaQuery()
|
||||
.eq(CanteenRiderBuildingBindEntity::getDeptId, deptId)
|
||||
.eq(CanteenRiderBuildingBindEntity::getBuildingId, entity.getBuildingId()));
|
||||
if (bind != null && verifyRiderInUnit(bind.getRiderUserId(), deptId)) {
|
||||
entity.setRiderUserId(bind.getRiderUserId());
|
||||
entity.setAssignType(CanteenDeliveryAssignType.BIND);
|
||||
entity.setStatus(CanteenDeliveryOrderStatus.WAIT_PICKUP);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 默认骑手池轮询
|
||||
List<CanteenRiderPoolEntity> pool = riderPoolMapper.selectList(
|
||||
Wrappers.<CanteenRiderPoolEntity>lambdaQuery()
|
||||
.eq(CanteenRiderPoolEntity::getDeptId, deptId)
|
||||
.orderByAsc(CanteenRiderPoolEntity::getSortOrder)
|
||||
.orderByAsc(CanteenRiderPoolEntity::getId));
|
||||
if (pool.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Integer buyerGender = fetchUserGender(entity.getBuyerUserId());
|
||||
List<CanteenRiderPoolEntity> filtered = filterPoolByBuyerGender(pool, buyerGender);
|
||||
long seq = nextSeq(deptId);
|
||||
int idx = (int) ((seq - 1) % filtered.size());
|
||||
if (idx < 0) {
|
||||
idx = 0;
|
||||
}
|
||||
CanteenRiderPoolEntity chosen = filtered.get(idx);
|
||||
if (!verifyRiderInUnit(chosen.getRiderUserId(), deptId)) {
|
||||
for (CanteenRiderPoolEntity p : filtered) {
|
||||
if (verifyRiderInUnit(p.getRiderUserId(), deptId)) {
|
||||
chosen = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!verifyRiderInUnit(chosen.getRiderUserId(), deptId)) {
|
||||
return;
|
||||
}
|
||||
entity.setRiderUserId(chosen.getRiderUserId());
|
||||
entity.setAssignType(CanteenDeliveryAssignType.ROUND_ROBIN);
|
||||
entity.setStatus(CanteenDeliveryOrderStatus.WAIT_PICKUP);
|
||||
}
|
||||
|
||||
private List<CanteenRiderPoolEntity> filterPoolByBuyerGender(List<CanteenRiderPoolEntity> pool, Integer buyerGender) {
|
||||
if (buyerGender == null || (buyerGender != 1 && buyerGender != 2)) {
|
||||
return pool;
|
||||
}
|
||||
List<CanteenRiderPoolEntity> matched = new ArrayList<>();
|
||||
for (CanteenRiderPoolEntity p : pool) {
|
||||
Integer g = fetchUserGender(p.getRiderUserId());
|
||||
if (buyerGender.equals(g)) {
|
||||
matched.add(p);
|
||||
}
|
||||
}
|
||||
return matched.isEmpty() ? pool : matched;
|
||||
}
|
||||
|
||||
private Integer fetchUserGender(Long userId) {
|
||||
if (userId == null) {
|
||||
return 0;
|
||||
}
|
||||
R<UserInfo> r = remoteUserService.getInnerByUserId(userId);
|
||||
if (r == null || !r.isOk() || r.getData() == null || r.getData().getSysUser() == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer g = r.getData().getSysUser().getGender();
|
||||
return g == null ? 0 : g;
|
||||
}
|
||||
|
||||
private boolean verifyRiderInUnit(Long riderUserId, Long unitDeptId) {
|
||||
if (riderUserId == null || unitDeptId == null) {
|
||||
return false;
|
||||
}
|
||||
R<UserInfo> r = remoteUserService.getInnerByUserId(riderUserId);
|
||||
if (r == null || !r.isOk() || r.getData() == null || r.getData().getSysUser() == null) {
|
||||
return false;
|
||||
}
|
||||
SysUser u = r.getData().getSysUser();
|
||||
Long riderDept = u.getDeptId();
|
||||
if (riderDept == null) {
|
||||
return false;
|
||||
}
|
||||
if (unitDeptId.equals(riderDept)) {
|
||||
return true;
|
||||
}
|
||||
R<List<SysDept>> parents = remoteDeptService.getParentDeptList(riderDept);
|
||||
if (parents == null || !parents.isOk() || parents.getData() == null) {
|
||||
return false;
|
||||
}
|
||||
return parents.getData().stream().anyMatch(d -> unitDeptId.equals(d.getDeptId()));
|
||||
}
|
||||
|
||||
private long nextSeq(Long deptId) {
|
||||
CanteenDeliveryAssignSeqEntity row = assignSeqMapper.selectById(deptId);
|
||||
if (row == null) {
|
||||
row = new CanteenDeliveryAssignSeqEntity();
|
||||
row.setDeptId(deptId);
|
||||
row.setSeq(0L);
|
||||
assignSeqMapper.insert(row);
|
||||
}
|
||||
assignSeqMapper.incrSeq(deptId);
|
||||
CanteenDeliveryAssignSeqEntity after = assignSeqMapper.selectById(deptId);
|
||||
return after != null && after.getSeq() != null ? after.getSeq() : 1L;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Void> cancelBySourceOrderId(String sourceOrderId) {
|
||||
if (StrUtil.isBlank(sourceOrderId)) {
|
||||
return R.failed("订单号不能为空");
|
||||
}
|
||||
CanteenDeliveryOrderEntity one = getOne(Wrappers.<CanteenDeliveryOrderEntity>lambdaQuery()
|
||||
.eq(CanteenDeliveryOrderEntity::getSourceOrderId, sourceOrderId));
|
||||
if (one == null) {
|
||||
return R.ok();
|
||||
}
|
||||
one.setStatus(CanteenDeliveryOrderStatus.CANCELLED);
|
||||
updateById(one);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CanteenDeliveryOrderAppVO> listForRider(Long riderUserId, String tab) {
|
||||
String t = StrUtil.emptyToDefault(tab, "all");
|
||||
var q = Wrappers.<CanteenDeliveryOrderEntity>lambdaQuery()
|
||||
.eq(CanteenDeliveryOrderEntity::getRiderUserId, riderUserId);
|
||||
switch (t) {
|
||||
case "pending" ->
|
||||
q.in(CanteenDeliveryOrderEntity::getStatus, CanteenDeliveryOrderStatus.WAIT_PICKUP, CanteenDeliveryOrderStatus.DELIVERING);
|
||||
case "completed" -> q.eq(CanteenDeliveryOrderEntity::getStatus, CanteenDeliveryOrderStatus.COMPLETED);
|
||||
case "cancelled" -> q.eq(CanteenDeliveryOrderEntity::getStatus, CanteenDeliveryOrderStatus.CANCELLED);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
q.orderByDesc(CanteenDeliveryOrderEntity::getCreateTime);
|
||||
List<CanteenDeliveryOrderEntity> list = list(q);
|
||||
return list.stream().map(this::toAppVoWithCart).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanteenDeliveryOrderAppVO detailForRider(Long riderUserId, Long id) {
|
||||
CanteenDeliveryOrderEntity e = getById(id);
|
||||
if (e == null || !Objects.equals(e.getRiderUserId(), riderUserId)) {
|
||||
return null;
|
||||
}
|
||||
return toAppVoWithCart(e);
|
||||
}
|
||||
|
||||
private CanteenDeliveryOrderAppVO toAppVoWithCart(CanteenDeliveryOrderEntity e) {
|
||||
CanteenDeliveryOrderAppVO vo = BeanUtils.toBean(e, CanteenDeliveryOrderAppVO.class);
|
||||
vo.setCarList(parseCartList(e.getCarListJson()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
private List<CanteenDeliveryCartItemDTO> parseCartList(String carListJson) {
|
||||
if (StrUtil.isBlank(carListJson)) {
|
||||
return List.of();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(carListJson, new TypeReference<List<CanteenDeliveryCartItemDTO>>() {});
|
||||
} catch (Exception ex) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Void> pickup(Long riderUserId, CanteenDeliveryPhotoDTO dto) {
|
||||
CanteenDeliveryOrderEntity e = getById(dto.getId());
|
||||
if (e == null || !Objects.equals(e.getRiderUserId(), riderUserId)) {
|
||||
return R.failed("订单不存在或无权操作");
|
||||
}
|
||||
if (e.getStatus() != CanteenDeliveryOrderStatus.WAIT_PICKUP) {
|
||||
return R.failed("当前状态不可上传取餐照");
|
||||
}
|
||||
e.setPickupPhotoUrl(dto.getPhotoUrl());
|
||||
e.setStatus(CanteenDeliveryOrderStatus.DELIVERING);
|
||||
updateById(e);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Void> deliver(Long riderUserId, CanteenDeliveryPhotoDTO dto) {
|
||||
CanteenDeliveryOrderEntity e = getById(dto.getId());
|
||||
if (e == null || !Objects.equals(e.getRiderUserId(), riderUserId)) {
|
||||
return R.failed("订单不存在或无权操作");
|
||||
}
|
||||
if (e.getStatus() != CanteenDeliveryOrderStatus.DELIVERING) {
|
||||
return R.failed("当前状态不可上传送达照");
|
||||
}
|
||||
e.setDeliveryPhotoUrl(dto.getPhotoUrl());
|
||||
e.setStatus(CanteenDeliveryOrderStatus.COMPLETED);
|
||||
updateById(e);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package com.bsd.pjcloud.canteen.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderBuildingBindEntity;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenRiderPoolEntity;
|
||||
import com.bsd.pjcloud.canteen.mapper.CanteenRiderBuildingBindMapper;
|
||||
import com.bsd.pjcloud.canteen.mapper.CanteenRiderPoolMapper;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenRiderConfigService;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CanteenRiderConfigServiceImpl implements CanteenRiderConfigService {
|
||||
|
||||
private final CanteenRiderBuildingBindMapper buildingBindMapper;
|
||||
private final CanteenRiderPoolMapper poolMapper;
|
||||
|
||||
@Override
|
||||
public R<Long> saveBuildingBind(Long deptId, Long buildingId, Long riderUserId) {
|
||||
long dup = buildingBindMapper.selectCount(Wrappers.<CanteenRiderBuildingBindEntity>lambdaQuery()
|
||||
.eq(CanteenRiderBuildingBindEntity::getDeptId, deptId)
|
||||
.eq(CanteenRiderBuildingBindEntity::getBuildingId, buildingId));
|
||||
if (dup > 0) {
|
||||
return R.failed("该单位下楼栋已绑定骑手,请先解绑");
|
||||
}
|
||||
CanteenRiderBuildingBindEntity e = new CanteenRiderBuildingBindEntity();
|
||||
e.setDeptId(deptId);
|
||||
e.setBuildingId(buildingId);
|
||||
e.setRiderUserId(riderUserId);
|
||||
buildingBindMapper.insert(e);
|
||||
return R.ok(e.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<Void> removeBuildingBind(Long id) {
|
||||
buildingBindMapper.deleteById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CanteenRiderBuildingBindEntity> listBuildingBind(Long deptId) {
|
||||
return buildingBindMapper.selectList(Wrappers.<CanteenRiderBuildingBindEntity>lambdaQuery()
|
||||
.eq(CanteenRiderBuildingBindEntity::getDeptId, deptId)
|
||||
.orderByAsc(CanteenRiderBuildingBindEntity::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<Long> savePool(Long deptId, Long riderUserId, Integer sortOrder) {
|
||||
CanteenRiderPoolEntity e = new CanteenRiderPoolEntity();
|
||||
e.setDeptId(deptId);
|
||||
e.setRiderUserId(riderUserId);
|
||||
e.setSortOrder(sortOrder == null ? 0 : sortOrder);
|
||||
poolMapper.insert(e);
|
||||
return R.ok(e.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<Void> removePool(Long id) {
|
||||
poolMapper.deleteById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CanteenRiderPoolEntity> listPool(Long deptId) {
|
||||
return poolMapper.selectList(Wrappers.<CanteenRiderPoolEntity>lambdaQuery()
|
||||
.eq(CanteenRiderPoolEntity::getDeptId, deptId)
|
||||
.orderByAsc(CanteenRiderPoolEntity::getSortOrder)
|
||||
.orderByAsc(CanteenRiderPoolEntity::getId));
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
package com.bsd.pjcloud.canteen.controller;
|
||||
|
||||
import com.bsd.pjcloud.canteen.service.CanteenDeliveryOrderService;
|
||||
import com.bsd.pjcloud.canteen.vo.app.CanteenDeliveryOrderAppVO;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* 本地可跑:不连库、不连 Nacos,仅校验 Controller 入参与路由。
|
||||
* <pre>
|
||||
* cd pjcloud-canteen/pjcloud-canteen-biz
|
||||
* mvn test -Dtest=CanteenDeliveryOrderControllerTest
|
||||
* </pre>
|
||||
*/
|
||||
@WebMvcTest(controllers = CanteenDeliveryOrderController.class)
|
||||
@AutoConfigureMockMvc(addFilters = false)
|
||||
class CanteenDeliveryOrderControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@MockBean
|
||||
private CanteenDeliveryOrderService canteenDeliveryOrderService;
|
||||
|
||||
private static final long RIDER_USER_ID = 9001L;
|
||||
|
||||
@BeforeEach
|
||||
void loginRider() {
|
||||
PjcloudUser rider = new PjcloudUser(
|
||||
RIDER_USER_ID,
|
||||
"rider_test",
|
||||
1L,
|
||||
"13800000000",
|
||||
null,
|
||||
null,
|
||||
"测试骑手",
|
||||
null,
|
||||
1L,
|
||||
"nop",
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
Collections.emptyList());
|
||||
UsernamePasswordAuthenticationToken auth =
|
||||
new UsernamePasswordAuthenticationToken(rider, rider.getPassword(), rider.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearSecurity() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerSyncFromOrder_ok() throws Exception {
|
||||
when(canteenDeliveryOrderService.syncFromOrder(any())).thenReturn(R.ok(12345L));
|
||||
|
||||
String body = """
|
||||
{"id":"TEST-ORDER-001","deptId":100}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post("/canteenDeliveryOrder/inner/sync-from-order")
|
||||
.header(SecurityConstants.FROM, SecurityConstants.FROM_IN)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.ok").value(true))
|
||||
.andExpect(jsonPath("$.data").value(12345));
|
||||
|
||||
verify(canteenDeliveryOrderService).syncFromOrder(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerSyncFromOrder_validationFail() throws Exception {
|
||||
mockMvc.perform(post("/canteenDeliveryOrder/inner/sync-from-order")
|
||||
.header(SecurityConstants.FROM, SecurityConstants.FROM_IN)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerCancel_ok() throws Exception {
|
||||
when(canteenDeliveryOrderService.cancelBySourceOrderId("TEST-ORDER-001")).thenReturn(R.ok());
|
||||
|
||||
mockMvc.perform(post("/canteenDeliveryOrder/inner/cancel-by-source-id")
|
||||
.header(SecurityConstants.FROM, SecurityConstants.FROM_IN)
|
||||
.param("sourceOrderId", "TEST-ORDER-001"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.ok").value(true));
|
||||
|
||||
verify(canteenDeliveryOrderService).cancelBySourceOrderId("TEST-ORDER-001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void appList_delegatesToService() throws Exception {
|
||||
CanteenDeliveryOrderAppVO vo = new CanteenDeliveryOrderAppVO();
|
||||
vo.setId(1L);
|
||||
vo.setSourceOrderId("SO-1");
|
||||
when(canteenDeliveryOrderService.listForRider(RIDER_USER_ID, "pending")).thenReturn(List.of(vo));
|
||||
|
||||
mockMvc.perform(get("/canteenDeliveryOrder/app/list").param("tab", "pending"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.ok").value(true))
|
||||
.andExpect(jsonPath("$.data[0].id").value(1))
|
||||
.andExpect(jsonPath("$.data[0].sourceOrderId").value("SO-1"));
|
||||
|
||||
verify(canteenDeliveryOrderService).listForRider(RIDER_USER_ID, "pending");
|
||||
}
|
||||
|
||||
@Test
|
||||
void appDetail_ok() throws Exception {
|
||||
CanteenDeliveryOrderAppVO vo = new CanteenDeliveryOrderAppVO();
|
||||
vo.setId(99L);
|
||||
when(canteenDeliveryOrderService.detailForRider(eq(RIDER_USER_ID), eq(99L))).thenReturn(vo);
|
||||
|
||||
mockMvc.perform(get("/canteenDeliveryOrder/app/99"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.ok").value(true))
|
||||
.andExpect(jsonPath("$.data.id").value(99));
|
||||
}
|
||||
|
||||
@Test
|
||||
void appDetail_notFound() throws Exception {
|
||||
when(canteenDeliveryOrderService.detailForRider(eq(RIDER_USER_ID), eq(404L))).thenReturn(null);
|
||||
|
||||
mockMvc.perform(get("/canteenDeliveryOrder/app/404"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.ok").value(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pickup_ok() throws Exception {
|
||||
when(canteenDeliveryOrderService.pickup(eq(RIDER_USER_ID), any())).thenReturn(R.ok());
|
||||
|
||||
mockMvc.perform(post("/canteenDeliveryOrder/app/pickup")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(
|
||||
java.util.Map.of("id", 1L, "photoUrl", "https://example.com/a.jpg"))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.ok").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deliver_ok() throws Exception {
|
||||
when(canteenDeliveryOrderService.deliver(eq(RIDER_USER_ID), any())).thenReturn(R.ok());
|
||||
|
||||
mockMvc.perform(post("/canteenDeliveryOrder/app/deliver")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(
|
||||
java.util.Map.of("id", 1L, "photoUrl", "https://example.com/b.jpg"))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.ok").value(true));
|
||||
}
|
||||
}
|
||||
+6
@@ -1,6 +1,7 @@
|
||||
package com.bsd.pjcloud.hard.api.dto.order.app;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@@ -46,6 +47,11 @@ public class HardAppTakeOutAddress {
|
||||
*/
|
||||
private String roomName;
|
||||
|
||||
/**
|
||||
* 详细地址
|
||||
*/
|
||||
private String fullAddress;
|
||||
|
||||
public String getAddressName() {
|
||||
String addressName = "";
|
||||
if (ObjectUtil.isNotEmpty(buildingName)) {
|
||||
|
||||
+7
@@ -92,6 +92,13 @@ public class HardOrderAddressEntity extends Model<HardOrderAddressEntity> {
|
||||
@Schema(description = "房间名")
|
||||
private String roomName;
|
||||
|
||||
|
||||
/**
|
||||
* 详细地址
|
||||
*/
|
||||
@Schema(description = "详细地址")
|
||||
private String fullAddress;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
|
||||
+21
@@ -17,8 +17,11 @@ import com.bsd.pjcloud.admin.api.feign.RemoteDeptService;
|
||||
import com.bsd.pjcloud.admin.api.feign.RemoteUserService;
|
||||
import com.bsd.pjcloud.admin.api.utils.DataUtil;
|
||||
import com.bsd.pjcloud.admin.api.vo.UserVO;
|
||||
import com.bsd.pjcloud.canteen.api.feign.RemoteCanteenDeliveryOrderService;
|
||||
import com.bsd.pjcloud.canteen.api.feign.RemoteOnlineCanteenSpuService;
|
||||
import com.bsd.pjcloud.canteen.dto.CanteenSpuUpdateStockReqDTO;
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryCartItemDTO;
|
||||
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryOrderSyncDTO;
|
||||
import com.bsd.pjcloud.common.core.constant.CacheConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.CommonConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.CommonRoleConstant;
|
||||
@@ -30,6 +33,7 @@ import com.bsd.pjcloud.common.core.util.CollectionUtils;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.hard.api.constant.HardProductNatureEnum;
|
||||
import com.bsd.pjcloud.hard.api.constant.HardThirdMiStatusEnum;
|
||||
import com.bsd.pjcloud.hard.api.constant.HardTypeEnum;
|
||||
@@ -163,6 +167,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final HardThirdMiOrderMapper hardThirdMiOrderMapper;
|
||||
private final RemoteCanteenDeliveryOrderService remoteCanteenDeliveryOrderService;
|
||||
|
||||
@Override
|
||||
public Page<HardOrderVo> pagejoin(Page page, HardOrderDto dto) {
|
||||
@@ -680,8 +685,24 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
addressEntity.setFloorNo(dto.getAddress().getFloorNo());
|
||||
addressEntity.setRoomId(dto.getAddress().getRoomId());
|
||||
addressEntity.setRoomName(dto.getAddress().getRoomName());
|
||||
addressEntity.setFullAddress(dto.getAddress().getFullAddress());
|
||||
hardOrderAddressService.save(addressEntity);
|
||||
}
|
||||
//调用外卖下单
|
||||
CanteenDeliveryOrderSyncDTO canteenDeliveryOrderSyncDTO = new CanteenDeliveryOrderSyncDTO();
|
||||
canteenDeliveryOrderSyncDTO.setBuyerUserId(dto.getUserId());
|
||||
canteenDeliveryOrderSyncDTO.setMerchantId(String.valueOf(hardManager.getId()));
|
||||
canteenDeliveryOrderSyncDTO.setMerchantName(hardManager.getName());
|
||||
canteenDeliveryOrderSyncDTO.setLogoPic(hardManager.getLogoPic());
|
||||
canteenDeliveryOrderSyncDTO.setFullAddress(dto.getAddress().getFullAddress());
|
||||
List<CanteenDeliveryCartItemDTO> orderCarList = BeanUtils.toBean(carList, CanteenDeliveryCartItemDTO.class);
|
||||
canteenDeliveryOrderSyncDTO.setCarList(orderCarList);
|
||||
canteenDeliveryOrderSyncDTO.setDeptId(SecurityUtils.getUser().getDeptId());
|
||||
canteenDeliveryOrderSyncDTO.setId(String.valueOf(orderObj.getId()));
|
||||
canteenDeliveryOrderSyncDTO.setFullAddress(dto.getAddress().getFullAddress());
|
||||
canteenDeliveryOrderSyncDTO.setBuildingId(dto.getAddress().getBuildingId());
|
||||
remoteCanteenDeliveryOrderService.syncFromOrder(canteenDeliveryOrderSyncDTO);
|
||||
|
||||
return orderObj.getId();
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -157,6 +157,13 @@ public class SysUser extends Model<SysUser> implements Serializable {
|
||||
@Schema(description = "手机号")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 性别:0未知 1男 2女(骑手派单等)
|
||||
*/
|
||||
@Schema(description = "性别:0未知 1男 2女")
|
||||
@TableField("gender")
|
||||
private Integer gender;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
|
||||
@@ -164,6 +164,12 @@ public class UserVO implements BaseFacesInterface, Serializable {
|
||||
@Schema(description = "手机号")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 性别:0未知 1男 2女
|
||||
*/
|
||||
@Schema(description = "性别:0未知 1男 2女")
|
||||
private Integer gender;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
|
||||
@@ -33,6 +33,11 @@
|
||||
<description>pjcloud 通用用户权限管理系统业务处理模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!--websocket 支持-->
|
||||
<dependency>
|
||||
<groupId>com.bsd</groupId>
|
||||
<artifactId>pjcloud-common-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
|
||||
+39
-1
@@ -74,6 +74,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
@@ -569,7 +570,7 @@ public class SysUserController {
|
||||
public R<List<UserSimpleVo>> getUserListByRoleCode(@RequestParam("code") String code) {
|
||||
SysRole sysRole = sysRoleService.getOne(Wrappers.<SysRole>lambdaQuery().eq(SysRole::getRoleCode, code));
|
||||
if (sysRole == null) {
|
||||
return R.failed("不存在共富商城商户角色的用户!");
|
||||
return R.failed("不存在角色用户!");
|
||||
}
|
||||
List<SysUserRole> list = sysUserRoleService.list(Wrappers.<SysUserRole>lambdaQuery().eq(SysUserRole::getRoleId, sysRole.getRoleId()));
|
||||
List<Long> userIds = list.stream().map(sysUserRole -> sysUserRole.getUserId()).collect(Collectors.toList());
|
||||
@@ -585,4 +586,41 @@ public class SysUserController {
|
||||
}).collect(Collectors.toList());
|
||||
return R.ok(userVOList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色编码与单位部门(含其子部门)获取用户列表,用于下拉等场景
|
||||
*/
|
||||
@Operation(summary = "根据角色编码与单位部门查询用户简要列表", description = "仅返回 deptId 为该单位或其下级部门内的用户,返回字段同 getUserListByRoleCode")
|
||||
@GetMapping("/getUserListByRoleCodeAndDept")
|
||||
public R<List<UserSimpleVo>> getUserListByRoleCodeAndDept(@RequestParam("code") String code,
|
||||
@RequestParam("deptId") Long deptId) {
|
||||
if (deptId == null) {
|
||||
return R.failed("deptId不能为空");
|
||||
}
|
||||
SysRole sysRole = sysRoleService.getOne(Wrappers.<SysRole>lambdaQuery().eq(SysRole::getRoleCode, code));
|
||||
if (sysRole == null) {
|
||||
return R.failed("不存在该角色!");
|
||||
}
|
||||
List<SysUserRole> roleRows = sysUserRoleService.list(Wrappers.<SysUserRole>lambdaQuery()
|
||||
.eq(SysUserRole::getRoleId, sysRole.getRoleId()));
|
||||
List<Long> userIds = roleRows.stream().map(SysUserRole::getUserId).collect(Collectors.toList());
|
||||
if (CollUtil.isEmpty(userIds)) {
|
||||
return R.ok(new ArrayList<>());
|
||||
}
|
||||
Set<Long> scopeDeptIds = sysDeptService.listDescendant(deptId).stream()
|
||||
.map(SysDept::getDeptId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
List<SysUser> listData = userService.list(Wrappers.<SysUser>lambdaQuery().in(SysUser::getUserId, userIds));
|
||||
List<UserSimpleVo> userVOList = listData.stream()
|
||||
.filter(u -> u.getDeptId() != null && scopeDeptIds.contains(u.getDeptId()))
|
||||
.map(sysUser -> {
|
||||
UserSimpleVo userVO = new UserSimpleVo();
|
||||
userVO.setUserId(sysUser.getUserId());
|
||||
userVO.setPhone(sysUser.getPhone());
|
||||
userVO.setName(sysUser.getName());
|
||||
return userVO;
|
||||
}).collect(Collectors.toList());
|
||||
return R.ok(userVOList);
|
||||
}
|
||||
}
|
||||
+12
@@ -1,9 +1,21 @@
|
||||
package com.bsd.pjcloud.admin.mapper.stats;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import com.bsd.pjcloud.admin.api.entity.stats.AppAccessAggDayEntity;
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Mapper
|
||||
public interface AppAccessAggDayMapper extends PjcloudBaseMapper<AppAccessAggDayEntity> {
|
||||
|
||||
/**
|
||||
* 物理删除该统计日下所有维度行(同分钟聚合,避免逻辑删除导致 uk_day_dim 重复)。
|
||||
*/
|
||||
@InterceptorIgnore(tenantLine = "true")
|
||||
@Delete("DELETE FROM app_access_agg_day WHERE stat_date = #{statDate}")
|
||||
int deletePhysicallyByStatDate(@Param("statDate") LocalDate statDate);
|
||||
}
|
||||
+12
@@ -1,9 +1,21 @@
|
||||
package com.bsd.pjcloud.admin.mapper.stats;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import com.bsd.pjcloud.admin.api.entity.stats.AppAccessAggMinuteEntity;
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Mapper
|
||||
public interface AppAccessAggMinuteMapper extends PjcloudBaseMapper<AppAccessAggMinuteEntity> {
|
||||
|
||||
/**
|
||||
* 物理删除该分钟桶下所有维度行。聚合任务不可用 {@link #delete},否则逻辑删除后唯一键仍冲突。
|
||||
*/
|
||||
@InterceptorIgnore(tenantLine = "true")
|
||||
@Delete("DELETE FROM app_access_agg_minute WHERE bucket_time = #{bucketTime}")
|
||||
int deletePhysicallyByBucketTime(@Param("bucketTime") LocalDateTime bucketTime);
|
||||
}
|
||||
+2
-4
@@ -285,8 +285,7 @@ public class AppAccessStatsServiceImpl implements AppAccessStatsService {
|
||||
queryWrapper.groupBy("tenant_id", "unit_id", "dept_id", "page_code");
|
||||
List<Map<String, Object>> rows = appAccessEventMapper.selectMaps(queryWrapper);
|
||||
|
||||
appAccessAggMinuteMapper.delete(new LambdaQueryWrapper<AppAccessAggMinuteEntity>()
|
||||
.eq(AppAccessAggMinuteEntity::getBucketTime, start));
|
||||
appAccessAggMinuteMapper.deletePhysicallyByBucketTime(start);
|
||||
if (CollUtil.isEmpty(rows)) {
|
||||
return;
|
||||
}
|
||||
@@ -336,8 +335,7 @@ public class AppAccessStatsServiceImpl implements AppAccessStatsService {
|
||||
queryWrapper.groupBy("tenant_id", "unit_id", "dept_id", "page_code");
|
||||
List<Map<String, Object>> rows = appAccessEventMapper.selectMaps(queryWrapper);
|
||||
|
||||
appAccessAggDayMapper.delete(new LambdaQueryWrapper<AppAccessAggDayEntity>()
|
||||
.eq(AppAccessAggDayEntity::getStatDate, date));
|
||||
appAccessAggDayMapper.deletePhysicallyByStatDate(date);
|
||||
for (Map<String, Object> row : rows) {
|
||||
AppAccessAggDayEntity entity = new AppAccessAggDayEntity();
|
||||
entity.setStatDate(date);
|
||||
|
||||
@@ -23,4 +23,14 @@ qn:
|
||||
accessKey: IVrAjjdrTeZi14pqDd2s5kb_iAIGXATEMtOgmQUF
|
||||
secretKey: SNhPscR3_xqX3E_B1XtMMWmLMrz7ipcguzNykQvo
|
||||
bucketName: bizcabinet
|
||||
domain: https://bizcabinet.zianxin.com
|
||||
domain: https://bizcabinet.zianxin.com
|
||||
|
||||
pjcloud:
|
||||
websocket:
|
||||
path: /ws/info # WebSocket 端点路径 (默认: /ws/info)
|
||||
allow-origins: "*" # 允许的跨域源 (默认: *)
|
||||
heartbeat: true # 是否启用心跳 (默认: true)
|
||||
map-session: true # 是否启用 Session 映射 (默认: true)
|
||||
message-distributor: redis # 消息分发器类型: local | redis (默认: local)
|
||||
send-time-limit: 10000 # 发送时间限制,单位毫秒 (默认: 10000)
|
||||
send-buffer-size-limit: 64000 # 发送缓冲区大小限制 (默认: 64000)
|
||||
@@ -30,6 +30,7 @@
|
||||
<result column="password" property="password"/>
|
||||
<result column="salt" property="salt"/>
|
||||
<result column="phone" property="phone"/>
|
||||
<result column="gender" property="gender"/>
|
||||
<result column="avatar" property="avatar"/>
|
||||
<result column="wx_openid" property="wxOpenid"/>
|
||||
<result column="qq_openid" property="qqOpenid"/>
|
||||
@@ -69,6 +70,7 @@
|
||||
u.password,
|
||||
u.salt,
|
||||
u.phone,
|
||||
u.gender,
|
||||
u.avatar,
|
||||
u.wx_openid,
|
||||
u.qq_openid,
|
||||
@@ -102,6 +104,7 @@
|
||||
u.password,
|
||||
u.salt,
|
||||
u.phone,
|
||||
u.gender,
|
||||
u.avatar,
|
||||
u.wx_openid,
|
||||
u.qq_openid,
|
||||
@@ -157,6 +160,7 @@
|
||||
u.password,
|
||||
u.salt,
|
||||
u.phone,
|
||||
u.gender,
|
||||
u.avatar,
|
||||
u.wx_openid,
|
||||
u.qq_openid,
|
||||
|
||||
Reference in new issue
Block a user