Browse Source

对接拼团数据1

master
wangfukang 17 hours ago
parent
commit
d357906f3b
  1. 7
      hiver-admin/src/main/resources/application.yml
  2. 16
      hiver-admin/test-output/test-report.html
  3. 4
      hiver-core/src/main/java/cc/hiver/core/dao/WorkerDao.java
  4. 4
      hiver-core/src/main/java/cc/hiver/core/entity/Worker.java
  5. 2
      hiver-core/src/main/java/cc/hiver/core/service/WorkerService.java
  6. 1
      hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java
  7. 5
      hiver-core/src/main/java/cc/hiver/core/serviceimpl/WorkerServiceImpl.java
  8. 67
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallDeliveryOrderController.java
  9. 22
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallOrderController.java
  10. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/OrderController.java
  11. 79
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopAreaController.java
  12. 366
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java
  13. 7
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopTakeawayController.java
  14. 37
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/UserAddressController.java
  15. 37
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java
  16. 10
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallDeliveryOrderMapper.java
  17. 6
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallDeliveryOrder.java
  18. 9
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/Shop.java
  19. 10
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopArea.java
  20. 11
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopTakeaway.java
  21. 8
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/UserAddress.java
  22. 31
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncProducer.java
  23. 50
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderCloudPrintConsumer.java
  24. 28
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderQueueConfig.java
  25. 22
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/BusinessHourPeriod.java
  26. 9
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/query/MallDeliveryOrderPageQuery.java
  27. 4
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/ShopAreaService.java
  28. 10
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallDeliveryOrderService.java
  29. 108
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopAreaServiceImpl.java
  30. 117
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopServiceImpl.java
  31. 59
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopTakeawayServiceImpl.java
  32. 58
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java
  33. 1
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java
  34. 39
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java
  35. 58
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java
  36. 106
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/DaQuCloudPrintService.java
  37. 521
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/DaQuPrinterUtil.java
  38. 313
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/DaQuReceiptRenderUtil.java
  39. 218
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/ShopBusinessHourUtil.java
  40. 2
      hiver-modules/hiver-mall/src/main/resources/db/shop_area_geo_polygon.sql
  41. 4
      hiver-modules/hiver-mall/src/main/resources/db/shop_cloud_print.sql
  42. 2
      hiver-modules/hiver-mall/src/main/resources/db/shop_starting_price.sql
  43. 16
      hiver-modules/hiver-mall/src/main/resources/db/shop_takeaway_business_hours.sql
  44. 8
      hiver-modules/hiver-mall/src/main/resources/db/transfer_worker_delivery.sql
  45. 3
      hiver-modules/hiver-mall/src/main/resources/db/user_address_geo.sql
  46. 130
      hiver-modules/hiver-mall/src/main/resources/mapper/MallDeliveryOrderMapper.xml
  47. 65
      hiver-modules/hiver-mall/src/main/resources/mapper/ShopAreaMapper.xml
  48. 10
      hiver-modules/hiver-mall/src/main/resources/mapper/ShopTakeawayMapper.xml
  49. 6
      hiver-modules/hiver-mall/src/main/resources/mapper/UserAddressMapper.xml

7
hiver-admin/src/main/resources/application.yml

@ -3,6 +3,12 @@ jasypt:
encryptor:
password: hiver
daqu:
printer:
appid: 1550073157890342912
appSecret: cxtFP8nYWx7VC48ov9V5QJ1HpGenWu4a
apiBaseUrl: https://api.trenditiot.com
ie:
aliyun:
green:
@ -537,6 +543,7 @@ logging:
jpush:
appKey: 130f556e8473c9b558777fe3
masterSecret: 2b4e5196dfc40a78db36480d
apnsProduction: true
aliyun:
openapi:
accessKeyId: 56640

16
hiver-admin/test-output/test-report.html

@ -35,7 +35,7 @@
<a href="#"><span class="badge badge-primary">Hiver</span></a>
</li>
<li class="m-r-10">
<a href="#"><span class="badge badge-primary">九月 09, 2026 10:15:14</span></a>
<a href="#"><span class="badge badge-primary">九月 19, 2026 15:49:28</span></a>
</li>
</ul>
</div>
@ -84,7 +84,7 @@
<div class="test-detail">
<span class="meta text-white badge badge-sm"></span>
<p class="name">passTest</p>
<p class="text-sm"><span>10:15:14 上</span> / <span>0.021 secs</span></p>
<p class="text-sm"><span>15:49:29 下</span> / <span>0.022 secs</span></p>
</div>
<div class="test-contents d-none">
<div class="detail-head">
@ -92,9 +92,9 @@
<div class="info">
<div class='float-right'><span class='badge badge-default'>#test-id=1</span></div>
<h5 class="test-status text-pass">passTest</h5>
<span class='badge badge-success'>09.09.2026 10:15:14</span>
<span class='badge badge-danger'>09.09.2026 10:15:14</span>
<span class='badge badge-default'>0.021 secs</span>
<span class='badge badge-success'>09.19.2026 15:49:29</span>
<span class='badge badge-danger'>09.19.2026 15:49:29</span>
<span class='badge badge-default'>0.022 secs</span>
</div>
<div class="m-t-10 m-l-5"></div>
</div>
@ -104,7 +104,7 @@
<tbody>
<tr class="event-row">
<td><span class="badge log pass-bg">Pass</span></td>
<td>10:15:14</td>
<td>15:49:29</td>
<td>
Test passed
</td>
@ -128,13 +128,13 @@
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Started</p>
<h3>九月 09, 2026 10:15:14</h3>
<h3>九月 19, 2026 15:49:28</h3>
</div></div>
</div>
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Ended</p>
<h3>九月 09, 2026 10:15:14</h3>
<h3>九月 19, 2026 15:49:29</h3>
</div></div>
</div>
<div class="col-md-3">

4
hiver-core/src/main/java/cc/hiver/core/dao/WorkerDao.java

