增加流水冲正功能
diff --git a/config/application-devel-pg.properties b/config/application-devel-pg.properties
index 7a4735f..daab12a 100644
--- a/config/application-devel-pg.properties
+++ b/config/application-devel-pg.properties
@@ -19,5 +19,5 @@
 jwt.expiration=3600
 auth.password.bcrypt.seed=
 spring.jackson.serialization.fail-on-empty-beans=false
-payapi.url=https://yy.dlsmk.cn/payapi
+payapi.url=http://localhost:8080/payapi
 
diff --git a/src/main/java/com/supwisdom/dlpay/framework/util/Constants.java b/src/main/java/com/supwisdom/dlpay/framework/util/Constants.java
index a8c7a22..85b75ac 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/util/Constants.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/util/Constants.java
@@ -17,4 +17,7 @@
   public final static String DEVICE_STATUS_CLOSED = "2";
   public final static String MONITOR_SUBAPP_WATER = "water";
   public final static int MONITOR_SUCCESS_CODE = 200;
+
+  public final static int YES = 1;
+  public final static int NO = 0;
 }
diff --git a/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java b/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java
index 92b9616..dc66622 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java
@@ -92,12 +92,17 @@
   public static final String PAYTYPE_RECHARGE_SERVICEFEE = "servicefee"; //收服务费
 
   /**
-   * reverse flag
+   * reverse type
    * - none : 无
-   * - cancel : 冲正
-   * - reverse : 手工撤销
+   * - refund : 冲正
    */
-  public static final String REVERSE_FLAG_NONE = "none";
-  public static final String REVERSE_FLAG_CANCEL = "cancel";
-  public static final String REVERSE_FLAG_REVERSE = "reverse";
+  public static final String REVERSE_TYPE_NONE = "none";
+  public static final String REVERSE_TYPE_REFUND = "refund";
+
+  /**
+   * payapi响应代码
+   */
+  public final static Integer PAYAPI_SUCCESS_RETCODE = 0;
+  public final static Integer PAYAPI_WAIT_QUERY = 55555;
+  public final static Integer PAYAPI_DEAL_ERROR = 30000;
 }
