Просмотр исходного кода

Merge remote-tracking branch 'origin/dev' into dev

zuy 2 лет назад
Родитель
Сommit
99698e5be2
24 измененных файлов с 576 добавлено и 77 удалено
  1. 2 0
      admin-web/src/views/admin/ordering/index.vue
  2. 1 0
      admin-web/src/views/admin/station/endpoint/index.vue
  3. 7 2
      admin/src/main/java/com/kym/admin/controller/FinanceController.java
  4. 9 0
      admin/src/main/resources/application-dev.yml
  5. 3 0
      common/src/main/java/com/kym/common/config/WxFapiaoConfig.java
  6. 3 1
      entity/src/main/java/com/kym/entity/BaseEntity.java
  7. 11 44
      entity/src/main/java/com/kym/entity/admin/EquipmentInfo.java
  8. 1 0
      entity/src/main/java/com/kym/entity/miniapp/ChargeOrder.java
  9. 16 0
      entity/src/main/java/com/kym/entity/miniapp/Invoice.java
  10. 162 0
      entity/src/main/java/com/kym/entity/wechat/FapiaoApplications.java
  11. 24 0
      entity/src/main/java/com/kym/entity/wechat/FapiaoDownload.java
  12. 3 3
      entity/src/main/java/com/kym/entity/wechat/InvoiceNotification.java
  13. 2 1
      mapper/src/main/resources/mappers/admin/EquipmentInfoMapper.xml
  14. 6 1
      mapper/src/main/resources/mappers/miniapp/InvoiceMapper.xml
  15. 6 3
      miniapp/src/main/java/com/kym/miniapp/controller/ChargerController.java
  16. 2 1
      service/src/main/java/com/kym/service/miniapp/ChargeService.java
  17. 34 0
      service/src/main/java/com/kym/service/miniapp/DelayService.java
  18. 3 2
      service/src/main/java/com/kym/service/miniapp/impl/ChargeOrderServiceImpl.java
  19. 55 9
      service/src/main/java/com/kym/service/miniapp/impl/ChargeServiceImpl.java
  20. 93 0
      service/src/main/java/com/kym/service/miniapp/impl/DelayServiceImpl.java
  21. 14 0
      service/src/main/java/com/kym/service/queue/ChargeDelayConsumer.java
  22. 41 0
      service/src/main/java/com/kym/service/queue/DelayedItem.java
  23. 4 0
      service/src/main/java/com/kym/service/wechat/WxPayService.java
  24. 74 10
      service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

+ 2 - 0
admin-web/src/views/admin/ordering/index.vue