@ -27,6 +27,10 @@ public interface WorkerDao extends HiverBaseDao<Worker, String> {
@Query("update Worker w set w.isFullTime=?2 where w.workerId=?1")
void changeFullTime(String id, Integer isFullTime);
@Modifying
@Query("update Worker w set w.canTransferOrder=?2 where w.workerId=?1")
void changeCanTransferOrder(String id, Integer canTransferOrder);
@Modifying
@Query("update Worker w set w.workerStatus=?2 where w.workerId = ?1")
void delById(String id, Integer workerStatusDisable);

4
hiver-core/src/main/java/cc/hiver/core/entity/Worker.java

@ -203,6 +203,10 @@ public class Worker implements Serializable {
@Column(name = "is_full_time")
private Integer isFullTime = 0;
@ApiModelProperty(value = "是否可以接中转单:0否 1是")
@Column(name = "can_transfer_order")
private Integer canTransferOrder = 0;
@ApiModelProperty(value = "评分")
@Column(name = "score")
private BigDecimal score;

2
hiver-core/src/main/java/cc/hiver/core/service/WorkerService.java

@ -56,6 +56,8 @@ public interface WorkerService extends HiverBaseService<Worker, String> {
void changeFullTime(String id, Integer isFullTime);
void changeCanTransferOrder(String id, Integer canTransferOrder);
/**
* 删除修改为禁用状态
*

1
hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java

@ -83,6 +83,7 @@ public class JPushServiceImpl implements JPushService {
.setApnsProduction(Boolean.TRUE.equals(apnsProduction))
.build())
.build();
System.out.println("JPush send target: " + registrationId + ", apnsProduction: " + apnsProduction + ", orderId: " + orderId);
PushResult result = jPushClient.sendPush(payload);
System.out.println("Push result: " + result);
} catch (Exception e) {

5
hiver-core/src/main/java/cc/hiver/core/serviceimpl/WorkerServiceImpl.java

@ -253,6 +253,11 @@ public class WorkerServiceImpl implements WorkerService {
workerDao.changeFullTime(id, isFullTime);
}
@Override
public void changeCanTransferOrder(String id, Integer canTransferOrder) {
workerDao.changeCanTransferOrder(id, canTransferOrder);
}
/**
* 删除修改为禁用状态
*

67
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallDeliveryOrderController.java

@ -257,10 +257,65 @@ public class MallDeliveryOrderController {
q.setTransferDelivery(1);
q.setDeliveryType(1);
q.setOrder("transferDelivery");
if (Boolean.TRUE.equals(q.getTransferWorkerView())) {
q.setShopId(null);
q.setStatus(1);
q.setTransferArriveEmpty(true);
}
IPage<MallDeliveryOrder> page = mallDeliveryOrderService.pageDelivery(q);
return new ResultUtil<Object>().setData(page);
}
@PostMapping("/transferAccept")
@ApiOperation(value = "配送员接中转配送单")
public Result<Object> transferAccept(@RequestParam String deliveryId,
@RequestParam String workerId,
@RequestParam(required = false) String workerName,
@RequestParam(required = false) String workerMobile) {
Worker worker = workerService.findByWorkerId(workerId);
if (worker == null) {
return ResultUtil.error("配送员不存在");
}
if (!Integer.valueOf(1).equals(worker.getCanTransferOrder())) {
return ResultUtil.error("当前配送员未开启中转接单");
}
MallDeliveryOrder delivery = mallDeliveryOrderService.getById(deliveryId);
if (delivery == null) {
return ResultUtil.error("配送单不存在");
}
if (!Integer.valueOf(1).equals(delivery.getTransferDelivery())) {
return ResultUtil.error("非中转配送单");
}
if (!Integer.valueOf(1).equals(delivery.getStatus())) {
return ResultUtil.error("当前中转单不可接单");
}
if (delivery.getTransferArriveTime() != null) {
return ResultUtil.error("该中转单已送达中转点");
}
if (StrUtil.isNotBlank(delivery.getTransferWorkerId()) && !workerId.equals(delivery.getTransferWorkerId())) {
return ResultUtil.error("该中转单已被接单");
}
if (StrUtil.isBlank(workerName)) {
workerName = worker.getWorkerName();
}
if (StrUtil.isBlank(workerMobile)) {
workerMobile = worker.getMobile();
}
LambdaUpdateWrapper<MallDeliveryOrder> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(MallDeliveryOrder::getId, deliveryId)
.eq(MallDeliveryOrder::getTransferDelivery, 1)
.eq(MallDeliveryOrder::getStatus, 1)
.isNull(MallDeliveryOrder::getTransferArriveTime)
.and(w -> w.isNull(MallDeliveryOrder::getTransferWorkerId).or().eq(MallDeliveryOrder::getTransferWorkerId, ""))
.set(MallDeliveryOrder::getTransferWorkerId, workerId)
.set(MallDeliveryOrder::getTransferWorkerName, workerName)
.set(MallDeliveryOrder::getTransferWorkerMobile, workerMobile);
if (!mallDeliveryOrderService.update(updateWrapper)) {
return ResultUtil.error("该中转单已被接单");
}
return ResultUtil.success("接单成功");
}
@PostMapping("/transferArrive")
@ApiOperation(value = "商家送达中转点")
public Result<Object> transferArrive(@RequestParam String deliveryId,
@ -581,4 +636,16 @@ public class MallDeliveryOrderController {
result.put("data", mallDeliveryOrderService.countOrdersLimit(regionId));
return new ResultUtil<Map<String, Object>>().setData(result);
}
@PostMapping("/countOrdersPage")
@ApiOperation(value = "后台分页查询配送员配送单量排名", notes = "支持日期范围,返回全部排名分页数据")
public Result<Object> countOrdersPage(@RequestBody MallDeliveryOrderPageQuery q) {
return new ResultUtil<Object>().setData(mallDeliveryOrderService.countOrdersPage(q));
}
@PostMapping("/countTransferOrdersPage")
@ApiOperation(value = "后台分页查询中转配送员配送单量排名", notes = "支持日期范围,返回全部排名分页数据")
public Result<Object> countTransferOrdersPage(@RequestBody MallDeliveryOrderPageQuery q) {
return new ResultUtil<Object>().setData(mallDeliveryOrderService.countTransferOrdersPage(q));
}
}

22
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallOrderController.java

@ -5,12 +5,14 @@ import cc.hiver.core.common.vo.Result;
import cc.hiver.mall.entity.MallOrder;
import cc.hiver.mall.entity.MallOrderGroup;
import cc.hiver.mall.entity.MallSettlementRecord;
import cc.hiver.mall.mq.OrderAsyncProducer;
import cc.hiver.mall.pojo.dto.CreateOrderDTO;
import cc.hiver.mall.pojo.query.MallOrderPageQuery;
import cc.hiver.mall.pojo.vo.MallOrderVO;
import cc.hiver.mall.service.mybatis.MallOrderGroupService;
import cc.hiver.mall.service.mybatis.MallOrderService;
import cc.hiver.mall.service.mybatis.MallSettlementRecordService;
import cc.hiver.mall.utils.DaQuCloudPrintService;
import cc.hiver.mall.utils.ShopGroupOrderCacheUtil;
import cc.hiver.mall.utils.UserPendingOrderCacheUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -48,6 +50,12 @@ public class MallOrderController {
@Autowired
private MallSettlementRecordService mallSettlementRecordService;
@Autowired
private OrderAsyncProducer orderAsyncProducer;
@Autowired
private DaQuCloudPrintService daQuCloudPrintService;
/**
* 下单普通购买 / 发起拼团 / 参团
*/
@ -155,6 +163,20 @@ public class MallOrderController {
return new ResultUtil<MallOrderVO>().setData(vo);
}
@PostMapping("/cloudPrint/{id}")
@ApiOperation("手动云打印订单小票")
public Result<Object> cloudPrint(@ApiParam("订单ID") @PathVariable String id) {
MallOrderVO vo = mallOrderService.getOrderDetail(id);
if (vo == null) {
return ResultUtil.error("订单不存在");
}
if (!daQuCloudPrintService.isCloudPrintShop(vo.getShopId())) {
return ResultUtil.error("当前商家未配置云打印");
}
orderAsyncProducer.sendCloudPrint(id, "MANUAL-" + System.currentTimeMillis());
return ResultUtil.success("云打印任务已发送");
}
/**
* 根据商家结算流水关联订单
*/

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/OrderController.java

@ -471,7 +471,7 @@ public class OrderController {
//获取AccessToken地址
String wechatQrcodeAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
wechatQrcodeAccessTokenUrl = wechatQrcodeAccessTokenUrl.replace("APPID", "wx6d7d4fb58d1502db").replace("APPSECRET", "3a2c8cd00926764bbd12fd7361e01f1d");
wechatQrcodeAccessTokenUrl = wechatQrcodeAccessTokenUrl.replace("APPID", "wx21a1caccef085db7").replace("APPSECRET", "dd1642569f7484664d62ff0fd811bc54");
try {
final URL url = new URL(wechatQrcodeAccessTokenUrl);

79
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopAreaController.java

@ -11,6 +11,8 @@ import cc.hiver.mall.entity.ShopArea;
import cc.hiver.mall.service.ShopAreaService;
import cc.hiver.mall.service.ShopService;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@ -307,6 +309,7 @@ public class ShopAreaController {
shopArea.setCenterLng(null);
shopArea.setCenterLat(null);
shopArea.setRadiusMeters(null);
shopArea.setGeoPolygon(null);
shopArea.setAreaKind(1);
return null;
}
@ -322,17 +325,75 @@ public class ShopAreaController {
} else if (commissionRatio < 0 || commissionRatio > 100) {
return ResultUtil.error("返佣比例需为0或1-100之间的整数");
}
boolean hasLng = shopArea.getCenterLng() != null;
boolean hasLat = shopArea.getCenterLat() != null;
boolean hasRadius = shopArea.getRadiusMeters() != null;
if (hasLng || hasLat || hasRadius) {
if (!hasLng || !hasLat) {
return ResultUtil.error("区域中心经纬度需同时设置");
shopArea.setCenterLng(null);
shopArea.setCenterLat(null);
shopArea.setRadiusMeters(null);
String polygon = normalizeGeoPolygon(shopArea.getGeoPolygon());
if (polygon == null && StrUtil.isNotBlank(shopArea.getGeoPolygon())) {
return ResultUtil.error("匹配范围至少需要3个合法坐标点");
}
shopArea.setGeoPolygon(polygon);
return null;
}
private String normalizeGeoPolygon(String geoPolygon) {
if (StrUtil.isBlank(geoPolygon)) {
return null;
}
try {
JSONArray source = JSONUtil.parseArray(geoPolygon);
if (source.size() < 3) {
return null;
}
if (!hasRadius || shopArea.getRadiusMeters() <= 0) {
return ResultUtil.error("匹配半径需为正整数(米)");
JSONArray normalized = new JSONArray();
for (Object item : source) {
Double lng = null;
Double lat = null;
if (item instanceof JSONArray) {
JSONArray point = (JSONArray) item;
if (point.size() >= 2) {
lng = point.getDouble(0);
lat = point.getDouble(1);
}
} else if (item instanceof java.util.List) {
java.util.List<?> point = (java.util.List<?>) item;
if (point.size() >= 2) {
lng = toDouble(point.get(0));
lat = toDouble(point.get(1));
}
} else if (item instanceof java.util.Map) {
java.util.Map<?, ?> point = (java.util.Map<?, ?>) item;
lng = toDouble(point.get("lng"));
lat = toDouble(point.get("lat"));
}
if (!isValidLngLat(lng, lat)) {
return null;
}
JSONArray point = new JSONArray();
point.add(lng);
point.add(lat);
normalized.add(point);
}
return normalized.toString();
} catch (Exception e) {
return null;
}
return null;
}
private Double toDouble(Object value) {
if (value == null) {
return null;
}
try {
return Double.valueOf(String.valueOf(value));
} catch (Exception e) {
return null;
}
}
private boolean isValidLngLat(Double lng, Double lat) {
return lng != null && lat != null
&& lng >= -180 && lng <= 180
&& lat >= -90 && lat <= 90;
}
}

366
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java

@ -44,6 +44,8 @@ import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.ShopTakeawayService;
import cc.hiver.mall.service.ShopUserService;
import cc.hiver.mall.service.mybatis.BaseDeliveryFeeSettingService;
import cc.hiver.mall.utils.DaQuPrinterUtil;
import cc.hiver.mall.utils.ShopBusinessHourUtil;
import cc.hiver.mall.utils.UpdateUtils;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONObject;
@ -57,6 +59,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
@ -75,8 +78,10 @@ import java.util.stream.Collectors;
@RequestMapping("/hiver/app/shop/")
public class ShopController {
private static final String GROUP_BUY_DEFAULT_PAGE_CACHE_PREFIX = "GROUP_BUY_DEFAULT_SHOP_PAGE:";
private static final long GROUP_BUY_DEFAULT_PAGE_CACHE_SECONDS = 5 * 60L;
private static final long GROUP_BUY_DEFAULT_PAGE_CACHE_SECONDS = 30L;
private static final DateTimeFormatter BUSINESS_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private static final String PRINTING_METHOD_BLUETOOTH = "bluetooth";
private static final String PRINTING_METHOD_CLOUD = "cloud";
@Autowired
private ShopService shopService;
@ -108,6 +113,9 @@ public class ShopController {
@Autowired
private BaseDeliveryFeeSettingService baseDeliveryFeeSettingService;
@Autowired
private DaQuPrinterUtil daQuPrinterUtil;
@RequestMapping(value = "/getAll", method = RequestMethod.GET)
@ApiOperation("获取全部数据")
public Result<List<Shop>> getAll() {
@ -220,6 +228,143 @@ public class ShopController {
return ResultUtil.success("编辑成功");
}
@RequestMapping(value = "/bindPrinter", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("绑定大趋云打印机")
public Result<Object> bindPrinter(DaQuPrinterBindRequest request) {
if (request == null) {
return ResultUtil.error("参数不能为空");
}
String sn = request.getSn() == null ? "" : request.getSn().trim();
String key = request.getKey() == null ? "" : request.getKey().trim();
String shopId = request.getShopId() == null ? "" : request.getShopId().trim();
if (CharSequenceUtil.isBlank(shopId)) {
return ResultUtil.error("店铺id不能为空,请先保存店铺后再绑定打印机");
}
Shop shop = shopService.get(shopId);
if (shop == null) {
return ResultUtil.error("店铺不存在");
}
if (CharSequenceUtil.isBlank(sn)) {
return ResultUtil.error("请输入打印机SN");
}
if (CharSequenceUtil.isBlank(key)) {
return ResultUtil.error("请输入设备绑定码");
}
String name = request.getName();
if (CharSequenceUtil.isBlank(name)) {
name = request.getShopName();
}
if (CharSequenceUtil.isBlank(name)) {
name = shop.getShopName();
}
try {
DaQuPrinterUtil.DaQuResponse response = daQuPrinterUtil.addPrinter(sn, key, name);
if (response != null && (response.isSuccess() || Integer.valueOf(31412).equals(response.getCode()))) {
shop.setPrinterSn(sn);
shop.setPrinterName(name);
shopService.update(shop);
shopService.refreshShopCache(shop.getId(), shop.getRegionId());
String successMessage = response.isSuccess() ? "打印机绑定成功" : "打印机已绑定,已关联到当前商家";
return new ResultUtil<Object>().setData(shop, successMessage);
}
String message = response == null || CharSequenceUtil.isBlank(response.getMessage())
? "打印机绑定失败"
: response.getMessage();
return ResultUtil.error(message);
} catch (Exception e) {
log.error("Bind DaQu printer failed. sn={}", sn, e);
return ResultUtil.error("打印机绑定失败:" + e.getMessage());
}
}
@RequestMapping(value = "/printConfig", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("查询店铺打印配置")
public Result<Object> getPrintConfig(@RequestParam String shopId) {
if (CharSequenceUtil.isBlank(shopId)) {
return ResultUtil.error("店铺id不能为空");
}
Shop shop = shopService.get(shopId);
if (shop == null) {
return ResultUtil.error("店铺不存在");
}
return new ResultUtil<Object>().setData(buildPrintConfig(shop));
}
@RequestMapping(value = "/printConfig", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("保存店铺打印配置")
public Result<Object> savePrintConfig(PrintConfigRequest request) {
if (request == null || CharSequenceUtil.isBlank(request.getShopId())) {
return ResultUtil.error("店铺id不能为空");
}
Shop shop = shopService.get(request.getShopId());
if (shop == null) {
return ResultUtil.error("店铺不存在");
}
String printingMethod = normalizePrintingMethod(request.getPrintingMethod());
if (PRINTING_METHOD_CLOUD.equals(printingMethod) && CharSequenceUtil.isBlank(shop.getPrinterSn())) {
return ResultUtil.error("请先绑定云打印机");
}
shop.setPrintingMethod(printingMethod);
shopService.update(shop);
shopService.refreshShopCache(shop.getId(), shop.getRegionId());
return new ResultUtil<Object>().setData(buildPrintConfig(shop), "保存成功");
}
@RequestMapping(value = "/printTest", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("云打印测试")
public Result<Object> printTest(PrintConfigRequest request) {
if (request == null || CharSequenceUtil.isBlank(request.getShopId())) {
return ResultUtil.error("店铺id不能为空");
}
Shop shop = shopService.get(request.getShopId());
if (shop == null) {
return ResultUtil.error("店铺不存在");
}
if (CharSequenceUtil.isBlank(shop.getPrinterSn())) {
return ResultUtil.error("请先绑定云打印机");
}
try {
DaQuPrinterUtil.PrintRequest printRequest = new DaQuPrinterUtil.PrintRequest();
printRequest.setSn(shop.getPrinterSn());
printRequest.setVoice("");
printRequest.setContent("<C><FONT bolder=1 height=2 width=2>Cloud Print Test</FONT></C><BR>"
+ "<LEFT>Shop: " + shop.getShopName() + "</LEFT><BR>"
+ "<LEFT>SN: " + shop.getPrinterSn() + "</LEFT><BR>"
+ "<C>--------------------------------</C><BR>");
printRequest.setCopies(1);
printRequest.setExpiresInSeconds(600);
printRequest.setOutTradeNo("SHOP-TEST-" + shop.getId() + "-" + System.currentTimeMillis());
DaQuPrinterUtil.DaQuResponse response = daQuPrinterUtil.print(printRequest);
if (response != null && response.isSuccess()) {
return new ResultUtil<Object>().setData(response.getData(), "测试打印已发送");
}
return ResultUtil.error(response == null ? "测试打印失败" : response.getMessage());
} catch (Exception e) {
log.error("Cloud printer test failed. shopId={}", shop.getId(), e);
return ResultUtil.error("测试打印失败:" + e.getMessage());
}
}
private String normalizePrintingMethod(String printingMethod) {
if (PRINTING_METHOD_CLOUD.equalsIgnoreCase(printingMethod)) {
return PRINTING_METHOD_CLOUD;
}
return PRINTING_METHOD_BLUETOOTH;
}
private PrintConfigResponse buildPrintConfig(Shop shop) {
PrintConfigResponse response = new PrintConfigResponse();
response.setShopId(shop.getId());
response.setPrintingMethod(normalizePrintingMethod(shop.getPrintingMethod()));
response.setPrinterSn(shop.getPrinterSn());
response.setPrinterName(shop.getPrinterName());
return response;
}
@RequestMapping(value = "/modifyShopNameById", method = RequestMethod.PUT)
@ResponseBody
@ApiOperation("根据店铺id修改店铺名")
@ -316,6 +461,10 @@ public class ShopController {
long queryStartTime = System.currentTimeMillis();
String regionId = shop.getRegionId();
boolean adminQuery = CharSequenceUtil.isNotBlank(shop.getShopIcon());
boolean hasKeyword = hasSearchKeyword(shop);
boolean useRegionCache = CharSequenceUtil.isNotBlank(regionId)
&& !hasKeyword
&& CharSequenceUtil.isBlank(shop.getShopName());
String defaultPageCacheKey = buildDefaultGroupBuyPageCacheKey(shop, pageVo, adminQuery);
if (CharSequenceUtil.isNotBlank(defaultPageCacheKey)) {
String cacheJson = redisTemplateHelper.get(defaultPageCacheKey);
@ -337,7 +486,7 @@ public class ShopController {
}
}
}
if (CharSequenceUtil.isNotBlank(regionId)) {
if (useRegionCache) {
List<Object> values = redisTemplateHelper.hValues("SHOP_CACHE:" + regionId);
if (values != null && !values.isEmpty()) {
List<Shop> allShops = new ArrayList<>();
@ -378,7 +527,7 @@ public class ShopController {
continue;
}
}
if (CharSequenceUtil.isNotBlank(shop.getKeyWord())) {
if (hasKeyword) {
String kw = shop.getKeyWord();
boolean match = false;
if (s.getShopName() != null && s.getShopName().contains(kw)) {
@ -460,7 +609,7 @@ public class ShopController {
}
// --- 核心优化:获取当前页的商品数据(二层缓存架构的核心拼装) ---
if (!pageList.isEmpty()) {
if (!adminQuery && !pageList.isEmpty()) {
List<String> productKeys = new ArrayList<>();
for (Shop s : pageList) {
productKeys.add("SHOP_PRODUCTS:" + s.getId());
@ -472,7 +621,7 @@ public class ShopController {
if (CharSequenceUtil.isNotBlank(productsJson)) {
List<ProductPageVO> productsReturn = new ArrayList<>();
List<ProductPageVO> products = JSONUtil.toList(productsJson, ProductPageVO.class);
if (CharSequenceUtil.isNotBlank(shop.getKeyWord())) {
if (hasKeyword) {
for (ProductPageVO p : products) {
if (p.getDelFlag() == 1 && p.getProductName() != null && p.getProductName().contains(shop.getKeyWord())) {
productsReturn.add(p);
@ -501,48 +650,125 @@ public class ShopController {
return new ResultUtil<Page<Shop>>().setData(pageResult);
}
}
final Page<Shop> page = shopService.findByCondition(shop, PageUtil.initPage(pageVo));
Page<Shop> page;
if (adminQuery) {
page = shopService.findByCondition(shop, PageUtil.initPage(pageVo));
fillShopTakeawayAndProducts(page.getContent(), shop, false);
} else {
// 用户端:全量查询 → 挂 takeaway → 按多时段营业状态排序 → 再分页(与缓存路径一致)
Page<Shop> allPage = shopService.findByCondition(shop, Pageable.unpaged());
List<Shop> allShops = new ArrayList<>(allPage.getContent());
fillShopTakeawayOnly(allShops);
applyUserShopListSort(allShops, pageVo);
String currentBusinessTime = LocalTime.now().format(BUSINESS_TIME_FORMATTER);
allShops.sort((s1, s2) -> compareUserShopPriority(s1, s2, currentBusinessTime));
int pageNumber = pageVo.getPageNumber() > 0 ? pageVo.getPageNumber() : 1;
int pageSize = pageVo.getPageSize() > 0 ? pageVo.getPageSize() : 10;
int fromIndex = (pageNumber - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, allShops.size());
List<Shop> pageList = fromIndex >= allShops.size()
? new ArrayList<>()
: new ArrayList<>(allShops.subList(fromIndex, toIndex));
fillShopProducts(pageList, shop);
fillGroupDeliveryTime(pageList, shop.getRegionId());
fillBaseDeliveryFee(pageList, shop.getRegionId());
page = new PageImpl<>(pageList, PageUtil.initPage(pageVo), allShops.size());
}
cacheDefaultGroupBuyPage(defaultPageCacheKey, page.getContent(), page.getTotalElements());
logSlowShopListQuery("db", queryStartTime, page.getTotalElements(), page.getContent().size(), shop.getRegionId());
return new ResultUtil<Page<Shop>>().setData(page);
}
final List<String> shopIdList = new ArrayList<>();
page.getContent().forEach(e -> {
shopIdList.add(e.getId());
});
if(shopIdList.size() > 0){
//查询业务信息
List<ShopTakeaway> shopTakeaways = shopTakeawayService.selectListByshopId(shopIdList);
//查询推荐商品
final ProductPageQuery productPageQuery = new ProductPageQuery();
productPageQuery.setPageNum(1);
productPageQuery.setPageSize(100);
productPageQuery.setShopIdList(shopIdList);
productPageQuery.setDelFlag(1);
if(CharSequenceUtil.isBlank(shop.getKeyWord())){
productPageQuery.setIsPush(ShopUserConstant.SHOP_ADMIN_COST[1]);
}else{
productPageQuery.setProductName(shop.getKeyWord());
private void applyUserShopListSort(List<Shop> allShops, PageVo pageVo) {
if (allShops == null || allShops.isEmpty() || pageVo == null) {
return;
}
String sort = pageVo.getSort();
String order = pageVo.getOrder();
if (CharSequenceUtil.isBlank(sort)) {
return;
}
allShops.sort((s1, s2) -> {
int compareResult = 0;
if ("saleCount".equals(sort)) {
Integer c1 = s1.getSaleCount() == null ? 0 : s1.getSaleCount();
Integer c2 = s2.getSaleCount() == null ? 0 : s2.getSaleCount();
compareResult = c1.compareTo(c2);
} else if ("shopScore".equals(sort)) {
BigDecimal score1 = s1.getShopScore() == null ? BigDecimal.ZERO : s1.getShopScore();
BigDecimal score2 = s2.getShopScore() == null ? BigDecimal.ZERO : s2.getShopScore();
compareResult = score1.compareTo(score2);
} else if ("shoprank".equals(sort)) {
Integer shoprank1 = s1.getShoprank() == null ? 0 : s1.getShoprank();
Integer shoprank2 = s2.getShoprank() == null ? 0 : s2.getShoprank();
compareResult = shoprank1.compareTo(shoprank2);
}
IPage<ProductPageVO> productList = shopService.getShareList(productPageQuery);
Map<String, List<ProductPageVO>> productsByShopId = new HashMap<>();
if(productList.getRecords().size() > 0){
productList.getRecords().forEach(productPageVO -> {
productsByShopId.computeIfAbsent(productPageVO.getShopId(), key -> new ArrayList<>()).add(productPageVO);
});
if ("desc".equalsIgnoreCase(order)) {
compareResult = -compareResult;
}
Map<String, ShopTakeaway> takeawayByShopId = new HashMap<>();
for(ShopTakeaway shopTakeawa : shopTakeaways) {
takeawayByShopId.put(shopTakeawa.getShopId(), shopTakeawa);
return compareResult;
});
}
private void fillShopTakeawayOnly(List<Shop> shops) {
if (shops == null || shops.isEmpty()) {
return;
}
List<String> shopIdList = shops.stream().map(Shop::getId).collect(Collectors.toList());
List<ShopTakeaway> shopTakeaways = shopTakeawayService.selectListByshopId(shopIdList);
Map<String, ShopTakeaway> takeawayByShopId = new HashMap<>();
if (shopTakeaways != null) {
for (ShopTakeaway takeaway : shopTakeaways) {
takeawayByShopId.put(takeaway.getShopId(), takeaway);
}
page.getContent().forEach(e -> {
e.setProducts(productsByShopId.getOrDefault(e.getId(), new ArrayList<>()));
e.setShopTakeaway(takeawayByShopId.get(e.getId()));
}
for (Shop shopItem : shops) {
shopItem.setShopTakeaway(takeawayByShopId.get(shopItem.getId()));
}
}
private void fillShopProducts(List<Shop> shops, Shop query) {
if (shops == null || shops.isEmpty()) {
return;
}
List<String> shopIdList = shops.stream().map(Shop::getId).collect(Collectors.toList());
final ProductPageQuery productPageQuery = new ProductPageQuery();
productPageQuery.setPageNum(1);
productPageQuery.setPageSize(100);
productPageQuery.setShopIdList(shopIdList);
productPageQuery.setDelFlag(1);
if (CharSequenceUtil.isBlank(query.getKeyWord())) {
productPageQuery.setIsPush(ShopUserConstant.SHOP_ADMIN_COST[1]);
} else {
productPageQuery.setProductName(query.getKeyWord());
}
IPage<ProductPageVO> productList = shopService.getShareList(productPageQuery);
Map<String, List<ProductPageVO>> productsByShopId = new HashMap<>();
if (productList != null && productList.getRecords() != null) {
productList.getRecords().forEach(productPageVO -> {
productsByShopId.computeIfAbsent(productPageVO.getShopId(), key -> new ArrayList<>()).add(productPageVO);
});
fillGroupDeliveryTime(page.getContent(), shop.getRegionId());
fillBaseDeliveryFee(page.getContent(), shop.getRegionId());
}
cacheDefaultGroupBuyPage(defaultPageCacheKey, page.getContent(), page.getTotalElements());
logSlowShopListQuery("db", queryStartTime, page.getTotalElements(), page.getContent().size(), shop.getRegionId());
return new ResultUtil<Page<Shop>>().setData(page);
for (Shop shopItem : shops) {
shopItem.setProducts(productsByShopId.getOrDefault(shopItem.getId(), new ArrayList<>()));
}
}
private void fillShopTakeawayAndProducts(List<Shop> shops, Shop query) {
fillShopTakeawayAndProducts(shops, query, true);
}
private void fillShopTakeawayAndProducts(List<Shop> shops, Shop query, boolean includeProducts) {
if (shops == null || shops.isEmpty()) {
return;
}
fillShopTakeawayOnly(shops);
if (includeProducts) {
fillShopProducts(shops, query);
}
fillGroupDeliveryTime(shops, query.getRegionId());
fillBaseDeliveryFee(shops, query.getRegionId());
}
private String buildDefaultGroupBuyPageCacheKey(Shop shop, PageVo pageVo, boolean adminQuery) {
@ -560,7 +786,7 @@ public class ShopController {
if (shop.getIsStudent() != null ||
CharSequenceUtil.isNotBlank(shop.getShopArea()) ||
CharSequenceUtil.isNotBlank(shop.getShopType()) ||
CharSequenceUtil.isNotBlank(shop.getKeyWord()) ||
hasSearchKeyword(shop) ||
CharSequenceUtil.isNotBlank(shop.getShopName())) {
return null;
}
@ -578,6 +804,12 @@ public class ShopController {
return dto;
}
private boolean hasSearchKeyword(Shop shop) {
return shop != null
&& CharSequenceUtil.isNotBlank(shop.getKeyWord())
&& !"zhuyesearch".equals(shop.getKeyWord());
}
private boolean containsUserHiddenShop(List<Shop> shops) {
for (Shop shop : shops) {
if (!isUserVisibleShop(shop)) {
@ -616,32 +848,7 @@ public class ShopController {
private int getBusinessTimeOrder(Shop shop, String currentTime) {
ShopTakeaway takeaway = shop == null ? null : shop.getShopTakeaway();
if (takeaway == null) {
return 1;
}
String begin = normalizeBusinessTime(takeaway.getBusinessHourBegin());
String end = normalizeBusinessTime(takeaway.getBusinessHourEnd());
if (CharSequenceUtil.isBlank(begin) || CharSequenceUtil.isBlank(end)) {
return 1;
}
if (begin.equals(end)) {
return 0;
}
boolean inBusinessTime;
if (begin.compareTo(end) <= 0) {
inBusinessTime = begin.compareTo(currentTime) <= 0 && end.compareTo(currentTime) >= 0;
} else {
inBusinessTime = begin.compareTo(currentTime) <= 0 || end.compareTo(currentTime) >= 0;
}
return inBusinessTime ? 0 : 1;
}
private String normalizeBusinessTime(String value) {
if (CharSequenceUtil.isBlank(value)) {
return "";
}
String time = value.trim();
return time.length() >= 5 ? time.substring(0, 5) : time;
return ShopBusinessHourUtil.getBusinessTimeOrder(takeaway, currentTime);
}
private void logSlowShopListQuery(String source, long startTime, long totalElements, int pageSize, String regionId) {
@ -894,4 +1101,27 @@ public class ShopController {
shopService.refreshShopCache(shop.getId(), shop.getRegionId());
return ResultUtil.data(shop);
}
@Data
public static class DaQuPrinterBindRequest {
private String shopId;
private String sn;
private String key;
private String name;
private String shopName;
}
@Data
public static class PrintConfigRequest {
private String shopId;
private String printingMethod;
}
@Data
public static class PrintConfigResponse {
private String shopId;
private String printingMethod;
private String printerSn;
private String printerName;
}
}

7
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopTakeawayController.java

@ -24,6 +24,7 @@ import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
@ -43,6 +44,12 @@ public class ShopTakeawayController {
@Autowired
private ShopTakeawayService shopTakeawayService;
@InitBinder
public void initBinder(WebDataBinder binder) {
// form 提交无法绑定 List<BusinessHourPeriod>,营业时段只用 businessHours JSON
binder.setDisallowedFields("businessHourList");
}
@RequestMapping(value = "/getByShopId", method = RequestMethod.GET)
@ApiOperation("根据店铺id获取外卖业务配置")
public Result<ShopTakeaway> getByShopId(

37
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/UserAddressController.java

@ -2,8 +2,10 @@ package cc.hiver.mall.controller;
import cc.hiver.core.common.utils.ResultUtil;
import cc.hiver.core.common.vo.Result;
import cc.hiver.mall.entity.ShopArea;
import cc.hiver.mall.entity.UserAddress;
import cc.hiver.mall.pojo.vo.UserAddressVo;
import cc.hiver.mall.service.ShopAreaService;
import cc.hiver.mall.service.mybatis.UserAddressService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -26,10 +28,16 @@ public class UserAddressController {
@Autowired
private UserAddressService userAddressService;
@Autowired
private ShopAreaService shopAreaService;
@RequestMapping(value = "/save", method = RequestMethod.POST)
@ApiOperation(value = "新增用户收货地址")
public Result save(UserAddress userAddress) {
Result validateResult = validateSocialAddress(userAddress);
if (validateResult != null) {
return validateResult;
}
// 如果是第一条地址或者传过来就是默认,设置默认逻辑可在这里增加
// 为了安全起见这里直接调用 save,不处理复杂逻辑
boolean result = userAddressService.save(userAddress);
@ -58,6 +66,10 @@ public class UserAddressController {
if (StringUtils.isBlank(userAddress.getRegionId())) {
userAddress.setRegionId(old.getRegionId());
}
Result validateResult = validateSocialAddress(userAddress);
if (validateResult != null) {
return validateResult;
}
boolean result = userAddressService.updateById(userAddress);
if (result) {
@ -118,4 +130,27 @@ public class UserAddressController {
return ResultUtil.success("设置成功");
}
private Result validateSocialAddress(UserAddress userAddress) {
if (userAddress == null || StringUtils.isBlank(userAddress.getRegionId())) {
return null;
}
ShopArea region = shopAreaService.get(userAddress.getRegionId());
if (region == null || !Integer.valueOf(2).equals(region.getAreaKind())) {
return null;
}
if (userAddress.getAddressLng() == null || userAddress.getAddressLat() == null) {
return ResultUtil.error("请选择带定位的收货地址");
}
ShopArea matched = shopAreaService.matchByLocation(
userAddress.getAddressLat().doubleValue(),
userAddress.getAddressLng().doubleValue()
);
if (matched == null || !userAddress.getRegionId().equals(matched.getId())) {
return ResultUtil.error("收货地址不在当前区域配送范围内");
}
userAddress.setAreaId(null);
userAddress.setFloor(null);
return null;
}
}

37
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java

@ -140,10 +140,12 @@ public class WorkerController {
if(u.getRegion().equals(worker.getRegion())){
return new ResultUtil<Worker>().setData(worker);
}else{
String oldRegion = worker.getRegion();
worker.setRegion(u.getRegion());
workerService.update(worker);
syncWorkerRegionCache(oldRegion, worker);
if(worker.getIsFullTime() != 1){
String oldRegion = worker.getRegion();
worker.setRegion(u.getRegion());
workerService.update(worker);
syncWorkerRegionCache(oldRegion, worker);
}
return new ResultUtil<Worker>().setData(worker);
}
}else{
@ -325,6 +327,33 @@ public class WorkerController {
return ResultUtil.success("操作成功");
}
@RequestMapping(value = "/admin/transferOrder/{workerId}", method = RequestMethod.POST)
@ApiOperation(value = "后台设置配送员是否可以接中转单")
public Result changeCanTransferOrder(@ApiParam("配送员唯一id标识") @PathVariable String workerId,
@RequestParam("canTransferOrder") Integer canTransferOrder) {
Worker worker = workerService.findByWorkerId(workerId);
if (worker == null) {
return ResultUtil.error("配送员不存在");
}
if (canTransferOrder == null || (canTransferOrder != 0 && canTransferOrder != 1)) {
return ResultUtil.error("中转接单状态参数错误");
}
workerService.changeCanTransferOrder(workerId, canTransferOrder);
worker = workerService.findByWorkerId(workerId);
if (worker == null) {
return ResultUtil.error("配送员不存在");
}
worker.setCanTransferOrder(canTransferOrder);
if (StrUtil.isNotBlank(worker.getRegion())) {
WorkerRedisVo workerRedisVo = workerRedisCacheUtil.get(worker.getRegion(), worker.getWorkerId());
if (workerRedisVo != null) {
workerRedisVo.setWorker(worker);
workerRedisCacheUtil.update(worker.getRegion(), workerRedisVo);
}
}
return ResultUtil.success("操作成功");
}
/**
* 配送员上线
*

10
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallDeliveryOrderMapper.java

@ -53,6 +53,16 @@ public interface MallDeliveryOrderMapper extends BaseMapper<MallDeliveryOrder> {
List<Worker> countOrdersLimit(@Param("regionId") String regionId);
/**
* 后台按日期范围分页查询配送员配送单量排名全部数据
*/
IPage<Worker> countOrdersPage(IPage<?> page, @Param("q") MallDeliveryOrderPageQuery q);
/**
* 后台按日期范围分页查询中转配送员配送单量排名全部数据
*/
IPage<Worker> countTransferOrdersPage(IPage<?> page, @Param("q") MallDeliveryOrderPageQuery q);
List<Map<String, Object>> countOrdersByStatus(@Param("workerId") String workerId);
MallDeliveryOrder selectByGroupId(@Param("orderId") String orderId);

6
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallDeliveryOrder.java

@ -109,6 +109,12 @@ public class MallDeliveryOrder implements Serializable {
private String transferArriveImage;
@ApiModelProperty(value = "中转取餐码")
private String transferPickupCode;
@ApiModelProperty(value = "中转配送员ID")
private String transferWorkerId;
@ApiModelProperty(value = "中转配送员名称")
private String transferWorkerName;
@ApiModelProperty(value = "中转配送员电话")
private String transferWorkerMobile;
@Transient
@TableField(exist = false)
@ApiModelProperty(value = "商家出餐时间")

9
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/Shop.java

@ -178,6 +178,9 @@ public class Shop extends HiverBaseEntity {
@ApiModelProperty(value = "1外卖商家 / 2团购商家")
private Integer merchantType;
@ApiModelProperty(value = "起送价格")
private BigDecimal startingPrice;
@ApiModelProperty(value = "明厨亮灶视频地址")
private String openKitchenVideoUrl;
@ -217,6 +220,12 @@ public class Shop extends HiverBaseEntity {
@ApiModelProperty(value = "中转地点名称")
private String transferAddressName;
@ApiModelProperty(value = "大趋云打印机SN")
private String printerSn;
@ApiModelProperty(value = "大趋云打印机名称")
private String printerName;
@Transient
@TableField(exist = false)
@ApiModelProperty(value = "店铺抽佣等设置")

10
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopArea.java

@ -64,17 +64,21 @@ public class ShopArea extends HiverBaseEntity {
@ApiModelProperty(value = "返佣比例,按百分比计算")
private Integer commissionRatio = 0;
@ApiModelProperty(value = "区域中心经度(仅一级区域,用于定位自动匹配)")
@ApiModelProperty(value = "区域中心经度(旧字段,仅一级区域)")
@Column(precision = 12, scale = 6)
private BigDecimal centerLng;
@ApiModelProperty(value = "区域中心纬度(仅一级区域,用于定位自动匹配)")
@ApiModelProperty(value = "区域中心纬度(旧字段,仅一级区域)")
@Column(precision = 12, scale = 6)
private BigDecimal centerLat;
@ApiModelProperty(value = "自动匹配半径(米,仅一级区域)")
@ApiModelProperty(value = "自动匹配半径(旧字段,米,仅一级区域)")
private Integer radiusMeters;
@ApiModelProperty(value = "定位自动匹配多边形坐标JSON(仅一级区域,GCJ-02,经纬度数组)")
@Column(columnDefinition = "text")
private String geoPolygon;
@ApiModelProperty(value = "区域类型:1校园区域 2社会区域(仅一级区域)")
@Column(nullable = false, columnDefinition = "int default 1")
private Integer areaKind = 1;

11
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopTakeaway.java

@ -16,6 +16,7 @@ limitations under the License.
package cc.hiver.mall.entity;
import cc.hiver.core.base.HiverBaseEntity;
import cc.hiver.mall.pojo.dto.BusinessHourPeriod;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
@ -26,7 +27,9 @@ import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.math.BigDecimal;
import java.util.List;
/**
* 店铺外卖业务配置实体类
@ -71,6 +74,14 @@ public class ShopTakeaway extends HiverBaseEntity {
@ApiModelProperty(value = "营业结束时间")
private String businessHourEnd;
@ApiModelProperty(value = "营业时段JSON,例:[{\"begin\":\"09:00\",\"end\":\"14:00\"}]")
private String businessHours;
@Transient
@TableField(exist = false)
@ApiModelProperty(value = "营业时段列表")
private List<BusinessHourPeriod> businessHourList;
@ApiModelProperty(value = "状态 0正常营业 1暂停营业 2冻结")
private Integer status;

8
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/UserAddress.java

@ -7,6 +7,8 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "用户收货地址")
@ -45,6 +47,12 @@ public class UserAddress extends HiverBaseEntity {
@ApiModelProperty(value = "区域名称")
private String areaName;
@ApiModelProperty(value = "收货地址经度(社会区域自由地址)")
private BigDecimal addressLng;
@ApiModelProperty(value = "收货地址纬度(社会区域自由地址)")
private BigDecimal addressLat;
@ApiModelProperty(value = "是否默认地址 (0否 1是)")
private Integer isDefault;
}

31
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncProducer.java

@ -53,6 +53,37 @@ public class OrderAsyncProducer {
log.info("【订单MQ】已发出配送完成异步收尾任务, deliveryId={}", params.get("deliveryId"));
}
public void sendCloudPrint(String orderId, String printReason) {
sendCloudPrint(orderId, printReason, 0);
}
public void sendCloudPrint(String orderId, String printReason, int retryCount) {
Map<String, Object> msg = new HashMap<>();
msg.put("orderId", orderId);
msg.put("printReason", printReason);
msg.put("retryCount", retryCount);
msg.put("timestamp", System.currentTimeMillis());
rabbitTemplate.convertAndSend(OrderQueueConfig.ORDER_DIRECT_EXCHANGE, OrderQueueConfig.ASYNC_PRINT_ROUTING, JSON.toJSONString(msg));
log.info("【云打印MQ】已发出云打印任务, orderId={}, reason={}, retryCount={}", orderId, printReason, retryCount);
}
public void sendCloudPrintDelayRetry(String orderId, String printReason, int retryCount, long delayMillis) {
Map<String, Object> msg = new HashMap<>();
msg.put("orderId", orderId);
msg.put("printReason", printReason);
msg.put("retryCount", retryCount);
msg.put("timestamp", System.currentTimeMillis());
rabbitTemplate.convertAndSend(OrderQueueConfig.ORDER_DIRECT_EXCHANGE,
OrderQueueConfig.ASYNC_PRINT_RETRY_DELAY_ROUTING,
JSON.toJSONString(msg),
message -> {
message.getMessageProperties().setExpiration(String.valueOf(delayMillis));
return message;
});
log.info("【云打印MQ】已发出云打印延迟重试任务, orderId={}, reason={}, retryCount={}, delayMillis={}",
orderId, printReason, retryCount, delayMillis);
}
/**
* 发送退款/售后申请超时延时消息默认1小时后自动同意
* @param refundId 退款记录IDMallRefundRecord.id

50
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderCloudPrintConsumer.java

@ -0,0 +1,50 @@
package cc.hiver.mall.mq;
import cc.hiver.mall.utils.DaQuCloudPrintService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class OrderCloudPrintConsumer {
private static final int MAX_RETRY_COUNT = 3;
private static final long[] RETRY_DELAY_MILLIS = {
60 * 1000L,
3 * 60 * 1000L,
5 * 60 * 1000L
};
@Autowired
private DaQuCloudPrintService daQuCloudPrintService;
@Autowired
private OrderAsyncProducer orderAsyncProducer;
@RabbitListener(queues = OrderQueueConfig.ASYNC_PRINT_QUEUE)
public void handleCloudPrint(String message) {
String orderId = null;
String printReason = null;
int retryCount = 0;
try {
JSONObject body = JSON.parseObject(message);
orderId = body.getString("orderId");
printReason = body.getString("printReason");
retryCount = body.getIntValue("retryCount");
daQuCloudPrintService.printOrder(orderId, printReason);
} catch (Exception e) {
if (orderId != null && retryCount < MAX_RETRY_COUNT) {
int nextRetryCount = retryCount + 1;
long delayMillis = RETRY_DELAY_MILLIS[Math.min(retryCount, RETRY_DELAY_MILLIS.length - 1)];
log.warn("【云打印MQ】处理失败,准备延迟重试。orderId={}, reason={}, retryCount={}, delayMillis={}, err={}",
orderId, printReason, nextRetryCount, delayMillis, e.getMessage());
orderAsyncProducer.sendCloudPrintDelayRetry(orderId, printReason, nextRetryCount, delayMillis);
return;
}
log.error("【云打印MQ】处理失败,已达到最大重试次数,不影响订单主流程。message={}, err={}", message, e.getMessage(), e);
}
}
}

28
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderQueueConfig.java

@ -25,6 +25,11 @@ public class OrderQueueConfig {
public static final String ASYNC_CACHE_QUEUE = "order.async.cache.queue";
public static final String ASYNC_CACHE_ROUTING = "order.async.cache.routing.key";
public static final String ASYNC_PRINT_QUEUE = "order.async.print.queue";
public static final String ASYNC_PRINT_ROUTING = "order.async.print.routing.key";
public static final String ASYNC_PRINT_RETRY_DELAY_QUEUE = "order.async.print.retry.delay.queue";
public static final String ASYNC_PRINT_RETRY_DELAY_ROUTING = "order.async.print.retry.delay.routing.key";
// 延时消息 DLX 配置
// 原理:将消息发至 delay 队列(没有消费者),等过期后,由 rabbitmq 自动抛入 dead 交换机,最终进入 dead 队列供消费。
public static final String DELAY_EXCHANGE = "order.delay.exchange";
@ -62,6 +67,29 @@ public class OrderQueueConfig {
return BindingBuilder.bind(asyncCacheQueue()).to(orderDirectExchange()).with(ASYNC_CACHE_ROUTING);
}
@Bean
public Queue asyncPrintQueue() {
return new Queue(ASYNC_PRINT_QUEUE, true);
}
@Bean
public Binding bindingAsyncPrintQueue() {
return BindingBuilder.bind(asyncPrintQueue()).to(orderDirectExchange()).with(ASYNC_PRINT_ROUTING);
}
@Bean
public Queue asyncPrintRetryDelayQueue() {
Map<String, Object> args = new HashMap<>(2);
args.put("x-dead-letter-exchange", ORDER_DIRECT_EXCHANGE);
args.put("x-dead-letter-routing-key", ASYNC_PRINT_ROUTING);
return new Queue(ASYNC_PRINT_RETRY_DELAY_QUEUE, true, false, false, args);
}
@Bean
public Binding bindingAsyncPrintRetryDelayQueue() {
return BindingBuilder.bind(asyncPrintRetryDelayQueue()).to(orderDirectExchange()).with(ASYNC_PRINT_RETRY_DELAY_ROUTING);
}
// ----------------- 死信(延时)架构配置 -----------------
@Bean

22
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/BusinessHourPeriod.java

@ -0,0 +1,22 @@
package cc.hiver.mall.pojo.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 营业时段
*/
@Data
@ApiModel("营业时段")
public class BusinessHourPeriod implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("开始时间 HH:mm")
private String begin;
@ApiModelProperty("结束时间 HH:mm")
private String end;
}

9
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/query/MallDeliveryOrderPageQuery.java

@ -68,6 +68,15 @@ public class MallDeliveryOrderPageQuery extends HiverBasePageQuery {
@ApiModelProperty("是否只查询未送达中转点")
private Boolean transferArriveEmpty;
@ApiModelProperty("中转配送员ID")
private String transferWorkerId;
@ApiModelProperty("是否配送员中转接单视角")
private Boolean transferWorkerView;
@ApiModelProperty("配送员中转视角是否查询已接单待送达")
private Boolean transferWorkerAccepted;
@ApiModelProperty("排序规则(如果有值且为deliveryFee,则按佣金降序)")
private String order;

4
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/ShopAreaService.java

@ -59,8 +59,8 @@ public interface ShopAreaService extends HiverBaseService<ShopArea, String> {
ShopArea findTopShopAreaById(String id);
/**
* 根据经纬度匹配一级区域+半径
* 仅返回半径内命中的校区都不在半径内则返回 null
* 根据经纬度匹配一级区域在多边形内
* 仅返回匹配范围内命中的区域未绘制范围或未命中则返回 null
*
* @param lat 纬度
* @param lng 经度

10
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallDeliveryOrderService.java

@ -82,6 +82,16 @@ public interface MallDeliveryOrderService extends IService<MallDeliveryOrder> {
List<Worker> countOrdersLimit(String regionId);
/**
* 后台按日期范围分页查询配送员配送单量排名
*/
IPage<Worker> countOrdersPage(MallDeliveryOrderPageQuery q);
/**
* 后台按日期范围分页查询中转配送员配送单量排名
*/
IPage<Worker> countTransferOrdersPage(MallDeliveryOrderPageQuery q);
List<Map<Integer, Integer>> countOrdersByType(String regionId);
List<Map<Integer, Integer>> countOrdersByTypeAdmin(MallDeliveryOrderPageQuery q);

108
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopAreaServiceImpl.java

@ -7,13 +7,14 @@ import cc.hiver.mall.dao.ShopAreaDao;
import cc.hiver.mall.entity.ShopArea;
import cc.hiver.mall.service.ShopAreaService;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
@ -25,7 +26,6 @@ import java.util.List;
@Service
@Transactional
public class ShopAreaServiceImpl implements ShopAreaService {
private static final double EARTH_RADIUS_METERS = 6371000D;
@Autowired
private ShopAreaDao shopAreaDao;
@ -113,32 +113,98 @@ public class ShopAreaServiceImpl implements ShopAreaService {
if (rootAreas == null || rootAreas.isEmpty()) {
return null;
}
ShopArea best = null;
double bestDistance = Double.MAX_VALUE;
for (ShopArea area : rootAreas) {
BigDecimal centerLat = area.getCenterLat();
BigDecimal centerLng = area.getCenterLng();
Integer radiusMeters = area.getRadiusMeters();
if (centerLat == null || centerLng == null || radiusMeters == null || radiusMeters <= 0) {
List<double[]> polygon = parseGeoPolygon(area.getGeoPolygon());
if (polygon.size() < 3) {
continue;
}
double distance = distanceMeters(lat, lng, centerLat.doubleValue(), centerLng.doubleValue());
if (distance <= radiusMeters && distance < bestDistance) {
best = area;
bestDistance = distance;
if (containsPoint(polygon, lng, lat)) {
return area;
}
}
return best;
return null;
}
private double distanceMeters(double lat1, double lng1, double lat2, double lng2) {
double dLat = Math.toRadians(lat2 - lat1);
double dLng = Math.toRadians(lng2 - lng1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
* Math.sin(dLng / 2) * Math.sin(dLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS_METERS * c;
private List<double[]> parseGeoPolygon(String geoPolygon) {
List<double[]> points = new ArrayList<>();
if (CharSequenceUtil.isBlank(geoPolygon)) {
return points;
}
try {
JSONArray array = JSONUtil.parseArray(geoPolygon);
for (Object item : array) {
Double pointLng = null;
Double pointLat = null;
if (item instanceof JSONArray) {
JSONArray point = (JSONArray) item;
if (point.size() >= 2) {
pointLng = point.getDouble(0);
pointLat = point.getDouble(1);
}
} else if (item instanceof java.util.List) {
java.util.List<?> point = (java.util.List<?>) item;
if (point.size() >= 2) {
pointLng = toDouble(point.get(0));
pointLat = toDouble(point.get(1));
}
} else if (item instanceof java.util.Map) {
java.util.Map<?, ?> point = (java.util.Map<?, ?>) item;
pointLng = toDouble(point.get("lng"));
pointLat = toDouble(point.get("lat"));
}
if (isValidLngLat(pointLng, pointLat)) {
points.add(new double[]{pointLng, pointLat});
}
}
} catch (Exception ignored) {
return new ArrayList<>();
}
return points;
}
private Double toDouble(Object value) {
if (value == null) {
return null;
}
try {
return Double.valueOf(String.valueOf(value));
} catch (Exception e) {
return null;
}
}
private boolean isValidLngLat(Double lng, Double lat) {
return lng != null && lat != null
&& lng >= -180 && lng <= 180
&& lat >= -90 && lat <= 90;
}
private boolean containsPoint(List<double[]> polygon, double lng, double lat) {
boolean inside = false;
for (int i = 0, j = polygon.size() - 1; i < polygon.size(); j = i++) {
double[] pi = polygon.get(i);
double[] pj = polygon.get(j);
if (isPointOnSegment(lng, lat, pj[0], pj[1], pi[0], pi[1])) {
return true;
}
boolean intersects = ((pi[1] > lat) != (pj[1] > lat))
&& (lng < (pj[0] - pi[0]) * (lat - pi[1]) / (pj[1] - pi[1]) + pi[0]);
if (intersects) {
inside = !inside;
}
}
return inside;
}
private boolean isPointOnSegment(double x, double y, double x1, double y1, double x2, double y2) {
double cross = (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1);
if (Math.abs(cross) > 1e-10) {
return false;
}
return x >= Math.min(x1, x2) - 1e-10
&& x <= Math.max(x1, x2) + 1e-10
&& y >= Math.min(y1, y2) - 1e-10
&& y <= Math.max(y1, y2) + 1e-10;
}
@Override

117
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopServiceImpl.java

@ -40,6 +40,7 @@ import cc.hiver.mall.productpicture.service.ProductPictureService;
import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.mybatis.ProductGroupBuyPriceService;
import cc.hiver.mall.utils.DateUtil;
import cc.hiver.mall.utils.ShopBusinessHourUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -56,8 +57,6 @@ import org.springframework.transaction.annotation.Transactional;
import javax.persistence.criteria.*;
import java.math.BigDecimal;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -71,7 +70,6 @@ import java.util.Map;
@Transactional
public class ShopServiceImpl implements ShopService {
private static final String GROUP_BUY_DEFAULT_PAGE_CACHE_PREFIX = "GROUP_BUY_DEFAULT_SHOP_PAGE:";
private static final DateTimeFormatter BUSINESS_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
@Autowired
private ShopDao shopDao;
@ -115,12 +113,11 @@ public class ShopServiceImpl implements ShopService {
public Page<Shop> findByCondition(Shop shop, Pageable pageable) {
boolean adminQuery = CharSequenceUtil.isNotBlank(shop.getShopIcon());
Sort pageSort = pageable.getSort();
if (adminQuery) {
if (pageable.isPaged()) {
pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.unsorted());
} else {
pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.unsorted());
pageable = Pageable.unpaged();
}
String currentBusinessTime = LocalTime.now().format(BUSINESS_TIME_FORMATTER);
return shopDao.findAll(new Specification<Shop>() {
@Nullable
@Override
@ -136,26 +133,30 @@ public class ShopServiceImpl implements ShopService {
final List<Predicate> list = new ArrayList<>();
if (CharSequenceUtil.isNotBlank(shop.getKeyWord()) && !"zhuyesearch".equals(shop.getKeyWord())) {
String keyword = shop.getKeyWord();
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<cc.hiver.mall.entity.Product> qw = new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
qw.select("shop_id").like("product_name", keyword);
List<cc.hiver.mall.entity.Product> productList = productMapper.selectList(qw);
List<String> shopIds = new ArrayList<>();
if (productList != null && !productList.isEmpty()) {
for (cc.hiver.mall.entity.Product p : productList) {
if (p.getShopId() != null && !shopIds.contains(p.getShopId())) {
shopIds.add(p.getShopId());
if (adminQuery) {
list.add(cb.like(ShopNameField, '%' + keyword + '%'));
} else {
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<cc.hiver.mall.entity.Product> qw = new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
qw.select("distinct shop_id").like("product_name", keyword);
List<Object> productShopIdList = productMapper.selectObjs(qw);
List<String> shopIds = new ArrayList<>();
if (productShopIdList != null && !productShopIdList.isEmpty()) {
for (Object shopId : productShopIdList) {
if (shopId != null) {
shopIds.add(String.valueOf(shopId));
}
}
}
}
if (!shopIds.isEmpty()) {
Path<String> idField = root.get("id");
Predicate namePredicate = cb.like(ShopNameField, '%' + keyword + '%');
Predicate idPredicate = idField.in(shopIds);
list.add(cb.or(namePredicate, idPredicate));
} else {
list.add(cb.like(ShopNameField, '%' + keyword + '%'));
if (!shopIds.isEmpty()) {
Path<String> idField = root.get("id");
Predicate namePredicate = cb.like(ShopNameField, '%' + keyword + '%');
Predicate idPredicate = idField.in(shopIds);
list.add(cb.or(namePredicate, idPredicate));
} else {
list.add(cb.like(ShopNameField, '%' + keyword + '%'));
}
}
}
if (CharSequenceUtil.isNotBlank(shop.getShopName())) {
@ -200,10 +201,8 @@ public class ShopServiceImpl implements ShopService {
}
cq.orderBy(orderList);
} else if (!isCountQuery(cq)) {
// 用户端营业时段排序改由内存按 businessHours JSON 全量排序后再分页,此处仅保留请求侧次要排序
List<Order> orderList = new ArrayList<>();
orderList.add(cb.asc(buildShopStatusOrder(statusField, cb)));
orderList.add(cb.asc(buildTakeawayStatusOrder(root, cq, cb)));
orderList.add(cb.asc(buildBusinessTimeOrder(root, cq, cb, currentBusinessTime)));
for (Sort.Order order : pageSort) {
if ("status".equals(order.getProperty())) {
continue;
@ -211,7 +210,9 @@ public class ShopServiceImpl implements ShopService {
Path<Object> sortField = root.get(order.getProperty());
orderList.add(order.isAscending() ? cb.asc(sortField) : cb.desc(sortField));
}
cq.orderBy(orderList);
if (!orderList.isEmpty()) {
cq.orderBy(orderList);
}
}
return null;
}
@ -223,63 +224,6 @@ public class ShopServiceImpl implements ShopService {
return Long.class.equals(resultType) || long.class.equals(resultType);
}
private Expression<Integer> buildShopStatusOrder(Path<Integer> statusField, CriteriaBuilder cb) {
return cb.<Integer>selectCase()
.when(cb.equal(statusField, ShopConstant.SHOP_STATUS_NORMAL), 0)
.otherwise(1);
}
private Expression<Integer> buildTakeawayStatusOrder(Root<Shop> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Subquery<Integer> statusQuery = cq.subquery(Integer.class);
Root<ShopTakeaway> takeawayRoot = statusQuery.from(ShopTakeaway.class);
statusQuery.select(cb.min(takeawayRoot.<Integer>get("status")));
statusQuery.where(cb.equal(takeawayRoot.get("shopId"), root.get("id")));
return cb.<Integer>selectCase()
.when(cb.isNull(statusQuery), 99)
.otherwise(statusQuery);
}
private Expression<Integer> buildBusinessTimeOrder(Root<Shop> root, CriteriaQuery<?> cq, CriteriaBuilder cb, String currentTime) {
Subquery<String> beginQuery = buildTakeawayStringSubquery(root, cq, cb, "businessHourBegin");
Subquery<String> endQuery = buildTakeawayStringSubquery(root, cq, cb, "businessHourEnd");
Expression<String> now = cb.literal(currentTime);
Expression<Boolean> missingTime = cb.or(
cb.isNull(beginQuery),
cb.isNull(endQuery),
cb.equal(beginQuery, ""),
cb.equal(endQuery, "")
);
Expression<Boolean> sameDayRange = cb.lessThanOrEqualTo(beginQuery, endQuery);
Expression<Boolean> allDayRange = cb.equal(beginQuery, endQuery);
Expression<Boolean> inSameDayRange = cb.and(
cb.lessThanOrEqualTo(beginQuery, now),
cb.greaterThanOrEqualTo(endQuery, now)
);
Expression<Boolean> inOvernightRange = cb.or(
cb.lessThanOrEqualTo(beginQuery, now),
cb.greaterThanOrEqualTo(endQuery, now)
);
Expression<Integer> sameDayResult = cb.<Integer>selectCase()
.when(inSameDayRange, 0)
.otherwise(1);
Expression<Integer> overnightResult = cb.<Integer>selectCase()
.when(inOvernightRange, 0)
.otherwise(1);
return cb.<Integer>selectCase()
.when(missingTime, 1)
.when(allDayRange, 0)
.when(sameDayRange, sameDayResult)
.otherwise(overnightResult);
}
private Subquery<String> buildTakeawayStringSubquery(Root<Shop> root, CriteriaQuery<?> cq, CriteriaBuilder cb, String fieldName) {
Subquery<String> subquery = cq.subquery(String.class);
Root<ShopTakeaway> takeawayRoot = subquery.from(ShopTakeaway.class);
subquery.select(cb.least(takeawayRoot.<String>get(fieldName)));
subquery.where(cb.equal(takeawayRoot.get("shopId"), root.get("id")));
return subquery;
}
@Override
public Shop findByShopName(String ShopName) {
return shopDao.findByShopName(ShopName);
@ -419,6 +363,9 @@ public class ShopServiceImpl implements ShopService {
cacheDTO.setShop(shop);
ShopTakeaway takeaway = shopTakeawayMapper.selectByPrimaryKey(shopId);
if (takeaway != null) {
ShopBusinessHourUtil.fillBusinessHourList(takeaway);
}
cacheDTO.setShopTakeaway(takeaway);
ProductPageQuery query = new ProductPageQuery();

59
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopTakeawayServiceImpl.java

@ -20,6 +20,7 @@ import cc.hiver.mall.entity.Shop;
import cc.hiver.mall.entity.ShopTakeaway;
import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.ShopTakeawayService;
import cc.hiver.mall.utils.ShopBusinessHourUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
@ -49,7 +50,9 @@ public class ShopTakeawayServiceImpl extends ServiceImpl<ShopTakeawayMapper, Sh
@Override
public ShopTakeaway selectByPrimaryKey(String shopId) {
return shopTakeawayMapper.selectByPrimaryKey(shopId);
ShopTakeaway takeaway = shopTakeawayMapper.selectByPrimaryKey(shopId);
ShopBusinessHourUtil.fillBusinessHourList(takeaway);
return takeaway;
}
@Override
@ -63,37 +66,51 @@ public class ShopTakeawayServiceImpl extends ServiceImpl<ShopTakeawayMapper, Sh
@Override
public ShopTakeaway insert(ShopTakeaway shopTakeaway) {
ShopBusinessHourUtil.normalizeForSave(shopTakeaway);
shopTakeawayMapper.insert(shopTakeaway);
if (shopTakeaway.getRegionId() != null) {
shopService.refreshShopCache(shopTakeaway.getShopId(), shopTakeaway.getRegionId());
Shop shop = syncShopStatus(shopTakeaway);
String regionId = resolveRefreshRegionId(shopTakeaway, null, shop);
if (CharSequenceUtil.isNotBlank(regionId)) {
shopService.refreshShopCache(shopTakeaway.getShopId(), regionId);
}
return shopTakeaway;
}
@Override
public void updateByPrimaryKeySelective(ShopTakeaway shopTakeaway) {
ShopBusinessHourUtil.normalizeForSave(shopTakeaway);
ShopTakeaway old = shopTakeawayMapper.selectByPrimaryKey(shopTakeaway.getShopId());
shopTakeawayMapper.updateByPrimaryKeySelective(shopTakeaway);
Shop shop = shopService.get(shopTakeaway.getShopId());
if(Integer.valueOf(0).equals(shopTakeaway.getStatus())){
// 恢复营业
if (shop != null) {
shop.setStatus(1);
shopService.update(shop);
}
}else if(Integer.valueOf(1).equals(shopTakeaway.getStatus())){
// 暂停营业
if (shop != null) {
shop.setStatus(2);
shopService.update(shop);
}
}
Shop shop = syncShopStatus(shopTakeaway);
String regionId = resolveRefreshRegionId(shopTakeaway, old, shop);
if (CharSequenceUtil.isNotBlank(regionId)) {
shopService.refreshShopCache(shopTakeaway.getShopId(), regionId);
}
}
private Shop syncShopStatus(ShopTakeaway shopTakeaway) {
if (shopTakeaway == null || shopTakeaway.getStatus() == null || CharSequenceUtil.isBlank(shopTakeaway.getShopId())) {
return null;
}
Shop shop = shopService.get(shopTakeaway.getShopId());
if (shop == null) {
return null;
}
Integer targetStatus = null;
if (Integer.valueOf(0).equals(shopTakeaway.getStatus())) {
// 外卖业务恢复营业时,商家恢复启用状态。
targetStatus = 1;
} else if (Integer.valueOf(1).equals(shopTakeaway.getStatus())) {
// 外卖业务暂停营业时,商家进入暂停营业状态。
targetStatus = 2;
}
if (targetStatus != null && !targetStatus.equals(shop.getStatus())) {
shop.setStatus(targetStatus);
shopService.update(shop);
}
return shop;
}
private String resolveRefreshRegionId(ShopTakeaway current, ShopTakeaway old, Shop shop) {
if (shop != null && CharSequenceUtil.isNotBlank(shop.getRegionId())) {
return shop.getRegionId();
@ -109,7 +126,13 @@ public class ShopTakeawayServiceImpl extends ServiceImpl<ShopTakeawayMapper, Sh
@Override
public List<ShopTakeaway> selectListByshopId(List<String> shopIdList) {
return shopTakeawayMapper.selectListByshopId(shopIdList);
List<ShopTakeaway> list = shopTakeawayMapper.selectListByshopId(shopIdList);
if (list != null) {
for (ShopTakeaway takeaway : list) {
ShopBusinessHourUtil.fillBusinessHourList(takeaway);
}
}
return list;
}
}

58
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java

@ -18,6 +18,7 @@ import cc.hiver.mall.pojo.query.MallDeliveryOrderPageQuery;
import cc.hiver.mall.pojo.query.MallRefundRecordPageQuery;
import cc.hiver.mall.pojo.vo.MallOrderFreeVO;
import cc.hiver.mall.pojo.vo.MallOrderVO;
import cc.hiver.mall.pojo.vo.WorkerRedisVo;
import cc.hiver.mall.service.mybatis.*;
import cc.hiver.mall.utils.*;
import cn.hutool.json.JSONUtil;
@ -129,6 +130,9 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
@Autowired
private WorkerServiceImpl workerServiceImpl;
@Autowired
private WorkerRedisCacheUtil workerRedisCacheUtil;
@Autowired
private MallUserCouponService mallUserCouponService;
@ -487,8 +491,11 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
} else {
triggerShopCookTimeoutEvent(cookTimeoutOrderIds, order);
}
if (transferDelivery && StringUtils.isNotBlank(delivery.getOrderId())) {
orderAsyncProducer.sendDelayMessage(delivery.getOrderId(), "Transfer_Arrive_Timeout", transferDelayMillis);
if (transferDelivery) {
if (StringUtils.isNotBlank(delivery.getOrderId())) {
orderAsyncProducer.sendDelayMessage(delivery.getOrderId(), "Transfer_Arrive_Timeout", transferDelayMillis);
}
notifyTransferWorkers(delivery);
}
// 放进配送员缓存
@ -517,11 +524,46 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
}
}).start();
}
if (Boolean.TRUE.equals(notifyShop)) {
for (String orderId : finalPushOrderIds) {
orderAsyncProducer.sendCloudPrint(orderId, "ACCEPT_ORDER");
}
}
log.info("配送员 {} 接单成功,deliveryId={}", workerId, deliveryId);
return 3;
}
private void notifyTransferWorkers(MallDeliveryOrder delivery) {
if (delivery == null || StringUtils.isBlank(delivery.getRegionId())) {
return;
}
List<WorkerRedisVo> workerRedisVoList = workerRedisCacheUtil.getAll(delivery.getRegionId());
if (workerRedisVoList == null || workerRedisVoList.isEmpty()) {
return;
}
String pushId = StringUtils.defaultIfBlank(delivery.getOrderId(), delivery.getId());
Set<String> clientIds = new LinkedHashSet<>();
for (WorkerRedisVo workerRedisVo : workerRedisVoList) {
Worker worker = workerRedisVo == null ? null : workerRedisVo.getWorker();
if (worker == null
|| !Integer.valueOf(1).equals(worker.getCanTransferOrder())
|| !Integer.valueOf(1).equals(worker.getIsOnLine())
|| Integer.valueOf(3).equals(worker.getWorkerStatus())
|| StringUtils.isBlank(worker.getClientId())) {
continue;
}
clientIds.add(worker.getClientId());
}
for (String clientId : clientIds) {
try {
jPushService.sendPushNotification(clientId, "您有一笔新的中转配送单,请及时接单", pushId);
} catch (Exception e) {
log.warn("中转配送推送失败, deliveryId={}, err={}", delivery.getId(), e.getMessage());
}
}
}
private String resolveShopPushClientId(MallOrder order) {
if (order == null || StringUtils.isBlank(order.getShopId())) {
return null;
@ -1131,6 +1173,18 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
return this.baseMapper.countOrdersLimit(regionId);
}
@Override
public IPage<Worker> countOrdersPage(MallDeliveryOrderPageQuery q) {
IPage<Worker> page = new Page<>(q.getPageNum(), q.getPageSize());
return this.baseMapper.countOrdersPage(page, q);
}
@Override
public IPage<Worker> countTransferOrdersPage(MallDeliveryOrderPageQuery q) {
IPage<Worker> page = new Page<>(q.getPageNum(), q.getPageSize());
return this.baseMapper.countTransferOrdersPage(page, q);
}
@Override
public List<Map<Integer, Integer>> countOrdersByType(String regionId) {
return this.baseMapper.countOrdersByType(regionId);

1
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java

@ -186,6 +186,7 @@ public class MallOrderGroupServiceImpl extends ServiceImpl<MallOrderGroupMapper,
if (shop != null && StringUtils.isNotBlank(shop.getClientId())) {
jPushService.sendPushNotification(shop.getClientId(), "您有一笔新的到店订单",order.getId());
}
orderAsyncProducer.sendCloudPrint(order.getId(), "SHOP_INSTANT_MAKE");
// 触发商家出餐超时监听
triggerShopCookTimeoutEvent(order);
}

39
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java

@ -24,6 +24,7 @@ import cc.hiver.mall.pojo.query.MallOrderPageQuery;
import cc.hiver.mall.pojo.vo.MallOrderFreeVO;
import cc.hiver.mall.pojo.vo.MallOrderVO;
import cc.hiver.mall.service.CommentService;
import cc.hiver.mall.service.ShopAreaService;
import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.mybatis.DealingsRecordService;
import cc.hiver.mall.service.mybatis.MallDeliveryOrderService;
@ -211,6 +212,9 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
@Autowired
private UserService userService;
@Autowired
private ShopAreaService shopAreaService;
// ================================================================
// 核心下单逻辑
// ================================================================
@ -228,6 +232,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
throw new RuntimeException("外卖配送时收货地址不能为空");
}
}
validateSocialDeliveryAddress(dto);
// 2. 扣库存 & 计算商品总金额
BigDecimal goodsAmount = BigDecimal.ZERO;
BigDecimal savedAmount = BigDecimal.ZERO;
@ -760,6 +765,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
if (shop != null && StringUtils.isNotBlank(shop.getClientId())) {
jPushService.sendPushNotification(shop.getClientId(), "您有一笔新的到店订单",orderId);
}
orderAsyncProducer.sendCloudPrint(orderId, "SHOP_INSTANT_MAKE");
// 触发商家出餐超时监听
triggerShopCookTimeoutEvent(orderId);
}
@ -1590,6 +1596,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
if (shop != null && StringUtils.isNotBlank(shop.getClientId())) {
jPushService.sendPushNotification(shop.getClientId(), "您有一笔新的到店订单",orderId);
}
orderAsyncProducer.sendCloudPrint(orderId, "SHOP_INSTANT_MAKE");
// 触发商家出餐超时监听
triggerShopCookTimeoutEvent(orderId);
@ -2296,6 +2303,34 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
mallDeliveryOrderMapper.insert(delivery);
}
private void validateSocialDeliveryAddress(CreateOrderDTO dto) {
if (dto == null || DELIVERY_TYPE_EXPRESS != (dto.getDeliveryType() == null ? 0 : dto.getDeliveryType())) {
return;
}
if (StringUtils.isBlank(dto.getRegionId()) || StringUtils.isBlank(dto.getAddressId())) {
return;
}
ShopArea region = shopAreaService.get(dto.getRegionId());
if (region == null || !Integer.valueOf(2).equals(region.getAreaKind())) {
return;
}
UserAddress address = userAddressMapper.selectById(dto.getAddressId());
if (address == null) {
throw new RuntimeException("收货地址不存在");
}
if (address.getAddressLng() == null || address.getAddressLat() == null) {
throw new RuntimeException("请选择带定位的收货地址");
}
ShopArea matched = shopAreaService.matchByLocation(
address.getAddressLat().doubleValue(),
address.getAddressLng().doubleValue()
);
if (matched == null || !dto.getRegionId().equals(matched.getId())) {
throw new RuntimeException("收货地址不在当前区域配送范围内");
}
dto.setPutAreaId(null);
}
private LocalDateTime calculateTransferArriveTime(String regionId, String shopId, Integer deliveryType,
Date mustFinishTime, boolean appointmentDelivery) {
if (appointmentDelivery && mustFinishTime != null) {
@ -2622,6 +2657,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
//发送商家通知
if (!isRunnerOrder(order)) {
jPushService.sendPushNotification(shopService.findById(order.getShopId()).getClientId(), "您有一笔订单申请退款",order.getId());
orderAsyncProducer.sendCloudPrint(order.getId(), "REFUND");
}
}else if(refundTypeStatus == 2){
record.setLinkId(workerId);
@ -2641,6 +2677,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
//发送商家通知
if(order.getOtherOrder() == null){
jPushService.sendPushNotification(shopService.findById(order.getShopId()).getClientId(), "您有一笔订单申请退款,配送员原因",order.getId());
orderAsyncProducer.sendCloudPrint(order.getId(), "REFUND");
}
}else if(refundTypeStatus == 3){
if (!isMerchantDeliveryOrder(order)) {
@ -2652,6 +2689,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
orderAsyncProducer.sendRefundDelayMessage(record.getId(), order.getId());
if (!isRunnerOrder(order)) {
jPushService.sendPushNotification(shopService.findById(order.getShopId()).getClientId(), "您有一笔订单申请退款",order.getId());
orderAsyncProducer.sendCloudPrint(order.getId(), "REFUND");
}
return;
}
@ -2677,6 +2715,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
orderAsyncProducer.sendRefundDelayMessage(record.getId(), order.getId());
//发送商家通知
jPushService.sendPushNotification(shopService.findById(order.getShopId()).getClientId(), "您有一笔订单申请退款",order.getId());
orderAsyncProducer.sendCloudPrint(order.getId(), "REFUND");
MallRefundRecord mallRefundRecord1 = copyRefundRecord(record);
mallRefundRecord1.setId(SnowFlakeUtil.nextId().toString());
mallRefundRecord1.setLinkId(workerId);

58
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java

@ -831,6 +831,8 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
refundWechatForAfterSaleWithOrderLock(order, mallRefundRecord);
if(mallRefundRecord.getLinkId().toUpperCase().startsWith("W")){
deductWorkerBalance(mallRefundRecord, DealingsRecordConstant.DEALINGS_TYPE[3]);
// 售后扣配送员:外卖订单配送单更新为已取消
cancelDeliveryOrderForWorkerAfterSale(order, mallRefundRecord);
}
}
//都处理了
@ -927,32 +929,34 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
mallUserCouponService.refundCoupon(mallRefundRecord.getOrderId());
if(mallRefundRecord.getDeliveryType() == DELIVERY_TYPE_PEISONG){
// 商家原因全额退款且配送员已取货(正常发佣金)时配送单应为已送达(3),其余场景取消(4)
int deliveryTargetStatus = Integer.valueOf(1).equals(mallRefundRecord.getRefundTypeStatus())
&& merchantRecordIncludesDeliveryFee(order, mallRefundRecord) ? 3 : 4;
//面对面拼团
if(mallRefundRecord.getOrderType() == STATUS_WAIT_PICKUP){
MallDeliveryOrder delivery = mallDeliveryOrderMapper.selectByGroupId(mallRefundRecord.getOrderId());
//更新配送单
if(delivery.getStatus() != 4){
if(delivery.getStatus() != 4 && delivery.getStatus() != 3){
LambdaUpdateWrapper<MallDeliveryOrder> duw = new LambdaUpdateWrapper<>();
duw.eq(MallDeliveryOrder::getOrderId, delivery.getOrderId())
.set(MallDeliveryOrder::getStatus, 4);
.set(MallDeliveryOrder::getStatus, deliveryTargetStatus);
mallDeliveryOrderMapper.update(null, duw);
//更新缓存
LambdaQueryWrapper<MallDeliveryOrder> qw = new LambdaQueryWrapper<>();
qw.eq(MallDeliveryOrder::getOrderId, delivery.getOrderId());
MallDeliveryOrder deliveryOrder = mallDeliveryOrderMapper.selectOne(qw);
workerOrderCacheUtil.remove(delivery.getWorkerId(),delivery.getId());
}
}else{
//更新配送单
LambdaUpdateWrapper<MallDeliveryOrder> duw = new LambdaUpdateWrapper<>();
duw.eq(MallDeliveryOrder::getOrderId, mallRefundRecord.getOrderId())
.set(MallDeliveryOrder::getStatus, 4);
.set(MallDeliveryOrder::getStatus, deliveryTargetStatus);
mallDeliveryOrderMapper.update(null, duw);
//更新缓存
LambdaQueryWrapper<MallDeliveryOrder> qw = new LambdaQueryWrapper<>();
qw.eq(MallDeliveryOrder::getOrderId, mallRefundRecord.getOrderId());
MallDeliveryOrder deliveryOrder = mallDeliveryOrderMapper.selectOne(qw);
workerOrderCacheUtil.remove(deliveryOrder.getWorkerId(),deliveryOrder.getId());
if (deliveryOrder != null) {
workerOrderCacheUtil.remove(deliveryOrder.getWorkerId(),deliveryOrder.getId());
}
}
}
refundWechatForRefundRecord(order, mallRefundRecord);
@ -980,6 +984,8 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
refundWechatForRefundRecord(order, mallRefundRecord);
if(mallRefundRecord.getLinkId().toUpperCase().startsWith("W")){
deductWorkerBalance(mallRefundRecord, DealingsRecordConstant.DEALINGS_TYPE[2]);
// 售后扣配送员:外卖订单配送单更新为已取消
cancelDeliveryOrderForWorkerAfterSale(order, mallRefundRecord);
}
}else{
//订单还是变成已完成状态
@ -1083,6 +1089,7 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
if (shop != null && shop.getClientId() != null && !"".equals(shop.getClientId())) {
jPushService.sendPushNotification(shop.getClientId(), "您有一笔订单用户申请售后,请尽快处理", order.getId());
}
orderAsyncProducer.sendCloudPrint(order.getId(), "REFUND");
} catch (Exception ignored) {
}
}
@ -1116,6 +1123,43 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
mallOrderMapper.update(null, uw);
}
/**
* 售后同意且扣配送员余额时外卖配送单置为已取消(4)并清理配送员订单缓存
*/
private void cancelDeliveryOrderForWorkerAfterSale(MallOrder order, MallRefundRecord mallRefundRecord) {
if (order == null || mallRefundRecord == null) {
return;
}
Integer deliveryType = mallRefundRecord.getDeliveryType() != null
? mallRefundRecord.getDeliveryType() : order.getDeliveryType();
if (!Integer.valueOf(DELIVERY_TYPE_PEISONG).equals(deliveryType)) {
return;
}
Integer refundType = mallRefundRecord.getRefundType();
Integer refundTypeStatus = mallRefundRecord.getRefundTypeStatus();
boolean matchRefundType = Integer.valueOf(2).equals(refundType) || Integer.valueOf(3).equals(refundType);
boolean matchRefundTypeStatus = Integer.valueOf(2).equals(refundTypeStatus) || Integer.valueOf(3).equals(refundTypeStatus);
// refundType=2(退配送费)可不带 refundTypeStatus;refundType=3 需为配送员/双方原因
if (!matchRefundType || (Integer.valueOf(3).equals(refundType) && !matchRefundTypeStatus)) {
return;
}
Integer orderType = mallRefundRecord.getOrderType() != null
? mallRefundRecord.getOrderType() : order.getOrderType();
MallDeliveryOrder delivery = Integer.valueOf(ORDER_TYPE_FACETOFACE).equals(orderType)
? mallDeliveryOrderMapper.selectByGroupId(mallRefundRecord.getOrderId())
: findDeliveryByOrderId(mallRefundRecord.getOrderId());
if (delivery == null || Integer.valueOf(4).equals(delivery.getStatus())) {
return;
}
LambdaUpdateWrapper<MallDeliveryOrder> duw = new LambdaUpdateWrapper<>();
duw.eq(MallDeliveryOrder::getId, delivery.getId())
.set(MallDeliveryOrder::getStatus, 4);
mallDeliveryOrderMapper.update(null, duw);
if (delivery.getWorkerId() != null) {
workerOrderCacheUtil.remove(delivery.getWorkerId(), delivery.getId());
}
}
/**
* 根据订单ID查找配送单
*/

106
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/DaQuCloudPrintService.java

@ -0,0 +1,106 @@
package cc.hiver.mall.utils;
import cc.hiver.mall.entity.Shop;
import cc.hiver.mall.pojo.vo.MallOrderVO;
import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.mybatis.MallOrderService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class DaQuCloudPrintService {
private static final Logger log = LoggerFactory.getLogger(DaQuCloudPrintService.class);
public static final String PRINTING_METHOD_CLOUD = "cloud";
private static final String PRINT_LOCK_KEY_PREFIX = "CLOUD_PRINT:LOCK:";
private static final String PRINT_SUCCESS_KEY_PREFIX = "CLOUD_PRINT:SUCCESS:";
@Autowired
private MallOrderService mallOrderService;
@Autowired
private ShopService shopService;
@Autowired
private DaQuPrinterUtil daQuPrinterUtil;
@Autowired
private StringRedisTemplate stringRedisTemplate;
public boolean isCloudPrintShop(String shopId) {
if (StringUtils.isBlank(shopId)) {
return false;
}
Shop shop = shopService.findById(shopId);
return isCloudPrintShop(shop);
}
public boolean isCloudPrintShop(Shop shop) {
return shop != null
&& PRINTING_METHOD_CLOUD.equalsIgnoreCase(shop.getPrintingMethod())
&& StringUtils.isNotBlank(shop.getPrinterSn());
}
public DaQuPrinterUtil.DaQuResponse printOrder(String orderId, String printReason) throws Exception {
if (StringUtils.isBlank(orderId)) {
throw new IllegalArgumentException("orderId is blank");
}
MallOrderVO order = mallOrderService.getOrderDetail(orderId);
if (order == null) {
throw new IllegalArgumentException("order not found: " + orderId);
}
Shop shop = shopService.findById(order.getShopId());
if (!isCloudPrintShop(shop)) {
log.info("Skip cloud print, shop is not cloud printer. orderId={}, shopId={}", orderId, order.getShopId());
return null;
}
String content = DaQuReceiptRenderUtil.render(order, printReason);
DaQuPrinterUtil.PrintRequest request = new DaQuPrinterUtil.PrintRequest();
request.setSn(shop.getPrinterSn());
request.setVoice(DaQuReceiptRenderUtil.voiceCode(printReason));
request.setContent(content);
request.setCopies(1);
request.setExpiresInSeconds(7200);
String outTradeNo = buildOutTradeNo(orderId, printReason);
request.setOutTradeNo(outTradeNo);
String successKey = PRINT_SUCCESS_KEY_PREFIX + outTradeNo;
if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(successKey))) {
log.info("Skip cloud print, already succeeded. orderId={}, outTradeNo={}", orderId, outTradeNo);
return null;
}
String lockKey = PRINT_LOCK_KEY_PREFIX + outTradeNo;
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.MINUTES);
if (!Boolean.TRUE.equals(locked)) {
throw new IllegalStateException("same cloud print outTradeNo is processing: " + outTradeNo);
}
try {
DaQuPrinterUtil.DaQuResponse response = daQuPrinterUtil.print(request);
if (response == null || !response.isSuccess()) {
log.warn("Cloud print failed. orderId={}, shopId={}, printerSn={}, responseCode={}, message={}",
orderId, order.getShopId(), shop.getPrinterSn(),
response == null ? null : response.getCode(),
response == null ? null : response.getMessage());
throw new IllegalStateException("Cloud print failed: "
+ (response == null ? "empty response" : response.getCode() + ":" + response.getMessage()));
} else {
stringRedisTemplate.opsForValue().set(successKey, "1", 7, TimeUnit.DAYS);
log.info("Cloud print accepted. orderId={}, shopId={}, printerSn={}, outTradeNo={}, data={}",
orderId, order.getShopId(), shop.getPrinterSn(), outTradeNo, response.getData());
}
return response;
} finally {
stringRedisTemplate.delete(lockKey);
}
}
private String buildOutTradeNo(String orderId, String printReason) {
String reason = StringUtils.defaultIfBlank(printReason, "AUTO");
String value = orderId + "-" + reason;
return value.length() > 100 ? value.substring(0, 100) : value;
}
}

521
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/DaQuPrinterUtil.java

@ -0,0 +1,521 @@
package cc.hiver.mall.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class DaQuPrinterUtil {
private static final Logger log = LoggerFactory.getLogger(DaQuPrinterUtil.class);
private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json;charset=UTF-8");
private final OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build();
@Value("${daqu.printer.appid:}")
private String appId;
@Value("${daqu.printer.appSecret:}")
private String appSecret;
@Value("${daqu.printer.apiBaseUrl:https://api.trenditiot.com}")
private String apiBaseUrl;
public DaQuResponse addPrinter(String sn, String key, String name) throws IOException {
return addPrinters(Collections.singletonList(new PrinterBindRequest(sn, key, name)));
}
public DaQuResponse addPrinters(List<PrinterBindRequest> printers) throws IOException {
return post("/openapi/addPrinter", printers);
}
public DaQuResponse editPrinters(List<PrinterEditRequest> printers) throws IOException {
return post("/openapi/editPrinter", printers);
}
public DaQuResponse delPrinter(String sn) throws IOException {
return delPrinters(Collections.singletonList(sn));
}
public DaQuResponse delPrinters(List<String> snList) throws IOException {
return post("/openapi/delPrinter", snList);
}
public DaQuResponse print(PrintRequest printRequest) throws IOException {
return post("/openapi/print", printRequest);
}
public DaQuResponse getPrintStatus(String sn, String printId) throws IOException {
return post("/openapi/getPrintStatus", new PrintStatusRequest(sn, printId));
}
public DaQuResponse getDeviceStatus(String sn) throws IOException {
return post("/openapi/getDeviceStatus", new SnRequest(sn));
}
public DaQuResponse batchGetDeviceStatus(List<String> snList) throws IOException {
return post("/openapi/batchGetDeviceStatus", snList);
}
public DaQuResponse cleanWaitingQueue(String sn) throws IOException {
return post("/openapi/cleanWaitingQueue", new SnRequest(sn));
}
public DaQuResponse setVolume(String sn, int volume) throws IOException {
return post("/openapi/setVolume", new VolumeRequest(sn, volume));
}
public DaQuResponse setImage(String sn, int index, String imageBase64) throws IOException {
return post("/openapi/setImage", new ImageRequest(sn, index, imageBase64));
}
public DaQuResponse payInVoice(PayInVoiceRequest request) throws IOException {
return post("/openapi/payInVoice", request);
}
public String buildSign(String uid, long stime, String requestBody) {
checkConfig();
String origin = uid + appId + stime + appSecret;
if (requestBody != null) {
origin += requestBody;
}
return DigestUtils.md5DigestAsHex(origin.getBytes(StandardCharsets.UTF_8));
}
private DaQuResponse post(String path, Object requestBody) throws IOException {
checkConfig();
String body = requestBody == null ? null : JSON.toJSONString(requestBody);
String uid = UUID.randomUUID().toString();
long stime = System.currentTimeMillis() / 1000;
String sign = buildSign(uid, stime, body);
RequestBody okhttpBody = RequestBody.create(JSON_MEDIA_TYPE, body == null ? "" : body);
Request request = new Request.Builder()
.url(buildUrl(path))
.addHeader("Content-Type", "application/json;charset=UTF-8")
.addHeader("appid", appId)
.addHeader("uid", uid)
.addHeader("stime", String.valueOf(stime))
.addHeader("sign", sign)
.post(okhttpBody)
.build();
try (Response response = httpClient.newCall(request).execute()) {
String responseBody = response.body() == null ? "" : response.body().string();
if (!response.isSuccessful()) {
log.error("DaQu printer request failed. path={}, httpStatus={}, body={}", path, response.code(), responseBody);
throw new IOException("DaQu printer request failed, httpStatus=" + response.code());
}
return JSON.parseObject(responseBody, DaQuResponse.class);
}
}
private String buildUrl(String path) {
String baseUrl = apiBaseUrl;
while (baseUrl.endsWith("/")) {
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
}
return baseUrl + path;
}
private void checkConfig() {
if (!StringUtils.hasText(appId) || !StringUtils.hasText(appSecret)) {
throw new IllegalStateException("DaQu printer config is missing: daqu.printer.appid/daqu.printer.appSecret");
}
}
public static class DaQuResponse {
private Integer code;
private String message;
private Object data;
public boolean isSuccess() {
return code != null && code == 0;
}
public JSONObject getDataObject() {
if (data == null) {
return null;
}
if (data instanceof JSONObject) {
return (JSONObject) data;
}
return JSON.parseObject(JSON.toJSONString(data));
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
public static class PrinterBindRequest {
private String sn;
private String key;
private String name;
private Integer lang;
public PrinterBindRequest() {
}
public PrinterBindRequest(String sn, String key, String name) {
this.sn = sn;
this.key = key;
this.name = name;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getLang() {
return lang;
}
public void setLang(Integer lang) {
this.lang = lang;
}
}
public static class PrinterEditRequest {
private String sn;
private String name;
private Integer lang;
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getLang() {
return lang;
}
public void setLang(Integer lang) {
this.lang = lang;
}
}
public static class PrintRequest {
private String sn;
private String voice;
private Integer voicePlayTimes;
private Integer voicePlayInterval;
private String content;
private Integer copies;
private Integer expiresInSeconds;
private String outTradeNo;
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getVoice() {
return voice;
}
public void setVoice(String voice) {
this.voice = voice;
}
public Integer getVoicePlayTimes() {
return voicePlayTimes;
}
public void setVoicePlayTimes(Integer voicePlayTimes) {
this.voicePlayTimes = voicePlayTimes;
}
public Integer getVoicePlayInterval() {
return voicePlayInterval;
}
public void setVoicePlayInterval(Integer voicePlayInterval) {
this.voicePlayInterval = voicePlayInterval;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Integer getCopies() {
return copies;
}
public void setCopies(Integer copies) {
this.copies = copies;
}
public Integer getExpiresInSeconds() {
return expiresInSeconds;
}
public void setExpiresInSeconds(Integer expiresInSeconds) {
this.expiresInSeconds = expiresInSeconds;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
}
public static class PayInVoiceRequest {
private String sn;
private Integer payChannel;
private Integer payAmount;
private Integer voicePlayTimes;
private Integer voicePlayInterval;
private Integer expiresInSeconds;
private String outTradeNo;
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public Integer getPayChannel() {
return payChannel;
}
public void setPayChannel(Integer payChannel) {
this.payChannel = payChannel;
}
public Integer getPayAmount() {
return payAmount;
}
public void setPayAmount(Integer payAmount) {
this.payAmount = payAmount;
}
public Integer getVoicePlayTimes() {
return voicePlayTimes;
}
public void setVoicePlayTimes(Integer voicePlayTimes) {
this.voicePlayTimes = voicePlayTimes;
}
public Integer getVoicePlayInterval() {
return voicePlayInterval;
}
public void setVoicePlayInterval(Integer voicePlayInterval) {
this.voicePlayInterval = voicePlayInterval;
}
public Integer getExpiresInSeconds() {
return expiresInSeconds;
}
public void setExpiresInSeconds(Integer expiresInSeconds) {
this.expiresInSeconds = expiresInSeconds;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
}
public static class PrintStatusRequest {
private String sn;
private String printId;
public PrintStatusRequest() {
}
public PrintStatusRequest(String sn, String printId) {
this.sn = sn;
this.printId = printId;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getPrintId() {
return printId;
}
public void setPrintId(String printId) {
this.printId = printId;
}
}
public static class SnRequest {
private String sn;
public SnRequest() {
}
public SnRequest(String sn) {
this.sn = sn;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
}
public static class VolumeRequest {
private String sn;
private Integer volume;
public VolumeRequest() {
}
public VolumeRequest(String sn, Integer volume) {
this.sn = sn;
this.volume = volume;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public Integer getVolume() {
return volume;
}
public void setVolume(Integer volume) {
this.volume = volume;
}
}
public static class ImageRequest {
private String sn;
private Integer index;
private String imageBase64;
public ImageRequest() {
}
public ImageRequest(String sn, Integer index, String imageBase64) {
this.sn = sn;
this.index = index;
this.imageBase64 = imageBase64;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
public String getImageBase64() {
return imageBase64;
}
public void setImageBase64(String imageBase64) {
this.imageBase64 = imageBase64;
}
}
}

313
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/DaQuReceiptRenderUtil.java

@ -0,0 +1,313 @@
package cc.hiver.mall.utils;
import cc.hiver.mall.entity.*;
import cc.hiver.mall.pojo.vo.MallOrderVO;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public final class DaQuReceiptRenderUtil {
private static final String LINE = "--------------------------------";
private static final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
private DaQuReceiptRenderUtil() {
}
public static String render(MallOrderVO order, String printReason) {
StringBuilder sb = new StringBuilder();
MallDeliveryOrder delivery = order.getDeliveryInfo();
List<MallRefundRecord> refunds = order.getMallRefundRecord();
boolean refundPrint = isRefundPrint(printReason);
boolean forcedOrderPrint = isOrderEventPrint(printReason);
boolean hasRefund = refundPrint && refunds != null && !refunds.isEmpty();
centerLarge(sb, orderNo(order));
centerLarge(sb, orderTypeText(order, delivery, refundPrint, forcedOrderPrint));
if (delivery != null && Integer.valueOf(1).equals(delivery.getAppointmentDelivery())) {
left(sb, "要求送达时间:" + formatTime(delivery.getMustFinishTime()));
}
left(sb, defaultString(order.getShopName()));
left(sb, "下单时间:" + formatTime(order.getCreateTime()));
if (!hasRefund) {
if (Integer.valueOf(1).equals(order.getDeliveryType())) {
left(sb, "预计送达时间:" + formatTime(delivery == null ? null : delivery.getMustFinishTime()));
} else if (Integer.valueOf(1).equals(order.getUserRequireMake())) {
left(sb, "核销时间:" + formatTime(order.getCreateTime()));
}
} else {
if (Integer.valueOf(1).equals(order.getUserRequireMake()) && Integer.valueOf(2).equals(order.getDeliveryType())) {
left(sb, "核销时间:" + formatTime(order.getCreateTime()));
}
left(sb, "申请退款时间:" + formatTime(refunds.get(0).getCreateTime()));
}
separator(sb);
if (shouldPrintDeliveryAddress(order, refundPrint, forcedOrderPrint)) {
largeLeft(sb, delivery == null ? "" : delivery.getReceiverName());
}
left(sb, "联系电话: " + maskPhoneLastFour(order.getReceiverPhone()));
if (shouldPrintDeliveryAddress(order, refundPrint, forcedOrderPrint)) {
largeLeft(sb, "地址:" + (delivery == null ? "" : delivery.getReceiverAddress()));
}
left(sb, "顾客电话已被隐藏,您可登录商家端或骑手端查看");
separator(sb);
if (order.getGoodsList() != null) {
for (MallOrderGoods goods : order.getGoodsList()) {
largeLeft(sb, defaultString(goods.getProductName()));
if (StringUtils.contains(goods.getSpecs(), ",")) {
left(sb, delNode(goods.getSpecs()));
}
largeLeft(sb, "¥" + amount(goods.getPrice()) + " X" + safeQuantity(goods.getQuantity()));
}
}
separator(sb);
left(sb, "配 送 费:" + amount(order.getDeliveryFee()));
if (positive(order.getPackageFee())) {
left(sb, "打 包 费:" + amount(order.getPackageFee()));
}
left(sb, "商品金额:" + amount(order.getGoodsAmount()));
if (order.getUserCoupon() != null && !order.getUserCoupon().isEmpty()) {
MallUserCoupon coupon = order.getUserCoupon().get(0);
left(sb, "优 惠 券:-" + amount(coupon.getDiscountAmount()));
}
if (positive(order.getFreeAmount())) {
left(sb, "锦鲤免单:-" + amount(order.getFreeAmount()));
}
left(sb, "合计金额:" + amount(safe(order.getGoodsAmount()).add(safe(order.getDeliveryFee())).add(safe(order.getPackageFee()))));
left(sb, (hasRefund ? "应退金额:" : "实付金额:") + amount(order.getTotalAmount()));
largeLeft(sb, "备注:" + defaultString(order.getRemark()));
br(sb);
if (hasRefund) {
MallRefundRecord refund = refunds.get(0);
left(sb, "退款原因:" + refundReason(refund));
appendRefundGoods(sb, refund);
}
separator(sb);
left(sb, "商品取到后,如有任何商品问题,请及时联系商家和平台,客服将为您服务,谢谢您的惠顾");
br(sb);
centerLarge(sb, "半径里");
center(sb, "万物拼团更省钱");
br(sb);
return sb.toString();
}
public static String voiceCode(String printReason) {
return StringUtils.containsIgnoreCase(printReason, "refund") ? "5" : "10";
}
private static void appendRefundGoods(StringBuilder sb, MallRefundRecord refund) {
if (refund.getItems() == null || refund.getItems().isEmpty()) {
return;
}
separator(sb);
for (MallReturnOrderGoods goods : refund.getItems()) {
largeLeft(sb, defaultString(goods.getProductName()));
if (StringUtils.contains(goods.getSpecs(), ",")) {
left(sb, delNode(goods.getSpecs()));
}
largeLeft(sb, "¥" + amount(goods.getPrice()) + " X" + safeQuantity(goods.getQuantity()));
}
}
private static boolean shouldPrintDeliveryAddress(MallOrderVO order, boolean refundPrint, boolean forcedOrderPrint) {
if (!Integer.valueOf(1).equals(order.getDeliveryType()) || refundPrint) {
return false;
}
if (forcedOrderPrint) {
return true;
}
Integer status = order.getStatus();
return !Integer.valueOf(7).equals(status)
&& !Integer.valueOf(11).equals(status)
&& !Integer.valueOf(5).equals(status)
&& !Integer.valueOf(6).equals(status)
&& !Integer.valueOf(8).equals(status)
&& !Integer.valueOf(12).equals(status);
}
private static String orderNo(MallOrderVO order) {
MallDeliveryOrder delivery = order.getDeliveryInfo();
if (Integer.valueOf(1).equals(order.getDeliveryType())
&& Integer.valueOf(3).equals(order.getOrderType())
&& delivery != null
&& StringUtils.isNotBlank(delivery.getNumberCode())) {
return delivery.getNumberCode();
}
return defaultString(order.getNumberCode());
}
private static String orderTypeText(MallOrderVO order, MallDeliveryOrder delivery, boolean refundPrint, boolean forcedOrderPrint) {
Integer status = order.getStatus();
if (delivery != null && Integer.valueOf(1).equals(delivery.getAppointmentDelivery())
&& Integer.valueOf(11).equals(status)) {
return "预约单";
}
if (delivery != null && Integer.valueOf(1).equals(delivery.getTransferDelivery())
&& !Integer.valueOf(7).equals(status) && !Integer.valueOf(11).equals(status)) {
return "取餐码(" + defaultString(delivery.getTransferPickupCode()) + ")";
}
if (refundPrint) {
return "申请退款";
}
if (Integer.valueOf(1).equals(order.getDeliveryType()) && Integer.valueOf(3).equals(order.getOrderType())) {
return "面对面配送";
}
if (!forcedOrderPrint) {
if (Integer.valueOf(7).equals(status) || Integer.valueOf(11).equals(status)) {
return "申请退款";
}
if (Integer.valueOf(3).equals(status) && Integer.valueOf(2).equals(order.getDeliveryType())
&& !Integer.valueOf(1).equals(order.getUserRequireMake())) {
return "待消费";
}
if (Integer.valueOf(5).equals(status)) {
return "已完成";
}
if (Integer.valueOf(6).equals(status)) {
return "已取消";
}
if (Integer.valueOf(8).equals(status)) {
return "已退款";
}
if (Integer.valueOf(12).equals(status)) {
return "已售后";
}
}
if (Integer.valueOf(0).equals(order.getIsPack()) && order.getOtherOrder() == null) {
return "到店-堂食";
}
if (Integer.valueOf(1).equals(order.getDeliveryType()) && Integer.valueOf(1).equals(order.getIsPack())) {
return "配送";
}
if (Integer.valueOf(1).equals(order.getIsPack()) && order.getOtherOrder() == null) {
return "到店-打包";
}
return "";
}
private static boolean isRefundPrint(String printReason) {
return StringUtils.containsIgnoreCase(printReason, "REFUND");
}
private static boolean isOrderEventPrint(String printReason) {
return StringUtils.containsIgnoreCase(printReason, "ACCEPT_ORDER")
|| StringUtils.containsIgnoreCase(printReason, "SHOP_INSTANT_MAKE");
}
private static String refundReason(MallRefundRecord refund) {
return getCancelReasonDescription(refund.getReason()) + " " + getRefundTypeText(refund) + "|" + getRefundReasonTypeText(refund);
}
private static String getCancelReasonDescription(String reason) {
if (StringUtils.isBlank(reason)) {
return "";
}
int chineseColonIndex = reason.indexOf(':');
int englishColonIndex = reason.indexOf(':');
int colonIndex = chineseColonIndex >= 0 ? chineseColonIndex : englishColonIndex;
return colonIndex >= 0 ? reason.substring(colonIndex + 1) : reason;
}
private static String getRefundTypeText(MallRefundRecord refund) {
if (Integer.valueOf(1).equals(refund.getRefundType())) {
return "退商品";
}
if (Integer.valueOf(2).equals(refund.getRefundType())) {
return "退配送费";
}
return "全额退款";
}
private static String getRefundReasonTypeText(MallRefundRecord refund) {
if (Integer.valueOf(1).equals(refund.getRefundTypeStatus())) {
return "商家原因";
}
if (Integer.valueOf(2).equals(refund.getRefundTypeStatus())) {
return "配送员原因";
}
if (Integer.valueOf(3).equals(refund.getRefundTypeStatus())) {
return StringUtils.contains(refund.getLinkId(), "W") ? "配送员原因" : "商家原因";
}
return "平台退款";
}
private static void centerLarge(StringBuilder sb, String text) {
sb.append("<C><FONT bolder=1 height=2 width=2>").append(clean(text)).append("</FONT></C><BR>");
}
private static void largeLeft(StringBuilder sb, String text) {
sb.append("<LEFT><FONT bolder=0 height=2 width=2>").append(clean(text)).append("</FONT></LEFT><BR>");
}
private static void center(StringBuilder sb, String text) {
sb.append("<C>").append(clean(text)).append("</C><BR>");
}
private static void left(StringBuilder sb, String text) {
sb.append("<LEFT>").append(clean(text)).append("</LEFT><BR>");
}
private static void separator(StringBuilder sb) {
center(sb, LINE);
}
private static void br(StringBuilder sb) {
sb.append("<BR>");
}
private static String maskPhoneLastFour(String phone) {
if (StringUtils.isBlank(phone)) {
return "";
}
String value = String.valueOf(phone);
if (value.length() <= 4) {
return value;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < value.length() - 4; i++) {
sb.append('*');
}
return sb.append(value.substring(value.length() - 4)).toString();
}
private static String formatTime(Date date) {
return date == null ? "" : new SimpleDateFormat(TIME_PATTERN).format(date);
}
private static String delNode(String text) {
return defaultString(text).replace("{", "").replace("}", "").replace("\"", "");
}
private static int safeQuantity(Integer quantity) {
return quantity == null ? 0 : quantity;
}
private static boolean positive(BigDecimal amount) {
return amount != null && amount.compareTo(BigDecimal.ZERO) > 0;
}
private static BigDecimal safe(BigDecimal amount) {
return amount == null ? BigDecimal.ZERO : amount;
}
private static String amount(BigDecimal amount) {
return safe(amount).stripTrailingZeros().toPlainString();
}
private static String defaultString(String text) {
return text == null ? "" : text;
}
private static String clean(String text) {
return defaultString(text)
.replaceAll("[\\ud800-\\udfff]", "")
.replace("<", "<")
.replace(">", ">")
.replace("&", "&");
}
}

218
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/ShopBusinessHourUtil.java

@ -0,0 +1,218 @@
package cc.hiver.mall.utils;
import cc.hiver.mall.entity.ShopTakeaway;
import cc.hiver.mall.pojo.dto.BusinessHourPeriod;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONUtil;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 商家营业时段工具支持多时段跨夜
*/
public final class ShopBusinessHourUtil {
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private ShopBusinessHourUtil() {
}
public static String normalizeTime(String value) {
if (CharSequenceUtil.isBlank(value)) {
return "";
}
String time = value.trim();
int colonIndex = time.indexOf(':');
if (colonIndex < 0) {
return "";
}
String hourPart = time.substring(0, colonIndex).trim();
int lastSpaceIndex = hourPart.lastIndexOf(' ');
int lastTIndex = hourPart.lastIndexOf('T');
int cutIndex = Math.max(lastSpaceIndex, lastTIndex);
if (cutIndex >= 0 && cutIndex + 1 < hourPart.length()) {
hourPart = hourPart.substring(cutIndex + 1);
}
String minutePart = time.substring(colonIndex + 1).trim();
if (minutePart.length() > 2) {
minutePart = minutePart.substring(0, 2);
}
try {
int hour = Integer.parseInt(hourPart);
int minute = Integer.parseInt(minutePart);
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
return "";
}
return String.format("%02d:%02d", hour, minute);
} catch (Exception e) {
return "";
}
}
public static List<BusinessHourPeriod> parsePeriods(ShopTakeaway takeaway) {
if (takeaway == null) {
return Collections.emptyList();
}
List<BusinessHourPeriod> periods = parsePeriods(takeaway.getBusinessHours());
if (!periods.isEmpty()) {
return periods;
}
String begin = normalizeTime(takeaway.getBusinessHourBegin());
String end = normalizeTime(takeaway.getBusinessHourEnd());
if (CharSequenceUtil.isBlank(begin) || CharSequenceUtil.isBlank(end)) {
return Collections.emptyList();
}
BusinessHourPeriod period = new BusinessHourPeriod();
period.setBegin(begin);
period.setEnd(end);
return Collections.singletonList(period);
}
public static List<BusinessHourPeriod> parsePeriods(String businessHoursJson) {
if (CharSequenceUtil.isBlank(businessHoursJson)) {
return Collections.emptyList();
}
try {
List<BusinessHourPeriod> list = JSONUtil.toList(businessHoursJson.trim(), BusinessHourPeriod.class);
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
List<BusinessHourPeriod> result = new ArrayList<>();
for (BusinessHourPeriod item : list) {
if (item == null) {
continue;
}
String begin = normalizeTime(item.getBegin());
String end = normalizeTime(item.getEnd());
if (CharSequenceUtil.isBlank(begin) || CharSequenceUtil.isBlank(end)) {
continue;
}
BusinessHourPeriod period = new BusinessHourPeriod();
period.setBegin(begin);
period.setEnd(end);
result.add(period);
}
return result;
} catch (Exception e) {
return Collections.emptyList();
}
}
public static String toJson(List<BusinessHourPeriod> periods) {
List<BusinessHourPeriod> cleaned = cleanPeriods(periods);
if (cleaned.isEmpty()) {
return null;
}
return JSONUtil.toJsonStr(cleaned);
}
public static List<BusinessHourPeriod> cleanPeriods(List<BusinessHourPeriod> periods) {
if (periods == null || periods.isEmpty()) {
return Collections.emptyList();
}
List<BusinessHourPeriod> result = new ArrayList<>();
for (BusinessHourPeriod item : periods) {
if (item == null) {
continue;
}
String begin = normalizeTime(item.getBegin());
String end = normalizeTime(item.getEnd());
if (CharSequenceUtil.isBlank(begin) || CharSequenceUtil.isBlank(end)) {
continue;
}
BusinessHourPeriod period = new BusinessHourPeriod();
period.setBegin(begin);
period.setEnd(end);
result.add(period);
}
return result;
}
/**
* 规范化写入同步 businessHours JSON并回填 begin/end取首时段兼容旧逻辑
*/
public static void normalizeForSave(ShopTakeaway takeaway) {
if (takeaway == null) {
return;
}
List<BusinessHourPeriod> periods = parsePeriods(takeaway.getBusinessHours());
if (periods.isEmpty() && takeaway.getBusinessHourList() != null) {
periods = cleanPeriods(takeaway.getBusinessHourList());
}
if (periods.isEmpty()) {
String begin = normalizeTime(takeaway.getBusinessHourBegin());
String end = normalizeTime(takeaway.getBusinessHourEnd());
if (CharSequenceUtil.isNotBlank(begin) && CharSequenceUtil.isNotBlank(end)) {
BusinessHourPeriod period = new BusinessHourPeriod();
period.setBegin(begin);
period.setEnd(end);
periods = Collections.singletonList(period);
}
}
if (periods.isEmpty()) {
takeaway.setBusinessHours(null);
takeaway.setBusinessHourList(Collections.emptyList());
return;
}
takeaway.setBusinessHours(JSONUtil.toJsonStr(periods));
takeaway.setBusinessHourList(periods);
takeaway.setBusinessHourBegin(periods.get(0).getBegin());
takeaway.setBusinessHourEnd(periods.get(0).getEnd());
}
public static void fillBusinessHourList(ShopTakeaway takeaway) {
if (takeaway == null) {
return;
}
takeaway.setBusinessHourList(parsePeriods(takeaway));
}
/**
* @return 0=营业中1=非营业中
*/
public static int getBusinessTimeOrder(ShopTakeaway takeaway, String currentTime) {
return isInBusinessHours(takeaway, currentTime) ? 0 : 1;
}
public static boolean isInBusinessHours(ShopTakeaway takeaway) {
return isInBusinessHours(takeaway, LocalTime.now().format(TIME_FORMATTER));
}
public static boolean isInBusinessHours(ShopTakeaway takeaway, String currentTime) {
List<BusinessHourPeriod> periods = parsePeriods(takeaway);
if (periods.isEmpty()) {
return false;
}
String now = normalizeTime(currentTime);
if (CharSequenceUtil.isBlank(now)) {
now = LocalTime.now().format(TIME_FORMATTER);
}
for (BusinessHourPeriod period : periods) {
if (isInSinglePeriod(period.getBegin(), period.getEnd(), now)) {
return true;
}
}
return false;
}
public static boolean isInSinglePeriod(String begin, String end, String currentTime) {
String b = normalizeTime(begin);
String e = normalizeTime(end);
String now = normalizeTime(currentTime);
if (CharSequenceUtil.isBlank(b) || CharSequenceUtil.isBlank(e) || CharSequenceUtil.isBlank(now)) {
return false;
}
if (b.equals(e)) {
return true;
}
if (b.compareTo(e) <= 0) {
return b.compareTo(now) <= 0 && e.compareTo(now) >= 0;
}
// 跨夜:如 22:00-02:00
return b.compareTo(now) <= 0 || e.compareTo(now) >= 0;
}
}

2
hiver-modules/hiver-mall/src/main/resources/db/shop_area_geo_polygon.sql

@ -0,0 +1,2 @@
ALTER TABLE `t_shop_area`
ADD COLUMN `geo_polygon` text DEFAULT NULL COMMENT '定位自动匹配多边形坐标JSON(仅一级区域,GCJ-02)';

4
hiver-modules/hiver-mall/src/main/resources/db/shop_cloud_print.sql

@ -0,0 +1,4 @@
ALTER TABLE t_shop
ADD COLUMN printing_method VARCHAR(20) NOT NULL DEFAULT 'bluetooth' COMMENT '打印方式 bluetooth:蓝牙 cloud:云打印',
ADD COLUMN printer_sn VARCHAR(50) DEFAULT NULL COMMENT '大趋云打印机SN',
ADD COLUMN printer_name VARCHAR(50) DEFAULT NULL COMMENT '大趋云打印机名称';

2
hiver-modules/hiver-mall/src/main/resources/db/shop_starting_price.sql

@ -0,0 +1,2 @@
ALTER TABLE t_shop
ADD COLUMN starting_price DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '起送价格';

16
hiver-modules/hiver-mall/src/main/resources/db/shop_takeaway_business_hours.sql

@ -0,0 +1,16 @@
-- 商家外卖营业时段(多时段 JSON)
ALTER TABLE `t_shop_takeaway`
ADD COLUMN `business_hours` text DEFAULT NULL COMMENT '营业时段JSON,例:[{"begin":"09:00","end":"14:00"},{"begin":"17:00","end":"22:00"}]' AFTER `business_hour_end`;
-- 历史单时段数据回填
UPDATE `t_shop_takeaway`
SET `business_hours` = CONCAT(
'[{"begin":"',
LEFT(TRIM(`business_hour_begin`), 5),
'","end":"',
LEFT(TRIM(`business_hour_end`), 5),
'"}]'
)
WHERE (`business_hours` IS NULL OR `business_hours` = '')
AND `business_hour_begin` IS NOT NULL AND TRIM(`business_hour_begin`) <> ''
AND `business_hour_end` IS NOT NULL AND TRIM(`business_hour_end`) <> '';

8
hiver-modules/hiver-mall/src/main/resources/db/transfer_worker_delivery.sql

@ -0,0 +1,8 @@
-- 配送员中转接单能力与中转配送员记录
ALTER TABLE `t_worker`
ADD COLUMN `can_transfer_order` TINYINT NULL DEFAULT 0 COMMENT '是否可以接中转单:0否 1是' AFTER `is_full_time`;
ALTER TABLE `mall_delivery_order`
ADD COLUMN `transfer_worker_id` VARCHAR(64) NULL COMMENT '中转配送员ID' AFTER `transfer_pickup_code`,
ADD COLUMN `transfer_worker_name` VARCHAR(64) NULL COMMENT '中转配送员名称' AFTER `transfer_worker_id`,
ADD COLUMN `transfer_worker_mobile` VARCHAR(32) NULL COMMENT '中转配送员电话' AFTER `transfer_worker_name`;

3
hiver-modules/hiver-mall/src/main/resources/db/user_address_geo.sql

@ -0,0 +1,3 @@
ALTER TABLE `t_user_address`
ADD COLUMN `address_lng` decimal(12,6) DEFAULT NULL COMMENT '收货地址经度(社会区域自由地址)',
ADD COLUMN `address_lat` decimal(12,6) DEFAULT NULL COMMENT '收货地址纬度(社会区域自由地址)';

130
hiver-modules/hiver-mall/src/main/resources/mapper/MallDeliveryOrderMapper.xml

@ -47,6 +47,9 @@
<result column="transfer_arrive_time" property="transferArriveTime"/>
<result column="transfer_arrive_image" property="transferArriveImage"/>
<result column="transfer_pickup_code" property="transferPickupCode"/>
<result column="transfer_worker_id" property="transferWorkerId"/>
<result column="transfer_worker_name" property="transferWorkerName"/>
<result column="transfer_worker_mobile" property="transferWorkerMobile"/>
</resultMap>
<!-- 分页查询配送单,支持多条件过滤(抢单大厅:hallOnly=true时只查workerId为空的) -->
@ -60,7 +63,8 @@
d.region_id,d.remark, d.all_count, d.phone_number, d.get_codes, d.get_pictures, d.is_big,
d.is_return,d.worker_name, d.arrive_time,d.new_worker,d.worker_phone,
d.transfer_delivery,d.transfer_address_id,d.transfer_address_name,
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code,
d.transfer_worker_id,d.transfer_worker_name,d.transfer_worker_mobile
FROM mall_delivery_order d
<where>
<if test="q.regionId != null and q.regionId != ''">
@ -118,6 +122,23 @@
<if test="q.transferArriveEmpty != null and q.transferArriveEmpty == false">
AND d.transfer_arrive_time IS NOT NULL and d.status not in (-1,4)
</if>
<if test="q.transferWorkerView != null and q.transferWorkerView == true">
AND d.status = 1
AND d.transfer_arrive_time IS NULL
<choose>
<when test="q.transferWorkerAccepted != null and q.transferWorkerAccepted == true">
AND d.transfer_worker_id = #{q.transferWorkerId}
</when>
<otherwise>
AND (d.transfer_worker_id IS NULL OR d.transfer_worker_id = '')
</otherwise>
</choose>
</if>
<if test="q.transferWorkerView == null or q.transferWorkerView == false">
<if test="q.transferWorkerId != null and q.transferWorkerId != ''">
AND d.transfer_worker_id = #{q.transferWorkerId}
</if>
</if>
</where>
ORDER BY
<choose>
@ -160,7 +181,8 @@
d.region_id,d.remark, d.all_count, d.phone_number, d.get_codes, d.get_pictures, d.is_big,
d.is_return,d.worker_name, d.arrive_time,d.new_worker,d.worker_phone,
d.transfer_delivery,d.transfer_address_id,d.transfer_address_name,
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code,
d.transfer_worker_id,d.transfer_worker_name,d.transfer_worker_mobile
FROM mall_delivery_order d
LEFT JOIN (
SELECT delivery_order_id, MAX(reassign_time) AS latest_reassign_time
@ -226,7 +248,8 @@
d.region_id,d.remark, d.all_count, d.phone_number, d.get_codes, d.get_pictures,
d.is_big,d.is_return,d.worker_name,d.arrive_time,d.new_worker,d.worker_phone,
d.transfer_delivery,d.transfer_address_id,d.transfer_address_name,
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code,
d.transfer_worker_id,d.transfer_worker_name,d.transfer_worker_mobile
FROM mall_delivery_order d
<where>
d.status not in (-1,4)
@ -299,7 +322,8 @@
d.region_id,d.remark, d.all_count, d.phone_number, d.get_codes, d.get_pictures,
d.is_big,d.is_return,d.worker_name,d.arrive_time,d.new_worker,d.worker_phone,
d.transfer_delivery,d.transfer_address_id,d.transfer_address_name,
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code,
d.transfer_worker_id,d.transfer_worker_name,d.transfer_worker_mobile
FROM mall_delivery_order d
LEFT JOIN mall_order o ON o.id = d.order_id
LEFT JOIN (
@ -308,7 +332,14 @@
GROUP BY delivery_order_id
) rr ON rr.delivery_order_id = d.id
<where>
d.status not in (-1,4)
<choose>
<when test="q.status != null">
AND d.status = #{q.status}
</when>
<otherwise>
AND d.status not in (-1,4)
</otherwise>
</choose>
<if test="q.regionId != null and q.regionId != ''">
AND d.region_id = #{q.regionId}
</if>
@ -327,9 +358,6 @@
<if test="q.shopName != null and q.shopName != ''">
AND d.shop_name like concat('%',#{q.shopName},'%')
</if>
<if test="q.status != null">
AND d.status = #{q.status}
</if>
<if test="q.getAreaId != null and q.getAreaId != ''">
AND d.get_area_id = #{q.getAreaId}
</if>
@ -359,13 +387,20 @@
AND d.transfer_arrive_time IS NULL
</if>
</where>
ORDER BY (CASE WHEN o.shop_make_time IS NULL THEN 1 ELSE 0 END) ASC,
o.shop_make_time ASC,
(CASE WHEN d.must_finish_time IS NOT NULL AND d.must_finish_time &lt; NOW() THEN 0 ELSE 1 END) ASC,
(CASE WHEN rr.latest_reassign_time IS NULL THEN 1 ELSE 0 END) ASC,
rr.latest_reassign_time DESC,
(CASE WHEN d.must_finish_time IS NULL THEN 1 ELSE 0 END) ASC,
d.must_finish_time ASC,d.delivery_fee DESC
<choose>
<when test="q.status != null and (q.status == 3 or q.status == 4)">
ORDER BY d.must_finish_time DESC
</when>
<otherwise>
ORDER BY (CASE WHEN o.shop_make_time IS NULL THEN 1 ELSE 0 END) ASC,
o.shop_make_time ASC,
(CASE WHEN d.must_finish_time IS NOT NULL AND d.must_finish_time &lt; NOW() THEN 0 ELSE 1 END) ASC,
(CASE WHEN rr.latest_reassign_time IS NULL THEN 1 ELSE 0 END) ASC,
rr.latest_reassign_time DESC,
(CASE WHEN d.must_finish_time IS NULL THEN 1 ELSE 0 END) ASC,
d.must_finish_time ASC,d.delivery_fee DESC
</otherwise>
</choose>
</select>
<select id="selectPageVOByStatusHis" resultMap="deliveryMap">
@ -378,7 +413,8 @@
d.region_id,d.remark, d.all_count, d.phone_number, d.get_codes, d.get_pictures,
d.is_big,d.is_return,d.worker_name,d.arrive_time,d.new_worker,d.worker_phone,
d.transfer_delivery,d.transfer_address_id,d.transfer_address_name,
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code,
d.transfer_worker_id,d.transfer_worker_name,d.transfer_worker_mobile
FROM mall_delivery_order d
<where>
d.status not in (-1,4)
@ -443,7 +479,8 @@
d.region_id,d.remark, d.all_count, d.phone_number, d.get_codes, d.get_pictures,
d.is_big,d.is_return,d.worker_name,d.arrive_time,d.new_worker,d.worker_phone,
d.transfer_delivery,d.transfer_address_id,d.transfer_address_name,
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code
d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code,
d.transfer_worker_id,d.transfer_worker_name,d.transfer_worker_mobile
FROM mall_delivery_order d
where d.group_id = (select id from mall_order_group where 1 = 1
<if test='orderId != null and orderId.trim() neq ""'>
@ -557,6 +594,65 @@
ORDER BY t.orderCount DESC;
</select>
<!-- 后台:按日期范围分页查询配送员配送单量排名 -->
<select id="countOrdersPage" resultType="cc.hiver.core.entity.Worker">
SELECT
t.orderCount,
tt.worker_name AS workerName,
tt.mobile AS mobile
FROM (
SELECT
COUNT(id) AS orderCount,
worker_id
FROM mall_delivery_order
WHERE `status` = 3
AND worker_id IS NOT NULL
AND worker_id != ''
<if test="q.regionId != null and q.regionId != ''">
AND region_id = #{q.regionId}
</if>
<if test="q.startDate != null and q.startDate != ''">
AND DATE(finish_time) &gt;= #{q.startDate}
</if>
<if test="q.endDate != null and q.endDate != ''">
AND DATE(finish_time) &lt;= #{q.endDate}
</if>
GROUP BY worker_id
) t
LEFT JOIN t_worker tt ON t.worker_id = tt.worker_id
ORDER BY t.orderCount DESC
</select>
<!-- 后台:按日期范围分页查询中转配送员配送单量排名 -->
<select id="countTransferOrdersPage" resultType="cc.hiver.core.entity.Worker">
SELECT
t.orderCount,
tt.worker_name AS workerName,
tt.mobile AS mobile
FROM (
SELECT
COUNT(id) AS orderCount,
transfer_worker_id
FROM mall_delivery_order
WHERE `status` = 3
AND transfer_delivery = 1
AND transfer_worker_id IS NOT NULL
AND transfer_worker_id != ''
<if test="q.regionId != null and q.regionId != ''">
AND region_id = #{q.regionId}
</if>
<if test="q.startDate != null and q.startDate != ''">
AND DATE(finish_time) &gt;= #{q.startDate}
</if>
<if test="q.endDate != null and q.endDate != ''">
AND DATE(finish_time) &lt;= #{q.endDate}
</if>
GROUP BY transfer_worker_id
) t
LEFT JOIN t_worker tt ON t.transfer_worker_id = tt.worker_id
ORDER BY t.orderCount DESC
</select>
<select id="countOrdersWorkerIndex" resultType="java.util.Map">
SELECT
COUNT(id) AS orderCount,

65
hiver-modules/hiver-mall/src/main/resources/mapper/ShopAreaMapper.xml

@ -18,11 +18,16 @@
<result column="parent_agent_id" property="parentAgentId" jdbcType="VARCHAR" />
<result column="parent_agent_name" property="parentAgentName" jdbcType="VARCHAR" />
<result column="commission_ratio" property="commissionRatio" jdbcType="INTEGER" />
<result column="center_lng" property="centerLng" jdbcType="DECIMAL" />
<result column="center_lat" property="centerLat" jdbcType="DECIMAL" />
<result column="radius_meters" property="radiusMeters" jdbcType="INTEGER" />
<result column="geo_polygon" property="geoPolygon" jdbcType="LONGVARCHAR" />
<result column="area_kind" property="areaKind" jdbcType="INTEGER" />
</resultMap>
<sql id="Base_Column_List" >
id, create_by, create_time, del_flag, update_by, update_time, is_parent, parent_id,
sort_order, status, title, is_canteen, delivery_enabled, parent_agent_id, parent_agent_name,
commission_ratio
commission_ratio, center_lng, center_lat, radius_meters, geo_polygon, area_kind
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" >
select
@ -39,12 +44,14 @@
del_flag, update_by, update_time,
is_parent, parent_id, sort_order,
status, title, is_canteen, delivery_enabled, parent_agent_id, parent_agent_name,
commission_ratio)
commission_ratio, center_lng, center_lat, radius_meters, geo_polygon, area_kind)
values (#{id,jdbcType=VARCHAR}, #{createBy,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP},
#{delFlag,jdbcType=INTEGER}, #{updateBy,jdbcType=VARCHAR}, #{updateTime,jdbcType=TIMESTAMP},
#{isParent,jdbcType=BIT}, #{parentId,jdbcType=VARCHAR}, #{sortOrder,jdbcType=DECIMAL},
#{status,jdbcType=INTEGER}, #{title,jdbcType=VARCHAR}, #{isCanteen,jdbcType=INTEGER}, #{deliveryEnabled,jdbcType=INTEGER},
#{parentAgentId,jdbcType=VARCHAR}, #{parentAgentName,jdbcType=VARCHAR}, #{commissionRatio,jdbcType=INTEGER})
#{parentAgentId,jdbcType=VARCHAR}, #{parentAgentName,jdbcType=VARCHAR}, #{commissionRatio,jdbcType=INTEGER},
#{centerLng,jdbcType=DECIMAL}, #{centerLat,jdbcType=DECIMAL}, #{radiusMeters,jdbcType=INTEGER},
#{geoPolygon,jdbcType=LONGVARCHAR}, #{areaKind,jdbcType=INTEGER})
</insert>
<insert id="insertSelective" parameterType="cc.hiver.mall.entity.ShopArea" >
insert into t_shop_area
@ -97,6 +104,21 @@
<if test="commissionRatio != null" >
commission_ratio,
</if>
<if test="centerLng != null" >
center_lng,
</if>
<if test="centerLat != null" >
center_lat,
</if>
<if test="radiusMeters != null" >
radius_meters,
</if>
<if test="geoPolygon != null" >
geo_polygon,
</if>
<if test="areaKind != null" >
area_kind,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
@ -147,6 +169,21 @@
<if test="commissionRatio != null" >
#{commissionRatio,jdbcType=INTEGER},
</if>
<if test="centerLng != null" >
#{centerLng,jdbcType=DECIMAL},
</if>
<if test="centerLat != null" >
#{centerLat,jdbcType=DECIMAL},
</if>
<if test="radiusMeters != null" >
#{radiusMeters,jdbcType=INTEGER},
</if>
<if test="geoPolygon != null" >
#{geoPolygon,jdbcType=LONGVARCHAR},
</if>
<if test="areaKind != null" >
#{areaKind,jdbcType=INTEGER},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="cc.hiver.mall.entity.ShopArea" >
@ -197,6 +234,21 @@
<if test="commissionRatio != null" >
commission_ratio = #{commissionRatio,jdbcType=INTEGER},
</if>
<if test="centerLng != null" >
center_lng = #{centerLng,jdbcType=DECIMAL},
</if>
<if test="centerLat != null" >
center_lat = #{centerLat,jdbcType=DECIMAL},
</if>
<if test="radiusMeters != null" >
radius_meters = #{radiusMeters,jdbcType=INTEGER},
</if>
<if test="geoPolygon != null" >
geo_polygon = #{geoPolygon,jdbcType=LONGVARCHAR},
</if>
<if test="areaKind != null" >
area_kind = #{areaKind,jdbcType=INTEGER},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -216,7 +268,12 @@
delivery_enabled = #{deliveryEnabled,jdbcType=INTEGER},
parent_agent_id = #{parentAgentId,jdbcType=VARCHAR},
parent_agent_name = #{parentAgentName,jdbcType=VARCHAR},
commission_ratio = #{commissionRatio,jdbcType=INTEGER}
commission_ratio = #{commissionRatio,jdbcType=INTEGER},
center_lng = #{centerLng,jdbcType=DECIMAL},
center_lat = #{centerLat,jdbcType=DECIMAL},
radius_meters = #{radiusMeters,jdbcType=INTEGER},
geo_polygon = #{geoPolygon,jdbcType=LONGVARCHAR},
area_kind = #{areaKind,jdbcType=INTEGER}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

10
hiver-modules/hiver-mall/src/main/resources/mapper/ShopTakeawayMapper.xml

@ -16,6 +16,7 @@
<result column="commission_rate_one" property="commissionRateOne" jdbcType="VARCHAR" />
<result column="business_hour_begin" property="businessHourBegin" jdbcType="VARCHAR" />
<result column="business_hour_end" property="businessHourEnd" jdbcType="VARCHAR" />
<result column="business_hours" property="businessHours" jdbcType="VARCHAR" />
<result column="status" property="status" jdbcType="INTEGER" />
<result column="type" property="type" jdbcType="VARCHAR" />
<result column="is_delivery" property="isDelivery" jdbcType="INTEGER" />
@ -26,7 +27,7 @@
</resultMap>
<sql id="Base_Column_List" >
id,create_by, create_time, update_time, update_by,
del_flag,shop_id,shop_name,region_id,commission_rate_more,virtual_group_commission_rate,commission_rate_one,business_hour_begin,business_hour_end,status,type,is_delivery,cooking_time,shoprank,is_price_lock,join_time
del_flag,shop_id,shop_name,region_id,commission_rate_more,virtual_group_commission_rate,commission_rate_one,business_hour_begin,business_hour_end,business_hours,status,type,is_delivery,cooking_time,shoprank,is_price_lock,join_time
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" >
select
@ -58,10 +59,10 @@
</delete>
<insert id="insert" parameterType="cc.hiver.mall.entity.ShopTakeaway" >
insert into t_shop_takeaway (id,create_by, create_time, update_time, update_by,
del_flag,shop_id, shop_name,region_id,commission_rate_more, virtual_group_commission_rate, commission_rate_one, business_hour_begin, business_hour_end, status, type, is_delivery, cooking_time, shoprank, is_price_lock, join_time)
del_flag,shop_id, shop_name,region_id,commission_rate_more, virtual_group_commission_rate, commission_rate_one, business_hour_begin, business_hour_end, business_hours, status, type, is_delivery, cooking_time, shoprank, is_price_lock, join_time)
values (#{id,jdbcType=VARCHAR},#{createBy,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP},
#{updateTime,jdbcType=TIMESTAMP},#{updateBy,jdbcType=VARCHAR},#{delFlag,jdbcType=INTEGER}
,#{shopId,jdbcType=VARCHAR},#{shopName,jdbcType=VARCHAR},#{regionId,jdbcType=VARCHAR}, #{commissionRateMore,jdbcType=DECIMAL}, #{virtualGroupCommissionRate,jdbcType=DECIMAL}, #{commissionRateOne,jdbcType=DECIMAL}, #{businessHourBegin,jdbcType=VARCHAR}, #{businessHourEnd,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER}, #{type,jdbcType=VARCHAR}, #{isDelivery,jdbcType=INTEGER}, #{cookingTime,jdbcType=INTEGER}, #{shoprank,jdbcType=INTEGER}, #{isPriceLock,jdbcType=INTEGER}, #{joinTime,jdbcType=VARCHAR})
,#{shopId,jdbcType=VARCHAR},#{shopName,jdbcType=VARCHAR},#{regionId,jdbcType=VARCHAR}, #{commissionRateMore,jdbcType=DECIMAL}, #{virtualGroupCommissionRate,jdbcType=DECIMAL}, #{commissionRateOne,jdbcType=DECIMAL}, #{businessHourBegin,jdbcType=VARCHAR}, #{businessHourEnd,jdbcType=VARCHAR}, #{businessHours,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER}, #{type,jdbcType=VARCHAR}, #{isDelivery,jdbcType=INTEGER}, #{cookingTime,jdbcType=INTEGER}, #{shoprank,jdbcType=INTEGER}, #{isPriceLock,jdbcType=INTEGER}, #{joinTime,jdbcType=VARCHAR})
</insert>
<update id="updateByPrimaryKeySelective" parameterType="cc.hiver.mall.entity.ShopTakeaway" >
@ -91,6 +92,9 @@
<if test="businessHourEnd != null" >
business_hour_end = #{businessHourEnd,jdbcType=VARCHAR},
</if>
<if test="businessHours != null" >
business_hours = #{businessHours,jdbcType=VARCHAR},
</if>
<if test="status != null" >
status = #{status,jdbcType=INTEGER},
</if>

6
hiver-modules/hiver-mall/src/main/resources/mapper/UserAddressMapper.xml

@ -12,6 +12,9 @@
<result column="floor" jdbcType="VARCHAR" property="floor" />
<result column="room_num" jdbcType="VARCHAR" property="roomNum" />
<result column="address_tag" jdbcType="VARCHAR" property="addressTag" />
<result column="area_name" jdbcType="VARCHAR" property="areaName" />
<result column="address_lng" jdbcType="DECIMAL" property="addressLng" />
<result column="address_lat" jdbcType="DECIMAL" property="addressLat" />
<result column="is_default" jdbcType="INTEGER" property="isDefault" />
<result column="create_by" jdbcType="VARCHAR" property="createBy" />
@ -22,7 +25,8 @@
</resultMap>
<sql id="Base_Column_List">
id, user_id, receiver_name, receiver_phone, sex, area_id, floor, room_num, address_tag, is_default,
id, user_id, receiver_name, receiver_phone, sex, area_id, floor, room_num, address_tag, area_name,
address_lng, address_lat, is_default,
create_by, create_time, update_by, update_time, del_flag
</sql>

Loading…
Cancel
Save