diff --git a/src/main/java/com/supwisdom/dlpay/water/bean/ReverseRequestBean.java b/src/main/java/com/supwisdom/dlpay/water/bean/ReverseRequestBean.java
new file mode 100644
index 0000000..234b752
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/water/bean/ReverseRequestBean.java
@@ -0,0 +1,22 @@
+package com.supwisdom.dlpay.water.bean;
+
+public class ReverseRequestBean {
+  private int cobillno;
+  private String reason;
+
+  public int getCobillno() {
+    return cobillno;
+  }
+
+  public void setCobillno(int cobillno) {
+    this.cobillno = cobillno;
+  }
+
+  public String getReason() {
+    return reason;
+  }
+
+  public void setReason(String reason) {
+    this.reason = reason;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/water/domain/TAccdtl.java b/src/main/java/com/supwisdom/dlpay/water/domain/TAccdtl.java
index 32c26d2..67bedae 100644
--- a/src/main/java/com/supwisdom/dlpay/water/domain/TAccdtl.java
+++ b/src/main/java/com/supwisdom/dlpay/water/domain/TAccdtl.java
@@ -56,6 +56,18 @@
     @Column(name = "refno", length = 32)
     private String refno;
 
+    @Column(name = "reversebillno", length = 32)
+    private String reversebillno;
+
+    @Column(name = "reversetype")
+    private String reversetype;
+
+    @Column(name = "reverseflag")
+    private Integer reverseflag;
+
+    @Column(name = "remark",length = 100)
+    private String remark;
+
     /**
      * 是否已更新用户入账金额
      */
@@ -182,4 +194,36 @@
     public void setUpdatedacc(Boolean updatedacc) {
         this.updatedacc = updatedacc;
     }
+
+    public String getReversebillno() {
+        return reversebillno;
+    }
+
+    public void setReversebillno(String reversebillno) {
+        this.reversebillno = reversebillno;
+    }
+
+    public String getReversetype() {
+        return reversetype;
+    }
+
+    public void setReversetype(String reversetype) {
+        this.reversetype = reversetype;
+    }
+
+    public Integer getReverseflag() {
+        return reverseflag;
+    }
+
+    public void setReverseflag(Integer reverseflag) {
+        this.reverseflag = reverseflag;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/supwisdom/dlpay/water/domain/TCollectdtl.java b/src/main/java/com/supwisdom/dlpay/water/domain/TCollectdtl.java
index adf0f38..834b260 100644
--- a/src/main/java/com/supwisdom/dlpay/water/domain/TCollectdtl.java
+++ b/src/main/java/com/supwisdom/dlpay/water/domain/TCollectdtl.java
@@ -76,6 +76,9 @@
   @Column(name = "nextcredittime",length = 14)
   private String nextCreditTime;
 
+  @Column(name = "reverseflag")
+  private Integer reverseflag;
+
   public String getCardPhyId() {
     return cardPhyId;
   }
@@ -227,4 +230,12 @@
   public void setActualamount(Double actualamount) {
     this.actualamount = actualamount;
   }
+
+  public Integer getReverseflag() {
+    return reverseflag;
+  }
+
+  public void setReverseflag(Integer reverseflag) {
+    this.reverseflag = reverseflag;
+  }
 }
diff --git a/src/main/java/com/supwisdom/dlpay/water/pay/WaterPayAsyncTask.java b/src/main/java/com/supwisdom/dlpay/water/pay/WaterPayAsyncTask.java
index 8d00a27..ef4d69d 100644
--- a/src/main/java/com/supwisdom/dlpay/water/pay/WaterPayAsyncTask.java
+++ b/src/main/java/com/supwisdom/dlpay/water/pay/WaterPayAsyncTask.java
@@ -180,6 +180,9 @@
         accdtl.setCardPhyId(collectdtl.getCardPhyId());
         accdtl.setTransTime(collectdtl.getTransTime());
         accdtl.setTransDate(collectdtl.getTransDate());
+        accdtl.setStatus(TradeDict.DTL_STATUS_INIT);
+        accdtl.setReverseflag(Constants.NO);
+        accdtl.setReversetype(TradeDict.REVERSE_TYPE_NONE);
         return accdtl;
     }
 
diff --git a/src/main/java/com/supwisdom/dlpay/water/pojo/TAccdtlDTO.java b/src/main/java/com/supwisdom/dlpay/water/pojo/TAccdtlDTO.java
index 9138845..3f68379 100644
--- a/src/main/java/com/supwisdom/dlpay/water/pojo/TAccdtlDTO.java
+++ b/src/main/java/com/supwisdom/dlpay/water/pojo/TAccdtlDTO.java
@@ -18,6 +18,9 @@
     private String cardphyid;
     private String accdate;
     private String refno;
+    private Integer reverseflag;
+    private String reversetype;
+    private String reversebillno;
 
     public String getTranstime() {
         return transtime;
@@ -154,4 +157,28 @@
     public void setActualamount(Double actualamount) {
         this.actualamount = actualamount;
     }
+
+    public Integer getReverseflag() {
+        return reverseflag;
+    }
+
+    public void setReverseflag(Integer reverseflag) {
+        this.reverseflag = reverseflag;
+    }
+
+    public String getReversetype() {
+        return reversetype;
+    }
+
+    public void setReversetype(String reversetype) {
+        this.reversetype = reversetype;
+    }
+
+    public String getReversebillno() {
+        return reversebillno;
+    }
+
+    public void setReversebillno(String reversebillno) {
+        this.reversebillno = reversebillno;
+    }
 }
diff --git a/src/main/java/com/supwisdom/dlpay/water/pojo/TCollectdtlDTO.java b/src/main/java/com/supwisdom/dlpay/water/pojo/TCollectdtlDTO.java
index f7bdb50..22c315f 100644
--- a/src/main/java/com/supwisdom/dlpay/water/pojo/TCollectdtlDTO.java
+++ b/src/main/java/com/supwisdom/dlpay/water/pojo/TCollectdtlDTO.java
@@ -40,6 +40,8 @@
     private String accdate;
     @Column
     private String entryno;
+    @Column
+    private Integer reverseflag;
 
     public String getEntryno() {
         return entryno;
@@ -168,4 +170,12 @@
     public void setActualamount(Double actualamount) {
         this.actualamount = actualamount;
     }
+
+    public Integer getReverseflag() {
+        return reverseflag;
+    }
+
+    public void setReverseflag(Integer reverseflag) {
+        this.reverseflag = reverseflag;
+    }
 }
diff --git a/src/main/java/com/supwisdom/dlpay/water/service/CollectdtlService.java b/src/main/java/com/supwisdom/dlpay/water/service/CollectdtlService.java
index 09b2e30..0fdfbcb 100644
--- a/src/main/java/com/supwisdom/dlpay/water/service/CollectdtlService.java
+++ b/src/main/java/com/supwisdom/dlpay/water/service/CollectdtlService.java
@@ -4,7 +4,9 @@
 import com.supwisdom.dlpay.framework.util.PageResult;
 import com.supwisdom.dlpay.water.StartWaterRequest;
 import com.supwisdom.dlpay.water.UploadRecordRequest;
+import com.supwisdom.dlpay.water.bean.ReverseRequestBean;
 import com.supwisdom.dlpay.water.bean.TransdtlCountSearchBean;
+import com.supwisdom.dlpay.water.domain.TAccdtl;
 import com.supwisdom.dlpay.water.domain.TCollectdtl;
 import com.supwisdom.dlpay.water.pojo.TCollectdtlDTO;
 import com.supwisdom.dlpay.water.bean.TransdtlSearchBean;
@@ -33,6 +35,9 @@
     @Transactional(rollbackFor = Exception.class)
     PageResult<TCollectdtlDTO> queryTransdtlDTOByParam(TransdtlSearchBean param);
 
+    @Transactional(rollbackFor = Exception.class)
+    TAccdtl reverseCollectDtl(ReverseRequestBean bean);
+
     @Transactional(rollbackFor = Exception.class,readOnly = true)
     PageResult<TTransdtlCountVO> queryTransdtlCountByParam(TransdtlCountSearchBean param);
 
diff --git a/src/main/java/com/supwisdom/dlpay/water/service/impl/AccdtlServiceImpl.java b/src/main/java/com/supwisdom/dlpay/water/service/impl/AccdtlServiceImpl.java
index 3e6d042..516ca11 100644
--- a/src/main/java/com/supwisdom/dlpay/water/service/impl/AccdtlServiceImpl.java
+++ b/src/main/java/com/supwisdom/dlpay/water/service/impl/AccdtlServiceImpl.java
@@ -121,7 +121,7 @@
 
     @Override
     public PageResult<TAccdtlDTO> queryAccdtlByParam(TransdtlSearchBean param) {
-        StringBuffer querySql = new StringBuffer("select t1.billno,t1.amount,actualamount,t1.citizencardno,t1.cardphyid,t1.deviceno,t1.mode,t1.status,t1.transdate,t1.refno,t1.transtime,t1.accdate,t1.water_in_100ml watersumhundredlitre,t1.name username,t2.devicename,t2.areaname,t2.areano " +
+        StringBuffer querySql = new StringBuffer("select t1.billno,t1.amount,actualamount,t1.citizencardno,t1.cardphyid,t1.deviceno,t1.mode,t1.status,t1.transdate,t1.refno,t1.transtime,t1.accdate,t1.water_in_100ml watersumhundredlitre,t1.name username,t1.reversetype,t1.reverseflag,t1.reversebillno,t2.devicename,t2.areaname,t2.areano " +
                 "from (select dtl.*,person.name from tb_accentrydtl dtl left join tb_person person on dtl.userid = person.userid) t1," +
                 "(select device.deviceno,device.devicename,area.areaname,area.areano from tb_device device,tb_area area where device.areano = area.areano) t2 " +
                 "where t1.deviceno = t2.deviceno");
diff --git a/src/main/kotlin/com/supwisdom/dlpay/water/controller/api_controller.kt b/src/main/kotlin/com/supwisdom/dlpay/water/controller/api_controller.kt
index 4d940f9..4d6b2f8 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/water/controller/api_controller.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/water/controller/api_controller.kt
@@ -171,6 +171,7 @@
                 status = TradeDict.DTL_STATUS_INIT
                 authStatus = true
                 uploadStatus = false
+                reverseflag = Constants.NO
             }
             val savedTrans = collectdtlService.createNewTransdtl(trans)
             //4. 将流水 cobillno 和费率信息返回给终端
@@ -205,6 +206,7 @@
                 status = TradeDict.DTL_STATUS_INIT
                 authStatus = false
                 uploadStatus = false
+                reverseflag = Constants.NO
             }
             val savedTrans = collectdtlService.createNewTransdtl(trans)
             //2.将流水号及认证地址返回给终端
diff --git a/src/main/kotlin/com/supwisdom/dlpay/water/controller/water_controller.kt b/src/main/kotlin/com/supwisdom/dlpay/water/controller/water_controller.kt
index 09c06ca..a0c9716 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/water/controller/water_controller.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/water/controller/water_controller.kt
@@ -1,6 +1,7 @@
 package com.supwisdom.dlpay.water.controller
 
 import com.supwisdom.dlpay.api.bean.JsonResult
+import com.supwisdom.dlpay.exception.TransactionProcessException
 import com.supwisdom.dlpay.framework.ResponseBodyBuilder
 import com.supwisdom.dlpay.framework.util.*
 import com.supwisdom.dlpay.system.service.UserDataService
@@ -10,11 +11,13 @@
 import com.supwisdom.dlpay.water.domain.*
 import com.supwisdom.dlpay.water.pojo.*
 import com.supwisdom.dlpay.water.service.*
+import mu.KotlinLogging
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.http.ResponseEntity
 import org.springframework.stereotype.Controller
 import org.springframework.ui.Model
 import org.springframework.web.bind.annotation.*
+import java.lang.reflect.UndeclaredThrowableException
 
 @Controller
 class RegionController {
@@ -359,9 +362,14 @@
     @Autowired
     private lateinit var collectdtlService: CollectdtlService
 
+    private val logger = KotlinLogging.logger { }
+
     @GetMapping("/collectdtl/index")
     fun dtlIndexView() = "system/collectdtl/index"
 
+    @GetMapping("/collectdtl/reverse")
+    fun loadReverse() = "system/collectdtl/reverse"
+
     @GetMapping("/collectdtl/list")
     @ResponseBody
     fun queryTransdtl(@RequestParam("devicename", required = false) devicename: String,
@@ -407,6 +415,32 @@
         }
     }
 
+    @PostMapping("/collectdtl/reverse")
+    @ResponseBody
+    fun reverseCollectDtl(@RequestBody bean: ReverseRequestBean): ResponseEntity<Any> {
+        try {
+            if (bean.cobillno == 0 || bean.reason.isNullOrEmpty()) {
+                return ResponseEntity.ok(ResponseBodyBuilder.create()
+                        .fail(TradeErrorCode.INPUT_DATA_ERROR, "请求参数不能为空"))
+            }
+            val refundAccdtl = collectdtlService.reverseCollectDtl(bean)
+            if (refundAccdtl.status == TradeDict.DTL_STATUS_SUCCESS) {
+                return ResponseEntity.ok(ResponseBodyBuilder.create()
+                        .success("冲正成功"))
+            }
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(WaterErrorCode.PROCESS_ERROR,"冲正失败,请稍后重试"))
+        } catch (ex: Exception) {
+            logger.error("系统异常",ex)
+            if (ex is UndeclaredThrowableException) {
+                return ResponseEntity.ok(ResponseBodyBuilder.create()
+                        .exception(WaterErrorCode.PROCESS_ERROR, ex, ex.undeclaredThrowable.message))
+            }
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .exception(WaterErrorCode.PROCESS_ERROR, ex, "服务器异常,请稍后再试"))
+        }
+    }
+
     @GetMapping("/dtlcount/index")
     fun dtlCountIndexView() = "system/dtlcount/index"
 
