49 changed files with 2454 additions and 254 deletions
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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("&", "&"); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -0,0 +1,2 @@ |
|||
ALTER TABLE `t_shop_area` |
|||
ADD COLUMN `geo_polygon` text DEFAULT NULL COMMENT '定位自动匹配多边形坐标JSON(仅一级区域,GCJ-02)'; |
|||
@ -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 '大趋云打印机名称'; |
|||
@ -0,0 +1,2 @@ |
|||
ALTER TABLE t_shop |
|||
ADD COLUMN starting_price DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '起送价格'; |
|||
@ -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`) <> ''; |
|||
@ -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`; |
|||
@ -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 '收货地址纬度(社会区域自由地址)'; |
|||
Loading…
Reference in new issue