@@ -239,6 +239,8 @@ const state = reactive({
     data: [] as Array < any >,
     loading: false,
     columns: [
+      {label: '站点ID',  prop: 'stationId',width:100, resizable: true, fixed: 'left'},
+      {label: '手机号',  prop: 'mobilePhone',width:130, resizable: true, fixed: 'left'},
       // {label: '站点ID',  prop: 'stationId',width:100, resizable: true, fixed: 'left'},
       {label: '站点',  prop: 'stationName',width:150, resizable: true, fixed: 'left'},
       {label: '充电订单号', prop: 'startChargeSeq', width:170, resizable: true, fixed: 'left'},

+ 1 - 0
admin-web/src/views/admin/station/endpoint/index.vue

@@ -169,6 +169,7 @@ const state = reactive({
       {label: '站点编号', prop: 'stationNo', resizable: true, width: 100, fixed: 'left'},
       {label: '站点名称', prop: 'stationName', resizable: true, width: 200, fixed: 'left'},
       {label: '充电桩编号', prop: 'shortId', resizable: true, width: 110, fixed: 'left'},
+      {label: '车位编号', prop: 'parkingNo', resizable: true, width: 90, fixed: 'left'},
       {label: '充电桩序列号', prop: 'equipmentId', width: 180, resizable: true},
       {label: '设备型号', prop: 'equipmentModel', width: 150, resizable: true},
       {label: '服务状态', prop: 'serviceStatus', resizable: true, width: 130},

+ 7 - 2
admin/src/main/java/com/kym/admin/controller/FinanceController.java

@@ -25,7 +25,12 @@ public class FinanceController {
         this.refundLogService = refundLogService;
     }
 
-    @SysLog("退款申请列表")
+    /**
+     * 退款申请列表
+     *
+     * @param params
+     * @return
+     */
     @GetMapping("/listRefundLog")
     R<?> listRefundLog(@ModelAttribute CommonQueryParam params) {
         return R.success(refundLogService.listRefundLog(params));
@@ -39,7 +44,7 @@ public class FinanceController {
     }
 
     @GetMapping("handleInvoice")
-    R<?> handleInvoice(){
+    R<?> handleInvoice() {
         return R.success(wxPayService.baseInformation());
     }
 

+ 9 - 0
admin/src/main/resources/application-dev.yml

@@ -36,6 +36,15 @@ wechat:
     aesKey: #微信小程序消息服务器配置的EncodingAESKey
     msgDataFormat: JSON
 
+  fapiao:
+    baseInformation: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/base-information
+    taxCodes: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/tax-codes
+    fapiaoApplications: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications
+    notifyUrl: https://dev-cloud.kuaiyuman.cn/admin/finance/fapiaoNotify
+    devConfig: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/development-config
+    fapiaoFiles: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications/%s/fapiao-files
+    queryFapiao: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications/%s
+
 spring:
   datasource:
     druid: #以下是全局默认值,可以全局更改

+ 3 - 0
common/src/main/java/com/kym/common/config/WxFapiaoConfig.java

@@ -18,5 +18,8 @@ public class WxFapiaoConfig {
     private String taxCodes;
     private String fapiaoApplications;
     private String notifyUrl;
+    private String devConfig;
+    private String fapiaoFiles;
+    private String queryFapiao;
 
 }

+ 3 - 1
entity/src/main/java/com/kym/entity/BaseEntity.java

@@ -3,6 +3,7 @@ package com.kym.entity;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
+import java.io.Serializable;
 import java.time.LocalDateTime;
 
 /**
@@ -11,7 +12,8 @@ import java.time.LocalDateTime;
  * @date 2023-08-14 19:00
  */
 @Data
-public class BaseEntity {
+public class BaseEntity implements Serializable {
+    private static final long serialVersionUID = 1L;
     /**
      * ID
      */

+ 11 - 44
entity/src/main/java/com/kym/entity/admin/EquipmentInfo.java

@@ -19,11 +19,12 @@ import lombok.Data;
 @TableName("t_equipment_info")
 public class EquipmentInfo extends BaseEntity {
 
-    private static final long serialVersionUID = 1L;
-
-    private static int SERVICE_STATUS_空闲 = 0;
-    private static int SERVICE_STATUS_已连接 = 1;
-    private static int SERVICE_STATUS_充电中 = 2;
+    public static int SERVICE_STATUS_离线 = 0;
+    public static int SERVICE_STATUS_空闲 = 1;
+    public static int SERVICE_STATUS_已连接 = 2;
+    public static int SERVICE_STATUS_充电中 = 3;
+    public static int SERVICE_STATUS_预约中 = 4;
+    public static int SERVICE_STATUS_故障 = 255;
 
     /**
      * 公司id
@@ -83,6 +84,11 @@ public class EquipmentInfo extends BaseEntity {
      */
     private Integer equipmentType;
 
+    /**
+     * 车位编号
+     */
+    private String parkingNo;
+
     /**
      * 位置坐标
      * <p>
@@ -121,13 +127,6 @@ public class EquipmentInfo extends BaseEntity {
         return this;
     }
 
-    public void setStationNo(String stationNo) {
-        this.stationNo = stationNo;
-    }
-
-    public void setEquipmentId(String equipmentId) {
-        this.equipmentId = equipmentId;
-    }
 
     public EquipmentInfo setShortId(String shortId) {
         this.shortId = shortId;
@@ -137,43 +136,11 @@ public class EquipmentInfo extends BaseEntity {
         return this;
     }
 
-    public void setManufacturerId(String manufacturerId) {
-        this.manufacturerId = manufacturerId;
-    }
-
-    public void setManufacturerName(String manufacturerName) {
-        this.manufacturerName = manufacturerName;
-    }
-
-    public void setEquipmentModel(String equipmentModel) {
-        this.equipmentModel = equipmentModel;
-    }
-
-    public void setProductionDate(String productionDate) {
-        this.productionDate = productionDate;
-    }
-
-    public void setEquipmentType(Integer equipmentType) {
-        this.equipmentType = equipmentType;
-    }
-
-    public void setLocation(JSONObject location) {
-        this.location = location;
-    }
-
     public EquipmentInfo setPower(Double power) {
         this.power = power;
         return this;
     }
 
-    public void setNetStatus(Integer netStatus) {
-        this.netStatus = netStatus;
-    }
-
-    public void setServiceStatus(Integer serviceStatus) {
-        this.serviceStatus = serviceStatus;
-    }
-
     public EquipmentInfo setStationName(String stationName) {
         this.stationName = stationName;
         return this;

+ 1 - 0
entity/src/main/java/com/kym/entity/miniapp/ChargeOrder.java

@@ -29,6 +29,7 @@ public class ChargeOrder extends BaseEntity implements Serializable {
     public static int ORDER_STATUS_失败 = 2;
 
 
+    public static int CHARGE_STATUS_预约中 = 0;
     public static int CHARGE_STATUS_启动中 = 1;
     public static int CHARGE_STATUS_充电中 = 2;
     public static int CHARGE_STATUS_停止中 = 3;

+ 16 - 0
entity/src/main/java/com/kym/entity/miniapp/Invoice.java

@@ -26,7 +26,18 @@ import java.util.List;
 public class Invoice extends BaseEntity {
     public static final String TYPE_个人 = "INDIVIDUAL";
     public static final String TYPE_企业 = "ORGANIZATION";
+
+    public static final int STATUS_待开票 = 0;
+    public static final int STATUS_已开票 = 1;
+
+
     private static final long serialVersionUID = 1L;
+
+    /**
+     * 微信发票申请id
+     */
+    private String applyId;
+
     private Long userId;
 
     /**
@@ -110,6 +121,11 @@ public class Invoice extends BaseEntity {
      */
     private String biller;
 
+    /**
+     * 发票状态
+     */
+    private Integer status;
+
     /**
      * 备注
      */

+ 162 - 0
entity/src/main/java/com/kym/entity/wechat/FapiaoApplications.java

@@ -0,0 +1,162 @@
+package com.kym.entity.wechat;
+
+import java.util.List;
+
+/**
+ * @author skyline
+ * @description 查询电子发票
+ * @date 2023-10-07 16:02
+ */
+public class FapiaoApplications {
+    private int total_count;
+    private List<FapiaoEntity> fapiao_information;
+
+    class FapiaoEntity {
+        private String fapiao_id;
+        /**
+         * ISSUE_ACCEPTED: 开票申请已受理
+         * ISSUED: 发票已开具
+         * REVERSE_ACCEPTED: 冲红申请已受理
+         * REVERSED: 发票已冲红
+         */
+        private String status;
+
+        /**
+         * 蓝字发票信息,当发票状态不为ISSUE_ACCEPTED时存在
+         */
+        private FapiaoInfo blue_fapiao;
+        /**
+         * 红字发票信息,当发票状态为REVERSED时存在
+         */
+        private FapiaoInfo red_fapiao;
+
+        /**
+         * 电子发票卡券信息
+         */
+        private CardInfo card_information;
+
+        /**
+         * 总价税合计,所有发票行单行金额合计的累加,单位:分
+         */
+        private int total_amount;
+        /**
+         * 总税额,所有发票行单行税额的累加,单位:分
+         */
+        private int tax_amount;
+        /**
+         * 总金额,所有发票行单行金额的累加,单位:分
+         */
+        private int amount;
+        /**
+         * 销售方信息
+         */
+        private InvoiceBaseInfo.SellerInfo seller_information;
+        /**
+         * 购买方信息
+         */
+        private FaPiao.BuyerInformation buyer_information;
+        /**
+         * 附加信息
+         */
+        private InvoiceBaseInfo.ExtraInfo extra_information;
+
+        /**
+         * 发票行信息
+         */
+        private List<FapiaoItem> items;
+        /**
+         * 备注
+         */
+        private String remark;
+    }
+
+    class FapiaoInfo {
+        /**
+         * 发票代码
+         */
+        private String fapiao_code;
+        /**
+         * 发票号码
+         */
+        private String fapiao_number;
+        /**
+         * 校验码
+         */
+        private String check_code;
+        /**
+         * 发票票面密码区内容
+         */
+        private String password;
+        /**
+         * 开票时间,遵循RFC3339标准格式
+         * 2020-07-01T12:00:00+08:00
+         */
+        private String fapiao_time;
+    }
+
+    class CardInfo {
+        private String card_appid;
+        private String card_openid;
+        private String card_id;
+        private String card_code;
+        /**
+         * INSERT_ACCEPTED: 插卡申请已受理
+         * INSERTED: 已插入用户卡包
+         * DISCARD_ACCEPTED: 作废申请已受理
+         * DISCARDED: 发票卡券已作废
+         */
+        private String card_status;
+    }
+
+    class FapiaoItem {
+        /**
+         * 税局侧规定的货物或应税劳务、服务税收分类编码
+         */
+        private String tax_code;
+        /**
+         * 由商户自定义的货物或应税劳务、服务名称
+         */
+        private String goods_name;
+        /**
+         * 规格型号
+         */
+        private String specification;
+        /**
+         * 单位
+         */
+        private String unit;
+        /**
+         * 数量,单位为10^-8^,100000000表示数量为1
+         */
+        private int quantity;
+        /**
+         * 单价,单位为10^-6^分,100000000表示1元
+         */
+        private int unit_price;
+        /**
+         * 单行金额,单位:分
+         */
+        private int amount;
+        /**
+         * 单行金额和税费的和,单位:分
+         */
+        private int tax_amount;
+        /**
+         * 税率,单位为万分之一,如1300代表13%
+         */
+        private int tax_rate;
+        /**
+         * 税收优惠政策标识可选取值:
+         * NO_FAVORABLE: 无优惠
+         * OUTSIDE_VAT: 不征税
+         * VAT_EXEMPT: 免税
+         * NORMAL_ZERO_RATED: 普通零税率
+         * EXPORT_ZERO_RATED: 出口零税率
+         */
+        private int tax_prefer_mark;
+        /**
+         * 指定该发票行是否折扣行,折扣行一定是被折扣行的下一行
+         */
+        private boolean discount = false;
+    }
+}

+ 24 - 0
entity/src/main/java/com/kym/entity/wechat/FapiaoDownload.java

@@ -0,0 +1,24 @@
+package com.kym.entity.wechat;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author skyline
+ * @description 发票下载
+ * @date 2023-10-07 15:42
+ */
+@Data
+public class FapiaoDownload {
+
+    private List<FapiaoDownloadInfo> fapiao_download_info_list;
+
+    @Data
+    public class FapiaoDownloadInfo {
+        private String fapiao_id;
+        private String download_url;
+        private String status;
+    }
+
+}

+ 3 - 3
entity/src/main/java/com/kym/entity/wechat/InvoiceNotification.java

@@ -32,7 +32,7 @@ public class InvoiceNotification {
     /**
      * 发票状态
      */
-    enum FapiaoStatus {
+    public enum FapiaoStatus {
         ISSUE_ACCEPTED, ISSUED, REVERSE_ACCEPTED, REVERSED
     }
 
@@ -40,12 +40,12 @@ public class InvoiceNotification {
     /**
      * 发票状态-卡包状态
      */
-    enum CardStatus {
+    public enum CardStatus {
         INSERT_ACCEPTED, INSERTED, DISCARD_ACCEPTED, DISCARDED
     }
 
     @Data
-    class FapiaoInformation {
+    public class FapiaoInformation {
 
         /**
          * 商户发票单号,唯一标识一张发票

+ 2 - 1
mapper/src/main/resources/mappers/admin/EquipmentInfoMapper.xml

@@ -12,6 +12,7 @@
         <result column="equipment_model" property="equipmentModel" />
         <result column="production_date" property="productionDate" />
         <result column="equipment_type" property="equipmentType" />
+        <result column="parking_no" property="parkingNo" />
         <result column="location" property="location" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
         <result column="power" property="power" />
         <result column="net_status" property="netStatus" />
@@ -20,7 +21,7 @@
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id,company_id, station_id,equipment_id, manufacturer_id, manufacturer_name, equipment_model, production_date, equipment_type, location, power, net_status, service_status,create_time,update_time
+        id,company_id, station_id,equipment_id, manufacturer_id, manufacturer_name, equipment_model, production_date, equipment_type, parking_no,location, power, net_status, service_status,create_time,update_time
     </sql>
 
 </mapper>

+ 6 - 1
mapper/src/main/resources/mappers/miniapp/InvoiceMapper.xml

@@ -4,6 +4,8 @@
 
     <!-- 通用查询映射结果 -->
     <resultMap id="BaseResultMap" type="com.kym.entity.miniapp.Invoice">
+        <result column="id" property="id" />
+        <result column="applyId" property="applyId" />
         <result column="user_id" property="userId" />
         <result column="order_details" property="orderDetails" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
         <result column="total_power" property="totalPower" />
@@ -20,12 +22,15 @@
         <result column="invoice_amount" property="invoiceAmount" />
         <result column="tax_info" property="taxInfo" />
         <result column="biller" property="biller" />
+        <result column="status" property="status" />
         <result column="remark" property="remark" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
     </resultMap>
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        user_id, order_details, total_power, total_money, elec_money, service_money, email, invoice_type, invoice_title, tax_id, address, bank_name, bank_account, invoice_amount, tax_info, biller, remark
+        id,apply_id,user_id, order_details, total_power, total_money, elec_money, service_money, email, invoice_type, invoice_title, tax_id, address, bank_name, bank_account, invoice_amount, tax_info, biller, status,remark,create_time,update_time
     </sql>
 
 </mapper>

+ 6 - 3
miniapp/src/main/java/com/kym/miniapp/controller/ChargerController.java

@@ -14,6 +14,7 @@ import com.kym.service.miniapp.ChargeService;
 import lombok.SneakyThrows;
 import org.springframework.web.bind.annotation.*;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 
@@ -79,9 +80,11 @@ public class ChargerController {
     }
 
     @ApiLog("启动充电")
-    @GetMapping("/startCharge/{connectorId}")
-    R startCharge(@PathVariable("connectorId") String connectorId) {
-        return R.success(chargeService.queryStartCharge(connectorId));
+    @GetMapping({"/startCharge/{connectorId}", "/startCharge/{connectorId}"})
+    R startCharge(@PathVariable("connectorId") String connectorId,
+                  @RequestParam(value = "isBooking", required = false) Boolean isBooking,
+                  @RequestParam(value = "startTime", required = false) LocalDateTime startTime) {
+        return R.success(chargeService.queryStartCharge(connectorId, isBooking,startTime));
     }
 
     @ApiLog("停止充电")

+ 2 - 1
service/src/main/java/com/kym/service/miniapp/ChargeService.java

@@ -3,6 +3,7 @@ package com.kym.service.miniapp;
 import com.kym.entity.enplus.response.EnBusinessPolicy;
 import com.kym.entity.miniapp.ChargeOrder;
 
+import java.time.LocalDateTime;
 import java.util.Map;
 
 /**
@@ -12,7 +13,7 @@ import java.util.Map;
  */
 public interface ChargeService {
 
-    Map queryStartCharge(String connectorId);
+    Map queryStartCharge(String connectorId, Boolean isBooking, LocalDateTime startTime);
 
     ChargeOrder queryEquipChargeStatus();
 

+ 34 - 0
service/src/main/java/com/kym/service/miniapp/DelayService.java

@@ -0,0 +1,34 @@
+package com.kym.service.miniapp;
+
+import com.kym.service.queue.DelayedItem;
+
+/**
+ * @author skyline
+ * @description
+ * @date 2023-10-08 21:41
+ */
+public interface DelayService<T> {
+    /**
+     * 添加延迟对象到延时队列
+     *
+     * @param delayedItem 延迟对象
+     * @return boolean
+     */
+    boolean addToOrderDelayQueue(DelayedItem<T> delayedItem);
+
+    /**
+     * 根据对象添加到指定延时队列
+     *
+     * @param data 数据对象
+     * @return boolean
+     */
+    boolean addToDelayQueue(T data);
+
+    /**
+     * 移除指定的延迟对象从延时队列中
+     *
+     * @param data
+     */
+    void removeToOrderDelayQueue(T data);
+
+}

+ 3 - 2
service/src/main/java/com/kym/service/miniapp/impl/ChargeOrderServiceImpl.java

@@ -43,8 +43,8 @@ import java.util.stream.Collectors;
 @DS("db-miniapp")
 public class ChargeOrderServiceImpl extends ServiceImpl<ChargeOrderMapper, ChargeOrder> implements ChargeOrderService {
 
-    private final KymCache kymCache;
 
+    private final KymCache kymCache;
     private final ExportService exportService;
 
     public ChargeOrderServiceImpl(KymCache kymCache, ExportService exportService) {
@@ -52,6 +52,7 @@ public class ChargeOrderServiceImpl extends ServiceImpl<ChargeOrderMapper, Charg
         this.exportService = exportService;
     }
 
+
     @Override
     public ChargeOrder getChargingOrderByUserId(Long userId) {
         return lambdaQuery()
@@ -173,7 +174,7 @@ public class ChargeOrderServiceImpl extends ServiceImpl<ChargeOrderMapper, Charg
             item.setServiceMoneyPercent(BigDecimal.ONE.subtract(item.getElecMoneyPercent()));
         }).collect(Collectors.toList());
         var map = Map.of(
-                "totalPower", BigDecimal.valueOf(res.stream().mapToDouble(StationStatVo::getTotalPower).sum()).setScale(2,RoundingMode.HALF_UP).doubleValue(),
+                "totalPower", BigDecimal.valueOf(res.stream().mapToDouble(StationStatVo::getTotalPower).sum()).setScale(2, RoundingMode.HALF_UP).doubleValue(),
                 "serviceMoney", res.stream().mapToInt(StationStatVo::getServiceMoney).sum()
         );
         return new PageBean<>(res).setExtraData(map);

+ 55 - 9
service/src/main/java/com/kym/service/miniapp/impl/ChargeServiceImpl.java

@@ -6,18 +6,25 @@ import com.kym.common.config.EnPlusConfig;
 import com.kym.common.constant.ResponseEnum;
 import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.OrderUtils;
+import com.kym.entity.admin.EquipmentInfo;
 import com.kym.entity.enplus.response.EnBusinessPolicy;
 import com.kym.entity.miniapp.ChargeOrder;
+import com.kym.service.admin.EquipmentInfoService;
 import com.kym.service.admin.EquipmentRelationService;
 import com.kym.service.enplus.EnPlusService;
 import com.kym.service.miniapp.AccountService;
 import com.kym.service.miniapp.ChargeOrderService;
 import com.kym.service.miniapp.ChargeService;
+import com.kym.service.queue.DelayedItem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
 import java.util.Map;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * @author skyline
@@ -31,6 +38,8 @@ public class ChargeServiceImpl implements ChargeService {
 
     private final EquipmentRelationService equipmentRelationService;
 
+    private final EquipmentInfoService equipmentInfoService;
+
     private final ChargeOrderService chargeOrderService;
 
     private final AccountService accountService;
@@ -39,8 +48,16 @@ public class ChargeServiceImpl implements ChargeService {
 
     private final EnPlusConfig enPlusConfig;
 
-    public ChargeServiceImpl(EquipmentRelationService equipmentRelationService, ChargeOrderService chargeOrderService, AccountService accountService, EnPlusService enPlusService, EnPlusConfig enPlusConfig) {
+    /**
+     * 预约充电队列
+     */
+    DelayQueue<DelayedItem> delayQueue = new DelayQueue<>();
+
+    private ExecutorService executorService = Executors.newFixedThreadPool(2);
+
+    public ChargeServiceImpl(EquipmentRelationService equipmentRelationService, EquipmentInfoService equipmentInfoService, ChargeOrderService chargeOrderService, AccountService accountService, EnPlusService enPlusService, EnPlusConfig enPlusConfig) {
         this.equipmentRelationService = equipmentRelationService;
+        this.equipmentInfoService = equipmentInfoService;
         this.chargeOrderService = chargeOrderService;
         this.accountService = accountService;
         this.enPlusService = enPlusService;
@@ -51,17 +68,26 @@ public class ChargeServiceImpl implements ChargeService {
      * 启动充电
      *
      * @param connectorId
+     * @param isBooking
+     * @param startTime
      * @return
      */
     @Override
-    public Map<String, String> queryStartCharge(String connectorId) {
+    public Map<String, String> queryStartCharge(String connectorId, Boolean isBooking, LocalDateTime startTime) {
         var userId = StpUtil.getLoginIdAsLong();
+//        if (isBooking) {
+//            // 预约充电通过connectorId查询预约中的订单
+//            var bookingOrder = chargeOrderService.lambdaQuery()
+//                    .eq(ChargeOrder::getConnectorId, connectorId).eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中).one();
+//            userId =bookingOrder.getUserId();
+//        }
         var map = getConnectorIdAndStationId(connectorId);
         connectorId = map.get("connectorId");
         var stationId = map.get("stationId");
         LOGGER.info("用户:{},设备:{}请求启动充电", userId, connectorId);
         // 二维码文本
         var qrCode = "";
+
         // 当前设备是是否有正在进行中的订单
         var chargeOrder = chargeOrderService.getChargingOrderByUserId(userId);
         if (chargeOrder != null) {
@@ -83,18 +109,38 @@ public class ChargeServiceImpl implements ChargeService {
         // 请求设备认证
         var equipAuth = enPlusService.queryEquipAuth(connectorId, startChargeSeq);
 
+        // 组装订单数据
+        var order = new ChargeOrder();
+        order.setUserId(userId);
+        order.setStationId(stationId);
+        order.setStartChargeSeq(startChargeSeq);
+        order.setConnectorId(connectorId);
+        order.setOrderStatus(ChargeOrder.ORDER_STATUS_未知);
+
+        // 如果是预约订单,则将订单放入预约充电延迟队列
+        if (isBooking) {
+            order.setChargeStatus(ChargeOrder.CHARGE_STATUS_预约中);
+            chargeOrderService.save(order);
+            var flag = delayQueue.offer(new DelayedItem<ChargeOrder>(order, startTime));
+            if (flag) {
+                // 修改设备状态为预约中
+                equipmentInfoService.lambdaUpdate()
+                        .set(EquipmentInfo::getServiceStatus, EquipmentInfo.SERVICE_STATUS_预约中)
+                        .eq(EquipmentInfo::getEquipmentId, connectorId.substring(0, 16))
+                        .update();
+                return Map.of("startChargeSeq", startChargeSeq);
+            } else {
+                throw new BusinessException("充电预约失败");
+            }
+        }
+
+        chargeOrderService.save(order);
+
         if (equipAuth.containsKey("SuccStat") && equipAuth.getIntValue("SuccStat") == 0) {
-            // TODO 查询业务策略信息(计费信息),目前计费在EN+完成,后续自主计费需要开发
             // 启动充电
             var startCharge = enPlusService.queryStartCharge(startChargeSeq, connectorId, qrCode, amount);
             if (startCharge.containsKey("SuccStat") && startCharge.getIntValue("SuccStat") == 0) {
                 // 启动成功,生成充电订单
-                var order = new ChargeOrder();
-                order.setUserId(userId);
-                order.setStationId(stationId);
-                order.setStartChargeSeq(startChargeSeq);
-                order.setConnectorId(connectorId);
-                order.setOrderStatus(ChargeOrder.ORDER_STATUS_未知);
                 order.setChargeStatus(startCharge.getIntValue("StartChargeSeqStat"));
                 chargeOrderService.save(order);
                 return Map.of("startChargeSeq", startChargeSeq);

+ 93 - 0
service/src/main/java/com/kym/service/miniapp/impl/DelayServiceImpl.java

@@ -0,0 +1,93 @@
+package com.kym.service.miniapp.impl;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.kym.entity.miniapp.ChargeOrder;
+import com.kym.service.miniapp.ChargeOrderService;
+import com.kym.service.miniapp.ChargeService;
+import com.kym.service.miniapp.DelayService;
+import com.kym.service.queue.DelayedItem;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @author skyline
+ * @description
+ * @date 2023-10-08 22:11
+ */
+@Service
+@Slf4j
+@DS("db-miniapp")
+public class DelayServiceImpl implements DelayService<ChargeOrder> {
+
+    /**
+     * 预约订单队列
+     */
+    private final static DelayQueue<DelayedItem<ChargeOrder>> DELAY_QUEUE = new DelayQueue<>();
+
+    private final ChargeOrderService chargeOrderService;
+    private final ChargeService chargeService;
+    /**
+     * 线程池
+     */
+    private final ExecutorService executor = Executors.newFixedThreadPool(2);
+
+    public DelayServiceImpl(ChargeOrderService chargeOrderService, ChargeService chargeService) {
+        this.chargeOrderService = chargeOrderService;
+        this.chargeService = chargeService;
+    }
+
+    @DS("db-miniapp")
+    // 这里不能使用@PostConstruct,在初始化完成后, bean 进入增强阶段, 所以这个阶段的任何AOP都是无效的,https://www.cnblogs.com/eternityz/p/15330069.html
+    @EventListener
+    public void init(ContextRefreshedEvent event) {
+        // 队列加载所有充电状态为预约中的订单,按照开始时间排序
+        var orderList = chargeOrderService.lambdaQuery()
+                .eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中)
+                .orderByAsc(ChargeOrder::getStartTime)
+                .list();
+        DELAY_QUEUE.addAll(orderList.stream().map(order -> new DelayedItem<>(order, order.getStartTime())).toList());
+
+        // 开启线程处理队列消息
+        executor.execute(() -> {
+            log.info("启动预约充电订单处理线程:{}", Thread.currentThread().getName());
+            DelayedItem<ChargeOrder> delayedItem;
+            while (true) {
+                try {
+                    delayedItem = DELAY_QUEUE.take();
+                    // 启动充电
+//                    chargeService.queryStartCharge(delayedItem.data.getConnectorId())
+                } catch (InterruptedException e) {
+                    log.error("预约充电队列take异常", e);
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+
+    }
+
+
+    @Override
+    public boolean addToOrderDelayQueue(DelayedItem<ChargeOrder> delayedItem) {
+        return DELAY_QUEUE.add(delayedItem);
+    }
+
+    @Override
+    public boolean addToDelayQueue(ChargeOrder chargeOrder) {
+        DelayedItem<ChargeOrder> orderDelayed = new DelayedItem<>(chargeOrder, chargeOrder.getStartTime());
+        return DELAY_QUEUE.add(orderDelayed);
+    }
+
+    @Override
+    public void removeToOrderDelayQueue(ChargeOrder chargeOrder) {
+        if (chargeOrder == null) {
+            return;
+        }
+        DELAY_QUEUE.removeIf(queue -> queue.data.getStartChargeSeq().equals(chargeOrder.getStartChargeSeq()));
+    }
+}

+ 14 - 0
service/src/main/java/com/kym/service/queue/ChargeDelayConsumer.java

@@ -0,0 +1,14 @@
+package com.kym.service.queue;
+
+/**
+ * @author skyline
+ * @description
+ * @date 2023-10-08 19:20
+ */
+
+public class ChargeDelayConsumer implements Runnable{
+    @Override
+    public void run() {
+
+    }
+}

+ 41 - 0
service/src/main/java/com/kym/service/queue/DelayedItem.java

@@ -0,0 +1,41 @@
+package com.kym.service.queue;
+
+import jakarta.validation.constraints.NotNull;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author skyline
+ * @description 延迟执行
+ * @date 2023-10-08 21:43
+ */
+public class DelayedItem<T> implements Delayed {
+    /**
+     * 业务对象
+     */
+    public final T data;
+
+    /**
+     * 预约启动时间
+     */
+    public final LocalDateTime startTime;
+
+    public DelayedItem(T data, LocalDateTime startTime) {
+        this.data = data;
+        this.startTime = startTime;
+    }
+
+
+    @Override
+    public long getDelay(@NotNull TimeUnit unit) {
+        return unit.convert(Duration.between(startTime, LocalDateTime.now()));
+    }
+
+    @Override
+    public int compareTo(@NotNull Delayed o) {
+        return (int) (getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS));
+    }
+}

+ 4 - 0
service/src/main/java/com/kym/service/wechat/WxPayService.java

@@ -29,4 +29,8 @@ public interface WxPayService {
     void wxNotify(HttpServletRequest request) throws IOException;
 
     InvoiceBaseInfo baseInformation();
+
+    void applyInvoice(String invoiceId);
+
+    void wxInvoiceNotify(HttpServletRequest request);
 }

+ 74 - 10
service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

@@ -13,11 +13,10 @@ import com.kym.common.config.WxPayConfig;
 import com.kym.common.constant.ResponseEnum;
 import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.CommUtil;
-import com.kym.common.utils.HttpUtil;
 import com.kym.common.utils.LambadaTools;
 import com.kym.common.utils.OrderUtils;
-import com.kym.entity.miniapp.*;
 import com.kym.entity.miniapp.Account;
+import com.kym.entity.miniapp.*;
 import com.kym.entity.wechat.*;
 import com.kym.service.miniapp.*;
 import com.kym.service.wechat.WxPayService;
@@ -54,10 +53,8 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Stream;
 
 
 /**
@@ -557,9 +554,9 @@ public class WxPayServiceImpl implements WxPayService {
     /**
      * 申请发票
      */
-
+    @Override
     public void applyInvoice(String invoiceId) {
-        var invoice = invoiceService.lambdaQuery().eq(Invoice::getId,invoiceId).one();
+        var invoice = invoiceService.lambdaQuery().eq(Invoice::getId, invoiceId).one();
         // 电费 税务商品编码:1100101020000000000
         var elecMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
         var elecMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotalAmount(elecMoney).setTaxCode("1100101020000000000");
@@ -574,6 +571,7 @@ public class WxPayServiceImpl implements WxPayService {
 
         // 发票申请单号
         var fapiaoApplyId = OrderUtils.getOrderNo();
+        invoiceService.lambdaUpdate().set(Invoice::getApplyId, fapiaoApplyId).eq(Invoice::getId, invoiceId).update();
 
         // 客户开票信息
         var buyerInformation = new FaPiao.BuyerInformation()
@@ -587,9 +585,10 @@ public class WxPayServiceImpl implements WxPayService {
 
         // 发票信息
         var fapiaoInformation = new FaPiao.FaPiaoInfomation();
+        // fapiao_id为invoice.id
         fapiaoInformation.setFapiao_id(String.valueOf(invoice.getId()))
                 .setTotal_amount(invoice.getInvoiceAmount())
-                .setItems(List.of(elecMoneyItem,serviceMoneyItem));
+                .setItems(List.of(elecMoneyItem, serviceMoneyItem));
 
         // 请求参数
         var fapiao = new FaPiao()
@@ -599,7 +598,7 @@ public class WxPayServiceImpl implements WxPayService {
                 .setFapiao_information(List.of(fapiaoInformation));
 
         var requestBody = new JsonRequestBody.Builder().body(JSONObject.toJSONString(fapiao)).build();
-        wxHttpClient.post(headers, fapiaoConfig.getFapiaoApplications(), requestBody,null);
+        wxHttpClient.post(headers, fapiaoConfig.getFapiaoApplications(), requestBody, null);
     }
 
     /**
@@ -608,13 +607,20 @@ public class WxPayServiceImpl implements WxPayService {
      * @param request
      * @return
      */
-    void wxInvoiceNotify(HttpServletRequest request) {
+    @Override
+    public void wxInvoiceNotify(HttpServletRequest request) {
         var notifyRes = handleWxNotify(request);
         try {
             InvoiceNotification invoiceNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], InvoiceNotification.class);
             LOGGER.info("微信开具发票结果通知回调{}:验签解密完毕,数据:\n{}", notifyRes[2], invoiceNotification);
-            // TODO: 2023-09-17 业务逻辑
+            // 业务逻辑
+            if (invoiceNotification.getFapiaoInformation().get(0).getFapiaoStatus().equals(InvoiceNotification.FapiaoStatus.ISSUED)) {
+                // 更新发票状态
+                invoiceService.lambdaUpdate().set(Invoice::getStatus, Invoice.STATUS_已开票).eq(Invoice::getApplyId, invoiceNotification.getFapiaoApplyId()).update();
 
+            } else {
+                LOGGER.error("微信开具发票失败:{}", invoiceNotification);
+            }
         } catch (ValidationException e) {
             // 签名验证失败,返回 401 UNAUTHORIZED 状态码
             LOGGER.error("微信开具发票结果通知验签失败", e);
@@ -622,5 +628,63 @@ public class WxPayServiceImpl implements WxPayService {
         }
     }
 
+    /**
+     * 微信电子发票配置开发选项,例如回调地址、全部账单展示开发票入口开关
+     */
+    void devConfig() {
+        var headers = new HttpHeaders();
+        headers.addHeader("Accept", "application/json");
+        headers.addHeader("Content-Type", "application/json");
+
+        var params = """
+                {
+                  "callback_url" : %s,
+                  "show_fapiao_cell" : false
+                }
+                """.stripIndent().formatted(fapiaoConfig.getNotifyUrl());
+        var requestBody = new JsonRequestBody.Builder().body(params).build();
+        var res = wxHttpClient.patch(headers, fapiaoConfig.getDevConfig(), requestBody, JSONObject.class);
+        LOGGER.info("微信电子发票开发设置:{}", res.toString());
+        // TODO: 2023-10-07 业务逻辑
+    }
+
+    // TODO: 2023-10-07 下载发票
+    void downloadInvoice(String invoiceId) {
+        var invoice = invoiceService.getById(invoiceId);
+        // TODO: 2023-10-07 查询发票下载信息,下载链接30s有效
+        var download = getFapiaoDownloadInfo(invoice.getApplyId());
+        var downloadInfo = download.getFapiao_download_info_list().get(0);
+        var params = "?token=%s&mchid=%s&openid=%s&invoice_code=%s&invoice_no=%s&fapiao_id=%s";
+        var inputStream = wxHttpClient.download(downloadInfo.getDownload_url().concat(params.formatted("", conf.getMchid(), "", "", "", invoice.getId())));
+
+    }
+
+    /**
+     * 获取发票下载信息
+     *
+     * @param applyId
+     * @return
+     */
+    private FapiaoDownload getFapiaoDownloadInfo(String applyId) {
+        var downloadUrl = fapiaoConfig.getFapiaoFiles().formatted(applyId);
+        var headers = new HttpHeaders();
+        headers.addHeader("Accept", "application/json");
+        var res = wxHttpClient.get(headers, downloadUrl, FapiaoDownload.class);
+        return res.getServiceResponse();
+    }
+
+    /**
+     * 查询电子发票
+     *
+     * @param applyId
+     */
+    private FapiaoApplications queryFapiao(String applyId) {
+        var headers = new HttpHeaders();
+        headers.addHeader("Accept", "application/json");
+        headers.addHeader("Content-Type", "application/json");
+        var res = wxHttpClient.get(headers, fapiaoConfig.getQueryFapiao().formatted(applyId), FapiaoApplications.class);
+        return res.getServiceResponse();
+    }
+
 
 }