diff --git a/src/main/kotlin/com/supwisdom/dlpay/water/service/collectdtl_service.kt b/src/main/kotlin/com/supwisdom/dlpay/water/service/collectdtl_service.kt
index c5c4f2b..b10d7ad 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/water/service/collectdtl_service.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/water/service/collectdtl_service.kt
@@ -1,19 +1,25 @@
 package com.supwisdom.dlpay.water.service
 
+import com.supwisdom.dlpay.api.bean.ConsumePayCancelParam
+import com.supwisdom.dlpay.api.bean.QueryDtlResultParam
 import com.supwisdom.dlpay.api.bean.UserInforResponse
 import com.supwisdom.dlpay.exception.TransactionProcessException
 import com.supwisdom.dlpay.framework.service.BusinessparaService
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.*
+import com.supwisdom.dlpay.paysdk.proxy.TransactionProxy
 import com.supwisdom.dlpay.water.StartWaterRequest
 import com.supwisdom.dlpay.water.UploadRecordRequest
+import com.supwisdom.dlpay.water.bean.ReverseRequestBean
 import com.supwisdom.dlpay.water.bean.TransdtlCountSearchBean
 import com.supwisdom.dlpay.water.dao.CollectdtlDao
 import com.supwisdom.dlpay.water.domain.TCollectdtl
 import com.supwisdom.dlpay.water.pojo.TCollectdtlDTO
 import com.supwisdom.dlpay.water.bean.TransdtlSearchBean
+import com.supwisdom.dlpay.water.dao.AccdtlDao
 import com.supwisdom.dlpay.water.dao.DtlCountDateDao
 import com.supwisdom.dlpay.water.dao.TransdtlCountDao
+import com.supwisdom.dlpay.water.domain.TAccdtl
 import com.supwisdom.dlpay.water.domain.TTransdtlCount
 import com.supwisdom.dlpay.water.domain.TdtlCountDate
 import com.supwisdom.dlpay.water.pojo.TTransdtlCountDTO
@@ -40,6 +46,9 @@
     private lateinit var collectdtlDao: CollectdtlDao
 
     @Autowired
+    private lateinit var accdtlDao: AccdtlDao
+
+    @Autowired
     private lateinit var em: EntityManager
 
     @Autowired
@@ -57,6 +66,9 @@
     @Autowired
     private lateinit var systemUtilService: SystemUtilService
 
+    @Autowired
+    private lateinit var transactionProxy: TransactionProxy
+
 
     override fun createNewTransdtl(dtl: TCollectdtl): TCollectdtl {
         collectdtlDao.save(dtl)
@@ -76,7 +88,7 @@
                 dtl.lastCredit = dateTime.hostdate + "0" // 0代表已尝试入账次数
                 dtl.nextCreditTime = dateTime.hostdatetime
                 dtl.status = TradeDict.DTL_STATUS_WIP
-            }else{
+            } else {
                 dtl.status = TradeDict.DTL_STATUS_SUCCESS
             }
         }
@@ -119,7 +131,7 @@
     }
 
     override fun queryTransdtlDTOByParam(param: TransdtlSearchBean): PageResult<TCollectdtlDTO>? {
-        val sql = StringBuffer("select t1.cobillno,t1.amount,t1.actualamount,t1.citizencardno,t1.cardphyid,t1.deviceno,t1.mode,t1.status,t1.transdate,t1.entryno,t1.transtime,t1.accdate,t1.water_in_100ml water_sum_hundred_litre,t1.name username,t2.devicename,t2.areaname,t2.areano  " +
+        val sql = StringBuffer("select t1.cobillno,t1.amount,t1.actualamount,t1.citizencardno,t1.cardphyid,t1.deviceno,t1.mode,t1.status,t1.transdate,t1.entryno,t1.transtime,t1.accdate,t1.water_in_100ml water_sum_hundred_litre,t1.name username,t1.reverseflag,t2.devicename,t2.areaname,t2.areano  " +
                 "from (select dtl.*,person.name from tb_collectdtl dtl left join tb_person person on dtl.userid = person.userid) t1," +
                 "(select device.deviceno,device.devicename,area.areaname,area.areano from tb_device device,tb_area area where device.areano = area.areano) t2 " +
                 "where t1.deviceno = t2.deviceno")
@@ -217,6 +229,104 @@
         return result
     }
 
+    override fun reverseCollectDtl(bean: ReverseRequestBean?): TAccdtl {
+        val collectdtl = collectdtlDao.findByCobillnoForUpdate(bean!!.cobillno)
+                ?: throw TransactionProcessException(WaterErrorCode.DATA_NOTFOUND_ERROR,
+                        "采集流水号不存在")
+        if (collectdtl.status != TradeDict.DTL_STATUS_SUCCESS || collectdtl.reverseflag == Constants.YES) {
+            throw TransactionProcessException(WaterErrorCode.DATA_NOTFOUND_ERROR,
+                    "采集流水状态异常,无法冲正")
+        }
+        if (collectdtl.actualamount == 0.0) {
+            throw TransactionProcessException(WaterErrorCode.DATA_NOTFOUND_ERROR,
+                    "采集流水实际扣费金额为0,无法冲正")
+        }
+        val accdtl = accdtlDao.findByBillno(collectdtl.entryno)
+                ?: throw TransactionProcessException(WaterErrorCode.DATA_NOTFOUND_ERROR,
+                        "入账流水未找到")
+        if (accdtl.status != TradeDict.DTL_STATUS_SUCCESS
+                || accdtl.reverseflag == Constants.YES
+                || accdtl.reversetype == TradeDict.REVERSE_TYPE_REFUND) {
+            throw TransactionProcessException(WaterErrorCode.DATA_NOTFOUND_ERROR,
+                    "入账流水状态异常")
+        }
+        // 创建一条新的冲正流水
+        val datetime = systemUtilService.sysdatetime
+        val refundAccdtl = TAccdtl().apply {
+            this.billno = systemUtilService.refno
+            this.amount = -accdtl.amount
+            this.cardPhyId = accdtl.cardPhyId
+            this.citizenCardno = accdtl.citizenCardno
+            this.status = TradeDict.DTL_STATUS_INIT
+            this.transDate = datetime.hostdate
+            this.transTime = datetime.hosttime
+            this.userid = accdtl.userid
+            this.actualamount = -accdtl.actualamount
+            this.reversebillno = accdtl.billno
+            this.reversetype = TradeDict.REVERSE_TYPE_REFUND
+            this.remark = bean.reason
+            this.mode = accdtl.mode
+            this.deviceno = accdtl.deviceno
+            this.reverseflag = Constants.NO
+        }
+        accdtlDao.save(refundAccdtl)
+        val payCancel = transactionProxy.payCancel(ConsumePayCancelParam().apply {
+            this.refno = accdtl.refno
+            this.requestbillno = refundAccdtl.billno
+            this.transdate = datetime.hostdate
+            this.transtime = datetime.hosttime
+        })
+        when (payCancel.retcode) {
+            TradeDict.PAYAPI_SUCCESS_RETCODE -> {
+                refundAccdtl.status = TradeDict.DTL_STATUS_SUCCESS
+                refundAccdtl.refno = payCancel.refno
+                refundAccdtl.accdate = datetime.hostdate
+                accdtlDao.save(refundAccdtl)
+                // 修改采集流水和入账流水的冲正状态
+                collectdtl.reverseflag = Constants.YES
+                collectdtlDao.save(collectdtl)
+
+                accdtl.reverseflag = Constants.YES
+                return accdtlDao.save(accdtl)
+            }
+            TradeDict.PAYAPI_WAIT_QUERY -> {
+                val queryResult = transactionProxy.queryDtlResult(QueryDtlResultParam().apply {
+                    this.refno = payCancel.refno
+                })
+                when (queryResult.retcode) {
+                    TradeDict.PAYAPI_SUCCESS_RETCODE -> {
+                        refundAccdtl.status = TradeDict.DTL_STATUS_SUCCESS
+                        refundAccdtl.refno = payCancel.refno
+                        refundAccdtl.accdate = datetime.hostdate
+                        accdtlDao.save(refundAccdtl)
+                        // 修改采集流水和入账流水的冲正状态
+                        collectdtl.reverseflag = Constants.YES
+                        collectdtlDao.save(collectdtl)
+
+                        accdtl.reverseflag = Constants.YES
+                        return accdtlDao.save(accdtl)
+                    }
+                    TradeDict.PAYAPI_DEAL_ERROR -> {
+                        logger.error("向payapi退费失败:[${queryResult.retmsg}]")
+                    }
+                    else -> {
+                        logger.error("未知的退费状态码:${queryResult.retcode},${queryResult.retmsg}")
+                    }
+                }
+                refundAccdtl.status = TradeDict.DTL_STATUS_FAIL
+                return accdtlDao.save(refundAccdtl)
+            }
+            TradeDict.PAYAPI_DEAL_ERROR -> {
+                logger.error("向payapi退费失败:[${payCancel.retmsg}]")
+                refundAccdtl.status = TradeDict.DTL_STATUS_FAIL
+                return accdtlDao.save(refundAccdtl)
+            }
+        }
+        logger.error("未知的退费状态码:${payCancel.retcode},${payCancel.retmsg}")
+        refundAccdtl.status = TradeDict.DTL_STATUS_FAIL
+        return accdtlDao.save(refundAccdtl)
+    }
+
     override fun queryTransdtlCountByParam(param: TransdtlCountSearchBean): PageResult<TTransdtlCountVO> {
         val querySql = StringBuffer("select t1.*,t2.devicename,t2.areaname from (select * from tb_transdtl_count) t1 left join " +
                 "(select device.deviceno,device.devicename,area.areaname from tb_device device left join tb_area area on device.areano = area.areano) t2 " +
@@ -270,11 +380,12 @@
         val dateTime = systemUtilsService.sysdatetime
         val date = dateTime.hostdate
         val time = dateTime.hostdatetime
-        return collectdtlDao.findWipCollectdtl(date + count,time,PageRequest.of(0, 10))
+        return collectdtlDao.findWipCollectdtl(date + count, time, PageRequest.of(0, 10))
     }
 
     override fun findByCobillnoForUpdate(cobillno: Int?): TCollectdtl {
-        return collectdtlDao.findByCobillnoForUpdate(cobillno) ?: throw RuntimeException("采集流水号:" + cobillno + "未找到")
+        return collectdtlDao.findByCobillnoForUpdate(cobillno)
+                ?: throw RuntimeException("采集流水号:" + cobillno + "未找到")
     }
 
     override fun generateCountdtl() {
@@ -288,14 +399,14 @@
         var accdate = dtlcountDate.countdate
         val querySql = "select t3.deviceno,t3.areano,coalesce(t4.accdate,:accdate) accdate,t4.mode,coalesce(t4.amount,0) amount,coalesce(t4.actualamount,0) actualamount,coalesce(t4.water,0) water,coalesce(t4.count,0) " +
                 "count from tb_device t3 LEFT JOIN (select t1.deviceno,t1.accdate,t1.mode,t1.amount,t1.actualamount,t1.water,t1.count,t2.areano from (select deviceno,accdate,mode,sum(cast(amount as DECIMAL(18,3))) amount,sum(cast(actualamount as DECIMAL(18,2))) actualamount,sum(water_in_100ml) water,count(cobillno) count " +
-                "from tb_collectdtl where accdate=:accdate and status = 'success' group by deviceno,mode,accdate) t1 LEFT JOIN " +
+                "from tb_collectdtl where accdate=:accdate and status = 'success' and (reverseflag <>1 or reverseflag ISNULL) group by deviceno,mode,accdate) t1 LEFT JOIN " +
                 "(select deviceno,areano from tb_device) t2 on t1.deviceno=t2.deviceno) t4 on t3.deviceno=t4.deviceno"
         for (i in 1..count) {
             if (accdate.toInt() >= currentDate.toInt()) {
                 logger.error { "统计流水日期<$accdate>应小于当前日期<$currentDate>" }
                 return
             }
-            if (transdtlCountDao.countByAccdate(accdate)>0) {
+            if (transdtlCountDao.countByAccdate(accdate) > 0) {
                 logger.error { accdate + "的统计流水已生成!" }
                 return
             }
@@ -319,7 +430,7 @@
             transdtlCountDao.saveAll(list)
             accdate = nextDay(accdate)
         }
-        dtlcountDate.countdate=accdate
+        dtlcountDate.countdate = accdate
         dtlCountDateDao.save(dtlcountDate)
     }
 
@@ -352,15 +463,15 @@
         }
     }
 
-    override fun getTodayAmount(userid:String): Double? {
+    override fun getTodayAmount(userid: String): Double? {
         val sql = "select sum(amount) from tb_collectdtl where status in ('wip','success') and userid=:userid and transdate=:date"
         val query = em.createNativeQuery(sql)
-        query.setParameter("userid",userid)
-        query.setParameter("date",systemUtilsService.sysdatetime.hostdate)
+        query.setParameter("userid", userid)
+        query.setParameter("date", systemUtilsService.sysdatetime.hostdate)
         return query.singleResult as Double?
     }
 
-    override fun saveCollectDtl(dtl: TCollectdtl): TCollectdtl?{
+    override fun saveCollectDtl(dtl: TCollectdtl): TCollectdtl? {
         return collectdtlDao.save(dtl)
     }
 }
\ No newline at end of file
diff --git a/src/main/resources/templates/system/accdtl/index.html b/src/main/resources/templates/system/accdtl/index.html
index ef8d6fc..f5a091a 100644
--- a/src/main/resources/templates/system/accdtl/index.html
+++ b/src/main/resources/templates/system/accdtl/index.html
@@ -122,47 +122,35 @@
             page: true,
             cols: [
                 [
-                    {field: 'billno', align: 'center', title: '流水号'},
-                    {field: 'deviceno', align: 'center', title: '设备号'},
-                    {field: 'devicename', align: 'center', title: '设备名称'},
-                    {field: 'areaname', align: 'center', title: '区域'},
-                    {field: 'watersumhundredlitre', align: 'center', title: '用水量'},
-                    {field: 'amount', align: 'center', title: '金额'},
-                    {field: 'actualamount', align: 'center', title: '实际扣费金额'},
-                    {field: 'username', align: 'center', title: '姓名'},
-                    {field: 'citizencardno', align: 'center', title: '卡号'},
-                    {field: 'cardphyid', align: 'center', title: '物理卡号'},
-                    {field: 'accdate', align: 'center', title: '入账日期',
-                    templet:function (item) {
-                        return formatDate(item.accdate)
-                    }},
-                    {field: 'refno', align: 'center', title: '支付中心流水'},
+                    {field: 'billno', align: 'center', title: '流水号', width: 200},
+                    {field: 'username', align: 'center', title: '姓名', width: 90},
+                    {field: 'citizencardno', align: 'center', title: '卡号', width: 95},
                     {
                         field: 'transdate',
                         align: 'center',
                         title: '采集时间',
+                        width: 105,
                         templet: function (item) {
                             return formatDate(item.transdate)
                         }
                     },
                     {
-                        field: 'transtype',
+                        field: 'reversetype',
                         align: 'center',
-                        title: '支付方式',
+                        title: '流水类型',
+                        width: 90,
                         templet: function (item) {
-                            if (item.mode === 'qrcode') {
-                                return '扫码'
-                            } else if (item.mode === 'card') {
-                                return '市民卡'
-                            } else {
-                                return item.mode
+                            if (item.reversetype === 'refund') {
+                                return '冲正流水'
                             }
+                            return '消费流水'
                         }
                     },
                     {
                         field: 'transStatus',
                         align: 'center',
                         title: '状态',
+                        width: 90,
                         templet: function (item) {
                             if (item.status === 'init') {
                                 return '初始化'
@@ -173,7 +161,47 @@
                             } else if (item.status === 'success') {
                                 return '成功';
                             } else {
-                                return item.status
+                                return '-'
+                            }
+                        }
+                    },
+                    {field: 'amount', align: 'center', title: '金额', width: 80},
+                    {field: 'actualamount', align: 'center', title: '实际扣费金额', width: 120},
+                    {
+                        field: 'reverseflag',
+                        align: 'center',
+                        title: '冲正状态',
+                        width: 90,
+                        templet: function (item) {
+                            if (item.reverseflag === 1) {
+                                return '已冲正'
+                            }
+                            return '-'
+                        }
+                    },
+                    {field: 'accdate', align: 'center', title: '入账日期', width: 105,
+                        templet:function (item) {
+                            return formatDate(item.accdate)
+                        }},
+                    {field: 'refno', align: 'center', title: '支付中心流水', width: 200},
+                    {field: 'reversebillno', align: 'center', title: '被退款流水号', width: 200},
+                    {field: 'deviceno', align: 'center', title: '设备号', width: 95},
+                    {field: 'devicename', align: 'center', title: '设备名称', width: 200},
+                    {field: 'areaname', align: 'center', title: '区域', width: 200},
+                    {field: 'watersumhundredlitre', align: 'center', title: '用水量', width: 80},
+                    {field: 'cardphyid', align: 'center', title: '物理卡号', width: 100},
+                    {
+                        field: 'transtype',
+                        align: 'center',
+                        title: '支付方式',
+                        width: 90,
+                        templet: function (item) {
+                            if (item.mode === 'qrcode') {
+                                return '扫码'
+                            } else if (item.mode === 'card') {
+                                return '市民卡'
+                            } else {
+                                return item.mode
                             }
                         }
                     }
diff --git a/src/main/resources/templates/system/collectdtl/index.html b/src/main/resources/templates/system/collectdtl/index.html
index a82dc66..d4c8ba1 100644
--- a/src/main/resources/templates/system/collectdtl/index.html
+++ b/src/main/resources/templates/system/collectdtl/index.html
@@ -114,7 +114,7 @@
             }
         });
         // 渲染表格
-        table.render({
+        let collectDtlTable = table.render({
             elem: '#watercollectdtltable',
             url: '[[@{/collectdtl/list}]]',
             where: {
@@ -129,48 +129,23 @@
             page: true,
             cols: [
                 [
-                    {field: 'cobillno', align: 'center', title: '流水号'},
-                    {field: 'deviceno', align: 'center', title: '设备号'},
-                    {field: 'devicename', align: 'center', title: '设备名称'},
-                    {field: 'areaname', align: 'center', title: '区域'},
-                    {field: 'waterSumHundredLitre', align: 'center', title: '用水量'},
-                    {field: 'amount', align: 'center', title: '金额'},
-                    {field: 'actualamount', align: 'center', title: '实际扣费金额'},
-                    {field: 'username', align: 'center', title: '姓名'},
-                    {field: 'citizencardno', align: 'center', title: '卡号'},
-                    {field: 'cardphyid', align: 'center', title: '物理卡号'},
-                    {field: 'accdate', align: 'center', title: '入账日期',
-                    templet:function (item) {
-                        return formatDate(item.accdate)
-                    }
-                    },
-                    {field: 'entryno', align: 'center', title: '入账流水号'},
+                    {field: 'cobillno', align: 'center', title: '流水号', width: 80},
+                    {field: 'username', align: 'center', title: '姓名', width: 90},
+                    {field: 'citizencardno', align: 'center', title: '卡号', width: 95},
                     {
                         field: 'transdate',
                         align: 'center',
                         title: '采集时间',
+                        width: 105,
                         templet: function (item) {
                             return formatDate(item.transdate)
                         }
                     },
                     {
-                        field: 'transtype',
-                        align: 'center',
-                        title: '支付方式',
-                        templet: function (item) {
-                            if (item.mode === 'qrcode') {
-                                return '扫码'
-                            } else if (item.mode === 'card') {
-                                return '市民卡'
-                            } else {
-                                return item.mode
-                            }
-                        }
-                    },
-                    {
                         field: 'transStatus',
                         align: 'center',
                         title: '状态',
+                        width: 90,
                         templet: function (item) {
                             if (item.status === 'init') {
                                 return '初始化'
@@ -184,11 +159,86 @@
                                 return item.status
                             }
                         }
+                    },
+                    {
+                        field: 'transtype',
+                        align: 'center',
+                        title: '支付方式',
+                        width: 90,
+                        templet: function (item) {
+                            if (item.mode === 'qrcode') {
+                                return '扫码'
+                            } else if (item.mode === 'card') {
+                                return '市民卡'
+                            } else {
+                                return item.mode
+                            }
+                        }
+                    },
+                    {field: 'amount', align: 'center', title: '金额', width: 80},
+                    {field: 'actualamount', align: 'center', title: '实际扣费金额', width: 120},
+                    {
+                        field: 'reverseflag',
+                        align: 'center',
+                        title: '冲正状态',
+                        width: 90,
+                        templet: function (item) {
+                            if (item.reverseflag === 1) {
+                                return '已冲正'
+                            }
+                            return '-'
+                        }
+                    },
+                    {field: 'deviceno', align: 'center', title: '设备号', width: 95},
+                    {field: 'devicename', align: 'center', title: '设备名称', width: 200},
+                    {field: 'areaname', align: 'center', title: '区域', width: 200},
+                    {field: 'waterSumHundredLitre', align: 'center', title: '用水量', width: 80},
+                    {field: 'cardphyid', align: 'center', title: '物理卡号', width: 100},
+                    {field: 'entryno', align: 'center', title: '入账流水号', width: 200},
+                    {field: 'accdate', align: 'center', title: '入账日期', width: 105,
+                    templet:function (item) {
+                        return formatDate(item.accdate)
+                    }
+                    },
+                    {
+                        field: 'oper',
+                        fixed: 'right',
+                        align: 'center',
+                        title: '操作',
+                        width: 85,
+                        templet: function (item) {
+                            var html;
+                            if (item.status === 'success' && item.reverseflag !== 1 && item.actualamount !== 0) {
+                                html = ' <a class="layui-btn  layui-btn-xs" lay-event="reverse"><i class="layui-icon layui-icon-edit"></i>冲正</a> ';
+                            } else {
+                                html = ' <a class="layui-btn  layui-btn-xs layui-btn-disabled"><i class="layui-icon layui-icon-edit"></i>冲正</a> ';
+                            }
+                            return html;
+                        }
                     }
                 ]
             ]
         });
-
+        table.on('tool(watercollectdtltable)', function (obj) {
+            let data = obj.data;
+            let layEvent = obj.event;
+            if (layEvent === 'reverse') {
+                showReverse(data);
+            }
+        });
+        let showReverse = function (data) {
+            if (data) {
+                admin.putTempData('t_func', data);
+            }
+            admin.popupCenter({
+                title: "冲正流水",
+                path: '[[@{/collectdtl/reverse}]]',
+                //  重新渲染表格
+                finish: function () {
+                    searchCollectDtl(collectDtlTable.config.page.curr);
+                }
+            });
+        };
         //  未入账开关选择
         var notacc;
         form.on('switch(watercollectdtl-notacc-switch)', function (data) {
@@ -205,6 +255,9 @@
         });
         // 搜索按钮点击事件
         $('#btn-search-watercollectdtl').click(function () {
+            searchCollectDtl(1)
+        });
+        function searchCollectDtl(page) {
             var devicename = $('#watercollectdtl-devicename-search-value').val().trim();
             var deviceno = $('#watercollectdtl-deviceno-search-value').val().trim();
             var areano = $('#watercollectdtl-select-region').val();
@@ -223,13 +276,13 @@
                 },
                 url: "[[@{/collectdtl/list}]]",
                 page: {
-                    curr: 1
+                    curr: page
                 },
                 text: {
                     none: '没有符合查询条件的流水'
                 }
             });
-        });
+        }
     });
     function formatDate(date){
         if (date == null) {
diff --git a/src/main/resources/templates/system/collectdtl/reverse.html b/src/main/resources/templates/system/collectdtl/reverse.html
new file mode 100644
index 0000000..bb10d47
--- /dev/null
+++ b/src/main/resources/templates/system/collectdtl/reverse.html
@@ -0,0 +1,92 @@
+<!-- operator表单弹窗 -->
+<form id="form" lay-filter="form" class="layui-form model-form">
+    <div class="layui-form-item">
+        <label class="layui-form-label">冲正流水</label>
+        <div class="layui-input-block">
+            <input name="cobillno" id="cobillno" readonly="readonly" type="text" class="layui-input"/>
+        </div>
+    </div>
+    <div class="layui-form-item">
+        <label class="layui-form-label">冲正对象</label>
+        <div class="layui-input-block">
+            <input name="username" id="username" readonly="readonly" type="text" class="layui-input"/>
+        </div>
+    </div>
+    <div class="layui-form-item">
+        <label class="layui-form-label">冲正金额</label>
+        <div class="layui-input-block">
+            <input name="actualamount" id="actualamount" readonly="readonly" type="text" class="layui-input"/>
+        </div>
+    </div>
+    <div class="layui-form-item">
+        <label class="layui-form-label">冲正原因</label>
+        <div class="layui-input-block">
+            <textarea name="reverse-reason" id="reverse-reason" maxlength="100"
+                      placeholder="请输入冲正原因" class="layui-textarea" lay-verify="required" required></textarea>
+        </div>
+    </div>
+    <div class="layui-form-item model-form-footer">
+        <button class="layui-btn layui-btn-primary" type="button" ew-event="closeDialog">取消</button>
+        <button class="layui-btn" lay-filter="form-submit" lay-submit id="submitbtn">保存</button>
+    </div>
+</form>
+
+<script>
+    layui.use(['layer', 'admin', 'form'], function () {
+        var layer = layui.layer;
+        var admin = layui.admin;
+        var form = layui.form;
+        var reverse_form_url = '[[@{/collectdtl/reverse}]]';
+        // 回显user数据
+        var reverse_form_func = admin.getTempData('t_func');
+        //  清除admin域中的数据
+        admin.putTempData('t_func', '');
+        if (reverse_form_func) {
+            form.val('form', reverse_form_func);
+        }
+
+        // 表单提交事件
+        form.on('submit(form-submit)', function (data) {
+            layer.load(2);
+            var param = {
+                "cobillno": $('input[id="cobillno"]').val(),
+                "reason": $('textarea[id="reverse-reason"]').val()
+            };
+            let token = $("meta[name='_csrf_token']").attr("value");
+            $.ajax({
+                type: "POST",
+                dataType: "json",
+                url: reverse_form_url,
+                headers: {
+                    'Accept': 'application/json;charset=UTF-8',
+                    'Content-Type': 'application/json;charset=UTF-8',
+                    'X-CSRF-TOKEN': token,
+                },
+                data: JSON.stringify(param),
+                success: function (result) {
+                    console.log(result);
+                    layer.closeAll('loading');
+
+                    if (result.retcode == 0) {
+                        layer.msg(result.retmsg, {icon: 1});
+                        admin.finishPopupCenter();
+                    } else if (result.retcode == 401) {
+                        layer.msg(result.msg, {icon: 2, time: 1500}, function () {
+                            location.replace('[[@{/login}]]');
+                        }, 1000);
+                        return;
+                    } else {
+                        console.log('err:' + result.retcode);
+                        layer.msg(result.retmsg, {icon: 2});
+                    }
+                },
+                error: function () {
+                    layer.closeAll('loading');
+                    layer.msg("请求服务器失败!", {icon: 2});
+                }
+            });
+            return false;
+        });
+    });
+
+</script>
\ No newline at end of file